| 1 | #!/usr/bin/env bash |
| 2 | # SPDX-License-Identifier: GPL-2.0 |
| 3 | # (c) 2014, Sasha Levin <sasha.levin@oracle.com> |
| 4 | #set -x |
| 5 | |
| 6 | usage() { |
| 7 | echo "Usage:" |
| 8 | echo " $0 -r <release>" |
| 9 | echo " $0 [<vmlinux> [<base_path>|auto [<modules_path>]]]" |
| 10 | echo " $0 -h" |
| 11 | } |
| 12 | |
| 13 | # Try to find a Rust demangler |
| 14 | if type llvm-cxxfilt >/dev/null 2>&1 ; then |
| 15 | cppfilt=llvm-cxxfilt |
| 16 | elif type c++filt >/dev/null 2>&1 ; then |
| 17 | cppfilt=c++filt |
| 18 | cppfilt_opts=-i |
| 19 | fi |
| 20 | |
| 21 | UTIL_SUFFIX= |
| 22 | if [[ -z ${LLVM:-} ]]; then |
| 23 | UTIL_PREFIX=${CROSS_COMPILE:-} |
| 24 | else |
| 25 | UTIL_PREFIX=llvm- |
| 26 | if [[ ${LLVM} == */ ]]; then |
| 27 | UTIL_PREFIX=${LLVM}${UTIL_PREFIX} |
| 28 | elif [[ ${LLVM} == -* ]]; then |
| 29 | UTIL_SUFFIX=${LLVM} |
| 30 | fi |
| 31 | fi |
| 32 | |
| 33 | READELF=${UTIL_PREFIX}readelf${UTIL_SUFFIX} |
| 34 | ADDR2LINE=${UTIL_PREFIX}addr2line${UTIL_SUFFIX} |
| 35 | NM=${UTIL_PREFIX}nm${UTIL_SUFFIX} |
| 36 | |
| 37 | if [[ $1 == "-h" ]] ; then |
| 38 | usage |
| 39 | exit 0 |
| 40 | elif [[ $1 == "-r" ]] ; then |
| 41 | vmlinux="" |
| 42 | basepath="auto" |
| 43 | modpath="" |
| 44 | release=$2 |
| 45 | |
| 46 | for fn in {,/usr/lib/debug}/boot/vmlinux-$release{,.debug} /lib/modules/$release{,/build}/vmlinux ; do |
| 47 | if [ -e "$fn" ] ; then |
| 48 | vmlinux=$fn |
| 49 | break |
| 50 | fi |
| 51 | done |
| 52 | |
| 53 | if [[ $vmlinux == "" ]] ; then |
| 54 | echo "ERROR! vmlinux image for release $release is not found" >&2 |
| 55 | usage |
| 56 | exit 2 |
| 57 | fi |
| 58 | else |
| 59 | vmlinux=$1 |
| 60 | basepath=${2-auto} |
| 61 | modpath=$3 |
| 62 | release="" |
| 63 | debuginfod= |
| 64 | |
| 65 | # Can we use debuginfod-find? |
| 66 | if type debuginfod-find >/dev/null 2>&1 ; then |
| 67 | debuginfod=${1-only} |
| 68 | fi |
| 69 | |
| 70 | if [[ $vmlinux == "" && -z $debuginfod ]] ; then |
| 71 | echo "ERROR! vmlinux image must be specified" >&2 |
| 72 | usage |
| 73 | exit 1 |
| 74 | fi |
| 75 | fi |
| 76 | |
| 77 | declare aarray_support=true |
| 78 | declare -A cache 2>/dev/null |
| 79 | if [[ $? != 0 ]]; then |
| 80 | aarray_support=false |
| 81 | else |
| 82 | declare -A modcache |
| 83 | fi |
| 84 | |
| 85 | find_module() { |
| 86 | if [[ -n $debuginfod ]] ; then |
| 87 | if [[ -n $modbuildid ]] ; then |
| 88 | debuginfod-find debuginfo $modbuildid && return |
| 89 | fi |
| 90 | |
| 91 | # Only using debuginfod so don't try to find vmlinux module path |
| 92 | if [[ $debuginfod == "only" ]] ; then |
| 93 | return |
| 94 | fi |
| 95 | fi |
| 96 | |
| 97 | if [ -z $release ] ; then |
| 98 | release=$(gdb -ex 'print init_uts_ns.name.release' -ex 'quit' -quiet -batch "$vmlinux" 2>/dev/null | sed -n 's/\$1 = "\(.*\)".*/\1/p') |
| 99 | fi |
| 100 | if [ -n "${release}" ] ; then |
| 101 | release_dirs="/usr/lib/debug/lib/modules/$release /lib/modules/$release" |
| 102 | fi |
| 103 | |
| 104 | found_without_debug_info=false |
| 105 | for dir in "$modpath" "$(dirname "$vmlinux")" ${release_dirs}; do |
| 106 | if [ -n "${dir}" ] && [ -e "${dir}" ]; then |
| 107 | for fn in $(find "$dir" -name "${module//_/[-_]}.ko*") ; do |
| 108 | if ${READELF} -WS "$fn" | grep -qwF .debug_line ; then |
| 109 | echo $fn |
| 110 | return |
| 111 | fi |
| 112 | found_without_debug_info=true |
| 113 | done |
| 114 | fi |
| 115 | done |
| 116 | |
| 117 | if [[ ${found_without_debug_info} == true ]]; then |
| 118 | echo "WARNING! No debugging info in module ${module}, rebuild with DEBUG_KERNEL and DEBUG_INFO" >&2 |
| 119 | else |
| 120 | echo "WARNING! Cannot find .ko for module ${module}, please pass a valid module path" >&2 |
| 121 | fi |
| 122 | |
| 123 | return 1 |
| 124 | } |
| 125 | |
| 126 | parse_symbol() { |
| 127 | # The structure of symbol at this point is: |
| 128 | # ([name]+[offset]/[total length]) |
| 129 | # |
| 130 | # For example: |
| 131 | # do_basic_setup+0x9c/0xbf |
| 132 | |
| 133 | if [[ $module == "" ]] ; then |
| 134 | local objfile=$vmlinux |
| 135 | elif [[ $aarray_support == true && "${modcache[$module]+isset}" == "isset" ]]; then |
| 136 | local objfile=${modcache[$module]} |
| 137 | else |
| 138 | local objfile=$(find_module) |
| 139 | if [[ $objfile == "" ]] ; then |
| 140 | return |
| 141 | fi |
| 142 | if [[ $aarray_support == true ]]; then |
| 143 | modcache[$module]=$objfile |
| 144 | fi |
| 145 | fi |
| 146 | |
| 147 | # Remove the englobing parenthesis |
| 148 | symbol=${symbol#\(} |
| 149 | symbol=${symbol%\)} |
| 150 | |
| 151 | # Strip segment |
| 152 | local segment |
| 153 | if [[ $symbol == *:* ]] ; then |
| 154 | segment=${symbol%%:*}: |
| 155 | symbol=${symbol#*:} |
| 156 | fi |
| 157 | |
| 158 | # Strip the symbol name so that we could look it up |
| 159 | local name=${symbol%+*} |
| 160 | |
| 161 | # Use 'nm vmlinux' to figure out the base address of said symbol. |
| 162 | # It's actually faster to call it every time than to load it |
| 163 | # all into bash. |
| 164 | if [[ $aarray_support == true && "${cache[$module,$name]+isset}" == "isset" ]]; then |
| 165 | local base_addr=${cache[$module,$name]} |
| 166 | else |
| 167 | local base_addr=$(${NM} "$objfile" 2>/dev/null | awk '$3 == "'$name'" && ($2 == "t" || $2 == "T") {print $1; exit}') |
| 168 | if [[ $base_addr == "" ]] ; then |
| 169 | # address not found |
| 170 | return |
| 171 | fi |
| 172 | if [[ $aarray_support == true ]]; then |
| 173 | cache[$module,$name]="$base_addr" |
| 174 | fi |
| 175 | fi |
| 176 | # Let's start doing the math to get the exact address into the |
| 177 | # symbol. First, strip out the symbol total length. |
| 178 | local expr=${symbol%/*} |
| 179 | |
| 180 | # Now, replace the symbol name with the base address we found |
| 181 | # before. |
| 182 | expr=${expr/$name/0x$base_addr} |
| 183 | |
| 184 | # Evaluate it to find the actual address |
| 185 | expr=$((expr)) |
| 186 | local address=$(printf "%x\n" "$expr") |
| 187 | |
| 188 | # Pass it to addr2line to get filename and line number |
| 189 | # Could get more than one result |
| 190 | if [[ $aarray_support == true && "${cache[$module,$address]+isset}" == "isset" ]]; then |
| 191 | local code=${cache[$module,$address]} |
| 192 | else |
| 193 | local code=$(${ADDR2LINE} -i -e "$objfile" "$address" 2>/dev/null) |
| 194 | if [[ $aarray_support == true ]]; then |
| 195 | cache[$module,$address]=$code |
| 196 | fi |
| 197 | fi |
| 198 | |
| 199 | # addr2line doesn't return a proper error code if it fails, so |
| 200 | # we detect it using the value it prints so that we could preserve |
| 201 | # the offset/size into the function and bail out |
| 202 | if [[ $code == "??:0" ]]; then |
| 203 | return |
| 204 | fi |
| 205 | |
| 206 | # Strip out the base of the path on each line |
| 207 | code=$(while read -r line; do echo "${line#$basepath/}"; done <<< "$code") |
| 208 | |
| 209 | # In the case of inlines, move everything to same line |
| 210 | code=${code//$'\n'/' '} |
| 211 | |
| 212 | # Demangle if the name looks like a Rust symbol and if |
| 213 | # we got a Rust demangler |
| 214 | if [[ $name =~ ^_R && $cppfilt != "" ]] ; then |
| 215 | name=$("$cppfilt" "$cppfilt_opts" "$name") |
| 216 | fi |
| 217 | |
| 218 | # Replace old address with pretty line numbers |
| 219 | symbol="$segment$name ($code)" |
| 220 | } |
| 221 | |
| 222 | debuginfod_get_vmlinux() { |
| 223 | local vmlinux_buildid=${1##* } |
| 224 | |
| 225 | if [[ $vmlinux != "" ]]; then |
| 226 | return |
| 227 | fi |
| 228 | |
| 229 | if [[ $vmlinux_buildid =~ ^[0-9a-f]+ ]]; then |
| 230 | vmlinux=$(debuginfod-find debuginfo $vmlinux_buildid) |
| 231 | if [[ $? -ne 0 ]] ; then |
| 232 | echo "ERROR! vmlinux image not found via debuginfod-find" >&2 |
| 233 | usage |
| 234 | exit 2 |
| 235 | fi |
| 236 | return |
| 237 | fi |
| 238 | echo "ERROR! Build ID for vmlinux not found. Try passing -r or specifying vmlinux" >&2 |
| 239 | usage |
| 240 | exit 2 |
| 241 | } |
| 242 | |
| 243 | decode_code() { |
| 244 | local scripts=`dirname "${BASH_SOURCE[0]}"` |
| 245 | |
| 246 | echo "$1" | $scripts/decodecode |
| 247 | } |
| 248 | |
| 249 | handle_line() { |
| 250 | if [[ $basepath == "auto" && $vmlinux != "" ]] ; then |
| 251 | module="" |
| 252 | symbol="kernel_init+0x0/0x0" |
| 253 | parse_symbol |
| 254 | basepath=${symbol#kernel_init (} |
| 255 | basepath=${basepath%/init/main.c:*)} |
| 256 | fi |
| 257 | |
| 258 | local words |
| 259 | |
| 260 | # Tokenize |
| 261 | read -a words <<<"$1" |
| 262 | |
| 263 | # Remove hex numbers. Do it ourselves until it happens in the |
| 264 | # kernel |
| 265 | |
| 266 | # We need to know the index of the last element before we |
| 267 | # remove elements because arrays are sparse |
| 268 | local last=$(( ${#words[@]} - 1 )) |
| 269 | |
| 270 | for i in "${!words[@]}"; do |
| 271 | # Remove the address |
| 272 | if [[ ${words[$i]} =~ \[\<([^]]+)\>\] ]]; then |
| 273 | unset words[$i] |
| 274 | fi |
| 275 | |
| 276 | # Format timestamps with tabs |
| 277 | if [[ ${words[$i]} == \[ && ${words[$i+1]} == *\] ]]; then |
| 278 | unset words[$i] |
| 279 | words[$i+1]=$(printf "[%13s\n" "${words[$i+1]}") |
| 280 | fi |
| 281 | done |
| 282 | |
| 283 | if [[ ${words[$last]} =~ ^[0-9a-f]+\] ]]; then |
| 284 | words[$last-1]="${words[$last-1]} ${words[$last]}" |
| 285 | unset words[$last] |
| 286 | last=$(( $last - 1 )) |
| 287 | fi |
| 288 | |
| 289 | # Extract info after the symbol if present. E.g.: |
| 290 | # func_name+0x54/0x80 (P) |
| 291 | # ^^^ |
| 292 | # The regex assumes only uppercase letters will be used. To be |
| 293 | # extended if needed. |
| 294 | local info_str="" |
| 295 | if [[ ${words[$last]} =~ \([A-Z]*\) ]]; then |
| 296 | info_str=${words[$last]} |
| 297 | unset words[$last] |
| 298 | last=$(( $last - 1 )) |
| 299 | fi |
| 300 | |
| 301 | if [[ ${words[$last]} =~ \[([^]]+)\] ]]; then |
| 302 | module=${words[$last]} |
| 303 | # some traces format is "(%pS)", which like "(foo+0x0/0x1 [bar])" |
| 304 | # so $module may like "[bar])". Strip the right parenthesis firstly |
| 305 | module=${module%\)} |
| 306 | module=${module#\[} |
| 307 | module=${module%\]} |
| 308 | modbuildid=${module#* } |
| 309 | module=${module% *} |
| 310 | if [[ $modbuildid == $module ]]; then |
| 311 | modbuildid= |
| 312 | fi |
| 313 | symbol=${words[$last-1]} |
| 314 | unset words[$last-1] |
| 315 | else |
| 316 | # The symbol is the last element, process it |
| 317 | symbol=${words[$last]} |
| 318 | module= |
| 319 | modbuildid= |
| 320 | fi |
| 321 | |
| 322 | unset words[$last] |
| 323 | parse_symbol # modifies $symbol |
| 324 | |
| 325 | # Add up the line number to the symbol |
| 326 | if [[ -z ${module} ]] |
| 327 | then |
| 328 | echo "${words[@]}" "$symbol ${info_str}" |
| 329 | else |
| 330 | echo "${words[@]}" "$symbol $module ${info_str}" |
| 331 | fi |
| 332 | } |
| 333 | |
| 334 | while read line; do |
| 335 | # Strip unexpected carriage return at end of line |
| 336 | line=${line%$'\r'} |
| 337 | |
| 338 | # Let's see if we have an address in the line |
| 339 | if [[ $line =~ \[\<([^]]+)\>\] ]] || |
| 340 | [[ $line =~ [^+\ ]+\+0x[0-9a-f]+/0x[0-9a-f]+ ]]; then |
| 341 | # Translate address to line numbers |
| 342 | handle_line "$line" |
| 343 | # Is it a code line? |
| 344 | elif [[ $line == *Code:* ]]; then |
| 345 | decode_code "$line" |
| 346 | # Is it a version line? |
| 347 | elif [[ -n $debuginfod && $line =~ PID:\ [0-9]+\ Comm: ]]; then |
| 348 | debuginfod_get_vmlinux "$line" |
| 349 | else |
| 350 | # Nothing special in this line, show it as is |
| 351 | echo "$line" |
| 352 | fi |
| 353 | done |