diff options
author | Josh Poimboeuf <jpoimboe@redhat.com> | 2017-06-28 10:11:07 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2017-06-30 10:19:19 +0200 |
commit | baa41469a7b992c1e3db2a39854219cc7442e48f (patch) | |
tree | 1a8e43a0d5e27db457c1cc6f8a4510b258b46bee /tools/objtool/check.c | |
parent | c207aee48037abca71c669cbec407b9891965c34 (diff) |
objtool: Implement stack validation 2.0
This is a major rewrite of objtool. Instead of only tracking frame
pointer changes, it now tracks all stack-related operations, including
all register saves/restores.
In addition to making stack validation more robust, this also paves the
way for undwarf generation.
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: live-patching@vger.kernel.org
Link: http://lkml.kernel.org/r/678bd94c0566c6129bcc376cddb259c4c5633004.1498659915.git.jpoimboe@redhat.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'tools/objtool/check.c')
-rw-r--r-- | tools/objtool/check.c | 676 |
1 files changed, 535 insertions, 141 deletions
diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 231a36053e07..2f80aa511243 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -27,10 +27,6 @@ #include <linux/hashtable.h> #include <linux/kernel.h> -#define STATE_FP_SAVED 0x1 -#define STATE_FP_SETUP 0x2 -#define STATE_FENTRY 0x4 - struct alternative { struct list_head list; struct instruction *insn; @@ -38,6 +34,7 @@ struct alternative { const char *objname; static bool nofp; +struct cfi_state initial_func_cfi; static struct instruction *find_insn(struct objtool_file *file, struct section *sec, unsigned long offset) @@ -56,7 +53,7 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file, { struct instruction *next = list_next_entry(insn, list); - if (&next->list == &file->insn_list || next->sec != insn->sec) + if (!next || &next->list == &file->insn_list || next->sec != insn->sec) return NULL; return next; @@ -67,7 +64,7 @@ static bool gcov_enabled(struct objtool_file *file) struct section *sec; struct symbol *sym; - list_for_each_entry(sec, &file->elf->sections, list) + for_each_sec(file, sec) list_for_each_entry(sym, &sec->symbol_list, list) if (!strncmp(sym->name, "__gcov_.", 8)) return true; @@ -75,9 +72,6 @@ static bool gcov_enabled(struct objtool_file *file) return false; } -#define for_each_insn(file, insn) \ - list_for_each_entry(insn, &file->insn_list, list) - #define func_for_each_insn(file, func, insn) \ for (insn = find_insn(file, func->sec, func->offset); \ insn && &insn->list != &file->insn_list && \ @@ -94,6 +88,9 @@ static bool gcov_enabled(struct objtool_file *file) #define sec_for_each_insn_from(file, insn) \ for (; insn; insn = next_insn_same_sec(file, insn)) +#define sec_for_each_insn_continue(file, insn) \ + for (insn = next_insn_same_sec(file, insn); insn; \ + insn = next_insn_same_sec(file, insn)) /* * Check if the function has been manually whitelisted with the @@ -103,7 +100,6 @@ static bool gcov_enabled(struct objtool_file *file) static bool ignore_func(struct objtool_file *file, struct symbol *func) { struct rela *rela; - struct instruction *insn; /* check for STACK_FRAME_NON_STANDARD */ if (file->whitelist && file->whitelist->rela) @@ -116,11 +112,6 @@ static bool ignore_func(struct objtool_file *file, struct symbol *func) return true; } - /* check if it has a context switching instruction */ - func_for_each_insn(file, func, insn) - if (insn->type == INSN_CONTEXT_SWITCH) - return true; - return false; } @@ -234,6 +225,17 @@ static int dead_end_function(struct objtool_file *file, struct symbol *func) return __dead_end_function(file, func, 0); } +static void clear_insn_state(struct insn_state *state) +{ + int i; + + memset(state, 0, sizeof(*state)); + state->cfa.base = CFI_UNDEFINED; + for (i = 0; i < CFI_NUM_REGS; i++) + state->regs[i].base = CFI_UNDEFINED; + state->drap_reg = CFI_UNDEFINED; +} + /* * Call the arch-specific instruction decoder for all the instructions and add * them to the global instruction list. @@ -246,23 +248,29 @@ static int decode_instructions(struct objtool_file *file) struct instruction *insn; int ret; - list_for_each_entry(sec, &file->elf->sections, list) { + for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; for (offset = 0; offset < sec->len; offset += insn->len) { insn = malloc(sizeof(*insn)); + if (!insn) { + WARN("malloc failed"); + return -1; + } memset(insn, 0, sizeof(*insn)); - INIT_LIST_HEAD(&insn->alts); + clear_insn_state(&insn->state); + insn->sec = sec; insn->offset = offset; ret = arch_decode_instruction(file->elf, sec, offset, sec->len - offset, &insn->len, &insn->type, - &insn->immediate); + &insn->immediate, + &insn->stack_op); if (ret) return ret; @@ -352,7 +360,7 @@ static void add_ignores(struct objtool_file *file) struct section *sec; struct symbol *func; - list_for_each_entry(sec, &file->elf->sections, list) { + for_each_sec(file, sec) { list_for_each_entry(func, &sec->symbol_list, list) { if (func->type != STT_FUNC) continue; @@ -361,7 +369,7 @@ static void add_ignores(struct objtool_file *file) continue; func_for_each_insn(file, func, insn) - insn->visited = true; + insn->ignore = true; } } } @@ -381,8 +389,7 @@ static int add_jump_destinations(struct objtool_file *file) insn->type != INSN_JUMP_UNCONDITIONAL) continue; - /* skip ignores */ - if (insn->visited) + if (insn->ignore) continue; rela = find_rela_by_dest_range(insn->sec, insn->offset, @@ -519,10 +526,13 @@ static int handle_group_alt(struct objtool_file *file, } memset(fake_jump, 0, sizeof(*fake_jump)); INIT_LIST_HEAD(&fake_jump->alts); + clear_insn_state(&fake_jump->state); + fake_jump->sec = special_alt->new_sec; fake_jump->offset = -1; fake_jump->type = INSN_JUMP_UNCONDITIONAL; fake_jump->jump_dest = list_next_entry(last_orig_insn, list); + fake_jump->ignore = true; if (!special_alt->new_len) { *new_insn = fake_jump; @@ -844,7 +854,7 @@ static int add_switch_table_alts(struct objtool_file *file) if (!file->rodata || !file->rodata->rela) return 0; - list_for_each_entry(sec, &file->elf->sections, list) { + for_each_sec(file, sec) { list_for_each_entry(func, &sec->symbol_list, list) { if (func->type != STT_FUNC) continue; @@ -901,21 +911,423 @@ static bool is_fentry_call(struct instruction *insn) return false; } -static bool has_modified_stack_frame(struct instruction *insn) +static bool has_modified_stack_frame(struct insn_state *state) { - return (insn->state & STATE_FP_SAVED) || - (insn->state & STATE_FP_SETUP); + int i; + + if (state->cfa.base != initial_func_cfi.cfa.base || + state->cfa.offset != initial_func_cfi.cfa.offset || + state->stack_size != initial_func_cfi.cfa.offset || + state->drap) + return true; + + for (i = 0; i < CFI_NUM_REGS; i++) + if (state->regs[i].base != initial_func_cfi.regs[i].base || + state->regs[i].offset != initial_func_cfi.regs[i].offset) + return true; + + return false; +} + +static bool has_valid_stack_frame(struct insn_state *state) +{ + if (state->cfa.base == CFI_BP && state->regs[CFI_BP].base == CFI_CFA && + state->regs[CFI_BP].offset == -16) + return true; + + if (state->drap && state->regs[CFI_BP].base == CFI_BP) + return true; + + return false; } -static bool has_valid_stack_frame(struct instruction *insn) +static void save_reg(struct insn_state *state, unsigned char reg, int base, + int offset) { - return (insn->state & STATE_FP_SAVED) && - (insn->state & STATE_FP_SETUP); + if ((arch_callee_saved_reg(reg) || + (state->drap && reg == state->drap_reg)) && + state->regs[reg].base == CFI_UNDEFINED) { + state->regs[reg].base = base; + state->regs[reg].offset = offset; + } } -static unsigned int frame_state(unsigned long state) +static void restore_reg(struct insn_state *state, unsigned char reg) { - return (state & (STATE_FP_SAVED | STATE_FP_SETUP)); + state->regs[reg].base = CFI_UNDEFINED; + state->regs[reg].offset = 0; +} + +/* + * A note about DRAP stack alignment: + * + * GCC has the concept of a DRAP register, which is used to help keep track of + * the stack pointer when aligning the stack. r10 or r13 is used as the DRAP + * register. The typical DRAP pattern is: + * + * 4c 8d 54 24 08 lea 0x8(%rsp),%r10 + * 48 83 e4 c0 and $0xffffffffffffffc0,%rsp + * 41 ff 72 f8 pushq -0x8(%r10) + * 55 push %rbp + * 48 89 e5 mov %rsp,%rbp + * (more pushes) + * 41 52 push %r10 + * ... + * 41 5a pop %r10 + * (more pops) + * 5d pop %rbp + * 49 8d 62 f8 lea -0x8(%r10),%rsp + * c3 retq + * + * There are some variations in the epilogues, like: + * + * 5b pop %rbx + * 41 5a pop %r10 + * 41 5c pop %r12 + * 41 5d pop %r13 + * 41 5e pop %r14 + * c9 leaveq + * 49 8d 62 f8 lea -0x8(%r10),%rsp + * c3 retq + * + * and: + * + * 4c 8b 55 e8 mov -0x18(%rbp),%r10 + * 48 8b 5d e0 mov -0x20(%rbp),%rbx + * 4c 8b 65 f0 mov -0x10(%rbp),%r12 + * 4c 8b 6d f8 mov -0x8(%rbp),%r13 + * c9 leaveq + * 49 8d 62 f8 lea -0x8(%r10),%rsp + * c3 retq + * + * Sometimes r13 is used as the DRAP register, in which case it's saved and + * restored beforehand: + * + * 41 55 push %r13 + * 4c 8d 6c 24 10 lea 0x10(%rsp),%r13 + * 48 83 e4 f0 and $0xfffffffffffffff0,%rsp + * ... + * 49 8d 65 f0 lea -0x10(%r13),%rsp + * 41 5d pop %r13 + * c3 retq + */ +static int update_insn_state(struct instruction *insn, struct insn_state *state) +{ + struct stack_op *op = &insn->stack_op; + struct cfi_reg *cfa = &state->cfa; + struct cfi_reg *regs = state->regs; + + /* stack operations don't make sense with an undefined CFA */ + if (cfa->base == CFI_UNDEFINED) { + if (insn->func) { + WARN_FUNC("undefined stack state", insn->sec, insn->offset); + return -1; + } + return 0; + } + + switch (op->dest.type) { + + case OP_DEST_REG: + switch (op->src.type) { + + case OP_SRC_REG: + if (cfa->base == op->src.reg && cfa->base == CFI_SP && + op->dest.reg == CFI_BP && regs[CFI_BP].base == CFI_CFA && + regs[CFI_BP].offset == -cfa->offset) { + + /* mov %rsp, %rbp */ + cfa->base = op->dest.reg; + state->bp_scratch = false; + } else if (state->drap) { + + /* drap: mov %rsp, %rbp */ + regs[CFI_BP].base = CFI_BP; + regs[CFI_BP].offset = -state->stack_size; + state->bp_scratch = false; + } else if (!nofp) { + + WARN_FUNC("unknown stack-related register move", + insn->sec, insn->offset); + return -1; + } + + break; + + case OP_SRC_ADD: + if (op->dest.reg == CFI_SP && op->src.reg == CFI_SP) { + + /* add imm, %rsp */ + state->stack_size -= op->src.offset; + if (cfa->base == CFI_SP) + cfa->offset -= op->src.offset; + break; + } + + if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) { + + /* lea disp(%rbp), %rsp */ + state->stack_size = -(op->src.offset + regs[CFI_BP].offset); + break; + } + + if (op->dest.reg != CFI_BP && op->src.reg == CFI_SP && + cfa->base == CFI_SP) { + + /* drap: lea disp(%rsp), %drap */ + state->drap_reg = op->dest.reg; + break; + } + + if (state->drap && op->dest.reg == CFI_SP && + op->src.reg == state->drap_reg) { + + /* drap: lea disp(%drap), %rsp */ + cfa->base = CFI_SP; + cfa->offset = state->stack_size = -op->src.offset; + state->drap_reg = CFI_UNDEFINED; + state->drap = false; + break; + } + + if (op->dest.reg == state->cfa.base) { + WARN_FUNC("unsupported stack register modification", + insn->sec, insn->offset); + return -1; + } + + break; + + case OP_SRC_AND: + if (op->dest.reg != CFI_SP || + (state->drap_reg != CFI_UNDEFINED && cfa->base != CFI_SP) || + (state->drap_reg == CFI_UNDEFINED && cfa->base != CFI_BP)) { + WARN_FUNC("unsupported stack pointer realignment", + insn->sec, insn->offset); + return -1; + } + + if (state->drap_reg != CFI_UNDEFINED) { + /* drap: and imm, %rsp */ + cfa->base = state->drap_reg; + cfa->offset = state->stack_size = 0; + state->drap = true; + + } + + /* + * Older versions of GCC (4.8ish) realign the stack + * without DRAP, with a frame pointer. + */ + + break; + + case OP_SRC_POP: + if (!state->drap && op->dest.type == OP_DEST_REG && + op->dest.reg == cfa->base) { + + /* pop %rbp */ + cfa->base = CFI_SP; + } + + if (regs[op->dest.reg].offset == -state->stack_size) { + + if (state->drap && cfa->base == CFI_BP_INDIRECT && + op->dest.type == OP_DEST_REG && + op->dest.reg == state->drap_reg) { + + /* drap: pop %drap */ + cfa->base = state->drap_reg; + cfa->offset = 0; + } + + restore_reg(state, op->dest.reg); + } + + state->stack_size -= 8; + if (cfa->base == CFI_SP) + cfa->offset -= 8; + + break; + + case OP_SRC_REG_INDIRECT: + if (state->drap && op->src.reg == CFI_BP && + op->src.offset == regs[op->dest.reg].offset) { + + /* drap: mov disp(%rbp), %reg */ + if (op->dest.reg == state->drap_reg) { + cfa->base = state->drap_reg; + cfa->offset = 0; + } + + restore_reg(state, op->dest.reg); + + } else if (op->src.reg == cfa->base && + op->src.offset == regs[op->dest.reg].offset + cfa->offset) { + + /* mov disp(%rbp), %reg */ + /* mov disp(%rsp), %reg */ + restore_reg(state, op->dest.reg); + } + + break; + + default: + WARN_FUNC("unknown stack-related instruction", + insn->sec, insn->offset); + return -1; + } + + break; + + case OP_DEST_PUSH: + state->stack_size += 8; + if (cfa->base == CFI_SP) + cfa->offset += 8; + + if (op->src.type != OP_SRC_REG) + break; + + if (state->drap) { + if (op->src.reg == cfa->base && op->src.reg == state->drap_reg) { + + /* drap: push %drap */ + cfa->base = CFI_BP_INDIRECT; + cfa->offset = -state->stack_size; + + /* save drap so we know when to undefine it */ + save_reg(state, op->src.reg, CFI_CFA, -state->stack_size); + + } else if (op->src.reg == CFI_BP && cfa->base == state->drap_reg) { + + /* drap: push %rbp */ + state->stack_size = 0; + + } else if (regs[op->src.reg].base == CFI_UNDEFINED) { + + /* drap: push %reg */ + save_reg(state, op->src.reg, CFI_BP, -state->stack_size); + } + + } else { + + /* push %reg */ + save_reg(state, op->src.reg, CFI_CFA, -state->stack_size); + } + + /* detect when asm code uses rbp as a scratch register */ + if (!nofp && insn->func && op->src.reg == CFI_BP && + cfa->base != CFI_BP) + state->bp_scratch = true; + break; + + case OP_DEST_REG_INDIRECT: + + if (state->drap) { + if (op->src.reg == cfa->base && op->src.reg == state->drap_reg) { + + /* drap: mov %drap, disp(%rbp) */ + cfa->base = CFI_BP_INDIRECT; + cfa->offset = op->dest.offset; + + /* save drap so we know when to undefine it */ + save_reg(state, op->src.reg, CFI_CFA, op->dest.offset); + } + + else if (regs[op->src.reg].base == CFI_UNDEFINED) { + + /* drap: mov reg, disp(%rbp) */ + save_reg(state, op->src.reg, CFI_BP, op->dest.offset); + } + + } else if (op->dest.reg == cfa->base) { + + /* mov reg, disp(%rbp) */ + /* mov reg, disp(%rsp) */ + save_reg(state, op->src.reg, CFI_CFA, + op->dest.offset - state->cfa.offset); + } + + break; + + case OP_DEST_LEAVE: + if ((!state->drap && cfa->base != CFI_BP) || + (state->drap && cfa->base != state->drap_reg)) { + WARN_FUNC("leave instruction with modified stack frame", + insn->sec, insn->offset); + return -1; + } + + /* leave (mov %rbp, %rsp; pop %rbp) */ + + state->stack_size = -state->regs[CFI_BP].offset - 8; + restore_reg(state, CFI_BP); + + if (!state->drap) { + cfa->base = CFI_SP; + cfa->offset -= 8; + } + + break; + + case OP_DEST_MEM: + if (op->src.type != OP_SRC_POP) { + WARN_FUNC("unknown stack-related memory operation", + insn->sec, insn->offset); + return -1; + } + + /* pop mem */ + state->stack_size -= 8; + if (cfa->base == CFI_SP) + cfa->offset -= 8; + + break; + + default: + WARN_FUNC("unknown stack-related instruction", + insn->sec, insn->offset); + return -1; + } + + return 0; +} + +static bool insn_state_match(struct instruction *insn, struct insn_state *state) +{ + struct insn_state *state1 = &insn->state, *state2 = state; + int i; + + if (memcmp(&state1->cfa, &state2->cfa, sizeof(state1->cfa))) { + WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", + insn->sec, insn->offset, + state1->cfa.base, state1->cfa.offset, + state2->cfa.base, state2->cfa.offset); + + } else if (memcmp(&state1->regs, &state2->regs, sizeof(state1->regs))) { + for (i = 0; i < CFI_NUM_REGS; i++) { + if (!memcmp(&state1->regs[i], &state2->regs[i], + sizeof(struct cfi_reg))) + continue; + + WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", + insn->sec, insn->offset, + i, state1->regs[i].base, state1->regs[i].offset, + i, state2->regs[i].base, state2->regs[i].offset); + break; + } + + } else if (state1->drap != state2->drap || + (state1->drap && state1->drap_reg != state2->drap_reg)) { + WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)", + insn->sec, insn->offset, + state1->drap, state1->drap_reg, + state2->drap, state2->drap_reg); + + } else + return true; + + return false; } /* @@ -924,24 +1336,22 @@ static unsigned int frame_state(unsigned long state) * each instruction and validate all the rules described in * tools/objtool/Documentation/stack-validation.txt. */ -static int validate_branch(struct objtool_file *file, - struct instruction *first, unsigned char first_state) +static int validate_branch(struct objtool_file *file, struct instruction *first, + struct insn_state state) { struct alternative *alt; struct instruction *insn; struct section *sec; struct symbol *func = NULL; - unsigned char state; int ret; insn = first; sec = insn->sec; - state = first_state; if (insn->alt_group && list_empty(&insn->alts)) { WARN_FUNC("don't know how to handle branch to middle of alternative instruction group", sec, insn->offset); - return 1; + return -1; } while (1) { @@ -951,23 +1361,21 @@ static int validate_branch(struct objtool_file *file, func->name, insn->func->name); return 1; } - - func = insn->func; } + func = insn->func; + if (insn->visited) { - if (frame_state(insn->state) != frame_state(state)) { - WARN_FUNC("frame pointer state mismatch", - sec, insn->offset); + if (!!insn_state_match(insn, &state)) return 1; - } return 0; } - insn->visited = true; insn->state = state; + insn->visited = true; + list_for_each_entry(alt, &insn->alts, list) { ret = validate_branch(file, alt->insn, state); if (ret) @@ -976,50 +1384,24 @@ static int validate_branch(struct objtool_file *file, switch (insn->type) { - case INSN_FP_SAVE: - if (!nofp) { - if (state & STATE_FP_SAVED) { - WARN_FUNC("duplicate frame pointer save", - sec, insn->offset); - return 1; - } - state |= STATE_FP_SAVED; - } - break; - - case INSN_FP_SETUP: - if (!nofp) { - if (state & STATE_FP_SETUP) { - WARN_FUNC("duplicate frame pointer setup", - sec, insn->offset); - return 1; - } - state |= STATE_FP_SETUP; - } - break; - - case INSN_FP_RESTORE: - if (!nofp) { - if (has_valid_stack_frame(insn)) - state &= ~STATE_FP_SETUP; - - state &= ~STATE_FP_SAVED; - } - break; - case INSN_RETURN: - if (!nofp && has_modified_stack_frame(insn)) { - WARN_FUNC("return without frame pointer restore", + if (func && has_modified_stack_frame(&state)) { + WARN_FUNC("return with modified stack frame", sec, insn->offset); return 1; } + + if (state.bp_scratch) { + WARN("%s uses BP as a scratch register", + insn->func->name); + return 1; + } + return 0; case INSN_CALL: - if (is_fentry_call(insn)) { - state |= STATE_FENTRY; + if (is_fentry_call(insn)) break; - } ret = dead_end_function(file, insn->call_dest); if (ret == 1) @@ -1029,7 +1411,7 @@ static int validate_branch(struct objtool_file *file, /* fallthrough */ case INSN_CALL_DYNAMIC: - if (!nofp && !has_valid_stack_frame(insn)) { + if (!nofp && func && !has_valid_stack_frame(&state)) { WARN_FUNC("call without frame pointer save/setup", sec, insn->offset); return 1; @@ -1043,8 +1425,8 @@ static int validate_branch(struct objtool_file *file, state); if (ret) return 1; - } else if (has_modified_stack_frame(insn)) { - WARN_FUNC("sibling call from callable instruction with changed frame pointer", + } else if (func && has_modified_stack_frame(&state)) { + WARN_FUNC("sibling call from callable instruction with modified stack frame", sec, insn->offset); return 1; } /* else it's a sibling call */ @@ -1055,15 +1437,29 @@ static int validate_branch(struct objtool_file *file, break; case INSN_JUMP_DYNAMIC: - if (list_empty(&insn->alts) && - has_modified_stack_frame(insn)) { - WARN_FUNC("sibling call from callable instruction with changed frame pointer", + if (func && list_empty(&insn->alts) && + has_modified_stack_frame(&state)) { + WARN_FUNC("sibling call from callable instruction with modified stack frame", sec, insn->offset); return 1; } return 0; + case INSN_CONTEXT_SWITCH: + if (func) { + WARN_FUNC("unsupported instruction in callable function", + sec, insn->offset); + return 1; + } + return 0; + + case INSN_STACK: + if (update_insn_state(insn, &state)) + return -1; + + break; + default: break; } @@ -1094,12 +1490,18 @@ static bool is_ubsan_insn(struct instruction *insn) "__ubsan_handle_builtin_unreachable")); } -static bool ignore_unreachable_insn(struct symbol *func, - struct instruction *insn) +static bool ignore_unreachable_insn(struct instruction *insn) { int i; - if (insn->type == INSN_NOP) + if (insn->ignore || insn->type == INSN_NOP) + return true; + + /* + * Ignore any unused exceptions. This can happen when a whitelisted + * function has an exception table entry. + */ + if (!strcmp(insn->sec->name, ".fixup")) return true; /* @@ -1108,6 +1510,8 @@ static bool ignore_unreachable_insn(struct symbol *func, * * End the search at 5 instructions to avoid going into the weeds. */ + if (!insn->func) + return false; for (i = 0; i < 5; i++) { if (is_kasan_insn(insn) || is_ubsan_insn(insn)) @@ -1118,7 +1522,7 @@ static bool ignore_unreachable_insn(struct symbol *func, continue; } - if (insn->offset + insn->len >= func->offset + func->len) + if (insn->offset + insn->len >= insn->func->offset + insn->func->len) break; insn = list_next_entry(insn, list); } @@ -1131,73 +1535,58 @@ static int validate_functions(struct objtool_file *file) struct section *sec; struct symbol *func; struct instruction *insn; + struct insn_state state; int ret, warnings = 0; - list_for_each_entry(sec, &file->elf->sections, list) { + clear_insn_state(&state); + + state.cfa = initial_func_cfi.cfa; + memcpy(&state.regs, &initial_func_cfi.regs, + CFI_NUM_REGS * sizeof(struct cfi_reg)); + state.stack_size = initial_func_cfi.cfa.offset; + + for_each_sec(file, sec) { list_for_each_entry(func, &sec->symbol_list, list) { if (func->type != STT_FUNC) continue; insn = find_insn(file, sec, func->offset); - if (!insn) + if (!insn || insn->ignore) continue; - ret = validate_branch(file, insn, 0); + ret = validate_branch(file, insn, state); warnings += ret; } } - list_for_each_entry(sec, &file->elf->sections, list) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; - - func_for_each_insn(file, func, insn) { - if (insn->visited) - continue; - - insn->visited = true; - - if (file->ignore_unreachables || warnings || - ignore_unreachable_insn(func, insn)) - continue; - - /* - * gcov produces a lot of unreachable - * instructions. If we get an unreachable - * warning and the file has gcov enabled, just - * ignore it, and all other such warnings for - * the file. - */ - if (!file->ignore_unreachables && - gcov_enabled(file)) { - file->ignore_unreachables = true; - continue; - } - - WARN_FUNC("function has unreachable instruction", insn->sec, insn->offset); - warnings++; - } - } - } - return warnings; } -static int validate_uncallable_instructions(struct objtool_file *file) +static int validate_reachable_instructions(struct objtool_file *file) { struct instruction *insn; - int warnings = 0; + + if (file->ignore_unreachables) + return 0; for_each_insn(file, insn) { - if (!insn->visited && insn->type == INSN_RETURN) { - WARN_FUNC("return instruction outside of a callable function", - insn->sec, insn->offset); - warnings++; - } + if (insn->visited || ignore_unreachable_insn(insn)) + continue; + + /* + * gcov produces a lot of unreachable instructions. If we get + * an unreachable warning and the file has gcov enabled, just + * ignore it, and all other such warnings for the file. Do + * this here because this is an expensive function. + */ + if (gcov_enabled(file)) + return 0; + + WARN_FUNC("unreachable instruction", insn->sec, insn->offset); + return 1; } - return warnings; + return 0; } static void cleanup(struct objtool_file *file) @@ -1226,10 +1615,8 @@ int check(const char *_objname, bool _nofp) nofp = _nofp; file.elf = elf_open(objname); - if (!file.elf) { - fprintf(stderr, "error reading elf file %s\n", objname); + if (!file.elf) return 1; - } INIT_LIST_HEAD(&file.insn_list); hash_init(file.insn_hash); @@ -1238,21 +1625,28 @@ int check(const char *_objname, bool _nofp) file.ignore_unreachables = false; file.c_file = find_section_by_name(file.elf, ".comment"); + arch_initial_func_cfi_state(&initial_func_cfi); + ret = decode_sections(&file); if (ret < 0) goto out; warnings += ret; - ret = validate_functions(&file); - if (ret < 0) + if (list_empty(&file.insn_list)) goto out; - warnings += ret; - ret = validate_uncallable_instructions(&file); + ret = validate_functions(&file); if (ret < 0) goto out; warnings += ret; + if (!warnings) { + ret = validate_reachable_instructions(&file); + if (ret < 0) + goto out; + warnings += ret; + } + out: cleanup(&file); |