gdb/gcc 调试编译技巧

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__,参考这篇文章

猜你喜欢

转载自blog.csdn.net/chenj_freedom/article/details/80332482