mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 19:00:25 +00:00
![Linus Torvalds](/assets/img/avatar_default.png)
Due to our compiler include directives, the build pathnames for header files often end up being of the form "$srcdir/./include/linux/xyz.h", which ends up having that extra "." path component after the build base in it. Teach faddr2line to skip that too, to make code generated in inline functions in header files match the filename for the regular C files. Rabin Vincent pointed out that I can't make a stricter regexp match by using the " at " prefix for the pathname, because that ends up being locale-dependent. But this does require that the path match be preceded by a space, to make it a bit more strict (that matters mainly if we didn't find any base_dir at all, and we only end up with the "./" part of the match) Acked-by: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Rabin Vincent <rabin@rab.in> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
177 lines
4.8 KiB
Bash
Executable file
177 lines
4.8 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# Translate stack dump function offsets.
|
|
#
|
|
# addr2line doesn't work with KASLR addresses. This works similarly to
|
|
# addr2line, but instead takes the 'func+0x123' format as input:
|
|
#
|
|
# $ ./scripts/faddr2line ~/k/vmlinux meminfo_proc_show+0x5/0x568
|
|
# meminfo_proc_show+0x5/0x568:
|
|
# meminfo_proc_show at fs/proc/meminfo.c:27
|
|
#
|
|
# If the address is part of an inlined function, the full inline call chain is
|
|
# printed:
|
|
#
|
|
# $ ./scripts/faddr2line ~/k/vmlinux native_write_msr+0x6/0x27
|
|
# native_write_msr+0x6/0x27:
|
|
# arch_static_branch at arch/x86/include/asm/msr.h:121
|
|
# (inlined by) static_key_false at include/linux/jump_label.h:125
|
|
# (inlined by) native_write_msr at arch/x86/include/asm/msr.h:125
|
|
#
|
|
# The function size after the '/' in the input is optional, but recommended.
|
|
# It's used to help disambiguate any duplicate symbol names, which can occur
|
|
# rarely. If the size is omitted for a duplicate symbol then it's possible for
|
|
# multiple code sites to be printed:
|
|
#
|
|
# $ ./scripts/faddr2line ~/k/vmlinux raw_ioctl+0x5
|
|
# raw_ioctl+0x5/0x20:
|
|
# raw_ioctl at drivers/char/raw.c:122
|
|
#
|
|
# raw_ioctl+0x5/0xb1:
|
|
# raw_ioctl at net/ipv4/raw.c:876
|
|
#
|
|
# Multiple addresses can be specified on a single command line:
|
|
#
|
|
# $ ./scripts/faddr2line ~/k/vmlinux type_show+0x10/45 free_reserved_area+0x90
|
|
# type_show+0x10/0x2d:
|
|
# type_show at drivers/video/backlight/backlight.c:213
|
|
#
|
|
# free_reserved_area+0x90/0x123:
|
|
# free_reserved_area at mm/page_alloc.c:6429 (discriminator 2)
|
|
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
|
|
command -v awk >/dev/null 2>&1 || die "awk isn't installed"
|
|
command -v readelf >/dev/null 2>&1 || die "readelf isn't installed"
|
|
command -v addr2line >/dev/null 2>&1 || die "addr2line isn't installed"
|
|
|
|
usage() {
|
|
echo "usage: faddr2line <object file> <func+offset> <func+offset>..." >&2
|
|
exit 1
|
|
}
|
|
|
|
warn() {
|
|
echo "$1" >&2
|
|
}
|
|
|
|
die() {
|
|
echo "ERROR: $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Try to figure out the source directory prefix so we can remove it from the
|
|
# addr2line output. HACK ALERT: This assumes that start_kernel() is in
|
|
# kernel/init.c! This only works for vmlinux. Otherwise it falls back to
|
|
# printing the absolute path.
|
|
find_dir_prefix() {
|
|
local objfile=$1
|
|
|
|
local start_kernel_addr=$(readelf -sW $objfile | awk '$8 == "start_kernel" {printf "0x%s", $2}')
|
|
[[ -z $start_kernel_addr ]] && return
|
|
|
|
local file_line=$(addr2line -e $objfile $start_kernel_addr)
|
|
[[ -z $file_line ]] && return
|
|
|
|
local prefix=${file_line%init/main.c:*}
|
|
if [[ -z $prefix ]] || [[ $prefix = $file_line ]]; then
|
|
return
|
|
fi
|
|
|
|
DIR_PREFIX=$prefix
|
|
return 0
|
|
}
|
|
|
|
__faddr2line() {
|
|
local objfile=$1
|
|
local func_addr=$2
|
|
local dir_prefix=$3
|
|
local print_warnings=$4
|
|
|
|
local func=${func_addr%+*}
|
|
local offset=${func_addr#*+}
|
|
offset=${offset%/*}
|
|
local size=
|
|
[[ $func_addr =~ "/" ]] && size=${func_addr#*/}
|
|
|
|
if [[ -z $func ]] || [[ -z $offset ]] || [[ $func = $func_addr ]]; then
|
|
warn "bad func+offset $func_addr"
|
|
DONE=1
|
|
return
|
|
fi
|
|
|
|
# Go through each of the object's symbols which match the func name.
|
|
# In rare cases there might be duplicates.
|
|
while read symbol; do
|
|
local fields=($symbol)
|
|
local sym_base=0x${fields[1]}
|
|
local sym_size=${fields[2]}
|
|
local sym_type=${fields[3]}
|
|
|
|
# calculate the address
|
|
local addr=$(($sym_base + $offset))
|
|
if [[ -z $addr ]] || [[ $addr = 0 ]]; then
|
|
warn "bad address: $sym_base + $offset"
|
|
DONE=1
|
|
return
|
|
fi
|
|
local hexaddr=0x$(printf %x $addr)
|
|
|
|
# weed out non-function symbols
|
|
if [[ $sym_type != "FUNC" ]]; then
|
|
[[ $print_warnings = 1 ]] &&
|
|
echo "skipping $func address at $hexaddr due to non-function symbol"
|
|
continue
|
|
fi
|
|
|
|
# if the user provided a size, make sure it matches the symbol's size
|
|
if [[ -n $size ]] && [[ $size -ne $sym_size ]]; then
|
|
[[ $print_warnings = 1 ]] &&
|
|
echo "skipping $func address at $hexaddr due to size mismatch ($size != $sym_size)"
|
|
continue;
|
|
fi
|
|
|
|
# make sure the provided offset is within the symbol's range
|
|
if [[ $offset -gt $sym_size ]]; then
|
|
[[ $print_warnings = 1 ]] &&
|
|
echo "skipping $func address at $hexaddr due to size mismatch ($offset > $sym_size)"
|
|
continue
|
|
fi
|
|
|
|
# separate multiple entries with a blank line
|
|
[[ $FIRST = 0 ]] && echo
|
|
FIRST=0
|
|
|
|
local hexsize=0x$(printf %x $sym_size)
|
|
echo "$func+$offset/$hexsize:"
|
|
addr2line -fpie $objfile $hexaddr | sed "s; $dir_prefix\(\./\)*; ;"
|
|
DONE=1
|
|
|
|
done < <(readelf -sW $objfile | awk -v f=$func '$8 == f {print}')
|
|
}
|
|
|
|
[[ $# -lt 2 ]] && usage
|
|
|
|
objfile=$1
|
|
[[ ! -f $objfile ]] && die "can't find objfile $objfile"
|
|
shift
|
|
|
|
DIR_PREFIX=supercalifragilisticexpialidocious
|
|
find_dir_prefix $objfile
|
|
|
|
FIRST=1
|
|
while [[ $# -gt 0 ]]; do
|
|
func_addr=$1
|
|
shift
|
|
|
|
# print any matches found
|
|
DONE=0
|
|
__faddr2line $objfile $func_addr $DIR_PREFIX 0
|
|
|
|
# if no match was found, print warnings
|
|
if [[ $DONE = 0 ]]; then
|
|
__faddr2line $objfile $func_addr $DIR_PREFIX 1
|
|
warn "no match for $func_addr"
|
|
fi
|
|
done
|