* [PATCH v4 00/28] objtool: Function validation tracing
@ 2025-11-13 16:48 Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 01/28] objtool: Move disassembly functions to a separated file Alexandre Chartre
` (29 more replies)
0 siblings, 30 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Hi,
These patches change objtool to disassemble code with libopcodes instead
of running objdump. You will find below:
- Changes: list of changes made in this version
- Overview: overview of the changes
- Notes: description of some particular behavior
- Examples: output examples
Thanks,
alex.
-----
Changes:
========
V4:
---
This version fixes a build issue when disassembly is not available. Compared
with V3, this is addresses by changes in patch 14 (objtool: Improve tracing
of alternative instructions). Other patches are similar to V3.
V3:
---
This version addresses comments from Josh and Peter, in particular:
- Josh: replace ERROR in disas_context_create with WARN
- Josh: do not change offstr() outside the disassembler
- Josh: duplicated "falls through to next function" warning
- Josh: validate_symbol() has extra newline before return
- Josh: "make -s" should be completely silent
- Josh: instructions with unwinding state changes are printing twice
- Josh: explain TRACE_INSN(insn, NULL): this prints an instruction with no
additional message.
- Peter: display alternative on a single line
- Peter: display nop-like instruction as NOP<n>
- Peter: in alternative show differences between jmp.d8 and jmp.d32 (jmp/jmpq is used now)
- Peter: show alternative feature name and flags
- Peter: alternative jumps to altinstr_aux - see NOTE below:
Disassembly can show default alternative jumping to .altinstr_aux
- Peter: some jump label target seems wrong (jmp +0) - NOTE below:
Disassembly can show alternative jumping to the next instruction
Other improvements:
- An alternatives is displayed on single line if each alternative has a
single instruction. Otherwise alternatives are dispayed side-by-side,
with one column for each lternative.
- Each alternative of a group alternative is displayed with its feature
name and flags: <flags><feature-name>
<flags> is made of the following characters:
'!' : ALT_FLAG_NOT
'+' : ALT_FLAG_DIRECT_CALL
'?' : unknown flag (i.e. any other flags)
- If an alternative is a jump table then "JUMP" is used as the feature
name.
- If an alternative is an exception table then "EXCEPTION" is used as the
feature name.
- Print the destination name of pv_ops calls when we can figure out if
XENPV mode is used or not. If the PV mode can't be predicted then print
the default pv_ops destination as a destination example.
- If a group alternative is a direct call then print the corresponding
pv_ops call.
Examples are shown below.
Overview:
=========
This provides the following changes to objtool.
- Disassemble code with libopcodes instead of running objdump
objtool executes the objdump command to disassemble code. In particular,
if objtool fails to validate a function then it will use objdump to
disassemble the entire file which is not very helpful when processing
a large file (like vmlinux.o).
Using libopcodes provides more control about the disassembly scope and
output, and it is possible to disassemble a single instruction or
a single function. Now when objtool fails to validate a function it
will disassemble that single function instead of disassembling the
entire file.
- Add the --trace <function> option to trace function validation
Figuring out why a function validation has failed can be difficult because
objtool checks all code flows (including alternatives) and maintains
instructions states (in particular call frame information).
The trace option allows to follow the function validation done by objtool
instruction per instruction, see what objtool is doing and get function
validation information. An output example is shown below.
- Add the --disas <function> option to disassemble functions
Disassembly is done using libopcodes and it will show the different code
alternatives.
Note: some changes are architecture specific (x86, powerpc, loongarch). Any
feedback about the behavior on powerpc and loongarch is welcome.
Notes:
======
Disassembly can show default alternative jumping to .altinstr_aux
-----------------------------------------------------------------
Disassembly can show a default alternative jumping to .altinstr_aux. This
happens when the _static_cpu_has() function is used. Its default code
jumps to .altinstr_aux where a test sequence is executed (test; jnz; jmp).
At runtime, this sequence is not used because the _static_cpu_has()
an alternative with the X86_FEATURE_ALWAYS feature.
debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 (X86_FEATURE_HYBRID_CPU) | jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS) # <alternative.debc>
dec1: perf_get_x86_pmu_capability+0x11 ud2
Disassembly can show alternative jumping to the next instruction
----------------------------------------------------------------
The disassembly can show jump tables with an alternative which jumps
to the next instruction.
For example:
def9: perf_get_x86_pmu_capability+0x49 NOP2 | jmp defb <perf_get_x86_pmu_capability+0x4b> (JUMP) # <alternative.def9>
defb: perf_get_x86_pmu_capability+0x4b mov 0x0(%rip),%rdi # 0xdf02 <x86_pmu+0xd8>
This disassembly is correct. These instructions come from:
cap->num_counters_gp = x86_pmu_num_counters(NULL)):
which will end up executing this statement:
if (static_branch_unlikely(&perf_is_hybrid) && NULL)
<do something>;
This statement is optimized to do nothing because the condition is
always false. But static_branch_unlikely() is implemented with a jump
table inside an "asm goto" statement, and "asm goto" statements are
not optimized.
So basically the code is optimized like this:
if (static_branch_unlikely(&perf_is_hybrid))
;
And this translates to the assembly code above.
Examples:
=========
Example 1 (--trace option): Trace the validation of the os_save() function
--------------------------------------------------------------------------
$ ./tools/objtool/objtool --hacks=jump_label --hacks=noinstr --hacks=skylake --ibt --orc --retpoline --rethunk --sls --static-call --uaccess --prefix=16 --link --trace os_xsave -v vmlinux.o
os_xsave: validation begin
59b20: os_xsave+0x0 push %r12 - state: cfa=rsp+16 r12=(cfa-16) stack_size=16
59b22: os_xsave+0x2 mov 0x0(%rip),%eax # 0x59b28 <alternatives_patched>
59b28: os_xsave+0x8 push %rbp - state: cfa=rsp+24 rbp=(cfa-24) stack_size=24
59b29: os_xsave+0x9 mov %rdi,%rbp
59b2c: os_xsave+0xc push %rbx - state: cfa=rsp+32 rbx=(cfa-32) stack_size=32
59b2d: os_xsave+0xd mov 0x8(%rdi),%rbx
59b31: os_xsave+0x11 mov %rbx,%r12
59b34: os_xsave+0x14 shr $0x20,%r12
59b38: os_xsave+0x18 test %eax,%eax
59b3a: os_xsave+0x1a je 0x59b62 <os_xsave+0x42> - jump taken
59b62: os_xsave+0x42 | ud2
59b64: os_xsave+0x44 | jmp 0x59b3c <os_xsave+0x1c> - unconditional jump
59b3c: os_xsave+0x1c | | xor %edx,%edx
59b3e: os_xsave+0x1e | | mov %rbx,%rsi
59b41: os_xsave+0x21 | | mov %rbp,%rdi
59b44: os_xsave+0x24 | | callq 0x59b49 <xfd_validate_state> - call
59b49: os_xsave+0x29 | | mov %ebx,%eax
59b4b: os_xsave+0x2b | | mov %r12d,%edx
| | / <alternative.59b4e> EXCEPTION for instruction at 0x59b4e <os_xsave+0x2e>
59b55: os_xsave+0x35 | | | test %ebx,%ebx
59b57: os_xsave+0x37 | | | jne 0x59b66 <os_xsave+0x46> - jump taken
59b66: os_xsave+0x46 | | | | ud2
59b68: os_xsave+0x48 | | | | pop %rbx - state: cfa=rsp+24 rbx=<undef> stack_size=24
59b69: os_xsave+0x49 | | | | pop %rbp - state: cfa=rsp+16 rbp=<undef> stack_size=16
59b6a: os_xsave+0x4a | | | | pop %r12 - state: cfa=rsp+8 r12=<undef> stack_size=8
59b6c: os_xsave+0x4c | | | | jmpq 0x59b71 <__x86_return_thunk> - return
59b57: os_xsave+0x37 | | | jne 0x59b66 <os_xsave+0x46> - jump not taken
59b59: os_xsave+0x39 | | | pop %rbx - state: cfa=rsp+24 rbx=<undef> stack_size=24
59b5a: os_xsave+0x3a | | | pop %rbp - state: cfa=rsp+16 rbp=<undef> stack_size=16
59b5b: os_xsave+0x3b | | | pop %r12 - state: cfa=rsp+8 r12=<undef> stack_size=8
59b5d: os_xsave+0x3d | | | jmpq 0x59b62 <__x86_return_thunk> - return
| | \ <alternative.59b4e> EXCEPTION end
| | / <alternative.59b4e> X86_FEATURE_XSAVES
1b2b: .altinstr_replacement+0x1b2b | | | xsaves64 0x40(%rbp)
59b53: os_xsave+0x33 | | | xor %ebx,%ebx
59b55: os_xsave+0x35 | | | test %ebx,%ebx - already visited
| | \ <alternative.59b4e> X86_FEATURE_XSAVES end
| | / <alternative.59b4e> X86_FEATURE_XSAVEC
1b26: .altinstr_replacement+0x1b26 | | | xsavec64 0x40(%rbp)
59b53: os_xsave+0x33 | | | xor %ebx,%ebx - already visited
| | \ <alternative.59b4e> X86_FEATURE_XSAVEC end
| | / <alternative.59b4e> X86_FEATURE_XSAVEOPT
1b21: .altinstr_replacement+0x1b21 | | | xsaveopt64 0x40(%rbp)
59b53: os_xsave+0x33 | | | xor %ebx,%ebx - already visited
| | \ <alternative.59b4e> X86_FEATURE_XSAVEOPT end
| | / <alternative.59b4e> DEFAULT
59b4e: os_xsave+0x2e | | xsave64 0x40(%rbp)
59b53: os_xsave+0x33 | | xor %ebx,%ebx - already visited
59b3a: os_xsave+0x1a je 0x59b62 <os_xsave+0x42> - jump not taken
59b3c: os_xsave+0x1c xor %edx,%edx - already visited
os_xsave: validation end
Example 2 (--disas option): Single Instruction Alternatives
-----------------------------------------------------------
Alternatives with single instructions are displayed on a single line.
Instructions of the different alternatives are separated with a vertical
bar (|).
$ ./tools/objtool/objtool --disas=perf_get_x86_pmu_capability --link vmlinux.o
perf_get_x86_pmu_capability:
deb0: perf_get_x86_pmu_capability+0x0 endbr64
deb4: perf_get_x86_pmu_capability+0x4 callq 0xdeb9 <__fentry__>
deb9: perf_get_x86_pmu_capability+0x9 mov %rdi,%rdx
debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 (X86_FEATURE_HYBRID_CPU) | jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS) # <alternative.debc>
dec1: perf_get_x86_pmu_capability+0x11 ud2
dec3: perf_get_x86_pmu_capability+0x13 movq $0x0,(%rdx)
deca: perf_get_x86_pmu_capability+0x1a movq $0x0,0x8(%rdx)
ded2: perf_get_x86_pmu_capability+0x22 movq $0x0,0x10(%rdx)
deda: perf_get_x86_pmu_capability+0x2a movq $0x0,0x18(%rdx)
dee2: perf_get_x86_pmu_capability+0x32 jmpq 0xdee7 <__x86_return_thunk>
dee7: perf_get_x86_pmu_capability+0x37 cmpq $0x0,0x0(%rip) # 0xdeef <x86_pmu+0x10>
deef: perf_get_x86_pmu_capability+0x3f je 0xdec3 <perf_get_x86_pmu_capability+0x13>
def1: perf_get_x86_pmu_capability+0x41 mov 0x0(%rip),%eax # 0xdef7 <x86_pmu+0x8>
def7: perf_get_x86_pmu_capability+0x47 mov %eax,(%rdi)
def9: perf_get_x86_pmu_capability+0x49 NOP2 | jmp defb <perf_get_x86_pmu_capability+0x4b> (JUMP) # <alternative.def9>
defb: perf_get_x86_pmu_capability+0x4b mov 0x0(%rip),%rdi # 0xdf02 <x86_pmu+0xd8>
df02: perf_get_x86_pmu_capability+0x52 callq 0xdf07 <__sw_hweight64> | popcnt %rdi,%rax (X86_FEATURE_POPCNT) # <alternative.df02>
df07: perf_get_x86_pmu_capability+0x57 mov %eax,0x4(%rdx)
df0a: perf_get_x86_pmu_capability+0x5a NOP2 | jmp df0c <perf_get_x86_pmu_capability+0x5c> (JUMP) # <alternative.df0a>
df0c: perf_get_x86_pmu_capability+0x5c mov 0x0(%rip),%rdi # 0xdf13 <x86_pmu+0xe0>
df13: perf_get_x86_pmu_capability+0x63 callq 0xdf18 <__sw_hweight64> | popcnt %rdi,%rax (X86_FEATURE_POPCNT) # <alternative.df13>
df18: perf_get_x86_pmu_capability+0x68 mov %eax,0x8(%rdx)
df1b: perf_get_x86_pmu_capability+0x6b mov 0x0(%rip),%eax # 0xdf21 <x86_pmu+0xf8>
df21: perf_get_x86_pmu_capability+0x71 mov %eax,0xc(%rdx)
df24: perf_get_x86_pmu_capability+0x74 mov %eax,0x10(%rdx)
df27: perf_get_x86_pmu_capability+0x77 mov 0x0(%rip),%rax # 0xdf2e <x86_pmu+0x108>
df2e: perf_get_x86_pmu_capability+0x7e mov %eax,0x14(%rdx)
df31: perf_get_x86_pmu_capability+0x81 mov 0x0(%rip),%eax # 0xdf37 <x86_pmu+0x110>
df37: perf_get_x86_pmu_capability+0x87 mov %eax,0x18(%rdx)
df3a: perf_get_x86_pmu_capability+0x8a movzbl 0x0(%rip),%ecx # 0xdf41 <x86_pmu+0x1d1>
df41: perf_get_x86_pmu_capability+0x91 movzbl 0x1c(%rdx),%eax
df45: perf_get_x86_pmu_capability+0x95 shr %cl
df47: perf_get_x86_pmu_capability+0x97 and $0x1,%ecx
df4a: perf_get_x86_pmu_capability+0x9a and $0xfffffffe,%eax
df4d: perf_get_x86_pmu_capability+0x9d or %ecx,%eax
df4f: perf_get_x86_pmu_capability+0x9f mov %al,0x1c(%rdx)
df52: perf_get_x86_pmu_capability+0xa2 jmpq 0xdf57 <__x86_return_thunk>
Example 3 (--disas option): Alternatives with multiple instructions
-------------------------------------------------------------------
Alternatives with multiple instructions are displayed side-by-side, with
an header describing the alternative. The code in the first column is the
default code of the alternative.
$ ./tools/objtool/objtool --disas=__switch_to_asm --link vmlinux.o
__switch_to_asm:
82c0: __switch_to_asm+0x0 push %rbp
82c1: __switch_to_asm+0x1 push %rbx
82c2: __switch_to_asm+0x2 push %r12
82c4: __switch_to_asm+0x4 push %r13
82c6: __switch_to_asm+0x6 push %r14
82c8: __switch_to_asm+0x8 push %r15
82ca: __switch_to_asm+0xa mov %rsp,0x1670(%rdi)
82d1: __switch_to_asm+0x11 mov 0x1670(%rsi),%rsp
82d8: __switch_to_asm+0x18 mov 0xad8(%rsi),%rbx
82df: __switch_to_asm+0x1f mov %rbx,%gs:0x0(%rip) # 0x82e7 <__stack_chk_guard>
82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | NOP1 | mov $0x10,%r12
82e8: __switch_to_asm+0x28 | | NOP1 |
82e9: __switch_to_asm+0x29 | NOP1 | callq 0x82ef <__switch_to_asm+0x2f> |
82ea: __switch_to_asm+0x2a | NOP1 | |
82eb: __switch_to_asm+0x2b | NOP1 | |
82ec: __switch_to_asm+0x2c | NOP1 | |
82ed: __switch_to_asm+0x2d | NOP1 | |
82ee: __switch_to_asm+0x2e | NOP1 | int3 | callq 0x82f4 <__switch_to_asm+0x34>
82ef: __switch_to_asm+0x2f | NOP1 | add $0x8,%rsp |
82f0: __switch_to_asm+0x30 | NOP1 | |
82f1: __switch_to_asm+0x31 | NOP1 | |
82f2: __switch_to_asm+0x32 | NOP1 | |
82f3: __switch_to_asm+0x33 | NOP1 | lfence | int3
82f4: __switch_to_asm+0x34 | NOP1 | | callq 0x82fa <__switch_to_asm+0x3a>
82f5: __switch_to_asm+0x35 | NOP1 | |
82f6: __switch_to_asm+0x36 | NOP1 | |
82f7: __switch_to_asm+0x37 | NOP1 | |
82f8: __switch_to_asm+0x38 | NOP1 | |
82f9: __switch_to_asm+0x39 | NOP1 | | int3
82fa: __switch_to_asm+0x3a | NOP1 | | add $0x10,%rsp
82fb: __switch_to_asm+0x3b | NOP1 | |
82fc: __switch_to_asm+0x3c | NOP1 | |
82fd: __switch_to_asm+0x3d | NOP1 | |
82fe: __switch_to_asm+0x3e | NOP1 | | dec %r12
82ff: __switch_to_asm+0x3f | NOP1 | |
8300: __switch_to_asm+0x40 | NOP1 | |
8301: __switch_to_asm+0x41 | NOP1 | | jne 0x82ee <__switch_to_asm+0x2e>
8302: __switch_to_asm+0x42 | NOP1 | |
8303: __switch_to_asm+0x43 | NOP1 | | lfence
8304: __switch_to_asm+0x44 | NOP1 | |
8305: __switch_to_asm+0x45 | NOP1 | |
8306: __switch_to_asm+0x46 | NOP1 | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
8307: __switch_to_asm+0x47 | NOP1 | |
8308: __switch_to_asm+0x48 | NOP1 | |
8309: __switch_to_asm+0x49 | NOP1 | |
830a: __switch_to_asm+0x4a | NOP1 | |
830b: __switch_to_asm+0x4b | NOP1 | |
830c: __switch_to_asm+0x4c | NOP1 | |
830d: __switch_to_asm+0x4d | NOP1 | |
830e: __switch_to_asm+0x4e | NOP1 | |
830f: __switch_to_asm+0x4f | NOP1 | |
8310: __switch_to_asm+0x50 | NOP1 | |
8311: __switch_to_asm+0x51 | NOP1 | |
8312: __switch_to_asm+0x52 pop %r15
8314: __switch_to_asm+0x54 pop %r14
8316: __switch_to_asm+0x56 pop %r13
8318: __switch_to_asm+0x58 pop %r12
831a: __switch_to_asm+0x5a pop %rbx
831b: __switch_to_asm+0x5b pop %rbp
831c: __switch_to_asm+0x5c jmpq 0x8321 <__switch_to>
Example 4 (--disas option): Alternative with direct call
--------------------------------------------------------
An alternative with a direct call show the pv_ops call and
the default pv_ops function for this call.
$ ./tools/objtool/objtool --disas=paravirt_read_msr --link vmlinux.o
paravirt_read_msr:
c3d0: paravirt_read_msr+0x0 mov %edi,%edi
c3d2: paravirt_read_msr+0x2 callq *0x0(%rip) # 0xc3d8 <pv_ops+0xb0> | callq pv_ops[22] ~ native_read_msr (+X86_FEATURE_ALWAYS) # <alternative.c3d2>
c3d8: paravirt_read_msr+0x8 jmpq 0xc3dd <__x86_return_thunk>
Example 5 (--disas option): Alternative with direct call for XENPV
------------------------------------------------------------------
An alternative with a direct call in the XENPV case show the pv_ops
function for XEN.
early_ioremap_pmd:
332d0: early_ioremap_pmd+0x0 push %rbx
332d1: early_ioremap_pmd+0x1 mov %rdi,%rbx
332d4: early_ioremap_pmd+0x4 callq *0x0(%rip) # 0x332da <pv_ops+0x150> | mov %cr3,%rax (!X86_FEATURE_XENPV) | callq xen_read_cr3 (+X86_FEATURE_ALWAYS) # <alternative.332d4>
332da: early_ioremap_pmd+0xa mov 0x0(%rip),%rdx # 0x332e1 <sme_me_mask>
332e1: early_ioremap_pmd+0x11 mov 0x0(%rip),%ecx # 0x332e7 <pgdir_shift>
332e7: early_ioremap_pmd+0x17 mov %rbx,%rsi
332ea: early_ioremap_pmd+0x1a and 0x0(%rip),%rax # 0x332f1 <physical_mask>
332f1: early_ioremap_pmd+0x21 not %rdx
332f4: early_ioremap_pmd+0x24 and %rdx,%rax
332f7: early_ioremap_pmd+0x27 mov %rbx,%rdx
332fa: early_ioremap_pmd+0x2a shr %cl,%rdx
332fd: early_ioremap_pmd+0x2d and $0xfffffffffffff000,%rax
33303: early_ioremap_pmd+0x33 add 0x0(%rip),%rax # 0x3330a <page_offset_base>
3330a: early_ioremap_pmd+0x3a and $0x1ff,%edx
33310: early_ioremap_pmd+0x40 lea (%rax,%rdx,8),%rdi
33314: early_ioremap_pmd+0x44 callq 0x33319 <p4d_offset+0x0>
33319: early_ioremap_pmd+0x49 mov (%rax),%rdi
3331c: early_ioremap_pmd+0x4c callq *0x0(%rip) # 0x33322 <pv_ops+0x228> | mov %rdi,%rax (!X86_FEATURE_XENPV) | callq __raw_callee_save_xen_p4d_val (+X86_FEATURE_ALWAYS) # <alternative.3331c>
33322: early_ioremap_pmd+0x52 mov 0x0(%rip),%rdx # 0x33329 <page_offset_base>
33329: early_ioremap_pmd+0x59 and 0x0(%rip),%rax # 0x33330 <physical_mask>
33330: early_ioremap_pmd+0x60 and $0xfffffffffffff000,%rax
33336: early_ioremap_pmd+0x66 mov 0xff8(%rax,%rdx,1),%rdi
3333e: early_ioremap_pmd+0x6e callq *0x0(%rip) # 0x33344 <pv_ops+0x210> | mov %rdi,%rax (!X86_FEATURE_XENPV) | callq __raw_callee_save_xen_pud_val (+X86_FEATURE_ALWAYS) # <alternative.3333e>
33344: early_ioremap_pmd+0x74 mov 0x0(%rip),%rcx # 0x3334b <physical_mask>
3334b: early_ioremap_pmd+0x7b mov %rcx,%rdx
3334e: early_ioremap_pmd+0x7e and $0xfffffffffffff000,%rdx
33355: early_ioremap_pmd+0x85 and $0x80,%dil
33359: early_ioremap_pmd+0x89 je 0x33365 <early_ioremap_pmd+0x95>
3335b: early_ioremap_pmd+0x8b and $0xffffffffc0000000,%rcx
33362: early_ioremap_pmd+0x92 mov %rcx,%rdx
33365: early_ioremap_pmd+0x95 and %rax,%rdx
33368: early_ioremap_pmd+0x98 add 0x0(%rip),%rdx # 0x3336f <page_offset_base>
3336f: early_ioremap_pmd+0x9f pop %rbx
33370: early_ioremap_pmd+0xa0 lea 0xfc8(%rdx),%rax
33377: early_ioremap_pmd+0xa7 jmpq 0x3337c <__x86_return_thunk>
-----
Alexandre Chartre (28):
objtool: Move disassembly functions to a separated file
objtool: Create disassembly context
objtool: Disassemble code with libopcodes instead of running objdump
tool build: Remove annoying newline in build output
objtool: Print symbol during disassembly
objtool: Store instruction disassembly result
objtool: Disassemble instruction on warning or backtrace
objtool: Extract code to validate instruction from the validate branch
loop
objtool: Record symbol name max length
objtool: Add option to trace function validation
objtool: Trace instruction state changes during function validation
objtool: Improve register reporting during function validation
objtool: Identify the different types of alternatives
objtool: Improve tracing of alternative instructions
objtool: Do not validate IBT for .return_sites and .call_sites
objtool: Add the --disas=<function-pattern> action
objtool: Print headers for alternatives
objtool: Disassemble group alternatives
objtool: Print addresses with alternative instructions
objtool: Disassemble exception table alternatives
objtool: Disassemble jump table alternatives
objtool: Fix address references in alternatives
objtool: Provide access to feature and flags of group alternatives
objtool: Function to get the name of a CPU feature
objtool: Improve naming of group alternatives
objtool: Get the destination name of a PV call
objtool: Improve the disassembly of the pv_ops call
objtool: Print single line for alternatives with one instruction
.../x86/tools/gen-cpu-feature-names-x86.awk | 33 +
tools/build/Makefile.feature | 4 +-
tools/objtool/Build | 3 +
tools/objtool/Makefile | 23 +
tools/objtool/arch/loongarch/decode.c | 30 +
tools/objtool/arch/loongarch/special.c | 5 +
tools/objtool/arch/powerpc/decode.c | 31 +
tools/objtool/arch/powerpc/special.c | 5 +
tools/objtool/arch/x86/Build | 8 +
tools/objtool/arch/x86/decode.c | 36 +-
tools/objtool/arch/x86/special.c | 10 +
tools/objtool/builtin-check.c | 5 +-
tools/objtool/check.c | 739 ++++++-----
tools/objtool/disas.c | 1137 +++++++++++++++++
tools/objtool/include/objtool/arch.h | 14 +-
tools/objtool/include/objtool/builtin.h | 2 +
tools/objtool/include/objtool/check.h | 41 +-
tools/objtool/include/objtool/disas.h | 75 ++
tools/objtool/include/objtool/elf.h | 7 +
tools/objtool/include/objtool/objtool.h | 6 +-
tools/objtool/include/objtool/special.h | 4 +-
tools/objtool/include/objtool/trace.h | 139 ++
tools/objtool/include/objtool/warn.h | 17 +-
tools/objtool/objtool.c | 27 +-
tools/objtool/special.c | 2 +
tools/objtool/trace.c | 204 +++
26 files changed, 2287 insertions(+), 320 deletions(-)
create mode 100644 tools/arch/x86/tools/gen-cpu-feature-names-x86.awk
create mode 100644 tools/objtool/disas.c
create mode 100644 tools/objtool/include/objtool/disas.h
create mode 100644 tools/objtool/include/objtool/trace.h
create mode 100644 tools/objtool/trace.c
--
2.43.5
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH v4 01/28] objtool: Move disassembly functions to a separated file
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 02/28] objtool: Create disassembly context Alexandre Chartre
` (28 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
objtool disassembles functions which have warnings. Move the code
to do that to a dedicated file. The code is just moved, it is not
changed.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/Build | 1 +
tools/objtool/check.c | 81 ----------------------
tools/objtool/disas.c | 90 +++++++++++++++++++++++++
tools/objtool/include/objtool/objtool.h | 2 +
4 files changed, 93 insertions(+), 81 deletions(-)
create mode 100644 tools/objtool/disas.c
diff --git a/tools/objtool/Build b/tools/objtool/Build
index a3cdf8af6635a..677bf9148cba1 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -7,6 +7,7 @@ objtool-y += special.o
objtool-y += builtin-check.o
objtool-y += elf.o
objtool-y += objtool.o
+objtool-y += disas.o
objtool-$(BUILD_ORC) += orc_gen.o
objtool-$(BUILD_ORC) += orc_dump.o
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 9004fbc067693..5207e62ab690c 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4601,87 +4601,6 @@ static int validate_reachable_instructions(struct objtool_file *file)
return warnings;
}
-/* 'funcs' is a space-separated list of function names */
-static void disas_funcs(const char *funcs)
-{
- const char *objdump_str, *cross_compile;
- int size, ret;
- char *cmd;
-
- cross_compile = getenv("CROSS_COMPILE");
- if (!cross_compile)
- cross_compile = "";
-
- objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '"
- "BEGIN { split(_funcs, funcs); }"
- "/^$/ { func_match = 0; }"
- "/<.*>:/ { "
- "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);"
- "for (i in funcs) {"
- "if (funcs[i] == f) {"
- "func_match = 1;"
- "base = strtonum(\"0x\" $1);"
- "break;"
- "}"
- "}"
- "}"
- "{"
- "if (func_match) {"
- "addr = strtonum(\"0x\" $1);"
- "printf(\"%%04x \", addr - base);"
- "print;"
- "}"
- "}' 1>&2";
-
- /* fake snprintf() to calculate the size */
- size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1;
- if (size <= 0) {
- WARN("objdump string size calculation failed");
- return;
- }
-
- cmd = malloc(size);
-
- /* real snprintf() */
- snprintf(cmd, size, objdump_str, cross_compile, objname, funcs);
- ret = system(cmd);
- if (ret) {
- WARN("disassembly failed: %d", ret);
- return;
- }
-}
-
-static void disas_warned_funcs(struct objtool_file *file)
-{
- struct symbol *sym;
- char *funcs = NULL, *tmp;
-
- for_each_sym(file, sym) {
- if (sym->warned) {
- if (!funcs) {
- funcs = malloc(strlen(sym->name) + 1);
- if (!funcs) {
- ERROR_GLIBC("malloc");
- return;
- }
- strcpy(funcs, sym->name);
- } else {
- tmp = malloc(strlen(funcs) + strlen(sym->name) + 2);
- if (!tmp) {
- ERROR_GLIBC("malloc");
- return;
- }
- sprintf(tmp, "%s %s", funcs, sym->name);
- free(funcs);
- funcs = tmp;
- }
- }
- }
-
- if (funcs)
- disas_funcs(funcs);
-}
-
__weak bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc)
{
unsigned int type = reloc_type(reloc);
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
new file mode 100644
index 0000000000000..77de46beb496c
--- /dev/null
+++ b/tools/objtool/disas.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
+ */
+
+#include <objtool/arch.h>
+#include <objtool/warn.h>
+
+#include <linux/string.h>
+
+/* 'funcs' is a space-separated list of function names */
+static void disas_funcs(const char *funcs)
+{
+ const char *objdump_str, *cross_compile;
+ int size, ret;
+ char *cmd;
+
+ cross_compile = getenv("CROSS_COMPILE");
+ if (!cross_compile)
+ cross_compile = "";
+
+ objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '"
+ "BEGIN { split(_funcs, funcs); }"
+ "/^$/ { func_match = 0; }"
+ "/<.*>:/ { "
+ "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);"
+ "for (i in funcs) {"
+ "if (funcs[i] == f) {"
+ "func_match = 1;"
+ "base = strtonum(\"0x\" $1);"
+ "break;"
+ "}"
+ "}"
+ "}"
+ "{"
+ "if (func_match) {"
+ "addr = strtonum(\"0x\" $1);"
+ "printf(\"%%04x \", addr - base);"
+ "print;"
+ "}"
+ "}' 1>&2";
+
+ /* fake snprintf() to calculate the size */
+ size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1;
+ if (size <= 0) {
+ WARN("objdump string size calculation failed");
+ return;
+ }
+
+ cmd = malloc(size);
+
+ /* real snprintf() */
+ snprintf(cmd, size, objdump_str, cross_compile, objname, funcs);
+ ret = system(cmd);
+ if (ret) {
+ WARN("disassembly failed: %d", ret);
+ return;
+ }
+}
+
+void disas_warned_funcs(struct objtool_file *file)
+{
+ struct symbol *sym;
+ char *funcs = NULL, *tmp;
+
+ for_each_sym(file, sym) {
+ if (sym->warned) {
+ if (!funcs) {
+ funcs = malloc(strlen(sym->name) + 1);
+ if (!funcs) {
+ ERROR_GLIBC("malloc");
+ return;
+ }
+ strcpy(funcs, sym->name);
+ } else {
+ tmp = malloc(strlen(funcs) + strlen(sym->name) + 2);
+ if (!tmp) {
+ ERROR_GLIBC("malloc");
+ return;
+ }
+ sprintf(tmp, "%s %s", funcs, sym->name);
+ free(funcs);
+ funcs = tmp;
+ }
+ }
+ }
+
+ if (funcs)
+ disas_funcs(funcs);
+}
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index c0dc86a78ff65..4d3e94b70fd84 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -47,4 +47,6 @@ int check(struct objtool_file *file);
int orc_dump(const char *objname);
int orc_create(struct objtool_file *file);
+void disas_warned_funcs(struct objtool_file *file);
+
#endif /* _OBJTOOL_H */
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 02/28] objtool: Create disassembly context
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 01/28] objtool: Move disassembly functions to a separated file Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 03/28] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
` (27 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Create a structure to store information for disassembling functions.
For now, it is just a wrapper around an objtool file.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 6 ++++-
tools/objtool/disas.c | 32 +++++++++++++++++++++++--
tools/objtool/include/objtool/disas.h | 14 +++++++++++
tools/objtool/include/objtool/objtool.h | 2 --
4 files changed, 49 insertions(+), 5 deletions(-)
create mode 100644 tools/objtool/include/objtool/disas.h
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 5207e62ab690c..7b6fa1e3e9aac 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -11,6 +11,7 @@
#include <objtool/builtin.h>
#include <objtool/cfi.h>
#include <objtool/arch.h>
+#include <objtool/disas.h>
#include <objtool/check.h>
#include <objtool/special.h>
#include <objtool/warn.h>
@@ -4672,6 +4673,7 @@ static void free_insns(struct objtool_file *file)
int check(struct objtool_file *file)
{
+ struct disas_context *disas_ctx;
int ret = 0, warnings = 0;
arch_initial_func_cfi_state(&initial_func_cfi);
@@ -4804,7 +4806,9 @@ int check(struct objtool_file *file)
if (opts.werror && warnings)
WARN("%d warning(s) upgraded to errors", warnings);
print_args();
- disas_warned_funcs(file);
+ disas_ctx = disas_context_create(file);
+ disas_warned_funcs(disas_ctx);
+ disas_context_destroy(disas_ctx);
}
return ret;
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 77de46beb496c..8c751f3394865 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -4,10 +4,35 @@
*/
#include <objtool/arch.h>
+#include <objtool/disas.h>
#include <objtool/warn.h>
#include <linux/string.h>
+struct disas_context {
+ struct objtool_file *file;
+};
+
+struct disas_context *disas_context_create(struct objtool_file *file)
+{
+ struct disas_context *dctx;
+
+ dctx = malloc(sizeof(*dctx));
+ if (!dctx) {
+ WARN("failed to allocate disassembly context");
+ return NULL;
+ }
+
+ dctx->file = file;
+
+ return dctx;
+}
+
+void disas_context_destroy(struct disas_context *dctx)
+{
+ free(dctx);
+}
+
/* 'funcs' is a space-separated list of function names */
static void disas_funcs(const char *funcs)
{
@@ -58,12 +83,15 @@ static void disas_funcs(const char *funcs)
}
}
-void disas_warned_funcs(struct objtool_file *file)
+void disas_warned_funcs(struct disas_context *dctx)
{
struct symbol *sym;
char *funcs = NULL, *tmp;
- for_each_sym(file, sym) {
+ if (!dctx)
+ return;
+
+ for_each_sym(dctx->file, sym) {
if (sym->warned) {
if (!funcs) {
funcs = malloc(strlen(sym->name) + 1);
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
new file mode 100644
index 0000000000000..5c543b69fc612
--- /dev/null
+++ b/tools/objtool/include/objtool/disas.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ */
+
+#ifndef _DISAS_H
+#define _DISAS_H
+
+struct disas_context;
+struct disas_context *disas_context_create(struct objtool_file *file);
+void disas_context_destroy(struct disas_context *dctx);
+void disas_warned_funcs(struct disas_context *dctx);
+
+#endif /* _DISAS_H */
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index 4d3e94b70fd84..c0dc86a78ff65 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -47,6 +47,4 @@ int check(struct objtool_file *file);
int orc_dump(const char *objname);
int orc_create(struct objtool_file *file);
-void disas_warned_funcs(struct objtool_file *file);
-
#endif /* _OBJTOOL_H */
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 03/28] objtool: Disassemble code with libopcodes instead of running objdump
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 01/28] objtool: Move disassembly functions to a separated file Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 02/28] objtool: Create disassembly context Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 04/28] tool build: Remove annoying newline in build output Alexandre Chartre
` (26 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
objtool executes the objdump command to disassemble code. Use libopcodes
instead to have more control about the disassembly scope and output.
If libopcodes is not present then objtool is built without disassembly
support.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/Build | 3 +-
tools/objtool/Makefile | 22 +++
tools/objtool/arch/loongarch/decode.c | 12 ++
tools/objtool/arch/powerpc/decode.c | 12 ++
tools/objtool/arch/x86/decode.c | 12 ++
tools/objtool/check.c | 10 +-
tools/objtool/disas.c | 187 +++++++++++++++++---------
tools/objtool/include/objtool/arch.h | 9 ++
tools/objtool/include/objtool/check.h | 5 +
tools/objtool/include/objtool/disas.h | 29 ++++
10 files changed, 230 insertions(+), 71 deletions(-)
diff --git a/tools/objtool/Build b/tools/objtool/Build
index 677bf9148cba1..ee04fba8c9d16 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -7,7 +7,8 @@ objtool-y += special.o
objtool-y += builtin-check.o
objtool-y += elf.o
objtool-y += objtool.o
-objtool-y += disas.o
+
+objtool-$(BUILD_DISAS) += disas.o
objtool-$(BUILD_ORC) += orc_gen.o
objtool-$(BUILD_ORC) += orc_dump.o
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 8c20361dd100e..9fb83979ca890 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -7,6 +7,15 @@ srctree := $(patsubst %/,%,$(dir $(CURDIR)))
srctree := $(patsubst %/,%,$(dir $(srctree)))
endif
+#
+# To support disassembly, objtool needs libopcodes which is provided
+# with libbdf (binutils-dev or binutils-devel package).
+#
+FEATURE_USER = .objtool
+FEATURE_TESTS = libbfd disassembler-init-styled
+FEATURE_DISPLAY =
+include $(srctree)/tools/build/Makefile.feature
+
LIBSUBCMD_DIR = $(srctree)/tools/lib/subcmd/
ifneq ($(OUTPUT),)
LIBSUBCMD_OUTPUT = $(abspath $(OUTPUT))/libsubcmd
@@ -40,6 +49,18 @@ OBJTOOL_LDFLAGS := $(LIBELF_LIBS) $(LIBSUBCMD) $(KBUILD_HOSTLDFLAGS)
elfshdr := $(shell echo '$(pound)include <libelf.h>' | $(HOSTCC) $(OBJTOOL_CFLAGS) -x c -E - 2>/dev/null | grep elf_getshdr)
OBJTOOL_CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED)
+ifeq ($(feature-disassembler-init-styled), 1)
+ OBJTOOL_CFLAGS += -DDISASM_INIT_STYLED
+endif
+
+BUILD_DISAS := n
+
+ifeq ($(feature-libbfd),1)
+ BUILD_DISAS := y
+ OBJTOOL_CFLAGS += -DDISAS
+ OBJTOOL_LDFLAGS += -lopcodes
+endif
+
# Always want host compilation.
HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)"
@@ -56,6 +77,7 @@ ifeq ($(SRCARCH),loongarch)
BUILD_ORC := y
endif
+export BUILD_DISAS
export BUILD_ORC
export srctree OUTPUT CFLAGS SRCARCH AWK
include $(srctree)/tools/build/Makefile.include
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index 2e555c4060c5e..9fd88431e8f48 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string.h>
#include <objtool/check.h>
+#include <objtool/disas.h>
#include <objtool/warn.h>
#include <asm/inst.h>
#include <asm/orc_types.h>
@@ -414,3 +415,14 @@ unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *tabl
return reloc->sym->offset + reloc_addend(reloc);
}
}
+
+#ifdef DISAS
+
+int arch_disas_info_init(struct disassemble_info *dinfo)
+{
+ return disas_info_init(dinfo, bfd_arch_loongarch,
+ bfd_mach_loongarch32, bfd_mach_loongarch64,
+ NULL);
+}
+
+#endif /* DISAS */
diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c
index c851c51d4bd35..3c6fced37bcca 100644
--- a/tools/objtool/arch/powerpc/decode.c
+++ b/tools/objtool/arch/powerpc/decode.c
@@ -3,6 +3,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <objtool/check.h>
+#include <objtool/disas.h>
#include <objtool/elf.h>
#include <objtool/arch.h>
#include <objtool/warn.h>
@@ -128,3 +129,14 @@ unsigned int arch_reloc_size(struct reloc *reloc)
return 8;
}
}
+
+#ifdef DISAS
+
+int arch_disas_info_init(struct disassemble_info *dinfo)
+{
+ return disas_info_init(dinfo, bfd_arch_powerpc,
+ bfd_mach_ppc, bfd_mach_ppc64,
+ NULL);
+}
+
+#endif /* DISAS */
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index 0ad5cc70ecbe7..2f7045e1accc0 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -16,6 +16,7 @@
#include <asm/orc_types.h>
#include <objtool/check.h>
+#include <objtool/disas.h>
#include <objtool/elf.h>
#include <objtool/arch.h>
#include <objtool/warn.h>
@@ -892,3 +893,14 @@ bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc)
return false;
}
}
+
+#ifdef DISAS
+
+int arch_disas_info_init(struct disassemble_info *dinfo)
+{
+ return disas_info_init(dinfo, bfd_arch_i386,
+ bfd_mach_i386_i386, bfd_mach_x86_64,
+ "att");
+}
+
+#endif /* DISAS */
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 7b6fa1e3e9aac..86cd5385a14a0 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4786,8 +4786,6 @@ int check(struct objtool_file *file)
goto out;
}
- free_insns(file);
-
if (opts.stats) {
printf("nr_insns_visited: %ld\n", nr_insns_visited);
printf("nr_cfi: %ld\n", nr_cfi);
@@ -4807,9 +4805,13 @@ int check(struct objtool_file *file)
WARN("%d warning(s) upgraded to errors", warnings);
print_args();
disas_ctx = disas_context_create(file);
- disas_warned_funcs(disas_ctx);
- disas_context_destroy(disas_ctx);
+ if (disas_ctx) {
+ disas_warned_funcs(disas_ctx);
+ disas_context_destroy(disas_ctx);
+ }
}
+ free_insns(file);
+
return ret;
}
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 8c751f3394865..e9e7cb21b2a4b 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -4,18 +4,56 @@
*/
#include <objtool/arch.h>
+#include <objtool/check.h>
#include <objtool/disas.h>
#include <objtool/warn.h>
+#include <bfd.h>
#include <linux/string.h>
+#include <tools/dis-asm-compat.h>
struct disas_context {
struct objtool_file *file;
+ disassembler_ftype disassembler;
+ struct disassemble_info info;
};
+#define DINFO_FPRINTF(dinfo, ...) \
+ ((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))
+
+/*
+ * Initialize disassemble info arch, mach (32 or 64-bit) and options.
+ */
+int disas_info_init(struct disassemble_info *dinfo,
+ int arch, int mach32, int mach64,
+ const char *options)
+{
+ struct disas_context *dctx = dinfo->application_data;
+ struct objtool_file *file = dctx->file;
+
+ dinfo->arch = arch;
+
+ switch (file->elf->ehdr.e_ident[EI_CLASS]) {
+ case ELFCLASS32:
+ dinfo->mach = mach32;
+ break;
+ case ELFCLASS64:
+ dinfo->mach = mach64;
+ break;
+ default:
+ return -1;
+ }
+
+ dinfo->disassembler_options = options;
+
+ return 0;
+}
+
struct disas_context *disas_context_create(struct objtool_file *file)
{
struct disas_context *dctx;
+ struct disassemble_info *dinfo;
+ int err;
dctx = malloc(sizeof(*dctx));
if (!dctx) {
@@ -24,8 +62,49 @@ struct disas_context *disas_context_create(struct objtool_file *file)
}
dctx->file = file;
+ dinfo = &dctx->info;
+
+ init_disassemble_info_compat(dinfo, stdout,
+ (fprintf_ftype)fprintf,
+ fprintf_styled);
+
+ dinfo->read_memory_func = buffer_read_memory;
+ dinfo->application_data = dctx;
+
+ /*
+ * bfd_openr() is not used to avoid doing ELF data processing
+ * and caching that has already being done. Here, we just need
+ * to identify the target file so we call an arch specific
+ * function to fill some disassemble info (arch, mach).
+ */
+
+ dinfo->arch = bfd_arch_unknown;
+ dinfo->mach = 0;
+
+ err = arch_disas_info_init(dinfo);
+ if (err || dinfo->arch == bfd_arch_unknown || dinfo->mach == 0) {
+ WARN("failed to init disassembly arch");
+ goto error;
+ }
+
+ dinfo->endian = (file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) ?
+ BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE;
+
+ disassemble_init_for_target(dinfo);
+
+ dctx->disassembler = disassembler(dinfo->arch,
+ dinfo->endian == BFD_ENDIAN_BIG,
+ dinfo->mach, NULL);
+ if (!dctx->disassembler) {
+ WARN("failed to create disassembler function");
+ goto error;
+ }
return dctx;
+
+error:
+ free(dctx);
+ return NULL;
}
void disas_context_destroy(struct disas_context *dctx)
@@ -33,86 +112,62 @@ void disas_context_destroy(struct disas_context *dctx)
free(dctx);
}
-/* 'funcs' is a space-separated list of function names */
-static void disas_funcs(const char *funcs)
+/*
+ * Disassemble a single instruction. Return the size of the instruction.
+ */
+static size_t disas_insn(struct disas_context *dctx,
+ struct instruction *insn)
{
- const char *objdump_str, *cross_compile;
- int size, ret;
- char *cmd;
-
- cross_compile = getenv("CROSS_COMPILE");
- if (!cross_compile)
- cross_compile = "";
-
- objdump_str = "%sobjdump -wdr %s | gawk -M -v _funcs='%s' '"
- "BEGIN { split(_funcs, funcs); }"
- "/^$/ { func_match = 0; }"
- "/<.*>:/ { "
- "f = gensub(/.*<(.*)>:/, \"\\\\1\", 1);"
- "for (i in funcs) {"
- "if (funcs[i] == f) {"
- "func_match = 1;"
- "base = strtonum(\"0x\" $1);"
- "break;"
- "}"
- "}"
- "}"
- "{"
- "if (func_match) {"
- "addr = strtonum(\"0x\" $1);"
- "printf(\"%%04x \", addr - base);"
- "print;"
- "}"
- "}' 1>&2";
-
- /* fake snprintf() to calculate the size */
- size = snprintf(NULL, 0, objdump_str, cross_compile, objname, funcs) + 1;
- if (size <= 0) {
- WARN("objdump string size calculation failed");
- return;
+ disassembler_ftype disasm = dctx->disassembler;
+ struct disassemble_info *dinfo = &dctx->info;
+
+ if (insn->type == INSN_NOP) {
+ DINFO_FPRINTF(dinfo, "NOP%d", insn->len);
+ return insn->len;
}
- cmd = malloc(size);
+ /*
+ * Set the disassembler buffer to read data from the section
+ * containing the instruction to disassemble.
+ */
+ dinfo->buffer = insn->sec->data->d_buf;
+ dinfo->buffer_vma = 0;
+ dinfo->buffer_length = insn->sec->sh.sh_size;
- /* real snprintf() */
- snprintf(cmd, size, objdump_str, cross_compile, objname, funcs);
- ret = system(cmd);
- if (ret) {
- WARN("disassembly failed: %d", ret);
- return;
+ return disasm(insn->offset, &dctx->info);
+}
+
+/*
+ * Disassemble a function.
+ */
+static void disas_func(struct disas_context *dctx, struct symbol *func)
+{
+ struct instruction *insn;
+ size_t addr;
+
+ printf("%s:\n", func->name);
+ sym_for_each_insn(dctx->file, func, insn) {
+ addr = insn->offset;
+ printf(" %6lx: %s+0x%-6lx ",
+ addr, func->name, addr - func->offset);
+ disas_insn(dctx, insn);
+ printf("\n");
}
+ printf("\n");
}
+/*
+ * Disassemble all warned functions.
+ */
void disas_warned_funcs(struct disas_context *dctx)
{
struct symbol *sym;
- char *funcs = NULL, *tmp;
if (!dctx)
return;
for_each_sym(dctx->file, sym) {
- if (sym->warned) {
- if (!funcs) {
- funcs = malloc(strlen(sym->name) + 1);
- if (!funcs) {
- ERROR_GLIBC("malloc");
- return;
- }
- strcpy(funcs, sym->name);
- } else {
- tmp = malloc(strlen(funcs) + strlen(sym->name) + 2);
- if (!tmp) {
- ERROR_GLIBC("malloc");
- return;
- }
- sprintf(tmp, "%s %s", funcs, sym->name);
- free(funcs);
- funcs = tmp;
- }
- }
+ if (sym->warned)
+ disas_func(dctx, sym);
}
-
- if (funcs)
- disas_funcs(funcs);
}
diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h
index be33c7b43180a..02e6e385f174b 100644
--- a/tools/objtool/include/objtool/arch.h
+++ b/tools/objtool/include/objtool/arch.h
@@ -102,4 +102,13 @@ bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc);
unsigned int arch_reloc_size(struct reloc *reloc);
unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table);
+#ifdef DISAS
+
+#include <bfd.h>
+#include <dis-asm.h>
+
+int arch_disas_info_init(struct disassemble_info *dinfo);
+
+#endif /* DISAS */
+
#endif /* _ARCH_H */
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index 00fb745e72339..5290ac1ebbc1f 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -125,4 +125,9 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc
insn && insn->sec == _sec; \
insn = next_insn_same_sec(file, insn))
+#define sym_for_each_insn(file, sym, insn) \
+ for (insn = find_insn(file, sym->sec, sym->offset); \
+ insn && insn->offset < sym->offset + sym->len; \
+ insn = next_insn_same_sec(file, insn))
+
#endif /* _CHECK_H */
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
index 5c543b69fc612..3ec3ce2e4e6f0 100644
--- a/tools/objtool/include/objtool/disas.h
+++ b/tools/objtool/include/objtool/disas.h
@@ -7,8 +7,37 @@
#define _DISAS_H
struct disas_context;
+struct disassemble_info;
+
+#ifdef DISAS
+
struct disas_context *disas_context_create(struct objtool_file *file);
void disas_context_destroy(struct disas_context *dctx);
void disas_warned_funcs(struct disas_context *dctx);
+int disas_info_init(struct disassemble_info *dinfo,
+ int arch, int mach32, int mach64,
+ const char *options);
+
+#else /* DISAS */
+
+#include <objtool/warn.h>
+
+static inline struct disas_context *disas_context_create(struct objtool_file *file)
+{
+ WARN("Rebuild with libopcodes for disassembly support");
+ return NULL;
+}
+
+static inline void disas_context_destroy(struct disas_context *dctx) {}
+static inline void disas_warned_funcs(struct disas_context *dctx) {}
+
+static inline int disas_info_init(struct disassemble_info *dinfo,
+ int arch, int mach32, int mach64,
+ const char *options)
+{
+ return -1;
+}
+
+#endif /* DISAS */
#endif /* _DISAS_H */
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 04/28] tool build: Remove annoying newline in build output
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (2 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 03/28] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 05/28] objtool: Print symbol during disassembly Alexandre Chartre
` (25 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Remove the newline which is printed during feature discovery
when nothing else is printed.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/build/Makefile.feature | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature
index 32bbe29fe5f6c..300a329bc581d 100644
--- a/tools/build/Makefile.feature
+++ b/tools/build/Makefile.feature
@@ -315,5 +315,7 @@ endef
ifeq ($(FEATURE_DISPLAY_DEFERRED),)
$(call feature_display_entries)
- $(info )
+ ifeq ($(feature_display),1)
+ $(info )
+ endif
endif
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 05/28] objtool: Print symbol during disassembly
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (3 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 04/28] tool build: Remove annoying newline in build output Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 06/28] objtool: Store instruction disassembly result Alexandre Chartre
` (24 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Print symbols referenced during disassembly instead of just printing
raw addresses. Also handle address relocation.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/arch/loongarch/decode.c | 7 ++
tools/objtool/arch/powerpc/decode.c | 7 ++
tools/objtool/arch/x86/decode.c | 14 +++
tools/objtool/check.c | 9 --
tools/objtool/disas.c | 137 ++++++++++++++++++++++++++
tools/objtool/include/objtool/arch.h | 3 +-
tools/objtool/include/objtool/check.h | 9 ++
7 files changed, 176 insertions(+), 10 deletions(-)
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index 9fd88431e8f48..7645409918f5b 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -28,6 +28,13 @@ bool arch_pc_relative_reloc(struct reloc *reloc)
return false;
}
+unsigned long arch_pc_relative_offset(struct instruction *insn,
+ struct reloc *reloc)
+{
+ /* no PC relative relocation */
+ return 0;
+}
+
bool arch_callee_saved_reg(unsigned char reg)
{
switch (reg) {
diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c
index 3c6fced37bcca..9c3f49c455871 100644
--- a/tools/objtool/arch/powerpc/decode.c
+++ b/tools/objtool/arch/powerpc/decode.c
@@ -20,6 +20,13 @@ unsigned long arch_dest_reloc_offset(int addend)
return addend;
}
+unsigned long arch_pc_relative_offset(struct instruction *insn,
+ struct reloc *reloc)
+{
+ /* no PC relative relocation */
+ return 0;
+}
+
bool arch_callee_saved_reg(unsigned char reg)
{
return false;
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index 2f7045e1accc0..7bd874871c31b 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -103,6 +103,20 @@ bool arch_pc_relative_reloc(struct reloc *reloc)
return false;
}
+unsigned long arch_pc_relative_offset(struct instruction *insn,
+ struct reloc *reloc)
+{
+ /*
+ * Relocation information for a RIP-relative instruction is
+ * based on the RIP value at the end of the instruction. So
+ * to get the effective relocated address, the reference has
+ * to be adjusted with the number of bytes between the
+ * relocation offset and the end of the instruction.
+ */
+ return reloc_addend(reloc) +
+ insn->offset + insn->len - reloc_offset(reloc);
+}
+
#define ADD_OP(op) \
if (!(op = calloc(1, sizeof(*op)))) \
return -1; \
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 86cd5385a14a0..f67a77b77c263 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -132,15 +132,6 @@ static struct instruction *prev_insn_same_sym(struct objtool_file *file,
for (insn = next_insn_same_sec(file, insn); insn; \
insn = next_insn_same_sec(file, insn))
-static inline struct symbol *insn_call_dest(struct instruction *insn)
-{
- if (insn->type == INSN_JUMP_DYNAMIC ||
- insn->type == INSN_CALL_DYNAMIC)
- return NULL;
-
- return insn->_call_dest;
-}
-
static inline struct reloc *insn_jump_table(struct instruction *insn)
{
if (insn->type == INSN_JUMP_DYNAMIC ||
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index e9e7cb21b2a4b..d1b1c215e7f25 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -14,13 +14,147 @@
struct disas_context {
struct objtool_file *file;
+ struct instruction *insn;
disassembler_ftype disassembler;
struct disassemble_info info;
};
+static int sprint_name(char *str, const char *name, unsigned long offset)
+{
+ int len;
+
+ if (offset)
+ len = sprintf(str, "%s+0x%lx", name, offset);
+ else
+ len = sprintf(str, "%s", name);
+
+ return len;
+}
+
#define DINFO_FPRINTF(dinfo, ...) \
((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))
+static void disas_print_addr_sym(struct section *sec, struct symbol *sym,
+ bfd_vma addr, struct disassemble_info *dinfo)
+{
+ char symstr[1024];
+ char *str;
+
+ if (sym) {
+ sprint_name(symstr, sym->name, addr - sym->offset);
+ DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, symstr);
+ } else {
+ str = offstr(sec, addr);
+ DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, str);
+ free(str);
+ }
+}
+
+static void disas_print_addr_noreloc(bfd_vma addr,
+ struct disassemble_info *dinfo)
+{
+ struct disas_context *dctx = dinfo->application_data;
+ struct instruction *insn = dctx->insn;
+ struct symbol *sym = NULL;
+
+ if (insn->sym && addr >= insn->sym->offset &&
+ addr < insn->sym->offset + insn->sym->len) {
+ sym = insn->sym;
+ }
+
+ disas_print_addr_sym(insn->sec, sym, addr, dinfo);
+}
+
+static void disas_print_addr_reloc(bfd_vma addr, struct disassemble_info *dinfo)
+{
+ struct disas_context *dctx = dinfo->application_data;
+ struct instruction *insn = dctx->insn;
+ unsigned long offset;
+ struct reloc *reloc;
+ char symstr[1024];
+ char *str;
+
+ reloc = find_reloc_by_dest_range(dctx->file->elf, insn->sec,
+ insn->offset, insn->len);
+ if (!reloc) {
+ /*
+ * There is no relocation for this instruction although
+ * the address to resolve points to the next instruction.
+ * So this is an effective reference to the next IP, for
+ * example: "lea 0x0(%rip),%rdi". The kernel can reference
+ * the next IP with _THIS_IP_ macro.
+ */
+ DINFO_FPRINTF(dinfo, "0x%lx <_THIS_IP_>", addr);
+ return;
+ }
+
+ if (arch_pc_relative_reloc(reloc))
+ offset = arch_pc_relative_offset(insn, reloc);
+ else
+ offset = reloc_addend(reloc);
+
+ /*
+ * If the relocation symbol is a section name (for example ".bss")
+ * then we try to further resolve the name.
+ */
+ if (reloc->sym->type == STT_SECTION) {
+ str = offstr(reloc->sym->sec, reloc->sym->offset + offset);
+ DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, str);
+ free(str);
+ } else {
+ sprint_name(symstr, reloc->sym->name, offset);
+ DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, symstr);
+ }
+}
+
+/*
+ * Resolve an address into a "<symbol>+<offset>" string.
+ */
+static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo)
+{
+ struct disas_context *dctx = dinfo->application_data;
+ struct instruction *insn = dctx->insn;
+ struct instruction *jump_dest;
+ struct symbol *sym;
+ bool is_reloc;
+
+ /*
+ * If the instruction is a call/jump and it references a
+ * destination then this is likely the address we are looking
+ * up. So check it first.
+ */
+ jump_dest = insn->jump_dest;
+ if (jump_dest && jump_dest->sym && jump_dest->offset == addr) {
+ disas_print_addr_sym(jump_dest->sec, jump_dest->sym,
+ addr, dinfo);
+ return;
+ }
+
+ /*
+ * If the address points to the next instruction then there is
+ * probably a relocation. It can be a false positive when the
+ * current instruction is referencing the address of the next
+ * instruction. This particular case will be handled in
+ * disas_print_addr_reloc().
+ */
+ is_reloc = (addr == insn->offset + insn->len);
+
+ /*
+ * The call destination offset can be the address we are looking
+ * up, or 0 if there is a relocation.
+ */
+ sym = insn_call_dest(insn);
+ if (sym && (sym->offset == addr || (sym->offset == 0 && is_reloc))) {
+ DINFO_FPRINTF(dinfo, "0x%lx <%s>", addr, sym->name);
+ return;
+ }
+
+ if (!is_reloc)
+ disas_print_addr_noreloc(addr, dinfo);
+ else
+ disas_print_addr_reloc(addr, dinfo);
+}
+
/*
* Initialize disassemble info arch, mach (32 or 64-bit) and options.
*/
@@ -69,6 +203,7 @@ struct disas_context *disas_context_create(struct objtool_file *file)
fprintf_styled);
dinfo->read_memory_func = buffer_read_memory;
+ dinfo->print_address_func = disas_print_address;
dinfo->application_data = dctx;
/*
@@ -121,6 +256,8 @@ static size_t disas_insn(struct disas_context *dctx,
disassembler_ftype disasm = dctx->disassembler;
struct disassemble_info *dinfo = &dctx->info;
+ dctx->insn = insn;
+
if (insn->type == INSN_NOP) {
DINFO_FPRINTF(dinfo, "NOP%d", insn->len);
return insn->len;
diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h
index 02e6e385f174b..2bd7a76f053d9 100644
--- a/tools/objtool/include/objtool/arch.h
+++ b/tools/objtool/include/objtool/arch.h
@@ -98,7 +98,8 @@ int arch_rewrite_retpolines(struct objtool_file *file);
bool arch_pc_relative_reloc(struct reloc *reloc);
bool arch_absolute_reloc(struct elf *elf, struct reloc *reloc);
-
+unsigned long arch_pc_relative_offset(struct instruction *insn,
+ struct reloc *reloc);
unsigned int arch_reloc_size(struct reloc *reloc);
unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table);
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index 5290ac1ebbc1f..4adbcd760c6f6 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -115,6 +115,15 @@ static inline bool is_jump(struct instruction *insn)
return is_static_jump(insn) || is_dynamic_jump(insn);
}
+static inline struct symbol *insn_call_dest(struct instruction *insn)
+{
+ if (insn->type == INSN_JUMP_DYNAMIC ||
+ insn->type == INSN_CALL_DYNAMIC)
+ return NULL;
+
+ return insn->_call_dest;
+}
+
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset);
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 06/28] objtool: Store instruction disassembly result
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (4 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 05/28] objtool: Print symbol during disassembly Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 07/28] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
` (23 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When disassembling an instruction store the result instead of directly
printing it.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 77 +++++++++++++++++++++++++++++++++++++++----
1 file changed, 71 insertions(+), 6 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index d1b1c215e7f25..aad5bc3651b8c 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -12,9 +12,16 @@
#include <linux/string.h>
#include <tools/dis-asm-compat.h>
+/*
+ * Size of the buffer for storing the result of disassembling
+ * a single instruction.
+ */
+#define DISAS_RESULT_SIZE 1024
+
struct disas_context {
struct objtool_file *file;
struct instruction *insn;
+ char result[DISAS_RESULT_SIZE];
disassembler_ftype disassembler;
struct disassemble_info info;
};
@@ -34,6 +41,59 @@ static int sprint_name(char *str, const char *name, unsigned long offset)
#define DINFO_FPRINTF(dinfo, ...) \
((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))
+static int disas_result_fprintf(struct disas_context *dctx,
+ const char *fmt, va_list ap)
+{
+ char *buf = dctx->result;
+ size_t avail, len;
+
+ len = strlen(buf);
+ if (len >= DISAS_RESULT_SIZE - 1) {
+ WARN_FUNC(dctx->insn->sec, dctx->insn->offset,
+ "disassembly buffer is full");
+ return -1;
+ }
+ avail = DISAS_RESULT_SIZE - len;
+
+ len = vsnprintf(buf + len, avail, fmt, ap);
+ if (len < 0 || len >= avail) {
+ WARN_FUNC(dctx->insn->sec, dctx->insn->offset,
+ "disassembly buffer is truncated");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int disas_fprintf(void *stream, const char *fmt, ...)
+{
+ va_list arg;
+ int rv;
+
+ va_start(arg, fmt);
+ rv = disas_result_fprintf(stream, fmt, arg);
+ va_end(arg);
+
+ return rv;
+}
+
+/*
+ * For init_disassemble_info_compat().
+ */
+static int disas_fprintf_styled(void *stream,
+ enum disassembler_style style,
+ const char *fmt, ...)
+{
+ va_list arg;
+ int rv;
+
+ va_start(arg, fmt);
+ rv = disas_result_fprintf(stream, fmt, arg);
+ va_end(arg);
+
+ return rv;
+}
+
static void disas_print_addr_sym(struct section *sec, struct symbol *sym,
bfd_vma addr, struct disassemble_info *dinfo)
{
@@ -198,9 +258,8 @@ struct disas_context *disas_context_create(struct objtool_file *file)
dctx->file = file;
dinfo = &dctx->info;
- init_disassemble_info_compat(dinfo, stdout,
- (fprintf_ftype)fprintf,
- fprintf_styled);
+ init_disassemble_info_compat(dinfo, dctx,
+ disas_fprintf, disas_fprintf_styled);
dinfo->read_memory_func = buffer_read_memory;
dinfo->print_address_func = disas_print_address;
@@ -247,6 +306,11 @@ void disas_context_destroy(struct disas_context *dctx)
free(dctx);
}
+static char *disas_result(struct disas_context *dctx)
+{
+ return dctx->result;
+}
+
/*
* Disassemble a single instruction. Return the size of the instruction.
*/
@@ -257,6 +321,7 @@ static size_t disas_insn(struct disas_context *dctx,
struct disassemble_info *dinfo = &dctx->info;
dctx->insn = insn;
+ dctx->result[0] = '\0';
if (insn->type == INSN_NOP) {
DINFO_FPRINTF(dinfo, "NOP%d", insn->len);
@@ -285,10 +350,10 @@ static void disas_func(struct disas_context *dctx, struct symbol *func)
printf("%s:\n", func->name);
sym_for_each_insn(dctx->file, func, insn) {
addr = insn->offset;
- printf(" %6lx: %s+0x%-6lx ",
- addr, func->name, addr - func->offset);
disas_insn(dctx, insn);
- printf("\n");
+ printf(" %6lx: %s+0x%-6lx %s\n",
+ addr, func->name, addr - func->offset,
+ disas_result(dctx));
}
printf("\n");
}
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 07/28] objtool: Disassemble instruction on warning or backtrace
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (5 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 06/28] objtool: Store instruction disassembly result Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 08/28] objtool: Extract code to validate instruction from the validate branch loop Alexandre Chartre
` (22 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When an instruction warning (WARN_INSN) or backtrace (BT_INSN) is issued,
disassemble the instruction to provide more context.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 36 ++++++++++++++++++++++-----
tools/objtool/disas.c | 5 ++--
tools/objtool/include/objtool/check.h | 2 ++
tools/objtool/include/objtool/disas.h | 13 ++++++++++
tools/objtool/include/objtool/warn.h | 16 ++++++++----
5 files changed, 58 insertions(+), 14 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index f67a77b77c263..5b977bdb5512f 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4662,11 +4662,34 @@ static void free_insns(struct objtool_file *file)
free(chunk->addr);
}
+static struct disas_context *objtool_disas_ctx;
+
+const char *objtool_disas_insn(struct instruction *insn)
+{
+ struct disas_context *dctx = objtool_disas_ctx;
+
+ if (!dctx)
+ return "";
+
+ disas_insn(dctx, insn);
+ return disas_result(dctx);
+}
+
int check(struct objtool_file *file)
{
- struct disas_context *disas_ctx;
+ struct disas_context *disas_ctx = NULL;
int ret = 0, warnings = 0;
+ /*
+ * If the verbose or backtrace option is used then we need a
+ * disassembly context to disassemble instruction or function
+ * on warning or backtrace.
+ */
+ if (opts.verbose || opts.backtrace) {
+ disas_ctx = disas_context_create(file);
+ objtool_disas_ctx = disas_ctx;
+ }
+
arch_initial_func_cfi_state(&initial_func_cfi);
init_cfi_state(&init_cfi);
init_cfi_state(&func_cfi);
@@ -4795,11 +4818,12 @@ int check(struct objtool_file *file)
if (opts.werror && warnings)
WARN("%d warning(s) upgraded to errors", warnings);
print_args();
- disas_ctx = disas_context_create(file);
- if (disas_ctx) {
- disas_warned_funcs(disas_ctx);
- disas_context_destroy(disas_ctx);
- }
+ disas_warned_funcs(disas_ctx);
+ }
+
+ if (disas_ctx) {
+ disas_context_destroy(disas_ctx);
+ objtool_disas_ctx = NULL;
}
free_insns(file);
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index aad5bc3651b8c..c8f3fad086faa 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -306,7 +306,7 @@ void disas_context_destroy(struct disas_context *dctx)
free(dctx);
}
-static char *disas_result(struct disas_context *dctx)
+char *disas_result(struct disas_context *dctx)
{
return dctx->result;
}
@@ -314,8 +314,7 @@ static char *disas_result(struct disas_context *dctx)
/*
* Disassemble a single instruction. Return the size of the instruction.
*/
-static size_t disas_insn(struct disas_context *dctx,
- struct instruction *insn)
+size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
{
disassembler_ftype disasm = dctx->disassembler;
struct disassemble_info *dinfo = &dctx->info;
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index 4adbcd760c6f6..f3ea144d4746c 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -139,4 +139,6 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc
insn && insn->offset < sym->offset + sym->len; \
insn = next_insn_same_sec(file, insn))
+const char *objtool_disas_insn(struct instruction *insn);
+
#endif /* _CHECK_H */
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
index 3ec3ce2e4e6f0..1aee1fbe0bb97 100644
--- a/tools/objtool/include/objtool/disas.h
+++ b/tools/objtool/include/objtool/disas.h
@@ -17,6 +17,8 @@ void disas_warned_funcs(struct disas_context *dctx);
int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
const char *options);
+size_t disas_insn(struct disas_context *dctx, struct instruction *insn);
+char *disas_result(struct disas_context *dctx);
#else /* DISAS */
@@ -38,6 +40,17 @@ static inline int disas_info_init(struct disassemble_info *dinfo,
return -1;
}
+static inline size_t disas_insn(struct disas_context *dctx,
+ struct instruction *insn)
+{
+ return -1;
+}
+
+static inline char *disas_result(struct disas_context *dctx)
+{
+ return NULL;
+}
+
#endif /* DISAS */
#endif /* _DISAS_H */
diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h
index cb8fe846d9ddd..8be02483823dc 100644
--- a/tools/objtool/include/objtool/warn.h
+++ b/tools/objtool/include/objtool/warn.h
@@ -77,9 +77,11 @@ static inline char *offstr(struct section *sec, unsigned long offset)
#define WARN_INSN(insn, format, ...) \
({ \
struct instruction *_insn = (insn); \
- if (!_insn->sym || !_insn->sym->warned) \
+ if (!_insn->sym || !_insn->sym->warned) { \
WARN_FUNC(_insn->sec, _insn->offset, format, \
##__VA_ARGS__); \
+ BT_INSN(_insn, ""); \
+ } \
if (_insn->sym) \
_insn->sym->warned = 1; \
})
@@ -87,10 +89,14 @@ static inline char *offstr(struct section *sec, unsigned long offset)
#define BT_INSN(insn, format, ...) \
({ \
if (opts.verbose || opts.backtrace) { \
- struct instruction *_insn = (insn); \
- char *_str = offstr(_insn->sec, _insn->offset); \
- WARN(" %s: " format, _str, ##__VA_ARGS__); \
- free(_str); \
+ struct instruction *__insn = (insn); \
+ char *_str = offstr(__insn->sec, __insn->offset); \
+ const char *_istr = objtool_disas_insn(__insn); \
+ int _len; \
+ _len = snprintf(NULL, 0, " %s: " format, _str, ##__VA_ARGS__); \
+ _len = (_len < 50) ? 50 - _len : 0; \
+ WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \
+ free(_str); \
} \
})
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 08/28] objtool: Extract code to validate instruction from the validate branch loop
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (6 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 07/28] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 09/28] objtool: Record symbol name max length Alexandre Chartre
` (21 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
The code to validate a branch loops through all instructions of the
branch and validate each instruction. Move the code to validate an
instruction to a separated function.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 392 ++++++++++++++++++++++--------------------
1 file changed, 208 insertions(+), 184 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 5b977bdb5512f..609994ad6ab41 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3535,256 +3535,280 @@ static bool skip_alt_group(struct instruction *insn)
return alt_insn->type == INSN_CLAC || alt_insn->type == INSN_STAC;
}
-/*
- * Follow the branch starting at the given instruction, and recursively follow
- * any other branches (jumps). Meanwhile, track the frame pointer state at
- * each instruction and validate all the rules described in
- * tools/objtool/Documentation/objtool.txt.
- */
static int validate_branch(struct objtool_file *file, struct symbol *func,
- struct instruction *insn, struct insn_state state)
+ struct instruction *insn, struct insn_state state);
+
+static int validate_insn(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state *statep,
+ struct instruction *prev_insn, struct instruction *next_insn,
+ bool *dead_end)
{
struct alternative *alt;
- struct instruction *next_insn, *prev_insn = NULL;
- struct section *sec;
u8 visited;
int ret;
- if (func && func->ignore)
- return 0;
+ /*
+ * Any returns before the end of this function are effectively dead
+ * ends, i.e. validate_branch() has reached the end of the branch.
+ */
+ *dead_end = true;
- sec = insn->sec;
+ visited = VISITED_BRANCH << statep->uaccess;
+ if (insn->visited & VISITED_BRANCH_MASK) {
+ if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
+ return 1;
- while (1) {
- next_insn = next_insn_to_validate(file, insn);
+ if (insn->visited & visited)
+ return 0;
+ } else {
+ nr_insns_visited++;
+ }
- if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
- /* Ignore KCFI type preambles, which always fall through */
- if (!strncmp(func->name, "__cfi_", 6) ||
- !strncmp(func->name, "__pfx_", 6) ||
- !strncmp(func->name, "__pi___cfi_", 11) ||
- !strncmp(func->name, "__pi___pfx_", 11))
- return 0;
+ if (statep->noinstr)
+ statep->instr += insn->instr;
- if (file->ignore_unreachables)
- return 0;
+ if (insn->hint) {
+ if (insn->restore) {
+ struct instruction *save_insn, *i;
- WARN("%s() falls through to next function %s()",
- func->name, insn_func(insn)->name);
- func->warned = 1;
+ i = insn;
+ save_insn = NULL;
- return 1;
- }
+ sym_for_each_insn_continue_reverse(file, func, i) {
+ if (i->save) {
+ save_insn = i;
+ break;
+ }
+ }
- visited = VISITED_BRANCH << state.uaccess;
- if (insn->visited & VISITED_BRANCH_MASK) {
- if (!insn->hint && !insn_cfi_match(insn, &state.cfi))
+ if (!save_insn) {
+ WARN_INSN(insn, "no corresponding CFI save for CFI restore");
return 1;
+ }
- if (insn->visited & visited)
- return 0;
- } else {
- nr_insns_visited++;
+ if (!save_insn->visited) {
+ /*
+ * If the restore hint insn is at the
+ * beginning of a basic block and was
+ * branched to from elsewhere, and the
+ * save insn hasn't been visited yet,
+ * defer following this branch for now.
+ * It will be seen later via the
+ * straight-line path.
+ */
+ if (!prev_insn)
+ return 0;
+
+ WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
+ return 1;
+ }
+
+ insn->cfi = save_insn->cfi;
+ nr_cfi_reused++;
}
- if (state.noinstr)
- state.instr += insn->instr;
+ statep->cfi = *insn->cfi;
+ } else {
+ /* XXX track if we actually changed statep->cfi */
- if (insn->hint) {
- if (insn->restore) {
- struct instruction *save_insn, *i;
+ if (prev_insn && !cficmp(prev_insn->cfi, &statep->cfi)) {
+ insn->cfi = prev_insn->cfi;
+ nr_cfi_reused++;
+ } else {
+ insn->cfi = cfi_hash_find_or_add(&statep->cfi);
+ }
+ }
- i = insn;
- save_insn = NULL;
+ insn->visited |= visited;
- sym_for_each_insn_continue_reverse(file, func, i) {
- if (i->save) {
- save_insn = i;
- break;
- }
- }
+ if (propagate_alt_cfi(file, insn))
+ return 1;
- if (!save_insn) {
- WARN_INSN(insn, "no corresponding CFI save for CFI restore");
- return 1;
- }
+ if (insn->alts) {
+ for (alt = insn->alts; alt; alt = alt->next) {
+ ret = validate_branch(file, func, alt->insn, *statep);
+ if (ret) {
+ BT_INSN(insn, "(alt)");
+ return ret;
+ }
+ }
+ }
- if (!save_insn->visited) {
- /*
- * If the restore hint insn is at the
- * beginning of a basic block and was
- * branched to from elsewhere, and the
- * save insn hasn't been visited yet,
- * defer following this branch for now.
- * It will be seen later via the
- * straight-line path.
- */
- if (!prev_insn)
- return 0;
+ if (skip_alt_group(insn))
+ return 0;
- WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
- return 1;
- }
+ if (handle_insn_ops(insn, next_insn, statep))
+ return 1;
- insn->cfi = save_insn->cfi;
- nr_cfi_reused++;
- }
+ switch (insn->type) {
- state.cfi = *insn->cfi;
- } else {
- /* XXX track if we actually changed state.cfi */
+ case INSN_RETURN:
+ return validate_return(func, insn, statep);
- if (prev_insn && !cficmp(prev_insn->cfi, &state.cfi)) {
- insn->cfi = prev_insn->cfi;
- nr_cfi_reused++;
- } else {
- insn->cfi = cfi_hash_find_or_add(&state.cfi);
- }
+ case INSN_CALL:
+ case INSN_CALL_DYNAMIC:
+ ret = validate_call(file, insn, statep);
+ if (ret)
+ return ret;
+
+ if (opts.stackval && func && !is_special_call(insn) &&
+ !has_valid_stack_frame(statep)) {
+ WARN_INSN(insn, "call without frame pointer save/setup");
+ return 1;
}
- insn->visited |= visited;
+ break;
- if (propagate_alt_cfi(file, insn))
- return 1;
+ case INSN_JUMP_CONDITIONAL:
+ case INSN_JUMP_UNCONDITIONAL:
+ if (is_sibling_call(insn)) {
+ ret = validate_sibling_call(file, insn, statep);
+ if (ret)
+ return ret;
- if (insn->alts) {
- for (alt = insn->alts; alt; alt = alt->next) {
- ret = validate_branch(file, func, alt->insn, state);
- if (ret) {
- BT_INSN(insn, "(alt)");
- return ret;
- }
+ } else if (insn->jump_dest) {
+ ret = validate_branch(file, func,
+ insn->jump_dest, *statep);
+ if (ret) {
+ BT_INSN(insn, "(branch)");
+ return ret;
}
}
- if (skip_alt_group(insn))
+ if (insn->type == INSN_JUMP_UNCONDITIONAL)
return 0;
- if (handle_insn_ops(insn, next_insn, &state))
- return 1;
-
- switch (insn->type) {
-
- case INSN_RETURN:
- return validate_return(func, insn, &state);
+ break;
- case INSN_CALL:
- case INSN_CALL_DYNAMIC:
- ret = validate_call(file, insn, &state);
+ case INSN_JUMP_DYNAMIC:
+ case INSN_JUMP_DYNAMIC_CONDITIONAL:
+ if (is_sibling_call(insn)) {
+ ret = validate_sibling_call(file, insn, statep);
if (ret)
return ret;
+ }
- if (opts.stackval && func && !is_special_call(insn) &&
- !has_valid_stack_frame(&state)) {
- WARN_INSN(insn, "call without frame pointer save/setup");
- return 1;
- }
+ if (insn->type == INSN_JUMP_DYNAMIC)
+ return 0;
- break;
+ break;
- case INSN_JUMP_CONDITIONAL:
- case INSN_JUMP_UNCONDITIONAL:
- if (is_sibling_call(insn)) {
- ret = validate_sibling_call(file, insn, &state);
- if (ret)
- return ret;
+ case INSN_SYSCALL:
+ if (func && (!next_insn || !next_insn->hint)) {
+ WARN_INSN(insn, "unsupported instruction in callable function");
+ return 1;
+ }
- } else if (insn->jump_dest) {
- ret = validate_branch(file, func,
- insn->jump_dest, state);
- if (ret) {
- BT_INSN(insn, "(branch)");
- return ret;
- }
- }
+ break;
- if (insn->type == INSN_JUMP_UNCONDITIONAL)
- return 0;
+ case INSN_SYSRET:
+ if (func && (!next_insn || !next_insn->hint)) {
+ WARN_INSN(insn, "unsupported instruction in callable function");
+ return 1;
+ }
+ return 0;
+
+ case INSN_STAC:
+ if (!opts.uaccess)
break;
- case INSN_JUMP_DYNAMIC:
- case INSN_JUMP_DYNAMIC_CONDITIONAL:
- if (is_sibling_call(insn)) {
- ret = validate_sibling_call(file, insn, &state);
- if (ret)
- return ret;
- }
+ if (statep->uaccess) {
+ WARN_INSN(insn, "recursive UACCESS enable");
+ return 1;
+ }
- if (insn->type == INSN_JUMP_DYNAMIC)
- return 0;
+ statep->uaccess = true;
+ break;
+ case INSN_CLAC:
+ if (!opts.uaccess)
break;
- case INSN_SYSCALL:
- if (func && (!next_insn || !next_insn->hint)) {
- WARN_INSN(insn, "unsupported instruction in callable function");
- return 1;
- }
+ if (!statep->uaccess && func) {
+ WARN_INSN(insn, "redundant UACCESS disable");
+ return 1;
+ }
- break;
+ if (func_uaccess_safe(func) && !statep->uaccess_stack) {
+ WARN_INSN(insn, "UACCESS-safe disables UACCESS");
+ return 1;
+ }
- case INSN_SYSRET:
- if (func && (!next_insn || !next_insn->hint)) {
- WARN_INSN(insn, "unsupported instruction in callable function");
- return 1;
- }
+ statep->uaccess = false;
+ break;
- return 0;
+ case INSN_STD:
+ if (statep->df) {
+ WARN_INSN(insn, "recursive STD");
+ return 1;
+ }
- case INSN_STAC:
- if (!opts.uaccess)
- break;
+ statep->df = true;
+ break;
- if (state.uaccess) {
- WARN_INSN(insn, "recursive UACCESS enable");
- return 1;
- }
+ case INSN_CLD:
+ if (!statep->df && func) {
+ WARN_INSN(insn, "redundant CLD");
+ return 1;
+ }
- state.uaccess = true;
- break;
+ statep->df = false;
+ break;
- case INSN_CLAC:
- if (!opts.uaccess)
- break;
+ default:
+ break;
+ }
- if (!state.uaccess && func) {
- WARN_INSN(insn, "redundant UACCESS disable");
- return 1;
- }
+ *dead_end = insn->dead_end;
- if (func_uaccess_safe(func) && !state.uaccess_stack) {
- WARN_INSN(insn, "UACCESS-safe disables UACCESS");
- return 1;
- }
+ return 0;
+}
- state.uaccess = false;
- break;
+/*
+ * Follow the branch starting at the given instruction, and recursively follow
+ * any other branches (jumps). Meanwhile, track the frame pointer state at
+ * each instruction and validate all the rules described in
+ * tools/objtool/Documentation/objtool.txt.
+ */
+static int validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state)
+{
+ struct instruction *next_insn, *prev_insn = NULL;
+ struct section *sec;
+ bool dead_end;
+ int ret;
- case INSN_STD:
- if (state.df) {
- WARN_INSN(insn, "recursive STD");
- return 1;
- }
+ if (func && func->ignore)
+ return 0;
- state.df = true;
- break;
+ sec = insn->sec;
- case INSN_CLD:
- if (!state.df && func) {
- WARN_INSN(insn, "redundant CLD");
- return 1;
- }
+ while (1) {
+ next_insn = next_insn_to_validate(file, insn);
- state.df = false;
- break;
+ if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
+ /* Ignore KCFI type preambles, which always fall through */
+ if (!strncmp(func->name, "__cfi_", 6) ||
+ !strncmp(func->name, "__pfx_", 6) ||
+ !strncmp(func->name, "__pi___cfi_", 11) ||
+ !strncmp(func->name, "__pi___pfx_", 11))
+ return 0;
- default:
- break;
+ if (file->ignore_unreachables)
+ return 0;
+
+ WARN("%s() falls through to next function %s()",
+ func->name, insn_func(insn)->name);
+ func->warned = 1;
+
+ return 1;
}
- if (insn->dead_end)
- return 0;
+ ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
+ &dead_end);
+ if (dead_end)
+ break;
if (!next_insn) {
if (state.cfi.cfa.base == CFI_UNDEFINED)
@@ -3802,7 +3826,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
insn = next_insn;
}
- return 0;
+ return ret;
}
static int validate_unwind_hint(struct objtool_file *file,
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 09/28] objtool: Record symbol name max length
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (7 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 08/28] objtool: Extract code to validate instruction from the validate branch loop Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 10/28] objtool: Add option to trace function validation Alexandre Chartre
` (20 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Keep track of the maximum length of symbol names. This will help
formatting the code flow between different functions.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 609994ad6ab41..1fd56e8afbfc6 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -35,6 +35,8 @@ static struct cfi_state init_cfi;
static struct cfi_state func_cfi;
static struct cfi_state force_undefined_cfi;
+static size_t sym_name_max_len;
+
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset)
{
@@ -2463,6 +2465,7 @@ static bool is_profiling_func(const char *name)
static int classify_symbols(struct objtool_file *file)
{
struct symbol *func;
+ size_t len;
for_each_sym(file, func) {
if (func->type == STT_NOTYPE && strstarts(func->name, ".L"))
@@ -2489,6 +2492,10 @@ static int classify_symbols(struct objtool_file *file)
if (is_profiling_func(func->name))
func->profiling_func = true;
+
+ len = strlen(func->name);
+ if (len > sym_name_max_len)
+ sym_name_max_len = len;
}
return 0;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 10/28] objtool: Add option to trace function validation
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (8 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 09/28] objtool: Record symbol name max length Alexandre Chartre
@ 2025-11-13 16:48 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 11/28] objtool: Trace instruction state changes during " Alexandre Chartre
` (19 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:48 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Add an option to trace and have information during the validation
of specified functions. Functions are specified with the --trace
option which can be a single function name (e.g. --trace foo to
trace the function with the name "foo"), or a shell wildcard
pattern (e.g. --trace foo* to trace all functions with a name
starting with "foo").
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/Build | 1 +
tools/objtool/builtin-check.c | 1 +
tools/objtool/check.c | 108 ++++++++++++++++++++----
tools/objtool/disas.c | 89 +++++++++++++++++++
tools/objtool/include/objtool/builtin.h | 1 +
tools/objtool/include/objtool/check.h | 8 +-
tools/objtool/include/objtool/disas.h | 11 +++
tools/objtool/include/objtool/trace.h | 68 +++++++++++++++
tools/objtool/include/objtool/warn.h | 1 +
tools/objtool/trace.c | 9 ++
10 files changed, 278 insertions(+), 19 deletions(-)
create mode 100644 tools/objtool/include/objtool/trace.h
create mode 100644 tools/objtool/trace.c
diff --git a/tools/objtool/Build b/tools/objtool/Build
index ee04fba8c9d16..6e62ffd407926 100644
--- a/tools/objtool/Build
+++ b/tools/objtool/Build
@@ -9,6 +9,7 @@ objtool-y += elf.o
objtool-y += objtool.o
objtool-$(BUILD_DISAS) += disas.o
+objtool-$(BUILD_DISAS) += trace.o
objtool-$(BUILD_ORC) += orc_gen.o
objtool-$(BUILD_ORC) += orc_dump.o
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index 0f6b197cfcb03..7c95d3d263d72 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -100,6 +100,7 @@ static const struct option check_options[] = {
OPT_STRING('o', "output", &opts.output, "file", "output file name"),
OPT_BOOLEAN(0, "sec-address", &opts.sec_address, "print section addresses in warnings"),
OPT_BOOLEAN(0, "stats", &opts.stats, "print statistics"),
+ OPT_STRING(0, "trace", &opts.trace, "func", "trace function validation"),
OPT_BOOLEAN('v', "verbose", &opts.verbose, "verbose warnings"),
OPT_BOOLEAN(0, "Werror", &opts.werror, "return error on warnings"),
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 1fd56e8afbfc6..e047cf12f0ddb 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3,6 +3,7 @@
* Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
+#include <fnmatch.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
@@ -14,6 +15,7 @@
#include <objtool/disas.h>
#include <objtool/check.h>
#include <objtool/special.h>
+#include <objtool/trace.h>
#include <objtool/warn.h>
#include <objtool/endianness.h>
@@ -35,7 +37,9 @@ static struct cfi_state init_cfi;
static struct cfi_state func_cfi;
static struct cfi_state force_undefined_cfi;
-static size_t sym_name_max_len;
+struct disas_context *objtool_disas_ctx;
+
+size_t sym_name_max_len;
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset)
@@ -3519,8 +3523,10 @@ static bool skip_alt_group(struct instruction *insn)
return false;
/* ANNOTATE_IGNORE_ALTERNATIVE */
- if (insn->alt_group->ignore)
+ if (insn->alt_group->ignore) {
+ TRACE_INSN(insn, "alt group ignored");
return true;
+ }
/*
* For NOP patched with CLAC/STAC, only follow the latter to avoid
@@ -3544,6 +3550,8 @@ static bool skip_alt_group(struct instruction *insn)
static int validate_branch(struct objtool_file *file, struct symbol *func,
struct instruction *insn, struct insn_state state);
+static int do_validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state);
static int validate_insn(struct objtool_file *file, struct symbol *func,
struct instruction *insn, struct insn_state *statep,
@@ -3565,8 +3573,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
return 1;
- if (insn->visited & visited)
+ if (insn->visited & visited) {
+ TRACE_INSN(insn, "already visited");
return 0;
+ }
} else {
nr_insns_visited++;
}
@@ -3603,8 +3613,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
* It will be seen later via the
* straight-line path.
*/
- if (!prev_insn)
+ if (!prev_insn) {
+ TRACE_INSN(insn, "defer restore");
return 0;
+ }
WARN_INSN(insn, "objtool isn't smart enough to handle this CFI save/restore combo");
return 1;
@@ -3632,13 +3644,24 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
return 1;
if (insn->alts) {
+ int i, num_alts;
+
+ num_alts = 0;
+ for (alt = insn->alts; alt; alt = alt->next)
+ num_alts++;
+
+ i = 1;
for (alt = insn->alts; alt; alt = alt->next) {
+ TRACE_INSN(insn, "alternative %d/%d", i, num_alts);
ret = validate_branch(file, func, alt->insn, *statep);
if (ret) {
BT_INSN(insn, "(alt)");
return ret;
}
+ i++;
}
+
+ TRACE_INSN(insn, "alternative orig");
}
if (skip_alt_group(insn))
@@ -3650,10 +3673,16 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
switch (insn->type) {
case INSN_RETURN:
+ TRACE_INSN(insn, "return");
return validate_return(func, insn, statep);
case INSN_CALL:
case INSN_CALL_DYNAMIC:
+ if (insn->type == INSN_CALL)
+ TRACE_INSN(insn, "call");
+ else
+ TRACE_INSN(insn, "indirect call");
+
ret = validate_call(file, insn, statep);
if (ret)
return ret;
@@ -3669,13 +3698,18 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
case INSN_JUMP_CONDITIONAL:
case INSN_JUMP_UNCONDITIONAL:
if (is_sibling_call(insn)) {
+ TRACE_INSN(insn, "sibling call");
ret = validate_sibling_call(file, insn, statep);
if (ret)
return ret;
} else if (insn->jump_dest) {
- ret = validate_branch(file, func,
- insn->jump_dest, *statep);
+ if (insn->type == INSN_JUMP_UNCONDITIONAL)
+ TRACE_INSN(insn, "unconditional jump");
+ else
+ TRACE_INSN(insn, "jump taken");
+
+ ret = validate_branch(file, func, insn->jump_dest, *statep);
if (ret) {
BT_INSN(insn, "(branch)");
return ret;
@@ -3685,10 +3719,12 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
if (insn->type == INSN_JUMP_UNCONDITIONAL)
return 0;
+ TRACE_INSN(insn, "jump not taken");
break;
case INSN_JUMP_DYNAMIC:
case INSN_JUMP_DYNAMIC_CONDITIONAL:
+ TRACE_INSN(insn, "indirect jump");
if (is_sibling_call(insn)) {
ret = validate_sibling_call(file, insn, statep);
if (ret)
@@ -3701,6 +3737,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
break;
case INSN_SYSCALL:
+ TRACE_INSN(insn, "syscall");
if (func && (!next_insn || !next_insn->hint)) {
WARN_INSN(insn, "unsupported instruction in callable function");
return 1;
@@ -3709,6 +3746,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
break;
case INSN_SYSRET:
+ TRACE_INSN(insn, "sysret");
if (func && (!next_insn || !next_insn->hint)) {
WARN_INSN(insn, "unsupported instruction in callable function");
return 1;
@@ -3717,6 +3755,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
return 0;
case INSN_STAC:
+ TRACE_INSN(insn, "stac");
if (!opts.uaccess)
break;
@@ -3729,6 +3768,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
break;
case INSN_CLAC:
+ TRACE_INSN(insn, "clac");
if (!opts.uaccess)
break;
@@ -3746,6 +3786,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
break;
case INSN_STD:
+ TRACE_INSN(insn, "std");
if (statep->df) {
WARN_INSN(insn, "recursive STD");
return 1;
@@ -3755,6 +3796,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
break;
case INSN_CLD:
+ TRACE_INSN(insn, "cld");
if (!statep->df && func) {
WARN_INSN(insn, "redundant CLD");
return 1;
@@ -3767,8 +3809,10 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
break;
}
- *dead_end = insn->dead_end;
+ if (insn->dead_end)
+ TRACE_INSN(insn, "dead end");
+ *dead_end = insn->dead_end;
return 0;
}
@@ -3778,8 +3822,8 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
* each instruction and validate all the rules described in
* tools/objtool/Documentation/objtool.txt.
*/
-static int validate_branch(struct objtool_file *file, struct symbol *func,
- struct instruction *insn, struct insn_state state)
+static int do_validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state)
{
struct instruction *next_insn, *prev_insn = NULL;
struct section *sec;
@@ -3791,7 +3835,10 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
sec = insn->sec;
- while (1) {
+ do {
+
+ insn->trace = 0;
+
next_insn = next_insn_to_validate(file, insn);
if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
@@ -3814,10 +3861,15 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
ret = validate_insn(file, func, insn, &state, prev_insn, next_insn,
&dead_end);
- if (dead_end)
- break;
- if (!next_insn) {
+ if (!insn->trace) {
+ if (ret)
+ TRACE_INSN(insn, "warning (%d)", ret);
+ else
+ TRACE_INSN(insn, NULL);
+ }
+
+ if (!dead_end && !next_insn) {
if (state.cfi.cfa.base == CFI_UNDEFINED)
return 0;
if (file->ignore_unreachables)
@@ -3831,7 +3883,20 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
prev_insn = insn;
insn = next_insn;
- }
+
+ } while (!dead_end);
+
+ return ret;
+}
+
+static int validate_branch(struct objtool_file *file, struct symbol *func,
+ struct instruction *insn, struct insn_state state)
+{
+ int ret;
+
+ trace_depth_inc();
+ ret = do_validate_branch(file, func, insn, state);
+ trace_depth_dec();
return ret;
}
@@ -4277,9 +4342,18 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
if (opts.uaccess)
state->uaccess = sym->uaccess_safe;
+ if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) {
+ trace_enable();
+ TRACE("%s: validation begin\n", sym->name);
+ }
+
ret = validate_branch(file, insn_func(insn), insn, *state);
if (ret)
BT_INSN(insn, "<=== (sym)");
+
+ TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end");
+ trace_disable();
+
return ret;
}
@@ -4693,8 +4767,6 @@ static void free_insns(struct objtool_file *file)
free(chunk->addr);
}
-static struct disas_context *objtool_disas_ctx;
-
const char *objtool_disas_insn(struct instruction *insn)
{
struct disas_context *dctx = objtool_disas_ctx;
@@ -4716,8 +4788,10 @@ int check(struct objtool_file *file)
* disassembly context to disassemble instruction or function
* on warning or backtrace.
*/
- if (opts.verbose || opts.backtrace) {
+ if (opts.verbose || opts.backtrace || opts.trace) {
disas_ctx = disas_context_create(file);
+ if (!disas_ctx)
+ opts.trace = false;
objtool_disas_ctx = disas_ctx;
}
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index c8f3fad086faa..181e4ce975d36 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -311,6 +311,95 @@ char *disas_result(struct disas_context *dctx)
return dctx->result;
}
+#define DISAS_INSN_OFFSET_SPACE 10
+#define DISAS_INSN_SPACE 60
+
+/*
+ * Print a message in the instruction flow. If insn is not NULL then
+ * the instruction address is printed in addition of the message,
+ * otherwise only the message is printed. In all cases, the instruction
+ * itself is not printed.
+ */
+void disas_print_info(FILE *stream, struct instruction *insn, int depth,
+ const char *format, ...)
+{
+ const char *addr_str;
+ va_list args;
+ int len;
+ int i;
+
+ len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE;
+ if (depth < 0) {
+ len += depth;
+ depth = 0;
+ }
+
+ if (insn && insn->sec) {
+ addr_str = offstr(insn->sec, insn->offset);
+ fprintf(stream, "%6lx: %-*s ", insn->offset, len, addr_str);
+ free((char *)addr_str);
+ } else {
+ len += DISAS_INSN_OFFSET_SPACE + 1;
+ fprintf(stream, "%-*s", len, "");
+ }
+
+ /* print vertical bars to show the code flow */
+ for (i = 0; i < depth; i++)
+ fprintf(stream, "| ");
+
+ if (format) {
+ va_start(args, format);
+ vfprintf(stream, format, args);
+ va_end(args);
+ }
+}
+
+/*
+ * Print an instruction address (offset and function), the instruction itself
+ * and an optional message.
+ */
+void disas_print_insn(FILE *stream, struct disas_context *dctx,
+ struct instruction *insn, int depth,
+ const char *format, ...)
+{
+ char fake_nop_insn[32];
+ const char *insn_str;
+ bool fake_nop;
+ va_list args;
+ int len;
+
+ /*
+ * Alternative can insert a fake nop, sometimes with no
+ * associated section so nothing to disassemble.
+ */
+ fake_nop = (!insn->sec && insn->type == INSN_NOP);
+ if (fake_nop) {
+ snprintf(fake_nop_insn, 32, "<fake nop> (%d bytes)", insn->len);
+ insn_str = fake_nop_insn;
+ } else {
+ disas_insn(dctx, insn);
+ insn_str = disas_result(dctx);
+ }
+
+ /* print the instruction */
+ len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1;
+ disas_print_info(stream, insn, depth, "%-*s", len, insn_str);
+
+ /* print message if any */
+ if (!format)
+ return;
+
+ if (strcmp(format, "\n") == 0) {
+ fprintf(stream, "\n");
+ return;
+ }
+
+ fprintf(stream, " - ");
+ va_start(args, format);
+ vfprintf(stream, format, args);
+ va_end(args);
+}
+
/*
* Disassemble a single instruction. Return the size of the instruction.
*/
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index ab22673862e1b..e79a52c098883 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -38,6 +38,7 @@ struct opts {
const char *output;
bool sec_address;
bool stats;
+ const char *trace;
bool verbose;
bool werror;
};
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index f3ea144d4746c..e5f97acb62522 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -64,8 +64,9 @@ struct instruction {
noendbr : 1,
unret : 1,
visited : 4,
- no_reloc : 1;
- /* 10 bit hole */
+ no_reloc : 1,
+ trace : 1;
+ /* 9 bit hole */
struct alt_group *alt_group;
struct instruction *jump_dest;
@@ -141,4 +142,7 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruc
const char *objtool_disas_insn(struct instruction *insn);
+extern size_t sym_name_max_len;
+extern struct disas_context *objtool_disas_ctx;
+
#endif /* _CHECK_H */
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
index 1aee1fbe0bb97..5db75d06f2197 100644
--- a/tools/objtool/include/objtool/disas.h
+++ b/tools/objtool/include/objtool/disas.h
@@ -19,6 +19,11 @@ int disas_info_init(struct disassemble_info *dinfo,
const char *options);
size_t disas_insn(struct disas_context *dctx, struct instruction *insn);
char *disas_result(struct disas_context *dctx);
+void disas_print_info(FILE *stream, struct instruction *insn, int depth,
+ const char *format, ...);
+void disas_print_insn(FILE *stream, struct disas_context *dctx,
+ struct instruction *insn, int depth,
+ const char *format, ...);
#else /* DISAS */
@@ -51,6 +56,12 @@ static inline char *disas_result(struct disas_context *dctx)
return NULL;
}
+static inline void disas_print_info(FILE *stream, struct instruction *insn,
+ int depth, const char *format, ...) {}
+static inline void disas_print_insn(FILE *stream, struct disas_context *dctx,
+ struct instruction *insn, int depth,
+ const char *format, ...) {}
+
#endif /* DISAS */
#endif /* _DISAS_H */
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
new file mode 100644
index 0000000000000..ea0904a0ce001
--- /dev/null
+++ b/tools/objtool/include/objtool/trace.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ */
+
+#ifndef _TRACE_H
+#define _TRACE_H
+
+#include <objtool/check.h>
+#include <objtool/disas.h>
+
+#ifdef DISAS
+
+extern bool trace;
+extern int trace_depth;
+
+#define TRACE(fmt, ...) \
+({ if (trace) \
+ fprintf(stderr, fmt, ##__VA_ARGS__); \
+})
+
+#define TRACE_INSN(insn, fmt, ...) \
+({ \
+ if (trace) { \
+ disas_print_insn(stderr, objtool_disas_ctx, \
+ insn, trace_depth - 1, \
+ fmt, ##__VA_ARGS__); \
+ fprintf(stderr, "\n"); \
+ insn->trace = 1; \
+ } \
+})
+
+static inline void trace_enable(void)
+{
+ trace = true;
+ trace_depth = 0;
+}
+
+static inline void trace_disable(void)
+{
+ trace = false;
+}
+
+static inline void trace_depth_inc(void)
+{
+ if (trace)
+ trace_depth++;
+}
+
+static inline void trace_depth_dec(void)
+{
+ if (trace)
+ trace_depth--;
+}
+
+#else /* DISAS */
+
+#define TRACE(fmt, ...)
+#define TRACE_INSN(insn, fmt, ...)
+
+static inline void trace_enable(void) {}
+static inline void trace_disable(void) {}
+static inline void trace_depth_inc(void) {}
+static inline void trace_depth_dec(void) {}
+
+#endif
+
+#endif /* _TRACE_H */
diff --git a/tools/objtool/include/objtool/warn.h b/tools/objtool/include/objtool/warn.h
index 8be02483823dc..08540baa02126 100644
--- a/tools/objtool/include/objtool/warn.h
+++ b/tools/objtool/include/objtool/warn.h
@@ -97,6 +97,7 @@ static inline char *offstr(struct section *sec, unsigned long offset)
_len = (_len < 50) ? 50 - _len : 0; \
WARN(" %s: " format " %*s%s", _str, ##__VA_ARGS__, _len, "", _istr); \
free(_str); \
+ __insn->trace = 1; \
} \
})
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
new file mode 100644
index 0000000000000..bc3113ba72fdb
--- /dev/null
+++ b/tools/objtool/trace.c
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ */
+
+#include <objtool/trace.h>
+
+bool trace;
+int trace_depth;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 11/28] objtool: Trace instruction state changes during function validation
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (9 preceding siblings ...)
2025-11-13 16:48 ` [PATCH v4 10/28] objtool: Add option to trace function validation Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-14 21:21 ` Josh Poimboeuf
2025-11-13 16:49 ` [PATCH v4 12/28] objtool: Improve register reporting " Alexandre Chartre
` (18 subsequent siblings)
29 siblings, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
During function validation, objtool maintains a per-instruction state,
in particular to track call frame information. When tracing validation,
print any instruction state changes.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 8 +-
tools/objtool/include/objtool/trace.h | 10 ++
tools/objtool/trace.c | 133 ++++++++++++++++++++++++++
3 files changed, 150 insertions(+), 1 deletion(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index e047cf12f0ddb..26682ac0185ec 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3558,6 +3558,8 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
struct instruction *prev_insn, struct instruction *next_insn,
bool *dead_end)
{
+ /* prev_state is not used if there is no disassembly support */
+ struct insn_state prev_state __maybe_unused;
struct alternative *alt;
u8 visited;
int ret;
@@ -3667,7 +3669,11 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
if (skip_alt_group(insn))
return 0;
- if (handle_insn_ops(insn, next_insn, statep))
+ prev_state = *statep;
+ ret = handle_insn_ops(insn, next_insn, statep);
+ TRACE_INSN_STATE(insn, &prev_state, statep);
+
+ if (ret)
return 1;
switch (insn->type) {
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
index ea0904a0ce001..5b8abdb9b09fb 100644
--- a/tools/objtool/include/objtool/trace.h
+++ b/tools/objtool/include/objtool/trace.h
@@ -30,6 +30,12 @@ extern int trace_depth;
} \
})
+#define TRACE_INSN_STATE(insn, sprev, snext) \
+({ \
+ if (trace) \
+ trace_insn_state(insn, sprev, snext); \
+})
+
static inline void trace_enable(void)
{
trace = true;
@@ -53,10 +59,14 @@ static inline void trace_depth_dec(void)
trace_depth--;
}
+void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
+ struct insn_state *snext);
+
#else /* DISAS */
#define TRACE(fmt, ...)
#define TRACE_INSN(insn, fmt, ...)
+#define TRACE_INSN_STATE(insn, sprev, snext)
static inline void trace_enable(void) {}
static inline void trace_disable(void) {}
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
index bc3113ba72fdb..f45ae3e88d428 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -7,3 +7,136 @@
bool trace;
int trace_depth;
+
+/*
+ * Macros to trace CFI state attributes changes.
+ */
+
+#define TRACE_CFI_ATTR(attr, prev, next, fmt, ...) \
+({ \
+ if ((prev)->attr != (next)->attr) \
+ TRACE("%s=" fmt " ", #attr, __VA_ARGS__); \
+})
+
+#define TRACE_CFI_ATTR_BOOL(attr, prev, next) \
+ TRACE_CFI_ATTR(attr, prev, next, \
+ "%s", (next)->attr ? "true" : "false")
+
+#define TRACE_CFI_ATTR_NUM(attr, prev, next, fmt) \
+ TRACE_CFI_ATTR(attr, prev, next, fmt, (next)->attr)
+
+#define CFI_REG_NAME_MAXLEN 16
+
+/*
+ * Return the name of a register. Note that the same static buffer
+ * is returned if the name is dynamically generated.
+ */
+static const char *cfi_reg_name(unsigned int reg)
+{
+ static char rname_buffer[CFI_REG_NAME_MAXLEN];
+
+ switch (reg) {
+ case CFI_UNDEFINED:
+ return "<undefined>";
+ case CFI_CFA:
+ return "cfa";
+ case CFI_SP_INDIRECT:
+ return "(sp)";
+ case CFI_BP_INDIRECT:
+ return "(bp)";
+ }
+
+ if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == 1)
+ return NULL;
+
+ return (const char *)rname_buffer;
+}
+
+/*
+ * Functions and macros to trace CFI registers changes.
+ */
+
+static void trace_cfi_reg(const char *prefix, int reg, const char *fmt,
+ int base_prev, int offset_prev,
+ int base_next, int offset_next)
+{
+ const char *rname;
+
+ if (base_prev == base_next && offset_prev == offset_next)
+ return;
+
+ if (prefix)
+ TRACE("%s:", prefix);
+
+ rname = cfi_reg_name(reg);
+
+ if (base_next == CFI_UNDEFINED) {
+ TRACE("%1$s=<undef> ", rname);
+ } else {
+ TRACE(fmt, rname,
+ cfi_reg_name(base_next), offset_next);
+ }
+}
+
+static void trace_cfi_reg_val(const char *prefix, int reg,
+ int base_prev, int offset_prev,
+ int base_next, int offset_next)
+{
+ trace_cfi_reg(prefix, reg, "%1$s=%2$s%3$+d ",
+ base_prev, offset_prev, base_next, offset_next);
+}
+
+static void trace_cfi_reg_ref(const char *prefix, int reg,
+ int base_prev, int offset_prev,
+ int base_next, int offset_next)
+{
+ trace_cfi_reg(prefix, reg, "%1$s=(%2$s%3$+d) ",
+ base_prev, offset_prev, base_next, offset_next);
+}
+
+#define TRACE_CFI_REG_VAL(reg, prev, next) \
+ trace_cfi_reg_val(NULL, reg, prev.base, prev.offset, \
+ next.base, next.offset)
+
+#define TRACE_CFI_REG_REF(reg, prev, next) \
+ trace_cfi_reg_ref(NULL, reg, prev.base, prev.offset, \
+ next.base, next.offset)
+
+void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
+ struct insn_state *snext)
+{
+ struct cfi_state *cprev, *cnext;
+ int i;
+
+ if (!memcmp(sprev, snext, sizeof(struct insn_state)))
+ return;
+
+ cprev = &sprev->cfi;
+ cnext = &snext->cfi;
+
+ disas_print_insn(stderr, objtool_disas_ctx, insn,
+ trace_depth - 1, "state: ");
+
+ /* print registers changes */
+ TRACE_CFI_REG_VAL(CFI_CFA, cprev->cfa, cnext->cfa);
+ for (i = 0; i < CFI_NUM_REGS; i++) {
+ TRACE_CFI_REG_VAL(i, cprev->vals[i], cnext->vals[i]);
+ TRACE_CFI_REG_REF(i, cprev->regs[i], cnext->regs[i]);
+ }
+
+ /* print attributes changes */
+ TRACE_CFI_ATTR_NUM(stack_size, cprev, cnext, "%d");
+ TRACE_CFI_ATTR_BOOL(drap, cprev, cnext);
+ if (cnext->drap) {
+ trace_cfi_reg_val("drap", cnext->drap_reg,
+ cprev->drap_reg, cprev->drap_offset,
+ cnext->drap_reg, cnext->drap_offset);
+ }
+ TRACE_CFI_ATTR_BOOL(bp_scratch, cprev, cnext);
+ TRACE_CFI_ATTR_NUM(instr, sprev, snext, "%d");
+ TRACE_CFI_ATTR_NUM(uaccess_stack, sprev, snext, "%u");
+
+ TRACE("\n");
+
+ insn->trace = 1;
+}
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 12/28] objtool: Improve register reporting during function validation
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (10 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 11/28] objtool: Trace instruction state changes during " Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 13/28] objtool: Identify the different types of alternatives Alexandre Chartre
` (17 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When tracing function validation, instruction state changes can
report changes involving registers. These registers are reported
with the name "r<num>" (e.g. "r3"). Print the CPU specific register
name instead of a generic name (e.g. print "rbx" instead of "r3"
on x86).
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/arch/loongarch/decode.c | 11 +++++++++++
tools/objtool/arch/powerpc/decode.c | 12 ++++++++++++
tools/objtool/arch/x86/decode.c | 8 ++++++++
tools/objtool/include/objtool/arch.h | 2 ++
tools/objtool/trace.c | 7 +++++++
5 files changed, 40 insertions(+)
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index 7645409918f5b..2c1a6cb61f76f 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -8,6 +8,17 @@
#include <linux/objtool_types.h>
#include <arch/elf.h>
+const char *arch_reg_name[CFI_NUM_REGS] = {
+ "zero", "ra", "tp", "sp",
+ "a0", "a1", "a2", "a3",
+ "a4", "a5", "a6", "a7",
+ "t0", "t1", "t2", "t3",
+ "t4", "t5", "t6", "t7",
+ "t8", "u0", "fp", "s0",
+ "s1", "s2", "s3", "s4",
+ "s5", "s6", "s7", "s8"
+};
+
int arch_ftrace_match(char *name)
{
return !strcmp(name, "_mcount");
diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c
index 9c3f49c455871..74d1a8603535a 100644
--- a/tools/objtool/arch/powerpc/decode.c
+++ b/tools/objtool/arch/powerpc/decode.c
@@ -10,6 +10,18 @@
#include <objtool/builtin.h>
#include <objtool/endianness.h>
+const char *arch_reg_name[CFI_NUM_REGS] = {
+ "r0", "sp", "r2", "r3",
+ "r4", "r5", "r6", "r7",
+ "r8", "r9", "r10", "r11",
+ "r12", "r13", "r14", "r15",
+ "r16", "r17", "r18", "r19",
+ "r20", "r21", "r22", "r23",
+ "r24", "r25", "r26", "r27",
+ "r28", "r29", "r30", "r31",
+ "ra"
+};
+
int arch_ftrace_match(char *name)
{
return !strcmp(name, "_mcount");
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index 7bd874871c31b..d651d8921ab47 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -24,6 +24,14 @@
#include <objtool/builtin.h>
#include <arch/elf.h>
+const char *arch_reg_name[CFI_NUM_REGS] = {
+ "rax", "rcx", "rdx", "rbx",
+ "rsp", "rbp", "rsi", "rdi",
+ "r8", "r9", "r10", "r11",
+ "r12", "r13", "r14", "r15",
+ "ra"
+};
+
int arch_ftrace_match(char *name)
{
return !strcmp(name, "__fentry__");
diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h
index 2bd7a76f053d9..5bcd0df5d8b61 100644
--- a/tools/objtool/include/objtool/arch.h
+++ b/tools/objtool/include/objtool/arch.h
@@ -103,6 +103,8 @@ unsigned long arch_pc_relative_offset(struct instruction *insn,
unsigned int arch_reloc_size(struct reloc *reloc);
unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table);
+extern const char *arch_reg_name[CFI_NUM_REGS];
+
#ifdef DISAS
#include <bfd.h>
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
index f45ae3e88d428..ef9250d4646bb 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -34,6 +34,7 @@ int trace_depth;
static const char *cfi_reg_name(unsigned int reg)
{
static char rname_buffer[CFI_REG_NAME_MAXLEN];
+ const char *rname;
switch (reg) {
case CFI_UNDEFINED:
@@ -46,6 +47,12 @@ static const char *cfi_reg_name(unsigned int reg)
return "(bp)";
}
+ if (reg < CFI_NUM_REGS) {
+ rname = arch_reg_name[reg];
+ if (rname)
+ return rname;
+ }
+
if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == 1)
return NULL;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 13/28] objtool: Identify the different types of alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (11 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 12/28] objtool: Improve register reporting " Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 14/28] objtool: Improve tracing of alternative instructions Alexandre Chartre
` (16 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Alternative code, including jump table and exception table, is represented
with the same struct alternative structure. But there is no obvious way to
identify whether the struct represents alternative instructions, a jump
table or an exception table.
So add a type to struct alternative to clearly identify the type of
alternative.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 13 ++++++++-----
tools/objtool/include/objtool/check.h | 12 ++++++++++++
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 26682ac0185ec..93268b7c015e3 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -25,11 +25,6 @@
#include <linux/static_call_types.h>
#include <linux/string.h>
-struct alternative {
- struct alternative *next;
- struct instruction *insn;
-};
-
static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache;
static struct cfi_init_state initial_func_cfi;
@@ -1910,6 +1905,7 @@ static int add_special_section_alts(struct objtool_file *file)
struct list_head special_alts;
struct instruction *orig_insn, *new_insn;
struct special_alt *special_alt, *tmp;
+ enum alternative_type alt_type;
struct alternative *alt;
int ret;
@@ -1948,11 +1944,17 @@ static int add_special_section_alts(struct objtool_file *file)
if (ret)
return ret;
+ alt_type = ALT_TYPE_INSTRUCTIONS;
+
} else if (special_alt->jump_or_nop) {
ret = handle_jump_alt(file, special_alt, orig_insn,
&new_insn);
if (ret)
return ret;
+
+ alt_type = ALT_TYPE_JUMP_TABLE;
+ } else {
+ alt_type = ALT_TYPE_EX_TABLE;
}
alt = calloc(1, sizeof(*alt));
@@ -1963,6 +1965,7 @@ static int add_special_section_alts(struct objtool_file *file)
alt->insn = new_insn;
alt->next = orig_insn->alts;
+ alt->type = alt_type;
orig_insn->alts = alt;
list_del(&special_alt->list);
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index e5f97acb62522..73310c56e91b6 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -38,6 +38,18 @@ struct alt_group {
bool ignore;
};
+enum alternative_type {
+ ALT_TYPE_INSTRUCTIONS,
+ ALT_TYPE_JUMP_TABLE,
+ ALT_TYPE_EX_TABLE,
+};
+
+struct alternative {
+ struct alternative *next;
+ struct instruction *insn;
+ enum alternative_type type;
+};
+
#define INSN_CHUNK_BITS 8
#define INSN_CHUNK_SIZE (1 << INSN_CHUNK_BITS)
#define INSN_CHUNK_MAX (INSN_CHUNK_SIZE - 1)
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 14/28] objtool: Improve tracing of alternative instructions
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (12 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 13/28] objtool: Identify the different types of alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 15/28] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
` (15 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When tracing function validation, improve the reporting of
alternative instruction by more clearly showing the different
alternatives beginning and end.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 18 +++-----
tools/objtool/disas.c | 34 +++++++++++++++
tools/objtool/include/objtool/disas.h | 6 +++
tools/objtool/include/objtool/trace.h | 61 +++++++++++++++++++++++++++
tools/objtool/trace.c | 55 ++++++++++++++++++++++++
5 files changed, 162 insertions(+), 12 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 93268b7c015e3..9321486e6265e 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3527,7 +3527,7 @@ static bool skip_alt_group(struct instruction *insn)
/* ANNOTATE_IGNORE_ALTERNATIVE */
if (insn->alt_group->ignore) {
- TRACE_INSN(insn, "alt group ignored");
+ TRACE_ALT(insn, "alt group ignored");
return true;
}
@@ -3561,8 +3561,9 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
struct instruction *prev_insn, struct instruction *next_insn,
bool *dead_end)
{
- /* prev_state is not used if there is no disassembly support */
+ /* prev_state and alt_name are not used if there is no disassembly support */
struct insn_state prev_state __maybe_unused;
+ char *alt_name __maybe_unused = NULL;
struct alternative *alt;
u8 visited;
int ret;
@@ -3649,24 +3650,17 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
return 1;
if (insn->alts) {
- int i, num_alts;
-
- num_alts = 0;
- for (alt = insn->alts; alt; alt = alt->next)
- num_alts++;
-
- i = 1;
for (alt = insn->alts; alt; alt = alt->next) {
- TRACE_INSN(insn, "alternative %d/%d", i, num_alts);
+ TRACE_ALT_BEGIN(insn, alt, alt_name);
ret = validate_branch(file, func, alt->insn, *statep);
+ TRACE_ALT_END(insn, alt, alt_name);
if (ret) {
BT_INSN(insn, "(alt)");
return ret;
}
- i++;
}
- TRACE_INSN(insn, "alternative orig");
+ TRACE_ALT_INFO_NOADDR(insn, "/ ", "DEFAULT");
}
if (skip_alt_group(insn))
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 181e4ce975d36..058e2053c31a7 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -3,6 +3,8 @@
* Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com>
*/
+#define _GNU_SOURCE
+
#include <objtool/arch.h>
#include <objtool/check.h>
#include <objtool/disas.h>
@@ -427,6 +429,38 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
return disasm(insn->offset, &dctx->info);
}
+/*
+ * Provide a name for an alternative.
+ */
+char *disas_alt_name(struct alternative *alt)
+{
+ char *str = NULL;
+
+ switch (alt->type) {
+
+ case ALT_TYPE_EX_TABLE:
+ str = strdup("EXCEPTION");
+ break;
+
+ case ALT_TYPE_JUMP_TABLE:
+ str = strdup("JUMP");
+ break;
+
+ case ALT_TYPE_INSTRUCTIONS:
+ /*
+ * This is a non-default group alternative. Create a unique
+ * name using the offset of the first original and alternative
+ * instructions.
+ */
+ asprintf(&str, "ALTERNATIVE %lx.%lx",
+ alt->insn->alt_group->orig_group->first_insn->offset,
+ alt->insn->alt_group->first_insn->offset);
+ break;
+ }
+
+ return str;
+}
+
/*
* Disassemble a function.
*/
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
index 5db75d06f2197..f1bef2a6003e8 100644
--- a/tools/objtool/include/objtool/disas.h
+++ b/tools/objtool/include/objtool/disas.h
@@ -6,6 +6,7 @@
#ifndef _DISAS_H
#define _DISAS_H
+struct alternative;
struct disas_context;
struct disassemble_info;
@@ -24,6 +25,7 @@ void disas_print_info(FILE *stream, struct instruction *insn, int depth,
void disas_print_insn(FILE *stream, struct disas_context *dctx,
struct instruction *insn, int depth,
const char *format, ...);
+char *disas_alt_name(struct alternative *alt);
#else /* DISAS */
@@ -61,6 +63,10 @@ static inline void disas_print_info(FILE *stream, struct instruction *insn,
static inline void disas_print_insn(FILE *stream, struct disas_context *dctx,
struct instruction *insn, int depth,
const char *format, ...) {}
+static inline char *disas_alt_name(struct alternative *alt)
+{
+ return NULL;
+}
#endif /* DISAS */
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
index 5b8abdb9b09fb..9a608523b6b08 100644
--- a/tools/objtool/include/objtool/trace.h
+++ b/tools/objtool/include/objtool/trace.h
@@ -19,6 +19,21 @@ extern int trace_depth;
fprintf(stderr, fmt, ##__VA_ARGS__); \
})
+/*
+ * Print the instruction address and a message. The instruction
+ * itself is not printed.
+ */
+#define TRACE_ADDR(insn, fmt, ...) \
+({ \
+ if (trace) { \
+ disas_print_info(stderr, insn, trace_depth - 1, \
+ fmt "\n", ##__VA_ARGS__); \
+ } \
+})
+
+/*
+ * Print the instruction address, the instruction and a message.
+ */
#define TRACE_INSN(insn, fmt, ...) \
({ \
if (trace) { \
@@ -36,6 +51,36 @@ extern int trace_depth;
trace_insn_state(insn, sprev, snext); \
})
+#define TRACE_ALT_FMT(pfx, fmt) pfx "<alternative.%lx> " fmt
+
+#define TRACE_ALT(insn, fmt, ...) \
+ TRACE_INSN(insn, TRACE_ALT_FMT("", fmt), \
+ (insn)->offset, ##__VA_ARGS__)
+
+#define TRACE_ALT_INFO(insn, pfx, fmt, ...) \
+ TRACE_ADDR(insn, TRACE_ALT_FMT(pfx, fmt), \
+ (insn)->offset, ##__VA_ARGS__)
+
+#define TRACE_ALT_INFO_NOADDR(insn, pfx, fmt, ...) \
+ TRACE_ADDR(NULL, TRACE_ALT_FMT(pfx, fmt), \
+ (insn)->offset, ##__VA_ARGS__)
+
+#define TRACE_ALT_BEGIN(insn, alt, alt_name) \
+({ \
+ if (trace) { \
+ alt_name = disas_alt_name(alt); \
+ trace_alt_begin(insn, alt, alt_name); \
+ } \
+})
+
+#define TRACE_ALT_END(insn, alt, alt_name) \
+({ \
+ if (trace) { \
+ trace_alt_end(insn, alt, alt_name); \
+ free(alt_name); \
+ } \
+})
+
static inline void trace_enable(void)
{
trace = true;
@@ -61,17 +106,33 @@ static inline void trace_depth_dec(void)
void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
struct insn_state *snext);
+void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
+ char *alt_name);
+void trace_alt_end(struct instruction *orig_insn, struct alternative *alt,
+ char *alt_name);
#else /* DISAS */
#define TRACE(fmt, ...)
+#define TRACE_ADDR(insn, fmt, ...)
#define TRACE_INSN(insn, fmt, ...)
#define TRACE_INSN_STATE(insn, sprev, snext)
+#define TRACE_ALT(insn, fmt, ...)
+#define TRACE_ALT_INFO(insn, fmt, ...)
+#define TRACE_ALT_INFO_NOADDR(insn, fmt, ...)
+#define TRACE_ALT_BEGIN(insn, alt, alt_name)
+#define TRACE_ALT_END(insn, alt, alt_name)
static inline void trace_enable(void) {}
static inline void trace_disable(void) {}
static inline void trace_depth_inc(void) {}
static inline void trace_depth_dec(void) {}
+static inline void trace_alt_begin(struct instruction *orig_insn,
+ struct alternative *alt,
+ char *alt_name) {};
+static inline void trace_alt_end(struct instruction *orig_insn,
+ struct alternative *alt,
+ char *alt_name) {};
#endif
diff --git a/tools/objtool/trace.c b/tools/objtool/trace.c
index ef9250d4646bb..1c3d961b5123a 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -147,3 +147,58 @@ void trace_insn_state(struct instruction *insn, struct insn_state *sprev,
insn->trace = 1;
}
+
+void trace_alt_begin(struct instruction *orig_insn, struct alternative *alt,
+ char *alt_name)
+{
+ struct instruction *alt_insn;
+ char suffix[2];
+
+ alt_insn = alt->insn;
+
+ if (alt->type == ALT_TYPE_EX_TABLE) {
+ /*
+ * When there is an exception table then the instruction
+ * at the original location is executed but it can cause
+ * an exception. In that case, the execution will be
+ * redirected to the alternative instruction.
+ *
+ * The instruction at the original location can have
+ * instruction alternatives, so we just print the location
+ * of the instruction that can cause the exception and
+ * not the instruction itself.
+ */
+ TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s for instruction at 0x%lx <%s+0x%lx>",
+ alt_name,
+ orig_insn->offset, orig_insn->sym->name,
+ orig_insn->offset - orig_insn->sym->offset);
+ } else {
+ TRACE_ALT_INFO_NOADDR(orig_insn, "/ ", "%s", alt_name);
+ }
+
+ if (alt->type == ALT_TYPE_JUMP_TABLE) {
+ /*
+ * For a jump alternative, if the default instruction is
+ * a NOP then it is replaced with the jmp instruction,
+ * otherwise it is replaced with a NOP instruction.
+ */
+ trace_depth++;
+ if (orig_insn->type == INSN_NOP) {
+ suffix[0] = (orig_insn->len == 5) ? 'q' : '\0';
+ TRACE_ADDR(orig_insn, "jmp%-3s %lx <%s+0x%lx>", suffix,
+ alt_insn->offset, alt_insn->sym->name,
+ alt_insn->offset - alt_insn->sym->offset);
+ } else {
+ TRACE_ADDR(orig_insn, "NOP%d", orig_insn->len);
+ trace_depth--;
+ }
+ }
+}
+
+void trace_alt_end(struct instruction *orig_insn, struct alternative *alt,
+ char *alt_name)
+{
+ if (alt->type == ALT_TYPE_JUMP_TABLE && orig_insn->type == INSN_NOP)
+ trace_depth--;
+ TRACE_ALT_INFO_NOADDR(orig_insn, "\\ ", "%s end", alt_name);
+}
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 15/28] objtool: Do not validate IBT for .return_sites and .call_sites
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (13 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 14/28] objtool: Improve tracing of alternative instructions Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 16/28] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
` (14 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
The .return_sites and .call_sites sections reference text addresses,
but not with the intent to indirect branch to them, so they don't
need to be validated for IBT.
This is useful when running objtool on object files which already
have .return_sites or .call_sites sections, for example to re-run
objtool after it has reported an error or a warning.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 9321486e6265e..646cb7fe61b02 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4627,6 +4627,8 @@ static int validate_ibt(struct objtool_file *file)
!strcmp(sec->name, ".llvm.call-graph-profile") ||
!strcmp(sec->name, ".llvm_bb_addr_map") ||
!strcmp(sec->name, "__tracepoints") ||
+ !strcmp(sec->name, ".return_sites") ||
+ !strcmp(sec->name, ".call_sites") ||
strstr(sec->name, "__patchable_function_entries"))
continue;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 16/28] objtool: Add the --disas=<function-pattern> action
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (14 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 15/28] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 17/28] objtool: Print headers for alternatives Alexandre Chartre
` (13 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Add the --disas=<function-pattern> actions to disassemble the specified
functions. The function pattern can be a single function name (e.g.
--disas foo to disassemble the function with the name "foo"), or a shell
wildcard pattern (e.g. --disas foo* to disassemble all functions with a
name starting with "foo").
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/builtin-check.c | 4 ++-
tools/objtool/check.c | 35 ++++++++++++++-----------
tools/objtool/disas.c | 27 +++++++++++++++++++
tools/objtool/include/objtool/builtin.h | 1 +
tools/objtool/include/objtool/disas.h | 2 ++
5 files changed, 52 insertions(+), 17 deletions(-)
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index 7c95d3d263d72..7ce49a00bdabc 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -73,6 +73,7 @@ static int parse_hacks(const struct option *opt, const char *str, int unset)
static const struct option check_options[] = {
OPT_GROUP("Actions:"),
+ OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"),
OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks),
OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"),
OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"),
@@ -160,7 +161,8 @@ static bool opts_valid(void)
return false;
}
- if (opts.hack_jump_label ||
+ if (opts.disas ||
+ opts.hack_jump_label ||
opts.hack_noinstr ||
opts.ibt ||
opts.mcount ||
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 646cb7fe61b02..25c1c7185d4f6 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -2570,7 +2570,7 @@ static int decode_sections(struct objtool_file *file)
* Must be before add_jump_destinations(), which depends on 'func'
* being set for alternatives, to enable proper sibling call detection.
*/
- if (opts.stackval || opts.orc || opts.uaccess || opts.noinstr) {
+ if (opts.stackval || opts.orc || opts.uaccess || opts.noinstr || opts.disas) {
ret = add_special_section_alts(file);
if (ret)
return ret;
@@ -4789,14 +4789,15 @@ int check(struct objtool_file *file)
int ret = 0, warnings = 0;
/*
- * If the verbose or backtrace option is used then we need a
- * disassembly context to disassemble instruction or function
- * on warning or backtrace.
+ * Create a disassembly context if we might disassemble any
+ * instruction or function.
*/
- if (opts.verbose || opts.backtrace || opts.trace) {
+ if (opts.verbose || opts.backtrace || opts.trace || opts.disas) {
disas_ctx = disas_context_create(file);
- if (!disas_ctx)
+ if (!disas_ctx) {
+ opts.disas = false;
opts.trace = false;
+ }
objtool_disas_ctx = disas_ctx;
}
@@ -4918,19 +4919,21 @@ int check(struct objtool_file *file)
}
out:
- if (!ret && !warnings)
- return 0;
-
- if (opts.werror && warnings)
- ret = 1;
-
- if (opts.verbose) {
+ if (ret || warnings) {
if (opts.werror && warnings)
- WARN("%d warning(s) upgraded to errors", warnings);
- print_args();
- disas_warned_funcs(disas_ctx);
+ ret = 1;
+
+ if (opts.verbose) {
+ if (opts.werror && warnings)
+ WARN("%d warning(s) upgraded to errors", warnings);
+ print_args();
+ disas_warned_funcs(disas_ctx);
+ }
}
+ if (opts.disas)
+ disas_funcs(disas_ctx);
+
if (disas_ctx) {
disas_context_destroy(disas_ctx);
objtool_disas_ctx = NULL;
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 058e2053c31a7..9b5d9075ab2d3 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -4,6 +4,7 @@
*/
#define _GNU_SOURCE
+#include <fnmatch.h>
#include <objtool/arch.h>
#include <objtool/check.h>
@@ -495,3 +496,29 @@ void disas_warned_funcs(struct disas_context *dctx)
disas_func(dctx, sym);
}
}
+
+void disas_funcs(struct disas_context *dctx)
+{
+ bool disas_all = !strcmp(opts.disas, "*");
+ struct section *sec;
+ struct symbol *sym;
+
+ for_each_sec(dctx->file, sec) {
+
+ if (!(sec->sh.sh_flags & SHF_EXECINSTR))
+ continue;
+
+ sec_for_each_sym(sec, sym) {
+ /*
+ * If the function had a warning and the verbose
+ * option is used then the function was already
+ * disassemble.
+ */
+ if (opts.verbose && sym->warned)
+ continue;
+
+ if (disas_all || fnmatch(opts.disas, sym->name, 0) == 0)
+ disas_func(dctx, sym);
+ }
+ }
+}
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index e79a52c098883..cde9474b57820 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -27,6 +27,7 @@ struct opts {
int prefix;
bool cfi;
bool noabs;
+ const char *disas;
/* options: */
bool backtrace;
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
index f1bef2a6003e8..15fd1237f03fc 100644
--- a/tools/objtool/include/objtool/disas.h
+++ b/tools/objtool/include/objtool/disas.h
@@ -15,6 +15,7 @@ struct disassemble_info;
struct disas_context *disas_context_create(struct objtool_file *file);
void disas_context_destroy(struct disas_context *dctx);
void disas_warned_funcs(struct disas_context *dctx);
+void disas_funcs(struct disas_context *dctx);
int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
const char *options);
@@ -39,6 +40,7 @@ static inline struct disas_context *disas_context_create(struct objtool_file *fi
static inline void disas_context_destroy(struct disas_context *dctx) {}
static inline void disas_warned_funcs(struct disas_context *dctx) {}
+static inline void disas_funcs(struct disas_context *dctx) {}
static inline int disas_info_init(struct disassemble_info *dinfo,
int arch, int mach32, int mach64,
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 17/28] objtool: Print headers for alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (15 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 16/28] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 18/28] objtool: Disassemble group alternatives Alexandre Chartre
` (12 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When using the --disas option, objtool doesn't currently disassemble
any alternative. Print an header for each alternative. This identifies
places where alternatives are present but alternative code is still
not disassembled at the moment.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 154 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 148 insertions(+), 6 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 9b5d9075ab2d3..57a13da5e1b39 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -29,6 +29,43 @@ struct disas_context {
struct disassemble_info info;
};
+/*
+ * Maximum number of alternatives
+ */
+#define DISAS_ALT_MAX 5
+
+/*
+ * Maximum number of instructions per alternative
+ */
+#define DISAS_ALT_INSN_MAX 50
+
+/*
+ * Information to disassemble an alternative
+ */
+struct disas_alt {
+ struct instruction *orig_insn; /* original instruction */
+ struct alternative *alt; /* alternative or NULL if default code */
+ char *name; /* name for this alternative */
+ int width; /* formatting width */
+};
+
+/*
+ * Wrapper around asprintf() to allocate and format a string.
+ * Return the allocated string or NULL on error.
+ */
+static char *strfmt(const char *fmt, ...)
+{
+ va_list ap;
+ char *str;
+ int rv;
+
+ va_start(ap, fmt);
+ rv = vasprintf(&str, fmt, ap);
+ va_end(ap);
+
+ return rv == -1 ? NULL : str;
+}
+
static int sprint_name(char *str, const char *name, unsigned long offset)
{
int len;
@@ -317,6 +354,9 @@ char *disas_result(struct disas_context *dctx)
#define DISAS_INSN_OFFSET_SPACE 10
#define DISAS_INSN_SPACE 60
+#define DISAS_PRINSN(dctx, insn, depth) \
+ disas_print_insn(stdout, dctx, insn, depth, "\n")
+
/*
* Print a message in the instruction flow. If insn is not NULL then
* the instruction address is printed in addition of the message,
@@ -462,21 +502,123 @@ char *disas_alt_name(struct alternative *alt)
return str;
}
+/*
+ * Initialize an alternative. The default alternative should be initialized
+ * with alt=NULL.
+ */
+static int disas_alt_init(struct disas_alt *dalt,
+ struct instruction *orig_insn,
+ struct alternative *alt,
+ int alt_num)
+{
+ dalt->orig_insn = orig_insn;
+ dalt->alt = alt;
+ dalt->name = alt ? strfmt("ALTERNATIVE %d", alt_num) :
+ strfmt("<alternative.%lx>", orig_insn->offset);
+ if (!dalt->name)
+ return -1;
+ dalt->width = strlen(dalt->name);
+
+ return 0;
+}
+
+/*
+ * Disassemble an alternative.
+ *
+ * Return the last instruction in the default alternative so that
+ * disassembly can continue with the next instruction. Return NULL
+ * on error.
+ */
+static void *disas_alt(struct disas_context *dctx,
+ struct instruction *orig_insn)
+{
+ struct disas_alt alts[DISAS_ALT_MAX] = { 0 };
+ struct alternative *alt;
+ int alt_count;
+ int alt_id;
+ int i;
+ int err;
+
+ alt_id = orig_insn->offset;
+
+ /*
+ * Initialize the default alternative.
+ */
+ err = disas_alt_init(&alts[0], orig_insn, NULL, 0);
+ if (err)
+ goto error;
+
+ /*
+ * Initialize all other alternatives.
+ */
+ i = 1;
+ for (alt = orig_insn->alts; alt; alt = alt->next) {
+ if (i >= DISAS_ALT_MAX) {
+ WARN("Alternative %lx has more alternatives than supported",
+ orig_insn->offset);
+ break;
+ }
+ err = disas_alt_init(&alts[i], orig_insn, alt, i);
+ if (err)
+ goto error;
+
+ i++;
+ }
+ alt_count = i;
+
+ /*
+ * Print an header with the name of each alternative.
+ */
+ disas_print_info(stdout, orig_insn, -2, NULL);
+ for (i = 0; i < alt_count; i++) {
+ printf("| %-*s ", alts[i].width, alts[i].name);
+ free(alts[i].name);
+ }
+ printf("\n");
+
+ /*
+ * Currently we are not disassembling any alternative but just
+ * printing alternative names. Return NULL to have disas_func()
+ * resume the disassembly with the default alternative.
+ */
+ return NULL;
+
+error:
+ WARN("Failed to disassemble alternative %x", alt_id);
+
+ for (i = 0; i < DISAS_ALT_MAX; i++)
+ free(alts[i].name);
+
+ return NULL;
+}
+
/*
* Disassemble a function.
*/
static void disas_func(struct disas_context *dctx, struct symbol *func)
{
+ struct instruction *insn_start;
struct instruction *insn;
- size_t addr;
printf("%s:\n", func->name);
sym_for_each_insn(dctx->file, func, insn) {
- addr = insn->offset;
- disas_insn(dctx, insn);
- printf(" %6lx: %s+0x%-6lx %s\n",
- addr, func->name, addr - func->offset,
- disas_result(dctx));
+ if (insn->alts) {
+ insn_start = insn;
+ insn = disas_alt(dctx, insn);
+ if (insn)
+ continue;
+ /*
+ * There was an error with disassembling
+ * the alternative. Resume disassembling
+ * at the current instruction, this will
+ * disassemble the default alternative
+ * only and continue with the code after
+ * the alternative.
+ */
+ insn = insn_start;
+ }
+
+ DISAS_PRINSN(dctx, insn, 0);
}
printf("\n");
}
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 18/28] objtool: Disassemble group alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (16 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 17/28] objtool: Print headers for alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 19/28] objtool: Print addresses with alternative instructions Alexandre Chartre
` (11 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When using the --disas option, disassemble all group alternatives.
Jump tables and exception tables (which are handled as alternatives)
are not disassembled at the moment.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 146 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 138 insertions(+), 8 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 57a13da5e1b39..321256f746425 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -47,8 +47,14 @@ struct disas_alt {
struct alternative *alt; /* alternative or NULL if default code */
char *name; /* name for this alternative */
int width; /* formatting width */
+ char *insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */
};
+#define DALT_DEFAULT(dalt) (!(dalt)->alt)
+#define DALT_INSN(dalt) (DALT_DEFAULT(dalt) ? (dalt)->orig_insn : (dalt)->alt->insn)
+#define DALT_GROUP(dalt) (DALT_INSN(dalt)->alt_group)
+#define DALT_ALTID(dalt) ((dalt)->orig_insn->offset)
+
/*
* Wrapper around asprintf() to allocate and format a string.
* Return the allocated string or NULL on error.
@@ -470,6 +476,21 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
return disasm(insn->offset, &dctx->info);
}
+static struct instruction *next_insn_same_alt(struct objtool_file *file,
+ struct alt_group *alt_grp,
+ struct instruction *insn)
+{
+ if (alt_grp->last_insn == insn || alt_grp->nop == insn)
+ return NULL;
+
+ return next_insn_same_sec(file, insn);
+}
+
+#define alt_for_each_insn(file, alt_grp, insn) \
+ for (insn = alt_grp->first_insn; \
+ insn; \
+ insn = next_insn_same_alt(file, alt_grp, insn))
+
/*
* Provide a name for an alternative.
*/
@@ -522,6 +543,83 @@ static int disas_alt_init(struct disas_alt *dalt,
return 0;
}
+static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str)
+{
+ int len;
+
+ if (index >= DISAS_ALT_INSN_MAX) {
+ WARN("Alternative %lx.%s has more instructions than supported",
+ DALT_ALTID(dalt), dalt->name);
+ return -1;
+ }
+
+ len = strlen(insn_str);
+ dalt->insn[index] = insn_str;
+ if (len > dalt->width)
+ dalt->width = len;
+
+ return 0;
+}
+
+/*
+ * Disassemble an alternative and store instructions in the disas_alt
+ * structure. Return the number of instructions in the alternative.
+ */
+static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
+{
+ struct objtool_file *file;
+ struct instruction *insn;
+ char *str;
+ int count;
+ int err;
+
+ file = dctx->file;
+ count = 0;
+
+ alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
+
+ disas_insn(dctx, insn);
+ str = strdup(disas_result(dctx));
+ if (!str)
+ return -1;
+
+ err = disas_alt_add_insn(dalt, count, str);
+ if (err)
+ break;
+ count++;
+ }
+
+ return count;
+}
+
+/*
+ * Disassemble the default alternative.
+ */
+static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt)
+{
+ char *str;
+ int err;
+
+ if (DALT_GROUP(dalt))
+ return disas_alt_group(dctx, dalt);
+
+ /*
+ * Default alternative with no alt_group: this is the default
+ * code associated with either a jump table or an exception
+ * table and no other instruction alternatives. In that case
+ * the default alternative is made of a single instruction.
+ */
+ disas_insn(dctx, dalt->orig_insn);
+ str = strdup(disas_result(dctx));
+ if (!str)
+ return -1;
+ err = disas_alt_add_insn(dalt, 0, str);
+ if (err)
+ return -1;
+
+ return 1;
+}
+
/*
* Disassemble an alternative.
*
@@ -534,22 +632,30 @@ static void *disas_alt(struct disas_context *dctx,
{
struct disas_alt alts[DISAS_ALT_MAX] = { 0 };
struct alternative *alt;
+ struct disas_alt *dalt;
+ int insn_count;
int alt_count;
int alt_id;
- int i;
+ char *str;
+ int count;
+ int i, j;
int err;
alt_id = orig_insn->offset;
/*
- * Initialize the default alternative.
+ * Initialize and disassemble the default alternative.
*/
err = disas_alt_init(&alts[0], orig_insn, NULL, 0);
if (err)
goto error;
+ insn_count = disas_alt_default(dctx, &alts[0]);
+ if (insn_count < 0)
+ goto error;
+
/*
- * Initialize all other alternatives.
+ * Initialize and disassemble all other alternatives.
*/
i = 1;
for (alt = orig_insn->alts; alt; alt = alt->next) {
@@ -558,10 +664,25 @@ static void *disas_alt(struct disas_context *dctx,
orig_insn->offset);
break;
}
- err = disas_alt_init(&alts[i], orig_insn, alt, i);
+ dalt = &alts[i];
+ err = disas_alt_init(dalt, orig_insn, alt, i);
if (err)
goto error;
+ /*
+ * Only group alternatives are supported at the moment.
+ */
+ switch (dalt->alt->type) {
+ case ALT_TYPE_INSTRUCTIONS:
+ count = disas_alt_group(dctx, dalt);
+ break;
+ default:
+ count = 0;
+ }
+ if (count < 0)
+ goto error;
+
+ insn_count = count > insn_count ? count : insn_count;
i++;
}
alt_count = i;
@@ -577,11 +698,20 @@ static void *disas_alt(struct disas_context *dctx,
printf("\n");
/*
- * Currently we are not disassembling any alternative but just
- * printing alternative names. Return NULL to have disas_func()
- * resume the disassembly with the default alternative.
+ * Print instructions for each alternative.
*/
- return NULL;
+ for (j = 0; j < insn_count; j++) {
+ disas_print_info(stdout, NULL, -2, NULL);
+ for (i = 0; i < alt_count; i++) {
+ dalt = &alts[i];
+ str = dalt->insn[j];
+ printf("| %-*s ", dalt->width, str ?: "");
+ free(str);
+ }
+ printf("\n");
+ }
+
+ return orig_insn->alt_group ? orig_insn->alt_group->last_insn : orig_insn;
error:
WARN("Failed to disassemble alternative %x", alt_id);
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 19/28] objtool: Print addresses with alternative instructions
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (17 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 18/28] objtool: Disassemble group alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 20/28] objtool: Disassemble exception table alternatives Alexandre Chartre
` (10 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
All alternatives are disassemble side-by-side when using the --disas
option. However the address of each instruction is not printed because
instructions from different alternatives are not necessarily aligned.
Change this behavior to print the address of each instruction. Spaces
will appear between instructions from the same alternative when
instructions from different alternatives do not have the same alignment.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 152 +++++++++++++++++++++++++++++++++---------
1 file changed, 121 insertions(+), 31 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 321256f746425..f4bd802e38474 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -47,7 +47,11 @@ struct disas_alt {
struct alternative *alt; /* alternative or NULL if default code */
char *name; /* name for this alternative */
int width; /* formatting width */
- char *insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */
+ struct {
+ char *str; /* instruction string */
+ int offset; /* instruction offset */
+ } insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */
+ int insn_idx; /* index of the next instruction to print */
};
#define DALT_DEFAULT(dalt) (!(dalt)->alt)
@@ -364,16 +368,14 @@ char *disas_result(struct disas_context *dctx)
disas_print_insn(stdout, dctx, insn, depth, "\n")
/*
- * Print a message in the instruction flow. If insn is not NULL then
- * the instruction address is printed in addition of the message,
- * otherwise only the message is printed. In all cases, the instruction
- * itself is not printed.
+ * Print a message in the instruction flow. If sec is not NULL then the
+ * address at the section offset is printed in addition of the message,
+ * otherwise only the message is printed.
*/
-void disas_print_info(FILE *stream, struct instruction *insn, int depth,
- const char *format, ...)
+static void disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
+ int depth, const char *format, va_list ap)
{
const char *addr_str;
- va_list args;
int len;
int i;
@@ -383,9 +385,9 @@ void disas_print_info(FILE *stream, struct instruction *insn, int depth,
depth = 0;
}
- if (insn && insn->sec) {
- addr_str = offstr(insn->sec, insn->offset);
- fprintf(stream, "%6lx: %-*s ", insn->offset, len, addr_str);
+ if (sec) {
+ addr_str = offstr(sec, offset);
+ fprintf(stream, "%6lx: %-*s ", offset, len, addr_str);
free((char *)addr_str);
} else {
len += DISAS_INSN_OFFSET_SPACE + 1;
@@ -396,11 +398,44 @@ void disas_print_info(FILE *stream, struct instruction *insn, int depth,
for (i = 0; i < depth; i++)
fprintf(stream, "| ");
- if (format) {
- va_start(args, format);
- vfprintf(stream, format, args);
- va_end(args);
+ if (format)
+ vfprintf(stream, format, ap);
+}
+
+static void disas_print(FILE *stream, struct section *sec, unsigned long offset,
+ int depth, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ disas_vprint(stream, sec, offset, depth, format, args);
+ va_end(args);
+}
+
+/*
+ * Print a message in the instruction flow. If insn is not NULL then
+ * the instruction address is printed in addition of the message,
+ * otherwise only the message is printed. In all cases, the instruction
+ * itself is not printed.
+ */
+void disas_print_info(FILE *stream, struct instruction *insn, int depth,
+ const char *format, ...)
+{
+ struct section *sec;
+ unsigned long off;
+ va_list args;
+
+ if (insn) {
+ sec = insn->sec;
+ off = insn->offset;
+ } else {
+ sec = NULL;
+ off = 0;
}
+
+ va_start(args, format);
+ disas_vprint(stream, sec, off, depth, format, args);
+ va_end(args);
}
/*
@@ -534,6 +569,7 @@ static int disas_alt_init(struct disas_alt *dalt,
{
dalt->orig_insn = orig_insn;
dalt->alt = alt;
+ dalt->insn_idx = 0;
dalt->name = alt ? strfmt("ALTERNATIVE %d", alt_num) :
strfmt("<alternative.%lx>", orig_insn->offset);
if (!dalt->name)
@@ -543,7 +579,8 @@ static int disas_alt_init(struct disas_alt *dalt,
return 0;
}
-static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str)
+static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
+ int offset)
{
int len;
@@ -554,7 +591,8 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str)
}
len = strlen(insn_str);
- dalt->insn[index] = insn_str;
+ dalt->insn[index].str = insn_str;
+ dalt->insn[index].offset = offset;
if (len > dalt->width)
dalt->width = len;
@@ -569,12 +607,14 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
{
struct objtool_file *file;
struct instruction *insn;
+ int offset;
char *str;
int count;
int err;
file = dctx->file;
count = 0;
+ offset = 0;
alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
@@ -583,9 +623,10 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
if (!str)
return -1;
- err = disas_alt_add_insn(dalt, count, str);
+ err = disas_alt_add_insn(dalt, count, str, offset);
if (err)
break;
+ offset += insn->len;
count++;
}
@@ -613,13 +654,63 @@ static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt)
str = strdup(disas_result(dctx));
if (!str)
return -1;
- err = disas_alt_add_insn(dalt, 0, str);
+ err = disas_alt_add_insn(dalt, 0, str, 0);
if (err)
return -1;
return 1;
}
+/*
+ * For each alternative, if there is an instruction at the specified
+ * offset then print this instruction, otherwise print a blank entry.
+ * The offset is an offset from the start of the alternative.
+ *
+ * Return the offset for the next instructions to print, or -1 if all
+ * instructions have been printed.
+ */
+static int disas_alt_print_insn(struct disas_alt *alts, int alt_count,
+ int insn_count, int offset)
+{
+ struct disas_alt *dalt;
+ int offset_next;
+ char *str;
+ int i, j;
+
+ offset_next = -1;
+
+ for (i = 0; i < alt_count; i++) {
+ dalt = &alts[i];
+ j = dalt->insn_idx;
+ if (j == -1) {
+ printf("| %-*s ", dalt->width, "");
+ continue;
+ }
+
+ if (dalt->insn[j].offset == offset) {
+ str = dalt->insn[j].str;
+ printf("| %-*s ", dalt->width, str ?: "");
+ free(str);
+ if (++j < insn_count) {
+ dalt->insn_idx = j;
+ } else {
+ dalt->insn_idx = -1;
+ continue;
+ }
+ } else {
+ printf("| %-*s ", dalt->width, "");
+ }
+
+ if (dalt->insn[j].offset > 0 &&
+ (offset_next == -1 ||
+ (dalt->insn[j].offset < offset_next)))
+ offset_next = dalt->insn[j].offset;
+ }
+ printf("\n");
+
+ return offset_next;
+}
+
/*
* Disassemble an alternative.
*
@@ -633,13 +724,14 @@ static void *disas_alt(struct disas_context *dctx,
struct disas_alt alts[DISAS_ALT_MAX] = { 0 };
struct alternative *alt;
struct disas_alt *dalt;
+ int offset_next;
int insn_count;
int alt_count;
int alt_id;
- char *str;
+ int offset;
int count;
- int i, j;
int err;
+ int i;
alt_id = orig_insn->offset;
@@ -700,16 +792,14 @@ static void *disas_alt(struct disas_context *dctx,
/*
* Print instructions for each alternative.
*/
- for (j = 0; j < insn_count; j++) {
- disas_print_info(stdout, NULL, -2, NULL);
- for (i = 0; i < alt_count; i++) {
- dalt = &alts[i];
- str = dalt->insn[j];
- printf("| %-*s ", dalt->width, str ?: "");
- free(str);
- }
- printf("\n");
- }
+ offset_next = 0;
+ do {
+ offset = offset_next;
+ disas_print(stdout, orig_insn->sec, orig_insn->offset + offset,
+ -2, NULL);
+ offset_next = disas_alt_print_insn(alts, alt_count, insn_count,
+ offset);
+ } while (offset_next > offset);
return orig_insn->alt_group ? orig_insn->alt_group->last_insn : orig_insn;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 20/28] objtool: Disassemble exception table alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (18 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 19/28] objtool: Print addresses with alternative instructions Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 21/28] objtool: Disassemble jump " Alexandre Chartre
` (9 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When using the --disas option, also disable exception tables (EX_TABLE)
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 45 +++++++++++++++++++++++++++++++++++++++----
1 file changed, 41 insertions(+), 4 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index f4bd802e38474..670fb8c646282 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -567,13 +567,26 @@ static int disas_alt_init(struct disas_alt *dalt,
struct alternative *alt,
int alt_num)
{
+ char *str;
+
dalt->orig_insn = orig_insn;
dalt->alt = alt;
dalt->insn_idx = 0;
- dalt->name = alt ? strfmt("ALTERNATIVE %d", alt_num) :
- strfmt("<alternative.%lx>", orig_insn->offset);
- if (!dalt->name)
+ if (!alt) {
+ str = strfmt("<alternative.%lx>", orig_insn->offset);
+ } else {
+ switch (alt->type) {
+ case ALT_TYPE_EX_TABLE:
+ str = strdup("EXCEPTION");
+ break;
+ default:
+ str = strfmt("ALTERNATIVE %d", alt_num);
+ break;
+ }
+ }
+ if (!str)
return -1;
+ dalt->name = str;
dalt->width = strlen(dalt->name);
return 0;
@@ -599,6 +612,26 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
return 0;
}
+/*
+ * Disassemble an exception table alternative.
+ */
+static int disas_alt_extable(struct disas_alt *dalt)
+{
+ struct instruction *alt_insn;
+ char *str;
+
+ alt_insn = dalt->alt->insn;
+ str = strfmt("resume at 0x%lx <%s+0x%lx>",
+ alt_insn->offset, alt_insn->sym->name,
+ alt_insn->offset - alt_insn->sym->offset);
+ if (!str)
+ return -1;
+
+ disas_alt_add_insn(dalt, 0, str, 0);
+
+ return 1;
+}
+
/*
* Disassemble an alternative and store instructions in the disas_alt
* structure. Return the number of instructions in the alternative.
@@ -762,12 +795,16 @@ static void *disas_alt(struct disas_context *dctx,
goto error;
/*
- * Only group alternatives are supported at the moment.
+ * Only group alternatives and exception tables are
+ * supported at the moment.
*/
switch (dalt->alt->type) {
case ALT_TYPE_INSTRUCTIONS:
count = disas_alt_group(dctx, dalt);
break;
+ case ALT_TYPE_EX_TABLE:
+ count = disas_alt_extable(dalt);
+ break;
default:
count = 0;
}
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 21/28] objtool: Disassemble jump table alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (19 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 20/28] objtool: Disassemble exception table alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 22/28] objtool: Fix address references in alternatives Alexandre Chartre
` (8 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When using the --disas option, also disable jump tables.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 41 +++++++++++++++++++++++++++++++++++------
1 file changed, 35 insertions(+), 6 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 670fb8c646282..6ec241806f5c4 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -579,6 +579,9 @@ static int disas_alt_init(struct disas_alt *dalt,
case ALT_TYPE_EX_TABLE:
str = strdup("EXCEPTION");
break;
+ case ALT_TYPE_JUMP_TABLE:
+ str = strdup("JUMP");
+ break;
default:
str = strfmt("ALTERNATIVE %d", alt_num);
break;
@@ -612,6 +615,34 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
return 0;
}
+static int disas_alt_jump(struct disas_alt *dalt)
+{
+ struct instruction *orig_insn;
+ struct instruction *dest_insn;
+ char suffix[2] = { 0 };
+ char *str;
+
+ orig_insn = dalt->orig_insn;
+ dest_insn = dalt->alt->insn;
+
+ if (orig_insn->type == INSN_NOP) {
+ if (orig_insn->len == 5)
+ suffix[0] = 'q';
+ str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix,
+ dest_insn->offset, dest_insn->sym->name,
+ dest_insn->offset - dest_insn->sym->offset);
+ } else {
+ str = strfmt("NOP%d", orig_insn->len);
+ }
+
+ if (!str)
+ return -1;
+
+ disas_alt_add_insn(dalt, 0, str, 0);
+
+ return 1;
+}
+
/*
* Disassemble an exception table alternative.
*/
@@ -794,10 +825,7 @@ static void *disas_alt(struct disas_context *dctx,
if (err)
goto error;
- /*
- * Only group alternatives and exception tables are
- * supported at the moment.
- */
+ count = -1;
switch (dalt->alt->type) {
case ALT_TYPE_INSTRUCTIONS:
count = disas_alt_group(dctx, dalt);
@@ -805,8 +833,9 @@ static void *disas_alt(struct disas_context *dctx,
case ALT_TYPE_EX_TABLE:
count = disas_alt_extable(dalt);
break;
- default:
- count = 0;
+ case ALT_TYPE_JUMP_TABLE:
+ count = disas_alt_jump(dalt);
+ break;
}
if (count < 0)
goto error;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 22/28] objtool: Fix address references in alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (20 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 21/28] objtool: Disassemble jump " Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 23/28] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
` (7 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When using the --disas option, alternatives are disassembled but
address references in non-default alternatives can be incorrect.
The problem is that alternatives are shown as if they were replacing the
original code of the alternative. So if an alternative is referencing
an address inside the alternative then the reference has to be
adjusted to the location of the original code.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 70 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 66 insertions(+), 4 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 6ec241806f5c4..e506fdda4d897 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -24,6 +24,7 @@
struct disas_context {
struct objtool_file *file;
struct instruction *insn;
+ bool alt_applied;
char result[DISAS_RESULT_SIZE];
disassembler_ftype disassembler;
struct disassemble_info info;
@@ -160,6 +161,43 @@ static void disas_print_addr_sym(struct section *sec, struct symbol *sym,
}
}
+static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo)
+{
+ struct disas_context *dctx = dinfo->application_data;
+ struct instruction *orig_first_insn;
+ struct alt_group *alt_group;
+ unsigned long offset;
+ struct symbol *sym;
+
+ /*
+ * Check if we are processing an alternative at the original
+ * instruction address (i.e. if alt_applied is true) and if
+ * we are referencing an address inside the alternative.
+ *
+ * For example, this happens if there is a branch inside an
+ * alternative. In that case, the address should be updated
+ * to a reference inside the original instruction flow.
+ */
+ if (!dctx->alt_applied)
+ return false;
+
+ alt_group = dctx->insn->alt_group;
+ if (!alt_group || !alt_group->orig_group ||
+ addr < alt_group->first_insn->offset ||
+ addr > alt_group->last_insn->offset)
+ return false;
+
+ orig_first_insn = alt_group->orig_group->first_insn;
+ offset = addr - alt_group->first_insn->offset;
+
+ addr = orig_first_insn->offset + offset;
+ sym = orig_first_insn->sym;
+
+ disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo);
+
+ return true;
+}
+
static void disas_print_addr_noreloc(bfd_vma addr,
struct disassemble_info *dinfo)
{
@@ -167,6 +205,9 @@ static void disas_print_addr_noreloc(bfd_vma addr,
struct instruction *insn = dctx->insn;
struct symbol *sym = NULL;
+ if (disas_print_addr_alt(addr, dinfo))
+ return;
+
if (insn->sym && addr >= insn->sym->offset &&
addr < insn->sym->offset + insn->sym->len) {
sym = insn->sym;
@@ -235,8 +276,9 @@ static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo)
*/
jump_dest = insn->jump_dest;
if (jump_dest && jump_dest->sym && jump_dest->offset == addr) {
- disas_print_addr_sym(jump_dest->sec, jump_dest->sym,
- addr, dinfo);
+ if (!disas_print_addr_alt(addr, dinfo))
+ disas_print_addr_sym(jump_dest->sec, jump_dest->sym,
+ addr, dinfo);
return;
}
@@ -486,13 +528,22 @@ void disas_print_insn(FILE *stream, struct disas_context *dctx,
/*
* Disassemble a single instruction. Return the size of the instruction.
+ *
+ * If alt_applied is true then insn should be an instruction from of an
+ * alternative (i.e. insn->alt_group != NULL), and it is disassembled
+ * at the location of the original code it is replacing. When the
+ * instruction references any address inside the alternative then
+ * these references will be re-adjusted to replace the original code.
*/
-size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
+static size_t disas_insn_common(struct disas_context *dctx,
+ struct instruction *insn,
+ bool alt_applied)
{
disassembler_ftype disasm = dctx->disassembler;
struct disassemble_info *dinfo = &dctx->info;
dctx->insn = insn;
+ dctx->alt_applied = alt_applied;
dctx->result[0] = '\0';
if (insn->type == INSN_NOP) {
@@ -511,6 +562,17 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
return disasm(insn->offset, &dctx->info);
}
+size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
+{
+ return disas_insn_common(dctx, insn, false);
+}
+
+static size_t disas_insn_alt(struct disas_context *dctx,
+ struct instruction *insn)
+{
+ return disas_insn_common(dctx, insn, true);
+}
+
static struct instruction *next_insn_same_alt(struct objtool_file *file,
struct alt_group *alt_grp,
struct instruction *insn)
@@ -682,7 +744,7 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
- disas_insn(dctx, insn);
+ disas_insn_alt(dctx, insn);
str = strdup(disas_result(dctx));
if (!str)
return -1;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 23/28] objtool: Provide access to feature and flags of group alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (21 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 22/28] objtool: Fix address references in alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 24/28] objtool: Function to get the name of a CPU feature Alexandre Chartre
` (6 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Each alternative of a group alternative depends on a specific
feature and flags. Provide access to the feature/flags for each
alternative as an attribute (feature) in struct alt_group.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 2 ++
tools/objtool/include/objtool/check.h | 1 +
tools/objtool/include/objtool/special.h | 2 +-
tools/objtool/special.c | 2 ++
4 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 25c1c7185d4f6..817ac70fed9fa 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1738,6 +1738,7 @@ static int handle_group_alt(struct objtool_file *file,
orig_alt_group->last_insn = last_orig_insn;
orig_alt_group->nop = NULL;
orig_alt_group->ignore = orig_insn->ignore_alts;
+ orig_alt_group->feature = 0;
} else {
if (orig_alt_group->last_insn->offset + orig_alt_group->last_insn->len -
orig_alt_group->first_insn->offset != special_alt->orig_len) {
@@ -1841,6 +1842,7 @@ static int handle_group_alt(struct objtool_file *file,
new_alt_group->nop = nop;
new_alt_group->ignore = (*new_insn)->ignore_alts;
new_alt_group->cfi = orig_alt_group->cfi;
+ new_alt_group->feature = special_alt->feature;
return 0;
}
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index 73310c56e91b6..c54dd0aae1f60 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -36,6 +36,7 @@ struct alt_group {
struct cfi_state **cfi;
bool ignore;
+ unsigned int feature;
};
enum alternative_type {
diff --git a/tools/objtool/include/objtool/special.h b/tools/objtool/include/objtool/special.h
index 72d09c0adf1a1..b22410745e4a1 100644
--- a/tools/objtool/include/objtool/special.h
+++ b/tools/objtool/include/objtool/special.h
@@ -25,7 +25,7 @@ struct special_alt {
struct section *new_sec;
unsigned long new_off;
- unsigned int orig_len, new_len; /* group only */
+ unsigned int orig_len, new_len, feature; /* group only */
};
int special_get_alts(struct elf *elf, struct list_head *alts);
diff --git a/tools/objtool/special.c b/tools/objtool/special.c
index c80fed8a840ee..93fb371b05207 100644
--- a/tools/objtool/special.c
+++ b/tools/objtool/special.c
@@ -82,6 +82,8 @@ static int get_alt_entry(struct elf *elf, const struct special_entry *entry,
entry->orig_len);
alt->new_len = *(unsigned char *)(sec->data->d_buf + offset +
entry->new_len);
+ alt->feature = *(unsigned int *)(sec->data->d_buf + offset +
+ entry->feature);
}
orig_reloc = find_reloc_by_dest(elf, sec, offset + entry->orig);
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 24/28] objtool: Function to get the name of a CPU feature
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (22 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 23/28] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 25/28] objtool: Improve naming of group alternatives Alexandre Chartre
` (5 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Add a function to get the name of a CPU feature. The function is
architecture dependent and currently only implemented for x86. The
feature names are automatically generated from the cpufeatures.h
include file.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
.../x86/tools/gen-cpu-feature-names-x86.awk | 33 +++++++++++++++++++
tools/objtool/Makefile | 1 +
tools/objtool/arch/loongarch/special.c | 5 +++
tools/objtool/arch/powerpc/special.c | 5 +++
tools/objtool/arch/x86/Build | 8 +++++
tools/objtool/arch/x86/special.c | 10 ++++++
tools/objtool/include/objtool/special.h | 2 ++
7 files changed, 64 insertions(+)
create mode 100644 tools/arch/x86/tools/gen-cpu-feature-names-x86.awk
diff --git a/tools/arch/x86/tools/gen-cpu-feature-names-x86.awk b/tools/arch/x86/tools/gen-cpu-feature-names-x86.awk
new file mode 100644
index 0000000000000..1b1c1d84225c2
--- /dev/null
+++ b/tools/arch/x86/tools/gen-cpu-feature-names-x86.awk
@@ -0,0 +1,33 @@
+#!/bin/awk -f
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025, Oracle and/or its affiliates.
+#
+# Usage: awk -f gen-cpu-feature-names-x86.awk cpufeatures.h > cpu-feature-names.c
+#
+
+BEGIN {
+ print "/* cpu feature name array generated from cpufeatures.h */"
+ print "/* Do not change this code. */"
+ print
+ print "static const char *cpu_feature_names[(NCAPINTS+NBUGINTS)*32] = {"
+
+ feature_expr = "(X86_FEATURE_[A-Z0-9_]+)\\s+\\(([0-9*+ ]+)\\)"
+ debug_expr = "(X86_BUG_[A-Z0-9_]+)\\s+X86_BUG\\(([0-9*+ ]+)\\)"
+}
+
+/^#define X86_FEATURE_/ {
+ if (match($0, feature_expr, m)) {
+ print "\t[" m[2] "] = \"" m[1] "\","
+ }
+}
+
+/^#define X86_BUG_/ {
+ if (match($0, debug_expr, m)) {
+ print "\t[NCAPINTS*32+(" m[2] ")] = \"" m[1] "\","
+ }
+}
+
+END {
+ print "};"
+}
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 9fb83979ca890..b6df180121589 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -109,6 +109,7 @@ clean: $(LIBSUBCMD)-clean
$(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL)
$(Q)find $(OUTPUT) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete
$(Q)$(RM) $(OUTPUT)arch/x86/lib/inat-tables.c $(OUTPUT)fixdep
+ $(Q)$(RM) $(OUTPUT)arch/x86/lib/cpu-feature-names.c $(OUTPUT)fixdep
FORCE:
diff --git a/tools/objtool/arch/loongarch/special.c b/tools/objtool/arch/loongarch/special.c
index a80b75f7b061f..aba774109437f 100644
--- a/tools/objtool/arch/loongarch/special.c
+++ b/tools/objtool/arch/loongarch/special.c
@@ -194,3 +194,8 @@ struct reloc *arch_find_switch_table(struct objtool_file *file,
return rodata_reloc;
}
+
+const char *arch_cpu_feature_name(int feature_number)
+{
+ return NULL;
+}
diff --git a/tools/objtool/arch/powerpc/special.c b/tools/objtool/arch/powerpc/special.c
index 51610689abf72..8f9bf61ca0899 100644
--- a/tools/objtool/arch/powerpc/special.c
+++ b/tools/objtool/arch/powerpc/special.c
@@ -18,3 +18,8 @@ struct reloc *arch_find_switch_table(struct objtool_file *file,
{
exit(-1);
}
+
+const char *arch_cpu_feature_name(int feature_number)
+{
+ return NULL;
+}
diff --git a/tools/objtool/arch/x86/Build b/tools/objtool/arch/x86/Build
index 3dedb2fd8f3a0..1067355361b56 100644
--- a/tools/objtool/arch/x86/Build
+++ b/tools/objtool/arch/x86/Build
@@ -11,4 +11,12 @@ $(OUTPUT)arch/x86/lib/inat-tables.c: $(inat_tables_script) $(inat_tables_maps)
$(OUTPUT)arch/x86/decode.o: $(OUTPUT)arch/x86/lib/inat-tables.c
+cpu_features = ../arch/x86/include/asm/cpufeatures.h
+cpu_features_script = ../arch/x86/tools/gen-cpu-feature-names-x86.awk
+
+$(OUTPUT)arch/x86/lib/cpu-feature-names.c: $(cpu_features_script) $(cpu_features)
+ $(Q)$(call echo-cmd,gen)$(AWK) -f $(cpu_features_script) $(cpu_features) > $@
+
+$(OUTPUT)arch/x86/special.o: $(OUTPUT)arch/x86/lib/cpu-feature-names.c
+
CFLAGS_decode.o += -I$(OUTPUT)arch/x86/lib
diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c
index 06ca4a2659a45..720127fd4d025 100644
--- a/tools/objtool/arch/x86/special.c
+++ b/tools/objtool/arch/x86/special.c
@@ -4,6 +4,10 @@
#include <objtool/special.h>
#include <objtool/builtin.h>
#include <objtool/warn.h>
+#include <asm/cpufeatures.h>
+
+/* cpu feature name array generated from cpufeatures.h */
+#include "lib/cpu-feature-names.c"
void arch_handle_alternative(struct special_alt *alt)
{
@@ -134,3 +138,9 @@ struct reloc *arch_find_switch_table(struct objtool_file *file,
*table_size = 0;
return rodata_reloc;
}
+
+const char *arch_cpu_feature_name(int feature_number)
+{
+ return (feature_number < ARRAY_SIZE(cpu_feature_names)) ?
+ cpu_feature_names[feature_number] : NULL;
+}
diff --git a/tools/objtool/include/objtool/special.h b/tools/objtool/include/objtool/special.h
index b22410745e4a1..121c3761899c1 100644
--- a/tools/objtool/include/objtool/special.h
+++ b/tools/objtool/include/objtool/special.h
@@ -38,4 +38,6 @@ bool arch_support_alt_relocation(struct special_alt *special_alt,
struct reloc *arch_find_switch_table(struct objtool_file *file,
struct instruction *insn,
unsigned long *table_size);
+const char *arch_cpu_feature_name(int feature_number);
+
#endif /* _SPECIAL_H */
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 25/28] objtool: Improve naming of group alternatives
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (23 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 24/28] objtool: Function to get the name of a CPU feature Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 26/28] objtool: Get the destination name of a PV call Alexandre Chartre
` (4 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Improve the naming of group alternatives by showing the feature name and
flags used by the alternative.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/check.c | 1 -
tools/objtool/disas.c | 86 ++++++++++++++++++++++++++++---------------
2 files changed, 57 insertions(+), 30 deletions(-)
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 817ac70fed9fa..43593766e80e0 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3661,7 +3661,6 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
return ret;
}
}
-
TRACE_ALT_INFO_NOADDR(insn, "/ ", "DEFAULT");
}
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index e506fdda4d897..2a6c3708ec315 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -9,6 +9,7 @@
#include <objtool/arch.h>
#include <objtool/check.h>
#include <objtool/disas.h>
+#include <objtool/special.h>
#include <objtool/warn.h>
#include <bfd.h>
@@ -60,6 +61,21 @@ struct disas_alt {
#define DALT_GROUP(dalt) (DALT_INSN(dalt)->alt_group)
#define DALT_ALTID(dalt) ((dalt)->orig_insn->offset)
+#define ALT_FLAGS_SHIFT 16
+#define ALT_FLAG_NOT (1 << 0)
+#define ALT_FLAG_DIRECT_CALL (1 << 1)
+#define ALT_FEATURE_MASK ((1 << ALT_FLAGS_SHIFT) - 1)
+
+static int alt_feature(unsigned ft_flags)
+{
+ return (ft_flags & ALT_FEATURE_MASK);
+}
+
+static int alt_flags(unsigned ft_flags)
+{
+ return (ft_flags >> ALT_FLAGS_SHIFT);
+}
+
/*
* Wrapper around asprintf() to allocate and format a string.
* Return the allocated string or NULL on error.
@@ -593,7 +609,12 @@ static struct instruction *next_insn_same_alt(struct objtool_file *file,
*/
char *disas_alt_name(struct alternative *alt)
{
+ char pfx[4] = { 0 };
char *str = NULL;
+ const char *name;
+ int feature;
+ int flags;
+ int num;
switch (alt->type) {
@@ -607,13 +628,37 @@ char *disas_alt_name(struct alternative *alt)
case ALT_TYPE_INSTRUCTIONS:
/*
- * This is a non-default group alternative. Create a unique
- * name using the offset of the first original and alternative
- * instructions.
+ * This is a non-default group alternative. Create a name
+ * based on the feature and flags associated with this
+ * alternative. Use either the feature name (it is available)
+ * or the feature number. And add a prefix to show the flags
+ * used.
+ *
+ * Prefix flags characters:
+ *
+ * '!' alternative used when feature not enabled
+ * '+' direct call alternative
+ * '?' unknown flag
*/
- asprintf(&str, "ALTERNATIVE %lx.%lx",
- alt->insn->alt_group->orig_group->first_insn->offset,
- alt->insn->alt_group->first_insn->offset);
+
+ feature = alt->insn->alt_group->feature;
+ num = alt_feature(feature);
+ flags = alt_flags(feature);
+ str = pfx;
+
+ if (flags & ~(ALT_FLAG_NOT | ALT_FLAG_DIRECT_CALL))
+ *str++ = '?';
+ if (flags & ALT_FLAG_DIRECT_CALL)
+ *str++ = '+';
+ if (flags & ALT_FLAG_NOT)
+ *str++ = '!';
+
+ name = arch_cpu_feature_name(num);
+ if (!name)
+ str = strfmt("%sFEATURE 0x%X", pfx, num);
+ else
+ str = strfmt("%s%s", pfx, name);
+
break;
}
@@ -626,32 +671,15 @@ char *disas_alt_name(struct alternative *alt)
*/
static int disas_alt_init(struct disas_alt *dalt,
struct instruction *orig_insn,
- struct alternative *alt,
- int alt_num)
+ struct alternative *alt)
{
- char *str;
-
dalt->orig_insn = orig_insn;
dalt->alt = alt;
dalt->insn_idx = 0;
- if (!alt) {
- str = strfmt("<alternative.%lx>", orig_insn->offset);
- } else {
- switch (alt->type) {
- case ALT_TYPE_EX_TABLE:
- str = strdup("EXCEPTION");
- break;
- case ALT_TYPE_JUMP_TABLE:
- str = strdup("JUMP");
- break;
- default:
- str = strfmt("ALTERNATIVE %d", alt_num);
- break;
- }
- }
- if (!str)
+ dalt->name = alt ? disas_alt_name(alt) :
+ strfmt("<alternative.%lx>", orig_insn->offset);
+ if (!dalt->name)
return -1;
- dalt->name = str;
dalt->width = strlen(dalt->name);
return 0;
@@ -864,7 +892,7 @@ static void *disas_alt(struct disas_context *dctx,
/*
* Initialize and disassemble the default alternative.
*/
- err = disas_alt_init(&alts[0], orig_insn, NULL, 0);
+ err = disas_alt_init(&alts[0], orig_insn, NULL);
if (err)
goto error;
@@ -883,7 +911,7 @@ static void *disas_alt(struct disas_context *dctx,
break;
}
dalt = &alts[i];
- err = disas_alt_init(dalt, orig_insn, alt, i);
+ err = disas_alt_init(dalt, orig_insn, alt);
if (err)
goto error;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 26/28] objtool: Get the destination name of a PV call
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (24 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 25/28] objtool: Improve naming of group alternatives Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call Alexandre Chartre
` (3 subsequent siblings)
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
Add a function to get the destination name of a PV call. The destination
depends on the content of the pv_ops[] array which can dynamically change
at runtime.
However there are cases where we can speculate on the content of pv_ops[]
and provide the function name corresponding to the call. For example,
when an alternative depends on the X86_FEATURE_XENPV feature then we know
that the corresponding code will be using the Xen pv_ops[] values.
If we can't figure out the exact content of pv_ops[] then provide the
function name from the default pv_ops[] array.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/arch/x86/decode.c | 2 +-
tools/objtool/check.c | 99 ++++++++++++++++++++++---
tools/objtool/include/objtool/check.h | 4 +
tools/objtool/include/objtool/elf.h | 7 ++
tools/objtool/include/objtool/objtool.h | 6 +-
tools/objtool/objtool.c | 27 ++++++-
6 files changed, 129 insertions(+), 16 deletions(-)
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index d651d8921ab47..9fef0d94517ca 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -685,7 +685,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
return -1;
}
- objtool_pv_add(file, idx, func);
+ objtool_pv_add(file, idx, func, PV_MODE_DEFAULT);
}
break;
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 43593766e80e0..73caebbf8b6de 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -538,7 +538,8 @@ static int decode_instructions(struct objtool_file *file)
/*
* Read the pv_ops[] .data table to find the static initialized values.
*/
-static int add_pv_ops(struct objtool_file *file, const char *symname)
+static int add_pv_ops(struct objtool_file *file, const char *symname,
+ enum pv_mode pv_mode)
{
struct symbol *sym, *func;
unsigned long off, end;
@@ -568,7 +569,7 @@ static int add_pv_ops(struct objtool_file *file, const char *symname)
return -1;
}
- if (objtool_pv_add(file, idx, func))
+ if (objtool_pv_add(file, idx, func, pv_mode))
return -1;
off = reloc_offset(reloc) + 1;
@@ -584,24 +585,27 @@ static int add_pv_ops(struct objtool_file *file, const char *symname)
*/
static int init_pv_ops(struct objtool_file *file)
{
- static const char *pv_ops_tables[] = {
- "pv_ops",
- "xen_cpu_ops",
- "xen_irq_ops",
- "xen_mmu_ops",
- NULL,
+ static struct {
+ const char *name;
+ enum pv_mode mode;
+ } pv_ops_tables[] = {
+ { "pv_ops", PV_MODE_DEFAULT },
+ { "xen_cpu_ops", PV_MODE_XENPV },
+ { "xen_irq_ops", PV_MODE_XENPV },
+ { "xen_mmu_ops", PV_MODE_XENPV },
+ { NULL },
};
const char *pv_ops;
struct symbol *sym;
int idx, nr, ret;
- if (!opts.noinstr)
+ if (!opts.noinstr && !opts.disas)
return 0;
file->pv_ops = NULL;
sym = find_symbol_by_name(file->elf, "pv_ops");
- if (!sym)
+ if (!sym || !sym->len)
return 0;
nr = sym->len / sizeof(unsigned long);
@@ -614,8 +618,8 @@ static int init_pv_ops(struct objtool_file *file)
for (idx = 0; idx < nr; idx++)
INIT_LIST_HEAD(&file->pv_ops[idx].targets);
- for (idx = 0; (pv_ops = pv_ops_tables[idx]); idx++) {
- ret = add_pv_ops(file, pv_ops);
+ for (idx = 0; (pv_ops = pv_ops_tables[idx].name); idx++) {
+ ret = add_pv_ops(file, pv_ops, pv_ops_tables[idx].mode);
if (ret)
return ret;
}
@@ -3379,6 +3383,77 @@ static bool pv_call_dest(struct objtool_file *file, struct instruction *insn)
return file->pv_ops[idx].clean;
}
+/*
+ * Return the name of the destination of a PV call.
+ *
+ * The destination depends on the specified pv_mode. If an exact
+ * destination cannot be found then the name shows the position of
+ * the destination in the pv_ops[] array, and it is followed by
+ * the operation name for the default PV mode. For example:
+ * "pv_ops[61] ~ native_set_pte"
+ *
+ * The destination name can be followed by a '*' character if there
+ * is code that can override the pv_ops[] entry.
+ *
+ * The function returns NULL if there is no call and the operation
+ * is a NOP.
+ */
+const char *pv_call_dest_name(struct objtool_file *file,
+ struct instruction *insn,
+ enum pv_mode pv_mode)
+{
+ struct symbol *target_default = NULL;
+ struct symbol *target = NULL;
+ static char pvname[64];
+ const char *note = "";
+ struct reloc *reloc;
+ int idx;
+
+ reloc = insn_reloc(file, insn);
+ if (!reloc || strcmp(reloc->sym->name, "pv_ops"))
+ return NULL;
+
+ idx = (arch_dest_reloc_offset(reloc_addend(reloc)) / sizeof(void *));
+
+ if (file->pv_ops) {
+
+ target_default = file->pv_ops[idx].target_default;
+
+ switch (pv_mode) {
+
+ case PV_MODE_DEFAULT:
+ target = target_default;
+ break;
+
+ case PV_MODE_XENPV:
+ target = file->pv_ops[idx].target_xen;
+ break;
+
+ case PV_MODE_UNKNOWN:
+ break;
+ }
+
+ if (file->pv_ops[idx].target_override > 0)
+ note = " *";
+ }
+
+ if (target) {
+ if (!strcmp(target->name, "nop_func"))
+ return NULL;
+
+ snprintf(pvname, sizeof(pvname), "%s%s", target->name, note);
+
+ } else if (target_default) {
+ snprintf(pvname, sizeof(pvname), "pv_ops[%d] ~ %s%s",
+ idx, target_default->name, note);
+ } else {
+ snprintf(pvname, sizeof(pvname), "pv_ops[%d]", idx);
+ }
+
+ return pvname;
+}
+
+
static inline bool noinstr_call_dest(struct objtool_file *file,
struct instruction *insn,
struct symbol *func)
diff --git a/tools/objtool/include/objtool/check.h b/tools/objtool/include/objtool/check.h
index c54dd0aae1f60..e352ed64f9edd 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -138,6 +138,10 @@ static inline struct symbol *insn_call_dest(struct instruction *insn)
return insn->_call_dest;
}
+const char *pv_call_dest_name(struct objtool_file *file,
+ struct instruction *insn,
+ enum pv_mode pv_mode);
+
struct instruction *find_insn(struct objtool_file *file,
struct section *sec, unsigned long offset);
diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h
index df8434d3b7440..1d55f5da16932 100644
--- a/tools/objtool/include/objtool/elf.h
+++ b/tools/objtool/include/objtool/elf.h
@@ -46,6 +46,12 @@ struct section {
struct reloc *relocs;
};
+enum pv_mode {
+ PV_MODE_UNKNOWN,
+ PV_MODE_DEFAULT,
+ PV_MODE_XENPV,
+};
+
struct symbol {
struct list_head list;
struct rb_node node;
@@ -72,6 +78,7 @@ struct symbol {
u8 ignore : 1;
u8 nocfi : 1;
struct list_head pv_target;
+ enum pv_mode pv_mode;
struct reloc *relocs;
struct section *group_sec;
};
diff --git a/tools/objtool/include/objtool/objtool.h b/tools/objtool/include/objtool/objtool.h
index c0dc86a78ff65..cb25bf502f2b2 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -17,6 +17,9 @@
struct pv_state {
bool clean;
struct list_head targets;
+ struct symbol *target_default;
+ struct symbol *target_xen;
+ int target_override;
};
struct objtool_file {
@@ -41,7 +44,8 @@ struct objtool_file {
struct objtool_file *objtool_open_read(const char *_objname);
-int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func);
+int objtool_pv_add(struct objtool_file *file, int idx, struct symbol *func,
+ enum pv_mode pv_mode);
int check(struct objtool_file *file);
int orc_dump(const char *objname);
diff --git a/tools/objtool/objtool.c b/tools/objtool/objtool.c
index 5c8b974ad0f9d..95dfef07a530f 100644
--- a/tools/objtool/objtool.c
+++ b/tools/objtool/objtool.c
@@ -44,9 +44,10 @@ struct objtool_file *objtool_open_read(const char *filename)
return &file;
}
-int objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func)
+int objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func,
+ enum pv_mode pv_mode)
{
- if (!opts.noinstr)
+ if (!opts.noinstr && !opts.disas)
return 0;
if (!f->pv_ops) {
@@ -54,6 +55,28 @@ int objtool_pv_add(struct objtool_file *f, int idx, struct symbol *func)
return -1;
}
+ if (opts.disas) {
+ switch (pv_mode) {
+
+ case PV_MODE_DEFAULT:
+ if (f->pv_ops[idx].target_default)
+ f->pv_ops[idx].target_override++;
+ else
+ f->pv_ops[idx].target_default = func;
+ break;
+
+ case PV_MODE_XENPV:
+ f->pv_ops[idx].target_xen = func;
+ break;
+
+ default:
+ BUG();
+ }
+ }
+
+ if (!opts.noinstr)
+ return 0;
+
/*
* These functions will be patched into native code,
* see paravirt_patch().
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (25 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 26/28] objtool: Get the destination name of a PV call Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-15 1:34 ` kernel test robot
2025-11-13 16:49 ` [PATCH v4 28/28] objtool: Print single line for alternatives with one instruction Alexandre Chartre
` (2 subsequent siblings)
29 siblings, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When using the --disas option, print the destination name of pv_ops
calls when we can figure out if XENPV mode is used or not. If the
PV mode can't be predicted when print the default pv_ops destination
as a destination example.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 101 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 89 insertions(+), 12 deletions(-)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 2a6c3708ec315..20d64b58182ce 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -12,6 +12,7 @@
#include <objtool/special.h>
#include <objtool/warn.h>
+#include <asm/cpufeatures.h>
#include <bfd.h>
#include <linux/string.h>
#include <tools/dis-asm-compat.h>
@@ -54,6 +55,7 @@ struct disas_alt {
int offset; /* instruction offset */
} insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */
int insn_idx; /* index of the next instruction to print */
+ int pv_mode; /* PV mode */
};
#define DALT_DEFAULT(dalt) (!(dalt)->alt)
@@ -665,13 +667,65 @@ char *disas_alt_name(struct alternative *alt)
return str;
}
+/*
+ * Set the PV mode for the current alternative and return the PV mode
+ * to use for next alternatives.
+ */
+static enum pv_mode disas_alt_set_pvmode(struct disas_alt *dalt,
+ enum pv_mode pv_mode)
+{
+ struct alt_group *alt_group;
+ int feature;
+ int flags;
+
+ dalt->pv_mode = pv_mode;
+
+ alt_group = DALT_GROUP(dalt);
+ if (!alt_group)
+ return pv_mode;
+
+ feature = alt_feature(alt_group->feature);
+ flags = alt_flags(alt_group->feature);
+
+ /*
+ * The only PV mode we identify is the XENPV mode which is
+ * enabled with the X86_FEATURE_XENPV feature. When we are
+ * sure that XENPV mode is not used then assume that the
+ * default PV mode is used.
+ */
+ if (feature != X86_FEATURE_XENPV)
+ return pv_mode;
+
+ if (flags & ALT_FLAG_NOT) {
+ /*
+ * This alternative is not used with XENPV mode, so
+ * it is used in default mode. Then next alternatives
+ * will be used in XENPV mode.
+ */
+ dalt->pv_mode = PV_MODE_DEFAULT;
+ pv_mode = PV_MODE_XENPV;
+ } else {
+ /*
+ * This alternative is used with XENPV mode so next
+ * alternatives will apply in default mode.
+ */
+ dalt->pv_mode = PV_MODE_XENPV;
+ pv_mode = PV_MODE_DEFAULT;
+ }
+
+ return pv_mode;
+}
+
/*
* Initialize an alternative. The default alternative should be initialized
* with alt=NULL.
+ *
+ * Return the PV mode to use for the next alternative or -1 on error.
*/
-static int disas_alt_init(struct disas_alt *dalt,
- struct instruction *orig_insn,
- struct alternative *alt)
+static enum pv_mode disas_alt_init(struct disas_alt *dalt,
+ struct instruction *orig_insn,
+ struct alternative *alt,
+ enum pv_mode pv_mode)
{
dalt->orig_insn = orig_insn;
dalt->alt = alt;
@@ -682,7 +736,7 @@ static int disas_alt_init(struct disas_alt *dalt,
return -1;
dalt->width = strlen(dalt->name);
- return 0;
+ return disas_alt_set_pvmode(dalt, pv_mode);
}
static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
@@ -753,27 +807,50 @@ static int disas_alt_extable(struct disas_alt *dalt)
return 1;
}
+static bool disas_alt_is_direct_call(struct instruction *insn)
+{
+ return (insn->alt_group &&
+ (alt_flags(insn->alt_group->feature) & ALT_FLAG_DIRECT_CALL));
+}
+
/*
* Disassemble an alternative and store instructions in the disas_alt
* structure. Return the number of instructions in the alternative.
*/
static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
{
+ struct instruction *orig_insn;
struct objtool_file *file;
struct instruction *insn;
+ const char *name;
int offset;
char *str;
int count;
int err;
file = dctx->file;
+ orig_insn = dalt->orig_insn;
count = 0;
offset = 0;
alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
-
- disas_insn_alt(dctx, insn);
- str = strdup(disas_result(dctx));
+ /*
+ * An alternative direct call initially has the
+ * "call BUG_func" instruction but it will be
+ * replaced with a direct call to the target of
+ * the pv_ops call in the original instruction.
+ */
+ if (disas_alt_is_direct_call(insn)) {
+ name = pv_call_dest_name(file, orig_insn,
+ dalt->pv_mode);
+ if (name)
+ str = strfmt("callq %s", name);
+ else
+ str = strdup("NOP");
+ } else {
+ disas_insn_alt(dctx, insn);
+ str = strdup(disas_result(dctx));
+ }
if (!str)
return -1;
@@ -876,6 +953,7 @@ static void *disas_alt(struct disas_context *dctx,
struct instruction *orig_insn)
{
struct disas_alt alts[DISAS_ALT_MAX] = { 0 };
+ enum pv_mode pv_mode = PV_MODE_UNKNOWN;
struct alternative *alt;
struct disas_alt *dalt;
int offset_next;
@@ -884,7 +962,6 @@ static void *disas_alt(struct disas_context *dctx,
int alt_id;
int offset;
int count;
- int err;
int i;
alt_id = orig_insn->offset;
@@ -892,8 +969,8 @@ static void *disas_alt(struct disas_context *dctx,
/*
* Initialize and disassemble the default alternative.
*/
- err = disas_alt_init(&alts[0], orig_insn, NULL);
- if (err)
+ pv_mode = disas_alt_init(&alts[0], orig_insn, NULL, pv_mode);
+ if (pv_mode < 0)
goto error;
insn_count = disas_alt_default(dctx, &alts[0]);
@@ -911,8 +988,8 @@ static void *disas_alt(struct disas_context *dctx,
break;
}
dalt = &alts[i];
- err = disas_alt_init(dalt, orig_insn, alt);
- if (err)
+ pv_mode = disas_alt_init(dalt, orig_insn, alt, pv_mode);
+ if (pv_mode < 0)
goto error;
count = -1;
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* [PATCH v4 28/28] objtool: Print single line for alternatives with one instruction
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (26 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call Alexandre Chartre
@ 2025-11-13 16:49 ` Alexandre Chartre
2025-11-13 19:55 ` [PATCH v4 00/28] objtool: Function validation tracing David Laight
2025-11-14 1:48 ` Josh Poimboeuf
29 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-13 16:49 UTC (permalink / raw)
To: linux-kernel, mingo, jpoimboe, peterz; +Cc: alexandre.chartre
When disassembling, if an instruction has alternatives which are all
made of a single instruction then print the original instruction and
alternative instructions on a single line with no header. Alternatives
are described in a comment on the same line, after the different
instructions.
Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
tools/objtool/disas.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 20d64b58182ce..2fc5821acbc3f 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -1012,6 +1012,24 @@ static void *disas_alt(struct disas_context *dctx,
}
alt_count = i;
+ /*
+ * Print default and non-default alternatives.
+ *
+ * If all alternatives have a single instruction then print all
+ * alternatives on a single line. Otherwise, print alternatives
+ * side-by-side with an header and a line for each instruction
+ * of the different alternatives.
+ */
+
+ if (insn_count == 1) {
+ disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL);
+ printf("%s", alts[0].insn[0].str);
+ for (i = 1; i < alt_count; i++)
+ printf(" | %s (%s)", alts[i].insn[0].str, alts[i].name);
+ printf(" # <alternative.%x>\n", alt_id);
+ return orig_insn;
+ }
+
/*
* Print an header with the name of each alternative.
*/
--
2.43.5
^ permalink raw reply related [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (27 preceding siblings ...)
2025-11-13 16:49 ` [PATCH v4 28/28] objtool: Print single line for alternatives with one instruction Alexandre Chartre
@ 2025-11-13 19:55 ` David Laight
2025-11-14 8:53 ` Alexandre Chartre
2025-11-14 1:48 ` Josh Poimboeuf
29 siblings, 1 reply; 49+ messages in thread
From: David Laight @ 2025-11-13 19:55 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: linux-kernel, mingo, jpoimboe, peterz
On Thu, 13 Nov 2025 17:48:49 +0100
Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> Hi,
>
> These patches change objtool to disassemble code with libopcodes instead
> of running objdump. You will find below:
>
> - Changes: list of changes made in this version
> - Overview: overview of the changes
> - Notes: description of some particular behavior
> - Examples: output examples
...
> Example 3 (--disas option): Alternatives with multiple instructions
> -------------------------------------------------------------------
> Alternatives with multiple instructions are displayed side-by-side, with
> an header describing the alternative. The code in the first column is the
> default code of the alternative.
>
>
> $ ./tools/objtool/objtool --disas=__switch_to_asm --link vmlinux.o
> __switch_to_asm:
> 82c0: __switch_to_asm+0x0 push %rbp
> 82c1: __switch_to_asm+0x1 push %rbx
> 82c2: __switch_to_asm+0x2 push %r12
> 82c4: __switch_to_asm+0x4 push %r13
> 82c6: __switch_to_asm+0x6 push %r14
> 82c8: __switch_to_asm+0x8 push %r15
> 82ca: __switch_to_asm+0xa mov %rsp,0x1670(%rdi)
> 82d1: __switch_to_asm+0x11 mov 0x1670(%rsi),%rsp
> 82d8: __switch_to_asm+0x18 mov 0xad8(%rsi),%rbx
> 82df: __switch_to_asm+0x1f mov %rbx,%gs:0x0(%rip) # 0x82e7 <__stack_chk_guard>
> 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | NOP1 | mov $0x10,%r12
> 82e8: __switch_to_asm+0x28 | | NOP1 |
> 82e9: __switch_to_asm+0x29 | NOP1 | callq 0x82ef <__switch_to_asm+0x2f> |
> 82ea: __switch_to_asm+0x2a | NOP1 | |
> 82eb: __switch_to_asm+0x2b | NOP1 | |
> 82ec: __switch_to_asm+0x2c | NOP1 | |
> 82ed: __switch_to_asm+0x2d | NOP1 | |
> 82ee: __switch_to_asm+0x2e | NOP1 | int3 | callq 0x82f4 <__switch_to_asm+0x34>
> 82ef: __switch_to_asm+0x2f | NOP1 | add $0x8,%rsp |
> 82f0: __switch_to_asm+0x30 | NOP1 | |
> 82f1: __switch_to_asm+0x31 | NOP1 | |
> 82f2: __switch_to_asm+0x32 | NOP1 | |
> 82f3: __switch_to_asm+0x33 | NOP1 | lfence | int3
> 82f4: __switch_to_asm+0x34 | NOP1 | | callq 0x82fa <__switch_to_asm+0x3a>
> 82f5: __switch_to_asm+0x35 | NOP1 | |
> 82f6: __switch_to_asm+0x36 | NOP1 | |
> 82f7: __switch_to_asm+0x37 | NOP1 | |
> 82f8: __switch_to_asm+0x38 | NOP1 | |
> 82f9: __switch_to_asm+0x39 | NOP1 | | int3
> 82fa: __switch_to_asm+0x3a | NOP1 | | add $0x10,%rsp
> 82fb: __switch_to_asm+0x3b | NOP1 | |
> 82fc: __switch_to_asm+0x3c | NOP1 | |
> 82fd: __switch_to_asm+0x3d | NOP1 | |
> 82fe: __switch_to_asm+0x3e | NOP1 | | dec %r12
> 82ff: __switch_to_asm+0x3f | NOP1 | |
> 8300: __switch_to_asm+0x40 | NOP1 | |
> 8301: __switch_to_asm+0x41 | NOP1 | | jne 0x82ee <__switch_to_asm+0x2e>
> 8302: __switch_to_asm+0x42 | NOP1 | |
> 8303: __switch_to_asm+0x43 | NOP1 | | lfence
> 8304: __switch_to_asm+0x44 | NOP1 | |
> 8305: __switch_to_asm+0x45 | NOP1 | |
> 8306: __switch_to_asm+0x46 | NOP1 | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
> 8307: __switch_to_asm+0x47 | NOP1 | |
> 8308: __switch_to_asm+0x48 | NOP1 | |
> 8309: __switch_to_asm+0x49 | NOP1 | |
> 830a: __switch_to_asm+0x4a | NOP1 | |
> 830b: __switch_to_asm+0x4b | NOP1 | |
> 830c: __switch_to_asm+0x4c | NOP1 | |
> 830d: __switch_to_asm+0x4d | NOP1 | |
> 830e: __switch_to_asm+0x4e | NOP1 | |
> 830f: __switch_to_asm+0x4f | NOP1 | |
> 8310: __switch_to_asm+0x50 | NOP1 | |
> 8311: __switch_to_asm+0x51 | NOP1 | |
> 8312: __switch_to_asm+0x52 pop %r15
> 8314: __switch_to_asm+0x54 pop %r14
> 8316: __switch_to_asm+0x56 pop %r13
> 8318: __switch_to_asm+0x58 pop %r12
> 831a: __switch_to_asm+0x5a pop %rbx
> 831b: __switch_to_asm+0x5b pop %rbp
> 831c: __switch_to_asm+0x5c jmpq 0x8321 <__switch_to>
That might be rather easier to read if the alternatives followed each other.
Not all of us want to use a very wide window to look at object files.
(I didn't see any other example like that either.)
Similarly in Ex 5:
332d4: early_ioremap_pmd+0x4 callq *0x0(%rip) # 0x332da <pv_ops+0x150> | mov %cr3,%rax (!X86_FEATURE_XENPV) | callq xen_read_cr3 (+X86_FEATURE_ALWAYS) # <alternative.332d4>
might be more readable flipped to something like:
332d4: early_ioremap_pmd+0x4 callq *0x0(%rip) # 0x332da <pv_ops+0x150>
!X86_FEATURE_XENPV: mov %cr3,%rax
+X86_FEATURE_ALWAYS: callq xen_read_cr3
David
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
` (28 preceding siblings ...)
2025-11-13 19:55 ` [PATCH v4 00/28] objtool: Function validation tracing David Laight
@ 2025-11-14 1:48 ` Josh Poimboeuf
2025-11-14 9:56 ` Alexandre Chartre
29 siblings, 1 reply; 49+ messages in thread
From: Josh Poimboeuf @ 2025-11-14 1:48 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: linux-kernel, mingo, peterz
On Thu, Nov 13, 2025 at 05:48:49PM +0100, Alexandre Chartre wrote:
> Changes:
> ========
>
> V4:
> ---
> This version fixes a build issue when disassembly is not available. Compared
> with V3, this is addresses by changes in patch 14 (objtool: Improve tracing
> of alternative instructions). Other patches are similar to V3.
For the next revision, please base on tip/master, as there are some
major objtool changes pending for the next merge window.
Most of my comments below are bikeshedding, they are not required for
the next revision and can be addressed in followup patch sets if you'd
rather do it that way.
> - Each alternative of a group alternative is displayed with its feature
> name and flags: <flags><feature-name>
>
> <flags> is made of the following characters:
>
> '!' : ALT_FLAG_NOT
> '+' : ALT_FLAG_DIRECT_CALL
> '?' : unknown flag (i.e. any other flags)
Other than '!', the meaning of the flags isn't intuitive. Maybe it
should just show the source code names:
ALT_NOT(X86_FEATURE_FOO)
ALT_DIRECT_CALL(X86_FEATURE_BAR)
ALT_UNKNOWN_FLAG(X86_FEATURE_BAZ)
?
> - If an alternative is a jump table then "JUMP" is used as the feature
> name.
Hm, it's a bit confusing to label a jump label as an "alternative" as
those are two distinct things (though I'm aware that objtool conflates
the two).
> - If an alternative is an exception table then "EXCEPTION" is used as the
> feature name.
Ditto.
> Disassembly can show default alternative jumping to .altinstr_aux
> -----------------------------------------------------------------
> Disassembly can show a default alternative jumping to .altinstr_aux. This
> happens when the _static_cpu_has() function is used. Its default code
> jumps to .altinstr_aux where a test sequence is executed (test; jnz; jmp).
>
> At runtime, this sequence is not used because the _static_cpu_has()
> an alternative with the X86_FEATURE_ALWAYS feature.
>
>
> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 (X86_FEATURE_HYBRID_CPU) | jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS) # <alternative.debc>
I'm finding this one-line format considerably more difficult to parse
than the slightly longer two-line form:
debc: perf_get_x86_pmu_capability+0xc <alternative.debc> | X86_FEATURE_HYBRID_CPU | X86_FEATURE_ALWAYS
debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 | jmpq 0x61a <perf_get_x86_pmu_capability+0x37>
Also, I wonder if we can make NOP5 lowercase (nop5), since it really is
just an instruction, not something special like a feature.
> Disassembly can show alternative jumping to the next instruction
> ----------------------------------------------------------------
>
> The disassembly can show jump tables with an alternative which jumps
> to the next instruction.
>
> For example:
>
> def9: perf_get_x86_pmu_capability+0x49 NOP2 | jmp defb <perf_get_x86_pmu_capability+0x4b> (JUMP) # <alternative.def9>
I'm also struggling to read this one.
Maybe this needs a two-line form as well:
def9: perf_get_x86_pmu_capability+0x49 <static_branch.def9> |
def9: perf_get_x86_pmu_capability+0x49 NOP2 | jmp defb <perf_get_x86_pmu_capability+0x4b>
> Example 2 (--disas option): Single Instruction Alternatives
> -----------------------------------------------------------
I would like to convert this to a dedicated "disas" subcommand which can
be run like "objtool disas <func>" or so. But again that can probably
be done in a followup.
> Example 3 (--disas option): Alternatives with multiple instructions
> -------------------------------------------------------------------
> Alternatives with multiple instructions are displayed side-by-side, with
> an header describing the alternative. The code in the first column is the
> default code of the alternative.
>
>
> $ ./tools/objtool/objtool --disas=__switch_to_asm --link vmlinux.o
> __switch_to_asm:
> 82c0: __switch_to_asm+0x0 push %rbp
> 82c1: __switch_to_asm+0x1 push %rbx
> 82c2: __switch_to_asm+0x2 push %r12
> 82c4: __switch_to_asm+0x4 push %r13
> 82c6: __switch_to_asm+0x6 push %r14
> 82c8: __switch_to_asm+0x8 push %r15
> 82ca: __switch_to_asm+0xa mov %rsp,0x1670(%rdi)
> 82d1: __switch_to_asm+0x11 mov 0x1670(%rsi),%rsp
> 82d8: __switch_to_asm+0x18 mov 0xad8(%rsi),%rbx
> 82df: __switch_to_asm+0x1f mov %rbx,%gs:0x0(%rip) # 0x82e7 <__stack_chk_guard>
> 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
Are the alternatives swapped? I believe this comes from the following
code, so the !X86_FEATURE_ALWAYS column should be last?
.macro FILL_RETURN_BUFFER reg:req nr:req ftr:req ftr2=ALT_NOT(X86_FEATURE_ALWAYS)
ALTERNATIVE_2 "jmp .Lskip_rsb_\@", \
__stringify(__FILL_RETURN_BUFFER(\reg,\nr)), \ftr, \
__stringify(nop;nop;__FILL_ONE_RETURN), \ftr2
.Lskip_rsb_\@:
.endm
> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | NOP1 | mov $0x10,%r12
> 82e8: __switch_to_asm+0x28 | | NOP1 |
> 82e9: __switch_to_asm+0x29 | NOP1 | callq 0x82ef <__switch_to_asm+0x2f> |
> 82ea: __switch_to_asm+0x2a | NOP1 | |
> 82eb: __switch_to_asm+0x2b | NOP1 | |
> 82ec: __switch_to_asm+0x2c | NOP1 | |
> 82ed: __switch_to_asm+0x2d | NOP1 | |
> 82ee: __switch_to_asm+0x2e | NOP1 | int3 | callq 0x82f4 <__switch_to_asm+0x34>
> 82ef: __switch_to_asm+0x2f | NOP1 | add $0x8,%rsp |
> 82f0: __switch_to_asm+0x30 | NOP1 | |
> 82f1: __switch_to_asm+0x31 | NOP1 | |
> 82f2: __switch_to_asm+0x32 | NOP1 | |
> 82f3: __switch_to_asm+0x33 | NOP1 | lfence | int3
> 82f4: __switch_to_asm+0x34 | NOP1 | | callq 0x82fa <__switch_to_asm+0x3a>
> 82f5: __switch_to_asm+0x35 | NOP1 | |
> 82f6: __switch_to_asm+0x36 | NOP1 | |
> 82f7: __switch_to_asm+0x37 | NOP1 | |
> 82f8: __switch_to_asm+0x38 | NOP1 | |
> 82f9: __switch_to_asm+0x39 | NOP1 | | int3
> 82fa: __switch_to_asm+0x3a | NOP1 | | add $0x10,%rsp
> 82fb: __switch_to_asm+0x3b | NOP1 | |
> 82fc: __switch_to_asm+0x3c | NOP1 | |
> 82fd: __switch_to_asm+0x3d | NOP1 | |
> 82fe: __switch_to_asm+0x3e | NOP1 | | dec %r12
> 82ff: __switch_to_asm+0x3f | NOP1 | |
> 8300: __switch_to_asm+0x40 | NOP1 | |
> 8301: __switch_to_asm+0x41 | NOP1 | | jne 0x82ee <__switch_to_asm+0x2e>
> 8302: __switch_to_asm+0x42 | NOP1 | |
> 8303: __switch_to_asm+0x43 | NOP1 | | lfence
> 8304: __switch_to_asm+0x44 | NOP1 | |
> 8305: __switch_to_asm+0x45 | NOP1 | |
> 8306: __switch_to_asm+0x46 | NOP1 | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
> 8307: __switch_to_asm+0x47 | NOP1 | |
> 8308: __switch_to_asm+0x48 | NOP1 | |
> 8309: __switch_to_asm+0x49 | NOP1 | |
> 830a: __switch_to_asm+0x4a | NOP1 | |
> 830b: __switch_to_asm+0x4b | NOP1 | |
> 830c: __switch_to_asm+0x4c | NOP1 | |
> 830d: __switch_to_asm+0x4d | NOP1 | |
> 830e: __switch_to_asm+0x4e | NOP1 | |
> 830f: __switch_to_asm+0x4f | NOP1 | |
> 8310: __switch_to_asm+0x50 | NOP1 | |
> 8311: __switch_to_asm+0x51 | NOP1 | |
I like this a lot, but I think it could be vertically compressed quite a
bit, and superfluous NOPs removed:
82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | nop1 | mov $0x10,%r12
82e8: __switch_to_asm+0x28 | | nop1 |
82e9: __switch_to_asm+0x29 | | callq 0x82ef <__switch_to_asm+0x2f> |
82ee: __switch_to_asm+0x2e | | int3 | callq 0x82f4 <__switch_to_asm+0x34>
82ef: __switch_to_asm+0x2f | | add $0x8,%rsp |
82f3: __switch_to_asm+0x33 | | lfence | int3
82f4: __switch_to_asm+0x34 | | | callq 0x82fa <__switch_to_asm+0x3a>
82f9: __switch_to_asm+0x39 | | | int3
82fa: __switch_to_asm+0x3a | | | add $0x10,%rsp
82fe: __switch_to_asm+0x3e | | | dec %r12
8301: __switch_to_asm+0x41 | | | jne 0x82ee <__switch_to_asm+0x2e>
8303: __switch_to_asm+0x43 | | | lfence
8306: __switch_to_asm+0x46 | | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
That reads much nicer to me.
--
Josh
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-13 19:55 ` [PATCH v4 00/28] objtool: Function validation tracing David Laight
@ 2025-11-14 8:53 ` Alexandre Chartre
0 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-14 8:53 UTC (permalink / raw)
To: David Laight; +Cc: alexandre.chartre, linux-kernel, mingo, jpoimboe, peterz
On 11/13/25 20:55, David Laight wrote:
> On Thu, 13 Nov 2025 17:48:49 +0100
> Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
>
>> Hi,
>>
>> These patches change objtool to disassemble code with libopcodes instead
>> of running objdump. You will find below:
>>
>> - Changes: list of changes made in this version
>> - Overview: overview of the changes
>> - Notes: description of some particular behavior
>> - Examples: output examples
> ...
>> Example 3 (--disas option): Alternatives with multiple instructions
>> -------------------------------------------------------------------
>> Alternatives with multiple instructions are displayed side-by-side, with
>> an header describing the alternative. The code in the first column is the
>> default code of the alternative.
>>
>>
>> $ ./tools/objtool/objtool --disas=__switch_to_asm --link vmlinux.o
>> __switch_to_asm:
>> 82c0: __switch_to_asm+0x0 push %rbp
>> 82c1: __switch_to_asm+0x1 push %rbx
>> 82c2: __switch_to_asm+0x2 push %r12
>> 82c4: __switch_to_asm+0x4 push %r13
>> 82c6: __switch_to_asm+0x6 push %r14
>> 82c8: __switch_to_asm+0x8 push %r15
>> 82ca: __switch_to_asm+0xa mov %rsp,0x1670(%rdi)
>> 82d1: __switch_to_asm+0x11 mov 0x1670(%rsi),%rsp
>> 82d8: __switch_to_asm+0x18 mov 0xad8(%rsi),%rbx
>> 82df: __switch_to_asm+0x1f mov %rbx,%gs:0x0(%rip) # 0x82e7 <__stack_chk_guard>
>> 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
>> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | NOP1 | mov $0x10,%r12
>> 82e8: __switch_to_asm+0x28 | | NOP1 |
>> 82e9: __switch_to_asm+0x29 | NOP1 | callq 0x82ef <__switch_to_asm+0x2f> |
>> 82ea: __switch_to_asm+0x2a | NOP1 | |
>> 82eb: __switch_to_asm+0x2b | NOP1 | |
>> 82ec: __switch_to_asm+0x2c | NOP1 | |
>> 82ed: __switch_to_asm+0x2d | NOP1 | |
>> 82ee: __switch_to_asm+0x2e | NOP1 | int3 | callq 0x82f4 <__switch_to_asm+0x34>
>> 82ef: __switch_to_asm+0x2f | NOP1 | add $0x8,%rsp |
>> 82f0: __switch_to_asm+0x30 | NOP1 | |
>> 82f1: __switch_to_asm+0x31 | NOP1 | |
>> 82f2: __switch_to_asm+0x32 | NOP1 | |
>> 82f3: __switch_to_asm+0x33 | NOP1 | lfence | int3
>> 82f4: __switch_to_asm+0x34 | NOP1 | | callq 0x82fa <__switch_to_asm+0x3a>
>> 82f5: __switch_to_asm+0x35 | NOP1 | |
>> 82f6: __switch_to_asm+0x36 | NOP1 | |
>> 82f7: __switch_to_asm+0x37 | NOP1 | |
>> 82f8: __switch_to_asm+0x38 | NOP1 | |
>> 82f9: __switch_to_asm+0x39 | NOP1 | | int3
>> 82fa: __switch_to_asm+0x3a | NOP1 | | add $0x10,%rsp
>> 82fb: __switch_to_asm+0x3b | NOP1 | |
>> 82fc: __switch_to_asm+0x3c | NOP1 | |
>> 82fd: __switch_to_asm+0x3d | NOP1 | |
>> 82fe: __switch_to_asm+0x3e | NOP1 | | dec %r12
>> 82ff: __switch_to_asm+0x3f | NOP1 | |
>> 8300: __switch_to_asm+0x40 | NOP1 | |
>> 8301: __switch_to_asm+0x41 | NOP1 | | jne 0x82ee <__switch_to_asm+0x2e>
>> 8302: __switch_to_asm+0x42 | NOP1 | |
>> 8303: __switch_to_asm+0x43 | NOP1 | | lfence
>> 8304: __switch_to_asm+0x44 | NOP1 | |
>> 8305: __switch_to_asm+0x45 | NOP1 | |
>> 8306: __switch_to_asm+0x46 | NOP1 | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
>> 8307: __switch_to_asm+0x47 | NOP1 | |
>> 8308: __switch_to_asm+0x48 | NOP1 | |
>> 8309: __switch_to_asm+0x49 | NOP1 | |
>> 830a: __switch_to_asm+0x4a | NOP1 | |
>> 830b: __switch_to_asm+0x4b | NOP1 | |
>> 830c: __switch_to_asm+0x4c | NOP1 | |
>> 830d: __switch_to_asm+0x4d | NOP1 | |
>> 830e: __switch_to_asm+0x4e | NOP1 | |
>> 830f: __switch_to_asm+0x4f | NOP1 | |
>> 8310: __switch_to_asm+0x50 | NOP1 | |
>> 8311: __switch_to_asm+0x51 | NOP1 | |
>> 8312: __switch_to_asm+0x52 pop %r15
>> 8314: __switch_to_asm+0x54 pop %r14
>> 8316: __switch_to_asm+0x56 pop %r13
>> 8318: __switch_to_asm+0x58 pop %r12
>> 831a: __switch_to_asm+0x5a pop %rbx
>> 831b: __switch_to_asm+0x5b pop %rbp
>> 831c: __switch_to_asm+0x5c jmpq 0x8321 <__switch_to>
>
> That might be rather easier to read if the alternatives followed each other.
> Not all of us want to use a very wide window to look at object files.
> (I didn't see any other example like that either.)
>
> Similarly in Ex 5:
> 332d4: early_ioremap_pmd+0x4 callq *0x0(%rip) # 0x332da <pv_ops+0x150> | mov %cr3,%rax (!X86_FEATURE_XENPV) | callq xen_read_cr3 (+X86_FEATURE_ALWAYS) # <alternative.332d4>
> might be more readable flipped to something like:
> 332d4: early_ioremap_pmd+0x4 callq *0x0(%rip) # 0x332da <pv_ops+0x150>
> !X86_FEATURE_XENPV: mov %cr3,%rax
> +X86_FEATURE_ALWAYS: callq xen_read_cr3
>
I initially had alternatives followed each other, but PeterZ suggested to
have them on one line. But I agree that this requires a large window.
I can add an option to select the display: either following each other (that
would be the default), or side-by-side.
Thanks,
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-14 1:48 ` Josh Poimboeuf
@ 2025-11-14 9:56 ` Alexandre Chartre
2025-11-14 21:34 ` Josh Poimboeuf
0 siblings, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-14 9:56 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: alexandre.chartre, linux-kernel, mingo, peterz
On 11/14/25 02:48, Josh Poimboeuf wrote:
> On Thu, Nov 13, 2025 at 05:48:49PM +0100, Alexandre Chartre wrote:
>> Changes:
>> ========
>>
>> V4:
>> ---
>> This version fixes a build issue when disassembly is not available. Compared
>> with V3, this is addresses by changes in patch 14 (objtool: Improve tracing
>> of alternative instructions). Other patches are similar to V3.
>
> For the next revision, please base on tip/master, as there are some
> major objtool changes pending for the next merge window.
Ok, I will rebase the next revision on tip/master.
> Most of my comments below are bikeshedding, they are not required for
> the next revision and can be addressed in followup patch sets if you'd
> rather do it that way.
If changes are simple, I will try to address them immediately otherwise
defer to next patches.
>> - Each alternative of a group alternative is displayed with its feature
>> name and flags: <flags><feature-name>
>>
>> <flags> is made of the following characters:
>>
>> '!' : ALT_FLAG_NOT
>> '+' : ALT_FLAG_DIRECT_CALL
>> '?' : unknown flag (i.e. any other flags)
>
> Other than '!', the meaning of the flags isn't intuitive. Maybe it
> should just show the source code names:
>
> ALT_NOT(X86_FEATURE_FOO)
>
> ALT_DIRECT_CALL(X86_FEATURE_BAR)
>
> ALT_UNKNOWN_FLAG(X86_FEATURE_BAZ)
>
I think '?' is meaningful too, but I wasn't sure about '+'.
I am using single characters to keep the alternative name short. It can already
be fairly long because of the feature name (like "X86_FEATURE_SPEC_STORE_BYPASS_DISABLE")
Also I am assuming that flags can be combined (although that's not currently
the case) so that would be more difficult with full ALT_* names and the
result would be much longer.
>> - If an alternative is a jump table then "JUMP" is used as the feature
>> name.
>
> Hm, it's a bit confusing to label a jump label as an "alternative" as
> those are two distinct things (though I'm aware that objtool conflates
> the two).
>
>> - If an alternative is an exception table then "EXCEPTION" is used as the
>> feature name.
>
> Ditto.
>
Yes, the wording is not good, I use it just because objtool handles jump
labels and exception tables as alternative. I will reword to something
better.
>> Disassembly can show default alternative jumping to .altinstr_aux
>> -----------------------------------------------------------------
>> Disassembly can show a default alternative jumping to .altinstr_aux. This
>> happens when the _static_cpu_has() function is used. Its default code
>> jumps to .altinstr_aux where a test sequence is executed (test; jnz; jmp).
>>
>> At runtime, this sequence is not used because the _static_cpu_has()
>> an alternative with the X86_FEATURE_ALWAYS feature.
>>
>>
>> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 (X86_FEATURE_HYBRID_CPU) | jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS) # <alternative.debc>
>
> I'm finding this one-line format considerably more difficult to parse
> than the slightly longer two-line form:
>
> debc: perf_get_x86_pmu_capability+0xc <alternative.debc> | X86_FEATURE_HYBRID_CPU | X86_FEATURE_ALWAYS
> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 | jmpq 0x61a <perf_get_x86_pmu_capability+0x37>
Another option could be:
debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> (<alternative.debc>) |
NOP5 (X86_FEATURE_HYBRID_CPU) |
jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS)
I think I will use this option when displaying alternative one after the other,
and your suggestion when displaying side-by-side, and add an option to select
the display.
>
> Also, I wonder if we can make NOP5 lowercase (nop5), since it really is
> just an instruction, not something special like a feature.
This indicates that this is a pseudo instruction, NOP5 is actually nopl 0x00(%eax,%eax,1).
Even NOP1 can be a simple nop but also xchg %rax,%rax.
>> Disassembly can show alternative jumping to the next instruction
>> ----------------------------------------------------------------
>>
>> The disassembly can show jump tables with an alternative which jumps
>> to the next instruction.
>>
>> For example:
>>
>> def9: perf_get_x86_pmu_capability+0x49 NOP2 | jmp defb <perf_get_x86_pmu_capability+0x4b> (JUMP) # <alternative.def9>
>
> I'm also struggling to read this one.
>
> Maybe this needs a two-line form as well:
>
> def9: perf_get_x86_pmu_capability+0x49 <static_branch.def9> |
> def9: perf_get_x86_pmu_capability+0x49 NOP2 | jmp defb <perf_get_x86_pmu_capability+0x4b>
I will do something similar as suggested above.
>> Example 2 (--disas option): Single Instruction Alternatives
>> -----------------------------------------------------------
>
> I would like to convert this to a dedicated "disas" subcommand which can
> be run like "objtool disas <func>" or so. But again that can probably
> be done in a followup.
Ok, I will look at it.
>> Example 3 (--disas option): Alternatives with multiple instructions
>> -------------------------------------------------------------------
>> Alternatives with multiple instructions are displayed side-by-side, with
>> an header describing the alternative. The code in the first column is the
>> default code of the alternative.
>>
>>
>> $ ./tools/objtool/objtool --disas=__switch_to_asm --link vmlinux.o
>> __switch_to_asm:
>> 82c0: __switch_to_asm+0x0 push %rbp
>> 82c1: __switch_to_asm+0x1 push %rbx
>> 82c2: __switch_to_asm+0x2 push %r12
>> 82c4: __switch_to_asm+0x4 push %r13
>> 82c6: __switch_to_asm+0x6 push %r14
>> 82c8: __switch_to_asm+0x8 push %r15
>> 82ca: __switch_to_asm+0xa mov %rsp,0x1670(%rdi)
>> 82d1: __switch_to_asm+0x11 mov 0x1670(%rsi),%rsp
>> 82d8: __switch_to_asm+0x18 mov 0xad8(%rsi),%rbx
>> 82df: __switch_to_asm+0x1f mov %rbx,%gs:0x0(%rip) # 0x82e7 <__stack_chk_guard>
>> 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
>
> Are the alternatives swapped? I believe this comes from the following
> code, so the !X86_FEATURE_ALWAYS column should be last?
>
> .macro FILL_RETURN_BUFFER reg:req nr:req ftr:req ftr2=ALT_NOT(X86_FEATURE_ALWAYS)
> ALTERNATIVE_2 "jmp .Lskip_rsb_\@", \
> __stringify(__FILL_RETURN_BUFFER(\reg,\nr)), \ftr, \
> __stringify(nop;nop;__FILL_ONE_RETURN), \ftr2
>
> .Lskip_rsb_\@:
> .endm
I will check but I process/print alternative in the order provided by
objtool (in struct alternative)
>> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | NOP1 | mov $0x10,%r12
>> 82e8: __switch_to_asm+0x28 | | NOP1 |
>> 82e9: __switch_to_asm+0x29 | NOP1 | callq 0x82ef <__switch_to_asm+0x2f> |
>> 82ea: __switch_to_asm+0x2a | NOP1 | |
>> 82eb: __switch_to_asm+0x2b | NOP1 | |
>> 82ec: __switch_to_asm+0x2c | NOP1 | |
>> 82ed: __switch_to_asm+0x2d | NOP1 | |
>> 82ee: __switch_to_asm+0x2e | NOP1 | int3 | callq 0x82f4 <__switch_to_asm+0x34>
>> 82ef: __switch_to_asm+0x2f | NOP1 | add $0x8,%rsp |
>> 82f0: __switch_to_asm+0x30 | NOP1 | |
>> 82f1: __switch_to_asm+0x31 | NOP1 | |
>> 82f2: __switch_to_asm+0x32 | NOP1 | |
>> 82f3: __switch_to_asm+0x33 | NOP1 | lfence | int3
>> 82f4: __switch_to_asm+0x34 | NOP1 | | callq 0x82fa <__switch_to_asm+0x3a>
>> 82f5: __switch_to_asm+0x35 | NOP1 | |
>> 82f6: __switch_to_asm+0x36 | NOP1 | |
>> 82f7: __switch_to_asm+0x37 | NOP1 | |
>> 82f8: __switch_to_asm+0x38 | NOP1 | |
>> 82f9: __switch_to_asm+0x39 | NOP1 | | int3
>> 82fa: __switch_to_asm+0x3a | NOP1 | | add $0x10,%rsp
>> 82fb: __switch_to_asm+0x3b | NOP1 | |
>> 82fc: __switch_to_asm+0x3c | NOP1 | |
>> 82fd: __switch_to_asm+0x3d | NOP1 | |
>> 82fe: __switch_to_asm+0x3e | NOP1 | | dec %r12
>> 82ff: __switch_to_asm+0x3f | NOP1 | |
>> 8300: __switch_to_asm+0x40 | NOP1 | |
>> 8301: __switch_to_asm+0x41 | NOP1 | | jne 0x82ee <__switch_to_asm+0x2e>
>> 8302: __switch_to_asm+0x42 | NOP1 | |
>> 8303: __switch_to_asm+0x43 | NOP1 | | lfence
>> 8304: __switch_to_asm+0x44 | NOP1 | |
>> 8305: __switch_to_asm+0x45 | NOP1 | |
>> 8306: __switch_to_asm+0x46 | NOP1 | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
>> 8307: __switch_to_asm+0x47 | NOP1 | |
>> 8308: __switch_to_asm+0x48 | NOP1 | |
>> 8309: __switch_to_asm+0x49 | NOP1 | |
>> 830a: __switch_to_asm+0x4a | NOP1 | |
>> 830b: __switch_to_asm+0x4b | NOP1 | |
>> 830c: __switch_to_asm+0x4c | NOP1 | |
>> 830d: __switch_to_asm+0x4d | NOP1 | |
>> 830e: __switch_to_asm+0x4e | NOP1 | |
>> 830f: __switch_to_asm+0x4f | NOP1 | |
>> 8310: __switch_to_asm+0x50 | NOP1 | |
>> 8311: __switch_to_asm+0x51 | NOP1 | |
>
> I like this a lot, but I think it could be vertically compressed quite a
> bit, and superfluous NOPs removed:
>
> 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52> | nop1 | mov $0x10,%r12
> 82e8: __switch_to_asm+0x28 | | nop1 |
> 82e9: __switch_to_asm+0x29 | | callq 0x82ef <__switch_to_asm+0x2f> |
> 82ee: __switch_to_asm+0x2e | | int3 | callq 0x82f4 <__switch_to_asm+0x34>
> 82ef: __switch_to_asm+0x2f | | add $0x8,%rsp |
> 82f3: __switch_to_asm+0x33 | | lfence | int3
> 82f4: __switch_to_asm+0x34 | | | callq 0x82fa <__switch_to_asm+0x3a>
> 82f9: __switch_to_asm+0x39 | | | int3
> 82fa: __switch_to_asm+0x3a | | | add $0x10,%rsp
> 82fe: __switch_to_asm+0x3e | | | dec %r12
> 8301: __switch_to_asm+0x41 | | | jne 0x82ee <__switch_to_asm+0x2e>
> 8303: __switch_to_asm+0x43 | | | lfence
> 8306: __switch_to_asm+0x46 | | | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
>
> That reads much nicer to me.
>
Yeah, better. I can easily do that by getting rid of trailing NOPs.
Thanks,
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 11/28] objtool: Trace instruction state changes during function validation
2025-11-13 16:49 ` [PATCH v4 11/28] objtool: Trace instruction state changes during " Alexandre Chartre
@ 2025-11-14 21:21 ` Josh Poimboeuf
2025-11-17 7:33 ` Alexandre Chartre
0 siblings, 1 reply; 49+ messages in thread
From: Josh Poimboeuf @ 2025-11-14 21:21 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: linux-kernel, mingo, peterz
On Thu, Nov 13, 2025 at 05:49:00PM +0100, Alexandre Chartre wrote:
> +/*
> + * Return the name of a register. Note that the same static buffer
> + * is returned if the name is dynamically generated.
> + */
> +static const char *cfi_reg_name(unsigned int reg)
> +{
> + static char rname_buffer[CFI_REG_NAME_MAXLEN];
> +
> + switch (reg) {
> + case CFI_UNDEFINED:
> + return "<undefined>";
> + case CFI_CFA:
> + return "cfa";
> + case CFI_SP_INDIRECT:
> + return "(sp)";
> + case CFI_BP_INDIRECT:
> + return "(bp)";
> + }
> +
> + if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == 1)
> + return NULL;
An snprintf() error would either be -1 (error) or ">=
CFI_REG_NAME_MAXLEN" (truncation).
Also maybe return "(error)" or so, so the caller doesn't need to check
the return value.
> +static void trace_cfi_reg(const char *prefix, int reg, const char *fmt,
> + int base_prev, int offset_prev,
> + int base_next, int offset_next)
> +{
> + const char *rname;
> +
> + if (base_prev == base_next && offset_prev == offset_next)
> + return;
> +
> + if (prefix)
> + TRACE("%s:", prefix);
> +
> + rname = cfi_reg_name(reg);
> +
> + if (base_next == CFI_UNDEFINED) {
> + TRACE("%1$s=<undef> ", rname);
> + } else {
> + TRACE(fmt, rname,
> + cfi_reg_name(base_next), offset_next);
Since cfi_reg_name() can reuse the same static local buffer, rname and
cfi_reg_name(base_next) might point to the same string?
--
Josh
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-14 9:56 ` Alexandre Chartre
@ 2025-11-14 21:34 ` Josh Poimboeuf
2025-11-17 7:50 ` Alexandre Chartre
0 siblings, 1 reply; 49+ messages in thread
From: Josh Poimboeuf @ 2025-11-14 21:34 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: linux-kernel, mingo, peterz
On Fri, Nov 14, 2025 at 10:56:48AM +0100, Alexandre Chartre wrote:
> > Other than '!', the meaning of the flags isn't intuitive. Maybe it
> > should just show the source code names:
> >
> > ALT_NOT(X86_FEATURE_FOO)
> >
> > ALT_DIRECT_CALL(X86_FEATURE_BAR)
> >
> > ALT_UNKNOWN_FLAG(X86_FEATURE_BAZ)
> >
>
> I think '?' is meaningful too, but I wasn't sure about '+'.
>
> I am using single characters to keep the alternative name short. It can already
> be fairly long because of the feature name (like "X86_FEATURE_SPEC_STORE_BYPASS_DISABLE")
>
> Also I am assuming that flags can be combined (although that's not currently
> the case) so that would be more difficult with full ALT_* names and the
> result would be much longer.
Ok.
> > > - If an alternative is a jump table then "JUMP" is used as the feature
> > > name.
> >
> > Hm, it's a bit confusing to label a jump label as an "alternative" as
> > those are two distinct things (though I'm aware that objtool conflates
> > the two).
> >
> > > - If an alternative is an exception table then "EXCEPTION" is used as the
> > > feature name.
> >
> > Ditto.
> >
>
> Yes, the wording is not good, I use it just because objtool handles jump
> labels and exception tables as alternative. I will reword to something
> better.
I meant not only the wording here, but also the "<alternative.def9>"
labels shown in the disassembly.
> > > Disassembly can show default alternative jumping to .altinstr_aux
> > > -----------------------------------------------------------------
> > > Disassembly can show a default alternative jumping to .altinstr_aux. This
> > > happens when the _static_cpu_has() function is used. Its default code
> > > jumps to .altinstr_aux where a test sequence is executed (test; jnz; jmp).
> > >
> > > At runtime, this sequence is not used because the _static_cpu_has()
> > > an alternative with the X86_FEATURE_ALWAYS feature.
> > >
> > >
> > > debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 (X86_FEATURE_HYBRID_CPU) | jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS) # <alternative.debc>
> >
> > I'm finding this one-line format considerably more difficult to parse
> > than the slightly longer two-line form:
> >
> > debc: perf_get_x86_pmu_capability+0xc <alternative.debc> | X86_FEATURE_HYBRID_CPU | X86_FEATURE_ALWAYS
> > debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 | jmpq 0x61a <perf_get_x86_pmu_capability+0x37>
>
>
> Another option could be:
>
> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> (<alternative.debc>) |
> NOP5 (X86_FEATURE_HYBRID_CPU) |
> jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS)
>
> I think I will use this option when displaying alternative one after the other,
> and your suggestion when displaying side-by-side, and add an option to select
> the display.
Hm, but how would that "one after the other" display look for an
alternative with multiple instructions? A "compact vs wide" option is
ok, but within each of those I think it's helpful to use a consistent
format regardless of whether the alternative has one or multiple
instructions.
> > Also, I wonder if we can make NOP5 lowercase (nop5), since it really is
> > just an instruction, not something special like a feature.
>
> This indicates that this is a pseudo instruction, NOP5 is actually nopl 0x00(%eax,%eax,1).
> Even NOP1 can be a simple nop but also xchg %rax,%rax.
But it still represents a single instruction... I view it as a readable
shorthand rather than a pseudo instruction.
> > > 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
> >
> > Are the alternatives swapped? I believe this comes from the following
> > code, so the !X86_FEATURE_ALWAYS column should be last?
> >
> > .macro FILL_RETURN_BUFFER reg:req nr:req ftr:req ftr2=ALT_NOT(X86_FEATURE_ALWAYS)
> > ALTERNATIVE_2 "jmp .Lskip_rsb_\@", \
> > __stringify(__FILL_RETURN_BUFFER(\reg,\nr)), \ftr, \
> > __stringify(nop;nop;__FILL_ONE_RETURN), \ftr2
> >
> > .Lskip_rsb_\@:
> > .endm
>
> I will check but I process/print alternative in the order provided by
> objtool (in struct alternative)
It's quite possible objtool has them in reverse order. The order
shouldn't matter for objtool validate_branch(), but definitely matters
here.
--
Josh
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call
2025-11-13 16:49 ` [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call Alexandre Chartre
@ 2025-11-15 1:34 ` kernel test robot
2025-11-17 7:37 ` Alexandre Chartre
0 siblings, 1 reply; 49+ messages in thread
From: kernel test robot @ 2025-11-15 1:34 UTC (permalink / raw)
To: Alexandre Chartre, linux-kernel, mingo, jpoimboe, peterz
Cc: llvm, oe-kbuild-all, alexandre.chartre
Hi Alexandre,
kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on v6.18-rc5]
[cannot apply to next-20251114]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Alexandre-Chartre/objtool-Move-disassembly-functions-to-a-separated-file/20251114-020940
base: linus/master
patch link: https://lore.kernel.org/r/20251113164917.2563486-28-alexandre.chartre%40oracle.com
patch subject: [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call
:::::: branch date: 30 hours ago
:::::: commit date: 30 hours ago
config: loongarch-randconfig-001-20251115 (https://download.01.org/0day-ci/archive/20251115/202511150854.iDlHBWI5-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251115/202511150854.iDlHBWI5-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/r/202511150854.iDlHBWI5-lkp@intel.com/
All errors (new ones prefixed by >>):
>> disas.c:15:10: fatal error: 'asm/cpufeatures.h' file not found
15 | #include <asm/cpufeatures.h>
| ^~~~~~~~~~~~~~~~~~~
1 error generated.
make[5]: *** [tools/build/Makefile.build:85: tools/objtool/disas.o] Error 1 shuffle=3303159566
make[5]: Target '__build' not remade because of errors.
make[4]: *** [Makefile:87: tools/objtool/objtool-in.o] Error 2 shuffle=3303159566
make[4]: Target 'all' not remade because of errors.
make[3]: *** [Makefile:73: objtool] Error 2 shuffle=3303159566
make[2]: *** [Makefile:1449: tools/objtool] Error 2 shuffle=3303159566
make[2]: Target 'prepare' not remade because of errors.
make[1]: *** [Makefile:248: __sub-make] Error 2 shuffle=3303159566
make[1]: Target 'prepare' not remade because of errors.
make: *** [Makefile:248: __sub-make] Error 2 shuffle=3303159566
make: Target 'prepare' not remade because of errors.
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 11/28] objtool: Trace instruction state changes during function validation
2025-11-14 21:21 ` Josh Poimboeuf
@ 2025-11-17 7:33 ` Alexandre Chartre
0 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-17 7:33 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: alexandre.chartre, linux-kernel, mingo, peterz
On 11/14/25 22:21, Josh Poimboeuf wrote:
> On Thu, Nov 13, 2025 at 05:49:00PM +0100, Alexandre Chartre wrote:
>> +/*
>> + * Return the name of a register. Note that the same static buffer
>> + * is returned if the name is dynamically generated.
>> + */
>> +static const char *cfi_reg_name(unsigned int reg)
>> +{
>> + static char rname_buffer[CFI_REG_NAME_MAXLEN];
>> +
>> + switch (reg) {
>> + case CFI_UNDEFINED:
>> + return "<undefined>";
>> + case CFI_CFA:
>> + return "cfa";
>> + case CFI_SP_INDIRECT:
>> + return "(sp)";
>> + case CFI_BP_INDIRECT:
>> + return "(bp)";
>> + }
>> +
>> + if (snprintf(rname_buffer, CFI_REG_NAME_MAXLEN, "r%d", reg) == 1)
>> + return NULL;
>
> An snprintf() error would either be -1 (error) or ">=
> CFI_REG_NAME_MAXLEN" (truncation).
>
> Also maybe return "(error)" or so, so the caller doesn't need to check
> the return value.
Right, I will fix that.
>> +static void trace_cfi_reg(const char *prefix, int reg, const char *fmt,
>> + int base_prev, int offset_prev,
>> + int base_next, int offset_next)
>> +{
>> + const char *rname;
>> +
>> + if (base_prev == base_next && offset_prev == offset_next)
>> + return;
>> +
>> + if (prefix)
>> + TRACE("%s:", prefix);
>> +
>> + rname = cfi_reg_name(reg);
>> +
>> + if (base_next == CFI_UNDEFINED) {
>> + TRACE("%1$s=<undef> ", rname);
>> + } else {
>> + TRACE(fmt, rname,
>> + cfi_reg_name(base_next), offset_next);
>
> Since cfi_reg_name() can reuse the same static local buffer, rname and
> cfi_reg_name(base_next) might point to the same string?
>
Yes, good catch. I will fix.
Thanks,
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call
2025-11-15 1:34 ` kernel test robot
@ 2025-11-17 7:37 ` Alexandre Chartre
0 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-17 7:37 UTC (permalink / raw)
To: kernel test robot, linux-kernel, mingo, jpoimboe, peterz
Cc: alexandre.chartre, llvm, oe-kbuild-all
On 11/15/25 02:34, kernel test robot wrote:
> Hi Alexandre,
>
> kernel test robot noticed the following build errors:
>
> [auto build test ERROR on linus/master]
> [also build test ERROR on v6.18-rc5]
> [cannot apply to next-20251114]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch#_base_tree_information]
>
> url: https://github.com/intel-lab-lkp/linux/commits/Alexandre-Chartre/objtool-Move-disassembly-functions-to-a-separated-file/20251114-020940
> base: linus/master
> patch link: https://lore.kernel.org/r/20251113164917.2563486-28-alexandre.chartre%40oracle.com
> patch subject: [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call
> :::::: branch date: 30 hours ago
> :::::: commit date: 30 hours ago
> config: loongarch-randconfig-001-20251115 (https://download.01.org/0day-ci/archive/20251115/202511150854.iDlHBWI5-lkp@intel.com/config)
> compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
> reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251115/202511150854.iDlHBWI5-lkp@intel.com/reproduce)
>
> If you fix the issue in a separate patch/commit (i.e. not just a new version of
> the same patch/commit), kindly add following tags
> | Reported-by: kernel test robot <lkp@intel.com>
> | Closes: https://lore.kernel.org/r/202511150854.iDlHBWI5-lkp@intel.com/
>
> All errors (new ones prefixed by >>):
>
>>> disas.c:15:10: fatal error: 'asm/cpufeatures.h' file not found
> 15 | #include <asm/cpufeatures.h>
> | ^~~~~~~~~~~~~~~~~~~
> 1 error generated.
> make[5]: *** [tools/build/Makefile.build:85: tools/objtool/disas.o] Error 1 shuffle=3303159566
> make[5]: Target '__build' not remade because of errors.
> make[4]: *** [Makefile:87: tools/objtool/objtool-in.o] Error 2 shuffle=3303159566
> make[4]: Target 'all' not remade because of errors.
> make[3]: *** [Makefile:73: objtool] Error 2 shuffle=3303159566
> make[2]: *** [Makefile:1449: tools/objtool] Error 2 shuffle=3303159566
> make[2]: Target 'prepare' not remade because of errors.
> make[1]: *** [Makefile:248: __sub-make] Error 2 shuffle=3303159566
> make[1]: Target 'prepare' not remade because of errors.
> make: *** [Makefile:248: __sub-make] Error 2 shuffle=3303159566
> make: Target 'prepare' not remade because of errors.
>
The problem is that I am referring to some cpu features (i.e. X86_FEATURE_XENPV)
to identify pv_ops. I need to abstract this per architecture. Will fix it.
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-14 21:34 ` Josh Poimboeuf
@ 2025-11-17 7:50 ` Alexandre Chartre
2025-11-17 9:42 ` David Laight
0 siblings, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-17 7:50 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: alexandre.chartre, linux-kernel, mingo, peterz
On 11/14/25 22:34, Josh Poimboeuf wrote:
> On Fri, Nov 14, 2025 at 10:56:48AM +0100, Alexandre Chartre wrote:
>>> Other than '!', the meaning of the flags isn't intuitive. Maybe it
>>> should just show the source code names:
>>>
>>> ALT_NOT(X86_FEATURE_FOO)
>>>
>>> ALT_DIRECT_CALL(X86_FEATURE_BAR)
>>>
>>> ALT_UNKNOWN_FLAG(X86_FEATURE_BAZ)
>>>
>>
>> I think '?' is meaningful too, but I wasn't sure about '+'.
>>
>> I am using single characters to keep the alternative name short. It can already
>> be fairly long because of the feature name (like "X86_FEATURE_SPEC_STORE_BYPASS_DISABLE")
>>
>> Also I am assuming that flags can be combined (although that's not currently
>> the case) so that would be more difficult with full ALT_* names and the
>> result would be much longer.
>
> Ok.
>
>>>> - If an alternative is a jump table then "JUMP" is used as the feature
>>>> name.
>>>
>>> Hm, it's a bit confusing to label a jump label as an "alternative" as
>>> those are two distinct things (though I'm aware that objtool conflates
>>> the two).
>>>
>>>> - If an alternative is an exception table then "EXCEPTION" is used as the
>>>> feature name.
>>>
>>> Ditto.
>>>
>>
>> Yes, the wording is not good, I use it just because objtool handles jump
>> labels and exception tables as alternative. I will reword to something
>> better.
>
> I meant not only the wording here, but also the "<alternative.def9>"
> labels shown in the disassembly.
>
Right, I will change it <alternative.xxx>, <jump_label.xxx>, <ex_table.xxx>.
>>>> Disassembly can show default alternative jumping to .altinstr_aux
>>>> -----------------------------------------------------------------
>>>> Disassembly can show a default alternative jumping to .altinstr_aux. This
>>>> happens when the _static_cpu_has() function is used. Its default code
>>>> jumps to .altinstr_aux where a test sequence is executed (test; jnz; jmp).
>>>>
>>>> At runtime, this sequence is not used because the _static_cpu_has()
>>>> an alternative with the X86_FEATURE_ALWAYS feature.
>>>>
>>>>
>>>> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 (X86_FEATURE_HYBRID_CPU) | jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS) # <alternative.debc>
>>>
>>> I'm finding this one-line format considerably more difficult to parse
>>> than the slightly longer two-line form:
>>>
>>> debc: perf_get_x86_pmu_capability+0xc <alternative.debc> | X86_FEATURE_HYBRID_CPU | X86_FEATURE_ALWAYS
>>> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> | NOP5 | jmpq 0x61a <perf_get_x86_pmu_capability+0x37>
>>
>>
>> Another option could be:
>>
>> debc: perf_get_x86_pmu_capability+0xc jmpq 0xdec1 <.altinstr_aux+0xfc> (<alternative.debc>) |
>> NOP5 (X86_FEATURE_HYBRID_CPU) |
>> jmpq 0x61a <perf_get_x86_pmu_capability+0x37> (X86_FEATURE_ALWAYS)
>>
>> I think I will use this option when displaying alternative one after the other,
>> and your suggestion when displaying side-by-side, and add an option to select
>> the display.
>
> Hm, but how would that "one after the other" display look for an
> alternative with multiple instructions? A "compact vs wide" option is
> ok, but within each of those I think it's helpful to use a consistent
> format regardless of whether the alternative has one or multiple
> instructions.
>
David raises the issue that a side-by-side display requires a large window.
The compact display could be like this:
Alternative with single instruction:
bb8: do_one_initcall+0x1a8 <alternative.bb8>
= callq *0x0(%rip) # 0xbbe <pv_ops+0xf8> (if default)
= sti (if !X86_FEATURE_XENPV)
= callq BUG_func (if +X86_FEATURE_ALWAYS)
Alternative with multiple instructions:
82e7: __switch_to_asm+0x27 <alternative.82e7>
= DEFAULT
82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52>
|
= !X86_FEATURE_ALWAYS
82e7: __switch_to_asm+0x27 | NOP1
82e8: __switch_to_asm+0x28 | NOP1
82e9: __switch_to_asm+0x29 | callq 0x82ef <__switch_to_asm+0x2f>
82ee: __switch_to_asm+0x2e | int3
82ef: __switch_to_asm+0x2f | add $0x8,%rsp
82f3: __switch_to_asm+0x33 | lfence
|
= X86_FEATURE_RSB_CTXSW
82e7: __switch_to_asm+0x27 | mov $0x10,%r12
82ee: __switch_to_asm+0x2e | callq 0x82f4 <__switch_to_asm+0x34>
82f3: __switch_to_asm+0x33 | int3
82f4: __switch_to_asm+0x34 | callq 0x82fa <__switch_to_asm+0x3a>
82f9: __switch_to_asm+0x39 | int3
82fa: __switch_to_asm+0x3a | add $0x10,%rsp
82fe: __switch_to_asm+0x3e | dec %r12
8301: __switch_to_asm+0x41 | jne 0x82ee <__switch_to_asm+0x2e>
8303: __switch_to_asm+0x43 | lfence
8306: __switch_to_asm+0x46 | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
|
>>> Also, I wonder if we can make NOP5 lowercase (nop5), since it really is
>>> just an instruction, not something special like a feature.
>>
>> This indicates that this is a pseudo instruction, NOP5 is actually nopl 0x00(%eax,%eax,1).
>> Even NOP1 can be a simple nop but also xchg %rax,%rax.
>
> But it still represents a single instruction... I view it as a readable
> shorthand rather than a pseudo instruction.
>
Ok. I will change to nop<n>.
>>>> 82e7: __switch_to_asm+0x27 | <alternative.82e7> | !X86_FEATURE_ALWAYS | X86_FEATURE_RSB_CTXSW
>>>
>>> Are the alternatives swapped? I believe this comes from the following
>>> code, so the !X86_FEATURE_ALWAYS column should be last?
>>>
>>> .macro FILL_RETURN_BUFFER reg:req nr:req ftr:req ftr2=ALT_NOT(X86_FEATURE_ALWAYS)
>>> ALTERNATIVE_2 "jmp .Lskip_rsb_\@", \
>>> __stringify(__FILL_RETURN_BUFFER(\reg,\nr)), \ftr, \
>>> __stringify(nop;nop;__FILL_ONE_RETURN), \ftr2
>>>
>>> .Lskip_rsb_\@:
>>> .endm
>>
>> I will check but I process/print alternative in the order provided by
>> objtool (in struct alternative)
>
> It's quite possible objtool has them in reverse order. The order
> shouldn't matter for objtool validate_branch(), but definitely matters
> here.
>
That's probably the issue. I think objtool reads alternatives in the right
order but it adds each of them at the beginning of the same list. So they
end up in reverse order in the list.
Thanks,
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 7:50 ` Alexandre Chartre
@ 2025-11-17 9:42 ` David Laight
2025-11-17 9:47 ` Alexandre Chartre
0 siblings, 1 reply; 49+ messages in thread
From: David Laight @ 2025-11-17 9:42 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: Josh Poimboeuf, linux-kernel, mingo, peterz
On Mon, 17 Nov 2025 08:50:45 +0100
Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> On 11/14/25 22:34, Josh Poimboeuf wrote:
...
> David raises the issue that a side-by-side display requires a large window.
>
> The compact display could be like this:
>
> Alternative with single instruction:
>
> bb8: do_one_initcall+0x1a8 <alternative.bb8>
> = callq *0x0(%rip) # 0xbbe <pv_ops+0xf8> (if default)
> = sti (if !X86_FEATURE_XENPV)
> = callq BUG_func (if +X86_FEATURE_ALWAYS)
>
> Alternative with multiple instructions:
>
> 82e7: __switch_to_asm+0x27 <alternative.82e7>
> = DEFAULT
> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52>
> |
> = !X86_FEATURE_ALWAYS
> 82e7: __switch_to_asm+0x27 | NOP1
> 82e8: __switch_to_asm+0x28 | NOP1
> 82e9: __switch_to_asm+0x29 | callq 0x82ef <__switch_to_asm+0x2f>
> 82ee: __switch_to_asm+0x2e | int3
> 82ef: __switch_to_asm+0x2f | add $0x8,%rsp
> 82f3: __switch_to_asm+0x33 | lfence
> |
> = X86_FEATURE_RSB_CTXSW
> 82e7: __switch_to_asm+0x27 | mov $0x10,%r12
> 82ee: __switch_to_asm+0x2e | callq 0x82f4 <__switch_to_asm+0x34>
> 82f3: __switch_to_asm+0x33 | int3
> 82f4: __switch_to_asm+0x34 | callq 0x82fa <__switch_to_asm+0x3a>
> 82f9: __switch_to_asm+0x39 | int3
> 82fa: __switch_to_asm+0x3a | add $0x10,%rsp
> 82fe: __switch_to_asm+0x3e | dec %r12
> 8301: __switch_to_asm+0x41 | jne 0x82ee <__switch_to_asm+0x2e>
> 8303: __switch_to_asm+0x43 | lfence
> 8306: __switch_to_asm+0x46 | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
That does looks better.
Although I think there ought to be some indication of the 31 NOP bytes
at the end of the middle alternative.
I'd also decode those callq as 'callq .+6' - not sure what other people think?
It is rather specific to that code.
David
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 9:42 ` David Laight
@ 2025-11-17 9:47 ` Alexandre Chartre
2025-11-17 12:37 ` David Laight
0 siblings, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-17 9:47 UTC (permalink / raw)
To: David Laight
Cc: alexandre.chartre, Josh Poimboeuf, linux-kernel, mingo, peterz
On 11/17/25 10:42, David Laight wrote:
> On Mon, 17 Nov 2025 08:50:45 +0100
> Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
>
>> On 11/14/25 22:34, Josh Poimboeuf wrote:
> ...
>> David raises the issue that a side-by-side display requires a large window.
>>
>> The compact display could be like this:
>>
>> Alternative with single instruction:
>>
>> bb8: do_one_initcall+0x1a8 <alternative.bb8>
>> = callq *0x0(%rip) # 0xbbe <pv_ops+0xf8> (if default)
>> = sti (if !X86_FEATURE_XENPV)
>> = callq BUG_func (if +X86_FEATURE_ALWAYS)
>>
>> Alternative with multiple instructions:
>>
>> 82e7: __switch_to_asm+0x27 <alternative.82e7>
>> = DEFAULT
>> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52>
>> |
>> = !X86_FEATURE_ALWAYS
>> 82e7: __switch_to_asm+0x27 | NOP1
>> 82e8: __switch_to_asm+0x28 | NOP1
>> 82e9: __switch_to_asm+0x29 | callq 0x82ef <__switch_to_asm+0x2f>
>> 82ee: __switch_to_asm+0x2e | int3
>> 82ef: __switch_to_asm+0x2f | add $0x8,%rsp
>> 82f3: __switch_to_asm+0x33 | lfence
>> |
>> = X86_FEATURE_RSB_CTXSW
>> 82e7: __switch_to_asm+0x27 | mov $0x10,%r12
>> 82ee: __switch_to_asm+0x2e | callq 0x82f4 <__switch_to_asm+0x34>
>> 82f3: __switch_to_asm+0x33 | int3
>> 82f4: __switch_to_asm+0x34 | callq 0x82fa <__switch_to_asm+0x3a>
>> 82f9: __switch_to_asm+0x39 | int3
>> 82fa: __switch_to_asm+0x3a | add $0x10,%rsp
>> 82fe: __switch_to_asm+0x3e | dec %r12
>> 8301: __switch_to_asm+0x41 | jne 0x82ee <__switch_to_asm+0x2e>
>> 8303: __switch_to_asm+0x43 | lfence
>> 8306: __switch_to_asm+0x46 | movq $0xffffffffffffffff,%gs:0x0(%rip) # 0x20b <__x86_call_depth>
>
> That does looks better.
> Although I think there ought to be some indication of the 31 NOP bytes
> at the end of the middle alternative.
I am now compacting the code by removing all trailing NOPs. I should probably
improve that with printing the actual number of NOPs, for example NOP31 (or nop31)
> I'd also decode those callq as 'callq .+6' - not sure what other people think?
> It is rather specific to that code.
This is done by libopcodes. I will need to check if there is an option to display
the branch distance instead of the branch target.
Thanks,
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 9:47 ` Alexandre Chartre
@ 2025-11-17 12:37 ` David Laight
2025-11-17 13:11 ` Alexandre Chartre
0 siblings, 1 reply; 49+ messages in thread
From: David Laight @ 2025-11-17 12:37 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: Josh Poimboeuf, linux-kernel, mingo, peterz
On Mon, 17 Nov 2025 10:47:06 +0100
Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> On 11/17/25 10:42, David Laight wrote:
...
> > Although I think there ought to be some indication of the 31 NOP bytes
> > at the end of the middle alternative.
>
> I am now compacting the code by removing all trailing NOPs. I should probably
> improve that with printing the actual number of NOPs, for example NOP31 (or nop31)
That is the sort of thing I was thinking of.
Perhaps the actual opcodes on one line - eg: NOP5; NOP5; NOP5; NOP1
> > I'd also decode those callq as 'callq .+6' - not sure what other people think?
> > It is rather specific to that code.
>
> This is done by libopcodes. I will need to check if there is an option to display
> the branch distance instead of the branch target.
The 'problem' is that mostly you want the branch target - except when it is small.
Then you don't need both 'address' and 'symbol+offset', and it is quicker to find
the target by looking at the branch distance.
I'm not sure how you'd please everyone :-)
I'm sure one of the disassemblers ends up giving you the target address in a form
that isn't on the instruction line!
I've definitely counted opcode bytes to find the target.
David
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 12:37 ` David Laight
@ 2025-11-17 13:11 ` Alexandre Chartre
2025-11-17 22:09 ` David Laight
0 siblings, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-17 13:11 UTC (permalink / raw)
To: David Laight
Cc: alexandre.chartre, Josh Poimboeuf, linux-kernel, mingo, peterz
On 11/17/25 13:37, David Laight wrote:
> On Mon, 17 Nov 2025 10:47:06 +0100
> Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
>
>> On 11/17/25 10:42, David Laight wrote:
> ...
>>> Although I think there ought to be some indication of the 31 NOP bytes
>>> at the end of the middle alternative.
>>
>> I am now compacting the code by removing all trailing NOPs. I should probably
>> improve that with printing the actual number of NOPs, for example NOP31 (or nop31)
>
> That is the sort of thing I was thinking of.
> Perhaps the actual opcodes on one line - eg: NOP5; NOP5; NOP5; NOP1
That might not always be very compact. For example __switch_to_asm() has 41 NOP1.
I will use NOP<n> for now, and we can improve later.
>>> I'd also decode those callq as 'callq .+6' - not sure what other people think?
>>> It is rather specific to that code.
>>
>> This is done by libopcodes. I will need to check if there is an option to display
>> the branch distance instead of the branch target.
>
> The 'problem' is that mostly you want the branch target - except when it is small.
> Then you don't need both 'address' and 'symbol+offset', and it is quicker to find
> the target by looking at the branch distance.
> I'm not sure how you'd please everyone :-)
>
> I'm sure one of the disassemblers ends up giving you the target address in a form
> that isn't on the instruction line!
> I've definitely counted opcode bytes to find the target.
>
I will investigate this for a later patch. Maybe have both the distance and the
destination (could be an option?).
Thanks,
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 13:11 ` Alexandre Chartre
@ 2025-11-17 22:09 ` David Laight
2025-11-17 22:38 ` Josh Poimboeuf
2025-11-18 7:19 ` Alexandre Chartre
0 siblings, 2 replies; 49+ messages in thread
From: David Laight @ 2025-11-17 22:09 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: Josh Poimboeuf, linux-kernel, mingo, peterz
On Mon, 17 Nov 2025 14:11:55 +0100
Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> On 11/17/25 13:37, David Laight wrote:
> > On Mon, 17 Nov 2025 10:47:06 +0100
> > Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> >
> >> On 11/17/25 10:42, David Laight wrote:
> > ...
> >>> Although I think there ought to be some indication of the 31 NOP bytes
> >>> at the end of the middle alternative.
> >>
> >> I am now compacting the code by removing all trailing NOPs. I should probably
> >> improve that with printing the actual number of NOPs, for example NOP31 (or nop31)
> >
> > That is the sort of thing I was thinking of.
> > Perhaps the actual opcodes on one line - eg: NOP5; NOP5; NOP5; NOP1
>
> That might not always be very compact. For example __switch_to_asm() has 41 NOP1.
> I will use NOP<n> for now, and we can improve later.
Could you use NOP1*41 (etc) so that NOP5*4 is different from NOP1*20?
(I'm guessing you hand-decode the standard NOP sequences already?)
Hmm... you don't want to execute that many 0x90 bytes.
I think that case might have had a jump around them.
Do I remember something about the trailing nop being merged?
Perhaps that is the kernel patching code.
Something made me think objtool might (also) be doing it.
David
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 22:09 ` David Laight
@ 2025-11-17 22:38 ` Josh Poimboeuf
2025-11-18 9:58 ` David Laight
2025-11-18 7:19 ` Alexandre Chartre
1 sibling, 1 reply; 49+ messages in thread
From: Josh Poimboeuf @ 2025-11-17 22:38 UTC (permalink / raw)
To: David Laight; +Cc: Alexandre Chartre, linux-kernel, mingo, peterz
On Mon, Nov 17, 2025 at 10:09:53PM +0000, David Laight wrote:
> On Mon, 17 Nov 2025 14:11:55 +0100
> Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
>
> > On 11/17/25 13:37, David Laight wrote:
> > > On Mon, 17 Nov 2025 10:47:06 +0100
> > > Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> > >
> > >> On 11/17/25 10:42, David Laight wrote:
> > > ...
> > >>> Although I think there ought to be some indication of the 31 NOP bytes
> > >>> at the end of the middle alternative.
I'm not sure we need that. It's already implied those gaps will be
filled with NOPs. This could add unnecessary visual clutter.
> > >>
> > >> I am now compacting the code by removing all trailing NOPs. I should probably
> > >> improve that with printing the actual number of NOPs, for example NOP31 (or nop31)
> > >
> > > That is the sort of thing I was thinking of.
> > > Perhaps the actual opcodes on one line - eg: NOP5; NOP5; NOP5; NOP1
> >
> > That might not always be very compact. For example __switch_to_asm() has 41 NOP1.
> > I will use NOP<n> for now, and we can improve later.
>
> Could you use NOP1*41 (etc) so that NOP5*4 is different from NOP1*20?
> (I'm guessing you hand-decode the standard NOP sequences already?)
>
> Hmm... you don't want to execute that many 0x90 bytes.
> I think that case might have had a jump around them.
>
> Do I remember something about the trailing nop being merged?
> Perhaps that is the kernel patching code.
> Something made me think objtool might (also) be doing it.
Yes, IIRC, the alternatives code merges the small NOPs into bigger ones.
--
Josh
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 22:09 ` David Laight
2025-11-17 22:38 ` Josh Poimboeuf
@ 2025-11-18 7:19 ` Alexandre Chartre
2025-11-18 9:12 ` Peter Zijlstra
1 sibling, 1 reply; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-18 7:19 UTC (permalink / raw)
To: David Laight
Cc: alexandre.chartre, Josh Poimboeuf, linux-kernel, mingo, peterz
On 11/17/25 23:09, David Laight wrote:
> On Mon, 17 Nov 2025 14:11:55 +0100
> Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
>
>> On 11/17/25 13:37, David Laight wrote:
>>> On Mon, 17 Nov 2025 10:47:06 +0100
>>> Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
>>>
>>>> On 11/17/25 10:42, David Laight wrote:
>>> ...
>>>>> Although I think there ought to be some indication of the 31 NOP bytes
>>>>> at the end of the middle alternative.
>>>>
>>>> I am now compacting the code by removing all trailing NOPs. I should probably
>>>> improve that with printing the actual number of NOPs, for example NOP31 (or nop31)
>>>
>>> That is the sort of thing I was thinking of.
>>> Perhaps the actual opcodes on one line - eg: NOP5; NOP5; NOP5; NOP1
>>
>> That might not always be very compact. For example __switch_to_asm() has 41 NOP1.
>> I will use NOP<n> for now, and we can improve later.
>
> Could you use NOP1*41 (etc) so that NOP5*4 is different from NOP1*20?
> (I'm guessing you hand-decode the standard NOP sequences already?)
Yes, objtool already identifies standard NOP sequences.
> Hmm... you don't want to execute that many 0x90 bytes.
> I think that case might have had a jump around them.
In that specific case, they are not executed, they are after a jump:
82e7: __switch_to_asm+0x27 <alternative.82e7>
= DEFAULT
82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52>
82e9: __switch_to_asm+0x29 | NOP41
|
alex.
> Do I remember something about the trailing nop being merged?
> Perhaps that is the kernel patching code.
> Something made me think objtool might (also) be doing it.
>
> David
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-18 7:19 ` Alexandre Chartre
@ 2025-11-18 9:12 ` Peter Zijlstra
2025-11-18 11:39 ` Alexandre Chartre
0 siblings, 1 reply; 49+ messages in thread
From: Peter Zijlstra @ 2025-11-18 9:12 UTC (permalink / raw)
To: Alexandre Chartre; +Cc: David Laight, Josh Poimboeuf, linux-kernel, mingo
On Tue, Nov 18, 2025 at 08:19:14AM +0100, Alexandre Chartre wrote:
> In that specific case, they are not executed, they are after a jump:
>
> 82e7: __switch_to_asm+0x27 <alternative.82e7>
> = DEFAULT
> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52>
> 82e9: __switch_to_asm+0x29 | NOP41
nop41 is a bit naf since x86 can only have 15 byte instructions :-)
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-17 22:38 ` Josh Poimboeuf
@ 2025-11-18 9:58 ` David Laight
0 siblings, 0 replies; 49+ messages in thread
From: David Laight @ 2025-11-18 9:58 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: Alexandre Chartre, linux-kernel, mingo, peterz
On Mon, 17 Nov 2025 14:38:49 -0800
Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> On Mon, Nov 17, 2025 at 10:09:53PM +0000, David Laight wrote:
> > On Mon, 17 Nov 2025 14:11:55 +0100
> > Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> >
> > > On 11/17/25 13:37, David Laight wrote:
> > > > On Mon, 17 Nov 2025 10:47:06 +0100
> > > > Alexandre Chartre <alexandre.chartre@oracle.com> wrote:
> > > >
> > > >> On 11/17/25 10:42, David Laight wrote:
> > > > ...
> > > >>> Although I think there ought to be some indication of the 31 NOP bytes
> > > >>> at the end of the middle alternative.
>
> I'm not sure we need that. It's already implied those gaps will be
> filled with NOPs. This could add unnecessary visual clutter.
But you need some idea of the size of the gap.
A large gap isn't really a good idea and may mean it is better to
refactor the code.
While the execution time of nops might be zero, there is still
the cost of fetching and decoding them.
...
> > Do I remember something about the trailing nop being merged?
> > Perhaps that is the kernel patching code.
> > Something made me think objtool might (also) be doing it.
>
> Yes, IIRC, the alternatives code merges the small NOPs into bigger ones.
That probably means it doesn't matter how objdump displays them.
But perhaps it ought to output NOP*5 rather than NOP5 to make it
clear it is a block of NOP (that will converted later) rather
than a single NOP5 instruction.
David
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH v4 00/28] objtool: Function validation tracing
2025-11-18 9:12 ` Peter Zijlstra
@ 2025-11-18 11:39 ` Alexandre Chartre
0 siblings, 0 replies; 49+ messages in thread
From: Alexandre Chartre @ 2025-11-18 11:39 UTC (permalink / raw)
To: Peter Zijlstra
Cc: alexandre.chartre, David Laight, Josh Poimboeuf, linux-kernel,
mingo
On 11/18/25 10:12, Peter Zijlstra wrote:
> On Tue, Nov 18, 2025 at 08:19:14AM +0100, Alexandre Chartre wrote:
>
>> In that specific case, they are not executed, they are after a jump:
>>
>> 82e7: __switch_to_asm+0x27 <alternative.82e7>
>> = DEFAULT
>> 82e7: __switch_to_asm+0x27 | jmp 0x8312 <__switch_to_asm+0x52>
>> 82e9: __switch_to_asm+0x29 | NOP41
>
> nop41 is a bit naf since x86 can only have 15 byte instructions :-)
Yes, "nop1 x41" would be better for this specific case. But I think
I will just drop it for now as Josh suggested.
alex.
^ permalink raw reply [flat|nested] 49+ messages in thread
end of thread, other threads:[~2025-11-18 11:40 UTC | newest]
Thread overview: 49+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-13 16:48 [PATCH v4 00/28] objtool: Function validation tracing Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 01/28] objtool: Move disassembly functions to a separated file Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 02/28] objtool: Create disassembly context Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 03/28] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 04/28] tool build: Remove annoying newline in build output Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 05/28] objtool: Print symbol during disassembly Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 06/28] objtool: Store instruction disassembly result Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 07/28] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 08/28] objtool: Extract code to validate instruction from the validate branch loop Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 09/28] objtool: Record symbol name max length Alexandre Chartre
2025-11-13 16:48 ` [PATCH v4 10/28] objtool: Add option to trace function validation Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 11/28] objtool: Trace instruction state changes during " Alexandre Chartre
2025-11-14 21:21 ` Josh Poimboeuf
2025-11-17 7:33 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 12/28] objtool: Improve register reporting " Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 13/28] objtool: Identify the different types of alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 14/28] objtool: Improve tracing of alternative instructions Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 15/28] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 16/28] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 17/28] objtool: Print headers for alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 18/28] objtool: Disassemble group alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 19/28] objtool: Print addresses with alternative instructions Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 20/28] objtool: Disassemble exception table alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 21/28] objtool: Disassemble jump " Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 22/28] objtool: Fix address references in alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 23/28] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 24/28] objtool: Function to get the name of a CPU feature Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 25/28] objtool: Improve naming of group alternatives Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 26/28] objtool: Get the destination name of a PV call Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 27/28] objtool: Improve the disassembly of the pv_ops call Alexandre Chartre
2025-11-15 1:34 ` kernel test robot
2025-11-17 7:37 ` Alexandre Chartre
2025-11-13 16:49 ` [PATCH v4 28/28] objtool: Print single line for alternatives with one instruction Alexandre Chartre
2025-11-13 19:55 ` [PATCH v4 00/28] objtool: Function validation tracing David Laight
2025-11-14 8:53 ` Alexandre Chartre
2025-11-14 1:48 ` Josh Poimboeuf
2025-11-14 9:56 ` Alexandre Chartre
2025-11-14 21:34 ` Josh Poimboeuf
2025-11-17 7:50 ` Alexandre Chartre
2025-11-17 9:42 ` David Laight
2025-11-17 9:47 ` Alexandre Chartre
2025-11-17 12:37 ` David Laight
2025-11-17 13:11 ` Alexandre Chartre
2025-11-17 22:09 ` David Laight
2025-11-17 22:38 ` Josh Poimboeuf
2025-11-18 9:58 ` David Laight
2025-11-18 7:19 ` Alexandre Chartre
2025-11-18 9:12 ` Peter Zijlstra
2025-11-18 11:39 ` Alexandre Chartre
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox