内存写越界导致破环堆结构引起的崩溃问题定位经验[如报错malloc(): memory corruption或free(): invalid next size]

线上C模块内存崩溃排查
本文分享了一次线上C模块频繁出现内存崩溃的问题排查过程。通过对核心错误信息的分析及利用gdb等工具,最终定位到越界写入引发的内存问题,并总结了宝贵的经验教训。



转载自:点击打开链接http://blog.youkuaiyun.com/slvher/article/details/9144161



               前段时间开发的一个后端C模块上线后,线上出core,初始时,由于访问压力不大,所以崩溃是上线3天左右出现的。当时用gdb跟进调用堆栈并检查源码,发现出core位置的代码没有啥问题。由于当时开发任务较重,且该模块不保存状态(崩溃重启不影响对外服务),所以没有深入跟进。后来随着客户端版本逐渐放量导致访问压力上升,噩梦开始了。。。
        该模块会不定时core掉,而且几乎每次崩溃时的调用堆栈都不一样,关键是最后几层堆栈很多都位于几乎不可能出问题的代码中,比如库函数或厂里的公共库。
        好在在众多core文件中发现规律:每次基本都是在对内存动态操作时挂掉,比如malloc/realloc/free/new/delete都引起了崩溃。而且幸运的是,崩溃进程还是输出了一些关键信息,比如下面这些(这些是在不同的崩溃时刻分别输出的):
*** glibc detected *** malloc(): memory corruption: 0x0000002a95c1ff10 ***
*** glibc detected *** double free or corruption (out): 0x0000000000f0d910 ***
*** glibc detected *** free(): invalid next size (normal): 0x0000002a96103b00 ***
*** glibc detected *** free(): invalid next size (fast): 0x0000000000f349d0 ***
*** glibc detected *** corrupted double-linked list: 0x0000002a95f062e0 ***
        从上面的日志也可以看到,每次引起崩溃的直接原因都可能不同。用gdb又仔细查看core文件发现,有时进程是收到SIGABRT信号后退出,有时又是收到SIGSEGV信号后退出。
        由此,基本定位了崩溃原因:内存访问越界导致破坏了heap的数据结构。用valgrind在线下环境启动进程,试图重现崩溃或定位越界访问的代码,遗憾的是,脚本压了1个小时也没出现崩溃,而valgrind的输出报告也没有越界代码位置的提示。
        最终,仔细检查源码后发现,在某个回调函数中,new出来的buffer接收完通过http post方式发送过来的2进制数据后,我又多写了1行代码,类似于:recv_buf[data_len] = '\0',导致越界多写1个字节,最终引起各种莫名其妙的内存崩溃。

经验教训:
        1)调用堆栈信息对定位问题帮助很大,但也不可尽信。比如这次遇到的情况,每次出core的调用堆栈几乎都不一样,而且最后几层栈帧都是不可能出现问题的库函数或久经考验的公司公共库,这种情况下,思维需要跳出局部,在更高的层次寻找规律或原因
        2)一旦定位崩溃属于堆内存读写越界问题,就仔细检查自己的代码吧,因为库函数或公共库出问题的概率太小了,所以不要存在侥幸心理,这个时候,盲目的自信要不得
        3)本来自认为对内存操作已经很小心了,没想到还是在想当然的瞬间写下犯错的代码,导致最终花费很多时间和精力去“捉虫”。不过好在跟进崩溃的过程中增加了一点分析/定位问题的经验,也算有些收获吧

=============== EOF =================

 
`malloc(): memory corruption` 报错通常意味着在使用动态内存分配函数 `malloc` 及其相关操作时,程序对堆内存的管理出现了问题导致内存数据结构坏。以下是一些可能的原因及分析: #### 1. 缓冲区溢出 在使用 `malloc` 分配内存后,如果向分配的内存区域入的数据超过了分配的大小,就会覆盖相邻的堆内存区域,坏堆的内部数据结构。例如: ```c #include <stdio.h> #include <stdlib.h> int main() { char *ptr = (char *)malloc(10); for (int i = 0; i < 20; i++) { ptr[i] = 'A'; } free(ptr); return 0; } ``` 在这个例子中,`malloc` 只分配了 10 个字节的内存,但程序试图入 20 个字符,这会导致缓冲区溢出,可能触发 `malloc()` 内存损坏错误。 #### 2. 重复释放内存 对同一块已经释放的内存再次调用 `free` 函数,会坏堆的管理结构。例如: ```c #include <stdio.h> #include <stdlib.h> int main() { char *ptr = (char *)malloc(10); free(ptr); free(ptr); // 重复释放内存 return 0; } ``` #### 3. 释放非 `malloc` 分配的内存 如果尝试释放不是通过 `malloc`、`calloc` `realloc` 分配的内存,也会导致堆损坏。例如: ```c #include <stdio.h> #include <stdlib.h> int main() { char arr[10]; free(arr); // 释放非动态分配的内存 return 0; } ``` #### 4. 内存操作函数使用不当 在使用 `memcpy`、`strcpy` 等内存操作函数时,如果参数设置不正确,可能会导致内存越界访问。例如,`memcpy` 的 `size` 参数比分配的空间大: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *ptr = (char *)malloc(10); char source[20] = "This is a long string"; memcpy(ptr, source, 20); // size 参数过大 free(ptr); return 0; } ``` ### 解决建议 - **检查内存分配和释放**:确保每次 `malloc` 都有对应的 `free`,且不会重复释放内存。 - **检查缓冲区边界**:在向分配的内存入数据时,确保不超过分配的大小。 - **使用调试工具**:可以使用 `valgrind` 等工具来检测内存泄漏和内存损坏问题。例如: ```bash valgrind --leak-check=full ./your_program ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值