1
2
3
4
5
6
7
8
9
10
11#include <linux/kernel.h>
12#include <linux/init.h>
13#include <linux/sched.h>
14#include <linux/slab.h>
15#include <linux/kallsyms.h>
16
17#include <asm/uaccess.h>
18#include <asm/assembly.h>
19#include <asm/asm-offsets.h>
20#include <asm/ptrace.h>
21
22#include <asm/unwind.h>
23
24
25#ifdef DEBUG
26#define dbg(x...) printk(x)
27#else
28#define dbg(x...)
29#endif
30
31#define KERNEL_START (KERNEL_BINARY_TEXT_START)
32
33extern struct unwind_table_entry __start___unwind[];
34extern struct unwind_table_entry __stop___unwind[];
35
36static spinlock_t unwind_lock;
37
38
39
40
41
42static struct unwind_table kernel_unwind_table __read_mostly;
43static LIST_HEAD(unwind_tables);
44
45static inline const struct unwind_table_entry *
46find_unwind_entry_in_table(const struct unwind_table *table, unsigned long addr)
47{
48 const struct unwind_table_entry *e = NULL;
49 unsigned long lo, hi, mid;
50
51 lo = 0;
52 hi = table->length - 1;
53
54 while (lo <= hi) {
55 mid = (hi - lo) / 2 + lo;
56 e = &table->table[mid];
57 if (addr < e->region_start)
58 hi = mid - 1;
59 else if (addr > e->region_end)
60 lo = mid + 1;
61 else
62 return e;
63 }
64
65 return NULL;
66}
67
68static const struct unwind_table_entry *
69find_unwind_entry(unsigned long addr)
70{
71 struct unwind_table *table;
72 const struct unwind_table_entry *e = NULL;
73
74 if (addr >= kernel_unwind_table.start &&
75 addr <= kernel_unwind_table.end)
76 e = find_unwind_entry_in_table(&kernel_unwind_table, addr);
77 else
78 list_for_each_entry(table, &unwind_tables, list) {
79 if (addr >= table->start &&
80 addr <= table->end)
81 e = find_unwind_entry_in_table(table, addr);
82 if (e)
83 break;
84 }
85
86 return e;
87}
88
89static void
90unwind_table_init(struct unwind_table *table, const char *name,
91 unsigned long base_addr, unsigned long gp,
92 void *table_start, void *table_end)
93{
94 struct unwind_table_entry *start = table_start;
95 struct unwind_table_entry *end =
96 (struct unwind_table_entry *)table_end - 1;
97
98 table->name = name;
99 table->base_addr = base_addr;
100 table->gp = gp;
101 table->start = base_addr + start->region_start;
102 table->end = base_addr + end->region_end;
103 table->table = (struct unwind_table_entry *)table_start;
104 table->length = end - start + 1;
105 INIT_LIST_HEAD(&table->list);
106
107 for (; start <= end; start++) {
108 if (start < end &&
109 start->region_end > (start+1)->region_start) {
110 printk("WARNING: Out of order unwind entry! %p and %p\n", start, start+1);
111 }
112
113 start->region_start += base_addr;
114 start->region_end += base_addr;
115 }
116}
117
118static void
119unwind_table_sort(struct unwind_table_entry *start,
120 struct unwind_table_entry *finish)
121{
122 struct unwind_table_entry el, *p, *q;
123
124 for (p = start + 1; p < finish; ++p) {
125 if (p[0].region_start < p[-1].region_start) {
126 el = *p;
127 q = p;
128 do {
129 q[0] = q[-1];
130 --q;
131 } while (q > start &&
132 el.region_start < q[-1].region_start);
133 *q = el;
134 }
135 }
136}
137
138struct unwind_table *
139unwind_table_add(const char *name, unsigned long base_addr,
140 unsigned long gp,
141 void *start, void *end)
142{
143 struct unwind_table *table;
144 unsigned long flags;
145 struct unwind_table_entry *s = (struct unwind_table_entry *)start;
146 struct unwind_table_entry *e = (struct unwind_table_entry *)end;
147
148 unwind_table_sort(s, e);
149
150 table = kmalloc(sizeof(struct unwind_table), GFP_USER);
151 if (table == NULL)
152 return NULL;
153 unwind_table_init(table, name, base_addr, gp, start, end);
154 spin_lock_irqsave(&unwind_lock, flags);
155 list_add_tail(&table->list, &unwind_tables);
156 spin_unlock_irqrestore(&unwind_lock, flags);
157
158 return table;
159}
160
161void unwind_table_remove(struct unwind_table *table)
162{
163 unsigned long flags;
164
165 spin_lock_irqsave(&unwind_lock, flags);
166 list_del(&table->list);
167 spin_unlock_irqrestore(&unwind_lock, flags);
168
169 kfree(table);
170}
171
172
173int unwind_init(void)
174{
175 long start, stop;
176 register unsigned long gp __asm__ ("r27");
177
178 start = (long)&__start___unwind[0];
179 stop = (long)&__stop___unwind[0];
180
181 spin_lock_init(&unwind_lock);
182
183 printk("unwind_init: start = 0x%lx, end = 0x%lx, entries = %lu\n",
184 start, stop,
185 (stop - start) / sizeof(struct unwind_table_entry));
186
187 unwind_table_init(&kernel_unwind_table, "kernel", KERNEL_START,
188 gp,
189 &__start___unwind[0], &__stop___unwind[0]);
190#if 0
191 {
192 int i;
193 for (i = 0; i < 10; i++)
194 {
195 printk("region 0x%x-0x%x\n",
196 __start___unwind[i].region_start,
197 __start___unwind[i].region_end);
198 }
199 }
200#endif
201 return 0;
202}
203
204#ifdef CONFIG_64BIT
205#define get_func_addr(fptr) fptr[2]
206#else
207#define get_func_addr(fptr) fptr[0]
208#endif
209
210static int unwind_special(struct unwind_frame_info *info, unsigned long pc, int frame_size)
211{
212 extern void handle_interruption(int, struct pt_regs *);
213 static unsigned long *hi = (unsigned long *)&handle_interruption;
214
215 if (pc == get_func_addr(hi)) {
216 struct pt_regs *regs = (struct pt_regs *)(info->sp - frame_size - PT_SZ_ALGN);
217 dbg("Unwinding through handle_interruption()\n");
218 info->prev_sp = regs->gr[30];
219 info->prev_ip = regs->iaoq[0];
220
221 return 1;
222 }
223
224 return 0;
225}
226
227static void unwind_frame_regs(struct unwind_frame_info *info)
228{
229 const struct unwind_table_entry *e;
230 unsigned long npc;
231 unsigned int insn;
232 long frame_size = 0;
233 int looking_for_rp, rpoffset = 0;
234
235 e = find_unwind_entry(info->ip);
236 if (e == NULL) {
237 unsigned long sp;
238 extern char _stext[], _etext[];
239
240 dbg("Cannot find unwind entry for 0x%lx; forced unwinding\n", info->ip);
241
242#ifdef CONFIG_KALLSYMS
243
244 {
245 char symname[KSYM_NAME_LEN];
246 char *modname;
247
248 kallsyms_lookup(info->ip, NULL, NULL, &modname,
249 symname);
250
251 dbg("info->ip = 0x%lx, name = %s\n", info->ip, symname);
252
253 if (strcmp(symname, "_switch_to_ret") == 0) {
254 info->prev_sp = info->sp - CALLEE_SAVE_FRAME_SIZE;
255 info->prev_ip = *(unsigned long *)(info->prev_sp - RP_OFFSET);
256 dbg("_switch_to_ret @ %lx - setting "
257 "prev_sp=%lx prev_ip=%lx\n",
258 info->ip, info->prev_sp,
259 info->prev_ip);
260 return;
261 } else if (strcmp(symname, "ret_from_kernel_thread") == 0 ||
262 strcmp(symname, "syscall_exit") == 0) {
263 info->prev_ip = info->prev_sp = 0;
264 return;
265 }
266 }
267#endif
268
269
270
271
272
273
274
275
276 sp = info->sp & ~63;
277 do {
278 unsigned long tmp;
279
280 info->prev_sp = sp - 64;
281 info->prev_ip = 0;
282 if (get_user(tmp, (unsigned long *)(info->prev_sp - RP_OFFSET)))
283 break;
284 info->prev_ip = tmp;
285 sp = info->prev_sp;
286 } while (info->prev_ip < (unsigned long)_stext ||
287 info->prev_ip > (unsigned long)_etext);
288
289 info->rp = 0;
290
291 dbg("analyzing func @ %lx with no unwind info, setting "
292 "prev_sp=%lx prev_ip=%lx\n", info->ip,
293 info->prev_sp, info->prev_ip);
294 } else {
295 dbg("e->start = 0x%x, e->end = 0x%x, Save_SP = %d, "
296 "Save_RP = %d, Millicode = %d size = %u\n",
297 e->region_start, e->region_end, e->Save_SP, e->Save_RP,
298 e->Millicode, e->Total_frame_size);
299
300 looking_for_rp = e->Save_RP;
301
302 for (npc = e->region_start;
303 (frame_size < (e->Total_frame_size << 3) ||
304 looking_for_rp) &&
305 npc < info->ip;
306 npc += 4) {
307
308 insn = *(unsigned int *)npc;
309
310 if ((insn & 0xffffc000) == 0x37de0000 ||
311 (insn & 0xffe00000) == 0x6fc00000) {
312
313 frame_size += (insn & 0x1 ? -1 << 13 : 0) |
314 ((insn & 0x3fff) >> 1);
315 dbg("analyzing func @ %lx, insn=%08x @ "
316 "%lx, frame_size = %ld\n", info->ip,
317 insn, npc, frame_size);
318 } else if ((insn & 0xffe00008) == 0x73c00008) {
319
320 frame_size += (insn & 0x1 ? -1 << 13 : 0) |
321 (((insn >> 4) & 0x3ff) << 3);
322 dbg("analyzing func @ %lx, insn=%08x @ "
323 "%lx, frame_size = %ld\n", info->ip,
324 insn, npc, frame_size);
325 } else if (insn == 0x6bc23fd9) {
326
327 rpoffset = 20;
328 looking_for_rp = 0;
329 dbg("analyzing func @ %lx, insn=stw rp,"
330 "-20(sp) @ %lx\n", info->ip, npc);
331 } else if (insn == 0x0fc212c1) {
332
333 rpoffset = 16;
334 looking_for_rp = 0;
335 dbg("analyzing func @ %lx, insn=std rp,"
336 "-16(sp) @ %lx\n", info->ip, npc);
337 }
338 }
339
340 if (!unwind_special(info, e->region_start, frame_size)) {
341 info->prev_sp = info->sp - frame_size;
342 if (e->Millicode)
343 info->rp = info->r31;
344 else if (rpoffset)
345 info->rp = *(unsigned long *)(info->prev_sp - rpoffset);
346 info->prev_ip = info->rp;
347 info->rp = 0;
348 }
349
350 dbg("analyzing func @ %lx, setting prev_sp=%lx "
351 "prev_ip=%lx npc=%lx\n", info->ip, info->prev_sp,
352 info->prev_ip, npc);
353 }
354}
355
356void unwind_frame_init(struct unwind_frame_info *info, struct task_struct *t,
357 struct pt_regs *regs)
358{
359 memset(info, 0, sizeof(struct unwind_frame_info));
360 info->t = t;
361 info->sp = regs->gr[30];
362 info->ip = regs->iaoq[0];
363 info->rp = regs->gr[2];
364 info->r31 = regs->gr[31];
365
366 dbg("(%d) Start unwind from sp=%08lx ip=%08lx\n",
367 t ? (int)t->pid : -1, info->sp, info->ip);
368}
369
370void unwind_frame_init_from_blocked_task(struct unwind_frame_info *info, struct task_struct *t)
371{
372 struct pt_regs *r = &t->thread.regs;
373 struct pt_regs *r2;
374
375 r2 = kmalloc(sizeof(struct pt_regs), GFP_ATOMIC);
376 if (!r2)
377 return;
378 *r2 = *r;
379 r2->gr[30] = r->ksp;
380 r2->iaoq[0] = r->kpc;
381 unwind_frame_init(info, t, r2);
382 kfree(r2);
383}
384
385void unwind_frame_init_running(struct unwind_frame_info *info, struct pt_regs *regs)
386{
387 unwind_frame_init(info, current, regs);
388}
389
390int unwind_once(struct unwind_frame_info *next_frame)
391{
392 unwind_frame_regs(next_frame);
393
394 if (next_frame->prev_sp == 0 ||
395 next_frame->prev_ip == 0)
396 return -1;
397
398 next_frame->sp = next_frame->prev_sp;
399 next_frame->ip = next_frame->prev_ip;
400 next_frame->prev_sp = 0;
401 next_frame->prev_ip = 0;
402
403 dbg("(%d) Continue unwind to sp=%08lx ip=%08lx\n",
404 next_frame->t ? (int)next_frame->t->pid : -1,
405 next_frame->sp, next_frame->ip);
406
407 return 0;
408}
409
410int unwind_to_user(struct unwind_frame_info *info)
411{
412 int ret;
413
414 do {
415 ret = unwind_once(info);
416 } while (!ret && !(info->ip & 3));
417
418 return ret;
419}
420