mirror of
https://github.com/torvalds/linux.git
synced 2024-11-16 09:02:00 +00:00
7664c5a1da
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 - implement CONFIG_DEBUG_BUGVERBOSE consistently 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 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 information. If report_bug() determines that it wasn't a BUG which caused the trap, it returns BUG_TRAP_TYPE_NONE. Some architectures (powerpc) implement WARN using the same mechanism; if the illegal instruction was the result of a WARN, then report_bug(Q) returns CONFIG_DEBUG_BUGVERBOSE; otherwise it returns BUG_TRAP_TYPE_BUG. 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. Unsetting CONFIG_DEBUG_BUGVERBOSE will reduce the kernel size by some amount. At the very least, filename and line information will not be recorded for each but, but architectures may decide to store no extra information per BUG at all. Unfortunately, gcc doesn't have a general way to mark an asm() as noreturn, so architectures will generally have to include an infinite loop (or similar) in the BUG code, so that gcc knows execution won't continue beyond that point. gcc does have a __builtin_trap() operator which may be useful to achieve the same effect, unfortunately it cannot be used to actually implement the BUG itself, because there's no way to get the instruction's address for use in generating the __bug_table entry. [randy.dunlap@oracle.com: Handle BUG=n, GENERIC_BUG=n to prevent build errors] [bunk@stusta.de: include/linux/bug.h must always #include <linux/module.h] Signed-off-by: Jeremy Fitzhardinge <jeremy@goop.org> Cc: Andi Kleen <ak@muc.de> Cc: Hugh Dickens <hugh@veritas.com> Cc: Michael Ellerman <michael@ellerman.id.au> Cc: Paul Mackerras <paulus@samba.org> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Rusty Russell <rusty@rustcorp.com.au> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
164 lines
4.0 KiB
C
164 lines
4.0 KiB
C
/*
|
|
Generic support for BUG()
|
|
|
|
This respects the following config options:
|
|
|
|
CONFIG_BUG - emit BUG traps. Nothing happens without this.
|
|
CONFIG_GENERIC_BUG - enable this code.
|
|
CONFIG_DEBUG_BUGVERBOSE - emit full file+line information for each BUG
|
|
|
|
CONFIG_BUG and CONFIG_DEBUG_BUGVERBOSE are potentially user-settable
|
|
(though they're generally always on).
|
|
|
|
CONFIG_GENERIC_BUG is set by each architecture using this code.
|
|
|
|
To use this, your architecture must:
|
|
|
|
1. Set up the config options:
|
|
- Enable CONFIG_GENERIC_BUG if CONFIG_BUG
|
|
|
|
2. Implement BUG (and optionally BUG_ON, WARN, WARN_ON)
|
|
- Define HAVE_ARCH_BUG
|
|
- Implement BUG() to generate a faulting instruction
|
|
- NOTE: struct bug_entry does not have "file" or "line" entries
|
|
when CONFIG_DEBUG_BUGVERBOSE is not enabled, so you must generate
|
|
the values accordingly.
|
|
|
|
3. Implement the trap
|
|
- In the illegal instruction trap handler (typically), verify
|
|
that the fault was in kernel mode, and call report_bug()
|
|
- report_bug() will return whether it was a false alarm, a warning,
|
|
or an actual bug.
|
|
- You must implement the is_valid_bugaddr(bugaddr) callback which
|
|
returns true if the eip is a real kernel address, and it points
|
|
to the expected BUG trap instruction.
|
|
|
|
Jeremy Fitzhardinge <jeremy@goop.org> 2006
|
|
*/
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/bug.h>
|
|
|
|
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
|
|
|
|
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);
|
|
}
|
|
|
|
enum bug_trap_type report_bug(unsigned long bugaddr)
|
|
{
|
|
const struct bug_entry *bug;
|
|
const char *file;
|
|
unsigned line, warning;
|
|
|
|
if (!is_valid_bugaddr(bugaddr))
|
|
return BUG_TRAP_TYPE_NONE;
|
|
|
|
bug = find_bug(bugaddr);
|
|
|
|
printk(KERN_EMERG "------------[ cut here ]------------\n");
|
|
|
|
file = NULL;
|
|
line = 0;
|
|
warning = 0;
|
|
|
|
if (bug) {
|
|
#ifdef CONFIG_DEBUG_BUGVERBOSE
|
|
file = bug->file;
|
|
line = bug->line;
|
|
#endif
|
|
warning = (bug->flags & BUGFLAG_WARNING) != 0;
|
|
}
|
|
|
|
if (warning) {
|
|
/* this is a WARN_ON rather than BUG/BUG_ON */
|
|
if (file)
|
|
printk(KERN_ERR "Badness at %s:%u\n",
|
|
file, line);
|
|
else
|
|
printk(KERN_ERR "Badness at %p "
|
|
"[verbose debug info unavailable]\n",
|
|
(void *)bugaddr);
|
|
|
|
dump_stack();
|
|
return BUG_TRAP_TYPE_WARN;
|
|
}
|
|
|
|
if (file)
|
|
printk(KERN_CRIT "kernel BUG at %s:%u!\n",
|
|
file, line);
|
|
else
|
|
printk(KERN_CRIT "Kernel BUG at %p "
|
|
"[verbose debug info unavailable]\n",
|
|
(void *)bugaddr);
|
|
|
|
return BUG_TRAP_TYPE_BUG;
|
|
}
|