Gdb 使用方法概述

调试目标、关键信息、常用命令、调试流程、技巧与注意事项 五个方面,全面介绍如何使用 GDB 进行高效调试。


一、GDB 调试的核心目标

使用 GDB 的目的通常包括:

  1. 定位程序崩溃原因(如段错误、非法指令)
  2. 分析死循环或逻辑错误
  3. 查看变量值变化过程
  4. 验证函数调用流程
  5. 检查内存访问问题(越界、use-after-free)
  6. 逆向分析二进制程序(无源码时)

二、调试时应重点关注的信息

1. 调用栈(Call Stack)

  • 函数是如何一层层调用的?
  • 崩溃发生在哪一层?
  • 调用者是谁?参数是什么?

🔍 关键命令:bt(backtrace)、frame N


2. 当前执行位置(源码 + 汇编)

  • 程序停在了哪一行代码?
  • 是不是预期的执行路径?
  • 是否跳过了某些判断或循环?

🔍 关键命令:listlayout asmx/i $pc


3. 变量和参数的值

  • 局部变量、全局变量、函数参数是否符合预期?
  • 是否有未初始化、越界、类型错误?

🔍 关键命令:printinfo localsinfo args


4. 寄存器状态

  • 特别是在崩溃时,rip(指令指针)、rsp(栈指针)、rax(返回值)等是否合法?
  • 是否访问了非法地址(如 0x0)?

🔍 关键命令:info registersx/gx $rsp


5. 内存内容

  • 指针指向的内存是否有效?
  • 字符串、数组、结构体内容是否正确?
  • 是否存在内存越界或堆损坏?

🔍 关键命令:x(examine)、print *ptrprint arr[i]


6. 控制流(断点、单步、条件)

  • 如何控制程序执行?
  • 如何跳入/跳过函数?
  • 如何在特定条件下中断?

🔍 关键命令:breakstepnextfinishcontinue


7. 线程与多进程状态(多线程程序)

  • 哪个线程导致了问题?
  • 是否死锁、竞争条件?

🔍 关键命令:info threadsthread Nthread apply all bt


三、GDB 常用命令分类详解

🧩 1. 启动与加载

命令说明
gdb ./program启动 GDB 并加载可执行文件
gdb ./program core加载 core dump 文件进行事后调试
gdb --pid PID附加到正在运行的进程(需权限)

⚠️ 确保程序用 -g 编译(保留调试信息):

gcc -g -O0 -o program program.c

🧩 2. 断点控制

命令说明
break func在函数 func 处设断点
break file.c:100在文件第 100 行设断点
break *0x401000在地址处设断点(汇编调试)
tbreak func临时断点(只触发一次)
hbreak func硬件断点(用于只读内存)
condition 1 i==10给断点 1 添加条件
delete / clear删除所有断点
disable / enable禁用/启用断点

🧩 3. 程序执行控制

命令说明
run [args]运行程序,可带命令行参数
continue (c)继续运行(从断点恢复)
step (s)单步执行,进入函数内部
next (n)单步执行,不进入函数
finish执行完当前函数并返回
until运行到指定行(跳出循环)
return强制从当前函数返回(可指定返回值)
jump跳转到某行(慎用,可能破坏栈)

🧩 4. 查看源码与执行位置

命令说明
list (l)显示当前行附近源码
list func显示函数源码
list 50,60显示第 50~60 行
layout src切换到源码视图(TUI 模式)
layout asm查看汇编代码
layout reg查看寄存器
focus cmd回到命令行模式

🧩 5. 查看变量与内存

命令说明
print var (p var)打印变量值
print &var打印变量地址
print *ptr打印指针指向的内容
print func()调用函数(慎用)
print/x var以十六进制打印
print (char*)ptr强制类型转换打印
x/10xw $rsp查看栈顶 10 个 4 字节 word(十六进制)
x/s ptr把内存当字符串打印
x/20gx ptr查看 20 个 8 字节十六进制值

x/[n][格式][大小] addrn=数量,格式=x,i,d,u,s,c,大小=b,h,w,g(1,2,4,8 字节)


🧩 6. 栈帧与调用栈

命令说明
bt显示调用栈(backtrace)
bt full显示调用栈 + 每帧的局部变量
frame N (f N)切换到第 N 层栈帧
up / down向上/向下移动栈帧
info frame显示当前栈帧详细信息(如你提供的例子)
info args显示当前函数参数
info locals显示当前函数局部变量

🧩 7. 寄存器

命令说明
info registers显示所有寄存器
info registers rax rbx显示指定寄存器
print $rax打印寄存器值
set $rax = 100修改寄存器值(慎用)

🧩 8. 多线程调试

命令说明
info threads查看所有线程
thread N切换到线程 N
thread apply all bt打印所有线程的调用栈
set scheduler-locking on锁定调度,只调试当前线程

🧩 9. 监视点(Watchpoint)

命令说明
watch varvar 被修改时中断
rwatch varvar 被读取时中断
awatch var读写都中断
info watchpoints查看监视点

适用于调试“某个变量为什么被改了”的问题。


🧩 10. 其他实用命令

命令说明
shell cmd在 GDB 中执行 shell 命令
define cmd定义宏命令
source script.gdb执行 GDB 脚本
set confirm off关闭确认提示
set print pretty on美化结构体打印
disassemble func反汇编函数
info symbol 0x...查地址对应的符号
maintenance info sections查看内存段(如 .text, .data)

四、典型调试流程(以段错误为例)

# 1. 启动 GDB(假设程序崩溃生成了 core 文件)
gdb ./myapp core
 
# 2. 查看调用栈
(gdb) bt
 
# 3. 切换到出问题的帧
(gdb) frame 0
 
# 4. 查看源码
(gdb) list
 
# 5. 查看变量和寄存器
(gdb) print ptr
(gdb) info registers
 
# 6. 检查内存访问
(gdb) x/s $rdi    # 如果是字符串函数出错
(gdb) x/10gx ptr-8
 
# 7. 查看参数和局部变量
(gdb) info args
(gdb) info locals
 
# 8. 检查是否空指针或野指针
(gdb) print ptr
# 如果是 0x0 或 0x1 或 0xffffffff,则非法

五、高级技巧与注意事项

✅ 技巧 1:使用 .gdbinit 自定义配置

# ~/.gdbinit
set confirm off
set print pretty on
set history save on
set scheduler-locking on
define hook-quit
    echo "Bye!\n"
end

✅ 技巧 2:自动打印常用变量

define hook-stop
    print var1
    print *ptr
    info registers rip rsp
end

✅ 技巧 3:调试优化过的代码(-O2)

  • 使用 gcc -O2 -g 仍可调试,但变量可能被优化。
  • print 可能提示 <optimized out>
  • 可尝试查看寄存器或汇编代码。

✅ 技巧 4:结合 valgrind 使用

valgrind --tool=memcheck ./myapp
# 发现内存错误后,再用 GDB 定位

✅ 技巧 5:远程调试(交叉调试)

target remote :1234
# 配合 gdbserver 使用

六、常见问题排查建议

问题建议命令
程序崩溃bt, info registers, x/i $pc
变量值不对print, watch, info locals
死循环Ctrl+C 中断,bt 看位置
多线程死锁info threads, thread apply all bt
函数没调用break, info break
内存越界valgrind, watch, x 查内存

栈帧

你提供的 info frame 命令输出是 GDB(GNU 调试器)中用于查看当前栈帧(stack frame)详细信息的命令结果。下面我将逐行详细解读这个输出,帮助你理解程序在崩溃或断点处的调用上下文。


📌 命令:(gdb) info frame

该命令用于显示当前栈帧(stack frame)的元信息,包括:

  • 栈帧的位置(栈指针)
  • 返回地址(return address)
  • 调用者帧
  • 参数和局部变量的位置
  • 保存的寄存器等

🔍 逐行解读输出

Stack level 0, frame at 0x7fffffffd4d0:
  • Stack level 0:表示这是调用栈的最顶层帧(即当前正在执行的函数)。
  • frame at 0x7fffffffd4d0:当前栈帧的“帧指针”(frame pointer)所在地址是 0x7fffffffd4d0。这个地址通常指向当前函数的栈帧起始位置(或接近栈帧底部)。

💡 注意:现代编译器可能优化掉帧指针(-fomit-frame-pointer),所以这个地址可能不是真正的 rbp,而是 GDB 推测的帧位置。


rip = 0x55555556af8b in runTest<bfloat16>
    (/home/hrj/SCCL/tests/oneDeviceFusion.cpp:553); saved rip = 0x55555555ad33
  • rip = 0x55555556af8b:当前指令指针(RIP,x86_64 架构)指向的地址是 0x55555556af8b,即程序当前正在执行的指令地址。
  • in runTest<bfloat16>:这个地址位于函数 runTest<bfloat16> 中,这是一个 C++ 模板函数,模板参数为 bfloat16
  • (/home/hrj/SCCL/tests/oneDeviceFusion.cpp:553):当前执行位置在源文件 oneDeviceFusion.cpp 的第 553 行
  • saved rip = 0x55555555ad33:这是保存的返回地址,即当 runTest<bfloat16> 函数执行完后,应该返回到的调用者地址。这个值通常保存在栈上(push %rip 的结果)。

✅ 这个“saved rip”是函数调用时由 call 指令自动压入栈的,用于 ret 指令返回。


called by frame at 0x7fffffffd500
  • 当前函数(runTest<bfloat16>)是由栈帧位于 0x7fffffffd500 的函数调用的。
  • 也就是说,0x7fffffffd500调用者(caller)的栈帧地址。
  • 你可以用 info frame 1 查看这个调用者的帧信息。

source language c++.
  • 当前帧对应的源代码是 C++ 语言编写,GDB 可以使用 C++ 的符号解析规则(如函数名修饰、类、模板等)。

Arglist at 0x7fffffffd4c0, args:
  • Arglist at 0x7fffffffd4c0:函数参数列表(参数区)的地址是 0x7fffffffd4c0
  • args: 后面没有列出具体参数,说明 GDB 无法识别或当前没有可显示的参数值(可能是因为优化、无调试符号、或参数未使用)。

💡 如果你希望看到参数值,可以尝试:

(gdb) info args

或直接打印某个参数:

(gdb) print param_name

Locals at 0x7fffffffd4c0, Previous frame's sp is 0x7fffffffd4d0
  • Locals at 0x7fffffffd4c0:局部变量存储在地址 0x7fffffffd4c0
  • 注意:这个地址和 Arglist 相同,说明参数和局部变量可能共享同一块栈空间(常见于优化后的代码)。
  • Previous frame's sp is 0x7fffffffd4d0
    • “Previous frame” 指的是调用者的栈帧。
    • 它的栈指针(sp)是 0x7fffffffd4d0
    • 也就是说,调用 runTest<bfloat16> 之前,栈顶是 0x7fffffffd4d0

⚠️ 注意:sp 是栈指针(stack pointer),fp 是帧指针(frame pointer)。这里 Previous frame's sp 实际上是当前帧的起始位置。


Saved registers:
 rbx at 0x7fffffffd4b8, rbp at 0x7fffffffd4c0, rip at 0x7fffffffd4c8
  • 列出了在当前栈帧中被保存的寄存器及其在栈中的位置:
    • rbx at 0x7fffffffd4b8:调用函数前,rbx 寄存器的值被保存在栈地址 0x7fffffffd4b8。因为 rbx 是 callee-saved 寄存器,函数若要使用它,必须先保存原值。
    • rbp at 0x7fffffffd4c0rbp(帧指针)被保存在 0x7fffffffd4c0。注意这个地址也用于参数和局部变量,说明 rbp 被压栈后,栈帧布局以此为基准。
    • rip at 0x7fffffffd4c8:返回地址(即 saved rip)保存在 0x7fffffffd4c8。这是 call 指令压入的地址。

🧠 栈布局示意图(从高地址到低地址):

0x7fffffffd4d0 +-----------------------+
                 |       (当前帧)        |
                 +-----------------------+
                 |       arg/local       | ← 0x7fffffffd4c0
                 +-----------------------+
                 |        saved rbp      | ← 0x7fffffffd4c0 (重叠)
                 +-----------------------+
                 |        saved rip      | ← 0x7fffffffd4c8
                 +-----------------------+
                 |        saved rbx      | ← 0x7fffffffd4b8
0x7fffffffd4b8 +-----------------------+

✅ 总结:这个 info frame 告诉我们什么?

信息项内容
当前函数runTest<bfloat16>
源码位置oneDeviceFusion.cpp:553
当前执行地址0x55555556af8b
返回地址0x55555555ad33(将返回到调用者)
调用者帧0x7fffffffd500
参数/局部变量位置0x7fffffffd4c0
保存的寄存器rbx, rbp, rip(返回地址)
栈帧布局使用了帧指针,参数、局部变量、保存寄存器共享栈空间

🔧 建议的后续调试命令

  1. 查看源码

    (gdb) list

    查看第 553 行附近的代码。

  2. 查看参数和局部变量

    (gdb) info args
    (gdb) info locals
  3. 查看调用栈

    (gdb) bt
    (gdb) bt full
  4. 查看调用者帧

    (gdb) info frame 1
  5. 检查返回地址对应函数

    (gdb) info symbol 0x55555555ad33

⚠️ 注意事项

  • 如果代码编译时使用了 -O2 或更高优化级别,GDB 显示的变量位置可能不准确(变量被优化到寄存器或消除)。
  • 确保编译时加上 -g 选项以包含调试信息。
  • 模板函数 runTest<bfloat16> 的符号可能被 mangling(名称修饰),可用 info symbolptype 辅助分析。

C/C++ 程序常见错误提示及 GDB 调试指南

一、常见错误和异常提示

1. 编译时错误(Compile-time Errors)

语法错误

// 缺少分号
error: expected ';' before '}' token
error: expected unqualified-id before 'return'
 
// 变量未声明
error: 'undefined_variable' was not declared in this scope
 
// 类型不匹配
error: cannot convert 'int*' to 'double*' in assignment
error: invalid conversion from 'const char*' to 'char'
 
// 函数未定义
error: 'function_name' was not declared in this scope
undefined reference to `function_name'

头文件相关

// 头文件未找到
fatal error: header_file.h: No such file or directory
compilation terminated.
 
// 重复包含
warning: #pragma once in main file

2. 运行时错误(Runtime Errors)

段错误(Segmentation Fault)

Segmentation fault (core dumped)

常见原因:

  • 空指针解引用
  • 数组越界
  • 野指针使用
  • 栈溢出

总线错误(Bus Error)

Bus error (core dumped)

常见原因:

  • 内存对齐问题
  • 访问无效的内存地址

中止错误(Aborted)

Aborted (core dumped)

常见原因:

  • assert() 失败
  • free() 重复释放内存
  • malloc() 内存不足

浮点异常

Floating point exception (core dumped)

常见原因:

  • 除以零
  • 无效的浮点运算

二、GDB 调试实战

1. 基本 GDB 调试流程

# 编译时添加调试信息
gcc -g -o program program.c
g++ -g -o program program.cpp
 
# 启动GDB
gdb ./program
 
# 运行程序
(gdb) run
# 或带参数运行
(gdb) run arg1 arg2

2. 常见错误的 GDB 调试方法

场景 1:段错误调试

// segfault_example.c
#include <stdio.h>
 
void bad_function() {
    int *ptr = NULL;
    *ptr = 42;  // 段错误
}
 
int main() {
    printf("程序开始\n");
    bad_function();
    printf("程序结束\n");
    return 0;
}

GDB 调试步骤:

# 启动GDB
gdb ./segfault_example
 
# 运行程序
(gdb) run
 
# 程序崩溃后,查看调用栈
(gdb) bt
# 输出:
# #0  0x0000555555555156 in bad_function () at segfault_example.c:5
# #1  0x000055555555517a in main () at segfault_example.c:10
 
# 查看当前栈帧的源代码
(gdb) list
# 输出:
#    1    #include <stdio.h>
#    2    
#    3    void bad_function() {
#    4        int *ptr = NULL;
#    5        *ptr = 42;  // 段错误
#    6    }
#    7    
#    8    int main() {
#    9        printf("程序开始\n");
#   10        bad_function();
 
# 查看变量值
(gdb) print ptr
# $1 = (int *) 0x0
 
# 查看寄存器
(gdb) info registers

场景 2:数组越界调试

// array_overflow.c
#include <stdio.h>
 
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    // 数组越界
    for (int i = 0; i <= 5; i++) {  // 错误:应该是 i < 5
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    
    return 0;
}

GDB 调试步骤:

# 设置断点
(gdb) break array_overflow.c:8
 
# 运行程序
(gdb) run
 
# 单步执行
(gdb) next
(gdb) next
 
# 查看变量
(gdb) print i
(gdb) print arr[i]
(gdb) print arr
 
# 继续执行
(gdb) continue

场景 3:内存泄漏检测

// memory_leak.c
#include <stdio.h>
#include <stdlib.h>
 
void memory_leak_function() {
    int *ptr = (int*)malloc(sizeof(int) * 10);
    // 忘记释放内存
    // free(ptr);
}
 
int main() {
    memory_leak_function();
    printf("内存泄漏测试\n");
    return 0;
}

使用 Valgrind 检测内存泄漏:

valgrind --tool=memcheck --leak-check=full ./memory_leak
 
# 输出:
# ==12345== HEAP SUMMARY:
# ==12345==     in use at exit: 40 bytes in 1 blocks
# ==12345==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
# ==12345== 
# ==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
# ==12345==    at 0x4C2E0EF: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
# ==12345==    by 0x108728: memory_leak_function (memory_leak.c:6)
# ==12345==    by 0x108745: main (memory_leak.c:11)

3. GDB 高级调试技巧

3.1 条件断点

# 在特定条件下停止
(gdb) break file.c:15 if x > 10
 
# 仅在第100次执行时停止
(gdb) break file.c:20
(gdb) command 1
> silent
> printf "x = %d, y = %d\n", x, y
> continue
> end

3.2 监视点(Watchpoint)

# 当变量值改变时停止
(gdb) watch variable_name
 
# 当内存地址被修改时停止
(gdb) watch *0x7ffffffeed00
 
# 条件监视点
(gdb) watch x if x == 0

3.3 函数调用调试

# 查看函数参数
(gdb) info args
 
# 查看局部变量
(gdb) info locals
 
# 打印变量值
(gdb) print variable_name
(gdb) p/x ptr      # 以十六进制显示
(gdb) p/d count    # 以十进制显示
(gdb) p array[0]@5 # 显示数组前5个元素

3.4 多线程调试

# 查看所有线程
(gdb) info threads
 
# 切换线程
(gdb) thread 2
 
# 查看特定线程的调用栈
(gdb) thread apply 2 bt
 
# 设置线程特定断点
(gdb) break file.c:15 thread 2

4. 实用 GDB 命令速查

命令说明
run运行程序
breakb设置断点
nextn单步执行(不进入函数)
steps单步执行(进入函数)
continuec继续执行
printp打印变量值
backtracebt显示调用栈
info locals显示局部变量
info args显示函数参数
listl显示源代码
watch设置监视点
delete删除断点
quitq退出 GDB

5. 调试配置文件

创建 .gdbinit 文件进行个性化配置:

# ~/.gdbinit
set confirm off
set pagination off
set print pretty on
set print array on
set print array-indexes on
set print elements 0
set history save on
set history filename ~/.gdb_history
set history size 1000
 
# 自动加载符号
set auto-solib-add on
 
# 显示源代码行数
set listsize 20
 
# 颜色支持
set style enabled on

6. 常见问题解决

问题 1:GDB 无法显示源代码

# 确保编译时包含调试信息
gcc -g -o program program.c
 
# 检查源文件路径
(gdb) show directories
 
# 添加源文件搜索路径
(gdb) directory /path/to/source

问题 2:Core 文件调试

# 使用core文件调试
gdb ./program core
 
# 或
gdb --core=core ./program
 
# 查看程序崩溃时的状态
(gdb) bt
(gdb) info registers
(gdb) info sharedlibrary

问题 3:动态库调试

# 确保加载了动态库的调试符号
(gdb) info sharedlibrary
 
# 如果缺少调试符号,安装对应的debug包
# Ubuntu/Debian: sudo apt-get install libxxx-dev libxxx-dbg
# CentOS/RHEL: sudo yum install libxxx-devel debuginfo-install libxxx

通过掌握这些常见的错误提示和 GDB 调试技巧,您可以更有效地定位和解决 C/C++ 程序中的各种问题。记住,良好的调试习惯包括:编译时添加调试信息、使用断点逐步排查、善用监视点和条件断点,以及结合 Valgrind 等工具进行内存分析。

Core Dump C/C++ 程序内核崩溃及 GDB 调试指南

1. 内核崩溃(Core Dump)是什么?

内核崩溃(Core Dump)是当程序异常终止时,操作系统将程序的内存映像保存到磁盘文件的过程。这个文件包含了程序崩溃时的完整内存状态,包括:

  • 程序的内存映像
  • 寄存器状态
  • 调用栈信息
  • 变量值
  • 堆栈内容

常见导致内核崩溃的原因

// 1. 空指针解引用
int *ptr = NULL;
*ptr = 10;  // SIGSEGV (段错误)
 
// 2. 数组越界
int arr[5];
arr[10] = 100;  // 可能导致SIGSEGV
 
// 3. 野指针
int *p;
*p = 42;  // 使用未初始化的指针
 
// 4. 内存重复释放
int *ptr = malloc(sizeof(int));
free(ptr);
free(ptr);  // 重复释放,SIGABRT
 
// 5. 栈溢出
void infinite_recursion() {
    infinite_recursion();  // 无限递归导致栈溢出
}
 
// 6. 缓冲区溢出
char buffer[10];
strcpy(buffer, "This string is too long!");  // 缓冲区溢出

2. 启用 Core Dump 功能

2.1 检查和设置 Core Dump 限制

# 查看当前core dump大小限制
ulimit -c
 
# 设置core dump大小为无限制
ulimit -c unlimited
 
# 或者设置具体大小(如1GB)
ulimit -c 1073741824
 
# 永久设置:编辑 /etc/security/limits.conf
# * soft core unlimited
# * hard core unlimited

2.2 配置 Core 文件命名和位置

# 查看当前core文件命名模式
cat /proc/sys/kernel/core_pattern
 
# 设置core文件命名模式
echo "core.%e.%p.%h.%t" | sudo tee /proc/sys/kernel/core_pattern
 
# 常用的core文件命名模式:
# %e - 可执行文件名
# %p - 进程PID
# %h - 主机名
# %t - 时间戳(UNIX时间)
# %u - 用户ID
# %g - 组ID

2.3 创建 Core Dump 测试程序

// crash_test.c
#include <stdio.h>
#include <stdlib.h>
 
void bad_function() {
    int *ptr = NULL;
    printf("即将访问空指针...\n");
    *ptr = 42;  // 这将导致段错误
}
 
int main() {
    printf("程序开始执行\n");
    printf("PID: %d\n", getpid());
    
    bad_function();
    
    printf("这行不会被执行\n");
    return 0;
}

编译并运行:

gcc -g -o crash_test crash_test.c
./crash_test

3. 使用 GDB 调试 Core 文件

3.1 基本 GDB 调试命令

# 使用GDB加载程序和core文件
gdb ./crash_test core
 
# 或者
gdb --core=core ./crash_test

3.2 GDB 常用调试命令

# 启动GDB后常用命令:
 
# 显示程序崩溃时的调用栈
(gdb) bt
(gdb) backtrace
 
# 显示带源代码行号的调用栈
(gdb) bt full
 
# 切换到特定栈帧
(gdb) frame 0
(gdb) frame 1
 
# 查看当前栈帧的局部变量
(gdb) info locals
 
# 查看特定变量的值
(gdb) print variable_name
(gdb) p ptr
(gdb) p *ptr
 
# 查看寄存器内容
(gdb) info registers
 
# 查看程序崩溃时的汇编代码
(gdb) disassemble
 
# 查看特定函数的汇编代码
(gdb) disassemble function_name
 
# 继续执行程序(通常不会成功)
(gdb) continue
 
# 退出GDB
(gdb) quit

3.3 实际调试示例

假设我们有以下程序:

// example.c
#include <stdio.h>
#include <string.h>
 
void process_string(char *str) {
    char buffer[50];
    
    // 模拟缓冲区溢出
    strcpy(buffer, str);
    
    // 处理字符串
    for (int i = 0; i < strlen(buffer); i++) {
        buffer[i] = toupper(buffer[i]);
    }
    
    printf("Processed: %s\n", buffer);
}
 
void analyze_data(int *data, int count) {
    int sum = 0;
    
    // 数组越界访问
    for (int i = 0; i <= count; i++) {  // 注意:应该是 i < count
        sum += data[i];
    }
    
    printf("Sum: %d\n", sum);
}
 
int main(int argc, char *argv[]) {
    // 测试缓冲区溢出
    char long_string[100] = "This is a very long string that will cause buffer overflow";
    process_string(long_string);
    
    // 测试数组越界
    int numbers[5] = {1, 2, 3, 4, 5};
    analyze_data(numbers, 5);
    
    return 0;
}

编译时包含调试信息:

gcc -g -fsanitize=address -o example example.c

运行程序产生 core 文件,然后使用 GDB 调试:

# 加载程序和core文件
gdb ./example core
 
# 查看调用栈
(gdb) bt
# 输出可能类似:
# #0  0x00007f8a1b2c5337 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
# #1  0x00007f8a1b2c8a28 in __GI_abort () at abort.c:89
# #2  0x00007f8a1b3062a4 in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7f8a1b415210 "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:175
# #3  0x00007f8a1b3afbe8 in __GI___fortify_fail (msg=<optimized out>) at fortify_fail.c:37
# #4  0x00007f8a1b3afbb9 in __GI___fortify_fail_abort (need_backtrace=need_backtrace@entry=0, msg=msg@entry=0x7f8a1b4150d8 "stack smashing detected") at fortify_fail.c:43
# #5  0x00007f8a1b3afb66 in __stack_chk_fail () at stack_chk_fail.c:28
# #6  0x00000000004006b6 in process_string (str=0x7ffca5e8b9f0 "This is a very long string that will cause buffer overflow") at example.c:8
# #7  0x000000000040075d in main (argc=1, argv=0x7ffca5e8baa8) at example.c:27
 
# 切换到问题函数的栈帧
(gdb) frame 6
# #6  0x00000000004006b6 in process_string (str=0x7ffca5e8b9f0 "This is a very long string that will cause buffer overflow") at example.c:8
 
# 查看源代码
(gdb) list
# 输出源代码,显示问题行
 
# 查看局部变量
(gdb) info locals
# buffer = "This is a very long string that will cause buffer ove\000\060\252\202\232|\177\000\000\360Y\252\232|\177\000"
 
# 查看传入的参数
(gdb) print str

4. 高级调试技巧

4.1 使用 GDB 脚本自动化调试

# debug_script.gdb
set confirm off
set pagination off
 
# 加载core文件
target core core
 
# 显示调用栈
backtrace full
 
# 显示寄存器
info registers
 
# 显示所有线程的调用栈(多线程程序)
thread apply all backtrace
 
# 保存调试信息到文件
set logging on
set logging file debug_output.txt
 
# 执行常用命令
backtrace
info locals
info args
 
set logging off
 
quit

使用脚本:

gdb -x debug_script.gdb ./program core

4.2 分析多线程程序的 Core 文件

// multithread_crash.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
 
void* thread_function(void* arg) {
    int thread_id = *(int*)arg;
    
    printf("线程 %d 开始执行\n", thread_id);
    
    // 模拟线程崩溃
    if (thread_id == 2) {
        int *ptr = NULL;
        *ptr = 42;  // 线程2将崩溃
    }
    
    sleep(5);  // 其他线程继续运行
    return NULL;
}
 
int main() {
    pthread_t threads[3];
    int thread_ids[3] = {1, 2, 3};
    
    // 创建多个线程
    for (int i = 0; i < 3; i++) {
        pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
    }
    
    // 等待线程结束
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    return 0;
}

调试多线程 core 文件:

# 查看所有线程
(gdb) info threads
 
# 切换到特定线程
(gdb) thread 2
 
# 查看当前线程的调用栈
(gdb) bt
 
# 查看所有线程的调用栈
(gdb) thread apply all bt

4.3 内存泄漏检测(结合 Valgrind)

# 使用Valgrind检测内存问题
valgrind --tool=memcheck --leak-check=full ./program
 
# Valgrind会报告:
# - 内存泄漏
# - 无效内存访问
# - 未初始化内存使用
# - 内存管理错误

5. 预防和最佳实践

5.1 编译时启用安全检查

# 启用各种安全检查
gcc -g -O2 \
    -fsanitize=address \
    -fsanitize=undefined \
    -fstack-protector-all \
    -D_FORTIFY_SOURCE=2 \
    -Wformat-security \
    -Werror \
    program.c -o program

5.2 代码中的预防措施

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
// 安全的字符串复制
char* safe_strcpy(char *dest, size_t dest_size, const char *src) {
    if (!dest || !src || dest_size == 0) {
        return NULL;
    }
    
    size_t src_len = strlen(src);
    if (src_len >= dest_size) {
        return NULL;  // 目标缓冲区太小
    }
    
    strcpy(dest, src);
    return dest;
}
 
// 安全的内存访问
int safe_array_access(int *array, size_t size, size_t index) {
    if (!array || index >= size) {
        fprintf(stderr, "数组访问越界: index=%zu, size=%zu\n", index, size);
        return -1;
    }
    return array[index];
}
 
// 智能指针模式(C语言模拟)
typedef struct {
    void *ptr;
    size_t size;
} SafePointer;
 
SafePointer* safe_malloc(size_t size) {
    SafePointer *sp = malloc(sizeof(SafePointer));
    if (!sp) return NULL;
    
    sp->ptr = malloc(size);
    sp->size = size;
    
    if (!sp->ptr) {
        free(sp);
        return NULL;
    }
    
    return sp;
}
 
void safe_free(SafePointer *sp) {
    if (sp) {
        if (sp->ptr) {
            free(sp->ptr);
            sp->ptr = NULL;
        }
        free(sp);
    }
}

6. 常见问题排查

6.1 Core 文件未生成

# 检查core dump是否启用
ulimit -c
 
# 检查文件系统是否有足够空间
df -h
 
# 检查目录权限
ls -ld /path/to/core/directory
 
# 检查core_pattern设置
cat /proc/sys/kernel/core_pattern
 
# 检查系统日志
dmesg | grep -i core

6.2 GDB 无法加载符号

# 确保程序编译时包含调试信息
(gdb) info sources
(gdb) info functions
 
# 如果没有符号信息,重新编译
gcc -g -o program program.c

通过以上方法,您可以有效地调试 C/C++ 程序的内核崩溃问题,快速定位和修复程序中的内存错误。

MPI 多进程调试

${MPI_HOME}/bin/mpiexec --allow-run-as-root --mca pml ^ucx --mca btl ^openib -x BR_UMD_DEBUG_P2P_ACCESS_CHECK=0 \ --mca plm_rsh_args "-p 22 -Y" --mca btl_tcp_if_include 10.90.24.0/24 --host 10.90.24.64:8,10.90.24.66:8 \
xterm -e gdb --args ./program args 

-Y 启动启动转发,xterm 启动图形界面

mpi

debugging - MPI并行程序的调试技巧 - galois - SegmentFault 思否

[程序调试 — 中国科大超级计算中心用户使用手册 :2024-05-18 版 文档](程序调试 - 中国科大超级计算中心用户使用手册 :2024-05-18版 文档)