悲しいことに、多くの経験豊富な開発者でさえ、このツールキットに精通しておらず、多くの企業はこのプラクティスを仕事に取り入れていません。 事後デバッグについて話している。
この記事では、この獣を使った作業の基本を示したいと思います。おそらく、開発者にスカムの分野での私の知識を広げるようにプッシュするでしょう。 そのため、開発者、チームリーダーはC ++ Windowsを読むように招待されています。開発部門の責任者と知り合えることは素晴らしいことです。
プログラムを破る
まず、実行しようとするとクラッシュする小さなプログラムを作成します。
これを行うには、VS2008を使用して、次のコードでC ++コンソールアプリケーションを作成します。
#include "stdafx.h"
int _tmain( int argc, _TCHAR* argv[])
{
int * p = NULL;
p[0] = 10;
return 0;
}
* This source code was highlighted with Source Code Highlighter .
ご覧のとおり、ヌルポインターを作成し、そこに値を詰め込みます。 リリース構成でプログラムをコンパイルし、エクスプローラーから実行します。 次のようなものが表示されます。
[プログラムのデバッグ]ボタンをクリックすると、高い確率でデバッガが立ち上がり、エラーが発生した場所が表示されます。 このプロジェクトはデフォルト設定であったため、私のスタジオは立ち上がって、ソースコード内のエラーの場所を示しました。
何らかの理由でスタジオがこれをどのように行ったかわからない場合は、プロジェクトの設定を確認してください。
デフォルトでは、強調表示された値が設定されていました。 これは、プログラムとともに、プログラムに関するデバッグ情報を含むpdbファイルが作成されることを意味します。 作成したコンピューターと同じコンピューターでプログラムを起動したため、デバッグ中にVisual Studioがこのpdbファイルを取得し、保存されたパスを使用して開くことができました。次に、Visual Studioはクラッシュスタックを開き、コード行を表示しましたそれが例外を引き起こしました。 そして、pdbファイル、Visual Studio、ソースがない他の誰かのコンピューターでクラッシュが発生した場合はどうなりますか? この場合、私たち自身が例外的な状況を把握し、それが発生した理由に関するすべての可能なデータを収集する必要があります。
未処理の例外ハンドラーを設定します
プログラムを次のように書き換えます。
#include "stdafx.h"
#include <windows.h>
#include <io.h>
LONG WINAPI CustomUnhandledExceptionFilter( PEXCEPTION_POINTERS pExInfo )
{
_tprintf( TEXT( "Exception!" ) );
return EXCEPTION_EXECUTE_HANDLER;
}
int _tmain( int argc, _TCHAR* argv[])
{
LPTOP_LEVEL_EXCEPTION_FILTER hOldFilter = SetUnhandledExceptionFilter( CustomUnhandledExceptionFilter );
int * p = NULL;
p[0] = 10;
SetUnhandledExceptionFilter( hOldFilter );
return 0;
}
* This source code was highlighted with Source Code Highlighter .
ご覧のとおり、CustomUnhandledExceptionFilter関数を追加しました。これは、「Exception!」という単語をコンソールに出力するだけです。 そしてメイン関数では、最初に未処理の例外のフィルターを設定し、古いものを最後の場所に戻します。
プログラムをコンパイルし、実行します...それだけです! これ以上エラーメッセージはありません。 これで、「Exception!」という単語がコンソールにのみ表示されます。
次のステップに進み、落下に関する情報を保存しましょう。
軽い DbgHelp.libがあるようにします
今から楽しみが始まります。 www.microsoft.com/whdc/DevTools/Debugging/default.mspxにアクセスします
そこからシステムのデバッグツールfor Windowsをダウンロードします。 これは、WinDBGデバッガーおよびデバッグSDKのインストーラーです。
これをすべてコンピューターにインストールし、Debugging Tools For WindowsへのSDKへのパスがスタジオで作成されていることを確認します。
次に、プロジェクトに応じて、DbgHelp.libライブラリを規定し、CustomUnhandledExceptionFilter関数を次のように変更します。
#include <dbghelp.h>
LONG WINAPI CustomUnhandledExceptionFilter( PEXCEPTION_POINTERS pExInfo )
{
HANDLE hFile;
hFile = CreateFile( TEXT( "c:\\minidump.dmp" ), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
if ( NULL == hFile || INVALID_HANDLE_VALUE == hFile )
return EXCEPTION_EXECUTE_HANDLER;
MINIDUMP_EXCEPTION_INFORMATION eInfo;
eInfo.ThreadId = GetCurrentThreadId();
eInfo.ExceptionPointers = pExInfo;
eInfo.ClientPointers = FALSE;
MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile,
MiniDumpNormal, &eInfo, NULL, NULL);
CloseHandle( hFile );
return EXCEPTION_EXECUTE_HANDLER;
}
* This source code was highlighted with Source Code Highlighter .
ここでは、dbghelp.hヘッダーファイルをインクルードし、MinidumpWriteDump関数を呼び出してアプリケーションクラッシュダンプを保存しました。
dbghelp.dllファイルをDebugging Tools For WindowsフォルダーからアプリケーションのReleaseフォルダーにコピーして実行するだけです。 minidump.dmpファイルはc:\ドライブに表示されます-これはアプリケーションの死体であり、分析します。
アプリケーション病理学者
Debugging Tools For WindowsパッケージからWinDbgデバッガーを実行します。 [ファイル]-> [クラッシュダンプを開く]に移動し、minidump.dmpファイルを開き、ワークスペースの保存に関する質問に否定的に答えます。 次に、(ファイル)->(シンボルファイルパス...)メニューに移動し、プロジェクトのリリースフォルダーへのパスを入力し、ファイル->イメージファイルパスに同じパスを入力します...ファイル->ソースファイルパス...ウィンドウで、プロジェクトのソースを含むフォルダーを指定します。 これで準備の準備が整いました。
そして、デバッガのコマンドラインに、切望されているコマンドを書きます
!analyze -v
数秒後に、次のようなリストを取得します。
FAULTING_IP:
CrashHandler!Wmain + 11 [d:\ work \ crashhandler \ crashhandler.cpp @ 35]
00401091 c7010a000000 mov dword ptr [ecx]、0Ah
EXCEPTION_RECORD:ffffffff-(.exr 0xffffffffffffffff)
ExceptionAddress:00401091(CrashHandler!Wmain + 0x00000011)
ExceptionCode:c0000005(アクセス違反)
ExceptionFlags:00000000
NumberParameters:2
パラメーター[0]:00000001
パラメーター[1]:00000000
アドレス00000000への書き込みを試みます
PROCESS_NAME:CrashHandler.exe
ADDITIONAL_DEBUG_TEXT:
'!Findthebuild'コマンドを使用して、ターゲットビルド情報を検索します。
ビルド情報が利用可能な場合は、 '!Findthebuild -s;を実行します。 .reload 'シンボルパスを設定し、シンボルをロードします。
FAULTING_MODULE:75bf0000 kernel32
DEBUG_FLR_IMAGE_TIMESTAMP:4bb07e29
MODULE_NAME:CrashHandler
ERROR_CODE:(NTSTATUS)0xc0000005-EXCEPTION_CODE:(NTSTATUS)0xc0000005-EXCEPTION_PARAMETER1:00000001
EXCEPTION_PARAMETER2:00000000
WRITE_ADDRESS:00000000
FOLLOWUP_IP:
CrashHandler!Wmain + 11 [d:\ work \ crashhandler \ crashhandler.cpp @ 35]
00401091 c7010a000000 mov dword ptr [ecx]、0Ah
FAULTING_THREAD:0000109c
BUGCHECK_STR:APPLICATION_FAULT_NULL_POINTER_WRITE_WRONG_SYMBOLS
PRIMARY_PROBLEM_CLASS:NULL_POINTER_WRITE
DEFAULT_BUCKET_ID:NULL_POINTER_WRITE
LAST_CONTROL_TRANSFER:0040120dから00401091
STACK_TEXT:
0018ff44 0040120d 00000001 003c2dc0 003c3c90 CrashHandler!Wmain + 0x11 [d:\ work \ crashhandler \ crashhandler.cpp @ 35]
0018ff88 75c03677 7efde000 0018ffd4 773b9d72 CrashHandler!__ tmainCRTStartup + 0x10f [f:\ dd \ vctools \ crt_bld \ self_x86 \ crt \ src \ crtexe.c @ 583]
警告:スタックのアンワインド情報は利用できません。 次のフレームが間違っている可能性があります。
0018ff94 773b9d72 7efde000 6819a792 00000000 kernel32!BaseThreadInitThunk + 0x12
0018ffd4 773b9d45 00401355 7efde000 00000000 ntdll!RtlInitializeExceptionChain + 0x63
0018ffec 00000000 00401355 7efde000 00000000 ntdll!RtlInitializeExceptionChain + 0x36
STACK_COMMAND:〜0s; .ecxr; kb
FAULTING_SOURCE_CODE:
31:int * p = NULL;
32:p [0] = 10;
33:
34:SetUnhandledExceptionFilter(hOldFilter);
> 35:
36:0を返します。
37:}
38:
SYMBOL_STACK_INDEX:0
SYMBOL_NAME:CrashHandler!Wmain + 11
FOLLOWUP_NAME:MachineOwner
IMAGE_NAME:CrashHandler.exe
BUCKET_ID:WRONG_SYMBOLS
FAILURE_BUCKET_ID:NULL_POINTER_WRITE_c0000005_CrashHandler.exe!Wmain
このリストには、プログラムに何が起こったかを理解するためのすべてが含まれています。
EXCEPTION_RECORDセクションには例外に関する情報があります。STACK_TEXTセクションには、アドレス、関数名、ソースコード、コード行を含むフォールスタックが表示されます。
これで、プログラムのexe-shnikとdbghelp.dllファイルを別のコンピューターに転送し、そこで実行できます。 そこからminidump.dmpファイルを取得し、作業コンピューターでこのファイルを再度解析します。 結果は同じになります。 これは事後デバッグです。
次のセクションでは、いくつかのヒントを示します。
さらなる開発
上記の例では、落下時にダンプを収集するプリミティブプログラムを作成し、このダンプを解析して落下スタックを取得できました。 そして今、この種のことで仕事を整理することに関するいくつかの賢明な考え。
- 常識では、プログラムで再利用するために、未処理の例外ハンドラ自体を別のdllに移動する必要があります。 ハンドラーを登録するには、アプリケーションのエントリポイントのスタックにインスタンスが作成されるクラスを作成すると便利です(コンストラクターでハンドラーをインストールし、デストラクタで削除できます)
- ダンプに加えて、DumpWriteDump関数のコールバック内のデータ構造を手動で逆アセンブルすることにより、例外に関する情報を含むファイルを作成することもできます(明確にするためにこの関数のヘルプを参照してください)。 一般に、アーカイバをダンプコレクタに固定してすべてをシェイクすることもできますが、ハンドラにリエントラント保護を挿入することを忘れないでください(例外処理中に例外が発生した場合)
- クラッシュスタックに関する信頼できるデータを取得するには、アプリケーションにロードされたすべてのモジュールからデバッグシンボルを取得する必要があります。 これを行うには、デバッグ情報サーバーを展開する必要があります。これを行う方法については、記事「 habrahabr.ru/blogs/development/89094/#habracut」で書きました
- dbghelp.dbgライブラリは、Windows、Visual Studioなどに長い間バンドルされてきました。 したがって、dll-hellを誤動作させないために、アプリケーションで再配布し、必要なインスタンスを正確にロードしてください。
さらに掘る場所
この記事が好きで、事後デバッグを採用したい場合は、次のことを検討する必要があります。
- Debugging Tools For Windows、SDK、およびWinDbgデバッガーからのヘルプ
- Oleg Starodumovのブログdebuginfo.comには、有用な記事とユーティリティが含まれています。
- 古いが非常に便利なダンプコレクションライブラリの実装をご覧くださいwww.codeproject.com/KB/debug/XCrashReportPt1.aspx(4つの部分があります)
- John Robbinsのデバッグ本と彼のブログを読んでください(彼のニックネームはBugslayerです)。
まあ、空想。 たとえば、非常に有能な専門家であった私の以前の同僚の1人が、女性を収集するライブラリを作成し、それらを会社の特別なメールボックスに送りました。 このメールボックスには、ダンプを解析し、スタック上のなじみのあるコンポーネントを探し、ダンプ付きの手紙をこれらのモジュールを担当するプログラマーに転送するロボットがありました。 私がこの方法に間違えなければ、彼はRSDN Journalに記事を書きさえしました。
ご質問がある場合は、幸運とトラブルのないコードをお試しください。質問してください。