最近由于项目需要,被老板安排开发一个WINDOWS USER MODE DLL, 即WINDOWS用户模式下的动态链接库。
写代码,实现具体的功能这类初等工作,对于大多数软件开发工程师来讲,只是时间与工作量上的问题,具体的WINDOWS DLL的知识点,本人参考了Jeffrey Richter的《WINDOWS核心编程》中的DLL几章,不再熬述。
这里谈一下关于USER MODE DLL在调试过程中所涉及到的一些知识点,以及它们彼此之间的关系:
1. DebugBreak / int 3;
2. OutputDebugString
3. bcdedit /debug (on/off)
4. Windbg user/kernel mode debug
5. IsDebuggerPresent() 函数返回值
6. DebugView.exe
7. EPROCESS.debugport
8. PEB.beingdebugged
1. DebugBreak / __asm int 3; 起到中断到调试器的功能。
在DLL初步编码完成,第一次上系统跑的时候,可以在DllMain中尝试加入它们,以确定DLL是否被服务或者EXE正确调用。
当然,也可以在其它阶段的函数中调用,不过,笔者在该断点HIT之时,通过.reload /f命令,正确加载了调试符号,就可以通过动态设置、取消断点的方式来继续进一步的调试。
2. OutputDebugString & DebugView.exe & Windbg user/kernel mode & IsDebuggerPresent
以及EPROCESS.debugport & PED.beingdebugged
这些个因素有他们之间的彼此关联。
首先,必须的一步,在Win7,8,8.1上使用DebugView.exe必须在注册表中加入:
HKLM/SYSTEM/CurrentControlSet/Control/Session Manager
Debug Print Filter (Key)
DWORD(type),DEFAULT (name),0xF(value)
其次,OutputDebugSting函数本质上是通过异常机制工作的(raise exception), 具体的理论方面的内容请参考张银奎《software debugging》, chapter 10.
这里只谈本人的一点实践经验。
第一,在目标进程没有被Windbg Attach的时候,即目标进程不处于被调试状态,在这种状态下,目标程序的EPROCESS.debugport == NULL, PEB.beingdebugged == FALSE.
IsDebuggerPresent()函数的判断依据实则为PEB.beingdebugged, 在没有被windbg attach时,返回为FALSE。
如果在你的代码中,有下面的代码段,OutputDebugString输出"Debugger not Present"到DebugView.exe中.
if(IsDebuggerPresent()){
OutputDebugString(TEXT("Debugger Present\n"));
}else{
OutputDebugString(TEXT("Debugger not Present\n"));
}
但是,当有windbg attach到目标进程,该目标进程进入到被调试状态,以上代码则输出“Debugger Present”到Windbg的Command窗口。
值得注意的是,IsDebuggerPresent()只与windbg attach有关,与系统是否处于kernel debug无关。
进一步讲,当目标进程被windbg attach后,上述进程处于user mode debug状态,进入user mode debug状态,则影响了EPROCESS.debugport与PEC.beingdebugged.
而通过设置NET双机调试,利用DebugBreak/int 3; 将DLL中断到windbg, 该行为并不影响以上两个变量,因为,此时目标进程处于kernel mode debug,从而IsDebuggerPresent返回为FALSE。
3. bcdedit /debug on/off & DebugBreak/int 3;
bcdedit /debug on的状态下, DebugBreak/int 3;会中断到调试器,如果此时系统没有双机联调,或者进程没有被attach, 具体表现则是系统一直pending.
bcdedit /debug off的状态下,DebugBreak/int 3;产生异常, 但由于该异常并未中断到调试器,该异常得不到正确处理,系统最终将该进程结束。
4. bcdedit /debug on/off & Windbg user/kernel mode & DebugBreak/int 3;
最后,来讲一讲他们之间的关系。
DebugBreak/int 3;作为一种异常,总归要有东西去处理。
假设, 被调试机器(target),既通过NET连接到了调试主机(host,此时bcdedit /debug on), 又在本地通过WINDBG ATTACH到了目标进程。
在这种情况下,DebugBreak/int 3;首先被本地WINDBG处理, 既中断到本地的WINDBG。
如果目标进程未被WINDBG ATTACH,则该异常中断到主机的WINDBG。
但是,如果此时也没有远程(NET)HOST WINDBG连接,则造成一个无东西处理的内核异常,则出现上面所说的,系统PENDING。
而在bcdedit /debug off并且既没有远程也没有本地WINDBG,则这是无东西处理的用户异常,最终,系统将该进程终止掉了。
后记:
EPROCESS的成员DebugPort是一个数据结构,作为调试子系统与调试器交互的一种通道(手段)。
其主要的数据成员包括一个EVENT用来信号通知,一个MUTEX用来同步链表操作,以及LIST_ENTRY用来排列调试事件。
DebugBreak的内核函数为DbgBreakPoint.
写代码是正面交战,敌人在明处,花时间与功夫,一点点地编码调试总归能够完成任务。
调试则正好相反,调试环境的搭建,调试关键知识的掌握,以及躲在暗处的BUG,从经验与系统知识上来讲,需要更加扎实的功底。
前阵子翻过一下张银奎的《software debugging》, 1000多页的内容,让人看了后面,忘了前面。
纠其原因,就是看书的方法不当,以软件开发的的思维习惯与软件开发的经验去看这本书,只能是花了时间功夫多,收获甚少。
但在开发这个DLL项目过程当中,实实在在地将一些调试方面的知识应用在实际当中,有了问题查找资料,然后再实践,之后,通过《software debugging》这本书,confirm遇到的这些调试知识点,调试技巧,达到举一反三,融会贯通的效果,将调试的基本观念用在软件开发的过程当中,才是实实在在的积小流,积跬步的过程。