Files
linux/tools/perf/tests/shell/test_dwarf_unwind_multithreaded.sh
Alessio Podda f2effca1ef perf unwind-libdw: Fix unwinding of multi-threaded processes
The libdw callback API has two levels: dwfl_getthread_frames() first finds
the requested thread using the next_thread() or get_thread() callback and
then walks its stack.

Since perf only has a snapshot of the stack of a single thread, it
provides a stubbed-out API that always returns the pid the Dwfl was
attached with (i.e. whatever was passed to dwfl_attach_state()), rather
than the actual sampled thread's TID.

Commit 6b2658b3f3 ("perf unwind-libdw: Don't discard loaded ELF/DWARF
after every unwind") changed libdw unwinding from recreating the Dwfl
object for each sample to caching it in struct maps, which is shared by
every thread in the process. It left next_thread() unchanged.

Since the pid passed to dwfl_attach_state() is only set at creation, only
the thread of the first sample is ever found. As a result,
dwfl_getthread_frames() fails with ESRCH when asked to unwind a sample
from another thread.

Make next_thread() return the current sample's TID, provide get_thread()
so libdw can find it directly, and pass the process PID expected by
dwfl_attach_state(). This allows libdw to unwind samples from every thread
in a multi-threaded process.

Add a shell regression test that records a four-thread workload and
verifies that libdw recovers the worker callchain for every worker TID.

Fixes: 6b2658b3f3 ("perf unwind-libdw: Don't discard loaded ELF/DWARF after every unwind")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Alessio Podda <aleph.pi.gh@gmail.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
2026-07-31 15:31:20 -07:00

66 lines
1.4 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# Test libdw unwinding of multi-threaded processes (exclusive)
set -e
if ! perf check feature -q libdw-dwarf-unwind; then
echo "Skip: libdw DWARF unwinding is not available"
exit 2
fi
tmpdir=$(mktemp -d /tmp/perf-test-dwarf-unwind-multithreaded.XXXXXX)
perf_data="$tmpdir/perf.data"
perf_script="$tmpdir/perf-script.txt"
nr_threads=4
nr_worker_threads=$((nr_threads - 1))
cleanup()
{
trap - EXIT TERM INT
rm -rf "$tmpdir"
}
trap cleanup EXIT TERM INT
if ! perf record -q -e task-clock:u -F 99 --call-graph dwarf,8192 \
-o "$perf_data" -- perf test -w thloop 2 "$nr_threads"
then
echo "Skip: failed to record task-clock:u"
exit 2
fi
if ! perf script --unwind-style=libdw \
-F comm,pid,tid,event,ip,sym -i "$perf_data" > "$perf_script"
then
echo "Error: failed to process the recording with libdw" >&2
exit 1
fi
nr_unwound_threads=$(
awk '
BEGIN { RS = "" }
# thfunc is the worker-only caller of test_loop. Finding it proves
# that libdw unwound beyond the sampled leaf for this worker TID.
/thfunc/ {
split($2, id, "/")
seen[id[2]] = 1
}
END {
for (tid in seen)
nr_tids++
print nr_tids + 0
}
' "$perf_script"
)
if [ "$nr_unwound_threads" -ne "$nr_worker_threads" ]; then
echo "Error: expected callchains for $nr_worker_threads worker TIDs," \
"found $nr_unwound_threads" >&2
exit 1
fi
exit 0