Figure 1 MemDiffDemo.cpp
//==================================================
// MemDiff - Matt Pietrek 1999
// Microsoft Systems Journal, November 1999
// FILE: MemDiffDemo.cpp
//==================================================
#include <windows.h>
#include "memdiff.h"
int main()
{
HANDLE h1, h2;
//
// Create a new heap, and allocate a bunch of memory. The
// HEAP_ZERO_MEMORY forces all the allocated pages in
//
HANDLE hHeap1 = HeapCreate( 0, 0, 0 );
HeapAlloc( hHeap1, HEAP_ZERO_MEMORY, 0x7000 );
//
// Take the initial snapshot
//
h1 = MDTakeSnapshot( GetCurrentProcess() );
// Get rid of hHeap1. This test ensures that we see something in
// the Snapshot1 details report
HeapDestroy( hHeap1 );
//
// Load, then free a DLL. Extra memory paged into the
// working set, but not freed *should* show up.
//
HMODULE hModule = LoadLibrary( "WININET.DLL" );
FreeLibrary( hModule );
//
// Allocate some memory, and leave it around
//
PVOID p = VirtualAlloc( 0, 0x1000, MEM_COMMIT, PAGE_READWRITE );
memset( p, 0, 0x1000 ); // Touch it!
HANDLE hHeap2 = HeapCreate( 0, 0, 0 );
HeapAlloc( hHeap2, HEAP_ZERO_MEMORY, 0x1234 );
HANDLE hFileMapping = CreateFileMapping( INVALID_HANDLE_VALUE, 0,
PAGE_READWRITE, 0, 0x2000, 0 );
if ( hFileMapping )
{
PVOID p2 = MapViewOfFile(hFileMapping, FILE_MAP_WRITE, 0, 0, 0x2000);
if ( p2 )
memset( p2, 0, 0x2000 );
}
//
// Take the second snapshot
//
h2 = MDTakeSnapshot( GetCurrentProcess() );
//
// Compare the two snapshots to generate a report
//
MDCompareSnapshot( h1, h2, GetStdHandle(STD_OUTPUT_HANDLE), false );
//
// Free the snapshot handles (aka "memory")
//
MDFreeSnapshot( h1 );
MDFreeSnapshot( h2 );
return 0;
}
Figure 2 Nonverbose Output
Memory Difference: 36KB
Memory allocated: 72KB
Memory freed: 36KB
Private memory: 20KB Shared(potentially): 52KB
Snapshot1 (items are no longer in memory)
===============================================================================
Address Size Owner
008B0000 36K <unknown>
Snapshot2 (these items have been paged in)
===============================================================================
Address Size Owner
00900000 4K VirtualAlloc/Stack/Other
00910000 16K Heap
00950000 8K VirtualAlloc/Stack/Other
77E62000 8K D:\WINNT\system32\USER32.dll
77E92000 4K D:\WINNT\system32\KERNEL32.dll
77F72000 20K D:\WINNT\system32\GDI32.DLL
77F89000 12K D:\WINNT\System32\ntdll.dll
Figure 3 Verbose Output
Snapshot2 (these items have been paged in)
===============================================================================
RW:read-write RO:read-only EX:executable CW:copy-on-write SH:shared
Address Size Attribute Owner
00900000 4K RW VirtualAlloc/Stack/Other
00910000 16K RW Heap
00950000 8K RW SH VirtualAlloc/Stack/Other
77E62000 8K RO EX SH D:\WINNT\system32\USER32.dll
77E92000 4K RO EX SH D:\WINNT\system32\KERNEL32.dll
77F72000 20K RO EX SH D:\WINNT\system32\GDI32.DLL
77F89000 12K RO EX SH D:\WINNT\System32\ntdll.dll
Figure 4 memdiff.cpp
//==================================================
// MemDiff - Matt Pietrek 1999
// Microsoft Systems Journal, November 1999
// FILE: memdiff.cpp
//==================================================
#include "StdAfx.h"
#include <stdarg.h>
#include "memdiff.h"
//===================================================================
static int __cdecl MDSort( const void *pElem1, const void *pElem2 );
void FilterOutCommonPages( HANDLE hSnapshot1, HANDLE hSnapshot2 );
void SummaryReport( HANDLE hSnapshot1,
HANDLE hSnapshot2,
HANDLE hOutputFile );
void DetailedReport(HANDLE hSnapshot1,
HANDLE hSnapshot2,
HANDLE hOutputFile,
bool fVerbose = false );
void MDPrintf( HANDLE hOutputFile, char * pszFmt, ... );
//===================================================================
typedef struct MEMDIFF_SNAPSHOT
{
DWORD m_signature;
DWORD m_dwAllocSize;
DWORD m_fCompared; // 0 initially, 1 after filtering common pages
DWORD m_nPages; // Must come last!!!
} * PMEMDIFF_SNAPSHOT;
#define SNAPSHOT_SIGNATURE 0x50594F52
static DWORD g_kbMultiplier;
//===================================================================
extern "C"
HANDLE
__stdcall
MDTakeSnapshot( HANDLE hProcess )
{
unsigned dwAlloc = 0;
top:
dwAlloc += 0x2000;
PMEMDIFF_SNAPSHOT pSnapshot;
pSnapshot = (PMEMDIFF_SNAPSHOT)VirtualAlloc(0,
dwAlloc,
MEM_COMMIT,
PAGE_READWRITE);
if ( !pSnapshot )
return 0;
PVOID pPageInfoStart = &pSnapshot->m_nPages;
if ( 0 == QueryWorkingSet( hProcess, pPageInfoStart,
dwAlloc - sizeof(*pSnapshot) ) )
{
VirtualFree( pSnapshot, 0, MEM_RELEASE );
return 0;
}
else // It succeded.... But...
{
// Were there more pages than we had buffer size for?
if ( pSnapshot->m_nPages > (dwAlloc / sizeof(DWORD)) )
{
if ( dwAlloc < 0x20000 ) // Limits us to 128MB on an x86
{
VirtualFree( pSnapshot, 0, MEM_RELEASE );
pSnapshot = 0;
goto top;
}
else
return 0;
}
}
pSnapshot->m_signature = SNAPSHOT_SIGNATURE;
pSnapshot->m_fCompared = 0;
pSnapshot->m_dwAllocSize = dwAlloc;
return (HANDLE) pSnapshot;
}
extern "C"
BOOL
__stdcall
MDCompareSnapshot( HANDLE hSnapshot1,
HANDLE hSnapshot2,
HANDLE hOutputFile,
bool fVerbose )
{
if ( !hSnapshot1 || !hSnapshot2 )
return false;
if ( IsBadReadPtr( (PVOID)hSnapshot1, sizeof(MEMDIFF_SNAPSHOT) ) )
return false;
if ( IsBadReadPtr( (PVOID)hSnapshot2, sizeof(MEMDIFF_SNAPSHOT) ) )
return false;
if ( ((PMEMDIFF_SNAPSHOT)hSnapshot1)->m_signature != SNAPSHOT_SIGNATURE )
return false;
if ( ((PMEMDIFF_SNAPSHOT)hSnapshot2)->m_signature != SNAPSHOT_SIGNATURE )
return false;
if ( ((PMEMDIFF_SNAPSHOT)hSnapshot1)->m_fCompared
|| ((PMEMDIFF_SNAPSHOT)hSnapshot2)->m_fCompared )
{
MDPrintf( hOutputFile,
"One or both snapshots has already been compared against."
"\r\nSnapshots are only good for one compare.\r\n" );
return false;
}
SYSTEM_INFO sysInfo;
GetSystemInfo( &sysInfo );
g_kbMultiplier = sysInfo.dwPageSize/1024;
FilterOutCommonPages( hSnapshot1, hSnapshot2 );
SummaryReport( hSnapshot1, hSnapshot2, hOutputFile );
MDPrintf( hOutputFile, "\r\n" );
DetailedReport( hSnapshot1, hSnapshot2, hOutputFile, fVerbose );
return true;
}
extern "C"
BOOL
__stdcall
MDFreeSnapshot( HANDLE hSnapshot )
{
if ( ((PMEMDIFF_SNAPSHOT)hSnapshot)->m_signature != SNAPSHOT_SIGNATURE )
return false;
return VirtualFree( hSnapshot, 0, MEM_RELEASE );
}
/*========================================================================*/
// Helper function routines
//
static int __cdecl MDSort( const void *pElem1, const void *pElem2 )
{
DWORD elem1 = *(PDWORD)pElem1;
DWORD elem2 = *(PDWORD)pElem2;
if ( elem1 > elem2 )
return 1;
else if ( elem1 < elem2 )
return -1;
else
return 0;
}
void FilterOutCommonPages( HANDLE hSnapshot1, HANDLE hSnapshot2 )
{
PMEMDIFF_SNAPSHOT pSnapshot1 = (PMEMDIFF_SNAPSHOT)hSnapshot1;
PMEMDIFF_SNAPSHOT pSnapshot2 = (PMEMDIFF_SNAPSHOT)hSnapshot2;
pSnapshot1->m_fCompared = 1; // So we don't use it again
pSnapshot2->m_fCompared = 1; // So we don't use it again
DWORD nPages1 = pSnapshot1->m_nPages;
DWORD nPages2 = pSnapshot2->m_nPages;
PDWORD pPages1 = (PDWORD)(pSnapshot1 + 1);
PDWORD pPages2 = (PDWORD)(pSnapshot2 + 1);
qsort( pPages1, nPages1, sizeof(DWORD), MDSort );
qsort( pPages2, nPages2, sizeof(DWORD), MDSort );
//
// Clean this up! Basically, set all pages that are the same
// in both snapshots to the value '0'
//
DWORD jStart = 0;
for ( DWORD i = 0; i < nPages1; i++ )
{
DWORD pAddr1 = pPages1[i] & 0xFFFFF000;
for ( DWORD j = jStart; j < nPages2; j++ )
{
DWORD pAddr2 = pPages2[j] & 0xFFFFF000;
if ( pAddr1 == pAddr2 )
{
pPages1[i] = pPages2[j] = 0;
jStart = j+1;
break;
}
if ( pAddr2 > pAddr1 )
break;
}
}
// now filter out the pages used by snapshot1 for snapshot1's data
for ( i = 0; i < nPages1; i++ )
{
DWORD pAddr1 = pPages1[i] & 0xFFFFF000;
if ( (pAddr1 >= (DWORD)pSnapshot1) &&
(pAddr1 < ((DWORD)pSnapshot1 + pSnapshot1->m_dwAllocSize)) )
{
pPages1[i] = 0;
}
}
// now filter out the pages used by snapshot2 for snapshot2's data
for ( i = 0; i < nPages2; i++ )
{
DWORD pAddr2 = pPages2[i] & 0xFFFFF000;
if ( (pAddr2 >= (DWORD)pSnapshot2) &&
(pAddr2 < ((DWORD)pSnapshot2 + pSnapshot2->m_dwAllocSize)) )
{
pPages2[i] = 0;
}
}
}
void SummaryReport( HANDLE hSnapshot1,
HANDLE hSnapshot2,
HANDLE hOutputFile )
{
PMEMDIFF_SNAPSHOT pSnapshot1 = (PMEMDIFF_SNAPSHOT)hSnapshot1;
PMEMDIFF_SNAPSHOT pSnapshot2 = (PMEMDIFF_SNAPSHOT)hSnapshot2;
DWORD nPages1 = pSnapshot1->m_nPages;
DWORD nPages2 = pSnapshot2->m_nPages;
PDWORD pPages1 = (PDWORD)(pSnapshot1 + 1);
PDWORD pPages2 = (PDWORD)(pSnapshot2 + 1);
DWORD nPagesLeft1 = 0, nPagesShared1 = 0;
DWORD nPagesLeft2 = 0, nPagesShared2 = 0;
for ( DWORD i = 0; i < nPages1; i++ )
{
if ( 0 == pPages1[i] )
continue;
nPagesLeft1++;
if ( pPages1[i] & 0x100 )
nPagesShared1++;
}
for ( i = 0; i < nPages2; i++ )
{
if ( 0 == pPages2[i] )
continue;
nPagesLeft2++;
if ( pPages2[i] & 0x100 )
nPagesShared2++;
}
MDPrintf( hOutputFile, "Memory Difference: %uKB\r\n",
(long)((nPagesLeft2 - nPagesLeft1)) * g_kbMultiplier );
MDPrintf( hOutputFile, "Memory allocated: %uKB\r\n",
nPagesLeft2 * g_kbMultiplier );
MDPrintf( hOutputFile, "Memory freed: %uKB\r\n",
nPagesLeft1 * g_kbMultiplier );
MDPrintf( hOutputFile,
"Private memory: %uKB Shared(potentially): %uKB\r\n",
(nPagesLeft2 - nPagesShared2) * g_kbMultiplier,
(nPagesShared2 * g_kbMultiplier) );
}
void DetailedReportHelper(HANDLE hSnapshot, HANDLE hOutputFile, bool fVerbose)
{
MDPrintf( hOutputFile, "==============================================="
"================================\r\n" );
if ( fVerbose )
{
MDPrintf( hOutputFile, "RW:read-write RO:read-only EX:executable "
"CW:copy-on-write SH:shared\r\n" );
MDPrintf( hOutputFile, "Address Size Attribute Owner\r\n" );
}
else
{
MDPrintf( hOutputFile, "Address Size Owner\r\n" );
}
PMEMDIFF_SNAPSHOT pSnapshot = (PMEMDIFF_SNAPSHOT)hSnapshot;
DWORD nPages = pSnapshot->m_nPages;
PDWORD pPages = (PDWORD)(pSnapshot + 1);
CProcessHeaps heaps;
for ( DWORD i = 0; i < nPages; ) // we bump up 'i' at end
{
if ( 0 == pPages[i] ) // Skip empty (common) pages
{
i++;
continue;
}
//
// Gather basic information about this page
//
PVOID pAddr = (PVOID)(pPages[i] & 0xFFFFF000);
DWORD pageAttrib = pPages[i] & 0x00000FFF;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(pAddr, &mbi, sizeof(mbi));
PVOID allocationBase = mbi.AllocationBase;
//
// Now coalesce this page with adjoining, similar pages
//
DWORD nCoalescedPages = 1;
DWORD j;
if ( fVerbose ) // Pages must be contiguous, and share the same
{ // attributes and allocation base
for ( j=i+1; j < nPages; j++ )
{
if ( 0 == pPages[j] )
break;
if ( (pPages[j] & 0x00000FFF) != pageAttrib )
break;
VirtualQuery( (PVOID)pPages[j], &mbi, sizeof(mbi));
if ( mbi.AllocationBase != allocationBase )
break;
nCoalescedPages++;
}
}
else // Pages must share the same allocation base
{
for ( j=i+1; j < nPages; j++ )
{
if ( 0 == pPages[j] )
continue;
VirtualQuery( (PVOID)pPages[j], &mbi, sizeof(mbi));
if ( mbi.AllocationBase != allocationBase )
break;
nCoalescedPages++;
}
}
DWORD nProcessedPages = j - i;
char szPageAttrib[128] = {0};
if ( (pageAttrib & 5) == 5 )
{
strcat( szPageAttrib, "CW " );
}
else
{
if ( pageAttrib & 1 )
strcat( szPageAttrib, "RO " );
if ( pageAttrib & 2 )
strcat( szPageAttrib, "EX " );
if ( pageAttrib & 4 )
strcat( szPageAttrib, "RW " );
}
if ( pageAttrib & 0x100 )
{
strcat( szPageAttrib, "SH " );
}
char szPageOwner[MAX_PATH];
szPageOwner[0] = 0;
if ( 0 == allocationBase )
{
strcpy( szPageOwner, "<unknown>" );
}
else if ( !GetModuleFileName((HINSTANCE)allocationBase,
szPageOwner, sizeof(szPageOwner)) )
{
if ( heaps.IsAddressInHeaps( allocationBase) )
{
wsprintf( szPageOwner, "Heap" );
}
else if ( GetMappedFileNameA( GetCurrentProcess(), allocationBase,
szPageOwner, sizeof(szPageOwner)))
{
// GetMappedFileNameW is just a call to
// NtQueryVirtualMemory( 2 ). (0 is VirtualQueryEx);
}
else
wsprintf( szPageOwner, "VirtualAlloc/Stack/Other" );
}
if ( fVerbose )
{
MDPrintf( hOutputFile, "%08X %5uK %-9s %s\r\n",
pAddr, nCoalescedPages * g_kbMultiplier,
szPageAttrib, szPageOwner );
}
else
{
MDPrintf( hOutputFile, "%08X %5uK %s\r\n",
pAddr, nCoalescedPages * g_kbMultiplier, szPageOwner );
}
i += nProcessedPages;
}
}
void DetailedReport(HANDLE hSnapshot1,
HANDLE hSnapshot2,
HANDLE hOutputFile,
bool fVerbose)
{
MDPrintf(hOutputFile, "Snapshot1 (items are no longer in memory)\r\n");
DetailedReportHelper( hSnapshot1, hOutputFile, fVerbose );
MDPrintf( hOutputFile, "\r\n" );
MDPrintf(hOutputFile, "Snapshot2 (these items have been paged in)\r\n");
DetailedReportHelper( hSnapshot2, hOutputFile, fVerbose );
}
void MDPrintf( HANDLE hOutputFile, char * pszFmt, ... )
{
va_list marker;
va_start( marker, pszFmt );
char szBuffer[1024];
int retValue = wvsprintf( szBuffer, pszFmt, marker );
if ( retValue )
{
DWORD dwWritten;
WriteFile( hOutputFile, szBuffer, retValue, &dwWritten, 0 );
}
va_end( marker );
}