From 97a6d8c75f40fd7eaf7646c131e65023fb3f01ec Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Fri, 10 Jul 2026 23:58:23 +0900 Subject: [PATCH] tls: fix local-exec addresses and external symbols Preserve symbolic offsets when materializing i386 TLS addresses, generate ARM64 TLS addresses from the thread pointer, and carry RISC-V addends on both halves of the TPREL relocation pair. Keep undefined ELF TLS symbols typed as STT_TLS so objects produced by TCC can be linked by the system linker. Extend the TLS regression coverage for narrow signed and unsigned accesses, address-taking, runtime indexing, thread isolation, large RISC-V offsets, unsupported runtimes, and external TLS definitions. --- arm64-gen.c | 71 +++++++++++++++------------- i386-gen.c | 2 +- riscv64-gen.c | 4 +- tccelf.c | 6 ++- tests/tests2/144_tls.c | 76 ++++++++++++++++++++++++++++-- tests/tests2/144_tls.expect | 1 + tests/tests2/146+_tls_extern.c | 2 + tests/tests2/146_tls_extern.c | 22 +++++++++ tests/tests2/146_tls_extern.expect | 1 + tests/tests2/Makefile | 28 +++++++++-- 10 files changed, 165 insertions(+), 48 deletions(-) create mode 100644 tests/tests2/146+_tls_extern.c create mode 100644 tests/tests2/146_tls_extern.c create mode 100644 tests/tests2/146_tls_extern.expect diff --git a/arm64-gen.c b/arm64-gen.c index f5a81231..6e6e4784 100644 --- a/arm64-gen.c +++ b/arm64-gen.c @@ -468,20 +468,8 @@ static void arm64_strv(int sz_, int dst, int bas, uint64_t off) } } -static void arm64_sym(int r, Sym *sym, unsigned long addend) +static void arm64_add_offset(int r, unsigned long addend) { -#ifdef TCC_TARGET_PE - /* PE links symbol addresses directly; there is no ELF-style GOT here. */ - greloca(cur_text_section, sym, ind, R_AARCH64_ADR_PREL_PG_HI21, 0); - o(ARM64_ADRP | r); // adrp xr, #sym - greloca(cur_text_section, sym, ind, R_AARCH64_ADD_ABS_LO12_NC, 0); - o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(r) | r); // add xr, xr, #sym -#else - greloca(cur_text_section, sym, ind, R_AARCH64_ADR_GOT_PAGE, 0); - o(ARM64_ADRP | r); // adrp xr, #sym - greloca(cur_text_section, sym, ind, R_AARCH64_LD64_GOT_LO12_NC, 0); - o(ARM64_LDR_X | ARM64_RN(r) | r); // ld xr,[xr, #sym] -#endif if (addend) { // add xr, xr, #addend if (addend & 0xffful) @@ -504,17 +492,35 @@ static void arm64_sym(int r, Sym *sym, unsigned long addend) } } +static void arm64_sym(int r, Sym *sym, unsigned long addend) +{ +#ifdef TCC_TARGET_PE + /* PE links symbol addresses directly; there is no ELF-style GOT here. */ + greloca(cur_text_section, sym, ind, R_AARCH64_ADR_PREL_PG_HI21, 0); + o(ARM64_ADRP | r); // adrp xr, #sym + greloca(cur_text_section, sym, ind, R_AARCH64_ADD_ABS_LO12_NC, 0); + o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(r) | r); // add xr, xr, #sym +#else + greloca(cur_text_section, sym, ind, R_AARCH64_ADR_GOT_PAGE, 0); + o(ARM64_ADRP | r); // adrp xr, #sym + greloca(cur_text_section, sym, ind, R_AARCH64_LD64_GOT_LO12_NC, 0); + o(ARM64_LDR_X | ARM64_RN(r) | r); // ld xr,[xr, #sym] +#endif + arm64_add_offset(r, addend); +} + static void arm64_load_cmp(int r, SValue *sv); -static void arm64_tls_x30(SValue *sv) +static void arm64_tls_sym(int r, Sym *sym, unsigned long addend) { - o(0xd53bd05e); /* mrs x30, tpidr_el0 */ - greloca(cur_text_section, sv->sym, ind, R_AARCH64_TLSLE_ADD_TPREL_HI12, 0); - /* add x30, x30, #0, lsl #12 */ - o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_SH(1) | ARM64_RN(30) | ARM64_RD(30)); - greloca(cur_text_section, sv->sym, ind, R_AARCH64_TLSLE_ADD_TPREL_LO12, 0); - /* add x30, x30, #0 */ - o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(30) | ARM64_RD(30)); + o(0xd53bd040 | r); /* mrs xr, tpidr_el0 */ + greloca(cur_text_section, sym, ind, R_AARCH64_TLSLE_ADD_TPREL_HI12, 0); + /* add xr, xr, #0, lsl #12 */ + o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_SH(1) | ARM64_RN(r) | ARM64_RD(r)); + greloca(cur_text_section, sym, ind, R_AARCH64_TLSLE_ADD_TPREL_LO12, 0); + /* add xr, xr, #0 */ + o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(r) | ARM64_RD(r)); + arm64_add_offset(r, addend); } ST_FUNC void load(int r, SValue *sv) @@ -537,7 +543,9 @@ ST_FUNC void load(int r, SValue *sv) if (svr == (VT_CONST | VT_LVAL)) { uint64_t i = sv->c.i; - if (sv->sym) + if (sv->sym && (sv->sym->type.t & VT_TLS)) + arm64_tls_sym(30, sv->sym, 0); + else if (sv->sym) arm64_sym(30, sv->sym, // use x30 for address arm64_check_offset(0, arm64_type_size(svtt), i)); else @@ -564,7 +572,7 @@ ST_FUNC void load(int r, SValue *sv) if (svr == (VT_CONST | VT_LVAL | VT_SYM)) { if (sv->sym->type.t & VT_TLS) - arm64_tls_x30(sv); + arm64_tls_sym(30, sv->sym, 0); else arm64_sym(30, sv->sym, // use x30 for address arm64_check_offset(0, arm64_type_size(svtt), svcoff)); @@ -578,7 +586,10 @@ ST_FUNC void load(int r, SValue *sv) } if (svr == (VT_CONST | VT_SYM)) { - arm64_sym(intr(r), sv->sym, svcul); + if (sv->sym->type.t & VT_TLS) + arm64_tls_sym(intr(r), sv->sym, svcul); + else + arm64_sym(intr(r), sv->sym, svcul); return; } @@ -659,7 +670,7 @@ ST_FUNC void store(int r, SValue *sv) if (svr == (VT_CONST | VT_LVAL)) { uint64_t i = sv->c.i; if (sv->sym && (sv->sym->type.t & VT_TLS)) - arm64_tls_x30(sv); + arm64_tls_sym(30, sv->sym, 0); else if (sv->sym) arm64_sym(30, sv->sym, // use x30 for address @@ -686,15 +697,7 @@ ST_FUNC void store(int r, SValue *sv) if (svr == (VT_CONST | VT_LVAL | VT_SYM)) { if (sv->sym->type.t & VT_TLS) { - o(0xd53bd05e); /* mrs x30, tpidr_el0 */ - greloca(cur_text_section, sv->sym, ind, - R_AARCH64_TLSLE_ADD_TPREL_HI12, 0); - o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_SH(1) | - ARM64_RN(30) | ARM64_RD(30)); /* add x30, x30, #0, lsl #12 */ - greloca(cur_text_section, sv->sym, ind, - R_AARCH64_TLSLE_ADD_TPREL_LO12, 0); - o(ARM64_ADD_IMM | ARM64_SF(1) | - ARM64_RN(30) | ARM64_RD(30)); /* add x30, x30, #0 */ + arm64_tls_sym(30, sv->sym, 0); if (IS_FREG(r)) arm64_strv(arm64_type_size(svtt), fltr(r), 30, svcoff); else diff --git a/i386-gen.c b/i386-gen.c index dbf28323..54d24806 100644 --- a/i386-gen.c +++ b/i386-gen.c @@ -354,7 +354,7 @@ ST_FUNC void load(int r, SValue *sv) } else { if ((fr & VT_SYM) && (sv->sym->type.t & VT_TLS)) { oad(0x058b65 | REG_VALUE(r) << 19, 0); /* mov gs:0,r */ - oad(0xC081 | REG_VALUE(r) << 8, 0); /* add tpoffs,r */ + oad(0xC081 | REG_VALUE(r) << 8, fc); /* add tpoffs,r */ greloc(cur_text_section, sv->sym, ind - 4, R_386_TLS_LE); #if defined CONFIG_TCC_PIC } else if ((fr & (VT_VALMASK|VT_SYM)) == (VT_CONST|VT_SYM)) { diff --git a/riscv64-gen.c b/riscv64-gen.c index 0efd836a..110097be 100644 --- a/riscv64-gen.c +++ b/riscv64-gen.c @@ -186,10 +186,8 @@ static int load_symofs(int r, SValue *sv, int forstore, int *new_fc) R_RISCV_TPREL_HI20, sv->c.i); o(0x37 | (rr << 7)); // lui RR, 0 %tprel_hi(sym) greloca(cur_text_section, sv->sym, ind, - R_RISCV_TPREL_LO12_I, 0); + R_RISCV_TPREL_LO12_I, sv->c.i); EI(0x13, 0, rr, rr, 0); // addi RR, RR, 0 %tprel_lo(sym) - if (fc) - EI(0x13, 0, rr, rr, fc); // addi RR, RR, fc ER(0x33, 0, rr, rr, 4, 0); // add RR, RR, tp *new_fc = 0; return rr; diff --git a/tccelf.c b/tccelf.c index a61758c8..7e9758af 100644 --- a/tccelf.c +++ b/tccelf.c @@ -205,8 +205,10 @@ ST_FUNC void tccelf_end_file(TCCState *s1) #ifndef TCC_TARGET_PE if (sym_bind == STB_GLOBAL && s1->output_type == TCC_OUTPUT_OBJ) { /* undefined symbols with STT_FUNC are confusing gnu ld when - linking statically to STT_GNU_IFUNC */ - sym_type = STT_NOTYPE; + linking statically to STT_GNU_IFUNC. Keep TLS typed, + otherwise gnu ld rejects non-TLS refs to TLS definitions. */ + if (sym_type != STT_TLS) + sym_type = STT_NOTYPE; } #endif sym->st_info = ELFW(ST_INFO)(sym_bind, sym_type); diff --git a/tests/tests2/144_tls.c b/tests/tests2/144_tls.c index 6fe659b3..82c844ce 100644 --- a/tests/tests2/144_tls.c +++ b/tests/tests2/144_tls.c @@ -6,6 +6,11 @@ typedef enum { tls_a, tls_b, tls_c } tls_enum_type; typedef struct { int tls_d, tls_e, tls_f; } tls_struct_type; +typedef struct { + int *init; + signed short *element; +} tls_address_type; + __thread char tls_char = 42; __thread short tls_short = 43; __thread int tls_init = 44; @@ -17,6 +22,41 @@ __thread long double tls_long_double = 48.0; __thread int *tls_ptr = (int *)49; __thread tls_enum_type tls_enum = tls_b; __thread tls_struct_type tls_struct = { 50, 51, 52 }; +__thread signed char tls_signed_char = -5; +__thread unsigned char tls_unsigned_char = 250; +__thread signed short tls_signed_short = -1234; +__thread unsigned short tls_unsigned_short = 60000; +__thread signed short tls_array[4] = { -1000, 2000, -3000, 4000 }; + +#if defined __riscv && __riscv_xlen == 64 +typedef struct { + char padding[4096]; + int value; +} tls_large_type; + +__thread tls_large_type tls_large = { { 0 }, 123456 }; +#endif + +static int check_extra(int changed) +{ + volatile int index = 2; + + if (changed) + return tls_signed_char != -7 || tls_unsigned_char != 42 + || tls_signed_short != -2222 || tls_unsigned_short != 12345 + || tls_array[1] != 2222 || tls_array[index] != -3333; + + if (tls_signed_char != -5 || tls_unsigned_char != 250 + || tls_signed_short != -1234 || tls_unsigned_short != 60000 + || tls_array[1] != 2000 || tls_array[index] != -3000) + return 1; + +#if defined __riscv && __riscv_xlen == 64 + if (tls_large.value != 123456) + return 1; +#endif + return 0; +} static int check(void) { @@ -40,9 +80,18 @@ static int check(void) static void *thread_func(void *arg) { - (void)arg; + tls_address_type *main_addresses = arg; + int *init_address = &tls_init; + signed short *element_address = &tls_array[1]; + volatile int index = 2; long errors = check(); + if (init_address == main_addresses->init + || element_address == main_addresses->element + || *init_address != 44 || *element_address != 2000 + || tls_array[index] != -3000 || check_extra(0)) + errors = 1; + tls_char = 10; tls_short = 20; tls_init = 30; @@ -56,6 +105,12 @@ static void *thread_func(void *arg) tls_struct.tls_d = 100; tls_struct.tls_e = 110; tls_struct.tls_f = 120; + *element_address = 2222; + tls_array[index] = -3333; + tls_signed_char = -7; + tls_unsigned_char = 42; + tls_signed_short = -2222; + tls_unsigned_short = 12345; CHECK(tls_char, "%d", 10); CHECK(tls_short, "%d", 20); @@ -71,6 +126,10 @@ static void *thread_func(void *arg) CHECK(tls_struct.tls_e, "%d", 110); CHECK(tls_struct.tls_f, "%d", 120); + if (*init_address != 30 || *element_address != 2222 + || tls_array[index] != -3333 || check_extra(1)) + errors = 1; + return (void *)errors; } @@ -79,9 +138,20 @@ int main() pthread_t t; void *ret; int errors = check(); + tls_address_type addresses = { &tls_init, &tls_array[1] }; - pthread_create(&t, NULL, thread_func, NULL); + if (*addresses.init != 44 || *addresses.element != 2000 + || check_extra(0)) + errors = 1; + + pthread_create(&t, NULL, thread_func, &addresses); pthread_join(t, &ret); - return (ret ? 1 : 0) | errors | check(); + errors |= (ret ? 1 : 0) | check(); + if (*addresses.init != 44 || *addresses.element != 2000 + || check_extra(0)) + errors = 1; + + printf("errors: %d\n", errors); + return errors; } diff --git a/tests/tests2/144_tls.expect b/tests/tests2/144_tls.expect index 250a449a..5e883f57 100644 --- a/tests/tests2/144_tls.expect +++ b/tests/tests2/144_tls.expect @@ -50,3 +50,4 @@ 50 51 52 +errors: 0 diff --git a/tests/tests2/146+_tls_extern.c b/tests/tests2/146+_tls_extern.c new file mode 100644 index 00000000..eecd69be --- /dev/null +++ b/tests/tests2/146+_tls_extern.c @@ -0,0 +1,2 @@ +__thread signed short extern_tls_short = 1234; +__thread float extern_tls_float = 1.5f; diff --git a/tests/tests2/146_tls_extern.c b/tests/tests2/146_tls_extern.c new file mode 100644 index 00000000..cfae9fda --- /dev/null +++ b/tests/tests2/146_tls_extern.c @@ -0,0 +1,22 @@ +#include + +extern __thread signed short extern_tls_short; +extern __thread float extern_tls_float; + +int main(void) +{ + int errors = 0; + + if (extern_tls_short != 1234 || extern_tls_float != 1.5f) + errors = 1; + + extern_tls_short = -2222; + extern_tls_float = 2.5f; + extern_tls_float += 1.0f; + + if (extern_tls_short != -2222 || extern_tls_float != 3.5f) + errors = 1; + + printf("extern tls errors: %d\n", errors); + return errors; +} diff --git a/tests/tests2/146_tls_extern.expect b/tests/tests2/146_tls_extern.expect new file mode 100644 index 00000000..8305aa16 --- /dev/null +++ b/tests/tests2/146_tls_extern.expect @@ -0,0 +1 @@ +extern tls errors: 0 diff --git a/tests/tests2/Makefile b/tests/tests2/Makefile index a272004e..63a183c9 100644 --- a/tests/tests2/Makefile +++ b/tests/tests2/Makefile @@ -15,6 +15,9 @@ endif ifeq (,$(filter i386,$(ARCH))) SKIP += 98_al_ax_extend.test 99_fastcall.test endif +ifeq (,$(filter x86_64 riscv64 arm64 i386,$(ARCH))) + SKIP += 146_tls_extern.test # TLS only implemented on these architectures +endif ifeq (,$(filter i386 x86_64,$(ARCH))) SKIP += 85_asm-outside-function.test # x86 asm SKIP += 127_asm_goto.test # hardcodes x86 asm @@ -39,6 +42,9 @@ endif ifeq (-$(findstring gcc,$(CC))-,--) SKIP += $(patsubst %.expect,%.test,$(GEN-ALWAYS)) endif +ifeq ($(CC_NAME),tcc) + SKIP += 146_tls_extern.test # needs an external system linker +endif ifeq (-$(CONFIG_WIN32)-$(CONFIG_i386)$(CONFIG_arm)-,--yes-) SKIP += 95_bitfields%.test # type_align is different on 32bit-non-windows endif @@ -47,19 +53,22 @@ ifeq (-$(CONFIG_WIN32)-,-yes-) SKIP += 114_bound_signal.test # No pthread support SKIP += 117_builtins.test # win32 port doesn't define __builtins SKIP += 124_atomic_counter.test # No pthread support - SKIP += 144_tls.test # No pthread support + SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported endif ifneq (,$(filter OpenBSD FreeBSD NetBSD,$(TARGETOS))) SKIP += 106_versym.test # no pthread_condattr_setpshared SKIP += 114_bound_signal.test # libc problem signal/fork SKIP += 116_bound_setjmp2.test # No TLS_FUNC/TLS_VAR in bcheck.c - SKIP += 144_tls.test # No tls support + SKIP += 144_tls.test 146_tls_extern.test # No tls support +endif +ifeq ($(TARGETOS),DragonFly) + SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported endif ifeq ($(CONFIG_OSX),yes) - SKIP += 144_tls.test # TLS runtime not supported on Mach-O + SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported on Mach-O endif ifeq (-$(ARCH)-,-arm-) - SKIP += 144_tls.test # doesn't work currently + SKIP += 144_tls.test 146_tls_extern.test # doesn't work currently endif ifneq (-$(ARCH)-,-arm64-) SKIP += 138_arm64_encoding.test @@ -122,6 +131,15 @@ GEN-ALWAYS = 144_tls.test: FLAGS += -pthread 144_tls.test: NORUN = true +# Verify that undefined TLS symbols in TCC objects work with the system linker. +146_tls_extern.test: NORUN = true +146_tls_extern.test: T1 = \ + $(TCC) -c $1 -o 146_tls_extern-main.o && \ + $(TCC) -c $(subst 146,146+,$1) -o 146_tls_extern-def.o && \ + $(CC) -no-pie -Wl,-z,noexecstack 146_tls_extern-main.o \ + 146_tls_extern-def.o -o $(basename $@).exe && \ + ./$(basename $@).exe + 112_backtrace.test: FLAGS += -dt -b 112_backtrace.test 113_btdll.test 126_bound_global.test: FILTER += \ -e 's;[0-9A-Fa-fx]\{5,\};........;g' \ @@ -204,4 +222,4 @@ $(sort $(GEN-ALWAYS) $(UPDATE)) : force force: clean : - rm -f fred.txt *.output *.exe *.dll *.so *.def $(GEN-ALWAYS) + rm -f fred.txt *.output *.o *.exe *.dll *.so *.def $(GEN-ALWAYS)