linux - std::string::assign() method from libstdc++.so.6中奇怪的SIGSEGV段错误

标签 linux string segmentation-fault assign

我的程序最近在运行时遇到奇怪的段错误。 我想知道以前是否有人遇到过此错误,以及如何解决。 这是更多信息:
基本信息:

  • CentOS 5.2,内核版本为2.6.18
  • g++(GCC)4.1.2 20080704(Red Hat 4.1.2-50)
  • CPU:英特尔x86系列
  • libstdc++。so.6.0.8
  • 我的程序将启动多个线程来处理数据。段错误发生在其中一个线程中。
  • 尽管它是一个多线程程序,但段错误似乎发生在本地std::string对象上。我将在稍后的代码片段中显示。
  • 该程序使用-g,-Wall和-fPIC编译,没有-O2或其他优化选项。

  • 核心转储信息:
    Core was generated by `./myprog'.
    Program terminated with signal 11, Segmentation fault.
    #0  0x06f6d919 in __gnu_cxx::__exchange_and_add(int volatile*, int) () from /usr/lib/libstdc++.so.6
    (gdb) bt
    #0  0x06f6d919 in __gnu_cxx::__exchange_and_add(int volatile*, int) () from /usr/lib/libstdc++.so.6
    #1  0x06f507c3 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::assign(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /usr/lib/libstdc++.so.6
    #2  0x06f50834 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator=(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) () from /usr/lib/libstdc++.so.6
    #3  0x081402fc in Q_gdw::ProcessData (this=0xb2f79f60) at ../../../myprog/src/Q_gdw/Q_gdw.cpp:798
    #4  0x08117d3a in DataParser::Parse (this=0x8222720) at ../../../myprog/src/DataParser.cpp:367
    #5  0x08119160 in DataParser::run (this=0x8222720) at ../../../myprog/src/DataParser.cpp:338
    #6  0x080852ed in Utility::__dispatch (arg=0x8222720) at ../../../common/thread/Thread.cpp:603
    #7  0x0052c832 in start_thread () from /lib/libpthread.so.0
    #8  0x00ca845e in clone () from /lib/libc.so.6
    
    请注意,段故障始于 basic_string::operator =()内。
    相关代码:
    (我显示的代码超出了所需的数量,请暂时忽略编码风格。)
    int Q_gdw::ProcessData()
    {
        char tmpTime[10+1] = {0};
        char A01Time[12+1] = {0};
        std::string tmpTimeStamp;
    
        // Get the timestamp from TP
        if((m_BackFrameBuff[11] & 0x80) >> 7)
        {
            for (i = 0; i < 12; i++)
            {
                A01Time[i] = (char)A15Result[i];
            }
            tmpTimeStamp = FormatTimeStamp(A01Time, 12);  // Segfault occurs on this line
    
    这是此FormatTimeStamp方法的原型(prototype):
    std::string FormatTimeStamp(const char *time, int len)
    
    我认为这样的字符串赋值操作应该是一种常用的操作,但是我只是不明白为什么这里会发生段错误。
    我调查的是:
    我在网上搜索了答案。我看着here。答复说,尝试使用定义的_GLIBCXX_FULLY_DYNAMIC_STRING宏重新编译程序。我试过了,但崩溃仍然发生。
    我还看着here。它还说用_GLIBCXX_FULLY_DYNAMIC_STRING重新编译该程序,但是作者似乎正在处理我的另一个问题,因此我认为他的解决方案不适合我。

    于2011年8月15日更新
    这是此FormatTimeStamp的原始代码。我知道编码看起来不太好(例如,魔法数字太多。),但让我们先关注崩溃问题。
    string Q_gdw::FormatTimeStamp(const char *time, int len)
    {
        string timeStamp;
        string tmpstring;
    
        if (time)  // It is guaranteed that "time" is correctly zero-terminated, so don't worry about any overflow here.
            tmpstring = time;
    
        // Get the current time point.
        int year, month, day, hour, minute, second;
    #ifndef _WIN32
        struct timeval timeVal;
        struct tm *p;
        gettimeofday(&timeVal, NULL);
        p = localtime(&(timeVal.tv_sec));
        year = p->tm_year + 1900;
        month = p->tm_mon + 1;
        day = p->tm_mday;
        hour = p->tm_hour;
        minute = p->tm_min;
        second = p->tm_sec;
    #else
        SYSTEMTIME sys;
        GetLocalTime(&sys);
        year = sys.wYear;
        month = sys.wMonth;
        day = sys.wDay;
        hour = sys.wHour;
        minute = sys.wMinute;
        second = sys.wSecond;
    #endif
    
        if (0 == len)
        {
            // The "time" doesn't specify any time so we just use the current time
            char tmpTime[30];
            memset(tmpTime, 0, 30);
            sprintf(tmpTime, "%d-%d-%d %d:%d:%d.000", year, month, day, hour, minute, second);
            timeStamp = tmpTime;
        }
        else if (6 == len)
        {
            // The "time" specifies "day-month-year" with each being 2-digit.
            // For example: "150811" means "August 15th, 2011".
            timeStamp = "20";
            timeStamp = timeStamp + tmpstring.substr(4, 2) + "-" + tmpstring.substr(2, 2) + "-" +
                    tmpstring.substr(0, 2);
        }
        else if (8 == len)
        {
            // The "time" specifies "minute-hour-day-month" with each being 2-digit.
            // For example: "51151508" means "August 15th, 15:51".
            // As the year is not specified, the current year will be used.
            string strYear;
            stringstream sstream;
            sstream << year;
            sstream >> strYear;
            sstream.clear();
    
            timeStamp = strYear + "-" + tmpstring.substr(6, 2) + "-" + tmpstring.substr(4, 2) + " " +
                    tmpstring.substr(2, 2) + ":" + tmpstring.substr(0, 2) + ":00.000";
        }
        else if (10 == len)
        {
            // The "time" specifies "minute-hour-day-month-year" with each being 2-digit.
            // For example: "5115150811" means "August 15th, 2011, 15:51".
            timeStamp = "20";
            timeStamp = timeStamp + tmpstring.substr(8, 2) + "-" + tmpstring.substr(6, 2) + "-" + tmpstring.substr(4, 2) + " " +
                    tmpstring.substr(2, 2) + ":" + tmpstring.substr(0, 2) + ":00.000";
        }
        else if (12 == len)
        {
            // The "time" specifies "second-minute-hour-day-month-year" with each being 2-digit.
            // For example: "305115150811" means "August 15th, 2011, 15:51:30".
            timeStamp = "20";
            timeStamp = timeStamp + tmpstring.substr(10, 2) + "-" + tmpstring.substr(8, 2) + "-" + tmpstring.substr(6, 2) + " " +
                    tmpstring.substr(4, 2) + ":" + tmpstring.substr(2, 2) + ":" + tmpstring.substr(0, 2) + ".000";
        }
    
        return timeStamp;
    }
    

    于2011年8月19日更新
    这个问题终于得到解决。实际上,FormatTimeStamp()函数与根本原因无关。段错误是由本地char缓冲区的写溢出引起的。
    可以使用以下更简单的程序来重现此问题(请暂时忽略某些变量的错误命名):
    (与“g++ -Wall -g main.cpp”一起编译)
    #include <string>
    #include <iostream>
    
    void overflow_it(char * A15, char * A15Result)
    {
        int m;
        int t = 0,i = 0;
        char temp[3];
    
        for (m = 0; m < 6; m++)
        {
            t = ((*A15 & 0xf0) >> 4) *10 ;
            t += *A15 & 0x0f;
            A15 ++;
            
            std::cout << "m = " << m << "; t = " << t << "; i = " << i << std::endl;
            
            memset(temp, 0, sizeof(temp));
            sprintf((char *)temp, "%02d", t);   // The buggy code: temp is not big enough when t is a 3-digit integer.
            A15Result[i++] = temp[0];
            A15Result[i++] = temp[1];
        }
    }
    
    int main(int argc, char * argv[])
    {
        std::string str;
    
        {
            char tpTime[6] = {0};
            char A15Result[12] = {0};
            
            // Initialize tpTime
            for(int i = 0; i < 6; i++)
                tpTime[i] = char(154);  // 154 would result in a 3-digit t in overflow_it().
            
            overflow_it(tpTime, A15Result);
            
            str.assign(A15Result);
        }
    
        std::cout << "str says: " << str << std::endl;
    
        return 0;
    }
    
    在继续之前,我们应该记住以下两个事实:
    1)。我的机器是Intel x86机器,因此使用的是Little Endian规则。因此,对于一个int类型的变量“m”(其值为10),它的内存布局可能是这样的:
    Starting addr:0xbf89bebc: m(byte#1): 10
                   0xbf89bebd: m(byte#2): 0
                   0xbf89bebe: m(byte#3): 0
                   0xbf89bebf: m(byte#4): 0
    
    2)。上面的程序在主线程中运行。当涉及到overflow_it()函数时,线程堆栈中的变量布局如下所示(仅显示重要变量):
    0xbfc609e9 : temp[0]
    0xbfc609ea : temp[1]
    0xbfc609eb : temp[2]
    0xbfc609ec : m(byte#1) <-- Note that m follows temp immediately.  m(byte#1) happens to be the byte temp[3].
    0xbfc609ed : m(byte#2)
    0xbfc609ee : m(byte#3)
    0xbfc609ef : m(byte#4)
    0xbfc609f0 : t
    ...(3 bytes)
    0xbfc609f4 : i
    ...(3 bytes)
    ...(etc. etc. etc...)
    0xbfc60a26 : A15Result  <-- Data would be written to this buffer in overflow_it()
    ...(11 bytes)
    0xbfc60a32 : tpTime
    ...(5 bytes)
    0xbfc60a38 : str    <-- Note the str takes up 4 bytes.  Its starting address is **16 bytes** behind A15Result.
    
    我的分析:
    1)。 m是overflow_it()中的一个计数器,在每个for循环中其值都增加1,并且其最大值假定不大于6。因此,它的值可以完全存储在m(byte#1)(记住它是Little Endian)中碰巧是temp 3
    2)。在错误的代码行中:当t是3位整数(例如109)时,sprintf()调用将导致缓冲区溢出,因为将数字109序列化为字符串“109”实际上需要4个字节:'1' ,“0”,“9”和终止符“\0”。因为temp []仅分配了3个字节,所以最终的'\0'肯定会写入temp 3,它只是m(byte#1),不幸的是存储了m的值。结果,每次将m的值重置为0。
    3)。程序员的期望是,overflow_it()中的for循环仅执行6次,每次将m递增1。由于m始终重置为0,因此实际循环时间远远超过6倍。
    4)。让我们看一下overflow_it()中的变量i:每次执行for循环时,i的值将增加2,并且将访问A15Result [i]。但是,如果编译并运行该程序,您将看到i值最终加起来为24,这意味着overflow_it()将数据写入A15Result [0]至A15Result [23]范围的字节中。请注意,对象str仅落后A15Result [0] 16个字节,因此overflow_it()已“清除” str并破坏了其正确的内存布局。
    5)。我认为std::string的正确使用是非POD数据结构,这取决于实例化的std::string对象必须具有正确的内部状态。但是在此程序中,str的内部布局已在外部强制更改。这就是为什么assign()方法调用最终会导致段错误的原因。

    于2011年8月26日更新
    在我于2011年8月19日发布的先前更新中,我说过segfault是由对本地std::string对象的一种方法调用导致的,该对象的内存布局已损坏,因此成为“销毁”对象。这不是一个“永远”的真实故事。考虑下面的C++程序:
    //C++
    class A {
        public:
            void Hello(const std::string& name) {
               std::cout << "hello " << name;
             }
    };
    int main(int argc, char** argv)
    {
        A* pa = NULL; //!!
        pa->Hello("world");
        return 0;
    }
    
    Hello()调用将成功。即使为pa分配了明显错误的指针,它也会成功。原因是:根据C++对象模型,类的非虚拟方法不在对象的内存布局中。 C++编译器将A::Hello()方法转换为类似A_Hello_xxx(A * const this,...)之类的方法,该方法可能是全局函数。因此,只要您不对“this”指针进行操作,事情就可以顺利进行。
    此事实表明,“不良”对象是,而不是,这是导致SIGSEGV segfault的根本原因。 std::string中的assign()方法不是虚拟的,因此“错误的” std::string对象不会导致段错误。最终导致段错误的原因还必须有其他原因。
    我注意到该段错误来自__gnu_cxx::__ exchange_and_add()函数,因此我在this web page中查看了其源代码:
    00046   static inline _Atomic_word 
    00047   __exchange_and_add(volatile _Atomic_word* __mem, int __val)
    00048   { return __sync_fetch_and_add(__mem, __val); }
    
    __exchange_and_add()最终调用__sync_fetch_and_add()。根据this web page,__sync_fetch_and_add()是GCC内置函数,其行为如下:
    type __sync_fetch_and_add (type *ptr, type value, ...)
    {
        tmp = *ptr; 
        *ptr op= value; // Here the "op=" means "+=" as this function is "_and_add".
        return tmp;
    }
    
    在那里!传入的ptr指针在此处取消引用。在08/19/2011程序中,ptr实际上是assign()方法中“错误” std::string对象的“this”指针。正是在这一点上的脱节实际上导致了SIGSEGV分段错误。
    我们可以使用以下程序对此进行测试:
    #include <bits/atomicity.h>
    
    int main(int argc, char * argv[])
    {
        __sync_fetch_and_add((_Atomic_word *)0, 10);    // Would result in a segfault.
    
        return 0;
    }
    

    最佳答案

    有两种可能的可能性:

  • 在第798行之前破坏了本地tmpTimeStamp的某些代码
    对象
  • FormatTimeStamp()的返回值不正确。
  • _GLIBCXX_FULLY_DYNAMIC_STRING最有可能是红色鲱 fish ,并且与问题无关。

    如果您为debuginfo安装libstdc++包(我不知道CentOS上的名称),您将能够“查看”该代码,并能够判断是左侧(LHS)还是左侧分配运算符(operator)的RHS导致了问题。

    如果不可能,则必须在程序集级别进行调试。进入#2框架并执行x/4x $ebp应该为您提供先前的ebp, call 者地址(0x081402fc),LHS(应与帧&tmpTimeStamp中的#3匹配)和RHS。从那里走,祝你好运!

    关于linux - std::string::assign() method from libstdc++.so.6中奇怪的SIGSEGV段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7038124/

    相关文章:

    c - 如何在Linux中使用SOCK_RAW接收IPv4数据包?

    linux - 在现代 Linux 上永久删除 root 权限

    vb.net - 为什么 String.Format 给出此字符串的 inputString 未格式化错误

    c++ - 段错误 C++ Sundaram

    c++ - 使用 INotify 监视具有多个符号链接(symbolic link)的文件

    java - Spring查询字符串简单验证?

    c - 从 fgets() 输入中删除尾随换行符

    c - 字符串数组实现返回段错误

    函数中的 C++ 段错误

    linux - 如何在 Fedora Linux 上安装对话框实用程序?