diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/VMM/VMMR3/DBGFR3PlugIn.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/VMM/VMMR3/DBGFR3PlugIn.cpp')
-rw-r--r-- | src/VBox/VMM/VMMR3/DBGFR3PlugIn.cpp | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/src/VBox/VMM/VMMR3/DBGFR3PlugIn.cpp b/src/VBox/VMM/VMMR3/DBGFR3PlugIn.cpp new file mode 100644 index 00000000..5ba71be9 --- /dev/null +++ b/src/VBox/VMM/VMMR3/DBGFR3PlugIn.cpp @@ -0,0 +1,616 @@ +/* $Id: DBGFR3PlugIn.cpp $ */ +/** @file + * DBGF - Debugger Facility, Plug-In Support. + */ + +/* + * Copyright (C) 2008-2019 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF +#include <VBox/vmm/dbgf.h> +#include <VBox/vmm/mm.h> +#include "DBGFInternal.h" +#include <VBox/vmm/uvm.h> +#include <VBox/vmm/vm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/version.h> + +#include <iprt/alloca.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/dir.h> +#include <iprt/ldr.h> +#include <iprt/param.h> +#include <iprt/path.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +#define DBGF_PLUG_IN_READ_LOCK(pUVM) \ + do { int rcLock = RTCritSectRwEnterShared(&pUVM->dbgf.s.CritSect); AssertRC(rcLock); } while (0) +#define DBGF_PLUG_IN_READ_UNLOCK(pUVM) \ + do { int rcLock = RTCritSectRwLeaveShared(&pUVM->dbgf.s.CritSect); AssertRC(rcLock); } while (0) + +#define DBGF_PLUG_IN_WRITE_LOCK(pUVM) \ + do { int rcLock = RTCritSectRwEnterExcl(&pUVM->dbgf.s.CritSect); AssertRC(rcLock); } while (0) +#define DBGF_PLUG_IN_WRITE_UNLOCK(pUVM) \ + do { int rcLock = RTCritSectRwLeaveExcl(&pUVM->dbgf.s.CritSect); AssertRC(rcLock); } while (0) + +/** Max allowed length of a plug-in name (excludes the path and suffix). */ +#define DBGFPLUGIN_MAX_NAME 64 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Plug-in tracking record. + */ +typedef struct DBGFPLUGIN +{ + /** Pointer to the next plug-in. */ + struct DBGFPLUGIN *pNext; + /** The loader handle. */ + RTLDRMOD hLdrMod; + /** The plug-in entry point. */ + PFNDBGFPLUGIN pfnEntry; + /** The name length. */ + uint8_t cchName; + /** The plug-in name (variable length). */ + char szName[1]; +} DBGFPLUGIN; +/** Pointer to plug-in tracking record. */ +typedef DBGFPLUGIN *PDBGFPLUGIN; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(void) dbgfPlugInUnloadAll(PUVM pUVM); +static FNDBGFHANDLERINT dbgfR3PlugInInfoList; + + +/** + * Internal init routine called by DBGFR3Init(). + * + * @returns VBox status code. + * @param pUVM The user mode VM handle. + */ +int dbgfR3PlugInInit(PUVM pUVM) +{ + return DBGFR3InfoRegisterInternal(pUVM->pVM, "plugins", "Lists the debugger plug-ins.", dbgfR3PlugInInfoList); +} + + +/** + * Internal cleanup routine called by DBGFR3Term(). + * + * @param pUVM The user mode VM handle. + */ +void dbgfR3PlugInTerm(PUVM pUVM) +{ + dbgfPlugInUnloadAll(pUVM); +} + + +/** + * Extracts the plug-in name from a plug-in specifier that may or may not + * include path and/or suffix. + * + * @returns VBox status code. + * + * @param pszDst Where to return the name. At least DBGFPLUGIN_MAX_NAME + * worth of buffer space. + * @param pszPlugIn The plug-in module specifier to parse. + * @param pErrInfo Optional error information structure. + */ +static int dbgfPlugInExtractName(char *pszDst, const char *pszPlugIn, PRTERRINFO pErrInfo) +{ + /* + * Parse out the name stopping at the extension. + */ + const char *pszName = RTPathFilename(pszPlugIn); + if (!pszName || !*pszName) + return VERR_INVALID_NAME; + if (!RTStrNICmp(pszName, RT_STR_TUPLE(DBGF_PLUG_IN_PREFIX))) + { + pszName += sizeof(DBGF_PLUG_IN_PREFIX) - 1; + if (!*pszName) + return RTErrInfoSetF(pErrInfo, VERR_INVALID_NAME, "Invalid plug-in name: nothing after the prefix"); + } + + int ch; + size_t cchName = 0; + while ( (ch = pszName[cchName]) != '\0' + && ch != '.') + { + if ( RT_C_IS_ALPHA(ch) + || (RT_C_IS_DIGIT(ch) && cchName != 0)) + cchName++; + else + { + if (!RT_C_IS_DIGIT(ch)) + return RTErrInfoSetF(pErrInfo, VERR_INVALID_NAME, "Invalid plug-in name: '%c' is not alphanumeric", ch); + return RTErrInfoSetF(pErrInfo, VERR_INVALID_NAME, + "Invalid plug-in name: Cannot start with a digit (after the prefix)"); + } + } + + if (cchName >= DBGFPLUGIN_MAX_NAME) + return RTErrInfoSetF(pErrInfo, VERR_INVALID_NAME, "Invalid plug-in name: too long (max %u)", DBGFPLUGIN_MAX_NAME); + + /* + * We're very picky about the extension when present. + */ + if ( ch == '.' + && RTStrICmp(&pszName[cchName], RTLdrGetSuff())) + return RTErrInfoSetF(pErrInfo, VERR_INVALID_NAME, + "Invalid plug-in name: Suffix isn't the default dll/so/dylib one (%s): '%s'", + RTLdrGetSuff(), &pszName[cchName]); + + /* + * Copy it. + */ + memcpy(pszDst, pszName, cchName); + pszDst[cchName] = '\0'; + return VINF_SUCCESS; +} + + +/** + * Locate a loaded plug-in. + * + * @returns Pointer to the plug-in tracking structure. + * @param pUVM Pointer to the user-mode VM structure. + * @param pszName The name of the plug-in we're looking for. + * @param ppPrev Where to optionally return the pointer to the + * previous list member. + */ +static PDBGFPLUGIN dbgfR3PlugInLocate(PUVM pUVM, const char *pszName, PDBGFPLUGIN *ppPrev) +{ + PDBGFPLUGIN pPrev = NULL; + PDBGFPLUGIN pCur = pUVM->dbgf.s.pPlugInHead; + while (pCur) + { + if (!RTStrICmp(pCur->szName, pszName)) + { + if (ppPrev) + *ppPrev = pPrev; + return pCur; + } + + /* advance */ + pPrev = pCur; + pCur = pCur->pNext; + } + return NULL; +} + + +/** + * Try load the specified plug-in module. + * + * @returns VINF_SUCCESS on success, path error or loader error on failure. + * + * @param pPlugIn The plug-in tracing record. + * @param pszModule Module name. + * @param pErrInfo Optional error information structure. + */ +static int dbgfR3PlugInTryLoad(PDBGFPLUGIN pPlugIn, const char *pszModule, PRTERRINFO pErrInfo) +{ + /* + * Load it and try resolve the entry point. + */ + int rc = SUPR3HardenedVerifyPlugIn(pszModule, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTLdrLoadEx(pszModule, &pPlugIn->hLdrMod, RTLDRLOAD_FLAGS_LOCAL, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(pPlugIn->hLdrMod, DBGF_PLUG_IN_ENTRYPOINT, (void **)&pPlugIn->pfnEntry); + if (RT_SUCCESS(rc)) + { + LogRel(("DBGF: Loaded Plug-In '%s' (%s)\n", pPlugIn->szName, pszModule)); + return VINF_SUCCESS; + } + + RTErrInfoSet(pErrInfo, rc, "Failed to locate plug-in entrypoint (" DBGF_PLUG_IN_ENTRYPOINT ")" ); + LogRel(("DBGF: RTLdrGetSymbol('%s', '%s',) -> %Rrc\n", pszModule, DBGF_PLUG_IN_ENTRYPOINT, rc)); + + RTLdrClose(pPlugIn->hLdrMod); + pPlugIn->hLdrMod = NIL_RTLDRMOD; + } + return rc; +} + + +/** + * RTPathTraverseList callback. + * + * @returns See FNRTPATHTRAVERSER. + * + * @param pchPath See FNRTPATHTRAVERSER. + * @param cchPath See FNRTPATHTRAVERSER. + * @param pvUser1 The plug-in specifier. + * @param pvUser2 The plug-in tracking record. + */ +static DECLCALLBACK(int) dbgfR3PlugInLoadCallback(const char *pchPath, size_t cchPath, void *pvUser1, void *pvUser2) +{ + PDBGFPLUGIN pPlugIn = (PDBGFPLUGIN)pvUser1; + PRTERRINFO pErrInfo = (PRTERRINFO)pvUser2; + + /* + * Join the path and the specified plug-in name, adding prefix and suffix. + */ + const char *pszSuff = RTLdrGetSuff(); + size_t const cchSuff = strlen(pszSuff); + size_t const cchModule = cchPath + sizeof(RTPATH_SLASH_STR) + sizeof(DBGF_PLUG_IN_PREFIX) + pPlugIn->cchName + cchSuff + 4; + char *pszModule = (char *)alloca(cchModule); + AssertReturn(pszModule, VERR_TRY_AGAIN); + memcpy(pszModule, pchPath, cchPath); + pszModule[cchPath] = '\0'; + + int rc = RTPathAppend(pszModule, cchModule, DBGF_PLUG_IN_PREFIX); + AssertRCReturn(rc, VERR_TRY_AGAIN); + strcat(&pszModule[cchPath], pPlugIn->szName); + strcat(&pszModule[cchPath + sizeof(DBGF_PLUG_IN_PREFIX) - 1 + pPlugIn->cchName], pszSuff); + Assert(strlen(pszModule) < cchModule - 4); + + if (RTPathExists(pszModule)) + { + rc = dbgfR3PlugInTryLoad(pPlugIn, pszModule, pErrInfo); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + } + + return VERR_TRY_AGAIN; +} + + +/** + * Loads a plug-in. + * + * @returns VBox status code. + * @param pUVM Pointer to the user-mode VM structure. + * @param pszName The plug-in name. + * @param pszMaybeModule Path to the plug-in, or just the + * plug-in name as specified by the user. Ignored + * if no path. + * @param pErrInfo Optional error information structure. + */ +static DECLCALLBACK(int) dbgfR3PlugInLoad(PUVM pUVM, const char *pszName, const char *pszMaybeModule, PRTERRINFO pErrInfo) +{ + DBGF_PLUG_IN_WRITE_LOCK(pUVM); + + /* + * Check if a plug-in by the given name already exists. + */ + PDBGFPLUGIN pPlugIn = dbgfR3PlugInLocate(pUVM, pszName, NULL); + if (pPlugIn) + { + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); + return RTErrInfoSetF(pErrInfo, VERR_ALREADY_EXISTS, "A plug-in by the name '%s' already exists", pszName); + } + + /* + * Create a module structure and we can pass around via RTPathTraverseList if needed. + */ + size_t cbName = strlen(pszName) + 1; + pPlugIn = (PDBGFPLUGIN)MMR3HeapAllocZU(pUVM, MM_TAG_DBGF, RT_UOFFSETOF_DYN(DBGFPLUGIN, szName[cbName])); + if (RT_UNLIKELY(!pPlugIn)) + { + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); + return VERR_NO_MEMORY; + } + memcpy(pPlugIn->szName, pszName, cbName); + pPlugIn->cchName = (uint8_t)cbName - 1; + Assert(pPlugIn->cchName == cbName - 1); + + /* + * If the caller specified a path, try load exactly what was specified. + */ + int rc; + if (RTPathHavePath(pszMaybeModule)) + rc = dbgfR3PlugInTryLoad(pPlugIn, pszMaybeModule, pErrInfo); + else + { + /* + * No path specified, search for the plug-in using the canonical + * module name for it. + */ + RTErrInfoClear(pErrInfo); + + /* 1. The private architecture directory. */ + char szPath[_4K]; + rc = RTPathAppPrivateArch(szPath, sizeof(szPath)); + if (RT_SUCCESS(rc)) + rc = RTPathTraverseList(szPath, '\0', dbgfR3PlugInLoadCallback, pPlugIn, pErrInfo); + if (RT_FAILURE_NP(rc)) + { + /* 2. The config value 'PlugInPath' */ + int rc2 = CFGMR3QueryString(CFGMR3GetChild(CFGMR3GetRootU(pUVM), "/DBGF"), "PlugInPath", szPath, sizeof(szPath)); + if (RT_SUCCESS(rc2)) + rc = RTPathTraverseList(szPath, ';', dbgfR3PlugInLoadCallback, pPlugIn, pErrInfo); + if (RT_FAILURE_NP(rc)) + { + /* 3. The VBOXDBG_PLUG_IN_PATH environment variable. */ + rc2 = RTEnvGetEx(RTENV_DEFAULT, "VBOXDBG_PLUG_IN_PATH", szPath, sizeof(szPath), NULL); + if (RT_SUCCESS(rc2)) + rc = RTPathTraverseList(szPath, ';', dbgfR3PlugInLoadCallback, pPlugIn, pErrInfo); + } + } + + if (rc == VERR_END_OF_STRING) + rc = VERR_FILE_NOT_FOUND; + if (pErrInfo && !RTErrInfoIsSet(pErrInfo)) + RTErrInfoSetF(pErrInfo, rc, "Failed to locate '%s'", pPlugIn->szName); + } + if (RT_SUCCESS(rc)) + { + /* + * Try initialize it. + */ + rc = pPlugIn->pfnEntry(DBGFPLUGINOP_INIT, pUVM, VBOX_VERSION); + if (RT_SUCCESS(rc)) + { + /* + * Link it and we're good. + */ + pPlugIn->pNext = pUVM->dbgf.s.pPlugInHead; + pUVM->dbgf.s.pPlugInHead = pPlugIn; + + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); + return VINF_SUCCESS; + } + + RTErrInfoSet(pErrInfo, rc, "Plug-in init failed"); + LogRel(("DBGF: Plug-in '%s' failed during init: %Rrc\n", pPlugIn->szName, rc)); + RTLdrClose(pPlugIn->hLdrMod); + } + MMR3HeapFree(pPlugIn); + + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); + return rc; +} + + +/** + * Load a debugging plug-in. + * + * @returns VBox status code. + * @retval VERR_ALREADY_EXISTS if the module was already loaded. + * @retval VINF_BUFFER_OVERFLOW if the actual plug-in name buffer was too small + * (the plug-in was still successfully loaded). + * @param pUVM Pointer to the user-mode VM structure. + * @param pszPlugIn The plug-in name. This may specify the exact path to + * the plug-in module, or it may just specify the core name + * of the plug-in without prefix, suffix and path. + * @param pszActual Buffer to return the actual plug-in name in. Optional. + * This will be returned on VERR_ALREADY_EXSIST too. + * @param cbActual The size of @a pszActual. + * @param pErrInfo Optional error information structure. + */ +VMMR3DECL(int) DBGFR3PlugInLoad(PUVM pUVM, const char *pszPlugIn, char *pszActual, size_t cbActual, PRTERRINFO pErrInfo) +{ + UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); + AssertPtrReturn(pszPlugIn, VERR_INVALID_PARAMETER); + + /* + * Extract the plug-in name. Copy it to the return buffer as we'll want to + * return it in the VERR_ALREADY_EXISTS case too. + */ + char szName[DBGFPLUGIN_MAX_NAME]; + int rc = dbgfPlugInExtractName(szName, pszPlugIn, pErrInfo); + if (RT_SUCCESS(rc)) + { + int rc2 = VINF_SUCCESS; + if (pszActual) + rc2 = RTStrCopy(pszActual, cbActual, szName); + + /* + * Write lock releated DBGF bits and try load it. + */ + rc = VMR3ReqPriorityCallWaitU(pUVM, 0 /*idDstCpu*/, (PFNRT)dbgfR3PlugInLoad, 4, pUVM, szName, pszPlugIn, pErrInfo); + if (rc2 != VINF_SUCCESS && RT_SUCCESS(rc)) + rc = VINF_BUFFER_OVERFLOW; + } + + return rc; +} + + +/** + * Load all plug-ins from the architechture private directory of VBox. + * + * @param pUVM Pointer to the user-mode VM structure. + */ +VMMR3DECL(void) DBGFR3PlugInLoadAll(PUVM pUVM) +{ + UVM_ASSERT_VALID_EXT_RETURN_VOID(pUVM); + + /* + * Pass it on to EMT(0) if necessary (thanks to DBGFR3Os*). + */ + if (VMR3GetVMCPUId(pUVM->pVM) != 0) + { + VMR3ReqPriorityCallVoidWaitU(pUVM, 0 /*idDstCpu*/, (PFNRT)DBGFR3PlugInLoadAll, 1, pUVM); + return; + } + + + /* + * Open the architecture specific directory with a filter on our prefix + * and names including a dot. + */ + const char *pszSuff = RTLdrGetSuff(); + size_t cchSuff = strlen(pszSuff); + + char szPath[RTPATH_MAX]; + int rc = RTPathAppPrivateArch(szPath, sizeof(szPath) - cchSuff); + AssertRCReturnVoid(rc); + size_t offDir = strlen(szPath); + + rc = RTPathAppend(szPath, sizeof(szPath) - cchSuff, DBGF_PLUG_IN_PREFIX "*"); + AssertRCReturnVoid(rc); + strcat(szPath, pszSuff); + + RTDIR hDir; + rc = RTDirOpenFiltered(&hDir, szPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /* + * Now read it and try load each of the plug-in modules. + */ + RTDIRENTRY DirEntry; + while (RT_SUCCESS(RTDirRead(hDir, &DirEntry, NULL))) + { + szPath[offDir] = '\0'; + rc = RTPathAppend(szPath, sizeof(szPath), DirEntry.szName); + if (RT_SUCCESS(rc)) + { + char szName[DBGFPLUGIN_MAX_NAME]; + rc = dbgfPlugInExtractName(szName, DirEntry.szName, NULL); + if (RT_SUCCESS(rc)) + { + DBGF_PLUG_IN_WRITE_LOCK(pUVM); + dbgfR3PlugInLoad(pUVM, szName, szPath, NULL); + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); + } + } + } + + RTDirClose(hDir); + } +} + + +/** + * Unloads a plug-in by name (no path, prefix or suffix). + * + * @returns VBox status code. + * @retval VERR_NOT_FOUND if the specified plug-in wasn't found. + * @param pUVM Pointer to the user-mode VM structure. + * @param pszName The name of the plug-in to unload. + */ +VMMR3DECL(int) DBGFR3PlugInUnload(PUVM pUVM, const char *pszName) +{ + UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); + + /* + * Pass it on to EMT(0) if necessary (thanks to DBGFR3Os*). + */ + if (VMR3GetVMCPUId(pUVM->pVM) != 0) + return VMR3ReqPriorityCallWaitU(pUVM, 0 /*idDstCpu*/, (PFNRT)DBGFR3PlugInUnload, 2, pUVM, pszName); + + + /* + * Find the plug-in. + */ + DBGF_PLUG_IN_WRITE_LOCK(pUVM); + + int rc; + PDBGFPLUGIN pPrevPlugIn; + PDBGFPLUGIN pPlugIn = dbgfR3PlugInLocate(pUVM, pszName, &pPrevPlugIn); + if (pPlugIn) + { + /* + * Unlink, terminate, unload and free the plug-in. + */ + if (pPrevPlugIn) + pPrevPlugIn->pNext = pPlugIn->pNext; + else + pUVM->dbgf.s.pPlugInHead = pPlugIn->pNext; + + pPlugIn->pfnEntry(DBGFPLUGINOP_TERM, pUVM, 0); + RTLdrClose(pPlugIn->hLdrMod); + + pPlugIn->pfnEntry = NULL; + pPlugIn->hLdrMod = NIL_RTLDRMOD; + MMR3HeapFree(pPlugIn->pNext); + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; + + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); + return rc; +} + + +/** + * Unload all plug-ins. + * + * @param pUVM Pointer to the user-mode VM structure. + */ +static DECLCALLBACK(void) dbgfPlugInUnloadAll(PUVM pUVM) +{ + DBGF_PLUG_IN_WRITE_LOCK(pUVM); + + while (pUVM->dbgf.s.pPlugInHead) + { + PDBGFPLUGIN pPlugin = pUVM->dbgf.s.pPlugInHead; + pUVM->dbgf.s.pPlugInHead = pPlugin->pNext; + + pPlugin->pfnEntry(DBGFPLUGINOP_TERM, pUVM, 0); + + int rc2 = RTLdrClose(pPlugin->hLdrMod); + AssertRC(rc2); + + pPlugin->pfnEntry = NULL; + pPlugin->hLdrMod = NIL_RTLDRMOD; + MMR3HeapFree(pPlugin); + } + + DBGF_PLUG_IN_WRITE_UNLOCK(pUVM); +} + + +/** + * Unloads all plug-ins. + * + * @param pUVM Pointer to the user-mode VM structure. + */ +VMMR3DECL(void) DBGFR3PlugInUnloadAll(PUVM pUVM) +{ + UVM_ASSERT_VALID_EXT_RETURN_VOID(pUVM); + /* Thanks to DBGFR3Os, this must be done on EMT(0). */ + VMR3ReqPriorityCallVoidWaitU(pUVM, 0 /*idDstCpu*/, (PFNRT)dbgfPlugInUnloadAll, 1, pUVM); +} + + + +/** + * @callback_method_impl{FNDBGFHANDLERINT, The 'plugins' info item.} + */ +static DECLCALLBACK(void) dbgfR3PlugInInfoList(PVM pVM, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PDBGFPLUGIN pPlugIn = pVM->pUVM->dbgf.s.pPlugInHead; + RT_NOREF_PV(pszArgs); + if (pPlugIn) + { + pHlp->pfnPrintf(pHlp, "Debugging plug-in%s: %s", pPlugIn->pNext ? "s" : "", pPlugIn->szName); + while ((pPlugIn = pPlugIn->pNext) != NULL) + pHlp->pfnPrintf(pHlp, ", %s", pPlugIn->szName); + pHlp->pfnPrintf(pHlp, "\n"); + + } + else + pHlp->pfnPrintf(pHlp, "No plug-ins loaded\n"); +} + |