{
if (dCount <= dSize)
{
dCount /= sizeof( HMODULE );
break;
}
}
else
{
dCount = 0;
}
phList = dbgMemoryDestroy(phList);
if ( !(dSize = dCount) ) break;
}
}
if ( pdCount != NULL) *pdCount = dCount;
return phList;
}
列表1-7; 枚举进程模块
EnumProcessModules()返回指定进程所有模块的句柄的引用 。在Windows 2000中,一个HMODULE只是简单的模块映像基址 。在SDK头文件windef.h中,HMODULE被定义为HINSTANCE的别名,二者都是HANDLE类型 。严格的来讲HMODULE并不是一个句柄 。通常,句柄是系统管理的一个表的索引,可通过此表来查找对象属性 。系统返回的所有句柄都有一个与特定对象相关的计数器,在一个对象的所有句柄没有返回系统时,该对象不能从内存中被移除 。Win32 API提供了CloseHandle()函数用于关闭句柄 。该函数与Native API NtClose()等价 。有关HMODULEs最重要的事情是,这些“handles”不需要关闭 。
另一件让人困惑的事是,事实上,模块句柄通常并不被保证是一直有效的 。SDK的GetModuleHandle()函数文档提示到,在多线程程序中必须更加注意模块句柄,因为一个线程可以通过卸载HMODULE引用的模块而让另一个线程拥有的HMODULE无效 。在多任务环境下,一个程序(如调试器)使用另一程序的模块句柄时也许注意这一点 。这似乎使HMODULEs没有多大用处了,但是,在下面两种情况中,HMODULE的有效性会保持足够长的时间:
1.由LoadLibrary()或LoadLibraryEx()返回的HMODULE在进程调用FreeLibrary()之前都会一直有效,由于这些函数包含了模块引用计数,所以即使在多线程程序中,这也会阻止模块被意外卸载 。
2.如果HMODULE指向的模块会永久的存在,那么它也会一直有效 。例如,所有Windows 2000内核组件(不包括内核模式的驱动程序)总是被映射到每个进程的相同固定地址上,并且在进程生命期里一直在那里 。
不幸地是,这些情况并不适用于EnumProcessModules()函数返回的模块句柄,至少通常不行 。复制到调用者提供的缓冲区中的HMODULE,在获取进程快照那一刻其所表示映像基址是有效的 。稍后,进程可能调用FreeLibrary()来释放一个或多个模块,并将其从内存中移除,此时它们的句柄将无效,随后进程很有可能立即调用LoadLibrary()加载了另一个DLL,而此新模块恰好映射到了前面释放的地址上 。这看上去是不是很熟悉?是的,同样的问题也存在于EnumDeviceDrivers()的指针数组和EnumProcesses()函数的ID数组 。不过,这些问题是可以避免的 。psapid.dll通过调用未文档化的API函数来完成数据收集工作后,考虑这些数据的完整性,可返回一个完整的请求对象的快照,其中应包括所有感兴趣的属性信息 。这样就没有必要在稍后调用另一个函数来获取附加的信息了 。我的观点是,psapi.dll的设计过于简单,因为它忽略了数据的完整性,这也是我不会将此DLL作为一个专业调试工具的基础的原因 。
与EnumDeviceDrivers()和EnunProcesses()函数相比EnumProcessModules()函数算是个好公民了,因为如果调用者提供的缓冲区不能放下全部的输出数据,它会准确地提示有多少字节没有复制 。注意列表1-7没有包括一个循环,在那里缓冲区会不断增大直到足够的大 。然而,仍然需要trial-and-error循环,因为在下一次调用时,EnumProcessModules报告的所需大小可能已经无效了(如果指定进程在两次调用之间又加载了新的模块) 。因此,列表1-7中的代码将不断枚举模块直到EnumProcessModules()报告需要的缓冲区等于或小于实际可用大小,或者出现了错误 。
我不想描述EnumProcessModules()的等价函数,因为该函数要比EnumDeviceDrivers和EnumProcesses稍微复杂些,它涉及几个未文档化的数据结构 。基本上,它还是通过调用NtQuerySystemInformation()函数(当然,该函数也没有文档化)来获取目标进程环境块(PEB)的地址,通过该地址可获取一个模块信息链表 。因为不管是PEB还是这个链表在调用进程的地址空间都是无法直接使用的,EnumProcessModules调用Win32 API ReadProcessMemory()(该函数有文档记载)来遍历目标进程的地址空间 。顺便说一下,PEB结构的布局将在第7章讨论,在附录C中,可以找到该结构的定义 。
推荐阅读
- Windows2000的日志文件详述及删除方法
- 命令篇 Windows 2000/XP的CMD命令教程 (3)
- 2 《Undocumented Windows 2000 Secrets》翻译 --- 第三章
- 如何安装/卸载 Windows 2000 的公钥证书颁发机构
- 使用 Windows 2000 备份程序备份和还原系统状态
- 8 《Undocumented Windows 2000 Secrets》翻译 --- 第四章
- 佐助见到秽土鼬是哪集
- Windows 2000 上配置和应用安全模板
- 如何在 Windows 2000 中启用自动登录
- 2 《Undocumented Windows 2000 Secrets》翻译 --- 第四章