用户管理  |   用户注册                                                                                    首 页软件下载教程中心办公指南flash动画文档下载办公公文

www.4oa.com - 中科软件园

投递文章 用户管理 投稿指南 资讯通告 :
站内搜索: 您的位置中科软件园 > 教程中心 > 操作系统 > Windows 2000 > 原理开发 > 教程内容

《Undocumented Windows 2000 Secrets》翻译 --- 3

2005-5-21 8:48:15  来源:本站整理  作者:不详 【 投递文章
内容提要:第一章Windows2000对调试技术的支持翻译:Kendiv更新:Monday,January17,2005枚举系统模块和驱动(Drivers)psapi.dll可以返回当前内存中的内核模块。这本是...

第一章  Windows 2000对调试技术的支持

翻译:Kendiv

更新:Monday, January 17, 2005

枚举系统模块和驱动(Drivers

psapi.dll可以返回当前内存中的内核模块。这本是非常简单的工作。psapi.dllEnumDeviceDrivers()函数接受一个PVOID类型的数组,它将用当前活动的内核驱动模块(active kernel-mode driver)的映像基址(image base address)来填充这个数组,这包括基本的内核模块ntdll.dllntoskrnl.exeWin32K.syshal.dllbootvid.dll。返回值是这些可执行文件映射到的虚拟内存地址(译注,也称作线性地址)。如果你使用内核调试器或其他调试工具检查这些地址的最初几个字节,你将清楚地认出那个有名的DOS stub程序,它以著名的Mark Zbikowski的首字母大写“MZ”开始,内含一个文本消息--This program cannot be run in DOS mode”或类似的东西。列表1-3展示了一个使用EnumDeviceDrivers()的简单函数,以及EnumDeviceDrivers函数的原型。

BOOL WINAPI EnumDeviceDrivers ( PVOID* lpImageBase,

                                DWORD  cb,

                                                        PDWORD lpcbNeeded);

PPVOID WINAPI dbgDriverAddresses( PDWORD pdCount )

{

       DWORD dSize;

       DWORD dCount = 0;

       PPVOID ppList = NULL;

       dSize = SIZE_MINIMUM * sizeof( PVOID );

       while ( (ppList = dbgMemoryCreate(dSize)) != NULL )

       {

              if ( EnumDeviceDrivers( ppList, dSize, &dCount) && (dCount < dSize) )

              {

                     dCount /= sizeof( PVOID );

                     break;

              }

              dCount = 0;

              ppList = dbgMemoryDestroy( ppList );

              if ( (dSize <<= 1) > (SIZE_MAXIMUM * sizeof( PVOID )))

              {

                     break;

              }

       }

       if ( pdCount != NULL )

       {

              *pdCount = dCount;

       }

       return ppList;

}

列表1-3   枚举系统模块地址

EnumDeviceDrivers()期望三个参数:一个数组指针,一个表示输入大小的值以及一个用于输出的类型为DWORD的变量。第二个参数指定了传入的数组的字节数,第三个参数表示复制到该数组中的字节数。因此,你必须将返回值除以sizeof(PVOID)来确定有多少个地址数据复制到了数组中。不幸的是,该函数不能帮助你确定该提供多大的数组,尽管它实际上知道有多少个Driver在运行。但它仅仅告诉你返回了多少字节,而且,如果数组太小,它会隐藏多出的字节。因此,你必须使用无聊的trial-and-error循环来确定适当的数组大小,就如同列表1-3所示的那样,只要返回值与数组大小相同就假定还有数据未复制到数组中。在刚开始时,代码中使用了一个合理的最小值--256(由SIZE_MINIMUM表示),这通常都足够大了,但是如果不够的话,在开始新的循环时,数组大小会增加为原来的2倍,直到获取了所有的指针或者数组大小超过了65,536。数组使用的内存缓冲区由两个帮助函数dbgMemoryCreate()dbgMemoryDestroy()提供,这两个函数只是Win32函数LocalAllocLocalFree的外包而已,这儿就不列出了。

BOOL WINAPI EnumDeviceDrivers( PVOID* lpImageBase,

                               DWORD  cb,

                                                    DWORD* lpcbNeeded)

{

       SYSTEM_MODULE_INFORMATION  smi;

       PSYSTEM_MODULE_INFORMATION psmi;

       DWORD                      dSize, i;

       NTSTATUS                   ns;

       BOOL                       fOk = FALSE;

       ns = NtQuerySystemInformation( SystemModuleInformation,

                                         &smi, sizeof(smi),NULL);

       if ( (STATUS_SUCCESS == ns) | (STATUS_INFO_LENGTH_MISMATCH == ns) )

       {

              dSize = sizeof(SYSTEM_MODULE_INFORMATION)

                        +  (smi.dCount*sizeof(SYSTEM_MODULE));

              if ( (psmi = LocalAlloc(LEME_FIXED,dSize)) != NULL )

              {

                     ns = NtQuerySystemInformation( SystemModuleInformation,psmi,dSize,NULL );

                     if ( ns == STATUS_SUCCESS )

                     {

                            for( i = 0; (i < psmi->dCount) && (i < cb/sizeof(DWORD));  i++)

                                   lpImageBase[i] = psmi->aModules[i].pImageBase;

                            *lpcbNeeded = i*sizeof(DWORD);

                            fOk = TRUE;

                     }

                     LocalFree(psmi);

                     if ( !fOk )  SetLastError( RtlNtStatusToDosError(ns) );

              }

       }

       else

              SetLastError( RtlNtStatusToDosError(ns) );

       return fOk;

}

列表1-4  EnumDeviceDrivers函数的示列

列表1-4列出了EnumDeviceDrivers()一种可能的实现方式。注意这并不是来自psapi.dll的原始代码。但通过C编译器它可以变成等效的二进制代码。为了保持简单干净,我省略了源代码中易分散注意力的细节,比如结构化异常等。在列表1-4的中间,你会看到NtQuerySystemInformation()函数作了很多工作。这是我非常喜欢的Windows 2000函数之一,因为该函数可以访问多种重要的数据结构,如驱动、进程、线程、句柄(handle)和LPC端口列表等等。我的文章“Inside Windows NT Sytem Data”(出版于199911月的Dr.Dobb’s Journal)在第一时间提供了有关该函数的内部信息及其搭档函数NtSetSystemInformation()文档化资料。另外的全面讲述这两个函数的文档可以在Gary Nebbett的《Indispendsable Windows NT/2000 Native API Reference》中找到。

不要过于担心列表1-4列出的EnumDeviceDrivers()函数的实现细节。我增加这些代码片断只是为了例举该函数有趣的一面,这像一根红线贯穿于psapi.dll。在使用SystemModuleInformation标志第二次调用NtQuerySystemInformation()获取了完整的驱动列表后,代码遍历驱动模块数组并将其pImageBase成员复制到调用者提供的指针数组(名为lpImageBase[])中。这似乎很正确,但除非你不知道NtQuerySystemInformation提供的模块数组所包含的其他信息。这些数据结构都是没有文档化的,但是我现在可以告诉你,这些信息同样是有关模块在内存中的大小、它们的路径和名称、引用计数(load counts)和其他一些标志信息的。甚至文件名在路径中的偏移量也是很容易就能得到的!,EnumDeviceDrivers()残忍的丢掉了所有这些有用的信息,仅仅保留了映像基址(Image Base address)。

所以如果你试图通过返回的指针来获取有关模块的更多信息,则肯定会失败。当你调用GetDeviceDriverFileName()来获取指定映像基址对应的文件路径时,猜猜psapi.dll会怎样做?它会运行与列表1-4类似的代码来获取完整的驱动列表,并遍历该列表来寻找指定的映像基址。如果它找到一个匹配项,就将其路径复制到调用者的缓冲区中。这难道很高效吗?为什么EnumDeviceDrivers不在它首次遍历驱动列表时就复制路径呢?按这样的方式实现此函数并没有多么困难。除去性能问题,这种设计还有另一个潜在的问题:如果在GetDeviceDriverFileName()执行之前指定的模块就已经被卸载了会怎么样呢?该模块的地址将不会出现在第二次获取的驱动列表中,GetDeviceDriverFileName()将会失败。我真不明白微软为什么会发布这样的DLL

[1] [2] [3]  下一页

(评论内容只代表网友观点,与本站立场无关!)[ 全部评论 ]

网友评论:

    用户名:

    评   分:100分 85分 70分 55分 40分 25分 10分 0分

    内 容:

                 (注“”为必填内容。) 验证码: 验证码,看不清楚?请点击刷新验证码

关于本站 - 网站帮助 - 广告合作 - 下载声明 - 友情连接 - 网站地图 -有事点这里