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>
This commit is contained in:
Mounir IDRASSI 2026-07-28 18:00:59 +09:00
parent 31020bf963
commit 384614a945
14 changed files with 262 additions and 2 deletions

1
tcc.h
View File

@ -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

View File

@ -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 */

View File

@ -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;
}

View File

@ -0,0 +1,13 @@
#include <stdio.h>
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;
}

View File

@ -0,0 +1,11 @@
#include <stdio.h>
int check_linker_symbols(void);
int main(void)
{
int errors = check_linker_symbols();
printf("linker symbols errors: %d\n", errors);
return errors;
}

View File

@ -0,0 +1,2 @@
linker symbols errors: 0
user definitions errors: 0

View File

@ -0,0 +1 @@
unsigned char copy_reloc_data[257];

View File

@ -0,0 +1,18 @@
#include <stdio.h>
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;
}

View File

@ -0,0 +1,18 @@
#include <stdio.h>
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;
}

View File

@ -0,0 +1,2 @@
end copy relocation errors: 0
user end copy relocation errors: 0

View File

@ -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;

View File

@ -0,0 +1,31 @@
#include <stdio.h>
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;
}

View File

@ -0,0 +1 @@
linker boundary errors: 0

View File

@ -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)