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