Skip to content

Commit 7c7900f

Browse files
jpoimboeIngo Molnar
authored and
Ingo Molnar
committed
x86/unwind: Add new unwind interface and implementations
The x86 stack dump code is a bit of a mess. dump_trace() uses callbacks, and each user of it seems to have slightly different requirements, so there are several slightly different callbacks floating around. Also there are some upcoming features which will need more changes to the stack dump code, including the printing of stack pt_regs, reliable stack detection for live patching, and a DWARF unwinder. Each of those features would at least need more callbacks and/or callback interfaces, resulting in a much bigger mess than what we have today. Before doing all that, we should try to clean things up and replace dump_trace() with something cleaner and more flexible. The new unwinder is a simple state machine which was heavily inspired by a suggestion from Andy Lutomirski: https://lkml.kernel.org/r/CALCETrUbNTqaM2LRyXGRx=kVLRPeY5A3Pc6k4TtQxF320rUT=w@mail.gmail.com It's also similar to the libunwind API: http://www.nongnu.org/libunwind/man/libunwind(3).html Some if its advantages: - Simplicity: no more callback sprawl and less code duplication. - Flexibility: it allows the caller to stop and inspect the stack state at each step in the unwinding process. - Modularity: the unwinder code, console stack dump code, and stack metadata analysis code are all better separated so that changing one of them shouldn't have much of an impact on any of the others. Two implementations are added which conform to the new unwind interface: - The frame pointer unwinder which is used for CONFIG_FRAME_POINTER=y. - The "guess" unwinder which is used for CONFIG_FRAME_POINTER=n. This isn't an "unwinder" per se. All it does is scan the stack for kernel text addresses. But with no frame pointers, guesses are better than nothing in most cases. Suggested-by: Andy Lutomirski <[email protected]> Signed-off-by: Josh Poimboeuf <[email protected]> Cc: Andy Lutomirski <[email protected]> Cc: Borislav Petkov <[email protected]> Cc: Brian Gerst <[email protected]> Cc: Byungchul Park <[email protected]> Cc: Denys Vlasenko <[email protected]> Cc: Frederic Weisbecker <[email protected]> Cc: H. Peter Anvin <[email protected]> Cc: Kees Cook <[email protected]> Cc: Linus Torvalds <[email protected]> Cc: Nilay Vaish <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Steven Rostedt <[email protected]> Cc: Thomas Gleixner <[email protected]> Link: http://lkml.kernel.org/r/6dc2f909c47533d213d0505f0a113e64585bec82.1474045023.git.jpoimboe@redhat.com Signed-off-by: Ingo Molnar <[email protected]>
1 parent b2c16e1 commit 7c7900f

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

arch/x86/include/asm/unwind.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef _ASM_X86_UNWIND_H
2+
#define _ASM_X86_UNWIND_H
3+
4+
#include <linux/sched.h>
5+
#include <linux/ftrace.h>
6+
#include <asm/ptrace.h>
7+
#include <asm/stacktrace.h>
8+
9+
struct unwind_state {
10+
struct stack_info stack_info;
11+
unsigned long stack_mask;
12+
struct task_struct *task;
13+
int graph_idx;
14+
#ifdef CONFIG_FRAME_POINTER
15+
unsigned long *bp;
16+
#else
17+
unsigned long *sp;
18+
#endif
19+
};
20+
21+
void __unwind_start(struct unwind_state *state, struct task_struct *task,
22+
struct pt_regs *regs, unsigned long *first_frame);
23+
24+
bool unwind_next_frame(struct unwind_state *state);
25+
26+
static inline bool unwind_done(struct unwind_state *state)
27+
{
28+
return state->stack_info.type == STACK_TYPE_UNKNOWN;
29+
}
30+
31+
static inline
32+
void unwind_start(struct unwind_state *state, struct task_struct *task,
33+
struct pt_regs *regs, unsigned long *first_frame)
34+
{
35+
first_frame = first_frame ? : get_stack_pointer(task, regs);
36+
37+
__unwind_start(state, task, regs, first_frame);
38+
}
39+
40+
#ifdef CONFIG_FRAME_POINTER
41+
42+
static inline
43+
unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
44+
{
45+
if (unwind_done(state))
46+
return NULL;
47+
48+
return state->bp + 1;
49+
}
50+
51+
unsigned long unwind_get_return_address(struct unwind_state *state);
52+
53+
#else /* !CONFIG_FRAME_POINTER */
54+
55+
static inline
56+
unsigned long *unwind_get_return_address_ptr(struct unwind_state *state)
57+
{
58+
return NULL;
59+
}
60+
61+
static inline
62+
unsigned long unwind_get_return_address(struct unwind_state *state)
63+
{
64+
if (unwind_done(state))
65+
return 0;
66+
67+
return ftrace_graph_ret_addr(state->task, &state->graph_idx,
68+
*state->sp, state->sp);
69+
}
70+
71+
#endif /* CONFIG_FRAME_POINTER */
72+
73+
#endif /* _ASM_X86_UNWIND_H */

arch/x86/kernel/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ obj-$(CONFIG_EFI) += sysfb_efi.o
125125
obj-$(CONFIG_PERF_EVENTS) += perf_regs.o
126126
obj-$(CONFIG_TRACING) += tracepoint.o
127127

128+
ifdef CONFIG_FRAME_POINTER
129+
obj-y += unwind_frame.o
130+
else
131+
obj-y += unwind_guess.o
132+
endif
133+
128134
###
129135
# 64 bit specific files
130136
ifeq ($(CONFIG_X86_64),y)

arch/x86/kernel/unwind_frame.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include <linux/sched.h>
2+
#include <asm/ptrace.h>
3+
#include <asm/bitops.h>
4+
#include <asm/stacktrace.h>
5+
#include <asm/unwind.h>
6+
7+
#define FRAME_HEADER_SIZE (sizeof(long) * 2)
8+
9+
unsigned long unwind_get_return_address(struct unwind_state *state)
10+
{
11+
unsigned long addr;
12+
unsigned long *addr_p = unwind_get_return_address_ptr(state);
13+
14+
if (unwind_done(state))
15+
return 0;
16+
17+
addr = ftrace_graph_ret_addr(state->task, &state->graph_idx, *addr_p,
18+
addr_p);
19+
20+
return __kernel_text_address(addr) ? addr : 0;
21+
}
22+
EXPORT_SYMBOL_GPL(unwind_get_return_address);
23+
24+
static bool update_stack_state(struct unwind_state *state, void *addr,
25+
size_t len)
26+
{
27+
struct stack_info *info = &state->stack_info;
28+
29+
/*
30+
* If addr isn't on the current stack, switch to the next one.
31+
*
32+
* We may have to traverse multiple stacks to deal with the possibility
33+
* that 'info->next_sp' could point to an empty stack and 'addr' could
34+
* be on a subsequent stack.
35+
*/
36+
while (!on_stack(info, addr, len))
37+
if (get_stack_info(info->next_sp, state->task, info,
38+
&state->stack_mask))
39+
return false;
40+
41+
return true;
42+
}
43+
44+
bool unwind_next_frame(struct unwind_state *state)
45+
{
46+
unsigned long *next_bp;
47+
48+
if (unwind_done(state))
49+
return false;
50+
51+
next_bp = (unsigned long *)*state->bp;
52+
53+
/* make sure the next frame's data is accessible */
54+
if (!update_stack_state(state, next_bp, FRAME_HEADER_SIZE))
55+
return false;
56+
57+
/* move to the next frame */
58+
state->bp = next_bp;
59+
return true;
60+
}
61+
EXPORT_SYMBOL_GPL(unwind_next_frame);
62+
63+
void __unwind_start(struct unwind_state *state, struct task_struct *task,
64+
struct pt_regs *regs, unsigned long *first_frame)
65+
{
66+
memset(state, 0, sizeof(*state));
67+
state->task = task;
68+
69+
/* don't even attempt to start from user mode regs */
70+
if (regs && user_mode(regs)) {
71+
state->stack_info.type = STACK_TYPE_UNKNOWN;
72+
return;
73+
}
74+
75+
/* set up the starting stack frame */
76+
state->bp = get_frame_pointer(task, regs);
77+
78+
/* initialize stack info and make sure the frame data is accessible */
79+
get_stack_info(state->bp, state->task, &state->stack_info,
80+
&state->stack_mask);
81+
update_stack_state(state, state->bp, FRAME_HEADER_SIZE);
82+
83+
/*
84+
* The caller can provide the address of the first frame directly
85+
* (first_frame) or indirectly (regs->sp) to indicate which stack frame
86+
* to start unwinding at. Skip ahead until we reach it.
87+
*/
88+
while (!unwind_done(state) &&
89+
(!on_stack(&state->stack_info, first_frame, sizeof(long)) ||
90+
state->bp < first_frame))
91+
unwind_next_frame(state);
92+
}
93+
EXPORT_SYMBOL_GPL(__unwind_start);

arch/x86/kernel/unwind_guess.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <linux/sched.h>
2+
#include <linux/ftrace.h>
3+
#include <asm/ptrace.h>
4+
#include <asm/bitops.h>
5+
#include <asm/stacktrace.h>
6+
#include <asm/unwind.h>
7+
8+
bool unwind_next_frame(struct unwind_state *state)
9+
{
10+
struct stack_info *info = &state->stack_info;
11+
12+
if (unwind_done(state))
13+
return false;
14+
15+
do {
16+
for (state->sp++; state->sp < info->end; state->sp++)
17+
if (__kernel_text_address(*state->sp))
18+
return true;
19+
20+
state->sp = info->next_sp;
21+
22+
} while (!get_stack_info(state->sp, state->task, info,
23+
&state->stack_mask));
24+
25+
return false;
26+
}
27+
EXPORT_SYMBOL_GPL(unwind_next_frame);
28+
29+
void __unwind_start(struct unwind_state *state, struct task_struct *task,
30+
struct pt_regs *regs, unsigned long *first_frame)
31+
{
32+
memset(state, 0, sizeof(*state));
33+
34+
state->task = task;
35+
state->sp = first_frame;
36+
37+
get_stack_info(first_frame, state->task, &state->stack_info,
38+
&state->stack_mask);
39+
40+
if (!__kernel_text_address(*first_frame))
41+
unwind_next_frame(state);
42+
}
43+
EXPORT_SYMBOL_GPL(__unwind_start);

0 commit comments

Comments
 (0)