From 384614a945b68b78343a25414eb74c26947ae542 Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Tue, 28 Jul 2026 18:00:59 +0900 Subject: [PATCH] tccelf: provide complete ELF boundary symbols The traditional boundary symbols were recorded against the canonical .text, .data and .bss sections before dynamic binding and final layout. Separately named allocatable sections and copy-relocated data could therefore fall outside the advertised ranges. Track definitions synthesized by the linker and finalize them from the ordered loadable sections. Refresh both regular and dynamic symbol tables, and give the plain etext, edata and end names PROVIDE semantics on every ELF target while preserving user definitions. Cover archive references, overrides, copy relocations and independently named text/data/bss sections. Reported-by: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> --- tcc.h | 1 + tccelf.c | 95 ++++++++++++++++++++++ tests/tests2/148+_linker_symbols.c | 13 +++ tests/tests2/148+override_linker_symbols.c | 13 +++ tests/tests2/148_linker_symbols.c | 11 +++ tests/tests2/148_linker_symbols.expect | 2 + tests/tests2/149+_end_copy_reloc.c | 1 + tests/tests2/149+override_end_copy_reloc.c | 18 ++++ tests/tests2/149_end_copy_reloc.c | 18 ++++ tests/tests2/149_end_copy_reloc.expect | 2 + tests/tests2/150+_linker_boundaries.c | 14 ++++ tests/tests2/150_linker_boundaries.c | 31 +++++++ tests/tests2/150_linker_boundaries.expect | 1 + tests/tests2/Makefile | 44 +++++++++- 14 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 tests/tests2/148+_linker_symbols.c create mode 100644 tests/tests2/148+override_linker_symbols.c create mode 100644 tests/tests2/148_linker_symbols.c create mode 100644 tests/tests2/148_linker_symbols.expect create mode 100644 tests/tests2/149+_end_copy_reloc.c create mode 100644 tests/tests2/149+override_end_copy_reloc.c create mode 100644 tests/tests2/149_end_copy_reloc.c create mode 100644 tests/tests2/149_end_copy_reloc.expect create mode 100644 tests/tests2/150+_linker_boundaries.c create mode 100644 tests/tests2/150_linker_boundaries.c create mode 100644 tests/tests2/150_linker_boundaries.expect diff --git a/tcc.h b/tcc.h index 7b4c2371..15333b3d 100644 --- a/tcc.h +++ b/tcc.h @@ -722,6 +722,7 @@ struct sym_attr { unsigned plt_offset; int plt_sym; int dyn_index; + unsigned char linker_sym:1; #ifdef TCC_TARGET_ARM unsigned char plt_thumb_stub:1; #endif diff --git a/tccelf.c b/tccelf.c index 7e9758af..4837936d 100644 --- a/tccelf.c +++ b/tccelf.c @@ -1560,6 +1560,90 @@ ST_FUNC int set_global_sym(TCCState *s1, const char *name, Section *sec, addr_t ELFW(ST_INFO)(name ? STB_GLOBAL : STB_LOCAL, STT_NOTYPE), 0, shn, name); } +#ifndef ELF_OBJ_ONLY +static int set_linker_sym(TCCState *s1, const char *name, + Section *sec, addr_t offs) +{ + int sym_index = find_elf_sym(symtab_section, name); + ElfW(Sym) *sym = sym_index + ? &((ElfW(Sym) *)symtab_section->data)[sym_index] : NULL; + /* Mirror the set_elf_sym cases where the new definition wins. */ + int is_linker_sym = !sym || sym->st_shndx == SHN_UNDEF + || ELFW(ST_BIND)(sym->st_info) == STB_WEAK + || (sec && sec != bss_section + && ((sym->st_other & ST_ASM_SET) + || sym->st_shndx == SHN_COMMON + || sym->st_shndx == bss_section->sh_num)); + + sym_index = set_global_sym(s1, name, sec, offs); + if (is_linker_sym) + get_sym_attr(s1, sym_index, 1)->linker_sym = 1; + return sym_index; +} + +static void provide_linker_sym(TCCState *s1, const char *name, + Section *sec, addr_t offs) +{ + int sym_index = find_elf_sym(symtab_section, name); + + if (!sym_index + || ((ElfW(Sym) *)symtab_section->data)[sym_index].st_shndx != SHN_UNDEF) + return; + set_linker_sym(s1, name, sec, offs); +} + +static void update_linker_sym(TCCState *s1, const char *name, + Section *sec, addr_t offs) +{ + int sym_index = find_elf_sym(symtab_section, name); + + if (sym_index && sym_index < s1->nb_sym_attrs + && s1->sym_attrs[sym_index].linker_sym) { + ElfW(Sym) *sym = &((ElfW(Sym) *)symtab_section->data)[sym_index]; + + sym->st_value = offs; + sym->st_shndx = sec->sh_num; + if (s1->dynsym) { + sym_index = find_elf_sym(s1->dynsym, name); + if (sym_index) { + sym = &((ElfW(Sym) *)s1->dynsym->data)[sym_index]; + sym->st_value = offs; + sym->st_shndx = sec->sh_num; + } + } + } +} + +static void finalize_linker_symbols(TCCState *s1, int *sec_order) +{ + Section *etext = text_section; + Section *edata = data_section; + Section *end = bss_section; + Section *s; + int i; + + /* Follow the final loadable-section order rather than assuming that + .text, .data and .bss are the last sections of their regions. */ + for (i = 1; i < s1->nb_sections; ++i) { + s = s1->sections[sec_order[i]]; + if (!(s->sh_flags & SHF_ALLOC)) + continue; + end = s; + if (!(s->sh_flags & SHF_WRITE)) + etext = s; + if (s->sh_type != SHT_NOBITS) + edata = s; + } + + update_linker_sym(s1, "_etext", etext, etext->sh_size); + update_linker_sym(s1, "etext", etext, etext->sh_size); + update_linker_sym(s1, "_edata", edata, edata->sh_size); + update_linker_sym(s1, "edata", edata, edata->sh_size); + update_linker_sym(s1, "_end", end, end->sh_size); + update_linker_sym(s1, "end", end, end->sh_size); +} +#endif + static void add_init_array_defines(TCCState *s1, const char *section_name) { Section *s; @@ -1884,9 +1968,19 @@ static void tcc_add_linker_symbols(TCCState *s1) int i; Section *s; +#ifndef ELF_OBJ_ONLY + set_linker_sym(s1, "_etext", text_section, -1); + set_linker_sym(s1, "_edata", data_section, -1); + set_linker_sym(s1, "_end", bss_section, -1); + /* These conventional ELF symbols have PROVIDE semantics. */ + provide_linker_sym(s1, "etext", text_section, -1); + provide_linker_sym(s1, "edata", data_section, -1); + provide_linker_sym(s1, "end", bss_section, -1); +#else set_global_sym(s1, "_etext", text_section, -1); set_global_sym(s1, "_edata", data_section, -1); set_global_sym(s1, "_end", bss_section, -1); +#endif #if TARGETOS_OpenBSD set_global_sym(s1, "__executable_start", NULL, ELF_START_ADDR); #endif @@ -3081,6 +3175,7 @@ static int elf_output_file(TCCState *s1, const char *filename) sec_order = tcc_malloc(sizeof(int) * 2 * s1->nb_sections); /* compute section to program header mapping */ layout_sections(s1, sec_order, &dyninf); + finalize_linker_symbols(s1, sec_order); if (dynamic) { /* put in GOT the dynamic section address and relocate PLT */ diff --git a/tests/tests2/148+_linker_symbols.c b/tests/tests2/148+_linker_symbols.c new file mode 100644 index 00000000..bf82d31e --- /dev/null +++ b/tests/tests2/148+_linker_symbols.c @@ -0,0 +1,13 @@ +extern char etext[], _etext[]; +extern char edata[], _edata[]; +extern char end[], _end[]; + +int check_linker_symbols(void) +{ + int errors = 0; + + errors += etext != _etext; + errors += edata != _edata; + errors += end != _end; + return errors; +} diff --git a/tests/tests2/148+override_linker_symbols.c b/tests/tests2/148+override_linker_symbols.c new file mode 100644 index 00000000..cd96e74f --- /dev/null +++ b/tests/tests2/148+override_linker_symbols.c @@ -0,0 +1,13 @@ +#include + +int etext(void) { return 1; } +int edata(void) { return 2; } +int end(void) { return 3; } + +int main(void) +{ + int errors = etext() + edata() + end() != 6; + + printf("user definitions errors: %d\n", errors); + return errors; +} diff --git a/tests/tests2/148_linker_symbols.c b/tests/tests2/148_linker_symbols.c new file mode 100644 index 00000000..0788f8db --- /dev/null +++ b/tests/tests2/148_linker_symbols.c @@ -0,0 +1,11 @@ +#include + +int check_linker_symbols(void); + +int main(void) +{ + int errors = check_linker_symbols(); + + printf("linker symbols errors: %d\n", errors); + return errors; +} diff --git a/tests/tests2/148_linker_symbols.expect b/tests/tests2/148_linker_symbols.expect new file mode 100644 index 00000000..ef7716df --- /dev/null +++ b/tests/tests2/148_linker_symbols.expect @@ -0,0 +1,2 @@ +linker symbols errors: 0 +user definitions errors: 0 diff --git a/tests/tests2/149+_end_copy_reloc.c b/tests/tests2/149+_end_copy_reloc.c new file mode 100644 index 00000000..9fd6c196 --- /dev/null +++ b/tests/tests2/149+_end_copy_reloc.c @@ -0,0 +1 @@ +unsigned char copy_reloc_data[257]; diff --git a/tests/tests2/149+override_end_copy_reloc.c b/tests/tests2/149+override_end_copy_reloc.c new file mode 100644 index 00000000..52560b1f --- /dev/null +++ b/tests/tests2/149+override_end_copy_reloc.c @@ -0,0 +1,18 @@ +#include + +extern unsigned char copy_reloc_data[257]; + +int end(void) { return 42; } +int _end(void) { return 24; } + +int main(void) +{ + int errors; + + copy_reloc_data[256] = 42; + errors = end() != 42; + errors += _end() != 24; + errors += copy_reloc_data[256] != 42; + printf("user end copy relocation errors: %d\n", errors); + return errors; +} diff --git a/tests/tests2/149_end_copy_reloc.c b/tests/tests2/149_end_copy_reloc.c new file mode 100644 index 00000000..4981b8ab --- /dev/null +++ b/tests/tests2/149_end_copy_reloc.c @@ -0,0 +1,18 @@ +#include + +extern unsigned char copy_reloc_data[257]; +extern char end[], _end[]; + +int main(void) +{ + unsigned long data_end; + int errors = 0; + + copy_reloc_data[256] = 42; + data_end = (unsigned long)(copy_reloc_data + sizeof copy_reloc_data); + errors += end != _end; + errors += (unsigned long)end < data_end; + errors += copy_reloc_data[256] != 42; + printf("end copy relocation errors: %d\n", errors); + return errors; +} diff --git a/tests/tests2/149_end_copy_reloc.expect b/tests/tests2/149_end_copy_reloc.expect new file mode 100644 index 00000000..675c2783 --- /dev/null +++ b/tests/tests2/149_end_copy_reloc.expect @@ -0,0 +1,2 @@ +end copy relocation errors: 0 +user end copy relocation errors: 0 diff --git a/tests/tests2/150+_linker_boundaries.c b/tests/tests2/150+_linker_boundaries.c new file mode 100644 index 00000000..1f6beacc --- /dev/null +++ b/tests/tests2/150+_linker_boundaries.c @@ -0,0 +1,14 @@ +void __attribute__((section(".text.linker_test"), noinline, used)) +linker_test_text(void) +{ +} + +unsigned char linker_test_data[23] + __attribute__((section(".data.linker_test"), used)) = { 1 }; + +unsigned char linker_test_bss[257] + __attribute__((section(".bss.linker_test"), used)); + +/* set_global_sym replaces BSS/common definitions of these symbols. */ +unsigned char _etext; +unsigned char _edata; diff --git a/tests/tests2/150_linker_boundaries.c b/tests/tests2/150_linker_boundaries.c new file mode 100644 index 00000000..f482f278 --- /dev/null +++ b/tests/tests2/150_linker_boundaries.c @@ -0,0 +1,31 @@ +#include + +extern void linker_test_text(void); +extern unsigned char linker_test_data[23]; +extern unsigned char linker_test_bss[257]; +extern char etext[], _etext[]; +extern char edata[], _edata[]; +extern char end[], _end[]; + +int main(void) +{ + unsigned long text_addr = (unsigned long)linker_test_text; + unsigned long data_end = (unsigned long)(linker_test_data + + sizeof linker_test_data); + unsigned long bss_end = (unsigned long)(linker_test_bss + + sizeof linker_test_bss); + int errors = 0; + + linker_test_data[22] = 42; + linker_test_bss[256] = 42; + errors += etext != _etext; + errors += edata != _edata; + errors += end != _end; + errors += (unsigned long)etext <= text_addr; + errors += (unsigned long)edata < data_end; + errors += (unsigned long)end < bss_end; + errors += linker_test_data[22] != 42; + errors += linker_test_bss[256] != 42; + printf("linker boundary errors: %d\n", errors); + return errors; +} diff --git a/tests/tests2/150_linker_boundaries.expect b/tests/tests2/150_linker_boundaries.expect new file mode 100644 index 00000000..feb3f6cc --- /dev/null +++ b/tests/tests2/150_linker_boundaries.expect @@ -0,0 +1 @@ +linker boundary errors: 0 diff --git a/tests/tests2/Makefile b/tests/tests2/Makefile index 3e18000c..1c094d42 100644 --- a/tests/tests2/Makefile +++ b/tests/tests2/Makefile @@ -38,12 +38,13 @@ ifeq ($(CONFIG_bcheck),no) endif ifeq ($(CONFIG_dll),no) SKIP += 113_btdll.test 147_def_library.test # no shared lib support yet + SKIP += 149_end_copy_reloc.test 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 + SKIP += 146_tls_extern.test 150_linker_boundaries.test # needs external tools endif ifeq (-$(CONFIG_WIN32)-$(CONFIG_i386)$(CONFIG_arm)-,--yes-) SKIP += 95_bitfields%.test # type_align is different on 32bit-non-windows @@ -54,6 +55,8 @@ ifeq (-$(CONFIG_WIN32)-,-yes-) SKIP += 117_builtins.test # win32 port doesn't define __builtins SKIP += 124_atomic_counter.test # No pthread support SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported + SKIP += 148_linker_symbols.test 149_end_copy_reloc.test # ELF linker tests + SKIP += 150_linker_boundaries.test endif ifneq (,$(filter OpenBSD FreeBSD NetBSD,$(TARGETOS))) SKIP += 106_versym.test # no pthread_condattr_setpshared @@ -66,6 +69,11 @@ ifeq ($(TARGETOS),DragonFly) endif ifeq ($(CONFIG_OSX),yes) SKIP += 144_tls.test 146_tls_extern.test # TLS runtime not supported on Mach-O + SKIP += 148_linker_symbols.test 149_end_copy_reloc.test # ELF linker tests + SKIP += 150_linker_boundaries.test +endif +ifeq ($(CONFIG_pie),yes) + SKIP += 149_end_copy_reloc.test # PIE executables do not use copy relocations endif ifeq (-$(ARCH)-,-arm-) SKIP += 144_tls.test 146_tls_extern.test # doesn't work currently @@ -143,6 +151,38 @@ GEN-ALWAYS = 146_tls_extern-def.o -o $(basename $@).exe && \ ./$(basename $@).exe +# Check optional ELF linker symbols, including references from an archive. +148_linker_symbols.test: NORUN = true +148_linker_symbols.test: T1 = ( \ + $(TCC) -c $(subst 148,148+,$1) -o 148_linker_symbols.o && \ + $(AR) rcs 148_linker_symbols.a 148_linker_symbols.o && \ + $(TCC) $1 148_linker_symbols.a -o $(basename $@).exe && \ + ./$(basename $@).exe && \ + $(TCC) $(subst 148,148+override,$1) -o 148_linker_symbols-override.exe && \ + ./148_linker_symbols-override.exe \ + ) + +# Copy relocations must be included in the final end symbols. +149_end_copy_reloc.test: NORUN = true +149_end_copy_reloc.test: T1 = ( \ + $(TCC) -shared $(subst 149,149+,$1) -o 149_end_copy_reloc$(DLLSUF) && \ + $(TCC) $1 ./149_end_copy_reloc$(DLLSUF) -Wl,-rpath=. \ + -o $(basename $@).exe && \ + ./$(basename $@).exe && \ + $(TCC) $(subst 149,149+override,$1) ./149_end_copy_reloc$(DLLSUF) \ + -Wl,-rpath=. -o 149_end_copy_reloc-override.exe && \ + ./149_end_copy_reloc-override.exe \ + ) + +# Boundary symbols must cover separately named sections from external objects. +150_linker_boundaries.test: NORUN = true +150_linker_boundaries.test: T1 = ( \ + $(CC) -fno-pic -fno-pie -c $(subst 150,150+,$1) \ + -o 150_linker_boundaries.o && \ + $(TCC) $1 150_linker_boundaries.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' \ @@ -233,4 +273,4 @@ $(sort $(GEN-ALWAYS) $(UPDATE)) : force force: clean : - rm -f fred.txt *.output *.o *.exe *.dll *.so *.def $(GEN-ALWAYS) + rm -f fred.txt *.output *.o *.a *.exe *.dll *.so *.def $(GEN-ALWAYS)