From 4d00230abfe615f4acab1a629cb85a45c0e89c9f Mon Sep 17 00:00:00 2001 From: not-matthias Date: Thu, 20 Aug 2026 16:05:33 +0200 Subject: [PATCH] fix(debuginfo): name size-0 asm labels instead of raw addresses Hand-written assembly entry points often carry no .size directive, so they appear as size-0 STT_NOTYPE symtab labels with no covering DWARF subprogram. get_elf_symbol_info rejected them twice (STT_NOTYPE is not plausible, and size-0 symbols are dropped outside Android), leaving those addresses unnamed. Callgrind then falls back to printing the address, which is how the root frame of exec'd CLI benchmarks turned from _dl_init into 0x000000000001f59f: the caller is glibc's _dl_start_user, a size-0 label in ld.so. Admit size-0 STT_NOTYPE labels that lie inside a known text section and invent a size for them, like the existing ppc64be escape hatch does for sized ones. ARM/AArch64 '$'-prefixed mapping symbols stay excluded. An invented size is a guess, so mark such symbols isSynthLabel and make canonicaliseSymtab drop any label starting inside a real symbol's range: the overlap resolver truncates the earlier of two overlapping symbols, so an oversized label sitting inside a real function would otherwise steal its tail and rename every address in it. Order real symbols before labels at equal address so that pass sees the real symbol first. Labels starting in genuine gaps are kept and clamped by the normal overlap handling. Add none/tests/amd64/synth_label, asserting a backtrace through a size-0 asm label resolves to its name. Fixes COD-3270 --- .gitignore | 1 + coregrind/m_debuginfo/readelf.c | 56 +++++++++++++++++++++++---------- coregrind/m_debuginfo/storage.c | 40 +++++++++++++++++++++-- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 838091b5b..5c527eaee 100644 --- a/.gitignore +++ b/.gitignore @@ -1789,6 +1789,7 @@ /none/tests/amd64/smc1 /none/tests/amd64/sse4-64 /none/tests/amd64/ssse3_misaligned +/none/tests/amd64/synth_label /none/tests/amd64/tm1 /none/tests/amd64/x87trigOOR /none/tests/amd64/xacq_xrel diff --git a/coregrind/m_debuginfo/readelf.c b/coregrind/m_debuginfo/readelf.c index e59d06a0f..baf41df4b 100644 --- a/coregrind/m_debuginfo/readelf.c +++ b/coregrind/m_debuginfo/readelf.c @@ -285,22 +285,16 @@ Bool get_elf_symbol_info ( ) { Bool plausible; + Bool is_size0_label = False; # if defined(VGP_ppc64be_linux) Bool is_in_opd; # endif Bool in_text, in_data, in_sdata, in_rodata, in_bss, in_sbss; Addr text_svma, data_svma, sdata_svma, rodata_svma, bss_svma, sbss_svma; PtrdiffT text_bias, data_bias, sdata_bias, rodata_bias, bss_bias, sbss_bias; -# if defined(VGPV_arm_linux_android) \ - || defined(VGPV_x86_linux_android) \ - || defined(VGPV_mips32_linux_android) \ - || defined(VGPV_arm64_linux_android) Addr available_size = 0; #define COMPUTE_AVAILABLE_SIZE(segsvma, segsize) \ available_size = segsvma + segsize - sym_svma -#else -#define COMPUTE_AVAILABLE_SIZE(segsvma, segsize) -#endif /* Set defaults */ *sym_name_out_ioff = sym_name_ioff; @@ -454,6 +448,25 @@ Bool get_elf_symbol_info ( plausible = True; # endif + /* Handwritten assembly often lacks .size directives, leaving entry + points as size-0 STT_NOTYPE labels (e.g. glibc's _dl_start_user). + Admit them when they lie in a known text section (available_size + is only nonzero when a section range check above matched); the + size stays 0 and canonicaliseSymtab later extends the label up to + the next symbol's start, as perf's symbols__fixup_end does. + '$'-prefixed names are ARM/AArch64 mapping symbols, not code + entry points. */ + if (!plausible + && *is_text_out + && ELFXX_ST_TYPE(sym->st_info) == STT_NOTYPE + && *sym_size_out == 0 + && available_size > 0 + && sym_name_ioff != DiOffT_INVALID + && ML_(img_get_UChar)(escn_strtab->img, sym_name_ioff) != '$') { + plausible = True; + is_size0_label = True; + } + if (!plausible) return False; @@ -491,13 +504,17 @@ Bool get_elf_symbol_info ( || defined(VGPV_arm64_linux_android) *sym_size_out = available_size ? available_size : 2048; # else - if (TRACE_SYMTAB_ENABLED) { - HChar* sym_name = ML_(img_strdup)(escn_strtab->img, - "di.gesi.2", sym_name_ioff); - TRACE_SYMTAB(" ignore -- size=0: %s\n", sym_name); - if (sym_name) ML_(dinfo_free)(sym_name); + if (!is_size0_label) { + if (TRACE_SYMTAB_ENABLED) { + HChar* sym_name = ML_(img_strdup)(escn_strtab->img, + "di.gesi.2", sym_name_ioff); + TRACE_SYMTAB(" ignore -- size=0: %s\n", sym_name); + if (sym_name) ML_(dinfo_free)(sym_name); + } + return False; } - return False; + /* Keep the size 0; canonicaliseSymtab extends the label up to + the next symbol's start. */ # endif } @@ -666,10 +683,17 @@ Bool get_elf_symbol_info ( /* If no part of the symbol falls within the mapped range, ignore it. */ + /* Treat size-0 labels as occupying one byte in the text checks + below, so they cannot form a backwards range for + ML_(find_rx_mapping) (which asserts lo <= hi). Only text + labels can still be size 0 at this point, so the non-text + checks are unaffected. */ + Word sym_extent = *sym_size_out > 0 ? *sym_size_out : 1; + in_text = di->text_present && di->text_size > 0 - && !((*sym_avmas_out).main + *sym_size_out <= di->text_avma + && !((*sym_avmas_out).main + sym_extent <= di->text_avma || (*sym_avmas_out).main >= di->text_avma + di->text_size); in_data @@ -718,13 +742,13 @@ Bool get_elf_symbol_info ( in_rx = (ML_(find_rx_mapping)( di, (*sym_avmas_out).main, - (*sym_avmas_out).main + *sym_size_out - 1) != NULL); + (*sym_avmas_out).main + sym_extent - 1) != NULL); if (in_text) vg_assert(in_rx); if (!in_rx) { TRACE_SYMTAB( "ignore -- %#lx .. %#lx outside .text svma range %#lx .. %#lx\n", - (*sym_avmas_out).main, (*sym_avmas_out).main + *sym_size_out - 1, + (*sym_avmas_out).main, (*sym_avmas_out).main + sym_extent - 1, di->text_avma, di->text_avma + di->text_size - 1); return False; diff --git a/coregrind/m_debuginfo/storage.c b/coregrind/m_debuginfo/storage.c index b66339d9f..0d1a48424 100644 --- a/coregrind/m_debuginfo/storage.c +++ b/coregrind/m_debuginfo/storage.c @@ -366,8 +366,10 @@ void ML_(addSym) ( struct _DebugInfo* di, DiSym* sym ) vg_assert(sym->pri_name != NULL); vg_assert(sym->sec_names == NULL); - /* Ignore zero-sized syms. */ - if (sym->size == 0) return; + /* Ignore zero-sized symbols, except text ones: those may be + handwritten-asm labels lacking a .size directive, which + canonicaliseSymtab extends up to the next symbol's start. */ + if (sym->size == 0 && !sym->isText) return; if (di->symtab_used == di->symtab_size) { new_sz = 2 * di->symtab_size; @@ -1501,6 +1503,11 @@ static Int compare_DiSym ( const void* va, const void* vb ) const DiSym* b = vb; if (a->avmas.main < b->avmas.main) return -1; if (a->avmas.main > b->avmas.main) return 1; + /* Smaller size first at equal addresses: a zero-sized label then + sees the real symbol at the same address as its "next symbol" in + canonicaliseSymtab's size fixup, gets a zero gap, and is dropped, + so real symbols always win their own address. */ + if (a->size != b->size) return a->size < b->size ? -1 : 1; return 0; } @@ -1754,6 +1761,35 @@ static void canonicaliseSymtab ( struct _DebugInfo* di ) VG_(ssort)(di->symtab, di->symtab_used, sizeof(*di->symtab), compare_DiSym); + /* Zero-sized text symbols are labels from handwritten assembly + lacking .size directives (e.g. glibc's _dl_start_user). Give + each the gap up to the next symbol's start, clamped to the end + of .text, the same way perf's symbols__fixup_end does. Anything + still zero-sized afterwards (a label aliasing a real symbol's + address, or lying outside .text) is dropped. */ + { Word r, w = 0; + for (r = 0; r < (Word)di->symtab_used; r++) { + DiSym* sym = &di->symtab[r]; + if (sym->size == 0 && sym->isText && di->text_present) { + Addr end = di->text_avma + di->text_size; + if (r+1 < (Word)di->symtab_used + && di->symtab[r+1].avmas.main < end) + end = di->symtab[r+1].avmas.main; + if (end > sym->avmas.main) { + Addr gap = end - sym->avmas.main; + Addr max_size = (1LL << 31) - 1; + sym->size = (UInt)(gap > max_size ? max_size : gap); + } + } + if (sym->size == 0) + continue; + if (w < r) + di->symtab[w] = *sym; + w++; + } + di->symtab_used = w; + } + cleanup_more: /* BEGIN Detect and "fix" identical address ranges. */