windows-nt/Source/XPSP1/NT/base/tools/umdh/umdh.c

2767 lines
80 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1996-2000 Microsoft Corporation
Module Name:
umdh.c
Abstract:
Quick and not-so-dirty user-mode dh for heap.
Author(s):
Tim Fleehart (TimF) 18-Jun-1999
Silviu Calinoiu (SilviuC) 22-Feb-2000
Revision History:
TimF 18-Jun-99 Initial version
SilviuC 30-Jun-00 TIMF_DBG converted to -v option
SilviuC 06-Feb-00 Massage the code in preparation for speedup fixes
ChrisW 22-Mar-01 Added process suspend code
--*/
//
// Wish List
//
// [-] Option to dump as much as possible without any symbols
// [-] Switch to dbghelp.dll library (get rid of imagehlp.dll)
// [+] Fast symbol lookup
// [+] Faster stack database manipulation
// [-] Faster heap metadata manipulation
// [+] Better memory management for huge processes
// [+] More debug info for PSS issues
// [+] File, line info and umdh version for each reported error (helps PSS).
// [+] Cache for read from target virtual space in case we do it repeatedly.
// [+] Set a symbols path automatically
// [+] Continue to work even if you get errors from imagehlp functions.
//
// [-] Use (if present) dbgexts.dlls library (print file, line info, etc.)
// [-] Integrate dhcmp type of functionality and new features
// [-] No symbols required for page heap groveling (use magic patterns)
// [-] Load/save raw trace database (based on start address)
// [-] Consistency check for a raw trace database
// [-] Log symbol file required for unresolved stacks
// [-] Option to do partial dumps (e.g. only ole32 related).
//
//
// Bugs
//
// [-] Partial copy error when dumping csrss.
// [-] (null) function names in the dump once in a while.
// [-] we can get error reads because the process is not suspended (heaps get destroyed etc.)
// [-] Perf problems have been reported
// [-] Work even if suspend permission not available
//
//
// Versioning
//
// 5.1.001 - standard Whistler version (back compatible with Windows 2000)
// 5.1.002 - umdh works now on IA64
// 5.1.003 - allows target process to be suspended
//
#define UMDH_VERSION "5.1.003 "
#define UMDH_OS_MAJOR_VERSION 5
#define UMDH_OS_MINOR_VERSION 1
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntos.h>
#define NOWINBASEINTERLOCK
#include <windows.h>
#include <lmcons.h>
// #include <imagehlp.h>
#include <dbghelp.h>
#include <heap.h>
#include <heappagi.h>
#include <stktrace.h>
#include "types.h"
#include "symbols.h"
#include "miscellaneous.h"
#include "database.h"
#include "heapwalk.h"
#include "dhcmp.h"
#include "ntpsapi.h"
//
// FlaggedTrace holds the trace index of which we want to show all allocated
// blocks, or one of two flag values, 0, to dump all, or SHOW_NO_ALLOC_BLOCKS
// to dump none.
//
ULONG FlaggedTrace = SHOW_NO_ALLOC_BLOCKS;
BOOL
UmdhEnumerateModules(
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.Target,
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) {
Error (NULL, 0,
"SymLoadModule (%s, %p) failed with error %X (%u)",
ModuleName, BaseOfDll,
GetLastError(), GetLastError());
return FALSE;
}
if (Globals.InfoLevel > 0) {
Comment (" %s (%p) ...", ModuleName, BaseOfDll);
}
return TRUE;
}
/*
* Collect the data required in the STACK_TRACE_DATA entry from the HEAP_ENTRY
* in the target process.
*/
USHORT
UmdhCollectHeapEntryData(
IN OUT HEAP_ENTRY *CurrentBlock,
IN OUT STACK_TRACE_DATA *Std,
IN OUT UCHAR *Flags
)
{
UCHAR UnusedSize;
USHORT BlockSize = 0;
BOOL PageHeapBlock;
PageHeapBlock = FALSE;
/*
* Read Flags for this entry, Size, and UnusedBytes fields to calculate the
* actual size of this allocation.
*/
if (!READVM(&(CurrentBlock -> Flags),
Flags,
sizeof *Flags)) {
/*
* Failed to read Flags field of the current block.
*/
fprintf(stderr,
"READVM(CurrentBlock Flags) failed.\n");
} else if (!READVM(&(CurrentBlock -> Size),
&BlockSize,
sizeof BlockSize)) {
fprintf(stderr,
"READVM(CurrentBlock Size) failed.\n");
/*
* One never knows if an API will trash output parameters on failure.
*/
BlockSize = 0;
} else if (!(*Flags & HEAP_ENTRY_BUSY)) {
/*
* This block is not interesting if *Flags doesn't contain
* HEAP_ENTRY_BUSY; it is free and need not be considered further. It
* is important however to have read the block-size (above), as there
* may be more allocations to consider past this free block.
*/
;
} else if (!READVM(&(CurrentBlock -> UnusedBytes),
&UnusedSize,
sizeof UnusedSize)) {
fprintf(stderr,
"READVM(CurrentBlock UnusedSize) failed.\n");
} else {
// UCHAR
Debug (NULL, 0,
"CurrentBlock -> Flags:0x%p:0x%x\n",
&(CurrentBlock-> Flags),
*Flags);
// USHORT
Debug (NULL, 0,
"CurrentBlock -> Size:0x%p:0x%x\n",
&(CurrentBlock -> Size),
BlockSize);
// UCHAR
Debug (NULL, 0,
"CurrentBlock -> UnusedBytes:0x%p:0x%x\n",
&(CurrentBlock -> UnusedBytes),
UnusedSize);
//
// Try to determine the stack trace index for this allocation.
//
if (Globals.LightPageHeapActive) {
/*
* Read trace index from DPH_BLOCK_INFORMATION, which is at
* (DPH_BLOCK_INFORMATION *)(CurrentBlock + 1) -> TraceIndex.
*/
DPH_BLOCK_INFORMATION *Block, DphBlock;
Block = (DPH_BLOCK_INFORMATION *)(CurrentBlock + 1);
if (!READVM(Block,
&DphBlock,
sizeof DphBlock)) {
fprintf(stderr,
"READVM(DPH_BLOCK_INFORMATION) failed.\n");
} else if (DphBlock.StartStamp ==
DPH_NORMAL_BLOCK_START_STAMP_FREE) {
/*
* Ignore this record. When debug-page-heap is used, heap
* blocks point to allocated blocks and 'freed' blocks. Heap
* code is responsible for these 'freed' blocks not application
* code.
*/
;
} else if (DphBlock.StartStamp == 0) {
/*
* The first block in the heap is created specially by the
* heap code and does not contain debug-page-heap
* information. Ignore it.
*/
;
} else if ((DphBlock.StartStamp !=
DPH_NORMAL_BLOCK_START_STAMP_ALLOCATED)) {
#if 0 //silviuc: this can happen for fixed address heaps (they are never page heap)
fprintf(stderr,
"Unexpected value (0x%lx) of DphBlock -> StartStamp "
"read from Block %p\n",
DphBlock.StartStamp,
Block);
#endif
PageHeapBlock = FALSE;
} else if ((DphBlock.EndStamp !=
DPH_NORMAL_BLOCK_END_STAMP_ALLOCATED)) {
#if 0 //silviuc: this can happen for fixed address heaps (they are never page heap)
fprintf(stderr,
"Unexpected value (0x%lx) of DphBlock -> EndStamp "
"read from Block %p\n",
DphBlock.EndStamp,
Block);
#endif
PageHeapBlock = FALSE;
} else {
Std -> TraceIndex = DphBlock.TraceIndex;
Std -> BlockAddress = DphBlock.Heap;
Std -> BytesAllocated = DphBlock.ActualSize;
/*
* This stack is one allocation.
*/
Std -> AllocationCount = 1;
PageHeapBlock = TRUE;
}
if (PageHeapBlock) {
// ULONG
Debug (NULL, 0,
"DPH Block: StartStamp:0x%p:0x%lx\n",
&(Block -> StartStamp),
DphBlock.StartStamp);
// PVOID
Debug (NULL, 0,
" Heap = 0x%p\n",
DphBlock.Heap);
// SIZE_T
Debug (NULL, 0,
" RequestedSize = 0x%x\n",
DphBlock.RequestedSize);
// SIZE_T
Debug (NULL, 0,
" ActualSize = 0x%x\n",
DphBlock.ActualSize);
// USHORT
Debug (NULL, 0,
" TraceIndex = 0x%x\n",
DphBlock.TraceIndex);
// PVOID
Debug (NULL, 0,
" StackTrace = 0x%p\n",
DphBlock.StackTrace);
// ULONG
Debug (NULL, 0,
" EndStamp = 0x%lx\n",
DphBlock.EndStamp);
}
}
else if (*Flags & HEAP_ENTRY_EXTRA_PRESENT) {
/*
* If HEAP_ENTRY_EXTRA information is present it is at the end of
* the allocated block. Try to read the trace-index of the stack
* which made the allocation.
*/
HEAP_ENTRY_EXTRA *Hea;
/*
* BlockSize includes the bytes used by HEAP_ENTRY_EXTRA. The
* HEAP_ENTRY_EXTRA block is at the end of the heap block. Add
* the BlockSize and subtract a HEAP_EXTRA_ENTRY to get the
* address of the HEAP_ENTRY_EXTRA block.
*/
Hea = (HEAP_ENTRY_EXTRA *)(CurrentBlock + BlockSize) - 1;
if (!READVM(&(Hea -> AllocatorBackTraceIndex),
&(Std -> TraceIndex),
sizeof Std -> TraceIndex)) {
/*
* Just in case READVM puts stuff here on failure.
*/
Std -> TraceIndex = 0;
fprintf(stderr,
"READVM(HeapEntryExtra TraceIndex) failed.\n");
} else {
/*
* Save the address that was returned to the allocator (rather
* than the raw address of the heap block).
*/
Std -> BlockAddress = (CurrentBlock + 1);
/*
* We have enough data to calculate the block size.
*/
Std -> BytesAllocated = (BlockSize << HEAP_GRANULARITY_SHIFT);
#ifndef DH_COMPATIBLE
/*
* DH doesn't subtract off the UnusedSize in order to be usable
* interchangeably with DH we need to leave it on too. This tends
* to inflate the size of an allocation reported by DH or UMDH.
*/
Std -> BytesAllocated -= UnusedSize;
#endif
/*
* This stack is one allocation.
*/
Std -> AllocationCount = 1;
}
if (Globals.Verbose) {
// USHORT
fprintf(stderr,
"Hea -> AllocatorBackTraceIndex:0x%p:0x%x\n",
&(Hea -> AllocatorBackTraceIndex),
Std -> TraceIndex);
}
}
}
return BlockSize;
}
VOID
UmdhCollectVirtualAllocdData(
IN OUT HEAP_VIRTUAL_ALLOC_ENTRY *CurrentBlock,
IN OUT STACK_TRACE_DATA *Std
)
{
if (!READVM(&(CurrentBlock -> CommitSize),
&(Std -> BytesAllocated),
sizeof Std -> BytesAllocated)) {
fprintf(stderr,
"READVM(CurrentBlock CommitSize) failed.\n");
} else if (!READVM(&(CurrentBlock -> ExtraStuff.AllocatorBackTraceIndex),
&(Std -> TraceIndex),
sizeof Std -> TraceIndex)) {
fprintf(stderr,
"READVM(CurrentBlock TraceIndex) failed.\n");
} else {
/*
* From this view, each stack represents one allocation.
*/
Std -> AllocationCount = 1;
}
}
VOID
UmdhGetHEAPDATA(
IN OUT HEAPDATA *HeapData
)
{
HEAP_VIRTUAL_ALLOC_ENTRY *Anchor, *VaEntry;
ULONG Segment;
/*
* List that helps keep track of heap fragmentation
* statistics.
*/
HEAP_ENTRY_LIST List;
Initialize(&List);
if (HeapData -> BaseAddress == NULL) {
/*
* This was in the process heap list but it's not active or it's
* signature didn't match HEAP_SIGNATURE; skip it.
*/
return;
}
/*
* Examine each segment of the heap.
*/
for (Segment = 0; Segment < HEAP_MAXIMUM_SEGMENTS; Segment++) {
/*
* Read address of segment, and then first and last blocks within
* the segment.
*/
HEAP_ENTRY *CurrentBlock, *LastValidEntry;
HEAP_SEGMENT *HeapSegment;
HEAP_UNCOMMMTTED_RANGE *pUncommittedRanges;
ULONG NumberOfPages, Signature, UncommittedPages;
if (!READVM(&(HeapData -> BaseAddress -> Segments[Segment]),
&HeapSegment,
sizeof HeapSegment)) {
fprintf(stderr,
"READVM(Segments[%d]) failed.\n",
Segment);
} else if (!HeapSegment) {
/*
* This segment looks empty.
*
* DH agrees here.
*/
continue;
} else if (!READVM(&(HeapSegment -> Signature),
&Signature,
sizeof Signature)) {
fprintf(stderr,
"READVM(HeapSegment Signature) failed.\n");
} else if (Signature != HEAP_SEGMENT_SIGNATURE) {
/*
* Signature mismatch.
*/
fprintf(stderr,
"Heap 'segment' at %p has and unexpected signature "
"of 0x%lx\n",
&(HeapSegment -> Signature),
Signature);
} else if (!READVM(&(HeapSegment -> FirstEntry),
&CurrentBlock,
sizeof CurrentBlock)) {
fprintf(stderr,
"READVM(HeapSegment FirstEntry) failed.\n");
} else if (!READVM(&(HeapSegment -> LastValidEntry),
&LastValidEntry,
sizeof LastValidEntry)) {
fprintf(stderr,
"READVM(HeapSegment LastValidEntry) failed.\n");
} else if (!READVM(&(HeapSegment -> NumberOfPages),
&NumberOfPages,
sizeof NumberOfPages)) {
fprintf(stderr,
"READVM(HeapSegment NumberOfPages) failed.\n");
} else if (!READVM(&(HeapSegment -> NumberOfUnCommittedPages),
&UncommittedPages,
sizeof UncommittedPages)) {
fprintf(stderr,
"READVM(HeapSegment NumberOfUnCommittedPages) failed.\n");
} else if (!READVM(&(HeapSegment -> UnCommittedRanges),
&pUncommittedRanges,
sizeof pUncommittedRanges)) {
fprintf(stderr,
"READVM(HeapSegment UncommittedRanges) failed.\n");
} else {
/*
* Examine each block in the Segment.
*/
if (Globals.Verbose) {
// HEAP_SEGMENT *
fprintf(stderr,
"\nHeapData -> BaseAddress -> Segments[%d]:0x%p:0x%p\n",
Segment,
&(HeapData -> BaseAddress -> Segments[Segment]),
HeapSegment);
// HEAP_ENTRY *
fprintf(stderr,
"HeapSegment -> FirstEntry:0x%p:0x%p\n",
&(HeapSegment -> FirstEntry),
CurrentBlock);
// HEAP_ENTRY *
fprintf(stderr,
"HeapSegment -> LastValidEntry:0x%p:0x%p\n",
&(HeapSegment -> LastValidEntry),
LastValidEntry);
// ULONG
fprintf(stderr,
"HeapSegment -> NumberOfPages:0x%p:0x%lx\n",
&(HeapSegment -> NumberOfPages),
NumberOfPages);
// ULONG
fprintf(stderr,
"HeapSegment -> NumberOfUncommittedPages:0x%p:0x%lx\n",
&(HeapSegment -> NumberOfUnCommittedPages),
UncommittedPages);
}
/*
* Each heap segment is one VA chunk.
*/
HeapData -> VirtualAddressChunks += 1;
HeapData -> BytesCommitted += (NumberOfPages - UncommittedPages) *
PAGE_SIZE;
/*
* LastValidEntry indicate the end of the reserved region; make it
* the end of the committed region. We should also be able to
* calculate this value as (BaseAddress + ((NumberOfPages -
* NumberOfUnCommittedPages) * PAGE_SIZE)).
*/
while (CurrentBlock < LastValidEntry) {
UCHAR Flags;
USHORT BlockSize;
if (Globals.Verbose) {
// HEAP_ENTRY *
fprintf(stderr,
"\nNew LastValidEntry = %p\n",
LastValidEntry);
}
/*
* inserting all the blocks for this heap into HeapEntryList.
*/
{
UCHAR State;
USHORT Size;
if (!READVM(&(CurrentBlock -> Flags),
&State,
sizeof State)) {
fprintf(stderr,
"READVM (CurrentBlock Flags) failed.\n");
}
else if (!READVM(&(CurrentBlock -> Size),
&Size,
sizeof Size)) {
fprintf(stderr,
"READVM (CurrentBlock Size) failed.\n");
}
else {
HEAP_ENTRY_INFO HeapEntryInfo;
HeapEntryInfo.BlockState = HEAP_BLOCK_FREE;
if ((State & 0x1) == HEAP_ENTRY_BUSY) {
HeapEntryInfo.BlockState = HEAP_BLOCK_BUSY;
}
HeapEntryInfo.BlockSize = Size;
HeapEntryInfo.BlockCount = 1;
InsertHeapEntry(&List, &HeapEntryInfo);
}
}
/*
* If the stack sort data buffer is full, try to make it
* larger.
*/
if (HeapData -> TraceDataEntryMax == 0) {
HeapData -> StackTraceData = XALLOC(SORT_DATA_BUFFER_INCREMENT *
sizeof (STACK_TRACE_DATA));
if (HeapData -> StackTraceData == NULL) {
fprintf(stderr,
"xalloc of %d bytes failed.\n",
SORT_DATA_BUFFER_INCREMENT *
sizeof (STACK_TRACE_DATA));
} else {
HeapData -> TraceDataEntryMax = SORT_DATA_BUFFER_INCREMENT;
}
} else if (HeapData -> TraceDataEntryCount ==
HeapData -> TraceDataEntryMax) {
STACK_TRACE_DATA *tmp;
ULONG OriginalCount;
OriginalCount = HeapData -> TraceDataEntryMax;
HeapData -> TraceDataEntryMax += SORT_DATA_BUFFER_INCREMENT;
tmp = XREALLOC(HeapData -> StackTraceData,
HeapData -> TraceDataEntryMax *
sizeof (STACK_TRACE_DATA));
if (tmp == NULL) {
fprintf(stderr,
"realloc(%d) failed.\n",
HeapData -> TraceDataEntryMax *
sizeof (STACK_TRACE_DATA));
/*
* Undo the increase in size so we don't actually try
* to use it.
*/
HeapData -> TraceDataEntryMax -= SORT_DATA_BUFFER_INCREMENT;
} else {
/*
* Zero newly allocated bytes in the region.
*/
RtlZeroMemory(tmp + OriginalCount,
SORT_DATA_BUFFER_INCREMENT *
sizeof (STACK_TRACE_DATA));
/*
* Use the new pointer.
*/
HeapData -> StackTraceData = tmp;
}
}
/*
* If there is space in the buffer, collect data.
*/
if (HeapData -> TraceDataEntryCount <
HeapData -> TraceDataEntryMax) {
BlockSize = UmdhCollectHeapEntryData(CurrentBlock,
&(HeapData -> StackTraceData[
HeapData -> TraceDataEntryCount]),
&Flags);
if (BlockSize == 0) {
/*
* Something went wrong.
*/
fprintf(stderr,
"UmdhGetHEAPDATA got BlockSize == 0\n");
fprintf(stderr,
"HeapSegment = 0x%p, LastValidEntry = 0x%p\n",
HeapSegment,
LastValidEntry);
break;
} else {
/*
* Keep track of data in sort data buffer.
*/
HeapData -> TraceDataEntryCount += 1;
}
} else {
fprintf(stderr,
"UmdhGetHEAPDATA ran out of TraceDataEntries\n");
}
if (Flags & HEAP_ENTRY_LAST_ENTRY) {
/*
* BlockSize is the number of units of size (sizeof
* (HEAP_ENTRY)) to move forward to find the next block.
* This makes the pointer arithmetic appropriate below.
*/
CurrentBlock += BlockSize;
if (pUncommittedRanges == NULL) {
CurrentBlock = LastValidEntry;
} else {
HEAP_UNCOMMMTTED_RANGE UncommittedRange;
if (!READVM(pUncommittedRanges,
&UncommittedRange,
sizeof UncommittedRange)) {
fprintf(stderr,
"READVM(pUncommittedRanges) failed.\n");
/*
* On failure the only reasonable thing we can do
* is stop looking at this segment.
*/
CurrentBlock = LastValidEntry;
} else {
if (Globals.Verbose) {
// HEAP_UNCOMMITTED_RANGE
fprintf(stderr,
"pUncomittedRanges:0x%p:0x%x\n",
pUncommittedRanges,
UncommittedRange);
}
CurrentBlock = (PHEAP_ENTRY)((PCHAR)UncommittedRange.Address +
UncommittedRange.Size);
pUncommittedRanges = UncommittedRange.Next;
}
}
} else {
/*
* BlockSize is the number of units of size (sizeof
* (HEAP_ENTRY)) to move forward to find the next block.
* This makes the pointer arithmetic appropriate below.
*/
CurrentBlock += BlockSize;
}
}
}
}
/*
* Display heap fragmentation statistics.
*/
DisplayHeapFragStatistics(Globals.OutFile, HeapData->BaseAddress, &List);
DestroyList(&List);
/*
* Examine entries for the blocks created by NtAllocateVirtualMemory. For
* these, it looks like when they are in the list they are live.
*/
if (!READVM(&(HeapData -> BaseAddress -> VirtualAllocdBlocks.Flink),
&Anchor,
sizeof Anchor)) {
fprintf(stderr,
"READVM(reading heap VA anchor) failed.\n");
} else if (!READVM(&(Anchor -> Entry.Flink),
&VaEntry,
sizeof VaEntry)) {
fprintf(stderr,
"READVM(Anchor Flink) failed.\n");
} else {
if (Globals.Verbose) {
fprintf(stderr,
"\nHeapData -> BaseAddress -> VirtualAllocdBlocks.Flink:%p:%p\n",
&(HeapData -> BaseAddress -> VirtualAllocdBlocks.Flink),
Anchor);
fprintf(stderr,
"Anchor -> Entry.Flink:%p:%p\n",
&(Anchor -> Entry.Flink),
VaEntry);
}
/*
* If the list is empty
* &(HeapData -> BaseAddress -> VirtualAllocdBlocks.Flink) will be equal to
* HeapData -> BaseAddress -> VirtualAllocdBlocks.Flink and Anchor
* will be equal to VaEntry). Advancing VaEntry each time through will
* cause it to be equal to Anchor when we have examined the entire list.
*/
while (Anchor != VaEntry) {
/*
* If the stack sort data buffer is full, try to make it larger.
*/
if (HeapData -> TraceDataEntryMax == 0) {
HeapData -> StackTraceData = XALLOC(SORT_DATA_BUFFER_INCREMENT *
sizeof (STACK_TRACE_DATA));
if (HeapData -> StackTraceData == NULL) {
fprintf(stderr,
"xalloc of %d bytes failed.\n",
SORT_DATA_BUFFER_INCREMENT *
sizeof (STACK_TRACE_DATA));
} else {
HeapData -> TraceDataEntryMax = SORT_DATA_BUFFER_INCREMENT;
}
} else if (HeapData -> TraceDataEntryCount ==
HeapData -> TraceDataEntryMax) {
STACK_TRACE_DATA *tmp;
ULONG OriginalCount;
OriginalCount = HeapData -> TraceDataEntryMax;
HeapData -> TraceDataEntryMax += SORT_DATA_BUFFER_INCREMENT;
tmp = XREALLOC(HeapData -> StackTraceData,
HeapData -> TraceDataEntryMax * sizeof (STACK_TRACE_DATA));
if (tmp == NULL) {
fprintf(stderr,
"realloc(%d) failed.\n",
HeapData -> TraceDataEntryMax *
sizeof (STACK_TRACE_DATA));
/*
* Undo the increase in size so we don't actually try to
* use it.
*/
HeapData -> TraceDataEntryMax -= SORT_DATA_BUFFER_INCREMENT;
} else {
/*
* Zero newly allocated bytes in the region.
*/
RtlZeroMemory(tmp + OriginalCount,
SORT_DATA_BUFFER_INCREMENT *
sizeof (STACK_TRACE_DATA));
/*
* Use the new pointer.
*/
HeapData -> StackTraceData = tmp;
}
}
/*
* If there is space in the buffer, collect data.
*/
if (HeapData -> TraceDataEntryCount < HeapData -> TraceDataEntryMax) {
UmdhCollectVirtualAllocdData(VaEntry,
&(HeapData -> StackTraceData[HeapData ->
TraceDataEntryCount]));
HeapData -> TraceDataEntryCount += 1;
}
/*
* Count the VA chunk.
*/
HeapData -> VirtualAddressChunks += 1;
/*
* Advance the next element in the list.
*/
if (!READVM(&(VaEntry -> Entry.Flink),
&VaEntry,
sizeof VaEntry)) {
fprintf(stderr,
"READVM(VaEntry Flink) failed.\n");
/*
* If this read failed, we may be unable to terminate this loop
* properly; do it explicitly.
*/
break;
}
if (Globals.Verbose) {
fprintf(stderr,
"VaEntry -> Entry.Flink:%p:%p\n",
&(VaEntry -> Entry.Flink),
VaEntry);
}
}
}
}
#define HEAP_TYPE_UNKNOWN 0
#define HEAP_TYPE_NT_HEAP 1
#define HEAP_TYPE_PAGE_HEAP 2
BOOL
UmdhDetectHeapType (
PVOID HeapAddress,
PDWORD HeapType
)
{
BOOL Result;
HEAP HeapData;
*HeapType = HEAP_TYPE_UNKNOWN;
Result = READVM (HeapAddress,
&HeapData,
sizeof HeapData);
if (Result == FALSE) {
return FALSE;
}
if (HeapData.Signature == 0xEEFFEEFF) {
*HeapType = HEAP_TYPE_NT_HEAP;
return TRUE;
}
else if (HeapData.Signature == 0xEEEEEEEE) {
*HeapType = HEAP_TYPE_PAGE_HEAP;
return TRUE;
}
else {
*HeapType = HEAP_TYPE_UNKNOWN;
return TRUE;
}
}
BOOLEAN
UmdhGetHeapsInformation (
IN OUT PHEAPINFO HeapInfo
)
/*++
Routine Description:
UmdhGetHeaps
Note that when the function is called it assumes the trace database
was completely read from the target process.
Arguments:
Return Value:
True if operation succeeded.
--*/
{
NTSTATUS Status;
PROCESS_BASIC_INFORMATION Pbi;
PVOID Addr;
BOOL Result;
PHEAP * ProcessHeaps;
ULONG j;
ULONG PageHeapFlags;
//
// Get some information about the target process.
//
Status = NtQueryInformationProcess(Globals.Target,
ProcessBasicInformation,
&Pbi,
sizeof Pbi,
NULL);
if (! NT_SUCCESS(Status)) {
Error (__FILE__, __LINE__,
"NtQueryInformationProcess failed with status %X\n",
Status);
return FALSE;
}
//
// Dump the stack trace database pointer.
//
Comment ("Stack trace data base @ %p", ((PSTACK_TRACE_DATABASE)(Globals.Database))->CommitBase);
Comment ("# traces in the data base %u", ((PSTACK_TRACE_DATABASE)(Globals.Database))->NumberOfEntriesAdded);
//
// Find out if this process is using debug-page-heap functionality.
//
Addr = SymbolAddress (DEBUG_PAGE_HEAP_NAME);
Result = READVM(Addr,
&(Globals.PageHeapActive),
sizeof (Globals.PageHeapActive));
if (Result == FALSE) {
Error (NULL, 0,
"READVM(&RtlpDebugPageHeap) failed.\n"
"\nntdll.dll symbols are probably incorrect.\n");
}
if (Globals.PageHeapActive) {
Addr = SymbolAddress (DEBUG_PAGE_HEAP_FLAGS_NAME);
Result = READVM(Addr,
&PageHeapFlags,
sizeof PageHeapFlags);
if (Result == FALSE) {
Error (NULL, 0,
"READVM(&RtlpDphGlobalFlags) failed.\n"
"\nntdll.dll symbols are probably incorrect.\n");
}
if ((PageHeapFlags & PAGE_HEAP_ENABLE_PAGE_HEAP) == 0) {
Globals.LightPageHeapActive = TRUE;
}
}
//
// ISSUE: SilviuC: we do not work yet if full page heap is enabled.
//
if (Globals.PageHeapActive && !Globals.LightPageHeapActive) {
Comment ("UMDH cannot be used if full page heap or application "
"verifier with full page heap is enabled for the process.");
Error (NULL, 0,
"UMDH cannot be used if full page heap or application "
"verifier with full page heap is enabled for the process.");
return FALSE;
}
//
// Get the number of heaps from the PEB.
//
Result = READVM (&(Pbi.PebBaseAddress->NumberOfHeaps),
&(HeapInfo->NumberOfHeaps),
sizeof (HeapInfo->NumberOfHeaps));
if (Result == FALSE) {
Error (NULL, 0, "READVM(Peb.NumberOfHeaps) failed.\n");
return FALSE;
}
Debug (NULL, 0,
"Pbi.PebBaseAddress -> NumberOfHeaps:0x%p:0x%lx\n",
&(Pbi.PebBaseAddress -> NumberOfHeaps),
HeapInfo -> NumberOfHeaps);
HeapInfo->Heaps = XALLOC(HeapInfo->NumberOfHeaps * sizeof (HEAPDATA));
if (HeapInfo->Heaps == NULL) {
Error (NULL, 0,
"xalloc of %d bytes failed.\n",
HeapInfo -> NumberOfHeaps * sizeof (HEAPDATA));
return FALSE;
}
Result = READVM(&(Pbi.PebBaseAddress -> ProcessHeaps),
&ProcessHeaps,
sizeof ProcessHeaps);
if (Result == FALSE) {
XFREE (HeapInfo->Heaps);
HeapInfo->Heaps = NULL;
Error (NULL, 0,
"READVM(Peb.ProcessHeaps) failed.\n");
return FALSE;
}
Debug (NULL, 0,
"Pbi.PebBaseAddress -> ProcessHeaps:0x%p:0x%p\n",
&(Pbi.PebBaseAddress -> ProcessHeaps),
ProcessHeaps);
//
// Iterate heaps
//
for (j = 0; j < HeapInfo -> NumberOfHeaps; j += 1) {
PHEAP HeapBase;
PHEAPDATA HeapData;
ULONG Signature;
USHORT ProcessHeapsListIndex;
HeapData = &(HeapInfo -> Heaps[j]);
//
// Read the address of the heap.
//
Result = READVM (&(ProcessHeaps[j]),
&(HeapData -> BaseAddress),
sizeof HeapData -> BaseAddress);
if (Result == FALSE) {
Error (NULL, 0,
"READVM(ProcessHeaps[%d]) failed.\n",
j);
Warning (NULL, 0,
"Skipping heap @ %p because we cannot read it.",
HeapData -> BaseAddress);
//
// Error while reading. Forget the address of this heap.
//
HeapData->BaseAddress = NULL;
continue;
}
Debug (NULL, 0,
"** ProcessHeaps[0x%x]:0x%p:0x%p\n",
j,
&(ProcessHeaps[j]),
HeapData -> BaseAddress);
HeapBase = HeapData->BaseAddress;
//
// What type of heap is this ? It should be an NT heap because page heaps
// are not inserted into the PEB list of heaps.
//
{
DWORD Type;
BOOL DetectResult;
DetectResult = UmdhDetectHeapType (HeapBase, &Type);
if (! (DetectResult && Type == HEAP_TYPE_NT_HEAP)) {
Error (NULL, 0,
"Detected a heap that is not an NT heap @ %p",
HeapBase);
}
}
/*
* Does the heap think that it is within range ? (We
* already think it is.)
*/
if (!READVM(&(HeapBase -> ProcessHeapsListIndex),
&ProcessHeapsListIndex,
sizeof ProcessHeapsListIndex)) {
fprintf(stderr,
"READVM(HeapBase ProcessHeapsListIndex) failed.\n");
/*
* Forget the base address of this heap.
*/
HeapData -> BaseAddress = NULL;
continue;
}
if (Globals.Verbose) {
fprintf(stderr,
"&(HeapBase -> ProcessHeapsListIndex):0x%p:0x%lx\n",
&(HeapBase -> ProcessHeapsListIndex),
ProcessHeapsListIndex);
}
/*
* A comment in
* ntos\rtl\heapdll.c:RtlpRemoveHeapFromProcessList
* states: "Note that the heaps stored index is bias by
* one", thus ">" in the following test.
*/
if (ProcessHeapsListIndex > HeapInfo -> NumberOfHeaps) {
/*
* Invalid index. Forget the base address of this
* heap.
*/
fprintf(stderr,
"Heap at index %d has index of %d, but max "
"is %d\n",
j,
ProcessHeapsListIndex,
HeapInfo -> NumberOfHeaps);
fprintf(stderr,
"&(Pbi.PebBaseAddress -> NumberOfHeaps) = 0x%p\n",
&(Pbi.PebBaseAddress -> NumberOfHeaps));
HeapData -> BaseAddress = NULL;
continue;
}
/*
* Check the signature to see if it is really a heap.
*/
if (!READVM(&(HeapBase -> Signature),
&Signature,
sizeof Signature)) {
fprintf(stderr,
"READVM(HeapBase Signature) failed.\n");
/*
* Forget the base address of this heap.
*/
HeapData -> BaseAddress = NULL;
continue;
}
else if (Signature != HEAP_SIGNATURE) {
fprintf(stderr,
"Heap at index %d does not have a correct "
"signature (0x%lx)\n",
j,
Signature);
/*
* Forget the base address of this heap.
*/
HeapData -> BaseAddress = NULL;
continue;
}
/*
* And read other interesting heap bits.
*/
if (!READVM(&(HeapBase -> Flags),
&(HeapData -> Flags),
sizeof HeapData -> Flags)) {
fprintf(stderr,
"READVM(HeapBase Flags) failed.\n");
/*
* Forget the base address of this heap.
*/
HeapData -> BaseAddress = NULL;
continue;
}
if (Globals.Verbose) {
fprintf(stderr,
"HeapBase -> Flags:0x%p:0x%lx\n",
&(HeapBase -> Flags),
HeapData -> Flags);
}
if (!READVM(&(HeapBase -> AllocatorBackTraceIndex),
&(HeapData -> CreatorBackTraceIndex),
sizeof HeapData -> CreatorBackTraceIndex)) {
fprintf(stderr,
"READVM(HeapBase AllocatorBackTraceIndex) failed.\n");
/*
* Forget the base address of this heap.
*/
HeapData -> BaseAddress = NULL;
continue;
}
if (Globals.Verbose) {
fprintf(stderr,
"HeapBase -> AllocatorBackTraceIndex:0x%p:0x%lx\n",
&(HeapBase -> AllocatorBackTraceIndex),
HeapData -> CreatorBackTraceIndex);
}
if (!READVM(&(HeapBase -> TotalFreeSize),
&(HeapData -> TotalFreeSize),
sizeof HeapData -> TotalFreeSize)) {
fprintf(stderr,
"READVM(HeapBase TotalFreeSize) failed.\n");
/*
* Forget the base address of this heap.
*/
HeapData -> BaseAddress = NULL;
continue;
}
if (Globals.Verbose) {
fprintf(stderr,
"HeapBase -> TotalFreeSize:0x%p:0x%p\n",
&(HeapBase -> TotalFreeSize),
HeapData -> TotalFreeSize);
}
}
/*
* We got as much as we could.
*/
return TRUE;
}
int
__cdecl
UmdhSortSTACK_TRACE_DATAByTraceIndex(
const STACK_TRACE_DATA *h1,
const STACK_TRACE_DATA *h2
)
{
LONG Result;
/*
* Sort such that items with identical TraceIndex are adjacent. (That
* this results in ascending order is irrelevant).
*/
Result = h1 -> TraceIndex - h2 -> TraceIndex;
if (0 == Result) {
/*
* For two items with identical TraceIndex, sort into ascending order
* by BytesAllocated.
*/
if (h1 -> BytesAllocated > h2 -> BytesAllocated) {
Result = 1;
} else if (h1 -> BytesAllocated < h2 -> BytesAllocated) {
Result = -1;
} else {
Result = 0;
}
}
return Result;
}
int
__cdecl
UmdhSortSTACK_TRACE_DATABySize(
const STACK_TRACE_DATA *h1,
const STACK_TRACE_DATA *h2
)
{
LONG Result = 0;
// if (SortByAllocs) {
/*
* Sort into descending order by AllocationCount.
*/
if (h2 -> AllocationCount > h1 -> AllocationCount) {
Result = 1;
} else if (h2 -> AllocationCount < h1 -> AllocationCount) {
Result = -1;
} else {
Result = 0;
}
// }
if (!Result) {
/*
* Sort into descending order by total bytes.
*/
if (((h1 -> BytesAllocated * h1 -> AllocationCount) + h1 -> BytesExtra) >
((h2 -> BytesAllocated * h2 -> AllocationCount) + h2 -> BytesExtra)) {
Result = -1;
} else if (((h1 -> BytesAllocated * h1 -> AllocationCount) + h1 -> BytesExtra) <
((h2 -> BytesAllocated * h2 -> AllocationCount) + h2 -> BytesExtra)) {
Result = +1;
} else {
Result = 0;
}
}
if (!Result) {
/*
* Bytes or AllocationCounts are equal, sort into ascending order by
* stack trace index.
*/
Result = h1 -> TraceIndex - h2 -> TraceIndex;
}
if (!Result) {
/*
* Previous equal; sort by heap address. This should result in heap
* addresses dumpped by -d being in sorted order.
*/
if (h1 -> BlockAddress < h2 -> BlockAddress) {
Result = -1;
} else {
/*
* No other sort, just make it "after".
*/
Result = 1;
}
}
return Result;
}
VOID
UmdhCoalesceSTACK_TRACE_DATA(
IN OUT STACK_TRACE_DATA *Std,
IN ULONG Count
)
{
ULONG i = 0;
/*
* For every entry allocated from the same stack trace, coalesce them into
* a single entry by moving allocation count and any extra bytes into the
* first entry then zeroing the AllocationCount on the other entry.
*/
while ((i + 1) < Count) {
ULONG j;
/*
* Identical entries should be adjacent, so start with the next.
*/
j = i + 1;
while (j < Count) {
if (Std[i].TraceIndex == Std[j].TraceIndex) {
/*
* These two allocations were made from the same stack trace,
* coalesce.
*/
if (Std[j].BytesAllocated > Std[i].BytesAllocated) {
/*
* Add any extra bytes from the second allocation so we
* can determine the total number of bytes from this trace.
*/
Std[i].BytesExtra += Std[j].BytesAllocated -
Std[i].BytesAllocated;
}
/*
* Move the AllocationCount of the second trace into the first.
*/
Std[i].AllocationCount += Std[j].AllocationCount;
Std[j].AllocationCount = 0;
++j;
} else {
/*
* Mismatch; look no further.
*/
break;
}
}
/*
* Advance to the next uncoalesced entry.
*/
i = j;
}
}
VOID
UmdhShowHEAPDATA(
IN PHEAPDATA HeapData
)
{
Info(" Flags: %08lx", HeapData -> Flags);
Info(" Number Of Entries: %d", HeapData -> TraceDataEntryCount);
Info(" Number Of Tags: <unknown>");
Info(" Bytes Allocated: %p", HeapData -> BytesCommitted - (HeapData -> TotalFreeSize << HEAP_GRANULARITY_SHIFT));
Info(" Bytes Committed: %p",HeapData -> BytesCommitted);
Info(" Total FreeSpace: %p", HeapData -> TotalFreeSize << HEAP_GRANULARITY_SHIFT);
Info(" Number of Virtual Address chunks used: %lx", HeapData -> VirtualAddressChunks);
Info(" Address Space Used: <unknown>");
Info(" Entry Overhead: %d", sizeof (HEAP_ENTRY));
Info(" Creator: (Backtrace%05d)", HeapData -> CreatorBackTraceIndex);
UmdhDumpStackByIndex(HeapData->CreatorBackTraceIndex);
}
VOID
UmdhShowStacks(
STACK_TRACE_DATA *Std,
ULONG StackTraceCount,
ULONG Threshold
)
{
ULONG i;
for (i = 0; i < StackTraceCount; i++) {
/*
* The default Threshold is set to 0 in main(), so stacks with
* AllocationCount == 0 as a result of the Coalesce will skipped here.
*/
if (Std[i].AllocationCount > Threshold) {
if ((Std[i].TraceIndex == 0) ||
((ULONG)Std[i].TraceIndex == 0xFEEE)) {
/*
* I'm not sure where either of these come from, I suspect
* that the zero case comes from the last entry in some list.
* The too-large case being 0xFEEE, suggests that I'm looking
* at free pool. In either case we don't have any useful
* information; don't print it.
*/
continue;
}
/*
* This number of allocations from this point exceeds the
* threshold, dump interesting information.
*/
fprintf(Globals.OutFile, "%p bytes ",
(Std[i].AllocationCount * Std[i].BytesAllocated) +
Std[i].BytesExtra);
if (Std[i].AllocationCount > 1) {
if (Std[i].BytesExtra) {
fprintf(Globals.OutFile, "in 0x%lx allocations (@ 0x%p + 0x%p) ",
Std[i].AllocationCount,
Std[i].BytesAllocated,
Std[i].BytesExtra);
} else {
fprintf(Globals.OutFile, "in 0x%lx allocations (@ 0x%p) ",
Std[i].AllocationCount,
Std[i].BytesAllocated);
}
}
fprintf(Globals.OutFile, "by: BackTrace%05d\n",
Std[i].TraceIndex);
UmdhDumpStackByIndex(Std[i].TraceIndex);
/*
* If FlaggedTrace == the trace we are currently looking at, then
* dump the blocks that come from that trace. FlaggedTrace == 0
* indicates 'dump all stacks'.
*/
if ((FlaggedTrace != SHOW_NO_ALLOC_BLOCKS) &&
((FlaggedTrace == Std[i].TraceIndex) ||
(FlaggedTrace == 0))) {
ULONG ColumnCount, l;
fprintf(Globals.OutFile, "Allocations for trace BackTrace%05d:\n",
Std[i].TraceIndex);
ColumnCount = 0;
/*
* Here we rely on the remaining stack having AllocationCount
* == 0, so should be at greater indexes than the current
* stack.
*/
for (l = i; l < StackTraceCount; l++) {
/*
* If the stack at [l] matches the stack at [i], dump it
* here.
*/
if (Std[l].TraceIndex == Std[i].TraceIndex) {
fprintf(Globals.OutFile, "%p ",
Std[l].BlockAddress);
ColumnCount += 10;
if ((ColumnCount + 10) > 80) {
fprintf(Globals.OutFile, "\n");
ColumnCount = 0;
}
}
}
fprintf(Globals.OutFile, "\n\n\n");
}
}
}
}
/////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////// resume/suspend
/////////////////////////////////////////////////////////////////////
//
// Note. We need to dynamically discover the NtSuspend/ResumeProcess
// entry points because these where not present in W2000.
//
VOID
UmdhSuspendProcess(
VOID
)
{
HINSTANCE hLibrary;
NTSTATUS NtStatus;
typedef NTSTATUS (NTAPI* NTSUSPENDPROC)(HANDLE);
NTSUSPENDPROC pSuspend;
hLibrary= LoadLibrary( TEXT("ntdll.dll") );
if( hLibrary ) {
pSuspend= (NTSUSPENDPROC) GetProcAddress( hLibrary, "NtSuspendProcess" );
if( pSuspend ) {
NtStatus= (*pSuspend)( Globals.Target );
Comment ( "NtSuspendProcess Status= %08x",NtStatus);
if (NT_SUCCESS(NtStatus)) {
Globals.TargetSuspended = TRUE;
}
}
FreeLibrary( hLibrary ); hLibrary= NULL;
}
return;
}
VOID
UmdhResumeProcess(
VOID
)
{
HINSTANCE hLibrary;
NTSTATUS NtStatus;
typedef NTSTATUS (NTAPI* NTRESUMEPROC)(HANDLE);
NTRESUMEPROC pResume;
if (Globals.TargetSuspended == FALSE) {
return;
}
hLibrary= LoadLibrary( TEXT("ntdll.dll") );
if( hLibrary ) {
pResume= (NTRESUMEPROC) GetProcAddress( hLibrary, "NtResumeProcess" );
if( pResume ) {
NtStatus= (*pResume)( Globals.Target );
Comment ( "NtResumeProcess Status= %08x",NtStatus);
if (NT_SUCCESS(NtStatus)) {
Globals.TargetSuspended = FALSE;
}
}
FreeLibrary( hLibrary ); hLibrary= NULL;
}
return;
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
VOID
UmdhGrovel (
IN ULONG Pid,
IN ULONG Threshold
)
/*++
Routine Description:
UmdhGrovel
Arguments:
Pid = PID of target process
Threshold - ???
Return Value:
None.
--*/
{
BOOL Result;
HEAPINFO HeapInfo;
ULONG Heap;
PHEAPDATA HeapData;
Comment ("Connecting to process %u ...", Pid);
//
// 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.Target = OpenProcess(
PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ |
PROCESS_SUSPEND_RESUME,
FALSE,
Pid);
if (Globals.Target == NULL) {
Error (__FILE__, __LINE__,
"OpenProcess(%u) failed with error %u", Pid, GetLastError());
return;
}
//
// Attach ImageHlp and enumerate the modules.
//
Comment ("Process %u opened (handle=%d) ...", Pid, Globals.Target );
Result = SymInitialize(Globals.Target, // target process
NULL, // standard symbols search path
TRUE); // invade process space with symbols
if (Result == FALSE) {
ULONG ErrorCode = GetLastError();
if (ErrorCode >= 0x80000000) {
Error (__FILE__, __LINE__,
"imagehlp.SymInitialize() failed with error %X", ErrorCode);
}
else {
Error (__FILE__, __LINE__,
"imagehlp.SymInitialize() failed with error %u", ErrorCode);
}
goto ErrorReturn;
}
Comment ("Debug library initialized ...", Pid);
SymSetOptions(SYMOPT_CASE_INSENSITIVE |
SYMOPT_DEFERRED_LOADS |
(Globals.LineInfo ? SYMOPT_LOAD_LINES : 0) |
SYMOPT_UNDNAME);
Comment ("Debug options set: %08X", SymGetOptions());
// Result = SymRegisterCallback (Globals.Target,
// SymbolDbgHelpCallback,
// NULL);
if (Result == FALSE) {
Warning (NULL, 0, "Failed to register symbol callback function.");
}
Result = SymEnumerateModules (Globals.Target,
UmdhEnumerateModules,
Globals.Target);
if (Result == FALSE) {
Error (__FILE__, __LINE__,
"imagehlp.SymEnumerateModules() failed with error %u", GetLastError());
goto ErrorReturn;
}
Comment ("Module enumeration completed.");
//
// 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.
//
if (TraceDbInitialize (Globals.Target) == FALSE) {
goto ErrorReturn;
}
//
// Suspend target process.
//
// ISSUE: SilviuC: cannot suspend csrss.exe. Need to code to avoid that.
// UmdhSuspendProcess();
try {
//
// If we want just a raw dump then do it and return withouth getting any information
// about heaps.
//
if (Globals.RawDump) {
TraceDbDump ();
goto ErrorReturn;
}
//
// Read heap information.
//
Result = UmdhGetHeapsInformation (&HeapInfo);
if (Result == FALSE) {
Error (__FILE__, __LINE__,
"Failed to get heaps information.");
goto ErrorReturn;
}
//
// Print heap summary
//
Info ("\n - - - - - - - - - - Heap summary - - - - - - - - - -\n");
for (Heap = 0; Heap < HeapInfo.NumberOfHeaps; Heap += 1) {
HeapData = &(HeapInfo.Heaps[Heap]);
if (HeapData->BaseAddress == NULL) {
continue;
}
Info (" %p", HeapData->BaseAddress);
}
//
// Examine each heap.
//
for (Heap = 0; Heap < HeapInfo.NumberOfHeaps; Heap += 1) {
HeapData = &(HeapInfo.Heaps[Heap]);
if (HeapData->BaseAddress == NULL) {
//
// SilviuC: Can this really happen?
//
// This was in the process heap list but it's not
// active or it's signature didn't match
// HEAP_SIGNATURE; skip it.
//
Warning (__FILE__, __LINE__, "Got a null heap base address");
continue;
}
//
// Get information about this heap.
//
// Silviuc: Waht if we fail reading?
//
UmdhGetHEAPDATA(HeapData);
//
// Sort the HeapData->StackTraceData by TraceIndex.
//
qsort(HeapData->StackTraceData,
HeapData->TraceDataEntryCount,
sizeof (HeapData->StackTraceData[0]),
UmdhSortSTACK_TRACE_DATAByTraceIndex);
//
// Coalesce HeapData->StackTraceEntries by
// AllocationCount, zeroing allocation count for
// duplicate entries.
//
UmdhCoalesceSTACK_TRACE_DATA(HeapData->StackTraceData,
HeapData->TraceDataEntryCount);
//
// Sort the HeapData -> StackTraceData in ascending
// order by Size (BytesAllocated * AllocationCount) or
// if SortByAllocs is set, into descending order by
// number of allocations.
//
qsort(HeapData->StackTraceData,
HeapData->TraceDataEntryCount,
sizeof (HeapData->StackTraceData[0]),
UmdhSortSTACK_TRACE_DATABySize);
//
// Display Heap header info. The first `*' character is used by the
// dhcmp to synchronize log parsing.
//
Info ("\n*- - - - - - - - - - Start of data for heap @ %p - - - - - - - - - -\n",
HeapData->BaseAddress);
UmdhShowHEAPDATA(HeapData);
//
// The following line is required by dhcmp tool.
//
Info ("*- - - - - - - - - - Heap %p Hogs - - - - - - - - - -\n",
HeapData->BaseAddress);
//
// Display Stack trace info for stack in this heap.
//
UmdhShowStacks(HeapData->StackTraceData,
HeapData->TraceDataEntryCount,
Threshold);
Info ("\n*- - - - - - - - - - End of data for heap @ %p - - - - - - - - - -\n",
HeapData->BaseAddress);
//
// Clean up the allocations we made during this loop.
//
XFREE (HeapData->StackTraceData);
HeapData->StackTraceData = NULL;
}
XFREE(HeapInfo.Heaps);
HeapInfo.Heaps = NULL;
}
finally {
//
// Super important to resume target process even if umdh
// has a bug and crashes.
//
// UmdhResumeProcess ();
}
//
// Clean up.
//
ErrorReturn:
SymCleanup(Globals.Target);
CloseHandle(Globals.Target); Globals.Target= NULL;
}
VOID
UmdhUsage(
char *BadArg
)
{
if (BadArg) {
fprintf(stderr,
"\nUnexpected argument \"%s\"\n\n",
BadArg);
}
fprintf(stderr,
"umdh version %s \n"
"1. umdh {-p:(int)Process-id {-t:(int)Threshold} {-f:(char *)Filename} \n"
" {-d{:(int)Trace-Number}} {-v{:(char *)Filename}} \n"
" {-i:(int)Infolevel} {-l} {-r{:(int)Index}} \n"
" } \n"
"\n"
"2. umdh { {-h} {-v} File1 { File2 } } \n"
"\n"
"umdh can be used in two modes - \n"
"\n"
"When used in the first mode, it dumps the user mode heap (acts as old-umdh), \n"
"while used in the second mode acts as dhcmp. \n"
" \n"
" Options when used in MODE 1: \n"
" \n"
" -t Optional. Only dump stack that account for more allocations than \n"
" specified value. Defaults to 0; dump all stacks. \n"
"\n"
" -f Optional. Indicates output file. Destroys an existing file of the \n"
" same name. Default is to dump to stdout. \n"
"\n"
" -p Required. Indicates the Process-ID to examine. \n"
"\n"
" -d Optional. Dump address of each outstanding allocation. \n"
" Optional inclusion of an integer numeric argument causes dump of \n"
" only those blocks allocated from this BackTrace. \n"
"\n"
" -v Optional. Dumps debug output to stderr or to a file. \n"
"\n"
" -i Optional. Zero is default (no additional info). The greater the \n"
" number the more data is displayed. Supported numbers: 0, 1. \n"
"\n"
" -l Optional. Print file and line number information for traces. \n"
"\n"
" -r Optional. Print a raw dump of the trace database without any \n"
" heap information. If an index is specified then only the trace \n"
" with that particular index will be dumped. \n"
"\n"
" -x Optional. Suspend the Process while dumping heaps. \n"
"\n"
" -h Optional. Usage message. ie This message. \n"
" \n"
" Parameters are accepted in any order. \n"
" \n"
" \n"
" UMDH uses the dbghelp library to resolve symbols, therefore \n"
" _NT_SYMBOL_PATH must be set appropriately. For example: \n"
" \n"
" set _NT_SYMBOL_PATH=symsrv*symsrv.dll*\\\\symbols\\symbols \n"
" \n"
" to use the symbol server, otherwise the appropriate local or network path. \n"
" If no symbol path is set, umdh will use by default %%windir%%\\symbols. \n"
" \n"
" See http://dbg/symbols for more information about setting up symbols. \n"
" \n"
" UMDH requires also to have stack trace collection enabled for the process. \n"
" This can be done with the gflags tool. For example to enable stack trace \n"
" collection for notepad, the command is: `gflags -i notepad.exe +ust'. \n"
" \n"
"\n"
" When used in MODE 2: \n"
"\n"
" I) UMDH [-d] dh_dump1.txt dh_dump2.txt \n"
" This compares two DH dumps, useful for finding leaks. \n"
" dh_dump1.txt & dh_dump2.txt are obtained before and after some test \n"
" scenario. DHCMP matches the backtraces from each file and calculates \n"
" the increase in bytes allocated for each backtrace. These are then \n"
" displayed in descending order of size of leak \n"
" The first line of each backtrace output shows the size of the leak in \n"
" bytes, followed by the (last-first) difference in parentheses. \n"
" Leaks of size 0 are not shown. \n"
"\n"
" II) UMDH [-d] dh_dump.txt \n"
" For each allocation backtrace, the number of bytes allocated will be \n"
" attributed to each callsite (each line of the backtrace). The number \n"
" of bytes allocated per callsite are summed and the callsites are then \n"
" displayed in descending order of bytes allocated. This is useful for \n"
" finding a leak that is reached via many different codepaths. \n"
" ntdll!RtlAllocateHeap@12 will appear first when analyzing DH dumps of \n"
" csrss.exe, since all allocation will have gone through that routine. \n"
" Similarly, ProcessApiRequest will be very prominent too, since that \n"
" appears in most allocation backtraces. Hence the useful thing to do \n"
" with mode 2 output is to use dhcmp to comapre two of them: \n"
" umdh dh_dump1.txt > tmp1.txt \n"
" umdh dh_dump2.txt > tmp2.txt \n"
" umdh tmp1.txt tmp2.txt \n"
" the output will show the differences. \n"
"\n"
" Flags: \n"
" -d Output in decimal (default is hexadecimal) \n"
" -v Verbose output: include the actual backtraces as well as summary \n"
" information \n"
" (Verbose output is only interesting in mode 1 above.) \n",
UMDH_VERSION);
exit(EXIT_FAILURE);
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////// OS versioning
/////////////////////////////////////////////////////////////////////
// return TRUE if we can run on this version
BOOL
UmdhCheckOsVersion (
)
{
OSVERSIONINFO OsInfo;
BOOL Result;
ZeroMemory (&OsInfo, sizeof OsInfo);
OsInfo.dwOSVersionInfoSize = sizeof OsInfo;
Result = GetVersionEx (&OsInfo);
if (Result == FALSE) {
Comment ( "GetVersionInfoEx() failed with error %u",
GetLastError());
return FALSE;
}
Comment ("OS version %u.%u %s",
OsInfo.dwMajorVersion, OsInfo.dwMinorVersion,
OsInfo.szCSDVersion);
Comment ("Umdh OS version %u.%u",
UMDH_OS_MAJOR_VERSION, UMDH_OS_MINOR_VERSION);
if (OsInfo.dwMajorVersion < 4) {
Comment ( "Umdh does not run on systems older than 4.0");
return FALSE;
}
else if (OsInfo.dwMajorVersion == 4) {
//
// ISSUE: silviuc: add check to run only on NT4 SP6.
//
if (OsInfo.dwMajorVersion != UMDH_OS_MAJOR_VERSION
|| OsInfo.dwMinorVersion != UMDH_OS_MINOR_VERSION) {
Comment (
"Cannot run umdh for OS version %u.%u on a %u.%u system",
UMDH_OS_MAJOR_VERSION, UMDH_OS_MINOR_VERSION,
OsInfo.dwMajorVersion, OsInfo.dwMinorVersion);
return FALSE;
}
}
else if (OsInfo.dwMajorVersion == 5) {
if (OsInfo.dwMajorVersion != UMDH_OS_MAJOR_VERSION
|| OsInfo.dwMinorVersion != UMDH_OS_MINOR_VERSION) {
if (! (OsInfo.dwMinorVersion == 0 && UMDH_OS_MINOR_VERSION == 1)) {
Comment (
"Cannot run umdh for OS version %u.%u on a %u.%u system",
UMDH_OS_MAJOR_VERSION, UMDH_OS_MINOR_VERSION,
OsInfo.dwMajorVersion, OsInfo.dwMinorVersion);
return FALSE;
}
}
}
else {
Warning (NULL, 0, "OS version %u.%u",
OsInfo.dwMajorVersion,
OsInfo.dwMinorVersion);
}
return TRUE;
}
/////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////// main
/////////////////////////////////////////////////////////////////////
BOOL UMDH( ULONG argc, PCHAR * argv)
{
BOOLEAN WasEnabled;
CHAR CompName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD CompNameLength = MAX_COMPUTERNAME_LENGTH + 1;
NTSTATUS Status;
SYSTEMTIME st;
ULONG Pid = PID_NOT_PASSED_FLAG;
ULONG Threshold = 0;
ULONG i;
LARGE_INTEGER StartStamp;
LARGE_INTEGER EndStamp;
FILE * File;
ZeroMemory( &Globals, sizeof(Globals) );
Globals.Version = UMDH_VERSION;
Globals.OutFile = stdout;
Globals.ErrorFile = stderr;
/*
* Make an effort to understand passed arguments.
*/
if ((argc < 2) || (argc > 6)) {
return FALSE;
}
if (argc == 2 && strstr (argv[1], "?") != NULL) {
return FALSE;
}
i = 1;
while (i < argc) {
//
// Accept either '-' or '/' as argument specifier.
//
if ((argv[i][0] == '-') || (argv[i][0] == '/')) {
switch (tolower(argv[i][1])) {
case 'd':
if (argv[i][2] == ':') {
FlaggedTrace = atoi(&(argv[i][3]));
}
else {
FlaggedTrace = 0;
}
break;
case 't':
if (argv[i][2] == ':') {
Threshold = atoi(&(argv[i][3]));
}
else {
return FALSE;
}
break;
case 'p':
/*
* Is the first character of the remainder of this
* argument a number ? If not don't try to send it to
* atoi.
*/
if (argv[i][2] == ':') {
if (!isdigit(argv[i][3])) {
fprintf(stderr,
"\nInvalid pid specified with \"-p:\"\n");
return FALSE;
}
else {
Pid = atoi(&(argv[i][3]));
}
}
else {
return FALSE;
}
break;
case 'f':
if (argv[i][2] == ':') {
File = fopen (&(argv[i][3]), "w");
if (File == NULL) {
Comment ( "Failed to open output file `%s'",
&(argv[i][3]));
exit( EXIT_FAILURE );
}
else {
Globals.OutFile = File;
}
}
else {
return FALSE;
}
break;
//
// Possible future option for saving the trace database in a binary format.
// Not really useful right now because we still need access to the target
// process in order to get various data (modules loaded, heaps, etc.).
#if 0
case 's':
if (argv[i][2] == ':') {
Globals.DumpFileName = &(argv[i][3]);
}
else {
return FALSE;
}
break;
#endif
case 'v':
Globals.Verbose = TRUE;
if (argv[i][2] == ':') {
File = fopen (&(argv[i][3]), "w");
if (File == NULL) {
Comment ( "Failed to open error file `%s'",
&(argv[i][3]));
exit( EXIT_FAILURE );
}
else {
Globals.ErrorFile = File;
}
}
break;
case 'i':
Globals.InfoLevel = 1;
if (argv[i][2] == ':') {
Globals.InfoLevel = atoi (&(argv[i][3]));
}
break;
case 'l':
Globals.LineInfo = TRUE;
break;
case 'r':
Globals.RawDump = TRUE;
if (argv[i][2] == ':') {
Globals.RawIndex = (USHORT)(atoi (&(argv[i][3])));
}
break;
case 'x':
Globals.Suspend = TRUE;
break;
case 'h': /* FALLTHROUGH */
case '?':
return FALSE;
break;
default:
return FALSE;
break;
}
}
else {
return FALSE;
}
i++;
}
if (Pid == PID_NOT_PASSED_FLAG) {
fprintf(stderr,
"\nNo pid specified.\n");
return FALSE;
}
//
// Stamp umdh log with time and computer name.
//
GetLocalTime(&st);
GetComputerName(CompName, &CompNameLength);
Comment ("");
Comment ("UMDH: version %s: Logtime %4u-%02u-%02u %02u:%02u - Machine=%s - PID=%u",
Globals.Version,
st.wYear,
st.wMonth,
st.wDay,
st.wHour,
st.wMinute,
CompName,
Pid);
Comment ("\n");
if( !UmdhCheckOsVersion() ) {
exit(EXIT_FAILURE);;
}
QueryPerformanceCounter (&StartStamp);
//
// Try to come up with a guess for the symbols path if none is defined.
//
SetSymbolsPath ();
//
// Enable debug privilege, so that we can attach to the indicated
// process. If it fails complain but try anyway just in case the user can
// actually open the process without privilege.
//
// SilviuC: do we need debug privilege?
//
WasEnabled = TRUE;
Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE,
TRUE,
FALSE,
&WasEnabled);
if (! NT_SUCCESS(Status)) {
Warning (__FILE__, __LINE__,
"RtlAdjustPrivilege(enable) failed with status = %X",
Status);
//
// If we could not enable the privilege, indicate that it was already
// enabled so that we do not attempt to disable it later.
//
WasEnabled = TRUE;
}
else {
Comment ("Debug privilege has been enabled.");
}
//
// Increase priority of umdh as much as possible. This has the role of
// preventing heap activity in the process being grovelled.
//
// SilviuC: we might need to enable the SE_INC_BASE_PRIORITY privilege.
//
#if 0
{
BOOL Result;
Result = SetPriorityClass (GetCurrentProcess(),
HIGH_PRIORITY_CLASS);
if (Result == FALSE) {
Warning (NULL, 0,
"SetPriorityClass failed with error %u");
}
else {
Result = SetThreadPriority (GetCurrentThread(),
THREAD_PRIORITY_HIGHEST);
if (Result == FALSE) {
Warning (NULL, 0,
"SetThreadPriority failed with error %u");
}
else {
Comment ("Priority of UMDH thread has been increased.");
}
}
}
#endif
//
// Initialize heap for persistent allocations.
//
SymbolsHeapInitialize();
//
// We may not have SeDebugPrivilege, but try anyway.
// SilviuC: we should print an error if we do not have this privilege
//
UmdhGrovel(Pid, Threshold);
//
// Disable SeDebugPrivilege if we enabled it.
//
if (! WasEnabled) {
Status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE,
FALSE,
FALSE,
&WasEnabled);
if (! NT_SUCCESS(Status)) {
Warning (__FILE__, __LINE__,
"RtlAdjustPrivilege(disable) failed with status = %X\n",
Status);
}
}
//
// Statistics
//
ReportStatistics ();
{
LARGE_INTEGER Frequency;
QueryPerformanceCounter (&EndStamp);
QueryPerformanceFrequency (&Frequency);
Debug (NULL, 0, "Start stamp %I64u", StartStamp.QuadPart);
Debug (NULL, 0, "End stamp %I64u", EndStamp.QuadPart);
Debug (NULL, 0, "Frequency %I64u", Frequency.QuadPart);
Frequency.QuadPart /= 1000; // ticks per msec
if (Frequency.QuadPart) {
Comment ("Elapse time %I64u msecs.",
(EndStamp.QuadPart - StartStamp.QuadPart) / (Frequency.QuadPart));
}
}
{
FILETIME CreateTime, ExitTime, KernelTime, UserTime;
BOOL bSta;
bSta= GetProcessTimes( NtCurrentProcess(),
&CreateTime,
&ExitTime,
&KernelTime,
&UserTime );
if( bSta ) {
LONGLONG User64, Kernel64;
DWORD dwUser, dwKernel;
Kernel64= *(LONGLONG*) &KernelTime;
User64= *(LONGLONG*) &UserTime;
dwKernel= (DWORD) (Kernel64/10000);
dwUser= (DWORD) (User64/10000);
Comment( "CPU time User: %u msecs. Kernel: %u msecs.",
dwUser, dwKernel );
}
}
//
// Cleanup
//
SymCleanup(Globals.Target);
Globals.Target = NULL;
fflush (Globals.OutFile);
fflush (Globals.ErrorFile);
if (Globals.OutFile != stdout) {
fclose (Globals.OutFile);
}
if (Globals.ErrorFile != stderr) {
fclose (Globals.ErrorFile);
}
return TRUE;
}
VOID __cdecl
#if defined (_PART_OF_DH_)
UmdhMain(
#else
main(
#endif
ULONG argc,
PCHAR *argv
)
/*
VOID __cdecl
main(
ULONG argc,
PCHAR *argv
)
*/
{
/*
* Make an effort to understand passed arguments.
*/
if (UMDH (argc, argv)) {
}
else if (DHCMP (argc, argv)) {
}
else {
UmdhUsage (NULL);
}
return;
}