When sys_info() was factored out of kernel/panic.c by
commit b76e89e50fc3 ("panic: generalize panic_print's function to show
sys info"), __sys_info() gained an unconditional call to
trigger_all_cpu_backtrace() when SYS_INFO_ALL_BT is set, while
panic_other_cpus_shutdown() retained its own call to
panic_trigger_all_cpu_backtrace().
This causes two problems during a kernel panic:
1. vpanic() invokes panic_trigger_all_cpu_backtrace() inside
panic_other_cpus_shutdown() before stopping secondary CPUs, and
then invokes trigger_all_cpu_backtrace() a second time inside
sys_info(panic_print) after secondary CPUs have already been
shut down, stalling for up to 10 seconds waiting for offline
CPUs to respond to NMIs. Furthermore, if SYS_INFO_ALL_BT is
configured via the kernel.kernel_sys_info sysctl rather than
panic_print, panic_other_cpus_shutdown() misses it prior to CPU
shutdown.
2. If sys_info(SYS_INFO_ALL_BT) is invoked on the panicking CPU
prior to panic_other_cpus_shutdown(), calling
trigger_all_cpu_backtrace() directly bypasses
panic_trigger_all_cpu_backtrace() and leaves
panic_triggering_all_cpu_backtrace unset. Because panic_cpu is
already active, vprintk_emit() silently drops all NMI backtrace
messages originating from non-panic CPUs.
Export panic_trigger_all_cpu_backtrace() with a panic_on_this_cpu() and
one-shot guard so all-CPU backtraces are only emitted once before
secondary CPUs are stopped, consult sys_info_get_mask(panic_print) in
panic_other_cpus_shutdown(), record prior Oops stack dumps in
panic_try_start(), and route SYS_INFO_ALL_BT in __sys_info()
through panic_trigger_all_cpu_backtrace() on the panicking CPU.
Fixes: b76e89e50fc3 ("panic: generalize panic_print's function to show sys
info")
Signed-off-by: Aaron Tomlin <[email protected]>
---
include/linux/panic.h | 1 +
include/linux/sys_info.h | 1 +
kernel/panic.c | 31 +++++++++++++++++++++++++++----
lib/sys_info.c | 17 ++++++++++++++---
4 files changed, 43 insertions(+), 7 deletions(-)
diff --git a/include/linux/panic.h b/include/linux/panic.h
index 17e61b61c45f..3b56f873f1e2 100644
--- a/include/linux/panic.h
+++ b/include/linux/panic.h
@@ -57,6 +57,7 @@ void panic_reset(void);
bool panic_in_progress(void);
bool panic_on_this_cpu(void);
bool panic_on_other_cpu(void);
+void panic_trigger_all_cpu_backtrace(void);
/*
* Only to be used by arch init code. If the user over-wrote the default
diff --git a/include/linux/sys_info.h b/include/linux/sys_info.h
index a5bc3ea3d44b..e7699b82aa49 100644
--- a/include/linux/sys_info.h
+++ b/include/linux/sys_info.h
@@ -18,6 +18,7 @@
#define SYS_INFO_BLOCKED_TASKS 0x00000080
void sys_info(unsigned long si_mask);
+unsigned long sys_info_get_mask(unsigned long si_mask);
unsigned long sys_info_parse_param(char *str);
#ifdef CONFIG_SYSCTL
diff --git a/kernel/panic.c b/kernel/panic.c
index 574ee5a12f92..4cb26914b2af 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -70,6 +70,7 @@ static bool panic_console_replay;
bool panic_triggering_all_cpu_backtrace;
static bool panic_this_cpu_backtrace_printed;
+static bool panic_all_cpu_backtrace_printed;
int panic_timeout = CONFIG_PANIC_TIMEOUT;
EXPORT_SYMBOL_GPL(panic_timeout);
@@ -451,13 +452,21 @@ bool panic_try_start(void)
old_cpu = PANIC_CPU_INVALID;
this_cpu = raw_smp_processor_id();
- return atomic_try_cmpxchg(&panic_cpu, &old_cpu, this_cpu);
+ if (!atomic_try_cmpxchg(&panic_cpu, &old_cpu, this_cpu))
+ return false;
+
+ if (atomic_read(&panic_redirect_cpu) == PANIC_CPU_INVALID &&
+ (test_taint(TAINT_DIE) || oops_in_progress))
+ panic_this_cpu_backtrace_printed = true;
+
+ return true;
}
EXPORT_SYMBOL(panic_try_start);
void panic_reset(void)
{
- atomic_set(&panic_cpu, PANIC_CPU_INVALID);
+ panic_this_cpu_backtrace_printed = false;
+ atomic_set_release(&panic_cpu, PANIC_CPU_INVALID);
}
EXPORT_SYMBOL(panic_reset);
@@ -538,8 +547,21 @@ void check_panic_on_warn(const char *origin)
origin, limit);
}
-static void panic_trigger_all_cpu_backtrace(void)
+/**
+ * panic_trigger_all_cpu_backtrace - trigger NMI backtraces on all CPUs once
+ *
+ * Temporarily permit non-panic CPUs to emit messages into the printk ring
+ * buffer while capturing all-CPU backtraces during a panic. Subsequent calls
+ * are ignored so backtraces are not re-triggered after secondary CPUs have
+ * been shut down.
+ */
+void panic_trigger_all_cpu_backtrace(void)
{
+ if (!panic_on_this_cpu() || panic_all_cpu_backtrace_printed)
+ return;
+
+ panic_all_cpu_backtrace_printed = true;
+
/* Temporary allow non-panic CPUs to write their backtraces. */
panic_triggering_all_cpu_backtrace = true;
@@ -558,8 +580,9 @@ static void panic_trigger_all_cpu_backtrace(void)
*/
static void panic_other_cpus_shutdown(bool crash_kexec)
{
- if (panic_print & SYS_INFO_ALL_BT)
+ if (sys_info_get_mask(panic_print) & SYS_INFO_ALL_BT)
panic_trigger_all_cpu_backtrace();
+ panic_all_cpu_backtrace_printed = true;
/*
* Note that smp_send_stop() is the usual SMP shutdown function,
diff --git a/lib/sys_info.c b/lib/sys_info.c
index f32a06ec9ed4..496893eb776b 100644
--- a/lib/sys_info.c
+++ b/lib/sys_info.c
@@ -7,6 +7,7 @@
#include <linux/kernel.h>
#include <linux/ftrace.h>
#include <linux/nmi.h>
+#include <linux/panic.h>
#include <linux/sched/debug.h>
#include <linux/string.h>
#include <linux/sysctl.h>
@@ -153,14 +154,24 @@ static void __sys_info(unsigned long si_mask)
if (si_mask & SYS_INFO_FTRACE)
ftrace_dump(DUMP_ALL);
- if (si_mask & SYS_INFO_ALL_BT)
- trigger_all_cpu_backtrace();
+ if (si_mask & SYS_INFO_ALL_BT) {
+ if (panic_on_this_cpu())
+ panic_trigger_all_cpu_backtrace();
+ else if (!panic_in_progress())
+ trigger_all_cpu_backtrace();
+ }
if (si_mask & SYS_INFO_BLOCKED_TASKS)
show_state_filter(TASK_UNINTERRUPTIBLE);
}
+unsigned long sys_info_get_mask(unsigned long si_mask)
+{
+ return (si_mask & ~SYS_INFO_PANIC_CONSOLE_REPLAY) ? :
+ READ_ONCE(kernel_si_mask);
+}
+
void sys_info(unsigned long si_mask)
{
- __sys_info(si_mask ? : kernel_si_mask);
+ __sys_info(sys_info_get_mask(si_mask));
}
--
2.55.0