翻译:Weird SIGSEGV segmentation fault in std::string::assign() method from libstdc++.so.6

Stack overflow地址:Weird SIGSEGV segmentation fault in std::string::assign() method from libstdc++.so.6


翻译:

我的程序最近遇到了一个奇怪的段错误在运行过程中。我想知道是否有人曾经遇到过这个错误并且知道如何解决它。这里有一些更多的信息:

基本信息:

CentOS 5.2, kernal version is 2.6.18

g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)

CPU: Intel x86 family

libstdc++.so.6.0.8

我的程序会以多线程来处理数据。段错误发生在其中一个线程上。

尽管这是一个多线程程序,这个段错误看似发生在局部std::string对象上。稍后我会展示代码片段。

这个程序编译带有-g、-Wall和-fPIC选项,并且没有-O2或者其他优化选项。

core信息:

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, std::allocator >::assign(std::basic_string, std::allocator > const&) () from /usr/lib/libstdc++.so.6

#2  0x06f50834 in std::basic_string, std::allocator >::operator=(std::basic_string, std::allocator > 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函数的原型:

std::string FormatTimeStamp(const char *time, int len)

我认为这样的string赋值操作应该是非常常见的,但是我不理解为什么段错误发生在这里。

我调查的东西:

我在网上查找答案。我看了这里。它的回复说尝试重新编译这个程序带有_GLIBCXX_FULLY_DYNAMIC_STRING 宏定义,我试了之后崩溃现象仍然会发生。

我还看了这里。它也说重新编译程序使用_GLIBCXX_FULLY_DYNAMIC_STRING,但是这个作者似乎处理了和我不同的问题,因此我不认为他的解决方案对我有效。

Updated on 08/15/2011

Hi朋友们,这是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;

}

Updated on 08/19/2011

这个问题最终被定位到原因并且解决了。事实上,FormatTimeStamp()函数和根本原因没有关系。崩溃的原因是局部char缓存区写越界导致的。

这个问题可以被重现使用下面简单的程序(从现在开始请忽略一些变量的不好的起名):

(编译带有g++ -Wall -g main.cpp)

#include

#include

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的机器,所以它使用小端字节序规则。因为对于int型变量m,比如说它的值为10,内存布局可能像下面这样:

tarting 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()中,每次循环它的值增加1,最大值应该不超过6、因此它的值可以被完全的存储在m(byte#1)(记住这是小端字节序)恰好是temp3

2)在buggy行:当t是一个三个数字的整型,比如109,然后sprintf()调用会导致缓冲区溢出,因为序列化数字109到字符串“109”实际上需要4个字节:‘1’,'0','9'和终止符'\n'。因为temp数组只分配了3个字节,最后的'\0'将会被写到temp3,它就在m(byte#1),那里不幸的存储着我们的值。结果,每次m的值都被重置为0

3)程序猿的期望,然而,是overflow_it()中的for循环将会执行6次,每次m加一。因为m一直被重置为0,实际上循环次数远超过6次

4)来看变量i在overflow_it():每次循环执行的时候,i值会增加2,A15Result[i]将会被访问。然后,如果你编译并运行这个程序,你将会看到i的值最终被加到了24,这意味着overflow_it()在A15Result[0]到A15Result[23]字节范围内写数据。注意str对象只在A15Result[0]后面的16个字节处,因此overflow_it()已经扫过str对象并且摧毁了它正确的内存布局

5)我认为正确的使用std::string,作为一个non-POD数据结构,取决于std::string对象必须有一个正确的内部状态。但是在这个程序中,str的内部布局被外界环境强制改变了。这应该就是为什么assgin()函数调用最终导致崩溃的原因

Update on 08/26/2011

在我之前08/19/2011的更新中,我说引起段错误的原因是,一个方法调用了局部的内存布局被破坏的std::string对象,因此变成了"destroyed"对象。这不“总“是正确的。考虑下面的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"指针,事情就会非常完美。

这个事实说明了"bad"对象不是引起段错误的根本原因。这个assgin()方法是std::string的一个非虚函数,因为"bad" std::string对象不会引起段错误。这里肯定有其他原因最终引发了段错误。

我注意到了段错误来自于__gnu_cxx::__exchange_and_add()函数,因为我跟进了源码在这里

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()。根据这里,__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实际上是"bad" std::string对象的“this”指针在assgin()方法中。这个指针的解引用实际上引起了段错误。

我们可以用下面的程序进行测试:

#include

int main(int argc, char * argv[])

{

    __sync_fetch_and_add((_Atomic_word *)0, 10);    // Would result in a segfault.

    return 0;

}


Answer1:

这里有两个可能的原因:

1.在798行之前的一些代码损坏了本地的tmpTimeStamp对象

2.FormatTimeStamp()函数返回的值是败坏的

这个_GLIBCXX_FULLY_DYNAMIC_STRING 更像是在转移注意力,没有什么用途对于程序来说。

如果你安装了libstdc++中的debuginfo这个包(我不知道它在CentOS上叫什么),你可以对代码“see info”,并且可以判断出LHS还是赋值操作的RHS引发的这个问题。

如果这不可行,你就必须在汇编级别进行调试。



Answer2:

我猜在函数FormatTimeStamp中有一些问题,但是没有源码很难去说一些什么。重新检查你的程序使用Valgrind。通常这会帮助你解决这类的bug。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容