7

現在、OSSアプリケーションに例外と例外処理を追加する作業を行っています。例外は最初から一般的な考え方でしたが、私は優れた例外フレームワークを見つけたいと思っていました。正直なところ、C ++の例外処理の規則とイディオムを、使用を開始する前にもう少しよく理解してください。私は、C#/。Net、Python、および例外を使用するその他の言語について多くの経験があります。私はそのアイデアに不思議ではありません(しかし、マスターからはほど遠いです)。

C#とPythonでは、未処理の例外が発生すると、ユーザーは優れたスタックトレースを取得し、一般に非常に有用な貴重なデバッグ情報を多数取得します。OSSアプリケーションで作業している場合、ユーザーにその情報を問題レポートに貼り付けることは...まあ、それなしでは生きていけないと思っているとだけ言っておきましょう。このC++プロジェクトでは、「アプリケーションがクラッシュしました」、またはより多くの情報を持っているユーザーから「X、Y、Zを実行した後、クラッシュしました」というメッセージが表示されます。しかし、私もそのデバッグ情報が欲しいです!

クロスプラットフォームとクロスコンパイラの方法でC++例外スタックトレースを取得する方法は見当たらないという事実で、私はすでに(そして非常に困難に)安心していますが、関数名などを取得できることはわかっています。関連情報。

そして今、私は未処理の例外のためにそれを望んでいます。私はboost::exceptionを使用していますが、この非常に優れたdiagnostic_information thingsamajigを使用して、(マングルされていない)関数名、ファイル、行、そして最も重要なことに、プログラマーがその例外に追加したその他の例外固有の情報を出力できます。

当然、可能な限りコード内で例外を処理しますが、カップルをすり抜けさせないようにするのはそれほど単純ではありません(もちろん、意図せずに)。

したがって、私がやりたいのは、メインのエントリポイントをtryブロック内にラップcatchして、アプリケーションでエラーが発生したことをユーザーに通知する特別なダイアログを作成し、ユーザーが[詳細]または[デバッグ]をクリックすると、より詳細な情報が表示されるようにすることです。情報」など。これには、diagnostic_informationの文字列が含まれます。次に、この情報を問題レポートに貼り付けるようにユーザーに指示できます。

しかし、しつこい腸の感覚は、すべてをtryブロックでラップすることは本当に悪い考えだと私に言っています。私がやろうとしていることは愚かですか?もしそうなら(そしてそうでなくても)、私が望むことを達成するためのより良い方法は何ですか?

4

4 に答える 4

33

main()にtry / catchブロックを入れても問題ありませんが、問題は発生しません。とにかく、プログラムは未処理の例外で停止しています。ただし、非常に重要なスタックトレースを取得することは、あなたの探求にはまったく役立ちません。キャッチブロックが例外をトラップするとき、その情報はgonzoです。

C++例外をキャッチすることもあまり役に立ちません。std::exceptionから派生した例外でプログラムが停止する確率はかなり低いです。それが起こる可能性がありますが。C / C ++アプリで発生する可能性がはるかに高いのは、ハードウェア例外による死亡であり、AccessViolationはnumerounoです。それらをトラップするには、main()メソッドに__tryキーワードと__exceptキーワードが必要です。繰り返しになりますが、使用できるコンテキストはほとんどなく、基本的に例外コードしかありません。AVは、どの正確なメモリ位置が例外を引き起こしたかも教えてくれます。

これは単なるクロスプラットフォームの問題ではなく、どのプラットフォームでも適切なスタックトレースを取得することはできません。スタックを歩く信頼できる方法はありません。これを危険な旅にする最適化(フレームポインターの省略など)が多すぎます。これはC/C ++の方法です。できるだけ速くし、爆発したときに何が起こったのか見当がつかないようにします。

あなたがする必要があるのは、C /C++の方法でこれらの種類の問題をデバッグすることです。ミニダンプを作成する必要があります。これは、例外が発生したときのプロセスイメージのスナップショットである古い「コアダンプ」にほぼ類似しています。当時、あなたは実際にコアの完全なダンプを取得しました。進歩はありましたが、現在は「ミニ」です。完全なコアダンプには2ギガバイト近くかかるため、ある程度必要です。実際には、プログラムの状態を診断するのに非常にうまく機能します。

Windowsでは、SetUnhandledExceptionFilter()を呼び出すことから始まり、プログラムが未処理の例外で終了したときに実行される関数へのコールバック関数ポインターを提供します。例外、C++およびSEH。次のリソースはdbghelp.dllで、Windows用のデバッグツールのダウンロードで入手できます。MiniDumpWriteDump()というエントリポイントがあり、ミニダンプを作成します。

MiniDumpWriteDump()によって作成されたファイルを取得すると、かなり黄金色になります。.dmpファイルは、プロジェクトのようにVisualStudioに読み込むことができます。F5キーを押すと、VSは、プロセスでロードされたDLLの.pdbファイルをロードしようとしばらくの間グラインドします。シンボルサーバーをセットアップする必要があります。これは、適切なスタックトレースを取得するために非常に重要です。すべてが機能する場合は、例外がスローされた正確な場所で「デバッグブレーク」が発生します。スタックトレースを使用します。

これをスムーズに機能させるために必要なこと:

  • ビルドサーバーを使用してバイナリを作成します。デバッグシンボル(.pdbファイル)をシンボルサーバーにプッシュして、ミニダンプをデバッグするときにすぐに利用できるようにする必要があります。
  • すべてのモジュールのデバッグシンボルを検出できるようにデバッガーを構成します。WindowsのデバッグシンボルはMicrosoftから入手できます。コードのシンボルは、上記のシンボルサーバーから取得する必要があります。
  • 未処理の例外をトラップしてミニダンプを作成するコードを記述します。SetUnhandledExceptionFilter()について説明しましたが、ミニダンプを作成するコードは、クラッシュしたプログラムに含まれていてはなりません。ミニダンプを正常に書き込むことができる可能性はかなり低く、プログラムの状態は未定です。最善の方法は、名前付きのMutexを監視する「ガード」プロセスを実行することです。例外フィルターでミューテックスを設定でき、ガードでミニダンプを作成できます。
  • ミニダンプをクライアントのマシンから自分のマシンに転送する方法を作成します。そのためにAmazonのS3サービスをリーズナブルなレートで使用しています。
  • ミニダンプハンドラーをデバッグデータベースに接続します。Jiraを使用します。これには、同じ「署名」を持つ以前のクラッシュのデータベースに対してクラッシュバケットを検証できるWebサービスがあります。一意である場合、またはヒット数が十分でない場合は、クラッシュマネージャーコードにミニダンプをAmazonにアップロードして、バグデータベースエントリを作成するように依頼します。

まあ、それは私が働いている会社のために私がしたことです。非常にうまく機能し、クラッシュバケットの頻度を数千から数十に減らしました。オープンソースのffdshowコンポーネントの作成者への個人的なメッセージ:私は情熱を持ってあなたを憎みます。しかし、あなたはもう私たちのアプリをクラッシュさせることはありません!バガー。

于 2009-12-26T22:48:11.430 に答える
3

すべてのコードを1つのtry/catchブロックにラップすることは問題ありません。たとえば、内部の実行が遅くなることはありません。実際、私のすべてのプログラムには、このフレームワーク(に類似したコード)があります。

int execute(int pArgc, char *pArgv[])
{
    // do stuff
}

int main(int pArgc, char *pArgv[])
{
    // maybe setup some debug stuff,
    // like splitting cerr to log.txt

    try
    {
        return execute(pArgc, pArgv);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Unhandled exception:\n" << e.what() << std::endl;
        // or other methods of displaying an error

        return EXIT_FAILURE;
    }
    catch (...)
    {
        std::cerr << "Unknown exception!" << std::endl;

        return EXIT_FAILURE;
    }
}
于 2009-12-26T22:04:22.517 に答える
1

いいえ、それは愚かではありません。これは非常に良い考えであり、もちろん、未処理の例外が発生するまで、実行時に実質的に費用はかかりません。

OSによって提供されるスレッドをラップする例外ハンドラーがすでに存在することに注意してください(そして別のハンドラーはCランタイムによって提供されると思います)。正しい動作を得るには、これらのハンドラーに特定の例外を渡す必要がある場合があります。一部のアーキテクチャでは、ミスアライメントされたデータへのアクセスは例外ハンドラによって処理されます。したがって、特殊なケースEXCEPTION_DATATYPE_MISALIGNMENTを使用して、それをより高いレベルの例外ハンドラーに渡すことができます。

レジスター、アプリのバージョンとビルド番号、例外タイプ、スタックダンプを、コードのアドレスとなる可能性のある16進値のモジュール名とオフセットで注釈が付けられた16進で含めます。exeのバージョン番号とビルド番号/日付を必ず含めてください。

VirtualQueryスタック値を「ModuleName+Offset」に非常に簡単に変換するために使用することもできます。そして、それを.MAPファイルと組み合わせると、クラッシュした場所が正確にわかることがよくあります。

ベータテスターをトレーニングしてテキストを簡単に送信できることがわかりましたが、初期の頃は、テキストではなくエラーダイアログの画像を取得していました。これは、編集コントロールを右クリックして[すべて選択]と[コピー]のメニューを表示できることを多くのユーザーが知らないためだと思います。もう一度やり直す場合は、そのテキストをクリップボードにコピーするボタンを追加して、メールに簡単に貼り付けることができるようにします。

「エラーレポートの送信」ボタンが必要な場合はさらに便利ですが、ユーザーにテキストを自分の電子メールに取り込む方法を提供するだけで、ほとんどの方法でそこに到達でき、危険信号は発生しません。 「私は彼らとどのような情報を共有していますか?」について

于 2009-12-26T22:32:28.683 に答える
0

実際、boost :: Diagnostics_informationは、「グローバル」catch(...)ブロックで使用するように特別に設計されており、到達してはならない例外に関する情報を表示します。ただし、boost::diagnostic_informationによって返される文字列はユーザ​​ーフレンドリーではないことに注意してください。

于 2010-01-14T07:34:18.063 に答える