GDB: 使用 Linux GDB 识别内存泄漏

使用 Linux GDB 识别内存泄漏

摘要

本文档介绍如何在 Linux 上使用 GDB 跟踪内存泄漏。本文档适用于通过 malloc() 分配但未释放的内存块的内存泄漏问题。

正文

调试器运行速度较慢,可能会导致性能问题。如果可以通过 LD_PRELOAD 或其他方式覆盖默认的 malloc()/free() 例程,这可能比使用调试器更快且更简单。

注意:下面提供的 GDB 脚本可以根据需要进行修改。需记住,由于 GDB 版本、操作系统版本、编译选项等因素,可能会导致结果略有不同,因此应将此脚本视为一个基础,而不是在所有平台上对任何可执行文件都适用的通用解决方案。

首先,我们需要明确要做的事情:

  • 每次进入 malloc() 时,应将请求分配的内存大小保存到一个变量中。
  • 每次从 malloc() 返回时,应打印大小和从 malloc() 返回的地址。
  • 每次进入 free() 时,应打印即将释放的指针。

由于我们不希望每次进行内存分配或释放时都手动与调试器交互,因此需要将命令放入脚本中,让 GDB 作为参数执行该脚本,无需任何手动干预。以下是脚本的示例以及典型的输出,并对脚本进行一些解释。

GDB 脚本 (gdbcmd1)

set pagination off
set breakpoint pending on
set logging file gdbcmd1.out
set logging on
hbreak malloc
commands
set $mallocsize = (unsigned long long) $rdi
continue
end
hbreak *(malloc+191)
commands
printf "malloc(%ld)=0x%016lx\n",$mallocsize,$rax
continue
end
hbreak free
commands
printf "free(0x%016lx)\n", (unsigned long long) $rdi
continue
end
continue
  • RDI寄存器:用于传递第一个整数或指针类型的参数。这意味着当你按照规范调用一个函数时,如果该函数的第一个参数是整型或者是指针类型(包括数组,因为它们在C语言中被视为指针),那么这个参数的值会被放入RDI寄存器中传递给被调用的函数。
  • RAX寄存器:整数和指针类型的返回值:使用RAX寄存器。如果返回值是一个64位以内的整数或指针,那么这个值会被放入RAX寄存器中返回给调用者。

使用脚本附加运行进程

# gdb --command=gdbcmd1 server2 11543

程序将运行并将输出发送到名为 gdbcmd1.out 的文件中。由于该文件还包含 GDB 消息,除了我们在脚本中添加的 printf 输出外,可以通过以下命令获取更清晰的输出:

# grep -e "malloc" -e "free" gdbcmd1.out

输出示例

malloc(57)=0x0000000012e1b260
free(0x0000000012e1b260)
malloc(57)=0x0000000012e1b260
free(0x0000000012e1b260)
malloc(100)=0x000000000e497d30
malloc(15)=0x0000000011bda010
malloc(568)=0x0000000011bda030
free(0x0000000000000000)
malloc(2248)=0x0000000011bda270
free(0x0000000011bda030)
malloc(20)=0x0000000011bda030
malloc(20)=0x0000000011bda050
malloc(20)=0x0000000011bda070
malloc(20)=0x0000000011bda090
malloc(20)=0x0000000011bda0b0
malloc(20)=0x0000000011bda0d0
free(0x0000000011bda010)
malloc(15)=0x0000000011bda010
free(0x0000000011bda010)
malloc(15)=0x0000000011bda010
malloc(21)=0x0000000011bda210
malloc(21)=0x0000000011bda230
malloc(56)=0x0000000011bdaf60

得到输出后,可以解析以找出从未释放的内存块。之后,可以在 malloc() 断点的命令中添加 ‘where’ 命令,以查看分配的来源。例如,可以将以下内容:

hbreak malloc
commands
set $mallocsize = (unsigned long long) $rdi
continue
end

替换为:

hbreak malloc
commands
set $mallocsize = (unsigned long long) $rdi
where
continue
end

关于 GDB 脚本 gdbcmd1 的一些说明

  • set pagination off:防止每次屏幕被消息填满时需要按 ‘return’,这可以视为某种 ‘自动滚动’ 选项。
  • set breakpoint pending on:如果包含函数的库尚未加载,调试器会在放置断点之前等待其加载。在这种情况下,它应该已经在运行进程中加载。
  • set logging file gdbcmd1.out:指示 GDB 将输出保存到名为 gdbcmd1.out 的文件中。
  • set logging on:指示 GDB 开始将输出发送到 gdbcmd1.out。
  • hbreak malloc:在 malloc 的入口处设置一个 ‘硬件辅助’ 断点。将请求分配的大小保存到一个名为 ‘mallocsize’ 的变量中。在 X86 AMD 上,函数的第一个参数在寄存器 $rdi 中。
  • hbreak *(malloc+191):在 malloc 返回处设置一个 ‘硬件辅助’ 断点。打印大小以及 malloc 返回的指针。在 X86 AMD 上,返回值在寄存器 $rax 中。有关从 malloc 找到返回指令偏移量的方法,请参见下方说明。
  • hbreak free:在 free 函数入口处设置断点,打印即将释放的指针地址,然后继续执行程序。

找到 malloc 的返回指令偏移量

可以使用 GDB 的 ‘finish’ 指令,但在许多情况下它可能无法按预期工作,因此采用上述方法作为替代。要找到 malloc 中的返回指令偏移量,需要检查 malloc 的汇编代码(在 GDB 中使用 ‘disas malloc’),并找到 ‘retq’(64 位)指令。例如:

(gdb) disas malloc
Dump of assembler code for function malloc:
0x0000003f8a673fb0<malloc+0>:   mov %rbp,-0x10(%rsp)
0x0000003f8a673fb5<malloc+5>:   mov %rbx,-0x18(%rsp)
0x0000003f8a673fba<malloc+10>:  mov %rdi,%rbp
0x0000003f8a673fbd<malloc+13>:  mov %r12,-0x8(%rsp)
0x0000003f8a673fc2<malloc+18>:  sub $0x18,%rsp
0x0000003f8a673fc6<malloc+22>:  mov 0x2dbe53(%rip),%rax
0x0000003f8a673fcd<malloc+29>:  mov (%rax),%rax
0x0000003f8a674055<malloc+165>: mov 0x8(%rsp),%rbp
0x0000003f8a67405a<malloc+170>: mov 0x10(%rsp),%r12
0x0000003f8a67405f<malloc+175>: add $0x18,%rsp
0x0000003f8a674063<malloc+179>: retq
0x0000003f8a674064<malloc+180>: mov %rbx,%rdi
0x0000003f8a674067<malloc+183>: mov %rbp,%rsi
0x0000003f8a67406a<malloc+186>: xor %r12d,%r12d

计算返回指令的偏移量:

0x0000003f8a674063 - 0x0000003f8a673fb0 = 179

可能需要对脚本进行一些调整以获得所需的结果,但此脚本应为你提供一个良好的起点。

参考:LINUX GDB: IDENTIFY MEMORY LEAKS

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值