通过打印来调试
- 内核提供的打印函数printk()和C库提供的printf()函数功能几乎相同,printk()在任何地方都能调用它,既可以在进程上下文和中断上下文中被调用,也可以在持有锁时被调用,还可以在多处理器上同时被调用。
- printk()可以指定一个日志级别,内核根据这个级别来判断是否在终端上打印消息。内核级别最高位KERN_EMERG,最低位KERN_DEBUG,当调用printk()不指定日志等级时,默认的等级位KERN_WARNING。
- 内核消息都被保存在一个LOG_BUF_LEN大小的环形队列中。该缓冲区大小可以在编译时通过设置CONFIG_LOG_BUF_SHIFT进行调整。
- 在Linux系统上,用户空间的守护进程klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程,将它们保存在系统日志文件中。
OOPS
- oops是内核告知用户有异常发生的最常用的方式。内核不能采取像用户空间出现错误时使用的一些简单手段(例如进程强制退出),内核只能发布oops,这个过程包括向终端上输出错误消息,输出寄存器中保存的信息并输出可跟踪的回溯线索。
内核调试配置选项
- 在编译内核的时候,为了方便调试内核代码,内核提供了许多配置选项,它们都依赖于CONFIG_DEBUG_KERNEL。
- 常见的调试配置选项有slab layer debugging(slab层调试选项)、high-memory debugging(高端内存调试选项)、IO mapping debugging(IO映射调试选项)、spin-lock debugging(自旋锁调试选项)、stack-overflow checking(栈溢出检查选项)和sleep-inside-spinlock checking(自旋锁内睡眠选项)
引发BUG并打印信息
- BUG()和BUG_ON()当被调用时,会引发oops,使得系统打印栈的回溯信息。因此BUG()和BUG_ON()被内核当作断言使用,在不希望发生的条件发生时,便调用该断言。
- panic()可以引发更严重的错误,调用panic()不但会打印错误消息,而且会挂起整个系统。
- dump_stack()不会引发各种异常行为,只是在终端上打印一下栈的回溯信息来帮助调试。
内核调试器
- 可以使用标准的gdb对正在运行的内核进行查看,gdb的启动命令和调试进程的方法大致相同。gdb vmlinux /proc/kcore ,其中vmlinux为未经压缩的内核映像文件,/proc/kcore作为一个参数选项,是作为core文件来使用的,通过它可以访问到内核驻留的高端内存。
- gdb只能用来查看内核变量的值或者反汇编一个函数,它没法修改内核数据,也不能单步执行内核代码,不能加断点。
- kgdb是一个补丁,它可以让我们在远端主机上通过串口利用gdb的所有功能对目标计算机上的内核进行调试。