mirror of https://github.com/tongzx/nt5src
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.
2766 lines
80 KiB
2766 lines
80 KiB
/*++
|
|
|
|
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;
|
|
}
|
|
|