“踩内存”引发的,内存问题分析总结
背景
日常开发中,后端服务为了追求性能,常常会采用C/C++进行开发,享受C/C++带来性能提升的同时,开发者需要自己实现内存管理,只要程序实现上有缺陷,就会导致服务不可用,最近就遇到一次“踩内存”导致的服务不可用。
问题现象
同事A的新版本发布上线后,就收到告警消息,模块产生了大量的coredump文件,导致请求出现失败,运维同学经过简单排查,明确不是机器异常、网络故障引起的coredump。
同事A初步分析发现,每次程序出现coredump的位置不太一样,产生coredump的位置都不是新开发的代码,基本都在使用malloc进行内存分配时出现了异常,问题很像是内存出现异常。
分析过程
从linux的系统日志中,看到程序的coredump时,产生了以下报错。
free(): invalid pointer: 0x000000000900ba22 *** general protection ip:7fdae4c988a0 sp:7ffc673242a0 error:0 in malloc(): smallbin double linked lilst corrupted: 0x0000000009ce16c0 ***
上述日志,可以直接理解为程序在释放内存时指针异常,程序访问了内存中保护区域,malloc 内部维护的内存管理链表出现异常,基本可以明确程序出现了“踩内存”导致内存管理出现异常。回退新版本后,不再产生coredump,进一步确定是新版本引入的问题。
分析方法
常见分析思路
根据过往经验,”踩内存”类问题,因缺乏明确的异常位置信息,一般都需要花费很长时间进行定位,主要的定位方法如下。
尝试复现,增加辅助日志,这时候就需要对产生coredump时的流量重放,这里强烈推荐使用k6作为流量重放工具。如果能够复现,这时候就可以使用日志分析法、排除法进行分析。
阅读代码,分析异常,有的case测试环境无法复现,这个时候就需要阅读新增代码分析潜在问题,需要结合过往经验,最好是能够多个人一起看。一般来说,将要上线的代码常见场景基本都测试通过了,要重点关注一些异常场景,分析新增代码可能带来的潜在风险。
借助工具,分析风险,现在有很多用于分析内存问题的辅助工具,例如,Valgrind 或 AddressSanitizer,推荐使用AddressSanitizer,这个工具已经集成在GCC编译器,不过要使用4.8.5以上版本的GCC,可以直接在日志中看到潜在风险的代码行数。
ASAN使用方法
同事A的这个版本,修改点并不是特别多,都是一些常规的代码修改。几个同事一起看,也没有发现有啥明显的问题。只能借助工具进行分析,考虑到使用成本,我们在代码中集成了AddressSanitizer ,只需要在编辑脚本中增加参数即可,使用方法如下。
gcc -fsanitize=address -g your_program.c -o your_program export $ASAN_OPTIONS= log_path=./asan_log_%p:halt_on_error=0:detect_leaks=1:verbosity=1
使用上述参数会把检测到异常的堆栈写入到asan_log中,当检测到非致命错误时会继续执行。在使用中还需要安装AddressSanitizer库, 具体安装方法可以参考google 相关教程。
原因分析
同事A的这个模块,历史比较悠久,代码中很多坑。使用AddressSanitizer 工具以后,检测出以下几类问题。
- new 和delete 使用不匹配,使用new 分配数组,却使用delete 释放对象,没有使用delete [],直接会报下列错误。
- 数组越界,数组下标超过限制,向数组中写入数据,刚写入程序不会出现问题,可导致程序内存出现异常,例如malloc、free异常,具有一定的随机性。
- 访问野指针,对象已经被释放了,还是会继续读取对象中的数据,根据经验在单线程一般不会有问题,也依赖编译器的实现。
根据扫描出来的问题,结合同学A的代码修改,基本明确就是因为数组越界引起的踩内存问题,数组大小为27,传入的index 却是-124,导致数组越界写入。引起内存异常,根据问题构造场景,100%复现问题。
总结
针对这个内存问题,我一直都在思考如何减少此类问题产生,我想到了以下方法。
- 对于历史模块的修改,要评估修改的每一行代码会产生什么影响,例如参考之前的修改,调用了一个相同的函数,可要关注函数传入的参数,是否会不符合预期。
- 尽力不要复制代码,很多开发喜欢复制一段旧代码,再针对性修改,缺乏思考的过程,很容易产生BUG。
- 关注编译告警,参数非法、类型不匹配,隐式转换,这些问题在编译过程中,都会产生告警,及时处理,规避异常BUG。
- 定期review,代码review不依赖其他同学,在每个功能开发完毕后,要review本地修改会带来哪些影响,会不会产生问题,不要过度自信。
上述此次内存异常问题的分析过程,以及AddressSanitizer 的使用方法,希望能够对各位同仁分析内存异常类问题有帮助,欢迎大家积极讨论,减少程序BUG 产生。
4 thoughts on ““踩内存”引发的,内存问题分析总结”
只有像博主样高手才能注意到
抬举我了,都是互联网搬砖工人。
博主好久没更新了
哈哈,近期开始更新了。。