diff options
author | James Hogan <james.hogan@imgtec.com> | 2012-10-05 12:01:38 -0400 |
---|---|---|
committer | James Hogan <james.hogan@imgtec.com> | 2013-03-02 15:09:52 -0500 |
commit | e8de3486a4b06389d4f996eb2dda678a39f20115 (patch) | |
tree | 2a72c3bf3394335fb638cb27ae187919387c94f1 | |
parent | 086e9dc0e2ca925b1b58caefd04ed2757d14790b (diff) |
metag: Stack unwinding
Add stack unwinding support for metag.
Signed-off-by: James Hogan <james.hogan@imgtec.com>
-rw-r--r-- | arch/metag/include/asm/stacktrace.h | 20 | ||||
-rw-r--r-- | arch/metag/kernel/stacktrace.c | 187 |
2 files changed, 207 insertions, 0 deletions
diff --git a/arch/metag/include/asm/stacktrace.h b/arch/metag/include/asm/stacktrace.h new file mode 100644 index 000000000000..2830a0fe7ac9 --- /dev/null +++ b/arch/metag/include/asm/stacktrace.h | |||
@@ -0,0 +1,20 @@ | |||
1 | #ifndef __ASM_STACKTRACE_H | ||
2 | #define __ASM_STACKTRACE_H | ||
3 | |||
4 | struct stackframe { | ||
5 | unsigned long fp; | ||
6 | unsigned long sp; | ||
7 | unsigned long lr; | ||
8 | unsigned long pc; | ||
9 | }; | ||
10 | |||
11 | struct metag_frame { | ||
12 | unsigned long fp; | ||
13 | unsigned long lr; | ||
14 | }; | ||
15 | |||
16 | extern int unwind_frame(struct stackframe *frame); | ||
17 | extern void walk_stackframe(struct stackframe *frame, | ||
18 | int (*fn)(struct stackframe *, void *), void *data); | ||
19 | |||
20 | #endif /* __ASM_STACKTRACE_H */ | ||
diff --git a/arch/metag/kernel/stacktrace.c b/arch/metag/kernel/stacktrace.c new file mode 100644 index 000000000000..5510361d5bea --- /dev/null +++ b/arch/metag/kernel/stacktrace.c | |||
@@ -0,0 +1,187 @@ | |||
1 | #include <linux/export.h> | ||
2 | #include <linux/sched.h> | ||
3 | #include <linux/stacktrace.h> | ||
4 | |||
5 | #include <asm/stacktrace.h> | ||
6 | |||
7 | #if defined(CONFIG_FRAME_POINTER) | ||
8 | |||
9 | #ifdef CONFIG_KALLSYMS | ||
10 | #include <linux/kallsyms.h> | ||
11 | #include <linux/module.h> | ||
12 | |||
13 | static unsigned long tbi_boing_addr; | ||
14 | static unsigned long tbi_boing_size; | ||
15 | |||
16 | static void tbi_boing_init(void) | ||
17 | { | ||
18 | /* We need to know where TBIBoingVec is and it's size */ | ||
19 | unsigned long size; | ||
20 | unsigned long offset; | ||
21 | char modname[MODULE_NAME_LEN]; | ||
22 | char name[KSYM_NAME_LEN]; | ||
23 | tbi_boing_addr = kallsyms_lookup_name("___TBIBoingVec"); | ||
24 | if (!tbi_boing_addr) | ||
25 | tbi_boing_addr = 1; | ||
26 | else if (!lookup_symbol_attrs(tbi_boing_addr, &size, | ||
27 | &offset, modname, name)) | ||
28 | tbi_boing_size = size; | ||
29 | } | ||
30 | #endif | ||
31 | |||
32 | #define ALIGN_DOWN(addr, size) ((addr)&(~((size)-1))) | ||
33 | |||
34 | /* | ||
35 | * Unwind the current stack frame and store the new register values in the | ||
36 | * structure passed as argument. Unwinding is equivalent to a function return, | ||
37 | * hence the new PC value rather than LR should be used for backtrace. | ||
38 | */ | ||
39 | int notrace unwind_frame(struct stackframe *frame) | ||
40 | { | ||
41 | struct metag_frame *fp = (struct metag_frame *)frame->fp; | ||
42 | unsigned long lr; | ||
43 | unsigned long fpnew; | ||
44 | |||
45 | if (frame->fp & 0x7) | ||
46 | return -EINVAL; | ||
47 | |||
48 | fpnew = fp->fp; | ||
49 | lr = fp->lr - 4; | ||
50 | |||
51 | #ifdef CONFIG_KALLSYMS | ||
52 | /* If we've reached TBIBoingVec then we're at an interrupt | ||
53 | * entry point or a syscall entry point. The frame pointer | ||
54 | * points to a pt_regs which can be used to continue tracing on | ||
55 | * the other side of the boing. | ||
56 | */ | ||
57 | if (!tbi_boing_addr) | ||
58 | tbi_boing_init(); | ||
59 | if (tbi_boing_size && lr >= tbi_boing_addr && | ||
60 | lr < tbi_boing_addr + tbi_boing_size) { | ||
61 | struct pt_regs *regs = (struct pt_regs *)fpnew; | ||
62 | if (user_mode(regs)) | ||
63 | return -EINVAL; | ||
64 | fpnew = regs->ctx.AX[1].U0; | ||
65 | lr = regs->ctx.DX[4].U1; | ||
66 | } | ||
67 | #endif | ||
68 | |||
69 | /* stack grows up, so frame pointers must decrease */ | ||
70 | if (fpnew < (ALIGN_DOWN((unsigned long)fp, THREAD_SIZE) + | ||
71 | sizeof(struct thread_info)) || fpnew >= (unsigned long)fp) | ||
72 | return -EINVAL; | ||
73 | |||
74 | /* restore the registers from the stack frame */ | ||
75 | frame->fp = fpnew; | ||
76 | frame->pc = lr; | ||
77 | |||
78 | return 0; | ||
79 | } | ||
80 | #else | ||
81 | int notrace unwind_frame(struct stackframe *frame) | ||
82 | { | ||
83 | struct metag_frame *sp = (struct metag_frame *)frame->sp; | ||
84 | |||
85 | if (frame->sp & 0x7) | ||
86 | return -EINVAL; | ||
87 | |||
88 | while (!kstack_end(sp)) { | ||
89 | unsigned long addr = sp->lr - 4; | ||
90 | sp--; | ||
91 | |||
92 | if (__kernel_text_address(addr)) { | ||
93 | frame->sp = (unsigned long)sp; | ||
94 | frame->pc = addr; | ||
95 | return 0; | ||
96 | } | ||
97 | } | ||
98 | return -EINVAL; | ||
99 | } | ||
100 | #endif | ||
101 | |||
102 | void notrace walk_stackframe(struct stackframe *frame, | ||
103 | int (*fn)(struct stackframe *, void *), void *data) | ||
104 | { | ||
105 | while (1) { | ||
106 | int ret; | ||
107 | |||
108 | if (fn(frame, data)) | ||
109 | break; | ||
110 | ret = unwind_frame(frame); | ||
111 | if (ret < 0) | ||
112 | break; | ||
113 | } | ||
114 | } | ||
115 | EXPORT_SYMBOL(walk_stackframe); | ||
116 | |||
117 | #ifdef CONFIG_STACKTRACE | ||
118 | struct stack_trace_data { | ||
119 | struct stack_trace *trace; | ||
120 | unsigned int no_sched_functions; | ||
121 | unsigned int skip; | ||
122 | }; | ||
123 | |||
124 | static int save_trace(struct stackframe *frame, void *d) | ||
125 | { | ||
126 | struct stack_trace_data *data = d; | ||
127 | struct stack_trace *trace = data->trace; | ||
128 | unsigned long addr = frame->pc; | ||
129 | |||
130 | if (data->no_sched_functions && in_sched_functions(addr)) | ||
131 | return 0; | ||
132 | if (data->skip) { | ||
133 | data->skip--; | ||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | trace->entries[trace->nr_entries++] = addr; | ||
138 | |||
139 | return trace->nr_entries >= trace->max_entries; | ||
140 | } | ||
141 | |||
142 | void save_stack_trace_tsk(struct task_struct *tsk, struct stack_trace *trace) | ||
143 | { | ||
144 | struct stack_trace_data data; | ||
145 | struct stackframe frame; | ||
146 | |||
147 | data.trace = trace; | ||
148 | data.skip = trace->skip; | ||
149 | |||
150 | if (tsk != current) { | ||
151 | #ifdef CONFIG_SMP | ||
152 | /* | ||
153 | * What guarantees do we have here that 'tsk' is not | ||
154 | * running on another CPU? For now, ignore it as we | ||
155 | * can't guarantee we won't explode. | ||
156 | */ | ||
157 | if (trace->nr_entries < trace->max_entries) | ||
158 | trace->entries[trace->nr_entries++] = ULONG_MAX; | ||
159 | return; | ||
160 | #else | ||
161 | data.no_sched_functions = 1; | ||
162 | frame.fp = thread_saved_fp(tsk); | ||
163 | frame.sp = thread_saved_sp(tsk); | ||
164 | frame.lr = 0; /* recovered from the stack */ | ||
165 | frame.pc = thread_saved_pc(tsk); | ||
166 | #endif | ||
167 | } else { | ||
168 | register unsigned long current_sp asm ("A0StP"); | ||
169 | |||
170 | data.no_sched_functions = 0; | ||
171 | frame.fp = (unsigned long)__builtin_frame_address(0); | ||
172 | frame.sp = current_sp; | ||
173 | frame.lr = (unsigned long)__builtin_return_address(0); | ||
174 | frame.pc = (unsigned long)save_stack_trace_tsk; | ||
175 | } | ||
176 | |||
177 | walk_stackframe(&frame, save_trace, &data); | ||
178 | if (trace->nr_entries < trace->max_entries) | ||
179 | trace->entries[trace->nr_entries++] = ULONG_MAX; | ||
180 | } | ||
181 | |||
182 | void save_stack_trace(struct stack_trace *trace) | ||
183 | { | ||
184 | save_stack_trace_tsk(current, trace); | ||
185 | } | ||
186 | EXPORT_SYMBOL_GPL(save_stack_trace); | ||
187 | #endif | ||