调试排查工具介绍(gdb、strace、Valgrind等)
1.GDB用户态调试 /KGDB内核级调试
使用GDB调试之前,需要用-g选项编译程序,这样可执行文件中才会包含符号表(变量名、函数名、行号等信息)g++ -g -o myprogram myprogram.cpp
#步骤1:启动GDB并设置断点,(delete是删断点) gdb ./test (gdb) break main # 在main函数入口打断点 (gdb) break 25 # 在源文件第25行打断点 (gdb) break func1 if x==100 # 条件断点:x等于100时才触发 (gdb) info breakpoints # 查看所有断点 # 步骤2:运行程序 (gdb) run # 开始运行,直到遇到断点 (gdb) run arg1 arg2 # 带命令行参数运行 (gdb) run < input.txt # 重定向标准输入 # 步骤3:当程序停在断点时,单步执行与继续 (gdb) next # n,单步执行,不进入函数内部 (gdb) step # s,单步执行,会进入函数内部 (gdb) continue # c,继续运行,直到下一个断点或程序结束 (gdb) finish # 执行完当前函数并返回(跳出函数) (gdb) until 30 # 运行到第30行(用于跳出循环) # 步骤4:查看状态 (gdb) print var # 打印变量var的值 (gdb) display var # 每次暂停时自动打印var的值 (gdb) info locals # 查看当前函数的所有局部变量 (gdb) info registers # 查看寄存器内容 (gdb) backtrace # bt,查看函数调用栈(崩溃时最常用) (gdb) list # 显示当前执行的源代码 (gdb) disas # 显示汇编代码 # 步骤5:修改变量(调试时临时改变行为) (gdb) set var x=100 # 将变量x的值改为100 (gdb) call func2() # 手动调用函数func2举例:
段错误 (core dumped) : SIGSEGV 信号,错误码编号为 11。是由于程序尝试访问不存在的内存区域引发的。
常见原因如下:
访问未初始化的指针(空指针解引用),ptr 未指向合法内存(如未通过new 分配内存)。
数组越界访问
释放后使用内存
栈溢出。递归过深或局部变量占用过多栈空间。
如何定位和解决?
运行 GDB执行程序,查看堆栈信息:(gdb) bt(Backtrace)定位错误位置。
利用gdb调试器跟踪调用栈
使用gdb,在程序崩溃时查看调用栈(bt命令),定位最后执行的函数,检查该函数中是否有异常内存操作。若栈溢出,调用栈会显示递归层数过深的函数;若堆溢出,可能指向new/malloc的异常调用。
Core Dump文件是程序崩溃时的内存快照,包含虚拟内存布局、寄存器状态、调用栈等信息。
具体实现:
解析 Core Dump 中的 “程序头” 获取内存段信息(代码段、数据段、堆、栈的地址范围);
从栈内存中提取调用栈(函数返回地址),映射到源代码的行号;
查看堆内存中的对象状态(如通过print查看指针指向的内存内容),判断是否有异常分配(如超大对象)。
2. strace /ftrace / ltrace / ptrace/bpftrace 等,或使用trace-cmd工具
ptrace:Linux 的系统调用接口(API),供调试/跟踪进程使用。
strace:基于 ptrace,跟踪进程执行时的 系统调用 和 接收到的信号。是排查线上故障(如文件打不开、权限错误、网络连接失败等)的首选工具。
ltrace:跟踪用户态库函数(动态链接的函数调用),通常也基于 ptrace。
ftrace:内核内建的跟踪框架,跟踪内核函数、上下文切换、IRQ、调度等,需在内核/tracefs 中使用(通常需 root)。
# -c 打印执行uptime时系统系统调用的时间、次数、出错次数和syscall strace -c uptime # -f 跟踪主进程及其所有子进程(对 shell 脚本、多进程程序很重要) strace -f ./my_script.sh # 显示 ls 命令执行过程中所有的系统调用(如 openat, read, write, close 等) strace ls /tmp # 把 strace 输出写入 debug.log,避免干扰终端 strace -o debug.log nginx -t # 跟踪 PID 为 1234 的进程(需有权限),常用于诊断卡住的服务 strace -p 1234 # 只跟踪特定类型的系统调用,如open、read、write、network等 strace -e trace=open,read,write curl https://example.comstrace 使用实践:定位一次系统无法解析域名故障。
无法访问外网域名,提示Name or service not know。且已检查系统DNS配置文件/etc/resolv.conf正确,排除DNS解析失败。域名解析通常跟系统读取文件相关,因此只查看open file的过程。
strace -e strace=open ping www.baidu.com
如上图所示在系统调用过程中出现/usr/lib64/libnss_dns.so.2文件缺失,则问题根因已确定为libnss_dns.so.2系统库文件缺失。libnss_dns.so.2文件由glibc-devel包产生,因此重新安装该包即可。
3.Valgrind - 内存泄漏检测 (该释放的资源没有及时释放)
内存泄漏原因:
1.new/delete,new[]/delete[] 没有配对使用,忘记释放动态分配的内存;
2.异常安全漏洞:异常改变了正常的执行流程,导致释放代码被跳过;
3.当多个部分共享同一内存时,释放责任不明确;
4.使用STL容器时存储了指针,清空容器时,指针指向的资源其实并没有释放,造成内存泄漏;
5.智能指针循环引用问题
方法:使用完内存或资源后立即释放;使用智能指针自动管理内存生命周期;RAII 类封装资源。
内存池能显著降低内存泄露的风险,一次性从系统申请一大块内存;后续的小对象分配不再调用 new/malloc,而是从池中切分;当整个池不再需要时,一次性释放整块内存,无需逐个 delete。
valgrind
安装完valgrind,编译程序时一定加上-g! gcc -g -o leak leak.c
运行Valgrind检测,valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./leak
--tool=memcheck:使用内存检查工具(默认工具)
--leak-check=full:详细显示内存泄漏信息
--show-leak-kinds=all:显示所有类型的泄漏
--track-origins=yes 追踪未初始化值的来源
--log-file=valgrind.log 将输出保存到文件
--num-callers=20 显示20层调用栈
分析Valgrind输出结果,解读关键信息,比如:
definitely lost: 100 bytes:明确的内存泄漏,100字节
by 0x10916D: main (leak.c:6):泄漏发生在 leak.c 文件的第6行(就是 malloc(100) 那行)
1 allocs, 0 frees:分配了1次,释放了0次
ASan
另一个方法是ASan(AddressSanitizer)生成报告,但不一定100%罗列出来问题。
优点是比 Valgrind 快很多,但泄漏检测不如 Valgrind 精确。需额外开启内存泄漏检测:
gcc -fsanitize=address -fno-omit-frame-pointer -g -o leak leak.c