ltrace和strace是Linux上常用的调试工具:strace跟踪用户程序的系统调用;ltrace跟踪动态库函数的调用。
本文介绍怎么使用ltrace找到内存泄露-调用了malloc而没有调用对应的free函数释放内存。
看下面一段无意义的代码:
#include <stdlib.h>
#include <stdio.h>
int main()
{
char *p = (char*)malloc(1024);
sprintf(p, "%s", "abcde");
printf("%s\n", p);
int *p1 = (int*)malloc(4);
*p1 = 100;
void *p2 = malloc(256);
free(p1);
int a = 2;
printf("%d\n", a);
free(p);
void *p3 = malloc(124);
free(p2);
void *p4 = malloc(23);
free(p4);
return 0;
}
使用ltrace调试:
$ ltrace ./a.out
__libc_start_main(0x4005f6, 1, 0x7ffc2cd65088, 0x4006c0 <unfinished ...>
malloc(1024) = 0x1ae7010
puts("abcde"abcde) = 6
malloc(4) = 0x1ae7830
malloc(256) = 0x1ae7850
free(0x1ae7830) = <void>
printf("%d\n", 22) = 2
free(0x1ae7010) = <void>
malloc(124) = 0x1ae7010
free(0x1ae7850) = <void>
malloc(23) = 0x1ae70a0
free(0x1ae70a0) = <void>
+++ exited (status 0) +++
找到我们关注的malloc和free函数调用:
malloc(1024) = 0x1ae7010
malloc(4) = 0x1ae7830
malloc(256) = 0x1ae7850
free(0x1ae7830) = <void>
free(0x1ae7010) = <void>
malloc(124) = 0x1ae7010
free(0x1ae7850) = <void>
malloc(23) = 0x1ae70a0
free(0x1ae70a0) = <void>
简单的跟踪malloc分配内存地址和free释放内存地址就可以找到哪里发生了内存泄露,上面0x1ae7010没有释放(malloc(124))。为了简单我忽略了calloc/realloc。
ltrace并不能直接定位到调用位置,也就是说我们只知道发生了内存泄露。后续你可以选择使用Valgrind(http://valgrind.org/)定位位置,它是内存调试、内存泄漏检测以及性能分析的工具。
使用Python脚本分析输出,实现自动检测:
把j结果输出到文件:
$ ltrace -o memlake ./a.out
$ cat memlake
__libc_start_main(0x4005f6, 1, 0x7fff9caa3138, 0x4006c0 <unfinished ...>
malloc(1024) = 0x152c010
puts("abcde") = 6
malloc(4) = 0x152c830
malloc(256) = 0x152c850
free(0x152c830) = <void>
printf("%d\n", 2) = 2
free(0x152c010) = <void>
malloc(124) = 0x152c010
free(0x152c850) = <void>
malloc(23) = 0x152c0a0
free(0x152c0a0) = <void>
+++ exited (status 0) +++
Python脚本:
import re
import sys
import shlex
with open(sys.argv[1], 'r') as f:
flines = f.readlines()
lines = filter(lambda x: 'malloc' in x or 'free' in x, flines)
memtable = {}
for line in lines:
tokens = shlex.split(line)
# malloc(size) = addr
if 'malloc' in tokens[0]:
size = re.search(r'malloc\((.*)\)', tokens[0]).group(1)
addr = tokens[2]
memtable[addr] = size
# free(addr) = <void>
elif 'free' in tokens[0]:
addr = re.search(r'free\((.*)\)', tokens[0]).group(1)
memtable.pop(addr, None)
for k, v in memtable.iteritems():
print('leaked', v, 'bytes of memory at', k)
$ python checkmem.py memlake
('leaked', '124', 'bytes of memory at', '0x152c010')
$ python checkmem.py memlake
('leaked', '124', 'bytes of memory at', '0x152c010')
注意:上面脚本并没有处理calloc和realloc。