Windbg是微软提供的一款免费的,专门针对Windows应用程序的调试工具。借助于Windbg, 我们常见的软件问题:软件异常,死锁,内存泄漏等,就可以进行高效的排查。
在开始用WinDbg调试应用程序之前,我们得先做些准备工作。
上述3个操作步聚比较简单,均在File菜单的子菜单项中设置,此处就不在细说,值得一提的就是需要设置的符号文件路径有三类:
2.MFC提供的一系列DLL对应的符号文件,一般情况下在C:\WINDOWS\system32目录。
至此我们已经完成了准备工作。如果说,选择题,填空题,简答题是考试时常遇到的题型, 那么让我们来看看,在软件调试过程中常遇到的题型如何解吧。
现在介绍如何利用Windbg进行软件异常,死锁,内存泄漏等软件问题的排查方法。
-
常见的软件崩溃主要是断言与未处理异常,断言引起的软件崩溃相对来说比较容易定位,而未处理异常的定位比较困难,现在看看如何利用Windbg进行未处理异常错误的排查,其步聚如下:
-
0 Id: 1524.1520 Suspend: 1 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr Args to Child
0012d44c 7c92e9ab 7c8094f2 00000002 0012d478 ntdll!KiFastSystemCallRet
0012d450 7c8094f2 00000002 0012d478 00000001 ntdll!ZwWaitForMultipleObjects+0xc
0012d4ec 7c809c86 00000002 0012d61c 00000000 kernel32!WaitForMultipleObjectsEx+0x12c
0012d508 6976763c 00000002 0012d61c 00000000 kernel32!WaitForMultipleObjects+0x18
0012de9c 697682b1 0012f1d4 ffffffff 00198310 faultrep!StartDWException+0x5df
0012ef10 7c863059 0012f1d4 ffffffff 00415460 faultrep!ReportFault+0x533
0012f184 1021595d 0012f1d4 00402218 00400000 kernel32!UnhandledExceptionFilter+0x4cf
0012f1a8 00402187 c0000094 0012f1d4 10212843 MSVCRTD!_XcptFilter+0x3d [winxfltr.c @ 228]
0012ffc0 7c816d4f 7c99ce64 00000000 7ffdd000 JustTest!WinMainCRTStartup+0x1d7 [crtexe.c @ 45]
0012fff0 00000000 00401fb0 00000000 78746341 kernel32!BaseProcessStart+0x23
# 1 Id: 1524.14e4 Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr Args to Child
00c7ffc8 7c9707a8 00000005 00000004 00000001 ntdll!DbgBreakPoint
00c7fff4 00000000 00000000 00000000 00000000 ntdll!DbgUiRemoteBreakin+0x2d
-
0012f1d4 0012f2c8 0012f2dc 0012f200 7c9237bf
0012f1e4 0012f2c8 0012ffb0 0012f2dc 0012f29c
0012f1f4 0012f814 7c9237d8 0012ffb0 0012f2b0
0012f204 7c92378b 0012f2c8 0012ffb0 0012f2dc
0012f214 0012f29c 00402218 00000001 0012f2c8
0012f224 0012ffb0 7c957860 0012f2c8 0012ffb0
-
eax=00000000 ebx=00000000 ecx=0012fe74 edx=00000000 esi=00144c90 edi=0012f600
eip=00401caf esp=0012f5a8 ebp=0012f600 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216
JustTest!CJustTestDlg::OnButton1+0x2f:
00401caf f77df4 idiv eax,dword ptr [ebp-0Ch] ss:0023:0012f5f4=00000000
-
*** Stack trace for last set context - .thread/.cxr resets it
# ChildEBP RetAddr Args to Child
01 0012f638 5f439ffb 0012fe74 000003e8 00000000 MFC42D!_AfxDispatchCmdMsg+0xa2 [cmdtarg.cpp @ 88]
03 0012f6c0 5f431f33 000003e8 00000000 00000000 MFC42D!CDialog::OnCmdMsg+0x24 [dlgcore.cpp @ 97]
04 0012f720 5f431135 000003e8 00230fd2 0012f8a0 MFC42D!CWnd::OnCommand+0x138 [wincore.cpp @ 2099]
05 0012f820 5f4310b8 00000111 000003e8 00230fd2 MFC42D!CWnd::OnWndMsg+0x53 [wincore.cpp @ 1608]
06 0012f840 5f42ec09 00000111 000003e8 00230fd2 MFC42D!CWnd::WindowProc+0x2e [wincore.cpp @ 1596]
07 0012f8b4 5f42f0f5 0012fe74 001b0f56 00000111 MFC42D!AfxCallWndProc+0xed [wincore.cpp @ 215]
08 0012f8e0 5f49265d 001b0f56 00000111 000003e8 MFC42D!AfxWndProc+0xad [wincore.cpp @ 379]
09 0012f910 77d18709 001b0f56 00000111 000003e8 MFC42D!AfxWndProcBase+0x4a [afxstate.cpp @ 220]
0a 0012f93c 77d187eb 5f492613 001b0f56 00000111 USER32!InternalCallWinProc+0x28
0b 0012f9a4 77d1b368 00000000 5f492613 001b0f56 USER32!UserCallWinProcCheckWow+0x150
0c 0012f9f8 77d1b3b4 00668e50 00000111 000003e8 USER32!DispatchClientMessage+0xa3
0d 0012fa20 7c92eae3 0012fa30 00000018 00668e50 USER32!__fnDWORD+0x24
0e 0012fa44 77d194e3 77d1de6e 001b0f56 00000111 ntdll!KiUserCallbackDispatcher+0x13
0f 0012fa80 77d1b7ab 00668e50 00000111 000003e8 USER32!NtUserMessageCall+0xc
10 0012faa0 77d4fc9d 001b0f56 00000111 000003e8 USER32!SendMessageW+0x7f
11 0012fab8 77d46530 00669450 00000000 00669450 USER32!xxxButtonNotifyParent+0x41
12 0012fad4 77d28386 0014777c 00000001 00000000 USER32!xxxBNReleaseCapture+0xf8
13 0012fb58 77d2887a 00669450 00000202 00000000 USER32!ButtonWndProcWorker+0x6d5
-
有人说:多线程是万恶的根源。确实,在多线程编程中有两类 问题是不容易处理的。一:死锁,二:共享资源保护。
-
. 0 Id: 1a04.109c Suspend: 1 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr Args to Child
0012f510 7c92e9c0 7c93901b 00000780 00000000 ntdll!KiFastSystemCallRet
0012f514 7c93901b 00000780 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
0012f59c 7c92104b 00416868 0040237d 00416868 ntdll!RtlpWaitForCriticalSection+0x132
0012f5a4 0040237d 00416868 0012f8a0 00144c90 ntdll!RtlEnterCriticalSection+0x46
1 Id: 1a04.1288 Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr Args to Child
00d5fef0 7c92d85c 7c8023ed 00000000 00d5ff24 ntdll!KiFastSystemCallRet
00d5fef4 7c8023ed 00000000 00d5ff24 00d5ffb4 ntdll!NtDelayExecution+0xc
00d5ff4c 7c802451 00002710 00000000 00d5ffb4 kernel32!SleepEx+0x61
00d5ff5c 00401ca9 00002710 74680000 746800e0 kernel32!Sleep+0xf
00d5ffec 00000000 00401087 00000000 00000000 kernel32!BaseThreadStart+0x37
该示例程序有两个活动线程,但是此时这个示例程序对于用户的任何操作均未响应,可以判断这个程序死锁了。
根据线程0中的函数RtlEnterCriticalSection,初步判断该线程死锁是由于临界区引起。
因此利用!cs 命令找出当前被占有的临界区及对应的线程调用栈。
-----------------------------------------
Critical section = 0x00416868 (JustTest!g_cs+0x0)
ChildEBP RetAddr Args to Child
00d5fef0 7c92d85c 7c8023ed 00000000 00d5ff24 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
00d5fef4 7c8023ed 00000000 00d5ff24 00d5ffb4 ntdll!NtDelayExecution+0xc (FPO: [2,0,0])
00d5ff4c 7c802451 00002710 00000000 00d5ffb4 kernel32!SleepEx+0x61 (FPO: [Non-Fpo])
00d5ff5c 00401ca9 00002710 74680000 746800e0 kernel32!Sleep+0xf (FPO: [1,0,0])
00d5ffb4 7c80b50b 00000000 74680000 746800e0 JustTest!WndProc+0x39 (CONV: stdcall)
00d5ffec 00000000 00401087 00000000 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])
-
假若现在有两个示例程序,JustTest.exe 与 JustTest0.exe 它们之间处于互锁状态。现在看看如何利用Windbg对进程间程序互锁的情况进行调试。
-
lkd> !process 0 2 justtest0.exe
PROCESS 88db38b8 SessionId: 0 Cid: 02d8 Peb: 7ffd4000 ParentCid: 08e8
DirBase: 0a4c0c80 ObjectTable: e4941c40 HandleCount: 41.
88ed9b90 Mutant - owning thread 88d9a590
以上信息显示进程 justtest0.exe的线程88ee8020在等待一个互斥体Mutant (88ed9b90), 该互斥体被线程88d9a590所占有
-
Owning Process 0 Image: <Unknown>
Attached Process 88d9c8b0 Image: JustTest.exe
Wait Start TickCount 204830 Ticks: 24996 (0:00:06:30.562)
Context Switch Count 2836 LargeStack
Win32 Start Address 0x004020d0
Stack Init a98a1000 Current a98a0c20 Base a98a1000 Limit a989b000 Call 0
Priority 10 BasePriority 8 PriorityDecrement 0 DecrementCount 16
注意:使用以上方法调试内核对象引起的死锁,需要注意的一点是!process 命令显示的线程信息中,只会显示该线程在等待的内核对象的信息,而不会显示该线程占有的的内核对象的信息
-
-
从以上的介绍中,你会发现对于程序异常,死锁现象都比较容易定位,那么对于内存泄漏这种情况又如何呢?同样,排查方法也是比较简单
现在介绍一下如何利用windbg提供的工具来排查内存泄漏问题.
修改注册表使操作系统对每一次内存分配操作时的调用栈进行记录:
umdh -pn:ProgramName -f:firstTraceFile.txt
umdh -pn:ProgramName -f:secondTraceFile.txt
umdh firstTraceFile.txt secondTraceFile.txt > result.txt
E)分析result.txt, 判断这段时间内导到内存泄漏的函数调用栈
测试过程是,先执行5次NEW操作,再执行3次delete操作
// Each log entry has the following syntax:
// + BYTES_DELTA (NEW_BYTES - OLD_BYTES) NEW_COUNT allocs BackTrace TRACEID
// + COUNT_DELTA (NEW_COUNT - OLD_COUNT) BackTrace TRACEID allocations
// BYTES_DELTA - increase in bytes between before and after log
// NEW_BYTES - bytes in after log
// OLD_BYTES - bytes in before log
// COUNT_DELTA - increase in allocations between before and after log
// NEW_COUNT - number of allocations in after log
// OLD_COUNT - number of allocations in before log
// TRACEID - decimal index of the stack trace in the trace database
// (can be used to search for allocation instances in the original
2f70 ( 1fa0 - 4f10) 2 allocs BackTrace360
- 3 ( 2 - 5) BackTrace360 allocations
ntdll!RtlDebugAllocateHeap+000000E1
ntdll!RtlAllocateHeapSlowly+00000044
ntdll!RtlAllocateHeap+00000E64
MSVCRTD!_heap_alloc_base+0000013C (malloc.c, 200)
MSVCRTD!_heap_alloc_dbg+000001A2 (dbgheap.c, 378)
MSVCRTD!_nh_malloc_dbg+00000049 (dbgheap.c, 248)
MSVCRTD!_malloc_dbg+0000001F (dbgheap.c, 165)
MFC42D!operator new+00000024 (afxmem.cpp, 373)
MFC42D!operator new+00000016 (afxmem.cpp, 65)
te!CTeDlg::OnButton1+00000037 (E:\test\teDlg.cpp, 180)
MFC42D!_AfxDispatchCmdMsg+000000A2 (cmdtarg.cpp, 88)
MFC42D!CCmdTarget::OnCmdMsg+00000274 (cmdtarg.cpp, 302)
MFC42D!CDialog::OnCmdMsg+00000024 (dlgcore.cpp, 97)
MFC42D!CWnd::OnCommand+00000138 (wincore.cpp, 2099)
MFC42D!CWnd::OnWndMsg+00000053 (wincore.cpp, 1608)
MFC42D!CWnd::WindowProc+0000002E (wincore.cpp, 1596)
MFC42D!AfxCallWndProc+000000ED (wincore.cpp, 215)