Gdb 使用方法概述
从 调试目标、关键信息、常用命令、调试流程、技巧与注意事项 五个方面,全面介绍如何使用 GDB 进行高效调试。
一、GDB 调试的核心目标
使用 GDB 的目的通常包括:
- 定位程序崩溃原因(如段错误、非法指令)
- 分析死循环或逻辑错误
- 查看变量值变化过程
- 验证函数调用流程
- 检查内存访问问题(越界、use-after-free)
- 逆向分析二进制程序(无源码时)
二、调试时应重点关注的信息
1. 调用栈(Call Stack)
- 函数是如何一层层调用的?
- 崩溃发生在哪一层?
- 调用者是谁?参数是什么?
🔍 关键命令:
bt(backtrace)、frame N
2. 当前执行位置(源码 + 汇编)
- 程序停在了哪一行代码?
- 是不是预期的执行路径?
- 是否跳过了某些判断或循环?
🔍 关键命令:
list、layout asm、x/i $pc
3. 变量和参数的值
- 局部变量、全局变量、函数参数是否符合预期?
- 是否有未初始化、越界、类型错误?
🔍 关键命令:
info locals、info args
4. 寄存器状态
- 特别是在崩溃时,
rip(指令指针)、rsp(栈指针)、rax(返回值)等是否合法? - 是否访问了非法地址(如
0x0)?
🔍 关键命令:
info registers、x/gx $rsp
5. 内存内容
- 指针指向的内存是否有效?
- 字符串、数组、结构体内容是否正确?
- 是否存在内存越界或堆损坏?
🔍 关键命令:
x(examine)、print *ptr、print arr[i]
6. 控制流(断点、单步、条件)
- 如何控制程序执行?
- 如何跳入/跳过函数?
- 如何在特定条件下中断?
🔍 关键命令:
break、step、next、finish、continue
7. 线程与多进程状态(多线程程序)
- 哪个线程导致了问题?
- 是否死锁、竞争条件?
🔍 关键命令:
info threads、thread N、thread 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][格式][大小] addr:n=数量,格式=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 var | 当 var 被修改时中断 |
rwatch var | 当 var 被读取时中断 |
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 0x7fffffffd4c0:rbp(帧指针)被保存在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(返回地址) |
| 栈帧布局 | 使用了帧指针,参数、局部变量、保存寄存器共享栈空间 |
🔧 建议的后续调试命令
-
查看源码:
(gdb) list查看第 553 行附近的代码。
-
查看参数和局部变量:
(gdb) info args (gdb) info locals -
查看调用栈:
(gdb) bt (gdb) bt full -
查看调用者帧:
(gdb) info frame 1 -
检查返回地址对应函数:
(gdb) info symbol 0x55555555ad33
⚠️ 注意事项
- 如果代码编译时使用了
-O2或更高优化级别,GDB 显示的变量位置可能不准确(变量被优化到寄存器或消除)。 - 确保编译时加上
-g选项以包含调试信息。 - 模板函数
runTest<bfloat16>的符号可能被 mangling(名称修饰),可用info symbol或ptype辅助分析。
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 file2. 运行时错误(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 arg22. 常见错误的 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
> end3.2 监视点(Watchpoint)
# 当变量值改变时停止
(gdb) watch variable_name
# 当内存地址被修改时停止
(gdb) watch *0x7ffffffeed00
# 条件监视点
(gdb) watch x if x == 03.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 24. 实用 GDB 命令速查
| 命令 | 说明 |
|---|---|
run | 运行程序 |
break 或 b | 设置断点 |
next 或 n | 单步执行(不进入函数) |
step 或 s | 单步执行(进入函数) |
continue 或 c | 继续执行 |
print 或 p | 打印变量值 |
backtrace 或 bt | 显示调用栈 |
info locals | 显示局部变量 |
info args | 显示函数参数 |
list 或 l | 显示源代码 |
watch | 设置监视点 |
delete | 删除断点 |
quit 或 q | 退出 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 on6. 常见问题解决
问题 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 unlimited2.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 - 组ID2.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_test3. 使用 GDB 调试 Core 文件
3.1 基本 GDB 调试命令
# 使用GDB加载程序和core文件
gdb ./crash_test core
# 或者
gdb --core=core ./crash_test3.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) quit3.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 str4. 高级调试技巧
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 core4.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 bt4.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 program5.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 core6.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版 文档)