windows-nt/Source/XPSP1/NT/base/ntos/perf/hooks.c
2020-09-26 16:20:57 +08:00

864 lines
22 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
hooks.c
Abstract:
This module contains performance hooks.
Author:
Stephen Hsiao (shsiao) 01-Jan-2000
Revision History:
--*/
#include "perfp.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, PerfInfoFlushProfileCache)
#pragma alloc_text(PAGEWMI, PerfProfileInterrupt)
#pragma alloc_text(PAGEWMI, PerfInfoLogBytesAndUnicodeString)
#pragma alloc_text(PAGEWMI, PerfInfoLogFileName)
#pragma alloc_text(PAGEWMI, PerfInfoCalcHashValue)
#pragma alloc_text(PAGEWMI, PerfInfoAddToFileHash)
#pragma alloc_text(PAGEWMI, PerfInfoFileNameRunDown)
#pragma alloc_text(PAGEWMI, PerfInfoProcessRunDown)
#pragma alloc_text(PAGEWMI, PerfInfoSysModuleRunDown)
#endif //ALLOC_PRAGMA
VOID
PerfInfoFlushProfileCache(
VOID
)
/*++
Routine description:
Flushes the profile cache to the log buffer. To make sure it get's valid data
we read the 2 seperate version numbers (1 before and 1 after) to check if it's
been changed. If so, we just read again. If that fails often, then we disable
the cache. Once the cache is read, we clear it. This may cause samples to be
lost but that's ok as this is statistical and it won't matter.
Arguments:
CheckVersion - If FALSE, the version is not checked. This used when the profile
interrupt code flushes the cache.
Return Value:
None
--*/
{
ULONG PreviousInProgress;
if ((PerfProfileCache.Entries == 0) || (PerfInfoSampledProfileCaching == FALSE)) {
return;
}
//
// Signal the interrupt not to mess with the cache
//
PreviousInProgress = InterlockedIncrement(&PerfInfoSampledProfileFlushInProgress);
if (PreviousInProgress != 1) {
//
// A flush is already in progress so just return.
//
InterlockedDecrement(&PerfInfoSampledProfileFlushInProgress);
return;
}
//
// Log the portion of the cache that has valid data.
//
PerfInfoLogBytes(PERFINFO_LOG_TYPE_SAMPLED_PROFILE_CACHE,
&PerfProfileCache,
FIELD_OFFSET(PERFINFO_SAMPLED_PROFILE_CACHE, Sample) +
(PerfProfileCache.Entries *
sizeof(PERFINFO_SAMPLED_PROFILE_INFORMATION))
);
//
// Clear the cache for the next set of entries.
//
PerfProfileCache.Entries = 0;
//
// Let the interrupt fill the cache again.
//
InterlockedDecrement(&PerfInfoSampledProfileFlushInProgress);
}
VOID
FASTCALL
PerfProfileInterrupt(
IN KPROFILE_SOURCE Source,
IN PVOID InstructionPointer
)
/*++
Routine description:
Implements instruction profiling. If the source is not the one we're sampling on,
we return. If caching is off, we write any samples coming from the immediately to
the log. If caching is on, wrap the cache update with writes to the two versions so
that the flush routine can know if it has a valid buffer.
Arguments:
Source - Type of profile interrupt
InstructionPointer - IP at the time of the interrupt
Return Value:
None
--*/
{
ULONG i;
PERFINFO_SAMPLED_PROFILE_INFORMATION SampleData;
#ifdef _X86_
ULONG_PTR TwiddledIP;
#endif // _X86_
ULONG ThreadId;
if (!PERFINFO_IS_GROUP_ON(PERF_PROFILE) &&
(Source != PerfInfoProfileSourceActive)
) {
//
// We don't handle multple sources.
//
return;
}
ThreadId = HandleToUlong(PsGetCurrentThread()->Cid.UniqueThread);
if (!PerfInfoSampledProfileCaching ||
PerfInfoSampledProfileFlushInProgress != 0) {
//
// No caching. Log and return.
//
SampleData.ThreadId = ThreadId;
SampleData.InstructionPointer = InstructionPointer;
SampleData.Count = 1;
PerfInfoLogBytes(PERFINFO_LOG_TYPE_SAMPLED_PROFILE,
&SampleData,
sizeof(PERFINFO_SAMPLED_PROFILE_INFORMATION)
);
return;
}
#ifdef _X86_
//
// Clear the low two bits to have more cache hits for loops. Don't waste
// cycles on other architectures.
//
TwiddledIP = (ULONG_PTR)InstructionPointer & ~3;
#endif // _X86_
//
// Initial walk thru Instruction Pointer Cache. Bump Count if address is in cache.
//
for (i = 0; i < PerfProfileCache.Entries ; i++) {
if ((PerfProfileCache.Sample[i].ThreadId == ThreadId) &&
#ifdef _X86_
(((ULONG_PTR)PerfProfileCache.Sample[i].InstructionPointer & ~3) == TwiddledIP)
#else
(PerfProfileCache.Sample[i].InstructionPointer == InstructionPointer)
#endif // _X86_
) {
//
// If we find the instruction pointer in the cache, bump the count
//
PerfProfileCache.Sample[i].Count++;
return;
}
}
if (PerfProfileCache.Entries < PERFINFO_SAMPLED_PROFILE_CACHE_MAX) {
//
// If we find an empty spot in the cache, use it for this instruction pointer
//
PerfProfileCache.Sample[i].ThreadId = ThreadId;
PerfProfileCache.Sample[i].InstructionPointer = InstructionPointer;
PerfProfileCache.Sample[i].Count = 1;
PerfProfileCache.Entries++;
return;
}
//
// Flush the cache
//
PerfInfoLogBytes(PERFINFO_LOG_TYPE_SAMPLED_PROFILE_CACHE,
&PerfProfileCache,
sizeof(PERFINFO_SAMPLED_PROFILE_CACHE)
);
PerfProfileCache.Sample[0].ThreadId = ThreadId;
PerfProfileCache.Sample[0].InstructionPointer = InstructionPointer;
PerfProfileCache.Sample[0].Count = 1;
PerfProfileCache.Entries = 1;
return;
}
VOID
FASTCALL
PerfInfoLogDpc(
IN PVOID DpcRoutine,
IN ULONGLONG InitialTime
)
/*++
Routine description:
This routine logs the exit of a DPC service routine
Arguments:
DPCRoutine - DPC service routine
InitialTime - Timestamp before service routine was called. The timestamp in
the event is used as the end time.
--*/
{
PERFINFO_DPC_INFORMATION st;
st.DpcRoutine = DpcRoutine;
st.InitialTime = InitialTime;
PerfInfoLogBytes(
PERFINFO_LOG_TYPE_DPC,
(PVOID) &st,
sizeof(st));
}
VOID
FASTCALL
PerfInfoLogInterrupt(
IN PVOID InServiceRoutine,
IN ULONG RetVal,
IN ULONGLONG InitialTime
)
/*++
Routine Description:
This callout routine is called from ntoskrnl.exe (ke\intsup.asm) to log an
interrupt and how long it takes to complete.
Arguments:
InServiceRoutine Address of routine that serviced the interrupt.
RetVal Value returned from InServiceRoutine.
InitialTime Timestamp before ISR was called. The timestamp in
the event is used as the end time.
Return Value:
None
--*/
{
PERFINFO_INTERRUPT_INFORMATION EventInfo;
EventInfo.ServiceRoutine = InServiceRoutine;
EventInfo.ReturnValue = RetVal;
EventInfo.InitialTime = InitialTime;
PerfInfoLogBytes(PERFINFO_LOG_TYPE_INTERRUPT,
&EventInfo,
sizeof(EventInfo));
return;
}
NTSTATUS
PerfInfoLogBytesAndUnicodeString(
USHORT HookId,
PVOID SourceData,
ULONG SourceByteCount,
PUNICODE_STRING String
)
/*++
Routine description:
This routine logs data with UniCode string at the end of the hook.
Arguments:
HookId - Hook Id.
SourceData - Pointer to the data to be copied
SourceByteCount - Number of bytes to be copied.
String - The string to be logged.
Return Value:
Status
--*/
{
NTSTATUS Status;
PERFINFO_HOOK_HANDLE Hook;
ULONG ByteCount;
ULONG StringBytes;
if (String == NULL) {
StringBytes = 0;
} else {
StringBytes = String->Length;
}
ByteCount = (SourceByteCount + StringBytes + sizeof(WCHAR));
Status = PerfInfoReserveBytes(&Hook, HookId, ByteCount);
if (NT_SUCCESS(Status))
{
const PVOID pvTemp = PERFINFO_HOOK_HANDLE_TO_DATA(Hook, PVOID);
RtlCopyMemory(pvTemp, SourceData, SourceByteCount);
if (StringBytes != 0) {
RtlCopyMemory(PERFINFO_APPLY_OFFSET_GIVING_TYPE(pvTemp, SourceByteCount, PVOID),
String->Buffer,
StringBytes
);
}
(PERFINFO_APPLY_OFFSET_GIVING_TYPE(pvTemp, SourceByteCount, PWCHAR))[StringBytes / sizeof(WCHAR)] = UNICODE_NULL;
PERF_FINISH_HOOK(Hook);
Status = STATUS_SUCCESS;
}
return Status;
}
NTSTATUS
PerfInfoLogFileName(
PVOID FileObject,
PUNICODE_STRING SourceString
)
/*++
Routine Description:
This routine logs a FileObject pointer and FileName to the log. The pointer is used
as hash key to map this name to other trace events.
Arguments:
FileObject - Pointer to the FileName member within the FILE_OBJECT
structure. The FileName may not yet be initialized,
so the actual data comes from the SourceString
parameter.
SourceString - Optional pointer to the source string.
Return Value:
STATUS_SUCCESS
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PERFINFO_FILENAME_INFORMATION FileInfo;
if ((FileObject != NULL) &&
(SourceString != NULL) &&
(SourceString->Length != 0)) {
FileInfo.HashKeyFileNamePointer = FileObject;
Status = PerfInfoLogBytesAndUnicodeString(PERFINFO_LOG_TYPE_FILENAME_CREATE,
&FileInfo,
FIELD_OFFSET(PERFINFO_FILENAME_INFORMATION, FileName),
SourceString);
}
return Status;
}
ULONG
PerfInfoCalcHashValue(
PVOID Key,
ULONG Len
)
/*++
Routine Description:
Generic hash routine.
Arguments:
Key - Pointer to data to calculate a hash value for.
Len - Number of bytes pointed to by key.
Return Value:
Hash value.
--*/
{
char *cp = Key;
ULONG i, ConvKey=0;
for(i = 0; i < Len; i++)
{
ConvKey = 37 * ConvKey + (unsigned int) *cp;
cp++;
}
#define RNDM_CONSTANT 314159269
#define RNDM_PRIME 1000000007
return (abs(RNDM_CONSTANT * ConvKey) % RNDM_PRIME);
}
BOOLEAN
PerfInfoAddToFileHash(
PPERFINFO_ENTRY_TABLE HashTable,
PFILE_OBJECT ObjectPointer
)
/*++
Routine Description:
This routine add a FileObject into the specified
hash table if it is not already there.
Arguments:
HashTable - pointer to a hash table to be used.
ObjectPointer - This is used as a key to identify a mapping.
Return Value:
TRUE - If either the FileObject was in the table or we add it.
FALSE - If the table is full.
--*/
{
ULONG HashIndex;
LONG i;
BOOLEAN Result = FALSE;
LONG TableSize = HashTable->NumberOfEntries;
PVOID *Table;
Table = HashTable->Table;
//
// Get the hashed index into the table where the entry ideally
// should be at.
//
HashIndex = PerfInfoCalcHashValue((PVOID)&ObjectPointer,
sizeof(ObjectPointer)) % TableSize;
for (i = 0; i < TableSize; i++) {
if(Table[HashIndex] == NULL) {
//
// Found a empty slot. Reference the object and insert
// it into the table.
//
ObReferenceObject(ObjectPointer);
Table[HashIndex] = ObjectPointer;
Result = TRUE;
break;
} else if (Table[HashIndex] == ObjectPointer) {
//
// Found a slot. Reference the object and insert
// it into the table.
//
Result = TRUE;
break;
}
//
// Try next slot.
//
HashIndex = (HashIndex + 1) % TableSize;
}
return Result;
}
NTSTATUS
PerfInfoFileNameRunDown (
)
/*++
Routine Description:
This routine walks through multiple lists to collect the names of all files.
It includes:
1. Handle table: for all file handles
2. Process Vad for all file objects mapped in VAD.
3. MmUnusedSegment List
4. CcDirtySharedCacheMapList & CcCleanSharedCacheMapList
Arguments:
None.
Return Value:
BUGBUG Need proper return/ error handling
--*/
{
PEPROCESS Process;
ULONG AllocateBytes;
PFILE_OBJECT *FileObjects;
PFILE_OBJECT *File;
PERFINFO_ENTRY_TABLE HashTable;
extern POBJECT_TYPE IoFileObjectType;
LONG i;
//
// First create a tempory hash table to build the list of
// files to walk through
//
AllocateBytes = PAGE_SIZE + sizeof(PVOID) * IoFileObjectType->TotalNumberOfObjects;
//
// Run up to page boundary
//
AllocateBytes = PERFINFO_ROUND_UP(AllocateBytes, PAGE_SIZE);
HashTable.Table = ExAllocatePoolWithTag(NonPagedPool, AllocateBytes, PERFPOOLTAG);
if (HashTable.Table == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
} else {
//
// Allocation Succeeded
//
HashTable.NumberOfEntries = AllocateBytes / sizeof(PVOID);
RtlZeroMemory(HashTable.Table, AllocateBytes);
}
//
// Walk through the Cc SharedCacheMapList
//
CcPerfFileRunDown(&HashTable);
//
// Now, walk through each process
//
for (Process = PsGetNextProcess (NULL);
Process != NULL;
Process = PsGetNextProcess (Process)) {
//
// First Walk the VAD tree
//
FileObjects = MmPerfVadTreeWalk(Process);
if (FileObjects != NULL) {
File = FileObjects;
while (*File != NULL) {
PerfInfoAddToFileHash(&HashTable, *File);
ObDereferenceObject(*File);
File += 1;
}
ExFreePool(FileObjects);
}
//
// Next, walk the handle Table
//
ObPerfHandleTableWalk (Process, &HashTable);
}
//
// Walk through the kernel handle table;
//
ObPerfHandleTableWalk(NULL, &HashTable);
//
// Walk through the MmUnusedSegmentList;
//
FileObjects = MmPerfUnusedSegmentsEnumerate();
if (FileObjects != NULL) {
File = FileObjects;
while (*File != NULL) {
PerfInfoAddToFileHash(&HashTable, *File);
ObDereferenceObject(*File);
File += 1;
}
ExFreePool(FileObjects);
}
//
// Now we have walked through all list.
// Log the filenames and dereference the objects.
//
for (i = 0; i < HashTable.NumberOfEntries; i++) {
if (HashTable.Table[i]) {
PFILE_OBJECT FileObject = HashTable.Table[i];
PerfInfoLogFileName(FileObject, &FileObject->FileName);
ObDereferenceObject(FileObject);
}
}
//
// Free the tables reserved.
//
ExFreePool(HashTable.Table);
return STATUS_SUCCESS;
}
NTSTATUS
PerfInfoProcessRunDown (
)
/*++
Routine Description:
This routine does the Process and thread rundown in the kernel mode.
Since this routine is called only by global logger (i.e., trace from boot),
no Sid info is collected.
Arguments:
None.
Return Value:
Status
--*/
{
NTSTATUS Status;
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
PSYSTEM_EXTENDED_THREAD_INFORMATION ThreadInfo;
PCHAR Buffer;
ULONG BufferSize = 4096;
ULONG ReturnLength;
retry:
Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, PERFPOOLTAG);
if (!Buffer) {
return STATUS_NO_MEMORY;
}
Status = NtQuerySystemInformation( SystemExtendedProcessInformation,
Buffer,
BufferSize,
&ReturnLength
);
if (Status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(Buffer);
BufferSize += 8192;
goto retry;
}
if (NT_SUCCESS(Status)) {
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) Buffer;
while (TRUE) {
PWMI_PROCESS_INFORMATION WmiProcessInfo;
PWMI_EXTENDED_THREAD_INFORMATION WmiThreadInfo;
PERFINFO_HOOK_HANDLE Hook;
ANSI_STRING ProcessName;
PUCHAR AuxPtr;
ULONG NameLength;
ULONG ByteCount;
ULONG SidLength = sizeof(ULONG);
ULONG TmpSid = 0;
ULONG TotalOffset = 0;
ULONG i;
//
// Process Information
//
if ( ProcessInfo->ImageName.Buffer && ProcessInfo->ImageName.Length > 0 ) {
NameLength = ProcessInfo->ImageName.Length / sizeof(WCHAR) + 1;
}
else {
NameLength = 1;
}
ByteCount = FIELD_OFFSET(WMI_PROCESS_INFORMATION, Sid) + SidLength + NameLength;
Status = PerfInfoReserveBytes(&Hook,
WMI_LOG_TYPE_PROCESS_DC_START,
ByteCount);
if (NT_SUCCESS(Status)){
WmiProcessInfo = PERFINFO_HOOK_HANDLE_TO_DATA(Hook, PWMI_PROCESS_INFORMATION);
WmiProcessInfo->ProcessId = HandleToUlong(ProcessInfo->UniqueProcessId);
WmiProcessInfo->ParentId = HandleToUlong(ProcessInfo->InheritedFromUniqueProcessId);
WmiProcessInfo->SessionId = ProcessInfo->SessionId;
WmiProcessInfo->PageDirectoryBase = ProcessInfo->PageDirectoryBase;
AuxPtr = (PUCHAR) (&WmiProcessInfo->Sid);
RtlCopyMemory(AuxPtr, &TmpSid, SidLength);
AuxPtr += SidLength;
if (NameLength > 1) {
ProcessName.Buffer = AuxPtr;
ProcessName.MaximumLength = (USHORT) NameLength;
RtlUnicodeStringToAnsiString( &ProcessName,
(PUNICODE_STRING) &ProcessInfo->ImageName,
FALSE);
AuxPtr += NameLength;
}
*AuxPtr = '\0';
PERF_FINISH_HOOK(Hook);
}
//
// Thread Information
//
ThreadInfo = (PSYSTEM_EXTENDED_THREAD_INFORMATION) (ProcessInfo + 1);
for (i=0; i < ProcessInfo->NumberOfThreads; i++) {
Status = PerfInfoReserveBytes(&Hook,
WMI_LOG_TYPE_THREAD_DC_START,
sizeof(WMI_EXTENDED_THREAD_INFORMATION));
if (NT_SUCCESS(Status)){
WmiThreadInfo = PERFINFO_HOOK_HANDLE_TO_DATA(Hook, PWMI_EXTENDED_THREAD_INFORMATION);
WmiThreadInfo->ProcessId = HandleToUlong(ThreadInfo->ThreadInfo.ClientId.UniqueProcess);
WmiThreadInfo->ThreadId = HandleToUlong(ThreadInfo->ThreadInfo.ClientId.UniqueThread);
WmiThreadInfo->StackBase = ThreadInfo->StackBase;
WmiThreadInfo->StackLimit = ThreadInfo->StackLimit;
WmiThreadInfo->UserStackBase = NULL;
WmiThreadInfo->UserStackLimit = NULL;
WmiThreadInfo->StartAddr = ThreadInfo->ThreadInfo.StartAddress;
WmiThreadInfo->Win32StartAddr = ThreadInfo->Win32StartAddress;
WmiThreadInfo->WaitMode = -1;
PERF_FINISH_HOOK(Hook);
}
ThreadInfo += 1;
}
if (ProcessInfo->NextEntryOffset == 0) {
break;
} else {
TotalOffset += ProcessInfo->NextEntryOffset;
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) &Buffer[TotalOffset];
}
}
}
ExFreePool(Buffer);
return Status;
}
NTSTATUS
PerfInfoSysModuleRunDown (
)
/*++
Routine Description:
This routine does the rundown for loaded drivers in the kernel mode.
Arguments:
None.
Return Value:
Status
--*/
{
NTSTATUS Status;
PRTL_PROCESS_MODULES Modules;
PRTL_PROCESS_MODULE_INFORMATION ModuleInfo;
PVOID Buffer;
ULONG BufferSize = 4096;
ULONG ReturnLength;
ULONG i;
USHORT HookId = WMI_LOG_TYPE_PROCESS_LOAD_IMAGE;
retry:
Buffer = ExAllocatePoolWithTag(NonPagedPool, BufferSize, PERFPOOLTAG);
if (!Buffer) {
return STATUS_NO_MEMORY;
}
Status = NtQuerySystemInformation( SystemModuleInformation,
Buffer,
BufferSize,
&ReturnLength
);
if (Status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(Buffer);
BufferSize += 8192;
goto retry;
}
if (NT_SUCCESS(Status)) {
Modules = (PRTL_PROCESS_MODULES) Buffer;
for (i = 0, ModuleInfo = & (Modules->Modules[0]);
i < Modules->NumberOfModules;
i ++, ModuleInfo ++) {
PWMI_IMAGELOAD_INFORMATION ImageLoadInfo;
UNICODE_STRING WstrModuleName;
ANSI_STRING AstrModuleName;
ULONG SizeModuleName;
PERFINFO_HOOK_HANDLE Hook;
ULONG ByteCount;
RtlInitAnsiString( &AstrModuleName, ModuleInfo->FullPathName);
SizeModuleName = sizeof(WCHAR) * (AstrModuleName.Length) + sizeof(WCHAR);
ByteCount = FIELD_OFFSET(WMI_IMAGELOAD_INFORMATION, FileName)
+ SizeModuleName;
Status = PerfInfoReserveBytes(&Hook, WMI_LOG_TYPE_PROCESS_LOAD_IMAGE, ByteCount);
if (NT_SUCCESS(Status)){
ImageLoadInfo = PERFINFO_HOOK_HANDLE_TO_DATA(Hook, PWMI_IMAGELOAD_INFORMATION);
ImageLoadInfo->ImageBase = ModuleInfo->ImageBase;
ImageLoadInfo->ImageSize = ModuleInfo->ImageSize;
ImageLoadInfo->ProcessId = HandleToUlong(NULL);
WstrModuleName.Buffer = (LPWSTR) &ImageLoadInfo->FileName[0];
WstrModuleName.MaximumLength = (USHORT) SizeModuleName;
RtlAnsiStringToUnicodeString(&WstrModuleName, & AstrModuleName, FALSE);
PERF_FINISH_HOOK(Hook);
}
}
}
ExFreePool(Buffer);
return Status;
}