利用gdb远程调试驱动模块

来源:互联网 发布:cygwin与linux区别 编辑:程序博客网 时间:2024/05/22 23:54

    在前面的文章中我总结了gdb远程调试内核的大致过程,由于一开始利用kgdb就是想调试自己的驱动模块,所以,这里我对怎么利用gdb远程调试驱动模块做一个总结。

    其实调试驱动模块和调试内核的过程是完全一样的,但是调试驱动模块一个很重要的地方就是驱动的初始化的调试很困难,这我在下面会详细说明原因。

    通过两天的摸索,我感觉虽然gdb可以远程调试内核,但是由于调试内核相对于调试普通程序的特殊性,很多在调试普通程序能做的事情,在调试内核的时候却做不了(比如查看某个变量的值,比如跳转到某个函数中,这些在调试内核的时候都有可能失效)。下面我们就说一下驱动模块调试的大致过程。

    1,启动目标机

    这里由于我们不调试内核,所以内核的启动可以直接运行,不需要gdb的参与,当然我们也可以在grub下添加kgdbwait让内核启动受远程的gdb控制,如果不添加,代表直接启动内核。

    2,目标机开启中断模式

    stty ispeed 115200 ospeed 115200 -F /dev/ttyS0

    echo g > /proc/sysrq-trigger

    3,远程gdb调控

    在目标机开启中断模式后,目标机所有内核程序就会被挂起,等待远程gdb调试,这个时候我们还没有加载要调试的驱动模块,但是我们可以设置断点,也可以输入continue让目标机继续运行。注意如果想调试内核的初始化模块,这个时候就可以设置必要的断点,使内核在初始化驱动模块的时候进入中断。调试驱动初始化注意事项:
在驱动初始化的时候,由于驱动还没有完全加载到系统中,所以我们无法获得内核的.text,.data等信息,所以我们这时候是无法在驱动代码上设置中断的,晚上有说先第一次插入内核不调试,等到加载进去后,再把.text等信息记录下来,然后重启目标机,重启后再加载驱动之前再把这些信心利用add-sysmbol-file命令加载到gdb中,然后再设置断点,想想这样感觉非常棒,但是我试过,最后结果是完全设置不了断点。所以如果想调试驱动的初始化,我们就必须在驱动初始化过程中涉及到的内核函数上加上断点,然后再继续运行。

    4,驱动初始化之后的调试

    初始化之后,首先我们要活得驱动代码在内存中的位置:

    cat /sys/modules/*/section/.text

    然后在gdb中加载驱动的符号表

    add-sysmbol-file *.ko 0x(.text的值)

    这个时候我们就可以在驱动代码上设置正确的断点了,然后进行正常的调试。

    下面说一下驱动调试的注意事项:

    1,在驱动代码上设置不了断点,会出现can not access address at *的问题

    我花了很长时候在这个问题上,最后我的问题就是在一些函数前面加了__devinit,__devexit,__init,_exit等属性,这些宏的含义这里先不总结,编译器会把这些宏对应的驱动代码优化到合适的内存位置,以减小内存占用以及提高效率,导致设置断点失败,所以解决办法就是去掉驱动代码中的这些约束,这对驱动程序的运行结果并没有什么影响。

    2,value optimized out的问题

    有人说要在编译内核的Makefile中把默认的O2优化改成O0,让编译器不对内核进行优化,但是晚上也有人说这样不可行,内核的编译时必须优化的,我这里是没有做任何改动,关于这个问题其实没有什么关系,因为可能在一个函数里我们看不到某一个变量的值,但是在另一个函数里我们又可以看见的,所以这个问题并没有什么影响,不要一有问题就想着重新编译内核,很浪费时间。

    再谈如何调试驱动模块的初始化代码:
    上面已经说过怎么调试驱动模块的初始化代码,但是上面说的并没有彻底解决问题,按照之前的做法,我在gdb开始调试的时候,在初始化代码涉及的内核原函数上设置断点,然后再运行,这样的确会让驱动停在所在的断点上,但是我之前遇到的问题是,当内核函数执行完成之后,我无法回到自己编写的内核代码上,而是会出现下面的问题:


    出现这样的问题的原因就是我没有正确载入驱动的符号表,即*.ko文件。载入*.ko文件的位置很重要,下面先说一下集中载入*.ko文件错误的方式:

    *在刚进入gdb就载入,这时候模块代码还没有读入到内存中,这个时候会出现设置不了断点的情况,这也是最常出现的情况。

    *在sys_init_module函数之后载入,我一开始以为sys_init_module之后,驱动代码就会被读入内存中,但是在这个函数之后载入的时候,同样遇到不能设置断点的情况。

    *把*.ko文件载入到错误的内存区域上,由于要想调试初始化代码,就必须在probe或者其他初始化函数之前载入*.ko文件。但是如果最后载入的*.ko文件的内存地址和实际加载驱动模块之后的.text地址不一样,这个时候会出现cannot access memory at address *的问题:

    

    所以,从上面的可以看出,什么时候载入*.ko文件并不重要,重要的是载入的位置,以及设置断点的时机。对于载入的位置,我们知道,系统第一次载入驱动模块之后,.text值总是不变的,所以我们可以在第一次载入驱动模块的时候,记下.text的值,然后重启,重启之后,打开gdb,按照前面的.text的值载入我们的*.ko文件,这样就能保证地址的一致性。

   至于设置断点的时机,很重要的一点就是在驱动代码上设置断点,必须要等到驱动代码载入到内存中才可以设置,由于我写的驱动和PCIE有关系,所以我可以现在__pci_register_driver函数上设置一个断点,这是一个内核函数,所以我可以设置断点,等驱动程序运行到__pci_register_driver上的时候,这个时候我们自己的驱动代码肯定已经加载到内存中,这个时候我们就可以对驱动代码设置断点了(b probe)。这个驱动的初始化代码就可以从probe函数开始调试了。

0 0
原创粉丝点击