Linux驱动开发HTR3218项目BUG(二):memcpy导致的内核崩溃

文章描述了一个开发者在编写驱动程序时,由于memcpy误传值而非地址,导致内核崩溃的情况。通过分析内核崩溃信息和汇编代码,作者追踪到问题出在htr3218_write_regs函数中对buf参数的处理,最终解决了这个问题。
摘要由CSDN通过智能技术生成

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/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(又得重启一遍…)

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值