Bizarre SIGSEGV faute de segmentation dans std::string::méthode assign() de la bibliothèque libstdc++..6

mon programme a récemment rencontré un segfault étrange en cours d'exécution. je veux savoir si quelqu'un avait rencontré cette erreur et comment il pourrait être fixée. Voici plus d'infos:

les infos de Base:

  • CentOS 5.2, kernel version est la version 2.6.18
  • g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
  • CPU: Intel x86 family
  • libstdc++.donc.6.0.8
  • mon programme démarre plusieurs fois threads pour traiter les données. Le segfault s'est produit dans l'un des fils.
  • bien qu'il s'agisse d'un programme multi-thread, le segfault semblait se produire sur un objet local std::string. Je le montrerai dans le code plus tard.
  • le programme est compilé avec-g, - Wall et-fPIC, et sans -O2 ou autres options d'optimisation.

Le core dump info:

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

veuillez noter que le segfault commence dans le basic_string::operator=().

Le code correspondant: (J'ai montré plus de code que cela pourrait être nécessaire, et s'il vous plaît ignorer les choses de style de codage pour l'instant.)

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

et voici le prototype de cette méthode FormatTimeStamp:

std::string FormatTimeStamp(const char *time, int len)

je pense que de telles opérations d'assignation de chaîne de caractères devraient être une sorte de couramment utilisées, mais je ne comprends pas pourquoi un segfault pourrait se produire ici.

Ce Que Je avons étudié:

j'ai cherché des réponses sur le web. J'ai regardé ici. La réponse dit d'essayer de recompiler le programme avec la macro _glibcxx_fly_dynamic_string définie. J'ai essayé, mais le crash se produit toujours.

j'ai également regardé ici. Il dit aussi de recompiler le programme avec _GLIBCXX_FULLY_DYNAMIC_STRING, mais l'auteur semble avoir affaire à un autre problème avec le mien, donc je ne pense pas que sa solution fonctionne m'.


mise à Jour sur 08/15/2011

Salut Les gars, voici le code original de ce FormatTimeStamp. Je comprends que le codage ne semble pas très agréable (trop de nombres magiques, par exemple..), mais concentrons-nous d'abord sur la question des accidents.

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;
}

mise à Jour sur 08/19/2011

ce problème a finalement été réglé. La fonction FormatTimeStamp() n'a rien à en rapport avec la cause profonde, en fait. Le segfault est causé par un débordement d'écriture d'un tampon char local.

ce problème peut être reproduit avec le programme plus simple suivant (veuillez ignorer les mauvais noms de certaines variables pour l'instant):

(Compilé avec g++ -Wall-g principal.rpc")

#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;
}

voici deux faits dont nous devrions nous souvenir avant de continuer: 1). Ma machine est une machine Intel x86 donc elle utilise la règle Little Endian. Donc pour une variable " m" de type int, dont la valeur est, disons, 10, disposition de la mémoire pourrait être comme ceci:

Starting addr:0xbf89bebc: m(byte#1): 10
               0xbf89bebd: m(byte#2): 0
               0xbf89bebe: m(byte#3): 0
               0xbf89bebf: m(byte#4): 0

2). Le programme ci-dessus fonctionne dans le thread principal. En ce qui concerne la fonction overflow_it (), la disposition des variables dans la pile de threads ressemble à ceci(qui ne montre que les variables importantes):

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.

Mon analyse:

1). m est un compteur dans overflow_it() dont la valeur est incrémentée de 1 à chaque boucle et dont la valeur max est censé pas plus de 6. Ainsi, sa valeur pourrait être stockée complètement en m (octet#1) (rappelez-vous que C'est Little Endian) qui se trouve être temp 3.

2). Dans la ligne buggy: quand t est un entier à 3 chiffres, tel que 109, alors l'appel sprintf() résulterait en un dépassement de tampon, parce que sérialiser le nombre 109 à la chaîne "109" nécessite en fait 4 octets: '1', '0', '9' et une terminaison ''. Puisque temp[] est attribué avec 3 octets seulement, le ' ' final serait certainement ecrit à temp 3, qui est juste le m (octet#1), qui malheureusement stocke la valeur de M. Par conséquent, la valeur de m est réinitialisée à 0 à chaque fois.

3). Le programmeur s'attend cependant à ce que la boucle for dans le overflow_it() n'exécute que 6 fois, chaque fois m étant incrémenté de 1. Parce que m est toujours réinitialisé à 0, le temps réel de boucle est beaucoup plus de 6 fois.

4). Regardons la variable i dans ovflow_it (): chaque fois que le for la boucle est exécutée, i est incrémenté de 2, et A15Result[i] sera accessible. Cependant, si vous compilez et exécutez ce programme, vous verrez que la valeur i s'additionne finalement à 24, ce qui signifie que les données d'écriture de overflow_it() vont de A15Result[0] à a15result[23]. Notez que la str d'objet n'est que de 16 octets derrière A15Result[0], donc le overflow_it() a "balayé" la str et détruit sa configuration mémoire correcte.

5). Je pense que l'utilisation correcte de std:: string, comme il est une structure de données non-POD, dépend de ce que l'objet std::string instancié doit avoir un état interne correct. Mais dans ce programme, la disposition interne de str a été changée par la force externe. C'est pourquoi l'appel de méthode assign() provoquerait finalement un segfault.


mise à Jour sur 08/26/2011

dans ma précédente mise à jour du 19/08/2011, j'ai dit que le segfault était causé par un appel de méthode sur un objet std local::string dont la disposition de la mémoire avait été brisée et est donc devenue un objet "détruit". Ce n'est pas un "toujours" histoire vraie. Considérons le programme C++ ci-dessous:

//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;
}

l'appel Hello () réussirait. Cela réussirait même si vous assignez un pointeur manifestement mauvais à l'AP. La raison en est que les méthodes non-virtuelles d'une classe ne résident pas dans la disposition mémoire de l'objet, selon le modèle d'objet C++. Le compilateur C++ tourne la méthode a::Hello() vers quelque chose comme, disons, A_Hello_xxx(a * const ce. ,..) qui pourrait être une fonction globale. Ainsi, aussi longtemps que vous n'opérez pas sur le pointeur "ceci", les choses pourraient aller assez bien.

de Ce fait montre qu'un "mauvais" objet est la cause profonde qui résulte dans le SIGSEGV segfault. La méthode assign () n'est pas virtuelle dans std::string, donc l'objet "bad" std::string ne provoquerait pas de segfault. Il doit y avoir une autre raison qui a finalement causé l'erreur de segmentation.

j'ai remarqué que le segfault vient de la fonction__gnu_CXX::__exchange _ et _ _ add (), alors j'ai regardé son code source dans cette page web:

00046   static inline _Atomic_word 
00047   __exchange_and_add(volatile _Atomic_word* __mem, int __val)
00048   { return __sync_fetch_and_add(__mem, __val); }

__change_et_add() appelle enfin l' __synchronisation_fetch_et_add(). Selon cette page web, le_ _ sync _ fetch_et _ add () est une fonction BUILTIN de GCC dont le comportement est comme ceci:

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;
}

ça Y est! Le pointeur ptr est déréférencé ici. Dans le programme 08/19/2011, le ptr est en fait le pointeur" this "De l'objet" bad " std::string dans la méthode assign (). C'est la défaillance à ce point qui a causé la faille de segmentation SIGSEGV.

Nous avons pu tester, avec le programme suivant:

#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;
}
14
demandé sur yaobin 2011-08-12 13:26:24

2 réponses

Il y a deux possibilités probables:

  • un code avant la ligne 798 a corrompu le local tmpTimeStamp l'objet
  • la valeur de retour de FormatTimeStamp() était en quelque sorte mauvais.

_GLIBCXX_FULLY_DYNAMIC_STRING est probablement un leurre et n'a rien à voir avec le problème.

Si vous installez debuginfo package libstdc++ (Je ne sais pas comment il s'appelle CentOS), vous serez en mesure de "voir dans" ce code, et pourrait être en mesure de dire si le côté gauche (LHS) ou le RHS de l'opérateur d'affectation a causé le problème.

Si ce n'est pas possible, vous aurez de débogage au niveau de l'assemblage. Allez dans image #2 et en faisant x/4x $ebp devrait vous donner précédent ebp, adresse de l'appelant (0x081402fc), LHS (doit correspondre &tmpTimeStamp image #3), et RHS. À partir de là, et bonne chance!

2
répondu Employed Russian 2011-08-12 15:06:15

je suppose qu'il pourrait y avoir un problème à l'intérieur de FormatTimeStamp fonction, mais sans code source, il est difficile de dire quoi que ce soit. Essayez de vérifier votre programme sous Valgrind. En général, cela aide à corriger ce genre d'insectes.

2
répondu ks1322 2011-08-12 14:52:28