mirror of https://github.com/torvalds/linux.git
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:
parent
935c0b6a05
commit
a05de0a772
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue