mirror of https://github.com/BOINC/boinc.git
955 lines
24 KiB
C
Executable File
955 lines
24 KiB
C
Executable File
/***************************************************************************
|
|
* HeapCheck - a heap debugging library
|
|
* Copyright (C) 2001 Thanassis Tsiodras (ttsiod@softlab.ntua.gr)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
**************************************************************************
|
|
*
|
|
* HeapCheck 1.2
|
|
* =============
|
|
*
|
|
* A simple debugging allocator, designed to help heap bug-hunting.
|
|
* Like most debugging heap allocators, it helps you find memory leaks.
|
|
* It also helps you where others can't: in catching invalid accesses.
|
|
* It uses the paging system to create inaccessible pages by the side
|
|
* of your normal allocations. This way, accesses outside the heap-
|
|
* allocated space cause an exception (even READ accesses). This is much
|
|
* better than heap debugging done with:
|
|
*
|
|
* 1. Visual C++ 6.0 Runtime Heap Debugging:
|
|
* This only checks for heap corruption (i.e. only WRITINGS outside
|
|
* the allocations) and even then, it only catches writings in the
|
|
* NO_MANS_LAND_SIZE (default:4 bytes) on either side of the blocks.
|
|
* The detection of these errors is done whenever the user (or the system)
|
|
* calls _CrtCheckMemory(). HeapCheck catches READ accesses as well,
|
|
* at the EXACT place they are made, and in a much larger
|
|
* block (a page in i386 systems is 4096 bytes).
|
|
*
|
|
* 2. BoundsChecker:
|
|
* This is a very good debugging tool, capable of catching almost
|
|
* every bug. However, in order to perform all these checks, a lot
|
|
* of CPU cycles are used, especially in instrumentation mode and
|
|
* maximum memory checking (some programs won't work correctly when
|
|
* they run so slowly, e.g. protocol stacks). HeapCheck catches only
|
|
* heap-related errors, but it uses the paging hardware to do the
|
|
* checking. The net result is that the HeapCheck debug versions of
|
|
* programs run at almost the same speed as normal debug versions.
|
|
*
|
|
* I hope (but can't guarantee) that it will help you in your heap
|
|
* bug hunting. It has definitely helped me with my projects.
|
|
* Many thanks for the original idea go to the creator of UNIX's
|
|
* Electric Fence, Bruce Perens. Oh, and make sure you use the
|
|
* correct (Win2000) versions of the debugging libraries. I used:
|
|
*
|
|
* dbghelp.dll : 5.0.2195.1
|
|
* imagehlp.dll : 5.0.2195.1
|
|
*
|
|
* Maybe SymGetLineFromAddr works with other combinations, but I used
|
|
* these successfully under both NT 4.0sp6 and Win2000.
|
|
*
|
|
* Happy coding!
|
|
* Thanassis Tsiodras
|
|
* ttsiod@softlab.ntua.gr
|
|
*
|
|
**************************************************************************
|
|
*/
|
|
|
|
/*
|
|
* Remember, all of this code gets called only in _DEBUG builds!
|
|
* Check swaps.h to see why...
|
|
*/
|
|
|
|
#ifdef _DEBUG
|
|
|
|
#include <windows.h>
|
|
#include <crtdbg.h>
|
|
#include <imagehlp.h>
|
|
|
|
/* Include prototypes for HeapCheck functions */
|
|
#include "HeapCheckPrototypes.h"
|
|
|
|
/*
|
|
* It is possible to write code that bypasses the allocation mechanisms of
|
|
* the library, and allocates from the standard VC-heap. An example inside
|
|
* the runtime library is the use of an enhanced 'new' operator in
|
|
* iostrini.cpp (line 21) and cerrinit.cpp (line 21) where _new_crt is used.
|
|
* _new_crt maps through a #define to a 'new' operator that takes extra
|
|
* parameters. HeapCheck can't catch this call, so when the time comes for
|
|
* the deletion of these blocks, the library's delete operator doesn't find
|
|
* them in it's tables. It is capable though to understand that these are
|
|
* VC-heap blocks, through the use of _CrtIsValidHeapPointer.
|
|
*
|
|
* If you #define NO_VC_HEAP_ERRS, the library won't complain for such
|
|
* situations. This is the default, but if you use inside your code direct
|
|
* calls to _malloc_dbg, _calloc_dbg, etc, you should disable this.
|
|
*/
|
|
|
|
#define NO_VC_HEAP_ERRS
|
|
|
|
/*
|
|
* Modify this for Alpha, PowerPC, etc. It is the page size used by
|
|
* the virtual memory hardware. For Intel architectures, this is 4096
|
|
* bytes. For others, place a breakpoint in the call to HeapCheckStartup's
|
|
* GetSystemInfo, and read the 'dwPageSize' field of the 'si' variable.
|
|
*/
|
|
|
|
#define PAGE_SIZE 4096
|
|
|
|
/*
|
|
* Total concurrent allocations possible. If your program needs more,
|
|
* you'll get an assertion telling you to increase this.
|
|
*/
|
|
|
|
#define MAX_ALLOCATIONS 16384
|
|
|
|
/*
|
|
* Total heap available to the application. If your program needs more,
|
|
* you'll get an assertion telling you to increase this.
|
|
*/
|
|
|
|
#define MAX_ALLOCATABLE_BLOCK 8*1048576
|
|
|
|
/*
|
|
* Max depth of call stack looked up when our functions are called
|
|
*/
|
|
|
|
#define MAX_STACK_DEPTH 30
|
|
|
|
/*
|
|
* Define PRINT_NUMERIC_EIP to get stack traces that boldly go
|
|
* where no imagehlp.dll has gone before... Hope you have SoftIce...
|
|
*/
|
|
|
|
//#define PRINT_NUMERIC_EIP
|
|
|
|
/*
|
|
* Variables
|
|
*/
|
|
|
|
static DWORD dwAllocationGranularity = 0;
|
|
|
|
static PCHAR pMemoryBase;
|
|
static DWORD dwTotalPages;
|
|
static DWORD dwFreePages;
|
|
|
|
static struct tag_allocations {
|
|
DWORD isFree;
|
|
PCHAR pData;
|
|
DWORD dwPages;
|
|
DWORD dwLength;
|
|
DWORD EIPs[MAX_STACK_DEPTH];
|
|
} allocations[MAX_ALLOCATIONS];
|
|
|
|
static BYTE pagesState[MAX_ALLOCATABLE_BLOCK/PAGE_SIZE];
|
|
|
|
static DWORD dwTotalPeakMemory = 0, dwTotalAllocated = 0;
|
|
|
|
static CRITICAL_SECTION section;
|
|
|
|
/*
|
|
* Function prototypes.
|
|
*/
|
|
|
|
/*
|
|
* Weird hack, in order to support 5 and 6 argument passing to _CrtDbgReport.
|
|
* Unforunately, with VC 5.0/6.0, RPT macros go up until _RPT4!
|
|
*/
|
|
|
|
#ifndef _RPT5
|
|
#define _RPT5(rptno, msg, arg1, arg2, arg3, arg4, arg5) \
|
|
do { if ((1 == _CrtDbgReport(rptno, NULL, 0, NULL, msg, arg1, arg2, arg3, arg4, arg5))) \
|
|
_CrtDbgBreak(); } while (0)
|
|
#endif
|
|
|
|
#ifndef _RPT6
|
|
#define _RPT6(rptno, msg, arg1, arg2, arg3, arg4, arg5, arg6) \
|
|
do { if ((1 == _CrtDbgReport(rptno, NULL, 0, NULL, msg, arg1, arg2, arg3, arg4, arg5, arg6))) \
|
|
_CrtDbgBreak(); } while (0)
|
|
#endif
|
|
|
|
static void HeapCheckStartup(void);
|
|
static void HeapCheckShutDown(void);
|
|
|
|
/*
|
|
* Simple allocator of pages that never frees.
|
|
*/
|
|
|
|
static void *
|
|
FindConsecutiveFreePagesSimple(DWORD dwPages)
|
|
{
|
|
return pMemoryBase + PAGE_SIZE*(dwTotalPages - dwFreePages);
|
|
}
|
|
|
|
/*
|
|
* Page allocator that searches for free pages.
|
|
* Can be improved with better algorithms, but it's good enough for
|
|
* most projects...
|
|
*/
|
|
|
|
static void *
|
|
FindConsecutiveFreePages(DWORD dwPages)
|
|
{
|
|
DWORD i;
|
|
PVOID pv;
|
|
|
|
for(i=0; i<sizeof(pagesState); i++) {
|
|
if (!pagesState[i]) {
|
|
// Found a free page. Make sure there's enough free pages after it...
|
|
|
|
PVOID test;
|
|
|
|
// If you land here after an assertion, stop debugging
|
|
// and increase MAX_ALLOCATABLE_BLOCK
|
|
_ASSERTE ((i + dwPages - 1) < sizeof(pagesState));
|
|
|
|
// Search for used-up page in our searching range...
|
|
test = memchr(&pagesState[i], 1, dwPages);
|
|
|
|
if (!test) // We found enough pages.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If you land here after an assertion 'Retry', stop debugging
|
|
// and increase MAX_ALLOCATABLE_BLOCK
|
|
_ASSERTE(i<sizeof(pagesState));
|
|
|
|
// Set 1 to these pages's slots to reflect NON-FREE state
|
|
FillMemory(&pagesState[i], dwPages, 1);
|
|
|
|
pv = pMemoryBase + i*PAGE_SIZE;
|
|
return pv;
|
|
}
|
|
|
|
/*
|
|
* Utility function for VC-heap blocks.
|
|
* Should be using binary search, but hey...I'm lazy.
|
|
*
|
|
* NOT TESTED YET - Who cares about VC-blocks?
|
|
*/
|
|
|
|
static DWORD
|
|
GetSize(PVOID pData)
|
|
{
|
|
// Search for block length....
|
|
DWORD dwTest;
|
|
|
|
for(dwTest=0; dwTest<MAX_ALLOCATABLE_BLOCK; dwTest++)
|
|
if (_CrtIsMemoryBlock(pData, dwTest, NULL, NULL, NULL))
|
|
break;
|
|
|
|
if (dwTest == MAX_ALLOCATABLE_BLOCK)
|
|
return -1;
|
|
else
|
|
return dwTest;
|
|
}
|
|
|
|
static void FillCallStack(
|
|
DWORD EIPs[])
|
|
{
|
|
DWORD LevelOneFrame;
|
|
DWORD dwFrame = 0;
|
|
|
|
__asm {
|
|
push eax
|
|
push ebx
|
|
mov eax, [ebp] // return to the frame of the caller
|
|
mov LevelOneFrame, eax
|
|
pop ebx
|
|
pop eax
|
|
}
|
|
|
|
while(1) {
|
|
DWORD _eip;
|
|
|
|
if (IsBadReadPtr((VOID*)LevelOneFrame, 8))
|
|
break;
|
|
if (LevelOneFrame & 3)
|
|
break;
|
|
|
|
_eip = ((PULONG) LevelOneFrame)[1];
|
|
if (!_eip)
|
|
break;
|
|
|
|
// If you end up in this assertion, stop debugging and re-compile
|
|
// with an increased value for the MAX_STACK_DEPTH constant.
|
|
_ASSERTE(dwFrame < MAX_STACK_DEPTH);
|
|
|
|
EIPs[dwFrame++] = _eip;
|
|
LevelOneFrame = ((PULONG) LevelOneFrame)[0];
|
|
}
|
|
EIPs[dwFrame] = 0;
|
|
}
|
|
|
|
static void PrintCaller()
|
|
{
|
|
DWORD LevelOneFrame;
|
|
IMAGEHLP_LINE dbgLine;
|
|
DWORD dwTemp;
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
__asm {
|
|
push eax
|
|
push ebx
|
|
mov eax, [ebp] // return to the frame of the caller
|
|
mov LevelOneFrame, eax
|
|
pop ebx
|
|
pop eax
|
|
}
|
|
|
|
while(1) {
|
|
DWORD _eip;
|
|
|
|
if (IsBadReadPtr((VOID*)LevelOneFrame, 8))
|
|
break;
|
|
if (LevelOneFrame & 3)
|
|
break;
|
|
|
|
_eip = ((PULONG) LevelOneFrame)[1];
|
|
if (!_eip)
|
|
break;
|
|
|
|
ZeroMemory(&dbgLine, sizeof(IMAGEHLP_LINE));
|
|
dbgLine.SizeOfStruct = sizeof(IMAGEHLP_LINE);
|
|
if(!SymGetLineFromAddr(hProcess, _eip, &dwTemp, &dbgLine)) {
|
|
//_RPT1(_CRT_WARN, "Called from EIP = %x\n", _eip);
|
|
break;
|
|
} else {
|
|
_RPT3(
|
|
_CRT_WARN,
|
|
"\tCalled from line %d(+%d bytes) of %s\n",
|
|
dbgLine.LineNumber,
|
|
dwTemp,
|
|
dbgLine.FileName);
|
|
}
|
|
|
|
LevelOneFrame = ((PULONG) LevelOneFrame)[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Post-allocation fence versions of malloc, calloc, realloc and free
|
|
*/
|
|
void *
|
|
HeapCheckPostFenceMalloc(
|
|
size_t blockSize)
|
|
{
|
|
DWORD dwSlot;
|
|
DWORD dwPages;
|
|
DWORD dwOldProtection;
|
|
PCHAR data = NULL;
|
|
BOOL bResult = FALSE;
|
|
LPVOID lpvResult = NULL;
|
|
static DWORD dwFirstTime = 1;
|
|
|
|
if (dwFirstTime) {
|
|
dwFirstTime = 0;
|
|
HeapCheckStartup();
|
|
}
|
|
|
|
if (!blockSize)
|
|
return NULL;
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
for(dwSlot = 0; dwSlot<MAX_ALLOCATIONS; dwSlot++)
|
|
if (allocations[dwSlot].isFree) {
|
|
allocations[dwSlot].isFree = 0;
|
|
break;
|
|
}
|
|
|
|
// If you end up in this assertion, stop debugging and re-compile
|
|
// with an increased value for the MAX_ALLOCATIONS constant.
|
|
_ASSERTE(dwSlot != MAX_ALLOCATIONS);
|
|
|
|
// Calculate number of requires pages
|
|
dwPages = ((blockSize - 1) / PAGE_SIZE) + 2;
|
|
|
|
//_RPT2(_CRT_WARN, "Requested %7d bytes, granted %2d pages\n", blockSize, dwPages);
|
|
|
|
// Make sure we have enough room
|
|
if (dwFreePages < dwPages) {
|
|
_RPT1(
|
|
_CRT_WARN,
|
|
"Your application requires more free memory...\n"
|
|
"Increase MAX_ALLOCATABLE_BLOCK in %s.", __FILE__);
|
|
_ASSERTE(dwFreePages >= dwPages);
|
|
}
|
|
|
|
data = (char*)FindConsecutiveFreePages(dwPages);
|
|
|
|
// OK, now make data-pages available...
|
|
lpvResult = VirtualAlloc(
|
|
(LPVOID) data,
|
|
(dwPages-1)*PAGE_SIZE,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE);
|
|
_ASSERTE(lpvResult != NULL );
|
|
|
|
VirtualProtect(
|
|
(LPVOID) data,
|
|
(dwPages-1)*PAGE_SIZE,
|
|
PAGE_READWRITE,
|
|
&dwOldProtection);
|
|
|
|
// and fence-page untouchable!
|
|
bResult = VirtualFree(
|
|
(LPVOID) (data + (dwPages-1)*PAGE_SIZE),
|
|
PAGE_SIZE,
|
|
MEM_DECOMMIT);
|
|
_ASSERTE(bResult == TRUE);
|
|
|
|
data += (PAGE_SIZE - (blockSize % PAGE_SIZE)) % PAGE_SIZE;
|
|
|
|
dwFreePages -= dwPages;
|
|
|
|
allocations[dwSlot].pData = data;
|
|
allocations[dwSlot].dwPages = dwPages;
|
|
allocations[dwSlot].dwLength = blockSize;
|
|
|
|
FillCallStack(allocations[dwSlot].EIPs);
|
|
|
|
// Cause problems to users who think malloc zeroes block memory...
|
|
FillMemory(data, blockSize, 0xCD);
|
|
|
|
dwTotalAllocated += blockSize;
|
|
if (dwTotalPeakMemory<dwTotalAllocated)
|
|
dwTotalPeakMemory = dwTotalAllocated;
|
|
|
|
LeaveCriticalSection(§ion);
|
|
|
|
return (void *)data;
|
|
}
|
|
|
|
void *
|
|
HeapCheckPostFenceCalloc(
|
|
size_t blockSize)
|
|
{
|
|
PVOID *data = (PVOID*) HeapCheckPostFenceMalloc(blockSize);
|
|
if (data)
|
|
FillMemory(data, blockSize, 0);
|
|
return data;
|
|
}
|
|
|
|
|
|
void *
|
|
HeapCheckPostFenceRealloc(
|
|
void *ptr,
|
|
size_t size)
|
|
{
|
|
void *tmpData;
|
|
DWORD dw;
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
for(dw=0; dw<MAX_ALLOCATIONS; dw++)
|
|
if(!allocations[dw].isFree)
|
|
if(allocations[dw].pData == ptr)
|
|
break;
|
|
|
|
if(dw == MAX_ALLOCATIONS) {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"### ERROR ### Attempt to realloc unallocated block!...\n");
|
|
PrintCaller();
|
|
_ASSERTE(dw != MAX_ALLOCATIONS);
|
|
}
|
|
|
|
LeaveCriticalSection(§ion);
|
|
|
|
tmpData = HeapCheckPostFenceMalloc(size);
|
|
if (!tmpData)
|
|
return NULL;
|
|
|
|
if (size < allocations[dw].dwLength)
|
|
CopyMemory(tmpData, allocations[dw].pData, size);
|
|
else
|
|
CopyMemory(tmpData, allocations[dw].pData, allocations[dw].dwLength);
|
|
|
|
HeapCheckPostFenceFree(ptr);
|
|
|
|
return tmpData;
|
|
}
|
|
|
|
void
|
|
HeapCheckPostFenceFree(
|
|
void *pData)
|
|
{
|
|
PCHAR pTmp = (PCHAR) pData;
|
|
DWORD dw;
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
for(dw=0; dw<MAX_ALLOCATIONS; dw++)
|
|
if(!allocations[dw].isFree)
|
|
if(allocations[dw].pData == pData)
|
|
break;
|
|
|
|
if(dw == MAX_ALLOCATIONS) {
|
|
// This is a block not allocated from us...
|
|
// Check first to see if it was allocated from
|
|
// the normal VC heap through any direct calls
|
|
// (brain-dead coding, that is)
|
|
|
|
if (_CrtIsValidHeapPointer(pData)) {
|
|
#ifndef NO_VC_HEAP_ERRS
|
|
char *origFileName;
|
|
DWORD origLineNo, origBlockSize;
|
|
|
|
if (_CrtIsMemoryBlock(
|
|
pData,
|
|
origBlockSize = GetSize(pData),
|
|
NULL,
|
|
&origFileName,
|
|
&origLineNo))
|
|
{
|
|
_RPT6(
|
|
_CRT_WARN,
|
|
"Freeing VC-heap allocated block (%d bytes from line %d of %s)\n",
|
|
origBlockSize, origLineNo, origFileName);
|
|
PrintCaller();
|
|
}
|
|
else {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"Freeing unknown VC-heap allocated block\n");
|
|
PrintCaller();
|
|
_CrtDbgBreak();
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"### ERROR ### Attempt to free unallocated block!... \n");
|
|
PrintCaller();
|
|
_CrtDbgBreak();
|
|
}
|
|
LeaveCriticalSection(§ion);
|
|
return;
|
|
}
|
|
|
|
// Make data pages inaccessible, since they are freed!
|
|
pTmp -= (((DWORD)pTmp) % PAGE_SIZE);
|
|
VirtualFree(
|
|
pTmp,
|
|
(allocations[dw].dwPages - 1)*PAGE_SIZE,
|
|
MEM_DECOMMIT);
|
|
|
|
// Set these pages to 'available' again.
|
|
ZeroMemory(
|
|
&pagesState[ (pTmp - pMemoryBase)/PAGE_SIZE ],
|
|
allocations[dw].dwPages);
|
|
dwFreePages += allocations[dw].dwPages;
|
|
|
|
dwTotalAllocated -= allocations[dw].dwLength;
|
|
|
|
allocations[dw].isFree = 1;
|
|
allocations[dw].pData = NULL;
|
|
allocations[dw].dwPages = 0;
|
|
allocations[dw].dwLength = 0;
|
|
allocations[dw].EIPs[0] = 0;
|
|
|
|
LeaveCriticalSection(§ion);
|
|
}
|
|
|
|
/*
|
|
* Pre-allocation fence versions of malloc, calloc, realloc and free
|
|
*
|
|
*/
|
|
|
|
void *
|
|
HeapCheckPreFenceMalloc(
|
|
size_t blockSize)
|
|
{
|
|
DWORD dwSlot;
|
|
DWORD dwPages, dwOldProtection;
|
|
PCHAR data = NULL;
|
|
BOOL bResult = FALSE;
|
|
LPVOID lpvResult = NULL;
|
|
static DWORD dwFirstTime = 1;
|
|
|
|
if (dwFirstTime) {
|
|
dwFirstTime = 0;
|
|
HeapCheckStartup();
|
|
}
|
|
|
|
if (!blockSize)
|
|
return NULL;
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
for(dwSlot = 0; dwSlot<MAX_ALLOCATIONS; dwSlot++)
|
|
if (allocations[dwSlot].isFree) {
|
|
allocations[dwSlot].isFree = 0;
|
|
break;
|
|
}
|
|
|
|
// If you end up in this assertion, stop debugging and re-compile
|
|
// with an increased value for the MAX_ALLOCATIONS constant.
|
|
_ASSERTE(dwSlot != MAX_ALLOCATIONS);
|
|
|
|
// Calculate number of requires pages
|
|
dwPages = ((blockSize - 1) / PAGE_SIZE) + 2;
|
|
|
|
//_RPT2(_CRT_WARN, "Requested %7d bytes, granted %2d pages\n", blockSize, dwPages);
|
|
|
|
// Make sure we have enough room
|
|
if (dwFreePages < dwPages) {
|
|
_RPT1(
|
|
_CRT_WARN,
|
|
"Your application requires more free memory...\n"
|
|
"Change MAX_ALLOCATABLE_BLOCK in %s.", __FILE__);
|
|
_ASSERTE(dwFreePages >= dwPages);
|
|
}
|
|
|
|
data = (char*) FindConsecutiveFreePages(dwPages);
|
|
|
|
// OK, now make fence-page untouchable...
|
|
bResult = VirtualFree(
|
|
(LPVOID) data,
|
|
PAGE_SIZE,
|
|
MEM_DECOMMIT);
|
|
_ASSERTE(bResult == TRUE);
|
|
|
|
// and data-pages available!
|
|
lpvResult = VirtualAlloc(
|
|
(LPVOID) (data+PAGE_SIZE),
|
|
(dwPages-1)*PAGE_SIZE,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE);
|
|
_ASSERTE(lpvResult != NULL );
|
|
|
|
VirtualProtect(
|
|
(LPVOID) (data+PAGE_SIZE),
|
|
(dwPages-1)*PAGE_SIZE,
|
|
PAGE_READWRITE,
|
|
&dwOldProtection);
|
|
|
|
data += PAGE_SIZE;
|
|
|
|
dwFreePages -= dwPages;
|
|
|
|
allocations[dwSlot].pData = data;
|
|
allocations[dwSlot].dwPages = dwPages;
|
|
allocations[dwSlot].dwLength = blockSize;
|
|
|
|
FillCallStack(allocations[dwSlot].EIPs);
|
|
|
|
// Cause problems to users who think malloc zeroes block memory...
|
|
FillMemory(data, blockSize, 0xCD);
|
|
|
|
dwTotalAllocated += blockSize;
|
|
if (dwTotalPeakMemory<dwTotalAllocated)
|
|
dwTotalPeakMemory = dwTotalAllocated;
|
|
|
|
LeaveCriticalSection(§ion);
|
|
|
|
return (void *)data;
|
|
}
|
|
|
|
void *
|
|
HeapCheckPreFenceCalloc(
|
|
size_t blockSize)
|
|
{
|
|
PVOID *data = (PVOID*) HeapCheckPreFenceMalloc(blockSize);
|
|
if (data)
|
|
FillMemory(data, blockSize, 0);
|
|
return data;
|
|
}
|
|
|
|
|
|
void *
|
|
HeapCheckPreFenceRealloc(
|
|
void *ptr,
|
|
size_t size)
|
|
{
|
|
void *tmpData;
|
|
DWORD dw;
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
for(dw=0; dw<MAX_ALLOCATIONS; dw++)
|
|
if(!allocations[dw].isFree)
|
|
if(allocations[dw].pData == ptr)
|
|
break;
|
|
if(dw == MAX_ALLOCATIONS) {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"### ERROR ### Attempt to realloc unallocated block!...\n");
|
|
PrintCaller();
|
|
_ASSERTE(dw != MAX_ALLOCATIONS);
|
|
}
|
|
|
|
LeaveCriticalSection(§ion);
|
|
|
|
tmpData = HeapCheckPreFenceMalloc(size);
|
|
if (!tmpData)
|
|
return NULL;
|
|
|
|
if (size < allocations[dw].dwLength)
|
|
CopyMemory(tmpData, allocations[dw].pData, size);
|
|
else
|
|
CopyMemory(tmpData, allocations[dw].pData, allocations[dw].dwLength);
|
|
|
|
HeapCheckPreFenceFree(ptr);
|
|
|
|
return tmpData;
|
|
}
|
|
|
|
void
|
|
HeapCheckPreFenceFree(
|
|
void *pData)
|
|
{
|
|
PCHAR pTmp;
|
|
DWORD dw;
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
for(dw=0; dw<MAX_ALLOCATIONS; dw++)
|
|
if(!allocations[dw].isFree)
|
|
if(allocations[dw].pData == pData)
|
|
break;
|
|
|
|
if(dw == MAX_ALLOCATIONS) {
|
|
// This is a block not allocated from us...
|
|
// Check first to see if it was allocated from
|
|
// the normal VC heap through any direct calls
|
|
// (brain-dead coding, that is)
|
|
|
|
if (_CrtIsValidHeapPointer(pData)) {
|
|
#ifndef NO_VC_HEAP_ERRS
|
|
char *origFileName;
|
|
DWORD origLineNo, origBlockSize;
|
|
|
|
if (_CrtIsMemoryBlock(
|
|
pData,
|
|
origBlockSize = GetSize(pData),
|
|
NULL,
|
|
&origFileName,
|
|
&origLineNo))
|
|
{
|
|
_RPT6(
|
|
_CRT_WARN,
|
|
"Freeing VC-heap allocated block (%d bytes from line %d of %s)\n",
|
|
origBlockSize, origLineNo, origFileName);
|
|
PrintCaller();
|
|
}
|
|
else {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"Freeing unknown VC-heap allocated block\n");
|
|
PrintCaller();
|
|
_CrtDbgBreak();
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"### ERROR ### Attempt to free unallocated block!... \n");
|
|
PrintCaller();
|
|
_CrtDbgBreak();
|
|
}
|
|
LeaveCriticalSection(§ion);
|
|
|
|
return;
|
|
}
|
|
|
|
// Make data pages inaccessible, since they are freed!
|
|
VirtualFree(
|
|
pData,
|
|
(allocations[dw].dwPages - 1)*PAGE_SIZE,
|
|
MEM_DECOMMIT);
|
|
|
|
// Set these pages to 'available' again.
|
|
pTmp = (PCHAR) pData;
|
|
pTmp -= PAGE_SIZE;
|
|
ZeroMemory(
|
|
&pagesState[ (pTmp - pMemoryBase)/PAGE_SIZE ],
|
|
allocations[dw].dwPages);
|
|
dwFreePages += allocations[dw].dwPages;
|
|
|
|
dwTotalAllocated -= allocations[dw].dwLength;
|
|
|
|
allocations[dw].isFree = 1;
|
|
allocations[dw].pData = NULL;
|
|
allocations[dw].dwPages = 0;
|
|
allocations[dw].dwLength = 0;
|
|
allocations[dw].EIPs[0] = 0;
|
|
|
|
LeaveCriticalSection(§ion);
|
|
}
|
|
|
|
|
|
/*
|
|
* Startup and shutdown functions
|
|
*/
|
|
|
|
static void
|
|
HeapCheckStartup()
|
|
{
|
|
SYSTEM_INFO si;
|
|
DWORD dw;
|
|
CHAR dbgPath[MAX_PATH], *pEnd;
|
|
|
|
SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES );
|
|
|
|
// Try to read the symbols from the path of the .exe file
|
|
if (!GetModuleFileName(
|
|
NULL,
|
|
dbgPath,
|
|
sizeof(dbgPath))) {
|
|
|
|
// if we failed, do your best
|
|
if(!SymInitialize(GetCurrentProcess(), NULL, TRUE)) {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"HEAPCHECK: Won't be able to read file/line information...:(\n");
|
|
}
|
|
} else {
|
|
pEnd = strrchr(dbgPath, '\\');
|
|
if (!pEnd) {
|
|
// if we failed, do your best
|
|
if(!SymInitialize(GetCurrentProcess(), NULL, TRUE)) {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"HEAPCHECK: Won't be able to read file/line information...:(\n");
|
|
}
|
|
} else {
|
|
// 99% probability of success with file/line info...!
|
|
*pEnd = 0;
|
|
if(!SymInitialize(GetCurrentProcess(), dbgPath, TRUE)) {
|
|
_RPT0(
|
|
_CRT_WARN,
|
|
"HEAPCHECK: Won't be able to read file/line information...:(\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
InitializeCriticalSection(§ion);
|
|
|
|
GetSystemInfo(&si);
|
|
|
|
_ASSERTE(si.dwPageSize == PAGE_SIZE);
|
|
dwAllocationGranularity = si.dwAllocationGranularity;
|
|
|
|
_ASSERTE(dwAllocationGranularity <= MAX_ALLOCATABLE_BLOCK);
|
|
|
|
pMemoryBase = (PCHAR) VirtualAlloc(
|
|
NULL, // Place memory base wherever
|
|
MAX_ALLOCATABLE_BLOCK, // Total heap memory available
|
|
MEM_RESERVE, // For now, just reserve it
|
|
PAGE_NOACCESS); // and make it no-touch.
|
|
|
|
_ASSERTE(pMemoryBase != NULL);
|
|
|
|
dwTotalPages = MAX_ALLOCATABLE_BLOCK / PAGE_SIZE;
|
|
dwFreePages = dwTotalPages;
|
|
|
|
if(atexit(HeapCheckShutDown)) {
|
|
_RPT0(_CRT_WARN, "### WARNING ### Can't check for memory leaks automatically!\n");
|
|
_RPT0(_CRT_WARN, "### WARNING ### Call HeapCheckShutDown at the end of your app,\n");
|
|
}
|
|
|
|
for(dw=0; dw<MAX_ALLOCATIONS; dw++)
|
|
allocations[dw].isFree = 1;
|
|
|
|
ZeroMemory(pagesState, sizeof(pagesState));
|
|
}
|
|
|
|
static void
|
|
HeapCheckShutDown()
|
|
{
|
|
BOOL bSuccess;
|
|
DWORD dw;
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
|
|
EnterCriticalSection(§ion);
|
|
|
|
_RPT0(_CRT_WARN, "\n##################################\n");
|
|
_RPT0(_CRT_WARN, "####### HeapCheck report ######\n");
|
|
_RPT0(_CRT_WARN, "##################################\n\n");
|
|
|
|
for(dw=0; dw<MAX_ALLOCATIONS; dw++) {
|
|
if (!allocations[dw].isFree) {
|
|
|
|
// We have to use IMAGEHLP.DLL. God help us...
|
|
IMAGEHLP_LINE dbgLine;
|
|
DWORD dwTemp, dwCodePlaces = 0;
|
|
|
|
_RPT1(
|
|
_CRT_WARN,
|
|
"### WARNING ### Memory leak (%d bytes) found... Allocated:\n",
|
|
allocations[dw].dwLength);
|
|
|
|
while(allocations[dw].EIPs[dwCodePlaces]) {
|
|
|
|
ZeroMemory(&dbgLine, sizeof(IMAGEHLP_LINE));
|
|
dbgLine.SizeOfStruct = sizeof(IMAGEHLP_LINE);
|
|
if(!SymGetLineFromAddr(
|
|
hProcess,
|
|
allocations[dw].EIPs[dwCodePlaces],
|
|
&dwTemp,
|
|
&dbgLine))
|
|
{
|
|
#ifdef PRINT_NUMERIC_EIP
|
|
_RPT1(
|
|
_CRT_WARN,
|
|
"\tfrom EIP = %x\n",
|
|
allocations[dw].EIPs[dwCodePlaces]);
|
|
#endif
|
|
} else {
|
|
_RPT3(
|
|
_CRT_WARN,
|
|
"\tfrom line %d(+%d bytes) of %s\n",
|
|
dbgLine.LineNumber,
|
|
dwTemp,
|
|
dbgLine.FileName);
|
|
}
|
|
dwCodePlaces++;
|
|
|
|
// If you land here after an assertion, stop debugging
|
|
// and increase MAX_STACK_DEPTH
|
|
_ASSERTE(dwCodePlaces < MAX_STACK_DEPTH);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Decommit the entire block. */
|
|
bSuccess = VirtualFree(
|
|
pMemoryBase, /* base address of block */
|
|
MAX_ALLOCATABLE_BLOCK, /* bytes of committed pages */
|
|
MEM_DECOMMIT); /* decommit the pages */
|
|
_ASSERTE(bSuccess);
|
|
|
|
/* Release the entire block. */
|
|
if (bSuccess)
|
|
bSuccess = VirtualFree(
|
|
pMemoryBase, /* base address of block */
|
|
0, /* releases the entire block */
|
|
MEM_RELEASE); /* releases the pages */
|
|
_ASSERTE(bSuccess);
|
|
|
|
_RPT1(_CRT_WARN, "HeapCheck Statistics:\n\tMaximum memory allocated = %d\n\n", dwTotalPeakMemory);
|
|
|
|
SymCleanup(hProcess);
|
|
|
|
LeaveCriticalSection(§ion);
|
|
DeleteCriticalSection(§ion);
|
|
}
|
|
|
|
#endif
|