diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-07-17 02:55:33 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-07-17 02:55:33 +0300 |
commit | 0c182ac2ebc5470a725632b08cee9a52065bbe71 (patch) | |
tree | 27912d908e0c40995fe4c7682486315b244586e1 | |
parent | 151647ab581013b482893d4e2218cd29b005cd6b (diff) | |
parent | 8e366d83edce3065ff3372bedc281c5e217c0550 (diff) | |
download | linux-0c182ac2ebc5470a725632b08cee9a52065bbe71.tar.xz |
Merge tag 'objtool-core-2024-07-16' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull objtool updates from Ingo Molnar:
- Fix bug that caused objtool to confuse certain memory ops added by
KASAN instrumentation as stack accesses
- Various faddr2line optimizations
- Improve error messages
* tag 'objtool-core-2024-07-16' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
objtool/x86: objtool can confuse memory and stack access
objtool: Use "action" in error message to be consistent with help
scripts/faddr2line: Check only two symbols when calculating symbol size
scripts/faddr2line: Remove call to addr2line from find_dir_prefix()
scripts/faddr2line: Invoke addr2line as a single long-running process
scripts/faddr2line: Pass --addresses argument to addr2line
scripts/faddr2line: Check vmlinux only once
scripts/faddr2line: Combine three readelf calls into one
scripts/faddr2line: Reduce number of readelf calls to three
-rwxr-xr-x | scripts/faddr2line | 110 | ||||
-rw-r--r-- | tools/objtool/arch/x86/decode.c | 8 | ||||
-rw-r--r-- | tools/objtool/builtin-check.c | 4 |
3 files changed, 93 insertions, 29 deletions
diff --git a/scripts/faddr2line b/scripts/faddr2line index 587415a52b6f..fe0cc45f03be 100755 --- a/scripts/faddr2line +++ b/scripts/faddr2line @@ -85,15 +85,17 @@ command -v ${ADDR2LINE} >/dev/null 2>&1 || die "${ADDR2LINE} isn't installed" # init/main.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} --symbols --wide $objfile | sed 's/\[.*\]//' | + local start_kernel_addr=$(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${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 + run_addr2line ${start_kernel_addr} "" + [[ -z $ADDR2LINE_OUT ]] && return + local file_line=${ADDR2LINE_OUT#* at } + if [[ -z $file_line ]] || [[ $file_line = $ADDR2LINE_OUT ]]; then + return + fi local prefix=${file_line%init/main.c:*} if [[ -z $prefix ]] || [[ $prefix = $file_line ]]; then return @@ -103,6 +105,71 @@ find_dir_prefix() { return 0 } +run_readelf() { + local objfile=$1 + local out=$(${READELF} --file-header --section-headers --symbols --wide $objfile) + + # This assumes that readelf first prints the file header, then the section headers, then the symbols. + # Note: It seems that GNU readelf does not prefix section headers with the "There are X section headers" + # line when multiple options are given, so let's also match with the "Section Headers:" line. + ELF_FILEHEADER=$(echo "${out}" | sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/q;p') + ELF_SECHEADERS=$(echo "${out}" | sed -n '/There are [0-9]* section headers, starting at offset\|Section Headers:/,$p' | sed -n '/Symbol table .* contains [0-9]* entries:/q;p') + ELF_SYMS=$(echo "${out}" | sed -n '/Symbol table .* contains [0-9]* entries:/,$p') +} + +check_vmlinux() { + # vmlinux uses absolute addresses in the section table rather than + # section offsets. + IS_VMLINUX=0 + local file_type=$(echo "${ELF_FILEHEADER}" | + ${AWK} '$1 == "Type:" { print $2; exit }') + if [[ $file_type = "EXEC" ]] || [[ $file_type == "DYN" ]]; then + IS_VMLINUX=1 + fi +} + +init_addr2line() { + local objfile=$1 + + check_vmlinux + + ADDR2LINE_ARGS="--functions --pretty-print --inlines --addresses --exe=$objfile" + if [[ $IS_VMLINUX = 1 ]]; then + # If the executable file is vmlinux, we don't pass section names to + # addr2line, so we can launch it now as a single long-running process. + coproc ADDR2LINE_PROC (${ADDR2LINE} ${ADDR2LINE_ARGS}) + fi +} + +run_addr2line() { + local addr=$1 + local sec_name=$2 + + if [[ $IS_VMLINUX = 1 ]]; then + # We send to the addr2line process: (1) the address, then (2) a sentinel + # value, i.e., something that can't be interpreted as a valid address + # (i.e., ","). This causes addr2line to write out: (1) the answer for + # our address, then (2) either "?? ??:0" or "0x0...0: ..." (if + # using binutils' addr2line), or "," (if using LLVM's addr2line). + echo ${addr} >& "${ADDR2LINE_PROC[1]}" + echo "," >& "${ADDR2LINE_PROC[1]}" + local first_line + read -r first_line <& "${ADDR2LINE_PROC[0]}" + ADDR2LINE_OUT=$(echo "${first_line}" | sed 's/^0x[0-9a-fA-F]*: //') + while read -r line <& "${ADDR2LINE_PROC[0]}"; do + if [[ "$line" == "?? ??:0" ]] || [[ "$line" == "," ]] || [[ $(echo "$line" | ${GREP} "^0x00*: ") ]]; then + break + fi + ADDR2LINE_OUT+=$'\n'$(echo "$line" | sed 's/^0x[0-9a-fA-F]*: //') + done + else + # Run addr2line as a single invocation. + local sec_arg + [[ -z $sec_name ]] && sec_arg="" || sec_arg="--section=${sec_name}" + ADDR2LINE_OUT=$(${ADDR2LINE} ${ADDR2LINE_ARGS} ${sec_arg} ${addr} | sed 's/^0x[0-9a-fA-F]*: //') + fi +} + __faddr2line() { local objfile=$1 local func_addr=$2 @@ -113,8 +180,6 @@ __faddr2line() { 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 $func_offset ]] || [[ $sym_name = $func_addr ]]; then @@ -123,14 +188,6 @@ __faddr2line() { 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 }') - if [[ $file_type = "EXEC" ]] || [[ $file_type == "DYN" ]]; then - is_vmlinux=1 - fi - # 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. @@ -143,8 +200,7 @@ __faddr2line() { local sec_name # Get the section size: - sec_size=$(${READELF} --section-headers --wide $objfile | - sed 's/\[ /\[/' | + sec_size=$(echo "${ELF_SECHEADERS}" | sed 's/\[ /\[/' | ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print "0x" $6; exit }') if [[ -z $sec_size ]]; then @@ -154,8 +210,7 @@ __faddr2line() { fi # Get the section name: - sec_name=$(${READELF} --section-headers --wide $objfile | - sed 's/\[ /\[/' | + sec_name=$(echo "${ELF_SECHEADERS}" | sed 's/\[ /\[/' | ${AWK} -v sec=$sym_sec '$1 == "[" sec "]" { print $2; exit }') if [[ -z $sec_name ]]; then @@ -197,7 +252,7 @@ __faddr2line() { found=2 break fi - done < <(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2) + done < <(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${AWK} -v sec=$sym_sec '$7 == sec' | sort --key=2 | ${GREP} -A1 --no-group-separator " ${sym_name}$") if [[ $found = 0 ]]; then warn "can't find symbol: sym_name: $sym_name sym_sec: $sym_sec sym_addr: $sym_addr sym_elf_size: $sym_elf_size" @@ -249,9 +304,8 @@ __faddr2line() { # Pass section address to addr2line and strip absolute paths # from the output: - 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\(\./\)*; ;") + run_addr2line $addr $sec_name + local output=$(echo "${ADDR2LINE_OUT}" | sed "s; $dir_prefix\(\./\)*; ;") [[ -z $output ]] && continue # Default output (non --list): @@ -278,7 +332,7 @@ __faddr2line() { DONE=1 - done < <(${READELF} --symbols --wide $objfile | sed 's/\[.*\]//' | ${AWK} -v fn=$sym_name '$8 == fn') + done < <(echo "${ELF_SYMS}" | sed 's/\[.*\]//' | ${AWK} -v fn=$sym_name '$8 == fn') } [[ $# -lt 2 ]] && usage @@ -291,10 +345,14 @@ LIST=0 [[ ! -f $objfile ]] && die "can't find objfile $objfile" shift -${READELF} --section-headers --wide $objfile | ${GREP} -q '\.debug_info' || die "CONFIG_DEBUG_INFO not enabled" +run_readelf $objfile + +echo "${ELF_SECHEADERS}" | ${GREP} -q '\.debug_info' || die "CONFIG_DEBUG_INFO not enabled" + +init_addr2line $objfile DIR_PREFIX=supercalifragilisticexpialidocious -find_dir_prefix $objfile +find_dir_prefix FIRST=1 while [[ $# -gt 0 ]]; do diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 3a1d80a7878d..ed6bff0e01dc 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -125,8 +125,14 @@ bool arch_pc_relative_reloc(struct reloc *reloc) #define is_RIP() ((modrm_rm & 7) == CFI_BP && modrm_mod == 0) #define have_SIB() ((modrm_rm & 7) == CFI_SP && mod_is_mem()) +/* + * Check the ModRM register. If there is a SIB byte then check with + * the SIB base register. But if the SIB base is 5 (i.e. CFI_BP) and + * ModRM mod is 0 then there is no base register. + */ #define rm_is(reg) (have_SIB() ? \ - sib_base == (reg) && sib_index == CFI_SP : \ + sib_base == (reg) && sib_index == CFI_SP && \ + (sib_base != CFI_BP || modrm_mod != 0) : \ modrm_rm == (reg)) #define rm_is_mem(reg) (mod_is_mem() && !is_RIP() && rm_is(reg)) diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 5e21cfb7661d..387d56a7f5fb 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -144,7 +144,7 @@ static bool opts_valid(void) opts.static_call || opts.uaccess) { if (opts.dump_orc) { - ERROR("--dump can't be combined with other options"); + ERROR("--dump can't be combined with other actions"); return false; } @@ -159,7 +159,7 @@ static bool opts_valid(void) if (opts.dump_orc) return true; - ERROR("At least one command required"); + ERROR("At least one action required"); return false; } |