多线程环境下自定义std::streambuf触发SIGSEGV故障求助
多线程环境下自定义std::streambuf导致SIGSEGV的修复方案
我实现了一个继承自std::streambuf的输出缓冲区LogStreamBuf,将其替换cout的默认缓冲区后,在多线程环境中大量输出时触发SIGSEGV错误。gdb回溯显示memcpy的__n参数为无效值4294967295,判断是线程安全问题,但找不到修复方法,相关调试信息与代码如下:
GDB回溯信息
(gdb) bt #0 0xb698ff64 in memcpy () at ../sysdeps/arm/armv7/multiarch/memcpy_impl.S:487 #1 0xb6b5a4dc in std::char_traits<char>::copy (__n=4294967295, __s2=0xb3ef984a "60\340", <incomplete sequence \333>, __s1=<optimized out>) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/char_traits.h:350 #2 std::basic_streambuf<char, std::char_traits<char>>::xsputn (this=0xa4db5c <gLogStreamBuf>, __s=0xb3ef984a "60\340", <incomplete sequence \333>, __n=2) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/streambuf.tcc:90 #3 0xb6b482c8 in std::basic_streambuf<char, std::char_traits<char>>::sputn (__n=<optimized out>, __s=<optimized out>, this=0xa4db5c <gLogStreamBuf>) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/streambuf:451 #4 std::ostreambuf_iterator<char, std::char_traits<char>>::_M_put (__len=<optimized out>, __ws=<optimized out>, this=<synthetic pointer>) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/streambuf_iterator.h:282 #5 std::__write<char> (__len=<optimized out>, __ws=<optimized out>, __s=...) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/locale_facets.h:121 #6 std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::_M_insert_int<unsigned long> (this=<optimized out>, __s=..., __io=..., __fill=<optimized out>, __fill@entry=32 ' ', __v=<optimized out>, __v@entry=60) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/locale_facets.tcc:933 #7 0xb6b483aa in std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::do_put (this=<optimized out>, __s=..., __io=..., __fill=32 ' ', __v=60) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/locale_facets.h:2515 #8 0xb6b4f8f2 in std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char>>>::put (__v=60, __fill=<optimized out>, __io=..., __s=..., this=0xb6b94178 <(anonymous namespace)::num_put_c>) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/locale_facets.h:2376 #9 std::ostream::_M_insert<unsigned long> (this=0xa005f0 <std::cout@@GLIBCXX_3.4>, __v=60) at /home/tcwg-buildslave/workspace/tcwg-make-release_1/_build/builds/x86_64-unknown-linux-gnu/arm-linux-gnueabihf/gcc.git~linaro-7.5-2019.12-stage2/arm-linux-gnueabihf/libstdc++-v3/include/bits/ostream.tcc:73
LogStreamBuf实现代码
LogStreamBuf gLogStreamBuf; #define kInBufSize 48*1024 LogStreamBuf::LogStreamBuf() { fInBuf = new char[kInBufSize]; setbuf( fInBuf, kInBufSize ); setp( fInBuf, (fInBuf + kInBufSize) ); setg( fInBuf, fInBuf, (fInBuf + kInBufSize) ); } void LogStreamBuf::RedirectStreams() { cout << "Redirecting output to LogStreamBuf!"<<endl; cout.rdbuf(this); } int LogStreamBuf::overflow( int ch ) { Lock(); for(char *p=gptr(); p < epptr(); p++) { // writes characters to the associated output sequence from the put area } setp( fInBuf, (fInBuf + InBufLen()) ); setg( fInBuf, fInBuf, (fInBuf + InBufLen()) ); Unlock(); } int LogStreamBuf::sync() { Lock(); for(char *p=gptr(); p < pptr(); p++) { // sync characters pointer p to the associated output sequence from the put area count++; } gbump(count); Unlock(); } int LogStreamBuf::underflow() { setg( fInBuf, fInBuf, (fInBuf + InBufLen()) ); return sgetc(); }
相关输出代码
ostream& MacAddress::Print( ostream& ostrm ) const { int oldFill = ostrm.fill(); uint8 macAddress[6]; Get(macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]); ostrm.fill( '0' ); ostrm << hex << setw(2) << right << (unsigned int) macAddress[0] << ':' << setw(2) << right << (unsigned int) macAddress[1] << ':' << setw(2) << right << (unsigned int) macAddress[2] << ':' << setw(2) << right << (unsigned int) macAddress[3] << ':' <=====bt shows the problem starts from here << setw(2) << right << (unsigned int) macAddress[4] << ':' << setw(2) << right << (unsigned int) macAddress[5] << dec; ostrm.fill(oldFill); return ostrm; } inline std::ostream& operator<<( std::ostream& ostrm, const MacAddress& ins ) { return ins.Print( ostrm ); } cout << "Current Sucriber's MACaddr " << pSucriber->macAddress() << endl; <<=== the cout sentence
修复方案
1. 补全线程安全保护
当前仅在overflow和sync加锁,但std::streambuf的xsputn、sputc等核心写入方法未被保护,多线程同时操作会导致缓冲区指针(pptr、epptr等)被并发修改,出现指针越界或计算溢出(比如__n=4294967295就是unsigned int的-1,属于指针计算错误)。需要:
- 重写
xsputn,加锁后实现安全写入:std::streamsize LogStreamBuf::xsputn(const char* s, std::streamsize n) { Lock(); std::streamsize written = 0; while (n > 0) { // 计算当前缓冲区剩余空间 std::streamsize available = epptr() - pptr(); if (available == 0) { // 缓冲区满,调用overflow腾出空间 if (overflow(traits_type::eof()) == traits_type::eof()) { break; } available = epptr() - pptr(); } std::streamsize to_write = std::min(n, available); std::copy(s, s + to_write, pptr()); pbump(to_write); s += to_write; n -= to_write; written += to_write; } Unlock(); return written; } - 重写
sputc,加锁后确保单线程修改指针:int LogStreamBuf::sputc(char c) { Lock(); if (pptr() == epptr()) { if (overflow(c) == traits_type::eof()) { Unlock(); return traits_type::eof(); } } else { *pptr() = c; pbump(1); } Unlock(); return c; }
2. 修正缓冲区指针逻辑错误
输出缓冲区应使用pbase/pptr/epptr(输出相关指针),而非gptr(输入指针),原代码中overflow和sync误用gptr导致遍历范围错误,结合多线程竞争加剧问题:
- 修正
overflow:int LogStreamBuf::overflow( int ch ) { Lock(); // 获取已写入的数据长度 size_t len = pptr() - pbase(); if (len > 0) { // 将pbase()到pptr()的数据写入目标输出序列 // ... 替换为你的实际写入逻辑 ... } // 重置输出缓冲区 setp(fInBuf, fInBuf + kInBufSize); // 处理额外传入的字符 if (ch != traits_type::eof()) { *pptr() = static_cast<char>(ch); pbump(1); } Unlock(); return ch; } - 修正
sync:int LogStreamBuf::sync() { Lock(); size_t len = pptr() - pbase(); if (len > 0) { // 同步pbase()到pptr()的数据到目标输出序列 // ... 替换为你的实际写入逻辑 ... // 重置输出缓冲区 setp(fInBuf, fInBuf + kInBufSize); } Unlock(); return 0; // 成功返回0,失败返回-1 }
3. 移除不必要的输入缓冲区实现
underflow是输入缓冲区的方法,当前实现的是输出缓冲区,无需重写该方法,直接删除underflow的实现,避免干扰输出缓冲区状态。
4. 修复全局实例初始化顺序问题
全局变量gLogStreamBuf可能早于std::cout初始化,导致RedirectStreams中操作未初始化的cout出现未定义行为。建议将gLogStreamBuf改为局部静态变量,或在main函数中显式初始化后再调用RedirectStreams:
// 替换全局变量为局部静态变量 LogStreamBuf& GetLogStreamBuf() { static LogStreamBuf instance; return instance; } // 在main中初始化 int main() { GetLogStreamBuf().RedirectStreams(); // ... 其他逻辑 ... }
内容的提问来源于stack exchange,提问作者jackxie
相关产品推荐
相关产品推荐

