关键词:MALLOC_CHECK_、mtrace()、muntrace()、MALLOC_TRACE、mprobe()、-lmcheck等等。
MALLOC_CHECK_提供了类似于mcheck()和mprobe()函数的功能,但是无需对程序进行修改和重新编译。
设置不同整数值可以控制程序对内存分配错误的响应方式。
0 - 不产生错误信息,也不中止这个程序
1 - 产生错误信息,但是不中止这个程序
2 - 不产生错误信息,但是中止这个程序
3 - 产生错误信息,并中止这个程序
下面构造一个double free的错误程序验证一下:
#include
#include
void main(void)
{
char *s = NULL;
s = malloc();
free(s);
free(s);
}
env MALLOC_CHECK_=0 ./dfree的效果类似于直接执行./dfree。
env MALLOC_CHECK_=1 ./dfree产生如下的错误信息:
*** Error in `./dfree': free(): invalid pointer: 0x0000000000e22010 ***
env MALLOC_CHECK_=2 ./dfree简单的终止程序,生成coredump文件。
Aborted (core dumped)
通过分析core文件,然后bt full查看backtrace:
(gdb) bt full
resultvar =
pid =
selftid =
save\_stage =
act = {\_\_sigaction\_handler = {sa\_handler = 0x0, sa\_sigaction = 0x0}, sa\_mask = {\_\_val = { <repeats times>, , }}, sa\_flags = , sa\_restorer = 0x0}
sigs = {\_\_val = {, <repeats times>}}
No locals.
No locals.
ar\_ptr = <optimized out>
p = <optimized out>
hook = <optimized out>
No symbol table info available.
env MALLOC_CHECK_=3 ./dfree显示更多的信息并且coredump。
*** Error in `./dfree': free(): invalid pointer: 0x0000000000aa0010 ***
======= Backtrace: =========--------------------------------------------------------------------问题点的backtrace。
/lib/x86_64-linux-gnu/libc.so.(+0x777e5)[0x7f640d3447e5]
/lib/x86_64-linux-gnu/libc.so.(+0x7f72a)[0x7f640d34c72a]
/lib/x86_64-linux-gnu/libc.so.(cfree+0xf7)[0x7f640d3515e7]
./dfree[0x40059c]
/lib/x86_64-linux-gnu/libc.so.(__libc_start_main+0xf0)[0x7f640d2ed830]
./dfree[0x400499]
======= Memory map: ========---------------------------------------------------------------------当前进程的maps。
下面是详细的栈信息:
# 0x00007f640d302428 in __GI_raise (sig=sig@entry=) at ../sysdeps/unix/sysv/linux/raise.c:
resultvar =
pid =
selftid =
save\_stage =
act = {\_\_sigaction\_handler = {sa\_handler = 0x2070782d72203030, sa\_sigaction = 0x2070782d72203030}, sa\_mask = {\_\_val = {, , , ,
, , , , , , , , , ,
, }}, sa\_flags = , sa\_restorer = 0x48}
sigs = {\_\_val = {, <repeats times>}}
ap = <error reading variable ap (Attempt to dereference a generic pointer.)>
fd =
on\_2 = <optimized out>
list = <optimized out>
nlist = <optimized out>
cp = <optimized out>
written = <optimized out>
buf = "0000000000aa0010"
cp = <optimized out>
ar\_ptr = 0x7f640d691b20 <main\_arena>
ptr = <optimized out>
str = 0x7f640d45acaf "free(): invalid pointer"
action = <optimized out>
No locals.
ar\_ptr = <optimized out>
p = <optimized out>
hook = <optimized out>
No symbol table info available.
mtrace()和muntrace()函数分别在程序中打开和关闭对内存分配调用进行跟踪的功能。
这两个函数要与环境变量MALLOC_TRACE搭配使用,该变量定义了写入跟踪信息的文件名。
构造malloc()但是不释放的场景:
#include
#include
#include
int main(int argc, char **argv)
{
mtrace();
char \* p = malloc();
free(p);
p = malloc();
muntrace();
return ;
}
gcc mtrace.c -o mtrace -g编译带调试信息。
export MALLOC_TRACE=/home/al/test/mtrace.log设置mtrace()信息输出路径。
然后执行./mtrace,就会在MALLOC_TRACE下生成malloc()/free()轨迹信息。
= Start
@ ./mtrace:[0x400624] + 0x10b4450 0x64
@ ./mtrace:[0x400634] - 0x10b4450
@ ./mtrace:[0x40063e] + 0x10b44c0 0x3e8
= End
上面的信息格式为:@ 程序名称:[内存分配释放调用的地址] +/- 操作的内存地址 参数。
+表示malloc,-表示free。
或者如下类型log:
@ 程序名称:(函数名称+偏移量) [内存分配释放调用地址] +/-/> 操作内存地址 参数。
+:对应一个malloc()操作。
-:对应一个free()操作。
>:对应一个realloc()操作,成对出现。<表示释放之前内存,>表示申请后结果。如果realloc()的内存第一次申请,那么就对应一个+号。
@ /usr/lib/libglib-2.0.so.:(g_malloc+0x2a)[0x2ac7c412] + 0x2b510f50 0x3------------------------g_malloc()中申请0x3字节大小内存,返回地址为0x2b510f50。
@ /usr/lib/libglib-2.0.so.:(g_realloc+0x38)[0x2ac7c4cc] + 0x2b510f60 0x20----------------------g_realloc()指针之前没有对应内存,所以对应+。表示申请0x20字节大小内存,返回地址为0x2b510f60。
@ /usr/lib/libglib-2.0.so.:(g_free+0x20)[0x2ac7c524] - 0x2b510f40
@ /usr/lib/libglib-2.0.so.:(g_malloc+0x2a)[0x2ac7c412] + 0x2b510f40 0xc------------------------在g_malloc()中申请0xc字节大小内存,返回地址为0x2b510f40.
@ /usr/lib/libglib-2.0.so.:(g_malloc+0x2a)[0x2ac7c412] + 0x2b510f88 0xc
@ /usr/lib/libglib-2.0.so.:(g_realloc+0x38)[0x2ac7c4cc] < 0x2b510f60---------------------------g_realloc()对应的内存已经存在,所以先释放0x2b510f60内存。
@ /usr/lib/libglib-2.0.so.:(g_realloc+0x38)[0x2ac7c4cc] > 0x2b510f98 0x40----------------------然后重新申请0x40大小的内存,返回地址为0x2b50f98.
@ /usr/lib/libglib-2.0.so.:(g_free+0x20)[0x2ac7c524] - 0x2b510f40------------------------------在g_free()中释放地址为0x2b510f40的内存。
@ /usr/lib/libglib-2.0.so.:(g_realloc+0x38)[0x2ac7c4cc] < 0x2b510f98---------------------------g_realoc()对应内存已经存在,释放0x2b510f98对应内存。
@ /usr/lib/libglib-2.0.so.:(g_realloc+0x38)[0x2ac7c4cc] > 0x2b513660 0x80----------------------然后重新申请0x80大小内存,返回地址为0x2b513660。
通过mtrace mtrace mtrace.log可以可读性更强的信息。第一个mtrace是解析mtrace.log的工具,第二个mtrace是测试程序。
Address Size Caller
0x00000000010b44c0 0x3e8 at /home/al/test/mtrace.c:
这里面对过滤掉正常malloc()/free()信息,留下的是泄漏内存。
Address表示泄漏地址,Size表示泄漏大小,Caller表示泄漏点代码位置。
如下是重复释放问题,mtrace()会如何处理呢?
#include
#include
#include
void main(void)
{
char *s = NULL;
mtrace();
s = malloc();
free(s);
free(s);
muntrace();
}
执行./dfree结果如下:
*** Error in `./dfree': double free or corruption (fasttop): 0x000000000188b450 ***
mtrace输出结果还有一些可能不是异常的提示。
= Start
[0x8048209] - 0x8064cc8
[0x8048209] - 0x8064ce0
[0x8048209] - 0x8064cf8
[0x80481eb] + 0x8064c48 0x14
[0x80481eb] + 0x8064c60 0x14
[0x80481eb] + 0x8064c78 0x14
[0x80481eb] + 0x8064c90 0x14
= End
输出结果如下:
- 0x08064cc8 Free was never alloc'd /home/drepper/tst.c:39
Address Size Caller
0x08064c48 0x14 at /home/drepper/tst.c:
0x08064c60 0x14 at /home/drepper/tst.c:
0x08064c78 0x14 at /home/drepper/tst.c:
0x08064c90 0x14 at /home/drepper/tst.c:
参考资料:《3.2.4.4 Interpreting the traces》。
- 0x000000000002ba00 Realloc was never alloc'd 0x2af654bc
(-表示释放) (free对应内存地址) Realloc (在log中行号) was never alloc'd (free释放点)
realloc()首先释放然后重新申请内存,表示在realloc()释放内存的时候没有找到对应的alloc()。
+ 0x000000000006ea48 Alloc duplicate: 0x2b2ef800 /lib/libstdc++.so.:(_Znwj+0x24)[0x2b2ef800]
(+表示alloc()) (alloc()内存地址) Alloc (在log中行号) duplicate: (alloc()调用点)
表示两次alloc两次,申请的内存地址是一样的。前一次alloc()到本次alloc()之间没有free。
mcheck()函数允许程序对已分配内存块进行一致性检查。
#include
int mcheck(void (*abortfunc)(enum mcheck_status mstatus));
调用该函数后,后续内存分配、释放都将进行内存连续性检查,并在内存连续性检查失败后,调用abortfunc。
枚举体mcheck_status如下:
enum mcheck_status
{
MCHECK_DISABLED = -, /* Consistency checking is not turned on. */
MCHECK_OK, /* Block is fine. */
MCHECK_FREE, /* Block freed twice. */
MCHECK_HEAD, /* Memory before the block was clobbered. */
MCHECK_TAIL /* Memory after the block was clobbered. */
};
下例创建捕获错误函数abortfun(),三种你错误类型:重复释放、头覆盖、尾覆盖。
#include
#include
#include
#include
#include
void abortfun(enum mcheck_status mstatus)
{
if(mstatus == MCHECK_FREE)
fprintf(stderr, "Block freed twice.\n");
else if(mstatus == MCHECK_HEAD)
fprintf(stderr, "Memory before the block was clobbered.\n");
else if(mstatus == MCHECK_TAIL)
fprintf(stderr, "Memory after the block was clobbered.\n");
else
fprintf(stderr, "Block is fine.\n");
}
void main(void)
{
char *s = NULL;
if(mcheck(abortfun) != )
{
fprintf(stderr, "mcheck:%s\\n", strerror(errno));
return;
}
s = malloc();
\*(s-) = ;--------------------------头覆盖。
\*(s+) = ;-------------------------尾覆盖。
free(s);
free(s);-----------------------------重复释放。
}
执行后结果如下,捕获到了尾覆盖和重复释放,头覆盖没有捕获到;但是单独头覆盖是可以捕获到的。
并且产生了coredump文件。
Memory after the block was clobbered.
Block freed twice.
*** Error in `./dfree': double free or corruption (fasttop): 0x0000000000957030 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.(+0x777e5)[0x7fc2eac267e5]
/lib/x86_64-linux-gnu/libc.so.(+0x8037a)[0x7fc2eac2f37a]
/lib/x86_64-linux-gnu/libc.so.(cfree+0x4c)[0x7fc2eac3353c]
/lib/x86_64-linux-gnu/libc.so.(+0x87fa0)[0x7fc2eac36fa0]
/lib/x86_64-linux-gnu/libc.so.(cfree+0xf7)[0x7fc2eac335e7]
./dfree[0x40084a]
/lib/x86_64-linux-gnu/libc.so.(__libc_start_main+0xf0)[0x7fc2eabcf830]
./dfree[0x400659]
======= Memory map: ========
函数格式介绍:
#include
enum mcheck_status mprobe(void *ptr);
mprobe()示例:
#include
#include
#include
#include
#include
void abortfun(enum mcheck_status mstatus)
{
if(mstatus == MCHECK_FREE)
fprintf(stderr, "Block freed twice.\n");
else if(mstatus == MCHECK_HEAD)
fprintf(stderr, "Memory before the block was clobbered.\n");
else if(mstatus == MCHECK_TAIL)
fprintf(stderr, "Memory after the block was clobbered.\n");
else
fprintf(stderr, "Block is fine.\n");
}
void main(void)
{
char *s = NULL;
if(mcheck(abortfun) != )
{
fprintf(stderr, "mcheck:%s\\n", strerror(errno));
return;
}
s = malloc();
mprobe(s);------------------------------正确
mprobe(s-);----------------------------错误,返回MCHECK\_HEAD错误类型。
mprobe(s+);---------------------------错误,返回MCHECK\_HEAD错误类型。
free(s);
}
返回结果:
Memory before the block was clobbered.
Memory before the block was clobbered.
在编译的时候加上-lmcheck,不需要修改代码就可以对malloc()/free()进行检查。
#include
#include
void main(void)
{
char *s = NULL;
s = malloc();
free(s);
free(s);
}
gcc dfree.c -o dfree -lmcheck编译后,执行./dfree。
block freed twice
Aborted (core dumped)
查看coredump bt full如下:
# 0x00007fdf1e826428 in __GI_raise (sig=sig@entry=) at ../sysdeps/unix/sysv/linux/raise.c:
resultvar =
pid =
selftid =
save\_stage =
act = {\_\_sigaction\_handler = {sa\_handler = 0x3065383363666637, sa\_sigaction = 0x3065383363666637}, sa\_mask = {\_\_val = {, , , ,
, , , , , , , , , ,
, }}, sa\_flags = , sa\_restorer = 0x54}
sigs = {\_\_val = {, <repeats times>}}
ap = <error reading variable ap (Attempt to dereference a generic pointer.)>
fd =
on\_2 = <optimized out>
list = <optimized out>
nlist = <optimized out>
cp = <optimized out>
written = <optimized out>
buf = "000000000186a010"
cp = <optimized out>
ar\_ptr = <optimized out>
str = 0x7fdf1e981fa0 "double free or corruption (fasttop)"
action =
size = <optimized out>
fb = <optimized out>
nextchunk = <optimized out>
nextsize = <optimized out>
nextinuse = <optimized out>
prevsize = <optimized out>
bck = <optimized out>
fwd = <optimized out>
errstr = <optimized out>
locked = <optimized out>
ar\_ptr = <optimized out>
p = <optimized out>
hook = <optimized out>
No symbol table info available.
No symbol table info available.
No symbol table info available.
No symbol table info available.
at ../csu/libc-start.c:
result = <optimized out>
unwind\_buf = {cancel\_jmp\_buf = {{jmp\_buf = {, , , , , , -, -}, mask\_was\_saved = }}, priv = {pad = {0x0, 0x0, 0x400610 <main+>,
0x7fdf1ebcbab0 <\_dl\_fini>}, data = {prev = 0x0, cleanup = 0x0, canceltype = }}}
not\_first\_call = <optimized out>
No symbol table info available.
No symbol table info available.
No symbol table info available.
No symbol table info available.
No symbol table info available.
No symbol table info available.
对比以上几个内存你检查手段:MALLOC_CHECK_最简单,其次是-lmcheck,最后是mtrace()/muntrace()、mcheck()、mprobe()。
但是这几种技术检查的全面性都不够,没有一种能够全面检查内存泄漏、内存踩踏、重复释放的。
要想全面的检查还是需要Valgrind这种技术,参考《valgrind使用方法》。
参考文档:《mtrace-内存使用追踪(内存)》、《mcheck 函数使用(glibc-3-内存)》
手机扫一扫
移动阅读更方便
你可能感兴趣的文章