From: Jeremy Fitzhardinge This patch adds common handling for kernel BUGs, for use by architectures as they wish. The code is derived from arch/powerpc. The advantages of having common BUG handling are: - consistent BUG reporting across architectures - shared implementation of out-of-line file/line data This means that in inline impact of BUG is just the illegal instruction itself, which is an improvement for i386 and x86-64. A BUG is represented in the instruction stream as an illegal instruction, which has file/line/function information associated with it. This extra information is stored in the __bug_table section in the ELF file. When the kernel gets an illegal instruction, it first confirms it might possibly be from a BUG (ie, in kernel mode, the right illegal instruction). It then calls report_bug(). This searches __bug_table for a matching instruction pointer, and if found, prints the corresponding file/line/function information. Some architectures (powerpc) implement WARN using the same mechanism; if the illegal instruction was the result of a WARN, then report_bug() returns 1; otherwise it returns 0. lib/bug.c keeps a list of loaded modules which can be searched for __bug_table entries. The architecture must call module_bug_finalize()/module_bug_cleanup() from its corresponding module_finalize/cleanup functions. This patch also converts i386, x86-64 and powerpc to use this infrastructure. I have only tested i386; x86-64 and powerpc are not even compile-tested in this patch. Because powerpc also records the function name, I added this to i386 and x86-64 for consistency. Strictly speaking the function name is redundant with kallsyms, so perhaps it can be dropped from powerpc. i386 implements CONFIG_DEBUG_BUGVERBOSE, but x86-64 and powerpc do not. This should probably be made more consistent. [akpm@osdl.org: Fix CONFIG_MODULES=n] Signed-off-by: Jeremy Fitzhardinge Cc: Andi Kleen Cc: Hugh Dickens Cc: Michael Ellerman Cc: Paul Mackerras Cc: Benjamin Herrenschmidt Cc: Rusty Russell Signed-off-by: Andrew Morton --- include/asm-generic/vmlinux.lds.h | 8 + include/linux/bug.h | 19 ++++ include/linux/module.h | 7 + lib/Makefile | 2 lib/bug.c | 118 ++++++++++++++++++++++++++++ 5 files changed, 154 insertions(+) diff -puN include/asm-generic/vmlinux.lds.h~generic-bug-handling include/asm-generic/vmlinux.lds.h --- a/include/asm-generic/vmlinux.lds.h~generic-bug-handling +++ a/include/asm-generic/vmlinux.lds.h @@ -195,5 +195,13 @@ .stab.indexstr 0 : { *(.stab.indexstr) } \ .comment 0 : { *(.comment) } +#define BUG_TABLE \ + . = ALIGN(8); \ + __bug_table : AT(ADDR(__bug_table) - LOAD_OFFSET) { \ + __start___bug_table = .; \ + *(__bug_table) \ + __stop___bug_table = .; \ + } + #define NOTES \ .notes : { *(.note.*) } :note diff -puN /dev/null include/linux/bug.h --- /dev/null +++ a/include/linux/bug.h @@ -0,0 +1,19 @@ +#ifndef _LINUX_BUG_H +#define _LINUX_BUG_H + +#include + +#ifdef CONFIG_GENERIC_BUG +#include + +int report_bug(unsigned long bug_addr); + +int module_bug_finalize(const Elf_Ehdr *, const Elf_Shdr *, + struct module *); +void module_bug_cleanup(struct module *); + +/* These are defined by the architecture */ +int is_valid_bugaddr(unsigned long addr); + +#endif /* CONFIG_GENERIC_BUG */ +#endif /* _LINUX_BUG_H */ diff -puN include/linux/module.h~generic-bug-handling include/linux/module.h --- a/include/linux/module.h~generic-bug-handling +++ a/include/linux/module.h @@ -322,6 +322,13 @@ struct module unsigned int taints; /* same bits as kernel:tainted */ +#ifdef CONFIG_GENERIC_BUG + /* Support for BUG */ + struct list_head bug_list; + struct bug_entry *bug_table; + unsigned num_bugs; +#endif + #ifdef CONFIG_MODULE_UNLOAD /* Reference counts */ struct module_ref ref[NR_CPUS]; diff -puN lib/Makefile~generic-bug-handling lib/Makefile --- a/lib/Makefile~generic-bug-handling +++ a/lib/Makefile @@ -55,6 +55,8 @@ obj-$(CONFIG_AUDIT_GENERIC) += audit.o obj-$(CONFIG_SWIOTLB) += swiotlb.o +lib-$(CONFIG_GENERIC_BUG) += bug.o + hostprogs-y := gen_crc32table clean-files := crc32table.h diff -puN /dev/null lib/bug.c --- /dev/null +++ a/lib/bug.c @@ -0,0 +1,118 @@ +#include +#include +#include + +extern const struct bug_entry __start___bug_table[], __stop___bug_table[]; + +#ifdef CONFIG_MODULES +static LIST_HEAD(module_bug_list); + +static const struct bug_entry *module_find_bug(unsigned long bugaddr) +{ + struct module *mod; + + list_for_each_entry(mod, &module_bug_list, bug_list) { + const struct bug_entry *bug = mod->bug_table; + unsigned i; + + for (i = 0; i < mod->num_bugs; ++i, ++bug) + if (bugaddr == bug->bug_addr) + return bug; + } + return NULL; +} + +int module_bug_finalize(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs, + struct module *mod) +{ + char *secstrings; + unsigned int i; + + mod->bug_table = NULL; + mod->num_bugs = 0; + + /* Find the __bug_table section, if present */ + secstrings = (char *)hdr + sechdrs[hdr->e_shstrndx].sh_offset; + for (i = 1; i < hdr->e_shnum; i++) { + if (strcmp(secstrings+sechdrs[i].sh_name, "__bug_table")) + continue; + mod->bug_table = (void *) sechdrs[i].sh_addr; + mod->num_bugs = sechdrs[i].sh_size / sizeof(struct bug_entry); + break; + } + + /* + * Strictly speaking this should have a spinlock to protect against + * traversals, but since we only traverse on BUG()s, a spinlock + * could potentially lead to deadlock and thus be counter-productive. + */ + list_add(&mod->bug_list, &module_bug_list); + + return 0; +} + +void module_bug_cleanup(struct module *mod) +{ + list_del(&mod->bug_list); +} + +#else + +static inline const struct bug_entry *module_find_bug(unsigned long bugaddr) +{ + return NULL; +} +#endif + +static const struct bug_entry *find_bug(unsigned long bugaddr) +{ + const struct bug_entry *bug; + + for (bug = __start___bug_table; bug < __stop___bug_table; ++bug) + if (bugaddr == bug->bug_addr) + return bug; + + return module_find_bug(bugaddr); +} + +int report_bug(unsigned long bugaddr) +{ + const struct bug_entry *bug; + const char *file, *function; + unsigned line; + + if (!is_valid_bugaddr(bugaddr)) + return 0; + + bug = find_bug(bugaddr); + if (!bug) + return 0; + + printk(KERN_EMERG "------------[ cut here ]------------\n"); + + file = bug_get_file(bug); + line = bug_get_line(bug); + function = bug_get_function(bug); + + if (is_warning_bug(bug)) { + /* this is a WARN_ON rather than BUG/BUG_ON */ + if (file) + printk(KERN_ERR "Badness in %s at %s:%u\n", + function, file, line); + else + printk(KERN_ERR "Badness at %p [verbose debug info unavailable]\n", + (void *)bugaddr); + + dump_stack(); + return 1; + } + + if (file) + printk(KERN_CRIT "kernel BUG in %s at %s:%u!\n", + function, file, line); + else + printk(KERN_EMERG "Kernel BUG at %p [verbose debug info unavailable]\n", + (void *)bugaddr); + + return 0; +} _