阅读:5734回复:10
使用 KGDB 调试 Linux 内核
Author : ZC Miao
Date : Sunday, July 20 2008 * 简介 从 2.6.25 开始,Linux 主干内核开始内置了代码级调试器 kgdb。通过 kgdb,开发者就可以在内核代码中设置断点,单步调试和观察变量。为了使用 kgdb,你需要有两个系统。一个作为开发系统,一个作为测试系统嗯。两台机器通过串口线连接。需要调试的内核运行在测试己其上。串口线用于 gdb 连接远程目标。kgdb 已经还可以支持通过以太网连接两台机器,不过目前内核中(2.6.26)还没有这部分代码。 本文通过 qemu 模拟器运行 2.6.26 的 内核作为测试系统,并通过 qemu 的虚拟TCP串口设备功能,使开发系统的 gdb 可以通过连接本地 tcp 端口调试内核。本文使用的开发系统: Fedora 9, 2.6.25.10-86.fc9.i686 QEMU PC emulator version 0.9.1, Copyright (c) 2003-2008 Fabrice Bellard GNU gdb Fedora (6.8-11.fc9) gcc (GCC) 4.3.0 20080428 (Red Hat 4.3.0-8) * 配置和启动 1. 内核选项 kgdb 选项 : CONFIG_KGDB=y CONFIG_KGDB_SERIAL_CONSOLE=y 选项在 Kernel hacking 里可以找到 同时,为了能在系统运行时中断系统并出发远程 gdb,必须打开内核 Magic Sys-Rq 键选项 : CONFIG_MAGIC_SYSRQ=y 打开内核符号调试: CONFIG_DEBUG_INFO=y 注意事项: CONFIG_DEBUG_RODATA 必须被禁用,否则kgdb讲无法正常使用。 2. 启动 qemu 代码: qemu ${QEMU_ARGS} \ -kernel "$VMLINUZ" \ -append "${KERNEL_CMDLINE} kgdboc=ttyS0,115200 kgdbwait" \ -serial tcp::${KGDB_TCPPORT},server \ -hda "${QEMU_HDA}" >/dev/null & qemu 选项中: -kernel 编译好的内核,压缩过的 -append 传给 kernel 的命令行选项 -serial 使系统的第一个串口与本地的 TCP 端口关联起来,用于模拟终端,这样gdb就可以连接到目标系统而不用串口线了。 -hda 用于运行开发系统的Linux系统qemu硬盘。你也可以在用 nfs root 启动系统,但qemu必须要求你指定至少一个qemu硬盘,你可以使用空的硬盘,用qemu-img工具创建。 kernel 选项中: kgdboc 的意思是 kgdb over console,这里将kgdb连接的console设置为ttyS0,波特率为115200 kgdbwait 使 kernel 在启动过程中等待 gdb 的连接。 3. gdb 连接测试系统 qemu 启动后会等待 gdb 连接 tcp 端口以启动模拟串口。 运行下面的命令启动gdb,如果没设置KGDB_GDB变量,默认用 gdbtui 启动,其中 VMLINUX 是未压缩的 kernel 文件。 ${KGDB_GDB:-gdbtui} $VMLINUX 设置波特率 (gdb) set remotebaud 115200 连接目标 (gdb) target remote tcp:localhost:${KGDB_TCPPORT} 连接成功后由于启动了 kgdbwait 选项,kgdb 会在内核启动的某个阶段得到控制权: 代码: Remote debugging using tcp:localhost:4555 kgdb_register_io_module (new_kgdb_io_ops=) at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/kernel/kgdb.c:1674 (gdb) 现在就可以开始使用 kgdb 调试内核了。 * 基本功能 1. 杀死 gdb 如果 gdb 遇到问题无法相应了,可以在测试系统上运行下列命令中断调试: 代码: echo -e "\003" > /dev/ttyS1 如果你重启系统了,gdb 也需要重新连接系统。 2. 中断测试系统 为了在测试系统运行的时候让 gdb 获得控制权,需要使用 Sys-Rq。关于 Sys-Rq 的详细设置方法,见 Documentation/sysrq.txt。 你可以在系统运行时键盘上输入 Sys Rq+g,然后 gdb 就会出现: 代码: Program received signal SIGTRAP, Trace/breakpoint trap. [Switching to Thread -1] sysrq_handle_gdb (key=103, tty=0xc7869800) at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/kernel/kgdb.c:1674 (gdb) 如果你不知道如何在键盘上输入,或者你需要程序控制,则可以用下列等价命令模拟: 代码: # echo g > /proc/sysrq-trigger 3. 继续执行测试系统 代码: (gdb) c 即可 4. 设置断点,查询变量,单步调试,和调用堆栈 这些都和调试普通程序一样,比如: 代码: (gdb) b sys_open Breakpoint 1 at 0xc046d268: file /data/home/hellwolf/mydoc/prog/linux/linux-2.6/ fs/open.c, line 1107. (gdb) c Continuing. [New Thread 612] [Switching to Thread 612] Breakpoint 1, sys_open (filename=0xb7f68a9c "/etc/ld.so.cache", flags=0, mode=0) at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/fs/open.c:1107 (gdb) p filename $1 = 0xb7f68a9c "/etc/ld.so.cache" (gdb)n (gdb)bt #0 sys_open (filename=0xb7f68a9c "/etc/ld.so.cache", flags=0, mode=0) at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/fs/open.c:1113 #1 0xc0403976 in system_call () at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/arch/x86/kernel/entry_32.S :377 #2 0x00000000 in ?? () 5. 局限性 kgdb 不支持 tracepoints 和 watchpoints,除非有硬件调试器支持。 kgdb 的 threads 也无法正常使用。 * GDB 脚本 gdb 可以定义 sequences 脚本, 功能局限,但是可以用来简化一些工作。比如在 gdb 调试状态下 lsmod 的脚本: 代码: set $_lsmod_modules = modules->next # 1 10 15 printf "Module Size\n" while $_lsmod_modules != &modules set $_lsmod_module = (struct module*)((unsigned char*)$_lsmod_modules-(unsigned int)&(((struct module*)0)->list)) printf "%-15s %d\n", $_lsmod_module->name, $_lsmod_module->init_size + $_lsmod_module->core_size set $_lsmod_modules = $_lsmod_modules->next end 用 source 命令可以读入外部的 gdb 命令序列文件。 gdb 脚本最大的局限性在于,没有内置的字符串比较功能,比如我要得到一个(struct module*),我必须遍历模块列表然后做字符串匹配。这里我用了一个比较特殊的解决办法,我定义了两个gdb sequences : 代码: define xsrun if $argc == 0 printf "Usage : xsrun xsourcecmd args...\n" else shell rm -f /tmp/*.xsource shell echo 0 > /tmp/seq.xsource if $argc == 1 xsource '$arg0' end if $argc == 2 xsource '$arg0' '$arg1' end if $argc == 3 xsource '$arg0' '$arg1' '$arg2' end if $argc == 4 xsource '$arg0' '$arg1' '$arg2' '$arg3' end if $argc == 5 xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' end if $argc == 6 xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' end if $argc == 7 xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' end if $argc == 8 xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' end if $argc == 9 xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8' end if $argc == 10 xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8' '$arg9' end end end define xsource if $argc == 1 shell $TOOLSDIR/kgdb-xsource '$arg0' end if $argc == 2 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' end if $argc == 3 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' end if $argc == 4 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' end if $argc == 5 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' end if $argc == 6 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' end if $argc == 7 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' end if $argc == 8 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' end if $argc == 9 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8' end if $argc == 10 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8' '$arg9' end if $argc > 0 source /tmp/current.xsource end end 其中 $TOOLSDIR/kgdb-xsource 是我自定义的一个寻找名字为 '$arg0' 的 xsource 脚本的工具,我的实现就是在某个目录下找名字为 $arg0 的文件: 代码: $ cat tools/kgdb-xsource #!/bin/bash . $(dirname "$0")/common.sh if [ "$#" == 0 ];then echo "kgdb-xsource xsourcecmd args...\\n\"" > /tmp/current.xsource else XSOURCECMD=$1 shift XSOURCESEQ=$(cat /tmp/seq.xsource) XSOURCESEQ=$((${XSOURCESEQ}+1)) echo ${XSOURCESEQ} > /tmp/seq.xsource XSOURCEOFILE=/tmp/${XSOURCESEQ}-${XSOURCECMD}.xsource if [ -f "$XSOURCEDIR/$XSOURCECMD" ] && [ -x "$XSOURCEDIR/$XSOURCECMD" ];then $XSOURCEDIR/$XSOURCECMD "$@" > ${XSOURCEOFILE} rm -f /tmp/current.xsource ln -s ${XSOURCEOFILE} /tmp/current.xsource else echo "printf \"No such xsource command : $XSOURCECMD\\n\"" > /tmp/current.xsource fi fi 这些脚本的目的就是,通过外部脚本生成 gdb 脚本,然后再让 gdb 执行。他首先寻找 xsource 脚本,然后运行它,得到的输出(gdb sequences)保存到 /tmp 下的某个文件中,并链接为 /tmp/current.xsource, 然后让 gdb 执行改文件。 比如 strcmp_vs 脚本,这个脚本就是为了比较内部变量和一个字符串的: 代码: $ cat tools/xsource.d/strcmp_vs #!/usr/bin/perl if (@ARGV != 3) { print "printf \"Usage : strcmp result var str\\n\""; exit 1 } ($result, $var, $str) = @ARGV; print 'if '; $i = 0; for (0..length($str)-1) { $i = $_; $c = substr($str, $i, 1); print ' && ' if $i; print "${var}[$i] == '$c'"; } ++$i; print " && ${var}[$i] == '\\0' set \$$result = 0 else set \$$result = -1 end "; 利用这个基本命令,我们现在就可以实现 getmod 功能拉: 代码: $ cat tools/xsource.d/getmod #!/bin/sh [ $# != 2 ] && echo "printf \"Usage : getmod modvar modname\n\"" && exit 1 MODVAR=$1 MODNAME=$2 cat next set \$_getmod_modfound = 0 while \$_getmod_modules != &modules set \$_getmod_module = (struct module*)((unsigned char*)\$_getmod_modules-(unsigned int)&(((struct module*)0)->list)) xsource strcmp_vs _getmod_modfound \$_getmod_module->name $MODNAME if \$_getmod_modfound == 0 set \$$MODVAR = \$_getmod_module set \$_getmod_modfound = 1 loop_break end set \$_getmod_modules = \$_getmod_modules->next end if \$_getmod_modfound != 1 printf "Module \\"$MODNAME\\" cannot be found\\n" set \$$MODVAR = 0 end EOF 运行范例: 代码: (gdb) xsource getmod modvar hello (gdb) print $modvar->sect_attrs->attrs[0]->name $9 = 0xc7855b40 ".note.gnu.build-id" * 调试内核模块 调试内核模块需要额外的步骤,因为内核模块是动态载入的,gdb 一开始无法从内核 VMLINUX 文件中得到模块中的函数和变量地址信息。这里必须用 gdb 的 add-symbol-file 命令: 代码: add-symbol-file $MODFILE \\ $TEXT_ADDR \\ -s .data $DATA_ADDR \\ -s .bss $BSS_ADDR 其中 MODFILE 是 .ko 文件, 如何获得那三个地址是这里最关键的。这里有两种方法: 1. 从 sysfs 中获得 这种方法比较简单: 代码: $ cat /sys/module/snd/sections/.text 0xf8f83000 $ cat /sys/module/snd/sections/.data 0xf8f8d040 $ cat /sys/module/snd/sections/.bss 0xf8f8e280 2. 用 gdb 脚本 这个脚本先通过上面的 getmod 脚本得到(struct module*),然后从里面得到所需要的 sections 的地址,最后调用 add-symbol-file 命令。不过因为有大量的 gdb 交互,所以会比较慢 代码: $ cat tools/xsource.d/addmod2gdb #!/bin/sh # addmod2gdb($modname) [ $# != 1 ] && echo "printf \"Usage : addmod2gdb modname\n\"" && exit 1 MODNAME=$1 MODFILE=$KMODSDIR/$MODNAME/${MODNAME}.ko if [ -f $MODFILE ];then cat sect_attrs->nsections if \$_addmod2gdb_c == 3 loop_break end xsource strcmp_vs _addmod2gdb_r \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].name .text if \$_addmod2gdb_r == 0 set \$_addmod2gdb_text = \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].address set \$_addmod2gdb_c = \$_addmod2gdb_c + 1 set \$_addmod2gdb_i = \$_addmod2gdb_i + 1 loop_continue end xsource strcmp_vs _addmod2gdb_r \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].name .data if \$_addmod2gdb_r == 0 set \$_addmod2gdb_data = \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].address set \$_addmod2gdb_c = \$_addmod2gdb_c + 1 set \$_addmod2gdb_i = \$_addmod2gdb_i + 1 loop_continue end xsource strcmp_vs _addmod2gdb_r \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].name .bss if \$_addmod2gdb_r == 0 set \$_addmod2gdb_bss = \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].address set \$_addmod2gdb_c = \$_addmod2gdb_c + 1 set \$_addmod2gdb_i = \$_addmod2gdb_i + 1 loop_continue end set \$_addmod2gdb_i = \$_addmod2gdb_i + 1 end add-symbol-file $MODFILE \\ \$_addmod2gdb_text \\ -s .data \$_addmod2gdb_data \\ -s .bss \$_addmod2gdb_bss end EOF else echo "printf \"No such module file : $MODFILE\n\"" fi * 参考资料 官方网站 http://kgdb.linsyssoft.com/ |
|
|
沙发#
发布于:2008-08-07 09:32
请问斑竹,linux下有没有内核开发的IDE,类似于windows的driverstudio那样的集成开发环境。
|
|
板凳#
发布于:2008-08-07 14:10
没有。可也不需要。他的make制作的很精良,比windows好多了
|
|
|
地板#
发布于:2008-08-11 16:44
其实我觉得,make不是问题,关键是断点和跟踪调试,使用命令行断点和跟踪,觉得真是不方便啊!可能受windows影响太深了啊!
|
|
地下室#
发布于:2008-08-12 10:27
看怎么理解了。
windows的ide是集成了make和调试器。而linux则遵循简单原则,make是make,调试器是调试器。如果想得到windows的ide相似的效果,可以考虑emacs工具。 |
|
|
5楼#
发布于:2008-08-18 14:52
emacs能够调试内核源码吗?
|
|
6楼#
发布于:2008-08-18 14:58
还有,搂主说2.6.25以上内核集成了kgdb,我刚下的2.6.26.2内核,里面的确集成了kgdb。看过以前搂主的一篇往内核打kgdb补丁,在一台主机下运行两个vmware linux进行内核调试。那么可不可以用两个vmware linux来调试2.6.26的内核呢?
为什么我的target在启动后,只会等待kgdb很短的时间,然后就进入X windows了呢? |
|
7楼#
发布于:2008-08-26 10:19
这个可以的,我做的就是在两台虚拟机之间做的调试
|
|
|
8楼#
发布于:2008-08-29 10:09
现在目标机不能中断停止的问题解决了,是因为2.6.26内核的kgdb的启动需要一个新参数kgdbdoc!
现在又有一个问题了,目标机启动中断停止后,显示Waiting for connection from remote gdb...!我需要使用modgdb调试模块,发现在目标机下不能输入命令!!这是什么原因呢? |
|
9楼#
发布于:2009-03-29 13:19
我想请问楼主!我的内核是2.6.21的!怎么办呢?找不到KGDB的补丁哦
|
|
10楼#
发布于:2009-04-01 10:15
回上两层的
1.貌似现在的gdb6.0 比modgdb 好用 我现在就用普通的gdb 调试module没问题 2. 2.6.21属于前不着村 后不挨店的 你只有在6.25以后版本开发吧 之后再拿来21编译 有问题的话应该改一下就好了 |
|