Name: IDE Oops Dumper for x86 Author: Rusty Russell Status: Tested on 2.5.34 i386 SMP D: This is an experimental IDE oops dumper for 2.5. To test it you D: will need the userspace utilities from: D: http://www.kernel.org/pub/linux/kernel/people/rusty/oopser-0.1.tar.gz diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/arch/i386/kernel/i386_ksyms.c .29808-linux-2.5.69-bk13.updated/arch/i386/kernel/i386_ksyms.c --- .29808-linux-2.5.69-bk13/arch/i386/kernel/i386_ksyms.c 2003-05-19 10:53:39.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/arch/i386/kernel/i386_ksyms.c 2003-05-19 13:08:27.000000000 +1000 @@ -195,6 +195,7 @@ extern int is_sony_vaio_laptop; EXPORT_SYMBOL(is_sony_vaio_laptop); EXPORT_SYMBOL(__PAGE_KERNEL); +EXPORT_SYMBOL(oops_handler); #ifdef CONFIG_HIGHMEM EXPORT_SYMBOL(kmap); diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/arch/i386/kernel/traps.c .29808-linux-2.5.69-bk13.updated/arch/i386/kernel/traps.c --- .29808-linux-2.5.69-bk13/arch/i386/kernel/traps.c 2003-05-19 10:53:39.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/arch/i386/kernel/traps.c 2003-05-19 13:07:58.000000000 +1000 @@ -254,6 +254,15 @@ void die(const char * str, struct pt_reg { static int die_counter; + if (oops_handler) { + int ret; + local_irq_disable(); + ret = oops_handler(str, regs, err); + local_irq_enable(); + /* They had an oops handler, but it failed. Get word out */ + if (ret != 0) + printk(KERN_EMERG "Oops handler failed: %i\n", ret); + } console_verbose(); spin_lock_irq(&die_lock); bust_spinlocks(1); @@ -798,6 +807,7 @@ do { \ "3" ((char *) (addr)),"2" ((seg) << 16)); \ } while (0) +int (*oops_handler)(const char *str, struct pt_regs *regs, long err); /* * This needs to use 'idt_table' rather than 'idt', and diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/Kconfig .29808-linux-2.5.69-bk13.updated/drivers/char/Kconfig --- .29808-linux-2.5.69-bk13/drivers/char/Kconfig 2003-05-05 12:36:58.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/Kconfig 2003-05-19 13:11:02.000000000 +1000 @@ -1000,5 +1000,16 @@ config HANGCHECK_TIMER out to lunch past a certain margin. It can reboot the system or merely print a warning. +config OOPSER + tristate "/dev/oopser support (EXPERIMENTAL)" + depends on EXPERIMENTAL + help + The oopser dumps kernel errors (oopses and panics) to an IDE + device. If in doubt, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you want). + The module will be called mwave. If you want to compile it as + a module, say M here and read Documentation/modules.txt. endmenu diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/Makefile .29808-linux-2.5.69-bk13.updated/drivers/char/Makefile --- .29808-linux-2.5.69-bk13/drivers/char/Makefile 2003-03-25 12:17:02.000000000 +1100 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/Makefile 2003-05-19 13:07:22.000000000 +1000 @@ -76,6 +76,7 @@ obj-$(CONFIG_AGP) += agp/ obj-$(CONFIG_DRM) += drm/ obj-$(CONFIG_PCMCIA) += pcmcia/ obj-$(CONFIG_IPMI_HANDLER) += ipmi/ +obj-$(CONFIG_OOPSER) += oopser/ obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/Makefile .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/Makefile --- .29808-linux-2.5.69-bk13/drivers/char/oopser/Makefile 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/Makefile 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,28 @@ +# +# Makefile for /dev/oopser support. +# +list-multi := oopser_dev.o + +obj-$(CONFIG_OOPSER) += oopser_dev.o + +oopser_dev-objs-y = interface.o dumper.o +oopser_dev-objs-$(CONFIG_X86) += dumper_x86_ide.o regs_x86.o + +include $(TOPDIR)/Rules.make + +# Check that external references do not slip into dumper core. +oopser_dev-leaf := dumper_x86_ide.o regs_x86.o dumper.o +oopser_dev_ALLOWED := tainted|log_buf|log_end + +oopser_dev-check: $(oopser_dev-leaf) + @for f in $(oopser_dev-leaf); do \ + for sym in `nm $$f|fgrep ' U '|sed 's/.* U //'|grep -vwE '$(oopser_dev_ALLOWED)'`; \ + do \ + if nm $(oopser_dev-leaf)|grep -q " [TD] $$sym\$$"; then :; \ + else echo $$f:1:References symbol $$sym|sed 's/.o:/.c:/'>&2; \ + fi; \ + done; \ + done; true + +oopser_dev.o: $(oopser_dev-objs-y) #oopser_dev-check + $(LD) $(LD_RFLAG) -r -o $@ $(oopser_dev-objs-y) diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/decode-table.c .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/decode-table.c --- .29808-linux-2.5.69-bk13/drivers/char/oopser/decode-table.c 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/decode-table.c 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,67 @@ +static const char huff_dec_chars[] + = "_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +struct huff_decode_table { signed char jump[2]; }; +static const struct huff_decode_table huff_decode[] = { + [0] = { { 1, 2 } }, + [1] = { { 2, 3 } }, + [3] = { { 2, -41 } }, + [5] = { { -37, 1 } }, + [6] = { { -49, 1 } }, + [7] = { { -58, -61 } }, + [4] = { { 4, 0 } }, + [8] = { { -45, -55 } }, + [2] = { { 7, 8 } }, + [9] = { { 2, 3 } }, + [11] = { { 2, -54 } }, + [13] = { { -57, 1 } }, + [14] = { { 1, -47 } }, + [15] = { { -60, 1 } }, + [16] = { { 1, -53 } }, + [17] = { { 1, 2 } }, + [18] = { { -62, -5 } }, + [19] = { { 1, -9 } }, + [20] = { { 1, -2 } }, + [21] = { { 1, -13 } }, + [22] = { { -29, 1 } }, + [23] = { { -21, 1 } }, + [24] = { { 1, -22 } }, + [25] = { { -34, 1 } }, + [26] = { { -24, -31 } }, + [12] = { { -63, -56 } }, + [10] = { { 17, 18 } }, + [27] = { { 2, 3 } }, + [29] = { { -48, -52 } }, + [30] = { { 1, -40 } }, + [31] = { { 1, -43 } }, + [32] = { { -59, 1 } }, + [33] = { { 1, 2 } }, + [34] = { { -4, -3 } }, + [35] = { { 1, 2 } }, + [36] = { { 2, 3 } }, + [38] = { { -19, -6 } }, + [39] = { { 1, -7 } }, + [40] = { { -26, 1 } }, + [41] = { { -10, 1 } }, + [42] = { { 1, -8 } }, + [43] = { { -23, 1 } }, + [44] = { { -28, -32 } }, + [37] = { { -46, 8 } }, + [45] = { { 1, -1 } }, + [46] = { { 1, -11 } }, + [47] = { { 1, -25 } }, + [48] = { { -30, 1 } }, + [49] = { { 1, 2 } }, + [50] = { { 2, -27 } }, + [52] = { { 1, -14 } }, + [53] = { { 1, -12 } }, + [54] = { { 1, -36 } }, + [55] = { { 1, -35 } }, + [56] = { { -16, -33 } }, + [51] = { { 6, 7 } }, + [57] = { { -18, -20 } }, + [58] = { { -15, -17 } }, + [28] = { { 31, 32 } }, + [59] = { { -50, 2 } }, + [61] = { { -42, 1 } }, + [62] = { { -38, -44 } }, + [60] = { { -39, -51 } }, }; diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/dumper.c .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/dumper.c --- .29808-linux-2.5.69-bk13/drivers/char/oopser/dumper.c 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/dumper.c 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,208 @@ +/* This is the core dumper code. It must *not* reference any outside + code, except the arch/subsystem specific routines, which is done + through the oopser_write and oopser_read pointer. */ + +#include +#include +#include +#include +#include +#include +#include +#include "oopser_priv.h" +#include "decode-table.c" + +int (*oopser_block_write)(const char dump[512], unsigned int block); +int (*oopser_block_read)(char dump[512], unsigned int block); + +static char kversion[] = UTS_RELEASE " " UTS_MACHINE " " UTS_VERSION " " LINUX_COMPILER; + +static char page[LINUX_OOPSER_DUMP_SIZE]; + +struct oopser_symtable *symtable = NULL; + +static inline int get_bit(const unsigned char *addr, unsigned int *bitnum) +{ + int bit; + + bit = addr[*bitnum / 8] & (1 << (*bitnum % 8)); + (*bitnum)++; + return !!bit; +} + +static char get_char(const unsigned char *addr, unsigned int *bitnum) +{ + int next; + unsigned int cursor; + + /* Start at zero: jumps are in terms of offset from current pos. */ + for (cursor = 0; + (next = huff_decode[cursor].jump[get_bit(addr, bitnum)]) > 0; + cursor += next) ; + return huff_dec_chars[-next]; +} + +static void decode_name(const char *prefix, + const unsigned char *encnames, + unsigned int symnum, + char *outname) +{ + unsigned int i, bitnum; + + /* Swallow previous names. */ + for (i = 0, bitnum = 0; i < symnum; i++) + while (get_char(encnames, &bitnum) != 0); + + if (prefix) { + outname = add_string(outname, prefix); + outname = add_string(outname, "."); + } + + do { + *outname = get_char(encnames, &bitnum); + outname++; + } while (outname[-1]); +} + +static int find_addr(const struct oopser_symtable *table, + unsigned long off, + char *outname) +{ + unsigned int i; + + for (i = 0; off > table->symsize[i]; i++) + off -= table->symsize[i]; + + decode_name(table->prefix, table->encnames, i, outname); + return (int)off; +} + +/* Return -ENOENT on out-of-range. Give a 64 bytes at least for outname. */ +int symbol_decode(unsigned long addr, char *outname) +{ + struct oopser_symtable *i; + + for (i = symtable; i; i = i->next) { + if (addr >= i->start_addr && addr <= i->end_addr) { + return find_addr(i, addr - i->start_addr, outname); + } + } + return -ENOENT; +} + +static int write_block(int (*write)(const char dump[512], unsigned int block), + unsigned int blocknum, const char *p) +{ + int ret; + + /* If we can, check there is currently a valid signature */ + if (oopser_block_read) { + static unsigned char oldcontents[512]; + oldcontents[0] = 0; + ret = oopser_block_read(oldcontents, blocknum); + if (ret < 0) + return ret; + for (ret = 0; ret < sizeof(LINUX_OOPSER_SIGNATURE)-1; ret++) + if (oldcontents[ret] != LINUX_OOPSER_SIGNATURE[ret]) + return -EBADF; + } + + return write(p, blocknum); +} + +static int dump_stage(int (*write)(const char dump[512], unsigned int block), + char *p, int size, unsigned int block) +{ + int ret = 0; + + while (block < LINUX_OOPSER_BLOCKS && size > 0) { + ret = write_block(write, block, p); + if (ret < 0) + return ret; + p += 512; + size -= 512; + block++; + } + return block; +} + +/* FIXME: Stolen from printk.c: find a better way */ +#if defined(CONFIG_MULTIQUAD) || defined(CONFIG_IA64) +#define LOG_BUF_LEN (65536) +#elif defined(CONFIG_ARCH_S390) +#define LOG_BUF_LEN (131072) +#elif defined(CONFIG_SMP) +#define LOG_BUF_LEN (32768) +#else +#define LOG_BUF_LEN (16384) +#endif +#define LOG_BUF(idx) (log_buf[(idx) % LOG_BUF_LEN]) + +static char *dump_log_buffer(char *p) +{ + extern char log_buf[]; + extern unsigned long log_end; + unsigned int i; + + /* FIXME: If < 1024 chars total in buffer. This will give NULs */ + for (i = 0; i < 1024; i++) + (*p++) = LOG_BUF(LOG_BUF_LEN + log_end - 1024 + i); + *p = '\0'; + return p; +} + +/* Called with interrupts off */ +int oopser_oops_handler(const char *str, struct pt_regs *regs, long err) +{ + char *p; + int blocks; + int (*write)(const char dump[512], unsigned int block); + + write = oopser_block_write; + oopser_block_write = NULL; + + if (!write) + return 0; + + /* We dump on stages, getting more ambitious as we go. */ + + /* Stage 0: we have an oops. */ + p = add_string(page, LINUX_OOPSER_SIGNATURE " -r\n"); + p = add_string(p, "OOPS="); p = add_string(p, str); + p = add_string(p, "\nERR="); p = add_int(p, err); + p = add_string(p, "\nVERSION="); p = add_string(p, kversion); + p = add_string(p, "\n"); + blocks = dump_stage(write, page, p - page, 0); + if (blocks < 0) + return blocks; + + /* Stage 1: we have some registers. */ + p = dump_registers(page, regs); + blocks = dump_stage(write, page, p - page, blocks); + if (blocks < 0) + return blocks; + + /* Stage 2: Process information */ + p = add_string(page, "CPU="); p = add_int(p,smp_processor_id()); + p = add_string(p, "\nT="); p = add_int(p, tainted); + p = add_string(p, "\nProcess="); p = add_string(p, current->comm); + p = add_string(p, "\nPID="); p = add_int(p, current->pid); + p = add_string(p, "\n"); + blocks = dump_stage(write, page, p - page, blocks); + if (blocks < 0) + return blocks; + + /* Stage 3: stack, backtrace, code. */ + p = dump_code_context(page, regs); + blocks = dump_stage(write, page, p - page, blocks); + if (blocks < 0) + return blocks; + + /* Stage 4: last 1k of printk ring buffer. */ + p = dump_log_buffer(page); + blocks = dump_stage(write, page, p - page, blocks); + if (blocks < 0) + return blocks; + + return 0; +} diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/dumper_x86_ide.c .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/dumper_x86_ide.c --- .29808-linux-2.5.69-bk13/drivers/char/oopser/dumper_x86_ide.c 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/dumper_x86_ide.c 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,134 @@ +/* This is the core dumper code: x86 IDE. It must *not* reference any + outside code. */ + +#include +#include +#include +#include +#include +#include "oopser_priv.h" + +static struct linux_oopser_ide ideinfo; + +/* Wait for at least N usecs (1 clock per cycle, 10GHz processor = 10000) */ +static inline void oopser_usec(unsigned int num_usec) +{ + unsigned int i; + for (i = 0; i < 10000 * num_usec; i++); +} + +/* Wait for up to one second. */ +static int wait_for(unsigned char flag) +{ + unsigned int i; + unsigned char b; + + /* Pause (at least 400ns) in case we just issued a command */ + oopser_usec(1); + for (i = 0; i < 1000; i++) { + b = inb(HD_STATUS); + if (!(b & BUSY_STAT)) { + if (b & ERR_STAT) return 0; + if (b & flag) return 1; + } + oopser_usec(1000); + } + return 0; +} + +static int wait_before_command(void) +{ + return wait_for(READY_STAT); +} + +static int wait_before_data(void) +{ + return wait_for(DRQ_STAT); +} + +static int read_one_sector(int master, unsigned int lba, + unsigned char sect[512]) +{ + /* Bits 24-27, 0x40=LBA, 0x10=slave */ + if (!wait_before_command()) return -EIO; + outb(0x40 | (master ? 0 : 0x10) | ((lba >> 24) & 0x0F), HD_CURRENT); + + if (!wait_before_command()) return -EIO; + outb(1, HD_NSECTOR); /* One sector */ + /* Bits 0-7 of LBA address */ + outb(lba & 0xFF, HD_SECTOR); + /* Bits 8-15 */ + outb((lba >> 8) & 0xFF, HD_LCYL); + /* Bits 16-23 */ + outb((lba >> 16) & 0xFF, HD_HCYL); + /* READ SECTOR (with retries) */ + outb(0x20, HD_COMMAND); + + /* Read one sector (256 words) */ + if (!wait_before_data()) return -EIO; + insw(HD_DATA, sect, 256); + +#if 0 + printk("Contents of sector %u\n", lba); + printk("SECTOR: %02X %02X %02X %02X %02X %02X %02X %02X ...\n", + sect[0], sect[1], sect[2], sect[3], + sect[4], sect[5], sect[6], sect[7]); + printk("SECTOR: ... %02X %02X %02X %02X %02X %02X %02X %02X\n", + sect[504], sect[505], sect[506], sect[507], + sect[508], sect[509], sect[510], sect[511]); +#endif + return 0; +} + +static int write_one_sector(int master, unsigned int lba, + const unsigned char sect[512]) +{ + /* Bits 24-27, 0x40=LBA, 0x10=slave */ + if (!wait_before_command()) return -EIO; + outb(0x40 | (master ? 0 : 0x10) | ((lba >> 24) & 0x0F), HD_CURRENT); + + if (!wait_before_command()) return -EIO; + outb(1, HD_NSECTOR); /* One sector */ + /* Bits 0-7 of LBA address */ + outb(lba & 0xFF, HD_SECTOR); + /* Bits 8-15 */ + outb((lba >> 8) & 0xFF, HD_LCYL); + /* Bits 16-23 */ + outb((lba >> 16) & 0xFF, HD_HCYL); + /* WRITE SECTOR (with retries) */ + outb(0x30, HD_COMMAND); + + /* Write one sector (256 words) */ + if (!wait_before_data()) return -EIO; + outsw(HD_DATA, sect, 256); + return 0; +} + +int oopser_set_ide(const char *buffer, size_t len) +{ + if (len != sizeof(ideinfo)) + return -EINVAL; + memcpy(&ideinfo, buffer, sizeof(ideinfo)); + printk(KERN_INFO "Oopser set: %s %u...\n", + ideinfo.master ? "master" : "slave", + ideinfo.lba[0]); + return 0; +} + +/* Called with interrupts off */ +int oopser_read_ide(char dump[512], unsigned int block) +{ + /* Wait for non-busy: if not, reset anyway */ + wait_before_command(); + /* Soft reset of drive (set nIEN as well) */ + outb(0x0e, HD_CMD); + oopser_usec(1); /* 400ns according to spec */ + outb(0x0a, HD_CMD); + + return read_one_sector(ideinfo.master, ideinfo.lba[block], dump); +} + +int oopser_write_ide(const char dump[512], unsigned int block) +{ + return write_one_sector(ideinfo.master, ideinfo.lba[block], dump); +} diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/interface.c .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/interface.c --- .29808-linux-2.5.69-bk13/drivers/char/oopser/interface.c 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/interface.c 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,244 @@ +/* This is the only part of the in-kernel oops dumper which can call + into the rest of the kernel. */ +#include "oopser_priv.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OOPS_MAJOR 240 + +/* Protect open and close */ +static DECLARE_MUTEX(oopser_lock); +static unsigned int use_count = 0; + +struct oopser_symlist *oopser_symlist; +static int oopser_type = -1; + +static int oopser_open(struct inode *inode, struct file *filp) +{ + if (down_interruptible(&oopser_lock) != 0) + return -EINTR; + + if (use_count) { + up(&oopser_lock); + return -EBUSY; + } + + use_count++; + up(&oopser_lock); + return 0; +} + +static int oopser_close(struct inode *inode, struct file *filp) +{ + down(&oopser_lock); + use_count--; + up(&oopser_lock); + return 0; +} + +static unsigned long get_size(unsigned int num, unsigned short sizes[]) +{ + unsigned long size = 0; + unsigned int i; + + for (i = 0; i < num; i++) + size += sizes[i]; + + return size; +} + +/* Add symbols. */ +static int add_oopser_symbols(const char *ubuffer, size_t len) +{ + struct linux_oopser_symbols syms; + struct oopser_symtable *new; + size_t extra; + + if (len < sizeof(syms)) + return -EINVAL; + if (copy_from_user(&syms, ubuffer, sizeof(syms)) != 0) + return -EFAULT; + if (len < sizeof(syms) + sizeof(unsigned short)*syms.num_syms) + return -EINVAL; + /* Check against excesses before we kmalloc */ + if (syms.num_syms > 8192 || len > 120000) + return -ENOMEM; + + extra = len - sizeof(syms); + + new = kmalloc(sizeof(*new) + extra, GFP_KERNEL); + if (!new) return -ENOMEM; + + /* Copy the rest (sizes, prefix, then encoded names) */ + if (copy_from_user(&new->symsize, ubuffer + sizeof(syms), extra) != 0){ + kfree(new); + return -EFAULT; + } + + /* FIXME: Do verification --RR */ + new->next = symtable; + new->prefix = (char *)&new->symsize[syms.num_syms]; + new->start_addr = syms.base_address; + new->end_addr = syms.base_address + + get_size(syms.num_syms, new->symsize); + new->encnames = (unsigned char *)(new->prefix + strlen(new->prefix)+1); + new->num_syms = syms.num_syms; + printk(KERN_INFO "oopser: %u symbols for %s added\n", + new->num_syms, new->prefix); + + wmb(); /* Need this before setting symtable = new */ + symtable = new; + return 0; +} + +/* Arm the oopser. */ +static int oopser_arm(const char *ubuffer, size_t len) +{ + int ret = 0; + + if (len != sizeof(unsigned int)) return -EINVAL; + + if (oopser_block_write) ret = -EEXIST; + else { + switch (oopser_type) { + case LINUX_OOPSER_TYPE_IDE: + printk(KERN_INFO "Oopser armed: IDE\n"); + oopser_block_write = oopser_write_ide; + oopser_block_read = oopser_read_ide; + break; + default: + /* Oopser unset */ + ret = -EROFS; + } + } + + return ret; +} + +/* Disarm the oopser. */ +static int oopser_disarm(const char *ubuffer, size_t len) +{ + int ret = 0; + + if (len != sizeof(unsigned int)) return -EINVAL; + + if (!oopser_block_write) ret = -ENOENT; + else { + oopser_block_write = NULL; + oopser_block_read = NULL; + oopser_type = -1; + printk(KERN_INFO "Oopser disarmed\n"); + } + + return ret; +} + +/* Set the dump position. */ +static int oopser_set(const char *ubuffer, size_t len) +{ + char buffer[256]; + int ret = 0; + + /* Check it's not armed */ + if (oopser_block_write) return -EBUSY; + + /* Copy here, so dumper doesn't have to call copy_from_user */ + if (len < sizeof(unsigned int)*2) return -EINVAL; + if (len > sizeof(buffer)) return -ENOSPC; + if (copy_from_user(buffer, ubuffer, len) != 0) + return -EFAULT; + memcpy(&oopser_type, buffer + sizeof(unsigned long), sizeof(int)); + + switch (oopser_type) { + case LINUX_OOPSER_TYPE_IDE: + ret = oopser_set_ide(buffer, len); + break; + default: + printk(KERN_DEBUG "oopser: unknown set type %u\n", + oopser_type); + ret = -EINVAL; + break; + } + if (ret < 0) + oopser_type = -1; + return ret; +} + +static ssize_t oopser_write(struct file *filp, + const char *ubuffer, + size_t len, + loff_t *off) +{ + unsigned int command; + int ret; + + if (len < sizeof(unsigned int)) + return -EINVAL; + + if (get_user(command, (unsigned int *)ubuffer) != 0) + return -EFAULT; + + if (down_interruptible(&oopser_lock) != 0) + return -EINTR; + + switch (command) { + case LINUX_OOPSER_SYMBOLS: + ret = add_oopser_symbols(ubuffer, len); + break; + case LINUX_OOPSER_SET: + ret = oopser_set(ubuffer, len); + break; + case LINUX_OOPSER_ARM: + ret = oopser_arm(ubuffer, len); + break; + case LINUX_OOPSER_DISARM: + ret = oopser_disarm(ubuffer, len); + break; + case LINUX_OOPSER_TEST: + goto test; + default: + ret = -EINVAL; + break; + } + up(&oopser_lock); + return ret < 0 ? (ssize_t)ret : len; + + test: + use_count--; + up(&oopser_lock); + *((char *)0) = 0; +} + +static struct file_operations oopser_fops = { + open: oopser_open, + release: oopser_close, + write: oopser_write, +}; + +/* FIXME: write-protect the module pages */ +int __init init(void) +{ + oops_handler = oopser_oops_handler; + return register_chrdev(OOPS_MAJOR, "oopser", &oopser_fops); +} + +void __exit fini(void) +{ + oops_handler = NULL; + unregister_chrdev(OOPS_MAJOR, "oopser"); +} + +module_init(init); +module_exit(fini); +MODULE_LICENSE("GPL"); diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/oopser_priv.h .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/oopser_priv.h --- .29808-linux-2.5.69-bk13/drivers/char/oopser/oopser_priv.h 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/oopser_priv.h 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,106 @@ +#ifndef _OOPSER_PRIV_H +#define _OOPSER_PRIV_H +#include +#include +#include +#include + +struct oopser_symtable +{ + /* Next symbol table (eg. modules) */ + struct oopser_symtable *next; + + /* Prefix for this symbol, if any */ + char *prefix; + + /* Address range this symbol table covers. */ + unsigned long start_addr; + unsigned long end_addr; + + /* Pointer to encoded names */ + unsigned char *encnames; + + /* How many symbols are there? */ + unsigned short num_syms; + + /* The offsets of the symbols themselves */ + unsigned short symsize[0]; +}; + +extern struct oopser_symtable *symtable; + +/* Returns < 0 on not found, or offset inside function when found */ +int symbol_decode(unsigned long addr, char *outname); + +/* Set once we are armed. */ +extern int (*oopser_block_write)(const char dump[512], unsigned int block); +extern int (*oopser_block_read)(char dump[512], unsigned int block); +int oopser_oops_handler(const char *str, struct pt_regs *regs, long err); + +/* IDE routines */ +extern int oopser_write_ide(const char dump[512], unsigned int block); +extern int oopser_read_ide(char dump[512], unsigned int block); +extern int oopser_set_ide(const char *buffer, size_t len); + +/* Platform-specific routines (but please keep same field names!) */ +struct pt_regs; +extern char *dump_registers(char *p, struct pt_regs *regs); +extern char *dump_code_context(char *p, struct pt_regs *regs); + +/* String routines for parts which can't access sprintf */ +/* Poor man's string routines */ +static inline char *add_string(char *p, const char *string) +{ + unsigned int i = 0; + + do { + p[i] = string[i]; + } while (string[i++]); + + return p+i-1; +} + +static inline char tohex(unsigned char num) +{ + if (num < 10) return '0' + num; + else return 'a' + num - 10; +} + +static inline char *add_hexchar(char *p, unsigned char num) +{ + char digits[3]; + + digits[0] = tohex(num >> 4); + digits[1] = tohex(num & 0xF); + digits[2] = 0; + return add_string(p, digits); +} + +static inline char *add_hexlong(char *p, unsigned long num) +{ + int i; + + p = add_string(p, "0x"); + for (i = BITS_PER_LONG - 8; i >= 0; i -= 8) + p = add_hexchar(p, num >> i); + return p; +} + +static inline char *add_int(char *p, unsigned int i) +{ + int started = 0; + unsigned int mag; + + for (mag = 1000000000; mag; mag /= 10) { + if (i/mag) { + *(p++) = tohex(i/mag); + i %= mag; + started = 1; + } else if (started || mag == 1) { + *(p++) = '0'; + } + } + *p = '\0'; + return p; +} +#endif /* _OOPSER_PRIV_H */ diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/drivers/char/oopser/regs_x86.c .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/regs_x86.c --- .29808-linux-2.5.69-bk13/drivers/char/oopser/regs_x86.c 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/drivers/char/oopser/regs_x86.c 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,119 @@ +/* x86-specific dumper code. It must *not* reference any outside + code. */ +#include +#include +#include +#include "oopser_priv.h" + +static char *dump_code(char *p, struct pt_regs *regs) +{ + unsigned int i; + unsigned char c; + + p = add_string(p, "CODE="); + if (regs->eip < PAGE_OFFSET) + p = add_string(p, "EIP-BAD"); + else { + for (i=0; i < 20; i++) { + if (__get_user(c, &((unsigned char*)regs->eip)[i])) { + p = add_string(p, "EIP-BAD"); + break; + } + p = add_hexchar(p, c); + p = add_string(p, " "); + } + } + return add_string(p, "\n"); +} + +static char *dump_backtrace(char *p, unsigned long *stack) +{ + unsigned long addr; + unsigned int i = 0; + + while (((long) stack & (THREAD_SIZE-1)) != 0 && i < 20) { + int where_in_fn; + char name[64]; + + addr = *stack++; + where_in_fn = symbol_decode(addr, name); + if (where_in_fn >= 0) { + p = add_string(p, "TRACE="); + p = add_hexlong(p, addr); + p = add_string(p, "("); + p = add_string(p, name); + p = add_string(p, "+"); + p = add_int(p, where_in_fn); + p = add_string(p, ")\n"); + i++; + } + } + return p; +} + +static char *dump_stack(char *p, unsigned long *stack) +{ + int i; + + p = add_string(p, "STACK="); + for (i=0; i < 24; i++) { + if (((long) stack & (THREAD_SIZE-1)) == 0) + break; + p = add_hexlong(p, *stack++); + p = add_string(p, " "); + } + return add_string(p, "\n"); +} + +/* This is the entry point */ +char *dump_registers(char *p, struct pt_regs *regs) +{ + unsigned long esp; + unsigned short ss; + + esp = (unsigned long) (®s->esp); + ss = __KERNEL_DS; + if (regs->xcs & 3) { + esp = regs->esp; + ss = regs->xss & 0xffff; + } + + /* Dump the regigsters (arch-specific). */ + p = add_string(p, "PC="); p = add_hexlong(p, regs->eip); + p = add_string(p, "\nREG eax="); p = add_hexlong(p, regs->eax); + p = add_string(p, "\nREG ebx="); p = add_hexlong(p, regs->ebx); + p = add_string(p, "\nREG ecx="); p = add_hexlong(p, regs->ecx); + p = add_string(p, "\nREG edx="); p = add_hexlong(p, regs->edx); + p = add_string(p, "\nREG esi="); p = add_hexlong(p, regs->esi); + p = add_string(p, "\nREG edi="); p = add_hexlong(p, regs->edi); + p = add_string(p, "\nREG ebp="); p = add_hexlong(p, regs->ebp); + p = add_string(p, "\nREG esp="); p = add_hexlong(p, esp); + p = add_string(p, "\nREG ds="); p = add_hexlong(p, regs->xds & 0xffff); + p = add_string(p, "\nREG es="); p = add_hexlong(p, regs->xes & 0xffff); + p = add_string(p, "\nREG ss="); p = add_hexlong(p, ss); + p = add_string(p, "\nREG xcs="); p = add_hexlong(p, regs->xcs & 0xffff); + + return add_string(p, "\n"); +} + +char *dump_code_context(char *p, struct pt_regs *regs) +{ + unsigned long esp, ss; + + esp = (unsigned long) (®s->esp); + if (regs->xcs & 3) { + esp = regs->esp; + ss = regs->xss & 0xffff; + } + + /* When in-kernel, we also print out the stack and code at the + * time of the fault.. */ + if (!(regs->xcs & 3)) { + /* Stephen Rothwell says: if this is wrong, don't try */ + if ((0xffff & regs->xcs) == __KERNEL_CS) + p = dump_code(p, regs); + p = dump_stack(p, (unsigned long*)esp); + p = dump_backtrace(p, (unsigned long*)esp); + } + return p; +} diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/include/linux/kernel.h .29808-linux-2.5.69-bk13.updated/include/linux/kernel.h --- .29808-linux-2.5.69-bk13/include/linux/kernel.h 2003-04-20 18:05:14.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/include/linux/kernel.h 2003-05-19 13:06:28.000000000 +1000 @@ -102,6 +102,8 @@ static inline void console_verbose(void) console_loglevel = 15; } +struct pt_regs; +extern int (*oops_handler)(const char *str, struct pt_regs *regs, long err); extern void bust_spinlocks(int yes); extern int oops_in_progress; /* If set, an oops, panic(), BUG() or die() is in progress */ extern int panic_on_oops; diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/include/linux/oopser.h .29808-linux-2.5.69-bk13.updated/include/linux/oopser.h --- .29808-linux-2.5.69-bk13/include/linux/oopser.h 1970-01-01 10:00:00.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/include/linux/oopser.h 2003-05-19 13:06:28.000000000 +1000 @@ -0,0 +1,55 @@ +#ifndef _LINUX_OOPSER_H +#define _LINUX_OOPSER_H + +#define LINUX_OOPSER_SIGNATURE "#! /sbin/oopsd " +#define LINUX_OOPSER_DUMP_SIZE 4096 +#define LINUX_OOPSER_BLOCKS (LINUX_OOPSER_DUMP_SIZE/512) + +/* Each command consists of int, then data, in single write. */ +enum linux_oopser_command +{ + /* Followed by num syms, base address, 16-bit sizes, + NUL-terminated module name ("" for kernel), then Huffman + encoded names. */ + LINUX_OOPSER_SYMBOLS, + /* Followed by type, then struct oopser_ide etc. */ + LINUX_OOPSER_SET, + /* Arm the device */ + LINUX_OOPSER_ARM, + /* Disarm the device */ + LINUX_OOPSER_DISARM, + /* Test (causes oops, kills process) */ + LINUX_OOPSER_TEST, +}; + +enum linux_oopser_type +{ + LINUX_OOPSER_TYPE_IDE, +}; + +struct linux_oopser_ide +{ + unsigned int command; /* == LINUX_OOPSER_SET */ + unsigned int type; /* == LINUX_OOPSER_TYPE_IDE */ + + /* Master or slave? */ + int master; + + /* Absolute block addresses. */ + unsigned int lba[LINUX_OOPSER_BLOCKS]; +}; + +struct linux_oopser_symbols +{ + unsigned int command; /* == LINUX_OOPSER_SYMBOLS */ + + unsigned int num_syms; + /* This is the kernel's idea of long, not yours */ + unsigned long base_address; + /* Size of each function: num_syms long. */ + unsigned short size[0]; + + /* char module_name[]; */ + /* unsigned char huffnames[]; */ +}; +#endif /*_LINUX_OOPSER_H*/ diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/kernel/ksyms.c .29808-linux-2.5.69-bk13.updated/kernel/ksyms.c --- .29808-linux-2.5.69-bk13/kernel/ksyms.c 2003-05-19 10:53:51.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/kernel/ksyms.c 2003-05-19 13:07:01.000000000 +1000 @@ -600,6 +600,7 @@ EXPORT_SYMBOL(__per_cpu_offset); /* debug */ EXPORT_SYMBOL(dump_stack); +EXPORT_SYMBOL_GPL(tainted); EXPORT_SYMBOL(ptrace_notify); EXPORT_SYMBOL(current_kernel_time); diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .29808-linux-2.5.69-bk13/kernel/printk.c .29808-linux-2.5.69-bk13.updated/kernel/printk.c --- .29808-linux-2.5.69-bk13/kernel/printk.c 2003-05-05 12:37:13.000000000 +1000 +++ .29808-linux-2.5.69-bk13.updated/kernel/printk.c 2003-05-19 13:06:28.000000000 +1000 @@ -68,7 +68,7 @@ struct console *console_drivers; */ static spinlock_t logbuf_lock = SPIN_LOCK_UNLOCKED; -static char log_buf[LOG_BUF_LEN]; +char log_buf[LOG_BUF_LEN]; #define LOG_BUF(idx) (log_buf[(idx) & LOG_BUF_MASK]) /* @@ -77,7 +77,7 @@ static char log_buf[LOG_BUF_LEN]; */ static unsigned long log_start; /* Index into log_buf: next char to be read by syslog() */ static unsigned long con_start; /* Index into log_buf: next char to be sent to consoles */ -static unsigned long log_end; /* Index into log_buf: most-recently-written-char + 1 */ +unsigned long log_end; /* Index into log_buf: most-recently-written-char + 1 */ static unsigned long logged_chars; /* Number of chars produced since last read+clear operation */ struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES]; @@ -678,3 +678,6 @@ void tty_write_message(struct tty_struct tty->driver->write(tty, 0, msg, strlen(msg)); return; } + +EXPORT_SYMBOL_GPL(log_buf); +EXPORT_SYMBOL_GPL(log_end);