faddr2line: Fix overlapping text section failures, the sequel
authorJosh Poimboeuf <jpoimboe@kernel.org>
Thu, 2 Jun 2022 00:42:22 +0000 (17:42 -0700)
committerJosh Poimboeuf <jpoimboe@kernel.org>
Mon, 6 Jun 2022 18:50:11 +0000 (11:50 -0700)
If a function lives in a section other than .text, but .text also exists
in the object, faddr2line may wrongly assume .text.  This can result in
comically wrong output.  For example:

  $ scripts/faddr2line vmlinux.o enter_from_user_mode+0x1c
  enter_from_user_mode+0x1c/0x30:
  find_next_bit at /home/jpoimboe/git/linux/./include/linux/find.h:40
  (inlined by) perf_clear_dirty_counters at /home/jpoimboe/git/linux/arch/x86/events/core.c:2504

Fix it by passing the section name to addr2line, unless the object file
is vmlinux, in which case the symbol table uses absolute addresses.

Fixes: 1d1a0e7c5100 ("scripts/faddr2line: Fix overlapping text section failures")
Reported-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://lore.kernel.org/r/7d25bc1408bd3a750ac26e60d2f2815a5f4a8363.1654130536.git.jpoimboe@kernel.org
scripts/faddr2line

index 0e6268d..94ed98d 100755 (executable)
@@ -95,17 +95,25 @@ __faddr2line() {
        local print_warnings=$4
 
        local sym_name=${func_addr%+*}
-       local offset=${func_addr#*+}
-       offset=${offset%/*}
+       local func_offset=${func_addr#*+}
+       func_offset=${func_offset%/*}
        local user_size=
+       local file_type
+       local is_vmlinux=0
        [[ $func_addr =~ "/" ]] && user_size=${func_addr#*/}
 
-       if [[ -z $sym_name ]] || [[ -z $offset ]] || [[ $sym_name = $func_addr ]]; then
+       if [[ -z $sym_name ]] || [[ -z $func_offset ]] || [[ $sym_name = $func_addr ]]; then
                warn "bad func+offset $func_addr"
                DONE=1
                return
        fi
 
+       # vmlinux uses absolute addresses in the section table rather than
+       # section offsets.
+       local file_type=$(${READELF} --file-header $objfile |
+               ${AWK} '$1 == "Type:" { print $2; exit }')
+       [[ $file_type = "EXEC" ]] && is_vmlinux=1
+
        # Go through each of the object's symbols which match the func name.
        # In rare cases there might be duplicates, in which case we print all
        # matches.
@@ -114,9 +122,11 @@ __faddr2line() {
                local sym_addr=0x${fields[1]}
                local sym_elf_size=${fields[2]}
                local sym_sec=${fields[6]}
+               local sec_size
+               local sec_name
 
                # Get the section size:
-               local sec_size=$(${READELF} --section-headers --wide $objfile |
+               sec_size=$(${READELF} --section-headers --wide $objfile |
                        sed 's/\[ /\[/' |
                        ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }')
 
@@ -126,6 +136,17 @@ __faddr2line() {
                        return
                fi
 
+               # Get the section name:
+               sec_name=$(${READELF} --section-headers --wide $objfile |
+                       sed 's/\[ /\[/' |
+                       ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print $2; exit }')
+
+               if [[ -z $sec_name ]]; then
+                       warn "bad section name: section: $sym_sec"
+                       DONE=1
+                       return
+               fi
+
                # Calculate the symbol size.
                #
                # Unfortunately we can't use the ELF size, because kallsyms
@@ -174,10 +195,10 @@ __faddr2line() {
 
                sym_size=0x$(printf %x $sym_size)
 
-               # Calculate the section address from user-supplied offset:
-               local addr=$(($sym_addr + $offset))
+               # Calculate the address from user-supplied offset:
+               local addr=$(($sym_addr + $func_offset))
                if [[ -z $addr ]] || [[ $addr = 0 ]]; then
-                       warn "bad address: $sym_addr + $offset"
+                       warn "bad address: $sym_addr + $func_offset"
                        DONE=1
                        return
                fi
@@ -191,9 +212,9 @@ __faddr2line() {
                fi
 
                # Make sure the provided offset is within the symbol's range:
-               if [[ $offset -gt $sym_size ]]; then
+               if [[ $func_offset -gt $sym_size ]]; then
                        [[ $print_warnings = 1 ]] &&
-                               echo "skipping $sym_name address at $addr due to size mismatch ($offset > $sym_size)"
+                               echo "skipping $sym_name address at $addr due to size mismatch ($func_offset > $sym_size)"
                        continue
                fi
 
@@ -202,11 +223,13 @@ __faddr2line() {
                [[ $FIRST = 0 ]] && echo
                FIRST=0
 
-               echo "$sym_name+$offset/$sym_size:"
+               echo "$sym_name+$func_offset/$sym_size:"
 
                # Pass section address to addr2line and strip absolute paths
                # from the output:
-               local output=$(${ADDR2LINE} -fpie $objfile $addr | sed "s; $dir_prefix\(\./\)*; ;")
+               local args="--functions --pretty-print --inlines --exe=$objfile"
+               [[ $is_vmlinux = 0 ]] && args="$args --section=$sec_name"
+               local output=$(${ADDR2LINE} $args $addr | sed "s; $dir_prefix\(\./\)*; ;")
                [[ -z $output ]] && continue
 
                # Default output (non --list):