/* * ReactOS kernel * Copyright (C) 1998-2003 ReactOS Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id$ * * PROJECT: ReactOS kernel * FILE: ntoskrnl/dbg/profile.c * PURPOSE: Kernel profiling * PROGRAMMER: Casper S. Hornstrup (chorns@users.sourceforge.net) * UPDATE HISTORY: * Created 12/01/2003 */ /* INCLUDES *****************************************************************/ #define NTOS_MODE_KERNEL #include #include #include "kdb.h" #define NDEBUG #include /* FUNCTIONS *****************************************************************/ #define PROFILE_SESSION_LENGTH 30 /* Session length in seconds */ typedef struct _PROFILE_DATABASE_ENTRY { ULONG_PTR Address; } PROFILE_DATABASE_ENTRY, *PPROFILE_DATABASE_ENTRY; #define PDE_BLOCK_ENTRIES ((PAGE_SIZE - (sizeof(LIST_ENTRY) + sizeof(ULONG))) / sizeof(PROFILE_DATABASE_ENTRY)) typedef struct _PROFILE_DATABASE_BLOCK { LIST_ENTRY ListEntry; ULONG UsedEntries; PROFILE_DATABASE_ENTRY Entries[PDE_BLOCK_ENTRIES]; } PROFILE_DATABASE_BLOCK, *PPROFILE_DATABASE_BLOCK; typedef struct _PROFILE_DATABASE { LIST_ENTRY ListHead; } PROFILE_DATABASE, *PPROFILE_DATABASE; typedef struct _SAMPLE_GROUP_INFO { ULONG_PTR Address; ULONG Count; CHAR Description[128]; LIST_ENTRY ListEntry; } SAMPLE_GROUP_INFO, *PSAMPLE_GROUP_INFO; static volatile BOOLEAN KdbProfilingInitialized = FALSE; static volatile BOOLEAN KdbProfilingEnabled = FALSE; static volatile BOOLEAN KdbProfilingSuspended = FALSE; static PPROFILE_DATABASE KdbProfileDatabase = NULL; static KDPC KdbProfilerCollectorDpc; static HANDLE KdbProfilerThreadHandle; static CLIENT_ID KdbProfilerThreadCid; static HANDLE KdbProfilerLogFile; static KTIMER KdbProfilerTimer; static KMUTEX KdbProfilerLock; static BOOLEAN KdbEnableProfiler = FALSE; VOID KdbDeleteProfileDatabase(PPROFILE_DATABASE ProfileDatabase) { PLIST_ENTRY current = NULL; current = RemoveHeadList(&ProfileDatabase->ListHead); while (current != &ProfileDatabase->ListHead) { PPROFILE_DATABASE_BLOCK block = CONTAINING_RECORD( current, PROFILE_DATABASE_BLOCK, ListEntry); ExFreePool(block); current = RemoveHeadList(&ProfileDatabase->ListHead); } } VOID KdbAddEntryToProfileDatabase(PPROFILE_DATABASE ProfileDatabase, ULONG_PTR Address) { PPROFILE_DATABASE_BLOCK block; if (IsListEmpty(&ProfileDatabase->ListHead)) { block = ExAllocatePool(NonPagedPool, sizeof(PROFILE_DATABASE_BLOCK)); assert(block); block->UsedEntries = 0; InsertTailList(&ProfileDatabase->ListHead, &block->ListEntry); block->Entries[block->UsedEntries++].Address = Address; return; } block = CONTAINING_RECORD(ProfileDatabase->ListHead.Blink, PROFILE_DATABASE_BLOCK, ListEntry); if (block->UsedEntries >= PDE_BLOCK_ENTRIES) { block = ExAllocatePool(NonPagedPool, sizeof(PROFILE_DATABASE_BLOCK)); assert(block); block->UsedEntries = 0; InsertTailList(&ProfileDatabase->ListHead, &block->ListEntry); } block->Entries[block->UsedEntries++].Address = Address; } VOID KdbInitProfiling() { KdbEnableProfiler = TRUE; } VOID KdbInitProfiling2() { if (KdbEnableProfiler) { KdbEnableProfiling(); KdbProfilingInitialized = TRUE; } } VOID KdbSuspendProfiling() { KdbProfilingSuspended = TRUE; } VOID KdbResumeProfiling() { KdbProfilingSuspended = FALSE; } BOOLEAN KdbProfilerGetSymbolInfo(PVOID address, OUT PCH NameBuffer) { PLIST_ENTRY current_entry; MODULE_TEXT_SECTION* current; extern LIST_ENTRY ModuleTextListHead; ULONG_PTR RelativeAddress; NTSTATUS Status; ULONG LineNumber; CHAR FileName[256]; CHAR FunctionName[256]; current_entry = ModuleTextListHead.Flink; while (current_entry != &ModuleTextListHead && current_entry != NULL) { current = CONTAINING_RECORD(current_entry, MODULE_TEXT_SECTION, ListEntry); if (address >= (PVOID)current->Base && address < (PVOID)(current->Base + current->Length)) { RelativeAddress = (ULONG_PTR) address - current->Base; Status = LdrGetAddressInformation(¤t->SymbolInfo, RelativeAddress, &LineNumber, FileName, FunctionName); if (NT_SUCCESS(Status)) { sprintf(NameBuffer, "%s (%s)", FileName, FunctionName); return(TRUE); } return(TRUE); } current_entry = current_entry->Flink; } return(FALSE); } PLIST_ENTRY KdbProfilerLargestSampleGroup(PLIST_ENTRY SamplesListHead) { PLIST_ENTRY current; PLIST_ENTRY largest; ULONG count; count = 0; largest = SamplesListHead->Flink; current = SamplesListHead->Flink; while (current != SamplesListHead) { PSAMPLE_GROUP_INFO sgi = CONTAINING_RECORD( current, SAMPLE_GROUP_INFO, ListEntry); if (sgi->Count > count) { largest = current; count = sgi->Count; } current = current->Flink; } if (count == 0) { return NULL; } return largest; } VOID KdbProfilerWriteString(PCH String) { IO_STATUS_BLOCK Iosb; NTSTATUS Status; ULONG Length; Length = strlen(String); Status = NtWriteFile(KdbProfilerLogFile, NULL, NULL, NULL, &Iosb, String, Length, NULL, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("NtWriteFile() failed with status 0x%.08x\n", Status); } } NTSTATUS KdbProfilerWriteSampleGroups(PLIST_ENTRY SamplesListHead) { CHAR Buffer[256]; PLIST_ENTRY current = NULL; PLIST_ENTRY Largest; KdbProfilerWriteString("\r\n\r\n"); KdbProfilerWriteString("Count Symbol\n"); KdbProfilerWriteString("--------------------------------------------------\r\n"); current = SamplesListHead->Flink; while (current != SamplesListHead) { Largest = KdbProfilerLargestSampleGroup(SamplesListHead); if (Largest != NULL) { PSAMPLE_GROUP_INFO sgi = CONTAINING_RECORD( Largest, SAMPLE_GROUP_INFO, ListEntry); //DbgPrint("%.08lu %s\n", sgi->Count, sgi->Description); sprintf(Buffer, "%.08lu %s\r\n", sgi->Count, sgi->Description); KdbProfilerWriteString(Buffer); RemoveEntryList(Largest); ExFreePool(sgi); } else { break; } current = SamplesListHead->Flink; } return STATUS_SUCCESS; } LONG STDCALL KdbProfilerKeyCompare(IN PVOID Key1, IN PVOID Key2) { int value = strcmp(Key1, Key2); if (value == 0) return 0; return (value < 0) ? -1 : 1; } NTSTATUS KdbProfilerAnalyzeSamples() { CHAR NameBuffer[512]; ULONG KeyLength; PLIST_ENTRY current = NULL; HASH_TABLE Hashtable; LIST_ENTRY SamplesListHead; ULONG Index; ULONG_PTR Address; if (!ExInitializeHashTable(&Hashtable, 17, KdbProfilerKeyCompare, TRUE)) { DPRINT1("ExInitializeHashTable() failed."); KEBUGCHECK(0); } InitializeListHead(&SamplesListHead); current = RemoveHeadList(&KdbProfileDatabase->ListHead); while (current != &KdbProfileDatabase->ListHead) { PPROFILE_DATABASE_BLOCK block; block = CONTAINING_RECORD(current, PROFILE_DATABASE_BLOCK, ListEntry); for (Index = 0; Index < block->UsedEntries; Index++) { PSAMPLE_GROUP_INFO sgi; Address = block->Entries[Index].Address; if (KdbProfilerGetSymbolInfo((PVOID) Address, (PCH) &NameBuffer)) { } else { sprintf(NameBuffer, "(0x%.08lx)", (ULONG) Address); } KeyLength = strlen(NameBuffer); if (!ExSearchHashTable(&Hashtable, (PVOID) NameBuffer, KeyLength, (PVOID *) &sgi)) { sgi = ExAllocatePool(NonPagedPool, sizeof(SAMPLE_GROUP_INFO)); assert(sgi); sgi->Address = Address; sgi->Count = 1; strcpy(sgi->Description, NameBuffer); InsertTailList(&SamplesListHead, &sgi->ListEntry); ExInsertHashTable(&Hashtable, sgi->Description, KeyLength, (PVOID) sgi); } else { sgi->Count++; } } ExFreePool(block); current = RemoveHeadList(&KdbProfileDatabase->ListHead); } KdbProfilerWriteSampleGroups(&SamplesListHead); ExDeleteHashTable(&Hashtable); KdbDeleteProfileDatabase(KdbProfileDatabase); return STATUS_SUCCESS; } VOID STDCALL_FUNC KdbProfilerThreadMain(PVOID Context) { for (;;) { KeWaitForSingleObject(&KdbProfilerTimer, Executive, KernelMode, TRUE, NULL); KeWaitForSingleObject(&KdbProfilerLock, Executive, KernelMode, FALSE, NULL); KdbSuspendProfiling(); KdbProfilerAnalyzeSamples(); KdbResumeProfiling(); KeReleaseMutex(&KdbProfilerLock, FALSE); } } VOID KdbDisableProfiling() { if (KdbProfilingEnabled == TRUE) { /* FIXME: Implement */ #if 0 KdbProfilingEnabled = FALSE; /* Stop timer */ /* Close file */ if (KdbProfileDatabase != NULL) { KdbDeleteProfileDatabase(KdbProfileDatabase); ExFreePool(KdbProfileDatabase); KdbProfileDatabase = NULL; } #endif } } /* * SystemArgument1 = EIP */ static VOID STDCALL KdbProfilerCollectorDpcRoutine(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2) { ULONG_PTR address = (ULONG_PTR) SystemArgument1; KdbAddEntryToProfileDatabase(KdbProfileDatabase, address); } VOID KdbEnableProfiling() { if (KdbProfilingEnabled == FALSE) { NTSTATUS Status; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING FileName; IO_STATUS_BLOCK Iosb; LARGE_INTEGER DueTime; RtlInitUnicodeString(&FileName, L"\\SystemRoot\\profiler.log"); InitializeObjectAttributes(&ObjectAttributes, &FileName, 0, NULL, NULL); Status = NtCreateFile(&KdbProfilerLogFile, FILE_ALL_ACCESS, &ObjectAttributes, &Iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_SUPERSEDE, FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to create profiler log file\n"); return; } Status = PsCreateSystemThread(&KdbProfilerThreadHandle, THREAD_ALL_ACCESS, NULL, NULL, &KdbProfilerThreadCid, KdbProfilerThreadMain, NULL); if (!NT_SUCCESS(Status)) { DPRINT1("Failed to create profiler thread\n"); return; } KeInitializeMutex(&KdbProfilerLock, 0); KdbProfileDatabase = ExAllocatePool(NonPagedPool, sizeof(PROFILE_DATABASE)); assert(KdbProfileDatabase); InitializeListHead(&KdbProfileDatabase->ListHead); KeInitializeDpc(&KdbProfilerCollectorDpc, KdbProfilerCollectorDpcRoutine, NULL); /* Initialize our periodic timer and its associated DPC object. When the timer expires, the KdbProfilerSessionEndDpc deferred procedure call (DPC) is queued */ KeInitializeTimerEx(&KdbProfilerTimer, SynchronizationTimer); /* Start the periodic timer with an initial and periodic relative expiration time of PROFILE_SESSION_LENGTH seconds */ DueTime.QuadPart = -(LONGLONG) PROFILE_SESSION_LENGTH * 1000 * 10000; KeSetTimerEx(&KdbProfilerTimer, DueTime, PROFILE_SESSION_LENGTH * 1000, NULL); KdbProfilingEnabled = TRUE; } } VOID KdbProfileInterrupt(ULONG_PTR Address) { assert(KeGetCurrentIrql() == PROFILE_LEVEL); if (KdbProfilingInitialized != TRUE) { return; } if ((KdbProfilingEnabled) && (!KdbProfilingSuspended)) { (BOOLEAN) KeInsertQueueDpc(&KdbProfilerCollectorDpc, (PVOID) Address, NULL); } }