Back to home page

OSCL-LXR

 
 

    


0001 #!/bin/bash
0002 # SPDX-License-Identifier: GPL-2.0
0003 #
0004 # Translate stack dump function offsets.
0005 #
0006 # addr2line doesn't work with KASLR addresses.  This works similarly to
0007 # addr2line, but instead takes the 'func+0x123' format as input:
0008 #
0009 #   $ ./scripts/faddr2line ~/k/vmlinux meminfo_proc_show+0x5/0x568
0010 #   meminfo_proc_show+0x5/0x568:
0011 #   meminfo_proc_show at fs/proc/meminfo.c:27
0012 #
0013 # If the address is part of an inlined function, the full inline call chain is
0014 # printed:
0015 #
0016 #   $ ./scripts/faddr2line ~/k/vmlinux native_write_msr+0x6/0x27
0017 #   native_write_msr+0x6/0x27:
0018 #   arch_static_branch at arch/x86/include/asm/msr.h:121
0019 #    (inlined by) static_key_false at include/linux/jump_label.h:125
0020 #    (inlined by) native_write_msr at arch/x86/include/asm/msr.h:125
0021 #
0022 # The function size after the '/' in the input is optional, but recommended.
0023 # It's used to help disambiguate any duplicate symbol names, which can occur
0024 # rarely.  If the size is omitted for a duplicate symbol then it's possible for
0025 # multiple code sites to be printed:
0026 #
0027 #   $ ./scripts/faddr2line ~/k/vmlinux raw_ioctl+0x5
0028 #   raw_ioctl+0x5/0x20:
0029 #   raw_ioctl at drivers/char/raw.c:122
0030 #
0031 #   raw_ioctl+0x5/0xb1:
0032 #   raw_ioctl at net/ipv4/raw.c:876
0033 #
0034 # Multiple addresses can be specified on a single command line:
0035 #
0036 #   $ ./scripts/faddr2line ~/k/vmlinux type_show+0x10/45 free_reserved_area+0x90
0037 #   type_show+0x10/0x2d:
0038 #   type_show at drivers/video/backlight/backlight.c:213
0039 #
0040 #   free_reserved_area+0x90/0x123:
0041 #   free_reserved_area at mm/page_alloc.c:6429 (discriminator 2)
0042 
0043 
0044 set -o errexit
0045 set -o nounset
0046 
0047 usage() {
0048         echo "usage: faddr2line [--list] <object file> <func+offset> <func+offset>..." >&2
0049         exit 1
0050 }
0051 
0052 warn() {
0053         echo "$1" >&2
0054 }
0055 
0056 die() {
0057         echo "ERROR: $1" >&2
0058         exit 1
0059 }
0060 
0061 READELF="${CROSS_COMPILE:-}readelf"
0062 ADDR2LINE="${CROSS_COMPILE:-}addr2line"
0063 AWK="awk"
0064 GREP="grep"
0065 
0066 command -v ${AWK} >/dev/null 2>&1 || die "${AWK} isn't installed"
0067 command -v ${READELF} >/dev/null 2>&1 || die "${READELF} isn't installed"
0068 command -v ${ADDR2LINE} >/dev/null 2>&1 || die "${ADDR2LINE} isn't installed"
0069 
0070 # Try to figure out the source directory prefix so we can remove it from the
0071 # addr2line output.  HACK ALERT: This assumes that start_kernel() is in
0072 # init/main.c!  This only works for vmlinux.  Otherwise it falls back to
0073 # printing the absolute path.
0074 find_dir_prefix() {
0075         local objfile=$1
0076 
0077         local start_kernel_addr=$(${READELF} --symbols --wide $objfile | ${AWK} '$8 == "start_kernel" {printf "0x%s", $2}')
0078         [[ -z $start_kernel_addr ]] && return
0079 
0080         local file_line=$(${ADDR2LINE} -e $objfile $start_kernel_addr)
0081         [[ -z $file_line ]] && return
0082 
0083         local prefix=${file_line%init/main.c:*}
0084         if [[ -z $prefix ]] || [[ $prefix = $file_line ]]; then
0085                 return
0086         fi
0087 
0088         DIR_PREFIX=$prefix
0089         return 0
0090 }
0091 
0092 __faddr2line() {
0093         local objfile=$1
0094         local func_addr=$2
0095         local dir_prefix=$3
0096         local print_warnings=$4
0097 
0098         local sym_name=${func_addr%+*}
0099         local func_offset=${func_addr#*+}
0100         func_offset=${func_offset%/*}
0101         local user_size=
0102         local file_type
0103         local is_vmlinux=0
0104         [[ $func_addr =~ "/" ]] && user_size=${func_addr#*/}
0105 
0106         if [[ -z $sym_name ]] || [[ -z $func_offset ]] || [[ $sym_name = $func_addr ]]; then
0107                 warn "bad func+offset $func_addr"
0108                 DONE=1
0109                 return
0110         fi
0111 
0112         # vmlinux uses absolute addresses in the section table rather than
0113         # section offsets.
0114         local file_type=$(${READELF} --file-header $objfile |
0115                 ${AWK} '$1 == "Type:" { print $2; exit }')
0116         if [[ $file_type = "EXEC" ]] || [[ $file_type == "DYN" ]]; then
0117                 is_vmlinux=1
0118         fi
0119 
0120         # Go through each of the object's symbols which match the func name.
0121         # In rare cases there might be duplicates, in which case we print all
0122         # matches.
0123         while read line; do
0124                 local fields=($line)
0125                 local sym_addr=0x${fields[1]}
0126                 local sym_elf_size=${fields[2]}
0127                 local sym_sec=${fields[6]}
0128                 local sec_size
0129                 local sec_name
0130 
0131                 # Get the section size:
0132                 sec_size=$(${READELF} --section-headers --wide $objfile |
0133                         sed 's/\[ /\[/' |
0134                         ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }')
0135 
0136                 if [[ -z $sec_size ]]; then
0137                         warn "bad section size: section: $sym_sec"
0138                         DONE=1
0139                         return
0140                 fi
0141 
0142                 # Get the section name:
0143                 sec_name=$(${READELF} --section-headers --wide $objfile |
0144                         sed 's/\[ /\[/' |
0145                         ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print $2; exit }')
0146 
0147                 if [[ -z $sec_name ]]; then
0148                         warn "bad section name: section: $sym_sec"
0149                         DONE=1
0150                         return
0151                 fi
0152 
0153                 # Calculate the symbol size.
0154                 #
0155                 # Unfortunately we can't use the ELF size, because kallsyms
0156                 # also includes the padding bytes in its size calculation.  For
0157                 # kallsyms, the size calculation is the distance between the
0158                 # symbol and the next symbol in a sorted list.
0159                 local sym_size
0160                 local cur_sym_addr
0161                 local found=0
0162                 while read line; do
0163                         local fields=($line)
0164                         cur_sym_addr=0x${fields[1]}
0165                         local cur_sym_elf_size=${fields[2]}
0166                         local cur_sym_name=${fields[7]:-}
0167 
0168                         if [[ $cur_sym_addr = $sym_addr ]] &&
0169                            [[ $cur_sym_elf_size = $sym_elf_size ]] &&
0170                            [[ $cur_sym_name = $sym_name ]]; then
0171                                 found=1
0172                                 continue
0173                         fi
0174 
0175                         if [[ $found = 1 ]]; then
0176                                 sym_size=$(($cur_sym_addr - $sym_addr))
0177                                 [[ $sym_size -lt $sym_elf_size ]] && continue;
0178                                 found=2
0179                                 break
0180                         fi
0181                 done < <(${READELF} --symbols --wide $objfile | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2)
0182 
0183                 if [[ $found = 0 ]]; then
0184                         warn "can't find symbol: sym_name: $sym_name sym_sec: $sym_sec sym_addr: $sym_addr sym_elf_size: $sym_elf_size"
0185                         DONE=1
0186                         return
0187                 fi
0188 
0189                 # If nothing was found after the symbol, assume it's the last
0190                 # symbol in the section.
0191                 [[ $found = 1 ]] && sym_size=$(($sec_size - $sym_addr))
0192 
0193                 if [[ -z $sym_size ]] || [[ $sym_size -le 0 ]]; then
0194                         warn "bad symbol size: sym_addr: $sym_addr cur_sym_addr: $cur_sym_addr"
0195                         DONE=1
0196                         return
0197                 fi
0198 
0199                 sym_size=0x$(printf %x $sym_size)
0200 
0201                 # Calculate the address from user-supplied offset:
0202                 local addr=$(($sym_addr + $func_offset))
0203                 if [[ -z $addr ]] || [[ $addr = 0 ]]; then
0204                         warn "bad address: $sym_addr + $func_offset"
0205                         DONE=1
0206                         return
0207                 fi
0208                 addr=0x$(printf %x $addr)
0209 
0210                 # If the user provided a size, make sure it matches the symbol's size:
0211                 if [[ -n $user_size ]] && [[ $user_size -ne $sym_size ]]; then
0212                         [[ $print_warnings = 1 ]] &&
0213                                 echo "skipping $sym_name address at $addr due to size mismatch ($user_size != $sym_size)"
0214                         continue;
0215                 fi
0216 
0217                 # Make sure the provided offset is within the symbol's range:
0218                 if [[ $func_offset -gt $sym_size ]]; then
0219                         [[ $print_warnings = 1 ]] &&
0220                                 echo "skipping $sym_name address at $addr due to size mismatch ($func_offset > $sym_size)"
0221                         continue
0222                 fi
0223 
0224                 # In case of duplicates or multiple addresses specified on the
0225                 # cmdline, separate multiple entries with a blank line:
0226                 [[ $FIRST = 0 ]] && echo
0227                 FIRST=0
0228 
0229                 echo "$sym_name+$func_offset/$sym_size:"
0230 
0231                 # Pass section address to addr2line and strip absolute paths
0232                 # from the output:
0233                 local args="--functions --pretty-print --inlines --exe=$objfile"
0234                 [[ $is_vmlinux = 0 ]] && args="$args --section=$sec_name"
0235                 local output=$(${ADDR2LINE} $args $addr | sed "s; $dir_prefix\(\./\)*; ;")
0236                 [[ -z $output ]] && continue
0237 
0238                 # Default output (non --list):
0239                 if [[ $LIST = 0 ]]; then
0240                         echo "$output" | while read -r line
0241                         do
0242                                 echo $line
0243                         done
0244                         DONE=1;
0245                         continue
0246                 fi
0247 
0248                 # For --list, show each line with its corresponding source code:
0249                 echo "$output" | while read -r line
0250                 do
0251                         echo
0252                         echo $line
0253                         n=$(echo $line | sed 's/.*:\([0-9]\+\).*/\1/g')
0254                         n1=$[$n-5]
0255                         n2=$[$n+5]
0256                         f=$(echo $line | sed 's/.*at \(.\+\):.*/\1/g')
0257                         ${AWK} 'NR>=strtonum("'$n1'") && NR<=strtonum("'$n2'") { if (NR=='$n') printf(">%d<", NR); else printf(" %d ", NR); printf("\t%s\n", $0)}' $f
0258                 done
0259 
0260                 DONE=1
0261 
0262         done < <(${READELF} --symbols --wide $objfile | ${AWK} -v fn=$sym_name '$4 == "FUNC" && $8 == fn')
0263 }
0264 
0265 [[ $# -lt 2 ]] && usage
0266 
0267 objfile=$1
0268 
0269 LIST=0
0270 [[ "$objfile" == "--list" ]] && LIST=1 && shift && objfile=$1
0271 
0272 [[ ! -f $objfile ]] && die "can't find objfile $objfile"
0273 shift
0274 
0275 ${READELF} --section-headers --wide $objfile | ${GREP} -q '\.debug_info' || die "CONFIG_DEBUG_INFO not enabled"
0276 
0277 DIR_PREFIX=supercalifragilisticexpialidocious
0278 find_dir_prefix $objfile
0279 
0280 FIRST=1
0281 while [[ $# -gt 0 ]]; do
0282         func_addr=$1
0283         shift
0284 
0285         # print any matches found
0286         DONE=0
0287         __faddr2line $objfile $func_addr $DIR_PREFIX 0
0288 
0289         # if no match was found, print warnings
0290         if [[ $DONE = 0 ]]; then
0291                 __faddr2line $objfile $func_addr $DIR_PREFIX 1
0292                 warn "no match for $func_addr"
0293         fi
0294 done