/*++ BUILD Version: 0001 // Increment this if a change has global effects Copyright (c) 1992 Microsoft Corporation Module Name: prflibva.c Abstract: Virtual address space counter evaluation routines computes the process and image virtual address space usage for return via Perfmon API Author: Stolen from the "internal" PVIEW SDK program and adapted for Perfmon by: a-robw (Bob Watson) 11/29/92 Revision History: --*/ // // define routine's "personality" // #define UNICODE 1 // // Include files // #include #include #include #include #include #include #include #include #include #define PERF_HEAP hLibHeap #include #include "perfsprc.h" #define DEFAULT_INCR (64*1024) #ifdef _WIN64 #define STOP_AT (PVOID)(0xFFFFFFFF80000000) #else #define STOP_AT (PVOID)(0x80000000) #endif // Function Prototypes PPROCESS_VA_INFO GetProcessVaData ( IN PSYSTEM_PROCESS_INFORMATION ); PMODINFO GetModuleVaData ( PLDR_DATA_TABLE_ENTRY, // module information structure PPROCESS_VA_INFO // process data structure ); BOOL FreeProcessVaData ( IN PPROCESS_VA_INFO ); BOOL FreeModuleVaData ( IN PMODINFO ); PMODINFO LocateModInfo( IN PMODINFO, IN PVOID, IN SIZE_T ); DWORD ProtectionToIndex( IN ULONG ); DWORD dwProcessCount; DWORD dwModuleCount; PPROCESS_VA_INFO GetSystemVaData ( IN PSYSTEM_PROCESS_INFORMATION pFirstProcess ) /*++ GetSystemVaData Obtains the Process and Image Virtual Address information for all processes running on the system. (note that the routines called by this function allocate data structures consequently the corresponding FreeSystemVaData must be called to prevent memory "leaks") Arguments IN PSYSTEM_PROCESS_INFORMATION pFirstProcess Pointer to first process in list of process structures returned by NtQuerySystemInformation service Return Value Pointer to first process in list of processes or NULL if unable to obtain data --*/ { PSYSTEM_PROCESS_INFORMATION pThisProcess; PPROCESS_VA_INFO pReturnValue = NULL; PPROCESS_VA_INFO pLastProcess; PPROCESS_VA_INFO pNewProcess; DWORD dwStartTime; BOOL bMoreProcesses; dwProcessCount = 0; dwModuleCount = 0; if (pFirstProcess != NULL) { pThisProcess = pFirstProcess; pLastProcess = NULL; bMoreProcesses = TRUE; while ( bMoreProcesses ) { // loop exit is at bottom of loop dwStartTime = GetTickCount (); pNewProcess = GetProcessVaData( pThisProcess); // pointer to process Info structure if (pNewProcess) { // process data found OK pNewProcess->LookUpTime = GetTickCount() - dwStartTime; dwProcessCount++; if (!pLastProcess) { // this is the first process returned pReturnValue = pNewProcess; // save return value here } else { pLastProcess->pNextProcess = pNewProcess; } pLastProcess = pNewProcess; } if ( pThisProcess->NextEntryOffset == 0 ) { bMoreProcesses = FALSE; // this is the last entry } else { // point to the next process info structure pThisProcess = (PSYSTEM_PROCESS_INFORMATION) ((PBYTE)pThisProcess + pThisProcess->NextEntryOffset); } } return pReturnValue; // return pointer to first list entry } else { return NULL; } } PPROCESS_VA_INFO GetProcessVaData ( IN PSYSTEM_PROCESS_INFORMATION pProcess ) /*++ GetProcessVaData Gets the Virtual Memory usage details for the process passed in the argument list. Collects the data for all images in use by the process. Note that this routine allocates data structures that must be freed (using the FreeProcessVaData routine) when finished with them. Arguments IN HANDLE hProcess handle to the process to collect data for Return Value Pointer to completed Process VA info structure or NULL if unable to collect data --*/ { NTSTATUS Status; HANDLE hProcess; PPROCESS_VA_INFO pThisProcess; PPEB pPeb; PPEB_LDR_DATA Ldr; PLIST_ENTRY LdrHead, LdrNext; LDR_DATA_TABLE_ENTRY LdrEntryData, *pLdrEntry; PMODINFO pNewModule, pLastModule; PVOID pBaseAddress; MEMORY_BASIC_INFORMATION VaBasicInfo; DWORD dwProtection; PMODINFO pMod; SIZE_T dwRegionSize; OBJECT_ATTRIBUTES obProcess; CLIENT_ID ClientId; PUNICODE_STRING pProcessNameBuffer; // get handle to process ClientId.UniqueThread = (HANDLE)NULL; ClientId.UniqueProcess = pProcess->UniqueProcessId; InitializeObjectAttributes( &obProcess, NULL, 0, NULL, NULL ); Status = NtOpenProcess( &hProcess, (ACCESS_MASK)PROCESS_ALL_ACCESS, &obProcess, &ClientId); if (! NT_SUCCESS(Status)){ // unable to open the process, but still want to // create pThisProcess so we will not mess up // the process sequence. hProcess = 0; // return NULL; // unable to open process } // allocate structure pThisProcess = ALLOCMEM (sizeof (PROCESS_VA_INFO)); if (pThisProcess) { // allocation successful // initialize fields pThisProcess->BasicInfo = ALLOCMEM (sizeof (PROCESS_BASIC_INFORMATION)); if (!pThisProcess->BasicInfo) { // Bailout if unable to allocate memory goto PBailOut; } // zero process counters pThisProcess->MappedGuard = 0; pThisProcess->PrivateGuard = 0; pThisProcess->ImageReservedBytes = 0; pThisProcess->ImageFreeBytes = 0; pThisProcess->ReservedBytes = 0; pThisProcess->FreeBytes = 0; // get process short name from Process Info Structure // alloc a new buffer since GetProcessShortName reuses the name // buffer pThisProcess->pProcessName = ALLOCMEM ((sizeof(UNICODE_STRING) + MAX_PROCESS_NAME_LENGTH)); if (pThisProcess->pProcessName != NULL) { pThisProcess->pProcessName->Length = 0; pThisProcess->pProcessName->MaximumLength = MAX_PROCESS_NAME_LENGTH; pThisProcess->pProcessName->Buffer = (PWSTR)(&pThisProcess->pProcessName[1]); pProcessNameBuffer = GetProcessShortName (pProcess); RtlCopyUnicodeString (pThisProcess->pProcessName, pProcessNameBuffer); } else { pThisProcess->pProcessName = NULL; } pThisProcess->dwProcessId = HandleToUlong(pProcess->UniqueProcessId); pThisProcess->hProcess = hProcess; // zero list pointers pThisProcess->pMemBlockInfo = NULL; pThisProcess->pNextProcess = NULL; if (hProcess) { Status = NtQueryInformationProcess ( hProcess, ProcessBasicInformation, pThisProcess->BasicInfo, sizeof (PROCESS_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(Status)){ // if error reading data, then bail out goto SuccessExit; } // get pointer to the Process Environment Block pPeb = pThisProcess->BasicInfo->PebBaseAddress; // read address of loader information structure Status = NtReadVirtualMemory ( hProcess, &pPeb->Ldr, &Ldr, sizeof (Ldr), NULL); // bail out if unable to read information if (!NT_SUCCESS(Status)){ // if error reading data, then bail out goto SuccessExit; } // // get head pointer to linked list of memory modules used by // this process // LdrHead = &Ldr->InMemoryOrderModuleList; // Get address of next list entry Status = NtReadVirtualMemory ( hProcess, &LdrHead->Flink, &LdrNext, sizeof (LdrNext), NULL); // bail out if unable to read information if (!NT_SUCCESS(Status)){ // if error reading data, then bail out goto SuccessExit; } pLastModule = NULL; // walk down the list of modules until back at the top. // to list all the images in use by this process while ( LdrNext != LdrHead ) { // get record attached to list entry pLdrEntry = CONTAINING_RECORD(LdrNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); Status = NtReadVirtualMemory( hProcess, pLdrEntry, &LdrEntryData, sizeof(LdrEntryData), NULL ); // if unable to read memory, then give up rest of search // and return what we have already. if ( !NT_SUCCESS(Status) ) { goto SuccessExit; } pNewModule = GetModuleVaData ( &LdrEntryData, pThisProcess); if (pNewModule) { // if structure returned... dwModuleCount++; if (!pLastModule) { // if this is the first module... // then set list head pointer pThisProcess->pMemBlockInfo = pNewModule; } else { // otherwise link to list pLastModule->pNextModule = pNewModule; } pLastModule = pNewModule; } LdrNext = LdrEntryData.InMemoryOrderLinks.Flink; } // end while not at end of list // now that we have a list of all images, query the process' // virtual memory for the list of memory blocks in use by this // process and assign them to the appropriate category of memory pBaseAddress = NULL; // start at 0 and go to end of User VA space while (pBaseAddress < STOP_AT) { // truncate to 32-bit if necessary Status = NtQueryVirtualMemory ( hProcess, pBaseAddress, MemoryBasicInformation, &VaBasicInfo, sizeof(VaBasicInfo), NULL); if (!NT_SUCCESS(Status)) { goto SuccessExit; } else { // get protection type for index into counter array dwRegionSize = VaBasicInfo.RegionSize; switch (VaBasicInfo.State) { case MEM_COMMIT: // if the memory is for an IMAGE, then search the image list // for the corresponding image to update dwProtection = ProtectionToIndex(VaBasicInfo.Protect); if (VaBasicInfo.Type == MEM_IMAGE) { // update process total pThisProcess->MemTotals.CommitVector[dwProtection] += dwRegionSize; pMod = LocateModInfo (pThisProcess->pMemBlockInfo, pBaseAddress, dwRegionSize); if (pMod) { // if matching image found, then update pMod->CommitVector[dwProtection] += dwRegionSize; pMod->TotalCommit += dwRegionSize; } else { // otherwise update orphan total pThisProcess->OrphanTotals.CommitVector[dwProtection] += dwRegionSize; } } else { // if not assigned to an image, then update the process // counters if (VaBasicInfo.Type == MEM_MAPPED) { pThisProcess->MappedCommit[dwProtection] += dwRegionSize; } else { pThisProcess->PrivateCommit[dwProtection] += dwRegionSize; } } break; case MEM_RESERVE: if (VaBasicInfo.Type == MEM_IMAGE) { pThisProcess->ImageReservedBytes += dwRegionSize; } else { pThisProcess->ReservedBytes += dwRegionSize; } break; case MEM_FREE: if (VaBasicInfo.Type == MEM_IMAGE) { pThisProcess->ImageFreeBytes += dwRegionSize; } else { pThisProcess->FreeBytes += dwRegionSize; } break; default: break; } // end switch (VaBasicInfo.State) } // endif QueryVM ok // go to next memory block pBaseAddress = (PVOID)((ULONG_PTR)pBaseAddress + dwRegionSize); } // end whil not at the end of memory } // endif hProcess not NULL } // endif pThisProcess not NULL SuccessExit: if (hProcess) CloseHandle(hProcess); return pThisProcess; // // error recovery section, called when the routine is unable to // complete successfully to clean up before leaving // PBailOut: if (pThisProcess->BasicInfo) { FREEMEM (pThisProcess->BasicInfo); } FREEMEM (pThisProcess); if (hProcess) CloseHandle(hProcess); return NULL; } PMODINFO GetModuleVaData ( PLDR_DATA_TABLE_ENTRY ModuleListEntry, // module information structure PPROCESS_VA_INFO pProcess // process data structure ) /*++ GetModuleVaData Gets the Virtual Memory usage details for the module pointed to by the Process Memory Module List Entry argument in the argument list Note that this routine allocates data structures that must be freed (using the FreeModuleVaData routine) when finished with them. Arguments IN HANDLE ModuleListEntry Return Value Pointer to completed Module VA info structure or NULL if unable to collect data --*/ { PMODINFO pThisModule = NULL; // module structure that is returned PUNICODE_STRING pusInstanceName = NULL; // process->image PUNICODE_STRING pusLongInstanceName = NULL; // process->fullimagepath UNICODE_STRING usImageFileName = {0,0, NULL}; // image file name UNICODE_STRING usExeFileName = {0,0, NULL}; // short name UNICODE_STRING usNtFileName = {0,0, NULL}; // full Nt File Name PWCHAR p,p1; NTSTATUS Status; HANDLE hFile; HANDLE hMappedFile; WORD wStringSize; PVOID MappedAddress; PVOID MapBase; SIZE_T dwMappedSize; PIMAGE_DOS_HEADER DosHeader; PIMAGE_NT_HEADERS FileHeader; LARGE_INTEGER liSectionSize; PLARGE_INTEGER pliSectionSize; LARGE_INTEGER liSectionOffset; OBJECT_ATTRIBUTES obFile; IO_STATUS_BLOCK IoStatusBlock; BOOL bRetCode; USHORT wBufOffset; USHORT wDiffSize; // allocate this item's memory pThisModule = ALLOCMEM (sizeof (MODINFO)); if (!pThisModule) { return NULL; } // allocate this items Instance Name Buffer wStringSize = (WORD)(ModuleListEntry->BaseDllName.MaximumLength + sizeof (UNICODE_NULL)); pusInstanceName = ALLOCMEM (wStringSize + sizeof(UNICODE_STRING)) ; if (!pusInstanceName) { goto MBailOut; } pusInstanceName->Length = 0; pusInstanceName->MaximumLength = wStringSize; pusInstanceName->Buffer = (PWCHAR)&pusInstanceName[1]; // save instance name using full file path wStringSize = (WORD)(ModuleListEntry->FullDllName.MaximumLength + sizeof (UNICODE_NULL)); pusLongInstanceName = ALLOCMEM (wStringSize + sizeof (UNICODE_STRING)); if (!pusLongInstanceName) { goto MBailOut; } pusLongInstanceName->Length = 0; pusLongInstanceName->MaximumLength = wStringSize; pusLongInstanceName->Buffer = (PWCHAR)&pusLongInstanceName[1]; // allocate temporary buffer for image name usImageFileName.Length = ModuleListEntry->FullDllName.Length; usImageFileName.MaximumLength = ModuleListEntry->FullDllName.MaximumLength; usImageFileName.Buffer = ALLOCMEM(usImageFileName.MaximumLength); if ( !usImageFileName.Buffer ) { goto MBailOut; } // allocate temporary buffer for exe name usExeFileName.Length = ModuleListEntry->BaseDllName.Length; usExeFileName.MaximumLength = ModuleListEntry->BaseDllName.MaximumLength; usExeFileName.Buffer = ALLOCMEM(usExeFileName.MaximumLength); if ( !usExeFileName.Buffer ) { goto MBailOut; } usExeFileName.Buffer[0] = UNICODE_NULL; // read base .exe/.dll name of image Status = NtReadVirtualMemory( pProcess->hProcess, ModuleListEntry->BaseDllName.Buffer, usExeFileName.Buffer, usExeFileName.MaximumLength, NULL ); if ( !NT_SUCCESS(Status) ) { goto MBailOut; } usImageFileName.Buffer[0] = UNICODE_NULL; // read full name of image Status = NtReadVirtualMemory( pProcess->hProcess, ModuleListEntry->FullDllName.Buffer, usImageFileName.Buffer, usImageFileName.MaximumLength, NULL ); if ( !NT_SUCCESS(Status) ) { goto MBailOut; } // make a DOS filename to convert to NT again wDiffSize = wBufOffset = 0; p = p1 = usImageFileName.Buffer; while (*p != (WCHAR)0){ if (*p == L':'){ p1 = p; wDiffSize = wBufOffset; } wBufOffset += sizeof(WCHAR); p++; } if (p1 != usImageFileName.Buffer) { // move pointer usImageFileName.Buffer = --p1; // adjust length fields wDiffSize -= (USHORT)(sizeof(WCHAR)); usImageFileName.Length = usImageFileName.Length - wDiffSize; usImageFileName.MaximumLength = usImageFileName.MaximumLength - wDiffSize; } // Create/copy a NT filename for Nt file operation bRetCode = RtlDosPathNameToNtPathName_U ( usImageFileName.Buffer, &usNtFileName, NULL, NULL); if ( !bRetCode ) { goto MBailOut; } // get handle to file InitializeObjectAttributes( &obFile, &usNtFileName, FILE_ATTRIBUTE_NORMAL | OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = NtCreateFile ( &hFile, (ACCESS_MASK)GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES, &obFile, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL & FILE_ATTRIBUTE_VALID_FLAGS, FILE_SHARE_READ, FILE_OPEN, 0, NULL, 0); if (!NT_SUCCESS(Status)) { goto MBailOut; } pliSectionSize = &liSectionSize; liSectionSize.HighPart = 0; liSectionSize.LowPart = 0; InitializeObjectAttributes ( &obFile, NULL, 0, NULL, NULL); Status = NtCreateSection ( &hMappedFile, SECTION_QUERY | SECTION_MAP_READ, &obFile, pliSectionSize, PAGE_READONLY, SEC_COMMIT, hFile); if ( ! NT_SUCCESS(Status)) { CloseHandle(hFile); goto MBailOut; } // get pointer to mapped memory MappedAddress = MapBase = NULL; dwMappedSize = 0; liSectionOffset.LowPart = 0; liSectionOffset.HighPart = 0; Status = NtMapViewOfSection ( hMappedFile, NtCurrentProcess(), &MapBase, 0L, 0L, &liSectionOffset, &dwMappedSize, ViewShare, 0L, PAGE_READONLY); CloseHandle(hMappedFile); if (NT_SUCCESS(Status)) { MappedAddress = MapBase; } else { CloseHandle(hFile); goto MBailOut; } // check for dos image signature (if a dos file) DosHeader = (PIMAGE_DOS_HEADER)MappedAddress; if ( DosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { UnmapViewOfFile(MappedAddress); CloseHandle(hFile); goto MBailOut; } FileHeader = (PIMAGE_NT_HEADERS)((UINT_PTR)DosHeader + DosHeader->e_lfanew); if ( FileHeader->Signature != IMAGE_NT_SIGNATURE ) { UnmapViewOfFile(MappedAddress); CloseHandle(hFile); goto MBailOut; } // get base address for this module and save in local data structure pThisModule->BaseAddress = ModuleListEntry->DllBase; // get image name RtlCopyUnicodeString ( pusInstanceName, &usExeFileName); RtlCopyUnicodeString ( pusLongInstanceName, &usImageFileName); pThisModule->InstanceName = pusInstanceName; pThisModule->LongInstanceName = pusLongInstanceName; pThisModule->pNextModule = NULL; pThisModule->TotalCommit = 0; memset ( &pThisModule->CommitVector[0], 0, sizeof (pThisModule->CommitVector)); pThisModule->VirtualSize = FileHeader->OptionalHeader.SizeOfImage; // close file handles UnmapViewOfFile(MappedAddress); CloseHandle(hFile); // free local memory // this is allocated by an RTL function RtlDosPathNameToNtPathName_U. RtlFreeHeap(RtlProcessHeap(), 0, usNtFileName.Buffer); // FREEMEM ( // RelativeName.RelativeName.Buffer); FREEMEM (usExeFileName.Buffer); return (pThisModule); // return pointer to completed module structure // // Module bail out point, called when the routine is unable to continue // for some reason. This cleans up any allocated memory, etc. // MBailOut: if (pThisModule) { FREEMEM (pThisModule); } if (usNtFileName.Buffer) { // this is allocated by an RTL function RtlDosPathNameToNtPathName_U. RtlFreeHeap(RtlProcessHeap(), 0, usNtFileName.Buffer); } // if (RelativeName.RelativeName.Buffer) { // FREEMEM ( // RelativeName.RelativeName.Buffer); // } if (pusInstanceName) { FREEMEM (pusInstanceName); } if (pusLongInstanceName) { FREEMEM (pusLongInstanceName); } if (usExeFileName.Buffer){ FREEMEM (usExeFileName.Buffer); } if (usImageFileName.Buffer) { FREEMEM(usImageFileName.Buffer); } return NULL; } PMODINFO LocateModInfo( IN PMODINFO pFirstMod, IN PVOID pAddress, IN SIZE_T dwExtent ) /*++ LocateModInfo Locates the images associated with the address passed in the argument list Arguments IN PMODINFO pFirstMod, first module entry in process list IN PVOID Address Address to search for in list Return Value Pointer to matching image or NULL if no match found --*/ { PMODINFO pThisMod; pThisMod = pFirstMod; while (pThisMod) { // go to end of list or match is found // match criteria are: // address >= Module BaseAddress and // address+extent between base and base+image_extent if (pAddress >= pThisMod->BaseAddress) { if ((PVOID)((PDWORD)pAddress + dwExtent) <= (PVOID)((ULONG_PTR)pThisMod->BaseAddress+pThisMod->VirtualSize)) { return (pThisMod); } } pThisMod = pThisMod->pNextModule; } return NULL; } DWORD ProtectionToIndex( IN ULONG Protection ) /*++ ProtectionToIndex Determine the memory access protection type and return local code Arguments IN ULONG Protection Process memory protection mask Return Value Local value of protection type --*/ { Protection &= (PAGE_NOACCESS | PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); switch ( Protection ) { case PAGE_NOACCESS: return NOACCESS; case PAGE_READONLY: return READONLY; case PAGE_READWRITE: return READWRITE; case PAGE_WRITECOPY: return WRITECOPY; case PAGE_EXECUTE: return EXECUTE; case PAGE_EXECUTE_READ: return EXECUTEREAD; case PAGE_EXECUTE_READWRITE: return EXECUTEREADWRITE; case PAGE_EXECUTE_WRITECOPY: return EXECUTEWRITECOPY; default: return 0xFFFFFFFF; } } BOOL FreeSystemVaData ( IN PPROCESS_VA_INFO pFirstProcess ) { PPROCESS_VA_INFO pThisProcess, pNextProcess; pThisProcess = pFirstProcess; while (pThisProcess) { pNextProcess = pThisProcess->pNextProcess; // save pointer to next FreeProcessVaData (pThisProcess); pThisProcess = pNextProcess; // do next until NULL pointer } return (FALSE); } BOOL FreeProcessVaData ( IN PPROCESS_VA_INFO pProcess ) { PMODINFO pThisModule, pNextModule; if (pProcess) { if (pProcess->pProcessName) { FREEMEM (pProcess->pProcessName); pProcess->pProcessName = NULL; } if (pProcess->BasicInfo) { FREEMEM (pProcess->BasicInfo); pProcess->BasicInfo = NULL; } pThisModule = pProcess->pMemBlockInfo; while (pThisModule) { pNextModule = pThisModule->pNextModule; FreeModuleVaData (pThisModule); pThisModule = pNextModule; } // // and finally throw ourselves away // FREEMEM (pProcess); } return FALSE; } BOOL FreeModuleVaData ( IN PMODINFO pModule ) { if (pModule) { if (pModule->InstanceName) { FREEMEM(pModule->InstanceName); pModule->InstanceName = NULL; } if (pModule->LongInstanceName) { FREEMEM(pModule->LongInstanceName); pModule->LongInstanceName = NULL; } FREEMEM (pModule); } return FALSE; }