#include "cmPrefix.h" #include "cmGlobal.h" #include "cmRpt.h" #include "cmErr.h" #include "cmMem.h" #include "cmFloatTypes.h" #include "cmMath.h" // Block layout // // |a |b |c |d |e |f // |xx...xxx|yyyy|zzzz|gggggggg|ddd...ddd|gggggggg| // // // xxxx = alignment (prefix) bytes - size is given by yyyy (may contain up to alignByteCnt-1 bytes). // yyyy = count of bytes preceding yyyy // zzzz = count of data bytes // gggg = guard area (size is fixed by guardByteCnt arg. to cmMemInitialize()) // dddd = data area // // d = e - guardByteCnt // c = d - sizeof(unsigned) // b = c - sizeof(unsigned) // a = b - yyyy // // Notes: // 1) The smallest allocation is dataByteCnt + (2*sizeof(unsigned) // 2) If allocByteCnt > 0 then the smallest allocation is allocByteCnt + (2*sizeof(unsigned)) + dataByteCnt. // #define _cmMmDataToByteCntPtr( dp, gbc ) (dp==NULL?NULL:(((char*)(dp))-(gbc)-sizeof(unsigned))) #define _cmMmDataToByteCnt( dp, gbc ) (dp==NULL?0 :(*(unsigned*)_cmMmDataToByteCntPtr(dp,gbc))) #define _cmMmDataToPrefixCntPtr( dp, gbc ) (dp==NULL?NULL:( _cmMmDataToByteCntPtr(dp,gbc) - sizeof(unsigned))) #define _cmMmDataToPrefixCnt( dp, gbc ) (dp==NULL?0 :(*(unsigned*)_cmMmDataToPrefixCntPtr(dp,gbc))) #define _cmMmDataToBasePtr( dp, gbc ) (dp==NULL?NULL:(_cmMmDataToPrefixCntPtr(dp,gbc) - _cmMmDataToPrefixCnt(dp,gbc))) #define _cmMmDataToTotalByteCnt( dp, gbc ) (dp==NULL?0 :(sizeof(unsigned) + (2*gbc) + _cmMmDataToByteCnt(dp,gbc) + (_cmMmDataToPrefixCnt(dp,gbc) + sizeof(unsigned)))) #define _cmMmDataToPreGuardPtr( dp, gbc ) (dp==NULL?NULL:(((char*)(dp))-(gbc))) #define _cmMmDataToPostGuardPtr( dp, gbc ) (dp==NULL?NULL:(((char*)(dp))+_cmMmDataToByteCnt(dp,gbc))) enum { kFreedMmFl = 0x01, kDblFreeMmFl = 0x02 }; typedef struct cmMmRecd_str { unsigned uniqueId; // void* dataPtr; // dataPtr may be NULL if the assoc'd alloc request was for 0 bytes. unsigned dataByteCnt; // data area bytes on original allocation unsigned fileLine; const char* fileNameStr; const char* funcNameStr; unsigned flags; struct cmMmRecd_str* linkPtr; } cmMmRecd_t; typedef struct { cmRpt_t* rpt; cmErr_t err; cmMmRecd_t* listPtr; unsigned nextId; unsigned alignByteCnt; unsigned guardByteCnt; cmAllocMmFunc_t allocFunc; cmFreeMmFunc_t freeFunc; void* funcArgPtr; char uninitChar; char freeChar; char guardChar; unsigned flags; } cmMm_t; cmMmH_t cmMmNullHandle = { NULL }; cmMm_t* _cmMmHandleToPtr( cmMmH_t h ) { assert(h.h != NULL ); return (cmMm_t*)h.h; } cmMmRC_t _cmMmCheckGuards( cmMm_t* p, cmMmRecd_t* rp ) { // it is possible that dataPtr is NULL if zero bytes was requested at allocation if( rp->dataPtr == NULL ) return kOkMmRC; // check the pre data area guard space char* pp = _cmMmDataToPreGuardPtr( rp->dataPtr, p->guardByteCnt ); char* ep = pp + p->guardByteCnt; for(;ppguardChar ) return kGuardCorruptMmRC; // check the post data area guard space pp = _cmMmDataToPostGuardPtr( rp->dataPtr, p->guardByteCnt ); ep = pp + p->guardByteCnt; for(;ppguardChar ) return kGuardCorruptMmRC; // if this block was freed and the kFillFreedMmFl was set then check for write-after-free if( cmIsFlag(rp->flags,kFreedMmFl) && cmIsFlag(p->flags,kFillFreedMmFl) ) { pp = rp->dataPtr; ep = pp + _cmMmDataToByteCnt(pp,p->guardByteCnt); for(; ppfreeChar ) return kWriteAfterFreeMmRC; } return kOkMmRC; } cmMmRecd_t* _cmMmFindRecd( cmMm_t* p, const void* dataPtr ) { cmMmRecd_t* rp = p->listPtr; while( rp != NULL ) { if( rp->dataPtr == dataPtr ) break; rp = rp->linkPtr; } return rp; } cmMmRC_t _cmMmFree( cmMm_t* p, void* dataPtr, cmMmRecd_t* rp ) { cmMmRC_t rc = kOkMmRC; if( p->freeFunc(p->funcArgPtr, _cmMmDataToBasePtr(dataPtr,p->guardByteCnt)) == false ) { if( rp == NULL ) rc = cmErrMsg(&p->err,kFreeFailMmRC,"Memory free failed on data area at %p.",dataPtr); else rc = cmErrMsg(&p->err,kFreeFailMmRC,"Memory free failed on data area at %p. Allocated with id:%i at %s line:%i %s.",dataPtr, rp->uniqueId,rp->funcNameStr,rp->fileLine,rp->fileNameStr); } return rc; } cmMmRC_t _cmMmFreeP( cmMm_t* p, void* dataPtr ) { if( dataPtr == NULL ) return kOkMmRC; cmMmRecd_t* rp = p->listPtr; cmMmRC_t rc = kOkMmRC; // if we are tracking alloc's and free's if( cmIsFlag(p->flags,kTrackMmFl) ) { // locate the tracking recd rp = _cmMmFindRecd(p,dataPtr); // if no tracking recd was found if( rp == NULL ) return cmErrMsg(&p->err,kMissingRecdMmRC,"Unable to locate a tracking record associated with released data area pointer:%p.",dataPtr); // if this block was already freed then this is a double free if( cmIsFlag(rp->flags,kFreedMmFl) ) rp->flags = cmSetFlag(rp->flags,kDblFreeMmFl); else // otherwise fill the freed block with the freeChar (if requested) if( cmIsFlag(p->flags,kFillFreedMmFl) ) memset(dataPtr, p->freeChar, _cmMmDataToByteCnt(dataPtr,p->guardByteCnt)); // mark the block as having been freed rp->flags = cmSetFlag(rp->flags,kFreedMmFl); } // if we are not deferring free to the finalize stage ... then free the memory here if( cmIsFlag(p->flags,kDeferFreeMmFl) == false) rc = _cmMmFree(p,dataPtr,rp); return rc; } void* _cmMmAllocate(cmMm_t* p, void* orgDataPtr, unsigned newByteCnt, unsigned flags ) { unsigned abc = cmIsFlag(flags,kAlignMmFl) ? p->alignByteCnt : 0; unsigned gbc = p->guardByteCnt; char* bp = NULL; char* dp = NULL; char* idp = NULL; unsigned orgByteCnt = 0; unsigned prefixByteCnt = 0; // Determine the total allocation block size including the auxillary data. // // alignment bytes + data_byte_cnt + guard bytes + actual data unsigned ttlByteCnt = abc+sizeof(unsigned) + sizeof(unsigned) + (2*gbc) + newByteCnt; // if this is a reallocation if( orgDataPtr != NULL ) { // asking to reallocate a block with zero bytes free's the original block and returns NULL // (this is in keeping with realloc() behaviour) if( newByteCnt == 0 ) { _cmMmFreeP(p,orgDataPtr); return NULL; } // get the size of the currently allocated block orgByteCnt = _cmMmDataToByteCnt(orgDataPtr,gbc); // if the requested data area is <= the alloc'd data area if( newByteCnt <= orgByteCnt) { // if preservation was requested simply return with the original data area ptr if( cmIsFlag(flags,kPreserveMmFl) ) return orgDataPtr; // otherwise initialze the data area dp = orgDataPtr; goto initLabel; } // expansion was requested } // if an empty data area was requested if( newByteCnt == 0 ) return NULL; // // A new data block must be allocated // // allocate the memory block via a callback if((bp = p->allocFunc(p->funcArgPtr,ttlByteCnt)) == NULL ) { cmErrMsg(&p->err,kAllocFailMmRC,"Attempt to allocate %i bytes failed.",ttlByteCnt); goto errLabel; } // make the data area offset: data_byte_cnt + guard bytes dp = bp + (2*sizeof(unsigned)) + gbc; if( abc ) { unsigned long alignMask = abc - 1; // alignment requires an additional offset dp += abc; // align the data area to the specified boundary char* ndp = ((char*)((unsigned long)dp & (~alignMask))); prefixByteCnt = abc - (dp - ndp); dp = ndp; } // set the prefix byteCnt *(unsigned*)_cmMmDataToPrefixCntPtr(dp,gbc) = prefixByteCnt; // set the data area byte cnt *(unsigned*)_cmMmDataToByteCntPtr(dp,gbc) = newByteCnt; // uncomment this line to print memory layout information for each block //printf("prefix:%i prefix*:%p bytes:%i bytes*:%p base:%p==%p data:%p\n",_cmMmDataToPrefixCnt(dp,gbc),_cmMmDataToPrefixCntPtr(dp,gbc),_cmMmDataToByteCnt(dp,gbc),_cmMmDataToByteCntPtr(dp,gbc),_cmMmDataToBasePtr(dp,gbc),bp,dp); // set the guard bytes if( gbc ) { void* gp = _cmMmDataToPreGuardPtr( dp,gbc); if( gp != NULL ) memset(gp, p->guardChar, gbc); gp = _cmMmDataToPostGuardPtr( dp,gbc); if( gp != NULL ) memset(gp, p->guardChar, gbc); } initLabel: // // initialize the new data area // idp = dp; // if this is a reallocation with expansion ... if( orgDataPtr != NULL && newByteCnt > orgByteCnt ) { // ... and preservation was requested if( cmIsFlag(flags,kPreserveMmFl) ) { // copy original data into the new block memcpy(idp,orgDataPtr,orgByteCnt); idp += orgByteCnt; newByteCnt -= orgByteCnt; } _cmMmFreeP(p,orgDataPtr); // free the original data block } // if zeroing was requested if( cmIsFlag(flags,kZeroMmFl)) memset(idp,0,newByteCnt); else // if uninitialized data should be set to a known character if( cmIsFlag(p->flags,kFillUninitMmFl) ) memset(idp,p->uninitChar,newByteCnt); return dp; errLabel: return NULL; } cmMmRC_t cmMmInitialize( cmMmH_t* hp, cmAllocMmFunc_t allocFunc, cmFreeMmFunc_t freeFunc, void* funcArgPtr, unsigned guardByteCnt, unsigned alignByteCnt, unsigned flags, cmRpt_t* rpt ) { cmMm_t* p; cmErr_t err; cmErrSetup(&err,rpt,"Memory Manager"); // validate alignByteCnt if(alignByteCnt>0 && cmIsPowerOfTwo(alignByteCnt)==false) return cmErrMsg(&err,kParamErrMmRC,"The 'alignByteCnt' parameter must be a power of two."); // allocate the main object if((p= calloc(1,sizeof(cmMm_t))) == NULL ) return cmErrMsg(&err,kObjAllocFailMmRC,"Object allocation failed."); // setting kDeferFreeMmFl only makes sense when kTrackMmFl is also set if(cmIsFlag(flags,kTrackMmFl)==false && cmIsFlag(flags,kDeferFreeMmFl)==true) { cmErrMsg(&err,kParamErrMmRC,"The flag 'kDeferFreeMmFl' may only be set if the 'kTrackMmFl' is also set. 'kDeferFreeMmFl' is begin disabled."); flags = cmClrFlag(flags,kDeferFreeMmFl); } cmErrClone(&p->err,&err); p->rpt = rpt; p->alignByteCnt = alignByteCnt; p->guardByteCnt = guardByteCnt; p->allocFunc = allocFunc; p->freeFunc = freeFunc; p->funcArgPtr = funcArgPtr; p->uninitChar = 0x55; // non-zeroed data areas are automatically filled w/ this value on allocation p->freeChar = 0x33; // data areas are overwritten with this value on free p->guardChar = 0xAA; // guard areas are set with this value p->flags = flags; hp->h = p; return kOkMmRC; } cmMmRC_t cmMmFinalize( cmMmH_t* hp ) { cmMm_t* p; cmMmRC_t rc = kOkMmRC; if( hp == NULL || cmMmIsValid(*hp)==false ) return kOkMmRC; p = _cmMmHandleToPtr(*hp); cmMmRecd_t* rp = p->listPtr; while( rp != NULL ) { cmMmRecd_t* tp = rp->linkPtr; cmMmRC_t rrc; if( cmIsFlag(p->flags,kDeferFreeMmFl) ) if((rrc = _cmMmFree(p,rp->dataPtr,rp)) != kOkMmRC ) rc = rrc; free(rp); rp = tp; } free(p); hp->h = NULL; return rc; } unsigned cmMmGuardByteCount( cmMmH_t h ) { cmMm_t* p = _cmMmHandleToPtr(h); return p->guardByteCnt; } unsigned cmMmAlignByteCount( cmMmH_t h ) { cmMm_t* p = _cmMmHandleToPtr(h); return p->alignByteCnt; } unsigned cmMmInitializeFlags( cmMmH_t h ) { cmMm_t* p = _cmMmHandleToPtr(h); return p->flags; } bool cmMmIsValid( cmMmH_t h ) { return h.h != NULL; } void* cmMmAllocate( cmMmH_t h, void* orgDataPtr, unsigned newEleCnt, unsigned newEleByteCnt, enum cmMmAllocFlags_t flags, const char* fileName, const char* funcName, unsigned fileLine ) { cmMm_t* p = _cmMmHandleToPtr(h); unsigned newByteCnt = newEleCnt * newEleByteCnt; void* ndp = _cmMmAllocate(p,orgDataPtr,newByteCnt,flags); /* if( p->nextId == 1575 ) { cmErrMsg(&p->err,kOkMmRC,"Breakpoint for memory allocation id:%i.",p->nextId); } */ // if we are tracking changes if( cmIsFlag(p->flags,kTrackMmFl) ) { // // NOTE: it is possible that ndp is NULL if newByteCnt == 0. // cmMmRecd_t* rp = NULL; // if a new memory block was allocated if( orgDataPtr == NULL || orgDataPtr != ndp ) { // allocate a new tracking recd if( (rp = calloc(1,sizeof(cmMmRecd_t))) == NULL ) { cmErrMsg(&p->err,kTrkAllocFailMmRC,"Unable to allocate a tracking record for %s line:%i %s.",funcName,fileLine,fileName); return ndp; } // initialize the new tracking recd rp->uniqueId = p->nextId; rp->dataPtr = ndp; rp->dataByteCnt = newByteCnt; rp->fileLine = fileLine; rp->fileNameStr = fileName; rp->funcNameStr = funcName; rp->flags = 0; rp->linkPtr = p->listPtr; //printf("%i %i %s %i %s\n",rp->uniqueId,newByteCnt,funcName,fileLine,fileName); p->listPtr = rp; assert( _cmMmCheckGuards(p,rp) == kOkMmRC ); ++p->nextId; } else // a reallocation occurred. if( orgDataPtr == ndp ) { if((rp = _cmMmFindRecd(p,orgDataPtr)) == NULL ) cmErrMsg(&p->err,kMissingRecdMmRC,"Unable to locate a tracking record associated with reallocation data area pointer:%p.",orgDataPtr); } } return ndp; } cmMmRC_t cmMmFree( cmMmH_t h, void* dataPtr ) { cmMm_t* p = _cmMmHandleToPtr(h); return _cmMmFreeP(p,dataPtr); } cmMmRC_t cmMmFreeDebug( cmMmH_t h, void* dataPtr, const char* fileName, const char* funcName, unsigned fileLine ) { cmMmRC_t rc; if((rc = cmMmFree(h,dataPtr)) != kOkMmRC ) cmErrMsg(&_cmMmHandleToPtr(h)->err,rc,"Memory free failed at %s() line:%i %s",funcName,fileLine,fileName); return rc; } cmMmRC_t cmMmFreePtr( cmMmH_t h, void** dataPtrPtr ) { assert(dataPtrPtr != NULL ); cmMmRC_t rc = cmMmFree(h,*dataPtrPtr); *dataPtrPtr = NULL; return rc; } cmMmRC_t cmMmFreePtrDebug( cmMmH_t h, void* dataPtr, const char* fileName, const char* funcName, unsigned fileLine ) { cmMmRC_t rc; if((rc = cmMmFreePtr(h,dataPtr)) != kOkMmRC ) cmErrMsg(&_cmMmHandleToPtr(h)->err,rc,"Memory free failed at %s() line:%i %s",funcName,fileLine,fileName); return rc; } unsigned cmMmByteCount( cmMmH_t h, const void* dataPtr ) { cmMm_t* p = _cmMmHandleToPtr(h); return _cmMmDataToByteCnt(dataPtr,p->guardByteCnt); } unsigned cmMmDebugId( cmMmH_t h, const void* dataPtr) { cmMm_t* p = _cmMmHandleToPtr(h); const cmMmRecd_t* rp = NULL; // if we are tracking alloc's and free's if( cmIsFlag(p->flags,kTrackMmFl) ) { // locate the tracking recd rp = _cmMmFindRecd(p,dataPtr); } return rp==NULL ? cmInvalidId : rp->uniqueId; } cmMmRC_t _cmMmError( cmMm_t* p, cmMmRC_t rc, const cmMmRecd_t* rp, const char* msg ) { return cmErrMsg(&p->err,rc,"%s detected on block id:%i from %s() line:%i %s.",msg,rp->uniqueId,rp->funcNameStr,rp->fileLine,rp->fileNameStr); } cmMmRC_t _cmMmRecdPrint( cmMm_t* p, cmMmRecd_t* rp, cmMmRC_t rc ) { void* dp = rp->dataPtr; unsigned gbc = p->guardByteCnt; char* lbl = NULL; switch( rc ) { case kOkMmRC: lbl = "Ok "; break; case kLeakDetectedMmRC: lbl = "Memory Leak "; break; case kWriteAfterFreeMmRC: lbl = "Write After Free"; break; case kDblFreeDetectedMmRC: lbl = "Double Free "; break; case kGuardCorruptMmRC: lbl = "Guard Corrupt "; break; default: lbl = "Unknown Status "; } cmRptPrintf(p->err.rpt,"%s id:%5i data:%p : data:%5i prefix:%5i total:%5i base:%p : %5i %s %s\n", lbl,rp->uniqueId,rp->dataPtr, _cmMmDataToByteCnt(dp,gbc), _cmMmDataToPrefixCnt(dp,gbc), _cmMmDataToTotalByteCnt(dp,gbc), _cmMmDataToBasePtr(dp,gbc), rp->fileLine,rp->funcNameStr,rp->fileNameStr ); return rc; } cmMmRC_t cmMmReport( cmMmH_t h, unsigned flags ) { cmMm_t* p = _cmMmHandleToPtr(h); unsigned allocByteCnt = 0; unsigned ttlByteCnt = 0; unsigned dataFrByteCnt = 0; unsigned ttlFrByteCnt = 0; unsigned dataLkByteCnt = 0; unsigned ttlLkByteCnt = 0; unsigned allocBlkCnt = 0; unsigned freeBlkCnt = 0; unsigned leakBlkCnt = 0; cmMmRecd_t* rp = p->listPtr; cmMmRC_t ret_rc = kOkMmRC; for(; rp != NULL; rp = rp->linkPtr,++allocBlkCnt ) { cmMmRC_t rc = kOkMmRC; unsigned blkDataByteCnt = _cmMmDataToByteCnt(rp->dataPtr,p->guardByteCnt); unsigned blkTtlByteCnt = _cmMmDataToTotalByteCnt(rp->dataPtr,p->guardByteCnt); allocByteCnt += blkDataByteCnt; ttlByteCnt += blkTtlByteCnt; // if this block was freed or never alloc'd if( cmIsFlag(rp->flags,kFreedMmFl) || rp->dataPtr == NULL ) { ++freeBlkCnt; dataFrByteCnt += blkDataByteCnt; ttlFrByteCnt += blkTtlByteCnt; } else // if this block leaked { ++leakBlkCnt; dataLkByteCnt += blkDataByteCnt; ttlLkByteCnt += blkTtlByteCnt; if( cmIsFlag(flags,kIgnoreLeaksMmFl) == false ) rc = _cmMmRecdPrint(p,rp,kLeakDetectedMmRC); } // if this block was double freed if( cmIsFlag(rp->flags,kDblFreeMmFl) ) rc = _cmMmRecdPrint(p,rp,kDblFreeDetectedMmRC); // check for guard corruption and write-after-free cmMmRC_t t_rc = _cmMmCheckGuards(p,rp); switch( t_rc ) { case kOkMmRC: break; case kGuardCorruptMmRC: rc = _cmMmRecdPrint(p,rp,t_rc); break; case kWriteAfterFreeMmRC: rc = _cmMmRecdPrint(p,rp,t_rc); break; default: assert(0); break; } if( rc != kOkMmRC ) ret_rc = rc; else { if( cmIsFlag(flags,kIgnoreNormalMmFl)==false ) _cmMmRecdPrint(p,rp,rc); } } if( p->listPtr != NULL && cmIsFlag(flags,kSuppressSummaryMmFl)==false ) { cmRptPrintf(p->err.rpt,"Blocks Allocated:%i Freed:%i Leaked:%i\n",allocBlkCnt,freeBlkCnt,leakBlkCnt); cmRptPrintf(p->err.rpt,"Bytes Allocated: data=%i total=%i Freed: data=%i total=%i Leaked: data=%i total=%i\n",allocByteCnt,ttlByteCnt,dataFrByteCnt,ttlFrByteCnt,dataLkByteCnt,ttlLkByteCnt); } return ret_rc; } cmMmRC_t cmMmIsGuardCorrupt( cmMmH_t h, unsigned id ) { cmMm_t* p = _cmMmHandleToPtr(h); cmMmRecd_t* rp = p->listPtr; while(rp != NULL ) { if( rp->uniqueId == id ) break; rp = rp->linkPtr; } if( rp == NULL ) return cmErrMsg(&p->err,kMissingRecdMmRC,"Unable to locate the tracking record associated with id %i.",id); return _cmMmCheckGuards(p,rp); } cmMmRC_t cmMmCheckAllGuards( cmMmH_t h ) { cmMm_t* p = _cmMmHandleToPtr(h); cmMmRecd_t* rp = p->listPtr; cmMmRC_t rc = kOkMmRC; while(rp != NULL ) { if((rc = _cmMmCheckGuards(p,rp)) != kOkMmRC ) rc = cmErrMsg(&p->err,rc,"A corrupt guard or 'write after free' was detected in the data area allocated with id:%i at %s (line:%i) %s.",rp->uniqueId,rp->funcNameStr,rp->fileLine,rp->fileNameStr); rp = rp->linkPtr; } return rc; }