美文网首页
[转]Linux驱动调试-根据oops调试

[转]Linux驱动调试-根据oops调试

作者: Nothing_655f | 来源:发表于2020-09-08 10:54 被阅读0次

Linux驱动调试-根据oops定位错误代码行

1.当驱动有误时,比如,访问的内存地址是非法的,便会打印一大串的oops出来**

1.1以LED驱动为例

将open()函数里的ioremap()屏蔽掉,直接使用物理地址的GPIOF,如下图所示:

img

1.2然后编译装载26th_segmentfault并执行测试程序后,内核便打印了oops出来,如下图所示:

img

2.接下来,我们便来分析oops:

Unable to handle kernel paging request at virtual address 56000050
      //无法处理内核页面请求的虚拟地址56000050
​
pgd = c3850000
[56000050] *pgd=00000000
​
Internal error: Oops: 5 [#1]
        //内部错误oops
​
Modules linked in: 26th_segmentfault
        //表示内部错误发生在26th_segmentfault.ko驱动模块里
​
CPU: 0    Not tainted  (2.6.22.6 #2)
PC is at first_drv_open+0x78/0x12c [26th_segmentfault]
        //PC值:程序运行成功的最后一次地址,位于first_drv_open()函数里,偏移值0x78,该函数总大小0x12c
​
LR is at 0xc0365ed8             //LR值
​
/*发生错误时的各个寄存器值*/
pc : [<bf000078>]    lr : [<c0365ed8>]    psr: 80000013
sp : c3fcbe80  ip : c0365ed8  fp : c3fcbe94
r10: 00000000  r9 : c3fca000  r8 : c04df960
r7 : 00000000  r6 : 00000000  r5 : bf000de4  r4 : 00000000
r3 : 00000000  r2 : 56000050  r1 : 00000001  r0 : 00000052
​
Flags: Nzcv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: c000717f  Table: 33850000  DAC: 00000015
Process 26th_segmentfau (pid: 813, stack limit = 0xc3fca258)
            //发生错误时,进程名称为26th_segmentfault
​
Stack: (0xc3fcbe80 to 0xc3fcc000)                     //栈信息
be80: c06d7660 c3e880c0 c3fcbebc c3fcbe98 c008d888 bf000010 00000000 c04df960
bea0: c3e880c0 c008d73c c0474e20 c3fb9534 c3fcbee4 c3fcbec0 c0089e48 c008d74c
bec0: c04df960 c3fcbf04 00000003 ffffff9c c002c044 c380a000 c3fcbefc c3fcbee8
bee0: c0089f64 c0089d58 00000000 00000002 c3fcbf68 c3fcbf00 c0089fb8 c0089f40
bf00: c3fcbf04 c3fb9534 c0474e20 00000000 00000000 c3851000 00000101 00000001
bf20: 00000000 c3fca000 c04c90a8 c04c90a0 ffffffe8 c380a000 c3fcbf68 c3fcbf48
bf40: c008a16c c009fc70 00000003 00000000 c04df960 00000002 be84ce38 c3fcbf94
bf60: c3fcbf6c c008a2f4 c0089f88 00008588 be84ce84 00008718 0000877c 00000005
bf80: c002c044 4013365c c3fcbfa4 c3fcbf98 c008a3a8 c008a2b0 00000000 c3fcbfa8
bfa0: c002bea0 c008a394 be84ce84 00008718 be84ce30 00000002 be84ce38 be84ce30
bfc0: be84ce84 00008718 0000877c 00000003 00008588 00000000 4013365c be84ce58
bfe0: 00000000 be84ce28 0000266c 400c98e0 60000010 be84ce30 30002031 30002431
​
Backtrace:                                        //回溯信息
[<bf000000>] (first_drv_open+0x0/0x12c [26th_segmentfault]) from [<c008d888>] (chrdev_open+0x14c/0x164)
 r5:c3e880c0 r4:c06d7660
[<c008d73c>] (chrdev_open+0x0/0x164) from [<c0089e48>] (__dentry_open+0x100/0x1e8)
 r8:c3fb9534 r7:c0474e20 r6:c008d73c r5:c3e880c0 r4:c04df960
[<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:be84ce38 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: bf000094 bf0000b4 bf0000d4 e5952000 (e5923000)
Segmentation fault

2.1上面的 回溯信息,表示了函数的整个调用过程

比如上面的回溯信息表示:

  • sys_open()->do_sys_open()->do_filp_open()->nameidata_to_filp()->chrdev_open()->first_drv_open();

最终错误出在了first_drv_open();

若内核没有配置回溯信息显示,则就不会打印函数调用过程,可以修改内核的.config文件,添加:

img

//CONFIG_FRAME_POINTER,表示帧指针,用fp寄存器表示

内核里,就会通过fp寄存器记录函数的运行位置,并存到栈里,然后当出问题时,从栈里调出fp寄存器,查看函数的调用关系,就可以看到回溯信息.

(PS:若不配置,也可以直接通过栈来分析函数调用过程,在下章会分析到:http://www.cnblogs.com/lifexy/p/8011966.html)

2.2 而有些内核的环境不同,opps也可能不会打印出上面的:

Modules linked in: 26th_segmentfault
PC is at first_drv_open+0x78/0x12c [26th_segmentfault]

这些相关信息, 只打印PC值,就根本无法知道,到底是驱动模块出的问题,还是内核自带的函数出的问题?

所以oops里的最重要内容还是这一段: pc : [ bf000078 ]

2.3 那么如何来确定,该PC值地址位于内核的函数,还是我们装载的驱动模块?

答:

可以在内核源码的根目录下通过的“vi System.map”来查看,该文件保存了内核里所有(符号、函数)的虚拟地址映射,比如下图的内核函数root_dev_setup():

img

通过vi命令的:0和:$命令行,可以看到内核的虚拟地址是c0004000~c03cebf4

所以,pc值bf000078为的驱动模块的地址值

2.4 当有多个驱动装载时,又如何区分PC值是哪个驱动的函数的地址值?

答:通过/proc/kallsyms来查看:
//(kernel all symbols)查看所有的内核标号(包括内核函数,装载的驱动函数,变量符号等)的地址值

# cat /proc/kallsyms  

如下图所示,在kallsyms.txt里,找到pc值bf000078位于26th_segmentfault驱动里first_drv_open()函数下的bf000000+0x78中

img

2.5然后将驱动生成反汇编:

arm-linux-objdump -D 26th_segmentfault.ko >26th_segmentfault.dis

如果想要带上源码,可以带上对应的参数
-dSl 参数

arm-linux-objdump -dSl 26th_segmentfault.ko >26th_segmentfault.dis

2.6 打开反汇编:

如下图所示,左边是kallsyms.txt,右边是26th_segmentfault.dis反汇编

img

显然pc值bf000078,就位于反汇编的78地址处:

Disassembly of section .text:         //.text段起始地址为0x00
00000000 <first_drv_open>:
​
38: e59fc0e8   ldr   ip, [pc, #232]; 128 <.text+0x128> //ip=.text段+0x128里的内容
... ...
​
50: e585c000   str   ip, [r5]       //r5=.text段+0x128里的内容
... ...
​
74: e5952000   ldr   r2, [r5]           //r2=.text段+0x128里的内容
78: e5923000   ldr   r3, [r2]                  // r3=.text段+0x128里的内容
7c: e3c33c3f   bic   r3,  r3,   #16128    ;0x3f00  //清除0x56000050的bit8~13
... ...
​
128:  56000050  undefined                //.text段+0x128里的内容=0x56000050

从上面看到, 78地址处,主要是将0x56000050(r2)地址里的内容放入r3中.

0x56000050是个物理地址,在linux眼中便是个非法地址,所以出错

并找到出错地方位于first_drv_open ()函数下:

img

3.若发生错误的驱动位于内核的地址值时

3.1 还是以26th_segmentfault.c为例,首先加入内核:
//将有问题的驱动复制到字符驱动目录下

#cp 26th_segmentfault.c   /linux-2.6.22.6/drivers/char/  
#vi Makefile

添加:

obj-y += 26th_segmentfault.o //y:将该驱动放入内核中

3.2 然后make uImage装载新内核后,再运行测试程序,便会打印出opps信息

3.3在内核源码的根目录下通过:

# arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis

将整个内核反汇编, vmlinux:未压缩的内核

3.4 vi vmlinux.dis,然后通过oops信息的PC值直接来查找地址即可

接下来下章便通过信息来分析函数调用过程:http://www.cnblogs.com/lifexy/p/8011966.html

Linux驱动调试-根据oops的栈信息,确定函数调用过程

1.上章的oops栈信息如下图所示:

img
  • 9fe0: 代表最初的栈顶SP寄存器位置

  • 9e80:代表函数出错的SP寄存器位置

2.我们先来分析上图的栈信息,又是怎样的过程呢?

2.1内核主要是通过STMDB和LDMIA汇编命令来入栈和出栈

(STMDB和LDMIA汇编命令参考: http://www.cnblogs.com/lifexy/p/7363208.html)

内核每进入一个函数就会通过STMDB,将上个函数的内容值存入栈顶sp,然后栈顶sp-4.

当内核的某个函数出问题时,内核便通过LDMIA,将栈顶sp打印出来,然后栈顶sp+4,直到到达最初的栈顶

2.2我们以下图的3个函数为例:

img

若c()函数出问题后,内核就会打印b()函数的内容(0x03,LR), 打印a()函数的内容(0x02,LR),直到sp到达栈顶为止

其中lr值,便代表各个函数的调用关系

3.接下来我们便以上章的oops里的栈信息来分析

在上章里,我们找到PC值bf000078在26th_segmentfault驱动模块first_drv_open()函数下出错。

3.1先来看first_drv_open()函数,找到STMDB入栈的lr值,来确定被哪个函数调用的

img

如上图所示,first_drv_open()函数里,通过stmdb sp!, {r4, r5, fp, ip, lr, pc} 存入了6个值,

所以, 返回到上个函数的值lr =c008d888

在上章,我们便分析到:

内核的虚拟地址是c0004000~c03cebf4,所以c008d888位于内核的某个函数里

3.2 然后将内核进行反汇编

在内核源码的根目录下:
//-D:反汇编所有段 vmlinux:未压缩的内核

# arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.txt     

3.3 打开vmlinux.txt

如下图所示,搜索c008d888:

img

往上翻,找到c008d888位于函数chrdev_open()下:

img

如上图所示, chrdev_open()函数存了10个值,所以,返回到上个函数的值lr= c0089e48

3.4 继续搜索c0089e48:

img

往上翻,找到c0089e48位于函数__dentry_open ()下:

img

如上图所示, __dentry_open()函数存了10个值,所以,第二个值lr= c0089f64

3.5 继续搜索c0089f64:

img

往上翻,找到c0089f64位于函数nameidata_to_filp()下:

img

如上图所示, nameidata_to_filp函数存了6个值,所以,第二个值lr= c0089fb8

... ...(此处省略n字)

4.最终分析出,栈信息的调用过程如下:

  • ret_fast_syscall()->

  • sys_open()->

  • do_sys_open()->

  • do_filp_open()->

  • nameidata_to_filp()->

  • chrdev_open()->

  • first_drv_open();

来自 https://www.cnblogs.com/lifexy/p/8006748.html
来自 https://www.cnblogs.com/lifexy/p/8011966.html

相关文章

  • [转]Linux驱动调试-根据oops调试

    Linux驱动调试-根据oops定位错误代码行 1.当驱动有误时,比如,访问的内存地址是非法的,便会打印一大串的o...

  • 6. Oops栈回溯

    1. 解读系统崩溃2. linux中oops信息的调试及栈回溯3. 根据函数调用过程谈栈回溯原理 ESP的问题 虽...

  • 2018-09-18 Linux内核调试

    【Gooooood转】Linux内核调试方法总结

  • python调试

    linux python调试技巧 Linux下Python基础调试 http://blog.163.com/liu...

  • linux编程入门(七)-使用gdb调试程序

    程序开发离不开调试,可以断点调试,也可以打log调试,linux下断点调试c,c++程序用gdb。 断点调试虽然很...

  • Linux内核SD卡

    本文介绍如何三招搞定Linux SD卡驱动调试。 一、SD卡介绍 SD Card(Secure Digital M...

  • M2使用教程

    使用adb调试android设备 前提是安装好adb调试驱动为F:\Android\sdk\platform-to...

  • Andriod ADB Interface驱动安装失败Confi

    介绍: 1.Linux或Apple或OS X ,已经安装了USB驱动调试为Android的帮助,确认您的Andro...

  • Linux程序调试方法汇总

    Linux下程序的调试方法汇总 在linux中讨论调试工具主要是为那些入门者提供一些帮助。调试工具能让我们能够监测...

  • 串口调试工具kermit安装和配置

    在windows系统中,使用USB转UART串口工具调试时,需要安装对应的串口驱动。如果直接在网上下载驱动,可能会...

网友评论

      本文标题:[转]Linux驱动调试-根据oops调试

      本文链接:https://www.haomeiwen.com/subject/ljsdektx.html