#include #include #include #define ArraySize(x) ((int)(sizeof(x) / sizeof((x)[0]))) #define MIN(x,y) ((x)<(y) ? (x) : (y)) typedef unsigned int u32; typedef unsigned char u8; typedef long long int i64; typedef unsigned long long int u64; #if defined(__GLIBC__) && defined(LSM_DEBUG_MEM) extern int backtrace(void**,int); extern void backtrace_symbols_fd(void*const*,int,int); # define TM_BACKTRACE 12 #else # define backtrace(A,B) 1 # define backtrace_symbols_fd(A,B,C) #endif typedef struct TmBlockHdr TmBlockHdr; typedef struct TmAgg TmAgg; typedef struct TmGlobal TmGlobal; struct TmGlobal { /* Linked list of all currently outstanding allocations. And a table of ** all allocations, past and present, indexed by backtrace() info. */ TmBlockHdr *pFirst; #ifdef TM_BACKTRACE TmAgg *aHash[10000]; #endif /* Underlying malloc/realloc/free functions */ void *(*xMalloc)(int); /* underlying malloc(3) function */ void *(*xRealloc)(void *, int); /* underlying realloc(3) function */ void (*xFree)(void *); /* underlying free(3) function */ /* Mutex to protect pFirst and aHash */ void (*xEnterMutex)(TmGlobal*); /* Call this to enter the mutex */ void (*xLeaveMutex)(TmGlobal*); /* Call this to leave mutex */ void (*xDelMutex)(TmGlobal*); /* Call this to delete mutex */ void *pMutex; /* Mutex handle */ void *(*xSaveMalloc)(void *, size_t); void *(*xSaveRealloc)(void *, void *, size_t); void (*xSaveFree)(void *, void *); /* OOM injection scheduling. If nCountdown is greater than zero when a ** malloc attempt is made, it is decremented. If this means nCountdown ** transitions from 1 to 0, then the allocation fails. If bPersist is true ** when this happens, nCountdown is then incremented back to 1 (so that the ** next attempt fails too). */ int nCountdown; int bPersist; int bEnable; void (*xHook)(void *); void *pHookCtx; }; struct TmBlockHdr { TmBlockHdr *pNext; TmBlockHdr *pPrev; int nByte; #ifdef TM_BACKTRACE TmAgg *pAgg; #endif u32 iForeGuard; }; #ifdef TM_BACKTRACE struct TmAgg { int nAlloc; /* Number of allocations at this path */ int nByte; /* Total number of bytes allocated */ int nOutAlloc; /* Number of outstanding allocations */ int nOutByte; /* Number of outstanding bytes */ void *aFrame[TM_BACKTRACE]; /* backtrace() output */ TmAgg *pNext; /* Next object in hash-table collision */ }; #endif #define FOREGUARD 0x80F5E153 #define REARGUARD 0xE4676B53 static const u32 rearguard = REARGUARD; #define ROUND8(x) (((x)+7)&~7) #define BLOCK_HDR_SIZE (ROUND8( sizeof(TmBlockHdr) )) static void lsmtest_oom_error(void){ static int nErr = 0; nErr++; } static void tmEnterMutex(TmGlobal *pTm){ pTm->xEnterMutex(pTm); } static void tmLeaveMutex(TmGlobal *pTm){ pTm->xLeaveMutex(pTm); } static void *tmMalloc(TmGlobal *pTm, int nByte){ TmBlockHdr *pNew; /* New allocation header block */ u8 *pUser; /* Return value */ int nReq; /* Total number of bytes requested */ assert( sizeof(rearguard)==4 ); nReq = BLOCK_HDR_SIZE + nByte + 4; pNew = (TmBlockHdr *)pTm->xMalloc(nReq); memset(pNew, 0, sizeof(TmBlockHdr)); tmEnterMutex(pTm); assert( pTm->nCountdown>=0 ); assert( pTm->bPersist==0 || pTm->bPersist==1 ); if( pTm->bEnable && pTm->nCountdown==1 ){ /* Simulate an OOM error. */ lsmtest_oom_error(); pTm->xFree(pNew); pTm->nCountdown = pTm->bPersist; if( pTm->xHook ) pTm->xHook(pTm->pHookCtx); pUser = 0; }else{ if( pTm->bEnable && pTm->nCountdown ) pTm->nCountdown--; pNew->iForeGuard = FOREGUARD; pNew->nByte = nByte; pNew->pNext = pTm->pFirst; if( pTm->pFirst ){ pTm->pFirst->pPrev = pNew; } pTm->pFirst = pNew; pUser = &((u8 *)pNew)[BLOCK_HDR_SIZE]; memset(pUser, 0x56, nByte); memcpy(&pUser[nByte], &rearguard, 4); #ifdef TM_BACKTRACE { TmAgg *pAgg; int i; u32 iHash = 0; void *aFrame[TM_BACKTRACE]; memset(aFrame, 0, sizeof(aFrame)); backtrace(aFrame, TM_BACKTRACE); for(i=0; iaHash); for(pAgg=pTm->aHash[iHash]; pAgg; pAgg=pAgg->pNext){ if( memcmp(pAgg->aFrame, aFrame, sizeof(aFrame))==0 ) break; } if( !pAgg ){ pAgg = (TmAgg *)pTm->xMalloc(sizeof(TmAgg)); memset(pAgg, 0, sizeof(TmAgg)); memcpy(pAgg->aFrame, aFrame, sizeof(aFrame)); pAgg->pNext = pTm->aHash[iHash]; pTm->aHash[iHash] = pAgg; } pAgg->nAlloc++; pAgg->nByte += nByte; pAgg->nOutAlloc++; pAgg->nOutByte += nByte; pNew->pAgg = pAgg; } #endif } tmLeaveMutex(pTm); return pUser; } static void tmFree(TmGlobal *pTm, void *p){ if( p ){ TmBlockHdr *pHdr; u8 *pUser = (u8 *)p; tmEnterMutex(pTm); pHdr = (TmBlockHdr *)(pUser - BLOCK_HDR_SIZE); assert( pHdr->iForeGuard==FOREGUARD ); assert( 0==memcmp(&pUser[pHdr->nByte], &rearguard, 4) ); if( pHdr->pPrev ){ assert( pHdr->pPrev->pNext==pHdr ); pHdr->pPrev->pNext = pHdr->pNext; }else{ assert( pHdr==pTm->pFirst ); pTm->pFirst = pHdr->pNext; } if( pHdr->pNext ){ assert( pHdr->pNext->pPrev==pHdr ); pHdr->pNext->pPrev = pHdr->pPrev; } #ifdef TM_BACKTRACE pHdr->pAgg->nOutAlloc--; pHdr->pAgg->nOutByte -= pHdr->nByte; #endif tmLeaveMutex(pTm); memset(pUser, 0x58, pHdr->nByte); memset(pHdr, 0x57, sizeof(TmBlockHdr)); pTm->xFree(pHdr); } } static void *tmRealloc(TmGlobal *pTm, void *p, int nByte){ void *pNew; pNew = tmMalloc(pTm, nByte); if( pNew && p ){ TmBlockHdr *pHdr; u8 *pUser = (u8 *)p; pHdr = (TmBlockHdr *)(pUser - BLOCK_HDR_SIZE); memcpy(pNew, p, MIN(nByte, pHdr->nByte)); tmFree(pTm, p); } return pNew; } static void tmMallocOom( TmGlobal *pTm, int nCountdown, int bPersist, void (*xHook)(void *), void *pHookCtx ){ assert( nCountdown>=0 ); assert( bPersist==0 || bPersist==1 ); pTm->nCountdown = nCountdown; pTm->bPersist = bPersist; pTm->xHook = xHook; pTm->pHookCtx = pHookCtx; pTm->bEnable = 1; } static void tmMallocOomEnable( TmGlobal *pTm, int bEnable ){ pTm->bEnable = bEnable; } static void tmMallocCheck( TmGlobal *pTm, int *pnLeakAlloc, int *pnLeakByte, FILE *pFile ){ TmBlockHdr *pHdr; int nLeak = 0; int nByte = 0; if( pTm==0 ) return; for(pHdr=pTm->pFirst; pHdr; pHdr=pHdr->pNext){ nLeak++; nByte += pHdr->nByte; } if( pnLeakAlloc ) *pnLeakAlloc = nLeak; if( pnLeakByte ) *pnLeakByte = nByte; #ifdef TM_BACKTRACE if( pFile ){ int i; fprintf(pFile, "LEAKS\n"); for(i=0; iaHash); i++){ TmAgg *pAgg; for(pAgg=pTm->aHash[i]; pAgg; pAgg=pAgg->pNext){ if( pAgg->nOutAlloc ){ int j; fprintf(pFile, "%d %d ", pAgg->nOutByte, pAgg->nOutAlloc); for(j=0; jaFrame[j]); } fprintf(pFile, "\n"); } } } fprintf(pFile, "\nALLOCATIONS\n"); for(i=0; iaHash); i++){ TmAgg *pAgg; for(pAgg=pTm->aHash[i]; pAgg; pAgg=pAgg->pNext){ int j; fprintf(pFile, "%d %d ", pAgg->nByte, pAgg->nAlloc); for(j=0; jaFrame[j]); fprintf(pFile, "\n"); } } } #else (void)pFile; #endif } #include "lsm.h" #include "stdlib.h" typedef struct LsmMutex LsmMutex; struct LsmMutex { lsm_env *pEnv; lsm_mutex *pMutex; }; static void tmLsmMutexEnter(TmGlobal *pTm){ LsmMutex *p = (LsmMutex *)pTm->pMutex; p->pEnv->xMutexEnter(p->pMutex); } static void tmLsmMutexLeave(TmGlobal *pTm){ LsmMutex *p = (LsmMutex *)(pTm->pMutex); p->pEnv->xMutexLeave(p->pMutex); } static void tmLsmMutexDel(TmGlobal *pTm){ LsmMutex *p = (LsmMutex *)pTm->pMutex; pTm->xFree(p); } static void *tmLsmMalloc(int n){ return malloc(n); } static void tmLsmFree(void *ptr){ free(ptr); } static void *tmLsmRealloc(void *ptr, int n){ return realloc(ptr, n); } static void *tmLsmEnvMalloc(lsm_env *p, size_t n){ return tmMalloc((TmGlobal *)(p->pMemCtx), n); } static void tmLsmEnvFree(lsm_env *p, void *ptr){ tmFree((TmGlobal *)(p->pMemCtx), ptr); } static void *tmLsmEnvRealloc(lsm_env *p, void *ptr, size_t n){ return tmRealloc((TmGlobal *)(p->pMemCtx), ptr, n); } void testMallocInstall(lsm_env *pEnv){ TmGlobal *pGlobal; LsmMutex *pMutex; assert( pEnv->pMemCtx==0 ); /* Allocate and populate a TmGlobal structure. */ pGlobal = (TmGlobal *)tmLsmMalloc(sizeof(TmGlobal)); memset(pGlobal, 0, sizeof(TmGlobal)); pGlobal->xMalloc = tmLsmMalloc; pGlobal->xRealloc = tmLsmRealloc; pGlobal->xFree = tmLsmFree; pMutex = (LsmMutex *)pGlobal->xMalloc(sizeof(LsmMutex)); pMutex->pEnv = pEnv; pEnv->xMutexStatic(pEnv, LSM_MUTEX_HEAP, &pMutex->pMutex); pGlobal->xEnterMutex = tmLsmMutexEnter; pGlobal->xLeaveMutex = tmLsmMutexLeave; pGlobal->xDelMutex = tmLsmMutexDel; pGlobal->pMutex = (void *)pMutex; pGlobal->xSaveMalloc = pEnv->xMalloc; pGlobal->xSaveRealloc = pEnv->xRealloc; pGlobal->xSaveFree = pEnv->xFree; /* Set up pEnv to the use the new TmGlobal */ pEnv->pMemCtx = (void *)pGlobal; pEnv->xMalloc = tmLsmEnvMalloc; pEnv->xRealloc = tmLsmEnvRealloc; pEnv->xFree = tmLsmEnvFree; } void testMallocUninstall(lsm_env *pEnv){ TmGlobal *p = (TmGlobal *)pEnv->pMemCtx; pEnv->pMemCtx = 0; if( p ){ pEnv->xMalloc = p->xSaveMalloc; pEnv->xRealloc = p->xSaveRealloc; pEnv->xFree = p->xSaveFree; p->xDelMutex(p); tmLsmFree(p); } } void testMallocCheck( lsm_env *pEnv, int *pnLeakAlloc, int *pnLeakByte, FILE *pFile ){ if( pEnv->pMemCtx==0 ){ *pnLeakAlloc = 0; *pnLeakByte = 0; }else{ tmMallocCheck((TmGlobal *)(pEnv->pMemCtx), pnLeakAlloc, pnLeakByte, pFile); } } void testMallocOom( lsm_env *pEnv, int nCountdown, int bPersist, void (*xHook)(void *), void *pHookCtx ){ TmGlobal *pTm = (TmGlobal *)(pEnv->pMemCtx); tmMallocOom(pTm, nCountdown, bPersist, xHook, pHookCtx); } void testMallocOomEnable(lsm_env *pEnv, int bEnable){ TmGlobal *pTm = (TmGlobal *)(pEnv->pMemCtx); tmMallocOomEnable(pTm, bEnable); }