热门搜索:Photoshop 平面设计 Linux Vista Windows ASP.NET qq word 病毒 XP Excel 标志设计 

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

来源:ccxunmeng(读取中...) 2005-7-28 【字体: 】 切换为

列表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

枚举活动进程

psapi.dll的另一个典型工作就是枚举当前系统中运行的进程。为此目的,该DLL提供了EnumProcesses()函数。该函数的工作与EnumDeviceDrivers()十分类似,不过返回的是进程ID而不是虚拟地址了。再次提示,该函数并不会提示缓冲区大小不足,因此我们还需再次使用trial-and-error循环,如列表1-5所示,这些代码和列表1-3很相似,除了有些不同的符号和类型名称。

一个进程ID是一个全局数字标签可在整个系统中唯一标识一个进程。进程和线程ID都取自同一个数字池(pool of numbers),从以0开始的Idle进程,在同一时间,所有运行的进程和线程都不会有相同的ID。但是,当一个进程结束后,另一个进程可能会再次使用该结束进程或线程的ID。因此,在X时间获取的一个进程IDY时间可能会代表另一个完全不同的进程。也有可能在其使用的那一刻还没有定义或者指定给了某个线程。所以,EnumProcesses()返回一个简单的进程ID列表并不能可靠的代表当前系统活动进程的快照。如果考虑该函数的实现方式,这个设计缺陷真是无法原谅。列表1-6psapi.dll另一个函数的克隆,大致勾勒出了EnumProcessees()的基本动作。和EnumDeviceDrivers()类似,它也依赖NtQuerySystemInformation()函数,不过在调用时,用SystemProcessInformation代替了SystemModuleInformation。注意列表1-6中间的循环,在哪儿lpidProcess[]数组被来自SYSTEM_PROCESS_INFORMATION结构中的数据填充。没什么好惊奇的,该结构也没有文档化。

BOOL WINAPI EnumProcesses( DWORD* lpidProcess,

                           DWORD  cb,

                                             DWORD* lpcbNeeded);

PDWORD WINAPI dbgProcessIds( PDWORD pdCount )

{

       DWORD dSize;

       DWORD dCount = 0;

       PDWORD pdList = NULL;

       dSize = SIZE_MINIMUM * sizeof( DWORD );

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

       {

              if ( EnumProcesses( pdList, dSize, &dCount) && (dCount < dSize) )

              {

                     dCount /= sizeof( DWORD );

                     break;

              }

              dCount = 0;

              pdList = dbgMemoryDestroy(pdList);

              if ( (dSize <<= 1) > (SIZE_MXAIMUM*sizeof(DWORD)) )  break;

       }

       if ( pdCount != NULL ) *pdCount = dCount;

       return pdList;

}

列表1-5  枚举进程ID

    在看过EnumDeviceDrivers()是如何浪费从NtQuerySystemInformation()返回的数据后,不幸的是,EnumProcesses也是和其类似的函数,但,事实上,这个函数更糟糕!因为可用的进程信息要远多于驱动模块的信息,因为进程数据之后还包含很多有关系统中每个线程的详细信息。在我写下这段文字时,我的系统正运行着37个进程,调用NtQuerySystemInformation()产了一个24,488字节的数据块!而当EnumProcesses()处理完这些数据后,仅剩下了148字节,这些刚好够存放37个进程ID

尽管EnumDeviceDirvers()让我有些难过,但EnumProcesses()却真正伤害了我的心。如果你需要使用未文档化API函数的理由,那这两个函数就是最好的证据。如果实际的工作只需一步既可完成,那为什么还要使用如此低效的函数呢?为什么不自己调用NtQuerySystemInformation()函数自由的获取感兴趣的系统信息?微软提供的许多系统管理工具都依赖于NtQuerySystemInformation()而不是psapi.dllso why settle for less?

BOOL WINAPI EnumProcesses( PDWORD lpidProcess,

                         DWORD  cb,

                                           PDWORD lpcbNeeded)

{

       PSYSTEM_PROCESS_INFORMATION pspi, pSpiNext;

       DWORD                       dSize, i;

       NTSTATUS                    ns;

       BOOL                        fOk = FALSE;

    // 0x8000 = 32KB

       for (dSize=0x8000; ((pspi = LocalAlloc(LMEM_FIXED,dSize)) != NULL);

            dSize += 0x8000)

       {

              ns = NtQuerySystemInformation( SystemProcessInformation,pspi,

                                                dSize, NULL);

              if ( STATUS_SUCCESS == ns )

              {

                     pSpiNext = pspi;

                     for ( i=0; i < cb/sizeof(DWORD); i++ )

                     {

                            lpidProcess[i] = pspiNext->dUniqueProcessId;

                            pSpiNext = (PSYSTEM_PROCESS_INFORMATION)

                                          ((BYTE)pSpiNext+pSpiNext->dNext);

                     }

                     *lpcbNeeded = i * sizeof(DWORD);

                     fOk = TRUE;

              }

              LocalFree(pspi);

              if ( fOk || (ns != STATUS_INFO_LENGTH_MISMATCH) )

              {

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

                     break;

              }

              return fOk;

       }

列表1-6  EnumProcesses()函数的示例实现

枚举进程模块

    一但你从EnumProcess()返回的进程列表中发现了你感兴趣的进程ID,你可能会想知道在此进程的虚拟地址空间中加载了哪些模块。psapi.dll提供了另一个API函数来完成此功能,叫做EnumProcessModules()。与EnumDeviceDrivers()EnumProcesses()不同,这个函数需要四个参数(参见列表1-7)。不同于前两个返回系统全局列表的函数,EnumProcessModules()只取回指定进程的列表,因此,增加的那个参数唯一表示一个进程。然而,该函数需要一个进程句柄(HANDLE)来代替进程ID。为了通过进程ID获取其句柄(HANDLE),必须调用OpenProcess()函数。

BOOL WINAPI EnumProcessModule( HNADLE   hProcess,

                               HMODULE* lphModule,

                                                    DWORD    cb,

                                                    DWORD*   lpcbNeeded);

PHMODULE WINAPI dbgProcessModules( HANDLE hProcess, PDWORD pdCount)

{

       DWORD    dSize;

       DWORD    dCount = 0;

       PHMODULE phList = NULL;

       if ( hProcess != NULL )

       {

              dSize = SIZE_MINIMUM * sizeof( HMODULE );

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

              {

                     if ( EnumProcessModules(hProcess,phList,dSize,&dCount))

                     {

                            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  枚举进程模块

 

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

关注此文的读者还看过:
    用户评论
评论内容:不能超过100字,需审核,请自觉遵守互联网相关政策法规。
发表评论: 匿名发表 用户名: loading 位网友发表了评论 查看评论
(0/100)