Странный SIGSEGV ошибка сегментации в методе std :: string :: assign () из libstdc ++. so.6

Моя программа недавно обнаружила странный segfault при запуске. Я хочу знать, сталкивался ли кто-нибудь с этой ошибкой раньше и как ее можно исправить. Дополнительная информация:

Основная информация:

  • CentOS 5.2, версия ядра - 2.6.18
  • g ++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
  • ЦП: Intel x86 family
  • libstdc ++. so.6.0.8
  • Моя программа запускает несколько потоков для обработки данных. Ошибка сегментации произошла в одном из потоков.
  • Хотя это многопоточная программа, похоже, что ошибка сегментации возникла в локальном объекте 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, 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

Обратите внимание, что 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, но автор, похоже, имеет дело с моей проблемой, поэтому я не думаю, что его решение мне подходит.


Обновлено 15.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;
}

Обновлено 19.08.2011.

Эта проблема наконец-то решена и исправлено. На самом деле функция FormatTimeStamp () не имеет ничего общего с основной причиной. Segfault вызван переполнением локального символьного буфера при записи.

Эту проблему можно воспроизвести с помощью следующей более простой программы (пожалуйста, пока не обращайте внимания на неправильные наименования некоторых переменных):

(Скомпилировано с помощью «g ++ -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;
}

Вот два факта перед тем как продолжить, мы должны помнить: 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 в каждом цикле for и максимальное значение которого предполагается не больше 6. Таким образом, это значение может быть полностью сохранено в m (байт №1) (помните, что это Little Endian), что оказывается темп 3 .

2). В строке с ошибками: когда t представляет собой трехзначное целое число, например 109, тогда вызов sprintf () приведет к переполнению буфера, поскольку для сериализации числа 109 в строку «109» на самом деле требуется 4 байта: '1' , '0', '9' и завершающий '\ 0'. Поскольку temp [] выделяется только 3 байтами, последний '\ 0' определенно будет записан в temp 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 отстает от A15Result [0] всего на 16 байт, поэтому overflow_it () «прочесал» str и уничтожил его правильную структуру памяти.

5). Я думаю, что правильное использование std :: string, поскольку это структура данных, не относящаяся к POD, зависит от того, что этот экземпляр объекта std :: string должен иметь правильное внутреннее состояние. Но в этой программе внутренняя структура str была изменена принудительно извне. Это должно быть причиной того, что вызов метода assign () в конечном итоге вызовет segfault.


Обновление от 26.08.2011

В моем предыдущем обновлении от 19.08.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, ...), который может быть глобальной функцией. Таким образом, пока вы не работаете с указателем «this», все может идти хорошо.

Этот факт показывает, что «плохой» объект НЕ является основной причиной, которая приводит к SIGSEGV segfault. Метод assign () не является виртуальным в std :: string, поэтому "плохой" объект 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. В программе от 19.08.2011 ptr фактически является указателем this на «плохой» объект std :: string в методе assign (). Именно разыграние в этот момент фактически вызвало ошибку сегментации SIGSEGV.

Мы можем проверить это с помощью следующей программы:

#include 

int main(int argc, char * argv[])
{
    __sync_fetch_and_add((_Atomic_word *)0, 10);    // Would result in a segfault.

    return 0;
}

17
задан yaobin 26 August 2011 в 15:31
поделиться