addr2line.sh (7462B)
1 #!/usr/bin/env bash 2 # test/rt/addr2line.sh — L3a backtrace round-trip (addr2line + symbolize). 3 # 4 # Compiles test/rt/addr2line_prog.c (which prints its own backtrace via 5 # __kit_print_backtrace), links a static non-PIE ELF, runs it to capture the 6 # raw "#N 0xADDR" lines, then symbolizes those addresses two ways, asserting 7 # the expected function names (bt_leaf / bt_mid / bt_root / test_main) appear: 8 # 9 # * `kit addr2line -f` — the GNU-style "addresses in, file:line out" lane, 10 # fed the bare addresses grepped from the stream. 11 # * `kit symbolize` — fed the raw "#N 0xADDR" stream verbatim; it must 12 # keep the "#N" framing and append "<func> at file:line" in place (the 13 # "#0 0x401136 bt_leaf at addr2line_prog.c:51:3" shape). 14 # 15 # Together they prove the freestanding capture/print path emits addresses the 16 # hosted DWARF tools resolve. See doc/plan/BACKTRACE.md (L3a / WS5). 17 # 18 # The per-arch wiring mirrors test/rt/run.sh's lane R: each arch maps to an 19 # <arch>-linux exec tuple, the matching build/rt/<triple>/libkit_rt.a, a clang 20 # freestanding start.o, a link via link-exe-runner, and an exec via 21 # exec_target. Because the image is a static non-PIE ELF, the return addresses 22 # printed at run time equal its link-time addresses, so they pipe straight to 23 # addr2line. Anything missing (rt archive, exec runner, clang start.o) is a 24 # SKIP, not a failure. Set KIT_TEST_ALLOW_SKIP=1 to exit 0 with skips. 25 26 set -u 27 28 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 29 PROG_SRC="$ROOT/test/rt/addr2line_prog.c" 30 BUILD_DIR="$ROOT/build/test/rt-addr2line" 31 KIT="$ROOT/build/kit" 32 LINK_EXE_RUNNER="$ROOT/build/test/link-exe-runner" 33 START_SRC="$ROOT/test/link/harness/start.c" 34 35 export KIT_KIT_DIR="$ROOT/test/lib" 36 # shellcheck source=../lib/kit_sh_kit.sh 37 . "$ROOT/test/lib/kit_sh_kit.sh" 38 kit_report_init 39 [ "${KIT_TEST_ALLOW_SKIP:-0}" = 1 ] || KIT_SKIP_IS_FAILURE=1 40 41 mkdir -p "$BUILD_DIR" 42 43 if [ ! -x "$KIT" ]; then 44 skip_test "kit" "kit driver missing at $KIT -- run \`make bin\` first" 45 kit_summary test-rt-addr2line 46 kit_exit 47 fi 48 if [ ! -x "$LINK_EXE_RUNNER" ]; then 49 skip_test "link-exe-runner" "missing at $LINK_EXE_RUNNER -- run \`make test-rt-runtime\`" 50 kit_summary test-rt-addr2line 51 kit_exit 52 fi 53 54 # exec_target wiring (same host-detection knobs test/rt/run.sh exports). 55 have_qemu=0 56 QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)" 57 [ -n "$QEMU_BIN" ] && have_qemu=1 58 have_podman=0 59 command -v podman >/dev/null 2>&1 && have_podman=1 60 arch_raw="$(uname -m 2>/dev/null || true)" 61 is_aarch64=0 62 if [ "$(uname -s 2>/dev/null)" = "Linux" ]; then 63 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1 64 fi 65 export have_qemu QEMU_BIN have_podman is_aarch64 66 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR" 67 export EXEC_TARGET_MOUNT_ROOT 68 # shellcheck source=../lib/exec_target.sh 69 . "$ROOT/test/lib/exec_target.sh" 70 71 arch_triple() { 72 case "$1" in 73 aa64) echo "aarch64-linux-gnu" ;; 74 x64) echo "x86_64-linux-gnu" ;; 75 rv64) echo "riscv64-linux-gnu" ;; 76 *) return 1 ;; 77 esac 78 } 79 rt_archive() { 80 case "$1" in 81 aa64) echo "$ROOT/build/rt/aarch64-linux/libkit_rt.a" ;; 82 x64) echo "$ROOT/build/rt/x86_64-linux/libkit_rt.a" ;; 83 rv64) echo "$ROOT/build/rt/riscv64-linux/libkit_rt.a" ;; 84 *) return 1 ;; 85 esac 86 } 87 clang_extra_flags() { 88 case "$1" in 89 rv64) echo "-march=rv64gc" ;; 90 *) echo "" ;; 91 esac 92 } 93 94 # Functions the backtrace must symbolize to, innermost first. _start may show 95 # as ?? (start.o is built without -g), so it is not required. 96 WANT_FUNCS="bt_leaf bt_mid bt_root test_main" 97 98 run_one() { # <arch> <opt> 99 local arch="$1" opt="$2" name="$1/O$2 round-trip" sym_name="$1/O$2 symbolize" 100 local triple rtlib extra work obj exe start_obj 101 triple="$(arch_triple "$arch")" || { not_ok "$name" "unknown arch"; return; } 102 rtlib="$(rt_archive "$arch")" 103 extra="$(clang_extra_flags "$arch")" 104 work="$BUILD_DIR/$arch/O$opt" 105 mkdir -p "$work" 106 obj="$work/prog.o"; exe="$work/prog.exe"; start_obj="$work/start.o" 107 108 if [ ! -f "$rtlib" ]; then 109 skip_test "$name" "runtime archive missing at $rtlib"; return 110 fi 111 if ! exec_target_supported "$arch"; then 112 skip_test "$name" "no execution runner"; return 113 fi 114 if ! clang --target="$triple" $extra -O1 -ffreestanding -fno-stack-protector \ 115 -fno-PIC -fno-pie -c "$START_SRC" -o "$start_obj" \ 116 >"$work/start.out" 2>"$work/start.err"; then 117 skip_test "$name" "clang cannot build start.o for $triple"; return 118 fi 119 120 # -g so addr2line has DWARF; static non-PIE so runtime addr == link addr. 121 # The chain is @[.noinline] + non-tail, so the frames survive at O1 too. 122 if ! "$KIT" cc -target "$triple" -g -O"$opt" -Werror -c "$PROG_SRC" -o "$obj" \ 123 >"$work/cc.out" 2>"$work/cc.err"; then 124 not_ok "$name" "$work/cc.err"; return 125 fi 126 if ! KIT_TEST_ARCH="$arch" "$LINK_EXE_RUNNER" -o "$exe" "$obj" "$start_obj" \ 127 --archive "$rtlib" >"$work/link.out" 2>"$work/link.err"; then 128 not_ok "$name" "$work/link.err"; return 129 fi 130 131 exec_target_run "$arch" "$exe" "$work/run.out" "$work/run.err" 132 if [ "$RUN_RC" -ne 42 ]; then 133 printf 'expected exit 42, got %s\n' "$RUN_RC" > "$work/run.diag" 134 cat "$work/run.err" >> "$work/run.diag" 2>/dev/null 135 not_ok "$name" "$work/run.diag"; return 136 fi 137 138 # --- Lane 1: kit addr2line -f over the bare captured addresses. --- 139 local addrs 140 addrs="$(grep -oE '0x[0-9a-fA-F]+' "$work/run.out" 2>/dev/null | tr '\n' ' ')" 141 if [ -z "$addrs" ]; then 142 printf 'no "#N 0xADDR" lines captured; run.out was:\n' > "$work/sym.diag" 143 cat "$work/run.out" >> "$work/sym.diag" 2>/dev/null 144 not_ok "$name" "$work/sym.diag"; return 145 fi 146 # shellcheck disable=SC2086 147 "$KIT" addr2line -f -e "$exe" $addrs >"$work/sym.out" 2>"$work/sym.err" 148 149 local fn missing="" 150 for fn in $WANT_FUNCS; do 151 grep -qw "$fn" "$work/sym.out" || missing="$missing $fn" 152 done 153 if [ -n "$missing" ]; then 154 printf 'addr2line missing function(s):%s\naddresses: %s\nsymbolized:\n' \ 155 "$missing" "$addrs" > "$work/sym.diag" 156 cat "$work/sym.out" >> "$work/sym.diag" 2>/dev/null 157 not_ok "$name" "$work/sym.diag" 158 else 159 ok "$name" 160 fi 161 162 # --- Lane 2: kit symbolize annotates the raw "#N 0xADDR" stream in place. --- 163 # The whole captured stream is piped in verbatim; symbolize must keep the 164 # "#N 0x<hex>" framing and append "<func> at file:line[:col]" on each frame 165 # line (lines without an address pass through untouched). 166 "$KIT" symbolize -e "$exe" <"$work/run.out" >"$work/symz.out" 2>"$work/symz.err" 167 168 local sym_missing="" 169 for fn in $WANT_FUNCS; do 170 # Require BOTH the preserved framing and the appended symbolization: 171 # #<n> 0x<hex> <fn> at <file>:<line> 172 grep -qE "^#[0-9]+ 0x[0-9a-fA-F]+ ${fn} at .+:[0-9]+" "$work/symz.out" \ 173 || sym_missing="$sym_missing $fn" 174 done 175 if [ -n "$sym_missing" ]; then 176 printf 'symbolize missing annotated frame(s):%s\nstream:\n' \ 177 "$sym_missing" > "$work/symz.diag" 178 cat "$work/run.out" >> "$work/symz.diag" 2>/dev/null 179 printf '\nsymbolized:\n' >> "$work/symz.diag" 180 cat "$work/symz.out" >> "$work/symz.diag" 2>/dev/null 181 not_ok "$sym_name" "$work/symz.diag" 182 else 183 ok "$sym_name" 184 fi 185 } 186 187 for arch in ${KIT_RT_RUNTIME_ARCHES:-aa64 x64 rv64}; do 188 case "$arch" in 189 aa64|x64|rv64) 190 for opt in ${KIT_RT_OPT_LEVELS:-0 1}; do run_one "$arch" "$opt"; done ;; 191 *) not_ok "$arch" "unknown arch" ;; 192 esac 193 done 194 195 kit_summary test-rt-addr2line 196 kit_exit