首页
社区
课程
招聘
[求助]关于windbg调试进程栈的问题
发表于: 2012-6-4 11:27 5658

[求助]关于windbg调试进程栈的问题

2012-6-4 11:27
5658
问题: 下文是《通往WinDbg的捷径(一)》这篇文章关于解决调试死锁的问题。请问如何从输出中判断出有什么临界资源,然后被什么进程调用?

解决实际的问题
调试死锁问题
当我们的应用程序挂起或停止响应时,最自然的问题是:它现在正在做什么?它在哪里被困住了?当然,我们可以用Visual Studio调试器附上应用程序,检查所有线程的调用栈。但我们同样可以用CDB,而且会更快一些。下列命令将使CDB以入侵模式附上应用程序,打印所有的调用栈,把结果保存在日志文件里,然后退出:
  cdb -pv -pn myapp.exe -logo out.txt -lines -c "~*kb;q"
('kb'命令要求CDB打印当前线程的调用栈;'~*'前缀要求CDB在进程所有已存在的线程里重复执行'kb'命令)。
[/URL] DeadLockDemo.cpp是一个演示典型的死锁问题的例子。如果你编译并运行,它的工作线程马上会被困住,如果我们运行上述的命令来查看应用程序的线程正在做什么,将看到下列类似的内容(在这,以及后面,我们将省略启动消息):
.  0  Id: 6fc.4fc Suspend: 1 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr  Args to Child              
0012fdf8 7c90d85c 7c8023ed 00000000 0012fe2c ntdll!KiFastSystemCallRet
0012fdfc 7c8023ed 00000000 0012fe2c 0012ff54 ntdll!NtDelayExecution+0xc
0012fe54 7c802451 0036ee80 00000000 0012ff54 kernel32!SleepEx+0x61
0012fe64 004308a9 0036ee80 a0f63080 01c63442 kernel32!Sleep+0xf
0012ff54 00432342 00000001 003336e8 003337c8 DeadLockDemo!wmain+0xd9
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 154]
0012ffb8 004320fd 0012fff0 7c816d4f a0f63080 DeadLockDemo!__tmainCRTStartup+0x232
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\crt0.c @ 318]
0012ffc0 7c816d4f a0f63080 01c63442 7ffdd000 DeadLockDemo!wmainCRTStartup+0xd
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\crt0.c @ 187]
0012fff0 00000000 0042e5aa 00000000 78746341 kernel32!BaseProcessStart+0x23

   1  Id: 6fc.3d8 Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr  Args to Child              
005afc14 7c90e9c0 7c91901b 000007d4 00000000 ntdll!KiFastSystemCallRet
005afc18 7c91901b 000007d4 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
005afca0 7c90104b 004a0638 00430b7f 004a0638 ntdll!RtlpWaitForCriticalSection+0x132
005afca8 00430b7f 004a0638 005afe6c 005afe78 ntdll!RtlEnterCriticalSection+0x46
005afd8c 00430b15 005aff60 005afe78 003330a0 DeadLockDemo!CCriticalSection::Lock+0x2f
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 62]
005afe6c 004309f1 004a0638 f3d065d5 00334fc8 DeadLockDemo!CCritSecLock::CCritSecLock+0x35
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 90]
005aff6c 004311b1 00000000 f3d06511 00334fc8 DeadLockDemo!ThreadOne+0xa1
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 182]
005affa8 00431122 00000000 005affec 7c80b50b DeadLockDemo!_callthreadstartex+0x51
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 348]
005affb4 7c80b50b 003330a0 00334fc8 00330001 DeadLockDemo!_threadstartex+0xa2
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 331]
005affec 00000000 00431080 003330a0 00000000 kernel32!BaseThreadStart+0x37

   2  Id: 6fc.284 Suspend: 1 Teb: 7ffdc000 Unfrozen
ChildEBP RetAddr  Args to Child              
006afc14 7c90e9c0 7c91901b 000007d8 00000000 ntdll!KiFastSystemCallRet
006afc18 7c91901b 000007d8 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc
006afca0 7c90104b 004a0620 00430b7f 004a0620 ntdll!RtlpWaitForCriticalSection+0x132
006afca8 00430b7f 004a0620 006afe6c 006afe78 ntdll!RtlEnterCriticalSection+0x46
006afd8c 00430b15 006aff60 006afe78 003332e0 DeadLockDemo!CCriticalSection::Lock+0x2f
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 62]
006afe6c 00430d11 004a0620 f3e065d5 00334fc8 DeadLockDemo!CCritSecLock::CCritSecLock+0x35
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 90]
006aff6c 004311b1 00000000 f3e06511 00334fc8 DeadLockDemo!ThreadTwo+0xa1
  [c:\tests\deadlockdemo\deadlockdemo.cpp @ 202]
006affa8 00431122 00000000 006affec 7c80b50b DeadLockDemo!_callthreadstartex+0x51
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 348]
006affb4 7c80b50b 003332e0 00334fc8 00330001 DeadLockDemo!_threadstartex+0xa2
  [f:\rtm\vctools\crt_bld\self_x86\crt\src\threadex.c @ 331]
006affec 00000000 00431080 003332e0 00000000 kernel32!BaseThreadStart+0x37
调用栈(和源行号)暗示ThreadOne正在占用临界区CritSecOne并等待临界区CritSecTwo,然而ThreadTwo正占用临界区CritSecTwo并等待临界区CritSecOne。这是典型的“lock acquisition order”死锁例子,在那里,两个线程需要得到同一组同步的对象,以不同的顺序使用。如果你想避免这种类型的死锁,必须保证所有的线程以相同的顺序得到所需的同步对象(在这个例子里,ThreadOne和ThreadTwo能同意首先得到CritSecOne,然后得到CritSecTwo来避免死锁)。
在默认情况下,'kb'命令只显示调用栈的前20帧。如果你想查看更多的栈帧,你可以显式指明显示的栈帧数量(例如,'kb100'命令要求调试器显示100帧)。在WinDbg会话里,可以用.kframes命令改变随后命令的默认限制。
我们的例子只包含了三个简单的线程,很容易看出哪个线程应该为死锁负责。在大应用程序里,很难找出可疑的线程并进行验证。那我们应该怎么做呢?在大部分情况下,我们应该知道那个没有正常运转的线程(否则,我们怎么会注意到应用程序出现异常了呢?)。通常,这个线程是在等待同步对象,这个对象因为某些原因暂时不可用。这个对象为什么不可用呢?如果我们知道哪个线程正在占用这个对象(拥有它,换句话说),应该能答出这个问题。如果这个对象碰巧在临界区,!locks命令应该能帮助我们识别出它的当前所有者。当不带参数使用时,这条命令显示应用程序线程正在占用的临界区的列表。输出的内容不包括已释放的临界区。
让我看看实际使用中的!locks命令:
  cdb -pv -pn myapp.exe -logo out.txt -lines -c "!locks;q"
下面是这条命令的输出内容(同样以DeadLockDemo.cpp为例):
CritSec DeadLockDemo!CritSecOne+0 at 004A0620
LockCount          1
RecursionCount     1
OwningThread       3d8
EntryCount         1
ContentionCount    1
*** Locked

CritSec DeadLockDemo!CritSecTwo+0 at 004A0638
LockCount          1
RecursionCount     1
OwningThread       284
EntryCount         1
ContentionCount    1
*** Locked

仔细查看了40个临界区
查看!locks命令的输出(尤其是OwningThread字段),我们可以推断出临界区CritSecOne被ID为0x3d8的线程占用,临界区CritSecTwo被ID为0x284的线程占用。我们可以在'kb'命令的输出内容(在前面的输出里)里找出这些IDs对应的线程。
如果应用程序使用其它种类的同步对象(例如,互斥),识别它们的所有者将更难一些(需要内核调试器),我准备在以后的文章中再介绍这部分内容

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//