linux下定位多线程内存越界问题实践总结

最近定位了在一个多线程服务器程序(OceanBase MergeServer)中 , 一个线程非法篡改另一个线程的内存而导致程序core掉的问题 。 定位这个问题历经曲折 , 尝试了各种内存调试的办法 。 往往感觉就要柳暗花明了 , 却发现又进入了另一个死胡同 。 最后 , 使用强大的mprotect+backtrace+libsigsegv等工具成功定位了问题 。 整个定位过程遇到的问题和解决办法对于多线程内存越界问题都很典型 , 简单总结一下和大家分享 。
现象 core是在系统集成测试过程中发现的 。 服务器程序MergeServer有一个50个工作线程组成的线程池 , 当使用8个线程的测试程序通过MergeServer读取数据时 , 后者偶尔会core掉 。 用gdb查看core文件 , 发现core的原因是一个指针的地址非法 , 当进程访问指针指向的地址时引起了段错误(segment fault) 。 见下图 。
linux下定位多线程内存越界问题实践总结文章插图
发生越界的指针ptr_位于一个叫做cname_的对象中 , 而这个对象是一个动态数组field_columns_的第10个元素的成员 。 如下图 。
linux下定位多线程内存越界问题实践总结文章插图
复现问题 之后 , 花了2天的时间 , 终于找到了重现问题的方法 。 重现多次 , 可以观察到如下一些现象:
1. 随着客户端并发数的加大(从8个线程到16个线程) , 出core的概率加大;
2. 减少服务器端线程池中的线程数(从50个到2个) , 就不能复现core了 。
3. 被篡改的那个指针 , 总是有一半(高4字节)被改为了0 , 而另一半看起来似乎是正确的 。
4. 请看前一节 , 重现多次 , 每次出core , 都是因为field_columns_这个动态数组的第10个元素data_[9]的cname_成员的ptr_成员被篡改 。 这是一个不好解释的奇怪现象 。
5. 在代码中插入检查点 , 从field_columns_中内容最初产生到读取导致越界的这段代码序列中“埋点” , 既使用二分查找法定位篡改cname_的代码位置 。 结果发现 , 程序有时core到检查点前 , 有时又core到检查点后 。
综合以上现象 , 初步判断这是一个多线程程序中内存越界的问题 。
使用glibc的MALLOC_CHECK_ 因为是一个内存问题 , 考虑使用一些内存调试工具来定位问题 。 因为OB内部对于内存块有自己的缓存 , 需要去除它的影响 。 修改OB内存分配器 , 让它每次都直接调用c库的malloc和free等 , 不做缓存 。 然后 , 可以使用glibc内置的内存块完整性检查功能 。
使用这一特性 , 程序无需重新编译 , 只需要在运行的时候设置环境变量MALLOC_CHECK_(注意结尾的下划线) 。 每当在程序运行过程free内存给glibc时 , glibc会检查其隐藏的元数据的完整性 , 如果发现错误就会立即abort 。 用类似下面的命令行启动server程序:
export MALLOC_CHECK_=2bin/mergeserver -z 45447 -r 10.232.36.183:45401 -p45441使用MALLOC_CHECK_以后 , 程序core到了不同的位置 , 是在调用free时 , glibc检查内存块前面的校验头错误而abort掉了 。 如下图 。
linux下定位多线程内存越界问题实践总结文章插图
但这个core能带给我们想信息也很少 。 我们只是找到了另外一种稍高效地重现问题的方法而已 。 或许最初看到的core的现象是延后显现而已 , 其实“更早”的时刻内存就被破坏掉了 。
valgrind glibc提供的MALLOC_CHECK_功能太简单了 , 有没有更高级点的工具不光能够报告错误 , 还能分析出问题原因来?我们自然想到了大名鼎鼎的valgrind 。 用valgrind来检查内存问题 , 程序也不需要重新编译 , 只需要使用valgrind来启动:
nohup valgrind --error-limit=no --suppressions=suppress bin/mergeserver -z 45447 -r 10.232.36.183:45401 -p45441 >nohup.out--tt-darkmode-color: #999999;">默认情况下 , 当valgrind发现了1000中不同的错误 , 或者总数超过1000万次错误后 , 会停止报告错误 。 加了--error-limit=no以后可以禁止这一特性 。 --suppressions用来屏蔽掉一些不关心的误报的问题 。 经过一翻折腾 , 用valgrind复现不了core的问题 。 valgrind报出的错误也都是一些与问题无关的误报 。 大概是因为valgrind运行程序大约会使程序性能慢10倍以上 , 这会影响多线程程序运行时的时序 , 导致core不能复现 。 此路不通 。
需要C/C++ Linux高级服务器架构师学习资料后台私信“资料”(包括C/C++ , Linux , golang技术 , Nginx , ZeroMQ , MySQL , Redis , fastdfs , MongoDB , ZK , 流媒体 , CDN , P2P , K8S , Docker , TCP/IP , 协程 , DPDK , ffmpeg等)
linux下定位多线程内存越界问题实践总结文章插图