标 题: 调试器设计(2) 
作 者: Tweek
时 间: 2010-2-26

最开始,调试器给我的感觉是:被调试程序是调试器的一个子程序,调试器完全包含和控制 被调试程序。相当于一种包含关系。

而且:调试器的基本循环。我一直觉得不能裁开。只能这样不断循环。
while ( TRUE == bContinue )
    {
  
         bContinue = WaitForDebugEvent ( &stDE , INFINITE ) ;
{
……
}
    ContinueDebugEvent(stDE.dwProcessId, stDE.dwThreadId, DBG_CONTINUE);
  }

但是,后来。才发现,很多都是微软的猫腻。

CreateProcess和直接运行exe没有本质区别。
WaritForDebugEvent仅仅是等待调试事件的返回。
ContinueDebugEvent仅仅是继续被挂起的被调试程序。
任何调试事件 都是内核捕捉处理。内核在捕捉到事件后,处理保存,然后,发送调试事件信息的副本过去。
细细想想ring3的调试器,不过是在被调试进程被挂起的时候,对其信息的获取和修改。

调试事件和异常,是被调试程序自己的事情。我相信 exe在运行的时候 内核对程序也是以调试方式进行的。而ring3的调试器,不过是内核把自己的东西 分了一份出来而已。

还是 来看一个例子:test是 可以自己产生int3断点。可以附加进程进行调试。可以创建子进程的 测试程序。



我们直接点击int3.程序出错,提示是否需要调试



我们可以用vs调试程序。

关闭程序。重新运行,并创建子进程。



子进程和父进程都是同一exe模块。

输入父进程PID,debug父进程。

然后弹出



CREATE_PROCESS_DEBUG_EVENT的调试事件。接着点击确定后



大量的 LOAD_DLL_DEBUG_EVENT 事件。

我们知道,父进程的创建和加载dll,先于子进程。但是,子进程在附加调试父进程的时候,却可以捕捉到已经过去的调试事件。

那么,调试器捕捉到的异常,都应该是被进程内核提前获得和保留的。我相信,内核态有一个长长的队列,按照顺序保存这进程出现的所有调试事件。一旦有其他进程有了获得的权限后,就按照顺序,通过底层发给相应的进程。

我们继续疯狂,点掉大量的对话框,最后……  没有对话框了。程序出现未响应状态。这是因为里面程序在等待调试事件。这时,回到父进程,点击int3.



子进程捕捉到 异常。



异常地址有点怪,应该是被重定位了。

然后,父进程退出 系统,但是退出的时候,并没有提示,是否需要调试。那么如何知道,程序已经被其他程序调试。系统应该保留了一个队列,保存调试事件信息。如果遇到系统无法处理的事件,那么将它放入队列,检测是否有其他进程在调试,如果有,发送过去,并清空刚才的那个信息。如果没有,则提示是否需要调试,并调用vs附加相应进程。

虽然,中断和异常都会被内核获得,然后保留。但是,并不是所有的异常都会需要调试器处理。有些系统自己故意设置的int3断点,就会被系统自己处理掉,但是,同样会返回给调试器调试异常。

我用我自己写的一个简单调试引擎加载debuggee



第一个异常断点是在0x770ce60e(这个断点的作用在介绍int3断点的时候介绍)

查看下内存(上次说过,dll是映射的,所以查看哪个进程的内存空间,Ntdll加载的地址都是一样的)



中断在Ntdll上。

再用刚才的调试引擎反汇编



果然是int3.但是,系统并没有崩溃,或者提示错误,只是将这个异常平静的发送给了调试器。

对于程序可不可以在自己进程空间对自己进行调试?我没有测试过。不过可以想到,微软是不会这样设计的,windows的调试是为了分析和处理异常的,不是用来完成破解。如果,程序可以自己处理异常,那么还需要把把自己产生的异常,再发给自己一次吗?

最后,再完整的看看 程序从被加载到被调试的大概过程:

调试器用CreateProcess或者DebugActiveProcess得到获取一个进程所有调试事件的权限(这个权限仅仅是为了后面的调试API的使用)。然后,被调试进程在出现异常的时候,马上被进程的内核捕捉到,进程被系统挂起。如果内核代码有处理这种问题的方法,会在被处理后,发送相关信息到一个队列,调试器利用WaitForDebugEvent在这个队列里面得到调试事件(需要有调试权限才能执行这个函数)。在进行完一些操作后,调用ContinueDebugEvent 继续执行调试事件。等待下一次异常事件的到来。

这个需要提到一个问题ContinueDebugEvent的参数DBG_EXCEPTION_NOT_HANDLED和DBG_CONTINUE如果选择前一个,将直接执行被挂起线程。如果选择后一个参数,将停止所有异常处理然后执行挂起线程。我不太明白msdn上说的停止异常处理是指的调试器还是被调试程序。但是,在我测试的范围里,还没有感觉出这个两个参数在ring3级体现的不同。

(附件是测试程序,在D盘根目录下运行。)
                                                                                                           待续……

上传的附件 teste.rar