翻譯: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


翻譯:

我的程序最近遇到了一個(gè)奇怪的段錯(cuò)誤在運(yùn)行過(guò)程中。我想知道是否有人曾經(jīng)遇到過(guò)這個(gè)錯(cuò)誤并且知道如何解決它低滩。這里有一些更多的信息:

基本信息:

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

我的程序會(huì)以多線程來(lái)處理數(shù)據(jù)秤标。段錯(cuò)誤發(fā)生在其中一個(gè)線程上忙迁。

盡管這是一個(gè)多線程程序,這個(gè)段錯(cuò)誤看似發(fā)生在局部std::string對(duì)象上凶杖。稍后我會(huì)展示代碼片段嘁锯。

這個(gè)程序編譯帶有-g掂林、-Wall和-fPIC選項(xiàng)粘我,并且沒(méi)有-O2或者其他優(yōu)化選項(xiàng)鼓蜒。

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

請(qǐng)注意段錯(cuò)誤在basic_string::operator=()處開(kāi)始。

相關(guān)的代碼:(我展示了很多可能需要的代碼征字,請(qǐng)暫時(shí)忽略代碼格式的事情)

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函數(shù)的原型:

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

我認(rèn)為這樣的string賦值操作應(yīng)該是非常常見(jiàn)的都弹,但是我不理解為什么段錯(cuò)誤發(fā)生在這里。

我調(diào)查的東西:

我在網(wǎng)上查找答案匙姜。我看了這里畅厢。它的回復(fù)說(shuō)嘗試重新編譯這個(gè)程序帶有_GLIBCXX_FULLY_DYNAMIC_STRING 宏定義,我試了之后崩潰現(xiàn)象仍然會(huì)發(fā)生氮昧。

我還看了這里框杜。它也說(shuō)重新編譯程序使用_GLIBCXX_FULLY_DYNAMIC_STRING,但是這個(gè)作者似乎處理了和我不同的問(wèn)題袖肥,因此我不認(rèn)為他的解決方案對(duì)我有效咪辱。

Updated on 08/15/2011

Hi朋友們,這是FormatTimeStamp的源碼椎组。我理解這個(gè)代碼看上去不完美(比如:太多的魔法數(shù)...)油狂,但是首先把注意力放在崩潰問(wèn)題上:

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

這個(gè)問(wèn)題最終被定位到原因并且解決了。事實(shí)上寸癌,F(xiàn)ormatTimeStamp()函數(shù)和根本原因沒(méi)有關(guān)系专筷。崩潰的原因是局部char緩存區(qū)寫(xiě)越界導(dǎo)致的。

這個(gè)問(wèn)題可以被重現(xiàn)使用下面簡(jiǎn)單的程序(從現(xiàn)在開(kāi)始請(qǐng)忽略一些變量的不好的起名):

(編譯帶有g(shù)++ -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;

}

在繼續(xù)之前蒸苇,我們要記住兩個(gè)事實(shí):1)我的機(jī)器是一臺(tái)Intel x86的機(jī)器磷蛹,所以它使用小端字節(jié)序規(guī)則。因?yàn)閷?duì)于int型變量m溪烤,比如說(shuō)它的值為10味咳,內(nèi)存布局可能像下面這樣:

tarting addr:0xbf89bebc: m(byte#1): 10

? ? ? ? ? ? ? 0xbf89bebd: m(byte#2): 0

? ? ? ? ? ? ? 0xbf89bebe: m(byte#3): 0

? ? ? ? ? ? ? 0xbf89bebf: m(byte#4): 0

2)上面的程序在主線程中運(yùn)行。當(dāng)運(yùn)行到overflow_it()函數(shù)的是氛什,它的線程局部堆椵汉可能像這樣(只列出重要變量):

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是一個(gè)計(jì)數(shù)者在overflow()中,每次循環(huán)它的值增加1枪眉,最大值應(yīng)該不超過(guò)6捺檬、因此它的值可以被完全的存儲(chǔ)在m(byte#1)(記住這是小端字節(jié)序)恰好是temp3

2)在buggy行:當(dāng)t是一個(gè)三個(gè)數(shù)字的整型,比如109贸铜,然后sprintf()調(diào)用會(huì)導(dǎo)致緩沖區(qū)溢出堡纬,因?yàn)樾蛄谢瘮?shù)字109到字符串“109”實(shí)際上需要4個(gè)字節(jié):‘1’,'0','9'和終止符'\n'。因?yàn)閠emp數(shù)組只分配了3個(gè)字節(jié)蒿秦,最后的'\0'將會(huì)被寫(xiě)到temp3烤镐,它就在m(byte#1),那里不幸的存儲(chǔ)著我們的值棍鳖。結(jié)果炮叶,每次m的值都被重置為0

3)程序猿的期望碗旅,然而,是overflow_it()中的for循環(huán)將會(huì)執(zhí)行6次镜悉,每次m加一祟辟。因?yàn)閙一直被重置為0,實(shí)際上循環(huán)次數(shù)遠(yuǎn)超過(guò)6次

4)來(lái)看變量i在overflow_it():每次循環(huán)執(zhí)行的時(shí)候侣肄,i值會(huì)增加2旧困,A15Result[i]將會(huì)被訪問(wèn)。然后稼锅,如果你編譯并運(yùn)行這個(gè)程序吼具,你將會(huì)看到i的值最終被加到了24,這意味著overflow_it()在A15Result[0]到A15Result[23]字節(jié)范圍內(nèi)寫(xiě)數(shù)據(jù)矩距。注意str對(duì)象只在A15Result[0]后面的16個(gè)字節(jié)處拗盒,因此overflow_it()已經(jīng)掃過(guò)str對(duì)象并且摧毀了它正確的內(nèi)存布局

5)我認(rèn)為正確的使用std::string,作為一個(gè)non-POD數(shù)據(jù)結(jié)構(gòu)锥债,取決于std::string對(duì)象必須有一個(gè)正確的內(nèi)部狀態(tài)锣咒。但是在這個(gè)程序中,str的內(nèi)部布局被外界環(huán)境強(qiáng)制改變了赞弥。這應(yīng)該就是為什么assgin()函數(shù)調(diào)用最終導(dǎo)致崩潰的原因

Update on 08/26/2011

在我之前08/19/2011的更新中毅整,我說(shuō)引起段錯(cuò)誤的原因是,一個(gè)方法調(diào)用了局部的內(nèi)存布局被破壞的std::string對(duì)象绽左,因此變成了"destroyed"對(duì)象悼嫉。這不“總“是正確的∑纯考慮下面的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()函數(shù)調(diào)用會(huì)成功戏蔑。它將會(huì)程序即使你分配了一個(gè)明顯錯(cuò)誤的指針給pa,原因是:一個(gè)類的非虛函數(shù)不在對(duì)象的內(nèi)部?jī)?nèi)存布局上鲁纠,根據(jù)C++對(duì)象模型总棵。C++編譯器將A::Hello()函數(shù)轉(zhuǎn)換為像這樣子:A_Hello_xxx(A * const this, ...),它是一個(gè)全局函數(shù)改含。因此情龄,只要你不操作"this"指針,事情就會(huì)非常完美捍壤。

這個(gè)事實(shí)說(shuō)明了"bad"對(duì)象不是引起段錯(cuò)誤的根本原因骤视。這個(gè)assgin()方法是std::string的一個(gè)非虛函數(shù),因?yàn)?bad" std::string對(duì)象不會(huì)引起段錯(cuò)誤鹃觉。這里肯定有其他原因最終引發(fā)了段錯(cuò)誤专酗。

我注意到了段錯(cuò)誤來(lái)自于__gnu_cxx::__exchange_and_add()函數(shù),因?yàn)槲腋M(jìn)了源碼在這里

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()最終調(diào)用了?__sync_fetch_and_add()盗扇。根據(jù)這里祷肯,__sync_fetch_and_add() 是GCC的內(nèi)建函數(shù)它的行為像下面這樣:

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的程序中佑笋,這個(gè)ptr實(shí)際上是"bad" std::string對(duì)象的“this”指針在assgin()方法中拜轨。這個(gè)指針的解引用實(shí)際上引起了段錯(cuò)誤。

我們可以用下面的程序進(jìn)行測(cè)試:

#include

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

{

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

? ? return 0;

}


Answer1:

這里有兩個(gè)可能的原因:

1.在798行之前的一些代碼損壞了本地的tmpTimeStamp對(duì)象

2.FormatTimeStamp()函數(shù)返回的值是敗壞的

這個(gè)_GLIBCXX_FULLY_DYNAMIC_STRING 更像是在轉(zhuǎn)移注意力允青,沒(méi)有什么用途對(duì)于程序來(lái)說(shuō)。

如果你安裝了libstdc++中的debuginfo這個(gè)包(我不知道它在CentOS上叫什么)卵沉,你可以對(duì)代碼“see info”颠锉,并且可以判斷出LHS還是賦值操作的RHS引發(fā)的這個(gè)問(wèn)題。

如果這不可行史汗,你就必須在匯編級(jí)別進(jìn)行調(diào)試琼掠。



Answer2:

我猜在函數(shù)FormatTimeStamp中有一些問(wèn)題,但是沒(méi)有源碼很難去說(shuō)一些什么停撞。重新檢查你的程序使用Valgrind瓷蛙。通常這會(huì)幫助你解決這類的bug。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末戈毒,一起剝皮案震驚了整個(gè)濱河市艰猬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埋市,老刑警劉巖冠桃,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異道宅,居然都是意外死亡食听,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門污茵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)樱报,“玉大人,你說(shuō)我怎么就攤上這事泞当〖8颍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵襟士,是天一觀的道長(zhǎng)笤受。 經(jīng)常有香客問(wèn)我,道長(zhǎng)敌蜂,這世上最難降的妖魔是什么箩兽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮章喉,結(jié)果婚禮上汗贫,老公的妹妹穿的比我還像新娘身坐。我一直安慰自己,他們只是感情好落包,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布部蛇。 她就那樣靜靜地躺著,像睡著了一般咐蝇。 火紅的嫁衣襯著肌膚如雪涯鲁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天有序,我揣著相機(jī)與錄音抹腿,去河邊找鬼。 笑死旭寿,一個(gè)胖子當(dāng)著我的面吹牛警绩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盅称,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼肩祥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了缩膝?” 一聲冷哼從身側(cè)響起混狠,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疾层,沒(méi)想到半個(gè)月后檀蹋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡云芦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年俯逾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舱禽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坠七,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布旗笔,位于F島的核電站彪置,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝇恶。R本人自食惡果不足惜拳魁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撮弧。 院中可真熱鬧潘懊,春花似錦姚糊、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至释树,卻和暖如春肠槽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奢啥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工秸仙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扫尺。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像炊汤,于是被迫代替她去往敵國(guó)和親正驻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容