| 
									
										
										
										
											2008-04-04 00:51:41 +02:00
										 |  |  | #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; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 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: | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 		printk(KERN_WARNING "WARNING: kmemcheck: Caught %d-bit read from %s memory (%p)\n", | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 			8 * e->size, e->state < ARRAY_SIZE(desc) ? | 
					
						
							|  |  |  | 				desc[e->state] : "(invalid shadow state)", | 
					
						
							|  |  |  | 			(void *) e->address); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 		printk(KERN_WARNING); | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 		for (i = 0; i < SHADOW_COPY_SIZE; ++i) | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 			printk(KERN_CONT "%02x", e->memory_copy[i]); | 
					
						
							|  |  |  | 		printk(KERN_CONT "\n"); | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 		printk(KERN_WARNING); | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 		for (i = 0; i < SHADOW_COPY_SIZE; ++i) { | 
					
						
							|  |  |  | 			if (e->shadow_copy[i] < ARRAY_SIZE(short_desc)) | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 				printk(KERN_CONT " %c", short_desc[e->shadow_copy[i]]); | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 			else | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 				printk(KERN_CONT " ?"); | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2009-12-28 11:02:15 +02:00
										 |  |  | 		printk(KERN_CONT "\n"); | 
					
						
							|  |  |  | 		printk(KERN_WARNING "%*c\n", 2 + 2 | 
					
						
							| 
									
										
										
										
											2009-02-26 10:47:50 +02:00
										 |  |  | 			* (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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-04 00:51:41 +02:00
										 |  |  | 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; | 
					
						
							| 
									
										
										
										
											2011-06-08 16:09:21 +09:00
										 |  |  | 	save_stack_trace_regs(regs, &e->trace); | 
					
						
							| 
									
										
										
										
											2008-04-04 00:51:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* 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); | 
					
						
							|  |  |  | } |