mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 12:21:22 -05:00
In case of a kernel crash caused by a protection exception, print the unmodified PSW address as reported by the CPU. The protection exception handler modifies the PSW address in order to keep fault handling easy, however that leads to misleading call traces. Therefore restore the original PSW address before printing it. Before this change the output in case of a protection exception looks like this: Oops: 0004 ilc:2 [#1]SMP Krnl PSW : 0704c00180000000 000003ffe0b40d78 (sysrq_handle_crash+0x28/0x40) R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:3 CC:0 PM:0 RI:0 EA:3 ... Krnl Code: 000003ffe0b40d66: e3e0f0980024 stg %r14,152(%r15) 000003ffe0b40d6c: c010fffffff2 larl %r1,000003ffe0b40d50 #000003ffe0b40d72: c0200046b6bc larl %r2,000003ffe1417aea >000003ffe0b40d78: 92021000 mvi 0(%r1),2 000003ffe0b40d7c: c0e5ffae03d6 brasl %r14,000003ffe0101528 With this change it looks like this: Oops: 0004 ilc:2 [#1]SMP Krnl PSW : 0704c00180000000 000003ffe0b40dfc (sysrq_handle_crash+0x2c/0x40) R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:3 CC:0 PM:0 RI:0 EA:3 ... Krnl Code: 000003ffe0b40dec: c010fffffff2 larl %r1,000003ffe0b40dd0 000003ffe0b40df2: c0200046b67c larl %r2,000003ffe1417aea *000003ffe0b40df8: 92021000 mvi 0(%r1),2 >000003ffe0b40dfc: c0e5ffae03b6 brasl %r14,000003ffe0101568 000003ffe0b40e02: 0707 bcr 0,%r7 Note that with this change the PSW address points to the instruction behind the instruction which caused the exception like it is expected for protection exceptions. This also replaces the '#' marker in the disassembly with '*', which allows to distinguish between new and old behavior. Reviewed-by: Alexander Gordeev <agordeev@linux.ibm.com> Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
225 lines
6.0 KiB
C
225 lines
6.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Stack dumping functions
|
|
*
|
|
* Copyright IBM Corp. 1999, 2013
|
|
*/
|
|
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/hardirq.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/utsname.h>
|
|
#include <linux/export.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/debug.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <asm/asm-offsets.h>
|
|
#include <asm/processor.h>
|
|
#include <asm/debug.h>
|
|
#include <asm/dis.h>
|
|
#include <asm/ipl.h>
|
|
#include <asm/unwind.h>
|
|
|
|
const char *stack_type_name(enum stack_type type)
|
|
{
|
|
switch (type) {
|
|
case STACK_TYPE_TASK:
|
|
return "task";
|
|
case STACK_TYPE_IRQ:
|
|
return "irq";
|
|
case STACK_TYPE_NODAT:
|
|
return "nodat";
|
|
case STACK_TYPE_RESTART:
|
|
return "restart";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(stack_type_name);
|
|
|
|
static inline bool in_stack(unsigned long sp, struct stack_info *info,
|
|
enum stack_type type, unsigned long stack)
|
|
{
|
|
if (sp < stack || sp >= stack + THREAD_SIZE)
|
|
return false;
|
|
info->type = type;
|
|
info->begin = stack;
|
|
info->end = stack + THREAD_SIZE;
|
|
return true;
|
|
}
|
|
|
|
static bool in_task_stack(unsigned long sp, struct task_struct *task,
|
|
struct stack_info *info)
|
|
{
|
|
unsigned long stack = (unsigned long)task_stack_page(task);
|
|
|
|
return in_stack(sp, info, STACK_TYPE_TASK, stack);
|
|
}
|
|
|
|
static bool in_irq_stack(unsigned long sp, struct stack_info *info)
|
|
{
|
|
unsigned long stack = get_lowcore()->async_stack - STACK_INIT_OFFSET;
|
|
|
|
return in_stack(sp, info, STACK_TYPE_IRQ, stack);
|
|
}
|
|
|
|
static bool in_nodat_stack(unsigned long sp, struct stack_info *info)
|
|
{
|
|
unsigned long stack = get_lowcore()->nodat_stack - STACK_INIT_OFFSET;
|
|
|
|
return in_stack(sp, info, STACK_TYPE_NODAT, stack);
|
|
}
|
|
|
|
static bool in_mcck_stack(unsigned long sp, struct stack_info *info)
|
|
{
|
|
unsigned long stack = get_lowcore()->mcck_stack - STACK_INIT_OFFSET;
|
|
|
|
return in_stack(sp, info, STACK_TYPE_MCCK, stack);
|
|
}
|
|
|
|
static bool in_restart_stack(unsigned long sp, struct stack_info *info)
|
|
{
|
|
unsigned long stack = get_lowcore()->restart_stack - STACK_INIT_OFFSET;
|
|
|
|
return in_stack(sp, info, STACK_TYPE_RESTART, stack);
|
|
}
|
|
|
|
int get_stack_info(unsigned long sp, struct task_struct *task,
|
|
struct stack_info *info, unsigned long *visit_mask)
|
|
{
|
|
if (!sp)
|
|
goto unknown;
|
|
|
|
/* Sanity check: ABI requires SP to be aligned 8 bytes. */
|
|
if (sp & 0x7)
|
|
goto unknown;
|
|
|
|
/* Check per-task stack */
|
|
if (in_task_stack(sp, task, info))
|
|
goto recursion_check;
|
|
|
|
if (task != current)
|
|
goto unknown;
|
|
|
|
/* Check per-cpu stacks */
|
|
if (!in_irq_stack(sp, info) &&
|
|
!in_nodat_stack(sp, info) &&
|
|
!in_restart_stack(sp, info) &&
|
|
!in_mcck_stack(sp, info))
|
|
goto unknown;
|
|
|
|
recursion_check:
|
|
/*
|
|
* Make sure we don't iterate through any given stack more than once.
|
|
* If it comes up a second time then there's something wrong going on:
|
|
* just break out and report an unknown stack type.
|
|
*/
|
|
if (*visit_mask & (1UL << info->type))
|
|
goto unknown;
|
|
*visit_mask |= 1UL << info->type;
|
|
return 0;
|
|
unknown:
|
|
info->type = STACK_TYPE_UNKNOWN;
|
|
return -EINVAL;
|
|
}
|
|
|
|
void show_stack(struct task_struct *task, unsigned long *stack,
|
|
const char *loglvl)
|
|
{
|
|
struct unwind_state state;
|
|
|
|
printk("%sCall Trace:\n", loglvl);
|
|
unwind_for_each_frame(&state, task, NULL, (unsigned long) stack)
|
|
printk(state.reliable ? "%s [<%016lx>] %pSR \n" :
|
|
"%s([<%016lx>] %pSR)\n",
|
|
loglvl, state.ip, (void *) state.ip);
|
|
debug_show_held_locks(task ? : current);
|
|
}
|
|
|
|
static void show_last_breaking_event(struct pt_regs *regs)
|
|
{
|
|
printk("Last Breaking-Event-Address:\n");
|
|
printk(" [<%016lx>] ", regs->last_break);
|
|
if (user_mode(regs)) {
|
|
print_vma_addr(KERN_CONT, regs->last_break);
|
|
pr_cont("\n");
|
|
} else {
|
|
pr_cont("%pSR\n", (void *)regs->last_break);
|
|
}
|
|
}
|
|
|
|
void show_registers(struct pt_regs *regs)
|
|
{
|
|
struct psw_bits *psw = &psw_bits(regs->psw);
|
|
unsigned long pswaddr;
|
|
char *mode;
|
|
|
|
pswaddr = regs->psw.addr;
|
|
if (test_pt_regs_flag(regs, PIF_PSW_ADDR_ADJUSTED))
|
|
pswaddr = __forward_psw(regs->psw, regs->int_code >> 16);
|
|
mode = user_mode(regs) ? "User" : "Krnl";
|
|
printk("%s PSW : %px %px", mode, (void *)regs->psw.mask, (void *)pswaddr);
|
|
if (!user_mode(regs))
|
|
pr_cont(" (%pSR)", (void *)pswaddr);
|
|
pr_cont("\n");
|
|
printk(" R:%x T:%x IO:%x EX:%x Key:%x M:%x W:%x "
|
|
"P:%x AS:%x CC:%x PM:%x", psw->per, psw->dat, psw->io, psw->ext,
|
|
psw->key, psw->mcheck, psw->wait, psw->pstate, psw->as, psw->cc, psw->pm);
|
|
pr_cont(" RI:%x EA:%x\n", psw->ri, psw->eaba);
|
|
printk("%s GPRS: %016lx %016lx %016lx %016lx\n", mode,
|
|
regs->gprs[0], regs->gprs[1], regs->gprs[2], regs->gprs[3]);
|
|
printk(" %016lx %016lx %016lx %016lx\n",
|
|
regs->gprs[4], regs->gprs[5], regs->gprs[6], regs->gprs[7]);
|
|
printk(" %016lx %016lx %016lx %016lx\n",
|
|
regs->gprs[8], regs->gprs[9], regs->gprs[10], regs->gprs[11]);
|
|
printk(" %016lx %016lx %016lx %016lx\n",
|
|
regs->gprs[12], regs->gprs[13], regs->gprs[14], regs->gprs[15]);
|
|
show_code(regs);
|
|
}
|
|
|
|
void show_regs(struct pt_regs *regs)
|
|
{
|
|
show_regs_print_info(KERN_DEFAULT);
|
|
show_registers(regs);
|
|
/* Show stack backtrace if pt_regs is from kernel mode */
|
|
if (!user_mode(regs))
|
|
show_stack(NULL, (unsigned long *) regs->gprs[15], KERN_DEFAULT);
|
|
show_last_breaking_event(regs);
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(die_lock);
|
|
|
|
void __noreturn die(struct pt_regs *regs, const char *str)
|
|
{
|
|
static int die_counter;
|
|
|
|
oops_enter();
|
|
lgr_info_log();
|
|
debug_stop_all();
|
|
console_verbose();
|
|
spin_lock_irq(&die_lock);
|
|
bust_spinlocks(1);
|
|
printk("%s: %04x ilc:%d [#%d]", str, regs->int_code & 0xffff,
|
|
regs->int_code >> 17, ++die_counter);
|
|
pr_cont("SMP ");
|
|
if (debug_pagealloc_enabled())
|
|
pr_cont("DEBUG_PAGEALLOC");
|
|
pr_cont("\n");
|
|
notify_die(DIE_OOPS, str, regs, 0, regs->int_code & 0xffff, SIGSEGV);
|
|
print_modules();
|
|
show_regs(regs);
|
|
bust_spinlocks(0);
|
|
add_taint(TAINT_DIE, LOCKDEP_NOW_UNRELIABLE);
|
|
spin_unlock_irq(&die_lock);
|
|
if (in_interrupt())
|
|
panic("Fatal exception in interrupt");
|
|
if (panic_on_oops)
|
|
panic("Fatal exception: panic_on_oops");
|
|
oops_exit();
|
|
make_task_dead(SIGSEGV);
|
|
}
|