[MIPS] dump_stack() based on prologue code analysis
authorAtsushi Nemoto <anemo@mba.ocn.ne.jp>
Sat, 29 Jul 2006 14:27:20 +0000 (23:27 +0900)
committerRalf Baechle <ralf@linux-mips.org>
Wed, 27 Sep 2006 12:37:06 +0000 (13:37 +0100)
Instead of dump all possible address in the stack, unwind the stack frame
based on prologue code analysis, as like as get_wchan() does.  While the
code analysis might fail for some reason, there is a new kernel option
"raw_show_trace" to disable this feature.

Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/kernel/process.c
arch/mips/kernel/traps.c

index 7ab67f786bfe277327f55517364855f4fc728b2f..8709a46a45c143b1ef736e9daa2b50ad9ad9b2b0 100644 (file)
@@ -281,7 +281,7 @@ static struct mips_frame_info {
 } *schedule_frame, mfinfo[64];
 static int mfinfo_num;
 
-static int __init get_frame_info(struct mips_frame_info *info)
+static int get_frame_info(struct mips_frame_info *info)
 {
        int i;
        void *func = info->func;
@@ -329,14 +329,12 @@ static int __init get_frame_info(struct mips_frame_info *info)
                                ip->i_format.simmediate / sizeof(long);
                }
        }
-       if (info->pc_offset == -1 || info->frame_size == 0) {
-               if (func == schedule)
-                       printk("Can't analyze prologue code at %p\n", func);
-               info->pc_offset = -1;
-               info->frame_size = 0;
-       }
-
-       return 0;
+       if (info->frame_size && info->pc_offset >= 0) /* nested */
+               return 0;
+       if (info->pc_offset < 0) /* leaf */
+               return 1;
+       /* prologue seems boggus... */
+       return -1;
 }
 
 static int __init frame_info_init(void)
@@ -367,8 +365,15 @@ static int __init frame_info_init(void)
        mfinfo[0].func = schedule;
        schedule_frame = &mfinfo[0];
 #endif
-       for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++)
-               get_frame_info(&mfinfo[i]);
+       for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) {
+               struct mips_frame_info *info = &mfinfo[i];
+               if (get_frame_info(info)) {
+                       /* leaf or unknown */
+                       if (info->func == schedule)
+                               printk("Can't analyze prologue code at %p\n",
+                                      info->func);
+               }
+       }
 
        mfinfo_num = i;
        return 0;
@@ -427,6 +432,8 @@ unsigned long get_wchan(struct task_struct *p)
                if (i < 0)
                        break;
 
+               if (mfinfo[i].pc_offset < 0)
+                       break;
                pc = ((unsigned long *)frame)[mfinfo[i].pc_offset];
                if (!mfinfo[i].frame_size)
                        break;
@@ -437,3 +444,40 @@ unsigned long get_wchan(struct task_struct *p)
        return pc;
 }
 
+#ifdef CONFIG_KALLSYMS
+/* used by show_frametrace() */
+unsigned long unwind_stack(struct task_struct *task,
+                          unsigned long **sp, unsigned long pc)
+{
+       unsigned long stack_page;
+       struct mips_frame_info info;
+       char *modname;
+       char namebuf[KSYM_NAME_LEN + 1];
+       unsigned long size, ofs;
+
+       stack_page = (unsigned long)task_stack_page(task);
+       if (!stack_page)
+               return 0;
+
+       if (!kallsyms_lookup(pc, &size, &ofs, &modname, namebuf))
+               return 0;
+       if (ofs == 0)
+               return 0;
+
+       info.func = (void *)(pc - ofs);
+       info.func_size = ofs;   /* analyze from start to ofs */
+       if (get_frame_info(&info)) {
+               /* leaf or unknown */
+               *sp += info.frame_size / sizeof(long);
+               return 0;
+       }
+       if ((unsigned long)*sp < stack_page ||
+           (unsigned long)*sp + info.frame_size / sizeof(long) >
+           stack_page + THREAD_SIZE - 32)
+               return 0;
+
+       pc = (*sp)[info.pc_offset];
+       *sp += info.frame_size / sizeof(long);
+       return pc;
+}
+#endif
index c6f70467e7506e81bdc90c2f169a80877d3fbf35..7aa9dfc57b818110fd80fd82371c60f981d8f9c0 100644 (file)
@@ -98,24 +98,53 @@ static void show_trace(unsigned long *stack)
        printk("\n");
 }
 
+#ifdef CONFIG_KALLSYMS
+static int raw_show_trace;
+static int __init set_raw_show_trace(char *str)
+{
+       raw_show_trace = 1;
+       return 1;
+}
+__setup("raw_show_trace", set_raw_show_trace);
+
+extern unsigned long unwind_stack(struct task_struct *task,
+                                 unsigned long **sp, unsigned long pc);
+static void show_frametrace(struct task_struct *task, struct pt_regs *regs)
+{
+       const int field = 2 * sizeof(unsigned long);
+       unsigned long *stack = (long *)regs->regs[29];
+       unsigned long pc = regs->cp0_epc;
+       int top = 1;
+
+       if (raw_show_trace || !__kernel_text_address(pc)) {
+               show_trace(stack);
+               return;
+       }
+       printk("Call Trace:\n");
+       while (__kernel_text_address(pc)) {
+               printk(" [<%0*lx>] ", field, pc);
+               print_symbol("%s\n", pc);
+               pc = unwind_stack(task, &stack, pc);
+               if (top && pc == 0)
+                       pc = regs->regs[31];    /* leaf? */
+               top = 0;
+       }
+       printk("\n");
+}
+#else
+#define show_frametrace(task, r) show_trace((long *)(r)->regs[29]);
+#endif
+
 /*
  * This routine abuses get_user()/put_user() to reference pointers
  * with at least a bit of error checking ...
  */
-void show_stack(struct task_struct *task, unsigned long *sp)
+static void show_stacktrace(struct task_struct *task, struct pt_regs *regs)
 {
        const int field = 2 * sizeof(unsigned long);
        long stackdata;
        int i;
-       unsigned long *stack;
-
-       if (!sp) {
-               if (task && task != current)
-                       sp = (unsigned long *) task->thread.reg29;
-               else
-                       sp = (unsigned long *) &sp;
-       }
-       stack = sp;
+       unsigned long *sp = (unsigned long *)regs->regs[29];
 
        printk("Stack :");
        i = 0;
@@ -136,7 +165,44 @@ void show_stack(struct task_struct *task, unsigned long *sp)
                i++;
        }
        printk("\n");
-       show_trace(stack);
+       show_frametrace(task, regs);
+}
+
+static noinline void prepare_frametrace(struct pt_regs *regs)
+{
+       __asm__ __volatile__(
+               "1: la $2, 1b\n\t"
+#ifdef CONFIG_64BIT
+               "sd $2, %0\n\t"
+               "sd $29, %1\n\t"
+               "sd $31, %2\n\t"
+#else
+               "sw $2, %0\n\t"
+               "sw $29, %1\n\t"
+               "sw $31, %2\n\t"
+#endif
+               : "=m" (regs->cp0_epc),
+               "=m" (regs->regs[29]), "=m" (regs->regs[31])
+               : : "memory");
+}
+
+void show_stack(struct task_struct *task, unsigned long *sp)
+{
+       struct pt_regs regs;
+       if (sp) {
+               regs.regs[29] = (unsigned long)sp;
+               regs.regs[31] = 0;
+               regs.cp0_epc = 0;
+       } else {
+               if (task && task != current) {
+                       regs.regs[29] = task->thread.reg29;
+                       regs.regs[31] = 0;
+                       regs.cp0_epc = task->thread.reg31;
+               } else {
+                       prepare_frametrace(&regs);
+               }
+       }
+       show_stacktrace(task, &regs);
 }
 
 /*
@@ -146,6 +212,14 @@ void dump_stack(void)
 {
        unsigned long stack;
 
+#ifdef CONFIG_KALLSYMS
+       if (!raw_show_trace) {
+               struct pt_regs regs;
+               prepare_frametrace(&regs);
+               show_frametrace(current, &regs);
+               return;
+       }
+#endif
        show_trace(&stack);
 }
 
@@ -265,7 +339,7 @@ void show_registers(struct pt_regs *regs)
        print_modules();
        printk("Process %s (pid: %d, threadinfo=%p, task=%p)\n",
                current->comm, current->pid, current_thread_info(), current);
-       show_stack(current, (long *) regs->regs[29]);
+       show_stacktrace(current, regs);
        show_code((unsigned int *) regs->cp0_epc);
        printk("\n");
 }