LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 3844|回复: 2

使用 KGDB 调试 Linux 内核

[复制链接]
发表于 2008-7-20 21:26:50 | 显示全部楼层 |阅读模式
原文 : http://blog.chinaunix.net/u/8057/showart_1087126.html

Author : ZC Miao <hellwolf.misty@gmail.com>
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)
[color="Red"]注:当使用 qemu 的时候, 可以直接使用 qemu -s -S 功能调试,不需要 KGDB 的支持,这里的例子是模拟一个类似调试真实平台的例子。



* 配置和启动
1. 内核选项
kgdb 选项 :
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
选项在 Kernel hacking 里可以找到
同时,为了能在系统运行时中断系统并出发远程 gdb,必须打开内核 Magic Sys-Rq 键选项 :
CONFIG_MAGIC_SYSRQ=y
打开内核符号调试:
CONFIG_DEBUG_INFO=y
[color="Red"]注意事项:
CONFIG_DEBUG_RODATA 必须被禁用,否则kgdb讲无法正常使用。

2. 启动 qemu
  1. qemu ${QEMU_ARGS} \
  2.         -kernel "$VMLINUZ" \
  3.         -append "${KERNEL_CMDLINE} kgdboc=ttyS0,115200 kgdbwait" \
  4.         -serial tcp::${KGDB_TCPPORT},server \
  5.         -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 会在内核启动的某个阶段得到控制权:
  1. Remote debugging using tcp:localhost:4555
  2. kgdb_register_io_module (new_kgdb_io_ops=<value optimized out>)
  3.     at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/kernel/kgdb.c:1674
  4. (gdb)
复制代码
现在就可以开始使用 kgdb 调试内核了。


* 基本功能
1. 杀死 gdb
如果 gdb 遇到问题无法相应了,可以在测试系统上运行下列命令中断调试:
  1. echo -e "\003" > /dev/ttyS1
复制代码
如果你重启系统了,gdb 也需要重新连接系统。

2. 中断测试系统
为了在测试系统运行的时候让 gdb 获得控制权,需要使用 Sys-Rq。关于 Sys-Rq 的详细设置方法,见 Documentation/sysrq.txt。 你可以在系统运行时键盘上输入 Sys Rq+g,然后 gdb 就会出现:
  1. Program received signal SIGTRAP, Trace/breakpoint trap.
  2. [Switching to Thread -1]
  3. sysrq_handle_gdb (key=103, tty=0xc7869800)
  4.     at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/kernel/kgdb.c:1674
  5. (gdb)
复制代码
如果你不知道如何在键盘上输入,或者你需要程序控制,则可以用下列等价命令模拟:
  1. # echo g > /proc/sysrq-trigger
复制代码

3. 继续执行测试系统
  1. (gdb) c
复制代码
即可

4. 设置断点,查询变量,单步调试,和调用堆栈
这些都和调试普通程序一样,比如:
  1. (gdb) b        sys_open
  2. Breakpoint 1 at 0xc046d268: file /data/home/hellwolf/mydoc/prog/linux/linux-2.6/
  3. fs/open.c, line 1107.
  4. (gdb) c
  5. Continuing.
  6. [New Thread 612]
  7. [Switching to Thread 612]
  8. Breakpoint 1, sys_open (filename=0xb7f68a9c "/etc/ld.so.cache", flags=0,
  9.     mode=0) at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/fs/open.c:1107
  10. (gdb) p filename
  11. $1 = 0xb7f68a9c "/etc/ld.so.cache"
  12. (gdb)n
  13. (gdb)bt
  14. #0  sys_open (filename=0xb7f68a9c "/etc/ld.so.cache", flags=0, mode=0)
  15.     at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/fs/open.c:1113
  16. #1  0xc0403976 in system_call ()
  17.     at /data/home/hellwolf/mydoc/prog/linux/linux-2.6/arch/x86/kernel/entry_32.S
  18. :377
  19. #2  0x00000000 in ?? ()
复制代码

5. 局限性
kgdb 不支持 tracepoints 和 watchpoints,除非有硬件调试器支持。
kgdb 的 threads 也无法正常使用。


* GDB 脚本
gdb 可以定义 sequences 脚本, 功能局限,但是可以用来简化一些工作。比如在 gdb 调试状态下 lsmod 的脚本:
  1. set $_lsmod_modules = modules->next
  2. #       1        10   15
  3. printf "Module          Size\n"  
  4. while $_lsmod_modules != &modules
  5.         set $_lsmod_module = (struct module*)((unsigned char*)$_lsmod_modules-(unsigned int)&(((struct module*)0)->list))
  6.         printf "%-15s %d\n", $_lsmod_module->name, $_lsmod_module->init_size + $_lsmod_module->core_size
  7.         set $_lsmod_modules = $_lsmod_modules->next
  8. end
复制代码
用 source 命令可以读入外部的 gdb 命令序列文件。
gdb 脚本最大的局限性在于,没有内置的字符串比较功能,比如我要得到一个(struct module*),我必须遍历模块列表然后做字符串匹配。这里我用了一个比较特殊的解决办法,我定义了两个gdb sequences :
  1. define xsrun
  2.         if $argc == 0
  3.                 printf "Usage : xsrun xsourcecmd args...\n"
  4.         else
  5.                 shell rm -f /tmp/*.xsource
  6.                 shell echo 0 > /tmp/seq.xsource
  7.                 if $argc == 1
  8.                         xsource '$arg0'
  9.                 end
  10.                 if $argc == 2
  11.                         xsource '$arg0' '$arg1'
  12.                 end
  13.                 if $argc == 3
  14.                         xsource '$arg0' '$arg1' '$arg2'
  15.                 end
  16.                 if $argc == 4
  17.                         xsource '$arg0' '$arg1' '$arg2' '$arg3'
  18.                 end
  19.                 if $argc == 5
  20.                         xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4'
  21.                 end
  22.                 if $argc == 6
  23.                         xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5'
  24.                 end
  25.                 if $argc == 7
  26.                         xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6'
  27.                 end
  28.                 if $argc == 8
  29.                         xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7'
  30.                 end
  31.                 if $argc == 9
  32.                         xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8'
  33.                 end
  34.                 if $argc == 10
  35.                         xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8' '$arg9'
  36.                 end
  37.         end
  38. end
  39. define xsource
  40.         if $argc == 1
  41.                 shell $TOOLSDIR/kgdb-xsource '$arg0'
  42.         end
  43.         if $argc == 2
  44.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1'
  45.         end
  46.         if $argc == 3
  47.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2'
  48.         end
  49.         if $argc == 4
  50.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3'
  51.         end
  52.         if $argc == 5
  53.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4'
  54.         end
  55.         if $argc == 6
  56.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5'
  57.         end
  58.         if $argc == 7
  59.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6'
  60.         end
  61.         if $argc == 8
  62.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7'
  63.         end
  64.         if $argc == 9
  65.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8'
  66.         end
  67.         if $argc == 10
  68.                 shell $TOOLSDIR/kgdb-xsource '$arg0' '$arg1' '$arg2' '$arg3' '$arg4' '$arg5' '$arg6' '$arg7' '$arg8' '$arg9'
  69.         end
  70.         if $argc > 0
  71.                 source /tmp/current.xsource
  72.         end
  73. end
复制代码
其中 $TOOLSDIR/kgdb-xsource 是我自定义的一个寻找名字为 '$arg0' 的 xsource 脚本的工具,我的实现就是在某个目录下找名字为 $arg0 的文件:
  1. $ cat tools/kgdb-xsource
  2. #!/bin/bash
  3. . $(dirname "$0")/common.sh
  4. if [ "$#" == 0 ];then
  5.         echo "kgdb-xsource xsourcecmd args...\\n"" > /tmp/current.xsource
  6. else
  7.         XSOURCECMD=$1
  8.         shift
  9.         XSOURCESEQ=$(cat /tmp/seq.xsource)
  10.         XSOURCESEQ=$((${XSOURCESEQ}+1))
  11.         echo ${XSOURCESEQ} > /tmp/seq.xsource
  12.         XSOURCEOFILE=/tmp/${XSOURCESEQ}-${XSOURCECMD}.xsource
  13.         if [ -f "$XSOURCEDIR/$XSOURCECMD" ] && [ -x "$XSOURCEDIR/$XSOURCECMD" ];then
  14.                 $XSOURCEDIR/$XSOURCECMD "$@" > ${XSOURCEOFILE}
  15.                 rm -f /tmp/current.xsource
  16.                 ln -s ${XSOURCEOFILE} /tmp/current.xsource
  17.         else
  18.                 echo "printf "No such xsource command : $XSOURCECMD\\n"" > /tmp/current.xsource
  19.         fi
  20. fi
复制代码
这些脚本的目的就是,通过外部脚本生成 gdb 脚本,然后再让 gdb 执行。他首先寻找 xsource 脚本,然后运行它,得到的输出(gdb sequences)保存到 /tmp 下的某个文件中,并链接为 /tmp/current.xsource, 然后让 gdb 执行改文件。
比如 strcmp_vs 脚本,这个脚本就是为了比较内部变量和一个字符串的:
  1. $ cat tools/xsource.d/strcmp_vs
  2. #!/usr/bin/perl
  3. if (@ARGV != 3) {
  4.     print "printf "Usage : strcmp result var str\\n"";
  5.     exit 1
  6. }
  7. ($result, $var, $str) = @ARGV;
  8. print 'if ';
  9. $i = 0;
  10. for (0..length($str)-1) {
  11.     $i = $_;
  12.     $c = substr($str, $i, 1);
  13.     print ' && ' if $i;
  14.     print "${var}[$i] == '$c'";
  15. }
  16. ++$i;
  17. print " && ${var}[$i] == '\\0'
  18.     set \$$result = 0
  19. else
  20.     set \$$result = -1
  21. end
  22. ";
复制代码
利用这个基本命令,我们现在就可以实现 getmod 功能拉:
  1. $ cat tools/xsource.d/getmod
  2. #!/bin/sh
  3. [ $# != 2 ] && echo "printf "Usage : getmod modvar modname\n"" && exit 1
  4. MODVAR=$1
  5. MODNAME=$2
  6. cat <<EOF
  7. set \$_getmod_modules = modules->next
  8. set \$_getmod_modfound = 0
  9. while \$_getmod_modules != &modules
  10.         set \$_getmod_module = (struct module*)((unsigned char*)\$_getmod_modules-(unsigned int)&(((struct module*)0)->list))
  11.         xsource strcmp_vs _getmod_modfound \$_getmod_module->name $MODNAME
  12.         if \$_getmod_modfound == 0
  13.                 set \$$MODVAR = \$_getmod_module
  14.                 set \$_getmod_modfound = 1
  15.                 loop_break
  16.         end
  17.         set \$_getmod_modules = \$_getmod_modules->next
  18. end
  19. if \$_getmod_modfound != 1
  20.         printf "Module \"$MODNAME\" cannot be found\\n"
  21.         set \$$MODVAR = 0
  22. end
  23. EOF
复制代码
运行范例:
  1. (gdb) xsource getmod modvar hello
  2. (gdb) print $modvar->sect_attrs->attrs[0]->name
  3. $9 = 0xc7855b40 ".note.gnu.build-id"
复制代码

* 调试内核模块

调试内核模块需要额外的步骤,因为内核模块是动态载入的,gdb 一开始无法从内核 VMLINUX 文件中得到模块中的函数和变量地址信息。这里必须用 gdb 的 add-symbol-file 命令:
  1.         add-symbol-file $MODFILE \\
  2.                 $TEXT_ADDR \\
  3.                 -s .data $DATA_ADDR \\
  4.                 -s .bss $BSS_ADDR
复制代码
其中 MODFILE 是 .ko 文件, 如何获得那三个地址是这里最关键的。这里有两种方法:
1. 从 sysfs 中获得
这种方法比较简单:
  1. $ cat /sys/module/snd/sections/.text
  2. 0xf8f83000
  3. $ cat /sys/module/snd/sections/.data
  4. 0xf8f8d040
  5. $ cat /sys/module/snd/sections/.bss
  6. 0xf8f8e280
复制代码

2. 用 gdb 脚本
这个脚本先通过上面的 getmod 脚本得到(struct module*),然后从里面得到所需要的 sections 的地址,最后调用 add-symbol-file 命令。不过因为有大量的 gdb 交互,所以会比较慢
  1. $ cat tools/xsource.d/addmod2gdb
  2. #!/bin/sh
  3. # addmod2gdb($modname)
  4. [ $# != 1 ] && echo "printf "Usage : addmod2gdb modname\n"" && exit 1
  5. MODNAME=$1
  6. MODFILE=$KMODSDIR/$MODNAME/${MODNAME}.ko
  7. if [ -f $MODFILE ];then
  8.         cat <<EOF
  9. xsource getmod _addmod2gdb_mod $MODNAME
  10. if \$_addmod2gdb_mod != 0
  11.         set \$_addmod2gdb_i = 0
  12.         set \$_addmod2gdb_c = 0
  13.         while \$_addmod2gdb_i < \$_addmod2gdb_mod->sect_attrs->nsections
  14.                 if \$_addmod2gdb_c == 3
  15.                         loop_break
  16.                 end
  17.                 xsource strcmp_vs _addmod2gdb_r \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].name .text
  18.                 if \$_addmod2gdb_r == 0
  19.                         set \$_addmod2gdb_text = \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].address
  20.                         set \$_addmod2gdb_c = \$_addmod2gdb_c + 1
  21.                         set \$_addmod2gdb_i = \$_addmod2gdb_i + 1
  22.                         loop_continue
  23.                 end
  24.                 xsource strcmp_vs _addmod2gdb_r \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].name .data
  25.                 if \$_addmod2gdb_r == 0
  26.                         set \$_addmod2gdb_data = \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].address
  27.                         set \$_addmod2gdb_c = \$_addmod2gdb_c + 1
  28.                         set \$_addmod2gdb_i = \$_addmod2gdb_i + 1
  29.                         loop_continue
  30.                 end
  31.                 xsource strcmp_vs _addmod2gdb_r \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].name .bss
  32.                 if \$_addmod2gdb_r == 0
  33.                         set \$_addmod2gdb_bss = \$_addmod2gdb_mod->sect_attrs->attrs[\$_addmod2gdb_i].address
  34.                         set \$_addmod2gdb_c = \$_addmod2gdb_c + 1
  35.                         set \$_addmod2gdb_i = \$_addmod2gdb_i + 1
  36.                         loop_continue
  37.                 end
  38.                 set \$_addmod2gdb_i = \$_addmod2gdb_i + 1
  39.         end
  40.         add-symbol-file $MODFILE \\
  41.                 \$_addmod2gdb_text \\
  42.                 -s .data \$_addmod2gdb_data \\
  43.                 -s .bss \$_addmod2gdb_bss
  44. end
  45. EOF
  46. else
  47.         echo "printf "No such module file : $MODFILE\n""
  48. fi
复制代码

* 参考资料
官方网站 http://kgdb.linsyssoft.com/
 楼主| 发表于 2008-7-23 00:20:39 | 显示全部楼层
更新一句:
注:当使用 qemu 的时候, 可以直接使用 qemu -s -S 功能调试,不需要 KGDB 的支持,这里的例子是模拟一个类似调试真实平台的例子。
回复 支持 反对

使用道具 举报

发表于 2008-8-4 16:28:22 | 显示全部楼层
太感谢斑竹!!!!
很不错!!!
kgdb终于集成到Linux内核中了。。。。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表