When objtool writes changes back to vmlinux with --ftr-fixup --link,
libelf recalculates the file layout and inserts padding between
sections. This corrupts the ELF segment structure, causing kexec to
fail with:
ELF Note corrupted !
Cannot determine the file type of vmlinux
This happens because libelf's default behavior assumes it can freely
rearrange section offsets when writing, which breaks the carefully
constructed vmlinux layout produced by the linker.
Set ELF_F_LAYOUT after elf_begin() to instruct libelf that the
application is responsible for the file layout. This prevents libelf
from inserting padding or repositioning sections while still allowing
data modifications to be written back.
Signed-off-by: Sathvika Vasireddy <redacted>
---
tools/objtool/elf.c | 3 +++
1 file changed, 3 insertions(+)
Objtool throws a lot of can't find starting instruction warnings
when run on vmlinux with --ftr-fixup option.
These warnings are seen because find_insn() function looks for
instructions at offsets that are relative to the start of the section.
In case of individual object files (.o), there are no can't find
starting instruction warnings seen because the actual offset
associated with an instruction is itself a relative offset since the
sections start at offset 0x0.
However, in case of vmlinux, find_insn() function fails to find
instructions at the actual offset associated with an instruction
since the sections in vmlinux do not start at offset 0x0. Due to
this, find_insn() will look for absolute offset and not the relative
offset. This is resulting in a lot of can't find starting instruction
warnings when objtool is run on vmlinux.
To fix this, pass offset that is relative to the start of the section
to find_insn().
find_insn() is also looking for symbols of size 0. But, objtool does
not store empty STT_NOTYPE symbols in the rbtree. Due to this,
for empty symbols, objtool is throwing can't find starting
instruction warnings. Fix this by ignoring symbols that are of
size 0 since objtool does not add them to the rbtree.
Signed-off-by: Sathvika Vasireddy <redacted>
---
tools/objtool/check.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
PowerPC __ftr_alt* sections must currently be placed near .text because
they lack the executable attribute, preventing the linker from inserting
stubs. Branches in the alternate code must reach their targets directly.
This can cause build failures as the kernel grows.
Fix this by processing alternate feature relocations at build time with
objtool. Link with --emit-relocs to preserve relocation information,
mark __ftr_alt* sections executable ("ax") so the linker can insert
stubs, then use objtool --ftr-fixup to recompute branch offsets based
on their runtime locations.
objtool already has ELF parsing and instruction patching infrastructure,
so this avoids introducing a separate tool as was originally proposed [1].
[1] https://lore.kernel.org/linuxppc-dev/20170521010130.13552-1-npiggin@gmail.com/
Co-developed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Sathvika Vasireddy <redacted>
---
tools/objtool/arch/powerpc/decode.c | 15 +-
tools/objtool/arch/powerpc/special.c | 451 ++++++++++++++++++++++++
tools/objtool/builtin-check.c | 2 +
tools/objtool/check.c | 29 +-
tools/objtool/elf.c | 1 +
tools/objtool/include/objtool/builtin.h | 1 +
tools/objtool/include/objtool/special.h | 56 +++
tools/objtool/special.c | 29 ++
8 files changed, 582 insertions(+), 2 deletions(-)
@@ -77,7 +90,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *secimm=ins&0x3fffffc;if(imm&0x2000000)imm-=0x4000000;-imm|=ins&2;/* AA flag */+insn->immediate=imm|aa;break;}
@@ -23,3 +33,444 @@ const char *arch_cpu_feature_name(int feature_number){returnNULL;}+++intprocess_alt_data(structobjtool_file*file)+{+structsection*section;++section=find_section_by_name(file->elf,".__ftr_alternates.text");+ftr_alt=section;++if(!ftr_alt){+if(opts.link)+WARN(".__ftr_alternates.text section not found in vmlinux\n");+returnopts.link?-1:0;+}++fe_alt_start=ftr_alt->sh.sh_addr;+fe_alt_end=ftr_alt->sh.sh_addr+ftr_alt->sh.sh_size;++return0;++}++staticintis_le(structobjtool_file*file)+{+returnfile->elf->ehdr.e_ident[EI_DATA]==ELFDATA2LSB;+}++staticintis_64bit(structobjtool_file*file)+{+returnfile->elf->ehdr.e_ident[EI_CLASS]==ELFCLASS64;+}++staticuint32_tf32_to_cpu(structobjtool_file*file,uint32_tval)+{+if(is_le(file))+return__le32_to_cpu(val);+else+return__be32_to_cpu(val);+}++staticuint64_tf64_to_cpu(structobjtool_file*file,uint64_tval)+{+if(is_le(file))+return__le64_to_cpu(val);+else+return__be64_to_cpu(val);+}++staticuint32_tcpu_to_f32(structobjtool_file*file,uint32_tval)+{+if(is_le(file))+return__cpu_to_le32(val);+else+return__cpu_to_be32(val);+}++intprocess_fixup_entries(structobjtool_file*file)+{+structsection*sec;+inti;++for_each_sec(file->elf,sec){+Elf_Data*data;+unsignedintnr;++if(strstr(sec->name,"_ftr_fixup")==NULL)+continue;++if(strstr(sec->name,".rela")!=NULL)+continue;++data=sec->data;+if(!data||data->d_size==0)+continue;++if(is_64bit(file))+nr=data->d_size/sizeof(structfixup_entry_64);+else+nr=data->d_size/sizeof(structfixup_entry_32);++for(i=0;i<nr;i++){+unsignedlongidx;+unsignedlonglongoff;+structfixup_entry*dst;++if(is_64bit(file)){+structfixup_entry_64*src;++idx=i*sizeof(structfixup_entry_64);+off=sec->sh.sh_addr+data->d_off+idx;+src=data->d_buf+idx;++if(src->alt_start_off==src->alt_end_off)+continue;++structfixup_entry*tmp=realloc(fes,(nr_fes+1)+*sizeof(structfixup_entry));++if(!tmp){+free(fes);+fes=NULL;+return-1;+}+fes=tmp;+dst=&fes[nr_fes++];++dst->mask=f64_to_cpu(file,src->mask);+dst->value=f64_to_cpu(file,src->value);+dst->start_off=f64_to_cpu(file,src->start_off)+off;+dst->end_off=f64_to_cpu(file,src->end_off)+off;+dst->alt_start_off=f64_to_cpu(file,src->alt_start_off)+off;+dst->alt_end_off=f64_to_cpu(file,src->alt_end_off)+off;+}else{+structfixup_entry_32*src;++idx=i*sizeof(structfixup_entry_32);+off=sec->sh.sh_addr+data->d_off+idx;+src=data->d_buf+idx;++if(src->alt_start_off==src->alt_end_off)+continue;++structfixup_entry*tmp=realloc(fes,(nr_fes+1)+*sizeof(structfixup_entry));++if(!tmp){+free(fes);+fes=NULL;+return-1;+}+fes=tmp;+dst=&fes[nr_fes++];++dst->mask=f32_to_cpu(file,src->mask);+dst->value=f32_to_cpu(file,src->value);+dst->start_off=(int32_t)f32_to_cpu(file,src->start_off)+off;+dst->end_off=(int32_t)f32_to_cpu(file,src->end_off)+off;+dst->alt_start_off=(int32_t)f32_to_cpu(file,+src->alt_start_off)+off;+dst->alt_end_off=(int32_t)f32_to_cpu(file,+src->alt_end_off)+off;+}+}+}+return0;+}++structfixup_entry*find_fe_altaddr(uint64_taddr)+{+unsignedinti;++if(addr<fe_alt_start)+returnNULL;+if(addr>=fe_alt_end)+returnNULL;++for(i=0;i<nr_fes;i++){+if(addr>=fes[i].alt_start_off&&addr<fes[i].alt_end_off)+return&fes[i];+}+returnNULL;+}++intset_uncond_branch_target(uint32_t*insn,+constuint64_taddr,uint64_ttarget)+{+uint32_ti=*insn;+int64_toffset;++offset=target;+if(!(i&BRANCH_ABSOLUTE))+offset=offset-addr;++/* Check we can represent the target in the instruction format */+if(offset<-0x2000000||offset>0x1fffffc||offset&0x3)+return-EOVERFLOW;++/* Mask out the flags and target, so they don't step on each other. */+*insn=0x48000000|(i&0x3)|(offset&0x03FFFFFC);++return0;+}++intset_cond_branch_target(uint32_t*insn,+constuint64_taddr,uint64_ttarget)+{+uint32_ti=*insn;+int64_toffset;++offset=target;++if(!(i&BRANCH_ABSOLUTE))+offset=offset-addr;++/* Check we can represent the target in the instruction format */+if(offset<-0x8000||offset>0x7FFF||offset&0x3)+return-EOVERFLOW;++/* Mask out the flags and target, so they don't step on each other. */+*insn=0x40000000|(i&0x3FF0003)|(offset&0xFFFC);++return0;+}++voidcheck_and_flatten_fixup_entries(void)+{+staticstructfixup_entry*fe;+unsignedinti;++for(i=0;i<nr_fes;i++){+structfixup_entry*parent;+uint64_tnested_off;+uint64_tsize;++fe=&fes[i];++parent=find_fe_altaddr(fe->start_off);+if(!parent)+continue;++size=fe->end_off-fe->start_off;+nested_off=fe->start_off-parent->alt_start_off;++fe->start_off=parent->start_off+nested_off;+fe->end_off=fe->start_off+size;+}+}+++staticstructsymbol*find_symbol_at_address_within_section(structsection*sec,+unsignedlongaddress)+{+structsymbol*sym;++sec_for_each_sym(sec,sym){+if(sym->sym.st_value<=address&&address<sym->sym.st_value+sym->len)+returnsym;+}++returnNULL;+}++staticintis_local_symbol(uint8_tst_other)+{+/* STO_PPC64_LOCAL_MASK: bits [7:5] encode the local entry point offset */+return(st_other&(7<<5))==0;+}++staticstructsymbol*find_symbol_at_address(structobjtool_file*file,+unsignedlongaddress)+{+structsection*sec;+structsymbol*sym;++list_for_each_entry(sec,&file->elf->sections,list){+sym=find_symbol_at_address_within_section(sec,address);+if(sym)+returnsym;+}+returnNULL;+}++intprocess_alt_relocations(structobjtool_file*file)+{+structsection*section;+size_tn=0;+structreloc*relocation;+structsymbol*sym;+structfixup_entry*fe;+uint64_taddr;+uint64_tscn_delta;+uint64_tdst_addr;+constchar*insn_ptr;+unsignedlongtarget;+structsymbol*symbol;+intis_local;+intj;+uint32_tnew_insn;+uint32_tfile_insn;+structinstructiondecoded_insn={0};+uint32_t*insn_ptr_raw;+uint32_tinsn;++section=find_section_by_name(file->elf,".rela.__ftr_alternates.text");+if(!section){+printf(".rela.__ftr_alternates.text section not found.\n");+return0;+}++for(j=0;j<sec_num_entries(section);j++){++relocation=§ion->relocs[j];+sym=relocation->sym;++addr=reloc_offset(relocation);+target=sym->sym.st_value+reloc_addend(relocation);+symbol=find_symbol_at_address(file,target);++if(symbol){+is_local=is_local_symbol(symbol->sym.st_other);+if(!is_local)+target=target+0x8;+}++n++;+fe=find_fe_altaddr(addr);+if(!fe)+continue;++if(target>=fe->alt_start_off&&target<fe->alt_end_off)+continue;++if(target>=ftr_alt->sh.sh_addr&&+target<ftr_alt->sh.sh_addr+ftr_alt->sh.sh_size)+return-1;++scn_delta=addr-ftr_alt->sh.sh_addr;+dst_addr=addr-fe->alt_start_off+fe->start_off;++if(!ftr_alt->data||!ftr_alt->data->d_buf)+continue;++if(arch_decode_instruction(file,ftr_alt,scn_delta,4,&decoded_insn)<0)+continue;++insn_ptr_raw=(uint32_t*)(ftr_alt->data->d_buf+scn_delta);++insn=f32_to_cpu(file,*insn_ptr_raw);+new_insn=insn;++switch(decoded_insn.type){+caseINSN_JUMP_CONDITIONAL:+if(set_cond_branch_target(&new_insn,dst_addr,target)!=0)+continue;+break;++caseINSN_JUMP_UNCONDITIONAL:+if(set_uncond_branch_target(&new_insn,dst_addr,target)!=0)+continue;+break;++caseINSN_CALL:+if(set_uncond_branch_target(&new_insn,dst_addr,target)!=0)+continue;+break;+default:+continue;+}++if(new_insn==insn)+continue;++file_insn=cpu_to_f32(file,new_insn);+insn_ptr=(constchar*)&file_insn;+elf_write_insn(file->elf,ftr_alt,scn_delta,sizeof(file_insn),insn_ptr);+}+return0;+}++intprocess_exception_entries(structobjtool_file*file)+{+structsection*section;+Elf_Data*data;+unsignedintnr,i;++section=find_section_by_name(file->elf,"__ex_table");+if(!section){+printf("__ex_table section not found\n");+return0;+}++data=section->data;+if(!data||data->d_size==0)+return0;++nr=data->d_size/sizeof(structexception_entry);++for(i=0;i<nr;i++){+structexception_entry*ex;+unsignedlongidx;+uint64_texaddr;+unsignedlonglongoff;++idx=i*sizeof(structexception_entry);+off=section->sh.sh_addr+data->d_off+idx;+ex=data->d_buf+idx;++exaddr=off+(int32_t)f32_to_cpu(file,ex->insn);++if(exaddr<fe_alt_start)+continue;+if(exaddr>=fe_alt_end)+continue;++return-1;+}+return0;+}++intprocess_bug_entries(structobjtool_file*file)+{+structsection*section;+Elf_Data*data;+unsignedintnr,i;++section=find_section_by_name(file->elf,"__bug_table");+if(!section){+printf("__bug_table section not found\n");+return0;+}++data=section->data;+if(!data||data->d_size==0)+return0;++if(data->d_size%sizeof(structbug_entry)!=0)+return-1;++nr=data->d_size/sizeof(structbug_entry);+for(i=0;i<nr;i++){+structbug_entry*bug;+unsignedlongidx;+uint64_tentry_addr;+uint64_tbugaddr;+int32_tbug_disp;++idx=i*sizeof(structbug_entry);+entry_addr=section->sh.sh_addr+data->d_off+idx;+bug=(structbug_entry*)(data->d_buf+idx);+bug_disp=f32_to_cpu(file,bug->bug_addr_disp);+bugaddr=entry_addr+bug_disp;++if(bugaddr<fe_alt_start)+continue;+if(bugaddr>=fe_alt_end)+continue;++return-1;+}+return0;+}
@@ -1636,9 +1636,11 @@ static int add_jump_destinations(struct objtool_file *file)dest_off==func->offset+func->len)continue;+ERROR_INSN(insn,"can't find jump dest instruction at %s",-offstr(dest_sec,dest_off));+offstr(dest_sec,dest_off));return-1;+}if(!dest_sym||is_sec_sym(dest_sym)){
@@ -5021,6 +5023,31 @@ int check(struct objtool_file *file)if(!nr_insns)gotoout;+if(opts.ftr_fixup){+ret=process_alt_data(file);+if(ret<0)+returnret;++ret=process_fixup_entries(file);+if(ret<0)+returnret;++check_and_flatten_fixup_entries();++ret=process_exception_entries(file);+if(ret<0)+returnret;++ret=process_bug_entries(file);+if(ret<0)+returnret;++ret=process_alt_relocations(file);+if(ret<0)+returnret;+}++if(opts.retpoline)warnings+=validate_retpoline(file);
@@ -28,6 +63,8 @@ struct special_alt {unsignedintorig_len,new_len,feature;/* group only */};+intprocess_alt_data(structobjtool_file*file);+intspecial_get_alts(structelf*elf,structlist_head*alts);voidarch_handle_alternative(structspecial_alt*alt);
@@ -1478,6 +1478,13 @@ ifdef CONFIG_OBJTOOLprepare:tools/objtoolendif+# CONFIG_OBJTOOL and CONFIG_HAVE_OBJTOOL_FTR_FIXUP are unrelated, separate+# options. It was integrated in objtool in order to borrow the elf parser,+# but this is different from how the other objtool commands are used.+ifdef CONFIG_HAVE_OBJTOOL_FTR_FIXUP+prepare:tools/objtool+endif+ifdef CONFIG_BPFifdef CONFIG_DEBUG_INFO_BTFprepare:tools/bpf/resolve_btfids
@@ -65,7 +65,8 @@ ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)# Final link of vmlinux with optional arch pass after final linkcmd_link_vmlinux=\$<"$(LD)""$(KBUILD_LDFLAGS)""$(LDFLAGS_vmlinux)""$@";\-$(if$(ARCH_POSTLINK),$(MAKE)-f$(ARCH_POSTLINK)$@,true)+$(if$(ARCH_POSTLINK),$(MAKE)-f$(ARCH_POSTLINK)$@,true);\+$(if$(CONFIG_HAVE_OBJTOOL_FTR_FIXUP),$(objtool)--ftr-fixup--link$@,true)targets+=vmlinux.unstripped.vmlinux.export.ovmlinux.unstripped:scripts/link-vmlinux.shvmlinux.o .vmlinux.export.o$(KBUILD_LDS)FORCE
@@ -53,22 +53,10 @@ static u32 *calc_addr(struct fixup_entry *fcur, long offset)staticintpatch_alt_instruction(u32*src,u32*dest,u32*alt_start,u32*alt_end){-interr;ppc_inst_tinstr;instr=ppc_inst_read(src);-if(instr_is_relative_branch(ppc_inst_read(src))){-u32*target=(u32*)branch_target(src);--/* Branch within the section doesn't need translating */-if(target<alt_start||target>alt_end){-err=translate_branch(&instr,dest,src);-if(err)-return1;-}-}-raw_patch_instruction(dest,instr);return0;
@@ -82,11 +82,15 @@ endif# vmlinux# ---------------------------------------------------------------------------+# These configurations require vmlinux.unstripped to be linked with+# '--emit-relocs', which need to be stripped from the final vmlinux.+uses-emit-relocs:=$(or$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS),$(CONFIG_HAVE_OBJTOOL_FTR_FIXUP))+remove-section-y:=.modinfo-remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS)+='.rel*''!.rel*.dyn'+remove-section-$(uses-emit-relocs)+='.rel*''!.rel*.dyn'# for compatibility with binutils < 2.32# https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=c12d9fa2afe7abcbe407a00e15719e1a1350c2a7-remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS)+='.rel.*'+remove-section-$(uses-emit-relocs)+='.rel.*'remove-symbols:=-w--strip-unneeded-symbol='__mod_device_table__*'
Objtool is throwing unannotated intra-function call warnings
when run on vmlinux with --ftr-fixup option.
One such example:
vmlinux: warning: objtool: .text+0x3d94:
unannotated intra-function call
.text + 0x3d94 = c000000000008000 + 3d94 = c0000000000081d4
c0000000000081d4: 45 24 02 48 bl c00000000002a618
<system_reset_exception+0x8>
c00000000002a610 <system_reset_exception>:
c00000000002a610: 0e 01 4c 3c addis r2,r12,270
c00000000002a610: R_PPC64_REL16_HA .TOC.
c00000000002a614: f0 6c 42 38 addi r2,r2,27888
c00000000002a614: R_PPC64_REL16_LO .TOC.+0x4
c00000000002a618: a6 02 08 7c mflr r0
This is happening because we should be looking for destination
symbols that are at absolute offsets instead of relative offsets.
After fixing dest_off to point to absolute offset, there are still
a lot of these warnings shown.
In the above example, objtool is computing the destination
offset to be c00000000002a618, which points to a completely
different instruction. find_call_destination() is looking for this
offset and failing. Instead, we should be looking for destination
offset c00000000002a610 which points to system_reset_exception
function.
Even after fixing the way destination offset is computed, and
after looking for dest_off - 0x8 in cases where the original offset
is not found, there are still a lot of unannotated intra-function
call warnings generated. This is due to symbols that are not
properly annotated.
So, for now, as a hack to curb these warnings, do not emit
unannotated intra-function call warnings when objtool is run
with --ftr-fixup option.
Skip add_jump_destinations() and suppress intra-function call
errors in --ftr-fixup mode. The feature fixup pass only needs fixup
entry tables, relocation entries from .__ftr_alternates.text, and
elf_write_insn() to patch branch offsets.
Signed-off-by: Sathvika Vasireddy <redacted>
---
tools/objtool/check.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
@@ -1644,7 +1644,6 @@ static int add_jump_destinations(struct objtool_file *file)dest_off==func->offset+func->len)continue;-ERROR_INSN(insn,"can't find jump dest instruction at %s",offstr(dest_sec,dest_off));return-1;
Le 05/05/2026 à 10:46, Sathvika Vasireddy a écrit :
This patch series implements build-time fixup of alternate feature
relative addresses for powerpc.
Previously, Nicholas Piggin proposed a build-time solution using a
custom PowerPC tool [1], which provided the foundation for this approach.
The current implementation leverages objtool's existing ELF parsing
infrastructure to do the same.
From: Peter Zijlstra <peterz@infradead.org> Date: 2026-05-05 11:41:20
On Tue, May 05, 2026 at 02:16:22PM +0530, Sathvika Vasireddy wrote:
This patch series implements build-time fixup of alternate feature
relative addresses for powerpc.
Previously, Nicholas Piggin proposed a build-time solution using a
custom PowerPC tool [1], which provided the foundation for this approach.
The current implementation leverages objtool's existing ELF parsing
infrastructure to do the same.
This patchset applies atop powerpc/merge branch.
[1] Original PowerPC tool approach:
http://patchwork.ozlabs.org/project/linuxppc-dev/patch/20170521010130.13552-1-npiggin@gmail.com/
Testing:
Build and Boot tested on ppc64le, ppc64be, and ppc32be configs.
Sathvika Vasireddy (6):
objtool/powerpc: Add build-time fixup of alternate feature branch
targets
objtool: Set ELF_F_LAYOUT flag to preserve vmlinux segment layout
objtool: Fix "can't find starting instruction" warnings on vmlinux
objtool/powerpc: Skip jump destination analysis and unnanotated
intra-function call warnings for --ftr-fixup
kbuild: Add objtool integration for PowerPC feature fixups
powerpc: Enable build-time feature fixup processing by default
Ooh, so 2/3 are because you're not running objtool on vmlinux.o, but
you're running it later in the link stage?
AFAICT ELF_F_LAYOUT should not hurt the normal objtool run, but it
shouldn't be needed either, because we run much earlier, before sections
are placed.
And that also explains why you're the first to have trouble with 'can't
find starting instruction'. vmlinux.o will have unplaced sections that
are all relative 0.
Hmm... perhaps this ftr fixup should be its own command, like klp. Not
munged into check thing. Josh?
On Tue, May 05, 2026 at 04:45:39PM +0200, Peter Zijlstra wrote:
quoted
On Tue, May 05, 2026 at 02:16:23PM +0530, Sathvika Vasireddy wrote:
quoted
switch (opcode) {
+ case 16:
Like case 18 below, this wants a comment describing which instruction
this is; bclr ?
No, it is bc/bca/bcl/bcla, it is quite similar to type 18 which is b/ba/bl/bla, but that's a conditional branch to a 14-bit offset while type 18 is a non-conditional branch to a 24-bit offset (10 bits are the conditional in the bc, hence the reduced offset size).
Also, isn't that typically used for return, suggesting at least
something should have INSN_RETURN on?
Hi!
On Tue, May 05, 2026 at 04:45:39PM +0200, Peter Zijlstra wrote:
On Tue, May 05, 2026 at 02:16:23PM +0530, Sathvika Vasireddy wrote:
quoted
switch (opcode) {
+ case 16:
Like case 18 below, this wants a comment describing which instruction
this is; bclr ?
Yes. It is 19/16, b[c]lr (primary opcode 19, secondary opcode 16).
Where is it described what INSN_RETURN actually means for objtool? Not
in the header file :-(
For PowerPC, all three of b[c]lr[l], b[c]ctr[l], and b[c]tar[l] are
indirect jumps, to an address in a register. They do absolutely nothing
typically done in a function epilog, like "return" insns in some other
archs do. But the BH field ("branch hint") in the instruction helps
predict the control flow involving the insn as something in a call and
return pattern :-)
Segher
From: Peter Zijlstra <peterz@infradead.org> Date: 2026-05-06 07:12:54
On Tue, May 05, 2026 at 10:56:58AM -0500, Segher Boessenkool wrote:
Hi!
On Tue, May 05, 2026 at 04:45:39PM +0200, Peter Zijlstra wrote:
quoted
On Tue, May 05, 2026 at 02:16:23PM +0530, Sathvika Vasireddy wrote:
quoted
switch (opcode) {
+ case 16:
Like case 18 below, this wants a comment describing which instruction
this is; bclr ?
Yes. It is 19/16, b[c]lr (primary opcode 19, secondary opcode 16).
Where is it described what INSN_RETURN actually means for objtool? Not
in the header file :-(
Yeah, nowhere much I'm afraid, it is very much organic growth that is
firmly rooted in x86.
RETURN, along with sibling/tail CALLs validate things like the stack
frame being in identical state as on function entry and a few other
sanity checks (DF flag not set, no uaccess).
There is also a pile of hacks around the whole return thunk mitigation
thing. But that might be less relevant for other archs.
Right, that is not something x86 has, but I don't see a reason we can't
add that. With return thunks, Clang (and I've heard GCC is also
considering this) does something very close to conditional return.
With return thunks, regular RET instruction is replaced with a JMP, or
rather Jcc in this specific case, to the out of line __x86_return_thunk
symbol. Objtool currently treats that as a conditional sibling/tail
call, which isn't wrong.
Anyway, with that patch, I think validate_insn() wants something like so
added:
case INSN_COND_RETURN:
validate_return(func, insn, statep);
break;
That way we do the return checks, but don't terminate the control flow.
After all, when the condition is taken, we had better have the stack
frame in the same state etc.
Hi!
On Wed, May 06, 2026 at 09:00:00AM +0200, Peter Zijlstra wrote:
On Tue, May 05, 2026 at 10:56:58AM -0500, Segher Boessenkool wrote:
quoted
On Tue, May 05, 2026 at 04:45:39PM +0200, Peter Zijlstra wrote:
quoted
On Tue, May 05, 2026 at 02:16:23PM +0530, Sathvika Vasireddy wrote:
quoted
switch (opcode) {
+ case 16:
Like case 18 below, this wants a comment describing which instruction
this is; bclr ?
Yes. It is 19/16, b[c]lr (primary opcode 19, secondary opcode 16).
Where is it described what INSN_RETURN actually means for objtool? Not
in the header file :-(
Yeah, nowhere much I'm afraid, it is very much organic growth that is
firmly rooted in x86.
RETURN, along with sibling/tail CALLs validate things like the stack
frame being in identical state as on function entry and a few other
sanity checks (DF flag not set, no uaccess).
Huh.
On function entry, there is *no* accessible stack frame, on our ABIs
(typically you can still access your parent's frame of course, but then
you first need to find out who your parent is, etc.) All stack frames
are always set up by separate store instructions. We are a RISC
architecture after all (POWER means "Performance Optimisation With
Enhanced RISC"). So objtool checks if we actually tore down all
stack frames? What a very useful thing to do.
Stack frames are a software concept in the first place, it has nothing
to do with the hardware, *at all*. This is a bit different on archs
that *actually* have such a thing as a frame pointer, that don't emulate
it using a GPR (or something in memory!)
There is also a pile of hacks around the whole return thunk mitigation
thing. But that might be less relevant for other archs.
I don't really want to think about it :-) Horrors!
There are many other archs where all (or almost all, "all normal", call/
return sequences use a "link register", often called exactly that. It's
the modern consensus to design call/return around that, I'd say even.
It would be nice if this abstraction worked well ;-)
Segher
From: Peter Zijlstra <peterz@infradead.org> Date: 2026-05-06 14:22:43
On Wed, May 06, 2026 at 08:58:57AM -0500, Segher Boessenkool wrote:
Hi!
On Wed, May 06, 2026 at 09:00:00AM +0200, Peter Zijlstra wrote:
quoted
On Tue, May 05, 2026 at 10:56:58AM -0500, Segher Boessenkool wrote:
quoted
On Tue, May 05, 2026 at 04:45:39PM +0200, Peter Zijlstra wrote:
quoted
On Tue, May 05, 2026 at 02:16:23PM +0530, Sathvika Vasireddy wrote:
quoted
switch (opcode) {
+ case 16:
Like case 18 below, this wants a comment describing which instruction
this is; bclr ?
Yes. It is 19/16, b[c]lr (primary opcode 19, secondary opcode 16).
Where is it described what INSN_RETURN actually means for objtool? Not
in the header file :-(
Yeah, nowhere much I'm afraid, it is very much organic growth that is
firmly rooted in x86.
RETURN, along with sibling/tail CALLs validate things like the stack
frame being in identical state as on function entry and a few other
sanity checks (DF flag not set, no uaccess).
Huh.
On function entry, there is *no* accessible stack frame, on our ABIs
(typically you can still access your parent's frame of course, but then
you first need to find out who your parent is, etc.) All stack frames
are always set up by separate store instructions. We are a RISC
architecture after all (POWER means "Performance Optimisation With
Enhanced RISC"). So objtool checks if we actually tore down all
stack frames? What a very useful thing to do.
Stack frames are a software concept in the first place, it has nothing
to do with the hardware, *at all*. This is a bit different on archs
that *actually* have such a thing as a frame pointer, that don't emulate
it using a GPR (or something in memory!)
So, remember that objtool was born to generate ORC stack unwind data.
It needs to track the stack state at every instruction to accomplish
this.
One of the basic sanity checks it does is ensuring there is no lingering
stack state on RETURN -- this would obviously cause the
caller/returned-to context to get a wee bit upset.
And yes, I realize this might sound quite mad to a compiler person. But
remember that we have a ton of ASM (inline and otherwise) mixed in with
our C code. And while you can add DWARF CFI to ASM, this has
historically been a bit of a trainwreck, not in the least because the
annotations got wrong and nothing warned about it until the unwinder
would explode.
Objtool solves all of that, by doing build time reconstruction of the
control flow and stack state it guarantees everything is consistent at
build time. Additionally it can deal with fun things like alternatives
/ static branches etc.
It can deal with both -fframe-pointer and -fomit-frame-pointer (on x86).
When the frame pointer reg (BP) is used, it also validates this is done
correctly.
This has caught many whoopsies over the years.
Then because this thing is parsing object files and decoding control
flow, features were added, like the uaccess scope checks.
quoted
There is also a pile of hacks around the whole return thunk mitigation
thing. But that might be less relevant for other archs.
I don't really want to think about it :-) Horrors!
There are many other archs where all (or almost all, "all normal", call/
return sequences use a "link register", often called exactly that. It's
the modern consensus to design call/return around that, I'd say even.
It would be nice if this abstraction worked well ;-)
Agreed. Its just that this thing has a very strong x86 flavour per its
legacy ;-) We'll get it sorted.
That is indeed more; isn't bcl something like COND_CALL ? (another one
of them things we don't have).
Yeah, all of bc[l][a] are conditional branches. "a" (AA=1) means the
branch target is an absolute address (in the top or bottom 8kB of the
address space), and "l" (LK=1) means set LR to the NIA ("next
instruction address") before doing the branch.
The BO field describes the condition. 0x14 means "always". (But there
also are primary opcodes for b[l][a], with a bigger target field, 24
bits instead of the 14 bits of bc[l][a]; you do not have the BI and BO
fields then of course) ("branch index" and "branch option" btw).
Right, that is not something x86 has, but I don't see a reason we can't
add that. With return thunks, Clang (and I've heard GCC is also
considering this) does something very close to conditional return.
A conditional blr does not map well to most compiled languages. It is
nice for hand-written machine code though :-)
For compiled code you almost always want to make some of the associated
stack frame maintenance conditional as well (because the ABIs require
this, if nothing else!)
I guess we could add some special-case stuff for it, maybe a peephole,
but we probably would have to write a whole new pass for it, stuff with
branch instructions always requires special checks :-p
That way we do the return checks, but don't terminate the control flow.
After all, when the condition is taken, we had better have the stack
frame in the same state etc.
Hi!
On Wed, May 06, 2026 at 04:22:14PM +0200, Peter Zijlstra wrote:
On Wed, May 06, 2026 at 08:58:57AM -0500, Segher Boessenkool wrote:
quoted
Huh.
On function entry, there is *no* accessible stack frame, on our ABIs
(typically you can still access your parent's frame of course, but then
you first need to find out who your parent is, etc.) All stack frames
are always set up by separate store instructions. We are a RISC
architecture after all (POWER means "Performance Optimisation With
Enhanced RISC"). So objtool checks if we actually tore down all
stack frames? What a very useful thing to do.
Stack frames are a software concept in the first place, it has nothing
to do with the hardware, *at all*. This is a bit different on archs
that *actually* have such a thing as a frame pointer, that don't emulate
it using a GPR (or something in memory!)
So, remember that objtool was born to generate ORC stack unwind data.
It needs to track the stack state at every instruction to accomplish
this.
Yup.
One of the basic sanity checks it does is ensuring there is no lingering
stack state on RETURN -- this would obviously cause the
caller/returned-to context to get a wee bit upset.
And yes, I realize this might sound quite mad to a compiler person. But
It doesn't sound weird *at all*, we have to jump through many hoops in
the compiler as well as in the ABIs to make this work at all.
remember that we have a ton of ASM (inline and otherwise) mixed in with
our C code. And while you can add DWARF CFI to ASM, this has
historically been a bit of a trainwreck, not in the least because the
annotations got wrong and nothing warned about it until the unwinder
would explode.
Typically, someone who writes inline assembler code cannot write CFI
statements properly *at all*, he/she just doesn't have all the necessary
information! Only the compiler does. It can put all kinds of stuff on
the stack for example. Writing CFI statements manually is really forr
someone writing machine code, or actual assembler code perhaps.
quoted
There are many other archs where all (or almost all, "all normal", call/
return sequences use a "link register", often called exactly that. It's
the modern consensus to design call/return around that, I'd say even.
It would be nice if this abstraction worked well ;-)
Agreed. Its just that this thing has a very strong x86 flavour per its
legacy ;-) We'll get it sorted.
On Tue, May 05, 2026 at 01:40:50PM +0200, Peter Zijlstra wrote:
On Tue, May 05, 2026 at 02:16:22PM +0530, Sathvika Vasireddy wrote:
quoted
This patch series implements build-time fixup of alternate feature
relative addresses for powerpc.
Previously, Nicholas Piggin proposed a build-time solution using a
custom PowerPC tool [1], which provided the foundation for this approach.
The current implementation leverages objtool's existing ELF parsing
infrastructure to do the same.
This patchset applies atop powerpc/merge branch.
[1] Original PowerPC tool approach:
http://patchwork.ozlabs.org/project/linuxppc-dev/patch/20170521010130.13552-1-npiggin@gmail.com/
Testing:
Build and Boot tested on ppc64le, ppc64be, and ppc32be configs.
Sathvika Vasireddy (6):
objtool/powerpc: Add build-time fixup of alternate feature branch
targets
objtool: Set ELF_F_LAYOUT flag to preserve vmlinux segment layout
objtool: Fix "can't find starting instruction" warnings on vmlinux
objtool/powerpc: Skip jump destination analysis and unnanotated
intra-function call warnings for --ftr-fixup
kbuild: Add objtool integration for PowerPC feature fixups
powerpc: Enable build-time feature fixup processing by default
Ooh, so 2/3 are because you're not running objtool on vmlinux.o, but
you're running it later in the link stage?
AFAICT ELF_F_LAYOUT should not hurt the normal objtool run, but it
shouldn't be needed either, because we run much earlier, before sections
are placed.
And that also explains why you're the first to have trouble with 'can't
find starting instruction'. vmlinux.o will have unplaced sections that
are all relative 0.
Hmm... perhaps this ftr fixup should be its own command, like klp. Not
munged into check thing. Josh?
Hi Peter,
As the original author for the patch series will not be working on this
feature anymore, I'd like to take over and move this feature forward.
Your point about only using objtool at object stage, makes sense.
Would you prefer if I rework this as a saperate tool then?
Regards
~Shivang