summaryrefslogtreecommitdiffstats
path: root/src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp')
-rw-r--r--src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp782
1 files changed, 782 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..99bef44a
--- /dev/null
+++ b/src/VBox/VMM/VMMR3/DBGFR3SampleReport.cpp
@@ -0,0 +1,782 @@
+/* $Id: DBGFR3SampleReport.cpp $ */
+/** @file
+ * DBGF - Debugger Facility, Sample report creation.
+ */
+
+/*
+ * Copyright (C) 2021-2023 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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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;
+}
+