ASIO实现ICMP ping断网时触发double free崩溃排查
问题根因
崩溃的直接诱因是Ubuntu 16.04 apt源预装的Boost 1.58版本和编译环境存在C++ ABI不兼容,结合旧版Boost的适配缺陷导致堆内存破坏:
- 系统预装的Boost 1.58编译时默认使用GCC预C++11 ABI(
_GLIBCXX_USE_CXX11_ABI=0),如果编译业务代码时使用GCC 5及以上版本的默认配置(_GLIBCXX_USE_CXX11_ABI=1),调用boost::system::error_code::message()时会出现跨ABI的std::string内存操作:Boost库内部用旧ABI的内存布局分配字符串内存,回到业务代码侧按新ABI的std::string结构析构时,会错误释放内存、破坏堆空闲链表结构。 - 堆内存破坏存在延后触发特性:
error.message()执行时已经完成了非法内存操作,但C运行库的堆校验逻辑不会立刻触发,直到start_send()函数退出、局部变量boost::asio::streambuf request_buffer析构执行内存释放时,才检测到堆结构损坏,抛出double free or corruption错误并触发SIGABRT,因此GDB回溯会误将崩溃点定位到streambuf的析构流程。 - Valgrind环境下不崩溃是因为Valgrind替换了默认内存分配器,自带的堆实现不会做即时的堆块合法性校验,因此不会触发abort,但它检测到的
error.message()相关内存泄漏已经指向了跨模块非法内存操作的问题。注释掉error.message()打印后崩溃消失,也直接验证了这个根因。 - 额外隐患:Boost 1.58发布时未完成对
std::bind的完整适配,在异步回调绑定场景下存在参数转发、智能指针引用计数异常的bug,长期运行也可能触发内存问题。
修复方案
按优先级依次处理即可:
- 解决ABI兼容性问题
编译代码时添加编译选项-D_GLIBCXX_USE_CXX11_ABI=0,强制业务代码使用和系统Boost一致的旧ABI,彻底解决std::string跨二进制边界分配释放的问题。如果需要使用C++11 ABI,不要依赖apt源的Boost包,自行编译对应版本的Boost,编译时添加-D_GLIBCXX_USE_CXX11_ABI=1保持和业务代码的ABI一致。 - 规避旧版Boost的std::bind适配缺陷
要么将所有异步回调绑定逻辑回退为使用boost::bind,要么将Boost版本升级到1.66及以上——从该版本开始Asio模块才完整适配C++11标准库的std::bind、std::function组件。 - 优化错误处理逻辑
即使ABI一致,也不要在IO操作的错误分支直接链式调用error.message()输出日志,可以先将错误码值、错误分类值拷贝为局部整型变量,再做字符串转换,避免Boost内部临时字符串对象的生命周期异常。 - 修正重试逻辑的潜在栈溢出问题
确认send_to失败后的重试逻辑是在定时器的异步回调中触发start_send(),不要在同步send_to返回错误的当前调用栈内直接递归调用start_send(),否则网卡长时间断开时会出现无限递归导致栈溢出。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

