2.3.3 通知(Debug Event)是操作系统跟调试器交流的一种方法
通知,也叫做调试信息(Debug Events),是操作系统在某些事件发生的时候,通知调试器的一个手段。跟异常处理相似,操作系统在某些事件发生的时候,会检查当前进程是否有调试器加载。如果有,就会给调试器发送对应的消息,以便使用调试器进行观察。跟异常不一样的地方就是,只有调试器才会得到通知,应用程序本身是得不到的。同时调试器得到通知后不需要做什么处理,没有1st /2nd chance的差别。在Windbg帮助文件的Controlling Exceptions and Events主题里面,可以看到关于通知的所有代号。常见的通知有:DLL的加载、卸载,线程的创建、退出等。
案例分析:VB6的版本问题
客户用VB6开发的程序,在VB6 IDE调试的时候无法访问Access 2003创建的数据库,访问Access 97的数据库却是好的。如果换一台开发机,测试就一切正常。
这个问题的思路非常简单,既然只有一台机器有问题,说明是环境的原因。既然访问Access 97没问题,或许跟Access客户端文件,也就是DAO的版本有关。通过工具Windbg目录下的tlist工具检查进程中加载的DLL,发现有问题的机器加载的是dao350.dll,没有问题的机器加载的是dao360.dll。下一步就需要知道为什么加载的是dao350.dll?
DAO是一个COM对象,很有可能是通过COM对象加载的方法完成的。那么,可以采取1.2节中ShellExecute同时打开两个文件的处理方法,从创建COM的API: CoCreateInstanceEx开始,用wt命令跟踪整个函数的执行,保存下来后比较两种不同情况的异同。通过这个方法肯定是可以找出原因的,不过要想用wt命令一直跟踪到LoadLibrary函数加载这个DLL,可能需要执行一整天。所以,应该找一个可操作性更强一点的方法来检查。既然最后要追踪到LoadLibrary为止,那何不在这个函数上设置断点,观察检查DAO350.DLL加载起来的情况?
在LoadLibrary上设定断点并不是一个很好的方法。因为:
1. 加载DLL不一定要调用LoadLibrary的。可以直接调用Native API,比如ntdll!LdrLoadDll。
2. 假设有几十个DLL要加载,如果每次LoadLibrary都断下来,操作起来也是很麻烦的事情。虽然可以通过条件断点判断LoadLibrary的参数来决定是否断下来,但是设定条件断点也是很麻烦的。
最好的方法,就是使用通知,在moudle load的时候,系统给调试器发送通知。由于Windbg在收到moudle load通知的时候,可以使用通配符来判断 DLL的名字,操作起来就简单多了。首先,在Windbg中用sxe ld:dao*.dll设置截获Moudle Load的通知,当文件名是dao*.dll的时候,Windbg就会停下来。(关于Windbg的详细信息,以及这里使用到的命令,后面都有章节详细介绍)。看到的结果就是:
0:008> sxe ld:dao*.dll
ModLoad: 1b740000 1b7c8000 C:/Program Files/Common Files/Microsoft Shared/DAO/DAO360.DLL
eax=00000001 ebx=00000000 ecx=0013e301 edx=00000000 esi=7ffdf000 edi=20000000
eip=7c82ed54 esp=0013e300 ebp=0013e344 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c82ed54 c3 ret
ntdll!KiFastSystemCallRet
ntdll!NtMapViewOfSection
ntdll!LdrpMapViewOfDllSection
ntdll!LdrpMapDll
ntdll!LdrpLoadDll
ntdll!LdrLoadDll
0013e9c4 776ab4d0 0013ea40 00000000 00000008 kernel32!LoadLibraryExW
ole32!CClassCache::CDllPathEntry::LoadDll
ole32!CClassCache::CDllPathEntry::Create_rl
ole32!CClassCache::CClassEntry::CreateDllClassEntry_rl
ole32!CClassCache::GetClassObjectActivator
ole32!CClassCache::GetClassObject
ole32!CServerContextActivator::GetClassObject
ole32!ActivationPropertiesIn::DelegateGetClassObject
ole32!CApartmentActivator::GetClassObject
ole32!CProcessActivator::GCOCallback
ole32!CProcessActivator::AttemptActivation
ole32!CProcessActivator::ActivateByContext
ole32!CProcessActivator::GetClassObject
ole32!ActivationPropertiesIn::DelegateGetClassObject
ole32!CClientContextActivator::GetClassObject
ole32!ActivationPropertiesIn::DelegateGetClassObject
ole32!ICoGetClassObject
ole32!CComActivator::DoGetClassObject
ole32!CoGetClassObject
VB6!VBCoGetClassObject
VB6!_DBErrCreateDao36DBEngine
通过检查LoadLibraryExW的参数,可以看到:
0:000> du 0013ea40
0013ea40 "C:/Program Files/Common Files/Mi"
0013ea80 "crosoft Shared/DAO/DAO360.DLL"
从上面的信息可以看到:
1. DAO360不是通过CoCreateInstanceEx加载进来的,而是另外一个COM API: CoGetClassObject。所以如果对CoCreateInstanceEx做想当然的跟踪,就浪费时间了。
2. COM调用的发起者是VB6!_DBErrCreateDao36DBEngine这个函数。应该仔细检查这个函数。
有了前面DLL HELL 案例的教训,在检查这个函数前,首先检查VB6.EXE的版本。发现正常情况下的版本是6.00.9782,有问题的机器上的版本是6.00.8176。在有问题的机器上安装Visual Studio 6,SP6升级VB6版本后,问题解决。