x86/unwind/orc: Detect the end of the stack
The existing UNWIND_HINT_EMPTY annotations happen to be good indicators of where entry code calls into C code for the first time. So also use them to mark the end of the stack for the ORC unwinder. Use that information to set unwind->error if the ORC unwinder doesn't unwind all the way to the end. This will be needed for enabling HAVE_RELIABLE_STACKTRACE for the ORC unwinder so we can use it with the livepatch consistency model. Thanks to Jiri Slaby for teaching the ORCs about the unwind hints. Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Signed-off-by: Jiri Slaby <jslaby@suse.cz> Acked-by: Josh Poimboeuf <jpoimboe@redhat.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Borislav Petkov <bp@alien8.de> Cc: Brian Gerst <brgerst@gmail.com> Cc: Denys Vlasenko <dvlasenk@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Link: https://lkml.kernel.org/lkml/20180518064713.26440-5-jslaby@suse.cz Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
parent
0c414367c0
commit
d31a580266
@ -408,6 +408,7 @@ ENTRY(ret_from_fork)
|
|||||||
|
|
||||||
1:
|
1:
|
||||||
/* kernel thread */
|
/* kernel thread */
|
||||||
|
UNWIND_HINT_EMPTY
|
||||||
movq %r12, %rdi
|
movq %r12, %rdi
|
||||||
CALL_NOSPEC %rbx
|
CALL_NOSPEC %rbx
|
||||||
/*
|
/*
|
||||||
|
@ -88,6 +88,7 @@ struct orc_entry {
|
|||||||
unsigned sp_reg:4;
|
unsigned sp_reg:4;
|
||||||
unsigned bp_reg:4;
|
unsigned bp_reg:4;
|
||||||
unsigned type:2;
|
unsigned type:2;
|
||||||
|
unsigned end:1;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -101,6 +102,7 @@ struct unwind_hint {
|
|||||||
s16 sp_offset;
|
s16 sp_offset;
|
||||||
u8 sp_reg;
|
u8 sp_reg;
|
||||||
u8 type;
|
u8 type;
|
||||||
|
u8 end;
|
||||||
};
|
};
|
||||||
#endif /* __ASSEMBLY__ */
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
* the debuginfo as necessary. It will also warn if it sees any
|
* the debuginfo as necessary. It will also warn if it sees any
|
||||||
* inconsistencies.
|
* inconsistencies.
|
||||||
*/
|
*/
|
||||||
.macro UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=0 type=ORC_TYPE_CALL
|
.macro UNWIND_HINT sp_reg=ORC_REG_SP sp_offset=0 type=ORC_TYPE_CALL end=0
|
||||||
#ifdef CONFIG_STACK_VALIDATION
|
#ifdef CONFIG_STACK_VALIDATION
|
||||||
.Lunwind_hint_ip_\@:
|
.Lunwind_hint_ip_\@:
|
||||||
.pushsection .discard.unwind_hints
|
.pushsection .discard.unwind_hints
|
||||||
@ -35,12 +35,14 @@
|
|||||||
.short \sp_offset
|
.short \sp_offset
|
||||||
.byte \sp_reg
|
.byte \sp_reg
|
||||||
.byte \type
|
.byte \type
|
||||||
|
.byte \end
|
||||||
|
.balign 4
|
||||||
.popsection
|
.popsection
|
||||||
#endif
|
#endif
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro UNWIND_HINT_EMPTY
|
.macro UNWIND_HINT_EMPTY
|
||||||
UNWIND_HINT sp_reg=ORC_REG_UNDEFINED
|
UNWIND_HINT sp_reg=ORC_REG_UNDEFINED end=1
|
||||||
.endm
|
.endm
|
||||||
|
|
||||||
.macro UNWIND_HINT_REGS base=%rsp offset=0 indirect=0 extra=1 iret=0
|
.macro UNWIND_HINT_REGS base=%rsp offset=0 indirect=0 extra=1 iret=0
|
||||||
@ -86,19 +88,21 @@
|
|||||||
|
|
||||||
#else /* !__ASSEMBLY__ */
|
#else /* !__ASSEMBLY__ */
|
||||||
|
|
||||||
#define UNWIND_HINT(sp_reg, sp_offset, type) \
|
#define UNWIND_HINT(sp_reg, sp_offset, type, end) \
|
||||||
"987: \n\t" \
|
"987: \n\t" \
|
||||||
".pushsection .discard.unwind_hints\n\t" \
|
".pushsection .discard.unwind_hints\n\t" \
|
||||||
/* struct unwind_hint */ \
|
/* struct unwind_hint */ \
|
||||||
".long 987b - .\n\t" \
|
".long 987b - .\n\t" \
|
||||||
".short " __stringify(sp_offset) "\n\t" \
|
".short " __stringify(sp_offset) "\n\t" \
|
||||||
".byte " __stringify(sp_reg) "\n\t" \
|
".byte " __stringify(sp_reg) "\n\t" \
|
||||||
".byte " __stringify(type) "\n\t" \
|
".byte " __stringify(type) "\n\t" \
|
||||||
|
".byte " __stringify(end) "\n\t" \
|
||||||
|
".balign 4 \n\t" \
|
||||||
".popsection\n\t"
|
".popsection\n\t"
|
||||||
|
|
||||||
#define UNWIND_HINT_SAVE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_SAVE)
|
#define UNWIND_HINT_SAVE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_SAVE, 0)
|
||||||
|
|
||||||
#define UNWIND_HINT_RESTORE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_RESTORE)
|
#define UNWIND_HINT_RESTORE UNWIND_HINT(0, 0, UNWIND_HINT_TYPE_RESTORE, 0)
|
||||||
|
|
||||||
#endif /* __ASSEMBLY__ */
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ static int orc_sort_cmp(const void *_a, const void *_b)
|
|||||||
* whitelisted .o files which didn't get objtool generation.
|
* whitelisted .o files which didn't get objtool generation.
|
||||||
*/
|
*/
|
||||||
orc_a = cur_orc_table + (a - cur_orc_ip_table);
|
orc_a = cur_orc_table + (a - cur_orc_ip_table);
|
||||||
return orc_a->sp_reg == ORC_REG_UNDEFINED ? -1 : 1;
|
return orc_a->sp_reg == ORC_REG_UNDEFINED && !orc_a->end ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_MODULES
|
#ifdef CONFIG_MODULES
|
||||||
@ -352,7 +352,7 @@ static bool deref_stack_iret_regs(struct unwind_state *state, unsigned long addr
|
|||||||
|
|
||||||
bool unwind_next_frame(struct unwind_state *state)
|
bool unwind_next_frame(struct unwind_state *state)
|
||||||
{
|
{
|
||||||
unsigned long ip_p, sp, orig_ip, prev_sp = state->sp;
|
unsigned long ip_p, sp, orig_ip = state->ip, prev_sp = state->sp;
|
||||||
enum stack_type prev_type = state->stack_info.type;
|
enum stack_type prev_type = state->stack_info.type;
|
||||||
struct orc_entry *orc;
|
struct orc_entry *orc;
|
||||||
bool indirect = false;
|
bool indirect = false;
|
||||||
@ -363,9 +363,9 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
/* Don't let modules unload while we're reading their ORC data. */
|
/* Don't let modules unload while we're reading their ORC data. */
|
||||||
preempt_disable();
|
preempt_disable();
|
||||||
|
|
||||||
/* Have we reached the end? */
|
/* End-of-stack check for user tasks: */
|
||||||
if (state->regs && user_mode(state->regs))
|
if (state->regs && user_mode(state->regs))
|
||||||
goto done;
|
goto the_end;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Find the orc_entry associated with the text address.
|
* Find the orc_entry associated with the text address.
|
||||||
@ -374,9 +374,16 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
* calls and calls to noreturn functions.
|
* calls and calls to noreturn functions.
|
||||||
*/
|
*/
|
||||||
orc = orc_find(state->signal ? state->ip : state->ip - 1);
|
orc = orc_find(state->signal ? state->ip : state->ip - 1);
|
||||||
if (!orc || orc->sp_reg == ORC_REG_UNDEFINED)
|
if (!orc)
|
||||||
goto done;
|
goto err;
|
||||||
orig_ip = state->ip;
|
|
||||||
|
/* End-of-stack check for kernel threads: */
|
||||||
|
if (orc->sp_reg == ORC_REG_UNDEFINED) {
|
||||||
|
if (!orc->end)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
goto the_end;
|
||||||
|
}
|
||||||
|
|
||||||
/* Find the previous frame's stack: */
|
/* Find the previous frame's stack: */
|
||||||
switch (orc->sp_reg) {
|
switch (orc->sp_reg) {
|
||||||
@ -402,7 +409,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
if (!state->regs || !state->full_regs) {
|
if (!state->regs || !state->full_regs) {
|
||||||
orc_warn("missing regs for base reg R10 at ip %pB\n",
|
orc_warn("missing regs for base reg R10 at ip %pB\n",
|
||||||
(void *)state->ip);
|
(void *)state->ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
sp = state->regs->r10;
|
sp = state->regs->r10;
|
||||||
break;
|
break;
|
||||||
@ -411,7 +418,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
if (!state->regs || !state->full_regs) {
|
if (!state->regs || !state->full_regs) {
|
||||||
orc_warn("missing regs for base reg R13 at ip %pB\n",
|
orc_warn("missing regs for base reg R13 at ip %pB\n",
|
||||||
(void *)state->ip);
|
(void *)state->ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
sp = state->regs->r13;
|
sp = state->regs->r13;
|
||||||
break;
|
break;
|
||||||
@ -420,7 +427,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
if (!state->regs || !state->full_regs) {
|
if (!state->regs || !state->full_regs) {
|
||||||
orc_warn("missing regs for base reg DI at ip %pB\n",
|
orc_warn("missing regs for base reg DI at ip %pB\n",
|
||||||
(void *)state->ip);
|
(void *)state->ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
sp = state->regs->di;
|
sp = state->regs->di;
|
||||||
break;
|
break;
|
||||||
@ -429,7 +436,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
if (!state->regs || !state->full_regs) {
|
if (!state->regs || !state->full_regs) {
|
||||||
orc_warn("missing regs for base reg DX at ip %pB\n",
|
orc_warn("missing regs for base reg DX at ip %pB\n",
|
||||||
(void *)state->ip);
|
(void *)state->ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
sp = state->regs->dx;
|
sp = state->regs->dx;
|
||||||
break;
|
break;
|
||||||
@ -437,12 +444,12 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
default:
|
default:
|
||||||
orc_warn("unknown SP base reg %d for ip %pB\n",
|
orc_warn("unknown SP base reg %d for ip %pB\n",
|
||||||
orc->sp_reg, (void *)state->ip);
|
orc->sp_reg, (void *)state->ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indirect) {
|
if (indirect) {
|
||||||
if (!deref_stack_reg(state, sp, &sp))
|
if (!deref_stack_reg(state, sp, &sp))
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Find IP, SP and possibly regs: */
|
/* Find IP, SP and possibly regs: */
|
||||||
@ -451,7 +458,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
ip_p = sp - sizeof(long);
|
ip_p = sp - sizeof(long);
|
||||||
|
|
||||||
if (!deref_stack_reg(state, ip_p, &state->ip))
|
if (!deref_stack_reg(state, ip_p, &state->ip))
|
||||||
goto done;
|
goto err;
|
||||||
|
|
||||||
state->ip = ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
state->ip = ftrace_graph_ret_addr(state->task, &state->graph_idx,
|
||||||
state->ip, (void *)ip_p);
|
state->ip, (void *)ip_p);
|
||||||
@ -465,7 +472,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
if (!deref_stack_regs(state, sp, &state->ip, &state->sp)) {
|
if (!deref_stack_regs(state, sp, &state->ip, &state->sp)) {
|
||||||
orc_warn("can't dereference registers at %p for ip %pB\n",
|
orc_warn("can't dereference registers at %p for ip %pB\n",
|
||||||
(void *)sp, (void *)orig_ip);
|
(void *)sp, (void *)orig_ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->regs = (struct pt_regs *)sp;
|
state->regs = (struct pt_regs *)sp;
|
||||||
@ -477,7 +484,7 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
if (!deref_stack_iret_regs(state, sp, &state->ip, &state->sp)) {
|
if (!deref_stack_iret_regs(state, sp, &state->ip, &state->sp)) {
|
||||||
orc_warn("can't dereference iret registers at %p for ip %pB\n",
|
orc_warn("can't dereference iret registers at %p for ip %pB\n",
|
||||||
(void *)sp, (void *)orig_ip);
|
(void *)sp, (void *)orig_ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->regs = (void *)sp - IRET_FRAME_OFFSET;
|
state->regs = (void *)sp - IRET_FRAME_OFFSET;
|
||||||
@ -500,18 +507,18 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
|
|
||||||
case ORC_REG_PREV_SP:
|
case ORC_REG_PREV_SP:
|
||||||
if (!deref_stack_reg(state, sp + orc->bp_offset, &state->bp))
|
if (!deref_stack_reg(state, sp + orc->bp_offset, &state->bp))
|
||||||
goto done;
|
goto err;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ORC_REG_BP:
|
case ORC_REG_BP:
|
||||||
if (!deref_stack_reg(state, state->bp + orc->bp_offset, &state->bp))
|
if (!deref_stack_reg(state, state->bp + orc->bp_offset, &state->bp))
|
||||||
goto done;
|
goto err;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
orc_warn("unknown BP base reg %d for ip %pB\n",
|
orc_warn("unknown BP base reg %d for ip %pB\n",
|
||||||
orc->bp_reg, (void *)orig_ip);
|
orc->bp_reg, (void *)orig_ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent a recursive loop due to bad ORC data: */
|
/* Prevent a recursive loop due to bad ORC data: */
|
||||||
@ -520,13 +527,16 @@ bool unwind_next_frame(struct unwind_state *state)
|
|||||||
state->sp <= prev_sp) {
|
state->sp <= prev_sp) {
|
||||||
orc_warn("stack going in the wrong direction? ip=%pB\n",
|
orc_warn("stack going in the wrong direction? ip=%pB\n",
|
||||||
(void *)orig_ip);
|
(void *)orig_ip);
|
||||||
goto done;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
preempt_enable();
|
preempt_enable();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
done:
|
err:
|
||||||
|
state->error = true;
|
||||||
|
|
||||||
|
the_end:
|
||||||
preempt_enable();
|
preempt_enable();
|
||||||
state->stack_info.type = STACK_TYPE_UNKNOWN;
|
state->stack_info.type = STACK_TYPE_UNKNOWN;
|
||||||
return false;
|
return false;
|
||||||
|
@ -88,6 +88,7 @@ struct orc_entry {
|
|||||||
unsigned sp_reg:4;
|
unsigned sp_reg:4;
|
||||||
unsigned bp_reg:4;
|
unsigned bp_reg:4;
|
||||||
unsigned type:2;
|
unsigned type:2;
|
||||||
|
unsigned end:1;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -101,6 +102,7 @@ struct unwind_hint {
|
|||||||
s16 sp_offset;
|
s16 sp_offset;
|
||||||
u8 sp_reg;
|
u8 sp_reg;
|
||||||
u8 type;
|
u8 type;
|
||||||
|
u8 end;
|
||||||
};
|
};
|
||||||
#endif /* __ASSEMBLY__ */
|
#endif /* __ASSEMBLY__ */
|
||||||
|
|
||||||
|
@ -1156,6 +1156,7 @@ static int read_unwind_hints(struct objtool_file *file)
|
|||||||
|
|
||||||
cfa->offset = hint->sp_offset;
|
cfa->offset = hint->sp_offset;
|
||||||
insn->state.type = hint->type;
|
insn->state.type = hint->type;
|
||||||
|
insn->state.end = hint->end;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -31,7 +31,7 @@ struct insn_state {
|
|||||||
int stack_size;
|
int stack_size;
|
||||||
unsigned char type;
|
unsigned char type;
|
||||||
bool bp_scratch;
|
bool bp_scratch;
|
||||||
bool drap;
|
bool drap, end;
|
||||||
int drap_reg, drap_offset;
|
int drap_reg, drap_offset;
|
||||||
struct cfi_reg vals[CFI_NUM_REGS];
|
struct cfi_reg vals[CFI_NUM_REGS];
|
||||||
};
|
};
|
||||||
|
@ -203,7 +203,8 @@ int orc_dump(const char *_objname)
|
|||||||
|
|
||||||
print_reg(orc[i].bp_reg, orc[i].bp_offset);
|
print_reg(orc[i].bp_reg, orc[i].bp_offset);
|
||||||
|
|
||||||
printf(" type:%s\n", orc_type_name(orc[i].type));
|
printf(" type:%s end:%d\n",
|
||||||
|
orc_type_name(orc[i].type), orc[i].end);
|
||||||
}
|
}
|
||||||
|
|
||||||
elf_end(elf);
|
elf_end(elf);
|
||||||
|
@ -31,6 +31,8 @@ int create_orc(struct objtool_file *file)
|
|||||||
struct cfi_reg *cfa = &insn->state.cfa;
|
struct cfi_reg *cfa = &insn->state.cfa;
|
||||||
struct cfi_reg *bp = &insn->state.regs[CFI_BP];
|
struct cfi_reg *bp = &insn->state.regs[CFI_BP];
|
||||||
|
|
||||||
|
orc->end = insn->state.end;
|
||||||
|
|
||||||
if (cfa->base == CFI_UNDEFINED) {
|
if (cfa->base == CFI_UNDEFINED) {
|
||||||
orc->sp_reg = ORC_REG_UNDEFINED;
|
orc->sp_reg = ORC_REG_UNDEFINED;
|
||||||
continue;
|
continue;
|
||||||
|
Loading…
Reference in New Issue
Block a user