// // _NEW.CPP // // Source file for ArchiveLib 1.0 // // Copyright (c) Greenleaf Software, Inc. 1994 // All Rights Reserved // // CONTENTS // // PointerInHeap() // operator new() // operator delete() // // DESCRIPTION // // One of the defensive programming measures we have taken in this // library is to provide versions of ::new and ::delete that do a // little extra work. Of course, these all go away if _DEBUG isn't // defined. // // Basically, there are a couple of things at work here. First, we // have taken over the new operator and delete operator, so we are // guaranteed that all C++ memory allocation will take place through // these routines. Therefore, we can take certain liberties with // them. // // The most important thing we do when someone wants to allocate // memory is to allocate an extra 12 bytes of data beyond what has // been requested. We use 8 bytes at the start of the block and // 4 bytes at the end of the block for our own purposes. The pointer // we return to the requester is actually at the start of the block // plus eight bytes. // // The first four bytes at the start of the block are used to store // the size of the block. The next four bytes hold a long word containing // a leading picket, which is just a special word four bytes long. // If the user underwrites the block of data for some reason, one of // those four bytes will probably be corrupted. At the end of the block, // we store a trailing picket that has the same purpose. It holds // a special pattern of four bytes. If the user overwrites the block // of data, those four bytes will be corrupted. // // We check the pickets when the ::delete operator is called. That way, // when an object is going to be freed, we can instantly detect if it // has been abused in some fashion. // // The ::delete operator here also attempts to make sure that the pointer // being deleted points to a block that is actually in the heap. This // isn't always possible, but it works under most MS-DOS models, and // works under Windows small and medium models. Under Windows large // memory models, we can walk the global heap to look for pointers, // but we might not find them, since the RTL might be using a subsegment // allocation scheme. // // In addition to checking the heap, under Windows the ::delete function // can also call the IsBadWritePtr() function to see if this is just // a completely hosed up pointer. // // Note that it is kind of obtrusive to redefine ::new and ::delete. There // is an excellent chance that this will interfere with other libraries, // such as MFC. Fortunately, we have made it easy to get around this. // First, it is relatively simple to just delete this module from your // library, using: LIB ALXX-_NEW; If you don't want to go to that // trouble, you can also define AL_DISABLE_NEW and rebuild this module, // which should also make it go away. But if you don't need to make // this code disappear, you ought to leave it in, it might save you a lot // of trouble some day. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // // #include "arclib.h" #pragma hdrstop #include #include // // The MS-DOS heapwalk functions are in different header files depending // on who you are. Note that I don't walk the heap under MS-DOS with // Symantec or Watcom. Not sure if I can. // #if defined( AL_BORLAND ) #include #elif defined( AL_MICROSOFT ) #include #endif // // Walking the Windows heap requires TOOLHELP.DLL. It would be great // if Borland provided the TOOLHELP API under their DPMI extenders, but // I don't think they do. I don't think the heap walk functions are // available under Win 32s either. // #if defined( AL_WINDOWS_GUI ) && !defined( AL_FLAT_MODEL ) #include #endif /* * To completely eliminate this stuff, all you have to do is define * AL_DISABLE_NEW before rebuilding the library. */ #ifndef AL_DISABLE_NEW // // When I pop up an error message, it sometimes helps to know where it came // from. This definition is used to create the message box. // #if defined( AL_BUILDING_DLL ) #define LIB_TYPE "DLL" #else #define LIB_TYPE "Static" #endif // // If Debug is not turned on, none of this stuff happens. I also don't // work with Microsoft huge model, things get nasty in there. // #if defined( _DEBUG ) && !( defined( AL_MICROSOFT ) && defined( _M_I86HM ) ) // // int PointerInHeap( void *p ) // // ARGUMENTS: // // p : The pointer under test. // // RETURNS // // An integer, true or false. // // DESCRIPTION // // This function is called by ::delete() to see if the pointer we are // trying to delete is in fact in the heap. If it isn't, we could cause // quite a bit of trouble if we try to delete it. // // Under MS-DOS, this function just executes the normal heapwalk functions // supported by Microsoft and Borland. Under Windows small memory // models, we use the Toohelp API to walk the local heap. Under all // other circumstances, we just give up and always return a true value. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // // // This is the Microsoft MS-DOS version. It also looks like it is set // up to work with Win 32s, but I'm not sure why, since we haven't // released support for it yet. I think that is a typo. // // This function just uses the heapwalk RTL function to check for the // presence of the pointer in the heap. // #if defined( AL_MICROSOFT ) && ( !defined( AL_WINDOWS_MEMORY ) || defined( AL_FLAT_MODEL ) ) int PointerInHeap( void *p ) { AL_ASSERT( _heapchk() == _HEAPOK, "Heap fails internal consistency check" ); _HEAPINFO heapinfo; heapinfo._pentry = 0; while ( _heapwalk( &heapinfo ) == _HEAPOK ) if ( heapinfo._pentry == (int __far *) p ) return 1; return 0; } // // This is the Borland MS-DOS version. It looks like it also works under // Win 32s, which might be more reasonable, since we do support Borland // in that mode. // // Like the previous function, this guy just uses the heapwalk API to // check the local heap for the presence of the pointer. // #elif defined( AL_BORLAND ) && ( !defined( AL_WINDOWS_MEMORY ) || defined( AL_FLAT_MODEL ) ) int PointerInHeap( void *p ) { AL_ASSERT( heapcheck() == _HEAPOK, LIB_TYPE " heap fails internal consistency check" ); struct heapinfo info; info.ptr = 0; while ( heapwalk( &info ) == _HEAPOK ) #if defined( AL_LARGE_DATA ) && !defined( AL_FLAT_MODEL ) if ( info.ptr == (void huge *) p ) return 1; #else if ( info.ptr == p ) return 1; #endif return 0; } // // Microsoft is nice enough to support the heapwalk API under Windows // large memory models also. This is good, since the TOOLHELP API would // flounder when confronted with a subsegment allocation strategy. // #elif defined( AL_MICROSOFT ) && defined( AL_WINDOWS_MEMORY ) && defined( AL_LARGE_DATA ) int PointerInHeap( void *p ) { AL_ASSERT( _fheapchk() == _HEAPOK, LIB_TYPE " heap fails internal consistency check" ); _HEAPINFO heapinfo; heapinfo._pentry = 0; while ( _fheapwalk( &heapinfo ) == _HEAPOK ) if ( heapinfo._pentry == p ) return 1; return 0; } // // Under Windows small and medium memory models, the TOOLHELP API lets // us walk the local heap, looking for an entry. No subsegment allocation // scheme will get in the way. // // I should be able to use this with Watcom, but I am using a little bit of // inline assembly to get my data segment. This inline assembly won't // work with Watcom, so someday I will have to add a little code to // get things working right with them also. // #elif defined( AL_WINDOWS_MEMORY ) && !defined( AL_FLAT_MODEL ) && !defined( AL_LARGE_DATA ) && !defined( AL_WATCOM ) int PointerInHeap( void *p ) { LOCALENTRY LEntry; WORD wHeap; // // I need to search the local heap that is in my data segment. // _asm mov ax,ds _asm mov wHeap,ax LEntry.dwSize = sizeof( LOCALENTRY ); if ( LocalFirst( &LEntry, (HGLOBAL) wHeap ) ) { do { if ( LEntry.wAddress == (WORD) p ) return 1; } while ( LocalNext( &LEntry ) ); } return 0; } // // When all else fails, give up! // #else int PointerInHeap( void * ){ return 1; } #endif // // void *operator new( size_t size ) // // ARGUMENTS: // // size : The amount of memory being requested. // // RETURNS // // A pointer to the newly allocated storage area, or a 0 in the event // of failure. // // DESCRIPTION // // This version of ::new() does what I described at the top of the file. // It allocates a block of memory as requested, and includes eight // extra bytes. Four bytes are reserved at the start and end of the memory // block for our "pickets". These pickets hold a fixed pattern in memory // that can be tested for accidental modification. When ::delete() is // called, we check the area to see if the caller munged it, and // cause an assertion error if they did. The other four bytes are needed // to keep the size of the block on hand. Otherwise I wouldn't know how // to get to the end of the block to check the trailing picket. // // Note that if you are using set_new_handler() or exceptions, this stuff // is probably going to hose you up badly. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // void *operator new( size_t size ) { if ( ( (long) size + 12 ) > 65535L ) return 0; char *p = (char *) malloc( size + 12 ); if ( !p ) return 0; ( (long *) p)[ 0 ] = (long) size; ( (long *) p)[ 1 ] = 0x12345678L; ( (long *)(p + 8 + size))[ 0 ] = 0xfedcba98L; return p + 8; } // // void operator delete( void *ptr ) // // ARGUMENTS: // // ptr : A pointer to the memory block the user wishes to delete. // // RETURNS // // Nothing. // // DESCRIPTION // // After the user has had the chance to muck with this memory block for // a while, he or she will want to return it to the heap. We do a bunch // of checks here before that happens, to see if any serious mistakes have // been made. If we detect any serious mistakes, we just abort the // program with an assertion error. // // First we check to see if Windows thinks it is even a valid pointer. If // we don't do this, some of the other code here will GPF if you call delete // with a really bad pointer. Those GPFs are a lot less informative than // our nice assertion failures. // // If it looks like it is a valid pointer, the next thing we do is try to // see if the pointer is in our heap. A common mistake is trying to free // a pointer twice, or freeing a pointer that has been incremented or // decremented. Either of these can royally foul the heap. // // If it looks like the pointer really is in the heap, there is still one // last thing to check. I take a quick glance at both the leading and // trailing pickets to see if either of them have been mangled. A simple // overwrite or underwrite by just one byte can be catastrophic, but we // detect it easily here. // // If all of that goes as expected, we are free to finally return the // storage to the heap. Just for good luck, I clear it out first. That // way if anyone is foolish enough to try and use the data after it has // been deleted, they will at least see that there is nothing intelligent // store there. // // REVISION HISTORY // // May 22, 1994 1.0A : First release // void operator delete( void *ptr ) { #if defined( AL_WINDOWS_MEMORY ) && !defined( AL_FLAT_MODEL ) AL_ASSERT( !IsBadWritePtr( ptr, 1 ), "delete: delete called for ptr Windows doesn't like" ); #endif char *p = (char *) ptr; AL_ASSERT( PointerInHeap( p - 8 ), "delete: delete called for pointer not found in the " LIB_TYPE " heap" ); AL_ASSERT( ( (long *) p )[ -1 ] == 0x12345678L, "delete : Data corrupted in object's leading picket in the " LIB_TYPE " heap" ); size_t size = (size_t) ( (long *) p )[ -2 ]; char *ep = p + size; AL_ASSERT( ( (long *) ep )[ 0 ] == 0xfedcba98L, "delete : Data corrupted in object's trailing picket in the " LIB_TYPE " heap" ); memset( p - 8, size + 12, 0 ); //Clear it before freeing it free( ((char *) p - 8 ) ); } #endif //#ifdef _DEBUG etc. #endif // #ifdef AL_DISABLE_NEW