主机环境: win7X64Sp1
装了WDK后, 默认的WinDbg是X64版本.
当WinDbg -I后, 是将X64程序的异常处理交给了WinDbgX64.
我写了个Demo, 搞了个野指针, 向野指针中写内容时, Demo挂了, WinDbgX64捕获不到.
在WDK安装光盘(GRMWDK_EN_7600_1.ISO)中, 有X86版WinDbg的安装程序 : \Debuggers\dbg_x86.msi
装好WinDbgX86, 切到安装后的目录: C:\Program Files (x86)\Debugging Tools for Windows (x86)\
运行WinDbg -I, 就可以捕获到x86版应用程序的异常.
假设挂掉的x86应用层程序是我们自己的, 有源码.
#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>
int Add(int a, int b, int * pI);
int _tmain(int argc, _TCHAR* argv[])
{
int x = 1;
int y = 2;
int * pI = NULL; ///< 模拟野指针
_tprintf(L"Add(%d, %d) = %d\r\n", x, y, Add(x, y, pI));
getchar();
return 0;
}
int Add(int a, int b, int * pI)
{
int c = 0;
c = a + b;
*pI = c; ///< 模拟异常
return c;
}
将程序编译成x86Rlease带调式符号, 拷贝工程目录之外运行.
运行Demo, 挂了之后, 被WinDbgX86版捕获.
Microsoft (R) Windows Debugger Version 6.12.0002.633 X86
Copyright (c) Microsoft Corporation. All rights reserved.
*** wait with pending attach
Symbol search path is: SRV*D:\WinDbgSysSymbolsWin7X86*http://msdl.microsoft.com/download/symbols/
Executable search path is:
ModLoad: 00f10000 00f16000 D:\MyWorkDir\dump\TestConsole.exe
ModLoad: 774f0000 77670000 C:\Windows\SysWOW64\ntdll.dll
ModLoad: 74d50000 74e60000 C:\Windows\syswow64\kernel32.dll
ModLoad: 75380000 753c7000 C:\Windows\syswow64\KERNELBASE.dll
ModLoad: 71ff0000 72093000 C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_50934f2ebcb7eb57\MSVCR90.dll
ModLoad: 72500000 7254c000 C:\Windows\system32\apphelp.dll
(18dc.18f4): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=0038f5a4 ecx=00000000 edx=00000000 esi=00000001 edi=00f1337c
eip=775115de esp=0038f590 ebp=0038fa5c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
ntdll!ZwRaiseException+0x12:
775115de 83c404 add esp,4
运行命令 !analyze -v, 居然直接看到了分析后的源代码错误行数,
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
FAULTING_IP:
TestConsole!Add+1a [d:\myworkdir\testconsole\testconsole.cpp @ 24]
00f1107a 8911 mov dword ptr [ecx],edx
EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 00f1107a (TestConsole!Add+0x0000001a)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000001
Parameter[1]: 00000000
Attempt to write to address 00000000
FAULTING_THREAD: 000018f4
DEFAULT_BUCKET_ID: NULL_POINTER_READ
PROCESS_NAME: TestConsole.exe
ERROR_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx
EXCEPTION_PARAMETER1: 00000001
EXCEPTION_PARAMETER2: 00000000
WRITE_ADDRESS: 00000000
FOLLOWUP_IP:
TestConsole!Add+1a [d:\myworkdir\testconsole\testconsole.cpp @ 24]
00f1107a 8911 mov dword ptr [ecx],edx
MOD_LIST: <ANALYSIS/>
NTGLOBALFLAG: 0
APPLICATION_VERIFIER_FLAGS: 0
CONTEXT: 0038f5f4 -- (.cxr 0x38f5f4)
eax=00000003 ebx=00000000 ecx=00000000 edx=00000003 esi=00000001 edi=00f1337c
eip=00f1107a esp=0038fa58 ebp=0038fa5c iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
TestConsole!Add+0x1a:
00f1107a 8911 mov dword ptr [ecx],edx ds:002b:00000000=????????
Resetting default scope
LAST_CONTROL_TRANSFER: from 00f1102c to 00f1107a
PRIMARY_PROBLEM_CLASS: NULL_POINTER_READ
BUGCHECK_STR: APPLICATION_FAULT_NULL_POINTER_READ_NULL_POINTER_WRITE
STACK_TEXT:
0038fa5c 00f1102c 00000001 00000002 00000000 TestConsole!Add+0x1a [d:\myworkdir\testconsole\testconsole.cpp @ 24]
0038fa7c 00f111ec 00000001 00103ea8 00105d98 TestConsole!wmain+0x2c [d:\myworkdir\testconsole\testconsole.cpp @ 13]
0038fac0 74d633aa 7efde000 0038fb0c 77529ef2 TestConsole!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
0038facc 77529ef2 7efde000 77c2b3f1 00000000 kernel32!BaseThreadInitThunk+0xe
0038fb0c 77529ec5 00f11334 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70
0038fb24 00000000 00f11334 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b
FAULTING_SOURCE_CODE:
20: {
21: int c = 0;
22:
23: c = a + b;
> 24: *pI = c; ///< ?¡ê?a¨°¨¬3¡ê
25:
26: return c;
27: }
SYMBOL_STACK_INDEX: 0
SYMBOL_NAME: testconsole!Add+1a
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: TestConsole
IMAGE_NAME: TestConsole.exe
DEBUG_FLR_IMAGE_TIMESTAMP: 51b41328
STACK_COMMAND: .cxr 0x38f5f4 ; kb
FAILURE_BUCKET_ID: NULL_POINTER_READ_c0000005_TestConsole.exe!Add
BUCKET_ID: APPLICATION_FAULT_NULL_POINTER_READ_NULL_POINTER_WRITE_testconsole!Add+1a
Followup: MachineOwner
---------
我还没有设置windbg参数(符号路径, 源代码目录, 映像目录)!
可能和我在本机编译有关系, EXE中有这些调试信息的指示.
看到报错信息: TestConsole!Add+1a [d:\myworkdir\testconsole\testconsole.cpp @ 24]
可以看出报错的函数为 TestConsole!Add, 反汇编这个函数进行分析.
0:000> uf TestConsole!Add
TestConsole!Add [d:\myworkdir\testconsole\testconsole.cpp @ 20]:
20 00f11060 55 push ebp
20 00f11061 8bec mov ebp,esp
20 00f11063 51 push ecx
21 00f11064 c745fc00000000 mov dword ptr [ebp-4],0
23 00f1106b 8b4508 mov eax,dword ptr [ebp+8]
23 00f1106e 03450c add eax,dword ptr [ebp+0Ch]
23 00f11071 8945fc mov dword ptr [ebp-4],eax
24 00f11074 8b4d10 mov ecx,dword ptr [ebp+10h]
24 00f11077 8b55fc mov edx,dword ptr [ebp-4]
24 00f1107a 8911 mov dword ptr [ecx],edx
26 00f1107c 8b45fc mov eax,dword ptr [ebp-4]
27 00f1107f 8be5 mov esp,ebp
27 00f11081 5d pop ebp
27 00f11082 c3 ret
分析函数反汇编内容
0:000> uf TestConsole!Add
TestConsole!Add [d:\myworkdir\testconsole\testconsole.cpp @ 20]:
20 00f11060 55 push ebp ///< 保存现场
20 00f11061 8bec mov ebp,esp ///< 保存栈针
20 00f11063 51 push ecx ///< 保存现场
21 00f11064 c745fc00000000 mov dword ptr [ebp-4],0 ///< 函数内的临时变量清0
23 00f1106b 8b4508 mov eax,dword ptr [ebp+8] ///< 函数第一个参数赋值到eax
23 00f1106e 03450c add eax,dword ptr [ebp+0Ch] ///< 函数第二个参数累加到eax
23 00f11071 8945fc mov dword ptr [ebp-4],eax ///< 将eax赋值到临时变量
24 00f11074 8b4d10 mov ecx,dword ptr [ebp+10h] ///< 将参数3赋值到ecx
24 00f11077 8b55fc mov edx,dword ptr [ebp-4] ///< 将临时变量赋值到dex
24 00f1107a 8911 mov dword ptr [ecx],edx ///< 将临时变量(累加和)给出参3, 报错了
26 00f1107c 8b45fc mov eax,dword ptr [ebp-4]
27 00f1107f 8be5 mov esp,ebp
27 00f11081 5d pop ebp
27 00f11082 c3 ret
说明参数3指针为空, 引起错误
现在要找到ecx为啥为空?
看栈调用链
0038fa5c 00f1102c 00000001 00000002 00000000 TestConsole!Add+0x1a [d:\myworkdir\testconsole\testconsole.cpp @ 24]
0038fa7c 00f111ec 00000001 00103ea8 00105d98 TestConsole!wmain+0x2c [d:\myworkdir\testconsole\testconsole.cpp @ 13]
看到 TestConsole!wmain 调用 TestConsole!Add
反汇编 TestConsole!wmain
0:000> uf TestConsole!wmain
TestConsole!wmain [d:\myworkdir\testconsole\testconsole.cpp @ 8]:
8 00f11000 55 push ebp
8 00f11001 8bec mov ebp,esp
8 00f11003 83ec0c sub esp,0Ch
9 00f11006 c745f801000000 mov dword ptr [ebp-8],1
10 00f1100d c745fc02000000 mov dword ptr [ebp-4],2
11 00f11014 c745f400000000 mov dword ptr [ebp-0Ch],0
13 00f1101b 8b45f4 mov eax,dword ptr [ebp-0Ch]
13 00f1101e 50 push eax
13 00f1101f 8b4dfc mov ecx,dword ptr [ebp-4]
13 00f11022 51 push ecx
13 00f11023 8b55f8 mov edx,dword ptr [ebp-8]
13 00f11026 52 push edx
13 00f11027 e834000000 call TestConsole!Add (00f11060)
13 00f1102c 83c40c add esp,0Ch
13 00f1102f 50 push eax
13 00f11030 8b45fc mov eax,dword ptr [ebp-4]
13 00f11033 50 push eax
13 00f11034 8b4df8 mov ecx,dword ptr [ebp-8]
13 00f11037 51 push ecx
13 00f11038 68f420f100 push offset TestConsole!GS_ExceptionPointers+0x8 (00f120f4)
13 00f1103d ff15a420f100 call dword ptr [TestConsole!_imp__wprintf (00f120a4)]
13 00f11043 83c410 add esp,10h
15 00f11046 ff159c20f100 call dword ptr [TestConsole!_imp__getchar (00f1209c)]
16 00f1104c 33c0 xor eax,eax
17 00f1104e 8be5 mov esp,ebp
17 00f11050 5d pop ebp
17 00f11051 c3 ret
分析 TestConsole!wmain 反汇编结果
0:000> uf TestConsole!wmain
TestConsole!wmain [d:\myworkdir\testconsole\testconsole.cpp @ 8]:
8 00f11000 55 push ebp ///< 保存现场
8 00f11001 8bec mov ebp,esp ///< 保存栈针
8 00f11003 83ec0c sub esp,0Ch ///< 开辟3个DWORD
9 00f11006 c745f801000000 mov dword ptr [ebp-8],1 ///< dw1 = 1
10 00f1100d c745fc02000000 mov dword ptr [ebp-4],2 ///< dw2 = 2
11 00f11014 c745f400000000 mov dword ptr [ebp-0Ch],0 ///< dw3 = 0
13 00f1101b 8b45f4 mov eax,dword ptr [ebp-0Ch] ///< push dw3, 参数3为NULL~
13 00f1101e 50 push eax
13 00f1101f 8b4dfc mov ecx,dword ptr [ebp-4] ///< push dw2
13 00f11022 51 push ecx
13 00f11023 8b55f8 mov edx,dword ptr [ebp-8] ///< push dw1
13 00f11026 52 push edx
13 00f11027 e834000000 call TestConsole!Add (00f11060)
13 00f1102c 83c40c add esp,0Ch
13 00f1102f 50 push eax
13 00f11030 8b45fc mov eax,dword ptr [ebp-4]
13 00f11033 50 push eax
13 00f11034 8b4df8 mov ecx,dword ptr [ebp-8]
13 00f11037 51 push ecx
13 00f11038 68f420f100 push offset TestConsole!GS_ExceptionPointers+0x8 (00f120f4)
13 00f1103d ff15a420f100 call dword ptr [TestConsole!_imp__wprintf (00f120a4)]
13 00f11043 83c410 add esp,10h
15 00f11046 ff159c20f100 call dword ptr [TestConsole!_imp__getchar (00f1209c)]
16 00f1104c 33c0 xor eax,eax
17 00f1104e 8be5 mov esp,ebp
17 00f11050 5d pop ebp
17 00f11051 c3 ret
可以看出Add函数参数3为空的原因是: Add函数调用前, 参数3硬编码赋值成NULL, 引起.
IDA分析反汇编结果
在WinDbg中经过 !analyze -v 分析后, 得到报错地址为 00f1107a, 段地址为0x00f1, 偏移地址为0x107a
用IDA反汇编PE文件, 看到main函数所在的地址为.text:0040xxxx, 按下 g 键, 输入地址为0040107A,
前面是段地址和IDA代码中相同即可, 后面的偏移地址输入要和WinDbg中报错地址的偏移地址相同.
.text:00401060 sub_401060 proc near ; CODE XREF: _wmain+27p
.text:00401060
.text:00401060 var_4 = dword ptr -4
.text:00401060 arg_0 = dword ptr 8
.text:00401060 arg_4 = dword ptr 0Ch
.text:00401060 arg_8 = dword ptr 10h
.text:00401060
.text:00401060 push ebp
.text:00401061 mov ebp, esp
.text:00401063 push ecx
.text:00401064 mov [ebp+var_4], 0
.text:0040106B mov eax, [ebp+arg_0]
.text:0040106E add eax, [ebp+arg_4]
.text:00401071 mov [ebp+var_4], eax
.text:00401074 mov ecx, [ebp+arg_8]
.text:00401077 mov edx, [ebp+var_4]
.text:0040107A mov [ecx], edx ; 报错
.text:0040107C mov eax, [ebp+var_4]
.text:0040107F mov esp, ebp
.text:00401081 pop ebp
.text:00401082 retn
.text:00401082 sub_401060 endp
ecx 是从[ebp + arg_8]中来的, 是函数调用的第三个参数
在IDA中查找 sub_401060 函数调用(按下 x 键), 只有一处调用了此函数
.text:00401000 _wmain proc near ; CODE XREF: ___tmainCRTStartup+10Ap
.text:00401000
.text:00401000 var_C = dword ptr -0Ch
.text:00401000 var_8 = dword ptr -8
.text:00401000 var_4 = dword ptr -4
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 sub esp, 0Ch
.text:00401006 mov [ebp+var_8], 1
.text:0040100D mov [ebp+var_4], 2
.text:00401014 mov [ebp+var_C], 0 ; 这里将参数3清0了
.text:0040101B mov eax, [ebp+var_C]
.text:0040101E push eax
.text:0040101F mov ecx, [ebp+var_4]
.text:00401022 push ecx
.text:00401023 mov edx, [ebp+var_8]
.text:00401026 push edx
.text:00401027 call sub_401060 ; 调用报错的函数
.text:0040102C add esp, 0Ch
.text:0040102F push eax
在IDA中结合WinDbg中得到的报错地址, 进行分析出错的原因, 比只用WinDbg方便.
在WinDbg中有种方法可以看到调用前的代码.
经过 !analyze -v, 可以知道报错地址为T_estConsole+0x107a, 这句所在函数的返回地址为0134102c
0037fca4 0134102c 00000001 00000002 00000000 T_estConsole+0x107a
用WinDbg看看, 返回地址前的汇编代码是什么. 如果是硬编码引起的错误. 很快就能发现问题.
u 0134102c L-0x40
T_estConsole+0xfec:
01340fec 0000 add byte ptr [eax],al
01340fee 0000 add byte ptr [eax],al
01340ff0 0000 add byte ptr [eax],al
01340ff2 0000 add byte ptr [eax],al
01340ff4 0000 add byte ptr [eax],al
01340ff6 0000 add byte ptr [eax],al
01340ff8 0000 add byte ptr [eax],al
01340ffa 0000 add byte ptr [eax],al
01340ffc 0000 add byte ptr [eax],al
01340ffe 0000 add byte ptr [eax],al
01341000 55 push ebp
01341001 8bec mov ebp,esp
01341003 83ec0c sub esp,0Ch
01341006 c745f801000000 mov dword ptr [ebp-8],1
0134100d c745fc02000000 mov dword ptr [ebp-4],2
01341014 c745f400000000 mov dword ptr [ebp-0Ch],0 ///< 硬编码将参数3搞成0了
0134101b 8b45f4 mov eax,dword ptr [ebp-0Ch]
0134101e 50 push eax
0134101f 8b4dfc mov ecx,dword ptr [ebp-4]
01341022 51 push ecx
01341023 8b55f8 mov edx,dword ptr [ebp-8]
01341026 52 push edx
01341027 e834000000 call T_estConsole+0x1060 (01341060)
用WinDbg从调用返回处往上看, 可以知道参数的来源~