3.3 异常与事件
在调试器语境中,事件是一个基本概念,Windbg是事件驱动的。Windows操作系统的调试子系统,是“事件”的发生源。调试器的所有操作,都是因事件而动,因事件被处理而中继。Windows定义了9类调试事件,异常是其中一类(ID为1)。所以异常和事件,这二者是前者包含于后者的关系。
系统对各种异常和调试事件进行了分类,执行sx命令可以列出针对当前调试目标的异常或非异常事件的处理。下面是一个片段:
0:009> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - break
ld - Load module - break
(only break for livekd.exe)
ud - Unload module - ignore
ser - System error - ignore
ibp - Initial breakpoint - break
iml - Initial module load - ignore
out - Debuggee output - output
av - Access violation - break - not handled
asrt - Assertion failure -break - not handled
aph - Application hang - break - not handled
bpe - Break instruction exception - break
bpec - Break instructionexception continue - handled
eh - C++ EH exception - second-chance break -not handled
clr - CLR exception - second-chance break -not handled
clrn - CLR notificationexception - second-chance break - handled
cce - Control-Break exception - break
cc - Control-Break exception continue -handled
cce - Control-C exception - break
cc - Control-C exception continue - handled
dm - Data misaligned - break - not handled
dbce - Debugger commandexception - ignore - handled
gp - Guard page violation - break - nothandled
ii - Illegal instruction - second-chancebreak - not handled
ip - In-page I/O error - break - not handled
dz - Integer divide-by-zero - break - nothandled
iov - Integer overflow - break - not handled
ch - Invalid handle - break
hc - Invalid handle continue - not handled
lsq - Invalid lock sequence - break - not handled
isc - Invalid system call - break - nothandled
3c - Port disconnected - second-chance break- not handled
svh - Service hang - break - not handled
sse - Single step exception - break
ssec - Single stepexception continue - handled
sbo - Security check failure or stack bufferoverrun - break - not handled
sov - Stack overflow - break - not handled
vs - Verifier stop - break - not handled
vcpp - Visual C++ exception- ignore - handled
wkd - Wake debugger - break - not handled
rto - Windows Runtime Originate Error -second-chance break - not handled
rtt - Windows Runtime Transform Error -second-chance break - not handled
wob - WOW64 breakpoint - break - handled
wos - WOW64 single step exception - break -handled
* - Other exception - second-chance break -not handled
可以看到这几个调试事件,当发生进程退出(Exit Process)和初始化断点(Initial breakpoint)事件的时候,调试器应当被中断(Break)。模块加载(Load Modual)以及有调试输出(Debuggen Output)时,需要输出相关信息;其他的都被忽略掉,不做处理(Ignore)。 我们分析一下前两个事件。使用调试器调试记事本进程时,不管是用.attach挂载方式还是.create创建方式,在调试器正式侵入记事本进程前,都会有一个中断(Initial breakpoint异常);调试开始后运行一段时间,在外面将记事本关闭,又会发生一个中断(Exit Process异常)。
可以通过Debug|Event Filters…打开事件设置对话框。这个对话框中列出了全部调试事件,用户可分别对它们进行设置。
这个对话框列出了对于当前调试会话可用的全部调试事件。针对每个调试事件,可设置其属性。右列Execution和Continue两组单选键,分别表示事件的中断属性与中继属性。右列Argument按钮可设置调试事件执行参数(上图中Load Module事件有一个Kernel32.dll参数,即当Kernel32.dll模块被加载时,调试器将被中断),Commands按钮可设置事件两轮机会发生时的执行命令。
更细致的内容,本章无力铺陈,请读者参阅《Windows 高级调试》(Mario & Daniel 2009 机械工业出版社)第三章,及《软件调试》(张银奎 2008 电子工业出版社)第9、30章相关内容。
sxr:
此命令将当前所有对调试事件的设置,恢复到调试器的默认设置。最后一个字母r表示Reset。
sx{e|d|n|i}:
这4个命令分别代表了图8-38中Execution组(中断属性)中的四个按钮,即Enable、Disable、Output、Ignore。Enable是开启中断,Disable是禁止事件中断(但对于异常,只禁止第一轮机会,第二轮机会到来时仍会中断到调试器),Output是禁止中断但会输出相关信息,Ignore表示完全忽略这个事件(对于异常,Output和Ignore两选项使得两轮机会都不会中断到调试器)。
sx{e|d|n|i} -h:
上述命令如果带上-h选项,就不是设置中断属性,而是设置中继属性了。对应了图8-38中的Continue组。其中sxe –h表示Handled,se{d|n|i} –h都表示Not Handled。
下面继续介绍异常、事件相关的其他调试命令:
.lastevent:
显示最近发生的一个调试事件,往往是导致中断发生的那个。下图显示的是一个很典型的初始化断点引发的中断事件。
.exr:
此命令显示一个异常记录的详细内容,传入一个异常记录地址:
§ .exr 记录地址
如果仅仅为了显示最近的一条异常记录,可以用-1代替异常记录地址:
§ .exr -1
由于异常是事件的一种,所以使用.exr -1命令得到的异常,可能和使用.lastevent命令获取的事件(其实是异常),是同一个。但二者显示的信息各有侧重点。请对照图8-39看下面,同样的初始化断点异常,使用.exr命令时所显示的信息:
0:009> .lastevent
Last event: 4f0.1538: Breakinstruction exception - code 80000003 (first chance)
debugger time: Fri Aug 23 16:58:02.995 2013(UTC + 8:00)
还有一个类似的命令:!cppexr,他分析并显示一个C++异常信息。
.bugcheck:
此命令不带参数。在内核环境下,显示当前bug check的详细信息;可用于活动调试或者crash dump调试环境中。用户环境不可用。见下图:
0:009> .exr -1
ExceptionAddress:0000000077090530 (ntdll!DbgBreakPoint)
ExceptionCode: 80000003 (Break instructionexception)
ExceptionFlags: 00000000
NumberParameters: 1
Parameter[0]: 0000000000000000
!analyze:
此命令分析当前最近的异常事件(如果在进行dump分析,则是bug check),并显示分析结果。这个异常事件,就是上面.lastevent命令对应的事件。
§ -v:显示异常的详细信息,这个选项在调试错误的时候,最有用。
§ -f:f是force的缩写。强制将任何事件都当作异常来分析,即使仅仅是普通的断点事件。将因此多输出一些内容。
§ -hang:这个选项很有用,对于遇到死锁的情况,它会分析原因。在内核环境中,它分析内核锁和DPC栈;在用户环境中,它分析线程的调用栈。用户环境中,调试器只会对当前线程进行分析,所以一定要将线程环境切换到最可能引起问题的那个线程中去,才有帮助。这个参数非常有用,当真的遇到死锁时,它可以救命(另一个分析死锁的有效命令是!locks)。
§ -show bug-check-代码 [参数]:在内核环境下,显示指定的bug check的详细信息。
!error:
此命令和VC里面内置的errlook工具类似(请有兴趣的读者使用作者编写的免费软件e-look,它比errlook功能更好且易于使用)。用来根据错误码,查看对应的可读错误信息。微软系统中常用的全局错误码有两套,一套是Win32错误码,通过函数GetLastError()获得的值;另一套是NTSTATUS值。!error命令对这二者都能支持。区别的方法,若错误码后面无参数1,则为win32错误码;否则就是NTSTATUS错误码。
比如,获取错误码为2的Win32错误信息,可用:!error 2
获取错误码为2的NTSTATUS错误信息,可用:!error 2 1
!gle:
此命令是Get Last Error的缩写。它调用Win32接口函数GetLastError()取得线程的错误值,并打印分析结果。如果带有-all选项,则针对当前进程的所有线程(内核环境下为所有用户线程)执行GetLastError()操作;否则仅针对当前线程。
gh/gn:这两个命令是g命令的扩展。
gh是go with Exception handled的缩写,意思是:把异常标识为已处理而并继续执行程序;注意这里面的措辞,仅仅把异常“标识为”已处理,而并非真的被处理了。gh的作用在于,当遇到某个可以忽略的非致命异常时,将它先放过一边,而继续执行程序。
而gn是go with Exception nothandled的缩写,意思是,对异常不进行任何处理,而继续执行程序。这时候,程序自己的异常处理模块将有机会处理异常。