gdb基本技巧
set args *
设置启动参数
next
简写n,单步执行
step
简写s,单步进入
finish
跳出函数
run
简写r,开始执行
continue
简写c,继续执行,直到断电
breakpoint
简写b,设置断点
举例:
case1: b main.c:20 在main.c的第20行设置一个断点
case2: b function1 在function1函数入口处设置一个断点
info breakpoint
简写info b,查看已经设置的断点
enable/disable breakpoint n
简写disable b n,使断点有效/失效,n是用info b查看的断点编号
delete breakpoint n
简写d breakpoint n,删除断点,n是用info b查看的断点编号
print
简写p,打印变量,还可以格式化打印变量
/x 按十六进制格式显示变量。
/d 按十进制格式显示变量。
/u 按十六进制格式显示无符号整型。
/o 按八进制格式显示变量。
/t 按二进制格式显示变量。
/a 按十六进制格式显示变量。
/c 按字符格式显示变量。
/f 按浮点数格式显示变量。
举例:
case1:p i 打印i变量的值
case2:p $rbp 打印rbp寄存器的值
case3:见下图
display/undisplay
监视变量,每次程序停止时候,就会展示变量。同样也支持格式化,和print命令格式化类似。
display也可以和info/disable/enable/delete命令联合使用。
举例:见下图
examine
简写x,查看内存。同样也支持格式化。类似print命令。
FMT is a repeat count followed by a format letter and a size letter.(中文翻译:格式化(FMT)格式为一个数字(指:后面要显示多少个单元)+格式字符+大小字符)
举例:
分析:一个数字分别测试1,8,4,所以后面打印了1个数,8个数,4个数;格式字符都用x测试,所以都是十六进制显示;大小字符测试了b和g,b就是一个byte一个byte显示,g就是8个byte显示,但是有点奇怪,这边16个byte显示,不知道是不是32位系统和64位系统的差别。但是反正能看得到内存就OK。
info
通用的展示命令。可以用help info查看下。
backtrace
简写bt,查看函数堆栈。
help
查看gdb命令帮助
gdb调试coredump
可以看下另一篇博文。点击我。
coredump堆栈信息丢失,如何处理?
为何堆栈信息会丢失?
缓冲区溢出有可能导致堆栈丢失,如下,我写了个缓冲区溢出的测试程序。
#include <stdio.h>
#include <string.h>
void copyData(char* str) {
char dst[4];
strcpy(dst, str);
}
void func3(char* str3)
{
copyData(str3);
}
void func2(int p2, char* str2)
{
int b = p2;
func3(str2);
}
void func1(int p1, char* str1)
{
int a = p1;
func2(a, str1);
}
void main() {
char* src = "11111111111111111111111111111111111111111111111";
//char* src = "111";
func1(100, src);
}
这段代码运行的时候会产生coredump文件,gdb一下看看。如下,显示一堆问号:
如何解决?
网上搜了下有两种方式,一种是手动还原函数调用堆栈;另一种是自己再手动记录另一份函数调用堆栈。这篇文章可以参考下,点击我。
第一种方法比较麻烦,而且也未必奏效。这里着重分析下第二种方法,即:自己再手动记录另一份函数调用堆栈。
手动记录函数调用堆栈使用了gcc的function instrumentation机制(可查看gcc的man page来获取更详细信息),编译时如果为gcc加上“-finstrument-functions”选项,那在每个函数的入口和出口处会各增加一个额外的hook函数的调用,增加的这两个函数分别为:
void __cyg_profile_func_enter (void *this_fn, void *call_site);
void __cyg_profile_func_exit (void *this_fn, void *call_site);
其中第一个参数为当前函数的起始地址,第二个参数为返回地址,即caller函数中的地址。
这是什么意思呢?例如我们写了一个函数func_test(),定义如下:
static void func_test(v)
{
/* your code... */
}
那通过-finstrument-functions选项编译后,这个函数的定义就变成了:
static void func_test(v)
{
__cyg_profile_func_enter(this_fn, call_site);
/* your code... */
__cyg_profile_func_exit(this_fn, call_site);
}
原理分析完毕,现在我们就用这个特性来手动记录函数调用堆栈。在上面的代码中修改一下:
#include <stdio.h>
#include <string.h>
#define DUMP(func, call) printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call)
void __attribute__((__no_instrument_function__))
__cyg_profile_func_enter(void *this_func, void *call_site)
{
DUMP(this_func, call_site);
}
void __attribute__((__no_instrument_function__))
__cyg_profile_func_exit(void *this_func, void *call_site)
{
DUMP(this_func, call_site);
}
void copyData(char* str) {
char dst[4];
strcpy(dst, str);
}
void func3(char* str3)
{
copyData(str3);
}
void func2(int p2, char* str2)
{
int b = p2;
func3(str2);
}
void func1(int p1, char* str1)
{
int a = p1;
func2(a, str1);
}
void main() {
char* src = "11111111111111111111111111111111111111111111111";
//char* src = "111";
func1(100, src);
}
然后编译的时候要加入-finstrument-functions选项:
执行后,就可以看到自己的函数调用堆栈了。
但是后面都只是函数地址,那么如何看到函数名呢?可以使用gnu的另一个工具addr2line。
值得注意的是:如果不想跟踪某个函数,可以给该函数指定“no_instrument_function”属性。需要注意的是,__cyg_profile_func_enter()和__cyg_profile_func_exit()这两个hook函数是一定要加上“no_instrument_function”属性的,不然,自己跟踪自己就会无限循环导致程序崩溃,当然,也不能在这两个hook函数中调用其他需要被跟踪的函数。
调试宏
C99标准支持一些预定义宏,可以用来方便地调试程序。如下:
参考文档,点击我。
把常用的列出来,如下:
__FILE__ :显示文件 %s
__LINE__ :显示行号 %d
__FUNCTION__ :显示函数名 %s
说明:
1、__FILE__,当编译的时候,gcc是用绝对全路径编译的,那么最终显示的文件名字也是绝对全路径;如果gcc是用相对路径编译的,那么最终显示的文件名字也是相对路径的。如下图
2、更高级地使用__FILE__和__LINE__,参考这篇文章。