一、问题

在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():


点击(此处)折叠或打开

  1. #define BUG_ON(condition) do { if (unlikely(condition)) BUG(); }


BUG()宏定义,本质是调用ud2汇编指令


点击(此处)折叠或打开

  1. #define BUG()    \
  2. do {    \
  3. asm volatile("1:\tud2\n"    \
  4. ".pushsection __bug_table,\"a\"\n"    \
  5. __BUG_C0    \
  6. "\t.word %c1, 0\n"    \
  7. "\t.org 2b+%c2\n"    \
  8. ".popsection"    \
  9. : : "i" (__FILE__), "i" (__LINE__),    \
  10. "i" (sizeof(struct bug_entry)));    \
  11. unreachable();    \
  12. } 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


点击(此处)折叠或打开

  1. ENTRY(invalid_op)
  2. RING0_INT_FRAME
  3. ASM_CLAC
  4. pushl_cfi $0
  5. pushl_cfi $do_invalid_op
  6. jmp error_code
  7. CFI_ENDPROC
  8. END(invalid_op)


do_invalid_op的实现代码中不好找,主要是因为其不是直接实现的,而是通过宏实现,关键字不好搜。内核中这种实现方式还比较多,比如page的几个flag的判断接口。

点击(此处)折叠或打开

  1. DO_ERROR_INFO(X86_TRAP_UD, SIGILL, "invalid opcode", invalid_op, ILL_ILLOPN,
  2. regs->ip)


DO_ERROR_INFO宏实现:

点击(此处)折叠或打开

  1. #define DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr)        \
  2. dotraplinkage void do_##name(struct pt_regs *regs, long error_code)    \
  3. {                                    \
  4. siginfo_t info;                            \
  5. enum ctx_state prev_state;                    \
  6. \
  7. info.si_signo = signr;                        \
  8. info.si_errno = 0;                        \
  9. info.si_code = sicode;                        \
  10. info.si_addr = (void __user *)siaddr;                \
  11. prev_state = exception_enter();                    \
  12. if (notify_die(DIE_TRAP, str, regs, error_code,            \
  13. trapnr, signr) == NOTIFY_STOP) {        \
  14. exception_exit(prev_state);                \
  15. return;                            \
  16. }                                \
  17. conditional_sti(regs);                        \
  18. do_trap(trapnr, signr, str, regs, error_code, &info);        \
  19. exception_exit(prev_state);                    \
  20. }

3、invalid opcode异常处理流程

do_trap流程:

do_trap()

    ->do_trap_no_signal()

        ->die()

            ->report_bug()

            ->__die()


do_trap()->do_trap_no_signal():


点击(此处)折叠或打开

  1. static int __kprobes
  2. do_trap_no_signal(struct task_struct *tsk, int trapnr, char *str,
  3. struct pt_regs *regs,    long error_code)
  4. {
  5. #ifdef CONFIG_X86_32
  6. /*判断是否有VM86标记,如果有的话,则进行相关处理。*/
  7. if (regs->flags & X86_VM_MASK) {
  8. /*
  9. * Traps 0, 1, 3, 4, and 5 should be forwarded to vm86.
  10. * On nmi (interrupt 2), do_trap should not be called.
  11. */
  12. /*当异常号小于6时(不包括2(NMI),NMI不会进入到do_trap流程),进入vm86处理。*/
  13. if (trapnr < X86_TRAP_UD) {
  14. if (!handle_vm86_trap((struct kernel_vm86_regs *) regs,
  15. error_code, trapnr))
  16. return 0;
  17. }
  18. return -1;
  19. }
  20. #endif
  21. /*是否发生异常时处于内核态?*/
  22. if (!user_mode(regs)) {
  23. /*查找fixup表,看是否有预定义好的修正处理,有的话就进行相关处理*/
  24. if (!fixup_exception(regs)) {
  25. /*设置错误码和异常号到任务描述符中*/
  26. tsk->thread.error_code = error_code;
  27. tsk->thread.trap_nr = trapnr;
  28. /*调用die,进入"死机"流程*/
  29. die(str, regs, error_code);
  30. }
  31. return 0;
  32. }
  33. return -1;
  34. }


do_trap()->do_trap_no_signal()->die():


点击(此处)折叠或打开

  1. /*
  2. * This is gone through when something in the kernel has done something bad
  3. * and is about to be terminated:
  4. */
  5. /*内核出问题了,进入终止流程*/
  6. void die(const char *str, struct pt_regs *regs, long err)
  7. {
  8. /*oops前的相关处理,包括关闭相关trace,获取die相关的锁等(防止死锁)*/
  9. unsigned long flags = oops_begin();
  10. int sig = SIGSEGV;
  11. /*如果是内核态触发,则应该是内核bug了,需要打印相关提示*/
  12. if (!user_mode_vm(regs))
  13. /*报告bug,打印相关信息*/
  14. report_bug(regs->ip, regs);
  15. /*打印相关信息,包括EIP,堆栈等。*/
  16. if (__die(str, regs, err))
  17. sig = 0;
  18. /*是否die相关的锁,并根据情况进行kdump或panic*/
  19. oops_end(flags, regs, sig);
  20. }


do_trap()->do_trap_no_signal()->report_bug():


点击(此处)折叠或打开

  1. /*报告bug,打印相关信息*/
  2. enum bug_trap_type report_bug(unsigned long bugaddr, struct pt_regs *regs)
  3. {
  4. const struct bug_entry *bug;
  5. const char *file;
  6. unsigned line, warning;
  7. if (!is_valid_bugaddr(bugaddr))
  8. return BUG_TRAP_TYPE_NONE;
  9. /*通过出错的IP指针从"bug_table"中找到相关的错误信息,包括行号之类的*/
  10. bug = find_bug(bugaddr);
  11. file = NULL;
  12. line = 0;
  13. warning = 0;
  14. if (bug) {
  15. #ifdef CONFIG_DEBUG_BUGVERBOSE
  16. #ifndef CONFIG_GENERIC_BUG_RELATIVE_POINTERS
  17. file = bug->file;
  18. #else
  19. file = (const char *)bug + bug->file_disp;
  20. #endif
  21. line = bug->line;
  22. #endif
  23. warning = (bug->flags & BUGFLAG_WARNING) != 0;
  24. }
  25. if (warning) {
  26. /* this is a WARN_ON rather than BUG/BUG_ON */
  27. printk(KERN_WARNING "------------[ cut here ]------------\n");
  28. if (file)
  29. printk(KERN_WARNING "WARNING: at %s:%u\n",
  30. file, line);
  31. else
  32. printk(KERN_WARNING "WARNING: at %p "
  33. "[verbose debug info unavailable]\n",
  34. (void *)bugaddr);
  35. print_modules();
  36. show_regs(regs);
  37. print_oops_end_marker();
  38. /* Just a warning, don't kill lockdep. */
  39. add_taint(BUG_GET_TAINT(bug), LOCKDEP_STILL_OK);
  40. return BUG_TRAP_TYPE_WARN;
  41. }
  42. printk(KERN_DEFAULT "------------[ cut here ]------------\n");
  43. if (file)
  44. /*打印bug提示,就是平常常见的打印了*/
  45. printk(KERN_CRIT "kernel BUG at %s:%u!\n",
  46. file, line);
  47. else
  48. printk(KERN_CRIT "Kernel BUG at %p "
  49. "[verbose debug info unavailable]\n",
  50. (void *)bugaddr);
  51. return BUG_TRAP_TYPE_BUG;
  52. }