Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3397 lines
88 KiB

/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
oh.cxx
Abstract:
Prints out kernel handle information for a given process or systemwide
and compares logs with handle information for possible leaks.
Author:
SteveWo (probably)
ChrisW - tweaks, GC style leak checking
SilviuC - support for stack traces
SilviuC - added log compare functionality (a la ohcmp)
Futures:
get -h to work without -p (ie stack traces for all processes)
for the -u feature, look for unaligned values
--*/
//
// OH version.
//
// Please update this for important changes
// so that people can understand what version of the tool
// created a log.
//
#define LARGE_HITCOUNT 1234
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <memory.h>
#include <tchar.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <common.ver>
#include <dbghelp.h>
#include "MAPSTRINGINT.h"
LPTSTR OhHelpText =
TEXT("oh - Object handles dump --") BUILD_MACHINE_TAG TEXT("\n")
VER_LEGALCOPYRIGHT_STR TEXT("\n")
TEXT(" \n")
TEXT("OH [DUMP_OPTIONS ...] \n")
TEXT("OH [FLAGS_OPTIONS ...] \n")
TEXT("OH -c [COMPARE_OPTIONS ...] BEFORE_LOG AFTER_LOG \n")
TEXT(" \n")
TEXT("DUMP_OPTIONS are: \n")
TEXT(" \n")
TEXT(" -p N - displays only open handles for process with ID of n. If not \n")
TEXT(" specified perform a system wide dump. \n")
TEXT(" -t TYPENAME - displays only open object names of specified type. \n")
TEXT(" -o FILENAME - specifies the name of the file to write the output to. \n")
TEXT(" -a includes objects with no name. \n")
TEXT(" -s display summary information \n")
TEXT(" -h display stack traces for handles (a process ID must be specified) \n")
TEXT(" -u display only handles with no references in process memory \n")
TEXT(" -v verbose mode (used for debugging oh) \n")
TEXT(" NAME - displays only handles that contain the specified name. \n")
TEXT(" \n")
TEXT("FLAGS_OPTIONS are: \n")
TEXT(" \n")
TEXT(" [+kst|-kst] - enable or disable kst flag (kernel mode stack traces). \n")
TEXT(" [+otl|-otl] - enable or disable otl flag (object lists). \n")
TEXT(" \n")
TEXT("The `OH [+otl] [+kst]' command can be used to set the global flags \n")
TEXT("needed by OH. `+otl' is needed for all OH options and `+kst' is needed \n")
TEXT("by the `-h' option. The changes will take effect only after reboot. \n")
TEXT("The flags can be disabled by using `-otl' or `-kst' respectively. \n")
TEXT(" \n")
TEXT("COMPARE_OPTIONS are: \n")
TEXT(" \n")
TEXT(" -l Print most interesting increases in a separate initial section.\n")
TEXT(" -t Do not add TRACE id to the names if files contain traces. \n")
TEXT(" -all Report decreases as well as increases. \n")
TEXT(" \n")
TEXT("If the OH files have been created with -h option (they contain traces) \n")
TEXT("then handle names will be printed using this syntax: (TRACEID) NAME. \n")
TEXT("In case of a potential leak just search for the TRACEID in the original \n")
TEXT("OH file to find the stack trace. \n")
TEXT(" \n");
typedef DWORD PID;
PID ProcessId;
WCHAR TypeName[ 128 ];
WCHAR SearchName[ 512 ];
typedef struct _HANDLE_AUX_INFO {
ULONG_PTR HandleValue; // value of handle for a process
PID Pid; // Process ID for this handle
DWORD HitCount; // number of references
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleEntry; // points to main table entry
} HANDLE_AUX_INFO, *PHANDLE_AUX_INFO;
//
// Globals
//
struct {
FILE* Output; // output file
PRTL_PROCESS_BACKTRACES TraceDatabase; // stack traces
HANDLE TargetProcess; // process handle to get symtab info from
BOOL DumpTraces; // True if we are to dump stack traces
HANDLE_AUX_INFO* AuxInfo; // additional data for every handle
DWORD AuxSize; // number of entries in AuxInfo
BOOL fOnlyShowNoRefs; // Only show handles with NO references
BOOL fVerbose; // display debugging text
} Globals;
//
// Log comparing main function (ohcmp).
//
VOID
OhCmpCompareLogs (
IN LONG argc,
IN LPTSTR argv[]
);
//
// Global flags handling
//
DWORD
OhGetCurrentGlobalFlags (
);
VOID
OhSetRequiredGlobalFlag (
DWORD Flags
);
VOID
OhResetRequiredGlobalFlag (
DWORD Flags
);
//
// Memory management
//
PVOID
OhAlloc (
SIZE_T Size
);
VOID
OhFree (
PVOID P
);
PVOID
OhZoneAlloc(
IN OUT SIZE_T *Length
);
VOID
OhZoneFree(
IN PVOID Buffer
);
PVOID
OhZoneAllocEx(
SIZE_T Size
);
//
// Others
//
VOID
OhError (
PCHAR File,
ULONG Line,
PCHAR Format,
...
);
VOID
OhDumpHandles (
BOOLEAN DumpAnonymousHandles
);
VOID
OhInitializeHandleStackTraces (
PID Pid
);
VOID
OhDumpStackTrace (
PRTL_PROCESS_BACKTRACES TraceDatabase,
USHORT Index
);
BOOL
OhSetSymbolsPath (
);
VOID
OhStampLog (
VOID
);
VOID
Info (
PCHAR Format,
...
);
VOID
OhComment (
PCHAR Format,
...
);
VOID
Warning (
PCHAR File,
ULONG Line,
PCHAR Format,
...
);
PRTL_DEBUG_INFORMATION
RtlQuerySystemDebugInformation(
ULONG Flags
);
VOID
DoSummary( VOID );
BOOLEAN
AnsiToUnicode(
LPCSTR Source,
PWSTR Destination,
ULONG NumberOfChars
)
{
if (NumberOfChars == 0) {
NumberOfChars = strlen( Source );
}
if (MultiByteToWideChar( CP_ACP,
MB_PRECOMPOSED,
Source,
NumberOfChars,
Destination,
NumberOfChars
) != (LONG)NumberOfChars
) {
SetLastError( ERROR_NO_UNICODE_TRANSLATION );
return FALSE;
}
else {
Destination[ NumberOfChars ] = UNICODE_NULL;
return TRUE;
}
}
/////////////////////////////////////////////////////////////////////
///////////////////////////////////// main(), command line processing
/////////////////////////////////////////////////////////////////////
VOID
Help (
VOID
)
/*++
Routine Description:
This routine prints out a message describing the proper usage of OH.
Arguments:
None.
Return value:
None.
--*/
{
fputs (OhHelpText, stderr);
exit (1);
}
int __cdecl
main (
int argc,
char *argv[],
char *envp[]
)
{
BOOLEAN fAnonymousToo;
BOOLEAN fDoSummary;
char *s;
NTSTATUS Status;
CHAR OutputFileName [MAX_PATH];
DWORD GlobalFlags;
int Index;
BOOL FlagsSetRequested = FALSE;
BOOLEAN bJunk;
_try {
//
// Print to stdout for now.
//
Globals.Output = stdout;
//
// Check for help first.
//
if (argc >= 2 && strstr (argv[1], "?") != NULL) {
Help ();
}
//
// Get current global flags.
//
GlobalFlags = OhGetCurrentGlobalFlags ();
OutputFileName[0]='\0';
ProcessId = 0;
fAnonymousToo = FALSE;
fDoSummary = FALSE ;
//
// Before any other command line parsing check for `+otl' or `+kst' options.
//
for (Index = 1; Index < argc; Index += 1) {
if (_stricmp (argv[Index], "+otl") == 0) {
OhSetRequiredGlobalFlag (FLG_MAINTAIN_OBJECT_TYPELIST);
FlagsSetRequested = TRUE;
}
else if (_stricmp (argv[Index], "+kst") == 0) {
OhSetRequiredGlobalFlag (FLG_KERNEL_STACK_TRACE_DB);
FlagsSetRequested = TRUE;
}
else if (_stricmp (argv[Index], "-otl") == 0) {
OhResetRequiredGlobalFlag (FLG_MAINTAIN_OBJECT_TYPELIST);
FlagsSetRequested = TRUE;
}
else if (_stricmp (argv[Index], "-kst") == 0) {
OhResetRequiredGlobalFlag (FLG_KERNEL_STACK_TRACE_DB);
FlagsSetRequested = TRUE;
}
}
if (FlagsSetRequested == TRUE) {
exit (0);
}
//
// Now check if we want log comparing functionality (a la ohcmp).
//
if (argc > 2 && _stricmp (argv[1], "-c") == 0) {
OhCmpCompareLogs (argc - 1, &(argv[1]));
exit (0);
}
//
// Figure out if we have the +otl global flag. We need to do this
// before getting into real oh functionality.
//
if ((GlobalFlags & FLG_MAINTAIN_OBJECT_TYPELIST) == 0) {
Info ("The system global flag `maintain object type lists' is not enabled \n"
"for this system. Please use `oh +otl' to enable it and then reboot. \n");
exit (1);
}
//
// Finally parse the `oh' command line.
//
while (--argc) {
s = *++argv;
if (*s == '/' || *s == '-') {
while (*++s) {
switch (tolower(*s)) {
case 'a':
case 'A':
fAnonymousToo = TRUE;
break;
case 'p':
case 'P':
if (--argc) {
ProcessId = (PID)atol( *++argv );
}
else {
Help();
}
break;
case 'h':
case 'H':
Globals.DumpTraces = TRUE;
break;
case 'o':
case 'O':
if (--argc) {
strncpy( OutputFileName, *++argv, sizeof(OutputFileName)-1 );
OutputFileName[sizeof(OutputFileName)-1]= 0;
}
else {
Help();
}
break;
case 't':
case 'T':
if (--argc) {
//
// BogdanA - make sure we do not overflow TypeName
//
AnsiToUnicode( *++argv, TypeName, sizeof(TypeName)/sizeof(WCHAR) - 1);
}
else {
Help();
}
break;
case 's':
case 'S':
fDoSummary = TRUE;
break;
case 'u':
case 'U':
Globals.fOnlyShowNoRefs= TRUE;
break;
case 'v':
case 'V':
Globals.fVerbose= TRUE;
break;
default:
Help();
}
}
}
else
if (*SearchName) {
Help();
}
else {
AnsiToUnicode( s, SearchName, sizeof(SearchName)/sizeof(WCHAR) -1 );
}
}
if (OutputFileName[0] == '\0') {
Globals.Output = stdout;
}
else {
Globals.Output = fopen (OutputFileName, "w");
}
if (Globals.Output == NULL) {
OhError (NULL, 0,
"cannot open output file `%s' (error %u) \n",
OutputFileName,
GetLastError ());
}
//
// Get debug privilege. This will be useful for opening processes.
//
Status= RtlAdjustPrivilege( SE_DEBUG_PRIVILEGE,
TRUE,
FALSE,
&bJunk);
if( !NT_SUCCESS(Status) ) {
Info ( "RtlAdjustPrivilege(SE_DEBUG) failed with status = %X. -u may not work.",
Status);
}
//
// Stamp the log with OS version, time, machine name, etc.
//
OhStampLog ();
if (Globals.DumpTraces) {
if (ProcessId == 0) {
OhError (NULL,
0,
"`-h' option can be used only if a process ID is specified with `-p PID'");
}
if ((GlobalFlags & FLG_KERNEL_STACK_TRACE_DB) == 0) {
Info ("The system global flag `get kernel mode stack traces' is not enabled \n"
"for this system. Please use `oh +kst' to enable it and then reboot. \n");
exit (1);
}
OhInitializeHandleStackTraces ( ProcessId );
}
RtlQuerySystemDebugInformation( 0 );
OhDumpHandles (fAnonymousToo);
if ( fDoSummary ) {
DoSummary();
}
if (Globals.Output != stdout) {
fclose (Globals.Output);
}
return 0;
}
_except (EXCEPTION_EXECUTE_HANDLER) {
OhComment ("Exception %X raised within OH process. Aborting ... \n",
_exception_code());
}
return 0;
}
typedef struct _PROCESS_INFO {
LIST_ENTRY Entry;
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
PSYSTEM_THREAD_INFORMATION ThreadInfo[ 1 ];
} PROCESS_INFO, *PPROCESS_INFO;
LIST_ENTRY ProcessListHead;
PSYSTEM_OBJECTTYPE_INFORMATION ObjectInformation;
PSYSTEM_HANDLE_INFORMATION_EX HandleInformation;
PSYSTEM_PROCESS_INFORMATION ProcessInformation;
typedef struct _TYPE_COUNT {
UNICODE_STRING TypeName ;
ULONG HandleCount ;
} TYPE_COUNT, * PTYPE_COUNT ;
#define MAX_TYPE_NAMES 128
TYPE_COUNT TypeCounts[ MAX_TYPE_NAMES + 1 ] ;
UNICODE_STRING UnknownTypeIndex;
#define RTL_NEW( p ) RtlAllocateHeap( RtlProcessHeap(), HEAP_ZERO_MEMORY, sizeof( *p ) )
BOOLEAN
OhLoadSystemModules(
PRTL_DEBUG_INFORMATION Buffer
);
BOOLEAN
OhLoadSystemObjects(
PRTL_DEBUG_INFORMATION Buffer
);
BOOLEAN
OhLoadSystemHandles(
PRTL_DEBUG_INFORMATION Buffer
);
BOOLEAN
OhLoadSystemProcesses(
PRTL_DEBUG_INFORMATION Buffer
);
PSYSTEM_PROCESS_INFORMATION
OhFindProcessInfoForCid(
IN PID UniqueProcessId
);
PRTL_DEBUG_INFORMATION
RtlQuerySystemDebugInformation(
ULONG Flags
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
if (!OhLoadSystemObjects( NULL )) {
fprintf( stderr, "OH2: Unable to query system object information.\n" );
exit (1);
}
if (!OhLoadSystemHandles( NULL )) {
fprintf( stderr, "OH3: Unable to query system handle information.\n" );
exit (1);
}
if (!OhLoadSystemProcesses( NULL )) {
fprintf( stderr, "OH4: Unable to query system process information.\n" );
exit (1);
}
return NULL;
}
BOOLEAN
OhLoadSystemObjects(
PRTL_DEBUG_INFORMATION Buffer
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
NTSTATUS Status;
SYSTEM_OBJECTTYPE_INFORMATION ObjectInfoBuffer;
SIZE_T RequiredLength, NewLength=0;
ULONG i;
PSYSTEM_OBJECTTYPE_INFORMATION TypeInfo;
ObjectInformation = &ObjectInfoBuffer;
RequiredLength = sizeof( *ObjectInformation );
while (TRUE) {
Status = NtQuerySystemInformation( SystemObjectInformation,
ObjectInformation,
(ULONG)RequiredLength,
(ULONG *)&NewLength
);
if (Status == STATUS_INFO_LENGTH_MISMATCH && NewLength > RequiredLength) {
if (ObjectInformation != &ObjectInfoBuffer) {
OhZoneFree (ObjectInformation);
}
RequiredLength = NewLength + 4096;
ObjectInformation = (PSYSTEM_OBJECTTYPE_INFORMATION)OhZoneAlloc (&RequiredLength);
if( ObjectInformation == NULL ) {
return FALSE;
}
}
else if (!NT_SUCCESS( Status )) {
if( ObjectInformation != &ObjectInfoBuffer ) {
OhZoneFree (ObjectInformation);
}
return FALSE;
}
else {
break;
}
}
TypeInfo = ObjectInformation;
while (TRUE) {
if (TypeInfo->TypeIndex < MAX_TYPE_NAMES) {
TypeCounts[ TypeInfo->TypeIndex ].TypeName = TypeInfo->TypeName;
}
if (TypeInfo->NextEntryOffset == 0) {
break;
}
TypeInfo = (PSYSTEM_OBJECTTYPE_INFORMATION)
((PCHAR)ObjectInformation + TypeInfo->NextEntryOffset);
}
RtlInitUnicodeString( &UnknownTypeIndex, L"UnknownTypeIdx" );
for (i=0; i<=MAX_TYPE_NAMES; i++) {
if (TypeCounts[ i ].TypeName.Length == 0 ) {
TypeCounts[ i ].TypeName = UnknownTypeIndex;
}
}
return TRUE;
}
BOOLEAN
OhLoadSystemHandles(
PRTL_DEBUG_INFORMATION Buffer
)
/*++
Routine description:
This routine ...
Parameters:
Information buffer to fill.
Return value:
True if all information was obtained from kernel side.
--*/
{
NTSTATUS Status;
SYSTEM_HANDLE_INFORMATION_EX HandleInfoBuffer;
SIZE_T RequiredLength;
SIZE_T NewLength = 0;
PSYSTEM_OBJECTTYPE_INFORMATION TypeInfo;
PSYSTEM_OBJECT_INFORMATION ObjectInfo;
HandleInformation = &HandleInfoBuffer;
RequiredLength = sizeof( *HandleInformation );
while (TRUE) {
Status = NtQuerySystemInformation( SystemExtendedHandleInformation,
HandleInformation,
(ULONG)RequiredLength,
(ULONG *)&NewLength
);
if (Status == STATUS_INFO_LENGTH_MISMATCH && NewLength > RequiredLength) {
if (HandleInformation != &HandleInfoBuffer) {
OhZoneFree (HandleInformation);
}
RequiredLength = NewLength + 4096; // slop, since we may trigger more handle creations.
HandleInformation = (PSYSTEM_HANDLE_INFORMATION_EX)OhZoneAlloc( &RequiredLength );
if (HandleInformation == NULL) {
return FALSE;
}
}
else if (!NT_SUCCESS( Status )) {
if (HandleInformation != &HandleInfoBuffer) {
OhZoneFree (HandleInformation);
}
OhError (__FILE__, __LINE__,
"query (SystemExtendedHandleInformation) failed with status %08X \n",
Status);
return FALSE;
}
else {
break;
}
}
TypeInfo = ObjectInformation;
while (TRUE) {
ObjectInfo = (PSYSTEM_OBJECT_INFORMATION)
((PCHAR)TypeInfo->TypeName.Buffer + TypeInfo->TypeName.MaximumLength);
while (TRUE) {
if (ObjectInfo->HandleCount != 0) {
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleEntry;
ULONG HandleNumber;
HandleEntry = &HandleInformation->Handles[ 0 ];
HandleNumber = 0;
while (HandleNumber++ < HandleInformation->NumberOfHandles) {
if (!(HandleEntry->HandleAttributes & 0x80) &&
HandleEntry->Object == ObjectInfo->Object
) {
HandleEntry->Object = ObjectInfo;
HandleEntry->HandleAttributes |= 0x80;
}
HandleEntry++;
}
}
if (ObjectInfo->NextEntryOffset == 0) {
break;
}
ObjectInfo = (PSYSTEM_OBJECT_INFORMATION)
((PCHAR)ObjectInformation + ObjectInfo->NextEntryOffset);
}
if (TypeInfo->NextEntryOffset == 0) {
break;
}
TypeInfo = (PSYSTEM_OBJECTTYPE_INFORMATION)
((PCHAR)ObjectInformation + TypeInfo->NextEntryOffset);
}
return TRUE;
}
BOOLEAN
OhLoadSystemProcesses(
PRTL_DEBUG_INFORMATION Buffer
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
NTSTATUS Status;
SIZE_T RequiredLength;
ULONG i, TotalOffset;
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
PSYSTEM_THREAD_INFORMATION ThreadInfo;
PPROCESS_INFO ProcessEntry;
UCHAR NameBuffer[ MAX_PATH ];
ANSI_STRING AnsiString;
const SIZE_T SIZE_64_KB = 0x10000;
//
// Always initialize the list head, so that a failed
// NtQuerySystemInformation call won't cause an AV later on.
//
InitializeListHead (&ProcessListHead);
RequiredLength = SIZE_64_KB;
ProcessInformation = (PSYSTEM_PROCESS_INFORMATION)OhZoneAllocEx (RequiredLength);
while (TRUE) {
Status = NtQuerySystemInformation (SystemProcessInformation,
ProcessInformation,
(ULONG)RequiredLength,
NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH) {
OhZoneFree (ProcessInformation);
//
// Check for number overflow.
//
if (RequiredLength * 2 < RequiredLength) {
return FALSE;
}
RequiredLength *= 2;
ProcessInformation = (PSYSTEM_PROCESS_INFORMATION)OhZoneAllocEx (RequiredLength);
}
else if (! NT_SUCCESS(Status)) {
OhError (__FILE__, __LINE__,
"query (SystemProcessInformation) failed with status %08X \n",
Status);
}
else {
//
// We managed to read the process information.
//
break;
}
}
ProcessInfo = ProcessInformation;
TotalOffset = 0;
while (TRUE) {
SIZE_T ProcessEntrySize;
NameBuffer[sizeof(NameBuffer) - 1] = 0;
if (ProcessInfo->ImageName.Buffer == NULL) {
_snprintf ((PCHAR)NameBuffer,
sizeof(NameBuffer) - 1,
"System Process (%p)",
ProcessInfo->UniqueProcessId );
}
else {
_snprintf ((PCHAR)NameBuffer,
sizeof(NameBuffer) - 1,
"%wZ",
&ProcessInfo->ImageName );
}
RtlInitAnsiString( &AnsiString, (PCSZ)NameBuffer );
RtlAnsiStringToUnicodeString( &ProcessInfo->ImageName, &AnsiString, TRUE );
ProcessEntrySize = sizeof (*ProcessEntry) + sizeof (ThreadInfo) * ProcessInfo->NumberOfThreads;
ProcessEntry = (PPROCESS_INFO)OhAlloc (ProcessEntrySize);
InitializeListHead( &ProcessEntry->Entry );
ProcessEntry->ProcessInfo = ProcessInfo;
ThreadInfo = (PSYSTEM_THREAD_INFORMATION)(ProcessInfo + 1);
for (i = 0; i < ProcessInfo->NumberOfThreads; i += 1) {
ProcessEntry->ThreadInfo[i] = ThreadInfo;
ThreadInfo += 1;
}
InsertTailList( &ProcessListHead, &ProcessEntry->Entry );
if (ProcessInfo->NextEntryOffset == 0) {
break;
}
TotalOffset += ProcessInfo->NextEntryOffset;
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) ((PCHAR)ProcessInformation + TotalOffset);
}
return TRUE;
}
PSYSTEM_PROCESS_INFORMATION
OhFindProcessInfoForCid(
IN PID UniqueProcessId
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
PLIST_ENTRY Next, Head;
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
PPROCESS_INFO ProcessEntry;
UCHAR NameBuffer [64];
ANSI_STRING AnsiString;
Head = &ProcessListHead;
Next = Head->Flink;
while (Next != Head) {
ProcessEntry = CONTAINING_RECORD (Next,
PROCESS_INFO,
Entry);
ProcessInfo = ProcessEntry->ProcessInfo;
if( ProcessInfo->UniqueProcessId == UlongToHandle(UniqueProcessId) ) {
return ProcessInfo;
}
Next = Next->Flink;
}
ProcessEntry = (PPROCESS_INFO)RtlAllocateHeap (RtlProcessHeap(),
HEAP_ZERO_MEMORY,
sizeof( *ProcessEntry ) + sizeof( *ProcessInfo ));
if (ProcessEntry == NULL) {
printf ("Failed to allocate memory for process\n");
ExitProcess (0);
}
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)(ProcessEntry+1);
ProcessEntry->ProcessInfo = ProcessInfo;
sprintf ((PCHAR)NameBuffer, "Unknown Process" );
RtlInitAnsiString( &AnsiString, (PCSZ)NameBuffer );
RtlAnsiStringToUnicodeString( (PUNICODE_STRING)&ProcessInfo->ImageName, &AnsiString, TRUE );
ProcessInfo->UniqueProcessId = UlongToHandle(UniqueProcessId);
InitializeListHead( &ProcessEntry->Entry );
InsertTailList( &ProcessListHead, &ProcessEntry->Entry );
return ProcessInfo;
}
//
// comparison routine used by qsort and bsearch routines
// key: (Pid, HandleValue)
//
int _cdecl AuxInfoCompare( const void* Arg1, const void* Arg2 )
{
HANDLE_AUX_INFO* Ele1= (HANDLE_AUX_INFO*) Arg1;
HANDLE_AUX_INFO* Ele2= (HANDLE_AUX_INFO*) Arg2;
if( Ele1->Pid < Ele2->Pid ) {
return -1;
}
if( Ele1->Pid > Ele2->Pid ) {
return 1;
}
if( Ele1->HandleValue < Ele2->HandleValue ) {
return -1;
}
if( Ele1->HandleValue > Ele2->HandleValue ) {
return 1;
}
return 0;
}
// OhGatherData
//
// Search through a region of process space for anything that looks
// like it could the value of a handle that is opened by that process.
// If we find a reference, increment the AuxInfo.Hitcount field for that handle.
//
// returns: FALSE if it couldn't scan the region
BOOL
OhGatherData(
IN HANDLE ProcHan,
IN PID PidToExamine,
IN PVOID VAddr,
IN SIZE_T RegionSize
)
{
PDWORD Buf;
SIZE_T BytesRead;
BOOL bStatus;
SIZE_T i;
HANDLE_AUX_INFO AuxToCompare;
HANDLE_AUX_INFO* AuxInfo;
Buf= (PDWORD)LocalAlloc( LPTR, RegionSize );
if( Buf == NULL ) {
OhComment("Failed to alloc mem size= %d\n",RegionSize);
SetLastError( ERROR_NOT_ENOUGH_MEMORY);
return FALSE;
}
bStatus= ReadProcessMemory( ProcHan, VAddr, Buf, RegionSize, &BytesRead );
if( !bStatus ) {
OhComment ( "Failed to ReadProcessMemory\n");
if( Buf ) {
LocalFree( Buf );
}
return FALSE;
}
// feature: this only looks for aligned dword refs. may want unaligned too.
for( i=0; i < BytesRead/sizeof(DWORD); i++ ) {
AuxToCompare.HandleValue= Buf[ i ] & (~3); // kernel ignores 2 lsb
AuxToCompare.Pid= PidToExamine;
AuxInfo= (HANDLE_AUX_INFO*) bsearch( &AuxToCompare,
Globals.AuxInfo,
Globals.AuxSize,
sizeof( HANDLE_AUX_INFO ),
&AuxInfoCompare );
if( AuxInfo ) {
AuxInfo->HitCount++;
}
}
LocalFree( Buf );
return TRUE;
}
VOID OhSearchForReferencedHandles( PID PidToExamine )
{
HANDLE_AUX_INFO AuxToCompare;
DWORD HandleNumber;
HANDLE ProcHan= NULL; // process to examine
PVOID VAddr; // pointer into process virtual memory
MEMORY_BASIC_INFORMATION MemoryInfo;
DWORD CurrentProtect;
SIZE_T Size;
// ignore process IDs= 0 or 4 because they are the system process
if( ( PidToExamine == (PID)0 ) || ( PidToExamine == (PID)4 ) ) {
return;
}
AuxToCompare.Pid= PidToExamine;
ProcHan= OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
(PID) PidToExamine );
if( NULL == ProcHan ) {
OhComment ( "Could not open process %d\n",PidToExamine );
goto errorexit;
}
// zero the hit counts for this process number
// if we can't read the memory, we will re-fill the HitCount fields with non-zero
for( HandleNumber=0; HandleNumber < Globals.AuxSize; HandleNumber++ ) {
if( Globals.AuxInfo[ HandleNumber ].Pid == PidToExamine ) {
Globals.AuxInfo[ HandleNumber ].HitCount= 0;
}
}
// read thru all interesting process memory
// if we get a value that the process could have written, then see if
// it matches one of our handle values. Keep track of the number of matches.
// if any HitCount field is zero when we are done, there is no way to reference it.
// modulo (encrypted it in memory (xor), truncated into a short, or hidden it
// in a file, memory section not mapped, registry.)
for( VAddr= 0;
VAddr < (PVOID) (0x80000000-0x10000);
VAddr= (PVOID) ((PCHAR) VAddr+ MemoryInfo.RegionSize) )
{
MemoryInfo.RegionSize=0x1000;
Size= VirtualQueryEx( ProcHan,
VAddr,
&MemoryInfo,
sizeof( MemoryInfo ) );
if( Size != sizeof(MemoryInfo) ) {
fprintf(stderr,"VirtualQueryEx failed at %p LastError %d\n",VAddr,GetLastError() );
}
else {
CurrentProtect= MemoryInfo.Protect;
if( MemoryInfo.State == MEM_COMMIT ) {
if( (CurrentProtect & (PAGE_READWRITE|PAGE_READWRITE) ) &&
( (CurrentProtect&PAGE_GUARD)==0 )
) {
BOOL bSta;
bSta= OhGatherData( ProcHan, PidToExamine, VAddr, MemoryInfo.RegionSize );
if( !bSta ) {
goto errorexit;
}
}
}
}
}
CloseHandle( ProcHan ); ProcHan= NULL;
return;
// If we have an error, just mark all the HitCount values so it looks like they
// have been referenced.
errorexit:
for( HandleNumber=0; HandleNumber < Globals.AuxSize; HandleNumber++ ) {
if( Globals.AuxInfo[ HandleNumber ].Pid == PidToExamine ) {
Globals.AuxInfo[ HandleNumber].HitCount= LARGE_HITCOUNT;
}
}
CloseHandle( ProcHan ); ProcHan= NULL;
return;
}
VOID
OhBuildAuxTables( PID PidToExamine )
/*++
Routine description:
Creates auxillary table keyed with (HandleValue,Pid) containing HitCount
Parameters:
The Process ID to examine
Return value:
--*/
{
PID PreviousUniqueProcessId;
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleEntry;
ULONG HandleNumber;
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
DWORD TotalHandles;
PID LastPid;
HANDLE_AUX_INFO* AuxInfo;
Globals.AuxInfo= NULL;
Globals.AuxSize= 0;
TotalHandles=(DWORD) HandleInformation->NumberOfHandles;
//
// Make PREfast happy. Not really needed for the correct execution
// of the code.
//
ProcessInfo = NULL;
// Allocate AuxInfo table
AuxInfo= (HANDLE_AUX_INFO*) LocalAlloc( LPTR, TotalHandles*sizeof(HANDLE_AUX_INFO) );
if( NULL == AuxInfo ) {
return;
}
Globals.AuxInfo= AuxInfo;
Globals.AuxSize= TotalHandles;
// populate the table with key information
HandleEntry = &HandleInformation->Handles[ 0 ];
PreviousUniqueProcessId = (PID) -1;
for (HandleNumber = 0; HandleNumber < TotalHandles; HandleNumber += 1) {
if (PreviousUniqueProcessId != (PID)HandleEntry->UniqueProcessId) {
PreviousUniqueProcessId = (PID)HandleEntry->UniqueProcessId;
ProcessInfo = OhFindProcessInfoForCid( PreviousUniqueProcessId );
}
AuxInfo[ HandleNumber ].HandleValue = HandleEntry->HandleValue;
AuxInfo[ HandleNumber ].Pid = HandleToUlong(ProcessInfo ? ProcessInfo->UniqueProcessId : NULL);
AuxInfo[ HandleNumber ].HitCount = LARGE_HITCOUNT;
AuxInfo[ HandleNumber ].HandleEntry = HandleEntry;
HandleEntry += 1;
}
// Sort the table so bsearch works later
qsort( AuxInfo,
TotalHandles,
sizeof( HANDLE_AUX_INFO ),
AuxInfoCompare );
//
// Search the process or processes for references and keep count
//
if( PidToExamine ) {
OhSearchForReferencedHandles( PidToExamine );
return;
}
//
// No Pid then do all the Pids on the system
// (actually only search Pids that have kernel handles)
//
LastPid= (PID) -1;
for( HandleNumber=0; HandleNumber < TotalHandles; HandleNumber++ ) {
PID ThisPid= Globals.AuxInfo[ HandleNumber ].Pid;
if( LastPid != ThisPid ) {
OhSearchForReferencedHandles( ThisPid );
LastPid= ThisPid;
}
}
}
VOID
OhDumpHandles (
BOOLEAN DumpAnonymousHandles
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
PID PreviousUniqueProcessId;
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX HandleEntry;
ULONG HandleNumber;
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
PSYSTEM_OBJECT_INFORMATION ObjectInfo;
PUNICODE_STRING ObjectTypeName;
WCHAR ObjectName[ 1024 ];
PVOID Object;
CHAR OutputLine[ 512 ];
PWSTR s;
ULONG n;
DWORD d = 0;
BOOL AnyRefs;
OhBuildAuxTables( ProcessId );
HandleEntry = &HandleInformation->Handles[ 0 ];
HandleNumber = 0;
PreviousUniqueProcessId = (PID) -1;
for (HandleNumber = 0;
HandleNumber < HandleInformation->NumberOfHandles;
HandleNumber++, HandleEntry++
)
{
if (PreviousUniqueProcessId != (PID)HandleEntry->UniqueProcessId) {
PreviousUniqueProcessId = (PID)HandleEntry->UniqueProcessId;
ProcessInfo = OhFindProcessInfoForCid( PreviousUniqueProcessId );
}
ObjectName[ 0 ] = UNICODE_NULL;
if (HandleEntry->HandleAttributes & 0x80) {
ObjectInfo = (PSYSTEM_OBJECT_INFORMATION)(HandleEntry->Object);
Object = ObjectInfo->Object;
_try {
if (ObjectInfo->NameInfo.Name.Length != 0 &&
*(ObjectInfo->NameInfo.Name.Buffer) == UNICODE_NULL
) {
ObjectInfo->NameInfo.Name.Length = 0;
}
//
// Make sure n is length of Object minus the NULL terminator
//
n = min(ObjectInfo->NameInfo.Name.Length / sizeof(WCHAR), sizeof(ObjectName) / sizeof( WCHAR) - 1);
wcsncpy( ObjectName,
ObjectInfo->NameInfo.Name.Buffer,
n
);
ObjectName[ n ] = UNICODE_NULL;
}
_except( EXCEPTION_EXECUTE_HANDLER ) {
_snwprintf( ObjectName, sizeof(ObjectName)/sizeof(WCHAR) - 1,
L"[%04x, %04x, %08p]",
ObjectInfo->NameInfo.Name.MaximumLength,
ObjectInfo->NameInfo.Name.Length,
ObjectInfo->NameInfo.Name.Buffer
);
ObjectName[sizeof(ObjectName)/sizeof(WCHAR) - 1] = 0;
}
}
else {
ObjectInfo = NULL;
Object = HandleEntry->Object;
}
if( ProcessId != 0 && ProcessInfo->UniqueProcessId != UlongToHandle(ProcessId) ) {
continue;
}
ObjectTypeName = &TypeCounts[ HandleEntry->ObjectTypeIndex < MAX_TYPE_NAMES ?
HandleEntry->ObjectTypeIndex : MAX_TYPE_NAMES ].TypeName ;
TypeCounts[ HandleEntry->ObjectTypeIndex < MAX_TYPE_NAMES ?
HandleEntry->ObjectTypeIndex : MAX_TYPE_NAMES ].HandleCount++ ;
if (TypeName[0]) {
if (_wcsicmp( TypeName, ObjectTypeName->Buffer )) {
continue;
}
}
if (!*ObjectName) {
if (! DumpAnonymousHandles) {
continue;
}
}
else
if (SearchName[0]) {
if (!wcsstr( ObjectName, SearchName )) {
s = ObjectName;
n = wcslen( SearchName );
while (*s) {
if (!_wcsnicmp( s, SearchName, n )) {
break;
}
s += 1;
}
if (!*s) {
continue;
}
}
}
// See if there were any references to this handle in the process memory space
AnyRefs= TRUE;
{
HANDLE_AUX_INFO* AuxInfo;
HANDLE_AUX_INFO AuxToCompare;
AuxToCompare.Pid= HandleToUlong( ProcessInfo->UniqueProcessId );
AuxToCompare.HandleValue= HandleEntry->HandleValue;
AuxInfo= (HANDLE_AUX_INFO*) bsearch( &AuxToCompare,
Globals.AuxInfo,
Globals.AuxSize,
sizeof( HANDLE_AUX_INFO ),
&AuxInfoCompare );
if( AuxInfo ) {
if( AuxInfo->HitCount == 0 ) {
AnyRefs=FALSE;
}
}
}
if( (!Globals.fOnlyShowNoRefs) || (Globals.fOnlyShowNoRefs && (AnyRefs==FALSE) ) ) {
if( Globals.fOnlyShowNoRefs) {
Info ( "noref_" );
}
if (Globals.DumpTraces) {
Info ("%p %-14wZ %-14wZ %04x (%04x) %ws\n",
ProcessInfo->UniqueProcessId,
&ProcessInfo->ImageName,
ObjectTypeName,
HandleEntry->HandleValue,
HandleEntry->CreatorBackTraceIndex,
*ObjectName ? ObjectName : L"");
}
else {
Info ("%p %-14wZ %-14wZ %04x %ws\n",
ProcessInfo->UniqueProcessId,
&ProcessInfo->ImageName,
ObjectTypeName,
HandleEntry->HandleValue,
*ObjectName ? ObjectName : L"");
}
if (HandleEntry->CreatorBackTraceIndex && Globals.TraceDatabase) {
OhDumpStackTrace (Globals.TraceDatabase,
HandleEntry->CreatorBackTraceIndex);
}
}
}
return;
}
VOID
DoSummary( VOID )
/*++
Routine description:
Parameters:
Return value:
--*/
{
ULONG i ;
ULONG ignored ;
Info ("Summary: \n");
for ( i = 0 ; i < MAX_TYPE_NAMES ; i++ )
{
if ( TypeCounts[ i ].HandleCount )
{
Info (" %-20ws\t%d\n",
TypeCounts[ i ].TypeName.Buffer,
TypeCounts[ i ].HandleCount );
}
}
}
/////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////// Handle stack traces
/////////////////////////////////////////////////////////////////////
VOID
OhError (
PCHAR File,
ULONG Line,
PCHAR Format,
...
);
PCHAR
OhNameForAddress(
IN HANDLE UniqueProcess,
IN PVOID Address
);
PRTL_PROCESS_BACKTRACES
OhLoadSystemTraceDatabase (
)
{
const SIZE_T OneMb = 0x100000;
NTSTATUS Status;
PRTL_PROCESS_BACKTRACES TraceDatabase;
SIZE_T RequiredLength;
SIZE_T CurrentLength;
CurrentLength = OneMb;
RequiredLength = 0;
TraceDatabase = (PRTL_PROCESS_BACKTRACES)VirtualAlloc (NULL,
CurrentLength,
MEM_COMMIT,
PAGE_READWRITE);
if (TraceDatabase == NULL) {
OhError (__FILE__, __LINE__,
"failed to allocate %p bytes", CurrentLength);
}
while (TRUE) {
Status = NtQuerySystemInformation (SystemStackTraceInformation,
TraceDatabase,
(ULONG)CurrentLength,
(ULONG *)&RequiredLength);
if (Status == STATUS_INFO_LENGTH_MISMATCH) {
CurrentLength = RequiredLength + OneMb;
VirtualFree (TraceDatabase,
0,
MEM_RELEASE);
TraceDatabase = (PRTL_PROCESS_BACKTRACES)VirtualAlloc (NULL,
CurrentLength,
MEM_COMMIT,
PAGE_READWRITE);
if (TraceDatabase == NULL) {
OhError (__FILE__, __LINE__,
"failed to allocate %p bytes", CurrentLength);
}
}
else if (! NT_SUCCESS(Status)) {
OhError (__FILE__, __LINE__,
"QuerySystemInformation failed with status %08x",Status);
}
else {
//
// We managed to read the stack trace database.
//
break;
}
}
return TraceDatabase;
}
VOID
OhDumpStackTrace (
PRTL_PROCESS_BACKTRACES TraceDatabase,
USHORT Index
)
{
PRTL_PROCESS_BACKTRACE_INFORMATION Trace;
USHORT I;
PCHAR Name;
if (Index >= TraceDatabase->NumberOfBackTraces) {
return;
}
Trace = &(TraceDatabase->BackTraces[Index - 1]);
if (Trace->Depth > 0) {
Info ("\n");
}
for (I = 0; I < Trace->Depth; I += 1) {
if ((ULONG_PTR)(Trace->BackTrace[I]) < 0x80000000) {
if (Trace->BackTrace[I] == NULL) {
break;
}
Name = OhNameForAddress (Globals.TargetProcess,
Trace->BackTrace[I]);
Info ("\t%p %s\n",
Trace->BackTrace[I],
(Name ? Name : "<unknown>"));
}
else {
Info ("\t%p <kernel address>\n",
Trace->BackTrace[I]);
}
}
Info ("\n");
}
BOOL
OhEnumerateModules(
IN LPSTR ModuleName,
IN ULONG_PTR BaseOfDll,
IN PVOID UserContext
)
/*++
UmdhEnumerateModules
Module enumeration 'proc' for imagehlp. Call SymLoadModule on the
specified module and if that succeeds cache the module name.
ModuleName is an LPSTR indicating the name of the module imagehlp is
enumerating for us;
BaseOfDll is the load address of the DLL, which we don't care about, but
SymLoadModule does;
UserContext is a pointer to the relevant SYMINFO, which identifies
our connection.
--*/
{
DWORD64 Result;
Result = SymLoadModule(Globals.TargetProcess,
NULL, // hFile not used
NULL, // use symbol search path
ModuleName, // ModuleName from Enum
BaseOfDll, // LoadAddress from Enum
0); // Let ImageHlp figure out DLL size
// SilviuC: need to understand exactly what does this function return
if (Result) {
Warning (NULL, 0,
"SymLoadModule (%s, %p) failed with error %X (%u)",
ModuleName, BaseOfDll,
GetLastError(), GetLastError());
return FALSE;
}
OhComment (" %s (%p) ...", ModuleName, BaseOfDll);
return TRUE;
}
VOID
Info (
PCHAR Format,
...
)
{
va_list Params;
va_start (Params, Format);
vfprintf (Globals.Output, Format, Params);
}
VOID
OhComment (
PCHAR Format,
...
)
{
va_list Params;
va_start (Params, Format);
fprintf (Globals.Output, "// ");
vfprintf (Globals.Output, Format, Params);
fprintf (Globals.Output, "\n");
}
VOID
Warning (
PCHAR File,
ULONG Line,
PCHAR Format,
...
)
{
va_list Params;
va_start (Params, Format);
if (File) {
fprintf (stderr, "Warning: %s: %u: ", File, Line);
}
else {
fprintf (stderr, "Warning: ");
}
vfprintf (stderr, Format, Params);
fprintf (stderr, "\n");
}
VOID
OhError (
PCHAR File,
ULONG Line,
PCHAR Format,
...
)
{
va_list Params;
va_start (Params, Format);
if (File) {
fprintf (stderr, "Error: %s: %u: ", File, Line);
}
else {
fprintf (stderr, "Error: ");
}
vfprintf (stderr, Format, Params);
fprintf (stderr, "\n");
exit (1);
}
VOID
OhStampLog (
VOID
)
/*++
Routine description:
This routines writes an initial stamp in the log.
Parameters:
None.
Return value:
None.
--*/
{
CHAR CompName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD CompNameLength = MAX_COMPUTERNAME_LENGTH + 1;
SYSTEMTIME st;
OSVERSIONINFOEX OsInfo;
//
// Stamp the log
//
ZeroMemory (&OsInfo, sizeof OsInfo);
OsInfo.dwOSVersionInfoSize = sizeof OsInfo;
GetVersionEx ((POSVERSIONINFO)(&OsInfo));
GetLocalTime(&st);
GetComputerName(CompName, &CompNameLength);
OhComment ("");
OhComment ("TIME: %4u-%02u-%02u %02u:%02u", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute);
OhComment ("MACHINE: %s", CompName);
OhComment ("BUILD: %u", OsInfo.dwBuildNumber);
OhComment ("OH version: %s", BUILD_MACHINE_TAG);
OhComment ("");
OhComment ("");
}
VOID
OhInitializeHandleStackTraces (
PID Pid
)
/*++
Routine description:
This routine initializes all interal structures required to
read handle stack traces. It will adjust priviles (in order
for this to work on lsass, winlogon, etc.), open the process,
read from kernel the trace database.
Parameters:
Pid - process ID for the process for which we will get traces.
Return value:
None.
--*/
{
BOOL Result;
NTSTATUS Status;
//
// Check if we have a symbols path defined and define a default one
// if not.
//
OhSetSymbolsPath ();
//
// Imagehlp library needs the query privilege for the process
// handle and of course we need also read privilege because
// we will read all sorts of things from the process.
//
Globals.TargetProcess = OpenProcess (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
Pid);
if (Globals.TargetProcess == NULL) {
OhError (__FILE__, __LINE__,
"OpenProcess(%u) failed with error %u", Pid, GetLastError());
}
OhComment ("Process %u opened.", Pid);
//
// Attach ImageHlp and enumerate the modules.
//
Result = SymInitialize(Globals.TargetProcess, // target process
NULL, // standard symbols search path
TRUE); // invade process space with symbols
if (Result == FALSE) {
ULONG ErrorCode = GetLastError();
if (ErrorCode >= 0x80000000) {
OhError (__FILE__, __LINE__,
"imagehlp.SymInitialize() failed with error %X", ErrorCode);
}
else {
OhError (__FILE__, __LINE__,
"imagehlp.SymInitialize() failed with error %u", ErrorCode);
}
}
OhComment ("Dbghelp initialized.");
SymSetOptions(SYMOPT_CASE_INSENSITIVE |
SYMOPT_DEFERRED_LOADS |
SYMOPT_LOAD_LINES |
SYMOPT_UNDNAME);
OhComment ("Enumerating modules ...");
OhComment ("");
Result = SymEnumerateModules (Globals.TargetProcess,
OhEnumerateModules,
Globals.TargetProcess);
if (Result == FALSE) {
OhError (__FILE__, __LINE__,
"SymEnumerateModules() failed with error %u", GetLastError());
}
OhComment ("");
OhComment ("Finished module enumeration.");
//
// Initialize local trace database. Note that order is important.
// Initialize() assumes the process handle to the target process
// already exists and the symbol management package was initialized.
//
OhComment ("Loading stack trace database ...");
Globals.TraceDatabase = OhLoadSystemTraceDatabase ();
OhComment ("Initialization finished.");
OhComment ("");
OhComment ("\n");
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////// Symbol lookup
/////////////////////////////////////////////////////////////////////
#define HNDL_SYMBOL_MAP_BUCKETS 4096
typedef struct _HNDL_SYMBOL_ENTRY
{
PVOID Address;
PCHAR Symbol;
struct _HNDL_SYMBOL_ENTRY * Next;
} HNDL_SYMBOL_ENTRY, * PHNDL_SYMBOL_ENTRY;
PHNDL_SYMBOL_ENTRY OhSymbolsMap [HNDL_SYMBOL_MAP_BUCKETS];
PCHAR
OhFindSymbol (
PVOID Address
)
{
ULONG_PTR Bucket = ((ULONG_PTR)Address >> 2) % HNDL_SYMBOL_MAP_BUCKETS;
PHNDL_SYMBOL_ENTRY Node = OhSymbolsMap[Bucket];
while (Node != NULL ) {
if (Node->Address == Address) {
return Node->Symbol;
}
Node = Node->Next;
}
return NULL;
}
VOID
OhInsertSymbol (
PCHAR Symbol,
PVOID Address
)
{
ULONG_PTR Bucket = ((ULONG_PTR)Address >> 2) % HNDL_SYMBOL_MAP_BUCKETS;
PHNDL_SYMBOL_ENTRY New;
New = (PHNDL_SYMBOL_ENTRY) OhAlloc (sizeof (HNDL_SYMBOL_ENTRY));
New->Symbol = Symbol;
New->Address = Address;
New->Next = OhSymbolsMap[Bucket];
OhSymbolsMap[Bucket] = New;
}
PCHAR
OhNameForAddress(
IN HANDLE UniqueProcess,
IN PVOID Address
)
{
IMAGEHLP_MODULE ModuleInfo;
CHAR SymbolBuffer[512];
PIMAGEHLP_SYMBOL Symbol;
ULONG_PTR Offset;
CHAR Name [512 + 100];
SIZE_T TotalSize;
BOOL Result;
PVOID Duplicate;
PCHAR SymbolName;
//
// Lookup in map first ..
//
SymbolName = OhFindSymbol (Address);
if (SymbolName != NULL) {
return SymbolName;
}
TotalSize = 0;
ModuleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE);
if (SymGetModuleInfo (UniqueProcess, (ULONG_PTR)Address, &ModuleInfo)) {
TotalSize += strlen( ModuleInfo.ModuleName );
}
else {
Warning (NULL, 0,
"Symbols: cannot identify module for address %p",
Address);
return NULL;
}
Symbol = (PIMAGEHLP_SYMBOL)SymbolBuffer;
Symbol->MaxNameLength = 512 - sizeof(IMAGEHLP_SYMBOL) - 1;
Name[sizeof(Name) / sizeof(Name[0]) - 1] = 0;
if (SymGetSymFromAddr (UniqueProcess, (ULONG_PTR)Address, &Offset, Symbol)) {
TotalSize += strlen (Symbol->Name) + 16 + 3;
_snprintf (Name,
sizeof(Name) / sizeof(Name[0]) - 1,
"%s!%s+%08X", ModuleInfo.ModuleName, Symbol->Name, Offset);
Duplicate = _strdup(Name);
OhInsertSymbol ((PCHAR)Duplicate, Address);
return (PCHAR)Duplicate;
}
else {
Warning (NULL, 0,
"Symbols: incorrect symbols for module %s (address %p)",
ModuleInfo.ModuleName,
Address);
TotalSize += strlen ("???") + 16 + 5;
_snprintf (Name,
sizeof(Name) / sizeof(Name[0]) - 1,
"%s!%s @ %p", ModuleInfo.ModuleName, "???", Address);
Duplicate = _strdup(Name);
OhInsertSymbol ((PCHAR)Duplicate, Address);
return (PCHAR)Duplicate;
}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////// Registry handling
/////////////////////////////////////////////////////////////////////
//
// Registry key name to read/write system global flags
//
#define KEYNAME_SESSION_MANAGER "SYSTEM\\CurrentControlSet\\Control\\Session Manager"
DWORD
OhGetCurrentGlobalFlags (
)
{
SYSTEM_FLAGS_INFORMATION Information;
NTSTATUS Status;
Status = NtQuerySystemInformation (SystemFlagsInformation,
&Information,
sizeof Information,
NULL);
if (! NT_SUCCESS(Status)) {
OhError (NULL, 0,
"cannot get current global flags settings (error %08X) \n",
Status);
}
return Information.Flags;
}
DWORD
OhGetSystemRegistryFlags (
)
{
DWORD cbKey;
DWORD GFlags;
DWORD type;
HKEY hKey;
LONG Result;
Result = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
KEYNAME_SESSION_MANAGER,
0,
KEY_READ | KEY_WRITE,
&hKey);
if (Result != ERROR_SUCCESS) {
OhError (NULL, 0,
"cannot open registry key `%s' \n",
KEYNAME_SESSION_MANAGER);
}
cbKey = sizeof (GFlags);
Result = RegQueryValueEx (hKey,
"GlobalFlag",
0,
&type,
(LPBYTE)&GFlags,
&cbKey);
if (Result != ERROR_SUCCESS || type != REG_DWORD) {
OhError (NULL, 0,
"cannot read registry value '%s'\n",
KEYNAME_SESSION_MANAGER "\\GlobalFlag");
}
RegCloseKey (hKey);
hKey = NULL;
return GFlags;
}
VOID
OhSetSystemRegistryFlags(
DWORD GFlags
)
{
HKEY hKey;
LONG Result;
Result = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
KEYNAME_SESSION_MANAGER,
0,
KEY_READ | KEY_WRITE,
&hKey);
if (Result != ERROR_SUCCESS) {
OhError (NULL, 0,
"cannot open registry key '%s'\n",
KEYNAME_SESSION_MANAGER );
}
Result = RegSetValueEx (hKey,
"GlobalFlag",
0,
REG_DWORD,
(LPBYTE)&GFlags,
sizeof( GFlags ));
if (Result != ERROR_SUCCESS) {
OhError (NULL, 0,
"cannot write registry value '%s'\n",
KEYNAME_SESSION_MANAGER "\\GlobalFlag" );
}
RegCloseKey (hKey);
hKey = NULL;
}
VOID
OhSetRequiredGlobalFlag (
DWORD Flags
)
{
DWORD RegistryFlags;
if ((Flags & FLG_KERNEL_STACK_TRACE_DB)) {
RegistryFlags = OhGetSystemRegistryFlags ();
OhSetSystemRegistryFlags (RegistryFlags | FLG_KERNEL_STACK_TRACE_DB);
Info ("Enabled `kernel mode stack traces' flag needed for handle traces. \n"
"Will take effect next time you boot. \n"
" \n");
}
else if ((Flags & FLG_MAINTAIN_OBJECT_TYPELIST)) {
RegistryFlags = OhGetSystemRegistryFlags ();
OhSetSystemRegistryFlags (RegistryFlags | FLG_MAINTAIN_OBJECT_TYPELIST);
Info ("Enabled `object type list' flag needed by the OH utility. \n"
"Will take effect next time you boot. \n"
" \n");
}
}
VOID
OhResetRequiredGlobalFlag (
DWORD Flags
)
{
DWORD RegistryFlags;
if ((Flags & FLG_KERNEL_STACK_TRACE_DB)) {
RegistryFlags = OhGetSystemRegistryFlags ();
RegistryFlags &= ~FLG_KERNEL_STACK_TRACE_DB;
OhSetSystemRegistryFlags (RegistryFlags);
Info ("Disabled `kernel mode stack traces' flag needed for handle traces. \n"
"Will take effect next time you boot. \n"
" \n");
}
else if ((Flags & FLG_MAINTAIN_OBJECT_TYPELIST)) {
RegistryFlags = OhGetSystemRegistryFlags ();
RegistryFlags &= ~FLG_MAINTAIN_OBJECT_TYPELIST;
OhSetSystemRegistryFlags (RegistryFlags);
Info ("Disabled `object type list' flag needed by the OH utility. \n"
"Will take effect next time you boot. \n"
" \n");
}
}
/////////////////////////////////////////////////////////////////////
////////////////////////////////////////// Memory management routines
/////////////////////////////////////////////////////////////////////
PVOID
OhAlloc (
SIZE_T Size
)
{
PVOID P;
P = RtlAllocateHeap (RtlProcessHeap(), HEAP_ZERO_MEMORY, Size);
if (P == NULL) {
OhError (__FILE__, __LINE__,
"failed to allocate %u bytes",
Size);
}
return P;
}
VOID
OhFree (
PVOID P
)
{
RtlFreeHeap (RtlProcessHeap(), 0, P);
}
PVOID
OhZoneAlloc(
IN OUT SIZE_T *Length
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
PVOID Buffer;
MEMORY_BASIC_INFORMATION MemoryInformation;
Buffer = VirtualAlloc (NULL,
*Length,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (Buffer == NULL) {
OhError (__FILE__, __LINE__,
"failed to allocate %u bytes",
*Length);
}
else if (Buffer != NULL &&
VirtualQuery (Buffer, &MemoryInformation, sizeof (MemoryInformation))) {
*Length = MemoryInformation.RegionSize;
}
return Buffer;
}
PVOID
OhZoneAllocEx(
SIZE_T Size
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
PVOID Buffer;
Buffer = VirtualAlloc (NULL,
Size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
if (Buffer == NULL) {
OhError (__FILE__, __LINE__,
"failed to allocate %u bytes",
Size);
}
return Buffer;
}
VOID
OhZoneFree(
IN PVOID Buffer
)
/*++
Routine description:
Parameters:
Return value:
--*/
{
if (!VirtualFree (Buffer,
0,
MEM_RELEASE)) {
fprintf( stderr, "Unable to free buffer memory %p, error == %u\n", Buffer, GetLastError() );
exit( 1 );
}
}
BOOL
OhSetSymbolsPath (
)
/*++
Routine Description:
OhSetSymbolsPath tries to set automatically the symbol path if
_NT_SYMBOL_PATH environment variable is not already defined.
Arguments:
None.
Return Value:
Returns TRUE if the symbols path seems to be ok, that is
_NT_SYMBOL_PATH was defined or we managed to define it to
a meaningful value.
--*/
{
TCHAR Buffer [MAX_PATH];
DWORD Length;
BOOL Result;
Length = GetEnvironmentVariable (TEXT("_NT_SYMBOL_PATH"),
Buffer,
MAX_PATH);
if (Length == 0) {
Warning (NULL, 0,
"_NT_SYMBOL_PATH variable is not defined. Will be set to %%windir%%\\symbols.");
Length = GetEnvironmentVariable (TEXT("windir"),
Buffer,
MAX_PATH);
if (Length == 0) {
OhError (NULL, 0,
"Cannot get value of WINDIR environment variable.");
return FALSE;
}
//
// Check that we have enough space to append a string....
//
if (strlen(Buffer) + strlen("\\symbols") >= sizeof(Buffer)) {
OhError (NULL, 0,
"Huge WINDIR string `%s'", Buffer);
return FALSE;
}
strcat (Buffer, TEXT("\\symbols"));
Result = SetEnvironmentVariable (TEXT("_NT_SYMBOL_PATH"),
Buffer);
if (Result == FALSE) {
OhError (NULL, 0,
"Failed to set _NT_SYMBOL_PATH to `%s'", Buffer);
return FALSE;
}
OhComment ("_NT_SYMBOL_PATH set by default to %s", Buffer);
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////// Log compare
/////////////////////////////////////////////////////////////////////
LPTSTR
OhCmpSearchStackTrace (
LPTSTR FileName,
LPTSTR TraceId
);
PSTRINGTOINTASSOCIATION
MAPSTRINGTOINT::GetStartPosition(
VOID
)
/*++
Routine Description:
This routine retrieves the first association in the list for iteration with the
MAPSTRINGTOINT::GetNextAssociation function.
Arguments:
None.
Return value:
The first association in the list, or NULL if the map is empty.
--*/
{
return Associations;
}
VOID
MAPSTRINGTOINT::GetNextAssociation(
IN OUT PSTRINGTOINTASSOCIATION & Position,
OUT LPTSTR & Key,
OUT LONG & Value)
/*++
Routine Description:
This routine retrieves the data for the current association and sets Position to
point to the next association (or NULL if this is the last association.)
Arguments:
Position - Supplies the current association and returns the next association.
Key - Returns the key for the current association.
Value - Returns the value for the current association.
Return value:
None.
--*/
{
Key = Position->Key;
Value = Position->Value;
Position = Position->Next;
}
MAPSTRINGTOINT::MAPSTRINGTOINT(
)
/*++
Routine Description:
This routine initializes a MAPSTRINGTOINT to be empty.
Arguments:
None.
Return value:
None.
--*/
{
Associations = NULL;
}
MAPSTRINGTOINT::~MAPSTRINGTOINT(
)
/*++
Routine Description:
This routine cleans up memory used by a MAPSTRINGTOINT.
Arguments:
None.
Return value:
None.
--*/
{
PSTRINGTOINTASSOCIATION Deleting;
// clean up associations
while (Associations != NULL) {
// save pointer to first association
Deleting = Associations;
// remove first association from list
Associations = Deleting->Next;
// free removed association
free (Deleting->Key);
delete Deleting;
}
}
LONG &
MAPSTRINGTOINT::operator [] (
IN LPTSTR Key
)
/*++
Routine Description:
This routine retrieves an l-value for the value associated with a given key.
Arguments:
Key - The key for which the value is to be retrieved.
Return value:
A reference to the value associated with the provided key.
--*/
{
PSTRINGTOINTASSOCIATION CurrentAssociation = Associations;
// search for key
while(CurrentAssociation != NULL) {
if(!_tcscmp(CurrentAssociation->Key, Key)) {
// found key, return value
return CurrentAssociation->Value;
}
CurrentAssociation = CurrentAssociation->Next;
}
// not found, create new association
CurrentAssociation = new STRINGTOINTASSOCIATION;
if (CurrentAssociation == NULL) {
_tprintf(_T("Memory allocation failure\n"));
exit (0);
}
if (Key == NULL) {
_tprintf(_T("Null object name\n"));
exit (0);
}
else if (_tcscmp (Key, "") == 0) {
_tprintf(_T("Invalid object name `%s'\n"), Key);
exit (0);
}
CurrentAssociation->Key = _tcsdup(Key);
if (CurrentAssociation->Key == NULL) {
_tprintf(_T("Memory string allocation failure\n"));
exit (0);
}
// add association to front of list
CurrentAssociation->Next = Associations;
Associations = CurrentAssociation;
// return value for new association
return CurrentAssociation->Value;
}
BOOLEAN
MAPSTRINGTOINT::Lookup(
IN LPTSTR Key,
OUT LONG & Value
)
/*++
Routine Description:
This routine retrieves an r-value for the value association with a given key.
Arguments:
Key - The key for which the associated value is to be retrieved.
Value - Returns the value associated with Key if Key is present in the map.
Return value:
TRUE if the key is present in the map, FALSE otherwise.
--*/
{
PSTRINGTOINTASSOCIATION CurrentAssociation = Associations;
// search for key
while (CurrentAssociation != NULL) {
if(!_tcscmp(CurrentAssociation->Key , Key)) {
// found key, return it
Value = CurrentAssociation->Value;
return TRUE;
}
CurrentAssociation = CurrentAssociation->Next;
}
// didn't find it
return FALSE;
}
BOOLEAN
OhCmpPopulateMapsFromFile(
IN LPTSTR FileName,
OUT MAPSTRINGTOINT & TypeMap,
OUT MAPSTRINGTOINT & NameMap,
BOOLEAN FileWithTraces
)
/*++
Routine Description:
This routine parses an OH output file and fills two maps with the number of handles of
each type and the number of handles to each named object.
Arguments:
FileName - OH output file to parse.
TypeMap - Map to fill with handle type information.
NameMap - Map to fill with named object information.
Return value:
TRUE if the file was successfully parsed, FALSE otherwise.
--*/
{
LONG HowMany;
LPTSTR Name, Type, Process, Pid, Value;
LPTSTR NewLine;
TCHAR LineBuffer[512];
TCHAR ObjectName[512];
TCHAR ObjectTypeName[512];
FILE *InputFile;
ULONG LineNumber;
BOOLEAN rc;
LineNumber = 0;
// open file
InputFile = _tfopen(FileName, _T("rt"));
if (InputFile == NULL) {
_ftprintf(stderr, _T("Error opening oh file %s.\n"), FileName);
return FALSE;
}
rc = TRUE;
// loop through lines in oh output
ObjectTypeName[sizeof(ObjectTypeName)/sizeof(TCHAR) - 1] = 0;
while (_fgetts(LineBuffer, sizeof(LineBuffer) / sizeof(TCHAR), InputFile)
&& !( feof(InputFile) || ferror(InputFile) ) ) {
LineNumber += 1;
// trim off newline
if((NewLine = _tcschr(LineBuffer, _T('\n'))) != NULL) {
*NewLine = _T('\0');
}
// ignore lines that start with white space or are empty.
if (LineBuffer[0] == _T('\0') ||
LineBuffer[0] == _T('\t') ||
LineBuffer[0] == _T(' ')) {
continue;
}
// ignore lines that start with a comment
if( LineBuffer[0] == _T('/') && LineBuffer[1] == _T('/') ) {
continue;
}
// skip pid
if((Pid = _tcstok(LineBuffer, _T(" \t"))) == NULL) {
rc = FALSE;
break;
}
// skip process name
if((Process = _tcstok(NULL, _T(" \t"))) == NULL) {
rc = FALSE;
break;
}
// Type points to the type of handle
if ((Type = _tcstok(NULL, _T(" \t"))) == NULL) {
rc = FALSE;
break;
}
// Value points to handle value
if ((Value = _tcstok(NULL, _T(" \t"))) == NULL) {
rc = FALSE;
break;
}
// HowMany = number of previous handles with this type
_sntprintf (ObjectTypeName,
sizeof(ObjectTypeName) / sizeof(TCHAR) - 1,
TEXT("<%s/%s/%s/%s>"),
Process,
Pid,
Type,
Value);
if (TypeMap.Lookup(ObjectTypeName, HowMany) == FALSE) {
HowMany = 0;
}
// add another handle of this type
TypeMap[ObjectTypeName] = (HowMany + 1);
//
// Name points to the name. These are magic numbers based on the way
// OH formats output. The output is a little bit different if the
// `-h' option of OH was used (this dumps stack traces too).
//
Name = LineBuffer + 39 + 5;
if (FileWithTraces) {
Name += 7;
}
ObjectName[sizeof(ObjectName)/sizeof(TCHAR) - 1] = 0;
if (_tcscmp (Name, "") == 0) {
_sntprintf (ObjectName,
sizeof(ObjectName) / sizeof(TCHAR) - 1,
TEXT("<%s/%s/%s/%s>::<<noname>>"),
Process,
Pid,
Type,
Value);
}
else {
_sntprintf (ObjectName,
sizeof(ObjectName) / sizeof(TCHAR) - 1,
TEXT("<%s/%s/%s/%s>::%s"),
Process,
Pid,
Type,
Value,
Name);
}
// HowMany = number of previous handles with this name
// printf("name --> `%s' \n", ObjectName);
if (NameMap.Lookup(ObjectName, HowMany) == FALSE) {
HowMany = 0;
}
// add another handle with this name and read the next line
// note -- NameMap[] is a class operator, not an array.
NameMap[ObjectName] = (HowMany + 1);
}
// done, close file
fclose(InputFile);
return rc;
}
int
__cdecl
OhCmpKeyCompareAssociation (
const void * Left,
const void * Right
)
{
PSTRINGTOINTASSOCIATION X;
PSTRINGTOINTASSOCIATION Y;
X = (PSTRINGTOINTASSOCIATION)Left;
Y = (PSTRINGTOINTASSOCIATION)Right;
return _tcscmp (X->Key, Y->Key);
}
int
__cdecl
OhCmpValueCompareAssociation (
const void * Left,
const void * Right
)
{
PSTRINGTOINTASSOCIATION X;
PSTRINGTOINTASSOCIATION Y;
X = (PSTRINGTOINTASSOCIATION)Left;
Y = (PSTRINGTOINTASSOCIATION)Right;
return Y->Value - X->Value;
}
VOID
OhCmpPrintIncreases(
IN MAPSTRINGTOINT & BeforeMap,
IN MAPSTRINGTOINT & AfterMap,
IN BOOLEAN ReportIncreasesOnly,
IN BOOLEAN PrintHighlights,
IN LPTSTR AfterLogName
)
/*++
Routine Description:
This routine compares two maps and prints out the differences between them.
Arguments:
BeforeMap - First map to compare.
AfterMap - Second map to compare.
ReportIncreasesOnly - TRUE for report only increases from BeforeMap to AfterMap,
FALSE for report all differences.
Return value:
None.
--*/
{
PSTRINGTOINTASSOCIATION Association = NULL;
LONG HowManyBefore = 0;
LONG HowManyAfter = 0;
LPTSTR Key = NULL;
PSTRINGTOINTASSOCIATION SortBuffer;
ULONG SortBufferSize;
ULONG SortBufferIndex;
//
// Loop through associations in map and figure out how many output lines
// we will have.
//
SortBufferSize = 0;
for (Association = AfterMap.GetStartPosition(),
AfterMap.GetNextAssociation(Association, Key, HowManyAfter);
Association != NULL;
AfterMap.GetNextAssociation(Association, Key, HowManyAfter)) {
// look up value for this key in BeforeMap
if(BeforeMap.Lookup(Key, HowManyBefore) == FALSE) {
HowManyBefore = 0;
}
// should we report this?
if((HowManyAfter > HowManyBefore) ||
((!ReportIncreasesOnly) && (HowManyAfter != HowManyBefore))) {
SortBufferSize += 1;
}
}
//
// Loop through associations in map again this time filling the output buffer.
//
SortBufferIndex = 0;
SortBuffer = new STRINGTOINTASSOCIATION[SortBufferSize];
if (SortBuffer == NULL) {
_ftprintf(stderr, _T("Failed to allocate internal buffer of %u bytes.\n"),
SortBufferSize);
return;
}
for (Association = AfterMap.GetStartPosition(),
AfterMap.GetNextAssociation(Association, Key, HowManyAfter);
Association != NULL;
AfterMap.GetNextAssociation(Association, Key, HowManyAfter)) {
// look up value for this key in BeforeMap
if(BeforeMap.Lookup(Key, HowManyBefore) == FALSE) {
HowManyBefore = 0;
}
// should we report this?
if((HowManyAfter > HowManyBefore) ||
((!ReportIncreasesOnly) && (HowManyAfter != HowManyBefore))) {
ZeroMemory (&(SortBuffer[SortBufferIndex]),
sizeof (STRINGTOINTASSOCIATION));
SortBuffer[SortBufferIndex].Key = Key;
SortBuffer[SortBufferIndex].Value = HowManyAfter - HowManyBefore;
SortBufferIndex += 1;
}
}
//
// Sort the output buffer using the Key.
//
if (PrintHighlights) {
qsort (SortBuffer,
SortBufferSize,
sizeof (STRINGTOINTASSOCIATION),
OhCmpValueCompareAssociation);
}
else {
qsort (SortBuffer,
SortBufferSize,
sizeof (STRINGTOINTASSOCIATION),
OhCmpKeyCompareAssociation);
}
//
// Dump the buffer.
//
for (SortBufferIndex = 0; SortBufferIndex < SortBufferSize; SortBufferIndex += 1) {
if (PrintHighlights) {
if (SortBuffer[SortBufferIndex].Value >= 1) {
TCHAR TraceId[7];
LPTSTR Start;
_tprintf(_T("%d\t%s\n"),
SortBuffer[SortBufferIndex].Value,
SortBuffer[SortBufferIndex].Key);
Start = _tcsstr (SortBuffer[SortBufferIndex].Key, "(");
if (Start == NULL) {
TraceId[0] = 0;
}
else {
_tcsncpy (TraceId,
Start,
6);
TraceId[6] = 0;
}
_tprintf (_T("%s"), OhCmpSearchStackTrace (AfterLogName, TraceId));
}
}
else {
_tprintf(_T("%d\t%s\n"),
SortBuffer[SortBufferIndex].Value,
SortBuffer[SortBufferIndex].Key);
}
}
//
// Clean up memory.
//
if (SortBuffer) {
delete[] SortBuffer;
}
}
VOID
OhCmpCompareLogs (
IN LONG argc,
IN LPTSTR argv[]
)
/*++
Routine Description:
This routine parses program arguments, reads the two input files, and prints out the
differences.
Arguments:
argc - Number of command-line arguments.
argv - Command-line arguments.
Return value:
None.
--*/
{
MAPSTRINGTOINT TypeMapBefore, TypeMapAfter;
MAPSTRINGTOINT NameMapBefore, NameMapAfter;
LPTSTR BeforeFileName=NULL;
LPTSTR AfterFileName=NULL;
BOOLEAN ReportIncreasesOnly = TRUE;
BOOLEAN Interpreted = FALSE;
BOOLEAN Result;
BOOLEAN FileWithTraces;
BOOLEAN PrintHighlights;
// parse arguments
FileWithTraces = FALSE;
PrintHighlights = FALSE;
for (LONG n = 1; n < argc; n++) {
Interpreted = FALSE;
switch(argv[n][0]) {
case _T('-'):
case _T('/'):
// the argument is a switch
if(_tcsicmp(argv[n]+1, _T("all")) == 0) {
ReportIncreasesOnly = FALSE;
Interpreted = TRUE;
}
else if (_tcsicmp(argv[n]+1, _T("t")) == 0) {
FileWithTraces = TRUE;
Interpreted = TRUE;
}
else if (_tcsicmp(argv[n]+1, _T("l")) == 0) {
PrintHighlights = TRUE;
Interpreted = TRUE;
}
break;
default:
// the argument is a file name
if(BeforeFileName == NULL) {
BeforeFileName = argv[n];
Interpreted = TRUE;
} else {
if(AfterFileName == NULL) {
AfterFileName = argv[n];
Interpreted = TRUE;
} else {
Help();
}
}
break;
}
if(!Interpreted) {
Help();
}
}
// did user specify required arguments?
if((BeforeFileName == NULL) || (AfterFileName == NULL)) {
Help();
}
// read oh1 file
Result = OhCmpPopulateMapsFromFile (BeforeFileName,
TypeMapBefore,
NameMapBefore,
FileWithTraces);
if(Result == FALSE) {
_ftprintf(stderr, _T("Failed to read first OH output file.\n"));
return;
}
// read oh2 file
Result = OhCmpPopulateMapsFromFile (AfterFileName,
TypeMapAfter,
NameMapAfter,
FileWithTraces);
if(Result == FALSE) {
_ftprintf(stderr, _T("Failed to read second OH output file.\n"));
return;
}
// print out increases by handle name
if (PrintHighlights) {
_putts (TEXT(" \n")
TEXT("// \n")
TEXT("// Possible leaks (DELTA <PROCESS/PID/TYPE/VALUE>::NAME): \n")
TEXT("// \n")
TEXT("// Note that the NAME can appear as `(TRACEID) NAME' if output \n")
TEXT("// is generated by comparing OH files containing traces. In this case \n")
TEXT("// just search in the `AFTER' OH log file for the trace id to \n")
TEXT("// find the stack trace creating the handle possibly leaked. \n")
TEXT("// \n")
TEXT("// \n"));
OhCmpPrintIncreases (NameMapBefore,
NameMapAfter,
ReportIncreasesOnly,
TRUE,
AfterFileName);
}
// print out increases by handle type
_putts (TEXT (" \n")
TEXT("// \n")
TEXT("// Handle types (DELTA <PROCESS/PID/TYPE/VALUE>): \n")
TEXT("// \n")
TEXT("// DELTA is the additional number of handles found in the `AFTER' log. \n")
TEXT("// PROCESS is the process name having a handle increase. \n")
TEXT("// PID is the process PID having a handle increase. \n")
TEXT("// TYPE is the type of the handle \n")
TEXT("// VALUE is the handle value \n")
TEXT("// \n")
TEXT("// \n"));
OhCmpPrintIncreases (TypeMapBefore,
TypeMapAfter,
ReportIncreasesOnly,
FALSE,
NULL);
// print out increases by handle name
_putts (TEXT(" \n")
TEXT("// \n")
TEXT("// Objects (named and anonymous) (DELTA <PROCESS/PID/TYPE/VALUE>::NAME): \n")
TEXT("// \n")
TEXT("// DELTA is the additional number of handles found in the `AFTER' log. \n")
TEXT("// PROCESS is the process name having a handle increase. \n")
TEXT("// PID is the process PID having a handle increase. \n")
TEXT("// TYPE is the type of the handle \n")
TEXT("// NAME is the name of the handle. Anonymous handles appear with name <<noname>>.\n")
TEXT("// VALUE is the handle value \n")
TEXT("// \n")
TEXT("// Note that the NAME can appear as `(TRACEID) NAME' if output \n")
TEXT("// is generated by comparing OH files containing traces. In this case \n")
TEXT("// just search in the `AFTER' OH log file for the trace id to \n")
TEXT("// find the stack trace creating the handle possibly leaked. \n")
TEXT("// \n")
TEXT("// \n"));
OhCmpPrintIncreases (NameMapBefore,
NameMapAfter,
ReportIncreasesOnly,
FALSE,
NULL);
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
TCHAR OhCmpStackTraceBuffer [0x10000];
LPTSTR
OhCmpSearchStackTrace (
LPTSTR FileName,
LPTSTR TraceId
)
{
TCHAR LineBuffer[512];
FILE *InputFile;
ULONG spaceLeft;
OhCmpStackTraceBuffer[0] = 0;
//
// Open file.
//
InputFile = _tfopen(FileName, _T("rt"));
if (InputFile == NULL) {
_ftprintf(stderr, _T("Error opening oh file %s.\n"), FileName);
return NULL;
}
//
// Loop through lines in oh output.
//
spaceLeft = sizeof(OhCmpStackTraceBuffer) / sizeof(OhCmpStackTraceBuffer[0]) - 1;
OhCmpStackTraceBuffer[spaceLeft] = 0;
while (_fgetts(LineBuffer, sizeof(LineBuffer) / sizeof(TCHAR), InputFile)
&& !( feof(InputFile) || ferror(InputFile) ) ) {
//
// Skip line if it does not contain trace ID.
//
if (_tcsstr (LineBuffer, TraceId) == NULL) {
continue;
}
//
// We have got a trace ID. We need now to copy everything
// to a trace buffer until we get a line containing a character
// in column zero.
//
while (_fgetts(LineBuffer, sizeof(LineBuffer) / sizeof(TCHAR), InputFile)
&& !( feof(InputFile) || ferror(InputFile) ) ) {
if (LineBuffer[0] == _T(' ') ||
LineBuffer[0] == _T('\0') ||
LineBuffer[0] == _T('\n') ||
LineBuffer[0] == _T('\t')) {
//
// Keep track of how much space we have left...
//
if (spaceLeft < _tcslen(LineBuffer)) {
break;
} else {
spaceLeft -= _tcslen(LineBuffer);
_tcscat (OhCmpStackTraceBuffer, LineBuffer);
}
}
else {
break;
}
}
break;
}
//
// Close file.
fclose(InputFile);
return OhCmpStackTraceBuffer;
}