Моя программа недавно столкнулась с странным segfault при запуске. Я хочу знать, встречался ли кто-то с этой ошибкой раньше и как это можно было бы исправить. Вот дополнительная информация:
Основная информация:
- CentOS 5.2, версия для ядра - 2.6.18
- g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
- Процессор: семейство Intel x86
- libstdС++. So.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.08.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, поэтому он использует правило Little 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 в каждом цикле и максимальное значение которого не должно превышать 6. Таким образом, его значение может быть полностью сохранено в m (байт # 1) (помните, что Little Endian), который бывает 3.
2). В строке с ошибкой: Когда t является 3-значным целым числом, таким как 109, тогда вызов sprintf() приведет к переполнению буфера, потому что сериализация номера 109 в строке "109" фактически требует 4 байта: '1', '0', '9' и завершение '\ 0'. Поскольку temp [] выделяется только с 3 байтами, окончательный '\ 0' определенно будет записан в temp 3, который является только m ( байт # 1), который, к сожалению, сохраняет значение m. В результате значение m равно reset to 0 каждый раз.
3). Однако ожидание программиста состоит в том, что цикл for в overflow_it() будет выполняться только 6 раз, причем каждый раз, когда m увеличивается на 1. Поскольку m всегда reset до 0, фактическое время цикла намного больше, чем 6 раз.
4). Давайте посмотрим на переменную я в overflow_it(): каждый раз, когда цикл for выполняется, значение я увеличивается на 2, и будет доступен доступ к A15Result [i]. Однако, если вы скомпилируете и запустите эту программу, вы увидите, что значение я добавит до 24, что означает, что overflow_it() записывает данные в байты от A15Result [0] до A15Result [23]. Обратите внимание, что объект str равен всего 16 байтам позади A15Result [0], поэтому overflow_it() имеет "sweeped through" str и уничтожает правильную макет памяти.
5). Я думаю, что правильное использование std::string, так как это не-POD-структура данных, зависит от того, что экземпляр объекта std::string должен иметь правильное внутреннее состояние. Но в этой программе внутренняя структура str была изменена силой извне. Это должно быть, почему вызов метода assign(), наконец, вызовет segfault.
Обновление от 26.08.2011
В моем предыдущем обновлении от 08/19/2011 я сказал, что segfault был вызван вызовом метода локального объекта std::string, структура памяти которого была сломана и, таким образом, стала "уничтоженным" объектом. Это не "всегда" истинная история. Рассмотрим программу на С++ ниже:
//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. Причина в том, что не виртуальные методы класса не находятся внутри макета памяти объекта, в соответствии с объектной моделью С++. Компилятор С++ превращает метод A:: Hello() в нечто вроде, скажем, A_Hello_xxx (A * const this,...), которое может быть глобальной функцией. Таким образом, до тех пор, пока вы не будете работать с указателем "this", все будет хорошо.
Этот факт показывает, что "плохим" объектом является НЕ основная причина, которая приводит к segfault SIGSEGV. Метод assign() не является виртуальным в std::string, поэтому объект "bad" std::string не будет вызывать segfault. Должна быть другая причина, которая в конечном итоге вызвала segfault.
Я заметил, что segfault происходит из функции __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 фактически является указателем "this" объекта "bad" std::string в методе assign(). Именно разрывы в этой точке фактически вызвали ошибку сегментации 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;
}