You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程环境下自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 19:18:17