TOOLHELP32 In WIN2000

作者:陆麟
转载请征得作者同意.
2001.7.29



        WINDOWS 2000在KERNEL32.DLL中提供了TOOLHELP32 API的支持. 但是同时发现了在WIN2000中的TOOLHELP32和在WIN9X下的不稳定性.
        最近, 经过反汇编了部分的TOOLHELP32代码, TOOLHELP32在WIN2000中的实现是通过调用NtQuerySystemInformation, InfoClass = 5实现的. 但是为什么说不稳定呢? 那是因为NtQuerySystemInformation在产生错误时的返回值有可能并不能正确反馈给调用者. 为什么呢?
        CreateToolhelp32Snapshot是整个TOOLHELP32的关键, 调用它, 它会调用NtQuerySystemInformation搜集整个系统在调用发生时的状态信息.  创建一个SECTION对象临时保存起来. 返回SECTION对象的句柄. 每当调用Thread32First/Thread32Next, Process32First/Process32Next时, 在函数调用的内部会将SECTION对象映射到内存, 然后将数据COPY到调用者传递进来的数据结构指针中. 在调用NtQuerySystemInformation获取进程/线程信息时, 必须传递足够大的BUFFER, TOOLHELP32在初始时调用ZwAllocateVirtualMemroy分配0x10000字节的内存. 将分配到的内存作为BUFFER传递给NtQuerySystemInformation调用. 每当调用返回BUFFER不够大, TOOLHELP32就将缓冲区增大0x2000后重新分配内存. 再次尝试. 但是NtQuerySystemInformation在长度不够时的返回值并不是TOOLHELP32所期待的. 在我的测试中, 当调用NtQuerySystemInformation时传递小于实际需要的BUFFER时,返回值为0xC0000005. 也就是STATUS_ACCESS_VIOLATION. 而TOOLHELP32期待的返回值是STATUS_INFO_LENGTH_MISMATCH--0xC0000004. 也就是说如果第一次0x10000字节的BUFFER不能容纳足够的信息时, WIN2000的TOOLHELP会失败. 不继续分配更多的内存继续尝试. 所以如果需要的话, 调用NtQuerySystemInformation仍然是正确的选择.
        下面给出NtQuerySystemInformation的原形. 该原形在很久以前就在DDJ上公布了:
            NTSTATUS WINAPI NtQuerySystemInformation(
                    int SystemInfoClass
                    PVOID SystemInfoBuffer,
                    ULONG SystemInfoBufferSize,
                    PULONG BytesReturned);
        当需要获取进程和线程信息时, SystemInfoClass为5. SystemInfoBuffer将返回PROCESSTHREADSYSTEMINFO:
typedef struct _IO_COUNTERSEX {
    LARGE_INTEGER ReadOperationCount;
    LARGE_INTEGER WriteOperationCount;
    LARGE_INTEGER OtherOperationCount;
    LARGE_INTEGER ReadTransferCount;
    LARGE_INTEGER WriteTransferCount;
    LARGE_INTEGER OtherTransferCount;
} IO_COUNTERSEX, *PIO_COUNTERSEX;

typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;

typedef enum _KWAIT_REASON {
    Executive,
    FreePage,
    PageIn,
    PoolAllocation,
    DelayExecution,
    Suspended,
    UserRequest,
    WrExecutive,
    WrFreePage,
    WrPageIn,
    WrPoolAllocation,
    WrDelayExecution,
    WrSuspended,
    WrUserRequest,
    WrEventPair,
    WrQueue,
    WrLpcReceive,
    WrLpcReply,
    WrVirtualMemory,
    WrPageOut,
    WrRendezvous,
    Spare2,
    Spare3,
    Spare4,
    Spare5,
    Spare6,
    WrKernel,
    MaximumWaitReason
} KWAIT_REASON;

typedef struct ThreadSysInfo_t {
 LARGE_INTEGER ThreadKernelTime;
 LARGE_INTEGER ThreadUserTime;
 LARGE_INTEGER ThreadCreateTime;
 ULONG TickCount;
 ULONG StartEIP;
 CLIENT_ID ClientId;
 ULONG DynamicPriority;
 ULONG BasePriority;
 ULONG nSwitches;
 ULONG Unknown;
 KWAIT_REASON WaitReason;
}THREADSYSINFO, *PTHREADSYSINFO;

typedef struct ProcessThreadSystemInfo {
 ULONG RelativeOffset;
 ULONG nThreads;
 ULONG Unused1[6];
 LARGE_INTEGER ProcessCreateTime;
 LARGE_INTEGER ProcessUserTime;
 LARGE_INTEGER ProcessKernelTime;
 LSA_UNICODE_STRING ProcessName;
 ULONG BasePriority;
 ULONG ProcessId;
 ULONG ParentProcessId;
 ULONG HandleCount;
 ULONG Unused2[2];
 ULONG PeakVirtualSizeBytes;
 ULONG TotalVirtualSizeBytes;
 ULONG nPageFaults;
 ULONG PeakWorkingSetSizeBytes;
 ULONG TotalWorkingSetSizeBytes;
 ULONG PeakPagedPoolUsagePages;
 ULONG TotalPagedPoolUsagePages;
 ULONG PeakNonPagedPoolUsagePages;
 ULONG TotalNonPagedPoolUsagePages;
 ULONG TotalPageFileUsageBytes;
 ULONG PeakPageFileUsageBytes;
 //ULONG TotalPrivateBytes;  //For NT4
 IO_COUNTERSEX IoCounters;  // Windows 2000 only
 THREADSYSINFO ThreadSysInfo[1];
} PROCESSTHREADSYSTEMINFO, *PPROCESSTHREADSYSTEMINFO;