mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 01:41:39 +00:00
tile: improve stack backtrace
This commit fixes a number of issues with the tile backtrace code. - Don't try to identify userspace shared object or executable paths if we are doing a backtrace from an interrupt; it's not legal, and also unlikely to be interesting. Likewise, don't try to do it for other address spaces, since d_path() assumes it is being called in "current" context. - Move "in_backtrace" from thread_struct to thread_info. This way we can access it even if our stack thread_info has been clobbered, which makes backtracing more robust. - Avoid using "current" directly when testing for is_sigreturn(). Since "current" may be corrupt, we're better off using kbt->task explicitly to look up the vdso_base for the current task. Conveniently, this simplifies the internal APIs (we only need one is_sigreturn() function now). - Avoid bogus "Odd fault" warning when pc/sp/ex1 are all zero, as is true for kernel threads above the last frame. - Hook into Tejun Heo's dump_stack() framework in lib/dump_stack.c. - Write last entry in save_stack_trace() as ULONG_MAX, not zero, since ftrace (at least) relies on finding that marker. - Implement save_stack_trace_regs() and save_strack_trace_user(), and set CONFIG_USER_STACKTRACE_SUPPORT. Signed-off-by: Chris Metcalf <cmetcalf@ezchip.com>
This commit is contained in:
parent
e8200baa2f
commit
47ad7b9bbe
9 changed files with 107 additions and 82 deletions
|
@ -24,6 +24,7 @@ config TILE
|
|||
select MODULES_USE_ELF_RELA
|
||||
select HAVE_ARCH_TRACEHOOK
|
||||
select HAVE_SYSCALL_TRACEPOINTS
|
||||
select USER_STACKTRACE_SUPPORT
|
||||
select ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE
|
||||
select HAVE_DEBUG_STACKOVERFLOW
|
||||
select ARCH_WANT_FRAME_POINTERS
|
||||
|
|
|
@ -111,8 +111,6 @@ struct thread_struct {
|
|||
unsigned long long interrupt_mask;
|
||||
/* User interrupt-control 0 state */
|
||||
unsigned long intctrl_0;
|
||||
/* Is this task currently doing a backtrace? */
|
||||
bool in_backtrace;
|
||||
/* Any other miscellaneous processor state bits */
|
||||
unsigned long proc_status;
|
||||
#if !CHIP_HAS_FIXED_INTVEC_BASE()
|
||||
|
|
|
@ -58,17 +58,14 @@ extern int KBacktraceIterator_end(struct KBacktraceIterator *kbt);
|
|||
/* Advance to the next frame. */
|
||||
extern void KBacktraceIterator_next(struct KBacktraceIterator *kbt);
|
||||
|
||||
/* Dump just the contents of the pt_regs structure. */
|
||||
extern void tile_show_regs(struct pt_regs *);
|
||||
|
||||
/*
|
||||
* Dump stack given complete register info. Use only from the
|
||||
* architecture-specific code; show_stack()
|
||||
* and dump_stack() (in entry.S) are architecture-independent entry points.
|
||||
* and dump_stack() are architecture-independent entry points.
|
||||
*/
|
||||
extern void tile_show_stack(struct KBacktraceIterator *, int headers);
|
||||
|
||||
/* Dump stack of current process, with registers to seed the backtrace. */
|
||||
extern void dump_stack_regs(struct pt_regs *);
|
||||
|
||||
/* Helper method for assembly dump_stack(). */
|
||||
extern void _dump_stack(int dummy, ulong pc, ulong lr, ulong sp, ulong r52);
|
||||
extern void tile_show_stack(struct KBacktraceIterator *);
|
||||
|
||||
#endif /* _ASM_TILE_STACK_H */
|
||||
|
|
|
@ -42,6 +42,7 @@ struct thread_info {
|
|||
unsigned long unalign_jit_tmp[4]; /* temp r0..r3 storage */
|
||||
void __user *unalign_jit_base; /* unalign fixup JIT base */
|
||||
#endif
|
||||
bool in_backtrace; /* currently doing backtrace? */
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -27,13 +27,6 @@ STD_ENTRY(current_text_addr)
|
|||
{ move r0, lr; jrp lr }
|
||||
STD_ENDPROC(current_text_addr)
|
||||
|
||||
STD_ENTRY(dump_stack)
|
||||
{ move r2, lr; lnk r1 }
|
||||
{ move r4, r52; addli r1, r1, dump_stack - . }
|
||||
{ move r3, sp; j _dump_stack }
|
||||
jrp lr /* keep backtracer happy */
|
||||
STD_ENDPROC(dump_stack)
|
||||
|
||||
STD_ENTRY(KBacktraceIterator_init_current)
|
||||
{ move r2, lr; lnk r1 }
|
||||
{ move r4, r52; addli r1, r1, KBacktraceIterator_init_current - . }
|
||||
|
|
|
@ -546,33 +546,43 @@ void exit_thread(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
void show_regs(struct pt_regs *regs)
|
||||
void tile_show_regs(struct pt_regs *regs)
|
||||
{
|
||||
struct task_struct *tsk = validate_current();
|
||||
int i;
|
||||
|
||||
if (tsk != &corrupt_current)
|
||||
show_regs_print_info(KERN_ERR);
|
||||
#ifdef __tilegx__
|
||||
for (i = 0; i < 17; i++)
|
||||
pr_err(" r%-2d: " REGFMT " r%-2d: " REGFMT " r%-2d: " REGFMT "\n",
|
||||
pr_err(" r%-2d: "REGFMT" r%-2d: "REGFMT" r%-2d: "REGFMT"\n",
|
||||
i, regs->regs[i], i+18, regs->regs[i+18],
|
||||
i+36, regs->regs[i+36]);
|
||||
pr_err(" r17: " REGFMT " r35: " REGFMT " tp : " REGFMT "\n",
|
||||
pr_err(" r17: "REGFMT" r35: "REGFMT" tp : "REGFMT"\n",
|
||||
regs->regs[17], regs->regs[35], regs->tp);
|
||||
pr_err(" sp : " REGFMT " lr : " REGFMT "\n", regs->sp, regs->lr);
|
||||
pr_err(" sp : "REGFMT" lr : "REGFMT"\n", regs->sp, regs->lr);
|
||||
#else
|
||||
for (i = 0; i < 13; i++)
|
||||
pr_err(" r%-2d: " REGFMT " r%-2d: " REGFMT " r%-2d: " REGFMT " r%-2d: " REGFMT "\n",
|
||||
pr_err(" r%-2d: "REGFMT" r%-2d: "REGFMT
|
||||
" r%-2d: "REGFMT" r%-2d: "REGFMT"\n",
|
||||
i, regs->regs[i], i+14, regs->regs[i+14],
|
||||
i+27, regs->regs[i+27], i+40, regs->regs[i+40]);
|
||||
pr_err(" r13: " REGFMT " tp : " REGFMT " sp : " REGFMT " lr : " REGFMT "\n",
|
||||
pr_err(" r13: "REGFMT" tp : "REGFMT" sp : "REGFMT" lr : "REGFMT"\n",
|
||||
regs->regs[13], regs->tp, regs->sp, regs->lr);
|
||||
#endif
|
||||
pr_err(" pc : " REGFMT " ex1: %ld faultnum: %ld\n",
|
||||
regs->pc, regs->ex1, regs->faultnum);
|
||||
pr_err(" pc : "REGFMT" ex1: %ld faultnum: %ld flags:%s%s%s%s\n",
|
||||
regs->pc, regs->ex1, regs->faultnum,
|
||||
is_compat_task() ? " compat" : "",
|
||||
(regs->flags & PT_FLAGS_DISABLE_IRQ) ? " noirq" : "",
|
||||
!(regs->flags & PT_FLAGS_CALLER_SAVES) ? " nocallersave" : "",
|
||||
(regs->flags & PT_FLAGS_RESTORE_REGS) ? " restoreregs" : "");
|
||||
}
|
||||
|
||||
dump_stack_regs(regs);
|
||||
void show_regs(struct pt_regs *regs)
|
||||
{
|
||||
struct KBacktraceIterator kbt;
|
||||
|
||||
show_regs_print_info(KERN_DEFAULT);
|
||||
tile_show_regs(regs);
|
||||
|
||||
KBacktraceIterator_init(&kbt, NULL, regs);
|
||||
tile_show_stack(&kbt);
|
||||
}
|
||||
|
||||
/* To ensure stack dump on tiles occurs one by one. */
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/mmzone.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/string.h>
|
||||
#include <asm/backtrace.h>
|
||||
#include <asm/page.h>
|
||||
|
@ -109,7 +110,7 @@ static struct pt_regs *valid_fault_handler(struct KBacktraceIterator* kbt)
|
|||
if (kbt->verbose)
|
||||
pr_err(" <%s while in user mode>\n", fault);
|
||||
} else {
|
||||
if (kbt->verbose)
|
||||
if (kbt->verbose && (p->pc != 0 || p->sp != 0 || p->ex1 != 0))
|
||||
pr_err(" (odd fault: pc %#lx, sp %#lx, ex1 %#lx?)\n",
|
||||
p->pc, p->sp, p->ex1);
|
||||
return NULL;
|
||||
|
@ -119,10 +120,12 @@ static struct pt_regs *valid_fault_handler(struct KBacktraceIterator* kbt)
|
|||
return p;
|
||||
}
|
||||
|
||||
/* Is the pc pointing to a sigreturn trampoline? */
|
||||
static int is_sigreturn(unsigned long pc)
|
||||
/* Is the iterator pointing to a sigreturn trampoline? */
|
||||
static int is_sigreturn(struct KBacktraceIterator *kbt)
|
||||
{
|
||||
return current->mm && (pc == VDSO_SYM(&__vdso_rt_sigreturn));
|
||||
return kbt->task->mm &&
|
||||
(kbt->it.pc == ((ulong)kbt->task->mm->context.vdso_base +
|
||||
(ulong)&__vdso_rt_sigreturn));
|
||||
}
|
||||
|
||||
/* Return a pt_regs pointer for a valid signal handler frame */
|
||||
|
@ -131,7 +134,7 @@ static struct pt_regs *valid_sigframe(struct KBacktraceIterator* kbt,
|
|||
{
|
||||
BacktraceIterator *b = &kbt->it;
|
||||
|
||||
if (is_sigreturn(b->pc) && b->sp < PAGE_OFFSET &&
|
||||
if (is_sigreturn(kbt) && b->sp < PAGE_OFFSET &&
|
||||
b->sp % sizeof(long) == 0) {
|
||||
int retval;
|
||||
pagefault_disable();
|
||||
|
@ -151,11 +154,6 @@ static struct pt_regs *valid_sigframe(struct KBacktraceIterator* kbt,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static int KBacktraceIterator_is_sigreturn(struct KBacktraceIterator *kbt)
|
||||
{
|
||||
return is_sigreturn(kbt->it.pc);
|
||||
}
|
||||
|
||||
static int KBacktraceIterator_restart(struct KBacktraceIterator *kbt)
|
||||
{
|
||||
struct pt_regs *p;
|
||||
|
@ -178,7 +176,7 @@ static int KBacktraceIterator_next_item_inclusive(
|
|||
{
|
||||
for (;;) {
|
||||
do {
|
||||
if (!KBacktraceIterator_is_sigreturn(kbt))
|
||||
if (!is_sigreturn(kbt))
|
||||
return KBT_ONGOING;
|
||||
} while (backtrace_next(&kbt->it));
|
||||
|
||||
|
@ -357,51 +355,50 @@ static void describe_addr(struct KBacktraceIterator *kbt,
|
|||
*/
|
||||
static bool start_backtrace(void)
|
||||
{
|
||||
if (current->thread.in_backtrace) {
|
||||
if (current_thread_info()->in_backtrace) {
|
||||
pr_err("Backtrace requested while in backtrace!\n");
|
||||
return false;
|
||||
}
|
||||
current->thread.in_backtrace = true;
|
||||
current_thread_info()->in_backtrace = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void end_backtrace(void)
|
||||
{
|
||||
current->thread.in_backtrace = false;
|
||||
current_thread_info()->in_backtrace = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This method wraps the backtracer's more generic support.
|
||||
* It is only invoked from the architecture-specific code; show_stack()
|
||||
* and dump_stack() (in entry.S) are architecture-independent entry points.
|
||||
* and dump_stack() are architecture-independent entry points.
|
||||
*/
|
||||
void tile_show_stack(struct KBacktraceIterator *kbt, int headers)
|
||||
void tile_show_stack(struct KBacktraceIterator *kbt)
|
||||
{
|
||||
int i;
|
||||
int have_mmap_sem = 0;
|
||||
|
||||
if (!start_backtrace())
|
||||
return;
|
||||
if (headers) {
|
||||
/*
|
||||
* Add a blank line since if we are called from panic(),
|
||||
* then bust_spinlocks() spit out a space in front of us
|
||||
* and it will mess up our KERN_ERR.
|
||||
*/
|
||||
pr_err("Starting stack dump of tid %d, pid %d (%s) on cpu %d at cycle %lld\n",
|
||||
kbt->task->pid, kbt->task->tgid, kbt->task->comm,
|
||||
raw_smp_processor_id(), get_cycles());
|
||||
}
|
||||
kbt->verbose = 1;
|
||||
i = 0;
|
||||
for (; !KBacktraceIterator_end(kbt); KBacktraceIterator_next(kbt)) {
|
||||
char namebuf[KSYM_NAME_LEN+100];
|
||||
unsigned long address = kbt->it.pc;
|
||||
|
||||
/* Try to acquire the mmap_sem as we pass into userspace. */
|
||||
if (address < PAGE_OFFSET && !have_mmap_sem && kbt->task->mm)
|
||||
/*
|
||||
* Try to acquire the mmap_sem as we pass into userspace.
|
||||
* If we're in an interrupt context, don't even try, since
|
||||
* it's not safe to call e.g. d_path() from an interrupt,
|
||||
* since it uses spin locks without disabling interrupts.
|
||||
* Note we test "kbt->task == current", not "kbt->is_current",
|
||||
* since we're checking that "current" will work in d_path().
|
||||
*/
|
||||
if (kbt->task == current && address < PAGE_OFFSET &&
|
||||
!have_mmap_sem && kbt->task->mm && !in_interrupt()) {
|
||||
have_mmap_sem =
|
||||
down_read_trylock(&kbt->task->mm->mmap_sem);
|
||||
}
|
||||
|
||||
describe_addr(kbt, address, have_mmap_sem,
|
||||
namebuf, sizeof(namebuf));
|
||||
|
@ -416,24 +413,12 @@ void tile_show_stack(struct KBacktraceIterator *kbt, int headers)
|
|||
}
|
||||
if (kbt->end == KBT_LOOP)
|
||||
pr_err("Stack dump stopped; next frame identical to this one\n");
|
||||
if (headers)
|
||||
pr_err("Stack dump complete\n");
|
||||
if (have_mmap_sem)
|
||||
up_read(&kbt->task->mm->mmap_sem);
|
||||
end_backtrace();
|
||||
}
|
||||
EXPORT_SYMBOL(tile_show_stack);
|
||||
|
||||
|
||||
/* This is called from show_regs() and _dump_stack() */
|
||||
void dump_stack_regs(struct pt_regs *regs)
|
||||
{
|
||||
struct KBacktraceIterator kbt;
|
||||
KBacktraceIterator_init(&kbt, NULL, regs);
|
||||
tile_show_stack(&kbt, 1);
|
||||
}
|
||||
EXPORT_SYMBOL(dump_stack_regs);
|
||||
|
||||
static struct pt_regs *regs_to_pt_regs(struct pt_regs *regs,
|
||||
ulong pc, ulong lr, ulong sp, ulong r52)
|
||||
{
|
||||
|
@ -445,11 +430,15 @@ static struct pt_regs *regs_to_pt_regs(struct pt_regs *regs,
|
|||
return regs;
|
||||
}
|
||||
|
||||
/* This is called from dump_stack() and just converts to pt_regs */
|
||||
/* Deprecated function currently only used by kernel_double_fault(). */
|
||||
void _dump_stack(int dummy, ulong pc, ulong lr, ulong sp, ulong r52)
|
||||
{
|
||||
struct KBacktraceIterator kbt;
|
||||
struct pt_regs regs;
|
||||
dump_stack_regs(regs_to_pt_regs(®s, pc, lr, sp, r52));
|
||||
|
||||
regs_to_pt_regs(®s, pc, lr, sp, r52);
|
||||
KBacktraceIterator_init(&kbt, NULL, ®s);
|
||||
tile_show_stack(&kbt);
|
||||
}
|
||||
|
||||
/* This is called from KBacktraceIterator_init_current() */
|
||||
|
@ -461,22 +450,30 @@ void _KBacktraceIterator_init_current(struct KBacktraceIterator *kbt, ulong pc,
|
|||
regs_to_pt_regs(®s, pc, lr, sp, r52));
|
||||
}
|
||||
|
||||
/* This is called only from kernel/sched/core.c, with esp == NULL */
|
||||
/*
|
||||
* Called from sched_show_task() with task != NULL, or dump_stack()
|
||||
* with task == NULL. The esp argument is always NULL.
|
||||
*/
|
||||
void show_stack(struct task_struct *task, unsigned long *esp)
|
||||
{
|
||||
struct KBacktraceIterator kbt;
|
||||
if (task == NULL || task == current)
|
||||
if (task == NULL || task == current) {
|
||||
KBacktraceIterator_init_current(&kbt);
|
||||
else
|
||||
KBacktraceIterator_next(&kbt); /* don't show first frame */
|
||||
} else {
|
||||
KBacktraceIterator_init(&kbt, task, NULL);
|
||||
tile_show_stack(&kbt, 0);
|
||||
}
|
||||
tile_show_stack(&kbt);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_STACKTRACE
|
||||
|
||||
/* Support generic Linux stack API too */
|
||||
|
||||
void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace)
|
||||
static void save_stack_trace_common(struct task_struct *task,
|
||||
struct pt_regs *regs,
|
||||
bool user,
|
||||
struct stack_trace *trace)
|
||||
{
|
||||
struct KBacktraceIterator kbt;
|
||||
int skip = trace->skip;
|
||||
|
@ -484,31 +481,57 @@ void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace)
|
|||
|
||||
if (!start_backtrace())
|
||||
goto done;
|
||||
if (task == NULL || task == current)
|
||||
if (regs != NULL) {
|
||||
KBacktraceIterator_init(&kbt, NULL, regs);
|
||||
} else if (task == NULL || task == current) {
|
||||
KBacktraceIterator_init_current(&kbt);
|
||||
else
|
||||
skip++; /* don't show KBacktraceIterator_init_current */
|
||||
} else {
|
||||
KBacktraceIterator_init(&kbt, task, NULL);
|
||||
}
|
||||
for (; !KBacktraceIterator_end(&kbt); KBacktraceIterator_next(&kbt)) {
|
||||
if (skip) {
|
||||
--skip;
|
||||
continue;
|
||||
}
|
||||
if (i >= trace->max_entries || kbt.it.pc < PAGE_OFFSET)
|
||||
if (i >= trace->max_entries ||
|
||||
(!user && kbt.it.pc < PAGE_OFFSET))
|
||||
break;
|
||||
trace->entries[i++] = kbt.it.pc;
|
||||
}
|
||||
end_backtrace();
|
||||
done:
|
||||
if (i < trace->max_entries)
|
||||
trace->entries[i++] = ULONG_MAX;
|
||||
trace->nr_entries = i;
|
||||
}
|
||||
|
||||
void save_stack_trace_tsk(struct task_struct *task, struct stack_trace *trace)
|
||||
{
|
||||
save_stack_trace_common(task, NULL, false, trace);
|
||||
}
|
||||
EXPORT_SYMBOL(save_stack_trace_tsk);
|
||||
|
||||
void save_stack_trace(struct stack_trace *trace)
|
||||
{
|
||||
save_stack_trace_tsk(NULL, trace);
|
||||
save_stack_trace_common(NULL, NULL, false, trace);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(save_stack_trace);
|
||||
|
||||
void save_stack_trace_regs(struct pt_regs *regs, struct stack_trace *trace)
|
||||
{
|
||||
save_stack_trace_common(NULL, regs, false, trace);
|
||||
}
|
||||
|
||||
void save_stack_trace_user(struct stack_trace *trace)
|
||||
{
|
||||
/* Trace user stack if we are not a kernel thread. */
|
||||
if (current->mm)
|
||||
save_stack_trace_common(NULL, task_pt_regs(current),
|
||||
true, trace);
|
||||
else if (trace->nr_entries < trace->max_entries)
|
||||
trace->entries[trace->nr_entries++] = ULONG_MAX;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* In entry.S */
|
||||
|
|
|
@ -407,6 +407,9 @@ void do_nmi(struct pt_regs *regs, int fault_num, unsigned long reason)
|
|||
}
|
||||
}
|
||||
|
||||
/* Deprecated function currently only used here. */
|
||||
extern void _dump_stack(int dummy, ulong pc, ulong lr, ulong sp, ulong r52);
|
||||
|
||||
void kernel_double_fault(int dummy, ulong pc, ulong lr, ulong sp, ulong r52)
|
||||
{
|
||||
_dump_stack(dummy, pc, lr, sp, r52);
|
||||
|
|
|
@ -26,7 +26,6 @@ EXPORT_SYMBOL(finv_user_asm);
|
|||
#include <linux/kernel.h>
|
||||
#include <asm/processor.h>
|
||||
EXPORT_SYMBOL(current_text_addr);
|
||||
EXPORT_SYMBOL(dump_stack);
|
||||
|
||||
/* arch/tile/kernel/head.S */
|
||||
EXPORT_SYMBOL(empty_zero_page);
|
||||
|
|
Loading…
Reference in a new issue