From: Olivier Houchard Date: Wed, 16 Sep 2026 14:10:08 +0000 (+0200) Subject: MEDIUM: sym: store a compressed symbol table in a loadable section X-Git-Tag: v3.5-dev9~234 X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=005038b80d468cdca0a9fdcb4b3c993ec02f8367;p=haproxy.git MEDIUM: sym: store a compressed symbol table in a loadable section Implement the MiniDebugInfo, and store the symtab, compressed with xz, into the .gnu_debugdata section, provided the needed utilities are available, and we're building an ELF binary. Makes it a loadable section, so that it is always available, and teach haproxy to parse it, so that we will always get all available symbols for binaries built that way, even if we can't even read the binary itself. --- diff --git a/Makefile b/Makefile index a260832cf..a968f26d9 100644 --- a/Makefile +++ b/Makefile @@ -183,6 +183,10 @@ endif #### Toolchain options. CC = cc LD = $(CC) +NM = nm +OBJCOPY = objcopy +READELF = readelf +XZ = xz #### Default optimizations # Those are integrated early in the list of CFLAGS, and may be overridden by @@ -347,7 +351,7 @@ use_opts = USE_EPOLL USE_KQUEUE USE_NETFILTER USE_POLL \ USE_MATH USE_DEVICEATLAS USE_51DEGREES \ USE_WURFL USE_OBSOLETE_LINKER USE_PRCTL USE_PROCCTL \ USE_THREAD_DUMP USE_EVPORTS USE_QUIC USE_PROMEX \ - USE_MEMORY_PROFILING USE_SHM_OPEN \ + USE_MEMORY_PROFILING USE_SHM_OPEN USE_MINIDEBUG \ USE_STATIC_PCRE USE_STATIC_PCRE2 \ USE_PCRE USE_PCRE_JIT USE_PCRE2 USE_PCRE2_JIT \ USE_QUIC_OPENSSL_COMPAT USE_KTLS @@ -563,6 +567,20 @@ OPTIONS_OBJS = #### Extra objects to be built and integrated (used only for development) EXTRA_OBJS = +# A compressed table of the symbols which are not exported (mostly the static +# functions) may be stored into the executable's .gnu_debugdata section, so +# that backtraces still name them once the executable is stripped or when its +# file is unreachable. This is the "MiniDebugInfo" format that gdb reads too, +# so gdb benefits from it as well. It is produced after the link using nm, +# objcopy and xz, hence it is enabled by default only when the three are +# available, together with the readelf that scripts/minidbg.sh needs, and the +# compiler produces ELF objects, as the placeholders are ELF-specific. That +# script appends the table to the program just linked, which moves nothing, so +# it needs neither reserved room nor a second link. +ifeq ($(origin USE_MINIDEBUG),undefined) +USE_MINIDEBUG := $(if $(and $(shell command -v $(NM)),$(shell command -v $(OBJCOPY)),$(shell command -v $(READELF)),$(shell command -v $(XZ)),$(shell $(CC) -dM -E -xc - /dev/null | grep -F __ELF__)),1,) +endif + # This variable collects all USE_* values except those set to "implicit". This # is used to report a list of all flags which were used to build this version. # Do not assign anything to it. @@ -1007,6 +1025,10 @@ OBJS += src/xz_dec_stream.o src/xz_dec_lzma2.o src/xz_crc32.o \ src/xz_crc64.o endif +ifneq ($(USE_MINIDEBUG:0=),) + MINIDEBUG_TOOL = $(SHELL) scripts/minidbg.sh +endif + HATERM_OBJS += $(OBJS) src/haterm_init.o src/hbuf.o HALOAD_OBJS += $(OBJS) src/haload_init.o src/haload.o src/hbuf.o @@ -1055,14 +1077,48 @@ else .build_opts: endif # non-empty target +# Extracts the symbols which are not in the dynamic symbol table, builds a +# symbols-only ELF file out of them, compresses it and appends it to the program +# just linked, where minidbg.sh points its .gnu_debugdata placeholders at it. +# 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. +define make_minidebug + $(call qinfo, MINIDBG)if ! ( \ + $(NM) -D $@ --format=posix --defined-only | awk '{ print $$1 }' | sort > $@.dsyms && \ + $(NM) $@ --format=posix --defined-only | awk '$$2 == "T" || $$2 == "t" { print $$1 }' | sort > $@.fsyms && \ + comm -13 $@.dsyms $@.fsyms > $@.ksyms && \ + $(OBJCOPY) --only-keep-debug $@ $@.dbg && \ + $(OBJCOPY) -S --remove-section .gdb_index --remove-section .comment --keep-symbols=$@.ksyms $@.dbg $@.mini && \ + $(XZ) -f --check=crc64 $@.mini \ + ); then \ + 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; \ + exit 1; \ + fi; \ + rm -f $@.dsyms $@.fsyms $@.ksyms $@.dbg $@.mini $@.mini.xz +endef + haproxy: $(OPTIONS_OBJS) $(OBJS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) +ifneq ($(USE_MINIDEBUG:0=),) + $(make_minidebug) +endif haterm: $(OPTIONS_OBJS) $(HATERM_OBJS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) +ifneq ($(USE_MINIDEBUG:0=),) + $(make_minidebug) +endif haload: $(OPTIONS_OBJS) $(HALOAD_OBJS) $(cmd_LD) $(ARCH_FLAGS) $(LDFLAGS) -o $@ $^ $(LDOPTS) +ifneq ($(USE_MINIDEBUG:0=),) + $(make_minidebug) +endif objsize: haproxy $(Q)objdump -t $^|grep ' g '|grep -F '.text'|awk '{print $$5 FS $$6}'|sort @@ -1165,6 +1221,7 @@ uninstall: clean: $(Q)rm -f *.[oas] src/*.[oas] haproxy haterm test .build_opts .build_opts.new + $(Q)for p in haproxy haterm haload; do rm -f $$p.dsyms $$p.fsyms $$p.ksyms $$p.dbg $$p.mini $$p.mini.xz; done $(Q)for dir in . src dev/* admin/* addons/* include/* doc; do rm -f $$dir/*~ $$dir/*.rej $$dir/core; done $(Q)rm -f haproxy-$(VERSION).tar.gz haproxy-$(VERSION)$(SUBVERS)$(EXTRAVERSION).tar.gz $(Q)rm -f haproxy-$(VERSION) haproxy-$(VERSION)$(SUBVERS)$(EXTRAVERSION) nohup.out gmon.out diff --git a/scripts/minidbg.sh b/scripts/minidbg.sh new file mode 100755 index 000000000..bcb369e88 --- /dev/null +++ b/scripts/minidbg.sh @@ -0,0 +1,145 @@ +#!/bin/sh +# +# Stores a compressed symbol table into an already linked ELF file. +# +# Copyright (C) 2026 HAProxy Technologies +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# +# The table describes the file it is stored in, so it can only be produced once +# that file is linked, and storing it must not move anything. The table is +# therefore appended at the end, the .gnu_debugdata section header reserved at +# link time is pointed at it so that gdb finds it by name, and the note segment +# reserved at link time is turned into a loadable one covering it, so that it is +# also readable from memory alone. The address is picked past everything the +# file already maps. +# +# usage: minidbg.sh + +: "${READELF:=readelf}" + +elf="$1" +pay="$2" +# The loader maps the segment with mmap(), which needs its file offset and its +# address to be congruent modulo the page size of the machine that runs it, not +# of the one that builds it. Aligning both to the largest page size in use (64 kB +# on arm64 and ppc64) satisfies every smaller one as well, at the cost of at most +# that much padding once. +PAGE=65536 + +die() { echo "minidbg: $1" >&2; exit 1; } + +[ $# = 2 ] || die "usage: $0
" + +hdr=$(LC_ALL=C "$READELF" -hW "$elf") || die "cannot read $elf" +case "$hdr" in + *ELF64*) w=8 ;; + *ELF32*) w=4 ;; + *) die "unknown ELF class" ;; +esac +case "$hdr" in + *"big endian"*) be=1 ;; + *) be=0 ;; +esac +# an empty value would silently be taken as zero by the arithmetic below, so +# 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" ;; + esac +} + +phoff=$(echo "$hdr" | awk '/Start of program header/ { print $(NF-3) }') +phentsize=$(echo "$hdr" | awk '/Size of program header/ { print $(NF-1) }') +shoff=$(echo "$hdr" | awk '/Start of section header/ { print $(NF-3) }') +shentsize=$(echo "$hdr" | awk '/Size of section header/ { print $(NF-1) }') +num "program header offset" "$phoff" +num "program header size" "$phentsize" +num "section header offset" "$shoff" +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" +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?" +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?" + +# 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" +seg=$(echo "$segs" | awk -v want="$note" ' + $2 ~ /^0x/ && $3 ~ /^0x/ && $4 ~ /^0x/ { + if ($1 == "NOTE") { + o = $2; z = $5; sub(/^0x0*/, "", o); sub(/^0x0*/, "", z) + if (o " " z == want) { print idx; exit } + } + idx++ + }') +[ -n "$seg" ] || die "the note of $elf has no segment of its own" +num "segment index" "$seg" + +# the table is mapped past everything the file already maps +maxva=0 +for e in $(echo "$segs" | awk '$1 == "LOAD" && $3 ~ /^0x/ { print $3 "+" $6 }'); do + e=$(( $e )) + [ $e -gt $maxva ] && maxva=$e +done +[ $maxva -gt 0 ] || die "$elf has no loadable segment" + +paylen=$(wc -c < "$pay") || die "cannot read $pay" +[ "$paylen" -gt 0 ] || die "$pay is empty" +elflen=$(wc -c < "$elf") +off=$(( (elflen + PAGE - 1) / PAGE * PAGE )) +vaddr=$(( (maxva + PAGE - 1) / PAGE * PAGE )) + +# writes bytes of at , in the file's byte order +wr() { + _o=$1; _s=$2; _v=$3; _i=0; _e='' + while [ $_i -lt $_s ]; do + _b=$(printf '\\0%03o' $(( (_v >> (8 * _i)) & 255 ))) + if [ "$be" = 1 ]; then _e="$_b$_e"; else _e="$_e$_b"; fi + _i=$(( _i + 1 )) + done + printf '%b' "$_e" | dd of="$elf" bs=1 seek=$_o conv=notrunc 2>/dev/null || + die "cannot patch $elf" +} + +# append at a page-aligned end, so that offset and address stay congruent +dd if=/dev/zero bs=1 count=$(( off - elflen )) 2>/dev/null >> "$elf" || die "cannot pad $elf" +cat "$pay" >> "$elf" || die "cannot append the table to $elf" + +if [ $w = 8 ]; then + sh_type=4; sh_flags=8; sh_addr=16; sh_off=24; sh_size=32; sh_align=48 + ph_type=0; ph_flags=4; ph_off=8; ph_va=16; ph_pa=24; ph_fsz=32; ph_msz=40; ph_align=48 +else + sh_type=4; sh_flags=8; sh_addr=12; sh_off=16; sh_size=20; sh_align=32 + ph_type=0; ph_off=4; ph_va=8; ph_pa=12; ph_fsz=16; ph_msz=20; ph_flags=24; ph_align=28 +fi + +s=$(( shoff + sec * shentsize )) +wr $(( s + sh_type )) 4 1 # SHT_PROGBITS +wr $(( s + sh_flags )) $w 2 # SHF_ALLOC +wr $(( s + sh_addr )) $w $vaddr +wr $(( s + sh_off )) $w $off +wr $(( s + sh_size )) $w $paylen +wr $(( s + sh_align )) $w 16 + +p=$(( phoff + seg * phentsize )) +wr $(( p + ph_type )) 4 1 # PT_LOAD +wr $(( p + ph_flags )) 4 4 # PF_R +wr $(( p + ph_off )) $w $off +wr $(( p + ph_va )) $w $vaddr +wr $(( p + ph_pa )) $w $vaddr +wr $(( p + ph_fsz )) $w $paylen +wr $(( p + ph_msz )) $w $paylen +wr $(( p + ph_align )) $w $PAGE diff --git a/src/sym.c b/src/sym.c index f30dfb9b1..9763877aa 100644 --- a/src/sym.c +++ b/src/sym.c @@ -16,9 +16,10 @@ * linker exposes dl_iterate_phdr(), from the exported symbols of the * executable and of every loaded shared library, read straight from their * memory image (what dladdr() sees, but lock-free and usable in a chroot). - * It also carries the helpers needed to collect the symbols of an ELF image - * available in memory, which the next commits use to read the symbol tables - * found in the objects themselves and in their files. + * The compressed symbol tables that objects may embed in a loadable + * .gnu_debugdata section (the MiniDebugInfo format that gdb also reads) are + * decompressed and parsed as well, which brings the local/static functions + * and works even on a stripped or unreadable file. * * Lookups (sym_resolve()) are lock-free and async-signal-safe: they only read * an immutable snapshot, replaced only while no thread is running and @@ -134,6 +135,10 @@ static struct sym_table * volatile cur_symtab; struct sym_build { struct sym_entry *syms; /* growing array of collected symbols */ unsigned int n, alloc; + void **bufs; /* memory blocks to release at the end of the build */ + unsigned int nbufs, balloc; + const unsigned char *debugdata; /* compressed table of the object being collected */ + size_t debugdata_size; struct sym_obj **objs; /* objects created this build, handed to the snapshot */ unsigned int nobjs, oalloc; int oom; /* set on any allocation failure */ @@ -230,7 +235,30 @@ static void add_known(struct sym_build *b, const struct sym_obj *obj) #include #include +#include +/* Largest decompressed symbol table we accept, and largest LZMA2 dictionary we + * let the decoder allocate for it. + */ +#define SYM_DEBUGDATA_MAX (64 * 1024 * 1024) +#define SYM_DEBUGDATA_DICT_MAX (64 * 1024 * 1024) + +#ifdef USE_MINIDEBUG +/* Placeholders for this program's own table, filled in by scripts/minidbg.sh + * after the link: the section gives it the name gdb looks for, and the note, + * whose alignment makes linkers give it a segment of its own, gives it the + * segment entry that is turned into a loadable one. + */ +__attribute__((section(".gnu_debugdata"), aligned(16), used)) +static const unsigned char sym_debugdata_room[16]; + +__attribute__((section(".note.hapdbg"), aligned(16), used)) +static const struct { + uint32_t namesz, descsz, type; + char name[8]; + uint32_t desc; +} sym_debugdata_note = { 7, 4, 1, "HAPDBG", 0 }; +#endif /* native ELF class / endianness / symbol-type accessor, derived from the * toolchain since glibc doesn't provide *_NATIVE convenience macros. @@ -267,7 +295,7 @@ static inline int in_map(const void *ptr, size_t len, const void *map, size_t sz /* validates an ELF header for the native class/endianness and a sane section * header table that fits in the -byte mapping. Returns 1 if usable. */ -static __maybe_unused int elf_hdr_ok(const ElfW(Ehdr) *eh, size_t sz) +static int elf_hdr_ok(const ElfW(Ehdr) *eh, size_t sz) { if (!in_map(eh, sizeof(*eh), eh, sz)) return 0; @@ -290,8 +318,25 @@ static __maybe_unused int elf_hdr_ok(const ElfW(Ehdr) *eh, size_t sz) +/* records a memory block to release when the build finishes */ +static int build_keep_buf(struct sym_build *b, void *buf) +{ + if (b->nbufs >= b->balloc) { + unsigned int na = b->balloc ? b->balloc * 2 : 8; + void **nb = realloc(b->bufs, na * sizeof(*nb)); -static __maybe_unused void collect_syms(struct sym_build *b, const void *map, size_t sz, + if (!nb) { + b->oom = 1; + return -1; + } + b->bufs = nb; + b->balloc = na; + } + b->bufs[b->nbufs++] = buf; + return 0; +} + +static void collect_syms(struct sym_build *b, const void *map, size_t sz, const ElfW(Shdr) *symsh, const ElfW(Shdr) *strsh, unsigned long base, const struct sym_obj *obj) { @@ -337,7 +382,7 @@ static __maybe_unused void collect_syms(struct sym_build *b, const void *map, si -static __maybe_unused void elf_find_symtab(const ElfW(Ehdr) *eh, +static void elf_find_symtab(const ElfW(Ehdr) *eh, const ElfW(Shdr) **symtab, const ElfW(Shdr) **strtab) { const ElfW(Shdr) *sh = (const ElfW(Shdr) *)((const char *)eh + eh->e_shoff); @@ -353,11 +398,98 @@ static __maybe_unused void elf_find_symtab(const ElfW(Ehdr) *eh, } } +/* Decompresses the xz-compressed ELF image + */ +#if !defined(HA_HAVE_DL_ITERATE_PHDR) +/* the loader callback is its only caller here; the file parsing which also + * needs it comes next, and is itself kept compiled without the API. + */ +__maybe_unused +#endif +static void collect_debugdata(struct sym_build *b, const struct sym_obj *obj, + const unsigned char *blob, size_t size) +{ + static const unsigned char magic[6] = { 0xfd, '7', 'z', 'X', 'Z', 0x00 }; + static int crc_done; + const ElfW(Shdr) *symtab, *strtab; + const ElfW(Ehdr) *eh; + struct xz_dec *dec; + struct xz_buf xb; + unsigned char *out, *new; + size_t outsz = 1024 * 1024; + enum xz_ret ret; + + /* room left empty by a build without the required tools, or not a + * compressed table at all. + */ + if (size < sizeof(magic) || memcmp(blob, magic, sizeof(magic)) != 0) + return; + + if (!crc_done) { + xz_crc32_init(); + xz_crc64_init(); + crc_done = 1; + } + + dec = xz_dec_init(XZ_DYNALLOC, SYM_DEBUGDATA_DICT_MAX); + if (!dec) + return; + + out = malloc(outsz); + if (!out) + goto end; + + memset(&xb, 0, sizeof(xb)); + xb.in = blob; + xb.in_size = size; + xb.out = out; + xb.out_size = outsz; + + while ((ret = xz_dec_run(dec, &xb)) == XZ_OK) { + /* only a full output buffer is worth retrying, anything else + * means the stream is truncated. + */ + if (xb.out_pos < xb.out_size || outsz >= SYM_DEBUGDATA_MAX) + goto end; + new = realloc(out, outsz * 2); + if (!new) + goto end; + out = new; + outsz *= 2; + xb.out = out; + xb.out_size = outsz; + } + + if (ret != XZ_STREAM_END) + goto end; + + eh = (const ElfW(Ehdr) *)out; + if (!elf_hdr_ok(eh, xb.out_pos)) + goto end; + + elf_find_symtab(eh, &symtab, &strtab); + if (!symtab || build_keep_buf(b, out) != 0) + goto end; + + collect_syms(b, out, xb.out_pos, symtab, strtab, obj->base, obj); + out = NULL; /* now owned by the builder */ + end: + free(out); + xz_dec_end(dec); +} #if defined(HA_HAVE_DL_ITERATE_PHDR) +/* A compressed symbol table stored into an object by the build sits alone in a + * segment of its own (see scripts/minidbg.sh), which is how it is recognised + * below: nothing has to be exported for it, and the same applies to any other + * object carrying one. Being in a segment is what makes it readable from memory + * alone once the file is stripped, replaced or unreachable. + */ +static const unsigned char sym_xz_magic[6] = { 0xfd, '7', 'z', 'X', 'Z', 0x00 }; + /* * Collects the exported symbols of the object as provided by the dynamic * linker. @@ -373,6 +505,9 @@ static void collect_dynsym_mem(struct sym_build *b, const struct dl_phdr_info *i unsigned long base = info->dlpi_addr; size_t strsz = 0, nsyms = 0, i; + b->debugdata = NULL; + b->debugdata_size = 0; + for (i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type == PT_DYNAMIC) { dyn = (const ElfW(Dyn) *)(base + info->dlpi_phdr[i].p_vaddr); @@ -428,22 +563,42 @@ static void collect_dynsym_mem(struct sym_build *b, const struct dl_phdr_info *i for (i = 0; i < nsyms && !b->oom; i++) { unsigned char type = SYM_ST_TYPE(sym[i].st_info); - if (type != STT_FUNC && type != STT_GNU_IFUNC) - continue; if (sym[i].st_shndx == SHN_UNDEF || sym[i].st_shndx >= SHN_LORESERVE) continue; if (!sym[i].st_value || sym[i].st_name >= strsz || !str[sym[i].st_name]) continue; + if (type != STT_FUNC && type != STT_GNU_IFUNC) + continue; + build_add_sym(b, base + sym[i].st_value, sym[i].st_size, str + sym[i].st_name, obj); } } +/* Returns the end address of the loaded segment covering in the object + * described by , or 0 when it is covered by none. + */ +static unsigned long seg_end(const struct dl_phdr_info *info, unsigned long addr) +{ + unsigned long beg; + int idx; + + for (idx = 0; idx < info->dlpi_phnum; idx++) { + if (info->dlpi_phdr[idx].p_type != PT_LOAD) + continue; + beg = info->dlpi_addr + info->dlpi_phdr[idx].p_vaddr; + if (addr >= beg && addr < beg + info->dlpi_phdr[idx].p_memsz) + return beg + info->dlpi_phdr[idx].p_memsz; + } + return 0; +} + /* dl_iterate_phdr() callback: collects each loaded object into the builder. - * Exported symbols come from the memory image, and the well-known entry - * points of sym_known_fcts[] are always added for the executable. Returns - * non-zero (stopping the iteration) on OOM. + * Local symbols come from the file's .symtab (or separate debug file) when + * readable, exported ones always from the memory image, and the well-known + * entry points of sym_known_fcts[] are always added for the executable. + * Returns non-zero (stopping the iteration) on OOM. */ static int phdr_cb(struct dl_phdr_info *info, size_t size, void *data) { @@ -492,10 +647,41 @@ static int phdr_cb(struct dl_phdr_info *info, size_t size, void *data) before = b->n; - /* the memory image first, then the well-known functions (sizeless) as - * a last resort: the first collected wins dedup (radix sort is stable). + /* the embedded table first, so that it wins dedup over the memory + * image (radix sort is stable), then the well-known functions + * (sizeless) as a last resort. */ collect_dynsym_mem(b, info, obj); + + /* a table stored by the build is covered by a segment of its own, whose + * contents identify it. + */ + for (idx = 0; !b->debugdata && idx < info->dlpi_phnum; idx++) { + const unsigned char *p; + + if (info->dlpi_phdr[idx].p_type != PT_LOAD || + !(info->dlpi_phdr[idx].p_flags & PF_R) || + info->dlpi_phdr[idx].p_filesz < sizeof(sym_xz_magic)) + continue; + p = (const unsigned char *)(info->dlpi_addr + info->dlpi_phdr[idx].p_vaddr); + if (memcmp(p, sym_xz_magic, sizeof(sym_xz_magic)) == 0) { + b->debugdata = p; + b->debugdata_size = info->dlpi_phdr[idx].p_filesz; + } + } + /* stay within the mapping no matter what the segment advertises */ + if (b->debugdata) { + unsigned long end = seg_end(info, (unsigned long)b->debugdata); + + if (!end) + b->debugdata_size = 0; + else if (end - (unsigned long)b->debugdata < b->debugdata_size) + b->debugdata_size = end - (unsigned long)b->debugdata; + } + + if (b->debugdata && b->debugdata_size) + collect_debugdata(b, obj, b->debugdata, b->debugdata_size); + if (is_exe) add_known(b, obj); @@ -650,6 +836,10 @@ static void sym_build(void) published = 1; leave: + for (i = 0; i < b.nbufs; i++) + free(b.bufs[i]); + free(b.bufs); + /* on success the objects are referenced by the published symbols and * survive with the snapshot; on failure free them. */