Странная ошибка сегментации SIGSEGV в методе std :: string :: assign () из libstdc ++. So.6 - PullRequest
16 голосов
/ 12 августа 2011

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

Базовая информация:

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


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

Обновлено 08/0819/2011

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

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

(Скомпилировано с помощью "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 для каждого цикла for, а максимальное значение предполагается не больше 6. Таким образом, его значение может быть полностью сохранено в m (байт # 1) (помните, что это Little Endian), котороебывает temp 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 находится всего в 16 байтах после A15Result [0], поэтому overflow_it () «протолкнул» str и уничтожил его правильную структуру памяти.

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


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

В моем предыдущем обновлении от 08/ 19/2011, я сказал, что ошибка вызвана вызовом метода для локального объекта 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», дела могут идти очень хорошо.

Этот факт показывает, что «плохой» объект является NOT основной причиной, по которойрезультаты в 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 здесь разыменовывается.В программе от 08.08.2011 ptr фактически является указателем «this» «плохого» объекта 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;
}

Ответы [ 2 ]

2 голосов
/ 12 августа 2011

Есть две вероятные возможности:

  • некоторый код перед строкой 798 повредил локальный tmpTimeStamp объект
  • возвращаемое значение из FormatTimeStamp() было как-то плохо.

_GLIBCXX_FULLY_DYNAMIC_STRING, скорее всего, красная сельдь и не имеет никакого отношения к проблеме.

Если вы установите debuginfo пакет для libstdc++ (я не знаю, как он называется в CentOS), вы сможете «разобраться» в этом коде и, возможно, сможете определить, стороны (LHS) или RHS оператора присваивания вызвали проблему.

Если это невозможно, вам придется отлаживать это на уровне сборки. Переход к кадру #2 и выполнение x/4x $ebp должны дать вам предыдущий ebp, адрес вызывающего абонента (0x081402fc), LHS (должен соответствовать &tmpTimeStamp в кадре #3) и RHS. Иди оттуда, и удачи!

2 голосов
/ 12 августа 2011

Полагаю, внутри функции FormatTimeStamp может быть какая-то проблема, но без исходного кода трудно что-либо сказать.Попробуйте проверить свою программу под Valgrind.Обычно это помогает исправлять ошибки такого рода.

...