版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.youkuaiyun.com/xi_xix_i/article/details/134030023
一、memcpy导致内核崩溃问题
在项目中写了一个驱动程序,然后在insmod .ko的时候,导致内核崩溃了(真的很烦,然后还重启不了,只能断电重启),崩溃后报出的信息如下所示
根据pc和lr寄存器的值,发现程序运行到了htr3218_write_regs
的0x50处
首先根据这个信息,可以确定出错的位置是位于函数htr3218_write_regs()
中,现在贴上驱动中该函数的代码。
static s32 htr3218_write_regs(struct htr3218_i2c_device *dev, u8 reg,
u8* buf, int len)
{
int ret;
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = dev->client;
printk("in_write_regs, \r\n");
b[0] = reg;
memcpy(&b[1], buf, len);
msg.addr = client->addr; /* htr3218 地址 */
msg.flags = 0; /* 0为发送数据 */
msg.buf = &b[0];
msg.len = len + 1;
// msg.len = 1;
ret = i2c_transfer(client->adapter, &msg, 1);
printk("transfer ret: %d\r\n", ret);
printk("device addr: 0x%x, reg: 0x%x ,val: 0x%x\r\n", client->addr, reg, buf[0]);
// ret = i2c_smbus_write_byte_data(client, reg, reg_data);
// printk("transfer ret: %d\r\n", ret);
return ret;
}
如果代码不长的话,可以在每一行代码后面打印信息来判断出错函数在哪里,但是比较长的函数代码这个方法就行不通了。当然这个代码也不长,但是本着学习的态度,还是通过其他方法来找到出错代码。因为是在海思平台上写的驱动,所以通过海思的工具链来反汇编一下.o文件,使用如下命令把反汇编之后的汇编代码输出到txt文件中:
/opt/linux/x86-arm/aarch64-mix210-linux/bin/aarch64-mix210-linux-objdump -D htrtest.o > htrtest_dump.txt
得到汇编代码,把该函数的汇编代码贴上来:
0000000000000028 <htr3218_write_regs.constprop.1>:
28: a9aa7bfd stp x29, x30, [sp, #-352]!
2c: 90000002 adrp x2, 0 <htr3218_open_ops>
30: 91000042 add x2, x2, #0x0
34: 910003fd mov x29, sp
38: a9025bf5 stp x21, x22, [sp, #32]
3c: 12001c16 and w22, w0, #0xff
40: a90153f3 stp x19, x20, [sp, #16]
44: 90000013 adrp x19, 0 <__stack_chk_guard>
48: f9001bf7 str x23, [sp, #48]
4c: 91000273 add x19, x19, #0x0
50: aa0103f7 mov x23, x1
54: f9400260 ldr x0, [x19]
58: f900afa0 str x0, [x29, #344]
5c: d2800000 mov x0, #0x0 // #0
60: f840c054 ldur x20, [x2, #12]
64: 90000000 adrp x0, 0 <htr3218_open_ops>
68: 91000000 add x0, x0, #0x0
6c: 94000000 bl 0 <printk>
70: 390163b6 strb w22, [x29, #88]
74: 52800022 mov w2, #0x1 // #1
78: 394002e4 ldrb w4, [x23]
7c: 910123a1 add x1, x29, #0x48
80: 79400683 ldrh w3, [x20, #2]
84: f9400e80 ldr x0, [x20, #24]
88: 390167a4 strb w4, [x29, #89]
8c: 910163a4 add x4, x29, #0x58
90: 790093a3 strh w3, [x29, #72]
94: 52a00043 mov w3, #0x20000 // #131072
98: f9002ba4 str x4, [x29, #80]
9c: b804a3a3 stur w3, [x29, #74]
a0: 94000000 bl 0 <i2c_transfer>
a4: 2a0003e1 mov w1, w0
a8: 2a0003f5 mov w21, w0
ac: 90000000 adrp x0, 0 <htr3218_open_ops>
b0: 91000000 add x0, x0, #0x0
b4: 94000000 bl 0 <printk>
b8: 79400681 ldrh w1, [x20, #2]
bc: 2a1603e2 mov w2, w22
c0: 394002e3 ldrb w3, [x23]
c4: 90000000 adrp x0, 0 <htr3218_open_ops>
c8: 91000000 add x0, x0, #0x0
cc: 94000000 bl 0 <printk>
d0: f940afa2 ldr x2, [x29, #344]
d4: f9400261 ldr x1, [x19]
d8: ca010041 eor x1, x2, x1
dc: b50000e1 cbnz x1, f8 <htr3218_write_regs.constprop.1+0xd0>
e0: 2a1503e0 mov w0, w21
e4: f9401bf7 ldr x23, [sp, #48]
e8: a94153f3 ldp x19, x20, [sp, #16]
ec: a9425bf5 ldp x21, x22, [sp, #32]
f0: a8d67bfd ldp x29, x30, [sp], #352
f4: d65f03c0 ret
f8: 94000000 bl 0 <__stack_chk_fail>
fc: d503201f nop
根据内核崩溃时提示的信息,找到htr3218_write_regs.constprop.1+0x50
处的指令(实际指令地址为函数地址0x28+0x50=0x78),即出错时cpu执行的指令。可以看到,此处指令为78: 394002e4 ldrb w4, [x23]
,将存储器地址为x23
的字节数据读入寄存器w4
,并将w4
的高24位清零(海思平台是64位的arm,但根据这篇文章的介绍,64位通用寄存器x0-x30
当做32位来用的时候就是w0-w30
,更具体的细节没有深究,只是开始疑惑了一下为什么明明是64位寄存器LDRB指令却只将高24位清0,后续可以看看)。
然后继续找函数中对寄存器x23
的操作,发现地址为0x50以及0x48的指令对寄存器x23
进行了操作。0x48的指令只是把x23
的值入栈了,所以重点放在0x50这条指令mov x23, x1
上。
但是在htr3218_write_regs
这个函数中没有对x1
寄存器的操作,而且x0
开始的前几个寄存器往往是调用函数的时候传参用的,所以这应该传入的某个参数。然后在汇编代码中找到调用函数htr3218_write_regs()
的位置(在驱动程序中是htr3218_module_init()
函数下调用的,就不贴上全部C语言代码了),发现指令1a4: d2800001 mov x1, #0x0
,所以是这个参数,导致了htr3218_write_regs
中指令的出错。并且知道了这个参数传入的值为0x0
。
0000000000000100 <htr3218_module_init>:
100: a9bb7bfd stp x29, x30, [sp, #-80]!
104: 910003fd mov x29, sp
108: a90153f3 stp x19, x20, [sp, #16]
10c: 90000014 adrp x20, 0 <__stack_chk_guard>
...
18c: 90000000 adrp x0, 0 <htr3218_open_ops>
190: 91000000 add x0, x0, #0x0
194: 52800038 mov w24, #0x1 // #1
198: 910002d6 add x22, x22, #0x0
19c: 910142d6 add x22, x22, #0x50
1a0: 94000000 bl 0 <printk>
1a4: d2800001 mov x1, #0x0 // #0
1a8: 528009e0 mov w0, #0x4f // #79
1ac: 97ffff9f bl 28 <htr3218_write_regs.constprop.1>
...
2e0: 17ffffa9 b 184 <htr3218_module_init+0x84>
2e4: d503201f nop
对照着下面驱动程序中的的部分init函数来看,发现在调用htr3218_write_regs()
函数时确实传入了0x0
。
int htr3218_module_init(void) /* modprobe .ko的时候执行*/
{
int ret = 0;
float a = 10*0.01;
u8 buf[1];
u8 buf_single = 0;
printk("in module_init!\r\n");
....
/* *********************************************i2c设备相关的初始化****************************************** */
htr3218_data.device.max_channel = HTR3218_USED_CHANNEL_NUM;
htr3218_data.bus_adapter = NULL;
ret = htr3218_i2c_init(&htr3218_data); /* 这个函数给bus_adpater和device.client初始化 */
if (!ret)
{
printk("Init htr3218 i2c client fail %d\n", ret);
goto i2c_error;
}
printk("device addr:0x%x \r\n", htr3218_data.device.client->addr);
htr3218_write_regs(&htr3218_data.device, 0x4f, 0x0, 1);
....
}
而根据该函数的定义htr3218_write_regs(struct htr3218_i2c_device *dev, u8 reg, u8* buf, int len)
来看,第三个参数为u8* buf,在驱动程序中使用buf这个参数的代码为
memcpy(&b[1], buf, len);
所以问题应该是出在这里,其实这时候已经发现问题了,调用htr3218_write_regs
应该传入一个地址,然后在该函数中在把地址中的值通过memcpy
取出来,我调用htr3218_write_regs
的时候迷糊了,直接把值传进去了,导致memcpy
要去找0x0
这个地址的值,这肯定是个非法地址,所以导致内核的崩溃。其实内核崩溃信息的第一行也说了:
uable to handle kernel NULL pointer dereference at virtual address 000000000000000
但是为了严谨,还是加上打印信息,判断一下是否是这行代码的问题:
printk("********************befor_memcpy************************ \r\n");
memcpy(&b[1], buf, len);
printk("********************after_memcpy************************ \r\n");
然后再编译,insmod一次驱动程序,确实是打印了before_memcpy,没有打印after_memcpy(又得重启一遍…)