mirror of
https://github.com/torvalds/linux.git
synced 2024-12-14 07:02:23 +00:00
d0208639db
Before merging all different stack tracers the call traces printed had
an indicator if an entry can be considered reliable or not.
Unreliable entries were put in braces, reliable not. Currently all
lines contain these extra braces.
This patch restores the old behaviour by adding an extra "reliable"
parameter to the callback functions. Only show_trace makes currently
use of it.
Before:
[ 0.804751] Call Trace:
[ 0.804753] ([<000000000017d0e0>] try_to_wake_up+0x318/0x5e0)
[ 0.804756] ([<0000000000161d64>] create_worker+0x174/0x1c0)
After:
[ 0.804751] Call Trace:
[ 0.804753] ([<000000000017d0e0>] try_to_wake_up+0x318/0x5e0)
[ 0.804756] [<0000000000161d64>] create_worker+0x174/0x1c0
Fixes: 758d39ebd3
("s390/dumpstack: merge all four stack tracers")
Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
234 lines
6.1 KiB
C
234 lines
6.1 KiB
C
/*
|
|
* 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 <asm/processor.h>
|
|
#include <asm/debug.h>
|
|
#include <asm/dis.h>
|
|
#include <asm/ipl.h>
|
|
|
|
/*
|
|
* For dump_trace we have tree different stack to consider:
|
|
* - the panic stack which is used if the kernel stack has overflown
|
|
* - the asynchronous interrupt stack (cpu related)
|
|
* - the synchronous kernel stack (process related)
|
|
* The stack trace can start at any of the three stacks and can potentially
|
|
* touch all of them. The order is: panic stack, async stack, sync stack.
|
|
*/
|
|
static unsigned long
|
|
__dump_trace(dump_trace_func_t func, void *data, unsigned long sp,
|
|
unsigned long low, unsigned long high)
|
|
{
|
|
struct stack_frame *sf;
|
|
struct pt_regs *regs;
|
|
|
|
while (1) {
|
|
if (sp < low || sp > high - sizeof(*sf))
|
|
return sp;
|
|
sf = (struct stack_frame *) sp;
|
|
if (func(data, sf->gprs[8], 0))
|
|
return sp;
|
|
/* Follow the backchain. */
|
|
while (1) {
|
|
low = sp;
|
|
sp = sf->back_chain;
|
|
if (!sp)
|
|
break;
|
|
if (sp <= low || sp > high - sizeof(*sf))
|
|
return sp;
|
|
sf = (struct stack_frame *) sp;
|
|
if (func(data, sf->gprs[8], 1))
|
|
return sp;
|
|
}
|
|
/* Zero backchain detected, check for interrupt frame. */
|
|
sp = (unsigned long) (sf + 1);
|
|
if (sp <= low || sp > high - sizeof(*regs))
|
|
return sp;
|
|
regs = (struct pt_regs *) sp;
|
|
if (!user_mode(regs)) {
|
|
if (func(data, regs->psw.addr, 1))
|
|
return sp;
|
|
}
|
|
low = sp;
|
|
sp = regs->gprs[15];
|
|
}
|
|
}
|
|
|
|
void dump_trace(dump_trace_func_t func, void *data, struct task_struct *task,
|
|
unsigned long sp)
|
|
{
|
|
unsigned long frame_size;
|
|
|
|
frame_size = STACK_FRAME_OVERHEAD + sizeof(struct pt_regs);
|
|
#ifdef CONFIG_CHECK_STACK
|
|
sp = __dump_trace(func, data, sp,
|
|
S390_lowcore.panic_stack + frame_size - 4096,
|
|
S390_lowcore.panic_stack + frame_size);
|
|
#endif
|
|
sp = __dump_trace(func, data, sp,
|
|
S390_lowcore.async_stack + frame_size - ASYNC_SIZE,
|
|
S390_lowcore.async_stack + frame_size);
|
|
task = task ?: current;
|
|
__dump_trace(func, data, sp,
|
|
(unsigned long)task_stack_page(task),
|
|
(unsigned long)task_stack_page(task) + THREAD_SIZE);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dump_trace);
|
|
|
|
struct return_address_data {
|
|
unsigned long address;
|
|
int depth;
|
|
};
|
|
|
|
static int __return_address(void *data, unsigned long address, int reliable)
|
|
{
|
|
struct return_address_data *rd = data;
|
|
|
|
if (rd->depth--)
|
|
return 0;
|
|
rd->address = address;
|
|
return 1;
|
|
}
|
|
|
|
unsigned long return_address(int depth)
|
|
{
|
|
struct return_address_data rd = { .depth = depth + 2 };
|
|
|
|
dump_trace(__return_address, &rd, NULL, current_stack_pointer());
|
|
return rd.address;
|
|
}
|
|
EXPORT_SYMBOL_GPL(return_address);
|
|
|
|
static int show_address(void *data, unsigned long address, int reliable)
|
|
{
|
|
if (reliable)
|
|
printk(" [<%016lx>] %pSR \n", address, (void *)address);
|
|
else
|
|
printk("([<%016lx>] %pSR)\n", address, (void *)address);
|
|
return 0;
|
|
}
|
|
|
|
static void show_trace(struct task_struct *task, unsigned long sp)
|
|
{
|
|
if (!sp)
|
|
sp = task ? task->thread.ksp : current_stack_pointer();
|
|
printk("Call Trace:\n");
|
|
dump_trace(show_address, NULL, task, sp);
|
|
if (!task)
|
|
task = current;
|
|
debug_show_held_locks(task);
|
|
}
|
|
|
|
void show_stack(struct task_struct *task, unsigned long *sp)
|
|
{
|
|
unsigned long *stack;
|
|
int i;
|
|
|
|
stack = sp;
|
|
if (!stack) {
|
|
if (!task)
|
|
stack = (unsigned long *)current_stack_pointer();
|
|
else
|
|
stack = (unsigned long *)task->thread.ksp;
|
|
}
|
|
for (i = 0; i < 20; i++) {
|
|
if (((addr_t) stack & (THREAD_SIZE-1)) == 0)
|
|
break;
|
|
if ((i * sizeof(long) % 32) == 0)
|
|
printk("%s ", i == 0 ? "" : "\n");
|
|
printk("%016lx ", *stack++);
|
|
}
|
|
printk("\n");
|
|
show_trace(task, (unsigned long)sp);
|
|
}
|
|
|
|
static void show_last_breaking_event(struct pt_regs *regs)
|
|
{
|
|
printk("Last Breaking-Event-Address:\n");
|
|
printk(" [<%016lx>] %pSR\n", regs->args[0], (void *)regs->args[0]);
|
|
}
|
|
|
|
void show_registers(struct pt_regs *regs)
|
|
{
|
|
struct psw_bits *psw = &psw_bits(regs->psw);
|
|
char *mode;
|
|
|
|
mode = user_mode(regs) ? "User" : "Krnl";
|
|
printk("%s PSW : %p %p", mode, (void *)regs->psw.mask, (void *)regs->psw.addr);
|
|
if (!user_mode(regs))
|
|
printk(" (%pSR)", (void *)regs->psw.addr);
|
|
printk("\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->r, psw->t, psw->i, psw->e,
|
|
psw->key, psw->m, psw->w, psw->p, psw->as, psw->cc, psw->pm);
|
|
printk(" RI:%x EA:%x", psw->ri, psw->eaba);
|
|
printk("\n%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_trace(NULL, regs->gprs[15]);
|
|
show_last_breaking_event(regs);
|
|
}
|
|
|
|
static DEFINE_SPINLOCK(die_lock);
|
|
|
|
void 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);
|
|
#ifdef CONFIG_PREEMPT
|
|
printk("PREEMPT ");
|
|
#endif
|
|
#ifdef CONFIG_SMP
|
|
printk("SMP ");
|
|
#endif
|
|
if (debug_pagealloc_enabled())
|
|
printk("DEBUG_PAGEALLOC");
|
|
printk("\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();
|
|
do_exit(SIGSEGV);
|
|
}
|