Windows エラー報告の .wer ファイルの読み方

アプリケーションの異常終了時に Windows エラー報告 (Windows Error Reporting; WER) が生成する「Report.wer」ファイルの読み方について簡単に説明します。
生成される場所は「%ProgramData%\Microsoft\Windows\WER\ReportArchive」フォルダ内で、中身は単なるテキストファイルです。

動作確認環境

  • Windows 11 Home 23H2
  • Visual Studio Community 2022

EventType

例: EventType=APPCRASH

事象の種類を示します。「APPCRASH」は、アプリケーションがクラッシュしたことを示します。ほかにもハングアップしたときに記録される「AppHangB1」などがあります(「B1」の意味は不明です)。

EventTime

例: EventTime=133617708102278455

エラーが発生した日時を示します。単位は NT タイムエポック(Windows NT のシステム時刻, 1601年1月1日からの100ナノ秒単位の経過時間)です。

コマンドプロンプトから「w32tm /ntte <NTタイムエポック>」と入力することで、わかりやすい日時に変換できます。

C:\tmp> w32tm /ntte 133617708102278455
154650 03:00:10.2278455 - 2024/06/02 12:00:10

AppSessionGuid

例: AppSessionGuid=00004604-0001-0030-da8d-0bf598b4da01

最初の 8 桁はプロセスID です。16進数なので、「00004604」であれば 0x00004604=17924 です。
後ろの「4桁」-「12桁」は、プロセスの生成日時(起動日時)です。リトルエンディアンの 16 進数で表現された NT タイムエポックです。w32tm コマンドでわかりやすい日時に変換できます。

C:\tmp> w32tm /ntte 0x01dab498f50b8dda
154650 03:00:00.0054746 - 2024/06/02 12:00:00

TargetAppVer

例: TargetAppVer=2024//06//01:17:00:00!0!mymain.exe

「実行ファイルのビルド日時」と「実行ファイル名」が書いてあります。
ビルド日時は UTC(協定世界時)で表示されているため、プラス 9 時間すると日本時間になります。たとえば「2024/06/01 17:00:00」は、日本時間で翌日の「2024/06/02 02:00:00」です。
ビルド日時については、後述の「Sig[n] アプリケーションのタイムスタンプ」もご覧ください。

Sig[n] アプリケーション名

例: Sig[0].Name=アプリケーション名
   Sig[0].Value=mymain.exe

アプリケーション名(実行ファイル名)です。

Sig[n] アプリケーションのバージョン

例: Sig[1].Name=アプリケーションのバージョン
   Sig[1].Value=1.2.3.0

実行ファイルのバージョンです。「製品バージョン」ではなく「ファイルバージョン」のほうです。

Sig[n] アプリケーションのタイムスタンプ

例: Sig[2].Name=アプリケーションのタイムスタンプ
   Sig[2].Value=665b5390

実行ファイルのビルド日時です。単位は UNIX 時間(1970年1月1日からの経過秒数)です。
「ビルド日時」は、エクスプローラーのプロパティで表示される「作成日時」「更新日時」「アクセス日時」ではなく、ビルド時(プログラム生成時)にモジュール内に埋め込まれた日時です。

PowerShell を使って、わかりやすい日時に変換できます。

PS C:\> get-date ([DateTimeOffset]::FromUnixTimeSeconds(0x665b5390).LocalDateTime)

2024年6月2日 2:00:00

新しい PowerShell では次のコマンドで変換できるようですが、インストールしていないので未確認です。

PS C:\> Get-Date -UnixTimeSeconds 0x665b5390

C 言語でも変換できます。

C:\tmp> type test.c
#include <stdio.h>
#include <time.h>

int main()
{
    time_t t = 0x665b5390;
    printf("%s", ctime(&t));
    return  0;
}

C:\tmp> cl test.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.39.33523 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

test.c
Microsoft (R) Incremental Linker Version 14.39.33523.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:test.exe
test.obj

C:\tmp> test.exe
Sun Jun  2 02:00:00 2024

モジュールに埋め込まれたビルド日時は、Visual Studio 付属の dumpbin コマンドで表示できます。

C:\tmp> dumpbin /headers mymain.exe
Microsoft (R) COFF/PE Dumper Version 14.39.33523.0
Copyright (C) Microsoft Corporation.  All rights reserved.

......
        665B5390 time date stamp Sun Jun  2 02:00:00 2024
......

ただし、モジュールによっては、「ビルド日時」の領域に、モジュールをビルドした日時ではなくモジュールのバイナリから計算されたハッシュ値が書き込まれている場合があります。詳細は参考文献 [1] を参照してください。

Sig[n] 障害モジュールの名前

例: Sig[3].Name=障害モジュールの名前
   Sig[3].Value=MyDll1.dll

障害が発生したモジュールの名前です。実行ファイル名(.exe)のこともありますし、リンクされている DLL 名(.dll) のこともあります。

ただし、ここに記録されたモジュールに障害の真の原因があるとは限りません。
たとえば、再帰の無限リピートでスタックオーバーフローが発生した場合、再帰しているモジュール名ではなく KERNELBASE.dll が記録されます。

例: Sig[3].Name=障害モジュールの名前
   Sig[3].Value=KERNELBASE.dll

想定外の方向にジャンプしたせいで偶然 ntdll.dll の中に入りそこで異常が発生した場合、ジャンプしたモジュールの名前ではなく ntdll.dll が記録されます。

例: Sig[3].Name=障害モジュールの名前
   Sig[3].Value=ntdll.dll

このように、OS のモジュールの名前があっても「OS のバグ」と断定しないよう注意が必要です。

また、モジュールがロードされていないアドレスで異常が発生した場合、たとえばメモリの 0 バイト目にジャンプしてしまった場合、「StackHash_ac46」などといった名前が記録されます。

例: Sig[3].Name=障害モジュールの名前
   Sig[3].Value=StackHash_ac46

「StackHash_xxxx」というモジュールが存在するわけではありません。

Sig[n] 障害モジュールのバージョン

例: Sig[4].Name=障害モジュールのバージョン
   Sig[4].Value=3.2.1.0

障害モジュールのバージョンです。「製品バージョン」ではなく「ファイルバージョン」のほうです。

Sig[n] 障害モジュールのタイムスタンプ

例: Sig[5].Name=障害モジュールのタイムスタンプ
   Sig[5].Value=665c6547

障害モジュールに埋め込まれているビルド日時です。ビルド日時については、前述の「Sig[n] アプリケーションのタイムスタンプ」をご覧ください。

Sig[n] 例外コード

例: Sig[6].Name=例外コード
   Sig[6].Value=c0000005

例外のコードです。次のような方法で意味を調べます。

  • インターネットで調べる。
  • Visual Studio に含まれている ntstatus.h を参照する。
  • Visual Studio に含まれている errlook.exe を実行する。
  • Windows 付属の certutil.exe を実行する。
  • Microsoft エラールックアップツールをダウンロードして実行する。

certutil コマンドの使用例です。

C:\> certutil -error 0xC0000005
0xc0000005 (NT: 0xc0000005 STATUS_ACCESS_VIOLATION) -- 3221225477 (-1073741819)
エラー メッセージ テキスト: 0x%p の命令が 0x%p のメモリを参照しました。メモリが %s になることはできませんでした。
......

.wer ファイルに記録される例外コードの例です。

  • 0xC0000005 アクセス違反
  • 0xC000001D 不正な命令
  • 0xC0000094 ゼロ除算
  • 0xC00000FD スタックオーバーフロー
  • 0xC0000374 ヒープ破壊
  • 0x80000003 ブレークポイントに到達

Sig[n] 例外オフセット

例: Sig[7].Name=例外オフセット
    Sig[7].Value=00007114

例外発生アドレスです。障害モジュールのロードアドレスを起点としたメモリ上のオフセット(相対アドレス)になります。ファイルの先頭からのオフセットではありません。

たとえば、障害モジュールが MyDll1.dll、例外オフセットが 0x7114 だったとします。
この場合、WinDbg ツールの [File]-[Open Crash Dump] から MyDll1.dll ファイルを開き、[Command] ウィンドウから「u MyDll1+0x7114 L1」コマンド(MyDll1 のオフセット 0x7114 バイト目から 1 行分を逆アセンブルの意)と入力すると、例外を起こした命令がわかります。

0:000> u MyDll1+0x7114 L1
Mydll1!DllMain+0x24 [C:\tp\MyDll\Mydll1.c @ 50]:
10007114 c701cdab3412    mov     dword ptr [ecx],1234ABCDh

Visual Studio 付属の dumpbin コマンドを使って特定してもよいでしょう。
まず、dumpbin /headers でモジュールのベースアドレスを調べます。

C:\tmp> dumpbin /headers mydll1.dll
Microsoft (R) COFF/PE Dumper Version 14.39.33523.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file mydll1.dll
......
OPTIONAL HEADER VALUES
             10B magic # (PE32)
           14.39 linker version
           64E00 size of code
           12000 size of initialized data
               0 size of uninitialized data
            3AC1 entry point (10003AC1) @ILT+10940(__DllMainCRTStartup@12)
            1000 base of code
           66000 base of data
        10000000 image base (10000000 to 1007AFFF)
            1000 section alignment
             200 file alignment
            6.00 operating system version
            0.00 image version
......

次に、dumpbin /disasm で障害モジュールを逆アセンブルします。

C:\tmp> dumpbin /disasm mydll1.dll

Microsoft (R) COFF/PE Dumper Version 14.39.33523.0
Copyright (C) Microsoft Corporation.  All rights reserved.
Dump of file mydll1.dll
File Type: DLL

  ......
  1000710A: C7 45 F8 00 00 00  mov     dword ptr [ebp-8],0
            00
  10007111: 8B 4D F8           mov     ecx,dword ptr [ebp-8]
  10007114: C7 01 CD AB 34 12  mov     dword ptr [ecx],1234ABCDh
  1000711A: B8 01 00 00 00     mov     eax,1
  1000711F: 8B E5              mov     esp,ebp
  ......

「ベースアドレス」+「障害オフセット」にある命令、この例では 0x10000000 + 0x7114 = 0x10007114 にある「mov dword ptr [ecx],1234ABCDh」が例外を起こした命令になります。直前を見ると ecx レジスタに 0 をセットしており、アドレス 0 に値を書き込もうとしていわゆる NULL ポインタ例外のアクセス違反が発生したと強く推測されます(よそのアドレスからジャンプしてきた可能性もあるので断定はしません)。

参考文献

[1] やや低レイヤー研究所「Windows システムファイルのビルド日時が表示されない謎を探る