KGDB Manual | |
| |
来源: ChinaUnix博客 日期: 2005.10.22 21:57 (共有0条评论) 我要评论 | |
More Detail Inside......... 必备条件 ---------硬件 ---------软件 ---------硬件安置 内核 ---------准备 ---------连接并调试内核 模块调试 ---------开发、测试机上的条件 ---------于测试机上装载模块 ---------于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 /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__( 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 查看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 ( http://kgdb.linsyssoft.com/downloads/psmacros ) 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 |
kgdb
最新推荐文章于 2024-09-10 18:43:18 发布