Add a shell test for synthesized callchains from Arm CoreSight trace. The test runs only on arm64 systems with cs_etm event and gcc available.
Build a small test program for syscall, record them with CoreSight trace data, and decode with itrace callchain synthesis enabled. Verify that the push and pop callchain.
After:
perf test 150 -vvv
150: Check Arm CoreSight synthesized callchain: --- start --- test child forked, pid 13528 Test callchain push: PASS Test callchain pop: PASS ---- end(0) ---- 150: Check Arm CoreSight synthesized callchain : Ok
Assisted-by: Codex:GPT-5.5 Signed-off-by: Leo Yan leo.yan@arm.com --- .../tests/shell/test_arm_coresight_callchain.sh | 235 +++++++++++++++++++++ 1 file changed, 235 insertions(+)
diff --git a/tools/perf/tests/shell/test_arm_coresight_callchain.sh b/tools/perf/tests/shell/test_arm_coresight_callchain.sh new file mode 100755 index 0000000000000000000000000000000000000000..0e5a5d1129ae7d34f8e0c5942fb62d27db3e862d --- /dev/null +++ b/tools/perf/tests/shell/test_arm_coresight_callchain.sh @@ -0,0 +1,235 @@ +#!/bin/bash +# Check Arm CoreSight synthesized callchain (exclusive) +# SPDX-License-Identifier: GPL-2.0 + +glb_err=1 + +if ! tmpdir=$(mktemp -d /tmp/perf-cs-callchain-test.XXXXXX); then + echo "mktemp failed" + exit 1 +fi + +cleanup_files() +{ + rm -rf "$tmpdir" +} + +trap cleanup_files EXIT +trap 'cleanup_files; exit $glb_err' TERM INT + +skip_if_system_is_not_ready() +{ + [ "$(uname -m)" = "aarch64" ] || { + echo "Skip: arm64 only test" >&2 + return 2 + } + + perf list | grep -q 'cs_etm//' || { + echo "Skip: cs_etm event is not available" >&2 + return 2 + } + + command -v gcc >/dev/null 2>&1 || { + echo "Skip: gcc is not available" >&2 + return 2 + } + + return 0 +} + +build_test_program() +{ + local src=$1 + local bin=$2 + + gcc -g -O0 -o "$bin" "$src" +} + +record_trace() +{ + local bin=$1 + local data=$2 + local script=$3 + + perf record -m ,32M -o "$data" --per-thread -e cs_etm// -- "$bin" >/dev/null 2>&1 && + perf script --itrace=g16i10il64 -i "$data" > "$script" +} + +check_regex() +{ + local name=$1 + local regex=$2 + local script=$3 + + if grep -Pzo "$regex" "$script" >/dev/null; then + echo "Test $name: PASS" + return 0 + else + echo "Test $name: FAIL" + return 1 + fi +} + +run_test() +{ + local name=$1 + local src=$tmpdir/$name.S + local bin=$tmpdir/$name + local data=$tmpdir/perf.$name.data + local script=$tmpdir/perf.$name.script + local regex + + "${name}_src" "$src" + + if ! build_test_program "$src" "$bin"; then + echo "$name: build failed" + return + fi + + if ! record_trace "$bin" "$data" "$script"; then + echo "$name: perf record/script failed" + return + fi + + regex=$("${name}_push_regex") + check_regex "${name} push" "$regex" "$script" || return + + regex=$("${name}_pop_regex") + check_regex "${name} pop" "$regex" "$script" || return + + glb_err=0 +} + +callchain_src() +{ + cat > "$1" <<'EOF' +/* callchain.S */ + .text + + .global do_svc + .type do_svc, %function +do_svc: + stp x29, x30, [sp, #-16]! + mov x29, sp + + mov x0, #1 + adr x1, msg + mov x2, #23 + mov x8, #64 + + nop + nop // Pad nops for 9 insns before svc + + b 1f +1: + svc #0 + + nop + nop + nop + nop + nop + nop + nop + nop // Pad nops for 10 insns after svc + + ldp x29, x30, [sp], #16 + ret + .size do_svc, .-do_svc + + .global foo + .type foo, %function +foo: + stp x29, x30, [sp, #-16]! + mov x29, sp + nop + nop + nop + nop + nop + nop + nop // Pad nops for 9 insns before call + + bl do_svc + + nop + nop + nop + nop + nop + nop + nop + nop // Pad nops for 10 insns after call + + ldp x29, x30, [sp], #16 + ret + .size foo, .-foo + + .global main + .type main, %function +main: + stp x29, x30, [sp, #-16]! + mov x29, sp + + bl foo + + mov w0, #0 + ldp x29, x30, [sp], #16 + .size main, .-main + ret + + .section .rodata +msg: + .asciz "hello from svc syscall\n" +EOF +} + +callchain_push_regex() +{ + printf '%s' \ +'callchain[[:space:]]+[0-9]+ [[0-9]+][[:space:]]+10 instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ foo+0x[[:xdigit:]]+ (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ main+0xc (.*/callchain)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*'\ +'\n'\ +'callchain[[:space:]]+[0-9]+ [[0-9]+][[:space:]]+10 instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ do_svc+0x[[:xdigit:]]+ (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ foo+0x28 (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ main+0xc (.*/callchain)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*'\ +'\n'\ +'callchain[[:space:]]+[0-9]+ [[0-9]+][[:space:]]+10 instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ (vectors|el.*_64_sync|tramp_vectors)+0x[[:xdigit:]]+ ([kernel.kallsyms])\n'\ +'[[:space:]]+[[:xdigit:]]+ do_svc+0x28 (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ foo+0x28 (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ main+0xc (.*/callchain)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +} + +callchain_pop_regex() +{ + printf '%s' \ +'callchain[[:space:]]+[0-9]+ [[0-9]+][[:space:]]+10 instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ (ret_to_user|tramp_exit)+0x[[:xdigit:]]+ ([kernel.kallsyms])\n'\ +'[[:space:]]+[[:xdigit:]]+ do_svc+0x28 (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ foo+0x28 (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ main+0xc (.*/callchain)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*'\ +'\n'\ +'callchain[[:space:]]+[0-9]+ [[0-9]+][[:space:]]+10 instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ do_svc+0x[[:xdigit:]]+ (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ foo+0x28 (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ main+0xc (.*/callchain)\n' \ +'([[:space:]]+[[:xdigit:]]+ .*\n)*'\ +'\n'\ +'callchain[[:space:]]+[0-9]+ [[0-9]+][[:space:]]+10 instructions:[[:space:]]*\n'\ +'[[:space:]]+[[:xdigit:]]+ foo+0x[[:xdigit:]]+ (.*/callchain)\n'\ +'[[:space:]]+[[:xdigit:]]+ main+0xc (.*/callchain)\n'\ +'([[:space:]]+[[:xdigit:]]+ .*\n)*' +} + +skip_if_system_is_not_ready || exit 2 + +run_test "callchain" + +exit $glb_err