现象
使用malloc使用,出现memory corruption,程序异常退出。
原因
发生了内存异常使用,malloc在申请内存的时候,发现内存冲突,接收到SIGABRT信号退出。
样例
memory corruption 不一定是当前的 malloc出现问题,很有可能是前一次的内存处理有问题,比如memset越界、memcpy越界之类的。
如下的代码样例,在某个项目中出现了下面的情况。原有的实现功能是,header_buf的长度<header的长度,从而只复制header_buf的有效长度。
然而在某些设备上,由于长度信息与设备相关,导致复制的长度超过了申请的长度。导致后续的malloc或者free出现异常。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(void)
{
char *header, *header_buf, *next_buf;
int min_io_size, header_len;
min_io_size = 64; /* 实际是从设备读取的,此处打桩演示 */
header_len = 108; /* 实际是通过计算出来的,此处打桩演示 */
header = (char *) malloc(min_io_size);
if (!header) {
printf("%d\n", errno);
return -1;
}
header_buf = (char *) malloc(header_len);
if (!header_buf) {
printf("%d\n", errno);
return -1;
}
memset(header, 0xf, min_io_size);
memset(header_buf, 0xe, header_len);
memcpy(header, header_buf, header_len); /* 此处memcpy108B到64B的空间,会引起后续异常 */
next_buf = (char *) malloc(64);
free(header_buf);
free(header);
free(next_buf);
return 0;
}
gdb调试信息
memcpy前的数据长度,一个是64B,一个108,正常。
(gdb) n
118 memcpy(header, header_buf, header_len); /* 此处memcpy108B到64B的空间,会引起后续异常 */
(gdb) p header
$1 = 0x603010 '\017' <repeats 64 times>
(gdb) p header_buf
$2 = 0x603060 '\016' <repeats 108 times>
memcpy后的数据长度,64B的数据异常变为188B。
(gdb)
$4 = 0x603010 '\016' <repeats 188 times>
(gdb) p header_buf
$5 = 0x603060 '\016' <repeats 108 times>
(gdb)
经过越界的memcpy,出现如下error log。
Error in `/home/work/t/mem-list/test_list': munmap_chunk(): invalid pointer: 0x0000000000603060 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7ffff78677e5]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x1a8)[0x7ffff7874698]
/home/work/t/mem-list/test_list[0x400c13]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7ffff7810830]
/home/work/t/mem-list/test_list[0x400769]
======= Memory map: ========
00400000-00402000 r-xp 00000000 08:01 1068805 /home/work/t/mem-list/test_list
00601000-00602000 r--p 00001000 08:01 1068805 /home/work/t/mem-list/test_list
00602000-00603000 rw-p 00002000 08:01 1068805 /home/work/t/mem-list/test_list
00603000-00624000 rw-p 00000000 00:00 0 [heap]
7ffff75da000-7ffff75f0000 r-xp 00000000 08:01 3674661 /lib/x86_64-linux-gnu/libgcc_s.so.1
7ffff75f0000-7ffff77ef000 ---p 00016000 08:01 3674661 /lib/x86_64-linux-gnu/libgcc_s.so.1
7ffff77ef000-7ffff77f0000 rw-p 00015000 08:01 3674661 /lib/x86_64-linux-gnu/libgcc_s.so.1
7ffff77f0000-7ffff79b0000 r-xp 00000000 08:01 3676047 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff79b0000-7ffff7bb0000 ---p 001c0000 08:01 3676047 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7bb0000-7ffff7bb4000 r--p 001c0000 08:01 3676047 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7bb4000-7ffff7bb6000 rw-p 001c4000 08:01 3676047 /lib/x86_64-linux-gnu/libc-2.23.so
7ffff7bb6000-7ffff7bba000 rw-p 00000000 00:00 0
7ffff7bba000-7ffff7bd2000 r-xp 00000000 08:01 3676034 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7bd2000-7ffff7dd1000 ---p 00018000 08:01 3676034 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd1000-7ffff7dd2000 r--p 00017000 08:01 3676034 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd2000-7ffff7dd3000 rw-p 00018000 08:01 3676034 /lib/x86_64-linux-gnu/libpthread-2.23.so
7ffff7dd3000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r-xp 00000000 08:01 3676033 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7fdb000-7ffff7fdf000 rw-p 00000000 00:00 0
7ffff7ff6000-7ffff7ff7000 rw-p 00000000 00:00 0
7ffff7ff7000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00025000 08:01 3676033 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffd000-7ffff7ffe000 rw-p 00026000 08:01 3676033 /lib/x86_64-linux-gnu/ld-2.23.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Program received signal SIGABRT, Aborted.
0x00007ffff7825428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
经验总结
1、出现 memory corruption,不要局限于当前的代码,需要往上回溯代码。
2、可以使用一些工具检查代码。比如coverity 会提示 “不可信任的值作为了参数,将未经检查的不可信任来源的值用作了函数的参数”
valgrind也会检测出内存异常位置。
3、如果此问题是必现的话,可以将此部分代码单独剥离出来。或者放在进程头部,减少其他模块影响,再采用二分法的方式逐步缩小出问题的代码范围。