4

現在、かなり大規模な C++ アプリケーション (CPU と RAM の使用量、およびコードの長さの点で大規模 - 100,000 行を超える) で、非常に奇妙なバグが発生しています。これは、デュアルコアの Sun Solaris 10 マシンで実行されています。このプログラムは株価フィードを購読し、ユーザーが設定した「ページ」に表示します (ページはユーザーがカスタマイズしたウィンドウ構造です - プログラムはユーザーがそのようなページを設定できるようにします)。このプログラムは、基盤となるライブラリの 1 つがマルチスレッドになるまで問題なく動作していました。これによって影響を受けるプログラムの部分は、それに応じて変更されました。私の問題に。

約 3 回の実行ごとに、プログラムは起動時にセグメンテーション違反を起こします。これは必ずしも難しいルールではありません。3 回続けてクラッシュし、5 回続けて動作することもあります。興味深いのはセグメンテーション違反です (読む: 痛い)。さまざまな形で現れる可能性がありますが、最も一般的なのは、関数 A が関数 B を呼び出し、関数 B に入るとフレーム ポインタが突然 0x000002 に設定されることです。機能 A:

   result_type emit(typename type_trait<T_arg1>::take _A_a1) const
     { return emitter_type::emit(impl_, _A_a1); }

これは単純なシグナルの実装です。impl_ と _A_a1 は、クラッシュ時のフレーム内で明確に定義されています。その命令を実際に実行すると、プログラム カウンター 0x000002 になります。

これは、その関数で常に発生するとは限りません。実際、かなりの数の場所で発生しますが、これはエラーの余地があまりない単純なケースの 1 つです。場合によっては、スタックに割り当てられた変数が理由もなく突然ジャンク メモリ (常に 0x000002) に置かれることがあります。また、同じコードが問題なく実行される場合もあります。それで、私の質問は、何がスタックをひどく壊すことができるのかということです. フレームポインタの値を実際に変更できるものは何ですか? 確かにそんな話は聞いたことがありません。私が考えることができる唯一のことは、配列に範囲外の書き込みをすることですが、それが発生した場合に発生するスタックプロテクターを使用して構築しました。ここでも、スタックの範囲内に収まっています。私もしない 各スレッドには独自のスタックがあるため (これはすべて pthread です)、別のスレッドが最初のスレッドのスタック上の変数を上書きする方法を確認してください。Linuxマシンでこれを構築しようとしましたが、セグメンテーション違反は発生しませんが、約3回に1回フリーズします。

4

14 に答える 14

9

スタックの破損、99.9% 間違いなく。

注意深く探す必要があるにおいは次のとおりです。

  • 「C」配列の使用
  • 'C' strcpy スタイル関数の使用
  • memcpy
  • mallocとフリー
  • ポインターを使用するあらゆるもののスレッドセーフ
  • 初期化されていない POD 変数。
  • ポインター演算
  • 参照によってローカル変数を返そうとする関数
于 2008-10-30T23:36:39.730 に答える
4

私は今日その正確な問題を抱えていてgdb、C配列の配列境界(私がそれを最も期待していなかった場所)を単に書いたことに気付く前に、ひざまずいて1時間まっすぐデバッグしていました。

したがって、可能であれば、vector代わりに s を使用してください。なぜなら、デバッグ モードでそれを試みると、適切な STL 実装が適切なコンパイラ メッセージを表示するからです (一方、C 配列は segfaults で罰せられます)。

于 2008-10-30T23:02:31.250 に答える
3

ここで、スタック オーバーフロースタック破損の間に混乱があります。

スタック オーバーフローは、オペレーティング システムがスレッドに割り当てたよりも多くのスタックを使用しようとすることによって発生する、非常に特殊な問題です。通常の3つの原因はこのようなものです。

void foo()
{
  foo();  // endless recursion - whoops!
}

void foo2()
{
  char myBuffer[A_VERY_BIG_NUMBER];  // The stack can't hold that much.
}

class bigObj
{
  char myBuffer[A_VERY_BIG_NUMBER];  
}

void foo2( bigObj big1)  // pass by value of a big object - whoops!
{
}

組み込みシステムでは、スレッド スタック サイズがバイト単位で測定される場合があり、単純な呼び出しシーケンスでさえ問題を引き起こす可能性があります。Windows のデフォルトでは、各スレッドは 1 メガバイトのスタックを取得するため、スタック オーバーフローが発生することはあまり一般的な問題ではありません。無限の再帰がない限り、スタック オーバーフローはスタック サイズを増やすことで常に軽減できますが、これは通常は最善の解決策ではありません。

スタックの破損とは、単純に、現在のスタック フレームの境界外に書き込みを行うことを意味し、その結果、他のデータが破損したり、スタック上のアドレスが返されたりする可能性があります。

それは最も簡単です: -

void foo()
{ 
  char message[10];

  message[10] = '!';  // whoops! beyond end of array
}
于 2008-10-31T17:16:21.913 に答える
3

あなたが言うように、あなたが「フレームポインター」と呼んでいるものはわかりません:

その命令を実際に実行すると、プログラム カウンター 0x000002 になります。

これにより、返信アドレスが破損しているように聞こえます。フレーム ポインターは、現在の関数呼び出しのコンテキストのスタック上の場所を指すポインターです。戻りアドレスを指している可能性があります (これは実装の詳細です) が、フレーム ポインター自体は戻りアドレスではありません。

本当に良い答えを得るには十分な情報がここにあるとは思いませんが、原因となる可能性のあるものは次のとおりです。

  • 呼び出し規約が正しくありません。関数のコンパイル方法とは異なる呼び出し規則を使用して関数を呼び出すと、スタックが破損する可能性があります。

  • RAMヒット。不正なポインターを介して書き込みを行うと、ガベージがスタックに蓄積される可能性があります。私は Solaris に詳しくありませんが、ほとんどのスレッド実装ではスレッドが同じプロセス アドレス空間にあるため、どのスレッドも他のスレッドのスタックにアクセスできます。スレッドが別のスレッドのスタックへのポインターを取得できる 1 つの方法は、ローカル変数のアドレスが、最終的に別のスレッドでポインターを処理する API に渡される場合です。物事を適切に同期しないと、ポインターが無効なデータにアクセスすることになります。「単純なシグナルの実装」を扱っていることを考えると、あるスレッドが別のスレッドにシグナルを送信している可能性があるようです。そのシグナルのパラメーターの 1 つに、ローカルへのポインターがあるのではないでしょうか?

于 2008-10-30T23:09:10.283 に答える
1

フレームポインタを壊すのは難しくありません-ルーチンの分解を見ると、ルーチンの開始時にプッシュされ、終了時にプルされていることがわかります-スタックを上書きすると、失われる可能性があります。スタックポインタはスタックが現在ある場所です-そしてフレームポインタはスタックが開始した場所です(現在のルーチンの場合)。

まず、すべてのライブラリと関連オブジェクトがクリーンに再構築され、すべてのコンパイラオプションが一貫していることを確認します-以前(Solaris 2.5)で、再構築されていないオブジェクトファイルが原因で同様の問題が発生しました。

それは上書きのように聞こえます-そしてそれが単に悪いオフセットであるならば、メモリの周りにガードブロックを置くことは役に立ちません。

各コアダンプの後で、コアファイルを調べて、障害間の類似点についてできる限り多くのことを学びます。次に、何が上書きされているかを特定してみてください。フレームポインタが最後のスタックポインタであることを覚えているので、現在のスタックフレームでフレームポインタの前に論理的に変更するべきではないので、これを記録して別の場所にコピーし、戻ったときに比較します。

于 2008-10-31T01:30:50.203 に答える
1

Valgrindを介して実行することは可能ですか? おそらく、Sun も同様のツールを提供しています。Intel VTune (実際には Thread Checker のことを考えていました) にも、スレッドのデバッグなどのための非常に優れたツールがいくつかあります。

あなたの雇用主がより高価なツールの費用を支払うことができるなら、彼らは本当にこの種の問題を解決するのをはるかに簡単にすることができます.

于 2008-10-31T00:03:56.820 に答える
1

C++ の初期化された変数と競合状態では、断続的なクラッシュの疑いがある可能性があります。

于 2008-10-30T23:38:31.787 に答える
1

これはスタック オーバーフローの問題のように思えます。何かが配列の境界を超えて書き込み、スタック上のスタック フレーム (およびおそらく戻りアドレスも) を踏みにじっています。このテーマに関する大きな文献があります。「The Shell Programmer's Guide」(第 2 版) には、役立つ SPARC の例があります。

于 2008-10-30T22:58:51.867 に答える
0

Valgrindを試してみましたが、残念ながらスタックエラーは検出されません。

「パフォーマンスの低下に加えて、Valgrindの重要な制限は、静的データまたはスタック割り当てデータの使用時に境界エラーを検出できないことです。」

私はこれがスタックオーバーフローの問題であることに同意する傾向があります。トリッキーなことはそれを追跡することです。私が言ったように、このことには100,000行以上のコードがあります(社内で開発されたカスタムライブラリを含む-その一部は1992年までさかのぼります)ので、誰かがそのようなものを捕まえるための良いトリックを持っているなら、私はありがたい。あらゆる場所で配列が処理されており、アプリはGUIにOIを使用しているため(OIについて聞いたことがない場合は、感謝してください)、論理的な誤謬を探すだけでも大変な作業であり、私の時間は短いです。

また、0x000002が疑わしいことに同意しました。これは、クラッシュ間の唯一の定数です。さらに奇妙なのは、これがマルチスレッドスイッチでのみ発生したという事実です。マルチスレッドの結果としてスタックが小さくなったことが、現在この問題を引き起こしていると思いますが、それは私の側の純粋な仮定です。

誰もこれを尋ねませんでしたが、私はgcc-4.2でビルドしました。また、ここでABIの安全性を保証できるので、それも問題ではありません。RAMヒットの「スタックの最後のガベージ」に関しては、それが普遍的に2であるという事実(コード内のさまざまな場所にありますが)は、ガベージがランダムになる傾向があることを疑わせます。

于 2008-10-31T00:49:40.987 に答える
0

また、0x000002が疑わしいことに同意しました。これは、クラッシュ間の唯一の定数です。さらに奇妙なのは、これがマルチスレッドスイッチでのみ発生したという事実です。マルチスレッドの結果としてスタックが小さくなったことが、現在この問題を引き起こしていると思いますが、それは私の側の純粋な仮定です。

参照またはアドレスによってスタックに何かを渡す場合、関数から最初のスレッドが返された後に別のスレッドがそれを使用しようとすると、これは間違いなく発生します。

アプリを単一のプロセッサに強制することで、これを再現できる場合があります。Sparcでそれをどのように行うのかわかりません。

于 2008-10-31T20:52:57.653 に答える
0

スタックの破損である可能性が高いという考えを支持します。マルチスレッド ライブラリへの切り替えによって、潜んでいるバグが露呈したのではないかと疑うようになったことを付け加えておきます。おそらく、バッファ オーバーフローのシーケンシングが未使用のメモリで発生していた可能性があります。現在、別のスレッドのスタックにヒットしています。他にも多くのシナリオが考えられます。

それがそれを見つける方法のヒントをあまり与えていない場合は申し訳ありません。

于 2008-10-30T23:21:22.990 に答える
0

変数に 2 の値を代入することは意味がありますが、代わりにそのアドレスを 2 に代入していますか?

他の詳細は私にはわかりませんが、「2」は問題の説明で繰り返されるテーマです。;)

于 2008-10-30T22:59:39.523 に答える
0

これは間違いなく、範囲外の配列またはバッファの書き込みによるスタックの破損のように聞こえます。スタック プロテクターは、書き込みがランダムではなくシーケンシャルである限り有効です。

于 2008-10-30T23:06:41.913 に答える