diff options
Diffstat (limited to 'arch/parisc/kernel/unwind.c')
| -rw-r--r-- | arch/parisc/kernel/unwind.c | 116 |
1 files changed, 82 insertions, 34 deletions
diff --git a/arch/parisc/kernel/unwind.c b/arch/parisc/kernel/unwind.c index db141108412..ddd988b267a 100644 --- a/arch/parisc/kernel/unwind.c +++ b/arch/parisc/kernel/unwind.c @@ -8,14 +8,17 @@ * understand what is happening here */ -#include <linux/config.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/kallsyms.h> +#include <linux/sort.h> #include <asm/uaccess.h> #include <asm/assembly.h> +#include <asm/asm-offsets.h> +#include <asm/ptrace.h> #include <asm/unwind.h> @@ -26,6 +29,8 @@ #define dbg(x...) #endif +#define KERNEL_START (KERNEL_BINARY_TEXT_START) + extern struct unwind_table_entry __start___unwind[]; extern struct unwind_table_entry __stop___unwind[]; @@ -35,7 +40,7 @@ static spinlock_t unwind_lock; * we can call unwind_init as early in the bootup process as * possible (before the slab allocator is initialized) */ -static struct unwind_table kernel_unwind_table; +static struct unwind_table kernel_unwind_table __read_mostly; static LIST_HEAD(unwind_tables); static inline const struct unwind_table_entry * @@ -75,8 +80,11 @@ find_unwind_entry(unsigned long addr) if (addr >= table->start && addr <= table->end) e = find_unwind_entry_in_table(table, addr); - if (e) + if (e) { + /* Move-to-front to exploit common traces */ + list_move(&table->list, &unwind_tables); break; + } } return e; @@ -111,24 +119,18 @@ unwind_table_init(struct unwind_table *table, const char *name, } } +static int cmp_unwind_table_entry(const void *a, const void *b) +{ + return ((const struct unwind_table_entry *)a)->region_start + - ((const struct unwind_table_entry *)b)->region_start; +} + static void unwind_table_sort(struct unwind_table_entry *start, struct unwind_table_entry *finish) { - struct unwind_table_entry el, *p, *q; - - for (p = start + 1; p < finish; ++p) { - if (p[0].region_start < p[-1].region_start) { - el = *p; - q = p; - do { - q[0] = q[-1]; - --q; - } while (q > start && - el.region_start < q[-1].region_start); - *q = el; - } - } + sort(start, finish - start, sizeof(struct unwind_table_entry), + cmp_unwind_table_entry, NULL); } struct unwind_table * @@ -166,7 +168,7 @@ void unwind_table_remove(struct unwind_table *table) } /* Called from setup_arch to import the kernel unwind info */ -static int unwind_init(void) +int __init unwind_init(void) { long start, stop; register unsigned long gp __asm__ ("r27"); @@ -197,6 +199,29 @@ static int unwind_init(void) return 0; } +#ifdef CONFIG_64BIT +#define get_func_addr(fptr) fptr[2] +#else +#define get_func_addr(fptr) fptr[0] +#endif + +static int unwind_special(struct unwind_frame_info *info, unsigned long pc, int frame_size) +{ + extern void handle_interruption(int, struct pt_regs *); + static unsigned long *hi = (unsigned long *)&handle_interruption; + + if (pc == get_func_addr(hi)) { + struct pt_regs *regs = (struct pt_regs *)(info->sp - frame_size - PT_SZ_ALGN); + dbg("Unwinding through handle_interruption()\n"); + info->prev_sp = regs->gr[30]; + info->prev_ip = regs->iaoq[0]; + + return 1; + } + + return 0; +} + static void unwind_frame_regs(struct unwind_frame_info *info) { const struct unwind_table_entry *e; @@ -208,19 +233,17 @@ static void unwind_frame_regs(struct unwind_frame_info *info) e = find_unwind_entry(info->ip); if (e == NULL) { unsigned long sp; - extern char _stext[], _etext[]; dbg("Cannot find unwind entry for 0x%lx; forced unwinding\n", info->ip); #ifdef CONFIG_KALLSYMS /* Handle some frequent special cases.... */ { - char symname[KSYM_NAME_LEN+1]; + char symname[KSYM_NAME_LEN]; char *modname; - unsigned long symsize, offset; - kallsyms_lookup(info->ip, &symsize, &offset, - &modname, symname); + kallsyms_lookup(info->ip, NULL, NULL, &modname, + symname); dbg("info->ip = 0x%lx, name = %s\n", info->ip, symname); @@ -257,8 +280,7 @@ static void unwind_frame_regs(struct unwind_frame_info *info) break; info->prev_ip = tmp; sp = info->prev_sp; - } while (info->prev_ip < (unsigned long)_stext || - info->prev_ip > (unsigned long)_etext); + } while (!kernel_text_address(info->prev_ip)); info->rp = 0; @@ -311,13 +333,15 @@ static void unwind_frame_regs(struct unwind_frame_info *info) } } - info->prev_sp = info->sp - frame_size; - if (e->Millicode) - info->rp = info->r31; - else if (rpoffset) - info->rp = *(unsigned long *)(info->prev_sp - rpoffset); - info->prev_ip = info->rp; - info->rp = 0; + if (!unwind_special(info, e->region_start, frame_size)) { + info->prev_sp = info->sp - frame_size; + if (e->Millicode) + info->rp = info->r31; + else if (rpoffset) + info->rp = *(unsigned long *)(info->prev_sp - rpoffset); + info->prev_ip = info->rp; + info->rp = 0; + } dbg("analyzing func @ %lx, setting prev_sp=%lx " "prev_ip=%lx npc=%lx\n", info->ip, info->prev_sp, @@ -344,7 +368,7 @@ void unwind_frame_init_from_blocked_task(struct unwind_frame_info *info, struct struct pt_regs *r = &t->thread.regs; struct pt_regs *r2; - r2 = (struct pt_regs *)kmalloc(sizeof(struct pt_regs), GFP_KERNEL); + r2 = kmalloc(sizeof(struct pt_regs), GFP_ATOMIC); if (!r2) return; *r2 = *r; @@ -390,4 +414,28 @@ int unwind_to_user(struct unwind_frame_info *info) return ret; } -module_init(unwind_init); +unsigned long return_address(unsigned int level) +{ + struct unwind_frame_info info; + struct pt_regs r; + unsigned long sp; + + /* initialize unwind info */ + asm volatile ("copy %%r30, %0" : "=r"(sp)); + memset(&r, 0, sizeof(struct pt_regs)); + r.iaoq[0] = (unsigned long) current_text_addr(); + r.gr[2] = (unsigned long) __builtin_return_address(0); + r.gr[30] = sp; + unwind_frame_init(&info, current, &r); + + /* unwind stack */ + ++level; + do { + if (unwind_once(&info) < 0 || info.ip == 0) + return 0; + if (!kernel_text_address(info.ip)) + return 0; + } while (info.ip && level--); + + return info.ip; +} |
