gdb的使用指南(下)
来源:互联网 发布:建筑会计软件 编辑:程序博客网 时间:2024/06/05 23:05
gdb的使用指南(下)
1.函数调用堆栈的分析
当程序因断点等其他情况而停止在某个函数内的某个地方时,如果我们想知道这个函数是通过什么途径被调用的,那么输出堆栈信息将是一个不错的解决方案。
比如说,对于下面的一个递归实现的求n!的程序–factorial.c。
#include <stdio.h>#include <stdlib.h>int factorial(int n){ if (n <= 1) { return 1; } return n * factorial(n - 1);}int main(int argc, char* argv[]){ int m = atoi(argv[1]); int n = factorial(m); printf("%d",n);}
如果我们想分析它的结束递归时的堆栈信息。
$ gcc -g -O0 factorial.c$ gdb a.out
对于factorial函数,它退出递归的条件是n=1,因此我们就要在这设置一个断点。
(gdb) break factorial if n == 1Breakpoint 1 at 0x6fb: file factorial.c, line 5.
当我们想求5!的值并以5作为启动参数时,很快就会遇到上面设置的那个断点。
(gdb) run 5Starting program: /media/luweirong/文档/Coding+Programming/C++/a.out 5Breakpoint 1, factorial (n=1) at factorial.c:55 if (n <= 1)
在这个时候,我们就可以通过backtrace命令,把当前的函数调用堆栈信息全部打印出来。
(gdb) backtrace#0 factorial (n=1) at factorial.c:5#1 0x0000555555554715 in factorial (n=2) at factorial.c:9#2 0x0000555555554715 in factorial (n=3) at factorial.c:9#3 0x0000555555554715 in factorial (n=4) at factorial.c:9#4 0x0000555555554715 in factorial (n=5) at factorial.c:9#5 0x000055555555474a in main (argc=2, argv=0x7fffffffcc08) at factorial.c:14
上面确实把函数的调用过程展示了出来,但是我们只能观察到各帧局部变量n的值。如果我们想详细了解各调用帧的具体情况,可以使用frame命令切换到某一帧(上面一共有6帧,分别为#0、#1、……、#5),再通过p或者info命令进行深入分析。
比如说,我们可以切换到在main函数中调用factorial()时的堆栈帧,然后查看变量m的值。
(gdb) frame 5#5 0x000055555555474a in main (argc=2, argv=0x7fffffffcc08) at factorial.c:1414 int n = factorial(m);(gdb) print m$1 = 5
然后继续运行程序直到退出。
(gdb) cContinuing.120Program exited normally.(gdb) quit
2.Core文件的分析
考虑下面的一个计算斐波那契数列的程序fibonacci.c,注意:它其实是有Bug的。
#include <stdio.h>#include <stdlib.h>int fibonacci(int);int main(int argc, char **argv) { int k, result; if (argc != 2) { printf("usage: %s n\n",argv[0]); return 1; } k = atoi(argv[1]); if (k < 1) { printf("illegal argument k=%d\n",k); return 1; } result = fibonacci(k); printf("fibonacci(%d) = %d\n", k, result); return 0;}int fibonacci(int k) { if (k == 1) { return 1; } else { return fibonacci(k-1) + fibonacci(k-2); }}
所谓的斐波那契数列,其递推公式为:
- fibonacci(1) = 1
- fibonacci(2) = 1
- fibonacci(k) = fibonacci(k-1) + fibonacci(k-2), where k = 2, 3, …
当我们编译并运行该程序时,错误将会发生。
$ gcc -g -O0 fibonacci.c$ ./a.out 10Segmentation fault
本来,当程序在遇到一些错误(段错误、非法指令、总线错误或用户自己生成的退出信息等等),进而crash掉时,系统会自动产生core文件记录crash时刻系统信息(包括内存和寄存器信息)用以程序员日后debug时可以使用。一般地,core文件在当前文件夹中存放。
但是,由于一般默认情况下,core文件的大小被设置为0,这样系统就不dump出core文件了。这时,使用命令:ulimit -c unlimited
把core文件的大小设置为无限大,或者使用数字来替代unlimited,对core文件的上限制做更精确的设定。
这样,让我们再运行一遍我们的程序。
$ gcc -g -O0 fibonacci.c$ ./a.out 10Segmentation fault (core dumped)
上面就多了core dumped
这样的输出信息,然后让我们看下系统为我们自动生成的core文件到底是什么。
$ lsa.out core.6828 fibonacci.c
core.6828
就是我们想要分析的core文件,其中的6828是进程ID,所以每次生成的core文件的名称几乎都不相同。
其实使用这个core文件来帮助我们分析崩溃原因的方法十分简单,只要我们把这个core文件作为gdb的启动参数去执行对应的程序即可。比如:
$ gdb a.out core.6828...
这样,我们就可以再次回到coredump发生的现场,程序在这里终止运行(有点像断点)。
Core was generated by `./a.out 10'.Program terminated with signal 11, Segmentation fault.Reading symbols from /lib/tls/libc.so.6...done.Loaded symbols for /lib/tls/libc.so.6Reading symbols from /lib/ld-linux.so.2...done.Loaded symbols for /lib/ld-linux.so.2#0 0x08048439 in fibonacci (k=-307072) at fibonacci.c:2727 return fibonacci(k-1) + fibonacci(k-2);(gdb)
为了更加准确地把握程序的崩溃原因,我们可以进一步使用backtrace
命令。
(gdb) backtrace#0 0x08048439 in fibonacci (k=-307072) at fibonacci.c:27#1 0x0804843e in fibonacci (k=-307071) at fibonacci.c:27#2 0x0804843e in fibonacci (k=-307070) at fibonacci.c:27...(省略)#27 0x0804843e in fibonacci (k=-307045) at fibonacci.c:27#28 0x0804843e in fibonacci (k=-307044) at fibonacci.c:27---Type <return> to continue, or q <return> to quit---
我们将会发现这个堆栈信息超长,为了避免过长的堆栈信息输出,gdb提示我们可以按q+回车来结束输出。
遇到这种堆栈调用很深的情况,我们可以大胆地猜测应该是遇到无限递归(缺少退出条件)的bug。更进一步,我们发现k的值变成了负数:
(gdb) print k$1 = -307072
所以我们可以断定是递归的退出条件写错了:
if (k == 1) { return 1;}
显然,斐波那契数列的递推初始值应该有两个:fibonacci(1)=1和fibonacci(2)=1,而上面的代码缺少了k==2时的特殊处理。下面我们将递归函数改写为:
int fibonacci(int k) { if (k == 1) { return 1; } else if (k == 2) { return 1; } else { return fibonacci(k-1) + fibonacci(k-2); }}
现在,让我们来验证下我们的猜测是否正确。
$ gcc -g -O0 fibonacci.c$ ./a.out 10fibonacci(10) = 55
这样,我们就通过分析core文件解决了一个“天大”的bug。
3.多线程调试
1.查看当前进程的所有线程信息
(gdb)info thread
2.切换当前调试的线程为指定ID的线程
(gdb)thread ID
3.让一个或者多个线程执行指定的GDB命令
(gdb)thread apply ID1 ID2 GDB命令
4.让所有被调试线程执行指定的GDB命令
(gdb)thread apply all GDB命令
5.锁定其他线程
在使用s(step)或者c(continue)命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?
我们可以通过这个命令来实现该需求:
set scheduler-locking off|on|step
- off:不锁定任何线程,也就是所有线程都执行,这是默认值;
- on:只有当前被调试程序会执行;
- step:在单步的时候,除了next过一个函数的情况以外,只有当前线程会执行。
- gdb的使用指南(下)
- gdb的使用指南(上)
- GDB使用指南
- gdb使用指南
- gdb使用指南
- GDB使用指南
- gdb使用指南
- GDB使用指南
- gdb使用指南
- GNU的源代码调试器 gdb 使用指南
- C++编程 (二)--- GDB使用指南
- GDB的使用方法(下)
- mac下的使用指南
- gdb入门-GDB使用指南
- GDB使用指南1
- GDB使用指南2
- GDB完全使用指南(1)
- Linux下的SSH使用指南
- jsp基础学习
- 周末作业
- Linux文件操作
- 日常笔记之hashmap
- 梯子意识
- gdb的使用指南(下)
- scala-类和对象
- 2017暑假七林集训day13
- Java中堆内存(heap)和栈内存(stack)的区别
- IntelliJ IDEA 中 右键新建时,选项没有Java class的解决方法和具体解释
- Python---元素分类
- Python笔记
- AJAX技术及一些参数说明
- mvc