gdb调试入门到进阶

gdb官方手册

https://sourceware.org/gdb/current/onlinedocs/gdb/

gdb工具介绍

GDB(GUN Project Debuger)是Stallman开发的用于类Unix系统下的代码调试工具。如果需要对程序进行调试,就要求程序在编译过程中加入了调试信息,gcc或者g++编译器通过传参-g可以控制最后的生成文件中包含调试信息。以下面简单的程序为例进行说明:

#include <stdio.h>
#include <stdlib.h>
long func(int n)
{
    long sum = 0;
    for (int i = 0; i < n; i++) {
        sum += i;
    }
    return sum;
}
int main(int argc, char *argv[])
{
    int i;
    long result = 0;
    for (i = 1; i <= 100; i++) {
        result += i;
    }
    printf("result[1-100] = %d\n", result);
    printf("result[1-250] = %ld\n", func(250));
    int result1 = atoi(argv[1]);
    printf("result[1-%d] = %ld\n", result1, func(result1));
    return 0;
}

分别执行命令:

g++ -m32 test0.cpp -o test0_nodebug
g++ -m32 -g test0.cpp -o test0_debug
加入调试信息的可执行文件大小几乎是未加入调试信息的可执行文件大小的两倍:

通过命令 readelf -S 查看两种可执行文件的段信息对比如下,从段的大小可以明显看出来右边的是加了 -g 选项生成的可执行文件:

gdb主要完成如下四个方面的功能:

  • 按用户自定义需求启动程序;
  • 让程序在用户指定的点断住;
  • 显示用户想要查看的程序当前信息;
  • 动态修改程序的执行环境。

常用命令

启动和退出

程序启动

启动gdb调试有如下几种方式:
  1.在gdb启动时就载入程序:

LEARN_USE/GDB> gdb <exe-file>
(gdb)

  2.启动gdb后再载入程序:

LEARN_USE/GDB> gdb
(gdb) file <exe-file>

  3.程序可能是已经在运行状态中了,可以通过获取程序运行的进程ID通过gdb调试:

LEARN_USE/GDB> gdb --pid <exe-file-pid>
(gdb)

运行的程序可能需要进行传参,传参有两种方式:
  1.通过启动gdb过程中加入--args选项进行传参:

LEARN_USE/GDB> gdb --args <exe-file> <args>
(gdb)

  2.通过gdb启动后在命令行执行set args配置参数:

LEARN_USE/GDB> gdb <exe-file>
(gdb) set args <args>

tips: 试试执行gdb --args g++ -g test1.cpp -o test1_64会发生是什么。


以下面的程序为例,程序接受一个入参作为程序delay的时间(以秒为单位)。

// 编译命令:gcc -m32 -g test1.cpp -o test1
#include <iostream>
#include <time.h>
#include <stdlib.h>
 
void delay_sec(int sec)
{
    time_t start_time, cur_time;
    start_time = time(&start_time);
    int cnt = 0;
    do {
        cnt++;
        cur_time = time(&cur_time);
    } while((cur_time - start_time) < sec);
}
 
int main(int argc, char *argv[])
{
    using std::cout;
    using std::endl;
 
    int sec = atoi(argv[1]);
    cout << "Delay " << sec << "(s)" << endl;
    double start, stop, use;
    start = time(NULL);
    delay_sec(sec);
    stop = time(NULL);
    use = (double)difftime(stop, start);
    cout << "Use time: " << use << " s" << endl;
    return 0;
}
  • gdb启动程序并传递参数:
    gdb --args test1 20
    截图
  • 利用gdb对运行中的程序进行调试:
    窗口A:运行程序,设置delay 20s
    截图
    窗口B:通过ps命令获取程序test1的pid,执行命令gdb pid xxxxx进入程序:
    截图

程序退出

退出gdb有如下三种方式:
  1.执行命令quit或q:

(gdb)quit
A debugging session is active.
 
        Inferior 1 [process 28895] will be killed.
 
Quit anyway? (y or n)

  2.执行命令exit(说明手册上介绍可以这种方式,但是在我的环境上无法识别);
  3通过执行Ctrl+D(EOF标示)中止程序(与之对比的是Ctrl+C命令,Ctrl+C是将gdb调试过程中断,但是并没有退出,中断操作是安全的,可以随时继续运行下去);


- gdb启动后执行help命令可以获取帮助信息: 截图 - lp命令后接执行的命令可以获取和该命令相关的详细命令: 截图 - apropos word命令可以搜索和word相关的所有命令(apropos -v word进行正则匹配): 截图

停止和继续运行

断点

通过命令break或者b可以设置断点,常用的设置断点方式有如下几种:

  1. 在指定函数打断点:b <function-name>;
    截图
  2. 在指定行打断点:b <line-num>;
    截图
  3. 在指定文件指定行打断点或者是在指定文件指定函数打断点:b <file-name>:<line-num>或b <file-name>:<function-name>;
    截图
    上述打断点方法的前提是我们有对应的调试信息,可以在调试过程中获取行号、文件名、函数名等信息,假设我们的被调试对象在调试过程
    中没有-g选项,如何设置断点呢?
  4. 在指定地址打断点:b *<address>,这里的address是指令的地址:
    截图
  5. 设置条件断点:b ... if ,condition可以是基本所有满足语法的逻辑语句。下面示例中设置了在result>20后断在18行的断点,继续执下去最后断住时result结果变为21.
    截图
  6. tbreak或者tb可以设置只生效一次的断点;
    截图
  7. rbreak 或者rb 可以对匹配正则表达式的所有函数打断点,如果调试过程中我们忘记了函数的具体名字就可以通过这种方式打断点。
    截图
  8. info b可以查看当前设置的断点的所有信息,包括断点的编号、断点已进入次数、断点是否生效、断点地址、断点所在文件。
    截图

观察点

用户可以通过设置观察点来监视程序运行过程中数据的变化,并在被监视对象发生改变的时候停止程序。常用的主要有如下几种方式:

  1. watch <expression>:在指定表达式位置设置断点,当表达式结果发生改变的时候程序停止(常用的写法是watch *<address>,这样在指定地址中保存的值发生改变的时候程序会停止,或者watch <var-name>,这样如果指定变量内容发生改变的时候程序会停止);
  2. rwatch <expression>:在指定对象被读取的时候断住;
  3. awatch <expression>:在指定对象被读或者被写的时候断住。
  4. info watchpoint 和 info breakpoint都可以显示观察点,观察点严格来说也属于断点,如果你查看所有断点的信息,也能看到其中有观察点的信息。
    同样观察点后面也是可以设置条件的,写法:watch *<address> if <condition>。

设置捕获点

catch命令平时很少用到,主要用于帮助在程序运行过程中捕获各种event,通过在gdb下执行命令help catch可以获取和catch相关的命令信息:
截图
比如catch load可以在动态库加载的时候断住:
截图
catch fork可以在调用fork的时候断住程序:
截图


删除、禁用断点、观察点、捕获点

  1. delete [range]可以删除指定的断点,比如delete 1删除编号为1的断点,delete 2-4可以删除编号2、3、4的断点。
  2. clear可以删除当前断住位置的断点,clear 可以删除指定函数的断点,同样clear :或clear :可以清除指定位置指定函数的断点。
  3. disable [range]可以禁用指定的断点;enable [range]可以使被禁用的断点重新生效。
  4. ignore 可以指定跳过断点n cnt次,下图中断点打在for循环中,设置跳过断点10次后重新停住。
    截图

继续运行

程序断在指定位置后,如果需要继续执行下去,可以通过如下几个命令:

  1. continue或者c继续执行下去;
  2. step <count>或者s <count>单步执行count条指令,如果没有指定count就默认执行一条,同vscode中的F11;
  3. next <count>或者n <count>单步执行count条代码,同vscode中的F10;
  4. finish执行完当前函数并返回后停止,继续以上面的代码为例进行说明;
  5. return直接从当前函数中退出,剩下的代码不再继续执行;
  6. until <location>:和step类似,单步执行指令,但是在遇到循环代码时会跳过(代码依然会执行到),如果后面跟上了location(函数名或者行号)则会在指定位置停住。
  7. jump :跳转到指定为位置,中间的代码不会继续执行下去;

断点位置自动执行命令

除了上面介绍的命令外还有一个命令结合断点非常有用,commands命令:设置在遇到断点后指定执行的指令,通过输入end结束命令的输入。commands <breakpoint n>即在指定断点断住后执行要求的命令,继续以上面的代码为例进行说明,要求在每次进入func函数中的for循环体内断住,并自动打印result变量的结果。
打断点(b ..)-> 命令窗口 -> 设置命令 -> 结束命令
截图
可以看到每次执行到设置的断点位置都会打印我们想要的信息。
截图

运行信息查看

查看局部变量

1.查看全局和静态变量
info variables
2.查看当前stack frame局部变量
info locals
3.查看当前stack frame参数
info args

修改程序

gdb attach pid
dump:导出内存
dump [格式] memory 文件名 起始地址 结束地址 # 把指定内存段写到文件
append:追加导出内存
append [binary] memory 文件名 起始地址 结构地址 # 按2进制追加到文件
restore:内存导入
restore 文件名 [binary] bias 起始地址 结构地址 # 恢复文件中内容到内存.如果文件内容是原始二进制,需要指定binary参数,不然会gdb自动识别文件格式

输出GDB调试信息到文件
第一步:set logging file E:\Test6186\GDBLOG.txt
第二步:set logging on

laysrc显示源码

退出 ctrl + x + a

core dump 总结

参考链接:https://www.cnblogs.com/Anker/p/6079580.html

posted @ 2022-10-28 16:45  胖白白  阅读(42)  评论(0编辑  收藏  举报