[PATCH] decode_stacktrace: Support heuristic caller address search

From: Masami Hiramatsu (Google)

Date: Thu Mar 05 2026 - 00:12:29 EST


From: Masami Hiramatsu (Google) <mhiramat@xxxxxxxxxx>

Add -c option to search call address search to decode_stacktrace.
This tries to decode line info backwards, starting from 1byte before
the return address, and displays the first line info it founds as
the caller address.
If it tries up to 10bytes before (or the symbol address) and still
can not find it, it gives up and decodes the return address.

With -c option:
Call Trace:
<TASK>
dump_stack_lvl (lib/dump_stack.c:94 lib/dump_stack.c:120)
lockdep_rcu_suspicious (kernel/locking/lockdep.c:6876)
event_filter_pid_sched_process_fork (kernel/trace/trace_events.c:1057)
kernel_clone (include/trace/events/sched.h:396 include/trace/events/sched.h:396 kernel/fork.c:2664)
__x64_sys_clone (kernel/fork.c:2795 kernel/fork.c:2779 kernel/fork.c:2779)
do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
? entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
? trace_irq_disable (include/trace/events/preemptirq.h:36)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)


Without -c option:
Call Trace:
<TASK>
dump_stack_lvl (lib/dump_stack.c:122)
lockdep_rcu_suspicious (kernel/locking/lockdep.c:6877)
event_filter_pid_sched_process_fork (kernel/trace/trace_events.c:?)
kernel_clone (include/trace/events/sched.h:? include/trace/events/sched.h:396 kernel/fork.c:2664)
__x64_sys_clone (kernel/fork.c:2779)
do_syscall_64 (arch/x86/entry/syscall_64.c:?)
? entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)
? trace_irq_disable (include/trace/events/preemptirq.h:36)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130)

Signed-off-by: Masami Hiramatsu (Google) <mhiramat@xxxxxxxxxx>
---
scripts/decode_stacktrace.sh | 51 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 5 deletions(-)

diff --git a/scripts/decode_stacktrace.sh b/scripts/decode_stacktrace.sh
index 8d01b741de62..78e0810af476 100755
--- a/scripts/decode_stacktrace.sh
+++ b/scripts/decode_stacktrace.sh
@@ -5,9 +5,11 @@

usage() {
echo "Usage:"
- echo " $0 -r <release>"
- echo " $0 [<vmlinux> [<base_path>|auto [<modules_path>]]]"
+ echo " $0 [-c] -r <release>"
+ echo " $0 [-c] [<vmlinux> [<base_path>|auto [<modules_path>]]]"
echo " $0 -h"
+ echo "Options:"
+ echo " -c: Decode heuristically searched call address."
}

# Try to find a Rust demangler
@@ -33,11 +35,17 @@ fi
READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX}
ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX}
NM=${UTIL_PREFIX}nm${UTIL_SUFFIX}
+call_search=false

if [[ $1 == "-h" ]] ; then
usage
exit 0
-elif [[ $1 == "-r" ]] ; then
+elif [[ $1 == "-c" ]] ; then
+ call_search=true
+ shift 1
+fi
+
+if [[ $1 == "-r" ]] ; then
vmlinux=""
basepath="auto"
modpath=""
@@ -123,6 +131,28 @@ find_module() {
return 1
}

+UNKNOWN_LINE="??:0"
+
+search_call_site() {
+ # Instead of using the return address, use the nearest line info
+ # address before given address.
+ local return_addr=${2}
+ local max=${3}
+ local i
+
+ for i in $(seq 1 ${max}); do
+ local expr=$((0x$return_addr-$i))
+ local address=$(printf "%x\n" "$expr")
+
+ local code=$(${ADDR2LINE} -i -e "${1}" "$address" 2>/dev/null)
+ local first=${code% *}
+ if [[ "$code" != "" && "$code" != ${UNKNOWN_LINE} && "${first#*:}" != "?" ]]; then
+ echo "$code"
+ break
+ fi
+ done
+}
+
parse_symbol() {
# The structure of symbol at this point is:
# ([name]+[offset]/[total length])
@@ -176,6 +206,9 @@ parse_symbol() {
# Let's start doing the math to get the exact address into the
# symbol. First, strip out the symbol total length.
local expr=${symbol%/*}
+ # Also parse the offset from symbol.
+ local offset=${expr#*+}
+ offset=$((offset))

# Now, replace the symbol name with the base address we found
# before.
@@ -190,7 +223,15 @@ parse_symbol() {
if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then
local code=${cache[$module,$address]}
else
- local code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null)
+ local code
+ if [[ $call_search == true && $offset != 0 ]]; then
+ code=$(search_call_site "$objfile" "$address" "$offset")
+ fi
+
+ if [[ "$code" == "" ]]; then
+ code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null)
+ fi
+
if [[ $aarray_support == true ]]; then
cache[$module,$address]=$code
fi
@@ -199,7 +240,7 @@ parse_symbol() {
# addr2line doesn't return a proper error code if it fails, so
# we detect it using the value it prints so that we could preserve
# the offset/size into the function and bail out
- if [[ $code == "??:0" ]]; then
+ if [[ $code == ${UNKNOWN_LINE} ]]; then
return
fi