diff options
Diffstat (limited to 'src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp')
-rw-r--r-- | src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp | 786 |
1 files changed, 786 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp b/src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp new file mode 100644 index 00000000..24b2f1db --- /dev/null +++ b/src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp @@ -0,0 +1,786 @@ +/* $Id: DBGFR3SampleReport.cpp $ */ +/** @file + * DBGF - Debugger Facility, Sample report creation. + */ + +/* + * Copyright (C) 2021-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_dbgf_sample_report DBGFR3SampleReport - Sample Report Interface + * + * @todo + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF +#include <VBox/vmm/dbgf.h> +#include "DBGFInternal.h" +#include <VBox/vmm/mm.h> +#include <VBox/vmm/uvm.h> +#include <VBox/vmm/vm.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include <iprt/timer.h> +#include <iprt/sort.h> +#include <iprt/string.h> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Maximum stack frame depth. */ +#define DBGF_SAMPLE_REPORT_FRAME_DEPTH_MAX 64 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Sample report state. + */ +typedef enum DBGFSAMPLEREPORTSTATE +{ + /** Invalid state do not use. */ + DBGFSAMPLEREPORTSTATE_INVALID = 0, + /** The sample report is ready to run. */ + DBGFSAMPLEREPORTSTATE_READY, + /** The sampple process is running currently. */ + DBGFSAMPLEREPORTSTATE_RUNNING, + /** The sample process is about to stop. */ + DBGFSAMPLEREPORTSTATE_STOPPING, + /** 32bit hack. */ + DBGFSAMPLEREPORTSTATE_32BIT_HACK = 0x7fffffff +} DBGFSAMPLEREPORTSTATE; + +/** Pointer to a single sample frame. */ +typedef struct DBGFSAMPLEFRAME *PDBGFSAMPLEFRAME; + +/** + * Frame information. + */ +typedef struct DBGFSAMPLEFRAME +{ + /** Frame address. */ + DBGFADDRESS AddrFrame; + /** Number of times this frame was encountered. */ + uint64_t cSamples; + /** Pointer to the array of frames below in the call stack. */ + PDBGFSAMPLEFRAME paFrames; + /** Number of valid entries in the frams array. */ + uint64_t cFramesValid; + /** Maximum number of entries in the frames array. */ + uint64_t cFramesMax; +} DBGFSAMPLEFRAME; +typedef const DBGFSAMPLEFRAME *PCDBGFSAMPLEFRAME; + + +/** + * Per VCPU sample report data. + */ +typedef struct DBGFSAMPLEREPORTVCPU +{ + /** The root frame. */ + DBGFSAMPLEFRAME FrameRoot; +} DBGFSAMPLEREPORTVCPU; +/** Pointer to the per VCPU sample report data. */ +typedef DBGFSAMPLEREPORTVCPU *PDBGFSAMPLEREPORTVCPU; +/** Pointer to const per VCPU sample report data. */ +typedef const DBGFSAMPLEREPORTVCPU *PCDBGFSAMPLEREPORTVCPU; + + +/** + * Internal sample report instance data. + */ +typedef struct DBGFSAMPLEREPORTINT +{ + /** References hold for this trace module. */ + volatile uint32_t cRefs; + /** The user mode VM handle. */ + PUVM pUVM; + /** State the sample report is currently in. */ + volatile DBGFSAMPLEREPORTSTATE enmState; + /** Flags passed during report creation. */ + uint32_t fFlags; + /** The timer handle for the sample report collector. */ + PRTTIMER hTimer; + /** The sample interval in microseconds. */ + uint32_t cSampleIntervalUs; + /** THe progress callback if set. */ + PFNDBGFPROGRESS pfnProgress; + /** Opaque user data passed with the progress callback. */ + void *pvProgressUser; + /** Number of microseconds left for sampling. */ + uint64_t cSampleUsLeft; + /** The report created after sampling was stopped. */ + char *pszReport; + /** Number of EMTs having a guest sample operation queued. */ + volatile uint32_t cEmtsActive; + /** Array of per VCPU samples collected. */ + DBGFSAMPLEREPORTVCPU aCpus[1]; +} DBGFSAMPLEREPORTINT; +/** Pointer to a const internal trace module instance data. */ +typedef DBGFSAMPLEREPORTINT *PDBGFSAMPLEREPORTINT; +/** Pointer to a const internal trace module instance data. */ +typedef const DBGFSAMPLEREPORTINT *PCDBGFSAMPLEREPORTINT; + + +/** + * Structure to pass to DBGFR3Info() and for doing all other + * output during fatal dump. + */ +typedef struct DBGFSAMPLEREPORTINFOHLP +{ + /** The helper core. */ + DBGFINFOHLP Core; + /** Pointer to the allocated character buffer. */ + char *pachBuf; + /** Number of bytes allocated for the character buffer. */ + size_t cbBuf; + /** Offset into the character buffer. */ + size_t offBuf; +} DBGFSAMPLEREPORTINFOHLP, *PDBGFSAMPLEREPORTINFOHLP; +/** Pointer to a DBGFSAMPLEREPORTINFOHLP structure. */ +typedef const DBGFSAMPLEREPORTINFOHLP *PCDBGFSAMPLEREPORTINFOHLP; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Print formatted string. + * + * @param pHlp Pointer to this structure. + * @param pszFormat The format string. + * @param ... Arguments. + */ +static DECLCALLBACK(void) dbgfR3SampleReportInfoHlp_pfnPrintf(PCDBGFINFOHLP pHlp, const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + pHlp->pfnPrintfV(pHlp, pszFormat, args); + va_end(args); +} + + +/** + * Print formatted string. + * + * @param pHlp Pointer to this structure. + * @param pszFormat The format string. + * @param args Argument list. + */ +static DECLCALLBACK(void) dbgfR3SampleReportInfoHlp_pfnPrintfV(PCDBGFINFOHLP pHlp, const char *pszFormat, va_list args) +{ + PDBGFSAMPLEREPORTINFOHLP pMyHlp = (PDBGFSAMPLEREPORTINFOHLP)pHlp; + + va_list args2; + va_copy(args2, args); + ssize_t cch = RTStrPrintf2V(&pMyHlp->pachBuf[pMyHlp->offBuf], pMyHlp->cbBuf - pMyHlp->offBuf, pszFormat, args2); + if (cch < 0) + { + /* Try increase the buffer. */ + char *pachBufNew = (char *)RTMemRealloc(pMyHlp->pachBuf, pMyHlp->cbBuf + RT_MAX(_4K, -cch)); + if (pachBufNew) + { + pMyHlp->pachBuf = pachBufNew; + pMyHlp->cbBuf += RT_MAX(_4K, -cch); + cch = RTStrPrintf2V(&pMyHlp->pachBuf[pMyHlp->offBuf], pMyHlp->cbBuf - pMyHlp->offBuf, pszFormat, args2); + Assert(cch > 0); + pMyHlp->offBuf += cch; + } + } + else + pMyHlp->offBuf += cch; + va_end(args2); +} + + +/** + * Initializes the sample report output helper. + * + * @param pHlp The structure to initialize. + */ +static void dbgfR3SampleReportInfoHlpInit(PDBGFSAMPLEREPORTINFOHLP pHlp) +{ + RT_BZERO(pHlp, sizeof(*pHlp)); + + pHlp->Core.pfnPrintf = dbgfR3SampleReportInfoHlp_pfnPrintf; + pHlp->Core.pfnPrintfV = dbgfR3SampleReportInfoHlp_pfnPrintfV; + pHlp->Core.pfnGetOptError = DBGFR3InfoGenericGetOptError; + + pHlp->pachBuf = (char *)RTMemAllocZ(_4K); + if (pHlp->pachBuf) + pHlp->cbBuf = _4K; +} + + +/** + * Deletes the sample report output helper. + * + * @param pHlp The structure to delete. + */ +static void dbgfR3SampleReportInfoHlpDelete(PDBGFSAMPLEREPORTINFOHLP pHlp) +{ + if (pHlp->pachBuf) + RTMemFree(pHlp->pachBuf); +} + + +/** + * Frees the given frame and all its descendants. + * + * @returns nothing. + * @param pFrame The frame to free. + */ +static void dbgfR3SampleReportFrameFree(PDBGFSAMPLEFRAME pFrame) +{ + for (uint32_t i = 0; i < pFrame->cFramesValid; i++) + dbgfR3SampleReportFrameFree(&pFrame->paFrames[i]); /** @todo Recursion... */ + + MMR3HeapFree(pFrame->paFrames); + memset(pFrame, 0, sizeof(*pFrame)); +} + + +/** + * Destroys the given sample report freeing all allocated resources. + * + * @returns nothing. + * @param pThis The sample report instance data. + */ +static void dbgfR3SampleReportDestroy(PDBGFSAMPLEREPORTINT pThis) +{ + for (uint32_t i = 0; i < pThis->pUVM->cCpus; i++) + dbgfR3SampleReportFrameFree(&pThis->aCpus[i].FrameRoot); + MMR3HeapFree(pThis); +} + + +/** + * Returns the frame belonging to the given address or NULL if not found. + * + * @returns Pointer to the descendant frame or NULL if not found. + * @param pFrame The frame to look for descendants with the matching address. + * @param pAddr The guest address to search for. + */ +static PDBGFSAMPLEFRAME dbgfR3SampleReportFrameFindByAddr(PCDBGFSAMPLEFRAME pFrame, PCDBGFADDRESS pAddr) +{ + for (uint32_t i = 0; i < pFrame->cFramesValid; i++) + if (!memcmp(pAddr, &pFrame->paFrames[i].AddrFrame, sizeof(*pAddr))) + return &pFrame->paFrames[i]; + + return NULL; +} + + +/** + * Adds the given address to as a descendant to the given frame. + * + * @returns Pointer to the newly inserted frame identified by the given address. + * @param pUVM The usermode VM handle. + * @param pFrame The frame to add the new one to as a descendant. + * @param pAddr The guest address to add. + */ +static PDBGFSAMPLEFRAME dbgfR3SampleReportAddFrameByAddr(PUVM pUVM, PDBGFSAMPLEFRAME pFrame, PCDBGFADDRESS pAddr) +{ + if (pFrame->cFramesValid == pFrame->cFramesMax) + { + uint32_t cFramesMaxNew = pFrame->cFramesMax + 10; + PDBGFSAMPLEFRAME paFramesNew = NULL; + if (pFrame->paFrames) + paFramesNew = (PDBGFSAMPLEFRAME)MMR3HeapRealloc(pFrame->paFrames, sizeof(*pFrame->paFrames) * cFramesMaxNew); + else + paFramesNew = (PDBGFSAMPLEFRAME)MMR3HeapAllocZU(pUVM, MM_TAG_DBGF, sizeof(*pFrame->paFrames) * cFramesMaxNew); + + if (!paFramesNew) + return NULL; + + pFrame->cFramesMax = cFramesMaxNew; + pFrame->paFrames = paFramesNew; + } + + PDBGFSAMPLEFRAME pFrameNew = &pFrame->paFrames[pFrame->cFramesValid++]; + pFrameNew->AddrFrame = *pAddr; + pFrameNew->cSamples = 1; + pFrameNew->paFrames = NULL; + pFrameNew->cFramesMax = 0; + pFrameNew->cFramesValid = 0; + return pFrameNew; +} + + +/** + * @copydoc FNRTSORTCMP + */ +static DECLCALLBACK(int) dbgfR3SampleReportFrameSortCmp(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PCDBGFSAMPLEFRAME pFrame1 = (PCDBGFSAMPLEFRAME)pvElement1; + PCDBGFSAMPLEFRAME pFrame2 = (PCDBGFSAMPLEFRAME)pvElement2; + + if (pFrame1->cSamples < pFrame2->cSamples) + return 1; + if (pFrame1->cSamples > pFrame2->cSamples) + return -1; + + return 0; +} + + +/** + * Dumps a single given frame to the release log. + * + * @returns nothing. + * @param pHlp The debug info helper used for printing. + * @param pUVM The usermode VM handle. + * @param pFrame The frame to dump. + * @param idxFrame The frame number. + */ +static void dbgfR3SampleReportDumpFrame(PCDBGFINFOHLP pHlp, PUVM pUVM, PCDBGFSAMPLEFRAME pFrame, uint32_t idxFrame) +{ + RTGCINTPTR offDisp; + RTDBGMOD hMod; + RTDBGSYMBOL SymPC; + + if (DBGFR3AddrIsValid(pUVM, &pFrame->AddrFrame)) + { + int rc = DBGFR3AsSymbolByAddr(pUVM, DBGF_AS_GLOBAL, &pFrame->AddrFrame, + RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED, + &offDisp, &SymPC, &hMod); + if (RT_SUCCESS(rc)) + { + const char *pszModName = hMod != NIL_RTDBGMOD ? RTDbgModName(hMod) : NULL; + + pHlp->pfnPrintf(pHlp, + "%*s%RU64 %s+%llx (%s) [%RGv]\n", idxFrame * 4, " ", + pFrame->cSamples, + SymPC.szName, offDisp, + hMod ? pszModName : "", + pFrame->AddrFrame.FlatPtr); + RTDbgModRelease(hMod); + } + else + pHlp->pfnPrintf(pHlp, "%*s%RU64 %RGv\n", idxFrame * 4, " ", pFrame->cSamples, pFrame->AddrFrame.FlatPtr); + } + else + pHlp->pfnPrintf(pHlp, "%*s%RU64 %RGv\n", idxFrame * 4, " ", pFrame->cSamples, pFrame->AddrFrame.FlatPtr); + + /* Sort by sample count. */ + RTSortShell(pFrame->paFrames, pFrame->cFramesValid, sizeof(*pFrame->paFrames), dbgfR3SampleReportFrameSortCmp, NULL); + + for (uint32_t i = 0; i < pFrame->cFramesValid; i++) + dbgfR3SampleReportDumpFrame(pHlp, pUVM, &pFrame->paFrames[i], idxFrame + 1); +} + + +/** + * Worker for dbgfR3SampleReportTakeSample(), doing the work in an EMT rendezvous point on + * each VCPU. + * + * @returns nothing. + * @param pThis Pointer to the sample report instance. + */ +static DECLCALLBACK(void) dbgfR3SampleReportSample(PDBGFSAMPLEREPORTINT pThis) +{ + PVM pVM = pThis->pUVM->pVM; + PVMCPU pVCpu = VMMGetCpu(pVM); + + PCDBGFSTACKFRAME pFrameFirst; + int rc = DBGFR3StackWalkBegin(pThis->pUVM, pVCpu->idCpu, DBGFCODETYPE_GUEST, &pFrameFirst); + if (RT_SUCCESS(rc)) + { + DBGFADDRESS aFrameAddresses[DBGF_SAMPLE_REPORT_FRAME_DEPTH_MAX]; + uint32_t idxFrame = 0; + + PDBGFSAMPLEFRAME pFrame = &pThis->aCpus[pVCpu->idCpu].FrameRoot; + pFrame->cSamples++; + + for (PCDBGFSTACKFRAME pStackFrame = pFrameFirst; + pStackFrame && idxFrame < RT_ELEMENTS(aFrameAddresses); + pStackFrame = DBGFR3StackWalkNext(pStackFrame)) + { + if (pThis->fFlags & DBGF_SAMPLE_REPORT_F_STACK_REVERSE) + { + PDBGFSAMPLEFRAME pFrameNext = dbgfR3SampleReportFrameFindByAddr(pFrame, &pStackFrame->AddrPC); + if (!pFrameNext) + pFrameNext = dbgfR3SampleReportAddFrameByAddr(pThis->pUVM, pFrame, &pStackFrame->AddrPC); + else + pFrameNext->cSamples++; + + pFrame = pFrameNext; + } + else + aFrameAddresses[idxFrame] = pStackFrame->AddrPC; + + idxFrame++; + } + + DBGFR3StackWalkEnd(pFrameFirst); + + if (!(pThis->fFlags & DBGF_SAMPLE_REPORT_F_STACK_REVERSE)) + { + /* Walk the frame stack backwards and construct the call stack. */ + while (idxFrame--) + { + PDBGFSAMPLEFRAME pFrameNext = dbgfR3SampleReportFrameFindByAddr(pFrame, &aFrameAddresses[idxFrame]); + if (!pFrameNext) + pFrameNext = dbgfR3SampleReportAddFrameByAddr(pThis->pUVM, pFrame, &aFrameAddresses[idxFrame]); + else + pFrameNext->cSamples++; + + pFrame = pFrameNext; + } + } + } + else + LogRelMax(10, ("Sampling guest stack on VCPU %u failed with rc=%Rrc\n", pVCpu->idCpu, rc)); + + /* Last EMT finishes the report when sampling was stopped. */ + uint32_t cEmtsActive = ASMAtomicDecU32(&pThis->cEmtsActive); + if ( ASMAtomicReadU32((volatile uint32_t *)&pThis->enmState) == DBGFSAMPLEREPORTSTATE_STOPPING + && !cEmtsActive) + { + rc = RTTimerDestroy(pThis->hTimer); AssertRC(rc); RT_NOREF(rc); + pThis->hTimer = NULL; + + DBGFSAMPLEREPORTINFOHLP Hlp; + PCDBGFINFOHLP pHlp = &Hlp.Core; + + dbgfR3SampleReportInfoHlpInit(&Hlp); + + /* Some early dump code. */ + for (uint32_t i = 0; i < pThis->pUVM->cCpus; i++) + { + PCDBGFSAMPLEREPORTVCPU pSampleVCpu = &pThis->aCpus[i]; + + pHlp->pfnPrintf(pHlp, "Sample report for vCPU %u:\n", i); + dbgfR3SampleReportDumpFrame(pHlp, pThis->pUVM, &pSampleVCpu->FrameRoot, 0); + } + + /* Shameless copy from VMMGuruMeditation.cpp */ + static struct + { + const char *pszInfo; + const char *pszArgs; + } const aInfo[] = + { + { "mappings", NULL }, + { "mode", "all" }, + { "handlers", "phys virt hyper stats" }, + { "timers", NULL }, + { "activetimers", NULL }, + }; + for (unsigned i = 0; i < RT_ELEMENTS(aInfo); i++) + { + pHlp->pfnPrintf(pHlp, + "!!\n" + "!! {%s, %s}\n" + "!!\n", + aInfo[i].pszInfo, aInfo[i].pszArgs); + DBGFR3Info(pVM->pUVM, aInfo[i].pszInfo, aInfo[i].pszArgs, pHlp); + } + + /* All other info items */ + DBGFR3InfoMulti(pVM, + "*", + "mappings|hma|cpum|cpumguest|cpumguesthwvirt|cpumguestinstr|cpumhyper|cpumhost|cpumvmxfeat|mode|cpuid" + "|pgmpd|pgmcr3|timers|activetimers|handlers|help|cfgm", + "!!\n" + "!! {%s}\n" + "!!\n", + pHlp); + + + /* done */ + pHlp->pfnPrintf(pHlp, + "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); + + if (pThis->pszReport) + RTMemFree(pThis->pszReport); + pThis->pszReport = Hlp.pachBuf; + Hlp.pachBuf = NULL; + dbgfR3SampleReportInfoHlpDelete(&Hlp); + + ASMAtomicXchgU32((volatile uint32_t *)&pThis->enmState, DBGFSAMPLEREPORTSTATE_READY); + + if (pThis->pfnProgress) + { + pThis->pfnProgress(pThis->pvProgressUser, 100); + pThis->pfnProgress = NULL; + pThis->pvProgressUser = NULL; + } + + DBGFR3SampleReportRelease(pThis); + } +} + + +/** + * @copydoc FNRTTIMER + */ +static DECLCALLBACK(void) dbgfR3SampleReportTakeSample(PRTTIMER pTimer, void *pvUser, uint64_t iTick) +{ + PDBGFSAMPLEREPORTINT pThis = (PDBGFSAMPLEREPORTINT)pvUser; + + if (pThis->cSampleUsLeft != UINT32_MAX) + { + int rc = VINF_SUCCESS; + uint64_t cUsSampled = iTick * pThis->cSampleIntervalUs; /** @todo Wrong if the timer resolution is different from what we've requested. */ + + /* Update progress. */ + if (pThis->pfnProgress) + rc = pThis->pfnProgress(pThis->pvProgressUser, cUsSampled * 99 / pThis->cSampleUsLeft); + + if ( cUsSampled >= pThis->cSampleUsLeft + || rc == VERR_DBGF_CANCELLED) + { + /* + * Let the EMTs do one last round in order to be able to destroy the timer (can't do this on the timer thread) + * and gather information from the devices. + */ + ASMAtomicCmpXchgU32((volatile uint32_t *)&pThis->enmState, DBGFSAMPLEREPORTSTATE_STOPPING, + DBGFSAMPLEREPORTSTATE_RUNNING); + + rc = RTTimerStop(pTimer); AssertRC(rc); RT_NOREF(rc); + } + } + + ASMAtomicAddU32(&pThis->cEmtsActive, pThis->pUVM->cCpus); + + for (uint32_t i = 0; i < pThis->pUVM->cCpus; i++) + { + int rc = VMR3ReqCallVoidNoWait(pThis->pUVM->pVM, i, (PFNRT)dbgfR3SampleReportSample, 1, pThis); + AssertRC(rc); + if (RT_FAILURE(rc)) + ASMAtomicDecU32(&pThis->cEmtsActive); + } +} + + +/** + * Creates a new sample report instance for the specified VM. + * + * @returns VBox status code. + * @param pUVM The usermode VM handle. + * @param cSampleIntervalUs The sample interval in micro seconds. + * @param fFlags Combination of DBGF_SAMPLE_REPORT_F_XXX. + * @param phSample Where to return the handle to the sample report on success. + */ +VMMR3DECL(int) DBGFR3SampleReportCreate(PUVM pUVM, uint32_t cSampleIntervalUs, uint32_t fFlags, PDBGFSAMPLEREPORT phSample) +{ + UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); + AssertReturn(!(fFlags & ~DBGF_SAMPLE_REPORT_F_VALID_MASK), VERR_INVALID_PARAMETER); + AssertPtrReturn(phSample, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + PDBGFSAMPLEREPORTINT pThis = (PDBGFSAMPLEREPORTINT)MMR3HeapAllocZU(pUVM, MM_TAG_DBGF, + RT_UOFFSETOF_DYN(DBGFSAMPLEREPORTINT, aCpus[pUVM->cCpus])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->pUVM = pUVM; + pThis->fFlags = fFlags; + pThis->cSampleIntervalUs = cSampleIntervalUs; + pThis->enmState = DBGFSAMPLEREPORTSTATE_READY; + pThis->cEmtsActive = 0; + + for (uint32_t i = 0; i < pUVM->cCpus; i++) + { + pThis->aCpus[i].FrameRoot.paFrames = NULL; + pThis->aCpus[i].FrameRoot.cSamples = 0; + pThis->aCpus[i].FrameRoot.cFramesValid = 0; + pThis->aCpus[i].FrameRoot.cFramesMax = 0; + } + + *phSample = pThis; + return VINF_SUCCESS; + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * Retains a reference to the given sample report handle. + * + * @returns New reference count. + * @param hSample Sample report handle. + */ +VMMR3DECL(uint32_t) DBGFR3SampleReportRetain(DBGFSAMPLEREPORT hSample) +{ + PDBGFSAMPLEREPORTINT pThis = hSample; + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + + +/** + * Release a given sample report handle reference. + * + * @returns New reference count, on 0 the sample report instance is destroyed. + * @param hSample Sample report handle. + */ +VMMR3DECL(uint32_t) DBGFR3SampleReportRelease(DBGFSAMPLEREPORT hSample) +{ + PDBGFSAMPLEREPORTINT pThis = hSample; + if (!pThis) + return 0; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(ASMAtomicReadU32((volatile uint32_t *)&pThis->enmState) == DBGFSAMPLEREPORTSTATE_READY, + 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + dbgfR3SampleReportDestroy(pThis); + return cRefs; +} + + +/** + * Starts collecting samples for the given sample report. + * + * @returns VBox status code. + * @param hSample Sample report handle. + * @param cSampleUs Number of microseconds to sample at the interval given during creation. + * Use UINT32_MAX to sample for an indefinite amount of time. + * @param pfnProgress Optional progress callback. + * @param pvUser Opaque user data to pass to the progress callback. + */ +VMMR3DECL(int) DBGFR3SampleReportStart(DBGFSAMPLEREPORT hSample, uint64_t cSampleUs, PFNDBGFPROGRESS pfnProgress, void *pvUser) +{ + PDBGFSAMPLEREPORTINT pThis = hSample; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(ASMAtomicCmpXchgU32((volatile uint32_t *)&pThis->enmState, DBGFSAMPLEREPORTSTATE_RUNNING, DBGFSAMPLEREPORTSTATE_READY), + VERR_INVALID_STATE); + + pThis->pfnProgress = pfnProgress; + pThis->pvProgressUser = pvUser; + pThis->cSampleUsLeft = cSampleUs; + + /* Try to detect the guest OS first so we can get more accurate symbols and addressing. */ + char szName[64]; + int rc = DBGFR3OSDetect(pThis->pUVM, &szName[0], sizeof(szName)); + if (RT_SUCCESS(rc)) + { + LogRel(("DBGF/SampleReport: Detected guest OS \"%s\"\n", szName)); + char szVersion[512]; + int rc2 = DBGFR3OSQueryNameAndVersion(pThis->pUVM, NULL, 0, szVersion, sizeof(szVersion)); + if (RT_SUCCESS(rc2)) + LogRel(("DBGF/SampleReport: Version : \"%s\"\n", szVersion)); + } + else + LogRel(("DBGF/SampleReport: Couldn't detect guest operating system rc=%Rcr\n", rc)); + + /* + * We keep an additional reference to ensure that the sample report stays alive, + * it will be dropped when the sample process is stopped. + */ + DBGFR3SampleReportRetain(pThis); + + rc = RTTimerCreateEx(&pThis->hTimer, pThis->cSampleIntervalUs * 1000, + RTTIMER_FLAGS_CPU_ANY | RTTIMER_FLAGS_HIGH_RES, + dbgfR3SampleReportTakeSample, pThis); + if (RT_SUCCESS(rc)) + rc = RTTimerStart(pThis->hTimer, 0 /*u64First*/); + if (RT_FAILURE(rc)) + { + if (pThis->hTimer) + { + int rc2 = RTTimerDestroy(pThis->hTimer); + AssertRC(rc2); RT_NOREF(rc2); + pThis->hTimer = NULL; + } + + bool fXchg = ASMAtomicCmpXchgU32((volatile uint32_t *)&pThis->enmState, DBGFSAMPLEREPORTSTATE_READY, + DBGFSAMPLEREPORTSTATE_RUNNING); + Assert(fXchg); RT_NOREF(fXchg); + DBGFR3SampleReportRelease(pThis); + } + + return rc; +} + + +/** + * Stops collecting samples for the given sample report. + * + * @returns VBox status code. + * @param hSample Sample report handle. + */ +VMMR3DECL(int) DBGFR3SampleReportStop(DBGFSAMPLEREPORT hSample) +{ + PDBGFSAMPLEREPORTINT pThis = hSample; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(ASMAtomicCmpXchgU32((volatile uint32_t *)&pThis->enmState, DBGFSAMPLEREPORTSTATE_STOPPING, + DBGFSAMPLEREPORTSTATE_RUNNING), + VERR_INVALID_STATE); + return VINF_SUCCESS; +} + + +/** + * Dumps the current sample report to the given file. + * + * @returns VBox status code. + * @retval VERR_INVALID_STATE if nothing was sampled so far for reporting. + * @param hSample Sample report handle. + * @param pszFilename The filename to dump the report to. + */ +VMMR3DECL(int) DBGFR3SampleReportDumpToFile(DBGFSAMPLEREPORT hSample, const char *pszFilename) +{ + PDBGFSAMPLEREPORTINT pThis = hSample; + + AssertReturn(pThis->pszReport, VERR_INVALID_STATE); + + PRTSTREAM hStream; + int rc = RTStrmOpen(pszFilename, "w", &hStream); + if (RT_SUCCESS(rc)) + { + rc = RTStrmPutStr(hStream, pThis->pszReport); + RTStrmClose(hStream); + } + + return rc; +} + |