1. 目的:
在ubuntu真实环境,使用gdb对一个PCIe设备的内核驱动代码(后面用X表示该内核驱动和设备)进行单步调试。
User Mode程序可直接在单机环境下调试。
2. 调试环境:
要调试真实环境中X内核驱动(不是仿真环境,不是虚拟环境),目标机必须是带X设备的物理机。目标机通过串口与调试机(或者称为开发机)相连,即通过kgdboc(kgdb on console)方式调试。另外还有kgdboe(kgdb on enthernet)方式,简单尝试过,会导致目标机hang死。
我用到的硬件环境:
1, 目标机为x86_64台式机,有X设备,主板上有COM口(见下图“目标机侧COM口和连线图”,非标准pin脚排布,且pin未引出)。
2, 调试机为另一台x86_64的物理机。
3, 使用USB转RS232串口线(链接如下)连接目标机和调试机。串口线COM口与目标机主板上COM口相连(目标机上设备为/dev/ttyS0),另一端与调试机的USB口相连(调试机上设备为/dev/ttyUSB0)。
绿联(UGREEN)USB转RS232串口线 USB转DB9针公头转接线 支持考勤机收银机标签打印机com口调试线
目标机侧COM口和连线图
问:如果目标机和调试机都用USB转串口(USB转TTL)的方式连接呢,能支持kgdb调试吗?
答:不行,缺省的USB转串口驱动不支持。要支持kgdboc,底层串口驱动需要支持“polling hooks”,即支持.poll_get_char和.poll_put_char接口;通常,驱动中用#ifdef CONFIG_CONSOLE_POLL包含这部分代码。添加上述功能后,可以支持。
3. 环境约定
为了减少拷贝,随时修改驱动,进行持续调试,我进行了如下约定(部分非必须):
1, 目标机和调试机运行的内核来自同一份内核源码,且保证内核版本信息一致,避免调试机编译的X驱动无法被目标机加载。
2, 目标机上运行X内核驱动。
3, 调试机上运行gdb和编译X驱动,调试机上有X驱动源码、驱动编译生成的ko文件、目标机内核文件(还有内核源码),gdb会用到上述文件。每次编译后,将X.ko拷贝到目标机上运行。
4. 具体执行步骤:
4.1. 目标机上内核编译和安装
假设已经有一份内核的源代码包,编译的内核需要支持kgdb调试。
依次执行如下:
执行cd /usr/src/linux-source-5.4.0/到源代码目录。
执行make clean; make mrproper净化源代码。
执行cp -v /boot/config-5.4.0-42-generic .config基于当前内核的配置(此步可省略)。
执行make menuconfig:
选中kernel hacking->Compile-time checks * ->compile kernel with debug info(默认已选);
修改kernel hacking->Compile-time checks * ->Warn for stack frames larger than *,从1024改为2048(Optional);
选中kernel hacking->Compile-time checks * ->enable full section mismatch analysis(可选项,防止内联);
选中kernel hacking->compile the kernel with frame pointers(可能没有这一项);
选中kernel hacking->Magic Sysrq key;
选中kernel hacking->Kernel debugging;
选中KGDB: kernel debugging with remote gdb –>kgdb:use kgdb over the serial console;
执行nproc查看cpu核数。
执行make –j 16进行编译(16为cpu核数),或者执行make bzImage进行编译。
编译生成的vmlinux是未压缩的内核文件,gdb加载这个文件并读取symbol符号信息。.arch/x86_64/boot/bzImage(.arch/x86/boot/bzImage)是压缩后的镜像文件。
将编译生成的vmlinux拷贝到调试机上。
编译成功后,执行make INSTALL_MOD_STRIP=1 modules_install && make install分别安装内核模块和内核。
修改/etc/default/grub使启动内核选项都显示出来。
修改目标机的内核启动参数添加如下信息:rw console=ttyS0,115200 kgdbwait kgdboc=ttyS0,115200 nokaslr (带有kgdbwait,目标机在启动时就会等待调试机的gdb连接,由于我们可以等待目标机启动后再手动加载内核驱动,故去掉kgdbwait)。
4.2. 调试机上内核编译和安装
调试机的新内核,不需要支持kgdb。
我使用与目标机相同内核和版本信息,是为了确保调试机编译的X驱动能被目标机加载。
详细步骤略。
4.3. 调试机上内核驱动编译
调试机上,基于新安装的内核编译X内核驱动。
把新编译生成的X.ko拷贝到目标机/usr/lib/modules/5.4.210/extra/目录。
把编译生成的X驱动拷贝到本地目录:/usr/src/kgdb_dir/(此目录用于存放调试用到的文件)。
4.4. 调试机上串口虚拟化(optional)
获取agent-proxy: kernel/kgdb/agent-proxy.git – agent-proxy for kgdb
执行cd /home/xxx/agent-proxy/。
执行./agent-proxy 5550^5551 0 /dev/ttyUSB0,115200。
通过串口虚拟化把目标机的控制台重定向到HOST:5550;把目标机到HOST的kgdb重定向到HOST:5551。
调试机执行telnet localhost 5550监听目标机控制台输出。
4.5. 调试机其他配置:
调试机上编写脚本~/.gdbinit,内容如下:
add-auto-load-safe-path /usr/src/linux-source-5.4.0(调试机上的目标机内核源码路径)
source ./vmlinux-gdb.py
4.6. 调试内核驱动X
目标机启动,如果带kgdbwait启动参数,目标机会进入等待连接状态;如果不带kgdbwait启动参数,正常进入系统,需要在目标机上手动执行echo g > /proc/sysrq-trigger使目标机处于等待gdb连接的状态。此时,在调试机console 5550的监听窗口,会停在kdb>提示符,输入kgdb后,目标机等待gdb连接。
调试机执行cd /usr/src/kgdb_dir/(此目录下有X驱动文件)。
调试机执行gdb ../linux-source-5.4.0/vmlinux(调试机上的目标机内核文件)。
调试机执行(gdb)target remote localhost:5551进行gdb连接。
此时目标机上X驱动未加载,设置断点(gdb)b do_init_module后让目标机正常执行。
目标机执行modprobe X加载X驱动模块。此时gdb会中断在do_init_module,运行(gdb)lx-symbols在gdb中加载X驱动模块对应的符号,加载后,就可以设置X驱动模块中的断点。
注:这里使用lx-symbols脚本自动加载对应的符号,与前面的拷贝X驱动模块到当前目录对应。手动加载方法是:目标机上执行cat /sys/module/X/sections/.text获取地址值,再调试机执行gdb>add-symbol-file X.ko 0xaddr。
注意:X驱动中函数的局部变量可能被优化掉,无法在gdb中查看。
针对整个内核,不能取消优化,不能完全关掉-O2,用-O0是无法完全编译通过的。
实测在源代码文件中添加以下方式是有效的。
#pragma GCC push_options
#pragma GCC optimize("O0")
<source code>
#pragma GCC pop_options
估计下述方法也应该有效:
Void __attribute__((optimize(“O0”))) foo(char data)
{}
<end>
原文链接:https://blog.csdn.net/thomastsm/article/details/142103014?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522f781bd8a572b3bb309d621a12b8fe0e9%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=f781bd8a572b3bb309d621a12b8fe0e9&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~times_rank-19-142103014-null-null.nonecase&utm_term=%E7%BB%BF%E8%81%94UGREEN