x86/traps: Print address on #GP
[linux-2.6-microblaze.git] / arch / x86 / kernel / traps.c
index 05da6b5..108ab1e 100644 (file)
@@ -56,6 +56,8 @@
 #include <asm/mpx.h>
 #include <asm/vm86.h>
 #include <asm/umip.h>
+#include <asm/insn.h>
+#include <asm/insn-eval.h>
 
 #ifdef CONFIG_X86_64
 #include <asm/x86_init.h>
@@ -518,10 +520,53 @@ exit_trap:
        do_trap(X86_TRAP_BR, SIGSEGV, "bounds", regs, error_code, 0, NULL);
 }
 
-dotraplinkage void
-do_general_protection(struct pt_regs *regs, long error_code)
+enum kernel_gp_hint {
+       GP_NO_HINT,
+       GP_NON_CANONICAL,
+       GP_CANONICAL
+};
+
+/*
+ * When an uncaught #GP occurs, try to determine the memory address accessed by
+ * the instruction and return that address to the caller. Also, try to figure
+ * out whether any part of the access to that address was non-canonical.
+ */
+static enum kernel_gp_hint get_kernel_gp_address(struct pt_regs *regs,
+                                                unsigned long *addr)
 {
-       const char *desc = "general protection fault";
+       u8 insn_buf[MAX_INSN_SIZE];
+       struct insn insn;
+
+       if (probe_kernel_read(insn_buf, (void *)regs->ip, MAX_INSN_SIZE))
+               return GP_NO_HINT;
+
+       kernel_insn_init(&insn, insn_buf, MAX_INSN_SIZE);
+       insn_get_modrm(&insn);
+       insn_get_sib(&insn);
+
+       *addr = (unsigned long)insn_get_addr_ref(&insn, regs);
+       if (*addr == -1UL)
+               return GP_NO_HINT;
+
+#ifdef CONFIG_X86_64
+       /*
+        * Check that:
+        *  - the operand is not in the kernel half
+        *  - the last byte of the operand is not in the user canonical half
+        */
+       if (*addr < ~__VIRTUAL_MASK &&
+           *addr + insn.opnd_bytes - 1 > __VIRTUAL_MASK)
+               return GP_NON_CANONICAL;
+#endif
+
+       return GP_CANONICAL;
+}
+
+#define GPFSTR "general protection fault"
+
+dotraplinkage void do_general_protection(struct pt_regs *regs, long error_code)
+{
+       char desc[sizeof(GPFSTR) + 50 + 2*sizeof(unsigned long) + 1] = GPFSTR;
        struct task_struct *tsk;
 
        RCU_LOCKDEP_WARN(!rcu_is_watching(), "entry code didn't wake RCU");
@@ -540,6 +585,9 @@ do_general_protection(struct pt_regs *regs, long error_code)
 
        tsk = current;
        if (!user_mode(regs)) {
+               enum kernel_gp_hint hint = GP_NO_HINT;
+               unsigned long gp_addr;
+
                if (fixup_exception(regs, X86_TRAP_GP, error_code, 0))
                        return;
 
@@ -556,8 +604,22 @@ do_general_protection(struct pt_regs *regs, long error_code)
                        return;
 
                if (notify_die(DIE_GPF, desc, regs, error_code,
-                              X86_TRAP_GP, SIGSEGV) != NOTIFY_STOP)
-                       die(desc, regs, error_code);
+                              X86_TRAP_GP, SIGSEGV) == NOTIFY_STOP)
+                       return;
+
+               if (error_code)
+                       snprintf(desc, sizeof(desc), "segment-related " GPFSTR);
+               else
+                       hint = get_kernel_gp_address(regs, &gp_addr);
+
+               if (hint != GP_NO_HINT)
+                       snprintf(desc, sizeof(desc), GPFSTR ", %s 0x%lx",
+                                (hint == GP_NON_CANONICAL) ?
+                                "probably for non-canonical address" :
+                                "maybe for address",
+                                gp_addr);
+
+               die(desc, regs, error_code);
                return;
        }