一、问题
在dmesg或messages中常见BUG_ON的相关打印,如:
------------[ cut here ]------------
kernel BUG at ...
也常见其它的异常打印,比如page_fault相关的,softlockup相关的,有时候不太好区分它们之间的差别,但区分它们却是否重要,直接关系着对问题本质的判断。
这里简单分析了一下BUG_ON在3.10 kernel代码中的实现。
二、基本原理
BUG_ON通过BUG宏实现。BUG最终是通过执行ud2汇编指令实现。ud2指令看起来有点陌生,大概就是undefine的意思,是一种让CPU产生invalid opcode异常的软件指令,此时会有相应的异常事件上报,内核捕获相应的异常,由预先注册的异常处理接口进行处理:打印相关错误信息,最终根据配置进行kdump或panic或停止当前进程。
三、代码分析
1、BUG_ON宏定义
BUG_ON宏定义,判断condition是否成立,成立则调用BUG():
-
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); }
BUG()宏定义,本质是调用ud2汇编指令
-
#define BUG() \
-
do { \
-
asm volatile("1:\tud2\n" \
-
".pushsection __bug_table,\"a\"\n" \
-
__BUG_C0 \
-
"\t.word %c1, 0\n" \
-
"\t.org 2b+%c2\n" \
-
".popsection" \
-
: : "i" (__FILE__), "i" (__LINE__), \
-
"i" (sizeof(struct bug_entry))); \
-
unreachable(); \
-
} while (0)
其中,'c' 在gcc中, 叫做operand code, 用在常量变量(constraint表示'i')和条件判断指令中. 作用是将这个常量值打印在指令中,对于常量如果不用‘c’,上述会出问题,不能正常运行。
其他都是assembler directive,主要目的是将bug相关的信息,比如文件名、行号等保存到预先定义好的bug_table中。
2、invalid opcode异常初始化流程
start_kernel
->trap_init
->set_intr_gate(X86_TRAP_UD, invalid_op);
invalid_op由汇编实现,代码在entry_32.S中,最终调用do_invalid_op
-
ENTRY(invalid_op)
-
RING0_INT_FRAME
-
ASM_CLAC
-
pushl_cfi $0
-
pushl_cfi $do_invalid_op
-
jmp error_code
-
CFI_ENDPROC
-
END(invalid_op)
do_invalid_op的实现代码中不好找,主要是因为其不是直接实现的,而是通过宏实现,关键字不好搜。内核中这种实现方式还比较多,比如page的几个flag的判断接口。
-
DO_ERROR_INFO(X86_TRAP_UD, SIGILL, "invalid opcode", invalid_op, ILL_ILLOPN,
-
regs->ip)
DO_ERROR_INFO宏实现:
-
#define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \
-
dotraplinkage void do_##name(struct pt_regs *regs, long error_code) \
-
{ \
-
siginfo_t info; \
-
enum ctx_state prev_state; \
-
\
-
info.si_signo = signr; \
-
info.si_errno = 0; \
-
info.si_code = sicode; \
-
info.si_addr = (void __user *)siaddr; \
-
prev_state = exception_enter(); \
-
if (notify_die(DIE_TRAP, str, regs, error_code, \
-
trapnr, signr) == NOTIFY_STOP) { \
-
exception_exit(prev_state); \
-
return; \
-
} \
-
conditional_sti(regs); \
-
do_trap(trapnr, signr, str, regs, error_code, &info); \
-
exception_exit(prev_state); \
-
}
3、
invalid opcode异常处理流程
do_trap流程:
do_trap()
->do_trap_no_signal()
->die()
->report_bug()
->__die()
do_trap()->do_trap_no_signal():
-
static int __kprobes
-
do_trap_no_signal(struct task_struct *tsk, int trapnr, char *str,
-
struct pt_regs *regs, long error_code)
-
{
-
#ifdef CONFIG_X86_32
-
/*判断是否有VM86标记,如果有的话,则进行相关处理。*/
-
if (regs->flags & X86_VM_MASK) {
-
/*
-
* Traps 0, 1, 3, 4, and 5 should be forwarded to vm86.
-
* On nmi (interrupt 2), do_trap should not be called.
-
*/
-
/*当异常号小于6时(不包括2(NMI),NMI不会进入到do_trap流程),进入vm86处理。*/
-
if (trapnr < X86_TRAP_UD) {
-
if (!handle_vm86_trap((struct kernel_vm86_regs *) regs,
-
error_code, trapnr))
-
return 0;
-
}
-
return -1;
-
}
-
#endif
-
/*是否发生异常时处于内核态?*/
-
if (!user_mode(regs)) {
-
/*查找fixup表,看是否有预定义好的修正处理,有的话就进行相关处理*/
-
if (!fixup_exception(regs)) {
-
/*设置错误码和异常号到任务描述符中*/
-
tsk->thread.error_code = error_code;
-
tsk->thread.trap_nr = trapnr;
-
/*调用die,进入"死机"流程*/
-
die(str, regs, error_code);
-
}
-
return 0;
-
}
-
-
return -1;
-
}
do_trap()->do_trap_no_signal()->die():
-
/*
-
* This is gone through when something in the kernel has done something bad
-
* and is about to be terminated:
-
*/
-
/*内核出问题了,进入终止流程*/
-
void die(const char *str, struct pt_regs *regs, long err)
-
{
-
/*oops前的相关处理,包括关闭相关trace,获取die相关的锁等(防止死锁)*/
-
unsigned long flags = oops_begin();
-
int sig = SIGSEGV;
-
/*如果是内核态触发,则应该是内核bug了,需要打印相关提示*/
-
if (!user_mode_vm(regs))
-
/*报告bug,打印相关信息*/
-
report_bug(regs->ip, regs);
-
/*打印相关信息,包括EIP,堆栈等。*/
-
if (__die(str, regs, err))
-
sig = 0;
-
/*是否die相关的锁,并根据情况进行kdump或panic*/
-
oops_end(flags, regs, sig);
-
}
do_trap()->do_trap_no_signal()->report_bug():
-
/*报告bug,打印相关信息*/
-
enum bug_trap_type report_bug(unsigned long bugaddr, struct pt_regs *regs)
-
{
-
const struct bug_entry *bug;
-
const char *file;
-
unsigned line, warning;
-
-
if (!is_valid_bugaddr(bugaddr))
-
return BUG_TRAP_TYPE_NONE;
-
/*通过出错的IP指针从"bug_table"中找到相关的错误信息,包括行号之类的*/
-
bug = find_bug(bugaddr);
-
-
file = NULL;
-
line = 0;
-
warning = 0;
-
-
if (bug) {
-
#ifdef CONFIG_DEBUG_BUGVERBOSE
-
#ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
-
file = bug->file;
-
#else
-
file = (const char *)bug + bug->file_disp;
-
#endif
-
line = bug->line;
-
#endif
-
warning = (bug->flags & BUGFLAG_WARNING) != 0;
-
}
-
-
if (warning) {
-
/* this is a WARN_ON rather than BUG/BUG_ON */
-
printk(KERN_WARNING "------------[ cut here ]------------\n");
-
-
if (file)
-
printk(KERN_WARNING "WARNING: at %s:%u\n",
-
file, line);
-
else
-
printk(KERN_WARNING "WARNING: at %p "
-
"[verbose debug info unavailable]\n",
-
(void *)bugaddr);
-
-
print_modules();
-
show_regs(regs);
-
print_oops_end_marker();
-
/* Just a warning, don't kill lockdep. */
-
add_taint(BUG_GET_TAINT(bug), LOCKDEP_STILL_OK);
-
return BUG_TRAP_TYPE_WARN;
-
}
-
-
printk(KERN_DEFAULT "------------[ cut here ]------------\n");
-
-
if (file)
-
/*打印bug提示,就是平常常见的打印了*/
-
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;
-
}
阅读(5387) | 评论(0) | 转发(0) |