プログラムはリリースビルドとしてのみクラッシュします—デバッグ方法は?


95

ここに「Schroedinger's Cat」タイプの問題があります-私のプログラム(実際には私のプログラムのテストスイートですが、それでもプログラム)がクラッシュしますが、組み込みのリリースモードで、コマンドラインから起動したときのみです。穴居人のデバッグ(つまり、いたるところに厄介なprintf()メッセージ)を通じて、コードがクラッシュしているテストメソッドを特定しましたが、残念ながら、実際のクラッシュはいくつかのデストラクタで発生しているようです。クリーンに実行される他のデストラクタ。

Visual Studio内でこのプログラムを実行しようとしても、クラッシュしません。WinDbg.exeから起動した場合も同様です。クラッシュは、コマンドラインから起動した場合にのみ発生します。これはWindows Vistaで起こっています。残念ながら、現在テストするためのXPマシンにアクセスできません。

私はWindowsのスタックトレース、またはプリントアウトしてもらうことができれば、それは本当にいいですね何かそれがきれいに出るたかのように、単にプログラムを終了以外を。ここでもっと意味のある情報を入手して、うまくいけばこのバグを修正する方法について誰かアドバイスはありますか?

編集:問題は確かに範囲外の配列が原因でした。これについては、この投稿で詳しく説明します。この問題の発見にご協力いただき、ありがとうございました!


そのテスト方法のサンプルを教えてもらえますか?
akalenuk 2008年

申し訳ありませんが、コードは複雑すぎてここに簡単に貼り付けることができません。前述したように、テストメソッド自体では発生せず、後でデストラクタで発生します。ただし、このメソッドには、初期化されていないポインタなどはありません。
Nik Reiman、

3
ほとんどの答えは推測に過ぎません。デバッガーをアタッチせずにクラッシュするリリースビルドを分析するには、いくつかの一般的な手法があります。stackoverflow.com
Sebastian

多分それはあなたのせいではあり
ブレントブラッドバーン2017

回答:


127

CまたはC ++プログラムはデバッガーで正常に実行されますが、外部で実行すると失敗するという、私が見たり聞いたりしたケースの100%で、原因は関数のローカル配列の終わりを超えて書き込まれています。(デバッガーはスタックに追加するため、重要なものを上書きする可能性は低くなります。)


31
誰かがこの男に葉巻を与えます!私の場合、P / Invoke関数に十分な容量がないStringBuilderを渡していました。眠っているときに誰かが魔法のマーカーで顔に書いているようなものだと思います:デバッガの下では、額に落書きができるので気付かないでしょうが、デバッガがなければ、最終的に目...そのようなもの。このヒントをありがとう!
ニコラスピアセッキ

1
私の場合、Obj-Cを使用するARMプロセッサでのアライメントの問題であることが判明しました。
アルモ2013年

1
11年経った今でもこれは真実です...あなたのベクターを予約することを忘れないでください。
David Tran

1
では、実際にデバッグできるように、デバッグモードの動作をどのように変更するのでしょうか。
ポールチャイルズ

1
「これでどこを見ればわかるか」が、デバッグで機能するすべてがどのように問題がどこにあるかを教えてくれます。ほとんどの場合、あなたの答えは正しいと思いますが、何を探すべきかを知ることは良い出発点ですが、問題がどこにあるかを正確に特定するために大規模なコードベースを調査することは、法外に費用がかかる可能性があります。
ポールチャイルズ

54

以前にこのような問題が発生したときは、一般に変数の初期化が原因でした。デバッグモードでは、変数とポインタは自動的にゼロに初期化されますが、リリースモードでは初期化されません。したがって、このようなコードがある場合

int* p;
....
if (p == 0) { // do stuff }

デバッグモードでは、if内のコードは実行されませんが、リリースモードではpに未定義の値が含まれるため、0になる可能性は低いため、コードが実行されてクラッシュが発生することがよくあります。

初期化されていない変数についてコードをチェックします。これは、配列の内容にも適用できます。


典型的なケースは、コンストラクターのメンバー初期化リスト(の1つ)にメンバー変数を置くのを忘れていることです。同じ効果がありますが、適切なメンバーの初期化も探す必要があることを知らない場合、見つけるのは困難です。
steffenj 2008年

1
デバッグモードでは、変数は通常、変数がどのような状態にあるかを示すためにデバッグで使用できる「コンパイラー定義定数」に初期化されます。例:ポインターNULLまたは0xDeadBeefが一般的です。
マーティンヨーク

通常、デバッグランタイムはメモリをゼロ以外の値に初期化します。これにより、特にNULLポインターテストにより、コードがポインターがNULLでないかのように動作します。それ以外の場合、リリースモードをクラッシュさせるデバッグモードで正しく実行されるコードがあります。
マイケル・バー、

1
いいえ、変数はまったく初期化されず、割り当てられるまで変数を「使用」することはUBのままです。ただし、基になるメモリの内容には、0x0000000、0xDEADBEEF、またはその他の認識可能なパターンがあらかじめ入力されていることがよくあります。
オービットでの軽さのレース2011年

26

これまでのところ、リリースアプリケーションのデバッグに使用できる手法について真剣に概説しようとした回答はありません。

  1. リリースビルドとデバッグビルドの動作は、さまざまな理由で異なります。 ここに優れた概要があります。これらの違いのそれぞれが、デバッグビルドには存在しないリリースビルドのバグを引き起こす可能性があります。

  2. デバッガの存在は、リリースビルドとデバッグビルドの両方で、プログラムの動作も変更する可能性がありますこの答えを見てください。要するに、少なくともVisual Studio Debuggerは、プログラムにアタッチされると自動的にデバッグヒープを使用します。環境変数_NO_DEBUG_HEAPを使用して、デバッグヒープをオフにすることができます。これは、コンピューターのプロパティ、またはVisual Studioのプロジェクト設定で指定できます。これにより、デバッガーを接続した状態でクラッシュを再現できる場合があります。

    ヒープ破損のデバッグについて詳しくは、こちらをご覧ください。

  3. 前のソリューションが機能しない場合は、未処理の例外をキャッチし、クラッシュが発生したインスタンスに事後デバッガをアタッチする必要があります。これに、たとえばWinDbgを使用できます。MSDNで入手可能な事後分析デバッガとそのインストールに関する詳細

  4. 例外処理コードを改善できます。これが本番アプリケーションである場合は、次のことを行う必要があります。

    a。を使用してカスタム終了ハンドラーをインストールするstd::set_terminate

    この問題をローカルでデバッグする場合は、終了ハンドラー内でエンドレスループを実行し、テキストがコンソールに出力std::terminateされて、呼び出されたことを通知できます。次に、デバッガーを接続し、呼び出しスタックを確認します。 または、この回答で説明されているようにスタックトレースを出力します。

    本番環境のアプリケーションでは、エラーレポートを、理想的にはここで説明するように問題を分析できる小さなメモリダンプと一緒に送りたいと思うかもしれません

    b。ハードウェアとソフトウェアの両方の例外をキャッチできる、Microsoftの構造化例外処理メカニズムを使用します。MSDNを参照してください。SEHを使用してコードの一部を保護し、a)と同じアプローチを使用して問題をデバッグできます。SEHは、プロダクションアプリからエラーレポートを送信するときに使用できる、発生した例外に関する詳細情報を提供します。


16

注意点:

配列オーバーラン-Visual Studioデバッガーは、クラッシュを停止する可能性のあるパディングを挿入します。

競合状態-アプリケーションが直接実行されたときにのみ競合状態が表示される場合、複数のスレッドが関与していますか。

リンク-正しいライブラリを取り込むリリースビルドです。

試すこと:

ミニダンプ-本当に使いやすい(msdnで調べるだけ)と、各スレッドの完全なクラッシュダンプが得られます。出力をVisual Studioにロードするだけで、クラッシュ時にデバッグしているかのようになります。


1
こんにちは-私はこの回答に対して匿名の反対票を投じました。理由を知りたいのですが?
morechilli 2013

12

WinDbgを事後デバッガとして設定できます。これにより、デバッガーが起動し、クラッシュが発生したときにプロセスにアタッチされます。事後デバッグ用にWinDbgをインストールするには、/ Iオプションを使用します(大文字で表記されていることに注意してください)。

windbg /I

詳細はこちら

原因については、他の回答が示唆しているように、それはおそらく統一された変数です。


2
デフォルトではありませんが、リリースビルドの場合でも、コンパイラーでPDBファイルを生成できることを忘れないでください。
マイケル・バー、

質問に対する唯一の本当の答えは本当に。
セバスチャン

10

何時間ものデバッグの後、最終的に問題の原因を発見しました。これは確かにバッファオーバーフローが原因で、1バイトの違いが発生しました。

char *end = static_cast<char*>(attr->data) + attr->dataSize;

これはフェンスポストエラー(1つずれたエラー)であり、次の方法で修正されました。

char *end = static_cast<char*>(attr->data) + attr->dataSize - 1;

奇妙なことに、コードのさまざまな部分の周りに_CrtCheckMemory()を数回呼び出し、それらは常に1を返しました。「return false」を配置することで問題の原因を見つけることができました。テストケースを呼び出し、最終的に試行錯誤により、どこに障害があったかを判断します。

皆さんのコメントありがとうございます-今日、windbg.exeについて多くのことを学びました!:)


8
今日、私は同様の問題をデバッグしていて、_CrtCheckMemory()は常に1を返していました。
Brian Morearty、2011

7

exeをリリース版としてビルドした場合でも、スタックトレースを可能にするPDB(プログラムデータベース)ファイルを生成し、限られた量の変数検査を実行できます。ビルド設定には、PDBファイルを作成するオプションがあります。これをオンにして再リンクします。次に、まずIDEから実行してみて、クラッシュするかどうかを確認します。もしそうなら、それから素晴らしい-あなたはすべてのものを見る準備ができています。そうでない場合、コマンドラインから実行すると、次の2つのいずれかを実行できます。

  1. EXEを実行し、クラッシュの前にAttach To Process(Visual Studioの[ツール]メニュー)を実行します。
  2. クラッシュ後、デバッガを起動するオプションを選択します。

PDBファイルをポイントするように求められたら、参照してそれらを見つけます。PDBがEXEまたはDLLと同じ出力フォルダーに置かれた場合、それらはおそらく自動的に選択されます。

PDBはソースへのリンクを提供し、スタックトレースや変数などを確認できるようにするための十分なシンボル情報を提供します。値は通常どおりに検査できますが、最適化パスが意味するのは単なる誤りであるため、誤った読み取りが行われる可能性があることに注意してください。レジスタに表示されるか、予期しない順序で発生します。

注意:ここではWindows / Visual Studio環境を想定しています。


3

IDEは通常、初期化されていない変数の内容をゼロ、null、またはその他の「賢明な」値に設定するため、このようなクラッシュはほとんど常に発生します。一方、ネイティブで実行すると、システムが拾ったランダムなゴミが表示されます。

したがって、エラーはほぼ確実に、適切に初期化される前にポインターを使用しているようなものを使用していて、それが危険な場所を指し示していないためにIDEでそれを回避している-または値がエラーチェック-しかしリリースモードではそれは厄介なことをします。


3

分析できるクラッシュダンプを取得するには:

  1. コードのpdbファイルを生成します。
  2. exeとdllが同じアドレスにロードされるようにリベースします。
  3. ワトソン博士などの事後分析デバッガを有効にする
  4. クラッシュファインダーなどのツールを使用して、クラッシュの失敗のアドレスを確認します。

また、Windowsのデバッグツールのツールも確認してください。アプリケーションを監視して、2回目の例外の前に発生したすべての1回目の例外を確認できます。

それが役に立てば幸い...


3

このようなエラーをデバッグする優れた方法は、デバッグビルドの最適化を有効にすることです。


2

アプリがあなたと同じように動作したときに問題が発生しました。これは、sprintfの厄介なバッファオーバーランであることが判明しました。当然、デバッガを接続して実行すると機能しました。私がしたことは、ハンドルされていない例外フィルター(SetUnhandledExceptionFilter)をインストールすることで、無限に(タイムアウト値がINFINITEの偽のハンドルでWaitForSingleObjectを使用して)ブロックしただけです。

だからあなたは次の線に沿って何かをすることができます:

長い__stdcall MyFilter(EXCEPTION_POINTERS *)
{
    ハンドルhEvt = :: CreateEventW(0,1,0,0);
    if(hEvt)
    {
        if(WAIT_FAILED == :: WaitForSingleObject(hEvt、INFINITE))
        {
            //ログの失敗
        }
    }

}
// wmain / WinMainのどこかに:
SetUnhandledExceptionFilter(MyFilter);

バグが発生した後、デバッガーを接続しました(GUIプログラムが応答を停止しました)。

次に、ダンプを取得して後で処理することができます。

.dump / ma path_to_dump_file

または、すぐにデバッグします。最も簡単な方法は、ランタイム例外処理機構によってプロセッサコンテキストが保存された場所を追跡することです。

sd esp 範囲 1003f

コマンドは、検索の長さを指定して、CONTEXTレコードのスタックアドレス空間を検索します。私は通常'l?10000'のようなものを使用します。注意してほしいのは、普段手がけていない例外フィルターフレームの近くにあるレコードなので、異常に大きな数値を使用しないでください。1003fは、プロセッサの状態をキャプチャするために使用されるフラグの組み合わせです(CONTEXT_FULLに対応すると思います)。検索は次のようになります。

0:000> sd esp l1000 1003f
0012c160 0001003f 00000000 00000000 00000000?...............

結果が返されたら、cxrコマンドでアドレスを使用します。

.cxr 0012c160

これにより、クラッシュ時に正確にこの新しいCONTEXTに移動します(アプリがクラッシュしたときにスタックトレースを正確に取得します)。さらに、以下を使用します。

.exr -1

発生した例外を正確に見つける。

それが役に立てば幸い。


2

重要な操作を「assert」マクロ内にラップしたために、これが発生する場合があります。ご存知かもしれませんが、「assert」はデバッグモードでのみ式を評価します。


1

診断情報の取得に関する問題について、WinDbg.exeの代わりにadplus.vbsを使用してみましたか?実行中のプロセスにアタッチするには、次を使用します

adplus.vbs -crash -p <process_id>

または、クラッシュがすぐに発生した場合にアプリケーションを起動するには:

adplus.vbs -crash -sc your_app.exe

adplus.vbsの詳細については、http//support.microsoft.com/kb/286350を参照してください。


1

デバッガーが接続されたNtdll.dll

コマンドライン/デスクトップから起動するのではなく、IDEまたはWinDbgからプログラムを起動する場合の違いの1つは、ntdll.dllがデバッガを接続して起動するときに、少し検証を実行する別のヒープ実装を使用することです。メモリの割り当て/解放。

ntdll.dllの予期ないユーザーブレークポイントで関連情報を読み取ることができます。問題を特定するのに役立つ可能性のあるツールの1つは、PageHeap.exeです。

クラッシュ分析

あなたが経験している「クラッシュ」とは何かを書いていない。プログラムがクラッシュし、エラー情報をMicrosoftに送信するように求められると、技術情報をクリックして少なくとも例外コードを確認できるようになります。努力すれば、事後分析も実行できます(Heisenbugを参照):WinApiプログラムが一部のコンピューターでクラッシュする


1

Vista SP1には、実際にはシステムに組み込まれた非常に優れたクラッシュダンプジェネレーターがあります。残念ながら、それはデフォルトではオンになっていません!

この記事を参照してください:http : //msdn.microsoft.com/en-us/library/bb787181(VS.85).aspx

このアプローチの利点は、影響を受けるシステムに追加のソフトウェアをインストールする必要がないことです。握って、握って、ベイビー!


1

私の経験として、それはほとんどメモリ破損の問題です。

例えば ​​:

char a[8];
memset(&a[0], 0, 16);

: /*use array a doing some thing */

コードを実行すると、デバッグモードで正常になる可能性が非常に高くなります。

しかし、リリースでは、クラッシュする可能性があります。

私にとって、記憶が範囲外である場所をくまなく探すことは、あまりにも厄介です。

Visual Leak Detector(windows)やvalgrind(linux)などのツールを使用する方が賢明です。


1

私は正しい答えをたくさん見ました。しかし、私を助けてくれたものはありません。私の場合、アライメントされていないメモリでSSE命令が誤って使用されていました。数学ライブラリ(使用している場合)を確認し、SIMDサポートを無効にして、クラッシュを再コンパイルして再現します。

例:

プロジェクトにはmathfuが含まれており、STL vector:std :: vector <mathfu :: vec2>のクラスを使用します。STLのデフォルトのアロケーターは必要な16バイトのアライメントを保証しないため、このような使用法はmathfu :: vec2アイテムの構築時にクラッシュを引き起こす可能性があります。この場合、アイデアを証明するため#define MATHFU_COMPILE_WITHOUT_SIMD_SUPPORT 1に、mathfuの各インクルードの前に定義し、リリース構成で再コンパイルして、再度確認できます。

デバッグRelWithDebInfo構成は私のプロジェクトのためによく働いたが、いないリリース 1。この動作の背後にある理由は、おそらくデバッガが割り当て/割り当て解除要求を処理し、メモリへのアクセスをチェックおよび確認するためにメモリ管理を行うためです。

Visual Studio 2015および2017環境で状況を経験しました。


0

似たようなことがGCCで一度起こりました。最終的なリリースを作成するときにのみ有効になり、開発プロセス中に有効にならない非常に積極的な最適化であることがわかりました。

まあ、正直に言うと、コードがその特定の最適化が行われなかったという事実に依存していることに気づかなかったので、それはgccではなく、私のせいでした。

たどるのにかなりの時間を要し、ニュースグループに質問して誰かに考えさせられたので、たどり着きました。それで、これがあなたにも起こっている場合に備えて、私は賛成を返します。


0

私は、この発見したこの記事のシナリオに便利。ISTRコンパイラオプションは少し時代遅れでした。Visual Studioプロジェクトオプションを調べて、リリースビルドなどのpdbファイルを生成する方法を確認します。


0

デバッガーの内部ではなく外部で発生するのではないかと疑っています。通常、デバッガで実行してもアプリケーションの動作は変わりません。コンソールとIDEの環境の違いを確認します。また、明らかに、最適化なしでデバッグ情報を使用してリリースをコンパイルし、それが動作に影響するかどうかを確認します。最後に、他の人々がここで提案した事後のデバッグツールをチェックしてください。通常、それらから手掛かりを得ることができます。


0

コードの行が実行されているように見える順序を変更する最適化により、リリースビルドのデバッグは面倒な場合があります。それは本当に混乱する可能性があります!

問題を少なくとも絞り込む1つの手法は、MessageBox()を使用して、コードがプログラムのどの部分に到達したかを示す簡単なステートメントを表示することです( "Starting Foo()"、 "Starting Foo2()"); 疑わしいコードの領域の関数の先頭に配置します(クラッシュしたときに何をしていましたか?)。どの関数かわかる場合は、メッセージボックスをコードのブロックに変更するか、関数内の個々の行にさえ変更して、数行に絞り込みます。次に、変数の値の出力を開始して、クラッシュした時点での変数の状態を確認できます。


彼はすでにprintfsを散りばめているので、メッセージボックスは勝ちません。パーティーに新しいものを持ち込むことはありません。
Greg Whitfield、

0

_CrtCheckMemory()を使用して、割り当てられたメモリの状態を確認してください。すべてがうまくいった場合、_CrtCheckMemoryTRUEを返し、そうでなければFALSEを返します


0

グローバルフラグを有効にしてソフトウェアを実行することもできます(Debugging Tools for Windowsをご覧ください)。多くの場合、問題を解決するのに役立ちます。


0

例外が発生したときにプログラムにミニダンプを生成させ、それをデバッガー(WinDbgなど)で開きます。注目する主な機能:MiniDumpWriteDump、SetUnhandledExceptionFilter


0

これは、誰かが有益だと思うかもしれないというケースでした。Qt Creatorのリリースでのみクラッシュしました-デバッグではクラッシュしませんでした。私は.iniファイルを使用していました(他のドライブにコピーできるアプリよりも、レジストリが破損すると設定が失われるアプリのほうが好きなので)。これは、アプリのディレクトリツリーの下に設定を保存するすべてのアプリに適用されます。デバッグビルドとリリースビルドが異なるディレクトリにある場合、それらの間で異なる設定を持つこともできます。他のチェックインされていないものを優先してチェックインしました。それが私のクラッシュの原因であることが判明しました。良いことを見つけました。

言いたくないのですが、クラッシュはMS Visual Studio Community Editionでしか診断できませんでした。VSをインストールした後、アプリをQt Creatorでクラッシュさせ、Visual Studioのデバッガーで開くことを選択しました。私のQtアプリにはシンボル情報がありませんでしたが、Qtライブラリにはいくつかの情報があることがわかりました。それは私を問題のある行に導いた。呼び出されているメソッドを確認できたためです。(それでも、Qtは便利で強力なクロスプラットフォームのLGPLフレームワークだと思います。)


-3

私はこのエラーが発生し、VSが!cleanしようとしてもクラッシュしました!私のプロジェクト。そのため、Releaseディレクトリから手動でobjファイルを削除し、その後、問題なくビルドしました。


-6

私はロルフに同意します。再現性が非常に重要であるため、非デバッグモードを使用しないでください。すべてのビルドはデバッグ可能でなければなりません。デバッグするターゲットが2つあると、デバッグ負荷が2倍以上になります。使用できない場合を除き、「デバッグモード」バージョンを出荷してください。その場合は、使用可能にしてください。


これはアプリケーションの10%で機能しますが、すべてのアプリケーションで確実に機能するわけではありません。DEBUGビルドとしてリリースされたゲームをプレイしますか?おそらくPDBと一緒にでも、分解に適したモードで商標付きの秘密のセキュリティコードを配布しますか?私はそうは思いません。
steffenj 2008年

Steffenj:ゲーム開発者にバグを見つけてほしい。理想的には、出荷前に出荷しますが、出荷後であれば、再現して追跡するために十分な情報を入手できるようにしてください。シークレットコードの場合、商標は適用されません。PDB?タンパク質データバンク?Pythonデバッガー?
wnoise 2008年

私見、それは悪い考えです。実行可能ファイルは大きく、最適化されておらず、実行速度が大幅に低下します。これらのケースは非常にまれです。彼らが起こったときに特に気が狂っていますが。非常にまれな最悪の場合のデバッグを心配して、一貫して劣った製品を提供するべきではありません。(私は多くの反対投票の1つではありませんでした。)私はNASAのプログラミングをしました。また、最低限、すべてのコード行を1回テストする必要があると述べました。単体テストも役立ちます。
CodeLurker 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.