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。