Arbitrary kernel context backtracing
authorBarret Rhoden <brho@cs.berkeley.edu>
Sun, 4 Aug 2013 18:57:48 +0000 (11:57 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Sun, 4 Aug 2013 19:21:27 +0000 (12:21 -0700)
We can backtrace any context compiled with frame pointers, given the
program counter and frame pointer.

The existing monitor command 'backtrace' will still backtrace the
calling context, when given no arguments.  Additionally, it will
backtrace any frame, given the PC and FP.

Kernel page faults on x86 will backtrace the faulting context.  Riscv
still needs an implementation of backtrace_frame().

backtrace_frame() will sort of work for userspace too, though the kernel
won't know the names of functions, and you need to compile all of
userspace with -fno-omit-frame-pointer (which we don't do by default).

kern/arch/riscv/kdebug.c
kern/arch/x86/kdebug.c
kern/arch/x86/trap.c
kern/arch/x86/trap.h
kern/arch/x86/trap32.c
kern/arch/x86/trap32.h
kern/arch/x86/trap64.c
kern/arch/x86/trap64.h
kern/include/kdebug.h
kern/src/monitor.c

index b517e18..de1453a 100644 (file)
@@ -24,3 +24,8 @@ void backtrace(void)
                fp = (void**)sp;
        }
 }
+
+void backtrace_frame(uintptr_t pc, uintptr_t fp)
+{
+       printk("\n\tTODO: backtrace frame on riscv\n\n");
+}
index ddb5883..fb73151 100644 (file)
@@ -307,24 +307,16 @@ void *debug_get_fn_addr(char *fn_name)
        return retval;
 }
 
-void backtrace(void)
+void backtrace_frame(uintptr_t eip, uintptr_t ebp)
 { 
        extern char (SNT RO _start)[];
-       unsigned long *ebp, eip;
        eipdebuginfo_t debuginfo;
        char buf[256];
        char *func_name;
        int j, i = 1;
-       ebp = (unsigned long*)read_bp();
-       // this is part of the way back into the call() instruction's bytes
-       // eagle-eyed readers should be able to explain why this is good enough,
-       // and retaddr (just *(ebp + 1) is not)
-       eip = *(ebp + 1) - 1;
-       // jump back a frame (out of backtrace)
-       ebp = (unsigned long*)(*ebp);
-       printk("Stack Backtrace on Core %d:\n", core_id());
+
        // on each iteration, ebp holds the stack frame and eip an addr in that func
-       while (1) {
+       while (ebp) {
                #ifdef CONFIG_X86_64
                func_name = get_fn_name(eip);
                printk("#%02d [<%p>] in %s\n", i++,  eip, func_name);
@@ -343,20 +335,34 @@ void backtrace(void)
                        debuginfo.eip_fn_addr, debuginfo.eip_file, debuginfo.eip_line);
                cprintf("    ebp: %x   Args:", ebp);
                for (j = 0; j < MIN(debuginfo.eip_fn_narg, 5); j++)
-                       cprintf(" %08x", *(ebp + 2 + j));
+                       cprintf(" %08x", *(uintptr_t*)(ebp + 2 + j));
                cprintf("\n");
                # ifdef CONFIG_RESET_STACKS
                if (!strncmp("__smp_idle", (char*)debuginfo.eip_fn_name, 10))
                        break;
                # endif /* CONFIG_RESET_STACKS */
                #endif /* CONFIG_X86_64 */
-               if (!ebp)
-                       break;
-               eip = *(ebp + 1) - 1;
-               ebp = (unsigned long*)(*ebp);
+               eip = *(uintptr_t*)(ebp + sizeof(uintptr_t)) - 1;
+               ebp = *(uintptr_t*)ebp;
        }
 }
 
+void backtrace(void)
+{
+       uintptr_t ebp, eip;
+       ebp = read_bp();
+       /* retaddr is right above ebp on the stack.  we subtract an additional 1 to
+        * make sure the eip we get is actually in the function that called us.
+        * i had a couple cases early on where call was the last instruction in a
+        * function, and simply reading the retaddr would point into another
+        * function (the next one in the object) */
+       eip = *(uintptr_t*)(ebp + sizeof(uintptr_t)) - 1;
+       /* jump back a frame (out of backtrace) */
+       ebp = *(uintptr_t*)ebp;
+       printk("Stack Backtrace on Core %d:\n", core_id());
+       backtrace_frame(eip, ebp);
+}
+
 /* Assumes 32-bit header */
 void print_fpu_state(struct ancillary_state *fpu)
 {
index 8c8ae2e..63d4b11 100644 (file)
@@ -209,6 +209,15 @@ static void handle_fperr(struct hw_trapframe *hw_tf)
        proc_destroy(current);
 }
 
+void backtrace_kframe(struct hw_trapframe *hw_tf)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       pcpui->__lock_depth_disabled++;
+       printk("\nBacktrace of faulting kernel context on Core %d:\n", core_id());
+       backtrace_frame(x86_get_hwtf_pc(hw_tf), x86_get_hwtf_fp(hw_tf));
+       pcpui->__lock_depth_disabled--;
+}
+
 /* Certain traps want IRQs enabled, such as the syscall.  Others can't handle
  * it, like the page fault handler.  Turn them on on a case-by-case basis. */
 static void trap_dispatch(struct hw_trapframe *hw_tf)
index 6c1f7be..76f8992 100644 (file)
@@ -94,6 +94,7 @@ extern pseudodesc_t idt_pd;
 extern taskstate_t ts;
 extern const char *x86_trapname(int trapno);
 extern void sysenter_handler(void);
+void backtrace_kframe(struct hw_trapframe *hw_tf);
 
 /* Defined and set up in in arch/init.c, used for XMM initialization */
 extern struct ancillary_state x86_default_fpu;
index c77ea00..3b5f358 100644 (file)
@@ -95,6 +95,7 @@ void page_fault_handler(struct hw_trapframe *hw_tf)
        /* TODO - handle kernel page faults */
        if ((hw_tf->tf_cs & 3) == 0) {
                print_trapframe(hw_tf);
+               backtrace_kframe(hw_tf);
                panic("Page Fault in the Kernel at 0x%08x!", fault_va);
                /* if we want to do something like kill a process or other code, be
                 * aware we are in a sort of irq-like context, meaning the main kernel
index e994cb9..e56ed11 100644 (file)
@@ -107,4 +107,14 @@ static inline void x86_set_stacktop_tss(struct taskstate *tss, uintptr_t top)
        tss->ts_ss0 = GD_KD;
 }
 
+static inline uintptr_t x86_get_hwtf_pc(struct hw_trapframe *hw_tf)
+{
+       return hw_tf->tf_eip;
+}
+
+static inline uintptr_t x86_get_hwtf_fp(struct hw_trapframe *hw_tf)
+{
+       return hw_tf->tf_regs.reg_ebp;
+}
+
 #endif /* ROS_KERN_ARCH_TRAP32_H */
index 0b032ed..9b4f927 100644 (file)
@@ -124,6 +124,7 @@ void page_fault_handler(struct hw_trapframe *hw_tf)
        /* TODO - handle kernel page faults */
        if ((hw_tf->tf_cs & 3) == 0) {
                print_trapframe(hw_tf);
+               backtrace_kframe(hw_tf);
                panic("Page Fault in the Kernel at 0x%08x!", fault_va);
                /* if we want to do something like kill a process or other code, be
                 * aware we are in a sort of irq-like context, meaning the main kernel
index a581ce5..9034768 100644 (file)
@@ -115,4 +115,14 @@ static inline void x86_set_stacktop_tss(struct taskstate *tss, uintptr_t top)
        tss->ts_rsp0 = top;
 }
 
+static inline uintptr_t x86_get_hwtf_pc(struct hw_trapframe *hw_tf)
+{
+       return hw_tf->tf_rip;
+}
+
+static inline uintptr_t x86_get_hwtf_fp(struct hw_trapframe *hw_tf)
+{
+       return hw_tf->tf_rbp;
+}
+
 #endif /* ROS_KERN_ARCH_TRAP64_H */
index 058a42a..26c0c71 100644 (file)
@@ -10,6 +10,7 @@ struct symtab_entry {
 };
 
 void backtrace(void);
+void backtrace_frame(uintptr_t pc, uintptr_t fp);
 
 /* Arch dependent, listed here for ease-of-use */
 static inline uintptr_t get_caller_pc(void);
index fe3bb50..2169b65 100644 (file)
@@ -127,7 +127,19 @@ static char RO* function_of(uint32_t address)
 
 int mon_backtrace(int argc, char **argv, struct hw_trapframe *hw_tf)
 {
-       backtrace();
+       uintptr_t pc, fp;
+       if (argc == 1) {
+               backtrace();
+               return 0;
+       }
+       if (argc != 3) {
+               printk("Need either no arguments, or two (PC and FP) in hex\n");
+               return 1;
+       }
+       pc = strtol(argv[1], 0, 16);
+       fp = strtol(argv[2], 0, 16);
+       printk("Backtrace from instruction %p, with frame pointer %p\n", pc, fp);
+       backtrace_frame(pc, fp);
        return 0;
 }