本文全面介绍了GDB在Linux上的安装、使用以及基本调试命令和操作,并通过一些简单的案例来演示GDB如何调试C/C++程序。

🎬个人简介:一个全栈工程师的升级之路!
📋个人专栏:问题处理专栏
🎀CSDN主页 发狂的小花
🌄人生秘诀:学习的本质就是极致重复!

目录

1 GDB概述与安装

1.1 GDB简介

1.2 GDB历史与发展

1.3 安装与配置

2 基本调试命令与操作

2.1 启动GDB并加载程序

2.2 设置断点

2.3 单步执行与进入函数

2.4 查看变量值与表达式求值

2.5 继续执行到下一个断点或程序结束

3 高级调试技巧与方法

3.1 多线程调试支持

3.2 核心转储文件分析

3.3 条件断点与临时断点设置

3.4 远程调试配置与实现

4 调试过程中常见问题及解决方案

4.1 符号表问题

4.2 内存泄漏检测与定位

4.3 段错误处理及原因分析

4.4 性能优化建议

5 调试示例

5.1 一个简单示例

5.2 gdb调试段错误

5.3 gdb 调试函数传参

5.4 断点测试

5.5 gdb 调试核心转存(coredump)


1 GDB概述与安装

1.1 GDB简介

        GDB(GNU Debugger)是GNU开源组织发布的一个强大的UNIX下的程序调试工具。

        GDB主要帮助你进行下面四个方面的功能:

        1. 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。

        2. 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)。

        3. 当程序被停住时,可以检查此时你的程序中所发生的事。

        4. 动态的改变你程序的执行环境。

1.2 GDB历史与发展

        GDB最初由Stallman编写,作为GNU项目的一部分,用于调试C语言程序。

        随着时间的推移,GDB的功能不断扩展,支持的语言也越来越多,包括C、Objective-C、Fortran、Ada等。

        GDB的版本也在不断更新,每个新版本都增加了新的特性和改进了性能。

1.3 安装与配置

        安装:在大多数Linux发行版中,GDB都是默认安装的。如果没有安装,可以通过包管理器(如apt、yum等)进行安装。

sudo apt install gdb

        配置:在使用GDB之前,需要进行一些基本的配置。例如,设置断点、观察点、命令历史记录等。这些配置可以通过编辑~/.gdbinit文件来实现。

        另外,还需要确保被调试的程序是带有调试信息的。在编译程序时,需要使用-g选项来生成调试信息。

        例如,使用gcc编译器编译程序时,可以使用以下命令:

gcc -g test.c -o test 

        然后使用gdb执行程序即可进行gdb调试,如下:

gdb test

2 基本调试命令与操作

        常用调试指令

-g:		使用该参数编译可以执行文件,得到调试表。
gdb ./a.out
list: 		list 1 列出源码。根据源码指定 行号设置断点。
b: 		b 20 在 20 行位置设置断点。
run/r: 		运行程序
n/next: 	下一条指令(会越过函数)
s/step: 	下一条指令(会进入函数)
p/print: 	p i 查看变量的值。
continue:继续执行断点后续指令。
finish:结束当前函数调用。
quit:退出 gdb 当前调试。
set args: 设置 main 函数命令行参数 (在 start、 run 之前)
run 字串 1 字串 2 ...: 设置 main 函数命令行参数
info/i b: 查看断点信息表
b 20 if i = 5: 设置条件断点。
ptype:查看变量类型。
bt:列出当前程序正存活着的栈帧。
frame: 根据栈帧编号,切换栈帧。
display:设置跟踪变量
undisplay:取消设置跟踪变量。 使用跟踪变量的编号

2.1 启动GDB并加载程序

        有两种方式:

        (1)gdb + 可执行程序

        在终端中输入 `gdb` 命令即可启动 GDB 调试器,gdb ./test。

        (2)gdb + 源文件

        gdb test.c ,需要在进入后执行file test加载符号表后才能进行调试。

2.2 设置断点

        (1)break 命令

        使用 `break` 命令在指定位置设置断点,例如 `break function_name` 或 `break filename:linenumber`。

        (2)tbreak 命令

        使用 `tbreak` 命令设置临时断点,程序执行到该断点时暂停,但断点只生效一次。

2.3 单步执行与进入函数

        (1)step 命令

        使用 `step` 命令单步执行程序,遇到函数调用时会进入函数内部。

        (2)next 命令

        使用 `next` 命令单步执行程序,遇到函数调用时不会进入函数内部,而是直接执行下一行代码。

2.4 查看变量值与表达式求值

        (1)print 命令

        使用 `print` 命令查看变量值或表达式求值,例如 `print variable_name` 或 `print expression`。

        (2)ptype 命令

        使用 `ptype` 命令查看变量类型,例如 `ptype variable_name`。

2.5 继续执行到下一个断点或程序结束

        (1)continue 命令

        使用 `continue` 命令继续执行程序,直到遇到下一个断点或程序结束。

        (2)until 命令

        使用 `until` 命令执行到指定位置,例如 `until function_name` 或 `until filename:linenumber`。

        (3)finish 命令

        使用 `finish` 命令执行完当前函数并返回到调用该函数的位置。

3 高级调试技巧与方法

3.1 多线程调试支持

        (1)线程信息查看

        使用`info threads`命令查看当前进程中的所有线程信息。

        (2)线程切换    

        使用`thread <n>`命令切换到指定编号的线程,其中`<n>`为线程编号。

        (3)线程断点设置         

        可以在特定线程上设置断点,以便仅在该线程执行时触发。

3.2 核心转储文件分析

        (1)核心转储文件生成

        当程序崩溃时,操作系统可以生成核心转储文件,记录程序崩溃时的内存状态。

        (2)GDB加载核心转储文件

        使用`gdb <executable> <core file>`命令加载核心转储文件。

        (3)分析核心转储文件

        通过GDB的命令行接口,可以检查程序崩溃时的堆栈信息、变量值等,以定位问题原因。

3.3 条件断点与临时断点设置

        (1)条件断点设置

        可以在设置断点时添加条件,以便仅在满足特定条件时触发断点。例如,`break <location> if <condition>`。

        (2)临时断点设置

        使用`tbreak <location>`命令设置临时断点,该断点在触发一次后自动删除。

        (3)断点命令列表

        可以为断点设置命令列表,当断点触发时自动执行这些命令。例如,`commands <breakpoint number>`。

3.4 远程调试配置与实现

        (1)远程调试原理

        远程调试允许在本地机器上运行GDB,而目标程序在远程机器上执行。通过网络连接,GDB可以发送控制命令并接收目标程序的执行状态。

        (2)远程调试配置

        需要在本地和远程机器上分别配置GDB服务器和客户端。在远程机器上启动GDB服务器,并在本地机器上配置GDB客户端连接到远程服务器。

        (3)远程调试实现

        在本地机器上使用`target remote <host>:<port>`命令连接到远程GDB服务器。然后,可以像在本地调试一样设置断点、单步执行等操作。

4 调试过程中常见问题及解决方案

4.1 符号表问题

        (1)符号表缺失

        在编译时未开启-g选项,导致调试信息未被包含在可执行文件中。解决方法是在编译时添加-g选项。

        (2)符号表不匹配

        源代码与编译生成的符号表不匹配,可能是由于源代码被修改或重新编译导致。解决方法是确保源代码与符号表一致,或重新编译生成新的符号表。

4.2 内存泄漏检测与定位

        (1)内存泄漏现象

        程序在运行过程中不断占用内存,且无法释放。这可能是由于动态分配的内存未被正确释放导致。

        (2)内存泄漏检测工具

        使用Valgrind等内存泄漏检测工具可以帮助定位内存泄漏问题。这些工具可以检测出哪些内存块被分配但未释放。

        (2)内存泄漏定位方法

        通过分析内存泄漏检测工具的报告,结合源代码,可以定位到具体的内存泄漏位置。修复方法包括确保在使用完动态分配的内存后及时释放,以及检查是否存在野指针等问题。

4.3 段错误处理及原因分析

        (1)段错误现象

        程序在运行过程中突然崩溃,并报告“段错误”或“Segmentation fault”。这通常是由于访问了非法内存地址导致。

        (2)段错误原因分析 

        可能的原因包括解引用空指针、越界访问数组、非法内存访问等。通过分析崩溃时的堆栈信息,可以定位到导致段错误的代码位置。

        (3) 段错误处理方法    

        根据段错误的原因,采取相应的处理措施。例如,对于解引用空指针的问题,需要确保在使用指针前进行非空检查;对于越界访问数组的问题,需要检查数组索引是否越界,并确保数组长度足够。

4.4 性能优化建议

        (1)优化算法

        针对程序中耗时的算法进行优化,例如使用更高效的算法或数据结构,减少不必要的计算或内存访问。

        (2)减少I/O操作

        减少不必要的文件读写或网络传输操作,例如通过缓存数据或使用更高效的数据传输方式来提高I/O性能。

        (3)多线程/并行计算

        利用多线程或并行计算技术,将程序中的计算任务分配到多个处理器核心上执行,从而提高程序的执行效率。

        (4)优化编译器选项

        调整编译器的优化选项,例如开启O2或O3优化级别,可以让编译器自动进行代码优化,提高程序的执行效率。   

5 调试示例

5.1 一个简单示例

#include <stdio.h>

void log(int i)
{
    printf(" i = %d \n",i);  
}

int main(void)
{
    int i = 0; 
    int arr[3] = {3,4,13}; 

    printf("hello gdb\n");

    for (i = 0; i < 4; i++) {
        log(arr[i]);
    }   
}

        编译:gcc -g -o test

        调试运行:gdb test 得到如下:

list/l n 从第n行开始显示源程序、一般显示10 行,后续继续输入list/l,就可以显示后面的程序

break/b n ,在第n行设置断点,这一行不会被执行

run/r 运行程序

按 next/n   或者step/s 继续向下执行

next/n :下一个,调用函数就开始运行

step/s :单步,会进入调用的函数 要注意的是,如果是系统函数,按 s 就出不来了,这时用 until+行号直接执行到行号处

s执行进入系统函数出不来,使用until +行数出来

print/p i 查看 i 变量的值

continue 继续运行直到结束,得到exited normally,表示正常退出

finish 结束当前函数的调用

上述的代码中有个很明显的数组越界,当执行continue时,可以清晰的看到打印出一个异常的i值。

5.2 gdb调试段错误

        这段代码有明显的段错误,p[1]不能赋值

#include <stdio.h>

void log(int i)
{
    printf(" i = %d \n",i);  
}

int main(void)
{
    int i = 0; 
    int arr[3] = {3,4,13};

    char *p = "Tom";
    
    p[1] = 'l';

    printf("hello gdb\n");

    for (i = 0; i < 3; i++) {
        log(arr[i]);
    }   
}


​

编译:gcc test.c -o test

调试:gdb test 得到

run 运行,可以很容易查到段错误的位置

5.3 gdb 调试函数传参

#include <stdio.h>

void log(int i)
{
    printf(" i = %d \n",i);  
}

int main(int argc, char *argv[])
{
    int i = 0; 
    int arr[3] = {3,4,13};

    printf("main addr = %d \n",argc);
    printf("main函数 参数1: %s 参数2: %s \n",argv[0],argv[1]);

    printf("hello gdb\n");

    for (i = 0; i < 3; i++) {
        log(arr[i]);
    }   
}

编译:gcc -g test.c -o test

调试:gdb test 得到

set args string1 string2 ...给主函数传入参数

或者 gdb test后使用run testHUHUHU 得到

5.4 断点测试

#include <stdio.h>

void log(int i)
{
    printf(" i = %d \n",i);  
}

int main(void)
{
    int i = 0; 
    int arr[3] = {3,4,13}; 

    printf("hello gdb\n");

    for (i = 0; i < 3; i++) {
        log(arr[i]);
    }   
}

编译,gdb调试后得到:

设置断点和条件断点

backtrace/bt 命令是列出当前堆栈中的所有帧。执行到13行时,栈上只有一帧,编号为0,属于 main 函数。

继续执行,当执行到新的函数时,用bt来查看此时的栈帧信息,下面的图可以看到i=2时,进入了断点2,此时进入log函数调用且暂停运行,因此bt显示有两个栈帧。

从下面输出结果,我们能够看出,有两个栈帧,第1帧属于 main 函数,第0帧属于 log 函数。 每个栈帧都列出了该函数的参数列表。从上面我们可以看出,main 函数没有参数,而 log 函数有参数,并且显示了其参数的值。

有一点我们可能比较迷惑,在第一次执行backtrace的时候,main 函数所在的栈帧编号为0,而第二次执行的时候,main 函数的栈帧为1,而 log 函数的栈帧为0,这是因为栈的增长方向是向下的。

info b/break 查看断点信息

frame 用来查看当前栈帧,栈帧用来存储函数的变量值等信息,默认情况下,GDB 总是位于当前正在执行函数对应栈帧的上下文中。

由于当前正在执行log函数,因此gdb位于第0帧的上下文中,使用p i可以查看变量i的值,使用ptype i 可以查看变量的类型,frame [num]可以切换栈帧

display可以设置跟踪变量

display 命令用于调试阶段查看某个变量或表达式的值,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来

undisplay:取消设置跟踪变量。 使用跟踪变量的编号

5.5 gdb 调试核心转存(coredump)

        Coredump,也被称为核心转储,是在进程突然崩溃时捕获的内存快照。当程序发生异常且在进程内部未被捕获时,操作系统会将进程此时的内存、寄存器状态和运行堆栈等信息保存在一个文件中。

        这个文件是二进制格式,可以使用gdb、elfdump、objdump等工具或者在Windows系统下的windebug、Solaris系统下的mdb来打开并分析其具体内容。

        通过使用ulimit -c命令,可以设置core文件的大小。如果该值为0,则不会产生core文件;如果该值过小,也不会生成core文件,因为core文件通常较大。

        使用ulimit -c unlimited命令可以设置为无限制大小,这样在任何情况下都会产生core文件。

要查看当前的core文件限制,可以使用ulimit -a命令。

如下图,执行后此时可以保存核心转存的内存快照:

一个例子:(典型的释放了常量区的内存,运行汇报coredumped)

#include "stdio.h"
#include "stdlib.h"

void dumpCrash()
{
    char *ptr = "Hello";
    free(ptr);
}
int main()
{
    dumpCrash();
    return 0;
}

命令端:ulimit -c unlimited

编译:gcc -g test.c -o coredump

执行:./coredump

调试:gdb coredump 后得到

r 和bt后,得到

由上述我们可以很容易的找到coredump的位置,进一步推断coredump的原因

当然,一般系统的coredump的场景远比这个复杂,但是逻辑都是一样的,我们需要先找到coredump的位置,再结合代码以及core文件推测coredump的原因。

🌈我的分享也就到此结束啦🌈
如果我的分享也能对你有帮助,那就太好了!
若有不足,还请大家多多指正,我们一起学习交流!
📢未来的富豪们:点赞👍→收藏⭐→关注🔍,如果能评论下就太惊喜了!
感谢大家的观看和支持!最后,☺祝愿大家每天有钱赚!!!欢迎关注、关注!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐