diff options
Diffstat (limited to 'tools/objtool')
-rw-r--r-- | tools/objtool/Documentation/stack-validation.txt | 8 | ||||
-rw-r--r-- | tools/objtool/arch.h | 5 | ||||
-rw-r--r-- | tools/objtool/arch/x86/decode.c | 31 | ||||
-rw-r--r-- | tools/objtool/check.c | 173 | ||||
-rw-r--r-- | tools/objtool/check.h | 2 | ||||
-rwxr-xr-x | tools/objtool/sync-check.sh | 3 |
6 files changed, 169 insertions, 53 deletions
diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt index de094670050b..ee26bb382b70 100644 --- a/tools/objtool/Documentation/stack-validation.txt +++ b/tools/objtool/Documentation/stack-validation.txt @@ -290,6 +290,14 @@ they mean, and suggestions for how to fix them. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646 +11. file.o: warning: unannotated intra-function call + + This warning means that a direct call is done to a destination which + is not at the beginning of a function. If this is a legit call, you + can remove this warning by putting the ANNOTATE_INTRA_FUNCTION_CALL + directive right before the call. + + If the error doesn't seem to make sense, it could be a bug in objtool. Feel free to ask the objtool maintainer for help. diff --git a/tools/objtool/arch.h b/tools/objtool/arch.h index ced3765c4f44..b1e6a0bc5e07 100644 --- a/tools/objtool/arch.h +++ b/tools/objtool/arch.h @@ -19,6 +19,7 @@ enum insn_type { INSN_CALL, INSN_CALL_DYNAMIC, INSN_RETURN, + INSN_EXCEPTION_RETURN, INSN_CONTEXT_SWITCH, INSN_STACK, INSN_BUG, @@ -64,6 +65,7 @@ struct op_src { struct stack_op { struct op_dest dest; struct op_src src; + struct list_head list; }; void arch_initial_func_cfi_state(struct cfi_state *state); @@ -71,7 +73,8 @@ void arch_initial_func_cfi_state(struct cfi_state *state); int arch_decode_instruction(struct elf *elf, struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, enum insn_type *type, - unsigned long *immediate, struct stack_op *op); + unsigned long *immediate, + struct list_head *ops_list); bool arch_callee_saved_reg(unsigned char reg); diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index a62e032863a8..2707ddb04863 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -69,13 +69,15 @@ bool arch_callee_saved_reg(unsigned char reg) int arch_decode_instruction(struct elf *elf, struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, enum insn_type *type, - unsigned long *immediate, struct stack_op *op) + unsigned long *immediate, + struct list_head *ops_list) { struct insn insn; int x86_64, sign; unsigned char op1, op2, rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, rex_x = 0, modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0, sib = 0; + struct stack_op *op; x86_64 = is_x86_64(elf); if (x86_64 == -1) @@ -116,6 +118,10 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, if (insn.sib.nbytes) sib = insn.sib.bytes[0]; + op = calloc(1, sizeof(*op)); + if (!op) + return -1; + switch (op1) { case 0x1: @@ -429,14 +435,30 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, *type = INSN_RETURN; break; + case 0xcf: /* iret */ + *type = INSN_EXCEPTION_RETURN; + + /* add $40, %rsp */ + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = 5*8; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + case 0xca: /* retf */ case 0xcb: /* retf */ - case 0xcf: /* iret */ *type = INSN_CONTEXT_SWITCH; break; case 0xe8: *type = INSN_CALL; + /* + * For the impact on the stack, a CALL behaves like + * a PUSH of an immediate value (the return address). + */ + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; break; case 0xfc: @@ -477,6 +499,11 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, *immediate = insn.immediate.nbytes ? insn.immediate.value : 0; + if (*type == INSN_STACK || *type == INSN_EXCEPTION_RETURN) + list_add_tail(&op->list, ops_list); + else + free(op); + return 0; } diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 06aaf04e629c..195cce6b4b5d 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -136,6 +136,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "panic", "do_exit", "do_task_dead", + "make_task_dead", "__module_put_and_exit", "complete_and_exit", "__reiserfs_panic", @@ -143,7 +144,8 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, "fortify_panic", "usercopy_abort", "machine_real_restart", - "rewind_stack_do_exit", + "rewind_stack_and_make_dead", + "cpu_bringup_and_idle", }; if (!func) @@ -161,7 +163,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, return false; insn = find_insn(file, func->sec, func->offset); - if (!insn->func) + if (!insn || !insn->func) return false; func_for_each_insn_all(file, func, insn) { @@ -253,6 +255,7 @@ static int decode_instructions(struct objtool_file *file) } memset(insn, 0, sizeof(*insn)); INIT_LIST_HEAD(&insn->alts); + INIT_LIST_HEAD(&insn->stack_ops); clear_insn_state(&insn->state); insn->sec = sec; @@ -262,7 +265,7 @@ static int decode_instructions(struct objtool_file *file) sec->len - offset, &insn->len, &insn->type, &insn->immediate, - &insn->stack_op); + &insn->stack_ops); if (ret) goto err; @@ -643,6 +646,7 @@ static int add_jump_destinations(struct objtool_file *file) return 0; } + /* * Find the destination instructions for all calls. */ @@ -664,10 +668,7 @@ static int add_call_destinations(struct objtool_file *file) dest_off); if (!insn->call_dest && !insn->ignore) { - WARN_FUNC("unsupported intra-function call", - insn->sec, insn->offset); - if (retpoline) - WARN("If this is a retpoline, please patch it in with alternatives and annotate it with ANNOTATE_NOSPEC_ALTERNATIVE."); + WARN_FUNC("unannotated intra-function call", insn->sec, insn->offset); return -1; } @@ -732,6 +733,7 @@ static int handle_group_alt(struct objtool_file *file, } memset(fake_jump, 0, sizeof(*fake_jump)); INIT_LIST_HEAD(&fake_jump->alts); + INIT_LIST_HEAD(&fake_jump->stack_ops); clear_insn_state(&fake_jump->state); fake_jump->sec = special_alt->new_sec; @@ -1289,6 +1291,58 @@ static int read_retpoline_hints(struct objtool_file *file) return 0; } + +static int read_intra_function_calls(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + struct rela *rela; + + sec = find_section_by_name(file->elf, ".rela.discard.intra_function_calls"); + if (!sec) + return 0; + + list_for_each_entry(rela, &sec->rela_list, list) { + unsigned long dest_off; + + if (rela->sym->type != STT_SECTION) { + WARN("unexpected relocation symbol type in %s", + sec->name); + return -1; + } + + insn = find_insn(file, rela->sym->sec, rela->addend); + if (!insn) { + WARN("bad .discard.intra_function_call entry"); + return -1; + } + + if (insn->type != INSN_CALL) { + WARN_FUNC("intra_function_call not a direct call", + insn->sec, insn->offset); + return -1; + } + + /* + * Treat intra-function CALLs as JMPs, but with a stack_op. + * See add_call_destinations(), which strips stack_ops from + * normal CALLs. + */ + insn->type = INSN_JUMP_UNCONDITIONAL; + + dest_off = insn->offset + insn->len + insn->immediate; + insn->jump_dest = find_insn(file, insn->sec, dest_off); + if (!insn->jump_dest) { + WARN_FUNC("can't find call dest at %s+0x%lx", + insn->sec, insn->offset, + insn->sec->name, dest_off); + return -1; + } + } + + return 0; +} + static void mark_rodata(struct objtool_file *file) { struct section *sec; @@ -1344,6 +1398,10 @@ static int decode_sections(struct objtool_file *file) if (ret) return ret; + ret = read_intra_function_calls(file); + if (ret) + return ret; + ret = add_call_destinations(file); if (ret) return ret; @@ -1365,7 +1423,7 @@ static int decode_sections(struct objtool_file *file) static bool is_fentry_call(struct instruction *insn) { - if (insn->type == INSN_CALL && + if (insn->type == INSN_CALL && insn->call_dest && insn->call_dest->type == STT_NOTYPE && !strcmp(insn->call_dest->name, "__fentry__")) return true; @@ -1403,10 +1461,11 @@ static bool has_valid_stack_frame(struct insn_state *state) return false; } -static int update_insn_state_regs(struct instruction *insn, struct insn_state *state) +static int update_insn_state_regs(struct instruction *insn, + struct insn_state *state, + struct stack_op *op) { struct cfi_reg *cfa = &state->cfa; - struct stack_op *op = &insn->stack_op; if (cfa->base != CFI_SP && cfa->base != CFI_SP_INDIRECT) return 0; @@ -1496,9 +1555,9 @@ static void restore_reg(struct insn_state *state, unsigned char reg) * 41 5d pop %r13 * c3 retq */ -static int update_insn_state(struct instruction *insn, struct insn_state *state) +static int update_insn_state(struct instruction *insn, struct insn_state *state, + struct stack_op *op) { - struct stack_op *op = &insn->stack_op; struct cfi_reg *cfa = &state->cfa; struct cfi_reg *regs = state->regs; @@ -1512,7 +1571,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) } if (state->type == ORC_TYPE_REGS || state->type == ORC_TYPE_REGS_IRET) - return update_insn_state_regs(insn, state); + return update_insn_state_regs(insn, state, op); switch (op->dest.type) { @@ -1851,6 +1910,42 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state) return 0; } +static int handle_insn_ops(struct instruction *insn, struct insn_state *state) +{ + struct stack_op *op; + + list_for_each_entry(op, &insn->stack_ops, list) { + int res; + + res = update_insn_state(insn, state, op); + if (res) + return res; + + if (op->dest.type == OP_DEST_PUSHF) { + if (!state->uaccess_stack) { + state->uaccess_stack = 1; + } else if (state->uaccess_stack >> 31) { + WARN_FUNC("PUSHF stack exhausted", + insn->sec, insn->offset); + return 1; + } + state->uaccess_stack <<= 1; + state->uaccess_stack |= state->uaccess; + } + + if (op->src.type == OP_SRC_POPF) { + if (state->uaccess_stack) { + state->uaccess = state->uaccess_stack & 1; + state->uaccess_stack >>= 1; + if (state->uaccess_stack == 1) + state->uaccess_stack = 0; + } + } + } + + return 0; +} + static bool insn_state_match(struct instruction *insn, struct insn_state *state) { struct insn_state *state1 = &insn->state, *state2 = state; @@ -1944,15 +2039,14 @@ static int validate_sibling_call(struct instruction *insn, struct insn_state *st * tools/objtool/Documentation/stack-validation.txt. */ static int validate_branch(struct objtool_file *file, struct symbol *func, - struct instruction *first, struct insn_state state) + struct instruction *insn, struct insn_state state) { struct alternative *alt; - struct instruction *insn, *next_insn; + struct instruction *next_insn; struct section *sec; u8 visited; int ret; - insn = first; sec = insn->sec; if (insn->alt_group && list_empty(&insn->alts)) { @@ -2005,16 +2099,6 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, } if (!save_insn->visited) { - /* - * Oops, no state to copy yet. - * Hopefully we can reach this - * instruction from another branch - * after the save insn has been - * visited. - */ - if (insn == first) - return 0; - WARN_FUNC("objtool isn't smart enough to handle this CFI save/restore combo", sec, insn->offset); return 1; @@ -2134,6 +2218,20 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, break; + case INSN_EXCEPTION_RETURN: + if (handle_insn_ops(insn, &state)) + return 1; + + /* + * This handles x86's sync_core() case, where we use an + * IRET to self. All 'normal' IRET instructions are in + * STT_NOTYPE entry symbols. + */ + if (func) + break; + + return 0; + case INSN_CONTEXT_SWITCH: if (func && (!next_insn || !next_insn->hint)) { WARN_FUNC("unsupported instruction in callable function", @@ -2143,29 +2241,8 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, return 0; case INSN_STACK: - if (update_insn_state(insn, &state)) + if (handle_insn_ops(insn, &state)) return 1; - - if (insn->stack_op.dest.type == OP_DEST_PUSHF) { - if (!state.uaccess_stack) { - state.uaccess_stack = 1; - } else if (state.uaccess_stack >> 31) { - WARN_FUNC("PUSHF stack exhausted", sec, insn->offset); - return 1; - } - state.uaccess_stack <<= 1; - state.uaccess_stack |= state.uaccess; - } - - if (insn->stack_op.src.type == OP_SRC_POPF) { - if (state.uaccess_stack) { - state.uaccess = state.uaccess_stack & 1; - state.uaccess_stack >>= 1; - if (state.uaccess_stack == 1) - state.uaccess_stack = 0; - } - } - break; case INSN_STAC: diff --git a/tools/objtool/check.h b/tools/objtool/check.h index 6d875ca6fce0..551d73f6ff73 100644 --- a/tools/objtool/check.h +++ b/tools/objtool/check.h @@ -42,7 +42,7 @@ struct instruction { struct rela *jump_table; struct list_head alts; struct symbol *func; - struct stack_op stack_op; + struct list_head stack_ops; struct insn_state state; struct orc_entry orc; }; diff --git a/tools/objtool/sync-check.sh b/tools/objtool/sync-check.sh index c3ae1e8ae119..2a1261bfbb62 100755 --- a/tools/objtool/sync-check.sh +++ b/tools/objtool/sync-check.sh @@ -4,6 +4,7 @@ FILES=' arch/x86/include/asm/inat_types.h arch/x86/include/asm/orc_types.h +arch/x86/include/asm/emulate_prefix.h arch/x86/lib/x86-opcode-map.txt arch/x86/tools/gen-insn-attr-x86.awk ' @@ -46,4 +47,4 @@ done check arch/x86/include/asm/inat.h '-I "^#include [\"<]\(asm/\)*inat_types.h[\">]"' check arch/x86/include/asm/insn.h '-I "^#include [\"<]\(asm/\)*inat.h[\">]"' check arch/x86/lib/inat.c '-I "^#include [\"<]\(../include/\)*asm/insn.h[\">]"' -check arch/x86/lib/insn.c '-I "^#include [\"<]\(../include/\)*asm/in\(at\|sn\).h[\">]"' +check arch/x86/lib/insn.c '-I "^#include [\"<]\(../include/\)*asm/in\(at\|sn\).h[\">]" -I "^#include [\"<]\(../include/\)*asm/emulate_prefix.h[\">]"' |