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
  1. off:不锁定任何线程,也就是所有线程都执行,这是默认值;
  2. on:只有当前被调试程序会执行;
  3. step:在单步的时候,除了next过一个函数的情况以外,只有当前线程会执行。
原创粉丝点击