18

私のプログラムは最近、実行中に奇妙なセグメンテーション違反に遭遇しました。 誰かが以前にこのエラーに遭遇したことがあるかどうか、そしてそれをどのように修正できるか知りたいです。 詳細は次のとおりです。

基本情報:

  • CentOS 5.2、カーネルバージョンは2.6.18です
  • g ++(GCC)4.1.2 20080704(Red Hat 4.1.2-50)
  • CPU:Intelx86ファミリー
  • libstdc++。so.6.0.8
  • 私のプログラムは、データを処理するために複数のスレッドを開始します。スレッドの1つでセグメンテーション違反が発生しました。
  • マルチスレッドプログラムですが、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)

このような文字列代入操作は、一般的に使用される操作の一種であると思いますが、ここでセグメンテーション違反が発生する理由がわかりません。

私が調査したこと:

私は答えをウェブで検索しました。ここを見ました。応答には、_GLIBCXX_FULLY_DYNAMIC_STRINGマクロを定義してプログラムを再コンパイルしてみてくださいと書かれています。試しましたが、それでもクラッシュが発生します。

私もここを見ました。また、_GLIBCXX_FULLY_DYNAMIC_STRINGを使用してプログラムを再コンパイルするように指示されていますが、作成者は私の別の問題を扱っているようです。したがって、彼の解決策は私にはうまくいかないと思います。


2011年8月15日に更新

これがこの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;
}

2011年8月19日に更新

この問題は最終的に解決され、修正されました。FormatTimeStamp()関数は、実際には根本的な原因とは何の関係もありません。セグメンテーション違反は、ローカル文字バッファの書き込みオーバーフローが原因で発生します。

この問題は、次のより単純なプログラムで再現できます(今のところ、いくつかの変数の間違った名前を無視してください)。

(「g ++ -Wall -gmain.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;
}

先に進む前に覚えておくべき2つの事実があります:1)。私のマシンはIntelx86マシンであるため、リトルエンディアンルールを使用しています。したがって、値がたとえば10であるint型の変数 "m"の場合、メモリレイアウトは次のようになります。

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

2)。上記のプログラムはメインスレッド内で実行されます。overlay_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()のカウンターであり、その値はforループごとに1ずつ増加し、最大値は6以下であると想定されます。したがって、その値はm(byte#1)(リトルエンディアンであることを思い出してください)に完全に格納できます。たまたま臨時雇用者3です。

2)。バグのある行:tが109などの3桁の整数の場合、数値109を文字列「109」にシリアル化するには実際には4バイトが必要なため、sprintf()呼び出しはバッファオーバーフローを引き起こします:「1」 、「0」、「9」および終了する「\0」。temp []は3バイトのみで割り当てられるため、最後の'\0'は間違いなくtemp3に書き込まれます。これは、残念ながらmの値を格納するm(byte#1)にすぎません。その結果、mの値は毎回0にリセットされます。

3)。ただし、プログラマーの予想では、overflow_it()のforループは6回だけ実行され、各時間mは1ずつ増加します。mは常に0にリセットされるため、実際のループ時間は6回をはるかに超えます。

4)。overlay_it()の変数iを見てみましょう。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()メソッドの呼び出しが最終的にセグメンテーション違反を引き起こす理由です。


2011年8月26日に更新

2011年8月19日の前回の更新で、セグメンテーション違反は、メモリレイアウトが壊れて「破壊された」オブジェクトになった、ローカルの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セグメンテーションフォールトを引き起こす根本的な原因ではないことを示しています。Assign()メソッドはstd :: stringで仮想化されていないため、「不正な」std::stringオブジェクトによってセグメンテーション違反が発生することはありません。最終的にセグメンテーション違反を引き起こした他の理由があるに違いありません。

セグメンテーション違反は__gnu_cxx::__ exchange_and_add()関数からのものであることに気付いたので、次のWebページでそのソースコードを調べました。

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()を呼び出します。このWebページによると、__ 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ポインタはここで逆参照されます。2011年8月19日のプログラムでは、ptrは、実際には、assign()メソッド内の「不良」std::stringオブジェクトの「this」ポインタです。実際に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;
}
4

2 に答える 2

2

関数内に問題があるのか​​もしれませんFormatTimeStampが、ソースコードがなければなんとも言えません。Valgrind でプログラムをチェックしてみてください。通常、これはそのような種類のバグを修正するのに役立ちます。

于 2011-08-12T14:52:28.267 に答える
2

次の 2 つの可能性があります。

  • 798 行目より前のコードでローカルtmpTimeStamp オブジェクトが破損している
  • からの戻り値FormatTimeStamp()がなんとなく悪かった。

これ_GLIBCXX_FULLY_DYNAMIC_STRINGはおそらくニシンであり、問​​題とは何の関係もありません。

debuginfoパッケージをインストールするとlibstdc++(CentOS で何と呼ばれているかはわかりません)、そのコードを「見る」ことができ、左側 (LHS) と右側 (RHS) のどちらであるかを判断できる場合があります。代入演算子が原因で問題が発生しました。

それが不可能な場合は、アセンブリ レベルでこれをデバッグする必要があります。フレーム#2に入って実行x/4x $ebpすると、以前ebpの 、発信者アドレス ( 0x081402fc)、LHS (&tmpTimeStampフレーム内で一致する必要があります#3)、および RHS が表示されます。そこから行って、頑張ってください!

于 2011-08-12T15:06:15.937 に答える