From: madvenka@linux.microsoft.com To: jpoimboe@redhat.com, peterz@infradead.org, chenzhongjin@huawei.com, mark.rutland@arm.com, broonie@kernel.org, nobuta.keiya@fujitsu.com, sjitindarsingh@gmail.com, catalin.marinas@arm.com, will@kernel.org, jamorris@linux.microsoft.com, linux-arm-kernel@lists.infradead.org, live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, madvenka@linux.microsoft.com Subject: [RFC PATCH v3 13/22] objtool: arm64: Walk instructions and compute CFI for each instruction Date: Thu, 2 Feb 2023 01:40:27 -0600 [thread overview] Message-ID: <20230202074036.507249-14-madvenka@linux.microsoft.com> (raw) In-Reply-To: <20230202074036.507249-1-madvenka@linux.microsoft.com> From: "Madhavan T. Venkataraman" <madvenka@linux.microsoft.com> Implement arch_initial_func_cfi_state() to initialize the CFI for a function. Add code to check() in dcheck.c to walk the instructions in every function and compute the CFI information for each instruction. Perform the following checks to validate the CFI: - Make sure that there is exactly one frame pointer prolog for an epilog. - Make sure that the frame pointer register is initialized to the location at which the previous frame pointer is stored on the stack. - Make sure that the frame pointer is restored in the epilog from the same location on stack where it was saved. - Make sure that the return address is restored in the epilog from the same location on stack where it was saved. - Make sure that the frame pointer and return address are saved on the stack adjacent to each other in the correct order as specified in the ABI. - If an instruction can be reached via two different code paths, make sure that the CFIs computed from traversing each path match for the instruction. - Every time the frame pointer or stack offset is changed, make sure the offsets have legal values. insn_cfi_match() is used to compare CFIs to see if they match. When there is a mismatch, the function emits error messages. With static checking, these errors result in failure. With dynamic checking, these errors only resulting in marking those instructions as unreliable for unwind. In the latter case, suppress the warning messages. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> --- tools/objtool/arch/arm64/decode.c | 15 ++ tools/objtool/check.c | 2 +- tools/objtool/dcheck.c | 287 +++++++++++++++++++++++++++ tools/objtool/include/objtool/insn.h | 3 +- tools/objtool/insn.c | 39 ++-- 5 files changed, 329 insertions(+), 17 deletions(-) diff --git a/tools/objtool/arch/arm64/decode.c b/tools/objtool/arch/arm64/decode.c index 81653ed3c323..f723be80c09a 100644 --- a/tools/objtool/arch/arm64/decode.c +++ b/tools/objtool/arch/arm64/decode.c @@ -22,6 +22,21 @@ /* --------------------- arch support functions ------------------------- */ +void arch_initial_func_cfi_state(struct cfi_init_state *state) +{ + int i; + + for (i = 0; i < CFI_NUM_REGS; i++) { + state->regs[i].base = CFI_UNDEFINED; + state->regs[i].offset = 0; + } + state->regs[CFI_FP].base = CFI_CFA; + + /* initial CFA (call frame address) */ + state->cfa.base = CFI_SP; + state->cfa.offset = 0; +} + unsigned long arch_dest_reloc_offset(int addend) { return addend; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index d14a2b7b8b37..94efe94a566e 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2863,7 +2863,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, visited = VISITED_BRANCH << state.uaccess; if (insn->visited & VISITED_BRANCH_MASK) { - if (!insn->hint && !insn_cfi_match(insn, &state.cfi)) + if (!insn->hint && !insn_cfi_match(insn, &state.cfi, true)) return 1; if (insn->visited & visited) diff --git a/tools/objtool/dcheck.c b/tools/objtool/dcheck.c index eb806a032a32..8b78cb608528 100644 --- a/tools/objtool/dcheck.c +++ b/tools/objtool/dcheck.c @@ -49,6 +49,283 @@ static void add_jump_destinations(struct objtool_file *file) } } +static bool update_cfi_state(struct cfi_state *cfi, struct stack_op *op) +{ + struct cfi_reg *cfa = &cfi->cfa; + struct cfi_reg *fp_reg = &cfi->regs[CFI_FP]; + struct cfi_reg *fp_val = &cfi->vals[CFI_FP]; + struct cfi_reg *ra_val = &cfi->vals[CFI_RA]; + enum op_src_type src_type = op->src.type; + enum op_dest_type dest_type = op->dest.type; + unsigned char dest_reg = op->dest.reg; + int offset; + + if (src_type == OP_SRC_ADD && dest_type == OP_DEST_REG) { + + if (op->src.reg == CFI_SP) { + if (op->dest.reg == CFI_SP) { + cfa->offset -= op->src.offset; + } else { + if (fp_reg->offset) { + /* FP is already set. */ + return false; + } + fp_reg->offset = -cfa->offset + op->src.offset; + if (fp_reg->offset != fp_val->offset) { + /* + * FP does not match the location + * where FP is stored on stack. + */ + return false; + } + } + } else { + if (op->dest.reg == CFI_SP) { + cfa->offset = + -(fp_reg->offset + op->src.offset); + } else { + /* Setting the FP from itself is unreliable. */ + return false; + } + } + /* + * When the stack pointer is restored in the frame pointer + * epilog, forget where the FP and RA were stored. + */ + if (cfa->offset < -fp_val->offset) + fp_val->offset = 0; + if (cfa->offset < -ra_val->offset) + ra_val->offset = 0; + goto out; + } + + if (src_type == OP_SRC_REG_INDIRECT && dest_type == OP_DEST_REG) { + offset = -cfa->offset + op->src.offset; + if (dest_reg == CFI_FP) { + if (!fp_val->offset || fp_val->offset != offset) { + /* + * Loading the FP from a different place than + * where it is stored. + */ + return false; + } + if (!ra_val->offset || + (ra_val->offset - fp_val->offset) != 8) { + /* FP and RA must be adjacent in a frame. */ + return false; + } + fp_reg->offset = 0; + } + goto out; + } + + if (src_type == OP_SRC_REG && dest_type == OP_DEST_REG_INDIRECT) { + offset = -cfa->offset + op->dest.offset; + if (dest_reg == CFI_FP) { + /* Record where the FP is stored on the stack. */ + fp_val->offset = offset; + } else { + /* Record where the RA is stored on the stack. */ + if (fp_val->offset && (offset - fp_val->offset) == 8) + ra_val->offset = offset; + } + goto out; + } + return false; +out: + if (cfa->offset < 0 || fp_reg->offset > 0 || + fp_val->offset > 0 || ra_val->offset > 0) { + /* Unexpected SP and FP offset values. */ + return false; + } + return true; +} + +static bool do_stack_ops(struct instruction *insn, struct insn_state *state) +{ + struct stack_op *op; + + list_for_each_entry(op, &insn->stack_ops, list) { + if (!update_cfi_state(&state->cfi, op)) + return false; + } + return true; +} + +static bool validate_branch(struct objtool_file *file, struct section *sec, + struct symbol *func, struct instruction *insn, + struct insn_state *state) +{ + struct symbol *insn_func = insn->func; + struct instruction *dest; + struct cfi_state save_cfi; + struct cfi_reg *cfa; + struct cfi_reg *regs; + unsigned long start, end; + + for (; insn; insn = next_insn_same_sec(file, insn)) { + + if (insn->func != insn_func) + return true; + + if (insn->cfi) + return insn_cfi_match(insn, &state->cfi, false); + + insn->cfi = cfi_hash_find_or_add(&state->cfi); + dest = insn->jump_dest; + + if (!do_stack_ops(insn, state)) + return false; + + switch (insn->type) { + case INSN_BUG: + return true; + + case INSN_UNRELIABLE: + return false; + + case INSN_RETURN: + cfa = &state->cfi.cfa; + regs = state->cfi.regs; + if (cfa->offset || regs[CFI_FP].offset) { + /* SP and FP offsets should be 0 on return. */ + return false; + } + return true; + + case INSN_CALL: + case INSN_CALL_DYNAMIC: + start = func->offset; + end = start + func->len; + /* Treat intra-function calls as jumps. */ + if (!dest || dest->sec != sec || + dest->offset <= start || dest->offset >= end) { + break; + } + + case INSN_JUMP_UNCONDITIONAL: + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_DYNAMIC: + if (dest) { + save_cfi = state->cfi; + if (!validate_branch(file, sec, func, dest, + state)) { + return false; + } + state->cfi = save_cfi; + } + if (insn->type == INSN_JUMP_UNCONDITIONAL || + insn->type == INSN_JUMP_DYNAMIC) { + return true; + } + break; + + default: + break; + } + } + return true; +} + +static bool walk_reachable(struct objtool_file *file, struct section *sec, + struct symbol *func) +{ + struct instruction *insn = find_insn(file, sec, func->offset); + struct insn_state state; + + func_for_each_insn(file, func, insn) { + + if (insn->offset != func->offset && + (insn->type != INSN_START || insn->cfi)) { + continue; + } + + init_insn_state(file, &state, sec); + set_func_state(&state.cfi); + + if (!validate_branch(file, sec, func, insn, &state)) + return false; + } + return true; +} + +static void remove_cfi(struct objtool_file *file, struct symbol *func) +{ + struct instruction *insn; + + func_for_each_insn(file, func, insn) { + insn->cfi = NULL; + } +} + +/* + * Instructions that were not visited by walk_reachable() would not have a + * CFI. Try to initialize their CFI. For instance, there could be a table of + * unconditional branches like for a switch statement. Or, code can be patched + * by the kernel at runtime. After patching, some of the previously unreachable + * code may become reachable. + * + * This follows the same pattern as the DWARF info generated by the compiler. + */ +static bool walk_unreachable(struct objtool_file *file, struct section *sec, + struct symbol *func) +{ + struct instruction *insn, *prev; + struct insn_state state; + + func_for_each_insn(file, func, insn) { + + if (insn->cfi) + continue; + + prev = list_prev_entry(insn, list); + if (!prev || prev->func != insn->func || !prev->cfi) + continue; + + if (prev->type != INSN_JUMP_UNCONDITIONAL && + prev->type != INSN_JUMP_DYNAMIC && + prev->type != INSN_BUG) { + continue; + } + + /* Propagate the CFI. */ + state.cfi = *prev->cfi; + if (!validate_branch(file, sec, func, insn, &state)) + return false; + } + return true; +} + +static void walk_section(struct objtool_file *file, struct section *sec) +{ + struct symbol *func; + + list_for_each_entry(func, &sec->symbol_list, list) { + + if (func->type != STT_FUNC || !func->len || + func->pfunc != func || func->alias != func) { + /* No CFI generated for this function. */ + continue; + } + + if (!walk_reachable(file, sec, func) || + !walk_unreachable(file, sec, func)) { + remove_cfi(file, func); + continue; + } + } +} + +static void walk_sections(struct objtool_file *file) +{ + struct section *sec; + + for_each_sec(file, sec) { + if (sec->sh.sh_flags & SHF_EXECINSTR) + walk_section(file, sec); + } +} + int check(struct objtool_file *file) { int ret; @@ -56,11 +333,21 @@ int check(struct objtool_file *file) if (!opts.stackval) return 1; + arch_initial_func_cfi_state(&initial_func_cfi); + + if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) + return -1; + ret = decode_instructions(file); if (ret) return ret; add_jump_destinations(file); + if (list_empty(&file->insn_list)) + return 0; + + walk_sections(file); + return 0; } diff --git a/tools/objtool/include/objtool/insn.h b/tools/objtool/include/objtool/insn.h index cfd1ae7e2e8e..3a43a591b318 100644 --- a/tools/objtool/include/objtool/insn.h +++ b/tools/objtool/include/objtool/insn.h @@ -84,7 +84,8 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn); struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn); -bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2); +bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2, + bool print); bool same_function(struct instruction *insn1, struct instruction *insn2); bool is_first_func_insn(struct objtool_file *file, struct instruction *insn); diff --git a/tools/objtool/insn.c b/tools/objtool/insn.c index e570b46ad39e..be3617d55aea 100644 --- a/tools/objtool/insn.c +++ b/tools/objtool/insn.c @@ -135,7 +135,8 @@ bool is_first_func_insn(struct objtool_file *file, struct instruction *insn) return false; } -bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) +bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2, + bool print) { struct cfi_state *cfi1 = insn->cfi; int i; @@ -147,10 +148,12 @@ bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) if (memcmp(&cfi1->cfa, &cfi2->cfa, sizeof(cfi1->cfa))) { - WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", - insn->sec, insn->offset, - cfi1->cfa.base, cfi1->cfa.offset, - cfi2->cfa.base, cfi2->cfa.offset); + if (print) { + WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", + insn->sec, insn->offset, + cfi1->cfa.base, cfi1->cfa.offset, + cfi2->cfa.base, cfi2->cfa.offset); + } } else if (memcmp(&cfi1->regs, &cfi2->regs, sizeof(cfi1->regs))) { for (i = 0; i < CFI_NUM_REGS; i++) { @@ -158,26 +161,32 @@ bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) sizeof(struct cfi_reg))) continue; - WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", - insn->sec, insn->offset, - i, cfi1->regs[i].base, cfi1->regs[i].offset, - i, cfi2->regs[i].base, cfi2->regs[i].offset); + if (print) { + WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", + insn->sec, insn->offset, + i, cfi1->regs[i].base, cfi1->regs[i].offset, + i, cfi2->regs[i].base, cfi2->regs[i].offset); + } break; } } else if (cfi1->type != cfi2->type) { - WARN_FUNC("stack state mismatch: type1=%d type2=%d", - insn->sec, insn->offset, cfi1->type, cfi2->type); + if (print) { + WARN_FUNC("stack state mismatch: type1=%d type2=%d", + insn->sec, insn->offset, cfi1->type, cfi2->type); + } } else if (cfi1->drap != cfi2->drap || (cfi1->drap && cfi1->drap_reg != cfi2->drap_reg) || (cfi1->drap && cfi1->drap_offset != cfi2->drap_offset)) { - WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", - insn->sec, insn->offset, - cfi1->drap, cfi1->drap_reg, cfi1->drap_offset, - cfi2->drap, cfi2->drap_reg, cfi2->drap_offset); + if (print) { + WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", + insn->sec, insn->offset, + cfi1->drap, cfi1->drap_reg, cfi1->drap_offset, + cfi2->drap, cfi2->drap_reg, cfi2->drap_offset); + } } else return true; -- 2.25.1
WARNING: multiple messages have this Message-ID (diff)
From: madvenka@linux.microsoft.com To: jpoimboe@redhat.com, peterz@infradead.org, chenzhongjin@huawei.com, mark.rutland@arm.com, broonie@kernel.org, nobuta.keiya@fujitsu.com, sjitindarsingh@gmail.com, catalin.marinas@arm.com, will@kernel.org, jamorris@linux.microsoft.com, linux-arm-kernel@lists.infradead.org, live-patching@vger.kernel.org, linux-kernel@vger.kernel.org, madvenka@linux.microsoft.com Subject: [RFC PATCH v3 13/22] objtool: arm64: Walk instructions and compute CFI for each instruction Date: Thu, 2 Feb 2023 01:40:27 -0600 [thread overview] Message-ID: <20230202074036.507249-14-madvenka@linux.microsoft.com> (raw) In-Reply-To: <20230202074036.507249-1-madvenka@linux.microsoft.com> From: "Madhavan T. Venkataraman" <madvenka@linux.microsoft.com> Implement arch_initial_func_cfi_state() to initialize the CFI for a function. Add code to check() in dcheck.c to walk the instructions in every function and compute the CFI information for each instruction. Perform the following checks to validate the CFI: - Make sure that there is exactly one frame pointer prolog for an epilog. - Make sure that the frame pointer register is initialized to the location at which the previous frame pointer is stored on the stack. - Make sure that the frame pointer is restored in the epilog from the same location on stack where it was saved. - Make sure that the return address is restored in the epilog from the same location on stack where it was saved. - Make sure that the frame pointer and return address are saved on the stack adjacent to each other in the correct order as specified in the ABI. - If an instruction can be reached via two different code paths, make sure that the CFIs computed from traversing each path match for the instruction. - Every time the frame pointer or stack offset is changed, make sure the offsets have legal values. insn_cfi_match() is used to compare CFIs to see if they match. When there is a mismatch, the function emits error messages. With static checking, these errors result in failure. With dynamic checking, these errors only resulting in marking those instructions as unreliable for unwind. In the latter case, suppress the warning messages. Signed-off-by: Madhavan T. Venkataraman <madvenka@linux.microsoft.com> --- tools/objtool/arch/arm64/decode.c | 15 ++ tools/objtool/check.c | 2 +- tools/objtool/dcheck.c | 287 +++++++++++++++++++++++++++ tools/objtool/include/objtool/insn.h | 3 +- tools/objtool/insn.c | 39 ++-- 5 files changed, 329 insertions(+), 17 deletions(-) diff --git a/tools/objtool/arch/arm64/decode.c b/tools/objtool/arch/arm64/decode.c index 81653ed3c323..f723be80c09a 100644 --- a/tools/objtool/arch/arm64/decode.c +++ b/tools/objtool/arch/arm64/decode.c @@ -22,6 +22,21 @@ /* --------------------- arch support functions ------------------------- */ +void arch_initial_func_cfi_state(struct cfi_init_state *state) +{ + int i; + + for (i = 0; i < CFI_NUM_REGS; i++) { + state->regs[i].base = CFI_UNDEFINED; + state->regs[i].offset = 0; + } + state->regs[CFI_FP].base = CFI_CFA; + + /* initial CFA (call frame address) */ + state->cfa.base = CFI_SP; + state->cfa.offset = 0; +} + unsigned long arch_dest_reloc_offset(int addend) { return addend; diff --git a/tools/objtool/check.c b/tools/objtool/check.c index d14a2b7b8b37..94efe94a566e 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -2863,7 +2863,7 @@ static int validate_branch(struct objtool_file *file, struct symbol *func, visited = VISITED_BRANCH << state.uaccess; if (insn->visited & VISITED_BRANCH_MASK) { - if (!insn->hint && !insn_cfi_match(insn, &state.cfi)) + if (!insn->hint && !insn_cfi_match(insn, &state.cfi, true)) return 1; if (insn->visited & visited) diff --git a/tools/objtool/dcheck.c b/tools/objtool/dcheck.c index eb806a032a32..8b78cb608528 100644 --- a/tools/objtool/dcheck.c +++ b/tools/objtool/dcheck.c @@ -49,6 +49,283 @@ static void add_jump_destinations(struct objtool_file *file) } } +static bool update_cfi_state(struct cfi_state *cfi, struct stack_op *op) +{ + struct cfi_reg *cfa = &cfi->cfa; + struct cfi_reg *fp_reg = &cfi->regs[CFI_FP]; + struct cfi_reg *fp_val = &cfi->vals[CFI_FP]; + struct cfi_reg *ra_val = &cfi->vals[CFI_RA]; + enum op_src_type src_type = op->src.type; + enum op_dest_type dest_type = op->dest.type; + unsigned char dest_reg = op->dest.reg; + int offset; + + if (src_type == OP_SRC_ADD && dest_type == OP_DEST_REG) { + + if (op->src.reg == CFI_SP) { + if (op->dest.reg == CFI_SP) { + cfa->offset -= op->src.offset; + } else { + if (fp_reg->offset) { + /* FP is already set. */ + return false; + } + fp_reg->offset = -cfa->offset + op->src.offset; + if (fp_reg->offset != fp_val->offset) { + /* + * FP does not match the location + * where FP is stored on stack. + */ + return false; + } + } + } else { + if (op->dest.reg == CFI_SP) { + cfa->offset = + -(fp_reg->offset + op->src.offset); + } else { + /* Setting the FP from itself is unreliable. */ + return false; + } + } + /* + * When the stack pointer is restored in the frame pointer + * epilog, forget where the FP and RA were stored. + */ + if (cfa->offset < -fp_val->offset) + fp_val->offset = 0; + if (cfa->offset < -ra_val->offset) + ra_val->offset = 0; + goto out; + } + + if (src_type == OP_SRC_REG_INDIRECT && dest_type == OP_DEST_REG) { + offset = -cfa->offset + op->src.offset; + if (dest_reg == CFI_FP) { + if (!fp_val->offset || fp_val->offset != offset) { + /* + * Loading the FP from a different place than + * where it is stored. + */ + return false; + } + if (!ra_val->offset || + (ra_val->offset - fp_val->offset) != 8) { + /* FP and RA must be adjacent in a frame. */ + return false; + } + fp_reg->offset = 0; + } + goto out; + } + + if (src_type == OP_SRC_REG && dest_type == OP_DEST_REG_INDIRECT) { + offset = -cfa->offset + op->dest.offset; + if (dest_reg == CFI_FP) { + /* Record where the FP is stored on the stack. */ + fp_val->offset = offset; + } else { + /* Record where the RA is stored on the stack. */ + if (fp_val->offset && (offset - fp_val->offset) == 8) + ra_val->offset = offset; + } + goto out; + } + return false; +out: + if (cfa->offset < 0 || fp_reg->offset > 0 || + fp_val->offset > 0 || ra_val->offset > 0) { + /* Unexpected SP and FP offset values. */ + return false; + } + return true; +} + +static bool do_stack_ops(struct instruction *insn, struct insn_state *state) +{ + struct stack_op *op; + + list_for_each_entry(op, &insn->stack_ops, list) { + if (!update_cfi_state(&state->cfi, op)) + return false; + } + return true; +} + +static bool validate_branch(struct objtool_file *file, struct section *sec, + struct symbol *func, struct instruction *insn, + struct insn_state *state) +{ + struct symbol *insn_func = insn->func; + struct instruction *dest; + struct cfi_state save_cfi; + struct cfi_reg *cfa; + struct cfi_reg *regs; + unsigned long start, end; + + for (; insn; insn = next_insn_same_sec(file, insn)) { + + if (insn->func != insn_func) + return true; + + if (insn->cfi) + return insn_cfi_match(insn, &state->cfi, false); + + insn->cfi = cfi_hash_find_or_add(&state->cfi); + dest = insn->jump_dest; + + if (!do_stack_ops(insn, state)) + return false; + + switch (insn->type) { + case INSN_BUG: + return true; + + case INSN_UNRELIABLE: + return false; + + case INSN_RETURN: + cfa = &state->cfi.cfa; + regs = state->cfi.regs; + if (cfa->offset || regs[CFI_FP].offset) { + /* SP and FP offsets should be 0 on return. */ + return false; + } + return true; + + case INSN_CALL: + case INSN_CALL_DYNAMIC: + start = func->offset; + end = start + func->len; + /* Treat intra-function calls as jumps. */ + if (!dest || dest->sec != sec || + dest->offset <= start || dest->offset >= end) { + break; + } + + case INSN_JUMP_UNCONDITIONAL: + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_DYNAMIC: + if (dest) { + save_cfi = state->cfi; + if (!validate_branch(file, sec, func, dest, + state)) { + return false; + } + state->cfi = save_cfi; + } + if (insn->type == INSN_JUMP_UNCONDITIONAL || + insn->type == INSN_JUMP_DYNAMIC) { + return true; + } + break; + + default: + break; + } + } + return true; +} + +static bool walk_reachable(struct objtool_file *file, struct section *sec, + struct symbol *func) +{ + struct instruction *insn = find_insn(file, sec, func->offset); + struct insn_state state; + + func_for_each_insn(file, func, insn) { + + if (insn->offset != func->offset && + (insn->type != INSN_START || insn->cfi)) { + continue; + } + + init_insn_state(file, &state, sec); + set_func_state(&state.cfi); + + if (!validate_branch(file, sec, func, insn, &state)) + return false; + } + return true; +} + +static void remove_cfi(struct objtool_file *file, struct symbol *func) +{ + struct instruction *insn; + + func_for_each_insn(file, func, insn) { + insn->cfi = NULL; + } +} + +/* + * Instructions that were not visited by walk_reachable() would not have a + * CFI. Try to initialize their CFI. For instance, there could be a table of + * unconditional branches like for a switch statement. Or, code can be patched + * by the kernel at runtime. After patching, some of the previously unreachable + * code may become reachable. + * + * This follows the same pattern as the DWARF info generated by the compiler. + */ +static bool walk_unreachable(struct objtool_file *file, struct section *sec, + struct symbol *func) +{ + struct instruction *insn, *prev; + struct insn_state state; + + func_for_each_insn(file, func, insn) { + + if (insn->cfi) + continue; + + prev = list_prev_entry(insn, list); + if (!prev || prev->func != insn->func || !prev->cfi) + continue; + + if (prev->type != INSN_JUMP_UNCONDITIONAL && + prev->type != INSN_JUMP_DYNAMIC && + prev->type != INSN_BUG) { + continue; + } + + /* Propagate the CFI. */ + state.cfi = *prev->cfi; + if (!validate_branch(file, sec, func, insn, &state)) + return false; + } + return true; +} + +static void walk_section(struct objtool_file *file, struct section *sec) +{ + struct symbol *func; + + list_for_each_entry(func, &sec->symbol_list, list) { + + if (func->type != STT_FUNC || !func->len || + func->pfunc != func || func->alias != func) { + /* No CFI generated for this function. */ + continue; + } + + if (!walk_reachable(file, sec, func) || + !walk_unreachable(file, sec, func)) { + remove_cfi(file, func); + continue; + } + } +} + +static void walk_sections(struct objtool_file *file) +{ + struct section *sec; + + for_each_sec(file, sec) { + if (sec->sh.sh_flags & SHF_EXECINSTR) + walk_section(file, sec); + } +} + int check(struct objtool_file *file) { int ret; @@ -56,11 +333,21 @@ int check(struct objtool_file *file) if (!opts.stackval) return 1; + arch_initial_func_cfi_state(&initial_func_cfi); + + if (!cfi_hash_alloc(1UL << (file->elf->symbol_bits - 3))) + return -1; + ret = decode_instructions(file); if (ret) return ret; add_jump_destinations(file); + if (list_empty(&file->insn_list)) + return 0; + + walk_sections(file); + return 0; } diff --git a/tools/objtool/include/objtool/insn.h b/tools/objtool/include/objtool/insn.h index cfd1ae7e2e8e..3a43a591b318 100644 --- a/tools/objtool/include/objtool/insn.h +++ b/tools/objtool/include/objtool/insn.h @@ -84,7 +84,8 @@ struct instruction *next_insn_same_sec(struct objtool_file *file, struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn); struct reloc *insn_reloc(struct objtool_file *file, struct instruction *insn); -bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2); +bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2, + bool print); bool same_function(struct instruction *insn1, struct instruction *insn2); bool is_first_func_insn(struct objtool_file *file, struct instruction *insn); diff --git a/tools/objtool/insn.c b/tools/objtool/insn.c index e570b46ad39e..be3617d55aea 100644 --- a/tools/objtool/insn.c +++ b/tools/objtool/insn.c @@ -135,7 +135,8 @@ bool is_first_func_insn(struct objtool_file *file, struct instruction *insn) return false; } -bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) +bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2, + bool print) { struct cfi_state *cfi1 = insn->cfi; int i; @@ -147,10 +148,12 @@ bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) if (memcmp(&cfi1->cfa, &cfi2->cfa, sizeof(cfi1->cfa))) { - WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", - insn->sec, insn->offset, - cfi1->cfa.base, cfi1->cfa.offset, - cfi2->cfa.base, cfi2->cfa.offset); + if (print) { + WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", + insn->sec, insn->offset, + cfi1->cfa.base, cfi1->cfa.offset, + cfi2->cfa.base, cfi2->cfa.offset); + } } else if (memcmp(&cfi1->regs, &cfi2->regs, sizeof(cfi1->regs))) { for (i = 0; i < CFI_NUM_REGS; i++) { @@ -158,26 +161,32 @@ bool insn_cfi_match(struct instruction *insn, struct cfi_state *cfi2) sizeof(struct cfi_reg))) continue; - WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", - insn->sec, insn->offset, - i, cfi1->regs[i].base, cfi1->regs[i].offset, - i, cfi2->regs[i].base, cfi2->regs[i].offset); + if (print) { + WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", + insn->sec, insn->offset, + i, cfi1->regs[i].base, cfi1->regs[i].offset, + i, cfi2->regs[i].base, cfi2->regs[i].offset); + } break; } } else if (cfi1->type != cfi2->type) { - WARN_FUNC("stack state mismatch: type1=%d type2=%d", - insn->sec, insn->offset, cfi1->type, cfi2->type); + if (print) { + WARN_FUNC("stack state mismatch: type1=%d type2=%d", + insn->sec, insn->offset, cfi1->type, cfi2->type); + } } else if (cfi1->drap != cfi2->drap || (cfi1->drap && cfi1->drap_reg != cfi2->drap_reg) || (cfi1->drap && cfi1->drap_offset != cfi2->drap_offset)) { - WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", - insn->sec, insn->offset, - cfi1->drap, cfi1->drap_reg, cfi1->drap_offset, - cfi2->drap, cfi2->drap_reg, cfi2->drap_offset); + if (print) { + WARN_FUNC("stack state mismatch: drap1=%d(%d,%d) drap2=%d(%d,%d)", + insn->sec, insn->offset, + cfi1->drap, cfi1->drap_reg, cfi1->drap_offset, + cfi2->drap, cfi2->drap_reg, cfi2->drap_offset); + } } else return true; -- 2.25.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
next prev parent reply other threads:[~2023-02-02 7:43 UTC|newest] Thread overview: 110+ messages / expand[flat|nested] mbox.gz Atom feed top [not found] <0337266cf19f4c98388e3f6d09f590d9de258dc7> 2023-02-02 7:40 ` [RFC PATCH v3 00/22] arm64: livepatch: Use ORC for dynamic frame pointer validation madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 01/22] objtool: Reorganize CFI code madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 02/22] objtool: Reorganize instruction-related code madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 03/22] objtool: Move decode_instructions() to a separate file madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 04/22] objtool: Reorganize Unwind hint code madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 05/22] objtool: Reorganize ORC types madvenka 2023-02-02 7:40 ` madvenka 2023-02-18 9:30 ` Suraj Jitindar Singh 2023-02-18 9:30 ` Suraj Jitindar Singh 2023-03-06 16:45 ` Madhavan T. Venkataraman 2023-03-06 16:45 ` Madhavan T. Venkataraman 2023-02-02 7:40 ` [RFC PATCH v3 06/22] objtool: Reorganize ORC code madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 07/22] objtool: Reorganize ORC kernel code madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 08/22] objtool: Introduce STATIC_CHECK madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 09/22] objtool: arm64: Add basic definitions and compile madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 10/22] objtool: arm64: Implement decoder for Dynamic FP validation madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 11/22] objtool: arm64: Invoke the decoder madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 12/22] objtool: arm64: Compute destinations for call and jump instructions madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` madvenka [this message] 2023-02-02 7:40 ` [RFC PATCH v3 13/22] objtool: arm64: Walk instructions and compute CFI for each instruction madvenka 2023-02-02 7:40 ` [RFC PATCH v3 14/22] objtool: arm64: Generate ORC data from CFI for object files madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 15/22] objtool: arm64: Add unwind hint support madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 16/22] arm64: Add unwind hints to exception handlers madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 17/22] arm64: Add kernel and module support for ORC madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 18/22] arm64: Build the kernel with ORC information madvenka 2023-02-02 7:40 ` madvenka 2023-02-10 7:52 ` Tomohiro Misono (Fujitsu) 2023-02-10 7:52 ` Tomohiro Misono (Fujitsu) 2023-02-11 4:34 ` Madhavan T. Venkataraman 2023-02-11 4:34 ` Madhavan T. Venkataraman 2023-02-02 7:40 ` [RFC PATCH v3 19/22] arm64: unwinder: Add a reliability check in the unwinder based on ORC madvenka 2023-02-02 7:40 ` madvenka 2023-02-23 4:07 ` Suraj Jitindar Singh 2023-02-23 4:07 ` Suraj Jitindar Singh 2023-03-06 16:52 ` Madhavan T. Venkataraman 2023-03-06 16:52 ` Madhavan T. Venkataraman 2023-02-02 7:40 ` [RFC PATCH v3 20/22] arm64: Define HAVE_DYNAMIC_FTRACE_WITH_ARGS madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 21/22] arm64: Define TIF_PATCH_PENDING for livepatch madvenka 2023-02-02 7:40 ` madvenka 2023-02-02 7:40 ` [RFC PATCH v3 22/22] arm64: Enable livepatch for ARM64 madvenka 2023-02-02 7:40 ` madvenka 2023-03-01 3:12 ` [RFC PATCH v3 00/22] arm64: livepatch: Use ORC for dynamic frame pointer validation Tomohiro Misono (Fujitsu) 2023-03-01 3:12 ` Tomohiro Misono (Fujitsu) 2023-03-02 16:23 ` Petr Mladek 2023-03-02 16:23 ` Petr Mladek 2023-03-03 9:40 ` Tomohiro Misono (Fujitsu) 2023-03-03 9:40 ` Tomohiro Misono (Fujitsu) 2023-03-06 16:58 ` Madhavan T. Venkataraman 2023-03-06 16:58 ` Madhavan T. Venkataraman 2023-03-06 16:57 ` Madhavan T. Venkataraman 2023-03-06 16:57 ` Madhavan T. Venkataraman 2023-03-23 17:17 ` Mark Rutland 2023-03-23 17:17 ` Mark Rutland 2023-04-08 3:40 ` Madhavan T. Venkataraman 2023-04-08 3:40 ` Madhavan T. Venkataraman 2023-04-11 13:25 ` Mark Rutland 2023-04-11 13:25 ` Mark Rutland 2023-04-12 4:17 ` Josh Poimboeuf 2023-04-12 4:17 ` Josh Poimboeuf 2023-04-12 4:48 ` Madhavan T. Venkataraman 2023-04-12 4:48 ` Madhavan T. Venkataraman 2023-04-12 4:50 ` Madhavan T. Venkataraman 2023-04-12 4:50 ` Madhavan T. Venkataraman 2023-04-12 5:01 ` Josh Poimboeuf 2023-04-12 5:01 ` Josh Poimboeuf 2023-04-12 14:50 ` Madhavan T. Venkataraman 2023-04-12 14:50 ` Madhavan T. Venkataraman 2023-04-12 15:52 ` Josh Poimboeuf 2023-04-12 15:52 ` Josh Poimboeuf 2023-04-13 14:59 ` Madhavan T. Venkataraman 2023-04-13 14:59 ` Madhavan T. Venkataraman 2023-04-13 16:30 ` Josh Poimboeuf 2023-04-13 16:30 ` Josh Poimboeuf 2023-04-15 4:27 ` Madhavan T. Venkataraman 2023-04-15 4:27 ` Madhavan T. Venkataraman 2023-04-15 5:05 ` Josh Poimboeuf 2023-04-15 5:05 ` Josh Poimboeuf 2023-04-15 16:15 ` Madhavan T. Venkataraman 2023-04-15 16:15 ` Madhavan T. Venkataraman 2023-04-16 8:21 ` Indu Bhagat 2023-04-16 8:21 ` Indu Bhagat 2023-04-13 17:04 ` Nick Desaulniers 2023-04-13 17:04 ` Nick Desaulniers 2023-04-13 18:15 ` Jose E. Marchesi 2023-04-13 18:15 ` Jose E. Marchesi 2023-04-15 4:14 ` Madhavan T. Venkataraman 2023-04-15 4:14 ` Madhavan T. Venkataraman 2023-12-14 20:49 ` ARM64 Livepatch based on SFrame Madhavan T. Venkataraman 2023-12-14 20:49 ` Madhavan T. Venkataraman 2023-12-15 13:04 ` Mark Rutland 2023-12-15 13:04 ` Mark Rutland 2023-12-15 15:15 ` Madhavan T. Venkataraman 2023-12-15 15:15 ` Madhavan T. Venkataraman
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20230202074036.507249-14-madvenka@linux.microsoft.com \ --to=madvenka@linux.microsoft.com \ --cc=broonie@kernel.org \ --cc=catalin.marinas@arm.com \ --cc=chenzhongjin@huawei.com \ --cc=jamorris@linux.microsoft.com \ --cc=jpoimboe@redhat.com \ --cc=linux-arm-kernel@lists.infradead.org \ --cc=linux-kernel@vger.kernel.org \ --cc=live-patching@vger.kernel.org \ --cc=mark.rutland@arm.com \ --cc=nobuta.keiya@fujitsu.com \ --cc=peterz@infradead.org \ --cc=sjitindarsingh@gmail.com \ --cc=will@kernel.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.