2025年Linux驱动调试之段错误分析_根据pc值确定出错的代码位置

Linux驱动调试之段错误分析_根据pc值确定出错的代码位置目录 1 修改驱动程序出现段错误 2 根据 PC 分析出错位置 2 1 驱动程序作为模块加载 2 1 1 根据 pc 值确定该指令属于内核还是外加的模块 2 1 2 假设它是加载的驱动程序引入的错误 怎么确定是哪一个驱动程序 2 1 3 找到了 first drv ko 2 2 驱动程序编入内核 2 2 1

大家好,我是讯享网,很高兴认识大家。

目录

1 修改驱动程序出现段错误

2 根据PC分析出错位置

2.1 驱动程序作为模块加载

2.1.1 根据pc值确定该指令属于内核还是外加的模块

2.1.2 假设它是加载的驱动程序引入的错误,怎么确定是哪一个驱动程序?

2.1.3 找到了first_drv.ko

2.2 驱动程序编入内核

2.2.1 根据pc值确定该指令属于内核还是外加的模块

2.2.2  反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis


1 修改驱动程序出现段错误

我们仍然使用之前的first_drv.c驱动程序来试验,我们故意把驱动程序改错,我们都知道在驱动程序中不能使用寄存器的物理地址,需要将寄存器的物理地址进行ioremap之后才能使用,这里我们故意使用物理地址,

static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpfcon = (volatile unsigned long *)0x; //(volatile unsigned long *)ioremap(0x, 16); gpfdat = gpfcon + 1; return 0; } 

讯享网

完整程序是

讯享网#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <linux/device.h> static struct class *firstdrv_class; static struct class_device *firstdrv_class_dev; volatile unsigned long *gpfcon = NULL; volatile unsigned long *gpfdat = NULL; static int first_drv_open(struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 配置GPF4,5,6为输出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; } static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk("first_drv_write\n"); copy_from_user(&val, buf, count); // copy_to_user(); if (val == 1) { // 点灯 *gpfdat &= ~((1<<4) | (1<<5) | (1<<6)); } else { // 灭灯 *gpfdat |= (1<<4) | (1<<5) | (1<<6); } return 0; } static struct file_operations first_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpfcon = (volatile unsigned long *)0x; //(volatile unsigned long *)ioremap(0x, 16); gpfdat = gpfcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); // 卸载 class_device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpfcon); } module_init(first_drv_init); module_exit(first_drv_exit); MODULE_LICENSE("GPL"); 

然后会出现段错误,根据内核打印出来的oops信息调试,
讯享网

Unable to handle kernel paging request at virtual address
内核使用来访问时发生了错误
pgd = c3eb0000
[] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: first_drv
CPU: 0    Not tainted  (2.6.22.6 #1)
PC is at first_drv_open+0x18(该指令的偏移)/0x3c(该函数的总大小) [first_drv]
PC就是发生错误的指令的地址
大多时候,PC值只会给出一个地址,不会指示说是在哪个函数里
LR is at chrdev_open+0x14c/0x164
LR寄存器的值
pc = 0xbf000018
pc : [<bf000018>]    lr : [<c008d888>]    psr: a0000013
sp : c3c7be88  ip : c3c7be98  fp : c3c7be94
r10: 00000000  r9 : c3c7a000  r8 : c049abc0
r7 : 00000000  r6 : 00000000  r5 : c3e740c0  r4 : c06d41e0
r3 : bf000000  r2 :  r1 : bf000964  r0 : 00000000
执行这条导致错误的指令时各个寄存器的值
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33eb0000  DAC: 00000015
Process firstdrvtest (pid: 777, stack limit = 0xc3c7a258)
发生错误时当前进程的名称是firstdrvtest


Stack: (0xc3c7be88 to 0xc3c7c000)
be80:                   c3c7bebc c3c7be98 c008d888 bf000010 00000000 c049abc0 
bea0: c3e740c0 c008d73c c0474e20 c3e766a8 c3c7bee4 c3c7bec0 c0089e48 c008d74c 
bec0: c049abc0 c3c7bf04 00000003 ffffff9c c002c044 c3d10000 c3c7befc c3c7bee8 
bee0: c0089f64 c0089d58 00000000 00000002 c3c7bf68 c3c7bf00 c0089fb8 c0089f40 
bf00: c3c7bf04 c3e766a8 c0474e20 00000000 00000000 c3eb1000 00000101 00000001 
bf20: 00000000 c3c7a000 c04a7468 c04a7460 ffffffe8 c3d10000 c3c7bf68 c3c7bf48 
bf40: c008a16c c009fc70 00000003 00000000 c049abc0 00000002 bec1fee0 c3c7bf94 
bf60: c3c7bf6c c008a2f4 c0089f88 00008520 bec1fed4 0000860c 00008670 00000005 
bf80: c002c044 c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000 c3c7bfa8 
bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0 00000001 
bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 c bec1fea8 
bfe0: 00000000 bec1fe84 0000266c 400c98e0 00008720 00000000 00000000 

Backtrace: (回溯)
[<bf000000>] (first_drv_open+0x0/0x3c [first_drv]) from [<c008d888>] (chrdev_open+0x14c/0x164)
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
[<c0089d48>] (__dentry_open+0x0/0x1e8) from [<c0089f64>] (nameidata_to_filp+0x34/0x48)
[<c0089f30>] (nameidata_to_filp+0x0/0x48) from [<c0089fb8>] (do_filp_open+0x40/0x48)
 r4:00000002
[<c0089f78>] (do_filp_open+0x0/0x48) from [<c008a2f4>] (do_sys_open+0x54/0xe4)
 r5:bec1fee0 r4:00000002
[<c008a2a0>] (do_sys_open+0x0/0xe4) from [<c008a3a8>] (sys_open+0x24/0x28)
[<c008a384>] (sys_open+0x0/0x28) from [<c002bea0>] (ret_fast_syscall+0x0/0x2c)
Code: e24cb004 e59f1024 e3a00000 e (e) 
Segmentation fault

这里的Backtrace只有配置了内核的CONFIG_FRAME_POINTER为yes才会有回溯信息

 如果我们没有配置这一项,那么我们就要自己分析栈,把调用信息分析出来。

2 根据PC分析出错位置

接下来我们看下能否根据PC值找到发生错误的代码在哪里,这个要分为两种情况。

2.1 驱动程序作为模块加载

2.1.1 根据pc值确定该指令属于内核还是外加的模块

我们make uImage将内核编译一下,会看到有个System.map文件,然后vi System.map,从第一行到最后一行,可以看到内核函数的地址范围。

如果不属于System.map里的范围,则它属于insmod加载的驱动程序

2.1.2 假设它是加载的驱动程序引入的错误,怎么确定是哪一个驱动程序?

先看看加载的驱动程序的函数的地址范围
cat /proc/kallsyms  (内核函数、加载的函数的地址)kallsyms:k指内核,all所有的,syms符号。
从这些信息里找到一个相近的地址, 这个地址<=0xbf000018
比如找到了:
bf000000 t first_drv_open    [first_drv]

意思是在驱动程序first_drv中有个first_drv_open函数,它的地址是bf000000,那个PC就是这个open函数加上18的地方,

2.1.3 找到了first_drv.ko

first_drv.ko: file format elf32-littlearm Disassembly of section .text: 00000000 <first_drv_open>: 0: e1a0c00d mov ip, sp 4: e92dd800 stmdb sp!, {fp, ip, lr, pc} 8: e24cb004 sub fp, ip, #4 ; 0x4 c: e59f1024 ldr r1, [pc, #36] ; 38 <__mod_vermagic5> 10: e3a00000 mov r0, #0 ; 0x0 14: e ldr r2, [r1] 18: e ldr r3, [r2] // 在这里出错 r2= 1c: e3c33c3f bic r3, r3, #16128 ; 0x3f00 20: e str r3, [r2] 24: e ldr r2, [r1] 28: e ldr r3, [r2] 2c: e3833c15 orr r3, r3, #5376 ; 0x1500 30: e str r3, [r2] 34: e89da800 ldmia sp, {fp, sp, pc} 38: 00000000 andeq r0, r0, r0

    first_drv.dis文件里              insmod后
00000000 <first_drv_open>:       bf000000 t first_drv_open    [first_drv]
00000018                         pc = bf000018

出错的这条指令是ldr r3, [r2],这条指令的意思是去r2所指的地方取一个值,把这个值存给r3,然后从之前报错时的打印信息可以看到r2的值是,然后要根据汇编语言找到C语言,

我们的first_drv_open C语言很简单,

讯享网static int first_drv_open(struct inode *inode, struct file *file) { static int cnt = 0; myprintk("first_drv_open : %d\n", ++cnt); /* 配置GPF4,5,6为输出 */ *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2))); *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2))); return 0; }

这里其实是对应

 18: e ldr r3, [r2] // 在这里出错 r2= 1c: e3c33c3f bic r3, r3, #16128 ; 0x3f00 20: e str r3, [r2]

是把gpfcon的值读出来,清除某一位,然后再存回去,然后就是*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));出错,然后看一下gpfcon是在哪里赋值的,从而找到了init函数赋值那里的错误。

2.2 驱动程序编入内核

我们把驱动编进内核,先把驱动程序拷贝到内核目录中

然后修改Makefile 

重新编译内核,并启动这个有问题的内核,

讯享网make uImage cp arch/arm/boot/uImage /work/nfs_root/uImage_bad reboot nfs  192.168.1.123:/work/nfs_root/uImage_bad bootm 

然后执行第一个驱动程序的测试程序,出现段错误

Modules linked in:
CPU: 0    Not tainted  (2.6.22.6 #2)
PC is at first_drv_open+0x18/0x3c
LR is at chrdev_open+0x14c/0x164
pc : [<c014e6c0>]    lr : [<c008638c>]    psr: a0000013
sp : c3a03e88  ip : c3a03e98  fp : c3a03e94
r10: 00000000  r9 : c3a02000  r8 : c03f3c60
r7 : 00000000  r6 : 00000000  r5 : c38a0c50  r4 : c3c1e780
r3 : c014e6a8  r2 :  r1 : c031a47c  r0 : 00000000
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 339f0000  DAC: 00000015
Process firstdrvtest (pid: 750, stack limit = 0xc3a02258)

2.2.1 根据pc值确定该指令属于内核还是外加的模块

pc=c014e6c0 属于内核(看System.map)

2.2.2  反汇编内核: arm-linux-objdump -D vmlinux > vmlinux.dis

在dis文件里搜c014e6c0
c014e6a8 <first_drv_open>:
c014e6a8:       e1a0c00d        mov     ip, sp
c014e6ac:       e92dd800        stmdb   sp!, {fp, ip, lr, pc}
c014e6b0:       e24cb004        sub     fp, ip, #4      ; 0x4
c014e6b4:       e59f1024        ldr     r1, [pc, #36]   ; c014e6e0 <.text+0x1276e0>
c014e6b8:       e3a00000        mov     r0, #0  ; 0x0
c014e6bc:       e        ldr     r2, [r1]
c014e6c0:       e        ldr     r3, [r2] // 在此出错 r2=

然后同样根据汇编语言找到C语言函数。

小讯
上一篇 2025-03-08 08:54
下一篇 2025-04-06 19:40

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/51757.html