使用GDB调试程序方法

发布于:2025-06-25 ⋅ 阅读:(17) ⋅ 点赞:(0)

写程序难免会遇到 Bug,这时我们就需要 GDB 来对程序进行调试了。调试需要在编译的时候,加上一些调试相关的信息,也就是说,需要指定 -g 选项。如:
 

$ gcc hello.c -o hello -g

其中 -g选项表示生成调试信息。

  • 当你在编译程序时加上-g选项,gcc会在生成的可执行文件中包含额外的调试信息。这些调试信息主要是关于源代码和目标代码之间的映射关系等内容。例如,它会记录变量的名称、类型、位置,函数的入口点等信息。

  • 这些调试信息对于使用调试工具(如gdb)来调试程序非常重要。调试工具可以利用这些信息来帮助你查看变量的值、设置断点、单步执行等操作。如果没有-g选项生成的调试信息,调试工具将很难准确地提供这些功能,因为它们无法知道源代码和目标代码之间的详细对应关系。

当我们生成了调试信息后,我们进入GDB开始对程序进行调试。

进入GDB调试界面

$ gdb executable_name                    # 不设置任何命令行参数
$ gdb --args executable_name [arg]...

比如:

$ gdb foo
$ gdb --args foo arg1 arg2 arg3

当然,我们也可以先进入调试界面,然后再设置命令行参数,如下所示:

$ gdb foo
(gdb) set args arg1 arg2 arg3

调试程序

查看源代码

我们可以用 list/l 命令查看源代码:
格式:

list/l [文件名:][行号|函数名]

常见用法:

(gdb) list                # 下翻源代码
(gdb) list -              # 上翻源代码
(gdb) list 20             # 查看20行附近的源代码
(gdb) list main           # 查看main函数附近的源代码
(gdb) list scanner.c:20   # 查看scanner.c文件第20行附近的源代码
(gdb) list scanner.c:scanToken  # 查看scanner.c文件scanToken函数附近的源代码

设置断点

我们可以用 break/b 命令设置断点:
格式:

break/b [文件名:][行号|函数名]

常见用法:

(gdb) break 20                    # 在第20行设置断点
(gdb) break main                  # 在main函数的开头设置断点
(gdb) break scanner.c:20          # 在scanner.c文件的第20行设置断点
(gdb) break scanner.c:scanToken   # 在scanner.c文件的scanToken函数开头设置断点

查看断点

我们可以用 info break/i b 命令查看断点信息:

格式:

info break/i b

常见用法:

(gdb) info break
Num  Type        Disp  Enb  Address             What
1    breakpoint  keep  y    0x0000555555554e1d  in main at main.c:79
2    breakpoint  keep  y    0x0000555555555a99  in scanToken at scanner.c:282

输出解释

  • Num:断点编号,你可以用这个编号来删除或禁用特定的断点。

  • Type:断点类型,这里是breakpoint

    • breakpoint :这是最常见的类型,表示一个普通的断点。当程序执行到指定的位置时,程序会暂停。

    • watchpoint:表示一个观察点,用于监视某个变量或内存地址的值是否发生变化。当变量的值发生变化时,程序会暂停。

    •  read watchpoint:表示一个只读观察点,用于监视某个变量或内存地址是否被读取。当变量的值被读取时,程序会暂停。

    • access watchpoint:表示一个访问观察点,用于监视某个变量或内存地址是否被读取或写入。当变量的值被读取或写入时,程序会暂停。

    • catchpoint:表示一个捕获点,用于捕获特定的事件,如异常、信号、系统调用等。当指定的事件发生时,程序会暂停。

    • tracepoint:表示一个跟踪点,用于在程序运行时收集数据,而不会暂停程序。跟踪点通常用于性能分析和数据收集。

    • breakpoint (pending):表示一个尚未解析的断点。这通常发生在指定的文件或行号在当前上下文中不可用时。

    • breakpoint (internal):表示一个内部断点,通常由gdb内部使用,用户通常不需要直接操作这些断点。

    • breakpoint (shadow):表示一个影子断点,通常用于多线程环境,用于在多个线程中设置相同的断点。

  • Disp:表示断点的显示方式,keep表示断点在程序退出时不会自动删除。

  • Enb:表示断点是否启用,y表示启用,n表示禁用。

  • Address:断点的内存地址。

  • What:断点的具体位置,包括文件名和行号。

删除断点


我们可以用 delete/d 命令删除断点:
格式:

delete/d [n] -- 删除所有断点或n号断点

常见用法:

(gdb) delete 2  # 删除2号断点
(gdb) d         # 删除所有断点

启动调试

我们可以用 run/r 命令启动调试:

(gdb) r

该命令只是用于启动调试,而不是用于跳跃。

继续

continue/c 命令可以运行到逻辑上的下一个断点处:

(gdb) c

假如有图中两个断点,当我们在第4行断点停住时,我们可以按c直接执行到第27行断点中间不会再停顿。

 忽略断点n次

我们可以用 ignore 命令来指定忽略某个断点多少次,这在调试循环的时候非常有用:
格式:

ignore N COUNT

常见用法:

(gdb) ignore 1 10   # 忽略1号断点10次单步调试

 有如下程序时,当我们需要在循环中设置断点调试时,我们可以使用ignore来跳过多次的断点

#include<stdio.h>

void fun(){
    int t = 0;
    for(int i = 1; i <= 10; i++){
        t++;
        printf("%d\n",t);
    }
}
int main(int argc, char const *argv[])
{
    fun();
    return 0;
}

step/s 命令可以用来进行单步调试,即遇到函数调用会进入函数。

(gdb) step

跳出函数

我们可以使用 finish 命令执行完整个函数:

(gdb) finish

当使用finish命令时,gdb会继续执行当前函数,直到该函数返回,而不会在当前函数内的其他断点处暂停。这意味着,即使当前函数中还有其他断点,finish命令也会忽略这些断点,直接执行到函数返回。

逐过程

next/n 命令表示逐过程,也就是说遇到函数调用,它不会进入函数,而是把函数调用
看作一条语句。

(gdb) n

监视

print/p 命令可以打印表达式的值:
格式:

print/p EXP

如:

(gdb) print PI*r*r

print/p 命令还可以改变变量的值:
格式:

print/p EXP=VAL

比如:

(gdb) print r=2.0

我们可以用 display 命令自动展示表达式的值:

display命令用于在程序每次暂停时自动显示指定的表达式的值。这是一个非常有用的工具,可以帮助你跟踪变量的变化,而无需手动输入print命令来查看它们的值。
格式:

display EXP   # 自动展示EXP
info display  # 显示所有自动展示的表达式信息
undisplay [n] # 删除所有或[n]号自动展示的表达式

常见用法:

(gdb) display r
(gdb) display PI*r*r
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:  y   r
2:  y   PI*r*r
(gdb) undisplay 2
(gdb) undisplay
Delete all auto-display expressions? (y or n)

输出解释:

  • 1: x = 0:表示编号为1的display表达式显示变量x的值为0。

  • Auto-display expressions now in effect:表示当前生效的自动显示表达式。

  • Num:表示display表达式的编号。

  • Enb:表示是否启用该display表达式,y表示启用,n表示禁用。

  • Expression:表示display表达式的内容。

在每次跳跃到某个断点时,都会实时显示表达式的值

(gdb) print t = 5
$10 = 5
(gdb) c
Continuing.
5
Breakpoint 7, fun () at test3.c:6
6               t++;
1: t = 5
2: i = 3
(gdb) c
Continuing.
Breakpoint 6, fun () at test3.c:7
7               printf("%d\n",t);
1: t = 6
2: i = 3
(gdb) c
Continuing.
6

我们还可以通过命令查看参数和局部变量的值: 

我们还可以通过命令查看参数和局部变量的值:
(gdb) info args   # 查看函数的参数
(gdb) info locals # 查看函数所有局部变量的值

info args命令用于显示当前函数的参数及其值。这个命令非常有用,特别是在你想要查看当前函数的参数值时,而无需手动输入print命令。

  • 显示当前函数的参数及其值info args命令会列出当前函数的所有参数及其当前值。

  • 方便调试:在调试过程中,特别是当你在函数内部时,这个命令可以帮助你快速了解函数的输入参数。

例如从如下的程序,在执行到ptint_hello函数时,使用 info args 时会显示输入的参数x和y的值.使用info local 会显示函数内的局部变量值,执行到函数外时不再显示。

#include <stdio.h>

void print_hello(int x, int y) {
    printf("Hello, world! x = %d, y = %d\n", x, y);
    int t = 1;
}

int main() {
    print_hello(10, 20);
    return 0;
}

查看内存

我们可以用 x 命令查看内存的值(一般用得很少,了解即可):

格式:
    x/nFU
    其中, n为一个整数,表示查看n个单元的内存
    F表示输出格式:
    常用的输出格式有:
        o(octal),
        x(hex),
        d(decimal),
        u(unsigned decimal),
        t(binary),
        f(float),
        c(char),
        ...
     默认输出格式为x(hex)。
    U表示内存单元:
        b(byte), h(halfword, 2 bytes), w(word, 4 bytes), g(giant, 8bytes)
    默认单位为w(word)

常见用法:
 

(gdb) x/4dw arr
0x7fffffffe3a0: 0 1 2 3
(gdb) x/4xb &i
0x7fffffffe38c: 0x37 0x25 0x00 0x00
# 其中i=9527

退出GDB

quit/q 命令可以退出 GDB。

(gdb) q

调试coredump文件

通常情况下,程序异常终止时,会产生 Coredump 文件。Coredump 文件类似飞机上的"黑匣子",它会保留程序"失事"瞬间的一些信息,通常包含寄存器的状态、栈调用情况等。Coredump 文件常用于辅助分析和 Debug。

1. 确保生成coredump文件

在Linux系统中,默认情况下可能不会生成coredump文件,需要手动开启:

首先查看是否允许生成coredump文件

$ ulimit -a
core file size  (blocks, -c) 0
data seg size   (kbytes, -d) unlimited
...
$ ulimit -c unlimited     # 将core文件的大小临时设置为不受限制

此命令将允许生成无限大的coredump文件。如果需要指定coredump文件的生成路径和格式,可以编辑/proc/sys/kernel/core_pattern文件。

设置Coredump文件的格式

sudo vim /etc/sysctl.conf     #打开配置文件
# 在里面添加下面这句话
kernel.core_pattern = %e_core_%s_%t  # 自定义格式%e:executable-name,%s:signal, %t:time
# 其中 %e 表示异常的文件名 %s表示发生异常产生的终止信号 %t 表示发生异常的时间
sudo sysctl -p  #表示让配置生效

2. 启动gdb并加载coredump文件

使用以下命令启动gdb并加载coredump文件:

gdb <可执行程序路径> <coredump文件路径>

例如:

./my_program              #先执行程序,产生coredump报错信息
gdb my_program core.1234  #然后进入GDB调试

3. 查看堆栈信息

gdb中输入bt(backtrace)命令,查看程序崩溃时的堆栈信息:

bt

这将显示从导致程序崩溃的位置开始的完整函数调用堆栈。

4. 查看变量和寄存器

  • 查看变量值

    print <变量名>
  • 查看寄存器状态

    info registers

5. 切换栈帧

如果需要查看特定栈帧中的变量,可以使用frame命令切换栈帧:

frame <栈帧号>

然后使用print命令查看该栈帧中的变量。

6. 查看源代码

如果可执行文件包含调试信息(编译时使用-g选项),可以使用list命令查看崩溃点附近的源代码:

list

7. 多线程调试

如果程序是多线程的,可以使用以下命令查看所有线程:

info threads

然后切换到特定线程:

thread <线程号>

并使用bt命令查看该线程的堆栈。

8. 分析动态库

如果崩溃发生在动态库中,可能需要加载动态库的符号信息。可以使用add-symbol-file命令加载符号文件:

add-symbol-file <符号文件路径> <加载地址>

加载地址可以通过info sharedlibrary命令获取。


网站公告

今日签到

点亮在社区的每一天
去签到