9c0729dc80
The various stack tracing routines take a 'bp' argument in which the caller is supposed to provide the base pointer to use, or 0 if doesn't have one. Since bp is garbage whenever CONFIG_FRAME_POINTER is not defined, this means all callers in principle should either always pass 0, or be conditional on CONFIG_FRAME_POINTER. However, there are only really three use cases for stack tracing: (a) Trace the current task, including IRQ stack if any (b) Trace the current task, but skip IRQ stack (c) Trace some other task In all cases, if CONFIG_FRAME_POINTER is not defined, bp should just be 0. If it _is_ defined, then - in case (a) bp should be gotten directly from the CPU's register, so the caller should pass NULL for regs, - in case (b) the caller should should pass the IRQ registers to dump_trace(), - in case (c) bp should be gotten from the top of the task's stack, so the caller should pass NULL for regs. Hence, the bp argument is not necessary because the combination of task and regs is sufficient to determine an appropriate value for bp. This patch introduces a new inline function stack_frame(task, regs) that computes the desired bp. This function is then called from the two versions of dump_stack(). Signed-off-by: Soren Sandmann <ssp@redhat.com> Acked-by: Steven Rostedt <rostedt@goodmis.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Ingo Molnar <mingo@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Peter Zijlstra <a.p.zijlstra@chello.nl> Cc: Arjan van de Ven <arjan@infradead.org>, Cc: Frederic Weisbecker <fweisbec@gmail.com>, Cc: Arnaldo Carvalho de Melo <acme@redhat.com>, LKML-Reference: <m3oc9rop28.fsf@dhcp-100-3-82.bos.redhat.com>> Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
228 lines
5.2 KiB
C
228 lines
5.2 KiB
C
#include <linux/interrupt.h>
|
|
#include <linux/kdebug.h>
|
|
#include <linux/kmemcheck.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/stacktrace.h>
|
|
#include <linux/string.h>
|
|
|
|
#include "error.h"
|
|
#include "shadow.h"
|
|
|
|
enum kmemcheck_error_type {
|
|
KMEMCHECK_ERROR_INVALID_ACCESS,
|
|
KMEMCHECK_ERROR_BUG,
|
|
};
|
|
|
|
#define SHADOW_COPY_SIZE (1 << CONFIG_KMEMCHECK_SHADOW_COPY_SHIFT)
|
|
|
|
struct kmemcheck_error {
|
|
enum kmemcheck_error_type type;
|
|
|
|
union {
|
|
/* KMEMCHECK_ERROR_INVALID_ACCESS */
|
|
struct {
|
|
/* Kind of access that caused the error */
|
|
enum kmemcheck_shadow state;
|
|
/* Address and size of the erroneous read */
|
|
unsigned long address;
|
|
unsigned int size;
|
|
};
|
|
};
|
|
|
|
struct pt_regs regs;
|
|
struct stack_trace trace;
|
|
unsigned long trace_entries[32];
|
|
|
|
/* We compress it to a char. */
|
|
unsigned char shadow_copy[SHADOW_COPY_SIZE];
|
|
unsigned char memory_copy[SHADOW_COPY_SIZE];
|
|
};
|
|
|
|
/*
|
|
* Create a ring queue of errors to output. We can't call printk() directly
|
|
* from the kmemcheck traps, since this may call the console drivers and
|
|
* result in a recursive fault.
|
|
*/
|
|
static struct kmemcheck_error error_fifo[CONFIG_KMEMCHECK_QUEUE_SIZE];
|
|
static unsigned int error_count;
|
|
static unsigned int error_rd;
|
|
static unsigned int error_wr;
|
|
static unsigned int error_missed_count;
|
|
|
|
static struct kmemcheck_error *error_next_wr(void)
|
|
{
|
|
struct kmemcheck_error *e;
|
|
|
|
if (error_count == ARRAY_SIZE(error_fifo)) {
|
|
++error_missed_count;
|
|
return NULL;
|
|
}
|
|
|
|
e = &error_fifo[error_wr];
|
|
if (++error_wr == ARRAY_SIZE(error_fifo))
|
|
error_wr = 0;
|
|
++error_count;
|
|
return e;
|
|
}
|
|
|
|
static struct kmemcheck_error *error_next_rd(void)
|
|
{
|
|
struct kmemcheck_error *e;
|
|
|
|
if (error_count == 0)
|
|
return NULL;
|
|
|
|
e = &error_fifo[error_rd];
|
|
if (++error_rd == ARRAY_SIZE(error_fifo))
|
|
error_rd = 0;
|
|
--error_count;
|
|
return e;
|
|
}
|
|
|
|
void kmemcheck_error_recall(void)
|
|
{
|
|
static const char *desc[] = {
|
|
[KMEMCHECK_SHADOW_UNALLOCATED] = "unallocated",
|
|
[KMEMCHECK_SHADOW_UNINITIALIZED] = "uninitialized",
|
|
[KMEMCHECK_SHADOW_INITIALIZED] = "initialized",
|
|
[KMEMCHECK_SHADOW_FREED] = "freed",
|
|
};
|
|
|
|
static const char short_desc[] = {
|
|
[KMEMCHECK_SHADOW_UNALLOCATED] = 'a',
|
|
[KMEMCHECK_SHADOW_UNINITIALIZED] = 'u',
|
|
[KMEMCHECK_SHADOW_INITIALIZED] = 'i',
|
|
[KMEMCHECK_SHADOW_FREED] = 'f',
|
|
};
|
|
|
|
struct kmemcheck_error *e;
|
|
unsigned int i;
|
|
|
|
e = error_next_rd();
|
|
if (!e)
|
|
return;
|
|
|
|
switch (e->type) {
|
|
case KMEMCHECK_ERROR_INVALID_ACCESS:
|
|
printk(KERN_WARNING "WARNING: kmemcheck: Caught %d-bit read from %s memory (%p)\n",
|
|
8 * e->size, e->state < ARRAY_SIZE(desc) ?
|
|
desc[e->state] : "(invalid shadow state)",
|
|
(void *) e->address);
|
|
|
|
printk(KERN_WARNING);
|
|
for (i = 0; i < SHADOW_COPY_SIZE; ++i)
|
|
printk(KERN_CONT "%02x", e->memory_copy[i]);
|
|
printk(KERN_CONT "\n");
|
|
|
|
printk(KERN_WARNING);
|
|
for (i = 0; i < SHADOW_COPY_SIZE; ++i) {
|
|
if (e->shadow_copy[i] < ARRAY_SIZE(short_desc))
|
|
printk(KERN_CONT " %c", short_desc[e->shadow_copy[i]]);
|
|
else
|
|
printk(KERN_CONT " ?");
|
|
}
|
|
printk(KERN_CONT "\n");
|
|
printk(KERN_WARNING "%*c\n", 2 + 2
|
|
* (int) (e->address & (SHADOW_COPY_SIZE - 1)), '^');
|
|
break;
|
|
case KMEMCHECK_ERROR_BUG:
|
|
printk(KERN_EMERG "ERROR: kmemcheck: Fatal error\n");
|
|
break;
|
|
}
|
|
|
|
__show_regs(&e->regs, 1);
|
|
print_stack_trace(&e->trace, 0);
|
|
}
|
|
|
|
static void do_wakeup(unsigned long data)
|
|
{
|
|
while (error_count > 0)
|
|
kmemcheck_error_recall();
|
|
|
|
if (error_missed_count > 0) {
|
|
printk(KERN_WARNING "kmemcheck: Lost %d error reports because "
|
|
"the queue was too small\n", error_missed_count);
|
|
error_missed_count = 0;
|
|
}
|
|
}
|
|
|
|
static DECLARE_TASKLET(kmemcheck_tasklet, &do_wakeup, 0);
|
|
|
|
/*
|
|
* Save the context of an error report.
|
|
*/
|
|
void kmemcheck_error_save(enum kmemcheck_shadow state,
|
|
unsigned long address, unsigned int size, struct pt_regs *regs)
|
|
{
|
|
static unsigned long prev_ip;
|
|
|
|
struct kmemcheck_error *e;
|
|
void *shadow_copy;
|
|
void *memory_copy;
|
|
|
|
/* Don't report several adjacent errors from the same EIP. */
|
|
if (regs->ip == prev_ip)
|
|
return;
|
|
prev_ip = regs->ip;
|
|
|
|
e = error_next_wr();
|
|
if (!e)
|
|
return;
|
|
|
|
e->type = KMEMCHECK_ERROR_INVALID_ACCESS;
|
|
|
|
e->state = state;
|
|
e->address = address;
|
|
e->size = size;
|
|
|
|
/* Save regs */
|
|
memcpy(&e->regs, regs, sizeof(*regs));
|
|
|
|
/* Save stack trace */
|
|
e->trace.nr_entries = 0;
|
|
e->trace.entries = e->trace_entries;
|
|
e->trace.max_entries = ARRAY_SIZE(e->trace_entries);
|
|
e->trace.skip = 0;
|
|
save_stack_trace_regs(&e->trace, regs);
|
|
|
|
/* Round address down to nearest 16 bytes */
|
|
shadow_copy = kmemcheck_shadow_lookup(address
|
|
& ~(SHADOW_COPY_SIZE - 1));
|
|
BUG_ON(!shadow_copy);
|
|
|
|
memcpy(e->shadow_copy, shadow_copy, SHADOW_COPY_SIZE);
|
|
|
|
kmemcheck_show_addr(address);
|
|
memory_copy = (void *) (address & ~(SHADOW_COPY_SIZE - 1));
|
|
memcpy(e->memory_copy, memory_copy, SHADOW_COPY_SIZE);
|
|
kmemcheck_hide_addr(address);
|
|
|
|
tasklet_hi_schedule_first(&kmemcheck_tasklet);
|
|
}
|
|
|
|
/*
|
|
* Save the context of a kmemcheck bug.
|
|
*/
|
|
void kmemcheck_error_save_bug(struct pt_regs *regs)
|
|
{
|
|
struct kmemcheck_error *e;
|
|
|
|
e = error_next_wr();
|
|
if (!e)
|
|
return;
|
|
|
|
e->type = KMEMCHECK_ERROR_BUG;
|
|
|
|
memcpy(&e->regs, regs, sizeof(*regs));
|
|
|
|
e->trace.nr_entries = 0;
|
|
e->trace.entries = e->trace_entries;
|
|
e->trace.max_entries = ARRAY_SIZE(e->trace_entries);
|
|
e->trace.skip = 1;
|
|
save_stack_trace(&e->trace);
|
|
|
|
tasklet_hi_schedule_first(&kmemcheck_tasklet);
|
|
}
|