ZMonster's Blog 巧者劳而智者忧,无能者无所求,饱食而遨游,泛若不系之舟

GDB调试技巧

以下是这几个月工作以来所学习到的GDB调试技巧,虽然并不全面,但我认为还是比较实用的一些东西。

bt/backtrace

这条命令可以在程序出错时打印出函数调用栈,方便追踪问题发生的所在。对于代码结构比较复杂的项目,这条命令是非常有用的,所以这条命令也是必须掌握的了。

比如对于下面这个程序:

#include <stdio.h>

int read_int(FILE *fp)
{
    int a = 0;
    fscanf(fp, "%d", &a);
    return a;
}

int main(int argc, char *argv[])
{
    FILE *file = fopen(argv[1], "r");
    int a = read_int(file);
    printf("read %d\n", a);
    fclose(file);
    return 0;
}

该程序没有检查文件是否打开成功,便进行读文件操作,在文件打开失败的时候,将会发生段错误。

sf-test.png

这个时候就可以用backtrace/bt 命令来发现是哪里出了问题:

gdb-backtrace.png

打印数组内容

在GDB中,如果在数组的定义所在的函数内查看数组内容,直接调用print命令即可,如,对下面的代码:

#include <stdio.h>

void add_one(int *arr, int len)
{
    int i = 0;
    for (i = 0; i < len; ++i) {
        arr[i] = arr[i] + 1;
    }
}

int main(int argc, char *argv[])
{
    int a[] = {1, 2, 3, 4, 5, 6, 7};

    add_one(a, 7);

    return 0;
}

在函数调用

add_one(a, 7);

前执行"p a",得到的结果如下:

gdb-print-array.png

但如果step进入函数 add_one ,执行"print arr",得到的却是这个结果了:

gdb-print-array2.png

这是因为在函数 add_one 中,数组的指针 a 作为参数传入后退化为了普通指针了,这个时候如果要我们手动一个一个打印来查看,那无疑是很痛苦的。但在GDB中有查看这样的连续内存数据的方法,而且有多种,如下:

  1. print *arr@7 / p *arr@7

    gdb-print-array3.png

    不管指针指向的内存中存储的是基本类型的数据还是自定义的类型,这个方法都适用。

  2. print (int [​7])*arr / p (int [​7])*arr

    gdb-print-array4.png

    同上,但在操作上要麻烦一点。

  3. x/7dw arr

    gdb-print-array5.png

    x 是GDB中用来检查内存的命令,其使用方法是:

    x/nfu addr
    

    其中 n 表示要重复打印的次数,默认值为1;*f* 表示输出的格式,支持 x(十六进制)、d(十进制)、 u(无符号整型)、 o(八进制)、t(二进制)、a(地址值)、c(字符型)、f(浮点型)、s(字符串)这几种格式,默认使用 x ;u表示每个输出的宽度,可以选择b(1字节)、h(2字节)、w(4字节)和g(8字节),默认为4字节(w)。

    不过对于自定义的复杂类型,这个方法并不好用。

设置源代码目录

有时候存在这样的情况,程序运行的机器和源代码所在的机器不是同一台机器,假如程序崩溃了,因为没有源代码,也只能大致知道是在什么地方出错了,但却没有办法详查。当然,这种情况下,一个很笨的办法是,把整个项目代码拿过来,配置好编译环境,然后用编译好的程序替换原来的程序,再进行调试。

但是这样实在是很麻烦。

我们是可以这样的,首先定位问题发生的代码范围(涉及到哪些源文件),并把这些涉及的源文件拷贝过来,然后在GDB调试时指定源代码目录就OK了。

指定源代码目录有两种方式:

  1. 在启动时使用 -d 选项设置
  2. 在启动后使用 directory 命令设置

当然了,最后说一下,像这种跨机器的调试,最好是在运行机器上设置好能生成core文件,否则要重现问题可能要花费一番功夫。打开core dump的方法是执行:

ulimit -c unlimited

当然,在shell中执行这个只是会暂时生效,如果需要在登录的时候生效,应该把这条语句写到 .bashrc 这个文件中去。