/* * Seven Kingdoms: Ancient Adversaries * * Copyright 1997,1998 Enlight Software Ltd. * * 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, see . * */ //Filename : OMEM.CPP //Description : Object Memory Management (Debug Version) #include #include #ifndef NO_MEM_CLASS #include #include //--------- Define Constants -----------// // We will write the PRE_CHK_VAL before every allocated memory block // and POST_CHK_VAL after them, so when freeing them, we could // know whether they have been underrun/overun or not #define CHK_VAL_SIZE sizeof(long) #define PRE_CHK_VAL 0x12345678 // value to detect underrun #define POST_CHK_VAL 0x87654321 // value to detect overrun // The followings should are selected to give maximum probability that // pointers loaded with these values will cause an obvious crash. // MALLOCVAL is the value to set malloc'd data to. #define BAD_VAL 0xFF // set to this value for freed memory (memory that we no longer occupy) #define ALLOC_VAL 0xEE // set to this value for memory just allocated //---------- define constant and structure --------// #define SPOOL_MEM 50 // 50 bytes spool memory for mem_add(), struct MemInfo { void *ptr; // this pointer directly point to useable buffer unsigned size; // bypassing the PRE_CHK_VAL char *file_name; int file_line; }; //-------- BEGIN OF FUNCTION Mem::Mem ------------// Mem::Mem() { info_array = (MemInfo *)malloc(sizeof(MemInfo) * 100); if ( info_array == NULL ) err.mem(); ptr_num = 100 ; ptr_used = 0; } //---------- END OF FUNCTION Mem::Mem ------------// //--------- BEGIN OF FUNCTION Mem::add --------------// // // memNum = the size of the memory to be allocated // fileName = file from which the client function calls // fileLine = line number of the client function in the file // char* Mem::add(unsigned memSize, char* fileName, int fileLine) { // ###### begin Gilbert 29/8 ######// //err_when( memSize > 1000000 ); //**BUGHERE, for temporary debugging only err_when( memSize > 0x800000 ); // ###### end Gilbert 29/8 ######// //----------- build up memory pointer table ---------// if ( ptr_used == ptr_num ) { ptr_num += 100 ; if ( ptr_num > 10000 ) err.run( " Mem::add() - Too many pointers " ); info_array = (MemInfo*) realloc( info_array, sizeof(MemInfo) * ptr_num ) ; if ( info_array == NULL ) err.mem(); } //----------- actually allocate memory -------------// char *allocPtr; allocPtr = (char *)malloc(sizeof(char)*( memSize + CHK_VAL_SIZE*2 )); // Pre-check & Post-check if ( allocPtr == NULL ) { err.mem(); return NULL; } else { // set check value before and after the allocated block, // so Mem::del() can use these check value to detect // underrun && overrun *((long*)allocPtr) = PRE_CHK_VAL; *((long*)(allocPtr+CHK_VAL_SIZE+memSize)) = POST_CHK_VAL; // fill the allocated block with a value, which may // have chance to reveal some hiden bugs memset( allocPtr+CHK_VAL_SIZE, ALLOC_VAL, memSize ); info_array[ptr_used].ptr = allocPtr+CHK_VAL_SIZE; info_array[ptr_used].size = memSize; info_array[ptr_used].file_name = fileName; info_array[ptr_used].file_line = fileLine; ptr_used++; return allocPtr+CHK_VAL_SIZE; } } //---------- END OF FUNCTION Mem::add ---------------// //--------- BEGIN OF FUNCTION Mem::add_clear --------------// // // Allocate the memory and set all the memory content to byte 0. // // memNum = the size of the memory to be allocated // fileName = file from which the client function calls // fileLine = line number of the client function in the file // char* Mem::add_clear(unsigned memSize, char* fileName, int fileLine) { err_when( memSize > 1000000 ); //**BUGHERE, for temporary debugging only //----------- build up memory pointer table ---------// if ( ptr_used == ptr_num ) { ptr_num += 100 ; if ( ptr_num > 10000 ) err.run( " Mem::add_clear() - Too many pointers " ); info_array = (MemInfo*) realloc( info_array, sizeof(MemInfo) * ptr_num ) ; if ( info_array == NULL ) err.mem(); } //----------- actually allocate memory -------------// char *allocPtr; allocPtr = (char*)malloc(sizeof(char)*( memSize + CHK_VAL_SIZE*2 )); // Pre-check & Post-check if ( allocPtr == NULL ) { err.mem(); return NULL; } else { // set check value before and after the allocated block, // so Mem::del() can use these check value to detect // underrun && overrun *((long*)allocPtr) = PRE_CHK_VAL; *((long*)(allocPtr+CHK_VAL_SIZE+memSize)) = POST_CHK_VAL; // fill the allocated block with a value, which may // have chance to reveal some hiden bugs memset( allocPtr+CHK_VAL_SIZE, 0, memSize ); info_array[ptr_used].ptr = allocPtr+CHK_VAL_SIZE; info_array[ptr_used].size = memSize; info_array[ptr_used].file_name = fileName; info_array[ptr_used].file_line = fileLine; ptr_used++; return allocPtr+CHK_VAL_SIZE; } } //---------- END OF FUNCTION Mem::add_clear ---------------// //-------- BEGIN OF FUNCTION Mem::resize_keep_data ----------// // // The Mem::resize() and realloc() may not function properly in // some case when the memory block has a considerable size. // // Calling this function resize_keep_data will do additional effort // to preserve the original data. // // orgPtr = the original memory data pointer // orgSize = the original data size // newSize = new size of memory required // fileName = file from which the client function calls // fileLine = line number of the client function in the file // // Returns : NULL - not enough memory // - pointer to the allocated memory // char* Mem::resize_keep_data(void *orgPtr, unsigned orgSize, unsigned newSize, char* fileName, int fileLine) { if( orgPtr == NULL ) return add( newSize, fileName, fileLine); if( newSize <= orgSize ) return resize(orgPtr, newSize, fileName, fileLine); //-------- save the original data first ------// char* saveBuf = (char*)malloc(sizeof(char)*(orgSize)); memcpy( saveBuf, orgPtr, orgSize ); //------ reallocate the memory --------// char* newPtr = resize(orgPtr, newSize, fileName, fileLine); //----- store the original data to the new buf -------// // // if the new pointer is the same as the orginal pointer // the original data should be kept without any change // //----------------------------------------------------// if( newPtr != orgPtr ) memcpy( newPtr, saveBuf, orgSize ); free(saveBuf); return newPtr; } //----------- END OF FUNCTION Mem::resize_keep_data ---------------// //-------- BEGIN OF FUNCTION Mem::resize ----------// // // orgPtr = the original memory data pointer // memSize = new size of memory required // fileName = file from which the client function calls // fileLine = line number of the client function in the file // // Returns : NULL - not enough memory // - pointer to the allocated memory // // Note : resize() must actually call realloc(), it can't call mem() // and add(), because some clients want to keep the content on the // existing buffer. (e.g. DynArray) // char* Mem::resize(void *orgPtr, unsigned memSize, char* fileName, int fileLine) { err_when( memSize > 1000000 ); //**BUGHERE, for temporary debugging only if( orgPtr == NULL ) return add( memSize, fileName, fileLine); //-------------------------------------------// char *newPtr; int i; for( i=ptr_used-1; i>=0; i-- ) { if( info_array[i].ptr == orgPtr ) { if( info_array[i].size != memSize ) { // Remember : MemInfo::ptr points directly to client buffer, // bypassing the PRE_CHK_VAL newPtr = (char*) realloc( (char*)orgPtr-CHK_VAL_SIZE, memSize+CHK_VAL_SIZE*2 ); if( newPtr == NULL ) err.mem(); // set the POST_CHK_VAL again as the size of it has changed *((long*)newPtr) = PRE_CHK_VAL; *((long*)(newPtr+CHK_VAL_SIZE+memSize)) = POST_CHK_VAL; info_array[i].ptr = newPtr + CHK_VAL_SIZE; info_array[i].size = memSize; } return (char*) info_array[i].ptr; } } err.run( "Mem::resize - Original memory pointer not found.\n" "File name : %s, line no. : %d \n", fileName, fileLine ); return NULL; } //----------- END OF FUNCTION Mem::resize ---------------// //-------- BEGIN OF FUNCTION Mem::del ----------// // // freePtr = the memory data pointer to be freed // fileName = file from which the client function calls // fileLine = line number of the client function in the file // void Mem::del(void *freePtr, char* fileName, int fileLine) { int i ; char* truePtr; for( i=ptr_used-1; i>=0; i-- ) { if( info_array[i].ptr == freePtr ) { // truePtr is the pointer actually point to the start of the allocated block, including PRE_CHK_VAL truePtr = (char*) freePtr - CHK_VAL_SIZE; //---- Check for Underwrite and Overwrite error ---// if( *((long*)truePtr) != PRE_CHK_VAL ) err.run( "Mem::del - Memory Underwritten, File name:%s, line no.:%d\n", fileName, fileLine ); if( *((long*)(truePtr+CHK_VAL_SIZE+info_array[i].size)) != POST_CHK_VAL ) err.run( "Mem::del - Memory Overwritten, File name:%s, line no.:%d\n", fileName, fileLine ); // fill the to be freed block with a value, which may // have chance to reveal some hiden bugs memset( truePtr+CHK_VAL_SIZE, BAD_VAL, info_array[i].size ); //--------- free it up --------------// free(truePtr); memmove( info_array+i, info_array+i+1, sizeof(MemInfo) * (ptr_used-i-1) ) ; ptr_used-- ; return ; } } err.run( "Mem::del - Free value not found, File name:%s, line no.:%d\n", fileName, fileLine ); } //----------- END OF FUNCTION Mem::del ---------------// //-------- BEGIN OF FUNCTION Mem::get_mem_size ----------// // // This function is mainly for debugging only. // // memPtr = the memory data pointer to be freed // int Mem::get_mem_size(void *memPtr) { for( int i=ptr_used-1; i>=0; i-- ) { if( info_array[i].ptr == memPtr ) return info_array[i].size; } err.run( "Error: Mem::get_mem_size()." ); return 0; } //----------- END OF FUNCTION Mem::get_mem_size ---------------// //-------- BEGIN OF FUNCTION Mem::~Mem ------------// Mem::~Mem() { if ( ptr_used > 0 ) { int i; for ( i=0; i< ptr_used ; i++ ) { err.msg( "Memory not freed. File name : %s, line no. : %d \n", info_array[i].file_name, info_array[i].file_line ); } } free(info_array); } //---------- END OF FUNCTION Mem::~Mem ------------// /* //-------- Begin of Overload "new" operator --------// void* operator new(size_t memSize) { void *memPtr = malloc(memSize); return memPtr; } //--------- End of Overload "new" operator ---------// //-------- Begin of Overload "delete" operator --------// void operator delete(void *memPtr) { free(memPtr); } //--------- End of Overload "delete" operator ---------// */ #else //-------- BEGIN OF FUNCTION mem_resize_keep_data ----------// // // This is the non-DEBUG version of Mem::resize_keep_data() // // orgPtr = the original memory data pointer // orgSize = the original data size // newSize = new size of memory required // // Returns : NULL - not enough memory // - pointer to the allocated memory // char* mem_resize_keep_data(void *orgPtr, unsigned orgSize, unsigned newSize) { if( orgPtr == NULL ) return (char*) malloc(newSize); if( newSize <= orgSize ) return (char*) realloc(orgPtr, newSize); //-------- save the original data first ------// char* saveBuf = (char*)malloc(sizeof(char)*(orgSize)); memcpy( saveBuf, orgPtr, orgSize ); //------ reallocate the memory --------// char* newPtr = (char*) realloc(orgPtr, newSize); //----- store the original data to the new buf -------// if( newPtr != orgPtr ) // only when the pointer has been changed memcpy( newPtr, saveBuf, orgSize ); free(saveBuf); return newPtr; } //----------- END OF FUNCTION mem_resize_keep_data ---------------// #endif //-----------------------------------------------------// // // An article from Walter Bright's MEM Package which is // very similar to our omem class // // WHAT MEM DOES: // -------------- // // 1. ISO/ANSI verification: // // When Walter wrote MEM, compiler compliance with ANSI standards was still // quite low. MEM verifies ISO/ANSI compliance for situations such as passing // NULL or size 0 to allocation/reallocation functions. // // 2. Logging of all allocations and frees: // // All MEM's functions pass the __FILE__ and __LINE__ arguments. During alloca- // tion, MEM makes an entry into a linked list and stores the file and line // information in the list for whichever allocation or free function is called. // // This linked list is the backbone of MEM. When MEM detects a bug, it tells // you where to look in which file to begin tracking the problem. // // 3. Verification of frees: // // Since MEM knows about all allocations, when a pointer is freed, MEM can // verify that the pointer was allocated originally. Additionally, MEM will // only allow a pointer to be freed once. // // Freed data is overwritten with a non-zero known value, flushing such problems // as continuing to reference data after it's been freed. The value written // over the data is selected to maximize the probability of a segment fault or // assertion failure if your application references it after it's been freed. // // MEM obviously can't directly detect "if" instances such as... // // mem_free(p); // if (p) ... // // ...but by guaranteeing that `p' points to garbage after being freed, code // like this will hopefully never work and will thus be easier to find. // // 4. Detection of pointer over- and under-run: // // Pointer overrun occurs when a program stores data past the end of a buffer, // e.g. // // p = malloc(strlen(s)); /* No space for terminating NUL */ // strcpy(p,s); /* Terminating NUL clobber memory */ // // Pointer underrun occurs when a program stores data before the beginning of a // buffer. This error occurs less often than overruns, but MEM detects it // anyway. MEM does this by allocating a little extra at each end of every // buffer, which is filled with a known value, called a sentinel. MEM detects // overruns and underruns by verifying the sentinel value when the buffer is // freed. // // 5. Dependence on values in buffer obtained from malloc(): // // When obtaining a buffer from malloc(), a program may develop erroneous and // creeping dependencies on whatever random (and sometimes repeatable) values // the buffer may contain. The mem_malloc() function prevents this by always // setting the data in a buffer to a known non-zero value before returning its // pointer. This also prevents another common error when running under MS-DOS // which doesn't clear unused memory when loading a program. These bugs are // particularly nasty to find since correct program operation may depend on what // was last run! // // 6. Realloc problems: // // Common problems when using realloc() are: 1) depending on realloc() *not* // shifting the location of the buffer in memory, and 2) depending on finding // certain values in the uninitialized region of the realloc'ed buffer. // // MEM flushes these out by *always* moving the buffer and stomping on values // past the initialized area. // // 7. Memory leak detection: // // Memory "leaks" are areas that are allocated but never freed. This can become // a major problem in programs that must run for long periods without interrup- // tion (e.g. BBS's). If there are leaks, eventually the program will run out // of memory and fail. // // Another form of memory leak occurs when a piece of allocated memory should // have been added to some central data structure, but wasn't. // // MEM find memory leaks by keeping track of all allocations and frees. When // mem_term() is called, a list of all unfreed allocations is printed along with // the files and line numbers where the allocations occurred. // // 8. Pointer checking: // // Sometimes it's useful to be able to verify that a pointer is actually // pointing into free store. MEM provides a function... // // mem_checkptr(void *p); // // ...to do this. // // 9. Consistency checking: // // Occasionally, even MEM's internal data structures get clobbered by a wild // pointer. When this happens, you can track it down by sprinkling your code // temporarily with calls to mem_check(), which performs a consistency check on // the free store. // // 10. Out of memory handling: // // MEM can be set using mem_setexception() (see MEM.H) to handle out-of-memory // conditions in any one of several predefined ways: // // 1. Present an "Out of memory" message and terminate the program. // 2. Abort the program with no message. // 3. Mimic ISO/ANSI and return NULL. // 4. Call a user-specified function, perhaps involving virtual memory // or some other "emergency reserve". // 5. Retry (be careful to avoid infinite loops!) // // 11. Companion techniques: // // Since MEM presets allocated and stomps on freed memory, this facilitates // adding your own code to add tags to your data structures when debugging. If // the structures are invalid, you'll know it because MEM will have clobbered // your verification tags. // //-----------------------------------------------------//