Two real bugs found testing v3 on hardware, both fixed. Repeated
relocations against the same TLS symbol were only marked handled on
the descriptor's attr, not the original symbol's, so a second access
built a duplicate. Building descriptors inside check_relocs() also
corrupted the symbol table, since create_symtab() sorts/snapshots it
first - fixed with a pre-pass before create_symtab(). 144_tls.test is
un-skipped for OSX+arm64 and passes; 146_tls_extern.test stays skipped
there - unrelated to this fix, `tcc -c` doesn't emit Mach-O objects
yet on this target, so the system linker has nothing to consume.
Verified on macOS/arm64 and (regression) a full native Linux build.
---
arm64-gen.c | 21 ++++++++-
arm64-link.c | 8 ++++
tcc.h | 6 +++
tccmacho.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++++-
tests/tests2/Makefile | 21 ++++++++-
5 files changed, 171 insertions(+), 3 deletions(-)

diff --git a/arm64-gen.c b/arm64-gen.c
index 85aa7c0..9f87e19 100644
--- a/arm64-gen.c
+++ b/arm64-gen.c
@@ -466,7 +466,26 @@ static void arm64_strv(int sz_, int dst, int bas, uint64_t 
off)
static void arm64_sym(int r, Sym *sym, addr_t addend)
{
if (sym->type.t & VT_TLS) {
-#if TCC_TARGET_PE
+#ifdef TCC_TARGET_MACHO
+ int i;
+ /* blr below may clobber x0-x17: save_regs() can't protect store()'s frozen 
raw r. */
+ save_regs(0);
+ o(ARM64_SUB_IMM | ARM64_SF(1) | ARM64_RN(31) | ARM64_RD(31) | 
ARM64_IMM12(160)); // sub sp, sp, #160
+ for (i = 0; i < 18; i += 2)
+ o(ARM64_STP_X | ARM64_RT(i) | ARM64_RT2(i + 1) | ARM64_RN(31) | 
ARM64_IMM7(i)); // stp xi, xi+1, [sp, #i*8]
+ greloca(cur_text_section, sym, ind, R_AARCH64_ADR_GOT_PAGE, 0);
+ o(ARM64_ADRP | 0); // adrp x0, &descriptor@page
+ greloca(cur_text_section, sym, ind, R_AARCH64_LD64_GOT_LO12_NC, 0);
+ o(ARM64_LDR_X | ARM64_RN(0) | 0); // ldr x0, [x0, &descriptor@pageoff]
+ o(ARM64_LDR_X | ARM64_RN(0) | 8); // ldr x8, [x0] ; x8 = thunk
+ o(ARM64_BLR | ARM64_RN(8)); // blr x8 ; x0 = real address
+ o(ARM64_STR_X | ARM64_RN(31) | ARM64_IMM12(144 / 8) | 0); // str x0, [sp, 
#144]
+ for (i = 0; i < 18; i += 2)
+ o(ARM64_LDP_X | ARM64_RT(i) | ARM64_RT2(i + 1) | ARM64_RN(31) | 
ARM64_IMM7(i)); // ldp xi, xi+1, [sp, #i*8]
+ o(ARM64_LDR_X | ARM64_RN(31) | ARM64_IMM12(144 / 8) | r); // ldr xr, [sp, 
#144]
+ o(ARM64_ADD_IMM | ARM64_SF(1) | ARM64_RN(31) | ARM64_RD(31) | 
ARM64_IMM12(160)); // add sp, sp, #160
+ goto add_addend;
+#elif TCC_TARGET_PE
Sym *s2 = external_global_sym(TOK___tls_index, &int_type);
int r2 = get_reg(RC_INT);
arm64_sym(30, s2, 0);
diff --git a/arm64-link.c b/arm64-link.c
index d3d925d..6da7294 100644
--- a/arm64-link.c
+++ b/arm64-link.c
@@ -45,6 +45,7 @@ ST_FUNC int code_reloc (int reloc_type)
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
+ case R_AARCH64_TLS_DTPREL64:
case R_AARCH64_GLOB_DAT:
case R_AARCH64_COPY:
return 0;
@@ -84,6 +85,7 @@ ST_FUNC int gotplt_entry_type (int reloc_type)
case R_AARCH64_TSTBR14:
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
+ case R_AARCH64_TLS_DTPREL64:
return NO_GOTPLT_ENTRY;

case R_AARCH64_ABS32:
@@ -202,6 +204,12 @@ ST_FUNC void relocate(TCCState *s1, ElfW_Rel *rel, int 
type, unsigned char *ptr,
}
add64le(ptr, val);
return;
+#ifdef TCC_TARGET_MACHO
+ case R_AARCH64_TLS_DTPREL64:
+ /* Mach-O TLV descriptor offset field: relative to arm64_tlv_base, not 
absolute. */
+ write64le(ptr, val - s1->arm64_tlv_base);
+ return;
+#endif
case R_AARCH64_ABS32:
if (s1->output_type & TCC_OUTPUT_DYN) {
/* XXX: this logic may depend on TCC's codegen
diff --git a/tcc.h b/tcc.h
index ce9b518..75c7ecd 100644
--- a/tcc.h
+++ b/tcc.h
@@ -730,6 +730,9 @@ struct sym_attr {
#ifdef TCC_TARGET_ARM
unsigned char plt_thumb_stub:1;
#endif
+#if defined(TCC_TARGET_ARM64) && defined(TCC_TARGET_MACHO)
+ int tlv_sym;
+#endif
};

struct TCCState {
@@ -909,6 +912,9 @@ struct TCCState {
Section *dynsym;
/* got & plt handling */
Section *got, *plt;
+#if defined(TCC_TARGET_ARM64)
+ addr_t arm64_tlv_base;
+#endif
/* exception handling */
Section *eh_frame_section;
Section *eh_frame_hdr_section;
diff --git a/tccmacho.c b/tccmacho.c
index cee09fd..dff6909 100644
--- a/tccmacho.c
+++ b/tccmacho.c
@@ -45,6 +45,7 @@
#define MH_DYLDLINK (0x4)
#define MH_DYLIB (0x6)
#define MH_PIE (0x200000)
+#define MH_HAS_TLV_DESCRIPTORS (0x800000)

#define CPU_SUBTYPE_LIB64 (0x80000000)
#define CPU_SUBTYPE_X86_ALL (3)
@@ -230,6 +231,9 @@ struct dyld_chained_ptr_64_bind
#define S_SYMBOL_STUBS 0x8
#define S_MOD_INIT_FUNC_POINTERS 0x9
#define S_MOD_TERM_FUNC_POINTERS 0xa
+#define S_THREAD_LOCAL_REGULAR 0x11
+#define S_THREAD_LOCAL_ZEROFILL 0x12
+#define S_THREAD_LOCAL_VARIABLES 0x13

#define S_ATTR_PURE_INSTRUCTIONS 0x80000000
#define S_ATTR_SOME_INSTRUCTIONS 0x00000400
@@ -400,6 +404,9 @@ enum skind {
sk_fini,
sk_rw_data,
sk_bss,
+ sk_thread_vars,
+ sk_thread_data,
+ sk_thread_bss,
sk_linkedit,
sk_last
};
@@ -648,6 +655,62 @@ static void tcc_macho_add_destructor(TCCState *s1)
add_array (s1, ".init_array", init_sym);
}

+#if defined(TCC_TARGET_ARM64)
+static int arm64_macho_tlv_desc(TCCState *s1, int sym_index)
+{
+ Section *tv;
+ int boot_index, desc_index;
+ addr_t addr;
+
+ desc_index = get_sym_attr(s1, sym_index, 1)->tlv_sym;
+ if (desc_index)
+ return desc_index;
+
+ tv = find_section(s1, ".thread_vars");
+ tv->sh_flags = SHF_ALLOC | SHF_WRITE;
+ tv->sh_type = SHT_PROGBITS;
+
+ boot_index = find_elf_sym(s1->symtab, "__tlv_bootstrap");
+ if (!boot_index)
+ boot_index = put_elf_sym(s1->symtab, 0, 0,
+ ELFW(ST_INFO)(STB_GLOBAL, STT_FUNC), 0, SHN_UNDEF, "__tlv_bootstrap");
+
+ addr = section_add(tv, 3 * PTR_SIZE, PTR_SIZE);
+ put_elf_reloca(s1->symtab, tv, addr, R_DATA_PTR, boot_index, 0);
+ put_elf_reloca(s1->symtab, tv, addr + 2 * PTR_SIZE, R_AARCH64_TLS_DTPREL64, 
sym_index, 0);
+
+ desc_index = put_elf_sym(s1->symtab, addr, PTR_SIZE,
+ ELFW(ST_INFO)(STB_LOCAL, STT_OBJECT), 0, tv->sh_num, NULL);
+ get_sym_attr(s1, sym_index, 1)->tlv_sym = desc_index;
+ return desc_index;
+}
+
+/* Must run before create_symtab(), which sorts/snapshots the symbol table. */
+static void arm64_macho_prepare_tlv(TCCState *s1)
+{
+ Section *s;
+ ElfW_Rel *rel;
+ int i, sym_index, type, gotplt_entry;
+ ElfW(Sym) *sym;
+
+ for (i = 1; i < s1->nb_sections; i++) {
+ s = s1->sections[i];
+ if (s->sh_type != SHT_RELX ||
+ !strncmp(s1->sections[s->sh_info]->name, ".debug_", 7))
+ continue;
+ for_each_elem(s, 0, rel, ElfW_Rel) {
+ type = ELFW(R_TYPE)(rel->r_info);
+ gotplt_entry = gotplt_entry_type(type);
+ sym_index = ELFW(R_SYM)(rel->r_info);
+ sym = &((ElfW(Sym) *)symtab_section->data)[sym_index];
+ if ((sym->st_shndx == SHN_UNDEF || gotplt_entry == ALWAYS_GOTPLT_ENTRY)
+ && ELFW(ST_TYPE)(sym->st_info) == STT_TLS)
+ arm64_macho_tlv_desc(s1, sym_index);
+ }
+ }
+}
+#endif
+
#ifdef CONFIG_NEW_MACHO
static void bind_rebase_add(struct macho *mo, int bind, int sh_info,
ElfW_Rel *rel, struct sym_attr *attr)
@@ -693,9 +756,25 @@ static void check_relocs(TCCState *s1, struct macho *mo)
|| gotplt_entry == ALWAYS_GOTPLT_ENTRY) {
attr = get_sym_attr(s1, sym_index, 1);
if (!attr->dyn_index) {
+#if defined(TCC_TARGET_ARM64)
+ int orig_index = sym_index;
+ if (ELFW(ST_TYPE)(sym->st_info) == STT_TLS) {
+ sym_index = arm64_macho_tlv_desc(s1, sym_index);
+ sym = &((ElfW(Sym) *)symtab_section->data)[sym_index];
+ attr = get_sym_attr(s1, sym_index, 1);
+ }
+#endif
attr->got_offset = s1->got->data_offset;
attr->plt_offset = -1;
attr->dyn_index = 1; /* used as flag */
+#if defined(TCC_TARGET_ARM64)
+ /* relocate() looks up got_offset via the original symbol, not the 
descriptor. */
+ if (sym_index != orig_index) {
+ struct sym_attr *oattr = get_sym_attr(s1, orig_index, 1);
+ oattr->got_offset = attr->got_offset;
+ oattr->dyn_index = 1;
+ }
+#endif
section_ptr_add(s1->got, PTR_SIZE);
put_elf_reloc(s1->symtab, s1->got, attr->got_offset,
R_JMP_SLOT, sym_index);
@@ -850,9 +929,24 @@ static void check_relocs(TCCState *s1, struct macho *mo)
|| gotplt_entry == ALWAYS_GOTPLT_ENTRY) {
attr = get_sym_attr(s1, sym_index, 1);
if (!attr->dyn_index) {
+#if defined(TCC_TARGET_ARM64)
+ int orig_index = sym_index;
+ if (ELFW(ST_TYPE)(sym->st_info) == STT_TLS) {
+ sym_index = arm64_macho_tlv_desc(s1, sym_index);
+ sym = &((ElfW(Sym) *)symtab_section->data)[sym_index];
+ attr = get_sym_attr(s1, sym_index, 1);
+ }
+#endif
attr->got_offset = s1->got->data_offset;
attr->plt_offset = -1;
attr->dyn_index = 1; /* used as flag */
+#if defined(TCC_TARGET_ARM64)
+ if (sym_index != orig_index) {
+ struct sym_attr *oattr = get_sym_attr(s1, orig_index, 1);
+ oattr->got_offset = attr->got_offset;
+ oattr->dyn_index = 1;
+ }
+#endif
section_ptr_add(s1->got, PTR_SIZE);
put_elf_reloc(s1->symtab, s1->got, attr->got_offset,
R_JMP_SLOT, sym_index);
@@ -1247,6 +1341,9 @@ const struct {
/*[sk_fini] =*/ { 4, S_MOD_TERM_FUNC_POINTERS, "__mod_term_func" },
/*[sk_rw_data] =*/ { 4, S_REGULAR, "__data" },
/*[sk_bss] =*/ { 4, S_ZEROFILL, "__bss" },
+ /*[sk_thread_vars] =*/ { 4, S_THREAD_LOCAL_VARIABLES, "__thread_vars" },
+ /*[sk_thread_data] =*/ { 4, S_THREAD_LOCAL_REGULAR, "__thread_data" },
+ /*[sk_thread_bss] =*/ { 4, S_THREAD_LOCAL_ZEROFILL, "__thread_bss" },
/*[sk_linkedit] =*/ { 5, S_REGULAR, NULL },
};

@@ -1649,7 +1746,7 @@ static void collect_sections(TCCState *s1, struct macho 
*mo, const char *filenam
default: sk = sk_unknown; break;
case SHT_INIT_ARRAY: sk = sk_init; break;
case SHT_FINI_ARRAY: sk = sk_fini; break;
- case SHT_NOBITS: sk = sk_bss; break;
+ case SHT_NOBITS: sk = (flags & SHF_TLS) ? sk_thread_bss : sk_bss; break;
case SHT_SYMTAB: sk = sk_discard; break;
case SHT_STRTAB:
if (s == stabstr_section)
@@ -1686,6 +1783,10 @@ static void collect_sections(TCCState *s1, struct macho 
*mo, const char *filenam
sk = sk_debug_str;
else if (s == dwarf_line_str_section)
sk = sk_debug_line_str;
+ else if (!strcmp(s->name, ".thread_vars"))
+ sk = sk_thread_vars;
+ else if (flags & SHF_TLS)
+ sk = sk_thread_data;
else if (flags & SHF_EXECINSTR)
sk = sk_text;
else if (flags & SHF_WRITE)
@@ -1756,8 +1857,14 @@ static void collect_sections(TCCState *s1, struct macho 
*mo, const char *filenam

dyldbv = add_lc(mo, LC_BUILD_VERSION, sizeof(*dyldbv));
dyldbv->platform = PLATFORM_MACOS;
+#ifdef TCC_TARGET_ARM64
+ /* dyld silently skips TLV thunk patching below minos 11.0 or without this 
flag. */
+ dyldbv->minos = (11 << 16);
+ dyldbv->sdk = (11 << 16);
+#else
dyldbv->minos = (10 << 16) + (6 << 8);
dyldbv->sdk = (10 << 16) + (6 << 8);
+#endif
dyldbv->ntools = 0;

dyldsv = add_lc(mo, LC_SOURCE_VERSION, sizeof(*dyldsv));
@@ -1978,6 +2085,8 @@ static void macho_write(TCCState *s1, struct macho *mo, 
FILE *fp)
mo->mh.mh.filetype = MH_DYLIB;
mo->mh.mh.flags = MH_DYLDLINK;
}
+ if (mo->sk_to_sect[sk_thread_vars].s)
+ mo->mh.mh.flags |= MH_HAS_TLV_DESCRIPTORS;
mo->mh.mh.ncmds = mo->nlc;
mo->mh.mh.sizeofcmds = 0;
for (i = 0; i < mo->nlc; i++)
@@ -2199,6 +2308,9 @@ ST_FUNC int macho_output_file(TCCState *s1, const char 
*filename)
tcc_add_runtime(s1);
tcc_macho_add_destructor(s1);
resolve_common_syms(s1);
+#if defined(TCC_TARGET_ARM64)
+ arm64_macho_prepare_tlv(s1);
+#endif
create_symtab(s1, &mo);
check_relocs(s1, &mo);
ret = check_symbols(s1, &mo);
@@ -2214,6 +2326,10 @@ ST_FUNC int macho_output_file(TCCState *s1, const char 
*filename)
goto do_ret;
// Macho uses bind/rebase instead of dynsym
s1->output_type = TCC_OUTPUT_EXE;
+#if defined(TCC_TARGET_ARM64)
+ s1->arm64_tlv_base = mo.sk_to_sect[sk_thread_data].s ? 
mo.sk_to_sect[sk_thread_data].s->sh_addr
+ : mo.sk_to_sect[sk_thread_bss].s ? mo.sk_to_sect[sk_thread_bss].s->sh_addr : 
0;
+#endif
relocate_sections(s1);
s1->output_type = save_output;
#ifdef CONFIG_NEW_MACHO
diff --git a/tests/tests2/Makefile b/tests/tests2/Makefile
index 83933e6..468803d 100644
--- a/tests/tests2/Makefile
+++ b/tests/tests2/Makefile
@@ -56,7 +56,14 @@ ifneq (,$(filter OpenBSD FreeBSD NetBSD 
DragonFly,$(TARGETOS)))
SKIP += 144_tls.test 146_tls_extern.test # No tls support
endif
ifeq ($(CONFIG_OSX),yes)
- SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported on Mach-O
+ ifneq (-$(ARCH)-,-arm64-)
+ SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported on Mach-O
+ else
+ # arm64 Mach-O TLS runtime works (see 144_tls); 146 still can't run because
+ # `tcc -c` doesn't emit Mach-O object files here (ELF unconditionally, even
+ # for non-TLS code) so there's nothing for the system linker to consume yet.
+ SKIP += 146_tls_extern.test # tcc -c has no Mach-O object output yet
+ endif
SKIP += 148_linker_symbols.test # ELF linker tests
endif
ifeq ($(CONFIG_pie),yes)
@@ -158,11 +165,23 @@ endif

# Verify that undefined TLS symbols in TCC objects work with the system linker.
# (note: '$*' is '146_tls_extern' here)
+# -Wl,-z,noexecstack is a GNU-ld/ELF option; Mach-O has no equivalent (and
+# stacks are non-executable by default there), so skip it under CONFIG_OSX.
+# (Currently moot since the test itself is skipped on OSX - see SKIP above -
+# but kept correct for whenever tcc -c gains real Mach-O object output.)
+ifeq ($(CONFIG_OSX),yes)
+146_tls_extern.test: T1 = \
+ $(TCC) -c $1 -o $*-main.o && \
+ $(TCC) -c $1 -DDEFS -o $*-defs.o && \
+ $(CC) -no-pie $*-main.o $*-defs.o -o $*.exe && \
+ ./$*.exe
+else
146_tls_extern.test: T1 = \
$(TCC) -c $1 -o $*-main.o && \
$(TCC) -c $1 -DDEFS -o $*-defs.o && \
$(CC) -no-pie -Wl,-z,noexecstack $*-main.o $*-defs.o -o $*.exe && \
./$*.exe
+endif
ifeq (-$(CONFIG_WIN32)-,-yes-)
146_tls_extern.test : CC=$(TCC) endif

-----------------------------------------------
Richard Wheeler
_______________________________________________
Tinycc-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel

Reply via email to