Визуальная ошибка сегментации SIGSEGV в методе std::string:: assign() из libstdС++. So.6
Моя программа недавно столкнулась с странным 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;
}
Ответы
Ответ 1
Возможны две возможности:
- некоторый код до строки 798 повредил локальный
tmpTimeStamp
Объект
- возвращаемое значение из
FormatTimeStamp()
было каким-то плохим.
_GLIBCXX_FULLY_DYNAMIC_STRING
, скорее всего, красная селедка и не имеет ничего общего с проблемой.
Если вы установите пакет debuginfo
для libstdc++
(я не знаю, что он назвал CentOS), вы сможете "видеть" этот код и, возможно, сможете определить, (LHS) или RHS оператора присваивания вызвали проблему.
Если это невозможно, вам придется отлаживать это на уровне сборки. Переход в рамку #2
и выполнение x/4x $ebp
должно дать вам предыдущий ebp
, адрес вызывающего абонента (0x081402fc
), LHS (должен соответствовать &tmpTimeStamp
в кадре #3
) и RHS. Идите оттуда, и удачи!
Ответ 2
Я предполагаю, что внутри функции FormatTimeStamp
может быть какая-то проблема, но без исходного кода трудно сказать что-либо. Попробуйте проверить свою программу под Valgrind. Обычно это помогает исправить такие ошибки.