GDB调试技巧
2014-07-27以下是这几个月工作以来所学习到的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; }
该程序没有检查文件是否打开成功,便进行读文件操作,在文件打开失败的时候,将会发生段错误。
这个时候就可以用backtrace/bt 命令来发现是哪里出了问题:
打印数组内容
在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",得到的结果如下:
但如果step进入函数 add_one ,执行"print arr",得到的却是这个结果了:
这是因为在函数 add_one 中,数组的指针 a 作为参数传入后退化为了普通指针了,这个时候如果要我们手动一个一个打印来查看,那无疑是很痛苦的。但在GDB中有查看这样的连续内存数据的方法,而且有多种,如下:
print *arr@7 / p *arr@7
不管指针指向的内存中存储的是基本类型的数据还是自定义的类型,这个方法都适用。
print (int [7])*arr / p (int [7])*arr
同上,但在操作上要麻烦一点。
x/7dw arr
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了。
指定源代码目录有两种方式:
- 在启动时使用 -d 选项设置
- 在启动后使用 directory 命令设置
当然了,最后说一下,像这种跨机器的调试,最好是在运行机器上设置好能生成core文件,否则要重现问题可能要花费一番功夫。打开core dump的方法是执行:
ulimit -c unlimited
当然,在shell中执行这个只是会暂时生效,如果需要在登录的时候生效,应该把这条语句写到 .bashrc 这个文件中去。