windows-nt/Source/XPSP1/NT/base/tools/resmon/ohcmp.cpp

1014 lines
24 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1998 Microsoft Corporation
Module Name:
ohcmp.cpp
Abstract:
This module reports the differences between two oh output files.
Author:
Matt Bandy (t-mattba) 23-Jul-1998
Revision History:
24-Jul-1998 t-mattba
Modified module to conform to coding standards.
11-Jun-2001 silviuc
Deal with handles that are recreated with a different value
and other simple output improvements (sorted output etc.).
--*/
#include <windows.h>
#include <common.ver>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <tchar.h>
#include "MAPSTRINGINT.h"
LPTSTR HelpText =
TEXT("ohcmp - Display difference between two OH output files --") BUILD_MACHINE_TAG TEXT("\n")
VER_LEGALCOPYRIGHT_STR TEXT("\n")
TEXT(" \n")
TEXT("ohcmp [OPTION ...] BEFORE_OH_FILE AFTER_OH_FILE \n")
TEXT(" \n")
TEXT("/h 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 ohcmp will print Names having 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");
LPTSTR
SearchStackTrace (
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
PopulateMapsFromFile(
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;
LPTSTR NewLine;
TCHAR LineBuffer[512];
TCHAR ObjectName[512];
TCHAR TypeName[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
while (_fgetts(LineBuffer, sizeof(LineBuffer), 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;
}
// HowMany = number of previous handles with this type
_stprintf (TypeName,
TEXT("<%s/%s/%s>"),
Process,
Pid,
Type);
if (TypeMap.Lookup(TypeName, HowMany) == FALSE) {
HowMany = 0;
}
// add another handle of this type
TypeMap[TypeName] = (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;
}
if (_tcscmp (Name, "") == 0) {
_stprintf (ObjectName,
TEXT("<%s/%s/%s>::<<noname>>"),
Process,
Pid,
Type);
}
else {
_stprintf (ObjectName,
TEXT("<%s/%s/%s>::%s"),
Process,
Pid,
Type,
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
KeyCompareAssociation (
const void * Left,
const void * Right
)
{
PSTRINGTOINTASSOCIATION X;
PSTRINGTOINTASSOCIATION Y;
X = (PSTRINGTOINTASSOCIATION)Left;
Y = (PSTRINGTOINTASSOCIATION)Right;
return _tcscmp (X->Key, Y->Key);
}
int
__cdecl
ValueCompareAssociation (
const void * Left,
const void * Right
)
{
PSTRINGTOINTASSOCIATION X;
PSTRINGTOINTASSOCIATION Y;
X = (PSTRINGTOINTASSOCIATION)Left;
Y = (PSTRINGTOINTASSOCIATION)Right;
return Y->Value - X->Value;
}
VOID
PrintIncreases(
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),
ValueCompareAssociation);
}
else {
qsort (SortBuffer,
SortBufferSize,
sizeof (STRINGTOINTASSOCIATION),
KeyCompareAssociation);
}
//
// 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"), SearchStackTrace (AfterLogName, TraceId));
}
}
else {
_tprintf(_T("%d\t%s\n"),
SortBuffer[SortBufferIndex].Value,
SortBuffer[SortBufferIndex].Key);
}
}
//
// Clean up memory.
//
if (SortBuffer) {
delete[] SortBuffer;
}
}
VOID
PrintUsage(
VOID
)
/*++
Routine Description:
This routine prints out a message describing the proper usage of OHCMP.
Arguments:
None.
Return value:
None.
--*/
{
_ftprintf (stderr, HelpText);
}
LONG _cdecl
_tmain(
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:
0 if comparison is successful, 1 otherwise.
--*/
{
try {
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("h")) == 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 {
// too many file arguments
PrintUsage();
return 1;
}
}
break;
}
if(!Interpreted) {
// user specified a bad argument
PrintUsage();
return 1;
}
}
// did user specify required arguments?
if((BeforeFileName == NULL) || (AfterFileName == NULL))
{
PrintUsage();
return 1;
}
// read oh1 file
Result = PopulateMapsFromFile (BeforeFileName,
TypeMapBefore,
NameMapBefore,
FileWithTraces);
if(Result == FALSE) {
_ftprintf(stderr, _T("Failed to read first OH output file.\n"));
return 1;
}
// read oh2 file
Result = PopulateMapsFromFile (AfterFileName,
TypeMapAfter,
NameMapAfter,
FileWithTraces);
if(Result == FALSE) {
_ftprintf(stderr, _T("Failed to read second OH output file.\n"));
return 1;
}
// print out increases by handle name
if (PrintHighlights) {
_putts (TEXT ("\n")
TEXT("// \n")
TEXT("// Possible leaks (DELTA <PROCESS/PID/TYPE>::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\n"));
PrintIncreases (NameMapBefore,
NameMapAfter,
ReportIncreasesOnly,
TRUE,
AfterFileName);
}
// print out increases by handle type
_putts (TEXT ("\n")
TEXT("// \n")
TEXT("// Handle types (DELTA <PROCESS/PID/TYPE>): \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("// \n\n"));
PrintIncreases (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>::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("// \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\n"));
PrintIncreases (NameMapBefore,
NameMapAfter,
ReportIncreasesOnly,
FALSE,
NULL);
return 0;
} catch (...) {
// this is mostly intended to catch out of memory conditions
_tprintf(_T("\nAn exception has been detected. OHCMP aborted.\n"));
return 1;
}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
TCHAR StackTraceBuffer [0x10000];
LPTSTR
SearchStackTrace (
LPTSTR FileName,
LPTSTR TraceId
)
{
TCHAR LineBuffer[512];
FILE *InputFile;
StackTraceBuffer[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.
//
while (_fgetts(LineBuffer, sizeof(LineBuffer), 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), InputFile)
&& !( feof(InputFile) || ferror(InputFile) ) ) {
if (LineBuffer[0] == _T(' ') ||
LineBuffer[0] == _T('\0') ||
LineBuffer[0] == _T('\n') ||
LineBuffer[0] == _T('\t')) {
_tcscat (StackTraceBuffer, LineBuffer);
}
else {
break;
}
}
break;
}
//
// Close file.
fclose(InputFile);
return StackTraceBuffer;
}