Странная ошибка сегментации SIGSEGV в методе std::string::assign() из libstdc++.Итак.6
моя программа недавно столкнулась со странным segfault при запуске. я хочу знать, встречал ли кто-нибудь эту ошибку раньше и как ее можно исправить. вот дополнительная информация:
основная информация:
- CentOS 5.2, версия ядра 2.6.18
- g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
- процессор: семейство Intel x86
- libstdc++.так что ... .6.0.8
- моя программа начнет несколько потоки для обработки данных. Segfault произошло в одном из потоков.
- хотя это многопоточная программа, казалось, что segfault происходит на локальном объекте 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
обратите внимание, что segfault начинается в пределах 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)
Я думаю, что такие операции назначения строк должны быть своего рода широко используемыми, но я просто не понимаю, почему здесь может произойти segfault.
Что Я исследовали:
Я искал в интернете ответы. Я посмотрел на здесь. В ответе говорится, что попробуйте перекомпилировать программу с определенным макросом _GLIBCXX_FULLY_DYNAMIC_STRING. Я пытался, но авария все еще происходит.
Я тоже посмотрел на здесь. Он также говорит, чтобы перекомпилировать программу с _GLIBCXX_FULLY_DYNAMIC_STRING, но автор, похоже, имеет дело с другой проблемой с моей, поэтому я не думаю, что его решение работает для меня.
Обновление ПО 08/15/2011
Привет, ребята, вот оригинальный код этой 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;
}
Обновление ПО 08/19/2011
эта проблема наконец была решена и исправлена. Функция FormatTimeStamp () не имеет ничего общего с первопричина, на самом деле. Segfault вызвано переполнением записи локального буфера 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, поэтому она использует маленькое правило Endian. Поэтому для переменной " m " типа int, чье значение, скажем, 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 (), значение которого увеличивается на 1 в каждом цикле for и максимальное значение которого не должно быть больше чем 6. Таким образом, его значение может быть полностью сохранено в m(байт#1)(Помните, что это маленький Endian), который, оказывается, temp3.
2). В багги-строке: когда t является 3-значным целым числом, например 109, вызов sprintf() приведет к переполнению буфера, потому что сериализация числа 109 в строку " 109 " фактически требует 4 байта: '1', '0', '9' и завершающее "". Поскольку temp[] выделяется только с 3 байтами, окончательный ' ' определенно будет записан в темп3, который является только m (байт#1), который, к сожалению, хранит значение m. В результате значение m каждый раз сбрасывается до 0.
3). Однако программист ожидает, что цикл for В overflow_it() будет выполняться только 6 раз, причем каждый раз m увеличивается на 1. Поскольку m всегда сбрасывается до 0, фактическое время цикла намного больше, чем в 6 раз.
4). Давайте посмотрим на переменную i в overflow_it (): каждый раз, когда цикл for выполняется, значение i увеличивается на 2, и a15result[i] будет доступен. Однако, если вы скомпилируете и запустите эту программу, вы увидите, что значение i, наконец, добавляет до 24, что означает, что overflow_it() записывает данные в байты от A15Result[0] до A15Result[23]. Обратите внимание, что объект str находится всего в 16 байтах за A15Result[0], таким образом, overflow_it () "пронесся" str и уничтожил его правильный макет памяти.
5). Я думаю, что правильное использование std::string, как это структура данных без POD зависит от того, что экземпляр std::string объект должен иметь правильное внутреннее состояние. Но в этой программе внутренняя компоновка str была изменена силой извне. Вот почему вызов метода assign (), наконец, вызовет segfault.
обновление от 26.08.2011
в моем предыдущем обновлении 08/19/2011 я сказал, что 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, ...) что может быть глобальной функцией. Таким образом, пока вы не работаете с указателем "это", все может идти довольно хорошо.
этот факт показывает, что" плохим " объектом является не первопричина, которая приводит к SIGSEGV segfault. Метод assign () не является виртуальным в std::string, поэтому "плохой" объект std::string не вызовет segfault. Должна быть какая-то другая причина, которая, наконец, вызвала segfault.
Я заметил, что segfault происходит от __gnu_cxx:: _ _ exchange_и _ add (), поэтому я затем просмотрел его исходный код в этой странице:
00046 static inline _Atomic_word
00047 __exchange_and_add(volatile _Atomic_word* __mem, int __val)
00048 { return __sync_fetch_and_add(__mem, __val); }
о __обмен_и_добавить (), наконец, называет __синхр_фетч_и_добавить(). Согласно этой странице, _ _ sync_fetch_и _ 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 на самом деле является "это" указатель" плохого " объекта std::string в методе assign (). Это derefenence в этот момент, что на самом деле стало причиной сегментации сигнала 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;
}
2 ответов
есть две вероятные возможности:
- некоторый код перед строкой 798 повредил локальный
tmpTimeStamp
объект - возвращаемое значение
FormatTimeStamp()
стало как-то плохо.
на _GLIBCXX_FULLY_DYNAMIC_STRING
скорее всего, это отвлекающий маневр и не имеет ничего общего с проблемой.
при установке на libstdc++
(Я не знаю, как это называется на CentOS), вы сможете "увидеть" этот код и, возможно, сможете сказать, проблема была вызвана левой стороной (LHS) или RHS оператора присваивания.
если это невозможно, вам придется отлаживать это на уровне сборки. Входим в кадр #2
и в x/4x $ebp
должен дать вам предыдущий ebp
, адрес абонента (0x081402fc
), LHS (должно соответствовать &tmpTimeStamp
в рамках #3
), и RHS. Идите оттуда, и удачи!
Я думаю, что может быть какая-то проблема внутри , но без исходного кода трудно что-либо сказать. Попробуйте проверить свою программу под Valgrind. Обычно это помогает исправить такие ошибки.