本节我们研究gdb更深层的用法:
数据断点:
可以根据变量的值来监视变量。
数据断点本质是硬件断点,数量有限。
watch var_name告诉gdb我们关注var_name这个变量,如果它的值被改变了,程序运行就停止。
数据断点常常配合gdb里面的内存查看命令使用。
x是检查内存数据的命令。
上图中的意思是打印从0x804a024地址开始连续4个字节的数据,呈现格式以16进制打印。x代表16进制。
上图中/a代表打印地址。
变量断点和内存查看实验:
watch.c程序如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 int g_var = 0; 6 7 void* thread_func(void* args) 8 { 9 sleep(5); 10 11 g_var = 1; 12 } 13 14 int main() 15 { 16 int i = 0; 17 pthread_t tid = 0; 18 19 pthread_create(&tid, NULL, thread_func, NULL); 20 21 for(i=0; i<10; i++) 22 { 23 printf("g_var = %d ", g_var); 24 25 sleep(1); 26 } 27 }
先编译运行一下:
子线程中将g_var的值改为1,影响到了主线程的工作,我们认为这是一个错误的程序,进行debug调试。
开始调试:
使用watch监视g_var:
continue继续执行程序:
当g_var被改变的时候,程序停下了。图中提示g_var的值被改写了,是被thread_func函数中的第12行改写的(gdb表达的意思是程序执行到第12行时,g_var的值被改写了,意味着是第11行改写的)。
查看内存的值:
上图也验证了当前系统是小端系统。
继续执行:
本次调试,我们确定了是什么地方修改了g_var的值。
函数调用相关:
为什么要打印函数调用栈呢?举个例子,当我们看一份开源代码的时候,我们想快速弄清楚函数的调用关系,这时可以在感兴趣的函数上打上断点,当程序断在这里时,可以用backtrace查看函数调用栈。
frame N可以跳转到某一个函数的上下文当中。切换过去之后可以查看一些函数参数信息等。
info fream可以查看栈帧信息。
每一次函数调用的活动记录就存在栈上,这就是栈帧。
实验:
frame.c程序如下:
1 #include <stdio.h> 2 3 4 int sum(int n) 5 { 6 int ret = 0; 7 8 if( n > 0 ) 9 { 10 ret = n + sum(n-1); 11 } 12 13 return ret; 14 } 15 16 17 int main() 18 { 19 int s = 0; 20 21 s = sum(10); 22 23 printf("sum = %d ", s); 24 25 return 0; 26 }
先编译运行一下程序:
启动gdb:
打一个条件断点:
继续执行:
程序在sum函数的第6行停下了。
backtrace打印了函数的调用信息。
main -> sum(10) -> sum(9) .... -> sum(0)
程序在sum(0)时停了下来。
next继续执行:
函数停在return ret的时候,我们通过info args打印当前栈帧这个函数参数的值,可以看到为0。
切换函数栈帧:
我们切换到7号栈帧上。
打印信息告诉我们,目前函数调用上下文里面语句停留在ret = n + sum(n-1);这一行。
info args打印参数信息,info locals打印局部变量的值:
将栈帧切换回0号栈帧,并打印寄存器:
当前的sp的值是0xbfffee80。
打印当前栈帧的详细信息:
我们关注Previous frame's sp is 0xbfffeeb0这个信息。
这就是上一个栈帧sp的值。
调用这个函数之前,ebp的值被保存在了0xbfffeea8地址处,eip保存在了0xbfffeeac地址处。
打印函数调用之前ebp的值:
继续验证上面的打印是否正确:
执行next,sum(0)返回,这就到了sum(1)的地方:
可以看到ebp的值和上一次的打印是一样的。esp的值也完全能对应上。
调试技巧:
whatis和ptype是动态查看符号的方法。
上图中在gdb中直接编译程序。
编译后用file载入运行。
符号查看:
从上图中可知,func是一个函数类型,具体为int(),返回值为int,没有参数。
实验:
tricks.c如下:
1 #include <stdio.h> 2 3 int g_var = 1; 4 5 struct ST 6 { 7 int i; 8 int j; 9 }; 10 11 int func() 12 { 13 struct ST st[5] = {0}; 14 int i = 0; 15 16 for(i=0; i<5; i++) 17 { 18 st[i].i = i; 19 st[i].j = i * i; 20 } 21 22 for(i=0; i<5; i++) 23 { 24 printf("st[%d].i = %d ", i, st[i].i); 25 printf("st[%d].j = %d ", i, st[i].j); 26 } 27 } 28 29 int main() 30 { 31 static c_var = 2; 32 33 func(); 34 35 return 0; 36 }
开始调试:
打印结果如下:
list是列出源文件的某一行的内容,但是相邻的几行也会打印出来。
设置显示的行数重新打印:
继续执行:
使用display使得每次执行到这个断点的时候都自动打印。
最后程序正常退出:
我们再次直接run,上次设置的东西还都存在:
undisplay可以取消之前的自动打印。
使用符号查看:
ptype是打印详细的类型定义。
查看全局的变量符号:
info functions可以得到以下信息:
小结: