objtool: Refactor add_jump_destinations()

The add_jump_destinations() logic is a bit weird and convoluted after
being incrementally tweaked over the years.  Refactor it to hopefully be
more logical and straightforward.

Acked-by: Petr Mladek <pmladek@suse.com>
Tested-by: Joe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
This commit is contained in:
Josh Poimboeuf 2025-09-17 09:03:45 -07:00
parent 935c0b6a05
commit a05de0a772
2 changed files with 106 additions and 120 deletions

View File

@ -1423,9 +1423,14 @@ static void add_return_call(struct objtool_file *file, struct instruction *insn,
} }
static bool is_first_func_insn(struct objtool_file *file, static bool is_first_func_insn(struct objtool_file *file,
struct instruction *insn, struct symbol *sym) struct instruction *insn)
{ {
if (insn->offset == sym->offset) struct symbol *func = insn_func(insn);
if (!func)
return false;
if (insn->offset == func->offset)
return true; return true;
/* Allow direct CALL/JMP past ENDBR */ /* Allow direct CALL/JMP past ENDBR */
@ -1433,51 +1438,30 @@ static bool is_first_func_insn(struct objtool_file *file,
struct instruction *prev = prev_insn_same_sym(file, insn); struct instruction *prev = prev_insn_same_sym(file, insn);
if (prev && prev->type == INSN_ENDBR && if (prev && prev->type == INSN_ENDBR &&
insn->offset == sym->offset + prev->len) insn->offset == func->offset + prev->len)
return true; return true;
} }
return false; return false;
} }
/*
* A sibling call is a tail-call to another symbol -- to differentiate from a
* recursive tail-call which is to the same symbol.
*/
static bool jump_is_sibling_call(struct objtool_file *file,
struct instruction *from, struct instruction *to)
{
struct symbol *fs = from->sym;
struct symbol *ts = to->sym;
/* Not a sibling call if from/to a symbol hole */
if (!fs || !ts)
return false;
/* Not a sibling call if not targeting the start of a symbol. */
if (!is_first_func_insn(file, to, ts))
return false;
/* Disallow sibling calls into STT_NOTYPE */
if (is_notype_sym(ts))
return false;
/* Must not be self to be a sibling */
return fs->pfunc != ts->pfunc;
}
/* /*
* Find the destination instructions for all jumps. * Find the destination instructions for all jumps.
*/ */
static int add_jump_destinations(struct objtool_file *file) static int add_jump_destinations(struct objtool_file *file)
{ {
struct instruction *insn, *jump_dest; struct instruction *insn;
struct reloc *reloc; struct reloc *reloc;
struct section *dest_sec;
unsigned long dest_off;
for_each_insn(file, insn) { for_each_insn(file, insn) {
struct symbol *func = insn_func(insn); struct symbol *func = insn_func(insn);
struct instruction *dest_insn;
struct section *dest_sec;
struct symbol *dest_sym;
unsigned long dest_off;
if (!is_static_jump(insn))
continue;
if (insn->jump_dest) { if (insn->jump_dest) {
/* /*
@ -1486,51 +1470,53 @@ static int add_jump_destinations(struct objtool_file *file)
*/ */
continue; continue;
} }
if (!is_static_jump(insn))
continue;
reloc = insn_reloc(file, insn); reloc = insn_reloc(file, insn);
if (!reloc) { if (!reloc) {
dest_sec = insn->sec; dest_sec = insn->sec;
dest_off = arch_jump_destination(insn); dest_off = arch_jump_destination(insn);
} else if (is_sec_sym(reloc->sym)) { dest_sym = dest_sec->sym;
dest_sec = reloc->sym->sec;
dest_off = arch_insn_adjusted_addend(insn, reloc);
} else if (reloc->sym->retpoline_thunk) {
if (add_retpoline_call(file, insn))
return -1;
continue;
} else if (reloc->sym->return_thunk) {
add_return_call(file, insn, true);
continue;
} else if (func) {
/*
* External sibling call or internal sibling call with
* STT_FUNC reloc.
*/
if (add_call_dest(file, insn, reloc->sym, true))
return -1;
continue;
} else if (reloc->sym->sec->idx) {
dest_sec = reloc->sym->sec;
dest_off = reloc->sym->sym.st_value +
arch_insn_adjusted_addend(insn, reloc);
} else { } else {
/* non-func asm code jumping to another file */ dest_sym = reloc->sym;
continue; if (is_undef_sym(dest_sym)) {
if (dest_sym->retpoline_thunk) {
if (add_retpoline_call(file, insn))
return -1;
continue;
}
if (dest_sym->return_thunk) {
add_return_call(file, insn, true);
continue;
}
/* External symbol */
if (func) {
/* External sibling call */
if (add_call_dest(file, insn, dest_sym, true))
return -1;
continue;
}
/* Non-func asm code jumping to external symbol */
continue;
}
dest_sec = dest_sym->sec;
dest_off = dest_sym->offset + arch_insn_adjusted_addend(insn, reloc);
} }
jump_dest = find_insn(file, dest_sec, dest_off); dest_insn = find_insn(file, dest_sec, dest_off);
if (!jump_dest) { if (!dest_insn) {
struct symbol *sym = find_symbol_by_offset(dest_sec, dest_off); struct symbol *sym = find_symbol_by_offset(dest_sec, dest_off);
/* /*
* This is a special case for retbleed_untrain_ret(). * retbleed_untrain_ret() jumps to
* It jumps to __x86_return_thunk(), but objtool * __x86_return_thunk(), but objtool can't find
* can't find the thunk's starting RET * the thunk's starting RET instruction,
* instruction, because the RET is also in the * because the RET is also in the middle of
* middle of another instruction. Objtool only * another instruction. Objtool only knows
* knows about the outer instruction. * about the outer instruction.
*/ */
if (sym && sym->embedded_insn) { if (sym && sym->embedded_insn) {
add_return_call(file, insn, false); add_return_call(file, insn, false);
@ -1538,73 +1524,73 @@ static int add_jump_destinations(struct objtool_file *file)
} }
/* /*
* GCOV/KCOV dead code can jump to the end of the * GCOV/KCOV dead code can jump to the end of
* function/section. * the function/section.
*/ */
if (file->ignore_unreachables && func && if (file->ignore_unreachables && func &&
dest_sec == insn->sec && dest_sec == insn->sec &&
dest_off == func->offset + func->len) dest_off == func->offset + func->len)
continue; continue;
ERROR_INSN(insn, "can't find jump dest instruction at %s+0x%lx", ERROR_INSN(insn, "can't find jump dest instruction at %s",
dest_sec->name, dest_off); offstr(dest_sec, dest_off));
return -1; return -1;
} }
/* if (!dest_sym || is_sec_sym(dest_sym)) {
* An intra-TU jump in retpoline.o might not have a relocation dest_sym = dest_insn->sym;
* for its jump dest, in which case the above if (!dest_sym)
* add_{retpoline,return}_call() didn't happen. goto set_jump_dest;
*/
if (jump_dest->sym && jump_dest->offset == jump_dest->sym->offset) {
if (jump_dest->sym->retpoline_thunk) {
if (add_retpoline_call(file, insn))
return -1;
continue;
}
if (jump_dest->sym->return_thunk) {
add_return_call(file, insn, true);
continue;
}
} }
/* if (dest_sym->retpoline_thunk && dest_insn->offset == dest_sym->offset) {
* Cross-function jump. if (add_retpoline_call(file, insn))
*/
if (func && insn_func(jump_dest) && !func->cold &&
insn_func(jump_dest)->cold) {
/*
* For GCC 8+, create parent/child links for any cold
* subfunctions. This is _mostly_ redundant with a
* similar initialization in read_symbols().
*
* If a function has aliases, we want the *first* such
* function in the symbol table to be the subfunction's
* parent. In that case we overwrite the
* initialization done in read_symbols().
*
* However this code can't completely replace the
* read_symbols() code because this doesn't detect the
* case where the parent function's only reference to a
* subfunction is through a jump table.
*/
func->cfunc = insn_func(jump_dest);
insn_func(jump_dest)->pfunc = func;
}
if (jump_is_sibling_call(file, insn, jump_dest)) {
/*
* Internal sibling call without reloc or with
* STT_SECTION reloc.
*/
if (add_call_dest(file, insn, insn_func(jump_dest), true))
return -1; return -1;
continue; continue;
} }
insn->jump_dest = jump_dest; if (dest_sym->return_thunk && dest_insn->offset == dest_sym->offset) {
add_return_call(file, insn, true);
continue;
}
if (!insn->sym || insn->sym == dest_insn->sym)
goto set_jump_dest;
/*
* Internal cross-function jump.
*/
/*
* For GCC 8+, create parent/child links for any cold
* subfunctions. This is _mostly_ redundant with a
* similar initialization in read_symbols().
*
* If a function has aliases, we want the *first* such
* function in the symbol table to be the subfunction's
* parent. In that case we overwrite the
* initialization done in read_symbols().
*
* However this code can't completely replace the
* read_symbols() code because this doesn't detect the
* case where the parent function's only reference to a
* subfunction is through a jump table.
*/
if (func && dest_sym->cold) {
func->cfunc = dest_sym;
dest_sym->pfunc = func;
goto set_jump_dest;
}
if (is_first_func_insn(file, dest_insn)) {
/* Internal sibling call */
if (add_call_dest(file, insn, dest_sym, true))
return -1;
continue;
}
set_jump_dest:
insn->jump_dest = dest_insn;
} }
return 0; return 0;

View File

@ -181,9 +181,9 @@ static inline unsigned int elf_text_rela_type(struct elf *elf)
return elf_addr_size(elf) == 4 ? R_TEXT32 : R_TEXT64; return elf_addr_size(elf) == 4 ? R_TEXT32 : R_TEXT64;
} }
static inline bool sym_has_sec(struct symbol *sym) static inline bool is_undef_sym(struct symbol *sym)
{ {
return sym->sec->idx; return !sym->sec->idx;
} }
static inline bool is_null_sym(struct symbol *sym) static inline bool is_null_sym(struct symbol *sym)