mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-25 12:04:46 +08:00
7c6986ade6
In raise_backtrace_ipi() we iterate through the cpumask of CPUs, sending
each an IPI asking them to do a backtrace, but we don't wait for the
backtrace to happen.
We then iterate through the CPU mask again, and if any CPU hasn't done
the backtrace and cleared itself from the mask, we print a trace on its
behalf, noting that the trace may be "stale".
This works well enough when a CPU is not responding, because in that
case it doesn't receive the IPI and the sending CPU is left to print the
trace. But when all CPUs are responding we are left with a race between
the sending and receiving CPUs, if the sending CPU wins the race then it
will erroneously print a trace.
This leads to spurious "stale" traces from the sending CPU, which can
then be interleaved messily with the receiving CPU, note the CPU
numbers, eg:
[ 1658.929157][ C7] rcu: Stack dump where RCU GP kthread last ran:
[ 1658.929223][ C7] Sending NMI from CPU 7 to CPUs 1:
[ 1658.929303][ C1] NMI backtrace for cpu 1
[ 1658.929303][ C7] CPU 1 didn't respond to backtrace IPI, inspecting paca.
[ 1658.929362][ C1] CPU: 1 PID: 325 Comm: kworker/1:1H Tainted: G W E 5.13.0-rc2+ #46
[ 1658.929405][ C7] irq_soft_mask: 0x01 in_mce: 0 in_nmi: 0 current: 325 (kworker/1:1H)
[ 1658.929465][ C1] Workqueue: events_highpri test_work_fn [test_lockup]
[ 1658.929549][ C7] Back trace of paca->saved_r1 (0xc0000000057fb400) (possibly stale):
[ 1658.929592][ C1] NIP: c00000000002cf50 LR: c008000000820178 CTR: c00000000002cfa0
To fix it, change the logic so that the sending CPU waits 5s for the
receiving CPU to print its trace. If the receiving CPU prints its trace
successfully then the sending CPU just continues, avoiding any spurious
"stale" trace.
This has the added benefit of allowing all CPUs to print their traces in
order and avoids any interleaving of their output.
Fixes: 5cc05910f2
("powerpc/64s: Wire up arch_trigger_cpumask_backtrace()")
Cc: stable@vger.kernel.org # v4.18+
Reported-by: Nathan Lynch <nathanl@linux.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
Link: https://lore.kernel.org/r/20210625140408.3351173-1-mpe@ellerman.id.au
228 lines
5.7 KiB
C
228 lines
5.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
* Stack trace utility functions etc.
|
|
*
|
|
* Copyright 2008 Christoph Hellwig, IBM Corp.
|
|
* Copyright 2018 SUSE Linux GmbH
|
|
* Copyright 2018 Nick Piggin, Michael Ellerman, IBM Corp.
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/module.h>
|
|
#include <linux/nmi.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <asm/ptrace.h>
|
|
#include <asm/processor.h>
|
|
#include <linux/ftrace.h>
|
|
#include <asm/kprobes.h>
|
|
|
|
#include <asm/paca.h>
|
|
|
|
void __no_sanitize_address arch_stack_walk(stack_trace_consume_fn consume_entry, void *cookie,
|
|
struct task_struct *task, struct pt_regs *regs)
|
|
{
|
|
unsigned long sp;
|
|
|
|
if (regs && !consume_entry(cookie, regs->nip))
|
|
return;
|
|
|
|
if (regs)
|
|
sp = regs->gpr[1];
|
|
else if (task == current)
|
|
sp = current_stack_frame();
|
|
else
|
|
sp = task->thread.ksp;
|
|
|
|
for (;;) {
|
|
unsigned long *stack = (unsigned long *) sp;
|
|
unsigned long newsp, ip;
|
|
|
|
if (!validate_sp(sp, task, STACK_FRAME_OVERHEAD))
|
|
return;
|
|
|
|
newsp = stack[0];
|
|
ip = stack[STACK_FRAME_LR_SAVE];
|
|
|
|
if (!consume_entry(cookie, ip))
|
|
return;
|
|
|
|
sp = newsp;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function returns an error if it detects any unreliable features of the
|
|
* stack. Otherwise it guarantees that the stack trace is reliable.
|
|
*
|
|
* If the task is not 'current', the caller *must* ensure the task is inactive.
|
|
*/
|
|
int __no_sanitize_address arch_stack_walk_reliable(stack_trace_consume_fn consume_entry,
|
|
void *cookie, struct task_struct *task)
|
|
{
|
|
unsigned long sp;
|
|
unsigned long newsp;
|
|
unsigned long stack_page = (unsigned long)task_stack_page(task);
|
|
unsigned long stack_end;
|
|
int graph_idx = 0;
|
|
bool firstframe;
|
|
|
|
stack_end = stack_page + THREAD_SIZE;
|
|
if (!is_idle_task(task)) {
|
|
/*
|
|
* For user tasks, this is the SP value loaded on
|
|
* kernel entry, see "PACAKSAVE(r13)" in _switch() and
|
|
* system_call_common()/EXCEPTION_PROLOG_COMMON().
|
|
*
|
|
* Likewise for non-swapper kernel threads,
|
|
* this also happens to be the top of the stack
|
|
* as setup by copy_thread().
|
|
*
|
|
* Note that stack backlinks are not properly setup by
|
|
* copy_thread() and thus, a forked task() will have
|
|
* an unreliable stack trace until it's been
|
|
* _switch()'ed to for the first time.
|
|
*/
|
|
stack_end -= STACK_FRAME_OVERHEAD + sizeof(struct pt_regs);
|
|
} else {
|
|
/*
|
|
* idle tasks have a custom stack layout,
|
|
* c.f. cpu_idle_thread_init().
|
|
*/
|
|
stack_end -= STACK_FRAME_OVERHEAD;
|
|
}
|
|
|
|
if (task == current)
|
|
sp = current_stack_frame();
|
|
else
|
|
sp = task->thread.ksp;
|
|
|
|
if (sp < stack_page + sizeof(struct thread_struct) ||
|
|
sp > stack_end - STACK_FRAME_MIN_SIZE) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (firstframe = true; sp != stack_end;
|
|
firstframe = false, sp = newsp) {
|
|
unsigned long *stack = (unsigned long *) sp;
|
|
unsigned long ip;
|
|
|
|
/* sanity check: ABI requires SP to be aligned 16 bytes. */
|
|
if (sp & 0xF)
|
|
return -EINVAL;
|
|
|
|
newsp = stack[0];
|
|
/* Stack grows downwards; unwinder may only go up. */
|
|
if (newsp <= sp)
|
|
return -EINVAL;
|
|
|
|
if (newsp != stack_end &&
|
|
newsp > stack_end - STACK_FRAME_MIN_SIZE) {
|
|
return -EINVAL; /* invalid backlink, too far up. */
|
|
}
|
|
|
|
/*
|
|
* We can only trust the bottom frame's backlink, the
|
|
* rest of the frame may be uninitialized, continue to
|
|
* the next.
|
|
*/
|
|
if (firstframe)
|
|
continue;
|
|
|
|
/* Mark stacktraces with exception frames as unreliable. */
|
|
if (sp <= stack_end - STACK_INT_FRAME_SIZE &&
|
|
stack[STACK_FRAME_MARKER] == STACK_FRAME_REGS_MARKER) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Examine the saved LR: it must point into kernel code. */
|
|
ip = stack[STACK_FRAME_LR_SAVE];
|
|
if (!__kernel_text_address(ip))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* FIXME: IMHO these tests do not belong in
|
|
* arch-dependent code, they are generic.
|
|
*/
|
|
ip = ftrace_graph_ret_addr(task, &graph_idx, ip, stack);
|
|
#ifdef CONFIG_KPROBES
|
|
/*
|
|
* Mark stacktraces with kretprobed functions on them
|
|
* as unreliable.
|
|
*/
|
|
if (ip == (unsigned long)kretprobe_trampoline)
|
|
return -EINVAL;
|
|
#endif
|
|
|
|
if (!consume_entry(cookie, ip))
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_PPC_BOOK3S_64) && defined(CONFIG_NMI_IPI)
|
|
static void handle_backtrace_ipi(struct pt_regs *regs)
|
|
{
|
|
nmi_cpu_backtrace(regs);
|
|
}
|
|
|
|
static void raise_backtrace_ipi(cpumask_t *mask)
|
|
{
|
|
struct paca_struct *p;
|
|
unsigned int cpu;
|
|
u64 delay_us;
|
|
|
|
for_each_cpu(cpu, mask) {
|
|
if (cpu == smp_processor_id()) {
|
|
handle_backtrace_ipi(NULL);
|
|
continue;
|
|
}
|
|
|
|
delay_us = 5 * USEC_PER_SEC;
|
|
|
|
if (smp_send_safe_nmi_ipi(cpu, handle_backtrace_ipi, delay_us)) {
|
|
// Now wait up to 5s for the other CPU to do its backtrace
|
|
while (cpumask_test_cpu(cpu, mask) && delay_us) {
|
|
udelay(1);
|
|
delay_us--;
|
|
}
|
|
|
|
// Other CPU cleared itself from the mask
|
|
if (delay_us)
|
|
continue;
|
|
}
|
|
|
|
p = paca_ptrs[cpu];
|
|
|
|
cpumask_clear_cpu(cpu, mask);
|
|
|
|
pr_warn("CPU %d didn't respond to backtrace IPI, inspecting paca.\n", cpu);
|
|
if (!virt_addr_valid(p)) {
|
|
pr_warn("paca pointer appears corrupt? (%px)\n", p);
|
|
continue;
|
|
}
|
|
|
|
pr_warn("irq_soft_mask: 0x%02x in_mce: %d in_nmi: %d",
|
|
p->irq_soft_mask, p->in_mce, p->in_nmi);
|
|
|
|
if (virt_addr_valid(p->__current))
|
|
pr_cont(" current: %d (%s)\n", p->__current->pid,
|
|
p->__current->comm);
|
|
else
|
|
pr_cont(" current pointer corrupt? (%px)\n", p->__current);
|
|
|
|
pr_warn("Back trace of paca->saved_r1 (0x%016llx) (possibly stale):\n", p->saved_r1);
|
|
show_stack(p->__current, (unsigned long *)p->saved_r1, KERN_WARNING);
|
|
}
|
|
}
|
|
|
|
void arch_trigger_cpumask_backtrace(const cpumask_t *mask, bool exclude_self)
|
|
{
|
|
nmi_trigger_cpumask_backtrace(mask, exclude_self, raise_backtrace_ipi);
|
|
}
|
|
#endif /* defined(CONFIG_PPC_BOOK3S_64) && defined(CONFIG_NMI_IPI) */
|