/* $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;
* you can redistribute it and/or modify it under the terms of the GNU
* 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 "internal/iprt.h"
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/avl.h>
#include <iprt/critsect.h>
#ifdef IN_RING3
# include <iprt/file.h>
#endif
#include <iprt/err.h>
#include <iprt/list.h>
#include <iprt/log.h>
#include <iprt/mem.h>
#include <iprt/semaphore.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#include "internal/file.h"
#include "internal/magics.h"
#include "internal/strhash.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/** Pointer to a memory tracker instance */
typedef struct RTMEMTRACKERINT *PRTMEMTRACKERINT;
/**
* Memory tracker statistics.
*/
typedef struct RTMEMTRACKERSTATS
{
/** Array of method calls. */
uint64_t volatile acMethodCalls[RTMEMTRACKERMETHOD_END];
/** 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;
} RTMEMTRACKERSTATS;
/** Pointer to memory tracker statistics. */
typedef RTMEMTRACKERSTATS *PRTMEMTRACKERSTATS;
/**
* Memory tracker user data.
*/
typedef struct RTMEMTRACKERUSER
{
/** Entry in the user list (RTMEMTRACKERINT::UserList). */
RTLISTNODE ListEntry;
/** Pointer to the tracker. */
PRTMEMTRACKERINT pTracker;
/** Critical section protecting the memory list. */
RTCRITSECT CritSect;
/** The list of memory allocated by this user (RTMEMTRACKERHDR). */
RTLISTANCHOR MemoryList;
/** 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. */
uint32_t idUser;
/** The statistics for this user. */
RTMEMTRACKERSTATS Stats;
/** The user (thread) name. */
char szName[32];
} RTMEMTRACKERUSER;
/** 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. */
AVLU32NODECORE Core;
/** Tag list entry for flat traversal while dumping. */
RTLISTNODE ListEntry;
/** Pointer to the next tag with the same hash (collisions). */
PRTMEMTRACKERTAG pNext;
/** The tag statistics. */
RTMEMTRACKERSTATS Stats;
/** The tag name length. */
size_t cchTag;
/** The tag string. */
char szTag[1];
} RTMEMTRACKERTAG;
/**
* The memory tracker instance.
*/
typedef struct RTMEMTRACKERINT
{
/** Cross roads semaphore separating dumping and normal operation.
* - NS - normal tracking.
* - EW - dumping tracking data. */
RTSEMXROADS hXRoads;
/** Critical section protecting the user list and tag database. */
RTCRITSECT CritSect;
/** List of RTMEMTRACKERUSER records. */
RTLISTANCHOR UserList;
/** The next user identifier number. */
uint32_t idUserNext;
/** The TLS index used for the per thread user records. */
RTTLS iTls;
/** Cross roads semaphore used to protect the tag database.
* - NS - lookup.
* - EW + critsect - insertion.
* @todo Replaced this by a read-write semaphore. */
RTSEMXROADS hXRoadsTagDb;
/** The root of the tag lookup database. */
AVLU32TREE TagDbRoot;
/** List of RTMEMTRACKERTAG records. */
RTLISTANCHOR TagList;
#if ARCH_BITS == 32
/** Alignment padding. */
uint32_t u32Alignment;
#endif
/** The global user record (fallback). */
RTMEMTRACKERUSER FallbackUser;
/** The global statistics. */
RTMEMTRACKERSTATS GlobalStats;
/** The number of busy (recursive) allocations. */
uint64_t volatile cBusyAllocs;
/** The number of busy (recursive) frees. */
uint64_t volatile cBusyFrees;
/** The number of tags. */
uint32_t cTags;
/** The number of users. */
uint32_t cUsers;
} RTMEMTRACKERINT;
AssertCompileMemberAlignment(RTMEMTRACKERINT, FallbackUser, 8);
/**
* Output callback structure.
*/
typedef struct RTMEMTRACKEROUTPUT
{
/** The printf like callback. */
DECLCALLBACKMEMBER(void, pfnPrintf)(struct RTMEMTRACKEROUTPUT *pThis, const char *pszFormat, ...);
/** The data. */
union
{
RTFILE hFile;
} uData;
} RTMEMTRACKEROUTPUT;
/** Pointer to a memory tracker output callback structure. */
typedef RTMEMTRACKEROUTPUT *PRTMEMTRACKEROUTPUT;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** Pointer to the default memory tracker. */
static PRTMEMTRACKERINT g_pDefaultTracker = NULL;
/**
* Creates a memory tracker.
*
* @returns IRPT status code.
* @param ppTracker Where to return the tracker instance.
*/
static int rtMemTrackerCreate(PRTMEMTRACKERINT *ppTracker)
{
PRTMEMTRACKERINT pTracker = (PRTMEMTRACKERINT)RTMemAllocZ(sizeof(*pTracker));
if (!pTracker)
return VERR_NO_MEMORY;
/*
* Create locks and stuff.
*/
int rc = RTCritSectInitEx(&pTracker->CritSect,
RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK,
NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
if (RT_SUCCESS(rc))
{
rc = RTSemXRoadsCreate(&pTracker->hXRoads);
if (RT_SUCCESS(rc))
{
rc = RTSemXRoadsCreate(&pTracker->hXRoadsTagDb);
if (RT_SUCCESS(rc))
{
rc = RTTlsAllocEx(&pTracker->iTls, NULL);
if (RT_SUCCESS(rc))
{
rc = RTCritSectInitEx(&pTracker->FallbackUser.CritSect,
RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK,
NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
if (RT_SUCCESS(rc))
{
/*
* Initialize the rest of the structure.
*/
RTListInit(&pTracker->UserList);
RTListInit(&pTracker->TagList);
RTListInit(&pTracker->FallbackUser.ListEntry);
RTListInit(&pTracker->FallbackUser.MemoryList);
pTracker->FallbackUser.pTracker = pTracker;
pTracker->FallbackUser.cInTracker = INT32_MIN / 2;
pTracker->FallbackUser.idUser = pTracker->idUserNext++;
strcpy(pTracker->FallbackUser.szName, "fallback");
*ppTracker = pTracker;
return VINF_SUCCESS;
}
RTTlsFree(pTracker->iTls);
}
RTSemXRoadsDestroy(pTracker->hXRoadsTagDb);
}
RTSemXRoadsDestroy(pTracker->hXRoads);
}
RTCritSectDelete(&pTracker->CritSect);
}
return rc;
}
/**
* Gets the user record to use.
*
* @returns Pointer to a user record.
* @param pTracker The tracker instance.
*/
static PRTMEMTRACKERUSER rtMemTrackerGetUser(PRTMEMTRACKERINT pTracker)
{
/* ASSUMES that RTTlsGet and RTTlsSet will not reenter. */
PRTMEMTRACKERUSER pUser = (PRTMEMTRACKERUSER)RTTlsGet(pTracker->iTls);
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.
*/
int rc = RTTlsSet(pTracker->iTls, &pTracker->FallbackUser);
if (RT_SUCCESS(rc))
{
pUser = (PRTMEMTRACKERUSER)RTMemAllocZ(sizeof(*pUser));
if (pUser)
{
rc = RTCritSectInitEx(&pUser->CritSect,
RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK,
NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
if (RT_SUCCESS(rc))
{
RTListInit(&pUser->ListEntry);
RTListInit(&pUser->MemoryList);
pUser->pTracker = pTracker;
pUser->cInTracker = 1;
const char *pszName = RTThreadSelfName();
if (pszName)
RTStrCopy(pUser->szName, sizeof(pUser->szName), pszName);
/*
* Register the new user record.
*/
rc = RTTlsSet(pTracker->iTls, pUser);
if (RT_SUCCESS(rc))
{
RTCritSectEnter(&pTracker->CritSect);
pUser->idUser = pTracker->idUserNext++;
RTListAppend(&pTracker->UserList, &pUser->ListEntry);
pTracker->cUsers++;
RTCritSectLeave(&pTracker->CritSect);
return pUser;
}
RTCritSectDelete(&pUser->CritSect);
}
RTMemFree(pUser);
}
else
rc = VERR_NO_MEMORY;
}
/* Failed, user the fallback. */
pUser = &pTracker->FallbackUser;
}
ASMAtomicIncS32(&pUser->cInTracker);
return pUser;
}
/**
* Counterpart to rtMemTrackerGetUser.
*
* @param pUser The user record to 'put' back.
*/
DECLINLINE(void) rtMemTrackerPutUser(PRTMEMTRACKERUSER pUser)
{
ASMAtomicDecS32(&pUser->cInTracker);
}
/**
* 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)
{
AssertPtr(pTracker);
AssertPtr(pUser);
if (pUser->cInTracker <= 0)
return NULL;
/*
* Hash tag string.
*/
size_t cchTag;
uint32_t uHash;
if (pszTag)
uHash = sdbmN(pszTag, 260, &cchTag);
else
{
pszTag = "";
cchTag = 0;
uHash = 0;
}
/*
* Look up the tag.
*/
RTSemXRoadsNSEnter(pTracker->hXRoadsTagDb);
PRTMEMTRACKERTAG pTag = (PRTMEMTRACKERTAG)RTAvlU32Get(&pTracker->TagDbRoot, uHash);
while ( pTag
&& ( pTag->cchTag != cchTag
|| memcmp(pTag->szTag, pszTag, cchTag)) )
pTag = pTag->pNext;
RTSemXRoadsNSLeave(pTracker->hXRoadsTagDb);
/*
* Create a new tag record if not found.
*/
if (RT_UNLIKELY(!pTag))
{
pTag = (PRTMEMTRACKERTAG)RTMemAllocZVar(RT_OFFSETOF(RTMEMTRACKERTAG, szTag[cchTag + 1]));
if (pTag)
{
pTag->Core.Key = uHash;
pTag->cchTag = cchTag;
memcpy(pTag->szTag, pszTag, cchTag + 1);
RTSemXRoadsEWEnter(pTracker->hXRoadsTagDb);
RTCritSectEnter(&pTracker->CritSect);
void *pvFreeMe = NULL;
PRTMEMTRACKERTAG pHeadTag = (PRTMEMTRACKERTAG)RTAvlU32Get(&pTracker->TagDbRoot, uHash);
if (!pHeadTag)
{
RTAvlU32Insert(&pTracker->TagDbRoot, &pTag->Core);
RTListAppend(&pTracker->TagList, &pTag->ListEntry);
pTracker->cTags++;
}
else
{
PRTMEMTRACKERTAG pTag2 = pHeadTag;
while ( pTag2
&& ( pTag2->cchTag != cchTag
|| memcmp(pTag2->szTag, pszTag, cchTag)) )
pTag2 = pTag2->pNext;
if (RT_LIKELY(!pTag2))
{
pTag->pNext = pHeadTag->pNext;
pHeadTag->pNext = pTag;
RTListAppend(&pTracker->TagList, &pTag->ListEntry);
pTracker->cTags++;
}
else
{
pvFreeMe = pTag;
pTag = pTag2;
}
}
RTCritSectLeave(&pTracker->CritSect);
RTSemXRoadsEWLeave(pTracker->hXRoadsTagDb);
if (RT_LIKELY(pvFreeMe))
RTMemFree(pvFreeMe);
}
}
return pTag;
}
/**
* Counterpart to rtMemTrackerGetTag.
*
* @param pTag The tag record to 'put' back.
*/
DECLINLINE(void) rtMemTrackerPutTag(PRTMEMTRACKERTAG pTag)
{
NOREF(pTag);
}
/**
* 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)
{
ASMAtomicAddU64(&pStats->cbTotalAllocated, cbUser);
ASMAtomicIncU64(&pStats->cTotalAllocatedBlocks);
ASMAtomicAddZ(&pStats->cbAllocated, cbUser);
ASMAtomicIncZ(&pStats->cAllocatedBlocks);
ASMAtomicIncU64(&pStats->acMethodCalls[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)
{
ASMAtomicSubZ(&pStats->cbAllocated, cbUser);
ASMAtomicDecZ(&pStats->cAllocatedBlocks);
ASMAtomicIncU64(&pStats->acMethodCalls[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.
*/
static void *rtMemTrackerHdrAllocEx(PRTMEMTRACKERINT pTracker, void *pv, size_t cbUser,
const char *pszTag, RTMEMTRACKERMETHOD enmMethod)
{
/*
* Check input.
*/
if (!pv)
return NULL;
AssertReturn(enmMethod > RTMEMTRACKERMETHOD_INVALID && enmMethod < RTMEMTRACKERMETHOD_END, NULL);
/*
* Initialize the header.
*/
PRTMEMTRACKERHDR pHdr = (PRTMEMTRACKERHDR)pv;
pHdr->uMagic = RTMEMTRACKERHDR_MAGIC;
pHdr->cbUser = cbUser;
RTListInit(&pHdr->ListEntry);
pHdr->pUser = NULL;
pHdr->pszTag = pszTag;
pHdr->pTag = NULL;
pHdr->pvUser = pHdr + 1;
/*
* Add it to the tracker if we've got one.
*/
if (pTracker)
{
PRTMEMTRACKERUSER pUser = rtMemTrackerGetUser(pTracker);
if (pUser->cInTracker == 1)
{
RTSemXRoadsNSEnter(pTracker->hXRoads);
/* Get the tag and update it's statistics. */
PRTMEMTRACKERTAG pTag = rtMemTrackerGetTag(pTracker, pUser, pszTag);
if (pTag)
{
pHdr->pTag = pTag;
rtMemTrackerStateRecordAlloc(&pTag->Stats, cbUser, enmMethod);
rtMemTrackerPutTag(pTag);
}
/* Link the header and update the user statistics. */
RTCritSectEnter(&pUser->CritSect);
RTListAppend(&pUser->MemoryList, &pHdr->ListEntry);
RTCritSectLeave(&pUser->CritSect);
pHdr->pUser = pUser;
rtMemTrackerStateRecordAlloc(&pUser->Stats, cbUser, enmMethod);
/* Update the global statistics. */
rtMemTrackerStateRecordAlloc(&pTracker->GlobalStats, cbUser, enmMethod);
RTSemXRoadsNSLeave(pTracker->hXRoads);
}
else
ASMAtomicIncU64(&pTracker->cBusyAllocs);
rtMemTrackerPutUser(pUser);
}
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.
*/
static void *rtMemTrackerHdrFreeCommon(PRTMEMTRACKERINT pTracker, void *pvUser, size_t cbUser,
const char *pszTag, RTMEMTRACKERMETHOD enmMethod,
size_t uDeadMagic)
{
PRTMEMTRACKERHDR pHdr = (PRTMEMTRACKERHDR)pvUser - 1;
AssertReturn(pHdr->uMagic == RTMEMTRACKERHDR_MAGIC, NULL);
Assert(pHdr->cbUser == cbUser || !cbUser); NOREF(cbUser);
Assert(pHdr->pvUser == pvUser);
AssertReturn(enmMethod > RTMEMTRACKERMETHOD_INVALID && enmMethod < RTMEMTRACKERMETHOD_END, NULL);
/*
* First mark it as free.
*/
pHdr->uMagic = uDeadMagic;
/*
* 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.
*/
PRTMEMTRACKERUSER pMemUser = pHdr->pUser;
if (pMemUser)
{
Assert(pMemUser->pTracker == pTracker); Assert(pTracker);
PRTMEMTRACKERUSER pCallingUser = rtMemTrackerGetUser(pTracker);
bool const fTakeXRoadsLock = pCallingUser->cInTracker <= 1;
if (fTakeXRoadsLock)
RTSemXRoadsNSEnter(pTracker->hXRoads);
RTCritSectEnter(&pMemUser->CritSect);
RTListNodeRemove(&pHdr->ListEntry);
RTCritSectLeave(&pMemUser->CritSect);
if (pCallingUser == pMemUser)
rtMemTrackerStateRecordFree(&pCallingUser->Stats, pHdr->cbUser, enmMethod);
else
{
ASMAtomicIncU64(&pCallingUser->Stats.cUserChanges);
ASMAtomicIncU64(&pCallingUser->Stats.acMethodCalls[enmMethod]);
ASMAtomicSubU64(&pMemUser->Stats.cbTotalAllocated, cbUser);
ASMAtomicSubZ(&pMemUser->Stats.cbAllocated, cbUser);
}
rtMemTrackerStateRecordFree(&pTracker->GlobalStats, pHdr->cbUser, enmMethod);
/** @todo we're currently ignoring pszTag, consider how to correctly
* attribute the free operation if the tags differ - it
* makes sense at all... */
NOREF(pszTag);
if (pHdr->pTag)
rtMemTrackerStateRecordFree(&pHdr->pTag->Stats, pHdr->cbUser, enmMethod);
if (fTakeXRoadsLock)
RTSemXRoadsNSLeave(pTracker->hXRoads);
rtMemTrackerPutUser(pCallingUser);
}
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.
*/
Assert(!pHdr->pTag);
if (pTracker)
ASMAtomicIncU64(&pTracker->cBusyFrees);
}
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;
return rtMemTrackerHdrFreeCommon(pTracker, pvOldUser, cbOldUser, pszTag,
RTMEMTRACKERMETHOD_REALLOC_PREP, RTMEMTRACKERHDR_MAGIC_REALLOC);
}
/**
* 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.
*/
static void *rtMemTrackerHdrReallocDoneEx(PRTMEMTRACKERINT pTracker, void *pvNew, size_t cbNewUser,
void *pvOldUser, const char *pszTag)
{
/* Succeeded? */
if (pvNew)
return rtMemTrackerHdrAllocEx(pTracker, pvNew, cbNewUser, pszTag, RTMEMTRACKERMETHOD_REALLOC_DONE);
/* Failed or just realloc to zero? */
if (cbNewUser)
{
PRTMEMTRACKERHDR pHdr = (PRTMEMTRACKERHDR)pvOldUser - 1;
AssertReturn(pHdr->uMagic == RTMEMTRACKERHDR_MAGIC_REALLOC, NULL);
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.
*/
static void *rtMemTrackerHdrFreeEx(PRTMEMTRACKERINT pTracker, void *pvUser, size_t cbUser,
const char *pszTag, RTMEMTRACKERMETHOD enmMethod)
{
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)
{
pOutput->pfnPrintf(pOutput,
" Currently allocated: %7zu blocks, %8zu bytes\n"
" Total allocation sum: %7RU64 blocks, %8RU64 bytes\n"
,
pStats->cAllocatedBlocks,
pStats->cbAllocated,
pStats->cTotalAllocatedBlocks,
pStats->cbTotalAllocated);
pOutput->pfnPrintf(pOutput,
" 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"
,
pStats->acMethodCalls[RTMEMTRACKERMETHOD_ALLOC],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_ALLOCZ],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_FREE],
pStats->cUserChanges,
pStats->acMethodCalls[RTMEMTRACKERMETHOD_REALLOC_PREP],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_REALLOC_DONE],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_REALLOC_FAILED],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_NEW],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_NEW_ARRAY],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_DELETE],
pStats->acMethodCalls[RTMEMTRACKERMETHOD_DELETE_ARRAY]);
}
else
{
pOutput->pfnPrintf(pOutput, " %zu bytes in %zu blocks\n",
pStats->cbAllocated, pStats->cAllocatedBlocks);
}
}
/**
* Internal worker that dumps all the memory tracking data.
*
* @param pTracker The tracker instance. Can be NULL.
* @param pOutput The output callback table.
*/
static void rtMemTrackerDumpAllWorker(PRTMEMTRACKERINT pTracker, PRTMEMTRACKEROUTPUT pOutput)
{
if (!pTracker)
return;
/*
* We use the EW direction to make sure the lists, trees and statistics
* does not change while we're working.
*/
PRTMEMTRACKERUSER pUser = rtMemTrackerGetUser(pTracker);
RTSemXRoadsEWEnter(pTracker->hXRoads);
/* Global statistics.*/
pOutput->pfnPrintf(pOutput, "*** Global statistics ***\n");
rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, true);
pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
/* Per tag statistics. */
pOutput->pfnPrintf(pOutput, "\n*** Tag statistics ***\n");
PRTMEMTRACKERTAG pTag, pNextTag;
RTListForEachSafe(&pTracker->TagList, pTag, pNextTag, RTMEMTRACKERTAG, ListEntry)
{
pOutput->pfnPrintf(pOutput, "Tag: %s\n", pTag->szTag);
rtMemTrackerDumpOneStatRecord(&pTag->Stats, pOutput, true);
pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
}
/* Per user statistics & blocks. */
pOutput->pfnPrintf(pOutput, "\n*** User statistics ***\n");
PRTMEMTRACKERUSER pCurUser, pNextUser;
RTListForEachSafe(&pTracker->UserList, pCurUser, pNextUser, RTMEMTRACKERUSER, ListEntry)
{
pOutput->pfnPrintf(pOutput, "User #%u: %s%s (cInTracker=%d)\n",
pCurUser->idUser,
pCurUser->szName,
pUser == pCurUser ? " (me)" : "",
pCurUser->cInTracker);
rtMemTrackerDumpOneStatRecord(&pCurUser->Stats, pOutput, true);
PRTMEMTRACKERHDR pCurHdr, pNextHdr;
RTListForEachSafe(&pCurUser->MemoryList, pCurHdr, pNextHdr, RTMEMTRACKERHDR, ListEntry)
{
if (pCurHdr->pTag)
pOutput->pfnPrintf(pOutput,
" %zu bytes at %p with tag %s\n"
" %.*Rhxd\n"
"\n",
pCurHdr->cbUser, pCurHdr->pvUser, pCurHdr->pTag->szTag,
RT_MIN(pCurHdr->cbUser, 16*3), pCurHdr->pvUser);
else
pOutput->pfnPrintf(pOutput,
" %zu bytes at %p without a tag\n"
" %.*Rhxd\n"
"\n",
pCurHdr->cbUser, pCurHdr->pvUser,
RT_MIN(pCurHdr->cbUser, 16*3), pCurHdr->pvUser);
}
pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
}
/* Repeat the global statistics. */
pOutput->pfnPrintf(pOutput, "*** Global statistics (reprise) ***\n");
rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, true);
pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
RTSemXRoadsEWLeave(pTracker->hXRoads);
rtMemTrackerPutUser(pUser);
}
/**
* 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.
*/
PRTMEMTRACKERUSER pUser = rtMemTrackerGetUser(pTracker);
RTSemXRoadsEWEnter(pTracker->hXRoads);
/* Global statistics.*/
pOutput->pfnPrintf(pOutput, "*** Global statistics ***\n");
rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, fVerbose);
if (fVerbose)
pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
/* Per tag statistics. */
pOutput->pfnPrintf(pOutput, "\n*** Tag statistics ***\n");
PRTMEMTRACKERTAG pTag, pNextTag;
RTListForEachSafe(&pTracker->TagList, pTag, pNextTag, RTMEMTRACKERTAG, ListEntry)
{
if ( fVerbose
|| pTag->Stats.cbAllocated)
{
pOutput->pfnPrintf(pOutput, "Tag: %s\n", pTag->szTag);
rtMemTrackerDumpOneStatRecord(&pTag->Stats, pOutput, fVerbose);
if (fVerbose)
pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
}
}
/* Per user statistics. */
pOutput->pfnPrintf(pOutput, "\n*** User statistics ***\n");
PRTMEMTRACKERUSER pCurUser, pNextUser;
RTListForEachSafe(&pTracker->UserList, pCurUser, pNextUser, RTMEMTRACKERUSER, ListEntry)
{
if ( fVerbose
|| pCurUser->Stats.cbAllocated
|| pCurUser == pUser)
{
pOutput->pfnPrintf(pOutput, "User #%u: %s%s (cInTracker=%d)\n",
pCurUser->idUser,
pCurUser->szName,
pUser == pCurUser ? " (me)" : "",
pCurUser->cInTracker);
rtMemTrackerDumpOneStatRecord(&pCurUser->Stats, pOutput, fVerbose);
if (fVerbose)
pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
}
}
if (fVerbose)
{
/* Repeat the global statistics. */
pOutput->pfnPrintf(pOutput, "*** Global statistics (reprise) ***\n");
rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, fVerbose);
pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
}
RTSemXRoadsEWLeave(pTracker->hXRoads);
rtMemTrackerPutUser(pUser);
}
/**
* @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to the release log}
*/
static DECLCALLBACK(void) rtMemTrackerDumpLogOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
{
NOREF(pThis);
va_list va;
va_start(va, pszFormat);
RTLogPrintfV(pszFormat, va);
va_end(va);
}
/**
* Internal worker for RTMemTrackerDumpAllToLog and RTMemTrackerDumpAllToLogEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
static void rtMemTrackerDumpAllToLogEx(PRTMEMTRACKERINT pTracker)
{
RTMEMTRACKEROUTPUT Output;
Output.pfnPrintf = rtMemTrackerDumpLogOutput;
rtMemTrackerDumpAllWorker(pTracker, &Output);
}
/**
* 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.
*/
static void rtMemTrackerDumpStatsToLogEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
{
RTMEMTRACKEROUTPUT Output;
Output.pfnPrintf = rtMemTrackerDumpLogOutput;
rtMemTrackerDumpStatsWorker(pTracker, &Output, fVerbose);
}
/**
* @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to the release log}
*/
static DECLCALLBACK(void) rtMemTrackerDumpLogRelOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
{
NOREF(pThis);
va_list va;
va_start(va, pszFormat);
RTLogRelPrintfV(pszFormat, va);
va_end(va);
}
/**
* Internal worker for RTMemTrackerDumpStatsToLog and
* RTMemTrackerDumpStatsToLogEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
static void rtMemTrackerDumpAllToLogRelEx(PRTMEMTRACKERINT pTracker)
{
RTMEMTRACKEROUTPUT Output;
Output.pfnPrintf = rtMemTrackerDumpLogRelOutput;
rtMemTrackerDumpAllWorker(pTracker, &Output);
}
/**
* 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.
*/
static void rtMemTrackerDumpStatsToLogRelEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
{
RTMEMTRACKEROUTPUT Output;
Output.pfnPrintf = rtMemTrackerDumpLogRelOutput;
rtMemTrackerDumpStatsWorker(pTracker, &Output, fVerbose);
}
#ifdef IN_RING3
/**
* @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to file}
*/
static DECLCALLBACK(void) rtMemTrackerDumpFileOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
{
va_list va;
va_start(va, pszFormat);
char szOutput[_4K];
size_t cchOutput = RTStrPrintfV(szOutput, sizeof(szOutput), pszFormat, va);
va_end(va);
RTFileWrite(pThis->uData.hFile, szOutput, cchOutput, NULL);
}
/**
* 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;
RTMEMTRACKEROUTPUT Output;
Output.pfnPrintf = rtMemTrackerDumpFileOutput;
Output.uData.hFile = hFile;
rtMemTrackerDumpStatsWorker(pTracker, &Output, fVerbose);
}
/**
* 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.
*/
static void rtMemTrackerDumpAllToFileHandle(PRTMEMTRACKERINT pTracker, RTFILE hFile)
{
if (hFile == NIL_RTFILE)
return;
RTMEMTRACKEROUTPUT Output;
Output.pfnPrintf = rtMemTrackerDumpFileOutput;
Output.uData.hFile = hFile;
rtMemTrackerDumpAllWorker(pTracker, &Output);
}
/**
* 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.
*/
static void rtMemTrackerDumpStatsToStdOutEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
{
rtMemTrackerDumpStatsToFileHandle(pTracker, fVerbose, rtFileGetStandard(RTHANDLESTD_OUTPUT));
}
/**
* Internal worker for RTMemTrackerDumpAllToStdOut and
* RTMemTrackerDumpAllToStdOutEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
static void rtMemTrackerDumpAllToStdOutEx(PRTMEMTRACKERINT pTracker)
{
rtMemTrackerDumpAllToFileHandle(pTracker, rtFileGetStandard(RTHANDLESTD_OUTPUT));
}
/**
* 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.
*/
static void rtMemTrackerDumpStatsToStdErrEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
{
rtMemTrackerDumpStatsToFileHandle(pTracker, fVerbose, rtFileGetStandard(RTHANDLESTD_ERROR));
}
/**
* Internal worker for RTMemTrackerDumpAllToStdErr and
* RTMemTrackerDumpAllToStdErrEx.
*
* @param pTracker The tracker instance. Can be NULL.
*/
static void rtMemTrackerDumpAllToStdErrEx(PRTMEMTRACKERINT pTracker)
{
rtMemTrackerDumpAllToFileHandle(pTracker, rtFileGetStandard(RTHANDLESTD_ERROR));
}
/**
* 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. */
RTFILE hFile;
int rc = RTFileOpen(&hFile, pszFilename,
RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE
| (0600 << RTFILE_O_CREATE_MODE_SHIFT));
if (RT_FAILURE(rc))
return;
rtMemTrackerDumpStatsToFileHandle(pTracker, fVerbose, hFile);
RTFileClose(hFile);
}
/**
* Internal worker for RTMemTrackerDumpAllToFile and
* RTMemTrackerDumpAllToFileEx.
*
* @param pTracker The tracker instance. Can be NULL.
* @param pszFilename The name of the output file.
*/
static void rtMemTrackerDumpAllToFileEx(PRTMEMTRACKERINT pTracker, const char *pszFilename)
{
if (!pTracker)
return;
RTFILE hFile;
int rc = RTFileOpen(&hFile, pszFilename,
RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE
| (0600 << RTFILE_O_CREATE_MODE_SHIFT));
if (RT_FAILURE(rc))
return;
rtMemTrackerDumpAllToFileHandle(pTracker, hFile);
RTFileClose(hFile);
}
#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;
PRTMEMTRACKERINT pTracker = NULL; /* gcc sucks. */
int rc = rtMemTrackerCreate(&pTracker);
if (RT_FAILURE(rc))
return NULL;
g_pDefaultTracker = pTracker;
return pTracker;
}
RTDECL(void *) RTMemTrackerHdrAlloc(void *pv, size_t cb, const char *pszTag, RTMEMTRACKERMETHOD enmMethod)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerHdrAllocEx(pTracker, pv, cb, pszTag, enmMethod);
}
RTDECL(void *) RTMemTrackerHdrReallocPrep(void *pvOldUser, size_t cbOldUser, const char *pszTag)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerHdrReallocPrepEx(pTracker, pvOldUser, cbOldUser, pszTag);
}
RTDECL(void *) RTMemTrackerHdrReallocDone(void *pvNew, size_t cbNewUser, void *pvOld, const char *pszTag)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerHdrReallocDoneEx(pTracker, pvNew, cbNewUser, pvOld, pszTag);
}
RTDECL(void *) RTMemTrackerHdrFree(void *pvUser, size_t cbUser, const char *pszTag, RTMEMTRACKERMETHOD enmMethod)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerHdrFreeEx(pTracker, pvUser, cbUser, pszTag, enmMethod);
}
RTDECL(void) RTMemTrackerDumpAllToLog(void)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpAllToLogEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToLogRel(void)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpAllToLogRelEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToStdOut(void)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpAllToStdOutEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToStdErr(void)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpAllToStdErrEx(pTracker);
}
RTDECL(void) RTMemTrackerDumpAllToFile(const char *pszFilename)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpAllToFileEx(pTracker, pszFilename);
}
RTDECL(void) RTMemTrackerDumpStatsToLog(bool fVerbose)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpStatsToLogEx(pTracker, fVerbose);
}
RTDECL(void) RTMemTrackerDumpStatsToLogRel(bool fVerbose)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpStatsToLogRelEx(pTracker, fVerbose);
}
RTDECL(void) RTMemTrackerDumpStatsToStdOut(bool fVerbose)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpStatsToStdOutEx(pTracker, fVerbose);
}
RTDECL(void) RTMemTrackerDumpStatsToStdErr(bool fVerbose)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpStatsToStdErrEx(pTracker, fVerbose);
}
RTDECL(void) RTMemTrackerDumpStatsToFile(bool fVerbose, const char *pszFilename)
{
PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
if (RT_UNLIKELY(!pTracker))
pTracker = rtMemTrackerLazyInitDefaultTracker();
return rtMemTrackerDumpStatsToFileEx(pTracker, fVerbose, pszFilename);
}