アクセス違反 - まずはスタックバックトレースから

アクセス違反などでクラッシュして終了したプロセスのダンプをデバッガで開くと、 クラッシュしたスレッドにコンテキストがセットされた状態で開かれます。 このため、ひとめみただけで、クラッシュの直接の原因を特定することが可能です。

その直接の原因にいたる経緯はひとめではわかりませんが、なぜアクセス違反を発生させたのか、などは ひとめでわかります。

「ひとめでわかる」 と言われても、デバッガを使ったことが無ければ、 なかなかピンとこないと思いますので、簡単な例をみてください。

クラッシュ時の解析例

次のようなコードがあったとします。

#include <string.h>

int main() {

     strcpy( NULL, NULL );
     return 0;

}

このコードでは strcpy 関数を呼び出していますが、文字列の元のアドレスも書き込み先も NULL がセットされています。 NULL で指し示される 0x00000000 番地は読込み、書き込み禁止ですから、このプログラムを実行するとアクセス違反が発生します。

試しにプログラムをビルドして、実行すると確かに次のように致命的エラーの発生を示すダイアログボックスが表示されます。

このサンプルでは、何しろコードがクラッシュするコードしか無いのでどこで問題が発生するか明らかではありますが、 これがデバッガではどのように見えるか確認してみましょう。

デバッガをアタッチして、プログラムを実行します。

C:\Temp\debug>c:\debuggers\cdb crashtest.exe

Microsoft (R) Windows Debugger Version 6.11.0001.404 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: crashtest.exe
Symbol search path is: srv*C:\websymbols*http://msdl.microsoft.com/download/symbo
Executable search path is:
ModLoad: 00400000 00417000   crashtest.exe
ModLoad: 77a00000 77b3c000   ntdll.dll
ModLoad: 77070000 77144000   C:\Windows\system32\kernel32.dll
ModLoad: 75d40000 75d8a000   C:\Windows\system32\KERNELBASE.dll
(e6c.17e0): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0012fb0c edx=77a464f4 esi=fffffffe edi=00000000
eip=77a9e60e esp=0012fb28 ebp=0012fb54 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!LdrpDoDebuggerBreak+0x2c:
77a9e60e cc              int     3
0:000> g

プログラムを続行すると、次のようにアクセス違反 (Access Violation, AV) で停止します。

(e6c.17e0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=003b2408 ebx=7ffdf000 ecx=00000000 edx=7efefeff esi=00000000 edi=00000000
eip=004010c3 esp=0012ff30 ebp=0012ff40 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
crashtest!strcat+0x93:
004010c3 8b01            mov     eax,dword ptr [ecx]  ds:0023:00000000=????????

ここで ecx が 00000000 であり、それを読み出そうとしたことがアクセス違反を発生させていることがわかります。

kbn コマンドでスタックバックトレースをみると、strcat 内のコードであることがわかります。

0:000> kbn
 # ChildEBP RetAddr  Args to Child
00 0012ff88 770c1174 7ffdf000 0012ffd4 77a5b3f5 crashtest!strcat+0x93
01 0012ff94 77a5b3f5 7ffdf000 7dd2d31f 00000000 kernel32!BaseThreadInitThunk+0xe
02 0012ffd4 77a5b3c8 004012da 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70
03 0012ffec 00000000 004012da 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000>

また、デバッガをアタッチしている状況でなくても、Windows Error Report (WER) のレジストリを構成しておけば、 プログラムがクラッシュしたときに自動的にユーザーダンプを採取することが可能です。このダンプを解析することによって、 原因を追究することが可能です。

WER ではデフォルトのダンプ出力フォルダは %LOCALAPPDATA%\CrashDumps なので、そこにあるダンプを確認します。

-z オプションでダンプファイルを指定して、cdb でユーザーダンプを開くと次のようになります。

>c:\debuggers\cdb -z C:\Users\Keisuke\AppData\Local\CrashDumps\crashtest.exe.3752.dmp

Microsoft (R) Windows Debugger Version 6.11.0001.404 X86
Copyright (c) Microsoft Corporation. All rights reserved.


Loading Dump File [C:\Users\Keisuke\AppData\Local\CrashDumps\crashtest.exe.3752.dmp]
User Mini Dump File with Full Memory: Only application data is available

Symbol search path is: srv*C:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is:
Windows 7 Version 7600 MP (2 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS
Machine Name:
Debug session time: Thu Feb  4 23:29:50.000 2010 (GMT-8)
System Uptime: 2 days 1:07:19.703
Process Uptime: 0 days 0:00:03.000
....
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(ea8.1288): Access violation - code c0000005 (first/second chance not available)
eax=00000000 ebx=0012f934 ecx=00000400 edx=00000000 esi=00000002 edi=00000000
eip=77a464f4 esp=0012f8e4 ebp=0012f980 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCallRet:
77a464f4 c3              ret
0:000> .ecxr
eax=00581be8 ebx=7ffdf000 ecx=00000000 edx=7efefeff esi=00000000 edi=00000000
eip=004010c3 esp=0012ff30 ebp=0012ff40 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
crashtest!strcat+0x93:
004010c3 8b01            mov     eax,dword ptr [ecx]  ds:0023:00000000=????????
0:000> kbn
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr  Args to Child
00 0012ff88 770c1174 7ffdf000 0012ffd4 77a5b3f5 crashtest!strcat+0x93
01 0012ff94 77a5b3f5 7ffdf000 7d35df4c 00000000 kernel32!BaseThreadInitThunk+0xe
02 0012ffd4 77a5b3c8 004012da 7ffdf000 00000000 ntdll!__RtlUserThreadStart+0x70
03 0012ffec 00000000 004012da 7ffdf000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:000>

詳細は省きますが、上記のようにライブデバッグの時と同様のログが確認できることがお分りいただけると思います。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説