• 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 根据pc值确定该指令属于内核还是外加的模块

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


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

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

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

    完整程序是

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. static struct class *firstdrv_class;
    13. static struct class_device *firstdrv_class_dev;
    14. volatile unsigned long *gpfcon = NULL;
    15. volatile unsigned long *gpfdat = NULL;
    16. static int first_drv_open(struct inode *inode, struct file *file)
    17. {
    18. //printk("first_drv_open\n");
    19. /* 配置GPF4,5,6为输出 */
    20. *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
    21. *gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
    22. return 0;
    23. }
    24. static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    25. {
    26. int val;
    27. //printk("first_drv_write\n");
    28. copy_from_user(&val, buf, count); // copy_to_user();
    29. if (val == 1)
    30. {
    31. // 点灯
    32. *gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
    33. }
    34. else
    35. {
    36. // 灭灯
    37. *gpfdat |= (1<<4) | (1<<5) | (1<<6);
    38. }
    39. return 0;
    40. }
    41. static struct file_operations first_drv_fops = {
    42. .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    43. .open = first_drv_open,
    44. .write = first_drv_write,
    45. };
    46. int major;
    47. static int first_drv_init(void)
    48. {
    49. major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
    50. firstdrv_class = class_create(THIS_MODULE, "firstdrv");
    51. firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
    52. gpfcon = (volatile unsigned long *)0x56000050; //(volatile unsigned long *)ioremap(0x56000050, 16);
    53. gpfdat = gpfcon + 1;
    54. return 0;
    55. }
    56. static void first_drv_exit(void)
    57. {
    58. unregister_chrdev(major, "first_drv"); // 卸载
    59. class_device_unregister(firstdrv_class_dev);
    60. class_destroy(firstdrv_class);
    61. iounmap(gpfcon);
    62. }
    63. module_init(first_drv_init);
    64. module_exit(first_drv_exit);
    65. MODULE_LICENSE("GPL");

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

    Unable to handle kernel paging request at virtual address 56000050
    内核使用56000050来访问时发生了错误
    pgd = c3eb0000
    [56000050] *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 : []    lr : []    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 : 56000050  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 4013365c c3c7bfa4 c3c7bf98 c008a3a8 c008a2b0 00000000 c3c7bfa8 
    bfa0: c002bea0 c008a394 bec1fed4 0000860c 00008720 00000002 bec1fee0 00000001 
    bfc0: bec1fed4 0000860c 00008670 00000002 00008520 00000000 4013365c bec1fea8 
    bfe0: 00000000 bec1fe84 0000266c 400c98e0 60000010 00008720 00000000 00000000 

    Backtrace: (回溯)
    [] (first_drv_open+0x0/0x3c [first_drv]) from [] (chrdev_open+0x14c/0x164)
    [] (chrdev_open+0x0/0x164) from [] (__dentry_open+0x100/0x1e8)
     r8:c3e766a8 r7:c0474e20 r6:c008d73c r5:c3e740c0 r4:c049abc0
    [] (__dentry_open+0x0/0x1e8) from [] (nameidata_to_filp+0x34/0x48)
    [] (nameidata_to_filp+0x0/0x48) from [] (do_filp_open+0x40/0x48)
     r4:00000002
    [] (do_filp_open+0x0/0x48) from [] (do_sys_open+0x54/0xe4)
     r5:bec1fee0 r4:00000002
    [] (do_sys_open+0x0/0xe4) from [] (sys_open+0x24/0x28)
    [] (sys_open+0x0/0x28) from [] (ret_fast_syscall+0x0/0x2c)
    Code: e24cb004 e59f1024 e3a00000 e5912000 (e5923000) 
    Segmentation fault

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

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

    2 根据PC分析出错位置

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

    2.1 驱动程序作为模块加载

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

    pc=0xbf000018 它属于什么的地址?是内核还是通过insmod加载的驱动程序?
    先判断是否属于内核的地址: 看System.map确定内核的函数的地址范围:c0004000~c03265a4

    我们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

    在PC上反汇编它: arm-linux-objdump -D first_drv.ko > frist_drv.dis
    在dis文件里找到first_drv_open

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

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

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

    我们的first_drv_open C语言很简单,

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

    这里其实是对应

    1. 18: e5923000 ldr r3, [r2] // 在这里出错 r2=56000050
    2. 1c: e3c33c3f bic r3, r3, #16128 ; 0x3f00
    3. 20: e5823000 str r3, [r2]

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

    2.2 驱动程序编入内核

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

    然后修改Makefile 

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

    1. make uImage
    2. cp arch/arm/boot/uImage /work/nfs_root/uImage_bad
    3. reboot
    4. nfs 32000000 192.168.1.123:/work/nfs_root/uImage_bad
    5. bootm 32000000

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

    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 : []    lr : []    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 : 56000050  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 :
    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:       e5912000        ldr     r2, [r1]
    c014e6c0:       e5923000        ldr     r3, [r2] // 在此出错 r2=56000050

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

  • 相关阅读:
    Elucidating the Design Space of Diffusion-Based Generative Models 阅读笔记
    TCP 连接管理机制(一)——TCP三次握手详解 + 为什么要有三次握手
    最好的天线基础知识!超实用 随时查询
    【Linux】-- 开发工具yum、vim、gcc、g++、gdb、make、makefile使用介绍
    ClickHouse集群为什么建议写分布式表
    MFC/QT 一些快要遗忘的细节:
    Java数据结构、list集合、ArrayList集合、LinkedList集合、Vector集合
    Java Heap Space: Understanding and Resolving Memory Issues
    如何用 php 实现邮件发送功能
    DataGrip 2023:让数据库开发变得更简单、更高效 mac/win版
  • 原文地址:https://blog.csdn.net/u013171226/article/details/126250977