-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
s390/dumpstack: fix call chain walking
dumpstack() did not always print a sane callchain when being called. The reason is that show_trace() accessed register 15 directly to get the current stack pointer and passed that pointer to __show_trace() which expects a valid stack frame pointer as argument. However due to tail call optimization the stack frame may not exist anymore when __show_trace() gets called and therefore an invalid stack frame pointer gets passed. To prevent that disable tail call optimization for call chain walking functions. So move all the show_* functions to a dumpstack.c file like other architectures have it already and add a -fno-optimize-sibling-calls compile flag to both dumpstack.c and stacktrace.c to prevent tail call optimization. Fixes callchains that looked e.g. like this: [ 12.868258] Call Trace: [ 12.868262] ([<0000000000008000>] 0x8000) Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
- Loading branch information
Heiko Carstens
authored and
Martin Schwidefsky
committed
Apr 17, 2013
1 parent
2b55732
commit 1bca09f
Showing
3 changed files
with
253 additions
and
250 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
/* | ||
* 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/module.h> | ||
#include <linux/sched.h> | ||
#include <asm/processor.h> | ||
#include <asm/debug.h> | ||
#include <asm/ipl.h> | ||
|
||
#ifndef CONFIG_64BIT | ||
#define LONG "%08lx " | ||
#define FOURLONG "%08lx %08lx %08lx %08lx\n" | ||
static int kstack_depth_to_print = 12; | ||
#else /* CONFIG_64BIT */ | ||
#define LONG "%016lx " | ||
#define FOURLONG "%016lx %016lx %016lx %016lx\n" | ||
static int kstack_depth_to_print = 20; | ||
#endif /* CONFIG_64BIT */ | ||
|
||
/* | ||
* For show_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 stack and can potentially | ||
* touch all of them. The order is: panic stack, async stack, sync stack. | ||
*/ | ||
static unsigned long | ||
__show_trace(unsigned long sp, unsigned long low, unsigned long high) | ||
{ | ||
struct stack_frame *sf; | ||
struct pt_regs *regs; | ||
|
||
while (1) { | ||
sp = sp & PSW_ADDR_INSN; | ||
if (sp < low || sp > high - sizeof(*sf)) | ||
return sp; | ||
sf = (struct stack_frame *) sp; | ||
printk("([<%016lx>] ", sf->gprs[8] & PSW_ADDR_INSN); | ||
print_symbol("%s)\n", sf->gprs[8] & PSW_ADDR_INSN); | ||
/* Follow the backchain. */ | ||
while (1) { | ||
low = sp; | ||
sp = sf->back_chain & PSW_ADDR_INSN; | ||
if (!sp) | ||
break; | ||
if (sp <= low || sp > high - sizeof(*sf)) | ||
return sp; | ||
sf = (struct stack_frame *) sp; | ||
printk(" [<%016lx>] ", sf->gprs[8] & PSW_ADDR_INSN); | ||
print_symbol("%s\n", sf->gprs[8] & PSW_ADDR_INSN); | ||
} | ||
/* 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; | ||
printk(" [<%016lx>] ", regs->psw.addr & PSW_ADDR_INSN); | ||
print_symbol("%s\n", regs->psw.addr & PSW_ADDR_INSN); | ||
low = sp; | ||
sp = regs->gprs[15]; | ||
} | ||
} | ||
|
||
static void show_trace(struct task_struct *task, unsigned long *stack) | ||
{ | ||
register unsigned long __r15 asm ("15"); | ||
unsigned long sp; | ||
|
||
sp = (unsigned long) stack; | ||
if (!sp) | ||
sp = task ? task->thread.ksp : __r15; | ||
printk("Call Trace:\n"); | ||
#ifdef CONFIG_CHECK_STACK | ||
sp = __show_trace(sp, S390_lowcore.panic_stack - 4096, | ||
S390_lowcore.panic_stack); | ||
#endif | ||
sp = __show_trace(sp, S390_lowcore.async_stack - ASYNC_SIZE, | ||
S390_lowcore.async_stack); | ||
if (task) | ||
__show_trace(sp, (unsigned long) task_stack_page(task), | ||
(unsigned long) task_stack_page(task) + THREAD_SIZE); | ||
else | ||
__show_trace(sp, S390_lowcore.thread_info, | ||
S390_lowcore.thread_info + THREAD_SIZE); | ||
if (!task) | ||
task = current; | ||
debug_show_held_locks(task); | ||
} | ||
|
||
void show_stack(struct task_struct *task, unsigned long *sp) | ||
{ | ||
register unsigned long *__r15 asm ("15"); | ||
unsigned long *stack; | ||
int i; | ||
|
||
if (!sp) | ||
stack = task ? (unsigned long *) task->thread.ksp : __r15; | ||
else | ||
stack = sp; | ||
|
||
for (i = 0; i < kstack_depth_to_print; i++) { | ||
if (((addr_t) stack & (THREAD_SIZE-1)) == 0) | ||
break; | ||
if ((i * sizeof(long) % 32) == 0) | ||
printk("%s ", i == 0 ? "" : "\n"); | ||
printk(LONG, *stack++); | ||
} | ||
printk("\n"); | ||
show_trace(task, sp); | ||
} | ||
|
||
static void show_last_breaking_event(struct pt_regs *regs) | ||
{ | ||
#ifdef CONFIG_64BIT | ||
printk("Last Breaking-Event-Address:\n"); | ||
printk(" [<%016lx>] ", regs->args[0] & PSW_ADDR_INSN); | ||
print_symbol("%s\n", regs->args[0] & PSW_ADDR_INSN); | ||
#endif | ||
} | ||
|
||
/* | ||
* The architecture-independent dump_stack generator | ||
*/ | ||
void dump_stack(void) | ||
{ | ||
printk("CPU: %d %s %s %.*s\n", | ||
task_thread_info(current)->cpu, print_tainted(), | ||
init_utsname()->release, | ||
(int)strcspn(init_utsname()->version, " "), | ||
init_utsname()->version); | ||
printk("Process %s (pid: %d, task: %p, ksp: %p)\n", | ||
current->comm, current->pid, current, | ||
(void *) current->thread.ksp); | ||
show_stack(NULL, NULL); | ||
} | ||
EXPORT_SYMBOL(dump_stack); | ||
|
||
static inline int mask_bits(struct pt_regs *regs, unsigned long bits) | ||
{ | ||
return (regs->psw.mask & bits) / ((~bits + 1) & bits); | ||
} | ||
|
||
void show_registers(struct pt_regs *regs) | ||
{ | ||
char *mode; | ||
|
||
mode = user_mode(regs) ? "User" : "Krnl"; | ||
printk("%s PSW : %p %p", | ||
mode, (void *) regs->psw.mask, | ||
(void *) regs->psw.addr); | ||
print_symbol(" (%s)\n", regs->psw.addr & PSW_ADDR_INSN); | ||
printk(" R:%x T:%x IO:%x EX:%x Key:%x M:%x W:%x " | ||
"P:%x AS:%x CC:%x PM:%x", mask_bits(regs, PSW_MASK_PER), | ||
mask_bits(regs, PSW_MASK_DAT), mask_bits(regs, PSW_MASK_IO), | ||
mask_bits(regs, PSW_MASK_EXT), mask_bits(regs, PSW_MASK_KEY), | ||
mask_bits(regs, PSW_MASK_MCHECK), mask_bits(regs, PSW_MASK_WAIT), | ||
mask_bits(regs, PSW_MASK_PSTATE), mask_bits(regs, PSW_MASK_ASC), | ||
mask_bits(regs, PSW_MASK_CC), mask_bits(regs, PSW_MASK_PM)); | ||
#ifdef CONFIG_64BIT | ||
printk(" EA:%x", mask_bits(regs, PSW_MASK_EA | PSW_MASK_BA)); | ||
#endif | ||
printk("\n%s GPRS: " FOURLONG, mode, | ||
regs->gprs[0], regs->gprs[1], regs->gprs[2], regs->gprs[3]); | ||
printk(" " FOURLONG, | ||
regs->gprs[4], regs->gprs[5], regs->gprs[6], regs->gprs[7]); | ||
printk(" " FOURLONG, | ||
regs->gprs[8], regs->gprs[9], regs->gprs[10], regs->gprs[11]); | ||
printk(" " FOURLONG, | ||
regs->gprs[12], regs->gprs[13], regs->gprs[14], regs->gprs[15]); | ||
show_code(regs); | ||
} | ||
|
||
void show_regs(struct pt_regs *regs) | ||
{ | ||
printk("CPU: %d %s %s %.*s\n", | ||
task_thread_info(current)->cpu, print_tainted(), | ||
init_utsname()->release, | ||
(int)strcspn(init_utsname()->version, " "), | ||
init_utsname()->version); | ||
printk("Process %s (pid: %d, task: %p, ksp: %p)\n", | ||
current->comm, current->pid, current, | ||
(void *) current->thread.ksp); | ||
show_registers(regs); | ||
/* Show stack backtrace if pt_regs is from kernel mode */ | ||
if (!user_mode(regs)) | ||
show_trace(NULL, (unsigned long *) 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 [#%d] ", str, regs->int_code & 0xffff, ++die_counter); | ||
#ifdef CONFIG_PREEMPT | ||
printk("PREEMPT "); | ||
#endif | ||
#ifdef CONFIG_SMP | ||
printk("SMP "); | ||
#endif | ||
#ifdef CONFIG_DEBUG_PAGEALLOC | ||
printk("DEBUG_PAGEALLOC"); | ||
#endif | ||
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); | ||
} |
Oops, something went wrong.