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