一、gdb常用指令:
gcc -g main.c //在目标文件加入源代码的信息
gdb a.out
(gdb) start //开始调试
(gdb) n //一条一条执行
(gdb) step/s //执行下一条,如果函数进入函数
(gdb) backtrace/bt //查看函数调用栈帧
(gdb) info/i locals //查看当前栈帧局部变量
(gdb) frame/f //选择栈帧,再查看局部变量
(gdb) print/p //打印变量的值
(gdb) finish //运行到当前函数返回
(gdb) set var sum=0 //修改变量值
(gdb) list/l 行号或函数名 //列出源码
(gdb) display/undisplay sum //每次停下显示变量的值/取消跟踪
(gdb) break/b 行号或函数名 //设置断点
(gdb) continue/c //连续运行
(gdb) info/i breakpoints //查看已经设置的断点
(gdb) delete breakpoints 2 //删除某个断点
(gdb) disable/enable breakpoints 3 //禁用/启用某个断点
(gdb) break 9 if sum != 0 //满足条件才激活断点
(gdb) run/r //重新从程序开头连续执行
(gdb) watch input[4] //设置观察点
(gdb) info/i watchpoints //查看设置的观察点
(gdb) x/7b input //打印存储器内容,b--每个字节一组,7--7组
(gdb) disassemble //反汇编当前函数或指定函数
(gdb) si // 一条指令一条指令调试 而 s 是一行一行代码
(gdb) info registers // 显示所有寄存器的当前值
(gdb) x/20 $esp //查看内存中开始的20个数
二,调试子进程
实际上,GDB 没有对多进程程序调试提供直接支持。例如,使用GDB调试某个进程,如果该进程fork了子进程,GDB会继续调试该进程,子进程会不受干扰地运行下去。 如果你事先在子进程代码里设定了断点,子进程会收到SIGTRAP信号并终止。那么该如何调试子进程呢?其实我们可以利用GDB的特点或者其他一些辅助手 段来达到目的。此外,GDB 也在较新内核上加入一些多进程调试支持。
接下来我们详细介绍2种方法,分别是 follow-fork-mode 方法,attach 子进程方法。
在2.5.60版Linux内核及以后,GDB对使用fork/vfork创建子进程的程序提供了follow-fork-mode选项来支持多进程调试。
follow-fork-mode的用法为:
set follow-fork-mode [parent|child]
- parent: fork之后继续调试父进程,子进程不受影响。
- child: fork之后调试子进程,父进程不受影响。
- 调试子进程时,设定在子进程上的断点将会有效,而父进程上的断点此时无效;反之依然。缺省gdb是调试主进程的
因此如果需要调试子进程,在启动gdb后,可以看到在启动之后自动跳转到了子进程代码中。
(gdb) set follow-fork-mode child (gdb) start 16 pid = fork(); (gdb) [New process 6238] this is futher process 6236,wait child 6238 [Switching to process 6238] |
众所周知,GDB有附着(attach)到正在运行的进程的功能,即attach <pid>命令。因此我们可以利用该命令attach到子进程然后进行调试。
例如我们要调试某个进程OwnTest,首先得到该进程的pid
host-192-10-10-8:/home/code # ps -ef|grep OwnTest root 8056 5964 0 14:06 pts/1 00:00:00 ./OwnTest root 8057 8056 0 14:06 pts/1 00:00:00 ./OwnTest root 8058 8057 0 14:06 pts/1 00:00:00 ./OwnTest root 8067 8933 0 14:06 pts/0 00:00:00 grep OwnTest 查看之后发现是三进程,通过pstree查看父子进程id情况。 host-192-10-10-8:/home/code # pstree 8056 OwnTest───OwnTest───OwnTest host-192-10-10-8:/home/code # pstree 8057 OwnTest───OwnTest |
现在就可以调试了。一个新的问题是,子进程一直在运行,attach上去后都不知道运行到哪里了。有没有办法解决呢?
一个办法是,在要调试的子进程初始代码中,比如main函数开始处,加入一段特殊代码,使子进程在某个条件成立时便循环睡眠等待,attach到进程后在该代码段后设上断点,再把成立的条件取消,使代码可以继续执行下去。
至于这段代码所采用的条件,看你的偏好了。比如我们可以检查一个指定的环境变量的值,或者检查一个特定的文件存不存在。以文件为例,其形式可以如下:
void debug_wait(char *tag_file) { while(1) { if (tag_file存在) 睡眠一段时间; else break; } } |
三,调试线程
当程序没有启动,线程还没有执行,此时利用gdb调试多线程和调试普通程序一样,通过设置断点,运行,查看信息等等。
在多线程编程时,当我们需要调试时,有时需要控制某些线程停在断点,有些线程继续执行。有时需要控制线程的运行顺序。有时需要中断某个线程,切换到其他线程。这些都可以通过gdb实现。
先介绍一下GDB多线程调试的基本命令。 如果目标进程已经core dump了,那么 gdb -c core xxx xxx是对应的程序文件。 如果目标进程还在运行,通常此时用于调试线程死锁的情况。有两种方法 一是 gdb -p xxx xxx是该进程的进程ID 或者用gcore xxx先获取对应进程的core,他会生成一个core文件 core.xxx
进入gdb后 (gdb) info threads 可以列出所有的线程,缺省设为当前的线程前面有一个*号 比如 gdb) info thread
这是1个死锁的例子,可以看到线程9 和线程2都停在 __ksleep上。 如果想看各个线程的详细堆栈信息,比如要看9的 gdb)thread 9 把当前线程设成9,然后就可以查看相关信息 比如 gdb)bt 将列出栈的调用情况,以及对应源代码中的位置,此时谨慎察看对应代码,一般必有结果。 |