memtracker.cpp revision e961f5bfe1727c6816d3dad3805ebe21b6ba1c64
/* $Id$ */
/** @file
* IPRT - Memory Tracker & Leak Detector.
*/
/*
* Copyright (C) 2010-2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#include <iprt/memtracker.h>
#include <iprt/critsect.h>
#ifdef IN_RING3
#endif
#include <iprt/semaphore.h>
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/** Pointer to a memory tracker instance */
typedef struct RTMEMTRACKERINT *PRTMEMTRACKERINT;
/**
* Memory tracker statistics.
*/
typedef struct RTMEMTRACKERSTATS
{
/** Array of method calls. */
/** The number of times this user freed or reallocated a memory block
* orignally allocated by someone else. */
uint64_t volatile cUserChanges;
/** The total number of bytes allocated ever. */
uint64_t volatile cbTotalAllocated;
/** The total number of blocks allocated ever. */
uint64_t volatile cTotalAllocatedBlocks;
/** The number of bytes currently allocated. */
size_t volatile cbAllocated;
/** The number of blocks currently allocated. */
size_t volatile cAllocatedBlocks;
/** Pointer to memory tracker statistics. */
typedef RTMEMTRACKERSTATS *PRTMEMTRACKERSTATS;
/**
* Memory tracker user data.
*/
typedef struct RTMEMTRACKERUSER
{
/** Entry in the user list (RTMEMTRACKERINT::UserList). */
/** Pointer to the tracker. */
/** Critical section protecting the memory list. */
/** The list of memory allocated by this user (RTMEMTRACKERHDR). */
/** Positive numbers indicates recursion.
* Negative numbers are used for the global user since that is shared by
* more than one thread. */
int32_t volatile cInTracker;
/** The user identifier. */
/** The statistics for this user. */
/** The user (thread) name. */
char szName[32];
/** Pointer to memory tracker per user data. */
typedef RTMEMTRACKERUSER *PRTMEMTRACKERUSER;
/**
* Memory tracker per tag statistics.
*/
typedef struct RTMEMTRACKERTAG
{
/** AVL node core for lookup by hash. */
/** Tag list entry for flat traversal while dumping. */
/** Pointer to the next tag with the same hash (collisions). */
/** The tag statistics. */
/** The tag name length. */
/** The tag string. */
char szTag[1];
/**
* The memory tracker instance.
*/
typedef struct RTMEMTRACKERINT
{
/** Cross roads semaphore separating dumping and normal operation.
* - NS - normal tracking.
* - EW - dumping tracking data. */
/** Critical section protecting the user list and tag database. */
/** List of RTMEMTRACKERUSER records. */
/** The next user identifier number. */
/** The TLS index used for the per thread user records. */
/** Cross roads semaphore used to protect the tag database.
* - NS - lookup.
* - EW + critsect - insertion.
* @todo Replaced this by a read-write semaphore. */
/** The root of the tag lookup database. */
/** List of RTMEMTRACKERTAG records. */
#if ARCH_BITS == 32
/** Alignment padding. */
#endif
/** The global user record (fallback). */
/** The global statistics. */
/** The number of busy (recursive) allocations. */
uint64_t volatile cBusyAllocs;
/** The number of busy (recursive) frees. */
uint64_t volatile cBusyFrees;
/** The number of tags. */
/** The number of users. */
/**
* Output callback structure.
*/
typedef struct RTMEMTRACKEROUTPUT
{
/** The printf like callback. */
/** The data. */
union
{
} uData;
/** Pointer to a memory tracker output callback structure. */
typedef RTMEMTRACKEROUTPUT *PRTMEMTRACKEROUTPUT;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** Pointer to the default memory tracker. */
/**
* Creates a memory tracker.
*
* @returns IRPT status code.
* @param ppTracker Where to return the tracker instance.
*/
{
if (!pTracker)
return VERR_NO_MEMORY;
/*
* Create locks and stuff.
*/
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/*
* Initialize the rest of the structure.
*/
return VINF_SUCCESS;
}
}
}
}
}
return rc;
}
/**
* Gets the user record to use.
*
* @returns Pointer to a user record.
* @param pTracker The tracker instance.
*/
{
/* ASSUMES that RTTlsGet and RTTlsSet will not reenter. */
if (RT_UNLIKELY(!pUser))
{
/*
* Is the thread currently initializing or terminating?
* If so, don't try add any user record for it as RTThread may barf or
* we might not get the thread name.
*/
if (!RTThreadIsSelfAlive())
return &pTracker->FallbackUser;
/*
* Allocate and initialize a new user record for this thread.
*
* We install the fallback user record while doing the allocation and
* locking so that we can deal with recursions.
*/
if (RT_SUCCESS(rc))
{
if (pUser)
{
if (RT_SUCCESS(rc))
{
const char *pszName = RTThreadSelfName();
if (pszName)
/*
* Register the new user record.
*/
if (RT_SUCCESS(rc))
{
return pUser;
}
}
}
else
rc = VERR_NO_MEMORY;
}
/* Failed, user the fallback. */
}
return pUser;
}
/**
* Counterpart to rtMemTrackerGetUser.
*
* @param pUser The user record to 'put' back.
*/
{
}
/**
* Get the tag record corresponding to @a pszTag.
*
* @returns The tag record. This may be NULL if we're out of memory or
* if something goes wrong.
*
* @param pTracker The tracker instance.
* @param pUser The user record of the caller. Must NOT be
* NULL. This is used to prevent infinite
* recursions when allocating a new tag record.
* @param pszTag The tag string. Can be NULL.
*/
DECLINLINE(PRTMEMTRACKERTAG) rtMemTrackerGetTag(PRTMEMTRACKERINT pTracker, PRTMEMTRACKERUSER pUser, const char *pszTag)
{
if (pUser->cInTracker <= 0)
return NULL;
/*
* Hash tag string.
*/
if (pszTag)
else
{
pszTag = "";
cchTag = 0;
uHash = 0;
}
/*
* Look up the tag.
*/
while ( pTag
/*
* Create a new tag record if not found.
*/
if (RT_UNLIKELY(!pTag))
{
if (pTag)
{
if (!pHeadTag)
{
}
else
{
while ( pTag2
{
}
else
{
}
}
}
}
return pTag;
}
/**
* Counterpart to rtMemTrackerGetTag.
*
* @param pTag The tag record to 'put' back.
*/
{
}
/**
* Record an allocation call.
*
* @param pStats The statistics record.
* @param cbUser The size of the allocation.
* @param enmMethod The allocation method.
*/
DECLINLINE(void) rtMemTrackerStateRecordAlloc(PRTMEMTRACKERSTATS pStats, size_t cbUser, RTMEMTRACKERMETHOD enmMethod)
{
}
/**
* Record a free call.
*
* @param pStats The statistics record.
* @param cbUser The size of the allocation.
* @param enmMethod The free method.
*/
DECLINLINE(void) rtMemTrackerStateRecordFree(PRTMEMTRACKERSTATS pStats, size_t cbUser, RTMEMTRACKERMETHOD enmMethod)
{
}
/**
* Internal RTMemTrackerHdrAlloc and RTMemTrackerHdrAllocEx worker.
*
* @returns Pointer to the user data allocation.
* @param pTracker The tracker instance. Can be NULL.
* @param pv The pointer to the allocated memory. This
* includes room for the header.
* @param cbUser The size requested by the user.
* @param pszTag The tag string.
* @param enmMethod The allocation method.
*/
{
/*
* Check input.
*/
if (!pv)
return NULL;
/*
* Initialize the header.
*/
/*
* Add it to the tracker if we've got one.
*/
if (pTracker)
{
{
/* Get the tag and update it's statistics. */
if (pTag)
{
}
/* Link the header and update the user statistics. */
/* Update the global statistics. */
}
else
}
return pHdr + 1;
}
/**
* Internal worker for rtMemTrackerHdrFreeEx and rtMemTrackerHdrReallocPrep.
*
* @returns Pointer to the original block.
* @param pTracker The tracker instance. Can be NULL.
* @param pvUser Pointer to the user memory.
* @param cbUser The size of the user memory or 0.
* @param pszTag The tag to associate the free with.
* @param enmMethod The free method.
* @param uDeadMagic The dead magic value to use.
*/
{
/*
* First mark it as free.
*/
/*
* If there is a association with a user, we need to unlink it and update
* the statistics.
*
* A note on the locking here. We don't take the crossroads semaphore when
* reentering the memory tracker on the same thread because we may be
* holding it in a different direction and would therefore deadlock.
*/
if (pMemUser)
{
if (fTakeXRoadsLock)
if (pCallingUser == pMemUser)
else
{
}
/** @todo we're currently ignoring pszTag, consider how to correctly
* attribute the free operation if the tags differ - it
* makes sense at all... */
if (fTakeXRoadsLock)
}
else
{
/*
* No tracked. This may happen even when pTracker != NULL when the same
* thread reenters the tracker when allocating tracker structures or memory
* in some subroutine like threading and locking.
*/
if (pTracker)
}
return pHdr;
}
/**
* Internal worker for RTMemTrackerHdrReallocPrep and
* RTMemTrackerHdrReallocPrepEx.
*
* @returns Pointer to the actual allocation.
* @param pTracker The tracker instance. Can be NULL.
* @param pvOldUser The user memory.
* @param cbOldUser The size of the user memory, 0 if unknown.
* @param pszTag The tag string.
*/
static void *rtMemTrackerHdrReallocPrepEx(PRTMEMTRACKERINT pTracker, void *pvOldUser, size_t cbOldUser, const char *pszTag)
{
if (!pvOldUser)
return NULL;
}
/**
* Internal worker for RTMemTrackerHdrReallocDone and
* RTMemTrackerHdrReallocDoneEx.
*
* @returns Pointer to the actual allocation.
* @param pTracker The tracker instance. Can be NULL.
* @param pvNew The new memory chunk. Can be NULL.
* @param cbNewUser The size of the new memory chunk.
* @param pvOldUser Pointer to the old user memory.
* @param pszTag The tag string.
*/
{
/* Succeeded? */
if (pvNew)
/* Failed or just realloc to zero? */
if (cbNewUser)
{
return rtMemTrackerHdrAllocEx(pTracker, pHdr, pHdr->cbUser, pszTag, RTMEMTRACKERMETHOD_REALLOC_FAILED);
}
/* Tealloc to zero bytes, i.e. free. */
return NULL;
}
/**
* Internal worker for RTMemTrackerHdrFree and RTMemTrackerHdrFreeEx.
*
* @returns Pointer to the actual allocation.
* @param pTracker The tracker instance. Can be NULL.
* @param pvUser The user memory.
* @param cbUser The size of the user memory, 0 if unknown.
* @param pszTag The tag string.
* @param enmMethod The free method.
*/
{
if (!pvUser)
return NULL;
return rtMemTrackerHdrFreeCommon(pTracker, pvUser, cbUser, pszTag, enmMethod, RTMEMTRACKERHDR_MAGIC_FREE);
}
/**
* Prints a statistics record.
*
* @param pStats The record.
* @param pOutput The output callback table.
* @param fVerbose Whether to print in terse or verbose form.
*/
DECLINLINE(void) rtMemTrackerDumpOneStatRecord(PRTMEMTRACKERSTATS pStats, PRTMEMTRACKEROUTPUT pOutput, bool fVerbose)
{
if (fVerbose)
{
" Currently allocated: %7zu blocks, %8zu bytes\n"
" Total allocation sum: %7RU64 blocks, %8RU64 bytes\n"
,
" Alloc: %7RU64 AllocZ: %7RU64 Free: %7RU64 User Chg: %7RU64\n"
" RPrep: %7RU64 RDone: %7RU64 RFail: %7RU64\n"
" New: %7RU64 New[]: %7RU64 Delete: %7RU64 Delete[]: %7RU64\n"
,
}
else
{
}
}
/**
* Internal worker that dumps all the memory tracking data.
*
* @param pTracker The tracker instance. Can be NULL.
* @param pOutput The output callback table.
*/
{
if (!pTracker)
return;
/*
* We use the EW direction to make sure the lists, trees and statistics
* does not change while we're working.
*/
/* Global statistics.*/
/* Per tag statistics. */
{
}
/* Per user statistics & blocks. */
{
{
" %zu bytes at %p with tag %s\n"
" %.*Rhxd\n"
"\n",
else
" %zu bytes at %p without a tag\n"
" %.*Rhxd\n"
"\n",
}
}
/* Repeat the global statistics. */
}
/**
* Internal worker that dumps the memory tracking statistics.
*
* @param pTracker The tracker instance. Can be NULL.
* @param pOutput The output callback table.
* @param fVerbose Whether to the verbose or quiet.
*/
static void rtMemTrackerDumpStatsWorker(PRTMEMTRACKERINT pTracker, PRTMEMTRACKEROUTPUT pOutput, bool fVerbose)
{
if (!pTracker)
return;
/*
* We use the EW direction to make sure the lists, trees and statistics
* does not change while we're working.
*/
/* Global statistics.*/
if (fVerbose)
/* Per tag statistics. */
{
if ( fVerbose
{
if (fVerbose)
}
}
/* Per user statistics. */
{
if ( fVerbose
{
if (fVerbose)
}
}
if (fVerbose)
{
/* Repeat the global statistics. */
}
}
/**
* @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to the release log}
*/
static DECLCALLBACK(void) rtMemTrackerDumpLogOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
{
}
/**
* Internal worker for RTMemTrackerDumpAllToLog and RTMemTrackerDumpAllToLogEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
{
}
/**
* Internal worker for RTMemTrackerDumpStatsToLog and
* RTMemTrackerDumpStatsToLogEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param fVerbose Whether to print all the stats or just the ones
* relevant to hunting leaks.
*/
{
}
/**
* @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to the release log}
*/
static DECLCALLBACK(void) rtMemTrackerDumpLogRelOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
{
}
/**
* Internal worker for RTMemTrackerDumpStatsToLog and
* RTMemTrackerDumpStatsToLogEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
{
}
/**
* Internal worker for RTMemTrackerDumpStatsToLogRel and
* RTMemTrackerDumpStatsToLogRelEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param fVerbose Whether to print all the stats or just the ones
* relevant to hunting leaks.
*/
{
}
#ifdef IN_RING3
/**
* @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to file}
*/
static DECLCALLBACK(void) rtMemTrackerDumpFileOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
{
}
/**
* Internal work that dumps the memory tracking statistics to a file handle.
*
* @param pTracker The tracker instance. Can be NULL.
* @param fVerbose Whether to print all the stats or just the ones
* relevant to hunting leaks.
* @param hFile The file handle. Can be NIL_RTFILE.
*/
static void rtMemTrackerDumpStatsToFileHandle(PRTMEMTRACKERINT pTracker, bool fVerbose, RTFILE hFile)
{
if (hFile == NIL_RTFILE)
return;
}
/**
* Internal work that dumps all the memory tracking information to a file
* handle.
*
* @param pTracker The tracker instance. Can be NULL.
* @param hFile The file handle. Can be NIL_RTFILE.
*/
{
if (hFile == NIL_RTFILE)
return;
}
/**
* Internal worker for RTMemTrackerDumpStatsToStdOut and
* RTMemTrackerDumpStatsToStdOutEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param fVerbose Whether to print all the stats or just the ones
* relevant to hunting leaks.
*/
{
}
/**
* Internal worker for RTMemTrackerDumpAllToStdOut and
* RTMemTrackerDumpAllToStdOutEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
{
}
/**
* Internal worker for RTMemTrackerDumpStatsToStdErr and
* RTMemTrackerDumpStatsToStdErrEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param fVerbose Whether to print all the stats or just the ones
* relevant to hunting leaks.
*/
{
}
/**
* Internal worker for RTMemTrackerDumpAllToStdErr and
* RTMemTrackerDumpAllToStdErrEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
{
}
/**
* Internal worker for RTMemTrackerDumpStatsToFile and
* RTMemTrackerDumpStatsToFileEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param fVerbose Whether to print all the stats or just the ones
* relevant to hunting leaks.
* @param pszFilename The name of the output file.
*/
static void rtMemTrackerDumpStatsToFileEx(PRTMEMTRACKERINT pTracker, bool fVerbose, const char *pszFilename)
{
if (!pTracker)
return;
/** @todo this is borked. */
| (0600 << RTFILE_O_CREATE_MODE_SHIFT));
if (RT_FAILURE(rc))
return;
}
/**
* Internal worker for RTMemTrackerDumpAllToFile and
* RTMemTrackerDumpAllToFileEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param pszFilename The name of the output file.
*/
{
if (!pTracker)
return;
| (0600 << RTFILE_O_CREATE_MODE_SHIFT));
if (RT_FAILURE(rc))
return;
}
#endif /* IN_RING3 */
/*
*
*
* Default tracker.
* Default tracker.
* Default tracker.
* Default tracker.
* Default tracker.
*
*
*/
/**
* Handles the lazy initialization when g_pDefaultTracker is NULL.
*
* @returns The newly created default tracker or NULL.
*/
static PRTMEMTRACKERINT rtMemTrackerLazyInitDefaultTracker(void)
{
/*
* Don't attempt initialize before RTThread has been initialized.
*/
if (!RTThreadIsInitialized())
return NULL;
/*
* Only one initialization at a time. For now we'll ASSUME that there
* won't be thread ending up here at the same time, only the same
* reentering from the allocator when creating the tracker.
*/
static volatile bool s_fInitialized = false;
if (ASMAtomicXchgBool(&s_fInitialized, true))
return g_pDefaultTracker;
if (RT_FAILURE(rc))
return NULL;
return pTracker;
}
RTDECL(void *) RTMemTrackerHdrAlloc(void *pv, size_t cb, const char *pszTag, RTMEMTRACKERMETHOD enmMethod)
{
if (RT_UNLIKELY(!pTracker))
}
{
if (RT_UNLIKELY(!pTracker))
}
RTDECL(void *) RTMemTrackerHdrReallocDone(void *pvNew, size_t cbNewUser, void *pvOld, const char *pszTag)
{
if (RT_UNLIKELY(!pTracker))
}
RTDECL(void *) RTMemTrackerHdrFree(void *pvUser, size_t cbUser, const char *pszTag, RTMEMTRACKERMETHOD enmMethod)
{
if (RT_UNLIKELY(!pTracker))
}
RTDECL(void) RTMemTrackerDumpAllToLog(void)
{
if (RT_UNLIKELY(!pTracker))
return rtMemTrackerDumpAllToLogEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToLogRel(void)
{
if (RT_UNLIKELY(!pTracker))
return rtMemTrackerDumpAllToLogRelEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToStdOut(void)
{
if (RT_UNLIKELY(!pTracker))
return rtMemTrackerDumpAllToStdOutEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToStdErr(void)
{
if (RT_UNLIKELY(!pTracker))
return rtMemTrackerDumpAllToStdErrEx(pTracker);
}
{
if (RT_UNLIKELY(!pTracker))
}
{
if (RT_UNLIKELY(!pTracker))
}
{
if (RT_UNLIKELY(!pTracker))
}
{
if (RT_UNLIKELY(!pTracker))
}
{
if (RT_UNLIKELY(!pTracker))
}
{
if (RT_UNLIKELY(!pTracker))
}