From: Olivier Houchard Date: Thu, 15 Oct 2026 14:50:23 +0000 (+0200) Subject: BUILD: sym: Build properly with gold as a linker X-Git-Tag: v3.5-dev10~187 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=4d9b317958fcb744f3a73c0517696926b2330ca7;p=haproxy.git BUILD: sym: Build properly with gold as a linker Some linkers, such as gold, will not let us do our trick of having our note in our own program header, and will merge it with other notes. If that happens, then detect it, and just link the binary a second time, now that we know the size of the section, as we created the xz file already, we can add it without wasting any binary space. --- diff --git a/Makefile b/Makefile index 469ed5640..f575e2a1f 100644 --- a/Makefile +++ b/Makefile @@ -1100,6 +1100,9 @@ endif # non-empty target # Only the last step can leave the program half-written, so it is the only one # whose failure is fatal; not being able to produce the table just leaves the # program without one, as happens when the tools do not know the target. +# If the linker was not being cooperative, and did not create our own special +# note in its own segment, then revert to try to link the binary a second time, +# once we know the size of .gnu_debugdata, to include it. # The nm options are spelled with their portable short forms (-P, -D) as some # implementations lack the long ones, and undefined symbols are told by their # type in awk instead of --defined-only, which these implementations lack too. @@ -1107,6 +1110,7 @@ define make_minidebug $(call qinfo, MINIDBG)if ! ( \ $(NM) -D -P $@ | awk '$$2 != "U" && $$2 != "w" { print $$1 }' | sort > $@.dsyms && \ $(NM) -P $@ | awk '$$2 == "T" || $$2 == "t" { print $$1 }' | sort > $@.fsyms && \ + $(NM) -P $@ | awk '$$2 == "T" || $$2 == "t" { print $$1, $$3 }' | sort > $@.symaddrs && \ comm -13 $@.dsyms $@.fsyms > $@.ksyms && \ $(OBJCOPY) --only-keep-debug $@ $@.dbg && \ $(OBJCOPY) -S --remove-section .gdb_index --remove-section .comment --keep-symbols=$@.ksyms $@.dbg $@.mini && \ @@ -1115,11 +1119,24 @@ define make_minidebug echo "$@: cannot build the symbol table, see the error above; the program is" >&2; \ echo "$@: left without one. Cross-builds need NM, OBJCOPY and READELF pointed at" >&2; \ echo "$@: the target's tools, and USE_MINIDEBUG=0 skips this step altogether." >&2; \ - elif ! READELF='$(READELF)' $(MINIDEBUG_TOOL) $@ $@.mini.xz; then \ - rm -f $@ $@.dsyms $@.fsyms $@.ksyms $@.dbg $@.mini $@.mini.xz; \ + elif READELF='$(READELF)' $(MINIDEBUG_TOOL) $@ $@.mini.xz; then \ + :; \ + elif [ $$? -ne 2 ]; then \ + rm -f $@ $@.dsyms $@.fsyms $@.symaddrs $@.ksyms $@.dbg $@.mini $@.mini.xz; \ exit 1; \ + elif ! { echo " LD $@ (relinked with the symbol table)" >&2 && \ + printf '.section .gnu_debugdata,"a"\n\t.hidden __symtab_xz\n\t.global __symtab_xz\n__symtab_xz:\n\t.incbin "%s"\n\t.hidden __symtab_xz_end\n\t.global __symtab_xz_end\n__symtab_xz_end:\n\t.section .note.GNU-stack,"",@progbits\n' "$@.mini.xz" > $@.mini.S && \ + $(CC) $(ARCH_FLAGS) -c -o $@.mini.raw.o $@.mini.S && \ + $(OBJCOPY) --remove-section .note.gnu.property $@.mini.raw.o $@.mini.o && \ + $(LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@.minidbg $@.mini.o $^ $(LDOPTS) && \ + $(NM) -P $@.minidbg 2>/dev/null | awk '$$2 == "T" || $$2 == "t" { print $$1, $$3 }' | sort | cmp -s - $@.symaddrs; }; then \ + echo "$@: the second linkage could not embed the symbol table either; the" >&2; \ + echo "$@: program is left without one. USE_MINIDEBUG=0 skips this step" >&2; \ + echo "$@: altogether." >&2; \ + else \ + mv -f $@.minidbg $@; \ fi; \ - rm -f $@.dsyms $@.fsyms $@.ksyms $@.dbg $@.mini $@.mini.xz + rm -f $@.dsyms $@.fsyms $@.symaddrs $@.ksyms $@.dbg $@.mini $@.mini.xz $@.mini.S $@.mini.raw.o $@.mini.o $@.minidbg endef haproxy: $(OPTIONS_OBJS) $(OBJS) diff --git a/scripts/minidbg.sh b/scripts/minidbg.sh index a3a732bb3..604f6d847 100755 --- a/scripts/minidbg.sh +++ b/scripts/minidbg.sh @@ -31,14 +31,20 @@ pay="$2" PAGE=65536 die() { echo "minidbg: $1" >&2; exit 1; } +# die2() is only used strictly before anything is written, and tells the +# caller with its status of 2 that the program may be kept as is, just +# without the table; this is how a linker that leaves no segment of its +# own to the note (e.g. gold merges the notes) is told apart from a failure +# that may have half-written the program. +die2() { echo "minidbg: $1" >&2; exit 2; } -[ $# = 2 ] || die "usage: $0 " +[ $# = 2 ] || die2 "usage: $0
" -hdr=$(LC_ALL=C "$READELF" -hW "$elf") || die "cannot read $elf" +hdr=$(LC_ALL=C "$READELF" -hW "$elf") || die2 "cannot read $elf" case "$hdr" in *ELF64*) w=8 ;; *ELF32*) w=4 ;; - *) die "unknown ELF class" ;; + *) die2 "unknown ELF class" ;; esac case "$hdr" in *"big endian"*) be=1 ;; @@ -48,7 +54,7 @@ esac # everything read from the listings is checked before being used num() { case "$2" in - '' | *[!0-9]*) die "cannot read the $1 of $elf from $READELF" ;; + '' | *[!0-9]*) die2 "cannot read the $1 of $elf from $READELF" ;; esac } @@ -64,19 +70,19 @@ num "section header size" "$shentsize" # the two placeholders: the section gives the name, the note gives the segment. # Their index in the listing is not their index in the table, so the section is # located by the number readelf prints, and the note by where it starts. -secs=$(LC_ALL=C "$READELF" -SW "$elf") || die "cannot read the sections of $elf" +secs=$(LC_ALL=C "$READELF" -SW "$elf") || die2 "cannot read the sections of $elf" sec=$(echo "$secs" | awk '{ for (i = 1; i < NF; i++) if ($i == ".gnu_debugdata") { n = (i == 2) ? $1 : $(i - 1); gsub(/[][]/, "", n); print n; exit } }') -[ -n "$sec" ] || die "no .gnu_debugdata section in $elf, was it reserved at link time?" +[ -n "$sec" ] || die2 "no .gnu_debugdata section in $elf, was it reserved at link time?" num "section index" "$sec" note=$(echo "$secs" | awk '{ for (i = 1; i < NF; i++) if ($i == ".note.hapdbg") { o = $(i + 3); z = $(i + 4); sub(/^0*/, "", o); sub(/^0*/, "", z) print o " " z; exit } }') -[ -n "$note" ] || die "no .note.hapdbg section in $elf, was it reserved at link time?" +[ -n "$note" ] || die2 "no .note.hapdbg section in $elf, was it reserved at link time?" # the note's own segment is the one that starts where the note does and holds # nothing else, so that turning it into a loadable one loses no other note -segs=$(LC_ALL=C "$READELF" -lW "$elf") || die "cannot read the segments of $elf" +segs=$(LC_ALL=C "$READELF" -lW "$elf") || die2 "cannot read the segments of $elf" seg=$(echo "$segs" | awk -v want="$note" ' $2 ~ /^0x/ && $3 ~ /^0x/ && $4 ~ /^0x/ { if ($1 == "NOTE") { @@ -85,7 +91,7 @@ seg=$(echo "$segs" | awk -v want="$note" ' } idx++ }') -[ -n "$seg" ] || die "the note of $elf has no segment of its own" +[ -n "$seg" ] || die2 "the note of $elf has no segment of its own" num "segment index" "$seg" # the table is mapped past everything the file already maps @@ -94,10 +100,10 @@ for e in $(echo "$segs" | awk '$1 == "LOAD" && $3 ~ /^0x/ { print $3 "+" $6 }'); e=$(( $e )) [ $e -gt $maxva ] && maxva=$e done -[ $maxva -gt 0 ] || die "$elf has no loadable segment" +[ $maxva -gt 0 ] || die2 "$elf has no loadable segment" -paylen=$(wc -c < "$pay") || die "cannot read $pay" -[ "$paylen" -gt 0 ] || die "$pay is empty" +paylen=$(wc -c < "$pay") || die2 "cannot read $pay" +[ "$paylen" -gt 0 ] || die2 "$pay is empty" elflen=$(wc -c < "$elf") off=$(( (elflen + PAGE - 1) / PAGE * PAGE )) vaddr=$(( (maxva + PAGE - 1) / PAGE * PAGE )) diff --git a/src/sym.c b/src/sym.c index 38ab98b8f..d59a0c63b 100644 --- a/src/sym.c +++ b/src/sym.c @@ -310,6 +310,15 @@ static const struct { } sym_debugdata_note = { 7, 4, 1, "HAPDBG", 0 }; #endif +/* The table may also be linked in by a second linkage of the program, used + * as a fallback by the build when the linker gives the note no segment of + * its own and the table cannot be appended instead (e.g. gold merges the + * notes). It is then located through these symbols, wherever the linker + * placed it; they are weak so that a build without it simply has no table. + */ +extern const unsigned char __symtab_xz[] __attribute__((weak)); +extern const unsigned char __symtab_xz_end[] __attribute__((weak)); + /* native ELF class / endianness / symbol-type accessor, derived from the * toolchain since glibc doesn't provide *_NATIVE convenience macros. */ @@ -1141,6 +1150,14 @@ static int phdr_cb(struct dl_phdr_info *info, size_t size, void *data) collect_dynsym_mem(b, info, obj); + /* the executable's own table, embedded by the second linkage, is + * located through its symbols, wherever the linker placed it. + */ + if (is_exe && __symtab_xz) { + b->debugdata = __symtab_xz; + b->debugdata_size = __symtab_xz_end - __symtab_xz; + } + /* a table stored by the build is covered by a segment of its own, whose * contents identify it. */