找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

634

积分

0

好友

92

主题
发表于 昨天 07:23 | 查看: 6| 回复: 0

GDB(GNU Debugger)是由Richard Stallman在1986年编写的开源调试器,作为GNU系统的一部分。它是一款功能强大的便携式调试工具,可以在许多类UNIX系统上运行,并支持包括C、C++在内的多种编程语言。

GDB的核心工作原理基于Linux内核提供的ptrace系统调用。当启动GDB调试程序时,会创建一个父子进程关系:GDB作为父进程,被调试程序作为子进程。子进程通过调用ptrace(PTRACE_TRACEME)将自己设置为被跟踪模式,而父进程(GDB)则可以通过ptrace系统调用来监控和控制子进程的执行流程,包括检查和修改其内存映像及寄存器。

GDB调试的实现完全建立在信号机制的基础上。当使用ptrace系统调用建立调试关系后,交付给目标程序的任何信号都会首先被GDB截获。例如,当设置断点时,GDB会在断点位置写入断点指令INT3(0xCC),当目标程序执行到该指令时会触发SIGTRAP信号,GDB捕获该信号并暂停程序执行。

一、GDB调试配置

在开始调试前,必须确保程序编译时包含调试信息。这是GDB调试的前提条件,没有调试信息的程序无法进行有效的调试。

1. 编译选项设置

使用GCC/G++编译时,必须添加-g选项来生成调试信息:

gcc -g -o program program.c

-g选项会告诉编译器生成调试符号信息,包括:

  • 变量的名字和类型
  • 源代码中对应的行号
  • 函数调用栈的信息 调试信息通常嵌入到ELF可执行文件的特殊节区中,如.debug_info.debug_line等。GCC默认生成DWARF格式的调试信息。
2. 优化选项与调试

在调试时,建议使用-O0选项(无优化)来确保调试体验最佳:

gcc -O0 -g -o program program.c

优化选项会影响调试体验,因为优化可能会改变程序的执行流程。如果需要保留部分优化但仍能调试,可以使用较低的优化级别如-O1-O2

3. Makefile配置

在项目中,通常使用Makefile进行构建。要在Makefile中启用调试支持,需要在编译选项中添加-g

# 基本Makefile配置
CFLAGS = -Wall -O0 -g  # C语言编译选项
CXXFLAGS = -Wall -O0 -g # C++编译选项
LDFLAGS = -g            # 链接选项
TARGET = my_program
SOURCES = main.c utils.c
OBJECTS = $(SOURCES:.c=.o)

all: $(TARGET)
$(TARGET): $(OBJECTS)
    $(CC) $(LDFLAGS) -o $@ $^
clean:
    rm -f $(OBJECTS) $(TARGET)

二、GDB操作使用

2.1 启动GDB与加载程序

2.1.1 启动GDB的多种方式

GDB提供了多种启动和加载程序的方式:

  1. 直接启动并加载程序gdb <program>
  2. 启动时指定参数gdb <program> --args <arguments>
  3. 先启动GDB再加载程序:在gdb环境中使用file <program>命令
  4. 调试core文件gdb <program> core,用于分析程序崩溃时生成的core dump文件
  5. attach到正在运行的进程gdb attach <pid>,可以调试一个已经在运行的进程,这是一个非常有用的调试手段。
2.1.2 命令行参数传递
  • 设置参数:(gdb) set args arg1 arg2 arg3
  • 查看参数:(gdb) show args
  • 在启动时传递参数:(gdb) run arg1 arg2 arg3

2.2 GDB命令基础与帮助系统

2.2.1 命令格式与语法

GDB命令基本语法为:command [arguments] [options] GDB支持命令缩写(如break可缩写为b)、Tab补全、历史记录和快捷键。

2.2.2 帮助系统使用

学会使用帮助系统是掌握GDB的关键:

  • (gdb) help:查看所有命令分类
  • (gdb) help <class>:查看指定分类的命令,如help breakpoints
  • (gdb) help <command>:查看指定命令的详细说明
  • (gdb) apropos <keyword>:搜索包含指定关键词的命令
  • (gdb) show version:显示当前GDB版本

2.3 基本调试流程示例

为了更好地理解GDB的基本操作,这里通过一个简单的C程序来演示完整的调试流程。

2.3.1 示例程序准备

创建一个简单的C程序sum.c

#include <stdio.h>
int sum(int a, int b) {
    int result = a + b;
    return result;
}
int main() {
    int x = 5;
    int y = 3;
    int z = sum(x, y);
    printf("The sum of %d and %d is %d\n", x, y, z);
    return 0;
}

使用GCC编译并包含调试信息:gcc -g -o sum sum.c

2.3.2 启动GDB并加载程序
  1. 启动GDB并加载程序:gdb sum
  2. 查看程序信息:(gdb) info files
  3. 查看源代码:(gdb) list main

2.4 设置断点与程序执行控制

2.4.1 断点设置方法

断点是调试中最基本也是最重要的功能之一:

  • 函数入口(gdb) break main(gdb) b main
  • 指定行号(gdb) break sum.c:10
  • 内存地址(gdb) break *0x4005a3
  • 临时断点(触发一次后自动删除)(gdb) tbreak <location>
  • 硬件断点(gdb) hbreak <location>,适用于ROM/EPROM代码调试
2.4.2 断点管理命令
  • 查看所有断点:(gdb) info breakpoints(gdb) info b
  • 删除断点:(gdb) delete 1(gdb) d 1
  • 删除所有断点:(gdb) delete breakpoints(gdb) d
  • 禁用/启用断点:(gdb) disable <number> / (gdb) enable <number>
2.4.3 程序执行控制
  • 启动程序(gdb) run(gdb) r
  • 继续执行(gdb) continue(gdb) c,直到遇到下一个断点
  • 单步执行(进入函数)(gdb) step(gdb) s
  • 单步执行(不进入函数)(gdb) next(gdb) n
  • 执行到指定位置(gdb) until <location>
  • 跳转到指定位置(gdb) jump <location>,可用于跳过错误代码

2.5 查看变量与内存

2.5.1 查看变量值
  • 打印变量值(gdb) print x(gdb) p x
  • 以特定格式打印
    (gdb) p /x z  # 以十六进制格式打印
    (gdb) p /t i  # 以二进制格式打印
  • 自动显示变量(gdb) display <expression>,每次程序停止时自动打印
  • 取消自动显示(gdb) undisplay <display number>
  • 查看局部变量(gdb) info locals
  • 查看函数参数(gdb) info args
2.5.2 内存查看命令
  • 查看内存命令(gdb) x/<format> <address>,x是examine的缩写
  • 内存查看格式说明x/<n/f/u> <addr>
    • n:要显示的单元数量
    • f:显示格式(x-十六进制, d-十进制, s-字符串等)
    • u:每个单元的大小(b-字节, h-半字, w-字)
  • 示例(gdb) x/3uh 0x54320 表示从地址0x54320开始,读取3个单元,每个单元2字节,按无符号十六进制显示。
  • 查看字符串(gdb) x/s <address>
  • 查看汇编指令(gdb) x/i <address>

2.6 函数调用栈分析

函数调用栈分析是调试复杂程序的重要工具,特别是在程序崩溃时。

  • 查看调用栈(gdb) backtrace(gdb) bt
  • 查看部分调用栈
    (gdb) backtrace 5   # 显示栈顶5层
    (gdb) backtrace -5  # 显示栈底5层
  • 切换栈帧(gdb) frame 2(gdb) f 2
  • 查看当前栈帧信息(gdb) info frame(gdb) i f
  • 查看所有线程的调用栈(多线程程序)(gdb) thread apply all backtrace

2.7 简单调试案例演示

下面通过一个具体的例子来演示完整的调试流程。假设我们有一个排序程序gdb_sort_demo.c

#include <stdio.h>
void select_sort(int arr[], int len) {
    int i, j, min_idx;
    for (i = 0; i < len - 1; i++) {
        min_idx = i;
        for (j = i + 1; j < len; j++) {
            if (arr[j] < arr[min_idx]) {
                min_idx = j;
            }
        }
        int temp = arr[i];
        arr[i] = arr[min_idx];
        arr[min_idx] = temp;
    }
}
int main() {
    int arr[] = {64, 25, 12, 22, 11};
    int len = sizeof(arr) / sizeof(arr[0]);
    select_sort(arr, len);
    printf("Sorted array: ");
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

编译并启动调试:

gcc -g -o gdb_sort_demo gdb_sort_demo.c
gdb gdb_sort_demo

调试步骤

  1. select_sort函数入口设置断点:(gdb) b select_sort
  2. 运行程序:(gdb) run
  3. 程序停在函数入口,查看参数:(gdb) p arr(gdb) p len
  4. 单步进入循环:(gdb) s
  5. min_idx赋值后查看其值:(gdb) p min_idx
  6. 继续执行直到排序完成:(gdb) c
  7. 查看排序后的数组:(gdb) p arr

通过这个例子,我们演示了如何设置断点、单步调试、查看变量等基本操作。在实际的Linux环境下,你可以根据程序的复杂度和问题类型,灵活运用这些功能进行高效的调试。




上一篇:Breach论坛数据查询平台详解:网络安全研究与信息验证工具
下一篇:微服务架构深入解析:从C/S、B/S到分布式架构演进与实战选型
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-10 20:06 , Processed in 0.093998 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表