objtool: Combine UNWIND_HINT_RET_OFFSET and UNWIND_HINT_FUNC
The ORC metadata generated for UNWIND_HINT_FUNC isn't actually very func-like. With certain usages it can cause stack state mismatches because it doesn't set the return address (CFI_RA). Also, users of UNWIND_HINT_RET_OFFSET no longer need to set a custom return stack offset. Instead they just need to specify a func-like situation, so the current ret_offset code is hacky for no good reason. Solve both problems by simplifying the RET_OFFSET handling and converting it into a more useful UNWIND_HINT_FUNC. If we end up needing the old 'ret_offset' functionality again in the future, we should be able to support it pretty easily with the addition of a custom 'sp_offset' in UNWIND_HINT_FUNC. Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Link: https://lore.kernel.org/r/db9d1f5d79dddfbb3725ef6d8ec3477ad199948d.1611263462.git.jpoimboe@redhat.com
This commit is contained in:
parent
081df94301
commit
b735bd3e68
@ -48,17 +48,8 @@
|
|||||||
UNWIND_HINT_REGS base=\base offset=\offset partial=1
|
UNWIND_HINT_REGS base=\base offset=\offset partial=1
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro UNWIND_HINT_FUNC sp_offset=8
|
.macro UNWIND_HINT_FUNC
|
||||||
UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=\sp_offset type=UNWIND_HINT_TYPE_CALL
|
UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=8 type=UNWIND_HINT_TYPE_FUNC
|
||||||
.endm
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RET_OFFSET: Used on instructions that terminate a function; mostly RETURN
|
|
||||||
* and sibling calls. On these, sp_offset denotes the expected offset from
|
|
||||||
* initial_func_cfi.
|
|
||||||
*/
|
|
||||||
.macro UNWIND_HINT_RET_OFFSET sp_offset=8
|
|
||||||
UNWIND_HINT sp_reg=ORC_REG_SP type=UNWIND_HINT_TYPE_RET_OFFSET sp_offset=\sp_offset
|
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
#endif /* __ASSEMBLY__ */
|
#endif /* __ASSEMBLY__ */
|
||||||
|
@ -277,7 +277,7 @@ SYM_INNER_LABEL(ftrace_regs_caller_end, SYM_L_GLOBAL)
|
|||||||
restore_mcount_regs 8
|
restore_mcount_regs 8
|
||||||
/* Restore flags */
|
/* Restore flags */
|
||||||
popfq
|
popfq
|
||||||
UNWIND_HINT_RET_OFFSET
|
UNWIND_HINT_FUNC
|
||||||
jmp ftrace_epilogue
|
jmp ftrace_epilogue
|
||||||
|
|
||||||
SYM_FUNC_END(ftrace_regs_caller)
|
SYM_FUNC_END(ftrace_regs_caller)
|
||||||
|
@ -28,7 +28,7 @@ SYM_FUNC_START_NOALIGN(__x86_retpoline_\reg)
|
|||||||
jmp .Lspec_trap_\@
|
jmp .Lspec_trap_\@
|
||||||
.Ldo_rop_\@:
|
.Ldo_rop_\@:
|
||||||
mov %\reg, (%_ASM_SP)
|
mov %\reg, (%_ASM_SP)
|
||||||
UNWIND_HINT_RET_OFFSET
|
UNWIND_HINT_FUNC
|
||||||
ret
|
ret
|
||||||
SYM_FUNC_END(__x86_retpoline_\reg)
|
SYM_FUNC_END(__x86_retpoline_\reg)
|
||||||
|
|
||||||
|
@ -29,11 +29,14 @@ struct unwind_hint {
|
|||||||
*
|
*
|
||||||
* UNWIND_HINT_TYPE_REGS_PARTIAL: Used in entry code to indicate that
|
* UNWIND_HINT_TYPE_REGS_PARTIAL: Used in entry code to indicate that
|
||||||
* sp_reg+sp_offset points to the iret return frame.
|
* sp_reg+sp_offset points to the iret return frame.
|
||||||
|
*
|
||||||
|
* UNWIND_HINT_FUNC: Generate the unwind metadata of a callable function.
|
||||||
|
* Useful for code which doesn't have an ELF function annotation.
|
||||||
*/
|
*/
|
||||||
#define UNWIND_HINT_TYPE_CALL 0
|
#define UNWIND_HINT_TYPE_CALL 0
|
||||||
#define UNWIND_HINT_TYPE_REGS 1
|
#define UNWIND_HINT_TYPE_REGS 1
|
||||||
#define UNWIND_HINT_TYPE_REGS_PARTIAL 2
|
#define UNWIND_HINT_TYPE_REGS_PARTIAL 2
|
||||||
#define UNWIND_HINT_TYPE_RET_OFFSET 3
|
#define UNWIND_HINT_TYPE_FUNC 3
|
||||||
|
|
||||||
#ifdef CONFIG_STACK_VALIDATION
|
#ifdef CONFIG_STACK_VALIDATION
|
||||||
|
|
||||||
|
@ -29,11 +29,14 @@ struct unwind_hint {
|
|||||||
*
|
*
|
||||||
* UNWIND_HINT_TYPE_REGS_PARTIAL: Used in entry code to indicate that
|
* UNWIND_HINT_TYPE_REGS_PARTIAL: Used in entry code to indicate that
|
||||||
* sp_reg+sp_offset points to the iret return frame.
|
* sp_reg+sp_offset points to the iret return frame.
|
||||||
|
*
|
||||||
|
* UNWIND_HINT_FUNC: Generate the unwind metadata of a callable function.
|
||||||
|
* Useful for code which doesn't have an ELF function annotation.
|
||||||
*/
|
*/
|
||||||
#define UNWIND_HINT_TYPE_CALL 0
|
#define UNWIND_HINT_TYPE_CALL 0
|
||||||
#define UNWIND_HINT_TYPE_REGS 1
|
#define UNWIND_HINT_TYPE_REGS 1
|
||||||
#define UNWIND_HINT_TYPE_REGS_PARTIAL 2
|
#define UNWIND_HINT_TYPE_REGS_PARTIAL 2
|
||||||
#define UNWIND_HINT_TYPE_RET_OFFSET 3
|
#define UNWIND_HINT_TYPE_FUNC 3
|
||||||
|
|
||||||
#ifdef CONFIG_STACK_VALIDATION
|
#ifdef CONFIG_STACK_VALIDATION
|
||||||
|
|
||||||
|
@ -563,8 +563,8 @@ void arch_initial_func_cfi_state(struct cfi_init_state *state)
|
|||||||
state->cfa.offset = 8;
|
state->cfa.offset = 8;
|
||||||
|
|
||||||
/* initial RA (return address) */
|
/* initial RA (return address) */
|
||||||
state->regs[16].base = CFI_CFA;
|
state->regs[CFI_RA].base = CFI_CFA;
|
||||||
state->regs[16].offset = -8;
|
state->regs[CFI_RA].offset = -8;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *arch_nop_insn(int len)
|
const char *arch_nop_insn(int len)
|
||||||
|
@ -1404,13 +1404,20 @@ static int add_jump_table_alts(struct objtool_file *file)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set_func_state(struct cfi_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;
|
||||||
|
}
|
||||||
|
|
||||||
static int read_unwind_hints(struct objtool_file *file)
|
static int read_unwind_hints(struct objtool_file *file)
|
||||||
{
|
{
|
||||||
struct section *sec, *relocsec;
|
struct section *sec, *relocsec;
|
||||||
struct reloc *reloc;
|
struct reloc *reloc;
|
||||||
struct unwind_hint *hint;
|
struct unwind_hint *hint;
|
||||||
struct instruction *insn;
|
struct instruction *insn;
|
||||||
struct cfi_reg *cfa;
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
sec = find_section_by_name(file->elf, ".discard.unwind_hints");
|
sec = find_section_by_name(file->elf, ".discard.unwind_hints");
|
||||||
@ -1445,22 +1452,20 @@ static int read_unwind_hints(struct objtool_file *file)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cfa = &insn->cfi.cfa;
|
insn->hint = true;
|
||||||
|
|
||||||
if (hint->type == UNWIND_HINT_TYPE_RET_OFFSET) {
|
if (hint->type == UNWIND_HINT_TYPE_FUNC) {
|
||||||
insn->ret_offset = bswap_if_needed(hint->sp_offset);
|
set_func_state(&insn->cfi);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
insn->hint = true;
|
|
||||||
|
|
||||||
if (arch_decode_hint_reg(insn, hint->sp_reg)) {
|
if (arch_decode_hint_reg(insn, hint->sp_reg)) {
|
||||||
WARN_FUNC("unsupported unwind_hint sp base reg %d",
|
WARN_FUNC("unsupported unwind_hint sp base reg %d",
|
||||||
insn->sec, insn->offset, hint->sp_reg);
|
insn->sec, insn->offset, hint->sp_reg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
cfa->offset = bswap_if_needed(hint->sp_offset);
|
insn->cfi.cfa.offset = bswap_if_needed(hint->sp_offset);
|
||||||
insn->cfi.type = hint->type;
|
insn->cfi.type = hint->type;
|
||||||
insn->cfi.end = hint->end;
|
insn->cfi.end = hint->end;
|
||||||
}
|
}
|
||||||
@ -1716,27 +1721,18 @@ static bool is_fentry_call(struct instruction *insn)
|
|||||||
|
|
||||||
static bool has_modified_stack_frame(struct instruction *insn, struct insn_state *state)
|
static bool has_modified_stack_frame(struct instruction *insn, struct insn_state *state)
|
||||||
{
|
{
|
||||||
u8 ret_offset = insn->ret_offset;
|
|
||||||
struct cfi_state *cfi = &state->cfi;
|
struct cfi_state *cfi = &state->cfi;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (cfi->cfa.base != initial_func_cfi.cfa.base || cfi->drap)
|
if (cfi->cfa.base != initial_func_cfi.cfa.base || cfi->drap)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (cfi->cfa.offset != initial_func_cfi.cfa.offset + ret_offset)
|
if (cfi->cfa.offset != initial_func_cfi.cfa.offset)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (cfi->stack_size != initial_func_cfi.cfa.offset + ret_offset)
|
if (cfi->stack_size != initial_func_cfi.cfa.offset)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
/*
|
|
||||||
* If there is a ret offset hint then don't check registers
|
|
||||||
* because a callee-saved register might have been pushed on
|
|
||||||
* the stack.
|
|
||||||
*/
|
|
||||||
if (ret_offset)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (i = 0; i < CFI_NUM_REGS; i++) {
|
for (i = 0; i < CFI_NUM_REGS; i++) {
|
||||||
if (cfi->regs[i].base != initial_func_cfi.regs[i].base ||
|
if (cfi->regs[i].base != initial_func_cfi.regs[i].base ||
|
||||||
cfi->regs[i].offset != initial_func_cfi.regs[i].offset)
|
cfi->regs[i].offset != initial_func_cfi.regs[i].offset)
|
||||||
@ -2880,10 +2876,7 @@ static int validate_section(struct objtool_file *file, struct section *sec)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
init_insn_state(&state, sec);
|
init_insn_state(&state, sec);
|
||||||
state.cfi.cfa = initial_func_cfi.cfa;
|
set_func_state(&state.cfi);
|
||||||
memcpy(&state.cfi.regs, &initial_func_cfi.regs,
|
|
||||||
CFI_NUM_REGS * sizeof(struct cfi_reg));
|
|
||||||
state.cfi.stack_size = initial_func_cfi.cfa.offset;
|
|
||||||
|
|
||||||
warnings += validate_symbol(file, sec, func, &state);
|
warnings += validate_symbol(file, sec, func, &state);
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,6 @@ struct instruction {
|
|||||||
bool retpoline_safe;
|
bool retpoline_safe;
|
||||||
s8 instr;
|
s8 instr;
|
||||||
u8 visited;
|
u8 visited;
|
||||||
u8 ret_offset;
|
|
||||||
struct alt_group *alt_group;
|
struct alt_group *alt_group;
|
||||||
struct symbol *call_dest;
|
struct symbol *call_dest;
|
||||||
struct instruction *jump_dest;
|
struct instruction *jump_dest;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user