在了解掌握gdb调试从入门到进阶(一)后,我们从具体案例出发,进阶我们的调试能力
GDB调试程序实例(一)
下面就开始使用具体的例子来详细解读GDB调试的过程
我们调试的两个代码为:
- test.c
#include
- func.c
#include
- 首先对上述程序编译并且运行:
- gcc func.c test.c -o test.out
- ./test.out
- 毫无疑问,程序肯定会产生错误,如下图:
- 那么现在我们开始使用gdb来定位出错误,在开启gdb调试之前,需要在编译源程序的时候加上-g选项,并将程序的崩溃信息转储的core文件
- gcc -g test.c func.c -o test.out //重新编译加上调试信息
- ulimit -c unlimited //让程序在崩溃时产生core文件
core程序的设定
在程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映像,同时加上调试信息)。使用gdb来查看core文件,可以指示出导致程序出错的代码所在文件和行数。
1.core文件的生成开关和大小限制
1)使用ulimit -c命令可查看core文件的生成开关。若结果为0,则表示关闭了此功能,不会生成core文件。
2)使用ulimit -c filesize命令,可以限制core文件的大小(filesize的单位为kbyte)。若ulimit -c unlimited,则表示core文件的大小不受限制。如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文 件的时候,gdb会提示错误。 可以将ulimit -c unlimited写入到.bashrc中。
这里我备注一下: 最好同时设置软硬开关: ulimit -c unlimited和ulimit -c unlimited -S)
2.core文件的名称和生成路径 core文件生成路径: 输入可执行文件运行命令的同一路径下。 若系统生成的core文件不带其它任何扩展名称,则全部命名为core。新的core文件生成将覆盖原来的core文件。
1)/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作为扩展。文件内容为1,表示添加pid作为扩展名,生成的core文件格式为core.xxxx;为0则表示生成的core文件同一命名为core。 可通过以下命令修改此文件: echo "1" > /proc/sys/kernel/core_uses_pid
2)proc/sys/kernel/core_pattern可以控制core文件保存位置和文件名格式。 可通过以下命令修改此文件: echo "/corefile/core-%e-%p-%t" > core_pattern,可以将core文件统一生成到/corefile目录下,产生的文件名为core-命令名-pid-时间戳 以下是参数列表: %p - insert pid into filename 添加pid %u - insert current uid into filename 添加当前uid %g - insert current gid into filename 添加当前gid %s - insert signal that caused the coredump into the filename 添加导致产生core的信号 %t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间 %h - insert hostname where the coredump happened into filename 添加主机名 %e - insert coredumping executable name into filename 添加命令名
输入命令:gdb test.out test.out-core-31648-1577779707 进行调试:
- 从这里我们甚至都直接看到了产生段错误的地方就在func.c程序中的func函数中出的问题。
- 输入quit命令退出当前gdb调试
输入命令:gdb 进行调试:输入gdb后再gdb调试模式下输入:file test.out
然后字gdb调试模式下输入run,显示结果最后部分如下:
通过上图我们很容易发现,程序执行的很快,瞬间就到了段错误那里。我们回到test.c程序中,会发现这段代码中:
for(i=0; i<100; i++) {
fa[i%3] ();
sleep(argc > 1); // 如果argc大于1,则执行睡眠函数
}
由于sleep参数中argc的参数为1(只有./test.out这个参数),所以不会睡眠。
但是我们可以在gdb中进行设置参数,输入命令set args DTsoft:
很明显,我们的程序运行起来变得慢很多,这里是由于我们加了一个命令行DTsoft,现在命令行参数就有两个,一个是可执行程序test.out,一个是DTsoft。
最后,输入ctrl + c可以终止程序的执行,再输入continue可以继续执行刚刚被终止的程序。
gdb动态连接到一个正在执行的程序,然后对其进行调试
在右边的终端我们运行程序的时候加一个参数DTsoft这样可以让上述的for循环中的sleep开启,让程序执行的慢一点
程序执行起来后,在左边的终端首先输入ps aux | grep test查看我们的程序的pid
然后sudo gdb 开启gdb,这里加上sudo以root模式开启,是因为动态连接正在运行的程序的话就需要以root模式
开启gdb进入gdb模式后,使用attach pid (这里的pid根据你自己查到的pid写)连接到我们运行的程序。
在我们连接到程序的一瞬间,发现程序的执行停止了(使用continue可以继续程序的执行,当程序运行到段错误那里,gdb可以发现错误),说明已经连接到运行中的程序,现在可以使用gdb对它进行调试了。怎么调试随你意,上面我们也说了几种简单的调试方法。
使用GDB进行监视变量的改变与查看内存实际代码案例分析
watch.c程序
#include
首先我们先编译运行上述程序:
- gcc -g -lpthread watch.c -o test.out
然后我们再sum函数处打一个断点,并给出条件,当n==0的时候断点成立
break sum if n==8
查看断点是否打上:info breakpoints
运行程序:continue
运行上述几个步骤后,程序运行到sum函数,并在sum函数递归调用到n==8的时候停止:
gdb 查看core进程的所有线程堆栈
#include
在linux下使用g++直接编译该cpp文件会报错,使用 g++ -o MultiThreadDump MultiThread.cpp -lpthread 编译,编译参数上带上-lpthread即可。
由于上面代码里在count等于5的时候,会delete一个未初始化的指针,肯定会coredump。如下,gdb打开coredump文件,能看到5个线程LWP的信息。
如何,查看每个线程的堆栈信息呢?
首先,info threads查看所有线程正在运行的指令信息
thread apply all bt打开所有线程的堆栈信息
查看指定线程堆栈信息:threadapply threadID bt,如:
thread apply 5 bt
进入指定线程栈空间
thread threadID如下
如上截图所示,可以跳转到指定的线程中,并查看所在线程的正在运行的堆栈信息和寄存器信息。