addr2line.sh (9540B)
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 addr2line_jobs() { 188 case "${KIT_RT_ADDR2LINE_JOBS:-${KIT_TEST_JOBS:-auto}}" in 189 ""|1) echo 1 ;; 190 auto) 191 n=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) 192 case "$n" in ''|*[!0-9]*) n=1 ;; esac 193 [ "$n" -gt 6 ] && n=6 194 [ "$n" -lt 1 ] && n=1 195 echo "$n" ;; 196 *[!0-9]*) 197 echo "invalid KIT_RT_ADDR2LINE_JOBS=${KIT_RT_ADDR2LINE_JOBS:-${KIT_TEST_JOBS:-auto}}" >&2 198 return 2 ;; 199 *) 200 n="${KIT_RT_ADDR2LINE_JOBS:-${KIT_TEST_JOBS}}" 201 [ "$n" -lt 1 ] && n=1 202 echo "$n" ;; 203 esac 204 } 205 206 running_jobs() { 207 jobs -pr | wc -l | tr -d '[:space:]' 208 } 209 210 wait_for_slot() { 211 max=$1 212 while [ "$(running_jobs)" -ge "$max" ]; do 213 sleep 0.05 214 done 215 } 216 217 replay_events() { 218 ev=$1 219 [ -f "$ev" ] || return 0 220 while IFS="$(printf '\t')" read -r kind a b; do 221 [ -n "$kind" ] || continue 222 case "$kind" in 223 PASS) kit_pass "$a" ;; 224 FAIL) kit_fail "$a" "$b" ;; 225 SKIP) kit_skip "$a" "$b" ;; 226 SKIP_NA) kit_skip_na "$a" "$b" ;; 227 XFAIL) kit_xfail "$a" "$b" ;; 228 XPASS) kit_xpass "$a" ;; 229 TIME) kit_time "$a" "$b" ;; 230 *) kit_fail "addr2line-event" "unknown event kind $kind" ;; 231 esac 232 done < "$ev" 233 } 234 235 parallel_dir="$BUILD_DIR/.parallel.$$" 236 mkdir -p "$parallel_dir" 237 order="$parallel_dir/order" 238 : > "$order" 239 jobs_max=$(addr2line_jobs) || { 240 kit_summary test-rt-addr2line 241 kit_exit 242 } 243 244 for arch in ${KIT_RT_RUNTIME_ARCHES:-aa64 x64 rv64}; do 245 case "$arch" in 246 aa64|x64|rv64) 247 for opt in ${KIT_RT_OPT_LEVELS:-0 1}; do 248 id="$arch-O$opt" 249 ev="$parallel_dir/$id.ev" 250 out="$parallel_dir/$id.out" 251 status="$parallel_dir/$id.status" 252 printf '%s\t%s\t%s\t%s\n' "$id" "$ev" "$out" "$status" >> "$order" 253 wait_for_slot "$jobs_max" 254 ( 255 KIT_EV="$ev" 256 export KIT_EV 257 run_one "$arch" "$opt" > "$out" 2>&1 258 printf '%s\n' "$?" > "$status" 259 ) & 260 done ;; 261 *) not_ok "$arch" "unknown arch" ;; 262 esac 263 done 264 265 wait || true 266 267 while IFS="$(printf '\t')" read -r id ev out status; do 268 [ -n "$id" ] || continue 269 cat "$out" 2>/dev/null || true 270 lane_rc=$(cat "$status" 2>/dev/null || printf '1\n') 271 if [ "$lane_rc" -ne 0 ]; then 272 kit_fail "$id" "worker exit=$lane_rc" 273 fi 274 replay_events "$ev" 275 done < "$order" 276 277 kit_summary test-rt-addr2line 278 kit_exit