/* ** 2012 July 7 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** */ #include #include #include #include "sqlite4.h" #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__) 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 */ sqlite4_mem_methods mem; }; 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 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->mem.xMalloc(pTm->mem.pMemEnv, nReq); memset(pNew, 0, sizeof(TmBlockHdr)); tmEnterMutex(pTm); 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->mem.xMalloc(pTm->mem.pMemEnv, 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 * -1]; 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->mem.xFree(pTm->mem.pMemEnv, 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 * -1]; memcpy(pNew, p, MIN(nByte, pHdr->nByte)); tmFree(pTm, p); } return pNew; } 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 } static void *tmLsmEnvXMalloc(void *p, sqlite4_size_t n){ return tmMalloc( (TmGlobal*) p, (int)n ); } static void tmLsmEnvXFree(void *p, void *ptr){ tmFree( (TmGlobal *)p, ptr ); } static void *tmLsmEnvXRealloc(void *ptr, void * mem, int n){ return tmRealloc((TmGlobal*)ptr, mem, n); } static sqlite4_size_t tmLsmXSize(void *p, void *ptr){ if(NULL==ptr){ return 0; }else{ unsigned char * pUc = (unsigned char *)ptr; TmBlockHdr * pBlock = (TmBlockHdr*)(pUc-BLOCK_HDR_SIZE); assert( pBlock->nByte > 0 ); return (sqlite4_size_t) pBlock->nByte; } } static int tmInitStub(void* ignored){ assert("Set breakpoint here."); return 0; } static void tmVoidStub(void* ignored){} int testMallocInstall(sqlite4_env *pEnv){ TmGlobal *pGlobal; /* Object containing allocation hash */ sqlite4_mem_methods allocator; /* This malloc system */ sqlite4_mem_methods orig; /* Underlying malloc system */ /* Allocate and populate a TmGlobal structure. sqlite4_malloc cannot be ** used to allocate the TmGlobal struct as this would cause the environment ** to move to "initialized" state and the SQLITE4_ENVCONFIG_MALLOC ** to fail. */ sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_GETMALLOC, &orig); pGlobal = (TmGlobal *)orig.xMalloc(orig.pMemEnv, sizeof(TmGlobal)); memset(pGlobal, 0, sizeof(TmGlobal)); memcpy(&pGlobal->mem, &orig, sizeof(orig)); /* Set up pEnv to the use the new TmGlobal */ allocator.xRealloc = tmLsmEnvXRealloc; allocator.xMalloc = tmLsmEnvXMalloc; allocator.xFree = tmLsmEnvXFree; allocator.xSize = tmLsmXSize; allocator.xInit = tmInitStub; allocator.xShutdown = tmVoidStub; allocator.xBeginBenign = tmVoidStub; allocator.xEndBenign = tmVoidStub; allocator.pMemEnv = pGlobal; return sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_MALLOC, &allocator); } int testMallocUninstall(sqlite4_env *pEnv){ TmGlobal *pGlobal; /* Object containing allocation hash */ sqlite4_mem_methods allocator; /* This malloc system */ int rc; sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_GETMALLOC, &allocator); assert( allocator.xMalloc==tmLsmEnvXMalloc ); pGlobal = (TmGlobal *)allocator.pMemEnv; rc = sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_MALLOC, &pGlobal->mem); if( rc==SQLITE4_OK ){ sqlite4_free(pEnv, pGlobal); } return rc; } void testMallocCheck( sqlite4_env *pEnv, int *pnLeakAlloc, int *pnLeakByte, FILE *pFile ){ TmGlobal *pGlobal; sqlite4_mem_methods allocator; /* This malloc system */ sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_GETMALLOC, &allocator); assert( allocator.xMalloc==tmLsmEnvXMalloc ); pGlobal = (TmGlobal *)allocator.pMemEnv; tmMallocCheck(pGlobal, pnLeakAlloc, pnLeakByte, pFile); } #include /* ** testmem install ** testmem uninstall ** testmem report ?FILENAME? */ static int testmem_cmd( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ sqlite4_env *pEnv; /* SQLite 4 environment to work with */ int iOpt; const char *azSub[] = {"install", "uninstall", "report", 0}; if( objc<2 ){ Tcl_WrongNumArgs(interp, 1, objv, "sub-command"); return TCL_ERROR; } if( Tcl_GetIndexFromObj(interp, objv[1], azSub, "sub-command", 0, &iOpt) ){ return TCL_ERROR; } pEnv = sqlite4_env_default(); switch( iOpt ){ case 0: { int rc; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 2, objv, ""); return TCL_ERROR; } rc = testMallocInstall(pEnv); if( rc!=SQLITE4_OK ){ Tcl_AppendResult(interp, "Failed to install testmem wrapper", 0); return TCL_ERROR; } break; } case 1: if( objc!=2 ){ Tcl_WrongNumArgs(interp, 2, objv, ""); return TCL_ERROR; } testMallocUninstall(pEnv); break; case 2: { int nLeakAlloc = 0; int nLeakByte = 0; FILE *pReport = 0; Tcl_Obj *pRes; if( objc!=2 && objc!=3 ){ Tcl_WrongNumArgs(interp, 2, objv, "?filename?"); return TCL_ERROR; } if( objc==3 ){ const char *zFile = Tcl_GetString(objv[2]); pReport = fopen(zFile, "w"); if( !pReport ){ Tcl_AppendResult(interp, "Failed to open file: ", zFile, 0); return TCL_ERROR; } } testMallocCheck(pEnv, &nLeakAlloc, &nLeakByte, pReport); if( pReport ) fclose(pReport); pRes = Tcl_NewObj(); Tcl_ListObjAppendElement(interp, pRes, Tcl_NewIntObj(nLeakAlloc)); Tcl_ListObjAppendElement(interp, pRes, Tcl_NewIntObj(nLeakByte)); Tcl_SetObjResult(interp, pRes); break; } } return TCL_OK; } int Sqlitetest_mem_Init(Tcl_Interp *interp){ Tcl_CreateObjCommand(interp, "testmem", testmem_cmd, 0, 0); return TCL_OK; }