public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 00/30] objtool: Function validation tracing
@ 2025-11-19 14:32 Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 01/30] objtool: Move disassembly functions to a separated file Alexandre Chartre
                   ` (30 more replies)
  0 siblings, 31 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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

Patches are now based on tip/master.

I am deferring the following changes to future patches:
- Josh: convert --disas option to subcommand
- David: provide branch distance for small branches

Thanks,

alex.

-----

Changes:
========

V5: 
---
- patches are now based on tip/master
- remove the resolution of direct/PV calls
  (added in V4 but this needs more work)
- Josh: fix rname
- Josh: change names for jump tables and exception tables
- Josh: display header line for single-line alternatives
- Josh: make NOP<n> lowercase
- Josh: fix alternatives order
- Josh: trim trailing NOPs
- David: indicate the number of trailing NOPs (nop*<n>)
- David: provide compact output for alternatives disassembly.
  The compact output is now the default, and there is a --wide
  option to provide a wide output where alternatives are displayed
  side-by-side.

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. XXX option?

- 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)

- A jump table is displayed the same way as an alternative, with the default
  branch (or not) instruction, and the corresponding substitute instruction.
  It is identified with the "JUMP" name.

- An exception table is displayed the same way as an alternative, with the
  default instruction (which can cause an exception), and a "resume at <desc>"
  string which indicates where the execution resumes if there is an exception.
  It is identified with the "EXCEPTION" name.

- An exception table can be present for an instruction which also has an
  alternative. In that case, the exception table is displayed similarly
  as the different group alternatives for this instruction.

- 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. **REMOVED IN V5**

- If a group alternative is a direct call then print the corresponding
  pv_ops call. **REMOVED IN V5**


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
 59be0:  os_xsave+0x0                  push   %r12                                          - state: cfa=rsp+16 r12=(cfa-16) stack_size=16 
 59be2:  os_xsave+0x2		       mov    0x0(%rip),%eax        # 0x59be8 <alternatives_patched>
 59be8:  os_xsave+0x8		       push   %rbp                                          - state: cfa=rsp+24 rbp=(cfa-24) stack_size=24 
 59be9:  os_xsave+0x9		       mov    %rdi,%rbp                                          
 59bec:  os_xsave+0xc		       push   %rbx					     - state: cfa=rsp+32 rbx=(cfa-32) stack_size=32 
 59bed:  os_xsave+0xd		       mov    0x8(%rdi),%rbx                                     
 59bf1:  os_xsave+0x11		       mov    %rbx,%r12                                          
 59bf4:  os_xsave+0x14		       shr    $0x20,%r12                                         
 59bf8:  os_xsave+0x18		       test   %eax,%eax                                          
 59bfa:  os_xsave+0x1a		       je     0x59c22 <os_xsave+0x42>                        - jump taken
 59c22:  os_xsave+0x42		       | ud2                                                     
 59c24:  os_xsave+0x44		       | jmp    0x59bfc <os_xsave+0x1c>                      - unconditional jump
 59bfc:  os_xsave+0x1c		       | | xor    %edx,%edx                                      
 59bfe:  os_xsave+0x1e		       | | mov    %rbx,%rsi                                      
 59c01:  os_xsave+0x21		       | | mov    %rbp,%rdi                                      
 59c04:  os_xsave+0x24		       | | callq  0x59c09 <xfd_validate_state>               - call
 59c09:  os_xsave+0x29		       | | mov    %ebx,%eax                                      
 59c0b:  os_xsave+0x2b		       | | mov    %r12d,%edx                                     
 	 			       | | / <alternative.59c0e> X86_FEATURE_XSAVEOPT
  1b29:  .altinstr_replacement+0x1b29  | | | xsaveopt64 0x40(%rbp)                               
 59c13:  os_xsave+0x33		       | | | xor    %ebx,%ebx                                    
 59c15:  os_xsave+0x35		       | | | test   %ebx,%ebx                                    
 59c17:  os_xsave+0x37		       | | | jne    0x59c26 <os_xsave+0x46>                  - jump taken
 59c26:  os_xsave+0x46		       | | | | ud2                                               
 59c28:  os_xsave+0x48		       | | | | pop    %rbx                                   - state: cfa=rsp+24 rbx=<undef> stack_size=24 
 59c29:  os_xsave+0x49		       | | | | pop    %rbp				     - state: cfa=rsp+16 rbp=<undef> stack_size=16 
 59c2a:  os_xsave+0x4a		       | | | | pop    %r12				     - state: cfa=rsp+8 r12=<undef> stack_size=8 
 59c2c:  os_xsave+0x4c		       | | | | jmpq   0x59c31 <__x86_return_thunk>	     - return
 59c17:  os_xsave+0x37		       | | | jne    0x59c26 <os_xsave+0x46>		     - jump not taken
 59c19:  os_xsave+0x39		       | | | pop    %rbx    				     - state: cfa=rsp+24 rbx=<undef> stack_size=24 
 59c1a:  os_xsave+0x3a		       | | | pop    %rbp				     - state: cfa=rsp+16 rbp=<undef> stack_size=16 
 59c1b:  os_xsave+0x3b		       | | | pop    %r12				     - state: cfa=rsp+8 r12=<undef> stack_size=8 
 59c1d:  os_xsave+0x3d		       | | | jmpq   0x59c22 <__x86_return_thunk>	     - return
 	 			       | | \ <alternative.59c0e> X86_FEATURE_XSAVEOPT
				       | | / <alternative.59c0e> X86_FEATURE_XSAVEC
  1b2e:  .altinstr_replacement+0x1b2e  | | | xsavec64 0x40(%rbp)                                 
 59c13:  os_xsave+0x33		       | | | xor    %ebx,%ebx                                - already visited
 	 			       | | \ <alternative.59c0e> X86_FEATURE_XSAVEC
				       | | / <alternative.59c0e> X86_FEATURE_XSAVES
  1b33:  .altinstr_replacement+0x1b33  | | | xsaves64 0x40(%rbp)                                 
 59c13:  os_xsave+0x33		       | | | xor    %ebx,%ebx                                - already visited
 	 			       | | \ <alternative.59c0e> X86_FEATURE_XSAVES
				       | | / <alternative.59c0e> EXCEPTION for instruction at 0x59c0e <os_xsave+0x2e>
 59c15:  os_xsave+0x35		       | | | test   %ebx,%ebx                                - already visited
 	 			       | | \ <alternative.59c0e> EXCEPTION
				       | | / <alternative.59c0e> DEFAULT
 59c0e:  os_xsave+0x2e		       | | xsave64 0x40(%rbp)                                    
 59c13:  os_xsave+0x33		       | | xor    %ebx,%ebx                                  - already visited
 59bfa:  os_xsave+0x1a		       je     0x59c22 <os_xsave+0x42>                        - jump not taken
 59bfc:  os_xsave+0x1c		       xor    %edx,%edx                                      - already visited
os_xsave: validation end


Example 2 (--disas option): Single Instruction Alternatives
-----------------------------------------------------------

Compact Output (default):

Alternatives with single instructions are displayed each on one line,
with the instruction and a description of the alternative.

$ ./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     <alternative.debc>
  	 				     = jmpq   0xdec1 <.altinstr_aux+0xfc>                 (if DEFAULT)
					     = jmpq   0x622 <perf_get_x86_pmu_capability+0x37>    (if X86_FEATURE_ALWAYS)
					     = nop5                                               (if X86_FEATURE_HYBRID_CPU)
  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    <jump_table.def9>
  	 				     = nop2                                              (if DEFAULT)
					     = jmp    defb <perf_get_x86_pmu_capability+0x4b>    (if JUMP)
  defb:  perf_get_x86_pmu_capability+0x4b    mov    0x0(%rip),%rdi        # 0xdf02 <x86_pmu+0xd8>      
  df02:  perf_get_x86_pmu_capability+0x52    <alternative.df02>
  	 				     = callq  0xdf07 <__sw_hweight64>    (if DEFAULT)
					     = popcnt %rdi,%rax                  (if X86_FEATURE_POPCNT)
  df07:  perf_get_x86_pmu_capability+0x57    mov    %eax,0x4(%rdx)                                     
  df0a:  perf_get_x86_pmu_capability+0x5a    <jump_table.df0a>
  	 				     = nop2                                              (if DEFAULT)
					     = jmp    df0c <perf_get_x86_pmu_capability+0x5c>    (if JUMP)
  df0c:  perf_get_x86_pmu_capability+0x5c    mov    0x0(%rip),%rdi        # 0xdf13 <x86_pmu+0xe0>      
  df13:  perf_get_x86_pmu_capability+0x63    <alternative.df13>
  	 				     = callq  0xdf18 <__sw_hweight64>    (if DEFAULT)
					     = popcnt %rdi,%rax                  (if X86_FEATURE_POPCNT)
  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>                        


Wide Output (--wide option):

Alternatives with single instructions are displayed side-by-side,
with an header.

$ ./tools/objtool/objtool --disas=perf_get_x86_pmu_capability --wide --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     | <alternative.debc>                 | X86_FEATURE_ALWAYS                              | X86_FEATURE_HYBRID_CPU 
  debc:  perf_get_x86_pmu_capability+0xc     | jmpq   0xdec1 <.altinstr_aux+0xfc> | jmpq   0x622 <perf_get_x86_pmu_capability+0x37> | nop5                   
  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    | <jump_table.def9> | JUMP                                           
  def9:  perf_get_x86_pmu_capability+0x49    | nop2              | jmp    defb <perf_get_x86_pmu_capability+0x4b> 
  defb:  perf_get_x86_pmu_capability+0x4b      mov    0x0(%rip),%rdi        # 0xdf02 <x86_pmu+0xd8>      
  df02:  perf_get_x86_pmu_capability+0x52    | <alternative.df02>             | X86_FEATURE_POPCNT 
  df02:  perf_get_x86_pmu_capability+0x52    | callq  0xdf07 <__sw_hweight64> | popcnt %rdi,%rax   
  df07:  perf_get_x86_pmu_capability+0x57      mov    %eax,0x4(%rdx)                                     
  df0a:  perf_get_x86_pmu_capability+0x5a    | <jump_table.df0a> | JUMP                                           
  df0a:  perf_get_x86_pmu_capability+0x5a    | nop2              | jmp    df0c <perf_get_x86_pmu_capability+0x5c> 
  df0c:  perf_get_x86_pmu_capability+0x5c      mov    0x0(%rip),%rdi        # 0xdf13 <x86_pmu+0xe0>      
  df13:  perf_get_x86_pmu_capability+0x63    | <alternative.df13>             | X86_FEATURE_POPCNT 
  df13:  perf_get_x86_pmu_capability+0x63    | callq  0xdf18 <__sw_hweight64> | popcnt %rdi,%rax   
  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
-------------------------------------------------------------------

Compact Output (default):

Alternatives with multiple instructions are displayed one above the other,
with an header describing 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>
  	 			   = DEFAULT
  82e7:  __switch_to_asm+0x27	   | jmp    0x8312 <__switch_to_asm+0x52>
  82e9:  __switch_to_asm+0x29	   | nop*41
  	 			   |
				   = 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>
  	 			   |
				   = !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 
  	 			   |
  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>                               


Wide Output (--wide option):

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 --wide  --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_RSB_CTXSW                                               | !X86_FEATURE_ALWAYS
  82e7:  __switch_to_asm+0x27	 | jmp    0x8312 <__switch_to_asm+0x52> | mov    $0x10,%r12						      | nop1
  82e8:  __switch_to_asm+0x28	 |                                      | 	 							      | nop1
  82e9:  __switch_to_asm+0x29	 | nop*41                               |                                                                     | callq  0x82ef <__switch_to_asm+0x2f>
  82ee:  __switch_to_asm+0x2e	 |                                      | callq  0x82f4 <__switch_to_asm+0x34>                                | int3
  82ef:  __switch_to_asm+0x2f	 |                                      |                                                                     | add    $0x8,%rsp
  82f3:  __switch_to_asm+0x33	 |                                      | int3                                                                | lfence
  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> |
  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>
  
-----

Alexandre Chartre (30):
  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: Add functions to better name 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: Preserve alternatives order
  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: Compact output for alternatives with one instruction
  objtool: Add wide output for disassembly
  objtool: Trim trailing NOPs in alternative

 .../x86/tools/gen-cpu-feature-names-x86.awk   |   33 +
 tools/build/Makefile.feature                  |    4 +-
 tools/objtool/Build                           |    3 +
 tools/objtool/Makefile                        |   24 +
 tools/objtool/arch/loongarch/decode.c         |   23 +
 tools/objtool/arch/loongarch/special.c        |    5 +
 tools/objtool/arch/powerpc/decode.c           |   24 +
 tools/objtool/arch/powerpc/special.c          |    5 +
 tools/objtool/arch/x86/Build                  |    8 +
 tools/objtool/arch/x86/decode.c               |   20 +
 tools/objtool/arch/x86/special.c              |   10 +
 tools/objtool/builtin-check.c                 |    4 +
 tools/objtool/check.c                         |  648 +++++----
 tools/objtool/disas.c                         | 1245 +++++++++++++++++
 tools/objtool/include/objtool/arch.h          |   11 +
 tools/objtool/include/objtool/builtin.h       |    3 +
 tools/objtool/include/objtool/check.h         |   35 +-
 tools/objtool/include/objtool/disas.h         |   81 ++
 tools/objtool/include/objtool/special.h       |    4 +-
 tools/objtool/include/objtool/trace.h         |  141 ++
 tools/objtool/include/objtool/warn.h          |   17 +-
 tools/objtool/special.c                       |    2 +
 tools/objtool/trace.c                         |  203 +++
 23 files changed, 2254 insertions(+), 299 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] 35+ messages in thread

* [PATCH v5 01/30] objtool: Move disassembly functions to a separated file
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 02/30] objtool: Create disassembly context Alexandre Chartre
                   ` (29 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 8cd71b9a5eef1..17e50a1766d0e 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 orc_dump.o
 objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 72c7f6f03350b..6a5b06052dd22 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4783,87 +4783,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->elf, 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..3a7cb1b8002ec
--- /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->elf, 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 f7051bbe0bcb2..35f926cf9c254 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -49,4 +49,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] 35+ messages in thread

* [PATCH v5 02/30] objtool: Create disassembly context
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 01/30] objtool: Move disassembly functions to a separated file Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 03/30] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
                   ` (28 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 6a5b06052dd22..5083e47eef08d 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -12,6 +12,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>
@@ -4854,6 +4855,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);
@@ -4997,7 +4999,9 @@ int check(struct objtool_file *file)
 	if (opts.verbose) {
 		if (opts.werror && warnings)
 			WARN("%d warning(s) upgraded to errors", warnings);
-		disas_warned_funcs(file);
+		disas_ctx = disas_context_create(file);
+		disas_warned_funcs(disas_ctx);
+		disas_context_destroy(disas_ctx);
 	}
 
 	if (opts.backup && make_backup())
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 3a7cb1b8002ec..7a18e51d43e62 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->elf, sym) {
+	if (!dctx)
+		return;
+
+	for_each_sym(dctx->file->elf, 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 35f926cf9c254..f7051bbe0bcb2 100644
--- a/tools/objtool/include/objtool/objtool.h
+++ b/tools/objtool/include/objtool/objtool.h
@@ -49,6 +49,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] 35+ messages in thread

* [PATCH v5 03/30] objtool: Disassemble code with libopcodes instead of running objdump
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 01/30] objtool: Move disassembly functions to a separated file Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 02/30] objtool: Create disassembly context Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 04/30] tool build: Remove annoying newline in build output Alexandre Chartre
                   ` (27 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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                |  23 ++++
 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                 |  14 +-
 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, 234 insertions(+), 72 deletions(-)

diff --git a/tools/objtool/Build b/tools/objtool/Build
index 17e50a1766d0e..9d1e8f28ef953 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 orc_dump.o
 objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 021f55b7bd872..fdc3d32dc79e7 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -70,6 +70,29 @@ OBJTOOL_CFLAGS += $(if $(elfshdr),,-DLIBELF_USE_DEPRECATED)
 # Always want host compilation.
 HOST_OVERRIDES := CC="$(HOSTCC)" LD="$(HOSTLD)" AR="$(HOSTAR)"
 
+#
+# 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
+
+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
+
+export BUILD_DISAS
+
 AWK = awk
 MKDIR = mkdir
 
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index 0115b97c526b8..1de86ebb637d5 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 3a9b748216edc..4f68b402e7855 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>
@@ -127,3 +128,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 cc85db7b65a41..83e9c604ce105 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>
@@ -949,3 +950,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 5083e47eef08d..de156e91ee8b7 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4980,8 +4980,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);
@@ -4990,8 +4988,10 @@ int check(struct objtool_file *file)
 	}
 
 out:
-	if (!ret && !warnings)
+	if (!ret && !warnings) {
+		free_insns(file);
 		return 0;
+	}
 
 	if (opts.werror && warnings)
 		ret = 1;
@@ -5000,10 +5000,14 @@ int check(struct objtool_file *file)
 		if (opts.werror && warnings)
 			WARN("%d warning(s) upgraded to errors", warnings);
 		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);
+
 	if (opts.backup && make_backup())
 		return 1;
 
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 7a18e51d43e62..11ac2ec04afc1 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->elf, 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 d89f8b5ec14e3..18c0e69ee6170 100644
--- a/tools/objtool/include/objtool/arch.h
+++ b/tools/objtool/include/objtool/arch.h
@@ -103,4 +103,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 d73b0c3ae1ee3..674f57466d125 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -127,4 +127,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] 35+ messages in thread

* [PATCH v5 04/30] tool build: Remove annoying newline in build output
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (2 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 03/30] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 05/30] objtool: Print symbol during disassembly Alexandre Chartre
                   ` (26 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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] 35+ messages in thread

* [PATCH v5 05/30] objtool: Print symbol during disassembly
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (3 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 04/30] tool build: Remove annoying newline in build output Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 06/30] objtool: Store instruction disassembly result Alexandre Chartre
                   ` (25 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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/check.c                 |   9 --
 tools/objtool/disas.c                 | 134 ++++++++++++++++++++++++++
 tools/objtool/include/objtool/check.h |   9 ++
 3 files changed, 143 insertions(+), 9 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index de156e91ee8b7..8937667f075df 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -134,15 +134,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 11ac2ec04afc1..dee10ab86fa29 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -14,13 +14,144 @@
 
 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;
+	}
+
+	offset = arch_insn_adjusted_addend(insn, 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 +200,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 +253,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/check.h b/tools/objtool/include/objtool/check.h
index 674f57466d125..ad9c73504b120 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -117,6 +117,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] 35+ messages in thread

* [PATCH v5 06/30] objtool: Store instruction disassembly result
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (4 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 05/30] objtool: Print symbol during disassembly Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 07/30] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
                   ` (24 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 dee10ab86fa29..89daa121b40b0 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;
+	int 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)
 {
@@ -195,9 +255,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;
@@ -244,6 +303,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.
  */
@@ -254,6 +318,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);
@@ -282,10 +347,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] 35+ messages in thread

* [PATCH v5 07/30] objtool: Disassemble instruction on warning or backtrace
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (5 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 06/30] objtool: Store instruction disassembly result Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 08/30] objtool: Extract code to validate instruction from the validate branch loop Alexandre Chartre
                   ` (23 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 8937667f075df..dd61c3242def5 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4844,11 +4844,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);
@@ -4990,11 +5013,12 @@ int check(struct objtool_file *file)
 	if (opts.verbose) {
 		if (opts.werror && warnings)
 			WARN("%d warning(s) upgraded to errors", warnings);
-		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 89daa121b40b0..a030b06c121d2 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -303,7 +303,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;
 }
@@ -311,8 +311,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 ad9c73504b120..f96aabd7d54dc 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -141,4 +141,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 a1e3927d8e7ce..f32abc7b1be14 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] 35+ messages in thread

* [PATCH v5 08/30] objtool: Extract code to validate instruction from the validate branch loop
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (6 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 07/30] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 09/30] objtool: Record symbol name max length Alexandre Chartre
                   ` (22 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 | 386 ++++++++++++++++++++++--------------------
 1 file changed, 205 insertions(+), 181 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index dd61c3242def5..9c1888e21c1d8 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3706,253 +3706,277 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
 	checksum_update(func, insn, &offset, sizeof(offset));
 }
 
-/*
- * 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;
 	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;
 
-	while (1) {
-		next_insn = next_insn_to_validate(file, insn);
+	visited = VISITED_BRANCH << statep->uaccess;
+	if (insn->visited & VISITED_BRANCH_MASK) {
+		if (!insn->hint && !insn_cfi_match(insn, &statep->cfi))
+			return 1;
 
-		if (opts.checksum && func && insn->sec)
-			checksum_update_insn(file, func, 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 (is_prefix_func(func))
-				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;
+	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;
+	while (1) {
+		next_insn = next_insn_to_validate(file, insn);
 
-		case INSN_CLD:
-			if (!state.df && func) {
-				WARN_INSN(insn, "redundant CLD");
-				return 1;
-			}
+		if (opts.checksum && func && insn->sec)
+			checksum_update_insn(file, func, insn);
 
-			state.df = false;
-			break;
+		if (func && insn_func(insn) && func != insn_func(insn)->pfunc) {
+			/* Ignore KCFI type preambles, which always fall through */
+			if (is_prefix_func(func))
+				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)
@@ -3970,7 +3994,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] 35+ messages in thread

* [PATCH v5 09/30] objtool: Record symbol name max length
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (7 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 08/30] objtool: Extract code to validate instruction from the validate branch loop Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 10/30] objtool: Add option to trace function validation Alexandre Chartre
                   ` (21 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 9c1888e21c1d8..42a33898b695d 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -37,6 +37,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)
 {
@@ -2484,6 +2486,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->elf, func) {
 		if (is_notype_sym(func) && strstarts(func->name, ".L"))
@@ -2510,6 +2513,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] 35+ messages in thread

* [PATCH v5 10/30] objtool: Add option to trace function validation
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (8 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 09/30] objtool: Record symbol name max length Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 11/30] objtool: Trace instruction state changes during " Alexandre Chartre
                   ` (20 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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                   | 104 +++++++++++++++++----
 tools/objtool/disas.c                   | 115 ++++++++++++++++++++++++
 tools/objtool/include/objtool/builtin.h |   1 +
 tools/objtool/include/objtool/check.h   |   6 +-
 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, 299 insertions(+), 18 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 9d1e8f28ef953..9982e665d58da 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 orc_dump.o
 objtool-$(BUILD_KLP) += builtin-klp.o klp-diff.o klp-post-link.o
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index aab7fa9c7e00a..3329d370006b4 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -103,6 +103,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 42a33898b695d..2352b9668b126 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4,6 +4,7 @@
  */
 
 #define _GNU_SOURCE /* memmem() */
+#include <fnmatch.h>
 #include <string.h>
 #include <stdlib.h>
 #include <inttypes.h>
@@ -15,6 +16,7 @@
 #include <objtool/disas.h>
 #include <objtool/check.h>
 #include <objtool/special.h>
+#include <objtool/trace.h>
 #include <objtool/warn.h>
 #include <objtool/checksum.h>
 #include <objtool/util.h>
@@ -37,7 +39,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)
@@ -3608,8 +3612,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
@@ -3715,6 +3721,8 @@ static void checksum_update_insn(struct objtool_file *file, struct symbol *func,
 
 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,
@@ -3736,8 +3744,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++;
 	}
@@ -3774,8 +3784,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;
@@ -3803,13 +3815,23 @@ 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 DEFAULT");
 	}
 
 	if (skip_alt_group(insn))
@@ -3821,10 +3843,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;
@@ -3840,13 +3868,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;
@@ -3856,10 +3889,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)
@@ -3872,6 +3907,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;
@@ -3880,6 +3916,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;
@@ -3888,6 +3925,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;
 
@@ -3900,6 +3938,7 @@ static int validate_insn(struct objtool_file *file, struct symbol *func,
 		break;
 
 	case INSN_CLAC:
+		TRACE_INSN(insn, "clac");
 		if (!opts.uaccess)
 			break;
 
@@ -3917,6 +3956,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;
@@ -3926,6 +3966,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;
@@ -3938,8 +3979,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;
 }
 
@@ -3949,8 +3992,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;
 	bool dead_end;
@@ -3959,7 +4002,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
 	if (func && func->ignore)
 		return 0;
 
-	while (1) {
+	do {
+		insn->trace = 0;
 		next_insn = next_insn_to_validate(file, insn);
 
 		if (opts.checksum && func && insn->sec)
@@ -3982,10 +4026,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)
@@ -3999,7 +4048,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;
 }
@@ -4460,10 +4522,18 @@ static int validate_symbol(struct objtool_file *file, struct section *sec,
 	if (opts.checksum)
 		checksum_init(func);
 
+	if (opts.trace && !fnmatch(opts.trace, sym->name, 0)) {
+		trace_enable();
+		TRACE("%s: validation begin\n", sym->name);
+	}
+
 	ret = validate_branch(file, func, insn, *state);
 	if (ret)
 		BT_INSN(insn, "<=== (sym)");
 
+	TRACE("%s: validation %s\n\n", sym->name, ret ? "failed" : "end");
+	trace_disable();
+
 	if (opts.checksum)
 		checksum_finish(func);
 
@@ -4875,8 +4945,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;
@@ -4898,8 +4966,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 a030b06c121d2..0ca6e6c8559fd 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -308,6 +308,121 @@ 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.
+ */
+static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
+			int depth, const char *format, va_list ap)
+{
+	const char *addr_str;
+	int i, n;
+	int len;
+
+	len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE;
+	if (depth < 0) {
+		len += depth;
+		depth = 0;
+	}
+
+	n = 0;
+
+	if (sec) {
+		addr_str = offstr(sec, offset);
+		n += fprintf(stream, "%6lx:  %-*s  ", offset, len, addr_str);
+		free((char *)addr_str);
+	} else {
+		len += DISAS_INSN_OFFSET_SPACE + 1;
+		n += fprintf(stream, "%-*s", len, "");
+	}
+
+	/* print vertical bars to show the code flow */
+	for (i = 0; i < depth; i++)
+		n += fprintf(stream, "| ");
+
+	if (format)
+		n += vfprintf(stream, format, ap);
+
+	return 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.
+ */
+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);
+}
+
+/*
+ * 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 bb0b25eb08ba4..991365c10f0e9 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -41,6 +41,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 f96aabd7d54dc..fde958683485f 100644
--- a/tools/objtool/include/objtool/check.h
+++ b/tools/objtool/include/objtool/check.h
@@ -66,7 +66,8 @@ struct instruction {
 	    visited		: 4,
 	    no_reloc		: 1,
 	    hole		: 1,
-	    fake		: 1;
+	    fake		: 1,
+	    trace		: 1;
 		/* 9 bit hole */
 
 	struct alt_group *alt_group;
@@ -143,4 +144,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..3f3c830ed114e
--- /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 f32abc7b1be14..25ff7942b4d54 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..134cc33ffe970
--- /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] 35+ messages in thread

* [PATCH v5 11/30] objtool: Trace instruction state changes during function validation
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (9 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 10/30] objtool: Add option to trace function validation Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 12/30] objtool: Improve register reporting " Alexandre Chartre
                   ` (19 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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                 | 132 ++++++++++++++++++++++++++
 3 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 2352b9668b126..e12dba144731f 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3729,6 +3729,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;
@@ -3837,7 +3839,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 3f3c830ed114e..33fe9c6acb4fd 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 134cc33ffe970..12bbad09d9c02 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -7,3 +7,135 @@
 
 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 "<error>";
+
+	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)
+{
+	char *rname;
+
+	if (base_prev == base_next && offset_prev == offset_next)
+		return;
+
+	if (prefix)
+		TRACE("%s:", prefix);
+
+	if (base_next == CFI_UNDEFINED) {
+		TRACE("%1$s=<undef> ", cfi_reg_name(reg));
+	} else {
+		rname = strdup(cfi_reg_name(reg));
+		TRACE(fmt, rname, cfi_reg_name(base_next), offset_next);
+		free(rname);
+	}
+}
+
+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] 35+ messages in thread

* [PATCH v5 12/30] objtool: Improve register reporting during function validation
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (10 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 11/30] objtool: Trace instruction state changes during " Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 13/30] objtool: Identify the different types of alternatives Alexandre Chartre
                   ` (18 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 1de86ebb637d5..6cd288150f495 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(const char *name)
 {
 	return !strcmp(name, "_mcount");
diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c
index 4f68b402e7855..e534ac1123b33 100644
--- a/tools/objtool/arch/powerpc/decode.c
+++ b/tools/objtool/arch/powerpc/decode.c
@@ -9,6 +9,18 @@
 #include <objtool/warn.h>
 #include <objtool/builtin.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(const char *name)
 {
 	return !strcmp(name, "_mcount");
diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c
index 83e9c604ce105..f4af825082284 100644
--- a/tools/objtool/arch/x86/decode.c
+++ b/tools/objtool/arch/x86/decode.c
@@ -23,6 +23,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(const char *name)
 {
 	return !strcmp(name, "__fentry__");
diff --git a/tools/objtool/include/objtool/arch.h b/tools/objtool/include/objtool/arch.h
index 18c0e69ee6170..8866158975fcb 100644
--- a/tools/objtool/include/objtool/arch.h
+++ b/tools/objtool/include/objtool/arch.h
@@ -103,6 +103,8 @@ 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);
 
+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 12bbad09d9c02..d70d47081e82d 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 "<error>";
 
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 13/30] objtool: Identify the different types of alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (11 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 12/30] objtool: Improve register reporting " Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 14/30] objtool: Add functions to better name alternatives Alexandre Chartre
                   ` (17 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 e12dba144731f..20630df83e85c 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -27,11 +27,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;
@@ -1945,6 +1940,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;
 
 	if (special_get_alts(file->elf, &special_alts))
@@ -1980,9 +1976,15 @@ static int add_special_section_alts(struct objtool_file *file)
 			if (handle_group_alt(file, special_alt, orig_insn, &new_insn))
 				return -1;
 
+			alt_type = ALT_TYPE_INSTRUCTIONS;
+
 		} else if (special_alt->jump_or_nop) {
 			if (handle_jump_alt(file, special_alt, orig_insn, &new_insn))
 				return -1;
+
+			alt_type = ALT_TYPE_JUMP_TABLE;
+		} else {
+			alt_type = ALT_TYPE_EX_TABLE;
 		}
 
 		alt = calloc(1, sizeof(*alt));
@@ -1993,6 +1995,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 fde958683485f..cbf4af58e29b2 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] 35+ messages in thread

* [PATCH v5 14/30] objtool: Add functions to better name alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (12 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 13/30] objtool: Identify the different types of alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 15/30] objtool: Improve tracing of alternative instructions Alexandre Chartre
                   ` (16 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  Cc: alexandre.chartre

Add the disas_alt_name() and disas_alt_type_name() to provide a
name and a type name for an alternative. This will be used to
better name alternatives when tracing their execution.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
 tools/objtool/disas.c                 | 72 +++++++++++++++++++++++++++
 tools/objtool/include/objtool/disas.h | 12 +++++
 2 files changed, 84 insertions(+)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 0ca6e6c8559fd..b53be240825da 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>
@@ -450,6 +452,76 @@ size_t disas_insn(struct disas_context *dctx, struct instruction *insn)
 	return disasm(insn->offset, &dctx->info);
 }
 
+/*
+ * Provide a name for the type of alternatives present at the
+ * specified instruction.
+ *
+ * An instruction can have alternatives with different types, for
+ * example alternative instructions and an exception table. In that
+ * case the name for the alternative instructions type is used.
+ *
+ * Return NULL if the instruction as no alternative.
+ */
+const char *disas_alt_type_name(struct instruction *insn)
+{
+	struct alternative *alt;
+	const char *name;
+
+	name = NULL;
+	for (alt = insn->alts; alt; alt = alt->next) {
+		if (alt->type == ALT_TYPE_INSTRUCTIONS) {
+			name = "alternative";
+			break;
+		}
+
+		switch (alt->type) {
+		case ALT_TYPE_EX_TABLE:
+			name = "ex_table";
+			break;
+		case ALT_TYPE_JUMP_TABLE:
+			name = "jump_table";
+			break;
+		default:
+			name = "unknown";
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * 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..8959d4c455622 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,8 @@ 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);
+const char *disas_alt_type_name(struct instruction *insn);
 
 #else /* DISAS */
 
@@ -61,6 +64,15 @@ 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;
+}
+
+static inline const char *disas_alt_type_name(struct instruction *insn)
+{
+	return NULL;
+}
 
 #endif /* DISAS */
 
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 15/30] objtool: Improve tracing of alternative instructions
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (13 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 14/30] objtool: Add functions to better name alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 16/30] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
                   ` (15 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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/include/objtool/trace.h | 65 ++++++++++++++++++++++++++-
 tools/objtool/trace.c                 | 55 +++++++++++++++++++++++
 3 files changed, 125 insertions(+), 13 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 20630df83e85c..fb000923718dc 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -3616,7 +3616,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;
 	}
 
@@ -3732,8 +3732,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;
@@ -3820,23 +3821,16 @@ 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 DEFAULT");
+		TRACE_ALT_INFO_NOADDR(insn, "/ ", "DEFAULT");
 	}
 
 	if (skip_alt_group(insn))
diff --git a/tools/objtool/include/objtool/trace.h b/tools/objtool/include/objtool/trace.h
index 33fe9c6acb4fd..70b574366797b 100644
--- a/tools/objtool/include/objtool/trace.h
+++ b/tools/objtool/include/objtool/trace.h
@@ -19,11 +19,26 @@ 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) {						\
 		disas_print_insn(stderr, objtool_disas_ctx,	\
-				 insn, trace_depth - 1,	\
+				 insn, trace_depth - 1,		\
 				 fmt, ##__VA_ARGS__);		\
 		fprintf(stderr, "\n");				\
 		insn->trace = 1;				\
@@ -36,6 +51,37 @@ extern int trace_depth;
 		trace_insn_state(insn, sprev, snext);		\
 })
 
+#define TRACE_ALT_FMT(pfx, fmt) pfx "<%s.%lx> " fmt
+#define TRACE_ALT_ARG(insn) disas_alt_type_name(insn), (insn)->offset
+
+#define TRACE_ALT(insn, fmt, ...)				\
+	TRACE_INSN(insn, TRACE_ALT_FMT("", fmt),		\
+		   TRACE_ALT_ARG(insn), ##__VA_ARGS__)
+
+#define TRACE_ALT_INFO(insn, pfx, fmt, ...)			\
+	TRACE_ADDR(insn, TRACE_ALT_FMT(pfx, fmt),		\
+		   TRACE_ALT_ARG(insn), ##__VA_ARGS__)
+
+#define TRACE_ALT_INFO_NOADDR(insn, pfx, fmt, ...)		\
+	TRACE_ADDR(NULL, TRACE_ALT_FMT(pfx, fmt),		\
+		   TRACE_ALT_ARG(insn), ##__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 +107,34 @@ 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 d70d47081e82d..5dec44dab781c 100644
--- a/tools/objtool/trace.c
+++ b/tools/objtool/trace.c
@@ -146,3 +146,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", alt_name);
+}
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 16/30] objtool: Do not validate IBT for .return_sites and .call_sites
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (14 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 15/30] objtool: Improve tracing of alternative instructions Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 17/30] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
                   ` (14 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 fb000923718dc..0da86834ab2ab 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -4805,6 +4805,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")			||
 		    !strcmp(sec->name, "__patchable_function_entries"))
 			continue;
 
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 17/30] objtool: Add the --disas=<function-pattern> action
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (15 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 16/30] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 18/30] objtool: Preserve alternatives order Alexandre Chartre
                   ` (13 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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           |  2 ++
 tools/objtool/check.c                   | 38 ++++++++++++++-----------
 tools/objtool/disas.c                   | 27 ++++++++++++++++++
 tools/objtool/include/objtool/builtin.h |  1 +
 tools/objtool/include/objtool/disas.h   |  2 ++
 5 files changed, 53 insertions(+), 17 deletions(-)

diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index 3329d370006b4..a0371312fe55a 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -75,6 +75,7 @@ static const struct option check_options[] = {
 	OPT_GROUP("Actions:"),
 	OPT_BOOLEAN(0,		 "checksum", &opts.checksum, "generate per-function checksums"),
 	OPT_BOOLEAN(0,		 "cfi", &opts.cfi, "annotate kernel control flow integrity (kCFI) function preambles"),
+	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"),
@@ -176,6 +177,7 @@ static bool opts_valid(void)
 	}
 
 	if (opts.checksum		||
+	    opts.disas			||
 	    opts.hack_jump_label	||
 	    opts.hack_noinstr		||
 	    opts.ibt			||
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 0da86834ab2ab..5bb932f211f6b 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -2632,7 +2632,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 (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label) {
+	if (validate_branch_enabled() || opts.noinstr || opts.hack_jump_label || opts.disas) {
 		if (add_special_section_alts(file))
 			return -1;
 	}
@@ -4967,14 +4967,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;
 	}
 
@@ -5108,20 +5109,20 @@ int check(struct objtool_file *file)
 	}
 
 out:
-	if (!ret && !warnings) {
-		free_insns(file);
-		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);
-		disas_warned_funcs(disas_ctx);
+			ret = 1;
+
+		if (opts.verbose) {
+			if (opts.werror && warnings)
+				WARN("%d warning(s) upgraded to errors", warnings);
+			disas_warned_funcs(disas_ctx);
+		}
 	}
 
+	if (opts.disas)
+		disas_funcs(disas_ctx);
+
 	if (disas_ctx) {
 		disas_context_destroy(disas_ctx);
 		objtool_disas_ctx = NULL;
@@ -5129,6 +5130,9 @@ int check(struct objtool_file *file)
 
 	free_insns(file);
 
+	if (!ret && !warnings)
+		return 0;
+
 	if (opts.backup && make_backup())
 		return 1;
 
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index b53be240825da..9cc952e03c356 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>
@@ -556,3 +557,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->elf, 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 991365c10f0e9..e3af664864f30 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -28,6 +28,7 @@ struct opts {
 	bool static_call;
 	bool uaccess;
 	int prefix;
+	const char *disas;
 
 	/* options: */
 	bool backtrace;
diff --git a/tools/objtool/include/objtool/disas.h b/tools/objtool/include/objtool/disas.h
index 8959d4c455622..e8f395eff1598 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);
@@ -40,6 +41,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] 35+ messages in thread

* [PATCH v5 18/30] objtool: Preserve alternatives order
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (16 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 17/30] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 19/30] objtool: Print headers for alternatives Alexandre Chartre
                   ` (12 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  Cc: alexandre.chartre

Preserve the order in which alternatives are defined. Currently
objtool stores alternatives in a list in reverse order.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
 tools/objtool/check.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 5bb932f211f6b..25839c3950a3c 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1942,6 +1942,7 @@ static int add_special_section_alts(struct objtool_file *file)
 	struct special_alt *special_alt, *tmp;
 	enum alternative_type alt_type;
 	struct alternative *alt;
+	struct alternative *a;
 
 	if (special_get_alts(file->elf, &special_alts))
 		return -1;
@@ -1994,9 +1995,20 @@ 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;
+		alt->next = NULL;
+
+		/*
+		 * Store alternatives in the same order they have been
+		 * defined.
+		 */
+		if (!orig_insn->alts) {
+			orig_insn->alts = alt;
+		} else {
+			for (a = orig_insn->alts; a->next; a = a->next)
+				;
+			a->next = alt;
+		}
 
 		list_del(&special_alt->list);
 		free(special_alt);
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 19/30] objtool: Print headers for alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (17 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 18/30] objtool: Preserve alternatives order Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 20/30] objtool: Disassemble group alternatives Alexandre Chartre
                   ` (11 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 | 188 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 182 insertions(+), 6 deletions(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 9cc952e03c356..f9b13d56acab7 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;
@@ -314,6 +351,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,
@@ -354,6 +394,19 @@ static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
 	return n;
 }
 
+static int disas_print(FILE *stream, struct section *sec, unsigned long offset,
+			int depth, const char *format, ...)
+{
+	va_list args;
+	int len;
+
+	va_start(args, format);
+	len = disas_vprint(stream, sec, offset, depth, format, args);
+	va_end(args);
+
+	return len;
+}
+
 /*
  * Print a message in the instruction flow. If insn is not NULL then
  * the instruction address is printed in addition of the message,
@@ -523,21 +576,144 @@ 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)
+{
+	dalt->orig_insn = orig_insn;
+	dalt->alt = alt;
+	dalt->name = alt ? disas_alt_name(alt) : strdup("DEFAULT");
+	if (!dalt->name)
+		return -1;
+	dalt->width = strlen(dalt->name);
+
+	return 0;
+}
+
+/*
+ * Print all alternatives one above the other.
+ */
+static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
+				    int alt_count)
+{
+	struct instruction *orig_insn;
+	int len;
+	int i;
+
+	orig_insn = dalts[0].orig_insn;
+
+	len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL);
+	printf("%s\n", alt_name);
+
+	for (i = 0; i < alt_count; i++)
+		printf("%*s= %s\n", len, "", dalts[i].name);
+}
+
+/*
+ * 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 dalts[DISAS_ALT_MAX] = { 0 };
+	struct alternative *alt;
+	int alt_count = 0;
+	char *alt_name;
+	int err;
+	int i;
+
+	alt_name = strfmt("<%s.%lx>", disas_alt_type_name(orig_insn),
+			  orig_insn->offset);
+	if (!alt_name) {
+		WARN("Failed to define name for alternative at instruction 0x%lx",
+		     orig_insn->offset);
+		goto done;
+	}
+
+	/*
+	 * Initialize the default alternative.
+	 */
+	err = disas_alt_init(&dalts[0], orig_insn, NULL);
+	if (err) {
+		WARN("%s: failed to initialize default alternative", alt_name);
+		goto done;
+	}
+
+	/*
+	 * Initialize all other alternatives.
+	 */
+	i = 1;
+	for (alt = orig_insn->alts; alt; alt = alt->next) {
+		if (i >= DISAS_ALT_MAX) {
+			WARN("%s has more alternatives than supported", alt_name);
+			break;
+		}
+		err = disas_alt_init(&dalts[i], orig_insn, alt);
+		if (err) {
+			WARN("%s: failed to disassemble alternative", alt_name);
+			goto done;
+		}
+
+		i++;
+	}
+	alt_count = i;
+
+	/*
+	 * Print default and non-default alternatives.
+	 *
+	 * At the moment, this just prints an header for each alternative.
+	 */
+	disas_alt_print_compact(alt_name, dalts, alt_count);
+
+done:
+	for (i = 0; i < alt_count; i++)
+		free(dalts[i].name);
+
+	free(alt_name);
+
+	/*
+	 * 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;
+}
+
 /*
  * 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] 35+ messages in thread

* [PATCH v5 20/30] objtool: Disassemble group alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (18 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 19/30] objtool: Print headers for alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 21/30] objtool: Print addresses with alternative instructions Alexandre Chartre
                   ` (10 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 | 166 +++++++++++++++++++++++++++++++++++++-----
 1 file changed, 149 insertions(+), 17 deletions(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index f9b13d56acab7..ae69bef2eb37e 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.
@@ -506,6 +512,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 the type of alternatives present at the
  * specified instruction.
@@ -594,23 +615,107 @@ 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;
+}
+
 /*
  * Print all alternatives one above the other.
  */
 static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
-				    int alt_count)
+				    int alt_count, int insn_count)
 {
 	struct instruction *orig_insn;
+	int i, j;
 	int len;
-	int i;
 
 	orig_insn = dalts[0].orig_insn;
 
 	len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL);
 	printf("%s\n", alt_name);
 
-	for (i = 0; i < alt_count; i++)
+	for (i = 0; i < alt_count; i++) {
 		printf("%*s= %s\n", len, "", dalts[i].name);
+		for (j = 0; j < insn_count; j++) {
+			if (!dalts[i].insn[j])
+				break;
+			printf("%*s| %s\n", len, "", dalts[i].insn[j]);
+		}
+		printf("%*s|\n", len, "");
+	}
 }
 
 /*
@@ -624,11 +729,15 @@ static void *disas_alt(struct disas_context *dctx,
 		       struct instruction *orig_insn)
 {
 	struct disas_alt dalts[DISAS_ALT_MAX] = { 0 };
+	struct instruction *last_insn = NULL;
 	struct alternative *alt;
+	struct disas_alt *dalt;
+	int insn_count = 0;
 	int alt_count = 0;
 	char *alt_name;
+	int count;
+	int i, j;
 	int err;
-	int i;
 
 	alt_name = strfmt("<%s.%lx>", disas_alt_type_name(orig_insn),
 			  orig_insn->offset);
@@ -639,7 +748,7 @@ static void *disas_alt(struct disas_context *dctx,
 	}
 
 	/*
-	 * Initialize the default alternative.
+	 * Initialize and disassemble the default alternative.
 	 */
 	err = disas_alt_init(&dalts[0], orig_insn, NULL);
 	if (err) {
@@ -647,8 +756,14 @@ static void *disas_alt(struct disas_context *dctx,
 		goto done;
 	}
 
+	insn_count = disas_alt_default(dctx, &dalts[0]);
+	if (insn_count < 0) {
+		WARN("%s: failed to disassemble default alternative", alt_name);
+		goto done;
+	}
+
 	/*
-	 * Initialize all other alternatives.
+	 * Initialize and disassemble all other alternatives.
 	 */
 	i = 1;
 	for (alt = orig_insn->alts; alt; alt = alt->next) {
@@ -656,35 +771,52 @@ static void *disas_alt(struct disas_context *dctx,
 			WARN("%s has more alternatives than supported", alt_name);
 			break;
 		}
-		err = disas_alt_init(&dalts[i], orig_insn, alt);
+		dalt = &dalts[i];
+		err = disas_alt_init(dalt, orig_insn, alt);
 		if (err) {
 			WARN("%s: failed to disassemble alternative", alt_name);
 			goto done;
 		}
 
+		/*
+		 * 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) {
+			WARN("%s: failed to disassemble alternative %s",
+			     alt_name, dalt->name);
+			goto done;
+		}
+
+		insn_count = count > insn_count ? count : insn_count;
 		i++;
 	}
 	alt_count = i;
 
 	/*
 	 * Print default and non-default alternatives.
-	 *
-	 * At the moment, this just prints an header for each alternative.
 	 */
-	disas_alt_print_compact(alt_name, dalts, alt_count);
+	disas_alt_print_compact(alt_name, dalts, alt_count, insn_count);
+
+	last_insn = orig_insn->alt_group ? orig_insn->alt_group->last_insn :
+		orig_insn;
 
 done:
-	for (i = 0; i < alt_count; i++)
+	for (i = 0; i < alt_count; i++) {
 		free(dalts[i].name);
+		for (j = 0; j < insn_count; j++)
+			free(dalts[i].insn[j]);
+	}
 
 	free(alt_name);
 
-	/*
-	 * 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;
+	return last_insn;
 }
 
 /*
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 21/30] objtool: Print addresses with alternative instructions
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (19 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 20/30] objtool: Disassemble group alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 22/30] objtool: Disassemble exception table alternatives Alexandre Chartre
                   ` (9 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 | 35 +++++++++++++++++++++++------------
 1 file changed, 23 insertions(+), 12 deletions(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index ae69bef2eb37e..6083a64f6ae49 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)
@@ -361,10 +365,9 @@ 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.
  */
 static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset,
 			int depth, const char *format, va_list ap)
@@ -607,6 +610,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 ? disas_alt_name(alt) : strdup("DEFAULT");
 	if (!dalt->name)
 		return -1;
@@ -615,7 +619,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;
 
@@ -626,7 +631,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;
 
@@ -641,12 +647,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) {
 
@@ -655,9 +663,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++;
 	}
 
@@ -685,7 +694,7 @@ 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;
 
@@ -710,9 +719,11 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
 	for (i = 0; i < alt_count; i++) {
 		printf("%*s= %s\n", len, "", dalts[i].name);
 		for (j = 0; j < insn_count; j++) {
-			if (!dalts[i].insn[j])
+			if (!dalts[i].insn[j].str)
 				break;
-			printf("%*s| %s\n", len, "", dalts[i].insn[j]);
+			disas_print(stdout, orig_insn->sec,
+				    orig_insn->offset + dalts[i].insn[j].offset, 0,
+				    "| %s\n", dalts[i].insn[j].str);
 		}
 		printf("%*s|\n", len, "");
 	}
@@ -811,7 +822,7 @@ static void *disas_alt(struct disas_context *dctx,
 	for (i = 0; i < alt_count; i++) {
 		free(dalts[i].name);
 		for (j = 0; j < insn_count; j++)
-			free(dalts[i].insn[j]);
+			free(dalts[i].insn[j].str);
 	}
 
 	free(alt_name);
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 22/30] objtool: Disassemble exception table alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (20 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 21/30] objtool: Print addresses with alternative instructions Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-20 17:28   ` Josh Poimboeuf
  2025-11-19 14:32 ` [PATCH v5 23/30] objtool: Disassemble jump " Alexandre Chartre
                   ` (8 subsequent siblings)
  30 siblings, 1 reply; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 6083a64f6ae49..018aba37b996b 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -639,6 +639,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.
@@ -790,12 +810,16 @@ static void *disas_alt(struct disas_context *dctx,
 		}
 
 		/*
-		 * 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] 35+ messages in thread

* [PATCH v5 23/30] objtool: Disassemble jump table alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (21 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 22/30] objtool: Disassemble exception table alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 24/30] objtool: Fix address references in alternatives Alexandre Chartre
                   ` (7 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 | 38 ++++++++++++++++++++++++++++++++------
 1 file changed, 32 insertions(+), 6 deletions(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 018aba37b996b..326e16c9f30a8 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -639,6 +639,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.
  */
@@ -809,10 +837,7 @@ static void *disas_alt(struct disas_context *dctx,
 			goto done;
 		}
 
-		/*
-		 * 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);
@@ -820,8 +845,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) {
 			WARN("%s: failed to disassemble alternative %s",
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 24/30] objtool: Fix address references in alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (22 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 23/30] objtool: Disassemble jump " Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 25/30] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
                   ` (6 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 326e16c9f30a8..f8917c8405d32 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;
@@ -232,8 +273,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;
 	}
 
@@ -490,13 +532,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) {
@@ -515,6 +566,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)
@@ -706,7 +768,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] 35+ messages in thread

* [PATCH v5 25/30] objtool: Provide access to feature and flags of group alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (23 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 24/30] objtool: Fix address references in alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 26/30] objtool: Function to get the name of a CPU feature Alexandre Chartre
                   ` (5 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 25839c3950a3c..ddc5ec74d9c99 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1772,6 +1772,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) {
@@ -1876,6 +1877,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 cbf4af58e29b2..2e1346ad5e926 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 e262af9171436..2a533afbc69aa 100644
--- a/tools/objtool/special.c
+++ b/tools/objtool/special.c
@@ -81,6 +81,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] 35+ messages in thread

* [PATCH v5 26/30] objtool: Function to get the name of a CPU feature
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (24 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 25/30] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 27/30] objtool: Improve naming of group alternatives Alexandre Chartre
                   ` (4 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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 fdc3d32dc79e7..ca49a11718889 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -126,6 +126,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 09300761f1085..b6b40c56da896 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] 35+ messages in thread

* [PATCH v5 27/30] objtool: Improve naming of group alternatives
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (25 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 26/30] objtool: Function to get the name of a CPU feature Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 28/30] objtool: Compact output for alternatives with one instruction Alexandre Chartre
                   ` (3 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  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/disas.c | 58 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 52 insertions(+), 6 deletions(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index f8917c8405d32..731c4495b53c7 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 int ft_flags)
+{
+	return (ft_flags & ALT_FEATURE_MASK);
+}
+
+static int alt_flags(unsigned int 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.
@@ -635,7 +651,12 @@ const char *disas_alt_type_name(struct instruction *insn)
  */
 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) {
 
@@ -649,13 +670,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;
 	}
 
@@ -892,6 +937,7 @@ static void *disas_alt(struct disas_context *dctx,
 			WARN("%s has more alternatives than supported", alt_name);
 			break;
 		}
+
 		dalt = &dalts[i];
 		err = disas_alt_init(dalt, orig_insn, alt);
 		if (err) {
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 28/30] objtool: Compact output for alternatives with one instruction
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (26 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 27/30] objtool: Improve naming of group alternatives Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 29/30] objtool: Add wide output for disassembly Alexandre Chartre
                   ` (2 subsequent siblings)
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  Cc: alexandre.chartre

When disassembling, if an instruction has alternatives which are all
made of a single instruction then print each alternative on a single
line (instruction + description) so that the output is more compact.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
 tools/objtool/disas.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index 731c4495b53c7..a4f905eac4e63 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -863,6 +863,7 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
 				    int alt_count, int insn_count)
 {
 	struct instruction *orig_insn;
+	int width;
 	int i, j;
 	int len;
 
@@ -871,6 +872,27 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
 	len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL);
 	printf("%s\n", alt_name);
 
+	/*
+	 * If all alternatives have a single instruction then print each
+	 * alternative on a single line. Otherwise, print alternatives
+	 * one above the other with a clear separation.
+	 */
+
+	if (insn_count == 1) {
+		width = 0;
+		for (i = 0; i < alt_count; i++) {
+			if (dalts[i].width > width)
+				width = dalts[i].width;
+		}
+
+		for (i = 0; i < alt_count; i++) {
+			printf("%*s= %-*s    (if %s)\n", len, "", width,
+			       dalts[i].insn[0].str, dalts[i].name);
+		}
+
+		return;
+	}
+
 	for (i = 0; i < alt_count; i++) {
 		printf("%*s= %s\n", len, "", dalts[i].name);
 		for (j = 0; j < insn_count; j++) {
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 29/30] objtool: Add wide output for disassembly
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (27 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 28/30] objtool: Compact output for alternatives with one instruction Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-19 14:32 ` [PATCH v5 30/30] objtool: Trim trailing NOPs in alternative Alexandre Chartre
  2025-11-20 17:30 ` [PATCH v5 00/30] objtool: Function validation tracing Josh Poimboeuf
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  Cc: alexandre.chartre

Add the --wide option to provide a wide output when disassembling.
With this option, the disassembly of alternatives is displayed
side-by-side instead of one above the other.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
 tools/objtool/builtin-check.c           |  1 +
 tools/objtool/disas.c                   | 95 ++++++++++++++++++++++++-
 tools/objtool/include/objtool/builtin.h |  1 +
 3 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index a0371312fe55a..b780df5137152 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -107,6 +107,7 @@ static const struct option check_options[] = {
 	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"),
+	OPT_BOOLEAN(0,		 "wide", &opts.wide, "wide output"),
 
 	OPT_END(),
 };
diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index a4f905eac4e63..f04bc14bef39e 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -856,6 +856,95 @@ static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt)
 	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 *dalts, 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 = &dalts[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 ?: "");
+			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;
+}
+
+/*
+ * Print all alternatives side-by-side.
+ */
+static void disas_alt_print_wide(char *alt_name, struct disas_alt *dalts, int alt_count,
+				 int insn_count)
+{
+	struct instruction *orig_insn;
+	int offset_next;
+	int offset;
+	int i;
+
+	orig_insn = dalts[0].orig_insn;
+
+	/*
+	 * Print an header with the name of each alternative.
+	 */
+	disas_print_info(stdout, orig_insn, -2, NULL);
+
+	if (strlen(alt_name) > dalts[0].width)
+		dalts[0].width = strlen(alt_name);
+	printf("| %-*s ", dalts[0].width, alt_name);
+
+	for (i = 1; i < alt_count; i++)
+		printf("| %-*s ", dalts[i].width, dalts[i].name);
+
+	printf("\n");
+
+	/*
+	 * Print instructions for each alternative.
+	 */
+	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(dalts, alt_count, insn_count,
+						   offset);
+	} while (offset_next > offset);
+}
+
 /*
  * Print all alternatives one above the other.
  */
@@ -993,7 +1082,11 @@ static void *disas_alt(struct disas_context *dctx,
 	/*
 	 * Print default and non-default alternatives.
 	 */
-	disas_alt_print_compact(alt_name, dalts, alt_count, insn_count);
+
+	if (opts.wide)
+		disas_alt_print_wide(alt_name, dalts, alt_count, insn_count);
+	else
+		disas_alt_print_compact(alt_name, dalts, alt_count, insn_count);
 
 	last_insn = orig_insn->alt_group ? orig_insn->alt_group->last_insn :
 		orig_insn;
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index e3af664864f30..b9e229ed4dc05 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -45,6 +45,7 @@ struct opts {
 	const char *trace;
 	bool verbose;
 	bool werror;
+	bool wide;
 };
 
 extern struct opts opts;
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v5 30/30] objtool: Trim trailing NOPs in alternative
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (28 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 29/30] objtool: Add wide output for disassembly Alexandre Chartre
@ 2025-11-19 14:32 ` Alexandre Chartre
  2025-11-20 17:30 ` [PATCH v5 00/30] objtool: Function validation tracing Josh Poimboeuf
  30 siblings, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-19 14:32 UTC (permalink / raw)
  To: linux-kernel, mingo, jpoimboe, peterz, david.laight.linux
  Cc: alexandre.chartre

When disassembling alternatives replace trailing NOPs with a single
indication of the number of bytes covered with NOPs.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
---
 tools/objtool/disas.c | 78 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 73 insertions(+), 5 deletions(-)

diff --git a/tools/objtool/disas.c b/tools/objtool/disas.c
index f04bc14bef39e..441b9306eafcc 100644
--- a/tools/objtool/disas.c
+++ b/tools/objtool/disas.c
@@ -52,6 +52,7 @@ struct disas_alt {
 	struct {
 		char *str;			/* instruction string */
 		int offset;			/* instruction offset */
+		int nops;			/* number of nops */
 	} insn[DISAS_ALT_INSN_MAX];		/* alternative instructions */
 	int insn_idx;				/* index of the next instruction to print */
 };
@@ -727,7 +728,7 @@ static int disas_alt_init(struct disas_alt *dalt,
 }
 
 static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
-			      int offset)
+			      int offset, int nops)
 {
 	int len;
 
@@ -740,6 +741,7 @@ static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str,
 	len = strlen(insn_str);
 	dalt->insn[index].str = insn_str;
 	dalt->insn[index].offset = offset;
+	dalt->insn[index].nops = nops;
 	if (len > dalt->width)
 		dalt->width = len;
 
@@ -752,6 +754,7 @@ static int disas_alt_jump(struct disas_alt *dalt)
 	struct instruction *dest_insn;
 	char suffix[2] = { 0 };
 	char *str;
+	int nops;
 
 	orig_insn = dalt->orig_insn;
 	dest_insn = dalt->alt->insn;
@@ -762,14 +765,16 @@ static int disas_alt_jump(struct disas_alt *dalt)
 		str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix,
 			     dest_insn->offset, dest_insn->sym->name,
 			     dest_insn->offset - dest_insn->sym->offset);
+		nops = 0;
 	} else {
 		str = strfmt("nop%d", orig_insn->len);
+		nops = orig_insn->len;
 	}
 
 	if (!str)
 		return -1;
 
-	disas_alt_add_insn(dalt, 0, str, 0);
+	disas_alt_add_insn(dalt, 0, str, 0, nops);
 
 	return 1;
 }
@@ -789,7 +794,7 @@ static int disas_alt_extable(struct disas_alt *dalt)
 	if (!str)
 		return -1;
 
-	disas_alt_add_insn(dalt, 0, str, 0);
+	disas_alt_add_insn(dalt, 0, str, 0, 0);
 
 	return 1;
 }
@@ -805,11 +810,13 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
 	int offset;
 	char *str;
 	int count;
+	int nops;
 	int err;
 
 	file = dctx->file;
 	count = 0;
 	offset = 0;
+	nops = 0;
 
 	alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
 
@@ -818,7 +825,8 @@ 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, offset);
+		nops = insn->type == INSN_NOP ? insn->len : 0;
+		err = disas_alt_add_insn(dalt, count, str, offset, nops);
 		if (err)
 			break;
 		offset += insn->len;
@@ -834,6 +842,7 @@ static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt)
 static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt)
 {
 	char *str;
+	int nops;
 	int err;
 
 	if (DALT_GROUP(dalt))
@@ -849,7 +858,8 @@ 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, 0);
+	nops = dalt->orig_insn->type == INSN_NOP ? dalt->orig_insn->len : 0;
+	err = disas_alt_add_insn(dalt, 0, str, 0, nops);
 	if (err)
 		return -1;
 
@@ -995,6 +1005,62 @@ static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts,
 	}
 }
 
+/*
+ * Trim NOPs in alternatives. This replaces trailing NOPs in alternatives
+ * with a single indication of the number of bytes covered with NOPs.
+ *
+ * Return the maximum numbers of instructions in all alternatives after
+ * trailing NOPs have been trimmed.
+ */
+static int disas_alt_trim_nops(struct disas_alt *dalts, int alt_count,
+			       int insn_count)
+{
+	struct disas_alt *dalt;
+	int nops_count;
+	const char *s;
+	int offset;
+	int count;
+	int nops;
+	int i, j;
+
+	count = 0;
+	for (i = 0; i < alt_count; i++) {
+		offset = 0;
+		nops = 0;
+		nops_count = 0;
+		dalt = &dalts[i];
+		for (j = insn_count - 1; j >= 0; j--) {
+			if (!dalt->insn[j].str || !dalt->insn[j].nops)
+				break;
+			offset = dalt->insn[j].offset;
+			free(dalt->insn[j].str);
+			dalt->insn[j].offset = 0;
+			dalt->insn[j].str = NULL;
+			nops += dalt->insn[j].nops;
+			nops_count++;
+		}
+
+		/*
+		 * All trailing NOPs have been removed. If there was a single
+		 * NOP instruction then re-add it. If there was a block of
+		 * NOPs then indicate the number of bytes than the block
+		 * covers (nop*<number-of-bytes>).
+		 */
+		if (nops_count) {
+			s = nops_count == 1 ? "" : "*";
+			dalt->insn[j + 1].str = strfmt("nop%s%d", s, nops);
+			dalt->insn[j + 1].offset = offset;
+			dalt->insn[j + 1].nops = nops;
+			j++;
+		}
+
+		if (j > count)
+			count = j;
+	}
+
+	return count + 1;
+}
+
 /*
  * Disassemble an alternative.
  *
@@ -1083,6 +1149,8 @@ static void *disas_alt(struct disas_context *dctx,
 	 * Print default and non-default alternatives.
 	 */
 
+	insn_count = disas_alt_trim_nops(dalts, alt_count, insn_count);
+
 	if (opts.wide)
 		disas_alt_print_wide(alt_name, dalts, alt_count, insn_count);
 	else
-- 
2.43.5


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH v5 22/30] objtool: Disassemble exception table alternatives
  2025-11-19 14:32 ` [PATCH v5 22/30] objtool: Disassemble exception table alternatives Alexandre Chartre
@ 2025-11-20 17:28   ` Josh Poimboeuf
  0 siblings, 0 replies; 35+ messages in thread
From: Josh Poimboeuf @ 2025-11-20 17:28 UTC (permalink / raw)
  To: Alexandre Chartre; +Cc: linux-kernel, mingo, peterz, david.laight.linux

On Wed, Nov 19, 2025 at 03:32:39PM +0100, Alexandre Chartre wrote:
> When using the --disas option, also disable exception tables (EX_TABLE)

"disassemble" (and the same typo in the next patch)

-- 
Josh

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v5 00/30] objtool: Function validation tracing
  2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
                   ` (29 preceding siblings ...)
  2025-11-19 14:32 ` [PATCH v5 30/30] objtool: Trim trailing NOPs in alternative Alexandre Chartre
@ 2025-11-20 17:30 ` Josh Poimboeuf
  2025-11-20 17:37   ` Alexandre Chartre
  2025-11-21 10:28   ` Peter Zijlstra
  30 siblings, 2 replies; 35+ messages in thread
From: Josh Poimboeuf @ 2025-11-20 17:30 UTC (permalink / raw)
  To: Alexandre Chartre; +Cc: linux-kernel, mingo, peterz, david.laight.linux

On Wed, Nov 19, 2025 at 03:32:17PM +0100, Alexandre Chartre 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
> 
> Patches are now based on tip/master.
> 
> I am deferring the following changes to future patches:
> - Josh: convert --disas option to subcommand
> - David: provide branch distance for small branches

Git shows the following untracked files

	tools/objtool/FEATURE-DUMP.objtool
	tools/objtool/arch/x86/lib/
	tools/objtool/feature/

so tools/objtool/.gitignore needs an update.

If you update .gitignore for the relevant patches, and fix the typos I
pointed out, I think we can go ahead and merge this thing.

-- 
Josh

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v5 00/30] objtool: Function validation tracing
  2025-11-20 17:30 ` [PATCH v5 00/30] objtool: Function validation tracing Josh Poimboeuf
@ 2025-11-20 17:37   ` Alexandre Chartre
  2025-11-21 10:28   ` Peter Zijlstra
  1 sibling, 0 replies; 35+ messages in thread
From: Alexandre Chartre @ 2025-11-20 17:37 UTC (permalink / raw)
  To: Josh Poimboeuf
  Cc: alexandre.chartre, linux-kernel, mingo, peterz,
	david.laight.linux



On 11/20/25 18:30, Josh Poimboeuf wrote:
> On Wed, Nov 19, 2025 at 03:32:17PM +0100, Alexandre Chartre 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
>>
>> Patches are now based on tip/master.
>>
>> I am deferring the following changes to future patches:
>> - Josh: convert --disas option to subcommand
>> - David: provide branch distance for small branches
> 
> Git shows the following untracked files
> 
> 	tools/objtool/FEATURE-DUMP.objtool
> 	tools/objtool/arch/x86/lib/
> 	tools/objtool/feature/
> 
> so tools/objtool/.gitignore needs an update.
> 
> If you update .gitignore for the relevant patches, and fix the typos I
> pointed out, I think we can go ahead and merge this thing.
> 

Good. I will fix all that and resend.

Thanks,

alex.


^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH v5 00/30] objtool: Function validation tracing
  2025-11-20 17:30 ` [PATCH v5 00/30] objtool: Function validation tracing Josh Poimboeuf
  2025-11-20 17:37   ` Alexandre Chartre
@ 2025-11-21 10:28   ` Peter Zijlstra
  1 sibling, 0 replies; 35+ messages in thread
From: Peter Zijlstra @ 2025-11-21 10:28 UTC (permalink / raw)
  To: Josh Poimboeuf; +Cc: Alexandre Chartre, linux-kernel, mingo, david.laight.linux

On Thu, Nov 20, 2025 at 09:30:46AM -0800, Josh Poimboeuf wrote:

> If you update .gitignore for the relevant patches, and fix the typos I
> pointed out, I think we can go ahead and merge this thing.

I'll presume this to be an ACK for the patches :-) Let me go queue up
the amended patches.

^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2025-11-21 10:28 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-19 14:32 [PATCH v5 00/30] objtool: Function validation tracing Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 01/30] objtool: Move disassembly functions to a separated file Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 02/30] objtool: Create disassembly context Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 03/30] objtool: Disassemble code with libopcodes instead of running objdump Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 04/30] tool build: Remove annoying newline in build output Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 05/30] objtool: Print symbol during disassembly Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 06/30] objtool: Store instruction disassembly result Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 07/30] objtool: Disassemble instruction on warning or backtrace Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 08/30] objtool: Extract code to validate instruction from the validate branch loop Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 09/30] objtool: Record symbol name max length Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 10/30] objtool: Add option to trace function validation Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 11/30] objtool: Trace instruction state changes during " Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 12/30] objtool: Improve register reporting " Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 13/30] objtool: Identify the different types of alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 14/30] objtool: Add functions to better name alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 15/30] objtool: Improve tracing of alternative instructions Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 16/30] objtool: Do not validate IBT for .return_sites and .call_sites Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 17/30] objtool: Add the --disas=<function-pattern> action Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 18/30] objtool: Preserve alternatives order Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 19/30] objtool: Print headers for alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 20/30] objtool: Disassemble group alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 21/30] objtool: Print addresses with alternative instructions Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 22/30] objtool: Disassemble exception table alternatives Alexandre Chartre
2025-11-20 17:28   ` Josh Poimboeuf
2025-11-19 14:32 ` [PATCH v5 23/30] objtool: Disassemble jump " Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 24/30] objtool: Fix address references in alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 25/30] objtool: Provide access to feature and flags of group alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 26/30] objtool: Function to get the name of a CPU feature Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 27/30] objtool: Improve naming of group alternatives Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 28/30] objtool: Compact output for alternatives with one instruction Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 29/30] objtool: Add wide output for disassembly Alexandre Chartre
2025-11-19 14:32 ` [PATCH v5 30/30] objtool: Trim trailing NOPs in alternative Alexandre Chartre
2025-11-20 17:30 ` [PATCH v5 00/30] objtool: Function validation tracing Josh Poimboeuf
2025-11-20 17:37   ` Alexandre Chartre
2025-11-21 10:28   ` Peter Zijlstra

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox