. "$BST_LIB" # NO BST-TIMEOUT MARKER, AND THAT IS A MEASUREMENT RATHER THAN AN OVERSIGHT. # Measured on the local-verify guest, 2026-08-19: elapsed=9s over four # consecutive runs, 48 cases. The runner's 60 s default is 6.7x that -- more # headroom than the five suites that DO declare a marker give themselves (~3x # declared over measured, see the packager README) -- so declaring one here # would be a number with nothing behind it. If a case is added that pushes the # cost up, take the new figure off the `==BST-SUITE ... elapsed=` line the run # prints and declare from that, not from an estimate. # # dwarfsrcfiles -- for each compile unit in an ELF file, print the CU's own # source path and then, TAB-indented beneath it, the whole DWARF file table. # 118 lines of C over libdwfl; everything it can get wrong is libdw's, which is # what makes it a direct check that the elfutils port really works on target. # # THE FIXTURES ARE NOT BUILT HERE, AND CANNOT BE. Every binary on this image is # a wasm module, so there is no native ELF for dwarfsrcfiles to read -- pointed # at one it correctly reports the file is not ELF, which is a case below rather # than an obstacle. The tool is here for the cross-architecture objects a # developer brings to the guest, and the image already carries exactly those: # elfutils-ptest installs upstream's whole tests/ tree, fixtures included. This # suite reads four of them, the same source 90-elfutils.test and # 90-debugedit.test read. Nothing is committed to the layer. # # THE ORACLE IS eu-readelf, AND THAT IS THE readelf THE TICKET MEANS. This image # ships no GNU binutils; elfutils-binutils is what provides a readelf, under the # eu- prefix, and `eu-readelf --debug-dump=decodedline` takes and means exactly # what the ticket's `readelf --debug-dump=decodedline` does. # # WHAT "MATCH" HAS TO MEAN, AND WHY IT IS NOT EQUALITY. The two tools answer two # different questions about the same table. dwarfsrcfiles lists the CU's file # table WHOLE; decodedline names only the files that own at least one line-table # row. On both hello/world fixtures that difference is real and measured: three # paths from decodedline against five from dwarfsrcfiles, the two extras being # the include headers -- and -- which are declared in the # table and never carry a row. So the assertion that carries the ticket is # INCLUSION, checked in the direction that can fail: every path decodedline # reports must appear in dwarfsrcfiles' listing. Asserting equality would go red # on a correct port; asserting only "both are non-empty" would go green on one # that emitted a fixed string. The extras are pinned by name in their own case, # so the inclusion cannot be satisfied by a listing that simply names everything. # # WHY TWO DWARF FIXTURES AND NOT ONE. DWARF 4 and DWARF 5 do not record the file # table the same way -- DWARF 5 admits an entry 0 for the primary source file # where DWARF 4 reserves it -- so a reader that handled one and not the other # could still pass a single-fixture suite. Measured on the native control the # two fixtures produce BYTE-IDENTICAL output, which is a statement about the # reader normalising the two encodings and is asserted as its own case. # # EVERY EXPECTATION HERE WAS MEASURED, 2026-08-19, against a native x86_64 # control: the same dwarfsrcfiles.c built from the same poky recipe source, and # the same elfutils 0.191 eu-* binaries this image ships. Two would have been # got wrong by reading the source instead: a file with NO DWARF exits 0 with # EMPTY output rather than diagnosing anything, and a wrong argument COUNT is # the one error that goes through the program's own `Usage` path -- every other # bad input is diagnosed by libdwfl's argp, in a different format. # # SPAWN BUDGET. exec on this arm is a fresh wasm module instantiation, ~2-3 s # apiece. Everything after the exec that produced a string is done with shell # parameter expansion -- the line splitting, the path extraction from # decodedline's ~100-line dump, the counting, the set comparison -- so this file # costs 19 execs for 43 cases and forks nothing to reach a verdict. require dwarfsrcfiles dwarfsrcfiles/present ptests=/usr/lib/elfutils/ptest/tests tmp=${TMPDIR:-/tmp}/bst-dwarfsrcfiles.$$ mkdir -p "$tmp" 2>/dev/null || tmp=/tmp NL=' ' TAB=' ' # The two include headers the file table declares and the line table never # references. Pinned by name: they are what makes the inclusion assertion above # a statement about set membership rather than about counting. EXTRA1=/opt/local/install/gcc/lib/gcc/x86_64-pc-linux-gnu/9.0.0/include/stddef.h EXTRA2=/usr/include/stdlib.h # --- exec-free helpers ------------------------------------------------------- # dsf_split : sets cu_n/cu_list (unindented CU lines) and # ent_n/ent_list (the TAB-indented file-table entries, indent removed). Both # lists are newline-terminated so a whole-line match can be anchored. dsf_split() { cu_n=0; ent_n=0; cu_list=; ent_list= _oi=$IFS; IFS=$NL for _l in $1; do case "$_l" in "$TAB"*) ent_n=$((ent_n + 1)); ent_list=$ent_list${_l#"$TAB"}$NL ;; ?*) cu_n=$((cu_n + 1)); cu_list=$cu_list$_l$NL ;; esac done IFS=$_oi } # dsf_has : whole-line membership. dsf_has() { case "$NL$1" in *"$NL$2$NL"*) return 0 ;; *) return 1 ;; esac } # dsf_uniq : sets uniq_n/uniq_list. dsf_uniq() { uniq_n=0; uniq_list= _oi=$IFS; IFS=$NL for _l in $1; do dsf_has "$uniq_list" "$_l" || { uniq_n=$((uniq_n + 1)); uniq_list=$uniq_list$_l$NL; } done IFS=$_oi } # dsf_decoded : sets rl_n/rl_list -- the distinct source paths # eu-readelf's decoded line table names. Its file headers are the only lines of # the dump shaped " (mtime: ", which is what this matches; every # other line is a row of numbers or a "CU [..]" banner. dsf_decoded() { rl_n=0; rl_list= _oi=$IFS; IFS=$NL for _l in $1; do case "$_l" in " /"*" (mtime: "*) _p=${_l#" "}; _p=${_p%%" (mtime: "*} dsf_has "$rl_list" "$_p" || { rl_n=$((rl_n + 1)); rl_list=$rl_list$_p$NL; } ;; esac done IFS=$_oi } # --- the tool itself --------------------------------------------------------- check_rc dwarfsrcfiles/tool-executable 0 test -x /usr/bin/dwarfsrcfiles # The ONLY error the program diagnoses in its own voice. Everything else below # comes out of libdwfl's argp, so these two cases pin that the wrapper is the # one poky ships and not something the recipe substituted. out=$(dwarfsrcfiles 2>&1); rc=$? check_eq dwarfsrcfiles/usage-noargs-rc 1 "$rc" case "$out" in *Usage*) bst_ok dwarfsrcfiles/usage-noargs-msg ;; *) bst_notok dwarfsrcfiles/usage-noargs-msg "want:'Usage' got:'$(_bst_1line "$out")'" ;; esac out=$(dwarfsrcfiles "$ptests" "$ptests" 2>&1); rc=$? check_eq dwarfsrcfiles/usage-twoargs-rc 1 "$rc" case "$out" in *Usage*) bst_ok dwarfsrcfiles/usage-twoargs-msg ;; *) bst_notok dwarfsrcfiles/usage-twoargs-msg "want:'Usage' got:'$(_bst_1line "$out")'" ;; esac # A path that does not exist is libdwfl's error, not the Usage path -- so this # case is what separates "the argument reached dwfl" from "the wrapper rejected # it before ever opening anything". out=$(dwarfsrcfiles "$tmp/no-such-file" 2>&1); rc=$? check_eq dwarfsrcfiles/missing-file-rc 1 "$rc" case "$out" in *"No such file or directory"*) bst_ok dwarfsrcfiles/missing-file-msg ;; *) bst_notok dwarfsrcfiles/missing-file-msg "want strerror(ENOENT) got:'$(_bst_1line "$out")'" ;; esac # --------------------------------------------------- non-ELF input is rejected # The ticket's third requirement. Two inputs, deliberately: a text file, which # is the portable statement, and the tool's OWN binary, which on this image is a # wasm module -- the input a user here is most likely to point it at by mistake, # and the one that would prove a loader confusion rather than a parser one. printf 'this is not an ELF file\n' > "$tmp/notelf.txt" out=$(dwarfsrcfiles "$tmp/notelf.txt" 2>&1); rc=$? check_eq dwarfsrcfiles/nonelf-text-rc 1 "$rc" case "$out" in *"not a valid ELF file"*) bst_ok dwarfsrcfiles/nonelf-text-msg ;; *) bst_notok dwarfsrcfiles/nonelf-text-msg "want:'not a valid ELF file' got:'$(_bst_1line "$out")'" ;; esac out=$(dwarfsrcfiles /usr/bin/dwarfsrcfiles 2>&1); rc=$? check_eq dwarfsrcfiles/nonelf-wasm-rc 1 "$rc" case "$out" in *"not a valid ELF file"*) bst_ok dwarfsrcfiles/nonelf-wasm-msg ;; *) bst_notok dwarfsrcfiles/nonelf-wasm-msg "want:'not a valid ELF file' got:'$(_bst_1line "$out")'" ;; esac # --------------------------------------------------------------- the fixtures have_bunzip2=0 command -v bunzip2 >/dev/null 2>&1 && have_bunzip2=1 have_readelf=0 command -v eu-readelf >/dev/null 2>&1 && have_readelf=1 DWCASES="stage rc cu-count cu-hello cu-world entry-count entry-uniq \ entries-absolute deterministic readelf-rc readelf-paths match-readelf \ extras-are-headers" d4out=; d5out= for fx in testfile-dwarf-4 testfile-dwarf-5; do case "$fx" in testfile-dwarf-4) dw=dwarf4 ;; *) dw=dwarf5 ;; esac src=$ptests/$fx.bz2 obj=$tmp/$dw.elf if [ "$have_bunzip2" -eq 0 ] || [ ! -s "$src" ] \ || ! bunzip2 -c "$src" > "$obj" 2>/dev/null || [ ! -s "$obj" ]; then for c in $DWCASES; do bst_skip "dwarfsrcfiles/$dw-$c" "no $src, or no bunzip2 to expand it" done continue fi bst_ok "dwarfsrcfiles/$dw-stage" out=$(dwarfsrcfiles "$obj" 2>/dev/null); rc=$? case "$dw" in dwarf4) d4out=$out ;; *) d5out=$out ;; esac check_eq "dwarfsrcfiles/$dw-rc" 0 "$rc" dsf_split "$out" # Two compile units, hello.c and world.c, each printed as comp_dir/name. check_eq "dwarfsrcfiles/$dw-cu-count" 2 "$cu_n" if dsf_has "$cu_list" /var/tmp/hello/hello.c; then bst_ok "dwarfsrcfiles/$dw-cu-hello" else bst_notok "dwarfsrcfiles/$dw-cu-hello" "CU line absent: $(_bst_1line "$cu_list")"; fi if dsf_has "$cu_list" /var/tmp/hello/world.c; then bst_ok "dwarfsrcfiles/$dw-cu-world" else bst_notok "dwarfsrcfiles/$dw-cu-world" "CU line absent: $(_bst_1line "$cu_list")"; fi # Six entries across the two CUs; five distinct, because hello.h is in both. check_eq "dwarfsrcfiles/$dw-entry-count" 6 "$ent_n" dsf_uniq "$ent_list" ent_uniq_list=$uniq_list check_eq "dwarfsrcfiles/$dw-entry-uniq" 5 "$uniq_n" # Every entry is absolute: the program joins a relative name onto the CU's # DW_AT_comp_dir itself, so a relative path here means that join was dropped. bad= _oi=$IFS; IFS=$NL for p in $ent_list; do case "$p" in /*) ;; *) bad=$bad$p" " ;; esac; done IFS=$_oi if [ -z "$bad" ]; then bst_ok "dwarfsrcfiles/$dw-entries-absolute" else bst_notok "dwarfsrcfiles/$dw-entries-absolute" "relative entries: $(_bst_1line "$bad")"; fi out2=$(dwarfsrcfiles "$obj" 2>/dev/null) check_eq "dwarfsrcfiles/$dw-deterministic" "$out" "$out2" # ------------------------------------------- the ticket's readelf comparison if [ "$have_readelf" -eq 0 ]; then for c in readelf-rc readelf-paths match-readelf extras-are-headers; do bst_skip "dwarfsrcfiles/$dw-$c" "no eu-readelf on this image to compare against" done continue fi rout=$(eu-readelf --debug-dump=decodedline "$obj" 2>/dev/null); rrc=$? check_eq "dwarfsrcfiles/$dw-readelf-rc" 0 "$rrc" dsf_decoded "$rout" # Three files own line-table rows. Pinned rather than merely ">0" so that a # decodedline that stopped reporting file headers -- which would make the # inclusion below vacuously true -- goes red HERE instead of passing there. check_eq "dwarfsrcfiles/$dw-readelf-paths" 3 "$rl_n" miss= _oi=$IFS; IFS=$NL for p in $rl_list; do dsf_has "$ent_uniq_list" "$p" || miss=$miss$p" "; done IFS=$_oi if [ "$rl_n" -gt 0 ] && [ -z "$miss" ]; then bst_ok "dwarfsrcfiles/$dw-match-readelf" else bst_notok "dwarfsrcfiles/$dw-match-readelf" \ "readelf paths absent from the listing: $(_bst_1line "$miss")"; fi # ...and the two the listing has that decodedline does not, by name. extras= _oi=$IFS; IFS=$NL for p in $ent_uniq_list; do dsf_has "$rl_list" "$p" || extras=$extras$p$NL; done IFS=$_oi if dsf_has "$extras" "$EXTRA1" && dsf_has "$extras" "$EXTRA2"; then dsf_uniq "$extras" if [ "$uniq_n" -eq 2 ]; then bst_ok "dwarfsrcfiles/$dw-extras-are-headers" else bst_notok "dwarfsrcfiles/$dw-extras-are-headers" \ "want exactly 2 extras got $uniq_n: $(_bst_1line "$extras")"; fi else bst_notok "dwarfsrcfiles/$dw-extras-are-headers" \ "the two declared-but-unreferenced headers are not the extras: $(_bst_1line "$extras")" fi done # The cross-format statement: the two encodings of the same program read back # the same. Guarded on both having been produced, so a fixture that failed to # stage reports a skip here instead of an equality between two empty strings -- # which would pass, and would be the most misleading green in the file. if [ -n "$d4out" ] && [ -n "$d5out" ]; then check_eq dwarfsrcfiles/dwarf4-eq-dwarf5 "$d4out" "$d5out" else bst_skip dwarfsrcfiles/dwarf4-eq-dwarf5 "one of the two DWARF fixtures did not stage" fi # ------------------------------- an ELF with no DWARF at all: empty, not error # The ticket's second requirement in its structural form. testfile55-64 is an # ordinary executable that carries a build-id note and no debug information, so # it needs no strip to make the point and costs no eu-strip spawn. NODEBUG="stage rc empty" src=$ptests/testfile55-64.bz2 obj=$tmp/nodebug.elf if [ "$have_bunzip2" -eq 0 ] || [ ! -s "$src" ] \ || ! bunzip2 -c "$src" > "$obj" 2>/dev/null || [ ! -s "$obj" ]; then for c in $NODEBUG; do bst_skip "dwarfsrcfiles/nodebug-$c" "no $src, or no bunzip2"; done else bst_ok dwarfsrcfiles/nodebug-stage out=$(dwarfsrcfiles "$obj" 2>/dev/null); rc=$? check_eq dwarfsrcfiles/nodebug-rc 0 "$rc" check_eq dwarfsrcfiles/nodebug-empty "" "$out" fi # ------------------------------------- and the same thing on a STRIPPED binary # The ticket names a stripped binary specifically, so the suite strips one -- # eu-strip -g over the DWARF 4 fixture -- rather than resting on the fixture # above, which was born without debug information. stderr is asserted EMPTY on # its own: "produces an empty list rather than an error" is two claims, and a # tool that printed a diagnostic and still exited 0 would satisfy only one. STRIPCASES="strip-rc rc empty no-stderr" obj=$tmp/dwarf4.elf if [ ! -s "$obj" ]; then for c in $STRIPCASES; do bst_skip "dwarfsrcfiles/stripped-$c" "the DWARF 4 fixture did not stage"; done elif ! command -v eu-strip >/dev/null 2>&1; then for c in $STRIPCASES; do bst_skip "dwarfsrcfiles/stripped-$c" "no eu-strip on this image"; done else check_rc dwarfsrcfiles/stripped-strip-rc 0 eu-strip -g -o "$tmp/stripped.elf" "$obj" if [ -s "$tmp/stripped.elf" ]; then err=$tmp/stripped.err out=$(dwarfsrcfiles "$tmp/stripped.elf" 2>"$err"); rc=$? check_eq dwarfsrcfiles/stripped-rc 0 "$rc" check_eq dwarfsrcfiles/stripped-empty "" "$out" check_rc dwarfsrcfiles/stripped-no-stderr 1 test -s "$err" else for c in rc empty no-stderr; do bst_skip "dwarfsrcfiles/stripped-$c" "eu-strip produced no output file" done fi fi # ------------------------------------------------------ a relocatable object # ET_REL is the case the program's own comment says it opens through dwfl for: # "in case argument is an ET_REL file (like kernel modules), libdwfl will fix up # relocations for us". A reader that only handled ET_EXEC would pass everything # above and fail here. ETCASES="stage rc cu" src=$ptests/testfile-dwarf5-fat-lto.o.bz2 obj=$tmp/etrel.o if [ "$have_bunzip2" -eq 0 ] || [ ! -s "$src" ] \ || ! bunzip2 -c "$src" > "$obj" 2>/dev/null || [ ! -s "$obj" ]; then for c in $ETCASES; do bst_skip "dwarfsrcfiles/etrel-$c" "no $src, or no bunzip2"; done else bst_ok dwarfsrcfiles/etrel-stage out=$(dwarfsrcfiles "$obj" 2>/dev/null); rc=$? check_eq dwarfsrcfiles/etrel-rc 0 "$rc" case "$out" in *"/home/mark/src/elfutils/s.c"*) bst_ok dwarfsrcfiles/etrel-cu ;; *) bst_notok dwarfsrcfiles/etrel-cu "want the CU source path, got:'$(_bst_1line "$out")'" ;; esac fi rm -rf "$tmp"