diff options
-rw-r--r-- | arch/x86/include/asm/unwind.h | 73 | ||||
-rw-r--r-- | arch/x86/kernel/Makefile | 6 | ||||
-rw-r--r-- | arch/x86/kernel/unwind_frame.c | 93 | ||||
-rw-r--r-- | arch/x86/kernel/unwind_guess.c | 43 |
4 files changed, 215 insertions, 0 deletions
diff --git a/arch/x86/include/asm/unwind.h b/arch/x86/include/asm/unwind.h new file mode 100644 index 000000000000..c4b6d1cafa46 --- /dev/null +++ b/arch/x86/include/asm/unwind.h | |||
@@ -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 */ | ||
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 0503f5bfb18d..45257cf84370 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile | |||
@@ -125,6 +125,12 @@ obj-$(CONFIG_EFI) += sysfb_efi.o | |||
125 | obj-$(CONFIG_PERF_EVENTS) += perf_regs.o | 125 | obj-$(CONFIG_PERF_EVENTS) += perf_regs.o |
126 | obj-$(CONFIG_TRACING) += tracepoint.o | 126 | obj-$(CONFIG_TRACING) += tracepoint.o |
127 | 127 | ||
128 | ifdef CONFIG_FRAME_POINTER | ||
129 | obj-y += unwind_frame.o | ||
130 | else | ||
131 | obj-y += unwind_guess.o | ||
132 | endif | ||
133 | |||
128 | ### | 134 | ### |
129 | # 64 bit specific files | 135 | # 64 bit specific files |
130 | ifeq ($(CONFIG_X86_64),y) | 136 | ifeq ($(CONFIG_X86_64),y) |
diff --git a/arch/x86/kernel/unwind_frame.c b/arch/x86/kernel/unwind_frame.c new file mode 100644 index 000000000000..a2456d4d286a --- /dev/null +++ b/arch/x86/kernel/unwind_frame.c | |||
@@ -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); | ||
diff --git a/arch/x86/kernel/unwind_guess.c b/arch/x86/kernel/unwind_guess.c new file mode 100644 index 000000000000..b5a834c93065 --- /dev/null +++ b/arch/x86/kernel/unwind_guess.c | |||
@@ -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); | ||