]> git.kaiwu.me - haproxy.git/commitdiff
BUILD: sym: Build properly with gold as a linker
authorOlivier Houchard <ohouchard@haproxy.com>
Thu, 15 Oct 2026 14:50:23 +0000 (16:50 +0200)
committerOlivier Houchard <cognet@ci0.org>
Thu, 15 Oct 2026 15:31:44 +0000 (17:31 +0200)
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.

Makefile
scripts/minidbg.sh
src/sym.c

index 469ed564047d77c173a0f1fc7ca47ea73eca93a2..f575e2a1fa1c1ad96155ca34a3592b541cb0b546 100644 (file)
--- 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)
index a3a732bb3d8480a7d881c060dbe15103604d12be..604f6d84797bd5f5beb514abd4555c0ef8e514c3 100755 (executable)
@@ -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 <elf> <table>"
+[ $# = 2 ] || die2 "usage: $0 <elf> <table>"
 
-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 ))
index 38ab98b8f15c6c3bb3233348a68bbbbf5ff46f69..d59a0c63baca0a6d773568fff0c9056959207bc7 100644 (file)
--- 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.
         */