1.概述
在Linux内核开发中,我们会经常遇到访问空指针导致内核Oops或panic。遇到这种问题,需要先定位出是哪一个函数、哪一个变量导致的异常。通常情况下,Linux内核会打印出异常时的栈、模块、CPU寄存器等信息,但某些情况下,栈被破环,只能输出寄存器信息(U-Boot发生异常时只会输出寄存器信息),那我们只能根据寄存器信息还原出发生异常时的现场。
2,实例
如下图所示,内核明确的出了异常原因-访问空指针,空指针解引用地址为0x00000003(若异常地址不为0,应该是访问了某个结构体中的变量,加上了偏移地址),发生异常的函数为__dwc3_gadget_ep_set_halt
,上一个调用函数为dwc3_ep0_stall_and_restart
。
pc寄存器保存了CPU执行的指令地址(0xffffff8008734ae4),可以根据该地址得到异常发生时调用的函数和访问的变量,lr保存了上一个调用函数的地址(0xffffff80087369e4)。下面使用gdb,还原发生异常时的现场。
- 首先启动aarch64-linux-gnu-gdb,使用file命令读取vmlinux镜像,获取Linux内核的所有符号。
- 使用list命令获取指令地址附件的符号。
如下图所示,0xffffff8008734ae4地址在__dwc3_gadget_ep_set_halt
函数中,内部调用了usb_endpoint_xfer_isoc
内联函数,异常就发生在该内联函数中。
__dwc3_gadget_ep_set_halt
函数部分代码如下,三个参数分别使用x0、x1、x2寄存器传递,内部调用了usb_endpoint_xfer_isoc
内联函数。
[drivers/usb/dwc3/gadget.c]
int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol)
{
struct dwc3_gadget_ep_cmd_params params;
struct dwc3 *dwc = dep->dwc;
int ret;
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
dev_err(dwc->dev, "%s is of Isochronous type\n", dep->name);
return -EINVAL;
}
memset(¶ms, 0x00, sizeof(params));
......
return ret;
}
如下图所示,lr寄存器保存的地址指向了dwc3_ep0_stall_and_restart
函数,该函数内部调用了__dwc3_gadget_ep_set_halt
函数。
- 使用x命令获取异常指令附近的汇编代码。可以使用x/20i 0xffffff8008734ae4命令获取异常地址以后的20条汇编指令,也可以使用x/20i __dwc3_gadget_ep_set_halt命令直接获取发生异常的函数的汇编代码。上面已经明确了发生异常的函数,因此这里直接获取该函数的汇编代码,便于分析。
如下图红框3所示,异常地址0xffffff8008734ae4处是一条内存加载指令,将x0寄存器中保存的地址加2得到一个新的地址,再将该地址处的一个字节的数据保存到w0寄存器中。x0寄存器由红框2更新,其保存的数据来自于x19寄存器保存的地址加56。如红框1所示,x19寄存器的值又来自于x0,而x0是函数的第一个参数,即端点数据结构struct dwc3_ep
的地址,地址为x19: ffffffc0f0b5c400
。红框2和红框3都是对struct dwc3_ep
数据结构内部变量的访问,偏移值56和3是内部变量相对于结构体基地址的偏移。
- 上面我们已经明确了发生异常的函数和访问的数据结构,现在我们需要根据偏移值确定访问的具体变量。
使用ptype /o struct dwc3_ep命令获取struct dwc3_ep
数据结构各个变量的偏移值.如下图所示,偏移值为56的变量为struct usb_ep
结构体中的desc
变量,即端点描述符的地址。
使用ptype /o struct usb_endpoint_descriptor命令获取struct usb_endpoint_descriptor
数据结构各个变量的偏移值。如下图所示,偏移值为3的变量为bmAttributes
,即端点的属性。
从上面的分析中可以看出,红框3中[x0, #3]=0x00000003,x0中保存的地址为NULL,即指针变量desc
是一个空指针,空指针解引用时发生了异常,也即在内联函数usb_endpoint_xfer_isoc
中访问bmAttributes
变量导致了异常。后续我们只需要排查是什么原因导致了指针变量desc
变为空指针即可。