x86/unwind/orc: Add 'unwind_debug' cmdline option
authorJosh Poimboeuf <jpoimboe@redhat.com>
Wed, 12 Apr 2023 20:31:05 +0000 (13:31 -0700)
committerJosh Poimboeuf <jpoimboe@kernel.org>
Tue, 16 May 2023 13:31:50 +0000 (06:31 -0700)
Sometimes the one-line ORC unwinder warnings aren't very helpful.  Add a
new 'unwind_debug' cmdline option which will dump the full stack
contents of the current task when an error condition is encountered.

Reviewed-by: Miroslav Benes <mbenes@suse.cz>
Link: https://lore.kernel.org/r/6afb9e48a05fd2046bfad47e69b061b43dfd0e0e.1681331449.git.jpoimboe@kernel.org
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Documentation/admin-guide/kernel-parameters.txt
arch/x86/kernel/unwind_orc.c

index 9e5bab2..f922eea 100644 (file)
        unknown_nmi_panic
                        [X86] Cause panic on unknown NMI.
 
+       unwind_debug    [X86-64]
+                       Enable unwinder debug output.  This can be
+                       useful for debugging certain unwinder error
+                       conditions, including corrupt stacks and
+                       bad/missing unwinder metadata.
+
        usbcore.authorized_default=
                        [USB] Default USB device authorization:
                        (default -1 = authorized except for wireless USB,
index 5fbcb22..7891727 100644 (file)
 
 #define orc_warn_current(args...)                                      \
 ({                                                                     \
-       if (state->task == current && !state->error)                    \
+       static bool dumped_before;                                      \
+       if (state->task == current && !state->error) {                  \
                orc_warn(args);                                         \
+               if (unwind_debug && !dumped_before) {                   \
+                       dumped_before = true;                           \
+                       unwind_dump(state);                             \
+               }                                                       \
+       }                                                               \
 })
 
 extern int __start_orc_unwind_ip[];
@@ -23,8 +29,49 @@ extern struct orc_entry __start_orc_unwind[];
 extern struct orc_entry __stop_orc_unwind[];
 
 static bool orc_init __ro_after_init;
+static bool unwind_debug __ro_after_init;
 static unsigned int lookup_num_blocks __ro_after_init;
 
+static int __init unwind_debug_cmdline(char *str)
+{
+       unwind_debug = true;
+
+       return 0;
+}
+early_param("unwind_debug", unwind_debug_cmdline);
+
+static void unwind_dump(struct unwind_state *state)
+{
+       static bool dumped_before;
+       unsigned long word, *sp;
+       struct stack_info stack_info = {0};
+       unsigned long visit_mask = 0;
+
+       if (dumped_before)
+               return;
+
+       dumped_before = true;
+
+       printk_deferred("unwind stack type:%d next_sp:%p mask:0x%lx graph_idx:%d\n",
+                       state->stack_info.type, state->stack_info.next_sp,
+                       state->stack_mask, state->graph_idx);
+
+       for (sp = __builtin_frame_address(0); sp;
+            sp = PTR_ALIGN(stack_info.next_sp, sizeof(long))) {
+               if (get_stack_info(sp, state->task, &stack_info, &visit_mask))
+                       break;
+
+               for (; sp < stack_info.end; sp++) {
+
+                       word = READ_ONCE_NOCHECK(*sp);
+
+                       printk_deferred("%0*lx: %0*lx (%pB)\n", BITS_PER_LONG/4,
+                                       (unsigned long)sp, BITS_PER_LONG/4,
+                                       word, (void *)word);
+               }
+       }
+}
+
 static inline unsigned long orc_ip(const int *ip)
 {
        return (unsigned long)ip + *ip;