必备条件
---------硬件
---------软件
---------硬件安置
内核
---------准备
---------连接并调试内核
模块调试
---------开发、测试机上的条件
---------于测试机上装载模块
---------于GDB中装载模块
---------卸载模块
调试内核
---------以GDB调试内核
---------控制内核之执行
---------栈之跟踪
---------内联函数
---------线程分析
---------Watchpoint
模块调试
---------内联函数
---------模块装载与卸载
---------loadmodule.sh之工作
---------init_module()之调试
体系依赖
---------x86 64
---------PowerPC
---------S390
************************************************************************************************************************************************
【 必备条件 】
『 硬件 』
KGDB需要两台x86(或其他体系)机器。一台运行被调试内核(测试机),另一台运行gdb(开发机)。
一个开发端 可以 被几台 测试端 使用。开发机、测试机的体系结构可以不同。但是,测试机 和 被调试内核的 体系结构必须匹配。
开发机 运行 每一台测试机上GDB 的一份拷贝。至少需要 128MB RAM来装载调试信息。
开发/测试机串口间用串行线连接。各机器需要空modem cable来连接串口。
KGDB近来的版本也可以运行于Ethernet之上,而如果KGDB运行在Ethernet上,串行线就不再需要。
如果需要模块调试,两台机器可以通过网络进行连接。网络连接需要模块调试组件锁所使用的rcp、rsh命令。KGDB本身不需网络连接,而只要一根串行线。通过
网络来连接机器,并查看测试机也是方便的。
『 软件 』
需要RH 7.3或之后版本。
测试机上运行被调试内核。如调试一个2.4.3版本内核,其需要在测试机上运行。开发机上不需要运行特定内核。
如果模块调试是不需要的,那么以上软件已经足够。如果需要,GDB、RH 7.3的模块组件是不够的。支持模块调试的gdb在本站可用,而模块组件在其他站点才
可获得。一个装载测试机的shell脚本需要放于开发机之上。更多的信息参见 模块调试 一节。
『 硬件安置 』
下面描述GDB运行于串行口上的设置。KGDB over Ethernet 不需要这个安装。
Null-modem Cable
使用null-modem电缆连接机器。null-modem电缆是连接串口的的三线电缆。末端是DB9、DB25连接口。
双端DB25连接口连接线如下所示:
Connector 1 pins - Connector 2 pins
2 (TxD) - 3 (RxD)
3 (RxD) - 2 (TxD)
7 (GND) - 7 (GND)
串行线传输率
串口传输率从110 baud ~ 115200 baud。波特率由串口芯片决定。默认为9600。一般来说,在测试内核和GDB间的波特率宁愿低一点。串口支持这一系列波
特率不是问题,但null-modem电缆却不是。推荐检测一下串口、null-modem电缆使用kgdb时所支持的最大速度。
多线程分析时,建议使用115200 baud,因为通常得到一个线程列表所需时间会比较长。
************************************************************************************************************************************************
【 内核 】
『 准备 』
给内核源码打上kgdb补丁。[ kernel hacking ]-----[ Remote kernel debugging ]。这将在内核中开启Kgdb代码。
KGDB的一些配置选项如下所述。
Thread analysis:
gdb可以和线程列表的kgdb stub交谈,获得线程堆栈踪迹。这个选项也开启了 帮助gdb获得线程精确状态 的代码。
线程分析添加了一些函数,如 调用时间表和向下函数的开支 (overhead to schedule and down functions)。
你可以禁用这个选项,如果你不想在执行速度上得到损失的话。
Console Messages Through GDB:
kgdb stub 将通过gdb发送控制台信息。由测试机发送的控制台信息 将 出现在运行gdb的开发机的终端上。其他控制台不受这个选项的影响。
Enable kernel asserts:
这个选项在kgdb 2.0前的版本已被删除。其开启内核断言。内核断言是一个条件,如果关闭执行路径期将要产生的错误,然后呼叫kgdb。内核断言帮助修改内核
、编写驱动。而Bugs也可以立刻跟踪,因bugs产生的无效条件在不同位置被检查。
内核断言添加检测断言条件的开支。如果不想获得这个开支的话,屏蔽它。
内核配置后,编译,修改grub。
kgdb stub需要以下内核命令行中的选项
kgdbwait : 让kgdb在内核启动时,等待gdb连接。
kgdb8250=<端口号>,<端口速度>
端口号在 0 ~ 3 之间,分别对应ttyS0(COM1) ~ ttyS3(COM4);端口速度 9600、19200、38400、57600、115200
kgdb2.0以前的版本,以上选项形式如下:
gdb : 内核启动时等待gdb连接
gdbttyS= : 告诉stub串行线使用,0~3对应ttyS0~ttyS3
gdbbaud= : 9600~115200
下面是一个实例:
——开发机端:——
1、解压内核源码
$ cd /mnt/work/build/old-pc
$ bunzip2 -c ~/linux-2.4.6.tar.bz2 | tar xvf -
$ mv linux linux-2.4.6-kgdb
2、打上补丁
$ cd linux-2.4.6-kgdb
$ patch -pl < ~/linux-2.4.6-kgdb.patch
3、配置内核
$ make menuconfig
配置[drivers]、[other kernel option]选项
[Kernel hacking] --
-- KGDB : kernel debugging with remote gdb --
--KGDB : Thread analysis
--KGDB : Console messages through gdb
[Device drivers] --
--Character devices --
--Serial drivers --
--KGDB: On generic serial port (8250)
kgdb 2.0.1前的版本
Goto "kernel hacking"
Enable "Remote (serial) kernel debugging with gdb"
(可选) Enable "Console messages through gdb (NEW)"
4、建立内核
$ make dep
$ make bzImage
5、拷贝内核至目标机器
(这里假定工作在开发机的用户 有在测试机root的帐号 拥有rsh权限)
$ rcp System.map testmach:/boot/System.map-2.4.6-kgdb
$ rcp arch/i386/boot/bzImage testmach:/boot/vmlinuz-2.4.6-kgdb
——测试机端:——
6、添加grub条目
title 2.6.0 kgdb
root (hd0,0)
kernel /boot/vmlinuz-2.6.0 ro root=/dev/hda1 rootfstype=ext3 kgdbwait kgdb8250=1,57600
LILO的条目(2.0以前的kgdb)
image=/boot/vmlinuz-2.4.6
label=kgdb
read-only
root=/dev/hda3
append="gdb gdbttyS=1 gdbbaud=115200"
测试机上的模块:
推荐:最好把所有驱动编译进内核, 而不是 编译成模块。因为后者还需要使用loadmodule.sh装载模块。(详细的参见 于测试机上装载模块 一节)。
『 连接并调试内核 』
进入内核源目录运行gdb,在命令行将vmlinux作为目标文件。
在测试机上运行kgdb kernel,直至其打印出如下信息:
Waiting for connection from remote gdb...
如果使用的kgdb是for linux 2.6.8-rc1之前的,将看不到上面信息( early_param()给出的 ),取而代之的是下面的信息:
Uncompressing Linux... Ok, booting the kernel.
然后在gdb中键入 target remote 命令 来连接目标机。这条命令需要指定串行线路径。
gdb 和 kgdb stub 将建立连接,gdb也将控制目标内核。
这时可以插入断点,打印出变量值,中断、继续执行。
一下是一个实例:
1、设定串行线适当值
$ stty ispeed 115200 ospeed 115200 < /dev/ttyS1
2、开启gdb。
由于vmlinux包含了许多调试信息,这将花一些时间。
如果还要调试模块,需要在站点下载一个gdb。(参见 模块调试 一节),其放在 /usr/local/bin/gdbmod. 偶尔做应用程序调试的话,可以改变一下名称。
当然,你也可以使用RH提供的默认gdb,如果不进行模块调试的话。
$ cd /
mnt/work/build/old-pc/linux-2.4.6-kgdb
$ gdbmod vmlinux
GNU gdb 20000204
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...
(gdb)
——测试机端:——
3、选择kgdb kernel,内核初始化等待gdb连接,出现以下信息:
Waiting for connection from remote gdb...
——开发机端:——
4、使用 target 连接测试机端
(gdb) target remote /dev/ttyS1
Remote debugging using /dev/ttyS1
breakpoint () at gdbstub.c:1153
1153 }
(gdb)
5、gdb已经连接了kgdb kernel,运行 c 命令 继续测试内核进度。
(gdb) c
Continuing.
以下显示的信息,需要在内核配置时选上 [ console messages though gdb ]
PCI: PCI BIOS revision 2.10 entry at 0xfb230, last bus=0
PCI: Using configuration type 1
PCI: Probing PCI hardware
Limiting direct PCI/PCI transfers.
Activating ISA DMA hang workarounds.
isapnp: Scanning for PnP cards...
isapnp: No Plug & Play device found
Linux NET4.0 for Linux 2.4
Based upon Swansea University Computer Society NET3.039
Starting kswapd v1.8
如果发生kernel panic 情况,而不是declaring a pnic ,内核将首先将控制权交于gdb,以便于进行分析。
************************************************************************************************************************************************
【 模块调试 】
『 开发、测试机上的条件 』
开发机上的gdb
gdb包含模块调试功能。可在kgdb下载页 ( )找到。gdb for kgdb 1.9 以及开发版本是不同的。这里的gdb
来源于gdb 6.0,包含gdb6.0所有功能,外加自动检测模块装载、卸载的能力。你可以下载源码,建立和安装gdb于/usr/local/bin/gdbmod。 (
)
在2.6内核中,使用开发版kgdb 的 kgdb 2.0 - 2.1.0 ,模块调试无法工作。
测试机上不需要特别设置。
编译并安装模块:
目标文件编译时需要有-g选项。
内核源中的模块应编译安装在测试机,或位于ramdisk。kgdb stub 添加 -g 于内核编译标识。这个对内核或模块编译都适用。你也许面对一个难题,-g选项可
以增加每个模块的体积大约10倍。
因为
内核源是在开发机上编译,你可能会发现很容易在相同机器上安装模块 以及之后 拷贝模块目录至测试机。
早于KGDB1.8版的模块调试设置。
模块调试能力 被建于 kgdb stub内,gdb for kgdb 1.9版本之前 需要外部支持(以 调试模块的脚本 形式)。关于这些版本的相关设置描述如下。
modutils
modutils 2.3.19 (以上版本)需要安装于 测试机。
modutils 2.3.19之前的insmod命令 在内核内初始化模块之前,产生模块 map 文件。以此功能,模块初始化函数可以被调试。
在此处下载 ( )。RH 7.0/7.1包含了所需modutils,当然基于它们的测试机就不需要做什么设
置。
gdb
gdb中有一个bug,由 add-symbol-file 命令 装载的symbols,gdb计算地址会产生错误。这个已经被修复。不幸的是,这个修复不会作用于gdb5.0。请使用
开发版中支持内核模块调试的gdb。
你可以使用为RH9创建的gdbmod
其也包含在站点上 。
注意需要 调试内核模块 的才建议使用。
上面的bug已被gdb6.0修复。用此取代也可以。
一个建议,将gdb安放于/usr/local/bin/gdbmod,改变名称是为了只在内核调试时使用,默认安装gdb用于进行一般调试。
装载模块:
要调试内核模块,可使用 add-symbol-file 命令来装载模块目标文件。这条命令需要模块文件中sections的地址。
loadmodule.sh () 可使 把装载模块进入内核 的过程自动化,并产生一个 gdb 脚本(用于将模
块文件 载入 gdb)。
脚本下载在开发机上, 测试机名 需要在脚本中指明:
TESTMACHINE=old-pc
脚本产生gdb scripts。这些产生的脚本将目标文件装载进gdb。产生这些脚本的源脚本路径也需要指定:
GDBSCRIPTS=/home/akale/mnt/work/gdbscripts
rsh permissions
测试机在root目录下有一个.rhosts文件,其给定了rsh permissions,写入开发机名字及开发者帐号。
.rhosts无法被Group和其他人读取。
# cd /root
#cat .rhosts
askii-pc amit
『 装载模块 』
通常用 insmod 或 modprobe 装载查看。GDB自动发现已装载模块,并装载目标文件以获得调试信息。
为了让GDB知道模块文件装载于何处,需要设置GDB变量:
solib-search-path
使用info sharelibrary来确定GDB确实发现已装载模块。
(gdb) set solib-search-path /lib/modules/2.4.23/kernel/drivers/block
(gdb) c
Continuing.
# modprobe loop
[New Thread 980]
Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at kgdbstub.c:1005
1005 atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0xc482a060 0xc482c0ef Yes /lib/modules/2.4.23/kernel/drivers/block/loop.o
(gdb) br lo_open
Breakpoint 1 at 0xc482b85c: file loop.c, line 911.
--在Kgdb1.8 或者更早的版本中装载模块--
1.8版本以前的需要loadmodule.sh脚本来装载模块。如果已装载,则不需要使用。
下面是一个使用loadmodule.sh的实例:
$ cd /mnt/work/build/old-pc/trfs/modules/trfs
$ loadmodule.sh trfs
Copying /mnt/work/build/old-pc/trfs/modules/trfs/trfs to old-pc
Loading module /mnt/work/build/old-pc/trfs/modules/trfs/trfs
Warning: modutils is reading from /etc/conf.modules because
/etc/modules.conf does not exist. The use of /etc/conf.modules is
deprecated, please rename /etc/conf.modules to /etc/modules.conf
as soon as possible. Command
mv /etc/conf.modules /etc/modules.conf
Generating script /mnt/work/gdbscripts/loadtrfs
$
上面产生的Warning是针对modutils的细节。其不影响kgdb的工作。warning的获得有无,取决于你的modutils升级到2.3.19后modules.conf有无变化。其实
RH7.0之后的将不会得到上面的warning。
命令产生了一个脚本,/mnt/work/gdbscript/loadtrfs。
显示如下
$ cat /mnt/work/gdbscripts/loadtrfs
add-symbol-file /mnt/work/build/old-pc/trfs/modules/trfs/trfs 0xc1808060 -s .text.lock 0xc180968c -s .rodata 0xc18097a0 -s __ksymtab
0xc1809a44 -s .data 0xc1809bc0
脚本中包含了一个gdb命令: add-symbol-file
指向模块目标文件的路径是第一个参数。
.text段的地址是传递的第二个参数。
目标文件中不同段的地址是更深一层的参数。
gdb需要这些段地址来计算一个目标文件中的符号地址。
模块将在此命令运行后装载进测试机。
$ rsh -l root old-pc /sbin/lsmod
Module Size Used by
trfs 7504 0 (unused)
$
『 在gdb中装载模块--更详细的关于1.8以前版本的 』
--将模块文件装载进带有kgdb 1.8/以前 的gdb--
--loadmodule.sh运行过程如下--
gdb调试目标模块需要调试信息。 一个目标文件被 gdb 命令: add-symbol-file 装载。
在测试内核中装入模块之后,产生于loadmodule.sh的gdb脚本,是将模块目标文件装入gdb的发动者。
让gdb等待用户命令,在正运行gdb的开发机的终端上 Ctrl + C即可。
presses Ctrl + C.
Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at gdbstub.c:1153
1153 }
(gdb) source /mnt/work/gdbscripts/loadtrfs
add symbol table from file "/mnt/work/build/old-pc/trfs/modules/trfs/trfs" at
.text_addr = 0xc1808060
.text.lock_addr = 0xc180968c
.rodata_addr = 0xc18097a0
__ksymtab_addr = 0xc1809a44
.data_addr = 0xc1809bc0
(gdb)
(gdb) c
Continuing............
--不使用loadmodule.sh装载的模块--
模块可以 自动装载,也可以使用 modprobe 、insmod来完成。
而通过这些方式装在的模块,不得不从目标机获得模块信息,并传给gdb。
使用 getsyms.sh ( ) 在目标机上得到相关信息。
下面使用一个装载有模块audio.o的内核来说明。
以模块目标文件名为参数来运行上面脚本。
$ bash getsyms.sh audio.o
add-symbol-file audio.o 0xd00b4060 -s .rodata 0xd00bc9bc -s .bss 0x -s .data 0xd00bf4e0
$
给gdb提供地址
(gdb) add-symbol-file audio.o 0xd00b4060 -s .rodata 0xd00bc9bc -s .bss 0x -s .data 0xd00bf4e0
add symbol table from file "audio.o" at
.text_addr = 0xd00b4060
.rodata_addr = 0xd00bc9bc
.bss_addr = 0x0
.data_addr = 0xd00bf4e0
(y or n) y
Reading symbols from audio.o...done.
(gdb)
现在gdb关于模块的调试信息已就绪,可以设置断点调试了。
直到getsyms.sh运行,模块中init_module函数才运行,所以这个程序不能用来调试 init_module函数。你不得不运行loadmodule.sh继续之。
『 卸载和装载模块 』
kgdb1.9才具有检测装载、卸载模块的能力。这根据你的需要而定。gdb将装/卸载相应的模块。
1.8以前的需要以下操作。
通常rmmod来卸载。再使用loadmodule.sh来装载。这之后你无法立即定位由loadmodule.sh产生的gdb脚本。这是因为gdb将已装载所有符号。你需要从gdb
中移除所有符号表,然后再开始。
使用 symbol-file 命令。
其移除已存在的符号表,并装载一个新的。
symbol-file vmlinux 将装载所有vmlinux 符号。然后使用通常过程来装载模块。
已发现以上过程有些时候不能正常工作。一个好的方法是从gdb中退出,再度更新。
可参考下文 使用gdb进行内核调试 ( ) 。
需要记住的是,上次gdb中设置的断点将在装入新的gdb时消失了。
************************************************************************************************************************************************
【 调试内核 】
『 以GDB调试内核 』
--使用gdb--
gdb内核调试的使用和调试应用程序相似。这里只是简要介绍一下调试时可能于到的一些情况。
大多数的使用问题在于在内核中使用内联函数。
kgdb支持gdb执行控制命令、堆栈跟踪、线程分析。它不支持gdb watchpoints。kgdb watchpoints 是通过gdb宏被访问的。这些宏的使用也将简要描述一下
。
info gdb 查阅gdb文档
help 针对相应的命令和标题名给出简短的描述
--杀死或终止gdb--
如果gdb不相应用户输入,可以终止它。
如果当gdb被终止,而测试内核被kgdb控制时,新启动的gdb将可以和kgdb再次建立连接。另外需要将内核载入kgdb中。
传送三个Ascii 字符给串行线。例子如下:
$ echo -e "03" > /dev/ttyS1
当gdb打印出命令提示,你可以推出,然后立即开启新的gdb来连接kgdb。
gdb在显示命令提示前移除所有断点。所以,新的gdb启动后,在内核中将没有断点。这需要再次加入所有断点。
但是,如果一个运行的gdb在加入断点后被杀死,这些断点依然存在于内核中。新的GDB却不知道他们的存在。因此,其中一个断点在并发执行期间偶然碰上,
gdb将不能给予适当的处理。因为此断点更改了代码,这一断点之后的执行是不可预知的。
发生这种事情时,推荐硬件重启一下。
kgdb1.6之前的版本在stub中维护这些断点,因此不存在这个问题。
kgdb stub 处理 kgdb硬件watchpoint,gdb全然不知所云。因此如果一个gdb被终止后,对于watchpoint将没有任何问题。
--测试机重启--
如果一台测试机器重启,在开始再次调试内核之前,推荐终止或杀死gdb。如果没有这样做,kgdb将仍然与gdb相连。
如果一些断点是在前一次内核执行中加入的,gdb将尝试在连接时移除他们。因为断点将在新内核执行中消失,结果是不可预知的。
『 控制内核之执行 』
--停止内核执行--
Ctrl + C 可以在gdb终端中停止内核执行。
gdb传送一个停止信息给kgdb stub,这将取得内核控制权,并联系gdb。然后gdb显示命令提示符,等待用户输入。
这时,所有处理器将被kgdb控制,内核中没有任何一个部分在执行。你可以使用任何gdb命令。
--继续内核执行--
continue 命令
--断点--
break 命令
在一个函数或者源码行上停止内核执行。此命令用函数名 或 附加 a: 和 行数 的源码文件名 为参数。
--通过代码步进调试--
step 命令步进代码语句调试。
这个命令运行内核中的一个语句,然后移交控制权给gdb。
其也可以步进函数调用。如果你想跳出函数调用的步进,使用 next 命令。
使用step命令,gdb让kgdb在步进时执行一条指令,直到抵达下一条代码语句。
kgdb不得不捕捉所有处理器,并在每一条指令步进后进行释放。如果一条代码语句时一个循环,步进将要花上一段时间。
比如 步进入 copy_to_user 函数,拷贝一个大的缓冲区将消耗一些时间。
next命令也有如此步骤。
『 栈之跟踪 』
一旦gdb进入命令模式,可使用 backtrace 来观察堆栈。
其将显示一个函数调用列表, 从 一个系统调用进入内核 处的函数开始。对于每一个函数,其打印出 下一个被调用函数所在的源码文件名 和 行号 ,以及执行停
止处的最里层函数。也会打印出这些函数的参数值。
使用Ctrl + C 中断调试器,然后如下查看堆栈跟踪:
(gdb) backtrace
#0 breakpoint () at gdbstub.c:1160
#1 0xc0188b6c in gdb_interrupt (irq=3, dev_id=0x0, regs=0xc02c9f9c)
at gdbserial.c:143
#2 0xc0108809 in handle_IRQ_event (irq=3, regs=0xc02c9f9c, action=0xc12fd200)
at irq.c:451
#3 0xc0108a0d in do_IRQ (regs={ebx = -1072672288, ecx = 0, edx = -1070825472,
esi = -1070825472, edi = -1072672288, ebp = -1070817328, eax = 0,
xds = -1072693224, xes = -1072693224, orig_eax = -253,
eip = -1072672241, xcs = 16, eflags = 582, esp = -1070817308,
xss = -1072672126}) at irq.c:621
#4 0xc0106e04 in ret_from_intr () at af_packet.c:1878
#5 0xc0105282 in cpu_idle () at process.c:135
#6 0xc02ca91f in start_kernel () at init/main.c:599
#7 0xc01001cf in L6 () at af_packet.c:1878
Cannot access memory at address 0x8e000
除非被打印出的堆栈帧号 被以一个参数指定给backtrace,gdb将在堆栈跟踪超出可抵达的地址空间时 停止打印回溯跟踪。
函数调用层次如下所示:
ret_from_intr,do_IRQ,handle_IRQ_event,gdb_interrupt
在ext2_readlink中置入断点,存取一个符号链接以使断点可达。
(gdb) br ext2_readlink
Breakpoint 2 at 0xc0158a05: file symlink.c, line 25.
(gdb) c
Continuing.
在测试机端运行命令 ls -l /boot/vmlinuz
Breakpoint 2, ext2_readlink (dentry=0xc763c6c0, buffer=0xbfffed84 "21405",
buflen=4096) at symlink.c:25
25 char *s = (char *)dentry->d_inode->u.ext2_i.i_data;
(gdb) bt
#0 ext2_readlink (dentry=0xc763c6c0, buffer=0xbfffed84 "21405",
buflen=4096) at symlink.c:25
#1 0xc013b027 in sys_readlink (path=0xbfffff77 "/boot/vmlinuz",
buf=0xbfffed84 "21405", bufsiz=4096) at stat.c:262
#2 0xc0106d83 in system_call () at af_packet.c:1878
#3 0x804aec8 in ?? () at af_packet.c:1878
#4 0x8049697 in ?? () at af_packet.c:1878
#5 0x400349cb in ?? () at af_packet.c:1878
在上面的回溯跟踪中,GDB已打印出一些无效堆栈帧。这是因为gdb不知在哪里停止回溯跟踪。我们可以因无效而忽略栈帧3 - 5。系统调用 readlink 在
system_call函数那里进入内核。其显示在af_packet.c中是错误的。
gdb不能计算出正确的代码行,因为这是一个汇编语言文件中的函数。gdb能够正确处理C代码中内联汇编。
更深的层次是 sys_readlink 和 ext2_readlink.
这之后我们使用 delete 移除所有断点并继续之:
(gdb) delete
Delete all breakpoints? (y or n) y
(gdb) c
Continuing.
『 内联函数 』
gdb回溯跟踪所打印的信息,对于查出执行停止处的函数调用层次是足够的。然而对于一个扩展的内联函数中的堆栈帧而言,却是不足的。因为内联函数是指内部
扩展,如果在一个内联函数中停止执行,或者如果一个内部函数的调用是由一个内联函数产生的,gdb显示源代码文件名 和 内联函数中语句的行号。其可能知道
被调用自哪个内联函数,在何处被外部函数调用。如果内联函数被调用两次,或者如果其不知被调用自哪个内联函数,下面的步骤将被用于发现这些信息。
gdb也可以在回溯跟踪中单独以函数名来显示代码地址。内联函数被调用处的语句可以从这些代码地址中发现。
disasfun.sh 可在此处 使用 从vmlinux文件中涉及内核函数的源代码 来实现分解(disassembly)。
vmlinux 中包含内核函数的绝对地址,因此在汇编代码中看到的地址 即内存中的地址。
实例如下所示:
kgdb thread analysis ( CONFIG_KGDB_THREAD )在内核配置中启用。GDB连接入目标内核。
先Ctrl + C ,再设置 断点于 __break()函数, 继续...
Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at gdbstub.c:1160
1160 }
(gdb) break __down
Breakpoint 1 at 0xc0105a43: file semaphore.c, line 62.
(gdb) c
Continuing.
找到断点,在目标机上运行 man lilo ,断点将被发现,gdb将进入命令模式。
Breakpoint 1, __down (sem=0xc7393f90) at semaphore.c:62
62 add_wait_queue_exclusive(&sem->wait, &wait);
(gdb) backtrace
#0 __down (sem=0xc7393f90) at semaphore.c:62
#1 0xc0105c70 in __down_failed () at af_packet.c:1878
#2 0xc011433b in do_fork (clone_flags=16657, stack_start=3221199556,
regs=0xc7393fc4, stack_size=0)
at /mnt/work/build/old-pc/linux-2.4.6-kgdb/include/asm/semaphore.h:120
#3 0xc010594b in sys_vfork (regs={ebx = 1074823660, ecx = 1074180970,
edx = 1074823660, esi = -1073767732, edi = 134744856, ebp = -1073767712,
eax = 190, xds = 43, xes = 43, orig_eax = 190, eip = 1074437320,
xcs = 35, eflags = 518, esp = -1073767740, xss = 43}) at process.c:719
sys_vfork函数中的行号被正确的显示:process.c 719行。我们可以通过列出此行号前后的代码.
(gdb) list process.c:719
714 * do not have enough call-clobbered registers to hold all
715 * the information you need.
716 */
717 asmlinkage int sys_vfork(struct pt_regs regs)
718 {
719 return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0);
720 }
721
722 /*
723 * sys_execve() executes a new program.
如gdb所示,do_fork函数是从sys_vfork函数中被调用的。考虑一下堆栈跟踪中帧2。
gdb显示出其位于 semaphore.h 中的120行。这是正确的。虽然不是太有用。
(gdb) list semaphore.h:118
113 */
114 static inline void down(struct semaphore * sem)
115 {
116 #if WAITQUEUE_DEBUG
117 CHECK_MAGIC(sem->__magic);
118 #endif
119
120 __asm__ __volatile__( <-----
121 "# atomic down operation "
122 LOCK "decl %0 " /* --sem->count */
得到的唯一信息是,其位于 箭头处所指的 do_fork 语句中 的内联扩展 down 函数。
gdb也已经从下一个被称为 0xc011433b 的函数中打印出 do_fork 代码的绝对地址。
这里我们使用disasfun.sh来发现该地址所指的代码位于哪一行。
命令disasfun vmlinux do_fork 所输出的部分内容如下所示:
if ((clone_flags & CLONE_VFORK) && (retval > 0))
c011431d: 8b 7d 08 mov 0x8(%ebp),%edi
c0114320: f7 c7 00 40 00 00 test $0x4000,%edi
c0114326: 74 13 je c011433b
c0114328: 83 7d d4 00 cmpl $0x0,0xffffffd4(%ebp)
c011432c: 7e 0d jle c011433b
#if WAITQUEUE_DEBUG
CHECK_MAGIC(sem->__magic);
#endif
__asm__ __volatile__(
c011432e: 8b 4d d0 mov 0xffffffd0(%ebp),%ecx
c0114331: f0 ff 4d ec lock decl 0xffffffec(%ebp)
c0114335: 0f 88 68 95 13 00 js c024d8a3
down(&sem);
return retval;
c011433b: 8b 45 d4 mov 0xffffffd4(%ebp),%eax <-----
c011433e: e9 8d 00 00 00 jmp c01143d0
查看fork.c,我们可知以上代码位于何处:
fork_out:
if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem)
『 线程分析 』
gdb具有分析应用程序线程的功能,其提供了一个线程列表。kgdb则也相应的提供内核线程调试,通过提供内核中的所有线程列表。
一个应用程序创建的所有线程共享相同的地址空间。相似的,所有内核线程共享内核地址空间。
每一个内核线程的用户地址空间也许不同。因此,gdb线程可以很好的分析 位于内核地址空间中的内核代码 和 数据结构 。
gdb info 页提供了更多使用gdb线程分析功能的信息。
下面是一个实例:
info threads_name 提供内核线程列表.
gdb) info thr
21 thread 516 schedule_timeout (timeout=2147483647) at sched.c:411
20 thread 515 schedule_timeout (timeout=2147483647) at sched.c:411
19 thread 514 schedule_timeout (timeout=2147483647) at sched.c:411
18 thread 513 schedule_timeout (timeout=2147483647) at sched.c:411
17 thread 512 schedule_timeout (timeout=2147483647) at sched.c:411
16 thread 511 schedule_timeout (timeout=2147483647) at sched.c:411
15 thread 438 schedule_timeout (timeout=2147483647) at sched.c:411
14 thread 420 schedule_timeout (timeout=-1013981316) at sched.c:439
13 thread 406 schedule_timeout (timeout=-1013629060) at sched.c:439
12 thread 392 do_syslog (type=2, buf=0x804dc20 "run/utmp", len=4095)
at printk.c:182
11 thread 383 schedule_timeout (timeout=2147483647) at sched.c:411
10 thread 328 schedule_timeout (timeout=2147483647) at sched.c:411
9 thread 270 schedule_timeout (timeout=-1011908724) at sched.c:439
8 thread 8 interruptible_sleep_on (q=0xc02c8848) at sched.c:814
7 thread 6 schedule_timeout (timeout=-1055490112) at sched.c:439
6 thread 5 interruptible_sleep_on (q=0xc02b74b4) at sched.c:814
5 thread 4 kswapd (unused=0x0) at vmscan.c:736
4 thread 3 ksoftirqd (__bind_cpu=0x0) at softirq.c:387
3 thread 2 context_thread (startup=0xc02e93c8) at context.c:101
2 thread 1 schedule_timeout (timeout=-1055703292) at sched.c:439
* 1 thread 0 breakpoint () at gdbstub.c:1159
(gdb)
gdb将线程各自的ID分配给他们,如上所示。当涉及到gdb中的线程,此ID被使用。
如上, 线程PID 7 拥有gdb ID 8。
要分析内核线程8,我们指定线程9给gdb。然后gdb切换至此线程以便进一步分析。
进一步的命令 如 backtrace 将应用于此线程。
(gdb) thr 9
[Switching to thread 9 (thread 270)]
#0 schedule_timeout (timeout=-1011908724) at sched.c:439
439 del_timer_sync(&timer);
(gdb) bt
#0 schedule_timeout (timeout=-1011908724) at sched.c:439
#1 0xc0113f36 in interruptible_sleep_on_timeout (q=0xc11601f0, timeout=134)
at sched.c:824
#2 0xc019e77c in rtl8139_thread (data=0xc1160000) at 8139too.c:1559
#3 0xc010564b in kernel_thread (fn=0x70617773, arg=0x6361635f,
flags=1767859560) at process.c:491
#4 0x19 in uhci_hcd_cleanup () at uhci.c:3052
#5 0x313330 in ?? () at af_packet.c:1891
Cannot access memory at address 0x31494350
(gdb) info regi
eax 0xc38fdf7c -1013981316
ecx 0x86 134
edx 0xc0339f9c -1070358628
ebx 0x40f13 266003
esp 0xc3af7f74 0xc3af7f74
ebp 0xc3af7fa0 0xc3af7fa0
esi 0xc3af7f8c -1011908724
edi 0xc3af7fbc -1011908676
eip 0xc011346d 0xc011346d
eflags 0x86 134
cs 0x10 16
ss 0x18 24
ds 0x18 24
es 0x18 24
fs 0xffff 65535
gs 0xffff 65535
fctrl 0x0 0
fstat 0x0 0
ftag 0x0 0
fiseg 0x0 0
fioff 0x0 0
foseg 0x0 0
fooff 0x0 0
---Type to continue, or q to quit---
fop 0x0 0
(gdb) thr 7
[Switching to thread 7 (thread 6)]
#0 schedule_timeout (timeout=-1055490112) at sched.c:439
439 del_timer_sync(&timer);
(gdb) bt
#0 schedule_timeout (timeout=-1055490112) at sched.c:439
#1 0xc0137ef2 in kupdate (startup=0xc02e9408) at buffer.c:2826
#2 0xc010564b in kernel_thread (fn=0xc3843a64, arg=0xc3843a68,
flags=3280222828) at process.c:491
#3 0xc3843a60 in ?? ()
Cannot access memory at address 0x1f4
(gdb)
进程信息宏 Process information macros ( ) ps 和 psname 对于线程分析是有用的(可在下载页
获得)。宏ps 提供了内核线程的名字和id。
(gdb) ps
0 swapper
1 init
2 keventd
3 ksoftirqd_CPU0
4 kswapd
5 bdflush
6 kupdated
8 khubd
270 eth0
328 portmap
383 syslogd
392 klogd
406 atd
420 crond
438 inetd
511 mingetty
512 mingetty
513 mingetty
514 mingetty
515 mingetty
516 mingetty
(gdb)
宏psname则可以用于 已知内核线程id时,获得线程名,
(gdb) psname 8
8 khubd
(gdb) psname 7
(gdb)
--通过内核线程使用gdb线程模式时的告诫--
* gdb也许需要一段时间通过慢速的串行线,来显示线程列表。如果线程数过百则甚至耗费几十秒时间。
* gdb假设线程id总是增加的。gdb 询问kgdb 已创建的线程,如果其id号大于先前线程列表中最大的线程号的话。
如果你认为你所使用的测试内核已经超过线程ids,最好从gdb中退出,重启后再获得线程列表。这可以大致解决gdb从先前线程列表中记忆最大线程号的难题。
『 Watchpoint 』
Kgdb stub 包含对使用IA-32(x86)调试功能 的硬件断点的支持。
这些断点不需要代码的修改。他们使用调试寄存器。在IA-32处理器中有4个硬件断点可用。每一个硬件断点可以是以下三种类型。
1、执行断点-----当位于断点地址的代码被执行时,执行断点将被触发。因为可用的硬件断点数是受限的,使用软件断点 (break命令)来取代执行硬件断点 就是
明智的了,除非是要避免修改代码。
2、写断点-------当位于断点地址的内存被写,写断点将被触发。写断点的长度表明被监视的数据类型长度。( Length is 1 for 1 byte data , 2 for 2 byte
data, 3 for 4 byte data. )
3、存取断点-----当位于断点地址的内存被读/写,存取断点将被触发。存取断点和写断点一样,拥有长度。
IA-32中的IO断点不被支持。
gdb stub对硬件断点的支持至今仍没有使用 被GDB使用的协议 ,硬件断点是通过GDB宏来被存取的。
硬件断点相关的GDB宏描述如下:
hwebrk------设置一个执行断点
使用: hwebrk breakpointno address
hwwbrk-----设置一个写断点
使用: hwwbrk breakpointno length address
hwabrk-----设置一个存取断点
使用: hwabrk breakpointno length address
hwrmbrk----移除一个断点
使用: hwrmbrk breakpointno
exinfo------告知出现一个软/硬断点
这些命令所需参数如下:
breakpointno ------- 0~3
length -------------- 1~3
address ------------ hex digits without 0x ,eg. c015e9bc
......to be continue