// Memwatch memory leak/overwrite/underwrite/double-free // detector. // // Modified for use with Visual C++ by Stephen Edwards, // 1/24/97. Please report problems under Visual C++ to // edwards@cs.vt.edu. // // To use: // + Add memwatch.cpp to your C++ project. // + Add '#include "Memwatch.h"' to all .cpp files. // + Examine the log file "memwatch.log" in the // current directory after running the program. // #include "Memwatch.h" #ifdef _MSC_VER # include # include # include # define MAXPATH 260 #else # include # include # include #endif #include #include #include //*******************************************************// // MemWatch // //*******************************************************// // Copyright © 1995 Terry Richards Software. All rights // // reserved. You may use this code free of charge except // // that you may not sell it. If you distribute this file // // please do so without modification. // //*******************************************************// // If you feel this code is of some value please locate // // somebody that needs it and give that amount to them. // // I would appreciate it if you would let me know what // // you did. // //*******************************************************// // MemWatch is a simple but suprisingly powerful memory // // monitor. It can detect the following memory errors: // // 1) new without delete. // // 2) multiple deletes of the same block. // // 3) corrupted pointers. // // 4) memory overwrites. // // 5) memory underwrites. // // A comprehensive report of any errors detected is // // written to MEMWATCH.LOG // //*******************************************************// // To use MemWatch simply add this file to your project // // and recompile. After testing you should remove it as // // it disables the XAlloc exception and it has an impact // // on performance. // // This code is most useful in a 32 bit program but also // // works and provides useful data in a 16 bit EXE. // //*******************************************************// // Version 1 released 7/13/95 - Initial release // //*******************************************************// // Terry Richards // // Terry Richards Software // // 58A Phelps Ave. // // New Brunswick NJ 08901 // // USA // // (908) 545-6533 // // CIS: 72330,1026 // //*******************************************************// //By default, this code reports all NULL deletes. If you have //a lot of these this can become annoying. You can turn off //these reports by commenting out the next line. #define REPORT_NULLS //Number of memory blocks that can be open. //There will be a message in MEMWATCH.LOG if //this is too small. As a rough guide, 400 is //enough for a trivial AppExpert program but //a program with a lot of memory activity can //use as many as 10,000 entries. #define NUM_ENTS 5000 //The safety character. The safety areas are filled with this //character so we can check them for memory over/underwrites #define SAFETY_CHAR '!' //The size of the safety areas. //The bigger you make these, the better your chances //of catching a memory over/under-run. However, //HI_SAFETY_SIZE + LO_SAFETY_SIZE bytes are added //to each memory allocation. // ***** IMPORTANT ***** //If you link with the dynamic libraries of if you have //any classes that have their own operator delete (or //delete[]) without a corresponding operator new (or //new[]) and that operator doesn't call ::delete //(or ::delete[]) then you should set LO_SAFETY_SIZE to //zero. If you start getting strange crashes on deletes //this is probably the problem. #define HI_SAFETY_SIZE 50 #define LO_SAFETY_SIZE 50 //This is so important we should validate it. However, there is a BC "feechur" //that lets this symbol be visible to all targets in a multi-target project even //if this particular target is being linked to the static libs. #ifdef _RTLDLL //If any Borland DLL is being used then this one must be #if LO_SAFETY_SIZE > 0 #pragma message Warning! Run-Time DLLs used with LO_SAFETY_SIZE > 0 #endif #endif //The max depth of the call stack to save (and print) //Currently this is only used for 32 bit code. //It does no harm to reduce this but you may not see the //full call stack on some calls. #define CALL_STACK_DEPTH 30 //The database. Information is stored here about //each memory allocation. This could be some sort of //growable array but that involves memory allocation //which would surely confuse the issue! To prevent //recursive problems we pre-allocate enough space for //NUM_ENTS entries. #ifdef __WIN32__ typedef struct{ int Depth; void * Address[CALL_STACK_DEPTH]; } CallStack; #endif //__WIN32__ typedef struct{ void * Address; size_t Size; #ifdef __WIN32__ CallStack From; #endif #if defined(_MSC_VER) && defined(_DEBUG) const char* fname; int lnum; #endif } MemControl; MemControl DB[NUM_ENTS]; BOOL DB_Blown=FALSE; //Global variables are used because we can't change //the parms or return types for new & delete. int MaxUsed,NumNew,NumDeletes,NumNullDeletes,NewCalled; char LogFileName[MAXPATH+1]; //This flag is used to prevent recursion. We set it whenever we are creating //or destroying an ofstream object. BOOL InternalCall=FALSE; //SaveCallStack. This routine can detect where a subroutine was //called from using the EBP register to find the calling routine's //stack and the peeking at the return address 4 bytes from //the top of that stack. Once it finds the return address //it subtracts 5 bytes because a 32-bit call instruction //is 5 bytes long. //Actually, this is not entirely true, some are 3 bytes and some are //2 bytes. Most are 5 bytes though and the calculated address is always //within 3 bytes of the correct statement. #ifdef __WIN32__ void SaveCallStack(void ** EBP,CallStack& TheStack){ //EBP points to the old stack void ** OldStack = EBP; //The calling routine's EBP is on the top of that stack void * NewEBP = *OldStack; //The return address is at old stack + 4 bytes (long *)OldStack ++; void * RetAddr = *OldStack; //Decrement address by 5 bytes for the call instruction (char *)RetAddr -= 5; if (LOWORD(RetAddr) == 0xfffb) return; TheStack.Address[TheStack.Depth] = RetAddr; TheStack.Depth++; //Keep tracing the call stack if (NewEBP && TheStack.Depth < CALL_STACK_DEPTH) SaveCallStack((void **) NewEBP,TheStack); } //This routine formats & prints the calling address stack saved above. void PrintCallStack(ofstream& os,CallStack& TheStack){ os << "Call Stack:\n"; for (int i=0;i MaxUsed) MaxUsed = i; NumNew++; //Store what we know about the memory block DB[i].Address = TheMem; DB[i].Size = Size; #ifdef __WIN32__ DB[i].From.Depth = 0; SaveCallStack((void **)_EBP,DB[i].From); #endif //__WIN32__ #if defined(_MSC_VER) && defined(_DEBUG) DB[i].fname = fname; DB[i].lnum = lnum; #endif // _MSC_VER //and hand it back. return TheMem; } #if __BORLANDC__ >= 0x400 //The new[] operator was implemented in BC 4.0 //We just pass it through to the regular new operator. void * operator new[] (size_t Size){ return operator new(Size); } #endif //BC >= 0x400 //Operator delete. We can check that the pointer was //previously created by new and that it has not already been //deleted. We can also check if the safety pools have been //stepped on. void operator delete (void * TheMem){ char HiTestString[HI_SAFETY_SIZE]; #if LO_SAFETY_SIZE > 0 char LoTestString[LO_SAFETY_SIZE]; #else char LoTestString[1]; #endif void * RealMem=(char *)TheMem - LO_SAFETY_SIZE; ofstream* os; BOOL UnderWrite=FALSE; BOOL OverWrite=FALSE; //Memory we allocated ourselves is not watched if (InternalCall){ if (TheMem) free(TheMem); return; } if (TheMem) NumDeletes++; else NumNullDeletes++; if (!TheMem){ #ifdef REPORT_NULLS InternalCall = TRUE; os = new ofstream(LogFileName,ios::app); //It is always valid to delete a null pointer //but you can set a breakpoint here to detect it. *os << "\n****************** WARNING ******************\n" << "NULL pointer deleted.\n" << "This is valid but suspicious\n"; #ifdef __WIN32__ CallStack TheStack; TheStack.Depth = 0; SaveCallStack((void **)_EBP,TheStack); *os << "It was deleted at:\n"; PrintCallStack(*os,TheStack); #endif //__WIN32__ delete os; InternalCall = FALSE; #endif //REPORT_NULLS return; } if (DB_Blown) return; //Is this pointer in our database? for (int i=0;i < NUM_ENTS && DB[i].Address != TheMem;i++); if (i == NUM_ENTS){ //No. This is not good. InternalCall = TRUE; os = new ofstream(LogFileName,ios::app); //Set a breakpoint on the next statement //If it is reached you are freeing something //that was not allocated or you are freeing //something twice. *os << "\n******************* ERROR *******************\n" << "Unknown pointer " << TheMem << " deleted.\n" << "Either the pointer is corrupt or was deleted \n" << "multiple times. \n"; #ifdef __WIN32__ CallStack TheStack; TheStack.Depth = 0; SaveCallStack((void **)_EBP,TheStack); *os << "It was deleted at:\n"; PrintCallStack(*os,TheStack); #endif //__WIN32__ delete os; InternalCall = FALSE; free(RealMem); return; } //Check the safety areas to see if they are the same as when //we handed the memory out. memset(HiTestString,SAFETY_CHAR,HI_SAFETY_SIZE); memset(LoTestString,SAFETY_CHAR,LO_SAFETY_SIZE); if (memcmp((char *)RealMem,LoTestString,LO_SAFETY_SIZE)) UnderWrite = TRUE; if (memcmp(((char *)TheMem)+DB[i].Size,HiTestString,HI_SAFETY_SIZE)) OverWrite = TRUE; if (OverWrite || UnderWrite){ //It's different - report the fact. InternalCall = TRUE; os = new ofstream(LogFileName,ios::app); //Set a breakpoint on the next statement //If it is reached the memory block has been //corrupted. *os << "\n****************** WARNING ******************\n" << "The memory block at " << TheMem << " is corrupt.\n"; if (UnderWrite) *os << "The low safety area has been changed.\n"; if (OverWrite) *os << "The high safety area has been changed.\n"; #ifdef __WIN32__ *os << "It was created at: " << "\n"; PrintCallStack(*os,DB[i].From); CallStack TheStack; TheStack.Depth = 0; SaveCallStack((void **)_EBP,TheStack); *os << "It was deleted at:\n"; PrintCallStack(*os,TheStack); #endif //__WIN32__ #if defined(_MSC_VER) && defined(_DEBUG) *os << "It was created at: " << DB[i].fname << ", line " << DB[i].lnum << "." << endl; #endif delete os; InternalCall = FALSE; } //We found the pointer so remove it from the database. DB[i].Address = 0; free(RealMem); } #if __BORLANDC__ >= 0x400 //See the comment for operator new[] void operator delete[] (void * TheMem){ //pass through to delete. delete TheMem; } #endif //BC >= 4.0