如何调试驱动开发过程中的Oops ?
本文是原「蜗窝讨论区」的历史存档(2016-08-30),来自版块「Linux kernel技术问答」,共 12 帖。讨论区已停止服务,此处仅供查阅。
驱动开发中经常遇到Oops问题,主要有如下两类情况: 1. Unable to handle kernel paging request at virtual address xxxxxxxx (非法内存访问) 2. Unable to handle kernel NULL pointer dereference at virtual address xxxxxxxx (空指针)
Oops可以看成是内核级的Segmentation Fault。应用程序如果进行了非法内存访问或执行了非法指令,会得到Segfault信号,一般的行为是coredump,应用程序也可以自己截获Segfault信号,自行处理。如果内核自己犯了这样的错误,则会打出Oops信息。
依据自己的经验也就知道如下两种调试方法: 1. objdump反编译(转: http://www.cnblogs.com/wwang/archive/2010/11/14/1876735.html) 2. printk的方法来调试Oops问题
曾经在面试的时候,有被问过如何调试Oops,也就回答了上述两种,但是面试官就问是否分析过堆栈内容,当时就蒙了,调试驱动从来没去分析过这部分.一般只看寄存器状态和函数调用栈.
调试的驱动不多,所以接触到的东西有限,还请大家看看是否有更好的方法来调试Oops,期待分享.
函数调用栈,不是反映了堆栈内容吗?
如果自己可以修改kernel,其实是可以把KALLSYMS打开,这样OOPS之后,连反汇编都不需要,直接可以打出C函数的栈回梭。
[== Undefined ==]
config KALLSYMS
bool "Load all symbols for debugging/ksymoops" if EXPERT
default y
help
Say Y here to let the kernel print out symbolic crash information and
symbolic stack backtraces. This increases the size of the kernel
somewhat, as all symbols have to be loaded into the kernel image.
config KALLSYMS_ALL
bool "Include all symbols in kallsyms"
depends on DEBUG_KERNEL && KALLSYMS
help
Normally kallsyms only contains the symbols of functions for nicer
OOPS messages and backtraces (i.e., symbols from the text and inittext
sections). This is sufficient for most cases. And only in very rare
cases (e.g., when a debugger is used) all symbols are required (e.g.,
names of variables from the data sections, etc).
This option makes sure that all symbols are loaded into the kernel
image (i.e., symbols from all sections) in cost of increased kernel
size (depending on the kernel configuration, it may be 300KiB or
something like this).
Say N unless you really need all symbols.
wowo 写道: 如果自己可以修改kernel,其实是可以把KALLSYMS打开,这样OOPS之后,连反汇编都不需要,直接可以打出C函数的栈回梭。
[== Undefined ==]
config KALLSYMS
bool "Load all symbols for debugging/ksymoops" if EXPERT
default y
help
Say Y here to let the kernel print out symbolic crash information and
symbolic stack backtraces. This increases the size of the kernel
somewhat, as all symbols have to be loaded into the kernel image.
config KALLSYMS_ALL
bool "Include all symbols in kallsyms"
depends on DEBUG_KERNEL && KALLSYMS
help
Normally kallsyms only contains the symbols of functions for nicer
OOPS messages and backtraces (i.e., symbols from the text and inittext
sections). This is sufficient for most cases. And only in very rare
cases (e.g., when a debugger is used) all symbols are required (e.g.,
names of variables from the data sections, etc).
This option makes sure that all symbols are loaded into the kernel
image (i.e., symbols from all sections) in cost of increased kernel
size (depending on the kernel configuration, it may be 300KiB or
something like this).
Say N unless you really need all symbols.
这个选项打开,确实可以将符号地址对应的符号名称显示出来(Eg: Call Trace),方便内核代码调试;当前函数调用栈(Call Trace)只能定位Oops出现在哪个函数中.但是Oops的具体位置,还是无从得知;能否通过分析函数堆栈(粗体标记)的相关信息来定位函数中Oops发生的具体位置? [== Undefined ==] [ 100.706083] Stack: [ 100.731783] f1b9ff88 c0101131 f82cf040 c076d240 fffffffc f82cf040 0072cff4 f82d2000 [ 100.759324] <0> fffffffc f82cf040 0072cff4 f1b9ffac c0182340 f19638f8 f137b340 f19638c0 [ 100.811396] <0> 00000004 09cc9018 09cc9018 00020000 f1b9e000 c01033ec 09cc9018 00015324 [ 100.891922] Call Trace: [ 100.916257] [<c0101131>] ? do_one_initcall+0x31/0x190 [ 100.943670] [<f82d2000>] ? hello_init+0x0/0x11 [ 100.970905] [<c0182340>] ? sys_init_module+0xb0/0x210 [ 100.995542] [<c01033ec>] ? syscall_call+0x7/0xb [ 101.024087] Code: <c7> 05 00 00 00 00 01 00 00 00 5d c3 00 00 00 00 00 00 00 00 00 00 [ 101.079592] EIP: [<f82d2005>] hello_init+0x5/0x11 SS:ESP 0068:f1
其实位置就在:do_one_initcall+0x31处,你去反汇编文件中,应该很容易找出来。 其实我觉得的这些oops的最大用处,就是告诉我们一个大致的位置,然后我们看看代码,应该就能猜出来了。
wowo 写道: 其实位置就在:do_one_initcall+0x31处,你去反汇编文件中,应该很容易找出来。 其实我觉得的这些oops的最大用处,就是告诉我们一个大致的位置,然后我们看看代码,应该就能猜出来了。
好的,谢谢指导
do_one_initcall+0x31 ,可以结合GNU的addr2line工具 结合vmlinux定位具体出问题代码行数,还原完整call stack 简单问题通常有了backtrace就可以定位-》解决,复杂问题就不行了. 如果更深入的问题就需要学习相关CPU架构的异常处理流程了(比如ARM异常处理流程)+memorydump离线分析了,此类问题对操作系统底层掌握要求很高,学习门槛也高.还是很有挑战的。
当你的BUG难以复现时,每一份异常LOG都弥足珍贵,需要详细研究。
通过OOPS里面的sp地址本身,你可以推算是否有stack overflow之类的明显异常。 再对栈进行回溯,可以反查出前几个函数调用时传递的参数,这对于问题定位会很有帮助。
想详细分析堆栈内容,可以搞搞 单片机 ,里面各种hard fault 情况下,各种栈溢出。裸机跑的,裸内存分析,很能提高能力。
peter 写道: 驱动开发中经常遇到Oops问题,主要有如下两类情况: 1. Unable to handle kernel paging request at virtual address xxxxxxxx (非法内存访问) 2. Unable to handle kernel NULL pointer dereference at virtual address xxxxxxxx (空指针)
Oops可以看成是内核级的Segmentation Fault。应用程序如果进行了非法内存访问或执行了非法指令,会得到Segfault信号,一般的行为是coredump,应用程序也可以自己截获Segfault信号,自行处理。如果内核自己犯了这样的错误,则会打出Oops信息。
依据自己的经验也就知道如下两种调试方法: 1. objdump反编译(转: http://www.cnblogs.com/wwang/archive/2010/11/14/1876735.html) 2. printk的方法来调试Oops问题
曾经在面试的时候,有被问过如何调试Oops,也就回答了上述两种,但是面试官就问是否分析过堆栈内容,当时就蒙了,调试驱动从来没去分析过这部分.一般只看寄存器状态和函数调用栈.
调试的驱动不多,所以接触到的东西有限,还请大家看看是否有更好的方法来调试Oops,期待分享.
victor.guo 写道: do_one_initcall+0x31 ,可以结合GNU的addr2line工具 结合vmlinux定位具体出问题代码行数,还原完整call stack 简单问题通常有了backtrace就可以定位-》解决,复杂问题就不行了. 如果更深入的问题就需要学习相关CPU架构的异常处理流程了(比如ARM异常处理流程)+memorydump离线分析了,此类问题对操作系统底层掌握要求很高,学习门槛也高.还是很有挑战的。
这种方法倒是没用过,学习了;但是objdump 也可以定位到具体的点
albuer 写道: 当你的BUG难以复现时,每一份异常LOG都弥足珍贵,需要详细研究。
通过OOPS里面的sp地址本身,你可以推算是否有stack overflow之类的明显异常。 再对栈进行回溯,可以反查出前几个函数调用时传递的参数,这对于问题定位会很有帮助。
你说的这点,估计是面试官想问我的,好高大上的东西
温柔海洋 写道: 想详细分析堆栈内容,可以搞搞 单片机 ,里面各种hard fault 情况下,各种栈溢出。裸机跑的,裸内存分析,很能提高能力。
peter 写道: 驱动开发中经常遇到Oops问题,主要有如下两类情况: 1. Unable to handle kernel paging request at virtual address xxxxxxxx (非法内存访问) 2. Unable to handle kernel NULL pointer dereference at virtual address xxxxxxxx (空指针)
Oops可以看成是内核级的Segmentation Fault。应用程序如果进行了非法内存访问或执行了非法指令,会得到Segfault信号,一般的行为是coredump,应用程序也可以自己截获Segfault信号,自行处理。如果内核自己犯了这样的错误,则会打出Oops信息。
依据自己的经验也就知道如下两种调试方法: 1. objdump反编译(转: http://www.cnblogs.com/wwang/archive/2010/11/14/1876735.html) 2. printk的方法来调试Oops问题
曾经在面试的时候,有被问过如何调试Oops,也就回答了上述两种,但是面试官就问是否分析过堆栈内容,当时就蒙了,调试驱动从来没去分析过这部分.一般只看寄存器状态和函数调用栈.
调试的驱动不多,所以接触到的东西有限,还请大家看看是否有更好的方法来调试Oops,期待分享.
只想针对具体问题来分析,很久没玩单片机了,我想基于SOC 应该也可以去分析吧
