Skip to content

Commit 4abff6d

Browse files
author
Peter Zijlstra
committed
objtool: Fix code relocs vs weak symbols
Occasionally objtool driven code patching (think .static_call_sites .retpoline_sites etc..) goes sideways and it tries to patch an instruction that doesn't match. Much head-scatching and cursing later the problem is as outlined below and affects every section that objtool generates for us, very much including the ORC data. The below uses .static_call_sites because it's convenient for demonstration purposes, but as mentioned the ORC sections, .retpoline_sites and __mount_loc are all similarly affected. Consider: foo-weak.c: extern void __SCT__foo(void); __attribute__((weak)) void foo(void) { return __SCT__foo(); } foo.c: extern void __SCT__foo(void); extern void my_foo(void); void foo(void) { my_foo(); return __SCT__foo(); } These generate the obvious code (gcc -O2 -fcf-protection=none -fno-asynchronous-unwind-tables -c foo*.c): foo-weak.o: 0000000000000000 <foo>: 0: e9 00 00 00 00 jmpq 5 <foo+0x5> 1: R_X86_64_PLT32 __SCT__foo-0x4 foo.o: 0000000000000000 <foo>: 0: 48 83 ec 08 sub $0x8,%rsp 4: e8 00 00 00 00 callq 9 <foo+0x9> 5: R_X86_64_PLT32 my_foo-0x4 9: 48 83 c4 08 add $0x8,%rsp d: e9 00 00 00 00 jmpq 12 <foo+0x12> e: R_X86_64_PLT32 __SCT__foo-0x4 Now, when we link these two files together, you get something like (ld -r -o foos.o foo-weak.o foo.o): foos.o: 0000000000000000 <foo-0x10>: 0: e9 00 00 00 00 jmpq 5 <foo-0xb> 1: R_X86_64_PLT32 __SCT__foo-0x4 5: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:0x0(%rax,%rax,1) f: 90 nop 0000000000000010 <foo>: 10: 48 83 ec 08 sub $0x8,%rsp 14: e8 00 00 00 00 callq 19 <foo+0x9> 15: R_X86_64_PLT32 my_foo-0x4 19: 48 83 c4 08 add $0x8,%rsp 1d: e9 00 00 00 00 jmpq 22 <foo+0x12> 1e: R_X86_64_PLT32 __SCT__foo-0x4 Noting that ld preserves the weak function text, but strips the symbol off of it (hence objdump doing that funny negative offset thing). This does lead to 'interesting' unused code issues with objtool when ran on linked objects, but that seems to be working (fingers crossed). So far so good.. Now lets consider the objtool static_call output section (readelf output, old binutils): foo-weak.o: Relocation section '.rela.static_call_sites' at offset 0x2c8 contains 1 entry: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 .text + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foo.o: Relocation section '.rela.static_call_sites' at offset 0x310 contains 2 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 .text + d 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foos.o: Relocation section '.rela.static_call_sites' at offset 0x430 contains 4 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000100000002 R_X86_64_PC32 0000000000000000 .text + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 0000000000000008 0000000100000002 R_X86_64_PC32 0000000000000000 .text + 1d 000000000000000c 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 So we have two patch sites, one in the dead code of the weak foo and one in the real foo. All is well. *HOWEVER*, when the toolchain strips unused section symbols it generates things like this (using new enough binutils): foo-weak.o: Relocation section '.rela.static_call_sites' at offset 0x2c8 contains 1 entry: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 foo + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foo.o: Relocation section '.rela.static_call_sites' at offset 0x310 contains 2 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000200000002 R_X86_64_PC32 0000000000000000 foo + d 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 foos.o: Relocation section '.rela.static_call_sites' at offset 0x430 contains 4 entries: Offset Info Type Symbol's Value Symbol's Name + Addend 0000000000000000 0000000100000002 R_X86_64_PC32 0000000000000000 foo + 0 0000000000000004 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 0000000000000008 0000000100000002 R_X86_64_PC32 0000000000000000 foo + d 000000000000000c 0000000d00000002 R_X86_64_PC32 0000000000000000 __SCT__foo + 1 And now we can see how that foos.o .static_call_sites goes side-ways, we now have _two_ patch sites in foo. One for the weak symbol at foo+0 (which is no longer a static_call site!) and one at foo+d which is in fact the right location. This seems to happen when objtool cannot find a section symbol, in which case it falls back to any other symbol to key off of, however in this case that goes terribly wrong! As such, teach objtool to create a section symbol when there isn't one. Fixes: 44f6a7c ("objtool: Fix seg fault with Clang non-section symbols") Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Acked-by: Josh Poimboeuf <jpoimboe@redhat.com> Link: https://lkml.kernel.org/r/20220419203807.655552918@infradead.org
1 parent c087c6e commit 4abff6d

File tree

1 file changed

+165
-22
lines changed

1 file changed

+165
-22
lines changed

tools/objtool/elf.c

Lines changed: 165 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -575,37 +575,180 @@ int elf_add_reloc(struct elf *elf, struct section *sec, unsigned long offset,
575575
return 0;
576576
}
577577

578-
int elf_add_reloc_to_insn(struct elf *elf, struct section *sec,
579-
unsigned long offset, unsigned int type,
580-
struct section *insn_sec, unsigned long insn_off)
578+
/*
579+
* Ensure that any reloc section containing references to @sym is marked
580+
* changed such that it will get re-generated in elf_rebuild_reloc_sections()
581+
* with the new symbol index.
582+
*/
583+
static void elf_dirty_reloc_sym(struct elf *elf, struct symbol *sym)
584+
{
585+
struct section *sec;
586+
587+
list_for_each_entry(sec, &elf->sections, list) {
588+
struct reloc *reloc;
589+
590+
if (sec->changed)
591+
continue;
592+
593+
list_for_each_entry(reloc, &sec->reloc_list, list) {
594+
if (reloc->sym == sym) {
595+
sec->changed = true;
596+
break;
597+
}
598+
}
599+
}
600+
}
601+
602+
/*
603+
* Move the first global symbol, as per sh_info, into a new, higher symbol
604+
* index. This fees up the shndx for a new local symbol.
605+
*/
606+
static int elf_move_global_symbol(struct elf *elf, struct section *symtab,
607+
struct section *symtab_shndx)
581608
{
609+
Elf_Data *data, *shndx_data = NULL;
610+
Elf32_Word first_non_local;
582611
struct symbol *sym;
583-
int addend;
612+
Elf_Scn *s;
584613

585-
if (insn_sec->sym) {
586-
sym = insn_sec->sym;
587-
addend = insn_off;
614+
first_non_local = symtab->sh.sh_info;
588615

589-
} else {
590-
/*
591-
* The Clang assembler strips section symbols, so we have to
592-
* reference the function symbol instead:
593-
*/
594-
sym = find_symbol_containing(insn_sec, insn_off);
595-
if (!sym) {
596-
/*
597-
* Hack alert. This happens when we need to reference
598-
* the NOP pad insn immediately after the function.
599-
*/
600-
sym = find_symbol_containing(insn_sec, insn_off - 1);
616+
sym = find_symbol_by_index(elf, first_non_local);
617+
if (!sym) {
618+
WARN("no non-local symbols !?");
619+
return first_non_local;
620+
}
621+
622+
s = elf_getscn(elf->elf, symtab->idx);
623+
if (!s) {
624+
WARN_ELF("elf_getscn");
625+
return -1;
626+
}
627+
628+
data = elf_newdata(s);
629+
if (!data) {
630+
WARN_ELF("elf_newdata");
631+
return -1;
632+
}
633+
634+
data->d_buf = &sym->sym;
635+
data->d_size = sizeof(sym->sym);
636+
data->d_align = 1;
637+
data->d_type = ELF_T_SYM;
638+
639+
sym->idx = symtab->sh.sh_size / sizeof(sym->sym);
640+
elf_dirty_reloc_sym(elf, sym);
641+
642+
symtab->sh.sh_info += 1;
643+
symtab->sh.sh_size += data->d_size;
644+
symtab->changed = true;
645+
646+
if (symtab_shndx) {
647+
s = elf_getscn(elf->elf, symtab_shndx->idx);
648+
if (!s) {
649+
WARN_ELF("elf_getscn");
650+
return -1;
601651
}
602652

603-
if (!sym) {
604-
WARN("can't find symbol containing %s+0x%lx", insn_sec->name, insn_off);
653+
shndx_data = elf_newdata(s);
654+
if (!shndx_data) {
655+
WARN_ELF("elf_newshndx_data");
605656
return -1;
606657
}
607658

608-
addend = insn_off - sym->offset;
659+
shndx_data->d_buf = &sym->sec->idx;
660+
shndx_data->d_size = sizeof(Elf32_Word);
661+
shndx_data->d_align = 4;
662+
shndx_data->d_type = ELF_T_WORD;
663+
664+
symtab_shndx->sh.sh_size += 4;
665+
symtab_shndx->changed = true;
666+
}
667+
668+
return first_non_local;
669+
}
670+
671+
static struct symbol *
672+
elf_create_section_symbol(struct elf *elf, struct section *sec)
673+
{
674+
struct section *symtab, *symtab_shndx;
675+
Elf_Data *shndx_data = NULL;
676+
struct symbol *sym;
677+
Elf32_Word shndx;
678+
679+
symtab = find_section_by_name(elf, ".symtab");
680+
if (symtab) {
681+
symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
682+
if (symtab_shndx)
683+
shndx_data = symtab_shndx->data;
684+
} else {
685+
WARN("no .symtab");
686+
return NULL;
687+
}
688+
689+
sym = malloc(sizeof(*sym));
690+
if (!sym) {
691+
perror("malloc");
692+
return NULL;
693+
}
694+
memset(sym, 0, sizeof(*sym));
695+
696+
sym->idx = elf_move_global_symbol(elf, symtab, symtab_shndx);
697+
if (sym->idx < 0) {
698+
WARN("elf_move_global_symbol");
699+
return NULL;
700+
}
701+
702+
sym->name = sec->name;
703+
sym->sec = sec;
704+
705+
// st_name 0
706+
sym->sym.st_info = GELF_ST_INFO(STB_LOCAL, STT_SECTION);
707+
// st_other 0
708+
// st_value 0
709+
// st_size 0
710+
shndx = sec->idx;
711+
if (shndx >= SHN_UNDEF && shndx < SHN_LORESERVE) {
712+
sym->sym.st_shndx = shndx;
713+
if (!shndx_data)
714+
shndx = 0;
715+
} else {
716+
sym->sym.st_shndx = SHN_XINDEX;
717+
if (!shndx_data) {
718+
WARN("no .symtab_shndx");
719+
return NULL;
720+
}
721+
}
722+
723+
if (!gelf_update_symshndx(symtab->data, shndx_data, sym->idx, &sym->sym, shndx)) {
724+
WARN_ELF("gelf_update_symshndx");
725+
return NULL;
726+
}
727+
728+
elf_add_symbol(elf, sym);
729+
730+
return sym;
731+
}
732+
733+
int elf_add_reloc_to_insn(struct elf *elf, struct section *sec,
734+
unsigned long offset, unsigned int type,
735+
struct section *insn_sec, unsigned long insn_off)
736+
{
737+
struct symbol *sym = insn_sec->sym;
738+
int addend = insn_off;
739+
740+
if (!sym) {
741+
/*
742+
* Due to how weak functions work, we must use section based
743+
* relocations. Symbol based relocations would result in the
744+
* weak and non-weak function annotations being overlaid on the
745+
* non-weak function after linking.
746+
*/
747+
sym = elf_create_section_symbol(elf, insn_sec);
748+
if (!sym)
749+
return -1;
750+
751+
insn_sec->sym = sym;
609752
}
610753

611754
return elf_add_reloc(elf, sec, offset, type, sym, addend);

0 commit comments

Comments
 (0)