summaryrefslogtreecommitdiffstats
path: root/src/VBox/Debugger
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Debugger')
-rw-r--r--src/VBox/Debugger/.scm-settings29
-rw-r--r--src/VBox/Debugger/DBGCBuiltInSymbols.cpp51
-rw-r--r--src/VBox/Debugger/DBGCCmdHlp.cpp1484
-rw-r--r--src/VBox/Debugger/DBGCCmdWorkers.cpp377
-rw-r--r--src/VBox/Debugger/DBGCCommands.cpp2072
-rw-r--r--src/VBox/Debugger/DBGCDumpImage.cpp807
-rw-r--r--src/VBox/Debugger/DBGCEmulateCodeView.cpp6982
-rw-r--r--src/VBox/Debugger/DBGCEval.cpp1663
-rw-r--r--src/VBox/Debugger/DBGCFunctions.cpp128
-rw-r--r--src/VBox/Debugger/DBGCGdbRemoteStub.cpp2864
-rw-r--r--src/VBox/Debugger/DBGCInternal.h678
-rw-r--r--src/VBox/Debugger/DBGCIo.cpp611
-rw-r--r--src/VBox/Debugger/DBGCIoProvInternal.h115
-rw-r--r--src/VBox/Debugger/DBGCIoProvIpc.cpp244
-rw-r--r--src/VBox/Debugger/DBGCIoProvTcp.cpp259
-rw-r--r--src/VBox/Debugger/DBGCIoProvUdp.cpp259
-rw-r--r--src/VBox/Debugger/DBGCOps.cpp1380
-rw-r--r--src/VBox/Debugger/DBGCRemoteKd.cpp4550
-rw-r--r--src/VBox/Debugger/DBGCScreenAscii.cpp445
-rw-r--r--src/VBox/Debugger/DBGConsole.cpp1378
-rw-r--r--src/VBox/Debugger/DBGPlugInCommonELF.cpp96
-rw-r--r--src/VBox/Debugger/DBGPlugInCommonELF.h63
-rw-r--r--src/VBox/Debugger/DBGPlugInCommonELFTmpl.cpp.h347
-rw-r--r--src/VBox/Debugger/DBGPlugInDarwin.cpp1120
-rw-r--r--src/VBox/Debugger/DBGPlugInDiggers.cpp87
-rw-r--r--src/VBox/Debugger/DBGPlugInFreeBsd.cpp961
-rw-r--r--src/VBox/Debugger/DBGPlugInLinux.cpp3045
-rw-r--r--src/VBox/Debugger/DBGPlugInLinuxModuleCodeTmpl.cpp.h560
-rw-r--r--src/VBox/Debugger/DBGPlugInLinuxModuleTableEntryTmpl.cpp.h32
-rw-r--r--src/VBox/Debugger/DBGPlugInLinuxModuleVerTmpl.cpp.h95
-rw-r--r--src/VBox/Debugger/DBGPlugInOS2.cpp1268
-rw-r--r--src/VBox/Debugger/DBGPlugInSolaris.cpp1162
-rw-r--r--src/VBox/Debugger/DBGPlugInWinNt.cpp1776
-rw-r--r--src/VBox/Debugger/DBGPlugIns.h51
-rw-r--r--src/VBox/Debugger/Makefile.kmk136
-rw-r--r--src/VBox/Debugger/VBoxDbg.cpp275
-rw-r--r--src/VBox/Debugger/VBoxDbgBase.cpp326
-rw-r--r--src/VBox/Debugger/VBoxDbgBase.h222
-rw-r--r--src/VBox/Debugger/VBoxDbgConsole.cpp1026
-rw-r--r--src/VBox/Debugger/VBoxDbgConsole.h447
-rw-r--r--src/VBox/Debugger/VBoxDbgDisas.h53
-rw-r--r--src/VBox/Debugger/VBoxDbgGui.cpp313
-rw-r--r--src/VBox/Debugger/VBoxDbgGui.h225
-rw-r--r--src/VBox/Debugger/VBoxDbgStatsQt.cpp3329
-rw-r--r--src/VBox/Debugger/VBoxDbgStatsQt.h272
-rw-r--r--src/VBox/Debugger/testcase/Makefile.kup0
-rw-r--r--src/VBox/Debugger/testcase/tstDBGCParser.cpp1300
-rw-r--r--src/VBox/Debugger/testcase/tstDBGCStubs.cpp862
48 files changed, 45825 insertions, 0 deletions
diff --git a/src/VBox/Debugger/.scm-settings b/src/VBox/Debugger/.scm-settings
new file mode 100644
index 00000000..7d2233ad
--- /dev/null
+++ b/src/VBox/Debugger/.scm-settings
@@ -0,0 +1,29 @@
+# $Id: .scm-settings $
+## @file
+# Source code massager settings for the Debugger.
+#
+
+#
+# Copyright (C) 2019-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
+#
+
+/*.h: --guard-relative-to-dir . --guard-prefix DEBUGGER_INCLUDED_SRC_
+
diff --git a/src/VBox/Debugger/DBGCBuiltInSymbols.cpp b/src/VBox/Debugger/DBGCBuiltInSymbols.cpp
new file mode 100644
index 00000000..727d2c09
--- /dev/null
+++ b/src/VBox/Debugger/DBGCBuiltInSymbols.cpp
@@ -0,0 +1,51 @@
+/* $Id: DBGCBuiltInSymbols.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Built-In Symbols.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include "DBGCInternal.h"
+
+
+
+/**
+ * Finds a builtin symbol.
+ *
+ * @returns Pointer to symbol descriptor on success.
+ * @returns NULL on failure.
+ * @param pDbgc The debug console instance.
+ * @param pszSymbol The symbol name.
+ */
+PCDBGCSYM dbgcLookupRegisterSymbol(PDBGC pDbgc, const char *pszSymbol)
+{
+ /** @todo externally registered symbols. */
+ RT_NOREF2(pDbgc, pszSymbol);
+ return NULL;
+}
+
diff --git a/src/VBox/Debugger/DBGCCmdHlp.cpp b/src/VBox/Debugger/DBGCCmdHlp.cpp
new file mode 100644
index 00000000..0ec6e906
--- /dev/null
+++ b/src/VBox/Debugger/DBGCCmdHlp.cpp
@@ -0,0 +1,1484 @@
+/* $Id: DBGCCmdHlp.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Command Helpers.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/param.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "DBGCInternal.h"
+
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnPrintf}
+ */
+static DECLCALLBACK(int) dbgcHlpPrintf(PDBGCCMDHLP pCmdHlp, size_t *pcbWritten, const char *pszFormat, ...)
+{
+ /*
+ * Do the formatting and output.
+ */
+ va_list args;
+ va_start(args, pszFormat);
+ int rc = pCmdHlp->pfnPrintfV(pCmdHlp, pcbWritten, pszFormat, args);
+ va_end(args);
+
+ return rc;
+}
+
+
+/**
+ * Outputs a string in quotes.
+ *
+ * @returns The number of bytes formatted.
+ * @param pfnOutput Pointer to output function.
+ * @param pvArgOutput Argument for the output function.
+ * @param chQuote The quote character.
+ * @param psz The string to quote.
+ * @param cch The string length.
+ */
+static size_t dbgcStringOutputInQuotes(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, char chQuote, const char *psz, size_t cch)
+{
+ size_t cchOutput = pfnOutput(pvArgOutput, &chQuote, 1);
+
+ while (cch > 0)
+ {
+ char *pchQuote = (char *)memchr(psz, chQuote, cch);
+ if (!pchQuote)
+ {
+ cchOutput += pfnOutput(pvArgOutput, psz, cch);
+ break;
+ }
+ size_t cchSub = pchQuote - psz + 1;
+ cchOutput += pfnOutput(pvArgOutput, psz, cchSub);
+ cchOutput += pfnOutput(pvArgOutput, &chQuote, 1);
+ cch -= cchSub;
+ psz += cchSub;
+ }
+
+ cchOutput += pfnOutput(pvArgOutput, &chQuote, 1);
+ return cchOutput;
+}
+
+
+/**
+ * Callback to format non-standard format specifiers, employed by dbgcPrintfV
+ * and others.
+ *
+ * @returns The number of bytes formatted.
+ * @param pvArg Formatter argument.
+ * @param pfnOutput Pointer to output function.
+ * @param pvArgOutput Argument for the output function.
+ * @param ppszFormat Pointer to the format string pointer. Advance this till the char
+ * after the format specifier.
+ * @param pArgs Pointer to the argument list. Use this to fetch the arguments.
+ * @param cchWidth Format Width. -1 if not specified.
+ * @param cchPrecision Format Precision. -1 if not specified.
+ * @param fFlags Flags (RTSTR_NTFS_*).
+ * @param chArgSize The argument size specifier, 'l' or 'L'.
+ */
+static DECLCALLBACK(size_t) dbgcStringFormatter(void *pvArg, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char **ppszFormat, va_list *pArgs, int cchWidth,
+ int cchPrecision, unsigned fFlags, char chArgSize)
+{
+ NOREF(cchWidth); NOREF(cchPrecision); NOREF(fFlags); NOREF(chArgSize); NOREF(pvArg);
+ if (**ppszFormat != 'D')
+ {
+ (*ppszFormat)++;
+ return 0;
+ }
+
+ (*ppszFormat)++;
+ switch (**ppszFormat)
+ {
+ /*
+ * Print variable without range.
+ * The argument is a const pointer to the variable.
+ */
+ case 'V':
+ {
+ (*ppszFormat)++;
+ PCDBGCVAR pVar = va_arg(*pArgs, PCDBGCVAR);
+ switch (pVar->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%%RGv", pVar->u.GCFlat);
+ case DBGCVAR_TYPE_GC_FAR:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%04x:%08x", pVar->u.GCFar.sel, pVar->u.GCFar.off);
+ case DBGCVAR_TYPE_GC_PHYS:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%%%%RGp", pVar->u.GCPhys);
+ case DBGCVAR_TYPE_HC_FLAT:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%#%RHv", (uintptr_t)pVar->u.pvHCFlat);
+ case DBGCVAR_TYPE_HC_PHYS:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "#%%%%%RHp", pVar->u.HCPhys);
+ case DBGCVAR_TYPE_NUMBER:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%llx", pVar->u.u64Number);
+ case DBGCVAR_TYPE_STRING:
+ return dbgcStringOutputInQuotes(pfnOutput, pvArgOutput, '"', pVar->u.pszString, (size_t)pVar->u64Range);
+ case DBGCVAR_TYPE_SYMBOL:
+ return dbgcStringOutputInQuotes(pfnOutput, pvArgOutput, '\'', pVar->u.pszString, (size_t)pVar->u64Range);
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ default:
+ return pfnOutput(pvArgOutput, "??", 2);
+ }
+ }
+
+ /*
+ * Print variable with range.
+ * The argument is a const pointer to the variable.
+ */
+ case 'v':
+ {
+ (*ppszFormat)++;
+ PCDBGCVAR pVar = va_arg(*pArgs, PCDBGCVAR);
+
+ char szRange[32];
+ switch (pVar->enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE:
+ szRange[0] = '\0';
+ break;
+ case DBGCVAR_RANGE_ELEMENTS:
+ RTStrPrintf(szRange, sizeof(szRange), " L %llx", pVar->u64Range);
+ break;
+ case DBGCVAR_RANGE_BYTES:
+ RTStrPrintf(szRange, sizeof(szRange), " LB %llx", pVar->u64Range);
+ break;
+ }
+
+ switch (pVar->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%%RGv%s", pVar->u.GCFlat, szRange);
+ case DBGCVAR_TYPE_GC_FAR:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%04x:%08x%s", pVar->u.GCFar.sel, pVar->u.GCFar.off, szRange);
+ case DBGCVAR_TYPE_GC_PHYS:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%%%%RGp%s", pVar->u.GCPhys, szRange);
+ case DBGCVAR_TYPE_HC_FLAT:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%#%RHv%s", (uintptr_t)pVar->u.pvHCFlat, szRange);
+ case DBGCVAR_TYPE_HC_PHYS:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "#%%%%%RHp%s", pVar->u.HCPhys, szRange);
+ case DBGCVAR_TYPE_NUMBER:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%llx%s", pVar->u.u64Number, szRange);
+ case DBGCVAR_TYPE_STRING:
+ return dbgcStringOutputInQuotes(pfnOutput, pvArgOutput, '"', pVar->u.pszString, (size_t)pVar->u64Range);
+ case DBGCVAR_TYPE_SYMBOL:
+ return dbgcStringOutputInQuotes(pfnOutput, pvArgOutput, '\'', pVar->u.pszString, (size_t)pVar->u64Range);
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ default:
+ return pfnOutput(pvArgOutput, "??", 2);
+ }
+ }
+
+ default:
+ AssertMsgFailed(("Invalid format type '%s'!\n", **ppszFormat));
+ return 0;
+ }
+}
+
+
+/**
+ * Output callback employed by dbgcHlpPrintfV.
+ *
+ * @returns number of bytes written.
+ * @param pvArg User argument.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) dbgcFormatOutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PDBGC pDbgc = (PDBGC)pvArg;
+ if (cbChars)
+ {
+ int rc = pDbgc->pfnOutput(pDbgc->pvOutputUser, pachChars, cbChars);
+ if (RT_SUCCESS(rc))
+ pDbgc->chLastOutput = pachChars[cbChars - 1];
+ else
+ {
+ pDbgc->rcOutput = rc;
+ cbChars = 0;
+ }
+ }
+
+ return cbChars;
+}
+
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnPrintfV}
+ */
+static DECLCALLBACK(int) dbgcHlpPrintfV(PDBGCCMDHLP pCmdHlp, size_t *pcbWritten, const char *pszFormat, va_list args)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Do the formatting and output.
+ */
+ pDbgc->rcOutput = 0;
+ size_t cb = RTStrFormatV(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, pszFormat, args);
+
+ if (pcbWritten)
+ *pcbWritten = cb;
+
+ return pDbgc->rcOutput;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnStrPrintfV}
+ */
+static DECLCALLBACK(size_t) dbgcHlpStrPrintfV(PDBGCCMDHLP pCmdHlp, char *pszBuf, size_t cbBuf,
+ const char *pszFormat, va_list va)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ return RTStrPrintfExV(dbgcStringFormatter, pDbgc, pszBuf, cbBuf, pszFormat, va);
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnStrPrintf}
+ */
+static DECLCALLBACK(size_t) dbgcHlpStrPrintf(PDBGCCMDHLP pCmdHlp, char *pszBuf, size_t cbBuf, const char *pszFormat, ...)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ va_list va;
+ va_start(va, pszFormat);
+ size_t cch = RTStrPrintfExV(dbgcStringFormatter, pDbgc, pszBuf, cbBuf, pszFormat, va);
+ va_end(va);
+ return cch;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVBoxErrorV}
+ */
+static DECLCALLBACK(int) dbgcHlpVBoxErrorV(PDBGCCMDHLP pCmdHlp, int rc, const char *pszFormat, va_list args)
+{
+ switch (rc)
+ {
+ case VINF_SUCCESS:
+ break;
+
+ default:
+ rc = pCmdHlp->pfnPrintf(pCmdHlp, NULL, "error: %Rrc: %s", rc, pszFormat ? " " : "\n");
+ if (RT_SUCCESS(rc) && pszFormat)
+ rc = pCmdHlp->pfnPrintfV(pCmdHlp, NULL, pszFormat, args);
+ if (RT_SUCCESS(rc))
+ rc = VERR_DBGC_COMMAND_FAILED;
+ break;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVBoxError}
+ */
+static DECLCALLBACK(int) dbgcHlpVBoxError(PDBGCCMDHLP pCmdHlp, int rc, const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ int rcRet = pCmdHlp->pfnVBoxErrorV(pCmdHlp, rc, pszFormat, args);
+ va_end(args);
+ return rcRet;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnMemRead}
+ */
+static DECLCALLBACK(int) dbgcHlpMemRead(PDBGCCMDHLP pCmdHlp, void *pvBuffer, size_t cbRead, PCDBGCVAR pVarPointer, size_t *pcbRead)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ DBGFADDRESS Address;
+ int rc;
+
+ /*
+ * Dummy check.
+ */
+ if (cbRead == 0)
+ {
+ if (*pcbRead)
+ *pcbRead = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Convert Far addresses getting size and the correct base address.
+ * Getting and checking the size is what makes this messy and slow.
+ */
+ DBGCVAR Var = *pVarPointer;
+ switch (pVarPointer->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FAR:
+ /* Use DBGFR3AddrFromSelOff for the conversion. */
+ Assert(pDbgc->pUVM);
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Address, Var.u.GCFar.sel, Var.u.GCFar.off);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* don't bother with flat selectors (for now). */
+ if (!DBGFADDRESS_IS_FLAT(&Address))
+ {
+ DBGFSELINFO SelInfo;
+ rc = DBGFR3SelQueryInfo(pDbgc->pUVM, pDbgc->idCpu, Address.Sel,
+ DBGFSELQI_FLAGS_DT_GUEST | DBGFSELQI_FLAGS_DT_ADJ_64BIT_MODE, &SelInfo);
+ if (RT_SUCCESS(rc))
+ {
+ RTGCUINTPTR cb; /* -1 byte */
+ if (DBGFSelInfoIsExpandDown(&SelInfo))
+ {
+ if ( !SelInfo.u.Raw.Gen.u1Granularity
+ && Address.off > UINT16_C(0xffff))
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ if (Address.off <= SelInfo.cbLimit)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ cb = (SelInfo.u.Raw.Gen.u1Granularity ? UINT32_C(0xffffffff) : UINT32_C(0xffff)) - Address.off;
+ }
+ else
+ {
+ if (Address.off > SelInfo.cbLimit)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ cb = SelInfo.cbLimit - Address.off;
+ }
+ if (cbRead - 1 > cb)
+ {
+ if (!pcbRead)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ cbRead = cb + 1;
+ }
+ }
+ }
+ Var.enmType = DBGCVAR_TYPE_GC_FLAT;
+ Var.u.GCFlat = Address.FlatPtr;
+ break;
+
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ break;
+
+ default:
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+
+
+ /*
+ * Copy page by page.
+ */
+ size_t cbLeft = cbRead;
+ for (;;)
+ {
+ /*
+ * Calc read size.
+ */
+ size_t cb = RT_MIN(PAGE_SIZE, cbLeft);
+ switch (pVarPointer->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT: cb = RT_MIN(cb, PAGE_SIZE - (Var.u.GCFlat & PAGE_OFFSET_MASK)); break;
+ case DBGCVAR_TYPE_GC_PHYS: cb = RT_MIN(cb, PAGE_SIZE - (Var.u.GCPhys & PAGE_OFFSET_MASK)); break;
+ case DBGCVAR_TYPE_HC_FLAT: cb = RT_MIN(cb, PAGE_SIZE - ((uintptr_t)Var.u.pvHCFlat & PAGE_OFFSET_MASK)); break;
+ case DBGCVAR_TYPE_HC_PHYS: cb = RT_MIN(cb, PAGE_SIZE - ((size_t)Var.u.HCPhys & PAGE_OFFSET_MASK)); break; /* size_t: MSC has braindead loss of data warnings! */
+ default: break;
+ }
+
+ /*
+ * Perform read.
+ */
+ switch (Var.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ rc = DBGFR3MemRead(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Address, Var.u.GCFlat),
+ pvBuffer, cb);
+ break;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ rc = DBGFR3MemRead(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromPhys(pDbgc->pUVM, &Address, Var.u.GCPhys),
+ pvBuffer, cb);
+ break;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ {
+ DBGCVAR Var2;
+ rc = dbgcOpAddrFlat(pDbgc, &Var, DBGCVAR_CAT_ANY, &Var2);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pvBuffer, Var2.u.pvHCFlat, cb);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+ break;
+ }
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ default:
+ rc = VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+
+ /*
+ * Check for failure.
+ */
+ if (RT_FAILURE(rc))
+ {
+ if (pcbRead && (*pcbRead = cbRead - cbLeft) > 0)
+ return VINF_SUCCESS;
+ return rc;
+ }
+
+ /*
+ * Next.
+ */
+ cbLeft -= cb;
+ if (!cbLeft)
+ break;
+ pvBuffer = (char *)pvBuffer + cb;
+ rc = DBGCCmdHlpEval(pCmdHlp, &Var, "%DV + %#zx", &Var, cb);
+ if (RT_FAILURE(rc))
+ {
+ if (pcbRead && (*pcbRead = cbRead - cbLeft) > 0)
+ return VINF_SUCCESS;
+ return rc;
+ }
+ }
+
+ /*
+ * Done
+ */
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return 0;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnMemWrite}
+ */
+static DECLCALLBACK(int) dbgcHlpMemWrite(PDBGCCMDHLP pCmdHlp, const void *pvBuffer, size_t cbWrite, PCDBGCVAR pVarPointer, size_t *pcbWritten)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ DBGFADDRESS Address;
+ int rc;
+
+ /*
+ * Dummy check.
+ */
+ if (cbWrite == 0)
+ {
+ if (*pcbWritten)
+ *pcbWritten = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Convert Far addresses getting size and the correct base address.
+ * Getting and checking the size is what makes this messy and slow.
+ */
+ DBGCVAR Var = *pVarPointer;
+ switch (pVarPointer->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FAR:
+ {
+ /* Use DBGFR3AddrFromSelOff for the conversion. */
+ Assert(pDbgc->pUVM);
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Address, Var.u.GCFar.sel, Var.u.GCFar.off);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* don't bother with flat selectors (for now). */
+ if (!DBGFADDRESS_IS_FLAT(&Address))
+ {
+ DBGFSELINFO SelInfo;
+ rc = DBGFR3SelQueryInfo(pDbgc->pUVM, pDbgc->idCpu, Address.Sel,
+ DBGFSELQI_FLAGS_DT_GUEST | DBGFSELQI_FLAGS_DT_ADJ_64BIT_MODE, &SelInfo);
+ if (RT_SUCCESS(rc))
+ {
+ RTGCUINTPTR cb; /* -1 byte */
+ if (DBGFSelInfoIsExpandDown(&SelInfo))
+ {
+ if ( !SelInfo.u.Raw.Gen.u1Granularity
+ && Address.off > UINT16_C(0xffff))
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ if (Address.off <= SelInfo.cbLimit)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ cb = (SelInfo.u.Raw.Gen.u1Granularity ? UINT32_C(0xffffffff) : UINT32_C(0xffff)) - Address.off;
+ }
+ else
+ {
+ if (Address.off > SelInfo.cbLimit)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ cb = SelInfo.cbLimit - Address.off;
+ }
+ if (cbWrite - 1 > cb)
+ {
+ if (!pcbWritten)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+ cbWrite = cb + 1;
+ }
+ }
+ }
+ Var.enmType = DBGCVAR_TYPE_GC_FLAT;
+ Var.u.GCFlat = Address.FlatPtr;
+ }
+ RT_FALL_THRU();
+ case DBGCVAR_TYPE_GC_FLAT:
+ rc = DBGFR3MemWrite(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Address, Var.u.GCFlat),
+ pvBuffer, cbWrite);
+ if (pcbWritten && RT_SUCCESS(rc))
+ *pcbWritten = cbWrite;
+ return rc;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ rc = DBGFR3MemWrite(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromPhys(pDbgc->pUVM, &Address, Var.u.GCPhys),
+ pvBuffer, cbWrite);
+ if (pcbWritten && RT_SUCCESS(rc))
+ *pcbWritten = cbWrite;
+ return rc;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ {
+ /*
+ * Copy HC memory page by page.
+ */
+ if (pcbWritten)
+ *pcbWritten = 0;
+ while (cbWrite > 0)
+ {
+ /* convert to flat address */
+ DBGCVAR Var2;
+ rc = dbgcOpAddrFlat(pDbgc, &Var, DBGCVAR_CAT_ANY, &Var2);
+ if (RT_FAILURE(rc))
+ {
+ if (pcbWritten && *pcbWritten)
+ return -VERR_INVALID_POINTER;
+ return VERR_INVALID_POINTER;
+ }
+
+ /* calc size. */
+ size_t cbChunk = PAGE_SIZE;
+ cbChunk -= (uintptr_t)Var.u.pvHCFlat & PAGE_OFFSET_MASK;
+ if (cbChunk > cbWrite)
+ cbChunk = cbWrite;
+
+ memcpy(Var2.u.pvHCFlat, pvBuffer, cbChunk);
+
+ /* advance */
+ if (Var.enmType == DBGCVAR_TYPE_HC_FLAT)
+ Var.u.pvHCFlat = (uint8_t *)Var.u.pvHCFlat + cbChunk;
+ else
+ Var.u.HCPhys += cbChunk;
+ pvBuffer = (uint8_t const *)pvBuffer + cbChunk;
+ if (pcbWritten)
+ *pcbWritten += cbChunk;
+ cbWrite -= cbChunk;
+ }
+
+ return VINF_SUCCESS;
+ }
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ return VERR_NOT_SUPPORTED;
+
+ default:
+ return VERR_NOT_IMPLEMENTED;
+ }
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnExec}
+ */
+static DECLCALLBACK(int) dbgcHlpExec(PDBGCCMDHLP pCmdHlp, const char *pszExpr, ...)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ /* Save the scratch state. */
+ char *pszScratch = pDbgc->pszScratch;
+ unsigned iArg = pDbgc->iArg;
+
+ /*
+ * Format the expression.
+ */
+ va_list args;
+ va_start(args, pszExpr);
+ size_t cbScratch = sizeof(pDbgc->achScratch) - (pDbgc->pszScratch - &pDbgc->achScratch[0]);
+ size_t cb = RTStrPrintfExV(dbgcStringFormatter, pDbgc, pDbgc->pszScratch, cbScratch, pszExpr, args);
+ va_end(args);
+ if (cb >= cbScratch)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Execute the command.
+ * We save and restore the arg index and scratch buffer pointer.
+ */
+ pDbgc->pszScratch = pDbgc->pszScratch + cb + 1;
+ int rc = dbgcEvalCommand(pDbgc, pszScratch, cb, false /* fNoExecute */);
+
+ /* Restore the scratch state. */
+ pDbgc->iArg = iArg;
+ pDbgc->pszScratch = pszScratch;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnEvalV}
+ */
+static DECLCALLBACK(int) dbgcHlpEvalV(PDBGCCMDHLP pCmdHlp, PDBGCVAR pResult, const char *pszExpr, va_list va)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Format the expression.
+ */
+ char szExprFormatted[2048];
+ size_t cb = RTStrPrintfExV(dbgcStringFormatter, pDbgc, szExprFormatted, sizeof(szExprFormatted), pszExpr, va);
+ /* ignore overflows. */
+
+ return dbgcEvalSub(pDbgc, &szExprFormatted[0], cb, DBGCVAR_CAT_ANY, pResult);
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnFailV}
+ */
+static DECLCALLBACK(int) dbgcHlpFailV(PDBGCCMDHLP pCmdHlp, PCDBGCCMD pCmd, const char *pszFormat, va_list va)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Do the formatting and output.
+ */
+ pDbgc->rcOutput = VINF_SUCCESS;
+ RTStrFormat(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, "%s: error: ", pCmd->pszCmd);
+ if (RT_FAILURE(pDbgc->rcOutput))
+ return pDbgc->rcOutput;
+ RTStrFormatV(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, pszFormat, va);
+ if (RT_FAILURE(pDbgc->rcOutput))
+ return pDbgc->rcOutput;
+ if (pDbgc->chLastOutput != '\n')
+ dbgcFormatOutput(pDbgc, "\n", 1);
+ return VERR_DBGC_COMMAND_FAILED;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnFailRcV}
+ */
+static DECLCALLBACK(int) dbgcHlpFailRcV(PDBGCCMDHLP pCmdHlp, PCDBGCCMD pCmd, int rc, const char *pszFormat, va_list va)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Do the formatting and output.
+ */
+ pDbgc->rcOutput = VINF_SUCCESS;
+ RTStrFormat(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, "%s: error: ", pCmd->pszCmd);
+ if (RT_FAILURE(pDbgc->rcOutput))
+ return pDbgc->rcOutput;
+ RTStrFormatV(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, pszFormat, va);
+ if (RT_FAILURE(pDbgc->rcOutput))
+ return pDbgc->rcOutput;
+ RTStrFormat(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, ": %Rrc\n", rc);
+ if (RT_FAILURE(pDbgc->rcOutput))
+ return pDbgc->rcOutput;
+
+ return VERR_DBGC_COMMAND_FAILED;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnParserError}
+ */
+static DECLCALLBACK(int) dbgcHlpParserError(PDBGCCMDHLP pCmdHlp, PCDBGCCMD pCmd, int iArg, const char *pszExpr, unsigned iLine)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Do the formatting and output.
+ */
+ pDbgc->rcOutput = VINF_SUCCESS;
+ RTStrFormat(dbgcFormatOutput, pDbgc, dbgcStringFormatter, pDbgc, "%s: parser error: iArg=%d iLine=%u pszExpr=%s\n",
+ pCmd->pszCmd, iArg, iLine, pszExpr);
+ if (RT_FAILURE(pDbgc->rcOutput))
+ return pDbgc->rcOutput;
+ return VERR_DBGC_COMMAND_FAILED;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVarToDbgfAddr}
+ */
+static DECLCALLBACK(int) dbgcHlpVarToDbgfAddr(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, PDBGFADDRESS pAddress)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ AssertPtr(pVar);
+ AssertPtr(pAddress);
+
+ switch (pVar->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ DBGFR3AddrFromFlat(pDbgc->pUVM, pAddress, pVar->u.GCFlat);
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_NUMBER:
+ DBGFR3AddrFromFlat(pDbgc->pUVM, pAddress, (RTGCUINTPTR)pVar->u.u64Number);
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, pAddress, pVar->u.GCFar.sel, pVar->u.GCFar.off);
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ DBGFR3AddrFromPhys(pDbgc->pUVM, pAddress, pVar->u.GCPhys);
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ {
+ DBGCVAR Var;
+ int rc = DBGCCmdHlpEval(&pDbgc->CmdHlp, &Var, "%%(%DV)", pVar);
+ if (RT_FAILURE(rc))
+ return rc;
+ return dbgcHlpVarToDbgfAddr(pCmdHlp, &Var, pAddress);
+ }
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ default:
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+ }
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVarFromDbgfAddr}
+ */
+static DECLCALLBACK(int) dbgcHlpVarFromDbgfAddr(PDBGCCMDHLP pCmdHlp, PCDBGFADDRESS pAddress, PDBGCVAR pResult)
+{
+ RT_NOREF1(pCmdHlp);
+ AssertPtrReturn(pAddress, VERR_INVALID_POINTER);
+ AssertReturn(DBGFADDRESS_IS_VALID(pAddress), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pResult, VERR_INVALID_POINTER);
+
+ switch (pAddress->fFlags & DBGFADDRESS_FLAGS_TYPE_MASK)
+ {
+ case DBGFADDRESS_FLAGS_FAR16:
+ case DBGFADDRESS_FLAGS_FAR32:
+ case DBGFADDRESS_FLAGS_FAR64:
+ DBGCVAR_INIT_GC_FAR(pResult, pAddress->Sel, pAddress->off);
+ break;
+
+ case DBGFADDRESS_FLAGS_FLAT:
+ DBGCVAR_INIT_GC_FLAT(pResult, pAddress->FlatPtr);
+ break;
+
+ case DBGFADDRESS_FLAGS_PHYS:
+ DBGCVAR_INIT_GC_PHYS(pResult, pAddress->FlatPtr);
+ break;
+
+ default:
+ DBGCVAR_INIT(pResult);
+ AssertMsgFailedReturn(("%#x\n", pAddress->fFlags), VERR_INVALID_PARAMETER);
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVarToNumber}
+ */
+static DECLCALLBACK(int) dbgcHlpVarToNumber(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, uint64_t *pu64Number)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ NOREF(pDbgc);
+
+ uint64_t u64Number;
+ switch (pVar->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ u64Number = pVar->u.GCFlat;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ u64Number = pVar->u.GCPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ u64Number = (uintptr_t)pVar->u.pvHCFlat;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ u64Number = (uintptr_t)pVar->u.HCPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ u64Number = (uintptr_t)pVar->u.u64Number;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ u64Number = (uintptr_t)pVar->u.GCFar.off;
+ break;
+ case DBGCVAR_TYPE_SYMBOL:
+ /** @todo try convert as symbol? */
+ case DBGCVAR_TYPE_STRING:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE; /** @todo better error code! */
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ *pu64Number = u64Number;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVarToBool}
+ */
+static DECLCALLBACK(int) dbgcHlpVarToBool(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, bool *pf)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ NOREF(pDbgc);
+
+ switch (pVar->enmType)
+ {
+ case DBGCVAR_TYPE_SYMBOL:
+ case DBGCVAR_TYPE_STRING:
+ if ( !RTStrICmp(pVar->u.pszString, "true")
+ || !RTStrICmp(pVar->u.pszString, "on")
+ || !RTStrICmp(pVar->u.pszString, "no")
+ || !RTStrICmp(pVar->u.pszString, "enabled"))
+ {
+ *pf = true;
+ return VINF_SUCCESS;
+ }
+ if ( !RTStrICmp(pVar->u.pszString, "false")
+ || !RTStrICmp(pVar->u.pszString, "off")
+ || !RTStrICmp(pVar->u.pszString, "yes")
+ || !RTStrICmp(pVar->u.pszString, "disabled"))
+ {
+ *pf = false;
+ return VINF_SUCCESS;
+ }
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE; /** @todo better error code! */
+
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ case DBGCVAR_TYPE_NUMBER:
+ *pf = pVar->u.u64Number != 0;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVarGetRange}
+ */
+static DECLCALLBACK(int) dbgcHlpVarGetRange(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, uint64_t cbElement, uint64_t cbDefault,
+ uint64_t *pcbRange)
+{
+ RT_NOREF1(pCmdHlp);
+/** @todo implement this properly, strings/symbols are not resolved now. */
+ switch (pVar->enmRangeType)
+ {
+ default:
+ case DBGCVAR_RANGE_NONE:
+ *pcbRange = cbDefault;
+ break;
+ case DBGCVAR_RANGE_BYTES:
+ *pcbRange = pVar->u64Range;
+ break;
+ case DBGCVAR_RANGE_ELEMENTS:
+ *pcbRange = pVar->u64Range * cbElement;
+ break;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnVarConvert}
+ */
+static DECLCALLBACK(int) dbgcHlpVarConvert(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, DBGCVARTYPE enmToType, bool fConvSyms,
+ PDBGCVAR pResult)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ DBGCVAR const InVar = *pVar; /* if pVar == pResult */
+ PCDBGCVAR pArg = &InVar; /* lazy bird, clean up later */
+ DBGFADDRESS Address;
+ int rc;
+
+ Assert(pDbgc->pUVM);
+
+ *pResult = InVar;
+ switch (InVar.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_GC_PHYS;
+ rc = DBGFR3AddrToPhys(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Address, pArg->u.GCFlat),
+ &pResult->u.GCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->enmType = DBGCVAR_TYPE_HC_FLAT;
+ rc = DBGFR3AddrToVolatileR3Ptr(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Address, pArg->u.GCFlat),
+ false /*fReadOnly */,
+ &pResult->u.pvHCFlat);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_HC_PHYS;
+ rc = DBGFR3AddrToHostPhys(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Address, pArg->u.GCFlat),
+ &pResult->u.GCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->enmType = enmToType;
+ pResult->u.u64Number = InVar.u.GCFlat;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Address, pArg->u.GCFar.sel, pArg->u.GCFar.off);
+ if (RT_SUCCESS(rc))
+ {
+ pResult->enmType = DBGCVAR_TYPE_GC_FLAT;
+ pResult->u.GCFlat = Address.FlatPtr;
+ return VINF_SUCCESS;
+ }
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Address, pArg->u.GCFar.sel, pArg->u.GCFar.off);
+ if (RT_SUCCESS(rc))
+ {
+ pResult->enmType = DBGCVAR_TYPE_GC_PHYS;
+ rc = DBGFR3AddrToPhys(pDbgc->pUVM, pDbgc->idCpu, &Address, &pResult->u.GCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Address, pArg->u.GCFar.sel, pArg->u.GCFar.off);
+ if (RT_SUCCESS(rc))
+ {
+ pResult->enmType = DBGCVAR_TYPE_HC_FLAT;
+ rc = DBGFR3AddrToVolatileR3Ptr(pDbgc->pUVM, pDbgc->idCpu, &Address,
+ false /*fReadOnly*/, &pResult->u.pvHCFlat);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Address, pArg->u.GCFar.sel, pArg->u.GCFar.off);
+ if (RT_SUCCESS(rc))
+ {
+ pResult->enmType = DBGCVAR_TYPE_HC_PHYS;
+ rc = DBGFR3AddrToHostPhys(pDbgc->pUVM, pDbgc->idCpu, &Address, &pResult->u.GCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->enmType = enmToType;
+ pResult->u.u64Number = InVar.u.GCFar.off;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ //rc = MMR3PhysGCPhys2GCVirtEx(pDbgc->pVM, pResult->u.GCPhys, ..., &pResult->u.GCFlat); - yea, sure.
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->enmType = DBGCVAR_TYPE_HC_FLAT;
+ rc = DBGFR3AddrToVolatileR3Ptr(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromPhys(pDbgc->pUVM, &Address, pArg->u.GCPhys),
+ false /*fReadOnly */,
+ &pResult->u.pvHCFlat);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_HC_PHYS;
+ rc = DBGFR3AddrToHostPhys(pDbgc->pUVM, pDbgc->idCpu,
+ DBGFR3AddrFromPhys(pDbgc->pUVM, &Address, pArg->u.GCPhys),
+ &pResult->u.HCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->enmType = enmToType;
+ pResult->u.u64Number = InVar.u.GCPhys;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_GC_PHYS;
+ rc = PGMR3DbgR3Ptr2GCPhys(pDbgc->pUVM, pArg->u.pvHCFlat, &pResult->u.GCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ /** @todo more memory types! */
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_HC_PHYS;
+ rc = PGMR3DbgR3Ptr2HCPhys(pDbgc->pUVM, pArg->u.pvHCFlat, &pResult->u.HCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ /** @todo more memory types! */
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->enmType = enmToType;
+ pResult->u.u64Number = (uintptr_t)InVar.u.pvHCFlat;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_GC_PHYS;
+ rc = PGMR3DbgHCPhys2GCPhys(pDbgc->pUVM, pArg->u.HCPhys, &pResult->u.GCPhys);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->enmType = enmToType;
+ pResult->u.u64Number = InVar.u.HCPhys;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_NUMBER:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ pResult->enmType = DBGCVAR_TYPE_GC_FLAT;
+ pResult->u.GCFlat = (RTGCPTR)InVar.u.u64Number;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_GC_PHYS;
+ pResult->u.GCPhys = (RTGCPHYS)InVar.u.u64Number;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->enmType = DBGCVAR_TYPE_HC_FLAT;
+ pResult->u.pvHCFlat = (void *)(uintptr_t)InVar.u.u64Number;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->enmType = DBGCVAR_TYPE_HC_PHYS;
+ pResult->u.HCPhys = (RTHCPHYS)InVar.u.u64Number;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_NUMBER:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ case DBGCVAR_TYPE_STRING:
+ switch (enmToType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ case DBGCVAR_TYPE_NUMBER:
+ if (fConvSyms)
+ {
+ rc = dbgcSymbolGet(pDbgc, InVar.u.pszString, enmToType, pResult);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ pResult->enmType = enmToType;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+ break;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ case DBGCVAR_TYPE_ANY:
+ break;
+ }
+
+ AssertMsgFailed(("f=%d t=%d\n", InVar.enmType, enmToType));
+ return VERR_INVALID_PARAMETER;
+}
+
+
+/**
+ * @interface_method_impl{DBGFINFOHLP,pfnPrintf}
+ */
+static DECLCALLBACK(void) dbgcHlpGetDbgfOutputHlp_Printf(PCDBGFINFOHLP pHlp, const char *pszFormat, ...)
+{
+ PDBGC pDbgc = RT_FROM_MEMBER(pHlp, DBGC, DbgfOutputHlp);
+ va_list va;
+ va_start(va, pszFormat);
+ pDbgc->CmdHlp.pfnPrintfV(&pDbgc->CmdHlp, NULL, pszFormat, va);
+ va_end(va);
+}
+
+
+/**
+ * @interface_method_impl{DBGFINFOHLP,pfnPrintfV}
+ */
+static DECLCALLBACK(void) dbgcHlpGetDbgfOutputHlp_PrintfV(PCDBGFINFOHLP pHlp, const char *pszFormat, va_list args)
+{
+ PDBGC pDbgc = RT_FROM_MEMBER(pHlp, DBGC, DbgfOutputHlp);
+ pDbgc->CmdHlp.pfnPrintfV(&pDbgc->CmdHlp, NULL, pszFormat, args);
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnGetDbgfOutputHlp}
+ */
+static DECLCALLBACK(PCDBGFINFOHLP) dbgcHlpGetDbgfOutputHlp(PDBGCCMDHLP pCmdHlp)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /* Lazy init */
+ if (!pDbgc->DbgfOutputHlp.pfnPrintf)
+ {
+ pDbgc->DbgfOutputHlp.pfnPrintf = dbgcHlpGetDbgfOutputHlp_Printf;
+ pDbgc->DbgfOutputHlp.pfnPrintfV = dbgcHlpGetDbgfOutputHlp_PrintfV;
+ pDbgc->DbgfOutputHlp.pfnGetOptError = DBGFR3InfoGenericGetOptError;
+ }
+
+ return &pDbgc->DbgfOutputHlp;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnGetCurrentCpu}
+ */
+static DECLCALLBACK(VMCPUID) dbgcHlpGetCurrentCpu(PDBGCCMDHLP pCmdHlp)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ return pDbgc->idCpu;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnGetCpuMode}
+ */
+static DECLCALLBACK(CPUMMODE) dbgcHlpGetCpuMode(PDBGCCMDHLP pCmdHlp)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ CPUMMODE enmMode = CPUMMODE_INVALID;
+ if (pDbgc->pUVM)
+ enmMode = DBGFR3CpuGetMode(pDbgc->pUVM, DBGCCmdHlpGetCurrentCpu(pCmdHlp));
+ if (enmMode == CPUMMODE_INVALID)
+#if HC_ARCH_BITS == 64
+ enmMode = CPUMMODE_LONG;
+#else
+ enmMode = CPUMMODE_PROTECTED;
+#endif
+ return enmMode;
+}
+
+
+/**
+ * @interface_method_impl{DBGCCMDHLP,pfnRegPrintf}
+ */
+static DECLCALLBACK(int) dbgcHlpRegPrintf(PDBGCCMDHLP pCmdHlp, VMCPUID idCpu, int f64BitMode, bool fTerse)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ char szDisAndRegs[8192];
+ int rc;
+
+ if (f64BitMode < 0)
+ f64BitMode = DBGFR3CpuIsIn64BitCode(pDbgc->pUVM, idCpu);
+
+ if (fTerse)
+ {
+ if (f64BitMode)
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, idCpu, &szDisAndRegs[0], sizeof(szDisAndRegs),
+ "u %016VR{rip} L 0\n"
+ "rax=%016VR{rax} rbx=%016VR{rbx} rcx=%016VR{rcx} rdx=%016VR{rdx}\n"
+ "rsi=%016VR{rsi} rdi=%016VR{rdi} r8 =%016VR{r8} r9 =%016VR{r9}\n"
+ "r10=%016VR{r10} r11=%016VR{r11} r12=%016VR{r12} r13=%016VR{r13}\n"
+ "r14=%016VR{r14} r15=%016VR{r15} %VRF{rflags}\n"
+ "rip=%016VR{rip} rsp=%016VR{rsp} rbp=%016VR{rbp}\n"
+ "cs=%04VR{cs} ds=%04VR{ds} es=%04VR{es} fs=%04VR{fs} gs=%04VR{gs} ss=%04VR{ss} rflags=%08VR{rflags}\n");
+ else
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, idCpu, szDisAndRegs, sizeof(szDisAndRegs),
+ "u %04VR{cs}:%08VR{eip} L 0\n"
+ "eax=%08VR{eax} ebx=%08VR{ebx} ecx=%08VR{ecx} edx=%08VR{edx} esi=%08VR{esi} edi=%08VR{edi}\n"
+ "eip=%08VR{eip} esp=%08VR{esp} ebp=%08VR{ebp} %VRF{eflags}\n"
+ "cs=%04VR{cs} ds=%04VR{ds} es=%04VR{es} fs=%04VR{fs} gs=%04VR{gs} ss=%04VR{ss} eflags=%08VR{eflags}\n");
+ }
+ else
+ {
+ if (f64BitMode)
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, idCpu, &szDisAndRegs[0], sizeof(szDisAndRegs),
+ "u %016VR{rip} L 0\n"
+ "rax=%016VR{rax} rbx=%016VR{rbx} rcx=%016VR{rcx} rdx=%016VR{rdx}\n"
+ "rsi=%016VR{rsi} rdi=%016VR{rdi} r8 =%016VR{r8} r9 =%016VR{r9}\n"
+ "r10=%016VR{r10} r11=%016VR{r11} r12=%016VR{r12} r13=%016VR{r13}\n"
+ "r14=%016VR{r14} r15=%016VR{r15} %VRF{rflags}\n"
+ "rip=%016VR{rip} rsp=%016VR{rsp} rbp=%016VR{rbp}\n"
+ "cs={%04VR{cs} base=%016VR{cs_base} limit=%08VR{cs_lim} flags=%04VR{cs_attr}} cr0=%016VR{cr0}\n"
+ "ds={%04VR{ds} base=%016VR{ds_base} limit=%08VR{ds_lim} flags=%04VR{ds_attr}} cr2=%016VR{cr2}\n"
+ "es={%04VR{es} base=%016VR{es_base} limit=%08VR{es_lim} flags=%04VR{es_attr}} cr3=%016VR{cr3}\n"
+ "fs={%04VR{fs} base=%016VR{fs_base} limit=%08VR{fs_lim} flags=%04VR{fs_attr}} cr4=%016VR{cr4}\n"
+ "gs={%04VR{gs} base=%016VR{gs_base} limit=%08VR{gs_lim} flags=%04VR{gs_attr}} cr8=%016VR{cr8}\n"
+ "ss={%04VR{ss} base=%016VR{ss_base} limit=%08VR{ss_lim} flags=%04VR{ss_attr}}\n"
+ "dr0=%016VR{dr0} dr1=%016VR{dr1} dr2=%016VR{dr2} dr3=%016VR{dr3}\n"
+ "dr6=%016VR{dr6} dr7=%016VR{dr7}\n"
+ "gdtr=%016VR{gdtr_base}:%04VR{gdtr_lim} idtr=%016VR{idtr_base}:%04VR{idtr_lim} rflags=%08VR{rflags}\n"
+ "ldtr={%04VR{ldtr} base=%016VR{ldtr_base} limit=%08VR{ldtr_lim} flags=%08VR{ldtr_attr}}\n"
+ "tr ={%04VR{tr} base=%016VR{tr_base} limit=%08VR{tr_lim} flags=%08VR{tr_attr}}\n"
+ " sysenter={cs=%04VR{sysenter_cs} eip=%08VR{sysenter_eip} esp=%08VR{sysenter_esp}}\n"
+ " efer=%016VR{efer}\n"
+ " pat=%016VR{pat}\n"
+ " sf_mask=%016VR{sf_mask}\n"
+ "krnl_gs_base=%016VR{krnl_gs_base}\n"
+ " lstar=%016VR{lstar}\n"
+ " star=%016VR{star} cstar=%016VR{cstar}\n"
+ "fcw=%04VR{fcw} fsw=%04VR{fsw} ftw=%04VR{ftw} mxcsr=%04VR{mxcsr} mxcsr_mask=%04VR{mxcsr_mask}\n"
+ );
+ else
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, idCpu, szDisAndRegs, sizeof(szDisAndRegs),
+ "u %04VR{cs}:%08VR{eip} L 0\n"
+ "eax=%08VR{eax} ebx=%08VR{ebx} ecx=%08VR{ecx} edx=%08VR{edx} esi=%08VR{esi} edi=%08VR{edi}\n"
+ "eip=%08VR{eip} esp=%08VR{esp} ebp=%08VR{ebp} %VRF{eflags}\n"
+ "cs={%04VR{cs} base=%08VR{cs_base} limit=%08VR{cs_lim} flags=%04VR{cs_attr}} dr0=%08VR{dr0} dr1=%08VR{dr1}\n"
+ "ds={%04VR{ds} base=%08VR{ds_base} limit=%08VR{ds_lim} flags=%04VR{ds_attr}} dr2=%08VR{dr2} dr3=%08VR{dr3}\n"
+ "es={%04VR{es} base=%08VR{es_base} limit=%08VR{es_lim} flags=%04VR{es_attr}} dr6=%08VR{dr6} dr7=%08VR{dr7}\n"
+ "fs={%04VR{fs} base=%08VR{fs_base} limit=%08VR{fs_lim} flags=%04VR{fs_attr}} cr0=%08VR{cr0} cr2=%08VR{cr2}\n"
+ "gs={%04VR{gs} base=%08VR{gs_base} limit=%08VR{gs_lim} flags=%04VR{gs_attr}} cr3=%08VR{cr3} cr4=%08VR{cr4}\n"
+ "ss={%04VR{ss} base=%08VR{ss_base} limit=%08VR{ss_lim} flags=%04VR{ss_attr}} cr8=%08VR{cr8}\n"
+ "gdtr=%08VR{gdtr_base}:%04VR{gdtr_lim} idtr=%08VR{idtr_base}:%04VR{idtr_lim} eflags=%08VR{eflags}\n"
+ "ldtr={%04VR{ldtr} base=%08VR{ldtr_base} limit=%08VR{ldtr_lim} flags=%04VR{ldtr_attr}}\n"
+ "tr ={%04VR{tr} base=%08VR{tr_base} limit=%08VR{tr_lim} flags=%04VR{tr_attr}}\n"
+ "sysenter={cs=%04VR{sysenter_cs} eip=%08VR{sysenter_eip} esp=%08VR{sysenter_esp}}\n"
+ "fcw=%04VR{fcw} fsw=%04VR{fsw} ftw=%04VR{ftw} mxcsr=%04VR{mxcsr} mxcsr_mask=%04VR{mxcsr_mask}\n"
+ );
+ }
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3RegPrintf failed");
+ char *pszRegs = strchr(szDisAndRegs, '\n');
+ *pszRegs++ = '\0';
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%s", pszRegs);
+
+ /*
+ * Disassemble one instruction at cs:[r|e]ip.
+ */
+ if (!f64BitMode && strstr(pszRegs, " vm ")) /* a bit ugly... */
+ return pCmdHlp->pfnExec(pCmdHlp, "uv86 %s", szDisAndRegs + 2);
+ return pCmdHlp->pfnExec(pCmdHlp, "%s", szDisAndRegs);
+}
+
+
+/**
+ * Initializes the Command Helpers for a DBGC instance.
+ *
+ * @param pDbgc Pointer to the DBGC instance.
+ */
+void dbgcInitCmdHlp(PDBGC pDbgc)
+{
+ pDbgc->CmdHlp.u32Magic = DBGCCMDHLP_MAGIC;
+ pDbgc->CmdHlp.pfnPrintfV = dbgcHlpPrintfV;
+ pDbgc->CmdHlp.pfnPrintf = dbgcHlpPrintf;
+ pDbgc->CmdHlp.pfnStrPrintf = dbgcHlpStrPrintf;
+ pDbgc->CmdHlp.pfnStrPrintfV = dbgcHlpStrPrintfV;
+ pDbgc->CmdHlp.pfnVBoxErrorV = dbgcHlpVBoxErrorV;
+ pDbgc->CmdHlp.pfnVBoxError = dbgcHlpVBoxError;
+ pDbgc->CmdHlp.pfnMemRead = dbgcHlpMemRead;
+ pDbgc->CmdHlp.pfnMemWrite = dbgcHlpMemWrite;
+ pDbgc->CmdHlp.pfnEvalV = dbgcHlpEvalV;
+ pDbgc->CmdHlp.pfnExec = dbgcHlpExec;
+ pDbgc->CmdHlp.pfnFailV = dbgcHlpFailV;
+ pDbgc->CmdHlp.pfnFailRcV = dbgcHlpFailRcV;
+ pDbgc->CmdHlp.pfnParserError = dbgcHlpParserError;
+ pDbgc->CmdHlp.pfnVarToDbgfAddr = dbgcHlpVarToDbgfAddr;
+ pDbgc->CmdHlp.pfnVarFromDbgfAddr = dbgcHlpVarFromDbgfAddr;
+ pDbgc->CmdHlp.pfnVarToNumber = dbgcHlpVarToNumber;
+ pDbgc->CmdHlp.pfnVarToBool = dbgcHlpVarToBool;
+ pDbgc->CmdHlp.pfnVarGetRange = dbgcHlpVarGetRange;
+ pDbgc->CmdHlp.pfnVarConvert = dbgcHlpVarConvert;
+ pDbgc->CmdHlp.pfnGetDbgfOutputHlp = dbgcHlpGetDbgfOutputHlp;
+ pDbgc->CmdHlp.pfnGetCurrentCpu = dbgcHlpGetCurrentCpu;
+ pDbgc->CmdHlp.pfnGetCpuMode = dbgcHlpGetCpuMode;
+ pDbgc->CmdHlp.pfnRegPrintf = dbgcHlpRegPrintf;
+ pDbgc->CmdHlp.u32EndMarker = DBGCCMDHLP_MAGIC;
+}
+
diff --git a/src/VBox/Debugger/DBGCCmdWorkers.cpp b/src/VBox/Debugger/DBGCCmdWorkers.cpp
new file mode 100644
index 00000000..f75b0026
--- /dev/null
+++ b/src/VBox/Debugger/DBGCCmdWorkers.cpp
@@ -0,0 +1,377 @@
+/* $Id: DBGCCmdWorkers.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Command Worker Routines.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/param.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+
+#include "DBGCInternal.h"
+
+
+
+
+//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
+//
+//
+// B r e a k p o i n t M a n a g e m e n t
+//
+//
+//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
+
+
+/**
+ * Adds a breakpoint to the DBGC breakpoint list.
+ */
+int dbgcBpAdd(PDBGC pDbgc, RTUINT iBp, const char *pszCmd)
+{
+ /*
+ * Check if it already exists.
+ */
+ PDBGCBP pBp = dbgcBpGet(pDbgc, iBp);
+ if (pBp)
+ return VERR_DBGC_BP_EXISTS;
+
+ /*
+ * Add the breakpoint.
+ */
+ if (pszCmd)
+ pszCmd = RTStrStripL(pszCmd);
+ size_t cchCmd = pszCmd ? strlen(pszCmd) : 0;
+ pBp = (PDBGCBP)RTMemAlloc(RT_UOFFSETOF_DYN(DBGCBP, szCmd[cchCmd + 1]));
+ if (!pBp)
+ return VERR_NO_MEMORY;
+ if (cchCmd)
+ memcpy(pBp->szCmd, pszCmd, cchCmd + 1);
+ else
+ pBp->szCmd[0] = '\0';
+ pBp->cchCmd = cchCmd;
+ pBp->iBp = iBp;
+ pBp->pNext = pDbgc->pFirstBp;
+ pDbgc->pFirstBp = pBp;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Updates the a breakpoint.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The DBGC instance.
+ * @param iBp The breakpoint to update.
+ * @param pszCmd The new command.
+ */
+int dbgcBpUpdate(PDBGC pDbgc, RTUINT iBp, const char *pszCmd)
+{
+ /*
+ * Find the breakpoint.
+ */
+ PDBGCBP pBp = dbgcBpGet(pDbgc, iBp);
+ if (!pBp)
+ return VERR_DBGC_BP_NOT_FOUND;
+
+ /*
+ * Do we need to reallocate?
+ */
+ if (pszCmd)
+ pszCmd = RTStrStripL(pszCmd);
+ if (!pszCmd || !*pszCmd)
+ pBp->szCmd[0] = '\0';
+ else
+ {
+ size_t cchCmd = strlen(pszCmd);
+ if (strlen(pBp->szCmd) >= cchCmd)
+ {
+ memcpy(pBp->szCmd, pszCmd, cchCmd + 1);
+ pBp->cchCmd = cchCmd;
+ }
+ else
+ {
+ /*
+ * Yes, let's do it the simple way...
+ */
+ int rc = dbgcBpDelete(pDbgc, iBp);
+ AssertRC(rc);
+ return dbgcBpAdd(pDbgc, iBp, pszCmd);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deletes a breakpoint.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The DBGC instance.
+ * @param iBp The breakpoint to delete.
+ */
+int dbgcBpDelete(PDBGC pDbgc, RTUINT iBp)
+{
+ /*
+ * Search thru the list, when found unlink and free it.
+ */
+ PDBGCBP pBpPrev = NULL;
+ PDBGCBP pBp = pDbgc->pFirstBp;
+ for (; pBp; pBp = pBp->pNext)
+ {
+ if (pBp->iBp == iBp)
+ {
+ if (pBpPrev)
+ pBpPrev->pNext = pBp->pNext;
+ else
+ pDbgc->pFirstBp = pBp->pNext;
+ RTMemFree(pBp);
+ return VINF_SUCCESS;
+ }
+ pBpPrev = pBp;
+ }
+
+ return VERR_DBGC_BP_NOT_FOUND;
+}
+
+
+/**
+ * Get a breakpoint.
+ *
+ * @returns Pointer to the breakpoint.
+ * @returns NULL if the breakpoint wasn't found.
+ * @param pDbgc The DBGC instance.
+ * @param iBp The breakpoint to get.
+ */
+PDBGCBP dbgcBpGet(PDBGC pDbgc, RTUINT iBp)
+{
+ /*
+ * Enumerate the list.
+ */
+ PDBGCBP pBp = pDbgc->pFirstBp;
+ for (; pBp; pBp = pBp->pNext)
+ if (pBp->iBp == iBp)
+ return pBp;
+ return NULL;
+}
+
+
+/**
+ * Executes the command of a breakpoint.
+ *
+ * @returns VINF_DBGC_BP_NO_COMMAND if there is no command associated with the breakpoint.
+ * @returns VERR_DBGC_BP_NOT_FOUND if the breakpoint wasn't found.
+ * @returns VERR_BUFFER_OVERFLOW if the is not enough space in the scratch buffer for the command.
+ * @returns VBox status code from dbgcEvalCommand() otherwise.
+ * @param pDbgc The DBGC instance.
+ * @param iBp The breakpoint to execute.
+ */
+int dbgcBpExec(PDBGC pDbgc, RTUINT iBp)
+{
+ /*
+ * Find the breakpoint.
+ */
+ PDBGCBP pBp = dbgcBpGet(pDbgc, iBp);
+ if (!pBp)
+ return VERR_DBGC_BP_NOT_FOUND;
+
+ /*
+ * Anything to do?
+ */
+ if (!pBp->cchCmd)
+ return VINF_DBGC_BP_NO_COMMAND;
+
+ /*
+ * Execute the command.
+ * This means copying it to the scratch buffer and process it as if it
+ * were user input. We must save and restore the state of the scratch buffer.
+ */
+ /* Save the scratch state. */
+ char *pszScratch = pDbgc->pszScratch;
+ unsigned iArg = pDbgc->iArg;
+
+ /* Copy the command to the scratch buffer. */
+ size_t cbScratch = sizeof(pDbgc->achScratch) - (pDbgc->pszScratch - &pDbgc->achScratch[0]);
+ if (pBp->cchCmd >= cbScratch)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pDbgc->pszScratch, pBp->szCmd, pBp->cchCmd + 1);
+
+ /* Execute the command. */
+ pDbgc->pszScratch = pDbgc->pszScratch + pBp->cchCmd + 1;
+ int rc = dbgcEvalCommands(pDbgc, pszScratch, pBp->cchCmd, false /* fNoExecute */);
+
+ /* Restore the scratch state. */
+ pDbgc->iArg = iArg;
+ pDbgc->pszScratch = pszScratch;
+
+ return rc;
+}
+
+
+
+
+//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
+//
+//
+// F l o w T r a c e M a n a g e m e n t
+//
+//
+//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
+
+
+
+/**
+ * Returns the trace flow module matching the given id or NULL if not found.
+ *
+ * @returns Pointer to the trace flow module or NULL if not found.
+ * @param pDbgc The DBGC instance.
+ * @param iTraceFlowMod The trace flow module identifier.
+ */
+DECLHIDDEN(PDBGCTFLOW) dbgcFlowTraceModGet(PDBGC pDbgc, uint32_t iTraceFlowMod)
+{
+ PDBGCTFLOW pIt;
+ RTListForEach(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow)
+ {
+ if (pIt->iTraceFlowMod == iTraceFlowMod)
+ return pIt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Inserts the given trace flow module into the list.
+ *
+ * @param pDbgc The DBGC instance.
+ * @param pTraceFlow The trace flow module.
+ */
+static void dbgcFlowTraceModInsert(PDBGC pDbgc, PDBGCTFLOW pTraceFlow)
+{
+ PDBGCTFLOW pIt = RTListGetLast(&pDbgc->LstTraceFlowMods, DBGCTFLOW, NdTraceFlow);
+
+ if ( !pIt
+ || pIt->iTraceFlowMod < pTraceFlow->iTraceFlowMod)
+ RTListAppend(&pDbgc->LstTraceFlowMods, &pTraceFlow->NdTraceFlow);
+ else
+ {
+ RTListForEach(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow)
+ {
+ if (pIt->iTraceFlowMod < pTraceFlow->iTraceFlowMod)
+ {
+ RTListNodeInsertBefore(&pIt->NdTraceFlow, &pTraceFlow->NdTraceFlow);
+ break;
+ }
+ }
+ }
+}
+
+
+/**
+ * Returns the smallest free flow trace mod identifier.
+ *
+ * @returns Free flow trace mod identifier.
+ * @param pDbgc The DBGC instance.
+ */
+static uint32_t dbgcFlowTraceModIdFindFree(PDBGC pDbgc)
+{
+ uint32_t iId = 0;
+
+ PDBGCTFLOW pIt;
+ RTListForEach(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow)
+ {
+ PDBGCTFLOW pNext = RTListGetNext(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow);
+ if ( ( pNext
+ && pIt->iTraceFlowMod + 1 != pNext->iTraceFlowMod)
+ || !pNext)
+ {
+ iId = pIt->iTraceFlowMod + 1;
+ break;
+ }
+ }
+
+ return iId;
+}
+
+
+/**
+ * Adds a flow trace module to the debugger console.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The DBGC instance.
+ * @param hFlowTraceMod The flow trace module to add.
+ * @param hFlow The control flow graph to add.
+ * @param piId Where to store the ID of the module on success.
+ */
+DECLHIDDEN(int) dbgcFlowTraceModAdd(PDBGC pDbgc, DBGFFLOWTRACEMOD hFlowTraceMod, DBGFFLOW hFlow, uint32_t *piId)
+{
+ /*
+ * Add the module.
+ */
+ PDBGCTFLOW pTraceFlow = (PDBGCTFLOW)RTMemAlloc(sizeof(DBGCTFLOW));
+ if (!pTraceFlow)
+ return VERR_NO_MEMORY;
+
+ pTraceFlow->hTraceFlowMod = hFlowTraceMod;
+ pTraceFlow->hFlow = hFlow;
+ pTraceFlow->iTraceFlowMod = dbgcFlowTraceModIdFindFree(pDbgc);
+ dbgcFlowTraceModInsert(pDbgc, pTraceFlow);
+
+ *piId = pTraceFlow->iTraceFlowMod;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deletes a breakpoint.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The DBGC instance.
+ * @param iTraceFlowMod The trace flow module identifier.
+ */
+DECLHIDDEN(int) dbgcFlowTraceModDelete(PDBGC pDbgc, uint32_t iTraceFlowMod)
+{
+ int rc = VINF_SUCCESS;
+ PDBGCTFLOW pTraceFlow = dbgcFlowTraceModGet(pDbgc, iTraceFlowMod);
+ if (pTraceFlow)
+ {
+ RTListNodeRemove(&pTraceFlow->NdTraceFlow);
+ RTMemFree(pTraceFlow);
+ }
+ else
+ rc = VERR_DBGC_BP_NOT_FOUND;
+
+ return rc;
+}
+
diff --git a/src/VBox/Debugger/DBGCCommands.cpp b/src/VBox/Debugger/DBGCCommands.cpp
new file mode 100644
index 00000000..786364cc
--- /dev/null
+++ b/src/VBox/Debugger/DBGCCommands.cpp
@@ -0,0 +1,2072 @@
+/* $Id: DBGCCommands.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Native Commands.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/param.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/dir.h>
+#include <iprt/file.h>
+#include <iprt/env.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/rand.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static FNDBGCCMD dbgcCmdHelp;
+static FNDBGCCMD dbgcCmdQuit;
+static FNDBGCCMD dbgcCmdStop;
+static FNDBGCCMD dbgcCmdDetect;
+static FNDBGCCMD dbgcCmdDmesg;
+extern FNDBGCCMD dbgcCmdDumpImage;
+static FNDBGCCMD dbgcCmdCpu;
+static FNDBGCCMD dbgcCmdInfo;
+static FNDBGCCMD dbgcCmdLog;
+static FNDBGCCMD dbgcCmdLogDest;
+static FNDBGCCMD dbgcCmdLogFlags;
+static FNDBGCCMD dbgcCmdLogFlush;
+static FNDBGCCMD dbgcCmdFormat;
+static FNDBGCCMD dbgcCmdLoadImage;
+static FNDBGCCMD dbgcCmdLoadInMem;
+static FNDBGCCMD dbgcCmdLoadMap;
+static FNDBGCCMD dbgcCmdLoadSeg;
+static FNDBGCCMD dbgcCmdMultiStep;
+static FNDBGCCMD dbgcCmdUnload;
+static FNDBGCCMD dbgcCmdSet;
+static FNDBGCCMD dbgcCmdUnset;
+static FNDBGCCMD dbgcCmdLoadVars;
+static FNDBGCCMD dbgcCmdShowVars;
+static FNDBGCCMD dbgcCmdSleep;
+static FNDBGCCMD dbgcCmdLoadPlugIn;
+static FNDBGCCMD dbgcCmdUnloadPlugIn;
+static FNDBGCCMD dbgcCmdHarakiri;
+static FNDBGCCMD dbgcCmdEcho;
+static FNDBGCCMD dbgcCmdRunScript;
+static FNDBGCCMD dbgcCmdWriteCore;
+static FNDBGCCMD dbgcCmdWriteGstMem;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** One argument of any kind. */
+static const DBGCVARDESC g_aArgAny[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_ANY, 0, "var", "Any type of argument." },
+};
+
+/** Multiple string arguments (min 1). */
+static const DBGCVARDESC g_aArgMultiStr[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, ~0U, DBGCVAR_CAT_STRING, 0, "strings", "One or more strings." },
+};
+
+/** Filename string. */
+static const DBGCVARDESC g_aArgFilename[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "path", "Filename string." },
+};
+
+
+/** 'cpu' arguments. */
+static const DBGCVARDESC g_aArgCpu[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER_NO_RANGE, 0, "idCpu", "CPU ID" },
+};
+
+
+/** 'dmesg' arguments. */
+static const DBGCVARDESC g_aArgDmesg[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER_NO_RANGE, 0, "messages", "Limit the output to the last N messages. (optional)" },
+};
+
+
+/** 'dumpimage' arguments. */
+static const DBGCVARDESC g_aArgDumpImage[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, ~0U, DBGCVAR_CAT_POINTER, 0, "address", "Address of image to dump." },
+};
+
+
+/** 'help' arguments. */
+static const DBGCVARDESC g_aArgHelp[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_STRING, 0, "cmd/op", "Zero or more command or operator names." },
+};
+
+
+/** 'info' arguments. */
+static const DBGCVARDESC g_aArgInfo[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "info", "The name of the info to display." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "args", "String arguments to the handler." },
+};
+
+
+/** loadimage arguments. */
+static const DBGCVARDESC g_aArgLoadImage[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "filename", "Filename string." },
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "The module address." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "name", "The module name. (optional)" },
+};
+
+
+/** loadmap arguments. */
+static const DBGCVARDESC g_aArgLoadMap[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "filename", "Filename string." },
+ { 1, 1, DBGCVAR_CAT_POINTER, DBGCVD_FLAGS_DEP_PREV, "address", "The module address." },
+ { 0, 1, DBGCVAR_CAT_STRING, DBGCVD_FLAGS_DEP_PREV, "name", "The module name. Empty string means default. (optional)" },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "subtrahend", "Value to subtract from the addresses in the map file to rebase it correctly to address. (optional)" },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "seg", "The module segment number (0-based). (optional)" },
+};
+
+
+/** loadinmem arguments. */
+static const DBGCVARDESC g_aArgLoadInMem[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "The module address." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "name", "The module name. (optional)" },
+};
+
+
+/** loadseg arguments. */
+static const DBGCVARDESC g_aArgLoadSeg[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "filename", "Filename string." },
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "The module address." },
+ { 1, 1, DBGCVAR_CAT_NUMBER, 0, "seg", "The module segment number (0-based)." },
+ { 0, 1, DBGCVAR_CAT_STRING, DBGCVD_FLAGS_DEP_PREV, "name", "The module name. Empty string means default. (optional)" },
+};
+
+
+/** log arguments. */
+static const DBGCVARDESC g_aArgLog[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "groups", "Group modifier string (quote it!)." }
+};
+
+
+/** logdest arguments. */
+static const DBGCVARDESC g_aArgLogDest[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "dests", "Destination modifier string (quote it!)." }
+};
+
+
+/** logflags arguments. */
+static const DBGCVARDESC g_aArgLogFlags[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "flags", "Flag modifier string (quote it!)." }
+};
+
+/** multistep arguments. */
+static const DBGCVARDESC g_aArgMultiStep[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER_NO_RANGE, 0, "count", "Number of steps to take, defaults to 64." },
+ { 0, 1, DBGCVAR_CAT_NUMBER_NO_RANGE, DBGCVD_FLAGS_DEP_PREV, "stride", "The length of each step, defaults to 1." },
+};
+
+
+/** loadplugin, unloadplugin. */
+static const DBGCVARDESC g_aArgPlugIn[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, ~0U, DBGCVAR_CAT_STRING, 0, "plugin", "Plug-in name or filename." },
+};
+
+
+/** 'set' arguments */
+static const DBGCVARDESC g_aArgSet[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_SYMBOL, 0, "var", "Variable name." },
+ { 1, 1, DBGCVAR_CAT_ANY, 0, "value", "Value to assign to the variable." },
+};
+
+/** 'sleep' arguments */
+static const DBGCVARDESC g_aArgSleep[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_NUMBER, 0, "milliseconds", "The sleep interval in milliseconds (max 30000ms)." },
+};
+
+/** 'stop' arguments */
+static const DBGCVARDESC g_aArgStop[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "idCpu", "CPU ID." },
+};
+
+/** loadplugin, unloadplugin. */
+static const DBGCVARDESC g_aArgUnload[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, ~0U, DBGCVAR_CAT_STRING, 0, "modname", "Unloads all mappings of the given modules in the active address space." },
+};
+
+/** 'unset' arguments */
+static const DBGCVARDESC g_aArgUnset[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, ~0U, DBGCVAR_CAT_SYMBOL, 0, "vars", "One or more variable names." },
+};
+
+/** writecore arguments. */
+static const DBGCVARDESC g_aArgWriteCore[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "path", "Filename string." },
+};
+
+/** writegstmem arguments. */
+static const DBGCVARDESC g_aArgWriteGstMem[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "filename", "Filename string." },
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "The guest address." }
+};
+
+
+
+/** Command descriptors for the basic commands. */
+const DBGCCMD g_aDbgcCmds[] =
+{
+ /* pszCmd, cArgsMin, cArgsMax, paArgDescs, cArgDescs, fFlags, pfnHandler pszSyntax, ....pszDescription */
+ { "bye", 0, 0, NULL, 0, 0, dbgcCmdQuit, "", "Exits the debugger." },
+ { "cpu", 0, 1, &g_aArgCpu[0], RT_ELEMENTS(g_aArgCpu), 0, dbgcCmdCpu, "[idCpu]", "If no argument, display the current CPU, else change to the specified CPU." },
+ { "echo", 1, ~0U, &g_aArgMultiStr[0], RT_ELEMENTS(g_aArgMultiStr), 0, dbgcCmdEcho, "<str1> [str2..[strN]]", "Displays the strings separated by one blank space and the last one followed by a newline." },
+ { "exit", 0, 0, NULL, 0, 0, dbgcCmdQuit, "", "Exits the debugger." },
+ { "format", 1, 1, &g_aArgAny[0], RT_ELEMENTS(g_aArgAny), 0, dbgcCmdFormat, "", "Evaluates an expression and formats it." },
+ { "detect", 0, 0, NULL, 0, 0, dbgcCmdDetect, "", "Detects or re-detects the guest os and starts the OS specific digger." },
+ { "dmesg", 0, 1, &g_aArgDmesg[0], RT_ELEMENTS(g_aArgDmesg), 0, dbgcCmdDmesg, "[N last messages]", "Displays the guest os kernel messages, if available." },
+ { "dumpimage", 1, ~0U, &g_aArgDumpImage[0], RT_ELEMENTS(g_aArgDumpImage), 0, dbgcCmdDumpImage, "<addr1> [addr2..[addrN]]", "Dumps executable images." },
+ { "harakiri", 0, 0, NULL, 0, 0, dbgcCmdHarakiri, "", "Kills debugger process." },
+ { "help", 0, ~0U, &g_aArgHelp[0], RT_ELEMENTS(g_aArgHelp), 0, dbgcCmdHelp, "[cmd/op [..]]", "Display help. For help about info items try 'info help'." },
+ { "info", 1, 2, &g_aArgInfo[0], RT_ELEMENTS(g_aArgInfo), 0, dbgcCmdInfo, "<info> [args]", "Display info register in the DBGF. For a list of info items try 'info help'." },
+ { "loadimage", 2, 3, &g_aArgLoadImage[0], RT_ELEMENTS(g_aArgLoadImage), 0, dbgcCmdLoadImage, "<filename> <address> [name]",
+ "Loads the symbols of an executable image at the specified address. "
+ /*"Optionally giving the module a name other than the file name stem."*/ }, /** @todo implement line breaks */
+ { "loadimage32",2, 3, &g_aArgLoadImage[0], RT_ELEMENTS(g_aArgLoadImage), 0, dbgcCmdLoadImage, "<filename> <address> [name]", "loadimage variant for selecting 32-bit images (mach-o)." },
+ { "loadimage64",2, 3, &g_aArgLoadImage[0], RT_ELEMENTS(g_aArgLoadImage), 0, dbgcCmdLoadImage, "<filename> <address> [name]", "loadimage variant for selecting 64-bit images (mach-o)." },
+ { "loadinmem", 1, 2, &g_aArgLoadInMem[0], RT_ELEMENTS(g_aArgLoadInMem), 0, dbgcCmdLoadInMem, "<address> [name]", "Tries to load a image mapped at the given address." },
+ { "loadmap", 2, 5, &g_aArgLoadMap[0], RT_ELEMENTS(g_aArgLoadMap), 0, dbgcCmdLoadMap, "<filename> <address> [name] [subtrahend] [seg]",
+ "Loads the symbols from a map file, usually at a specified address. "
+ /*"Optionally giving the module a name other than the file name stem "
+ "and a subtrahend to subtract from the addresses."*/ },
+ { "loadplugin", 1, 1, &g_aArgPlugIn[0], RT_ELEMENTS(g_aArgPlugIn), 0, dbgcCmdLoadPlugIn,"<plugin1> [plugin2..N]", "Loads one or more plugins" },
+ { "loadseg", 3, 4, &g_aArgLoadSeg[0], RT_ELEMENTS(g_aArgLoadSeg), 0, dbgcCmdLoadSeg, "<filename> <address> <seg> [name]",
+ "Loads the symbols of a segment in the executable image at the specified address. "
+ /*"Optionally giving the module a name other than the file name stem."*/ },
+ { "loadvars", 1, 1, &g_aArgFilename[0], RT_ELEMENTS(g_aArgFilename), 0, dbgcCmdLoadVars, "<filename>", "Load variables from file. One per line, same as the args to the set command." },
+ { "log", 0, 1, &g_aArgLog[0], RT_ELEMENTS(g_aArgLog), 0, dbgcCmdLog, "[group string]", "Displays or modifies the logging group settings (VBOX_LOG)" },
+ { "logdest", 0, 1, &g_aArgLogDest[0], RT_ELEMENTS(g_aArgLogDest), 0, dbgcCmdLogDest, "[dest string]", "Displays or modifies the logging destination (VBOX_LOG_DEST)." },
+ { "logflags", 0, 1, &g_aArgLogFlags[0], RT_ELEMENTS(g_aArgLogFlags), 0, dbgcCmdLogFlags, "[flags string]", "Displays or modifies the logging flags (VBOX_LOG_FLAGS)." },
+ { "logflush", 0, 0, NULL, 0, 0, dbgcCmdLogFlush, "", "Flushes the log buffers." },
+ { "multistep", 0, 2, &g_aArgMultiStep[0], RT_ELEMENTS(g_aArgMultiStep), 0, dbgcCmdMultiStep, "[count [stride]", "Performs the specified number of step-into operations. Stops early if non-step event occurs." },
+ { "quit", 0, 0, NULL, 0, 0, dbgcCmdQuit, "", "Exits the debugger." },
+ { "runscript", 1, 1, &g_aArgFilename[0], RT_ELEMENTS(g_aArgFilename), 0, dbgcCmdRunScript, "<filename>", "Runs the command listed in the script. Lines starting with '#' "
+ "(after removing blanks) are comment. blank lines are ignored. Stops on failure." },
+ { "set", 2, 2, &g_aArgSet[0], RT_ELEMENTS(g_aArgSet), 0, dbgcCmdSet, "<var> <value>", "Sets a global variable." },
+ { "showvars", 0, 0, NULL, 0, 0, dbgcCmdShowVars, "", "List all the defined variables." },
+ { "sleep", 1, 1, &g_aArgSleep[0], RT_ELEMENTS(g_aArgSleep), 0, dbgcCmdSleep, "<milliseconds>", "Sleeps for the given number of milliseconds (max 30000)." },
+ { "stop", 0, 1, &g_aArgStop[0], RT_ELEMENTS(g_aArgStop), 0, dbgcCmdStop, "[idCpu]", "Stop execution either of all or the specified CPU. (The latter is not recommended unless you know exactly what you're doing.)" },
+ { "unload", 1, ~0U, &g_aArgUnload[0], RT_ELEMENTS(g_aArgUnload), 0, dbgcCmdUnload, "<modname1> [modname2..N]", "Unloads one or more modules in the current address space." },
+ { "unloadplugin", 1, ~0U, &g_aArgPlugIn[0], RT_ELEMENTS(g_aArgPlugIn), 0, dbgcCmdUnloadPlugIn, "<plugin1> [plugin2..N]", "Unloads one or more plugins." },
+ { "unset", 1, ~0U, &g_aArgUnset[0], RT_ELEMENTS(g_aArgUnset), 0, dbgcCmdUnset, "<var1> [var1..[varN]]", "Unsets (delete) one or more global variables." },
+ { "writecore", 1, 1, &g_aArgWriteCore[0], RT_ELEMENTS(g_aArgWriteCore), 0, dbgcCmdWriteCore, "<filename>", "Write core to file." },
+ { "writegstmem", 2, 2, &g_aArgWriteGstMem[0], RT_ELEMENTS(g_aArgWriteGstMem), 0, dbgcCmdWriteGstMem, "<filename> <address>", "Load data from the given file and write it to guest memory at the given start address." },
+};
+/** The number of native commands. */
+const uint32_t g_cDbgcCmds = RT_ELEMENTS(g_aDbgcCmds);
+/** Pointer to head of the list of external commands. */
+static PDBGCEXTCMDS g_pExtCmdsHead;
+
+
+
+
+/**
+ * Finds a routine.
+ *
+ * @returns Pointer to the command descriptor.
+ * If the request was for an external command, the caller is responsible for
+ * unlocking the external command list.
+ * @returns NULL if not found.
+ * @param pDbgc The debug console instance.
+ * @param pachName Pointer to the routine string (not terminated).
+ * @param cchName Length of the routine name.
+ * @param fExternal Whether or not the routine is external.
+ */
+PCDBGCCMD dbgcCommandLookup(PDBGC pDbgc, const char *pachName, size_t cchName, bool fExternal)
+{
+ if (!fExternal)
+ {
+ /* emulation first, so commands can be overloaded (info ++). */
+ PCDBGCCMD pCmd = pDbgc->paEmulationCmds;
+ unsigned cLeft = pDbgc->cEmulationCmds;
+ while (cLeft-- > 0)
+ {
+ if ( !strncmp(pachName, pCmd->pszCmd, cchName)
+ && !pCmd->pszCmd[cchName])
+ return pCmd;
+ pCmd++;
+ }
+
+ for (unsigned iCmd = 0; iCmd < RT_ELEMENTS(g_aDbgcCmds); iCmd++)
+ {
+ if ( !strncmp(pachName, g_aDbgcCmds[iCmd].pszCmd, cchName)
+ && !g_aDbgcCmds[iCmd].pszCmd[cchName])
+ return &g_aDbgcCmds[iCmd];
+ }
+ }
+ else
+ {
+ DBGCEXTLISTS_LOCK_RD();
+ for (PDBGCEXTCMDS pExtCmds = g_pExtCmdsHead; pExtCmds; pExtCmds = pExtCmds->pNext)
+ {
+ for (unsigned iCmd = 0; iCmd < pExtCmds->cCmds; iCmd++)
+ {
+ if ( !strncmp(pachName, pExtCmds->paCmds[iCmd].pszCmd, cchName)
+ && !pExtCmds->paCmds[iCmd].pszCmd[cchName])
+ return &pExtCmds->paCmds[iCmd];
+ }
+ }
+ DBGCEXTLISTS_UNLOCK_RD();
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Register one or more external commands.
+ *
+ * @returns VBox status code.
+ * @param paCommands Pointer to an array of command descriptors.
+ * The commands must be unique. It's not possible
+ * to register the same commands more than once.
+ * @param cCommands Number of commands.
+ */
+DBGDECL(int) DBGCRegisterCommands(PCDBGCCMD paCommands, unsigned cCommands)
+{
+ /*
+ * Lock the list.
+ */
+ DBGCEXTLISTS_LOCK_WR();
+ PDBGCEXTCMDS pCur = g_pExtCmdsHead;
+ while (pCur)
+ {
+ if (paCommands == pCur->paCmds)
+ {
+ DBGCEXTLISTS_UNLOCK_WR();
+ AssertMsgFailed(("Attempt at re-registering %d command(s)!\n", cCommands));
+ return VWRN_DBGC_ALREADY_REGISTERED;
+ }
+ pCur = pCur->pNext;
+ }
+
+ /*
+ * Allocate new chunk.
+ */
+ int rc = 0;
+ pCur = (PDBGCEXTCMDS)RTMemAlloc(sizeof(*pCur));
+ RTMEM_MAY_LEAK(pCur);
+ if (pCur)
+ {
+ pCur->cCmds = cCommands;
+ pCur->paCmds = paCommands;
+ pCur->pNext = g_pExtCmdsHead;
+ g_pExtCmdsHead = pCur;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ DBGCEXTLISTS_UNLOCK_WR();
+
+ return rc;
+}
+
+
+/**
+ * Deregister one or more external commands previously registered by
+ * DBGCRegisterCommands().
+ *
+ * @returns VBox status code.
+ * @param paCommands Pointer to an array of command descriptors
+ * as given to DBGCRegisterCommands().
+ * @param cCommands Number of commands.
+ */
+DBGDECL(int) DBGCDeregisterCommands(PCDBGCCMD paCommands, unsigned cCommands)
+{
+ /*
+ * Lock the list.
+ */
+ DBGCEXTLISTS_LOCK_WR();
+ PDBGCEXTCMDS pPrev = NULL;
+ PDBGCEXTCMDS pCur = g_pExtCmdsHead;
+ while (pCur)
+ {
+ if (paCommands == pCur->paCmds)
+ {
+ if (pPrev)
+ pPrev->pNext = pCur->pNext;
+ else
+ g_pExtCmdsHead = pCur->pNext;
+ DBGCEXTLISTS_UNLOCK_WR();
+
+ RTMemFree(pCur);
+ return VINF_SUCCESS;
+ }
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ DBGCEXTLISTS_UNLOCK_WR();
+
+ NOREF(cCommands);
+ return VERR_DBGC_COMMANDS_NOT_REGISTERED;
+}
+
+
+/**
+ * Outputs a command or function summary line.
+ *
+ * @returns Output status code
+ * @param pCmdHlp The command helpers.
+ * @param pszName The name of the function or command.
+ * @param fExternal Whether it's external.
+ * @param pszSyntax The syntax.
+ * @param pszDescription The description.
+ */
+static int dbgcCmdHelpCmdOrFunc(PDBGCCMDHLP pCmdHlp, const char *pszName, bool fExternal,
+ const char *pszSyntax, const char *pszDescription)
+{
+ /*
+ * Aiming for "%-11s %-30s %s". Need to adjust when any of the two
+ * columns are two wide as well as break the last column up if its
+ * too wide.
+ */
+ size_t const cchMaxWidth = 100;
+ size_t const cchCol1 = 11;
+ size_t const cchCol2 = 30;
+ size_t const cchCol3 = cchMaxWidth - cchCol1 - cchCol2 - 2;
+
+ size_t const cchName = strlen(pszName) + fExternal;
+ size_t const cchSyntax = strlen(pszSyntax);
+ size_t cchDesc = strlen(pszDescription);
+
+ /* Can we do it the simple + fast way? */
+ if ( cchName <= cchCol1
+ && cchSyntax <= cchCol2
+ && cchDesc <= cchCol3)
+ return DBGCCmdHlpPrintf(pCmdHlp,
+ !fExternal ? "%-*s %-*s %s\n" : ".%-*s %-*s %s\n",
+ cchCol1, pszName,
+ cchCol2, pszSyntax,
+ pszDescription);
+
+ /* Column 1. */
+ size_t off = 0;
+ DBGCCmdHlpPrintf(pCmdHlp, !fExternal ? "%s" : ".%s", pszName);
+ off += cchName;
+ ssize_t cchPadding = cchCol1 - off;
+ if (cchPadding <= 0)
+ cchPadding = 0;
+
+ /* Column 2. */
+ DBGCCmdHlpPrintf(pCmdHlp, "%*s %s", cchPadding, "", pszSyntax);
+ off += cchPadding + 1 + cchSyntax;
+ cchPadding = cchCol1 + 1 + cchCol2 - off;
+ if (cchPadding <= 0)
+ cchPadding = 0;
+ off += cchPadding;
+
+ /* Column 3. */
+ for (;;)
+ {
+ ssize_t cchCurWidth = cchMaxWidth - off - 1;
+ if (cchCurWidth != (ssize_t)cchCol3)
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ else if ((ssize_t)cchDesc <= cchCurWidth)
+ return DBGCCmdHlpPrintf(pCmdHlp, "%*s %s\n", cchPadding, "", pszDescription);
+ else
+ {
+ /* Split on preceeding blank. */
+ const char *pszEnd = &pszDescription[cchCurWidth];
+ if (!RT_C_IS_BLANK(*pszEnd))
+ while (pszEnd != pszDescription && !RT_C_IS_BLANK(pszEnd[-1]))
+ pszEnd--;
+ const char *pszNext = pszEnd;
+
+ while (pszEnd != pszDescription && RT_C_IS_BLANK(pszEnd[-1]))
+ pszEnd--;
+ if (pszEnd == pszDescription)
+ {
+ while (*pszEnd && !RT_C_IS_BLANK(*pszEnd))
+ pszEnd++;
+ pszNext = pszEnd;
+ }
+
+ while (RT_C_IS_BLANK(*pszNext))
+ pszNext++;
+
+ /* Output it and advance to the next line. */
+ if (!*pszNext)
+ return DBGCCmdHlpPrintf(pCmdHlp, "%*s %.*s\n", cchPadding, "", pszEnd - pszDescription, pszDescription);
+ DBGCCmdHlpPrintf(pCmdHlp, "%*s %.*s\n", cchPadding, "", pszEnd - pszDescription, pszDescription);
+
+ /* next */
+ cchDesc -= pszNext - pszDescription;
+ pszDescription = pszNext;
+ }
+ off = cchCol1 + 1 + cchCol2;
+ cchPadding = off;
+ }
+}
+
+
+/**
+ * Prints full command help.
+ */
+static void dbgcCmdHelpCmdOrFuncFull(PDBGCCMDHLP pCmdHlp, const char *pszName, bool fExternal,
+ const char *pszSyntax, const char *pszDescription,
+ uint32_t cArgsMin, uint32_t cArgsMax,
+ PCDBGCVARDESC paArgDescs, uint32_t cArgDescs, uint32_t *pcHits)
+{
+ if (*pcHits)
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ *pcHits += 1;
+
+ /* the command */
+ dbgcCmdHelpCmdOrFunc(pCmdHlp, pszName, fExternal, pszSyntax, pszDescription);
+#if 1
+ char szTmp[80];
+ if (!cArgsMin && cArgsMin == cArgsMax)
+ RTStrPrintf(szTmp, sizeof(szTmp), "<no args>");
+ else if (cArgsMin == cArgsMax)
+ RTStrPrintf(szTmp, sizeof(szTmp), " <%u args>", cArgsMin);
+ else if (cArgsMax == ~0U)
+ RTStrPrintf(szTmp, sizeof(szTmp), " <%u+ args>", cArgsMin);
+ else
+ RTStrPrintf(szTmp, sizeof(szTmp), " <%u to %u args>", cArgsMin, cArgsMax);
+ dbgcCmdHelpCmdOrFunc(pCmdHlp, "", false, szTmp, "");
+#endif
+
+ /* argument descriptions. */
+ for (uint32_t i = 0; i < cArgDescs; i++)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, " %-12s %s", paArgDescs[i].pszName, paArgDescs[i].pszDescription);
+ if (!paArgDescs[i].cTimesMin)
+ {
+ if (paArgDescs[i].cTimesMax == ~0U)
+ DBGCCmdHlpPrintf(pCmdHlp, " <optional+>\n");
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " <optional-%u>\n", paArgDescs[i].cTimesMax);
+ }
+ else
+ {
+ if (paArgDescs[i].cTimesMax == ~0U)
+ DBGCCmdHlpPrintf(pCmdHlp, " <%u+>\n", paArgDescs[i].cTimesMin);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " <%u-%u>\n", paArgDescs[i].cTimesMin, paArgDescs[i].cTimesMax);
+ }
+ }
+}
+
+
+
+/**
+ * Prints full command help.
+ */
+static void dbgcPrintHelpCmd(PDBGCCMDHLP pCmdHlp, PCDBGCCMD pCmd, bool fExternal, uint32_t *pcHits)
+{
+ dbgcCmdHelpCmdOrFuncFull(pCmdHlp, pCmd->pszCmd, fExternal, pCmd->pszSyntax, pCmd->pszDescription,
+ pCmd->cArgsMin, pCmd->cArgsMax, pCmd->paArgDescs, pCmd->cArgDescs, pcHits);
+}
+
+
+/**
+ * Prints full function help.
+ */
+static void dbgcPrintHelpFunction(PDBGCCMDHLP pCmdHlp, PCDBGCFUNC pFunc, bool fExternal, uint32_t *pcHits)
+{
+ dbgcCmdHelpCmdOrFuncFull(pCmdHlp, pFunc->pszFuncNm, fExternal, pFunc->pszSyntax, pFunc->pszDescription,
+ pFunc->cArgsMin, pFunc->cArgsMax, pFunc->paArgDescs, pFunc->cArgDescs, pcHits);
+}
+
+
+static void dbgcCmdHelpCommandsWorker(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, PCDBGCCMD paCmds, uint32_t cCmds, bool fExternal,
+ const char *pszDescFmt, ...)
+{
+ RT_NOREF1(pDbgc);
+ if (pszDescFmt)
+ {
+ va_list va;
+ va_start(va, pszDescFmt);
+ pCmdHlp->pfnPrintfV(pCmdHlp, NULL, pszDescFmt, va);
+ va_end(va);
+ }
+
+ for (uint32_t i = 0; i < cCmds; i++)
+ dbgcCmdHelpCmdOrFunc(pCmdHlp, paCmds[i].pszCmd, fExternal, paCmds[i].pszSyntax, paCmds[i].pszDescription);
+}
+
+
+static void dbgcCmdHelpCommands(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, uint32_t *pcHits)
+{
+ if (*pcHits)
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ *pcHits += 1;
+
+ dbgcCmdHelpCommandsWorker(pDbgc, pCmdHlp, pDbgc->paEmulationCmds, pDbgc->cEmulationCmds, false,
+ "Commands for %s emulation:\n", pDbgc->pszEmulation);
+ dbgcCmdHelpCommandsWorker(pDbgc, pCmdHlp, g_aDbgcCmds, RT_ELEMENTS(g_aDbgcCmds), false,
+ "\nCommon Commands:\n");
+
+ DBGCEXTLISTS_LOCK_RD();
+ const char *pszDesc = "\nExternal Commands:\n";
+ for (PDBGCEXTCMDS pExtCmd = g_pExtCmdsHead; pExtCmd; pExtCmd = pExtCmd->pNext)
+ {
+ dbgcCmdHelpCommandsWorker(pDbgc, pCmdHlp, pExtCmd->paCmds, pExtCmd->cCmds, true, pszDesc);
+ pszDesc = NULL;
+ }
+ DBGCEXTLISTS_UNLOCK_RD();
+}
+
+
+static void dbgcCmdHelpFunctionsWorker(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, PCDBGCFUNC paFuncs, size_t cFuncs, bool fExternal,
+ const char *pszDescFmt, ...)
+{
+ RT_NOREF1(pDbgc);
+ if (pszDescFmt)
+ {
+ va_list va;
+ va_start(va, pszDescFmt);
+ DBGCCmdHlpPrintf(pCmdHlp, pszDescFmt, va);
+ va_end(va);
+ }
+
+ for (uint32_t i = 0; i < cFuncs; i++)
+ dbgcCmdHelpCmdOrFunc(pCmdHlp, paFuncs[i].pszFuncNm, fExternal, paFuncs[i].pszSyntax, paFuncs[i].pszDescription);
+}
+
+
+static void dbgcCmdHelpFunctions(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, uint32_t *pcHits)
+{
+ if (*pcHits)
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ *pcHits += 1;
+
+ dbgcCmdHelpFunctionsWorker(pDbgc, pCmdHlp, pDbgc->paEmulationFuncs, pDbgc->cEmulationFuncs, false,
+ "Functions for %s emulation:\n", pDbgc->pszEmulation);
+ dbgcCmdHelpFunctionsWorker(pDbgc, pCmdHlp, g_aDbgcFuncs, g_cDbgcFuncs, false,
+ "\nCommon Functions:\n");
+#if 0
+ DBGCEXTLISTS_LOCK_RD();
+ const char *pszDesc = "\nExternal Functions:\n";
+ for (PDBGCEXTFUNCS pExtFunc = g_pExtFuncsHead; pExtFunc; pExtFunc = pExtFunc->pNext)
+ {
+ dbgcCmdHelpFunctionsWorker(pDbgc, pCmdHlp, pExtFunc->paFuncs, pExtFunc->cFuncs, false,
+ pszDesc);
+ pszDesc = NULL;
+ }
+ DBGCEXTLISTS_UNLOCK_RD();
+#endif
+}
+
+
+static void dbgcCmdHelpOperators(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, uint32_t *pcHits)
+{
+ RT_NOREF1(pDbgc);
+ DBGCCmdHlpPrintf(pCmdHlp, !*pcHits ? "Operators:\n" : "\nOperators:\n");
+ *pcHits += 1;
+
+ unsigned iPrecedence = 0;
+ unsigned cLeft = g_cDbgcOps;
+ while (cLeft > 0)
+ {
+ for (unsigned i = 0; i < g_cDbgcOps; i++)
+ if (g_aDbgcOps[i].iPrecedence == iPrecedence)
+ {
+ dbgcCmdHelpCmdOrFunc(pCmdHlp, g_aDbgcOps[i].szName, false,
+ g_aDbgcOps[i].fBinary ? "Binary" : "Unary ",
+ g_aDbgcOps[i].pszDescription);
+ cLeft--;
+ }
+ iPrecedence++;
+ }
+}
+
+
+static void dbgcCmdHelpAll(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, uint32_t *pcHits)
+{
+ *pcHits += 1;
+ DBGCCmdHlpPrintf(pCmdHlp,
+ "\n"
+ "VirtualBox Debugger Help\n"
+ "------------------------\n"
+ "\n");
+ dbgcCmdHelpCommands(pDbgc, pCmdHlp, pcHits);
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ dbgcCmdHelpFunctions(pDbgc, pCmdHlp, pcHits);
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ dbgcCmdHelpOperators(pDbgc, pCmdHlp, pcHits);
+}
+
+
+static void dbgcCmdHelpSummary(PDBGC pDbgc, PDBGCCMDHLP pCmdHlp, uint32_t *pcHits)
+{
+ RT_NOREF1(pDbgc);
+ *pcHits += 1;
+ DBGCCmdHlpPrintf(pCmdHlp,
+ "\n"
+ "VirtualBox Debugger Help Summary\n"
+ "--------------------------------\n"
+ "\n"
+ "help commands Show help on all commands.\n"
+ "help functions Show help on all functions.\n"
+ "help operators Show help on all operators.\n"
+ "help all All the above.\n"
+ "help <cmd-pattern> [...]\n"
+ " Show details help on individual commands, simple\n"
+ " patterns can be used to match several commands.\n"
+ "help [summary] Displays this message.\n"
+ );
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'help' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdHelp(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc = VINF_SUCCESS;
+ uint32_t cHits = 0;
+
+ if (!cArgs)
+ /*
+ * No arguments, show summary.
+ */
+ dbgcCmdHelpSummary(pDbgc, pCmdHlp, &cHits);
+ else
+ {
+ /*
+ * Search for the arguments (strings).
+ */
+ DBGCEXTCMDS aFixedCmds[] =
+ {
+ { pDbgc->cEmulationCmds, pDbgc->paEmulationCmds, NULL },
+ { g_cDbgcCmds, g_aDbgcCmds, NULL },
+ };
+ DBGCEXTFUNCS aFixedFuncs[] =
+ {
+ { pDbgc->cEmulationFuncs, pDbgc->paEmulationFuncs, NULL },
+ { g_cDbgcFuncs, g_aDbgcFuncs, NULL },
+ };
+
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ {
+ AssertReturn(paArgs[iArg].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_BUG);
+ const char *pszPattern = paArgs[iArg].u.pszString;
+
+ /* aliases */
+ if ( !strcmp(pszPattern, "commands")
+ || !strcmp(pszPattern, "cmds") )
+ dbgcCmdHelpCommands(pDbgc, pCmdHlp, &cHits);
+ else if ( !strcmp(pszPattern, "functions")
+ || !strcmp(pszPattern, "funcs") )
+ dbgcCmdHelpFunctions(pDbgc, pCmdHlp, &cHits);
+ else if ( !strcmp(pszPattern, "operators")
+ || !strcmp(pszPattern, "ops") )
+ dbgcCmdHelpOperators(pDbgc, pCmdHlp, &cHits);
+ else if (!strcmp(pszPattern, "all"))
+ dbgcCmdHelpAll(pDbgc, pCmdHlp, &cHits);
+ else if (!strcmp(pszPattern, "summary"))
+ dbgcCmdHelpSummary(pDbgc, pCmdHlp, &cHits);
+ /* Individual commands. */
+ else
+ {
+ uint32_t const cPrevHits = cHits;
+
+ /* lookup in the emulation command list first */
+ for (unsigned j = 0; j < RT_ELEMENTS(aFixedCmds); j++)
+ for (unsigned i = 0; i < aFixedCmds[j].cCmds; i++)
+ if (RTStrSimplePatternMatch(pszPattern, aFixedCmds[j].paCmds[i].pszCmd))
+ dbgcPrintHelpCmd(pCmdHlp, &aFixedCmds[j].paCmds[i], false, &cHits);
+ for (unsigned j = 0; j < RT_ELEMENTS(aFixedFuncs); j++)
+ for (unsigned i = 0; i < aFixedFuncs[j].cFuncs; i++)
+ if (RTStrSimplePatternMatch(pszPattern, aFixedFuncs[j].paFuncs[i].pszFuncNm))
+ dbgcPrintHelpFunction(pCmdHlp, &aFixedFuncs[j].paFuncs[i], false, &cHits);
+
+ /* external commands */
+ if ( g_pExtCmdsHead
+ && ( *pszPattern == '.'
+ || *pszPattern == '?'
+ || *pszPattern == '*'))
+ {
+ DBGCEXTLISTS_LOCK_RD();
+ const char *pszPattern2 = pszPattern + (*pszPattern == '.' || *pszPattern == '?');
+ for (PDBGCEXTCMDS pExtCmd = g_pExtCmdsHead; pExtCmd; pExtCmd = pExtCmd->pNext)
+ for (unsigned i = 0; i < pExtCmd->cCmds; i++)
+ if (RTStrSimplePatternMatch(pszPattern2, pExtCmd->paCmds[i].pszCmd))
+ dbgcPrintHelpCmd(pCmdHlp, &pExtCmd->paCmds[i], true, &cHits);
+#if 0
+ for (PDBGCEXTFUNCS pExtFunc = g_pExtFuncsHead; pExtFunc; pExtFunc = pExtFunc->pNext)
+ for (unsigned i = 0; i < pExtFunc->cFuncs; i++)
+ if (RTStrSimplePatternMatch(pszPattern2, pExtFunc->paFuncs[i].pszFuncNm))
+ dbgcPrintHelpFunction(pCmdHlp, &pExtFunc->paFuncs[i], true, &cHits);
+#endif
+ DBGCEXTLISTS_UNLOCK_RD();
+ }
+
+ /* operators */
+ if (cHits == cPrevHits && strlen(paArgs[iArg].u.pszString) < sizeof(g_aDbgcOps[0].szName))
+ for (unsigned i = 0; i < g_cDbgcOps && RT_SUCCESS(rc); i++)
+ if (RTStrSimplePatternMatch(pszPattern, g_aDbgcOps[i].szName))
+ {
+ if (cHits++)
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ dbgcCmdHelpCmdOrFunc(pCmdHlp, g_aDbgcOps[i].szName, false,
+ g_aDbgcOps[i].fBinary ? "Binary" : "Unary ",
+ g_aDbgcOps[i].pszDescription);
+ }
+
+ /* found? */
+ if (cHits == cPrevHits)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "error: '%s' was not found!\n",
+ paArgs[iArg].u.pszString);
+ rc = VERR_DBGC_COMMAND_FAILED;
+ }
+ }
+ } /* foreach argument */
+ }
+
+ NOREF(pCmd);
+ NOREF(pUVM);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'multistep' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdMultiStep(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Parse arguments.
+ */
+ uint32_t cSteps = 64;
+ if (cArgs > 0)
+ {
+ if (paArgs[0].u.u64Number == 0 || paArgs[0].u.u64Number > _2G)
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_OUT_OF_RANGE,
+ "The 'count' argument is out of range: %#llx - 1..2GiB\n", paArgs[0].u.u64Number);
+ cSteps = (uint32_t)paArgs[0].u.u64Number;
+ }
+ uint32_t uStrideLength = 1;
+ if (cArgs > 1)
+ {
+ if (paArgs[1].u.u64Number == 0 || paArgs[1].u.u64Number > _2G)
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_OUT_OF_RANGE,
+ "The 'stride' argument is out of range: %#llx - 1..2GiB\n", paArgs[0].u.u64Number);
+ uStrideLength = (uint32_t)paArgs[0].u.u64Number;
+ }
+
+ /*
+ * Take the first step.
+ */
+ int rc = DBGFR3StepEx(pUVM, pDbgc->idCpu, DBGF_STEP_F_INTO, NULL, NULL, 0, uStrideLength);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgc->cMultiStepsLeft = cSteps;
+ pDbgc->uMultiStepStrideLength = uStrideLength;
+ pDbgc->pMultiStepCmd = pCmd;
+ pDbgc->fReady = false;
+ }
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3StepEx(,,DBGF_STEP_F_INTO,) failed");
+
+ NOREF(pCmd);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'quit'\, 'exit' and 'bye' commands. }
+ */
+static DECLCALLBACK(int) dbgcCmdQuit(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGCCmdHlpPrintf(pCmdHlp, "Quitting console...\n");
+ NOREF(pCmd);
+ NOREF(pUVM);
+ NOREF(paArgs);
+ NOREF(cArgs);
+ return VERR_DBGC_QUIT;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'stop' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdStop(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Parse arguments.
+ */
+ VMCPUID idCpu = VMCPUID_ALL;
+ if (cArgs == 1)
+ {
+ VMCPUID cCpus = DBGFR3CpuGetCount(pUVM);
+ if (paArgs[0].u.u64Number >= cCpus)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "idCpu %RU64 is out of range! Highest valid ID is %u.\n",
+ paArgs[0].u.u64Number, cCpus - 1);
+ idCpu = (VMCPUID)paArgs[0].u.u64Number;
+ }
+ else
+ Assert(cArgs == 0);
+
+ /*
+ * Try halt the VM or VCpu.
+ */
+ int rc = DBGFR3Halt(pUVM, idCpu);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(rc == VINF_SUCCESS || rc == VWRN_DBGF_ALREADY_HALTED);
+ if (rc != VWRN_DBGF_ALREADY_HALTED)
+ rc = VWRN_DBGC_CMD_PENDING;
+ else if (idCpu == VMCPUID_ALL)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "warning: The VM is already halted...\n");
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "warning: CPU %u is already halted...\n", idCpu);
+ }
+ else
+ rc = DBGCCmdHlpVBoxError(pCmdHlp, rc, "Executing DBGFR3Halt().");
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'echo' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdEcho(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Loop thru the arguments and print them with one space between.
+ */
+ int rc = 0;
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ AssertReturn(paArgs[i].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_BUG);
+ rc = DBGCCmdHlpPrintf(pCmdHlp, i ? " %s" : "%s", paArgs[i].u.pszString);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ NOREF(pCmd); NOREF(pUVM);
+ return DBGCCmdHlpPrintf(pCmdHlp, "\n");
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'runscript' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdRunScript(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF2(pUVM, pCmd);
+
+ /* check that the parser did what it's supposed to do. */
+ if ( cArgs != 1
+ || paArgs[0].enmType != DBGCVAR_TYPE_STRING)
+ return DBGCCmdHlpPrintf(pCmdHlp, "parser error\n");
+
+ /* Pass it on to a common function. */
+ const char *pszFilename = paArgs[0].u.pszString;
+ return dbgcEvalScript(DBGC_CMDHLP2DBGC(pCmdHlp), pszFilename, false /*fAnnounce*/);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'detect' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDetect(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /* check that the parser did what it's supposed to do. */
+ if (cArgs != 0)
+ return DBGCCmdHlpPrintf(pCmdHlp, "parser error\n");
+
+ /*
+ * Perform the detection.
+ */
+ char szName[64];
+ int rc = DBGFR3OSDetect(pUVM, szName, sizeof(szName));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Executing DBGFR3OSDetect().\n");
+ if (rc == VINF_SUCCESS)
+ {
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "Guest OS: %s\n", szName);
+ char szVersion[512];
+ int rc2 = DBGFR3OSQueryNameAndVersion(pUVM, NULL, 0, szVersion, sizeof(szVersion));
+ if (RT_SUCCESS(rc2))
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "Version : %s\n", szVersion);
+ }
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "Unable to figure out which guest OS it is, sorry.\n");
+ NOREF(pCmd); NOREF(paArgs);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dmesg' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDmesg(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /* check that the parser did what it's supposed to do. */
+ if (cArgs > 1)
+ return DBGCCmdHlpPrintf(pCmdHlp, "parser error\n");
+ uint32_t cMessages = UINT32_MAX;
+ if (cArgs == 1)
+ {
+ if (paArgs[0].enmType != DBGCVAR_TYPE_NUMBER)
+ return DBGCCmdHlpPrintf(pCmdHlp, "parser error\n");
+ cMessages = paArgs[0].u.u64Number <= UINT32_MAX ? (uint32_t)paArgs[0].u.u64Number : UINT32_MAX;
+ }
+
+ /*
+ * Query the interface.
+ */
+ int rc;
+ PDBGFOSIDMESG pDmesg = (PDBGFOSIDMESG)DBGFR3OSQueryInterface(pUVM, DBGFOSINTERFACE_DMESG);
+ if (pDmesg)
+ {
+ size_t cbActual;
+ size_t cbBuf = _512K;
+ char *pszBuf = (char *)RTMemAlloc(cbBuf);
+ if (pszBuf)
+ {
+ rc = pDmesg->pfnQueryKernelLog(pDmesg, pUVM, VMMR3GetVTable(), 0 /*fFlags*/, cMessages, pszBuf, cbBuf, &cbActual);
+
+ uint32_t cTries = 10;
+ while (rc == VERR_BUFFER_OVERFLOW && cbBuf < 16*_1M && cTries-- > 0)
+ {
+ RTMemFree(pszBuf);
+ cbBuf = RT_ALIGN_Z(cbActual + _4K, _4K);
+ pszBuf = (char *)RTMemAlloc(cbBuf);
+ if (RT_UNLIKELY(!pszBuf))
+ {
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Error allocating %#zu bytes.\n", cbBuf);
+ break;
+ }
+ rc = pDmesg->pfnQueryKernelLog(pDmesg, pUVM, VMMR3GetVTable(), 0 /*fFlags*/, cMessages, pszBuf, cbBuf, &cbActual);
+ }
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%s\n", pszBuf);
+ else if (rc == VERR_BUFFER_OVERFLOW && pszBuf)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%s\nWarning: incomplete\n", pszBuf);
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "pfnQueryKernelLog failed: %Rrc\n", rc);
+ RTMemFree(pszBuf);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Error allocating %#zu bytes.\n", cbBuf);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "The dmesg interface isn't implemented by guest OS.\n");
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'cpu' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdCpu(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /* check that the parser did what it's supposed to do. */
+ if ( cArgs != 0
+ && ( cArgs != 1
+ || paArgs[0].enmType != DBGCVAR_TYPE_NUMBER))
+ return DBGCCmdHlpPrintf(pCmdHlp, "parser error\n");
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ int rc;
+ if (!cArgs)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "Current CPU ID: %u\n", pDbgc->idCpu);
+ else
+ {
+ VMCPUID cCpus = DBGFR3CpuGetCount(pUVM);
+ if (paArgs[0].u.u64Number >= cCpus)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "error: idCpu %u is out of range! Highest ID is %u.\n",
+ paArgs[0].u.u64Number, cCpus-1);
+ else
+ {
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "Changed CPU from %u to %u.\n",
+ pDbgc->idCpu, (VMCPUID)paArgs[0].u.u64Number);
+ pDbgc->idCpu = (VMCPUID)paArgs[0].u.u64Number;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'info' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdInfo(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ if ( cArgs < 1
+ || cArgs > 2
+ || paArgs[0].enmType != DBGCVAR_TYPE_STRING
+ || paArgs[cArgs - 1].enmType != DBGCVAR_TYPE_STRING)
+ return DBGCCmdHlpPrintf(pCmdHlp, "internal error: The parser doesn't do its job properly yet.. quote the string.\n");
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Dump it.
+ */
+ int rc = DBGFR3InfoEx(pUVM, pDbgc->idCpu,
+ paArgs[0].u.pszString,
+ cArgs == 2 ? paArgs[1].u.pszString : NULL,
+ DBGCCmdHlpGetDbgfOutputHlp(pCmdHlp));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3InfoEx()\n");
+
+ NOREF(pCmd);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'log' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLog(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ int rc;
+ if (cArgs == 0)
+ {
+ char szBuf[_64K];
+ rc = RTLogQueryGroupSettings(NULL, szBuf, sizeof(szBuf));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "RTLogQueryGroupSettings(NULL,,%#zx)\n", sizeof(szBuf));
+ DBGCCmdHlpPrintf(pCmdHlp, "VBOX_LOG=%s\n", szBuf);
+ }
+ else
+ {
+ rc = DBGFR3LogModifyGroups(pUVM, paArgs[0].u.pszString);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3LogModifyGroups(%p,'%s')\n", pUVM, paArgs[0].u.pszString);
+ }
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'logdest' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLogDest(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ int rc;
+ if (cArgs == 0)
+ {
+ char szBuf[_16K];
+ rc = RTLogQueryDestinations(NULL, szBuf, sizeof(szBuf));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "RTLogQueryDestinations(NULL,,%#zx)\n", sizeof(szBuf));
+ DBGCCmdHlpPrintf(pCmdHlp, "VBOX_LOG_DEST=%s\n", szBuf);
+ }
+ else
+ {
+ rc = DBGFR3LogModifyDestinations(pUVM, paArgs[0].u.pszString);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3LogModifyDestinations(%p,'%s')\n", pUVM, paArgs[0].u.pszString);
+ }
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'logflags' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLogFlags(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ int rc;
+ if (cArgs == 0)
+ {
+ char szBuf[_16K];
+ rc = RTLogQueryFlags(NULL, szBuf, sizeof(szBuf));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "RTLogQueryFlags(NULL,,%#zx)\n", sizeof(szBuf));
+ DBGCCmdHlpPrintf(pCmdHlp, "VBOX_LOG_FLAGS=%s\n", szBuf);
+ }
+ else
+ {
+ rc = DBGFR3LogModifyFlags(pUVM, paArgs[0].u.pszString);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3LogModifyFlags(%p,'%s')\n", pUVM, paArgs[0].u.pszString);
+ }
+
+ NOREF(pCmd);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'logflush' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLogFlush(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF3(pCmdHlp, pUVM, paArgs);
+
+ RTLogFlush(NULL);
+ PRTLOGGER pLogRel = RTLogRelGetDefaultInstance();
+ if (pLogRel)
+ RTLogFlush(pLogRel);
+
+ NOREF(pCmd); NOREF(cArgs);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'format' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdFormat(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ LogFlow(("dbgcCmdFormat\n"));
+ static const char *apszRangeDesc[] =
+ {
+ "none", "bytes", "elements"
+ };
+ int rc;
+
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ {
+ switch (paArgs[iArg].enmType)
+ {
+ case DBGCVAR_TYPE_UNKNOWN:
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Unknown variable type!\n");
+ break;
+ case DBGCVAR_TYPE_GC_FLAT:
+ if (paArgs[iArg].enmRangeType != DBGCVAR_RANGE_NONE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Guest flat address: %%%08x range %lld %s\n",
+ paArgs[iArg].u.GCFlat,
+ paArgs[iArg].u64Range,
+ apszRangeDesc[paArgs[iArg].enmRangeType]);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Guest flat address: %%%08x\n",
+ paArgs[iArg].u.GCFlat);
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ if (paArgs[iArg].enmRangeType != DBGCVAR_RANGE_NONE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Guest far address: %04x:%08x range %lld %s\n",
+ paArgs[iArg].u.GCFar.sel,
+ paArgs[iArg].u.GCFar.off,
+ paArgs[iArg].u64Range,
+ apszRangeDesc[paArgs[iArg].enmRangeType]);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Guest far address: %04x:%08x\n",
+ paArgs[iArg].u.GCFar.sel,
+ paArgs[iArg].u.GCFar.off);
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ if (paArgs[iArg].enmRangeType != DBGCVAR_RANGE_NONE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Guest physical address: %%%%%08x range %lld %s\n",
+ paArgs[iArg].u.GCPhys,
+ paArgs[iArg].u64Range,
+ apszRangeDesc[paArgs[iArg].enmRangeType]);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Guest physical address: %%%%%08x\n",
+ paArgs[iArg].u.GCPhys);
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ if (paArgs[iArg].enmRangeType != DBGCVAR_RANGE_NONE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Host flat address: %%%08x range %lld %s\n",
+ paArgs[iArg].u.pvHCFlat,
+ paArgs[iArg].u64Range,
+ apszRangeDesc[paArgs[iArg].enmRangeType]);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Host flat address: %%%08x\n",
+ paArgs[iArg].u.pvHCFlat);
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ if (paArgs[iArg].enmRangeType != DBGCVAR_RANGE_NONE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Host physical address: %RHp range %lld %s\n",
+ paArgs[iArg].u.HCPhys,
+ paArgs[iArg].u64Range,
+ apszRangeDesc[paArgs[iArg].enmRangeType]);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Host physical address: %RHp\n",
+ paArgs[iArg].u.HCPhys);
+ break;
+
+ case DBGCVAR_TYPE_STRING:
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "String, %lld bytes long: %s\n",
+ paArgs[iArg].u64Range,
+ paArgs[iArg].u.pszString);
+ break;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Symbol, %lld bytes long: %s\n",
+ paArgs[iArg].u64Range,
+ paArgs[iArg].u.pszString);
+ break;
+
+ case DBGCVAR_TYPE_NUMBER:
+ if (paArgs[iArg].enmRangeType != DBGCVAR_RANGE_NONE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Number: hex %llx dec 0i%lld oct 0t%llo range %lld %s\n",
+ paArgs[iArg].u.u64Number,
+ paArgs[iArg].u.u64Number,
+ paArgs[iArg].u.u64Number,
+ paArgs[iArg].u64Range,
+ apszRangeDesc[paArgs[iArg].enmRangeType]);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Number: hex %llx dec 0i%lld oct 0t%llo\n",
+ paArgs[iArg].u.u64Number,
+ paArgs[iArg].u.u64Number,
+ paArgs[iArg].u.u64Number);
+ break;
+
+ default:
+ rc = DBGCCmdHlpPrintf(pCmdHlp,
+ "Invalid argument type %d\n",
+ paArgs[iArg].enmType);
+ break;
+ }
+ } /* arg loop */
+
+ NOREF(pCmd); NOREF(pUVM);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'loadimage' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLoadImage(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate the parsing and make sense of the input.
+ * This is a mess as usual because we don't trust the parser yet.
+ */
+ AssertReturn( cArgs >= 2
+ && cArgs <= 3
+ && paArgs[0].enmType == DBGCVAR_TYPE_STRING
+ && DBGCVAR_ISPOINTER(paArgs[1].enmType),
+ VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+
+ const char *pszFilename = paArgs[0].u.pszString;
+
+ DBGFADDRESS ModAddress;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[1], &ModAddress);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "pfnVarToDbgfAddr: %Dv\n", &paArgs[1]);
+
+ const char *pszModName = NULL;
+ if (cArgs >= 3)
+ {
+ AssertReturn(paArgs[2].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ pszModName = paArgs[2].u.pszString;
+ }
+
+ /*
+ * Determine the desired image arch from the load command used.
+ */
+ RTLDRARCH enmArch = RTLDRARCH_WHATEVER;
+ if (pCmd->pszCmd[sizeof("loadimage") - 1] == '3')
+ enmArch = RTLDRARCH_X86_32;
+ else if (pCmd->pszCmd[sizeof("loadimage") - 1] == '6')
+ enmArch = RTLDRARCH_AMD64;
+
+ /*
+ * Try create a module for it.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = DBGFR3AsLoadImage(pUVM, pDbgc->hDbgAs, pszFilename, pszModName, enmArch, &ModAddress, NIL_RTDBGSEGIDX, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3ModuleLoadImage(,,'%s','%s',%Dv,)\n",
+ pszFilename, pszModName, &paArgs[1]);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'loadinmem' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLoadInMem(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate the parsing and make sense of the input.
+ * This is a mess as usual because we don't trust the parser yet.
+ */
+ AssertReturn( cArgs >= 1
+ && cArgs <= 2
+ && DBGCVAR_ISPOINTER(paArgs[0].enmType)
+ && (cArgs < 2 || paArgs[1].enmType == DBGCVAR_TYPE_STRING),
+ VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+
+ RTLDRARCH enmArch = RTLDRARCH_WHATEVER;
+ const char *pszModName = cArgs >= 2 ? paArgs[1].u.pszString : NULL;
+ DBGFADDRESS ModAddress;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[0], &ModAddress);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "pfnVarToDbgfAddr: %Dv\n", &paArgs[1]);
+
+ /*
+ * Try create a module for it.
+ */
+ uint32_t fFlags = DBGFMODINMEM_F_NO_CONTAINER_FALLBACK | DBGFMODINMEM_F_NO_READER_FALLBACK;
+ RTDBGMOD hDbgMod;
+ RTERRINFOSTATIC ErrInfo;
+ rc = DBGFR3ModInMem(pUVM, &ModAddress, fFlags, pszModName, pszModName, enmArch, 0 /*cbImage*/,
+ &hDbgMod, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ {
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "DBGFR3ModInMem failed for %Dv: %s", &ModAddress, ErrInfo.Core.pszMsg);
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3ModInMem failed for %Dv", &ModAddress);
+ }
+
+ /*
+ * Link the module into the appropriate address space.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = DBGFR3AsLinkModule(pUVM, pDbgc->hDbgAs, hDbgMod, &ModAddress, NIL_RTDBGSEGIDX, RTDBGASLINK_FLAGS_REPLACE);
+ RTDbgModRelease(hDbgMod);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3AsLinkModule failed for %Dv", &ModAddress);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'loadmap' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLoadMap(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate the parsing and make sense of the input.
+ * This is a mess as usual because we don't trust the parser yet.
+ */
+ AssertReturn( cArgs >= 2
+ && cArgs <= 5
+ && paArgs[0].enmType == DBGCVAR_TYPE_STRING
+ && DBGCVAR_ISPOINTER(paArgs[1].enmType),
+ VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+
+ const char *pszFilename = paArgs[0].u.pszString;
+
+ DBGFADDRESS ModAddress;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[1], &ModAddress);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "pfnVarToDbgfAddr: %Dv\n", &paArgs[1]);
+
+ const char *pszModName = NULL;
+ if (cArgs >= 3)
+ {
+ AssertReturn(paArgs[2].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ pszModName = paArgs[2].u.pszString;
+ }
+
+ RTGCUINTPTR uSubtrahend = 0;
+ if (cArgs >= 4)
+ {
+ AssertReturn(paArgs[3].enmType == DBGCVAR_TYPE_NUMBER, VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ uSubtrahend = paArgs[3].u.u64Number;
+ }
+
+ RTDBGSEGIDX iModSeg = NIL_RTDBGSEGIDX;
+ if (cArgs >= 5)
+ {
+ AssertReturn(paArgs[4].enmType == DBGCVAR_TYPE_NUMBER, VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ iModSeg = (RTDBGSEGIDX)paArgs[4].u.u64Number;
+ if ( iModSeg != paArgs[4].u.u64Number
+ || iModSeg > RTDBGSEGIDX_LAST)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Segment index out of range: %Dv; range={0..%#x}\n", &paArgs[1], RTDBGSEGIDX_LAST);
+ }
+
+ /*
+ * Try create a module for it.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = DBGFR3AsLoadMap(pUVM, pDbgc->hDbgAs, pszFilename, pszModName, &ModAddress, NIL_RTDBGSEGIDX, uSubtrahend, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3AsLoadMap(,,'%s','%s',%Dv,)\n",
+ pszFilename, pszModName, &paArgs[1]);
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'loadseg' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLoadSeg(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate the parsing and make sense of the input.
+ * This is a mess as usual because we don't trust the parser yet.
+ */
+ AssertReturn( cArgs >= 3
+ && cArgs <= 4
+ && paArgs[0].enmType == DBGCVAR_TYPE_STRING
+ && DBGCVAR_ISPOINTER(paArgs[1].enmType)
+ && paArgs[2].enmType == DBGCVAR_TYPE_NUMBER,
+ VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+
+ const char *pszFilename = paArgs[0].u.pszString;
+
+ DBGFADDRESS ModAddress;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[1], &ModAddress);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "pfnVarToDbgfAddr: %Dv\n", &paArgs[1]);
+
+ RTDBGSEGIDX iModSeg = (RTDBGSEGIDX)paArgs[2].u.u64Number;
+ if ( iModSeg != paArgs[2].u.u64Number
+ || iModSeg > RTDBGSEGIDX_LAST)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Segment index out of range: %Dv; range={0..%#x}\n", &paArgs[1], RTDBGSEGIDX_LAST);
+
+ const char *pszModName = NULL;
+ if (cArgs >= 4)
+ {
+ AssertReturn(paArgs[3].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ pszModName = paArgs[3].u.pszString;
+ }
+
+ /*
+ * Call the debug info manager about this loading.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = DBGFR3AsLoadImage(pUVM, pDbgc->hDbgAs, pszFilename, pszModName, RTLDRARCH_WHATEVER,
+ &ModAddress, iModSeg, RTDBGASLINK_FLAGS_REPLACE);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3ModuleLoadImage(,,'%s','%s',%Dv,,)\n",
+ pszFilename, pszModName, &paArgs[1]);
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'unload' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdUnload(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate the parsing and make sense of the input.
+ * This is a mess as usual because we don't trust the parser yet.
+ */
+ AssertReturn( cArgs >= 1
+ && paArgs[0].enmType == DBGCVAR_TYPE_STRING,
+ VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ AssertReturn(paArgs[i].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+
+ int rc = DBGFR3AsUnlinkModuleByName(pUVM, pDbgc->hDbgAs, paArgs[i].u.pszString);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3AsUnlinkModuleByName(,,'%s')\n", paArgs[i].u.pszString);
+ }
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'set' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdSet(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /* parse sanity check. */
+ AssertMsg(paArgs[0].enmType == DBGCVAR_TYPE_STRING, ("expected string not %d as first arg!\n", paArgs[0].enmType));
+ if (paArgs[0].enmType != DBGCVAR_TYPE_STRING)
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+
+ /*
+ * A variable must start with an alpha chars and only contain alpha numerical chars.
+ */
+ const char *pszVar = paArgs[0].u.pszString;
+ if (!RT_C_IS_ALPHA(*pszVar) || *pszVar == '_')
+ return DBGCCmdHlpPrintf(pCmdHlp,
+ "syntax error: Invalid variable name '%s'. Variable names must match regex '[_a-zA-Z][_a-zA-Z0-9*'!",
+ paArgs[0].u.pszString);
+
+ while (RT_C_IS_ALNUM(*pszVar) || *pszVar == '_')
+ pszVar++;
+ if (*pszVar)
+ return DBGCCmdHlpPrintf(pCmdHlp,
+ "syntax error: Invalid variable name '%s'. Variable names must match regex '[_a-zA-Z][_a-zA-Z0-9*]'!",
+ paArgs[0].u.pszString);
+
+
+ /*
+ * Calc variable size.
+ */
+ size_t cbVar = (size_t)paArgs[0].u64Range + sizeof(DBGCNAMEDVAR);
+ if (paArgs[1].enmType == DBGCVAR_TYPE_STRING)
+ cbVar += 1 + (size_t)paArgs[1].u64Range;
+
+ /*
+ * Look for existing one.
+ */
+ pszVar = paArgs[0].u.pszString;
+ for (unsigned iVar = 0; iVar < pDbgc->cVars; iVar++)
+ {
+ if (!strcmp(pszVar, pDbgc->papVars[iVar]->szName))
+ {
+ /*
+ * Update existing variable.
+ */
+ void *pv = RTMemRealloc(pDbgc->papVars[iVar], cbVar);
+ if (!pv)
+ return VERR_DBGC_PARSE_NO_MEMORY;
+ PDBGCNAMEDVAR pVar = pDbgc->papVars[iVar] = (PDBGCNAMEDVAR)pv;
+
+ pVar->Var = paArgs[1];
+ memcpy(pVar->szName, paArgs[0].u.pszString, (size_t)paArgs[0].u64Range + 1);
+ if (paArgs[1].enmType == DBGCVAR_TYPE_STRING)
+ pVar->Var.u.pszString = (char *)memcpy(&pVar->szName[paArgs[0].u64Range + 1], paArgs[1].u.pszString, (size_t)paArgs[1].u64Range + 1);
+ return 0;
+ }
+ }
+
+ /*
+ * Allocate another.
+ */
+ PDBGCNAMEDVAR pVar = (PDBGCNAMEDVAR)RTMemAlloc(cbVar);
+
+ pVar->Var = paArgs[1];
+ memcpy(pVar->szName, pszVar, (size_t)paArgs[0].u64Range + 1);
+ if (paArgs[1].enmType == DBGCVAR_TYPE_STRING)
+ pVar->Var.u.pszString = (char *)memcpy(&pVar->szName[paArgs[0].u64Range + 1], paArgs[1].u.pszString, (size_t)paArgs[1].u64Range + 1);
+
+ /* need to reallocate the pointer array too? */
+ if (!(pDbgc->cVars % 0x20))
+ {
+ void *pv = RTMemRealloc(pDbgc->papVars, (pDbgc->cVars + 0x20) * sizeof(pDbgc->papVars[0]));
+ if (!pv)
+ {
+ RTMemFree(pVar);
+ return VERR_DBGC_PARSE_NO_MEMORY;
+ }
+ pDbgc->papVars = (PDBGCNAMEDVAR *)pv;
+ }
+ pDbgc->papVars[pDbgc->cVars++] = pVar;
+
+ NOREF(pCmd); NOREF(pUVM); NOREF(cArgs);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'unset' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdUnset(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ for (unsigned i = 0; i < cArgs; i++)
+ AssertReturn(paArgs[i].enmType == DBGCVAR_TYPE_SYMBOL, VERR_DBGC_PARSE_BUG);
+
+ /*
+ * Iterate the variables and unset them.
+ */
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ {
+ const char *pszVar = paArgs[iArg].u.pszString;
+
+ /*
+ * Look up the variable.
+ */
+ for (unsigned iVar = 0; iVar < pDbgc->cVars; iVar++)
+ {
+ if (!strcmp(pszVar, pDbgc->papVars[iVar]->szName))
+ {
+ /*
+ * Shuffle the array removing this entry.
+ */
+ void *pvFree = pDbgc->papVars[iVar];
+ if (iVar + 1 < pDbgc->cVars)
+ memmove(&pDbgc->papVars[iVar],
+ &pDbgc->papVars[iVar + 1],
+ (pDbgc->cVars - iVar - 1) * sizeof(pDbgc->papVars[0]));
+ pDbgc->papVars[--pDbgc->cVars] = NULL;
+
+ RTMemFree(pvFree);
+ }
+ } /* lookup */
+ } /* arg loop */
+
+ NOREF(pCmd); NOREF(pUVM);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'loadvars' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLoadVars(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Don't trust the parser.
+ */
+ if ( cArgs != 1
+ || paArgs[0].enmType != DBGCVAR_TYPE_STRING)
+ {
+ AssertMsgFailed(("Expected one string exactly!\n"));
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+
+ /*
+ * Iterate the variables and unset them.
+ */
+ FILE *pFile = fopen(paArgs[0].u.pszString, "r");
+ if (pFile)
+ {
+ char szLine[4096];
+ while (fgets(szLine, sizeof(szLine), pFile))
+ {
+ /* Strip it. */
+ char *psz = szLine;
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ int i = (int)strlen(psz) - 1;
+ while (i >= 0 && RT_C_IS_SPACE(psz[i]))
+ psz[i--] ='\0';
+ /* Execute it if not comment or empty line. */
+ if ( *psz != '\0'
+ && *psz != '#'
+ && *psz != ';')
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "dbg: set %s", psz);
+ pCmdHlp->pfnExec(pCmdHlp, "set %s", psz);
+ }
+ }
+ fclose(pFile);
+ }
+ else
+ return DBGCCmdHlpPrintf(pCmdHlp, "Failed to open file '%s'.\n", paArgs[0].u.pszString);
+
+ NOREF(pCmd); NOREF(pUVM);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'showvars' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdShowVars(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ for (unsigned iVar = 0; iVar < pDbgc->cVars; iVar++)
+ {
+ int rc = DBGCCmdHlpPrintf(pCmdHlp, "%-20s ", &pDbgc->papVars[iVar]->szName);
+ if (!rc)
+ rc = dbgcCmdFormat(pCmd, pCmdHlp, pUVM, &pDbgc->papVars[iVar]->Var, 1);
+ if (rc)
+ return rc;
+ }
+
+ NOREF(paArgs); NOREF(cArgs);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'sleep' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdSleep(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF(pCmd, pCmdHlp, pUVM, cArgs);
+ /** @todo make this interruptible. For now the command is limited to 30 sec. */
+ RTThreadSleep(RT_MIN((RTMSINTERVAL)paArgs[0].u.u64Number, RT_MS_30SEC));
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'loadplugin' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdLoadPlugIn(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF1(pUVM);
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Loop thru the plugin names.
+ */
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ char szPlugIn[128];
+ RTERRINFOSTATIC ErrInfo;
+ szPlugIn[0] = '\0';
+ int rc = DBGFR3PlugInLoad(pDbgc->pUVM, paArgs[i].u.pszString, szPlugIn, sizeof(szPlugIn), RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ DBGCCmdHlpPrintf(pCmdHlp, "Loaded plug-in '%s' (%s)\n", szPlugIn, paArgs[i].u.pszString);
+ else if (rc == VERR_ALREADY_EXISTS)
+ DBGCCmdHlpPrintf(pCmdHlp, "A plug-in named '%s' is already loaded\n", szPlugIn);
+ else if (szPlugIn[0])
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3PlugInLoad failed for '%s' ('%s'): %s",
+ szPlugIn, paArgs[i].u.pszString, ErrInfo.szMsg);
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3PlugInLoad failed for '%s': %s",
+ paArgs[i].u.pszString, ErrInfo.szMsg);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'unload' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdUnloadPlugIn(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF1(pUVM);
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Loop thru the given plug-in names.
+ */
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ int rc = DBGFR3PlugInUnload(pDbgc->pUVM, paArgs[i].u.pszString);
+ if (RT_SUCCESS(rc))
+ DBGCCmdHlpPrintf(pCmdHlp, "Unloaded plug-in '%s'\n", paArgs[i].u.pszString);
+ else if (rc == VERR_NOT_FOUND)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "'%s' was not found\n", paArgs[i].u.pszString);
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3PlugInUnload failed for '%s'", paArgs[i].u.pszString);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'harakiri' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdHarakiri(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ Log(("dbgcCmdHarakiri\n"));
+ for (;;)
+ exit(126);
+ NOREF(pCmd); NOREF(pCmdHlp); NOREF(pUVM); NOREF(paArgs); NOREF(cArgs);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'writecore' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdWriteCore(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ Log(("dbgcCmdWriteCore\n"));
+
+ /*
+ * Validate input, lots of paranoia here.
+ */
+ if ( cArgs != 1
+ || paArgs[0].enmType != DBGCVAR_TYPE_STRING)
+ {
+ AssertMsgFailed(("Expected one string exactly!\n"));
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+
+ const char *pszDumpPath = paArgs[0].u.pszString;
+ if (!pszDumpPath)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Missing file path.\n");
+
+ int rc = DBGFR3CoreWrite(pUVM, pszDumpPath, true /*fReplaceFile*/);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "DBGFR3WriteCore failed. rc=%Rrc\n", rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'writegstmem' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdWriteGstMem(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ LogFunc(("\n"));
+
+ /*
+ * Validate the parsing and make sense of the input.
+ * This is a mess as usual because we don't trust the parser yet.
+ */
+ AssertReturn( cArgs == 2
+ && paArgs[0].enmType == DBGCVAR_TYPE_STRING
+ && DBGCVAR_ISPOINTER(paArgs[1].enmType),
+ VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+
+ const char *pszFile = paArgs[0].u.pszString;
+ if (!pszFile)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Missing file path.\n");
+
+ DBGFADDRESS LoadAddress;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[1], &LoadAddress);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "pfnVarToDbgfAddr: %Dv\n", &paArgs[1]);
+
+ RTFILE hFile = NIL_RTFILE;
+ rc = RTFileOpen(&hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile;
+ rc = RTFileQuerySize(hFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvBuf = RTMemTmpAlloc(_16K);
+ if (RT_LIKELY(pvBuf))
+ {
+ size_t cbLeft = cbFile;
+
+ while ( cbLeft
+ && RT_SUCCESS(rc))
+ {
+ uint64_t cbThisCopy = RT_MIN(cbFile, _16K);
+
+ rc = RTFileRead(hFile, pvBuf, cbThisCopy, NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGFR3MemWrite(pUVM, pDbgc->idCpu, &LoadAddress, pvBuf, cbThisCopy);
+ if (RT_SUCCESS(rc))
+ DBGFR3AddrAdd(&LoadAddress, cbThisCopy);
+ else
+ {
+ DBGCVAR VarCur;
+ rc = DBGCCmdHlpVarFromDbgfAddr(pCmdHlp, &LoadAddress, &VarCur);
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3MemWrite(,,%DV,,%RX64) failed. rc=%Rrc\n", &VarCur, cbThisCopy, rc);
+ else
+ rc = DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGCCmdHlpVarFromDbgfAddr\n");
+ }
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "RTFileRead() failed. rc=%Rrc\n", rc);
+
+ cbLeft -= cbThisCopy;
+ }
+
+ if (RT_SUCCESS(rc))
+ DBGCCmdHlpPrintf(pCmdHlp, "Wrote 0x%RX64 (%RU64) bytes to %Dv\n", cbFile, cbFile, &paArgs[1]);
+
+ RTMemTmpFree(pvBuf);
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "RTMemTmpAlloc() failed. rc=%Rrc\n", rc);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "RTFileQuerySize() failed. rc=%Rrc\n", rc);
+
+ RTFileClose(hFile);
+ }
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "RTFileOpen(,%s,) failed. rc=%Rrc\n", pszFile, rc);
+
+ return rc;
+}
+
diff --git a/src/VBox/Debugger/DBGCDumpImage.cpp b/src/VBox/Debugger/DBGCDumpImage.cpp
new file mode 100644
index 00000000..fab04b4c
--- /dev/null
+++ b/src/VBox/Debugger/DBGCDumpImage.cpp
@@ -0,0 +1,807 @@
+/* $Id: DBGCDumpImage.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Native Commands.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/param.h>
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/formats/mz.h>
+#include <iprt/formats/pecoff.h>
+#include <iprt/formats/elf32.h>
+#include <iprt/formats/elf64.h>
+#include <iprt/formats/codeview.h>
+#include <iprt/formats/mach-o.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * PE dumper instance.
+ */
+typedef struct DUMPIMAGEPE
+{
+ /** Pointer to the image base address variable. */
+ PCDBGCVAR pImageBase;
+ /** Pointer to the file header. */
+ PCIMAGE_FILE_HEADER pFileHdr;
+ /** Pointer to the NT headers. */
+ union
+ {
+ PCIMAGE_NT_HEADERS32 pNt32;
+ PCIMAGE_NT_HEADERS64 pNt64;
+ void *pv;
+ } u;
+ /** Pointer to the section headers. */
+ PCIMAGE_SECTION_HEADER paShdrs;
+ /** Number of section headers. */
+ unsigned cShdrs;
+ /** Number of RVA and sizes (data directory entries). */
+ unsigned cDataDir;
+ /** Pointer to the data directory. */
+ PCIMAGE_DATA_DIRECTORY paDataDir;
+
+ /** The command descriptor (for failing the command). */
+ PCDBGCCMD pCmd;
+} DUMPIMAGEPE;
+/** Pointer to a PE dumper instance. */
+typedef DUMPIMAGEPE *PDUMPIMAGEPE;
+
+
+/** Helper for translating flags. */
+typedef struct
+{
+ uint32_t fFlag;
+ const char *pszNm;
+} DBGCDUMPFLAGENTRY;
+#define FLENT(a_Define) { a_Define, #a_Define }
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern FNDBGCCMD dbgcCmdDumpImage; /* See DBGCCommands.cpp. */
+
+
+/** Stringifies a 32-bit flag value. */
+static void dbgcDumpImageFlags32(PDBGCCMDHLP pCmdHlp, uint32_t fFlags, DBGCDUMPFLAGENTRY const *paEntries, size_t cEntries)
+{
+ for (size_t i = 0; i < cEntries; i++)
+ if (fFlags & paEntries[i].fFlag)
+ DBGCCmdHlpPrintf(pCmdHlp, " %s", paEntries[i].pszNm);
+}
+
+
+/*********************************************************************************************************************************
+* PE *
+*********************************************************************************************************************************/
+
+static const char *dbgcPeMachineName(uint16_t uMachine)
+{
+ switch (uMachine)
+ {
+ case IMAGE_FILE_MACHINE_I386 : return "I386";
+ case IMAGE_FILE_MACHINE_AMD64 : return "AMD64";
+ case IMAGE_FILE_MACHINE_UNKNOWN : return "UNKNOWN";
+ case IMAGE_FILE_MACHINE_BASIC_16 : return "BASIC_16";
+ case IMAGE_FILE_MACHINE_BASIC_16_TV : return "BASIC_16_TV";
+ case IMAGE_FILE_MACHINE_IAPX16 : return "IAPX16";
+ case IMAGE_FILE_MACHINE_IAPX16_TV : return "IAPX16_TV";
+ //case IMAGE_FILE_MACHINE_IAPX20 : return "IAPX20";
+ //case IMAGE_FILE_MACHINE_IAPX20_TV : return "IAPX20_TV";
+ case IMAGE_FILE_MACHINE_I8086 : return "I8086";
+ case IMAGE_FILE_MACHINE_I8086_TV : return "I8086_TV";
+ case IMAGE_FILE_MACHINE_I286_SMALL : return "I286_SMALL";
+ case IMAGE_FILE_MACHINE_MC68 : return "MC68";
+ //case IMAGE_FILE_MACHINE_MC68_WR : return "MC68_WR";
+ case IMAGE_FILE_MACHINE_MC68_TV : return "MC68_TV";
+ case IMAGE_FILE_MACHINE_MC68_PG : return "MC68_PG";
+ //case IMAGE_FILE_MACHINE_I286_LARGE : return "I286_LARGE";
+ case IMAGE_FILE_MACHINE_U370_WR : return "U370_WR";
+ case IMAGE_FILE_MACHINE_AMDAHL_470_WR: return "AMDAHL_470_WR";
+ case IMAGE_FILE_MACHINE_AMDAHL_470_RO: return "AMDAHL_470_RO";
+ case IMAGE_FILE_MACHINE_U370_RO : return "U370_RO";
+ case IMAGE_FILE_MACHINE_R4000 : return "R4000";
+ case IMAGE_FILE_MACHINE_WCEMIPSV2 : return "WCEMIPSV2";
+ case IMAGE_FILE_MACHINE_VAX_WR : return "VAX_WR";
+ case IMAGE_FILE_MACHINE_VAX_RO : return "VAX_RO";
+ case IMAGE_FILE_MACHINE_SH3 : return "SH3";
+ case IMAGE_FILE_MACHINE_SH3DSP : return "SH3DSP";
+ case IMAGE_FILE_MACHINE_SH4 : return "SH4";
+ case IMAGE_FILE_MACHINE_SH5 : return "SH5";
+ case IMAGE_FILE_MACHINE_ARM : return "ARM";
+ case IMAGE_FILE_MACHINE_THUMB : return "THUMB";
+ case IMAGE_FILE_MACHINE_ARMNT : return "ARMNT";
+ case IMAGE_FILE_MACHINE_AM33 : return "AM33";
+ case IMAGE_FILE_MACHINE_POWERPC : return "POWERPC";
+ case IMAGE_FILE_MACHINE_POWERPCFP : return "POWERPCFP";
+ case IMAGE_FILE_MACHINE_IA64 : return "IA64";
+ case IMAGE_FILE_MACHINE_MIPS16 : return "MIPS16";
+ case IMAGE_FILE_MACHINE_MIPSFPU : return "MIPSFPU";
+ case IMAGE_FILE_MACHINE_MIPSFPU16 : return "MIPSFPU16";
+ case IMAGE_FILE_MACHINE_EBC : return "EBC";
+ case IMAGE_FILE_MACHINE_M32R : return "M32R";
+ case IMAGE_FILE_MACHINE_ARM64 : return "ARM64";
+ }
+ return "??";
+}
+
+
+static const char *dbgcPeDataDirName(unsigned iDir)
+{
+ switch (iDir)
+ {
+ case IMAGE_DIRECTORY_ENTRY_EXPORT: return "EXPORT";
+ case IMAGE_DIRECTORY_ENTRY_IMPORT: return "IMPORT";
+ case IMAGE_DIRECTORY_ENTRY_RESOURCE: return "RESOURCE";
+ case IMAGE_DIRECTORY_ENTRY_EXCEPTION: return "EXCEPTION";
+ case IMAGE_DIRECTORY_ENTRY_SECURITY: return "SECURITY";
+ case IMAGE_DIRECTORY_ENTRY_BASERELOC: return "BASERELOC";
+ case IMAGE_DIRECTORY_ENTRY_DEBUG: return "DEBUG";
+ case IMAGE_DIRECTORY_ENTRY_ARCHITECTURE: return "ARCHITECTURE";
+ case IMAGE_DIRECTORY_ENTRY_GLOBALPTR: return "GLOBALPTR";
+ case IMAGE_DIRECTORY_ENTRY_TLS: return "TLS";
+ case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: return "LOAD_CONFIG";
+ case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: return "BOUND_IMPORT";
+ case IMAGE_DIRECTORY_ENTRY_IAT: return "IAT";
+ case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: return "DELAY_IMPORT";
+ case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR: return "COM_DESCRIPTOR";
+ }
+ return "??";
+}
+
+
+static const char *dbgPeDebugTypeName(uint32_t uType)
+{
+ switch (uType)
+ {
+ case IMAGE_DEBUG_TYPE_UNKNOWN: return "UNKNOWN";
+ case IMAGE_DEBUG_TYPE_COFF: return "COFF";
+ case IMAGE_DEBUG_TYPE_CODEVIEW: return "CODEVIEW";
+ case IMAGE_DEBUG_TYPE_FPO: return "FPO";
+ case IMAGE_DEBUG_TYPE_MISC: return "MISC";
+ case IMAGE_DEBUG_TYPE_EXCEPTION: return "EXCEPTION";
+ case IMAGE_DEBUG_TYPE_FIXUP: return "FIXUP";
+ case IMAGE_DEBUG_TYPE_OMAP_TO_SRC: return "OMAP_TO_SRC";
+ case IMAGE_DEBUG_TYPE_OMAP_FROM_SRC: return "OMAP_FROM_SRC";
+ case IMAGE_DEBUG_TYPE_BORLAND: return "BORLAND";
+ case IMAGE_DEBUG_TYPE_RESERVED10: return "RESERVED10";
+ case IMAGE_DEBUG_TYPE_CLSID: return "CLSID";
+ case IMAGE_DEBUG_TYPE_VC_FEATURE: return "VC_FEATURE";
+ case IMAGE_DEBUG_TYPE_POGO: return "POGO";
+ case IMAGE_DEBUG_TYPE_ILTCG: return "ILTCG";
+ case IMAGE_DEBUG_TYPE_MPX: return "MPX";
+ case IMAGE_DEBUG_TYPE_REPRO: return "REPRO";
+ }
+ return "??";
+}
+
+
+static int dbgcDumpImagePeDebugDir(PDUMPIMAGEPE pThis, PDBGCCMDHLP pCmdHlp, PCDBGCVAR pDataAddr, uint32_t cbData)
+{
+ uint32_t cEntries = cbData / sizeof(IMAGE_DEBUG_DIRECTORY);
+ for (uint32_t i = 0; i < cEntries; i++)
+ {
+ /*
+ * Read the entry into memory.
+ */
+ DBGCVAR DbgDirAddr;
+ int rc = DBGCCmdHlpEval(pCmdHlp, &DbgDirAddr, "%DV + %#RX32", pDataAddr, i * sizeof(IMAGE_DEBUG_DIRECTORY));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pThis->pCmd, rc, "DBGCCmdHlpEval failed on debug entry %u", i);
+
+ IMAGE_DEBUG_DIRECTORY DbgDir;
+ rc = DBGCCmdHlpMemRead(pCmdHlp, &DbgDir, sizeof(DbgDir), &DbgDirAddr, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pThis->pCmd, rc, "Failed to read %zu at %Dv", sizeof(DbgDir), &DbgDirAddr);
+
+ /*
+ * Dump it.
+ */
+ DBGCVAR DebugDataAddr = *pThis->pImageBase;
+ rc = DBGCCmdHlpEval(pCmdHlp, &DebugDataAddr, "%DV + %#RX32", pThis->pImageBase, DbgDir.AddressOfRawData);
+ DBGCCmdHlpPrintf(pCmdHlp, " Debug[%u]: %Dv/%08RX32 LB %06RX32 %u (%s) v%u.%u file=%RX32 ts=%08RX32 fl=%RX32\n",
+ i, &DebugDataAddr, DbgDir.AddressOfRawData, DbgDir.SizeOfData, DbgDir.Type,
+ dbgPeDebugTypeName(DbgDir.Type), DbgDir.MajorVersion, DbgDir.MinorVersion, DbgDir.PointerToRawData,
+ DbgDir.TimeDateStamp, DbgDir.Characteristics);
+ union
+ {
+ uint8_t abPage[0x1000];
+ CVPDB20INFO Pdb20;
+ CVPDB70INFO Pdb70;
+ IMAGE_DEBUG_MISC Misc;
+ } uBuf;
+ RT_ZERO(uBuf);
+
+ if (DbgDir.Type == IMAGE_DEBUG_TYPE_CODEVIEW)
+ {
+ if ( DbgDir.SizeOfData < sizeof(uBuf)
+ && DbgDir.SizeOfData > 16
+ && DbgDir.AddressOfRawData > 0
+ && RT_SUCCESS(rc))
+ {
+ rc = DBGCCmdHlpMemRead(pCmdHlp, &uBuf, DbgDir.SizeOfData, &DebugDataAddr, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pThis->pCmd, rc, "Failed to read %zu at %Dv",
+ DbgDir.SizeOfData, &DebugDataAddr);
+
+ if ( uBuf.Pdb20.u32Magic == CVPDB20INFO_MAGIC
+ && uBuf.Pdb20.offDbgInfo == 0
+ && DbgDir.SizeOfData > RT_UOFFSETOF(CVPDB20INFO, szPdbFilename) )
+ DBGCCmdHlpPrintf(pCmdHlp, " PDB2.0: ts=%08RX32 age=%RX32 %s\n",
+ uBuf.Pdb20.uTimestamp, uBuf.Pdb20.uAge, uBuf.Pdb20.szPdbFilename);
+ else if ( uBuf.Pdb20.u32Magic == CVPDB70INFO_MAGIC
+ && DbgDir.SizeOfData > RT_UOFFSETOF(CVPDB70INFO, szPdbFilename) )
+ DBGCCmdHlpPrintf(pCmdHlp, " PDB7.0: %RTuuid age=%u %s\n",
+ &uBuf.Pdb70.PdbUuid, uBuf.Pdb70.uAge, uBuf.Pdb70.szPdbFilename);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " Unknown PDB/codeview magic: %.8Rhxs\n", uBuf.abPage);
+ }
+ }
+ else if (DbgDir.Type == IMAGE_DEBUG_TYPE_MISC)
+ {
+ if ( DbgDir.SizeOfData < sizeof(uBuf)
+ && DbgDir.SizeOfData > RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data)
+ && DbgDir.AddressOfRawData > 0
+ && RT_SUCCESS(rc) )
+ {
+ rc = DBGCCmdHlpMemRead(pCmdHlp, &uBuf, DbgDir.SizeOfData, &DebugDataAddr, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pThis->pCmd, rc, "Failed to read %zu at %Dv",
+ DbgDir.SizeOfData, &DebugDataAddr);
+
+ if ( uBuf.Misc.DataType == IMAGE_DEBUG_MISC_EXENAME
+ && uBuf.Misc.Length == DbgDir.SizeOfData)
+ {
+ if (!uBuf.Misc.Unicode)
+ DBGCCmdHlpPrintf(pCmdHlp, " Misc DBG: ts=%RX32 %s\n",
+ DbgDir.TimeDateStamp, (const char *)&uBuf.Misc.Data[0]);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " Misc DBG: ts=%RX32 %ls\n",
+ DbgDir.TimeDateStamp, (PCRTUTF16)&uBuf.Misc.Data[0]);
+ }
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int dbgcDumpImagePeDataDirs(PDUMPIMAGEPE pThis, PDBGCCMDHLP pCmdHlp, unsigned cDataDirs, PCIMAGE_DATA_DIRECTORY paDataDirs)
+{
+ int rcRet = VINF_SUCCESS;
+ for (unsigned i = 0; i < cDataDirs; i++)
+ {
+ if (paDataDirs[i].Size || paDataDirs[i].VirtualAddress)
+ {
+ DBGCVAR DataAddr = *pThis->pImageBase;
+ DBGCCmdHlpEval(pCmdHlp, &DataAddr, "%DV + %#RX32", pThis->pImageBase, paDataDirs[i].VirtualAddress);
+ DBGCCmdHlpPrintf(pCmdHlp, "DataDir[%02u]: %Dv/%08RX32 LB %08RX32 %s\n",
+ i, &DataAddr, paDataDirs[i].VirtualAddress, paDataDirs[i].Size, dbgcPeDataDirName(i));
+ int rc = VINF_SUCCESS;
+ if ( i == IMAGE_DIRECTORY_ENTRY_DEBUG
+ && paDataDirs[i].Size >= sizeof(IMAGE_DEBUG_DIRECTORY))
+ rc = dbgcDumpImagePeDebugDir(pThis, pCmdHlp, &DataAddr, paDataDirs[i].Size);
+ if (RT_FAILURE(rc) && RT_SUCCESS(rcRet))
+ rcRet = rc;
+ }
+ }
+ return rcRet;
+}
+
+
+static int dbgcDumpImagePeSectionHdrs(PDUMPIMAGEPE pThis, PDBGCCMDHLP pCmdHlp, unsigned cShdrs, PCIMAGE_SECTION_HEADER paShdrs)
+{
+ for (unsigned i = 0; i < cShdrs; i++)
+ {
+ DBGCVAR SectAddr = *pThis->pImageBase;
+ DBGCCmdHlpEval(pCmdHlp, &SectAddr, "%DV + %#RX32", pThis->pImageBase, paShdrs[i].VirtualAddress);
+ DBGCCmdHlpPrintf(pCmdHlp, "Section[%02u]: %Dv/%08RX32 LB %08RX32 %.8s\n",
+ i, &SectAddr, paShdrs[i].VirtualAddress, paShdrs[i].Misc.VirtualSize, paShdrs[i].Name);
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int dbgcDumpImagePeOptHdr32(PDUMPIMAGEPE pThis, PDBGCCMDHLP pCmdHlp, PCIMAGE_NT_HEADERS32 pNtHdrs)
+{
+ RT_NOREF(pThis, pCmdHlp, pNtHdrs);
+ return VINF_SUCCESS;
+}
+
+static int dbgcDumpImagePeOptHdr64(PDUMPIMAGEPE pThis, PDBGCCMDHLP pCmdHlp, PCIMAGE_NT_HEADERS64 pNtHdrs)
+{
+ RT_NOREF(pThis, pCmdHlp, pNtHdrs);
+ return VINF_SUCCESS;
+}
+
+
+static int dbgcDumpImagePe(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PCDBGCVAR pImageBase,
+ PCDBGCVAR pPeHdrAddr, PCIMAGE_FILE_HEADER pFileHdr)
+{
+ /*
+ * Dump file header fields.
+ */
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: PE image - %#x (%s), %u sections\n", pImageBase, pFileHdr->Machine,
+ dbgcPeMachineName(pFileHdr->Machine), pFileHdr->NumberOfSections);
+ DBGCCmdHlpPrintf(pCmdHlp, "Characteristics: %#06x", pFileHdr->Characteristics);
+ if (pFileHdr->Characteristics & IMAGE_FILE_RELOCS_STRIPPED) DBGCCmdHlpPrintf(pCmdHlp, " RELOCS_STRIPPED");
+ if (pFileHdr->Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) DBGCCmdHlpPrintf(pCmdHlp, " EXECUTABLE_IMAGE");
+ if (pFileHdr->Characteristics & IMAGE_FILE_LINE_NUMS_STRIPPED) DBGCCmdHlpPrintf(pCmdHlp, " LINE_NUMS_STRIPPED");
+ if (pFileHdr->Characteristics & IMAGE_FILE_LOCAL_SYMS_STRIPPED) DBGCCmdHlpPrintf(pCmdHlp, " LOCAL_SYMS_STRIPPED");
+ if (pFileHdr->Characteristics & IMAGE_FILE_AGGRESIVE_WS_TRIM) DBGCCmdHlpPrintf(pCmdHlp, " AGGRESIVE_WS_TRIM");
+ if (pFileHdr->Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE) DBGCCmdHlpPrintf(pCmdHlp, " LARGE_ADDRESS_AWARE");
+ if (pFileHdr->Characteristics & IMAGE_FILE_16BIT_MACHINE) DBGCCmdHlpPrintf(pCmdHlp, " 16BIT_MACHINE");
+ if (pFileHdr->Characteristics & IMAGE_FILE_BYTES_REVERSED_LO) DBGCCmdHlpPrintf(pCmdHlp, " BYTES_REVERSED_LO");
+ if (pFileHdr->Characteristics & IMAGE_FILE_32BIT_MACHINE) DBGCCmdHlpPrintf(pCmdHlp, " 32BIT_MACHINE");
+ if (pFileHdr->Characteristics & IMAGE_FILE_DEBUG_STRIPPED) DBGCCmdHlpPrintf(pCmdHlp, " DEBUG_STRIPPED");
+ if (pFileHdr->Characteristics & IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) DBGCCmdHlpPrintf(pCmdHlp, " REMOVABLE_RUN_FROM_SWAP");
+ if (pFileHdr->Characteristics & IMAGE_FILE_NET_RUN_FROM_SWAP) DBGCCmdHlpPrintf(pCmdHlp, " NET_RUN_FROM_SWAP");
+ if (pFileHdr->Characteristics & IMAGE_FILE_SYSTEM) DBGCCmdHlpPrintf(pCmdHlp, " SYSTEM");
+ if (pFileHdr->Characteristics & IMAGE_FILE_DLL) DBGCCmdHlpPrintf(pCmdHlp, " DLL");
+ if (pFileHdr->Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY) DBGCCmdHlpPrintf(pCmdHlp, " UP_SYSTEM_ONLY");
+ if (pFileHdr->Characteristics & IMAGE_FILE_BYTES_REVERSED_HI) DBGCCmdHlpPrintf(pCmdHlp, " BYTES_REVERSED_HI");
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+
+ /*
+ * Allocate memory for all the headers, including section headers, and read them into memory.
+ */
+ size_t offSHdrs = pFileHdr->SizeOfOptionalHeader + sizeof(*pFileHdr) + sizeof(uint32_t);
+ size_t cbHdrs = offSHdrs + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
+ if (cbHdrs > _2M)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "%Dv: headers too big: %zu.\n", pImageBase, cbHdrs);
+
+ void *pvBuf = RTMemTmpAllocZ(cbHdrs);
+ if (!pvBuf)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "%Dv: failed to allocate %zu bytes.\n", pImageBase, cbHdrs);
+ int rc = DBGCCmdHlpMemRead(pCmdHlp, pvBuf, cbHdrs, pPeHdrAddr, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ DUMPIMAGEPE This;
+ RT_ZERO(This);
+ This.pImageBase = pImageBase;
+ This.pFileHdr = pFileHdr;
+ This.u.pv = pvBuf;
+ This.cShdrs = pFileHdr->NumberOfSections;
+ This.paShdrs = (PCIMAGE_SECTION_HEADER)((uintptr_t)pvBuf + offSHdrs);
+ This.pCmd = pCmd;
+
+ if (pFileHdr->SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32))
+ {
+ This.paDataDir = This.u.pNt32->OptionalHeader.DataDirectory;
+ This.cDataDir = This.u.pNt32->OptionalHeader.NumberOfRvaAndSizes;
+ rc = dbgcDumpImagePeOptHdr32(&This, pCmdHlp, This.u.pNt32);
+ }
+ else if (pFileHdr->SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER64))
+ {
+ This.paDataDir = This.u.pNt64->OptionalHeader.DataDirectory;
+ This.cDataDir = This.u.pNt64->OptionalHeader.NumberOfRvaAndSizes;
+ rc = dbgcDumpImagePeOptHdr64(&This, pCmdHlp, This.u.pNt64);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "%Dv: Unsupported optional header size: %#x\n",
+ pImageBase, pFileHdr->SizeOfOptionalHeader);
+
+ int rc2 = dbgcDumpImagePeSectionHdrs(&This, pCmdHlp, This.cShdrs, This.paShdrs);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+
+ rc2 = dbgcDumpImagePeDataDirs(&This, pCmdHlp, This.cDataDir, This.paDataDir);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "%Dv: Failed to read %zu at %Dv", pImageBase, cbHdrs, pPeHdrAddr);
+ RTMemTmpFree(pvBuf);
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* ELF *
+*********************************************************************************************************************************/
+
+static int dbgcDumpImageElf(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PCDBGCVAR pImageBase)
+{
+ RT_NOREF_PV(pCmd);
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: ELF image dumping not implemented yet.\n", pImageBase);
+ return VINF_SUCCESS;
+}
+
+
+/*********************************************************************************************************************************
+* Mach-O *
+*********************************************************************************************************************************/
+
+static const char *dbgcMachoFileType(uint32_t uType)
+{
+ switch (uType)
+ {
+ case MH_OBJECT: return "MH_OBJECT";
+ case MH_EXECUTE: return "MH_EXECUTE";
+ case MH_FVMLIB: return "MH_FVMLIB";
+ case MH_CORE: return "MH_CORE";
+ case MH_PRELOAD: return "MH_PRELOAD";
+ case MH_DYLIB: return "MH_DYLIB";
+ case MH_DYLINKER: return "MH_DYLINKER";
+ case MH_BUNDLE: return "MH_BUNDLE";
+ case MH_DYLIB_STUB: return "MH_DYLIB_STUB";
+ case MH_DSYM: return "MH_DSYM";
+ case MH_KEXT_BUNDLE: return "MH_KEXT_BUNDLE";
+ }
+ return "??";
+}
+
+
+static const char *dbgcMachoCpuType(int32_t iType, int32_t iSubType)
+{
+ switch (iType)
+ {
+ case CPU_TYPE_ANY: return "CPU_TYPE_ANY";
+ case CPU_TYPE_VAX: return "VAX";
+ case CPU_TYPE_MC680x0: return "MC680x0";
+ case CPU_TYPE_X86: return "X86";
+ case CPU_TYPE_X86_64:
+ switch (iSubType)
+ {
+ case CPU_SUBTYPE_X86_64_ALL: return "X86_64/ALL64";
+ }
+ return "X86_64";
+ case CPU_TYPE_MC98000: return "MC98000";
+ case CPU_TYPE_HPPA: return "HPPA";
+ case CPU_TYPE_MC88000: return "MC88000";
+ case CPU_TYPE_SPARC: return "SPARC";
+ case CPU_TYPE_I860: return "I860";
+ case CPU_TYPE_POWERPC: return "POWERPC";
+ case CPU_TYPE_POWERPC64: return "POWERPC64";
+
+ }
+ return "??";
+}
+
+
+static const char *dbgcMachoLoadCommand(uint32_t uCmd)
+{
+ switch (uCmd)
+ {
+ RT_CASE_RET_STR(LC_SEGMENT_32);
+ RT_CASE_RET_STR(LC_SYMTAB);
+ RT_CASE_RET_STR(LC_SYMSEG);
+ RT_CASE_RET_STR(LC_THREAD);
+ RT_CASE_RET_STR(LC_UNIXTHREAD);
+ RT_CASE_RET_STR(LC_LOADFVMLIB);
+ RT_CASE_RET_STR(LC_IDFVMLIB);
+ RT_CASE_RET_STR(LC_IDENT);
+ RT_CASE_RET_STR(LC_FVMFILE);
+ RT_CASE_RET_STR(LC_PREPAGE);
+ RT_CASE_RET_STR(LC_DYSYMTAB);
+ RT_CASE_RET_STR(LC_LOAD_DYLIB);
+ RT_CASE_RET_STR(LC_ID_DYLIB);
+ RT_CASE_RET_STR(LC_LOAD_DYLINKER);
+ RT_CASE_RET_STR(LC_ID_DYLINKER);
+ RT_CASE_RET_STR(LC_PREBOUND_DYLIB);
+ RT_CASE_RET_STR(LC_ROUTINES);
+ RT_CASE_RET_STR(LC_SUB_FRAMEWORK);
+ RT_CASE_RET_STR(LC_SUB_UMBRELLA);
+ RT_CASE_RET_STR(LC_SUB_CLIENT);
+ RT_CASE_RET_STR(LC_SUB_LIBRARY);
+ RT_CASE_RET_STR(LC_TWOLEVEL_HINTS);
+ RT_CASE_RET_STR(LC_PREBIND_CKSUM);
+ RT_CASE_RET_STR(LC_LOAD_WEAK_DYLIB);
+ RT_CASE_RET_STR(LC_SEGMENT_64);
+ RT_CASE_RET_STR(LC_ROUTINES_64);
+ RT_CASE_RET_STR(LC_UUID);
+ RT_CASE_RET_STR(LC_RPATH);
+ RT_CASE_RET_STR(LC_CODE_SIGNATURE);
+ RT_CASE_RET_STR(LC_SEGMENT_SPLIT_INFO);
+ RT_CASE_RET_STR(LC_REEXPORT_DYLIB);
+ RT_CASE_RET_STR(LC_LAZY_LOAD_DYLIB);
+ RT_CASE_RET_STR(LC_ENCRYPTION_INFO);
+ RT_CASE_RET_STR(LC_DYLD_INFO);
+ RT_CASE_RET_STR(LC_DYLD_INFO_ONLY);
+ RT_CASE_RET_STR(LC_LOAD_UPWARD_DYLIB);
+ RT_CASE_RET_STR(LC_VERSION_MIN_MACOSX);
+ RT_CASE_RET_STR(LC_VERSION_MIN_IPHONEOS);
+ RT_CASE_RET_STR(LC_FUNCTION_STARTS);
+ RT_CASE_RET_STR(LC_DYLD_ENVIRONMENT);
+ RT_CASE_RET_STR(LC_MAIN);
+ RT_CASE_RET_STR(LC_DATA_IN_CODE);
+ RT_CASE_RET_STR(LC_SOURCE_VERSION);
+ RT_CASE_RET_STR(LC_DYLIB_CODE_SIGN_DRS);
+ RT_CASE_RET_STR(LC_ENCRYPTION_INFO_64);
+ RT_CASE_RET_STR(LC_LINKER_OPTION);
+ RT_CASE_RET_STR(LC_LINKER_OPTIMIZATION_HINT);
+ RT_CASE_RET_STR(LC_VERSION_MIN_TVOS);
+ RT_CASE_RET_STR(LC_VERSION_MIN_WATCHOS);
+ RT_CASE_RET_STR(LC_NOTE);
+ RT_CASE_RET_STR(LC_BUILD_VERSION);
+ }
+ return "??";
+}
+
+
+static const char *dbgcMachoProt(uint32_t fProt)
+{
+ switch (fProt)
+ {
+ case VM_PROT_NONE: return "---";
+ case VM_PROT_READ: return "r--";
+ case VM_PROT_READ | VM_PROT_WRITE: return "rw-";
+ case VM_PROT_READ | VM_PROT_EXECUTE: return "r-x";
+ case VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE: return "rwx";
+ case VM_PROT_WRITE: return "-w-";
+ case VM_PROT_WRITE | VM_PROT_EXECUTE: return "-wx";
+ case VM_PROT_EXECUTE: return "-w-";
+ }
+ return "???";
+}
+
+
+static int dbgcDumpImageMachO(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PCDBGCVAR pImageBase, mach_header_64_t const *pHdr)
+{
+#define ENTRY(a_Define) { a_Define, #a_Define }
+ RT_NOREF_PV(pCmd);
+
+ /*
+ * Header:
+ */
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: Mach-O image (%s bit) - %s (%u) - %s (%#x / %#x)\n",
+ pImageBase, pHdr->magic == IMAGE_MACHO64_SIGNATURE ? "64" : "32",
+ dbgcMachoFileType(pHdr->filetype), pHdr->filetype,
+ dbgcMachoCpuType(pHdr->cputype, pHdr->cpusubtype), pHdr->cputype, pHdr->cpusubtype);
+
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: Flags: %#x", pImageBase, pHdr->flags);
+ static DBGCDUMPFLAGENTRY const s_aHdrFlags[] =
+ {
+ FLENT(MH_NOUNDEFS), FLENT(MH_INCRLINK),
+ FLENT(MH_DYLDLINK), FLENT(MH_BINDATLOAD),
+ FLENT(MH_PREBOUND), FLENT(MH_SPLIT_SEGS),
+ FLENT(MH_LAZY_INIT), FLENT(MH_TWOLEVEL),
+ FLENT(MH_FORCE_FLAT), FLENT(MH_NOMULTIDEFS),
+ FLENT(MH_NOFIXPREBINDING), FLENT(MH_PREBINDABLE),
+ FLENT(MH_ALLMODSBOUND), FLENT(MH_SUBSECTIONS_VIA_SYMBOLS),
+ FLENT(MH_CANONICAL), FLENT(MH_WEAK_DEFINES),
+ FLENT(MH_BINDS_TO_WEAK), FLENT(MH_ALLOW_STACK_EXECUTION),
+ FLENT(MH_ROOT_SAFE), FLENT(MH_SETUID_SAFE),
+ FLENT(MH_NO_REEXPORTED_DYLIBS), FLENT(MH_PIE),
+ FLENT(MH_DEAD_STRIPPABLE_DYLIB), FLENT(MH_HAS_TLV_DESCRIPTORS),
+ FLENT(MH_NO_HEAP_EXECUTION),
+ };
+ dbgcDumpImageFlags32(pCmdHlp, pHdr->flags, s_aHdrFlags, RT_ELEMENTS(s_aHdrFlags));
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ if (pHdr->reserved != 0 && pHdr->magic == IMAGE_MACHO64_SIGNATURE)
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: Reserved header field: %#x\n", pImageBase, pHdr->reserved);
+
+ /*
+ * And now the load commands.
+ */
+ const uint32_t cCmds = pHdr->ncmds;
+ const uint32_t cbCmds = pHdr->sizeofcmds;
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: %u load commands covering %#x bytes:\n", pImageBase, cCmds, cbCmds);
+ if (cbCmds > _16M)
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_OUT_OF_RANGE,
+ "%Dv: Commands too big: %#x bytes, max 16MiB\n", pImageBase, cbCmds);
+
+ /* Calc address of the first command: */
+ const uint32_t cbHdr = pHdr->magic == IMAGE_MACHO64_SIGNATURE ? sizeof(mach_header_64_t) : sizeof(mach_header_32_t);
+ DBGCVAR Addr;
+ int rc = DBGCCmdHlpEval(pCmdHlp, &Addr, "%DV + %#RX32", pImageBase, cbHdr);
+ AssertRCReturn(rc, rc);
+
+ /* Read them into a temp buffer: */
+ uint8_t *pbCmds = (uint8_t *)RTMemTmpAllocZ(cbCmds);
+ if (!pbCmds)
+ return VERR_NO_TMP_MEMORY;
+
+ rc = DBGCCmdHlpMemRead(pCmdHlp, pbCmds, cbCmds, &Addr, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ static const DBGCDUMPFLAGENTRY s_aSegFlags[] =
+ { FLENT(SG_HIGHVM), FLENT(SG_FVMLIB), FLENT(SG_NORELOC), FLENT(SG_PROTECTED_VERSION_1), };
+
+ /*
+ * Iterate the commands.
+ */
+ uint32_t offCmd = 0;
+ for (uint32_t iCmd = 0; iCmd < cCmds; iCmd++)
+ {
+ load_command_t const *pCurCmd = (load_command_t const *)&pbCmds[offCmd];
+ const uint32_t cbCurCmd = offCmd + sizeof(*pCurCmd) <= cbCmds ? pCurCmd->cmdsize : sizeof(*pCurCmd);
+ if (offCmd + cbCurCmd > cbCmds)
+ {
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_OUT_OF_RANGE,
+ "%Dv: Load command #%u (offset %#x + %#x) is out of bounds! cmdsize=%u (%#x) cmd=%u\n",
+ pImageBase, iCmd, offCmd, cbHdr, cbCurCmd, cbCurCmd,
+ offCmd + RT_UOFFSET_AFTER(load_command_t, cmd) <= cbCmds ? pCurCmd->cmd : UINT32_MAX);
+ break;
+ }
+
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: Load command #%u (offset %#x + %#x): %s (%u) LB %u\n",
+ pImageBase, iCmd, offCmd, cbHdr, dbgcMachoLoadCommand(pCurCmd->cmd), pCurCmd->cmd, cbCurCmd);
+ switch (pCurCmd->cmd)
+ {
+ case LC_SEGMENT_64:
+ if (cbCurCmd < sizeof(segment_command_64_t))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "%Dv: LC_SEGMENT64 is too short!\n", pImageBase);
+ else
+ {
+ segment_command_64_t const *pSeg = (segment_command_64_t const *)pCurCmd;
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: vmaddr: %016RX64 LB %08RX64 prot: %s(%x) maxprot: %s(%x) name: %.16s\n",
+ pImageBase, pSeg->vmaddr, pSeg->vmsize, dbgcMachoProt(pSeg->initprot), pSeg->initprot,
+ dbgcMachoProt(pSeg->maxprot), pSeg->maxprot, pSeg->segname);
+ DBGCCmdHlpPrintf(pCmdHlp, "%Dv: file: %016RX64 LB %08RX64 sections: %2u flags: %#x",
+ pImageBase, pSeg->fileoff, pSeg->filesize, pSeg->nsects, pSeg->flags);
+ dbgcDumpImageFlags32(pCmdHlp, pSeg->flags, s_aSegFlags, RT_ELEMENTS(s_aSegFlags));
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ if ( pSeg->nsects > _64K
+ || pSeg->nsects * sizeof(section_64_t) + sizeof(pSeg) > cbCurCmd)
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "%Dv: LC_SEGMENT64 is too short for all the sections!\n", pImageBase);
+ else
+ {
+ section_64_t const *paSec = (section_64_t const *)(pSeg + 1);
+ for (uint32_t iSec = 0; iSec < pSeg->nsects; iSec++)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp,
+ "%Dv: Section #%u: %016RX64 LB %08RX64 align: 2**%-2u name: %.16s",
+ pImageBase, iSec, paSec[iSec].addr, paSec[iSec].size, paSec[iSec].align,
+ paSec[iSec].sectname);
+ if (strncmp(pSeg->segname, paSec[iSec].segname, sizeof(pSeg->segname)))
+ DBGCCmdHlpPrintf(pCmdHlp, "(in %.16s)", paSec[iSec].segname);
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+
+ /// @todo Good night!
+ /// uint32_t offset;
+ /// uint32_t reloff;
+ /// uint32_t nreloc;
+ /// uint32_t flags;
+ /// /** For S_LAZY_SYMBOL_POINTERS, S_NON_LAZY_SYMBOL_POINTERS and S_SYMBOL_STUBS
+ /// * this is the index into the indirect symbol table. */
+ /// uint32_t reserved1;
+ /// uint32_t reserved2;
+ /// uint32_t reserved3;
+ ///
+ }
+ }
+ }
+ break;
+ }
+
+ /* Advance: */
+ offCmd += cbCurCmd;
+ }
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "%Dv: Error reading load commands %Dv LB %#x\n",
+ pImageBase, &Addr, cbCmds);
+ RTMemTmpFree(pbCmds);
+ return rc;
+#undef ENTRY
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dumpimage' command.}
+ */
+DECLCALLBACK(int) dbgcCmdDumpImage(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ int rcRet = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ {
+ union
+ {
+ uint8_t ab[0x10];
+ IMAGE_DOS_HEADER DosHdr;
+ struct
+ {
+ uint32_t u32Magic;
+ IMAGE_FILE_HEADER FileHdr;
+ } Nt;
+ mach_header_64_t MachO64;
+ } uBuf;
+ DBGCVAR const ImageBase = paArgs[iArg];
+ int rc = DBGCCmdHlpMemRead(pCmdHlp, &uBuf.DosHdr, sizeof(uBuf.DosHdr), &ImageBase, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * MZ.
+ */
+ if (uBuf.DosHdr.e_magic == IMAGE_DOS_SIGNATURE)
+ {
+ uint32_t offNewHdr = uBuf.DosHdr.e_lfanew;
+ if (offNewHdr < _256K && offNewHdr >= 16)
+ {
+ /* Look for new header. */
+ DBGCVAR NewHdrAddr;
+ rc = DBGCCmdHlpEval(pCmdHlp, &NewHdrAddr, "%DV + %#RX32", &ImageBase, offNewHdr);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGCCmdHlpMemRead(pCmdHlp, &uBuf.Nt, sizeof(uBuf.Nt), &NewHdrAddr, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* PE: */
+ if (uBuf.Nt.u32Magic == IMAGE_NT_SIGNATURE)
+ rc = dbgcDumpImagePe(pCmd, pCmdHlp, &ImageBase, &NewHdrAddr, &uBuf.Nt.FileHdr);
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "%Dv: Unknown new header magic: %.8Rhxs\n",
+ &ImageBase, uBuf.ab);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "%Dv: Failed to read %zu at %Dv",
+ &ImageBase, sizeof(uBuf.Nt), &NewHdrAddr);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "%Dv: Failed to calc address of new header", &ImageBase);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "%Dv: MZ header but e_lfanew=%#RX32 is out of bounds (16..256K).\n",
+ &ImageBase, offNewHdr);
+ }
+ /*
+ * ELF.
+ */
+ else if (uBuf.ab[0] == ELFMAG0 && uBuf.ab[1] == ELFMAG1 && uBuf.ab[2] == ELFMAG2 && uBuf.ab[3] == ELFMAG3)
+ rc = dbgcDumpImageElf(pCmd, pCmdHlp, &ImageBase);
+ /*
+ * Mach-O.
+ */
+ else if ( uBuf.MachO64.magic == IMAGE_MACHO64_SIGNATURE
+ || uBuf.MachO64.magic == IMAGE_MACHO32_SIGNATURE )
+ rc = dbgcDumpImageMachO(pCmd, pCmdHlp, &ImageBase, &uBuf.MachO64);
+ /*
+ * Dunno.
+ */
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "%Dv: Unknown magic: %.8Rhxs\n", &ImageBase, uBuf.ab);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "%Dv: Failed to read %zu", &ImageBase, sizeof(uBuf.DosHdr));
+ if (RT_FAILURE(rc) && RT_SUCCESS(rcRet))
+ rcRet = rc;
+ }
+ RT_NOREF(pUVM);
+ return rcRet;
+}
+
diff --git a/src/VBox/Debugger/DBGCEmulateCodeView.cpp b/src/VBox/Debugger/DBGCEmulateCodeView.cpp
new file mode 100644
index 00000000..b5851902
--- /dev/null
+++ b/src/VBox/Debugger/DBGCEmulateCodeView.cpp
@@ -0,0 +1,6982 @@
+/* $Id: DBGCEmulateCodeView.cpp $ */
+/** @file
+ * DBGC - Debugger Console, CodeView / WinDbg Emulation.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/dbgfflowtrace.h>
+#include <VBox/vmm/pgm.h>
+#include <VBox/vmm/cpum.h>
+#include <VBox/dis.h>
+#include <VBox/param.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/time.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static FNDBGCCMD dbgcCmdBrkAccess;
+static FNDBGCCMD dbgcCmdBrkClear;
+static FNDBGCCMD dbgcCmdBrkDisable;
+static FNDBGCCMD dbgcCmdBrkEnable;
+static FNDBGCCMD dbgcCmdBrkList;
+static FNDBGCCMD dbgcCmdBrkSet;
+static FNDBGCCMD dbgcCmdBrkREM;
+static FNDBGCCMD dbgcCmdDumpMem;
+static FNDBGCCMD dbgcCmdDumpDT;
+static FNDBGCCMD dbgcCmdDumpIDT;
+static FNDBGCCMD dbgcCmdDumpPageDir;
+static FNDBGCCMD dbgcCmdDumpPageDirBoth;
+static FNDBGCCMD dbgcCmdDumpPageHierarchy;
+static FNDBGCCMD dbgcCmdDumpPageTable;
+static FNDBGCCMD dbgcCmdDumpPageTableBoth;
+static FNDBGCCMD dbgcCmdDumpTSS;
+static FNDBGCCMD dbgcCmdDumpTypeInfo;
+static FNDBGCCMD dbgcCmdDumpTypedVal;
+static FNDBGCCMD dbgcCmdEditMem;
+static FNDBGCCMD dbgcCmdGo;
+static FNDBGCCMD dbgcCmdGoUp;
+static FNDBGCCMD dbgcCmdListModules;
+static FNDBGCCMD dbgcCmdListNear;
+static FNDBGCCMD dbgcCmdListSource;
+static FNDBGCCMD dbgcCmdListSymbols;
+static FNDBGCCMD dbgcCmdMemoryInfo;
+static FNDBGCCMD dbgcCmdReg;
+static FNDBGCCMD dbgcCmdRegGuest;
+static FNDBGCCMD dbgcCmdRegTerse;
+static FNDBGCCMD dbgcCmdSearchMem;
+static FNDBGCCMD dbgcCmdSearchMemType;
+static FNDBGCCMD dbgcCmdStepTrace;
+static FNDBGCCMD dbgcCmdStepTraceTo;
+static FNDBGCCMD dbgcCmdStepTraceToggle;
+static FNDBGCCMD dbgcCmdEventCtrl;
+static FNDBGCCMD dbgcCmdEventCtrlList;
+static FNDBGCCMD dbgcCmdEventCtrlReset;
+static FNDBGCCMD dbgcCmdStack;
+static FNDBGCCMD dbgcCmdUnassemble;
+static FNDBGCCMD dbgcCmdUnassembleCfg;
+static FNDBGCCMD dbgcCmdTraceFlowClear;
+static FNDBGCCMD dbgcCmdTraceFlowDisable;
+static FNDBGCCMD dbgcCmdTraceFlowEnable;
+static FNDBGCCMD dbgcCmdTraceFlowPrint;
+static FNDBGCCMD dbgcCmdTraceFlowReset;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** 'ba' arguments. */
+static const DBGCVARDESC g_aArgBrkAcc[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "access", "The access type: x=execute, rw=read/write (alias r), w=write, i=not implemented." },
+ { 1, 1, DBGCVAR_CAT_NUMBER, 0, "size", "The access size: 1, 2, 4, or 8. 'x' access requires 1, and 8 requires amd64 long mode." },
+ { 1, 1, DBGCVAR_CAT_GC_POINTER, 0, "address", "The address." },
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "passes", "The number of passes before we trigger the breakpoint. (0 is default)" },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "max passes", "The number of passes after which we stop triggering the breakpoint. (~0 is default)" },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "cmds", "String of commands to be executed when the breakpoint is hit. Quote it!" },
+};
+
+
+/** 'bc', 'bd', 'be' arguments. */
+static const DBGCVARDESC g_aArgBrks[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_NUMBER, 0, "#bp", "Breakpoint number." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "all", "All breakpoints." },
+};
+
+
+/** 'bp' arguments. */
+static const DBGCVARDESC g_aArgBrkSet[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_GC_POINTER, 0, "address", "The address." },
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "passes", "The number of passes before we trigger the breakpoint. (0 is default)" },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "max passes", "The number of passes after which we stop triggering the breakpoint. (~0 is default)" },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "cmds", "String of commands to be executed when the breakpoint is hit. Quote it!" },
+};
+
+
+/** 'br' arguments. */
+static const DBGCVARDESC g_aArgBrkREM[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_GC_POINTER, 0, "address", "The address." },
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "passes", "The number of passes before we trigger the breakpoint. (0 is default)" },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "max passes", "The number of passes after which we stop triggering the breakpoint. (~0 is default)" },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "cmds", "String of commands to be executed when the breakpoint is hit. Quote it!" },
+};
+
+
+/** 'd?' arguments. */
+static const DBGCVARDESC g_aArgDumpMem[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address where to start dumping memory." },
+};
+
+
+/** 'dg', 'dga', 'dl', 'dla' arguments. */
+static const DBGCVARDESC g_aArgDumpDT[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_NUMBER, 0, "sel", "Selector or selector range." },
+ { 0, ~0U, DBGCVAR_CAT_POINTER, 0, "address", "Far address which selector should be dumped." },
+};
+
+
+/** 'di', 'dia' arguments. */
+static const DBGCVARDESC g_aArgDumpIDT[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_NUMBER, 0, "int", "The interrupt vector or interrupt vector range." },
+};
+
+
+/** 'dpd*' arguments. */
+static const DBGCVARDESC g_aArgDumpPD[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "index", "Index into the page directory." },
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address which page directory entry to start dumping from. Range is applied to the page directory." },
+};
+
+
+/** 'dpda' arguments. */
+static const DBGCVARDESC g_aArgDumpPDAddr[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address of the page directory entry to start dumping from." },
+};
+
+
+/** 'dph*' arguments. */
+static const DBGCVARDESC g_aArgDumpPH[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_GC_POINTER, 0, "address", "Where in the address space to start dumping and for how long (range). The default address/range will be used if omitted." },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "cr3", "The CR3 value to use. The current CR3 of the context will be used if omitted." },
+ { 0, 1, DBGCVAR_CAT_STRING, DBGCVD_FLAGS_DEP_PREV, "mode", "The paging mode: legacy, pse, pae, long, ept. Append '-np' for nested paging and '-nx' for no-execute. The current mode will be used if omitted." },
+};
+
+
+/** 'dpt?' arguments. */
+static const DBGCVARDESC g_aArgDumpPT[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address which page directory entry to start dumping from." },
+};
+
+
+/** 'dpta' arguments. */
+static const DBGCVARDESC g_aArgDumpPTAddr[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address of the page table entry to start dumping from." },
+};
+
+
+/** 'dt' arguments. */
+static const DBGCVARDESC g_aArgDumpTSS[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "tss", "TSS selector number." },
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "tss:ign|addr", "TSS address. If the selector is a TSS selector, the offset will be ignored." }
+};
+
+
+/** 'dti' arguments. */
+static const DBGCVARDESC g_aArgDumpTypeInfo[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "type", "The type to dump" },
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "levels", "How many levels to dump the type information" }
+};
+
+
+/** 'dtv' arguments. */
+static const DBGCVARDESC g_aArgDumpTypedVal[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "type", "The type to use" },
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address to start dumping from." },
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "levels", "How many levels to dump" }
+};
+
+
+/** 'e?' arguments. */
+static const DBGCVARDESC g_aArgEditMem[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address where to write." },
+ { 1, ~0U, DBGCVAR_CAT_NUMBER, 0, "value", "Value to write." },
+};
+
+
+/** 'g' arguments. */
+static const DBGCVARDESC g_aArgGo[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "idCpu", "CPU ID." },
+};
+
+
+/** 'lm' arguments. */
+static const DBGCVARDESC g_aArgListMods[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_STRING, 0, "module", "Module name." },
+};
+
+
+/** 'ln' arguments. */
+static const DBGCVARDESC g_aArgListNear[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_POINTER, 0, "address", "Address of the symbol to look up." },
+ { 0, ~0U, DBGCVAR_CAT_SYMBOL, 0, "symbol", "Symbol to lookup." },
+};
+
+
+/** 'ls' arguments. */
+static const DBGCVARDESC g_aArgListSource[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address where to start looking for source lines." },
+};
+
+
+/** 'm' argument. */
+static const DBGCVARDESC g_aArgMemoryInfo[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "Pointer to obtain info about." },
+};
+
+
+/** 'p', 'pc', 'pt', 't', 'tc' and 'tt' arguments. */
+static const DBGCVARDESC g_aArgStepTrace[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_NUMBER, 0, "count", "Number of instructions or source lines to step." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "cmds", "String of commands to be executed afterwards. Quote it!" },
+};
+
+
+/** 'pa' and 'ta' arguments. */
+static const DBGCVARDESC g_aArgStepTraceTo[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER, 0, "address", "Where to stop" },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "cmds", "String of commands to be executed afterwards. Quote it!" },
+};
+
+
+/** 'r' arguments. */
+static const DBGCVARDESC g_aArgReg[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_SYMBOL, 0, "register", "Register to show or set." },
+ { 0, 1, DBGCVAR_CAT_STRING, DBGCVD_FLAGS_DEP_PREV, "=", "Equal sign." },
+ { 0, 1, DBGCVAR_CAT_NUMBER, DBGCVD_FLAGS_DEP_PREV, "value", "New register value." },
+};
+
+
+/** 's' arguments. */
+static const DBGCVARDESC g_aArgSearchMem[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_OPTION, 0, "-b", "Byte string." },
+ { 0, 1, DBGCVAR_CAT_OPTION, 0, "-w", "Word string." },
+ { 0, 1, DBGCVAR_CAT_OPTION, 0, "-d", "DWord string." },
+ { 0, 1, DBGCVAR_CAT_OPTION, 0, "-q", "QWord string." },
+ { 0, 1, DBGCVAR_CAT_OPTION, 0, "-a", "ASCII string." },
+ { 0, 1, DBGCVAR_CAT_OPTION, 0, "-u", "Unicode string." },
+ { 0, 1, DBGCVAR_CAT_OPTION_NUMBER, 0, "-n <Hits>", "Maximum number of hits." },
+ { 0, 1, DBGCVAR_CAT_GC_POINTER, 0, "range", "Register to show or set." },
+ { 0, ~0U, DBGCVAR_CAT_ANY, 0, "pattern", "Pattern to search for." },
+};
+
+
+/** 's?' arguments. */
+static const DBGCVARDESC g_aArgSearchMemType[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_GC_POINTER, 0, "range", "Register to show or set." },
+ { 1, ~0U, DBGCVAR_CAT_ANY, 0, "pattern", "Pattern to search for." },
+};
+
+
+/** 'sxe', 'sxn', 'sxi', 'sx-' arguments. */
+static const DBGCVARDESC g_aArgEventCtrl[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "-c", "The -c option, requires <cmds>." },
+ { 0, 1, DBGCVAR_CAT_STRING, DBGCVD_FLAGS_DEP_PREV, "cmds", "Command to execute on this event." },
+ { 0 /*weird*/, ~0U, DBGCVAR_CAT_STRING, 0, "event", "One or more events, 'all' refering to all events." },
+};
+
+/** 'sx' and 'sr' arguments. */
+static const DBGCVARDESC g_aArgEventCtrlOpt[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_STRING, 0, "event", "Zero or more events, 'all' refering to all events and being the default." },
+};
+
+/** 'u' arguments. */
+static const DBGCVARDESC g_aArgUnassemble[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address where to start disassembling." },
+};
+
+/** 'ucfg' arguments. */
+static const DBGCVARDESC g_aArgUnassembleCfg[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address where to start disassembling." },
+};
+
+/** 'x' arguments. */
+static const DBGCVARDESC g_aArgListSyms[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_STRING, 0, "symbols", "The symbols to list, format is Module!Symbol with wildcards being supoprted." }
+};
+
+/** 'tflowc' arguments. */
+static const DBGCVARDESC g_aArgTraceFlowClear[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_NUMBER, 0, "#tf", "Trace flow module number." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "all", "All trace flow modules." },
+};
+
+/** 'tflowd' arguments. */
+static const DBGCVARDESC g_aArgTraceFlowDisable[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_NUMBER, 0, "#tf", "Trace flow module number." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "all", "All trace flow modules." },
+};
+
+/** 'tflowe' arguments. */
+static const DBGCVARDESC g_aArgTraceFlowEnable[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, 1, DBGCVAR_CAT_POINTER, 0, "address", "Address where to start tracing." },
+ { 0, 1, DBGCVAR_CAT_OPTION_NUMBER, 0, "<Hits>", "Maximum number of hits before the module is disabled." }
+};
+
+/** 'tflowp', 'tflowr' arguments. */
+static const DBGCVARDESC g_aArgTraceFlowPrintReset[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 0, ~0U, DBGCVAR_CAT_NUMBER, 0, "#tf", "Trace flow module number." },
+ { 0, 1, DBGCVAR_CAT_STRING, 0, "all", "All trace flow modules." },
+};
+
+/** Command descriptors for the CodeView / WinDbg emulation.
+ * The emulation isn't attempting to be identical, only somewhat similar.
+ */
+const DBGCCMD g_aCmdsCodeView[] =
+{
+ /* pszCmd, cArgsMin, cArgsMax, paArgDescs, cArgDescs, fFlags, pfnHandler pszSyntax, ....pszDescription */
+ { "ba", 3, 6, &g_aArgBrkAcc[0], RT_ELEMENTS(g_aArgBrkAcc), 0, dbgcCmdBrkAccess, "<access> <size> <address> [passes [max passes]] [cmds]",
+ "Sets a data access breakpoint." },
+ { "bc", 1, ~0U, &g_aArgBrks[0], RT_ELEMENTS(g_aArgBrks), 0, dbgcCmdBrkClear, "all | <bp#> [bp# []]", "Deletes a set of breakpoints." },
+ { "bd", 1, ~0U, &g_aArgBrks[0], RT_ELEMENTS(g_aArgBrks), 0, dbgcCmdBrkDisable, "all | <bp#> [bp# []]", "Disables a set of breakpoints." },
+ { "be", 1, ~0U, &g_aArgBrks[0], RT_ELEMENTS(g_aArgBrks), 0, dbgcCmdBrkEnable, "all | <bp#> [bp# []]", "Enables a set of breakpoints." },
+ { "bl", 0, 0, NULL, 0, 0, dbgcCmdBrkList, "", "Lists all the breakpoints." },
+ { "bp", 1, 4, &g_aArgBrkSet[0], RT_ELEMENTS(g_aArgBrkSet), 0, dbgcCmdBrkSet, "<address> [passes [max passes]] [cmds]",
+ "Sets a breakpoint (int 3)." },
+ { "br", 1, 4, &g_aArgBrkREM[0], RT_ELEMENTS(g_aArgBrkREM), 0, dbgcCmdBrkREM, "<address> [passes [max passes]] [cmds]",
+ "Sets a recompiler specific breakpoint." },
+ { "d", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory using last element size and type." },
+ { "dF", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as far 16:16." },
+ { "dFs", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as far 16:16 with near symbols." },
+ { "da", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as ascii string." },
+ { "db", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory in bytes." },
+ { "dd", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory in double words." },
+ { "dds", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as double words with near symbols." },
+ { "da", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as ascii string." },
+ { "dg", 0, ~0U, &g_aArgDumpDT[0], RT_ELEMENTS(g_aArgDumpDT), 0, dbgcCmdDumpDT, "[sel [..]]", "Dump the global descriptor table (GDT)." },
+ { "dga", 0, ~0U, &g_aArgDumpDT[0], RT_ELEMENTS(g_aArgDumpDT), 0, dbgcCmdDumpDT, "[sel [..]]", "Dump the global descriptor table (GDT) including not-present entries." },
+ { "di", 0, ~0U, &g_aArgDumpIDT[0], RT_ELEMENTS(g_aArgDumpIDT), 0, dbgcCmdDumpIDT, "[int [..]]", "Dump the interrupt descriptor table (IDT)." },
+ { "dia", 0, ~0U, &g_aArgDumpIDT[0], RT_ELEMENTS(g_aArgDumpIDT), 0, dbgcCmdDumpIDT, "[int [..]]", "Dump the interrupt descriptor table (IDT) including not-present entries." },
+ { "dl", 0, ~0U, &g_aArgDumpDT[0], RT_ELEMENTS(g_aArgDumpDT), 0, dbgcCmdDumpDT, "[sel [..]]", "Dump the local descriptor table (LDT)." },
+ { "dla", 0, ~0U, &g_aArgDumpDT[0], RT_ELEMENTS(g_aArgDumpDT), 0, dbgcCmdDumpDT, "[sel [..]]", "Dump the local descriptor table (LDT) including not-present entries." },
+ { "dpd", 0, 1, &g_aArgDumpPD[0], RT_ELEMENTS(g_aArgDumpPD), 0, dbgcCmdDumpPageDir, "[addr|index]", "Dumps page directory entries of the default context." },
+ { "dpda", 0, 1, &g_aArgDumpPDAddr[0],RT_ELEMENTS(g_aArgDumpPDAddr), 0, dbgcCmdDumpPageDir, "[addr]", "Dumps memory at given address as a page directory." },
+ { "dpdb", 0, 1, &g_aArgDumpPD[0], RT_ELEMENTS(g_aArgDumpPD), 0, dbgcCmdDumpPageDirBoth, "[addr|index]", "Dumps page directory entries of the guest and the hypervisor. " },
+ { "dpdg", 0, 1, &g_aArgDumpPD[0], RT_ELEMENTS(g_aArgDumpPD), 0, dbgcCmdDumpPageDir, "[addr|index]", "Dumps page directory entries of the guest." },
+ { "dpdh", 0, 1, &g_aArgDumpPD[0], RT_ELEMENTS(g_aArgDumpPD), 0, dbgcCmdDumpPageDir, "[addr|index]", "Dumps page directory entries of the hypervisor. " },
+ { "dph", 0, 3, &g_aArgDumpPH[0], RT_ELEMENTS(g_aArgDumpPH), 0, dbgcCmdDumpPageHierarchy, "[addr [cr3 [mode]]", "Dumps the paging hierarchy at for specfied address range. Default context." },
+ { "dphg", 0, 3, &g_aArgDumpPH[0], RT_ELEMENTS(g_aArgDumpPH), 0, dbgcCmdDumpPageHierarchy, "[addr [cr3 [mode]]", "Dumps the paging hierarchy at for specfied address range. Guest context." },
+ { "dphh", 0, 3, &g_aArgDumpPH[0], RT_ELEMENTS(g_aArgDumpPH), 0, dbgcCmdDumpPageHierarchy, "[addr [cr3 [mode]]", "Dumps the paging hierarchy at for specfied address range. Hypervisor context." },
+ { "dp", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory in mode sized words." },
+ { "dps", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory in mode sized words with near symbols." },
+ { "dpt", 1, 1, &g_aArgDumpPT[0], RT_ELEMENTS(g_aArgDumpPT), 0, dbgcCmdDumpPageTable,"<addr>", "Dumps page table entries of the default context." },
+ { "dpta", 1, 1, &g_aArgDumpPTAddr[0],RT_ELEMENTS(g_aArgDumpPTAddr), 0, dbgcCmdDumpPageTable,"<addr>", "Dumps memory at given address as a page table." },
+ { "dptb", 1, 1, &g_aArgDumpPT[0], RT_ELEMENTS(g_aArgDumpPT), 0, dbgcCmdDumpPageTableBoth,"<addr>", "Dumps page table entries of the guest and the hypervisor." },
+ { "dptg", 1, 1, &g_aArgDumpPT[0], RT_ELEMENTS(g_aArgDumpPT), 0, dbgcCmdDumpPageTable,"<addr>", "Dumps page table entries of the guest." },
+ { "dpth", 1, 1, &g_aArgDumpPT[0], RT_ELEMENTS(g_aArgDumpPT), 0, dbgcCmdDumpPageTable,"<addr>", "Dumps page table entries of the hypervisor." },
+ { "dq", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory in quad words." },
+ { "dqs", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as quad words with near symbols." },
+ { "dt", 0, 1, &g_aArgDumpTSS[0], RT_ELEMENTS(g_aArgDumpTSS), 0, dbgcCmdDumpTSS, "[tss|tss:ign|addr]", "Dump the task state segment (TSS)." },
+ { "dt16", 0, 1, &g_aArgDumpTSS[0], RT_ELEMENTS(g_aArgDumpTSS), 0, dbgcCmdDumpTSS, "[tss|tss:ign|addr]", "Dump the 16-bit task state segment (TSS)." },
+ { "dt32", 0, 1, &g_aArgDumpTSS[0], RT_ELEMENTS(g_aArgDumpTSS), 0, dbgcCmdDumpTSS, "[tss|tss:ign|addr]", "Dump the 32-bit task state segment (TSS)." },
+ { "dt64", 0, 1, &g_aArgDumpTSS[0], RT_ELEMENTS(g_aArgDumpTSS), 0, dbgcCmdDumpTSS, "[tss|tss:ign|addr]", "Dump the 64-bit task state segment (TSS)." },
+ { "dti", 1, 2, &g_aArgDumpTypeInfo[0],RT_ELEMENTS(g_aArgDumpTypeInfo), 0, dbgcCmdDumpTypeInfo,"<type> [levels]", "Dump type information." },
+ { "dtv", 2, 3, &g_aArgDumpTypedVal[0],RT_ELEMENTS(g_aArgDumpTypedVal), 0, dbgcCmdDumpTypedVal,"<type> <addr> [levels]", "Dump a memory buffer using the information in the given type." },
+ { "du", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory as unicode string (little endian)." },
+ { "dw", 0, 1, &g_aArgDumpMem[0], RT_ELEMENTS(g_aArgDumpMem), 0, dbgcCmdDumpMem, "[addr]", "Dump memory in words." },
+ /** @todo add 'e', 'ea str', 'eza str', 'eu str' and 'ezu str'. See also
+ * dbgcCmdSearchMem and its dbgcVarsToBytes usage. */
+ { "eb", 2, 2, &g_aArgEditMem[0], RT_ELEMENTS(g_aArgEditMem), 0, dbgcCmdEditMem, "<addr> <value>", "Write a 1-byte value to memory." },
+ { "ew", 2, 2, &g_aArgEditMem[0], RT_ELEMENTS(g_aArgEditMem), 0, dbgcCmdEditMem, "<addr> <value>", "Write a 2-byte value to memory." },
+ { "ed", 2, 2, &g_aArgEditMem[0], RT_ELEMENTS(g_aArgEditMem), 0, dbgcCmdEditMem, "<addr> <value>", "Write a 4-byte value to memory." },
+ { "eq", 2, 2, &g_aArgEditMem[0], RT_ELEMENTS(g_aArgEditMem), 0, dbgcCmdEditMem, "<addr> <value>", "Write a 8-byte value to memory." },
+ { "g", 0, 1, &g_aArgGo[0], RT_ELEMENTS(g_aArgGo), 0, dbgcCmdGo, "[idCpu]", "Continue execution of all or the specified CPU. (The latter is not recommended unless you know exactly what you're doing.)" },
+ { "gu", 0, 0, NULL, 0, 0, dbgcCmdGoUp, "", "Go up - continue execution till after return." },
+ { "k", 0, 0, NULL, 0, 0, dbgcCmdStack, "", "Callstack." },
+ { "kv", 0, 0, NULL, 0, 0, dbgcCmdStack, "", "Verbose callstack." },
+ { "kg", 0, 0, NULL, 0, 0, dbgcCmdStack, "", "Callstack - guest." },
+ { "kgv", 0, 0, NULL, 0, 0, dbgcCmdStack, "", "Verbose callstack - guest." },
+ { "kh", 0, 0, NULL, 0, 0, dbgcCmdStack, "", "Callstack - hypervisor." },
+ { "lm", 0, ~0U, &g_aArgListMods[0], RT_ELEMENTS(g_aArgListMods), 0, dbgcCmdListModules, "[module [..]]", "List modules." },
+ { "lmv", 0, ~0U, &g_aArgListMods[0], RT_ELEMENTS(g_aArgListMods), 0, dbgcCmdListModules, "[module [..]]", "List modules, verbose." },
+ { "lmo", 0, ~0U, &g_aArgListMods[0], RT_ELEMENTS(g_aArgListMods), 0, dbgcCmdListModules, "[module [..]]", "List modules and their segments." },
+ { "lmov", 0, ~0U, &g_aArgListMods[0], RT_ELEMENTS(g_aArgListMods), 0, dbgcCmdListModules, "[module [..]]", "List modules and their segments, verbose." },
+ { "ln", 0, ~0U, &g_aArgListNear[0], RT_ELEMENTS(g_aArgListNear), 0, dbgcCmdListNear, "[addr/sym [..]]", "List symbols near to the address. Default address is CS:EIP." },
+ { "ls", 0, 1, &g_aArgListSource[0],RT_ELEMENTS(g_aArgListSource), 0, dbgcCmdListSource, "[addr]", "Source." },
+ { "m", 1, 1, &g_aArgMemoryInfo[0],RT_ELEMENTS(g_aArgMemoryInfo), 0, dbgcCmdMemoryInfo, "<addr>", "Display information about that piece of memory." },
+ { "p", 0, 2, &g_aArgStepTrace[0], RT_ELEMENTS(g_aArgStepTrace), 0, dbgcCmdStepTrace, "[count] [cmds]", "Step over." },
+ { "pr", 0, 0, NULL, 0, 0, dbgcCmdStepTraceToggle, "", "Toggle displaying registers for tracing & stepping (no code executed)." },
+ { "pa", 1, 1, &g_aArgStepTraceTo[0], RT_ELEMENTS(g_aArgStepTraceTo), 0, dbgcCmdStepTraceTo, "<addr> [count] [cmds]","Step to the given address." },
+ { "pc", 0, 0, &g_aArgStepTrace[0], RT_ELEMENTS(g_aArgStepTrace), 0, dbgcCmdStepTrace, "[count] [cmds]", "Step to the next call instruction." },
+ { "pt", 0, 0, &g_aArgStepTrace[0], RT_ELEMENTS(g_aArgStepTrace), 0, dbgcCmdStepTrace, "[count] [cmds]", "Step to the next return instruction." },
+ { "r", 0, 3, &g_aArgReg[0], RT_ELEMENTS(g_aArgReg), 0, dbgcCmdReg, "[reg [[=] newval]]", "Show or set register(s) - active reg set." },
+ { "rg", 0, 3, &g_aArgReg[0], RT_ELEMENTS(g_aArgReg), 0, dbgcCmdRegGuest, "[reg [[=] newval]]", "Show or set register(s) - guest reg set." },
+ { "rg32", 0, 0, NULL, 0, 0, dbgcCmdRegGuest, "", "Show 32-bit guest registers." },
+ { "rg64", 0, 0, NULL, 0, 0, dbgcCmdRegGuest, "", "Show 64-bit guest registers." },
+ { "rt", 0, 0, NULL, 0, 0, dbgcCmdRegTerse, "", "Toggles terse / verbose register info." },
+ { "s", 0, ~0U, &g_aArgSearchMem[0], RT_ELEMENTS(g_aArgSearchMem), 0, dbgcCmdSearchMem, "[options] <range> <pattern>", "Continue last search." },
+ { "sa", 2, ~0U, &g_aArgSearchMemType[0], RT_ELEMENTS(g_aArgSearchMemType),0, dbgcCmdSearchMemType, "<range> <pattern>", "Search memory for an ascii string." },
+ { "sb", 2, ~0U, &g_aArgSearchMemType[0], RT_ELEMENTS(g_aArgSearchMemType),0, dbgcCmdSearchMemType, "<range> <pattern>", "Search memory for one or more bytes." },
+ { "sd", 2, ~0U, &g_aArgSearchMemType[0], RT_ELEMENTS(g_aArgSearchMemType),0, dbgcCmdSearchMemType, "<range> <pattern>", "Search memory for one or more double words." },
+ { "sq", 2, ~0U, &g_aArgSearchMemType[0], RT_ELEMENTS(g_aArgSearchMemType),0, dbgcCmdSearchMemType, "<range> <pattern>", "Search memory for one or more quad words." },
+ { "su", 2, ~0U, &g_aArgSearchMemType[0], RT_ELEMENTS(g_aArgSearchMemType),0, dbgcCmdSearchMemType, "<range> <pattern>", "Search memory for an unicode string." },
+ { "sw", 2, ~0U, &g_aArgSearchMemType[0], RT_ELEMENTS(g_aArgSearchMemType),0, dbgcCmdSearchMemType, "<range> <pattern>", "Search memory for one or more words." },
+ { "sx", 0, ~0U, &g_aArgEventCtrlOpt[0], RT_ELEMENTS(g_aArgEventCtrlOpt), 0, dbgcCmdEventCtrlList, "[<event> [..]]", "Lists settings for exceptions, exits and other events. All if no filter is specified." },
+ { "sx-", 3, ~0U, &g_aArgEventCtrl[0], RT_ELEMENTS(g_aArgEventCtrl), 0, dbgcCmdEventCtrl, "-c <cmd> <event> [..]", "Modifies the command for one or more exceptions, exits or other event. 'all' addresses all." },
+ { "sxe", 1, ~0U, &g_aArgEventCtrl[0], RT_ELEMENTS(g_aArgEventCtrl), 0, dbgcCmdEventCtrl, "[-c <cmd>] <event> [..]", "Enable: Break into the debugger on the specified exceptions, exits and other events. 'all' addresses all." },
+ { "sxn", 1, ~0U, &g_aArgEventCtrl[0], RT_ELEMENTS(g_aArgEventCtrl), 0, dbgcCmdEventCtrl, "[-c <cmd>] <event> [..]", "Notify: Display info in the debugger and continue on the specified exceptions, exits and other events. 'all' addresses all." },
+ { "sxi", 1, ~0U, &g_aArgEventCtrl[0], RT_ELEMENTS(g_aArgEventCtrl), 0, dbgcCmdEventCtrl, "[-c <cmd>] <event> [..]", "Ignore: Ignore the specified exceptions, exits and other events ('all' = all of them). Without the -c option, the guest runs like normal." },
+ { "sxr", 0, 0, &g_aArgEventCtrlOpt[0], RT_ELEMENTS(g_aArgEventCtrlOpt), 0, dbgcCmdEventCtrlReset, "", "Reset the settings to default for exceptions, exits and other events. All if no filter is specified." },
+ { "t", 0, 2, &g_aArgStepTrace[0], RT_ELEMENTS(g_aArgStepTrace), 0, dbgcCmdStepTrace, "[count] [cmds]", "Trace ." },
+ { "tflowc", 1, ~0U, &g_aArgTraceFlowClear[0], RT_ELEMENTS(g_aArgTraceFlowClear), 0, dbgcCmdTraceFlowClear, "all | <tf#> [tf# []]", "Clears trace execution flow for the given method." },
+ { "tflowd", 0, 1, &g_aArgTraceFlowDisable[0], RT_ELEMENTS(g_aArgTraceFlowDisable), 0, dbgcCmdTraceFlowDisable, "all | <tf#> [tf# []]", "Disables trace execution flow for the given method." },
+ { "tflowe", 0, 2, &g_aArgTraceFlowEnable[0], RT_ELEMENTS(g_aArgTraceFlowEnable), 0, dbgcCmdTraceFlowEnable, "<addr> <hits>", "Enable trace execution flow of the given method." },
+ { "tflowp", 0, 1, &g_aArgTraceFlowPrintReset[0], RT_ELEMENTS(g_aArgTraceFlowPrintReset), 0, dbgcCmdTraceFlowPrint, "all | <tf#> [tf# []]", "Prints the collected trace data of the given method." },
+ { "tflowr", 0, 1, &g_aArgTraceFlowPrintReset[0], RT_ELEMENTS(g_aArgTraceFlowPrintReset), 0, dbgcCmdTraceFlowReset, "all | <tf#> [tf# []]", "Resets the collected trace data of the given trace flow module." },
+ { "tr", 0, 0, NULL, 0, 0, dbgcCmdStepTraceToggle, "", "Toggle displaying registers for tracing & stepping (no code executed)." },
+ { "ta", 1, 1, &g_aArgStepTraceTo[0], RT_ELEMENTS(g_aArgStepTraceTo), 0, dbgcCmdStepTraceTo, "<addr> [count] [cmds]","Trace to the given address." },
+ { "tc", 0, 0, &g_aArgStepTrace[0], RT_ELEMENTS(g_aArgStepTrace), 0, dbgcCmdStepTrace, "[count] [cmds]", "Trace to the next call instruction." },
+ { "tt", 0, 0, &g_aArgStepTrace[0], RT_ELEMENTS(g_aArgStepTrace), 0, dbgcCmdStepTrace, "[count] [cmds]", "Trace to the next return instruction." },
+ { "u", 0, 1, &g_aArgUnassemble[0],RT_ELEMENTS(g_aArgUnassemble), 0, dbgcCmdUnassemble, "[addr]", "Unassemble." },
+ { "u64", 0, 1, &g_aArgUnassemble[0],RT_ELEMENTS(g_aArgUnassemble), 0, dbgcCmdUnassemble, "[addr]", "Unassemble 64-bit code." },
+ { "u32", 0, 1, &g_aArgUnassemble[0],RT_ELEMENTS(g_aArgUnassemble), 0, dbgcCmdUnassemble, "[addr]", "Unassemble 32-bit code." },
+ { "u16", 0, 1, &g_aArgUnassemble[0],RT_ELEMENTS(g_aArgUnassemble), 0, dbgcCmdUnassemble, "[addr]", "Unassemble 16-bit code." },
+ { "uv86", 0, 1, &g_aArgUnassemble[0],RT_ELEMENTS(g_aArgUnassemble), 0, dbgcCmdUnassemble, "[addr]", "Unassemble 16-bit code with v8086/real mode addressing." },
+ { "ucfg", 0, 1, &g_aArgUnassembleCfg[0], RT_ELEMENTS(g_aArgUnassembleCfg), 0, dbgcCmdUnassembleCfg, "[addr]", "Unassemble creating a control flow graph." },
+ { "ucfgc", 0, 1, &g_aArgUnassembleCfg[0], RT_ELEMENTS(g_aArgUnassembleCfg), 0, dbgcCmdUnassembleCfg, "[addr]", "Unassemble creating a control flow graph with colors." },
+ { "x", 1, 1, &g_aArgListSyms[0], RT_ELEMENTS(g_aArgListSyms), 0, dbgcCmdListSymbols, "* | <Module!Symbol>", "Examine symbols." },
+};
+
+/** The number of commands in the CodeView/WinDbg emulation. */
+const uint32_t g_cCmdsCodeView = RT_ELEMENTS(g_aCmdsCodeView);
+
+
+/**
+ * Selectable debug event descriptors.
+ *
+ * @remarks Sorted by DBGCSXEVT::enmType value.
+ */
+const DBGCSXEVT g_aDbgcSxEvents[] =
+{
+ { DBGFEVENT_INTERRUPT_HARDWARE, "hwint", NULL, kDbgcSxEventKind_Interrupt, kDbgcEvtState_Disabled, 0, "Hardware interrupt" },
+ { DBGFEVENT_INTERRUPT_SOFTWARE, "swint", NULL, kDbgcSxEventKind_Interrupt, kDbgcEvtState_Disabled, 0, "Software interrupt" },
+ { DBGFEVENT_TRIPLE_FAULT, "triplefault", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Enabled, 0, "Triple fault "},
+ { DBGFEVENT_XCPT_DE, "xcpt_de", "de", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#DE (integer divide error)" },
+ { DBGFEVENT_XCPT_DB, "xcpt_db", "db", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#DB (debug)" },
+ { DBGFEVENT_XCPT_02, "xcpt_02", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_BP, "xcpt_bp", "bp", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#BP (breakpoint)" },
+ { DBGFEVENT_XCPT_OF, "xcpt_of", "of", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#OF (overflow (INTO))" },
+ { DBGFEVENT_XCPT_BR, "xcpt_br", "br", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#BR (bound range exceeded)" },
+ { DBGFEVENT_XCPT_UD, "xcpt_ud", "ud", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#UD (undefined opcode)" },
+ { DBGFEVENT_XCPT_NM, "xcpt_nm", "nm", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#NM (FPU not available)" },
+ { DBGFEVENT_XCPT_DF, "xcpt_df", "df", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#DF (double fault)" },
+ { DBGFEVENT_XCPT_09, "xcpt_09", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "Coprocessor segment overrun" },
+ { DBGFEVENT_XCPT_TS, "xcpt_ts", "ts", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, "#TS (task switch)" },
+ { DBGFEVENT_XCPT_NP, "xcpt_np", "np", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, "#NP (segment not present)" },
+ { DBGFEVENT_XCPT_SS, "xcpt_ss", "ss", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, "#SS (stack segment fault)" },
+ { DBGFEVENT_XCPT_GP, "xcpt_gp", "gp", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, "#GP (general protection fault)" },
+ { DBGFEVENT_XCPT_PF, "xcpt_pf", "pf", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, "#PF (page fault)" },
+ { DBGFEVENT_XCPT_0f, "xcpt_0f", "xcpt0f", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_MF, "xcpt_mf", "mf", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#MF (math fault)" },
+ { DBGFEVENT_XCPT_AC, "xcpt_ac", "ac", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#AC (alignment check)" },
+ { DBGFEVENT_XCPT_MC, "xcpt_mc", "mc", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#MC (machine check)" },
+ { DBGFEVENT_XCPT_XF, "xcpt_xf", "xf", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#XF (SIMD floating-point exception)" },
+ { DBGFEVENT_XCPT_VE, "xcpt_vd", "ve", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, "#VE (virtualization exception)" },
+ { DBGFEVENT_XCPT_15, "xcpt_15", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_16, "xcpt_16", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_17, "xcpt_17", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_18, "xcpt_18", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_19, "xcpt_19", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_1a, "xcpt_1a", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_1b, "xcpt_1b", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_1c, "xcpt_1c", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_1d, "xcpt_1d", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_XCPT_SX, "xcpt_sx", "sx", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, "#SX (security exception)" },
+ { DBGFEVENT_XCPT_1f, "xcpt_1f", "xcpt1f", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_HALT, "instr_halt", "hlt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_MWAIT, "instr_mwait", "mwait", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_MONITOR, "instr_monitor", "monitor", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_CPUID, "instr_cpuid", "cpuid", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_INVD, "instr_invd", "invd", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_WBINVD, "instr_wbinvd", "wbinvd", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_INVLPG, "instr_invlpg", "invlpg", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RDTSC, "instr_rdtsc", "rdtsc", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RDTSCP, "instr_rdtscp", "rdtscp", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RDPMC, "instr_rdpmc", "rdpmc", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RDMSR, "instr_rdmsr", "rdmsr", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_WRMSR, "instr_wrmsr", "wrmsr", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_CRX_READ, "instr_crx_read", "crx_read", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, NULL },
+ { DBGFEVENT_INSTR_CRX_WRITE, "instr_crx_write", "crx_write",kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, NULL },
+ { DBGFEVENT_INSTR_DRX_READ, "instr_drx_read", "drx_read", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, NULL },
+ { DBGFEVENT_INSTR_DRX_WRITE, "instr_drx_write", "drx_write",kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_TAKE_ARG, NULL },
+ { DBGFEVENT_INSTR_PAUSE, "instr_pause", "pause", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_XSETBV, "instr_xsetbv", "xsetbv", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SIDT, "instr_sidt", "sidt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_LIDT, "instr_lidt", "lidt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SGDT, "instr_sgdt", "sgdt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_LGDT, "instr_lgdt", "lgdt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SLDT, "instr_sldt", "sldt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_LLDT, "instr_lldt", "lldt", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_STR, "instr_str", "str", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_LTR, "instr_ltr", "ltr", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_GETSEC, "instr_getsec", "getsec", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RSM, "instr_rsm", "rsm", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RDRAND, "instr_rdrand", "rdrand", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_RDSEED, "instr_rdseed", "rdseed", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_XSAVES, "instr_xsaves", "xsaves", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_XRSTORS, "instr_xrstors", "xrstors", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMM_CALL, "instr_vmm_call", "vmm_call", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMCLEAR, "instr_vmx_vmclear", "vmclear", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMLAUNCH, "instr_vmx_vmlaunch", "vmlaunch", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMPTRLD, "instr_vmx_vmptrld", "vmptrld", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMPTRST, "instr_vmx_vmptrst", "vmptrst", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMREAD, "instr_vmx_vmread", "vmread", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMRESUME, "instr_vmx_vmresume", "vmresume", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMWRITE, "instr_vmx_vmwrite", "vmwrite", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMXOFF, "instr_vmx_vmxoff", "vmxoff", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMXON, "instr_vmx_vmxon", "vmxon", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_VMFUNC, "instr_vmx_vmfunc", "vmfunc", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_INVEPT, "instr_vmx_invept", "invept", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_INVVPID, "instr_vmx_invvpid", "invvpid", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_VMX_INVPCID, "instr_vmx_invpcid", "invpcid", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SVM_VMRUN, "instr_svm_vmrun", "vmrun", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SVM_VMLOAD, "instr_svm_vmload", "vmload", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SVM_VMSAVE, "instr_svm_vmsave", "vmsave", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SVM_STGI, "instr_svm_stgi", "stgi", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_INSTR_SVM_CLGI, "instr_svm_clgi", "clgi", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_TASK_SWITCH, "exit_task_switch", "task_switch", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_HALT, "exit_halt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_MWAIT, "exit_mwait", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_MONITOR, "exit_monitor", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_CPUID, "exit_cpuid", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_INVD, "exit_invd", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_WBINVD, "exit_wbinvd", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_INVLPG, "exit_invlpg", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RDTSC, "exit_rdtsc", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RDTSCP, "exit_rdtscp", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RDPMC, "exit_rdpmc", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RDMSR, "exit_rdmsr", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_WRMSR, "exit_wrmsr", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_CRX_READ, "exit_crx_read", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_CRX_WRITE, "exit_crx_write", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_DRX_READ, "exit_drx_read", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_DRX_WRITE, "exit_drx_write", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_PAUSE, "exit_pause", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_XSETBV, "exit_xsetbv", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SIDT, "exit_sidt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_LIDT, "exit_lidt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SGDT, "exit_sgdt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_LGDT, "exit_lgdt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SLDT, "exit_sldt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_LLDT, "exit_lldt", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_STR, "exit_str", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_LTR, "exit_ltr", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_GETSEC, "exit_getsec", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RSM, "exit_rsm", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RDRAND, "exit_rdrand", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_RDSEED, "exit_rdseed", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_XSAVES, "exit_xsaves", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_XRSTORS, "exit_xrstors", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMM_CALL, "exit_vmm_call", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMCLEAR, "exit_vmx_vmclear", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMLAUNCH, "exit_vmx_vmlaunch", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMPTRLD, "exit_vmx_vmptrld", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMPTRST, "exit_vmx_vmptrst", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMREAD, "exit_vmx_vmread", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMRESUME, "exit_vmx_vmresume", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMWRITE, "exit_vmx_vmwrite", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMXOFF, "exit_vmx_vmxoff", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMXON, "exit_vmx_vmxon", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VMFUNC, "exit_vmx_vmfunc", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_INVEPT, "exit_vmx_invept", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_INVVPID, "exit_vmx_invvpid", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_INVPCID, "exit_vmx_invpcid", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_EPT_VIOLATION, "exit_vmx_ept_violation", "eptvio", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_EPT_MISCONFIG, "exit_vmx_ept_misconfig", "eptmis", kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VAPIC_ACCESS, "exit_vmx_vapic_access", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_VMX_VAPIC_WRITE, "exit_vmx_vapic_write", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SVM_VMRUN, "exit_svm_vmrun", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SVM_VMLOAD, "exit_svm_vmload", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SVM_VMSAVE, "exit_svm_vmsave", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SVM_STGI, "exit_svm_stgi", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_EXIT_SVM_CLGI, "exit_svm_clgi", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_VMX_SPLIT_LOCK, "vmx_split_lock", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_IOPORT_UNASSIGNED, "pio_unassigned", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_IOPORT_UNUSED, "pio_unused", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_MEMORY_UNASSIGNED, "mmio_unassigned", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_MEMORY_ROM_WRITE, "rom_write", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, 0, NULL },
+ { DBGFEVENT_BSOD_MSR, "bsod_msr", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_BUGCHECK, NULL },
+ { DBGFEVENT_BSOD_EFI, "bsod_efi", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_BUGCHECK, NULL },
+ { DBGFEVENT_BSOD_VMMDEV, "bsod_vmmdev", NULL, kDbgcSxEventKind_Plain, kDbgcEvtState_Disabled, DBGCSXEVT_F_BUGCHECK, NULL },
+};
+/** Number of entries in g_aDbgcSxEvents. */
+const uint32_t g_cDbgcSxEvents = RT_ELEMENTS(g_aDbgcSxEvents);
+
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'g' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdGo(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Parse arguments.
+ */
+ VMCPUID idCpu = VMCPUID_ALL;
+ if (cArgs == 1)
+ {
+ VMCPUID cCpus = DBGFR3CpuGetCount(pUVM);
+ if (paArgs[0].u.u64Number >= cCpus)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "idCpu %RU64 is out of range! Highest valid ID is %u.\n",
+ paArgs[0].u.u64Number, cCpus - 1);
+ idCpu = (VMCPUID)paArgs[0].u.u64Number;
+ }
+ else
+ Assert(cArgs == 0);
+
+ /*
+ * Try resume the VM or CPU.
+ */
+ int rc = DBGFR3Resume(pUVM, idCpu);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(rc == VINF_SUCCESS || rc == VWRN_DBGF_ALREADY_RUNNING);
+ if (rc != VWRN_DBGF_ALREADY_RUNNING)
+ return VINF_SUCCESS;
+ if (idCpu == VMCPUID_ALL)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "The VM is already running");
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "CPU %u is already running", idCpu);
+ }
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3Resume");
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'gu' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdGoUp(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ RT_NOREF(pCmd, paArgs, cArgs);
+
+ /* The simple way out. */
+ PDBGFADDRESS pStackPop = NULL; /** @todo try set up some stack limitations */
+ RTGCPTR cbStackPop = 0;
+ int rc = DBGFR3StepEx(pUVM, pDbgc->idCpu, DBGF_STEP_F_OVER | DBGF_STEP_F_STOP_AFTER_RET, NULL, pStackPop, cbStackPop, _512K);
+ if (RT_SUCCESS(rc))
+ pDbgc->fReady = false;
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3StepEx(,,DBGF_STEP_F_OVER | DBGF_STEP_F_STOP_AFTER_RET,) failed");
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'ba' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkAccess(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Interpret access type.
+ */
+ if ( !strchr("xrwi", paArgs[0].u.pszString[0])
+ || paArgs[0].u.pszString[1])
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid access type '%s' for '%s'. Valid types are 'e', 'r', 'w' and 'i'",
+ paArgs[0].u.pszString, pCmd->pszCmd);
+ uint8_t fType = 0;
+ switch (paArgs[0].u.pszString[0])
+ {
+ case 'x': fType = X86_DR7_RW_EO; break;
+ case 'r': fType = X86_DR7_RW_RW; break;
+ case 'w': fType = X86_DR7_RW_WO; break;
+ case 'i': fType = X86_DR7_RW_IO; break;
+ }
+
+ /*
+ * Validate size.
+ */
+ if (fType == X86_DR7_RW_EO && paArgs[1].u.u64Number != 1)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid access size %RX64 for '%s'. 'x' access type requires size 1!",
+ paArgs[1].u.u64Number, pCmd->pszCmd);
+ switch (paArgs[1].u.u64Number)
+ {
+ case 1:
+ case 2:
+ case 4:
+ break;
+ /*case 8: - later*/
+ default:
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid access size %RX64 for '%s'. 1, 2 or 4!",
+ paArgs[1].u.u64Number, pCmd->pszCmd);
+ }
+ uint8_t cb = (uint8_t)paArgs[1].u.u64Number;
+
+ /*
+ * Convert the pointer to a DBGF address.
+ */
+ DBGFADDRESS Address;
+ int rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &paArgs[2], &Address);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr(,%DV,)", &paArgs[2]);
+
+ /*
+ * Pick out the optional arguments.
+ */
+ uint64_t iHitTrigger = 0;
+ uint64_t iHitDisable = UINT64_MAX;
+ const char *pszCmds = NULL;
+ unsigned iArg = 3;
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ iHitTrigger = paArgs[iArg].u.u64Number;
+ iArg++;
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ iHitDisable = paArgs[iArg].u.u64Number;
+ iArg++;
+ }
+ }
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_STRING)
+ {
+ pszCmds = paArgs[iArg].u.pszString;
+ iArg++;
+ }
+
+ /*
+ * Try set the breakpoint.
+ */
+ uint32_t iBp;
+ rc = DBGFR3BpSetReg(pUVM, &Address, iHitTrigger, iHitDisable, fType, cb, &iBp);
+ if (RT_SUCCESS(rc))
+ {
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = dbgcBpAdd(pDbgc, iBp, pszCmds);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Set access breakpoint %u at %RGv\n", iBp, Address.FlatPtr);
+ if (rc == VERR_DBGC_BP_EXISTS)
+ {
+ rc = dbgcBpUpdate(pDbgc, iBp, pszCmds);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Updated access breakpoint %u at %RGv\n", iBp, Address.FlatPtr);
+ }
+ int rc2 = DBGFR3BpClear(pDbgc->pUVM, iBp);
+ AssertRC(rc2);
+ }
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "Failed to set access breakpoint at %RGv", Address.FlatPtr);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'bc' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkClear(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Enumerate the arguments.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iBp = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iBp == paArgs[iArg].u.u64Number)
+ {
+ int rc2 = DBGFR3BpClear(pUVM, iBp);
+ if (RT_FAILURE(rc2))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc2, "DBGFR3BpClear(,%#x)", iBp);
+ if (RT_SUCCESS(rc2) || rc2 == VERR_DBGF_BP_NOT_FOUND)
+ dbgcBpDelete(pDbgc, iBp);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Breakpoint id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGCBP pBp = pDbgc->pFirstBp;
+ while (pBp)
+ {
+ uint32_t iBp = pBp->iBp;
+ pBp = pBp->pNext;
+
+ int rc2 = DBGFR3BpClear(pUVM, iBp);
+ if (RT_FAILURE(rc2))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc2, "DBGFR3BpClear(,%#x)", iBp);
+ if (RT_SUCCESS(rc2) || rc2 == VERR_DBGF_BP_NOT_FOUND)
+ dbgcBpDelete(pDbgc, iBp);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'bd' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkDisable(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Enumerate the arguments.
+ */
+ int rc = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iBp = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iBp == paArgs[iArg].u.u64Number)
+ {
+ rc = DBGFR3BpDisable(pUVM, iBp);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3BpDisable failed for breakpoint %#x", iBp);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Breakpoint id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ for (PDBGCBP pBp = pDbgc->pFirstBp; pBp; pBp = pBp->pNext)
+ {
+ int rc2 = DBGFR3BpDisable(pUVM, pBp->iBp);
+ if (RT_FAILURE(rc2))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc2, "DBGFR3BpDisable failed for breakpoint %#x", pBp->iBp);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'be' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkEnable(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Enumerate the arguments.
+ */
+ int rc = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iBp = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iBp == paArgs[iArg].u.u64Number)
+ {
+ rc = DBGFR3BpEnable(pUVM, iBp);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3BpEnable failed for breakpoint %#x", iBp);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Breakpoint id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ for (PDBGCBP pBp = pDbgc->pFirstBp; pBp; pBp = pBp->pNext)
+ {
+ int rc2 = DBGFR3BpEnable(pUVM, pBp->iBp);
+ if (RT_FAILURE(rc2))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc2, "DBGFR3BpEnable failed for breakpoint %#x", pBp->iBp);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+/**
+ * Breakpoint enumeration callback function.
+ *
+ * @returns VBox status code. Any failure will stop the enumeration.
+ * @param pUVM The user mode VM handle.
+ * @param pvUser The user argument.
+ * @param hBp The DBGF breakpoint handle.
+ * @param pBp Pointer to the breakpoint information. (readonly)
+ */
+static DECLCALLBACK(int) dbgcEnumBreakpointsCallback(PUVM pUVM, void *pvUser, DBGFBP hBp, PCDBGFBPPUB pBp)
+{
+ PDBGC pDbgc = (PDBGC)pvUser;
+ PDBGCBP pDbgcBp = dbgcBpGet(pDbgc, hBp);
+
+ /*
+ * BP type and size.
+ */
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%#4x %c ", hBp, DBGF_BP_PUB_IS_ENABLED(pBp) ? 'e' : 'd');
+ bool fHasAddress = false;
+ switch (DBGF_BP_PUB_GET_TYPE(pBp))
+ {
+ case DBGFBPTYPE_INT3:
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " p %RGv", pBp->u.Int3.GCPtr);
+ fHasAddress = true;
+ break;
+ case DBGFBPTYPE_REG:
+ {
+ char chType;
+ switch (pBp->u.Reg.fType)
+ {
+ case X86_DR7_RW_EO: chType = 'x'; break;
+ case X86_DR7_RW_WO: chType = 'w'; break;
+ case X86_DR7_RW_IO: chType = 'i'; break;
+ case X86_DR7_RW_RW: chType = 'r'; break;
+ default: chType = '?'; break;
+
+ }
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%d %c %RGv", pBp->u.Reg.cb, chType, pBp->u.Reg.GCPtr);
+ fHasAddress = true;
+ break;
+ }
+
+/** @todo realign the list when I/O and MMIO breakpoint command have been added and it's possible to test this code. */
+ case DBGFBPTYPE_PORT_IO:
+ case DBGFBPTYPE_MMIO:
+ {
+ uint32_t fAccess = DBGF_BP_PUB_GET_TYPE(pBp) == DBGFBPTYPE_PORT_IO ? pBp->u.PortIo.fAccess : pBp->u.Mmio.fAccess;
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, DBGF_BP_PUB_GET_TYPE(pBp) == DBGFBPTYPE_PORT_IO ? " i" : " m");
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " %c%c%c%c%c%c",
+ fAccess & DBGFBPIOACCESS_READ_MASK ? 'r' : '-',
+ fAccess & DBGFBPIOACCESS_READ_BYTE ? '1' : '-',
+ fAccess & DBGFBPIOACCESS_READ_WORD ? '2' : '-',
+ fAccess & DBGFBPIOACCESS_READ_DWORD ? '4' : '-',
+ fAccess & DBGFBPIOACCESS_READ_QWORD ? '8' : '-',
+ fAccess & DBGFBPIOACCESS_READ_OTHER ? '+' : '-');
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " %c%c%c%c%c%c",
+ fAccess & DBGFBPIOACCESS_WRITE_MASK ? 'w' : '-',
+ fAccess & DBGFBPIOACCESS_WRITE_BYTE ? '1' : '-',
+ fAccess & DBGFBPIOACCESS_WRITE_WORD ? '2' : '-',
+ fAccess & DBGFBPIOACCESS_WRITE_DWORD ? '4' : '-',
+ fAccess & DBGFBPIOACCESS_WRITE_QWORD ? '8' : '-',
+ fAccess & DBGFBPIOACCESS_WRITE_OTHER ? '+' : '-');
+ if (DBGF_BP_PUB_GET_TYPE(pBp) == DBGFBPTYPE_PORT_IO)
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " %04x-%04x",
+ pBp->u.PortIo.uPort, pBp->u.PortIo.uPort + pBp->u.PortIo.cPorts - 1);
+ else
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%RGp LB %03x", pBp->u.Mmio.PhysAddr, pBp->u.Mmio.cb);
+ break;
+ }
+
+ default:
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " unknown type %d!!", DBGF_BP_PUB_GET_TYPE(pBp));
+ AssertFailed();
+ break;
+
+ }
+ if (pBp->iHitDisable == ~(uint64_t)0)
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " %04RX64 (%04RX64 to ~0) ", pBp->cHits, pBp->iHitTrigger);
+ else
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " %04RX64 (%04RX64 to %04RX64)", pBp->cHits, pBp->iHitTrigger, pBp->iHitDisable);
+
+ /*
+ * Try resolve the address if it has one.
+ */
+ if (fHasAddress)
+ {
+ RTDBGSYMBOL Sym;
+ RTINTPTR off;
+ DBGFADDRESS Addr;
+ int rc = DBGFR3AsSymbolByAddr(pUVM, pDbgc->hDbgAs, DBGFR3AddrFromFlat(pDbgc->pUVM, &Addr, pBp->u.GCPtr),
+ RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED,
+ &off, &Sym, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!off)
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%s", Sym.szName);
+ else if (off > 0)
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%s+%RGv", Sym.szName, off);
+ else
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%s-%RGv", Sym.szName, -off);
+ }
+ }
+
+ /*
+ * The commands.
+ */
+ if (pDbgcBp)
+ {
+ if (pDbgcBp->cchCmd)
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "\n cmds: '%s'\n", pDbgcBp->szCmd);
+ else
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "\n");
+ }
+ else
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " [unknown bp]\n");
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'bl' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkList(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, -1, cArgs == 0);
+ NOREF(paArgs);
+
+ /*
+ * Enumerate the breakpoints.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc = DBGFR3BpEnum(pUVM, dbgcEnumBreakpointsCallback, pDbgc);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3BpEnum");
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'bp' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkSet(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Convert the pointer to a DBGF address.
+ */
+ DBGFADDRESS Address;
+ int rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &paArgs[0], &Address);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr(,'%DV',)", &paArgs[0]);
+
+ /*
+ * Pick out the optional arguments.
+ */
+ uint64_t iHitTrigger = 0;
+ uint64_t iHitDisable = UINT64_MAX;
+ const char *pszCmds = NULL;
+ unsigned iArg = 1;
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ iHitTrigger = paArgs[iArg].u.u64Number;
+ iArg++;
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ iHitDisable = paArgs[iArg].u.u64Number;
+ iArg++;
+ }
+ }
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_STRING)
+ {
+ pszCmds = paArgs[iArg].u.pszString;
+ iArg++;
+ }
+
+ /*
+ * Try set the breakpoint.
+ */
+ uint32_t iBp;
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = DBGFR3BpSetInt3(pUVM, pDbgc->idCpu, &Address, iHitTrigger, iHitDisable, &iBp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcBpAdd(pDbgc, iBp, pszCmds);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Set breakpoint %u at %RGv\n", iBp, Address.FlatPtr);
+ if (rc == VERR_DBGC_BP_EXISTS)
+ {
+ rc = dbgcBpUpdate(pDbgc, iBp, pszCmds);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Updated breakpoint %u at %RGv\n", iBp, Address.FlatPtr);
+ }
+ int rc2 = DBGFR3BpClear(pDbgc->pUVM, iBp);
+ AssertRC(rc2);
+ }
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "Failed to set breakpoint at %RGv", Address.FlatPtr);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'br' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdBrkREM(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Convert the pointer to a DBGF address.
+ */
+ DBGFADDRESS Address;
+ int rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &paArgs[0], &Address);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr(,'%DV',)", &paArgs[0]);
+
+ /*
+ * Pick out the optional arguments.
+ */
+ uint64_t iHitTrigger = 0;
+ uint64_t iHitDisable = UINT64_MAX;
+ const char *pszCmds = NULL;
+ unsigned iArg = 1;
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ iHitTrigger = paArgs[iArg].u.u64Number;
+ iArg++;
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ iHitDisable = paArgs[iArg].u.u64Number;
+ iArg++;
+ }
+ }
+ if (iArg < cArgs && paArgs[iArg].enmType == DBGCVAR_TYPE_STRING)
+ {
+ pszCmds = paArgs[iArg].u.pszString;
+ iArg++;
+ }
+
+ /*
+ * Try set the breakpoint.
+ */
+ uint32_t iBp;
+ rc = DBGFR3BpSetREM(pUVM, &Address, iHitTrigger, iHitDisable, &iBp);
+ if (RT_SUCCESS(rc))
+ {
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ rc = dbgcBpAdd(pDbgc, iBp, pszCmds);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Set REM breakpoint %u at %RGv\n", iBp, Address.FlatPtr);
+ if (rc == VERR_DBGC_BP_EXISTS)
+ {
+ rc = dbgcBpUpdate(pDbgc, iBp, pszCmds);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Updated REM breakpoint %u at %RGv\n", iBp, Address.FlatPtr);
+ }
+ int rc2 = DBGFR3BpClear(pDbgc->pUVM, iBp);
+ AssertRC(rc2);
+ }
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "Failed to set REM breakpoint at %RGv", Address.FlatPtr);
+}
+
+
+/**
+ * Helps the unassmble ('u') command display symbols it starts at and passes.
+ *
+ * @param pUVM The user mode VM handle.
+ * @param pCmdHlp The command helpers for printing via.
+ * @param hDbgAs The address space to look up addresses in.
+ * @param pAddress The current address.
+ * @param pcbCallAgain Where to return the distance to the next check (in
+ * instruction bytes).
+ */
+static void dbgcCmdUnassambleHelpListNear(PUVM pUVM, PDBGCCMDHLP pCmdHlp, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress,
+ PRTUINTPTR pcbCallAgain)
+{
+ RTDBGSYMBOL Symbol;
+ RTGCINTPTR offDispSym;
+ int rc = DBGFR3AsSymbolByAddr(pUVM, hDbgAs, pAddress,
+ RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED,
+ &offDispSym, &Symbol, NULL);
+ if (RT_FAILURE(rc) || offDispSym > _1G)
+ rc = DBGFR3AsSymbolByAddr(pUVM, hDbgAs, pAddress,
+ RTDBGSYMADDR_FLAGS_GREATER_OR_EQUAL | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED,
+ &offDispSym, &Symbol, NULL);
+ if (RT_SUCCESS(rc) && offDispSym < _1G)
+ {
+ if (!offDispSym)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%s:\n", Symbol.szName);
+ *pcbCallAgain = !Symbol.cb ? 64 : Symbol.cb;
+ }
+ else if (offDispSym > 0)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%s+%#llx:\n", Symbol.szName, (uint64_t)offDispSym);
+ *pcbCallAgain = !Symbol.cb ? 64 : Symbol.cb > (RTGCUINTPTR)offDispSym ? Symbol.cb - (RTGCUINTPTR)offDispSym : 1;
+ }
+ else
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%s-%#llx:\n", Symbol.szName, (uint64_t)-offDispSym);
+ *pcbCallAgain = !Symbol.cb ? 64 : (RTGCUINTPTR)-offDispSym + Symbol.cb;
+ }
+ }
+ else
+ *pcbCallAgain = UINT32_MAX;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'u' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdUnassemble(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, -1, cArgs <= 1);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 0 || DBGCVAR_ISPOINTER(paArgs[0].enmType));
+
+ if (!cArgs && !DBGCVAR_ISPOINTER(pDbgc->DisasmPos.enmType))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Don't know where to start disassembling");
+
+ /*
+ * Check the desired mode.
+ */
+ unsigned fFlags = DBGF_DISAS_FLAGS_NO_ADDRESS | DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED;
+ switch (pCmd->pszCmd[1])
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case '\0': fFlags |= DBGF_DISAS_FLAGS_DEFAULT_MODE; break;
+ case '6': fFlags |= DBGF_DISAS_FLAGS_64BIT_MODE; break;
+ case '3': fFlags |= DBGF_DISAS_FLAGS_32BIT_MODE; break;
+ case '1': fFlags |= DBGF_DISAS_FLAGS_16BIT_MODE; break;
+ case 'v': fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE; break;
+ }
+
+ /** @todo should use DBGFADDRESS for everything */
+
+ /*
+ * Find address.
+ */
+ if (!cArgs)
+ {
+ if (!DBGCVAR_ISPOINTER(pDbgc->DisasmPos.enmType))
+ {
+ /** @todo Batch query CS, RIP, CPU mode and flags. */
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, pDbgc->idCpu);
+ if (CPUMIsGuestIn64BitCode(pVCpu))
+ {
+ pDbgc->DisasmPos.enmType = DBGCVAR_TYPE_GC_FLAT;
+ pDbgc->SourcePos.u.GCFlat = CPUMGetGuestRIP(pVCpu);
+ }
+ else
+ {
+ pDbgc->DisasmPos.enmType = DBGCVAR_TYPE_GC_FAR;
+ pDbgc->SourcePos.u.GCFar.off = CPUMGetGuestEIP(pVCpu);
+ pDbgc->SourcePos.u.GCFar.sel = CPUMGetGuestCS(pVCpu);
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE
+ && (CPUMGetGuestEFlags(pVCpu) & X86_EFL_VM))
+ {
+ fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK;
+ fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE;
+ }
+ }
+
+ fFlags |= DBGF_DISAS_FLAGS_CURRENT_GUEST;
+ }
+ else if ((fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE && pDbgc->fDisasm)
+ {
+ fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK;
+ fFlags |= pDbgc->fDisasm & DBGF_DISAS_FLAGS_MODE_MASK;
+ }
+ pDbgc->DisasmPos.enmRangeType = DBGCVAR_RANGE_NONE;
+ }
+ else
+ pDbgc->DisasmPos = paArgs[0];
+ pDbgc->pLastPos = &pDbgc->DisasmPos;
+
+ /*
+ * Range.
+ */
+ switch (pDbgc->DisasmPos.enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE:
+ pDbgc->DisasmPos.enmRangeType = DBGCVAR_RANGE_ELEMENTS;
+ pDbgc->DisasmPos.u64Range = 10;
+ break;
+
+ case DBGCVAR_RANGE_ELEMENTS:
+ if (pDbgc->DisasmPos.u64Range > 2048)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Too many lines requested. Max is 2048 lines");
+ break;
+
+ case DBGCVAR_RANGE_BYTES:
+ if (pDbgc->DisasmPos.u64Range > 65536)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "The requested range is too big. Max is 64KB");
+ break;
+
+ default:
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Unknown range type %d", pDbgc->DisasmPos.enmRangeType);
+ }
+
+ /*
+ * Convert physical and host addresses to guest addresses.
+ */
+ RTDBGAS hDbgAs = pDbgc->hDbgAs;
+ int rc;
+ switch (pDbgc->DisasmPos.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ hDbgAs = DBGF_AS_PHYS;
+ RT_FALL_THRU();
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ {
+ DBGCVAR VarTmp;
+ rc = DBGCCmdHlpEval(pCmdHlp, &VarTmp, "%%(%Dv)", &pDbgc->DisasmPos);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "failed to evaluate '%%(%Dv)'", &pDbgc->DisasmPos);
+ pDbgc->DisasmPos = VarTmp;
+ break;
+ }
+ default: AssertFailed(); break;
+ }
+
+ DBGFADDRESS CurAddr;
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_16BIT_REAL_MODE
+ && pDbgc->DisasmPos.enmType == DBGCVAR_TYPE_GC_FAR)
+ DBGFR3AddrFromFlat(pUVM, &CurAddr, ((uint32_t)pDbgc->DisasmPos.u.GCFar.sel << 4) + pDbgc->DisasmPos.u.GCFar.off);
+ else
+ {
+ rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &pDbgc->DisasmPos, &CurAddr);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr failed on '%Dv'", &pDbgc->DisasmPos);
+ }
+
+ pDbgc->fDisasm = fFlags;
+
+ /*
+ * Figure out where we are and display it. Also calculate when we need to
+ * check for a new symbol if possible.
+ */
+ RTGCUINTPTR cbCheckSymbol;
+ dbgcCmdUnassambleHelpListNear(pUVM, pCmdHlp, hDbgAs, &CurAddr, &cbCheckSymbol);
+
+ /*
+ * Do the disassembling.
+ */
+ unsigned cTries = 32;
+ int iRangeLeft = (int)pDbgc->DisasmPos.u64Range;
+ if (iRangeLeft == 0) /* kludge for 'r'. */
+ iRangeLeft = -1;
+ for (;;)
+ {
+ /*
+ * Disassemble the instruction.
+ */
+ char szDis[256];
+ uint32_t cbInstr = 1;
+ if (pDbgc->DisasmPos.enmType == DBGCVAR_TYPE_GC_FLAT)
+ rc = DBGFR3DisasInstrEx(pUVM, pDbgc->idCpu, DBGF_SEL_FLAT, pDbgc->DisasmPos.u.GCFlat, fFlags,
+ &szDis[0], sizeof(szDis), &cbInstr);
+ else
+ rc = DBGFR3DisasInstrEx(pUVM, pDbgc->idCpu, pDbgc->DisasmPos.u.GCFar.sel, pDbgc->DisasmPos.u.GCFar.off, fFlags,
+ &szDis[0], sizeof(szDis), &cbInstr);
+ if (RT_SUCCESS(rc))
+ {
+ /* print it */
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%-16DV %s\n", &pDbgc->DisasmPos, &szDis[0]);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ /* bitch. */
+ int rc2 = DBGCCmdHlpPrintf(pCmdHlp, "Failed to disassemble instruction, skipping one byte.\n");
+ if (RT_FAILURE(rc2))
+ return rc2;
+ if (cTries-- > 0)
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "Too many disassembly failures. Giving up");
+ cbInstr = 1;
+ }
+
+ /* advance */
+ if (iRangeLeft < 0) /* 'r' */
+ break;
+ if (pDbgc->DisasmPos.enmRangeType == DBGCVAR_RANGE_ELEMENTS)
+ iRangeLeft--;
+ else
+ iRangeLeft -= cbInstr;
+ rc = DBGCCmdHlpEval(pCmdHlp, &pDbgc->DisasmPos, "(%Dv) + %x", &pDbgc->DisasmPos, cbInstr);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpEval(,,'(%Dv) + %x')", &pDbgc->DisasmPos, cbInstr);
+ if (iRangeLeft <= 0)
+ break;
+ fFlags &= ~DBGF_DISAS_FLAGS_CURRENT_GUEST;
+
+ /* Print next symbol? */
+ if (cbCheckSymbol <= cbInstr)
+ {
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_16BIT_REAL_MODE
+ && pDbgc->DisasmPos.enmType == DBGCVAR_TYPE_GC_FAR)
+ DBGFR3AddrFromFlat(pUVM, &CurAddr, ((uint32_t)pDbgc->DisasmPos.u.GCFar.sel << 4) + pDbgc->DisasmPos.u.GCFar.off);
+ else
+ rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &pDbgc->DisasmPos, &CurAddr);
+ if (RT_SUCCESS(rc))
+ dbgcCmdUnassambleHelpListNear(pUVM, pCmdHlp, hDbgAs, &CurAddr, &cbCheckSymbol);
+ else
+ cbCheckSymbol = UINT32_MAX;
+ }
+ else
+ cbCheckSymbol -= cbInstr;
+ }
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDGCSCREENBLIT}
+ */
+static DECLCALLBACK(int) dbgcCmdUnassembleCfgBlit(const char *psz, void *pvUser)
+{
+ PDBGCCMDHLP pCmdHlp = (PDBGCCMDHLP)pvUser;
+ return DBGCCmdHlpPrintf(pCmdHlp, "%s", psz);
+}
+
+
+/**
+ * Checks whether both addresses are equal.
+ *
+ * @returns true if both addresses point to the same location, false otherwise.
+ * @param pAddr1 First address.
+ * @param pAddr2 Second address.
+ */
+static bool dbgcCmdUnassembleCfgAddrEqual(PDBGFADDRESS pAddr1, PDBGFADDRESS pAddr2)
+{
+ return pAddr1->Sel == pAddr2->Sel
+ && pAddr1->off == pAddr2->off;
+}
+
+
+/**
+ * Checks whether the first given address is lower than the second one.
+ *
+ * @returns true if both addresses point to the same location, false otherwise.
+ * @param pAddr1 First address.
+ * @param pAddr2 Second address.
+ */
+static bool dbgcCmdUnassembleCfgAddrLower(PDBGFADDRESS pAddr1, PDBGFADDRESS pAddr2)
+{
+ return pAddr1->Sel == pAddr2->Sel
+ && pAddr1->off < pAddr2->off;
+}
+
+
+/**
+ * Calculates the size required for the given basic block including the
+ * border and spacing on the edges.
+ *
+ * @param hFlowBb The basic block handle.
+ * @param pDumpBb The dumper state to fill in for the basic block.
+ */
+static void dbgcCmdUnassembleCfgDumpCalcBbSize(DBGFFLOWBB hFlowBb, PDBGCFLOWBBDUMP pDumpBb)
+{
+ uint32_t fFlags = DBGFR3FlowBbGetFlags(hFlowBb);
+ uint32_t cInstr = DBGFR3FlowBbGetInstrCount(hFlowBb);
+
+ pDumpBb->hFlowBb = hFlowBb;
+ pDumpBb->cchHeight = cInstr + 4; /* Include spacing and border top and bottom. */
+ pDumpBb->cchWidth = 0;
+ DBGFR3FlowBbGetStartAddress(hFlowBb, &pDumpBb->AddrStart);
+
+ DBGFFLOWBBENDTYPE enmType = DBGFR3FlowBbGetType(hFlowBb);
+ if ( enmType == DBGFFLOWBBENDTYPE_COND
+ || enmType == DBGFFLOWBBENDTYPE_UNCOND_JMP
+ || enmType == DBGFFLOWBBENDTYPE_UNCOND_INDIRECT_JMP)
+ DBGFR3FlowBbGetBranchAddress(hFlowBb, &pDumpBb->AddrTarget);
+
+ if (fFlags & DBGF_FLOW_BB_F_INCOMPLETE_ERR)
+ {
+ const char *pszErr = NULL;
+ DBGFR3FlowBbQueryError(hFlowBb, &pszErr);
+ if (pszErr)
+ {
+ pDumpBb->cchHeight++;
+ pDumpBb->cchWidth = RT_MAX(pDumpBb->cchWidth, (uint32_t)strlen(pszErr));
+ }
+ }
+ for (unsigned i = 0; i < cInstr; i++)
+ {
+ const char *pszInstr = NULL;
+ int rc = DBGFR3FlowBbQueryInstr(hFlowBb, i, NULL, NULL, &pszInstr);
+ AssertRC(rc);
+ pDumpBb->cchWidth = RT_MAX(pDumpBb->cchWidth, (uint32_t)strlen(pszInstr));
+ }
+ pDumpBb->cchWidth += 4; /* Include spacing and border left and right. */
+}
+
+
+/**
+ * Dumps a top or bottom boundary line.
+ *
+ * @param hScreen The screen to draw to.
+ * @param uStartX Where to start drawing the boundary.
+ * @param uStartY Y coordinate.
+ * @param cchWidth Width of the boundary.
+ * @param enmColor The color to use for drawing.
+ */
+static void dbgcCmdUnassembleCfgDumpBbBoundary(DBGCSCREEN hScreen, uint32_t uStartX, uint32_t uStartY, uint32_t cchWidth,
+ DBGCSCREENCOLOR enmColor)
+{
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX, uStartY, '+', enmColor);
+ dbgcScreenAsciiDrawLineHorizontal(hScreen, uStartX + 1, uStartX + 1 + cchWidth - 2,
+ uStartY, '-', enmColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX + cchWidth - 1, uStartY, '+', enmColor);
+}
+
+
+/**
+ * Dumps a spacing line between the top or bottom boundary and the actual disassembly.
+ *
+ * @param hScreen The screen to draw to.
+ * @param uStartX Where to start drawing the spacing.
+ * @param uStartY Y coordinate.
+ * @param cchWidth Width of the spacing.
+ * @param enmColor The color to use for drawing.
+ */
+static void dbgcCmdUnassembleCfgDumpBbSpacing(DBGCSCREEN hScreen, uint32_t uStartX, uint32_t uStartY, uint32_t cchWidth,
+ DBGCSCREENCOLOR enmColor)
+{
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX, uStartY, '|', enmColor);
+ dbgcScreenAsciiDrawLineHorizontal(hScreen, uStartX + 1, uStartX + 1 + cchWidth - 2,
+ uStartY, ' ', enmColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX + cchWidth - 1, uStartY, '|', enmColor);
+}
+
+
+/**
+ * Writes a given text to the screen.
+ *
+ * @param hScreen The screen to draw to.
+ * @param uStartX Where to start drawing the line.
+ * @param uStartY Y coordinate.
+ * @param cchWidth Maximum width of the text.
+ * @param pszText The text to write.
+ * @param enmTextColor The color to use for drawing the text.
+ * @param enmBorderColor The color to use for drawing the border.
+ */
+static void dbgcCmdUnassembleCfgDumpBbText(DBGCSCREEN hScreen, uint32_t uStartX, uint32_t uStartY,
+ uint32_t cchWidth, const char *pszText,
+ DBGCSCREENCOLOR enmTextColor, DBGCSCREENCOLOR enmBorderColor)
+{
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX, uStartY, '|', enmBorderColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX + 1, uStartY, ' ', enmTextColor);
+ dbgcScreenAsciiDrawString(hScreen, uStartX + 2, uStartY, pszText, enmTextColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uStartX + cchWidth - 1, uStartY, '|', enmBorderColor);
+}
+
+
+/**
+ * Dumps one basic block using the dumper callback.
+ *
+ * @param pDumpBb The basic block dump state to dump.
+ * @param hScreen The screen to draw to.
+ */
+static void dbgcCmdUnassembleCfgDumpBb(PDBGCFLOWBBDUMP pDumpBb, DBGCSCREEN hScreen)
+{
+ uint32_t uStartY = pDumpBb->uStartY;
+ bool fError = RT_BOOL(DBGFR3FlowBbGetFlags(pDumpBb->hFlowBb) & DBGF_FLOW_BB_F_INCOMPLETE_ERR);
+ DBGCSCREENCOLOR enmColor = fError ? DBGCSCREENCOLOR_RED_BRIGHT : DBGCSCREENCOLOR_DEFAULT;
+
+ dbgcCmdUnassembleCfgDumpBbBoundary(hScreen, pDumpBb->uStartX, uStartY, pDumpBb->cchWidth, enmColor);
+ uStartY++;
+ dbgcCmdUnassembleCfgDumpBbSpacing(hScreen, pDumpBb->uStartX, uStartY, pDumpBb->cchWidth, enmColor);
+ uStartY++;
+
+ uint32_t cInstr = DBGFR3FlowBbGetInstrCount(pDumpBb->hFlowBb);
+ for (unsigned i = 0; i < cInstr; i++)
+ {
+ const char *pszInstr = NULL;
+ DBGFR3FlowBbQueryInstr(pDumpBb->hFlowBb, i, NULL, NULL, &pszInstr);
+ dbgcCmdUnassembleCfgDumpBbText(hScreen, pDumpBb->uStartX, uStartY + i,
+ pDumpBb->cchWidth, pszInstr, DBGCSCREENCOLOR_DEFAULT,
+ enmColor);
+ }
+ uStartY += cInstr;
+
+ if (fError)
+ {
+ const char *pszErr = NULL;
+ DBGFR3FlowBbQueryError(pDumpBb->hFlowBb, &pszErr);
+ if (pszErr)
+ dbgcCmdUnassembleCfgDumpBbText(hScreen, pDumpBb->uStartX, uStartY,
+ pDumpBb->cchWidth, pszErr, enmColor,
+ enmColor);
+ uStartY++;
+ }
+
+ dbgcCmdUnassembleCfgDumpBbSpacing(hScreen, pDumpBb->uStartX, uStartY, pDumpBb->cchWidth, enmColor);
+ uStartY++;
+ dbgcCmdUnassembleCfgDumpBbBoundary(hScreen, pDumpBb->uStartX, uStartY, pDumpBb->cchWidth, enmColor);
+ uStartY++;
+}
+
+
+/**
+ * Dumps one branch table using the dumper callback.
+ *
+ * @param pDumpBranchTbl The basic block dump state to dump.
+ * @param hScreen The screen to draw to.
+ */
+static void dbgcCmdUnassembleCfgDumpBranchTbl(PDBGCFLOWBRANCHTBLDUMP pDumpBranchTbl, DBGCSCREEN hScreen)
+{
+ uint32_t uStartY = pDumpBranchTbl->uStartY;
+ DBGCSCREENCOLOR enmColor = DBGCSCREENCOLOR_CYAN_BRIGHT;
+
+ dbgcCmdUnassembleCfgDumpBbBoundary(hScreen, pDumpBranchTbl->uStartX, uStartY, pDumpBranchTbl->cchWidth, enmColor);
+ uStartY++;
+ dbgcCmdUnassembleCfgDumpBbSpacing(hScreen, pDumpBranchTbl->uStartX, uStartY, pDumpBranchTbl->cchWidth, enmColor);
+ uStartY++;
+
+ uint32_t cSlots = DBGFR3FlowBranchTblGetSlots(pDumpBranchTbl->hFlowBranchTbl);
+ for (unsigned i = 0; i < cSlots; i++)
+ {
+ DBGFADDRESS Addr;
+ char szAddr[128];
+
+ RT_ZERO(szAddr);
+ DBGFR3FlowBranchTblGetAddrAtSlot(pDumpBranchTbl->hFlowBranchTbl, i, &Addr);
+
+ if (Addr.Sel == DBGF_SEL_FLAT)
+ RTStrPrintf(&szAddr[0], sizeof(szAddr), "%RGv", Addr.FlatPtr);
+ else
+ RTStrPrintf(&szAddr[0], sizeof(szAddr), "%04x:%RGv", Addr.Sel, Addr.off);
+
+ dbgcCmdUnassembleCfgDumpBbText(hScreen, pDumpBranchTbl->uStartX, uStartY + i,
+ pDumpBranchTbl->cchWidth, &szAddr[0], DBGCSCREENCOLOR_DEFAULT,
+ enmColor);
+ }
+ uStartY += cSlots;
+
+ dbgcCmdUnassembleCfgDumpBbSpacing(hScreen, pDumpBranchTbl->uStartX, uStartY, pDumpBranchTbl->cchWidth, enmColor);
+ uStartY++;
+ dbgcCmdUnassembleCfgDumpBbBoundary(hScreen, pDumpBranchTbl->uStartX, uStartY, pDumpBranchTbl->cchWidth, enmColor);
+ uStartY++;
+}
+
+
+/**
+ * Fills in the dump states for the basic blocks and branch tables.
+ *
+ * @returns VBox status code.
+ * @param hFlowIt The control flow graph iterator handle.
+ * @param hFlowBranchTblIt The control flow graph branch table iterator handle.
+ * @param paDumpBb The array of basic block dump states.
+ * @param paDumpBranchTbl The array of branch table dump states.
+ * @param cBbs Number of basic blocks.
+ * @param cBranchTbls Number of branch tables.
+ */
+static int dbgcCmdUnassembleCfgDumpCalcDimensions(DBGFFLOWIT hFlowIt, DBGFFLOWBRANCHTBLIT hFlowBranchTblIt,
+ PDBGCFLOWBBDUMP paDumpBb, PDBGCFLOWBRANCHTBLDUMP paDumpBranchTbl,
+ uint32_t cBbs, uint32_t cBranchTbls)
+{
+ RT_NOREF2(cBbs, cBranchTbls);
+
+ /* Calculate the sizes of each basic block first. */
+ DBGFFLOWBB hFlowBb = DBGFR3FlowItNext(hFlowIt);
+ uint32_t idx = 0;
+ while (hFlowBb)
+ {
+ dbgcCmdUnassembleCfgDumpCalcBbSize(hFlowBb, &paDumpBb[idx]);
+ idx++;
+ hFlowBb = DBGFR3FlowItNext(hFlowIt);
+ }
+
+ if (paDumpBranchTbl)
+ {
+ idx = 0;
+ DBGFFLOWBRANCHTBL hFlowBranchTbl = DBGFR3FlowBranchTblItNext(hFlowBranchTblIt);
+ while (hFlowBranchTbl)
+ {
+ paDumpBranchTbl[idx].hFlowBranchTbl = hFlowBranchTbl;
+ paDumpBranchTbl[idx].cchHeight = DBGFR3FlowBranchTblGetSlots(hFlowBranchTbl) + 4; /* Spacing and border. */
+ paDumpBranchTbl[idx].cchWidth = 25 + 4; /* Spacing and border. */
+ idx++;
+ hFlowBranchTbl = DBGFR3FlowBranchTblItNext(hFlowBranchTblIt);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Dumps the given control flow graph to the output.
+ *
+ * @returns VBox status code.
+ * @param hCfg The control flow graph handle.
+ * @param fUseColor Flag whether the output should be colorized.
+ * @param pCmdHlp The command helper callback table.
+ */
+static int dbgcCmdUnassembleCfgDump(DBGFFLOW hCfg, bool fUseColor, PDBGCCMDHLP pCmdHlp)
+{
+ int rc = VINF_SUCCESS;
+ DBGFFLOWIT hCfgIt = NULL;
+ DBGFFLOWBRANCHTBLIT hFlowBranchTblIt = NULL;
+ uint32_t cBbs = DBGFR3FlowGetBbCount(hCfg);
+ uint32_t cBranchTbls = DBGFR3FlowGetBranchTblCount(hCfg);
+ PDBGCFLOWBBDUMP paDumpBb = (PDBGCFLOWBBDUMP)RTMemTmpAllocZ(cBbs * sizeof(DBGCFLOWBBDUMP));
+ PDBGCFLOWBRANCHTBLDUMP paDumpBranchTbl = NULL;
+
+ if (cBranchTbls)
+ paDumpBranchTbl = (PDBGCFLOWBRANCHTBLDUMP)RTMemAllocZ(cBranchTbls * sizeof(DBGCFLOWBRANCHTBLDUMP));
+
+ if (RT_UNLIKELY(!paDumpBb || (!paDumpBranchTbl && cBranchTbls > 0)))
+ rc = VERR_NO_MEMORY;
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3FlowItCreate(hCfg, DBGFFLOWITORDER_BY_ADDR_LOWEST_FIRST, &hCfgIt);
+ if (RT_SUCCESS(rc) && cBranchTbls > 0)
+ rc = DBGFR3FlowBranchTblItCreate(hCfg, DBGFFLOWITORDER_BY_ADDR_LOWEST_FIRST, &hFlowBranchTblIt);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcCmdUnassembleCfgDumpCalcDimensions(hCfgIt, hFlowBranchTblIt, paDumpBb, paDumpBranchTbl,
+ cBbs, cBranchTbls);
+
+ /* Calculate the ASCII screen dimensions and create one. */
+ uint32_t cchWidth = 0;
+ uint32_t cchLeftExtra = 5;
+ uint32_t cchRightExtra = 5;
+ uint32_t cchHeight = 0;
+ for (unsigned i = 0; i < cBbs; i++)
+ {
+ PDBGCFLOWBBDUMP pDumpBb = &paDumpBb[i];
+ cchWidth = RT_MAX(cchWidth, pDumpBb->cchWidth);
+ cchHeight += pDumpBb->cchHeight;
+
+ /* Incomplete blocks don't have a successor. */
+ if (DBGFR3FlowBbGetFlags(pDumpBb->hFlowBb) & DBGF_FLOW_BB_F_INCOMPLETE_ERR)
+ continue;
+
+ switch (DBGFR3FlowBbGetType(pDumpBb->hFlowBb))
+ {
+ case DBGFFLOWBBENDTYPE_EXIT:
+ case DBGFFLOWBBENDTYPE_LAST_DISASSEMBLED:
+ break;
+ case DBGFFLOWBBENDTYPE_UNCOND_JMP:
+ if ( dbgcCmdUnassembleCfgAddrLower(&pDumpBb->AddrTarget, &pDumpBb->AddrStart)
+ || dbgcCmdUnassembleCfgAddrEqual(&pDumpBb->AddrTarget, &pDumpBb->AddrStart))
+ cchLeftExtra++;
+ else
+ cchRightExtra++;
+ break;
+ case DBGFFLOWBBENDTYPE_UNCOND:
+ cchHeight += 2; /* For the arrow down to the next basic block. */
+ break;
+ case DBGFFLOWBBENDTYPE_COND:
+ cchHeight += 2; /* For the arrow down to the next basic block. */
+ if ( dbgcCmdUnassembleCfgAddrLower(&pDumpBb->AddrTarget, &pDumpBb->AddrStart)
+ || dbgcCmdUnassembleCfgAddrEqual(&pDumpBb->AddrTarget, &pDumpBb->AddrStart))
+ cchLeftExtra++;
+ else
+ cchRightExtra++;
+ break;
+ case DBGFFLOWBBENDTYPE_UNCOND_INDIRECT_JMP:
+ default:
+ AssertFailed();
+ }
+ }
+
+ for (unsigned i = 0; i < cBranchTbls; i++)
+ {
+ PDBGCFLOWBRANCHTBLDUMP pDumpBranchTbl = &paDumpBranchTbl[i];
+ cchWidth = RT_MAX(cchWidth, pDumpBranchTbl->cchWidth);
+ cchHeight += pDumpBranchTbl->cchHeight;
+ }
+
+ cchWidth += 2;
+
+ DBGCSCREEN hScreen = NULL;
+ rc = dbgcScreenAsciiCreate(&hScreen, cchWidth + cchLeftExtra + cchRightExtra, cchHeight);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uY = 0;
+
+ /* Dump the branch tables first. */
+ for (unsigned i = 0; i < cBranchTbls; i++)
+ {
+ paDumpBranchTbl[i].uStartX = cchLeftExtra + (cchWidth - paDumpBranchTbl[i].cchWidth) / 2;
+ paDumpBranchTbl[i].uStartY = uY;
+ dbgcCmdUnassembleCfgDumpBranchTbl(&paDumpBranchTbl[i], hScreen);
+ uY += paDumpBranchTbl[i].cchHeight;
+ }
+
+ /* Dump the basic blocks and connections to the immediate successor. */
+ for (unsigned i = 0; i < cBbs; i++)
+ {
+ paDumpBb[i].uStartX = cchLeftExtra + (cchWidth - paDumpBb[i].cchWidth) / 2;
+ paDumpBb[i].uStartY = uY;
+ dbgcCmdUnassembleCfgDumpBb(&paDumpBb[i], hScreen);
+ uY += paDumpBb[i].cchHeight;
+
+ /* Incomplete blocks don't have a successor. */
+ if (DBGFR3FlowBbGetFlags(paDumpBb[i].hFlowBb) & DBGF_FLOW_BB_F_INCOMPLETE_ERR)
+ continue;
+
+ switch (DBGFR3FlowBbGetType(paDumpBb[i].hFlowBb))
+ {
+ case DBGFFLOWBBENDTYPE_EXIT:
+ case DBGFFLOWBBENDTYPE_LAST_DISASSEMBLED:
+ case DBGFFLOWBBENDTYPE_UNCOND_JMP:
+ case DBGFFLOWBBENDTYPE_UNCOND_INDIRECT_JMP:
+ break;
+ case DBGFFLOWBBENDTYPE_UNCOND:
+ /* Draw the arrow down to the next block. */
+ dbgcScreenAsciiDrawCharacter(hScreen, cchLeftExtra + cchWidth / 2, uY,
+ '|', DBGCSCREENCOLOR_BLUE_BRIGHT);
+ uY++;
+ dbgcScreenAsciiDrawCharacter(hScreen, cchLeftExtra + cchWidth / 2, uY,
+ 'V', DBGCSCREENCOLOR_BLUE_BRIGHT);
+ uY++;
+ break;
+ case DBGFFLOWBBENDTYPE_COND:
+ /* Draw the arrow down to the next block. */
+ dbgcScreenAsciiDrawCharacter(hScreen, cchLeftExtra + cchWidth / 2, uY,
+ '|', DBGCSCREENCOLOR_RED_BRIGHT);
+ uY++;
+ dbgcScreenAsciiDrawCharacter(hScreen, cchLeftExtra + cchWidth / 2, uY,
+ 'V', DBGCSCREENCOLOR_RED_BRIGHT);
+ uY++;
+ break;
+ default:
+ AssertFailed();
+ }
+ }
+
+ /* Last pass, connect all remaining branches. */
+ uint32_t uBackConns = 0;
+ uint32_t uFwdConns = 0;
+ for (unsigned i = 0; i < cBbs; i++)
+ {
+ PDBGCFLOWBBDUMP pDumpBb = &paDumpBb[i];
+ DBGFFLOWBBENDTYPE enmEndType = DBGFR3FlowBbGetType(pDumpBb->hFlowBb);
+
+ /* Incomplete blocks don't have a successor. */
+ if (DBGFR3FlowBbGetFlags(pDumpBb->hFlowBb) & DBGF_FLOW_BB_F_INCOMPLETE_ERR)
+ continue;
+
+ switch (enmEndType)
+ {
+ case DBGFFLOWBBENDTYPE_EXIT:
+ case DBGFFLOWBBENDTYPE_LAST_DISASSEMBLED:
+ case DBGFFLOWBBENDTYPE_UNCOND:
+ break;
+ case DBGFFLOWBBENDTYPE_COND:
+ case DBGFFLOWBBENDTYPE_UNCOND_JMP:
+ {
+ /* Find the target first to get the coordinates. */
+ PDBGCFLOWBBDUMP pDumpBbTgt = NULL;
+ for (unsigned idxDumpBb = 0; idxDumpBb < cBbs; idxDumpBb++)
+ {
+ pDumpBbTgt = &paDumpBb[idxDumpBb];
+ if (dbgcCmdUnassembleCfgAddrEqual(&pDumpBb->AddrTarget, &pDumpBbTgt->AddrStart))
+ break;
+ }
+
+ DBGCSCREENCOLOR enmColor = enmEndType == DBGFFLOWBBENDTYPE_UNCOND_JMP
+ ? DBGCSCREENCOLOR_YELLOW_BRIGHT
+ : DBGCSCREENCOLOR_GREEN_BRIGHT;
+
+ /*
+ * Use the right side for targets with higher addresses,
+ * left when jumping backwards.
+ */
+ if ( dbgcCmdUnassembleCfgAddrLower(&pDumpBb->AddrTarget, &pDumpBb->AddrStart)
+ || dbgcCmdUnassembleCfgAddrEqual(&pDumpBb->AddrTarget, &pDumpBb->AddrStart))
+ {
+ /* Going backwards. */
+ uint32_t uXVerLine = /*cchLeftExtra - 1 -*/ uBackConns + 1;
+ uint32_t uYHorLine = pDumpBb->uStartY + pDumpBb->cchHeight - 1 - 2;
+ uBackConns++;
+
+ /* Draw the arrow pointing to the target block. */
+ dbgcScreenAsciiDrawCharacter(hScreen, pDumpBbTgt->uStartX - 1, pDumpBbTgt->uStartY,
+ '>', enmColor);
+ /* Draw the horizontal line. */
+ dbgcScreenAsciiDrawLineHorizontal(hScreen, uXVerLine + 1, pDumpBbTgt->uStartX - 2,
+ pDumpBbTgt->uStartY, '-', enmColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uXVerLine, pDumpBbTgt->uStartY, '+',
+ enmColor);
+ /* Draw the vertical line down to the source block. */
+ dbgcScreenAsciiDrawLineVertical(hScreen, uXVerLine, pDumpBbTgt->uStartY + 1, uYHorLine - 1,
+ '|', enmColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uXVerLine, uYHorLine, '+', enmColor);
+ /* Draw the horizontal connection between the source block and vertical part. */
+ dbgcScreenAsciiDrawLineHorizontal(hScreen, uXVerLine + 1, pDumpBb->uStartX - 1,
+ uYHorLine, '-', enmColor);
+
+ }
+ else
+ {
+ /* Going forward. */
+ uint32_t uXVerLine = cchWidth + cchLeftExtra + (cchRightExtra - uFwdConns) - 1;
+ uint32_t uYHorLine = pDumpBb->uStartY + pDumpBb->cchHeight - 1 - 2;
+ uFwdConns++;
+
+ /* Draw the horizontal line. */
+ dbgcScreenAsciiDrawLineHorizontal(hScreen, pDumpBb->uStartX + pDumpBb->cchWidth,
+ uXVerLine - 1, uYHorLine, '-', enmColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uXVerLine, uYHorLine, '+', enmColor);
+ /* Draw the vertical line down to the target block. */
+ dbgcScreenAsciiDrawLineVertical(hScreen, uXVerLine, uYHorLine + 1, pDumpBbTgt->uStartY - 1,
+ '|', enmColor);
+ /* Draw the horizontal connection between the target block and vertical part. */
+ dbgcScreenAsciiDrawLineHorizontal(hScreen, pDumpBbTgt->uStartX + pDumpBbTgt->cchWidth,
+ uXVerLine, pDumpBbTgt->uStartY, '-', enmColor);
+ dbgcScreenAsciiDrawCharacter(hScreen, uXVerLine, pDumpBbTgt->uStartY, '+',
+ enmColor);
+ /* Draw the arrow pointing to the target block. */
+ dbgcScreenAsciiDrawCharacter(hScreen, pDumpBbTgt->uStartX + pDumpBbTgt->cchWidth,
+ pDumpBbTgt->uStartY, '<', enmColor);
+ }
+ break;
+ }
+ case DBGFFLOWBBENDTYPE_UNCOND_INDIRECT_JMP:
+ default:
+ AssertFailed();
+ }
+ }
+
+ rc = dbgcScreenAsciiBlit(hScreen, dbgcCmdUnassembleCfgBlit, pCmdHlp, fUseColor);
+ dbgcScreenAsciiDestroy(hScreen);
+ }
+ }
+
+ if (paDumpBb)
+ {
+ for (unsigned i = 0; i < cBbs; i++)
+ DBGFR3FlowBbRelease(paDumpBb[i].hFlowBb);
+ RTMemTmpFree(paDumpBb);
+ }
+
+ if (paDumpBranchTbl)
+ {
+ for (unsigned i = 0; i < cBranchTbls; i++)
+ DBGFR3FlowBranchTblRelease(paDumpBranchTbl[i].hFlowBranchTbl);
+ RTMemTmpFree(paDumpBranchTbl);
+ }
+
+ if (hCfgIt)
+ DBGFR3FlowItDestroy(hCfgIt);
+ if (hFlowBranchTblIt)
+ DBGFR3FlowBranchTblItDestroy(hFlowBranchTblIt);
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'ucfg' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdUnassembleCfg(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, -1, cArgs <= 1);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 0 || DBGCVAR_ISPOINTER(paArgs[0].enmType));
+
+ if (!cArgs && !DBGCVAR_ISPOINTER(pDbgc->DisasmPos.enmType))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Don't know where to start disassembling");
+
+ /*
+ * Check the desired mode.
+ */
+ unsigned fFlags = DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED;
+ bool fUseColor = false;
+ switch (pCmd->pszCmd[4])
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case '\0': fFlags |= DBGF_DISAS_FLAGS_DEFAULT_MODE; break;
+ case '6': fFlags |= DBGF_DISAS_FLAGS_64BIT_MODE; break;
+ case '3': fFlags |= DBGF_DISAS_FLAGS_32BIT_MODE; break;
+ case '1': fFlags |= DBGF_DISAS_FLAGS_16BIT_MODE; break;
+ case 'v': fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE; break;
+ case 'c': fUseColor = true; break;
+ }
+
+ /** @todo should use DBGFADDRESS for everything */
+
+ /*
+ * Find address.
+ */
+ if (!cArgs)
+ {
+ if (!DBGCVAR_ISPOINTER(pDbgc->DisasmPos.enmType))
+ {
+ /** @todo Batch query CS, RIP, CPU mode and flags. */
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, pDbgc->idCpu);
+ if (CPUMIsGuestIn64BitCode(pVCpu))
+ {
+ pDbgc->DisasmPos.enmType = DBGCVAR_TYPE_GC_FLAT;
+ pDbgc->SourcePos.u.GCFlat = CPUMGetGuestRIP(pVCpu);
+ }
+ else
+ {
+ pDbgc->DisasmPos.enmType = DBGCVAR_TYPE_GC_FAR;
+ pDbgc->SourcePos.u.GCFar.off = CPUMGetGuestEIP(pVCpu);
+ pDbgc->SourcePos.u.GCFar.sel = CPUMGetGuestCS(pVCpu);
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE
+ && (CPUMGetGuestEFlags(pVCpu) & X86_EFL_VM))
+ {
+ fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK;
+ fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE;
+ }
+ }
+
+ fFlags |= DBGF_DISAS_FLAGS_CURRENT_GUEST;
+ }
+ else if ((fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE && pDbgc->fDisasm)
+ {
+ fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK;
+ fFlags |= pDbgc->fDisasm & DBGF_DISAS_FLAGS_MODE_MASK;
+ }
+ pDbgc->DisasmPos.enmRangeType = DBGCVAR_RANGE_NONE;
+ }
+ else
+ pDbgc->DisasmPos = paArgs[0];
+ pDbgc->pLastPos = &pDbgc->DisasmPos;
+
+ /*
+ * Range.
+ */
+ switch (pDbgc->DisasmPos.enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE:
+ pDbgc->DisasmPos.enmRangeType = DBGCVAR_RANGE_ELEMENTS;
+ pDbgc->DisasmPos.u64Range = 10;
+ break;
+
+ case DBGCVAR_RANGE_ELEMENTS:
+ if (pDbgc->DisasmPos.u64Range > 2048)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Too many lines requested. Max is 2048 lines");
+ break;
+
+ case DBGCVAR_RANGE_BYTES:
+ if (pDbgc->DisasmPos.u64Range > 65536)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "The requested range is too big. Max is 64KB");
+ break;
+
+ default:
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Unknown range type %d", pDbgc->DisasmPos.enmRangeType);
+ }
+
+ /*
+ * Convert physical and host addresses to guest addresses.
+ */
+ RTDBGAS hDbgAs = pDbgc->hDbgAs;
+ int rc;
+ switch (pDbgc->DisasmPos.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ hDbgAs = DBGF_AS_PHYS;
+ RT_FALL_THRU();
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ {
+ DBGCVAR VarTmp;
+ rc = DBGCCmdHlpEval(pCmdHlp, &VarTmp, "%%(%Dv)", &pDbgc->DisasmPos);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "failed to evaluate '%%(%Dv)'", &pDbgc->DisasmPos);
+ pDbgc->DisasmPos = VarTmp;
+ break;
+ }
+ default: AssertFailed(); break;
+ }
+
+ DBGFADDRESS CurAddr;
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_16BIT_REAL_MODE
+ && pDbgc->DisasmPos.enmType == DBGCVAR_TYPE_GC_FAR)
+ DBGFR3AddrFromFlat(pUVM, &CurAddr, ((uint32_t)pDbgc->DisasmPos.u.GCFar.sel << 4) + pDbgc->DisasmPos.u.GCFar.off);
+ else
+ {
+ rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &pDbgc->DisasmPos, &CurAddr);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr failed on '%Dv'", &pDbgc->DisasmPos);
+ }
+
+ DBGFFLOW hCfg;
+ rc = DBGFR3FlowCreate(pUVM, pDbgc->idCpu, &CurAddr, 0 /*cbDisasmMax*/,
+ DBGF_FLOW_CREATE_F_TRY_RESOLVE_INDIRECT_BRANCHES, fFlags, &hCfg);
+ if (RT_SUCCESS(rc))
+ {
+ /* Dump the graph. */
+ rc = dbgcCmdUnassembleCfgDump(hCfg, fUseColor, pCmdHlp);
+ DBGFR3FlowRelease(hCfg);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowCreate failed on '%Dv'", &pDbgc->DisasmPos);
+
+ NOREF(pCmd);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'ls' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdListSource(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs <= 1);
+ if (cArgs == 1)
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ if (!pUVM && !cArgs && !DBGCVAR_ISPOINTER(pDbgc->SourcePos.enmType))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Don't know where to start listing...");
+ if (!pUVM && cArgs && DBGCVAR_ISGCPOINTER(paArgs[0].enmType))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "GC address but no VM");
+
+ /*
+ * Find address.
+ */
+ if (!cArgs)
+ {
+ if (!DBGCVAR_ISPOINTER(pDbgc->SourcePos.enmType))
+ {
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, pDbgc->idCpu);
+ pDbgc->SourcePos.enmType = DBGCVAR_TYPE_GC_FAR;
+ pDbgc->SourcePos.u.GCFar.off = CPUMGetGuestEIP(pVCpu);
+ pDbgc->SourcePos.u.GCFar.sel = CPUMGetGuestCS(pVCpu);
+ }
+ pDbgc->SourcePos.enmRangeType = DBGCVAR_RANGE_NONE;
+ }
+ else
+ pDbgc->SourcePos = paArgs[0];
+ pDbgc->pLastPos = &pDbgc->SourcePos;
+
+ /*
+ * Ensure the source address is flat GC.
+ */
+ switch (pDbgc->SourcePos.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ {
+ int rc = DBGCCmdHlpEval(pCmdHlp, &pDbgc->SourcePos, "%%(%Dv)", &pDbgc->SourcePos);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "error: Invalid address or address type. (rc=%d)\n", rc);
+ break;
+ }
+ default: AssertFailed(); break;
+ }
+
+ /*
+ * Range.
+ */
+ switch (pDbgc->SourcePos.enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE:
+ pDbgc->SourcePos.enmRangeType = DBGCVAR_RANGE_ELEMENTS;
+ pDbgc->SourcePos.u64Range = 10;
+ break;
+
+ case DBGCVAR_RANGE_ELEMENTS:
+ if (pDbgc->SourcePos.u64Range > 2048)
+ return DBGCCmdHlpPrintf(pCmdHlp, "error: Too many lines requested. Max is 2048 lines.\n");
+ break;
+
+ case DBGCVAR_RANGE_BYTES:
+ if (pDbgc->SourcePos.u64Range > 65536)
+ return DBGCCmdHlpPrintf(pCmdHlp, "error: The requested range is too big. Max is 64KB.\n");
+ break;
+
+ default:
+ return DBGCCmdHlpPrintf(pCmdHlp, "internal error: Unknown range type %d.\n", pDbgc->SourcePos.enmRangeType);
+ }
+
+ /*
+ * Do the disassembling.
+ */
+ bool fFirst = 1;
+ RTDBGLINE LinePrev = { 0, 0, 0, 0, 0, "" };
+ int iRangeLeft = (int)pDbgc->SourcePos.u64Range;
+ if (iRangeLeft == 0) /* kludge for 'r'. */
+ iRangeLeft = -1;
+ for (;;)
+ {
+ /*
+ * Get line info.
+ */
+ RTDBGLINE Line;
+ RTGCINTPTR off;
+ DBGFADDRESS SourcePosAddr;
+ int rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &pDbgc->SourcePos, &SourcePosAddr);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr(,%Dv)", &pDbgc->SourcePos);
+ rc = DBGFR3AsLineByAddr(pUVM, pDbgc->hDbgAs, &SourcePosAddr, &off, &Line, NULL);
+ if (RT_FAILURE(rc))
+ return VINF_SUCCESS;
+
+ unsigned cLines = 0;
+ if (memcmp(&Line, &LinePrev, sizeof(Line)))
+ {
+ /*
+ * Print filenamename
+ */
+ if (!fFirst && strcmp(Line.szFilename, LinePrev.szFilename))
+ fFirst = true;
+ if (fFirst)
+ {
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "[%s @ %d]\n", Line.szFilename, Line.uLineNo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Try open the file and read the line.
+ */
+ FILE *phFile = fopen(Line.szFilename, "r");
+ if (phFile)
+ {
+ /* Skip ahead to the desired line. */
+ char szLine[4096];
+ unsigned cBefore = fFirst ? RT_MIN(2, Line.uLineNo - 1) : Line.uLineNo - LinePrev.uLineNo - 1;
+ if (cBefore > 7)
+ cBefore = 0;
+ unsigned cLeft = Line.uLineNo - cBefore;
+ while (cLeft > 0)
+ {
+ szLine[0] = '\0';
+ if (!fgets(szLine, sizeof(szLine), phFile))
+ break;
+ cLeft--;
+ }
+ if (!cLeft)
+ {
+ /* print the before lines */
+ for (;;)
+ {
+ size_t cch = strlen(szLine);
+ while (cch > 0 && (szLine[cch - 1] == '\r' || szLine[cch - 1] == '\n' || RT_C_IS_SPACE(szLine[cch - 1])) )
+ szLine[--cch] = '\0';
+ if (cBefore-- <= 0)
+ break;
+
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %4d: %s\n", Line.uLineNo - cBefore - 1, szLine);
+ szLine[0] = '\0';
+ const char *pszShutUpGcc = fgets(szLine, sizeof(szLine), phFile); NOREF(pszShutUpGcc);
+ cLines++;
+ }
+ /* print the actual line */
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%08llx %4d: %s\n", Line.Address, Line.uLineNo, szLine);
+ }
+ fclose(phFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ fFirst = false;
+ }
+ else
+ return DBGCCmdHlpPrintf(pCmdHlp, "Warning: couldn't open source file '%s'\n", Line.szFilename);
+
+ LinePrev = Line;
+ }
+
+
+ /*
+ * Advance
+ */
+ if (iRangeLeft < 0) /* 'r' */
+ break;
+ if (pDbgc->SourcePos.enmRangeType == DBGCVAR_RANGE_ELEMENTS)
+ iRangeLeft -= cLines;
+ else
+ iRangeLeft -= 1;
+ rc = DBGCCmdHlpEval(pCmdHlp, &pDbgc->SourcePos, "(%Dv) + %x", &pDbgc->SourcePos, 1);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "Expression: (%Dv) + %x\n", &pDbgc->SourcePos, 1);
+ if (iRangeLeft <= 0)
+ break;
+ }
+
+ NOREF(pCmd);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'r' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdReg(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ return dbgcCmdRegGuest(pCmd, pCmdHlp, pUVM, paArgs, cArgs);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, Common worker for the dbgcCmdReg*()
+ * commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdRegCommon(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs,
+ const char *pszPrefix)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 1 || cArgs == 2 || cArgs == 3);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[0].enmType == DBGCVAR_TYPE_STRING
+ || paArgs[0].enmType == DBGCVAR_TYPE_SYMBOL);
+
+ /*
+ * Parse the register name and kind.
+ */
+ const char *pszReg = paArgs[0].u.pszString;
+ if (*pszReg == '@')
+ pszReg++;
+ VMCPUID idCpu = pDbgc->idCpu;
+ if (*pszPrefix)
+ idCpu |= DBGFREG_HYPER_VMCPUID;
+ if (*pszReg == '.')
+ {
+ pszReg++;
+ idCpu |= DBGFREG_HYPER_VMCPUID;
+ }
+ const char * const pszActualPrefix = idCpu & DBGFREG_HYPER_VMCPUID ? "." : "";
+
+ /*
+ * Query the register type & value (the setter needs the type).
+ */
+ DBGFREGVALTYPE enmType;
+ DBGFREGVAL Value;
+ int rc = DBGFR3RegNmQuery(pUVM, idCpu, pszReg, &Value, &enmType);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_DBGF_REGISTER_NOT_FOUND)
+ return DBGCCmdHlpVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "Unknown register: '%s%s'.\n",
+ pszActualPrefix, pszReg);
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3RegNmQuery failed querying '%s%s': %Rrc.\n",
+ pszActualPrefix, pszReg, rc);
+ }
+ if (cArgs == 1)
+ {
+ /*
+ * Show the register.
+ */
+ char szValue[160];
+ rc = DBGFR3RegFormatValue(szValue, sizeof(szValue), &Value, enmType, true /*fSpecial*/);
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%s%s=%s\n", pszActualPrefix, pszReg, szValue);
+ else
+ rc = DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3RegFormatValue failed: %Rrc.\n", rc);
+ }
+ else
+ {
+ DBGCVAR NewValueTmp;
+ PCDBGCVAR pNewValue;
+ if (cArgs == 3)
+ {
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 1, paArgs[1].enmType == DBGCVAR_TYPE_STRING);
+ if (strcmp(paArgs[1].u.pszString, "="))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Second argument must be '='.");
+ pNewValue = &paArgs[2];
+ }
+ else
+ {
+ /* Not possible to convince the parser to support both codeview and
+ windbg syntax and make the equal sign optional. Try help it. */
+ /** @todo make DBGCCmdHlpConvert do more with strings. */
+ rc = DBGCCmdHlpConvert(pCmdHlp, &paArgs[1], DBGCVAR_TYPE_NUMBER, true /*fConvSyms*/, &NewValueTmp);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "The last argument must be a value or valid symbol.");
+ pNewValue = &NewValueTmp;
+ }
+
+ /*
+ * Modify the register.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 1, pNewValue->enmType == DBGCVAR_TYPE_NUMBER);
+ if (enmType != DBGFREGVALTYPE_DTR)
+ {
+ enmType = DBGFREGVALTYPE_U64;
+ rc = DBGCCmdHlpVarToNumber(pCmdHlp, pNewValue, &Value.u64);
+ }
+ else
+ {
+ enmType = DBGFREGVALTYPE_DTR;
+ rc = DBGCCmdHlpVarToNumber(pCmdHlp, pNewValue, &Value.dtr.u64Base);
+ if (RT_SUCCESS(rc) && pNewValue->enmRangeType != DBGCVAR_RANGE_NONE)
+ Value.dtr.u32Limit = (uint32_t)pNewValue->u64Range;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGFR3RegNmSet(pUVM, idCpu, pszReg, &Value, enmType);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3RegNmSet failed settings '%s%s': %Rrc\n",
+ pszActualPrefix, pszReg, rc);
+ if (rc != VINF_SUCCESS)
+ DBGCCmdHlpPrintf(pCmdHlp, "%s: warning: %Rrc\n", pCmd->pszCmd, rc);
+ }
+ else
+ rc = DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3RegFormatValue failed: %Rrc.\n", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD,
+ * The 'rg'\, 'rg64' and 'rg32' commands\, worker for 'r'.}
+ */
+static DECLCALLBACK(int) dbgcCmdRegGuest(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Show all registers our selves.
+ */
+ if (cArgs == 0)
+ {
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ bool const f64BitMode = !strcmp(pCmd->pszCmd, "rg64")
+ || ( strcmp(pCmd->pszCmd, "rg32") != 0
+ && DBGFR3CpuIsIn64BitCode(pUVM, pDbgc->idCpu));
+ return DBGCCmdHlpRegPrintf(pCmdHlp, pDbgc->idCpu, f64BitMode, pDbgc->fRegTerse);
+ }
+ return dbgcCmdRegCommon(pCmd, pCmdHlp, pUVM, paArgs, cArgs, "");
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'rt' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdRegTerse(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ NOREF(pCmd); NOREF(pUVM); NOREF(paArgs); NOREF(cArgs);
+
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ pDbgc->fRegTerse = !pDbgc->fRegTerse;
+ return DBGCCmdHlpPrintf(pCmdHlp, pDbgc->fRegTerse ? "info: Terse register info.\n" : "info: Verbose register info.\n");
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'pr' and 'tr' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdStepTraceToggle(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ Assert(cArgs == 0); NOREF(pCmd); NOREF(pUVM); NOREF(paArgs); NOREF(cArgs);
+
+ /* Note! windbg accepts 'r' as a flag to 'p', 'pa', 'pc', 'pt', 't',
+ 'ta', 'tc' and 'tt'. We've simplified it. */
+ pDbgc->fStepTraceRegs = !pDbgc->fStepTraceRegs;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'p'\, 'pc'\, 'pt'\, 't'\, 'tc'\, and 'tt' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdStepTrace(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ if (cArgs != 0)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd,
+ "Sorry, but the '%s' command does not currently implement any arguments.\n", pCmd->pszCmd);
+
+ /* The 'count' has to be implemented by DBGC, whereas the
+ filtering is taken care of by DBGF. */
+
+ /*
+ * Convert the command to DBGF_STEP_F_XXX and other API input.
+ */
+ //DBGFADDRESS StackPop;
+ PDBGFADDRESS pStackPop = NULL;
+ RTGCPTR cbStackPop = 0;
+ uint32_t cMaxSteps = pCmd->pszCmd[0] == 'p' ? _512K : _64K;
+ uint32_t fFlags = pCmd->pszCmd[0] == 'p' ? DBGF_STEP_F_OVER : DBGF_STEP_F_INTO;
+ if (pCmd->pszCmd[1] == 'c')
+ fFlags |= DBGF_STEP_F_STOP_ON_CALL;
+ else if (pCmd->pszCmd[1] == 't')
+ fFlags |= DBGF_STEP_F_STOP_ON_RET;
+ else if (pCmd->pszCmd[0] != 'p')
+ cMaxSteps = 1;
+ else
+ {
+ /** @todo consider passing RSP + 1 in for 'p' and something else sensible for
+ * the 'pt' command. */
+ }
+
+ int rc = DBGFR3StepEx(pUVM, pDbgc->idCpu, fFlags, NULL, pStackPop, cbStackPop, cMaxSteps);
+ if (RT_SUCCESS(rc))
+ pDbgc->fReady = false;
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3StepEx(,,%#x,) failed", fFlags);
+
+ NOREF(pCmd); NOREF(paArgs); NOREF(cArgs);
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'pa' and 'ta' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdStepTraceTo(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ if (cArgs != 1)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd,
+ "Sorry, but the '%s' command only implements a single argument at present.\n", pCmd->pszCmd);
+ DBGFADDRESS Address;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[0], &Address);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "VarToDbgfAddr(,%Dv,)\n", &paArgs[0]);
+
+ uint32_t cMaxSteps = pCmd->pszCmd[0] == 'p' ? _512K : 1;
+ uint32_t fFlags = pCmd->pszCmd[0] == 'p' ? DBGF_STEP_F_OVER : DBGF_STEP_F_INTO;
+ rc = DBGFR3StepEx(pUVM, pDbgc->idCpu, fFlags, &Address, NULL, 0, cMaxSteps);
+ if (RT_SUCCESS(rc))
+ pDbgc->fReady = false;
+ else
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3StepEx(,,%#x,) failed", fFlags);
+ return rc;
+}
+
+
+/**
+ * Helper that tries to resolve a far address to a symbol and formats it.
+ *
+ * @returns Pointer to symbol string on success, NULL if not resolved.
+ * Free using RTStrFree.
+ * @param pCmdHlp The command helper structure.
+ * @param hAs The address space to use. NIL_RTDBGAS means no symbol resolving.
+ * @param sel The selector part of the address.
+ * @param off The offset part of the address.
+ * @param pszPrefix How to prefix the symbol string.
+ * @param pszSuffix How to suffix the symbol string.
+ */
+static char *dbgcCmdHlpFarAddrToSymbol(PDBGCCMDHLP pCmdHlp, RTDBGAS hAs, RTSEL sel, uint64_t off,
+ const char *pszPrefix, const char *pszSuffix)
+{
+ char *pszRet = NULL;
+ if (hAs != NIL_RTDBGAS)
+ {
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ DBGFADDRESS Addr;
+ int rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Addr, sel, off);
+ if (RT_SUCCESS(rc))
+ {
+ RTGCINTPTR offDispSym = 0;
+ PRTDBGSYMBOL pSymbol = DBGFR3AsSymbolByAddrA(pDbgc->pUVM, hAs, &Addr,
+ RTDBGSYMADDR_FLAGS_GREATER_OR_EQUAL
+ | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED,
+ &offDispSym, NULL);
+ if (pSymbol)
+ {
+ if (offDispSym == 0)
+ pszRet = RTStrAPrintf2("%s%s%s", pszPrefix, pSymbol->szName, pszSuffix);
+ else if (offDispSym > 0)
+ pszRet = RTStrAPrintf2("%s%s+%llx%s", pszPrefix, pSymbol->szName, (int64_t)offDispSym, pszSuffix);
+ else
+ pszRet = RTStrAPrintf2("%s%s-%llx%s", pszPrefix, pSymbol->szName, -(int64_t)offDispSym, pszSuffix);
+ RTDbgSymbolFree(pSymbol);
+ }
+ }
+ }
+ return pszRet;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'k'\, 'kg' and 'kh' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdStack(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Figure which context we're called for and start walking that stack.
+ */
+ int rc;
+ PCDBGFSTACKFRAME pFirstFrame;
+ bool const fGuest = true;
+ bool const fVerbose = pCmd->pszCmd[1] == 'v'
+ || (pCmd->pszCmd[1] != '\0' && pCmd->pszCmd[2] == 'v');
+ rc = DBGFR3StackWalkBegin(pUVM, pDbgc->idCpu, fGuest ? DBGFCODETYPE_GUEST : DBGFCODETYPE_HYPER, &pFirstFrame);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpPrintf(pCmdHlp, "Failed to begin stack walk, rc=%Rrc\n", rc);
+
+ /*
+ * Print the frames.
+ */
+ char szTmp[1024];
+ uint32_t fBitFlags = 0;
+ for (PCDBGFSTACKFRAME pFrame = pFirstFrame;
+ pFrame;
+ pFrame = DBGFR3StackWalkNext(pFrame))
+ {
+ uint32_t const fCurBitFlags = pFrame->fFlags & (DBGFSTACKFRAME_FLAGS_16BIT | DBGFSTACKFRAME_FLAGS_32BIT | DBGFSTACKFRAME_FLAGS_64BIT);
+ if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_16BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "# SS:BP Ret SS:BP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%02x %04RX16:%04RX16 %04RX16:%04RX16 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32",
+ pFrame->iFrame,
+ pFrame->AddrFrame.Sel,
+ (uint16_t)pFrame->AddrFrame.off,
+ pFrame->AddrReturnFrame.Sel,
+ (uint16_t)pFrame->AddrReturnFrame.off,
+ (uint32_t)pFrame->AddrReturnPC.Sel,
+ (uint32_t)pFrame->AddrReturnPC.off,
+ pFrame->Args.au32[0],
+ pFrame->Args.au32[1],
+ pFrame->Args.au32[2],
+ pFrame->Args.au32[3]);
+ }
+ else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "# EBP Ret EBP Ret CS:EIP Arg0 Arg1 Arg2 Arg3 CS:EIP / Symbol [line]\n");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%02x %08RX32 %08RX32 %04RX32:%08RX32 %08RX32 %08RX32 %08RX32 %08RX32",
+ pFrame->iFrame,
+ (uint32_t)pFrame->AddrFrame.off,
+ (uint32_t)pFrame->AddrReturnFrame.off,
+ (uint32_t)pFrame->AddrReturnPC.Sel,
+ (uint32_t)pFrame->AddrReturnPC.off,
+ pFrame->Args.au32[0],
+ pFrame->Args.au32[1],
+ pFrame->Args.au32[2],
+ pFrame->Args.au32[3]);
+ }
+ else if (fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT)
+ {
+ if (fCurBitFlags != fBitFlags)
+ pCmdHlp->pfnPrintf(pCmdHlp, NULL, "# RBP Ret SS:RBP Ret RIP CS:RIP / Symbol [line]\n");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%02x %016RX64 %04RX16:%016RX64 %016RX64",
+ pFrame->iFrame,
+ (uint64_t)pFrame->AddrFrame.off,
+ pFrame->AddrReturnFrame.Sel,
+ (uint64_t)pFrame->AddrReturnFrame.off,
+ (uint64_t)pFrame->AddrReturnPC.off);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ if (!pFrame->pSymPC)
+ rc = pCmdHlp->pfnPrintf(pCmdHlp, NULL,
+ fCurBitFlags & DBGFSTACKFRAME_FLAGS_64BIT
+ ? " %RTsel:%016RGv"
+ : fCurBitFlags & DBGFSTACKFRAME_FLAGS_32BIT
+ ? " %RTsel:%08RGv"
+ : " %RTsel:%04RGv"
+ , pFrame->AddrPC.Sel, pFrame->AddrPC.off);
+ else
+ {
+ RTGCINTPTR offDisp = pFrame->AddrPC.FlatPtr - pFrame->pSymPC->Value; /** @todo this isn't 100% correct for segmented stuff. */
+ if (offDisp > 0)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %s+%llx", pFrame->pSymPC->szName, (int64_t)offDisp);
+ else if (offDisp < 0)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %s-%llx", pFrame->pSymPC->szName, -(int64_t)offDisp);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %s", pFrame->pSymPC->szName);
+ }
+ if (RT_SUCCESS(rc) && pFrame->pLinePC)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " [%s @ 0i%d]", pFrame->pLinePC->szFilename, pFrame->pLinePC->uLineNo);
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "\n");
+
+ if (fVerbose && RT_SUCCESS(rc))
+ {
+ /*
+ * Display verbose frame info.
+ */
+ const char *pszRetType = "invalid";
+ switch (pFrame->enmReturnType)
+ {
+ case RTDBGRETURNTYPE_NEAR16: pszRetType = "retn/16"; break;
+ case RTDBGRETURNTYPE_NEAR32: pszRetType = "retn/32"; break;
+ case RTDBGRETURNTYPE_NEAR64: pszRetType = "retn/64"; break;
+ case RTDBGRETURNTYPE_FAR16: pszRetType = "retf/16"; break;
+ case RTDBGRETURNTYPE_FAR32: pszRetType = "retf/32"; break;
+ case RTDBGRETURNTYPE_FAR64: pszRetType = "retf/64"; break;
+ case RTDBGRETURNTYPE_IRET16: pszRetType = "iret-16"; break;
+ case RTDBGRETURNTYPE_IRET32: pszRetType = "iret/32s"; break;
+ case RTDBGRETURNTYPE_IRET32_PRIV: pszRetType = "iret/32p"; break;
+ case RTDBGRETURNTYPE_IRET32_V86: pszRetType = "iret/v86"; break;
+ case RTDBGRETURNTYPE_IRET64: pszRetType = "iret/64"; break;
+
+ case RTDBGRETURNTYPE_END:
+ case RTDBGRETURNTYPE_INVALID:
+ case RTDBGRETURNTYPE_32BIT_HACK:
+ break;
+ }
+ size_t cchLine = DBGCCmdHlpPrintfLen(pCmdHlp, " %s", pszRetType);
+ if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_USED_UNWIND_INFO)
+ cchLine += DBGCCmdHlpPrintfLen(pCmdHlp, " used-unwind-info");
+ if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_USED_ODD_EVEN)
+ cchLine += DBGCCmdHlpPrintfLen(pCmdHlp, " used-odd-even");
+ if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_REAL_V86)
+ cchLine += DBGCCmdHlpPrintfLen(pCmdHlp, " real-v86");
+ if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_MAX_DEPTH)
+ cchLine += DBGCCmdHlpPrintfLen(pCmdHlp, " max-depth");
+ if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_TRAP_FRAME)
+ cchLine += DBGCCmdHlpPrintfLen(pCmdHlp, " trap-frame");
+
+ if (pFrame->cSureRegs > 0)
+ {
+ cchLine = 1024; /* force new line */
+ for (uint32_t i = 0; i < pFrame->cSureRegs; i++)
+ {
+ if (cchLine > 80)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "\n ");
+ cchLine = 2;
+ }
+
+ szTmp[0] = '\0';
+ DBGFR3RegFormatValue(szTmp, sizeof(szTmp), &pFrame->paSureRegs[i].Value,
+ pFrame->paSureRegs[i].enmType, false);
+ const char *pszName = pFrame->paSureRegs[i].enmReg != DBGFREG_END
+ ? DBGFR3RegCpuName(pUVM, pFrame->paSureRegs[i].enmReg, pFrame->paSureRegs[i].enmType)
+ : pFrame->paSureRegs[i].pszName;
+ cchLine += DBGCCmdHlpPrintfLen(pCmdHlp, " %s=%s", pszName, szTmp);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ fBitFlags = fCurBitFlags;
+ }
+
+ DBGFR3StackWalkEnd(pFirstFrame);
+
+ NOREF(paArgs); NOREF(cArgs);
+ return rc;
+}
+
+
+/**
+ * Worker function that displays one descriptor entry (GDT, LDT, IDT).
+ *
+ * @returns pfnPrintf status code.
+ * @param pCmdHlp The DBGC command helpers.
+ * @param pDesc The descriptor to display.
+ * @param iEntry The descriptor entry number.
+ * @param fHyper Whether the selector belongs to the hypervisor or not.
+ * @param hAs Address space to use when resolving symbols.
+ * @param pfDblEntry Where to indicate whether the entry is two entries wide.
+ * Optional.
+ */
+static int dbgcCmdDumpDTWorker64(PDBGCCMDHLP pCmdHlp, PCX86DESC64 pDesc, unsigned iEntry, bool fHyper, RTDBGAS hAs,
+ bool *pfDblEntry)
+{
+ /* GUEST64 */
+ int rc;
+
+ const char *pszHyper = fHyper ? " HYPER" : "";
+ const char *pszPresent = pDesc->Gen.u1Present ? "P " : "NP";
+ if (pDesc->Gen.u1DescType)
+ {
+ static const char * const s_apszTypes[] =
+ {
+ "DataRO", /* 0 Read-Only */
+ "DataRO", /* 1 Read-Only - Accessed */
+ "DataRW", /* 2 Read/Write */
+ "DataRW", /* 3 Read/Write - Accessed */
+ "DownRO", /* 4 Expand-down, Read-Only */
+ "DownRO", /* 5 Expand-down, Read-Only - Accessed */
+ "DownRW", /* 6 Expand-down, Read/Write */
+ "DownRW", /* 7 Expand-down, Read/Write - Accessed */
+ "CodeEO", /* 8 Execute-Only */
+ "CodeEO", /* 9 Execute-Only - Accessed */
+ "CodeER", /* A Execute/Readable */
+ "CodeER", /* B Execute/Readable - Accessed */
+ "ConfE0", /* C Conforming, Execute-Only */
+ "ConfE0", /* D Conforming, Execute-Only - Accessed */
+ "ConfER", /* E Conforming, Execute/Readable */
+ "ConfER" /* F Conforming, Execute/Readable - Accessed */
+ };
+ const char *pszAccessed = pDesc->Gen.u4Type & RT_BIT(0) ? "A " : "NA";
+ const char *pszGranularity = pDesc->Gen.u1Granularity ? "G" : " ";
+ const char *pszBig = pDesc->Gen.u1DefBig ? "BIG" : " ";
+ uint32_t u32Base = X86DESC_BASE(pDesc);
+ uint32_t cbLimit = X86DESC_LIMIT_G(pDesc);
+
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Bas=%08x Lim=%08x DPL=%d %s %s %s %s AVL=%d L=%d%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], u32Base, cbLimit,
+ pDesc->Gen.u2Dpl, pszPresent, pszAccessed, pszGranularity, pszBig,
+ pDesc->Gen.u1Available, pDesc->Gen.u1Long, pszHyper);
+ }
+ else
+ {
+ static const char * const s_apszTypes[] =
+ {
+ "Ill-0 ", /* 0 0000 Reserved (Illegal) */
+ "Ill-1 ", /* 1 0001 Available 16-bit TSS */
+ "LDT ", /* 2 0010 LDT */
+ "Ill-3 ", /* 3 0011 Busy 16-bit TSS */
+ "Ill-4 ", /* 4 0100 16-bit Call Gate */
+ "Ill-5 ", /* 5 0101 Task Gate */
+ "Ill-6 ", /* 6 0110 16-bit Interrupt Gate */
+ "Ill-7 ", /* 7 0111 16-bit Trap Gate */
+ "Ill-8 ", /* 8 1000 Reserved (Illegal) */
+ "Tss64A", /* 9 1001 Available 32-bit TSS */
+ "Ill-A ", /* A 1010 Reserved (Illegal) */
+ "Tss64B", /* B 1011 Busy 32-bit TSS */
+ "Call64", /* C 1100 32-bit Call Gate */
+ "Ill-D ", /* D 1101 Reserved (Illegal) */
+ "Int64 ", /* E 1110 32-bit Interrupt Gate */
+ "Trap64" /* F 1111 32-bit Trap Gate */
+ };
+ switch (pDesc->Gen.u4Type)
+ {
+ /* raw */
+ case X86_SEL_TYPE_SYS_UNDEFINED:
+ case X86_SEL_TYPE_SYS_UNDEFINED2:
+ case X86_SEL_TYPE_SYS_UNDEFINED4:
+ case X86_SEL_TYPE_SYS_UNDEFINED3:
+ case X86_SEL_TYPE_SYS_286_TSS_AVAIL:
+ case X86_SEL_TYPE_SYS_286_TSS_BUSY:
+ case X86_SEL_TYPE_SYS_286_CALL_GATE:
+ case X86_SEL_TYPE_SYS_286_INT_GATE:
+ case X86_SEL_TYPE_SYS_286_TRAP_GATE:
+ case X86_SEL_TYPE_SYS_TASK_GATE:
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s %.8Rhxs DPL=%d %s%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], pDesc,
+ pDesc->Gen.u2Dpl, pszPresent, pszHyper);
+ break;
+
+ case X86_SEL_TYPE_SYS_386_TSS_AVAIL:
+ case X86_SEL_TYPE_SYS_386_TSS_BUSY:
+ case X86_SEL_TYPE_SYS_LDT:
+ {
+ const char *pszBusy = pDesc->Gen.u4Type & RT_BIT(1) ? "B " : "NB";
+ const char *pszBig = pDesc->Gen.u1DefBig ? "BIG" : " ";
+ const char *pszLong = pDesc->Gen.u1Long ? "LONG" : " ";
+
+ uint64_t u64Base = X86DESC64_BASE(pDesc);
+ uint32_t cbLimit = X86DESC_LIMIT_G(pDesc);
+
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Bas=%016RX64 Lim=%08x DPL=%d %s %s %s %sAVL=%d R=%d%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], u64Base, cbLimit,
+ pDesc->Gen.u2Dpl, pszPresent, pszBusy, pszLong, pszBig,
+ pDesc->Gen.u1Available, pDesc->Gen.u1Long | (pDesc->Gen.u1DefBig << 1),
+ pszHyper);
+ if (pfDblEntry)
+ *pfDblEntry = true;
+ break;
+ }
+
+ case X86_SEL_TYPE_SYS_386_CALL_GATE:
+ {
+ unsigned cParams = pDesc->au8[4] & 0x1f;
+ const char *pszCountOf = pDesc->Gen.u4Type & RT_BIT(3) ? "DC" : "WC";
+ RTSEL sel = pDesc->au16[1];
+ uint64_t off = pDesc->au16[0]
+ | ((uint64_t)pDesc->au16[3] << 16)
+ | ((uint64_t)pDesc->Gen.u32BaseHigh3 << 32);
+ char *pszSymbol = dbgcCmdHlpFarAddrToSymbol(pCmdHlp, hAs, sel, off, " (", ")");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Sel:Off=%04x:%016RX64 DPL=%d %s %s=%d%s%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], sel, off,
+ pDesc->Gen.u2Dpl, pszPresent, pszCountOf, cParams, pszHyper, pszSymbol ? pszSymbol : "");
+ RTStrFree(pszSymbol);
+ if (pfDblEntry)
+ *pfDblEntry = true;
+ break;
+ }
+
+ case X86_SEL_TYPE_SYS_386_INT_GATE:
+ case X86_SEL_TYPE_SYS_386_TRAP_GATE:
+ {
+ RTSEL sel = pDesc->Gate.u16Sel;
+ uint64_t off = pDesc->Gate.u16OffsetLow
+ | ((uint64_t)pDesc->Gate.u16OffsetHigh << 16)
+ | ((uint64_t)pDesc->Gate.u32OffsetTop << 32);
+ char *pszSymbol = dbgcCmdHlpFarAddrToSymbol(pCmdHlp, hAs, sel, off, " (", ")");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Sel:Off=%04x:%016RX64 DPL=%u %s IST=%u%s%s\n",
+ iEntry, s_apszTypes[pDesc->Gate.u4Type], sel, off,
+ pDesc->Gate.u2Dpl, pszPresent, pDesc->Gate.u3IST, pszHyper, pszSymbol ? pszSymbol : "");
+ RTStrFree(pszSymbol);
+ if (pfDblEntry)
+ *pfDblEntry = true;
+ break;
+ }
+
+ /* impossible, just it's necessary to keep gcc happy. */
+ default:
+ return VINF_SUCCESS;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker function that displays one descriptor entry (GDT, LDT, IDT).
+ *
+ * @returns pfnPrintf status code.
+ * @param pCmdHlp The DBGC command helpers.
+ * @param pDesc The descriptor to display.
+ * @param iEntry The descriptor entry number.
+ * @param fHyper Whether the selector belongs to the hypervisor or not.
+ * @param hAs Address space to use when resolving symbols.
+ */
+static int dbgcCmdDumpDTWorker32(PDBGCCMDHLP pCmdHlp, PCX86DESC pDesc, unsigned iEntry, bool fHyper, RTDBGAS hAs)
+{
+ int rc;
+
+ const char *pszHyper = fHyper ? " HYPER" : "";
+ const char *pszPresent = pDesc->Gen.u1Present ? "P " : "NP";
+ if (pDesc->Gen.u1DescType)
+ {
+ static const char * const s_apszTypes[] =
+ {
+ "DataRO", /* 0 Read-Only */
+ "DataRO", /* 1 Read-Only - Accessed */
+ "DataRW", /* 2 Read/Write */
+ "DataRW", /* 3 Read/Write - Accessed */
+ "DownRO", /* 4 Expand-down, Read-Only */
+ "DownRO", /* 5 Expand-down, Read-Only - Accessed */
+ "DownRW", /* 6 Expand-down, Read/Write */
+ "DownRW", /* 7 Expand-down, Read/Write - Accessed */
+ "CodeEO", /* 8 Execute-Only */
+ "CodeEO", /* 9 Execute-Only - Accessed */
+ "CodeER", /* A Execute/Readable */
+ "CodeER", /* B Execute/Readable - Accessed */
+ "ConfE0", /* C Conforming, Execute-Only */
+ "ConfE0", /* D Conforming, Execute-Only - Accessed */
+ "ConfER", /* E Conforming, Execute/Readable */
+ "ConfER" /* F Conforming, Execute/Readable - Accessed */
+ };
+ const char *pszAccessed = pDesc->Gen.u4Type & RT_BIT(0) ? "A " : "NA";
+ const char *pszGranularity = pDesc->Gen.u1Granularity ? "G" : " ";
+ const char *pszBig = pDesc->Gen.u1DefBig ? "BIG" : " ";
+ uint32_t u32Base = pDesc->Gen.u16BaseLow
+ | ((uint32_t)pDesc->Gen.u8BaseHigh1 << 16)
+ | ((uint32_t)pDesc->Gen.u8BaseHigh2 << 24);
+ uint32_t cbLimit = pDesc->Gen.u16LimitLow | (pDesc->Gen.u4LimitHigh << 16);
+ if (pDesc->Gen.u1Granularity)
+ cbLimit <<= PAGE_SHIFT;
+
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Bas=%08x Lim=%08x DPL=%d %s %s %s %s AVL=%d L=%d%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], u32Base, cbLimit,
+ pDesc->Gen.u2Dpl, pszPresent, pszAccessed, pszGranularity, pszBig,
+ pDesc->Gen.u1Available, pDesc->Gen.u1Long, pszHyper);
+ }
+ else
+ {
+ static const char * const s_apszTypes[] =
+ {
+ "Ill-0 ", /* 0 0000 Reserved (Illegal) */
+ "Tss16A", /* 1 0001 Available 16-bit TSS */
+ "LDT ", /* 2 0010 LDT */
+ "Tss16B", /* 3 0011 Busy 16-bit TSS */
+ "Call16", /* 4 0100 16-bit Call Gate */
+ "TaskG ", /* 5 0101 Task Gate */
+ "Int16 ", /* 6 0110 16-bit Interrupt Gate */
+ "Trap16", /* 7 0111 16-bit Trap Gate */
+ "Ill-8 ", /* 8 1000 Reserved (Illegal) */
+ "Tss32A", /* 9 1001 Available 32-bit TSS */
+ "Ill-A ", /* A 1010 Reserved (Illegal) */
+ "Tss32B", /* B 1011 Busy 32-bit TSS */
+ "Call32", /* C 1100 32-bit Call Gate */
+ "Ill-D ", /* D 1101 Reserved (Illegal) */
+ "Int32 ", /* E 1110 32-bit Interrupt Gate */
+ "Trap32" /* F 1111 32-bit Trap Gate */
+ };
+ switch (pDesc->Gen.u4Type)
+ {
+ /* raw */
+ case X86_SEL_TYPE_SYS_UNDEFINED:
+ case X86_SEL_TYPE_SYS_UNDEFINED2:
+ case X86_SEL_TYPE_SYS_UNDEFINED4:
+ case X86_SEL_TYPE_SYS_UNDEFINED3:
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s %.8Rhxs DPL=%d %s%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], pDesc,
+ pDesc->Gen.u2Dpl, pszPresent, pszHyper);
+ break;
+
+ case X86_SEL_TYPE_SYS_286_TSS_AVAIL:
+ case X86_SEL_TYPE_SYS_386_TSS_AVAIL:
+ case X86_SEL_TYPE_SYS_286_TSS_BUSY:
+ case X86_SEL_TYPE_SYS_386_TSS_BUSY:
+ case X86_SEL_TYPE_SYS_LDT:
+ {
+ const char *pszGranularity = pDesc->Gen.u1Granularity ? "G" : " ";
+ const char *pszBusy = pDesc->Gen.u4Type & RT_BIT(1) ? "B " : "NB";
+ const char *pszBig = pDesc->Gen.u1DefBig ? "BIG" : " ";
+ uint32_t u32Base = pDesc->Gen.u16BaseLow
+ | ((uint32_t)pDesc->Gen.u8BaseHigh1 << 16)
+ | ((uint32_t)pDesc->Gen.u8BaseHigh2 << 24);
+ uint32_t cbLimit = pDesc->Gen.u16LimitLow | (pDesc->Gen.u4LimitHigh << 16);
+ if (pDesc->Gen.u1Granularity)
+ cbLimit <<= PAGE_SHIFT;
+
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Bas=%08x Lim=%08x DPL=%d %s %s %s %s AVL=%d R=%d%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], u32Base, cbLimit,
+ pDesc->Gen.u2Dpl, pszPresent, pszBusy, pszGranularity, pszBig,
+ pDesc->Gen.u1Available, pDesc->Gen.u1Long | (pDesc->Gen.u1DefBig << 1),
+ pszHyper);
+ break;
+ }
+
+ case X86_SEL_TYPE_SYS_TASK_GATE:
+ {
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s TSS=%04x DPL=%d %s%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], pDesc->au16[1],
+ pDesc->Gen.u2Dpl, pszPresent, pszHyper);
+ break;
+ }
+
+ case X86_SEL_TYPE_SYS_286_CALL_GATE:
+ case X86_SEL_TYPE_SYS_386_CALL_GATE:
+ {
+ unsigned cParams = pDesc->au8[4] & 0x1f;
+ const char *pszCountOf = pDesc->Gen.u4Type & RT_BIT(3) ? "DC" : "WC";
+ RTSEL sel = pDesc->au16[1];
+ uint32_t off = pDesc->au16[0] | ((uint32_t)pDesc->au16[3] << 16);
+ char *pszSymbol = dbgcCmdHlpFarAddrToSymbol(pCmdHlp, hAs, sel, off, " (", ")");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Sel:Off=%04x:%08x DPL=%d %s %s=%d%s%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], sel, off,
+ pDesc->Gen.u2Dpl, pszPresent, pszCountOf, cParams, pszHyper, pszSymbol ? pszSymbol : "");
+ RTStrFree(pszSymbol);
+ break;
+ }
+
+ case X86_SEL_TYPE_SYS_286_INT_GATE:
+ case X86_SEL_TYPE_SYS_386_INT_GATE:
+ case X86_SEL_TYPE_SYS_286_TRAP_GATE:
+ case X86_SEL_TYPE_SYS_386_TRAP_GATE:
+ {
+ RTSEL sel = pDesc->au16[1];
+ uint32_t off = pDesc->au16[0] | ((uint32_t)pDesc->au16[3] << 16);
+ char *pszSymbol = dbgcCmdHlpFarAddrToSymbol(pCmdHlp, hAs, sel, off, " (", ")");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %s Sel:Off=%04x:%08x DPL=%d %s%s%s\n",
+ iEntry, s_apszTypes[pDesc->Gen.u4Type], sel, off,
+ pDesc->Gen.u2Dpl, pszPresent, pszHyper, pszSymbol ? pszSymbol : "");
+ RTStrFree(pszSymbol);
+ break;
+ }
+
+ /* impossible, just it's necessary to keep gcc happy. */
+ default:
+ return VINF_SUCCESS;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dg'\, 'dga'\, 'dl' and 'dla' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpDT(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Get the CPU mode, check which command variation this is
+ * and fix a default parameter if needed.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, pDbgc->idCpu);
+ CPUMMODE enmMode = CPUMGetGuestMode(pVCpu);
+ bool fGdt = pCmd->pszCmd[1] == 'g';
+ bool fAll = pCmd->pszCmd[2] == 'a';
+ RTSEL SelTable = fGdt ? 0 : X86_SEL_LDT;
+
+ DBGCVAR Var;
+ if (!cArgs)
+ {
+ cArgs = 1;
+ paArgs = &Var;
+ Var.enmType = DBGCVAR_TYPE_NUMBER;
+ Var.u.u64Number = fGdt ? 0 : 4;
+ Var.enmRangeType = DBGCVAR_RANGE_ELEMENTS;
+ Var.u64Range = 1024;
+ }
+
+ /*
+ * Process the arguments.
+ */
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ /*
+ * Retrieve the selector value from the argument.
+ * The parser may confuse pointers and numbers if more than one
+ * argument is given, that that into account.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, i, paArgs[i].enmType == DBGCVAR_TYPE_NUMBER || DBGCVAR_ISPOINTER(paArgs[i].enmType));
+ uint64_t u64;
+ unsigned cSels = 1;
+ switch (paArgs[i].enmType)
+ {
+ case DBGCVAR_TYPE_NUMBER:
+ u64 = paArgs[i].u.u64Number;
+ if (paArgs[i].enmRangeType != DBGCVAR_RANGE_NONE)
+ cSels = RT_MIN(paArgs[i].u64Range, 1024);
+ break;
+ case DBGCVAR_TYPE_GC_FAR: u64 = paArgs[i].u.GCFar.sel; break;
+ case DBGCVAR_TYPE_GC_FLAT: u64 = paArgs[i].u.GCFlat; break;
+ case DBGCVAR_TYPE_GC_PHYS: u64 = paArgs[i].u.GCPhys; break;
+ case DBGCVAR_TYPE_HC_FLAT: u64 = (uintptr_t)paArgs[i].u.pvHCFlat; break;
+ case DBGCVAR_TYPE_HC_PHYS: u64 = paArgs[i].u.HCPhys; break;
+ default: u64 = _64K; break;
+ }
+ if (u64 < _64K)
+ {
+ unsigned Sel = (RTSEL)u64;
+
+ /*
+ * Dump the specified range.
+ */
+ bool fSingle = cSels == 1;
+ while ( cSels-- > 0
+ && Sel < _64K)
+ {
+ DBGFSELINFO SelInfo;
+ int rc = DBGFR3SelQueryInfo(pUVM, pDbgc->idCpu, Sel | SelTable, DBGFSELQI_FLAGS_DT_GUEST, &SelInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (SelInfo.fFlags & DBGFSELINFO_FLAGS_REAL_MODE)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x RealM Bas=%04x Lim=%04x\n",
+ Sel, (unsigned)SelInfo.GCPtrBase, (unsigned)SelInfo.cbLimit);
+ else if ( fAll
+ || fSingle
+ || SelInfo.u.Raw.Gen.u1Present)
+ {
+ if (enmMode == CPUMMODE_PROTECTED)
+ rc = dbgcCmdDumpDTWorker32(pCmdHlp, &SelInfo.u.Raw, Sel,
+ !!(SelInfo.fFlags & DBGFSELINFO_FLAGS_HYPER), DBGF_AS_GLOBAL);
+ else
+ {
+ bool fDblSkip = false;
+ rc = dbgcCmdDumpDTWorker64(pCmdHlp, &SelInfo.u.Raw64, Sel,
+ !!(SelInfo.fFlags & DBGFSELINFO_FLAGS_HYPER), DBGF_AS_GLOBAL, &fDblSkip);
+ if (fDblSkip)
+ Sel += 4;
+ }
+ }
+ }
+ else
+ {
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %Rrc\n", Sel, rc);
+ if (!fAll)
+ return rc;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* next */
+ Sel += 8;
+ }
+ }
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "error: %llx is out of bounds\n", u64);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'di' and 'dia' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpIDT(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Establish some stuff like the current IDTR and CPU mode,
+ * and fix a default parameter.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ CPUMMODE enmMode = DBGCCmdHlpGetCpuMode(pCmdHlp);
+ uint16_t cbLimit = 0;
+ uint64_t GCFlat = 0;
+ int rc = DBGFR3RegCpuQueryXdtr(pDbgc->pUVM, pDbgc->idCpu, DBGFREG_IDTR, &GCFlat, &cbLimit);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3RegCpuQueryXdtr/DBGFREG_IDTR");
+ unsigned cbEntry;
+ switch (enmMode)
+ {
+ case CPUMMODE_REAL: cbEntry = sizeof(RTFAR16); break;
+ case CPUMMODE_PROTECTED: cbEntry = sizeof(X86DESC); break;
+ case CPUMMODE_LONG: cbEntry = sizeof(X86DESC64); break;
+ default:
+ return DBGCCmdHlpPrintf(pCmdHlp, "error: Invalid CPU mode %d.\n", enmMode);
+ }
+
+ bool fAll = pCmd->pszCmd[2] == 'a';
+ DBGCVAR Var;
+ if (!cArgs)
+ {
+ cArgs = 1;
+ paArgs = &Var;
+ Var.enmType = DBGCVAR_TYPE_NUMBER;
+ Var.u.u64Number = 0;
+ Var.enmRangeType = DBGCVAR_RANGE_ELEMENTS;
+ Var.u64Range = 256;
+ }
+
+ /*
+ * Process the arguments.
+ */
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, i, paArgs[i].enmType == DBGCVAR_TYPE_NUMBER);
+ if (paArgs[i].u.u64Number < 256)
+ {
+ RTGCUINTPTR iInt = (RTGCUINTPTR)paArgs[i].u.u64Number;
+ unsigned cInts = paArgs[i].enmRangeType != DBGCVAR_RANGE_NONE
+ ? paArgs[i].u64Range
+ : 1;
+ bool fSingle = cInts == 1;
+ while ( cInts-- > 0
+ && iInt < 256)
+ {
+ /*
+ * Try read it.
+ */
+ union
+ {
+ RTFAR16 Real;
+ X86DESC Prot;
+ X86DESC64 Long;
+ } u;
+ if (iInt * cbEntry + (cbEntry - 1) > cbLimit)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%04x not within the IDT\n", (unsigned)iInt);
+ if (!fAll && !fSingle)
+ return VINF_SUCCESS;
+ }
+ DBGCVAR AddrVar;
+ AddrVar.enmType = DBGCVAR_TYPE_GC_FLAT;
+ AddrVar.u.GCFlat = GCFlat + iInt * cbEntry;
+ AddrVar.enmRangeType = DBGCVAR_RANGE_NONE;
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &u, cbEntry, &AddrVar, NULL);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "Reading IDT entry %#04x.\n", (unsigned)iInt);
+
+ /*
+ * Display it.
+ */
+ switch (enmMode)
+ {
+ case CPUMMODE_REAL:
+ {
+ char *pszSymbol = dbgcCmdHlpFarAddrToSymbol(pCmdHlp, DBGF_AS_GLOBAL, u.Real.sel, u.Real.off, " (", ")");
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%04x %RTfp16%s\n", (unsigned)iInt, u.Real, pszSymbol ? pszSymbol : "");
+ RTStrFree(pszSymbol);
+ break;
+ }
+ case CPUMMODE_PROTECTED:
+ if (fAll || fSingle || u.Prot.Gen.u1Present)
+ rc = dbgcCmdDumpDTWorker32(pCmdHlp, &u.Prot, iInt, false, DBGF_AS_GLOBAL);
+ break;
+ case CPUMMODE_LONG:
+ if (fAll || fSingle || u.Long.Gen.u1Present)
+ rc = dbgcCmdDumpDTWorker64(pCmdHlp, &u.Long, iInt, false, DBGF_AS_GLOBAL, NULL);
+ break;
+ default: break; /* to shut up gcc */
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* next */
+ iInt++;
+ }
+ }
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "error: %llx is out of bounds (max 256)\n", paArgs[i].u.u64Number);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD,
+ * The 'da'\, 'dq'\, 'dqs'\, 'dd'\, 'dds'\, 'dw'\, 'db'\, 'dp'\, 'dps'\,
+ * and 'du' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpMem(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs <= 1);
+ if (cArgs == 1)
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+#define DBGC_DUMP_MEM_F_ASCII RT_BIT_32(31)
+#define DBGC_DUMP_MEM_F_UNICODE RT_BIT_32(30)
+#define DBGC_DUMP_MEM_F_FAR RT_BIT_32(29)
+#define DBGC_DUMP_MEM_F_SYMBOLS RT_BIT_32(28)
+#define DBGC_DUMP_MEM_F_SIZE UINT32_C(0x0000ffff)
+
+ /*
+ * Figure out the element size.
+ */
+ unsigned cbElement;
+ bool fAscii = false;
+ bool fUnicode = false;
+ bool fFar = false;
+ bool fSymbols = pCmd->pszCmd[1] && pCmd->pszCmd[2] == 's';
+ switch (pCmd->pszCmd[1])
+ {
+ default:
+ case 'b': cbElement = 1; break;
+ case 'w': cbElement = 2; break;
+ case 'd': cbElement = 4; break;
+ case 'q': cbElement = 8; break;
+ case 'a':
+ cbElement = 1;
+ fAscii = true;
+ break;
+ case 'F':
+ cbElement = 4;
+ fFar = true;
+ break;
+ case 'p':
+ cbElement = DBGFR3CpuIsIn64BitCode(pUVM, pDbgc->idCpu) ? 8 : 4;
+ break;
+ case 'u':
+ cbElement = 2;
+ fUnicode = true;
+ break;
+ case '\0':
+ fAscii = RT_BOOL(pDbgc->cbDumpElement & DBGC_DUMP_MEM_F_ASCII);
+ fSymbols = RT_BOOL(pDbgc->cbDumpElement & DBGC_DUMP_MEM_F_SYMBOLS);
+ fUnicode = RT_BOOL(pDbgc->cbDumpElement & DBGC_DUMP_MEM_F_UNICODE);
+ fFar = RT_BOOL(pDbgc->cbDumpElement & DBGC_DUMP_MEM_F_FAR);
+ cbElement = pDbgc->cbDumpElement & DBGC_DUMP_MEM_F_SIZE;
+ if (!cbElement)
+ cbElement = 1;
+ break;
+ }
+ uint32_t const cbDumpElement = cbElement
+ | (fSymbols ? DBGC_DUMP_MEM_F_SYMBOLS : 0)
+ | (fFar ? DBGC_DUMP_MEM_F_FAR : 0)
+ | (fUnicode ? DBGC_DUMP_MEM_F_UNICODE : 0)
+ | (fAscii ? DBGC_DUMP_MEM_F_ASCII : 0);
+ pDbgc->cbDumpElement = cbDumpElement;
+
+ /*
+ * Find address.
+ */
+ if (!cArgs)
+ pDbgc->DumpPos.enmRangeType = DBGCVAR_RANGE_NONE;
+ else
+ pDbgc->DumpPos = paArgs[0];
+
+ /*
+ * Range.
+ */
+ switch (pDbgc->DumpPos.enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE:
+ pDbgc->DumpPos.enmRangeType = DBGCVAR_RANGE_BYTES;
+ pDbgc->DumpPos.u64Range = 0x60;
+ break;
+
+ case DBGCVAR_RANGE_ELEMENTS:
+ if (pDbgc->DumpPos.u64Range > 2048)
+ return DBGCCmdHlpPrintf(pCmdHlp, "error: Too many elements requested. Max is 2048 elements.\n");
+ pDbgc->DumpPos.enmRangeType = DBGCVAR_RANGE_BYTES;
+ pDbgc->DumpPos.u64Range = (cbElement ? cbElement : 1) * pDbgc->DumpPos.u64Range;
+ break;
+
+ case DBGCVAR_RANGE_BYTES:
+ if (pDbgc->DumpPos.u64Range > 65536)
+ return DBGCCmdHlpPrintf(pCmdHlp, "error: The requested range is too big. Max is 64KB.\n");
+ break;
+
+ default:
+ return DBGCCmdHlpPrintf(pCmdHlp, "internal error: Unknown range type %d.\n", pDbgc->DumpPos.enmRangeType);
+ }
+
+ pDbgc->pLastPos = &pDbgc->DumpPos;
+
+ /*
+ * Do the dumping.
+ */
+ int cbLeft = (int)pDbgc->DumpPos.u64Range;
+ uint8_t u16Prev = '\0';
+ for (;;)
+ {
+ /*
+ * Read memory.
+ */
+ char achBuffer[16];
+ size_t cbReq = RT_MIN((int)sizeof(achBuffer), cbLeft);
+ size_t cb = RT_MIN((int)sizeof(achBuffer), cbLeft);
+ int rc = pCmdHlp->pfnMemRead(pCmdHlp, &achBuffer, cbReq, &pDbgc->DumpPos, &cb);
+ if (RT_FAILURE(rc))
+ {
+ if (u16Prev && u16Prev != '\n')
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "Reading memory at %DV.\n", &pDbgc->DumpPos);
+ }
+
+ /*
+ * Display it.
+ */
+ memset(&achBuffer[cb], 0, sizeof(achBuffer) - cb);
+ if (!fAscii && !fUnicode)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%DV:", &pDbgc->DumpPos);
+ unsigned i;
+ for (i = 0; i < cb; i += cbElement)
+ {
+ const char *pszSpace = " ";
+ if (cbElement <= 2 && i == 8)
+ pszSpace = "-";
+ switch (cbElement)
+ {
+ case 1:
+ DBGCCmdHlpPrintf(pCmdHlp, "%s%02x", pszSpace, *(uint8_t *)&achBuffer[i]);
+ break;
+ case 2:
+ DBGCCmdHlpPrintf(pCmdHlp, "%s%04x", pszSpace, *(uint16_t *)&achBuffer[i]);
+ break;
+ case 4:
+ if (!fFar)
+ DBGCCmdHlpPrintf(pCmdHlp, "%s%08x", pszSpace, *(uint32_t *)&achBuffer[i]);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "%s%04x:%04x:",
+ pszSpace, *(uint16_t *)&achBuffer[i + 2], *(uint16_t *)&achBuffer[i]);
+ break;
+ case 8:
+ DBGCCmdHlpPrintf(pCmdHlp, "%s%016llx", pszSpace, *(uint64_t *)&achBuffer[i]);
+ break;
+ }
+
+ if (fSymbols)
+ {
+ /* Try lookup symbol for the above address. */
+ DBGFADDRESS Addr;
+ rc = VINF_SUCCESS;
+ if (cbElement == 8)
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Addr, *(uint64_t *)&achBuffer[i]);
+ else if (!fFar)
+ DBGFR3AddrFromFlat(pDbgc->pUVM, &Addr, *(uint32_t *)&achBuffer[i]);
+ else
+ rc = DBGFR3AddrFromSelOff(pDbgc->pUVM, pDbgc->idCpu, &Addr,
+ *(uint16_t *)&achBuffer[i + 2], *(uint16_t *)&achBuffer[i]);
+ if (RT_SUCCESS(rc))
+ {
+ RTINTPTR offDisp;
+ RTDBGSYMBOL Symbol;
+ rc = DBGFR3AsSymbolByAddr(pUVM, pDbgc->hDbgAs, &Addr,
+ RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED,
+ &offDisp, &Symbol, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!offDisp)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %s", Symbol.szName);
+ else if (offDisp > 0)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %s + %RGv", Symbol.szName, offDisp);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " %s - %RGv", Symbol.szName, -offDisp);
+ if (Symbol.cb > 0)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " (LB %RGv)", Symbol.cb);
+ }
+ }
+
+ /* Next line prefix. */
+ unsigned iNext = i + cbElement;
+ if (iNext < cb)
+ {
+ DBGCVAR TmpPos = pDbgc->DumpPos;
+ DBGCCmdHlpEval(pCmdHlp, &TmpPos, "(%Dv) + %x", &pDbgc->DumpPos, iNext);
+ DBGCCmdHlpPrintf(pCmdHlp, "\n%DV:", &pDbgc->DumpPos);
+ }
+ }
+ }
+
+ /* Chars column. */
+ if (cbElement == 1)
+ {
+ while (i++ < sizeof(achBuffer))
+ DBGCCmdHlpPrintf(pCmdHlp, " ");
+ DBGCCmdHlpPrintf(pCmdHlp, " ");
+ for (i = 0; i < cb; i += cbElement)
+ {
+ uint8_t u8 = *(uint8_t *)&achBuffer[i];
+ if (RT_C_IS_PRINT(u8) && u8 < 127 && u8 >= 32)
+ DBGCCmdHlpPrintf(pCmdHlp, "%c", u8);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, ".");
+ }
+ }
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ }
+ else
+ {
+ /*
+ * We print up to the first zero and stop there.
+ * Only printables + '\t' and '\n' are printed.
+ */
+ if (!u16Prev)
+ DBGCCmdHlpPrintf(pCmdHlp, "%DV:\n", &pDbgc->DumpPos);
+ uint16_t u16 = '\0';
+ unsigned i;
+ for (i = 0; i < cb; i += cbElement)
+ {
+ u16Prev = u16;
+ if (cbElement == 1)
+ u16 = *(uint8_t *)&achBuffer[i];
+ else
+ u16 = *(uint16_t *)&achBuffer[i];
+ if ( u16 < 127
+ && ( (RT_C_IS_PRINT(u16) && u16 >= 32)
+ || u16 == '\t'
+ || u16 == '\n'))
+ DBGCCmdHlpPrintf(pCmdHlp, "%c", (int)u16);
+ else if (!u16)
+ break;
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "\\x%0*x", cbElement * 2, u16);
+ }
+ if (u16 == '\0')
+ cb = cbLeft = i + 1;
+ if (cbLeft - cb <= 0 && u16Prev != '\n')
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ }
+
+ /*
+ * Advance
+ */
+ cbLeft -= (int)cb;
+ rc = DBGCCmdHlpEval(pCmdHlp, &pDbgc->DumpPos, "(%Dv) + %x", &pDbgc->DumpPos, cb);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "Expression: (%Dv) + %x\n", &pDbgc->DumpPos, cb);
+ if (cbLeft <= 0)
+ break;
+ }
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Best guess at which paging mode currently applies to the guest
+ * paging structures.
+ *
+ * This have to come up with a decent answer even when the guest
+ * is in non-paged protected mode or real mode.
+ *
+ * @returns cr3.
+ * @param pDbgc The DBGC instance.
+ * @param pfPAE Where to store the page address extension indicator.
+ * @param pfLME Where to store the long mode enabled indicator.
+ * @param pfPSE Where to store the page size extension indicator.
+ * @param pfPGE Where to store the page global enabled indicator.
+ * @param pfNXE Where to store the no-execution enabled indicator.
+ */
+static RTGCPHYS dbgcGetGuestPageMode(PDBGC pDbgc, bool *pfPAE, bool *pfLME, bool *pfPSE, bool *pfPGE, bool *pfNXE)
+{
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pDbgc->pUVM, pDbgc->idCpu);
+ RTGCUINTREG cr4 = CPUMGetGuestCR4(pVCpu);
+ *pfPSE = !!(cr4 & X86_CR4_PSE);
+ *pfPGE = !!(cr4 & X86_CR4_PGE);
+ if (cr4 & X86_CR4_PAE)
+ {
+ *pfPSE = true;
+ *pfPAE = true;
+ }
+ else
+ *pfPAE = false;
+
+ *pfLME = CPUMGetGuestMode(pVCpu) == CPUMMODE_LONG;
+ *pfNXE = false; /* GUEST64 GUESTNX */
+ return CPUMGetGuestCR3(pVCpu);
+}
+
+
+/**
+ * Determine the shadow paging mode.
+ *
+ * @returns cr3.
+ * @param pDbgc The DBGC instance.
+ * @param pfPAE Where to store the page address extension indicator.
+ * @param pfLME Where to store the long mode enabled indicator.
+ * @param pfPSE Where to store the page size extension indicator.
+ * @param pfPGE Where to store the page global enabled indicator.
+ * @param pfNXE Where to store the no-execution enabled indicator.
+ */
+static RTHCPHYS dbgcGetShadowPageMode(PDBGC pDbgc, bool *pfPAE, bool *pfLME, bool *pfPSE, bool *pfPGE, bool *pfNXE)
+{
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pDbgc->pUVM, pDbgc->idCpu);
+
+ *pfPSE = true;
+ *pfPGE = false;
+ switch (PGMGetShadowMode(pVCpu))
+ {
+ default:
+ case PGMMODE_32_BIT:
+ *pfPAE = *pfLME = *pfNXE = false;
+ break;
+ case PGMMODE_PAE:
+ *pfLME = *pfNXE = false;
+ *pfPAE = true;
+ break;
+ case PGMMODE_PAE_NX:
+ *pfLME = false;
+ *pfPAE = *pfNXE = true;
+ break;
+ case PGMMODE_AMD64:
+ *pfNXE = false;
+ *pfPAE = *pfLME = true;
+ break;
+ case PGMMODE_AMD64_NX:
+ *pfPAE = *pfLME = *pfNXE = true;
+ break;
+ }
+ return PGMGetHyperCR3(pVCpu);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD,
+ * The 'dpd'\, 'dpda'\, 'dpdb'\, 'dpdg' and 'dpdh' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpPageDir(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs <= 1);
+ if (cArgs == 1 && pCmd->pszCmd[3] == 'a')
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ if (cArgs == 1 && pCmd->pszCmd[3] != 'a')
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[0].enmType == DBGCVAR_TYPE_NUMBER
+ || DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Guest or shadow page directories? Get the paging parameters.
+ */
+ bool fGuest = pCmd->pszCmd[3] != 'h';
+ if (!pCmd->pszCmd[3] || pCmd->pszCmd[3] == 'a')
+ fGuest = paArgs[0].enmType == DBGCVAR_TYPE_NUMBER ? true : DBGCVAR_ISGCPOINTER(paArgs[0].enmType);
+
+ bool fPAE, fLME, fPSE, fPGE, fNXE;
+ uint64_t cr3 = fGuest
+ ? dbgcGetGuestPageMode(pDbgc, &fPAE, &fLME, &fPSE, &fPGE, &fNXE)
+ : dbgcGetShadowPageMode(pDbgc, &fPAE, &fLME, &fPSE, &fPGE, &fNXE);
+ const unsigned cbEntry = fPAE ? sizeof(X86PTEPAE) : sizeof(X86PTE);
+
+ /*
+ * Setup default argument if none was specified.
+ * Fix address / index confusion.
+ */
+ DBGCVAR VarDefault;
+ if (!cArgs)
+ {
+ if (pCmd->pszCmd[3] == 'a')
+ {
+ if (fLME || fPAE)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Default argument for 'dpda' hasn't been fully implemented yet. Try with an address or use one of the other commands.\n");
+ if (fGuest)
+ DBGCVAR_INIT_GC_PHYS(&VarDefault, cr3);
+ else
+ DBGCVAR_INIT_HC_PHYS(&VarDefault, cr3);
+ }
+ else
+ DBGCVAR_INIT_GC_FLAT(&VarDefault, 0);
+ paArgs = &VarDefault;
+ cArgs = 1;
+ }
+ else if (paArgs[0].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ /* If it's a number (not an address), it's an index, so convert it to an address. */
+ Assert(pCmd->pszCmd[3] != 'a');
+ VarDefault = paArgs[0];
+ if (fPAE)
+ return DBGCCmdHlpPrintf(pCmdHlp, "PDE indexing is only implemented for 32-bit paging.\n");
+ if (VarDefault.u.u64Number >= PAGE_SIZE / cbEntry)
+ return DBGCCmdHlpPrintf(pCmdHlp, "PDE index is out of range [0..%d].\n", PAGE_SIZE / cbEntry - 1);
+ VarDefault.u.u64Number <<= X86_PD_SHIFT;
+ VarDefault.enmType = DBGCVAR_TYPE_GC_FLAT;
+ paArgs = &VarDefault;
+ }
+
+ /*
+ * Locate the PDE to start displaying at.
+ *
+ * The 'dpda' command takes the address of a PDE, while the others are guest
+ * virtual address which PDEs should be displayed. So, 'dpda' is rather simple
+ * while the others require us to do all the tedious walking thru the paging
+ * hierarchy to find the intended PDE.
+ */
+ unsigned iEntry = ~0U; /* The page directory index. ~0U for 'dpta'. */
+ DBGCVAR VarGCPtr = { NULL, }; /* The GC address corresponding to the current PDE (iEntry != ~0U). */
+ DBGCVAR VarPDEAddr; /* The address of the current PDE. */
+ unsigned cEntries; /* The number of entries to display. */
+ unsigned cEntriesMax; /* The max number of entries to display. */
+ int rc;
+ if (pCmd->pszCmd[3] == 'a')
+ {
+ VarPDEAddr = paArgs[0];
+ switch (VarPDEAddr.enmRangeType)
+ {
+ case DBGCVAR_RANGE_BYTES: cEntries = VarPDEAddr.u64Range / cbEntry; break;
+ case DBGCVAR_RANGE_ELEMENTS: cEntries = VarPDEAddr.u64Range; break;
+ default: cEntries = 10; break;
+ }
+ cEntriesMax = PAGE_SIZE / cbEntry;
+ }
+ else
+ {
+ /*
+ * Determine the range.
+ */
+ switch (paArgs[0].enmRangeType)
+ {
+ case DBGCVAR_RANGE_BYTES: cEntries = paArgs[0].u64Range / PAGE_SIZE; break;
+ case DBGCVAR_RANGE_ELEMENTS: cEntries = paArgs[0].u64Range; break;
+ default: cEntries = 10; break;
+ }
+
+ /*
+ * Normalize the input address, it must be a flat GC address.
+ */
+ rc = DBGCCmdHlpEval(pCmdHlp, &VarGCPtr, "%%(%Dv)", &paArgs[0]);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "%%(%Dv)", &paArgs[0]);
+ if (VarGCPtr.enmType == DBGCVAR_TYPE_HC_FLAT)
+ {
+ VarGCPtr.u.GCFlat = (uintptr_t)VarGCPtr.u.pvHCFlat;
+ VarGCPtr.enmType = DBGCVAR_TYPE_GC_FLAT;
+ }
+ if (fPAE)
+ VarGCPtr.u.GCFlat &= ~(((RTGCPTR)1 << X86_PD_PAE_SHIFT) - 1);
+ else
+ VarGCPtr.u.GCFlat &= ~(((RTGCPTR)1 << X86_PD_SHIFT) - 1);
+
+ /*
+ * Do the paging walk until we get to the page directory.
+ */
+ DBGCVAR VarCur;
+ if (fGuest)
+ DBGCVAR_INIT_GC_PHYS(&VarCur, cr3);
+ else
+ DBGCVAR_INIT_HC_PHYS(&VarCur, cr3);
+ if (fLME)
+ {
+ /* Page Map Level 4 Lookup. */
+ /* Check if it's a valid address first? */
+ VarCur.u.u64Number &= X86_PTE_PAE_PG_MASK;
+ VarCur.u.u64Number += (((uint64_t)VarGCPtr.u.GCFlat >> X86_PML4_SHIFT) & X86_PML4_MASK) * sizeof(X86PML4E);
+ X86PML4E Pml4e;
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pml4e, sizeof(Pml4e), &VarCur, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PML4E memory at %DV.\n", &VarCur);
+ if (!Pml4e.n.u1Present)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Page directory pointer table is not present for %Dv.\n", &VarGCPtr);
+
+ VarCur.u.u64Number = Pml4e.u & X86_PML4E_PG_MASK;
+ Assert(fPAE);
+ }
+ if (fPAE)
+ {
+ /* Page directory pointer table. */
+ X86PDPE Pdpe;
+ VarCur.u.u64Number += ((VarGCPtr.u.GCFlat >> X86_PDPT_SHIFT) & X86_PDPT_MASK_PAE) * sizeof(Pdpe);
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pdpe, sizeof(Pdpe), &VarCur, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PDPE memory at %DV.\n", &VarCur);
+ if (!Pdpe.n.u1Present)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Page directory is not present for %Dv.\n", &VarGCPtr);
+
+ iEntry = (VarGCPtr.u.GCFlat >> X86_PD_PAE_SHIFT) & X86_PD_PAE_MASK;
+ VarPDEAddr = VarCur;
+ VarPDEAddr.u.u64Number = Pdpe.u & X86_PDPE_PG_MASK;
+ VarPDEAddr.u.u64Number += iEntry * sizeof(X86PDEPAE);
+ }
+ else
+ {
+ /* 32-bit legacy - CR3 == page directory. */
+ iEntry = (VarGCPtr.u.GCFlat >> X86_PD_SHIFT) & X86_PD_MASK;
+ VarPDEAddr = VarCur;
+ VarPDEAddr.u.u64Number += iEntry * sizeof(X86PDE);
+ }
+ cEntriesMax = (PAGE_SIZE - iEntry) / cbEntry;
+ }
+
+ /* adjust cEntries */
+ cEntries = RT_MAX(1, cEntries);
+ cEntries = RT_MIN(cEntries, cEntriesMax);
+
+ /*
+ * The display loop.
+ */
+ DBGCCmdHlpPrintf(pCmdHlp, iEntry != ~0U ? "%DV (index %#x):\n" : "%DV:\n",
+ &VarPDEAddr, iEntry);
+ do
+ {
+ /*
+ * Read.
+ */
+ X86PDEPAE Pde;
+ Pde.u = 0;
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pde, cbEntry, &VarPDEAddr, NULL);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "Reading PDE memory at %DV.\n", &VarPDEAddr);
+
+ /*
+ * Display.
+ */
+ if (iEntry != ~0U)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%03x %DV: ", iEntry, &VarGCPtr);
+ iEntry++;
+ }
+ if (fPSE && Pde.b.u1Size)
+ DBGCCmdHlpPrintf(pCmdHlp,
+ fPAE
+ ? "%016llx big phys=%016llx %s %s %s %s %s avl=%02x %s %s %s %s %s"
+ : "%08llx big phys=%08llx %s %s %s %s %s avl=%02x %s %s %s %s %s",
+ Pde.u,
+ Pde.u & X86_PDE_PAE_PG_MASK,
+ Pde.b.u1Present ? "p " : "np",
+ Pde.b.u1Write ? "w" : "r",
+ Pde.b.u1User ? "u" : "s",
+ Pde.b.u1Accessed ? "a " : "na",
+ Pde.b.u1Dirty ? "d " : "nd",
+ Pde.b.u3Available,
+ Pde.b.u1Global ? (fPGE ? "g" : "G") : " ",
+ Pde.b.u1WriteThru ? "pwt" : " ",
+ Pde.b.u1CacheDisable ? "pcd" : " ",
+ Pde.b.u1PAT ? "pat" : "",
+ Pde.b.u1NoExecute ? (fNXE ? "nx" : "NX") : " ");
+ else
+ DBGCCmdHlpPrintf(pCmdHlp,
+ fPAE
+ ? "%016llx 4kb phys=%016llx %s %s %s %s %s avl=%02x %s %s %s %s"
+ : "%08llx 4kb phys=%08llx %s %s %s %s %s avl=%02x %s %s %s %s",
+ Pde.u,
+ Pde.u & X86_PDE_PAE_PG_MASK,
+ Pde.n.u1Present ? "p " : "np",
+ Pde.n.u1Write ? "w" : "r",
+ Pde.n.u1User ? "u" : "s",
+ Pde.n.u1Accessed ? "a " : "na",
+ Pde.u & RT_BIT(6) ? "6 " : " ",
+ Pde.n.u3Available,
+ Pde.u & RT_BIT(8) ? "8" : " ",
+ Pde.n.u1WriteThru ? "pwt" : " ",
+ Pde.n.u1CacheDisable ? "pcd" : " ",
+ Pde.u & RT_BIT(7) ? "7" : "",
+ Pde.n.u1NoExecute ? (fNXE ? "nx" : "NX") : " ");
+ if (Pde.u & UINT64_C(0x7fff000000000000))
+ DBGCCmdHlpPrintf(pCmdHlp, " weird=%RX64", (Pde.u & UINT64_C(0x7fff000000000000)));
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Advance.
+ */
+ VarPDEAddr.u.u64Number += cbEntry;
+ if (iEntry != ~0U)
+ VarGCPtr.u.GCFlat += fPAE ? RT_BIT_32(X86_PD_PAE_SHIFT) : RT_BIT_32(X86_PD_SHIFT);
+ } while (cEntries-- > 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dpdb' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpPageDirBoth(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ int rc1 = pCmdHlp->pfnExec(pCmdHlp, "dpdg %DV", &paArgs[0]);
+ int rc2 = pCmdHlp->pfnExec(pCmdHlp, "dpdh %DV", &paArgs[0]);
+ if (RT_FAILURE(rc1))
+ return rc1;
+ NOREF(pCmd); NOREF(paArgs); NOREF(cArgs);
+ return rc2;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dph*' commands and main part of 'm'.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpPageHierarchy(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Figure the context and base flags.
+ */
+ uint32_t fFlags = DBGFPGDMP_FLAGS_PAGE_INFO | DBGFPGDMP_FLAGS_PRINT_CR3;
+ if (pCmd->pszCmd[0] == 'm')
+ fFlags |= DBGFPGDMP_FLAGS_GUEST | DBGFPGDMP_FLAGS_SHADOW;
+ else if (pCmd->pszCmd[3] == '\0')
+ fFlags |= DBGFPGDMP_FLAGS_GUEST;
+ else if (pCmd->pszCmd[3] == 'g')
+ fFlags |= DBGFPGDMP_FLAGS_GUEST;
+ else if (pCmd->pszCmd[3] == 'h')
+ fFlags |= DBGFPGDMP_FLAGS_SHADOW;
+ else
+ AssertFailed();
+
+ if (pDbgc->cPagingHierarchyDumps == 0)
+ fFlags |= DBGFPGDMP_FLAGS_HEADER;
+ pDbgc->cPagingHierarchyDumps = (pDbgc->cPagingHierarchyDumps + 1) % 42;
+
+ /*
+ * Get the range.
+ */
+ PCDBGCVAR pRange = cArgs > 0 ? &paArgs[0] : pDbgc->pLastPos;
+ RTGCPTR GCPtrFirst = NIL_RTGCPTR;
+ int rc = DBGCCmdHlpVarToFlatAddr(pCmdHlp, pRange, &GCPtrFirst);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to convert %DV to a flat address: %Rrc", pRange, rc);
+
+ uint64_t cbRange;
+ rc = DBGCCmdHlpVarGetRange(pCmdHlp, pRange, PAGE_SIZE, PAGE_SIZE * 8, &cbRange);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to obtain the range of %DV: %Rrc", pRange, rc);
+
+ RTGCPTR GCPtrLast = RTGCPTR_MAX - GCPtrFirst;
+ if (cbRange >= GCPtrLast)
+ GCPtrLast = RTGCPTR_MAX;
+ else if (!cbRange)
+ GCPtrLast = GCPtrFirst;
+ else
+ GCPtrLast = GCPtrFirst + cbRange - 1;
+
+ /*
+ * Do we have a CR3?
+ */
+ uint64_t cr3 = 0;
+ if (cArgs > 1)
+ {
+ if ((fFlags & (DBGFPGDMP_FLAGS_GUEST | DBGFPGDMP_FLAGS_SHADOW)) == (DBGFPGDMP_FLAGS_GUEST | DBGFPGDMP_FLAGS_SHADOW))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "No CR3 or mode arguments when dumping both context, please.");
+ if (paArgs[1].enmType != DBGCVAR_TYPE_NUMBER)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "The CR3 argument is not a number: %DV", &paArgs[1]);
+ cr3 = paArgs[1].u.u64Number;
+ }
+ else
+ fFlags |= DBGFPGDMP_FLAGS_CURRENT_CR3;
+
+ /*
+ * Do we have a mode?
+ */
+ if (cArgs > 2)
+ {
+ if (paArgs[2].enmType != DBGCVAR_TYPE_STRING)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "The mode argument is not a string: %DV", &paArgs[2]);
+ static const struct MODETOFLAGS
+ {
+ const char *pszName;
+ uint32_t fFlags;
+ } s_aModeToFlags[] =
+ {
+ { "ept", DBGFPGDMP_FLAGS_EPT },
+ { "legacy", 0 },
+ { "legacy-np", DBGFPGDMP_FLAGS_NP },
+ { "pse", DBGFPGDMP_FLAGS_PSE },
+ { "pse-np", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_NP },
+ { "pae", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE },
+ { "pae-np", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_NP },
+ { "pae-nx", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_NXE },
+ { "pae-nx-np", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_NXE | DBGFPGDMP_FLAGS_NP },
+ { "long", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_LME },
+ { "long-np", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_LME | DBGFPGDMP_FLAGS_NP },
+ { "long-nx", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_LME | DBGFPGDMP_FLAGS_NXE },
+ { "long-nx-np", DBGFPGDMP_FLAGS_PSE | DBGFPGDMP_FLAGS_PAE | DBGFPGDMP_FLAGS_LME | DBGFPGDMP_FLAGS_NXE | DBGFPGDMP_FLAGS_NP }
+ };
+ int i = RT_ELEMENTS(s_aModeToFlags);
+ while (i-- > 0)
+ if (!strcmp(s_aModeToFlags[i].pszName, paArgs[2].u.pszString))
+ {
+ fFlags |= s_aModeToFlags[i].fFlags;
+ break;
+ }
+ if (i < 0)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Unknown mode: \"%s\"", paArgs[2].u.pszString);
+ }
+ else
+ fFlags |= DBGFPGDMP_FLAGS_CURRENT_MODE;
+
+ /*
+ * Call the worker.
+ */
+ rc = DBGFR3PagingDumpEx(pUVM, pDbgc->idCpu, fFlags, cr3, GCPtrFirst, GCPtrLast, 99 /*cMaxDepth*/,
+ DBGCCmdHlpGetDbgfOutputHlp(pCmdHlp));
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "DBGFR3PagingDumpEx: %Rrc\n", rc);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dpg*' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpPageTable(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 1);
+ if (pCmd->pszCmd[3] == 'a')
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ else
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[0].enmType == DBGCVAR_TYPE_NUMBER
+ || DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Guest or shadow page tables? Get the paging parameters.
+ */
+ bool fGuest = pCmd->pszCmd[3] != 'h';
+ if (!pCmd->pszCmd[3] || pCmd->pszCmd[3] == 'a')
+ fGuest = paArgs[0].enmType == DBGCVAR_TYPE_NUMBER ? true : DBGCVAR_ISGCPOINTER(paArgs[0].enmType);
+
+ bool fPAE, fLME, fPSE, fPGE, fNXE;
+ uint64_t cr3 = fGuest
+ ? dbgcGetGuestPageMode(pDbgc, &fPAE, &fLME, &fPSE, &fPGE, &fNXE)
+ : dbgcGetShadowPageMode(pDbgc, &fPAE, &fLME, &fPSE, &fPGE, &fNXE);
+ const unsigned cbEntry = fPAE ? sizeof(X86PTEPAE) : sizeof(X86PTE);
+
+ /*
+ * Locate the PTE to start displaying at.
+ *
+ * The 'dpta' command takes the address of a PTE, while the others are guest
+ * virtual address which PTEs should be displayed. So, 'pdta' is rather simple
+ * while the others require us to do all the tedious walking thru the paging
+ * hierarchy to find the intended PTE.
+ */
+ unsigned iEntry = ~0U; /* The page table index. ~0U for 'dpta'. */
+ DBGCVAR VarGCPtr; /* The GC address corresponding to the current PTE (iEntry != ~0U). */
+ DBGCVAR VarPTEAddr; /* The address of the current PTE. */
+ unsigned cEntries; /* The number of entries to display. */
+ unsigned cEntriesMax; /* The max number of entries to display. */
+ int rc;
+ if (pCmd->pszCmd[3] == 'a')
+ {
+ VarPTEAddr = paArgs[0];
+ switch (VarPTEAddr.enmRangeType)
+ {
+ case DBGCVAR_RANGE_BYTES: cEntries = VarPTEAddr.u64Range / cbEntry; break;
+ case DBGCVAR_RANGE_ELEMENTS: cEntries = VarPTEAddr.u64Range; break;
+ default: cEntries = 10; break;
+ }
+ cEntriesMax = PAGE_SIZE / cbEntry;
+ }
+ else
+ {
+ /*
+ * Determine the range.
+ */
+ switch (paArgs[0].enmRangeType)
+ {
+ case DBGCVAR_RANGE_BYTES: cEntries = paArgs[0].u64Range / PAGE_SIZE; break;
+ case DBGCVAR_RANGE_ELEMENTS: cEntries = paArgs[0].u64Range; break;
+ default: cEntries = 10; break;
+ }
+
+ /*
+ * Normalize the input address, it must be a flat GC address.
+ */
+ rc = DBGCCmdHlpEval(pCmdHlp, &VarGCPtr, "%%(%Dv)", &paArgs[0]);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "%%(%Dv)", &paArgs[0]);
+ if (VarGCPtr.enmType == DBGCVAR_TYPE_HC_FLAT)
+ {
+ VarGCPtr.u.GCFlat = (uintptr_t)VarGCPtr.u.pvHCFlat;
+ VarGCPtr.enmType = DBGCVAR_TYPE_GC_FLAT;
+ }
+ VarGCPtr.u.GCFlat &= ~(RTGCPTR)PAGE_OFFSET_MASK;
+
+ /*
+ * Do the paging walk until we get to the page table.
+ */
+ DBGCVAR VarCur;
+ if (fGuest)
+ DBGCVAR_INIT_GC_PHYS(&VarCur, cr3);
+ else
+ DBGCVAR_INIT_HC_PHYS(&VarCur, cr3);
+ if (fLME)
+ {
+ /* Page Map Level 4 Lookup. */
+ /* Check if it's a valid address first? */
+ VarCur.u.u64Number &= X86_PTE_PAE_PG_MASK;
+ VarCur.u.u64Number += (((uint64_t)VarGCPtr.u.GCFlat >> X86_PML4_SHIFT) & X86_PML4_MASK) * sizeof(X86PML4E);
+ X86PML4E Pml4e;
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pml4e, sizeof(Pml4e), &VarCur, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PML4E memory at %DV.\n", &VarCur);
+ if (!Pml4e.n.u1Present)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Page directory pointer table is not present for %Dv.\n", &VarGCPtr);
+
+ VarCur.u.u64Number = Pml4e.u & X86_PML4E_PG_MASK;
+ Assert(fPAE);
+ }
+ if (fPAE)
+ {
+ /* Page directory pointer table. */
+ X86PDPE Pdpe;
+ VarCur.u.u64Number += ((VarGCPtr.u.GCFlat >> X86_PDPT_SHIFT) & X86_PDPT_MASK_PAE) * sizeof(Pdpe);
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pdpe, sizeof(Pdpe), &VarCur, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PDPE memory at %DV.\n", &VarCur);
+ if (!Pdpe.n.u1Present)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Page directory is not present for %Dv.\n", &VarGCPtr);
+
+ VarCur.u.u64Number = Pdpe.u & X86_PDPE_PG_MASK;
+
+ /* Page directory (PAE). */
+ X86PDEPAE Pde;
+ VarCur.u.u64Number += ((VarGCPtr.u.GCFlat >> X86_PD_PAE_SHIFT) & X86_PD_PAE_MASK) * sizeof(Pde);
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pde, sizeof(Pde), &VarCur, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PDE memory at %DV.\n", &VarCur);
+ if (!Pde.n.u1Present)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Page table is not present for %Dv.\n", &VarGCPtr);
+ if (fPSE && Pde.n.u1Size)
+ return pCmdHlp->pfnExec(pCmdHlp, "dpd%s %Dv L3", &pCmd->pszCmd[3], &VarGCPtr);
+
+ iEntry = (VarGCPtr.u.GCFlat >> X86_PT_PAE_SHIFT) & X86_PT_PAE_MASK;
+ VarPTEAddr = VarCur;
+ VarPTEAddr.u.u64Number = Pde.u & X86_PDE_PAE_PG_MASK;
+ VarPTEAddr.u.u64Number += iEntry * sizeof(X86PTEPAE);
+ }
+ else
+ {
+ /* Page directory (legacy). */
+ X86PDE Pde;
+ VarCur.u.u64Number += ((VarGCPtr.u.GCFlat >> X86_PD_SHIFT) & X86_PD_MASK) * sizeof(Pde);
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pde, sizeof(Pde), &VarCur, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PDE memory at %DV.\n", &VarCur);
+ if (!Pde.n.u1Present)
+ return DBGCCmdHlpPrintf(pCmdHlp, "Page table is not present for %Dv.\n", &VarGCPtr);
+ if (fPSE && Pde.n.u1Size)
+ return pCmdHlp->pfnExec(pCmdHlp, "dpd%s %Dv L3", &pCmd->pszCmd[3], &VarGCPtr);
+
+ iEntry = (VarGCPtr.u.GCFlat >> X86_PT_SHIFT) & X86_PT_MASK;
+ VarPTEAddr = VarCur;
+ VarPTEAddr.u.u64Number = Pde.u & X86_PDE_PG_MASK;
+ VarPTEAddr.u.u64Number += iEntry * sizeof(X86PTE);
+ }
+ cEntriesMax = (PAGE_SIZE - iEntry) / cbEntry;
+ }
+
+ /* adjust cEntries */
+ cEntries = RT_MAX(1, cEntries);
+ cEntries = RT_MIN(cEntries, cEntriesMax);
+
+ /*
+ * The display loop.
+ */
+ DBGCCmdHlpPrintf(pCmdHlp, iEntry != ~0U ? "%DV (base %DV / index %#x):\n" : "%DV:\n",
+ &VarPTEAddr, &VarGCPtr, iEntry);
+ do
+ {
+ /*
+ * Read.
+ */
+ X86PTEPAE Pte;
+ Pte.u = 0;
+ rc = pCmdHlp->pfnMemRead(pCmdHlp, &Pte, cbEntry, &VarPTEAddr, NULL);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "Reading PTE memory at %DV.\n", &VarPTEAddr);
+
+ /*
+ * Display.
+ */
+ if (iEntry != ~0U)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%03x %DV: ", iEntry, &VarGCPtr);
+ iEntry++;
+ }
+ DBGCCmdHlpPrintf(pCmdHlp,
+ fPAE
+ ? "%016llx 4kb phys=%016llx %s %s %s %s %s avl=%02x %s %s %s %s %s"
+ : "%08llx 4kb phys=%08llx %s %s %s %s %s avl=%02x %s %s %s %s %s",
+ Pte.u,
+ Pte.u & X86_PTE_PAE_PG_MASK,
+ Pte.n.u1Present ? "p " : "np",
+ Pte.n.u1Write ? "w" : "r",
+ Pte.n.u1User ? "u" : "s",
+ Pte.n.u1Accessed ? "a " : "na",
+ Pte.n.u1Dirty ? "d " : "nd",
+ Pte.n.u3Available,
+ Pte.n.u1Global ? (fPGE ? "g" : "G") : " ",
+ Pte.n.u1WriteThru ? "pwt" : " ",
+ Pte.n.u1CacheDisable ? "pcd" : " ",
+ Pte.n.u1PAT ? "pat" : " ",
+ Pte.n.u1NoExecute ? (fNXE ? "nx" : "NX") : " "
+ );
+ if (Pte.u & UINT64_C(0x7fff000000000000))
+ DBGCCmdHlpPrintf(pCmdHlp, " weird=%RX64", (Pte.u & UINT64_C(0x7fff000000000000)));
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Advance.
+ */
+ VarPTEAddr.u.u64Number += cbEntry;
+ if (iEntry != ~0U)
+ VarGCPtr.u.GCFlat += PAGE_SIZE;
+ } while (cEntries-- > 0);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dptb' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpPageTableBoth(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ int rc1 = pCmdHlp->pfnExec(pCmdHlp, "dptg %DV", &paArgs[0]);
+ int rc2 = pCmdHlp->pfnExec(pCmdHlp, "dpth %DV", &paArgs[0]);
+ if (RT_FAILURE(rc1))
+ return rc1;
+ NOREF(pCmd); NOREF(cArgs);
+ return rc2;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dt' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpTSS(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc;
+
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs <= 1);
+ if (cArgs == 1)
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[0].enmType != DBGCVAR_TYPE_STRING
+ && paArgs[0].enmType != DBGCVAR_TYPE_SYMBOL);
+
+ /*
+ * Check if the command indicates the type.
+ */
+ enum { kTss16, kTss32, kTss64, kTssToBeDetermined } enmTssType = kTssToBeDetermined;
+ if (!strcmp(pCmd->pszCmd, "dt16"))
+ enmTssType = kTss16;
+ else if (!strcmp(pCmd->pszCmd, "dt32"))
+ enmTssType = kTss32;
+ else if (!strcmp(pCmd->pszCmd, "dt64"))
+ enmTssType = kTss64;
+
+ /*
+ * We can get a TSS selector (number), a far pointer using a TSS selector, or some kind of TSS pointer.
+ */
+ uint32_t SelTss = UINT32_MAX;
+ DBGCVAR VarTssAddr;
+ if (cArgs == 0)
+ {
+ /** @todo consider querying the hidden bits instead (missing API). */
+ uint16_t SelTR;
+ rc = DBGFR3RegCpuQueryU16(pUVM, pDbgc->idCpu, DBGFREG_TR, &SelTR);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to query TR, rc=%Rrc\n", rc);
+ DBGCVAR_INIT_GC_FAR(&VarTssAddr, SelTR, 0);
+ SelTss = SelTR;
+ }
+ else if (paArgs[0].enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ if (paArgs[0].u.u64Number < 0xffff)
+ DBGCVAR_INIT_GC_FAR(&VarTssAddr, (RTSEL)paArgs[0].u.u64Number, 0);
+ else
+ {
+ if (paArgs[0].enmRangeType == DBGCVAR_RANGE_ELEMENTS)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Element count doesn't combine with a TSS address.\n");
+ DBGCVAR_INIT_GC_FLAT(&VarTssAddr, paArgs[0].u.u64Number);
+ if (paArgs[0].enmRangeType == DBGCVAR_RANGE_BYTES)
+ {
+ VarTssAddr.enmRangeType = paArgs[0].enmRangeType;
+ VarTssAddr.u64Range = paArgs[0].u64Range;
+ }
+ }
+ }
+ else
+ VarTssAddr = paArgs[0];
+
+ /*
+ * Deal with TSS:ign by means of the GDT.
+ */
+ if (VarTssAddr.enmType == DBGCVAR_TYPE_GC_FAR)
+ {
+ SelTss = VarTssAddr.u.GCFar.sel;
+ DBGFSELINFO SelInfo;
+ rc = DBGFR3SelQueryInfo(pUVM, pDbgc->idCpu, VarTssAddr.u.GCFar.sel, DBGFSELQI_FLAGS_DT_GUEST, &SelInfo);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "DBGFR3SelQueryInfo(,%u,%d,,) -> %Rrc.\n",
+ pDbgc->idCpu, VarTssAddr.u.GCFar.sel, rc);
+
+ if (SelInfo.u.Raw.Gen.u1DescType)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "%04x is not a TSS selector. (!sys)\n", VarTssAddr.u.GCFar.sel);
+
+ switch (SelInfo.u.Raw.Gen.u4Type)
+ {
+ case X86_SEL_TYPE_SYS_286_TSS_BUSY:
+ case X86_SEL_TYPE_SYS_286_TSS_AVAIL:
+ if (enmTssType == kTssToBeDetermined)
+ enmTssType = kTss16;
+ break;
+
+ case X86_SEL_TYPE_SYS_386_TSS_BUSY: /* AMD64 too */
+ case X86_SEL_TYPE_SYS_386_TSS_AVAIL:
+ if (enmTssType == kTssToBeDetermined)
+ enmTssType = SelInfo.fFlags & DBGFSELINFO_FLAGS_LONG_MODE ? kTss64 : kTss32;
+ break;
+
+ default:
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "%04x is not a TSS selector. (type=%x)\n",
+ VarTssAddr.u.GCFar.sel, SelInfo.u.Raw.Gen.u4Type);
+ }
+
+ DBGCVAR_INIT_GC_FLAT(&VarTssAddr, SelInfo.GCPtrBase);
+ DBGCVAR_SET_RANGE(&VarTssAddr, DBGCVAR_RANGE_BYTES, RT_MAX(SelInfo.cbLimit + 1, SelInfo.cbLimit));
+ }
+
+ /*
+ * Determine the TSS type if none is currently given.
+ */
+ if (enmTssType == kTssToBeDetermined)
+ {
+ if ( VarTssAddr.u64Range > 0
+ && VarTssAddr.u64Range < sizeof(X86TSS32) - 4)
+ enmTssType = kTss16;
+ else
+ {
+ uint64_t uEfer;
+ rc = DBGFR3RegCpuQueryU64(pUVM, pDbgc->idCpu, DBGFREG_MSR_K6_EFER, &uEfer);
+ if ( RT_FAILURE(rc)
+ || !(uEfer & MSR_K6_EFER_LMA) )
+ enmTssType = kTss32;
+ else
+ enmTssType = kTss64;
+ }
+ }
+
+ /*
+ * Figure the min/max sizes.
+ * ASSUMES max TSS size is 64 KB.
+ */
+ uint32_t cbTssMin;
+ uint32_t cbTssMax;
+ switch (enmTssType)
+ {
+ case kTss16:
+ cbTssMin = cbTssMax = X86_SEL_TYPE_SYS_286_TSS_LIMIT_MIN + 1;
+ break;
+ case kTss32:
+ cbTssMin = X86_SEL_TYPE_SYS_386_TSS_LIMIT_MIN + 1;
+ cbTssMax = _64K;
+ break;
+ case kTss64:
+ cbTssMin = X86_SEL_TYPE_SYS_386_TSS_LIMIT_MIN + 1;
+ cbTssMax = _64K;
+ break;
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR);
+ }
+ uint32_t cbTss = VarTssAddr.enmRangeType == DBGCVAR_RANGE_BYTES ? (uint32_t)VarTssAddr.u64Range : 0;
+ if (cbTss == 0)
+ cbTss = cbTssMin;
+ else if (cbTss < cbTssMin)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Minimum TSS size is %u bytes, you specified %llu (%llx) bytes.\n",
+ cbTssMin, VarTssAddr.u64Range, VarTssAddr.u64Range);
+ else if (cbTss > cbTssMax)
+ cbTss = cbTssMax;
+ DBGCVAR_SET_RANGE(&VarTssAddr, DBGCVAR_RANGE_BYTES, cbTss);
+
+ /*
+ * Read the TSS into a temporary buffer.
+ */
+ uint8_t abBuf[_64K];
+ size_t cbTssRead;
+ rc = DBGCCmdHlpMemRead(pCmdHlp, abBuf, cbTss, &VarTssAddr, &cbTssRead);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to read TSS at %Dv: %Rrc\n", &VarTssAddr, rc);
+ if (cbTssRead < cbTssMin)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to read essential parts of the TSS (read %zu, min %zu).\n",
+ cbTssRead, cbTssMin);
+ if (cbTssRead < cbTss)
+ memset(&abBuf[cbTssRead], 0xff, cbTss - cbTssRead);
+
+
+ /*
+ * Format the TSS.
+ */
+ uint16_t offIoBitmap;
+ switch (enmTssType)
+ {
+ case kTss16:
+ {
+ PCX86TSS16 pTss = (PCX86TSS16)&abBuf[0];
+ if (SelTss != UINT32_MAX)
+ DBGCCmdHlpPrintf(pCmdHlp, "%04x TSS16 at %Dv\n", SelTss, &VarTssAddr);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "TSS16 at %Dv\n", &VarTssAddr);
+ DBGCCmdHlpPrintf(pCmdHlp,
+ "ax=%04x bx=%04x cx=%04x dx=%04x si=%04x di=%04x\n"
+ "ip=%04x sp=%04x bp=%04x\n"
+ "cs=%04x ss=%04x ds=%04x es=%04x flags=%04x\n"
+ "ss:sp0=%04x:%04x ss:sp1=%04x:%04x ss:sp2=%04x:%04x\n"
+ "prev=%04x ldtr=%04x\n"
+ ,
+ pTss->ax, pTss->bx, pTss->cx, pTss->dx, pTss->si, pTss->di,
+ pTss->ip, pTss->sp, pTss->bp,
+ pTss->cs, pTss->ss, pTss->ds, pTss->es, pTss->flags,
+ pTss->ss0, pTss->sp0, pTss->ss1, pTss->sp1, pTss->ss2, pTss->sp2,
+ pTss->selPrev, pTss->selLdt);
+ if (pTss->cs != 0)
+ pCmdHlp->pfnExec(pCmdHlp, "u %04x:%04x L 0", pTss->cs, pTss->ip);
+ offIoBitmap = 0;
+ break;
+ }
+
+ case kTss32:
+ {
+ PCX86TSS32 pTss = (PCX86TSS32)&abBuf[0];
+ if (SelTss != UINT32_MAX)
+ DBGCCmdHlpPrintf(pCmdHlp, "%04x TSS32 at %Dv (min=%04x)\n", SelTss, &VarTssAddr, cbTssMin);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "TSS32 at %Dv (min=%04x)\n", &VarTssAddr, cbTssMin);
+ DBGCCmdHlpPrintf(pCmdHlp,
+ "eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n"
+ "eip=%08x esp=%08x ebp=%08x\n"
+ "cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x eflags=%08x\n"
+ "ss:esp0=%04x:%08x ss:esp1=%04x:%08x ss:esp2=%04x:%08x\n"
+ "prev=%04x ldtr=%04x cr3=%08x debug=%u iomap=%04x\n"
+ ,
+ pTss->eax, pTss->ebx, pTss->ecx, pTss->edx, pTss->esi, pTss->edi,
+ pTss->eip, pTss->esp, pTss->ebp,
+ pTss->cs, pTss->ss, pTss->ds, pTss->es, pTss->fs, pTss->gs, pTss->eflags,
+ pTss->ss0, pTss->esp0, pTss->ss1, pTss->esp1, pTss->ss2, pTss->esp2,
+ pTss->selPrev, pTss->selLdt, pTss->cr3, pTss->fDebugTrap, pTss->offIoBitmap);
+ if (pTss->cs != 0)
+ pCmdHlp->pfnExec(pCmdHlp, "u %04x:%08x L 0", pTss->cs, pTss->eip);
+ offIoBitmap = pTss->offIoBitmap;
+ break;
+ }
+
+ case kTss64:
+ {
+ PCX86TSS64 pTss = (PCX86TSS64)&abBuf[0];
+ if (SelTss != UINT32_MAX)
+ DBGCCmdHlpPrintf(pCmdHlp, "%04x TSS64 at %Dv (min=%04x)\n", SelTss, &VarTssAddr, cbTssMin);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "TSS64 at %Dv (min=%04x)\n", &VarTssAddr, cbTssMin);
+ DBGCCmdHlpPrintf(pCmdHlp,
+ "rsp0=%016RX64 rsp1=%016RX64 rsp2=%016RX64\n"
+ "ist1=%016RX64 ist2=%016RX64\n"
+ "ist3=%016RX64 ist4=%016RX64\n"
+ "ist5=%016RX64 ist6=%016RX64\n"
+ "ist7=%016RX64 iomap=%04x\n"
+ ,
+ pTss->rsp0, pTss->rsp1, pTss->rsp2,
+ pTss->ist1, pTss->ist2,
+ pTss->ist3, pTss->ist4,
+ pTss->ist5, pTss->ist6,
+ pTss->ist7, pTss->offIoBitmap);
+ offIoBitmap = pTss->offIoBitmap;
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR);
+ }
+
+ /*
+ * Dump the interrupt redirection bitmap.
+ */
+ if (enmTssType != kTss16)
+ {
+ if ( offIoBitmap > cbTssMin
+ && offIoBitmap < cbTss) /** @todo check exactly what the edge cases are here. */
+ {
+ if (offIoBitmap - cbTssMin >= 32)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "Interrupt redirection:\n");
+ uint8_t const *pbIntRedirBitmap = &abBuf[offIoBitmap - 32];
+ uint32_t iStart = 0;
+ bool fPrev = ASMBitTest(pbIntRedirBitmap, 0); /* LE/BE issue */
+ for (uint32_t i = 0; i < 256; i++)
+ {
+ bool fThis = ASMBitTest(pbIntRedirBitmap, i);
+ if (fThis != fPrev)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "%02x-%02x %s\n", iStart, i - 1, fPrev ? "Protected mode" : "Redirected");
+ fPrev = fThis;
+ iStart = i;
+ }
+ }
+ DBGCCmdHlpPrintf(pCmdHlp, "%02x-%02x %s\n", iStart, 255, fPrev ? "Protected mode" : "Redirected");
+ }
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "Invalid interrupt redirection bitmap size: %u (%#x), expected 32 bytes.\n",
+ offIoBitmap - cbTssMin, offIoBitmap - cbTssMin);
+ }
+ else if (offIoBitmap > 0)
+ DBGCCmdHlpPrintf(pCmdHlp, "No interrupt redirection bitmap (-%#x)\n", cbTssMin - offIoBitmap);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "No interrupt redirection bitmap\n");
+ }
+
+ /*
+ * Dump the I/O permission bitmap if present. The IOPM cannot start below offset 0x68
+ * (that applies to both 32-bit and 64-bit TSSs since their size is the same).
+ * Note that there is always one padding byte that is not technically part of the bitmap
+ * and "must have all bits set". It's not clear what happens when it doesn't. All ports
+ * not covered by the bitmap are considered to be not accessible.
+ */
+ if (enmTssType != kTss16)
+ {
+ if (offIoBitmap < cbTss && offIoBitmap >= 0x68)
+ {
+ uint32_t cPorts = RT_MIN((cbTss - offIoBitmap) * 8, _64K);
+ DBGCVAR VarAddr;
+ DBGCCmdHlpEval(pCmdHlp, &VarAddr, "%DV + %#x", &VarTssAddr, offIoBitmap);
+ DBGCCmdHlpPrintf(pCmdHlp, "I/O bitmap at %DV - %#x ports:\n", &VarAddr, cPorts);
+
+ uint8_t const *pbIoBitmap = &abBuf[offIoBitmap];
+ uint32_t iStart = 0;
+ bool fPrev = ASMBitTest(pbIoBitmap, 0);
+ uint32_t cLine = 0;
+ for (uint32_t i = 1; i < _64K; i++)
+ {
+ bool fThis = i < cPorts ? ASMBitTest(pbIoBitmap, i) : true;
+ if (fThis != fPrev)
+ {
+ cLine++;
+ DBGCCmdHlpPrintf(pCmdHlp, "%04x-%04x %s%s", iStart, i-1,
+ fPrev ? "GP" : "OK", (cLine % 6) == 0 ? "\n" : " ");
+ fPrev = fThis;
+ iStart = i;
+ }
+ }
+ DBGCCmdHlpPrintf(pCmdHlp, "%04x-%04x %s\n", iStart, _64K-1, fPrev ? "GP" : "OK");
+ }
+ else if (offIoBitmap > 0)
+ DBGCCmdHlpPrintf(pCmdHlp, "No I/O bitmap (-%#x)\n", cbTssMin - offIoBitmap);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "No I/O bitmap\n");
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFR3TYPEDUMP, The 'dti' command dumper callback.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpTypeInfoCallback(uint32_t off, const char *pszField, uint32_t iLvl,
+ const char *pszType, uint32_t fTypeFlags,
+ uint32_t cElements, void *pvUser)
+{
+ PDBGCCMDHLP pCmdHlp = (PDBGCCMDHLP)pvUser;
+
+ /* Pad with spaces to match the level. */
+ for (uint32_t i = 0; i < iLvl; i++)
+ DBGCCmdHlpPrintf(pCmdHlp, " ");
+
+ size_t cbWritten = 0;
+ DBGCCmdHlpPrintfEx(pCmdHlp, &cbWritten, "+0x%04x %s", off, pszField);
+ while (cbWritten < 32)
+ {
+ /* Fill with spaces to get proper aligning. */
+ DBGCCmdHlpPrintf(pCmdHlp, " ");
+ cbWritten++;
+ }
+
+ DBGCCmdHlpPrintf(pCmdHlp, ": ");
+ if (fTypeFlags & DBGFTYPEREGMEMBER_F_ARRAY)
+ DBGCCmdHlpPrintf(pCmdHlp, "[%u] ", cElements);
+ if (fTypeFlags & DBGFTYPEREGMEMBER_F_POINTER)
+ DBGCCmdHlpPrintf(pCmdHlp, "Ptr ");
+ DBGCCmdHlpPrintf(pCmdHlp, "%s\n", pszType);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dti' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpTypeInfo(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 1 || cArgs == 2);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[0].enmType == DBGCVAR_TYPE_STRING);
+ if (cArgs == 2)
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[1].enmType == DBGCVAR_TYPE_NUMBER);
+
+ uint32_t cLvlMax = cArgs == 2 ? (uint32_t)paArgs[1].u.u64Number : UINT32_MAX;
+ return DBGFR3TypeDumpEx(pUVM, paArgs[0].u.pszString, 0 /* fFlags */, cLvlMax,
+ dbgcCmdDumpTypeInfoCallback, pCmdHlp);
+}
+
+
+static void dbgcCmdDumpTypedValCallbackBuiltin(PDBGCCMDHLP pCmdHlp, DBGFTYPEBUILTIN enmType, size_t cbType,
+ PDBGFTYPEVALBUF pValBuf)
+{
+ switch (enmType)
+ {
+ case DBGFTYPEBUILTIN_UINT8:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RU8", pValBuf->u8);
+ break;
+ case DBGFTYPEBUILTIN_INT8:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RI8", pValBuf->i8);
+ break;
+ case DBGFTYPEBUILTIN_UINT16:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RU16", pValBuf->u16);
+ break;
+ case DBGFTYPEBUILTIN_INT16:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RI16", pValBuf->i16);
+ break;
+ case DBGFTYPEBUILTIN_UINT32:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RU32", pValBuf->u32);
+ break;
+ case DBGFTYPEBUILTIN_INT32:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RI32", pValBuf->i32);
+ break;
+ case DBGFTYPEBUILTIN_UINT64:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RU64", pValBuf->u64);
+ break;
+ case DBGFTYPEBUILTIN_INT64:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RI64", pValBuf->i64);
+ break;
+ case DBGFTYPEBUILTIN_PTR32:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RX32", pValBuf->GCPtr);
+ break;
+ case DBGFTYPEBUILTIN_PTR64:
+ DBGCCmdHlpPrintf(pCmdHlp, "%RX64", pValBuf->GCPtr);
+ break;
+ case DBGFTYPEBUILTIN_PTR:
+ if (cbType == sizeof(uint32_t))
+ DBGCCmdHlpPrintf(pCmdHlp, "%RX32", pValBuf->GCPtr);
+ else if (cbType == sizeof(uint64_t))
+ DBGCCmdHlpPrintf(pCmdHlp, "%RX64", pValBuf->GCPtr);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "<Unsupported pointer width %u>", cbType);
+ break;
+ case DBGFTYPEBUILTIN_SIZE:
+ if (cbType == sizeof(uint32_t))
+ DBGCCmdHlpPrintf(pCmdHlp, "%RU32", pValBuf->size);
+ else if (cbType == sizeof(uint64_t))
+ DBGCCmdHlpPrintf(pCmdHlp, "%RU64", pValBuf->size);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "<Unsupported size width %u>", cbType);
+ break;
+ case DBGFTYPEBUILTIN_FLOAT32:
+ case DBGFTYPEBUILTIN_FLOAT64:
+ case DBGFTYPEBUILTIN_COMPOUND:
+ default:
+ AssertMsgFailed(("Invalid built-in type: %d\n", enmType));
+ }
+}
+
+/**
+ * @callback_method_impl{FNDBGFR3TYPEDUMP, The 'dtv' command dumper callback.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpTypedValCallback(uint32_t off, const char *pszField, uint32_t iLvl,
+ DBGFTYPEBUILTIN enmType, size_t cbType,
+ PDBGFTYPEVALBUF pValBuf, uint32_t cValBufs,
+ void *pvUser)
+{
+ PDBGCCMDHLP pCmdHlp = (PDBGCCMDHLP)pvUser;
+
+ /* Pad with spaces to match the level. */
+ for (uint32_t i = 0; i < iLvl; i++)
+ DBGCCmdHlpPrintf(pCmdHlp, " ");
+
+ size_t cbWritten = 0;
+ DBGCCmdHlpPrintfEx(pCmdHlp, &cbWritten, "+0x%04x %s", off, pszField);
+ while (cbWritten < 32)
+ {
+ /* Fill with spaces to get proper aligning. */
+ DBGCCmdHlpPrintf(pCmdHlp, " ");
+ cbWritten++;
+ }
+
+ DBGCCmdHlpPrintf(pCmdHlp, ": ");
+ if (cValBufs > 1)
+ DBGCCmdHlpPrintf(pCmdHlp, "[%u] [ ", cValBufs);
+
+ for (uint32_t i = 0; i < cValBufs; i++)
+ {
+ dbgcCmdDumpTypedValCallbackBuiltin(pCmdHlp, enmType, cbType, pValBuf);
+ if (i < cValBufs - 1)
+ DBGCCmdHlpPrintf(pCmdHlp, " , ");
+ pValBuf++;
+ }
+
+ if (cValBufs > 1)
+ DBGCCmdHlpPrintf(pCmdHlp, " ]");
+ DBGCCmdHlpPrintf(pCmdHlp, "\n");
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'dtv' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdDumpTypedVal(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 2 || cArgs == 3);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[0].enmType == DBGCVAR_TYPE_STRING);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, DBGCVAR_ISGCPOINTER(paArgs[1].enmType));
+ if (cArgs == 3)
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[2].enmType == DBGCVAR_TYPE_NUMBER);
+
+ /*
+ * Make DBGF address and fix the range.
+ */
+ DBGFADDRESS Address;
+ int rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, &paArgs[1], &Address);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "VarToDbgfAddr(,%Dv,)\n", &paArgs[1]);
+
+ uint32_t cLvlMax = cArgs == 3 ? (uint32_t)paArgs[2].u.u64Number : UINT32_MAX;
+ return DBGFR3TypeValDumpEx(pUVM, &Address, paArgs[0].u.pszString, 0 /* fFlags */, cLvlMax,
+ dbgcCmdDumpTypedValCallback, pCmdHlp);
+}
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'm' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdMemoryInfo(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGCCmdHlpPrintf(pCmdHlp, "Address: %DV\n", &paArgs[0]);
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ return dbgcCmdDumpPageHierarchy(pCmd, pCmdHlp, pUVM, paArgs, cArgs);
+}
+
+
+/**
+ * Converts one or more variables into a byte buffer for a
+ * given unit size.
+ *
+ * @returns VBox status codes:
+ * @retval VERR_TOO_MUCH_DATA if the buffer is too small, bitched.
+ * @retval VERR_INTERNAL_ERROR on bad variable type, bitched.
+ * @retval VINF_SUCCESS on success.
+ *
+ * @param pCmdHlp The command helper callback table.
+ * @param pvBuf The buffer to convert into.
+ * @param pcbBuf The buffer size on input. The size of the result on output.
+ * @param cbUnit The unit size to apply when converting.
+ * The high bit is used to indicate unicode string.
+ * @param paVars The array of variables to convert.
+ * @param cVars The number of variables.
+ */
+int dbgcVarsToBytes(PDBGCCMDHLP pCmdHlp, void *pvBuf, uint32_t *pcbBuf, size_t cbUnit, PCDBGCVAR paVars, unsigned cVars)
+{
+ union
+ {
+ uint8_t *pu8;
+ uint16_t *pu16;
+ uint32_t *pu32;
+ uint64_t *pu64;
+ } u, uEnd;
+ u.pu8 = (uint8_t *)pvBuf;
+ uEnd.pu8 = u.pu8 + *pcbBuf;
+
+ unsigned i;
+ for (i = 0; i < cVars && u.pu8 < uEnd.pu8; i++)
+ {
+ switch (paVars[i].enmType)
+ {
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ case DBGCVAR_TYPE_NUMBER:
+ {
+ uint64_t u64 = paVars[i].u.u64Number;
+ switch (cbUnit & 0x1f)
+ {
+ case 1:
+ do
+ {
+ *u.pu8++ = u64;
+ u64 >>= 8;
+ } while (u64);
+ break;
+ case 2:
+ do
+ {
+ *u.pu16++ = u64;
+ u64 >>= 16;
+ } while (u64);
+ break;
+ case 4:
+ *u.pu32++ = u64;
+ u64 >>= 32;
+ if (u64)
+ *u.pu32++ = u64;
+ break;
+ case 8:
+ *u.pu64++ = u64;
+ break;
+ }
+ break;
+ }
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ {
+ const char *psz = paVars[i].u.pszString;
+ size_t cbString = strlen(psz);
+ if (cbUnit & RT_BIT_32(31))
+ {
+ /* Explode char to unit. */
+ if (cbString > (uintptr_t)(uEnd.pu8 - u.pu8) * (cbUnit & 0x1f))
+ {
+ pCmdHlp->pfnVBoxError(pCmdHlp, VERR_TOO_MUCH_DATA, "Max %d bytes.\n", uEnd.pu8 - (uint8_t *)pvBuf);
+ return VERR_TOO_MUCH_DATA;
+ }
+ while (*psz)
+ {
+ switch (cbUnit & 0x1f)
+ {
+ case 1: *u.pu8++ = *psz; break;
+ case 2: *u.pu16++ = *psz; break;
+ case 4: *u.pu32++ = *psz; break;
+ case 8: *u.pu64++ = *psz; break;
+ }
+ psz++;
+ }
+ }
+ else
+ {
+ /* Raw copy with zero padding if the size isn't aligned. */
+ if (cbString > (uintptr_t)(uEnd.pu8 - u.pu8))
+ {
+ pCmdHlp->pfnVBoxError(pCmdHlp, VERR_TOO_MUCH_DATA, "Max %d bytes.\n", uEnd.pu8 - (uint8_t *)pvBuf);
+ return VERR_TOO_MUCH_DATA;
+ }
+
+ size_t cbCopy = cbString & ~(cbUnit - 1);
+ memcpy(u.pu8, psz, cbCopy);
+ u.pu8 += cbCopy;
+ psz += cbCopy;
+
+ size_t cbReminder = cbString & (cbUnit - 1);
+ if (cbReminder)
+ {
+ memcpy(u.pu8, psz, cbString & (cbUnit - 1));
+ memset(u.pu8 + cbReminder, 0, cbUnit - cbReminder);
+ u.pu8 += cbUnit;
+ }
+ }
+ break;
+ }
+
+ default:
+ *pcbBuf = u.pu8 - (uint8_t *)pvBuf;
+ pCmdHlp->pfnVBoxError(pCmdHlp, VERR_INTERNAL_ERROR,
+ "i=%d enmType=%d\n", i, paVars[i].enmType);
+ return VERR_INTERNAL_ERROR;
+ }
+ }
+ *pcbBuf = u.pu8 - (uint8_t *)pvBuf;
+ if (i != cVars)
+ {
+ pCmdHlp->pfnVBoxError(pCmdHlp, VERR_TOO_MUCH_DATA, "Max %d bytes.\n", uEnd.pu8 - (uint8_t *)pvBuf);
+ return VERR_TOO_MUCH_DATA;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'eb'\, 'ew'\, 'ed' and 'eq' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdEditMem(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs >= 2);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, DBGCVAR_ISPOINTER(paArgs[0].enmType));
+ for (unsigned iArg = 1; iArg < cArgs; iArg++)
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, paArgs[iArg].enmType == DBGCVAR_TYPE_NUMBER);
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Figure out the element size.
+ */
+ unsigned cbElement;
+ switch (pCmd->pszCmd[1])
+ {
+ default:
+ case 'b': cbElement = 1; break;
+ case 'w': cbElement = 2; break;
+ case 'd': cbElement = 4; break;
+ case 'q': cbElement = 8; break;
+ }
+
+ /*
+ * Do setting.
+ */
+ DBGCVAR Addr = paArgs[0];
+ for (unsigned iArg = 1;;)
+ {
+ size_t cbWritten;
+ int rc = pCmdHlp->pfnMemWrite(pCmdHlp, &paArgs[iArg].u, cbElement, &Addr, &cbWritten);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "Writing memory at %DV.\n", &Addr);
+ if (cbWritten != cbElement)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Only wrote %u out of %u bytes!\n", cbWritten, cbElement);
+
+ /* advance. */
+ iArg++;
+ if (iArg >= cArgs)
+ break;
+ rc = DBGCCmdHlpEval(pCmdHlp, &Addr, "%Dv + %#x", &Addr, cbElement);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "%%(%Dv)", &paArgs[0]);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Executes the search.
+ *
+ * @returns VBox status code.
+ * @param pCmdHlp The command helpers.
+ * @param pUVM The user mode VM handle.
+ * @param pAddress The address to start searching from. (undefined on output)
+ * @param cbRange The address range to search. Must not wrap.
+ * @param pabBytes The byte pattern to search for.
+ * @param cbBytes The size of the pattern.
+ * @param cbUnit The search unit.
+ * @param cMaxHits The max number of hits.
+ * @param pResult Where to store the result if it's a function invocation.
+ */
+static int dbgcCmdWorkerSearchMemDoIt(PDBGCCMDHLP pCmdHlp, PUVM pUVM, PDBGFADDRESS pAddress, RTGCUINTPTR cbRange,
+ const uint8_t *pabBytes, uint32_t cbBytes,
+ uint32_t cbUnit, uint64_t cMaxHits, PDBGCVAR pResult)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Do the search.
+ */
+ uint64_t cHits = 0;
+ for (;;)
+ {
+ /* search */
+ DBGFADDRESS HitAddress;
+ int rc = DBGFR3MemScan(pUVM, pDbgc->idCpu, pAddress, cbRange, 1, pabBytes, cbBytes, &HitAddress);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_DBGF_MEM_NOT_FOUND)
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "DBGFR3MemScan\n");
+
+ /* update the current address so we can save it (later). */
+ pAddress->off += cbRange;
+ pAddress->FlatPtr += cbRange;
+ cbRange = 0;
+ break;
+ }
+
+ /* report result */
+ DBGCVAR VarCur;
+ rc = DBGCCmdHlpVarFromDbgfAddr(pCmdHlp, &HitAddress, &VarCur);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGCCmdHlpVarFromDbgfAddr\n");
+ if (!pResult)
+ pCmdHlp->pfnExec(pCmdHlp, "db %DV LB 10", &VarCur);
+ else
+ DBGCVAR_ASSIGN(pResult, &VarCur);
+
+ /* advance */
+ cbRange -= HitAddress.FlatPtr - pAddress->FlatPtr;
+ *pAddress = HitAddress;
+ pAddress->FlatPtr += cbBytes;
+ pAddress->off += cbBytes;
+ if (cbRange <= cbBytes)
+ {
+ cbRange = 0;
+ break;
+ }
+ cbRange -= cbBytes;
+
+ if (++cHits >= cMaxHits)
+ {
+ /// @todo save the search.
+ break;
+ }
+ }
+
+ /*
+ * Save the search so we can resume it...
+ */
+ if (pDbgc->abSearch != pabBytes)
+ {
+ memcpy(pDbgc->abSearch, pabBytes, cbBytes);
+ pDbgc->cbSearch = cbBytes;
+ pDbgc->cbSearchUnit = cbUnit;
+ }
+ pDbgc->cMaxSearchHits = cMaxHits;
+ pDbgc->SearchAddr = *pAddress;
+ pDbgc->cbSearchRange = cbRange;
+
+ return cHits ? VINF_SUCCESS : VERR_DBGC_COMMAND_FAILED;
+}
+
+
+/**
+ * Resumes the previous search.
+ *
+ * @returns VBox status code.
+ * @param pCmdHlp Pointer to the command helper functions.
+ * @param pUVM The user mode VM handle.
+ * @param pResult Where to store the result of a function invocation.
+ */
+static int dbgcCmdWorkerSearchMemResume(PDBGCCMDHLP pCmdHlp, PUVM pUVM, PDBGCVAR pResult)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Make sure there is a previous command.
+ */
+ if (!pDbgc->cbSearch)
+ {
+ DBGCCmdHlpPrintf(pCmdHlp, "Error: No previous search\n");
+ return VERR_DBGC_COMMAND_FAILED;
+ }
+
+ /*
+ * Make range and address adjustments.
+ */
+ DBGFADDRESS Address = pDbgc->SearchAddr;
+ if (Address.FlatPtr == ~(RTGCUINTPTR)0)
+ {
+ Address.FlatPtr -= Address.off;
+ Address.off = 0;
+ }
+
+ RTGCUINTPTR cbRange = pDbgc->cbSearchRange;
+ if (!cbRange)
+ cbRange = ~(RTGCUINTPTR)0;
+ if (Address.FlatPtr + cbRange < pDbgc->SearchAddr.FlatPtr)
+ cbRange = ~(RTGCUINTPTR)0 - pDbgc->SearchAddr.FlatPtr + !!pDbgc->SearchAddr.FlatPtr;
+
+ return dbgcCmdWorkerSearchMemDoIt(pCmdHlp, pUVM, &Address, cbRange, pDbgc->abSearch, pDbgc->cbSearch,
+ pDbgc->cbSearchUnit, pDbgc->cMaxSearchHits, pResult);
+}
+
+
+/**
+ * Search memory, worker for the 's' and 's?' functions.
+ *
+ * @returns VBox status code.
+ * @param pCmdHlp Pointer to the command helper functions.
+ * @param pUVM The user mode VM handle.
+ * @param pAddress Where to start searching. If no range, search till end of address space.
+ * @param cMaxHits The maximum number of hits.
+ * @param chType The search type.
+ * @param paPatArgs The pattern variable array.
+ * @param cPatArgs Number of pattern variables.
+ * @param pResult Where to store the result of a function invocation.
+ */
+static int dbgcCmdWorkerSearchMem(PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR pAddress, uint64_t cMaxHits, char chType,
+ PCDBGCVAR paPatArgs, unsigned cPatArgs, PDBGCVAR pResult)
+{
+ if (pResult)
+ DBGCVAR_INIT_GC_FLAT(pResult, 0);
+
+ /*
+ * Convert the search pattern into bytes and DBGFR3MemScan can deal with.
+ */
+ uint32_t cbUnit;
+ switch (chType)
+ {
+ case 'a':
+ case 'b': cbUnit = 1; break;
+ case 'u': cbUnit = 2 | RT_BIT_32(31); break;
+ case 'w': cbUnit = 2; break;
+ case 'd': cbUnit = 4; break;
+ case 'q': cbUnit = 8; break;
+ default:
+ return pCmdHlp->pfnVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "chType=%c\n", chType);
+ }
+ uint8_t abBytes[RT_SIZEOFMEMB(DBGC, abSearch)];
+ uint32_t cbBytes = sizeof(abBytes);
+ int rc = dbgcVarsToBytes(pCmdHlp, abBytes, &cbBytes, cbUnit, paPatArgs, cPatArgs);
+ if (RT_FAILURE(rc))
+ return VERR_DBGC_COMMAND_FAILED;
+
+ /*
+ * Make DBGF address and fix the range.
+ */
+ DBGFADDRESS Address;
+ rc = pCmdHlp->pfnVarToDbgfAddr(pCmdHlp, pAddress, &Address);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "VarToDbgfAddr(,%Dv,)\n", pAddress);
+
+ RTGCUINTPTR cbRange;
+ switch (pAddress->enmRangeType)
+ {
+ case DBGCVAR_RANGE_BYTES:
+ cbRange = pAddress->u64Range;
+ if (cbRange != pAddress->u64Range)
+ cbRange = ~(RTGCUINTPTR)0;
+ break;
+
+ case DBGCVAR_RANGE_ELEMENTS:
+ cbRange = (RTGCUINTPTR)(pAddress->u64Range * cbUnit);
+ if ( cbRange != pAddress->u64Range * cbUnit
+ || cbRange < pAddress->u64Range)
+ cbRange = ~(RTGCUINTPTR)0;
+ break;
+
+ default:
+ cbRange = ~(RTGCUINTPTR)0;
+ break;
+ }
+ if (Address.FlatPtr + cbRange < Address.FlatPtr)
+ cbRange = ~(RTGCUINTPTR)0 - Address.FlatPtr + !!Address.FlatPtr;
+
+ /*
+ * Ok, do it.
+ */
+ return dbgcCmdWorkerSearchMemDoIt(pCmdHlp, pUVM, &Address, cbRange, abBytes, cbBytes, cbUnit, cMaxHits, pResult);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 's' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdSearchMem(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF2(pCmd, paArgs);
+
+ /* check that the parser did what it's supposed to do. */
+ //if ( cArgs <= 2
+ // && paArgs[0].enmType != DBGCVAR_TYPE_STRING)
+ // return DBGCCmdHlpPrintf(pCmdHlp, "parser error\n");
+
+ /*
+ * Repeat previous search?
+ */
+ if (cArgs == 0)
+ return dbgcCmdWorkerSearchMemResume(pCmdHlp, pUVM, NULL);
+
+ /*
+ * Parse arguments.
+ */
+
+ return -1;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 's?' command.}
+ */
+static DECLCALLBACK(int) dbgcCmdSearchMemType(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /* check that the parser did what it's supposed to do. */
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs >= 2 && DBGCVAR_ISGCPOINTER(paArgs[0].enmType));
+ return dbgcCmdWorkerSearchMem(pCmdHlp, pUVM, &paArgs[0], 25, pCmd->pszCmd[1], paArgs + 1, cArgs - 1, NULL);
+}
+
+
+/**
+ * Matching function for interrupts event names.
+ *
+ * This parses the interrupt number and length.
+ *
+ * @returns True if match, false if not.
+ * @param pPattern The user specified pattern to match.
+ * @param pszEvtName The event name.
+ * @param pCmdHlp Command helpers for warning about malformed stuff.
+ * @param piFirst Where to return start interrupt number on success.
+ * @param pcInts Where to return the number of interrupts on success.
+ */
+static bool dbgcEventIsMatchingInt(PCDBGCVAR pPattern, const char *pszEvtName, PDBGCCMDHLP pCmdHlp,
+ uint8_t *piFirst, uint16_t *pcInts)
+{
+ /*
+ * Ignore trailing hex digits when comparing with the event base name.
+ */
+ const char *pszPattern = pPattern->u.pszString;
+ const char *pszEnd = RTStrEnd(pszPattern, RTSTR_MAX);
+ while ( (uintptr_t)pszEnd > (uintptr_t)pszPattern
+ && RT_C_IS_XDIGIT(pszEnd[-1]))
+ pszEnd -= 1;
+ if (RTStrSimplePatternNMatch(pszPattern, pszEnd - pszPattern, pszEvtName, RTSTR_MAX))
+ {
+ /*
+ * Parse the index and length.
+ */
+ if (!*pszEnd)
+ *piFirst = 0;
+ else
+ {
+ int rc = RTStrToUInt8Full(pszEnd, 16, piFirst);
+ if (rc != VINF_SUCCESS)
+ {
+ if (RT_FAILURE(rc))
+ *piFirst = 0;
+ DBGCCmdHlpPrintf(pCmdHlp, "Warning: %Rrc parsing '%s' - interpreting it as %#x\n", rc, pszEnd, *piFirst);
+ }
+ }
+
+ if (pPattern->enmRangeType == DBGCVAR_RANGE_NONE)
+ *pcInts = 1;
+ else
+ *pcInts = RT_MAX(RT_MIN((uint16_t)pPattern->u64Range, 256 - *piFirst), 1);
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Updates a DBGC event config.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_MEMORY.
+ * @param ppEvtCfg The event configuration entry to update.
+ * @param pszCmd The new command. Leave command alone if NULL.
+ * @param enmEvtState The new event state.
+ * @param fChangeCmdOnly Whether to only update the command.
+ */
+static int dbgcEventUpdate(PDBGCEVTCFG *ppEvtCfg, const char *pszCmd, DBGCEVTSTATE enmEvtState, bool fChangeCmdOnly)
+{
+ PDBGCEVTCFG pEvtCfg = *ppEvtCfg;
+
+ /*
+ * If we've got a command string, update the command too.
+ */
+ if (pszCmd)
+ {
+ size_t cchCmd = strlen(pszCmd);
+ if ( !cchCmd
+ && ( !fChangeCmdOnly
+ ? enmEvtState == kDbgcEvtState_Disabled
+ : !pEvtCfg || pEvtCfg->enmState == kDbgcEvtState_Disabled))
+ {
+ /* NULL entry is fine if no command and disabled. */
+ RTMemFree(pEvtCfg);
+ *ppEvtCfg = NULL;
+ }
+ else
+ {
+ if (!pEvtCfg || pEvtCfg->cchCmd < cchCmd)
+ {
+ RTMemFree(pEvtCfg);
+ *ppEvtCfg = pEvtCfg = (PDBGCEVTCFG)RTMemAlloc(RT_UOFFSETOF_DYN(DBGCEVTCFG, szCmd[cchCmd + 1]));
+ if (!pEvtCfg)
+ return VERR_NO_MEMORY;
+ }
+ pEvtCfg->enmState = enmEvtState;
+ pEvtCfg->cchCmd = cchCmd;
+ memcpy(pEvtCfg->szCmd, pszCmd, cchCmd + 1);
+ }
+ }
+ /*
+ * Update existing or enable new. If NULL and not enabled, we can keep it that way.
+ */
+ else if (pEvtCfg || enmEvtState != kDbgcEvtState_Disabled)
+ {
+ if (!pEvtCfg)
+ {
+ *ppEvtCfg = pEvtCfg = (PDBGCEVTCFG)RTMemAlloc(sizeof(DBGCEVTCFG));
+ if (!pEvtCfg)
+ return VERR_NO_MEMORY;
+ pEvtCfg->cchCmd = 0;
+ pEvtCfg->szCmd[0] = '\0';
+ }
+ pEvtCfg->enmState = enmEvtState;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Record one settings change for a plain event.
+ *
+ * @returns The new @a cIntCfgs value.
+ * @param paEventCfgs The event setttings array. Must have DBGFEVENT_END
+ * entries.
+ * @param cEventCfgs The current number of entries in @a paEventCfgs.
+ * @param enmType The event to change the settings for.
+ * @param enmEvtState The new event state.
+ * @param iSxEvt Index into the g_aDbgcSxEvents array.
+ *
+ * @remarks We use abUnused[0] for the enmEvtState, while abUnused[1] and
+ * abUnused[2] are used for iSxEvt.
+ */
+static uint32_t dbgcEventAddPlainConfig(PDBGFEVENTCONFIG paEventCfgs, uint32_t cEventCfgs, DBGFEVENTTYPE enmType,
+ DBGCEVTSTATE enmEvtState, uint16_t iSxEvt)
+{
+ uint32_t iCfg;
+ for (iCfg = 0; iCfg < cEventCfgs; iCfg++)
+ if (paEventCfgs[iCfg].enmType == enmType)
+ break;
+ if (iCfg == cEventCfgs)
+ {
+ Assert(cEventCfgs < DBGFEVENT_END);
+ paEventCfgs[iCfg].enmType = enmType;
+ cEventCfgs++;
+ }
+ paEventCfgs[iCfg].fEnabled = enmEvtState > kDbgcEvtState_Disabled;
+ paEventCfgs[iCfg].abUnused[0] = enmEvtState;
+ paEventCfgs[iCfg].abUnused[1] = (uint8_t)iSxEvt;
+ paEventCfgs[iCfg].abUnused[2] = (uint8_t)(iSxEvt >> 8);
+ return cEventCfgs;
+}
+
+
+/**
+ * Record one or more interrupt event config changes.
+ *
+ * @returns The new @a cIntCfgs value.
+ * @param paIntCfgs Interrupt confiruation array. Must have 256 entries.
+ * @param cIntCfgs The current number of entries in @a paIntCfgs.
+ * @param iInt The interrupt number to start with.
+ * @param cInts The number of interrupts to change.
+ * @param pszName The settings name (hwint/swint).
+ * @param enmEvtState The new event state.
+ * @param bIntOp The new DBGF interrupt state.
+ */
+static uint32_t dbgcEventAddIntConfig(PDBGFINTERRUPTCONFIG paIntCfgs, uint32_t cIntCfgs, uint8_t iInt, uint16_t cInts,
+ const char *pszName, DBGCEVTSTATE enmEvtState, uint8_t bIntOp)
+{
+ bool const fHwInt = *pszName == 'h';
+
+ bIntOp |= (uint8_t)enmEvtState << 4;
+ uint8_t const bSoftState = !fHwInt ? bIntOp : DBGFINTERRUPTSTATE_DONT_TOUCH;
+ uint8_t const bHardState = fHwInt ? bIntOp : DBGFINTERRUPTSTATE_DONT_TOUCH;
+
+ while (cInts > 0)
+ {
+ uint32_t iCfg;
+ for (iCfg = 0; iCfg < cIntCfgs; iCfg++)
+ if (paIntCfgs[iCfg].iInterrupt == iInt)
+ break;
+ if (iCfg == cIntCfgs)
+ break;
+ if (fHwInt)
+ paIntCfgs[iCfg].enmHardState = bHardState;
+ else
+ paIntCfgs[iCfg].enmSoftState = bSoftState;
+ iInt++;
+ cInts--;
+ }
+
+ while (cInts > 0)
+ {
+ Assert(cIntCfgs < 256);
+ paIntCfgs[cIntCfgs].iInterrupt = iInt;
+ paIntCfgs[cIntCfgs].enmHardState = bHardState;
+ paIntCfgs[cIntCfgs].enmSoftState = bSoftState;
+ cIntCfgs++;
+ iInt++;
+ cInts--;
+ }
+
+ return cIntCfgs;
+}
+
+
+/**
+ * Applies event settings changes to DBGC and DBGF.
+ *
+ * @returns VBox status code (fully bitched)
+ * @param pCmdHlp The command helpers.
+ * @param pUVM The user mode VM handle.
+ * @param paIntCfgs Interrupt configuration array. We use the upper 4
+ * bits of the settings for the DBGCEVTSTATE. This
+ * will be cleared.
+ * @param cIntCfgs Number of interrupt configuration changes.
+ * @param paEventCfgs The generic event configuration array. We use the
+ * abUnused[0] member for the DBGCEVTSTATE, and
+ * abUnused[2:1] for the g_aDbgcSxEvents index.
+ * @param cEventCfgs The number of generic event settings changes.
+ * @param pszCmd The commands to associate with the changed events.
+ * If this is NULL, don't touch the command.
+ * @param fChangeCmdOnly Whether to only change the commands (sx-).
+ */
+static int dbgcEventApplyChanges(PDBGCCMDHLP pCmdHlp, PUVM pUVM, PDBGFINTERRUPTCONFIG paIntCfgs, uint32_t cIntCfgs,
+ PCDBGFEVENTCONFIG paEventCfgs, uint32_t cEventCfgs, const char *pszCmd, bool fChangeCmdOnly)
+{
+ int rc;
+
+ /*
+ * Apply changes to DBGC. This can only fail with out of memory error.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ if (cIntCfgs)
+ for (uint32_t iCfg = 0; iCfg < cIntCfgs; iCfg++)
+ {
+ DBGCEVTSTATE enmEvtState = (DBGCEVTSTATE)(paIntCfgs[iCfg].enmHardState >> 4);
+ paIntCfgs[iCfg].enmHardState &= 0xf;
+ if (paIntCfgs[iCfg].enmHardState != DBGFINTERRUPTSTATE_DONT_TOUCH)
+ {
+ rc = dbgcEventUpdate(&pDbgc->apHardInts[paIntCfgs[iCfg].iInterrupt], pszCmd, enmEvtState, fChangeCmdOnly);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ enmEvtState = (DBGCEVTSTATE)(paIntCfgs[iCfg].enmSoftState >> 4);
+ paIntCfgs[iCfg].enmSoftState &= 0xf;
+ if (paIntCfgs[iCfg].enmSoftState != DBGFINTERRUPTSTATE_DONT_TOUCH)
+ {
+ rc = dbgcEventUpdate(&pDbgc->apSoftInts[paIntCfgs[iCfg].iInterrupt], pszCmd, enmEvtState, fChangeCmdOnly);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+
+ if (cEventCfgs)
+ {
+ for (uint32_t iCfg = 0; iCfg < cEventCfgs; iCfg++)
+ {
+ Assert((unsigned)paEventCfgs[iCfg].enmType < RT_ELEMENTS(pDbgc->apEventCfgs));
+ uint16_t iSxEvt = RT_MAKE_U16(paEventCfgs[iCfg].abUnused[1], paEventCfgs[iCfg].abUnused[2]);
+ Assert(iSxEvt < RT_ELEMENTS(g_aDbgcSxEvents));
+ rc = dbgcEventUpdate(&pDbgc->apEventCfgs[iSxEvt], pszCmd, (DBGCEVTSTATE)paEventCfgs[iCfg].abUnused[0], fChangeCmdOnly);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+
+ /*
+ * Apply changes to DBGF.
+ */
+ if (!fChangeCmdOnly)
+ {
+ if (cIntCfgs)
+ {
+ rc = DBGFR3InterruptConfigEx(pUVM, paIntCfgs, cIntCfgs);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3InterruptConfigEx: %Rrc\n", rc);
+ }
+ if (cEventCfgs)
+ {
+ rc = DBGFR3EventConfigEx(pUVM, paEventCfgs, cEventCfgs);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpVBoxError(pCmdHlp, rc, "DBGFR3EventConfigEx: %Rrc\n", rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'sx[eni-]' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdEventCtrl(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Figure out which command this is.
+ */
+ uint8_t bIntOp;
+ DBGCEVTSTATE enmEvtState;
+ bool fChangeCmdOnly;
+ switch (pCmd->pszCmd[2])
+ {
+ case 'e': bIntOp = DBGFINTERRUPTSTATE_ENABLED; enmEvtState = kDbgcEvtState_Enabled; fChangeCmdOnly = false; break;
+ case 'n': bIntOp = DBGFINTERRUPTSTATE_ENABLED; enmEvtState = kDbgcEvtState_Notify; fChangeCmdOnly = false; break;
+ case '-': bIntOp = DBGFINTERRUPTSTATE_ENABLED; enmEvtState = kDbgcEvtState_Invalid; fChangeCmdOnly = true; break;
+ case 'i': bIntOp = DBGFINTERRUPTSTATE_DISABLED; enmEvtState = kDbgcEvtState_Disabled; fChangeCmdOnly = false; break;
+ default:
+ return DBGCCmdHlpVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "pszCmd=%s\n", pCmd->pszCmd);
+ }
+
+ /*
+ * Command option.
+ */
+ unsigned iArg = 0;
+ const char *pszCmd = NULL;
+ if ( cArgs >= iArg + 2
+ && paArgs[iArg].enmType == DBGCVAR_TYPE_STRING
+ && paArgs[iArg + 1].enmType == DBGCVAR_TYPE_STRING
+ && strcmp(paArgs[iArg].u.pszString, "-c") == 0)
+ {
+ pszCmd = paArgs[iArg + 1].u.pszString;
+ iArg += 2;
+ }
+ if (fChangeCmdOnly && !pszCmd)
+ return DBGCCmdHlpVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "The 'sx-' requires the '-c cmd' arguments.\n");
+
+ /*
+ * The remaining arguments are event specifiers to which the operation should be applied.
+ */
+ uint32_t cIntCfgs = 0;
+ DBGFINTERRUPTCONFIG aIntCfgs[256];
+ uint32_t cEventCfgs = 0;
+ DBGFEVENTCONFIG aEventCfgs[DBGFEVENT_END];
+
+ for (; iArg < cArgs; iArg++)
+ {
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, iArg, paArgs[iArg].enmType == DBGCVAR_TYPE_STRING
+ || paArgs[iArg].enmType == DBGCVAR_TYPE_SYMBOL);
+ uint32_t cHits = 0;
+ for (uint32_t iEvt = 0; iEvt < RT_ELEMENTS(g_aDbgcSxEvents); iEvt++)
+ if (g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Plain)
+ {
+ if ( RTStrSimplePatternMatch(paArgs[iArg].u.pszString, g_aDbgcSxEvents[iEvt].pszName)
+ || ( g_aDbgcSxEvents[iEvt].pszAltNm
+ && RTStrSimplePatternMatch(paArgs[iArg].u.pszString, g_aDbgcSxEvents[iEvt].pszAltNm)) )
+ {
+ cEventCfgs = dbgcEventAddPlainConfig(aEventCfgs, cEventCfgs, g_aDbgcSxEvents[iEvt].enmType,
+ enmEvtState, iEvt);
+ cHits++;
+ }
+ }
+ else
+ {
+ Assert(g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Interrupt);
+ uint8_t iInt;
+ uint16_t cInts;
+ if (dbgcEventIsMatchingInt(&paArgs[iArg], g_aDbgcSxEvents[iEvt].pszName, pCmdHlp, &iInt, &cInts))
+ {
+ cIntCfgs = dbgcEventAddIntConfig(aIntCfgs, cIntCfgs, iInt, cInts, g_aDbgcSxEvents[iEvt].pszName,
+ enmEvtState, bIntOp);
+ cHits++;
+ }
+ }
+ if (!cHits)
+ return DBGCCmdHlpVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "Unknown event: '%s'\n", paArgs[iArg].u.pszString);
+ }
+
+ /*
+ * Apply the changes.
+ */
+ return dbgcEventApplyChanges(pCmdHlp, pUVM, aIntCfgs, cIntCfgs, aEventCfgs, cEventCfgs, pszCmd, fChangeCmdOnly);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'sxr' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdEventCtrlReset(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF1(pCmd);
+ uint32_t cEventCfgs = 0;
+ DBGFEVENTCONFIG aEventCfgs[DBGFEVENT_END];
+ uint32_t cIntCfgs = 0;
+ DBGFINTERRUPTCONFIG aIntCfgs[256];
+
+ if (cArgs == 0)
+ {
+ /*
+ * All events.
+ */
+ for (uint32_t iInt = 0; iInt < 256; iInt++)
+ {
+ aIntCfgs[iInt].iInterrupt = iInt;
+ aIntCfgs[iInt].enmHardState = DBGFINTERRUPTSTATE_DONT_TOUCH;
+ aIntCfgs[iInt].enmSoftState = DBGFINTERRUPTSTATE_DONT_TOUCH;
+ }
+ cIntCfgs = 256;
+
+ for (uint32_t iEvt = 0; iEvt < RT_ELEMENTS(g_aDbgcSxEvents); iEvt++)
+ if (g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Plain)
+ {
+ aEventCfgs[cEventCfgs].enmType = g_aDbgcSxEvents[iEvt].enmType;
+ aEventCfgs[cEventCfgs].fEnabled = g_aDbgcSxEvents[iEvt].enmDefault > kDbgcEvtState_Disabled;
+ aEventCfgs[cEventCfgs].abUnused[0] = g_aDbgcSxEvents[iEvt].enmDefault;
+ aEventCfgs[cEventCfgs].abUnused[1] = (uint8_t)iEvt;
+ aEventCfgs[cEventCfgs].abUnused[2] = (uint8_t)(iEvt >> 8);
+ cEventCfgs++;
+ }
+ else
+ {
+ uint8_t const bState = ( g_aDbgcSxEvents[iEvt].enmDefault > kDbgcEvtState_Disabled
+ ? DBGFINTERRUPTSTATE_ENABLED : DBGFINTERRUPTSTATE_DISABLED)
+ | ((uint8_t)g_aDbgcSxEvents[iEvt].enmDefault << 4);
+ if (strcmp(g_aDbgcSxEvents[iEvt].pszName, "hwint") == 0)
+ for (uint32_t iInt = 0; iInt < 256; iInt++)
+ aIntCfgs[iInt].enmHardState = bState;
+ else
+ for (uint32_t iInt = 0; iInt < 256; iInt++)
+ aIntCfgs[iInt].enmSoftState = bState;
+ }
+ }
+ else
+ {
+ /*
+ * Selected events.
+ */
+ for (uint32_t iArg = 0; iArg < cArgs; iArg++)
+ {
+ unsigned cHits = 0;
+ for (uint32_t iEvt = 0; iEvt < RT_ELEMENTS(g_aDbgcSxEvents); iEvt++)
+ if (g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Plain)
+ {
+ if ( RTStrSimplePatternMatch(paArgs[iArg].u.pszString, g_aDbgcSxEvents[iEvt].pszName)
+ || ( g_aDbgcSxEvents[iEvt].pszAltNm
+ && RTStrSimplePatternMatch(paArgs[iArg].u.pszString, g_aDbgcSxEvents[iEvt].pszAltNm)) )
+ {
+ cEventCfgs = dbgcEventAddPlainConfig(aEventCfgs, cEventCfgs, g_aDbgcSxEvents[iEvt].enmType,
+ g_aDbgcSxEvents[iEvt].enmDefault, iEvt);
+ cHits++;
+ }
+ }
+ else
+ {
+ Assert(g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Interrupt);
+ uint8_t iInt;
+ uint16_t cInts;
+ if (dbgcEventIsMatchingInt(&paArgs[iArg], g_aDbgcSxEvents[iEvt].pszName, pCmdHlp, &iInt, &cInts))
+ {
+ cIntCfgs = dbgcEventAddIntConfig(aIntCfgs, cIntCfgs, iInt, cInts, g_aDbgcSxEvents[iEvt].pszName,
+ g_aDbgcSxEvents[iEvt].enmDefault,
+ g_aDbgcSxEvents[iEvt].enmDefault > kDbgcEvtState_Disabled
+ ? DBGFINTERRUPTSTATE_ENABLED : DBGFINTERRUPTSTATE_DISABLED);
+ cHits++;
+ }
+ }
+ if (!cHits)
+ return DBGCCmdHlpVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "Unknown event: '%s'\n", paArgs[iArg].u.pszString);
+ }
+ }
+
+ /*
+ * Apply the reset changes.
+ */
+ return dbgcEventApplyChanges(pCmdHlp, pUVM, aIntCfgs, cIntCfgs, aEventCfgs, cEventCfgs, "", false);
+}
+
+
+/**
+ * Used during DBGC initialization to configure events with defaults.
+ *
+ * @param pDbgc The DBGC instance.
+ */
+void dbgcEventInit(PDBGC pDbgc)
+{
+ if (pDbgc->pUVM)
+ dbgcCmdEventCtrlReset(NULL, &pDbgc->CmdHlp, pDbgc->pUVM, NULL, 0);
+}
+
+
+/**
+ * Used during DBGC termination to disable all events.
+ *
+ * @param pDbgc The DBGC instance.
+ */
+void dbgcEventTerm(PDBGC pDbgc)
+{
+/** @todo need to do more than just reset later. */
+ if (pDbgc->pUVM && VMR3GetStateU(pDbgc->pUVM) < VMSTATE_DESTROYING)
+ dbgcCmdEventCtrlReset(NULL, &pDbgc->CmdHlp, pDbgc->pUVM, NULL, 0);
+}
+
+
+static void dbgcEventDisplay(PDBGCCMDHLP pCmdHlp, const char *pszName, DBGCEVTSTATE enmDefault, PDBGCEVTCFG const *ppEvtCfg)
+{
+ RT_NOREF1(enmDefault);
+ PDBGCEVTCFG pEvtCfg = *ppEvtCfg;
+
+ const char *pszState;
+ switch (pEvtCfg ? pEvtCfg->enmState : kDbgcEvtState_Disabled)
+ {
+ case kDbgcEvtState_Disabled: pszState = "ignore"; break;
+ case kDbgcEvtState_Enabled: pszState = "enabled"; break;
+ case kDbgcEvtState_Notify: pszState = "notify"; break;
+ default:
+ AssertFailed();
+ pszState = "invalid";
+ break;
+ }
+
+ if (pEvtCfg && pEvtCfg->cchCmd > 0)
+ DBGCCmdHlpPrintf(pCmdHlp, "%-22s %-7s \"%s\"\n", pszName, pszState, pEvtCfg->szCmd);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "%-22s %s\n", pszName, pszState);
+}
+
+
+static void dbgcEventDisplayRange(PDBGCCMDHLP pCmdHlp, const char *pszBaseNm, DBGCEVTSTATE enmDefault,
+ PDBGCEVTCFG const *papEvtCfgs, unsigned iCfg, unsigned cCfgs)
+{
+ do
+ {
+ PCDBGCEVTCFG pFirstCfg = papEvtCfgs[iCfg];
+ if (pFirstCfg && pFirstCfg->enmState == kDbgcEvtState_Disabled && pFirstCfg->cchCmd == 0)
+ pFirstCfg = NULL;
+
+ unsigned const iFirstCfg = iCfg;
+ iCfg++;
+ while (iCfg < cCfgs)
+ {
+ PCDBGCEVTCFG pCurCfg = papEvtCfgs[iCfg];
+ if (pCurCfg && pCurCfg->enmState == kDbgcEvtState_Disabled && pCurCfg->cchCmd == 0)
+ pCurCfg = NULL;
+ if (pCurCfg != pFirstCfg)
+ {
+ if (!pCurCfg || !pFirstCfg)
+ break;
+ if (pCurCfg->enmState != pFirstCfg->enmState)
+ break;
+ if (pCurCfg->cchCmd != pFirstCfg->cchCmd)
+ break;
+ if (memcmp(pCurCfg->szCmd, pFirstCfg->szCmd, pFirstCfg->cchCmd) != 0)
+ break;
+ }
+ iCfg++;
+ }
+
+ char szName[16];
+ unsigned cEntries = iCfg - iFirstCfg;
+ if (cEntries == 1)
+ RTStrPrintf(szName, sizeof(szName), "%s%02x", pszBaseNm, iFirstCfg);
+ else
+ RTStrPrintf(szName, sizeof(szName), "%s%02x L %#x", pszBaseNm, iFirstCfg, cEntries);
+ dbgcEventDisplay(pCmdHlp, szName, enmDefault, &papEvtCfgs[iFirstCfg]);
+
+ cCfgs -= cEntries;
+ } while (cCfgs > 0);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'sx' commands.}
+ */
+static DECLCALLBACK(int) dbgcCmdEventCtrlList(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ RT_NOREF2(pCmd, pUVM);
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ if (cArgs == 0)
+ {
+ /*
+ * All events.
+ */
+ for (uint32_t iEvt = 0; iEvt < RT_ELEMENTS(g_aDbgcSxEvents); iEvt++)
+ if (g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Plain)
+ dbgcEventDisplay(pCmdHlp, g_aDbgcSxEvents[iEvt].pszName, g_aDbgcSxEvents[iEvt].enmDefault,
+ &pDbgc->apEventCfgs[iEvt]);
+ else if (strcmp(g_aDbgcSxEvents[iEvt].pszName, "hwint") == 0)
+ dbgcEventDisplayRange(pCmdHlp, g_aDbgcSxEvents[iEvt].pszName, g_aDbgcSxEvents[iEvt].enmDefault,
+ pDbgc->apHardInts, 0, 256);
+ else
+ dbgcEventDisplayRange(pCmdHlp, g_aDbgcSxEvents[iEvt].pszName, g_aDbgcSxEvents[iEvt].enmDefault,
+ pDbgc->apSoftInts, 0, 256);
+ }
+ else
+ {
+ /*
+ * Selected events.
+ */
+ for (uint32_t iArg = 0; iArg < cArgs; iArg++)
+ {
+ unsigned cHits = 0;
+ for (uint32_t iEvt = 0; iEvt < RT_ELEMENTS(g_aDbgcSxEvents); iEvt++)
+ if (g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Plain)
+ {
+ if ( RTStrSimplePatternMatch(paArgs[iArg].u.pszString, g_aDbgcSxEvents[iEvt].pszName)
+ || ( g_aDbgcSxEvents[iEvt].pszAltNm
+ && RTStrSimplePatternMatch(paArgs[iArg].u.pszString, g_aDbgcSxEvents[iEvt].pszAltNm)) )
+ {
+ dbgcEventDisplay(pCmdHlp, g_aDbgcSxEvents[iEvt].pszName, g_aDbgcSxEvents[iEvt].enmDefault,
+ &pDbgc->apEventCfgs[iEvt]);
+ cHits++;
+ }
+ }
+ else
+ {
+ Assert(g_aDbgcSxEvents[iEvt].enmKind == kDbgcSxEventKind_Interrupt);
+ uint8_t iInt;
+ uint16_t cInts;
+ if (dbgcEventIsMatchingInt(&paArgs[iArg], g_aDbgcSxEvents[iEvt].pszName, pCmdHlp, &iInt, &cInts))
+ {
+ if (strcmp(g_aDbgcSxEvents[iEvt].pszName, "hwint") == 0)
+ dbgcEventDisplayRange(pCmdHlp, g_aDbgcSxEvents[iEvt].pszName, g_aDbgcSxEvents[iEvt].enmDefault,
+ pDbgc->apHardInts, iInt, cInts);
+ else
+ dbgcEventDisplayRange(pCmdHlp, g_aDbgcSxEvents[iEvt].pszName, g_aDbgcSxEvents[iEvt].enmDefault,
+ pDbgc->apSoftInts, iInt, cInts);
+ cHits++;
+ }
+ }
+ if (cHits == 0)
+ return DBGCCmdHlpVBoxError(pCmdHlp, VERR_INVALID_PARAMETER, "Unknown event: '%s'\n", paArgs[iArg].u.pszString);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * List near symbol.
+ *
+ * @returns VBox status code.
+ * @param pCmdHlp Pointer to command helper functions.
+ * @param pUVM The user mode VM handle.
+ * @param pArg Pointer to the address or symbol to lookup.
+ */
+static int dbgcDoListNear(PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR pArg)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ RTDBGSYMBOL Symbol;
+ int rc;
+ if (pArg->enmType == DBGCVAR_TYPE_SYMBOL)
+ {
+ /*
+ * Lookup the symbol address.
+ */
+ rc = DBGFR3AsSymbolByName(pUVM, pDbgc->hDbgAs, pArg->u.pszString, &Symbol, NULL);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "DBGFR3AsSymbolByName(,,%s,)\n", pArg->u.pszString);
+
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%RTptr %s\n", Symbol.Value, Symbol.szName);
+ }
+ else
+ {
+ /*
+ * Convert it to a flat GC address and lookup that address.
+ */
+ DBGCVAR AddrVar;
+ rc = DBGCCmdHlpEval(pCmdHlp, &AddrVar, "%%(%DV)", pArg);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "%%(%DV)\n", pArg);
+
+ RTINTPTR offDisp;
+ DBGFADDRESS Addr;
+ rc = DBGFR3AsSymbolByAddr(pUVM, pDbgc->hDbgAs, DBGFR3AddrFromFlat(pDbgc->pUVM, &Addr, AddrVar.u.GCFlat),
+ RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL | RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED,
+ &offDisp, &Symbol, NULL);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "DBGFR3AsSymbolByAddr(,,%RGv,,)\n", AddrVar.u.GCFlat);
+
+ if (!offDisp)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%DV %s", &AddrVar, Symbol.szName);
+ else if (offDisp > 0)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%DV %s + %RGv", &AddrVar, Symbol.szName, offDisp);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "%DV %s - %RGv", &AddrVar, Symbol.szName, -offDisp);
+ if (Symbol.cb > 0)
+ rc = DBGCCmdHlpPrintf(pCmdHlp, " (LB %RGv)\n", Symbol.cb);
+ else
+ rc = DBGCCmdHlpPrintf(pCmdHlp, "\n");
+ }
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'ln' (listnear) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdListNear(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ if (!cArgs)
+ {
+ /*
+ * Current cs:eip symbol.
+ */
+ DBGCVAR AddrVar;
+ const char *pszFmtExpr = "%%(cs:eip)";
+ int rc = DBGCCmdHlpEval(pCmdHlp, &AddrVar, pszFmtExpr);
+ if (RT_FAILURE(rc))
+ return pCmdHlp->pfnVBoxError(pCmdHlp, rc, "%s\n", pszFmtExpr + 1);
+ return dbgcDoListNear(pCmdHlp, pUVM, &AddrVar);
+ }
+
+/** @todo Fix the darn parser, it's resolving symbols specified as arguments before we get in here. */
+ /*
+ * Iterate arguments.
+ */
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ {
+ int rc = dbgcDoListNear(pCmdHlp, pUVM, &paArgs[iArg]);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Matches the module patters against a module name.
+ *
+ * @returns true if matching, otherwise false.
+ * @param pszName The module name.
+ * @param paArgs The module pattern argument list.
+ * @param cArgs Number of arguments.
+ */
+static bool dbgcCmdListModuleMatch(const char *pszName, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ for (uint32_t i = 0; i < cArgs; i++)
+ if (RTStrSimplePatternMatch(paArgs[i].u.pszString, pszName))
+ return true;
+ return false;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'ln' (list near) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdListModules(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ bool const fMappings = pCmd->pszCmd[2] == 'o';
+ bool const fVerbose = pCmd->pszCmd[strlen(pCmd->pszCmd) - 1] == 'v';
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Iterate the modules in the current address space and print info about
+ * those matching the input.
+ */
+ RTDBGAS hAsCurAlias = pDbgc->hDbgAs;
+ for (uint32_t iAs = 0;; iAs++)
+ {
+ RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, hAsCurAlias);
+ uint32_t cMods = RTDbgAsModuleCount(hAs);
+ for (uint32_t iMod = 0; iMod < cMods; iMod++)
+ {
+ RTDBGMOD hMod = RTDbgAsModuleByIndex(hAs, iMod);
+ if (hMod != NIL_RTDBGMOD)
+ {
+ bool const fDeferred = RTDbgModIsDeferred(hMod);
+ bool const fExports = RTDbgModIsExports(hMod);
+ uint32_t const cSegs = fDeferred ? 1 : RTDbgModSegmentCount(hMod);
+ const char * const pszName = RTDbgModName(hMod);
+ const char * const pszImgFile = RTDbgModImageFile(hMod);
+ const char * const pszImgFileUsed = RTDbgModImageFileUsed(hMod);
+ const char * const pszDbgFile = RTDbgModDebugFile(hMod);
+ if ( cArgs == 0
+ || dbgcCmdListModuleMatch(pszName, paArgs, cArgs))
+ {
+ /*
+ * Find the mapping with the lower address, preferring a full
+ * image mapping, for the main line.
+ */
+ RTDBGASMAPINFO aMappings[128];
+ uint32_t cMappings = RT_ELEMENTS(aMappings);
+ int rc = RTDbgAsModuleQueryMapByIndex(hAs, iMod, &aMappings[0], &cMappings, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ bool fFull = false;
+ RTUINTPTR uMin = RTUINTPTR_MAX;
+ for (uint32_t iMap = 0; iMap < cMappings; iMap++)
+ if ( aMappings[iMap].Address < uMin
+ && ( !fFull
+ || aMappings[iMap].iSeg == NIL_RTDBGSEGIDX))
+ uMin = aMappings[iMap].Address;
+ if (!fVerbose || !pszImgFile)
+ DBGCCmdHlpPrintf(pCmdHlp, "%RGv %04x %s%s\n", (RTGCUINTPTR)uMin, cSegs, pszName,
+ fExports ? " (exports)" : fDeferred ? " (deferred)" : "");
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "%RGv %04x %-12s %s%s\n", (RTGCUINTPTR)uMin, cSegs, pszName, pszImgFile,
+ fExports ? " (exports)" : fDeferred ? " (deferred)" : "");
+ if (fVerbose && pszImgFileUsed)
+ DBGCCmdHlpPrintf(pCmdHlp, " Local image: %s\n", pszImgFileUsed);
+ if (fVerbose && pszDbgFile)
+ DBGCCmdHlpPrintf(pCmdHlp, " Debug file: %s\n", pszDbgFile);
+ if (fVerbose)
+ {
+ char szTmp[64];
+ RTTIMESPEC TimeSpec;
+ int64_t secTs = 0;
+ if (RT_SUCCESS(RTDbgModImageQueryProp(hMod, RTLDRPROP_TIMESTAMP_SECONDS, &secTs, sizeof(secTs), NULL)))
+ DBGCCmdHlpPrintf(pCmdHlp, " Timestamp: %08RX64 %s\n", secTs,
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&TimeSpec, secTs), szTmp, sizeof(szTmp)));
+ RTUUID Uuid;
+ if (RT_SUCCESS(RTDbgModImageQueryProp(hMod, RTLDRPROP_UUID, &Uuid, sizeof(Uuid), NULL)))
+ DBGCCmdHlpPrintf(pCmdHlp, " UUID: %RTuuid\n", &Uuid);
+ }
+
+ if (fMappings)
+ {
+ /* sort by address first - not very efficient. */
+ for (uint32_t i = 0; i + 1 < cMappings; i++)
+ for (uint32_t j = i + 1; j < cMappings; j++)
+ if (aMappings[j].Address < aMappings[i].Address)
+ {
+ RTDBGASMAPINFO Tmp = aMappings[j];
+ aMappings[j] = aMappings[i];
+ aMappings[i] = Tmp;
+ }
+
+ /* print */
+ if ( cMappings == 1
+ && aMappings[0].iSeg == NIL_RTDBGSEGIDX
+ && !fDeferred)
+ {
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ {
+ RTDBGSEGMENT SegInfo;
+ rc = RTDbgModSegmentByIndex(hMod, iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (SegInfo.uRva != RTUINTPTR_MAX)
+ DBGCCmdHlpPrintf(pCmdHlp, " %RGv %RGv #%02x %s\n",
+ (RTGCUINTPTR)(aMappings[0].Address + SegInfo.uRva),
+ (RTGCUINTPTR)SegInfo.cb, iSeg, SegInfo.szName);
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " %*s %RGv #%02x %s\n",
+ sizeof(RTGCUINTPTR)*2, "noload",
+ (RTGCUINTPTR)SegInfo.cb, iSeg, SegInfo.szName);
+ }
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " Error query segment #%u: %Rrc\n", iSeg, rc);
+ }
+ }
+ else
+ {
+ for (uint32_t iMap = 0; iMap < cMappings; iMap++)
+ if (aMappings[iMap].iSeg == NIL_RTDBGSEGIDX)
+ DBGCCmdHlpPrintf(pCmdHlp, " %RGv %RGv <everything>\n",
+ (RTGCUINTPTR)aMappings[iMap].Address,
+ (RTGCUINTPTR)RTDbgModImageSize(hMod));
+ else if (!fDeferred)
+ {
+ RTDBGSEGMENT SegInfo;
+ rc = RTDbgModSegmentByIndex(hMod, aMappings[iMap].iSeg, &SegInfo);
+ if (RT_FAILURE(rc))
+ {
+ RT_ZERO(SegInfo);
+ strcpy(SegInfo.szName, "error");
+ }
+ DBGCCmdHlpPrintf(pCmdHlp, " %RGv %RGv #%02x %s\n",
+ (RTGCUINTPTR)aMappings[iMap].Address,
+ (RTGCUINTPTR)SegInfo.cb,
+ aMappings[iMap].iSeg, SegInfo.szName);
+ }
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, " %RGv #%02x\n",
+ (RTGCUINTPTR)aMappings[iMap].Address, aMappings[iMap].iSeg);
+ }
+ }
+ }
+ else
+ DBGCCmdHlpPrintf(pCmdHlp, "%.*s %04x %s (rc=%Rrc)\n",
+ sizeof(RTGCPTR) * 2, "???????????", cSegs, pszName, rc);
+ /** @todo missing address space API for enumerating the mappings. */
+ }
+ RTDbgModRelease(hMod);
+ }
+ }
+ RTDbgAsRelease(hAs);
+
+ /* For DBGF_AS_RC_AND_GC_GLOBAL we're required to do more work. */
+ if (hAsCurAlias != DBGF_AS_RC_AND_GC_GLOBAL)
+ break;
+ AssertBreak(iAs == 0);
+ hAsCurAlias = DBGF_AS_GLOBAL;
+ }
+
+ NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'x' (examine symbols) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdListSymbols(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ AssertReturn(paArgs[0].enmType == DBGCVAR_TYPE_STRING, VERR_DBGC_PARSE_BUG);
+
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Allowed is either a single * to match everything or the Module!Symbol style
+ * which requiresa ! to separate module and symbol.
+ */
+ bool fDumpAll = strcmp(paArgs[0].u.pszString, "*") == 0;
+ const char *pszModule = NULL;
+ size_t cchModule = 0;
+ const char *pszSymbol = NULL;
+ if (!fDumpAll)
+ {
+ const char *pszDelimiter = strchr(paArgs[0].u.pszString, '!');
+ if (!pszDelimiter)
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid search string '%s' for '%s'. Valid are either '*' or the form <Module>!<Symbol> where the <Module> and <Symbol> can contain wildcards",
+ paArgs[0].u.pszString, pCmd->pszCmd);
+
+ pszModule = paArgs[0].u.pszString;
+ cchModule = pszDelimiter - pszModule;
+ pszSymbol = pszDelimiter + 1;
+ }
+
+ /*
+ * Iterate the modules in the current address space and print info about
+ * those matching the input.
+ */
+ RTDBGAS hAsCurAlias = pDbgc->hDbgAs;
+ for (uint32_t iAs = 0;; iAs++)
+ {
+ RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, hAsCurAlias);
+ uint32_t cMods = RTDbgAsModuleCount(hAs);
+ for (uint32_t iMod = 0; iMod < cMods; iMod++)
+ {
+ RTDBGMOD hMod = RTDbgAsModuleByIndex(hAs, iMod);
+ if (hMod != NIL_RTDBGMOD)
+ {
+ const char *pszModName = RTDbgModName(hMod);
+ if ( fDumpAll
+ || RTStrSimplePatternNMatch(pszModule, cchModule, pszModName, strlen(pszModName)))
+ {
+ RTDBGASMAPINFO aMappings[128];
+ uint32_t cMappings = RT_ELEMENTS(aMappings);
+ RTUINTPTR uMapping = 0;
+
+ /* Get the minimum mapping address of the module so we can print absolute values for the symbol later on. */
+ int rc = RTDbgAsModuleQueryMapByIndex(hAs, iMod, &aMappings[0], &cMappings, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ uMapping = RTUINTPTR_MAX;
+ for (uint32_t iMap = 0; iMap < cMappings; iMap++)
+ if (aMappings[iMap].Address < uMapping)
+ uMapping = aMappings[iMap].Address;
+ }
+
+ /* Go through the symbols and print any matches. */
+ uint32_t cSyms = RTDbgModSymbolCount(hMod);
+ for (uint32_t iSym = 0; iSym < cSyms; iSym++)
+ {
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByOrdinal(hMod, iSym, &SymInfo);
+ if ( RT_SUCCESS(rc)
+ && ( fDumpAll
+ || RTStrSimplePatternMatch(pszSymbol, &SymInfo.szName[0])))
+ DBGCCmdHlpPrintf(pCmdHlp, "%RGv %s!%s\n", uMapping + RTDbgModSegmentRva(hMod, SymInfo.iSeg) + (RTGCUINTPTR)SymInfo.Value, pszModName, &SymInfo.szName[0]);
+ }
+ }
+ RTDbgModRelease(hMod);
+ }
+ }
+ RTDbgAsRelease(hAs);
+
+ /* For DBGF_AS_RC_AND_GC_GLOBAL we're required to do more work. */
+ if (hAsCurAlias != DBGF_AS_RC_AND_GC_GLOBAL)
+ break;
+ AssertBreak(iAs == 0);
+ hAsCurAlias = DBGF_AS_GLOBAL;
+ }
+
+ RT_NOREF(pCmd);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'tflowc' (clear trace flow) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdTraceFlowClear(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Enumerate the arguments.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iFlowTraceMod = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iFlowTraceMod == paArgs[iArg].u.u64Number)
+ {
+ PDBGCTFLOW pFlowTrace = dbgcFlowTraceModGet(pDbgc, iFlowTraceMod);
+ if (pFlowTrace)
+ {
+ rc = DBGFR3FlowTraceModRelease(pFlowTrace->hTraceFlowMod);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowTraceModRelease failed for flow trace module %#x", iFlowTraceMod);
+ rc = DBGFR3FlowRelease(pFlowTrace->hFlow);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowRelease failed for flow trace module %#x", iFlowTraceMod);
+ dbgcFlowTraceModDelete(pDbgc, iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_NOT_FOUND, "Flow trace module %#x doesn't exist", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Flow trace mod id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGCTFLOW pIt, pItNext;
+ RTListForEachSafe(&pDbgc->LstTraceFlowMods, pIt, pItNext, DBGCTFLOW, NdTraceFlow)
+ {
+ int rc2 = DBGFR3FlowTraceModRelease(pIt->hTraceFlowMod);
+ if (RT_FAILURE(rc2))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc2, "DBGFR3FlowTraceModDisable failed for flow trace module %#x", pIt->iTraceFlowMod);
+ dbgcFlowTraceModDelete(pDbgc, pIt->iTraceFlowMod);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'tflowd' (disable trace flow) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdTraceFlowDisable(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ /*
+ * Enumerate the arguments.
+ */
+ RT_NOREF1(pUVM);
+ int rc = VINF_SUCCESS;
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iFlowTraceMod = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iFlowTraceMod == paArgs[iArg].u.u64Number)
+ {
+ PDBGCTFLOW pFlowTrace = dbgcFlowTraceModGet(pDbgc, iFlowTraceMod);
+ if (pFlowTrace)
+ {
+ rc = DBGFR3FlowTraceModDisable(pFlowTrace->hTraceFlowMod);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowTraceModDisable failed for flow trace module %#x", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_NOT_FOUND, "Flow trace module %#x doesn't exist", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Breakpoint id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGCTFLOW pIt;
+ RTListForEach(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow)
+ {
+ int rc2 = DBGFR3FlowTraceModDisable(pIt->hTraceFlowMod);
+ if (RT_FAILURE(rc2))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc2, "DBGFR3FlowTraceModDisable failed for flow trace module %#x",
+ pIt->iTraceFlowMod);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'tflowe' (enable trace flow) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdTraceFlowEnable(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+
+ /*
+ * Validate input.
+ */
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, -1, cArgs <= 2);
+ DBGC_CMDHLP_ASSERT_PARSER_RET(pCmdHlp, pCmd, 0, cArgs == 0 || DBGCVAR_ISPOINTER(paArgs[0].enmType));
+
+ if (!cArgs && !DBGCVAR_ISPOINTER(pDbgc->DisasmPos.enmType))
+ return DBGCCmdHlpFail(pCmdHlp, pCmd, "Don't know where to start disassembling");
+
+ /*
+ * Check the desired mode.
+ */
+ unsigned fFlags = DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED | DBGF_DISAS_FLAGS_DEFAULT_MODE;
+
+ /** @todo should use DBGFADDRESS for everything */
+
+ /*
+ * Find address.
+ */
+ if (!cArgs)
+ {
+ if (!DBGCVAR_ISPOINTER(pDbgc->DisasmPos.enmType))
+ {
+ /** @todo Batch query CS, RIP, CPU mode and flags. */
+ PVMCPU pVCpu = VMMR3GetCpuByIdU(pUVM, pDbgc->idCpu);
+ if (CPUMIsGuestIn64BitCode(pVCpu))
+ {
+ pDbgc->DisasmPos.enmType = DBGCVAR_TYPE_GC_FLAT;
+ pDbgc->SourcePos.u.GCFlat = CPUMGetGuestRIP(pVCpu);
+ }
+ else
+ {
+ pDbgc->DisasmPos.enmType = DBGCVAR_TYPE_GC_FAR;
+ pDbgc->SourcePos.u.GCFar.off = CPUMGetGuestEIP(pVCpu);
+ pDbgc->SourcePos.u.GCFar.sel = CPUMGetGuestCS(pVCpu);
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE
+ && (CPUMGetGuestEFlags(pVCpu) & X86_EFL_VM))
+ {
+ fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK;
+ fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE;
+ }
+ }
+
+ fFlags |= DBGF_DISAS_FLAGS_CURRENT_GUEST;
+ }
+ else if ((fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE && pDbgc->fDisasm)
+ {
+ fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK;
+ fFlags |= pDbgc->fDisasm & DBGF_DISAS_FLAGS_MODE_MASK;
+ }
+ pDbgc->DisasmPos.enmRangeType = DBGCVAR_RANGE_NONE;
+ }
+ else
+ pDbgc->DisasmPos = paArgs[0];
+ pDbgc->pLastPos = &pDbgc->DisasmPos;
+
+ /*
+ * Convert physical and host addresses to guest addresses.
+ */
+ RTDBGAS hDbgAs = pDbgc->hDbgAs;
+ int rc;
+ switch (pDbgc->DisasmPos.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ hDbgAs = DBGF_AS_PHYS;
+ /* fall thru */
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ {
+ DBGCVAR VarTmp;
+ rc = DBGCCmdHlpEval(pCmdHlp, &VarTmp, "%%(%Dv)", &pDbgc->DisasmPos);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "failed to evaluate '%%(%Dv)'", &pDbgc->DisasmPos);
+ pDbgc->DisasmPos = VarTmp;
+ break;
+ }
+ default: AssertFailed(); break;
+ }
+
+ DBGFADDRESS CurAddr;
+ if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_16BIT_REAL_MODE
+ && pDbgc->DisasmPos.enmType == DBGCVAR_TYPE_GC_FAR)
+ DBGFR3AddrFromFlat(pUVM, &CurAddr, ((uint32_t)pDbgc->DisasmPos.u.GCFar.sel << 4) + pDbgc->DisasmPos.u.GCFar.off);
+ else
+ {
+ rc = DBGCCmdHlpVarToDbgfAddr(pCmdHlp, &pDbgc->DisasmPos, &CurAddr);
+ if (RT_FAILURE(rc))
+ return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGCCmdHlpVarToDbgfAddr failed on '%Dv'", &pDbgc->DisasmPos);
+ }
+
+ DBGFFLOW hCfg;
+ rc = DBGFR3FlowCreate(pUVM, pDbgc->idCpu, &CurAddr, 0 /*cbDisasmMax*/,
+ DBGF_FLOW_CREATE_F_TRY_RESOLVE_INDIRECT_BRANCHES, fFlags, &hCfg);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create a probe. */
+ DBGFFLOWTRACEPROBE hFlowTraceProbe = NULL;
+ DBGFFLOWTRACEPROBE hFlowTraceProbeExit = NULL;
+ DBGFFLOWTRACEPROBEENTRY Entry;
+ DBGFFLOWTRACEMOD hFlowTraceMod = NULL;
+ uint32_t iTraceModId = 0;
+
+ RT_ZERO(Entry);
+ Entry.enmType = DBGFFLOWTRACEPROBEENTRYTYPE_DEBUGGER;
+
+ rc = DBGFR3FlowTraceProbeCreate(pUVM, NULL, &hFlowTraceProbe);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3FlowTraceProbeCreate(pUVM, NULL, &hFlowTraceProbeExit);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3FlowTraceProbeEntriesAdd(hFlowTraceProbeExit, &Entry, 1 /*cEntries*/);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3FlowTraceModCreateFromFlowGraph(pUVM, VMCPUID_ANY, hCfg, NULL,
+ hFlowTraceProbe, hFlowTraceProbe,
+ hFlowTraceProbeExit, &hFlowTraceMod);
+ if (RT_SUCCESS(rc))
+ rc = dbgcFlowTraceModAdd(pDbgc, hFlowTraceMod, hCfg, &iTraceModId);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3FlowTraceModEnable(hFlowTraceMod, 0, 0);
+ if (RT_SUCCESS(rc))
+ DBGCCmdHlpPrintf(pCmdHlp, "Enabled execution flow tracing %u at %RGv\n",
+ iTraceModId, CurAddr.FlatPtr);
+
+ if (hFlowTraceProbe)
+ DBGFR3FlowTraceProbeRelease(hFlowTraceProbe);
+ if (hFlowTraceProbeExit)
+ DBGFR3FlowTraceProbeRelease(hFlowTraceProbeExit);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowCreate failed on '%Dv'", &pDbgc->DisasmPos);
+
+ NOREF(pCmd);
+ return rc;
+}
+
+
+/**
+ * Enumerates and prints all records contained in the given flow tarce module.
+ *
+ * @returns VBox status code.
+ * @param pCmd The command.
+ * @param pCmdHlp The command helpers.
+ * @param hFlowTraceMod The flow trace module to print.
+ * @param hFlow The control flow graph assoicated with the given module.
+ * @param iFlowTraceMod The flow trace module identifier.
+ */
+static int dbgcCmdTraceFlowPrintOne(PDBGCCMDHLP pCmdHlp, PCDBGCCMD pCmd, DBGFFLOWTRACEMOD hFlowTraceMod,
+ DBGFFLOW hFlow, uint32_t iFlowTraceMod)
+{
+ RT_NOREF(hFlow);
+
+ DBGFFLOWTRACEREPORT hFlowTraceReport;
+ int rc = DBGFR3FlowTraceModQueryReport(hFlowTraceMod, &hFlowTraceReport);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRecords = DBGFR3FlowTraceReportGetRecordCount(hFlowTraceReport);
+ DBGCCmdHlpPrintf(pCmdHlp, "Report for flow trace module %#x (%u records):\n",
+ iFlowTraceMod, cRecords);
+
+ PDBGCFLOWBBDUMP paDumpBb = (PDBGCFLOWBBDUMP)RTMemTmpAllocZ(cRecords * sizeof(DBGCFLOWBBDUMP));
+ if (RT_LIKELY(paDumpBb))
+ {
+ /* Query the basic block referenced for each record and calculate the size. */
+ for (uint32_t i = 0; i < cRecords && RT_SUCCESS(rc); i++)
+ {
+ DBGFFLOWTRACERECORD hRec = NULL;
+ rc = DBGFR3FlowTraceReportQueryRecord(hFlowTraceReport, i, &hRec);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS Addr;
+ DBGFR3FlowTraceRecordGetAddr(hRec, &Addr);
+
+ DBGFFLOWBB hFlowBb = NULL;
+ rc = DBGFR3FlowQueryBbByAddress(hFlow, &Addr, &hFlowBb);
+ if (RT_SUCCESS(rc))
+ dbgcCmdUnassembleCfgDumpCalcBbSize(hFlowBb, &paDumpBb[i]);
+
+ DBGFR3FlowTraceRecordRelease(hRec);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Calculate the ASCII screen dimensions and create one. */
+ uint32_t cchWidth = 0;
+ uint32_t cchHeight = 0;
+ for (unsigned i = 0; i < cRecords; i++)
+ {
+ PDBGCFLOWBBDUMP pDumpBb = &paDumpBb[i];
+ cchWidth = RT_MAX(cchWidth, pDumpBb->cchWidth);
+ cchHeight += pDumpBb->cchHeight;
+
+ /* Incomplete blocks don't have a successor. */
+ if (DBGFR3FlowBbGetFlags(pDumpBb->hFlowBb) & DBGF_FLOW_BB_F_INCOMPLETE_ERR)
+ continue;
+
+ cchHeight += 2; /* For the arrow down to the next basic block. */
+ }
+
+
+ DBGCSCREEN hScreen = NULL;
+ rc = dbgcScreenAsciiCreate(&hScreen, cchWidth, cchHeight);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uY = 0;
+
+ /* Dump the basic blocks and connections to the immediate successor. */
+ for (unsigned i = 0; i < cRecords; i++)
+ {
+ paDumpBb[i].uStartX = (cchWidth - paDumpBb[i].cchWidth) / 2;
+ paDumpBb[i].uStartY = uY;
+ dbgcCmdUnassembleCfgDumpBb(&paDumpBb[i], hScreen);
+ uY += paDumpBb[i].cchHeight;
+
+ /* Incomplete blocks don't have a successor. */
+ if (DBGFR3FlowBbGetFlags(paDumpBb[i].hFlowBb) & DBGF_FLOW_BB_F_INCOMPLETE_ERR)
+ continue;
+
+ if (DBGFR3FlowBbGetType(paDumpBb[i].hFlowBb) != DBGFFLOWBBENDTYPE_EXIT)
+ {
+ /* Draw the arrow down to the next block. */
+ dbgcScreenAsciiDrawCharacter(hScreen, cchWidth / 2, uY,
+ '|', DBGCSCREENCOLOR_BLUE_BRIGHT);
+ uY++;
+ dbgcScreenAsciiDrawCharacter(hScreen, cchWidth / 2, uY,
+ 'V', DBGCSCREENCOLOR_BLUE_BRIGHT);
+ uY++;
+ }
+ }
+
+ rc = dbgcScreenAsciiBlit(hScreen, dbgcCmdUnassembleCfgBlit, pCmdHlp, false /*fUseColor*/);
+ dbgcScreenAsciiDestroy(hScreen);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to create virtual screen for flow trace module %#x", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to query all records of flow trace module %#x", iFlowTraceMod);
+
+ for (unsigned i = 0; i < cRecords; i++)
+ {
+ if (paDumpBb[i].hFlowBb)
+ DBGFR3FlowBbRelease(paDumpBb[i].hFlowBb);
+ }
+
+ RTMemTmpFree(paDumpBb);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to allocate memory for %u records", cRecords);
+
+ DBGFR3FlowTraceReportRelease(hFlowTraceReport);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Failed to query report for flow trace module %#x", iFlowTraceMod);
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'tflowp' (print trace flow) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdTraceFlowPrint(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Enumerate the arguments.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iFlowTraceMod = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iFlowTraceMod == paArgs[iArg].u.u64Number)
+ {
+ PDBGCTFLOW pFlowTrace = dbgcFlowTraceModGet(pDbgc, iFlowTraceMod);
+ if (pFlowTrace)
+ rc = dbgcCmdTraceFlowPrintOne(pCmdHlp, pCmd, pFlowTrace->hTraceFlowMod,
+ pFlowTrace->hFlow, pFlowTrace->iTraceFlowMod);
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_NOT_FOUND, "Flow trace module %#x doesn't exist", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Flow trace mod id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGCTFLOW pIt;
+ RTListForEach(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow)
+ {
+ rc = dbgcCmdTraceFlowPrintOne(pCmdHlp, pCmd, pIt->hTraceFlowMod,
+ pIt->hFlow, pIt->iTraceFlowMod);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCCMD, The 'tflowr' (reset trace flow) command.}
+ */
+static DECLCALLBACK(int) dbgcCmdTraceFlowReset(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs)
+{
+ DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM);
+
+ /*
+ * Enumerate the arguments.
+ */
+ PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp);
+ int rc = VINF_SUCCESS;
+ for (unsigned iArg = 0; iArg < cArgs && RT_SUCCESS(rc); iArg++)
+ {
+ if (paArgs[iArg].enmType != DBGCVAR_TYPE_STRING)
+ {
+ /* one */
+ uint32_t iFlowTraceMod = (uint32_t)paArgs[iArg].u.u64Number;
+ if (iFlowTraceMod == paArgs[iArg].u.u64Number)
+ {
+ PDBGCTFLOW pFlowTrace = dbgcFlowTraceModGet(pDbgc, iFlowTraceMod);
+ if (pFlowTrace)
+ {
+ rc = DBGFR3FlowTraceModClear(pFlowTrace->hTraceFlowMod);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowTraceModClear failed for flow trace module %#x", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, VERR_NOT_FOUND, "Flow trace module %#x doesn't exist", iFlowTraceMod);
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Flow trace mod id %RX64 is too large", paArgs[iArg].u.u64Number);
+ }
+ else if (!strcmp(paArgs[iArg].u.pszString, "all"))
+ {
+ /* all */
+ PDBGCTFLOW pIt;
+ RTListForEach(&pDbgc->LstTraceFlowMods, pIt, DBGCTFLOW, NdTraceFlow)
+ {
+ rc = DBGFR3FlowTraceModClear(pIt->hTraceFlowMod);
+ if (RT_FAILURE(rc))
+ rc = DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3FlowTraceModClear failed for flow trace module %#x", pIt->iTraceFlowMod);
+ }
+ }
+ else
+ rc = DBGCCmdHlpFail(pCmdHlp, pCmd, "Invalid argument '%s'", paArgs[iArg].u.pszString);
+ }
+ return rc;
+}
+
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, Reads a unsigned 8-bit value.}
+ */
+static DECLCALLBACK(int) dbgcFuncReadU8(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ RT_NOREF1(pUVM);
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ AssertReturn(DBGCVAR_ISPOINTER(paArgs[0].enmType), VERR_DBGC_PARSE_BUG);
+ AssertReturn(paArgs[0].enmRangeType == DBGCVAR_RANGE_NONE, VERR_DBGC_PARSE_BUG);
+
+ uint8_t b;
+ int rc = DBGCCmdHlpMemRead(pCmdHlp, &b, sizeof(b), &paArgs[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ DBGCVAR_INIT_NUMBER(pResult, b);
+
+ NOREF(pFunc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, Reads a unsigned 16-bit value.}
+ */
+static DECLCALLBACK(int) dbgcFuncReadU16(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ RT_NOREF1(pUVM);
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ AssertReturn(DBGCVAR_ISPOINTER(paArgs[0].enmType), VERR_DBGC_PARSE_BUG);
+ AssertReturn(paArgs[0].enmRangeType == DBGCVAR_RANGE_NONE, VERR_DBGC_PARSE_BUG);
+
+ uint16_t u16;
+ int rc = DBGCCmdHlpMemRead(pCmdHlp, &u16, sizeof(u16), &paArgs[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ DBGCVAR_INIT_NUMBER(pResult, u16);
+
+ NOREF(pFunc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, Reads a unsigned 32-bit value.}
+ */
+static DECLCALLBACK(int) dbgcFuncReadU32(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ RT_NOREF1(pUVM);
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ AssertReturn(DBGCVAR_ISPOINTER(paArgs[0].enmType), VERR_DBGC_PARSE_BUG);
+ AssertReturn(paArgs[0].enmRangeType == DBGCVAR_RANGE_NONE, VERR_DBGC_PARSE_BUG);
+
+ uint32_t u32;
+ int rc = DBGCCmdHlpMemRead(pCmdHlp, &u32, sizeof(u32), &paArgs[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ DBGCVAR_INIT_NUMBER(pResult, u32);
+
+ NOREF(pFunc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, Reads a unsigned 64-bit value.}
+ */
+static DECLCALLBACK(int) dbgcFuncReadU64(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ RT_NOREF1(pUVM);
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ AssertReturn(DBGCVAR_ISPOINTER(paArgs[0].enmType), VERR_DBGC_PARSE_BUG);
+ AssertReturn(paArgs[0].enmRangeType == DBGCVAR_RANGE_NONE, VERR_DBGC_PARSE_BUG);
+
+ uint64_t u64;
+ int rc = DBGCCmdHlpMemRead(pCmdHlp, &u64, sizeof(u64), &paArgs[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ DBGCVAR_INIT_NUMBER(pResult, u64);
+
+ NOREF(pFunc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, Reads a unsigned pointer-sized value.}
+ */
+static DECLCALLBACK(int) dbgcFuncReadPtr(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ AssertReturn(DBGCVAR_ISPOINTER(paArgs[0].enmType), VERR_DBGC_PARSE_BUG);
+ AssertReturn(paArgs[0].enmRangeType == DBGCVAR_RANGE_NONE, VERR_DBGC_PARSE_BUG);
+
+ CPUMMODE enmMode = DBGCCmdHlpGetCpuMode(pCmdHlp);
+ if (enmMode == CPUMMODE_LONG)
+ return dbgcFuncReadU64(pFunc, pCmdHlp, pUVM, paArgs, cArgs, pResult);
+ return dbgcFuncReadU32(pFunc, pCmdHlp, pUVM, paArgs, cArgs, pResult);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, The hi(value) function implementation.}
+ */
+static DECLCALLBACK(int) dbgcFuncHi(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+
+ uint16_t uHi;
+ switch (paArgs[0].enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT: uHi = (uint16_t)(paArgs[0].u.GCFlat >> 16); break;
+ case DBGCVAR_TYPE_GC_FAR: uHi = (uint16_t)paArgs[0].u.GCFar.sel; break;
+ case DBGCVAR_TYPE_GC_PHYS: uHi = (uint16_t)(paArgs[0].u.GCPhys >> 16); break;
+ case DBGCVAR_TYPE_HC_FLAT: uHi = (uint16_t)((uintptr_t)paArgs[0].u.pvHCFlat >> 16); break;
+ case DBGCVAR_TYPE_HC_PHYS: uHi = (uint16_t)(paArgs[0].u.HCPhys >> 16); break;
+ case DBGCVAR_TYPE_NUMBER: uHi = (uint16_t)(paArgs[0].u.u64Number >> 16); break;
+ default:
+ AssertFailedReturn(VERR_DBGC_PARSE_BUG);
+ }
+ DBGCVAR_INIT_NUMBER(pResult, uHi);
+ DBGCVAR_SET_RANGE(pResult, paArgs[0].enmRangeType, paArgs[0].u64Range);
+
+ NOREF(pFunc); NOREF(pCmdHlp); NOREF(pUVM);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, The low(value) function implementation.}
+ */
+static DECLCALLBACK(int) dbgcFuncLow(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+
+ uint16_t uLow;
+ switch (paArgs[0].enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT: uLow = (uint16_t)paArgs[0].u.GCFlat; break;
+ case DBGCVAR_TYPE_GC_FAR: uLow = (uint16_t)paArgs[0].u.GCFar.off; break;
+ case DBGCVAR_TYPE_GC_PHYS: uLow = (uint16_t)paArgs[0].u.GCPhys; break;
+ case DBGCVAR_TYPE_HC_FLAT: uLow = (uint16_t)(uintptr_t)paArgs[0].u.pvHCFlat; break;
+ case DBGCVAR_TYPE_HC_PHYS: uLow = (uint16_t)paArgs[0].u.HCPhys; break;
+ case DBGCVAR_TYPE_NUMBER: uLow = (uint16_t)paArgs[0].u.u64Number; break;
+ default:
+ AssertFailedReturn(VERR_DBGC_PARSE_BUG);
+ }
+ DBGCVAR_INIT_NUMBER(pResult, uLow);
+ DBGCVAR_SET_RANGE(pResult, paArgs[0].enmRangeType, paArgs[0].u64Range);
+
+ NOREF(pFunc); NOREF(pCmdHlp); NOREF(pUVM);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC,The low(value) function implementation.}
+ */
+static DECLCALLBACK(int) dbgcFuncNot(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ AssertReturn(cArgs == 1, VERR_DBGC_PARSE_BUG);
+ NOREF(pFunc); NOREF(pCmdHlp); NOREF(pUVM);
+ return DBGCCmdHlpEval(pCmdHlp, pResult, "!(%Dv)", &paArgs[0]);
+}
+
+
+/** Generic pointer argument wo/ range. */
+static const DBGCVARDESC g_aArgPointerWoRange[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER_NO_RANGE, 0, "value", "Address or number." },
+};
+
+/** Generic pointer or number argument. */
+static const DBGCVARDESC g_aArgPointerNumber[] =
+{
+ /* cTimesMin, cTimesMax, enmCategory, fFlags, pszName, pszDescription */
+ { 1, 1, DBGCVAR_CAT_POINTER_NUMBER, 0, "value", "Address or number." },
+};
+
+
+
+/** Function descriptors for the CodeView / WinDbg emulation.
+ * The emulation isn't attempting to be identical, only somewhat similar.
+ */
+const DBGCFUNC g_aFuncsCodeView[] =
+{
+ { "by", 1, 1, &g_aArgPointerWoRange[0], RT_ELEMENTS(g_aArgPointerWoRange), 0, dbgcFuncReadU8, "address", "Reads a byte at the given address." },
+ { "dwo", 1, 1, &g_aArgPointerWoRange[0], RT_ELEMENTS(g_aArgPointerWoRange), 0, dbgcFuncReadU32, "address", "Reads a 32-bit value at the given address." },
+ { "hi", 1, 1, &g_aArgPointerNumber[0], RT_ELEMENTS(g_aArgPointerNumber), 0, dbgcFuncHi, "value", "Returns the high 16-bit bits of a value." },
+ { "low", 1, 1, &g_aArgPointerNumber[0], RT_ELEMENTS(g_aArgPointerNumber), 0, dbgcFuncLow, "value", "Returns the low 16-bit bits of a value." },
+ { "not", 1, 1, &g_aArgPointerNumber[0], RT_ELEMENTS(g_aArgPointerNumber), 0, dbgcFuncNot, "address", "Boolean NOT." },
+ { "poi", 1, 1, &g_aArgPointerWoRange[0], RT_ELEMENTS(g_aArgPointerWoRange), 0, dbgcFuncReadPtr, "address", "Reads a pointer sized (CS) value at the given address." },
+ { "qwo", 1, 1, &g_aArgPointerWoRange[0], RT_ELEMENTS(g_aArgPointerWoRange), 0, dbgcFuncReadU64, "address", "Reads a 32-bit value at the given address." },
+ { "wo", 1, 1, &g_aArgPointerWoRange[0], RT_ELEMENTS(g_aArgPointerWoRange), 0, dbgcFuncReadU16, "address", "Reads a 16-bit value at the given address." },
+};
+
+/** The number of functions in the CodeView/WinDbg emulation. */
+const uint32_t g_cFuncsCodeView = RT_ELEMENTS(g_aFuncsCodeView);
+
diff --git a/src/VBox/Debugger/DBGCEval.cpp b/src/VBox/Debugger/DBGCEval.cpp
new file mode 100644
index 00000000..687bb88f
--- /dev/null
+++ b/src/VBox/Debugger/DBGCEval.cpp
@@ -0,0 +1,1663 @@
+/* $Id: DBGCEval.cpp $ */
+/** @file
+ * DBGC - Debugger Console, command evaluator.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+#include <stdio.h>
+
+#include "DBGCInternal.h"
+
+/** Rewrite in progress. */
+#define BETTER_ARGUMENT_MATCHING
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Bitmap where set bits indicates the characters the may start an operator name. */
+static uint32_t g_bmOperatorChars[256 / (4*8)];
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int dbgcCheckAndTypePromoteArgument(PDBGC pDbgc, DBGCVARCAT enmCategory, PDBGCVAR pArg);
+static int dbgcProcessArguments(PDBGC pDbgc, const char *pszCmdOrFunc,
+ uint32_t const cArgsMin, uint32_t const cArgsMax,
+ PCDBGCVARDESC const paVarDescs, uint32_t const cVarDescs,
+ char *pszArgs, unsigned *piArg, unsigned *pcArgs);
+
+
+
+/**
+ * Initializes g_bmOperatorChars.
+ */
+void dbgcEvalInit(void)
+{
+ memset(g_bmOperatorChars, 0, sizeof(g_bmOperatorChars));
+ for (unsigned iOp = 0; iOp < g_cDbgcOps; iOp++)
+ ASMBitSet(&g_bmOperatorChars[0], (uint8_t)g_aDbgcOps[iOp].szName[0]);
+}
+
+
+/**
+ * Checks whether the character may be the start of an operator.
+ *
+ * @returns true/false.
+ * @param ch The character.
+ */
+DECLINLINE(bool) dbgcIsOpChar(char ch)
+{
+ return ASMBitTest(&g_bmOperatorChars[0], (uint8_t)ch);
+}
+
+
+/**
+ * Returns the amount of free scratch space.
+ *
+ * @returns Number of unallocated bytes.
+ * @param pDbgc The DBGC instance.
+ */
+size_t dbgcGetFreeScratchSpace(PDBGC pDbgc)
+{
+ return sizeof(pDbgc->achScratch) - (pDbgc->pszScratch - &pDbgc->achScratch[0]);
+}
+
+
+/**
+ * Allocates a string from the scratch space.
+ *
+ * @returns Pointer to the allocated string buffer, NULL if out of space.
+ * @param pDbgc The DBGC instance.
+ * @param cbRequested The number of bytes to allocate.
+ */
+char *dbgcAllocStringScatch(PDBGC pDbgc, size_t cbRequested)
+{
+ if (cbRequested > dbgcGetFreeScratchSpace(pDbgc))
+ return NULL;
+ char *psz = pDbgc->pszScratch;
+ pDbgc->pszScratch += cbRequested;
+ return psz;
+}
+
+
+/**
+ * Evals an expression into a string or symbol (single quotes).
+ *
+ * The string memory is allocated from the scratch buffer.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The DBGC instance.
+ * @param pachExpr The string/symbol expression.
+ * @param cchExpr The length of the expression.
+ * @param pArg Where to return the string.
+ */
+static int dbgcEvalSubString(PDBGC pDbgc, const char *pachExpr, size_t cchExpr, PDBGCVAR pArg)
+{
+ Log2(("dbgcEvalSubString: cchExpr=%d pachExpr=%.*s\n", cchExpr, cchExpr, pachExpr));
+
+ /*
+ * Allocate scratch space for the string.
+ */
+ char *pszCopy = dbgcAllocStringScatch(pDbgc, cchExpr + 1);
+ if (!pszCopy)
+ return VERR_DBGC_PARSE_NO_SCRATCH;
+
+ /*
+ * Removing any quoting and escapings.
+ */
+ char const chQuote = *pachExpr;
+ if (chQuote == '"' || chQuote == '\'')
+ {
+ if (pachExpr[--cchExpr] != chQuote)
+ return VERR_DBGC_PARSE_UNBALANCED_QUOTE;
+
+ cchExpr--;
+ pachExpr++;
+ if (!memchr(pachExpr, chQuote, cchExpr))
+ memcpy(pszCopy, pachExpr, cchExpr);
+ else
+ {
+ size_t offSrc = 0;
+ size_t offDst = 0;
+ while (offSrc < cchExpr)
+ {
+ char const ch = pachExpr[offSrc++];
+ if (ch == chQuote)
+ {
+ if (pachExpr[offSrc] != ch)
+ return VERR_DBGC_PARSE_EXPECTED_BINARY_OP;
+ offSrc++;
+ }
+ pszCopy[offDst++] = ch;
+ }
+ }
+ }
+ else
+ memcpy(pszCopy, pachExpr, cchExpr);
+ pszCopy[cchExpr] = '\0';
+
+ /*
+ * Make the argument.
+ */
+ pArg->pDesc = NULL;
+ pArg->pNext = NULL;
+ pArg->enmType = chQuote == '"' ? DBGCVAR_TYPE_STRING : DBGCVAR_TYPE_SYMBOL;
+ pArg->u.pszString = pszCopy;
+ pArg->enmRangeType = DBGCVAR_RANGE_BYTES;
+ pArg->u64Range = cchExpr;
+
+ NOREF(pDbgc);
+ return VINF_SUCCESS;
+}
+
+
+static int dbgcEvalSubNum(const char *pachExpr, size_t cchExpr, unsigned uBase, PDBGCVAR pArg)
+{
+ Log2(("dbgcEvalSubNum: uBase=%d pachExpr=%.*s\n", uBase, cchExpr, pachExpr));
+
+ /*
+ * Empty expressions cannot be valid numbers.
+ */
+ if (!cchExpr)
+ return VERR_DBGC_PARSE_INVALID_NUMBER;
+
+ /*
+ * Convert to number.
+ */
+ uint64_t u64 = 0;
+ while (cchExpr-- > 0)
+ {
+ char const ch = *pachExpr;
+ uint64_t u64Prev = u64;
+ unsigned u = ch - '0';
+ if (u < 10 && u < uBase)
+ u64 = u64 * uBase + u;
+ else if (ch >= 'a' && (u = ch - ('a' - 10)) < uBase)
+ u64 = u64 * uBase + u;
+ else if (ch >= 'A' && (u = ch - ('A' - 10)) < uBase)
+ u64 = u64 * uBase + u;
+ else
+ return VERR_DBGC_PARSE_INVALID_NUMBER;
+
+ /* check for overflow - ARG!!! How to detect overflow correctly!?!?!? */
+ if (u64Prev != u64 / uBase)
+ return VERR_DBGC_PARSE_NUMBER_TOO_BIG;
+
+ /* next */
+ pachExpr++;
+ }
+
+ /*
+ * Initialize the argument.
+ */
+ pArg->pDesc = NULL;
+ pArg->pNext = NULL;
+ pArg->enmType = DBGCVAR_TYPE_NUMBER;
+ pArg->u.u64Number = u64;
+ pArg->enmRangeType = DBGCVAR_RANGE_NONE;
+ pArg->u64Range = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * dbgcEvalSubUnary worker that handles simple numeric or pointer expressions.
+ *
+ * @returns VBox status code. pResult contains the result on success.
+ * @param pDbgc Debugger console instance data.
+ * @param pszExpr The expression string.
+ * @param cchExpr The length of the expression.
+ * @param enmCategory The desired type category (for range / no range).
+ * @param pResult Where to store the result of the expression evaluation.
+ */
+static int dbgcEvalSubNumericOrPointer(PDBGC pDbgc, char *pszExpr, size_t cchExpr, DBGCVARCAT enmCategory,
+ PDBGCVAR pResult)
+{
+ char const ch = pszExpr[0];
+ char const ch2 = pszExpr[1];
+
+ /* 0x<hex digits> */
+ if (ch == '0' && (ch2 == 'x' || ch2 == 'X'))
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 16, pResult);
+
+ /* <hex digits>h */
+ if (RT_C_IS_XDIGIT(*pszExpr) && (pszExpr[cchExpr - 1] == 'h' || pszExpr[cchExpr - 1] == 'H'))
+ {
+ pszExpr[cchExpr] = '\0';
+ return dbgcEvalSubNum(pszExpr, cchExpr - 1, 16, pResult);
+ }
+
+ /* 0i<decimal digits> */
+ if (ch == '0' && ch2 == 'i')
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 10, pResult);
+
+ /* 0t<octal digits> */
+ if (ch == '0' && ch2 == 't')
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 8, pResult);
+
+ /* 0y<binary digits> */
+ if (ch == '0' && ch2 == 'y')
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 10, pResult);
+
+ /* Hex number? */
+ unsigned off = 0;
+ while (off < cchExpr && (RT_C_IS_XDIGIT(pszExpr[off]) || pszExpr[off] == '`'))
+ off++;
+ if (off == cchExpr)
+ return dbgcEvalSubNum(pszExpr, cchExpr, 16, pResult);
+
+ /*
+ * Some kind of symbol? Rejected double quoted strings, only unquoted
+ * and single quoted strings will be considered as symbols.
+ */
+ DBGCVARTYPE enmType;
+ bool fStripRange = false;
+ switch (enmCategory)
+ {
+ case DBGCVAR_CAT_POINTER_NUMBER: enmType = DBGCVAR_TYPE_NUMBER; break;
+ case DBGCVAR_CAT_POINTER_NUMBER_NO_RANGE: enmType = DBGCVAR_TYPE_NUMBER; fStripRange = true; break;
+ case DBGCVAR_CAT_POINTER: enmType = DBGCVAR_TYPE_NUMBER; break;
+ case DBGCVAR_CAT_POINTER_NO_RANGE: enmType = DBGCVAR_TYPE_NUMBER; fStripRange = true; break;
+ case DBGCVAR_CAT_GC_POINTER: enmType = DBGCVAR_TYPE_GC_FLAT; break;
+ case DBGCVAR_CAT_GC_POINTER_NO_RANGE: enmType = DBGCVAR_TYPE_GC_FLAT; fStripRange = true; break;
+ case DBGCVAR_CAT_NUMBER: enmType = DBGCVAR_TYPE_NUMBER; break;
+ case DBGCVAR_CAT_NUMBER_NO_RANGE: enmType = DBGCVAR_TYPE_NUMBER; fStripRange = true; break;
+ default:
+ AssertFailedReturn(VERR_DBGC_PARSE_NOT_IMPLEMENTED);
+ }
+
+ char const chQuote = *pszExpr;
+ if (chQuote == '"')
+ return VERR_DBGC_PARSE_INVALID_NUMBER;
+
+ if (chQuote == '\'')
+ {
+ if (pszExpr[cchExpr - 1] != chQuote)
+ return VERR_DBGC_PARSE_UNBALANCED_QUOTE;
+ pszExpr[cchExpr - 1] = '\0';
+ pszExpr++;
+ }
+
+ int rc = dbgcSymbolGet(pDbgc, pszExpr, enmType, pResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (fStripRange)
+ {
+ pResult->enmRangeType = DBGCVAR_RANGE_NONE;
+ pResult->u64Range = 0;
+ }
+ }
+ else if (rc == VERR_DBGC_PARSE_NOT_IMPLEMENTED)
+ rc = VERR_DBGC_PARSE_INVALID_NUMBER;
+ return rc;
+}
+
+
+/**
+ * dbgcEvalSubUnary worker that handles simple DBGCVAR_CAT_ANY expressions.
+ *
+ * @returns VBox status code. pResult contains the result on success.
+ * @param pDbgc Debugger console instance data.
+ * @param pszExpr The expression string.
+ * @param cchExpr The length of the expression.
+ * @param pResult Where to store the result of the expression evaluation.
+ */
+static int dbgcEvalSubUnaryAny(PDBGC pDbgc, char *pszExpr, size_t cchExpr, PDBGCVAR pResult)
+{
+ char const ch = pszExpr[0];
+ char const ch2 = pszExpr[1];
+ unsigned off = 2;
+
+ /* 0x<hex digits> */
+ if (ch == '0' && (ch2 == 'x' || ch2 == 'X'))
+ {
+ while (RT_C_IS_XDIGIT(pszExpr[off]) || pszExpr[off] == '`')
+ off++;
+ if (off == cchExpr)
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 16, pResult);
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult);
+ }
+
+ /* <hex digits>h */
+ if (RT_C_IS_XDIGIT(*pszExpr) && (pszExpr[cchExpr - 1] == 'h' || pszExpr[cchExpr - 1] == 'H'))
+ {
+ cchExpr--;
+ while (off < cchExpr && (RT_C_IS_XDIGIT(pszExpr[off]) || pszExpr[off] == '`'))
+ off++;
+ if (off == cchExpr)
+ {
+ pszExpr[cchExpr] = '\0';
+ return dbgcEvalSubNum(pszExpr, cchExpr, 16, pResult);
+ }
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr + 1, pResult);
+ }
+
+ /* 0n<decimal digits> or 0i<decimal digits> */
+ if (ch == '0' && (ch2 == 'n' || ch2 == 'i'))
+ {
+ while (RT_C_IS_DIGIT(pszExpr[off]) || pszExpr[off] == '`')
+ off++;
+ if (off == cchExpr)
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 10, pResult);
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult);
+ }
+
+ /* 0t<octal digits> */
+ if (ch == '0' && ch2 == 't')
+ {
+ while (RT_C_IS_ODIGIT(pszExpr[off]) || pszExpr[off] == '`')
+ off++;
+ if (off == cchExpr)
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 8, pResult);
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult);
+ }
+
+ /* 0y<binary digits> */
+ if (ch == '0' && ch2 == 'y')
+ {
+ while (pszExpr[off] == '0' || pszExpr[off] == '1' || pszExpr[off] == '`')
+ off++;
+ if (off == cchExpr)
+ return dbgcEvalSubNum(pszExpr + 2, cchExpr - 2, 10, pResult);
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult);
+ }
+
+ /* Ok, no prefix of suffix. Is it a hex number after all? If not it must
+ be a string. */
+ off = 0;
+ while (RT_C_IS_XDIGIT(pszExpr[off]) || pszExpr[off] == '`')
+ off++;
+ if (off == cchExpr)
+ return dbgcEvalSubNum(pszExpr, cchExpr, 16, pResult);
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult);
+}
+
+
+/**
+ * Handles a call.
+ *
+ * @returns VBox status code. pResult contains the result on success.
+ * @param pDbgc The DBGC instance.
+ * @param pszFuncNm The function name.
+ * @param cchFuncNm The length of the function name.
+ * @param fExternal Whether it's an external name.
+ * @param pszArgs The start of the arguments (after parenthesis).
+ * @param cchArgs The length for the argument (excluding
+ * parentesis).
+ * @param enmCategory The desired category of the result (ignored).
+ * @param pResult The result.
+ */
+static int dbgcEvalSubCall(PDBGC pDbgc, char *pszFuncNm, size_t cchFuncNm, bool fExternal, char *pszArgs, size_t cchArgs,
+ DBGCVARCAT enmCategory, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCategory);
+
+ /*
+ * Lookup the function.
+ */
+ PCDBGCFUNC pFunc = dbgcFunctionLookup(pDbgc, pszFuncNm, cchFuncNm, fExternal);
+ if (!pFunc)
+ return VERR_DBGC_PARSE_FUNCTION_NOT_FOUND;
+
+ /*
+ * Parse the arguments.
+ */
+ unsigned cArgs;
+ unsigned iArg;
+ pszArgs[cchArgs] = '\0';
+ int rc = dbgcProcessArguments(pDbgc, pFunc->pszFuncNm,
+ pFunc->cArgsMin, pFunc->cArgsMax, pFunc->paArgDescs, pFunc->cArgDescs,
+ pszArgs, &iArg, &cArgs);
+ if (RT_SUCCESS(rc))
+ rc = pFunc->pfnHandler(pFunc, &pDbgc->CmdHlp, pDbgc->pUVM, &pDbgc->aArgs[iArg], cArgs, pResult);
+ pDbgc->iArg = iArg;
+ return rc;
+}
+
+
+/**
+ * Evaluates one argument with respect to unary operators.
+ *
+ * @returns VBox status code. pResult contains the result on success.
+ *
+ * @param pDbgc Debugger console instance data.
+ * @param pszExpr The expression string.
+ * @param cchExpr The length of the expression.
+ * @param enmCategory The target category for the result.
+ * @param pResult Where to store the result of the expression evaluation.
+ */
+static int dbgcEvalSubUnary(PDBGC pDbgc, char *pszExpr, size_t cchExpr, DBGCVARCAT enmCategory, PDBGCVAR pResult)
+{
+ Log2(("dbgcEvalSubUnary: cchExpr=%d pszExpr=%s\n", cchExpr, pszExpr));
+
+ /*
+ * The state of the expression is now such that it will start by zero or more
+ * unary operators and being followed by an expression of some kind.
+ * The expression is either plain or in parenthesis.
+ *
+ * Being in a lazy, recursive mode today, the parsing is done as simple as possible. :-)
+ * ASSUME: unary operators are all of equal precedence.
+ */
+ int rc = VINF_SUCCESS;
+ PCDBGCOP pOp = dbgcOperatorLookup(pDbgc, pszExpr, false, ' ');
+ if (pOp)
+ {
+ /* binary operators means syntax error. */
+ if (pOp->fBinary)
+ return VERR_DBGC_PARSE_UNEXPECTED_OPERATOR;
+
+ /*
+ * If the next expression (the one following the unary operator) is in a
+ * parenthesis a full eval is needed. If not the unary eval will suffice.
+ */
+ /* calc and strip next expr. */
+ char *pszExpr2 = pszExpr + pOp->cchName;
+ while (RT_C_IS_BLANK(*pszExpr2))
+ pszExpr2++;
+
+ if (*pszExpr2)
+ {
+ DBGCVAR Arg;
+ if (*pszExpr2 == '(')
+ rc = dbgcEvalSub(pDbgc, pszExpr2, cchExpr - (pszExpr2 - pszExpr), pOp->enmCatArg1, &Arg);
+ else
+ rc = dbgcEvalSubUnary(pDbgc, pszExpr2, cchExpr - (pszExpr2 - pszExpr), pOp->enmCatArg1, &Arg);
+ if (RT_SUCCESS(rc))
+ rc = dbgcCheckAndTypePromoteArgument(pDbgc, pOp->enmCatArg1, &Arg);
+ if (RT_SUCCESS(rc))
+ rc = pOp->pfnHandlerUnary(pDbgc, &Arg, enmCategory, pResult);
+ }
+ else
+ rc = VERR_DBGC_PARSE_EMPTY_ARGUMENT;
+ return rc;
+ }
+
+ /*
+ * Could this be a function call?
+ *
+ * ASSUMPTIONS:
+ * - A function name only contains alphanumerical chars and it can not
+ * start with a numerical character.
+ * - Immediately following the name is a parenthesis which must cover
+ * the remaining part of the expression.
+ */
+ bool fExternal = *pszExpr == '.';
+ char *pszFun = fExternal ? pszExpr + 1 : pszExpr;
+ char *pszFunEnd = NULL;
+ if (pszExpr[cchExpr - 1] == ')' && RT_C_IS_ALPHA(*pszFun))
+ {
+ pszFunEnd = pszExpr + 1;
+ while (*pszFunEnd != '(' && RT_C_IS_ALNUM(*pszFunEnd))
+ pszFunEnd++;
+ if (*pszFunEnd != '(')
+ pszFunEnd = NULL;
+ }
+ if (pszFunEnd)
+ {
+ size_t cchFunNm = pszFunEnd - pszFun;
+ return dbgcEvalSubCall(pDbgc, pszFun, cchFunNm, fExternal, pszFunEnd + 1, cchExpr - cchFunNm - fExternal - 2,
+ enmCategory, pResult);
+ }
+
+ /*
+ * Assuming plain expression.
+ * Didn't find any operators, so it must be a plain expression.
+ * Go by desired category first, then if anythings go, try guess.
+ */
+ switch (enmCategory)
+ {
+ case DBGCVAR_CAT_ANY:
+ return dbgcEvalSubUnaryAny(pDbgc, pszExpr, cchExpr, pResult);
+
+ case DBGCVAR_CAT_POINTER_NUMBER:
+ case DBGCVAR_CAT_POINTER_NUMBER_NO_RANGE:
+ case DBGCVAR_CAT_POINTER:
+ case DBGCVAR_CAT_POINTER_NO_RANGE:
+ case DBGCVAR_CAT_GC_POINTER:
+ case DBGCVAR_CAT_GC_POINTER_NO_RANGE:
+ case DBGCVAR_CAT_NUMBER:
+ case DBGCVAR_CAT_NUMBER_NO_RANGE:
+ /* Pointers will be promoted later. */
+ return dbgcEvalSubNumericOrPointer(pDbgc, pszExpr, cchExpr, enmCategory, pResult);
+
+ case DBGCVAR_CAT_STRING:
+ case DBGCVAR_CAT_SYMBOL:
+ /* Symbols will be promoted later. */
+ return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult);
+
+ case DBGCVAR_CAT_OPTION:
+ case DBGCVAR_CAT_OPTION_STRING:
+ case DBGCVAR_CAT_OPTION_NUMBER:
+ return VERR_DBGC_PARSE_NOT_IMPLEMENTED;
+ }
+
+ AssertMsgFailed(("enmCategory=%d\n", enmCategory));
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Evaluates one argument.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDbgc Debugger console instance data.
+ * @param pszExpr The expression string.
+ * @param cchExpr The size of the expression string.
+ * @param enmCategory The target category for the result.
+ * @param pResult Where to store the result of the expression evaluation.
+ */
+int dbgcEvalSub(PDBGC pDbgc, char *pszExpr, size_t cchExpr, DBGCVARCAT enmCategory, PDBGCVAR pResult)
+{
+ Log2(("dbgcEvalSub: cchExpr=%d pszExpr=%s\n", cchExpr, pszExpr));
+
+ /*
+ * First we need to remove blanks in both ends.
+ * ASSUMES: There is no quoting unless the entire expression is a string.
+ */
+
+ /* stripping. */
+ while (cchExpr > 0 && RT_C_IS_BLANK(pszExpr[cchExpr - 1]))
+ pszExpr[--cchExpr] = '\0';
+ while (RT_C_IS_BLANK(*pszExpr))
+ pszExpr++, cchExpr--;
+ if (!*pszExpr)
+ return VERR_DBGC_PARSE_EMPTY_ARGUMENT;
+
+ /*
+ * Check if there are any parenthesis which needs removing.
+ */
+ if (pszExpr[0] == '(' && pszExpr[cchExpr - 1] == ')')
+ {
+ do
+ {
+ unsigned cPar = 1;
+ char *psz = pszExpr + 1;
+ char ch;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == '(')
+ cPar++;
+ else if (ch == ')')
+ {
+ if (cPar <= 0)
+ return VERR_DBGC_PARSE_UNBALANCED_PARENTHESIS;
+ cPar--;
+ if (cPar == 0 && psz[1]) /* If not at end, there's nothing to do. */
+ break;
+ }
+ /* next */
+ psz++;
+ }
+ if (ch)
+ break;
+
+ /* remove the parenthesis. */
+ pszExpr++;
+ cchExpr -= 2;
+ pszExpr[cchExpr] = '\0';
+
+ /* strip blanks. */
+ while (cchExpr > 0 && RT_C_IS_BLANK(pszExpr[cchExpr - 1]))
+ pszExpr[--cchExpr] = '\0';
+ while (RT_C_IS_BLANK(*pszExpr))
+ pszExpr++, cchExpr--;
+ if (!*pszExpr)
+ return VERR_DBGC_PARSE_EMPTY_ARGUMENT;
+ } while (pszExpr[0] == '(' && pszExpr[cchExpr - 1] == ')');
+ }
+
+ /*
+ * Now, we need to look for the binary operator with the lowest precedence.
+ *
+ * If there are no operators we're left with a simple expression which we
+ * evaluate with respect to unary operators
+ */
+ char *pszOpSplit = NULL;
+ PCDBGCOP pOpSplit = NULL;
+ unsigned cBinaryOps = 0;
+ unsigned cPar = 0;
+ unsigned cchWord = 0;
+ char chQuote = '\0';
+ char chPrev = ' ';
+ bool fBinary = false;
+ char *psz = pszExpr;
+ char ch;
+
+ while ((ch = *psz) != '\0')
+ {
+ /*
+ * String quoting.
+ */
+ if (chQuote)
+ {
+ if (ch == chQuote)
+ {
+ if (psz[1] == chQuote)
+ {
+ psz++; /* escaped quote */
+ cchWord++;
+ }
+ else
+ {
+ chQuote = '\0';
+ fBinary = true;
+ cchWord = 0;
+ }
+ }
+ else
+ cchWord++;
+ }
+ else if (ch == '"' || ch == '\'')
+ {
+ if (fBinary || cchWord)
+ return VERR_DBGC_PARSE_EXPECTED_BINARY_OP;
+ chQuote = ch;
+ }
+ /*
+ * Parentheses.
+ */
+ else if (ch == '(')
+ {
+ if (!cPar && fBinary && !cchWord)
+ return VERR_DBGC_PARSE_EXPECTED_BINARY_OP;
+ cPar++;
+ fBinary = false;
+ cchWord = 0;
+ }
+ else if (ch == ')')
+ {
+ if (cPar <= 0)
+ return VERR_DBGC_PARSE_UNBALANCED_PARENTHESIS;
+ cPar--;
+ fBinary = true;
+ cchWord = 0;
+ }
+ /*
+ * Potential operator.
+ */
+ else if (cPar == 0 && !RT_C_IS_BLANK(ch))
+ {
+ PCDBGCOP pOp = dbgcIsOpChar(ch)
+ ? dbgcOperatorLookup(pDbgc, psz, fBinary, chPrev)
+ : NULL;
+ if (pOp)
+ {
+ /* If not the right kind of operator we've got a syntax error. */
+ if (pOp->fBinary != fBinary)
+ return VERR_DBGC_PARSE_UNEXPECTED_OPERATOR;
+
+ /*
+ * Update the parse state and skip the operator.
+ */
+ if (!pOpSplit)
+ {
+ pOpSplit = pOp;
+ pszOpSplit = psz;
+ cBinaryOps = fBinary;
+ }
+ else if (fBinary)
+ {
+ cBinaryOps++;
+ if (pOp->iPrecedence >= pOpSplit->iPrecedence)
+ {
+ pOpSplit = pOp;
+ pszOpSplit = psz;
+ }
+ }
+
+ psz += pOp->cchName - 1;
+ fBinary = false;
+ cchWord = 0;
+ }
+ else if (fBinary && !cchWord)
+ return VERR_DBGC_PARSE_EXPECTED_BINARY_OP;
+ else
+ {
+ fBinary = true;
+ cchWord++;
+ }
+ }
+ else if (cPar == 0 && RT_C_IS_BLANK(ch))
+ cchWord++;
+
+ /* next */
+ psz++;
+ chPrev = ch;
+ } /* parse loop. */
+
+ if (chQuote)
+ return VERR_DBGC_PARSE_UNBALANCED_QUOTE;
+
+ /*
+ * Either we found an operator to divide the expression by or we didn't
+ * find any. In the first case it's divide and conquer. In the latter
+ * it's a single expression which needs dealing with its unary operators
+ * if any.
+ */
+ int rc;
+ if ( cBinaryOps
+ && pOpSplit->fBinary)
+ {
+ /* process 1st sub expression. */
+ *pszOpSplit = '\0';
+ DBGCVAR Arg1;
+ rc = dbgcEvalSub(pDbgc, pszExpr, pszOpSplit - pszExpr, pOpSplit->enmCatArg1, &Arg1);
+ if (RT_SUCCESS(rc))
+ {
+ /* process 2nd sub expression. */
+ char *psz2 = pszOpSplit + pOpSplit->cchName;
+ DBGCVAR Arg2;
+ rc = dbgcEvalSub(pDbgc, psz2, cchExpr - (psz2 - pszExpr), pOpSplit->enmCatArg2, &Arg2);
+ if (RT_SUCCESS(rc))
+ rc = dbgcCheckAndTypePromoteArgument(pDbgc, pOpSplit->enmCatArg1, &Arg1);
+ if (RT_SUCCESS(rc))
+ rc = dbgcCheckAndTypePromoteArgument(pDbgc, pOpSplit->enmCatArg2, &Arg2);
+ if (RT_SUCCESS(rc))
+ rc = pOpSplit->pfnHandlerBinary(pDbgc, &Arg1, &Arg2, pResult);
+ }
+ }
+ else if (cBinaryOps)
+ {
+ /* process sub expression. */
+ pszOpSplit += pOpSplit->cchName;
+ DBGCVAR Arg;
+ rc = dbgcEvalSub(pDbgc, pszOpSplit, cchExpr - (pszOpSplit - pszExpr), pOpSplit->enmCatArg1, &Arg);
+ if (RT_SUCCESS(rc))
+ rc = dbgcCheckAndTypePromoteArgument(pDbgc, pOpSplit->enmCatArg1, &Arg);
+ if (RT_SUCCESS(rc))
+ rc = pOpSplit->pfnHandlerUnary(pDbgc, &Arg, enmCategory, pResult);
+ }
+ else
+ /* plain expression, quoted string, or using unary operators perhaps with parentheses. */
+ rc = dbgcEvalSubUnary(pDbgc, pszExpr, cchExpr, enmCategory, pResult);
+
+ return rc;
+}
+
+
+/**
+ * Worker for dbgcProcessArguments that performs type checking and promoptions.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDbgc Debugger console instance data.
+ * @param enmCategory The target category for the result.
+ * @param pArg The argument to check and promote.
+ */
+static int dbgcCheckAndTypePromoteArgument(PDBGC pDbgc, DBGCVARCAT enmCategory, PDBGCVAR pArg)
+{
+ switch (enmCategory)
+ {
+ /*
+ * Anything goes
+ */
+ case DBGCVAR_CAT_ANY:
+ return VINF_SUCCESS;
+
+ /*
+ * Pointer with and without range.
+ * We can try resolve strings and symbols as symbols and promote
+ * numbers to flat GC pointers.
+ */
+ case DBGCVAR_CAT_POINTER_NO_RANGE:
+ case DBGCVAR_CAT_POINTER_NUMBER_NO_RANGE:
+ if (pArg->enmRangeType != DBGCVAR_RANGE_NONE)
+ return VERR_DBGC_PARSE_NO_RANGE_ALLOWED;
+ RT_FALL_THRU();
+ case DBGCVAR_CAT_POINTER:
+ case DBGCVAR_CAT_POINTER_NUMBER:
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ case DBGCVAR_TYPE_STRING:
+ {
+ DBGCVAR Var;
+ int rc = dbgcSymbolGet(pDbgc, pArg->u.pszString, DBGCVAR_TYPE_GC_FLAT, &Var);
+ if (RT_SUCCESS(rc))
+ {
+ /* deal with range */
+ if (pArg->enmRangeType != DBGCVAR_RANGE_NONE)
+ {
+ Var.enmRangeType = pArg->enmRangeType;
+ Var.u64Range = pArg->u64Range;
+ }
+ else if (enmCategory == DBGCVAR_CAT_POINTER_NO_RANGE)
+ Var.enmRangeType = DBGCVAR_RANGE_NONE;
+ *pArg = Var;
+ }
+ return rc;
+ }
+
+ case DBGCVAR_TYPE_NUMBER:
+ if ( enmCategory != DBGCVAR_CAT_POINTER_NUMBER
+ && enmCategory != DBGCVAR_CAT_POINTER_NUMBER_NO_RANGE)
+ {
+ RTGCPTR GCPtr = (RTGCPTR)pArg->u.u64Number;
+ pArg->enmType = DBGCVAR_TYPE_GC_FLAT;
+ pArg->u.GCFlat = GCPtr;
+ }
+ return VINF_SUCCESS;
+
+ default:
+ AssertMsgFailedReturn(("Invalid type %d\n", pArg->enmType), VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ }
+ break; /* (not reached) */
+
+ /*
+ * GC pointer with and without range.
+ * We can try resolve strings and symbols as symbols and
+ * promote numbers to flat GC pointers.
+ */
+ case DBGCVAR_CAT_GC_POINTER_NO_RANGE:
+ if (pArg->enmRangeType != DBGCVAR_RANGE_NONE)
+ return VERR_DBGC_PARSE_NO_RANGE_ALLOWED;
+ RT_FALL_THRU();
+ case DBGCVAR_CAT_GC_POINTER:
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_PHYS:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ case DBGCVAR_TYPE_STRING:
+ {
+ DBGCVAR Var;
+ int rc = dbgcSymbolGet(pDbgc, pArg->u.pszString, DBGCVAR_TYPE_GC_FLAT, &Var);
+ if (RT_SUCCESS(rc))
+ {
+ /* deal with range */
+ if (pArg->enmRangeType != DBGCVAR_RANGE_NONE)
+ {
+ Var.enmRangeType = pArg->enmRangeType;
+ Var.u64Range = pArg->u64Range;
+ }
+ else if (enmCategory == DBGCVAR_CAT_POINTER_NO_RANGE)
+ Var.enmRangeType = DBGCVAR_RANGE_NONE;
+ *pArg = Var;
+ }
+ return rc;
+ }
+
+ case DBGCVAR_TYPE_NUMBER:
+ {
+ RTGCPTR GCPtr = (RTGCPTR)pArg->u.u64Number;
+ pArg->enmType = DBGCVAR_TYPE_GC_FLAT;
+ pArg->u.GCFlat = GCPtr;
+ return VINF_SUCCESS;
+ }
+
+ default:
+ AssertMsgFailedReturn(("Invalid type %d\n", pArg->enmType), VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ }
+ break; /* (not reached) */
+
+ /*
+ * Number with or without a range.
+ * Numbers can be resolved from symbols, but we cannot demote a pointer
+ * to a number.
+ */
+ case DBGCVAR_CAT_NUMBER_NO_RANGE:
+ if (pArg->enmRangeType != DBGCVAR_RANGE_NONE)
+ return VERR_DBGC_PARSE_NO_RANGE_ALLOWED;
+ RT_FALL_THRU();
+ case DBGCVAR_CAT_NUMBER:
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+
+ case DBGCVAR_TYPE_NUMBER:
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ case DBGCVAR_TYPE_STRING:
+ {
+ DBGCVAR Var;
+ int rc = dbgcSymbolGet(pDbgc, pArg->u.pszString, DBGCVAR_TYPE_NUMBER, &Var);
+ if (RT_SUCCESS(rc))
+ {
+ /* deal with range */
+ if (pArg->enmRangeType != DBGCVAR_RANGE_NONE)
+ {
+ Var.enmRangeType = pArg->enmRangeType;
+ Var.u64Range = pArg->u64Range;
+ }
+ else if (enmCategory == DBGCVAR_CAT_POINTER_NO_RANGE)
+ Var.enmRangeType = DBGCVAR_RANGE_NONE;
+ *pArg = Var;
+ }
+ return rc;
+ }
+
+ default:
+ AssertMsgFailedReturn(("Invalid type %d\n", pArg->enmType), VERR_DBGC_PARSE_INCORRECT_ARG_TYPE);
+ }
+ break; /* (not reached) */
+
+ /*
+ * Symbols and strings are basically the same thing for the time being.
+ */
+ case DBGCVAR_CAT_STRING:
+ case DBGCVAR_CAT_SYMBOL:
+ {
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_STRING:
+ if (enmCategory == DBGCVAR_CAT_SYMBOL)
+ pArg->enmType = DBGCVAR_TYPE_SYMBOL;
+ return VINF_SUCCESS;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ if (enmCategory == DBGCVAR_CAT_STRING)
+ pArg->enmType = DBGCVAR_TYPE_STRING;
+ return VINF_SUCCESS;
+ default:
+ break;
+ }
+
+ /* Stringify numeric and pointer values. */
+ size_t cbScratch = sizeof(pDbgc->achScratch) - (pDbgc->pszScratch - &pDbgc->achScratch[0]);
+ size_t cch = pDbgc->CmdHlp.pfnStrPrintf(&pDbgc->CmdHlp, pDbgc->pszScratch, cbScratch, "%Dv", pArg);
+ if (cch + 1 >= cbScratch)
+ return VERR_DBGC_PARSE_NO_SCRATCH;
+
+ pArg->enmType = enmCategory == DBGCVAR_CAT_STRING ? DBGCVAR_TYPE_STRING : DBGCVAR_TYPE_SYMBOL;
+ pArg->u.pszString = pDbgc->pszScratch;
+ pArg->enmRangeType = DBGCVAR_RANGE_BYTES;
+ pArg->u64Range = cch;
+
+ pDbgc->pszScratch += cch + 1;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * These are not yet implemented.
+ */
+ case DBGCVAR_CAT_OPTION:
+ case DBGCVAR_CAT_OPTION_STRING:
+ case DBGCVAR_CAT_OPTION_NUMBER:
+ AssertMsgFailedReturn(("Not implemented enmCategory=%d\n", enmCategory), VERR_DBGC_PARSE_NOT_IMPLEMENTED);
+
+ default:
+ AssertMsgFailedReturn(("Bad enmCategory=%d\n", enmCategory), VERR_DBGC_PARSE_NOT_IMPLEMENTED);
+ }
+}
+
+
+/**
+ * Parses the arguments of one command.
+ *
+ * @returns VBox statuc code. On parser errors the index of the troublesome
+ * argument is indicated by *pcArg.
+ *
+ * @param pDbgc Debugger console instance data.
+ * @param pszCmdOrFunc The name of the function or command. (For logging.)
+ * @param cArgsMin See DBGCCMD::cArgsMin and DBGCFUNC::cArgsMin.
+ * @param cArgsMax See DBGCCMD::cArgsMax and DBGCFUNC::cArgsMax.
+ * @param paVarDescs See DBGCCMD::paVarDescs and DBGCFUNC::paVarDescs.
+ * @param cVarDescs See DBGCCMD::cVarDescs and DBGCFUNC::cVarDescs.
+ * @param pszArgs Pointer to the arguments to parse.
+ * @param piArg Where to return the index of the first argument in
+ * DBGC::aArgs. Always set. Caller must restore DBGC::iArg
+ * to this value when done, even on failure.
+ * @param pcArgs Where to store the number of arguments. In the event
+ * of an error this is (ab)used to store the index of the
+ * offending argument.
+ */
+static int dbgcProcessArguments(PDBGC pDbgc, const char *pszCmdOrFunc,
+ uint32_t const cArgsMin, uint32_t const cArgsMax,
+ PCDBGCVARDESC const paVarDescs, uint32_t const cVarDescs,
+ char *pszArgs, unsigned *piArg, unsigned *pcArgs)
+{
+ RT_NOREF1(pszCmdOrFunc);
+ Log2(("dbgcProcessArguments: pszCmdOrFunc=%s pszArgs='%s'\n", pszCmdOrFunc, pszArgs));
+
+ /*
+ * Check if we have any argument and if the command takes any.
+ */
+ *piArg = pDbgc->iArg;
+ *pcArgs = 0;
+ /* strip leading blanks. */
+ while (*pszArgs && RT_C_IS_BLANK(*pszArgs))
+ pszArgs++;
+ if (!*pszArgs)
+ {
+ if (!cArgsMin)
+ return VINF_SUCCESS;
+ return VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS;
+ }
+ if (!cArgsMax)
+ return VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS;
+
+ /*
+ * The parse loop.
+ */
+ PDBGCVAR pArg = &pDbgc->aArgs[pDbgc->iArg];
+ PCDBGCVARDESC pPrevDesc = NULL;
+ unsigned cCurDesc = 0;
+ unsigned iVar = 0;
+ unsigned iVarDesc = 0;
+ *pcArgs = 0;
+ do
+ {
+ /*
+ * Can we have another argument?
+ */
+ if (*pcArgs >= cArgsMax)
+ return VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS;
+ if (pDbgc->iArg >= RT_ELEMENTS(pDbgc->aArgs))
+ return VERR_DBGC_PARSE_ARGUMENT_OVERFLOW;
+ if (iVarDesc >= cVarDescs)
+ return VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS;
+
+ /* Walk argument descriptors. */
+ if (cCurDesc >= paVarDescs[iVarDesc].cTimesMax)
+ {
+ iVarDesc++;
+ if (iVarDesc >= cVarDescs)
+ return VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS;
+ cCurDesc = 0;
+ }
+
+ /*
+ * Find the end of the argument. This is just rough splitting,
+ * dbgcEvalSub will do stricter syntax checking later on.
+ */
+ int cPar = 0;
+ char chQuote = '\0';
+ char *pszEnd = NULL;
+ char *psz = pszArgs;
+ char ch;
+ bool fBinary = false;
+ for (;;)
+ {
+ /*
+ * Check for the end.
+ */
+ if ((ch = *psz) == '\0')
+ {
+ if (chQuote)
+ return VERR_DBGC_PARSE_UNBALANCED_QUOTE;
+ if (cPar)
+ return VERR_DBGC_PARSE_UNBALANCED_PARENTHESIS;
+ pszEnd = psz;
+ break;
+ }
+ /*
+ * When quoted we ignore everything but the quotation char.
+ * We use the REXX way of escaping the quotation char, i.e. double occurrence.
+ */
+ else if (chQuote)
+ {
+ if (ch == chQuote)
+ {
+ if (psz[1] == chQuote)
+ psz++; /* skip the escaped quote char */
+ else
+ {
+ chQuote = '\0'; /* end of quoted string. */
+ fBinary = true;
+ }
+ }
+ }
+ else if (ch == '\'' || ch == '"')
+ {
+ if (fBinary)
+ return VERR_DBGC_PARSE_EXPECTED_BINARY_OP;
+ chQuote = ch;
+ }
+ /*
+ * Parenthesis can of course be nested.
+ */
+ else if (ch == '(')
+ {
+ cPar++;
+ fBinary = false;
+ }
+ else if (ch == ')')
+ {
+ if (!cPar)
+ return VERR_DBGC_PARSE_UNBALANCED_PARENTHESIS;
+ cPar--;
+ fBinary = true;
+ }
+ else if (!cPar)
+ {
+ /*
+ * Encountering a comma is a definite end of parameter.
+ */
+ if (ch == ',')
+ {
+ pszEnd = psz++;
+ break;
+ }
+
+ /*
+ * Encountering blanks may mean the end of it all. A binary
+ * operator will force continued parsing.
+ */
+ if (RT_C_IS_BLANK(ch))
+ {
+ pszEnd = psz++; /* in case it's the end. */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+
+ if (*psz == ',')
+ {
+ psz++;
+ break;
+ }
+
+ PCDBGCOP pOp = dbgcOperatorLookup(pDbgc, psz, fBinary, ' ');
+ if (!pOp || pOp->fBinary != fBinary)
+ break; /* the end. */
+
+ psz += pOp->cchName;
+ while (RT_C_IS_BLANK(*psz)) /* skip blanks so we don't get here again */
+ psz++;
+ fBinary = false;
+ continue;
+ }
+
+ /*
+ * Look for operators without a space up front.
+ */
+ if (dbgcIsOpChar(ch))
+ {
+ PCDBGCOP pOp = dbgcOperatorLookup(pDbgc, psz, fBinary, ' ');
+ if (pOp)
+ {
+ if (pOp->fBinary != fBinary)
+ {
+ pszEnd = psz;
+ /** @todo this is a parsing error really. */
+ break; /* the end. */
+ }
+ psz += pOp->cchName;
+ while (RT_C_IS_BLANK(*psz)) /* skip blanks so we don't get here again */
+ psz++;
+ fBinary = false;
+ continue;
+ }
+ }
+ fBinary = true;
+ }
+
+ /* next char */
+ psz++;
+ }
+ *pszEnd = '\0';
+ /* (psz = next char to process) */
+ size_t cchArgs = strlen(pszArgs);
+
+ /*
+ * Try optional arguments until we find something which matches
+ * or can easily be promoted to what the descriptor want.
+ */
+ for (;;)
+ {
+ char *pszArgsCopy = (char *)RTMemDup(pszArgs, cchArgs + 1);
+ if (!pszArgsCopy)
+ return VERR_DBGC_PARSE_NO_MEMORY;
+
+ int rc = dbgcEvalSub(pDbgc, pszArgs, cchArgs, paVarDescs[iVarDesc].enmCategory, pArg);
+ if (RT_SUCCESS(rc))
+ rc = dbgcCheckAndTypePromoteArgument(pDbgc, paVarDescs[iVarDesc].enmCategory, pArg);
+ if (RT_SUCCESS(rc))
+ {
+ pArg->pDesc = pPrevDesc = &paVarDescs[iVarDesc];
+ cCurDesc++;
+ RTMemFree(pszArgsCopy);
+ break;
+ }
+
+ memcpy(pszArgs, pszArgsCopy, cchArgs + 1);
+ RTMemFree(pszArgsCopy);
+
+ /* Continue searching optional descriptors? */
+ if ( rc != VERR_DBGC_PARSE_INCORRECT_ARG_TYPE
+ && rc != VERR_DBGC_PARSE_INVALID_NUMBER
+ && rc != VERR_DBGC_PARSE_NO_RANGE_ALLOWED
+ )
+ return rc;
+
+ /* Try advance to the next descriptor. */
+ if (paVarDescs[iVarDesc].cTimesMin > cCurDesc)
+ return rc;
+ iVarDesc++;
+ if (!cCurDesc)
+ while ( iVarDesc < cVarDescs
+ && (paVarDescs[iVarDesc].fFlags & DBGCVD_FLAGS_DEP_PREV))
+ iVarDesc++;
+ if (iVarDesc >= cVarDescs)
+ return rc;
+ cCurDesc = 0;
+ }
+
+ /*
+ * Next argument.
+ */
+ iVar++;
+ pArg++;
+ pDbgc->iArg++;
+ *pcArgs += 1;
+ pszArgs = psz;
+ while (*pszArgs && RT_C_IS_BLANK(*pszArgs))
+ pszArgs++;
+ } while (*pszArgs);
+
+ /*
+ * Check that the rest of the argument descriptors indicate optional args.
+ */
+ if (iVarDesc < cVarDescs)
+ {
+ if (cCurDesc < paVarDescs[iVarDesc].cTimesMin)
+ return VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS;
+ iVarDesc++;
+ while (iVarDesc < cVarDescs)
+ {
+ if (paVarDescs[iVarDesc].cTimesMin)
+ return VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS;
+ iVarDesc++;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Evaluate one command.
+ *
+ * @returns VBox status code. This is also stored in DBGC::rcCmd.
+ *
+ * @param pDbgc Debugger console instance data.
+ * @param pszCmd Pointer to the command.
+ * @param cchCmd Length of the command.
+ * @param fNoExecute Indicates that no commands should actually be executed.
+ */
+int dbgcEvalCommand(PDBGC pDbgc, char *pszCmd, size_t cchCmd, bool fNoExecute)
+{
+ Assert(RTStrNLen(pszCmd, cchCmd) == cchCmd);
+ char *pszCmdInput = pszCmd;
+
+ /*
+ * Skip blanks.
+ */
+ while (RT_C_IS_BLANK(*pszCmd))
+ pszCmd++, cchCmd--;
+
+ /* external command? */
+ bool const fExternal = *pszCmd == '.';
+ if (fExternal)
+ pszCmd++, cchCmd--;
+
+ /*
+ * Find the end of the command name.
+ */
+ size_t cchName = 0;
+ while (cchName < cchCmd)
+ {
+ char const ch = pszCmd[cchName];
+ if (RT_C_IS_ALNUM(ch) || ch == '_')
+ cchName++;
+ else if (RT_C_IS_SPACE(ch))
+ break;
+ else
+ {
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Invalid command '%s'!\n", pszCmdInput);
+ return pDbgc->rcCmd = VERR_DBGC_PARSE_INVALD_COMMAND_NAME;
+ }
+ }
+
+ /*
+ * Find the command.
+ */
+ PCDBGCCMD pCmd = dbgcCommandLookup(pDbgc, pszCmd, cchName, fExternal);
+ if (!pCmd)
+ {
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Unknown command '%s'!\n", pszCmdInput);
+ return pDbgc->rcCmd = VERR_DBGC_PARSE_COMMAND_NOT_FOUND;
+ }
+
+ /*
+ * Parse arguments (if any).
+ *
+ * If the input isn't zero terminated, we have to make a copy because the
+ * argument parser code is to crappy to deal with sub-strings at present.
+ */
+ size_t offArgs = cchName;
+ while (offArgs < cchCmd && RT_C_IS_SPACE(pszCmd[offArgs]))
+ offArgs++;
+
+ char szEmpty[] = "";
+ char *pszArgsFree = NULL;
+ char *pszArgs = offArgs < cchCmd ? &pszCmd[offArgs] : szEmpty;
+ if (pszArgs[cchCmd - offArgs] != '\0')
+ {
+ /** @todo rewrite the code so it doesn't require modifiable input! */
+ pszArgsFree = pszArgs = (char *)RTMemDupEx(pszArgs, cchCmd - offArgs, 1);
+ AssertReturn(pszArgs, VERR_NO_MEMORY);
+ }
+
+ unsigned iArg;
+ unsigned cArgs;
+ int rc = dbgcProcessArguments(pDbgc, pCmd->pszCmd,
+ pCmd->cArgsMin, pCmd->cArgsMax, pCmd->paArgDescs, pCmd->cArgDescs,
+ pszArgs, &iArg, &cArgs);
+ if (RT_SUCCESS(rc))
+ {
+ AssertMsg(rc == VINF_SUCCESS, ("%Rrc\n", rc));
+
+ /*
+ * Execute the command.
+ */
+ if (!fNoExecute)
+ rc = pCmd->pfnHandler(pCmd, &pDbgc->CmdHlp, pDbgc->pUVM, &pDbgc->aArgs[iArg], cArgs);
+ pDbgc->rcCmd = rc;
+ pDbgc->iArg = iArg;
+ if (rc == VERR_DBGC_COMMAND_FAILED)
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ pDbgc->rcCmd = rc;
+ pDbgc->iArg = iArg;
+
+ /* report parse / eval error. */
+ switch (rc)
+ {
+ case VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Too few arguments. Minimum is %d for command '%s'.\n", pCmd->cArgsMin, pCmd->pszCmd);
+ break;
+ case VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Too many arguments. Maximum is %d for command '%s'.\n", pCmd->cArgsMax, pCmd->pszCmd);
+ break;
+ case VERR_DBGC_PARSE_ARGUMENT_OVERFLOW:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Too many arguments.\n");
+ break;
+ case VERR_DBGC_PARSE_UNBALANCED_QUOTE:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Unbalanced quote (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_UNBALANCED_PARENTHESIS:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Unbalanced parenthesis (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_EMPTY_ARGUMENT:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: An argument or subargument contains nothing useful (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_UNEXPECTED_OPERATOR:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Invalid operator usage (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_INVALID_NUMBER:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Syntax error: Invalid numeric value (argument %d). If a string was the intention, then quote it.\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_NUMBER_TOO_BIG:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Numeric overflow (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_INVALID_OPERATION:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Invalid operation attempted (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_FUNCTION_NOT_FOUND:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Function not found (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_NOT_A_FUNCTION:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: The function specified is not a function (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_NO_MEMORY:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Out memory in the regular heap! Expect odd stuff to happen...\n");
+ break;
+ case VERR_DBGC_PARSE_INCORRECT_ARG_TYPE:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Incorrect argument type (argument %d?).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_VARIABLE_NOT_FOUND:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: An undefined variable was referenced (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_CONVERSION_FAILED:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: A conversion between two types failed (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_NOT_IMPLEMENTED:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: You hit a debugger feature which isn't implemented yet (argument %d).\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_BAD_RESULT_TYPE:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Couldn't satisfy a request for a specific result type (argument %d). (Usually applies to symbols)\n", cArgs);
+ break;
+ case VERR_DBGC_PARSE_WRITEONLY_SYMBOL:
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp,
+ "Error: Cannot get symbol, it's set only (argument %d).\n", cArgs);
+ break;
+
+ case VERR_DBGC_COMMAND_FAILED:
+ break;
+
+ default:
+ if (RTErrIsKnown(rc))
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: %Rra\n", rc);
+ else
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Unknown error %d (%#x)!\n", rc, rc);
+ break;
+ }
+ }
+
+ RTMemFree(pszArgsFree);
+ return rc;
+}
+
+
+/**
+ * Evaluate one or commands separated by ';' or '\n'.
+ *
+ * @returns VBox status code. This is also stored in DBGC::rcCmd.
+ *
+ * @param pDbgc Debugger console instance data.
+ * @param pszCmds Pointer to the command.
+ * @param cchCmds Length of the command.
+ * @param fNoExecute Indicates that no commands should actually be executed.
+ */
+int dbgcEvalCommands(PDBGC pDbgc, char *pszCmds, size_t cchCmds, bool fNoExecute)
+{
+ /*
+ * Trim the input.
+ */
+ while (cchCmds > 0 && RT_C_IS_SPACE(pszCmds[cchCmds]))
+ cchCmds--;
+ while (cchCmds > 0 && RT_C_IS_SPACE(*pszCmds))
+ cchCmds--, pszCmds++;
+
+ /*
+ * Split up the commands and pass them to dbgcEvalCommand.
+ */
+ int rcRet = VINF_SUCCESS;
+ char chQuote = 0;
+ size_t offStart = 0;
+ size_t off = 0;
+ while (off < cchCmds)
+ {
+ char const ch = pszCmds[off];
+ if (ch == '"' || ch == '\'')
+ chQuote = ch == chQuote ? 0 : chQuote == 0 ? ch : chQuote;
+ else if (ch == ';' || ch == '\n')
+ {
+ /* Skip leading blanks and ignore empty commands. */
+ while (offStart < off && RT_C_IS_SPACE(pszCmds[offStart]))
+ offStart++;
+ if (off > offStart)
+ {
+ int rc = dbgcEvalCommand(pDbgc, &pszCmds[offStart], off - offStart, fNoExecute);
+ if (rcRet == VINF_SUCCESS || (RT_SUCCESS(rcRet) && RT_FAILURE(rc)))
+ rcRet = rc;
+ if ( rc == VERR_DBGC_QUIT
+ || rc == VWRN_DBGC_CMD_PENDING)
+ break;
+ }
+ offStart = ++off;
+ continue;
+ }
+ off++;
+ }
+
+ /*
+ * Pending command?
+ *
+ * No need to skip leading blanks here in order to check for empty
+ * commands, since we've already trimmed off tailing blanks.)
+ */
+ if (off > offStart)
+ {
+ int rc = dbgcEvalCommand(pDbgc, &pszCmds[offStart], off - offStart, fNoExecute);
+ if (rcRet == VINF_SUCCESS || (RT_SUCCESS(rcRet) && RT_FAILURE(rc)))
+ rcRet = rc;
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Loads the script in @a pszFilename and executes the commands within.
+ *
+ * @returns VBox status code. Will complain about error to console.
+ * @param pDbgc Debugger console instance data.
+ * @param pszFilename The path to the script file.
+ * @param fAnnounce Whether to announce the script.
+ */
+int dbgcEvalScript(PDBGC pDbgc, const char *pszFilename, bool fAnnounce)
+{
+ FILE *pFile = fopen(pszFilename, "r");
+ if (!pFile)
+ return DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Failed to open '%s'.\n", pszFilename);
+ if (fAnnounce)
+ DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Running script '%s'...\n", pszFilename);
+
+ /*
+ * Execute it line by line.
+ */
+ int rc = VINF_SUCCESS;
+ unsigned iLine = 0;
+ char szLine[8192];
+ while (fgets(szLine, sizeof(szLine), pFile))
+ {
+ /* check that the line isn't too long. */
+ char *pszEnd = strchr(szLine, '\0');
+ if (pszEnd == &szLine[sizeof(szLine) - 1])
+ {
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "runscript error: Line #%u is too long\n", iLine);
+ break;
+ }
+ iLine++;
+
+ /* strip leading blanks and check for comment / blank line. */
+ char *psz = RTStrStripL(szLine);
+ if ( *psz == '\0'
+ || *psz == '\n'
+ || *psz == '#')
+ continue;
+
+ /* strip trailing blanks and check for empty line (\r case). */
+ while ( pszEnd > psz
+ && RT_C_IS_SPACE(pszEnd[-1])) /* RT_C_IS_SPACE includes \n and \r normally. */
+ *--pszEnd = '\0';
+
+ /** @todo check for Control-C / Cancel at this point... */
+
+ /*
+ * Execute the command.
+ *
+ * This is a bit wasteful with scratch space btw., can fix it later.
+ * The whole return code crap should be fixed too, so that it's possible
+ * to know whether a command succeeded (RT_SUCCESS()) or failed, and
+ * more importantly why it failed.
+ */
+ /** @todo optimize this. */
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "%s", psz);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "runscript error: Line #%u is too long (exec overflowed)\n", iLine);
+ break;
+ }
+ if (rc == VWRN_DBGC_CMD_PENDING)
+ {
+ rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "runscript error: VWRN_DBGC_CMD_PENDING on line #%u, script terminated\n", iLine);
+ break;
+ }
+ }
+
+ fclose(pFile);
+ return rc;
+}
+
diff --git a/src/VBox/Debugger/DBGCFunctions.cpp b/src/VBox/Debugger/DBGCFunctions.cpp
new file mode 100644
index 00000000..e2c4d49d
--- /dev/null
+++ b/src/VBox/Debugger/DBGCFunctions.cpp
@@ -0,0 +1,128 @@
+/* $Id: DBGCFunctions.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Native Functions.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/assert.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+#include "DBGCInternal.h"
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Pointer to head of the list of exteranl functions. */
+static PDBGCEXTFUNCS g_pExtFuncsHead;
+
+
+
+
+/**
+ * @callback_method_impl{FNDBGCFUNC, The randu32() function implementation.}
+ */
+static DECLCALLBACK(int) dbgcFuncRandU32(PCDBGCFUNC pFunc, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, uint32_t cArgs,
+ PDBGCVAR pResult)
+{
+ AssertReturn(cArgs == 0, VERR_DBGC_PARSE_BUG);
+ uint32_t u32 = RTRandU32();
+ DBGCVAR_INIT_NUMBER(pResult, u32);
+ NOREF(pFunc); NOREF(pCmdHlp); NOREF(pUVM); NOREF(paArgs);
+ return VINF_SUCCESS;
+}
+
+
+/** Functions descriptors for the basic functions. */
+const DBGCFUNC g_aDbgcFuncs[] =
+{
+ /* pszCmd, cArgsMin, cArgsMax, paArgDescs, cArgDescs, fFlags, pfnHandler pszSyntax, ....pszDescription */
+ { "randu32", 0, 0, NULL, 0, 0, dbgcFuncRandU32, "", "Returns an unsigned 32-bit random number." },
+};
+
+/** The number of function descriptions in g_aDbgcFuncs. */
+const uint32_t g_cDbgcFuncs = RT_ELEMENTS(g_aDbgcFuncs);
+
+
+/**
+ * Looks up a function.
+ *
+ * @returns Pointer to the function descriptor on success, NULL if not found.
+ * @param pDbgc The DBGC instance.
+ * @param pachName The first charater in the name.
+ * @param cchName The length of the function name.
+ * @param fExternal Whether it's an external function.
+ */
+PCDBGCFUNC dbgcFunctionLookup(PDBGC pDbgc, const char *pachName, size_t cchName, bool fExternal)
+{
+ if (!fExternal)
+ {
+ /* emulation first, so commands can be overloaded (info ++). */
+ PCDBGCFUNC pFunc = pDbgc->paEmulationFuncs;
+ uint32_t cLeft = pDbgc->cEmulationFuncs;
+ while (cLeft-- > 0)
+ {
+ if ( !strncmp(pachName, pFunc->pszFuncNm, cchName)
+ && !pFunc->pszFuncNm[cchName])
+ return pFunc;
+ pFunc++;
+ }
+
+ for (uint32_t iFunc = 0; iFunc < RT_ELEMENTS(g_aDbgcFuncs); iFunc++)
+ {
+ if ( !strncmp(pachName, g_aDbgcFuncs[iFunc].pszFuncNm, cchName)
+ && !g_aDbgcFuncs[iFunc].pszFuncNm[cchName])
+ return &g_aDbgcFuncs[iFunc];
+ }
+ }
+ else
+ {
+ DBGCEXTLISTS_LOCK_RD();
+ for (PDBGCEXTFUNCS pExtFuncs = g_pExtFuncsHead; pExtFuncs; pExtFuncs = pExtFuncs->pNext)
+ {
+ for (uint32_t iFunc = 0; iFunc < pExtFuncs->cFuncs; iFunc++)
+ {
+ if ( !strncmp(pachName, pExtFuncs->paFuncs[iFunc].pszFuncNm, cchName)
+ && !pExtFuncs->paFuncs[iFunc].pszFuncNm[cchName])
+ return &pExtFuncs->paFuncs[iFunc];
+ }
+ }
+ DBGCEXTLISTS_UNLOCK_RD();
+ }
+
+ return NULL;
+}
+
diff --git a/src/VBox/Debugger/DBGCGdbRemoteStub.cpp b/src/VBox/Debugger/DBGCGdbRemoteStub.cpp
new file mode 100644
index 00000000..f38891f7
--- /dev/null
+++ b/src/VBox/Debugger/DBGCGdbRemoteStub.cpp
@@ -0,0 +1,2864 @@
+/* $Id: DBGCGdbRemoteStub.cpp $ */
+/** @file
+ * DBGC - Debugger Console, GDB Remote Stub.
+ */
+
+/*
+ * Copyright (C) 2010-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/vmapi.h> /* VMR3GetVM() */
+#include <VBox/vmm/hm.h> /* HMR3IsEnabled */
+#include <VBox/vmm/nem.h> /* NEMR3IsEnabled */
+#include <iprt/cdefs.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <stdlib.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Character indicating the start of a packet. */
+#define GDBSTUB_PKT_START '$'
+/** Character indicating the end of a packet (excluding the checksum). */
+#define GDBSTUB_PKT_END '#'
+/** The escape character. */
+#define GDBSTUB_PKT_ESCAPE '{'
+/** The out-of-band interrupt character. */
+#define GDBSTUB_OOB_INTERRUPT 0x03
+
+
+/** Indicate support for the 'qXfer:features:read' packet to support the target description. */
+#define GDBSTUBCTX_FEATURES_F_TGT_DESC RT_BIT(0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Trace point type.
+ */
+typedef enum GDBSTUBTPTYPE
+{
+ /** Invalid type, do not use. */
+ GDBSTUBTPTYPE_INVALID = 0,
+ /** An instruction software trace point. */
+ GDBSTUBTPTYPE_EXEC_SW,
+ /** An instruction hardware trace point. */
+ GDBSTUBTPTYPE_EXEC_HW,
+ /** A memory read trace point. */
+ GDBSTUBTPTYPE_MEM_READ,
+ /** A memory write trace point. */
+ GDBSTUBTPTYPE_MEM_WRITE,
+ /** A memory access trace point. */
+ GDBSTUBTPTYPE_MEM_ACCESS,
+ /** 32bit hack. */
+ GDBSTUBTPTYPE_32BIT_HACK = 0x7fffffff
+} GDBSTUBTPTYPE;
+
+
+/**
+ * GDB stub receive state.
+ */
+typedef enum GDBSTUBRECVSTATE
+{
+ /** Invalid state. */
+ GDBSTUBRECVSTATE_INVALID = 0,
+ /** Waiting for the start character. */
+ GDBSTUBRECVSTATE_PACKET_WAIT_FOR_START,
+ /** Reiceiving the packet body up until the END character. */
+ GDBSTUBRECVSTATE_PACKET_RECEIVE_BODY,
+ /** Receiving the checksum. */
+ GDBSTUBRECVSTATE_PACKET_RECEIVE_CHECKSUM,
+ /** Blow up the enum to 32bits for easier alignment of members in structs. */
+ GDBSTUBRECVSTATE_32BIT_HACK = 0x7fffffff
+} GDBSTUBRECVSTATE;
+
+
+/**
+ * GDB target register descriptor.
+ */
+typedef struct GDBREGDESC
+{
+ /** Register name. */
+ const char *pszName;
+ /** DBGF register index. */
+ DBGFREG enmReg;
+ /** Bitsize */
+ uint32_t cBits;
+ /** Type. */
+ const char *pszType;
+ /** Group. */
+ const char *pszGroup;
+} GDBREGDESC;
+/** Pointer to a GDB target register descriptor. */
+typedef GDBREGDESC *PGDBREGDESC;
+/** Pointer to a const GDB target register descriptor. */
+typedef const GDBREGDESC *PCGDBREGDESC;
+
+
+/**
+ * A tracepoint descriptor.
+ */
+typedef struct GDBSTUBTP
+{
+ /** List node for the list of tracepoints. */
+ RTLISTNODE NdTps;
+ /** The breakpoint number from the DBGF API. */
+ uint32_t iBp;
+ /** The tracepoint type for identification. */
+ GDBSTUBTPTYPE enmTpType;
+ /** The tracepoint address for identification. */
+ uint64_t GdbTgtAddr;
+ /** The tracepoint kind for identification. */
+ uint64_t uKind;
+} GDBSTUBTP;
+/** Pointer to a tracepoint. */
+typedef GDBSTUBTP *PGDBSTUBTP;
+
+
+/**
+ * GDB stub context data.
+ */
+typedef struct GDBSTUBCTX
+{
+ /** Internal debugger console data. */
+ DBGC Dbgc;
+ /** The current state when receiving a new packet. */
+ GDBSTUBRECVSTATE enmState;
+ /** Maximum number of bytes the packet buffer can hold. */
+ size_t cbPktBufMax;
+ /** Current offset into the packet buffer. */
+ size_t offPktBuf;
+ /** The size of the packet (minus the start, end characters and the checksum). */
+ size_t cbPkt;
+ /** Pointer to the packet buffer data. */
+ uint8_t *pbPktBuf;
+ /** Number of bytes left for the checksum. */
+ size_t cbChksumRecvLeft;
+ /** Send packet checksum. */
+ uint8_t uChkSumSend;
+ /** Feature flags supported we negotiated with the remote end. */
+ uint32_t fFeatures;
+ /** Pointer to the XML target description. */
+ char *pachTgtXmlDesc;
+ /** Size of the XML target description. */
+ size_t cbTgtXmlDesc;
+ /** Pointer to the selected GDB register set. */
+ PCGDBREGDESC paRegs;
+ /** Number of entries in the register set. */
+ uint32_t cRegs;
+ /** Flag whether the stub is in extended mode. */
+ bool fExtendedMode;
+ /** Flag whether was something was output using the 'O' packet since it was reset last. */
+ bool fOutput;
+ /** List of registered trace points.
+ * GDB removes breakpoints/watchpoints using the parameters they were
+ * registered with while we only use the BP number form DBGF internally.
+ * Means we have to track all registration so we can remove them later on. */
+ RTLISTANCHOR LstTps;
+ /** Flag whether a ThreadInfo query was started. */
+ bool fInThrdInfoQuery;
+ /** Next ID to return in the current ThreadInfo query. */
+ VMCPUID idCpuNextThrdInfoQuery;
+} GDBSTUBCTX;
+/** Pointer to the GDB stub context data. */
+typedef GDBSTUBCTX *PGDBSTUBCTX;
+/** Pointer to const GDB stub context data. */
+typedef const GDBSTUBCTX *PCGDBSTUBCTX;
+/** Pointer to a GDB stub context data pointer. */
+typedef PGDBSTUBCTX *PPGDBSTUBCTX;
+
+
+/**
+ * Specific query packet processor callback.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbVal Pointer to the remaining value.
+ * @param cbVal Size of the remaining value in bytes.
+ */
+typedef DECLCALLBACKTYPE(int, FNGDBSTUBQPKTPROC,(PGDBSTUBCTX pThis, const uint8_t *pbVal, size_t cbVal));
+typedef FNGDBSTUBQPKTPROC *PFNGDBSTUBQPKTPROC;
+
+
+/**
+ * 'q' packet processor.
+ */
+typedef struct GDBSTUBQPKTPROC
+{
+ /** Name */
+ const char *pszName;
+ /** Length of name in characters (without \0 terminator). */
+ uint32_t cchName;
+ /** The callback to call for processing the particular query. */
+ PFNGDBSTUBQPKTPROC pfnProc;
+} GDBSTUBQPKTPROC;
+/** Pointer to a 'q' packet processor entry. */
+typedef GDBSTUBQPKTPROC *PGDBSTUBQPKTPROC;
+/** Pointer to a const 'q' packet processor entry. */
+typedef const GDBSTUBQPKTPROC *PCGDBSTUBQPKTPROC;
+
+
+/**
+ * 'v' packet processor.
+ */
+typedef struct GDBSTUBVPKTPROC
+{
+ /** Name */
+ const char *pszName;
+ /** Length of name in characters (without \0 terminator). */
+ uint32_t cchName;
+ /** Replay to a query packet (ends with ?). */
+ const char *pszReplyQ;
+ /** Length of the query reply (without \0 terminator). */
+ uint32_t cchReplyQ;
+ /** The callback to call for processing the particular query. */
+ PFNGDBSTUBQPKTPROC pfnProc;
+} GDBSTUBVPKTPROC;
+/** Pointer to a 'q' packet processor entry. */
+typedef GDBSTUBVPKTPROC *PGDBSTUBVPKTPROC;
+/** Pointer to a const 'q' packet processor entry. */
+typedef const GDBSTUBVPKTPROC *PCGDBSTUBVPKTPROC;
+
+
+/**
+ * Feature callback.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbVal Pointer to the value.
+ * @param cbVal Size of the value in bytes.
+ */
+typedef DECLCALLBACKTYPE(int, FNGDBSTUBFEATHND,(PGDBSTUBCTX pThis, const uint8_t *pbVal, size_t cbVal));
+typedef FNGDBSTUBFEATHND *PFNGDBSTUBFEATHND;
+
+
+/**
+ * GDB feature descriptor.
+ */
+typedef struct GDBSTUBFEATDESC
+{
+ /** Feature name */
+ const char *pszName;
+ /** Length of the feature name in characters (without \0 terminator). */
+ uint32_t cchName;
+ /** The callback to call for processing the particular feature. */
+ PFNGDBSTUBFEATHND pfnHandler;
+ /** Flag whether the feature requires a value. */
+ bool fVal;
+} GDBSTUBFEATDESC;
+/** Pointer to a GDB feature descriptor. */
+typedef GDBSTUBFEATDESC *PGDBSTUBFEATDESC;
+/** Pointer to a const GDB feature descriptor. */
+typedef const GDBSTUBFEATDESC *PCGDBSTUBFEATDESC;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Tries to find a trace point with the given parameters in the list of registered trace points.
+ *
+ * @returns Pointer to the trace point registration record if found or NULL if none was found.
+ * @param pThis The GDB stub context.
+ * @param enmTpType The trace point type.
+ * @param GdbTgtAddr Target address given by GDB.
+ * @param uKind Trace point kind.
+ */
+static PGDBSTUBTP dbgcGdbStubTpFind(PGDBSTUBCTX pThis, GDBSTUBTPTYPE enmTpType, uint64_t GdbTgtAddr, uint64_t uKind)
+{
+ PGDBSTUBTP pTpCur = NULL;
+ RTListForEach(&pThis->LstTps, pTpCur, GDBSTUBTP, NdTps)
+ {
+ if ( pTpCur->enmTpType == enmTpType
+ && pTpCur->GdbTgtAddr == GdbTgtAddr
+ && pTpCur->uKind == uKind)
+ return pTpCur;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Registers a new trace point.
+ *
+ * @returns VBox status code.
+ * @param pThis The GDB stub context.
+ * @param enmTpType The trace point type.
+ * @param GdbTgtAddr Target address given by GDB.
+ * @param uKind Trace point kind.
+ * @param iBp The internal DBGF breakpoint ID this trace point was registered with.
+ */
+static int dbgcGdbStubTpRegister(PGDBSTUBCTX pThis, GDBSTUBTPTYPE enmTpType, uint64_t GdbTgtAddr, uint64_t uKind, uint32_t iBp)
+{
+ int rc = VERR_ALREADY_EXISTS;
+
+ /* Can't register a tracepoint with the same parameters twice or we can't decide whom to remove later on. */
+ PGDBSTUBTP pTp = dbgcGdbStubTpFind(pThis, enmTpType, GdbTgtAddr, uKind);
+ if (!pTp)
+ {
+ pTp = (PGDBSTUBTP)RTMemAllocZ(sizeof(*pTp));
+ if (pTp)
+ {
+ pTp->enmTpType = enmTpType;
+ pTp->GdbTgtAddr = GdbTgtAddr;
+ pTp->uKind = uKind;
+ pTp->iBp = iBp;
+ RTListAppend(&pThis->LstTps, &pTp->NdTps);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Deregisters the given trace point (needs to be unregistered from DBGF by the caller before).
+ *
+ * @param pTp The trace point to deregister.
+ */
+static void dbgcGdbStubTpDeregister(PGDBSTUBTP pTp)
+{
+ RTListNodeRemove(&pTp->NdTps);
+ RTMemFree(pTp);
+}
+
+
+/**
+ * Converts a given to the hexadecimal value if valid.
+ *
+ * @returns The hexadecimal value the given character represents 0-9,a-f,A-F or 0xff on error.
+ * @param ch The character to convert.
+ */
+DECLINLINE(uint8_t) dbgcGdbStubCtxChrToHex(char ch)
+{
+ if (ch >= '0' && ch <= '9')
+ return ch - '0';
+ if (ch >= 'A' && ch <= 'F')
+ return ch - 'A' + 0xa;
+ if (ch >= 'a' && ch <= 'f')
+ return ch - 'a' + 0xa;
+
+ return 0xff;
+}
+
+
+/**
+ * Converts a 4bit hex number to the appropriate character.
+ *
+ * @returns Character representing the 4bit hex number.
+ * @param uHex The 4 bit hex number.
+ */
+DECLINLINE(char) dbgcGdbStubCtxHexToChr(uint8_t uHex)
+{
+ if (uHex < 0xa)
+ return '0' + uHex;
+ if (uHex <= 0xf)
+ return 'A' + uHex - 0xa;
+
+ return 'X';
+}
+
+
+/**
+ * Wrapper for the I/O interface write callback.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pvPkt The packet data to send.
+ * @param cbPkt Size of the packet in bytes.
+ */
+DECLINLINE(int) dbgcGdbStubCtxWrite(PGDBSTUBCTX pThis, const void *pvPkt, size_t cbPkt)
+{
+ return pThis->Dbgc.pIo->pfnWrite(pThis->Dbgc.pIo, pvPkt, cbPkt, NULL /*pcbWritten*/);
+}
+
+
+/**
+ * Starts transmission of a new reply packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxReplySendBegin(PGDBSTUBCTX pThis)
+{
+ pThis->uChkSumSend = 0;
+
+ uint8_t chPktStart = GDBSTUB_PKT_START;
+ return dbgcGdbStubCtxWrite(pThis, &chPktStart, sizeof(chPktStart));
+}
+
+
+/**
+ * Sends the given data in the reply.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pvReplyData The reply data to send.
+ * @param cbReplyData Size of the reply data in bytes.
+ */
+static int dbgcGdbStubCtxReplySendData(PGDBSTUBCTX pThis, const void *pvReplyData, size_t cbReplyData)
+{
+ /* Update checksum. */
+ const uint8_t *pbData = (const uint8_t *)pvReplyData;
+ for (uint32_t i = 0; i < cbReplyData; i++)
+ pThis->uChkSumSend += pbData[i];
+
+ return dbgcGdbStubCtxWrite(pThis, pvReplyData, cbReplyData);
+}
+
+
+/**
+ * Finishes transmission of the current reply by sending the packet end character and the checksum.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxReplySendEnd(PGDBSTUBCTX pThis)
+{
+ uint8_t achPktEnd[3];
+
+ achPktEnd[0] = GDBSTUB_PKT_END;
+ achPktEnd[1] = dbgcGdbStubCtxHexToChr(pThis->uChkSumSend >> 4);
+ achPktEnd[2] = dbgcGdbStubCtxHexToChr(pThis->uChkSumSend & 0xf);
+
+ return dbgcGdbStubCtxWrite(pThis, &achPktEnd[0], sizeof(achPktEnd));
+}
+
+
+/**
+ * Sends the given reply packet, doing the framing, checksumming, etc. in one call.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pvReplyPkt The reply packet to send.
+ * @param cbReplyPkt Size of the reply packet in bytes.
+ */
+static int dbgcGdbStubCtxReplySend(PGDBSTUBCTX pThis, const void *pvReplyPkt, size_t cbReplyPkt)
+{
+ int rc = dbgcGdbStubCtxReplySendBegin(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcGdbStubCtxReplySendData(pThis, pvReplyPkt, cbReplyPkt);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendEnd(pThis);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Encodes the given buffer as a hexstring string it into the given destination buffer.
+ *
+ * @returns Status code.
+ * @param pbDst Where store the resulting hex string on success.
+ * @param cbDst Size of the destination buffer in bytes.
+ * @param pvSrc The data to encode.
+ * @param cbSrc Number of bytes to encode.
+ */
+DECLINLINE(int) dbgcGdbStubCtxEncodeBinaryAsHex(uint8_t *pbDst, size_t cbDst, const void *pvSrc, size_t cbSrc)
+{
+ return RTStrPrintHexBytes((char *)pbDst, cbDst, pvSrc, cbSrc, RTSTRPRINTHEXBYTES_F_UPPER);
+}
+
+
+/**
+ * Decodes the given ASCII hexstring as binary data up until the given separator is found or the end of the string is reached.
+ *
+ * @returns Status code.
+ * @param pbBuf The buffer containing the hexstring to convert.
+ * @param cbBuf Size of the buffer in bytes.
+ * @param puVal Where to store the decoded integer.
+ * @param chSep The character to stop conversion at.
+ * @param ppbSep Where to store the pointer in the buffer where the separator was found, optional.
+ */
+static int dbgcGdbStubCtxParseHexStringAsInteger(const uint8_t *pbBuf, size_t cbBuf, uint64_t *puVal, uint8_t chSep, const uint8_t **ppbSep)
+{
+ uint64_t uVal = 0;
+
+ while ( cbBuf
+ && *pbBuf != chSep)
+ {
+ uVal = uVal * 16 + dbgcGdbStubCtxChrToHex(*pbBuf++);
+ cbBuf--;
+ }
+
+ *puVal = uVal;
+
+ if (ppbSep)
+ *ppbSep = pbBuf;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Decodes the given ASCII hexstring as a byte buffer up until the given separator is found or the end of the string is reached.
+ *
+ * @returns Status code.
+ * @param pbBuf The buffer containing the hexstring to convert.
+ * @param cbBuf Size of the buffer in bytes.
+ * @param pvDst Where to store the decoded data.
+ * @param cbDst Maximum buffer size in bytes.
+ * @param pcbDecoded Where to store the number of consumed bytes from the input.
+ */
+DECLINLINE(int) dbgcGdbStubCtxParseHexStringAsByteBuf(const uint8_t *pbBuf, size_t cbBuf, void *pvDst, size_t cbDst, size_t *pcbDecoded)
+{
+ size_t cbDecode = RT_MIN(cbBuf, cbDst * 2);
+
+ if (pcbDecoded)
+ *pcbDecoded = cbDecode;
+
+ return RTStrConvertHexBytes((const char *)pbBuf, pvDst, cbDecode, 0 /* fFlags*/);
+}
+
+#if 0 /*unused for now*/
+/**
+ * Sends a 'OK' part of a reply packet only (packet start and end needs to be handled separately).
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxReplySendOkData(PGDBSTUBCTX pThis)
+{
+ char achOk[2] = { 'O', 'K' };
+ return dbgcGdbStubCtxReplySendData(pThis, &achOk[0], sizeof(achOk));
+}
+#endif
+
+
+/**
+ * Sends a 'OK' reply packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxReplySendOk(PGDBSTUBCTX pThis)
+{
+ char achOk[2] = { 'O', 'K' };
+ return dbgcGdbStubCtxReplySend(pThis, &achOk[0], sizeof(achOk));
+}
+
+#if 0 /*unused for now*/
+/**
+ * Sends a 'E NN' part of a reply packet only (packet start and end needs to be handled separately).
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param uErr The error code to send.
+ */
+static int dbgcGdbStubCtxReplySendErrData(PGDBSTUBCTX pThis, uint8_t uErr)
+{
+ char achErr[3] = { 'E', 0, 0 };
+ achErr[1] = dbgcGdbStubCtxHexToChr(uErr >> 4);
+ achErr[2] = dbgcGdbStubCtxHexToChr(uErr & 0xf);
+ return dbgcGdbStubCtxReplySendData(pThis, &achErr[0], sizeof(achErr));
+}
+#endif
+
+/**
+ * Sends a 'E NN' reply packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param uErr The error code to send.
+ */
+static int dbgcGdbStubCtxReplySendErr(PGDBSTUBCTX pThis, uint8_t uErr)
+{
+ char achErr[3] = { 'E', 0, 0 };
+ achErr[1] = dbgcGdbStubCtxHexToChr(uErr >> 4);
+ achErr[2] = dbgcGdbStubCtxHexToChr(uErr & 0xf);
+ return dbgcGdbStubCtxReplySend(pThis, &achErr[0], sizeof(achErr));
+}
+
+
+/**
+ * Sends a signal trap (S 05) packet to indicate that the target has stopped.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxReplySendSigTrap(PGDBSTUBCTX pThis)
+{
+ char achReply[32];
+ ssize_t cchStr = RTStrPrintf2(&achReply[0], sizeof(achReply), "T05thread:%02x;", pThis->Dbgc.idCpu + 1);
+ return dbgcGdbStubCtxReplySend(pThis, &achReply[0], cchStr);
+}
+
+
+/**
+ * Sends a GDB stub status code indicating an error using the error reply packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param rc The status code to send.
+ */
+static int dbgcGdbStubCtxReplySendErrSts(PGDBSTUBCTX pThis, int rc)
+{
+ /** @todo convert error codes maybe. */
+ return dbgcGdbStubCtxReplySendErr(pThis, (-rc) & 0xff);
+}
+
+
+/**
+ * Ensures that there is at least the given amount of bytes of free space left in the packet buffer.
+ *
+ * @returns Status code (error when increasing the buffer failed).
+ * @param pThis The GDB stub context.
+ * @param cbSpace Number of bytes required.
+ */
+static int dbgcGdbStubCtxEnsurePktBufSpace(PGDBSTUBCTX pThis, size_t cbSpace)
+{
+ if (pThis->cbPktBufMax - pThis->offPktBuf >= cbSpace)
+ return VINF_SUCCESS;
+
+ /* Slow path allocate new buffer and copy content over. */
+ int rc = VINF_SUCCESS;
+ size_t cbPktBufMaxNew = pThis->cbPktBufMax + cbSpace;
+ void *pvNew = RTMemRealloc(pThis->pbPktBuf, cbPktBufMaxNew);
+ if (pvNew)
+ {
+ pThis->pbPktBuf = (uint8_t *)pvNew;
+ pThis->cbPktBufMax = cbPktBufMaxNew;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Parses the arguments of a 'Z' and 'z' packet.
+ *
+ * @returns Status code.
+ * @param pbArgs Pointer to the start of the first argument.
+ * @param cbArgs Number of argument bytes.
+ * @param penmTpType Where to store the tracepoint type on success.
+ * @param pGdbTgtAddr Where to store the address on success.
+ * @param puKind Where to store the kind argument on success.
+ */
+static int dbgcGdbStubCtxParseTpPktArgs(const uint8_t *pbArgs, size_t cbArgs, GDBSTUBTPTYPE *penmTpType, uint64_t *pGdbTgtAddr, uint64_t *puKind)
+{
+ const uint8_t *pbPktSep = NULL;
+ uint64_t uType = 0;
+
+ int rc = dbgcGdbStubCtxParseHexStringAsInteger(pbArgs, cbArgs, &uType,
+ ',', &pbPktSep);
+ if (RT_SUCCESS(rc))
+ {
+ cbArgs -= (uintptr_t)(pbPktSep - pbArgs) - 1;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, cbArgs, pGdbTgtAddr,
+ ',', &pbPktSep);
+ if (RT_SUCCESS(rc))
+ {
+ cbArgs -= (uintptr_t)(pbPktSep - pbArgs) - 1;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, cbArgs, puKind,
+ GDBSTUB_PKT_END, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ switch (uType)
+ {
+ case 0:
+ *penmTpType = GDBSTUBTPTYPE_EXEC_SW;
+ break;
+ case 1:
+ *penmTpType = GDBSTUBTPTYPE_EXEC_HW;
+ break;
+ case 2:
+ *penmTpType = GDBSTUBTPTYPE_MEM_WRITE;
+ break;
+ case 3:
+ *penmTpType = GDBSTUBTPTYPE_MEM_READ;
+ break;
+ case 4:
+ *penmTpType = GDBSTUBTPTYPE_MEM_ACCESS;
+ break;
+ default:
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes the 'TStatus' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryTStatus(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ RT_NOREF(pbArgs, cbArgs);
+
+ char achReply[2] = { 'T', '0' };
+ return dbgcGdbStubCtxReplySend(pThis, &achReply[0], sizeof(achReply));
+}
+
+
+/**
+ * @copydoc FNGDBSTUBQPKTPROC
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessFeatXmlRegs(PGDBSTUBCTX pThis, const uint8_t *pbVal, size_t cbVal)
+{
+ /*
+ * xmlRegisters contain a list of supported architectures delimited by ','.
+ * Check that the architecture is in the supported list.
+ */
+ while (cbVal)
+ {
+ /* Find the next delimiter. */
+ size_t cbThisVal = cbVal;
+ const uint8_t *pbDelim = (const uint8_t *)memchr(pbVal, ',', cbVal);
+ if (pbDelim)
+ cbThisVal = pbDelim - pbVal;
+
+ const size_t cchArch64 = sizeof("i386:x86-64") - 1;
+ const size_t cchArch32 = sizeof("i386") - 1;
+ if ( !memcmp(pbVal, "i386:x86-64", RT_MIN(cbVal, cchArch64))
+ || !memcmp(pbVal, "i386", RT_MIN(cbVal, cchArch32)))
+ {
+ /* Set the flag to support the qXfer:features:read packet. */
+ pThis->fFeatures |= GDBSTUBCTX_FEATURES_F_TGT_DESC;
+ break;
+ }
+
+ cbVal -= cbThisVal + (pbDelim ? 1 : 0);
+ pbVal = pbDelim + (pbDelim ? 1 : 0);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Features which can be reported by the remote GDB which we might support.
+ *
+ * @note The sorting matters for features which start the same, the longest must come first.
+ */
+static const GDBSTUBFEATDESC g_aGdbFeatures[] =
+{
+#define GDBSTUBFEATDESC_INIT(a_Name, a_pfnHnd, a_fVal) { a_Name, sizeof(a_Name) - 1, a_pfnHnd, a_fVal }
+ GDBSTUBFEATDESC_INIT("xmlRegisters", dbgcGdbStubCtxPktProcessFeatXmlRegs, true),
+#undef GDBSTUBFEATDESC_INIT
+};
+
+
+/**
+ * Calculates the feature length of the next feature pointed to by the given arguments buffer.
+ *
+ * @returns Status code.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ * @param pcbArg Where to store the size of the argument in bytes on success (excluding the delimiter).
+ * @param pfTerminator Whereto store the flag whether the packet terminator (#) was seen as a delimiter.
+ */
+static int dbgcGdbStubCtxQueryPktQueryFeatureLen(const uint8_t *pbArgs, size_t cbArgs, size_t *pcbArg, bool *pfTerminator)
+{
+ const uint8_t *pbArgCur = pbArgs;
+
+ while ( cbArgs
+ && *pbArgCur != ';'
+ && *pbArgCur != GDBSTUB_PKT_END)
+ {
+ cbArgs--;
+ pbArgCur++;
+ }
+
+ if ( !cbArgs
+ && *pbArgCur != ';'
+ && *pbArgCur != GDBSTUB_PKT_END)
+ return VERR_NET_PROTOCOL_ERROR;
+
+ *pcbArg = pbArgCur - pbArgs;
+ *pfTerminator = *pbArgCur == GDBSTUB_PKT_END ? true : false;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sends the reply to the 'qSupported' packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxPktProcessQuerySupportedReply(PGDBSTUBCTX pThis)
+{
+ /** @todo Enhance. */
+ if (pThis->fFeatures & GDBSTUBCTX_FEATURES_F_TGT_DESC)
+ return dbgcGdbStubCtxReplySend(pThis, "qXfer:features:read+;vContSupported+", sizeof("qXfer:features:read+;vContSupported+") - 1);
+
+ return dbgcGdbStubCtxReplySend(pThis, NULL, 0);
+}
+
+
+/**
+ * Processes the 'Supported' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQuerySupported(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ /* Skip the : following the qSupported start. */
+ if ( cbArgs < 1
+ || pbArgs[0] != ':')
+ return VERR_NET_PROTOCOL_ERROR;
+
+ cbArgs--;
+ pbArgs++;
+
+ /*
+ * Each feature but the last one are separated by ; and the last one is delimited by the # packet end symbol.
+ * We first determine the boundaries of the reported feature and pass it to the appropriate handler.
+ */
+ int rc = VINF_SUCCESS;
+ while ( cbArgs
+ && RT_SUCCESS(rc))
+ {
+ bool fTerminator = false;
+ size_t cbArg = 0;
+ rc = dbgcGdbStubCtxQueryPktQueryFeatureLen(pbArgs, cbArgs, &cbArg, &fTerminator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for the feature handler. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aGdbFeatures); i++)
+ {
+ PCGDBSTUBFEATDESC pFeatDesc = &g_aGdbFeatures[i];
+
+ if ( cbArg > pFeatDesc->cchName /* At least one character must come after the feature name ('+', '-' or '='). */
+ && !memcmp(pFeatDesc->pszName, pbArgs, pFeatDesc->cchName))
+ {
+ /* Found, execute handler after figuring out whether there is a value attached. */
+ const uint8_t *pbVal = pbArgs + pFeatDesc->cchName;
+ size_t cbVal = cbArg - pFeatDesc->cchName;
+
+ if (pFeatDesc->fVal)
+ {
+ if ( *pbVal == '='
+ && cbVal > 1)
+ {
+ pbVal++;
+ cbVal--;
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ else if ( cbVal != 1
+ || ( *pbVal != '+'
+ && *pbVal != '-')) /* '+' and '-' are allowed to indicate support for a particular feature. */
+ rc = VERR_NET_PROTOCOL_ERROR;
+
+ if (RT_SUCCESS(rc))
+ rc = pFeatDesc->pfnHandler(pThis, pbVal, cbVal);
+ break;
+ }
+ }
+
+ cbArgs -= cbArg;
+ pbArgs += cbArg;
+ if (!fTerminator)
+ {
+ cbArgs--;
+ pbArgs++;
+ }
+ else
+ break;
+ }
+ }
+
+ /* If everything went alright send the reply with our supported features. */
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxPktProcessQuerySupportedReply(pThis);
+
+ return rc;
+}
+
+
+/**
+ * Sends the reply to a 'qXfer:object:read:...' request.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param offRead Where to start reading from within the object.
+ * @param cbRead How much to read.
+ * @param pbObj The start of the object.
+ * @param cbObj Size of the object.
+ */
+static int dbgcGdbStubCtxQueryXferReadReply(PGDBSTUBCTX pThis, uint32_t offRead, size_t cbRead, const uint8_t *pbObj, size_t cbObj)
+{
+ int rc = VINF_SUCCESS;
+ if (offRead < cbObj)
+ {
+ /** @todo Escaping */
+ size_t cbThisRead = offRead + cbRead < cbObj ? cbRead : cbObj - offRead;
+
+ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbThisRead + 1);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t *pbPktBuf = pThis->pbPktBuf;
+ *pbPktBuf++ = cbThisRead < cbRead ? 'l' : 'm';
+ memcpy(pbPktBuf, pbObj + offRead, cbThisRead);
+ rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbThisRead + 1);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NO_MEMORY);
+ }
+ else if (offRead == cbObj)
+ rc = dbgcGdbStubCtxReplySend(pThis, "l", sizeof("l") - 1);
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+
+ return rc;
+}
+
+
+/**
+ * Parses the annex:offset,length part of a 'qXfer:object:read:...' request.
+ *
+ * @returns Status code.
+ * @param pbArgs Start of the arguments beginning with annex.
+ * @param cbArgs Number of bytes remaining for the arguments.
+ * @param ppchAnnex Where to store the pointer to the beginning of the annex on success.
+ * @param pcchAnnex Where to store the number of characters for the annex on success.
+ * @param poffRead Where to store the offset on success.
+ * @param pcbRead Where to store the length on success.
+ */
+static int dbgcGdbStubCtxPktProcessQueryXferParseAnnexOffLen(const uint8_t *pbArgs, size_t cbArgs, const char **ppchAnnex, size_t *pcchAnnex,
+ uint32_t *poffRead, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ const uint8_t *pbSep = (const uint8_t *)memchr(pbArgs, ':', cbArgs);
+ if (pbSep)
+ {
+ *ppchAnnex = (const char *)pbArgs;
+ *pcchAnnex = pbSep - pbArgs;
+
+ pbSep++;
+ cbArgs -= *pcchAnnex + 1;
+
+ uint64_t u64Tmp = 0;
+ const uint8_t *pbLenStart = NULL;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(pbSep, cbArgs, &u64Tmp, ',', &pbLenStart);
+ if ( RT_SUCCESS(rc)
+ && (uint32_t)u64Tmp == u64Tmp)
+ {
+ *poffRead = (uint32_t)u64Tmp;
+ cbArgs -= pbLenStart - pbSep;
+
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(pbLenStart + 1, cbArgs, &u64Tmp, '#', &pbLenStart);
+ if ( RT_SUCCESS(rc)
+ && (size_t)u64Tmp == u64Tmp)
+ *pcbRead = (size_t)u64Tmp;
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+
+ return rc;
+}
+
+
+#define DBGREG_DESC_INIT_INT64(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 64, "int64", NULL }
+#define DBGREG_DESC_INIT_INT32(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "int32", NULL }
+#define DBGREG_DESC_INIT_DATA_PTR64(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 64, "data_ptr", NULL }
+#define DBGREG_DESC_INIT_CODE_PTR64(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 64, "code_ptr", NULL }
+#define DBGREG_DESC_INIT_DATA_PTR32(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "data_ptr", NULL }
+#define DBGREG_DESC_INIT_CODE_PTR32(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "code_ptr", NULL }
+#define DBGREG_DESC_INIT_X87(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 80, "i387_ext", NULL }
+#define DBGREG_DESC_INIT_X87_CTRL(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "int", "float" }
+
+
+/**
+ * amd64 GDB register set.
+ */
+static const GDBREGDESC g_aGdbRegs64[] =
+{
+ DBGREG_DESC_INIT_INT64( "rax", DBGFREG_RAX),
+ DBGREG_DESC_INIT_INT64( "rbx", DBGFREG_RBX),
+ DBGREG_DESC_INIT_INT64( "rcx", DBGFREG_RCX),
+ DBGREG_DESC_INIT_INT64( "rdx", DBGFREG_RDX),
+ DBGREG_DESC_INIT_INT64( "rsi", DBGFREG_RSI),
+ DBGREG_DESC_INIT_INT64( "rdi", DBGFREG_RDI),
+ DBGREG_DESC_INIT_DATA_PTR64("rbp", DBGFREG_RBP),
+ DBGREG_DESC_INIT_DATA_PTR64("rsp", DBGFREG_RSP),
+ DBGREG_DESC_INIT_INT64( "r8", DBGFREG_R8),
+ DBGREG_DESC_INIT_INT64( "r9", DBGFREG_R9),
+ DBGREG_DESC_INIT_INT64( "r10", DBGFREG_R10),
+ DBGREG_DESC_INIT_INT64( "r11", DBGFREG_R11),
+ DBGREG_DESC_INIT_INT64( "r12", DBGFREG_R12),
+ DBGREG_DESC_INIT_INT64( "r13", DBGFREG_R13),
+ DBGREG_DESC_INIT_INT64( "r14", DBGFREG_R14),
+ DBGREG_DESC_INIT_INT64( "r15", DBGFREG_R15),
+ DBGREG_DESC_INIT_CODE_PTR64("rip", DBGFREG_RIP),
+ DBGREG_DESC_INIT_INT32( "eflags", DBGFREG_FLAGS),
+ DBGREG_DESC_INIT_INT32( "cs", DBGFREG_CS),
+ DBGREG_DESC_INIT_INT32( "ss", DBGFREG_SS),
+ DBGREG_DESC_INIT_INT32( "ds", DBGFREG_DS),
+ DBGREG_DESC_INIT_INT32( "es", DBGFREG_ES),
+ DBGREG_DESC_INIT_INT32( "fs", DBGFREG_FS),
+ DBGREG_DESC_INIT_INT32( "gs", DBGFREG_GS),
+
+ DBGREG_DESC_INIT_X87( "st0", DBGFREG_ST0),
+ DBGREG_DESC_INIT_X87( "st1", DBGFREG_ST1),
+ DBGREG_DESC_INIT_X87( "st2", DBGFREG_ST2),
+ DBGREG_DESC_INIT_X87( "st3", DBGFREG_ST3),
+ DBGREG_DESC_INIT_X87( "st4", DBGFREG_ST4),
+ DBGREG_DESC_INIT_X87( "st5", DBGFREG_ST5),
+ DBGREG_DESC_INIT_X87( "st6", DBGFREG_ST6),
+ DBGREG_DESC_INIT_X87( "st7", DBGFREG_ST7),
+
+ DBGREG_DESC_INIT_X87_CTRL( "fctrl", DBGFREG_FCW),
+ DBGREG_DESC_INIT_X87_CTRL( "fstat", DBGFREG_FSW),
+ DBGREG_DESC_INIT_X87_CTRL( "ftag", DBGFREG_FTW),
+ DBGREG_DESC_INIT_X87_CTRL( "fop", DBGFREG_FOP),
+ DBGREG_DESC_INIT_X87_CTRL( "fioff", DBGFREG_FPUIP),
+ DBGREG_DESC_INIT_X87_CTRL( "fiseg", DBGFREG_FPUCS),
+ DBGREG_DESC_INIT_X87_CTRL( "fooff", DBGFREG_FPUDP),
+ DBGREG_DESC_INIT_X87_CTRL( "foseg", DBGFREG_FPUDS)
+};
+
+
+/**
+ * i386 GDB register set.
+ */
+static const GDBREGDESC g_aGdbRegs32[] =
+{
+ DBGREG_DESC_INIT_INT32( "eax", DBGFREG_EAX),
+ DBGREG_DESC_INIT_INT32( "ebx", DBGFREG_EBX),
+ DBGREG_DESC_INIT_INT32( "ecx", DBGFREG_ECX),
+ DBGREG_DESC_INIT_INT32( "edx", DBGFREG_EDX),
+ DBGREG_DESC_INIT_INT32( "esi", DBGFREG_ESI),
+ DBGREG_DESC_INIT_INT32( "edi", DBGFREG_EDI),
+ DBGREG_DESC_INIT_DATA_PTR32("ebp", DBGFREG_EBP),
+ DBGREG_DESC_INIT_DATA_PTR32("esp", DBGFREG_ESP),
+ DBGREG_DESC_INIT_CODE_PTR32("eip", DBGFREG_EIP),
+ DBGREG_DESC_INIT_INT32( "eflags", DBGFREG_FLAGS),
+ DBGREG_DESC_INIT_INT32( "cs", DBGFREG_CS),
+ DBGREG_DESC_INIT_INT32( "ss", DBGFREG_SS),
+ DBGREG_DESC_INIT_INT32( "ds", DBGFREG_DS),
+ DBGREG_DESC_INIT_INT32( "es", DBGFREG_ES),
+ DBGREG_DESC_INIT_INT32( "fs", DBGFREG_FS),
+ DBGREG_DESC_INIT_INT32( "gs", DBGFREG_GS),
+
+ DBGREG_DESC_INIT_X87( "st0", DBGFREG_ST0),
+ DBGREG_DESC_INIT_X87( "st1", DBGFREG_ST1),
+ DBGREG_DESC_INIT_X87( "st2", DBGFREG_ST2),
+ DBGREG_DESC_INIT_X87( "st3", DBGFREG_ST3),
+ DBGREG_DESC_INIT_X87( "st4", DBGFREG_ST4),
+ DBGREG_DESC_INIT_X87( "st5", DBGFREG_ST5),
+ DBGREG_DESC_INIT_X87( "st6", DBGFREG_ST6),
+ DBGREG_DESC_INIT_X87( "st7", DBGFREG_ST7),
+
+ DBGREG_DESC_INIT_X87_CTRL( "fctrl", DBGFREG_FCW),
+ DBGREG_DESC_INIT_X87_CTRL( "fstat", DBGFREG_FSW),
+ DBGREG_DESC_INIT_X87_CTRL( "ftag", DBGFREG_FTW),
+ DBGREG_DESC_INIT_X87_CTRL( "fop", DBGFREG_FOP),
+ DBGREG_DESC_INIT_X87_CTRL( "fioff", DBGFREG_FPUIP),
+ DBGREG_DESC_INIT_X87_CTRL( "fiseg", DBGFREG_FPUCS),
+ DBGREG_DESC_INIT_X87_CTRL( "fooff", DBGFREG_FPUDP),
+ DBGREG_DESC_INIT_X87_CTRL( "foseg", DBGFREG_FPUDS)
+};
+
+#undef DBGREG_DESC_INIT_CODE_PTR64
+#undef DBGREG_DESC_INIT_DATA_PTR64
+#undef DBGREG_DESC_INIT_CODE_PTR32
+#undef DBGREG_DESC_INIT_DATA_PTR32
+#undef DBGREG_DESC_INIT_INT32
+#undef DBGREG_DESC_INIT_INT64
+#undef DBGREG_DESC_INIT_X87
+#undef DBGREG_DESC_INIT_X87_CTRL
+
+
+/**
+ * Creates the target XML description.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxTgtXmlDescCreate(PGDBSTUBCTX pThis)
+{
+ static const char s_szXmlTgtHdr64[] =
+ "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
+ "<target version=\"1.0\">\n"
+ " <architecture>i386:x86-64</architecture>\n"
+ " <feature name=\"org.gnu.gdb.i386.core\">\n";
+ static const char s_szXmlTgtHdr32[] =
+ "<?xml version=\"1.0\"?>\n"
+ "<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
+ "<target version=\"1.0\">\n"
+ " <architecture>i386</architecture>\n"
+ " <feature name=\"org.gnu.gdb.i386.core\">\n";
+ static const char s_szXmlTgtFooter[] =
+ " </feature>\n"
+ "</target>\n";
+
+ int rc = VINF_SUCCESS;
+
+ pThis->pachTgtXmlDesc = (char *)RTStrAlloc(_32K);
+ if (pThis->pachTgtXmlDesc)
+ {
+ size_t cbLeft = _32K;
+ char *pachXmlCur = pThis->pachTgtXmlDesc;
+ pThis->cbTgtXmlDesc = cbLeft;
+
+ rc = RTStrCatP(&pachXmlCur, &cbLeft, pThis->paRegs == &g_aGdbRegs64[0] ? &s_szXmlTgtHdr64[0] : &s_szXmlTgtHdr32[0]);
+ if (RT_SUCCESS(rc))
+ {
+ /* Register */
+ for (uint32_t i = 0; i < pThis->cRegs && RT_SUCCESS(rc); i++)
+ {
+ const struct GDBREGDESC *pReg = &pThis->paRegs[i];
+
+ ssize_t cchStr = 0;
+ if (pReg->pszGroup)
+ cchStr = RTStrPrintf2(pachXmlCur, cbLeft,
+ "<reg name=\"%s\" bitsize=\"%u\" regnum=\"%u\" type=\"%s\" group=\"%s\"/>\n",
+ pReg->pszName, pReg->cBits, i, pReg->pszType, pReg->pszGroup);
+ else
+ cchStr = RTStrPrintf2(pachXmlCur, cbLeft,
+ "<reg name=\"%s\" bitsize=\"%u\" regnum=\"%u\" type=\"%s\"/>\n",
+ pReg->pszName, pReg->cBits, i, pReg->pszType);
+
+ if (cchStr > 0)
+ {
+ pachXmlCur += cchStr;
+ cbLeft -= cchStr;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTStrCatP(&pachXmlCur, &cbLeft, &s_szXmlTgtFooter[0]);
+
+ pThis->cbTgtXmlDesc -= cbLeft;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Returns the GDB register descriptor describing the given DBGF register enum.
+ *
+ * @returns Pointer to the GDB register descriptor or NULL if not found.
+ * @param pThis The GDB stub context.
+ * @param idxReg The register to look for.
+ */
+static const GDBREGDESC *dbgcGdbStubRegGet(PGDBSTUBCTX pThis, uint32_t idxReg)
+{
+ if (RT_LIKELY(idxReg < pThis->cRegs))
+ return &pThis->paRegs[idxReg];
+
+ return NULL;
+}
+
+
+/**
+ * Processes the 'C' query (query current thread ID).
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadId(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ RT_NOREF(pbArgs, cbArgs);
+
+ int rc = VERR_BUFFER_OVERFLOW;
+ char achReply[32];
+ ssize_t cchStr = RTStrPrintf(&achReply[0], sizeof(achReply), "QC %02x", pThis->Dbgc.idCpu + 1);
+ if (cchStr > 0)
+ rc = dbgcGdbStubCtxReplySend(pThis, &achReply[0], cchStr);
+
+ return rc;
+}
+
+
+/**
+ * Processes the 'Attached' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryAttached(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ RT_NOREF(pbArgs, cbArgs);
+
+ /* We always report attached so that the VM doesn't get killed when GDB quits. */
+ uint8_t bAttached = '1';
+ return dbgcGdbStubCtxReplySend(pThis, &bAttached, sizeof(bAttached));
+}
+
+
+/**
+ * Processes the 'Xfer:features:read' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryXferFeatRead(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ /* Skip the : following the Xfer:features:read start. */
+ if ( cbArgs < 1
+ || pbArgs[0] != ':')
+ return VERR_NET_PROTOCOL_ERROR;
+
+ cbArgs--;
+ pbArgs++;
+
+ int rc = VINF_SUCCESS;
+ if (pThis->fFeatures & GDBSTUBCTX_FEATURES_F_TGT_DESC)
+ {
+ /* Create the target XML description if not existing. */
+ if (!pThis->pachTgtXmlDesc)
+ rc = dbgcGdbStubCtxTgtXmlDescCreate(pThis);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Parse annex, offset and length and return the data. */
+ const char *pchAnnex = NULL;
+ size_t cchAnnex = 0;
+ uint32_t offRead = 0;
+ size_t cbRead = 0;
+
+ rc = dbgcGdbStubCtxPktProcessQueryXferParseAnnexOffLen(pbArgs, cbArgs,
+ &pchAnnex, &cchAnnex,
+ &offRead, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check whether the annex is supported. */
+ if ( cchAnnex == sizeof("target.xml") - 1
+ && !memcmp(pchAnnex, "target.xml", cchAnnex))
+ rc = dbgcGdbStubCtxQueryXferReadReply(pThis, offRead, cbRead, (const uint8_t *)pThis->pachTgtXmlDesc,
+ pThis->cbTgtXmlDesc);
+ else
+ rc = dbgcGdbStubCtxReplySendErr(pThis, 0);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0); /* Not supported. */
+
+ return rc;
+}
+
+
+/**
+ * Processes the 'Rcmd' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryRcmd(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ /* Skip the , following the qRcmd start. */
+ if ( cbArgs < 1
+ || pbArgs[0] != ',')
+ return VERR_NET_PROTOCOL_ERROR;
+
+ cbArgs--;
+ pbArgs++;
+
+ /* Decode the command. */
+ /** @todo Make this dynamic. */
+ char szCmd[_4K];
+ RT_ZERO(szCmd);
+
+ if (cbArgs / 2 >= sizeof(szCmd))
+ return VERR_NET_PROTOCOL_ERROR;
+
+ size_t cbDecoded = 0;
+ int rc = RTStrConvertHexBytesEx((const char *)pbArgs, &szCmd[0], sizeof(szCmd), 0 /*fFlags*/,
+ NULL /* ppszNext */, &cbDecoded);
+ if (rc == VWRN_TRAILING_CHARS)
+ rc = VINF_SUCCESS;
+ if (RT_SUCCESS(rc))
+ {
+ szCmd[cbDecoded] = '\0'; /* Ensure zero termination. */
+
+ pThis->fOutput = false;
+ rc = dbgcEvalCommand(&pThis->Dbgc, &szCmd[0], cbDecoded - 1, false /*fNoExecute*/);
+ dbgcGdbStubCtxReplySendOk(pThis);
+ if ( rc != VERR_DBGC_QUIT
+ && rc != VWRN_DBGC_CMD_PENDING)
+ rc = VINF_SUCCESS; /* ignore other statuses */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for both 'qfThreadInfo' and 'qsThreadInfo'.
+ *
+ * @returns VBox status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxPktProcessQueryThreadInfoWorker(PGDBSTUBCTX pThis)
+{
+ int rc = dbgcGdbStubCtxReplySendBegin(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t bReplyStart = { 'm' };
+ rc = dbgcGdbStubCtxReplySendData(pThis, &bReplyStart, sizeof(bReplyStart));
+ if (RT_SUCCESS(rc))
+ {
+ char achReply[32];
+ ssize_t cchStr = RTStrPrintf(&achReply[0], sizeof(achReply), "%02x", pThis->idCpuNextThrdInfoQuery + 1);
+ if (cchStr <= 0)
+ rc = VERR_BUFFER_OVERFLOW;
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendData(pThis, &achReply[0], cchStr);
+ pThis->idCpuNextThrdInfoQuery++;
+ }
+
+ rc = dbgcGdbStubCtxReplySendEnd(pThis);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes the 'fThreadInfo' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadInfoStart(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ RT_NOREF(pbArgs, cbArgs);
+
+ pThis->idCpuNextThrdInfoQuery = 0;
+ pThis->fInThrdInfoQuery = true;
+ return dbgcGdbStubCtxPktProcessQueryThreadInfoWorker(pThis);
+}
+
+
+/**
+ * Processes the 'fThreadInfo' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadInfoCont(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ RT_NOREF(pbArgs, cbArgs);
+
+ /* If we are in a thread info query we just send the end of list specifier (all thread IDs where sent previously already). */
+ if (!pThis->fInThrdInfoQuery)
+ return dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+
+ VMCPUID cCpus = DBGFR3CpuGetCount(pThis->Dbgc.pUVM);
+ if (pThis->idCpuNextThrdInfoQuery == cCpus)
+ {
+ pThis->fInThrdInfoQuery = false;
+ uint8_t bEoL = 'l';
+ return dbgcGdbStubCtxReplySend(pThis, &bEoL, sizeof(bEoL));
+ }
+
+ return dbgcGdbStubCtxPktProcessQueryThreadInfoWorker(pThis);
+}
+
+
+/**
+ * Processes the 'ThreadExtraInfo' query.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadExtraInfo(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ /* Skip the , following the qThreadExtraInfo start. */
+ if ( cbArgs < 1
+ || pbArgs[0] != ',')
+ return VERR_NET_PROTOCOL_ERROR;
+
+ cbArgs--;
+ pbArgs++;
+
+ /* We know there is an # character denoting the end so the following must return with VWRN_TRAILING_CHARS. */
+ VMCPUID idCpu;
+ int rc = RTStrToUInt32Ex((const char *)pbArgs, NULL /*ppszNext*/, 16, &idCpu);
+ if ( rc == VWRN_TRAILING_CHARS
+ && idCpu > 0)
+ {
+ idCpu--;
+
+ VMCPUID cCpus = DBGFR3CpuGetCount(pThis->Dbgc.pUVM);
+ if (idCpu < cCpus)
+ {
+ const char *pszCpuState = DBGFR3CpuGetState(pThis->Dbgc.pUVM, idCpu);
+ size_t cchCpuState = strlen(pszCpuState);
+
+ if (!pszCpuState)
+ pszCpuState = "DBGFR3CpuGetState() -> NULL";
+
+ rc = dbgcGdbStubCtxReplySendBegin(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /* Convert the characters to hex. */
+ const char *pachCur = pszCpuState;
+
+ while ( cchCpuState
+ && RT_SUCCESS(rc))
+ {
+ uint8_t achHex[512 + 1];
+ size_t cbThisSend = RT_MIN((sizeof(achHex) - 1) / 2, cchCpuState); /* Each character needs two bytes. */
+
+ rc = dbgcGdbStubCtxEncodeBinaryAsHex(&achHex[0], cbThisSend * 2 + 1, pachCur, cbThisSend);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendData(pThis, &achHex[0], cbThisSend * 2);
+
+ pachCur += cbThisSend;
+ cchCpuState -= cbThisSend;
+ }
+
+ dbgcGdbStubCtxReplySendEnd(pThis);
+ }
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+ }
+ else if ( RT_SUCCESS(rc)
+ || !idCpu)
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+
+ return rc;
+}
+
+
+/**
+ * List of supported query packets.
+ */
+static const GDBSTUBQPKTPROC g_aQPktProcs[] =
+{
+#define GDBSTUBQPKTPROC_INIT(a_Name, a_pfnProc) { a_Name, sizeof(a_Name) - 1, a_pfnProc }
+ GDBSTUBQPKTPROC_INIT("C", dbgcGdbStubCtxPktProcessQueryThreadId),
+ GDBSTUBQPKTPROC_INIT("Attached", dbgcGdbStubCtxPktProcessQueryAttached),
+ GDBSTUBQPKTPROC_INIT("TStatus", dbgcGdbStubCtxPktProcessQueryTStatus),
+ GDBSTUBQPKTPROC_INIT("Supported", dbgcGdbStubCtxPktProcessQuerySupported),
+ GDBSTUBQPKTPROC_INIT("Xfer:features:read", dbgcGdbStubCtxPktProcessQueryXferFeatRead),
+ GDBSTUBQPKTPROC_INIT("Rcmd", dbgcGdbStubCtxPktProcessQueryRcmd),
+ GDBSTUBQPKTPROC_INIT("fThreadInfo", dbgcGdbStubCtxPktProcessQueryThreadInfoStart),
+ GDBSTUBQPKTPROC_INIT("sThreadInfo", dbgcGdbStubCtxPktProcessQueryThreadInfoCont),
+ GDBSTUBQPKTPROC_INIT("ThreadExtraInfo", dbgcGdbStubCtxPktProcessQueryThreadExtraInfo),
+#undef GDBSTUBQPKTPROC_INIT
+};
+
+
+/**
+ * Processes a 'q' packet, sending the appropriate reply.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbQuery The query packet data (without the 'q').
+ * @param cbQuery Size of the remaining query packet in bytes.
+ */
+static int dbgcGdbStubCtxPktProcessQuery(PGDBSTUBCTX pThis, const uint8_t *pbQuery, size_t cbQuery)
+{
+ /* Search the query and execute the processor or return an empty reply if not supported. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aQPktProcs); i++)
+ {
+ size_t cbCmp = g_aQPktProcs[i].cchName < cbQuery ? g_aQPktProcs[i].cchName : cbQuery;
+
+ if (!memcmp(pbQuery, g_aQPktProcs[i].pszName, cbCmp))
+ return g_aQPktProcs[i].pfnProc(pThis, pbQuery + cbCmp, cbQuery - cbCmp);
+ }
+
+ return dbgcGdbStubCtxReplySend(pThis, NULL, 0);
+}
+
+
+/**
+ * Processes a 'vCont[;action[:thread-id]]' packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbArgs Pointer to the start of the arguments in the packet.
+ * @param cbArgs Size of arguments in bytes.
+ */
+static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessVCont(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Skip the ; following the identifier. */
+ if ( cbArgs < 2
+ || pbArgs[0] != ';')
+ return dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+
+ pbArgs++;
+ cbArgs--;
+
+ /** @todo For now we don't care about multiple threads and ignore thread IDs and multiple actions. */
+ switch (pbArgs[0])
+ {
+ case 'c':
+ {
+ if (DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ break;
+ }
+ case 's':
+ {
+ PDBGFADDRESS pStackPop = NULL;
+ RTGCPTR cbStackPop = 0;
+ rc = DBGFR3StepEx(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGF_STEP_F_INTO, NULL,
+ pStackPop, cbStackPop, 1 /*cMaxSteps*/);
+ if (RT_FAILURE(rc))
+ dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 't':
+ {
+ if (!DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ /* The reply will be send in the event loop. */
+ break;
+ }
+ default:
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+ }
+
+ return rc;
+}
+
+
+/**
+ * List of supported 'v<identifier>' packets.
+ */
+static const GDBSTUBVPKTPROC g_aVPktProcs[] =
+{
+#define GDBSTUBVPKTPROC_INIT(a_Name, a_pszReply, a_pfnProc) { a_Name, sizeof(a_Name) - 1, a_pszReply, sizeof(a_pszReply) - 1, a_pfnProc }
+ GDBSTUBVPKTPROC_INIT("Cont", "vCont;s;c;t", dbgcGdbStubCtxPktProcessVCont)
+#undef GDBSTUBVPKTPROC_INIT
+};
+
+
+/**
+ * Processes a 'v<identifier>' packet, sending the appropriate reply.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbPktRem The remaining packet data (without the 'v').
+ * @param cbPktRem Size of the remaining packet in bytes.
+ */
+static int dbgcGdbStubCtxPktProcessV(PGDBSTUBCTX pThis, const uint8_t *pbPktRem, size_t cbPktRem)
+{
+ /* Determine the end of the identifier, delimiters are '?', ';' or end of packet. */
+ bool fQuery = false;
+ const uint8_t *pbDelim = (const uint8_t *)memchr(pbPktRem, '?', cbPktRem);
+ if (!pbDelim)
+ pbDelim = (const uint8_t *)memchr(pbPktRem, ';', cbPktRem);
+ else
+ fQuery = true;
+
+ size_t cchId = 0;
+ if (pbDelim) /* Delimiter found, calculate length. */
+ cchId = pbDelim - pbPktRem;
+ else /* Not found, size goes till end of packet. */
+ cchId = cbPktRem;
+
+ /* Search the query and execute the processor or return an empty reply if not supported. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aVPktProcs); i++)
+ {
+ PCGDBSTUBVPKTPROC pVProc = &g_aVPktProcs[i];
+
+ if ( pVProc->cchName == cchId
+ && !memcmp(pbPktRem, pVProc->pszName, cchId))
+ {
+ /* Just send the static reply for a query and execute the processor for everything else. */
+ if (fQuery)
+ return dbgcGdbStubCtxReplySend(pThis, pVProc->pszReplyQ, pVProc->cchReplyQ);
+
+ /* Execute the handler. */
+ return pVProc->pfnProc(pThis, pbPktRem + cchId, cbPktRem - cchId);
+ }
+ }
+
+ return dbgcGdbStubCtxReplySend(pThis, NULL, 0);
+}
+
+
+/**
+ * Processes a 'H<op><thread-id>' packet, sending the appropriate reply.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param pbPktRem The remaining packet data (without the 'H').
+ * @param cbPktRem Size of the remaining packet in bytes.
+ */
+static int dbgcGdbStubCtxPktProcessH(PGDBSTUBCTX pThis, const uint8_t *pbPktRem, size_t cbPktRem)
+{
+ int rc = VINF_SUCCESS;
+
+ if (*pbPktRem == 'g')
+ {
+ cbPktRem--;
+ pbPktRem++;
+
+ /* We know there is an # character denoting the end so the following must return with VWRN_TRAILING_CHARS. */
+ VMCPUID idCpu;
+ rc = RTStrToUInt32Ex((const char *)pbPktRem, NULL /*ppszNext*/, 16, &idCpu);
+ if ( rc == VWRN_TRAILING_CHARS
+ && idCpu > 0)
+ {
+ idCpu--;
+
+ VMCPUID cCpus = DBGFR3CpuGetCount(pThis->Dbgc.pUVM);
+ if (idCpu < cCpus)
+ {
+ pThis->Dbgc.idCpu = idCpu;
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+ }
+ else /* Do not support the 'c' operation for now (will be handled through vCont later on anyway). */
+ rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0);
+
+ return rc;
+}
+
+
+/**
+ * Processes a completely received packet.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxPktProcess(PGDBSTUBCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->cbPkt >= 1)
+ {
+ switch (pThis->pbPktBuf[1])
+ {
+ case '!': /* Enabled extended mode. */
+ {
+ pThis->fExtendedMode = true;
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ break;
+ }
+ case '?':
+ {
+ /* Return signal state. */
+ rc = dbgcGdbStubCtxReplySendSigTrap(pThis);
+ break;
+ }
+ case 's': /* Single step, response will be sent in the event loop. */
+ {
+ PDBGFADDRESS pStackPop = NULL;
+ RTGCPTR cbStackPop = 0;
+ rc = DBGFR3StepEx(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGF_STEP_F_INTO, NULL,
+ pStackPop, cbStackPop, 1 /*cMaxSteps*/);
+ if (RT_FAILURE(rc))
+ dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'c': /* Continue, no response */
+ {
+ if (DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ break;
+ }
+ case 'H':
+ {
+ rc = dbgcGdbStubCtxPktProcessH(pThis, &pThis->pbPktBuf[2], pThis->cbPkt - 1);
+ break;
+ }
+ case 'T':
+ {
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ break;
+ }
+ case 'g': /* Read general registers. */
+ {
+ uint32_t idxRegMax = 0;
+ size_t cbRegs = 0;
+ for (;;)
+ {
+ const GDBREGDESC *pReg = &pThis->paRegs[idxRegMax++];
+ cbRegs += pReg->cBits / 8;
+ if (pReg->enmReg == DBGFREG_SS) /* Up to this seems to belong to the general register set. */
+ break;
+ }
+
+ size_t cbReplyPkt = cbRegs * 2 + 1; /* One byte needs two characters. */
+ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbReplyPkt);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbLeft = cbReplyPkt;
+ uint8_t *pbReply = pThis->pbPktBuf;
+
+ for (uint32_t i = 0; i < idxRegMax && RT_SUCCESS(rc); i++)
+ {
+ const GDBREGDESC *pReg = &pThis->paRegs[i];
+ size_t cbReg = pReg->cBits / 8;
+ union
+ {
+ uint32_t u32;
+ uint64_t u64;
+ uint8_t au8[8];
+ } RegVal;
+
+ if (pReg->cBits == 32)
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->enmReg, &RegVal.u32);
+ else
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->enmReg, &RegVal.u64);
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxEncodeBinaryAsHex(pbReply, cbLeft, &RegVal.au8[0], cbReg);
+
+ pbReply += cbReg * 2;
+ cbLeft -= cbReg * 2;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbReplyPkt);
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+
+ break;
+ }
+ case 'm': /* Read memory. */
+ {
+ uint64_t GdbTgtAddr = 0;
+ const uint8_t *pbPktSep = NULL;
+
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &GdbTgtAddr,
+ ',', &pbPktSep);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbProcessed = pbPktSep - &pThis->pbPktBuf[2];
+ uint64_t cbRead = 0;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, pThis->cbPkt - 1 - cbProcessed - 1, &cbRead, GDBSTUB_PKT_END, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbReplyPkt = cbRead * 2 + 1; /* One byte needs two characters. */
+
+ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbReplyPkt);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t *pbPktBuf = pThis->pbPktBuf;
+ size_t cbPktBufLeft = cbReplyPkt;
+ DBGFADDRESS AddrRead;
+
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrRead, GdbTgtAddr);
+
+ while ( cbRead
+ && RT_SUCCESS(rc))
+ {
+ uint8_t abTmp[_4K];
+ size_t cbThisRead = RT_MIN(cbRead, sizeof(abTmp));
+
+ rc = DBGFR3MemRead(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrRead, &abTmp[0], cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = dbgcGdbStubCtxEncodeBinaryAsHex(pbPktBuf, cbPktBufLeft, &abTmp[0], cbThisRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ DBGFR3AddrAdd(&AddrRead, cbThisRead);
+ cbRead -= cbThisRead;
+ pbPktBuf += cbThisRead;
+ cbPktBufLeft -= cbThisRead;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbReplyPkt);
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'M': /* Write memory. */
+ {
+ uint64_t GdbTgtAddr = 0;
+ const uint8_t *pbPktSep = NULL;
+
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &GdbTgtAddr,
+ ',', &pbPktSep);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbProcessed = pbPktSep - &pThis->pbPktBuf[2];
+ uint64_t cbWrite = 0;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, pThis->cbPkt - 1 - cbProcessed - 1, &cbWrite, ':', &pbPktSep);
+ if (RT_SUCCESS(rc))
+ {
+ cbProcessed = pbPktSep - &pThis->pbPktBuf[2];
+ const uint8_t *pbDataCur = pbPktSep + 1;
+ size_t cbDataLeft = pThis->cbPkt - 1 - cbProcessed - 1 - 1;
+ DBGFADDRESS AddrWrite;
+
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrWrite, GdbTgtAddr);
+
+ while ( cbWrite
+ && RT_SUCCESS(rc))
+ {
+ uint8_t abTmp[_4K];
+ size_t cbThisWrite = RT_MIN(cbWrite, sizeof(abTmp));
+ size_t cbDecoded = 0;
+
+ rc = dbgcGdbStubCtxParseHexStringAsByteBuf(pbDataCur, cbDataLeft, &abTmp[0], cbThisWrite, &cbDecoded);
+ if (!rc)
+ rc = DBGFR3MemWrite(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrWrite, &abTmp[0], cbThisWrite);
+
+ DBGFR3AddrAdd(&AddrWrite, cbThisWrite);
+ cbWrite -= cbThisWrite;
+ pbDataCur += cbDecoded;
+ cbDataLeft -= cbDecoded;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'p': /* Read a single register */
+ {
+ uint64_t uReg = 0;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &uReg,
+ GDBSTUB_PKT_END, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFREGVAL RegVal;
+ DBGFREGVALTYPE enmType;
+ const GDBREGDESC *pReg = dbgcGdbStubRegGet(pThis, uReg);
+ if (RT_LIKELY(pReg))
+ {
+ rc = DBGFR3RegNmQuery(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->pszName, &RegVal, &enmType);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbReg = pReg->cBits / 8;
+ size_t cbReplyPkt = cbReg * 2 + 1; /* One byte needs two characters. */
+
+ /* Encode data and send. */
+ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbReplyPkt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcGdbStubCtxEncodeBinaryAsHex(pThis->pbPktBuf, pThis->cbPktBufMax, &RegVal.au8[0], cbReg);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbReplyPkt);
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'P': /* Write a single register */
+ {
+ uint64_t uReg = 0;
+ const uint8_t *pbPktSep = NULL;
+ rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &uReg,
+ '=', &pbPktSep);
+ if (RT_SUCCESS(rc))
+ {
+ const GDBREGDESC *pReg = dbgcGdbStubRegGet(pThis, uReg);
+
+ if (pReg)
+ {
+ DBGFREGVAL RegVal;
+ DBGFREGVALTYPE enmValType = pReg->cBits == 64 ? DBGFREGVALTYPE_U64 : DBGFREGVALTYPE_U32;
+ size_t cbProcessed = pbPktSep - &pThis->pbPktBuf[2];
+ rc = dbgcGdbStubCtxParseHexStringAsByteBuf(pbPktSep + 1, pThis->cbPkt - 1 - cbProcessed - 1, &RegVal.au8[0], pReg->cBits / 8, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGFR3RegNmSet(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->pszName, &RegVal, enmValType);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'Z': /* Insert a breakpoint/watchpoint. */
+ {
+ GDBSTUBTPTYPE enmTpType = GDBSTUBTPTYPE_INVALID;
+ uint64_t GdbTgtTpAddr = 0;
+ uint64_t uKind = 0;
+
+ rc = dbgcGdbStubCtxParseTpPktArgs(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &enmTpType, &GdbTgtTpAddr, &uKind);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t iBp = 0;
+ DBGFADDRESS BpAddr;
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &BpAddr, GdbTgtTpAddr);
+
+ switch (enmTpType)
+ {
+ case GDBSTUBTPTYPE_EXEC_SW:
+ {
+ rc = DBGFR3BpSetInt3(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &BpAddr,
+ 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/, &iBp);
+ break;
+ }
+ case GDBSTUBTPTYPE_EXEC_HW:
+ {
+ rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &BpAddr,
+ 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/,
+ X86_DR7_RW_EO, 1 /*cb*/, &iBp);
+ break;
+ }
+ case GDBSTUBTPTYPE_MEM_ACCESS:
+ case GDBSTUBTPTYPE_MEM_READ:
+ {
+ rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &BpAddr,
+ 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/,
+ X86_DR7_RW_RW, uKind /*cb*/, &iBp);
+ break;
+ }
+ case GDBSTUBTPTYPE_MEM_WRITE:
+ {
+ rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &BpAddr,
+ 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/,
+ X86_DR7_RW_WO, uKind /*cb*/, &iBp);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid trace point type %d\n", enmTpType));
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcBpAdd(&pThis->Dbgc, iBp, NULL /*pszCmd*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcGdbStubTpRegister(pThis, enmTpType, GdbTgtTpAddr, uKind, iBp);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ else
+ dbgcBpDelete(&pThis->Dbgc, iBp);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ DBGFR3BpClear(pThis->Dbgc.pUVM, iBp);
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'z': /* Remove a breakpoint/watchpoint. */
+ {
+ GDBSTUBTPTYPE enmTpType = GDBSTUBTPTYPE_INVALID;
+ uint64_t GdbTgtTpAddr = 0;
+ uint64_t uKind = 0;
+
+ rc = dbgcGdbStubCtxParseTpPktArgs(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &enmTpType, &GdbTgtTpAddr, &uKind);
+ if (RT_SUCCESS(rc))
+ {
+ PGDBSTUBTP pTp = dbgcGdbStubTpFind(pThis, enmTpType, GdbTgtTpAddr, uKind);
+ if (pTp)
+ {
+ int rc2 = DBGFR3BpClear(pThis->Dbgc.pUVM, pTp->iBp);
+ if (RT_SUCCESS(rc2) || rc2 == VERR_DBGF_BP_NOT_FOUND)
+ dbgcBpDelete(&pThis->Dbgc, pTp->iBp);
+
+ if (RT_SUCCESS(rc2))
+ {
+ dbgcGdbStubTpDeregister(pTp);
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NOT_FOUND);
+ }
+ else
+ rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc);
+ break;
+ }
+ case 'q': /* Query packet */
+ {
+ rc = dbgcGdbStubCtxPktProcessQuery(pThis, &pThis->pbPktBuf[2], pThis->cbPkt - 1);
+ break;
+ }
+ case 'v': /* Multiletter identifier (verbose?) */
+ {
+ rc = dbgcGdbStubCtxPktProcessV(pThis, &pThis->pbPktBuf[2], pThis->cbPkt - 1);
+ break;
+ }
+ case 'R': /* Restart target. */
+ {
+ rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0);
+ break;
+ }
+ case 'k': /* Kill target. */
+ {
+ /* This is what the 'harakiri' command is doing. */
+ for (;;)
+ exit(126);
+ break;
+ }
+ case 'D': /* Detach */
+ {
+ rc = dbgcGdbStubCtxReplySendOk(pThis);
+ if (RT_SUCCESS(rc))
+ rc = VERR_DBGC_QUIT;
+ break;
+ }
+ default:
+ /* Not supported, send empty reply. */
+ rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Resets the packet buffer.
+ *
+ * @param pThis The GDB stub context.
+ */
+static void dbgcGdbStubCtxPktBufReset(PGDBSTUBCTX pThis)
+{
+ pThis->offPktBuf = 0;
+ pThis->cbPkt = 0;
+ pThis->cbChksumRecvLeft = 2;
+}
+
+
+/**
+ * Resets the given GDB stub context to the initial state.
+ *
+ * @param pThis The GDB stub context.
+ */
+static void dbgcGdbStubCtxReset(PGDBSTUBCTX pThis)
+{
+ pThis->enmState = GDBSTUBRECVSTATE_PACKET_WAIT_FOR_START;
+ dbgcGdbStubCtxPktBufReset(pThis);
+}
+
+
+/**
+ * Searches for the start character in the current data buffer.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param cbData Number of new bytes in the packet buffer.
+ * @param pcbProcessed Where to store the amount of bytes processed.
+ */
+static int dbgcGdbStubCtxPktBufSearchStart(PGDBSTUBCTX pThis, size_t cbData, size_t *pcbProcessed)
+{
+ int rc = VINF_SUCCESS;
+ const uint8_t *pbStart = (const uint8_t *)memchr(pThis->pbPktBuf, GDBSTUB_PKT_START, cbData);
+ if (pbStart)
+ {
+ /* Found the start character, align the start to the beginning of the packet buffer and advance the state machine. */
+ memmove(pThis->pbPktBuf, pbStart, cbData - (pbStart - pThis->pbPktBuf));
+ pThis->enmState = GDBSTUBRECVSTATE_PACKET_RECEIVE_BODY;
+ *pcbProcessed = (uintptr_t)(pbStart - pThis->pbPktBuf);
+ pThis->offPktBuf = 0;
+ }
+ else
+ {
+ /* Check for out of band characters. */
+ if (memchr(pThis->pbPktBuf, GDBSTUB_OOB_INTERRUPT, cbData) != NULL)
+ {
+ /* Stop target and send packet to indicate the target has stopped. */
+ if (!DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ /* The reply will be send in the event loop. */
+ }
+
+ /* Not found, ignore the received data and reset the packet buffer. */
+ dbgcGdbStubCtxPktBufReset(pThis);
+ *pcbProcessed = cbData;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Searches for the end character in the current data buffer.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param cbData Number of new bytes in the packet buffer.
+ * @param pcbProcessed Where to store the amount of bytes processed.
+ */
+static int dbgcGdbStubCtxPktBufSearchEnd(PGDBSTUBCTX pThis, size_t cbData, size_t *pcbProcessed)
+{
+ const uint8_t *pbEnd = (const uint8_t *)memchr(&pThis->pbPktBuf[pThis->offPktBuf], GDBSTUB_PKT_END, cbData);
+ if (pbEnd)
+ {
+ /* Found the end character, next comes the checksum. */
+ pThis->enmState = GDBSTUBRECVSTATE_PACKET_RECEIVE_CHECKSUM;
+
+ *pcbProcessed = (uintptr_t)(pbEnd - &pThis->pbPktBuf[pThis->offPktBuf]) + 1;
+ pThis->offPktBuf += *pcbProcessed;
+ pThis->cbPkt = pThis->offPktBuf - 1; /* Don't account for the start and end character. */
+ }
+ else
+ {
+ /* Not found, still in the middle of a packet. */
+ /** @todo Look for out of band characters. */
+ *pcbProcessed = cbData;
+ pThis->offPktBuf += cbData;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes the checksum.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param cbData Number of new bytes in the packet buffer.
+ * @param pcbProcessed Where to store the amount of bytes processed.
+ */
+static int dbgcGdbStubCtxPktBufProcessChksum(PGDBSTUBCTX pThis, size_t cbData, size_t *pcbProcessed)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbChksumProcessed = (cbData < pThis->cbChksumRecvLeft) ? cbData : pThis->cbChksumRecvLeft;
+
+ pThis->cbChksumRecvLeft -= cbChksumProcessed;
+ if (!pThis->cbChksumRecvLeft)
+ {
+ /* Verify checksum of the whole packet. */
+ uint8_t uChkSum = dbgcGdbStubCtxChrToHex(pThis->pbPktBuf[pThis->offPktBuf]) << 4
+ | dbgcGdbStubCtxChrToHex(pThis->pbPktBuf[pThis->offPktBuf + 1]);
+
+ uint8_t uSum = 0;
+ for (size_t i = 1; i < pThis->cbPkt; i++)
+ uSum += pThis->pbPktBuf[i];
+
+ if (uSum == uChkSum)
+ {
+ /* Checksum matches, send acknowledge and continue processing the complete payload. */
+ char chAck = '+';
+ rc = dbgcGdbStubCtxWrite(pThis, &chAck, sizeof(chAck));
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxPktProcess(pThis);
+ }
+ else
+ {
+ /* Send NACK and reset for the next packet. */
+ char chAck = '-';
+ rc = dbgcGdbStubCtxWrite(pThis, &chAck, sizeof(chAck));
+ }
+
+ dbgcGdbStubCtxReset(pThis);
+ }
+
+ *pcbProcessed += cbChksumProcessed;
+ return rc;
+}
+
+
+/**
+ * Process read data in the packet buffer based on the current state.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ * @param cbData Number of new bytes in the packet buffer.
+ */
+static int dbgcGdbStubCtxPktBufProcess(PGDBSTUBCTX pThis, size_t cbData)
+{
+ int rc = VINF_SUCCESS;
+
+ while ( cbData
+ && RT_SUCCESS(rc))
+ {
+ size_t cbProcessed = 0;
+
+ switch (pThis->enmState)
+ {
+ case GDBSTUBRECVSTATE_PACKET_WAIT_FOR_START:
+ {
+ rc = dbgcGdbStubCtxPktBufSearchStart(pThis, cbData, &cbProcessed);
+ break;
+ }
+ case GDBSTUBRECVSTATE_PACKET_RECEIVE_BODY:
+ {
+ rc = dbgcGdbStubCtxPktBufSearchEnd(pThis, cbData, &cbProcessed);
+ break;
+ }
+ case GDBSTUBRECVSTATE_PACKET_RECEIVE_CHECKSUM:
+ {
+ rc = dbgcGdbStubCtxPktBufProcessChksum(pThis, cbData, &cbProcessed);
+ break;
+ }
+ default:
+ /* Should never happen. */
+ rc = VERR_INTERNAL_ERROR;
+ }
+
+ cbData -= cbProcessed;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Receive data and processes complete packets.
+ *
+ * @returns Status code.
+ * @param pThis The GDB stub context.
+ */
+static int dbgcGdbStubCtxRecv(PGDBSTUBCTX pThis)
+{
+ /*
+ * Read in 32 bytes chunks for now (need some peek API to get the amount of bytes actually available
+ * to make it a bit more optimized).
+ */
+ int rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, 32);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbThisRead = 32;
+ rc = pThis->Dbgc.pIo->pfnRead(pThis->Dbgc.pIo, &pThis->pbPktBuf[pThis->offPktBuf], cbThisRead, &cbThisRead);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxPktBufProcess(pThis, cbThisRead);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes debugger events.
+ *
+ * @returns VBox status code.
+ * @param pThis The GDB stub context data.
+ * @param pEvent Pointer to event data.
+ */
+static int dbgcGdbStubCtxProcessEvent(PGDBSTUBCTX pThis, PCDBGFEVENT pEvent)
+{
+ /*
+ * Process the event.
+ */
+ PDBGC pDbgc = &pThis->Dbgc;
+ pThis->Dbgc.pszScratch = &pThis->Dbgc.achInput[0];
+ pThis->Dbgc.iArg = 0;
+ int rc = VINF_SUCCESS;
+ switch (pEvent->enmType)
+ {
+ /*
+ * The first part is events we have initiated with commands.
+ */
+ case DBGFEVENT_HALT_DONE:
+ {
+ rc = dbgcGdbStubCtxReplySendSigTrap(pThis);
+ break;
+ }
+
+
+ /*
+ * The second part is events which can occur at any time.
+ */
+ case DBGFEVENT_FATAL_ERROR:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbf event: Fatal error! (%s)\n",
+ dbgcGetEventCtx(pEvent->enmCtx));
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+ break;
+ }
+
+ case DBGFEVENT_BREAKPOINT:
+ case DBGFEVENT_BREAKPOINT_IO:
+ case DBGFEVENT_BREAKPOINT_MMIO:
+ case DBGFEVENT_BREAKPOINT_HYPER:
+ {
+ rc = dbgcBpExec(pDbgc, pEvent->u.Bp.hBp);
+ switch (rc)
+ {
+ case VERR_DBGC_BP_NOT_FOUND:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Unknown breakpoint %u! (%s)\n",
+ pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ case VINF_DBGC_BP_NO_COMMAND:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Breakpoint %u! (%s)\n",
+ pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ case VINF_BUFFER_OVERFLOW:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Breakpoint %u! Command too long to execute! (%s)\n",
+ pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ default:
+ break;
+ }
+ if (RT_SUCCESS(rc) && DBGFR3IsHalted(pDbgc->pUVM, VMCPUID_ALL))
+ {
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+
+ /* Set the resume flag to ignore the breakpoint when resuming execution. */
+ if ( RT_SUCCESS(rc)
+ && pEvent->enmType == DBGFEVENT_BREAKPOINT)
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r eflags.rf = 1");
+ }
+
+ rc = dbgcGdbStubCtxReplySendSigTrap(pThis);
+ break;
+ }
+
+ case DBGFEVENT_STEPPED:
+ case DBGFEVENT_STEPPED_HYPER:
+ {
+ rc = dbgcGdbStubCtxReplySendSigTrap(pThis);
+ break;
+ }
+
+ case DBGFEVENT_ASSERTION_HYPER:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "\ndbgf event: Hypervisor Assertion! (%s)\n"
+ "%s"
+ "%s"
+ "\n",
+ dbgcGetEventCtx(pEvent->enmCtx),
+ pEvent->u.Assert.pszMsg1,
+ pEvent->u.Assert.pszMsg2);
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+ break;
+ }
+
+ case DBGFEVENT_DEV_STOP:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "\n"
+ "dbgf event: DBGFSTOP (%s)\n"
+ "File: %s\n"
+ "Line: %d\n"
+ "Function: %s\n",
+ dbgcGetEventCtx(pEvent->enmCtx),
+ pEvent->u.Src.pszFile,
+ pEvent->u.Src.uLine,
+ pEvent->u.Src.pszFunction);
+ if (RT_SUCCESS(rc) && pEvent->u.Src.pszMessage && *pEvent->u.Src.pszMessage)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "Message: %s\n",
+ pEvent->u.Src.pszMessage);
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+ break;
+ }
+
+
+ case DBGFEVENT_INVALID_COMMAND:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Invalid command event!\n");
+ break;
+ }
+
+ case DBGFEVENT_POWERING_OFF:
+ {
+ pThis->Dbgc.fReady = false;
+ pThis->Dbgc.pIo->pfnSetReady(pThis->Dbgc.pIo, false);
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+ default:
+ {
+ /*
+ * Probably a generic event. Look it up to find its name.
+ */
+ PCDBGCSXEVT pEvtDesc = dbgcEventLookup(pEvent->enmType);
+ if (pEvtDesc)
+ {
+ if (pEvtDesc->enmKind == kDbgcSxEventKind_Interrupt)
+ {
+ Assert(pEvtDesc->pszDesc);
+ Assert(pEvent->u.Generic.cArgs == 1);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s no %#llx! (%s)\n",
+ pEvtDesc->pszDesc, pEvent->u.Generic.auArgs[0], pEvtDesc->pszName);
+ }
+ else if (pEvtDesc->fFlags & DBGCSXEVT_F_BUGCHECK)
+ {
+ Assert(pEvent->u.Generic.cArgs >= 5);
+ char szDetails[512];
+ DBGFR3FormatBugCheck(pDbgc->pUVM, szDetails, sizeof(szDetails), pEvent->u.Generic.auArgs[0],
+ pEvent->u.Generic.auArgs[1], pEvent->u.Generic.auArgs[2],
+ pEvent->u.Generic.auArgs[3], pEvent->u.Generic.auArgs[4]);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s %s%s!\n%s", pEvtDesc->pszName,
+ pEvtDesc->pszDesc ? "- " : "", pEvtDesc->pszDesc ? pEvtDesc->pszDesc : "",
+ szDetails);
+ }
+ else if ( (pEvtDesc->fFlags & DBGCSXEVT_F_TAKE_ARG)
+ || pEvent->u.Generic.cArgs > 1
+ || ( pEvent->u.Generic.cArgs == 1
+ && pEvent->u.Generic.auArgs[0] != 0))
+ {
+ if (pEvtDesc->pszDesc)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s - %s!",
+ pEvtDesc->pszName, pEvtDesc->pszDesc);
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s!", pEvtDesc->pszName);
+ if (pEvent->u.Generic.cArgs <= 1)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " arg=%#llx\n", pEvent->u.Generic.auArgs[0]);
+ else
+ {
+ for (uint32_t i = 0; i < pEvent->u.Generic.cArgs; i++)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " args[%u]=%#llx", i, pEvent->u.Generic.auArgs[i]);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\n");
+ }
+ }
+ else
+ {
+ if (pEvtDesc->pszDesc)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s - %s!\n",
+ pEvtDesc->pszName, pEvtDesc->pszDesc);
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s!\n", pEvtDesc->pszName);
+ }
+ }
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Unknown event %d!\n", pEvent->enmType);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Run the debugger console.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the GDB stub context.
+ */
+int dbgcGdbStubRun(PGDBSTUBCTX pThis)
+{
+ /* Select the register set based on the CPU mode. */
+ CPUMMODE enmMode = DBGCCmdHlpGetCpuMode(&pThis->Dbgc.CmdHlp);
+ switch (enmMode)
+ {
+ case CPUMMODE_PROTECTED:
+ pThis->paRegs = &g_aGdbRegs32[0];
+ pThis->cRegs = RT_ELEMENTS(g_aGdbRegs32);
+ break;
+ case CPUMMODE_LONG:
+ pThis->paRegs = &g_aGdbRegs64[0];
+ pThis->cRegs = RT_ELEMENTS(g_aGdbRegs64);
+ break;
+ case CPUMMODE_REAL:
+ default:
+ return DBGCCmdHlpPrintf(&pThis->Dbgc.CmdHlp, "error: Invalid CPU mode %d.\n", enmMode);
+ }
+
+ /*
+ * We're ready for commands now.
+ */
+ pThis->Dbgc.fReady = true;
+ pThis->Dbgc.pIo->pfnSetReady(pThis->Dbgc.pIo, true);
+
+ /*
+ * Main Debugger Loop.
+ *
+ * This loop will either block on waiting for input or on waiting on
+ * debug events. If we're forwarding the log we cannot wait for long
+ * before we must flush the log.
+ */
+ int rc;
+ for (;;)
+ {
+ rc = VERR_SEM_OUT_OF_TURN;
+ if (pThis->Dbgc.pUVM)
+ rc = DBGFR3QueryWaitable(pThis->Dbgc.pUVM);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Wait for a debug event.
+ */
+ DBGFEVENT Event;
+ rc = DBGFR3EventWait(pThis->Dbgc.pUVM, 32, &Event);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcGdbStubCtxProcessEvent(pThis, &Event);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (rc != VERR_TIMEOUT)
+ break;
+
+ /*
+ * Check for input.
+ */
+ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, 0))
+ {
+ rc = dbgcGdbStubCtxRecv(pThis);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else if (rc == VERR_SEM_OUT_OF_TURN)
+ {
+ /*
+ * Wait for input.
+ */
+ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, 1000))
+ {
+ rc = dbgcGdbStubCtxRecv(pThis);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGC::pfnOutput
+ */
+static DECLCALLBACK(int) dbgcOutputGdb(void *pvUser, const char *pachChars, size_t cbChars)
+{
+ PGDBSTUBCTX pThis = (PGDBSTUBCTX)pvUser;
+
+ pThis->fOutput = true;
+ int rc = dbgcGdbStubCtxReplySendBegin(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t chConOut = 'O';
+ rc = dbgcGdbStubCtxReplySendData(pThis, &chConOut, sizeof(chConOut));
+ if (RT_SUCCESS(rc))
+ {
+ /* Convert the characters to hex. */
+ const char *pachCur = pachChars;
+
+ while ( cbChars
+ && RT_SUCCESS(rc))
+ {
+ uint8_t achHex[512 + 1];
+ size_t cbThisSend = RT_MIN((sizeof(achHex) - 1) / 2, cbChars); /* Each character needs two bytes. */
+
+ rc = dbgcGdbStubCtxEncodeBinaryAsHex(&achHex[0], cbThisSend * 2 + 1, pachCur, cbThisSend);
+ if (RT_SUCCESS(rc))
+ rc = dbgcGdbStubCtxReplySendData(pThis, &achHex[0], cbThisSend * 2);
+
+ pachCur += cbThisSend;
+ cbChars -= cbThisSend;
+ }
+ }
+
+ dbgcGdbStubCtxReplySendEnd(pThis);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Creates a GDB stub context instance with the given backend.
+ *
+ * @returns VBox status code.
+ * @param ppGdbStubCtx Where to store the pointer to the GDB stub context instance on success.
+ * @param pIo Pointer to the I/O callback table.
+ * @param fFlags Flags controlling the behavior.
+ */
+static int dbgcGdbStubCtxCreate(PPGDBSTUBCTX ppGdbStubCtx, PCDBGCIO pIo, unsigned fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pIo, VERR_INVALID_POINTER);
+ AssertMsgReturn(!fFlags, ("%#x", fFlags), VERR_INVALID_PARAMETER);
+
+ /*
+ * Allocate and initialize.
+ */
+ PGDBSTUBCTX pThis = (PGDBSTUBCTX)RTMemAllocZ(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ dbgcInitCmdHlp(&pThis->Dbgc);
+ /*
+ * This is compied from the native debug console (will be used for monitor commands)
+ * in DBGCConsole.cpp. Try to keep both functions in sync.
+ */
+ pThis->Dbgc.pIo = pIo;
+ pThis->Dbgc.pfnOutput = dbgcOutputGdb;
+ pThis->Dbgc.pvOutputUser = pThis;
+ pThis->Dbgc.pVM = NULL;
+ pThis->Dbgc.pUVM = NULL;
+ pThis->Dbgc.idCpu = 0;
+ pThis->Dbgc.hDbgAs = DBGF_AS_GLOBAL;
+ pThis->Dbgc.pszEmulation = "CodeView/WinDbg";
+ pThis->Dbgc.paEmulationCmds = &g_aCmdsCodeView[0];
+ pThis->Dbgc.cEmulationCmds = g_cCmdsCodeView;
+ pThis->Dbgc.paEmulationFuncs = &g_aFuncsCodeView[0];
+ pThis->Dbgc.cEmulationFuncs = g_cFuncsCodeView;
+ //pThis->Dbgc.fLog = false;
+ pThis->Dbgc.fRegTerse = true;
+ pThis->Dbgc.fStepTraceRegs = true;
+ //pThis->Dbgc.cPagingHierarchyDumps = 0;
+ //pThis->Dbgc.DisasmPos = {0};
+ //pThis->Dbgc.SourcePos = {0};
+ //pThis->Dbgc.DumpPos = {0};
+ pThis->Dbgc.pLastPos = &pThis->Dbgc.DisasmPos;
+ //pThis->Dbgc.cbDumpElement = 0;
+ //pThis->Dbgc.cVars = 0;
+ //pThis->Dbgc.paVars = NULL;
+ //pThis->Dbgc.pPlugInHead = NULL;
+ //pThis->Dbgc.pFirstBp = NULL;
+ //pThis->Dbgc.abSearch = {0};
+ //pThis->Dbgc.cbSearch = 0;
+ pThis->Dbgc.cbSearchUnit = 1;
+ pThis->Dbgc.cMaxSearchHits = 1;
+ //pThis->Dbgc.SearchAddr = {0};
+ //pThis->Dbgc.cbSearchRange = 0;
+
+ //pThis->Dbgc.uInputZero = 0;
+ //pThis->Dbgc.iRead = 0;
+ //pThis->Dbgc.iWrite = 0;
+ //pThis->Dbgc.cInputLines = 0;
+ //pThis->Dbgc.fInputOverflow = false;
+ pThis->Dbgc.fReady = true;
+ pThis->Dbgc.pszScratch = &pThis->Dbgc.achScratch[0];
+ //pThis->Dbgc.iArg = 0;
+ //pThis->Dbgc.rcOutput = 0;
+ //pThis->Dbgc.rcCmd = 0;
+
+ //pThis->Dbgc.pszHistoryFile = NULL;
+ //pThis->Dbgc.pszGlobalInitScript = NULL;
+ //pThis->Dbgc.pszLocalInitScript = NULL;
+
+ dbgcEvalInit();
+
+ /* Init the GDB stub specific parts. */
+ pThis->cbPktBufMax = 0;
+ pThis->pbPktBuf = NULL;
+ pThis->fFeatures = GDBSTUBCTX_FEATURES_F_TGT_DESC;
+ pThis->pachTgtXmlDesc = NULL;
+ pThis->cbTgtXmlDesc = 0;
+ pThis->fExtendedMode = false;
+ pThis->fOutput = false;
+ pThis->fInThrdInfoQuery = false;
+ RTListInit(&pThis->LstTps);
+ dbgcGdbStubCtxReset(pThis);
+
+ *ppGdbStubCtx = pThis;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Destroys the given GDB stub context.
+ *
+ * @param pThis The GDB stub context to destroy.
+ */
+static void dbgcGdbStubDestroy(PGDBSTUBCTX pThis)
+{
+ AssertPtr(pThis);
+
+ /* Detach from the VM. */
+ if (pThis->Dbgc.pUVM)
+ DBGFR3Detach(pThis->Dbgc.pUVM);
+
+ /* Free config strings. */
+ RTStrFree(pThis->Dbgc.pszGlobalInitScript);
+ pThis->Dbgc.pszGlobalInitScript = NULL;
+ RTStrFree(pThis->Dbgc.pszLocalInitScript);
+ pThis->Dbgc.pszLocalInitScript = NULL;
+ RTStrFree(pThis->Dbgc.pszHistoryFile);
+ pThis->Dbgc.pszHistoryFile = NULL;
+
+ /* Finally, free the instance memory. */
+ RTMemFree(pThis);
+}
+
+
+DECL_HIDDEN_CALLBACK(int) dbgcGdbStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pUVM, VERR_INVALID_VM_HANDLE);
+ PVM pVM = NULL;
+ if (pUVM)
+ {
+ pVM = VMR3GetVM(pUVM);
+ AssertPtrReturn(pVM, VERR_INVALID_VM_HANDLE);
+ }
+
+ /*
+ * Allocate and initialize instance data
+ */
+ PGDBSTUBCTX pThis;
+ int rc = dbgcGdbStubCtxCreate(&pThis, pIo, fFlags);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!HMR3IsEnabled(pUVM) && !NEMR3IsEnabled(pUVM))
+ pThis->Dbgc.hDbgAs = DBGF_AS_RC_AND_GC_GLOBAL;
+
+ /*
+ * Attach to the specified VM.
+ */
+ if (RT_SUCCESS(rc) && pUVM)
+ {
+ rc = DBGFR3Attach(pUVM);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Dbgc.pVM = pVM;
+ pThis->Dbgc.pUVM = pUVM;
+ pThis->Dbgc.idCpu = 0;
+ }
+ else
+ rc = pThis->Dbgc.CmdHlp.pfnVBoxError(&pThis->Dbgc.CmdHlp, rc, "When trying to attach to VM %p\n", pThis->Dbgc.pVM);
+ }
+
+ /*
+ * Load plugins.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (pVM)
+ DBGFR3PlugInLoadAll(pThis->Dbgc.pUVM);
+ dbgcEventInit(&pThis->Dbgc);
+ //dbgcRunInitScripts(pDbgc); Not yet
+
+ if (!DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+
+ /*
+ * Run the debugger main loop.
+ */
+ rc = dbgcGdbStubRun(pThis);
+ dbgcEventTerm(&pThis->Dbgc);
+ }
+
+ /*
+ * Cleanup console debugger session.
+ */
+ dbgcGdbStubDestroy(pThis);
+ return rc == VERR_DBGC_QUIT ? VINF_SUCCESS : rc;
+}
+
diff --git a/src/VBox/Debugger/DBGCInternal.h b/src/VBox/Debugger/DBGCInternal.h
new file mode 100644
index 00000000..4776acae
--- /dev/null
+++ b/src/VBox/Debugger/DBGCInternal.h
@@ -0,0 +1,678 @@
+/* $Id: DBGCInternal.h $ */
+/** @file
+ * DBGC - Debugger Console, Internal Header File.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_DBGCInternal_h
+#define DEBUGGER_INCLUDED_SRC_DBGCInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/err.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/dbgfflowtrace.h>
+
+#include <iprt/list.h>
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+
+/**
+ * Debugger console per breakpoint data.
+ */
+typedef struct DBGCBP
+{
+ /** Pointer to the next breakpoint in the list. */
+ struct DBGCBP *pNext;
+ /** The breakpoint identifier. */
+ uint32_t iBp;
+ /** The size of the command. */
+ size_t cchCmd;
+ /** The command to execute when the breakpoint is hit. */
+ char szCmd[1];
+} DBGCBP;
+/** Pointer to a breakpoint. */
+typedef DBGCBP *PDBGCBP;
+
+
+typedef enum DBGCEVTSTATE
+{
+ kDbgcEvtState_Invalid = 0,
+ kDbgcEvtState_Disabled,
+ kDbgcEvtState_Enabled,
+ kDbgcEvtState_Notify
+} DBGCEVTSTATE;
+
+/**
+ * Debugger console per event configuration.
+ */
+typedef struct DBGCEVTCFG
+{
+ /** The event state. */
+ DBGCEVTSTATE enmState;
+ /** The size of the command. */
+ size_t cchCmd;
+ /** The command to execute when the event occurs. */
+ char szCmd[1];
+} DBGCEVTCFG;
+/** Pointer to a event configuration. */
+typedef DBGCEVTCFG *PDBGCEVTCFG;
+/** Pointer to a const event configuration. */
+typedef DBGCEVTCFG const *PCDBGCEVTCFG;
+
+
+/**
+ * Named variable.
+ *
+ * Always allocated from heap in one single block.
+ */
+typedef struct DBGCNAMEDVAR
+{
+ /** The variable. */
+ DBGCVAR Var;
+ /** Its name. */
+ char szName[1];
+} DBGCNAMEDVAR;
+/** Pointer to named variable. */
+typedef DBGCNAMEDVAR *PDBGCNAMEDVAR;
+
+
+/**
+ * Debugger console per trace flow data.
+ */
+typedef struct DBGCTFLOW
+{
+ /** Node for the trace flow module list. */
+ RTLISTNODE NdTraceFlow;
+ /** Handle of the DGF trace flow module. */
+ DBGFFLOWTRACEMOD hTraceFlowMod;
+ /** The control flow graph for the module. */
+ DBGFFLOW hFlow;
+ /** The trace flow module identifier. */
+ uint32_t iTraceFlowMod;
+} DBGCTFLOW;
+/** Pointer to the per trace flow data. */
+typedef DBGCTFLOW *PDBGCTFLOW;
+
+
+/**
+ * Debugger console status
+ */
+typedef enum DBGCSTATUS
+{
+ /** Normal status, .*/
+ DBGC_HALTED
+
+} DBGCSTATUS;
+
+
+/**
+ * Debugger console instance data.
+ */
+typedef struct DBGC
+{
+ /** Command helpers. */
+ DBGCCMDHLP CmdHlp;
+ /** Wrappers for DBGF output. */
+ DBGFINFOHLP DbgfOutputHlp;
+ /** Pointer to I/O callback structure. */
+ PCDBGCIO pIo;
+
+ /**
+ * Output a bunch of characters.
+ *
+ * @returns VBox status code.
+ * @param pvUser Opaque user data from DBGC::pvOutputUser.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnOutput, (void *pvUser, const char *pachChars, size_t cbChars));
+ /** Opqaue user data passed to DBGC::pfnOutput. */
+ void *pvOutputUser;
+
+ /** Pointer to the current VM. */
+ PVM pVM;
+ /** The user mode handle of the current VM. */
+ PUVM pUVM;
+ /** The ID of current virtual CPU. */
+ VMCPUID idCpu;
+ /** The current address space handle. */
+ RTDBGAS hDbgAs;
+ /** The current debugger emulation. */
+ const char *pszEmulation;
+ /** Pointer to the commands for the current debugger emulation. */
+ PCDBGCCMD paEmulationCmds;
+ /** The number of commands paEmulationCmds points to. */
+ uint32_t cEmulationCmds;
+ /** Pointer to the functions for the current debugger emulation. */
+ PCDBGCFUNC paEmulationFuncs;
+ /** The number of functions paEmulationFuncs points to. */
+ uint32_t cEmulationFuncs;
+ /** Log indicator. (If set we're writing the log to the console.) */
+ bool fLog;
+
+ /** Counter use to suppress the printing of the headers. */
+ uint8_t cPagingHierarchyDumps;
+ /** Indicates whether the register are terse or sparse. */
+ bool fRegTerse;
+
+ /** @name Stepping
+ * @{ */
+ /** Whether to display registers when tracing. */
+ bool fStepTraceRegs;
+ /** Number of multi-steps left, zero if not multi-stepping. */
+ uint32_t cMultiStepsLeft;
+ /** The multi-step stride length. */
+ uint32_t uMultiStepStrideLength;
+ /** The active multi-step command. */
+ PCDBGCCMD pMultiStepCmd;
+ /** @} */
+
+ /** Current disassembler position. */
+ DBGCVAR DisasmPos;
+ /** The flags that goes with DisasmPos. */
+ uint32_t fDisasm;
+ /** Current source position. (flat GC) */
+ DBGCVAR SourcePos;
+ /** Current memory dump position. */
+ DBGCVAR DumpPos;
+ /** Size of the previous dump element. */
+ unsigned cbDumpElement;
+ /** Points to DisasmPos, SourcePos or DumpPos depending on which was
+ * used last. */
+ PCDBGCVAR pLastPos;
+
+ /** Number of variables in papVars. */
+ unsigned cVars;
+ /** Array of global variables.
+ * Global variables can be referenced using the $ operator and set
+ * and unset using command with those names. */
+ PDBGCNAMEDVAR *papVars;
+
+ /** The list of breakpoints. (singly linked) */
+ PDBGCBP pFirstBp;
+ /** The list of known trace flow modules. */
+ RTLISTANCHOR LstTraceFlowMods;
+
+ /** Software interrupt events. */
+ PDBGCEVTCFG apSoftInts[256];
+ /** Hardware interrupt events. */
+ PDBGCEVTCFG apHardInts[256];
+ /** Selectable events (first few entries are unused). */
+ PDBGCEVTCFG apEventCfgs[DBGFEVENT_END];
+
+ /** Save search pattern. */
+ uint8_t abSearch[256];
+ /** The length of the search pattern. */
+ uint32_t cbSearch;
+ /** The search unit */
+ uint32_t cbSearchUnit;
+ /** The max hits. */
+ uint64_t cMaxSearchHits;
+ /** The address to resume searching from. */
+ DBGFADDRESS SearchAddr;
+ /** What's left of the original search range. */
+ RTGCUINTPTR cbSearchRange;
+
+ /** @name Parsing and Execution
+ * @{ */
+
+ /** Input buffer. */
+ char achInput[2048];
+ /** To ease debugging. */
+ unsigned uInputZero;
+ /** Write index in the input buffer. */
+ unsigned iWrite;
+ /** Read index in the input buffer. */
+ unsigned iRead;
+ /** The number of lines in the buffer. */
+ unsigned cInputLines;
+ /** Indicates that we have a buffer overflow condition.
+ * This means that input is ignored up to the next newline. */
+ bool fInputOverflow;
+ /** Indicates whether or we're ready for input. */
+ bool fReady;
+ /** Scratch buffer position. */
+ char *pszScratch;
+ /** Scratch buffer. */
+ char achScratch[16384];
+ /** Argument array position. */
+ unsigned iArg;
+ /** Array of argument variables. */
+ DBGCVAR aArgs[100];
+
+ /** rc from the last dbgcHlpPrintfV(). */
+ int rcOutput;
+ /** The last character we wrote. */
+ char chLastOutput;
+
+ /** rc from the last command. */
+ int rcCmd;
+ /** @} */
+
+ /** The command history file (not yet implemented). */
+ char *pszHistoryFile;
+ /** The global debugger init script. */
+ char *pszGlobalInitScript;
+ /** The per VM debugger init script. */
+ char *pszLocalInitScript;
+} DBGC;
+/** Pointer to debugger console instance data. */
+typedef DBGC *PDBGC;
+
+/** Converts a Command Helper pointer to a pointer to DBGC instance data. */
+#define DBGC_CMDHLP2DBGC(pCmdHlp) ( (PDBGC)((uintptr_t)(pCmdHlp) - RT_UOFFSETOF(DBGC, CmdHlp)) )
+
+
+/**
+ * Chunk of external commands.
+ */
+typedef struct DBGCEXTCMDS
+{
+ /** Number of commands descriptors. */
+ unsigned cCmds;
+ /** Pointer to array of command descriptors. */
+ PCDBGCCMD paCmds;
+ /** Pointer to the next chunk. */
+ struct DBGCEXTCMDS *pNext;
+} DBGCEXTCMDS;
+/** Pointer to chunk of external commands. */
+typedef DBGCEXTCMDS *PDBGCEXTCMDS;
+
+
+/**
+ * Chunk of external functions.
+ */
+typedef struct DBGCEXTFUNCS
+{
+ /** Number of functions descriptors. */
+ uint32_t cFuncs;
+ /** Pointer to array of functions descriptors. */
+ PCDBGCFUNC paFuncs;
+ /** Pointer to the next chunk. */
+ struct DBGCEXTFUNCS *pNext;
+} DBGCEXTFUNCS;
+/** Pointer to chunk of external functions. */
+typedef DBGCEXTFUNCS *PDBGCEXTFUNCS;
+
+
+
+/**
+ * Unary operator handler function.
+ *
+ * @returns 0 on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg The argument.
+ * @param enmCat The desired result category. Can be ignored.
+ * @param pResult Where to store the result.
+ */
+typedef DECLCALLBACKTYPE(int, FNDBGCOPUNARY,(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult));
+/** Pointer to a unary operator handler function. */
+typedef FNDBGCOPUNARY *PFNDBGCOPUNARY;
+
+
+/**
+ * Binary operator handler function.
+ *
+ * @returns 0 on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+typedef DECLCALLBACKTYPE(int, FNDBGCOPBINARY,(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult));
+/** Pointer to a binary operator handler function. */
+typedef FNDBGCOPBINARY *PFNDBGCOPBINARY;
+
+
+/**
+ * Operator descriptor.
+ */
+typedef struct DBGCOP
+{
+ /** Operator mnemonic. */
+ char szName[4];
+ /** Length of name. */
+ const unsigned cchName;
+ /** Whether or not this is a binary operator.
+ * Unary operators are evaluated right-to-left while binary are left-to-right. */
+ bool fBinary;
+ /** Precedence level. */
+ unsigned iPrecedence;
+ /** Unary operator handler. */
+ PFNDBGCOPUNARY pfnHandlerUnary;
+ /** Binary operator handler. */
+ PFNDBGCOPBINARY pfnHandlerBinary;
+ /** The category of the 1st argument.
+ * Set to DBGCVAR_CAT_ANY if anything goes. */
+ DBGCVARCAT enmCatArg1;
+ /** The category of the 2nd argument.
+ * Set to DBGCVAR_CAT_ANY if anything goes. */
+ DBGCVARCAT enmCatArg2;
+ /** Operator description. */
+ const char *pszDescription;
+} DBGCOP;
+/** Pointer to an operator descriptor. */
+typedef DBGCOP *PDBGCOP;
+/** Pointer to a const operator descriptor. */
+typedef const DBGCOP *PCDBGCOP;
+
+
+
+/** Pointer to symbol descriptor. */
+typedef struct DBGCSYM *PDBGCSYM;
+/** Pointer to const symbol descriptor. */
+typedef const struct DBGCSYM *PCDBGCSYM;
+
+/**
+ * Get builtin symbol.
+ *
+ * @returns 0 on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pSymDesc Pointer to the symbol descriptor.
+ * @param pCmdHlp Pointer to the command callback structure.
+ * @param enmType The result type.
+ * @param pResult Where to store the result.
+ */
+typedef DECLCALLBACKTYPE(int, FNDBGCSYMGET,(PCDBGCSYM pSymDesc, PDBGCCMDHLP pCmdHlp, DBGCVARTYPE enmType, PDBGCVAR pResult));
+/** Pointer to get function for a builtin symbol. */
+typedef FNDBGCSYMGET *PFNDBGCSYMGET;
+
+/**
+ * Set builtin symbol.
+ *
+ * @returns 0 on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pSymDesc Pointer to the symbol descriptor.
+ * @param pCmdHlp Pointer to the command callback structure.
+ * @param pValue The value to assign the symbol.
+ */
+typedef DECLCALLBACKTYPE(int, FNDBGCSYMSET,(PCDBGCSYM pSymDesc, PDBGCCMDHLP pCmdHlp, PCDBGCVAR pValue));
+/** Pointer to set function for a builtin symbol. */
+typedef FNDBGCSYMSET *PFNDBGCSYMSET;
+
+
+/**
+ * Symbol description (for builtin symbols).
+ */
+typedef struct DBGCSYM
+{
+ /** Symbol name. */
+ const char *pszName;
+ /** Get function. */
+ PFNDBGCSYMGET pfnGet;
+ /** Set function. (NULL if readonly) */
+ PFNDBGCSYMSET pfnSet;
+ /** User data. */
+ unsigned uUser;
+} DBGCSYM;
+
+
+/** Selectable debug event kind. */
+typedef enum
+{
+ kDbgcSxEventKind_Plain,
+ kDbgcSxEventKind_Interrupt
+} DBGCSXEVENTKIND;
+
+/**
+ * Selectable debug event name / type lookup table entry.
+ *
+ * This also contains the default setting and an alternative name.
+ */
+typedef struct DBGCSXEVT
+{
+ /** The event type. */
+ DBGFEVENTTYPE enmType;
+ /** The event name. */
+ const char *pszName;
+ /** Alternative event name (optional). */
+ const char *pszAltNm;
+ /** The kind of event. */
+ DBGCSXEVENTKIND enmKind;
+ /** The default state. */
+ DBGCEVTSTATE enmDefault;
+ /** Flags, DBGCSXEVT_F_XXX. */
+ uint32_t fFlags;
+ /** Description for use when reporting the event, optional. */
+ const char *pszDesc;
+} DBGCSXEVT;
+/** Pointer to a constant selectable debug event descriptor. */
+typedef DBGCSXEVT const *PCDBGCSXEVT;
+
+/** @name DBGCSXEVT_F_XXX
+ * @{ */
+#define DBGCSXEVT_F_TAKE_ARG RT_BIT_32(0)
+/** Windows bugcheck, should take 5 arguments. */
+#define DBGCSXEVT_F_BUGCHECK RT_BIT_32(1)
+/** @} */
+
+
+/**
+ * Control flow graph basic block dumper state
+ */
+typedef struct DBGCFLOWBBDUMP
+{
+ /** The basic block referenced. */
+ DBGFFLOWBB hFlowBb;
+ /** Cached start address. */
+ DBGFADDRESS AddrStart;
+ /** Target address. */
+ DBGFADDRESS AddrTarget;
+ /** Width of the basic block in chars. */
+ uint32_t cchWidth;
+ /** Height of the basic block in chars. */
+ uint32_t cchHeight;
+ /** X coordinate of the start. */
+ uint32_t uStartX;
+ /** Y coordinate of the start. */
+ uint32_t uStartY;
+} DBGCFLOWBBDUMP;
+/** Pointer to the control flow graph basic block dump state. */
+typedef DBGCFLOWBBDUMP *PDBGCFLOWBBDUMP;
+
+
+/**
+ * Control flow graph branch table dumper state.
+ */
+typedef struct DBGCFLOWBRANCHTBLDUMP
+{
+ /** The branch table referenced. */
+ DBGFFLOWBRANCHTBL hFlowBranchTbl;
+ /** Cached start address. */
+ DBGFADDRESS AddrStart;
+ /** Width of the branch table in chars. */
+ uint32_t cchWidth;
+ /** Height of the branch table in chars. */
+ uint32_t cchHeight;
+ /** X coordinate of the start. */
+ uint32_t uStartX;
+ /** Y coordinate of the start. */
+ uint32_t uStartY;
+} DBGCFLOWBRANCHTBLDUMP;
+/** Pointer to control flow graph branch table state. */
+typedef DBGCFLOWBRANCHTBLDUMP *PDBGCFLOWBRANCHTBLDUMP;
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+int dbgcBpAdd(PDBGC pDbgc, RTUINT iBp, const char *pszCmd);
+int dbgcBpUpdate(PDBGC pDbgc, RTUINT iBp, const char *pszCmd);
+int dbgcBpDelete(PDBGC pDbgc, RTUINT iBp);
+PDBGCBP dbgcBpGet(PDBGC pDbgc, RTUINT iBp);
+int dbgcBpExec(PDBGC pDbgc, RTUINT iBp);
+
+DECLHIDDEN(PDBGCTFLOW) dbgcFlowTraceModGet(PDBGC pDbgc, uint32_t iTraceFlowMod);
+DECLHIDDEN(int) dbgcFlowTraceModAdd(PDBGC pDbgc, DBGFFLOWTRACEMOD hFlowTraceMod, DBGFFLOW hFlow, uint32_t *piId);
+DECLHIDDEN(int) dbgcFlowTraceModDelete(PDBGC pDbgc, uint32_t iFlowTraceMod);
+
+void dbgcEvalInit(void);
+int dbgcEvalSub(PDBGC pDbgc, char *pszExpr, size_t cchExpr, DBGCVARCAT enmCategory, PDBGCVAR pResult);
+int dbgcEvalCommand(PDBGC pDbgc, char *pszCmd, size_t cchCmd, bool fNoExecute);
+int dbgcEvalCommands(PDBGC pDbgc, char *pszCmds, size_t cchCmds, bool fNoExecute);
+int dbgcEvalScript(PDBGC pDbgc, const char *pszFilename, bool fAnnounce);
+
+int dbgcSymbolGet(PDBGC pDbgc, const char *pszSymbol, DBGCVARTYPE enmType, PDBGCVAR pResult);
+PCDBGCSYM dbgcLookupRegisterSymbol(PDBGC pDbgc, const char *pszSymbol);
+PCDBGCOP dbgcOperatorLookup(PDBGC pDbgc, const char *pszExpr, bool fPreferBinary, char chPrev);
+PCDBGCCMD dbgcCommandLookup(PDBGC pDbgc, const char *pachName, size_t cchName, bool fExternal);
+PCDBGCFUNC dbgcFunctionLookup(PDBGC pDbgc, const char *pachName, size_t cchName, bool fExternal);
+
+DECLCALLBACK(int) dbgcOpRegister(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+DECLCALLBACK(int) dbgcOpAddrFlat(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+DECLCALLBACK(int) dbgcOpAddrHost(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+DECLCALLBACK(int) dbgcOpAddrPhys(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+DECLCALLBACK(int) dbgcOpAddrHostPhys(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+
+void dbgcInitCmdHlp(PDBGC pDbgc);
+
+void dbgcEventInit(PDBGC pDbgc);
+void dbgcEventTerm(PDBGC pDbgc);
+
+/** Console ASCII screen handle. */
+typedef struct DBGCSCREENINT *DBGCSCREEN;
+/** Pointer to ASCII screen handle. */
+typedef DBGCSCREEN *PDBGCSCREEN;
+
+/**
+ * ASCII screen blit callback.
+ *
+ * @returns VBox status code. Any non VINF_SUCCESS status code will abort the dumping.
+ *
+ * @param psz The string to dump
+ * @param pvUser Opaque user data.
+ */
+typedef DECLCALLBACKTYPE(int, FNDGCSCREENBLIT,(const char *psz, void *pvUser));
+/** Pointer to a FNDGCSCREENBLIT. */
+typedef FNDGCSCREENBLIT *PFNDGCSCREENBLIT;
+
+/**
+ * ASCII screen supported colors.
+ */
+typedef enum DBGCSCREENCOLOR
+{
+ /** Invalid color. */
+ DBGCSCREENCOLOR_INVALID = 0,
+ /** Default color of the terminal. */
+ DBGCSCREENCOLOR_DEFAULT,
+ /** Black. */
+ DBGCSCREENCOLOR_BLACK,
+ DBGCSCREENCOLOR_BLACK_BRIGHT,
+ /** Red. */
+ DBGCSCREENCOLOR_RED,
+ DBGCSCREENCOLOR_RED_BRIGHT,
+ /** Green. */
+ DBGCSCREENCOLOR_GREEN,
+ DBGCSCREENCOLOR_GREEN_BRIGHT,
+ /** Yellow. */
+ DBGCSCREENCOLOR_YELLOW,
+ DBGCSCREENCOLOR_YELLOW_BRIGHT,
+ /** Blue. */
+ DBGCSCREENCOLOR_BLUE,
+ DBGCSCREENCOLOR_BLUE_BRIGHT,
+ /** Magenta. */
+ DBGCSCREENCOLOR_MAGENTA,
+ DBGCSCREENCOLOR_MAGENTA_BRIGHT,
+ /** Cyan. */
+ DBGCSCREENCOLOR_CYAN,
+ DBGCSCREENCOLOR_CYAN_BRIGHT,
+ /** White. */
+ DBGCSCREENCOLOR_WHITE,
+ DBGCSCREENCOLOR_WHITE_BRIGHT
+} DBGCSCREENCOLOR;
+/** Pointer to a screen color. */
+typedef DBGCSCREENCOLOR *PDBGCSCREENCOLOR;
+
+DECLHIDDEN(int) dbgcScreenAsciiCreate(PDBGCSCREEN phScreen, uint32_t cchWidth, uint32_t cchHeight);
+DECLHIDDEN(void) dbgcScreenAsciiDestroy(DBGCSCREEN hScreen);
+DECLHIDDEN(int) dbgcScreenAsciiBlit(DBGCSCREEN hScreen, PFNDGCSCREENBLIT pfnBlit, void *pvUser, bool fAddColors);
+DECLHIDDEN(int) dbgcScreenAsciiDrawLineVertical(DBGCSCREEN hScreen, uint32_t uX, uint32_t uStartY,
+ uint32_t uEndY, char ch, DBGCSCREENCOLOR enmColor);
+DECLHIDDEN(int) dbgcScreenAsciiDrawLineHorizontal(DBGCSCREEN hScreen, uint32_t uStartX, uint32_t uEndX,
+ uint32_t uY, char ch, DBGCSCREENCOLOR enmColor);
+DECLHIDDEN(int) dbgcScreenAsciiDrawCharacter(DBGCSCREEN hScreen, uint32_t uX, uint32_t uY, char ch,
+ DBGCSCREENCOLOR enmColor);
+DECLHIDDEN(int) dbgcScreenAsciiDrawString(DBGCSCREEN hScreen, uint32_t uX, uint32_t uY, const char *pszText,
+ DBGCSCREENCOLOR enmColor);
+
+/* For tstDBGCParser: */
+int dbgcCreate(PDBGC *ppDbgc, PCDBGCIO pIo, unsigned fFlags);
+int dbgcRun(PDBGC pDbgc);
+int dbgcProcessInput(PDBGC pDbgc, bool fNoExecute);
+void dbgcDestroy(PDBGC pDbgc);
+
+DECLHIDDEN(const char *) dbgcGetEventCtx(DBGFEVENTCTX enmCtx);
+DECLHIDDEN(PCDBGCSXEVT) dbgcEventLookup(DBGFEVENTTYPE enmType);
+
+DECL_HIDDEN_CALLBACK(int) dbgcGdbStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags);
+DECL_HIDDEN_CALLBACK(int) dbgcKdStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags);
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+extern const DBGCCMD g_aDbgcCmds[];
+extern const uint32_t g_cDbgcCmds;
+extern const DBGCFUNC g_aDbgcFuncs[];
+extern const uint32_t g_cDbgcFuncs;
+extern const DBGCCMD g_aCmdsCodeView[];
+extern const uint32_t g_cCmdsCodeView;
+extern const DBGCFUNC g_aFuncsCodeView[];
+extern const uint32_t g_cFuncsCodeView;
+extern const DBGCOP g_aDbgcOps[];
+extern const uint32_t g_cDbgcOps;
+extern const DBGCSXEVT g_aDbgcSxEvents[];
+extern const uint32_t g_cDbgcSxEvents;
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** Locks the g_pExtCmdsHead and g_pExtFuncsHead lists for reading. */
+#define DBGCEXTLISTS_LOCK_RD() do { } while (0)
+/** Locks the g_pExtCmdsHead and g_pExtFuncsHead lists for writing. */
+#define DBGCEXTLISTS_LOCK_WR() do { } while (0)
+/** UnLocks the g_pExtCmdsHead and g_pExtFuncsHead lists after reading. */
+#define DBGCEXTLISTS_UNLOCK_RD() do { } while (0)
+/** UnLocks the g_pExtCmdsHead and g_pExtFuncsHead lists after writing. */
+#define DBGCEXTLISTS_UNLOCK_WR() do { } while (0)
+
+
+
+#endif /* !DEBUGGER_INCLUDED_SRC_DBGCInternal_h */
+
diff --git a/src/VBox/Debugger/DBGCIo.cpp b/src/VBox/Debugger/DBGCIo.cpp
new file mode 100644
index 00000000..14ba0fec
--- /dev/null
+++ b/src/VBox/Debugger/DBGCIo.cpp
@@ -0,0 +1,611 @@
+/* $Id: DBGCIo.cpp $ */
+/** @file
+ * DBGC - Debugger Console, I/O provider handling.
+ */
+
+/*
+ * Copyright (C) 2020-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/vmm/cfgm.h>
+#include <VBox/err.h>
+
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+
+#include <iprt/string.h>
+
+#include "DBGCIoProvInternal.h"
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Stub descriptor.
+ */
+typedef struct DBGCSTUB
+{
+ /** Name of the stub. */
+ const char *pszName;
+ /** Flag whether this is an ASCII based protocol which requires some newline handling. */
+ bool fAscii;
+ /**
+ * The runloop callback.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pIo Pointer to the I/O callback table.
+ * @param fFlags Flags for the runloop, MBZ for now.
+ */
+ DECLCALLBACKMEMBER(int, pfnRunloop, (PUVM pUVM, PCDBGCIO pIo, unsigned fFlags));
+} DBGCSTUB;
+/** Pointer to a stub descriptor. */
+typedef DBGCSTUB *PDBGCSTUB;
+/** Pointer to a const stub descriptor. */
+typedef const DBGCSTUB *PCDBGCSTUB;
+
+
+/** Pointer to the instance data of the debug console I/O. */
+typedef struct DBGCIOINT *PDBGCIOINT;
+
+
+/**
+ * A single debug console I/O service.
+ */
+typedef struct DBGCIOSVC
+{
+ /** Pointer to the owning structure. */
+ PDBGCIOINT pDbgcIo;
+ /** The user mode VM handle this service belongs to. */
+ PUVM pUVM;
+ /** The I/O provider registration record for this service. */
+ PCDBGCIOPROVREG pIoProvReg;
+ /** The I/O provider instance. */
+ DBGCIOPROV hDbgcIoProv;
+ /** The stub type. */
+ PCDBGCSTUB pStub;
+ /** The thread managing the service. */
+ RTTHREAD hThreadSvc;
+ /** Pointer to the I/O callback table currently being served. */
+ PCDBGCIO pIo;
+ /** The wrapping DBGC I/O callback table for ASCII based protocols. */
+ DBGCIO IoAscii;
+} DBGCIOSVC;
+/** Pointer to a single debug console I/O service. */
+typedef DBGCIOSVC *PDBGCIOSVC;
+/** Poitner to a const single debug console I/O service. */
+typedef const DBGCIOSVC *PCDBGCIOSVC;
+
+
+/**
+ * Debug console I/O instance data.
+ */
+typedef struct DBGCIOINT
+{
+ /** Number of configured I/O service instances. */
+ volatile uint32_t cSvcsCfg;
+ /** Number of running I/O service instances. */
+ volatile uint32_t cSvcsRunning;
+ /** Flag whether the services were asked to shut down. */
+ volatile bool fShutdown;
+ /** Array of active I/O service instances. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ DBGCIOSVC aSvc[RT_FLEXIBLE_ARRAY];
+} DBGCIOINT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Array of supported I/O providers.
+ */
+static PCDBGCIOPROVREG g_aIoProv[] =
+{
+ &g_DbgcIoProvTcp,
+ &g_DbgcIoProvUdp,
+ &g_DbgcIoProvIpc
+};
+
+
+static DECLCALLBACK(int) dbgcIoNativeStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags);
+
+/**
+ * Array of supported stubs.
+ */
+static const DBGCSTUB g_aStubs[] =
+{
+ /** pszName fAscii pfnRunloop */
+ { "Native", true, dbgcIoNativeStubRunloop },
+ { "Gdb", false, dbgcGdbStubRunloop },
+ { "Kd", false, dbgcKdStubRunloop }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Destroys all allocated data for the given dbeugger console I/O instance.
+ *
+ * @param pDbgcIo Pointer to the dbeugger console I/O instance data.
+ */
+static void dbgcIoDestroy(PDBGCIOINT pDbgcIo)
+{
+ for (uint32_t i = 0; i < pDbgcIo->cSvcsCfg; i++)
+ {
+ PDBGCIOSVC pIoSvc = &pDbgcIo->aSvc[i];
+
+ if (pIoSvc->hThreadSvc != NIL_RTTHREAD)
+ {
+ int rc = RTThreadWait(pIoSvc->hThreadSvc, RT_MS_10SEC, NULL /*prc*/);
+ AssertRC(rc);
+
+ pIoSvc->hThreadSvc = NIL_RTTHREAD;
+ pIoSvc->pIoProvReg->pfnDestroy(pIoSvc->hDbgcIoProv);
+ }
+ }
+
+ RTMemFree(pDbgcIo);
+}
+
+
+/**
+ * Returns the number of I/O services configured.
+ *
+ * @returns I/O service count.
+ * @param pCfgRoot The root of the config.
+ */
+static uint32_t dbgcIoGetSvcCount(PCFGMNODE pCfgRoot)
+{
+ uint32_t cSvcs = 0;
+ PCFGMNODE pNd = CFGMR3GetFirstChild(pCfgRoot);
+ while (pNd)
+ {
+ cSvcs++;
+ pNd = CFGMR3GetNextChild(pNd);
+ }
+
+ return cSvcs;
+}
+
+
+/**
+ * Returns a pointer to the I/O provider registration record matching the given name.
+ *
+ * @returns Pointer to the registration record or NULL if not found.
+ * @param pszName The name to look for (case insensitive matching).
+ */
+static PCDBGCIOPROVREG dbgcIoProvFindRegByName(const char *pszName)
+{
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aIoProv); i++)
+ {
+ if (!RTStrICmp(g_aIoProv[i]->pszName, pszName))
+ return g_aIoProv[i];
+ }
+ return NULL;
+}
+
+
+/**
+ * Returns a pointer to the stub record matching the given name.
+ *
+ * @returns Pointer to the stub record or NULL if not found.
+ * @param pszName The name to look for (case insensitive matching).
+ */
+static PCDBGCSTUB dbgcIoFindStubByName(const char *pszName)
+{
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aStubs); i++)
+ {
+ if (!RTStrICmp(g_aStubs[i].pszName, pszName))
+ return &g_aStubs[i];
+ }
+ return NULL;
+}
+
+
+/**
+ * Wrapper around DBGCCreate() to get it working as a callback.
+ */
+static DECLCALLBACK(int) dbgcIoNativeStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags)
+{
+ return DBGCCreate(pUVM, pIo, fFlags);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoAsciiDestroy(PCDBGCIO pIo)
+{
+ PDBGCIOSVC pIoSvc = RT_FROM_MEMBER(pIo, DBGCIOSVC, IoAscii);
+ pIoSvc->pIo->pfnDestroy(pIoSvc->pIo);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnInput}
+ */
+static DECLCALLBACK(bool) dbgcIoAsciiInput(PCDBGCIO pIo, uint32_t cMillies)
+{
+ PDBGCIOSVC pIoSvc = RT_FROM_MEMBER(pIo, DBGCIOSVC, IoAscii);
+ return pIoSvc->pIo->pfnInput(pIoSvc->pIo, cMillies);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnRead}
+ */
+static DECLCALLBACK(int) dbgcIoAsciiRead(PCDBGCIO pIo, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PDBGCIOSVC pIoSvc = RT_FROM_MEMBER(pIo, DBGCIOSVC, IoAscii);
+ return pIoSvc->pIo->pfnRead(pIoSvc->pIo, pvBuf, cbBuf, pcbRead);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnWrite}
+ */
+static DECLCALLBACK(int) dbgcIoAsciiWrite(PCDBGCIO pIo, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ PDBGCIOSVC pIoSvc = RT_FROM_MEMBER(pIo, DBGCIOSVC, IoAscii);
+
+ /*
+ * convert '\n' to '\r\n' while writing.
+ */
+ int rc = 0;
+ size_t cbLeft = cbBuf;
+ while (cbLeft)
+ {
+ size_t cb = cbLeft;
+ /* write newlines */
+ if (*(const char *)pvBuf == '\n')
+ {
+ rc = pIoSvc->pIo->pfnWrite(pIoSvc->pIo, "\r\n", 2, NULL);
+ cb = 1;
+ }
+ /* write till next newline */
+ else
+ {
+ const char *pszNL = (const char *)memchr(pvBuf, '\n', cbLeft);
+ if (pszNL)
+ cb = (uintptr_t)pszNL - (uintptr_t)pvBuf;
+ rc = pIoSvc->pIo->pfnWrite(pIoSvc->pIo, pvBuf, cb, NULL);
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* advance */
+ cbLeft -= cb;
+ pvBuf = (const char *)pvBuf + cb;
+ }
+
+ /*
+ * Set returned value and return.
+ */
+ if (pcbWritten)
+ *pcbWritten = cbBuf - cbLeft;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnSetReady}
+ */
+static DECLCALLBACK(void) dbgcIoAsciiSetReady(PCDBGCIO pIo, bool fReady)
+{
+ PDBGCIOSVC pIoSvc = RT_FROM_MEMBER(pIo, DBGCIOSVC, IoAscii);
+ return pIoSvc->pIo->pfnSetReady(pIoSvc->pIo, fReady);
+}
+
+
+/**
+ * The I/O thread handling the service.
+ */
+static DECLCALLBACK(int) dbgcIoSvcThread(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+
+ int rc = VINF_SUCCESS;
+ PDBGCIOSVC pIoSvc = (PDBGCIOSVC)pvUser;
+ PDBGCIOINT pDbgcIo = pIoSvc->pDbgcIo;
+ PCDBGCIOPROVREG pIoProvReg = pIoSvc->pIoProvReg;
+
+ while (!ASMAtomicReadBool(&pDbgcIo->fShutdown))
+ {
+ /* Wait until someone connects. */
+ rc = pIoProvReg->pfnWaitForConnect(pIoSvc->hDbgcIoProv, RT_INDEFINITE_WAIT, &pIoSvc->pIo);
+ if (RT_SUCCESS(rc))
+ {
+ PCDBGCIO pIo = pIoSvc->pIo;
+
+ if (pIoSvc->pStub->fAscii)
+ {
+ pIoSvc->IoAscii.pfnDestroy = dbgcIoAsciiDestroy;
+ pIoSvc->IoAscii.pfnInput = dbgcIoAsciiInput;
+ pIoSvc->IoAscii.pfnRead = dbgcIoAsciiRead;
+ pIoSvc->IoAscii.pfnWrite = dbgcIoAsciiWrite;
+ pIoSvc->IoAscii.pfnSetReady = dbgcIoAsciiSetReady;
+ pIo = &pIoSvc->IoAscii;
+ }
+
+ /* call the runloop for the connection. */
+ pIoSvc->pStub->pfnRunloop(pIoSvc->pUVM, pIo, 0 /*fFlags*/);
+
+ pIo->pfnDestroy(pIo);
+ }
+ else if ( rc != VERR_TIMEOUT
+ && rc != VERR_INTERRUPTED)
+ break;
+ }
+
+ if (!ASMAtomicDecU32(&pDbgcIo->cSvcsRunning))
+ dbgcIoDestroy(pDbgcIo);
+
+ return rc;
+}
+
+
+static int dbgcIoSvcInitWorker(PUVM pUVM, PDBGCIOSVC pIoSvc, PCDBGCIOPROVREG pIoProvReg,
+ PCDBGCSTUB pStub, PCFGMNODE pCfg, const char *pszName,
+ bool fIgnoreNetAddrInUse)
+{
+ pIoSvc->pUVM = pUVM;
+ pIoSvc->pIoProvReg = pIoProvReg;
+ pIoSvc->pStub = pStub;
+
+ /* Create the provider instance and spawn the dedicated thread handling that service. */
+ int rc = pIoProvReg->pfnCreate(&pIoSvc->hDbgcIoProv, pCfg);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadCreateF(&pIoSvc->hThreadSvc, dbgcIoSvcThread, pIoSvc, 0 /*cbStack*/,
+ RTTHREADTYPE_DEBUGGER, RTTHREADFLAGS_WAITABLE, "DbgcThrd-%s", pszName);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicIncU32(&pIoSvc->pDbgcIo->cSvcsRunning);
+ return VINF_SUCCESS;
+ }
+ else
+ rc = VMR3SetError(pUVM, rc, RT_SRC_POS,
+ "Configuration error: Creating an instance of the service \"%s\" failed",
+ pszName);
+
+ pIoProvReg->pfnDestroy(pIoSvc->hDbgcIoProv);
+ }
+ else if ( rc != VERR_NET_ADDRESS_IN_USE
+ || !fIgnoreNetAddrInUse)
+ rc = VMR3SetError(pUVM, rc, RT_SRC_POS,
+ "Configuration error: Creating an instance of the I/O provider \"%s\" failed",
+ pIoProvReg->pszName);
+
+ return rc;
+}
+
+
+/**
+ * Tries to initialize the given I/O service from the given config.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pIoSvc The I/O service instance to initialize.
+ * @param pCfg The config for the instance.
+ */
+static int dbgcIoSvcInit(PUVM pUVM, PDBGCIOSVC pIoSvc, PCFGMNODE pCfg)
+{
+ char szName[32 + 1]; RT_ZERO(szName);
+ int rc = CFGMR3GetName(pCfg, &szName[0], sizeof(szName));
+ if (RT_SUCCESS(rc))
+ {
+ char szIoProvName[32 + 1]; RT_ZERO(szIoProvName);
+ rc = CFGMR3QueryString(pCfg, "Provider", &szIoProvName[0], sizeof(szIoProvName));
+ if (RT_SUCCESS(rc))
+ {
+ char szStub[32 + 1]; RT_ZERO(szStub);
+ rc = CFGMR3QueryString(pCfg, "StubType", &szStub[0], sizeof(szStub));
+ if (RT_SUCCESS(rc))
+ {
+ PCDBGCIOPROVREG pIoProvReg = dbgcIoProvFindRegByName(szIoProvName);
+ if (pIoProvReg)
+ {
+ PCDBGCSTUB pStub = dbgcIoFindStubByName(szStub);
+ if (pStub)
+ rc = dbgcIoSvcInitWorker(pUVM, pIoSvc, pIoProvReg, pStub, pCfg, szName,
+ false /*fIgnoreNetAddrInUse*/);
+ else
+ rc = VMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, "Configuration error: The stub type \"%s\" could not be found",
+ szStub);
+ }
+ else
+ rc = VMR3SetError(pUVM, VERR_NOT_FOUND, RT_SRC_POS, "Configuration error: The provider \"%s\" could not be found",
+ szIoProvName);
+ }
+ else
+ rc = VM_SET_ERROR_U(pUVM, rc, "Configuration error: Querying \"StubType\" failed");
+ }
+ else
+ rc = VM_SET_ERROR_U(pUVM, rc, "Configuration error: Querying \"Provider\" failed");
+ }
+ else
+ rc = VM_SET_ERROR_U(pUVM, rc, "Configuration error: Querying service identifier failed (maybe too long)");
+
+ return rc;
+}
+
+
+/**
+ * Creates the DBGC I/O services from the legacy TCP config.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pKey The config key.
+ * @param ppvData Where to store the I/o instance data on success.
+ */
+static int dbgcIoCreateLegacyTcp(PUVM pUVM, PCFGMNODE pKey, void **ppvData)
+{
+ bool fEnabled;
+ int rc = CFGMR3QueryBoolDef(pKey, "Enabled", &fEnabled,
+#if defined(VBOX_WITH_DEBUGGER) && defined(VBOX_WITH_DEBUGGER_TCP_BY_DEFAULT)
+ true
+#else
+ false
+#endif
+ );
+ if (RT_FAILURE(rc))
+ return VM_SET_ERROR_U(pUVM, rc, "Configuration error: Failed querying \"DBGC/Enabled\"");
+
+ if (!fEnabled)
+ {
+ LogFlow(("DBGCTcpCreate: returns VINF_SUCCESS (Disabled)\n"));
+ return VINF_SUCCESS;
+ }
+
+ PDBGCIOINT pDbgcIo = (PDBGCIOINT)RTMemAllocZ(RT_UOFFSETOF_DYN(DBGCIOINT, aSvc[1]));
+ if (RT_LIKELY(pDbgcIo))
+ {
+ pDbgcIo->aSvc[0].pDbgcIo = pDbgcIo;
+ pDbgcIo->cSvcsCfg = 1;
+ pDbgcIo->cSvcsRunning = 1;
+ rc = dbgcIoSvcInitWorker(pUVM, &pDbgcIo->aSvc[0], &g_DbgcIoProvTcp, &g_aStubs[0], pKey, "TCP",
+ true /*fIgnoreNetAddrInUse*/);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvData = pDbgcIo;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pDbgcIo);
+ if (rc == VERR_NET_ADDRESS_IN_USE)
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ rc = VM_SET_ERROR_U(pUVM, rc, "Cannot start TCP-based debugging console service");
+ return rc;
+}
+
+
+/**
+ * Sets up debugger I/O based on the VM config.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param ppvData Where to store a pointer to the instance data.
+ */
+DBGDECL(int) DBGCIoCreate(PUVM pUVM, void **ppvData)
+{
+ /*
+ * Check what the configuration says.
+ */
+ PCFGMNODE pKey = CFGMR3GetChild(CFGMR3GetRootU(pUVM), "DBGC");
+ uint32_t cSvcs = dbgcIoGetSvcCount(pKey);
+ int rc = VINF_SUCCESS;
+
+ /* If no services are configured try the legacy config supporting TCP only. */
+ if (cSvcs)
+ {
+ PDBGCIOINT pDbgcIo = (PDBGCIOINT)RTMemAllocZ(RT_UOFFSETOF_DYN(DBGCIOINT, aSvc[cSvcs]));
+ if (RT_LIKELY(pDbgcIo))
+ {
+ pDbgcIo->cSvcsCfg = 0;
+ pDbgcIo->cSvcsRunning = 1;
+ pDbgcIo->fShutdown = false;
+
+ for (uint32_t i = 0; i < cSvcs; i++)
+ pDbgcIo->aSvc[i].hThreadSvc = NIL_RTTHREAD;
+
+ PCFGMNODE pSvcCfg = CFGMR3GetFirstChild(pKey);
+ for (uint32_t i = 0; i < cSvcs && RT_SUCCESS(rc); i++)
+ {
+ pDbgcIo->aSvc[i].pDbgcIo = pDbgcIo;
+
+ rc = dbgcIoSvcInit(pUVM, &pDbgcIo->aSvc[i], pSvcCfg);
+ if (RT_SUCCESS(rc))
+ pDbgcIo->cSvcsCfg++;
+ else
+ rc = VM_SET_ERROR_U(pUVM, rc, "Failed to initialize the debugger I/O service");
+
+ pSvcCfg = CFGMR3GetNextChild(pSvcCfg);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppvData = pDbgcIo;
+ else
+ {
+ if (!ASMAtomicDecU32(&pDbgcIo->cSvcsRunning))
+ dbgcIoDestroy(pDbgcIo);
+ }
+ }
+ else
+ rc = VM_SET_ERROR_U(pUVM, VERR_NO_MEMORY, "Failed to allocate memory for the debugger I/O service");
+ }
+ else
+ rc = dbgcIoCreateLegacyTcp(pUVM, pKey, ppvData);
+
+ return rc;
+}
+
+
+/**
+ * Terminates any running debugger services.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pvData The data returned by DBGCIoCreate.
+ */
+DBGDECL(int) DBGCIoTerminate(PUVM pUVM, void *pvData)
+{
+ RT_NOREF(pUVM);
+ PDBGCIOINT pDbgcIo = (PDBGCIOINT)pvData;
+
+ if (pDbgcIo)
+ {
+ ASMAtomicXchgBool(&pDbgcIo->fShutdown, true);
+
+ for (uint32_t i = 0; i < pDbgcIo->cSvcsCfg; i++)
+ {
+ PDBGCIOSVC pIoSvc = &pDbgcIo->aSvc[i];
+
+ if (pIoSvc->hThreadSvc != NIL_RTTHREAD)
+ pIoSvc->pIoProvReg->pfnWaitInterrupt(pIoSvc->hDbgcIoProv);
+ }
+
+ if (!ASMAtomicDecU32(&pDbgcIo->cSvcsRunning))
+ dbgcIoDestroy(pDbgcIo);
+ }
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Debugger/DBGCIoProvInternal.h b/src/VBox/Debugger/DBGCIoProvInternal.h
new file mode 100644
index 00000000..61df8685
--- /dev/null
+++ b/src/VBox/Debugger/DBGCIoProvInternal.h
@@ -0,0 +1,115 @@
+/* $Id: DBGCIoProvInternal.h $ */
+/** @file
+ * DBGC - Debugger Console, Internal I/O provider header file.
+ */
+
+/*
+ * Copyright (C) 2020-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_DBGCIoProvInternal_h
+#define DEBUGGER_INCLUDED_SRC_DBGCIoProvInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/err.h>
+#include <VBox/vmm/cfgm.h>
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+
+/** An Opaque I/O provider handle. */
+typedef struct DBGCIOPROVINT *DBGCIOPROV;
+/** Pointer to an opaque I/O provider handle. */
+typedef DBGCIOPROV *PDBGCIOPROV;
+
+
+/**
+ * I/O provider registration record.
+ */
+typedef struct DBGCIOPROVREG
+{
+ /** Unique name for the I/O provider. */
+ const char *pszName;
+ /** I/O provider description. */
+ const char *pszDesc;
+
+ /**
+ * Creates an I/O provider instance from the given config.
+ *
+ * @returns VBox status code.
+ * @param phDbgcIoProv Where to store the handle to the I/O provider instance on success.
+ * @param pCfg The config to use.
+ */
+ DECLCALLBACKMEMBER(int, pfnCreate, (PDBGCIOPROV phDbgcIoProv, PCFGMNODE pCfg));
+
+ /**
+ * Destroys the given I/O provider instance.
+ *
+ * @param hDbgcIoProv The I/O provider instance handle to destroy.
+ */
+ DECLCALLBACKMEMBER(void, pfnDestroy, (DBGCIOPROV hDbgcIoProv));
+
+ /**
+ * Waits for someone to connect to the provider instance.
+ *
+ * @returns VBox status code.
+ * @retval VERR_TIMEOUT if the waiting time was exceeded without anyone connecting.
+ * @retval VERR_INTERRUPTED if the waiting was interrupted by DBGCIOPROVREG::pfnWaitInterrupt.
+ * @param hDbgcIoProv The I/O provider instance handle.
+ * @param cMsTimeout Number of milliseconds to wait, use RT_INDEFINITE_WAIT to wait indefinitely.
+ * @param ppDbgcIo Where to return the I/O connection callback table upon a succesful return.
+ */
+ DECLCALLBACKMEMBER(int, pfnWaitForConnect, (DBGCIOPROV hDbgcIoProv, RTMSINTERVAL cMsTimeout, PCDBGCIO *ppDbgcIo));
+
+ /**
+ * Interrupts the thread waiting in DBGCIOPROVREG::pfnWaitForConnect.
+ *
+ * @returns VBox status code.
+ * @param hDbgcIoProv The I/O provider instance handle.
+ */
+ DECLCALLBACKMEMBER(int, pfnWaitInterrupt, (DBGCIOPROV hDbgcIoProv));
+
+} DBGCIOPROVREG;
+/** Pointer to an I/O provider registration record. */
+typedef DBGCIOPROVREG *PDBGCIOPROVREG;
+/** Pointer toa const I/O provider registration record. */
+typedef const DBGCIOPROVREG *PCDBGCIOPROVREG;
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+extern const DBGCIOPROVREG g_DbgcIoProvTcp;
+extern const DBGCIOPROVREG g_DbgcIoProvUdp;
+extern const DBGCIOPROVREG g_DbgcIoProvIpc;
+
+
+#endif /* !DEBUGGER_INCLUDED_SRC_DBGCIoProvInternal_h */
+
diff --git a/src/VBox/Debugger/DBGCIoProvIpc.cpp b/src/VBox/Debugger/DBGCIoProvIpc.cpp
new file mode 100644
index 00000000..707a9f5f
--- /dev/null
+++ b/src/VBox/Debugger/DBGCIoProvIpc.cpp
@@ -0,0 +1,244 @@
+/* $Id: DBGCIoProvIpc.cpp $ */
+/** @file
+ * DBGC - Debugger Console, IPC I/O provider.
+ */
+
+/*
+ * Copyright (C) 2020-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/localipc.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+
+#include "DBGCIoProvInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debug console IPC connection data.
+ */
+typedef struct DBGCIPCCON
+{
+ /** The I/O callback table for the console. */
+ DBGCIO Io;
+ /** The socket of the connection. */
+ RTLOCALIPCSESSION hSession;
+ /** Connection status. */
+ bool fAlive;
+} DBGCIPCCON;
+/** Pointer to the instance data of the console IPC backend. */
+typedef DBGCIPCCON *PDBGCIPCCON;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{DBGCIO,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoProvIpcIoDestroy(PCDBGCIO pIo)
+{
+ PDBGCIPCCON pIpcCon = RT_FROM_MEMBER(pIo, DBGCIPCCON, Io);
+ RTLocalIpcSessionClose(pIpcCon->hSession);
+ pIpcCon->fAlive =false;
+ RTMemFree(pIpcCon);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnInput}
+ */
+static DECLCALLBACK(bool) dbgcIoProvIpcIoInput(PCDBGCIO pIo, uint32_t cMillies)
+{
+ PDBGCIPCCON pIpcCon = RT_FROM_MEMBER(pIo, DBGCIPCCON, Io);
+ if (!pIpcCon->fAlive)
+ return false;
+ int rc = RTLocalIpcSessionWaitForData(pIpcCon->hSession, cMillies);
+ if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
+ pIpcCon->fAlive = false;
+ return rc != VERR_TIMEOUT;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnRead}
+ */
+static DECLCALLBACK(int) dbgcIoProvIpcIoRead(PCDBGCIO pIo, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PDBGCIPCCON pIpcCon = RT_FROM_MEMBER(pIo, DBGCIPCCON, Io);
+ if (!pIpcCon->fAlive)
+ return VERR_INVALID_HANDLE;
+ int rc = RTLocalIpcSessionRead(pIpcCon->hSession, pvBuf, cbBuf, pcbRead);
+ if (RT_SUCCESS(rc) && pcbRead != NULL && *pcbRead == 0)
+ rc = VERR_NET_SHUTDOWN;
+ if (RT_FAILURE(rc))
+ pIpcCon->fAlive = false;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnWrite}
+ */
+static DECLCALLBACK(int) dbgcIoProvIpcIoWrite(PCDBGCIO pIo, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ PDBGCIPCCON pIpcCon = RT_FROM_MEMBER(pIo, DBGCIPCCON, Io);
+ if (!pIpcCon->fAlive)
+ return VERR_INVALID_HANDLE;
+
+ int rc = RTLocalIpcSessionWrite(pIpcCon->hSession, pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ pIpcCon->fAlive = false;
+
+ if (pcbWritten)
+ *pcbWritten = cbBuf;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnSetReady}
+ */
+static DECLCALLBACK(void) dbgcIoProvIpcIoSetReady(PCDBGCIO pIo, bool fReady)
+{
+ /* stub */
+ NOREF(pIo);
+ NOREF(fReady);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnCreate}
+ */
+static DECLCALLBACK(int) dbgcIoProvIpcCreate(PDBGCIOPROV phDbgcIoProv, PCFGMNODE pCfg)
+{
+ /*
+ * Get the address configuration.
+ */
+ char szAddress[512];
+ int rc = CFGMR3QueryStringDef(pCfg, "Address", szAddress, sizeof(szAddress), "");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Configuration error: Failed querying \"Address\" -> rc=%Rc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Create the server.
+ */
+ RTLOCALIPCSERVER hIpcSrv;
+ rc = RTLocalIpcServerCreate(&hIpcSrv, szAddress, RTLOCALIPC_FLAGS_NATIVE_NAME);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("dbgcIoProvIpcCreate: Created server on \"%s\"\n", szAddress));
+ *phDbgcIoProv = (DBGCIOPROV)hIpcSrv;
+ return rc;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoProvIpcDestroy(DBGCIOPROV hDbgcIoProv)
+{
+ int rc = RTLocalIpcServerDestroy((RTLOCALIPCSERVER)hDbgcIoProv);
+ AssertRC(rc);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnWaitForConnect}
+ */
+static DECLCALLBACK(int) dbgcIoProvIpcWaitForConnect(DBGCIOPROV hDbgcIoProv, RTMSINTERVAL cMsTimeout, PCDBGCIO *ppDbgcIo)
+{
+ RTLOCALIPCSERVER hIpcSrv = (RTLOCALIPCSERVER)hDbgcIoProv;
+ RT_NOREF(cMsTimeout);
+
+ RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION;
+ int rc = RTLocalIpcServerListen(hIpcSrv, &hSession);
+ if (RT_SUCCESS(rc))
+ {
+ PDBGCIPCCON pIpcCon = (PDBGCIPCCON)RTMemAllocZ(sizeof(*pIpcCon));
+ if (RT_LIKELY(pIpcCon))
+ {
+ pIpcCon->Io.pfnDestroy = dbgcIoProvIpcIoDestroy;
+ pIpcCon->Io.pfnInput = dbgcIoProvIpcIoInput;
+ pIpcCon->Io.pfnRead = dbgcIoProvIpcIoRead;
+ pIpcCon->Io.pfnWrite = dbgcIoProvIpcIoWrite;
+ pIpcCon->Io.pfnPktBegin = NULL;
+ pIpcCon->Io.pfnPktEnd = NULL;
+ pIpcCon->Io.pfnSetReady = dbgcIoProvIpcIoSetReady;
+ pIpcCon->hSession = hSession;
+ pIpcCon->fAlive = true;
+ *ppDbgcIo = &pIpcCon->Io;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnWaitInterrupt}
+ */
+static DECLCALLBACK(int) dbgcIoProvIpcWaitInterrupt(DBGCIOPROV hDbgcIoProv)
+{
+ return RTLocalIpcServerCancel((RTLOCALIPCSERVER)hDbgcIoProv);
+}
+
+
+/**
+ * TCP I/O provider registration record.
+ */
+const DBGCIOPROVREG g_DbgcIoProvIpc =
+{
+ /** pszName */
+ "ipc",
+ /** pszDesc */
+ "IPC I/O provider.",
+ /** pfnCreate */
+ dbgcIoProvIpcCreate,
+ /** pfnDestroy */
+ dbgcIoProvIpcDestroy,
+ /** pfnWaitForConnect */
+ dbgcIoProvIpcWaitForConnect,
+ /** pfnWaitInterrupt */
+ dbgcIoProvIpcWaitInterrupt
+};
+
diff --git a/src/VBox/Debugger/DBGCIoProvTcp.cpp b/src/VBox/Debugger/DBGCIoProvTcp.cpp
new file mode 100644
index 00000000..6676486b
--- /dev/null
+++ b/src/VBox/Debugger/DBGCIoProvTcp.cpp
@@ -0,0 +1,259 @@
+/* $Id: DBGCIoProvTcp.cpp $ */
+/** @file
+ * DBGC - Debugger Console, TCP I/O provider.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/mem.h>
+#include <iprt/tcp.h>
+#include <iprt/assert.h>
+
+#include "DBGCIoProvInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debug console TCP connection data.
+ */
+typedef struct DBGCTCPCON
+{
+ /** The I/O callback table for the console. */
+ DBGCIO Io;
+ /** The socket of the connection. */
+ RTSOCKET hSock;
+ /** Connection status. */
+ bool fAlive;
+} DBGCTCPCON;
+/** Pointer to the instance data of the console TCP backend. */
+typedef DBGCTCPCON *PDBGCTCPCON;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{DBGCIO,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoProvTcpIoDestroy(PCDBGCIO pIo)
+{
+ PDBGCTCPCON pTcpCon = RT_FROM_MEMBER(pIo, DBGCTCPCON, Io);
+ RTSocketRelease(pTcpCon->hSock);
+ pTcpCon->fAlive =false;
+ RTMemFree(pTcpCon);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnInput}
+ */
+static DECLCALLBACK(bool) dbgcIoProvTcpIoInput(PCDBGCIO pIo, uint32_t cMillies)
+{
+ PDBGCTCPCON pTcpCon = RT_FROM_MEMBER(pIo, DBGCTCPCON, Io);
+ if (!pTcpCon->fAlive)
+ return false;
+ int rc = RTTcpSelectOne(pTcpCon->hSock, cMillies);
+ if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
+ pTcpCon->fAlive = false;
+ return rc != VERR_TIMEOUT;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnRead}
+ */
+static DECLCALLBACK(int) dbgcIoProvTcpIoRead(PCDBGCIO pIo, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PDBGCTCPCON pTcpCon = RT_FROM_MEMBER(pIo, DBGCTCPCON, Io);
+ if (!pTcpCon->fAlive)
+ return VERR_INVALID_HANDLE;
+ int rc = RTTcpRead(pTcpCon->hSock, pvBuf, cbBuf, pcbRead);
+ if (RT_SUCCESS(rc) && pcbRead != NULL && *pcbRead == 0)
+ rc = VERR_NET_SHUTDOWN;
+ if (RT_FAILURE(rc))
+ pTcpCon->fAlive = false;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnWrite}
+ */
+static DECLCALLBACK(int) dbgcIoProvTcpIoWrite(PCDBGCIO pIo, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ PDBGCTCPCON pTcpCon = RT_FROM_MEMBER(pIo, DBGCTCPCON, Io);
+ if (!pTcpCon->fAlive)
+ return VERR_INVALID_HANDLE;
+
+ int rc = RTTcpWrite(pTcpCon->hSock, pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ pTcpCon->fAlive = false;
+
+ if (pcbWritten)
+ *pcbWritten = cbBuf;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnSetReady}
+ */
+static DECLCALLBACK(void) dbgcIoProvTcpIoSetReady(PCDBGCIO pIo, bool fReady)
+{
+ /* stub */
+ NOREF(pIo);
+ NOREF(fReady);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnCreate}
+ */
+static DECLCALLBACK(int) dbgcIoProvTcpCreate(PDBGCIOPROV phDbgcIoProv, PCFGMNODE pCfg)
+{
+ /*
+ * Get the port configuration.
+ */
+ uint32_t u32Port;
+ int rc = CFGMR3QueryU32Def(pCfg, "Port", &u32Port, 5000);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Configuration error: Failed querying \"Port\" -> rc=%Rc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Get the address configuration.
+ */
+ char szAddress[512];
+ rc = CFGMR3QueryStringDef(pCfg, "Address", szAddress, sizeof(szAddress), "");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Configuration error: Failed querying \"Address\" -> rc=%Rc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Create the server.
+ */
+ PRTTCPSERVER pServer;
+ rc = RTTcpServerCreateEx(szAddress, u32Port, &pServer);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("dbgcIoProvTcpCreate: Created server on port %d %s\n", u32Port, szAddress));
+ *phDbgcIoProv = (DBGCIOPROV)pServer;
+ return rc;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoProvTcpDestroy(DBGCIOPROV hDbgcIoProv)
+{
+ int rc = RTTcpServerDestroy((PRTTCPSERVER)hDbgcIoProv);
+ AssertRC(rc);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnWaitForConnect}
+ */
+static DECLCALLBACK(int) dbgcIoProvTcpWaitForConnect(DBGCIOPROV hDbgcIoProv, RTMSINTERVAL cMsTimeout, PCDBGCIO *ppDbgcIo)
+{
+ PRTTCPSERVER pTcpSrv = (PRTTCPSERVER)hDbgcIoProv;
+ RT_NOREF(cMsTimeout);
+
+ RTSOCKET hSockCon = NIL_RTSOCKET;
+ int rc = RTTcpServerListen2(pTcpSrv, &hSockCon);
+ if (RT_SUCCESS(rc))
+ {
+ PDBGCTCPCON pTcpCon = (PDBGCTCPCON)RTMemAllocZ(sizeof(*pTcpCon));
+ if (RT_LIKELY(pTcpCon))
+ {
+ pTcpCon->Io.pfnDestroy = dbgcIoProvTcpIoDestroy;
+ pTcpCon->Io.pfnInput = dbgcIoProvTcpIoInput;
+ pTcpCon->Io.pfnRead = dbgcIoProvTcpIoRead;
+ pTcpCon->Io.pfnWrite = dbgcIoProvTcpIoWrite;
+ pTcpCon->Io.pfnPktBegin = NULL;
+ pTcpCon->Io.pfnPktEnd = NULL;
+ pTcpCon->Io.pfnSetReady = dbgcIoProvTcpIoSetReady;
+ pTcpCon->hSock = hSockCon;
+ pTcpCon->fAlive = true;
+ *ppDbgcIo = &pTcpCon->Io;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnWaitInterrupt}
+ */
+static DECLCALLBACK(int) dbgcIoProvTcpWaitInterrupt(DBGCIOPROV hDbgcIoProv)
+{
+ PRTTCPSERVER pTcpSrv = (PRTTCPSERVER)hDbgcIoProv;
+
+ RT_NOREF(pTcpSrv);
+ /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * TCP I/O provider registration record.
+ */
+const DBGCIOPROVREG g_DbgcIoProvTcp =
+{
+ /** pszName */
+ "tcp",
+ /** pszDesc */
+ "TCP I/O provider.",
+ /** pfnCreate */
+ dbgcIoProvTcpCreate,
+ /** pfnDestroy */
+ dbgcIoProvTcpDestroy,
+ /** pfnWaitForConnect */
+ dbgcIoProvTcpWaitForConnect,
+ /** pfnWaitInterrupt */
+ dbgcIoProvTcpWaitInterrupt
+};
+
diff --git a/src/VBox/Debugger/DBGCIoProvUdp.cpp b/src/VBox/Debugger/DBGCIoProvUdp.cpp
new file mode 100644
index 00000000..8bc1e126
--- /dev/null
+++ b/src/VBox/Debugger/DBGCIoProvUdp.cpp
@@ -0,0 +1,259 @@
+/* $Id: DBGCIoProvUdp.cpp $ */
+/** @file
+ * DBGC - Debugger Console, UDP I/O provider.
+ */
+
+/*
+ * Copyright (C) 2022-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/mem.h>
+#include <iprt/udp.h>
+#include <iprt/assert.h>
+
+#include "DBGCIoProvInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debug console UDP connection data.
+ */
+typedef struct DBGCUDPSRV
+{
+ /** The I/O callback table for the console. */
+ DBGCIO Io;
+ /** The socket of the connection. */
+ RTSOCKET hSock;
+ /** The address of the peer. */
+ RTNETADDR NetAddrPeer;
+ /** Flag whether the peer address was set. */
+ bool fPeerSet;
+ /** Connection status. */
+ bool fAlive;
+} DBGCUDPSRV;
+/** Pointer to the instance data of the console UDP backend. */
+typedef DBGCUDPSRV *PDBGCUDPSRV;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{DBGCIO,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoProvUdpIoDestroy(PCDBGCIO pIo)
+{
+ RT_NOREF(pIo);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnInput}
+ */
+static DECLCALLBACK(bool) dbgcIoProvUdpIoInput(PCDBGCIO pIo, uint32_t cMillies)
+{
+ PDBGCUDPSRV pUdpSrv = RT_FROM_MEMBER(pIo, DBGCUDPSRV, Io);
+ if (!pUdpSrv->fAlive)
+ return false;
+ int rc = RTSocketSelectOne(pUdpSrv->hSock, cMillies);
+ if (RT_FAILURE(rc) && rc != VERR_TIMEOUT)
+ pUdpSrv->fAlive = false;
+ return rc != VERR_TIMEOUT;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnRead}
+ */
+static DECLCALLBACK(int) dbgcIoProvUdpIoRead(PCDBGCIO pIo, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PDBGCUDPSRV pUdpSrv = RT_FROM_MEMBER(pIo, DBGCUDPSRV, Io);
+ if (!pUdpSrv->fAlive)
+ return VERR_INVALID_HANDLE;
+ int rc = RTSocketReadFrom(pUdpSrv->hSock, pvBuf, cbBuf, pcbRead, &pUdpSrv->NetAddrPeer);
+ if (RT_SUCCESS(rc) && pcbRead != NULL && *pcbRead == 0)
+ rc = VERR_NET_SHUTDOWN;
+ if (RT_FAILURE(rc))
+ pUdpSrv->fAlive = false;
+ pUdpSrv->fPeerSet = true;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnWrite}
+ */
+static DECLCALLBACK(int) dbgcIoProvUdpIoWrite(PCDBGCIO pIo, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ PDBGCUDPSRV pUdpSrv = RT_FROM_MEMBER(pIo, DBGCUDPSRV, Io);
+ if ( !pUdpSrv->fAlive
+ || !pUdpSrv->fPeerSet)
+ return VERR_INVALID_HANDLE;
+
+ int rc = RTSocketWriteTo(pUdpSrv->hSock, pvBuf, cbBuf, &pUdpSrv->NetAddrPeer);
+ if (RT_FAILURE(rc))
+ pUdpSrv->fAlive = false;
+
+ if (pcbWritten)
+ *pcbWritten = cbBuf;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIO,pfnSetReady}
+ */
+static DECLCALLBACK(void) dbgcIoProvUdpIoSetReady(PCDBGCIO pIo, bool fReady)
+{
+ /* stub */
+ NOREF(pIo);
+ NOREF(fReady);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnCreate}
+ */
+static DECLCALLBACK(int) dbgcIoProvUdpCreate(PDBGCIOPROV phDbgcIoProv, PCFGMNODE pCfg)
+{
+ /*
+ * Get the port configuration.
+ */
+ uint32_t u32Port;
+ int rc = CFGMR3QueryU32Def(pCfg, "Port", &u32Port, 5000);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Configuration error: Failed querying \"Port\" -> rc=%Rc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Get the address configuration.
+ */
+ char szAddress[512];
+ rc = CFGMR3QueryStringDef(pCfg, "Address", szAddress, sizeof(szAddress), "");
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("Configuration error: Failed querying \"Address\" -> rc=%Rc\n", rc));
+ return rc;
+ }
+
+ PDBGCUDPSRV pUdpSrv = (PDBGCUDPSRV)RTMemAllocZ(sizeof(*pUdpSrv));
+ if (RT_LIKELY(pUdpSrv))
+ {
+ pUdpSrv->Io.pfnDestroy = dbgcIoProvUdpIoDestroy;
+ pUdpSrv->Io.pfnInput = dbgcIoProvUdpIoInput;
+ pUdpSrv->Io.pfnRead = dbgcIoProvUdpIoRead;
+ pUdpSrv->Io.pfnWrite = dbgcIoProvUdpIoWrite;
+ pUdpSrv->Io.pfnPktBegin = NULL;
+ pUdpSrv->Io.pfnPktEnd = NULL;
+ pUdpSrv->Io.pfnSetReady = dbgcIoProvUdpIoSetReady;
+ pUdpSrv->fPeerSet = false;
+ pUdpSrv->fAlive = true;
+
+ /*
+ * Create the server.
+ */
+ rc = RTUdpCreateServerSocket(szAddress, u32Port, &pUdpSrv->hSock);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("dbgcIoProvUdpCreate: Created server on port %d %s\n", u32Port, szAddress));
+ *phDbgcIoProv = (DBGCIOPROV)pUdpSrv;
+ return rc;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnDestroy}
+ */
+static DECLCALLBACK(void) dbgcIoProvUdpDestroy(DBGCIOPROV hDbgcIoProv)
+{
+ PDBGCUDPSRV pUdpSrv = (PDBGCUDPSRV)hDbgcIoProv;
+
+ RTSocketRelease(pUdpSrv->hSock);
+ pUdpSrv->fAlive = false;
+ RTMemFree(pUdpSrv);
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnWaitForConnect}
+ */
+static DECLCALLBACK(int) dbgcIoProvUdpWaitForConnect(DBGCIOPROV hDbgcIoProv, RTMSINTERVAL cMsTimeout, PCDBGCIO *ppDbgcIo)
+{
+ PDBGCUDPSRV pUdpSrv = (PDBGCUDPSRV)hDbgcIoProv;
+
+ /* Wait for the first datagram. */
+ int rc = RTSocketSelectOne(pUdpSrv->hSock, cMsTimeout);
+ if (RT_SUCCESS(rc))
+ *ppDbgcIo = &pUdpSrv->Io;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{DBGCIOPROVREG,pfnWaitInterrupt}
+ */
+static DECLCALLBACK(int) dbgcIoProvUdpWaitInterrupt(DBGCIOPROV hDbgcIoProv)
+{
+ RT_NOREF(hDbgcIoProv);
+ /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * UDP I/O provider registration record.
+ */
+const DBGCIOPROVREG g_DbgcIoProvUdp =
+{
+ /** pszName */
+ "udp",
+ /** pszDesc */
+ "UDP I/O provider.",
+ /** pfnCreate */
+ dbgcIoProvUdpCreate,
+ /** pfnDestroy */
+ dbgcIoProvUdpDestroy,
+ /** pfnWaitForConnect */
+ dbgcIoProvUdpWaitForConnect,
+ /** pfnWaitInterrupt */
+ dbgcIoProvUdpWaitInterrupt
+};
+
diff --git a/src/VBox/Debugger/DBGCOps.cpp b/src/VBox/Debugger/DBGCOps.cpp
new file mode 100644
index 00000000..fe7a946b
--- /dev/null
+++ b/src/VBox/Debugger/DBGCOps.cpp
@@ -0,0 +1,1380 @@
+/* $Id: DBGCOps.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Operators.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/param.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgcOpMinus(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpPluss(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBooleanNot(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBitwiseNot(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpVar(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult);
+
+static DECLCALLBACK(int) dbgcOpAddrFar(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpMult(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpDiv(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpMod(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpAdd(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpSub(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBitwiseShiftLeft(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBitwiseShiftRight(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBitwiseAnd(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBitwiseXor(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBitwiseOr(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBooleanAnd(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpBooleanOr(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpRangeLength(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpRangeLengthBytes(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+static DECLCALLBACK(int) dbgcOpRangeTo(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult);
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Generic implementation of a binary operator.
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ * @param Operator The C operator.
+ * @param fIsDiv Set if it's division and we need to check for zero on the
+ * right hand side.
+ */
+#define DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, Operator, fIsDiv) \
+ do \
+ { \
+ if ((pArg1)->enmType == DBGCVAR_TYPE_STRING) \
+ return VERR_DBGC_PARSE_INVALID_OPERATION; \
+ \
+ /* Get the 64-bit right side value. */ \
+ uint64_t u64Right; \
+ int rc = dbgcOpHelperGetNumber((pDbgc), (pArg2), &u64Right); \
+ if ((fIsDiv) && RT_SUCCESS(rc) && !u64Right) /* div/0 kludge */ \
+ DBGCVAR_INIT_NUMBER((pResult), UINT64_MAX); \
+ else if (RT_SUCCESS(rc)) \
+ { \
+ /* Apply it to the left hand side. */ \
+ if ((pArg1)->enmType == DBGCVAR_TYPE_SYMBOL) \
+ { \
+ rc = dbgcSymbolGet((pDbgc), (pArg1)->u.pszString, DBGCVAR_TYPE_ANY, (pResult)); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ } \
+ else \
+ *(pResult) = *(pArg1); \
+ switch ((pResult)->enmType) \
+ { \
+ case DBGCVAR_TYPE_GC_FLAT: \
+ (pResult)->u.GCFlat = (pResult)->u.GCFlat Operator u64Right; \
+ break; \
+ case DBGCVAR_TYPE_GC_FAR: \
+ (pResult)->u.GCFar.off = (pResult)->u.GCFar.off Operator u64Right; \
+ break; \
+ case DBGCVAR_TYPE_GC_PHYS: \
+ (pResult)->u.GCPhys = (pResult)->u.GCPhys Operator u64Right; \
+ break; \
+ case DBGCVAR_TYPE_HC_FLAT: \
+ (pResult)->u.pvHCFlat = (void *)((uintptr_t)(pResult)->u.pvHCFlat Operator u64Right); \
+ break; \
+ case DBGCVAR_TYPE_HC_PHYS: \
+ (pResult)->u.HCPhys = (pResult)->u.HCPhys Operator u64Right; \
+ break; \
+ case DBGCVAR_TYPE_NUMBER: \
+ (pResult)->u.u64Number = (pResult)->u.u64Number Operator u64Right; \
+ break; \
+ default: \
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE; \
+ } \
+ } \
+ return rc; \
+ } while (0)
+
+
+/**
+ * Switch the factors/whatever so we preserve pointers.
+ * Far pointers are considered more important that physical and flat pointers.
+ *
+ * @param pArg1 The left side argument. Input & output.
+ * @param pArg2 The right side argument. Input & output.
+ */
+#define DBGC_GEN_ARIT_POINTER_TO_THE_LEFT(pArg1, pArg2) \
+ do \
+ { \
+ if ( DBGCVAR_ISPOINTER((pArg2)->enmType) \
+ && ( !DBGCVAR_ISPOINTER((pArg1)->enmType) \
+ || ( DBGCVAR_IS_FAR_PTR((pArg2)->enmType) \
+ && !DBGCVAR_IS_FAR_PTR((pArg1)->enmType)))) \
+ { \
+ PCDBGCVAR pTmp = (pArg1); \
+ (pArg2) = (pArg1); \
+ (pArg1) = pTmp; \
+ } \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Operators. */
+const DBGCOP g_aDbgcOps[] =
+{
+ /* szName is initialized as a 4 char array because of M$C elsewise optimizing it away in /Ox mode (the 'const char' vs 'char' problem). */
+ /* szName, cchName, fBinary, iPrecedence, pfnHandlerUnary, pfnHandlerBitwise */
+ { {'-'}, 1, false, 1, dbgcOpMinus, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Unary minus." },
+ { {'+'}, 1, false, 1, dbgcOpPluss, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Unary plus." },
+ { {'!'}, 1, false, 1, dbgcOpBooleanNot, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Boolean not." },
+ { {'~'}, 1, false, 1, dbgcOpBitwiseNot, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Bitwise complement." },
+ { {':'}, 1, true, 2, NULL, dbgcOpAddrFar, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Far pointer." },
+ { {'%'}, 1, false, 3, dbgcOpAddrFlat, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Flat address." },
+ { {'%','%'}, 2, false, 3, dbgcOpAddrPhys, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Physical address." },
+ { {'#'}, 1, false, 3, dbgcOpAddrHost, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Flat host address." },
+ { {'#','%','%'}, 3, false, 3, dbgcOpAddrHostPhys, NULL, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Physical host address." },
+ { {'$'}, 1, false, 3, dbgcOpVar, NULL, DBGCVAR_CAT_SYMBOL, DBGCVAR_CAT_ANY, "Reference a variable." },
+ { {'@'}, 1, false, 3, dbgcOpRegister, NULL, DBGCVAR_CAT_SYMBOL, DBGCVAR_CAT_ANY, "Reference a register." },
+ { {'*'}, 1, true, 10, NULL, dbgcOpMult, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Multiplication." },
+ { {'/'}, 1, true, 11, NULL, dbgcOpDiv, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Division." },
+ { {'m','o','d'}, 3, true, 12, NULL, dbgcOpMod, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Modulus." },
+ { {'+'}, 1, true, 13, NULL, dbgcOpAdd, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Addition." },
+ { {'-'}, 1, true, 14, NULL, dbgcOpSub, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Subtraction." },
+ { {'<','<'}, 2, true, 15, NULL, dbgcOpBitwiseShiftLeft, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Bitwise left shift." },
+ { {'>','>'}, 2, true, 16, NULL, dbgcOpBitwiseShiftRight, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Bitwise right shift." },
+ { {'&'}, 1, true, 17, NULL, dbgcOpBitwiseAnd, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Bitwise and." },
+ { {'^'}, 1, true, 18, NULL, dbgcOpBitwiseXor, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Bitwise exclusiv or." },
+ { {'|'}, 1, true, 19, NULL, dbgcOpBitwiseOr, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Bitwise inclusive or." },
+ { {'&','&'}, 2, true, 20, NULL, dbgcOpBooleanAnd, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Boolean and." },
+ { {'|','|'}, 2, true, 21, NULL, dbgcOpBooleanOr, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Boolean or." },
+ { {'L'}, 1, true, 22, NULL, dbgcOpRangeLength, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Range elements." },
+ { {'L','B'}, 2, true, 23, NULL, dbgcOpRangeLengthBytes, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Range bytes." },
+ { {'T'}, 1, true, 24, NULL, dbgcOpRangeTo, DBGCVAR_CAT_ANY, DBGCVAR_CAT_ANY, "Range to." }
+};
+
+/** Number of operators in the operator array. */
+const uint32_t g_cDbgcOps = RT_ELEMENTS(g_aDbgcOps);
+
+
+/**
+ * Converts an argument to a number value.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The DBGC instance.
+ * @param pArg The argument to convert.
+ * @param pu64Ret Where to return the value.
+ */
+static int dbgcOpHelperGetNumber(PDBGC pDbgc, PCDBGCVAR pArg, uint64_t *pu64Ret)
+{
+ DBGCVAR Var = *pArg;
+ switch (Var.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ *pu64Ret = Var.u.GCFlat;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ *pu64Ret = Var.u.GCFar.off;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ *pu64Ret = Var.u.GCPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ *pu64Ret = (uintptr_t)Var.u.pvHCFlat;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ *pu64Ret = Var.u.HCPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ *pu64Ret = Var.u.u64Number;
+ break;
+ case DBGCVAR_TYPE_SYMBOL:
+ {
+ int rc = dbgcSymbolGet(pDbgc, Var.u.pszString, DBGCVAR_TYPE_NUMBER, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ RT_FALL_THRU();
+ case DBGCVAR_TYPE_STRING:
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Negate (unary).}
+ */
+static DECLCALLBACK(int) dbgcOpMinus(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpMinus\n"));
+ *pResult = *pArg;
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ pResult->u.GCFlat = -(RTGCINTPTR)pResult->u.GCFlat;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ pResult->u.GCFar.off = -(int32_t)pResult->u.GCFar.off;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->u.GCPhys = (RTGCPHYS) -(int64_t)pResult->u.GCPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->u.pvHCFlat = (void *) -(intptr_t)pResult->u.pvHCFlat;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->u.HCPhys = (RTHCPHYS) -(int64_t)pResult->u.HCPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u.u64Number = -(int64_t)pResult->u.u64Number;
+ break;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ NOREF(pDbgc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Plus (unary).}
+ */
+static DECLCALLBACK(int) dbgcOpPluss(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpPluss\n"));
+ *pResult = *pArg;
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ case DBGCVAR_TYPE_NUMBER:
+ break;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ NOREF(pDbgc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Boolean not (unary).}
+ */
+static DECLCALLBACK(int) dbgcOpBooleanNot(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpBooleanNot\n"));
+ *pResult = *pArg;
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ pResult->u.u64Number = !pResult->u.GCFlat;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ pResult->u.u64Number = !pResult->u.GCFar.off && pResult->u.GCFar.sel <= 3;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->u.u64Number = !pResult->u.GCPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->u.u64Number = !pResult->u.pvHCFlat;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->u.u64Number = !pResult->u.HCPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u.u64Number = !pResult->u.u64Number;
+ break;
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ pResult->u.u64Number = !pResult->u64Range;
+ break;
+
+ case DBGCVAR_TYPE_UNKNOWN:
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ pResult->enmType = DBGCVAR_TYPE_NUMBER;
+ NOREF(pDbgc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Bitwise not (unary).}
+ */
+static DECLCALLBACK(int) dbgcOpBitwiseNot(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpBitwiseNot\n"));
+ *pResult = *pArg;
+ switch (pArg->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ pResult->u.GCFlat = ~pResult->u.GCFlat;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ pResult->u.GCFar.off = ~pResult->u.GCFar.off;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->u.GCPhys = ~pResult->u.GCPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->u.pvHCFlat = (void *)~(uintptr_t)pResult->u.pvHCFlat;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->u.HCPhys = ~pResult->u.HCPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u.u64Number = ~pResult->u.u64Number;
+ break;
+
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ NOREF(pDbgc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Reference variable (unary).}
+ */
+static DECLCALLBACK(int) dbgcOpVar(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpVar: %s\n", pArg->u.pszString));
+ AssertReturn(pArg->enmType == DBGCVAR_TYPE_SYMBOL, VERR_DBGC_PARSE_BUG);
+
+ /*
+ * Lookup the variable.
+ */
+ const char *pszVar = pArg->u.pszString;
+ for (unsigned iVar = 0; iVar < pDbgc->cVars; iVar++)
+ {
+ if (!strcmp(pszVar, pDbgc->papVars[iVar]->szName))
+ {
+ *pResult = pDbgc->papVars[iVar]->Var;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_DBGC_PARSE_VARIABLE_NOT_FOUND;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Reference register (unary).}
+ */
+DECLCALLBACK(int) dbgcOpRegister(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpRegister: %s\n", pArg->u.pszString));
+ AssertReturn(pArg->enmType == DBGCVAR_TYPE_SYMBOL, VERR_DBGC_PARSE_BUG);
+
+ /* Detect references to hypervisor registers. */
+ const char *pszReg = pArg->u.pszString;
+ VMCPUID idCpu = pDbgc->idCpu;
+ if (pszReg[0] == '.')
+ {
+ pszReg++;
+ idCpu |= DBGFREG_HYPER_VMCPUID;
+ }
+
+ /*
+ * If the desired result is a symbol, pass the argument along unmodified.
+ * This is a great help for "r @eax" and such, since it will be translated to "r eax".
+ */
+ if (enmCat == DBGCVAR_CAT_SYMBOL)
+ {
+ int rc = DBGFR3RegNmValidate(pDbgc->pUVM, idCpu, pszReg);
+ if (RT_SUCCESS(rc))
+ DBGCVAR_INIT_STRING(pResult, pArg->u.pszString);
+ return rc;
+ }
+
+ /*
+ * Get the register.
+ */
+ DBGFREGVALTYPE enmType;
+ DBGFREGVAL Value;
+ int rc = DBGFR3RegNmQuery(pDbgc->pUVM, idCpu, pszReg, &Value, &enmType);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmType)
+ {
+ case DBGFREGVALTYPE_U8:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u8);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_U16:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u16);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_U32:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u32);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_U64:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u64);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_U128:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u128.s.Lo);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_U256:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u256.QWords.qw0);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_U512:
+ DBGCVAR_INIT_NUMBER(pResult, Value.u512.QWords.qw0);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_R80:
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ DBGCVAR_INIT_NUMBER(pResult, (uint64_t)Value.r80Ex.lrd);
+#else
+ DBGCVAR_INIT_NUMBER(pResult, (uint64_t)Value.r80Ex.sj64.uFraction);
+#endif
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_DTR:
+ DBGCVAR_INIT_NUMBER(pResult, Value.dtr.u64Base);
+ return VINF_SUCCESS;
+
+ case DBGFREGVALTYPE_INVALID:
+ case DBGFREGVALTYPE_END:
+ case DBGFREGVALTYPE_32BIT_HACK:
+ break;
+ }
+ rc = VERR_INTERNAL_ERROR_5;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Flat address (unary).}
+ */
+DECLCALLBACK(int) dbgcOpAddrFlat(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpAddrFlat\n"));
+ DBGCVARTYPE enmType = DBGCVAR_ISHCPOINTER(pArg->enmType) ? DBGCVAR_TYPE_HC_FLAT : DBGCVAR_TYPE_GC_FLAT;
+ return DBGCCmdHlpConvert(&pDbgc->CmdHlp, pArg, enmType, true /*fConvSyms*/, pResult);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Physical address (unary).}
+ */
+DECLCALLBACK(int) dbgcOpAddrPhys(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpAddrPhys\n"));
+ DBGCVARTYPE enmType = DBGCVAR_ISHCPOINTER(pArg->enmType) ? DBGCVAR_TYPE_HC_PHYS : DBGCVAR_TYPE_GC_PHYS;
+ return DBGCCmdHlpConvert(&pDbgc->CmdHlp, pArg, enmType, true /*fConvSyms*/, pResult);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Physical host address (unary).}
+ */
+DECLCALLBACK(int) dbgcOpAddrHostPhys(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpAddrPhys\n"));
+ return DBGCCmdHlpConvert(&pDbgc->CmdHlp, pArg, DBGCVAR_TYPE_HC_PHYS, true /*fConvSyms*/, pResult);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Host address (unary).}
+ */
+DECLCALLBACK(int) dbgcOpAddrHost(PDBGC pDbgc, PCDBGCVAR pArg, DBGCVARCAT enmCat, PDBGCVAR pResult)
+{
+ RT_NOREF1(enmCat);
+ LogFlow(("dbgcOpAddrHost\n"));
+ return DBGCCmdHlpConvert(&pDbgc->CmdHlp, pArg, DBGCVAR_TYPE_HC_FLAT, true /*fConvSyms*/, pResult);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGCOPUNARY, Far address (unary).}
+ */
+static DECLCALLBACK(int) dbgcOpAddrFar(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpAddrFar\n"));
+ int rc;
+
+ switch (pArg1->enmType)
+ {
+ case DBGCVAR_TYPE_SYMBOL:
+ rc = dbgcSymbolGet(pDbgc, pArg1->u.pszString, DBGCVAR_TYPE_NUMBER, pResult);
+ if (RT_FAILURE(rc))
+ return rc;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ *pResult = *pArg1;
+ break;
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ pResult->u.GCFar.sel = (RTSEL)pResult->u.u64Number;
+
+ /* common code for the two types we support. */
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ pResult->u.GCFar.off = pArg2->u.GCFlat;
+ pResult->enmType = DBGCVAR_TYPE_GC_FAR;
+ break;
+
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->u.pvHCFlat = (void *)(uintptr_t)pArg2->u.GCFlat;
+ pResult->enmType = DBGCVAR_TYPE_GC_FAR;
+ break;
+
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u.GCFar.off = (RTGCPTR)pArg2->u.u64Number;
+ pResult->enmType = DBGCVAR_TYPE_GC_FAR;
+ break;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ {
+ DBGCVAR Var;
+ rc = dbgcSymbolGet(pDbgc, pArg2->u.pszString, DBGCVAR_TYPE_NUMBER, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.GCFar.off = (RTGCPTR)Var.u.u64Number;
+ pResult->enmType = DBGCVAR_TYPE_GC_FAR;
+ break;
+ }
+
+ default:
+ return VERR_DBGC_PARSE_INCORRECT_ARG_TYPE;
+ }
+ return VINF_SUCCESS;
+
+}
+
+
+/**
+ * Multiplication operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpMult(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpMult\n"));
+ DBGC_GEN_ARIT_POINTER_TO_THE_LEFT(pArg1, pArg2);
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, *, false);
+}
+
+
+/**
+ * Division operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpDiv(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpDiv\n"));
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, /, true);
+}
+
+
+/**
+ * Modulus operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpMod(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpMod\n"));
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, %, false);
+}
+
+
+/**
+ * Addition operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpAdd(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpAdd\n"));
+
+ /*
+ * An addition operation will return (when possible) the left side type in the
+ * expression. We make an omission for numbers, where we'll take the right side
+ * type instead. An expression where only the left hand side is a symbol we'll
+ * use the right hand type to try resolve it.
+ */
+ if ( pArg1->enmType == DBGCVAR_TYPE_STRING
+ || pArg2->enmType == DBGCVAR_TYPE_STRING)
+ return VERR_DBGC_PARSE_INVALID_OPERATION; /** @todo string contactenation later. */
+
+ if ( (pArg1->enmType == DBGCVAR_TYPE_NUMBER && pArg2->enmType != DBGCVAR_TYPE_SYMBOL)
+ || (pArg1->enmType == DBGCVAR_TYPE_SYMBOL && pArg2->enmType != DBGCVAR_TYPE_SYMBOL))
+ {
+ PCDBGCVAR pTmp = pArg2;
+ pArg2 = pArg1;
+ pArg1 = pTmp;
+ }
+
+ DBGCVAR Sym1, Sym2;
+ if (pArg1->enmType == DBGCVAR_TYPE_SYMBOL)
+ {
+ int rc = dbgcSymbolGet(pDbgc, pArg1->u.pszString, DBGCVAR_TYPE_ANY, &Sym1);
+ if (RT_FAILURE(rc))
+ return rc;
+ pArg1 = &Sym1;
+
+ rc = dbgcSymbolGet(pDbgc, pArg2->u.pszString, DBGCVAR_TYPE_ANY, &Sym2);
+ if (RT_FAILURE(rc))
+ return rc;
+ pArg2 = &Sym2;
+ }
+
+ int rc;
+ DBGCVAR Var;
+ DBGCVAR Var2;
+ switch (pArg1->enmType)
+ {
+ /*
+ * GC Flat
+ */
+ case DBGCVAR_TYPE_GC_FLAT:
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ default:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrFlat(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.GCFlat += pArg2->u.GCFlat;
+ break;
+ }
+ break;
+
+ /*
+ * GC Far
+ */
+ case DBGCVAR_TYPE_GC_FAR:
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ case DBGCVAR_TYPE_NUMBER:
+ *pResult = *pArg1;
+ pResult->u.GCFar.off += (RTGCPTR)pArg2->u.u64Number;
+ break;
+ default:
+ rc = dbgcOpAddrFlat(pDbgc, pArg1, DBGCVAR_CAT_ANY, pResult);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = dbgcOpAddrFlat(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.GCFlat += pArg2->u.GCFlat;
+ break;
+ }
+ break;
+
+ /*
+ * GC Phys
+ */
+ case DBGCVAR_TYPE_GC_PHYS:
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ default:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrPhys(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (Var.enmType != DBGCVAR_TYPE_GC_PHYS)
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ pResult->u.GCPhys += Var.u.GCPhys;
+ break;
+ }
+ break;
+
+ /*
+ * HC Flat
+ */
+ case DBGCVAR_TYPE_HC_FLAT:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrHost(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var2);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = dbgcOpAddrFlat(pDbgc, &Var2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.pvHCFlat = (char *)pResult->u.pvHCFlat + (uintptr_t)Var.u.pvHCFlat;
+ break;
+
+ /*
+ * HC Phys
+ */
+ case DBGCVAR_TYPE_HC_PHYS:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrHostPhys(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.HCPhys += Var.u.HCPhys;
+ break;
+
+ /*
+ * Numbers (see start of function)
+ */
+ case DBGCVAR_TYPE_NUMBER:
+ *pResult = *pArg1;
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_SYMBOL:
+ rc = dbgcSymbolGet(pDbgc, pArg2->u.pszString, DBGCVAR_TYPE_NUMBER, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ RT_FALL_THRU();
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u.u64Number += pArg2->u.u64Number;
+ break;
+ default:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ }
+ break;
+
+ default:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Subtraction operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpSub(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpSub\n"));
+
+ /*
+ * An subtraction operation will return the left side type in the expression.
+ * However, if the left hand side is a number and the right hand a pointer of
+ * some kind we'll convert the left hand side to the same type as the right hand.
+ * Any symbols will be resolved, strings will be rejected.
+ */
+ DBGCVAR Sym1, Sym2;
+ if ( pArg2->enmType == DBGCVAR_TYPE_SYMBOL
+ && ( pArg1->enmType == DBGCVAR_TYPE_NUMBER
+ || pArg1->enmType == DBGCVAR_TYPE_SYMBOL))
+ {
+ int rc = dbgcSymbolGet(pDbgc, pArg2->u.pszString, DBGCVAR_TYPE_ANY, &Sym2);
+ if (RT_FAILURE(rc))
+ return rc;
+ pArg2 = &Sym2;
+ }
+
+ if ( pArg1->enmType == DBGCVAR_TYPE_STRING
+ || pArg2->enmType == DBGCVAR_TYPE_STRING)
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+
+ if (pArg1->enmType == DBGCVAR_TYPE_SYMBOL)
+ {
+ DBGCVARTYPE enmType;
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_NUMBER:
+ enmType = DBGCVAR_TYPE_ANY;
+ break;
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ enmType = pArg2->enmType;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ enmType = DBGCVAR_TYPE_GC_FLAT;
+ break;
+ default: AssertFailedReturn(VERR_DBGC_IPE);
+ }
+ if (enmType != DBGCVAR_TYPE_STRING)
+ {
+ int rc = dbgcSymbolGet(pDbgc, pArg1->u.pszString, DBGCVAR_TYPE_ANY, &Sym1);
+ if (RT_FAILURE(rc))
+ return rc;
+ pArg1 = &Sym1;
+ }
+ }
+ else if (pArg1->enmType == DBGCVAR_TYPE_NUMBER)
+ {
+ PFNDBGCOPUNARY pOp = NULL;
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_GC_FLAT:
+ pOp = dbgcOpAddrFlat;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ pOp = dbgcOpAddrPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ pOp = dbgcOpAddrHost;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ pOp = dbgcOpAddrHostPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ break;
+ default: AssertFailedReturn(VERR_DBGC_IPE);
+ }
+ if (pOp)
+ {
+ int rc = pOp(pDbgc, pArg1, DBGCVAR_CAT_ANY, &Sym1);
+ if (RT_FAILURE(rc))
+ return rc;
+ pArg1 = &Sym1;
+ }
+ }
+
+ /*
+ * Normal processing.
+ */
+ int rc;
+ DBGCVAR Var;
+ DBGCVAR Var2;
+ switch (pArg1->enmType)
+ {
+ /*
+ * GC Flat
+ */
+ case DBGCVAR_TYPE_GC_FLAT:
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ default:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrFlat(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.GCFlat -= pArg2->u.GCFlat;
+ break;
+ }
+ break;
+
+ /*
+ * GC Far
+ */
+ case DBGCVAR_TYPE_GC_FAR:
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ case DBGCVAR_TYPE_NUMBER:
+ *pResult = *pArg1;
+ pResult->u.GCFar.off -= (RTGCPTR)pArg2->u.u64Number;
+ break;
+ default:
+ rc = dbgcOpAddrFlat(pDbgc, pArg1, DBGCVAR_CAT_ANY, pResult);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = dbgcOpAddrFlat(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.GCFlat -= pArg2->u.GCFlat;
+ break;
+ }
+ break;
+
+ /*
+ * GC Phys
+ */
+ case DBGCVAR_TYPE_GC_PHYS:
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ default:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrPhys(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (Var.enmType != DBGCVAR_TYPE_GC_PHYS)
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ pResult->u.GCPhys -= Var.u.GCPhys;
+ break;
+ }
+ break;
+
+ /*
+ * HC Flat
+ */
+ case DBGCVAR_TYPE_HC_FLAT:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrHost(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var2);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = dbgcOpAddrFlat(pDbgc, &Var2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.pvHCFlat = (char *)pResult->u.pvHCFlat - (uintptr_t)Var.u.pvHCFlat;
+ break;
+
+ /*
+ * HC Phys
+ */
+ case DBGCVAR_TYPE_HC_PHYS:
+ *pResult = *pArg1;
+ rc = dbgcOpAddrHostPhys(pDbgc, pArg2, DBGCVAR_CAT_ANY, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u.HCPhys -= Var.u.HCPhys;
+ break;
+
+ /*
+ * Numbers (see start of function)
+ */
+ case DBGCVAR_TYPE_NUMBER:
+ *pResult = *pArg1;
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_SYMBOL:
+ rc = dbgcSymbolGet(pDbgc, pArg2->u.pszString, DBGCVAR_TYPE_NUMBER, &Var);
+ if (RT_FAILURE(rc))
+ return rc;
+ RT_FALL_THRU();
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u.u64Number -= pArg2->u.u64Number;
+ break;
+ default:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ }
+ break;
+
+ default:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Bitwise shift left operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBitwiseShiftLeft(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBitwiseShiftLeft\n"));
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, <<, false);
+}
+
+
+/**
+ * Bitwise shift right operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBitwiseShiftRight(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBitwiseShiftRight\n"));
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, >>, false);
+}
+
+
+/**
+ * Bitwise and operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBitwiseAnd(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBitwiseAnd\n"));
+ DBGC_GEN_ARIT_POINTER_TO_THE_LEFT(pArg1, pArg2);
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, &, false);
+}
+
+
+/**
+ * Bitwise exclusive or operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBitwiseXor(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBitwiseXor\n"));
+ DBGC_GEN_ARIT_POINTER_TO_THE_LEFT(pArg1, pArg2);
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, ^, false);
+}
+
+
+/**
+ * Bitwise inclusive or operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBitwiseOr(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBitwiseOr\n"));
+ DBGC_GEN_ARIT_POINTER_TO_THE_LEFT(pArg1, pArg2);
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, |, false);
+}
+
+
+/**
+ * Boolean and operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBooleanAnd(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBooleanAnd\n"));
+ /** @todo force numeric return value? */
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, &&, false);
+}
+
+
+/**
+ * Boolean or operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpBooleanOr(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpBooleanOr\n"));
+ /** @todo force numeric return value? */
+ DBGC_GEN_ARIT_BINARY_OP(pDbgc, pArg1, pArg2, pResult, ||, false);
+}
+
+
+/**
+ * Range to operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpRangeLength(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpRangeLength\n"));
+
+ if (pArg1->enmType == DBGCVAR_TYPE_STRING)
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+
+ /*
+ * Make result. Symbols needs to be resolved.
+ */
+ if (pArg1->enmType == DBGCVAR_TYPE_SYMBOL)
+ {
+ int rc = dbgcSymbolGet(pDbgc, pArg1->u.pszString, DBGCVAR_TYPE_ANY, pResult);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ *pResult = *pArg1;
+
+ /*
+ * Convert 2nd argument to element count.
+ */
+ pResult->enmRangeType = DBGCVAR_RANGE_ELEMENTS;
+ switch (pArg2->enmType)
+ {
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u64Range = pArg2->u.u64Number;
+ break;
+
+ case DBGCVAR_TYPE_SYMBOL:
+ {
+ int rc = dbgcSymbolGet(pDbgc, pArg2->u.pszString, DBGCVAR_TYPE_NUMBER, pResult);
+ if (RT_FAILURE(rc))
+ return rc;
+ pResult->u64Range = pArg2->u.u64Number;
+ break;
+ }
+
+ case DBGCVAR_TYPE_STRING:
+ default:
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Range to operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpRangeLengthBytes(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpRangeLengthBytes\n"));
+ int rc = dbgcOpRangeLength(pDbgc, pArg1, pArg2, pResult);
+ if (RT_SUCCESS(rc))
+ pResult->enmRangeType = DBGCVAR_RANGE_BYTES;
+ return rc;
+}
+
+
+/**
+ * Range to operator (binary).
+ *
+ * @returns VINF_SUCCESS on success.
+ * @returns VBox evaluation / parsing error code on failure.
+ * The caller does the bitching.
+ * @param pDbgc Debugger console instance data.
+ * @param pArg1 The first argument.
+ * @param pArg2 The 2nd argument.
+ * @param pResult Where to store the result.
+ */
+static DECLCALLBACK(int) dbgcOpRangeTo(PDBGC pDbgc, PCDBGCVAR pArg1, PCDBGCVAR pArg2, PDBGCVAR pResult)
+{
+ LogFlow(("dbgcOpRangeTo\n"));
+
+ /*
+ * Calc number of bytes between the two args.
+ */
+ DBGCVAR Diff;
+ int rc = dbgcOpSub(pDbgc, pArg2, pArg1, &Diff);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Use the diff as the range of Arg1.
+ */
+ *pResult = *pArg1;
+ pResult->enmRangeType = DBGCVAR_RANGE_BYTES;
+ switch (Diff.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ pResult->u64Range = (RTGCUINTPTR)Diff.u.GCFlat;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ pResult->u64Range = Diff.u.GCPhys;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ pResult->u64Range = (uintptr_t)Diff.u.pvHCFlat;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ pResult->u64Range = Diff.u.HCPhys;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->u64Range = Diff.u.u64Number;
+ break;
+
+ case DBGCVAR_TYPE_GC_FAR:
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ default:
+ AssertMsgFailed(("Impossible!\n"));
+ return VERR_DBGC_PARSE_INVALID_OPERATION;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Searches for an operator descriptor which matches the start of
+ * the expression given us.
+ *
+ * @returns Pointer to the operator on success.
+ * @param pDbgc The debug console instance.
+ * @param pszExpr Pointer to the expression string which might start with an operator.
+ * @param fPreferBinary Whether to favour binary or unary operators.
+ * Caller must assert that it's the desired type! Both types will still
+ * be returned, this is only for resolving duplicates.
+ * @param chPrev The previous char. Some operators requires a blank in front of it.
+ */
+PCDBGCOP dbgcOperatorLookup(PDBGC pDbgc, const char *pszExpr, bool fPreferBinary, char chPrev)
+{
+ PCDBGCOP pOp = NULL;
+ for (unsigned iOp = 0; iOp < RT_ELEMENTS(g_aDbgcOps); iOp++)
+ {
+ if ( g_aDbgcOps[iOp].szName[0] == pszExpr[0]
+ && (!g_aDbgcOps[iOp].szName[1] || g_aDbgcOps[iOp].szName[1] == pszExpr[1])
+ && (!g_aDbgcOps[iOp].szName[2] || g_aDbgcOps[iOp].szName[2] == pszExpr[2]))
+ {
+ /*
+ * Check that we don't mistake it for some other operator which have more chars.
+ */
+ unsigned j;
+ for (j = iOp + 1; j < RT_ELEMENTS(g_aDbgcOps); j++)
+ if ( g_aDbgcOps[j].cchName > g_aDbgcOps[iOp].cchName
+ && g_aDbgcOps[j].szName[0] == pszExpr[0]
+ && (!g_aDbgcOps[j].szName[1] || g_aDbgcOps[j].szName[1] == pszExpr[1])
+ && (!g_aDbgcOps[j].szName[2] || g_aDbgcOps[j].szName[2] == pszExpr[2]) )
+ break;
+ if (j < RT_ELEMENTS(g_aDbgcOps))
+ continue; /* we'll catch it later. (for theoretical +,++,+++ cases.) */
+ pOp = &g_aDbgcOps[iOp];
+
+ /*
+ * Preferred type?
+ */
+ if (g_aDbgcOps[iOp].fBinary == fPreferBinary)
+ break;
+ }
+ }
+
+ if (pOp)
+ Log2(("dbgcOperatorLookup: pOp=%p %s\n", pOp, pOp->szName));
+ NOREF(pDbgc); NOREF(chPrev);
+ return pOp;
+}
+
diff --git a/src/VBox/Debugger/DBGCRemoteKd.cpp b/src/VBox/Debugger/DBGCRemoteKd.cpp
new file mode 100644
index 00000000..c73d6884
--- /dev/null
+++ b/src/VBox/Debugger/DBGCRemoteKd.cpp
@@ -0,0 +1,4550 @@
+/* $Id: DBGCRemoteKd.cpp $ */
+/** @file
+ * DBGC - Debugger Console, Windows Kd Remote Stub.
+ */
+
+/*
+ * Copyright (C) 2020-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/vmapi.h> /* VMR3GetVM() */
+#include <VBox/vmm/hm.h> /* HMR3IsEnabled */
+#include <VBox/vmm/nem.h> /* NEMR3IsEnabled */
+#include <iprt/assertcompile.h>
+#include <iprt/cdefs.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/sg.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/x86.h>
+#include <iprt/formats/pecoff.h>
+#include <iprt/formats/mz.h>
+
+#include <stdlib.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Number of milliseconds we wait for new data to arrive when a new packet was detected. */
+#define DBGC_KD_RECV_TIMEOUT_MS UINT32_C(1000)
+
+/** NT status code - Success. */
+#define NTSTATUS_SUCCESS 0
+/** NT status code - buffer overflow. */
+#define NTSTATUS_BUFFER_OVERFLOW UINT32_C(0x80000005)
+/** NT status code - operation unsuccesful. */
+#define NTSTATUS_UNSUCCESSFUL UINT32_C(0xc0000001)
+/** NT status code - operation not implemented. */
+#define NTSTATUS_NOT_IMPLEMENTED UINT32_C(0xc0000002)
+/** NT status code - Object not found. */
+#define NTSTATUS_NOT_FOUND UINT32_C(0xc0000225)
+
+/** Offset where the KD version block pointer is stored in the KPCR.
+ * From: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kprcb/amd64.htm */
+#define KD_KPCR_VERSION_BLOCK_ADDR_OFF 0x34
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * KD packet header as sent over the wire.
+ */
+typedef struct KDPACKETHDR
+{
+ /** Packet signature (leader) - defines the type of packet. */
+ uint32_t u32Signature;
+ /** Packet (sub) type. */
+ uint16_t u16SubType;
+ /** Size of the packet body in bytes.*/
+ uint16_t cbBody;
+ /** Packet ID. */
+ uint32_t idPacket;
+ /** Checksum of the packet body. */
+ uint32_t u32ChkSum;
+} KDPACKETHDR;
+AssertCompileSize(KDPACKETHDR, 16);
+/** Pointer to a packet header. */
+typedef KDPACKETHDR *PKDPACKETHDR;
+/** Pointer to a const packet header. */
+typedef const KDPACKETHDR *PCKDPACKETHDR;
+
+/** Signature for a data packet. */
+#define KD_PACKET_HDR_SIGNATURE_DATA UINT32_C(0x30303030)
+/** First byte for a data packet header. */
+#define KD_PACKET_HDR_SIGNATURE_DATA_BYTE 0x30
+/** Signature for a control packet. */
+#define KD_PACKET_HDR_SIGNATURE_CONTROL UINT32_C(0x69696969)
+/** First byte for a control packet header. */
+#define KD_PACKET_HDR_SIGNATURE_CONTROL_BYTE 0x69
+/** Signature for a breakin packet. */
+#define KD_PACKET_HDR_SIGNATURE_BREAKIN UINT32_C(0x62626262)
+/** First byte for a breakin packet header. */
+#define KD_PACKET_HDR_SIGNATURE_BREAKIN_BYTE 0x62
+
+/** @name Packet sub types.
+ * @{ */
+#define KD_PACKET_HDR_SUB_TYPE_STATE_CHANGE32 UINT16_C(1)
+#define KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE UINT16_C(2)
+#define KD_PACKET_HDR_SUB_TYPE_DEBUG_IO UINT16_C(3)
+#define KD_PACKET_HDR_SUB_TYPE_ACKNOWLEDGE UINT16_C(4)
+#define KD_PACKET_HDR_SUB_TYPE_RESEND UINT16_C(5)
+#define KD_PACKET_HDR_SUB_TYPE_RESET UINT16_C(6)
+#define KD_PACKET_HDR_SUB_TYPE_STATE_CHANGE64 UINT16_C(7)
+#define KD_PACKET_HDR_SUB_TYPE_POLL_BREAKIN UINT16_C(8)
+#define KD_PACKET_HDR_SUB_TYPE_TRACE_IO UINT16_C(9)
+#define KD_PACKET_HDR_SUB_TYPE_CONTROL_REQUEST UINT16_C(10)
+#define KD_PACKET_HDR_SUB_TYPE_FILE_IO UINT16_C(11)
+#define KD_PACKET_HDR_SUB_TYPE_MAX UINT16_C(12)
+/** @} */
+
+/** Initial packet ID value. */
+#define KD_PACKET_HDR_ID_INITIAL UINT32_C(0x80800800)
+/** Packet ID value after a resync. */
+#define KD_PACKET_HDR_ID_RESET UINT32_C(0x80800000)
+
+/** Trailing byte of a packet. */
+#define KD_PACKET_TRAILING_BYTE 0xaa
+
+
+/** Maximum number of parameters in the exception record. */
+#define KDPACKETEXCP_PARMS_MAX 15
+
+/**
+ * 64bit exception record.
+ */
+typedef struct KDPACKETEXCP64
+{
+ /** The exception code identifying the excpetion. */
+ uint32_t u32ExcpCode;
+ /** Flags associated with the exception. */
+ uint32_t u32ExcpFlags;
+ /** Pointer to a chained exception record. */
+ uint64_t u64PtrExcpRecNested;
+ /** Address where the exception occurred. */
+ uint64_t u64PtrExcpAddr;
+ /** Number of parameters in the exception information array. */
+ uint32_t cExcpParms;
+ /** Alignment. */
+ uint32_t u32Alignment;
+ /** Exception parameters array. */
+ uint64_t au64ExcpParms[KDPACKETEXCP_PARMS_MAX];
+} KDPACKETEXCP64;
+AssertCompileSize(KDPACKETEXCP64, 152);
+/** Pointer to an exception record. */
+typedef KDPACKETEXCP64 *PKDPACKETEXCP64;
+/** Pointer to a const exception record. */
+typedef const KDPACKETEXCP64 *PCKDPACKETEXCP64;
+
+
+/**
+ * amd64 NT context structure.
+ */
+typedef struct NTCONTEXT64
+{
+ /** The P[1-6]Home members. */
+ uint64_t au64PHome[6];
+ /** Context flags indicating the valid bits, see NTCONTEXT_F_XXX. */
+ uint32_t fContext;
+ /** MXCSR register. */
+ uint32_t u32RegMxCsr;
+ /** CS selector. */
+ uint16_t u16SegCs;
+ /** DS selector. */
+ uint16_t u16SegDs;
+ /** ES selector. */
+ uint16_t u16SegEs;
+ /** FS selector. */
+ uint16_t u16SegFs;
+ /** GS selector. */
+ uint16_t u16SegGs;
+ /** SS selector. */
+ uint16_t u16SegSs;
+ /** EFlags register. */
+ uint32_t u32RegEflags;
+ /** DR0 register. */
+ uint64_t u64RegDr0;
+ /** DR1 register. */
+ uint64_t u64RegDr1;
+ /** DR2 register. */
+ uint64_t u64RegDr2;
+ /** DR3 register. */
+ uint64_t u64RegDr3;
+ /** DR6 register. */
+ uint64_t u64RegDr6;
+ /** DR7 register. */
+ uint64_t u64RegDr7;
+ /** RAX register. */
+ uint64_t u64RegRax;
+ /** RCX register. */
+ uint64_t u64RegRcx;
+ /** RDX register. */
+ uint64_t u64RegRdx;
+ /** RBX register. */
+ uint64_t u64RegRbx;
+ /** RSP register. */
+ uint64_t u64RegRsp;
+ /** RBP register. */
+ uint64_t u64RegRbp;
+ /** RSI register. */
+ uint64_t u64RegRsi;
+ /** RDI register. */
+ uint64_t u64RegRdi;
+ /** R8 register. */
+ uint64_t u64RegR8;
+ /** R9 register. */
+ uint64_t u64RegR9;
+ /** R10 register. */
+ uint64_t u64RegR10;
+ /** R11 register. */
+ uint64_t u64RegR11;
+ /** R12 register. */
+ uint64_t u64RegR12;
+ /** R13 register. */
+ uint64_t u64RegR13;
+ /** R14 register. */
+ uint64_t u64RegR14;
+ /** R15 register. */
+ uint64_t u64RegR15;
+ /** RIP register. */
+ uint64_t u64RegRip;
+ /** Extended floating point save area. */
+ X86FXSTATE FxSave;
+ /** AVX(?) vector registers. */
+ RTUINT128U aRegsVec[26];
+ /** Vector control register. */
+ uint64_t u64RegVecCtrl;
+ /** Debug control. */
+ uint64_t u64DbgCtrl;
+ /** @todo lbr */
+ uint64_t u64LastBrToRip;
+ uint64_t u64LastBrFromRip;
+ uint64_t u64LastExcpToRip;
+ uint64_t u64LastExcpFromRip;
+} NTCONTEXT64;
+AssertCompileSize(NTCONTEXT64, 1232);
+AssertCompileMemberOffset(NTCONTEXT64, FxSave, 0x100);
+AssertCompileMemberOffset(NTCONTEXT64, aRegsVec, 0x300);
+/** Pointer to an amd64 NT context. */
+typedef NTCONTEXT64 *PNTCONTEXT64;
+/** Pointer to a const amd64 NT context. */
+typedef const NTCONTEXT64 *PCNTCONTEXT64;
+
+
+/**
+ * 64bit [GI]DT descriptor.
+ */
+typedef struct NTKCONTEXTDESC64
+{
+ /** Alignment. */
+ uint16_t au16Alignment[3];
+ /** Limit. */
+ uint16_t u16Limit;
+ /** Base address. */
+ uint64_t u64PtrBase;
+} NTKCONTEXTDESC64;
+AssertCompileSize(NTKCONTEXTDESC64, 2 * 8);
+/** Pointer to a 64bit [GI]DT descriptor. */
+typedef NTKCONTEXTDESC64 *PNTKCONTEXTDESC64;
+/** Pointer to a const 64bit [GI]DT descriptor. */
+typedef const NTKCONTEXTDESC64 *PCNTKCONTEXTDESC64;
+
+
+/**
+ * Kernel context as queried by KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE
+ */
+typedef struct NTKCONTEXT64
+{
+ /** CR0 register. */
+ uint64_t u64RegCr0;
+ /** CR2 register. */
+ uint64_t u64RegCr2;
+ /** CR3 register. */
+ uint64_t u64RegCr3;
+ /** CR4 register. */
+ uint64_t u64RegCr4;
+ /** DR0 register. */
+ uint64_t u64RegDr0;
+ /** DR1 register. */
+ uint64_t u64RegDr1;
+ /** DR2 register. */
+ uint64_t u64RegDr2;
+ /** DR3 register. */
+ uint64_t u64RegDr3;
+ /** DR6 register. */
+ uint64_t u64RegDr6;
+ /** DR7 register. */
+ uint64_t u64RegDr7;
+ /** GDTR. */
+ NTKCONTEXTDESC64 Gdtr;
+ /** IDTR. */
+ NTKCONTEXTDESC64 Idtr;
+ /** TR register. */
+ uint16_t u16RegTr;
+ /** LDTR register. */
+ uint16_t u16RegLdtr;
+ /** MXCSR register. */
+ uint32_t u32RegMxCsr;
+ /** Debug control. */
+ uint64_t u64DbgCtrl;
+ /** @todo lbr */
+ uint64_t u64LastBrToRip;
+ uint64_t u64LastBrFromRip;
+ uint64_t u64LastExcpToRip;
+ uint64_t u64LastExcpFromRip;
+ /** CR8 register. */
+ uint64_t u64RegCr8;
+ /** GS base MSR register. */
+ uint64_t u64MsrGsBase;
+ /** Kernel GS base MSR register. */
+ uint64_t u64MsrKernelGsBase;
+ /** STAR MSR register. */
+ uint64_t u64MsrStar;
+ /** LSTAR MSR register. */
+ uint64_t u64MsrLstar;
+ /** CSTAR MSR register. */
+ uint64_t u64MsrCstar;
+ /** SFMASK MSR register. */
+ uint64_t u64MsrSfMask;
+ /** XCR0 register. */
+ uint64_t u64RegXcr0;
+ /** Standard context. */
+ NTCONTEXT64 Ctx;
+} NTKCONTEXT64;
+AssertCompileMemberOffset(NTKCONTEXT64, Ctx, 224);
+/** Pointer to an amd64 NT context. */
+typedef NTKCONTEXT64 *PNTKCONTEXT64;
+/** Pointer to a const amd64 NT context. */
+typedef const NTKCONTEXT64 *PCNTKCONTEXT64;
+
+
+/**
+ * 32bit context FPU save area.
+ */
+typedef struct NTCONTEXT32_FPU_SAVE_AREA
+{
+ uint32_t u32CtrlWord;
+ uint32_t u32StatusWord;
+ uint32_t u32TagWord;
+ uint32_t u32ErrorOff;
+ uint32_t u32ErrorSel;
+ uint32_t u32DataOff;
+ uint32_t u32DataSel;
+ X86FPUMMX aFpuRegs[8];
+ uint32_t u32Cr0Npx;
+} NTCONTEXT32_FPU_SAVE_AREA;
+/** Pointer to an 32bit context FPU save area. */
+typedef NTCONTEXT32_FPU_SAVE_AREA *PNTCONTEXT32_FPU_SAVE_AREA;
+/** Pointer to a const 32bit context FPU save area. */
+typedef const NTCONTEXT32_FPU_SAVE_AREA *PCNTCONTEXT32_FPU_SAVE_AREA;
+
+
+/**
+ * i386 NT context structure.
+ */
+typedef struct NTCONTEXT32
+{
+ /** Context flags indicating the valid bits, see NTCONTEXT_F_XXX. */
+ uint32_t fContext;
+ /** DR0 register. */
+ uint32_t u32RegDr0;
+ /** DR1 register. */
+ uint32_t u32RegDr1;
+ /** DR2 register. */
+ uint32_t u32RegDr2;
+ /** DR3 register. */
+ uint32_t u32RegDr3;
+ /** DR6 register. */
+ uint32_t u32RegDr6;
+ /** DR7 register. */
+ uint32_t u32RegDr7;
+ /** Floating point save area. */
+ NTCONTEXT32_FPU_SAVE_AREA FloatSave;
+ /** GS segment. */
+ uint32_t u32SegGs;
+ /** FS segment. */
+ uint32_t u32SegFs;
+ /** ES segment. */
+ uint32_t u32SegEs;
+ /** DS segment. */
+ uint32_t u32SegDs;
+ /** EDI register. */
+ uint32_t u32RegEdi;
+ /** ESI register. */
+ uint32_t u32RegEsi;
+ /** EBX register. */
+ uint32_t u32RegEbx;
+ /** EDX register. */
+ uint32_t u32RegEdx;
+ /** ECX register. */
+ uint32_t u32RegEcx;
+ /** EAX register. */
+ uint32_t u32RegEax;
+ /** EBP register. */
+ uint32_t u32RegEbp;
+ /** EIP register. */
+ uint32_t u32RegEip;
+ /** CS segment. */
+ uint32_t u32SegCs;
+ /** EFLAGS register. */
+ uint32_t u32RegEflags;
+ /** ESP register. */
+ uint32_t u32RegEsp;
+ /** SS segment. */
+ uint32_t u32SegSs;
+ /** @todo Extended registers */
+ uint8_t abRegsExtended[512];
+} NTCONTEXT32;
+AssertCompileSize(NTCONTEXT32, 716);
+/** Pointer to an i386 NT context. */
+typedef NTCONTEXT32 *PNTCONTEXT32;
+/** Pointer to a const i386 NT context. */
+typedef const NTCONTEXT32 *PCNTCONTEXT32;
+
+
+/**
+ * 32bit [GI]DT descriptor.
+ */
+typedef struct NTKCONTEXTDESC32
+{
+ /** Alignment. */
+ uint16_t u16Alignment;
+ /** Limit. */
+ uint16_t u16Limit;
+ /** Base address. */
+ uint32_t u32PtrBase;
+} NTKCONTEXTDESC32;
+AssertCompileSize(NTKCONTEXTDESC32, 2 * 4);
+/** Pointer to an 32bit [GI]DT descriptor. */
+typedef NTKCONTEXTDESC32 *PNTKCONTEXTDESC32;
+/** Pointer to a const 32bit [GI]DT descriptor. */
+typedef const NTKCONTEXTDESC32 *PCNTKCONTEXTDESC32;
+
+
+/**
+ * 32bit Kernel context as queried by KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE
+ */
+typedef struct NTKCONTEXT32
+{
+ /** CR0 register. */
+ uint32_t u32RegCr0;
+ /** CR2 register. */
+ uint32_t u32RegCr2;
+ /** CR3 register. */
+ uint32_t u32RegCr3;
+ /** CR4 register. */
+ uint32_t u32RegCr4;
+ /** DR0 register. */
+ uint32_t u32RegDr0;
+ /** DR1 register. */
+ uint32_t u32RegDr1;
+ /** DR2 register. */
+ uint32_t u32RegDr2;
+ /** DR3 register. */
+ uint32_t u32RegDr3;
+ /** DR6 register. */
+ uint32_t u32RegDr6;
+ /** DR7 register. */
+ uint32_t u32RegDr7;
+ /** GDTR. */
+ NTKCONTEXTDESC32 Gdtr;
+ /** IDTR. */
+ NTKCONTEXTDESC32 Idtr;
+ /** TR register. */
+ uint16_t u16RegTr;
+ /** LDTR register. */
+ uint16_t u16RegLdtr;
+ /** Padding. */
+ uint8_t abPad[24];
+} NTKCONTEXT32;
+AssertCompileSize(NTKCONTEXT32, 84);
+/** Pointer to an i386 NT context. */
+typedef NTKCONTEXT32 *PNTKCONTEXT32;
+/** Pointer to a const i386 NT context. */
+typedef const NTKCONTEXT32 *PCNTKCONTEXT32;
+
+
+/** x86 context. */
+#define NTCONTEXT_F_X86 UINT32_C(0x00010000)
+/** AMD64 context. */
+#define NTCONTEXT_F_AMD64 UINT32_C(0x00100000)
+/** Control registers valid (CS, (R)SP, (R)IP, FLAGS and BP). */
+#define NTCONTEXT_F_CONTROL RT_BIT_32(0)
+/** Integer registers valid. */
+#define NTCONTEXT_F_INTEGER RT_BIT_32(1)
+/** Segment registers valid. */
+#define NTCONTEXT_F_SEGMENTS RT_BIT_32(2)
+/** Floating point registers valid. */
+#define NTCONTEXT_F_FLOATING_POINT RT_BIT_32(3)
+/** Debug registers valid. */
+#define NTCONTEXT_F_DEBUG RT_BIT_32(4)
+/** Extended registers valid (x86 only). */
+#define NTCONTEXT_F_EXTENDED RT_BIT_32(5)
+/** Full x86 context valid. */
+#define NTCONTEXT32_F_FULL (NTCONTEXT_F_X86 | NTCONTEXT_F_CONTROL | NTCONTEXT_F_INTEGER | NTCONTEXT_F_SEGMENTS)
+/** Full amd64 context valid. */
+#define NTCONTEXT64_F_FULL (NTCONTEXT_F_AMD64 | NTCONTEXT_F_CONTROL | NTCONTEXT_F_INTEGER | NTCONTEXT_F_SEGMENTS)
+
+
+/**
+ * 32bit exception record.
+ */
+typedef struct KDPACKETEXCP32
+{
+ /** The exception code identifying the excpetion. */
+ uint32_t u32ExcpCode;
+ /** Flags associated with the exception. */
+ uint32_t u32ExcpFlags;
+ /** Pointer to a chained exception record. */
+ uint32_t u32PtrExcpRecNested;
+ /** Address where the exception occurred. */
+ uint32_t u32PtrExcpAddr;
+ /** Number of parameters in the exception information array. */
+ uint32_t cExcpParms;
+ /** Exception parameters array. */
+ uint32_t au32ExcpParms[KDPACKETEXCP_PARMS_MAX];
+} KDPACKETEXCP32;
+AssertCompileSize(KDPACKETEXCP32, 80);
+/** Pointer to an exception record. */
+typedef KDPACKETEXCP32 *PKDPACKETEXCP32;
+/** Pointer to a const exception record. */
+typedef const KDPACKETEXCP32 *PCKDPACKETEXCP32;
+
+
+/** @name Exception codes.
+ * @{ */
+/** A breakpoint was hit. */
+#define KD_PACKET_EXCP_CODE_BKPT UINT32_C(0x80000003)
+/** An instruction was single stepped. */
+#define KD_PACKET_EXCP_CODE_SINGLE_STEP UINT32_C(0x80000004)
+/** @} */
+
+
+/** Maximum number of bytes in the instruction stream. */
+#define KD_PACKET_CTRL_REPORT_INSN_STREAM_MAX 16
+
+/**
+ * 64bit control report record.
+ */
+typedef struct KDPACKETCTRLREPORT64
+{
+ /** Value of DR6. */
+ uint64_t u64RegDr6;
+ /** Value of DR7. */
+ uint64_t u64RegDr7;
+ /** EFLAGS. */
+ uint32_t u32RegEflags;
+ /** Number of instruction bytes in the instruction stream. */
+ uint16_t cbInsnStream;
+ /** Report flags. */
+ uint16_t fFlags;
+ /** The instruction stream. */
+ uint8_t abInsn[KD_PACKET_CTRL_REPORT_INSN_STREAM_MAX];
+ /** CS selector. */
+ uint16_t u16SegCs;
+ /** DS selector. */
+ uint16_t u16SegDs;
+ /** ES selector. */
+ uint16_t u16SegEs;
+ /** FS selector. */
+ uint16_t u16SegFs;
+} KDPACKETCTRLREPORT64;
+AssertCompileSize(KDPACKETCTRLREPORT64, 2 * 8 + 4 + 2 * 2 + 16 + 4 * 2);
+/** Pointer to a control report record. */
+typedef KDPACKETCTRLREPORT64 *PKDPACKETCTRLREPORT64;
+/** Pointer to a const control report record. */
+typedef const KDPACKETCTRLREPORT64 *PCKDPACKETCTRLREPORT64;
+
+
+/**
+ * 64bit state change packet body.
+ */
+typedef struct KDPACKETSTATECHANGE64
+{
+ /** The new state. */
+ uint32_t u32StateNew;
+ /** The processor level. */
+ uint16_t u16CpuLvl;
+ /** The processor ID generating the state change. */
+ uint16_t idCpu;
+ /** Number of processors in the system. */
+ uint32_t cCpus;
+ /** Alignment. */
+ uint32_t u32Alignment;
+ /** The thread ID currently executing when the state change occurred. */
+ uint64_t idThread;
+ /** Program counter of the thread. */
+ uint64_t u64RipThread;
+ /** Data based on the state. */
+ union
+ {
+ /** Exception occurred data. */
+ struct
+ {
+ /** The exception record. */
+ KDPACKETEXCP64 ExcpRec;
+ /** First chance(?). */
+ uint32_t u32FirstChance;
+ } Exception;
+ } u;
+ /** The control report */
+ union
+ {
+ /** AMD64 control report. */
+ KDPACKETCTRLREPORT64 Amd64;
+ } uCtrlReport;
+} KDPACKETSTATECHANGE64;
+//AssertCompileSize(KDPACKETSTATECHANGE64, 4 + 2 * 2 + 2 * 4 + 2 * 8 + sizeof(KDPACKETEXCP64) + 4 + sizeof(KDPACKETCTRLREPORT64));
+/** Pointer to a 64bit state change packet body. */
+typedef KDPACKETSTATECHANGE64 *PKDPACKETSTATECHANGE64;
+/** Pointer to a const 64bit state change packet body. */
+typedef const KDPACKETSTATECHANGE64 *PCKDPACKETSTATECHANGE64;
+
+
+/** @name State change state types.
+ * @{ */
+/** Minimum state change type. */
+#define KD_PACKET_STATE_CHANGE_MIN UINT32_C(0x00003030)
+/** An exception occured. */
+#define KD_PACKET_STATE_CHANGE_EXCEPTION KD_PACKET_STATE_CHANGE_MIN
+/** Symbols were loaded(?). */
+#define KD_PACKET_STATE_CHANGE_LOAD_SYMBOLS UINT32_C(0x00003031)
+/** Command string (custom command was executed?). */
+#define KD_PACKET_STATE_CHANGE_CMD_STRING UINT32_C(0x00003032)
+/** Maximum state change type (exclusive). */
+#define KD_PACKET_STATE_CHANGE_MAX UINT32_C(0x00003033)
+/** @} */
+
+
+/**
+ * Debug I/O payload.
+ */
+typedef struct KDPACKETDEBUGIO
+{
+ /** Debug I/O payload type (KD_PACKET_DEBUG_IO_STRING). */
+ uint32_t u32Type;
+ /** The processor level. */
+ uint16_t u16CpuLvl;
+ /** The processor ID generating this packet. */
+ uint16_t idCpu;
+ /** Type dependent data. */
+ union
+ {
+ /** Debug string sent. */
+ struct
+ {
+ /** Length of the string following in bytes. */
+ uint32_t cbStr;
+ /** Some padding it looks like. */
+ uint32_t u32Pad;
+ } Str;
+ /** Debug prompt. */
+ struct
+ {
+ /** Length of prompt. */
+ uint32_t cbPrompt;
+ /** Size of the string returned on success. */
+ uint32_t cbReturn;
+ } Prompt;
+ } u;
+} KDPACKETDEBUGIO;
+AssertCompileSize(KDPACKETDEBUGIO, 16);
+/** Pointer to a Debug I/O payload. */
+typedef KDPACKETDEBUGIO *PKDPACKETDEBUGIO;
+/** Pointer to a const Debug I/O payload. */
+typedef const KDPACKETDEBUGIO *PCKDPACKETDEBUGIO;
+
+
+/** @name Debug I/O types.
+ * @{ */
+/** Debug string output (usually DbgPrint() and friends). */
+#define KD_PACKET_DEBUG_IO_STRING UINT32_C(0x00003230)
+/** Get debug string (DbgPrompt()). */
+#define KD_PACKET_DEBUG_IO_GET_STRING UINT32_C(0x00003231)
+/** @} */
+
+
+/**
+ * 64bit get version manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_GETVERSION64
+{
+ /** Major version. */
+ uint16_t u16VersMaj;
+ /** Minor version. */
+ uint16_t u16VersMin;
+ /** Protocol version. */
+ uint8_t u8VersProtocol;
+ /** KD secondary version. */
+ uint8_t u8VersKdSecondary;
+ /** Flags. */
+ uint16_t fFlags;
+ /** Machine type. */
+ uint16_t u16MachineType;
+ /** Maximum packet type. */
+ uint8_t u8MaxPktType;
+ /** Maximum state change */
+ uint8_t u8MaxStateChange;
+ /** Maximum manipulate request ID. */
+ uint8_t u8MaxManipulate;
+ /** Some simulation flag. */
+ uint8_t u8Simulation;
+ /** Padding. */
+ uint16_t u16Padding;
+ /** Kernel base. */
+ uint64_t u64PtrKernBase;
+ /** Pointer of the loaded module list head. */
+ uint64_t u64PtrPsLoadedModuleList;
+ /** Pointer of the debugger data list. */
+ uint64_t u64PtrDebuggerDataList;
+} KDPACKETMANIPULATE_GETVERSION64;
+AssertCompileSize(KDPACKETMANIPULATE_GETVERSION64, 40);
+/** Pointer to a 64bit get version manipulate payload. */
+typedef KDPACKETMANIPULATE_GETVERSION64 *PKDPACKETMANIPULATE_GETVERSION64;
+/** Pointer to a const 64bit get version manipulate payload. */
+typedef const KDPACKETMANIPULATE_GETVERSION64 *PCKDPACKETMANIPULATE_GETVERSION64;
+
+
+/** @name Get version flags.
+ * @{ */
+/** Flag whether this is a multi processor kernel. */
+#define KD_PACKET_MANIPULATE64_GET_VERSION_F_MP RT_BIT_32(0)
+/** Flag whether the pointer is 64bit. */
+#define KD_PACKET_MANIPULATE64_GET_VERSION_F_PTR64 RT_BIT_32(2)
+/** @} */
+
+
+/**
+ * 64bit memory transfer manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_XFERMEM64
+{
+ /** Target base address. */
+ uint64_t u64PtrTarget;
+ /** Requested number of bytes to transfer*/
+ uint32_t cbXferReq;
+ /** Number of bytes actually transferred (response). */
+ uint32_t cbXfered;
+ /** Some padding?. */
+ uint64_t au64Pad[3];
+} KDPACKETMANIPULATE_XFERMEM64;
+AssertCompileSize(KDPACKETMANIPULATE_XFERMEM64, 40);
+/** Pointer to a 64bit memory transfer manipulate payload. */
+typedef KDPACKETMANIPULATE_XFERMEM64 *PKDPACKETMANIPULATE_XFERMEM64;
+/** Pointer to a const 64bit memory transfer manipulate payload. */
+typedef const KDPACKETMANIPULATE_XFERMEM64 *PCKDPACKETMANIPULATE_XFERMEM64;
+
+
+/**
+ * 64bit control space transfer manipulate payload.
+ *
+ * @note Same layout as the memory transfer but the pointer has a different meaning so
+ * we moved it into a separate request structure.
+ */
+typedef struct KDPACKETMANIPULATE_XFERCTRLSPACE64
+{
+ /** Identifier of the item to transfer in the control space. */
+ uint64_t u64IdXfer;
+ /** Requested number of bytes to transfer*/
+ uint32_t cbXferReq;
+ /** Number of bytes actually transferred (response). */
+ uint32_t cbXfered;
+ /** Some padding?. */
+ uint64_t au64Pad[3];
+} KDPACKETMANIPULATE_XFERCTRLSPACE64;
+AssertCompileSize(KDPACKETMANIPULATE_XFERCTRLSPACE64, 40);
+/** Pointer to a 64bit memory transfer manipulate payload. */
+typedef KDPACKETMANIPULATE_XFERCTRLSPACE64 *PKDPACKETMANIPULATE_XFERCTRLSPACE64;
+/** Pointer to a const 64bit memory transfer manipulate payload. */
+typedef const KDPACKETMANIPULATE_XFERCTRLSPACE64 *PCKDPACKETMANIPULATE_XFERCTRLSPACE64;
+
+
+/** @name Known control space identifiers.
+ * @{ */
+/** Read/Write KPCR address. */
+#define KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KPCR UINT64_C(0)
+/** Read/Write KPCRB address. */
+#define KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KPCRB UINT64_C(1)
+/** Read/Write Kernel context. */
+#define KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KCTX UINT64_C(2)
+/** Read/Write current kernel thread. */
+#define KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KTHRD UINT64_C(3)
+/** @} */
+
+
+/**
+ * 64bit restore breakpoint manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_RESTOREBKPT64
+{
+ /** The breakpoint handle to restore. */
+ uint32_t u32HndBkpt;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[36];
+} KDPACKETMANIPULATE_RESTOREBKPT64;
+AssertCompileSize(KDPACKETMANIPULATE_RESTOREBKPT64, 40);
+/** Pointer to a 64bit restore breakpoint manipulate payload. */
+typedef KDPACKETMANIPULATE_RESTOREBKPT64 *PKDPACKETMANIPULATE_RESTOREBKPT64;
+/** Pointer to a const 64bit restore breakpoint manipulate payload. */
+typedef const KDPACKETMANIPULATE_RESTOREBKPT64 *PCKDPACKETMANIPULATE_RESTOREBKPT64;
+
+
+/**
+ * 64bit write breakpoint manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_WRITEBKPT64
+{
+ /** Where to write the breakpoint. */
+ uint64_t u64PtrBkpt;
+ /** The breakpoint handle returned in the response. */
+ uint32_t u32HndBkpt;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[28];
+} KDPACKETMANIPULATE_WRITEBKPT64;
+AssertCompileSize(KDPACKETMANIPULATE_WRITEBKPT64, 40);
+/** Pointer to a 64bit write breakpoint manipulate payload. */
+typedef KDPACKETMANIPULATE_WRITEBKPT64 *PKDPACKETMANIPULATE_WRITEBKPT64;
+/** Pointer to a const 64bit write breakpoint manipulate payload. */
+typedef const KDPACKETMANIPULATE_WRITEBKPT64 *PCKDPACKETMANIPULATE_WRITEBKPT64;
+
+
+/**
+ * Context extended manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_CONTEXTEX
+{
+ /** Where to start copying the context. */
+ uint32_t offStart;
+ /** Number of bytes to transfer. */
+ uint32_t cbXfer;
+ /** Number of bytes actually transfered. */
+ uint32_t cbXfered;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[28];
+} KDPACKETMANIPULATE_CONTEXTEX;
+AssertCompileSize(KDPACKETMANIPULATE_CONTEXTEX, 40);
+/** Pointer to a context extended manipulate payload. */
+typedef KDPACKETMANIPULATE_CONTEXTEX *PKDPACKETMANIPULATE_CONTEXTEX;
+/** Pointer to a const context extended manipulate payload. */
+typedef const KDPACKETMANIPULATE_CONTEXTEX *PCKDPACKETMANIPULATE_CONTEXTEX;
+
+
+/**
+ * Continue manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_CONTINUE
+{
+ /** Continue (status?). */
+ uint32_t u32NtContSts;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[36];
+} KDPACKETMANIPULATE_CONTINUE;
+AssertCompileSize(KDPACKETMANIPULATE_CONTINUE, 40);
+/** Pointer to a continue manipulate payload. */
+typedef KDPACKETMANIPULATE_CONTINUE *PKDPACKETMANIPULATE_CONTINUE;
+/** Pointer to a const continue manipulate payload. */
+typedef const KDPACKETMANIPULATE_CONTINUE *PCKDPACKETMANIPULATE_CONTINUE;
+
+
+/**
+ * Continue 2 manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_CONTINUE2
+{
+ /** Continue (status?). */
+ uint32_t u32NtContSts;
+ /** Trace flag. */
+ uint32_t fTrace;
+ /** Bitsize dependent data. */
+ union
+ {
+ /** 32bit. */
+ struct
+ {
+ /** DR7 value to continue with. */
+ uint32_t u32RegDr7;
+ /** @todo (?) */
+ uint32_t u32SymCurStart;
+ uint32_t u32SymCurEnd;
+ } x86;
+ /** 64bit. */
+ struct
+ {
+ /** DR7 value to continue with. */
+ uint64_t u64RegDr7;
+ /** @todo (?) */
+ uint64_t u64SymCurStart;
+ uint64_t u64SymCurEnd;
+ } amd64;
+ } u;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[8];
+} KDPACKETMANIPULATE_CONTINUE2;
+AssertCompileSize(KDPACKETMANIPULATE_CONTINUE2, 40);
+/** Pointer to a continue 2 manipulate payload. */
+typedef KDPACKETMANIPULATE_CONTINUE2 *PKDPACKETMANIPULATE_CONTINUE2;
+/** Pointer to a const continue 2 manipulate payload. */
+typedef const KDPACKETMANIPULATE_CONTINUE2 *PCKDPACKETMANIPULATE_CONTINUE2;
+
+
+/**
+ * Set context manipulate payload.
+ */
+typedef struct KDPACKETMANIPULATE_SETCONTEXT
+{
+ /** Continue (status?). */
+ uint32_t u32CtxFlags;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[36];
+} KDPACKETMANIPULATE_SETCONTEXT;
+AssertCompileSize(KDPACKETMANIPULATE_SETCONTEXT, 40);
+/** Pointer to a set context manipulate payload. */
+typedef KDPACKETMANIPULATE_SETCONTEXT *PKDPACKETMANIPULATE_SETCONTEXT;
+/** Pointer to a const set context manipulate payload. */
+typedef const KDPACKETMANIPULATE_SETCONTEXT *PCKDPACKETMANIPULATE_SETCONTEXT;
+
+
+/**
+ * Query memory properties payload.
+ */
+typedef struct KDPACKETMANIPULATE_QUERYMEMORY
+{
+ /** The address to query the properties for. */
+ uint64_t u64GCPtr;
+ /** Reserved. */
+ uint64_t u64Rsvd;
+ /** Address space type on return. */
+ uint32_t u32AddrSpace;
+ /** Protection flags. */
+ uint32_t u32Flags;
+ /** Blows up the request to the required size. */
+ uint8_t abPad[16];
+} KDPACKETMANIPULATE_QUERYMEMORY;
+AssertCompileSize(KDPACKETMANIPULATE_QUERYMEMORY, 40);
+/** Pointer to a query memory properties payload. */
+typedef KDPACKETMANIPULATE_QUERYMEMORY *PKDPACKETMANIPULATE_QUERYMEMORY;
+/** Pointer to a const query memory properties payload. */
+typedef const KDPACKETMANIPULATE_QUERYMEMORY *PCKDPACKETMANIPULATE_QUERYMEMORY;
+
+
+/** @name Query memory address space identifiers.
+ * @{ */
+/** Process memory space. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_SPACE_PROCESS UINT32_C(0)
+/** Session memory space. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_SPACE_SESSION UINT32_C(1)
+/** Kernel memory space. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_SPACE_KERNEL UINT32_C(2)
+/** @} */
+
+
+/** @name Query memory address protection flags.
+ * @{ */
+/** Readable. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_READ RT_BIT_32(0)
+/** Writable. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_WRITE RT_BIT_32(1)
+/** Executable. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_EXEC RT_BIT_32(2)
+/** Fixed address. */
+#define KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_FIXED RT_BIT_32(3)
+/** @} */
+
+
+/**
+ * Search memory payload.
+ */
+typedef struct KDPACKETMANIPULATE_SEARCHMEMORY
+{
+ /** The address to start searching at on input, found address on output. */
+ uint64_t u64GCPtr;
+ /** Number of bytes to search. */
+ uint64_t cbSearch;
+ /** Length of the pattern to search for following the payload. */
+ uint32_t cbPattern;
+ /** Padding to the required size. */
+ uint32_t au32Pad[5];
+} KDPACKETMANIPULATE_SEARCHMEMORY;
+AssertCompileSize(KDPACKETMANIPULATE_SEARCHMEMORY, 40);
+/** Pointer to a search memory properties payload. */
+typedef KDPACKETMANIPULATE_SEARCHMEMORY *PKDPACKETMANIPULATE_SEARCHMEMORY;
+/** Pointer to a const search memory properties payload. */
+typedef const KDPACKETMANIPULATE_SEARCHMEMORY *PCKDPACKETMANIPULATE_SEARCHMEMORY;
+
+
+/**
+ * Manipulate request packet header (Same for 32bit and 64bit).
+ */
+typedef struct KDPACKETMANIPULATEHDR
+{
+ /** The request to execute. */
+ uint32_t idReq;
+ /** The processor level to execute the request on. */
+ uint16_t u16CpuLvl;
+ /** The processor ID to execute the request on. */
+ uint16_t idCpu;
+ /** Return status code. */
+ uint32_t u32NtStatus;
+ /** Alignment. */
+ uint32_t u32Alignment;
+} KDPACKETMANIPULATEHDR;
+AssertCompileSize(KDPACKETMANIPULATEHDR, 3 * 4 + 2 * 2);
+/** Pointer to a manipulate request packet header. */
+typedef KDPACKETMANIPULATEHDR *PKDPACKETMANIPULATEHDR;
+/** Pointer to a const manipulate request packet header. */
+typedef const KDPACKETMANIPULATEHDR *PCPKDPACKETMANIPULATEHDR;
+
+
+/**
+ * 64bit manipulate state request packet.
+ */
+typedef struct KDPACKETMANIPULATE64
+{
+ /** Header. */
+ KDPACKETMANIPULATEHDR Hdr;
+ /** Request payloads. */
+ union
+ {
+ /** Get Version. */
+ KDPACKETMANIPULATE_GETVERSION64 GetVersion;
+ /** Read/Write memory. */
+ KDPACKETMANIPULATE_XFERMEM64 XferMem;
+ /** Continue. */
+ KDPACKETMANIPULATE_CONTINUE Continue;
+ /** Continue2. */
+ KDPACKETMANIPULATE_CONTINUE2 Continue2;
+ /** Set context. */
+ KDPACKETMANIPULATE_SETCONTEXT SetContext;
+ /** Read/Write control space. */
+ KDPACKETMANIPULATE_XFERCTRLSPACE64 XferCtrlSpace;
+ /** Restore breakpoint. */
+ KDPACKETMANIPULATE_RESTOREBKPT64 RestoreBkpt;
+ /** Write breakpoint. */
+ KDPACKETMANIPULATE_WRITEBKPT64 WriteBkpt;
+ /** Context extended. */
+ KDPACKETMANIPULATE_CONTEXTEX ContextEx;
+ /** Query memory. */
+ KDPACKETMANIPULATE_QUERYMEMORY QueryMemory;
+ /** Search memory. */
+ KDPACKETMANIPULATE_SEARCHMEMORY SearchMemory;
+ } u;
+} KDPACKETMANIPULATE64;
+AssertCompileSize(KDPACKETMANIPULATE64, 16 + 40);
+/** Pointer to a 64bit manipulate state request packet. */
+typedef KDPACKETMANIPULATE64 *PKDPACKETMANIPULATE64;
+/** Pointer to a const 64bit manipulate state request packet. */
+typedef const KDPACKETMANIPULATE64 *PCKDPACKETMANIPULATE64;
+
+/** @name Manipulate requests.
+ * @{ */
+/** Minimum available request. */
+#define KD_PACKET_MANIPULATE_REQ_MIN UINT32_C(0x00003130)
+/** Read virtual memory request. */
+#define KD_PACKET_MANIPULATE_REQ_READ_VIRT_MEM KD_PACKET_MANIPULATE_REQ_MIN
+/** Write virtual memory request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_VIRT_MEM UINT32_C(0x00003131)
+/** Get context request. */
+#define KD_PACKET_MANIPULATE_REQ_GET_CONTEXT UINT32_C(0x00003132)
+/** Set context request. */
+#define KD_PACKET_MANIPULATE_REQ_SET_CONTEXT UINT32_C(0x00003133)
+/** Write breakpoint request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_BKPT UINT32_C(0x00003134)
+/** Restore breakpoint request. */
+#define KD_PACKET_MANIPULATE_REQ_RESTORE_BKPT UINT32_C(0x00003135)
+/** Continue request. */
+#define KD_PACKET_MANIPULATE_REQ_CONTINUE UINT32_C(0x00003136)
+/** Read control space request. */
+#define KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE UINT32_C(0x00003137)
+/** Write control space request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_CTRL_SPACE UINT32_C(0x00003138)
+/** Read I/O space request. */
+#define KD_PACKET_MANIPULATE_REQ_READ_IO_SPACE UINT32_C(0x00003139)
+/** Write I/O space request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_IO_SPACE UINT32_C(0x0000313a)
+/** Reboot request. */
+#define KD_PACKET_MANIPULATE_REQ_REBOOT UINT32_C(0x0000313b)
+/** continue 2nd version request. */
+#define KD_PACKET_MANIPULATE_REQ_CONTINUE2 UINT32_C(0x0000313c)
+/** Read physical memory request. */
+#define KD_PACKET_MANIPULATE_REQ_READ_PHYS_MEM UINT32_C(0x0000313d)
+/** Write physical memory request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_PHYS_MEM UINT32_C(0x0000313e)
+/** Query special calls request. */
+#define KD_PACKET_MANIPULATE_REQ_QUERY_SPEC_CALLS UINT32_C(0x0000313f)
+/** Set special calls request. */
+#define KD_PACKET_MANIPULATE_REQ_SET_SPEC_CALLS UINT32_C(0x00003140)
+/** Clear special calls request. */
+#define KD_PACKET_MANIPULATE_REQ_CLEAR_SPEC_CALLS UINT32_C(0x00003141)
+/** Set internal breakpoint request. */
+#define KD_PACKET_MANIPULATE_REQ_SET_INTERNAL_BKPT UINT32_C(0x00003142)
+/** Get internal breakpoint request. */
+#define KD_PACKET_MANIPULATE_REQ_GET_INTERNAL_BKPT UINT32_C(0x00003143)
+/** Read I/O space extended request. */
+#define KD_PACKET_MANIPULATE_REQ_READ_IO_SPACE_EX UINT32_C(0x00003144)
+/** Write I/O space extended request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_IO_SPACE_EX UINT32_C(0x00003145)
+/** Get version request. */
+#define KD_PACKET_MANIPULATE_REQ_GET_VERSION UINT32_C(0x00003146)
+/** Write breakpoint extended request. */
+#define KD_PACKET_MANIPULATE_REQ_WRITE_BKPT_EX UINT32_C(0x00003147)
+/** Restore breakpoint extended request. */
+#define KD_PACKET_MANIPULATE_REQ_RESTORE_BKPT_EX UINT32_C(0x00003148)
+/** Cause a bugcheck request. */
+#define KD_PACKET_MANIPULATE_REQ_CAUSE_BUGCHECK UINT32_C(0x00003149)
+/** Cause a bugcheck request. */
+#define KD_PACKET_MANIPULATE_REQ_SWITCH_PROCESSOR UINT32_C(0x00003150)
+/** @todo 0x3151-0x3155 */
+/** Search memory for a pattern request. */
+#define KD_PACKET_MANIPULATE_REQ_SEARCH_MEMORY UINT32_C(0x00003156)
+/** @todo 0x3157-0x3159 */
+/** Clear all internal breakpoints request. */
+#define KD_PACKET_MANIPULATE_REQ_CLEAR_ALL_INTERNAL_BKPT UINT32_C(0x0000315a)
+/** Fill memory. */
+#define KD_PACKET_MANIPULATE_REQ_FILL_MEMORY UINT32_C(0x0000315b)
+/** Query memory properties. */
+#define KD_PACKET_MANIPULATE_REQ_QUERY_MEMORY UINT32_C(0x0000315c)
+/** @todo 0x315d, 0x315e */
+/** Get context extended request. */
+#define KD_PACKET_MANIPULATE_REQ_GET_CONTEXT_EX UINT32_C(0x0000315f)
+/** @todo 0x3160 */
+/** Maximum available request (exclusive). */
+#define KD_PACKET_MANIPULATE_REQ_MAX UINT32_C(0x00003161)
+/** @} */
+
+/**
+ * KD stub receive state.
+ */
+typedef enum KDRECVSTATE
+{
+ /** Invalid state. */
+ KDRECVSTATE_INVALID = 0,
+ /** Receiving the first byte of the packet header. */
+ KDRECVSTATE_PACKET_HDR_FIRST_BYTE,
+ /** Receiving the second byte of the packet header. */
+ KDRECVSTATE_PACKET_HDR_SECOND_BYTE,
+ /** Receiving the header. */
+ KDRECVSTATE_PACKET_HDR,
+ /** Receiving the packet body. */
+ KDRECVSTATE_PACKET_BODY,
+ /** Receiving the trailing byte. */
+ KDRECVSTATE_PACKET_TRAILER,
+ /** Blow up the enum to 32bits for easier alignment of members in structs. */
+ KDRECVSTATE_32BIT_HACK = 0x7fffffff
+} KDRECVSTATE;
+
+
+/**
+ * KD emulated hardware breakpoint.
+ */
+typedef struct KDCTXHWBP
+{
+ /** The DBGF breakpoint handle if active, NIL_DBGFBP if not active. */
+ DBGFBP hDbgfBp;
+ /** The linear address of the breakpoint if active. */
+ RTGCPTR GCPtrBp;
+ /** Access type of the breakpoint, see X86_DR7_RW_*. */
+ uint8_t fAcc;
+ /** Length flags of the breakpoint. */
+ uint8_t fLen;
+ /** Flag whether it is a local breakpoint. */
+ bool fLocal;
+ /** Flag whether it is a global breakpoint. */
+ bool fGlobal;
+ /** Flag whether the breakpoint has triggered since the last time of the reset. */
+ bool fTriggered;
+} KDCTXHWBP;
+/** Pointer to an emulated hardware breakpoint. */
+typedef KDCTXHWBP *PKDCTXHWBP;
+/** Pointer to a const emulated hardware breakpoint. */
+typedef const KDCTXHWBP *PCKDCTXHWBP;
+
+
+/**
+ * KD context data.
+ */
+typedef struct KDCTX
+{
+ /** Internal debugger console data. */
+ DBGC Dbgc;
+ /** Number of bytes received left for the current state. */
+ size_t cbRecvLeft;
+ /** Pointer where to write the next received data. */
+ uint8_t *pbRecv;
+ /** The current state when receiving a new packet. */
+ KDRECVSTATE enmState;
+ /** The timeout waiting for new data. */
+ RTMSINTERVAL msRecvTimeout;
+ /** Timestamp when we last received data from the remote end. */
+ uint64_t tsRecvLast;
+ /** Packet header being received. */
+ union
+ {
+ KDPACKETHDR Fields;
+ uint8_t ab[16];
+ } PktHdr;
+ /** The next packet ID to send. */
+ uint32_t idPktNext;
+ /** Offset into the body receive buffer. */
+ size_t offBodyRecv;
+ /** Body data. */
+ uint8_t abBody[_4K];
+ /** The trailer byte storage. */
+ uint8_t bTrailer;
+ /** Flag whether a breakin packet was received since the last time it was reset. */
+ bool fBreakinRecv;
+ /** Flag whether we entered the native VBox hypervisor through a bugcheck request. */
+ bool fInVBoxDbg;
+
+ /** Emulated hardware breakpoint handling. */
+ KDCTXHWBP aHwBp[4];
+ /** Flag whether a single step completed since last time this was cleared. */
+ bool fSingleStepped;
+
+ /** Pointer to the OS digger WinNt interface if a matching guest was detected. */
+ PDBGFOSIWINNT pIfWinNt;
+ /** Flag whether the detected guest is 32bit (false if 64bit). */
+ bool f32Bit;
+} KDCTX;
+/** Pointer to the KD context data. */
+typedef KDCTX *PKDCTX;
+/** Pointer to const KD context data. */
+typedef const KDCTX *PCKDCTX;
+/** Pointer to a KD context data pointer. */
+typedef PKDCTX *PPKDCTX;
+
+
+/**
+ * Register mapping descriptor.
+ */
+typedef struct KDREGDESC
+{
+ /** The DBGF register enum. */
+ DBGFREG enmReg;
+ /** Register width. */
+ DBGFREGVALTYPE enmValType;
+ /** The offset into the context structure where the value ends up. */
+ uintptr_t offReg;
+} KDREGDESC;
+/** Pointer to a register mapping structure. */
+typedef KDREGDESC *PKDREGDESC;
+/** Pointer to a const register mapping structure. */
+typedef const KDREGDESC *PCKDREGDESC;
+
+
+/** Creates a possibly sign extended guest context pointer which is required for 32bit targets. */
+#define KD_PTR_CREATE(a_pThis, a_GCPtr) ((a_pThis)->f32Bit && ((a_GCPtr) & RT_BIT_32(31)) ? (a_GCPtr) | UINT64_C(0xffffffff00000000) : (a_GCPtr))
+/** Returns the value of a possibly sign extended guest context pointer received for 32bit targets. */
+#define KD_PTR_GET(a_pThis, a_GCPtr) ((a_pThis)->f32Bit ? (a_GCPtr) & ~UINT64_C(0xffffffff00000000) : (a_GCPtr))
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** 64bit control register set. */
+static const KDREGDESC g_aRegsCtrl64[] =
+{
+ { DBGFREG_CS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, u16SegCs) },
+ { DBGFREG_SS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, u16SegSs) },
+ { DBGFREG_RIP, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRip) },
+ { DBGFREG_RSP, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRsp) },
+ { DBGFREG_RBP, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRbp) },
+ { DBGFREG_EFLAGS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT64, u32RegEflags) }
+};
+
+
+/** 64bit integer register set. */
+static const KDREGDESC g_aRegsInt64[] =
+{
+ { DBGFREG_RAX, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRax) },
+ { DBGFREG_RCX, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRcx) },
+ { DBGFREG_RDX, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRdx) },
+ { DBGFREG_RBX, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRbx) },
+ { DBGFREG_RSI, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRsi) },
+ { DBGFREG_RDI, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegRdi) },
+ { DBGFREG_R8, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR8) },
+ { DBGFREG_R9, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR9) },
+ { DBGFREG_R10, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR10) },
+ { DBGFREG_R11, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR11) },
+ { DBGFREG_R12, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR12) },
+ { DBGFREG_R13, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR13) },
+ { DBGFREG_R14, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR14) },
+ { DBGFREG_R15, DBGFREGVALTYPE_U64, RT_UOFFSETOF(NTCONTEXT64, u64RegR15) }
+};
+
+
+/** 64bit segments register set. */
+static const KDREGDESC g_aRegsSegs64[] =
+{
+ { DBGFREG_DS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, u16SegDs) },
+ { DBGFREG_ES, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, u16SegEs) },
+ { DBGFREG_FS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, u16SegFs) },
+ { DBGFREG_GS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, u16SegGs) }
+};
+
+
+/** 64bit floating point register set. */
+static const KDREGDESC g_aRegsFx64[] =
+{
+ { DBGFREG_FCW, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, FxSave.FCW) },
+ { DBGFREG_FSW, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, FxSave.FSW) },
+ { DBGFREG_FTW, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, FxSave.FTW) },
+ { DBGFREG_FOP, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, FxSave.FOP) },
+ { DBGFREG_FPUIP, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT64, FxSave.FPUIP) },
+ /// @todo Fails on Solaris { DBGFREG_FPUCS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, FxSave.CS) },
+ { DBGFREG_FPUDP, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT64, FxSave.FPUDP) },
+ /// @todo Fails on Solaris { DBGFREG_FPUDS, DBGFREGVALTYPE_U16, RT_UOFFSETOF(NTCONTEXT64, FxSave.DS) },
+ { DBGFREG_MXCSR, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT64, FxSave.MXCSR) },
+ { DBGFREG_MXCSR_MASK, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT64, FxSave.MXCSR_MASK) },
+ { DBGFREG_ST0, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[0]) },
+ { DBGFREG_ST1, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[1]) },
+ { DBGFREG_ST2, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[2]) },
+ { DBGFREG_ST3, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[3]) },
+ { DBGFREG_ST4, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[4]) },
+ { DBGFREG_ST5, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[5]) },
+ { DBGFREG_ST6, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[6]) },
+ { DBGFREG_ST7, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT64, FxSave.aRegs[7]) },
+ { DBGFREG_XMM0, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[0]) },
+ { DBGFREG_XMM1, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[1]) },
+ { DBGFREG_XMM2, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[2]) },
+ { DBGFREG_XMM3, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[3]) },
+ { DBGFREG_XMM4, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[4]) },
+ { DBGFREG_XMM5, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[5]) },
+ { DBGFREG_XMM6, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[6]) },
+ { DBGFREG_XMM7, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[7]) },
+ { DBGFREG_XMM8, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[8]) },
+ { DBGFREG_XMM9, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[9]) },
+ { DBGFREG_XMM10, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[10]) },
+ { DBGFREG_XMM11, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[11]) },
+ { DBGFREG_XMM12, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[12]) },
+ { DBGFREG_XMM13, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[13]) },
+ { DBGFREG_XMM14, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[14]) },
+ { DBGFREG_XMM15, DBGFREGVALTYPE_U128, RT_UOFFSETOF(NTCONTEXT64, FxSave.aXMM[15]) }
+};
+
+
+/** 32bit control register set. */
+static const KDREGDESC g_aRegsCtrl32[] =
+{
+ { DBGFREG_CS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32SegCs) },
+ { DBGFREG_SS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32SegSs) },
+ { DBGFREG_EIP, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEip) },
+ { DBGFREG_ESP, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEsp) },
+ { DBGFREG_EBP, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEbp) },
+ { DBGFREG_EFLAGS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEflags) }
+};
+
+
+/** 32bit integer register set. */
+static const KDREGDESC g_aRegsInt32[] =
+{
+ { DBGFREG_EAX, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEax) },
+ { DBGFREG_ECX, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEcx) },
+ { DBGFREG_EDX, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEdx) },
+ { DBGFREG_EBX, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEbx) },
+ { DBGFREG_ESI, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEsi) },
+ { DBGFREG_EDI, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegEdi) }
+};
+
+
+/** 32bit segments register set. */
+static const KDREGDESC g_aRegsSegs32[] =
+{
+ { DBGFREG_DS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32SegDs) },
+ { DBGFREG_ES, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32SegEs) },
+ { DBGFREG_FS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32SegFs) },
+ { DBGFREG_GS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32SegGs) }
+};
+
+
+/** 32bit debug register set. */
+static const KDREGDESC g_aRegsDbg32[] =
+{
+ { DBGFREG_DR0, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegDr0) },
+ { DBGFREG_DR1, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegDr1) },
+ { DBGFREG_DR2, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegDr2) },
+ { DBGFREG_DR3, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegDr3) },
+ { DBGFREG_DR6, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegDr6) },
+ { DBGFREG_DR7, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, u32RegDr7) }
+};
+
+
+/** 32bit floating point register set. */
+static const KDREGDESC g_aRegsFx32[] =
+{
+ { DBGFREG_FCW, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32CtrlWord) },
+ { DBGFREG_FSW, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32StatusWord)},
+ { DBGFREG_FTW, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32TagWord) },
+ { DBGFREG_FCW, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32CtrlWord) },
+ { DBGFREG_FPUIP, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32ErrorOff) },
+ { DBGFREG_FPUCS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32ErrorSel) },
+ { DBGFREG_FPUDS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32DataOff) },
+ { DBGFREG_FPUDS, DBGFREGVALTYPE_U32, RT_UOFFSETOF(NTCONTEXT32, FloatSave.u32DataSel) },
+ { DBGFREG_ST0, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[0]) },
+ { DBGFREG_ST1, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[1]) },
+ { DBGFREG_ST2, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[2]) },
+ { DBGFREG_ST3, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[3]) },
+ { DBGFREG_ST4, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[4]) },
+ { DBGFREG_ST5, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[5]) },
+ { DBGFREG_ST6, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[6]) },
+ { DBGFREG_ST7, DBGFREGVALTYPE_R80, RT_UOFFSETOF(NTCONTEXT32, FloatSave.aFpuRegs[7]) }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void dbgcKdCtxMsgSend(PKDCTX pThis, bool fWarning, const char *pszMsg);
+
+
+#ifdef LOG_ENABLED
+/**
+ * Returns a human readable string of the given packet sub type.
+ *
+ * @returns Pointer to sub type string.
+ * @param u16SubType The sub type to convert to a string.
+ */
+static const char *dbgcKdPktDumpSubTypeToStr(uint16_t u16SubType)
+{
+ switch (u16SubType)
+ {
+ case KD_PACKET_HDR_SUB_TYPE_STATE_CHANGE32: return "StateChange32";
+ case KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE: return "Manipulate";
+ case KD_PACKET_HDR_SUB_TYPE_DEBUG_IO: return "DebugIo";
+ case KD_PACKET_HDR_SUB_TYPE_ACKNOWLEDGE: return "Ack";
+ case KD_PACKET_HDR_SUB_TYPE_RESEND: return "Resend";
+ case KD_PACKET_HDR_SUB_TYPE_RESET: return "Reset";
+ case KD_PACKET_HDR_SUB_TYPE_STATE_CHANGE64: return "StateChange64";
+ case KD_PACKET_HDR_SUB_TYPE_POLL_BREAKIN: return "PollBreakin";
+ case KD_PACKET_HDR_SUB_TYPE_TRACE_IO: return "TraceIo";
+ case KD_PACKET_HDR_SUB_TYPE_CONTROL_REQUEST: return "ControlRequest";
+ case KD_PACKET_HDR_SUB_TYPE_FILE_IO: return "FileIo";
+ default: break;
+ }
+
+ return "<UNKNOWN>";
+}
+
+
+/**
+ * Returns a human readable string of the given manipulate request ID.
+ *
+ * @returns Human readable string (read only).
+ * @param idReq Request ID (API number in KD speak).
+ */
+static const char *dbgcKdPktDumpManipulateReqToStr(uint32_t idReq)
+{
+ switch (idReq)
+ {
+ case KD_PACKET_MANIPULATE_REQ_READ_VIRT_MEM: return "ReadVirtMem";
+ case KD_PACKET_MANIPULATE_REQ_WRITE_VIRT_MEM: return "WriteVirtMem";
+ case KD_PACKET_MANIPULATE_REQ_GET_CONTEXT: return "GetContext";
+ case KD_PACKET_MANIPULATE_REQ_SET_CONTEXT: return "SetContext";
+ case KD_PACKET_MANIPULATE_REQ_WRITE_BKPT: return "WriteBkpt";
+ case KD_PACKET_MANIPULATE_REQ_RESTORE_BKPT: return "RestoreBkpt";
+ case KD_PACKET_MANIPULATE_REQ_CONTINUE: return "Continue";
+ case KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE: return "ReadCtrlSpace";
+ case KD_PACKET_MANIPULATE_REQ_WRITE_CTRL_SPACE: return "WriteCtrlSpace";
+ case KD_PACKET_MANIPULATE_REQ_READ_IO_SPACE: return "ReadIoSpace";
+ case KD_PACKET_MANIPULATE_REQ_WRITE_IO_SPACE: return "WriteIoSpace";
+ case KD_PACKET_MANIPULATE_REQ_REBOOT: return "Reboot";
+ case KD_PACKET_MANIPULATE_REQ_CONTINUE2: return "Continue2";
+ case KD_PACKET_MANIPULATE_REQ_READ_PHYS_MEM: return "ReadPhysMem";
+ case KD_PACKET_MANIPULATE_REQ_WRITE_PHYS_MEM: return "WritePhysMem";
+ case KD_PACKET_MANIPULATE_REQ_QUERY_SPEC_CALLS: return "QuerySpecCalls";
+ case KD_PACKET_MANIPULATE_REQ_SET_SPEC_CALLS: return "SetSpecCalls";
+ case KD_PACKET_MANIPULATE_REQ_CLEAR_SPEC_CALLS: return "ClrSpecCalls";
+ case KD_PACKET_MANIPULATE_REQ_SET_INTERNAL_BKPT: return "SetIntBkpt";
+ case KD_PACKET_MANIPULATE_REQ_GET_INTERNAL_BKPT: return "GetIntBkpt";
+ case KD_PACKET_MANIPULATE_REQ_READ_IO_SPACE_EX: return "ReadIoSpaceEx";
+ case KD_PACKET_MANIPULATE_REQ_WRITE_IO_SPACE_EX: return "WriteIoSpaceEx";
+ case KD_PACKET_MANIPULATE_REQ_GET_VERSION: return "GetVersion";
+ case KD_PACKET_MANIPULATE_REQ_CLEAR_ALL_INTERNAL_BKPT: return "ClrAllIntBkpt";
+ case KD_PACKET_MANIPULATE_REQ_GET_CONTEXT_EX: return "GetContextEx";
+ case KD_PACKET_MANIPULATE_REQ_QUERY_MEMORY: return "QueryMemory";
+ case KD_PACKET_MANIPULATE_REQ_CAUSE_BUGCHECK: return "CauseBugCheck";
+ case KD_PACKET_MANIPULATE_REQ_SWITCH_PROCESSOR: return "SwitchProcessor";
+ case KD_PACKET_MANIPULATE_REQ_SEARCH_MEMORY: return "SearchMemory";
+ default: break;
+ }
+
+ return "<UNKNOWN>";
+}
+
+
+/**
+ * Dumps the content of a manipulate packet.
+ *
+ * @param pSgBuf S/G buffer containing the manipulate packet payload.
+ */
+static void dbgcKdPktDumpManipulate(PRTSGBUF pSgBuf)
+{
+ KDPACKETMANIPULATEHDR Hdr;
+ size_t cbCopied = RTSgBufCopyToBuf(pSgBuf, &Hdr, sizeof(Hdr));
+
+ if (cbCopied == sizeof(Hdr))
+ {
+ const char *pszReq = dbgcKdPktDumpManipulateReqToStr(Hdr.idReq);
+
+ Log3((" MANIPULATE(%#x (%s), %#x, %u, %#x)\n",
+ Hdr.idReq, pszReq, Hdr.u16CpuLvl, Hdr.idCpu, Hdr.u32NtStatus));
+
+ switch (Hdr.idReq)
+ {
+ case KD_PACKET_MANIPULATE_REQ_READ_VIRT_MEM:
+ case KD_PACKET_MANIPULATE_REQ_WRITE_VIRT_MEM:
+ case KD_PACKET_MANIPULATE_REQ_READ_PHYS_MEM:
+ case KD_PACKET_MANIPULATE_REQ_WRITE_PHYS_MEM:
+ {
+ KDPACKETMANIPULATE_XFERMEM64 XferMem64;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &XferMem64, sizeof(XferMem64));
+ if (cbCopied == sizeof(XferMem64))
+ {
+ Log3((" u64PtrTarget: %RX64\n"
+ " cbXferReq: %RX32\n"
+ " cbXfered: %RX32\n",
+ XferMem64.u64PtrTarget, XferMem64.cbXferReq, XferMem64.cbXfered));
+ }
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(XferMem64), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_RESTORE_BKPT:
+ {
+ KDPACKETMANIPULATE_RESTOREBKPT64 RestoreBkpt64;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &RestoreBkpt64, sizeof(RestoreBkpt64));
+ if (cbCopied == sizeof(RestoreBkpt64))
+ Log3((" u32HndBkpt: %RX32\n", RestoreBkpt64.u32HndBkpt));
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(RestoreBkpt64), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_WRITE_BKPT:
+ {
+ KDPACKETMANIPULATE_WRITEBKPT64 WriteBkpt64;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &WriteBkpt64, sizeof(WriteBkpt64));
+ if (cbCopied == sizeof(WriteBkpt64))
+ Log3((" u64PtrBkpt: %RX64\n"
+ " u32HndBkpt: %RX32\n",
+ WriteBkpt64.u64PtrBkpt, WriteBkpt64.u32HndBkpt));
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(WriteBkpt64), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_CONTINUE:
+ {
+ KDPACKETMANIPULATE_CONTINUE Continue;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &Continue, sizeof(Continue));
+ if (cbCopied == sizeof(Continue))
+ Log3((" u32NtContSts: %RX32\n", Continue.u32NtContSts));
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(Continue), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_CONTINUE2:
+ {
+ KDPACKETMANIPULATE_CONTINUE2 Continue;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &Continue, sizeof(Continue));
+ if (cbCopied == sizeof(Continue))
+ Log3((" u32NtContSts: %RX32\n"
+ " fTrace: %RX32\n",
+ Continue.u32NtContSts, Continue.fTrace));
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(Continue), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE:
+ case KD_PACKET_MANIPULATE_REQ_WRITE_CTRL_SPACE:
+ {
+ KDPACKETMANIPULATE_XFERCTRLSPACE64 XferCtrlSpace64;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &XferCtrlSpace64, sizeof(XferCtrlSpace64));
+ if (cbCopied == sizeof(XferCtrlSpace64))
+ {
+ Log3((" u64IdXfer: %RX64\n"
+ " cbXferReq: %RX32\n"
+ " cbXfered: %RX32\n",
+ XferCtrlSpace64.u64IdXfer, XferCtrlSpace64.cbXferReq, XferCtrlSpace64.cbXfered));
+ }
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(XferCtrlSpace64), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_GET_CONTEXT_EX:
+ {
+ KDPACKETMANIPULATE_CONTEXTEX GetContextEx;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &GetContextEx, sizeof(GetContextEx));
+ if (cbCopied == sizeof(GetContextEx))
+ {
+ Log3((" offStart: %RX32\n"
+ " cbXferReq: %RX32\n"
+ " cbXfered: %RX32\n",
+ GetContextEx.offStart, GetContextEx.cbXfer, GetContextEx.cbXfered));
+ }
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(GetContextEx), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_QUERY_MEMORY:
+ {
+ KDPACKETMANIPULATE_QUERYMEMORY QueryMemory;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &QueryMemory, sizeof(QueryMemory));
+ if (cbCopied == sizeof(QueryMemory))
+ {
+ Log3((" u64GCPtr: %RX64\n"
+ " u32AddrSpace: %RX32\n"
+ " u32Flags: %RX32\n",
+ QueryMemory.u64GCPtr, QueryMemory.u32AddrSpace, QueryMemory.u32Flags));
+ }
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(QueryMemory), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_SEARCH_MEMORY:
+ {
+ KDPACKETMANIPULATE_SEARCHMEMORY SearchMemory;
+ cbCopied = RTSgBufCopyToBuf(pSgBuf, &SearchMemory, sizeof(SearchMemory));
+ if (cbCopied == sizeof(SearchMemory))
+ {
+ Log3((" u64GCPtr: %RX64\n"
+ " cbSearch: %RX64\n"
+ " cbPattern: %RX32\n",
+ SearchMemory.u64GCPtr, SearchMemory.cbSearch, SearchMemory.cbPattern));
+ }
+ else
+ Log3((" Payload to small, expected %u, got %zu\n", sizeof(SearchMemory), cbCopied));
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_SWITCH_PROCESSOR:
+ default:
+ break;
+ }
+ }
+ else
+ Log3((" MANIPULATE(Header too small, expected %u, got %zu)\n", sizeof(Hdr), cbCopied));
+}
+
+
+/**
+ * Dumps the received packet to the debug log.
+ *
+ * @returns VBox status code.
+ * @param pPktHdr The packet header to dump.
+ * @param fRx Flag whether the packet was received (false indicates an outgoing packet).
+ */
+static void dbgcKdPktDump(PCKDPACKETHDR pPktHdr, PCRTSGSEG paSegs, uint32_t cSegs, bool fRx)
+{
+ RTSGBUF SgBuf;
+
+ RTSgBufInit(&SgBuf, paSegs, cSegs);
+
+ Log3(("%s KDPKTHDR(%#x, %#x (%s), %u, %#x, %#x)\n",
+ fRx ? "=>" : "<=",
+ pPktHdr->u32Signature, pPktHdr->u16SubType, dbgcKdPktDumpSubTypeToStr(pPktHdr->u16SubType),
+ pPktHdr->cbBody, pPktHdr->idPacket, pPktHdr->u32ChkSum));
+ switch (pPktHdr->u16SubType)
+ {
+ case KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE:
+ dbgcKdPktDumpManipulate(&SgBuf);
+ break;
+ default:
+ break;
+ }
+}
+#endif
+
+
+/**
+ * Resets the emulated hardware breakpoint state to a state similar after a reboot.
+ *
+ * @param pThis The KD context.
+ */
+static void dbgcKdCtxHwBpReset(PKDCTX pThis)
+{
+ pThis->fSingleStepped = false;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aHwBp); i++)
+ {
+ PKDCTXHWBP pBp = &pThis->aHwBp[i];
+
+ if (pBp->hDbgfBp != NIL_DBGFBP)
+ {
+ int rc = DBGFR3BpClear(pThis->Dbgc.pUVM, pBp->hDbgfBp);
+ AssertRC(rc);
+ }
+
+ pBp->hDbgfBp = NIL_DBGFBP;
+ pBp->GCPtrBp = 0;
+ pBp->fAcc = 0;
+ pBp->fLen = 0;
+ pBp->fLocal = false;
+ pBp->fGlobal = false;
+ pBp->fTriggered = false;
+ }
+}
+
+
+/**
+ * Updates the given breakpoint with the given properties.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pBp The breakpoint to update.
+ * @param fAcc Access mode.
+ * @param fLen Access length.
+ * @param fGlobal Global breakpoint.
+ * @param fLocal Local breakpoint.
+ * @param GCPtrBp Linear address of the breakpoint.
+ */
+static int dbgcKdCtxHwBpUpdate(PKDCTX pThis, PKDCTXHWBP pBp, uint8_t fAcc, uint8_t fLen,
+ bool fGlobal, bool fLocal, RTGCPTR GCPtrBp)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Did anything actually change?. */
+ if ( pBp->fAcc != fAcc
+ || pBp->fLen != fLen
+ || pBp->fGlobal != fGlobal
+ || pBp->fLocal != fLocal
+ || pBp->GCPtrBp != GCPtrBp)
+ {
+ /* Clear the old breakpoint. */
+ if (pBp->hDbgfBp != NIL_DBGFBP)
+ {
+ rc = DBGFR3BpClear(pThis->Dbgc.pUVM, pBp->hDbgfBp);
+ AssertRC(rc);
+ pBp->hDbgfBp = NIL_DBGFBP;
+ }
+
+ pBp->fAcc = fAcc;
+ pBp->fLen = fLen;
+ pBp->fGlobal = fGlobal;
+ pBp->fLocal = fLocal;
+ pBp->GCPtrBp = GCPtrBp;
+ if (pBp->fGlobal || pBp->fLocal)
+ {
+ DBGFADDRESS AddrBp;
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrBp, GCPtrBp);
+
+ uint8_t cb = 0;
+ switch (pBp->fLen)
+ {
+ case X86_DR7_LEN_BYTE:
+ cb = 1;
+ break;
+ case X86_DR7_LEN_WORD:
+ cb = 2;
+ break;
+ case X86_DR7_LEN_DWORD:
+ cb = 4;
+ break;
+ case X86_DR7_LEN_QWORD:
+ cb = 8;
+ break;
+ default:
+ AssertFailed();
+ return VERR_NET_PROTOCOL_ERROR;
+ }
+
+ rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &AddrBp, 0 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/,
+ pBp->fAcc, cb, &pBp->hDbgfBp);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Updates emulated hardware breakpoints based on the written DR7 value.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param uDr7 The DR7 value which is written.
+ */
+static int dbgcKdCtxHwBpDr7Update(PKDCTX pThis, uint32_t uDr7)
+{
+ int rc = VINF_SUCCESS;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aHwBp); i++)
+ {
+ PKDCTXHWBP pBp = &pThis->aHwBp[i];
+ uint8_t fAcc = X86_DR7_GET_RW(uDr7, i);
+ uint8_t fLen = X86_DR7_GET_LEN(uDr7, i);
+ bool fGlobal = (uDr7 & RT_BIT_32(1 + i * 2)) ? true : false;
+ bool fLocal = (uDr7 & RT_BIT_32(i * 2)) ? true : false;
+
+ int rc2 = dbgcKdCtxHwBpUpdate(pThis, pBp, fAcc, fLen, fGlobal, fLocal, pThis->aHwBp[i].GCPtrBp);
+ if ( RT_FAILURE(rc2)
+ && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Updates the linear guest pointer for the given hardware breakpoint.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pBp The breakpoint to update.
+ * @param GCPtrBp The linear breakpoint address.
+ */
+DECLINLINE(int) dbgcKdCtxHwBpGCPtrUpdate(PKDCTX pThis, PKDCTXHWBP pBp, RTGCPTR GCPtrBp)
+{
+ return dbgcKdCtxHwBpUpdate(pThis, pBp, pBp->fAcc, pBp->fLen, pBp->fGlobal, pBp->fLocal, GCPtrBp);
+}
+
+
+/**
+ * Calculates the DR7 value based on the emulated hardware breakpoint state and returns it.
+ *
+ * @returns The emulated DR7 value.
+ * @param pThis The KD context.
+ */
+static uint32_t dbgcKdCtxHwBpDr7Get(PKDCTX pThis)
+{
+ uint32_t uDr7 = 0;
+
+ uDr7 |= X86_DR7_RW(0, pThis->aHwBp[0].fAcc);
+ uDr7 |= X86_DR7_RW(1, pThis->aHwBp[1].fAcc);
+ uDr7 |= X86_DR7_RW(2, pThis->aHwBp[2].fAcc);
+ uDr7 |= X86_DR7_RW(3, pThis->aHwBp[3].fAcc);
+
+ uDr7 |= X86_DR7_LEN(0, pThis->aHwBp[0].fLen);
+ uDr7 |= X86_DR7_LEN(1, pThis->aHwBp[1].fLen);
+ uDr7 |= X86_DR7_LEN(2, pThis->aHwBp[2].fLen);
+ uDr7 |= X86_DR7_LEN(3, pThis->aHwBp[3].fLen);
+
+ uDr7 |= pThis->aHwBp[0].fGlobal ? X86_DR7_G(0) : 0;
+ uDr7 |= pThis->aHwBp[1].fGlobal ? X86_DR7_G(1) : 0;
+ uDr7 |= pThis->aHwBp[2].fGlobal ? X86_DR7_G(2) : 0;
+ uDr7 |= pThis->aHwBp[3].fGlobal ? X86_DR7_G(3) : 0;
+
+ uDr7 |= pThis->aHwBp[0].fLocal ? X86_DR7_L(0) : 0;
+ uDr7 |= pThis->aHwBp[1].fLocal ? X86_DR7_L(1) : 0;
+ uDr7 |= pThis->aHwBp[2].fLocal ? X86_DR7_L(2) : 0;
+ uDr7 |= pThis->aHwBp[3].fLocal ? X86_DR7_L(3) : 0;
+
+ return uDr7;
+}
+
+
+/**
+ * Updates emulated hardware breakpoints based on the written DR6 value.
+ *
+ * @param pThis The KD context.
+ * @param uDr6 The DR7 value which is written.
+ */
+static void dbgcKdCtxHwBpDr6Update(PKDCTX pThis, uint32_t uDr6)
+{
+ pThis->aHwBp[0].fTriggered = (uDr6 & X86_DR6_B0) ? true : false;
+ pThis->aHwBp[1].fTriggered = (uDr6 & X86_DR6_B1) ? true : false;
+ pThis->aHwBp[2].fTriggered = (uDr6 & X86_DR6_B2) ? true : false;
+ pThis->aHwBp[3].fTriggered = (uDr6 & X86_DR6_B3) ? true : false;
+ pThis->fSingleStepped = (uDr6 & X86_DR6_BS) ? true : false;
+}
+
+
+/**
+ * Calculates the DR6 value based on the emulated hardware breakpoint state and returns it.
+ *
+ * @returns The emulated DR6 value.
+ * @param pThis The KD context.
+ */
+static uint32_t dbgcKdCtxHwBpDr6Get(PKDCTX pThis)
+{
+ uint32_t uDr6 = 0;
+
+ if (pThis->aHwBp[0].fTriggered)
+ uDr6 |= X86_DR6_B0;
+ if (pThis->aHwBp[1].fTriggered)
+ uDr6 |= X86_DR6_B1;
+ if (pThis->aHwBp[2].fTriggered)
+ uDr6 |= X86_DR6_B2;
+ if (pThis->aHwBp[3].fTriggered)
+ uDr6 |= X86_DR6_B3;
+ if (pThis->fSingleStepped)
+ uDr6 |= X86_DR6_BS;
+
+ return uDr6;
+}
+
+
+/**
+ * Wrapper for the I/O interface write callback.
+ *
+ * @returns Status code.
+ * @param pThis The KD context.
+ * @param pvPkt The packet data to send.
+ * @param cbPkt Size of the packet in bytes.
+ */
+DECLINLINE(int) dbgcKdCtxWrite(PKDCTX pThis, const void *pvPkt, size_t cbPkt)
+{
+ return pThis->Dbgc.pIo->pfnWrite(pThis->Dbgc.pIo, pvPkt, cbPkt, NULL /*pcbWritten*/);
+}
+
+
+/**
+ * Queries a given register set and stores it into the given context buffer.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param paRegs The register set to query.
+ * @param cRegs Number of entries in the register set.
+ * @param pvCtx The context buffer to store the data into.
+ */
+static int dbgcKdCtxQueryRegs(PKDCTX pThis, VMCPUID idCpu, PCKDREGDESC paRegs, uint32_t cRegs, void *pvCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ for (uint32_t i = 0; i < cRegs && rc == VINF_SUCCESS; i++)
+ {
+ void *pvStart = (uint8_t *)pvCtx + paRegs[i].offReg;
+
+ switch (paRegs[i].enmValType)
+ {
+ case DBGFREGVALTYPE_U16: rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, paRegs[i].enmReg, (uint16_t *)pvStart); break;
+ case DBGFREGVALTYPE_U32: rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, paRegs[i].enmReg, (uint32_t *)pvStart); break;
+ case DBGFREGVALTYPE_U64: rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, paRegs[i].enmReg, (uint64_t *)pvStart); break;
+ //case DBGFREGVALTYPE_R80: rc = DBGFR3RegCpuQueryR80(pThis->Dbgc.pUVM, idCpu, paRegs[i].enmReg, (RTFLOAT80U *)pvStart); break;
+ //case DBGFREGVALTYPE_U128: rc = DBGFR3RegCpuQueryU128(pThis->Dbgc.pUVM, idCpu, paRegs[i].enmReg, (PRTUINT128U)pvStart); break;
+ default: AssertMsgFailedBreakStmt(("Register type %u not implemented\n", paRegs[i].enmValType), rc = VERR_NOT_IMPLEMENTED);
+ }
+
+ if ( rc == VINF_DBGF_ZERO_EXTENDED_REGISTER
+ || ( rc == VINF_DBGF_TRUNCATED_REGISTER
+ && paRegs[i].enmReg == DBGFREG_RFLAGS)) /* KD protocol specifies 32bit but is really 64bit. */
+ rc = VINF_SUCCESS;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && rc != VINF_SUCCESS)
+ rc = VERR_DBGF_UNSUPPORTED_CAST;
+
+ return rc;
+}
+
+
+/**
+ * Fills in the given 64bit NT context structure with the requested values.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param pNtCtx The NT context structure to fill in.
+ * @param fCtxFlags Combination of NTCONTEXT_F_XXX determining what to fill in.
+ */
+static int dbgcKdCtxQueryNtCtx64(PKDCTX pThis, VMCPUID idCpu, PNTCONTEXT64 pNtCtx, uint32_t fCtxFlags)
+{
+ RT_BZERO(pNtCtx, sizeof(*pNtCtx));
+
+ pNtCtx->fContext = NTCONTEXT_F_AMD64;
+ int rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_MXCSR, &pNtCtx->u32RegMxCsr);
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_CONTROL)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsCtrl64[0], RT_ELEMENTS(g_aRegsCtrl64), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_CONTROL;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_INTEGER)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsInt64[0], RT_ELEMENTS(g_aRegsInt64), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_INTEGER;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_SEGMENTS)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsSegs64[0], RT_ELEMENTS(g_aRegsSegs64), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_SEGMENTS;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_FLOATING_POINT)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsFx64[0], RT_ELEMENTS(g_aRegsFx64), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_FLOATING_POINT;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_DEBUG)
+ {
+ /** @todo NTCONTEXT_F_DEBUG */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Fills in the given 32bit NT context structure with the requested values.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param pNtCtx The NT context structure to fill in.
+ * @param fCtxFlags Combination of NTCONTEXT_F_XXX determining what to fill in.
+ */
+static int dbgcKdCtxQueryNtCtx32(PKDCTX pThis, VMCPUID idCpu, PNTCONTEXT32 pNtCtx, uint32_t fCtxFlags)
+{
+ RT_BZERO(pNtCtx, sizeof(*pNtCtx));
+
+ pNtCtx->fContext = NTCONTEXT_F_X86;
+
+ int rc = VINF_SUCCESS;
+ if (fCtxFlags & NTCONTEXT_F_CONTROL)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsCtrl32[0], RT_ELEMENTS(g_aRegsCtrl32), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_CONTROL;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_INTEGER)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsInt32[0], RT_ELEMENTS(g_aRegsInt32), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_INTEGER;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_SEGMENTS)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsSegs32[0], RT_ELEMENTS(g_aRegsSegs32), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_SEGMENTS;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_FLOATING_POINT)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsFx32[0], RT_ELEMENTS(g_aRegsFx32), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_FLOATING_POINT;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fCtxFlags & NTCONTEXT_F_DEBUG)
+ {
+ rc = dbgcKdCtxQueryRegs(pThis, idCpu, &g_aRegsDbg32[0], RT_ELEMENTS(g_aRegsDbg32), pNtCtx);
+ if (RT_SUCCESS(rc))
+ pNtCtx->fContext |= NTCONTEXT_F_DEBUG;
+ }
+
+ return rc;
+}
+
+
+#define KD_REG_INIT(a_pszName, a_enmType, a_ValMember, a_Val) \
+ do \
+ { \
+ aRegsSet[idxReg].pszName = a_pszName; \
+ aRegsSet[idxReg].enmType = a_enmType; \
+ aRegsSet[idxReg].Val.a_ValMember = a_Val; \
+ idxReg++; \
+ } while (0)
+#define KD_REG_INIT_DTR(a_pszName, a_Base, a_Limit) \
+ do \
+ { \
+ aRegsSet[idxReg].pszName = a_pszName; \
+ aRegsSet[idxReg].enmType = DBGFREGVALTYPE_DTR; \
+ aRegsSet[idxReg].Val.dtr.u64Base = a_Base; \
+ aRegsSet[idxReg].Val.dtr.u32Limit = a_Limit; \
+ idxReg++; \
+ } while (0)
+#define KD_REG_INIT_U16(a_pszName, a_Val) KD_REG_INIT(a_pszName, DBGFREGVALTYPE_U16, u16, a_Val)
+#define KD_REG_INIT_U32(a_pszName, a_Val) KD_REG_INIT(a_pszName, DBGFREGVALTYPE_U32, u32, a_Val)
+#define KD_REG_INIT_U64(a_pszName, a_Val) KD_REG_INIT(a_pszName, DBGFREGVALTYPE_U64, u64, a_Val)
+
+
+/**
+ * Writes the indicated values from the given context structure to the guests register set.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param pNtCtx The NT context structure to set.
+ * @param fCtxFlags Combination of NTCONTEXT_F_XXX determining what to set.
+ */
+static int dbgcKdCtxSetNtCtx64(PKDCTX pThis, VMCPUID idCpu, PCNTCONTEXT64 pNtCtx, uint32_t fCtxFlags)
+{
+ uint32_t idxReg = 0;
+ DBGFREGENTRYNM aRegsSet[64]; /** @todo Verify that this is enough when fully implemented. */
+
+ KD_REG_INIT_U32("mxcsr", pNtCtx->u32RegMxCsr);
+
+ if (fCtxFlags & NTCONTEXT_F_CONTROL)
+ {
+#if 0 /** @todo CPUM returns VERR_NOT_IMPLEMENTED */
+ KD_REG_INIT_U16("cs", pNtCtx->u16SegCs);
+ KD_REG_INIT_U16("ss", pNtCtx->u16SegSs);
+#endif
+ KD_REG_INIT_U64("rip", pNtCtx->u64RegRip);
+ KD_REG_INIT_U64("rsp", pNtCtx->u64RegRsp);
+ KD_REG_INIT_U64("rbp", pNtCtx->u64RegRbp);
+ KD_REG_INIT_U32("rflags", pNtCtx->u32RegEflags);
+ }
+
+ if (fCtxFlags & NTCONTEXT_F_INTEGER)
+ {
+ KD_REG_INIT_U64("rax", pNtCtx->u64RegRax);
+ KD_REG_INIT_U64("rcx", pNtCtx->u64RegRcx);
+ KD_REG_INIT_U64("rdx", pNtCtx->u64RegRdx);
+ KD_REG_INIT_U64("rbx", pNtCtx->u64RegRbx);
+ KD_REG_INIT_U64("rsi", pNtCtx->u64RegRsi);
+ KD_REG_INIT_U64("rdi", pNtCtx->u64RegRdi);
+ KD_REG_INIT_U64("r8", pNtCtx->u64RegR8);
+ KD_REG_INIT_U64("r9", pNtCtx->u64RegR9);
+ KD_REG_INIT_U64("r10", pNtCtx->u64RegR10);
+ KD_REG_INIT_U64("r11", pNtCtx->u64RegR11);
+ KD_REG_INIT_U64("r12", pNtCtx->u64RegR12);
+ KD_REG_INIT_U64("r13", pNtCtx->u64RegR13);
+ KD_REG_INIT_U64("r14", pNtCtx->u64RegR14);
+ KD_REG_INIT_U64("r15", pNtCtx->u64RegR15);
+ }
+
+ if (fCtxFlags & NTCONTEXT_F_SEGMENTS)
+ {
+#if 0 /** @todo CPUM returns VERR_NOT_IMPLEMENTED */
+ KD_REG_INIT_U16("ds", pNtCtx->u16SegDs);
+ KD_REG_INIT_U16("es", pNtCtx->u16SegEs);
+ KD_REG_INIT_U16("fs", pNtCtx->u16SegFs);
+ KD_REG_INIT_U16("gs", pNtCtx->u16SegGs);
+#endif
+ }
+
+ if (fCtxFlags & NTCONTEXT_F_FLOATING_POINT)
+ {
+ /** @todo NTCONTEXT_F_FLOATING_POINT. */
+ }
+
+ if (fCtxFlags & NTCONTEXT_F_DEBUG)
+ dbgcKdCtxMsgSend(pThis, true /*fWarning*/, "Setting local DR registers does not work!");
+
+ return DBGFR3RegNmSetBatch(pThis->Dbgc.pUVM, idCpu, &aRegsSet[0], idxReg);
+}
+
+
+/**
+ * Fills in the given 64bit NT kernel context structure with the requested values.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param pKNtCtx The NT context structure to fill in.
+ * @param fCtxFlags Combination of NTCONTEXT_F_XXX determining what to fill in.
+ */
+static int dbgcKdCtxQueryNtKCtx64(PKDCTX pThis, VMCPUID idCpu, PNTKCONTEXT64 pKNtCtx, uint32_t fCtxFlags)
+{
+ RT_BZERO(pKNtCtx, sizeof(*pKNtCtx));
+
+ int rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR0, &pKNtCtx->u64RegCr0);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR2, &pKNtCtx->u64RegCr2);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR3, &pKNtCtx->u64RegCr3);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR4, &pKNtCtx->u64RegCr4);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR8, &pKNtCtx->u64RegCr8);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_GDTR_LIMIT, &pKNtCtx->Gdtr.u16Limit);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_GDTR_BASE, &pKNtCtx->Gdtr.u64PtrBase);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_IDTR_LIMIT, &pKNtCtx->Idtr.u16Limit);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_IDTR_BASE, &pKNtCtx->Idtr.u64PtrBase);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_TR, &pKNtCtx->u16RegTr);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_LDTR, &pKNtCtx->u16RegLdtr);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_MXCSR, &pKNtCtx->u32RegMxCsr);
+
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_MSR_K8_GS_BASE, &pKNtCtx->u64MsrGsBase);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_MSR_K8_KERNEL_GS_BASE, &pKNtCtx->u64MsrKernelGsBase);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_MSR_K6_STAR, &pKNtCtx->u64MsrStar);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_MSR_K8_LSTAR, &pKNtCtx->u64MsrLstar);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_MSR_K8_CSTAR, &pKNtCtx->u64MsrCstar);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, idCpu, DBGFREG_MSR_K8_SF_MASK, &pKNtCtx->u64MsrSfMask);
+ /** @todo XCR0 */
+
+ /* Get the emulated DR register state. */
+ pKNtCtx->u64RegDr0 = pThis->aHwBp[0].GCPtrBp;
+ pKNtCtx->u64RegDr1 = pThis->aHwBp[1].GCPtrBp;
+ pKNtCtx->u64RegDr2 = pThis->aHwBp[2].GCPtrBp;
+ pKNtCtx->u64RegDr3 = pThis->aHwBp[3].GCPtrBp;
+ pKNtCtx->u64RegDr6 = dbgcKdCtxHwBpDr6Get(pThis);
+ pKNtCtx->u64RegDr7 = dbgcKdCtxHwBpDr7Get(pThis);
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxQueryNtCtx64(pThis, idCpu, &pKNtCtx->Ctx, fCtxFlags);
+
+ return rc;
+}
+
+
+/**
+ * Fills in the given 32bit NT kernel context structure with the requested values.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param pKNtCtx The NT context structure to fill in.
+ */
+static int dbgcKdCtxQueryNtKCtx32(PKDCTX pThis, VMCPUID idCpu, PNTKCONTEXT32 pKNtCtx)
+{
+ RT_BZERO(pKNtCtx, sizeof(*pKNtCtx));
+
+ int rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR0, &pKNtCtx->u32RegCr0);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR2, &pKNtCtx->u32RegCr2);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR3, &pKNtCtx->u32RegCr3);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_CR4, &pKNtCtx->u32RegCr4);
+
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_GDTR_LIMIT, &pKNtCtx->Gdtr.u16Limit);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_GDTR_BASE, &pKNtCtx->Gdtr.u32PtrBase);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_IDTR_LIMIT, &pKNtCtx->Idtr.u16Limit);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, idCpu, DBGFREG_IDTR_BASE, &pKNtCtx->Idtr.u32PtrBase);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_TR, &pKNtCtx->u16RegTr);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, idCpu, DBGFREG_LDTR, &pKNtCtx->u16RegLdtr);
+
+ /* Get the emulated DR register state. */
+ pKNtCtx->u32RegDr0 = (uint32_t)pThis->aHwBp[0].GCPtrBp;
+ pKNtCtx->u32RegDr1 = (uint32_t)pThis->aHwBp[1].GCPtrBp;
+ pKNtCtx->u32RegDr2 = (uint32_t)pThis->aHwBp[2].GCPtrBp;
+ pKNtCtx->u32RegDr3 = (uint32_t)pThis->aHwBp[3].GCPtrBp;
+ pKNtCtx->u32RegDr6 = dbgcKdCtxHwBpDr6Get(pThis);
+ pKNtCtx->u32RegDr7 = dbgcKdCtxHwBpDr7Get(pThis);
+
+ return rc;
+}
+
+
+/**
+ * Fills in the given 64bit NT kernel context structure with the requested values.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param idCpu The CPU to query the context for.
+ * @param pKNtCtx The NT context structure to fill in.
+ * @param cbSet How many bytes of the context are valid.
+ */
+static int dbgcKdCtxSetNtKCtx64(PKDCTX pThis, VMCPUID idCpu, PCNTKCONTEXT64 pKNtCtx, size_t cbSet)
+{
+ AssertReturn(cbSet >= RT_UOFFSETOF(NTKCONTEXT64, Ctx), VERR_INVALID_PARAMETER);
+
+ uint32_t idxReg = 0;
+ DBGFREGENTRYNM aRegsSet[64]; /** @todo Verify that this is enough when fully implemented. */
+
+ KD_REG_INIT_U64("cr0", pKNtCtx->u64RegCr0);
+ KD_REG_INIT_U64("cr2", pKNtCtx->u64RegCr2);
+ KD_REG_INIT_U64("cr3", pKNtCtx->u64RegCr3);
+ KD_REG_INIT_U64("cr4", pKNtCtx->u64RegCr4);
+ KD_REG_INIT_U64("cr8", pKNtCtx->u64RegCr8);
+
+ KD_REG_INIT_DTR("gdtr", pKNtCtx->Gdtr.u64PtrBase, pKNtCtx->Gdtr.u16Limit);
+ KD_REG_INIT_DTR("idtr", pKNtCtx->Idtr.u64PtrBase, pKNtCtx->Idtr.u16Limit);
+
+#if 0 /** @todo CPUM returns VERR_NOT_IMPLEMENTED */
+ KD_REG_INIT_U16("tr", pKNtCtx->u16RegTr);
+ KD_REG_INIT_U16("ldtr", pKNtCtx->u16RegLdtr);
+#endif
+ KD_REG_INIT_U32("mxcsr", pKNtCtx->u32RegMxCsr);
+
+ KD_REG_INIT_U64("msr_gs_base", pKNtCtx->u64MsrGsBase);
+ KD_REG_INIT_U64("krnl_gs_base", pKNtCtx->u64MsrKernelGsBase);
+ KD_REG_INIT_U64("star", pKNtCtx->u64MsrStar);
+ KD_REG_INIT_U64("lstar", pKNtCtx->u64MsrLstar);
+ KD_REG_INIT_U64("cstar", pKNtCtx->u64MsrCstar);
+ KD_REG_INIT_U64("sf_mask", pKNtCtx->u64MsrSfMask);
+
+ int rc = DBGFR3RegNmSetBatch(pThis->Dbgc.pUVM, idCpu, &aRegsSet[0], idxReg);
+ if ( RT_SUCCESS(rc)
+ && cbSet > RT_UOFFSETOF(NTKCONTEXT64, Ctx)) /** @todo Probably wrong. */
+ rc = dbgcKdCtxSetNtCtx64(pThis, idCpu, &pKNtCtx->Ctx, pKNtCtx->Ctx.fContext);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Update emulated hardware breakpoint state. */
+ dbgcKdCtxHwBpDr6Update(pThis, (uint32_t)pKNtCtx->u64RegDr6);
+ rc = dbgcKdCtxHwBpDr7Update(pThis, (uint32_t)pKNtCtx->u64RegDr7);
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxHwBpGCPtrUpdate(pThis, &pThis->aHwBp[0], pKNtCtx->u64RegDr0);
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxHwBpGCPtrUpdate(pThis, &pThis->aHwBp[1], pKNtCtx->u64RegDr1);
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxHwBpGCPtrUpdate(pThis, &pThis->aHwBp[2], pKNtCtx->u64RegDr2);
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxHwBpGCPtrUpdate(pThis, &pThis->aHwBp[3], pKNtCtx->u64RegDr3);
+ }
+
+ return rc;
+}
+
+#undef KD_REG_INIT_64
+#undef KD_REG_INIT_32
+#undef KD_REG_INIT_16
+#undef KD_REG_INIT_DTR
+#undef KD_REG_INIT
+
+
+/**
+ * Validates the given KD packet header.
+ *
+ * @returns Flag whether the packet header is valid, false if invalid.
+ * @param pPktHdr The packet header to validate.
+ */
+static bool dbgcKdPktHdrValidate(PCKDPACKETHDR pPktHdr)
+{
+ if ( pPktHdr->u32Signature != KD_PACKET_HDR_SIGNATURE_DATA
+ && pPktHdr->u32Signature != KD_PACKET_HDR_SIGNATURE_CONTROL
+ && pPktHdr->u32Signature != KD_PACKET_HDR_SIGNATURE_BREAKIN)
+ return false;
+
+ if (pPktHdr->u16SubType >= KD_PACKET_HDR_SUB_TYPE_MAX)
+ return false;
+
+ uint32_t idPacket = pPktHdr->idPacket & UINT32_C(0xfffffffe);
+ if ( idPacket != KD_PACKET_HDR_ID_INITIAL
+ && idPacket != KD_PACKET_HDR_ID_RESET
+ && idPacket != 0 /* Happens on the very first packet */)
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Generates a checksum from the given buffer.
+ *
+ * @returns Generated checksum.
+ * @param pv The data to generate a checksum from.
+ * @param cb Number of bytes to checksum.
+ */
+static uint32_t dbgcKdPktChkSumGen(const void *pv, size_t cb)
+{
+ const uint8_t *pb = (const uint8_t *)pv;
+ uint32_t u32ChkSum = 0;
+
+ while (cb--)
+ u32ChkSum += *pb++;
+
+ return u32ChkSum;
+}
+
+
+/**
+ * Generates a checksum from the given segments.
+ *
+ * @returns Generated checksum.
+ * @param paSegs Pointer to the array of segments containing the data.
+ * @param cSegs Number of segments.
+ * @param pcbChkSum Where to store the number of bytes checksummed, optional.
+ */
+static uint32_t dbgcKdPktChkSumGenSg(PCRTSGSEG paSegs, uint32_t cSegs, size_t *pcbChkSum)
+{
+ size_t cbChkSum = 0;
+ uint32_t u32ChkSum = 0;
+
+ for (uint32_t i = 0; i < cSegs; i++)
+ {
+ u32ChkSum += dbgcKdPktChkSumGen(paSegs[i].pvSeg, paSegs[i].cbSeg);
+ cbChkSum += paSegs[i].cbSeg;
+ }
+
+ if (pcbChkSum)
+ *pcbChkSum = cbChkSum;
+
+ return u32ChkSum;
+}
+
+
+/**
+ * Waits for an acknowledgment.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param msWait Maximum number of milliseconds to wait for an acknowledge.
+ * @param pfResend Where to store the resend requested flag on success.
+ */
+static int dbgcKdCtxPktWaitForAck(PKDCTX pThis, RTMSINTERVAL msWait, bool *pfResend)
+{
+ KDPACKETHDR PktAck;
+ uint8_t *pbCur = (uint8_t *)&PktAck;
+ size_t cbLeft = sizeof(PktAck);
+ uint64_t tsStartMs = RTTimeMilliTS();
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p msWait=%u pfResend=%p\n", pThis, msWait, pfResend));
+
+ RT_ZERO(PktAck);
+
+ /* There might be breakin packets in the queue, read until we get something else. */
+ while ( msWait
+ && RT_SUCCESS(rc))
+ {
+ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, msWait))
+ {
+ size_t cbRead = 0;
+ rc = pThis->Dbgc.pIo->pfnRead(pThis->Dbgc.pIo, pbCur, 1, &cbRead);
+ if ( RT_SUCCESS(rc)
+ && cbRead == 1)
+ {
+ uint64_t tsSpanMs = RTTimeMilliTS() - tsStartMs;
+ msWait -= RT_MIN(msWait, tsSpanMs);
+ tsStartMs = RTTimeMilliTS();
+
+ if (*pbCur == KD_PACKET_HDR_SIGNATURE_BREAKIN_BYTE)
+ pThis->fBreakinRecv = true;
+ else
+ {
+ pbCur++;
+ cbLeft--;
+ break;
+ }
+ }
+ }
+ else
+ rc = VERR_TIMEOUT;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !msWait)
+ rc = VERR_TIMEOUT;
+
+ if (RT_SUCCESS(rc))
+ {
+ while ( msWait
+ && RT_SUCCESS(rc)
+ && cbLeft)
+ {
+ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, msWait))
+ {
+ size_t cbRead = 0;
+ rc = pThis->Dbgc.pIo->pfnRead(pThis->Dbgc.pIo, pbCur, cbLeft, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t tsSpanMs = RTTimeMilliTS() - tsStartMs;
+ msWait -= RT_MIN(msWait, tsSpanMs);
+ tsStartMs = RTTimeMilliTS();
+
+ cbLeft -= cbRead;
+ pbCur += cbRead;
+ }
+ }
+ else
+ rc = VERR_TIMEOUT;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (PktAck.u32Signature == KD_PACKET_HDR_SIGNATURE_CONTROL)
+ {
+ if (PktAck.u16SubType == KD_PACKET_HDR_SUB_TYPE_ACKNOWLEDGE)
+ rc = VINF_SUCCESS;
+ else if (PktAck.u16SubType == KD_PACKET_HDR_SUB_TYPE_RESEND)
+ {
+ *pfResend = true;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc *pfResend=%RTbool\n", rc, *pfResend));
+ return rc;
+}
+
+
+/**
+ * Sends the given packet header and optional segmented body (the trailing byte is sent automatically).
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param u32Signature The signature to send.
+ * @param u16SubType The sub type to send.
+ * @param paSegs Pointer to the array of segments to send in the body, optional.
+ * @param cSegs Number of segments.
+ * @param fAck Flag whether to wait for an acknowledge.
+ */
+static int dbgcKdCtxPktSendSg(PKDCTX pThis, uint32_t u32Signature, uint16_t u16SubType,
+ PCRTSGSEG paSegs, uint32_t cSegs, bool fAck)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cRetriesLeft = 3;
+ uint8_t bTrailer = KD_PACKET_TRAILING_BYTE;
+ KDPACKETHDR Hdr;
+
+ size_t cbChkSum = 0;
+ uint32_t u32ChkSum = dbgcKdPktChkSumGenSg(paSegs, cSegs, &cbChkSum);
+
+ Hdr.u32Signature = u32Signature;
+ Hdr.u16SubType = u16SubType;
+ Hdr.cbBody = (uint16_t)cbChkSum;
+ Hdr.idPacket = pThis->idPktNext;
+ Hdr.u32ChkSum = u32ChkSum;
+
+#ifdef LOG_ENABLED
+ dbgcKdPktDump(&Hdr, paSegs, cSegs, false /*fRx*/);
+#endif
+
+ while (cRetriesLeft--)
+ {
+ bool fResend = false;
+
+ if (pThis->Dbgc.pIo->pfnPktBegin)
+ {
+ rc = pThis->Dbgc.pIo->pfnPktBegin(pThis->Dbgc.pIo, 0 /*cbPktHint*/);
+ AssertRC(rc);
+ }
+
+ rc = dbgcKdCtxWrite(pThis, &Hdr, sizeof(Hdr));
+ if ( RT_SUCCESS(rc)
+ && paSegs
+ && cSegs)
+ {
+ for (uint32_t i = 0; i < cSegs && RT_SUCCESS(rc); i++)
+ rc = dbgcKdCtxWrite(pThis, paSegs[i].pvSeg, paSegs[i].cbSeg);
+
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxWrite(pThis, &bTrailer, sizeof(bTrailer));
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pThis->Dbgc.pIo->pfnPktEnd)
+ rc = pThis->Dbgc.pIo->pfnPktEnd(pThis->Dbgc.pIo);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (fAck)
+ rc = dbgcKdCtxPktWaitForAck(pThis, 10 * 1000, &fResend);
+
+ if ( RT_SUCCESS(rc)
+ && !fResend)
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Sends the given packet header and optional body (the trailing byte is sent automatically).
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param u32Signature The signature to send.
+ * @param u16SubType The sub type to send.
+ * @param pvBody The body to send, optional.
+ * @param cbBody Body size in bytes.
+ * @param fAck Flag whether to wait for an acknowledge.
+ */
+DECLINLINE(int) dbgcKdCtxPktSend(PKDCTX pThis, uint32_t u32Signature, uint16_t u16SubType,
+ const void *pvBody, size_t cbBody,
+ bool fAck)
+{
+ RTSGSEG Seg;
+
+ Seg.pvSeg = (void *)pvBody;
+ Seg.cbSeg = cbBody;
+ return dbgcKdCtxPktSendSg(pThis, u32Signature, u16SubType, cbBody ? &Seg : NULL, cbBody ? 1 : 0, fAck);
+}
+
+
+/**
+ * Sends a resend packet answer.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ */
+DECLINLINE(int) dbgcKdCtxPktSendResend(PKDCTX pThis)
+{
+ return dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_CONTROL, KD_PACKET_HDR_SUB_TYPE_RESEND,
+ NULL /*pvBody*/, 0 /*cbBody*/, false /*fAck*/);
+}
+
+
+/**
+ * Sends a resend packet answer.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ */
+DECLINLINE(int) dbgcKdCtxPktSendReset(PKDCTX pThis)
+{
+ pThis->idPktNext = KD_PACKET_HDR_ID_INITIAL;
+ return dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_CONTROL, KD_PACKET_HDR_SUB_TYPE_RESET,
+ NULL /*pvBody*/, 0 /*cbBody*/, false /*fAck*/);
+}
+
+
+/**
+ * Sends an acknowledge packet answer.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ */
+DECLINLINE(int) dbgcKdCtxPktSendAck(PKDCTX pThis)
+{
+ return dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_CONTROL, KD_PACKET_HDR_SUB_TYPE_ACKNOWLEDGE,
+ NULL /*pvBody*/, 0 /*cbBody*/, false /*fAck*/);
+}
+
+
+/**
+ * Resets the packet receive state machine.
+ *
+ * @param pThis The KD context.
+ */
+static void dbgcKdCtxPktRecvReset(PKDCTX pThis)
+{
+ pThis->enmState = KDRECVSTATE_PACKET_HDR_FIRST_BYTE;
+ pThis->pbRecv = &pThis->PktHdr.ab[0];
+ pThis->cbRecvLeft = sizeof(pThis->PktHdr.ab[0]);
+ pThis->msRecvTimeout = RT_INDEFINITE_WAIT;
+ pThis->tsRecvLast = RTTimeMilliTS();
+}
+
+
+/**
+ * Sends a Debug I/O string packet.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context data.
+ * @param idCpu The CPU ID generating this packet.
+ * @param pachChars The characters to send (ASCII).
+ * @param cbChars Number of characters to send.
+ */
+static int dbgcKdCtxDebugIoStrSend(PKDCTX pThis, VMCPUID idCpu, const char *pachChars, size_t cbChars)
+{
+ KDPACKETDEBUGIO DebugIo;
+ RT_ZERO(DebugIo);
+
+ /* Fix your damn log strings if this exceeds 4GB... */
+ if (cbChars != (uint32_t)cbChars)
+ return VERR_BUFFER_OVERFLOW;
+
+ DebugIo.u32Type = KD_PACKET_DEBUG_IO_STRING;
+ DebugIo.u16CpuLvl = 0x6;
+ DebugIo.idCpu = (uint16_t)idCpu;
+ DebugIo.u.Str.cbStr = (uint32_t)cbChars;
+
+ RTSGSEG aRespSegs[2];
+
+ aRespSegs[0].pvSeg = &DebugIo;
+ aRespSegs[0].cbSeg = sizeof(DebugIo);
+ aRespSegs[1].pvSeg = (void *)pachChars;
+ aRespSegs[1].cbSeg = cbChars;
+
+ int rc = dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_DEBUG_IO,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+ if (RT_SUCCESS(rc))
+ pThis->idPktNext ^= 0x1;
+
+ return rc;
+}
+
+
+/**
+ * Sends a message to the remotes end.
+ *
+ * @param pThis The KD context data.
+ * @param fWarning Flag whether this is a warning or an informational message.
+ * @param pszMsg The message to send.
+ */
+static void dbgcKdCtxMsgSend(PKDCTX pThis, bool fWarning, const char *pszMsg)
+{
+ size_t cchMsg = strlen(pszMsg);
+
+ KDPACKETDEBUGIO DebugIo;
+ RT_ZERO(DebugIo);
+
+ DebugIo.u32Type = KD_PACKET_DEBUG_IO_STRING;
+ DebugIo.u16CpuLvl = 0x6;
+ DebugIo.idCpu = 0;
+
+ RTSGSEG aRespSegs[5];
+
+ aRespSegs[0].pvSeg = &DebugIo;
+ aRespSegs[0].cbSeg = sizeof(DebugIo);
+ aRespSegs[1].pvSeg = (void *)"VBoxDbg ";
+ aRespSegs[1].cbSeg = sizeof("VBoxDbg ") - 1;
+ if (fWarning)
+ {
+ aRespSegs[2].pvSeg = (void *)"WARNING ";
+ aRespSegs[2].cbSeg = sizeof("WARNING ") - 1;
+ }
+ else
+ {
+ aRespSegs[2].pvSeg = (void *)"INFO ";
+ aRespSegs[2].cbSeg = sizeof("INFO ") - 1;
+ }
+ aRespSegs[3].pvSeg = (void *)pszMsg;
+ aRespSegs[3].cbSeg = cchMsg;
+ aRespSegs[4].pvSeg = (void *)"\r\n";
+ aRespSegs[4].cbSeg = 2;
+
+ DebugIo.u.Str.cbStr = (uint32_t)( aRespSegs[1].cbSeg
+ + aRespSegs[2].cbSeg
+ + aRespSegs[3].cbSeg
+ + aRespSegs[4].cbSeg);
+
+ int rc = dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_DEBUG_IO,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+ if (RT_SUCCESS(rc))
+ pThis->idPktNext ^= 0x1;
+}
+
+
+/**
+ * Queries some user input from the remotes end.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context data.
+ * @param idCpu The CPU ID generating this packet.
+ * @param pachPrompt The prompt to send (ASCII).
+ * @param cbPrompt Number of characters to send for the prompt.
+ * @param cbResponseMax Maximum size for the response.
+ */
+static int dbgcKdCtxDebugIoGetStrSend(PKDCTX pThis, VMCPUID idCpu, const char *pachPrompt, size_t cbPrompt,
+ size_t cbResponseMax)
+{
+ KDPACKETDEBUGIO DebugIo;
+ RT_ZERO(DebugIo);
+
+ /* Fix your damn log strings if this exceeds 4GB... */
+ if ( cbPrompt != (uint32_t)cbPrompt
+ || cbResponseMax != (uint32_t)cbResponseMax)
+ return VERR_BUFFER_OVERFLOW;
+
+ DebugIo.u32Type = KD_PACKET_DEBUG_IO_GET_STRING;
+ DebugIo.u16CpuLvl = 0x6;
+ DebugIo.idCpu = (uint16_t)idCpu;
+ DebugIo.u.Prompt.cbPrompt = (uint32_t)cbPrompt;
+ DebugIo.u.Prompt.cbReturn = (uint32_t)cbResponseMax;
+
+ RTSGSEG aRespSegs[2];
+
+ aRespSegs[0].pvSeg = &DebugIo;
+ aRespSegs[0].cbSeg = sizeof(DebugIo);
+ aRespSegs[1].pvSeg = (void *)pachPrompt;
+ aRespSegs[1].cbSeg = cbPrompt;
+
+ int rc = dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_DEBUG_IO,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+ if (RT_SUCCESS(rc))
+ pThis->idPktNext ^= 0x1;
+
+ return rc;
+}
+
+
+/**
+ * Sends a state change event packet.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context data.
+ * @param enmType The event type.
+ */
+static int dbgcKdCtxStateChangeSend(PKDCTX pThis, DBGFEVENTTYPE enmType)
+{
+ LogFlowFunc(("pThis=%p enmType=%u\n", pThis, enmType));
+
+ /* Select the record to send based on the CPU mode. */
+ int rc = VINF_SUCCESS;
+ KDPACKETSTATECHANGE64 StateChange64;
+ RT_ZERO(StateChange64);
+
+ StateChange64.u32StateNew = KD_PACKET_STATE_CHANGE_EXCEPTION;
+ StateChange64.u16CpuLvl = 0x6; /** @todo Figure this one out. */
+ StateChange64.idCpu = pThis->Dbgc.idCpu;
+ StateChange64.cCpus = (uint16_t)DBGFR3CpuGetCount(pThis->Dbgc.pUVM);
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_RIP, &StateChange64.u64RipThread);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS AddrRip;
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrRip, StateChange64.u64RipThread);
+
+ StateChange64.u64RipThread = KD_PTR_CREATE(pThis, StateChange64.u64RipThread);
+
+ /** @todo Properly fill in the exception record. */
+ switch (enmType)
+ {
+ case DBGFEVENT_HALT_DONE:
+ case DBGFEVENT_BREAKPOINT:
+ case DBGFEVENT_BREAKPOINT_IO:
+ case DBGFEVENT_BREAKPOINT_MMIO:
+ case DBGFEVENT_BREAKPOINT_HYPER:
+ StateChange64.u.Exception.ExcpRec.u32ExcpCode = KD_PACKET_EXCP_CODE_BKPT;
+ break;
+ case DBGFEVENT_STEPPED:
+ case DBGFEVENT_STEPPED_HYPER:
+ pThis->fSingleStepped = true; /* For emulation of DR6. */
+ StateChange64.u.Exception.ExcpRec.u32ExcpCode = KD_PACKET_EXCP_CODE_SINGLE_STEP;
+ break;
+ default:
+ AssertMsgFailed(("Invalid DBGF event type for state change %d!\n", enmType));
+ }
+
+ StateChange64.u.Exception.ExcpRec.cExcpParms = 3;
+ StateChange64.u.Exception.u32FirstChance = 0x1;
+
+ /** @todo Properly fill in the control report. */
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_DR6, &StateChange64.uCtrlReport.Amd64.u64RegDr6);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_DR7, &StateChange64.uCtrlReport.Amd64.u64RegDr7);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_RFLAGS, &StateChange64.uCtrlReport.Amd64.u32RegEflags);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_CS, &StateChange64.uCtrlReport.Amd64.u16SegCs);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_DS, &StateChange64.uCtrlReport.Amd64.u16SegDs);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_ES, &StateChange64.uCtrlReport.Amd64.u16SegEs);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU16(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGFREG_FS, &StateChange64.uCtrlReport.Amd64.u16SegFs);
+
+ /* Read instruction bytes. */
+ StateChange64.uCtrlReport.Amd64.cbInsnStream = sizeof(StateChange64.uCtrlReport.Amd64.abInsn);
+ rc = DBGFR3MemRead(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrRip,
+ &StateChange64.uCtrlReport.Amd64.abInsn[0], StateChange64.uCtrlReport.Amd64.cbInsnStream);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->idPktNext = KD_PACKET_HDR_ID_INITIAL;
+ rc = dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_CHANGE64,
+ &StateChange64, sizeof(StateChange64), false /*fAck*/);
+ }
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Processes a get version 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64GetVersion(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATE64 Resp;
+ RT_ZERO(Resp);
+
+ /* Fill in the generic part. */
+ Resp.Hdr.idReq = KD_PACKET_MANIPULATE_REQ_GET_VERSION;
+ Resp.Hdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ Resp.Hdr.idCpu = pPktManip->Hdr.idCpu;
+ Resp.Hdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ /* Build our own response in case there is no Windows interface available. */
+ uint32_t NtBuildNumber = 0x0f2800; /* Used when there is no NT interface available, which probably breaks symbol loading. */
+ bool f32Bit = false;
+ if (pThis->pIfWinNt)
+ {
+ int rc = pThis->pIfWinNt->pfnQueryVersion(pThis->pIfWinNt, pThis->Dbgc.pUVM, VMMR3GetVTable(),
+ NULL /*puVersMajor*/, NULL /*puVersMinor*/,
+ &NtBuildNumber, &f32Bit);
+ if (RT_SUCCESS(rc))
+ rc = pThis->pIfWinNt->pfnQueryKernelPtrs(pThis->pIfWinNt, pThis->Dbgc.pUVM, VMMR3GetVTable(),
+ &Resp.u.GetVersion.u64PtrKernBase,
+ &Resp.u.GetVersion.u64PtrPsLoadedModuleList);
+ }
+
+ /* Fill in the request specific part. */
+ Resp.u.GetVersion.u16VersMaj = NtBuildNumber >> 16;
+ Resp.u.GetVersion.u16VersMin = NtBuildNumber & UINT32_C(0xffff);
+ Resp.u.GetVersion.u8VersProtocol = 0x6; /* From a Windows 10 guest. */
+ Resp.u.GetVersion.u8VersKdSecondary = pThis->f32Bit ? 0 : 0x2; /* amd64 has a versioned context (0 and 1 are obsolete). */
+ Resp.u.GetVersion.fFlags = KD_PACKET_MANIPULATE64_GET_VERSION_F_MP;
+ Resp.u.GetVersion.u8MaxPktType = KD_PACKET_HDR_SUB_TYPE_MAX;
+ Resp.u.GetVersion.u8MaxStateChange = KD_PACKET_STATE_CHANGE_MAX - KD_PACKET_STATE_CHANGE_MIN;
+ Resp.u.GetVersion.u8MaxManipulate = KD_PACKET_MANIPULATE_REQ_MAX - KD_PACKET_MANIPULATE_REQ_MIN;
+ Resp.u.GetVersion.u64PtrDebuggerDataList = 0;
+
+ if (f32Bit)
+ {
+ Resp.u.GetVersion.u16MachineType = IMAGE_FILE_MACHINE_I386;
+ Resp.u.GetVersion.u64PtrKernBase = KD_PTR_CREATE(pThis, Resp.u.GetVersion.u64PtrKernBase);
+ Resp.u.GetVersion.u64PtrPsLoadedModuleList = KD_PTR_CREATE(pThis, Resp.u.GetVersion.u64PtrPsLoadedModuleList);
+ }
+ else
+ {
+ Resp.u.GetVersion.u16MachineType = IMAGE_FILE_MACHINE_AMD64;
+ Resp.u.GetVersion.fFlags |= KD_PACKET_MANIPULATE64_GET_VERSION_F_PTR64;
+ }
+
+ return dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &Resp, sizeof(Resp), true /*fAck*/);
+}
+
+
+/**
+ * Processes a read memory 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64ReadMem(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_XFERMEM64 XferMem64;
+ uint8_t abMem[_4K];
+ RT_ZERO(RespHdr); RT_ZERO(XferMem64);
+
+ DBGFADDRESS AddrRead;
+ uint32_t cbRead = RT_MIN(sizeof(abMem), pPktManip->u.XferMem.cbXferReq);
+ if (pPktManip->Hdr.idReq == KD_PACKET_MANIPULATE_REQ_READ_VIRT_MEM)
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrRead, KD_PTR_GET(pThis, pPktManip->u.XferMem.u64PtrTarget));
+ else
+ DBGFR3AddrFromPhys(pThis->Dbgc.pUVM, &AddrRead, KD_PTR_GET(pThis, pPktManip->u.XferMem.u64PtrTarget));
+
+ RTSGSEG aRespSegs[3];
+ uint32_t cSegs = 2; /* Gets incremented when read is successful. */
+ RespHdr.idReq = pPktManip->Hdr.idReq;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ XferMem64.u64PtrTarget = pPktManip->u.XferMem.u64PtrTarget;
+ XferMem64.cbXferReq = pPktManip->u.XferMem.cbXferReq;
+ XferMem64.cbXfered = (uint32_t)cbRead;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &XferMem64;
+ aRespSegs[1].cbSeg = sizeof(XferMem64);
+
+ int rc = DBGFR3MemRead(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrRead, &abMem[0], cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ cSegs++;
+ aRespSegs[2].pvSeg = &abMem[0];
+ aRespSegs[2].cbSeg = cbRead;
+ }
+ else
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL; /** @todo Convert to an appropriate NT status code. */
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], cSegs, true /*fAck*/);
+}
+
+
+/**
+ * Processes a write memory 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64WriteMem(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_XFERMEM64 XferMem64;
+ RT_ZERO(RespHdr); RT_ZERO(XferMem64);
+
+ DBGFADDRESS AddrWrite;
+ const void *pv = &pThis->abBody[sizeof(*pPktManip)]; /* Data comes directly after the manipulate state body. */
+ uint32_t cbWrite = RT_MIN(sizeof(pThis->abBody) - sizeof(*pPktManip), pPktManip->u.XferMem.cbXferReq);
+ if (pPktManip->Hdr.idReq == KD_PACKET_MANIPULATE_REQ_WRITE_VIRT_MEM)
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrWrite, KD_PTR_GET(pThis, pPktManip->u.XferMem.u64PtrTarget));
+ else
+ DBGFR3AddrFromPhys(pThis->Dbgc.pUVM, &AddrWrite, KD_PTR_GET(pThis, pPktManip->u.XferMem.u64PtrTarget));
+
+ RTSGSEG aRespSegs[2];
+ uint32_t cSegs = 2;
+ RespHdr.idReq = pPktManip->Hdr.idReq;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ XferMem64.u64PtrTarget = pPktManip->u.XferMem.u64PtrTarget;
+ XferMem64.cbXferReq = pPktManip->u.XferMem.cbXferReq;
+ XferMem64.cbXfered = (uint32_t)cbWrite;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &XferMem64;
+ aRespSegs[1].cbSeg = sizeof(XferMem64);
+
+ int rc = DBGFR3MemWrite(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrWrite, pv, cbWrite);
+ if (RT_FAILURE(rc))
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL; /** @todo Convert to an appropriate NT status code. */
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], cSegs, true /*fAck*/);
+}
+
+
+/**
+ * Processes a continue request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64Continue(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ RT_NOREF(pPktManip);
+ int rc = VINF_SUCCESS;
+
+ /* No response, just resume. */
+ if (DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ rc = DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL);
+
+ return rc;
+}
+
+
+/**
+ * Processes a continue request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64Continue2(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Update DR7. */
+ if (pThis->f32Bit)
+ rc = dbgcKdCtxHwBpDr7Update(pThis, pPktManip->u.Continue2.u.x86.u32RegDr7);
+ else
+ rc = dbgcKdCtxHwBpDr7Update(pThis, (uint32_t)pPktManip->u.Continue2.u.amd64.u64RegDr7);
+
+ /* Resume if not single stepping, the single step will get a state change when the VM stepped. */
+ if (pPktManip->u.Continue2.fTrace)
+ {
+ PDBGFADDRESS pStackPop = NULL;
+ RTGCPTR cbStackPop = 0;
+ rc = DBGFR3StepEx(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGF_STEP_F_INTO, NULL,
+ pStackPop, cbStackPop, 1 /*cMaxSteps*/);
+ }
+ else if (DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ rc = DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL);
+
+ return rc;
+}
+
+
+/**
+ * Processes a set context request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64SetContext(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_SETCONTEXT SetContext;
+ RT_ZERO(RespHdr); RT_ZERO(SetContext);
+
+ PCNTCONTEXT64 pNtCtx = (PCNTCONTEXT64)&pThis->abBody[sizeof(*pPktManip)]; /* Data comes directly after the manipulate state body. */
+
+ RTSGSEG aRespSegs[2];
+ uint32_t cSegs = 2;
+ RespHdr.idReq = pPktManip->Hdr.idReq;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ /** @todo What do these flags mean? Can't be the context state to set because the valid one is
+ * in NTCONTEXT64::fContext (observed with WinDbg). */
+ SetContext.u32CtxFlags = pPktManip->u.SetContext.u32CtxFlags;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &SetContext;
+ aRespSegs[1].cbSeg = sizeof(SetContext);
+
+ int rc = dbgcKdCtxSetNtCtx64(pThis, pPktManip->Hdr.idCpu, pNtCtx, pNtCtx->fContext);
+ if (RT_FAILURE(rc))
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL; /** @todo Convert to an appropriate NT status code. */
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], cSegs, true /*fAck*/);
+}
+
+
+/**
+ * Processes a read control space 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64ReadCtrlSpace(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_XFERCTRLSPACE64 XferCtrlSpace64;
+ uint8_t abResp[sizeof(NTKCONTEXT64)];
+ uint32_t cbData = 0;
+ RT_ZERO(RespHdr); RT_ZERO(XferCtrlSpace64);
+ RT_ZERO(abResp);
+
+ RTSGSEG aRespSegs[3];
+ uint32_t cSegs = 2; /* Gets incremented when read is successful. */
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ XferCtrlSpace64.u64IdXfer = pPktManip->u.XferCtrlSpace.u64IdXfer;
+ XferCtrlSpace64.cbXferReq = pPktManip->u.XferCtrlSpace.cbXferReq;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &XferCtrlSpace64;
+ aRespSegs[1].cbSeg = sizeof(XferCtrlSpace64);
+
+ int rc = VINF_SUCCESS;
+ if (pThis->f32Bit)
+ {
+ if (pPktManip->u.XferCtrlSpace.u64IdXfer == sizeof(NTCONTEXT32))
+ {
+ /* Queries the kernel context. */
+ rc = dbgcKdCtxQueryNtKCtx32(pThis, RespHdr.idCpu, (PNTKCONTEXT32)&abResp[0]);
+ if (RT_SUCCESS(rc))
+ cbData = sizeof(NTKCONTEXT32);
+ }
+ }
+ else
+ {
+ switch (pPktManip->u.XferCtrlSpace.u64IdXfer)
+ {
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KPCR:
+ {
+ if (pThis->pIfWinNt)
+ {
+ RTGCUINTPTR GCPtrKpcr = 0;
+
+ rc = pThis->pIfWinNt->pfnQueryKpcrForVCpu(pThis->pIfWinNt, pThis->Dbgc.pUVM, VMMR3GetVTable(), RespHdr.idCpu,
+ &GCPtrKpcr, NULL /*pKpcrb*/);
+ if (RT_SUCCESS(rc))
+ memcpy(&abResp[0], &GCPtrKpcr, sizeof(GCPtrKpcr));
+ }
+
+ cbData = sizeof(RTGCUINTPTR);
+ break;
+ }
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KPCRB:
+ {
+ if (pThis->pIfWinNt)
+ {
+ RTGCUINTPTR GCPtrKpcrb = 0;
+
+ rc = pThis->pIfWinNt->pfnQueryKpcrForVCpu(pThis->pIfWinNt, pThis->Dbgc.pUVM, VMMR3GetVTable(), RespHdr.idCpu,
+ NULL /*pKpcr*/, &GCPtrKpcrb);
+ if (RT_SUCCESS(rc))
+ memcpy(&abResp[0], &GCPtrKpcrb, sizeof(GCPtrKpcrb));
+ }
+
+ cbData = sizeof(RTGCUINTPTR);
+ break;
+ }
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KCTX:
+ {
+ rc = dbgcKdCtxQueryNtKCtx64(pThis, RespHdr.idCpu, (PNTKCONTEXT64)&abResp[0], NTCONTEXT64_F_FULL);
+ if (RT_SUCCESS(rc))
+ cbData = sizeof(NTKCONTEXT64);
+ break;
+ }
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KTHRD:
+ {
+ if (pThis->pIfWinNt)
+ {
+ RTGCUINTPTR GCPtrCurThrd = 0;
+
+ rc = pThis->pIfWinNt->pfnQueryCurThrdForVCpu(pThis->pIfWinNt, pThis->Dbgc.pUVM, VMMR3GetVTable(),
+ RespHdr.idCpu, &GCPtrCurThrd);
+ if (RT_SUCCESS(rc))
+ memcpy(&abResp[0], &GCPtrCurThrd, sizeof(GCPtrCurThrd));
+ }
+
+ cbData = sizeof(RTGCUINTPTR);
+ break;
+ }
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && cbData)
+ {
+ XferCtrlSpace64.cbXfered = RT_MIN(cbData, XferCtrlSpace64.cbXferReq);
+
+ cSegs++;
+ aRespSegs[2].pvSeg = &abResp[0];
+ aRespSegs[2].cbSeg = cbData;
+ }
+ else if (RT_FAILURE(rc))
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL; /** @todo Convert to an appropriate NT status code. */
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], cSegs, true /*fAck*/);
+}
+
+
+/**
+ * Processes a write control space 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64WriteCtrlSpace(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_XFERCTRLSPACE64 XferCtrlSpace64;
+ uint32_t cbData = 0;
+ RT_ZERO(RespHdr); RT_ZERO(XferCtrlSpace64);
+
+ RTSGSEG aRespSegs[2];
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_WRITE_CTRL_SPACE;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ XferCtrlSpace64.u64IdXfer = pPktManip->u.XferCtrlSpace.u64IdXfer;
+ XferCtrlSpace64.cbXferReq = pPktManip->u.XferCtrlSpace.cbXferReq;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &XferCtrlSpace64;
+ aRespSegs[1].cbSeg = sizeof(XferCtrlSpace64);
+
+ int rc = VINF_SUCCESS;
+ switch (pPktManip->u.XferCtrlSpace.u64IdXfer)
+ {
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KCTX:
+ {
+ PCNTKCONTEXT64 pNtKCtx = (PCNTKCONTEXT64)&pThis->abBody[sizeof(*pPktManip)]; /* Data comes directly after the manipulate state body. */
+ rc = dbgcKdCtxSetNtKCtx64(pThis, RespHdr.idCpu, pNtKCtx, XferCtrlSpace64.cbXferReq);
+ if (RT_SUCCESS(rc))
+ cbData = RT_MIN(XferCtrlSpace64.cbXferReq, sizeof(NTKCONTEXT64));
+ break;
+ }
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KPCR:
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KPCRB:
+ case KD_PACKET_MANIPULATE64_CTRL_SPACE_ID_KTHRD:
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL; /** @todo Convert to an appropriate NT status code. */
+ else
+ XferCtrlSpace64.cbXfered = cbData;
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+}
+
+
+/**
+ * Processes a restore breakpoint 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64RestoreBkpt(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_RESTOREBKPT64 RestoreBkpt64;
+ RT_ZERO(RespHdr); RT_ZERO(RestoreBkpt64);
+
+ RTSGSEG aRespSegs[2];
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_RESTORE_BKPT;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ RestoreBkpt64.u32HndBkpt = pPktManip->u.RestoreBkpt.u32HndBkpt;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &RestoreBkpt64;
+ aRespSegs[1].cbSeg = sizeof(RestoreBkpt64);
+
+ int rc = DBGFR3BpClear(pThis->Dbgc.pUVM, pPktManip->u.RestoreBkpt.u32HndBkpt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcBpDelete(&pThis->Dbgc, pPktManip->u.RestoreBkpt.u32HndBkpt);
+ AssertRC(rc);
+ }
+ else if (rc != VERR_DBGF_BP_NOT_FOUND)
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL;
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+}
+
+
+/**
+ * Processes a write breakpoint 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64WriteBkpt(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_WRITEBKPT64 WriteBkpt64;
+ RT_ZERO(RespHdr); RT_ZERO(WriteBkpt64);
+
+ RTSGSEG aRespSegs[2];
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_WRITE_BKPT;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &WriteBkpt64;
+ aRespSegs[1].cbSeg = sizeof(WriteBkpt64);
+
+ WriteBkpt64.u64PtrBkpt = pPktManip->u.WriteBkpt.u64PtrBkpt;
+
+ DBGFADDRESS BpAddr;
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &BpAddr, KD_PTR_GET(pThis, pPktManip->u.WriteBkpt.u64PtrBkpt));
+ int rc = DBGFR3BpSetInt3(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &BpAddr,
+ 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/, &WriteBkpt64.u32HndBkpt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcBpAdd(&pThis->Dbgc, WriteBkpt64.u32HndBkpt, NULL /*pszCmd*/);
+ if (RT_FAILURE(rc))
+ DBGFR3BpClear(pThis->Dbgc.pUVM, WriteBkpt64.u32HndBkpt);
+ }
+ else
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL;
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+}
+
+
+/**
+ * Processes a get context extended 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64GetContextEx(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_CONTEXTEX ContextEx;
+ union
+ {
+ NTCONTEXT64 v64;
+ NTCONTEXT32 v32;
+ } NtCtx;
+ RT_ZERO(RespHdr); RT_ZERO(ContextEx); RT_ZERO(NtCtx);
+
+ RTSGSEG aRespSegs[3];
+ uint32_t cSegs = 2;
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_GET_CONTEXT_EX;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL;
+
+ ContextEx.offStart = pPktManip->u.ContextEx.offStart;
+ ContextEx.cbXfer = pPktManip->u.ContextEx.cbXfer;
+ ContextEx.cbXfered = 0;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &ContextEx;
+ aRespSegs[1].cbSeg = sizeof(ContextEx);
+
+ int rc = VINF_SUCCESS;
+ uint32_t cbCtx = pThis->f32Bit ? sizeof(NtCtx.v32) : sizeof(NtCtx.v64);
+ if (pThis->f32Bit)
+ dbgcKdCtxQueryNtCtx32(pThis, pPktManip->Hdr.idCpu, &NtCtx.v32, NTCONTEXT32_F_FULL);
+ else
+ dbgcKdCtxQueryNtCtx64(pThis, pPktManip->Hdr.idCpu, &NtCtx.v64, NTCONTEXT64_F_FULL);
+ if ( RT_SUCCESS(rc)
+ && pPktManip->u.ContextEx.offStart < cbCtx)
+ {
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+ ContextEx.cbXfered = RT_MIN(cbCtx - ContextEx.offStart, ContextEx.cbXfer);
+
+ aRespSegs[2].pvSeg = (uint8_t *)&NtCtx + ContextEx.offStart;
+ aRespSegs[2].cbSeg = ContextEx.cbXfered;
+ cSegs++;
+ }
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], cSegs, true /*fAck*/);
+}
+
+
+/**
+ * Processes a query memory 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64QueryMemory(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_QUERYMEMORY QueryMemory;
+ RT_ZERO(RespHdr); RT_ZERO(QueryMemory);
+
+ RTSGSEG aRespSegs[2];
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_QUERY_MEMORY;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ /** @todo Need DBGF API to query protection and privilege level from guest page tables. */
+ QueryMemory.u64GCPtr = pPktManip->u.QueryMemory.u64GCPtr;
+ QueryMemory.u32AddrSpace = KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_SPACE_KERNEL;
+ QueryMemory.u32Flags = KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_READ
+ | KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_WRITE
+ | KD_PACKET_MANIPULATE64_QUERY_MEMORY_ADDR_F_EXEC;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &QueryMemory;
+ aRespSegs[1].cbSeg = sizeof(QueryMemory);
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+}
+
+
+/**
+ * Processes a search memory 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64SearchMemory(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ KDPACKETMANIPULATEHDR RespHdr;
+ KDPACKETMANIPULATE_SEARCHMEMORY SearchMemory;
+ RT_ZERO(RespHdr); RT_ZERO(SearchMemory);
+
+ RTSGSEG aRespSegs[2];
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_SEARCH_MEMORY;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_SUCCESS;
+
+ SearchMemory.u64GCPtr = pPktManip->u.SearchMemory.u64GCPtr;
+ SearchMemory.cbSearch = pPktManip->u.SearchMemory.cbSearch;
+ SearchMemory.cbPattern = pPktManip->u.SearchMemory.cbPattern;
+
+ /* Validate the pattern length and start searching. */
+ if (pPktManip->u.SearchMemory.cbPattern < sizeof(pThis->abBody) - sizeof(*pPktManip))
+ {
+ DBGFADDRESS StartAddress;
+ DBGFADDRESS HitAddress;
+ VMCPUID idCpu = pPktManip->Hdr.idCpu;
+ DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &StartAddress, pPktManip->u.SearchMemory.u64GCPtr);
+
+ /** @todo WindDbg sends CPU ID 32 sometimes, maybe that means continue search on last used CPU?. */
+ if (idCpu >= DBGFR3CpuGetCount(pThis->Dbgc.pUVM))
+ idCpu = pThis->Dbgc.idCpu;
+
+ int rc = DBGFR3MemScan(pThis->Dbgc.pUVM, idCpu, &StartAddress, pPktManip->u.SearchMemory.cbSearch, 1,
+ &pThis->abBody[sizeof(*pPktManip)], pPktManip->u.SearchMemory.cbPattern, &HitAddress);
+ if (RT_SUCCESS(rc))
+ SearchMemory.u64GCPtr = HitAddress.FlatPtr;
+ else if (rc == VERR_DBGF_MEM_NOT_FOUND)
+ RespHdr.u32NtStatus = NTSTATUS_NOT_FOUND;
+ else
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL;
+ }
+ else
+ RespHdr.u32NtStatus = NTSTATUS_BUFFER_OVERFLOW;
+
+ aRespSegs[0].pvSeg = &RespHdr;
+ aRespSegs[0].cbSeg = sizeof(RespHdr);
+ aRespSegs[1].pvSeg = &SearchMemory;
+ aRespSegs[1].cbSeg = sizeof(SearchMemory);
+
+ return dbgcKdCtxPktSendSg(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &aRespSegs[0], RT_ELEMENTS(aRespSegs), true /*fAck*/);
+}
+
+
+/**
+ * Processes a cause bugcheck 64 request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ *
+ * @note We abuse this request to initiate a native VBox debugger command prompt from the remote end
+ * (There is monitor/Rcmd equivalent like with GDB unfortunately).
+ */
+static int dbgcKdCtxPktManipulate64CauseBugCheck(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ RT_NOREF(pPktManip);
+ pThis->fInVBoxDbg = true;
+ return dbgcKdCtxDebugIoGetStrSend(pThis, pThis->Dbgc.idCpu, "VBoxDbg>", sizeof("VBoxDbg>") - 1,
+ 512 /*cbResponseMax*/);
+}
+
+
+/**
+ * Processes a switch processor request.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ * @param pPktManip The manipulate packet request.
+ */
+static int dbgcKdCtxPktManipulate64SwitchProcessor(PKDCTX pThis, PCKDPACKETMANIPULATE64 pPktManip)
+{
+ int rc = VINF_SUCCESS;
+
+ if (RT_UNLIKELY(pPktManip->Hdr.idCpu >= DBGFR3CpuGetCount(pThis->Dbgc.pUVM)))
+ {
+ KDPACKETMANIPULATEHDR RespHdr;
+ RT_ZERO(RespHdr);
+
+ RespHdr.idReq = KD_PACKET_MANIPULATE_REQ_SWITCH_PROCESSOR;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_UNSUCCESSFUL; /** @todo Test this path. */
+ rc = dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &RespHdr, sizeof(RespHdr), true /*fAck*/);
+ }
+ else
+ {
+ pThis->Dbgc.idCpu = pPktManip->Hdr.idCpu;
+ rc = dbgcKdCtxStateChangeSend(pThis, DBGFEVENT_HALT_DONE);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes a manipulate packet.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ */
+static int dbgcKdCtxPktManipulate64Process(PKDCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+ PCKDPACKETMANIPULATE64 pPktManip = (PCKDPACKETMANIPULATE64)&pThis->abBody[0];
+
+ switch (pPktManip->Hdr.idReq)
+ {
+ case KD_PACKET_MANIPULATE_REQ_GET_VERSION:
+ {
+ rc = dbgcKdCtxPktManipulate64GetVersion(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_READ_VIRT_MEM:
+ case KD_PACKET_MANIPULATE_REQ_READ_PHYS_MEM:
+ {
+ rc = dbgcKdCtxPktManipulate64ReadMem(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_WRITE_VIRT_MEM:
+ case KD_PACKET_MANIPULATE_REQ_WRITE_PHYS_MEM:
+ {
+ rc = dbgcKdCtxPktManipulate64WriteMem(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_CONTINUE:
+ {
+ rc = dbgcKdCtxPktManipulate64Continue(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_CONTINUE2:
+ {
+ rc = dbgcKdCtxPktManipulate64Continue2(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_SET_CONTEXT:
+ {
+ rc = dbgcKdCtxPktManipulate64SetContext(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_READ_CTRL_SPACE:
+ {
+ rc = dbgcKdCtxPktManipulate64ReadCtrlSpace(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_WRITE_CTRL_SPACE:
+ {
+ rc = dbgcKdCtxPktManipulate64WriteCtrlSpace(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_RESTORE_BKPT:
+ {
+ rc = dbgcKdCtxPktManipulate64RestoreBkpt(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_WRITE_BKPT:
+ {
+ rc = dbgcKdCtxPktManipulate64WriteBkpt(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_CLEAR_ALL_INTERNAL_BKPT:
+ /* WinDbg doesn't seem to expect an answer apart from the ACK here. */
+ break;
+ case KD_PACKET_MANIPULATE_REQ_GET_CONTEXT_EX:
+ {
+ rc = dbgcKdCtxPktManipulate64GetContextEx(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_QUERY_MEMORY:
+ {
+ rc = dbgcKdCtxPktManipulate64QueryMemory(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_SEARCH_MEMORY:
+ {
+ rc = dbgcKdCtxPktManipulate64SearchMemory(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_CAUSE_BUGCHECK:
+ {
+ rc = dbgcKdCtxPktManipulate64CauseBugCheck(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_SWITCH_PROCESSOR:
+ {
+ rc = dbgcKdCtxPktManipulate64SwitchProcessor(pThis, pPktManip);
+ break;
+ }
+ case KD_PACKET_MANIPULATE_REQ_REBOOT:
+ {
+ rc = VMR3Reset(pThis->Dbgc.pUVM); /* Doesn't expect an answer here. */
+ if ( RT_SUCCESS(rc)
+ && DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL))
+ rc = DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ break;
+ }
+ default:
+ KDPACKETMANIPULATEHDR RespHdr;
+ RT_ZERO(RespHdr);
+
+ RespHdr.idReq = pPktManip->Hdr.idReq;
+ RespHdr.u16CpuLvl = pPktManip->Hdr.u16CpuLvl;
+ RespHdr.idCpu = pPktManip->Hdr.idCpu;
+ RespHdr.u32NtStatus = NTSTATUS_NOT_IMPLEMENTED;
+ rc = dbgcKdCtxPktSend(pThis, KD_PACKET_HDR_SIGNATURE_DATA, KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE,
+ &RespHdr, sizeof(RespHdr), true /*fAck*/);
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Tries to detect the guest OS running in the VM looking specifically for the Windows NT kind.
+ *
+ * @param pThis The KD context.
+ */
+static void dbgcKdCtxDetectGstOs(PKDCTX pThis)
+{
+ pThis->pIfWinNt = NULL;
+
+ /* Try detecting a Windows NT guest. */
+ char szName[64];
+ int rc = DBGFR3OSDetect(pThis->Dbgc.pUVM, szName, sizeof(szName));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pIfWinNt = (PDBGFOSIWINNT)DBGFR3OSQueryInterface(pThis->Dbgc.pUVM, DBGFOSINTERFACE_WINNT);
+ if (pThis->pIfWinNt)
+ LogRel(("DBGC/Kd: Detected Windows NT guest OS (%s)\n", &szName[0]));
+ else
+ LogRel(("DBGC/Kd: Detected guest OS is not of the Windows NT kind (%s)\n", &szName[0]));
+ }
+ else
+ {
+ LogRel(("DBGC/Kd: Unable to detect any guest operating system type, rc=%Rrc\n", rc));
+ rc = VINF_SUCCESS; /* Try to continue nevertheless. */
+ }
+
+ if (pThis->pIfWinNt)
+ {
+ rc = pThis->pIfWinNt->pfnQueryVersion(pThis->pIfWinNt, pThis->Dbgc.pUVM, VMMR3GetVTable(),
+ NULL /*puVersMajor*/, NULL /*puVersMinor*/,
+ NULL /*puBuildNumber*/, &pThis->f32Bit);
+ AssertRC(rc);
+ }
+ else
+ {
+ /*
+ * Try to detect bitness based on the current CPU mode which might fool us (32bit process running
+ * inside of 64bit host).
+ */
+ CPUMMODE enmMode = DBGCCmdHlpGetCpuMode(&pThis->Dbgc.CmdHlp);
+ if (enmMode == CPUMMODE_PROTECTED)
+ pThis->f32Bit = true;
+ else if (enmMode == CPUMMODE_LONG)
+ pThis->f32Bit = false;
+ else
+ LogRel(("DBGC/Kd: Heh, trying to debug real mode code with WinDbg are we? Good luck with that...\n"));
+ }
+}
+
+
+/**
+ * Processes a fully received packet.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ */
+static int dbgcKdCtxPktProcess(PKDCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ pThis->fBreakinRecv = false;
+
+ /* Verify checksum. */
+ if (dbgcKdPktChkSumGen(&pThis->abBody[0], pThis->PktHdr.Fields.cbBody) == pThis->PktHdr.Fields.u32ChkSum)
+ {
+ /** @todo Check packet id. */
+ if (pThis->PktHdr.Fields.u16SubType != KD_PACKET_HDR_SUB_TYPE_RESET)
+ {
+ pThis->idPktNext = pThis->PktHdr.Fields.idPacket;
+ rc = dbgcKdCtxPktSendAck(pThis);
+ }
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ RTSGSEG Seg;
+ Seg.pvSeg = &pThis->abBody[0];
+ Seg.cbSeg = pThis->PktHdr.Fields.cbBody;
+ dbgcKdPktDump(&pThis->PktHdr.Fields, &Seg, 1 /*cSegs*/, true /*fRx*/);
+#endif
+
+ switch (pThis->PktHdr.Fields.u16SubType)
+ {
+ case KD_PACKET_HDR_SUB_TYPE_RESET:
+ {
+ dbgcKdCtxDetectGstOs(pThis);
+
+ pThis->idPktNext = 0;
+ rc = dbgcKdCtxPktSendReset(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ if (rc == VWRN_DBGF_ALREADY_HALTED)
+ rc = dbgcKdCtxStateChangeSend(pThis, DBGFEVENT_HALT_DONE);
+ }
+ pThis->idPktNext = KD_PACKET_HDR_ID_RESET;
+ break;
+ }
+ case KD_PACKET_HDR_SUB_TYPE_STATE_MANIPULATE:
+ {
+ pThis->idPktNext = pThis->PktHdr.Fields.idPacket ^ 0x1;
+ rc = dbgcKdCtxPktManipulate64Process(pThis);
+ break;
+ }
+ case KD_PACKET_HDR_SUB_TYPE_ACKNOWLEDGE:
+ case KD_PACKET_HDR_SUB_TYPE_RESEND:
+ {
+ /* Don't do anything. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+ case KD_PACKET_HDR_SUB_TYPE_DEBUG_IO:
+ {
+ if (pThis->fInVBoxDbg)
+ {
+ pThis->idPktNext = pThis->PktHdr.Fields.idPacket ^ 0x1;
+ /* Get the string and execute it. */
+ PCKDPACKETDEBUGIO pPktDbgIo = (PCKDPACKETDEBUGIO)&pThis->abBody[0];
+ if ( pPktDbgIo->u32Type == KD_PACKET_DEBUG_IO_GET_STRING
+ && pPktDbgIo->u.Prompt.cbReturn <= sizeof(pThis->abBody) - sizeof(*pPktDbgIo) - 1)
+ {
+ if (pPktDbgIo->u.Prompt.cbReturn)
+ {
+ /* Terminate return value. */
+ pThis->abBody[sizeof(*pPktDbgIo) + pPktDbgIo->u.Prompt.cbReturn] = '\0';
+
+ const char *pszCmd = (const char *)&pThis->abBody[sizeof(*pPktDbgIo)];
+ /* Filter out 'exit' which is handled here directly and exits the debug loop. */
+ if (!strcmp(pszCmd, "exit"))
+ pThis->fInVBoxDbg = false;
+ else
+ {
+ rc = pThis->Dbgc.CmdHlp.pfnExec(&pThis->Dbgc.CmdHlp, pszCmd);
+ if (RT_SUCCESS(rc))
+ rc = dbgcKdCtxDebugIoGetStrSend(pThis, pThis->Dbgc.idCpu, "VBoxDbg>", sizeof("VBoxDbg>") - 1,
+ 512 /*cbResponseMax*/);
+ else
+ LogRel(("DBGC/Kd: Executing command \"%s\" failed with rc=%Rrc\n", pszCmd, rc));
+ }
+ }
+ else
+ rc = dbgcKdCtxDebugIoGetStrSend(pThis, pThis->Dbgc.idCpu, "VBoxDbg>", sizeof("VBoxDbg>") - 1,
+ 512 /*cbResponseMax*/);
+ }
+ else
+ LogRel(("DBGC/Kd: Received invalid DEBUG_IO packet from remote end, ignoring\n"));
+ }
+ else
+ LogRel(("DBGC/Kd: Received out of band DEBUG_IO packet from remote end, ignoring\n"));
+ break;
+ }
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+ }
+ else
+ {
+ pThis->idPktNext = pThis->PktHdr.Fields.idPacket;
+ rc = dbgcKdCtxPktSendResend(pThis);
+ }
+
+ if (pThis->fBreakinRecv)
+ {
+ pThis->fBreakinRecv = false;
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ if (rc == VWRN_DBGF_ALREADY_HALTED)
+ rc = dbgcKdCtxStateChangeSend(pThis, DBGFEVENT_HALT_DONE);
+ }
+
+ /* Next packet. */
+ dbgcKdCtxPktRecvReset(pThis);
+ return rc;
+}
+
+
+/**
+ * Processes the received data based on the current state.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context.
+ */
+static int dbgcKdCtxRecvDataProcess(PKDCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pThis->enmState)
+ {
+ case KDRECVSTATE_PACKET_HDR_FIRST_BYTE:
+ {
+ /* Does it look like a valid packet start?. */
+ if ( pThis->PktHdr.ab[0] == KD_PACKET_HDR_SIGNATURE_DATA_BYTE
+ || pThis->PktHdr.ab[0] == KD_PACKET_HDR_SIGNATURE_CONTROL_BYTE)
+ {
+ pThis->pbRecv = &pThis->PktHdr.ab[1];
+ pThis->cbRecvLeft = sizeof(pThis->PktHdr.ab[1]);
+ pThis->enmState = KDRECVSTATE_PACKET_HDR_SECOND_BYTE;
+ pThis->msRecvTimeout = DBGC_KD_RECV_TIMEOUT_MS;
+ }
+ else if (pThis->PktHdr.ab[0] == KD_PACKET_HDR_SIGNATURE_BREAKIN_BYTE)
+ {
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ if (rc == VWRN_DBGF_ALREADY_HALTED)
+ rc = dbgcKdCtxStateChangeSend(pThis, DBGFEVENT_HALT_DONE);
+ dbgcKdCtxPktRecvReset(pThis);
+ }
+ else
+ dbgcKdCtxPktRecvReset(pThis); /* Reset and continue. */
+ break;
+ }
+ case KDRECVSTATE_PACKET_HDR_SECOND_BYTE:
+ {
+ /*
+ * If the first and second byte differ there might be a single breakin
+ * packet byte received and this is actually the start of a new packet.
+ */
+ if (pThis->PktHdr.ab[0] != pThis->PktHdr.ab[1])
+ {
+ if (pThis->PktHdr.ab[0] == KD_PACKET_HDR_SIGNATURE_BREAKIN_BYTE)
+ {
+ /* Halt the VM and rearrange the packet receiving state machine. */
+ LogFlow(("DbgKd: Halting VM!\n"));
+
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ pThis->PktHdr.ab[0] = pThis->PktHdr.ab[1]; /* Overwrite the first byte with the new start. */
+ pThis->pbRecv = &pThis->PktHdr.ab[1];
+ pThis->cbRecvLeft = sizeof(pThis->PktHdr.ab[1]);
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR; /* Refuse talking to the remote end any further. */
+ }
+ else
+ {
+ /* Normal packet receive continues with the rest of the header. */
+ pThis->pbRecv = &pThis->PktHdr.ab[2];
+ pThis->cbRecvLeft = sizeof(pThis->PktHdr.Fields) - 2;
+ pThis->enmState = KDRECVSTATE_PACKET_HDR;
+ }
+ break;
+ }
+ case KDRECVSTATE_PACKET_HDR:
+ {
+ if ( dbgcKdPktHdrValidate(&pThis->PktHdr.Fields)
+ && pThis->PktHdr.Fields.cbBody <= sizeof(pThis->abBody))
+ {
+ /* Start receiving the body. */
+ if (pThis->PktHdr.Fields.cbBody)
+ {
+ pThis->pbRecv = &pThis->abBody[0];
+ pThis->cbRecvLeft = pThis->PktHdr.Fields.cbBody;
+ pThis->enmState = KDRECVSTATE_PACKET_BODY;
+ }
+ else /* No body means no trailer byte it looks like. */
+ rc = dbgcKdCtxPktProcess(pThis);
+ }
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ break;
+ }
+ case KDRECVSTATE_PACKET_BODY:
+ {
+ pThis->enmState = KDRECVSTATE_PACKET_TRAILER;
+ pThis->bTrailer = 0;
+ pThis->pbRecv = &pThis->bTrailer;
+ pThis->cbRecvLeft = sizeof(pThis->bTrailer);
+ break;
+ }
+ case KDRECVSTATE_PACKET_TRAILER:
+ {
+ if (pThis->bTrailer == KD_PACKET_TRAILING_BYTE)
+ rc = dbgcKdCtxPktProcess(pThis);
+ else
+ rc = VERR_NET_PROTOCOL_ERROR;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid receive state %d\n", pThis->enmState));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Receive data and processes complete packets.
+ *
+ * @returns Status code.
+ * @param pThis The KD context.
+ */
+static int dbgcKdCtxRecv(PKDCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p{.cbRecvLeft=%zu}\n", pThis, pThis->cbRecvLeft));
+
+ if (pThis->cbRecvLeft)
+ {
+ size_t cbRead = 0;
+ rc = pThis->Dbgc.pIo->pfnRead(pThis->Dbgc.pIo, pThis->pbRecv, pThis->cbRecvLeft, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->tsRecvLast = RTTimeMilliTS();
+ pThis->cbRecvLeft -= cbRead;
+ pThis->pbRecv += cbRead;
+ if (!pThis->cbRecvLeft)
+ rc = dbgcKdCtxRecvDataProcess(pThis);
+ }
+ }
+
+ LogFlowFunc(("returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Processes debugger events.
+ *
+ * @returns VBox status code.
+ * @param pThis The KD context data.
+ * @param pEvent Pointer to event data.
+ */
+static int dbgcKdCtxProcessEvent(PKDCTX pThis, PCDBGFEVENT pEvent)
+{
+ /*
+ * Process the event.
+ */
+ PDBGC pDbgc = &pThis->Dbgc;
+ pThis->Dbgc.pszScratch = &pThis->Dbgc.achInput[0];
+ pThis->Dbgc.iArg = 0;
+ int rc = VINF_SUCCESS;
+ VMCPUID idCpuOld = pDbgc->idCpu;
+ pDbgc->idCpu = pEvent->idCpu;
+ switch (pEvent->enmType)
+ {
+ /*
+ * The first part is events we have initiated with commands.
+ */
+ case DBGFEVENT_HALT_DONE:
+ {
+ rc = dbgcKdCtxStateChangeSend(pThis, pEvent->enmType);
+ break;
+ }
+
+ /*
+ * The second part is events which can occur at any time.
+ */
+ case DBGFEVENT_FATAL_ERROR:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbf event: Fatal error! (%s)\n",
+ dbgcGetEventCtx(pEvent->enmCtx));
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+ break;
+ }
+
+ case DBGFEVENT_BREAKPOINT:
+ case DBGFEVENT_BREAKPOINT_IO:
+ case DBGFEVENT_BREAKPOINT_MMIO:
+ case DBGFEVENT_BREAKPOINT_HYPER:
+ {
+ rc = dbgcBpExec(pDbgc, pEvent->u.Bp.hBp);
+ switch (rc)
+ {
+ case VERR_DBGC_BP_NOT_FOUND:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Unknown breakpoint %u! (%s)\n",
+ pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ case VINF_DBGC_BP_NO_COMMAND:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Breakpoint %u! (%s)\n",
+ pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ case VINF_BUFFER_OVERFLOW:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Breakpoint %u! Command too long to execute! (%s)\n",
+ pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ default:
+ break;
+ }
+ if (RT_SUCCESS(rc) && DBGFR3IsHalted(pDbgc->pUVM, VMCPUID_ALL))
+ {
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+
+ /* Set the resume flag to ignore the breakpoint when resuming execution. */
+ if ( RT_SUCCESS(rc)
+ && pEvent->enmType == DBGFEVENT_BREAKPOINT)
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r eflags.rf = 1");
+ }
+
+ /* Figure out the breakpoint and set the triggered flag for emulation of DR6. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aHwBp); i++)
+ {
+ if (pThis->aHwBp[i].hDbgfBp == pEvent->u.Bp.hBp)
+ {
+ pThis->aHwBp[i].fTriggered = true;
+ break;
+ }
+ }
+
+ rc = dbgcKdCtxStateChangeSend(pThis, pEvent->enmType);
+ break;
+ }
+
+ case DBGFEVENT_STEPPED:
+ case DBGFEVENT_STEPPED_HYPER:
+ {
+ pThis->fSingleStepped = true; /* For emulation of DR6. */
+ rc = dbgcKdCtxStateChangeSend(pThis, pEvent->enmType);
+ break;
+ }
+
+ case DBGFEVENT_ASSERTION_HYPER:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "\ndbgf event: Hypervisor Assertion! (%s)\n"
+ "%s"
+ "%s"
+ "\n",
+ dbgcGetEventCtx(pEvent->enmCtx),
+ pEvent->u.Assert.pszMsg1,
+ pEvent->u.Assert.pszMsg2);
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+ break;
+ }
+
+ case DBGFEVENT_DEV_STOP:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "\n"
+ "dbgf event: DBGFSTOP (%s)\n"
+ "File: %s\n"
+ "Line: %d\n"
+ "Function: %s\n",
+ dbgcGetEventCtx(pEvent->enmCtx),
+ pEvent->u.Src.pszFile,
+ pEvent->u.Src.uLine,
+ pEvent->u.Src.pszFunction);
+ if (RT_SUCCESS(rc) && pEvent->u.Src.pszMessage && *pEvent->u.Src.pszMessage)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "Message: %s\n",
+ pEvent->u.Src.pszMessage);
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r");
+ break;
+ }
+
+
+ case DBGFEVENT_INVALID_COMMAND:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Invalid command event!\n");
+ break;
+ }
+
+ case DBGFEVENT_POWERING_OFF:
+ {
+ pThis->Dbgc.fReady = false;
+ pThis->Dbgc.pIo->pfnSetReady(pThis->Dbgc.pIo, false);
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+ default:
+ {
+ /*
+ * Probably a generic event. Look it up to find its name.
+ */
+ PCDBGCSXEVT pEvtDesc = dbgcEventLookup(pEvent->enmType);
+ if (pEvtDesc)
+ {
+ if (pEvtDesc->enmKind == kDbgcSxEventKind_Interrupt)
+ {
+ Assert(pEvtDesc->pszDesc);
+ Assert(pEvent->u.Generic.cArgs == 1);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s no %#llx! (%s)\n",
+ pEvtDesc->pszDesc, pEvent->u.Generic.auArgs[0], pEvtDesc->pszName);
+ }
+ else if (pEvtDesc->fFlags & DBGCSXEVT_F_BUGCHECK)
+ {
+ Assert(pEvent->u.Generic.cArgs >= 5);
+ char szDetails[512];
+ DBGFR3FormatBugCheck(pDbgc->pUVM, szDetails, sizeof(szDetails), pEvent->u.Generic.auArgs[0],
+ pEvent->u.Generic.auArgs[1], pEvent->u.Generic.auArgs[2],
+ pEvent->u.Generic.auArgs[3], pEvent->u.Generic.auArgs[4]);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s %s%s!\n%s", pEvtDesc->pszName,
+ pEvtDesc->pszDesc ? "- " : "", pEvtDesc->pszDesc ? pEvtDesc->pszDesc : "",
+ szDetails);
+ }
+ else if ( (pEvtDesc->fFlags & DBGCSXEVT_F_TAKE_ARG)
+ || pEvent->u.Generic.cArgs > 1
+ || ( pEvent->u.Generic.cArgs == 1
+ && pEvent->u.Generic.auArgs[0] != 0))
+ {
+ if (pEvtDesc->pszDesc)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s - %s!",
+ pEvtDesc->pszName, pEvtDesc->pszDesc);
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s!", pEvtDesc->pszName);
+ if (pEvent->u.Generic.cArgs <= 1)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " arg=%#llx\n", pEvent->u.Generic.auArgs[0]);
+ else
+ {
+ for (uint32_t i = 0; i < pEvent->u.Generic.cArgs; i++)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " args[%u]=%#llx", i, pEvent->u.Generic.auArgs[i]);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\n");
+ }
+ }
+ else
+ {
+ if (pEvtDesc->pszDesc)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s - %s!\n",
+ pEvtDesc->pszName, pEvtDesc->pszDesc);
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s!\n", pEvtDesc->pszName);
+ }
+ }
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Unknown event %d!\n", pEvent->enmType);
+ break;
+ }
+ }
+
+ pDbgc->idCpu = idCpuOld;
+ return rc;
+}
+
+
+/**
+ * Handle a receive timeout.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the KD context.
+ */
+static int dbgcKdCtxRecvTimeout(PKDCTX pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("pThis=%p\n", pThis));
+
+ /*
+ * If a single breakin packet byte was received but the header is otherwise incomplete
+ * the VM is halted and a state change will be sent in the event processing loop.
+ */
+ if ( pThis->enmState == KDRECVSTATE_PACKET_HDR_SECOND_BYTE
+ && pThis->PktHdr.ab[0] == KD_PACKET_HDR_SIGNATURE_BREAKIN_BYTE)
+ {
+ LogFlow(("DbgKd: Halting VM!\n"));
+ rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL);
+ }
+ else /* Send a reset packet */ /** @todo Figure out the semantics in this case exactly. */
+ rc = dbgcKdCtxPktSendReset(pThis);
+
+ dbgcKdCtxPktRecvReset(pThis);
+
+ LogFlowFunc(("rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGC::pfnOutput
+ */
+static DECLCALLBACK(int) dbgcKdOutput(void *pvUser, const char *pachChars, size_t cbChars)
+{
+ PKDCTX pThis = (PKDCTX)pvUser;
+
+ return dbgcKdCtxDebugIoStrSend(pThis, pThis->Dbgc.idCpu, pachChars, cbChars);
+}
+
+
+/**
+ * Run the debugger console.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the KD context.
+ */
+int dbgcKdRun(PKDCTX pThis)
+{
+ /*
+ * We're ready for commands now.
+ */
+ pThis->Dbgc.fReady = true;
+ pThis->Dbgc.pIo->pfnSetReady(pThis->Dbgc.pIo, true);
+
+ /*
+ * Main Debugger Loop.
+ *
+ * This loop will either block on waiting for input or on waiting on
+ * debug events. If we're forwarding the log we cannot wait for long
+ * before we must flush the log.
+ */
+ int rc;
+ for (;;)
+ {
+ rc = VERR_SEM_OUT_OF_TURN;
+ if (pThis->Dbgc.pUVM)
+ rc = DBGFR3QueryWaitable(pThis->Dbgc.pUVM);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Wait for a debug event.
+ */
+ DBGFEVENT Evt;
+ rc = DBGFR3EventWait(pThis->Dbgc.pUVM, 32, &Evt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcKdCtxProcessEvent(pThis, &Evt);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (rc != VERR_TIMEOUT)
+ break;
+
+ /*
+ * Check for input.
+ */
+ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, 0))
+ {
+ rc = dbgcKdCtxRecv(pThis);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else if (rc == VERR_SEM_OUT_OF_TURN)
+ {
+ /*
+ * Wait for input.
+ */
+ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, 1000))
+ {
+ rc = dbgcKdCtxRecv(pThis);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if ( pThis->msRecvTimeout != RT_INDEFINITE_WAIT
+ && (RTTimeMilliTS() - pThis->tsRecvLast >= pThis->msRecvTimeout))
+ rc = dbgcKdCtxRecvTimeout(pThis);
+ }
+ else
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Creates a KD context instance with the given backend.
+ *
+ * @returns VBox status code.
+ * @param ppKdCtx Where to store the pointer to the KD stub context instance on success.
+ * @param pIo Pointer to the I/O callback table.
+ * @param fFlags Flags controlling the behavior.
+ */
+static int dbgcKdCtxCreate(PPKDCTX ppKdCtx, PCDBGCIO pIo, unsigned fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pIo, VERR_INVALID_POINTER);
+ AssertMsgReturn(!fFlags, ("%#x", fFlags), VERR_INVALID_PARAMETER);
+
+ /*
+ * Allocate and initialize.
+ */
+ PKDCTX pThis = (PKDCTX)RTMemAllocZ(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ dbgcInitCmdHlp(&pThis->Dbgc);
+ /*
+ * This is compied from the native debug console (will be used for monitor commands)
+ * in DBGCConsole.cpp. Try to keep both functions in sync.
+ */
+ pThis->Dbgc.pIo = pIo;
+ pThis->Dbgc.pfnOutput = dbgcKdOutput;
+ pThis->Dbgc.pvOutputUser = pThis;
+ pThis->Dbgc.pVM = NULL;
+ pThis->Dbgc.pUVM = NULL;
+ pThis->Dbgc.idCpu = 0;
+ pThis->Dbgc.hDbgAs = DBGF_AS_GLOBAL;
+ pThis->Dbgc.pszEmulation = "CodeView/WinDbg";
+ pThis->Dbgc.paEmulationCmds = &g_aCmdsCodeView[0];
+ pThis->Dbgc.cEmulationCmds = g_cCmdsCodeView;
+ pThis->Dbgc.paEmulationFuncs = &g_aFuncsCodeView[0];
+ pThis->Dbgc.cEmulationFuncs = g_cFuncsCodeView;
+ //pThis->Dbgc.fLog = false;
+ pThis->Dbgc.fRegTerse = true;
+ pThis->Dbgc.fStepTraceRegs = true;
+ //pThis->Dbgc.cPagingHierarchyDumps = 0;
+ //pThis->Dbgc.DisasmPos = {0};
+ //pThis->Dbgc.SourcePos = {0};
+ //pThis->Dbgc.DumpPos = {0};
+ pThis->Dbgc.pLastPos = &pThis->Dbgc.DisasmPos;
+ //pThis->Dbgc.cbDumpElement = 0;
+ //pThis->Dbgc.cVars = 0;
+ //pThis->Dbgc.paVars = NULL;
+ //pThis->Dbgc.pPlugInHead = NULL;
+ //pThis->Dbgc.pFirstBp = NULL;
+ //pThis->Dbgc.abSearch = {0};
+ //pThis->Dbgc.cbSearch = 0;
+ pThis->Dbgc.cbSearchUnit = 1;
+ pThis->Dbgc.cMaxSearchHits = 1;
+ //pThis->Dbgc.SearchAddr = {0};
+ //pThis->Dbgc.cbSearchRange = 0;
+
+ //pThis->Dbgc.uInputZero = 0;
+ //pThis->Dbgc.iRead = 0;
+ //pThis->Dbgc.iWrite = 0;
+ //pThis->Dbgc.cInputLines = 0;
+ //pThis->Dbgc.fInputOverflow = false;
+ pThis->Dbgc.fReady = true;
+ pThis->Dbgc.pszScratch = &pThis->Dbgc.achScratch[0];
+ //pThis->Dbgc.iArg = 0;
+ //pThis->Dbgc.rcOutput = 0;
+ //pThis->Dbgc.rcCmd = 0;
+
+ //pThis->Dbgc.pszHistoryFile = NULL;
+ //pThis->Dbgc.pszGlobalInitScript = NULL;
+ //pThis->Dbgc.pszLocalInitScript = NULL;
+
+ dbgcEvalInit();
+
+ pThis->fBreakinRecv = false;
+ pThis->fInVBoxDbg = false;
+ pThis->idPktNext = KD_PACKET_HDR_ID_INITIAL;
+ pThis->pIfWinNt = NULL;
+ pThis->f32Bit = false;
+ dbgcKdCtxPktRecvReset(pThis);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aHwBp); i++)
+ {
+ PKDCTXHWBP pBp = &pThis->aHwBp[i];
+ pBp->hDbgfBp = NIL_DBGFBP;
+ }
+
+ dbgcKdCtxHwBpReset(pThis);
+
+ *ppKdCtx = pThis;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Destroys the given KD context.
+ *
+ * @param pThis The KD context to destroy.
+ */
+static void dbgcKdCtxDestroy(PKDCTX pThis)
+{
+ AssertPtr(pThis);
+
+ pThis->pIfWinNt = NULL;
+
+ /* Detach from the VM. */
+ if (pThis->Dbgc.pUVM)
+ DBGFR3Detach(pThis->Dbgc.pUVM);
+
+ /* Free config strings. */
+ RTStrFree(pThis->Dbgc.pszGlobalInitScript);
+ pThis->Dbgc.pszGlobalInitScript = NULL;
+ RTStrFree(pThis->Dbgc.pszLocalInitScript);
+ pThis->Dbgc.pszLocalInitScript = NULL;
+ RTStrFree(pThis->Dbgc.pszHistoryFile);
+ pThis->Dbgc.pszHistoryFile = NULL;
+
+ /* Finally, free the instance memory. */
+ RTMemFree(pThis);
+}
+
+
+DECL_HIDDEN_CALLBACK(int) dbgcKdStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pUVM, VERR_INVALID_VM_HANDLE);
+ PVM pVM = NULL;
+ if (pUVM)
+ {
+ pVM = VMR3GetVM(pUVM);
+ AssertPtrReturn(pVM, VERR_INVALID_VM_HANDLE);
+ }
+
+ /*
+ * Allocate and initialize instance data
+ */
+ PKDCTX pThis;
+ int rc = dbgcKdCtxCreate(&pThis, pIo, fFlags);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!HMR3IsEnabled(pUVM) && !NEMR3IsEnabled(pUVM))
+ pThis->Dbgc.hDbgAs = DBGF_AS_RC_AND_GC_GLOBAL;
+
+ /*
+ * Attach to the specified VM.
+ */
+ if (RT_SUCCESS(rc) && pUVM)
+ {
+ rc = DBGFR3Attach(pUVM);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Dbgc.pVM = pVM;
+ pThis->Dbgc.pUVM = pUVM;
+ pThis->Dbgc.idCpu = 0;
+ }
+ else
+ rc = pThis->Dbgc.CmdHlp.pfnVBoxError(&pThis->Dbgc.CmdHlp, rc, "When trying to attach to VM %p\n", pThis->Dbgc.pVM);
+ }
+
+ /*
+ * Load plugins.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (pVM)
+ DBGFR3PlugInLoadAll(pThis->Dbgc.pUVM);
+ dbgcEventInit(&pThis->Dbgc);
+
+ /*
+ * Run the debugger main loop.
+ */
+ rc = dbgcKdRun(pThis);
+ dbgcEventTerm(&pThis->Dbgc);
+ }
+
+ /*
+ * Cleanup console debugger session.
+ */
+ dbgcKdCtxDestroy(pThis);
+ return rc == VERR_DBGC_QUIT ? VINF_SUCCESS : rc;
+}
diff --git a/src/VBox/Debugger/DBGCScreenAscii.cpp b/src/VBox/Debugger/DBGCScreenAscii.cpp
new file mode 100644
index 00000000..0c3b28c8
--- /dev/null
+++ b/src/VBox/Debugger/DBGCScreenAscii.cpp
@@ -0,0 +1,445 @@
+/* $Id: DBGCScreenAscii.cpp $ */
+/** @file
+ * DBGC - Debugger Console, ASCII screen with optional coloring support.
+ */
+
+/*
+ * Copyright (C) 2016-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "DBGCInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Debug console ASCII screen.
+ */
+typedef struct DBGCSCREENINT
+{
+ /** Width of the screen. */
+ uint32_t cchWidth;
+ /** Height of the screen. */
+ uint32_t cchHeight;
+ /** Extra amount of characters at the end of each line (usually terminator). */
+ uint32_t cchStride;
+ /** Pointer to the char buffer. */
+ char *pszScreen;
+ /** Color information for each pixel. */
+ PDBGCSCREENCOLOR paColors;
+} DBGCSCREENINT;
+/** Pointer to an ASCII screen. */
+typedef DBGCSCREENINT *PDBGCSCREENINT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Returns the buffer starting at the given position.
+ *
+ * @returns Pointer to the ASCII buffer.
+ * @param pThis The screen.
+ * @param uX Horizontal position.
+ * @param uY Vertical position.
+ */
+DECLINLINE(char *) dbgcScreenAsciiGetBufferAtPos(PDBGCSCREENINT pThis, uint32_t uX, uint32_t uY)
+{
+ AssertReturn(uX < pThis->cchWidth && uY < pThis->cchHeight, NULL);
+ return pThis->pszScreen + (pThis->cchWidth + pThis->cchStride) * uY + uX;
+}
+
+
+/**
+ * Returns the color buffer starting at the given position.
+ *
+ * @returns Pointer to the color buffer.
+ * @param pThis The screen.
+ * @param uX Horizontal position.
+ * @param uY Vertical position.
+ */
+DECLINLINE(PDBGCSCREENCOLOR) dbgcScreenAsciiGetColorBufferAtPos(PDBGCSCREENINT pThis, uint32_t uX, uint32_t uY)
+{
+ AssertReturn(uX < pThis->cchWidth && uY < pThis->cchHeight, NULL);
+ return &pThis->paColors[pThis->cchWidth * uY + uX];
+}
+
+
+/**
+ * Converts the given color the correct escape sequence.
+ *
+ * @returns Pointer to the string containing the escape sequence for the given color.
+ * @param enmColor The color.
+ */
+static const char *dbgcScreenAsciiColorToEscapeSeq(DBGCSCREENCOLOR enmColor)
+{
+ const char *psz = NULL;
+
+ switch (enmColor)
+ {
+ case DBGCSCREENCOLOR_DEFAULT:
+ psz = "\033[0m";
+ break;
+ case DBGCSCREENCOLOR_BLACK:
+ psz = "\033[30m";
+ break;
+ case DBGCSCREENCOLOR_BLACK_BRIGHT:
+ psz = "\033[30;1m";
+ break;
+ case DBGCSCREENCOLOR_RED:
+ psz = "\033[31m";
+ break;
+ case DBGCSCREENCOLOR_RED_BRIGHT:
+ psz = "\033[31;1m";
+ break;
+ case DBGCSCREENCOLOR_GREEN:
+ psz = "\033[32m";
+ break;
+ case DBGCSCREENCOLOR_GREEN_BRIGHT:
+ psz = "\033[32;1m";
+ break;
+ case DBGCSCREENCOLOR_YELLOW:
+ psz = "\033[33m";
+ break;
+ case DBGCSCREENCOLOR_YELLOW_BRIGHT:
+ psz = "\033[33;1m";
+ break;
+ case DBGCSCREENCOLOR_BLUE:
+ psz = "\033[34m";
+ break;
+ case DBGCSCREENCOLOR_BLUE_BRIGHT:
+ psz = "\033[34;1m";
+ break;
+ case DBGCSCREENCOLOR_MAGENTA:
+ psz = "\033[35m";
+ break;
+ case DBGCSCREENCOLOR_MAGENTA_BRIGHT:
+ psz = "\033[35;1m";
+ break;
+ case DBGCSCREENCOLOR_CYAN:
+ psz = "\033[36m";
+ break;
+ case DBGCSCREENCOLOR_CYAN_BRIGHT:
+ psz = "\033[36;1m";
+ break;
+ case DBGCSCREENCOLOR_WHITE:
+ psz = "\033[37m";
+ break;
+ case DBGCSCREENCOLOR_WHITE_BRIGHT:
+ psz = "\033[37;1m";
+ break;
+ default:
+ AssertFailed();
+ }
+
+ return psz;
+}
+
+
+/**
+ * Creates a new ASCII screen for layouting.
+ *
+ * @returns VBox status code.
+ * @param phScreen Where to store the handle to the screen instance on success.
+ * @param cchWidth Width of the screen in characters.
+ * @param cchHeight Height of the screen in characters.
+ */
+DECLHIDDEN(int) dbgcScreenAsciiCreate(PDBGCSCREEN phScreen, uint32_t cchWidth, uint32_t cchHeight)
+{
+ int rc = VINF_SUCCESS;
+
+ PDBGCSCREENINT pThis = (PDBGCSCREENINT)RTMemAllocZ(sizeof(DBGCSCREENINT));
+ if (pThis)
+ {
+ pThis->cchWidth = cchWidth;
+ pThis->cchHeight = cchHeight;
+ pThis->cchStride = 1; /* Zero terminators after every line. */
+ pThis->pszScreen = RTStrAlloc((cchWidth + 1) * cchHeight * sizeof(char));
+ if (RT_LIKELY(pThis->pszScreen))
+ {
+ pThis->paColors = (PDBGCSCREENCOLOR)RTMemAllocZ(cchWidth * cchHeight * sizeof(DBGCSCREENCOLOR));
+ if (RT_LIKELY(pThis->paColors))
+ {
+ memset(pThis->pszScreen, 0, (cchWidth + 1) * cchHeight * sizeof(char));
+ /* Initialize the screen with spaces. */
+ for (uint32_t i = 0; i < cchHeight; i++)
+ dbgcScreenAsciiDrawLineHorizontal(pThis, 0, cchWidth - 1, i, ' ',
+ DBGCSCREENCOLOR_DEFAULT);
+ *phScreen = pThis;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ RTStrFree(pThis->pszScreen);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Destroys a given ASCII screen.
+ *
+ * @param hScreen The screen handle.
+ */
+DECLHIDDEN(void) dbgcScreenAsciiDestroy(DBGCSCREEN hScreen)
+{
+ PDBGCSCREENINT pThis = hScreen;
+ AssertPtrReturnVoid(pThis);
+
+ RTStrFree(pThis->pszScreen);
+ RTMemFree(pThis->paColors);
+ RTMemFree(pThis);
+}
+
+
+/**
+ * Blits the entire screen using the given callback callback.
+ *
+ * @returns VBox status code.
+ * @param hScreen The screen to blit.
+ * @param pfnBlit Blitting callback.
+ * @param pvUser Opaque user data to pass to the dumper callback.
+ * @param fAddColors Flag whether to use the color info inserting
+ * appropriate escape sequences.
+ */
+DECLHIDDEN(int) dbgcScreenAsciiBlit(DBGCSCREEN hScreen, PFNDGCSCREENBLIT pfnBlit, void *pvUser, bool fAddColors)
+{
+ int rc = VINF_SUCCESS;
+ PDBGCSCREENINT pThis = hScreen;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ if (!fAddColors)
+ {
+ for (uint32_t iY = 0; iY < pThis->cchHeight && RT_SUCCESS(rc); iY++)
+ {
+ /* Play safe and restore line endings. */
+ char *psz = dbgcScreenAsciiGetBufferAtPos(pThis, 0, iY);
+ psz[pThis->cchWidth] = '\0';
+ rc = pfnBlit(psz, pvUser);
+ if (RT_SUCCESS(rc))
+ rc = pfnBlit("\n", pvUser);
+ }
+ }
+ else
+ {
+ for (uint32_t iY = 0; iY < pThis->cchHeight && RT_SUCCESS(rc); iY++)
+ {
+ /* Play safe and restore line endings. */
+ char *psz = dbgcScreenAsciiGetBufferAtPos(pThis, 0, iY);
+ PDBGCSCREENCOLOR pColor = dbgcScreenAsciiGetColorBufferAtPos(pThis, 0, iY);
+ psz[pThis->cchWidth] = '\0';
+
+ /*
+ * Blit only stuff with the same color at once so to be able to inject the
+ * correct color escape sequences.
+ */
+ uint32_t uStartX = 0;
+ while ( uStartX < pThis->cchWidth
+ && RT_SUCCESS(rc))
+ {
+ uint32_t cchWrite = 0;
+ DBGCSCREENCOLOR enmColorStart = *pColor;
+ while ( uStartX + cchWrite < pThis->cchWidth
+ && enmColorStart == *pColor)
+ {
+ pColor++;
+ cchWrite++;
+ }
+
+ const char *pszEsc = dbgcScreenAsciiColorToEscapeSeq(enmColorStart);
+ rc = pfnBlit(pszEsc, pvUser);
+ if (RT_SUCCESS(rc))
+ {
+ char chTmp = psz[cchWrite];
+ psz[cchWrite] = '\0';
+ rc = pfnBlit(psz, pvUser);
+ psz[cchWrite] = chTmp;
+ uStartX += cchWrite;
+ psz += cchWrite;
+ }
+ }
+ rc = pfnBlit("\n", pvUser);
+ }
+
+ /* Restore to default values at the end. */
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszEsc = dbgcScreenAsciiColorToEscapeSeq(DBGCSCREENCOLOR_DEFAULT);
+ rc = pfnBlit(pszEsc, pvUser);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Draws a single character to the screen at the given coordinates.
+ *
+ * @returns VBox status code.
+ * @param hScreen The screen handle.
+ * @param uX X coordinate.
+ * @param uY Y coordinate.
+ * @param ch Character to draw.
+ * @param enmColor The color to use.
+ */
+DECLHIDDEN(int) dbgcScreenAsciiDrawCharacter(DBGCSCREEN hScreen, uint32_t uX, uint32_t uY, char ch,
+ DBGCSCREENCOLOR enmColor)
+{
+ PDBGCSCREENINT pThis = hScreen;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ char *psz = dbgcScreenAsciiGetBufferAtPos(pThis, uX, uY);
+ PDBGCSCREENCOLOR pColor = dbgcScreenAsciiGetColorBufferAtPos(pThis, uX, uY);
+ AssertPtrReturn(psz, VERR_INVALID_STATE);
+ AssertPtrReturn(pColor, VERR_INVALID_STATE);
+ AssertReturn(*psz != '\0', VERR_INVALID_STATE);
+
+ *psz = ch;
+ *pColor = enmColor;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Draws a vertical line at the given coordinates.
+ *
+ * @returns VBox status code.
+ * @param hScreen The screen handle.
+ * @param uX X position to draw.
+ * @param uStartY Y position to start drawing.
+ * @param uEndY Y position to draw to (inclusive).
+ * @param ch The character to use for drawing.
+ * @param enmColor The color to use.
+ */
+DECLHIDDEN(int) dbgcScreenAsciiDrawLineVertical(DBGCSCREEN hScreen, uint32_t uX, uint32_t uStartY,
+ uint32_t uEndY, char ch, DBGCSCREENCOLOR enmColor)
+{
+ PDBGCSCREENINT pThis = hScreen;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ while (uStartY <= uEndY)
+ {
+ char *psz = dbgcScreenAsciiGetBufferAtPos(pThis, uX, uStartY);
+ PDBGCSCREENCOLOR pColor = dbgcScreenAsciiGetColorBufferAtPos(pThis, uX, uStartY);
+ AssertPtrReturn(psz, VERR_INVALID_STATE);
+ AssertPtrReturn(pColor, VERR_INVALID_STATE);
+ *psz = ch;
+ *pColor = enmColor;
+ uStartY++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Draws a horizontal line at the given coordinates.
+ *
+ * @returns VBox status code..
+ * @param hScreen The screen handle.
+ * @param uStartX X position to start drawing.
+ * @param uEndX X position to draw the line to (inclusive).
+ * @param uY Y position.
+ * @param ch The character to use for drawing.
+ * @param enmColor The color to use.
+ */
+DECLHIDDEN(int) dbgcScreenAsciiDrawLineHorizontal(DBGCSCREEN hScreen, uint32_t uStartX, uint32_t uEndX,
+ uint32_t uY, char ch, DBGCSCREENCOLOR enmColor)
+{
+ PDBGCSCREENINT pThis = hScreen;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ char *psz = dbgcScreenAsciiGetBufferAtPos(pThis, uStartX, uY);
+ PDBGCSCREENCOLOR pColor = dbgcScreenAsciiGetColorBufferAtPos(pThis, uStartX, uY);
+ AssertPtrReturn(psz, VERR_INVALID_STATE);
+ AssertPtrReturn(pColor, VERR_INVALID_STATE);
+
+ memset(psz, ch, uEndX - uStartX + 1);
+ for (unsigned i = 0; i < uEndX - uStartX + 1; i++)
+ pColor[i] = enmColor;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Draws a given string to the screen.
+ *
+ * @returns VBox status code..
+ * @param hScreen The screen handle.
+ * @param uX X position to start drawing.
+ * @param uY Y position.
+ * @param pszText The string to draw.
+ * @param enmColor The color to use.
+ */
+DECLHIDDEN(int) dbgcScreenAsciiDrawString(DBGCSCREEN hScreen, uint32_t uX, uint32_t uY, const char *pszText,
+ DBGCSCREENCOLOR enmColor)
+{
+ PDBGCSCREENINT pThis = hScreen;
+ size_t cchText = strlen(pszText);
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(uX + cchText <= pThis->cchWidth, VERR_OUT_OF_RANGE);
+ AssertReturn(uY < pThis->cchHeight, VERR_OUT_OF_RANGE);
+
+ char *psz = dbgcScreenAsciiGetBufferAtPos(pThis, uX, uY);
+ PDBGCSCREENCOLOR pColor = dbgcScreenAsciiGetColorBufferAtPos(pThis, uX, uY);
+ AssertPtrReturn(psz, VERR_INVALID_STATE);
+ AssertPtrReturn(pColor, VERR_INVALID_STATE);
+
+ memcpy(psz, pszText, cchText);
+
+ for (unsigned i = 0; i < cchText; i++)
+ pColor[i] = enmColor;
+
+ return VINF_SUCCESS;
+}
diff --git a/src/VBox/Debugger/DBGConsole.cpp b/src/VBox/Debugger/DBGConsole.cpp
new file mode 100644
index 00000000..52feea10
--- /dev/null
+++ b/src/VBox/Debugger/DBGConsole.cpp
@@ -0,0 +1,1378 @@
+/* $Id: DBGConsole.cpp $ */
+/** @file
+ * DBGC - Debugger Console.
+ */
+
+/*
+ * Copyright (C) 2006-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_dbgc DBGC - The Debug Console
+ *
+ * The debugger console is an early attempt to make some interactive
+ * debugging facilities for the VirtualBox VMM. It was initially only
+ * accessible thru a telnet session in debug builds. Later it was hastily built
+ * into the VBoxDbg module with a very simple Qt wrapper around it.
+ *
+ * The current state is that it's by default shipped with all standard
+ * VirtualBox builds. The GUI component is by default accessible in all
+ * non-release builds, while release builds require extra data, environment or
+ * command line options to make it visible.
+ *
+ * Now, even if we ship it with all standard builds we would like it to remain
+ * an optional feature that can be omitted when building VirtualBox. Therefore,
+ * all external code interfacing DBGC need to be enclosed in
+ * \#ifdef VBOX_WITH_DEBUGGER blocks. This is mandatory for components that
+ * register external commands.
+ *
+ *
+ * @section sec_dbgc_op Operation
+ *
+ * The console will process commands in a manner similar to the OS/2 and Windows
+ * kernel debuggers. This means ';' is a command separator and that when
+ * possible we'll use the same command names as these two uses. As an
+ * alternative we intent to provide a set of gdb-like commands as well and let
+ * the user decide which should take precedence.
+ *
+ *
+ * @subsection sec_dbg_op_numbers Numbers
+ *
+ * Numbers are hexadecimal unless specified with a prefix indicating
+ * elsewise. Prefixes:
+ * - '0x' - hexadecimal.
+ * - '0n' - decimal
+ * - '0t' - octal.
+ * - '0y' - binary.
+ *
+ * Some of the prefixes are a bit uncommon, the reason for this that the
+ * typical binary prefix '0b' can also be a hexadecimal value since no prefix or
+ * suffix is required for such values. Ditto for '0n' and '0' for decimal and
+ * octal.
+ *
+ * The '`' can be used in the numeric value to separate parts as the user
+ * wishes. Generally, though the debugger may use it in output as thousand
+ * separator in decimal numbers and 32-bit separator in hex numbers.
+ *
+ * For historical reasons, a 'h' suffix is suffered on hex numbers. Unlike most
+ * assemblers, a leading 0 before a-f is not required with the 'h' suffix.
+ *
+ * The prefix '0i' can be used instead of '0n', as it was the early decimal
+ * prefix employed by DBGC. It's being deprecated and may be removed later.
+ *
+ *
+ * @subsection sec_dbg_op_strings Strings and Symbols
+ *
+ * The debugger will try to guess, convert or promote what the type of an
+ * argument to a command, function or operator based on the input description of
+ * the receiver. If the user wants to make it clear to the debugger that
+ * something is a string, put it inside double quotes. Symbols should use
+ * single quotes, though we're current still a bit flexible on this point.
+ *
+ * If you need to put a quote character inside the quoted text, you escape it by
+ * repating it once: echo "printf(""hello world"");"
+ *
+ *
+ * @subsection sec_dbg_op_address Addressing modes
+ *
+ * - Default is flat. For compatibility '%' also means flat.
+ * - Segmented addresses are specified selector:offset.
+ * - Physical addresses are specified using '%%'.
+ * - The default target for the addressing is the guest context, the '#'
+ * will override this and set it to the host.
+ * Note that several operations won't work on host addresses.
+ *
+ * The '%', '%%' and '#' prefixes is implemented as unary operators, while ':'
+ * is a binary operator. Operator precedence takes care of evaluation order.
+ *
+ *
+ * @subsection sec_dbg_op_c_operators C/C++ Operators
+ *
+ * Most unary and binary arithmetic, comparison, logical and bitwise C/C++
+ * operators are supported by the debugger, with the same precedence rules of
+ * course. There is one notable change made due to the unary '%' and '%%'
+ * operators, and that is that the modulo (remainder) operator is called 'mod'
+ * instead of '%'. This saves a lot of trouble separating argument.
+ *
+ * There are no assignment operators. Instead some simple global variable space
+ * is provided thru the 'set' and 'unset' commands and the unary '$' operator.
+ *
+ *
+ * @subsection sec_dbg_op_registers Registers
+ *
+ * All registers and their sub-fields exposed by the DBGF API are accessible via
+ * the '\@' operator. A few CPU register are accessible directly (as symbols)
+ * without using the '\@' operator. Hypervisor registers are accessible by
+ * prefixing the register name with a dot ('.').
+ *
+ *
+ * @subsection sec_dbg_op_commands Commands
+ *
+ * Commands names are case sensitive. By convention they are lower cased, starts
+ * with a letter but may contain digits and underscores afterwards. Operators
+ * are not allowed in the name (not even part of it), as we would risk
+ * misunderstanding it otherwise.
+ *
+ * Commands returns a status code.
+ *
+ * The '.' prefix indicates the set of external commands. External commands are
+ * command registered by VMM components.
+ *
+ *
+ * @subsection sec_dbg_op_functions Functions
+ *
+ * Functions are similar to commands, but return a variable and can only be used
+ * as part of an expression making up the argument of a command, function,
+ * operator or language statement (if we get around to implement that).
+ *
+ *
+ * @section sec_dbgc_logging Logging
+ *
+ * The idea is to be able to pass thru debug and release logs to the console
+ * if the user so wishes. This feature requires some kind of hook into the
+ * logger instance and while this was sketched it hasn't yet been implemented
+ * (dbgcProcessLog and DBGC::fLog).
+ *
+ * This feature has not materialized and probably never will.
+ *
+ *
+ * @section sec_dbgc_linking Linking and API
+ *
+ * The DBGC code is linked into the VBoxVMM module.
+ *
+ * IMachineDebugger may one day be extended with a DBGC interface so we can work
+ * with DBGC remotely without requiring TCP. Some questions about callbacks
+ * (for output) and security (you may wish to restrict users from debugging a
+ * VM) needs to be answered first though.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/cfgm.h>
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/vmapi.h> /* VMR3GetVM() */
+#include <VBox/vmm/hm.h> /* HMR3IsEnabled */
+#include <VBox/vmm/nem.h> /* NEMR3IsEnabled */
+#include <VBox/err.h>
+#include <VBox/log.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#include "DBGCInternal.h"
+#include "DBGPlugIns.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int dbgcProcessLog(PDBGC pDbgc);
+
+
+/**
+ * Resolves a symbol (or tries to do so at least).
+ *
+ * @returns 0 on success.
+ * @returns VBox status on failure.
+ * @param pDbgc The debug console instance.
+ * @param pszSymbol The symbol name.
+ * @param enmType The result type. Specifying DBGCVAR_TYPE_GC_FAR may
+ * cause failure, avoid it.
+ * @param pResult Where to store the result.
+ */
+int dbgcSymbolGet(PDBGC pDbgc, const char *pszSymbol, DBGCVARTYPE enmType, PDBGCVAR pResult)
+{
+ int rc;
+
+ /*
+ * Builtin?
+ */
+ PCDBGCSYM pSymDesc = dbgcLookupRegisterSymbol(pDbgc, pszSymbol);
+ if (pSymDesc)
+ {
+ if (!pSymDesc->pfnGet)
+ return VERR_DBGC_PARSE_WRITEONLY_SYMBOL;
+ return pSymDesc->pfnGet(pSymDesc, &pDbgc->CmdHlp, enmType, pResult);
+ }
+
+ /*
+ * A typical register? (Guest only)
+ */
+ static const char s_szSixLetterRegisters[] =
+ "rflags;eflags;"
+ ;
+ static const char s_szThreeLetterRegisters[] =
+ "eax;rax;" "r10;" "r8d;r8w;r8b;" "cr0;" "dr0;"
+ "ebx;rbx;" "r11;" "r9d;r9w;r8b;" "dr1;"
+ "ecx;rcx;" "r12;" "cr2;" "dr2;"
+ "edx;rdx;" "r13;" "cr3;" "dr3;"
+ "edi;rdi;dil;" "r14;" "cr4;" "dr4;"
+ "esi;rsi;sil;" "r15;" "cr8;"
+ "ebp;rbp;"
+ "esp;rsp;" "dr6;"
+ "rip;eip;" "dr7;"
+ "efl;"
+ ;
+ static const char s_szTwoLetterRegisters[] =
+ "ax;al;ah;" "r8;"
+ "bx;bl;bh;" "r9;"
+ "cx;cl;ch;" "cs;"
+ "dx;dl;dh;" "ds;"
+ "di;" "es;"
+ "si;" "fs;"
+ "bp;" "gs;"
+ "sp;" "ss;"
+ "ip;"
+ ;
+ const char *pszRegSym = *pszSymbol == '.' ? pszSymbol + 1 : pszSymbol;
+ size_t const cchRegSym = strlen(pszRegSym);
+ if ( (cchRegSym == 2 && strstr(s_szTwoLetterRegisters, pszRegSym))
+ || (cchRegSym == 3 && strstr(s_szThreeLetterRegisters, pszRegSym))
+ || (cchRegSym == 6 && strstr(s_szSixLetterRegisters, pszRegSym)))
+ {
+ if (!strchr(pszSymbol, ';'))
+ {
+ DBGCVAR Var;
+ DBGCVAR_INIT_SYMBOL(&Var, pszSymbol);
+ rc = dbgcOpRegister(pDbgc, &Var, DBGCVAR_CAT_ANY, pResult);
+ if (RT_SUCCESS(rc))
+ return DBGCCmdHlpConvert(&pDbgc->CmdHlp, pResult, enmType, false /*fConvSyms*/, pResult);
+ }
+ }
+
+ /*
+ * Ask PDM.
+ */
+ /** @todo resolve symbols using PDM. */
+
+ /*
+ * Ask the debug info manager.
+ */
+ RTDBGSYMBOL Symbol;
+ rc = DBGFR3AsSymbolByName(pDbgc->pUVM, pDbgc->hDbgAs, pszSymbol, &Symbol, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Default return is a flat gc address.
+ */
+ DBGCVAR_INIT_GC_FLAT(pResult, Symbol.Value);
+ if (Symbol.cb)
+ DBGCVAR_SET_RANGE(pResult, DBGCVAR_RANGE_BYTES, Symbol.cb);
+
+ switch (enmType)
+ {
+ /* nothing to do. */
+ case DBGCVAR_TYPE_GC_FLAT:
+ case DBGCVAR_TYPE_ANY:
+ return VINF_SUCCESS;
+
+ /* impossible at the moment. */
+ case DBGCVAR_TYPE_GC_FAR:
+ return VERR_DBGC_PARSE_CONVERSION_FAILED;
+
+ /* simply make it numeric. */
+ case DBGCVAR_TYPE_NUMBER:
+ pResult->enmType = DBGCVAR_TYPE_NUMBER;
+ pResult->u.u64Number = Symbol.Value;
+ return VINF_SUCCESS;
+
+ /* cast it. */
+ case DBGCVAR_TYPE_GC_PHYS:
+ case DBGCVAR_TYPE_HC_FLAT:
+ case DBGCVAR_TYPE_HC_PHYS:
+ return DBGCCmdHlpConvert(&pDbgc->CmdHlp, pResult, enmType, false /*fConvSyms*/, pResult);
+
+ default:
+ AssertMsgFailed(("Internal error enmType=%d\n", enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+ }
+
+ return VERR_DBGC_PARSE_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Process all commands currently in the buffer.
+ *
+ * @returns VBox status code. Any error indicates the termination of the console session.
+ * @param pDbgc Debugger console instance data.
+ * @param fNoExecute Indicates that no commands should actually be executed.
+ */
+static int dbgcProcessCommands(PDBGC pDbgc, bool fNoExecute)
+{
+ /** @todo Replace this with a sh/ksh/csh/rexx like toplevel language that
+ * allows doing function, loops, if, cases, and such. */
+ int rc = VINF_SUCCESS;
+ while (pDbgc->cInputLines)
+ {
+ /*
+ * Empty the log buffer if we're hooking the log.
+ */
+ if (pDbgc->fLog)
+ {
+ rc = dbgcProcessLog(pDbgc);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (pDbgc->iRead == pDbgc->iWrite)
+ {
+ AssertMsgFailed(("The input buffer is empty while cInputLines=%d!\n", pDbgc->cInputLines));
+ pDbgc->cInputLines = 0;
+ return 0;
+ }
+
+ /*
+ * Copy the command to the parse buffer.
+ */
+ char chQuote = 0;
+ char ch;
+ char *psz = &pDbgc->achInput[pDbgc->iRead];
+ char *pszTrg = &pDbgc->achScratch[0];
+ AssertCompile(sizeof(pDbgc->achScratch) > sizeof(pDbgc->achInput));
+ while ((ch = *psz++) != '\0')
+ {
+ /* ';' and '\n' are termination characters, except for when they are
+ inside quotes. So, track quoting. */
+ if (ch == '"' || ch == '\'')
+ chQuote = chQuote == ch ? 0 : chQuote == 0 ? ch : chQuote;
+ else if ((ch == ';' || ch == '\n') && chQuote == 0)
+ break;
+
+ *pszTrg = ch;
+
+ if (psz == &pDbgc->achInput[sizeof(pDbgc->achInput)])
+ psz = &pDbgc->achInput[0];
+
+ /** @todo r=bird: off by one issue here? */
+ if (psz == &pDbgc->achInput[pDbgc->iWrite])
+ {
+ AssertMsgFailed(("The buffer contains no commands while cInputLines=%d!\n", pDbgc->cInputLines));
+ pDbgc->cInputLines = 0;
+ return 0;
+ }
+
+ pszTrg++;
+ }
+ *pszTrg = '\0';
+
+ /*
+ * Advance the buffer.
+ */
+ pDbgc->iRead = psz - &pDbgc->achInput[0];
+ if (ch == '\n')
+ pDbgc->cInputLines--;
+
+ /*
+ * Parse and execute this command.
+ */
+ pDbgc->pszScratch = pszTrg + 1;
+ pDbgc->iArg = 0;
+ rc = dbgcEvalCommand(pDbgc, &pDbgc->achScratch[0], pszTrg - &pDbgc->achScratch[0], fNoExecute);
+ if ( rc == VERR_DBGC_QUIT
+ || rc == VWRN_DBGC_CMD_PENDING)
+ break;
+ rc = VINF_SUCCESS; /* ignore other statuses */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Handle input buffer overflow.
+ *
+ * Will read any available input looking for a '\n' to reset the buffer on.
+ *
+ * @returns VBox status code.
+ * @param pDbgc Debugger console instance data.
+ */
+static int dbgcInputOverflow(PDBGC pDbgc)
+{
+ /*
+ * Assert overflow status and reset the input buffer.
+ */
+ if (!pDbgc->fInputOverflow)
+ {
+ pDbgc->fInputOverflow = true;
+ pDbgc->iRead = pDbgc->iWrite = 0;
+ pDbgc->cInputLines = 0;
+ pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Input overflow!!\n");
+ }
+
+ /*
+ * Eat input till no more or there is a '\n'.
+ * When finding a '\n' we'll continue normal processing.
+ */
+ while (pDbgc->pIo->pfnInput(pDbgc->pIo, 0))
+ {
+ size_t cbRead;
+ int rc = pDbgc->pIo->pfnRead(pDbgc->pIo, &pDbgc->achInput[0], sizeof(pDbgc->achInput) - 1, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ char *psz = (char *)memchr(&pDbgc->achInput[0], '\n', cbRead);
+ if (psz)
+ {
+ pDbgc->fInputOverflow = false;
+ pDbgc->iRead = psz - &pDbgc->achInput[0] + 1;
+ pDbgc->iWrite = (unsigned)cbRead;
+ pDbgc->cInputLines = 0;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Read input and do some preprocessing.
+ *
+ * @returns VBox status code.
+ * In addition to the iWrite and achInput, cInputLines is maintained.
+ * In case of an input overflow the fInputOverflow flag will be set.
+ * @param pDbgc Debugger console instance data.
+ */
+static int dbgcInputRead(PDBGC pDbgc)
+{
+ /*
+ * We have ready input.
+ * Read it till we don't have any or we have a full input buffer.
+ */
+ int rc = 0;
+ do
+ {
+ /*
+ * More available buffer space?
+ */
+ size_t cbLeft;
+ if (pDbgc->iWrite > pDbgc->iRead)
+ cbLeft = sizeof(pDbgc->achInput) - pDbgc->iWrite - (pDbgc->iRead == 0);
+ else
+ cbLeft = pDbgc->iRead - pDbgc->iWrite - 1;
+ if (!cbLeft)
+ {
+ /* overflow? */
+ if (!pDbgc->cInputLines)
+ rc = dbgcInputOverflow(pDbgc);
+ break;
+ }
+
+ /*
+ * Read one char and interpret it.
+ */
+ char achRead[128];
+ size_t cbRead;
+ rc = pDbgc->pIo->pfnRead(pDbgc->pIo, &achRead[0], RT_MIN(cbLeft, sizeof(achRead)), &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ char *psz = &achRead[0];
+ while (cbRead-- > 0)
+ {
+ char ch = *psz++;
+ switch (ch)
+ {
+ /*
+ * Ignore.
+ */
+ case '\0':
+ case '\r':
+ case '\a':
+ break;
+
+ /*
+ * Backspace.
+ */
+ case '\b':
+ Log2(("DBGC: backspace\n"));
+ if (pDbgc->iRead != pDbgc->iWrite)
+ {
+ unsigned iWriteUndo = pDbgc->iWrite;
+ if (pDbgc->iWrite)
+ pDbgc->iWrite--;
+ else
+ pDbgc->iWrite = sizeof(pDbgc->achInput) - 1;
+
+ if (pDbgc->achInput[pDbgc->iWrite] == '\n')
+ pDbgc->iWrite = iWriteUndo;
+ }
+ break;
+
+ /*
+ * Add char to buffer.
+ */
+ case '\t':
+ case '\n':
+ case ';':
+ switch (ch)
+ {
+ case '\t': ch = ' '; break;
+ case '\n': pDbgc->cInputLines++; break;
+ }
+ RT_FALL_THRU();
+ default:
+ Log2(("DBGC: ch=%02x\n", (unsigned char)ch));
+ pDbgc->achInput[pDbgc->iWrite] = ch;
+ if (++pDbgc->iWrite >= sizeof(pDbgc->achInput))
+ pDbgc->iWrite = 0;
+ break;
+ }
+ }
+
+ /* Terminate it to make it easier to read in the debugger. */
+ pDbgc->achInput[pDbgc->iWrite] = '\0';
+ } while (pDbgc->pIo->pfnInput(pDbgc->pIo, 0));
+
+ return rc;
+}
+
+
+/**
+ * Reads input, parses it and executes commands on '\n'.
+ *
+ * @returns VBox status code.
+ * @param pDbgc Debugger console instance data.
+ * @param fNoExecute Indicates that no commands should actually be executed.
+ */
+int dbgcProcessInput(PDBGC pDbgc, bool fNoExecute)
+{
+ /*
+ * We know there's input ready, so let's read it first.
+ */
+ int rc = dbgcInputRead(pDbgc);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now execute any ready commands.
+ */
+ if (pDbgc->cInputLines)
+ {
+ pDbgc->pIo->pfnSetReady(pDbgc->pIo, false);
+ pDbgc->fReady = false;
+ rc = dbgcProcessCommands(pDbgc, fNoExecute);
+ if (RT_SUCCESS(rc) && rc != VWRN_DBGC_CMD_PENDING)
+ pDbgc->fReady = true;
+
+ if ( RT_SUCCESS(rc)
+ && pDbgc->iRead == pDbgc->iWrite
+ && pDbgc->fReady)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "VBoxDbg> ");
+
+ if ( RT_SUCCESS(rc)
+ && pDbgc->fReady)
+ pDbgc->pIo->pfnSetReady(pDbgc->pIo, true);
+ }
+ /*
+ * else - we have incomplete line, so leave it in the buffer and
+ * wait for more input.
+ *
+ * Windows telnet client is in "character at a time" mode by
+ * default and putty sends eol as a separate packet that will be
+ * most likely read separately from the command line it
+ * terminates.
+ */
+
+ return rc;
+}
+
+
+/**
+ * Gets the event context identifier string.
+ * @returns Read only string.
+ * @param enmCtx The context.
+ */
+DECLHIDDEN(const char *) dbgcGetEventCtx(DBGFEVENTCTX enmCtx)
+{
+ switch (enmCtx)
+ {
+ case DBGFEVENTCTX_RAW: return "raw";
+ case DBGFEVENTCTX_REM: return "rem";
+ case DBGFEVENTCTX_HM: return "hwaccl";
+ case DBGFEVENTCTX_HYPER: return "hyper";
+ case DBGFEVENTCTX_OTHER: return "other";
+
+ case DBGFEVENTCTX_INVALID: return "!Invalid Event Ctx!";
+ default:
+ AssertMsgFailed(("enmCtx=%d\n", enmCtx));
+ return "!Unknown Event Ctx!";
+ }
+}
+
+
+/**
+ * Looks up a generic debug event.
+ *
+ * @returns Pointer to DBGCSXEVT structure if found, otherwise NULL.
+ * @param enmType The possibly generic event to find the descriptor for.
+ */
+DECLHIDDEN(PCDBGCSXEVT) dbgcEventLookup(DBGFEVENTTYPE enmType)
+{
+ uint32_t i = g_cDbgcSxEvents;
+ while (i-- > 0)
+ if (g_aDbgcSxEvents[i].enmType == enmType)
+ return &g_aDbgcSxEvents[i];
+ return NULL;
+}
+
+
+/**
+ * Processes debugger events.
+ *
+ * @returns VBox status code.
+ * @param pDbgc DBGC Instance data.
+ * @param pEvent Pointer to event data.
+ */
+static int dbgcProcessEvent(PDBGC pDbgc, PCDBGFEVENT pEvent)
+{
+ /*
+ * Flush log first.
+ */
+ if (pDbgc->fLog)
+ {
+ int rc = dbgcProcessLog(pDbgc);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Process the event.
+ */
+ pDbgc->pszScratch = &pDbgc->achInput[0];
+ pDbgc->iArg = 0;
+ bool fPrintPrompt = true;
+ int rc = VINF_SUCCESS;
+ VMCPUID const idCpuSaved = pDbgc->idCpu;
+ switch (pEvent->enmType)
+ {
+ /*
+ * The first part is events we have initiated with commands.
+ */
+ case DBGFEVENT_HALT_DONE:
+ {
+ /** @todo add option to suppress this on CPUs that aren't selected (like
+ * fRegTerse). */
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: CPU %u has halted! (%s)\n",
+ pEvent->idCpu, pEvent->idCpu, dbgcGetEventCtx(pEvent->enmCtx));
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpRegPrintf(&pDbgc->CmdHlp, pEvent->idCpu, -1, pDbgc->fRegTerse);
+ break;
+ }
+
+
+ /*
+ * The second part is events which can occur at any time.
+ */
+ case DBGFEVENT_FATAL_ERROR:
+ {
+ pDbgc->idCpu = pEvent->idCpu;
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbf event/%u: Fatal error! (%s)\n",
+ pEvent->idCpu, dbgcGetEventCtx(pEvent->enmCtx));
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpRegPrintf(&pDbgc->CmdHlp, pEvent->idCpu, -1, pDbgc->fRegTerse);
+ break;
+ }
+
+ case DBGFEVENT_BREAKPOINT:
+ case DBGFEVENT_BREAKPOINT_IO:
+ case DBGFEVENT_BREAKPOINT_MMIO:
+ case DBGFEVENT_BREAKPOINT_HYPER:
+ {
+ pDbgc->idCpu = pEvent->idCpu;
+ rc = dbgcBpExec(pDbgc, pEvent->u.Bp.hBp);
+ switch (rc)
+ {
+ case VERR_DBGC_BP_NOT_FOUND:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: Unknown breakpoint %u! (%s)\n",
+ pEvent->idCpu, pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ case VINF_DBGC_BP_NO_COMMAND:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: Breakpoint %u! (%s)\n",
+ pEvent->idCpu, pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ case VINF_BUFFER_OVERFLOW:
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: Breakpoint %u! Command too long to execute! (%s)\n",
+ pEvent->idCpu, pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx));
+ break;
+
+ default:
+ break;
+ }
+ if (RT_SUCCESS(rc) && DBGFR3IsHalted(pDbgc->pUVM, pEvent->idCpu))
+ {
+ rc = DBGCCmdHlpRegPrintf(&pDbgc->CmdHlp, pEvent->idCpu, -1, pDbgc->fRegTerse);
+
+ /* Set the resume flag to ignore the breakpoint when resuming execution. */
+ if ( RT_SUCCESS(rc)
+ && pEvent->enmType == DBGFEVENT_BREAKPOINT)
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r eflags.rf = 1");
+ }
+ else
+ pDbgc->idCpu = idCpuSaved;
+ break;
+ }
+
+ case DBGFEVENT_STEPPED:
+ case DBGFEVENT_STEPPED_HYPER:
+ {
+ if (!pDbgc->cMultiStepsLeft || pEvent->idCpu != idCpuSaved)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: Single step! (%s)\n",
+ pEvent->idCpu, dbgcGetEventCtx(pEvent->enmCtx));
+ else
+ pDbgc->cMultiStepsLeft -= 1;
+ if (RT_SUCCESS(rc))
+ {
+ if (pDbgc->fStepTraceRegs)
+ rc = DBGCCmdHlpRegPrintf(&pDbgc->CmdHlp, pEvent->idCpu, -1, pDbgc->fRegTerse);
+ else
+ {
+ char szCmd[80];
+ if (DBGFR3CpuIsIn64BitCode(pDbgc->pUVM, pDbgc->idCpu))
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, pDbgc->idCpu, szCmd, sizeof(szCmd), "u %016VR{rip} L 0");
+ else if (DBGFR3CpuIsInV86Code(pDbgc->pUVM, pDbgc->idCpu))
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, pDbgc->idCpu, szCmd, sizeof(szCmd), "uv86 %04VR{cs}:%08VR{eip} L 0");
+ else
+ rc = DBGFR3RegPrintf(pDbgc->pUVM, pDbgc->idCpu, szCmd, sizeof(szCmd), "u %04VR{cs}:%08VR{eip} L 0");
+ if (RT_SUCCESS(rc))
+ rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "%s", szCmd);
+ }
+ }
+
+ /* If multi-stepping, take the next step: */
+ if (pDbgc->cMultiStepsLeft > 0 && pEvent->idCpu == idCpuSaved)
+ {
+ int rc2 = DBGFR3StepEx(pDbgc->pUVM, pDbgc->idCpu, DBGF_STEP_F_INTO, NULL, NULL, 0, pDbgc->uMultiStepStrideLength);
+ if (RT_SUCCESS(rc2))
+ fPrintPrompt = false;
+ else
+ DBGCCmdHlpFailRc(&pDbgc->CmdHlp, pDbgc->pMultiStepCmd, rc2, "DBGFR3StepEx(,,DBGF_STEP_F_INTO,) failed");
+ }
+ else
+ pDbgc->idCpu = pEvent->idCpu;
+ break;
+ }
+
+ case DBGFEVENT_ASSERTION_HYPER:
+ {
+ pDbgc->idCpu = pEvent->idCpu;
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "\ndbgf event/%u: Hypervisor Assertion! (%s)\n"
+ "%s"
+ "%s"
+ "\n",
+ pEvent->idCpu,
+ dbgcGetEventCtx(pEvent->enmCtx),
+ pEvent->u.Assert.pszMsg1,
+ pEvent->u.Assert.pszMsg2);
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpRegPrintf(&pDbgc->CmdHlp, pEvent->idCpu, -1, pDbgc->fRegTerse);
+ break;
+ }
+
+ case DBGFEVENT_DEV_STOP:
+ {
+ pDbgc->idCpu = pEvent->idCpu;
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "\n"
+ "dbgf event/%u: DBGFSTOP (%s)\n"
+ "File: %s\n"
+ "Line: %d\n"
+ "Function: %s\n",
+ pEvent->idCpu,
+ dbgcGetEventCtx(pEvent->enmCtx),
+ pEvent->u.Src.pszFile,
+ pEvent->u.Src.uLine,
+ pEvent->u.Src.pszFunction);
+ if (RT_SUCCESS(rc) && pEvent->u.Src.pszMessage && *pEvent->u.Src.pszMessage)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "Message: %s\n",
+ pEvent->u.Src.pszMessage);
+ if (RT_SUCCESS(rc))
+ rc = DBGCCmdHlpRegPrintf(&pDbgc->CmdHlp, pEvent->idCpu, -1, pDbgc->fRegTerse);
+ break;
+ }
+
+
+ case DBGFEVENT_INVALID_COMMAND:
+ {
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Invalid command event!\n");
+ break;
+ }
+
+ case DBGFEVENT_POWERING_OFF:
+ {
+ pDbgc->fReady = false;
+ pDbgc->pIo->pfnSetReady(pDbgc->pIo, false);
+ pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\nVM is powering off!\n");
+ fPrintPrompt = false;
+ rc = VERR_GENERAL_FAILURE;
+ break;
+ }
+
+
+ default:
+ {
+ /*
+ * Probably a generic event. Look it up to find its name.
+ */
+ PCDBGCSXEVT pEvtDesc = dbgcEventLookup(pEvent->enmType);
+ if (pEvtDesc)
+ {
+ if (pEvtDesc->enmKind == kDbgcSxEventKind_Interrupt)
+ {
+ Assert(pEvtDesc->pszDesc);
+ Assert(pEvent->u.Generic.cArgs == 1);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: %s no %#llx! (%s)\n",
+ pEvent->idCpu, pEvtDesc->pszDesc, pEvent->u.Generic.auArgs[0], pEvtDesc->pszName);
+ }
+ else if (pEvtDesc->fFlags & DBGCSXEVT_F_BUGCHECK)
+ {
+ Assert(pEvent->u.Generic.cArgs >= 5);
+ char szDetails[512];
+ DBGFR3FormatBugCheck(pDbgc->pUVM, szDetails, sizeof(szDetails), pEvent->u.Generic.auArgs[0],
+ pEvent->u.Generic.auArgs[1], pEvent->u.Generic.auArgs[2],
+ pEvent->u.Generic.auArgs[3], pEvent->u.Generic.auArgs[4]);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: %s %s%s!\n%s", pEvent->idCpu,
+ pEvtDesc->pszName, pEvtDesc->pszDesc ? "- " : "",
+ pEvtDesc->pszDesc ? pEvtDesc->pszDesc : "", szDetails);
+ }
+ else if ( (pEvtDesc->fFlags & DBGCSXEVT_F_TAKE_ARG)
+ || pEvent->u.Generic.cArgs > 1
+ || ( pEvent->u.Generic.cArgs == 1
+ && pEvent->u.Generic.auArgs[0] != 0))
+ {
+ if (pEvtDesc->pszDesc)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: %s - %s!",
+ pEvent->idCpu, pEvtDesc->pszName, pEvtDesc->pszDesc);
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: %s!",
+ pEvent->idCpu, pEvtDesc->pszName);
+ if (pEvent->u.Generic.cArgs <= 1)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " arg=%#llx\n", pEvent->u.Generic.auArgs[0]);
+ else
+ {
+ for (uint32_t i = 0; i < pEvent->u.Generic.cArgs; i++)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " args[%u]=%#llx", i, pEvent->u.Generic.auArgs[i]);
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\n");
+ }
+ }
+ else
+ {
+ if (pEvtDesc->pszDesc)
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: %s - %s!\n",
+ pEvent->idCpu, pEvtDesc->pszName, pEvtDesc->pszDesc);
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event/%u: %s!\n",
+ pEvent->idCpu, pEvtDesc->pszName);
+ }
+ }
+ else
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Unknown event %d on CPU %u!\n",
+ pEvent->enmType, pEvent->idCpu);
+ break;
+ }
+ }
+
+ /*
+ * Prompt, anyone?
+ */
+ if (fPrintPrompt && RT_SUCCESS(rc))
+ {
+ /** @todo add CPU indicator to the prompt if an SMP VM? */
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "VBoxDbg> ");
+ pDbgc->fReady = true;
+ if (RT_SUCCESS(rc))
+ pDbgc->pIo->pfnSetReady(pDbgc->pIo, true);
+ pDbgc->cMultiStepsLeft = 0;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Prints any log lines from the log buffer.
+ *
+ * The caller must not call function this unless pDbgc->fLog is set.
+ *
+ * @returns VBox status code. (output related)
+ * @param pDbgc Debugger console instance data.
+ */
+static int dbgcProcessLog(PDBGC pDbgc)
+{
+ /** @todo */
+ NOREF(pDbgc);
+ return 0;
+}
+
+/** @callback_method_impl{FNRTDBGCFGLOG} */
+static DECLCALLBACK(void) dbgcDbgCfgLogCallback(RTDBGCFG hDbgCfg, uint32_t iLevel, const char *pszMsg, void *pvUser)
+{
+ /** @todo Add symbol noise setting. */
+ NOREF(hDbgCfg); NOREF(iLevel);
+ PDBGC pDbgc = (PDBGC)pvUser;
+ pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "%s", pszMsg);
+}
+
+
+/**
+ * Run the debugger console.
+ *
+ * @returns VBox status code.
+ * @param pDbgc Pointer to the debugger console instance data.
+ */
+int dbgcRun(PDBGC pDbgc)
+{
+ /*
+ * We're ready for commands now.
+ */
+ pDbgc->fReady = true;
+ pDbgc->pIo->pfnSetReady(pDbgc->pIo, true);
+
+ /*
+ * Main Debugger Loop.
+ *
+ * This loop will either block on waiting for input or on waiting on
+ * debug events. If we're forwarding the log we cannot wait for long
+ * before we must flush the log.
+ */
+ int rc;
+ for (;;)
+ {
+ rc = VERR_SEM_OUT_OF_TURN;
+ if (pDbgc->pUVM)
+ rc = DBGFR3QueryWaitable(pDbgc->pUVM);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Wait for a debug event.
+ */
+ DBGFEVENT Event;
+ rc = DBGFR3EventWait(pDbgc->pUVM, pDbgc->fLog ? 1 : 32, &Event);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgcProcessEvent(pDbgc, &Event);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (rc != VERR_TIMEOUT)
+ break;
+
+ /*
+ * Check for input.
+ */
+ if (pDbgc->pIo->pfnInput(pDbgc->pIo, 0))
+ {
+ rc = dbgcProcessInput(pDbgc, false /* fNoExecute */);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else if (rc == VERR_SEM_OUT_OF_TURN)
+ {
+ /*
+ * Wait for input. If Logging is enabled we'll only wait very briefly.
+ */
+ if (pDbgc->pIo->pfnInput(pDbgc->pIo, pDbgc->fLog ? 1 : 1000))
+ {
+ rc = dbgcProcessInput(pDbgc, false /* fNoExecute */);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ break;
+
+ /*
+ * Forward log output.
+ */
+ if (pDbgc->fLog)
+ {
+ rc = dbgcProcessLog(pDbgc);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Run the init scripts, if present.
+ *
+ * @param pDbgc The console instance.
+ */
+static void dbgcRunInitScripts(PDBGC pDbgc)
+{
+ /*
+ * Do the global one, if it exists.
+ */
+ if ( pDbgc->pszGlobalInitScript
+ && *pDbgc->pszGlobalInitScript != '\0'
+ && RTFileExists(pDbgc->pszGlobalInitScript))
+ dbgcEvalScript(pDbgc, pDbgc->pszGlobalInitScript, true /*fAnnounce*/);
+
+ /*
+ * Then do the local one, if it exists.
+ */
+ if ( pDbgc->pszLocalInitScript
+ && *pDbgc->pszLocalInitScript != '\0'
+ && RTFileExists(pDbgc->pszLocalInitScript))
+ dbgcEvalScript(pDbgc, pDbgc->pszLocalInitScript, true /*fAnnounce*/);
+}
+
+
+/**
+ * Reads the CFGM configuration of the DBGC.
+ *
+ * Popuplates the PDBGC::pszHistoryFile, PDBGC::pszGlobalInitScript and
+ * PDBGC::pszLocalInitScript members.
+ *
+ * @returns VBox status code.
+ * @param pDbgc The console instance.
+ * @param pUVM The user mode VM handle.
+ */
+static int dbgcReadConfig(PDBGC pDbgc, PUVM pUVM)
+{
+ /*
+ * Get and validate the configuration node.
+ */
+ PCFGMNODE pNode = CFGMR3GetChild(CFGMR3GetRootU(pUVM), "DBGC");
+ int rc = CFGMR3ValidateConfig(pNode, "/DBGC/",
+ "Enabled|"
+ "HistoryFile|"
+ "LocalInitScript|"
+ "GlobalInitScript|",
+ "*", "DBGC", 0);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Query the values.
+ */
+ char szHomeDefault[RTPATH_MAX];
+ rc = RTPathUserHome(szHomeDefault, sizeof(szHomeDefault) - 32);
+ AssertLogRelRCReturn(rc, rc);
+ size_t cchHome = strlen(szHomeDefault);
+
+ /** @cfgm{/DBGC/HistoryFile, string, ${HOME}/.vboxdbgc-history}
+ * The command history file of the VBox debugger. */
+ rc = RTPathAppend(szHomeDefault, sizeof(szHomeDefault), ".vboxdbgc-history");
+ AssertLogRelRCReturn(rc, rc);
+
+ char szPath[RTPATH_MAX];
+ rc = CFGMR3QueryStringDef(pNode, "HistoryFile", szPath, sizeof(szPath), szHomeDefault);
+ AssertLogRelRCReturn(rc, rc);
+
+ pDbgc->pszHistoryFile = RTStrDup(szPath);
+ AssertReturn(pDbgc->pszHistoryFile, VERR_NO_STR_MEMORY);
+
+ /** @cfgm{/DBGC/GlobalInitFile, string, ${HOME}/.vboxdbgc-init}
+ * The global init script of the VBox debugger. */
+ szHomeDefault[cchHome] = '\0';
+ rc = RTPathAppend(szHomeDefault, sizeof(szHomeDefault), ".vboxdbgc-init");
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = CFGMR3QueryStringDef(pNode, "GlobalInitScript", szPath, sizeof(szPath), szHomeDefault);
+ AssertLogRelRCReturn(rc, rc);
+
+ pDbgc->pszGlobalInitScript = RTStrDup(szPath);
+ AssertReturn(pDbgc->pszGlobalInitScript, VERR_NO_STR_MEMORY);
+
+ /** @cfgm{/DBGC/LocalInitFile, string, none}
+ * The VM local init script of the VBox debugger. */
+ rc = CFGMR3QueryString(pNode, "LocalInitScript", szPath, sizeof(szPath));
+ if (RT_SUCCESS(rc))
+ {
+ pDbgc->pszLocalInitScript = RTStrDup(szPath);
+ AssertReturn(pDbgc->pszLocalInitScript, VERR_NO_STR_MEMORY);
+ }
+ else
+ {
+ AssertLogRelReturn(rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT, rc);
+ pDbgc->pszLocalInitScript = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGC::pfnOutput
+ */
+static DECLCALLBACK(int) dbgcOutputNative(void *pvUser, const char *pachChars, size_t cbChars)
+{
+ PDBGC pDbgc = (PDBGC)pvUser;
+ return pDbgc->pIo->pfnWrite(pDbgc->pIo, pachChars, cbChars, NULL /*pcbWritten*/);
+}
+
+
+/**
+ * Creates a a new instance.
+ *
+ * @returns VBox status code.
+ * @param ppDbgc Where to store the pointer to the instance data.
+ * @param pIo Pointer to the I/O callback table.
+ * @param fFlags The flags.
+ */
+int dbgcCreate(PDBGC *ppDbgc, PCDBGCIO pIo, unsigned fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pIo, VERR_INVALID_POINTER);
+ AssertMsgReturn(!fFlags, ("%#x", fFlags), VERR_INVALID_PARAMETER);
+
+ /*
+ * Allocate and initialize.
+ */
+ PDBGC pDbgc = (PDBGC)RTMemAllocZ(sizeof(*pDbgc));
+ if (!pDbgc)
+ return VERR_NO_MEMORY;
+
+ dbgcInitCmdHlp(pDbgc);
+ pDbgc->pIo = pIo;
+ pDbgc->pfnOutput = dbgcOutputNative;
+ pDbgc->pvOutputUser = pDbgc;
+ pDbgc->pVM = NULL;
+ pDbgc->pUVM = NULL;
+ pDbgc->idCpu = 0;
+ pDbgc->hDbgAs = DBGF_AS_GLOBAL;
+ pDbgc->pszEmulation = "CodeView/WinDbg";
+ pDbgc->paEmulationCmds = &g_aCmdsCodeView[0];
+ pDbgc->cEmulationCmds = g_cCmdsCodeView;
+ pDbgc->paEmulationFuncs = &g_aFuncsCodeView[0];
+ pDbgc->cEmulationFuncs = g_cFuncsCodeView;
+ //pDbgc->fLog = false;
+ pDbgc->fRegTerse = true;
+ pDbgc->fStepTraceRegs = true;
+ //pDbgc->cPagingHierarchyDumps = 0;
+ //pDbgc->DisasmPos = {0};
+ //pDbgc->SourcePos = {0};
+ //pDbgc->DumpPos = {0};
+ pDbgc->pLastPos = &pDbgc->DisasmPos;
+ //pDbgc->cbDumpElement = 0;
+ //pDbgc->cVars = 0;
+ //pDbgc->paVars = NULL;
+ //pDbgc->pPlugInHead = NULL;
+ //pDbgc->pFirstBp = NULL;
+ RTListInit(&pDbgc->LstTraceFlowMods);
+ //pDbgc->abSearch = {0};
+ //pDbgc->cbSearch = 0;
+ pDbgc->cbSearchUnit = 1;
+ pDbgc->cMaxSearchHits = 1;
+ //pDbgc->SearchAddr = {0};
+ //pDbgc->cbSearchRange = 0;
+
+ //pDbgc->uInputZero = 0;
+ //pDbgc->iRead = 0;
+ //pDbgc->iWrite = 0;
+ //pDbgc->cInputLines = 0;
+ //pDbgc->fInputOverflow = false;
+ pDbgc->fReady = true;
+ pDbgc->pszScratch = &pDbgc->achScratch[0];
+ //pDbgc->iArg = 0;
+ //pDbgc->rcOutput = 0;
+ //pDbgc->rcCmd = 0;
+
+ //pDbgc->pszHistoryFile = NULL;
+ //pDbgc->pszGlobalInitScript = NULL;
+ //pDbgc->pszLocalInitScript = NULL;
+
+ dbgcEvalInit();
+
+ *ppDbgc = pDbgc;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a DBGC instance created by dbgcCreate.
+ *
+ * @param pDbgc Pointer to the debugger console instance data.
+ */
+void dbgcDestroy(PDBGC pDbgc)
+{
+ AssertPtr(pDbgc);
+
+ /* Disable log hook. */
+ if (pDbgc->fLog)
+ {
+
+ }
+
+ /* Detach from the VM. */
+ if (pDbgc->pUVM)
+ DBGFR3Detach(pDbgc->pUVM);
+
+ /* Free config strings. */
+ RTStrFree(pDbgc->pszGlobalInitScript);
+ pDbgc->pszGlobalInitScript = NULL;
+ RTStrFree(pDbgc->pszLocalInitScript);
+ pDbgc->pszLocalInitScript = NULL;
+ RTStrFree(pDbgc->pszHistoryFile);
+ pDbgc->pszHistoryFile = NULL;
+
+ /* Finally, free the instance memory. */
+ RTMemFree(pDbgc);
+}
+
+
+/**
+ * Make a console instance.
+ *
+ * This will not return until either an 'exit' command is issued or a error code
+ * indicating connection loss is encountered.
+ *
+ * @returns VINF_SUCCESS if console termination caused by the 'exit' command.
+ * @returns The VBox status code causing the console termination.
+ *
+ * @param pUVM The user mode VM handle.
+ * @param pIo Pointer to the I/O callback structure. This must contain
+ * a full set of function pointers to service the console.
+ * @param fFlags Reserved, must be zero.
+ * @remarks A forced termination of the console is easiest done by forcing the
+ * callbacks to return fatal failures.
+ */
+DBGDECL(int) DBGCCreate(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pUVM, VERR_INVALID_VM_HANDLE);
+ PVM pVM = NULL;
+ if (pUVM)
+ {
+ pVM = VMR3GetVM(pUVM);
+ AssertPtrReturn(pVM, VERR_INVALID_VM_HANDLE);
+ }
+
+ /*
+ * Allocate and initialize instance data
+ */
+ PDBGC pDbgc;
+ int rc = dbgcCreate(&pDbgc, pIo, fFlags);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!HMR3IsEnabled(pUVM) && !NEMR3IsEnabled(pUVM))
+ pDbgc->hDbgAs = DBGF_AS_RC_AND_GC_GLOBAL;
+
+ /*
+ * Print welcome message.
+ */
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "Welcome to the VirtualBox Debugger!\n");
+
+ /*
+ * Attach to the specified VM.
+ */
+ if (RT_SUCCESS(rc) && pUVM)
+ {
+ rc = dbgcReadConfig(pDbgc, pUVM);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGFR3Attach(pUVM);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgc->pVM = pVM;
+ pDbgc->pUVM = pUVM;
+ pDbgc->idCpu = 0;
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL,
+ "Current VM is %08x, CPU #%u\n" /** @todo get and print the VM name! */
+ , pDbgc->pVM, pDbgc->idCpu);
+ }
+ else
+ rc = pDbgc->CmdHlp.pfnVBoxError(&pDbgc->CmdHlp, rc, "When trying to attach to VM %p\n", pDbgc->pVM);
+ }
+ else
+ rc = pDbgc->CmdHlp.pfnVBoxError(&pDbgc->CmdHlp, rc, "Error reading configuration\n");
+ }
+
+ /*
+ * Load plugins.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (pVM)
+ DBGFR3PlugInLoadAll(pDbgc->pUVM);
+ dbgcEventInit(pDbgc);
+ dbgcRunInitScripts(pDbgc);
+
+ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "VBoxDbg> ");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Set debug config log callback.
+ */
+ RTDBGCFG hDbgCfg = DBGFR3AsGetConfig(pUVM);
+ if ( hDbgCfg != NIL_RTDBGCFG
+ && RTDbgCfgRetain(hDbgCfg) != UINT32_MAX)
+ {
+ int rc2 = RTDbgCfgSetLogCallback(hDbgCfg, dbgcDbgCfgLogCallback, pDbgc);
+ if (RT_FAILURE(rc2))
+ {
+ hDbgCfg = NIL_RTDBGCFG;
+ RTDbgCfgRelease(hDbgCfg);
+ }
+ }
+ else
+ hDbgCfg = NIL_RTDBGCFG;
+
+
+ /*
+ * Run the debugger main loop.
+ */
+ rc = dbgcRun(pDbgc);
+
+
+ /*
+ * Remove debug config log callback.
+ */
+ if (hDbgCfg != NIL_RTDBGCFG)
+ {
+ RTDbgCfgSetLogCallback(hDbgCfg, NULL, NULL);
+ RTDbgCfgRelease(hDbgCfg);
+ }
+ }
+ dbgcEventTerm(pDbgc);
+ }
+ else
+ pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\nDBGCCreate error: %Rrc\n", rc);
+
+
+ /*
+ * Cleanup console debugger session.
+ */
+ dbgcDestroy(pDbgc);
+ return rc == VERR_DBGC_QUIT ? VINF_SUCCESS : rc;
+}
+
diff --git a/src/VBox/Debugger/DBGPlugInCommonELF.cpp b/src/VBox/Debugger/DBGPlugInCommonELF.cpp
new file mode 100644
index 00000000..c40cf3cb
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInCommonELF.cpp
@@ -0,0 +1,96 @@
+/* $Id: DBGPlugInCommonELF.cpp $ */
+/** @file
+ * DBGPlugInCommonELF - Common code for dealing with ELF images.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugInCommonELF.h"
+
+#include <VBox/vmm/vmmr3vtable.h>
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct DBGDIGGERELFSEG
+{
+ /** The segment load address. */
+ RTGCPTR uLoadAddr;
+ /** The last address in the segment. */
+ RTGCPTR uLastAddr;
+ /** The segment index. */
+ RTDBGSEGIDX iSeg;
+} DBGDIGGERELFSEG;
+typedef DBGDIGGERELFSEG *PDBGDIGGERELFSEG;
+
+
+/**
+ * Links the segments of the module into the address space.
+ *
+ * @returns VBox status code on failure.
+ *
+ * @param hAs The address space.
+ * @param hMod The module.
+ * @param paSegs Array of segment indexes and load addresses.
+ * @param cSegs The number of segments in the array.
+ */
+static int dbgDiggerCommonLinkElfSegs(RTDBGAS hAs, RTDBGMOD hMod, PDBGDIGGERELFSEG paSegs, uint32_t cSegs)
+{
+ for (uint32_t i = 0; i < cSegs; i++)
+ if (paSegs[i].iSeg != NIL_RTDBGSEGIDX)
+ {
+ int rc = RTDbgAsModuleLinkSeg(hAs, hMod, paSegs[i].iSeg, paSegs[i].uLoadAddr, RTDBGASLINK_FLAGS_REPLACE);
+ if (RT_FAILURE(rc))
+ {
+ RTDbgAsModuleUnlink(hAs, hMod);
+ return rc;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Instantiate the code templates for dealing with the two ELF versions.
+ */
+
+#define ELF_MODE 32
+#include "DBGPlugInCommonELFTmpl.cpp.h"
+
+#undef ELF_MODE
+#define ELF_MODE 64
+#include "DBGPlugInCommonELFTmpl.cpp.h"
+
diff --git a/src/VBox/Debugger/DBGPlugInCommonELF.h b/src/VBox/Debugger/DBGPlugInCommonELF.h
new file mode 100644
index 00000000..ec88c5bc
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInCommonELF.h
@@ -0,0 +1,63 @@
+/* $Id: DBGPlugInCommonELF.h $ */
+/** @file
+ * DBGPlugInCommonELF - Common code for dealing with ELF images, Header.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_DBGPlugInCommonELF_h
+#define DEBUGGER_INCLUDED_SRC_DBGPlugInCommonELF_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/types.h>
+#include <iprt/formats/elf32.h>
+#include <iprt/formats/elf64.h>
+
+/** @name DBGDiggerCommonParseElf32Mod and DBGDiggerCommonParseElf64Mod flags
+ * @{ */
+/** Whether to adjust the symbol values or not. */
+#define DBG_DIGGER_ELF_ADJUST_SYM_VALUE RT_BIT_32(0)
+/** Indicates that we're missing section headers and that
+ * all section indexes are to be considered invalid. (Solaris hack.)
+ * This flag is incompatible with DBG_DIGGER_ELF_ADJUST_SYM_VALUE. */
+#define DBG_DIGGER_ELF_FUNNY_SHDRS RT_BIT_32(1)
+/** Valid bit mask. */
+#define DBG_DIGGER_ELF_MASK UINT32_C(0x00000003)
+/** @} */
+
+int DBGDiggerCommonParseElf32Mod(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszModName, const char *pszFilename, uint32_t fFlags,
+ Elf32_Ehdr const *pEhdr, Elf32_Shdr const *paShdrs,
+ Elf32_Sym const *paSyms, size_t cMaxSyms,
+ char const *pbStrings, size_t cbMaxStrings,
+ RTGCPTR MinAddr, RTGCPTR MaxAddr, uint64_t uModTag);
+
+int DBGDiggerCommonParseElf64Mod(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszModName, const char *pszFilename, uint32_t fFlags,
+ Elf64_Ehdr const *pEhdr, Elf64_Shdr const *paShdrs,
+ Elf64_Sym const *paSyms, size_t cMaxSyms,
+ char const *pbStrings, size_t cbMaxStrings,
+ RTGCPTR MinAddr, RTGCPTR MaxAddr, uint64_t uModTag);
+
+#endif /* !DEBUGGER_INCLUDED_SRC_DBGPlugInCommonELF_h */
+
diff --git a/src/VBox/Debugger/DBGPlugInCommonELFTmpl.cpp.h b/src/VBox/Debugger/DBGPlugInCommonELFTmpl.cpp.h
new file mode 100644
index 00000000..16be826a
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInCommonELFTmpl.cpp.h
@@ -0,0 +1,347 @@
+/* $Id: DBGPlugInCommonELFTmpl.cpp.h $ */
+/** @file
+ * DBGPlugInCommonELF - Code Template for dealing with one kind of ELF.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#if ELF_MODE == 32
+# define Elf_Ehdr Elf32_Ehdr
+# define Elf_Shdr Elf32_Shdr
+# define Elf_Phdr Elf32_Phdr
+# define Elf_Sym Elf32_Sym
+# define MY_ELFCLASS ELFCLASS32
+# define ELF_ST_BIND ELF32_ST_BIND
+# define DBGDiggerCommonParseElfMod DBGDiggerCommonParseElf32Mod
+#else
+# define Elf_Ehdr Elf64_Ehdr
+# define Elf_Shdr Elf64_Shdr
+# define Elf_Phdr Elf64_Phdr
+# define Elf_Sym Elf64_Sym
+# define MY_ELFCLASS ELFCLASS64
+# define ELF_ST_BIND ELF64_ST_BIND
+# define DBGDiggerCommonParseElfMod DBGDiggerCommonParseElf64Mod
+#endif
+
+
+/**
+ * Common ELF module parser.
+ *
+ * It takes the essential bits of the ELF module (elf header, section headers,
+ * symbol table and string table), and inserts/updates the module and symbols.
+ *
+ *
+ * @returns VBox status code.
+ *
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pszModName The module name.
+ * @param pszFilename The filename. optional.
+ * @param fFlags Flags.
+ * @param pEhdr Pointer to the ELF header.
+ * @param paShdrs Pointer to the section headers. The caller must verify that
+ * the e_shnum member of the ELF header is within the bounds of
+ * this table. The caller should also adjust the section addresses
+ * so these correspond to actual load addresses.
+ * @param paSyms Pointer to the symbol table.
+ * @param cMaxSyms The maximum number of symbols paSyms may hold. This isn't
+ * the exact count, it's just a cap for avoiding SIGSEGVs
+ * and general corruption.
+ * @param pbStrings Pointer to the string table.
+ * @param cbMaxStrings The size of the memory pbStrings points to. This doesn't
+ * have to match the string table size exactly, it's just to
+ * avoid SIGSEGV when a bad string index is encountered.
+ * @param MinAddr Min address to care about.
+ * @param MaxAddr Max address to care about (inclusive). Together
+ * with MinAddr this forms a valid address range for
+ * symbols and sections that we care about. Anything
+ * outside the range is ignored, except when doing
+ * sanity checks..
+ * @param uModTag Module tag. Pass 0 if tagging is of no interest.
+ */
+int DBGDiggerCommonParseElfMod(PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszModName, const char *pszFilename, uint32_t fFlags,
+ Elf_Ehdr const *pEhdr, Elf_Shdr const *paShdrs,
+ Elf_Sym const *paSyms, size_t cMaxSyms,
+ char const *pbStrings, size_t cbMaxStrings,
+ RTGCPTR MinAddr, RTGCPTR MaxAddr, uint64_t uModTag)
+{
+ AssertPtrReturn(pUVM, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVMM, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszModName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~DBG_DIGGER_ELF_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn((fFlags & (DBG_DIGGER_ELF_FUNNY_SHDRS | DBG_DIGGER_ELF_ADJUST_SYM_VALUE))
+ != (DBG_DIGGER_ELF_FUNNY_SHDRS | DBG_DIGGER_ELF_ADJUST_SYM_VALUE), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(paShdrs, VERR_INVALID_POINTER);
+ AssertPtrReturn(paSyms, VERR_INVALID_POINTER);
+ AssertPtrReturn(pbStrings, VERR_INVALID_POINTER);
+
+ /*
+ * Validate the ELF header.
+ */
+ if ( pEhdr->e_ident[EI_MAG0] != ELFMAG0
+ || pEhdr->e_ident[EI_MAG1] != ELFMAG1
+ || pEhdr->e_ident[EI_MAG2] != ELFMAG2
+ || pEhdr->e_ident[EI_MAG3] != ELFMAG3)
+ return VERR_INVALID_EXE_SIGNATURE;
+ if (pEhdr->e_ident[EI_CLASS] != MY_ELFCLASS)
+ return VERR_LDRELF_MACHINE;
+
+ if (pEhdr->e_ident[EI_DATA] != ELFDATA2LSB)
+ return VERR_LDRELF_ODD_ENDIAN;
+ if (pEhdr->e_ident[EI_VERSION] != EV_CURRENT)
+ return VERR_LDRELF_VERSION;
+ if (pEhdr->e_version != EV_CURRENT)
+ return VERR_LDRELF_VERSION;
+ if (pEhdr->e_ehsize != sizeof(*pEhdr))
+ return VERR_BAD_EXE_FORMAT;
+
+#if ELF_MODE == 32
+ if ( pEhdr->e_machine != EM_386
+ && pEhdr->e_machine != EM_486)
+ return VERR_LDRELF_MACHINE;
+#else
+ if (pEhdr->e_machine != EM_X86_64)
+ return VERR_LDRELF_MACHINE;
+#endif
+
+ if ( pEhdr->e_type != ET_DYN
+ && pEhdr->e_type != ET_REL
+ && pEhdr->e_type != ET_EXEC) //??
+ return VERR_BAD_EXE_FORMAT;
+ if ( pEhdr->e_phentsize != sizeof(Elf_Phdr)
+ && pEhdr->e_phentsize) //??
+ return VERR_BAD_EXE_FORMAT;
+ if (pEhdr->e_shentsize != sizeof(Elf_Shdr))
+ return VERR_BAD_EXE_FORMAT;
+ if (pEhdr->e_shentsize != sizeof(Elf_Shdr))
+ return VERR_BAD_EXE_FORMAT;
+ if (!ASMMemIsZero(&pEhdr->e_ident[EI_PAD], EI_NIDENT - EI_PAD)) //??
+ return VERR_BAD_EXE_FORMAT;
+
+ /*
+ * Validate the section headers, finding the string and symbol table
+ * headers and the load address while at it.
+ */
+ uint64_t uLoadAddr = UINT64_MAX;
+ const Elf_Shdr *pSymShdr = NULL;
+ const Elf_Shdr *pStrShdr = NULL;
+ for (unsigned iSh = fFlags & DBG_DIGGER_ELF_FUNNY_SHDRS ? 1 : 0; iSh < pEhdr->e_shnum; iSh++)
+ {
+ /* Minimal validation. */
+ if (paShdrs[iSh].sh_link >= pEhdr->e_shnum)
+ return VERR_BAD_EXE_FORMAT;
+
+ /* Is it the symbol table?*/
+ if (paShdrs[iSh].sh_type == SHT_SYMTAB)
+ {
+ if (pSymShdr)
+ return VERR_LDRELF_MULTIPLE_SYMTABS;
+ pSymShdr = &paShdrs[iSh];
+ if (pSymShdr->sh_entsize != sizeof(Elf32_Sym))
+ return VERR_BAD_EXE_FORMAT;
+ pStrShdr = &paShdrs[paShdrs[iSh].sh_link];
+ }
+ if (uLoadAddr > paShdrs[iSh].sh_addr)
+ uLoadAddr = paShdrs[iSh].sh_addr;
+ }
+
+ /*
+ * Validate the symbol table and determine the max section index
+ * when DBG_DIGGER_ELF_FUNNY_SHDRS is flagged.
+ */
+ uint32_t uMaxShIdx = fFlags & DBG_DIGGER_ELF_FUNNY_SHDRS ? 0 : pEhdr->e_shnum - 1;
+ size_t const cbStrings = pStrShdr ? pStrShdr->sh_size : cbMaxStrings;
+ size_t const cSyms = pSymShdr
+ ? RT_MIN(cMaxSyms, pSymShdr->sh_size / sizeof(Elf_Sym))
+ : cMaxSyms;
+ for (size_t iSym = 1; iSym < cSyms; iSym++)
+ {
+ if (paSyms[iSym].st_name >= cbStrings)
+ return VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET;
+ if (fFlags & DBG_DIGGER_ELF_FUNNY_SHDRS)
+ {
+ if ( paSyms[iSym].st_shndx > uMaxShIdx
+ && paSyms[iSym].st_shndx < SHN_LORESERVE)
+ uMaxShIdx = paSyms[iSym].st_shndx;
+ }
+ else if ( paSyms[iSym].st_shndx >= pEhdr->e_shnum
+ && paSyms[iSym].st_shndx != SHN_UNDEF
+ && ( paSyms[iSym].st_shndx < SHN_LORESERVE
+ /*|| paSyms[iSym].st_shndx > SHN_HIRESERVE*/
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_WEAK) )
+ return VERR_BAD_EXE_FORMAT;
+ }
+ if (uMaxShIdx > 4096)
+ return VERR_BAD_EXE_FORMAT;
+
+ /*
+ * Create new module.
+ * The funny ELF section headers on solaris makes this very complicated.
+ */
+ uint32_t cSegs = uMaxShIdx + 1;
+ PDBGDIGGERELFSEG paSegs = (PDBGDIGGERELFSEG)alloca(sizeof(paSegs[0]) * cSegs);
+ for (uint32_t i = 0; i < cSegs; i++)
+ {
+ paSegs[i].uLoadAddr = RTGCPTR_MAX;
+ paSegs[i].uLastAddr = 0;
+ paSegs[i].iSeg = NIL_RTDBGSEGIDX;
+ }
+
+ RTDBGMOD hMod;
+ int rc = RTDbgModCreate(&hMod, pszModName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTDbgModSetTag(hMod, uModTag); AssertRC(rc);
+
+ if (fFlags & DBG_DIGGER_ELF_FUNNY_SHDRS)
+ {
+ /* Seek out the min and max symbol values for each section. */
+ for (uint32_t iSym = 1; iSym < cSyms; iSym++)
+ {
+ /* Ignore undefined, absolute and weak symbols in this pass,
+ but include local ones as well as nameless. */
+ uint32_t iSh = paSyms[iSym].st_shndx;
+ if ( iSh != SHN_UNDEF
+ && iSh < cSegs
+ && ( ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_LOCAL))
+ {
+ /* Calc the address and check that it doesn't wrap with the size. */
+ RTGCUINTPTR Address = paSyms[iSym].st_value;
+ RTGCUINTPTR AddressLast = Address + RT_MAX(paSyms[iSym].st_size, 1) - 1;
+ if (AddressLast < Address)
+ continue;
+ if ( Address < MinAddr
+ || AddressLast > MaxAddr)
+ continue;
+
+ /* update min/max. */
+ if (Address < paSegs[iSh].uLoadAddr)
+ paSegs[iSh].uLoadAddr = Address;
+ if (AddressLast > paSegs[iSh].uLastAddr)
+ paSegs[iSh].uLastAddr = AddressLast;
+ }
+ }
+
+ /* Add the segments and fill in the translation table. */
+ RTGCPTR uRvaNext = 0;
+ for (unsigned i = 0; i < cSegs; i++)
+ if (paSegs[i].uLastAddr != 0)
+ {
+ char szSeg[32];
+ RTStrPrintf(szSeg, sizeof(szSeg), "sec%02u", i);
+ RTGCPTR cbSeg = paSegs[i].uLastAddr - paSegs[i].uLoadAddr + 1;
+ rc = RTDbgModSegmentAdd(hMod, uRvaNext, cbSeg, szSeg, 0 /*fFlags*/, &paSegs[i].iSeg);
+ if (RT_FAILURE(rc))
+ break;
+ uRvaNext += RT_ALIGN_T(cbSeg, 32, RTGCPTR);
+ }
+ }
+ else
+ {
+ /* Add the segments and fill in the translation table. */
+ for (unsigned i = 0; i < cSegs; i++)
+ if (paShdrs[i].sh_flags & SHF_ALLOC)
+ {
+ char szSeg[32];
+ RTStrPrintf(szSeg, sizeof(szSeg), "sec%02u", i);
+ rc = RTDbgModSegmentAdd(hMod, paShdrs[i].sh_addr - uLoadAddr, paShdrs[i].sh_size, szSeg, 0 /*fFlags*/, &paSegs[i].iSeg);
+ if (RT_FAILURE(rc))
+ break;
+ paSegs[i].uLoadAddr = paShdrs[i].sh_addr;
+ paSegs[i].uLastAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size - 1;
+ }
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTDbgModRelease(hMod);
+ return rc;
+ }
+
+
+ /*
+ * Add all relevant symbols in the module
+ */
+ for (uint32_t iSym = 1; iSym < cSyms; iSym++)
+ {
+ /* Undefined symbols are not exports, they are imports. */
+ RTDBGSEGIDX iSeg = paSyms[iSym].st_shndx;
+ if ( iSeg != SHN_UNDEF
+ && ( ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_LOCAL
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_WEAK))
+ {
+ /* Get the symbol name. */
+ if (paSyms[iSym].st_name >= cbMaxStrings)
+ continue;
+ const char *pszSymbol = pbStrings + paSyms[iSym].st_name;
+ if (!*pszSymbol)
+ continue;
+
+ /* Calc the address (value) and size. */
+ RTGCUINTPTR cbSym = paSyms[iSym].st_size;
+ RTGCUINTPTR offSeg = paSyms[iSym].st_value;
+ if (iSeg == SHN_ABS)
+ iSeg = RTDBGSEGIDX_ABS; /* absolute symbols are not subject to any relocation. */
+ else
+ {
+ Assert(iSeg < cSegs);
+ if (fFlags & (DBG_DIGGER_ELF_FUNNY_SHDRS | DBG_DIGGER_ELF_ADJUST_SYM_VALUE))
+ offSeg -= paSegs[iSeg].uLoadAddr;
+ iSeg = paSegs[iSeg].iSeg;
+ if (iSeg == NIL_RTDBGSEGIDX)
+ continue;
+ }
+ if (offSeg + cbSym < offSeg)
+ continue;
+
+ rc = RTDbgModSymbolAdd(hMod, pszSymbol, iSeg, offSeg, cbSym, 0 /*fFlags*/, NULL);
+ Log(("%02x:%RGv %RGv %s!%s (rc=%Rrc)\n", paSyms[iSym].st_shndx, offSeg, cbSym, pszModName, pszSymbol, rc));
+ }
+ /*else: silently ignore */
+ }
+
+ /*
+ * Link it into the address space.
+ */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hAs != NIL_RTDBGAS)
+ rc = dbgDiggerCommonLinkElfSegs(hAs, hMod, paSegs, cSegs);
+ else
+ rc = VERR_INTERNAL_ERROR;
+ RTDbgModRelease(hMod);
+ RTDbgAsRelease(hAs);
+ return rc;
+}
+
+
+#undef Elf_Ehdr
+#undef Elf_Shdr
+#undef Elf_Phdr
+#undef Elf_Sym
+#undef MY_ELFCLASS
+#undef ELF_ST_BIND
+#undef DBGDiggerCommonParseElfMod
+
diff --git a/src/VBox/Debugger/DBGPlugInDarwin.cpp b/src/VBox/Debugger/DBGPlugInDarwin.cpp
new file mode 100644
index 00000000..17bbfe9a
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInDarwin.cpp
@@ -0,0 +1,1120 @@
+/* $Id: DBGPlugInDarwin.cpp $ */
+/** @file
+ * DBGPlugInDarwin - Debugger and Guest OS Digger Plugin For Darwin / OS X.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugIns.h"
+#include <VBox/vmm/vmmr3vtable.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/ctype.h>
+#include <iprt/formats/mach-o.h>
+
+#undef LogRel2
+#define LogRel2 LogRel
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** @name Internal Darwin structures
+ * @{ */
+
+/**
+ * 32-bit darwin kernel module info structure (kmod_info_t).
+ */
+typedef struct OSX32_kmod_info
+{
+ uint32_t next;
+ int32_t info_version;
+ uint32_t id;
+ char name[64];
+ char version[64];
+ int32_t reference_count;
+ uint32_t reference_list; /**< Points to kmod_reference_t. */
+ uint32_t address; /**< Where in memory the kext is loaded. */
+ uint32_t size;
+ uint32_t hdr_size;
+ uint32_t start; /**< Address of kmod_start_func_t. */
+ uint32_t stop; /**< Address of kmod_stop_func_t. */
+} OSX32_kmod_info_t;
+
+/**
+ * 32-bit darwin kernel module info structure (kmod_info_t).
+ */
+#pragma pack(1)
+typedef struct OSX64_kmod_info
+{
+ uint64_t next;
+ int32_t info_version;
+ uint32_t id;
+ char name[64];
+ char version[64];
+ int32_t reference_count;
+ uint64_t reference_list; /**< Points to kmod_reference_t. Misaligned, duh. */
+ uint64_t address; /**< Where in memory the kext is loaded. */
+ uint64_t size;
+ uint64_t hdr_size;
+ uint64_t start; /**< Address of kmod_start_func_t. */
+ uint64_t stop; /**< Address of kmod_stop_func_t. */
+} OSX64_kmod_info_t;
+#pragma pack()
+
+/** The value of the info_version field. */
+#define OSX_KMOD_INFO_VERSION INT32_C(1)
+
+/** @} */
+
+
+/**
+ * Linux guest OS digger instance data.
+ */
+typedef struct DBGDIGGERDARWIN
+{
+ /** Whether the information is valid or not.
+ * (For fending off illegal interface method calls.) */
+ bool fValid;
+
+ /** Set if 64-bit kernel, clear if 32-bit.
+ * Set during probing. */
+ bool f64Bit;
+ /** The address of an kernel version string (there are several).
+ * This is set during probing. */
+ DBGFADDRESS AddrKernelVersion;
+ /** Kernel base address.
+ * This is set during probing. */
+ DBGFADDRESS AddrKernel;
+
+ /** The kernel message log interface. */
+ DBGFOSIDMESG IDmesg;
+} DBGDIGGERDARWIN;
+/** Pointer to the linux guest OS digger instance data. */
+typedef DBGDIGGERDARWIN *PDBGDIGGERDARWIN;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validates a 32-bit darwin kernel address */
+#define OSX32_VALID_ADDRESS(Addr) ((Addr) > UINT32_C(0x00001000) && (Addr) < UINT32_C(0xfffff000))
+/** Validates a 64-bit darwin kernel address */
+#define OSX64_VALID_ADDRESS(Addr) ((Addr) > UINT64_C(0xffff800000000000) && (Addr) < UINT64_C(0xfffffffffffff000))
+/** Validates a 32-bit or 64-bit darwin kernel address. */
+#define OSX_VALID_ADDRESS(a_f64Bits, a_Addr) \
+ ((a_f64Bits) ? OSX64_VALID_ADDRESS(a_Addr) : OSX32_VALID_ADDRESS(a_Addr))
+
+/** AppleOsX on little endian ASCII systems. */
+#define DIG_DARWIN_MOD_TAG UINT64_C(0x58734f656c707041)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgDiggerDarwinInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData);
+
+
+
+/**
+ * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog}
+ */
+static DECLCALLBACK(int) dbgDiggerDarwinIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, uint32_t fFlags,
+ uint32_t cMessages, char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ RT_NOREF1(fFlags);
+ PDBGDIGGERDARWIN pData = RT_FROM_MEMBER(pThis, DBGDIGGERDARWIN, IDmesg);
+
+ if (cMessages < 1)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * The 'msgbufp' variable points to a struct msgbuf (bsd/kern/subr_log.c).
+ */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ RTDBGMOD hMod;
+ int rc = RTDbgAsModuleByName(hAs, "mach_kernel", 0, &hMod);
+ if (RT_FAILURE(rc))
+ return VERR_NOT_FOUND;
+ RTDbgAsRelease(hAs);
+
+ DBGFADDRESS Addr;
+ RTGCPTR GCPtrMsgBufP = 0;
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByName(hMod, "_msgbufp", &SymInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SymInfo.Value + pData->AddrKernel.FlatPtr),
+ &GCPtrMsgBufP, pData->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to read _msgbufp at %RGv: %Rrc\n", Addr.FlatPtr, rc));
+ return VERR_NOT_FOUND;
+ }
+ if (!OSX_VALID_ADDRESS(pData->f64Bit, GCPtrMsgBufP))
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid address for _msgbufp: %RGv\n", GCPtrMsgBufP));
+ return VERR_NOT_FOUND;
+ }
+ }
+ else
+ {
+ rc = RTDbgModSymbolByName(hMod, "_msgbuf", &SymInfo);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to find _msgbufp and _msgbuf: %Rrc\n", rc));
+ return VERR_NOT_FOUND;
+ }
+ GCPtrMsgBufP = SymInfo.Value + pData->AddrKernel.FlatPtr;
+ if (!OSX_VALID_ADDRESS(pData->f64Bit, GCPtrMsgBufP))
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid address for _msgbuf: %RGv\n", GCPtrMsgBufP));
+ return VERR_NOT_FOUND;
+ }
+ }
+
+ /*
+ * Read the msgbuf structure.
+ */
+ struct
+ {
+ uint32_t msg_magic;
+ uint32_t msg_size;
+ uint32_t msg_bufx;
+ uint32_t msg_bufr;
+ uint64_t msg_bufc; /**< Size depends on windows size. */
+ } MsgBuf;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, GCPtrMsgBufP),
+ &MsgBuf, sizeof(MsgBuf) - (pData->f64Bit ? 0 : sizeof(uint32_t)) );
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to read msgbuf struct at %RGv: %Rrc\n", Addr.FlatPtr, rc));
+ return VERR_NOT_FOUND;
+ }
+ if (!pData->f64Bit)
+ MsgBuf.msg_bufc &= UINT32_MAX;
+
+ /*
+ * Validate the structure.
+ */
+ if ( MsgBuf.msg_magic != UINT32_C(0x63061)
+ || MsgBuf.msg_size < UINT32_C(4096)
+ || MsgBuf.msg_size > 16*_1M
+ || MsgBuf.msg_bufx > MsgBuf.msg_size
+ || MsgBuf.msg_bufr > MsgBuf.msg_size
+ || !OSX_VALID_ADDRESS(pData->f64Bit, MsgBuf.msg_bufc) )
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid MsgBuf data: magic=%#x size=%#x bufx=%#x bufr=%#x bufc=%RGv\n",
+ MsgBuf.msg_magic, MsgBuf.msg_size, MsgBuf.msg_bufx, MsgBuf.msg_bufr, MsgBuf.msg_bufc));
+ return VERR_INVALID_STATE;
+ }
+
+ /*
+ * Read the buffer.
+ */
+ char *pchMsgBuf = (char *)RTMemAlloc(MsgBuf.msg_size);
+ if (!pchMsgBuf)
+ {
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Failed to allocate %#x bytes of memory for the log buffer\n",
+ MsgBuf.msg_size));
+ return VERR_INVALID_STATE;
+ }
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, MsgBuf.msg_bufc), pchMsgBuf, MsgBuf.msg_size);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy it out raw.
+ */
+ uint32_t offDst = 0;
+ if (MsgBuf.msg_bufr < MsgBuf.msg_bufx)
+ {
+ /* Single chunk between the read and write offsets. */
+ uint32_t cbToCopy = MsgBuf.msg_bufx - MsgBuf.msg_bufr;
+ if (cbToCopy < cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbToCopy);
+ pszBuf[cbToCopy] = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ if (cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ offDst = cbToCopy + 1;
+ }
+ else
+ {
+ /* Two chunks, read offset to end, start to write offset. */
+ uint32_t cbFirst = MsgBuf.msg_size - MsgBuf.msg_bufr;
+ uint32_t cbSecond = MsgBuf.msg_bufx;
+ if (cbFirst + cbSecond < cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbFirst);
+ memcpy(&pszBuf[cbFirst], pchMsgBuf, cbSecond);
+ offDst = cbFirst + cbSecond;
+ pszBuf[offDst++] = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ offDst = cbFirst + cbSecond + 1;
+ if (cbFirst < cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbFirst);
+ memcpy(&pszBuf[cbFirst], pchMsgBuf, cbBuf - cbFirst);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ else if (cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[MsgBuf.msg_bufr], cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+
+ if (pcbActual)
+ *pcbActual = offDst;
+ }
+ else
+ LogRel(("dbgDiggerDarwinIDmsg_QueryKernelLog: Error reading %#x bytes at %RGv: %Rrc\n",
+ MsgBuf.msg_size, MsgBuf.msg_bufc, rc));
+ RTMemFree(pchMsgBuf);
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnStackUnwindAssist
+ */
+static DECLCALLBACK(int) dbgDiggerDarwinStackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu,
+ PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx,
+ RTDBGAS hAs, uint64_t *puScratch)
+{
+ RT_NOREF(pUVM, pVMM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryInterface
+ */
+static DECLCALLBACK(void *) dbgDiggerDarwinQueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+ switch (enmIf)
+ {
+ case DBGFOSINTERFACE_DMESG:
+ return &pThis->IDmesg;
+
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryVersion
+ */
+static DECLCALLBACK(int) dbgDiggerDarwinQueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData,
+ char *pszVersion, size_t cchVersion)
+{
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+ Assert(pThis->fValid);
+
+ /*
+ * It's all in the linux banner.
+ */
+ int rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &pThis->AddrKernelVersion, pszVersion, cchVersion);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszEnd = RTStrEnd(pszVersion, cchVersion);
+ AssertReturn(pszEnd, VERR_BUFFER_OVERFLOW);
+ while ( pszEnd > pszVersion
+ && RT_C_IS_SPACE(pszEnd[-1]))
+ pszEnd--;
+ *pszEnd = '\0';
+ }
+ else
+ RTStrPrintf(pszVersion, cchVersion, "DBGFR3MemRead -> %Rrc", rc);
+
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnTerm
+ */
+static DECLCALLBACK(void) dbgDiggerDarwinTerm(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+
+ pThis->fValid = false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnRefresh
+ */
+static DECLCALLBACK(int) dbgDiggerDarwinRefresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+ NOREF(pThis);
+ Assert(pThis->fValid);
+
+ /*
+ * For now we'll flush and reload everything.
+ */
+ dbgDiggerDarwinTerm(pUVM, pVMM, pvData);
+ return dbgDiggerDarwinInit(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * Helper function that tries to accertain whether a segment (__LINKEDIT) is
+ * present or not.
+ *
+ * @returns true if present, false if not.
+ * @param pUVM The user mode VM structure.
+ * @param pVMM The VMM function table.
+ * @param uSegAddr The segment addresss.
+ * @param cbSeg The segment size.
+ * @param uMinAddr Lowest allowed address.
+ * @param uMaxAddr Highest allowed address.
+ */
+static bool dbgDiggerDarwinIsSegmentPresent(PUVM pUVM, PCVMMR3VTABLE pVMM, uint64_t uSegAddr, uint64_t cbSeg,
+ uint64_t uMinAddr, uint64_t uMaxAddr)
+{
+ /*
+ * Validate the size and address.
+ */
+ if (cbSeg < 32)
+ {
+ LogRel(("OSXDig: __LINKEDIT too small %#RX64\n", cbSeg));
+ return false;
+ }
+ if (cbSeg > uMaxAddr - uMinAddr)
+ {
+ LogRel(("OSXDig: __LINKEDIT too big %#RX64, max %#RX64\n", cbSeg, uMaxAddr - uMinAddr));
+ return false;
+ }
+
+ if (uSegAddr < uMinAddr)
+ {
+ LogRel(("OSXDig: __LINKEDIT too low %#RX64, min %#RX64\n", uSegAddr, uMinAddr));
+ return false;
+ }
+ if (uSegAddr > uMaxAddr)
+ {
+ LogRel(("OSXDig: __LINKEDIT too high %#RX64, max %#RX64\n", uSegAddr, uMaxAddr));
+ return false;
+ }
+ if (uSegAddr + cbSeg > uMaxAddr)
+ {
+ LogRel(("OSXDig: __LINKEDIT ends too high %#RX64 (%#RX64+%#RX64), max %#RX64\n",
+ uSegAddr + cbSeg, uSegAddr, cbSeg, uMaxAddr));
+ return false;
+ }
+
+ /*
+ * Check that all the pages are present.
+ */
+ cbSeg += uSegAddr & X86_PAGE_OFFSET_MASK;
+ uSegAddr &= ~(uint64_t)X86_PAGE_OFFSET_MASK;
+ for (;;)
+ {
+ uint8_t abBuf[8];
+ DBGFADDRESS Addr;
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uSegAddr),
+ abBuf, sizeof(abBuf));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("OSXDig: __LINKEDIT read error at %#RX64: %Rrc\n", uSegAddr, rc));
+ return false;
+ }
+
+ /* Advance */
+ if (cbSeg <= X86_PAGE_SIZE)
+ return true;
+ cbSeg -= X86_PAGE_SIZE;
+ uSegAddr += X86_PAGE_SIZE;
+ }
+}
+
+
+/**
+ * Helper function that validates a segment (or section) name.
+ *
+ * @returns true if valid, false if not.
+ * @param pszName The name string.
+ * @param cbName The size of the string, including terminator.
+ */
+static bool dbgDiggerDarwinIsValidSegOrSectName(const char *pszName, size_t cbName)
+{
+ /* ascii chars */
+ char ch;
+ size_t off = 0;
+ while (off < cbName && (ch = pszName[off]))
+ {
+ if (RT_C_IS_CNTRL(ch) || ch >= 127)
+ return false;
+ off++;
+ }
+
+ /* Not empty nor 100% full. */
+ if (off == 0 || off == cbName)
+ return false;
+
+ /* remainder should be zeros. */
+ while (off < cbName)
+ {
+ if (pszName[off])
+ return false;
+ off++;
+ }
+
+ return true;
+}
+
+
+static int dbgDiggerDarwinAddModule(PDBGDIGGERDARWIN pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ uint64_t uModAddr, const char *pszName, bool *pf64Bit)
+{
+ RT_NOREF1(pThis);
+ union
+ {
+ uint8_t ab[2 * X86_PAGE_4K_SIZE];
+ mach_header_64_t Hdr64;
+ mach_header_32_t Hdr32;
+ } uBuf;
+
+ /* Read the first page of the image. */
+ DBGFADDRESS ModAddr;
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ModAddr, uModAddr), uBuf.ab, X86_PAGE_4K_SIZE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Validate the header. */
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, magic, mach_header_32_t, magic);
+ if ( uBuf.Hdr64.magic != IMAGE_MACHO64_SIGNATURE
+ && uBuf.Hdr32.magic != IMAGE_MACHO32_SIGNATURE)
+ return VERR_INVALID_EXE_SIGNATURE;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, cputype, mach_header_32_t, cputype);
+ bool f64Bit = uBuf.Hdr64.magic == IMAGE_MACHO64_SIGNATURE;
+ if (uBuf.Hdr32.cputype != (f64Bit ? CPU_TYPE_X86_64 : CPU_TYPE_I386))
+ return VERR_LDR_ARCH_MISMATCH;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, filetype, mach_header_32_t, filetype);
+ if ( uBuf.Hdr32.filetype != MH_EXECUTE
+ && uBuf.Hdr32.filetype != (f64Bit ? MH_KEXT_BUNDLE : MH_OBJECT))
+ return VERR_BAD_EXE_FORMAT;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, ncmds, mach_header_32_t, ncmds);
+ if (uBuf.Hdr32.ncmds > 256)
+ return VERR_BAD_EXE_FORMAT;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, sizeofcmds, mach_header_32_t, sizeofcmds);
+ if (uBuf.Hdr32.sizeofcmds > X86_PAGE_4K_SIZE * 2 - sizeof(mach_header_64_t))
+ return VERR_BAD_EXE_FORMAT;
+
+ /* Do we need to read a 2nd page to get all the load commands? If so, do it. */
+ if (uBuf.Hdr32.sizeofcmds + (f64Bit ? sizeof(mach_header_64_t) : sizeof(mach_header_32_t)) > X86_PAGE_4K_SIZE)
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ModAddr, uModAddr + X86_PAGE_4K_SIZE),
+ &uBuf.ab[X86_PAGE_4K_SIZE], X86_PAGE_4K_SIZE);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Process the load commands.
+ */
+ RTUUID Uuid = RTUUID_INITIALIZE_NULL;
+ RTDBGSEGMENT aSegs[24];
+ uint32_t cSegs = 0;
+ bool fHasLinkEdit = false;
+ uint32_t cLeft = uBuf.Hdr32.ncmds;
+ uint32_t cbLeft = uBuf.Hdr32.sizeofcmds;
+ union
+ {
+ uint8_t const *pb;
+ load_command_t const *pGenric;
+ segment_command_32_t const *pSeg32;
+ segment_command_64_t const *pSeg64;
+ uuid_command_t const *pUuid;
+ } uLCmd;
+ uLCmd.pb = &uBuf.ab[f64Bit ? sizeof(mach_header_64_t) : sizeof(mach_header_32_t)];
+
+ while (cLeft-- > 0)
+ {
+ uint32_t const cbCmd = uLCmd.pGenric->cmdsize;
+ if (cbCmd > cbLeft || cbCmd < sizeof(load_command_t))
+ return VERR_BAD_EXE_FORMAT;
+
+ switch (uLCmd.pGenric->cmd)
+ {
+ case LC_SEGMENT_32:
+ if (cbCmd != sizeof(segment_command_32_t) + uLCmd.pSeg32->nsects * sizeof(section_32_t))
+ return VERR_BAD_EXE_FORMAT;
+ if (!dbgDiggerDarwinIsValidSegOrSectName(uLCmd.pSeg32->segname, sizeof(uLCmd.pSeg32->segname)))
+ return VERR_INVALID_NAME;
+ if ( !strcmp(uLCmd.pSeg32->segname, "__LINKEDIT")
+ && !(fHasLinkEdit = dbgDiggerDarwinIsSegmentPresent(pUVM, pVMM, uLCmd.pSeg32->vmaddr, uLCmd.pSeg32->vmsize,
+ uModAddr, uModAddr + _64M)))
+ break; /* This usually is discarded or not loaded at all. */
+ if (cSegs >= RT_ELEMENTS(aSegs))
+ return VERR_BUFFER_OVERFLOW;
+ aSegs[cSegs].Address = uLCmd.pSeg32->vmaddr;
+ aSegs[cSegs].uRva = uLCmd.pSeg32->vmaddr - uModAddr;
+ aSegs[cSegs].cb = uLCmd.pSeg32->vmsize;
+ aSegs[cSegs].fFlags = uLCmd.pSeg32->flags; /* Abusing the flags field here... */
+ aSegs[cSegs].iSeg = cSegs;
+ AssertCompile(RTDBG_SEGMENT_NAME_LENGTH > sizeof(uLCmd.pSeg32->segname));
+ strcpy(aSegs[cSegs].szName, uLCmd.pSeg32->segname);
+ cSegs++;
+ break;
+
+ case LC_SEGMENT_64:
+ if (cbCmd != sizeof(segment_command_64_t) + uLCmd.pSeg64->nsects * sizeof(section_64_t))
+ return VERR_BAD_EXE_FORMAT;
+ if (!dbgDiggerDarwinIsValidSegOrSectName(uLCmd.pSeg64->segname, sizeof(uLCmd.pSeg64->segname)))
+ return VERR_INVALID_NAME;
+ if ( !strcmp(uLCmd.pSeg64->segname, "__LINKEDIT")
+ && !(fHasLinkEdit = dbgDiggerDarwinIsSegmentPresent(pUVM, pVMM, uLCmd.pSeg64->vmaddr, uLCmd.pSeg64->vmsize,
+ uModAddr, uModAddr + _128M)))
+ break; /* This usually is discarded or not loaded at all. */
+ if (cSegs >= RT_ELEMENTS(aSegs))
+ return VERR_BUFFER_OVERFLOW;
+ aSegs[cSegs].Address = uLCmd.pSeg64->vmaddr;
+ aSegs[cSegs].uRva = uLCmd.pSeg64->vmaddr - uModAddr;
+ aSegs[cSegs].cb = uLCmd.pSeg64->vmsize;
+ aSegs[cSegs].fFlags = uLCmd.pSeg64->flags; /* Abusing the flags field here... */
+ aSegs[cSegs].iSeg = cSegs;
+ AssertCompile(RTDBG_SEGMENT_NAME_LENGTH > sizeof(uLCmd.pSeg64->segname));
+ strcpy(aSegs[cSegs].szName, uLCmd.pSeg64->segname);
+ cSegs++;
+ break;
+
+ case LC_UUID:
+ if (cbCmd != sizeof(uuid_command_t))
+ return VERR_BAD_EXE_FORMAT;
+ if (RTUuidIsNull((PCRTUUID)&uLCmd.pUuid->uuid[0]))
+ return VERR_BAD_EXE_FORMAT;
+ memcpy(&Uuid, &uLCmd.pUuid->uuid[0], sizeof(uLCmd.pUuid->uuid));
+ break;
+
+ default:
+ /* Current known max plus a lot of slack. */
+ if (uLCmd.pGenric->cmd > LC_DYLIB_CODE_SIGN_DRS + 32)
+ return VERR_BAD_EXE_FORMAT;
+ break;
+ }
+
+ /* next */
+ cbLeft -= cbCmd;
+ uLCmd.pb += cbCmd;
+ }
+
+ if (cbLeft != 0)
+ {
+ LogRel(("OSXDig: uModAddr=%#RX64 - %u bytes of command left over!\n", uModAddr, cbLeft));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ /*
+ * Some post processing checks.
+ */
+ uint32_t iSeg;
+ for (iSeg = 0; iSeg < cSegs; iSeg++)
+ if (aSegs[iSeg].Address == uModAddr)
+ break;
+ if (iSeg >= cSegs)
+ {
+ LogRel2(("OSXDig: uModAddr=%#RX64 was not found among the segments segments\n", uModAddr));
+ return VERR_ADDRESS_CONFLICT;
+ }
+
+ /*
+ * Create a debug module.
+ */
+ RTDBGMOD hMod;
+ rc = RTDbgModCreateFromMachOImage(&hMod, pszName, NULL, f64Bit ? RTLDRARCH_AMD64 : RTLDRARCH_X86_32, NULL /*phLdrModIn*/,
+ 0 /*cbImage*/, cSegs, aSegs, &Uuid, pVMM->pfnDBGFR3AsGetConfig(pUVM),
+ RTDBGMOD_F_NOT_DEFERRED | (fHasLinkEdit ? RTDBGMOD_F_MACHO_LOAD_LINKEDIT : 0));
+
+
+ /*
+ * If module creation failed and we've got a linkedit segment, try open the
+ * image in-memory, because that will at a minimum give us symbol table symbols.
+ */
+ if (RT_FAILURE(rc) && fHasLinkEdit)
+ {
+ DBGFADDRESS DbgfAddr;
+ RTERRINFOSTATIC ErrInfo;
+ rc = pVMM->pfnDBGFR3ModInMem(pUVM, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &DbgfAddr, uModAddr),
+ DBGFMODINMEM_F_NO_CONTAINER_FALLBACK,
+ pszName, NULL /*pszFilename*/, f64Bit ? RTLDRARCH_AMD64 : RTLDRARCH_X86_32, 0 /*cbImage */,
+ &hMod, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ LogRel(("OSXDig: Failed to do an in-memory-opening of '%s' at %#RX64: %Rrc%s%s\n", pszName, uModAddr, rc,
+ RTErrInfoIsSet(&ErrInfo.Core) ? " - " : "", RTErrInfoIsSet(&ErrInfo.Core) ? ErrInfo.Core.pszMsg : ""));
+ }
+
+ /*
+ * Final fallback is a container module.
+ */
+ if (RT_FAILURE(rc))
+ {
+ rc = RTDbgModCreate(&hMod, pszName, 0, 0);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t uRvaNext = 0;
+ for (iSeg = 0; iSeg < cSegs && RT_SUCCESS(rc); iSeg++)
+ {
+ if ( aSegs[iSeg].uRva > uRvaNext
+ && aSegs[iSeg].uRva - uRvaNext < _1M)
+ uRvaNext = aSegs[iSeg].uRva;
+ rc = RTDbgModSegmentAdd(hMod, aSegs[iSeg].uRva, aSegs[iSeg].cb, aSegs[iSeg].szName, 0, NULL);
+ if (aSegs[iSeg].cb > 0 && RT_SUCCESS(rc))
+ {
+ char szTmp[RTDBG_SEGMENT_NAME_LENGTH + sizeof("_start")];
+ strcat(strcpy(szTmp, aSegs[iSeg].szName), "_start");
+ rc = RTDbgModSymbolAdd(hMod, szTmp, iSeg, 0 /*uRva*/, 0 /*cb*/, 0 /*fFlags*/, NULL);
+ }
+ uRvaNext += aSegs[iSeg].cb;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTDbgModRelease(hMod);
+ return rc;
+ }
+ }
+
+ /* Tag the module. */
+ rc = RTDbgModSetTag(hMod, DIG_DARWIN_MOD_TAG);
+ AssertRC(rc);
+
+ /*
+ * Link the module.
+ */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hAs != NIL_RTDBGAS)
+ {
+ //uint64_t uRvaNext = 0; - what was this?
+ uint32_t cLinked = 0;
+ iSeg = cSegs;
+ while (iSeg-- > 0) /* HACK: Map in reverse order to avoid replacing __TEXT. */
+ if (aSegs[iSeg].cb)
+ {
+ /* Find matching segment in the debug module. */
+ uint32_t iDbgSeg = 0;
+ while (iDbgSeg < cSegs)
+ {
+ RTDBGSEGMENT SegInfo;
+ int rc3 = RTDbgModSegmentByIndex(hMod, iDbgSeg, &SegInfo);
+ if (RT_SUCCESS(rc3) && !strcmp(SegInfo.szName, aSegs[iSeg].szName))
+ break;
+ iDbgSeg++;
+ }
+ AssertMsgStmt(iDbgSeg < cSegs, ("%s\n", aSegs[iSeg].szName), continue);
+
+ /* Map it. */
+ int rc2 = RTDbgAsModuleLinkSeg(hAs, hMod, iDbgSeg, aSegs[iSeg].Address, RTDBGASLINK_FLAGS_REPLACE /*fFlags*/);
+ if (RT_SUCCESS(rc2))
+ cLinked++;
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ if (RT_FAILURE(rc) && cLinked != 0)
+ rc = -rc;
+ }
+ else
+ rc = VERR_INTERNAL_ERROR;
+
+ RTDbgModRelease(hMod);
+ RTDbgAsRelease(hAs);
+
+ if (pf64Bit)
+ *pf64Bit = f64Bit;
+ return rc;
+}
+
+
+static bool dbgDiggerDarwinIsValidName(const char *pszName)
+{
+ char ch;
+ while ((ch = *pszName++) != '\0')
+ {
+ if (ch < 0x20 || ch >= 127)
+ return false;
+ }
+ return true;
+}
+
+
+static bool dbgDiggerDarwinIsValidVersion(const char *pszVersion)
+{
+ char ch;
+ while ((ch = *pszVersion++) != '\0')
+ {
+ if (ch < 0x20 || ch >= 127)
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnInit
+ */
+static DECLCALLBACK(int) dbgDiggerDarwinInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+ Assert(!pThis->fValid);
+
+ /*
+ * Add the kernel module.
+ */
+ bool f64Bit;
+ int rc = dbgDiggerDarwinAddModule(pThis, pUVM, pVMM, pThis->AddrKernel.FlatPtr, "mach_kernel", &f64Bit);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The list of modules can be found at the 'kmod' symbol, that means
+ * that we currently require some kind of symbol file for the kernel
+ * to be loaded at this point.
+ *
+ * Note! Could also use the 'gLoadedKextSummaries', but I don't think
+ * it's any easier to find without any kernel map than 'kmod'.
+ */
+ RTDBGSYMBOL SymInfo;
+ rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "mach_kernel!kmod", &SymInfo, NULL);
+ if (RT_FAILURE(rc))
+ rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "mach_kernel!_kmod", &SymInfo, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS AddrModInfo;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrModInfo, SymInfo.Value);
+
+ /* Read the variable. */
+ RTUINT64U uKmodValue = { 0 };
+ if (f64Bit)
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrModInfo, &uKmodValue.u, sizeof(uKmodValue.u));
+ else
+ rc = pVMM->pfnDBGFR3MemRead (pUVM, 0 /*idCpu*/, &AddrModInfo, &uKmodValue.s.Lo, sizeof(uKmodValue.s.Lo));
+ if (RT_SUCCESS(rc))
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrModInfo, uKmodValue.u);
+
+ /* Walk the list of modules. */
+ uint32_t cIterations = 0;
+ while (AddrModInfo.FlatPtr != 0)
+ {
+ /* Some extra loop conditions... */
+ if (!OSX_VALID_ADDRESS(f64Bit, AddrModInfo.FlatPtr))
+ {
+ LogRel(("OSXDig: Invalid kmod_info pointer: %RGv\n", AddrModInfo.FlatPtr));
+ break;
+ }
+ if (AddrModInfo.FlatPtr == uKmodValue.u && cIterations != 0)
+ {
+ LogRel(("OSXDig: kmod_info list looped back to the start.\n"));
+ break;
+ }
+ if (cIterations++ >= 2048)
+ {
+ LogRel(("OSXDig: Too many mod_info loops (%u)\n", cIterations));
+ break;
+ }
+
+ /*
+ * Read the kmod_info_t structure.
+ */
+ union
+ {
+ OSX64_kmod_info_t Info64;
+ OSX32_kmod_info_t Info32;
+ } uMod;
+ RT_ZERO(uMod);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrModInfo, &uMod,
+ f64Bit ? sizeof(uMod.Info64) : sizeof(uMod.Info32));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("OSXDig: Error reading kmod_info structure at %RGv: %Rrc\n", AddrModInfo.FlatPtr, rc));
+ break;
+ }
+
+ /*
+ * Validate the kmod_info_t structure.
+ */
+ int32_t iInfoVer = f64Bit ? uMod.Info64.info_version : uMod.Info32.info_version;
+ if (iInfoVer != OSX_KMOD_INFO_VERSION)
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad info_version %d\n", AddrModInfo.FlatPtr, iInfoVer));
+ break;
+ }
+
+ const char *pszName = f64Bit ? uMod.Info64.name : uMod.Info32.name;
+ if ( !*pszName
+ || !RTStrEnd(pszName, sizeof(uMod.Info64.name))
+ || !dbgDiggerDarwinIsValidName(pszName) )
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad name '%.*s'\n", AddrModInfo.FlatPtr,
+ sizeof(uMod.Info64.name), pszName));
+ break;
+ }
+
+ const char *pszVersion = f64Bit ? uMod.Info64.version : uMod.Info32.version;
+ if ( !RTStrEnd(pszVersion, sizeof(uMod.Info64.version))
+ || !dbgDiggerDarwinIsValidVersion(pszVersion) )
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad version '%.*s'\n", AddrModInfo.FlatPtr,
+ sizeof(uMod.Info64.version), pszVersion));
+ break;
+ }
+
+ int32_t cRefs = f64Bit ? uMod.Info64.reference_count : uMod.Info32.reference_count;
+ if (cRefs < -1 || cRefs > 16384)
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad reference_count %d\n", AddrModInfo.FlatPtr, cRefs));
+ break;
+ }
+
+ uint64_t uImageAddr = f64Bit ? uMod.Info64.address : uMod.Info32.address;
+ if (!OSX_VALID_ADDRESS(f64Bit, uImageAddr))
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad address %#llx\n", AddrModInfo.FlatPtr, uImageAddr));
+ break;
+ }
+
+ uint64_t cbImage = f64Bit ? uMod.Info64.size : uMod.Info32.size;
+ if (cbImage > 64U*_1M)
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad size %#llx\n", AddrModInfo.FlatPtr, cbImage));
+ break;
+ }
+
+ uint64_t cbHdr = f64Bit ? uMod.Info64.hdr_size : uMod.Info32.hdr_size;
+ if (cbHdr > 16U*_1M)
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad hdr_size %#llx\n", AddrModInfo.FlatPtr, cbHdr));
+ break;
+ }
+
+ uint64_t uStartAddr = f64Bit ? uMod.Info64.start : uMod.Info32.start;
+ if (!uStartAddr && !OSX_VALID_ADDRESS(f64Bit, uStartAddr))
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad start function %#llx\n", AddrModInfo.FlatPtr, uStartAddr));
+ break;
+ }
+
+ uint64_t uStopAddr = f64Bit ? uMod.Info64.stop : uMod.Info32.stop;
+ if (!uStopAddr && !OSX_VALID_ADDRESS(f64Bit, uStopAddr))
+ {
+ LogRel(("OSXDig: kmod_info @%RGv: Bad stop function %#llx\n", AddrModInfo.FlatPtr, uStopAddr));
+ break;
+ }
+
+ /*
+ * Try add the module.
+ */
+ LogRel(("OSXDig: kmod_info @%RGv: '%s' ver '%s', image @%#llx LB %#llx cbHdr=%#llx\n", AddrModInfo.FlatPtr,
+ pszName, pszVersion, uImageAddr, cbImage, cbHdr));
+ rc = dbgDiggerDarwinAddModule(pThis, pUVM, pVMM, uImageAddr, pszName, NULL);
+
+
+ /*
+ * Advance to the next kmod_info entry.
+ */
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrModInfo, f64Bit ? uMod.Info64.next : uMod.Info32.next);
+ }
+ }
+ else
+ LogRel(("OSXDig: Error reading the 'kmod' variable: %Rrc\n", rc));
+ }
+ else
+ LogRel(("OSXDig: Failed to locate the 'kmod' variable in mach_kernel.\n"));
+
+ pThis->fValid = true;
+ return VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnProbe
+ */
+static DECLCALLBACK(bool) dbgDiggerDarwinProbe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+
+ /*
+ * Look for a section + segment combo that normally only occures in
+ * mach_kernel. Follow it up with probing of the rest of the executable
+ * header. We must search a largish area because the more recent versions
+ * of darwin have random load address for security raisins.
+ */
+ static struct { uint64_t uStart, uEnd; } const s_aRanges[] =
+ {
+ /* 64-bit: */
+ { UINT64_C(0xffffff8000000000), UINT64_C(0xffffff81ffffffff), },
+
+ /* 32-bit - always search for this because of the hybrid 32-bit kernel
+ with cpu in long mode that darwin used for a number of versions. */
+ { UINT64_C(0x00001000), UINT64_C(0x0ffff000), }
+ };
+ for (unsigned iRange = pVMM->pfnDBGFR3CpuGetMode(pUVM, 0 /*idCpu*/) != CPUMMODE_LONG;
+ iRange < RT_ELEMENTS(s_aRanges);
+ iRange++)
+ {
+ DBGFADDRESS KernelAddr;
+ for (pVMM->pfnDBGFR3AddrFromFlat(pUVM, &KernelAddr, s_aRanges[iRange].uStart);
+ KernelAddr.FlatPtr < s_aRanges[iRange].uEnd;
+ KernelAddr.FlatPtr += X86_PAGE_4K_SIZE)
+ {
+ static const uint8_t s_abNeedle[16 + 16] =
+ {
+ '_','_','t','e','x','t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* section_32_t::sectname */
+ '_','_','K','L','D', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* section_32_t::segname. */
+ };
+
+ int rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, s_aRanges[iRange].uEnd - KernelAddr.FlatPtr,
+ 1, s_abNeedle, sizeof(s_abNeedle), &KernelAddr);
+ if (RT_FAILURE(rc))
+ break;
+ pVMM->pfnDBGFR3AddrSub(&KernelAddr, KernelAddr.FlatPtr & X86_PAGE_4K_OFFSET_MASK);
+
+ /*
+ * Read the first page of the image and check the headers.
+ */
+ union
+ {
+ uint8_t ab[X86_PAGE_4K_SIZE];
+ mach_header_64_t Hdr64;
+ mach_header_32_t Hdr32;
+ } uBuf;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &KernelAddr, uBuf.ab, X86_PAGE_4K_SIZE);
+ if (RT_FAILURE(rc))
+ continue;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, magic, mach_header_32_t, magic);
+ if ( uBuf.Hdr64.magic != IMAGE_MACHO64_SIGNATURE
+ && uBuf.Hdr32.magic != IMAGE_MACHO32_SIGNATURE)
+ continue;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, cputype, mach_header_32_t, cputype);
+ bool f64Bit = uBuf.Hdr64.magic == IMAGE_MACHO64_SIGNATURE;
+ if (uBuf.Hdr32.cputype != (f64Bit ? CPU_TYPE_X86_64 : CPU_TYPE_I386))
+ continue;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, filetype, mach_header_32_t, filetype);
+ if (uBuf.Hdr32.filetype != MH_EXECUTE)
+ continue;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, ncmds, mach_header_32_t, ncmds);
+ if (uBuf.Hdr32.ncmds > 256)
+ continue;
+ AssertCompileMembersSameSizeAndOffset(mach_header_64_t, sizeofcmds, mach_header_32_t, sizeofcmds);
+ if (uBuf.Hdr32.sizeofcmds > X86_PAGE_4K_SIZE * 2 - sizeof(mach_header_64_t))
+ continue;
+
+ /* Seems good enough for now.
+
+ If the above causes false positives, check the segments and make
+ sure there is a kernel version string in the right one. */
+ pThis->AddrKernel = KernelAddr;
+ pThis->f64Bit = f64Bit;
+
+ /*
+ * Finally, find the kernel version string.
+ */
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, 32*_1M, 1, RT_STR_TUPLE("Darwin Kernel Version"),
+ &pThis->AddrKernelVersion);
+ if (RT_FAILURE(rc))
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelVersion, 0);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnDestruct
+ */
+static DECLCALLBACK(void) dbgDiggerDarwinDestruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnConstruct
+ */
+static DECLCALLBACK(int) dbgDiggerDarwinConstruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData;
+
+ pThis->IDmesg.u32Magic = DBGFOSIDMESG_MAGIC;
+ pThis->IDmesg.pfnQueryKernelLog = dbgDiggerDarwinIDmsg_QueryKernelLog;
+ pThis->IDmesg.u32EndMagic = DBGFOSIDMESG_MAGIC;
+
+ return VINF_SUCCESS;
+}
+
+
+const DBGFOSREG g_DBGDiggerDarwin =
+{
+ /* .u32Magic = */ DBGFOSREG_MAGIC,
+ /* .fFlags = */ 0,
+ /* .cbData = */ sizeof(DBGDIGGERDARWIN),
+ /* .szName = */ "Darwin",
+ /* .pfnConstruct = */ dbgDiggerDarwinConstruct,
+ /* .pfnDestruct = */ dbgDiggerDarwinDestruct,
+ /* .pfnProbe = */ dbgDiggerDarwinProbe,
+ /* .pfnInit = */ dbgDiggerDarwinInit,
+ /* .pfnRefresh = */ dbgDiggerDarwinRefresh,
+ /* .pfnTerm = */ dbgDiggerDarwinTerm,
+ /* .pfnQueryVersion = */ dbgDiggerDarwinQueryVersion,
+ /* .pfnQueryInterface = */ dbgDiggerDarwinQueryInterface,
+ /* .pfnStackUnwindAssist = */ dbgDiggerDarwinStackUnwindAssist,
+ /* .u32EndMagic = */ DBGFOSREG_MAGIC
+};
+
diff --git a/src/VBox/Debugger/DBGPlugInDiggers.cpp b/src/VBox/Debugger/DBGPlugInDiggers.cpp
new file mode 100644
index 00000000..b27c6927
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInDiggers.cpp
@@ -0,0 +1,87 @@
+/* $Id: DBGPlugInDiggers.cpp $ */
+/** @file
+ * DbfPlugInDiggers - Debugger and Guest OS Digger Plug-in.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGC
+#include <VBox/dbg.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include "DBGPlugIns.h"
+#include <VBox/version.h>
+#include <iprt/errcore.h>
+
+
+DECLEXPORT(int) DbgPlugInEntry(DBGFPLUGINOP enmOperation, PUVM pUVM, PCVMMR3VTABLE pVMM, uintptr_t uArg)
+{
+ static PCDBGFOSREG s_aPlugIns[] =
+ {
+ &g_DBGDiggerDarwin,
+ &g_DBGDiggerFreeBsd,
+ &g_DBGDiggerLinux,
+ &g_DBGDiggerOS2,
+ &g_DBGDiggerSolaris,
+ &g_DBGDiggerWinNt
+ };
+
+ switch (enmOperation)
+ {
+ case DBGFPLUGINOP_INIT:
+ {
+ if (uArg != VBOX_VERSION)
+ return VERR_VERSION_MISMATCH;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aPlugIns); i++)
+ {
+ int rc = pVMM->pfnDBGFR3OSRegister(pUVM, s_aPlugIns[i]);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ while (i-- > 0)
+ pVMM->pfnDBGFR3OSDeregister(pUVM, s_aPlugIns[i]);
+ return rc;
+ }
+ }
+ return VINF_SUCCESS;
+ }
+
+ case DBGFPLUGINOP_TERM:
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aPlugIns); i++)
+ {
+ int rc = pVMM->pfnDBGFR3OSDeregister(pUVM, s_aPlugIns[i]);
+ AssertRC(rc);
+ }
+ return VINF_SUCCESS;
+ }
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+}
+
diff --git a/src/VBox/Debugger/DBGPlugInFreeBsd.cpp b/src/VBox/Debugger/DBGPlugInFreeBsd.cpp
new file mode 100644
index 00000000..6d8a04ee
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInFreeBsd.cpp
@@ -0,0 +1,961 @@
+/* $Id: DBGPlugInFreeBsd.cpp $ */
+/** @file
+ * DBGPlugInFreeBsd - Debugger and Guest OS Digger Plugin For FreeBSD.
+ */
+
+/*
+ * Copyright (C) 2016-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugIns.h"
+#include "DBGPlugInCommonELF.h"
+#include <VBox/vmm/vmmr3vtable.h>
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** FreeBSD on little endian ASCII systems. */
+#define DIG_FBSD_MOD_TAG UINT64_C(0x0044534265657246)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * FreeBSD .dynstr and .dynsym location probing state.
+ */
+typedef enum FBSDPROBESTATE
+{
+ /** Invalid state. */
+ FBSDPROBESTATE_INVALID = 0,
+ /** Searching for the end of the .dynstr section (terminator). */
+ FBSDPROBESTATE_DYNSTR_END,
+ /** Last symbol was a symbol terminator character. */
+ FBSDPROBESTATE_DYNSTR_SYM_TERMINATOR,
+ /** Last symbol was a symbol character. */
+ FBSDPROBESTATE_DYNSTR_SYM_CHAR
+} FBSDPROBESTATE;
+
+/**
+ * ELF headers union.
+ */
+typedef union ELFEHDRS
+{
+ /** 32bit version of the ELF header. */
+ Elf32_Ehdr Hdr32;
+ /** 64bit version of the ELF header. */
+ Elf64_Ehdr Hdr64;
+} ELFEHDRS;
+/** Pointer to a ELF header union. */
+typedef ELFEHDRS *PELFEHDRS;
+/** Pointer to const ELF header union. */
+typedef ELFEHDRS const *PCELFEHDRS;
+
+/**
+ * ELF symbol entry union.
+ */
+typedef union ELFSYMS
+{
+ /** 32bit version of the ELF section header. */
+ Elf32_Sym Hdr32;
+ /** 64bit version of the ELF section header. */
+ Elf64_Sym Hdr64;
+} ELFSYMS;
+/** Pointer to a ELF symbol entry union. */
+typedef ELFSYMS *PELFSYMS;
+/** Pointer to const ELF symbol entry union. */
+typedef ELFSYMS const *PCELFSYMS;
+
+/**
+ * Message buffer structure.
+ */
+typedef union FBSDMSGBUF
+{
+ /** 32bit version. */
+ struct
+ {
+ /** Message buffer pointer. */
+ uint32_t msg_ptr;
+ /** Magic value to identify the structure. */
+ uint32_t msg_magic;
+ /** Size of the buffer area. */
+ uint32_t msg_size;
+ /** Write sequence number. */
+ uint32_t msg_wseq;
+ /** Read sequence number. */
+ uint32_t msg_rseq;
+ /** @todo More fields which are not required atm. */
+ } Hdr32;
+ /** 64bit version. */
+ struct
+ {
+ /** Message buffer pointer. */
+ uint64_t msg_ptr;
+ /** Magic value to identify the structure. */
+ uint32_t msg_magic;
+ /** Size of the buffer area. */
+ uint32_t msg_size;
+ /** Write sequence number. */
+ uint32_t msg_wseq;
+ /** Read sequence number. */
+ uint32_t msg_rseq;
+ /** @todo More fields which are not required atm. */
+ } Hdr64;
+} FBSDMSGBUF;
+/** Pointer to a message buffer structure. */
+typedef FBSDMSGBUF *PFBSDMSGBUF;
+/** Pointer to a const message buffer structure. */
+typedef FBSDMSGBUF const *PCFBSDMSGBUF;
+
+/** Magic value to identify the message buffer structure. */
+#define FBSD_MSGBUF_MAGIC UINT32_C(0x063062)
+
+/**
+ * FreeBSD guest OS digger instance data.
+ */
+typedef struct DBGDIGGERFBSD
+{
+ /** Whether the information is valid or not.
+ * (For fending off illegal interface method calls.) */
+ bool fValid;
+ /** 64-bit/32-bit indicator. */
+ bool f64Bit;
+
+ /** Address of the start of the kernel ELF image,
+ * set during probing. */
+ DBGFADDRESS AddrKernelElfStart;
+ /** Address of the interpreter content aka "/red/herring". */
+ DBGFADDRESS AddrKernelInterp;
+ /** Address of the start of the text section. */
+ DBGFADDRESS AddrKernelText;
+
+ /** The kernel message log interface. */
+ DBGFOSIDMESG IDmesg;
+
+} DBGDIGGERFBSD;
+/** Pointer to the FreeBSD guest OS digger instance data. */
+typedef DBGDIGGERFBSD *PDBGDIGGERFBSD;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Min kernel address (32bit). */
+#define FBSD32_MIN_KRNL_ADDR UINT32_C(0x80000000)
+/** Max kernel address (32bit). */
+#define FBSD32_MAX_KRNL_ADDR UINT32_C(0xfffff000)
+
+/** Min kernel address (64bit). */
+#define FBSD64_MIN_KRNL_ADDR UINT64_C(0xFFFFF80000000000)
+/** Max kernel address (64bit). */
+#define FBSD64_MAX_KRNL_ADDR UINT64_C(0xFFFFFFFFFFF00000)
+
+
+/** Validates a 32-bit FreeBSD kernel address */
+#define FBSD32_VALID_ADDRESS(Addr) ( (Addr) > FBSD32_MIN_KRNL_ADDR \
+ && (Addr) < FBSD32_MAX_KRNL_ADDR)
+/** Validates a 64-bit FreeBSD kernel address */
+#define FBSD64_VALID_ADDRESS(Addr) ( (Addr) > FBSD64_MIN_KRNL_ADDR \
+ && (Addr) < FBSD64_MAX_KRNL_ADDR)
+
+/** Validates a FreeBSD kernel address. */
+#define FBSD_VALID_ADDRESS(a_pThis, a_Addr) ((a_pThis)->f64Bit ? FBSD64_VALID_ADDRESS(a_Addr) : FBSD32_VALID_ADDRESS(a_Addr))
+
+/** Maximum offset from the start of the ELF image we look for the /red/herring .interp section content. */
+#define FBSD_MAX_INTERP_OFFSET _16K
+/** The max kernel size. */
+#define FBSD_MAX_KERNEL_SIZE UINT32_C(0x0f000000)
+
+/** Versioned and bitness wrapper. */
+#define FBSD_UNION(a_pThis, a_pUnion, a_Member) ((a_pThis)->f64Bit ? (a_pUnion)->Hdr64. a_Member : (a_pUnion)->Hdr32. a_Member )
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgDiggerFreeBsdInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Table of common FreeBSD kernel addresses. */
+static uint64_t g_au64FreeBsdKernelAddresses[] =
+{
+ UINT64_C(0xc0100000),
+ UINT64_C(0xffffffff80100000)
+};
+/** Magic string which resides in the .interp section of the image. */
+static const uint8_t g_abNeedleInterp[] = "/red/herring";
+
+
+/**
+ * Load the symbols from the .dynsym and .dynstr sections given
+ * by their address in guest memory.
+ *
+ * @returns VBox status code.
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pszName The image name.
+ * @param uKernelStart The kernel start address.
+ * @param cbKernel Size of the kernel image.
+ * @param pAddrDynsym Start address of the .dynsym section.
+ * @param cSymbols Number of symbols in the .dynsym section.
+ * @param pAddrDynstr Start address of the .dynstr section containing the symbol names.
+ * @param cbDynstr Size of the .dynstr section.
+ */
+static int dbgDiggerFreeBsdLoadSymbols(PDBGDIGGERFBSD pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszName,
+ RTGCUINTPTR uKernelStart, size_t cbKernel, PDBGFADDRESS pAddrDynsym, uint32_t cSymbols,
+ PDBGFADDRESS pAddrDynstr, size_t cbDynstr)
+{
+ LogFlowFunc(("pThis=%#p pszName=%s uKernelStart=%RGv cbKernel=%zu pAddrDynsym=%#p{%RGv} cSymbols=%u pAddrDynstr=%#p{%RGv} cbDynstr=%zu\n",
+ pThis, pszName, uKernelStart, cbKernel, pAddrDynsym, pAddrDynsym->FlatPtr, cSymbols, pAddrDynstr, pAddrDynstr->FlatPtr, cbDynstr));
+
+ char *pbDynstr = (char *)RTMemAllocZ(cbDynstr + 1); /* Extra terminator. */
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrDynstr, pbDynstr, cbDynstr);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbDynsymEnt = pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
+ uint8_t *pbDynsym = (uint8_t *)RTMemAllocZ(cSymbols * cbDynsymEnt);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrDynsym, pbDynsym, cSymbols * cbDynsymEnt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a module for the kernel.
+ */
+ RTDBGMOD hMod;
+ rc = RTDbgModCreate(&hMod, pszName, cbKernel, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDbgModSetTag(hMod, DIG_FBSD_MOD_TAG); AssertRC(rc);
+ rc = VINF_SUCCESS;
+
+ /*
+ * Enumerate the symbols.
+ */
+ uint32_t cLeft = cSymbols;
+ while (cLeft-- > 0 && RT_SUCCESS(rc))
+ {
+ PCELFSYMS pSym = (PCELFSYMS)&pbDynsym[cLeft * cbDynsymEnt];
+ uint32_t idxSymStr = FBSD_UNION(pThis, pSym, st_name);
+ uint8_t uType = FBSD_UNION(pThis, pSym, st_info);
+ RTGCUINTPTR AddrVal = FBSD_UNION(pThis, pSym, st_value);
+ size_t cbSymVal = FBSD_UNION(pThis, pSym, st_size);
+
+ /* Add it without the type char. */
+ RT_NOREF(uType);
+ if ( AddrVal <= uKernelStart + cbKernel
+ && idxSymStr < cbDynstr)
+ {
+ rc = RTDbgModSymbolAdd(hMod, &pbDynstr[idxSymStr], RTDBGSEGIDX_RVA, AddrVal - uKernelStart,
+ cbSymVal, 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ {
+ if ( rc == VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE
+ || rc == VERR_DBG_INVALID_RVA
+ || rc == VERR_DBG_ADDRESS_CONFLICT
+ || rc == VERR_DBG_DUPLICATE_SYMBOL)
+ {
+ Log2(("dbgDiggerFreeBsdLoadSymbols: RTDbgModSymbolAdd(,%s,) failed %Rrc (ignored)\n",
+ &pbDynstr[idxSymStr], rc));
+ rc = VINF_SUCCESS;
+ }
+ else
+ Log(("dbgDiggerFreeBsdLoadSymbols: RTDbgModSymbolAdd(,%s,) failed %Rrc\n",
+ &pbDynstr[idxSymStr], rc));
+ }
+ }
+ }
+
+ /*
+ * Link the module into the address space.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hAs != NIL_RTDBGAS)
+ rc = RTDbgAsModuleLink(hAs, hMod, uKernelStart, RTDBGASLINK_FLAGS_REPLACE);
+ else
+ rc = VERR_INTERNAL_ERROR;
+ RTDbgAsRelease(hAs);
+ }
+ else
+ Log(("dbgDiggerFreeBsdLoadSymbols: Failed: %Rrc\n", rc));
+ RTDbgModRelease(hMod);
+ }
+ else
+ Log(("dbgDiggerFreeBsdLoadSymbols: RTDbgModCreate failed: %Rrc\n", rc));
+ }
+ else
+ Log(("dbgDiggerFreeBsdLoadSymbols: Reading symbol table at %RGv failed: %Rrc\n",
+ pAddrDynsym->FlatPtr, rc));
+ RTMemFree(pbDynsym);
+ }
+ else
+ Log(("dbgDiggerFreeBsdLoadSymbols: Reading symbol string table at %RGv failed: %Rrc\n",
+ pAddrDynstr->FlatPtr, rc));
+ RTMemFree(pbDynstr);
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * Process the kernel image.
+ *
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pszName The image name.
+ */
+static void dbgDiggerFreeBsdProcessKernelImage(PDBGDIGGERFBSD pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszName)
+{
+ /*
+ * FreeBSD has parts of the kernel ELF image in guest memory, starting with the
+ * ELF header and the content of the sections which are indicated to be loaded
+ * into memory (text, rodata, etc.) of course. Whats missing are the section headers
+ * which is understandable but unfortunate because it would make our life easier.
+ *
+ * All checked FreeBSD kernels so far have the following layout in the kernel:
+ * [.interp] - contains the /red/herring string we used for probing earlier
+ * [.hash] - contains the hashes of the symbol names, 8 byte alignment on 64bit, 4 byte on 32bit
+ * [.gnu.hash] - GNU hash section. (introduced somewhere between 10.0 and 12.0 @todo Find out when exactly)
+ * [.dynsym] - contains the ELF symbol descriptors, 8 byte alignment, 4 byte on 32bit
+ * [.dynstr] - contains the symbol names as a string table, 1 byte alignmnt
+ * [.text] - contains the executable code, 16 byte alignment.
+ *
+ * To find the start of the .dynsym and .dynstr sections we scan backwards from the start of the .text section
+ * and check for all characters allowed for symbol names and count the amount of symbols found. When the start of the
+ * .dynstr section is reached the number of entries in .dynsym is known and we can deduce the start address.
+ *
+ * This applied to the old code before the FreeBSD kernel introduced the .gnu.hash section
+ * (keeping it here for informational pruposes):
+ * The sections are always adjacent (sans alignment) so we just parse the .hash section right after
+ * .interp, ELF states that it can contain 32bit or 64bit words but all observed kernels
+ * always use 32bit words. It contains two counters at the beginning which we can use to
+ * deduct the .hash section size and the beginning of .dynsym.
+ * .dynsym contains an array of symbol descriptors which have a fixed size depending on the
+ * guest bitness.
+ * Finding the end of .dynsym is not easily doable as there is no counter available (it lives
+ * in the section headers) at this point so we just have to check whether the record is valid
+ * and if not check if it contains an ASCII string which marks the start of the .dynstr section.
+ */
+
+#if 0
+ DBGFADDRESS AddrInterpEnd = pThis->AddrKernelInterp;
+ DBGFR3AddrAdd(&AddrInterpEnd, sizeof(g_abNeedleInterp));
+
+ DBGFADDRESS AddrCur = pThis->AddrKernelText;
+ int rc = VINF_SUCCESS;
+ uint32_t cSymbols = 0;
+ size_t cbKernel = 512 * _1M;
+ RTGCUINTPTR uKernelStart = pThis->AddrKernelElfStart.FlatPtr;
+ FBSDPROBESTATE enmState = FBSDPROBESTATE_DYNSTR_END; /* Start searching for the end of the .dynstr section. */
+
+ while (AddrCur.FlatPtr > AddrInterpEnd.FlatPtr)
+ {
+ char achBuf[_16K];
+ size_t cbToRead = RT_MIN(sizeof(achBuf), AddrCur.FlatPtr - AddrInterpEnd.FlatPtr);
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrSub(&AddrCur, cbToRead), &achBuf[0], cbToRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ for (unsigned i = cbToRead; i > 0; i--)
+ {
+ char ch = achBuf[i - 1];
+
+ switch (enmState)
+ {
+ case FBSDPROBESTATE_DYNSTR_END:
+ {
+ if (ch != '\0')
+ enmState = FBSDPROBESTATE_DYNSTR_SYM_CHAR;
+ break;
+ }
+ case FBSDPROBESTATE_DYNSTR_SYM_TERMINATOR:
+ {
+ if ( RT_C_IS_ALNUM(ch)
+ || ch == '_'
+ || ch == '.')
+ enmState = FBSDPROBESTATE_DYNSTR_SYM_CHAR;
+ else
+ {
+ /* Two consecutive terminator symbols mean end of .dynstr section. */
+ pVMM->pfnDBGFR3AddrAdd(&AddrCur, i);
+ DBGFADDRESS AddrDynstrStart = AddrCur;
+ DBGFADDRESS AddrDynsymStart = AddrCur;
+ pVMM->pfnDBGFR3AddrSub(&AddrDynsymStart, cSymbols * (pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf64_Sym)));
+ LogFlowFunc(("Found all required section start addresses (.dynsym=%RGv cSymbols=%u, .dynstr=%RGv cb=%u)\n",
+ AddrDynsymStart.FlatPtr, cSymbols, AddrDynstrStart.FlatPtr,
+ pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr));
+ dbgDiggerFreeBsdLoadSymbols(pThis, pUVM, pVMM, pszName, uKernelStart, cbKernel,
+ &AddrDynsymStart, cSymbols, &AddrDynstrStart,
+ pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr);
+ return;
+ }
+ break;
+ }
+ case FBSDPROBESTATE_DYNSTR_SYM_CHAR:
+ {
+ if ( !RT_C_IS_ALNUM(ch)
+ && ch != '_'
+ && ch != '.')
+ {
+ /* Non symbol character. */
+ if (ch == '\0')
+ {
+ enmState = FBSDPROBESTATE_DYNSTR_SYM_TERMINATOR;
+ cSymbols++;
+ }
+ else
+ {
+ /* Indicates the end of the .dynstr section. */
+ pVMM->pfnDBGFR3AddrAdd(&AddrCur, i);
+ DBGFADDRESS AddrDynstrStart = AddrCur;
+ DBGFADDRESS AddrDynsymStart = AddrCur;
+ pVMM->pfnDBGFR3AddrSub(&AddrDynsymStart, cSymbols * (pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym)));
+ LogFlowFunc(("Found all required section start addresses (.dynsym=%RGv cSymbols=%u, .dynstr=%RGv cb=%u)\n",
+ AddrDynsymStart.FlatPtr, cSymbols, AddrDynstrStart.FlatPtr,
+ pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr));
+ dbgDiggerFreeBsdLoadSymbols(pThis, pUVM, pVMM, pszName, uKernelStart, cbKernel,
+ &AddrDynsymStart, cSymbols, &AddrDynstrStart,
+ pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr);
+ return;
+ }
+ }
+ break;
+ }
+ default:
+ AssertFailedBreak();
+ }
+ }
+ }
+
+ LogFlow(("Failed to find valid .dynsym and .dynstr sections (%Rrc), can't load kernel symbols\n", rc));
+#else
+ /* Calculate the start of the .hash section. */
+ DBGFADDRESS AddrHashStart = pThis->AddrKernelInterp;
+ pVMM->pfnDBGFR3AddrAdd(&AddrHashStart, sizeof(g_abNeedleInterp));
+ AddrHashStart.FlatPtr = RT_ALIGN_GCPT(AddrHashStart.FlatPtr, pThis->f64Bit ? 8 : 4, RTGCUINTPTR);
+ uint32_t au32Counters[2];
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrHashStart, &au32Counters[0], sizeof(au32Counters));
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbHash = (au32Counters[0] + au32Counters[1] + 2) * sizeof(uint32_t);
+ if (AddrHashStart.FlatPtr + cbHash < pThis->AddrKernelText.FlatPtr) /* Should be much smaller */
+ {
+ DBGFADDRESS AddrDynsymStart = AddrHashStart;
+ uint32_t cSymbols = 0;
+ size_t cbKernel = 0;
+ RTGCUINTPTR uKernelStart = pThis->AddrKernelElfStart.FlatPtr;
+
+ pVMM->pfnDBGFR3AddrAdd(&AddrDynsymStart, cbHash);
+ AddrDynsymStart.FlatPtr = RT_ALIGN_GCPT(AddrDynsymStart.FlatPtr, pThis->f64Bit ? 8 : 4, RTGCUINTPTR);
+
+ DBGFADDRESS AddrDynstrStart = AddrDynsymStart;
+ while (AddrDynstrStart.FlatPtr < pThis->AddrKernelText.FlatPtr)
+ {
+ size_t cbDynSymEnt = pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym);
+ uint8_t abBuf[_16K];
+ size_t cbToRead = RT_MIN(sizeof(abBuf), pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr);
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrDynstrStart, &abBuf[0], cbToRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ for (unsigned i = 0; i < cbToRead / cbDynSymEnt; i++)
+ {
+ PCELFSYMS pSym = (PCELFSYMS)&abBuf[i * cbDynSymEnt];
+ uint32_t idxSymStr = FBSD_UNION(pThis, pSym, st_name);
+ uint8_t uType = FBSD_UNION(pThis, pSym, st_info);
+ RTGCUINTPTR AddrVal = FBSD_UNION(pThis, pSym, st_value);
+ size_t cbSymVal = FBSD_UNION(pThis, pSym, st_size);
+
+ /*
+ * If the entry doesn't look valid check whether it contains an ASCII string,
+ * we then found the start of the .dynstr section.
+ */
+ RT_NOREF(uType);
+ if ( ELF32_ST_TYPE(uType) != STT_NOTYPE
+ && ( !FBSD_VALID_ADDRESS(pThis, AddrVal)
+ || cbSymVal > FBSD_MAX_KERNEL_SIZE
+ || idxSymStr > pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr))
+ {
+ LogFlowFunc(("Invalid symbol table entry found at %RGv\n",
+ AddrDynstrStart.FlatPtr + i * cbDynSymEnt));
+
+ uint8_t *pbBuf = &abBuf[i * cbDynSymEnt];
+ size_t cbLeft = cbToRead - i * cbDynSymEnt;
+ /*
+ * Check to the end of the buffer whether it contains only a certain set of
+ * ASCII characters and 0 terminators.
+ */
+ while ( cbLeft > 0
+ && ( RT_C_IS_ALNUM(*pbBuf)
+ || *pbBuf == '_'
+ || *pbBuf == '\0'
+ || *pbBuf == '.'))
+ {
+ cbLeft--;
+ pbBuf++;
+ }
+
+ if (!cbLeft)
+ {
+ pVMM->pfnDBGFR3AddrAdd(&AddrDynstrStart, i * cbDynSymEnt);
+ LogFlowFunc(("Found all required section start addresses (.dynsym=%RGv cSymbols=%u, .dynstr=%RGv cb=%u)\n",
+ AddrDynsymStart.FlatPtr, cSymbols, AddrDynstrStart.FlatPtr,
+ pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr));
+ dbgDiggerFreeBsdLoadSymbols(pThis, pUVM, pVMM, pszName, uKernelStart, cbKernel,
+ &AddrDynsymStart, cSymbols, &AddrDynstrStart,
+ pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr);
+ return;
+ }
+ else
+ LogFlowFunc(("Found invalid ASCII character in .dynstr section candidate: %#x\n", *pbBuf));
+ }
+ else
+ {
+ cSymbols++;
+ if ( ELF32_ST_TYPE(uType) != STT_NOTYPE
+ && FBSD_VALID_ADDRESS(pThis, AddrVal))
+ {
+ uKernelStart = RT_MIN(uKernelStart, AddrVal);
+ cbKernel = RT_MAX(cbKernel, AddrVal + cbSymVal - uKernelStart);
+ }
+ }
+ }
+
+ /* Don't account incomplete entries. */
+ pVMM->pfnDBGFR3AddrAdd(&AddrDynstrStart, (cbToRead / cbDynSymEnt) * cbDynSymEnt);
+ }
+ }
+ else
+ LogFlowFunc((".hash section overlaps with .text section: %zu (expected much less than %u)\n", cbHash,
+ pThis->AddrKernelText.FlatPtr - AddrHashStart.FlatPtr));
+ }
+#endif
+}
+
+
+/**
+ * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog}
+ */
+static DECLCALLBACK(int) dbgDiggerFreeBsdIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, uint32_t fFlags,
+ uint32_t cMessages, char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ PDBGDIGGERFBSD pData = RT_FROM_MEMBER(pThis, DBGDIGGERFBSD, IDmesg);
+ RT_NOREF(fFlags);
+
+ if (cMessages < 1)
+ return VERR_INVALID_PARAMETER;
+
+ /* Resolve the message buffer address from the msgbufp symbol. */
+ RTDBGSYMBOL SymInfo;
+ int rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "kernel!msgbufp", &SymInfo, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS AddrMsgBuf;
+
+ /* Read the message buffer pointer. */
+ RTGCPTR GCPtrMsgBufP = 0;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrMsgBuf, SymInfo.Value),
+ &GCPtrMsgBufP, pData->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ {
+ Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: failed to read msgbufp at %RGv: %Rrc\n", AddrMsgBuf.FlatPtr, rc));
+ return VERR_NOT_FOUND;
+ }
+ if (!FBSD_VALID_ADDRESS(pData, GCPtrMsgBufP))
+ {
+ Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Invalid address for msgbufp: %RGv\n", GCPtrMsgBufP));
+ return VERR_NOT_FOUND;
+ }
+
+ /* Read the structure. */
+ FBSDMSGBUF MsgBuf;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrMsgBuf, GCPtrMsgBufP),
+ &MsgBuf, sizeof(MsgBuf));
+ if (RT_SUCCESS(rc))
+ {
+ RTGCUINTPTR AddrBuf = FBSD_UNION(pData, &MsgBuf, msg_ptr);
+ uint32_t cbMsgBuf = FBSD_UNION(pData, &MsgBuf, msg_size);
+ uint32_t uMsgBufSeqR = FBSD_UNION(pData, &MsgBuf, msg_rseq);
+ uint32_t uMsgBufSeqW = FBSD_UNION(pData, &MsgBuf, msg_wseq);
+
+ /*
+ * Validate the structure.
+ */
+ if ( FBSD_UNION(pData, &MsgBuf, msg_magic) != FBSD_MSGBUF_MAGIC
+ || cbMsgBuf < UINT32_C(4096)
+ || cbMsgBuf > 16*_1M
+ || FBSD_UNION(pData, &MsgBuf, msg_rseq) > cbMsgBuf
+ || FBSD_UNION(pData, &MsgBuf, msg_wseq) > cbMsgBuf
+ || !FBSD_VALID_ADDRESS(pData, AddrBuf) )
+ {
+ Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Invalid MsgBuf data: msg_magic=%#x msg_size=%#x msg_rseq=%#x msg_wseq=%#x msg_ptr=%RGv\n",
+ FBSD_UNION(pData, &MsgBuf, msg_magic), cbMsgBuf, uMsgBufSeqR, uMsgBufSeqW, AddrBuf));
+ return VERR_INVALID_STATE;
+ }
+
+ /*
+ * Read the buffer.
+ */
+ char *pchMsgBuf = (char *)RTMemAlloc(cbMsgBuf);
+ if (!pchMsgBuf)
+ {
+ Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Failed to allocate %#x bytes of memory for the log buffer\n",
+ cbMsgBuf));
+ return VERR_INVALID_STATE;
+ }
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrMsgBuf, AddrBuf),
+ pchMsgBuf, cbMsgBuf);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy it out raw.
+ */
+ uint32_t offDst = 0;
+ if (uMsgBufSeqR < uMsgBufSeqW)
+ {
+ /* Single chunk between the read and write offsets. */
+ uint32_t cbToCopy = uMsgBufSeqW - uMsgBufSeqR;
+ if (cbToCopy < cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbToCopy);
+ pszBuf[cbToCopy] = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ if (cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ offDst = cbToCopy + 1;
+ }
+ else
+ {
+ /* Two chunks, read offset to end, start to write offset. */
+ uint32_t cbFirst = cbMsgBuf - uMsgBufSeqR;
+ uint32_t cbSecond = uMsgBufSeqW;
+ if (cbFirst + cbSecond < cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbFirst);
+ memcpy(&pszBuf[cbFirst], pchMsgBuf, cbSecond);
+ offDst = cbFirst + cbSecond;
+ pszBuf[offDst++] = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ offDst = cbFirst + cbSecond + 1;
+ if (cbFirst < cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbFirst);
+ memcpy(&pszBuf[cbFirst], pchMsgBuf, cbBuf - cbFirst);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ else if (cbBuf)
+ {
+ memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+
+ if (pcbActual)
+ *pcbActual = offDst;
+ }
+ else
+ Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Error reading %#x bytes at %RGv: %Rrc\n", cbBuf, AddrBuf, rc));
+ RTMemFree(pchMsgBuf);
+ }
+ else
+ LogFlowFunc(("Failed to read message buffer header: %Rrc\n", rc));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnStackUnwindAssist
+ */
+static DECLCALLBACK(int) dbgDiggerFreeBsdStackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu,
+ PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState,
+ PCCPUMCTX pInitialCtx, RTDBGAS hAs, uint64_t *puScratch)
+{
+ RT_NOREF(pUVM, pVMM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryInterface
+ */
+static DECLCALLBACK(void *) dbgDiggerFreeBsdQueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+ RT_NOREF(pUVM, pVMM);
+
+ switch (enmIf)
+ {
+ case DBGFOSINTERFACE_DMESG:
+ return &pThis->IDmesg;
+
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryVersion
+ */
+static DECLCALLBACK(int) dbgDiggerFreeBsdQueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData,
+ char *pszVersion, size_t cchVersion)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+ Assert(pThis->fValid); RT_NOREF(pThis);
+
+ RTDBGSYMBOL SymInfo;
+ int rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "kernel!version", &SymInfo, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS AddrVersion;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrVersion, SymInfo.Value);
+
+ rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &AddrVersion, pszVersion, cchVersion);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszEnd = RTStrEnd(pszVersion, cchVersion);
+ AssertReturn(pszEnd, VERR_BUFFER_OVERFLOW);
+ while ( pszEnd > pszVersion
+ && RT_C_IS_SPACE(pszEnd[-1]))
+ pszEnd--;
+ *pszEnd = '\0';
+ }
+ else
+ RTStrPrintf(pszVersion, cchVersion, "DBGFR3MemReadString -> %Rrc", rc);
+ }
+
+ return rc;
+}
+
+
+
+/**
+ * @copydoc DBGFOSREG::pfnTerm
+ */
+static DECLCALLBACK(void) dbgDiggerFreeBsdTerm(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+ Assert(pThis->fValid);
+ RT_NOREF(pUVM, pVMM);
+
+ pThis->fValid = false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnRefresh
+ */
+static DECLCALLBACK(int) dbgDiggerFreeBsdRefresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+ NOREF(pThis);
+ Assert(pThis->fValid);
+
+ dbgDiggerFreeBsdTerm(pUVM, pVMM, pvData);
+ return dbgDiggerFreeBsdInit(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnInit
+ */
+static DECLCALLBACK(int) dbgDiggerFreeBsdInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+ Assert(!pThis->fValid);
+
+ RT_NOREF1(pUVM);
+
+ dbgDiggerFreeBsdProcessKernelImage(pThis, pUVM, pVMM, "kernel");
+ pThis->fValid = true;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnProbe
+ */
+static DECLCALLBACK(bool) dbgDiggerFreeBsdProbe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+
+ /*
+ * Look for the magic ELF header near the known start addresses.
+ * If one is found look for the magic "/red/herring" string which is in the
+ * "interp" section not far away and then validate the start of the ELF header
+ * to be sure.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_au64FreeBsdKernelAddresses); i++)
+ {
+ static const uint8_t s_abNeedle[] = ELFMAG;
+ DBGFADDRESS KernelAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &KernelAddr, g_au64FreeBsdKernelAddresses[i]);
+ DBGFADDRESS HitAddr;
+ uint32_t cbLeft = FBSD_MAX_KERNEL_SIZE;
+
+ while (cbLeft > X86_PAGE_4K_SIZE)
+ {
+ int rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, cbLeft, 1,
+ s_abNeedle, sizeof(s_abNeedle) - 1, &HitAddr);
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * Look for the magic "/red/herring" near the header and verify the basic
+ * ELF header.
+ */
+ DBGFADDRESS HitAddrInterp;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, FBSD_MAX_INTERP_OFFSET, 1,
+ g_abNeedleInterp, sizeof(g_abNeedleInterp), &HitAddrInterp);
+ if (RT_SUCCESS(rc))
+ {
+ union
+ {
+ uint8_t ab[2 * X86_PAGE_4K_SIZE];
+ Elf32_Ehdr Hdr32;
+ Elf64_Ehdr Hdr64;
+ } ElfHdr;
+ AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_ident, Elf32_Ehdr, e_ident);
+ AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_type, Elf32_Ehdr, e_type);
+ AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_machine, Elf32_Ehdr, e_machine);
+ AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_version, Elf32_Ehdr, e_version);
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &HitAddr, &ElfHdr.ab[0], X86_PAGE_4K_SIZE);
+ if (RT_SUCCESS(rc))
+ {
+ /* We verified the magic above already by scanning for it. */
+ if ( ( ElfHdr.Hdr32.e_ident[EI_CLASS] == ELFCLASS32
+ || ElfHdr.Hdr32.e_ident[EI_CLASS] == ELFCLASS64)
+ && ElfHdr.Hdr32.e_ident[EI_DATA] == ELFDATA2LSB
+ && ElfHdr.Hdr32.e_ident[EI_VERSION] == EV_CURRENT
+ && ElfHdr.Hdr32.e_ident[EI_OSABI] == ELFOSABI_FREEBSD
+ && ElfHdr.Hdr32.e_type == ET_EXEC
+ && ( ElfHdr.Hdr32.e_machine == EM_386
+ || ElfHdr.Hdr32.e_machine == EM_X86_64)
+ && ElfHdr.Hdr32.e_version == EV_CURRENT)
+ {
+ pThis->f64Bit = ElfHdr.Hdr32.e_ident[EI_CLASS] == ELFCLASS64;
+ pThis->AddrKernelElfStart = HitAddr;
+ pThis->AddrKernelInterp = HitAddrInterp;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelText, FBSD_UNION(pThis, &ElfHdr, e_entry));
+ LogFunc(("Found %s FreeBSD kernel at %RGv (.interp section at %RGv, .text section at %RGv)\n",
+ pThis->f64Bit ? "amd64" : "i386", pThis->AddrKernelElfStart.FlatPtr,
+ pThis->AddrKernelInterp.FlatPtr, pThis->AddrKernelText.FlatPtr));
+ return true;
+ }
+ }
+ }
+
+ /*
+ * Advance.
+ */
+ RTGCUINTPTR cbDistance = HitAddr.FlatPtr - KernelAddr.FlatPtr + sizeof(s_abNeedle) - 1;
+ if (RT_UNLIKELY(cbDistance >= cbLeft))
+ break;
+
+ cbLeft -= cbDistance;
+ pVMM->pfnDBGFR3AddrAdd(&KernelAddr, cbDistance);
+ }
+ }
+ return false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnDestruct
+ */
+static DECLCALLBACK(void) dbgDiggerFreeBsdDestruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnConstruct
+ */
+static DECLCALLBACK(int) dbgDiggerFreeBsdConstruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData;
+ RT_NOREF(pUVM, pVMM);
+
+ pThis->fValid = false;
+ pThis->f64Bit = false;
+ pThis->IDmesg.u32Magic = DBGFOSIDMESG_MAGIC;
+ pThis->IDmesg.pfnQueryKernelLog = dbgDiggerFreeBsdIDmsg_QueryKernelLog;
+ pThis->IDmesg.u32EndMagic = DBGFOSIDMESG_MAGIC;
+
+ return VINF_SUCCESS;
+}
+
+
+const DBGFOSREG g_DBGDiggerFreeBsd =
+{
+ /* .u32Magic = */ DBGFOSREG_MAGIC,
+ /* .fFlags = */ 0,
+ /* .cbData = */ sizeof(DBGDIGGERFBSD),
+ /* .szName = */ "FreeBSD",
+ /* .pfnConstruct = */ dbgDiggerFreeBsdConstruct,
+ /* .pfnDestruct = */ dbgDiggerFreeBsdDestruct,
+ /* .pfnProbe = */ dbgDiggerFreeBsdProbe,
+ /* .pfnInit = */ dbgDiggerFreeBsdInit,
+ /* .pfnRefresh = */ dbgDiggerFreeBsdRefresh,
+ /* .pfnTerm = */ dbgDiggerFreeBsdTerm,
+ /* .pfnQueryVersion = */ dbgDiggerFreeBsdQueryVersion,
+ /* .pfnQueryInterface = */ dbgDiggerFreeBsdQueryInterface,
+ /* .pfnStackUnwindAssist = */ dbgDiggerFreeBsdStackUnwindAssist,
+ /* .u32EndMagic = */ DBGFOSREG_MAGIC
+};
+
diff --git a/src/VBox/Debugger/DBGPlugInLinux.cpp b/src/VBox/Debugger/DBGPlugInLinux.cpp
new file mode 100644
index 00000000..89ae306b
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInLinux.cpp
@@ -0,0 +1,3045 @@
+/* $Id: DBGPlugInLinux.cpp $ */
+/** @file
+ * DBGPlugInLinux - Debugger and Guest OS Digger Plugin For Linux.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugIns.h"
+#include "DBGPlugInCommonELF.h"
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/dis.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** @name InternalLinux structures
+ * @{ */
+
+
+/** @} */
+
+
+/**
+ * Config item type.
+ */
+typedef enum DBGDIGGERLINUXCFGITEMTYPE
+{
+ /** Invalid type. */
+ DBGDIGGERLINUXCFGITEMTYPE_INVALID = 0,
+ /** String. */
+ DBGDIGGERLINUXCFGITEMTYPE_STRING,
+ /** Number. */
+ DBGDIGGERLINUXCFGITEMTYPE_NUMBER,
+ /** Flag whether this feature is included in the
+ * kernel or as a module. */
+ DBGDIGGERLINUXCFGITEMTYPE_FLAG
+} DBGDIGGERLINUXCFGITEMTYPE;
+
+/**
+ * Item in the config database.
+ */
+typedef struct DBGDIGGERLINUXCFGITEM
+{
+ /** String space core. */
+ RTSTRSPACECORE Core;
+ /** Config item type. */
+ DBGDIGGERLINUXCFGITEMTYPE enmType;
+ /** Data based on the type. */
+ union
+ {
+ /** Number. */
+ int64_t i64Num;
+ /** Flag. */
+ bool fModule;
+ /** String - variable in size. */
+ char aszString[1];
+ } u;
+} DBGDIGGERLINUXCFGITEM;
+/** Pointer to a config database item. */
+typedef DBGDIGGERLINUXCFGITEM *PDBGDIGGERLINUXCFGITEM;
+/** Pointer to a const config database item. */
+typedef const DBGDIGGERLINUXCFGITEM *PCDBGDIGGERLINUXCFGITEM;
+
+/**
+ * Linux guest OS digger instance data.
+ */
+typedef struct DBGDIGGERLINUX
+{
+ /** Whether the information is valid or not.
+ * (For fending off illegal interface method calls.) */
+ bool fValid;
+ /** Set if 64-bit, clear if 32-bit. */
+ bool f64Bit;
+ /** Set if the kallsyms table uses relative addressing, clear
+ * if absolute addresses are used. */
+ bool fRelKrnlAddr;
+ /** The relative base when kernel symbols use offsets rather than
+ * absolute addresses. */
+ RTGCUINTPTR uKernelRelativeBase;
+ /** The guest kernel version used for version comparisons. */
+ uint32_t uKrnlVer;
+ /** The guest kernel major version. */
+ uint32_t uKrnlVerMaj;
+ /** The guest kernel minor version. */
+ uint32_t uKrnlVerMin;
+ /** The guest kernel build version. */
+ uint32_t uKrnlVerBld;
+
+ /** The address of the linux banner.
+ * This is set during probing. */
+ DBGFADDRESS AddrLinuxBanner;
+ /** Kernel base address.
+ * This is set during probing, refined during kallsyms parsing. */
+ DBGFADDRESS AddrKernelBase;
+ /** The kernel size. */
+ uint32_t cbKernel;
+
+ /** The number of kernel symbols (kallsyms_num_syms).
+ * This is set during init. */
+ uint32_t cKernelSymbols;
+ /** The size of the kernel name table (sizeof(kallsyms_names)). */
+ uint32_t cbKernelNames;
+ /** Number of entries in the kernel_markers table. */
+ uint32_t cKernelNameMarkers;
+ /** The size of the kernel symbol token table. */
+ uint32_t cbKernelTokenTable;
+ /** The address of the encoded kernel symbol names (kallsyms_names). */
+ DBGFADDRESS AddrKernelNames;
+ /** The address of the kernel symbol addresses (kallsyms_addresses). */
+ DBGFADDRESS AddrKernelAddresses;
+ /** The address of the kernel symbol name markers (kallsyms_markers). */
+ DBGFADDRESS AddrKernelNameMarkers;
+ /** The address of the kernel symbol token table (kallsyms_token_table). */
+ DBGFADDRESS AddrKernelTokenTable;
+ /** The address of the kernel symbol token index table (kallsyms_token_index). */
+ DBGFADDRESS AddrKernelTokenIndex;
+
+ /** The kernel message log interface. */
+ DBGFOSIDMESG IDmesg;
+
+ /** The config database root. */
+ RTSTRSPACE hCfgDb;
+} DBGDIGGERLINUX;
+/** Pointer to the linux guest OS digger instance data. */
+typedef DBGDIGGERLINUX *PDBGDIGGERLINUX;
+
+
+/**
+ * The current printk_log structure.
+ */
+typedef struct LNXPRINTKHDR
+{
+ /** Monotonic timestamp. */
+ uint64_t nsTimestamp;
+ /** The total size of this message record. */
+ uint16_t cbTotal;
+ /** The size of the text part (immediately follows the header). */
+ uint16_t cbText;
+ /** The size of the optional dictionary part (follows the text). */
+ uint16_t cbDict;
+ /** The syslog facility number. */
+ uint8_t bFacility;
+ /** First 5 bits are internal flags, next 3 bits are log level. */
+ uint8_t fFlagsAndLevel;
+} LNXPRINTKHDR;
+AssertCompileSize(LNXPRINTKHDR, 2*sizeof(uint64_t));
+/** Pointer to linux printk_log header. */
+typedef LNXPRINTKHDR *PLNXPRINTKHDR;
+/** Pointer to linux const printk_log header. */
+typedef LNXPRINTKHDR const *PCLNXPRINTKHDR;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** First kernel map address for 32bit Linux hosts (__START_KERNEL_map). */
+#define LNX32_KERNEL_ADDRESS_START UINT32_C(0xc0000000)
+/** First kernel map address for 64bit Linux hosts (__START_KERNEL_map). */
+#define LNX64_KERNEL_ADDRESS_START UINT64_C(0xffffffff80000000)
+/** Validates a 32-bit linux kernel address */
+#define LNX32_VALID_ADDRESS(Addr) ((Addr) > UINT32_C(0x80000000) && (Addr) < UINT32_C(0xfffff000))
+/** Validates a 64-bit linux kernel address */
+#define LNX64_VALID_ADDRESS(Addr) ((Addr) > UINT64_C(0xffff800000000000) && (Addr) < UINT64_C(0xfffffffffffff000))
+
+/** The max kernel size. */
+#define LNX_MAX_KERNEL_SIZE UINT32_C(0x0f000000)
+/** Maximum kernel log buffer size. */
+#define LNX_MAX_KERNEL_LOG_SIZE (16 * _1M)
+
+/** The maximum size we expect for kallsyms_names. */
+#define LNX_MAX_KALLSYMS_NAMES_SIZE UINT32_C(0x200000)
+/** The maximum size we expect for kallsyms_token_table. */
+#define LNX_MAX_KALLSYMS_TOKEN_TABLE_SIZE UINT32_C(0x10000)
+/** The minimum number of symbols we expect in kallsyms_num_syms. */
+#define LNX_MIN_KALLSYMS_SYMBOLS UINT32_C(2048)
+/** The maximum number of symbols we expect in kallsyms_num_syms. */
+#define LNX_MAX_KALLSYMS_SYMBOLS UINT32_C(1048576)
+/** The min length an encoded symbol in kallsyms_names is expected to have. */
+#define LNX_MIN_KALLSYMS_ENC_LENGTH UINT8_C(1)
+/** The max length an encoded symbol in kallsyms_names is expected to have.
+ * @todo check real life here. */
+#define LNX_MAX_KALLSYMS_ENC_LENGTH UINT8_C(28)
+/** The approximate maximum length of a string token. */
+#define LNX_MAX_KALLSYMS_TOKEN_LEN UINT16_C(32)
+/** Maximum compressed config size expected. */
+#define LNX_MAX_COMPRESSED_CFG_SIZE _1M
+
+/** Module tag for linux ('linuxmod' on little endian ASCII systems). */
+#define DIG_LNX_MOD_TAG UINT64_C(0x545f5d78758e898c)
+/** Macro for building a Linux kernel version which can be used for comparisons. */
+#define LNX_MK_VER(major, minor, build) (((major) << 22) | ((minor) << 12) | (build))
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgDiggerLinuxInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Table of common linux kernel addresses. */
+static uint64_t g_au64LnxKernelAddresses[] =
+{
+ UINT64_C(0xc0100000),
+ UINT64_C(0x90100000),
+ UINT64_C(0xffffffff80200000)
+};
+
+static const uint8_t g_abLinuxVersion[] = "Linux version ";
+/** The needle for searching for the kernel log area (the value is observed in pretty much all 32bit and 64bit x86 kernels).
+ * This needle should appear only once in the memory due to the address being filled in by a format string. */
+static const uint8_t g_abKrnlLogNeedle[] = "BIOS-e820: [mem 0x0000000000000000";
+
+
+/**
+ * Tries to resolve the kernel log buffer start and end by searching for needle.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM function table.
+ * @param pGCPtrLogBuf Where to store the start of the kernel log buffer on success.
+ * @param pcbLogBuf Where to store the size of the kernel log buffer on success.
+ */
+static int dbgDiggerLinuxKrnlLogBufFindByNeedle(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ RTGCPTR *pGCPtrLogBuf, uint32_t *pcbLogBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to find the needle, it should be very early in the kernel log buffer. */
+ DBGFADDRESS AddrScan;
+ DBGFADDRESS AddrHit;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrScan, pThis->f64Bit ? LNX64_KERNEL_ADDRESS_START : LNX32_KERNEL_ADDRESS_START);
+
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &AddrScan, ~(RTGCUINTPTR)0, 1 /*uAlign*/,
+ g_abKrnlLogNeedle, sizeof(g_abKrnlLogNeedle) - 1, &AddrHit);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbLogBuf = 0;
+ uint64_t tsLastNs = 0;
+ DBGFADDRESS AddrCur;
+
+ pVMM->pfnDBGFR3AddrSub(&AddrHit, sizeof(LNXPRINTKHDR));
+ AddrCur = AddrHit;
+
+ /* Try to find the end of the kernel log buffer. */
+ for (;;)
+ {
+ if (cbLogBuf >= LNX_MAX_KERNEL_LOG_SIZE)
+ break;
+
+ LNXPRINTKHDR Hdr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrCur, &Hdr, sizeof(Hdr));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t const cbLogAlign = 4;
+
+ /*
+ * If the header does not look valid anymore we stop.
+ * Timestamps are monotonically increasing.
+ */
+ if ( !Hdr.cbTotal /* Zero entry size means there is no record anymore, doesn't make sense to look futher. */
+ || Hdr.cbText + Hdr.cbDict + sizeof(Hdr) > Hdr.cbTotal
+ || (Hdr.cbTotal & (cbLogAlign - 1)) != 0
+ || tsLastNs > Hdr.nsTimestamp)
+ break;
+
+ /** @todo Maybe read text part and verify it is all ASCII. */
+
+ cbLogBuf += Hdr.cbTotal;
+ pVMM->pfnDBGFR3AddrAdd(&AddrCur, Hdr.cbTotal);
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /** @todo Go back to find the start address of the kernel log (or we loose potential kernel log messages). */
+
+ if ( RT_SUCCESS(rc)
+ && cbLogBuf)
+ {
+ /* Align log buffer size to a power of two. */
+ uint32_t idxBitLast = ASMBitLastSetU32(cbLogBuf);
+ idxBitLast--; /* There is at least one bit set, see check above. */
+
+ if (cbLogBuf & (RT_BIT_32(idxBitLast) - 1))
+ idxBitLast++;
+
+ *pGCPtrLogBuf = AddrHit.FlatPtr;
+ *pcbLogBuf = RT_MIN(RT_BIT_32(idxBitLast), LNX_MAX_KERNEL_LOG_SIZE);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Converts a given offset into an absolute address if relative kernel offsets are used for
+ * kallsyms.
+ *
+ * @returns The absolute kernel address.
+ * @param pThis The Linux digger data.
+ * @param uOffset The offset to convert.
+ */
+DECLINLINE(RTGCUINTPTR) dbgDiggerLinuxConvOffsetToAddr(PDBGDIGGERLINUX pThis, int32_t uOffset)
+{
+ RTGCUINTPTR uAddr;
+
+ /*
+ * How the absolute address is calculated from the offset depends on the
+ * CONFIG_KALLSYMS_ABSOLUTE_PERCPU config which is only set for 64bit
+ * SMP kernels (we assume that all 64bit kernels always have SMP enabled too).
+ */
+ if (pThis->f64Bit)
+ {
+ if (uOffset >= 0)
+ uAddr = uOffset;
+ else
+ uAddr = pThis->uKernelRelativeBase - 1 - uOffset;
+ }
+ else
+ uAddr = pThis->uKernelRelativeBase + (uint32_t)uOffset;
+
+ return uAddr;
+}
+
+/**
+ * Disassembles a simple getter returning the value for it.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM function table.
+ * @param hMod The module to use.
+ * @param pszSymbol The symbol of the getter.
+ * @param pvVal Where to store the value on success.
+ * @param cbVal Size of the value in bytes.
+ */
+static int dbgDiggerLinuxDisassembleSimpleGetter(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, RTDBGMOD hMod,
+ const char *pszSymbol, void *pvVal, uint32_t cbVal)
+{
+ int rc = VINF_SUCCESS;
+
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByName(hMod, pszSymbol, &SymInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the diassembling. Disassemble until a ret instruction is encountered
+ * or a limit is reached (don't want to disassemble for too long as the getter
+ * should be short).
+ * push and pop instructions are skipped as well as any mov instructions not
+ * touching the rax or eax register (depending on the size of the value).
+ */
+ unsigned cInstrDisassembled = 0;
+ uint32_t offInstr = 0;
+ bool fRet = false;
+ DISSTATE DisState;
+ RT_ZERO(DisState);
+
+ do
+ {
+ DBGFADDRESS Addr;
+ RTGCPTR GCPtrCur = (RTGCPTR)SymInfo.Value + pThis->AddrKernelBase.FlatPtr + offInstr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, GCPtrCur);
+
+ /* Prefetch the instruction. */
+ uint8_t abInstr[32];
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &abInstr[0], sizeof(abInstr));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbInstr = 0;
+
+ rc = DISInstr(&abInstr[0], pThis->f64Bit ? DISCPUMODE_64BIT : DISCPUMODE_32BIT, &DisState, &cbInstr);
+ if (RT_SUCCESS(rc))
+ {
+ switch (DisState.pCurInstr->uOpcode)
+ {
+ case OP_PUSH:
+ case OP_POP:
+ case OP_NOP:
+ case OP_LEA:
+ break;
+ case OP_RETN:
+ /* Getter returned, abort disassembling. */
+ fRet = true;
+ break;
+ case OP_MOV:
+ /*
+ * Check that the destination is either rax or eax depending on the
+ * value size.
+ *
+ * Param1 is the destination and Param2 the source.
+ */
+ if ( ( ( (DisState.Param1.fUse & (DISUSE_BASE | DISUSE_REG_GEN32))
+ && cbVal == sizeof(uint32_t))
+ || ( (DisState.Param1.fUse & (DISUSE_BASE | DISUSE_REG_GEN64))
+ && cbVal == sizeof(uint64_t)))
+ && DisState.Param1.Base.idxGenReg == DISGREG_RAX)
+ {
+ /* Parse the source. */
+ if (DisState.Param2.fUse & (DISUSE_IMMEDIATE32 | DISUSE_IMMEDIATE64))
+ memcpy(pvVal, &DisState.Param2.uValue, cbVal);
+ else if (DisState.Param2.fUse & (DISUSE_RIPDISPLACEMENT32|DISUSE_DISPLACEMENT32|DISUSE_DISPLACEMENT64))
+ {
+ RTGCPTR GCPtrVal = 0;
+
+ if (DisState.Param2.fUse & DISUSE_RIPDISPLACEMENT32)
+ GCPtrVal = GCPtrCur + DisState.Param2.uDisp.i32 + cbInstr;
+ else if (DisState.Param2.fUse & DISUSE_DISPLACEMENT32)
+ GCPtrVal = (RTGCPTR)DisState.Param2.uDisp.u32;
+ else if (DisState.Param2.fUse & DISUSE_DISPLACEMENT64)
+ GCPtrVal = (RTGCPTR)DisState.Param2.uDisp.u64;
+ else
+ AssertMsgFailedBreakStmt(("Invalid displacement\n"), rc = VERR_INVALID_STATE);
+
+ DBGFADDRESS AddrVal;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrVal, GCPtrVal),
+ pvVal, cbVal);
+ }
+ }
+ break;
+ default:
+ /* All other instructions will cause an error for now (playing safe here). */
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ cInstrDisassembled++;
+ offInstr += cbInstr;
+ }
+ }
+ } while ( RT_SUCCESS(rc)
+ && cInstrDisassembled < 20
+ && !fRet);
+ }
+
+ return rc;
+}
+
+/**
+ * Try to get at the log buffer starting address and size by disassembling emit_log_char.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM function table.
+ * @param hMod The module to use.
+ * @param pGCPtrLogBuf Where to store the log buffer pointer on success.
+ * @param pcbLogBuf Where to store the size of the log buffer on success.
+ */
+static int dbgDiggerLinuxQueryAsciiLogBufferPtrs(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, RTDBGMOD hMod,
+ RTGCPTR *pGCPtrLogBuf, uint32_t *pcbLogBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ /**
+ * We disassemble emit_log_char to get at the log buffer address and size.
+ * This is used in case the symbols are not exported in kallsyms.
+ *
+ * This is what it typically looks like:
+ * vmlinux!emit_log_char:
+ * %00000000c01204a1 56 push esi
+ * %00000000c01204a2 8b 35 d0 1c 34 c0 mov esi, dword [0c0341cd0h]
+ * %00000000c01204a8 53 push ebx
+ * %00000000c01204a9 8b 1d 74 3b 3e c0 mov ebx, dword [0c03e3b74h]
+ * %00000000c01204af 8b 0d d8 1c 34 c0 mov ecx, dword [0c0341cd8h]
+ * %00000000c01204b5 8d 56 ff lea edx, [esi-001h]
+ * %00000000c01204b8 21 da and edx, ebx
+ * %00000000c01204ba 88 04 11 mov byte [ecx+edx], al
+ * %00000000c01204bd 8d 53 01 lea edx, [ebx+001h]
+ * %00000000c01204c0 89 d0 mov eax, edx
+ * [...]
+ */
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByName(hMod, "emit_log_char", &SymInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the diassembling. Disassemble until a ret instruction is encountered
+ * or a limit is reached (don't want to disassemble for too long as the getter
+ * should be short). Certain instructions found are ignored (push, nop, etc.).
+ */
+ unsigned cInstrDisassembled = 0;
+ uint32_t offInstr = 0;
+ bool fRet = false;
+ DISSTATE DisState;
+ unsigned cAddressesUsed = 0;
+ struct { size_t cb; RTGCPTR GCPtrOrigSrc; } aAddresses[5];
+ RT_ZERO(DisState);
+ RT_ZERO(aAddresses);
+
+ do
+ {
+ DBGFADDRESS Addr;
+ RTGCPTR GCPtrCur = (RTGCPTR)SymInfo.Value + pThis->AddrKernelBase.FlatPtr + offInstr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, GCPtrCur);
+
+ /* Prefetch the instruction. */
+ uint8_t abInstr[32];
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &abInstr[0], sizeof(abInstr));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbInstr = 0;
+
+ rc = DISInstr(&abInstr[0], pThis->f64Bit ? DISCPUMODE_64BIT : DISCPUMODE_32BIT, &DisState, &cbInstr);
+ if (RT_SUCCESS(rc))
+ {
+ switch (DisState.pCurInstr->uOpcode)
+ {
+ case OP_PUSH:
+ case OP_POP:
+ case OP_NOP:
+ case OP_LEA:
+ case OP_AND:
+ case OP_CBW:
+ case OP_DEC:
+ break;
+ case OP_RETN:
+ /* emit_log_char returned, abort disassembling. */
+ rc = VERR_NOT_FOUND;
+ fRet = true;
+ break;
+ case OP_MOV:
+ case OP_MOVSXD:
+ /*
+ * If a mov is encountered writing to memory with al (or dil for amd64) being the source the
+ * character is stored and we can infer the base address and size of the log buffer from
+ * the source addresses.
+ */
+ if ( (DisState.Param2.fUse & DISUSE_REG_GEN8)
+ && ( (DisState.Param2.Base.idxGenReg == DISGREG_AL && !pThis->f64Bit)
+ || (DisState.Param2.Base.idxGenReg == DISGREG_DIL && pThis->f64Bit))
+ && DISUSE_IS_EFFECTIVE_ADDR(DisState.Param1.fUse))
+ {
+ RTGCPTR GCPtrLogBuf = 0;
+ uint32_t cbLogBuf = 0;
+
+ /*
+ * We can stop disassembling now and inspect all registers, look for a valid kernel address first.
+ * Only one of the accessed registers should hold a valid kernel address.
+ * For the log size look for the biggest non kernel address.
+ */
+ for (unsigned i = 0; i < cAddressesUsed; i++)
+ {
+ DBGFADDRESS AddrVal;
+ union { uint8_t abVal[8]; uint32_t u32Val; uint64_t u64Val; } Val;
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrVal,
+ aAddresses[i].GCPtrOrigSrc),
+ &Val.abVal[0], aAddresses[i].cb);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->f64Bit && aAddresses[i].cb == sizeof(uint64_t))
+ {
+ if (LNX64_VALID_ADDRESS(Val.u64Val))
+ {
+ if (GCPtrLogBuf == 0)
+ GCPtrLogBuf = Val.u64Val;
+ else
+ {
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+ }
+ }
+ else
+ {
+ AssertMsgBreakStmt(aAddresses[i].cb == sizeof(uint32_t),
+ ("Invalid value size\n"), rc = VERR_INVALID_STATE);
+
+ /* Might be a kernel address or a size indicator. */
+ if (!pThis->f64Bit && LNX32_VALID_ADDRESS(Val.u32Val))
+ {
+ if (GCPtrLogBuf == 0)
+ GCPtrLogBuf = Val.u32Val;
+ else
+ {
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+ }
+ else
+ {
+ /*
+ * The highest value will be the log buffer because the other
+ * accessed variables are indexes into the buffer and hence
+ * always smaller than the size.
+ */
+ if (cbLogBuf < Val.u32Val)
+ cbLogBuf = Val.u32Val;
+ }
+ }
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && GCPtrLogBuf != 0
+ && cbLogBuf != 0)
+ {
+ *pGCPtrLogBuf = GCPtrLogBuf;
+ *pcbLogBuf = cbLogBuf;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_NOT_FOUND;
+
+ fRet = true;
+ break;
+ }
+ else
+ {
+ /*
+ * In case of a memory to register move store the destination register index and the
+ * source address in the relation table for later processing.
+ */
+ if ( (DisState.Param1.fUse & (DISUSE_BASE | DISUSE_REG_GEN32 | DISUSE_REG_GEN64))
+ && (DisState.Param2.cb == sizeof(uint32_t) || DisState.Param2.cb == sizeof(uint64_t))
+ && (DisState.Param2.fUse & (DISUSE_RIPDISPLACEMENT32|DISUSE_DISPLACEMENT32|DISUSE_DISPLACEMENT64)))
+ {
+ RTGCPTR GCPtrVal = 0;
+
+ if (DisState.Param2.fUse & DISUSE_RIPDISPLACEMENT32)
+ GCPtrVal = GCPtrCur + DisState.Param2.uDisp.i32 + cbInstr;
+ else if (DisState.Param2.fUse & DISUSE_DISPLACEMENT32)
+ GCPtrVal = (RTGCPTR)DisState.Param2.uDisp.u32;
+ else if (DisState.Param2.fUse & DISUSE_DISPLACEMENT64)
+ GCPtrVal = (RTGCPTR)DisState.Param2.uDisp.u64;
+ else
+ AssertMsgFailedBreakStmt(("Invalid displacement\n"), rc = VERR_INVALID_STATE);
+
+ if (cAddressesUsed < RT_ELEMENTS(aAddresses))
+ {
+ /* movsxd reads always 32bits. */
+ if (DisState.pCurInstr->uOpcode == OP_MOVSXD)
+ aAddresses[cAddressesUsed].cb = sizeof(uint32_t);
+ else
+ aAddresses[cAddressesUsed].cb = DisState.Param2.cb;
+ aAddresses[cAddressesUsed].GCPtrOrigSrc = GCPtrVal;
+ cAddressesUsed++;
+ }
+ else
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ /* All other instructions will cause an error for now (playing safe here). */
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ cInstrDisassembled++;
+ offInstr += cbInstr;
+ }
+ }
+ } while ( RT_SUCCESS(rc)
+ && cInstrDisassembled < 20
+ && !fRet);
+ }
+
+ return rc;
+}
+
+/**
+ * Try to get at the log buffer starting address and size by disassembling some exposed helpers.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM function table.
+ * @param hMod The module to use.
+ * @param pGCPtrLogBuf Where to store the log buffer pointer on success.
+ * @param pcbLogBuf Where to store the size of the log buffer on success.
+ */
+static int dbgDiggerLinuxQueryLogBufferPtrs(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, RTDBGMOD hMod,
+ RTGCPTR *pGCPtrLogBuf, uint32_t *pcbLogBuf)
+{
+ int rc = VINF_SUCCESS;
+
+ struct { void *pvVar; uint32_t cbHost, cbGuest; const char *pszSymbol; } aSymbols[] =
+ {
+ { pGCPtrLogBuf, (uint32_t)sizeof(RTGCPTR), (uint32_t)(pThis->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t)), "log_buf_addr_get" },
+ { pcbLogBuf, (uint32_t)sizeof(uint32_t), (uint32_t)sizeof(uint32_t), "log_buf_len_get" }
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(aSymbols) && RT_SUCCESS(rc); i++)
+ {
+ RT_BZERO(aSymbols[i].pvVar, aSymbols[i].cbHost);
+ Assert(aSymbols[i].cbHost >= aSymbols[i].cbGuest);
+ rc = dbgDiggerLinuxDisassembleSimpleGetter(pThis, pUVM, pVMM, hMod, aSymbols[i].pszSymbol,
+ aSymbols[i].pvVar, aSymbols[i].cbGuest);
+ }
+
+ return rc;
+}
+
+/**
+ * Returns whether the log buffer is a simple ascii buffer or a record based implementation
+ * based on the kernel version found.
+ *
+ * @returns Flag whether the log buffer is the simple ascii buffer.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ */
+static bool dbgDiggerLinuxLogBufferIsAsciiBuffer(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ char szTmp[128];
+ char const *pszVer = &szTmp[sizeof(g_abLinuxVersion) - 1];
+
+ RT_ZERO(szTmp);
+ int rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &pThis->AddrLinuxBanner, szTmp, sizeof(szTmp) - 1);
+ if ( RT_SUCCESS(rc)
+ && RTStrVersionCompare(pszVer, "3.4") == -1)
+ return true;
+
+ return false;
+}
+
+/**
+ * Worker to get at the kernel log for pre 3.4 kernels where the log buffer was just a char buffer.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM user mdoe handle.
+ * @param pVMM The VMM function table.
+ * @param hMod The debug module handle.
+ * @param fFlags Flags reserved for future use, MBZ.
+ * @param cMessages The number of messages to retrieve, counting from the
+ * end of the log (i.e. like tail), use UINT32_MAX for all.
+ * @param pszBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbActual Where to store the number of bytes actually returned,
+ * including zero terminator. On VERR_BUFFER_OVERFLOW this
+ * holds the necessary buffer size. Optional.
+ */
+static int dbgDiggerLinuxLogBufferQueryAscii(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, RTDBGMOD hMod,
+ uint32_t fFlags, uint32_t cMessages,
+ char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ RT_NOREF2(fFlags, cMessages);
+ int rc = VINF_SUCCESS;
+ RTGCPTR GCPtrLogBuf;
+ uint32_t cbLogBuf;
+
+ struct { void *pvVar; size_t cbHost, cbGuest; const char *pszSymbol; } aSymbols[] =
+ {
+ { &GCPtrLogBuf, sizeof(GCPtrLogBuf), pThis->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t), "log_buf" },
+ { &cbLogBuf, sizeof(cbLogBuf), sizeof(cbLogBuf), "log_buf_len" },
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(aSymbols); i++)
+ {
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByName(hMod, aSymbols[i].pszSymbol, &SymInfo);
+ if (RT_SUCCESS(rc))
+ {
+ RT_BZERO(aSymbols[i].pvVar, aSymbols[i].cbHost);
+ Assert(aSymbols[i].cbHost >= aSymbols[i].cbGuest);
+ DBGFADDRESS Addr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr,
+ (RTGCPTR)SymInfo.Value + pThis->AddrKernelBase.FlatPtr),
+ aSymbols[i].pvVar, aSymbols[i].cbGuest);
+ if (RT_SUCCESS(rc))
+ continue;
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Reading '%s' at %RGv: %Rrc\n", aSymbols[i].pszSymbol, Addr.FlatPtr, rc));
+ }
+ else
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Error looking up '%s': %Rrc\n", aSymbols[i].pszSymbol, rc));
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+
+ /*
+ * Some kernels don't expose the variables in kallsyms so we have to try disassemble
+ * some public helpers to get at the addresses.
+ *
+ * @todo: Maybe cache those values so we don't have to do the heavy work every time?
+ */
+ if (rc == VERR_NOT_FOUND)
+ {
+ rc = dbgDiggerLinuxQueryAsciiLogBufferPtrs(pThis, pUVM, pVMM, hMod, &GCPtrLogBuf, &cbLogBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Check if the values make sense.
+ */
+ if (pThis->f64Bit ? !LNX64_VALID_ADDRESS(GCPtrLogBuf) : !LNX32_VALID_ADDRESS(GCPtrLogBuf))
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf' value %RGv is not valid.\n", GCPtrLogBuf));
+ return VERR_NOT_FOUND;
+ }
+ if ( cbLogBuf < 4096
+ || !RT_IS_POWER_OF_TWO(cbLogBuf)
+ || cbLogBuf > 16*_1M)
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf_len' value %#x is not valid.\n", cbLogBuf));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Read the whole log buffer.
+ */
+ uint8_t *pbLogBuf = (uint8_t *)RTMemAlloc(cbLogBuf);
+ if (!pbLogBuf)
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Failed to allocate %#x bytes for log buffer\n", cbLogBuf));
+ return VERR_NO_MEMORY;
+ }
+ DBGFADDRESS Addr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, GCPtrLogBuf), pbLogBuf, cbLogBuf);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Error reading %#x bytes of log buffer at %RGv: %Rrc\n",
+ cbLogBuf, Addr.FlatPtr, rc));
+ RTMemFree(pbLogBuf);
+ return VERR_NOT_FOUND;
+ }
+
+ /** @todo Try to parse where the single messages start to make use of cMessages. */
+ size_t cchLength = RTStrNLen((const char *)pbLogBuf, cbLogBuf);
+ memcpy(&pszBuf[0], pbLogBuf, RT_MIN(cbBuf, cchLength));
+
+ /* Done with the buffer. */
+ RTMemFree(pbLogBuf);
+
+ /* Set return size value. */
+ if (pcbActual)
+ *pcbActual = RT_MIN(cbBuf, cchLength);
+
+ return cbBuf <= cchLength ? VERR_BUFFER_OVERFLOW : VINF_SUCCESS;
+}
+
+
+/**
+ * Worker to process a given record based kernel log.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM user mode handle.
+ * @param pVMM The VMM function table.
+ * @param GCPtrLogBuf Flat guest address of the start of the log buffer.
+ * @param cbLogBuf Power of two aligned size of the log buffer.
+ * @param idxFirst Index in the log bfufer of the first message.
+ * @param idxNext Index where to write hte next message in the log buffer.
+ * @param fFlags Flags reserved for future use, MBZ.
+ * @param cMessages The number of messages to retrieve, counting from the
+ * end of the log (i.e. like tail), use UINT32_MAX for all.
+ * @param pszBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbActual Where to store the number of bytes actually returned,
+ * including zero terminator. On VERR_BUFFER_OVERFLOW this
+ * holds the necessary buffer size. Optional.
+ */
+static int dbgDiggerLinuxKrnLogBufferProcess(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, RTGCPTR GCPtrLogBuf,
+ uint32_t cbLogBuf, uint32_t idxFirst, uint32_t idxNext,
+ uint32_t fFlags, uint32_t cMessages, char *pszBuf, size_t cbBuf,
+ size_t *pcbActual)
+{
+ RT_NOREF(fFlags);
+
+ /*
+ * Check if the values make sense.
+ */
+ if (pThis->f64Bit ? !LNX64_VALID_ADDRESS(GCPtrLogBuf) : !LNX32_VALID_ADDRESS(GCPtrLogBuf))
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf' value %RGv is not valid.\n", GCPtrLogBuf));
+ return VERR_NOT_FOUND;
+ }
+ if ( cbLogBuf < _4K
+ || !RT_IS_POWER_OF_TWO(cbLogBuf)
+ || cbLogBuf > LNX_MAX_KERNEL_LOG_SIZE)
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf_len' value %#x is not valid.\n", cbLogBuf));
+ return VERR_NOT_FOUND;
+ }
+ uint32_t const cbLogAlign = 4;
+ if ( idxFirst > cbLogBuf - sizeof(LNXPRINTKHDR)
+ || (idxFirst & (cbLogAlign - 1)) != 0)
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_first_idx' value %#x is not valid.\n", idxFirst));
+ return VERR_NOT_FOUND;
+ }
+ if ( idxNext > cbLogBuf - sizeof(LNXPRINTKHDR)
+ || (idxNext & (cbLogAlign - 1)) != 0)
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_next_idx' value %#x is not valid.\n", idxNext));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Read the whole log buffer.
+ */
+ uint8_t *pbLogBuf = (uint8_t *)RTMemAlloc(cbLogBuf);
+ if (!pbLogBuf)
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Failed to allocate %#x bytes for log buffer\n", cbLogBuf));
+ return VERR_NO_MEMORY;
+ }
+ DBGFADDRESS Addr;
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, GCPtrLogBuf), pbLogBuf, cbLogBuf);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Error reading %#x bytes of log buffer at %RGv: %Rrc\n",
+ cbLogBuf, Addr.FlatPtr, rc));
+ RTMemFree(pbLogBuf);
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Count the messages in the buffer while doing some basic validation.
+ */
+ uint32_t const cbUsed = idxFirst == idxNext ? cbLogBuf /* could be empty... */
+ : idxFirst < idxNext ? idxNext - idxFirst : cbLogBuf - idxFirst + idxNext;
+ uint32_t cbLeft = cbUsed;
+ uint32_t offCur = idxFirst;
+ uint32_t cLogMsgs = 0;
+
+ while (cbLeft > 0)
+ {
+ PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur];
+ if (!pHdr->cbTotal)
+ {
+ /* Wrap around packet, most likely... */
+ if (cbLogBuf - offCur >= cbLeft)
+ break;
+ offCur = 0;
+ pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur];
+ }
+ if (RT_UNLIKELY( pHdr->cbTotal > cbLogBuf - sizeof(*pHdr) - offCur
+ || pHdr->cbTotal > cbLeft
+ || (pHdr->cbTotal & (cbLogAlign - 1)) != 0
+ || pHdr->cbTotal < (uint32_t)pHdr->cbText + (uint32_t)pHdr->cbDict + sizeof(*pHdr) ))
+ {
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Invalid printk_log record at %#x: cbTotal=%#x cbText=%#x cbDict=%#x cbLogBuf=%#x cbLeft=%#x\n",
+ offCur, pHdr->cbTotal, pHdr->cbText, pHdr->cbDict, cbLogBuf, cbLeft));
+ break;
+ }
+
+ if (pHdr->cbText > 0)
+ cLogMsgs++;
+
+ /* next */
+ offCur += pHdr->cbTotal;
+ cbLeft -= pHdr->cbTotal;
+ }
+ if (!cLogMsgs)
+ {
+ RTMemFree(pbLogBuf);
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Copy the messages into the output buffer.
+ */
+ offCur = idxFirst;
+ cbLeft = cbUsed - cbLeft;
+
+ /* Skip messages that the caller doesn't want. */
+ if (cMessages < cLogMsgs)
+ {
+ uint32_t cToSkip = cLogMsgs - cMessages;
+ cLogMsgs -= cToSkip;
+
+ while (cToSkip > 0)
+ {
+ PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur];
+ if (!pHdr->cbTotal)
+ {
+ offCur = 0;
+ pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur];
+ }
+ if (pHdr->cbText > 0)
+ cToSkip--;
+
+ /* next */
+ offCur += pHdr->cbTotal;
+ cbLeft -= pHdr->cbTotal;
+ }
+ }
+
+ /* Now copy the messages. */
+ size_t offDst = 0;
+ while (cbLeft > 0)
+ {
+ PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur];
+ if ( !pHdr->cbTotal
+ || !cLogMsgs)
+ {
+ if (cbLogBuf - offCur >= cbLeft)
+ break;
+ offCur = 0;
+ pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur];
+ }
+
+ if (pHdr->cbText > 0)
+ {
+ char *pchText = (char *)(pHdr + 1);
+ size_t cchText = RTStrNLen(pchText, pHdr->cbText);
+ if (offDst + cchText < cbBuf)
+ {
+ memcpy(&pszBuf[offDst], pHdr + 1, cchText);
+ pszBuf[offDst + cchText] = '\n';
+ }
+ else if (offDst < cbBuf)
+ memcpy(&pszBuf[offDst], pHdr + 1, cbBuf - offDst);
+ offDst += cchText + 1;
+ }
+
+ /* next */
+ offCur += pHdr->cbTotal;
+ cbLeft -= pHdr->cbTotal;
+ }
+
+ /* Done with the buffer. */
+ RTMemFree(pbLogBuf);
+
+ /* Make sure we've reserved a char for the terminator. */
+ if (!offDst)
+ offDst = 1;
+
+ /* Set return size value. */
+ if (pcbActual)
+ *pcbActual = offDst;
+
+ if (offDst <= cbBuf)
+ return VINF_SUCCESS;
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Worker to get at the kernel log for post 3.4 kernels where the log buffer contains records.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The VM user mdoe handle.
+ * @param pVMM The VMM function table.
+ * @param hMod The debug module handle.
+ * @param fFlags Flags reserved for future use, MBZ.
+ * @param cMessages The number of messages to retrieve, counting from the
+ * end of the log (i.e. like tail), use UINT32_MAX for all.
+ * @param pszBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbActual Where to store the number of bytes actually returned,
+ * including zero terminator. On VERR_BUFFER_OVERFLOW this
+ * holds the necessary buffer size. Optional.
+ */
+static int dbgDiggerLinuxLogBufferQueryRecords(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, RTDBGMOD hMod,
+ uint32_t fFlags, uint32_t cMessages,
+ char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ int rc = VINF_SUCCESS;
+ RTGCPTR GCPtrLogBuf;
+ uint32_t cbLogBuf;
+ uint32_t idxFirst;
+ uint32_t idxNext;
+
+ struct { void *pvVar; size_t cbHost, cbGuest; const char *pszSymbol; } aSymbols[] =
+ {
+ { &GCPtrLogBuf, sizeof(GCPtrLogBuf), pThis->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t), "log_buf" },
+ { &cbLogBuf, sizeof(cbLogBuf), sizeof(cbLogBuf), "log_buf_len" },
+ { &idxFirst, sizeof(idxFirst), sizeof(idxFirst), "log_first_idx" },
+ { &idxNext, sizeof(idxNext), sizeof(idxNext), "log_next_idx" },
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(aSymbols); i++)
+ {
+ RTDBGSYMBOL SymInfo;
+ rc = RTDbgModSymbolByName(hMod, aSymbols[i].pszSymbol, &SymInfo);
+ if (RT_SUCCESS(rc))
+ {
+ RT_BZERO(aSymbols[i].pvVar, aSymbols[i].cbHost);
+ Assert(aSymbols[i].cbHost >= aSymbols[i].cbGuest);
+ DBGFADDRESS Addr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr,
+ (RTGCPTR)SymInfo.Value + pThis->AddrKernelBase.FlatPtr),
+ aSymbols[i].pvVar, aSymbols[i].cbGuest);
+ if (RT_SUCCESS(rc))
+ continue;
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Reading '%s' at %RGv: %Rrc\n", aSymbols[i].pszSymbol, Addr.FlatPtr, rc));
+ }
+ else
+ LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Error looking up '%s': %Rrc\n", aSymbols[i].pszSymbol, rc));
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+
+ /*
+ * Some kernels don't expose the variables in kallsyms so we have to try disassemble
+ * some public helpers to get at the addresses.
+ *
+ * @todo: Maybe cache those values so we don't have to do the heavy work every time?
+ */
+ if (rc == VERR_NOT_FOUND)
+ {
+ idxFirst = 0;
+ idxNext = 0;
+ rc = dbgDiggerLinuxQueryLogBufferPtrs(pThis, pUVM, pVMM, hMod, &GCPtrLogBuf, &cbLogBuf);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Last resort, scan for a known value which should appear only once in the kernel log buffer
+ * and try to deduce the boundaries from there.
+ */
+ return dbgDiggerLinuxKrnlLogBufFindByNeedle(pThis, pUVM, pVMM, &GCPtrLogBuf, &cbLogBuf);
+ }
+ }
+
+ return dbgDiggerLinuxKrnLogBufferProcess(pThis, pUVM, pVMM, GCPtrLogBuf, cbLogBuf, idxFirst, idxNext,
+ fFlags, cMessages, pszBuf, cbBuf, pcbActual);
+}
+
+/**
+ * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog}
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, uint32_t fFlags,
+ uint32_t cMessages, char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ PDBGDIGGERLINUX pData = RT_FROM_MEMBER(pThis, DBGDIGGERLINUX, IDmesg);
+
+ if (cMessages < 1)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Resolve the symbols we need and read their values.
+ */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ RTDBGMOD hMod;
+ int rc = RTDbgAsModuleByName(hAs, "vmlinux", 0, &hMod);
+ RTDbgAsRelease(hAs);
+
+ size_t cbActual = 0;
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check whether the kernel log buffer is a simple char buffer or the newer
+ * record based implementation.
+ * The record based implementation was presumably introduced with kernel 3.4,
+ * see: http://thread.gmane.org/gmane.linux.kernel/1284184
+ */
+ if (dbgDiggerLinuxLogBufferIsAsciiBuffer(pData, pUVM, pVMM))
+ rc = dbgDiggerLinuxLogBufferQueryAscii(pData, pUVM, pVMM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual);
+ else
+ rc = dbgDiggerLinuxLogBufferQueryRecords(pData, pUVM, pVMM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual);
+
+ /* Release the module in any case. */
+ RTDbgModRelease(hMod);
+ }
+ else
+ {
+ /*
+ * For the record based kernel versions we have a last resort heuristic which doesn't
+ * require any symbols, try that here.
+ */
+ if (!dbgDiggerLinuxLogBufferIsAsciiBuffer(pData, pUVM, pVMM))
+ {
+ RTGCPTR GCPtrLogBuf = 0;
+ uint32_t cbLogBuf = 0;
+
+ rc = dbgDiggerLinuxKrnlLogBufFindByNeedle(pData, pUVM, pVMM, &GCPtrLogBuf, &cbLogBuf);
+ if (RT_SUCCESS(rc))
+ rc = dbgDiggerLinuxKrnLogBufferProcess(pData, pUVM, pVMM, GCPtrLogBuf, cbLogBuf, 0 /*idxFirst*/, 0 /*idxNext*/,
+ fFlags, cMessages, pszBuf, cbBuf, &cbActual);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+
+ if (RT_FAILURE(rc) && rc != VERR_BUFFER_OVERFLOW)
+ return rc;
+
+ if (pcbActual)
+ *pcbActual = cbActual;
+
+ /*
+ * All VBox strings are UTF-8 and bad things may in theory happen if we
+ * pass bad UTF-8 to code which assumes it's all valid. So, we enforce
+ * UTF-8 upon the guest kernel messages here even if they (probably) have
+ * no defined code set in reality.
+ */
+ if ( RT_SUCCESS(rc)
+ && cbActual <= cbBuf)
+ {
+ pszBuf[cbActual - 1] = '\0';
+ RTStrPurgeEncoding(pszBuf);
+ return VINF_SUCCESS;
+ }
+
+ if (cbBuf)
+ {
+ pszBuf[cbBuf - 1] = '\0';
+ RTStrPurgeEncoding(pszBuf);
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Worker destroying the config database.
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxCfgDbDestroyWorker(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PDBGDIGGERLINUXCFGITEM pCfgItem = (PDBGDIGGERLINUXCFGITEM)pStr;
+ RTStrFree((char *)pCfgItem->Core.pszString);
+ RTMemFree(pCfgItem);
+ NOREF(pvUser);
+ return 0;
+}
+
+
+/**
+ * Destroy the config database.
+ *
+ * @param pThis The Linux digger data.
+ */
+static void dbgDiggerLinuxCfgDbDestroy(PDBGDIGGERLINUX pThis)
+{
+ RTStrSpaceDestroy(&pThis->hCfgDb, dbgDiggerLinuxCfgDbDestroyWorker, NULL);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnStackUnwindAssist
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxStackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu,
+ PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx,
+ RTDBGAS hAs, uint64_t *puScratch)
+{
+ RT_NOREF(pUVM, pVMM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryInterface
+ */
+static DECLCALLBACK(void *) dbgDiggerLinuxQueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf)
+{
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+ RT_NOREF(pUVM, pVMM);
+
+ switch (enmIf)
+ {
+ case DBGFOSINTERFACE_DMESG:
+ return &pThis->IDmesg;
+
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryVersion
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxQueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData,
+ char *pszVersion, size_t cchVersion)
+{
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+ Assert(pThis->fValid);
+
+ /*
+ * It's all in the linux banner.
+ */
+ int rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &pThis->AddrLinuxBanner, pszVersion, cchVersion);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszEnd = RTStrEnd(pszVersion, cchVersion);
+ AssertReturn(pszEnd, VERR_BUFFER_OVERFLOW);
+ while ( pszEnd > pszVersion
+ && RT_C_IS_SPACE(pszEnd[-1]))
+ pszEnd--;
+ *pszEnd = '\0';
+ }
+ else
+ RTStrPrintf(pszVersion, cchVersion, "DBGFR3MemRead -> %Rrc", rc);
+
+ return rc;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnTerm
+ */
+static DECLCALLBACK(void) dbgDiggerLinuxTerm(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+ Assert(pThis->fValid);
+
+ /*
+ * Destroy configuration database.
+ */
+ dbgDiggerLinuxCfgDbDestroy(pThis);
+
+ /*
+ * Unlink and release our modules.
+ */
+ RTDBGAS hDbgAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hDbgAs != NIL_RTDBGAS)
+ {
+ uint32_t iMod = RTDbgAsModuleCount(hDbgAs);
+ while (iMod-- > 0)
+ {
+ RTDBGMOD hMod = RTDbgAsModuleByIndex(hDbgAs, iMod);
+ if (hMod != NIL_RTDBGMOD)
+ {
+ if (RTDbgModGetTag(hMod) == DIG_LNX_MOD_TAG)
+ {
+ int rc = RTDbgAsModuleUnlink(hDbgAs, hMod);
+ AssertRC(rc);
+ }
+ RTDbgModRelease(hMod);
+ }
+ }
+ RTDbgAsRelease(hDbgAs);
+ }
+
+ pThis->fValid = false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnRefresh
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxRefresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+ RT_NOREF(pThis);
+ Assert(pThis->fValid);
+
+ /*
+ * For now we'll flush and reload everything.
+ */
+ dbgDiggerLinuxTerm(pUVM, pVMM, pvData);
+ return dbgDiggerLinuxInit(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * Worker for dbgDiggerLinuxFindStartOfNamesAndSymbolCount that update the
+ * digger data.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pThis The Linux digger data to update.
+ * @param pVMM The VMM function table.
+ * @param pAddrKernelNames The kallsyms_names address.
+ * @param cKernelSymbols The number of kernel symbol.
+ * @param cbAddress The guest address size.
+ */
+static int dbgDiggerLinuxFoundStartOfNames(PDBGDIGGERLINUX pThis, PCVMMR3VTABLE pVMM, PCDBGFADDRESS pAddrKernelNames,
+ uint32_t cKernelSymbols, uint32_t cbAddress)
+{
+ pThis->cKernelSymbols = cKernelSymbols;
+ pThis->AddrKernelNames = *pAddrKernelNames;
+ pThis->AddrKernelAddresses = *pAddrKernelNames;
+ uint32_t cbSymbolsSkip = (pThis->fRelKrnlAddr ? 2 : 1) * cbAddress; /* Relative addressing introduces kallsyms_relative_base. */
+ uint32_t cbOffsets = pThis->fRelKrnlAddr ? sizeof(int32_t) : cbAddress; /* Offsets are always 32bits wide for relative addressing. */
+ uint32_t cbAlign = 0;
+
+ /*
+ * If the number of symbols is odd there is padding to align the following guest pointer
+ * sized data properly on 64bit systems with relative addressing.
+ */
+ if ( pThis->fRelKrnlAddr
+ && pThis->f64Bit
+ && (pThis->cKernelSymbols & 1))
+ cbAlign = sizeof(int32_t);
+ pVMM->pfnDBGFR3AddrSub(&pThis->AddrKernelAddresses, cKernelSymbols * cbOffsets + cbSymbolsSkip + cbAlign);
+
+ Log(("dbgDiggerLinuxFoundStartOfNames: AddrKernelAddresses=%RGv\n"
+ "dbgDiggerLinuxFoundStartOfNames: cKernelSymbols=%#x (at %RGv)\n"
+ "dbgDiggerLinuxFoundStartOfNames: AddrKernelName=%RGv\n",
+ pThis->AddrKernelAddresses.FlatPtr,
+ pThis->cKernelSymbols, pThis->AddrKernelNames.FlatPtr - cbAddress,
+ pThis->AddrKernelNames.FlatPtr));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to find the address of the kallsyms_names, kallsyms_num_syms and
+ * kallsyms_addresses symbols.
+ *
+ * The kallsyms_num_syms is read and stored in pThis->cKernelSymbols, while the
+ * addresses of the other two are stored as pThis->AddrKernelNames and
+ * pThis->AddrKernelAddresses.
+ *
+ * @returns VBox status code, success indicating that all three variables have
+ * been found and taken down.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ * @param pHitAddr An address we think is inside kallsyms_names.
+ */
+static int dbgDiggerLinuxFindStartOfNamesAndSymbolCount(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis,
+ PCDBGFADDRESS pHitAddr)
+{
+ /*
+ * Search backwards in chunks.
+ */
+ union
+ {
+ uint8_t ab[0x1000];
+ uint32_t au32[0x1000 / sizeof(uint32_t)];
+ uint64_t au64[0x1000 / sizeof(uint64_t)];
+ } uBuf;
+ uint32_t cbLeft = LNX_MAX_KALLSYMS_NAMES_SIZE;
+ uint32_t cbBuf = pHitAddr->FlatPtr & (sizeof(uBuf) - 1);
+ DBGFADDRESS CurAddr = *pHitAddr;
+ pVMM->pfnDBGFR3AddrSub(&CurAddr, cbBuf);
+ cbBuf += sizeof(uint64_t) - 1; /* In case our kobj hit is in the first 4/8 bytes. */
+ for (;;)
+ {
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &CurAddr, &uBuf, sizeof(uBuf));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Since Linux 4.6 there are two different methods to store the kallsyms addresses
+ * in the image.
+ *
+ * The first and longer existing method is to store the absolute addresses in an
+ * array starting at kallsyms_addresses followed by a field which stores the number
+ * of kernel symbols called kallsyms_num_syms.
+ * The newer method is to use offsets stored in kallsyms_offsets and have a base pointer
+ * to relate the offsets to called kallsyms_relative_base. One entry in kallsyms_offsets is
+ * always 32bit wide regardless of the guest pointer size (this halves the table on 64bit
+ * systems) but means more work for us for the 64bit case.
+ *
+ * When absolute addresses are used the following assumptions hold:
+ *
+ * We assume that the three symbols are aligned on guest pointer boundary.
+ *
+ * The boundary between the two tables should be noticable as the number
+ * is unlikely to be more than 16 millions, there will be at least one zero
+ * byte where it is, 64-bit will have 5 zero bytes. Zero bytes aren't all
+ * that common in the kallsyms_names table.
+ *
+ * Also the kallsyms_names table starts with a length byte, which means
+ * we're likely to see a byte in the range 1..31.
+ *
+ * The kallsyms_addresses are mostly sorted (except for the start where the
+ * absolute symbols are), so we'll spot a bunch of kernel addresses
+ * immediately preceeding the kallsyms_num_syms field.
+ *
+ * Lazy bird: If kallsyms_num_syms is on a buffer boundrary, we skip
+ * the check for kernel addresses preceeding it.
+ *
+ * For relative offsets most of the assumptions from above are true too
+ * except that we have to distinguish between the relative base address and the offsets.
+ * Every observed kernel has a valid kernel address fo the relative base and kallsyms_relative_base
+ * always comes before kallsyms_num_syms and is aligned on a guest pointer boundary.
+ * Offsets are stored before kallsyms_relative_base and don't contain valid kernel addresses.
+ *
+ * To distinguish between absolute and relative offsetting we check the data before a candidate
+ * for kallsyms_num_syms. If all entries before the kallsyms_num_syms candidate are valid kernel
+ * addresses absolute addresses are assumed. If this is not the case but the first entry before
+ * kallsyms_num_syms is a valid kernel address we check whether the data before and the possible
+ * relative base form a valid kernel address and assume relative offsets.
+ *
+ * Other notable changes between various Linux kernel versions:
+ *
+ * 4.20.0+: Commit 80ffbaa5b1bd98e80e3239a3b8cfda2da433009a made kallsyms_num_syms 32bit
+ * even on 64bit systems but the alignment of the variables makes the code below work for now
+ * (tested with a 5.4 and 5.12 kernel) do we keep it that way to avoid making the code even
+ * messy.
+ */
+ if (pThis->f64Bit)
+ {
+ uint32_t i = cbBuf / sizeof(uint64_t) - 1;
+ while (i-- > 0)
+ if ( uBuf.au64[i] <= LNX_MAX_KALLSYMS_SYMBOLS
+ && uBuf.au64[i] >= LNX_MIN_KALLSYMS_SYMBOLS)
+ {
+ uint8_t *pb = (uint8_t *)&uBuf.au64[i + 1];
+ if ( pb[0] <= LNX_MAX_KALLSYMS_ENC_LENGTH
+ && pb[0] >= LNX_MIN_KALLSYMS_ENC_LENGTH)
+ {
+ /*
+ * Check whether we have a valid kernel address and try to distinguish
+ * whether the kernel uses relative offsetting or absolute addresses.
+ */
+ if ( (i >= 1 && LNX64_VALID_ADDRESS(uBuf.au64[i - 1]))
+ && (i >= 2 && !LNX64_VALID_ADDRESS(uBuf.au64[i - 2]))
+ && (i >= 3 && !LNX64_VALID_ADDRESS(uBuf.au64[i - 3])))
+ {
+ RTGCUINTPTR uKrnlRelBase = uBuf.au64[i - 1];
+ DBGFADDRESS RelAddr = CurAddr;
+ int32_t aiRelOff[3];
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrAdd(&RelAddr,
+ (i - 1) * sizeof(uint64_t) - sizeof(aiRelOff)),
+ &aiRelOff[0], sizeof(aiRelOff));
+ if ( RT_SUCCESS(rc)
+ && LNX64_VALID_ADDRESS(uKrnlRelBase + aiRelOff[0])
+ && LNX64_VALID_ADDRESS(uKrnlRelBase + aiRelOff[1])
+ && LNX64_VALID_ADDRESS(uKrnlRelBase + aiRelOff[2]))
+ {
+ Log(("dbgDiggerLinuxFindStartOfNamesAndSymbolCount: relative base %RGv (at %RGv)\n",
+ uKrnlRelBase, CurAddr.FlatPtr + (i - 1) * sizeof(uint64_t)));
+ pThis->fRelKrnlAddr = true;
+ pThis->uKernelRelativeBase = uKrnlRelBase;
+ return dbgDiggerLinuxFoundStartOfNames(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, (i + 1) * sizeof(uint64_t)),
+ (uint32_t)uBuf.au64[i], sizeof(uint64_t));
+ }
+ }
+
+ if ( (i <= 0 || LNX64_VALID_ADDRESS(uBuf.au64[i - 1]))
+ && (i <= 1 || LNX64_VALID_ADDRESS(uBuf.au64[i - 2]))
+ && (i <= 2 || LNX64_VALID_ADDRESS(uBuf.au64[i - 3])))
+ return dbgDiggerLinuxFoundStartOfNames(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, (i + 1) * sizeof(uint64_t)),
+ (uint32_t)uBuf.au64[i], sizeof(uint64_t));
+ }
+ }
+ }
+ else
+ {
+ uint32_t i = cbBuf / sizeof(uint32_t) - 1;
+ while (i-- > 0)
+ if ( uBuf.au32[i] <= LNX_MAX_KALLSYMS_SYMBOLS
+ && uBuf.au32[i] >= LNX_MIN_KALLSYMS_SYMBOLS)
+ {
+ uint8_t *pb = (uint8_t *)&uBuf.au32[i + 1];
+ if ( pb[0] <= LNX_MAX_KALLSYMS_ENC_LENGTH
+ && pb[0] >= LNX_MIN_KALLSYMS_ENC_LENGTH)
+ {
+ /* Check for relative base addressing. */
+ if (i >= 1 && LNX32_VALID_ADDRESS(uBuf.au32[i - 1]))
+ {
+ RTGCUINTPTR uKrnlRelBase = uBuf.au32[i - 1];
+ if ( (i <= 1 || LNX32_VALID_ADDRESS(uKrnlRelBase + uBuf.au32[i - 2]))
+ && (i <= 2 || LNX32_VALID_ADDRESS(uKrnlRelBase + uBuf.au32[i - 3])))
+ {
+ Log(("dbgDiggerLinuxFindStartOfNamesAndSymbolCount: relative base %RGv (at %RGv)\n",
+ uKrnlRelBase, CurAddr.FlatPtr + (i - 1) * sizeof(uint32_t)));
+ pThis->fRelKrnlAddr = true;
+ pThis->uKernelRelativeBase = uKrnlRelBase;
+ return dbgDiggerLinuxFoundStartOfNames(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, (i + 1) * sizeof(uint32_t)),
+ uBuf.au32[i], sizeof(uint32_t));
+ }
+ }
+
+ if ( (i <= 0 || LNX32_VALID_ADDRESS(uBuf.au32[i - 1]))
+ && (i <= 1 || LNX32_VALID_ADDRESS(uBuf.au32[i - 2]))
+ && (i <= 2 || LNX32_VALID_ADDRESS(uBuf.au32[i - 3])))
+ return dbgDiggerLinuxFoundStartOfNames(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, (i + 1) * sizeof(uint32_t)),
+ uBuf.au32[i], sizeof(uint32_t));
+ }
+ }
+ }
+
+ /*
+ * Advance
+ */
+ if (RT_UNLIKELY(cbLeft <= sizeof(uBuf)))
+ {
+ Log(("dbgDiggerLinuxFindStartOfNamesAndSymbolCount: failed (pHitAddr=%RGv)\n", pHitAddr->FlatPtr));
+ return VERR_NOT_FOUND;
+ }
+ cbLeft -= sizeof(uBuf);
+ pVMM->pfnDBGFR3AddrSub(&CurAddr, sizeof(uBuf));
+ cbBuf = sizeof(uBuf);
+ }
+}
+
+
+/**
+ * Worker for dbgDiggerLinuxFindEndNames that records the findings.
+ *
+ * @returns VINF_SUCCESS
+ * @param pThis The linux digger data to update.
+ * @param pVMM The VMM function table.
+ * @param pAddrMarkers The address of the marker (kallsyms_markers).
+ * @param cbMarkerEntry The size of a marker entry (32-bit or 64-bit).
+ */
+static int dbgDiggerLinuxFoundMarkers(PDBGDIGGERLINUX pThis, PCVMMR3VTABLE pVMM,
+ PCDBGFADDRESS pAddrMarkers, uint32_t cbMarkerEntry)
+{
+ pThis->cbKernelNames = pAddrMarkers->FlatPtr - pThis->AddrKernelNames.FlatPtr;
+ pThis->AddrKernelNameMarkers = *pAddrMarkers;
+ pThis->cKernelNameMarkers = RT_ALIGN_32(pThis->cKernelSymbols, 256) / 256;
+ pThis->AddrKernelTokenTable = *pAddrMarkers;
+ pVMM->pfnDBGFR3AddrAdd(&pThis->AddrKernelTokenTable, pThis->cKernelNameMarkers * cbMarkerEntry);
+
+ Log(("dbgDiggerLinuxFoundMarkers: AddrKernelNames=%RGv cbKernelNames=%#x\n"
+ "dbgDiggerLinuxFoundMarkers: AddrKernelNameMarkers=%RGv cKernelNameMarkers=%#x\n"
+ "dbgDiggerLinuxFoundMarkers: AddrKernelTokenTable=%RGv\n",
+ pThis->AddrKernelNames.FlatPtr, pThis->cbKernelNames,
+ pThis->AddrKernelNameMarkers.FlatPtr, pThis->cKernelNameMarkers,
+ pThis->AddrKernelTokenTable.FlatPtr));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to find the end of kallsyms_names and thereby the start of
+ * kallsyms_markers and kallsyms_token_table.
+ *
+ * The kallsyms_names size is stored in pThis->cbKernelNames, the addresses of
+ * the two other symbols in pThis->AddrKernelNameMarkers and
+ * pThis->AddrKernelTokenTable. The number of marker entries is stored in
+ * pThis->cKernelNameMarkers.
+ *
+ * @returns VBox status code, success indicating that all three variables have
+ * been found and taken down.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ * @param pHitAddr An address we think is inside kallsyms_names.
+ */
+static int dbgDiggerLinuxFindEndOfNamesAndMore(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis, PCDBGFADDRESS pHitAddr)
+{
+ /*
+ * Search forward in chunks.
+ */
+ union
+ {
+ uint8_t ab[0x1000];
+ uint32_t au32[0x1000 / sizeof(uint32_t)];
+ uint64_t au64[0x1000 / sizeof(uint64_t)];
+ } uBuf;
+ bool fPendingZeroHit = false;
+ uint32_t cbLeft = LNX_MAX_KALLSYMS_NAMES_SIZE + sizeof(uBuf);
+ uint32_t offBuf = pHitAddr->FlatPtr & (sizeof(uBuf) - 1);
+ DBGFADDRESS CurAddr = *pHitAddr;
+ pVMM->pfnDBGFR3AddrSub(&CurAddr, offBuf);
+ for (;;)
+ {
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &CurAddr, &uBuf, sizeof(uBuf));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * The kallsyms_names table is followed by kallsyms_markers we assume,
+ * using sizeof(unsigned long) alignment like the preceeding symbols.
+ *
+ * The kallsyms_markers table has entried sizeof(unsigned long) and
+ * contains offsets into kallsyms_names. The kallsyms_markers used to
+ * index kallsyms_names and reduce seek time when looking up the name
+ * of an address/symbol. Each entry in kallsyms_markers covers 256
+ * symbol names.
+ *
+ * Because of this, the first entry is always zero and all the entries
+ * are ascending. It also follows that the size of the table can be
+ * calculated from kallsyms_num_syms.
+ *
+ * Note! We could also have walked kallsyms_names by skipping
+ * kallsyms_num_syms names, but this is faster and we will
+ * validate the encoded names later.
+ *
+ * git commit 80ffbaa5b1bd98e80e3239a3b8cfda2da433009a (which became 4.20+) makes kallsyms_markers
+ * and kallsyms_num_syms uint32_t, even on 64bit systems. Take that into account.
+ */
+ if ( pThis->f64Bit
+ && pThis->uKrnlVer < LNX_MK_VER(4, 20, 0))
+ {
+ if ( RT_UNLIKELY(fPendingZeroHit)
+ && uBuf.au64[0] >= (LNX_MIN_KALLSYMS_ENC_LENGTH + 1) * 256
+ && uBuf.au64[0] <= (LNX_MAX_KALLSYMS_ENC_LENGTH + 1) * 256)
+ return dbgDiggerLinuxFoundMarkers(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrSub(&CurAddr, sizeof(uint64_t)), sizeof(uint64_t));
+
+ uint32_t const cEntries = sizeof(uBuf) / sizeof(uint64_t);
+ for (uint32_t i = offBuf / sizeof(uint64_t); i < cEntries; i++)
+ if (uBuf.au64[i] == 0)
+ {
+ if (RT_UNLIKELY(i + 1 >= cEntries))
+ {
+ fPendingZeroHit = true;
+ break;
+ }
+ if ( uBuf.au64[i + 1] >= (LNX_MIN_KALLSYMS_ENC_LENGTH + 1) * 256
+ && uBuf.au64[i + 1] <= (LNX_MAX_KALLSYMS_ENC_LENGTH + 1) * 256)
+ return dbgDiggerLinuxFoundMarkers(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, i * sizeof(uint64_t)), sizeof(uint64_t));
+ }
+ }
+ else
+ {
+ if ( RT_UNLIKELY(fPendingZeroHit)
+ && uBuf.au32[0] >= (LNX_MIN_KALLSYMS_ENC_LENGTH + 1) * 256
+ && uBuf.au32[0] <= (LNX_MAX_KALLSYMS_ENC_LENGTH + 1) * 256)
+ return dbgDiggerLinuxFoundMarkers(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrSub(&CurAddr, sizeof(uint32_t)), sizeof(uint32_t));
+
+ uint32_t const cEntries = sizeof(uBuf) / sizeof(uint32_t);
+ for (uint32_t i = offBuf / sizeof(uint32_t); i < cEntries; i++)
+ if (uBuf.au32[i] == 0)
+ {
+ if (RT_UNLIKELY(i + 1 >= cEntries))
+ {
+ fPendingZeroHit = true;
+ break;
+ }
+ if ( uBuf.au32[i + 1] >= (LNX_MIN_KALLSYMS_ENC_LENGTH + 1) * 256
+ && uBuf.au32[i + 1] <= (LNX_MAX_KALLSYMS_ENC_LENGTH + 1) * 256)
+ return dbgDiggerLinuxFoundMarkers(pThis, pVMM,
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, i * sizeof(uint32_t)), sizeof(uint32_t));
+ }
+ }
+
+ /*
+ * Advance
+ */
+ if (RT_UNLIKELY(cbLeft <= sizeof(uBuf)))
+ {
+ Log(("dbgDiggerLinuxFindEndOfNamesAndMore: failed (pHitAddr=%RGv)\n", pHitAddr->FlatPtr));
+ return VERR_NOT_FOUND;
+ }
+ cbLeft -= sizeof(uBuf);
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, sizeof(uBuf));
+ offBuf = 0;
+ }
+}
+
+
+/**
+ * Locates the kallsyms_token_index table.
+ *
+ * Storing the address in pThis->AddrKernelTokenIndex and the size of the token
+ * table in pThis->cbKernelTokenTable.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ */
+static int dbgDiggerLinuxFindTokenIndex(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis)
+{
+ /*
+ * The kallsyms_token_table is very much like a string table. Due to the
+ * nature of the compression algorithm it is reasonably short (one example
+ * here is 853 bytes), so we'll not be reading it in chunks but in full.
+ * To be on the safe side, we read 8KB, ASSUMING we won't run into unmapped
+ * memory or any other nasty stuff...
+ */
+ union
+ {
+ uint8_t ab[0x2000];
+ uint16_t au16[0x2000 / sizeof(uint16_t)];
+ } uBuf;
+ DBGFADDRESS CurAddr = pThis->AddrKernelTokenTable;
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &CurAddr, &uBuf, sizeof(uBuf));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * We've got two choices here, either walk the string table or look for
+ * the next structure, kallsyms_token_index.
+ *
+ * The token index is a table of 256 uint16_t entries (index by bytes
+ * from kallsyms_names) that gives offsets in kallsyms_token_table. It
+ * starts with a zero entry and the following entries are sorted in
+ * ascending order. The range of the entries are reasonably small since
+ * kallsyms_token_table is small.
+ *
+ * The alignment seems to be sizeof(unsigned long), just like
+ * kallsyms_token_table.
+ *
+ * So, we start by looking for a zero 16-bit entry.
+ */
+ uint32_t cIncr = (pThis->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t)) / sizeof(uint16_t);
+
+ for (uint32_t i = 0; i < sizeof(uBuf) / sizeof(uint16_t) - 16; i += cIncr)
+ if ( uBuf.au16[i] == 0
+ && uBuf.au16[i + 1] > 0
+ && uBuf.au16[i + 1] <= LNX_MAX_KALLSYMS_TOKEN_LEN
+ && (uint16_t)(uBuf.au16[i + 2] - uBuf.au16[i + 1] - 1U) <= (uint16_t)LNX_MAX_KALLSYMS_TOKEN_LEN
+ && (uint16_t)(uBuf.au16[i + 3] - uBuf.au16[i + 2] - 1U) <= (uint16_t)LNX_MAX_KALLSYMS_TOKEN_LEN
+ && (uint16_t)(uBuf.au16[i + 4] - uBuf.au16[i + 3] - 1U) <= (uint16_t)LNX_MAX_KALLSYMS_TOKEN_LEN
+ && (uint16_t)(uBuf.au16[i + 5] - uBuf.au16[i + 4] - 1U) <= (uint16_t)LNX_MAX_KALLSYMS_TOKEN_LEN
+ && (uint16_t)(uBuf.au16[i + 6] - uBuf.au16[i + 5] - 1U) <= (uint16_t)LNX_MAX_KALLSYMS_TOKEN_LEN
+ )
+ {
+ pThis->AddrKernelTokenIndex = CurAddr;
+ pVMM->pfnDBGFR3AddrAdd(&pThis->AddrKernelTokenIndex, i * sizeof(uint16_t));
+ pThis->cbKernelTokenTable = i * sizeof(uint16_t);
+ return VINF_SUCCESS;
+ }
+
+ Log(("dbgDiggerLinuxFindTokenIndex: Failed (%RGv..%RGv)\n", CurAddr.FlatPtr, CurAddr.FlatPtr + (RTGCUINTPTR)sizeof(uBuf)));
+ return VERR_NOT_FOUND;
+}
+
+
+/**
+ * Loads the kernel symbols from the given kallsyms offset table decoding the symbol names
+ * (worker common for dbgDiggerLinuxLoadKernelSymbolsAbsolute() and dbgDiggerLinuxLoadKernelSymbolsRelative()).
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ * @param uKernelStart Flat kernel start address.
+ * @param cbKernel Size of the kernel in bytes.
+ * @param pauSymOff Pointer to the array of symbol offsets in the kallsyms table
+ * relative to the start of the kernel.
+ */
+static int dbgDiggerLinuxLoadKernelSymbolsWorker(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis, RTGCUINTPTR uKernelStart,
+ RTGCUINTPTR cbKernel, RTGCUINTPTR *pauSymOff)
+{
+ uint8_t *pbNames = (uint8_t *)RTMemAllocZ(pThis->cbKernelNames);
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelNames, pbNames, pThis->cbKernelNames);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszzTokens = (char *)RTMemAllocZ(pThis->cbKernelTokenTable);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelTokenTable, pszzTokens, pThis->cbKernelTokenTable);
+ if (RT_SUCCESS(rc))
+ {
+ uint16_t *paoffTokens = (uint16_t *)RTMemAllocZ(256 * sizeof(uint16_t));
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelTokenIndex, paoffTokens, 256 * sizeof(uint16_t));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a module for the kernel.
+ */
+ RTDBGMOD hMod;
+ rc = RTDbgModCreate(&hMod, "vmlinux", cbKernel, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDbgModSetTag(hMod, DIG_LNX_MOD_TAG); AssertRC(rc);
+ rc = VINF_SUCCESS;
+
+ /*
+ * Enumerate the symbols.
+ */
+ uint32_t offName = 0;
+ uint32_t cLeft = pThis->cKernelSymbols;
+ while (cLeft-- > 0 && RT_SUCCESS(rc))
+ {
+ /* Decode the symbol name first. */
+ if (RT_LIKELY(offName < pThis->cbKernelNames))
+ {
+ uint8_t cbName = pbNames[offName++];
+ if (RT_LIKELY(offName + cbName <= pThis->cbKernelNames))
+ {
+ char szSymbol[4096];
+ uint32_t offSymbol = 0;
+ while (cbName-- > 0)
+ {
+ uint8_t bEnc = pbNames[offName++];
+ uint16_t offToken = paoffTokens[bEnc];
+ if (RT_LIKELY(offToken < pThis->cbKernelTokenTable))
+ {
+ const char *pszToken = &pszzTokens[offToken];
+ char ch;
+ while ((ch = *pszToken++) != '\0')
+ if (offSymbol < sizeof(szSymbol) - 1)
+ szSymbol[offSymbol++] = ch;
+ }
+ else
+ {
+ rc = VERR_INVALID_UTF8_ENCODING;
+ break;
+ }
+ }
+ szSymbol[offSymbol < sizeof(szSymbol) ? offSymbol : sizeof(szSymbol) - 1] = '\0';
+
+ /* The offset. */
+ RTGCUINTPTR uSymOff = *pauSymOff;
+ pauSymOff++;
+
+ /* Add it without the type char. */
+ if (uSymOff <= cbKernel)
+ {
+ rc = RTDbgModSymbolAdd(hMod, &szSymbol[1], RTDBGSEGIDX_RVA, uSymOff,
+ 0 /*cb*/, 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ {
+ if ( rc == VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE
+ || rc == VERR_DBG_INVALID_RVA
+ || rc == VERR_DBG_ADDRESS_CONFLICT
+ || rc == VERR_DBG_DUPLICATE_SYMBOL)
+ {
+ Log2(("dbgDiggerLinuxLoadKernelSymbols: RTDbgModSymbolAdd(,%s,) failed %Rrc (ignored)\n", szSymbol, rc));
+ rc = VINF_SUCCESS;
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbols: RTDbgModSymbolAdd(,%s,) failed %Rrc\n", szSymbol, rc));
+ }
+ }
+ }
+ else
+ {
+ rc = VERR_END_OF_STRING;
+ Log(("dbgDiggerLinuxLoadKernelSymbols: offName=%#x cLeft=%#x cbName=%#x cbKernelNames=%#x\n",
+ offName, cLeft, cbName, pThis->cbKernelNames));
+ }
+ }
+ else
+ {
+ rc = VERR_END_OF_STRING;
+ Log(("dbgDiggerLinuxLoadKernelSymbols: offName=%#x cLeft=%#x cbKernelNames=%#x\n",
+ offName, cLeft, pThis->cbKernelNames));
+ }
+ }
+
+ /*
+ * Link the module into the address space.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hAs != NIL_RTDBGAS)
+ rc = RTDbgAsModuleLink(hAs, hMod, uKernelStart, RTDBGASLINK_FLAGS_REPLACE);
+ else
+ rc = VERR_INTERNAL_ERROR;
+ RTDbgAsRelease(hAs);
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbols: Failed: %Rrc\n", rc));
+ RTDbgModRelease(hMod);
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbols: RTDbgModCreate failed: %Rrc\n", rc));
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbols: Reading token index at %RGv failed: %Rrc\n",
+ pThis->AddrKernelTokenIndex.FlatPtr, rc));
+ RTMemFree(paoffTokens);
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbols: Reading token table at %RGv failed: %Rrc\n",
+ pThis->AddrKernelTokenTable.FlatPtr, rc));
+ RTMemFree(pszzTokens);
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbols: Reading encoded names at %RGv failed: %Rrc\n",
+ pThis->AddrKernelNames.FlatPtr, rc));
+ RTMemFree(pbNames);
+
+ return rc;
+}
+
+/**
+ * Loads the kernel symbols from the kallsyms table if it contains absolute addresses
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ */
+static int dbgDiggerLinuxLoadKernelSymbolsAbsolute(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis)
+{
+ /*
+ * Allocate memory for temporary table copies, reading the tables as we go.
+ */
+ uint32_t const cbGuestAddr = pThis->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
+ void *pvAddresses = RTMemAllocZ(pThis->cKernelSymbols * cbGuestAddr);
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelAddresses,
+ pvAddresses, pThis->cKernelSymbols * cbGuestAddr);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Figure out the kernel start and end and convert the absolute addresses to relative offsets.
+ */
+ RTGCUINTPTR uKernelStart = pThis->AddrKernelAddresses.FlatPtr;
+ RTGCUINTPTR uKernelEnd = pThis->AddrKernelTokenIndex.FlatPtr + 256 * sizeof(uint16_t);
+ RTGCUINTPTR *pauSymOff = (RTGCUINTPTR *)RTMemTmpAllocZ(pThis->cKernelSymbols * sizeof(RTGCUINTPTR));
+ uint32_t i;
+ if (cbGuestAddr == sizeof(uint64_t))
+ {
+ uint64_t *pauAddrs = (uint64_t *)pvAddresses;
+ for (i = 0; i < pThis->cKernelSymbols; i++)
+ if ( pauAddrs[i] < uKernelStart
+ && LNX64_VALID_ADDRESS(pauAddrs[i])
+ && uKernelStart - pauAddrs[i] < LNX_MAX_KERNEL_SIZE)
+ uKernelStart = pauAddrs[i];
+
+ for (i = pThis->cKernelSymbols - 1; i > 0; i--)
+ if ( pauAddrs[i] > uKernelEnd
+ && LNX64_VALID_ADDRESS(pauAddrs[i])
+ && pauAddrs[i] - uKernelEnd < LNX_MAX_KERNEL_SIZE)
+ uKernelEnd = pauAddrs[i];
+
+ for (i = 0; i < pThis->cKernelSymbols; i++)
+ pauSymOff[i] = pauAddrs[i] - uKernelStart;
+ }
+ else
+ {
+ uint32_t *pauAddrs = (uint32_t *)pvAddresses;
+ for (i = 0; i < pThis->cKernelSymbols; i++)
+ if ( pauAddrs[i] < uKernelStart
+ && LNX32_VALID_ADDRESS(pauAddrs[i])
+ && uKernelStart - pauAddrs[i] < LNX_MAX_KERNEL_SIZE)
+ uKernelStart = pauAddrs[i];
+
+ for (i = pThis->cKernelSymbols - 1; i > 0; i--)
+ if ( pauAddrs[i] > uKernelEnd
+ && LNX32_VALID_ADDRESS(pauAddrs[i])
+ && pauAddrs[i] - uKernelEnd < LNX_MAX_KERNEL_SIZE)
+ uKernelEnd = pauAddrs[i];
+
+ for (i = 0; i < pThis->cKernelSymbols; i++)
+ pauSymOff[i] = pauAddrs[i] - uKernelStart;
+ }
+
+ RTGCUINTPTR cbKernel = uKernelEnd - uKernelStart;
+ pThis->cbKernel = (uint32_t)cbKernel;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelBase, uKernelStart);
+ Log(("dbgDiggerLinuxLoadKernelSymbolsAbsolute: uKernelStart=%RGv cbKernel=%#x\n", uKernelStart, cbKernel));
+
+ rc = dbgDiggerLinuxLoadKernelSymbolsWorker(pUVM, pVMM, pThis, uKernelStart, cbKernel, pauSymOff);
+ if (RT_FAILURE(rc))
+ Log(("dbgDiggerLinuxLoadKernelSymbolsAbsolute: Loading symbols from given offset table failed: %Rrc\n", rc));
+ RTMemTmpFree(pauSymOff);
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbolsAbsolute: Reading symbol addresses at %RGv failed: %Rrc\n",
+ pThis->AddrKernelAddresses.FlatPtr, rc));
+ RTMemFree(pvAddresses);
+
+ return rc;
+}
+
+
+/**
+ * Loads the kernel symbols from the kallsyms table if it contains absolute addresses
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ */
+static int dbgDiggerLinuxLoadKernelSymbolsRelative(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis)
+{
+ /*
+ * Allocate memory for temporary table copies, reading the tables as we go.
+ */
+ int32_t *pai32Offsets = (int32_t *)RTMemAllocZ(pThis->cKernelSymbols * sizeof(int32_t));
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelAddresses,
+ pai32Offsets, pThis->cKernelSymbols * sizeof(int32_t));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Figure out the kernel start and end and convert the absolute addresses to relative offsets.
+ */
+ RTGCUINTPTR uKernelStart = pThis->AddrKernelAddresses.FlatPtr;
+ RTGCUINTPTR uKernelEnd = pThis->AddrKernelTokenIndex.FlatPtr + 256 * sizeof(uint16_t);
+ RTGCUINTPTR *pauSymOff = (RTGCUINTPTR *)RTMemTmpAllocZ(pThis->cKernelSymbols * sizeof(RTGCUINTPTR));
+ uint32_t i;
+
+ for (i = 0; i < pThis->cKernelSymbols; i++)
+ {
+ RTGCUINTPTR uSymAddr = dbgDiggerLinuxConvOffsetToAddr(pThis, pai32Offsets[i]);
+
+ if ( uSymAddr < uKernelStart
+ && (pThis->f64Bit ? LNX64_VALID_ADDRESS(uSymAddr) : LNX32_VALID_ADDRESS(uSymAddr))
+ && uKernelStart - uSymAddr < LNX_MAX_KERNEL_SIZE)
+ uKernelStart = uSymAddr;
+ }
+
+ for (i = pThis->cKernelSymbols - 1; i > 0; i--)
+ {
+ RTGCUINTPTR uSymAddr = dbgDiggerLinuxConvOffsetToAddr(pThis, pai32Offsets[i]);
+
+ if ( uSymAddr > uKernelEnd
+ && (pThis->f64Bit ? LNX64_VALID_ADDRESS(uSymAddr) : LNX32_VALID_ADDRESS(uSymAddr))
+ && uSymAddr - uKernelEnd < LNX_MAX_KERNEL_SIZE)
+ uKernelEnd = uSymAddr;
+
+ /* Store the offset from the derived kernel start address. */
+ pauSymOff[i] = uSymAddr - uKernelStart;
+ }
+
+ RTGCUINTPTR cbKernel = uKernelEnd - uKernelStart;
+ pThis->cbKernel = (uint32_t)cbKernel;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelBase, uKernelStart);
+ Log(("dbgDiggerLinuxLoadKernelSymbolsRelative: uKernelStart=%RGv cbKernel=%#x\n", uKernelStart, cbKernel));
+
+ rc = dbgDiggerLinuxLoadKernelSymbolsWorker(pUVM, pVMM, pThis, uKernelStart, cbKernel, pauSymOff);
+ if (RT_FAILURE(rc))
+ Log(("dbgDiggerLinuxLoadKernelSymbolsRelative: Loading symbols from given offset table failed: %Rrc\n", rc));
+ RTMemTmpFree(pauSymOff);
+ }
+ else
+ Log(("dbgDiggerLinuxLoadKernelSymbolsRelative: Reading symbol addresses at %RGv failed: %Rrc\n",
+ pThis->AddrKernelAddresses.FlatPtr, rc));
+ RTMemFree(pai32Offsets);
+
+ return rc;
+}
+
+
+/**
+ * Loads the kernel symbols.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis The Linux digger data.
+ */
+static int dbgDiggerLinuxLoadKernelSymbols(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERLINUX pThis)
+{
+ /*
+ * First the kernel itself.
+ */
+ if (pThis->fRelKrnlAddr)
+ return dbgDiggerLinuxLoadKernelSymbolsRelative(pUVM, pVMM, pThis);
+ return dbgDiggerLinuxLoadKernelSymbolsAbsolute(pUVM, pVMM, pThis);
+}
+
+
+/*
+ * The module structure changed it was easier to produce different code for
+ * each version of the structure. The C preprocessor rules!
+ */
+#define LNX_TEMPLATE_HEADER "DBGPlugInLinuxModuleCodeTmpl.cpp.h"
+
+#define LNX_BIT_SUFFIX _amd64
+#define LNX_PTR_T uint64_t
+#define LNX_64BIT 1
+#include "DBGPlugInLinuxModuleVerTmpl.cpp.h"
+
+#define LNX_BIT_SUFFIX _x86
+#define LNX_PTR_T uint32_t
+#define LNX_64BIT 0
+#include "DBGPlugInLinuxModuleVerTmpl.cpp.h"
+
+#undef LNX_TEMPLATE_HEADER
+
+static const struct
+{
+ uint32_t uVersion;
+ bool f64Bit;
+ uint64_t (*pfnProcessModule)(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGFADDRESS pAddrModule);
+} g_aModVersions[] =
+{
+#define LNX_TEMPLATE_HEADER "DBGPlugInLinuxModuleTableEntryTmpl.cpp.h"
+
+#define LNX_BIT_SUFFIX _amd64
+#define LNX_64BIT 1
+#include "DBGPlugInLinuxModuleVerTmpl.cpp.h"
+
+#define LNX_BIT_SUFFIX _x86
+#define LNX_64BIT 0
+#include "DBGPlugInLinuxModuleVerTmpl.cpp.h"
+
+#undef LNX_TEMPLATE_HEADER
+};
+
+
+/**
+ * Tries to find and process the module list.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ */
+static int dbgDiggerLinuxLoadModules(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ /*
+ * Locate the list head.
+ */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ RTDBGSYMBOL SymInfo;
+ int rc = RTDbgAsSymbolByName(hAs, "vmlinux!modules", &SymInfo, NULL);
+ RTDbgAsRelease(hAs);
+ if (RT_FAILURE(rc))
+ return VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerLinuxLoadModules: Failed to locate the module list (%Rrc).\n", rc));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Read the list anchor.
+ */
+ union
+ {
+ uint32_t volatile u32Pair[2];
+ uint64_t u64Pair[2];
+ } uListAnchor;
+ DBGFADDRESS Addr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SymInfo.Value),
+ &uListAnchor, pThis->f64Bit ? sizeof(uListAnchor.u64Pair) : sizeof(uListAnchor.u32Pair));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("dbgDiggerLinuxLoadModules: Error reading list anchor at %RX64: %Rrc\n", SymInfo.Value, rc));
+ return VERR_NOT_FOUND;
+ }
+ if (!pThis->f64Bit)
+ {
+ uListAnchor.u64Pair[1] = uListAnchor.u32Pair[1];
+ ASMCompilerBarrier();
+ uListAnchor.u64Pair[0] = uListAnchor.u32Pair[0];
+ }
+
+ if (pThis->uKrnlVer == 0)
+ {
+ LogRel(("dbgDiggerLinuxLoadModules: No valid kernel version given: %#x\n", pThis->uKrnlVer));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Find the g_aModVersion entry that fits the best.
+ * ASSUMES strict descending order by bitcount and version.
+ */
+ Assert(g_aModVersions[0].f64Bit == true);
+ unsigned i = 0;
+ if (!pThis->f64Bit)
+ while (i < RT_ELEMENTS(g_aModVersions) && g_aModVersions[i].f64Bit)
+ i++;
+ while ( i < RT_ELEMENTS(g_aModVersions)
+ && g_aModVersions[i].f64Bit == pThis->f64Bit
+ && pThis->uKrnlVer < g_aModVersions[i].uVersion)
+ i++;
+ if (i >= RT_ELEMENTS(g_aModVersions))
+ {
+ LogRel(("dbgDiggerLinuxLoadModules: Failed to find anything matching version: %u.%u.%u\n",
+ pThis->uKrnlVerMaj, pThis->uKrnlVerMin, pThis->uKrnlVerBld));
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Walk the list.
+ */
+ uint64_t uModAddr = uListAnchor.u64Pair[0];
+ for (size_t iModule = 0; iModule < 4096 && uModAddr != SymInfo.Value && uModAddr != 0; iModule++)
+ uModAddr = g_aModVersions[i].pfnProcessModule(pThis, pUVM, pVMM, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uModAddr));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if there is a likely kallsyms_names fragment at pHitAddr.
+ *
+ * @returns true if it's a likely fragment, false if not.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pHitAddr The address where paNeedle was found.
+ * @param pabNeedle The fragment we've been searching for.
+ * @param cbNeedle The length of the fragment.
+ */
+static bool dbgDiggerLinuxIsLikelyNameFragment(PUVM pUVM, PCVMMR3VTABLE pVMM, PCDBGFADDRESS pHitAddr,
+ uint8_t const *pabNeedle, uint8_t cbNeedle)
+{
+ /*
+ * Examples of lead and tail bytes of our choosen needle in a randomly
+ * picked kernel:
+ * k o b j
+ * 22 6b 6f 62 6a aa
+ * fc 6b 6f 62 6a aa
+ * 82 6b 6f 62 6a 5f - ascii trail byte (_).
+ * ee 6b 6f 62 6a aa
+ * fc 6b 6f 62 6a 5f - ascii trail byte (_).
+ * 0a 74 6b 6f 62 6a 5f ea - ascii lead (t) and trail (_) bytes.
+ * 0b 54 6b 6f 62 6a aa - ascii lead byte (T).
+ * ... omitting 29 samples similar to the last two ...
+ * d8 6b 6f 62 6a aa
+ * d8 6b 6f 62 6a aa
+ * d8 6b 6f 62 6a aa
+ * d8 6b 6f 62 6a aa
+ * f9 5f 6b 6f 62 6a 5f 94 - ascii lead and trail bytes (_)
+ * f9 5f 6b 6f 62 6a 0c - ascii lead byte (_).
+ * fd 6b 6f 62 6a 0f
+ * ... enough.
+ */
+ uint8_t abBuf[32];
+ DBGFADDRESS ReadAddr = *pHitAddr;
+ pVMM->pfnDBGFR3AddrSub(&ReadAddr, 2);
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &ReadAddr, abBuf, 2 + cbNeedle + 2);
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(&abBuf[2], pabNeedle, cbNeedle) == 0) /* paranoia */
+ {
+ uint8_t const bLead = abBuf[1] == '_' || abBuf[1] == 'T' || abBuf[1] == 't' ? abBuf[0] : abBuf[1];
+ uint8_t const offTail = 2 + cbNeedle;
+ uint8_t const bTail = abBuf[offTail] == '_' ? abBuf[offTail] : abBuf[offTail + 1];
+ if ( bLead >= 1 && (bLead < 0x20 || bLead >= 0x80)
+ && bTail >= 1 && (bTail < 0x20 || bTail >= 0x80))
+ return true;
+ Log(("dbgDiggerLinuxIsLikelyNameFragment: failed at %RGv: bLead=%#x bTail=%#x (offTail=%#x)\n",
+ pHitAddr->FlatPtr, bLead, bTail, offTail));
+ }
+ else
+ Log(("dbgDiggerLinuxIsLikelyNameFragment: failed at %RGv: Needle changed!\n", pHitAddr->FlatPtr));
+ }
+ else
+ Log(("dbgDiggerLinuxIsLikelyNameFragment: failed at %RGv: %Rrc\n", pHitAddr->FlatPtr, rc));
+
+ return false;
+}
+
+/**
+ * Tries to find and load the kernel symbol table with the given needle.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pabNeedle The needle to use for searching.
+ * @param cbNeedle Size of the needle in bytes.
+ */
+static int dbgDiggerLinuxFindSymbolTableFromNeedle(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ uint8_t const *pabNeedle, uint8_t cbNeedle)
+{
+ /*
+ * Go looking for the kallsyms table. If it's there, it will be somewhere
+ * after the linux_banner symbol, so use it for starting the search.
+ */
+ int rc = VINF_SUCCESS;
+ DBGFADDRESS CurAddr = pThis->AddrLinuxBanner;
+ uint32_t cbLeft = LNX_MAX_KERNEL_SIZE;
+ while (cbLeft > 4096)
+ {
+ DBGFADDRESS HitAddr;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &CurAddr, cbLeft, 1 /*uAlign*/,
+ pabNeedle, cbNeedle, &HitAddr);
+ if (RT_FAILURE(rc))
+ break;
+ if (dbgDiggerLinuxIsLikelyNameFragment(pUVM, pVMM, &HitAddr, pabNeedle, cbNeedle))
+ {
+ /* There will be another hit near by. */
+ pVMM->pfnDBGFR3AddrAdd(&HitAddr, 1);
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, LNX_MAX_KALLSYMS_NAMES_SIZE, 1 /*uAlign*/,
+ pabNeedle, cbNeedle, &HitAddr);
+ if ( RT_SUCCESS(rc)
+ && dbgDiggerLinuxIsLikelyNameFragment(pUVM, pVMM, &HitAddr, pabNeedle, cbNeedle))
+ {
+ /*
+ * We've got a very likely candidate for a location inside kallsyms_names.
+ * Try find the start of it, that is to say, try find kallsyms_num_syms.
+ * kallsyms_num_syms is aligned on sizeof(unsigned long) boundrary
+ */
+ rc = dbgDiggerLinuxFindStartOfNamesAndSymbolCount(pUVM, pVMM, pThis, &HitAddr);
+ if (RT_SUCCESS(rc))
+ rc = dbgDiggerLinuxFindEndOfNamesAndMore(pUVM, pVMM, pThis, &HitAddr);
+ if (RT_SUCCESS(rc))
+ rc = dbgDiggerLinuxFindTokenIndex(pUVM, pVMM, pThis);
+ if (RT_SUCCESS(rc))
+ rc = dbgDiggerLinuxLoadKernelSymbols(pUVM, pVMM, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = dbgDiggerLinuxLoadModules(pThis, pUVM, pVMM);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Advance.
+ */
+ RTGCUINTPTR cbDistance = HitAddr.FlatPtr - CurAddr.FlatPtr + cbNeedle;
+ if (RT_UNLIKELY(cbDistance >= cbLeft))
+ {
+ Log(("dbgDiggerLinuxInit: Failed to find kallsyms\n"));
+ break;
+ }
+ cbLeft -= cbDistance;
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, cbDistance);
+ }
+
+ return rc;
+}
+
+/**
+ * Skips whitespace and comments in the given config returning the pointer
+ * to the first non whitespace character.
+ *
+ * @returns Pointer to the first non whitespace character or NULL if the end
+ * of the string was reached.
+ * @param pszCfg The config string.
+ */
+static const char *dbgDiggerLinuxCfgSkipWhitespace(const char *pszCfg)
+{
+ do
+ {
+ while ( *pszCfg != '\0'
+ && ( RT_C_IS_SPACE(*pszCfg)
+ || *pszCfg == '\n'))
+ pszCfg++;
+
+ /* Do we have a comment? Skip it. */
+ if (*pszCfg == '#')
+ {
+ while ( *pszCfg != '\n'
+ && *pszCfg != '\0')
+ pszCfg++;
+ }
+ } while ( *pszCfg != '\0'
+ && ( RT_C_IS_SPACE(*pszCfg)
+ || *pszCfg == '\n'
+ || *pszCfg == '#'));
+
+ return pszCfg;
+}
+
+/**
+ * Parses an identifier at the given position.
+ *
+ * @returns VBox status code.
+ * @param pszCfg The config data.
+ * @param ppszCfgNext Where to store the pointer to the data following the identifier.
+ * @param ppszIde Where to store the pointer to the identifier on success.
+ * Free with RTStrFree().
+ */
+static int dbgDiggerLinuxCfgParseIde(const char *pszCfg, const char **ppszCfgNext, char **ppszIde)
+{
+ int rc = VINF_SUCCESS;
+ size_t cchIde = 0;
+
+ while ( *pszCfg != '\0'
+ && ( RT_C_IS_ALNUM(*pszCfg)
+ || *pszCfg == '_'))
+ {
+ cchIde++;
+ pszCfg++;
+ }
+
+ if (cchIde)
+ {
+ *ppszIde = RTStrDupN(pszCfg - cchIde, cchIde);
+ if (!*ppszIde)
+ rc = VERR_NO_STR_MEMORY;
+ }
+
+ *ppszCfgNext = pszCfg;
+ return rc;
+}
+
+/**
+ * Parses a value for a config item.
+ *
+ * @returns VBox status code.
+ * @param pszCfg The config data.
+ * @param ppszCfgNext Where to store the pointer to the data following the identifier.
+ * @param ppCfgItem Where to store the created config item on success.
+ */
+static int dbgDiggerLinuxCfgParseVal(const char *pszCfg, const char **ppszCfgNext,
+ PDBGDIGGERLINUXCFGITEM *ppCfgItem)
+{
+ int rc = VINF_SUCCESS;
+ PDBGDIGGERLINUXCFGITEM pCfgItem = NULL;
+
+ if (RT_C_IS_DIGIT(*pszCfg) || *pszCfg == '-')
+ {
+ /* Parse the number. */
+ int64_t i64Num;
+ rc = RTStrToInt64Ex(pszCfg, (char **)ppszCfgNext, 0, &i64Num);
+ if ( RT_SUCCESS(rc)
+ || rc == VWRN_TRAILING_CHARS
+ || rc == VWRN_TRAILING_SPACES)
+ {
+ pCfgItem = (PDBGDIGGERLINUXCFGITEM)RTMemAllocZ(sizeof(DBGDIGGERLINUXCFGITEM));
+ if (pCfgItem)
+ {
+ pCfgItem->enmType = DBGDIGGERLINUXCFGITEMTYPE_NUMBER;
+ pCfgItem->u.i64Num = i64Num;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else if (*pszCfg == '\"')
+ {
+ /* Parse a string. */
+ const char *pszCfgCur = pszCfg + 1;
+ while ( *pszCfgCur != '\0'
+ && *pszCfgCur != '\"')
+ pszCfgCur++;
+
+ if (*pszCfgCur == '\"')
+ {
+ pCfgItem = (PDBGDIGGERLINUXCFGITEM)RTMemAllocZ(RT_UOFFSETOF_DYN(DBGDIGGERLINUXCFGITEM,
+ u.aszString[pszCfgCur - pszCfg + 1]));
+ if (pCfgItem)
+ {
+ pCfgItem->enmType = DBGDIGGERLINUXCFGITEMTYPE_STRING;
+ RTStrCopyEx(&pCfgItem->u.aszString[0], pszCfgCur - pszCfg + 1, pszCfg, pszCfgCur - pszCfg);
+ *ppszCfgNext = pszCfgCur + 1;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_STATE;
+ }
+ else if ( *pszCfg == 'y'
+ || *pszCfg == 'm')
+ {
+ /* Included or module. */
+ pCfgItem = (PDBGDIGGERLINUXCFGITEM)RTMemAllocZ(sizeof(DBGDIGGERLINUXCFGITEM));
+ if (pCfgItem)
+ {
+ pCfgItem->enmType = DBGDIGGERLINUXCFGITEMTYPE_FLAG;
+ pCfgItem->u.fModule = *pszCfg == 'm';
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ pszCfg++;
+ *ppszCfgNext = pszCfg;
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ if (RT_SUCCESS(rc))
+ *ppCfgItem = pCfgItem;
+ else if (pCfgItem)
+ RTMemFree(pCfgItem);
+
+ return rc;
+}
+
+/**
+ * Parses the given kernel config and creates the config database.
+ *
+ * @returns VBox status code
+ * @param pThis The Linux digger data.
+ * @param pszCfg The config string.
+ */
+static int dbgDiggerLinuxCfgParse(PDBGDIGGERLINUX pThis, const char *pszCfg)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * The config is a text file with the following elements:
+ * # starts a comment which goes till the end of the line
+ * <Ide>=<val> where <Ide> is an identifier consisting of
+ * alphanumerical characters (including _)
+ * <val> denotes the value for the identifier and can have the following
+ * formats:
+ * (-)[0-9]* for numbers
+ * "..." for a string value
+ * m when a feature is enabled as a module
+ * y when a feature is enabled
+ * Newlines are used as a separator between values and mark the end
+ * of a comment
+ */
+ const char *pszCfgCur = pszCfg;
+ while ( RT_SUCCESS(rc)
+ && *pszCfgCur != '\0')
+ {
+ /* Start skipping the whitespace. */
+ pszCfgCur = dbgDiggerLinuxCfgSkipWhitespace(pszCfgCur);
+ if ( pszCfgCur
+ && *pszCfgCur != '\0')
+ {
+ char *pszIde = NULL;
+ /* Must be an identifier, parse it. */
+ rc = dbgDiggerLinuxCfgParseIde(pszCfgCur, &pszCfgCur, &pszIde);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Skip whitespace again (shouldn't be required because = follows immediately
+ * in the observed configs).
+ */
+ pszCfgCur = dbgDiggerLinuxCfgSkipWhitespace(pszCfgCur);
+ if ( pszCfgCur
+ && *pszCfgCur == '=')
+ {
+ pszCfgCur++;
+ pszCfgCur = dbgDiggerLinuxCfgSkipWhitespace(pszCfgCur);
+ if ( pszCfgCur
+ && *pszCfgCur != '\0')
+ {
+ /* Get the value. */
+ PDBGDIGGERLINUXCFGITEM pCfgItem = NULL;
+ rc = dbgDiggerLinuxCfgParseVal(pszCfgCur, &pszCfgCur, &pCfgItem);
+ if (RT_SUCCESS(rc))
+ {
+ pCfgItem->Core.pszString = pszIde;
+ bool fRc = RTStrSpaceInsert(&pThis->hCfgDb, &pCfgItem->Core);
+ if (!fRc)
+ {
+ RTStrFree(pszIde);
+ RTMemFree(pCfgItem);
+ rc = VERR_INVALID_STATE;
+ }
+ }
+ }
+ else
+ rc = VERR_EOF;
+ }
+ else
+ rc = VERR_INVALID_STATE;
+ }
+
+ if (RT_FAILURE(rc))
+ RTStrFree(pszIde);
+ }
+ else
+ break; /* Reached the end of the config. */
+ }
+
+ if (RT_FAILURE(rc))
+ dbgDiggerLinuxCfgDbDestroy(pThis);
+
+ return rc;
+}
+
+/**
+ * Decompresses the given config and validates the UTF-8 encoding.
+ *
+ * @returns VBox status code.
+ * @param pbCfgComp The compressed config.
+ * @param cbCfgComp Size of the compressed config.
+ * @param ppszCfg Where to store the pointer to the decompressed config
+ * on success.
+ */
+static int dbgDiggerLinuxCfgDecompress(const uint8_t *pbCfgComp, size_t cbCfgComp, char **ppszCfg)
+{
+ int rc = VINF_SUCCESS;
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+
+ rc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pbCfgComp, cbCfgComp, &hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsIosDecomp = NIL_RTVFSIOSTREAM;
+ rc = RTZipGzipDecompressIoStream(hVfsIos, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsIosDecomp);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCfg = NULL;
+ size_t cchCfg = 0;
+ size_t cbRead = 0;
+
+ do
+ {
+ uint8_t abBuf[_64K];
+ rc = RTVfsIoStrmRead(hVfsIosDecomp, abBuf, sizeof(abBuf), true /*fBlocking*/, &cbRead);
+ if (rc == VINF_EOF && cbRead == 0)
+ rc = VINF_SUCCESS;
+ if ( RT_SUCCESS(rc)
+ && cbRead > 0)
+ {
+ /* Append data. */
+ char *pszCfgNew = pszCfg;
+ rc = RTStrRealloc(&pszCfgNew, cchCfg + cbRead + 1);
+ if (RT_SUCCESS(rc))
+ {
+ pszCfg = pszCfgNew;
+ memcpy(pszCfg + cchCfg, &abBuf[0], cbRead);
+ cchCfg += cbRead;
+ pszCfg[cchCfg] = '\0'; /* Enforce string termination. */
+ }
+ }
+ } while (RT_SUCCESS(rc) && cbRead > 0);
+
+ if (RT_SUCCESS(rc))
+ *ppszCfg = pszCfg;
+ else if (RT_FAILURE(rc) && pszCfg)
+ RTStrFree(pszCfg);
+
+ RTVfsIoStrmRelease(hVfsIosDecomp);
+ }
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+
+ return rc;
+}
+
+/**
+ * Reads and decodes the compressed kernel config.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pAddrStart The start address of the compressed config.
+ * @param cbCfgComp The size of the compressed config.
+ */
+static int dbgDiggerLinuxCfgDecode(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ PCDBGFADDRESS pAddrStart, size_t cbCfgComp)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbCfgComp = (uint8_t *)RTMemTmpAlloc(cbCfgComp);
+ if (!pbCfgComp)
+ return VERR_NO_MEMORY;
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrStart, pbCfgComp, cbCfgComp);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszCfg = NULL;
+ rc = dbgDiggerLinuxCfgDecompress(pbCfgComp, cbCfgComp, &pszCfg);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTStrIsValidEncoding(pszCfg))
+ rc = dbgDiggerLinuxCfgParse(pThis, pszCfg);
+ else
+ rc = VERR_INVALID_UTF8_ENCODING;
+ RTStrFree(pszCfg);
+ }
+ }
+
+ RTMemFree(pbCfgComp);
+ return rc;
+}
+
+/**
+ * Tries to find the compressed kernel config in the kernel address space
+ * and sets up the config database.
+ *
+ * @returns VBox status code.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ */
+static int dbgDiggerLinuxCfgFind(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ /*
+ * Go looking for the IKCFG_ST string which indicates the start
+ * of the compressed config file.
+ */
+ static const uint8_t s_abCfgNeedleStart[] = "IKCFG_ST";
+ static const uint8_t s_abCfgNeedleEnd[] = "IKCFG_ED";
+ int rc = VINF_SUCCESS;
+ DBGFADDRESS CurAddr = pThis->AddrLinuxBanner;
+ uint32_t cbLeft = LNX_MAX_KERNEL_SIZE;
+ while (cbLeft > 4096)
+ {
+ DBGFADDRESS HitAddrStart;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &CurAddr, cbLeft, 1 /*uAlign*/,
+ s_abCfgNeedleStart, sizeof(s_abCfgNeedleStart) - 1, &HitAddrStart);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Check for the end marker which shouldn't be that far away. */
+ pVMM->pfnDBGFR3AddrAdd(&HitAddrStart, sizeof(s_abCfgNeedleStart) - 1);
+ DBGFADDRESS HitAddrEnd;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /* idCpu */, &HitAddrStart, LNX_MAX_COMPRESSED_CFG_SIZE,
+ 1 /* uAlign */, s_abCfgNeedleEnd, sizeof(s_abCfgNeedleEnd) - 1, &HitAddrEnd);
+ if (RT_SUCCESS(rc))
+ {
+ /* Allocate a buffer to hold the compressed data between the markers and fetch it. */
+ RTGCUINTPTR cbCfg = HitAddrEnd.FlatPtr - HitAddrStart.FlatPtr;
+ Assert(cbCfg == (size_t)cbCfg);
+ rc = dbgDiggerLinuxCfgDecode(pThis, pUVM, pVMM, &HitAddrStart, cbCfg);
+ if (RT_SUCCESS(rc))
+ break;
+ }
+
+ /*
+ * Advance.
+ */
+ RTGCUINTPTR cbDistance = HitAddrStart.FlatPtr - CurAddr.FlatPtr + sizeof(s_abCfgNeedleStart) - 1;
+ if (RT_UNLIKELY(cbDistance >= cbLeft))
+ {
+ LogFunc(("Failed to find compressed kernel config\n"));
+ break;
+ }
+ cbLeft -= cbDistance;
+ pVMM->pfnDBGFR3AddrAdd(&CurAddr, cbDistance);
+ }
+
+ return rc;
+}
+
+/**
+ * Probes for a Linux kernel starting at the given address.
+ *
+ * @returns Flag whether something which looks like a valid Linux kernel was found.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param uAddrStart The address to start scanning at.
+ * @param cbScan How much to scan.
+ */
+static bool dbgDiggerLinuxProbeWithAddr(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ RTGCUINTPTR uAddrStart, size_t cbScan)
+{
+ /*
+ * Look for "Linux version " at the start of the rodata segment.
+ * Hope that this comes before any message buffer or other similar string.
+ */
+ DBGFADDRESS KernelAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &KernelAddr, uAddrStart);
+ DBGFADDRESS HitAddr;
+ int rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &KernelAddr, cbScan, 1,
+ g_abLinuxVersion, sizeof(g_abLinuxVersion) - 1, &HitAddr);
+ if (RT_SUCCESS(rc))
+ {
+ char szTmp[128];
+ char const *pszX = &szTmp[sizeof(g_abLinuxVersion) - 1];
+ rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &HitAddr, szTmp, sizeof(szTmp));
+ if ( RT_SUCCESS(rc)
+ && ( ( pszX[0] == '2' /* 2.x.y with x in {0..6} */
+ && pszX[1] == '.'
+ && pszX[2] >= '0'
+ && pszX[2] <= '6')
+ || ( pszX[0] >= '3' /* 3.x, 4.x, ... 9.x */
+ && pszX[0] <= '9'
+ && pszX[1] == '.'
+ && pszX[2] >= '0'
+ && pszX[2] <= '9')
+ )
+ )
+ {
+ pThis->AddrKernelBase = KernelAddr;
+ pThis->AddrLinuxBanner = HitAddr;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Probes for a Linux kernel which has KASLR enabled.
+ *
+ * @returns Flag whether a possible candidate location was found.
+ * @param pThis The Linux digger data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ */
+static bool dbgDiggerLinuxProbeKaslr(PDBGDIGGERLINUX pThis, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ /**
+ * With KASLR the kernel is loaded at a different address at each boot making detection
+ * more difficult for us.
+ *
+ * The randomization is done in arch/x86/boot/compressed/kaslr.c:choose_random_location() (as of Nov 2017).
+ * At the end of the method a random offset is chosen using find_random_virt_addr() which is added to the
+ * kernel map start in the caller (the start of the kernel depends on the bit size, see LNX32_KERNEL_ADDRESS_START
+ * and LNX64_KERNEL_ADDRESS_START for 32bit and 64bit kernels respectively).
+ * The lowest offset possible is LOAD_PHYSICAL_ADDR which is defined in arch/x86/include/asm/boot.h
+ * using CONFIG_PHYSICAL_START aligned to CONFIG_PHYSICAL_ALIGN.
+ * The default CONFIG_PHYSICAL_START and CONFIG_PHYSICAL_ALIGN are both 0x1000000 no matter whether a 32bit
+ * or a 64bit kernel is used. So the lowest offset to the kernel start address is 0x1000000.
+ * The find_random_virt_addr() the number of possible slots where the kernel can be placed based on the image size
+ * is calculated using the following formula:
+ * cSlots = ((KERNEL_IMAGE_SIZE - 0x1000000 (minimum) - image_size) / 0x1000000 (CONFIG_PHYSICAL_ALIGN)) + 1
+ *
+ * KERNEL_IMAGE_SIZE is 1GB for 64bit kernels and 512MB for 32bit kernels, so the maximum number of slots (resulting
+ * in the largest possible offset) can be achieved when image_size (which contains the real size of the kernel image
+ * which is unknown for us) goes to 0 and a 1GB KERNEL_IMAGE_SIZE is assumed. With that the biggest cSlots which can be
+ * achieved is 64. The chosen random offset is taken from a random long integer using kaslr_get_random_long() modulo the
+ * number of slots which selects a slot between 0 and 63. The final offset is calculated using:
+ * offAddr = random_addr * 0x1000000 (CONFIG_PHYSICAL_ALIGN) + 0x1000000 (minimum)
+ *
+ * So the highest offset the kernel can start is 0x40000000 which is 1GB (plus the maximum kernel size we defined).
+ */
+ if (dbgDiggerLinuxProbeWithAddr(pThis, pUVM, pVMM, LNX64_KERNEL_ADDRESS_START, _1G + LNX_MAX_KERNEL_SIZE))
+ return true;
+
+ /*
+ * 32bit variant, makes sure we don't exceed the 4GB address space or DBGFR3MemScan() returns VERR_DBGF_MEM_NOT_FOUND immediately
+ * without searching the remainder of the address space.
+ *
+ * The default split is 3GB userspace and 1GB kernel, so we just search the entire upper 1GB kernel space.
+ */
+ if (dbgDiggerLinuxProbeWithAddr(pThis, pUVM, pVMM, LNX32_KERNEL_ADDRESS_START, _4G - LNX32_KERNEL_ADDRESS_START))
+ return true;
+
+ return false;
+}
+
+/**
+ * @copydoc DBGFOSREG::pfnInit
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+ Assert(!pThis->fValid);
+
+ char szVersion[256] = "Linux version 4.19.0";
+ int rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &pThis->AddrLinuxBanner, &szVersion[0], sizeof(szVersion));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get a numerical version number.
+ */
+ const char *pszVersion = szVersion;
+ while (*pszVersion && !RT_C_IS_DIGIT(*pszVersion))
+ pszVersion++;
+
+ size_t offVersion = 0;
+ uint32_t uMajor = 0;
+ while (pszVersion[offVersion] && RT_C_IS_DIGIT(pszVersion[offVersion]))
+ uMajor = uMajor * 10 + pszVersion[offVersion++] - '0';
+
+ if (pszVersion[offVersion] == '.')
+ offVersion++;
+
+ uint32_t uMinor = 0;
+ while (pszVersion[offVersion] && RT_C_IS_DIGIT(pszVersion[offVersion]))
+ uMinor = uMinor * 10 + pszVersion[offVersion++] - '0';
+
+ if (pszVersion[offVersion] == '.')
+ offVersion++;
+
+ uint32_t uBuild = 0;
+ while (pszVersion[offVersion] && RT_C_IS_DIGIT(pszVersion[offVersion]))
+ uBuild = uBuild * 10 + pszVersion[offVersion++] - '0';
+
+ pThis->uKrnlVer = LNX_MK_VER(uMajor, uMinor, uBuild);
+ pThis->uKrnlVerMaj = uMajor;
+ pThis->uKrnlVerMin = uMinor;
+ pThis->uKrnlVerBld = uBuild;
+ if (pThis->uKrnlVer == 0)
+ LogRel(("dbgDiggerLinuxInit: Failed to parse version string: %s\n", pszVersion));
+ }
+
+ /*
+ * Assume 64-bit kernels all live way beyond 32-bit address space.
+ */
+ pThis->f64Bit = pThis->AddrLinuxBanner.FlatPtr > UINT32_MAX;
+ pThis->fRelKrnlAddr = false;
+
+ pThis->hCfgDb = NULL;
+
+ /*
+ * Try to find the compressed kernel config and parse it before we try
+ * to get the symbol table, the config database is required to select
+ * the method to use.
+ */
+ rc = dbgDiggerLinuxCfgFind(pThis, pUVM, pVMM);
+ if (RT_FAILURE(rc))
+ LogFlowFunc(("Failed to find kernel config (%Rrc), no config database available\n", rc));
+
+ static const uint8_t s_abNeedle[] = "kobj";
+ rc = dbgDiggerLinuxFindSymbolTableFromNeedle(pThis, pUVM, pVMM, s_abNeedle, sizeof(s_abNeedle) - 1);
+ if (RT_FAILURE(rc))
+ {
+ /* Try alternate needle (seen on older x86 Linux kernels). */
+ static const uint8_t s_abNeedleAlt[] = "kobjec";
+ rc = dbgDiggerLinuxFindSymbolTableFromNeedle(pThis, pUVM, pVMM, s_abNeedleAlt, sizeof(s_abNeedleAlt) - 1);
+ if (RT_FAILURE(rc))
+ {
+ static const uint8_t s_abNeedleOSuseX86[] = "nmi"; /* OpenSuSe 10.2 x86 */
+ rc = dbgDiggerLinuxFindSymbolTableFromNeedle(pThis, pUVM, pVMM, s_abNeedleOSuseX86, sizeof(s_abNeedleOSuseX86) - 1);
+ }
+ }
+
+ pThis->fValid = true;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnProbe
+ */
+static DECLCALLBACK(bool) dbgDiggerLinuxProbe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_au64LnxKernelAddresses); i++)
+ {
+ if (dbgDiggerLinuxProbeWithAddr(pThis, pUVM, pVMM, g_au64LnxKernelAddresses[i], LNX_MAX_KERNEL_SIZE))
+ return true;
+ }
+
+ /* Maybe the kernel uses KASLR. */
+ if (dbgDiggerLinuxProbeKaslr(pThis, pUVM, pVMM))
+ return true;
+
+ return false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnDestruct
+ */
+static DECLCALLBACK(void) dbgDiggerLinuxDestruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnConstruct
+ */
+static DECLCALLBACK(int) dbgDiggerLinuxConstruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData;
+ pThis->IDmesg.u32Magic = DBGFOSIDMESG_MAGIC;
+ pThis->IDmesg.pfnQueryKernelLog = dbgDiggerLinuxIDmsg_QueryKernelLog;
+ pThis->IDmesg.u32EndMagic = DBGFOSIDMESG_MAGIC;
+
+ return VINF_SUCCESS;
+}
+
+
+const DBGFOSREG g_DBGDiggerLinux =
+{
+ /* .u32Magic = */ DBGFOSREG_MAGIC,
+ /* .fFlags = */ 0,
+ /* .cbData = */ sizeof(DBGDIGGERLINUX),
+ /* .szName = */ "Linux",
+ /* .pfnConstruct = */ dbgDiggerLinuxConstruct,
+ /* .pfnDestruct = */ dbgDiggerLinuxDestruct,
+ /* .pfnProbe = */ dbgDiggerLinuxProbe,
+ /* .pfnInit = */ dbgDiggerLinuxInit,
+ /* .pfnRefresh = */ dbgDiggerLinuxRefresh,
+ /* .pfnTerm = */ dbgDiggerLinuxTerm,
+ /* .pfnQueryVersion = */ dbgDiggerLinuxQueryVersion,
+ /* .pfnQueryInterface = */ dbgDiggerLinuxQueryInterface,
+ /* .pfnStackUnwindAssist = */ dbgDiggerLinuxStackUnwindAssist,
+ /* .u32EndMagic = */ DBGFOSREG_MAGIC
+};
+
diff --git a/src/VBox/Debugger/DBGPlugInLinuxModuleCodeTmpl.cpp.h b/src/VBox/Debugger/DBGPlugInLinuxModuleCodeTmpl.cpp.h
new file mode 100644
index 00000000..19c95900
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInLinuxModuleCodeTmpl.cpp.h
@@ -0,0 +1,560 @@
+/* $Id: DBGPlugInLinuxModuleCodeTmpl.cpp.h $ */
+/** @file
+ * DBGPlugInLinux - Code template for struct module processing.
+ */
+
+/*
+ * Copyright (C) 2019-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
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef LNX_MK_VER
+# define LNX_MK_VER(major, minor, build) (((major) << 22) | ((minor) << 12) | (build))
+#endif
+#if LNX_64BIT
+# define LNX_ULONG_T uint64_t
+#else
+# define LNX_ULONG_T uint32_t
+#endif
+#if LNX_64BIT
+# define PAD32ON64(seq) uint32_t RT_CONCAT(u32Padding,seq);
+#else
+# define PAD32ON64(seq)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Kernel module symbol (hasn't changed in ages).
+ */
+typedef struct RT_CONCAT(LNXMODKSYM,LNX_SUFFIX)
+{
+ LNX_ULONG_T uValue;
+ LNX_PTR_T uPtrSymName;
+} RT_CONCAT(LNXMODKSYM,LNX_SUFFIX);
+
+
+#if LNX_VER >= LNX_MK_VER(2,6,11)
+typedef struct RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX)
+{
+ LNX_PTR_T uPtrKName;
+# if LNX_VER < LNX_MK_VER(2,6,24)
+ char name[20];
+# endif
+# if LNX_VER < LNX_MK_VER(2,6,27)
+ int32_t cRefs;
+# if LNX_VER >= LNX_MK_VER(2,6,24)
+ PAD32ON64(0)
+# endif
+# endif
+ LNX_PTR_T uPtrNext;
+ LNX_PTR_T uPtrPrev;
+ LNX_PTR_T uPtrParent; /**< struct kobject pointer */
+ LNX_PTR_T uPtrKset; /**< struct kset pointer */
+ LNX_PTR_T uPtrKtype; /**< struct kobj_type pointer */
+ LNX_PTR_T uPtrDirEntry; /**< struct dentry pointer; 2.6.23+ sysfs_dirent. */
+# if LNX_VER >= LNX_MK_VER(2,6,17) && LNX_VER < LNX_MK_VER(2,6,24)
+ LNX_PTR_T aPtrWaitQueueHead[3];
+# endif
+# if LNX_VER >= LNX_MK_VER(2,6,27)
+ int32_t cRefs;
+ uint32_t uStateStuff;
+# elif LNX_VER >= LNX_MK_VER(2,6,25)
+ LNX_ULONG_T uStateStuff;
+# endif
+ /* non-kobject: */
+ LNX_PTR_T uPtrModule; /**< struct module pointer. */
+# if LNX_VER >= LNX_MK_VER(2,6,21)
+ LNX_PTR_T uPtrDriverDir; /**< Points to struct kobject. */
+# endif
+# if LNX_VER >= LNX_MK_VER(4,5,0)
+ LNX_PTR_T uPtrMp;
+ LNX_PTR_T uPtrCompletion; /**< Points to struct completion. */
+# endif
+} RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX);
+#endif
+#if LNX_VER == LNX_MK_VER(2,6,24) && LNX_64BIT
+AssertCompileMemberOffset(RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX), uPtrParent, 32);
+AssertCompileMemberOffset(RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX), uPtrParent, 32);
+AssertCompileSize(RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX), 80);
+#endif
+
+
+#if LNX_VER >= LNX_MK_VER(4,5,0)
+/**
+ * Red black tree node.
+ */
+typedef struct RT_CONCAT(LNXRBNODE,LNX_SUFFIX)
+{
+ LNX_ULONG_T uRbParentColor;
+ LNX_PTR_T uPtrRbRight;
+ LNX_PTR_T uPtrRbLeft;
+} RT_CONCAT(LNXRBNODE,LNX_SUFFIX);
+
+
+/**
+ * Latch tree node.
+ */
+typedef struct RT_CONCAT(LNXLATCHTREENODE,LNX_SUFFIX)
+{
+ RT_CONCAT(LNXRBNODE,LNX_SUFFIX) aNode[2];
+} RT_CONCAT(LNXLATCHTREENODE,LNX_SUFFIX);
+
+
+/**
+ * Module tree node.
+ */
+typedef struct RT_CONCAT(LNXMODTREENODE,LNX_SUFFIX)
+{
+ LNX_PTR_T uPtrKMod;
+ RT_CONCAT(LNXLATCHTREENODE,LNX_SUFFIX) Node;
+} RT_CONCAT(LNXMODTREENODE,LNX_SUFFIX);
+
+
+/**
+ * Module layout.
+ */
+typedef struct RT_CONCAT(LNXKMODLAYOUT,LNX_SUFFIX)
+{
+ LNX_PTR_T uPtrBase; /**< Base pointer to text and data. */
+ uint32_t cb; /**< Size of the module. */
+ uint32_t cbText; /**< Size of the text section. */
+ uint32_t cbRo; /**< Size of the readonly portion (text + ro data). */
+ RT_CONCAT(LNXMODTREENODE,LNX_SUFFIX) ModTreeNd; /**< Only available when CONFIG_MODULES_TREE_LOOKUP is set (default). */
+} RT_CONCAT(LNXKMODLAYOUT,LNX_SUFFIX);
+
+
+/**
+ * Mutex.
+ */
+typedef struct RT_CONCAT(LNXMUTEX,LNX_SUFFIX)
+{
+ LNX_ULONG_T uOwner;
+ uint32_t wait_lock; /**< Actually spinlock_t */
+ PAD32ON64(0)
+ LNX_PTR_T uWaitLstPtrNext;
+ LNX_PTR_T uWaitLstPtrPrev;
+} RT_CONCAT(LNXMUTEX,LNX_SUFFIX);
+#endif
+
+
+/**
+ * Maps to the start of struct module in include/linux/module.h.
+ */
+typedef struct RT_CONCAT(LNXKMODULE,LNX_SUFFIX)
+{
+#if LNX_VER >= LNX_MK_VER(4,5,0)
+ /* Completely new layout to not feed the spaghetti dragons further. */
+ int32_t state;
+ PAD32ON64(0)
+ LNX_PTR_T uPtrNext;
+ LNX_PTR_T uPtrPrev;
+ char name[64 - sizeof(LNX_PTR_T)];
+
+ RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX) mkobj;
+ LNX_PTR_T uPtrModInfoAttrs; /**< Points to struct module_attribute. */
+ LNX_PTR_T uPtrVersion; /**< String pointers. */
+ LNX_PTR_T uPtrSrcVersion; /**< String pointers. */
+ LNX_PTR_T uPtrHolderDir; /**< Points to struct kobject. */
+
+ /** @name Exported Symbols
+ * @{ */
+ LNX_PTR_T uPtrSyms; /**< Array of struct kernel_symbol. */
+ LNX_PTR_T uPtrCrcs; /**< unsigned long array */
+ uint32_t num_syms;
+ /** @} */
+
+ /** @name Kernel parameters
+ * @{ */
+ RT_CONCAT(LNXMUTEX,LNX_SUFFIX) Mtx; /**< Mutex. */
+ LNX_PTR_T uPtrKp; /**< Points to struct kernel_param */
+ uint32_t num_kp;
+ /** @} */
+
+ /** @name GPL Symbols
+ * @{ */
+ uint32_t num_gpl_syms;
+ LNX_PTR_T uPtrGplSyms; /**< Array of struct kernel_symbol. */
+ LNX_PTR_T uPtrGplCrcs; /**< unsigned long array */
+ /** @} */
+
+ /** @name Unused symbols
+ * @{ */
+ LNX_PTR_T uPtrUnusedSyms; /**< Array of struct kernel_symbol. */
+ LNX_PTR_T uPtrUnusedCrcs; /**< unsigned long array */
+ uint32_t num_unused_syms;
+ uint32_t num_unused_gpl_syms;
+ LNX_PTR_T uPtrUnusedGplSyms; /**< Array of struct kernel_symbol. */
+ LNX_PTR_T uPtrUnusedGplCrcs; /**< unsigned long array */
+ /** @} */
+
+ uint8_t sig_ok;
+ uint8_t async_probe_requested;
+
+ /** @name Future GPL Symbols
+ * @{ */
+ LNX_PTR_T uPtrGplFutureSyms; /**< Array of struct kernel_symbol. */
+ LNX_PTR_T uPtrGplFutureCrcs; /**< unsigned long array */
+ uint32_t num_gpl_future_syms;
+ /** @} */
+
+ /** @name Exception table.
+ * @{ */
+ uint32_t num_exentries;
+ LNX_PTR_T uPtrEntries; /**< struct exception_table_entry array. */
+ /** @} */
+
+ LNX_PTR_T pfnInit;
+ RT_CONCAT(LNXKMODLAYOUT,LNX_SUFFIX) CoreLayout; /**< Should be aligned on a cache line. */
+ RT_CONCAT(LNXKMODLAYOUT,LNX_SUFFIX) InitLayout;
+
+#elif LNX_VER >= LNX_MK_VER(2,5,48)
+ /*
+ * This first part is mostly always the same.
+ */
+ int32_t state;
+ PAD32ON64(0)
+ LNX_PTR_T uPtrNext;
+ LNX_PTR_T uPtrPrev;
+ char name[64 - sizeof(LNX_PTR_T)];
+
+ /*
+ * Here be spaghetti dragons.
+ */
+# if LNX_VER >= LNX_MK_VER(2,6,11)
+ RT_CONCAT(LNXMODKOBJECT,LNX_SUFFIX) mkobj; /**< Was just kobj for a while. */
+ LNX_PTR_T uPtrParamAttrs; /**< Points to struct module_param_attrs. */
+# if LNX_VER >= LNX_MK_VER(2,6,17)
+ LNX_PTR_T uPtrModInfoAttrs; /**< Points to struct module_attribute. */
+# endif
+# if LNX_VER == LNX_MK_VER(2,6,20)
+ LNX_PTR_T uPtrDriverDir; /**< Points to struct kobject. */
+# elif LNX_VER >= LNX_MK_VER(2,6,21)
+ LNX_PTR_T uPtrHolderDir; /**< Points to struct kobject. */
+# endif
+# if LNX_VER >= LNX_MK_VER(2,6,13)
+ LNX_PTR_T uPtrVersion; /**< String pointers. */
+ LNX_PTR_T uPtrSrcVersion; /**< String pointers. */
+# endif
+# else
+# if LNX_VER >= LNX_MK_VER(2,6,7)
+ LNX_PTR_T uPtrMkObj;
+# endif
+# if LNX_VER >= LNX_MK_VER(2,6,10)
+ LNX_PTR_T uPtrParamsKobject;
+# endif
+# endif
+
+ /** @name Exported Symbols
+ * @{ */
+# if LNX_VER < LNX_MK_VER(2,5,67)
+ LNX_PTR_T uPtrSymsNext, uPtrSymsPrev, uPtrSymsOwner;
+# if LNX_VER >= LNX_MK_VER(2,5,55)
+ int32_t syms_gplonly;
+ uint32_t num_syms;
+# else
+ uint32_t num_syms;
+ PAD32ON64(1)
+# endif
+# endif
+ LNX_PTR_T uPtrSyms; /**< Array of struct kernel_symbol. */
+# if LNX_VER >= LNX_MK_VER(2,5,67)
+ uint32_t num_syms;
+ PAD32ON64(1)
+# endif
+# if LNX_VER >= LNX_MK_VER(2,5,60)
+ LNX_PTR_T uPtrCrcs; /**< unsigned long array */
+# endif
+ /** @} */
+
+ /** @name GPL Symbols
+ * @since 2.5.55
+ * @{ */
+# if LNX_VER >= LNX_MK_VER(2,5,55)
+# if LNX_VER < LNX_MK_VER(2,5,67)
+ LNX_PTR_T uPtrGplSymsNext, uPtrGplSymsPrev, uPtrGplSymsOwner;
+# if LNX_VER >= LNX_MK_VER(2,5,55)
+ int32_t gpl_syms_gplonly;
+ uint32_t num_gpl_syms;
+# else
+ uint32_t num_gpl_syms;
+ PAD32ON64(2)
+# endif
+# endif
+ LNX_PTR_T uPtrGplSyms; /**< Array of struct kernel_symbol. */
+# if LNX_VER >= LNX_MK_VER(2,5,67)
+ uint32_t num_gpl_syms;
+ PAD32ON64(2)
+# endif
+# if LNX_VER >= LNX_MK_VER(2,5,60)
+ LNX_PTR_T uPtrGplCrcs; /**< unsigned long array */
+# endif
+# endif /* > 2.5.55 */
+ /** @} */
+
+ /** @name Unused Exported Symbols
+ * @since 2.6.18
+ * @{ */
+# if LNX_VER >= LNX_MK_VER(2,6,18)
+ LNX_PTR_T uPtrUnusedSyms; /**< Array of struct kernel_symbol. */
+ uint32_t num_unused_syms;
+ PAD32ON64(4)
+ LNX_PTR_T uPtrUnusedCrcs; /**< unsigned long array */
+# endif
+ /** @} */
+
+ /** @name Unused GPL Symbols
+ * @since 2.6.18
+ * @{ */
+# if LNX_VER >= LNX_MK_VER(2,6,18)
+ LNX_PTR_T uPtrUnusedGplSyms; /**< Array of struct kernel_symbol. */
+ uint32_t num_unused_gpl_syms;
+ PAD32ON64(5)
+ LNX_PTR_T uPtrUnusedGplCrcs; /**< unsigned long array */
+# endif
+ /** @} */
+
+ /** @name Future GPL Symbols
+ * @since 2.6.17
+ * @{ */
+# if LNX_VER >= LNX_MK_VER(2,6,17)
+ LNX_PTR_T uPtrGplFutureSyms; /**< Array of struct kernel_symbol. */
+ uint32_t num_gpl_future_syms;
+ PAD32ON64(3)
+ LNX_PTR_T uPtrGplFutureCrcs; /**< unsigned long array */
+# endif
+ /** @} */
+
+ /** @name Exception table.
+ * @{ */
+# if LNX_VER < LNX_MK_VER(2,5,67)
+ LNX_PTR_T uPtrXcptTabNext, uPtrXcptTabPrev;
+# endif
+ uint32_t num_exentries;
+ PAD32ON64(6)
+ LNX_PTR_T uPtrEntries; /**< struct exception_table_entry array. */
+ /** @} */
+
+ /*
+ * Hopefully less spaghetti from here on...
+ */
+ LNX_PTR_T pfnInit;
+ LNX_PTR_T uPtrModuleInit;
+ LNX_PTR_T uPtrModuleCore;
+ LNX_ULONG_T cbInit;
+ LNX_ULONG_T cbCore;
+# if LNX_VER >= LNX_MK_VER(2,5,74)
+ LNX_ULONG_T cbInitText;
+ LNX_ULONG_T cbCoreText;
+# endif
+
+# if LNX_VER >= LNX_MK_VER(2,6,18)
+ LNX_PTR_T uPtrUnwindInfo;
+# endif
+#else
+ uint32_t structure_size;
+
+#endif
+} RT_CONCAT(LNXKMODULE,LNX_SUFFIX);
+
+# if LNX_VER == LNX_MK_VER(2,6,24) && LNX_64BIT
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), uPtrParamAttrs, 160);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), num_syms, 208);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), num_gpl_syms, 232);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), num_unused_syms, 256);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), num_unused_gpl_syms, 280);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), num_gpl_future_syms, 304);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), num_exentries, 320);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), uPtrModuleCore, 352);
+AssertCompileMemberOffset(RT_CONCAT(LNXKMODULE,LNX_SUFFIX), uPtrUnwindInfo, 392);
+#endif
+
+
+
+/**
+ * Loads the kernel symbols at the given start address.
+ *
+ * @returns VBox status code.
+ * @param pUVM Pointer to the user-mode VM instance.
+ * @param hDbgMod The module handle to add the loaded symbols to.
+ * @param uPtrModuleStart The virtual address where the kernel module starts we want to extract symbols from.
+ * @param uPtrSymStart The start address of the array of symbols.
+ * @param cSyms Number of symbols in the array.
+ */
+static int RT_CONCAT(dbgDiggerLinuxLoadModuleSymbols,LNX_SUFFIX)(PUVM pUVM, PCVMMR3VTABLE pVMM, RTDBGMOD hDbgMod,
+ LNX_PTR_T uPtrModuleStart, LNX_PTR_T uPtrSymStart, uint32_t cSyms)
+{
+ int rc = VINF_SUCCESS;
+ DBGFADDRESS AddrSym;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrSym, uPtrSymStart);
+
+ while ( cSyms
+ && RT_SUCCESS(rc))
+ {
+ RT_CONCAT(LNXMODKSYM,LNX_SUFFIX) aSyms[64];
+ uint32_t cThisLoad = RT_MIN(cSyms, RT_ELEMENTS(aSyms));
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &AddrSym, &aSyms[0], cThisLoad * sizeof(aSyms[0]));
+ if (RT_SUCCESS(rc))
+ {
+ cSyms -= cThisLoad;
+ pVMM->pfnDBGFR3AddrAdd(&AddrSym, cThisLoad * sizeof(aSyms[0]));
+
+ for (uint32_t i = 0; i < cThisLoad; i++)
+ {
+ char szSymName[128];
+ DBGFADDRESS AddrSymName;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrSymName, aSyms[i].uPtrSymName),
+ &szSymName[0], sizeof(szSymName));
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Verify string encoding - ignore the symbol if it fails. */
+ rc = RTStrValidateEncodingEx(&szSymName[0], sizeof(szSymName), RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_FAILURE(rc))
+ continue;
+
+ Assert(aSyms[i].uValue >= uPtrModuleStart);
+ rc = RTDbgModSymbolAdd(hDbgMod, szSymName, RTDBGSEGIDX_RVA, aSyms[i].uValue - uPtrModuleStart,
+ 0 /*cb*/, 0 /*fFlags*/, NULL);
+ if (RT_SUCCESS(rc))
+ LogFlowFunc(("Added symbol '%s' successfully\n", szSymName));
+ else
+ {
+ LogFlowFunc(("Adding symbol '%s' failed with: %Rrc\n", szSymName, rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Version specific module processing code.
+ */
+static uint64_t RT_CONCAT(dbgDiggerLinuxLoadModule,LNX_SUFFIX)(PDBGDIGGERLINUX pThis, PUVM pUVM,
+ PCVMMR3VTABLE pVMM, PDBGFADDRESS pAddrModule)
+{
+ RT_CONCAT(LNXKMODULE,LNX_SUFFIX) Module;
+
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrSub(pAddrModule, RT_UOFFSETOF(RT_CONCAT(LNXKMODULE,LNX_SUFFIX),
+ uPtrNext)),
+ &Module, sizeof(Module));
+ if (RT_FAILURE(rc))
+ {
+ LogRelFunc(("Failed to read module structure at %#RX64: %Rrc\n", pAddrModule->FlatPtr, rc));
+ return 0;
+ }
+
+ /*
+ * Check the module name.
+ */
+#if LNX_VER >= LNX_MK_VER(2,5,48)
+ const char *pszName = Module.name;
+ size_t const cbName = sizeof(Module.name);
+#else
+
+#endif
+ if ( RTStrNLen(pszName, cbName) >= cbName
+ || RT_FAILURE(RTStrValidateEncoding(pszName))
+ || *pszName == '\0')
+ {
+ LogRelFunc(("%#RX64: Bad name: %.*Rhxs\n", pAddrModule->FlatPtr, (int)cbName, pszName));
+ return 0;
+ }
+
+ /*
+ * Create a simple module for it.
+ */
+#if LNX_VER >= LNX_MK_VER(4,5,0)
+ LNX_PTR_T uPtrModuleCore = Module.CoreLayout.uPtrBase;
+ uint32_t cbCore = Module.CoreLayout.cb;
+#else
+ LNX_PTR_T uPtrModuleCore = Module.uPtrModuleCore;
+ uint32_t cbCore = (uint32_t)Module.cbCore;
+#endif
+ LogRelFunc((" %#RX64: %#RX64 LB %#RX32 %s\n", pAddrModule->FlatPtr, uPtrModuleCore, cbCore, pszName));
+
+ RTDBGMOD hDbgMod;
+ rc = RTDbgModCreate(&hDbgMod, pszName, cbCore, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDbgModSetTag(hDbgMod, DIG_LNX_MOD_TAG);
+ if (RT_SUCCESS(rc))
+ {
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ rc = RTDbgAsModuleLink(hAs, hDbgMod, uPtrModuleCore, RTDBGASLINK_FLAGS_REPLACE /*fFlags*/);
+ RTDbgAsRelease(hAs);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RT_CONCAT(dbgDiggerLinuxLoadModuleSymbols,LNX_SUFFIX)(pUVM, pVMM, hDbgMod, uPtrModuleCore,
+ Module.uPtrSyms, Module.num_syms);
+ if (RT_FAILURE(rc))
+ LogRelFunc((" Faild to load symbols: %Rrc\n", rc));
+
+#if LNX_VER >= LNX_MK_VER(2,5,55)
+ rc = RT_CONCAT(dbgDiggerLinuxLoadModuleSymbols,LNX_SUFFIX)(pUVM, pVMM, hDbgMod, uPtrModuleCore,
+ Module.uPtrGplSyms, Module.num_gpl_syms);
+ if (RT_FAILURE(rc))
+ LogRelFunc((" Faild to load GPL symbols: %Rrc\n", rc));
+#endif
+
+#if LNX_VER >= LNX_MK_VER(2,6,17)
+ rc = RT_CONCAT(dbgDiggerLinuxLoadModuleSymbols,LNX_SUFFIX)(pUVM, pVMM, hDbgMod, uPtrModuleCore,
+ Module.uPtrGplFutureSyms, Module.num_gpl_future_syms);
+ if (RT_FAILURE(rc))
+ LogRelFunc((" Faild to load future GPL symbols: %Rrc\n", rc));
+#endif
+
+#if LNX_VER >= LNX_MK_VER(2,6,18)
+ rc = RT_CONCAT(dbgDiggerLinuxLoadModuleSymbols,LNX_SUFFIX)(pUVM, pVMM, hDbgMod, uPtrModuleCore,
+ Module.uPtrUnusedSyms, Module.num_unused_syms);
+ if (RT_FAILURE(rc))
+ LogRelFunc((" Faild to load unused symbols: %Rrc\n", rc));
+
+ rc = RT_CONCAT(dbgDiggerLinuxLoadModuleSymbols,LNX_SUFFIX)(pUVM, pVMM, hDbgMod, uPtrModuleCore,
+ Module.uPtrUnusedGplSyms, Module.num_unused_gpl_syms);
+ if (RT_FAILURE(rc))
+ LogRelFunc((" Faild to load unused GPL symbols: %Rrc\n", rc));
+#endif
+ }
+ }
+ else
+ LogRel(("DbgDiggerOs2: RTDbgModSetTag failed: %Rrc\n", rc));
+ RTDbgModRelease(hDbgMod);
+ }
+
+ RT_NOREF(pThis);
+ return Module.uPtrNext;
+}
+
+#undef LNX_VER
+#undef LNX_SUFFIX
+#undef LNX_ULONG_T
+#undef PAD32ON64
diff --git a/src/VBox/Debugger/DBGPlugInLinuxModuleTableEntryTmpl.cpp.h b/src/VBox/Debugger/DBGPlugInLinuxModuleTableEntryTmpl.cpp.h
new file mode 100644
index 00000000..294f1dcc
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInLinuxModuleTableEntryTmpl.cpp.h
@@ -0,0 +1,32 @@
+/* $Id: DBGPlugInLinuxModuleTableEntryTmpl.cpp.h $ */
+/** @file
+ * DBGPlugInLinux - Table entry template for struct module processing.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+ { LNX_VER, LNX_64BIT, RT_CONCAT(dbgDiggerLinuxLoadModule,LNX_SUFFIX) },
+
+#undef LNX_VER
+#undef LNX_SUFFIX
+
diff --git a/src/VBox/Debugger/DBGPlugInLinuxModuleVerTmpl.cpp.h b/src/VBox/Debugger/DBGPlugInLinuxModuleVerTmpl.cpp.h
new file mode 100644
index 00000000..a22c95bd
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInLinuxModuleVerTmpl.cpp.h
@@ -0,0 +1,95 @@
+/* $Id: DBGPlugInLinuxModuleVerTmpl.cpp.h $ */
+/** @file
+ * DBGPlugInLinux - Instantiate LNX_TEMPLATE_HEADER for all different struct module versions.
+ */
+
+/*
+ * Copyright (C) 2019-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
+ */
+
+/*
+ * Newest first so the list walker can select the right instance.
+ */
+
+#define LNX_VER LNX_MK_VER(4,5,0)
+#define LNX_SUFFIX RT_CONCAT(_4_5_0,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,27)
+#define LNX_SUFFIX RT_CONCAT(_2_6_27,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,25)
+#define LNX_SUFFIX RT_CONCAT(_2_6_25,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,24)
+#define LNX_SUFFIX RT_CONCAT(_2_6_24,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,21)
+#define LNX_SUFFIX RT_CONCAT(_2_6_21,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,20)
+#define LNX_SUFFIX RT_CONCAT(_2_6_20,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,18)
+#define LNX_SUFFIX RT_CONCAT(_2_6_18,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,17)
+#define LNX_SUFFIX RT_CONCAT(_2_6_17,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,13)
+#define LNX_SUFFIX RT_CONCAT(_2_6_13,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,11)
+#define LNX_SUFFIX RT_CONCAT(_2_6_11,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,6,7)
+#define LNX_SUFFIX RT_CONCAT(_2_6_7,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,5,67) /* Makes away with kernel_symbol_group and exception_table. */
+#define LNX_SUFFIX RT_CONCAT(_2_5_67,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,5,55) /* Adds gpl_symbols */
+#define LNX_SUFFIX RT_CONCAT(_2_5_55,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+#define LNX_VER LNX_MK_VER(2,5,48)
+#define LNX_SUFFIX RT_CONCAT(_2_5_48,LNX_BIT_SUFFIX)
+#include LNX_TEMPLATE_HEADER
+
+
+/*
+ * Cleanup.
+ */
+#undef LNX_PTR_T
+#undef LNX_64BIT
+#undef LNX_BIT_SUFFIX
+
diff --git a/src/VBox/Debugger/DBGPlugInOS2.cpp b/src/VBox/Debugger/DBGPlugInOS2.cpp
new file mode 100644
index 00000000..8488beef
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInOS2.cpp
@@ -0,0 +1,1268 @@
+/* $Id: DBGPlugInOS2.cpp $ */
+/** @file
+ * DBGPlugInOS2 - Debugger and Guest OS Digger Plugin For OS/2.
+ */
+
+/*
+ * Copyright (C) 2009-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugIns.h"
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+typedef enum DBGDIGGEROS2VER
+{
+ DBGDIGGEROS2VER_UNKNOWN,
+ DBGDIGGEROS2VER_1_x,
+ DBGDIGGEROS2VER_2_x,
+ DBGDIGGEROS2VER_3_0,
+ DBGDIGGEROS2VER_4_0,
+ DBGDIGGEROS2VER_4_5
+} DBGDIGGEROS2VER;
+
+/**
+ * OS/2 guest OS digger instance data.
+ */
+typedef struct DBGDIGGEROS2
+{
+ /** The user-mode VM handle for use in info handlers. */
+ PUVM pUVM;
+ /** The VMM function table for use in info handlers. */
+ PCVMMR3VTABLE pVMM;
+
+ /** Whether the information is valid or not.
+ * (For fending off illegal interface method calls.) */
+ bool fValid;
+ /** 32-bit (true) or 16-bit (false) */
+ bool f32Bit;
+
+ /** The OS/2 guest version. */
+ DBGDIGGEROS2VER enmVer;
+ uint8_t OS2MajorVersion;
+ uint8_t OS2MinorVersion;
+
+ /** Guest's Global Info Segment selector. */
+ uint16_t selGis;
+ /** The 16:16 address of the LIS. */
+ RTFAR32 Lis;
+
+ /** The kernel virtual address (excluding DOSMVDMINSTDATA & DOSSWAPINSTDATA). */
+ uint32_t uKernelAddr;
+ /** The kernel size. */
+ uint32_t cbKernel;
+
+} DBGDIGGEROS2;
+/** Pointer to the OS/2 guest OS digger instance data. */
+typedef DBGDIGGEROS2 *PDBGDIGGEROS2;
+
+/**
+ * 32-bit OS/2 loader module table entry.
+ */
+typedef struct LDRMTE
+{
+ uint16_t mte_flags2;
+ uint16_t mte_handle;
+ uint32_t mte_swapmte; /**< Pointer to LDRSMTE. */
+ uint32_t mte_link; /**< Pointer to next LDRMTE. */
+ uint32_t mte_flags1;
+ uint32_t mte_impmodcnt;
+ uint16_t mte_sfn;
+ uint16_t mte_usecnt;
+ char mte_modname[8];
+ uint32_t mte_RAS; /**< added later */
+ uint32_t mte_modver; /**< added even later. */
+} LDRMTE;
+/** @name LDRMTE::mte_flag2 values
+ * @{ */
+#define MTEFORMATMASK UINT16_C(0x0003)
+#define MTEFORMATR1 UINT16_C(0x0000)
+#define MTEFORMATNE UINT16_C(0x0001)
+#define MTEFORMATLX UINT16_C(0x0002)
+#define MTEFORMATR2 UINT16_C(0x0003)
+#define MTESYSTEMDLL UINT16_C(0x0004)
+#define MTELOADORATTACH UINT16_C(0x0008)
+#define MTECIRCLEREF UINT16_C(0x0010)
+#define MTEFREEFIXUPS UINT16_C(0x0020) /* had different meaning earlier */
+#define MTEPRELOADED UINT16_C(0x0040)
+#define MTEGETMTEDONE UINT16_C(0x0080)
+#define MTEPACKSEGDONE UINT16_C(0x0100)
+#define MTE20LIELIST UINT16_C(0x0200)
+#define MTESYSPROCESSED UINT16_C(0x0400)
+#define MTEPSDMOD UINT16_C(0x0800)
+#define MTEDLLONEXTLST UINT16_C(0x1000)
+#define MTEPDUMPCIRCREF UINT16_C(0x2000)
+/** @} */
+/** @name LDRMTE::mte_flag1 values
+ * @{ */
+#define MTE1_NOAUTODS UINT32_C(0x00000000)
+#define MTE1_SOLO UINT32_C(0x00000001)
+#define MTE1_INSTANCEDS UINT32_C(0x00000002)
+#define MTE1_INSTLIBINIT UINT32_C(0x00000004)
+#define MTE1_GINISETUP UINT32_C(0x00000008)
+#define MTE1_NOINTERNFIXUPS UINT32_C(0x00000010)
+#define MTE1_NOEXTERNFIXUPS UINT32_C(0x00000020)
+#define MTE1_CLASS_ALL UINT32_C(0x00000000)
+#define MTE1_CLASS_PROGRAM UINT32_C(0x00000040)
+#define MTE1_CLASS_GLOBAL UINT32_C(0x00000080)
+#define MTE1_CLASS_SPECIFIC UINT32_C(0x000000c0)
+#define MTE1_CLASS_MASK UINT32_C(0x000000c0)
+#define MTE1_MTEPROCESSED UINT32_C(0x00000100)
+#define MTE1_USED UINT32_C(0x00000200)
+#define MTE1_DOSLIB UINT32_C(0x00000400)
+#define MTE1_DOSMOD UINT32_C(0x00000800) /**< The OS/2 kernel (DOSCALLS).*/
+#define MTE1_MEDIAFIXED UINT32_C(0x00001000)
+#define MTE1_LDRINVALID UINT32_C(0x00002000)
+#define MTE1_PROGRAMMOD UINT32_C(0x00000000)
+#define MTE1_DEVDRVMOD UINT32_C(0x00004000)
+#define MTE1_LIBRARYMOD UINT32_C(0x00008000)
+#define MTE1_VDDMOD UINT32_C(0x00010000)
+#define MTE1_MVDMMOD UINT32_C(0x00020000)
+#define MTE1_INGRAPH UINT32_C(0x00040000)
+#define MTE1_GINIDONE UINT32_C(0x00080000)
+#define MTE1_ADDRALLOCED UINT32_C(0x00100000)
+#define MTE1_FSDMOD UINT32_C(0x00200000)
+#define MTE1_FSHMOD UINT32_C(0x00400000)
+#define MTE1_LONGNAMES UINT32_C(0x00800000)
+#define MTE1_MEDIACONTIG UINT32_C(0x01000000)
+#define MTE1_MEDIA16M UINT32_C(0x02000000)
+#define MTE1_SWAPONLOAD UINT32_C(0x04000000)
+#define MTE1_PORTHOLE UINT32_C(0x08000000)
+#define MTE1_MODPROT UINT32_C(0x10000000)
+#define MTE1_NEWMOD UINT32_C(0x20000000)
+#define MTE1_DLLTERM UINT32_C(0x40000000)
+#define MTE1_SYMLOADED UINT32_C(0x80000000)
+/** @} */
+
+
+/**
+ * 32-bit OS/2 swappable module table entry.
+ */
+typedef struct LDRSMTE
+{
+ uint32_t smte_mpages; /**< 0x00: module page count. */
+ uint32_t smte_startobj; /**< 0x04: Entrypoint segment number. */
+ uint32_t smte_eip; /**< 0x08: Entrypoint offset value. */
+ uint32_t smte_stackobj; /**< 0x0c: Stack segment number. */
+ uint32_t smte_esp; /**< 0x10: Stack offset value*/
+ uint32_t smte_pageshift; /**< 0x14: Page shift value. */
+ uint32_t smte_fixupsize; /**< 0x18: Size of the fixup section. */
+ uint32_t smte_objtab; /**< 0x1c: Pointer to LDROTE array. */
+ uint32_t smte_objcnt; /**< 0x20: Number of segments. */
+ uint32_t smte_objmap; /**< 0x20: Address of the object page map. */
+ uint32_t smte_itermap; /**< 0x20: File offset of the iterated data map*/
+ uint32_t smte_rsrctab; /**< 0x20: Pointer to resource table? */
+ uint32_t smte_rsrccnt; /**< 0x30: Number of resource table entries. */
+ uint32_t smte_restab; /**< 0x30: Pointer to the resident name table. */
+ uint32_t smte_enttab; /**< 0x30: Possibly entry point table address, if not file offset. */
+ uint32_t smte_fpagetab; /**< 0x30 */
+ uint32_t smte_frectab; /**< 0x40 */
+ uint32_t smte_impmod; /**< 0x44 */
+ uint32_t smte_impproc; /**< 0x48 */
+ uint32_t smte_datapage; /**< 0x4c */
+ uint32_t smte_nrestab; /**< 0x50 */
+ uint32_t smte_cbnrestab; /**< 0x54 */
+ uint32_t smte_autods; /**< 0x58 */
+ uint32_t smte_debuginfo; /**< 0x5c */
+ uint32_t smte_debuglen; /**< 0x60 */
+ uint32_t smte_heapsize; /**< 0x64 */
+ uint32_t smte_path; /**< 0x68 Address of full name string. */
+ uint16_t smte_semcount; /**< 0x6c */
+ uint16_t smte_semowner; /**< 0x6e */
+ uint32_t smte_pfilecache; /**< 0x70: Address of cached data if replace-module is used. */
+ uint32_t smte_stacksize; /**< 0x74: Stack size for .exe thread 1. */
+ uint16_t smte_alignshift; /**< 0x78: */
+ uint16_t smte_NEexpver; /**< 0x7a: */
+ uint16_t smte_pathlen; /**< 0x7c: Length of smte_path */
+ uint16_t smte_NEexetype; /**< 0x7e: */
+ uint16_t smte_csegpack; /**< 0x80: */
+ uint8_t smte_major_os; /**< 0x82: added later to lie about OS version */
+ uint8_t smte_minor_os; /**< 0x83: added later to lie about OS version */
+} LDRSMTE;
+AssertCompileSize(LDRSMTE, 0x84);
+
+typedef struct LDROTE
+{
+ uint32_t ote_size;
+ uint32_t ote_base;
+ uint32_t ote_flags;
+ uint32_t ote_pagemap;
+ uint32_t ote_mapsize;
+ union
+ {
+ uint32_t ote_vddaddr;
+ uint32_t ote_krnaddr;
+ struct
+ {
+ uint16_t ote_selector;
+ uint16_t ote_handle;
+ } s;
+ };
+} LDROTE;
+AssertCompileSize(LDROTE, 24);
+
+
+/**
+ * 32-bit system anchor block segment header.
+ */
+typedef struct SAS
+{
+ uint8_t SAS_signature[4];
+ uint16_t SAS_tables_data; /**< Offset to SASTABLES. */
+ uint16_t SAS_flat_sel; /**< 32-bit kernel DS (flat). */
+ uint16_t SAS_config_data; /**< Offset to SASCONFIG. */
+ uint16_t SAS_dd_data; /**< Offset to SASDD. */
+ uint16_t SAS_vm_data; /**< Offset to SASVM. */
+ uint16_t SAS_task_data; /**< Offset to SASTASK. */
+ uint16_t SAS_RAS_data; /**< Offset to SASRAS. */
+ uint16_t SAS_file_data; /**< Offset to SASFILE. */
+ uint16_t SAS_info_data; /**< Offset to SASINFO. */
+ uint16_t SAS_mp_data; /**< Offset to SASMP. SMP only. */
+} SAS;
+#define SAS_SIGNATURE "SAS "
+
+typedef struct SASTABLES
+{
+ uint16_t SAS_tbl_GDT;
+ uint16_t SAS_tbl_LDT;
+ uint16_t SAS_tbl_IDT;
+ uint16_t SAS_tbl_GDTPOOL;
+} SASTABLES;
+
+typedef struct SASCONFIG
+{
+ uint16_t SAS_config_table;
+} SASCONFIG;
+
+typedef struct SASDD
+{
+ uint16_t SAS_dd_bimodal_chain;
+ uint16_t SAS_dd_real_chain;
+ uint16_t SAS_dd_DPB_segment;
+ uint16_t SAS_dd_CDA_anchor_p;
+ uint16_t SAS_dd_CDA_anchor_r;
+ uint16_t SAS_dd_FSC;
+} SASDD;
+
+typedef struct SASVM
+{
+ uint32_t SAS_vm_arena;
+ uint32_t SAS_vm_object;
+ uint32_t SAS_vm_context;
+ uint32_t SAS_vm_krnl_mte; /**< Flat address of kernel MTE. */
+ uint32_t SAS_vm_glbl_mte; /**< Flat address of global MTE list head pointer variable. */
+ uint32_t SAS_vm_pft;
+ uint32_t SAS_vm_prt;
+ uint32_t SAS_vm_swap;
+ uint32_t SAS_vm_idle_head;
+ uint32_t SAS_vm_free_head;
+ uint32_t SAS_vm_heap_info;
+ uint32_t SAS_vm_all_mte; /**< Flat address of global MTE list head pointer variable. */
+} SASVM;
+
+
+#pragma pack(1)
+typedef struct SASTASK
+{
+ uint16_t SAS_task_PTDA; /**< Current PTDA selector. */
+ uint32_t SAS_task_ptdaptrs; /**< Flat address of process tree root. */
+ uint32_t SAS_task_threadptrs; /**< Flat address array of thread pointer array. */
+ uint32_t SAS_task_tasknumber; /**< Flat address of the TaskNumber variable. */
+ uint32_t SAS_task_threadcount; /**< Flat address of the ThreadCount variable. */
+} SASTASK;
+#pragma pack()
+
+
+#pragma pack(1)
+typedef struct SASRAS
+{
+ uint16_t SAS_RAS_STDA_p;
+ uint16_t SAS_RAS_STDA_r;
+ uint16_t SAS_RAS_event_mask;
+ uint32_t SAS_RAS_Perf_Buff;
+} SASRAS;
+#pragma pack()
+
+typedef struct SASFILE
+{
+ uint32_t SAS_file_MFT; /**< Handle. */
+ uint16_t SAS_file_SFT; /**< Selector. */
+ uint16_t SAS_file_VPB; /**< Selector. */
+ uint16_t SAS_file_CDS; /**< Selector. */
+ uint16_t SAS_file_buffers; /**< Selector. */
+} SASFILE;
+
+#pragma pack(1)
+typedef struct SASINFO
+{
+ uint16_t SAS_info_global; /**< GIS selector. */
+ uint32_t SAS_info_local; /**< 16:16 address of LIS for current task. */
+ uint32_t SAS_info_localRM;
+ uint16_t SAS_info_CDIB; /**< Selector. */
+} SASINFO;
+#pragma pack()
+
+typedef struct SASMP
+{
+ uint32_t SAS_mp_PCBFirst; /**< Flat address of PCB head. */
+ uint32_t SAS_mp_pLockHandles; /**< Flat address of lock handles. */
+ uint32_t SAS_mp_cProcessors; /**< Flat address of CPU count variable. */
+ uint32_t SAS_mp_pIPCInfo; /**< Flat address of IPC info pointer variable. */
+ uint32_t SAS_mp_pIPCHistory; /**< Flat address of IPC history pointer. */
+ uint32_t SAS_mp_IPCHistoryIdx; /**< Flat address of IPC history index variable. */
+ uint32_t SAS_mp_pFirstPSA; /**< Flat address of PSA. Added later. */
+ uint32_t SAS_mp_pPSAPages; /**< Flat address of PSA pages. */
+} SASMP;
+
+
+typedef struct OS2GIS
+{
+ uint32_t time;
+ uint32_t msecs;
+ uint8_t hour;
+ uint8_t minutes;
+ uint8_t seconds;
+ uint8_t hundredths;
+ int16_t timezone;
+ uint16_t cusecTimerInterval;
+ uint8_t day;
+ uint8_t month;
+ uint16_t year;
+ uint8_t weekday;
+ uint8_t uchMajorVersion;
+ uint8_t uchMinorVersion;
+ uint8_t chRevisionLetter;
+ uint8_t sgCurrent;
+ uint8_t sgMax;
+ uint8_t cHugeShift;
+ uint8_t fProtectModeOnly;
+ uint16_t pidForeground;
+ uint8_t fDynamicSched;
+ uint8_t csecMaxWait;
+ uint16_t cmsecMinSlice;
+ uint16_t cmsecMaxSlice;
+ uint16_t bootdrive;
+ uint8_t amecRAS[32];
+ uint8_t csgWindowableVioMax;
+ uint8_t csgPMMax;
+ uint16_t SIS_Syslog;
+ uint16_t SIS_MMIOBase;
+ uint16_t SIS_MMIOAddr;
+ uint8_t SIS_MaxVDMs;
+ uint8_t SIS_Reserved;
+} OS2GIS;
+
+typedef struct OS2LIS
+{
+ uint16_t pidCurrent;
+ uint16_t pidParent;
+ uint16_t prtyCurrent;
+ uint16_t tidCurrent;
+ uint16_t sgCurrent;
+ uint8_t rfProcStatus;
+ uint8_t bReserved1;
+ uint16_t fForeground;
+ uint8_t typeProcess;
+ uint8_t bReserved2;
+ uint16_t selEnvironment;
+ uint16_t offCmdLine;
+ uint16_t cbDataSegment;
+ uint16_t cbStack;
+ uint16_t cbHeap;
+ uint16_t hmod;
+ uint16_t selDS;
+} OS2LIS;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The 'SAS ' signature. */
+#define DIG_OS2_SAS_SIG RT_MAKE_U32_FROM_U8('S','A','S',' ')
+
+/** OS/2Warp on little endian ASCII systems. */
+#define DIG_OS2_MOD_TAG UINT64_C(0x43532f3257617270)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgDiggerOS2Init(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData);
+
+
+static int dbgDiggerOS2DisplaySelectorAndInfoEx(PDBGDIGGEROS2 pThis, PCDBGFINFOHLP pHlp, uint16_t uSel, uint32_t off,
+ int cchWidth, const char *pszMessage, PDBGFSELINFO pSelInfo)
+{
+ RT_ZERO(*pSelInfo);
+ int rc = pThis->pVMM->pfnDBGFR3SelQueryInfo(pThis->pUVM, 0 /*idCpu*/, uSel, DBGFSELQI_FLAGS_DT_GUEST, pSelInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (off == UINT32_MAX)
+ pHlp->pfnPrintf(pHlp, "%*s: %#06x (%RGv LB %#RX64 flags=%#x)\n",
+ cchWidth, pszMessage, uSel, pSelInfo->GCPtrBase, pSelInfo->cbLimit, pSelInfo->fFlags);
+ else
+ pHlp->pfnPrintf(pHlp, "%*s: %04x:%04x (%RGv LB %#RX64 flags=%#x)\n",
+ cchWidth, pszMessage, uSel, off, pSelInfo->GCPtrBase + off, pSelInfo->cbLimit - off, pSelInfo->fFlags);
+ }
+ else if (off == UINT32_MAX)
+ pHlp->pfnPrintf(pHlp, "%*s: %#06x (%Rrc)\n", cchWidth, pszMessage, uSel, rc);
+ else
+ pHlp->pfnPrintf(pHlp, "%*s: %04x:%04x (%Rrc)\n", cchWidth, pszMessage, uSel, off, rc);
+ return rc;
+}
+
+DECLINLINE(int) dbgDiggerOS2DisplaySelectorAndInfo(PDBGDIGGEROS2 pThis, PCDBGFINFOHLP pHlp, uint16_t uSel, uint32_t off,
+ int cchWidth, const char *pszMessage)
+{
+ DBGFSELINFO SelInfo;
+ return dbgDiggerOS2DisplaySelectorAndInfoEx(pThis, pHlp, uSel, off, cchWidth, pszMessage, &SelInfo);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLEREXT,
+ * Display the OS/2 system anchor segment}
+ */
+static DECLCALLBACK(void) dbgDiggerOS2InfoSas(void *pvUser, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PDBGDIGGEROS2 const pThis = (PDBGDIGGEROS2)pvUser;
+ PUVM const pUVM = pThis->pUVM;
+ PCVMMR3VTABLE const pVMM = pThis->pVMM;
+
+ DBGFSELINFO SelInfo;
+ int rc = pVMM->pfnDBGFR3SelQueryInfo(pUVM, 0 /*idCpu*/, 0x70, DBGFSELQI_FLAGS_DT_GUEST, &SelInfo);
+ if (RT_FAILURE(rc))
+ {
+ pHlp->pfnPrintf(pHlp, "DBGFR3SelQueryInfo failed on selector 0x70: %Rrc\n", rc);
+ return;
+ }
+ pHlp->pfnPrintf(pHlp, "Selector 0x70: %RGv LB %#RX64 (flags %#x)\n",
+ SelInfo.GCPtrBase, (uint64_t)SelInfo.cbLimit, SelInfo.fFlags);
+
+ /*
+ * The SAS header.
+ */
+ union
+ {
+ SAS Sas;
+ uint16_t au16Sas[sizeof(SAS) / sizeof(uint16_t)];
+ };
+ DBGFADDRESS Addr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SelInfo.GCPtrBase),
+ &Sas, sizeof(Sas));
+ if (RT_FAILURE(rc))
+ {
+ pHlp->pfnPrintf(pHlp, "Failed to read SAS header: %Rrc\n", rc);
+ return;
+ }
+ if (memcmp(&Sas.SAS_signature[0], SAS_SIGNATURE, sizeof(Sas.SAS_signature)) != 0)
+ {
+ pHlp->pfnPrintf(pHlp, "Invalid SAS signature: %#x %#x %#x %#x (expected %#x %#x %#x %#x)\n",
+ Sas.SAS_signature[0], Sas.SAS_signature[1], Sas.SAS_signature[2], Sas.SAS_signature[3],
+ SAS_SIGNATURE[0], SAS_SIGNATURE[1], SAS_SIGNATURE[2], SAS_SIGNATURE[3]);
+ return;
+ }
+ dbgDiggerOS2DisplaySelectorAndInfo(pThis, pHlp, Sas.SAS_flat_sel, UINT32_MAX, 15, "Flat kernel DS");
+ pHlp->pfnPrintf(pHlp, "SAS_tables_data: %#06x (%#RGv)\n", Sas.SAS_tables_data, SelInfo.GCPtrBase + Sas.SAS_tables_data);
+ pHlp->pfnPrintf(pHlp, "SAS_config_data: %#06x (%#RGv)\n", Sas.SAS_config_data, SelInfo.GCPtrBase + Sas.SAS_config_data);
+ pHlp->pfnPrintf(pHlp, " SAS_dd_data: %#06x (%#RGv)\n", Sas.SAS_dd_data, SelInfo.GCPtrBase + Sas.SAS_dd_data);
+ pHlp->pfnPrintf(pHlp, " SAS_vm_data: %#06x (%#RGv)\n", Sas.SAS_vm_data, SelInfo.GCPtrBase + Sas.SAS_vm_data);
+ pHlp->pfnPrintf(pHlp, " SAS_task_data: %#06x (%#RGv)\n", Sas.SAS_task_data, SelInfo.GCPtrBase + Sas.SAS_task_data);
+ pHlp->pfnPrintf(pHlp, " SAS_RAS_data: %#06x (%#RGv)\n", Sas.SAS_RAS_data, SelInfo.GCPtrBase + Sas.SAS_RAS_data);
+ pHlp->pfnPrintf(pHlp, " SAS_file_data: %#06x (%#RGv)\n", Sas.SAS_file_data, SelInfo.GCPtrBase + Sas.SAS_file_data);
+ pHlp->pfnPrintf(pHlp, " SAS_info_data: %#06x (%#RGv)\n", Sas.SAS_info_data, SelInfo.GCPtrBase + Sas.SAS_info_data);
+ bool fIncludeMP = true;
+ if (Sas.SAS_mp_data < sizeof(Sas))
+ fIncludeMP = false;
+ else
+ for (unsigned i = 2; i < RT_ELEMENTS(au16Sas) - 1; i++)
+ if (au16Sas[i] < sizeof(SAS))
+ {
+ fIncludeMP = false;
+ break;
+ }
+ if (fIncludeMP)
+ pHlp->pfnPrintf(pHlp, " SAS_mp_data: %#06x (%#RGv)\n", Sas.SAS_mp_data, SelInfo.GCPtrBase + Sas.SAS_mp_data);
+
+ /* shared databuf */
+ union
+ {
+ SASINFO Info;
+ } u;
+
+ /*
+ * Info data.
+ */
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SelInfo.GCPtrBase + Sas.SAS_info_data),
+ &u.Info, sizeof(u.Info));
+ if (RT_SUCCESS(rc))
+ {
+ pHlp->pfnPrintf(pHlp, "SASINFO:\n");
+ dbgDiggerOS2DisplaySelectorAndInfo(pThis, pHlp, u.Info.SAS_info_global, UINT32_MAX, 28, "Global info segment");
+ pHlp->pfnPrintf(pHlp, "%28s: %#010x\n", "Local info segment", u.Info.SAS_info_local);
+ pHlp->pfnPrintf(pHlp, "%28s: %#010x\n", "Local info segment (RM)", u.Info.SAS_info_localRM);
+ dbgDiggerOS2DisplaySelectorAndInfo(pThis, pHlp, u.Info.SAS_info_CDIB, UINT32_MAX, 28, "SAS_info_CDIB");
+ }
+ else
+ pHlp->pfnPrintf(pHlp, "Failed to read SAS info data: %Rrc\n", rc);
+
+ /** @todo more */
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLEREXT,
+ * Display the OS/2 global info segment}
+ */
+static DECLCALLBACK(void) dbgDiggerOS2InfoGis(void *pvUser, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PDBGDIGGEROS2 const pThis = (PDBGDIGGEROS2)pvUser;
+ PUVM const pUVM = pThis->pUVM;
+ PCVMMR3VTABLE const pVMM = pThis->pVMM;
+
+ DBGFSELINFO SelInfo;
+ int rc = dbgDiggerOS2DisplaySelectorAndInfoEx(pThis, pHlp, pThis->selGis, UINT32_MAX, 0, "Global info segment", &SelInfo);
+ if (RT_FAILURE(rc))
+ return;
+
+ /*
+ * Read the GIS.
+ */
+ DBGFADDRESS Addr;
+ OS2GIS Gis;
+ RT_ZERO(Gis);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SelInfo.GCPtrBase), &Gis,
+ RT_MIN(sizeof(Gis), SelInfo.cbLimit + 1));
+ if (RT_FAILURE(rc))
+ {
+ pHlp->pfnPrintf(pHlp, "Failed to read GIS: %Rrc\n", rc);
+ return;
+ }
+ pHlp->pfnPrintf(pHlp, " time: %#010x\n", Gis.time);
+ pHlp->pfnPrintf(pHlp, " msecs: %#010x\n", Gis.msecs);
+ pHlp->pfnPrintf(pHlp, " timestamp: %04u-%02u-%02u %02u:%02u:%02u.%02u\n",
+ Gis.year, Gis.month, Gis.day, Gis.hour, Gis.minutes, Gis.seconds, Gis.hundredths);
+ pHlp->pfnPrintf(pHlp, " timezone: %+2d (min delta)\n", (int)Gis.timezone);
+ pHlp->pfnPrintf(pHlp, " weekday: %u\n", Gis.weekday);
+ pHlp->pfnPrintf(pHlp, " cusecTimerInterval: %u\n", Gis.cusecTimerInterval);
+ pHlp->pfnPrintf(pHlp, " version: %u.%u\n", Gis.uchMajorVersion, Gis.uchMinorVersion);
+ pHlp->pfnPrintf(pHlp, " revision: %#04x (%c)\n", Gis.chRevisionLetter, Gis.chRevisionLetter);
+ pHlp->pfnPrintf(pHlp, " current screen grp: %#04x (%u)\n", Gis.sgCurrent, Gis.sgCurrent);
+ pHlp->pfnPrintf(pHlp, " max screen groups: %#04x (%u)\n", Gis.sgMax, Gis.sgMax);
+ pHlp->pfnPrintf(pHlp, "csgWindowableVioMax: %#x (%u)\n", Gis.csgWindowableVioMax, Gis.csgWindowableVioMax);
+ pHlp->pfnPrintf(pHlp, " csgPMMax: %#x (%u)\n", Gis.csgPMMax, Gis.csgPMMax);
+ pHlp->pfnPrintf(pHlp, " cHugeShift: %#04x\n", Gis.cHugeShift);
+ pHlp->pfnPrintf(pHlp, " fProtectModeOnly: %d\n", Gis.fProtectModeOnly);
+ pHlp->pfnPrintf(pHlp, " pidForeground: %#04x (%u)\n", Gis.pidForeground, Gis.pidForeground);
+ pHlp->pfnPrintf(pHlp, " fDynamicSched: %u\n", Gis.fDynamicSched);
+ pHlp->pfnPrintf(pHlp, " csecMaxWait: %u\n", Gis.csecMaxWait);
+ pHlp->pfnPrintf(pHlp, " cmsecMinSlice: %u\n", Gis.cmsecMinSlice);
+ pHlp->pfnPrintf(pHlp, " cmsecMaxSlice: %u\n", Gis.cmsecMaxSlice);
+ pHlp->pfnPrintf(pHlp, " bootdrive: %#x\n", Gis.bootdrive);
+ pHlp->pfnPrintf(pHlp, " amecRAS: %.32Rhxs\n", &Gis.amecRAS[0]);
+ pHlp->pfnPrintf(pHlp, " SIS_Syslog: %#06x (%u)\n", Gis.SIS_Syslog, Gis.SIS_Syslog);
+ pHlp->pfnPrintf(pHlp, " SIS_MMIOBase: %#06x\n", Gis.SIS_MMIOBase);
+ pHlp->pfnPrintf(pHlp, " SIS_MMIOAddr: %#06x\n", Gis.SIS_MMIOAddr);
+ pHlp->pfnPrintf(pHlp, " SIS_MaxVDMs: %#04x (%u)\n", Gis.SIS_MaxVDMs, Gis.SIS_MaxVDMs);
+ pHlp->pfnPrintf(pHlp, " SIS_Reserved: %#04x\n", Gis.SIS_Reserved);
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLEREXT,
+ * Display the OS/2 local info segment}
+ */
+static DECLCALLBACK(void) dbgDiggerOS2InfoLis(void *pvUser, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PDBGDIGGEROS2 const pThis = (PDBGDIGGEROS2)pvUser;
+ PUVM const pUVM = pThis->pUVM;
+ PCVMMR3VTABLE const pVMM = pThis->pVMM;
+
+ DBGFSELINFO SelInfo;
+ int rc = dbgDiggerOS2DisplaySelectorAndInfoEx(pThis, pHlp, pThis->Lis.sel, pThis->Lis.off, 19, "Local info segment", &SelInfo);
+ if (RT_FAILURE(rc))
+ return;
+
+ /*
+ * Read the LIS.
+ */
+ DBGFADDRESS Addr;
+ OS2LIS Lis;
+ RT_ZERO(Lis);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SelInfo.GCPtrBase + pThis->Lis.off),
+ &Lis, sizeof(Lis));
+ if (RT_FAILURE(rc))
+ {
+ pHlp->pfnPrintf(pHlp, "Failed to read LIS: %Rrc\n", rc);
+ return;
+ }
+ pHlp->pfnPrintf(pHlp, " pidCurrent: %#06x (%u)\n", Lis.pidCurrent, Lis.pidCurrent);
+ pHlp->pfnPrintf(pHlp, " pidParent: %#06x (%u)\n", Lis.pidParent, Lis.pidParent);
+ pHlp->pfnPrintf(pHlp, " prtyCurrent: %#06x (%u)\n", Lis.prtyCurrent, Lis.prtyCurrent);
+ pHlp->pfnPrintf(pHlp, " tidCurrent: %#06x (%u)\n", Lis.tidCurrent, Lis.tidCurrent);
+ pHlp->pfnPrintf(pHlp, " sgCurrent: %#06x (%u)\n", Lis.sgCurrent, Lis.sgCurrent);
+ pHlp->pfnPrintf(pHlp, " rfProcStatus: %#04x\n", Lis.rfProcStatus);
+ if (Lis.bReserved1)
+ pHlp->pfnPrintf(pHlp, " bReserved1: %#04x\n", Lis.bReserved1);
+ pHlp->pfnPrintf(pHlp, " fForeground: %#04x (%u)\n", Lis.fForeground, Lis.fForeground);
+ pHlp->pfnPrintf(pHlp, " typeProcess: %#04x (%u)\n", Lis.typeProcess, Lis.typeProcess);
+ if (Lis.bReserved2)
+ pHlp->pfnPrintf(pHlp, " bReserved2: %#04x\n", Lis.bReserved2);
+ dbgDiggerOS2DisplaySelectorAndInfo(pThis, pHlp, Lis.selEnvironment, UINT32_MAX, 19, "selEnvironment");
+ pHlp->pfnPrintf(pHlp, " offCmdLine: %#06x (%u)\n", Lis.offCmdLine, Lis.offCmdLine);
+ pHlp->pfnPrintf(pHlp, " cbDataSegment: %#06x (%u)\n", Lis.cbDataSegment, Lis.cbDataSegment);
+ pHlp->pfnPrintf(pHlp, " cbStack: %#06x (%u)\n", Lis.cbStack, Lis.cbStack);
+ pHlp->pfnPrintf(pHlp, " cbHeap: %#06x (%u)\n", Lis.cbHeap, Lis.cbHeap);
+ pHlp->pfnPrintf(pHlp, " hmod: %#06x\n", Lis.hmod); /** @todo look up the name*/
+ dbgDiggerOS2DisplaySelectorAndInfo(pThis, pHlp, Lis.selDS, UINT32_MAX, 19, "selDS");
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLEREXT,
+ * Display the OS/2 panic message}
+ */
+static DECLCALLBACK(void) dbgDiggerOS2InfoPanic(void *pvUser, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PDBGDIGGEROS2 const pThis = (PDBGDIGGEROS2)pvUser;
+ PUVM const pUVM = pThis->pUVM;
+ PCVMMR3VTABLE const pVMM = pThis->pVMM;
+
+ DBGFADDRESS HitAddr;
+ int rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &HitAddr, pThis->uKernelAddr),
+ pThis->cbKernel, 1, RT_STR_TUPLE("Exception in module:"), &HitAddr);
+ if (RT_FAILURE(rc))
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu&*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &HitAddr, pThis->uKernelAddr),
+ pThis->cbKernel, 1, RT_STR_TUPLE("Exception in device driver:"), &HitAddr);
+ /** @todo support pre-2001 kernels w/o the module/drivce name. */
+ if (RT_SUCCESS(rc))
+ {
+ char szMsg[728 + 1];
+ RT_ZERO(szMsg);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &HitAddr, szMsg, sizeof(szMsg) - 1);
+ if (szMsg[0] != '\0')
+ {
+ RTStrPurgeEncoding(szMsg);
+ char *psz = szMsg;
+ while (*psz != '\0')
+ {
+ char *pszEol = strchr(psz, '\r');
+ if (pszEol)
+ *pszEol = '\0';
+ pHlp->pfnPrintf(pHlp, "%s\n", psz);
+ if (!pszEol)
+ break;
+ psz = ++pszEol;
+ if (*psz == '\n')
+ psz++;
+ }
+ }
+ else
+ pHlp->pfnPrintf(pHlp, "DBGFR3MemRead -> %Rrc\n", rc);
+ }
+ else
+ pHlp->pfnPrintf(pHlp, "Unable to locate OS/2 panic message. (%Rrc)\n", rc);
+}
+
+
+
+/**
+ * @copydoc DBGFOSREG::pfnStackUnwindAssist
+ */
+static DECLCALLBACK(int) dbgDiggerOS2StackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu,
+ PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx,
+ RTDBGAS hAs, uint64_t *puScratch)
+{
+ RT_NOREF(pUVM, pVMM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryInterface
+ */
+static DECLCALLBACK(void *) dbgDiggerOS2QueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf)
+{
+ RT_NOREF(pUVM, pVMM, pvData, enmIf);
+ return NULL;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryVersion
+ */
+static DECLCALLBACK(int) dbgDiggerOS2QueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData,
+ char *pszVersion, size_t cchVersion)
+{
+ PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData;
+ RT_NOREF(pUVM, pVMM);
+ Assert(pThis->fValid);
+
+ char *achOS2ProductType[32];
+ char *pszOS2ProductType = (char *)achOS2ProductType;
+
+ if (pThis->OS2MajorVersion == 10)
+ {
+ RTStrPrintf(pszOS2ProductType, sizeof(achOS2ProductType), "OS/2 1.%02d", pThis->OS2MinorVersion);
+ pThis->enmVer = DBGDIGGEROS2VER_1_x;
+ }
+ else if (pThis->OS2MajorVersion == 20)
+ {
+ if (pThis->OS2MinorVersion < 30)
+ {
+ RTStrPrintf(pszOS2ProductType, sizeof(achOS2ProductType), "OS/2 2.%02d", pThis->OS2MinorVersion);
+ pThis->enmVer = DBGDIGGEROS2VER_2_x;
+ }
+ else if (pThis->OS2MinorVersion < 40)
+ {
+ RTStrPrintf(pszOS2ProductType, sizeof(achOS2ProductType), "OS/2 Warp");
+ pThis->enmVer = DBGDIGGEROS2VER_3_0;
+ }
+ else if (pThis->OS2MinorVersion == 40)
+ {
+ RTStrPrintf(pszOS2ProductType, sizeof(achOS2ProductType), "OS/2 Warp 4");
+ pThis->enmVer = DBGDIGGEROS2VER_4_0;
+ }
+ else
+ {
+ RTStrPrintf(pszOS2ProductType, sizeof(achOS2ProductType), "OS/2 Warp %d.%d",
+ pThis->OS2MinorVersion / 10, pThis->OS2MinorVersion % 10);
+ pThis->enmVer = DBGDIGGEROS2VER_4_5;
+ }
+ }
+ RTStrPrintf(pszVersion, cchVersion, "%u.%u (%s)", pThis->OS2MajorVersion, pThis->OS2MinorVersion, pszOS2ProductType);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnTerm
+ */
+static DECLCALLBACK(void) dbgDiggerOS2Term(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData;
+ Assert(pThis->fValid);
+
+ pVMM->pfnDBGFR3InfoDeregisterExternal(pUVM, "sas");
+ pVMM->pfnDBGFR3InfoDeregisterExternal(pUVM, "gis");
+ pVMM->pfnDBGFR3InfoDeregisterExternal(pUVM, "lis");
+ pVMM->pfnDBGFR3InfoDeregisterExternal(pUVM, "panic");
+
+ pThis->fValid = false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnRefresh
+ */
+static DECLCALLBACK(int) dbgDiggerOS2Refresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData;
+ NOREF(pThis);
+ Assert(pThis->fValid);
+
+ /*
+ * For now we'll flush and reload everything.
+ */
+ RTDBGAS hDbgAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hDbgAs != NIL_RTDBGAS)
+ {
+ uint32_t iMod = RTDbgAsModuleCount(hDbgAs);
+ while (iMod-- > 0)
+ {
+ RTDBGMOD hMod = RTDbgAsModuleByIndex(hDbgAs, iMod);
+ if (hMod != NIL_RTDBGMOD)
+ {
+ if (RTDbgModGetTag(hMod) == DIG_OS2_MOD_TAG)
+ {
+ int rc = RTDbgAsModuleUnlink(hDbgAs, hMod);
+ AssertRC(rc);
+ }
+ RTDbgModRelease(hMod);
+ }
+ }
+ RTDbgAsRelease(hDbgAs);
+ }
+
+ dbgDiggerOS2Term(pUVM, pVMM, pvData);
+ return dbgDiggerOS2Init(pUVM, pVMM, pvData);
+}
+
+
+/** Buffer shared by dbgdiggerOS2ProcessModule and dbgDiggerOS2Init.*/
+typedef union DBGDIGGEROS2BUF
+{
+ uint8_t au8[0x2000];
+ uint16_t au16[0x2000/2];
+ uint32_t au32[0x2000/4];
+ RTUTF16 wsz[0x2000/2];
+ char ach[0x2000];
+ LDROTE aOtes[0x2000 / sizeof(LDROTE)];
+ SAS sas;
+ SASVM sasvm;
+ LDRMTE mte;
+ LDRSMTE smte;
+ LDROTE ote;
+} DBGDIGGEROS2BUF;
+
+/** Arguments dbgdiggerOS2ProcessModule passes to the module open callback. */
+typedef struct
+{
+ const char *pszModPath;
+ const char *pszModName;
+ LDRMTE const *pMte;
+ LDRSMTE const *pSwapMte;
+} DBGDIGGEROS2OPEN;
+
+
+/**
+ * @callback_method_impl{FNRTDBGCFGOPEN, Debug image/image searching callback.}
+ */
+static DECLCALLBACK(int) dbgdiggerOs2OpenModule(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
+{
+ DBGDIGGEROS2OPEN *pArgs = (DBGDIGGEROS2OPEN *)pvUser1;
+
+ RTDBGMOD hDbgMod = NIL_RTDBGMOD;
+ int rc = RTDbgModCreateFromImage(&hDbgMod, pszFilename, pArgs->pszModName, RTLDRARCH_WHATEVER, hDbgCfg);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Do some info matching before using it? */
+
+ *(PRTDBGMOD)pvUser2 = hDbgMod;
+ return VINF_CALLBACK_RETURN;
+ }
+ LogRel(("DbgDiggerOs2: dbgdiggerOs2OpenModule: %Rrc - %s\n", rc, pszFilename));
+ return rc;
+}
+
+
+static void dbgdiggerOS2ProcessModule(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGEROS2 pThis, DBGDIGGEROS2BUF *pBuf,
+ const char *pszCacheSubDir, RTDBGAS hAs, RTDBGCFG hDbgCfg)
+{
+ RT_NOREF(pThis);
+
+ /*
+ * Save the MTE.
+ */
+ static const char * const s_apszMteFmts[4] = { "Reserved1", "NE", "LX", "Reserved2" };
+ LDRMTE const Mte = pBuf->mte;
+ if ((Mte.mte_flags2 & MTEFORMATMASK) != MTEFORMATLX)
+ {
+ LogRel(("DbgDiggerOs2: MTE format not implemented: %s (%d)\n",
+ s_apszMteFmts[(Mte.mte_flags2 & MTEFORMATMASK)], Mte.mte_flags2 & MTEFORMATMASK));
+ return;
+ }
+
+ /*
+ * Don't load program modules into the global address spaces.
+ */
+ if ((Mte.mte_flags1 & MTE1_CLASS_MASK) == MTE1_CLASS_PROGRAM)
+ {
+ LogRel(("DbgDiggerOs2: Program module, skipping.\n"));
+ return;
+ }
+
+ /*
+ * Try read the swappable MTE. Save it too.
+ */
+ DBGFADDRESS Addr;
+ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, Mte.mte_swapmte),
+ &pBuf->smte, sizeof(pBuf->smte));
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DbgDiggerOs2: Error reading swap mte @ %RX32: %Rrc\n", Mte.mte_swapmte, rc));
+ return;
+ }
+ LDRSMTE const SwapMte = pBuf->smte;
+
+ /* Ignore empty modules or modules with too many segments. */
+ if (SwapMte.smte_objcnt == 0 || SwapMte.smte_objcnt > RT_ELEMENTS(pBuf->aOtes))
+ {
+ LogRel(("DbgDiggerOs2: Skipping: smte_objcnt= %#RX32\n", SwapMte.smte_objcnt));
+ return;
+ }
+
+ /*
+ * Try read the path name, falling back on module name.
+ */
+ char szModPath[260];
+ rc = VERR_READ_ERROR;
+ if (SwapMte.smte_path != 0 && SwapMte.smte_pathlen > 0)
+ {
+ uint32_t cbToRead = RT_MIN(SwapMte.smte_path, sizeof(szModPath) - 1);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SwapMte.smte_path),
+ szModPath, cbToRead);
+ szModPath[cbToRead] = '\0';
+ }
+ if (RT_FAILURE(rc))
+ {
+ memcpy(szModPath, Mte.mte_modname, sizeof(Mte.mte_modname));
+ szModPath[sizeof(Mte.mte_modname)] = '\0';
+ RTStrStripR(szModPath);
+ }
+ LogRel(("DbgDiggerOS2: szModPath='%s'\n", szModPath));
+
+ /*
+ * Sanitize the module name.
+ */
+ char szModName[16];
+ memcpy(szModName, Mte.mte_modname, sizeof(Mte.mte_modname));
+ szModName[sizeof(Mte.mte_modname)] = '\0';
+ RTStrStripR(szModName);
+
+ /*
+ * Read the object table into the buffer.
+ */
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SwapMte.smte_objtab),
+ &pBuf->aOtes[0], sizeof(pBuf->aOtes[0]) * SwapMte.smte_objcnt);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DbgDiggerOs2: Error reading object table @ %#RX32 LB %#zx: %Rrc\n",
+ SwapMte.smte_objtab, sizeof(pBuf->aOtes[0]) * SwapMte.smte_objcnt, rc));
+ return;
+ }
+ for (uint32_t i = 0; i < SwapMte.smte_objcnt; i++)
+ {
+ LogRel(("DbgDiggerOs2: seg%u: %RX32 LB %#x\n", i, pBuf->aOtes[i].ote_base, pBuf->aOtes[i].ote_size));
+ /** @todo validate it. */
+ }
+
+ /*
+ * If it is the kernel, take down the general address range so we can easily search
+ * it all in one go when looking for panic messages and such.
+ */
+ if (Mte.mte_flags1 & MTE1_DOSMOD)
+ {
+ uint32_t uMax = 0;
+ uint32_t uMin = UINT32_MAX;
+ for (uint32_t i = 0; i < SwapMte.smte_objcnt; i++)
+ if (pBuf->aOtes[i].ote_base > _512M)
+ {
+ if (pBuf->aOtes[i].ote_base < uMin)
+ uMin = pBuf->aOtes[i].ote_base;
+ uint32_t uTmp = pBuf->aOtes[i].ote_base + pBuf->aOtes[i].ote_size;
+ if (uTmp > uMax)
+ uMax = uTmp;
+ }
+ if (uMax != 0)
+ {
+ pThis->uKernelAddr = uMin;
+ pThis->cbKernel = uMax - uMin;
+ LogRel(("DbgDiggerOs2: High kernel range: %#RX32 LB %#RX32 (%#RX32)\n", uMin, pThis->cbKernel, uMax));
+ }
+ }
+
+ /*
+ * No need to continue without an address space (shouldn't happen).
+ */
+ if (hAs == NIL_RTDBGAS)
+ return;
+
+ /*
+ * Try find a debug file for this module.
+ */
+ RTDBGMOD hDbgMod = NIL_RTDBGMOD;
+ if (hDbgCfg != NIL_RTDBGCFG)
+ {
+ DBGDIGGEROS2OPEN Args = { szModPath, szModName, &Mte, &SwapMte };
+ RTDbgCfgOpenEx(hDbgCfg, szModPath, pszCacheSubDir, NULL,
+ RT_OPSYS_OS2 | RTDBGCFG_O_CASE_INSENSITIVE | RTDBGCFG_O_EXECUTABLE_IMAGE
+ | RTDBGCFG_O_RECURSIVE | RTDBGCFG_O_NO_SYSTEM_PATHS,
+ dbgdiggerOs2OpenModule, &Args, &hDbgMod);
+ }
+
+ /*
+ * Fallback is a simple module into which we insert sections.
+ */
+ uint32_t cSegments = SwapMte.smte_objcnt;
+ if (hDbgMod == NIL_RTDBGMOD)
+ {
+ rc = RTDbgModCreate(&hDbgMod, szModName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uRva = 0;
+ for (uint32_t i = 0; i < SwapMte.smte_objcnt; i++)
+ {
+ char szSegNm[16];
+ RTStrPrintf(szSegNm, sizeof(szSegNm), "seg%u", i);
+ rc = RTDbgModSegmentAdd(hDbgMod, uRva, pBuf->aOtes[i].ote_size, szSegNm, 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DbgDiggerOs2: RTDbgModSegmentAdd failed (i=%u, ote_size=%#x): %Rrc\n",
+ i, pBuf->aOtes[i].ote_size, rc));
+ cSegments = i;
+ break;
+ }
+ uRva += RT_ALIGN_32(pBuf->aOtes[i].ote_size, _4K);
+ }
+ }
+ else
+ {
+ LogRel(("DbgDiggerOs2: RTDbgModCreate failed: %Rrc\n", rc));
+ return;
+ }
+ }
+
+ /*
+ * Tag the module and link its segments.
+ */
+ rc = RTDbgModSetTag(hDbgMod, DIG_OS2_MOD_TAG);
+ if (RT_SUCCESS(rc))
+ {
+ for (uint32_t i = 0; i < SwapMte.smte_objcnt; i++)
+ if (pBuf->aOtes[i].ote_base != 0)
+ {
+ rc = RTDbgAsModuleLinkSeg(hAs, hDbgMod, i, pBuf->aOtes[i].ote_base, RTDBGASLINK_FLAGS_REPLACE /*fFlags*/);
+ if (RT_FAILURE(rc))
+ LogRel(("DbgDiggerOs2: RTDbgAsModuleLinkSeg failed (i=%u, ote_base=%#x): %Rrc\n",
+ i, pBuf->aOtes[i].ote_base, rc));
+ }
+ }
+ else
+ LogRel(("DbgDiggerOs2: RTDbgModSetTag failed: %Rrc\n", rc));
+ RTDbgModRelease(hDbgMod);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnInit
+ */
+static DECLCALLBACK(int) dbgDiggerOS2Init(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData;
+ Assert(!pThis->fValid);
+
+ DBGDIGGEROS2BUF uBuf;
+ DBGFADDRESS Addr;
+ int rc;
+
+ /*
+ * Determine the OS/2 version.
+ */
+ /* Version info is at GIS:15h (major/minor/revision). */
+ rc = pVMM->pfnDBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, pThis->selGis, 0x15);
+ if (RT_FAILURE(rc))
+ return VERR_NOT_SUPPORTED;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, uBuf.au32, sizeof(uint32_t));
+ if (RT_FAILURE(rc))
+ return VERR_NOT_SUPPORTED;
+
+ pThis->OS2MajorVersion = uBuf.au8[0];
+ pThis->OS2MinorVersion = uBuf.au8[1];
+
+ pThis->fValid = true;
+
+ /*
+ * Try use SAS to find the module list.
+ */
+ rc = pVMM->pfnDBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, 0x70, 0x00);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &uBuf.sas, sizeof(uBuf.sas));
+ if (RT_SUCCESS(rc))
+ {
+ rc = pVMM->pfnDBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, 0x70, uBuf.sas.SAS_vm_data);
+ if (RT_SUCCESS(rc))
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &uBuf.sasvm, sizeof(uBuf.sasvm));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Work the module list.
+ */
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uBuf.sasvm.SAS_vm_all_mte),
+ &uBuf.au32[0], sizeof(uBuf.au32[0]));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uOs2Krnl = UINT32_MAX;
+ RTDBGCFG hDbgCfg = pVMM->pfnDBGFR3AsGetConfig(pUVM); /* (don't release this) */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_GLOBAL);
+
+ char szCacheSubDir[24];
+ RTStrPrintf(szCacheSubDir, sizeof(szCacheSubDir), "os2-%u.%u", pThis->OS2MajorVersion, pThis->OS2MinorVersion);
+
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uBuf.au32[0]);
+ while (Addr.FlatPtr != 0 && Addr.FlatPtr != UINT32_MAX)
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &uBuf.mte, sizeof(uBuf.mte));
+ if (RT_FAILURE(rc))
+ break;
+ LogRel(("DbgDiggerOs2: Module @ %#010RX32: %.8s %#x %#x\n", (uint32_t)Addr.FlatPtr,
+ uBuf.mte.mte_modname, uBuf.mte.mte_flags1, uBuf.mte.mte_flags2));
+ if (uBuf.mte.mte_flags1 & MTE1_DOSMOD)
+ uOs2Krnl = (uint32_t)Addr.FlatPtr;
+
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uBuf.mte.mte_link);
+ dbgdiggerOS2ProcessModule(pUVM, pVMM, pThis, &uBuf, szCacheSubDir, hAs, hDbgCfg);
+ }
+
+ /* Load the kernel again. To make sure we didn't drop any segments due
+ to overlap/conflicts/whatever. */
+ if (uOs2Krnl != UINT32_MAX)
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uOs2Krnl),
+ &uBuf.mte, sizeof(uBuf.mte));
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("DbgDiggerOs2: Module @ %#010RX32: %.8s %#x %#x [again]\n", (uint32_t)Addr.FlatPtr,
+ uBuf.mte.mte_modname, uBuf.mte.mte_flags1, uBuf.mte.mte_flags2));
+ dbgdiggerOS2ProcessModule(pUVM, pVMM, pThis, &uBuf, szCacheSubDir, hAs, hDbgCfg);
+ }
+ }
+
+ RTDbgAsRelease(hAs);
+ }
+ }
+ }
+ }
+
+ /*
+ * Register info handlers.
+ */
+ pVMM->pfnDBGFR3InfoRegisterExternal(pUVM, "sas", "Dumps the OS/2 system anchor block (SAS).", dbgDiggerOS2InfoSas, pThis);
+ pVMM->pfnDBGFR3InfoRegisterExternal(pUVM, "gis", "Dumps the OS/2 global info segment (GIS).", dbgDiggerOS2InfoGis, pThis);
+ pVMM->pfnDBGFR3InfoRegisterExternal(pUVM, "lis", "Dumps the OS/2 local info segment (current process).", dbgDiggerOS2InfoLis, pThis);
+ pVMM->pfnDBGFR3InfoRegisterExternal(pUVM, "panic", "Dumps the OS/2 system panic message.", dbgDiggerOS2InfoPanic, pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnProbe
+ */
+static DECLCALLBACK(bool) dbgDiggerOS2Probe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData;
+ DBGFADDRESS Addr;
+ int rc;
+ uint16_t offInfo;
+ union
+ {
+ uint8_t au8[8192];
+ uint16_t au16[8192/2];
+ uint32_t au32[8192/4];
+ RTUTF16 wsz[8192/2];
+ } u;
+
+ /*
+ * If the DWORD at 70:0 is 'SAS ' it's quite unlikely that this wouldn't be OS/2.
+ * Note: The SAS layout is similar between 16-bit and 32-bit OS/2, but not identical.
+ * 32-bit OS/2 will have the flat kernel data selector at SAS:06. The selector is 168h
+ * or similar. For 16-bit OS/2 the field contains a table offset into the SAS which will
+ * be much smaller. Fun fact: The global infoseg selector in the SAS is bimodal in 16-bit
+ * OS/2 and will work in real mode as well.
+ */
+ do {
+ rc = pVMM->pfnDBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, 0x70, 0x00);
+ if (RT_FAILURE(rc))
+ break;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, u.au32, 256);
+ if (RT_FAILURE(rc))
+ break;
+ if (u.au32[0] != DIG_OS2_SAS_SIG)
+ break;
+
+ /* This sure looks like OS/2, but a bit of paranoia won't hurt. */
+ if (u.au16[2] >= u.au16[4])
+ break;
+
+ /* If 4th word is bigger than 5th, it's the flat kernel mode selector. */
+ if (u.au16[3] > u.au16[4])
+ pThis->f32Bit = true;
+
+ /* Offset into info table is either at SAS:14h or SAS:16h. */
+ if (pThis->f32Bit)
+ offInfo = u.au16[0x14/2];
+ else
+ offInfo = u.au16[0x16/2];
+
+ /* The global infoseg selector is the first entry in the info table. */
+ SASINFO const *pInfo = (SASINFO const *)&u.au8[offInfo];
+ pThis->selGis = pInfo->SAS_info_global;
+ pThis->Lis.sel = RT_HI_U16(pInfo->SAS_info_local);
+ pThis->Lis.off = RT_LO_U16(pInfo->SAS_info_local);
+ return true;
+ } while (0);
+
+ return false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnDestruct
+ */
+static DECLCALLBACK(void) dbgDiggerOS2Destruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnConstruct
+ */
+static DECLCALLBACK(int) dbgDiggerOS2Construct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData;
+ pThis->fValid = false;
+ pThis->f32Bit = false;
+ pThis->enmVer = DBGDIGGEROS2VER_UNKNOWN;
+ pThis->pUVM = pUVM;
+ pThis->pVMM = pVMM;
+ return VINF_SUCCESS;
+}
+
+
+const DBGFOSREG g_DBGDiggerOS2 =
+{
+ /* .u32Magic = */ DBGFOSREG_MAGIC,
+ /* .fFlags = */ 0,
+ /* .cbData = */ sizeof(DBGDIGGEROS2),
+ /* .szName = */ "OS/2",
+ /* .pfnConstruct = */ dbgDiggerOS2Construct,
+ /* .pfnDestruct = */ dbgDiggerOS2Destruct,
+ /* .pfnProbe = */ dbgDiggerOS2Probe,
+ /* .pfnInit = */ dbgDiggerOS2Init,
+ /* .pfnRefresh = */ dbgDiggerOS2Refresh,
+ /* .pfnTerm = */ dbgDiggerOS2Term,
+ /* .pfnQueryVersion = */ dbgDiggerOS2QueryVersion,
+ /* .pfnQueryInterface = */ dbgDiggerOS2QueryInterface,
+ /* .pfnStackUnwindAssist = */ dbgDiggerOS2StackUnwindAssist,
+ /* .u32EndMagic = */ DBGFOSREG_MAGIC
+};
diff --git a/src/VBox/Debugger/DBGPlugInSolaris.cpp b/src/VBox/Debugger/DBGPlugInSolaris.cpp
new file mode 100644
index 00000000..9a8aefba
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInSolaris.cpp
@@ -0,0 +1,1162 @@
+/* $Id: DBGPlugInSolaris.cpp $ */
+/** @file
+ * DBGPlugInSolaris - Debugger and Guest OS Digger Plugin For Solaris.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugIns.h"
+#include "DBGPlugInCommonELF.h"
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Solaris on little endian ASCII systems. */
+#define DIG_SOL_MOD_TAG UINT64_C(0x00736972616c6f53)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** @name InternalSolaris structures
+ * @{ */
+
+/** sys/modctl.h */
+typedef struct SOL32v11_modctl
+{
+ uint32_t mod_next; /**< 0 */
+ uint32_t mod_prev; /**< 4 */
+ int32_t mod_id; /**< 8 */
+ uint32_t mod_mp; /**< c Pointer to the kernel runtime loader bits. */
+ uint32_t mod_inprogress_thread; /**< 10 */
+ uint32_t mod_modinfo; /**< 14 */
+ uint32_t mod_linkage; /**< 18 */
+ uint32_t mod_filename; /**< 1c */
+ uint32_t mod_modname; /**< 20 */
+ int8_t mod_busy; /**< 24 */
+ int8_t mod_want; /**< 25 */
+ int8_t mod_prim; /**< 26 this is 1 for 'unix' and a few others. */
+ int8_t mod_unused_padding; /**< 27 */
+ int32_t mod_ref; /**< 28 */
+ int8_t mod_loaded; /**< 2c */
+ int8_t mod_installed; /**< 2d */
+ int8_t mod_loadflags; /**< 2e */
+ int8_t mod_delay_unload; /**< 2f */
+ uint32_t mod_requisites; /**< 30 */
+ uint32_t mod___unused; /**< 34 */
+ int32_t mod_loadcnt; /**< 38 */
+ int32_t mod_nenabled; /**< 3c */
+ uint32_t mod_text; /**< 40 */
+ uint32_t mod_text_size; /**< 44 */
+ int32_t mod_gencount; /**< 48 */
+ uint32_t mod_requisite_loading; /**< 4c */
+} SOL32v11_modctl_t;
+AssertCompileSize(SOL32v11_modctl_t, 0x50);
+
+typedef struct SOL64v11_modctl
+{
+ uint64_t mod_next; /**< 0 */
+ uint64_t mod_prev; /**< 8 */
+ int32_t mod_id; /**< 10 */
+ int32_t mod_padding0;
+ uint64_t mod_mp; /**< 18 Pointer to the kernel runtime loader bits. */
+ uint64_t mod_inprogress_thread; /**< 20 */
+ uint64_t mod_modinfo; /**< 28 */
+ uint64_t mod_linkage; /**< 30 */
+ uint64_t mod_filename; /**< 38 */
+ uint64_t mod_modname; /**< 40 */
+ int8_t mod_busy; /**< 48 */
+ int8_t mod_want; /**< 49 */
+ int8_t mod_prim; /**< 4a this is 1 for 'unix' and a few others. */
+ int8_t mod_unused_padding; /**< 4b */
+ int32_t mod_ref; /**< 4c */
+ int8_t mod_loaded; /**< 50 */
+ int8_t mod_installed; /**< 51 */
+ int8_t mod_loadflags; /**< 52 */
+ int8_t mod_delay_unload; /**< 53 */
+ int32_t mod_padding1;
+ uint64_t mod_requisites; /**< 58 */
+ uint64_t mod___unused; /**< 60 */
+ int32_t mod_loadcnt; /**< 68 */
+ int32_t mod_nenabled; /**< 6c */
+ uint64_t mod_text; /**< 70 */
+ uint64_t mod_text_size; /**< 78 */
+ int32_t mod_gencount; /**< 80 */
+ int32_t mod_padding2;
+ uint64_t mod_requisite_loading; /**< 88 */
+} SOL64v11_modctl_t;
+AssertCompileSize(SOL64v11_modctl_t, 0x90);
+
+typedef struct SOL32v9_modctl
+{
+ uint32_t mod_next; /**< 0 */
+ uint32_t mod_prev; /**< 4 */
+ int32_t mod_id; /**< 8 */
+ uint32_t mod_mp; /**< c Pointer to the kernel runtime loader bits. */
+ uint32_t mod_inprogress_thread; /**< 10 */
+ uint32_t mod_modinfo; /**< 14 */
+ uint32_t mod_linkage; /**< 18 */
+ uint32_t mod_filename; /**< 1c */
+ uint32_t mod_modname; /**< 20 */
+ int32_t mod_busy; /**< 24 */
+ int32_t mod_stub; /**< 28 DIFF 1 */
+ int8_t mod_loaded; /**< 2c */
+ int8_t mod_installed; /**< 2d */
+ int8_t mod_loadflags; /**< 2e */
+ int8_t mod_want; /**< 2f DIFF 2 */
+ uint32_t mod_requisites; /**< 30 */
+ uint32_t mod_dependents; /**< 34 DIFF 3 */
+ int32_t mod_loadcnt; /**< 38 */
+ /* DIFF 4: 4 bytes added in v11 */
+ uint32_t mod_text; /**< 3c */
+ uint32_t mod_text_size; /**< 40 */
+ /* DIFF 5: 8 bytes added in v11 */
+} SOL32v9_modctl_t;
+AssertCompileSize(SOL32v9_modctl_t, 0x44);
+
+typedef struct SOL64v9_modctl
+{
+ uint64_t mod_next; /**< 0 */
+ uint64_t mod_prev; /**< 8 */
+ int32_t mod_id; /**< 10 */
+ int32_t mod_padding0;
+ uint64_t mod_mp; /**< 18 Pointer to the kernel runtime loader bits. */
+ uint64_t mod_inprogress_thread; /**< 20 */
+ uint64_t mod_modinfo; /**< 28 */
+ uint64_t mod_linkage; /**< 30 */
+ uint64_t mod_filename; /**< 38 */
+ uint64_t mod_modname; /**< 40 */
+ int32_t mod_busy; /**< 48 */
+ int32_t mod_stub; /**< 4c DIFF 1 - is this a pointer? */
+ int8_t mod_loaded; /**< 50 */
+ int8_t mod_installed; /**< 51 */
+ int8_t mod_loadflags; /**< 52 */
+ int8_t mod_want; /**< 53 DIFF 2 */
+ int32_t mod_padding1;
+ uint64_t mod_requisites; /**< 58 */
+ uint64_t mod_dependencies; /**< 60 DIFF 3 */
+ int32_t mod_loadcnt; /**< 68 */
+ int32_t mod_padding3; /**< 6c DIFF 4 */
+ uint64_t mod_text; /**< 70 */
+ uint64_t mod_text_size; /**< 78 */
+ /* DIFF 5: 8 bytes added in v11 */
+} SOL64v9_modctl_t;
+AssertCompileSize(SOL64v9_modctl_t, 0x80);
+
+typedef union SOL_modctl
+{
+ SOL32v9_modctl_t v9_32;
+ SOL32v11_modctl_t v11_32;
+ SOL64v9_modctl_t v9_64;
+ SOL64v11_modctl_t v11_64;
+} SOL_modctl_t;
+
+/** sys/kobj.h */
+typedef struct SOL32_module
+{
+ int32_t total_allocated; /**< 0 */
+ Elf32_Ehdr hdr; /**< 4 Easy to validate */
+ uint32_t shdrs; /**< 38 */
+ uint32_t symhdr; /**< 3c */
+ uint32_t strhdr; /**< 40 */
+ uint32_t depends_on; /**< 44 */
+ uint32_t symsize; /**< 48 */
+ uint32_t symspace; /**< 4c */
+ int32_t flags; /**< 50 */
+ uint32_t text_size; /**< 54 */
+ uint32_t data_size; /**< 58 */
+ uint32_t text; /**< 5c */
+ uint32_t data; /**< 60 */
+ uint32_t symtbl_section; /**< 64 */
+ uint32_t symtbl; /**< 68 */
+ uint32_t strings; /**< 6c */
+ uint32_t hashsize; /**< 70 */
+ uint32_t buckets; /**< 74 */
+ uint32_t chains; /**< 78 */
+ uint32_t nsyms; /**< 7c */
+ uint32_t bss_align; /**< 80 */
+ uint32_t bss_size; /**< 84 */
+ uint32_t bss; /**< 88 */
+ uint32_t filename; /**< 8c */
+ uint32_t head; /**< 90 */
+ uint32_t tail; /**< 94 */
+ uint32_t destination; /**< 98 */
+ uint32_t machdata; /**< 9c */
+ uint32_t ctfdata; /**< a0 */
+ uint32_t ctfsize; /**< a4 */
+ uint32_t fbt_tab; /**< a8 */
+ uint32_t fbt_size; /**< ac */
+ uint32_t fbt_nentries; /**< b0 */
+ uint32_t textwin; /**< b4 */
+ uint32_t textwin_base; /**< b8 */
+ uint32_t sdt_probes; /**< bc */
+ uint32_t sdt_nprobes; /**< c0 */
+ uint32_t sdt_tab; /**< c4 */
+ uint32_t sdt_size; /**< c8 */
+ uint32_t sigdata; /**< cc */
+ uint32_t sigsize; /**< d0 */
+} SOL32_module_t;
+AssertCompileSize(Elf32_Ehdr, 0x34);
+AssertCompileSize(SOL32_module_t, 0xd4);
+
+typedef struct SOL64_module
+{
+ int32_t total_allocated; /**< 0 */
+ int32_t padding0;
+ Elf64_Ehdr hdr; /**< 8 Easy to validate */
+ uint64_t shdrs; /**< 48 */
+ uint64_t symhdr; /**< 50 */
+ uint64_t strhdr; /**< 58 */
+ uint64_t depends_on; /**< 60 */
+ uint64_t symsize; /**< 68 */
+ uint64_t symspace; /**< 70 */
+ int32_t flags; /**< 78 */
+ int32_t padding1;
+ uint64_t text_size; /**< 80 */
+ uint64_t data_size; /**< 88 */
+ uint64_t text; /**< 90 */
+ uint64_t data; /**< 98 */
+ uint32_t symtbl_section; /**< a0 */
+ int32_t padding2;
+ uint64_t symtbl; /**< a8 */
+ uint64_t strings; /**< b0 */
+ uint32_t hashsize; /**< b8 */
+ int32_t padding3;
+ uint64_t buckets; /**< c0 */
+ uint64_t chains; /**< c8 */
+ uint32_t nsyms; /**< d0 */
+ uint32_t bss_align; /**< d4 */
+ uint64_t bss_size; /**< d8 */
+ uint64_t bss; /**< e0 */
+ uint64_t filename; /**< e8 */
+ uint64_t head; /**< f0 */
+ uint64_t tail; /**< f8 */
+ uint64_t destination; /**< 100 */
+ uint64_t machdata; /**< 108 */
+ uint64_t ctfdata; /**< 110 */
+ uint64_t ctfsize; /**< 118 */
+ uint64_t fbt_tab; /**< 120 */
+ uint64_t fbt_size; /**< 128 */
+ uint64_t fbt_nentries; /**< 130 */
+ uint64_t textwin; /**< 138 */
+ uint64_t textwin_base; /**< 140 */
+ uint64_t sdt_probes; /**< 148 */
+ uint64_t sdt_nprobes; /**< 150 */
+ uint64_t sdt_tab; /**< 158 */
+ uint64_t sdt_size; /**< 160 */
+ uint64_t sigdata; /**< 168 */
+ uint64_t sigsize; /**< 170 */
+} SOL64_module_t;
+AssertCompileSize(Elf64_Ehdr, 0x40);
+AssertCompileSize(SOL64_module_t, 0x178);
+
+typedef struct SOL_utsname
+{
+ char sysname[257];
+ char nodename[257];
+ char release[257];
+ char version[257];
+ char machine[257];
+} SOL_utsname_t;
+AssertCompileSize(SOL_utsname_t, 5 * 257);
+
+/** @} */
+
+
+/**
+ * Solaris guest OS digger instance data.
+ */
+typedef struct DBGDIGGERSOLARIS
+{
+ /** Whether the information is valid or not.
+ * (For fending off illegal interface method calls.) */
+ bool fValid;
+
+ /** Address of the 'unix' text segment.
+ * This is set during probing. */
+ DBGFADDRESS AddrUnixText;
+ /** Address of the 'unix' text segment.
+ * This is set during probing. */
+ DBGFADDRESS AddrUnixData;
+ /** Address of the 'unix' modctl_t (aka modules). */
+ DBGFADDRESS AddrUnixModCtl;
+ /** modctl_t version number. */
+ int iModCtlVer;
+ /** 64-bit/32-bit indicator. */
+ bool f64Bit;
+
+} DBGDIGGERSOLARIS;
+/** Pointer to the solaris guest OS digger instance data. */
+typedef DBGDIGGERSOLARIS *PDBGDIGGERSOLARIS;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Min kernel address. */
+#define SOL32_MIN_KRNL_ADDR UINT32_C(0x80000000)
+/** Max kernel address. */
+#define SOL32_MAX_KRNL_ADDR UINT32_C(0xfffff000)
+
+/** Min kernel address. */
+#define SOL64_MIN_KRNL_ADDR UINT64_C(0xFFFFC00000000000)
+/** Max kernel address. */
+#define SOL64_MAX_KRNL_ADDR UINT64_C(0xFFFFFFFFFFF00000)
+
+
+/** Validates a 32-bit solaris kernel address */
+#if 0 /* OpenSolaris, early boot have symspace at 0x27a2000 */
+# define SOL32_VALID_ADDRESS(Addr) ((Addr) > SOL32_MIN_KRNL_ADDR && (Addr) < SOL32_MAX_KRNL_ADDR)
+#else
+# define SOL32_VALID_ADDRESS(Addr) ( ((Addr) > SOL32_MIN_KRNL_ADDR && (Addr) < SOL32_MAX_KRNL_ADDR) \
+ || ((Addr) > UINT32_C(0x02000000) && (Addr) < UINT32_C(0x04000000)) /* boot */ )
+#endif
+
+/** Validates a 64-bit solaris kernel address */
+#define SOL64_VALID_ADDRESS(Addr) ( (Addr) > SOL64_MIN_KRNL_ADDR \
+ && (Addr) < SOL64_MAX_KRNL_ADDR)
+
+/** The max data segment size of the 'unix' module. */
+#define SOL_UNIX_MAX_DATA_SEG_SIZE 0x01000000
+
+/** The max code segment size of the 'unix' module.
+ * This is the same for both 64-bit and 32-bit. */
+#define SOL_UNIX_MAX_CODE_SEG_SIZE 0x00400000
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgDiggerSolarisInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData);
+
+
+
+/**
+ * @copydoc DBGFOSREG::pfnStackUnwindAssist
+ */
+static DECLCALLBACK(int) dbgDiggerSolarisStackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu,
+ PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState,
+ PCCPUMCTX pInitialCtx, RTDBGAS hAs, uint64_t *puScratch)
+{
+ RT_NOREF(pUVM, pVMM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryInterface
+ */
+static DECLCALLBACK(void *) dbgDiggerSolarisQueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf)
+{
+ RT_NOREF(pUVM, pVMM, pvData, enmIf);
+ return NULL;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryVersion
+ */
+static DECLCALLBACK(int) dbgDiggerSolarisQueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData,
+ char *pszVersion, size_t cchVersion)
+{
+ PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData;
+ Assert(pThis->fValid);
+
+ /*
+ * It's all in the utsname symbol...
+ */
+ SOL_utsname_t UtsName;
+ RT_ZERO(UtsName); /* Make MSC happy. */
+ DBGFADDRESS Addr;
+ RTDBGSYMBOL SymUtsName;
+ int rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "utsname", &SymUtsName, NULL);
+ if (RT_SUCCESS(rc))
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, SymUtsName.Value),
+ &UtsName, sizeof(UtsName));
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Try searching by the name...
+ */
+ memset(&UtsName, '\0', sizeof(UtsName));
+ strcpy(&UtsName.sysname[0], "SunOS");
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &pThis->AddrUnixData, SOL_UNIX_MAX_DATA_SEG_SIZE, 1,
+ &UtsName.sysname[0], sizeof(UtsName.sysname), &Addr);
+ if (RT_SUCCESS(rc))
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr,
+ Addr.FlatPtr - RT_OFFSETOF(SOL_utsname_t, sysname)),
+ &UtsName, sizeof(UtsName));
+ }
+
+ /*
+ * Copy out the result (if any).
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if ( UtsName.sysname[sizeof(UtsName.sysname) - 1] != '\0'
+ || UtsName.nodename[sizeof(UtsName.nodename) - 1] != '\0'
+ || UtsName.release[sizeof(UtsName.release) - 1] != '\0'
+ || UtsName.version[sizeof(UtsName.version) - 1] != '\0'
+ || UtsName.machine[sizeof(UtsName.machine) - 1] != '\0')
+ {
+ //rc = VERR_DBGF_UNEXPECTED_OS_DATA;
+ rc = VERR_GENERAL_FAILURE;
+ RTStrPrintf(pszVersion, cchVersion, "failed - bogus utsname");
+ }
+ else
+ RTStrPrintf(pszVersion, cchVersion, "%s %s", UtsName.version, UtsName.release);
+ }
+ else
+ RTStrPrintf(pszVersion, cchVersion, "failed - %Rrc", rc);
+
+ return rc;
+}
+
+
+
+/**
+ * Processes a modctl_t.
+ *
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis Our instance data.
+ * @param pModCtl Pointer to the modctl structure.
+ */
+static void dbgDiggerSolarisProcessModCtl32(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERSOLARIS pThis, SOL_modctl_t const *pModCtl)
+{
+ RT_NOREF1(pThis);
+
+ /* skip it if it's not loaded and installed */
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_loaded, v9_32.mod_loaded);
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_installed, v9_32.mod_installed);
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_id, v9_32.mod_id);
+ if ( ( !pModCtl->v9_32.mod_loaded
+ || !pModCtl->v9_32.mod_installed)
+ && pModCtl->v9_32.mod_id > 3)
+ return;
+
+ /*
+ * Read the module and file names first
+ */
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_modname, v9_32.mod_modname);
+ char szModName[64];
+ DBGFADDRESS Addr;
+ int rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pModCtl->v9_32.mod_modname),
+ szModName, sizeof(szModName));
+ if (RT_FAILURE(rc))
+ return;
+ if (!RTStrEnd(szModName, sizeof(szModName)))
+ szModName[sizeof(szModName) - 1] = '\0';
+
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_filename, v9_32.mod_filename);
+ char szFilename[256];
+ rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pModCtl->v9_32.mod_filename),
+ szFilename, sizeof(szFilename));
+ if (RT_FAILURE(rc))
+ strcpy(szFilename, szModName);
+ else if (!RTStrEnd(szFilename, sizeof(szFilename)))
+ szFilename[sizeof(szFilename) - 1] = '\0';
+
+ /*
+ * Then read the module struct and validate it.
+ */
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_mp, v9_32.mod_mp);
+ struct SOL32_module Module;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pModCtl->v9_32.mod_mp), &Module, sizeof(Module));
+ if (RT_FAILURE(rc))
+ return;
+
+ /* Basic validations of the elf header. */
+ if ( Module.hdr.e_ident[EI_MAG0] != ELFMAG0
+ || Module.hdr.e_ident[EI_MAG1] != ELFMAG1
+ || Module.hdr.e_ident[EI_MAG2] != ELFMAG2
+ || Module.hdr.e_ident[EI_MAG3] != ELFMAG3
+ || Module.hdr.e_ident[EI_CLASS] != ELFCLASS32
+ || Module.hdr.e_ident[EI_DATA] != ELFDATA2LSB
+ || Module.hdr.e_ident[EI_VERSION] != EV_CURRENT
+ || !ASMMemIsZero(&Module.hdr.e_ident[EI_PAD], EI_NIDENT - EI_PAD)
+ )
+ return;
+ if (Module.hdr.e_version != EV_CURRENT)
+ return;
+ if (Module.hdr.e_ehsize != sizeof(Module.hdr))
+ return;
+ if ( Module.hdr.e_type != ET_DYN
+ && Module.hdr.e_type != ET_REL
+ && Module.hdr.e_type != ET_EXEC) //??
+ return;
+ if ( Module.hdr.e_machine != EM_386
+ && Module.hdr.e_machine != EM_486)
+ return;
+ if ( Module.hdr.e_phentsize != sizeof(Elf32_Phdr)
+ && Module.hdr.e_phentsize) //??
+ return;
+ if (Module.hdr.e_shentsize != sizeof(Elf32_Shdr))
+ return;
+
+ if (Module.hdr.e_shentsize != sizeof(Elf32_Shdr))
+ return;
+
+ /* Basic validations of the rest of the stuff. */
+ if ( !SOL32_VALID_ADDRESS(Module.shdrs)
+ || !SOL32_VALID_ADDRESS(Module.symhdr)
+ || !SOL32_VALID_ADDRESS(Module.strhdr)
+ || (!SOL32_VALID_ADDRESS(Module.symspace) && Module.symspace)
+ || !SOL32_VALID_ADDRESS(Module.text)
+ || !SOL32_VALID_ADDRESS(Module.data)
+ || (!SOL32_VALID_ADDRESS(Module.symtbl) && Module.symtbl)
+ || (!SOL32_VALID_ADDRESS(Module.strings) && Module.strings)
+ || (!SOL32_VALID_ADDRESS(Module.head) && Module.head)
+ || (!SOL32_VALID_ADDRESS(Module.tail) && Module.tail)
+ || !SOL32_VALID_ADDRESS(Module.filename))
+ return;
+ if ( Module.symsize > _4M
+ || Module.hdr.e_shnum > 4096
+ || Module.nsyms > _256K)
+ return;
+
+ /* Ignore modules without symbols. */
+ if (!Module.symtbl || !Module.strings || !Module.symspace || !Module.symsize)
+ return;
+
+ /* Check that the symtbl and strings points inside the symspace. */
+ if (Module.strings - Module.symspace >= Module.symsize)
+ return;
+ if (Module.symtbl - Module.symspace >= Module.symsize)
+ return;
+
+ /*
+ * Read the section headers, symbol table and string tables.
+ */
+ size_t cb = Module.hdr.e_shnum * sizeof(Elf32_Shdr);
+ Elf32_Shdr *paShdrs = (Elf32_Shdr *)RTMemTmpAlloc(cb);
+ if (!paShdrs)
+ return;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, Module.shdrs), paShdrs, cb);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvSymSpace = RTMemTmpAlloc(Module.symsize + 1);
+ if (pvSymSpace)
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, Module.symspace),
+ pvSymSpace, Module.symsize);
+ if (RT_SUCCESS(rc))
+ {
+ ((uint8_t *)pvSymSpace)[Module.symsize] = 0;
+
+ /*
+ * Hand it over to the common ELF32 module parser.
+ */
+ char const *pbStrings = (char const *)pvSymSpace + (Module.strings - Module.symspace);
+ size_t cbMaxStrings = Module.symsize - (Module.strings - Module.symspace);
+
+ Elf32_Sym const *paSyms = (Elf32_Sym const *)((uintptr_t)pvSymSpace + (Module.symtbl - Module.symspace));
+ size_t cMaxSyms = (Module.symsize - (Module.symtbl - Module.symspace)) / sizeof(Elf32_Sym);
+ cMaxSyms = RT_MIN(cMaxSyms, Module.nsyms);
+
+ DBGDiggerCommonParseElf32Mod(pUVM, pVMM, szModName, szFilename, DBG_DIGGER_ELF_FUNNY_SHDRS,
+ &Module.hdr, paShdrs, paSyms, cMaxSyms, pbStrings, cbMaxStrings,
+ SOL32_MIN_KRNL_ADDR, SOL32_MAX_KRNL_ADDR - 1, DIG_SOL_MOD_TAG);
+ }
+ RTMemTmpFree(pvSymSpace);
+ }
+ }
+
+ RTMemTmpFree(paShdrs);
+ return;
+}
+
+
+/**
+ * Processes a modctl_t.
+ *
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pThis Our instance data.
+ * @param pModCtl Pointer to the modctl structure.
+ */
+static void dbgDiggerSolarisProcessModCtl64(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGDIGGERSOLARIS pThis, SOL_modctl_t const *pModCtl)
+{
+ RT_NOREF1(pThis);
+
+ /* skip it if it's not loaded and installed */
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_loaded, v9_64.mod_loaded);
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_installed, v9_64.mod_installed);
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_id, v9_64.mod_id);
+ if ( ( !pModCtl->v9_64.mod_loaded
+ || !pModCtl->v9_64.mod_installed)
+ && pModCtl->v9_64.mod_id > 3)
+ return;
+
+ /*
+ * Read the module and file names first
+ */
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_modname, v9_64.mod_modname);
+ char szModName[64];
+ DBGFADDRESS Addr;
+ int rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pModCtl->v9_64.mod_modname),
+ szModName, sizeof(szModName));
+ if (RT_FAILURE(rc))
+ return;
+ if (!RTStrEnd(szModName, sizeof(szModName)))
+ szModName[sizeof(szModName) - 1] = '\0';
+
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_filename, v9_64.mod_filename);
+ char szFilename[256];
+ rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pModCtl->v9_64.mod_filename),
+ szFilename, sizeof(szFilename));
+ if (RT_FAILURE(rc))
+ strcpy(szFilename, szModName);
+ else if (!RTStrEnd(szFilename, sizeof(szFilename)))
+ szFilename[sizeof(szFilename) - 1] = '\0';
+
+ /*
+ * Then read the module struct and validate it.
+ */
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_mp, v9_64.mod_mp);
+ struct SOL64_module Module;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pModCtl->v9_64.mod_mp), &Module, sizeof(Module));
+ if (RT_FAILURE(rc))
+ return;
+
+ /* Basic validations of the elf header. */
+ if ( Module.hdr.e_ident[EI_MAG0] != ELFMAG0
+ || Module.hdr.e_ident[EI_MAG1] != ELFMAG1
+ || Module.hdr.e_ident[EI_MAG2] != ELFMAG2
+ || Module.hdr.e_ident[EI_MAG3] != ELFMAG3
+ || Module.hdr.e_ident[EI_CLASS] != ELFCLASS64
+ || Module.hdr.e_ident[EI_DATA] != ELFDATA2LSB
+ || Module.hdr.e_ident[EI_VERSION] != EV_CURRENT
+ || !ASMMemIsZero(&Module.hdr.e_ident[EI_PAD], EI_NIDENT - EI_PAD)
+ )
+ return;
+ if (Module.hdr.e_version != EV_CURRENT)
+ return;
+ if (Module.hdr.e_ehsize != sizeof(Module.hdr))
+ return;
+ if ( Module.hdr.e_type != ET_DYN
+ && Module.hdr.e_type != ET_REL
+ && Module.hdr.e_type != ET_EXEC) //??
+ return;
+ if (Module.hdr.e_machine != EM_X86_64)
+ return;
+ if ( Module.hdr.e_phentsize != sizeof(Elf64_Phdr)
+ && Module.hdr.e_phentsize) //??
+ return;
+ if (Module.hdr.e_shentsize != sizeof(Elf64_Shdr))
+ return;
+
+ if (Module.hdr.e_shentsize != sizeof(Elf64_Shdr))
+ return;
+
+ /* Basic validations of the rest of the stuff. */
+ if ( !SOL64_VALID_ADDRESS(Module.shdrs)
+ || !SOL64_VALID_ADDRESS(Module.symhdr)
+ || !SOL64_VALID_ADDRESS(Module.strhdr)
+ || (!SOL64_VALID_ADDRESS(Module.symspace) && Module.symspace)
+ || !SOL64_VALID_ADDRESS(Module.text)
+ || !SOL64_VALID_ADDRESS(Module.data)
+ || (!SOL64_VALID_ADDRESS(Module.symtbl) && Module.symtbl)
+ || (!SOL64_VALID_ADDRESS(Module.strings) && Module.strings)
+ || (!SOL64_VALID_ADDRESS(Module.head) && Module.head)
+ || (!SOL64_VALID_ADDRESS(Module.tail) && Module.tail)
+ || !SOL64_VALID_ADDRESS(Module.filename))
+ return;
+ if ( Module.symsize > _4M
+ || Module.hdr.e_shnum > 4096
+ || Module.nsyms > _256K)
+ return;
+
+ /* Ignore modules without symbols. */
+ if (!Module.symtbl || !Module.strings || !Module.symspace || !Module.symsize)
+ return;
+
+ /* Check that the symtbl and strings points inside the symspace. */
+ if (Module.strings - Module.symspace >= Module.symsize)
+ return;
+ if (Module.symtbl - Module.symspace >= Module.symsize)
+ return;
+
+ /*
+ * Read the section headers, symbol table and string tables.
+ */
+ size_t cb = Module.hdr.e_shnum * sizeof(Elf64_Shdr);
+ Elf64_Shdr *paShdrs = (Elf64_Shdr *)RTMemTmpAlloc(cb);
+ if (!paShdrs)
+ return;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, Module.shdrs), paShdrs, cb);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvSymSpace = RTMemTmpAlloc(Module.symsize + 1);
+ if (pvSymSpace)
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, Module.symspace),
+ pvSymSpace, Module.symsize);
+ if (RT_SUCCESS(rc))
+ {
+ ((uint8_t *)pvSymSpace)[Module.symsize] = 0;
+
+ /*
+ * Hand it over to the common ELF64 module parser.
+ */
+ char const *pbStrings = (char const *)pvSymSpace + (Module.strings - Module.symspace);
+ size_t cbMaxStrings = Module.symsize - (Module.strings - Module.symspace);
+
+ Elf64_Sym const *paSyms = (Elf64_Sym const *)((uintptr_t)pvSymSpace + (uintptr_t)(Module.symtbl - Module.symspace));
+ size_t cMaxSyms = (Module.symsize - (Module.symtbl - Module.symspace)) / sizeof(Elf32_Sym);
+ cMaxSyms = RT_MIN(cMaxSyms, Module.nsyms);
+
+ DBGDiggerCommonParseElf64Mod(pUVM, pVMM, szModName, szFilename, DBG_DIGGER_ELF_FUNNY_SHDRS,
+ &Module.hdr, paShdrs, paSyms, cMaxSyms, pbStrings, cbMaxStrings,
+ SOL64_MIN_KRNL_ADDR, SOL64_MAX_KRNL_ADDR - 1, DIG_SOL_MOD_TAG);
+ }
+ RTMemTmpFree(pvSymSpace);
+ }
+ }
+
+ RTMemTmpFree(paShdrs);
+ return;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnTerm
+ */
+static DECLCALLBACK(void) dbgDiggerSolarisTerm(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData;
+ RT_NOREF(pUVM, pVMM);
+ Assert(pThis->fValid);
+
+ pThis->fValid = false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnRefresh
+ */
+static DECLCALLBACK(int) dbgDiggerSolarisRefresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData;
+ RT_NOREF(pThis);
+ Assert(pThis->fValid);
+
+ /*
+ * For now we'll flush and reload everything.
+ */
+ RTDBGAS hDbgAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hDbgAs != NIL_RTDBGAS)
+ {
+ uint32_t iMod = RTDbgAsModuleCount(hDbgAs);
+ while (iMod-- > 0)
+ {
+ RTDBGMOD hMod = RTDbgAsModuleByIndex(hDbgAs, iMod);
+ if (hMod != NIL_RTDBGMOD)
+ {
+ if (RTDbgModGetTag(hMod) == DIG_SOL_MOD_TAG)
+ {
+ int rc = RTDbgAsModuleUnlink(hDbgAs, hMod);
+ AssertRC(rc);
+ }
+ RTDbgModRelease(hMod);
+ }
+ }
+ RTDbgAsRelease(hDbgAs);
+ }
+
+ dbgDiggerSolarisTerm(pUVM, pVMM, pvData);
+ return dbgDiggerSolarisInit(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnInit
+ */
+static DECLCALLBACK(int) dbgDiggerSolarisInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData;
+ Assert(!pThis->fValid);
+ int rc;
+ size_t cbModCtl = 0;
+
+ /*
+ * On Solaris the kernel and is the global address space.
+ */
+ pVMM->pfnDBGFR3AsSetAlias(pUVM, DBGF_AS_KERNEL, DBGF_AS_GLOBAL);
+
+/** @todo Use debug_info, build 7x / S10U6. */
+
+ /*
+ * Find the 'unix' modctl_t structure (aka modules).
+ * We know it resides in the unix data segment.
+ */
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &pThis->AddrUnixModCtl, 0);
+
+ DBGFADDRESS CurAddr = pThis->AddrUnixData;
+ DBGFADDRESS MaxAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &MaxAddr, CurAddr.FlatPtr + SOL_UNIX_MAX_DATA_SEG_SIZE);
+ const uint8_t *pbExpr = (const uint8_t *)&pThis->AddrUnixText.FlatPtr;
+ const uint32_t cbExpr = pThis->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
+ while ( CurAddr.FlatPtr < MaxAddr.FlatPtr
+ && CurAddr.FlatPtr >= pThis->AddrUnixData.FlatPtr)
+ {
+ DBGFADDRESS HitAddr;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &CurAddr, MaxAddr.FlatPtr - CurAddr.FlatPtr, 1, pbExpr, cbExpr, &HitAddr);
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * Read out the modctl_t structure.
+ */
+ DBGFADDRESS ModCtlAddr;
+
+ /* v11 */
+ if (pThis->f64Bit)
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL32v11_modctl_t, mod_text));
+ SOL64v11_modctl_t ModCtlv11;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &ModCtlAddr, &ModCtlv11, sizeof(ModCtlv11));
+ if (RT_SUCCESS(rc))
+ {
+ if ( SOL64_VALID_ADDRESS(ModCtlv11.mod_next)
+ && SOL64_VALID_ADDRESS(ModCtlv11.mod_prev)
+ && ModCtlv11.mod_id == 0
+ && SOL64_VALID_ADDRESS(ModCtlv11.mod_mp)
+ && SOL64_VALID_ADDRESS(ModCtlv11.mod_filename)
+ && SOL64_VALID_ADDRESS(ModCtlv11.mod_modname)
+ && ModCtlv11.mod_prim == 1
+ && ModCtlv11.mod_loaded == 1
+ && ModCtlv11.mod_installed == 1
+ && ModCtlv11.mod_requisites == 0
+ && ModCtlv11.mod_loadcnt == 1
+ /*&& ModCtlv11.mod_text == pThis->AddrUnixText.FlatPtr*/
+ && ModCtlv11.mod_text_size < SOL_UNIX_MAX_CODE_SEG_SIZE
+ && ModCtlv11.mod_text_size >= _128K)
+ {
+ char szUnix[5];
+ DBGFADDRESS NameAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv11.mod_modname);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &NameAddr, &szUnix, sizeof(szUnix));
+ if (RT_SUCCESS(rc))
+ {
+ if (!strcmp(szUnix, "unix"))
+ {
+ pThis->AddrUnixModCtl = ModCtlAddr;
+ pThis->iModCtlVer = 11;
+ cbModCtl = sizeof(ModCtlv11);
+ break;
+ }
+ Log(("sol64 mod_name=%.*s v11\n", sizeof(szUnix), szUnix));
+ }
+ }
+ }
+ }
+ else
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL32v11_modctl_t, mod_text));
+ SOL32v11_modctl_t ModCtlv11;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &ModCtlAddr, &ModCtlv11, sizeof(ModCtlv11));
+ if (RT_SUCCESS(rc))
+ {
+ if ( SOL32_VALID_ADDRESS(ModCtlv11.mod_next)
+ && SOL32_VALID_ADDRESS(ModCtlv11.mod_prev)
+ && ModCtlv11.mod_id == 0
+ && SOL32_VALID_ADDRESS(ModCtlv11.mod_mp)
+ && SOL32_VALID_ADDRESS(ModCtlv11.mod_filename)
+ && SOL32_VALID_ADDRESS(ModCtlv11.mod_modname)
+ && ModCtlv11.mod_prim == 1
+ && ModCtlv11.mod_loaded == 1
+ && ModCtlv11.mod_installed == 1
+ && ModCtlv11.mod_requisites == 0
+ && ModCtlv11.mod_loadcnt == 1
+ /*&& ModCtlv11.mod_text == pThis->AddrUnixText.FlatPtr*/
+ && ModCtlv11.mod_text_size < SOL_UNIX_MAX_CODE_SEG_SIZE
+ && ModCtlv11.mod_text_size >= _128K)
+ {
+ char szUnix[5];
+ DBGFADDRESS NameAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv11.mod_modname);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &NameAddr, &szUnix, sizeof(szUnix));
+ if (RT_SUCCESS(rc))
+ {
+ if (!strcmp(szUnix, "unix"))
+ {
+ pThis->AddrUnixModCtl = ModCtlAddr;
+ pThis->iModCtlVer = 11;
+ cbModCtl = sizeof(ModCtlv11);
+ break;
+ }
+ Log(("sol32 mod_name=%.*s v11\n", sizeof(szUnix), szUnix));
+ }
+ }
+ }
+ }
+
+ /* v9 */
+ if (pThis->f64Bit)
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL64v9_modctl_t, mod_text));
+ SOL64v9_modctl_t ModCtlv9;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &ModCtlAddr, &ModCtlv9, sizeof(ModCtlv9));
+ if (RT_SUCCESS(rc))
+ {
+ if ( SOL64_VALID_ADDRESS(ModCtlv9.mod_next)
+ && SOL64_VALID_ADDRESS(ModCtlv9.mod_prev)
+ && ModCtlv9.mod_id == 0
+ && SOL64_VALID_ADDRESS(ModCtlv9.mod_mp)
+ && SOL64_VALID_ADDRESS(ModCtlv9.mod_filename)
+ && SOL64_VALID_ADDRESS(ModCtlv9.mod_modname)
+ && (ModCtlv9.mod_loaded == 1 || ModCtlv9.mod_loaded == 0)
+ && (ModCtlv9.mod_installed == 1 || ModCtlv9.mod_installed == 0)
+ && ModCtlv9.mod_requisites == 0
+ && (ModCtlv9.mod_loadcnt == 1 || ModCtlv9.mod_loadcnt == 0)
+ /*&& ModCtlv9.mod_text == pThis->AddrUnixText.FlatPtr*/
+ && ModCtlv9.mod_text_size < SOL_UNIX_MAX_CODE_SEG_SIZE)
+ {
+ char szUnix[5];
+ DBGFADDRESS NameAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv9.mod_modname);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &NameAddr, &szUnix, sizeof(szUnix));
+ if (RT_SUCCESS(rc))
+ {
+ if (!strcmp(szUnix, "unix"))
+ {
+ pThis->AddrUnixModCtl = ModCtlAddr;
+ pThis->iModCtlVer = 9;
+ cbModCtl = sizeof(ModCtlv9);
+ break;
+ }
+ Log(("sol64 mod_name=%.*s v9\n", sizeof(szUnix), szUnix));
+ }
+ }
+ }
+ }
+ else
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL32v9_modctl_t, mod_text));
+ SOL32v9_modctl_t ModCtlv9;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &ModCtlAddr, &ModCtlv9, sizeof(ModCtlv9));
+ if (RT_SUCCESS(rc))
+ {
+ if ( SOL32_VALID_ADDRESS(ModCtlv9.mod_next)
+ && SOL32_VALID_ADDRESS(ModCtlv9.mod_prev)
+ && ModCtlv9.mod_id == 0
+ && SOL32_VALID_ADDRESS(ModCtlv9.mod_mp)
+ && SOL32_VALID_ADDRESS(ModCtlv9.mod_filename)
+ && SOL32_VALID_ADDRESS(ModCtlv9.mod_modname)
+ && (ModCtlv9.mod_loaded == 1 || ModCtlv9.mod_loaded == 0)
+ && (ModCtlv9.mod_installed == 1 || ModCtlv9.mod_installed == 0)
+ && ModCtlv9.mod_requisites == 0
+ && (ModCtlv9.mod_loadcnt == 1 || ModCtlv9.mod_loadcnt == 0)
+ /*&& ModCtlv9.mod_text == pThis->AddrUnixText.FlatPtr*/
+ && ModCtlv9.mod_text_size < SOL_UNIX_MAX_CODE_SEG_SIZE )
+ {
+ char szUnix[5];
+ DBGFADDRESS NameAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv9.mod_modname);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &NameAddr, &szUnix, sizeof(szUnix));
+ if (RT_SUCCESS(rc))
+ {
+ if (!strcmp(szUnix, "unix"))
+ {
+ pThis->AddrUnixModCtl = ModCtlAddr;
+ pThis->iModCtlVer = 9;
+ cbModCtl = sizeof(ModCtlv9);
+ break;
+ }
+ Log(("sol32 mod_name=%.*s v9\n", sizeof(szUnix), szUnix));
+ }
+ }
+ }
+ }
+
+ /* next */
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &CurAddr, HitAddr.FlatPtr + cbExpr);
+ }
+
+ /*
+ * Walk the module chain and add the modules and their symbols.
+ */
+ if (pThis->AddrUnixModCtl.FlatPtr)
+ {
+ int iMod = 0;
+ CurAddr = pThis->AddrUnixModCtl;
+ do
+ {
+ /* read it */
+ SOL_modctl_t ModCtl;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0, &CurAddr, &ModCtl, cbModCtl);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("sol: bad modctl_t chain for module %d: %RGv - %Rrc\n", iMod, CurAddr.FlatPtr, rc));
+ break;
+ }
+
+ /* process it. */
+ if (pThis->f64Bit)
+ dbgDiggerSolarisProcessModCtl64(pUVM, pVMM, pThis, &ModCtl);
+ else
+ dbgDiggerSolarisProcessModCtl32(pUVM, pVMM, pThis, &ModCtl);
+
+ /* next */
+ if (pThis->f64Bit)
+ {
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_64.mod_next, v9_64.mod_next);
+ if (!SOL64_VALID_ADDRESS(ModCtl.v9_64.mod_next))
+ {
+ LogRel(("sol64: bad modctl_t chain for module %d at %RGv: %RGv\n", iMod, CurAddr.FlatPtr, (RTGCUINTPTR)ModCtl.v9_64.mod_next));
+ break;
+ }
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &CurAddr, ModCtl.v9_64.mod_next);
+ }
+ else
+ {
+ AssertCompile2MemberOffsets(SOL_modctl_t, v11_32.mod_next, v9_32.mod_next);
+ if (!SOL32_VALID_ADDRESS(ModCtl.v9_32.mod_next))
+ {
+ LogRel(("sol32: bad modctl_t chain for module %d at %RGv: %RGv\n", iMod, CurAddr.FlatPtr, (RTGCUINTPTR)ModCtl.v9_32.mod_next));
+ break;
+ }
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &CurAddr, ModCtl.v9_32.mod_next);
+ }
+ if (++iMod >= 1024)
+ {
+ LogRel(("sol32: too many modules (%d)\n", iMod));
+ break;
+ }
+ } while (CurAddr.FlatPtr != pThis->AddrUnixModCtl.FlatPtr);
+ }
+
+ pThis->fValid = true;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnProbe
+ */
+static DECLCALLBACK(bool) dbgDiggerSolarisProbe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData;
+
+ /*
+ * Look for "SunOS Release" in the text segment.
+ */
+ DBGFADDRESS Addr;
+ bool f64Bit = false;
+
+ /* 32-bit search range. */
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, 0xfe800000);
+ RTGCUINTPTR cbRange = 0xfec00000 - 0xfe800000;
+
+ DBGFADDRESS HitAddr;
+ static const uint8_t s_abSunRelease[] = "SunOS Release ";
+ int rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &Addr, cbRange, 1, s_abSunRelease, sizeof(s_abSunRelease) - 1, &HitAddr);
+ if (RT_FAILURE(rc))
+ {
+ /* 64-bit.... */
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, UINT64_C(0xfffffffffb800000));
+ cbRange = UINT64_C(0xfffffffffbd00000) - UINT64_C(0xfffffffffb800000);
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &Addr, cbRange, 1, s_abSunRelease, sizeof(s_abSunRelease) - 1, &HitAddr);
+ if (RT_FAILURE(rc))
+ return false;
+ f64Bit = true;
+ }
+
+ /*
+ * Look for the copyright string too, just to be sure.
+ */
+ static const uint8_t s_abSMI[] = "Sun Microsystems, Inc.";
+ static const uint8_t s_abORCL[] = "Oracle and/or its affiliates.";
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &Addr, cbRange, 1, s_abSMI, sizeof(s_abSMI) - 1, &HitAddr);
+ if (RT_FAILURE(rc))
+ {
+ /* Try the alternate copyright string. */
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0, &Addr, cbRange, 1, s_abORCL, sizeof(s_abORCL) - 1, &HitAddr);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+
+ /*
+ * Remember the unix text and data addresses and bitness.
+ */
+ pThis->AddrUnixText = Addr;
+ pVMM->pfnDBGFR3AddrAdd(&Addr, SOL_UNIX_MAX_CODE_SEG_SIZE);
+ pThis->AddrUnixData = Addr;
+ pThis->f64Bit = f64Bit;
+
+ return true;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnDestruct
+ */
+static DECLCALLBACK(void) dbgDiggerSolarisDestruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnConstruct
+ */
+static DECLCALLBACK(int) dbgDiggerSolarisConstruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+ return VINF_SUCCESS;
+}
+
+
+const DBGFOSREG g_DBGDiggerSolaris =
+{
+ /* .u32Magic = */ DBGFOSREG_MAGIC,
+ /* .fFlags = */ 0,
+ /* .cbData = */ sizeof(DBGDIGGERSOLARIS),
+ /* .szName = */ "Solaris",
+ /* .pfnConstruct = */ dbgDiggerSolarisConstruct,
+ /* .pfnDestruct = */ dbgDiggerSolarisDestruct,
+ /* .pfnProbe = */ dbgDiggerSolarisProbe,
+ /* .pfnInit = */ dbgDiggerSolarisInit,
+ /* .pfnRefresh = */ dbgDiggerSolarisRefresh,
+ /* .pfnTerm = */ dbgDiggerSolarisTerm,
+ /* .pfnQueryVersion = */ dbgDiggerSolarisQueryVersion,
+ /* .pfnQueryInterface = */ dbgDiggerSolarisQueryInterface,
+ /* .pfnStackUnwindAssist = */ dbgDiggerSolarisStackUnwindAssist,
+ /* .u32EndMagic = */ DBGFOSREG_MAGIC
+};
+
diff --git a/src/VBox/Debugger/DBGPlugInWinNt.cpp b/src/VBox/Debugger/DBGPlugInWinNt.cpp
new file mode 100644
index 00000000..8bdefc3f
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugInWinNt.cpp
@@ -0,0 +1,1776 @@
+/* $Id: DBGPlugInWinNt.cpp $ */
+/** @file
+ * DBGPlugInWindows - Debugger and Guest OS Digger Plugin For Windows NT.
+ */
+
+/*
+ * Copyright (C) 2009-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group.
+#include "DBGPlugIns.h"
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/cpumctx.h>
+#include <VBox/vmm/mm.h>
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+# include <VBox/vmm/vmapi.h>
+# include <VBox/dis.h>
+#endif
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/err.h>
+#include <VBox/param.h>
+#include <iprt/ctype.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/formats/pecoff.h>
+#include <iprt/formats/mz.h>
+#include <iprt/nt/nt-structures.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** @name Internal WinNT structures
+ * @{ */
+/**
+ * PsLoadedModuleList entry for 32-bit NT aka LDR_DATA_TABLE_ENTRY.
+ * Tested with XP.
+ */
+typedef struct NTMTE32
+{
+ struct
+ {
+ uint32_t Flink;
+ uint32_t Blink;
+ } InLoadOrderLinks,
+ InMemoryOrderModuleList,
+ InInitializationOrderModuleList;
+ uint32_t DllBase;
+ uint32_t EntryPoint;
+ /** @note This field is not a size in NT 3.1. It's NULL for images loaded by the
+ * boot loader, for other images it looks like some kind of pointer. */
+ uint32_t SizeOfImage;
+ struct
+ {
+ uint16_t Length;
+ uint16_t MaximumLength;
+ uint32_t Buffer;
+ } FullDllName,
+ BaseDllName;
+ uint32_t Flags;
+ uint16_t LoadCount;
+ uint16_t TlsIndex;
+ /* ... there is more ... */
+} NTMTE32;
+typedef NTMTE32 *PNTMTE32;
+
+/**
+ * PsLoadedModuleList entry for 64-bit NT aka LDR_DATA_TABLE_ENTRY.
+ */
+typedef struct NTMTE64
+{
+ struct
+ {
+ uint64_t Flink;
+ uint64_t Blink;
+ } InLoadOrderLinks, /**< 0x00 */
+ InMemoryOrderModuleList, /**< 0x10 */
+ InInitializationOrderModuleList; /**< 0x20 */
+ uint64_t DllBase; /**< 0x30 */
+ uint64_t EntryPoint; /**< 0x38 */
+ uint32_t SizeOfImage; /**< 0x40 */
+ uint32_t Alignment; /**< 0x44 */
+ struct
+ {
+ uint16_t Length; /**< 0x48,0x58 */
+ uint16_t MaximumLength; /**< 0x4a,0x5a */
+ uint32_t Alignment; /**< 0x4c,0x5c */
+ uint64_t Buffer; /**< 0x50,0x60 */
+ } FullDllName, /**< 0x48 */
+ BaseDllName; /**< 0x58 */
+ uint32_t Flags; /**< 0x68 */
+ uint16_t LoadCount; /**< 0x6c */
+ uint16_t TlsIndex; /**< 0x6e */
+ /* ... there is more ... */
+} NTMTE64;
+typedef NTMTE64 *PNTMTE64;
+
+/** MTE union. */
+typedef union NTMTE
+{
+ NTMTE32 vX_32;
+ NTMTE64 vX_64;
+} NTMTE;
+typedef NTMTE *PNTMTE;
+
+
+/**
+ * The essential bits of the KUSER_SHARED_DATA structure.
+ */
+typedef struct NTKUSERSHAREDDATA
+{
+ uint32_t TickCountLowDeprecated;
+ uint32_t TickCountMultiplier;
+ struct
+ {
+ uint32_t LowPart;
+ int32_t High1Time;
+ int32_t High2Time;
+
+ } InterruptTime,
+ SystemTime,
+ TimeZoneBias;
+ uint16_t ImageNumberLow;
+ uint16_t ImageNumberHigh;
+ RTUTF16 NtSystemRoot[260];
+ uint32_t MaxStackTraceDepth;
+ uint32_t CryptoExponent;
+ uint32_t TimeZoneId;
+ uint32_t LargePageMinimum;
+ uint32_t Reserved2[6];
+ uint32_t NtBuildNumber;
+ uint32_t NtProductType;
+ uint8_t ProductTypeIsValid;
+ uint8_t abPadding[3];
+ uint32_t NtMajorVersion;
+ uint32_t NtMinorVersion;
+ /* uint8_t ProcessorFeatures[64];
+ ...
+ */
+} NTKUSERSHAREDDATA;
+typedef NTKUSERSHAREDDATA *PNTKUSERSHAREDDATA;
+
+/** KI_USER_SHARED_DATA for i386 */
+#define NTKUSERSHAREDDATA_WINNT32 UINT32_C(0xffdf0000)
+/** KI_USER_SHARED_DATA for AMD64 */
+#define NTKUSERSHAREDDATA_WINNT64 UINT64_C(0xfffff78000000000)
+
+/** NTKUSERSHAREDDATA::NtProductType */
+typedef enum NTPRODUCTTYPE
+{
+ kNtProductType_Invalid = 0,
+ kNtProductType_WinNt = 1,
+ kNtProductType_LanManNt,
+ kNtProductType_Server
+} NTPRODUCTTYPE;
+
+
+/** NT image header union. */
+typedef union NTHDRSU
+{
+ IMAGE_NT_HEADERS32 vX_32;
+ IMAGE_NT_HEADERS64 vX_64;
+} NTHDRS;
+/** Pointer to NT image header union. */
+typedef NTHDRS *PNTHDRS;
+/** Pointer to const NT image header union. */
+typedef NTHDRS const *PCNTHDRS;
+
+
+/**
+ * NT KD version block.
+ */
+typedef struct NTKDVERSIONBLOCK
+{
+ uint16_t MajorVersion;
+ uint16_t MinorVersion;
+ uint8_t ProtocolVersion;
+ uint8_t KdSecondaryVersion;
+ uint16_t Flags;
+ uint16_t MachineType;
+ uint8_t MaxPacketType;
+ uint8_t MaxStateChange;
+ uint8_t MaxManipulate;
+ uint8_t Simulation;
+ uint16_t Unused;
+ uint64_t KernBase;
+ uint64_t PsLoadedModuleList;
+ uint64_t DebuggerDataList;
+} NTKDVERSIONBLOCK;
+/** Pointer to an NT KD version block. */
+typedef NTKDVERSIONBLOCK *PNTKDVERSIONBLOCK;
+/** Pointer to a const NT KD version block. */
+typedef const NTKDVERSIONBLOCK *PCNTKDVERSIONBLOCK;
+
+/** @} */
+
+
+
+typedef enum DBGDIGGERWINNTVER
+{
+ DBGDIGGERWINNTVER_UNKNOWN,
+ DBGDIGGERWINNTVER_3_1,
+ DBGDIGGERWINNTVER_3_5,
+ DBGDIGGERWINNTVER_4_0,
+ DBGDIGGERWINNTVER_5_0,
+ DBGDIGGERWINNTVER_5_1,
+ DBGDIGGERWINNTVER_6_0
+} DBGDIGGERWINNTVER;
+
+/**
+ * WinNT guest OS digger instance data.
+ */
+typedef struct DBGDIGGERWINNT
+{
+ /** Whether the information is valid or not.
+ * (For fending off illegal interface method calls.) */
+ bool fValid;
+ /** 32-bit (true) or 64-bit (false) */
+ bool f32Bit;
+ /** Set if NT 3.1 was detected.
+ * This implies both Misc.VirtualSize and NTMTE32::SizeOfImage are zero. */
+ bool fNt31;
+
+ /** The NT version. */
+ DBGDIGGERWINNTVER enmVer;
+ /** NTKUSERSHAREDDATA::NtProductType */
+ NTPRODUCTTYPE NtProductType;
+ /** NTKUSERSHAREDDATA::NtMajorVersion */
+ uint32_t NtMajorVersion;
+ /** NTKUSERSHAREDDATA::NtMinorVersion */
+ uint32_t NtMinorVersion;
+ /** NTKUSERSHAREDDATA::NtBuildNumber */
+ uint32_t NtBuildNumber;
+
+ /** The address of the ntoskrnl.exe image. */
+ DBGFADDRESS KernelAddr;
+ /** The address of the ntoskrnl.exe module table entry. */
+ DBGFADDRESS KernelMteAddr;
+ /** The address of PsLoadedModuleList. */
+ DBGFADDRESS PsLoadedModuleListAddr;
+
+ /** Array of detected KPCR addresses for each vCPU. */
+ PDBGFADDRESS paKpcrAddr;
+ /** Array of detected KPCRB addresses for each vCPU. */
+ PDBGFADDRESS paKpcrbAddr;
+
+ /** The Windows NT specifics interface. */
+ DBGFOSIWINNT IWinNt;
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+ /** Breakpoint owner handle for the DbgPrint/vDbgPrint{,Ex}... interception. */
+ DBGFBPOWNER hBpOwnerDbgPrint;
+ /** Breakpoint handle for the DbgPrint/vDbgPrint{,Ex}... interception. */
+ DBGFBP hBpDbgPrint;
+#endif
+} DBGDIGGERWINNT;
+/** Pointer to the linux guest OS digger instance data. */
+typedef DBGDIGGERWINNT *PDBGDIGGERWINNT;
+
+
+/**
+ * The WinNT digger's loader reader instance data.
+ */
+typedef struct DBGDIGGERWINNTRDR
+{
+ /** The VM handle (referenced). */
+ PUVM pUVM;
+ /** The image base. */
+ DBGFADDRESS ImageAddr;
+ /** The image size. */
+ uint32_t cbImage;
+ /** The file offset of the SizeOfImage field in the optional header if it
+ * needs patching, otherwise set to UINT32_MAX. */
+ uint32_t offSizeOfImage;
+ /** The correct image size. */
+ uint32_t cbCorrectImageSize;
+ /** Number of entries in the aMappings table. */
+ uint32_t cMappings;
+ /** Mapping hint. */
+ uint32_t iHint;
+ /** Mapping file offset to memory offsets, ordered by file offset. */
+ struct
+ {
+ /** The file offset. */
+ uint32_t offFile;
+ /** The size of this mapping. */
+ uint32_t cbMem;
+ /** The offset to the memory from the start of the image. */
+ uint32_t offMem;
+ } aMappings[1];
+} DBGDIGGERWINNTRDR;
+/** Pointer a WinNT loader reader instance data. */
+typedef DBGDIGGERWINNTRDR *PDBGDIGGERWINNTRDR;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validates a 32-bit Windows NT kernel address */
+#define WINNT32_VALID_ADDRESS(Addr) ((Addr) > UINT32_C(0x80000000) && (Addr) < UINT32_C(0xfffff000))
+/** Validates a 64-bit Windows NT kernel address */
+#define WINNT64_VALID_ADDRESS(Addr) ((Addr) > UINT64_C(0xffff800000000000) && (Addr) < UINT64_C(0xfffffffffffff000))
+/** Validates a kernel address. */
+#define WINNT_VALID_ADDRESS(pThis, Addr) ((pThis)->f32Bit ? WINNT32_VALID_ADDRESS(Addr) : WINNT64_VALID_ADDRESS(Addr))
+/** Versioned and bitness wrapper. */
+#define WINNT_UNION(pThis, pUnion, Member) ((pThis)->f32Bit ? (pUnion)->vX_32. Member : (pUnion)->vX_64. Member )
+
+/** The length (in chars) of the kernel file name (no path). */
+#define WINNT_KERNEL_BASE_NAME_LEN 12
+
+/** WindowsNT on little endian ASCII systems. */
+#define DIG_WINNT_MOD_TAG UINT64_C(0x54696e646f774e54)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) dbgDiggerWinNtInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Kernel names. */
+static const RTUTF16 g_wszKernelNames[][WINNT_KERNEL_BASE_NAME_LEN + 1] =
+{
+ { 'n', 't', 'o', 's', 'k', 'r', 'n', 'l', '.', 'e', 'x', 'e' }
+};
+
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+/**
+ * Queries the string from guest memory with the pointer in the given register, sanitizing it.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle.
+ * @param idCpu The CPU ID.
+ * @param enmReg The register to query the string pointer from.
+ * @param pszBuf Where to store the sanitized string.
+ * @param cbBuf Size of the buffer in number of bytes.
+ */
+static int dbgDiggerWinNtDbgPrintQueryStringFromReg(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, char *pszBuf, size_t cbBuf)
+{
+ uint64_t u64RegPtr = 0;
+ int rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, enmReg, &u64RegPtr);
+ if ( rc == VINF_SUCCESS
+ || rc == VINF_DBGF_ZERO_EXTENDED_REGISTER) /* Being strict about what we expect here. */
+ {
+ DBGFADDRESS AddrStr;
+ DBGFR3AddrFromFlat(pUVM, &AddrStr, u64RegPtr);
+ rc = DBGFR3MemRead(pUVM, idCpu, &AddrStr, pszBuf, cbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check that there is a zero terminator and purge invalid encoding (expecting UTF-8 here). */
+ size_t idx = 0;
+ for (idx = 0; idx < cbBuf; idx++)
+ if (pszBuf[idx] == '\0')
+ break;
+
+ if (idx == cbBuf)
+ pszBuf[cbBuf - 1] = '\0'; /* Force terminator, truncating the string. */
+ else
+ memset(&pszBuf[idx], 0, cbBuf - idx); /* Clear everything afterwards. */
+
+ /* Purge the string encoding. */
+ RTStrPurgeEncoding(pszBuf);
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_INVALID_STATE;
+
+ return rc;
+}
+
+
+/**
+ * @copydoc{FNDBGFBPHIT, Breakpoint callback for the DbgPrint interception.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) dbgDiggerWinNtDbgPrintHit(PVM pVM, VMCPUID idCpu, void *pvUserBp, DBGFBP hBp, PCDBGFBPPUB pBpPub, uint16_t fFlags)
+{
+ RT_NOREF(hBp, pBpPub, fFlags);
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvUserBp;
+ PUVM pUVM = VMR3GetUVM(pVM);
+
+ /*
+ * The worker prototype looks like the following:
+ * vDbgPrintExWorker(PCCH Prefix, ULONG ComponentId, ULONG Level, PCCH Format, va_list arglist, BOOL fUnknown)
+ *
+ * Depending on the bitness the parameters are grabbed from the appropriate registers and stack locations.
+ * For amd64 reading the following is recommended:
+ * https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019
+ * https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=vs-2019
+ * https://docs.microsoft.com/en-us/cpp/build/stack-usage?view=vs-2019
+ *
+ * @todo 32bit
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t idComponent = 0;
+ uint32_t iLevel = 0;
+ char aszPrefixStr[128]; /* Restricted size. */
+ char aszFmtStr[_1K]; /* Restricted size. */
+ DBGFADDRESS AddrVaList;
+ if (!pThis->f32Bit)
+ {
+ /*
+ * Grab the prefix, component, level, format string pointer from the registers and the argument list from the
+ * stack (mind the home area for the register arguments).
+ */
+ rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_RCX, &aszPrefixStr[0], sizeof(aszPrefixStr));
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_RDX, &idComponent);
+ if (RT_SUCCESS(rc))
+ rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_R8, &iLevel);
+ if (RT_SUCCESS(rc))
+ rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_R9, &aszFmtStr[0], sizeof(aszFmtStr));
+ if (RT_SUCCESS(rc))
+ {
+ /* Grabbing the pointer to the va list. The stack layout when we are here looks like (each entry is 64bit):
+ * +-------------+
+ * | ... |
+ * | VA list ptr |
+ * | (arg3/r9) |
+ * | (arg2/r8) |
+ * | (arg1/rdx) |
+ * | (arg0/rcx) |
+ * | return RIP |
+ * +-------------+ <- RSP
+ */
+ uint64_t uRegRsp = 0;
+ rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_RSP, &uRegRsp);
+ if (rc == VINF_SUCCESS)
+ {
+ DBGFADDRESS AddrVaListPtr;
+ RTGCUINTPTR GCPtrVaList = 0;
+
+ DBGFR3AddrFromFlat(pUVM, &AddrVaListPtr, uRegRsp + 5 * sizeof(RTGCUINTPTR));
+ rc = DBGFR3MemRead(pUVM, idCpu, &AddrVaListPtr, &GCPtrVaList, sizeof(GCPtrVaList));
+ if (RT_SUCCESS(rc))
+ DBGFR3AddrFromFlat(pUVM, &AddrVaList, GCPtrVaList);
+ }
+ else
+ rc = VERR_INVALID_STATE;
+ }
+ }
+ else
+ rc = VERR_NOT_IMPLEMENTED; /** @todo */
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("DigWinNt/DbgPrint: Queried arguments %s %#x %u %s %RGv\n", &aszPrefixStr[0], idComponent, iLevel, &aszFmtStr[0], AddrVaList.FlatPtr));
+ /** @todo Continue here. */
+ }
+ else
+ LogRel(("DigWinNt/DbgPrint: Failed to query all arguments with rc=%Rrc\n", rc));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Disassembles the given instruction and checks whether it is a call with a fixed address.
+ *
+ * @returns Flag whether the insturction at the given address is a call.
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ * @param pAddrInsn Guest address of the instruction.
+ * @param pAddrCall Where to store the destination if the instruction is a call.
+ */
+static bool dbgDiggerWinNtDbgPrintWrapperInsnIsCall(PDBGDIGGERWINNT pThis, PUVM pUVM, PCDBGFADDRESS pAddrInsn, PDBGFADDRESS pAddrCall)
+{
+ DISSTATE DisState;
+ RT_ZERO(DisState);
+
+ /* Prefetch the instruction. */
+ uint8_t abInstr[32];
+ int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrInsn, &abInstr[0], sizeof(abInstr));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbInsn = 0;
+ rc = DISInstr(&abInstr[0], pThis->f32Bit ? DISCPUMODE_32BIT : DISCPUMODE_64BIT, &DisState, &cbInsn);
+ if ( RT_SUCCESS(rc)
+ && DisState.pCurInstr->uOpcode == OP_CALL
+ && DisState.Param1.fUse & DISUSE_IMMEDIATE)
+ {
+ if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32 | DISUSE_IMMEDIATE64))
+ DBGFR3AddrFromFlat(pUVM, pAddrCall, DisState.Param1.uValue);
+ else if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32_REL | DISUSE_IMMEDIATE64_REL))
+ {
+ *pAddrCall = *pAddrInsn;
+ DBGFR3AddrAdd(pAddrCall, DisState.Param1.uValue + cbInsn);
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Tries to find the single call instruction of the DbgPrint/etc. worker in the given control flow graph
+ * (single basic block assumed).
+ *
+ * @returns VBox status code.
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ * @param hFlow The control flow graph handle.
+ * @param pAddr Where to store the worker address on success.
+ */
+static int dbgDiggerWinNtDbgPrintResolveWorker(PDBGDIGGERWINNT pThis, PUVM pUVM, DBGFFLOW hFlow, PDBGFADDRESS pAddr)
+{
+ DBGFFLOWBB hBb;
+ int rc = DBGFR3FlowQueryStartBb(hFlow, &hBb);
+ if (RT_SUCCESS(rc))
+ {
+ bool fCallFound = false;
+
+ for (uint32_t i = 0; i < DBGFR3FlowBbGetInstrCount(hBb) && RT_SUCCESS(rc); i++)
+ {
+ DBGFADDRESS AddrInsn;
+ uint32_t cbInsn;
+ rc = DBGFR3FlowBbQueryInstr(hBb, i, &AddrInsn, &cbInsn, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS AddrCall;
+ if (dbgDiggerWinNtDbgPrintWrapperInsnIsCall(pThis, pUVM, &AddrInsn, &AddrCall))
+ {
+ if (!fCallFound)
+ {
+ *pAddr = AddrCall;
+ fCallFound = true;
+ }
+ else
+ {
+ LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx contains multiple call instructions!\n"));
+ rc = VERR_ALREADY_EXISTS;
+ }
+ }
+ }
+ }
+
+ DBGFR3FlowBbRelease(hBb);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Tries to resolve and hook into the worker for all the DbgPrint like wrappers to be able
+ * to gather debug information from the system.
+ *
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ */
+static void dbgDiggerWinNtDbgPrintHook(PDBGDIGGERWINNT pThis, PUVM pUVM)
+{
+ /*
+ * This is a multi step process:
+ * 1. Try to resolve the address of vDbgPrint() (available since XP).
+ * 2. Create a control flow graph from the code and verify the following assumptions:
+ * 1. Only a single basic block.
+ * 2. Just one call instruction.
+ * @todo More?
+ * 3. Get the address from the called worker
+ * 4. Set a hardware breakpoint with our callback.
+ */
+ RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hAs != NIL_RTDBGAS)
+ {
+ RTDBGSYMBOL SymInfo;
+ int rc = RTDbgAsSymbolByName(hAs, "nt!vDbgPrintEx", &SymInfo, NULL /*phMod*/);
+ if (RT_SUCCESS(rc))
+ {
+ DBGFADDRESS Addr;
+ DBGFR3AddrFromFlat(pUVM, &Addr, (RTGCPTR)SymInfo.Value);
+
+ LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx resolved to %RGv\n", SymInfo.Value));
+
+ DBGFFLOW hCfg;
+ rc = DBGFR3FlowCreate(pUVM, 0 /*idCpu*/, &Addr, 512 /*cbDisasmMax*/,
+ 0 /*fFlagsFlow*/, DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED | DBGF_DISAS_FLAGS_DEFAULT_MODE,
+ &hCfg);
+ if (RT_SUCCESS(rc))
+ {
+ /* Verify assumptions. */
+ if (DBGFR3FlowGetBbCount(hCfg) == 1)
+ {
+ rc = dbgDiggerWinNtDbgPrintResolveWorker(pThis, pUVM, hCfg, &Addr);
+ if (RT_SUCCESS(rc))
+ {
+ /* Try to hook the worker. */
+ LogRel(("DigWinNt/DbgPrint: Worker for nt!vDbgPrintEx resolved to %RGv\n", Addr.FlatPtr));
+ rc = DBGFR3BpOwnerCreate(pUVM, dbgDiggerWinNtDbgPrintHit, NULL /*pfnBpIoHit*/, &pThis->hBpOwnerDbgPrint);
+ if (RT_SUCCESS(rc))
+ {
+ rc = DBGFR3BpSetInt3Ex(pUVM, pThis->hBpOwnerDbgPrint, pThis, 0 /*idCpu*/, &Addr, DBGF_BP_F_DEFAULT,
+ 0 /*iHitTrigger*/, 0 /*iHitDisable*/, &pThis->hBpDbgPrint);
+ if (RT_SUCCESS(rc))
+ LogRel(("DigWinNt/DbgPrint: Hooked nt!vDbgPrintEx worker hBp=%#x\n", pThis->hBpDbgPrint));
+ else
+ {
+ LogRel(("DigWinNt/DbgPrint: Setting hardware breakpoint for nt!vDbgPrintEx worker failed with rc=%Rrc\n", rc));
+ int rc2 = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint);
+ pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
+ AssertRC(rc2);
+ }
+ }
+ }
+ /* else LogRel() already done */
+ }
+ else
+ LogRel(("DigWinNt/DbgPrint: Control flow graph for nt!vDbgPrintEx has more than one basic block (%u)\n",
+ DBGFR3FlowGetBbCount(hCfg)));
+
+ DBGFR3FlowRelease(hCfg);
+ }
+ else
+ LogRel(("DigWinNt/DbgPrint: Failed to create control flow graph from nt!vDbgPrintEx rc=%Rrc\n", rc));
+ }
+ else
+ LogRel(("DigWinNt/DbgPrint: Failed to resolve nt!vDbgPrintEx -> rc=%Rrc\n", rc));
+ RTDbgAsRelease(hAs);
+ }
+ else
+ LogRel(("DigWinNt/DbgPrint: Failed to resolve kernel address space handle\n"));
+}
+#endif
+
+/**
+ * Tries to resolve the KPCR and KPCRB addresses for each vCPU.
+ *
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ */
+static void dbgDiggerWinNtResolveKpcr(PDBGDIGGERWINNT pThis, PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ /*
+ * Getting at the KPCR and KPCRB is explained here:
+ * https://www.geoffchappell.com/studies/windows/km/ntoskrnl/structs/kpcr.htm
+ * Together with the available offsets from:
+ * https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/ksamd64.inc#L883
+ * we can verify that the found addresses are valid by cross checking that the GDTR and self reference
+ * match what we expect.
+ */
+ VMCPUID cCpus = pVMM->pfnDBGFR3CpuGetCount(pUVM);
+ pThis->paKpcrAddr = (PDBGFADDRESS)RTMemAllocZ(cCpus * 2 * sizeof(DBGFADDRESS));
+ if (RT_LIKELY(pThis->paKpcrAddr))
+ {
+ pThis->paKpcrbAddr = &pThis->paKpcrAddr[cCpus];
+
+ /* Work each CPU, unexpected values in each CPU make the whole thing fail to play safe. */
+ int rc = VINF_SUCCESS;
+ for (VMCPUID idCpu = 0; (idCpu < cCpus) && RT_SUCCESS(rc); idCpu++)
+ {
+ PDBGFADDRESS pKpcrAddr = &pThis->paKpcrAddr[idCpu];
+ PDBGFADDRESS pKpcrbAddr = &pThis->paKpcrbAddr[idCpu];
+
+ if (pThis->f32Bit)
+ {
+ /* Read FS base */
+ uint32_t GCPtrKpcrBase = 0;
+
+ rc = pVMM->pfnDBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_FS_BASE, &GCPtrKpcrBase);
+ if ( RT_SUCCESS(rc)
+ && WINNT32_VALID_ADDRESS(GCPtrKpcrBase))
+ {
+ /*
+ * Read the start of the KPCR (@todo Probably move this to a global header)
+ * and verify its content.
+ */
+ struct
+ {
+ uint8_t abOoi[28]; /* Out of interest */
+ uint32_t GCPtrSelf;
+ uint32_t GCPtrCurrentPrcb;
+ uint32_t u32Irql;
+ uint32_t u32Iir;
+ uint32_t u32IirActive;
+ uint32_t u32Idr;
+ uint32_t GCPtrKdVersionBlock;
+ uint32_t GCPtrIdt;
+ uint32_t GCPtrGdt;
+ uint32_t GCPtrTss;
+ } Kpcr;
+
+ LogFlow(("DigWinNt/KPCR[%u]: GS Base %RGv\n", idCpu, GCPtrKpcrBase));
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, pKpcrAddr, GCPtrKpcrBase);
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, idCpu, pKpcrAddr, &Kpcr, sizeof(Kpcr));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t GCPtrGdt = 0;
+ uint32_t GCPtrIdt = 0;
+
+ rc = pVMM->pfnDBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_GDTR_BASE, &GCPtrGdt);
+ if (RT_SUCCESS(rc))
+ rc = pVMM->pfnDBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_IDTR_BASE, &GCPtrIdt);
+ if (RT_SUCCESS(rc))
+ {
+ if ( Kpcr.GCPtrGdt == GCPtrGdt
+ && Kpcr.GCPtrIdt == GCPtrIdt
+ && Kpcr.GCPtrSelf == pKpcrAddr->FlatPtr)
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, pKpcrbAddr, Kpcr.GCPtrCurrentPrcb);
+ LogRel(("DigWinNt/KPCR[%u]: KPCR=%RGv KPCRB=%RGv\n", idCpu, pKpcrAddr->FlatPtr, pKpcrbAddr->FlatPtr));
+
+ /*
+ * Try to extract the NT build number from the KD version block if it exists,
+ * the shared user data might have set it to 0.
+ *
+ * @todo We can use this method to get at the kern base and loaded module list if the other detection
+ * method fails (seen with Windows 10 x86).
+ * @todo On 32bit Windows the debugger data list is also always accessible this way contrary to
+ * the amd64 version where it is only available with "/debug on" set.
+ */
+ if (!pThis->NtBuildNumber)
+ {
+ NTKDVERSIONBLOCK KdVersBlock;
+ DBGFADDRESS AddrKdVersBlock;
+
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrKdVersBlock, Kpcr.GCPtrKdVersionBlock);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, idCpu, &AddrKdVersBlock, &KdVersBlock, sizeof(KdVersBlock));
+ if (RT_SUCCESS(rc))
+ pThis->NtBuildNumber = KdVersBlock.MinorVersion;
+ }
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: KPCR validation error GDT=(%RGv vs %RGv) KPCR=(%RGv vs %RGv)\n", idCpu,
+ Kpcr.GCPtrGdt, GCPtrGdt, Kpcr.GCPtrSelf, pKpcrAddr->FlatPtr));
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: Getting GDT or IDT base register failed with %Rrc\n", idCpu, rc));
+ }
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: Getting FS base register failed with %Rrc (%RGv)\n", idCpu, rc, GCPtrKpcrBase));
+ }
+ else
+ {
+ /* Read GS base which points to the base of the KPCR for each CPU. */
+ RTGCUINTPTR GCPtrTmp = 0;
+ rc = pVMM->pfnDBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_GS_BASE, &GCPtrTmp);
+ if ( RT_SUCCESS(rc)
+ && !WINNT64_VALID_ADDRESS(GCPtrTmp))
+ {
+ /*
+ * Could be a user address when we stopped the VM right in usermode,
+ * read the GS kernel base MSR instead.
+ */
+ rc = pVMM->pfnDBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_MSR_K8_KERNEL_GS_BASE, &GCPtrTmp);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && WINNT64_VALID_ADDRESS(GCPtrTmp))
+ {
+ LogFlow(("DigWinNt/KPCR[%u]: GS Base %RGv\n", idCpu, GCPtrTmp));
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, pKpcrAddr, GCPtrTmp);
+
+ rc = pVMM->pfnDBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_GDTR_BASE, &GCPtrTmp);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read the start of the KPCR (@todo Probably move this to a global header)
+ * and verify its content.
+ */
+ struct
+ {
+ RTGCUINTPTR GCPtrGdt;
+ RTGCUINTPTR GCPtrTss;
+ RTGCUINTPTR GCPtrUserRsp;
+ RTGCUINTPTR GCPtrSelf;
+ RTGCUINTPTR GCPtrCurrentPrcb;
+ } Kpcr;
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, idCpu, pKpcrAddr, &Kpcr, sizeof(Kpcr));
+ if (RT_SUCCESS(rc))
+ {
+ if ( Kpcr.GCPtrGdt == GCPtrTmp
+ && Kpcr.GCPtrSelf == pKpcrAddr->FlatPtr
+ /** @todo && TSS */ )
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, pKpcrbAddr, Kpcr.GCPtrCurrentPrcb);
+ LogRel(("DigWinNt/KPCR[%u]: KPCR=%RGv KPCRB=%RGv\n", idCpu, pKpcrAddr->FlatPtr, pKpcrbAddr->FlatPtr));
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: KPCR validation error GDT=(%RGv vs %RGv) KPCR=(%RGv vs %RGv)\n", idCpu,
+ Kpcr.GCPtrGdt, GCPtrTmp, Kpcr.GCPtrSelf, pKpcrAddr->FlatPtr));
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: Reading KPCR start at %RGv failed with %Rrc\n", idCpu, pKpcrAddr->FlatPtr, rc));
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: Getting GDT base register failed with %Rrc\n", idCpu, rc));
+ }
+ else
+ LogRel(("DigWinNt/KPCR[%u]: Getting GS base register failed with %Rrc\n", idCpu, rc));
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("DigWinNt/KPCR: Failed to detmine KPCR and KPCRB rc=%Rrc\n", rc));
+ RTMemFree(pThis->paKpcrAddr);
+ pThis->paKpcrAddr = NULL;
+ pThis->paKpcrbAddr = NULL;
+ }
+ }
+ else
+ LogRel(("DigWinNt/KPCR: Failed to allocate %u entries for the KPCR/KPCRB addresses\n", cCpus * 2));
+}
+
+
+/**
+ * Process a PE image found in guest memory.
+ *
+ * @param pThis The instance data.
+ * @param pUVM The user mode VM handle.
+ * @param pVMM The VMM function table.
+ * @param pszName The module name.
+ * @param pszFilename The image filename.
+ * @param pImageAddr The image address.
+ * @param cbImage The size of the image.
+ */
+static void dbgDiggerWinNtProcessImage(PDBGDIGGERWINNT pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszName,
+ const char *pszFilename, PCDBGFADDRESS pImageAddr, uint32_t cbImage)
+{
+ LogFlow(("DigWinNt: %RGp %#x %s\n", pImageAddr->FlatPtr, cbImage, pszName));
+
+ /*
+ * Do some basic validation first.
+ */
+ if ( (cbImage < sizeof(IMAGE_NT_HEADERS64) && !pThis->fNt31)
+ || cbImage >= _1M * 256)
+ {
+ Log(("DigWinNt: %s: Bad image size: %#x\n", pszName, cbImage));
+ return;
+ }
+
+ /*
+ * Use the common in-memory module reader to create a debug module.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTDBGMOD hDbgMod = NIL_RTDBGMOD;
+ int rc = pVMM->pfnDBGFR3ModInMem(pUVM, pImageAddr, pThis->fNt31 ? DBGFMODINMEM_F_PE_NT31 : 0, pszName, pszFilename,
+ pThis->f32Bit ? RTLDRARCH_X86_32 : RTLDRARCH_AMD64, cbImage,
+ &hDbgMod, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tag the module.
+ */
+ rc = RTDbgModSetTag(hDbgMod, DIG_WINNT_MOD_TAG);
+ AssertRC(rc);
+
+ /*
+ * Link the module.
+ */
+ RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hAs != NIL_RTDBGAS)
+ rc = RTDbgAsModuleLink(hAs, hDbgMod, pImageAddr->FlatPtr, RTDBGASLINK_FLAGS_REPLACE /*fFlags*/);
+ else
+ rc = VERR_INTERNAL_ERROR;
+ RTDbgModRelease(hDbgMod);
+ RTDbgAsRelease(hAs);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ Log(("DigWinNt: %s: DBGFR3ModInMem failed: %Rrc - %s\n", pszName, rc, ErrInfo.Core.pszMsg));
+ else
+ Log(("DigWinNt: %s: DBGFR3ModInMem failed: %Rrc\n", pszName, rc));
+}
+
+
+/**
+ * Generate a debugger compatible module name from a filename.
+ *
+ * @returns Pointer to module name (doesn't need to be pszName).
+ * @param pszFilename The source filename.
+ * @param pszName Buffer to put the module name in.
+ * @param cbName Buffer size.
+ */
+static const char *dbgDiggerWintNtFilenameToModuleName(const char *pszFilename, char *pszName, size_t cbName)
+{
+ /* Skip to the filename part of the filename. :-) */
+ pszFilename = RTPathFilenameEx(pszFilename, RTPATH_STR_F_STYLE_DOS);
+
+ /* We try use 'nt' for the kernel. */
+ if ( RTStrICmpAscii(pszFilename, "ntoskrnl.exe") == 0
+ || RTStrICmpAscii(pszFilename, "ntkrnlmp.exe") == 0)
+ return "nt";
+
+
+ /* Drop the extension if .dll or .sys. */
+ size_t cchFilename = strlen(pszFilename);
+ if ( cchFilename > 4
+ && pszFilename[cchFilename - 4] == '.')
+ {
+ if ( RTStrICmpAscii(&pszFilename[cchFilename - 4], ".sys") == 0
+ || RTStrICmpAscii(&pszFilename[cchFilename - 4], ".dll") == 0)
+ cchFilename -= 4;
+ }
+
+ /* Copy and do replacements. */
+ if (cchFilename >= cbName)
+ cchFilename = cbName - 1;
+ size_t off;
+ for (off = 0; off < cchFilename; off++)
+ {
+ char ch = pszFilename[off];
+ if (!RT_C_IS_ALNUM(ch))
+ ch = '_';
+ pszName[off] = ch;
+ }
+ pszName[off] = '\0';
+ return pszName;
+}
+
+
+/**
+ * @interface_method_impl{DBGFOSIWINNT,pfnQueryVersion}
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtIWinNt_QueryVersion(struct DBGFOSIWINNT *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ uint32_t *puVersMajor, uint32_t *puVersMinor,
+ uint32_t *puBuildNumber, bool *pf32Bit)
+{
+ PDBGDIGGERWINNT pData = RT_FROM_MEMBER(pThis, DBGDIGGERWINNT, IWinNt);
+ RT_NOREF(pUVM, pVMM);
+
+ if (puVersMajor)
+ *puVersMajor = pData->NtMajorVersion;
+ if (puVersMinor)
+ *puVersMinor = pData->NtMinorVersion;
+ if (puBuildNumber)
+ *puBuildNumber = pData->NtBuildNumber;
+ if (pf32Bit)
+ *pf32Bit = pData->f32Bit;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{DBGFOSIWINNT,pfnQueryKernelPtrs}
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtIWinNt_QueryKernelPtrs(struct DBGFOSIWINNT *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ PRTGCUINTPTR pGCPtrKernBase, PRTGCUINTPTR pGCPtrPsLoadedModuleList)
+{
+ PDBGDIGGERWINNT pData = RT_FROM_MEMBER(pThis, DBGDIGGERWINNT, IWinNt);
+ RT_NOREF(pUVM, pVMM);
+
+ *pGCPtrKernBase = pData->KernelAddr.FlatPtr;
+ *pGCPtrPsLoadedModuleList = pData->PsLoadedModuleListAddr.FlatPtr;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{DBGFOSIWINNT,pfnQueryKpcrForVCpu}
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtIWinNt_QueryKpcrForVCpu(struct DBGFOSIWINNT *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ VMCPUID idCpu, PRTGCUINTPTR pKpcr, PRTGCUINTPTR pKpcrb)
+{
+ PDBGDIGGERWINNT pData = RT_FROM_MEMBER(pThis, DBGDIGGERWINNT, IWinNt);
+
+ if (!pData->paKpcrAddr)
+ return VERR_NOT_SUPPORTED;
+
+ AssertReturn(idCpu < pVMM->pfnDBGFR3CpuGetCount(pUVM), VERR_INVALID_CPU_ID);
+
+ if (pKpcr)
+ *pKpcr = pData->paKpcrAddr[idCpu].FlatPtr;
+ if (pKpcrb)
+ *pKpcrb = pData->paKpcrbAddr[idCpu].FlatPtr;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{DBGFOSIWINNT,pfnQueryCurThrdForVCpu}
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtIWinNt_QueryCurThrdForVCpu(struct DBGFOSIWINNT *pThis, PUVM pUVM, PCVMMR3VTABLE pVMM,
+ VMCPUID idCpu, PRTGCUINTPTR pCurThrd)
+{
+ PDBGDIGGERWINNT pData = RT_FROM_MEMBER(pThis, DBGDIGGERWINNT, IWinNt);
+
+ if (!pData->paKpcrAddr)
+ return VERR_NOT_SUPPORTED;
+
+ AssertReturn(idCpu < pVMM->pfnDBGFR3CpuGetCount(pUVM), VERR_INVALID_CPU_ID);
+
+ DBGFADDRESS AddrCurThrdPtr = pData->paKpcrbAddr[idCpu];
+ pVMM->pfnDBGFR3AddrAdd(&AddrCurThrdPtr, 0x08); /** @todo Make this prettier. */
+ return pVMM->pfnDBGFR3MemRead(pUVM, idCpu, &AddrCurThrdPtr, pCurThrd, sizeof(*pCurThrd));
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnStackUnwindAssist
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtStackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu,
+ PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx,
+ RTDBGAS hAs, uint64_t *puScratch)
+{
+ Assert(pInitialCtx);
+
+ /*
+ * We want to locate trap frames here. The trap frame structure contains
+ * the 64-bit IRET frame, so given unwind information it's easy to identify
+ * using the return type and frame address.
+ */
+ if (pFrame->fFlags & DBGFSTACKFRAME_FLAGS_64BIT)
+ {
+ /*
+ * Is this a trap frame? If so, try read the trap frame.
+ */
+ if ( pFrame->enmReturnType == RTDBGRETURNTYPE_IRET64
+ && !(pFrame->AddrFrame.FlatPtr & 0x7)
+ && WINNT64_VALID_ADDRESS(pFrame->AddrFrame.FlatPtr) )
+ {
+ KTRAP_FRAME_AMD64 TrapFrame;
+ RT_ZERO(TrapFrame);
+ uint64_t const uTrapFrameAddr = pFrame->AddrFrame.FlatPtr
+ - RT_UOFFSETOF(KTRAP_FRAME_AMD64, ErrCdOrXcptFrameOrS);
+ int rc = pState->pfnReadStack(pState, uTrapFrameAddr, sizeof(TrapFrame), &TrapFrame);
+ if (RT_SUCCESS(rc))
+ {
+ /* Valid? Not too much else we can check here (EFlags isn't
+ reliable in manually construct frames). */
+ if (TrapFrame.ExceptionActive <= 2)
+ {
+ pFrame->fFlags |= DBGFSTACKFRAME_FLAGS_TRAP_FRAME;
+
+ /*
+ * Add sure 'register' information from the frame to the frame.
+ *
+ * To avoid code duplication, we do this in two steps in a loop.
+ * The first iteration only figures out how many registers we're
+ * going to save and allocates room for them. The second iteration
+ * does the actual adding.
+ */
+ uint32_t cRegs = pFrame->cSureRegs;
+ PDBGFREGVALEX paSureRegs = NULL;
+#define ADD_REG_NAMED(a_Type, a_ValMemb, a_Value, a_pszName) do { \
+ if (paSureRegs) \
+ { \
+ paSureRegs[iReg].pszName = a_pszName;\
+ paSureRegs[iReg].enmReg = DBGFREG_END; \
+ paSureRegs[iReg].enmType = a_Type; \
+ paSureRegs[iReg].Value.a_ValMemb = (a_Value); \
+ } \
+ iReg++; \
+ } while (0)
+#define MAYBE_ADD_GREG(a_Value, a_enmReg, a_idxReg) do { \
+ if (!(pState->u.x86.Loaded.s.fRegs & RT_BIT(a_idxReg))) \
+ { \
+ if (paSureRegs) \
+ { \
+ pState->u.x86.Loaded.s.fRegs |= RT_BIT(a_idxReg); \
+ pState->u.x86.auRegs[a_idxReg] = (a_Value); \
+ paSureRegs[iReg].Value.u64 = (a_Value); \
+ paSureRegs[iReg].enmReg = a_enmReg; \
+ paSureRegs[iReg].enmType = DBGFREGVALTYPE_U64; \
+ paSureRegs[iReg].pszName = NULL; \
+ } \
+ iReg++; \
+ } \
+ } while (0)
+ for (unsigned iLoop = 0; iLoop < 2; iLoop++)
+ {
+ uint32_t iReg = pFrame->cSureRegs;
+ ADD_REG_NAMED(DBGFREGVALTYPE_U64, u64, uTrapFrameAddr, "TrapFrame");
+ ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, TrapFrame.ExceptionActive, "ExceptionActive");
+ if (TrapFrame.ExceptionActive == 0)
+ {
+ ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, TrapFrame.PreviousIrql, "PrevIrql");
+ ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, (uint8_t)TrapFrame.ErrCdOrXcptFrameOrS, "IntNo");
+ }
+ else if ( TrapFrame.ExceptionActive == 1
+ && TrapFrame.FaultIndicator == ((TrapFrame.ErrCdOrXcptFrameOrS >> 1) & 0x9))
+ ADD_REG_NAMED(DBGFREGVALTYPE_U64, u64, TrapFrame.FaultAddrOrCtxRecOrTS, "cr2-probably");
+ if (TrapFrame.SegCs & X86_SEL_RPL)
+ ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, 1, "UserMode");
+ else
+ ADD_REG_NAMED(DBGFREGVALTYPE_U8, u8, 1, "KernelMode");
+ if (TrapFrame.ExceptionActive <= 1)
+ {
+ MAYBE_ADD_GREG(TrapFrame.Rax, DBGFREG_RAX, X86_GREG_xAX);
+ MAYBE_ADD_GREG(TrapFrame.Rcx, DBGFREG_RCX, X86_GREG_xCX);
+ MAYBE_ADD_GREG(TrapFrame.Rdx, DBGFREG_RDX, X86_GREG_xDX);
+ MAYBE_ADD_GREG(TrapFrame.R8, DBGFREG_R8, X86_GREG_x8);
+ MAYBE_ADD_GREG(TrapFrame.R9, DBGFREG_R9, X86_GREG_x9);
+ MAYBE_ADD_GREG(TrapFrame.R10, DBGFREG_R10, X86_GREG_x10);
+ MAYBE_ADD_GREG(TrapFrame.R11, DBGFREG_R11, X86_GREG_x11);
+ }
+ else if (TrapFrame.ExceptionActive == 2)
+ {
+ MAYBE_ADD_GREG(TrapFrame.Rbx, DBGFREG_RBX, X86_GREG_xBX);
+ MAYBE_ADD_GREG(TrapFrame.Rsi, DBGFREG_RSI, X86_GREG_xSI);
+ MAYBE_ADD_GREG(TrapFrame.Rdi, DBGFREG_RDI, X86_GREG_xDI);
+ }
+ // MAYBE_ADD_GREG(TrapFrame.Rbp, DBGFREG_RBP, X86_GREG_xBP); - KiInterrupt[Sub]Dispatch* may leave this invalid.
+
+ /* Done? */
+ if (iLoop > 0)
+ {
+ Assert(cRegs == iReg);
+ break;
+ }
+
+ /* Resize the array, zeroing the extension. */
+ if (pFrame->cSureRegs)
+ paSureRegs = (PDBGFREGVALEX)pVMM->pfnMMR3HeapRealloc(pFrame->paSureRegs, iReg * sizeof(paSureRegs[0]));
+ else
+ paSureRegs = (PDBGFREGVALEX)pVMM->pfnMMR3HeapAllocU(pUVM, MM_TAG_DBGF_STACK, iReg * sizeof(paSureRegs[0]));
+ AssertReturn(paSureRegs, VERR_NO_MEMORY);
+
+ pFrame->paSureRegs = paSureRegs;
+ RT_BZERO(&paSureRegs[pFrame->cSureRegs], (iReg - pFrame->cSureRegs) * sizeof(paSureRegs[0]));
+ cRegs = iReg;
+ }
+#undef ADD_REG_NAMED
+#undef MAYBE_ADD_GREG
+
+ /* Commit the register update. */
+ pFrame->cSureRegs = cRegs;
+ }
+ }
+ }
+ }
+
+ RT_NOREF(pUVM, pVMM, pvData, idCpu, hAs, pInitialCtx, puScratch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryInterface
+ */
+static DECLCALLBACK(void *) dbgDiggerWinNtQueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+
+ switch (enmIf)
+ {
+ case DBGFOSINTERFACE_WINNT:
+ return &pThis->IWinNt;
+ default:
+ return NULL;
+ }
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnQueryVersion
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtQueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData,
+ char *pszVersion, size_t cchVersion)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+ Assert(pThis->fValid);
+
+ const char *pszNtProductType;
+ switch (pThis->NtProductType)
+ {
+ case kNtProductType_WinNt: pszNtProductType = "-WinNT"; break;
+ case kNtProductType_LanManNt: pszNtProductType = "-LanManNT"; break;
+ case kNtProductType_Server: pszNtProductType = "-Server"; break;
+ default: pszNtProductType = ""; break;
+ }
+
+ RTStrPrintf(pszVersion, cchVersion, "%u.%u-%s%s (BuildNumber %u)", pThis->NtMajorVersion, pThis->NtMinorVersion,
+ pThis->f32Bit ? "x86" : "AMD64", pszNtProductType, pThis->NtBuildNumber);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnTerm
+ */
+static DECLCALLBACK(void) dbgDiggerWinNtTerm(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF1(pUVM);
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+ Assert(pThis->fValid);
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+ if (pThis->hBpDbgPrint != NIL_DBGFBP)
+ {
+ int rc = DBGFR3BpClear(pUVM, pThis->hBpDbgPrint);
+ AssertRC(rc);
+ pThis->hBpDbgPrint = NIL_DBGFBP;
+ }
+
+ if (pThis->hBpOwnerDbgPrint != NIL_DBGFBPOWNER)
+ {
+ int rc = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint);
+ AssertRC(rc);
+ pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
+ }
+#endif
+
+ /*
+ * As long as we're using our private LDR reader implementation,
+ * we must unlink and ditch the modules we created.
+ */
+ RTDBGAS hDbgAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+ if (hDbgAs != NIL_RTDBGAS)
+ {
+ uint32_t iMod = RTDbgAsModuleCount(hDbgAs);
+ while (iMod-- > 0)
+ {
+ RTDBGMOD hMod = RTDbgAsModuleByIndex(hDbgAs, iMod);
+ if (hMod != NIL_RTDBGMOD)
+ {
+ if (RTDbgModGetTag(hMod) == DIG_WINNT_MOD_TAG)
+ {
+ int rc = RTDbgAsModuleUnlink(hDbgAs, hMod);
+ AssertRC(rc);
+ }
+ RTDbgModRelease(hMod);
+ }
+ }
+ RTDbgAsRelease(hDbgAs);
+ }
+
+ if (pThis->paKpcrAddr)
+ RTMemFree(pThis->paKpcrAddr);
+ /* pThis->paKpcrbAddr comes from the same allocation as pThis->paKpcrAddr. */
+
+ pThis->paKpcrAddr = NULL;
+ pThis->paKpcrbAddr = NULL;
+
+ pThis->fValid = false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnRefresh
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtRefresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+ NOREF(pThis);
+ Assert(pThis->fValid);
+
+ /*
+ * For now we'll flush and reload everything.
+ */
+ dbgDiggerWinNtTerm(pUVM, pVMM, pvData);
+
+ return dbgDiggerWinNtInit(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnInit
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+ Assert(!pThis->fValid);
+
+ union
+ {
+ uint8_t au8[0x2000];
+ RTUTF16 wsz[0x2000/2];
+ NTKUSERSHAREDDATA UserSharedData;
+ } u;
+ DBGFADDRESS Addr;
+ int rc;
+
+ /*
+ * Figure the NT version.
+ */
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, pThis->f32Bit ? NTKUSERSHAREDDATA_WINNT32 : NTKUSERSHAREDDATA_WINNT64);
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &u, PAGE_SIZE);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->NtProductType = u.UserSharedData.ProductTypeIsValid && u.UserSharedData.NtProductType <= kNtProductType_Server
+ ? (NTPRODUCTTYPE)u.UserSharedData.NtProductType
+ : kNtProductType_Invalid;
+ pThis->NtMajorVersion = u.UserSharedData.NtMajorVersion;
+ pThis->NtMinorVersion = u.UserSharedData.NtMinorVersion;
+ pThis->NtBuildNumber = u.UserSharedData.NtBuildNumber;
+ }
+ else if (pThis->fNt31)
+ {
+ pThis->NtProductType = kNtProductType_WinNt;
+ pThis->NtMajorVersion = 3;
+ pThis->NtMinorVersion = 1;
+ pThis->NtBuildNumber = 0;
+ }
+ else
+ {
+ Log(("DigWinNt: Error reading KUSER_SHARED_DATA: %Rrc\n", rc));
+ return rc;
+ }
+
+ /*
+ * Dig out the module chain.
+ */
+ DBGFADDRESS AddrPrev = pThis->PsLoadedModuleListAddr;
+ Addr = pThis->KernelMteAddr;
+ do
+ {
+ /* Read the validate the MTE. */
+ NTMTE Mte;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &Mte, pThis->f32Bit ? sizeof(Mte.vX_32) : sizeof(Mte.vX_64));
+ if (RT_FAILURE(rc))
+ break;
+ if (WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Blink) != AddrPrev.FlatPtr)
+ {
+ Log(("DigWinNt: Bad Mte At %RGv - backpointer\n", Addr.FlatPtr));
+ break;
+ }
+ if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Flink)) )
+ {
+ Log(("DigWinNt: Bad Mte at %RGv - forward pointer\n", Addr.FlatPtr));
+ break;
+ }
+ if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer)))
+ {
+ Log(("DigWinNt: Bad Mte at %RGv - BaseDllName=%llx\n", Addr.FlatPtr, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer)));
+ break;
+ }
+ if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, FullDllName.Buffer)))
+ {
+ Log(("DigWinNt: Bad Mte at %RGv - FullDllName=%llx\n", Addr.FlatPtr, WINNT_UNION(pThis, &Mte, FullDllName.Buffer)));
+ break;
+ }
+ if (!WINNT_VALID_ADDRESS(pThis, WINNT_UNION(pThis, &Mte, DllBase)))
+ {
+ Log(("DigWinNt: Bad Mte at %RGv - DllBase=%llx\n", Addr.FlatPtr, WINNT_UNION(pThis, &Mte, DllBase) ));
+ break;
+ }
+
+ uint32_t const cbImageMte = !pThis->fNt31 ? WINNT_UNION(pThis, &Mte, SizeOfImage) : 0;
+ if ( !pThis->fNt31
+ && ( cbImageMte > _256M
+ || WINNT_UNION(pThis, &Mte, EntryPoint) - WINNT_UNION(pThis, &Mte, DllBase) > cbImageMte) )
+ {
+ Log(("DigWinNt: Bad Mte at %RGv - EntryPoint=%llx SizeOfImage=%x DllBase=%llx\n",
+ Addr.FlatPtr, WINNT_UNION(pThis, &Mte, EntryPoint), cbImageMte, WINNT_UNION(pThis, &Mte, DllBase)));
+ break;
+ }
+
+ /* Read the full name. */
+ DBGFADDRESS AddrName;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrName, WINNT_UNION(pThis, &Mte, FullDllName.Buffer));
+ uint16_t cbName = WINNT_UNION(pThis, &Mte, FullDllName.Length);
+ if (cbName < sizeof(u))
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrName, &u, cbName);
+ else
+ rc = VERR_OUT_OF_RANGE;
+ if (RT_FAILURE(rc))
+ {
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrName, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer));
+ cbName = WINNT_UNION(pThis, &Mte, BaseDllName.Length);
+ if (cbName < sizeof(u))
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrName, &u, cbName);
+ else
+ rc = VERR_OUT_OF_RANGE;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ u.wsz[cbName / 2] = '\0';
+
+ char *pszFilename;
+ rc = RTUtf16ToUtf8(u.wsz, &pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ char szModName[128];
+ const char *pszModName = dbgDiggerWintNtFilenameToModuleName(pszFilename, szModName, sizeof(szModName));
+
+ /* Read the start of the PE image and pass it along to a worker. */
+ DBGFADDRESS ImageAddr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ImageAddr, WINNT_UNION(pThis, &Mte, DllBase));
+ dbgDiggerWinNtProcessImage(pThis, pUVM, pVMM, pszModName, pszFilename, &ImageAddr, cbImageMte);
+ RTStrFree(pszFilename);
+ }
+ }
+
+ /* next */
+ AddrPrev = Addr;
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Flink));
+ } while ( Addr.FlatPtr != pThis->KernelMteAddr.FlatPtr
+ && Addr.FlatPtr != pThis->PsLoadedModuleListAddr.FlatPtr);
+
+ /* Try resolving the KPCR and KPCRB addresses for each vCPU. */
+ dbgDiggerWinNtResolveKpcr(pThis, pUVM, pVMM);
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+ /* Try to hook into the DbgPrint/vDbgPrint... code so we can gather information from the drivers. */
+ dbgDiggerWinNtDbgPrintHook(pThis, pUVM);
+#endif
+
+ pThis->fValid = true;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnProbe
+ */
+static DECLCALLBACK(bool) dbgDiggerWinNtProbe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+ DBGFADDRESS Addr;
+ union
+ {
+ uint8_t au8[8192];
+ uint16_t au16[8192/2];
+ uint32_t au32[8192/4];
+ IMAGE_DOS_HEADER MzHdr;
+ RTUTF16 wsz[8192/2];
+ X86DESCGATE a32Gates[X86_XCPT_PF + 1];
+ X86DESC64GATE a64Gates[X86_XCPT_PF + 1];
+ } u;
+
+ union
+ {
+ NTMTE32 v32;
+ NTMTE64 v64;
+ } uMte, uMte2, uMte3;
+
+ /*
+ * NT only runs in protected or long mode.
+ */
+ CPUMMODE const enmMode = pVMM->pfnDBGFR3CpuGetMode(pUVM, 0 /*idCpu*/);
+ if (enmMode != CPUMMODE_PROTECTED && enmMode != CPUMMODE_LONG)
+ return false;
+ bool const f64Bit = enmMode == CPUMMODE_LONG;
+ uint64_t const uStart = f64Bit ? UINT64_C(0xffff080000000000) : UINT32_C(0x80001000);
+ uint64_t const uEnd = f64Bit ? UINT64_C(0xffffffffffff0000) : UINT32_C(0xffff0000);
+
+ /*
+ * To approximately locate the kernel we examine the IDTR handlers.
+ *
+ * The exception/trap/fault handlers are all in NT kernel image, we pick
+ * KiPageFault here.
+ */
+ uint64_t uIdtrBase = 0;
+ uint16_t uIdtrLimit = 0;
+ int rc = pVMM->pfnDBGFR3RegCpuQueryXdtr(pUVM, 0, DBGFREG_IDTR, &uIdtrBase, &uIdtrLimit);
+ AssertRCReturn(rc, false);
+
+ const uint16_t cbMinIdtr = (X86_XCPT_PF + 1) * (f64Bit ? sizeof(X86DESC64GATE) : sizeof(X86DESCGATE));
+ if (uIdtrLimit < cbMinIdtr)
+ return false;
+
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uIdtrBase), &u, cbMinIdtr);
+ if (RT_FAILURE(rc))
+ return false;
+
+ uint64_t uKrnlStart = uStart;
+ uint64_t uKrnlEnd = uEnd;
+ if (f64Bit)
+ {
+ uint64_t uHandler = u.a64Gates[X86_XCPT_PF].u16OffsetLow
+ | ((uint32_t)u.a64Gates[X86_XCPT_PF].u16OffsetHigh << 16)
+ | ((uint64_t)u.a64Gates[X86_XCPT_PF].u32OffsetTop << 32);
+ if (uHandler < uStart || uHandler > uEnd)
+ return false;
+ uKrnlStart = (uHandler & ~(uint64_t)_4M) - _512M;
+ uKrnlEnd = (uHandler + (uint64_t)_4M) & ~(uint64_t)_4M;
+ }
+ else
+ {
+ uint32_t uHandler = RT_MAKE_U32(u.a32Gates[X86_XCPT_PF].u16OffsetLow, u.a32Gates[X86_XCPT_PF].u16OffsetHigh);
+ if (uHandler < uStart || uHandler > uEnd)
+ return false;
+ uKrnlStart = (uHandler & ~(uint64_t)_4M) - _64M;
+ uKrnlEnd = (uHandler + (uint64_t)_4M) & ~(uint64_t)_4M;
+ }
+
+ /*
+ * Look for the PAGELK section name that seems to be a part of all kernels.
+ * Then try find the module table entry for it. Since it's the first entry
+ * in the PsLoadedModuleList we can easily validate the list head and report
+ * success.
+ *
+ * Note! We ASSUME the section name is 8 byte aligned.
+ */
+ DBGFADDRESS KernelAddr;
+ for (pVMM->pfnDBGFR3AddrFromFlat(pUVM, &KernelAddr, uKrnlStart);
+ KernelAddr.FlatPtr < uKrnlEnd;
+ KernelAddr.FlatPtr += PAGE_SIZE)
+ {
+ bool fNt31 = false;
+ DBGFADDRESS const RetryAddress = KernelAddr;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, uEnd - KernelAddr.FlatPtr,
+ 8, "PAGELK\0", sizeof("PAGELK\0"), &KernelAddr);
+ if ( rc == VERR_DBGF_MEM_NOT_FOUND
+ && enmMode != CPUMMODE_LONG)
+ {
+ /* NT3.1 didn't have a PAGELK section, so look for _TEXT instead. The
+ following VirtualSize is zero, so check for that too. */
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &RetryAddress, uEnd - RetryAddress.FlatPtr,
+ 8, "_TEXT\0\0\0\0\0\0", sizeof("_TEXT\0\0\0\0\0\0"), &KernelAddr);
+ fNt31 = true;
+ }
+ if (RT_FAILURE(rc))
+ break;
+ pVMM->pfnDBGFR3AddrSub(&KernelAddr, KernelAddr.FlatPtr & PAGE_OFFSET_MASK);
+
+ /* MZ + PE header. */
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &KernelAddr, &u, sizeof(u));
+ if ( RT_SUCCESS(rc)
+ && u.MzHdr.e_magic == IMAGE_DOS_SIGNATURE
+ && !(u.MzHdr.e_lfanew & 0x7)
+ && u.MzHdr.e_lfanew >= 0x080
+ && u.MzHdr.e_lfanew <= 0x400) /* W8 is at 0x288*/
+ {
+ if (enmMode != CPUMMODE_LONG)
+ {
+ IMAGE_NT_HEADERS32 const *pHdrs = (IMAGE_NT_HEADERS32 const *)&u.au8[u.MzHdr.e_lfanew];
+ if ( pHdrs->Signature == IMAGE_NT_SIGNATURE
+ && pHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386
+ && pHdrs->FileHeader.SizeOfOptionalHeader == sizeof(pHdrs->OptionalHeader)
+ && pHdrs->FileHeader.NumberOfSections >= 10 /* the kernel has lots */
+ && (pHdrs->FileHeader.Characteristics & (IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL)) == IMAGE_FILE_EXECUTABLE_IMAGE
+ && pHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC
+ && pHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES
+ )
+ {
+ /* Find the MTE. */
+ RT_ZERO(uMte);
+ uMte.v32.DllBase = KernelAddr.FlatPtr;
+ uMte.v32.EntryPoint = KernelAddr.FlatPtr + pHdrs->OptionalHeader.AddressOfEntryPoint;
+ uMte.v32.SizeOfImage = !fNt31 ? pHdrs->OptionalHeader.SizeOfImage : 0; /* NT 3.1 didn't set the size. */
+ DBGFADDRESS HitAddr;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, uEnd - KernelAddr.FlatPtr,
+ 4 /*align*/, &uMte.v32.DllBase, 3 * sizeof(uint32_t), &HitAddr);
+ while (RT_SUCCESS(rc))
+ {
+ /* check the name. */
+ DBGFADDRESS MteAddr = HitAddr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrSub(&MteAddr, RT_OFFSETOF(NTMTE32, DllBase)),
+ &uMte2.v32, sizeof(uMte2.v32));
+ if ( RT_SUCCESS(rc)
+ && uMte2.v32.DllBase == uMte.v32.DllBase
+ && uMte2.v32.EntryPoint == uMte.v32.EntryPoint
+ && uMte2.v32.SizeOfImage == uMte.v32.SizeOfImage
+ && WINNT32_VALID_ADDRESS(uMte2.v32.InLoadOrderLinks.Flink)
+ && WINNT32_VALID_ADDRESS(uMte2.v32.BaseDllName.Buffer)
+ && WINNT32_VALID_ADDRESS(uMte2.v32.FullDllName.Buffer)
+ && uMte2.v32.BaseDllName.Length <= 128
+ && uMte2.v32.FullDllName.Length <= 260
+ )
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uMte2.v32.BaseDllName.Buffer),
+ u.wsz, uMte2.v32.BaseDllName.Length);
+ u.wsz[uMte2.v32.BaseDllName.Length / 2] = '\0';
+ if ( RT_SUCCESS(rc)
+ && ( !RTUtf16ICmp(u.wsz, g_wszKernelNames[0])
+ /* || !RTUtf16ICmp(u.wsz, g_wszKernelNames[1]) */
+ )
+ )
+ {
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr,
+ uMte2.v32.InLoadOrderLinks.Blink),
+ &uMte3.v32, RT_SIZEOFMEMB(NTMTE32, InLoadOrderLinks));
+ if ( RT_SUCCESS(rc)
+ && uMte3.v32.InLoadOrderLinks.Flink == MteAddr.FlatPtr
+ && WINNT32_VALID_ADDRESS(uMte3.v32.InLoadOrderLinks.Blink) )
+ {
+ Log(("DigWinNt: MteAddr=%RGv KernelAddr=%RGv SizeOfImage=%x &PsLoadedModuleList=%RGv (32-bit)\n",
+ MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v32.SizeOfImage, Addr.FlatPtr));
+ pThis->KernelAddr = KernelAddr;
+ pThis->KernelMteAddr = MteAddr;
+ pThis->PsLoadedModuleListAddr = Addr;
+ pThis->f32Bit = true;
+ pThis->fNt31 = fNt31;
+ return true;
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ Log2(("DigWinNt: Wrong module: MteAddr=%RGv ImageAddr=%RGv SizeOfImage=%#x '%ls'\n",
+ MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v32.SizeOfImage, u.wsz));
+ break; /* Not NT kernel */
+ }
+ }
+
+ /* next */
+ pVMM->pfnDBGFR3AddrAdd(&HitAddr, 4);
+ if (HitAddr.FlatPtr < uEnd)
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, uEnd - HitAddr.FlatPtr,
+ 4 /*align*/, &uMte.v32.DllBase, 3 * sizeof(uint32_t), &HitAddr);
+ else
+ rc = VERR_DBGF_MEM_NOT_FOUND;
+ }
+ }
+ }
+ else
+ {
+ IMAGE_NT_HEADERS64 const *pHdrs = (IMAGE_NT_HEADERS64 const *)&u.au8[u.MzHdr.e_lfanew];
+ if ( pHdrs->Signature == IMAGE_NT_SIGNATURE
+ && pHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64
+ && pHdrs->FileHeader.SizeOfOptionalHeader == sizeof(pHdrs->OptionalHeader)
+ && pHdrs->FileHeader.NumberOfSections >= 10 /* the kernel has lots */
+ && (pHdrs->FileHeader.Characteristics & (IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DLL))
+ == IMAGE_FILE_EXECUTABLE_IMAGE
+ && pHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC
+ && pHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES
+ )
+ {
+ /* Find the MTE. */
+ RT_ZERO(uMte.v64);
+ uMte.v64.DllBase = KernelAddr.FlatPtr;
+ uMte.v64.EntryPoint = KernelAddr.FlatPtr + pHdrs->OptionalHeader.AddressOfEntryPoint;
+ uMte.v64.SizeOfImage = pHdrs->OptionalHeader.SizeOfImage;
+ DBGFADDRESS ScanAddr;
+ DBGFADDRESS HitAddr;
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &ScanAddr, uStart),
+ uEnd - uStart, 8 /*align*/, &uMte.v64.DllBase, 5 * sizeof(uint32_t), &HitAddr);
+ while (RT_SUCCESS(rc))
+ {
+ /* Read the start of the MTE and check some basic members. */
+ DBGFADDRESS MteAddr = HitAddr;
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrSub(&MteAddr, RT_OFFSETOF(NTMTE64, DllBase)),
+ &uMte2.v64, sizeof(uMte2.v64));
+ if ( RT_SUCCESS(rc)
+ && uMte2.v64.DllBase == uMte.v64.DllBase
+ && uMte2.v64.EntryPoint == uMte.v64.EntryPoint
+ && uMte2.v64.SizeOfImage == uMte.v64.SizeOfImage
+ && WINNT64_VALID_ADDRESS(uMte2.v64.InLoadOrderLinks.Flink)
+ && WINNT64_VALID_ADDRESS(uMte2.v64.BaseDllName.Buffer)
+ && WINNT64_VALID_ADDRESS(uMte2.v64.FullDllName.Buffer)
+ && uMte2.v64.BaseDllName.Length <= 128
+ && uMte2.v64.FullDllName.Length <= 260
+ )
+ {
+ /* Try read the base name and compare with known NT kernel names. */
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr, uMte2.v64.BaseDllName.Buffer),
+ u.wsz, uMte2.v64.BaseDllName.Length);
+ u.wsz[uMte2.v64.BaseDllName.Length / 2] = '\0';
+ if ( RT_SUCCESS(rc)
+ && ( !RTUtf16ICmp(u.wsz, g_wszKernelNames[0])
+ /* || !RTUtf16ICmp(u.wsz, g_wszKernelNames[1]) */
+ )
+ )
+ {
+ /* Read the link entry of the previous entry in the list and check that its
+ forward pointer points at the MTE we've found. */
+ rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/,
+ pVMM->pfnDBGFR3AddrFromFlat(pUVM, &Addr,
+ uMte2.v64.InLoadOrderLinks.Blink),
+ &uMte3.v64, RT_SIZEOFMEMB(NTMTE64, InLoadOrderLinks));
+ if ( RT_SUCCESS(rc)
+ && uMte3.v64.InLoadOrderLinks.Flink == MteAddr.FlatPtr
+ && WINNT64_VALID_ADDRESS(uMte3.v64.InLoadOrderLinks.Blink) )
+ {
+ Log(("DigWinNt: MteAddr=%RGv KernelAddr=%RGv SizeOfImage=%x &PsLoadedModuleList=%RGv (32-bit)\n",
+ MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v64.SizeOfImage, Addr.FlatPtr));
+ pThis->KernelAddr = KernelAddr;
+ pThis->KernelMteAddr = MteAddr;
+ pThis->PsLoadedModuleListAddr = Addr;
+ pThis->f32Bit = false;
+ pThis->fNt31 = false;
+ return true;
+ }
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ Log2(("DigWinNt: Wrong module: MteAddr=%RGv ImageAddr=%RGv SizeOfImage=%#x '%ls'\n",
+ MteAddr.FlatPtr, KernelAddr.FlatPtr, uMte2.v64.SizeOfImage, u.wsz));
+ break; /* Not NT kernel */
+ }
+ }
+
+ /* next */
+ pVMM->pfnDBGFR3AddrAdd(&HitAddr, 8);
+ if (HitAddr.FlatPtr < uEnd)
+ rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, uEnd - HitAddr.FlatPtr,
+ 8 /*align*/, &uMte.v64.DllBase, 3 * sizeof(uint32_t), &HitAddr);
+ else
+ rc = VERR_DBGF_MEM_NOT_FOUND;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnDestruct
+ */
+static DECLCALLBACK(void) dbgDiggerWinNtDestruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM, pvData);
+}
+
+
+/**
+ * @copydoc DBGFOSREG::pfnConstruct
+ */
+static DECLCALLBACK(int) dbgDiggerWinNtConstruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData)
+{
+ RT_NOREF(pUVM, pVMM);
+ PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData;
+ pThis->fValid = false;
+ pThis->f32Bit = false;
+ pThis->enmVer = DBGDIGGERWINNTVER_UNKNOWN;
+
+ pThis->IWinNt.u32Magic = DBGFOSIWINNT_MAGIC;
+ pThis->IWinNt.pfnQueryVersion = dbgDiggerWinNtIWinNt_QueryVersion;
+ pThis->IWinNt.pfnQueryKernelPtrs = dbgDiggerWinNtIWinNt_QueryKernelPtrs;
+ pThis->IWinNt.pfnQueryKpcrForVCpu = dbgDiggerWinNtIWinNt_QueryKpcrForVCpu;
+ pThis->IWinNt.pfnQueryCurThrdForVCpu = dbgDiggerWinNtIWinNt_QueryCurThrdForVCpu;
+ pThis->IWinNt.u32EndMagic = DBGFOSIWINNT_MAGIC;
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+ pThis->hBpDbgPrint = NIL_DBGFBP;
+ pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+const DBGFOSREG g_DBGDiggerWinNt =
+{
+ /* .u32Magic = */ DBGFOSREG_MAGIC,
+ /* .fFlags = */ 0,
+ /* .cbData = */ sizeof(DBGDIGGERWINNT),
+ /* .szName = */ "WinNT",
+ /* .pfnConstruct = */ dbgDiggerWinNtConstruct,
+ /* .pfnDestruct = */ dbgDiggerWinNtDestruct,
+ /* .pfnProbe = */ dbgDiggerWinNtProbe,
+ /* .pfnInit = */ dbgDiggerWinNtInit,
+ /* .pfnRefresh = */ dbgDiggerWinNtRefresh,
+ /* .pfnTerm = */ dbgDiggerWinNtTerm,
+ /* .pfnQueryVersion = */ dbgDiggerWinNtQueryVersion,
+ /* .pfnQueryInterface = */ dbgDiggerWinNtQueryInterface,
+ /* .pfnStackUnwindAssist = */ dbgDiggerWinNtStackUnwindAssist,
+ /* .u32EndMagic = */ DBGFOSREG_MAGIC
+};
+
diff --git a/src/VBox/Debugger/DBGPlugIns.h b/src/VBox/Debugger/DBGPlugIns.h
new file mode 100644
index 00000000..975df4b3
--- /dev/null
+++ b/src/VBox/Debugger/DBGPlugIns.h
@@ -0,0 +1,51 @@
+/* $Id: DBGPlugIns.h $ */
+/** @file
+ * DBGPlugIns - Debugger Plug-Ins.
+ *
+ * This is just a temporary static wrapper for what may eventually
+ * become some fancy dynamic stuff.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_DBGPlugIns_h
+#define DEBUGGER_INCLUDED_SRC_DBGPlugIns_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/vmm/dbgf.h>
+
+RT_C_DECLS_BEGIN
+
+extern const DBGFOSREG g_DBGDiggerFreeBsd;
+extern const DBGFOSREG g_DBGDiggerDarwin;
+extern const DBGFOSREG g_DBGDiggerLinux;
+extern const DBGFOSREG g_DBGDiggerOS2;
+extern const DBGFOSREG g_DBGDiggerSolaris;
+extern const DBGFOSREG g_DBGDiggerWinNt;
+
+RT_C_DECLS_END
+
+#endif /* !DEBUGGER_INCLUDED_SRC_DBGPlugIns_h */
+
diff --git a/src/VBox/Debugger/Makefile.kmk b/src/VBox/Debugger/Makefile.kmk
new file mode 100644
index 00000000..5c343ae2
--- /dev/null
+++ b/src/VBox/Debugger/Makefile.kmk
@@ -0,0 +1,136 @@
+# $Id: Makefile.kmk $
+## @file
+# Makefile for the VBox debugger.
+#
+
+#
+# Copyright (C) 2006-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
+#
+
+SUB_DEPTH = ../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# The targets.
+#
+ifdef VBOX_WITH_DEBUGGER
+ LIBRARIES += Debugger
+ ifdef VBOX_WITH_TESTCASES
+ PROGRAMS += tstDBGCParser
+ endif
+endif # VBOX_WITH_DEBUGGER
+
+
+#
+# Debugger library - linked into VBoxVMM.
+#
+Debugger_TEMPLATE = VBoxR3Dll
+Debugger_DEFS = IN_VMM_R3 IN_DBG_R3 IN_DIS
+ifneq ($(KBUILD_TYPE),release)
+ Debugger_DEFS += VBOX_WITH_DEBUGGER_TCP_BY_DEFAULT
+endif
+Debugger_SOURCES = \
+ DBGConsole.cpp \
+ DBGCEval.cpp \
+ DBGCBuiltInSymbols.cpp \
+ DBGCCmdHlp.cpp \
+ DBGCCmdWorkers.cpp \
+ DBGCCommands.cpp \
+ DBGCDumpImage.cpp \
+ DBGCFunctions.cpp \
+ DBGCEmulateCodeView.cpp \
+ DBGCOps.cpp \
+ DBGCGdbRemoteStub.cpp \
+ DBGCRemoteKd.cpp \
+ DBGCIo.cpp \
+ DBGCIoProvTcp.cpp \
+ DBGCIoProvUdp.cpp \
+ DBGCIoProvIpc.cpp \
+ DBGCScreenAscii.cpp
+
+#
+# The diggers plug-in.
+#
+DLLS += DbgPlugInDiggers
+DbgPlugInDiggers_TEMPLATE = VBoxR3Dll
+DbgPlugInDiggers_DEFS = IN_DIS
+DbgPlugInDiggers_SOURCES = \
+ DBGPlugInDiggers.cpp \
+ DBGPlugInDarwin.cpp \
+ DBGPlugInLinux.cpp \
+ DBGPlugInSolaris.cpp \
+ DBGPlugInWinNt.cpp \
+ DBGPlugInOS2.cpp \
+ DBGPlugInFreeBsd.cpp \
+ DBGPlugInCommonELF.cpp
+DbgPlugInDiggers_LIBS = \
+ $(PATH_STAGE_LIB)/DisasmR3$(VBOX_SUFF_LIB) \
+ $(LIB_RUNTIME)
+$(call VBOX_SET_VER_INFO_DLL,DbgPlugInDiggers,VirtualBox Debugger Guest OS Digger Plug-in)
+
+
+#
+# The DBGC parser testcase.
+# This stubs all the VBoxVMM APIs.
+#
+tstDBGCParser_TEMPLATE = VBoxR3TstExe
+tstDBGCParser_DEFS = IN_VMM_R3
+tstDBGCParser_CXXFLAGS = $(VBOX_C_CXX_FLAGS_NO_UNUSED_PARAMETERS)
+tstDBGCParser_SOURCES = \
+ testcase/tstDBGCParser.cpp \
+ testcase/tstDBGCStubs.cpp
+tstDBGCParser_LIBS = \
+ $(Debugger_1_TARGET) \
+ $(LIB_RUNTIME)
+
+
+if defined(VBOX_WITH_QTGUI) && defined(VBOX_WITH_DEBUGGER_GUI)
+ #
+ # Debugger GUI component (Qt).
+ #
+ ifndef VBOX_WITH_QT6
+ USES += qt5
+ else
+ USES += qt6
+ endif
+ DLLS += VBoxDbg
+ VBoxDbg_TEMPLATE = VBoxQtGuiDll
+ VBoxDbg_DEFS = IN_DBG_R3
+ VBoxDbg_INCS = .
+ VBoxDbg_QT_MODULES = Core Gui Widgets
+ VBoxDbg_QT_MOCHDRS = \
+ VBoxDbgGui.h \
+ VBoxDbgConsole.h \
+ VBoxDbgStatsQt.h
+ VBoxDbg_SOURCES = \
+ VBoxDbg.cpp \
+ VBoxDbgGui.cpp \
+ VBoxDbgBase.cpp \
+ VBoxDbgConsole.cpp \
+ VBoxDbgStatsQt.cpp
+ VBoxDbg_LDFLAGS.darwin = \
+ -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxDbg.dylib
+ $(call VBOX_SET_VER_INFO_DLL,VBoxDbg,VirtualBox Debugger GUI)
+endif # Qt
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/Debugger/VBoxDbg.cpp b/src/VBox/Debugger/VBoxDbg.cpp
new file mode 100644
index 00000000..7fca634b
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbg.cpp
@@ -0,0 +1,275 @@
+/* $Id: VBoxDbg.cpp $ */
+/** @file
+ * VBox Debugger GUI.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGG
+#define VBOX_COM_NO_ATL
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h> /* Include via cleanup wrapper before VirtualBox.h includes it via rpc.h. */
+# include <VirtualBox.h>
+#else /* !RT_OS_WINDOWS */
+# include <VirtualBox_XPCOM.h>
+#endif /* !RT_OS_WINDOWS */
+#include <VBox/dbggui.h>
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+
+#include "VBoxDbgGui.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Debugger GUI instance data.
+ */
+typedef struct DBGGUI
+{
+ /** Magic number (DBGGUI_MAGIC). */
+ uint32_t u32Magic;
+ /** Pointer to the Debugger GUI manager object. */
+ VBoxDbgGui *pVBoxDbgGui;
+} DBGGUI;
+
+/** DBGGUI magic value (Werner Heisenberg). */
+#define DBGGUI_MAGIC 0x19011205
+/** Invalid DBGGUI magic value. */
+#define DBGGUI_MAGIC_DEAD 0x19760201
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Virtual method table for simplifying dynamic linking. */
+static const DBGGUIVT g_dbgGuiVT =
+{
+ DBGGUIVT_VERSION,
+ DBGGuiDestroy,
+ DBGGuiAdjustRelativePos,
+ DBGGuiShowStatistics,
+ DBGGuiShowCommandLine,
+ DBGGuiSetParent,
+ DBGGuiSetMenu,
+ DBGGUIVT_VERSION
+};
+
+
+/**
+ * Internal worker for DBGGuiCreate and DBGGuiCreateForVM.
+ *
+ * @returns VBox status code.
+ * @param pSession The ISession interface. (DBGGuiCreate)
+ * @param pUVM The VM handle. (DBGGuiCreateForVM)
+ * @param pVMM The VMM function table.
+ * @param ppGui See DBGGuiCreate.
+ * @param ppGuiVT See DBGGuiCreate.
+ */
+static int dbgGuiCreate(ISession *pSession, PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGGUI *ppGui, PCDBGGUIVT *ppGuiVT)
+{
+ /*
+ * Allocate and initialize the Debugger GUI handle.
+ */
+ PDBGGUI pGui = (PDBGGUI)RTMemAlloc(sizeof(*pGui));
+ if (!pGui)
+ return VERR_NO_MEMORY;
+ pGui->u32Magic = DBGGUI_MAGIC;
+ pGui->pVBoxDbgGui = new VBoxDbgGui();
+
+ int rc;
+ if (pSession)
+ rc = pGui->pVBoxDbgGui->init(pSession);
+ else
+ rc = pGui->pVBoxDbgGui->init(pUVM, pVMM);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Successfully initialized.
+ */
+ *ppGui = pGui;
+ if (ppGuiVT)
+ *ppGuiVT = &g_dbgGuiVT;
+ return rc;
+ }
+
+ /*
+ * Failed, cleanup.
+ */
+ delete pGui->pVBoxDbgGui;
+ RTMemFree(pGui);
+ *ppGui = NULL;
+ if (ppGuiVT)
+ *ppGuiVT = NULL;
+ return rc;
+}
+
+
+/**
+ * Creates the debugger GUI.
+ *
+ * @returns VBox status code.
+ * @param pSession The VirtualBox session.
+ * @param ppGui Where to store the pointer to the debugger instance.
+ * @param ppGuiVT Where to store the virtual method table pointer.
+ * Optional.
+ */
+DBGDECL(int) DBGGuiCreate(ISession *pSession, PDBGGUI *ppGui, PCDBGGUIVT *ppGuiVT)
+{
+ AssertPtrReturn(pSession, VERR_INVALID_POINTER);
+ return dbgGuiCreate(pSession, NULL, NULL, ppGui, ppGuiVT);
+}
+
+
+/**
+ * Creates the debugger GUI given a VM handle.
+ *
+ * @returns VBox status code.
+ * @param pUVM The VM handle.
+ * @param pVMM The VMM function table.
+ * @param ppGui Where to store the pointer to the debugger instance.
+ * @param ppGuiVT Where to store the virtual method table pointer.
+ * Optional.
+ */
+DBGDECL(int) DBGGuiCreateForVM(PUVM pUVM, PCVMMR3VTABLE pVMM, PDBGGUI *ppGui, PCDBGGUIVT *ppGuiVT)
+{
+ AssertPtrReturn(pUVM, VERR_INVALID_POINTER);
+ AssertPtrReturn(pVMM, VERR_INVALID_POINTER);
+ AssertReturn(VMMR3VTABLE_IS_COMPATIBLE(pVMM->uMagicVersion), VERR_VERSION_MISMATCH);
+ AssertReturn(pVMM->pfnVMR3RetainUVM(pUVM) != UINT32_MAX, VERR_INVALID_POINTER);
+
+ int rc = dbgGuiCreate(NULL, pUVM, pVMM, ppGui, ppGuiVT);
+
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ return rc;
+}
+
+
+/**
+ * Destroys the debugger GUI.
+ *
+ * @returns VBox status code.
+ * @param pGui The instance returned by DBGGuiCreate().
+ */
+DBGDECL(int) DBGGuiDestroy(PDBGGUI pGui)
+{
+ /*
+ * Validate.
+ */
+ if (!pGui)
+ return VERR_INVALID_PARAMETER;
+ AssertMsgReturn(pGui->u32Magic == DBGGUI_MAGIC, ("u32Magic=%#x\n", pGui->u32Magic), VERR_INVALID_PARAMETER);
+
+ /*
+ * Do the job.
+ */
+ pGui->u32Magic = DBGGUI_MAGIC_DEAD;
+ delete pGui->pVBoxDbgGui;
+ RTMemFree(pGui);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Notifies the debugger GUI that the console window (or whatever) has changed
+ * size or position.
+ *
+ * @param pGui The instance returned by DBGGuiCreate().
+ * @param x The x-coordinate of the window the debugger is relative to.
+ * @param y The y-coordinate of the window the debugger is relative to.
+ * @param cx The width of the window the debugger is relative to.
+ * @param cy The height of the window the debugger is relative to.
+ */
+DBGDECL(void) DBGGuiAdjustRelativePos(PDBGGUI pGui, int x, int y, unsigned cx, unsigned cy)
+{
+ AssertReturn(pGui, (void)VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pGui->u32Magic == DBGGUI_MAGIC, ("u32Magic=%#x\n", pGui->u32Magic), (void)VERR_INVALID_PARAMETER);
+ pGui->pVBoxDbgGui->adjustRelativePos(x, y, cx, cy);
+}
+
+
+/**
+ * Shows the default statistics window.
+ *
+ * @returns VBox status code.
+ * @param pGui The instance returned by DBGGuiCreate().
+ * @param pszFilter Filter pattern.
+ * @param pszExpand Expand pattern.
+ */
+DBGDECL(int) DBGGuiShowStatistics(PDBGGUI pGui, const char *pszFilter, const char *pszExpand)
+{
+ AssertReturn(pGui, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pGui->u32Magic == DBGGUI_MAGIC, ("u32Magic=%#x\n", pGui->u32Magic), VERR_INVALID_PARAMETER);
+ return pGui->pVBoxDbgGui->showStatistics(pszFilter, pszExpand);
+}
+
+
+/**
+ * Shows the default command line window.
+ *
+ * @returns VBox status code.
+ * @param pGui The instance returned by DBGGuiCreate().
+ */
+DBGDECL(int) DBGGuiShowCommandLine(PDBGGUI pGui)
+{
+ AssertReturn(pGui, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pGui->u32Magic == DBGGUI_MAGIC, ("u32Magic=%#x\n", pGui->u32Magic), VERR_INVALID_PARAMETER);
+ return pGui->pVBoxDbgGui->showConsole();
+}
+
+
+/**
+ * Sets the parent windows.
+ *
+ * @param pGui The instance returned by DBGGuiCreate().
+ * @param pvParent Pointer to a QWidget object.
+ *
+ * @remarks This will no affect any existing windows, so call it right after
+ * creating the thing.
+ */
+DBGDECL(void) DBGGuiSetParent(PDBGGUI pGui, void *pvParent)
+{
+ return pGui->pVBoxDbgGui->setParent((QWidget *)pvParent);
+}
+
+
+/**
+ * Sets the debug menu object.
+ *
+ * @param pGui The instance returned by DBGGuiCreate().
+ * @param pvMenu Pointer to a QMenu object.
+ *
+ * @remarks Call right after creation or risk losing menu item.
+ */
+DBGDECL(void) DBGGuiSetMenu(PDBGGUI pGui, void *pvMenu)
+{
+ return pGui->pVBoxDbgGui->setMenu((QMenu *)pvMenu);
+}
+
diff --git a/src/VBox/Debugger/VBoxDbgBase.cpp b/src/VBox/Debugger/VBoxDbgBase.cpp
new file mode 100644
index 00000000..e537840c
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgBase.cpp
@@ -0,0 +1,326 @@
+/* $Id: VBoxDbgBase.cpp $ */
+/** @file
+ * VBox Debugger GUI - Base classes.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGG
+#include <iprt/errcore.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <limits.h>
+#include "VBoxDbgBase.h"
+#include "VBoxDbgGui.h"
+
+#include <QApplication>
+#include <QWidgetList>
+
+
+
+VBoxDbgBase::VBoxDbgBase(VBoxDbgGui *a_pDbgGui)
+ : m_pDbgGui(a_pDbgGui), m_pUVM(NULL), m_pVMM(NULL), m_hGUIThread(RTThreadNativeSelf())
+{
+ NOREF(m_pDbgGui); /* shut up warning. */
+
+ /*
+ * Register
+ */
+ m_pUVM = a_pDbgGui->getUvmHandle();
+ m_pVMM = a_pDbgGui->getVMMFunctionTable();
+ if (m_pUVM && m_pVMM)
+ {
+ m_pVMM->pfnVMR3RetainUVM(m_pUVM);
+
+ int rc = m_pVMM->pfnVMR3AtStateRegister(m_pUVM, atStateChange, this);
+ AssertRC(rc);
+ }
+}
+
+
+VBoxDbgBase::~VBoxDbgBase()
+{
+ /*
+ * If the VM is still around.
+ */
+ /** @todo need to do some locking here? */
+ PUVM pUVM = ASMAtomicXchgPtrT(&m_pUVM, NULL, PUVM);
+ PCVMMR3VTABLE pVMM = ASMAtomicXchgPtrT(&m_pVMM, NULL, PCVMMR3VTABLE);
+ if (pUVM && pVMM)
+ {
+ int rc = pVMM->pfnVMR3AtStateDeregister(pUVM, atStateChange, this);
+ AssertRC(rc);
+
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ }
+}
+
+
+int
+VBoxDbgBase::stamReset(const QString &rPat)
+{
+ QByteArray Utf8Array = rPat.toUtf8();
+ const char *pszPat = !rPat.isEmpty() ? Utf8Array.constData() : NULL;
+ PUVM pUVM = m_pUVM;
+ PCVMMR3VTABLE pVMM = m_pVMM;
+ if ( pUVM
+ && pVMM
+ && pVMM->pfnVMR3GetStateU(pUVM) < VMSTATE_DESTROYING)
+ return pVMM->pfnSTAMR3Reset(pUVM, pszPat);
+ return VERR_INVALID_HANDLE;
+}
+
+
+int
+VBoxDbgBase::stamEnum(const QString &rPat, PFNSTAMR3ENUM pfnEnum, void *pvUser)
+{
+ QByteArray Utf8Array = rPat.toUtf8();
+ const char *pszPat = !rPat.isEmpty() ? Utf8Array.constData() : NULL;
+ PUVM pUVM = m_pUVM;
+ PCVMMR3VTABLE pVMM = m_pVMM;
+ if ( pUVM
+ && pVMM
+ && pVMM->pfnVMR3GetStateU(pUVM) < VMSTATE_DESTROYING)
+ return pVMM->pfnSTAMR3Enum(pUVM, pszPat, pfnEnum, pvUser);
+ return VERR_INVALID_HANDLE;
+}
+
+
+int
+VBoxDbgBase::dbgcCreate(PCDBGCIO pIo, unsigned fFlags)
+{
+ PUVM pUVM = m_pUVM;
+ PCVMMR3VTABLE pVMM = m_pVMM;
+ if ( pUVM
+ && pVMM
+ && pVMM->pfnVMR3GetStateU(pUVM) < VMSTATE_DESTROYING)
+ return pVMM->pfnDBGCCreate(pUVM, pIo, fFlags);
+ return VERR_INVALID_HANDLE;
+}
+
+
+/*static*/ DECLCALLBACK(void)
+VBoxDbgBase::atStateChange(PUVM pUVM, PCVMMR3VTABLE pVMM, VMSTATE enmState, VMSTATE /*enmOldState*/, void *pvUser)
+{
+ VBoxDbgBase *pThis = (VBoxDbgBase *)pvUser; NOREF(pUVM);
+ switch (enmState)
+ {
+ case VMSTATE_TERMINATED:
+ {
+ /** @todo need to do some locking here? */
+ PUVM pUVM2 = ASMAtomicXchgPtrT(&pThis->m_pUVM, NULL, PUVM);
+ PCVMMR3VTABLE pVMM2 = ASMAtomicXchgPtrT(&pThis->m_pVMM, NULL, PCVMMR3VTABLE);
+ if (pUVM2 && pVMM2)
+ {
+ Assert(pUVM2 == pUVM);
+ Assert(pVMM2 == pVMM);
+ pThis->sigTerminated();
+ pVMM->pfnVMR3ReleaseUVM(pUVM2);
+ }
+ break;
+ }
+
+ case VMSTATE_DESTROYING:
+ pThis->sigDestroying();
+ break;
+
+ default:
+ break;
+ }
+ RT_NOREF(pVMM);
+}
+
+
+void
+VBoxDbgBase::sigDestroying()
+{
+}
+
+
+void
+VBoxDbgBase::sigTerminated()
+{
+}
+
+
+
+
+//
+//
+//
+// V B o x D b g B a s e W i n d o w
+// V B o x D b g B a s e W i n d o w
+// V B o x D b g B a s e W i n d o w
+//
+//
+//
+
+unsigned VBoxDbgBaseWindow::m_cxBorder = 0;
+unsigned VBoxDbgBaseWindow::m_cyBorder = 0;
+
+
+VBoxDbgBaseWindow::VBoxDbgBaseWindow(VBoxDbgGui *a_pDbgGui, QWidget *a_pParent, const char *a_pszTitle)
+ : QWidget(a_pParent, Qt::Window), VBoxDbgBase(a_pDbgGui), m_pszTitle(a_pszTitle), m_fPolished(false)
+ , m_x(INT_MAX), m_y(INT_MAX), m_cx(0), m_cy(0)
+{
+ /* Set the title, using the parent one as prefix when possible: */
+ if (!parent())
+ {
+ QString strMachineName = a_pDbgGui->getMachineName();
+ if (strMachineName.isEmpty())
+ setWindowTitle(QString("VBoxDbg - %1").arg(m_pszTitle));
+ else
+ setWindowTitle(QString("%1 - VBoxDbg - %2").arg(strMachineName).arg(m_pszTitle));
+ }
+ else
+ {
+ setWindowTitle(QString("%1 - %2").arg(parentWidget()->windowTitle()).arg(m_pszTitle));
+
+ /* Install an event filter so we can make adjustments when the parent title changes: */
+ parent()->installEventFilter(this);
+ }
+}
+
+
+VBoxDbgBaseWindow::~VBoxDbgBaseWindow()
+{
+
+}
+
+
+void
+VBoxDbgBaseWindow::vShow()
+{
+ show();
+ /** @todo this ain't working right. HELP! */
+ setWindowState(windowState() & ~Qt::WindowMinimized);
+ //activateWindow();
+ //setFocus();
+ vPolishSizeAndPos();
+}
+
+
+void
+VBoxDbgBaseWindow::vReposition(int a_x, int a_y, unsigned a_cx, unsigned a_cy, bool a_fResize)
+{
+ if (a_fResize)
+ {
+ m_cx = a_cx;
+ m_cy = a_cy;
+
+ QSize BorderSize = frameSize() - size();
+ if (BorderSize == QSize(0,0))
+ BorderSize = vGuessBorderSizes();
+
+ resize(a_cx - BorderSize.width(), a_cy - BorderSize.height());
+ }
+
+ m_x = a_x;
+ m_y = a_y;
+ move(a_x, a_y);
+}
+
+
+bool
+VBoxDbgBaseWindow::event(QEvent *a_pEvt)
+{
+ bool fRc = QWidget::event(a_pEvt);
+ if ( a_pEvt->type() == QEvent::Paint
+ || a_pEvt->type() == QEvent::UpdateRequest
+ || a_pEvt->type() == QEvent::LayoutRequest) /** @todo Someone with Qt knowledge should figure out how to properly do this. */
+ vPolishSizeAndPos();
+ return fRc;
+}
+
+
+bool VBoxDbgBaseWindow::eventFilter(QObject *pWatched, QEvent *pEvent)
+{
+ /* We're only interested in title changes to the parent so we can amend our own title: */
+ if ( pWatched == parent()
+ && pEvent->type() == QEvent::WindowTitleChange)
+ setWindowTitle(QString("%1 - %2").arg(parentWidget()->windowTitle()).arg(m_pszTitle));
+
+ /* Forward to base-class: */
+ return QWidget::eventFilter(pWatched, pEvent);
+}
+
+
+void
+VBoxDbgBaseWindow::vPolishSizeAndPos()
+{
+ /* Ignore if already done or no size set. */
+ if ( m_fPolished
+ || (m_x == INT_MAX && m_y == INT_MAX))
+ return;
+
+ QSize BorderSize = frameSize() - size();
+ if (BorderSize != QSize(0,0))
+ m_fPolished = true;
+
+ vReposition(m_x, m_y, m_cx, m_cy, m_cx || m_cy);
+}
+
+
+QSize
+VBoxDbgBaseWindow::vGuessBorderSizes()
+{
+#ifdef Q_WS_X11 /* (from the qt gui) */
+ /*
+ * On X11, there is no way to determine frame geometry (including WM
+ * decorations) before the widget is shown for the first time. Stupidly
+ * enumerate other top level widgets to find the thickest frame.
+ */
+ if (!m_cxBorder && !m_cyBorder) /* (only till we're successful) */
+ {
+ int cxExtra = 0;
+ int cyExtra = 0;
+
+ QWidgetList WidgetList = QApplication::topLevelWidgets();
+ for (QListIterator<QWidget *> it(WidgetList); it.hasNext(); )
+ {
+ QWidget *pCurWidget = it.next();
+ if (pCurWidget->isVisible())
+ {
+ int const cxFrame = pCurWidget->frameGeometry().width() - pCurWidget->width();
+ cxExtra = qMax(cxExtra, cxFrame);
+ int const cyFrame = pCurWidget->frameGeometry().height() - pCurWidget->height();
+ cyExtra = qMax(cyExtra, cyFrame);
+ if (cyExtra && cxExtra)
+ break;
+ }
+ }
+
+ if (cxExtra || cyExtra)
+ {
+ m_cxBorder = cxExtra;
+ m_cyBorder = cyExtra;
+ }
+ }
+#endif /* X11 */
+ return QSize(m_cxBorder, m_cyBorder);
+}
+
diff --git a/src/VBox/Debugger/VBoxDbgBase.h b/src/VBox/Debugger/VBoxDbgBase.h
new file mode 100644
index 00000000..58689bba
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgBase.h
@@ -0,0 +1,222 @@
+/* $Id: VBoxDbgBase.h $ */
+/** @file
+ * VBox Debugger GUI - Base classes.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_VBoxDbgBase_h
+#define DEBUGGER_INCLUDED_SRC_VBoxDbgBase_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/vmm/vmmr3vtable.h>
+#include <VBox/dbg.h>
+#include <iprt/thread.h>
+#include <QString>
+#include <QWidget>
+
+class VBoxDbgGui;
+
+
+/**
+ * VBox Debugger GUI Base Class.
+ *
+ * The purpose of this class is to hide the VM handle, abstract VM
+ * operations, and finally to make sure the GUI won't crash when
+ * the VM dies.
+ */
+class VBoxDbgBase
+{
+public:
+ /**
+ * Construct the object.
+ *
+ * @param a_pDbgGui Pointer to the debugger gui object.
+ */
+ VBoxDbgBase(VBoxDbgGui *a_pDbgGui);
+
+ /**
+ * Destructor.
+ */
+ virtual ~VBoxDbgBase();
+
+
+ /**
+ * Checks if the VM is OK for normal operations.
+ * @returns true if ok, false if not.
+ */
+ bool isVMOk() const
+ {
+ return m_pUVM != NULL;
+ }
+
+ /**
+ * Checks if the current thread is the GUI thread or not.
+ * @return true/false accordingly.
+ */
+ bool isGUIThread() const
+ {
+ return m_hGUIThread == RTThreadNativeSelf();
+ }
+
+ /** @name Operations
+ * @{ */
+ /**
+ * Wrapper for STAMR3Reset().
+ */
+ int stamReset(const QString &rPat);
+ /**
+ * Wrapper for STAMR3Enum().
+ */
+ int stamEnum(const QString &rPat, PFNSTAMR3ENUM pfnEnum, void *pvUser);
+ /**
+ * Wrapper for DBGCCreate().
+ */
+ int dbgcCreate(PCDBGCIO pIo, unsigned fFlags);
+ /** @} */
+
+
+protected:
+ /** @name Signals
+ * @{ */
+ /**
+ * Called when the VM is being destroyed.
+ */
+ virtual void sigDestroying();
+ /**
+ * Called when the VM has been terminated.
+ */
+ virtual void sigTerminated();
+ /** @} */
+
+
+private:
+ /** @callback_method_impl{FNVMATSTATE} */
+ static DECLCALLBACK(void) atStateChange(PUVM pUVM, PCVMMR3VTABLE pVMM, VMSTATE enmState, VMSTATE enmOldState, void *pvUser);
+
+private:
+ /** Pointer to the debugger GUI object. */
+ VBoxDbgGui *m_pDbgGui;
+ /** The user mode VM handle. */
+ PUVM volatile m_pUVM;
+ /** The VMM function table. */
+ PCVMMR3VTABLE volatile m_pVMM;
+ /** The handle of the GUI thread. */
+ RTNATIVETHREAD m_hGUIThread;
+};
+
+
+/**
+ * VBox Debugger GUI Base Window Class.
+ *
+ * This is just a combination of QWidget and VBoxDbgBase with some additional
+ * functionality for window management. This class is not intended for control
+ * widgets, only normal top-level windows.
+ */
+class VBoxDbgBaseWindow : public QWidget, public VBoxDbgBase
+{
+public:
+ /**
+ * Construct the object.
+ *
+ * @param a_pDbgGui Pointer to the debugger gui object.
+ * @param a_pParent Pointer to the parent object.
+ * @param a_pszTitle The window title string (persistent, not copied).
+ */
+ VBoxDbgBaseWindow(VBoxDbgGui *a_pDbgGui, QWidget *a_pParent, const char *a_pszTitle);
+
+ /**
+ * Destructor.
+ */
+ virtual ~VBoxDbgBaseWindow();
+
+ /**
+ * Shows the window and gives it focus.
+ */
+ void vShow();
+
+ /**
+ * Repositions the window, taking the frame decoration into account.
+ *
+ * @param a_x The new x coordinate.
+ * @param a_y The new x coordinate.
+ * @param a_cx The total width.
+ * @param a_cy The total height.
+ * @param a_fResize Whether to resize it as well.
+ */
+ void vReposition(int a_x, int a_y, unsigned a_cx, unsigned a_cy, bool a_fResize);
+
+protected:
+ /**
+ * For polishing the window size (X11 mess).
+ *
+ * @returns true / false.
+ * @param a_pEvt The event.
+ */
+ virtual bool event(QEvent *a_pEvt);
+
+ /**
+ * Event filter for various purposes (mainly title bar).
+ *
+ * @param pWatched The object event came to.
+ * @param pEvent The event being handled.
+ */
+ virtual bool eventFilter(QObject *pWatched, QEvent *pEvent);
+
+ /**
+ * Internal worker for polishing the size and position (X11 hacks).
+ */
+ void vPolishSizeAndPos();
+
+ /**
+ * Internal worker that guesses the border sizes.
+ */
+ QSize vGuessBorderSizes();
+
+private:
+ /** The Window title string (inflexible, read only). */
+ const char *m_pszTitle;
+ /** Whether we've done the size polishing in showEvent or not. */
+ bool m_fPolished;
+ /** The desired x coordinate. */
+ int m_x;
+ /** The desired y coordinate. */
+ int m_y;
+ /** The desired width. */
+ unsigned m_cx;
+ /** The desired height. */
+ unsigned m_cy;
+
+ /** Best effort x border size (for X11). */
+ static unsigned m_cxBorder;
+ /** Best effort y border size (for X11). */
+ static unsigned m_cyBorder;
+};
+
+#endif /* !DEBUGGER_INCLUDED_SRC_VBoxDbgBase_h */
+
diff --git a/src/VBox/Debugger/VBoxDbgConsole.cpp b/src/VBox/Debugger/VBoxDbgConsole.cpp
new file mode 100644
index 00000000..90381731
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgConsole.cpp
@@ -0,0 +1,1026 @@
+/* $Id: VBoxDbgConsole.cpp $ */
+/** @file
+ * VBox Debugger GUI - Console.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGG
+#include "VBoxDbgConsole.h"
+#include "VBoxDbgGui.h"
+
+#include <QLabel>
+#include <QApplication>
+#include <QFont>
+#include <QLineEdit>
+#include <QHBoxLayout>
+#include <QAction>
+#include <QContextMenuEvent>
+#include <QMenu>
+
+#include <VBox/dbg.h>
+#include <VBox/vmm/cfgm.h>
+#include <iprt/errcore.h>
+
+#include <iprt/thread.h>
+#include <iprt/tcp.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+
+#include <VBox/com/string.h>
+
+
+
+/*
+ *
+ * V B o x D b g C o n s o l e O u t p u t
+ * V B o x D b g C o n s o l e O u t p u t
+ * V B o x D b g C o n s o l e O u t p u t
+ *
+ *
+ */
+
+/*static*/ const uint32_t VBoxDbgConsoleOutput::s_uMinFontSize = 6;
+
+
+VBoxDbgConsoleOutput::VBoxDbgConsoleOutput(QWidget *pParent/* = NULL*/, IVirtualBox *a_pVirtualBox /* = NULL */,
+ const char *pszName/* = NULL*/)
+ : QTextEdit(pParent), m_uCurLine(0), m_uCurPos(0), m_hGUIThread(RTThreadNativeSelf()), m_pVirtualBox(a_pVirtualBox)
+{
+ setReadOnly(true);
+ setUndoRedoEnabled(false);
+ setOverwriteMode(false);
+ setPlainText("");
+ setTextInteractionFlags(Qt::TextBrowserInteraction);
+ setAutoFormatting(QTextEdit::AutoAll);
+ setTabChangesFocus(true);
+ setAcceptRichText(false);
+
+ /*
+ * Create actions for color-scheme menu items.
+ */
+ m_pGreenOnBlackAction = new QAction(tr("Green On Black"), this);
+ m_pGreenOnBlackAction->setCheckable(true);
+ m_pGreenOnBlackAction->setShortcut(Qt::ControlModifier + Qt::Key_1);
+ m_pGreenOnBlackAction->setData((int)kGreenOnBlack);
+ connect(m_pGreenOnBlackAction, SIGNAL(triggered()), this, SLOT(sltSelectColorScheme()));
+
+ m_pBlackOnWhiteAction = new QAction(tr("Black On White"), this);
+ m_pBlackOnWhiteAction->setCheckable(true);
+ m_pBlackOnWhiteAction->setShortcut(Qt::ControlModifier + Qt::Key_2);
+ m_pBlackOnWhiteAction->setData((int)kBlackOnWhite);
+ connect(m_pBlackOnWhiteAction, SIGNAL(triggered()), this, SLOT(sltSelectColorScheme()));
+
+ /* Create action group for grouping of exclusive color-scheme menu items. */
+ QActionGroup *pActionColorGroup = new QActionGroup(this);
+ pActionColorGroup->addAction(m_pGreenOnBlackAction);
+ pActionColorGroup->addAction(m_pBlackOnWhiteAction);
+ pActionColorGroup->setExclusive(true);
+
+ /*
+ * Create actions for font menu items.
+ */
+ m_pCourierFontAction = new QAction(tr("Courier"), this);
+ m_pCourierFontAction->setCheckable(true);
+ m_pCourierFontAction->setShortcut(Qt::ControlModifier + Qt::Key_D);
+ m_pCourierFontAction->setData((int)kFontType_Courier);
+ connect(m_pCourierFontAction, SIGNAL(triggered()), this, SLOT(sltSelectFontType()));
+
+ m_pMonospaceFontAction = new QAction(tr("Monospace"), this);
+ m_pMonospaceFontAction->setCheckable(true);
+ m_pMonospaceFontAction->setShortcut(Qt::ControlModifier + Qt::Key_M);
+ m_pMonospaceFontAction->setData((int)kFontType_Monospace);
+ connect(m_pMonospaceFontAction, SIGNAL(triggered()), this, SLOT(sltSelectFontType()));
+
+ /* Create action group for grouping of exclusive font menu items. */
+ QActionGroup *pActionFontGroup = new QActionGroup(this);
+ pActionFontGroup->addAction(m_pCourierFontAction);
+ pActionFontGroup->addAction(m_pMonospaceFontAction);
+ pActionFontGroup->setExclusive(true);
+
+ /*
+ * Create actions for font size menu.
+ */
+ uint32_t const uDefaultFontSize = font().pointSize();
+ m_pActionFontSizeGroup = new QActionGroup(this);
+ for (uint32_t i = 0; i < RT_ELEMENTS(m_apFontSizeActions); i++)
+ {
+ char szTitle[32];
+ RTStrPrintf(szTitle, sizeof(szTitle), s_uMinFontSize + i != uDefaultFontSize ? "%upt" : "%upt (default)",
+ s_uMinFontSize + i);
+ m_apFontSizeActions[i] = new QAction(tr(szTitle), this);
+ m_apFontSizeActions[i]->setCheckable(true);
+ m_apFontSizeActions[i]->setData(i + s_uMinFontSize);
+ connect(m_apFontSizeActions[i], SIGNAL(triggered()), this, SLOT(sltSelectFontSize()));
+ m_pActionFontSizeGroup->addAction(m_apFontSizeActions[i]);
+ }
+
+ /*
+ * Set the defaults (which syncs with the menu item checked state).
+ */
+ /* color scheme: */
+ com::Bstr bstrColor;
+ HRESULT hrc = m_pVirtualBox ? m_pVirtualBox->GetExtraData(com::Bstr("DbgConsole/ColorScheme").raw(), bstrColor.asOutParam()) : E_FAIL;
+ if ( SUCCEEDED(hrc)
+ && bstrColor.compareUtf8("blackonwhite", com::Bstr::CaseInsensitive) == 0)
+ setColorScheme(kBlackOnWhite, false /*fSaveIt*/);
+ else
+ setColorScheme(kGreenOnBlack, false /*fSaveIt*/);
+
+ /* font: */
+ com::Bstr bstrFont;
+ hrc = m_pVirtualBox ? m_pVirtualBox->GetExtraData(com::Bstr("DbgConsole/Font").raw(), bstrFont.asOutParam()) : E_FAIL;
+ if ( SUCCEEDED(hrc)
+ && bstrFont.compareUtf8("monospace", com::Bstr::CaseInsensitive) == 0)
+ setFontType(kFontType_Monospace, false /*fSaveIt*/);
+ else
+ setFontType(kFontType_Courier, false /*fSaveIt*/);
+
+ /* font size: */
+ com::Bstr bstrFontSize;
+ hrc = m_pVirtualBox ? m_pVirtualBox->GetExtraData(com::Bstr("DbgConsole/FontSize").raw(), bstrFontSize.asOutParam()) : E_FAIL;
+ if (SUCCEEDED(hrc))
+ {
+ com::Utf8Str strFontSize(bstrFontSize);
+ uint32_t uFontSizePrf = strFontSize.strip().toUInt32();
+ if ( uFontSizePrf - s_uMinFontSize < (uint32_t)RT_ELEMENTS(m_apFontSizeActions)
+ && uFontSizePrf != uDefaultFontSize)
+ setFontSize(uFontSizePrf, false /*fSaveIt*/);
+ }
+
+ NOREF(pszName);
+}
+
+
+VBoxDbgConsoleOutput::~VBoxDbgConsoleOutput()
+{
+ Assert(m_hGUIThread == RTThreadNativeSelf());
+ if (m_pVirtualBox)
+ {
+ m_pVirtualBox->Release();
+ m_pVirtualBox = NULL;
+ }
+}
+
+
+void
+VBoxDbgConsoleOutput::contextMenuEvent(QContextMenuEvent *pEvent)
+{
+ /*
+ * Create the context menu and add the menu items.
+ */
+ QMenu *pMenu = createStandardContextMenu();
+ pMenu->addSeparator();
+
+ QMenu *pColorMenu = pMenu->addMenu(tr("Co&lor Scheme"));
+ pColorMenu->addAction(m_pGreenOnBlackAction);
+ pColorMenu->addAction(m_pBlackOnWhiteAction);
+
+ QMenu *pFontMenu = pMenu->addMenu(tr("&Font Family"));
+ pFontMenu->addAction(m_pCourierFontAction);
+ pFontMenu->addAction(m_pMonospaceFontAction);
+
+ QMenu *pFontSize = pMenu->addMenu(tr("Font &Size"));
+ for (unsigned i = 0; i < RT_ELEMENTS(m_apFontSizeActions); i++)
+ pFontSize->addAction(m_apFontSizeActions[i]);
+
+ pMenu->exec(pEvent->globalPos());
+ delete pMenu;
+}
+
+
+void
+VBoxDbgConsoleOutput::setColorScheme(VBoxDbgConsoleColor enmScheme, bool fSaveIt)
+{
+ const char *pszSetting;
+ QAction *pAction;
+ switch (enmScheme)
+ {
+ case kGreenOnBlack:
+ setStyleSheet("QTextEdit { background-color: black; color: rgb(0, 224, 0) }");
+ pszSetting = "GreenOnBlack";
+ pAction = m_pGreenOnBlackAction;
+ break;
+ case kBlackOnWhite:
+ setStyleSheet("QTextEdit { background-color: white; color: black }");
+ pszSetting = "BlackOnWhite";
+ pAction = m_pBlackOnWhiteAction;
+ break;
+ default:
+ AssertFailedReturnVoid();
+ }
+
+ m_enmColorScheme = kGreenOnBlack;
+
+ /* When going through a slot, the action is typically checked already by Qt. */
+ if (!pAction->isChecked())
+ pAction->setChecked(true);
+
+ /* Make this setting persistent. */
+ if (m_pVirtualBox && fSaveIt)
+ m_pVirtualBox->SetExtraData(com::Bstr("DbgConsole/ColorScheme").raw(), com::Bstr(pszSetting).raw());
+}
+
+
+void
+VBoxDbgConsoleOutput::setFontType(VBoxDbgConsoleFontType enmFontType, bool fSaveIt)
+{
+ QFont Font = font();
+ QAction *pAction;
+ const char *pszSetting;
+ switch (enmFontType)
+ {
+ case kFontType_Courier:
+#ifdef Q_WS_MAC
+ Font = QFont("Monaco", Font.pointSize(), QFont::Normal, FALSE);
+ Font.setStyleStrategy(QFont::NoAntialias);
+#else
+ Font.setStyleHint(QFont::TypeWriter);
+ Font.setFamily("Courier [Monotype]");
+#endif
+ pszSetting = "Courier";
+ pAction = m_pCourierFontAction;
+ break;
+
+ case kFontType_Monospace:
+ Font.setStyleHint(QFont::TypeWriter);
+ Font.setStyleStrategy(QFont::PreferAntialias);
+ Font.setFamily("Monospace [Monotype]");
+ pszSetting = "Monospace";
+ pAction = m_pMonospaceFontAction;
+ break;
+
+ default:
+ AssertFailedReturnVoid();
+ }
+
+ setFont(Font);
+
+ /* When going through a slot, the action is typically checked already by Qt. */
+ if (!pAction->isChecked())
+ pAction->setChecked(true);
+
+ /* Make this setting persistent. */
+ if (m_pVirtualBox && fSaveIt)
+ m_pVirtualBox->SetExtraData(com::Bstr("DbgConsole/Font").raw(), com::Bstr(pszSetting).raw());
+}
+
+
+void
+VBoxDbgConsoleOutput::setFontSize(uint32_t uFontSize, bool fSaveIt)
+{
+ uint32_t idxAction = uFontSize - s_uMinFontSize;
+ if (idxAction < (uint32_t)RT_ELEMENTS(m_apFontSizeActions))
+ {
+ if (!m_apFontSizeActions[idxAction]->isChecked())
+ m_apFontSizeActions[idxAction]->setChecked(true);
+
+ QFont Font = font();
+ Font.setPointSize(uFontSize);
+ setFont(Font);
+
+ /* Make this setting persistent if requested. */
+ if (fSaveIt && m_pVirtualBox)
+ m_pVirtualBox->SetExtraData(com::Bstr("DbgConsole/FontSize").raw(), com::BstrFmt("%u", uFontSize).raw());
+ }
+}
+
+
+void
+VBoxDbgConsoleOutput::sltSelectColorScheme()
+{
+ QAction *pAction = qobject_cast<QAction *>(sender());
+ if (pAction)
+ setColorScheme((VBoxDbgConsoleColor)pAction->data().toInt(), true /*fSaveIt*/);
+}
+
+
+void
+VBoxDbgConsoleOutput::sltSelectFontType()
+{
+ QAction *pAction = qobject_cast<QAction *>(sender());
+ if (pAction)
+ setFontType((VBoxDbgConsoleFontType)pAction->data().toInt(), true /*fSaveIt*/);
+}
+
+
+void
+VBoxDbgConsoleOutput::sltSelectFontSize()
+{
+ QAction *pAction = qobject_cast<QAction *>(sender());
+ if (pAction)
+ setFontSize(pAction->data().toUInt(), true /*fSaveIt*/);
+}
+
+
+void
+VBoxDbgConsoleOutput::appendText(const QString &rStr, bool fClearSelection)
+{
+ Assert(m_hGUIThread == RTThreadNativeSelf());
+
+ if (rStr.isEmpty() || rStr.isNull() || !rStr.length())
+ return;
+
+ /*
+ * Insert all in one go and make sure it's visible.
+ *
+ * We need to move the cursor and unselect any selected text before
+ * inserting anything, otherwise, text will disappear.
+ */
+ QTextCursor Cursor = textCursor();
+ if (!fClearSelection && Cursor.hasSelection())
+ {
+ QTextCursor SavedCursor = Cursor;
+ Cursor.clearSelection();
+ Cursor.movePosition(QTextCursor::End);
+
+ Cursor.insertText(rStr);
+
+ setTextCursor(SavedCursor);
+ }
+ else
+ {
+ if (Cursor.hasSelection())
+ Cursor.clearSelection();
+ if (!Cursor.atEnd())
+ Cursor.movePosition(QTextCursor::End);
+
+ Cursor.insertText(rStr);
+
+ setTextCursor(Cursor);
+ ensureCursorVisible();
+ }
+}
+
+
+
+
+/*
+ *
+ * V B o x D b g C o n s o l e I n p u t
+ * V B o x D b g C o n s o l e I n p u t
+ * V B o x D b g C o n s o l e I n p u t
+ *
+ *
+ */
+
+
+VBoxDbgConsoleInput::VBoxDbgConsoleInput(QWidget *pParent/* = NULL*/, const char *pszName/* = NULL*/)
+ : QComboBox(pParent), m_hGUIThread(RTThreadNativeSelf())
+{
+ addItem(""); /* invariant: empty command line is the last item */
+
+ setEditable(true);
+ setInsertPolicy(NoInsert);
+ setCompleter(0);
+ setMaxCount(50);
+ const QLineEdit *pEdit = lineEdit();
+ if (pEdit)
+ connect(pEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+
+ NOREF(pszName);
+}
+
+
+VBoxDbgConsoleInput::~VBoxDbgConsoleInput()
+{
+ Assert(m_hGUIThread == RTThreadNativeSelf());
+}
+
+
+void
+VBoxDbgConsoleInput::setLineEdit(QLineEdit *pEdit)
+{
+ Assert(m_hGUIThread == RTThreadNativeSelf());
+ QComboBox::setLineEdit(pEdit);
+ if (lineEdit() == pEdit && pEdit)
+ connect(pEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
+}
+
+
+void
+VBoxDbgConsoleInput::returnPressed()
+{
+ Assert(m_hGUIThread == RTThreadNativeSelf());
+
+ QString strCommand = currentText();
+ /** @todo trim whitespace? */
+ if (strCommand.isEmpty())
+ return;
+
+ /* deal with the current command. */
+ emit commandSubmitted(strCommand);
+
+
+ /*
+ * Add current command to history.
+ */
+ bool fNeedsAppending = true;
+
+ /* invariant: empty line at the end */
+ int iLastItem = count() - 1;
+ Assert(itemText(iLastItem).isEmpty());
+
+ /* have previous command? check duplicate. */
+ if (iLastItem > 0)
+ {
+ const QString strPrevCommand(itemText(iLastItem - 1));
+ if (strCommand == strPrevCommand)
+ fNeedsAppending = false;
+ }
+
+ if (fNeedsAppending)
+ {
+ /* history full? drop the oldest command. */
+ if (count() == maxCount())
+ {
+ removeItem(0);
+ --iLastItem;
+ }
+
+ /* insert before the empty line. */
+ insertItem(iLastItem, strCommand);
+ }
+
+ /* invariant: empty line at the end */
+ int iNewLastItem = count() - 1;
+ Assert(itemText(iNewLastItem).isEmpty());
+
+ /* select empty line to present "new" command line to the user */
+ setCurrentIndex(iNewLastItem);
+}
+
+
+
+
+
+
+/*
+ *
+ * V B o x D b g C o n s o l e
+ * V B o x D b g C o n s o l e
+ * V B o x D b g C o n s o l e
+ *
+ *
+ */
+
+
+VBoxDbgConsole::VBoxDbgConsole(VBoxDbgGui *a_pDbgGui, QWidget *a_pParent/* = NULL*/, IVirtualBox *a_pVirtualBox/* = NULL */)
+ : VBoxDbgBaseWindow(a_pDbgGui, a_pParent, "Console"), m_pOutput(NULL), m_pInput(NULL), m_fInputRestoreFocus(false),
+ m_pszInputBuf(NULL), m_cbInputBuf(0), m_cbInputBufAlloc(0),
+ m_pszOutputBuf(NULL), m_cbOutputBuf(0), m_cbOutputBufAlloc(0),
+ m_pTimer(NULL), m_fUpdatePending(false), m_Thread(NIL_RTTHREAD), m_EventSem(NIL_RTSEMEVENT),
+ m_fTerminate(false), m_fThreadTerminated(false)
+{
+ /* Delete dialog on close: */
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ /*
+ * Create the output text box.
+ */
+ m_pOutput = new VBoxDbgConsoleOutput(this, a_pVirtualBox);
+
+ /* try figure a suitable size */
+ QLabel *pLabel = new QLabel( "11111111111111111111111111111111111111111111111111111111111111111111111111111112222222222", this);
+ pLabel->setFont(m_pOutput->font());
+ QSize Size = pLabel->sizeHint();
+ delete pLabel;
+ Size.setWidth((int)(Size.width() * 1.10));
+ Size.setHeight(Size.width() / 2);
+ resize(Size);
+
+ /*
+ * Create the input combo box (with a label).
+ */
+ QHBoxLayout *pLayout = new QHBoxLayout();
+ //pLayout->setSizeConstraint(QLayout::SetMaximumSize);
+
+ pLabel = new QLabel(" Command ");
+ pLayout->addWidget(pLabel);
+ pLabel->setMaximumSize(pLabel->sizeHint());
+ pLabel->setAlignment(Qt::AlignCenter);
+
+ m_pInput = new VBoxDbgConsoleInput(NULL);
+ pLayout->addWidget(m_pInput);
+ m_pInput->setDuplicatesEnabled(false);
+ connect(m_pInput, SIGNAL(commandSubmitted(const QString &)), this, SLOT(commandSubmitted(const QString &)));
+
+# if 0//def Q_WS_MAC
+ pLabel = new QLabel(" ");
+ pLayout->addWidget(pLabel);
+ pLabel->setMaximumSize(20, m_pInput->sizeHint().height() + 6);
+ pLabel->setMinimumSize(20, m_pInput->sizeHint().height() + 6);
+# endif
+
+ QWidget *pHBox = new QWidget(this);
+ pHBox->setLayout(pLayout);
+
+ m_pInput->setEnabled(false); /* (we'll get a ready notification) */
+
+
+ /*
+ * Vertical layout box on the whole widget.
+ */
+ QVBoxLayout *pVLayout = new QVBoxLayout();
+ pVLayout->setContentsMargins(0, 0, 0, 0);
+ pVLayout->setSpacing(5);
+ pVLayout->addWidget(m_pOutput);
+ pVLayout->addWidget(pHBox);
+ setLayout(pVLayout);
+
+ /*
+ * The tab order is from input to output, not the other way around as it is by default.
+ */
+ setTabOrder(m_pInput, m_pOutput);
+ m_fInputRestoreFocus = true; /* hack */
+
+ /*
+ * Setup the timer.
+ */
+ m_pTimer = new QTimer(this);
+ connect(m_pTimer, SIGNAL(timeout()), SLOT(updateOutput()));
+
+ /*
+ * Init the backend structure.
+ */
+ m_Back.Core.pfnInput = backInput;
+ m_Back.Core.pfnRead = backRead;
+ m_Back.Core.pfnWrite = backWrite;
+ m_Back.Core.pfnSetReady = backSetReady;
+ m_Back.pSelf = this;
+
+ /*
+ * Create the critical section, the event semaphore and the debug console thread.
+ */
+ int rc = RTCritSectInit(&m_Lock);
+ AssertRC(rc);
+
+ rc = RTSemEventCreate(&m_EventSem);
+ AssertRC(rc);
+
+ rc = RTThreadCreate(&m_Thread, backThread, this, 0, RTTHREADTYPE_DEBUGGER, RTTHREADFLAGS_WAITABLE, "VBoxDbgC");
+ AssertRC(rc);
+ if (RT_FAILURE(rc))
+ m_Thread = NIL_RTTHREAD;
+
+ /*
+ * Shortcuts.
+ */
+ m_pFocusToInput = new QAction("", this);
+ m_pFocusToInput->setShortcut(QKeySequence("Ctrl+L"));
+ addAction(m_pFocusToInput);
+ connect(m_pFocusToInput, SIGNAL(triggered(bool)), this, SLOT(actFocusToInput()));
+
+ m_pFocusToOutput = new QAction("", this);
+ m_pFocusToOutput->setShortcut(QKeySequence("Ctrl+O"));
+ addAction(m_pFocusToOutput);
+ connect(m_pFocusToOutput, SIGNAL(triggered(bool)), this, SLOT(actFocusToOutput()));
+
+ addAction(m_pOutput->m_pBlackOnWhiteAction);
+ addAction(m_pOutput->m_pGreenOnBlackAction);
+ addAction(m_pOutput->m_pCourierFontAction);
+ addAction(m_pOutput->m_pMonospaceFontAction);
+}
+
+
+VBoxDbgConsole::~VBoxDbgConsole()
+{
+ Assert(isGUIThread());
+
+ /*
+ * Wait for the thread.
+ */
+ ASMAtomicWriteBool(&m_fTerminate, true);
+ RTSemEventSignal(m_EventSem);
+ if (m_Thread != NIL_RTTHREAD)
+ {
+ int rc = RTThreadWait(m_Thread, 15000, NULL);
+ AssertRC(rc);
+ m_Thread = NIL_RTTHREAD;
+ }
+
+ /*
+ * Free resources.
+ */
+ delete m_pTimer;
+ m_pTimer = NULL;
+ RTCritSectDelete(&m_Lock);
+ RTSemEventDestroy(m_EventSem);
+ m_EventSem = 0;
+ m_pOutput = NULL;
+ m_pInput = NULL;
+ if (m_pszInputBuf)
+ {
+ RTMemFree(m_pszInputBuf);
+ m_pszInputBuf = NULL;
+ }
+ m_cbInputBuf = 0;
+ m_cbInputBufAlloc = 0;
+
+ delete m_pFocusToInput;
+ m_pFocusToInput = NULL;
+ delete m_pFocusToOutput;
+ m_pFocusToOutput = NULL;
+
+ if (m_pszOutputBuf)
+ {
+ RTMemFree(m_pszOutputBuf);
+ m_pszOutputBuf = NULL;
+ }
+}
+
+
+void
+VBoxDbgConsole::commandSubmitted(const QString &rCommand)
+{
+ Assert(isGUIThread());
+
+ lock();
+ RTSemEventSignal(m_EventSem);
+
+ QByteArray Utf8Array = rCommand.toUtf8();
+ const char *psz = Utf8Array.constData();
+ size_t cb = strlen(psz);
+
+ /*
+ * Make sure we've got space for the input.
+ */
+ if (cb + m_cbInputBuf >= m_cbInputBufAlloc)
+ {
+ size_t cbNew = RT_ALIGN_Z(cb + m_cbInputBufAlloc + 1, 128);
+ void *pv = RTMemRealloc(m_pszInputBuf, cbNew);
+ if (!pv)
+ {
+ unlock();
+ return;
+ }
+ m_pszInputBuf = (char *)pv;
+ m_cbInputBufAlloc = cbNew;
+ }
+
+ /*
+ * Add the input and output it.
+ */
+ memcpy(m_pszInputBuf + m_cbInputBuf, psz, cb);
+ m_cbInputBuf += cb;
+ m_pszInputBuf[m_cbInputBuf++] = '\n';
+
+ m_pOutput->appendText(rCommand + "\n", true /*fClearSelection*/);
+ m_pOutput->ensureCursorVisible();
+
+ m_fInputRestoreFocus = m_pInput->hasFocus(); /* dirty focus hack */
+ m_pInput->setEnabled(false);
+
+ Log(("VBoxDbgConsole::commandSubmitted: %s (input-enabled=%RTbool)\n", psz, m_pInput->isEnabled()));
+ unlock();
+}
+
+
+void
+VBoxDbgConsole::updateOutput()
+{
+ Assert(isGUIThread());
+
+ lock();
+ m_fUpdatePending = false;
+ if (m_cbOutputBuf)
+ {
+ m_pOutput->appendText(QString::fromUtf8((const char *)m_pszOutputBuf, (int)m_cbOutputBuf), false /*fClearSelection*/);
+ m_cbOutputBuf = 0;
+ }
+ unlock();
+}
+
+
+/**
+ * Lock the object.
+ */
+void
+VBoxDbgConsole::lock()
+{
+ RTCritSectEnter(&m_Lock);
+}
+
+
+/**
+ * Unlocks the object.
+ */
+void
+VBoxDbgConsole::unlock()
+{
+ RTCritSectLeave(&m_Lock);
+}
+
+
+
+/**
+ * Checks if there is input.
+ *
+ * @returns true if there is input ready.
+ * @returns false if there not input ready.
+ * @param pBack Pointer to VBoxDbgConsole::m_Back.
+ * @param cMillies Number of milliseconds to wait on input data.
+ */
+/*static*/ DECLCALLBACK(bool)
+VBoxDbgConsole::backInput(PCDBGCIO pBack, uint32_t cMillies)
+{
+ VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCIO(pBack);
+ pThis->lock();
+
+ bool fRc = true;
+ if (!pThis->m_cbInputBuf)
+ {
+ /*
+ * Wait outside the lock for the requested time, then check again.
+ */
+ pThis->unlock();
+ RTSemEventWait(pThis->m_EventSem, cMillies);
+ pThis->lock();
+ fRc = pThis->m_cbInputBuf
+ || ASMAtomicUoReadBool(&pThis->m_fTerminate);
+ }
+
+ pThis->unlock();
+ return fRc;
+}
+
+
+/**
+ * Read input.
+ *
+ * @returns VBox status code.
+ * @param pBack Pointer to VBoxDbgConsole::m_Back.
+ * @param pvBuf Where to put the bytes we read.
+ * @param cbBuf Maximum nymber of bytes to read.
+ * @param pcbRead Where to store the number of bytes actually read.
+ * If NULL the entire buffer must be filled for a
+ * successful return.
+ */
+/*static*/ DECLCALLBACK(int)
+VBoxDbgConsole::backRead(PCDBGCIO pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCIO(pBack);
+ Assert(pcbRead); /** @todo implement this bit */
+ if (pcbRead)
+ *pcbRead = 0;
+
+ pThis->lock();
+ int rc = VINF_SUCCESS;
+ if (!ASMAtomicUoReadBool(&pThis->m_fTerminate))
+ {
+ if (pThis->m_cbInputBuf)
+ {
+ const char *psz = pThis->m_pszInputBuf;
+ size_t cbRead = RT_MIN(pThis->m_cbInputBuf, cbBuf);
+ memcpy(pvBuf, psz, cbRead);
+ psz += cbRead;
+ pThis->m_cbInputBuf -= cbRead;
+ if (*psz)
+ memmove(pThis->m_pszInputBuf, psz, pThis->m_cbInputBuf);
+ pThis->m_pszInputBuf[pThis->m_cbInputBuf] = '\0';
+ *pcbRead = cbRead;
+ }
+ }
+ else
+ rc = VERR_GENERAL_FAILURE;
+ pThis->unlock();
+ return rc;
+}
+
+
+/**
+ * Write (output).
+ *
+ * @returns VBox status code.
+ * @param pBack Pointer to VBoxDbgConsole::m_Back.
+ * @param pvBuf What to write.
+ * @param cbBuf Number of bytes to write.
+ * @param pcbWritten Where to store the number of bytes actually written.
+ * If NULL the entire buffer must be successfully written.
+ */
+/*static*/ DECLCALLBACK(int)
+VBoxDbgConsole::backWrite(PCDBGCIO pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCIO(pBack);
+ int rc = VINF_SUCCESS;
+
+ pThis->lock();
+ if (cbBuf + pThis->m_cbOutputBuf >= pThis->m_cbOutputBufAlloc)
+ {
+ size_t cbNew = RT_ALIGN_Z(cbBuf + pThis->m_cbOutputBufAlloc + 1, 1024);
+ void *pv = RTMemRealloc(pThis->m_pszOutputBuf, cbNew);
+ if (!pv)
+ {
+ pThis->unlock();
+ if (pcbWritten)
+ *pcbWritten = 0;
+ return VERR_NO_MEMORY;
+ }
+ pThis->m_pszOutputBuf = (char *)pv;
+ pThis->m_cbOutputBufAlloc = cbNew;
+ }
+
+ /*
+ * Add the output.
+ */
+ memcpy(pThis->m_pszOutputBuf + pThis->m_cbOutputBuf, pvBuf, cbBuf);
+ pThis->m_cbOutputBuf += cbBuf;
+ pThis->m_pszOutputBuf[pThis->m_cbOutputBuf] = '\0';
+ if (pcbWritten)
+ *pcbWritten = cbBuf;
+
+ if (ASMAtomicUoReadBool(&pThis->m_fTerminate))
+ rc = VERR_GENERAL_FAILURE;
+
+ /*
+ * Tell the GUI thread to draw this text.
+ * We cannot do it from here without frequent crashes.
+ */
+ if (!pThis->m_fUpdatePending)
+ QApplication::postEvent(pThis, new VBoxDbgConsoleEvent(VBoxDbgConsoleEvent::kUpdate));
+
+ pThis->unlock();
+
+ return rc;
+}
+
+
+/*static*/ DECLCALLBACK(void)
+VBoxDbgConsole::backSetReady(PCDBGCIO pBack, bool fReady)
+{
+ VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCIO(pBack);
+ if (fReady)
+ QApplication::postEvent(pThis, new VBoxDbgConsoleEvent(VBoxDbgConsoleEvent::kInputEnable));
+}
+
+
+/*static*/ DECLCALLBACK(int)
+VBoxDbgConsole::backThread(RTTHREAD Thread, void *pvUser)
+{
+ VBoxDbgConsole *pThis = (VBoxDbgConsole *)pvUser;
+ LogFlow(("backThread: Thread=%p pvUser=%p\n", (void *)Thread, pvUser));
+
+ NOREF(Thread);
+
+ /*
+ * Create and execute the console.
+ */
+ int rc = pThis->dbgcCreate(&pThis->m_Back.Core, 0);
+
+ ASMAtomicUoWriteBool(&pThis->m_fThreadTerminated, true);
+ if (!ASMAtomicUoReadBool(&pThis->m_fTerminate))
+ QApplication::postEvent(pThis, new VBoxDbgConsoleEvent(rc == VINF_SUCCESS
+ ? VBoxDbgConsoleEvent::kTerminatedUser
+ : VBoxDbgConsoleEvent::kTerminatedOther));
+ LogFlow(("backThread: returns %Rrc (m_fTerminate=%RTbool)\n", rc, ASMAtomicUoReadBool(&pThis->m_fTerminate)));
+ return rc;
+}
+
+
+bool
+VBoxDbgConsole::event(QEvent *pGenEvent)
+{
+ Assert(isGUIThread());
+ if (pGenEvent->type() == (QEvent::Type)VBoxDbgConsoleEvent::kEventNumber)
+ {
+ VBoxDbgConsoleEvent *pEvent = (VBoxDbgConsoleEvent *)pGenEvent;
+
+ switch (pEvent->command())
+ {
+ /* make update pending. */
+ case VBoxDbgConsoleEvent::kUpdate:
+ lock();
+ if (!m_fUpdatePending)
+ {
+ m_fUpdatePending = true;
+ m_pTimer->setSingleShot(true);
+ m_pTimer->start(10);
+ }
+ unlock();
+ break;
+
+ /* Re-enable the input field and restore focus. */
+ case VBoxDbgConsoleEvent::kInputEnable:
+ Log(("VBoxDbgConsole: kInputEnable (input-enabled=%RTbool)\n", m_pInput->isEnabled()));
+ m_pInput->setEnabled(true);
+ if ( m_fInputRestoreFocus
+ && !m_pInput->hasFocus())
+ m_pInput->setFocus(); /* this is a hack. */
+ m_fInputRestoreFocus = false;
+ break;
+
+ /* The thread terminated by user command (exit, quit, bye). */
+ case VBoxDbgConsoleEvent::kTerminatedUser:
+ Log(("VBoxDbgConsole: kTerminatedUser (input-enabled=%RTbool)\n", m_pInput->isEnabled()));
+ m_pInput->setEnabled(false);
+ close();
+ break;
+
+ /* The thread terminated for some unknown reason., disable input */
+ case VBoxDbgConsoleEvent::kTerminatedOther:
+ Log(("VBoxDbgConsole: kTerminatedOther (input-enabled=%RTbool)\n", m_pInput->isEnabled()));
+ m_pInput->setEnabled(false);
+ break;
+
+ /* paranoia */
+ default:
+ AssertMsgFailed(("command=%d\n", pEvent->command()));
+ break;
+ }
+ return true;
+ }
+
+ return VBoxDbgBaseWindow::event(pGenEvent);
+}
+
+
+void
+VBoxDbgConsole::keyReleaseEvent(QKeyEvent *pEvent)
+{
+ //RTAssertMsg2("VBoxDbgConsole::keyReleaseEvent: %d (%#x); mod=%#x\n", pEvent->key(), pEvent->key(), pEvent->modifiers());
+ switch (pEvent->key())
+ {
+ case Qt::Key_F5:
+ if (pEvent->modifiers() == 0)
+ commandSubmitted("g");
+ break;
+
+ case Qt::Key_F8:
+ if (pEvent->modifiers() == 0)
+ commandSubmitted("t");
+ break;
+
+ case Qt::Key_F10:
+ if (pEvent->modifiers() == 0)
+ commandSubmitted("p");
+ break;
+
+ case Qt::Key_F11:
+ if (pEvent->modifiers() == 0)
+ commandSubmitted("t");
+ else if (pEvent->modifiers() == Qt::ShiftModifier)
+ commandSubmitted("gu");
+ break;
+
+ case Qt::Key_Cancel: /* == break */
+ if (pEvent->modifiers() == Qt::ControlModifier)
+ commandSubmitted("stop");
+ break;
+ case Qt::Key_Delete:
+ if (pEvent->modifiers() == Qt::AltModifier)
+ commandSubmitted("stop");
+ break;
+ }
+}
+
+
+void
+VBoxDbgConsole::closeEvent(QCloseEvent *a_pCloseEvt)
+{
+ if (m_fThreadTerminated)
+ a_pCloseEvt->accept();
+}
+
+
+void
+VBoxDbgConsole::actFocusToInput()
+{
+ if (!m_pInput->hasFocus())
+ m_pInput->setFocus(Qt::ShortcutFocusReason);
+}
+
+
+void
+VBoxDbgConsole::actFocusToOutput()
+{
+ if (!m_pOutput->hasFocus())
+ m_pOutput->setFocus(Qt::ShortcutFocusReason);
+}
+
diff --git a/src/VBox/Debugger/VBoxDbgConsole.h b/src/VBox/Debugger/VBoxDbgConsole.h
new file mode 100644
index 00000000..8e04fcd0
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgConsole.h
@@ -0,0 +1,447 @@
+/* $Id: VBoxDbgConsole.h $ */
+/** @file
+ * VBox Debugger GUI - Console.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_VBoxDbgConsole_h
+#define DEBUGGER_INCLUDED_SRC_VBoxDbgConsole_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxDbgBase.h"
+
+#include <QTextEdit>
+#include <QComboBox>
+#include <QTimer>
+#include <QEvent>
+#include <QActionGroup>
+
+#include <iprt/critsect.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+// VirtualBox COM interfaces declarations (generated header)
+#ifdef VBOX_WITH_XPCOM
+# include <VirtualBox_XPCOM.h>
+#else
+# include <iprt/win/windows.h> /* Include via cleanup wrapper before VirtualBox.h includes it via rpc.h. */
+# include <VirtualBox.h>
+#endif
+
+
+class VBoxDbgConsoleOutput : public QTextEdit
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param pParent Parent Widget.
+ * @param pVirtualBox VirtualBox object for storing extra data.
+ * @param pszName Widget name.
+ */
+ VBoxDbgConsoleOutput(QWidget *pParent = NULL, IVirtualBox *pVirtualBox = NULL, const char *pszName = NULL);
+
+ /**
+ * Destructor
+ */
+ virtual ~VBoxDbgConsoleOutput();
+
+ /**
+ * Appends text.
+ * This differs from QTextEdit::append() in that it won't start on a new paragraph
+ * unless the previous char was a newline ('\n').
+ *
+ * @param rStr The text string to append.
+ * @param fClearSelection Whether to clear selected text before appending.
+ * If @c false the selection and window position
+ * are preserved.
+ */
+ virtual void appendText(const QString &rStr, bool fClearSelection);
+
+ /** The action to switch to black-on-white color scheme. */
+ QAction *m_pBlackOnWhiteAction;
+ /** The action to switch to green-on-black color scheme. */
+ QAction *m_pGreenOnBlackAction;
+
+ /** The action to switch to Courier font. */
+ QAction *m_pCourierFontAction;
+ /** The action to switch to Monospace font. */
+ QAction *m_pMonospaceFontAction;
+
+protected:
+ typedef enum { kGreenOnBlack, kBlackOnWhite } VBoxDbgConsoleColor;
+ typedef enum { kFontType_Monospace, kFontType_Courier } VBoxDbgConsoleFontType;
+
+ /**
+ * Context menu event.
+ * This adds custom menu items for the output view.
+ *
+ * @param pEvent Pointer to the event.
+ */
+ virtual void contextMenuEvent(QContextMenuEvent *pEvent);
+
+ /**
+ * Sets the color scheme.
+ *
+ * @param enmScheme The new color scheme.
+ * @param fSaveIt Whether to save it.
+ */
+ void setColorScheme(VBoxDbgConsoleColor enmScheme, bool fSaveIt);
+
+ /**
+ * Sets the font type / family.
+ *
+ * @param enmFontType The font type.
+ * @param fSaveIt Whether to save it.
+ */
+ void setFontType(VBoxDbgConsoleFontType enmFontType, bool fSaveIt);
+
+ /**
+ * Sets the font size.
+ *
+ * @param uFontSize The new font size in points.
+ * @param fSaveIt Whether to save it.
+ */
+ void setFontSize(uint32_t uFontSize, bool fSaveIt);
+
+
+ /** The current line (paragraph) number. */
+ unsigned m_uCurLine;
+ /** The position in the current line. */
+ unsigned m_uCurPos;
+ /** The handle to the GUI thread. */
+ RTNATIVETHREAD m_hGUIThread;
+ /** The current color scheme (foreground on background). */
+ VBoxDbgConsoleColor m_enmColorScheme;
+ /** The IVirtualBox object */
+ IVirtualBox *m_pVirtualBox;
+
+ /** Array of font size actions 6..22pt. */
+ QAction *m_apFontSizeActions[22 - 6 + 1];
+ /** Action group for m_apFontSizeActions. */
+ QActionGroup *m_pActionFontSizeGroup;
+
+ /** The minimum font size. */
+ static const uint32_t s_uMinFontSize;
+
+private slots:
+ /**
+ * Selects color scheme
+ */
+ void sltSelectColorScheme();
+
+ /**
+ * Selects font type.
+ */
+ void sltSelectFontType();
+
+ /**
+ * Selects font size.
+ */
+ void sltSelectFontSize();
+};
+
+
+/**
+ * The Debugger Console Input widget.
+ *
+ * This is a combobox which only responds to \<return\>.
+ */
+class VBoxDbgConsoleInput : public QComboBox
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param pParent Parent Widget.
+ * @param pszName Widget name.
+ */
+ VBoxDbgConsoleInput(QWidget *pParent = NULL, const char *pszName = NULL);
+
+ /**
+ * Destructor
+ */
+ virtual ~VBoxDbgConsoleInput();
+
+ /**
+ * We overload this method to get signaled upon returnPressed().
+ *
+ * See QComboBox::setLineEdit for full description.
+ * @param pEdit The new line edit widget.
+ * @remark This won't be called during the constructor.
+ */
+ virtual void setLineEdit(QLineEdit *pEdit);
+
+signals:
+ /**
+ * New command submitted.
+ */
+ void commandSubmitted(const QString &rCommand);
+
+private slots:
+ /**
+ * Returned was pressed.
+ *
+ * Will emit commandSubmitted().
+ */
+ void returnPressed();
+
+protected:
+ /** The handle to the GUI thread. */
+ RTNATIVETHREAD m_hGUIThread;
+};
+
+
+/**
+ * The Debugger Console.
+ */
+class VBoxDbgConsole : public VBoxDbgBaseWindow
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param a_pDbgGui Pointer to the debugger gui object.
+ * @param a_pParent Parent Widget.
+ * @param a_pVirtualBox VirtualBox object for storing extra data.
+ */
+ VBoxDbgConsole(VBoxDbgGui *a_pDbgGui, QWidget *a_pParent = NULL, IVirtualBox *a_pVirtualBox = NULL);
+
+ /**
+ * Destructor
+ */
+ virtual ~VBoxDbgConsole();
+
+protected slots:
+ /**
+ * Handler called when a command is submitted.
+ * (Enter or return pressed in the combo box.)
+ *
+ * @param rCommand The submitted command.
+ */
+ void commandSubmitted(const QString &rCommand);
+
+ /**
+ * Updates the output with what's currently in the output buffer.
+ * This is called by a timer or a User event posted by the debugger thread.
+ */
+ void updateOutput();
+
+ /**
+ * Changes the focus to the input field.
+ */
+ void actFocusToInput();
+
+ /**
+ * Changes the focus to the output viewer widget.
+ */
+ void actFocusToOutput();
+
+protected:
+ /**
+ * Override the closeEvent so we can choose delete the window when
+ * it is closed.
+ *
+ * @param a_pCloseEvt The close event.
+ */
+ virtual void closeEvent(QCloseEvent *a_pCloseEvt);
+
+ /**
+ * Lock the object.
+ */
+ void lock();
+
+ /**
+ * Unlocks the object.
+ */
+ void unlock();
+
+protected:
+ /** @name Debug Console Backend.
+ * @{
+ */
+
+
+ /**
+ * Checks if there is input.
+ *
+ * @returns true if there is input ready.
+ * @returns false if there not input ready.
+ * @param pBack Pointer to VBoxDbgConsole::m_Back.
+ * @param cMillies Number of milliseconds to wait on input data.
+ */
+ static DECLCALLBACK(bool) backInput(PCDBGCIO pIo, uint32_t cMillies);
+
+ /**
+ * Read input.
+ *
+ * @returns VBox status code.
+ * @param pBack Pointer to VBoxDbgConsole::m_Back.
+ * @param pvBuf Where to put the bytes we read.
+ * @param cbBuf Maximum nymber of bytes to read.
+ * @param pcbRead Where to store the number of bytes actually read.
+ * If NULL the entire buffer must be filled for a
+ * successful return.
+ */
+ static DECLCALLBACK(int) backRead(PCDBGCIO pIo, void *pvBuf, size_t cbBuf, size_t *pcbRead);
+
+ /**
+ * Write (output).
+ *
+ * @returns VBox status code.
+ * @param pBack Pointer to VBoxDbgConsole::m_Back.
+ * @param pvBuf What to write.
+ * @param cbBuf Number of bytes to write.
+ * @param pcbWritten Where to store the number of bytes actually written.
+ * If NULL the entire buffer must be successfully written.
+ */
+ static DECLCALLBACK(int) backWrite(PCDBGCIO pIo, const void *pvBuf, size_t cbBuf, size_t *pcbWritten);
+
+ /**
+ * @copydoc DBGCIO::pfnSetReady
+ */
+ static DECLCALLBACK(void) backSetReady(PCDBGCIO pIo, bool fReady);
+
+ /**
+ * The Debugger Console Thread
+ *
+ * @returns VBox status code (ignored).
+ * @param Thread The thread handle.
+ * @param pvUser Pointer to the VBoxDbgConsole object.s
+ */
+ static DECLCALLBACK(int) backThread(RTTHREAD Thread, void *pvUser);
+
+ /** @} */
+
+protected:
+ /**
+ * Processes GUI command posted by the console thread.
+ *
+ * Qt3 isn't thread safe on any platform, meaning there is no locking, so, as
+ * a result we have to be very careful. All operations on objects which we share
+ * with the main thread has to be posted to it so it can perform it.
+ */
+ bool event(QEvent *pEvent);
+
+ /**
+ * For implementing keyboard shortcuts.
+ *
+ * @param pEvent The key event.
+ */
+ void keyReleaseEvent(QKeyEvent *pEvent);
+
+protected:
+ /** The output widget. */
+ VBoxDbgConsoleOutput *m_pOutput;
+ /** The input widget. */
+ VBoxDbgConsoleInput *m_pInput;
+ /** A hack to restore focus to the combobox after a command execution. */
+ bool m_fInputRestoreFocus;
+ /** The input buffer. */
+ char *m_pszInputBuf;
+ /** The amount of input in the buffer. */
+ size_t m_cbInputBuf;
+ /** The allocated size of the buffer. */
+ size_t m_cbInputBufAlloc;
+
+ /** The output buffer. */
+ char *m_pszOutputBuf;
+ /** The amount of output in the buffer. */
+ size_t m_cbOutputBuf;
+ /** The allocated size of the buffer. */
+ size_t m_cbOutputBufAlloc;
+ /** The timer object used to process output in a delayed fashion. */
+ QTimer *m_pTimer;
+ /** Set when an output update is pending. */
+ bool volatile m_fUpdatePending;
+
+ /** The debugger console thread. */
+ RTTHREAD m_Thread;
+ /** The event semaphore used to signal the debug console thread about input. */
+ RTSEMEVENT m_EventSem;
+ /** The critical section used to lock the object. */
+ RTCRITSECT m_Lock;
+ /** When set the thread will cause the debug console thread to terminate. */
+ bool volatile m_fTerminate;
+ /** Has the thread terminated?
+ * Used to do the right thing in closeEvent; the console is dead if the
+ * thread has terminated. */
+ bool volatile m_fThreadTerminated;
+
+ /** The debug console backend structure.
+ * Use VBOXDBGCONSOLE_FROM_DBGCIO to convert the DBGCIO pointer to a object pointer. */
+ struct VBoxDbgConsoleBack
+ {
+ DBGCIO Core;
+ VBoxDbgConsole *pSelf;
+ } m_Back;
+
+ /**
+ * Converts a pointer to VBoxDbgConsole::m_Back to VBoxDbgConsole pointer.
+ * @todo find a better way because offsetof is undefined on objects and g++ gets very noisy because of that.
+ */
+# define VBOXDBGCONSOLE_FROM_DBGCIO(pIo) ( ((struct VBoxDbgConsoleBack *)(pBack))->pSelf )
+
+ /** Change focus to the input field. */
+ QAction *m_pFocusToInput;
+ /** Change focus to the output viewer widget. */
+ QAction *m_pFocusToOutput;
+};
+
+
+/**
+ * Simple event class for push certain operations over
+ * onto the GUI thread.
+ */
+class VBoxDbgConsoleEvent : public QEvent
+{
+public:
+ typedef enum { kUpdate, kInputEnable, kTerminatedUser, kTerminatedOther } VBoxDbgConsoleEventType;
+ enum { kEventNumber = QEvent::User + 42 };
+
+ VBoxDbgConsoleEvent(VBoxDbgConsoleEventType enmCommand)
+ : QEvent((QEvent::Type)kEventNumber), m_enmCommand(enmCommand)
+ {
+ }
+
+ VBoxDbgConsoleEventType command() const
+ {
+ return m_enmCommand;
+ }
+
+private:
+ VBoxDbgConsoleEventType m_enmCommand;
+};
+
+
+#endif /* !DEBUGGER_INCLUDED_SRC_VBoxDbgConsole_h */
+
diff --git a/src/VBox/Debugger/VBoxDbgDisas.h b/src/VBox/Debugger/VBoxDbgDisas.h
new file mode 100644
index 00000000..35f0aec8
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgDisas.h
@@ -0,0 +1,53 @@
+/* $Id: VBoxDbgDisas.h $ */
+/** @file
+ * VBox Debugger GUI - Disassembly View.
+ */
+
+/*
+ * Copyright (C) 2008-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_VBoxDbgDisas_h
+#define DEBUGGER_INCLUDED_SRC_VBoxDbgDisas_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/**
+ * Feature list:
+ * - Combobox (or some entry field with history similar to the command) for
+ * entering the address. Should support registers and other symbols, and
+ * possibly also grok various operators like the debugger command line.
+ * => Needs to make the DBGC evaluator available somehow.
+ * - Refresh manual/interval (for EIP or other non-fixed address expression).
+ * - Scrollable - down is not an issue, up is a bit more difficult.
+ * - Hide/Unhide PATM patches (jumps/int3/whatever) button in the guest disas.
+ * - Drop down for selecting mixed original guest disas and PATM/REM disas
+ * (Guest Only, Guest+PATM, Guest+REM).
+ *
+ */
+class VBoxDbgDisas
+{
+
+};
+
+#endif /* !DEBUGGER_INCLUDED_SRC_VBoxDbgDisas_h */
+
diff --git a/src/VBox/Debugger/VBoxDbgGui.cpp b/src/VBox/Debugger/VBoxDbgGui.cpp
new file mode 100644
index 00000000..3134aad3
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgGui.cpp
@@ -0,0 +1,313 @@
+/* $Id: VBoxDbgGui.cpp $ */
+/** @file
+ * VBox Debugger GUI - The Manager.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGG
+#define VBOX_COM_NO_ATL
+#include <VBox/com/defs.h>
+#include <iprt/errcore.h>
+
+#include "VBoxDbgGui.h"
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+# include <QScreen>
+#else
+# include <QDesktopWidget>
+#endif
+#include <QApplication>
+
+
+
+VBoxDbgGui::VBoxDbgGui() :
+ m_pDbgStats(NULL), m_pDbgConsole(NULL), m_pSession(NULL), m_pConsole(NULL),
+ m_pMachineDebugger(NULL), m_pMachine(NULL), m_pUVM(NULL), m_pVMM(NULL),
+ m_pParent(NULL), m_pMenu(NULL),
+ m_x(0), m_y(0), m_cx(0), m_cy(0), m_xDesktop(0), m_yDesktop(0), m_cxDesktop(0), m_cyDesktop(0)
+{
+
+}
+
+
+int VBoxDbgGui::init(PUVM pUVM, PCVMMR3VTABLE pVMM)
+{
+ /*
+ * Set the VM handle and update the desktop size.
+ */
+ m_pUVM = pUVM; /* Note! This eats the incoming reference to the handle! */
+ m_pVMM = pVMM;
+ updateDesktopSize();
+
+ return VINF_SUCCESS;
+}
+
+
+int VBoxDbgGui::init(ISession *pSession)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ /*
+ * Query the VirtualBox interfaces.
+ */
+ m_pSession = pSession;
+ m_pSession->AddRef();
+
+ HRESULT hrc = m_pSession->COMGETTER(Machine)(&m_pMachine);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = m_pSession->COMGETTER(Console)(&m_pConsole);
+ if (SUCCEEDED(hrc))
+ {
+ hrc = m_pConsole->COMGETTER(Debugger)(&m_pMachineDebugger);
+ if (SUCCEEDED(hrc))
+ {
+ /*
+ * Get the VM handle.
+ */
+ LONG64 llUVM = 0;
+ LONG64 llVMMFunctionTable = 0;
+ hrc = m_pMachineDebugger->GetUVMAndVMMFunctionTable((int64_t)VMMR3VTABLE_MAGIC_VERSION,
+ &llVMMFunctionTable, &llUVM);
+ if (SUCCEEDED(hrc))
+ {
+ PUVM pUVM = (PUVM)(intptr_t)llUVM;
+ PCVMMR3VTABLE pVMM = (PCVMMR3VTABLE)(intptr_t)llVMMFunctionTable;
+ rc = init(pUVM, pVMM);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ pVMM->pfnVMR3ReleaseUVM(pUVM);
+ }
+
+ /* damn, failure! */
+ m_pMachineDebugger->Release();
+ m_pMachineDebugger = NULL;
+ }
+ m_pConsole->Release();
+ m_pConsole = NULL;
+ }
+ m_pMachine->Release();
+ m_pMachine = NULL;
+ }
+
+ return rc;
+}
+
+
+VBoxDbgGui::~VBoxDbgGui()
+{
+ if (m_pDbgStats)
+ {
+ delete m_pDbgStats;
+ m_pDbgStats = NULL;
+ }
+
+ if (m_pDbgConsole)
+ {
+ delete m_pDbgConsole;
+ m_pDbgConsole = NULL;
+ }
+
+ if (m_pMachineDebugger)
+ {
+ m_pMachineDebugger->Release();
+ m_pMachineDebugger = NULL;
+ }
+
+ if (m_pConsole)
+ {
+ m_pConsole->Release();
+ m_pConsole = NULL;
+ }
+
+ if (m_pMachine)
+ {
+ m_pMachine->Release();
+ m_pMachine = NULL;
+ }
+
+ if (m_pSession)
+ {
+ m_pSession->Release();
+ m_pSession = NULL;
+ }
+
+ if (m_pUVM)
+ {
+ Assert(m_pVMM);
+ m_pVMM->pfnVMR3ReleaseUVM(m_pUVM);
+ m_pUVM = NULL;
+ m_pVMM = NULL;
+ }
+}
+
+void
+VBoxDbgGui::setParent(QWidget *pParent)
+{
+ m_pParent = pParent;
+}
+
+
+void
+VBoxDbgGui::setMenu(QMenu *pMenu)
+{
+ m_pMenu = pMenu;
+}
+
+
+int
+VBoxDbgGui::showStatistics(const char *pszFilter, const char *pszExpand)
+{
+ if (!m_pDbgStats)
+ {
+ m_pDbgStats = new VBoxDbgStats(this,
+ pszFilter && *pszFilter ? pszFilter : "*",
+ pszExpand && *pszExpand ? pszExpand : NULL,
+ 2, m_pParent);
+ connect(m_pDbgStats, SIGNAL(destroyed(QObject *)), this, SLOT(notifyChildDestroyed(QObject *)));
+ repositionStatistics();
+ }
+
+ m_pDbgStats->vShow();
+ return VINF_SUCCESS;
+}
+
+
+void
+VBoxDbgGui::repositionStatistics(bool fResize/* = true*/)
+{
+ /*
+ * Move it to the right side of the VBox console,
+ * and resize it to cover all the space to the left side of the desktop.
+ */
+ if (m_pDbgStats)
+ m_pDbgStats->vReposition(m_x + m_cx, m_y,
+ m_cxDesktop - m_cx - m_x + m_xDesktop, m_cyDesktop - m_y + m_yDesktop,
+ fResize);
+}
+
+
+int
+VBoxDbgGui::showConsole()
+{
+ if (!m_pDbgConsole)
+ {
+ IVirtualBox *pVirtualBox = NULL;
+ m_pMachine->COMGETTER(Parent)(&pVirtualBox);
+ m_pDbgConsole = new VBoxDbgConsole(this, m_pParent, pVirtualBox);
+ connect(m_pDbgConsole, SIGNAL(destroyed(QObject *)), this, SLOT(notifyChildDestroyed(QObject *)));
+ repositionConsole();
+ }
+
+ m_pDbgConsole->vShow();
+ return VINF_SUCCESS;
+}
+
+
+void
+VBoxDbgGui::repositionConsole(bool fResize/* = true*/)
+{
+ /*
+ * Move it to the bottom of the VBox console,
+ * and resize it to cover the space down to the bottom of the desktop.
+ */
+ if (m_pDbgConsole)
+ m_pDbgConsole->vReposition(m_x, m_y + m_cy,
+ RT_MAX(m_cx, 32), m_cyDesktop - m_cy - m_y + m_yDesktop,
+ fResize);
+}
+
+
+void
+VBoxDbgGui::updateDesktopSize()
+{
+ QRect Rct(0, 0, 1600, 1200);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
+ QScreen *pScreen = QApplication::screenAt(QPoint(m_x, m_y));
+ if (pScreen)
+ Rct = pScreen->availableGeometry();
+#else
+ QDesktopWidget *pDesktop = QApplication::desktop();
+ if (pDesktop)
+ Rct = pDesktop->availableGeometry(QPoint(m_x, m_y));
+#endif
+ m_xDesktop = Rct.x();
+ m_yDesktop = Rct.y();
+ m_cxDesktop = Rct.width();
+ m_cyDesktop = Rct.height();
+}
+
+
+void
+VBoxDbgGui::adjustRelativePos(int x, int y, unsigned cx, unsigned cy)
+{
+ /* Disregard a width less than 640 since it will mess up the console,
+ * but only if previos width was already initialized.. */
+ if ((cx < 640) && (m_cx > 0))
+ cx = m_cx;
+
+ const bool fResize = cx != m_cx || cy != m_cy;
+ const bool fMoved = x != m_x || y != m_y;
+
+ m_x = x;
+ m_y = y;
+ m_cx = cx;
+ m_cy = cy;
+
+ if (fMoved)
+ updateDesktopSize();
+ repositionConsole(fResize);
+ repositionStatistics(fResize);
+}
+
+
+QString
+VBoxDbgGui::getMachineName() const
+{
+ QString strName;
+ AssertReturn(m_pMachine, strName);
+ BSTR bstr;
+ HRESULT hrc = m_pMachine->COMGETTER(Name)(&bstr);
+ if (SUCCEEDED(hrc))
+ {
+ strName = QString::fromUtf16(bstr);
+ SysFreeString(bstr);
+ }
+ return strName;
+}
+
+
+void
+VBoxDbgGui::notifyChildDestroyed(QObject *pObj)
+{
+ if (m_pDbgStats == pObj)
+ m_pDbgStats = NULL;
+ else if (m_pDbgConsole == pObj)
+ m_pDbgConsole = NULL;
+}
+
diff --git a/src/VBox/Debugger/VBoxDbgGui.h b/src/VBox/Debugger/VBoxDbgGui.h
new file mode 100644
index 00000000..47f42ebd
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgGui.h
@@ -0,0 +1,225 @@
+/* $Id: VBoxDbgGui.h $ */
+/** @file
+ * VBox Debugger GUI - The Manager.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_VBoxDbgGui_h
+#define DEBUGGER_INCLUDED_SRC_VBoxDbgGui_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+// VirtualBox COM interfaces declarations (generated header)
+#ifdef VBOX_WITH_XPCOM
+# include <VirtualBox_XPCOM.h>
+#else
+# include <iprt/win/windows.h> /* Include via cleanup wrapper before VirtualBox.h includes it via rpc.h. */
+# include <VirtualBox.h>
+#endif
+
+#include "VBoxDbgStatsQt.h"
+#include "VBoxDbgConsole.h"
+
+
+/**
+ * The Debugger GUI manager class.
+ *
+ * It's job is to provide a C callable external interface and manage the
+ * windows and bit making up the debugger GUI.
+ */
+class VBoxDbgGui : public QObject
+{
+ Q_OBJECT;
+
+public:
+ /**
+ * Create a default VBoxDbgGui object.
+ */
+ VBoxDbgGui();
+
+ /**
+ * Initializes a VBoxDbgGui object by ISession.
+ *
+ * @returns VBox status code.
+ * @param pSession VBox Session object.
+ */
+ int init(ISession *pSession);
+
+ /**
+ * Initializes a VBoxDbgGui object by VM handle.
+ *
+ * @returns VBox status code.
+ * @param pUVM The user mode VM handle. The caller's reference will be
+ * consumed on success.
+ * @param pVMM The VMM function table.
+ */
+ int init(PUVM pUVM, PCVMMR3VTABLE pVMM);
+
+ /**
+ * Destroys the VBoxDbgGui object.
+ */
+ virtual ~VBoxDbgGui();
+
+ /**
+ * Sets the parent widget.
+ *
+ * @param pParent New parent widget.
+ * @remarks This only affects new windows.
+ */
+ void setParent(QWidget *pParent);
+
+ /**
+ * Sets the menu object.
+ *
+ * @param pMenu New menu object.
+ * @remarks This only affects new menu additions.
+ */
+ void setMenu(QMenu *pMenu);
+
+ /**
+ * Show the default statistics window, creating it if necessary.
+ *
+ * @returns VBox status code.
+ * @param pszFilter Filter pattern.
+ * @param pszExpand Expand pattern.
+ */
+ int showStatistics(const char *pszFilter, const char *pszExpand);
+
+ /**
+ * Repositions and resizes (optionally) the statistics to its defaults
+ *
+ * @param fResize If set (default) the size of window is also changed.
+ */
+ void repositionStatistics(bool fResize = true);
+
+ /**
+ * Show the console window (aka. command line), creating it if necessary.
+ *
+ * @returns VBox status code.
+ */
+ int showConsole();
+
+ /**
+ * Repositions and resizes (optionally) the console to its defaults
+ *
+ * @param fResize If set (default) the size of window is also changed.
+ */
+ void repositionConsole(bool fResize = true);
+
+ /**
+ * Update the desktop size.
+ * This is called whenever the reference window changes position.
+ */
+ void updateDesktopSize();
+
+ /**
+ * Notifies the debugger GUI that the console window (or whatever) has changed
+ * size or position.
+ *
+ * @param x The x-coordinate of the window the debugger is relative to.
+ * @param y The y-coordinate of the window the debugger is relative to.
+ * @param cx The width of the window the debugger is relative to.
+ * @param cy The height of the window the debugger is relative to.
+ */
+ void adjustRelativePos(int x, int y, unsigned cx, unsigned cy);
+
+ /**
+ * Gets the user mode VM handle.
+ * @returns The UVM handle.
+ */
+ PUVM getUvmHandle() const
+ {
+ return m_pUVM;
+ }
+
+ /**
+ * Gets the VMM function table.
+ * @returns The VMM function table.
+ */
+ PCVMMR3VTABLE getVMMFunctionTable() const
+ {
+ return m_pVMM;
+ }
+
+ /**
+ * @returns The name of the machine.
+ */
+ QString getMachineName() const;
+
+protected slots:
+ /**
+ * Notify that a child object (i.e. a window is begin destroyed).
+ * @param pObj The object which is being destroyed.
+ */
+ void notifyChildDestroyed(QObject *pObj);
+
+protected:
+
+ /** The debugger statistics. */
+ VBoxDbgStats *m_pDbgStats;
+ /** The debugger console (aka. command line). */
+ VBoxDbgConsole *m_pDbgConsole;
+
+ /** The VirtualBox session. */
+ ISession *m_pSession;
+ /** The VirtualBox console. */
+ IConsole *m_pConsole;
+ /** The VirtualBox Machine Debugger. */
+ IMachineDebugger *m_pMachineDebugger;
+ /** The VirtualBox Machine. */
+ IMachine *m_pMachine;
+ /** The VM instance. */
+ PVM m_pVM;
+ /** The user mode VM handle. */
+ PUVM m_pUVM;
+ /** The VMM function table. */
+ PCVMMR3VTABLE m_pVMM;
+
+ /** The parent widget. */
+ QWidget *m_pParent;
+ /** The menu object for the 'debug' menu. */
+ QMenu *m_pMenu;
+
+ /** The x-coordinate of the window we're relative to. */
+ int m_x;
+ /** The y-coordinate of the window we're relative to. */
+ int m_y;
+ /** The width of the window we're relative to. */
+ unsigned m_cx;
+ /** The height of the window we're relative to. */
+ unsigned m_cy;
+ /** The x-coordinate of the desktop. */
+ int m_xDesktop;
+ /** The y-coordinate of the desktop. */
+ int m_yDesktop;
+ /** The size of the desktop. */
+ unsigned m_cxDesktop;
+ /** The size of the desktop. */
+ unsigned m_cyDesktop;
+};
+
+
+#endif /* !DEBUGGER_INCLUDED_SRC_VBoxDbgGui_h */
+
diff --git a/src/VBox/Debugger/VBoxDbgStatsQt.cpp b/src/VBox/Debugger/VBoxDbgStatsQt.cpp
new file mode 100644
index 00000000..5c08b9e2
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgStatsQt.cpp
@@ -0,0 +1,3329 @@
+/* $Id: VBoxDbgStatsQt.cpp $ */
+/** @file
+ * VBox Debugger GUI - Statistics.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DBGG
+#include "VBoxDbgStatsQt.h"
+
+#include <QLocale>
+#include <QPushButton>
+#include <QSpinBox>
+#include <QLabel>
+#include <QClipboard>
+#include <QApplication>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QKeySequence>
+#include <QAction>
+#include <QContextMenuEvent>
+#include <QHeaderView>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+
+#include "VBoxDbgGui.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The number of column. */
+#define DBGGUI_STATS_COLUMNS 9
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * The state of a statistics sample node.
+ *
+ * This is used for two pass refresh (1. get data, 2. update the view) and
+ * for saving the result of a diff.
+ */
+typedef enum DBGGUISTATSNODESTATE
+{
+ /** The typical invalid zeroth entry. */
+ kDbgGuiStatsNodeState_kInvalid = 0,
+ /** The node is the root node. */
+ kDbgGuiStatsNodeState_kRoot,
+ /** The node is visible. */
+ kDbgGuiStatsNodeState_kVisible,
+ /** The node should be refreshed. */
+ kDbgGuiStatsNodeState_kRefresh,
+ /** diff: The node equals. */
+ kDbgGuiStatsNodeState_kDiffEqual,
+ /** diff: The node in set 1 is less than the one in set 2. */
+ kDbgGuiStatsNodeState_kDiffSmaller,
+ /** diff: The node in set 1 is greater than the one in set 2. */
+ kDbgGuiStatsNodeState_kDiffGreater,
+ /** diff: The node is only in set 1. */
+ kDbgGuiStatsNodeState_kDiffOnlyIn1,
+ /** diff: The node is only in set 2. */
+ kDbgGuiStatsNodeState_kDiffOnlyIn2,
+ /** The end of the valid state values. */
+ kDbgGuiStatsNodeState_kEnd
+} DBGGUISTATENODESTATE;
+
+
+/**
+ * A tree node representing a statistic sample.
+ *
+ * The nodes carry a reference to the parent and to its position among its
+ * siblings. Both of these need updating when the grand parent or parent adds a
+ * new child. This will hopefully not be too expensive but rather pay off when
+ * we need to create a parent index.
+ */
+typedef struct DBGGUISTATSNODE
+{
+ /** Pointer to the parent. */
+ PDBGGUISTATSNODE pParent;
+ /** Array of pointers to the child nodes. */
+ PDBGGUISTATSNODE *papChildren;
+ /** The number of children. */
+ uint32_t cChildren;
+ /** Our index among the parent's children. */
+ uint32_t iSelf;
+ /** The unit string. (not allocated) */
+ const char *pszUnit;
+ /** The data type.
+ * For filler nodes not containing data, this will be set to STAMTYPE_INVALID. */
+ STAMTYPE enmType;
+ /** The data at last update. */
+ union
+ {
+ /** STAMTYPE_COUNTER. */
+ STAMCOUNTER Counter;
+ /** STAMTYPE_PROFILE. */
+ STAMPROFILE Profile;
+ /** STAMTYPE_PROFILE_ADV. */
+ STAMPROFILEADV ProfileAdv;
+ /** STAMTYPE_RATIO_U32. */
+ STAMRATIOU32 RatioU32;
+ /** STAMTYPE_U8 & STAMTYPE_U8_RESET. */
+ uint8_t u8;
+ /** STAMTYPE_U16 & STAMTYPE_U16_RESET. */
+ uint16_t u16;
+ /** STAMTYPE_U32 & STAMTYPE_U32_RESET. */
+ uint32_t u32;
+ /** STAMTYPE_U64 & STAMTYPE_U64_RESET. */
+ uint64_t u64;
+ /** STAMTYPE_BOOL and STAMTYPE_BOOL_RESET. */
+ bool f;
+ /** STAMTYPE_CALLBACK. */
+ QString *pStr;
+ } Data;
+ /** The delta. */
+ int64_t i64Delta;
+ /** The name. */
+ char *pszName;
+ /** The length of the name. */
+ size_t cchName;
+ /** The description string. */
+ QString *pDescStr;
+ /** The node state. */
+ DBGGUISTATENODESTATE enmState;
+} DBGGUISTATSNODE;
+
+
+/**
+ * Recursion stack.
+ */
+typedef struct DBGGUISTATSSTACK
+{
+ /** The top stack entry. */
+ int32_t iTop;
+ /** The stack array. */
+ struct DBGGUISTATSSTACKENTRY
+ {
+ /** The node. */
+ PDBGGUISTATSNODE pNode;
+ /** The current child. */
+ int32_t iChild;
+ /** Name string offset (if used). */
+ uint16_t cchName;
+ } a[32];
+} DBGGUISTATSSTACK;
+
+
+
+
+/**
+ * The item model for the statistics tree view.
+ *
+ * This manages the DBGGUISTATSNODE trees.
+ */
+class VBoxDbgStatsModel : public QAbstractItemModel
+{
+protected:
+ /** The root of the sample tree. */
+ PDBGGUISTATSNODE m_pRoot;
+
+private:
+ /** Next update child. This is UINT32_MAX when invalid. */
+ uint32_t m_iUpdateChild;
+ /** Pointer to the node m_szUpdateParent represent and m_iUpdateChild refers to. */
+ PDBGGUISTATSNODE m_pUpdateParent;
+ /** The length of the path. */
+ size_t m_cchUpdateParent;
+ /** The path to the current update parent, including a trailing slash. */
+ char m_szUpdateParent[1024];
+ /** Inserted or/and removed nodes during the update. */
+ bool m_fUpdateInsertRemove;
+
+
+public:
+ /**
+ * Constructor.
+ *
+ * @param a_pParent The parent object. See QAbstractItemModel in the Qt
+ * docs for details.
+ */
+ VBoxDbgStatsModel(QObject *a_pParent);
+
+ /**
+ * Destructor.
+ *
+ * This will free all the data the model holds.
+ */
+ virtual ~VBoxDbgStatsModel();
+
+ /**
+ * Updates the data matching the specified pattern.
+ *
+ * This will should invoke updatePrep, updateCallback and updateDone.
+ *
+ * It is vitally important that updateCallback is fed the data in the right
+ * order. The code make very definite ASSUMPTIONS about the ordering being
+ * strictly sorted and taking the slash into account when doing so.
+ *
+ * @returns true if we reset the model and it's necessary to set the root index.
+ * @param a_rPatStr The selection pattern.
+ *
+ * @remarks The default implementation is an empty stub.
+ */
+ virtual bool updateStatsByPattern(const QString &a_rPatStr);
+
+ /**
+ * Similar to updateStatsByPattern, except that it only works on a sub-tree and
+ * will not remove anything that's outside that tree.
+ *
+ * @param a_rIndex The sub-tree root. Invalid index means root.
+ *
+ * @todo Create a default implementation using updateStatsByPattern.
+ */
+ virtual void updateStatsByIndex(QModelIndex const &a_rIndex);
+
+ /**
+ * Reset the stats matching the specified pattern.
+ *
+ * @param a_rPatStr The selection pattern.
+ *
+ * @remarks The default implementation is an empty stub.
+ */
+ virtual void resetStatsByPattern(QString const &a_rPatStr);
+
+ /**
+ * Reset the stats of a sub-tree.
+ *
+ * @param a_rIndex The sub-tree root. Invalid index means root.
+ * @param a_fSubTree Whether to reset the sub-tree as well. Default is true.
+ *
+ * @remarks The default implementation makes use of resetStatsByPattern
+ */
+ virtual void resetStatsByIndex(QModelIndex const &a_rIndex, bool a_fSubTree = true);
+
+ /**
+ * Iterator callback function.
+ * @returns true to continue, false to stop.
+ */
+ typedef bool FNITERATOR(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex, const char *pszFullName, void *pvUser);
+
+ /**
+ * Callback iterator.
+ *
+ * @param a_rPatStr The selection pattern.
+ * @param a_pfnCallback Callback function.
+ * @param a_pvUser Callback argument.
+ * @param a_fMatchChildren How to handle children of matching nodes:
+ * - @c true: continue with the children,
+ * - @c false: skip children.
+ */
+ virtual void iterateStatsByPattern(QString const &a_rPatStr, FNITERATOR *a_pfnCallback, void *a_pvUser,
+ bool a_fMatchChildren = true);
+
+ /**
+ * Gets the model index of the root node.
+ *
+ * @returns root index.
+ */
+ QModelIndex getRootIndex(void) const;
+
+
+protected:
+ /**
+ * Set the root node.
+ *
+ * This will free all the current data before taking the ownership of the new
+ * root node and its children.
+ *
+ * @param a_pRoot The new root node.
+ */
+ void setRootNode(PDBGGUISTATSNODE a_pRoot);
+
+ /** Creates the root node. */
+ static PDBGGUISTATSNODE createRootNode(void);
+
+ /** Creates and insert a node under the given parent. */
+ static PDBGGUISTATSNODE createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
+
+ /** Creates and insert a node under the given parent with correct Qt
+ * signalling. */
+ PDBGGUISTATSNODE createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition);
+
+ /**
+ * Resets the node to a pristine state.
+ *
+ * @param pNode The node.
+ */
+ static void resetNode(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Initializes a pristine node.
+ */
+ static int initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc);
+
+ /**
+ * Updates (or reinitializes if you like) a node.
+ */
+ static void updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc);
+
+ /**
+ * Called by updateStatsByPattern(), makes the necessary preparations.
+ *
+ * @returns Success indicator.
+ */
+ bool updatePrepare(void);
+
+ /**
+ * Called by updateStatsByPattern(), finalizes the update.
+ *
+ * @return See updateStatsByPattern().
+ *
+ * @param a_fSuccess Whether the update was successful or not.
+ *
+ */
+ bool updateDone(bool a_fSuccess);
+
+ /**
+ * updateCallback() worker taking care of in-tree inserts and removals.
+ *
+ * @returns The current node.
+ * @param pszName The name of the tree element to update.
+ */
+ PDBGGUISTATSNODE updateCallbackHandleOutOfOrder(const char *pszName);
+
+ /**
+ * updateCallback() worker taking care of tail insertions.
+ *
+ * @returns The current node.
+ * @param pszName The name of the tree element to update.
+ */
+ PDBGGUISTATSNODE updateCallbackHandleTail(const char *pszName);
+
+ /**
+ * updateCallback() worker that advances the update state to the next data node
+ * in anticipation of the next updateCallback call.
+ *
+ * @param pNode The current node.
+ */
+ void updateCallbackAdvance(PDBGGUISTATSNODE pNode);
+
+ /** Callback used by updateStatsByPattern() and updateStatsByIndex() to feed
+ * changes.
+ * @copydoc FNSTAMR3ENUM */
+ static DECLCALLBACK(int) updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
+ const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser);
+
+ /**
+ * Calculates the full path of a node.
+ *
+ * @returns Number of bytes returned, negative value on buffer overflow
+ *
+ * @param pNode The node.
+ * @param psz The output buffer.
+ * @param cch The size of the buffer.
+ */
+ static ssize_t getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
+
+ /**
+ * Calculates the full path of a node, returning the string pointer.
+ *
+ * @returns @a psz. On failure, NULL.
+ *
+ * @param pNode The node.
+ * @param psz The output buffer.
+ * @param cch The size of the buffer.
+ */
+ static char *getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch);
+
+ /**
+ * Check if the first node is an ancestor to the second one.
+ *
+ * @returns true/false.
+ * @param pAncestor The first node, the alleged ancestor.
+ * @param pDescendant The second node, the alleged descendant.
+ */
+ static bool isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant);
+
+ /**
+ * Advance to the next node in the tree.
+ *
+ * @returns Pointer to the next node, NULL if we've reached the end or
+ * was handed a NULL node.
+ * @param pNode The current node.
+ */
+ static PDBGGUISTATSNODE nextNode(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Advance to the next node in the tree that contains data.
+ *
+ * @returns Pointer to the next data node, NULL if we've reached the end or
+ * was handed a NULL node.
+ * @param pNode The current node.
+ */
+ static PDBGGUISTATSNODE nextDataNode(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Advance to the previous node in the tree.
+ *
+ * @returns Pointer to the previous node, NULL if we've reached the end or
+ * was handed a NULL node.
+ * @param pNode The current node.
+ */
+ static PDBGGUISTATSNODE prevNode(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Advance to the previous node in the tree that contains data.
+ *
+ * @returns Pointer to the previous data node, NULL if we've reached the end or
+ * was handed a NULL node.
+ * @param pNode The current node.
+ */
+ static PDBGGUISTATSNODE prevDataNode(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Removes a node from the tree.
+ *
+ * @returns pNode.
+ * @param pNode The node.
+ */
+ static PDBGGUISTATSNODE removeNode(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Removes a node from the tree and destroys it and all its descendants.
+ *
+ * @param pNode The node.
+ */
+ static void removeAndDestroyNode(PDBGGUISTATSNODE pNode);
+
+ /** Removes a node from the tree and destroys it and all its descendants
+ * performing the required Qt signalling. */
+ void removeAndDestroy(PDBGGUISTATSNODE pNode);
+
+ /**
+ * Destroys a statistics tree.
+ *
+ * @param a_pRoot The root of the tree. NULL is fine.
+ */
+ static void destroyTree(PDBGGUISTATSNODE a_pRoot);
+
+ /**
+ * Stringifies exactly one node, no children.
+ *
+ * This is for logging and clipboard.
+ *
+ * @param a_pNode The node.
+ * @param a_rString The string to append the stringified node to.
+ */
+ static void stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString);
+
+ /**
+ * Stringifies a node and its children.
+ *
+ * This is for logging and clipboard.
+ *
+ * @param a_pNode The node.
+ * @param a_rString The string to append the stringified node to.
+ */
+ static void stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString);
+
+public:
+ /**
+ * Converts the specified tree to string.
+ *
+ * This is for logging and clipboard.
+ *
+ * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
+ * @param a_rString Where to return to return the string dump.
+ */
+ void stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
+
+ /**
+ * Dumps the given (sub-)tree as XML.
+ *
+ * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
+ * @param a_rString Where to return to return the XML dump.
+ */
+ void xmlifyTree(QModelIndex &a_rRoot, QString &a_rString) const;
+
+ /**
+ * Puts the stringified tree on the clipboard.
+ *
+ * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
+ */
+ void copyTreeToClipboard(QModelIndex &a_rRoot) const;
+
+
+protected:
+ /** Worker for logTree. */
+ static void logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog);
+
+public:
+ /** Logs a (sub-)tree.
+ *
+ * @param a_rRoot Where to start. Use QModelIndex() to start at the root.
+ * @param a_fReleaseLog Whether to use the release log (true) or the debug log (false).
+ */
+ void logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const;
+
+protected:
+ /** Gets the unit. */
+ static QString strUnit(PCDBGGUISTATSNODE pNode);
+ /** Gets the value/times. */
+ static QString strValueTimes(PCDBGGUISTATSNODE pNode);
+ /** Gets the minimum value. */
+ static QString strMinValue(PCDBGGUISTATSNODE pNode);
+ /** Gets the average value. */
+ static QString strAvgValue(PCDBGGUISTATSNODE pNode);
+ /** Gets the maximum value. */
+ static QString strMaxValue(PCDBGGUISTATSNODE pNode);
+ /** Gets the total value. */
+ static QString strTotalValue(PCDBGGUISTATSNODE pNode);
+ /** Gets the delta value. */
+ static QString strDeltaValue(PCDBGGUISTATSNODE pNode);
+
+ /**
+ * Destroys a node and all its children.
+ *
+ * @param a_pNode The node to destroy.
+ */
+ static void destroyNode(PDBGGUISTATSNODE a_pNode);
+
+ /**
+ * Converts an index to a node pointer.
+ *
+ * @returns Pointer to the node, NULL if invalid reference.
+ * @param a_rIndex Reference to the index
+ */
+ inline PDBGGUISTATSNODE nodeFromIndex(const QModelIndex &a_rIndex) const
+ {
+ if (RT_LIKELY(a_rIndex.isValid()))
+ return (PDBGGUISTATSNODE)a_rIndex.internalPointer();
+ return NULL;
+ }
+
+public:
+
+ /** @name Overridden QAbstractItemModel methods
+ * @{ */
+ virtual int columnCount(const QModelIndex &a_rParent) const;
+ virtual QVariant data(const QModelIndex &a_rIndex, int a_eRole) const;
+ virtual Qt::ItemFlags flags(const QModelIndex &a_rIndex) const;
+ virtual bool hasChildren(const QModelIndex &a_rParent) const;
+ virtual QVariant headerData(int a_iSection, Qt::Orientation a_ePrientation, int a_eRole) const;
+ virtual QModelIndex index(int a_iRow, int a_iColumn, const QModelIndex &a_rParent) const;
+ virtual QModelIndex parent(const QModelIndex &a_rChild) const;
+ virtual int rowCount(const QModelIndex &a_rParent) const;
+ ///virtual void sort(int a_iColumn, Qt::SortOrder a_eOrder);
+ /** @} */
+};
+
+
+/**
+ * Model using the VM / STAM interface as data source.
+ */
+class VBoxDbgStatsModelVM : public VBoxDbgStatsModel, public VBoxDbgBase
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @param a_pDbgGui Pointer to the debugger gui object.
+ * @param a_rPatStr The selection pattern.
+ * @param a_pParent The parent object. NULL is fine.
+ * @param a_pVMM The VMM function table.
+ */
+ VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM);
+
+ /** Destructor */
+ virtual ~VBoxDbgStatsModelVM();
+
+ virtual bool updateStatsByPattern(const QString &a_rPatStr);
+ virtual void resetStatsByPattern(const QString &a_rPatStr);
+
+protected:
+ /**
+ * Enumeration callback used by createNewTree.
+ */
+ static DECLCALLBACK(int) createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
+ const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc,
+ void *pvUser);
+
+ /**
+ * Constructs a new statistics tree by query data from the VM.
+ *
+ * @returns Pointer to the root of the tree we've constructed. This will be NULL
+ * if the STAM API throws an error or we run out of memory.
+ * @param a_rPatStr The selection pattern.
+ */
+ PDBGGUISTATSNODE createNewTree(QString &a_rPatStr);
+
+ /** The VMM function table. */
+ PCVMMR3VTABLE m_pVMM;
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Formats a number into a 64-byte buffer.
+ */
+static char *formatNumber(char *psz, uint64_t u64)
+{
+ static const char s_szDigits[] = "0123456789";
+ psz += 63;
+ *psz-- = '\0';
+ unsigned cDigits = 0;
+ for (;;)
+ {
+ const unsigned iDigit = u64 % 10;
+ u64 /= 10;
+ *psz = s_szDigits[iDigit];
+ if (!u64)
+ break;
+ psz--;
+ if (!(++cDigits % 3))
+ *psz-- = ',';
+ }
+ return psz;
+}
+
+
+/**
+ * Formats a number into a 64-byte buffer.
+ * (18 446 744 073 709 551 615)
+ */
+static char *formatNumberSigned(char *psz, int64_t i64)
+{
+ static const char s_szDigits[] = "0123456789";
+ psz += 63;
+ *psz-- = '\0';
+ const bool fNegative = i64 < 0;
+ uint64_t u64 = fNegative ? -i64 : i64;
+ unsigned cDigits = 0;
+ for (;;)
+ {
+ const unsigned iDigit = u64 % 10;
+ u64 /= 10;
+ *psz = s_szDigits[iDigit];
+ if (!u64)
+ break;
+ psz--;
+ if (!(++cDigits % 3))
+ *psz-- = ',';
+ }
+ if (fNegative)
+ *--psz = '-';
+ return psz;
+}
+
+
+/**
+ * Formats a unsigned hexadecimal number into a into a 64-byte buffer.
+ */
+static char *formatHexNumber(char *psz, uint64_t u64, unsigned cZeros)
+{
+ static const char s_szDigits[] = "0123456789abcdef";
+ psz += 63;
+ *psz-- = '\0';
+ unsigned cDigits = 0;
+ for (;;)
+ {
+ const unsigned iDigit = u64 % 16;
+ u64 /= 16;
+ *psz = s_szDigits[iDigit];
+ ++cDigits;
+ if (!u64 && cDigits >= cZeros)
+ break;
+ psz--;
+ if (!(cDigits % 8))
+ *psz-- = '\'';
+ }
+ return psz;
+}
+
+
+#if 0/* unused */
+/**
+ * Formats a sort key number.
+ */
+static void formatSortKey(char *psz, uint64_t u64)
+{
+ static const char s_szDigits[] = "0123456789abcdef";
+ /* signed */
+ *psz++ = '+';
+
+ /* 16 hex digits */
+ psz[16] = '\0';
+ unsigned i = 16;
+ while (i-- > 0)
+ {
+ if (u64)
+ {
+ const unsigned iDigit = u64 % 16;
+ u64 /= 16;
+ psz[i] = s_szDigits[iDigit];
+ }
+ else
+ psz[i] = '0';
+ }
+}
+#endif
+
+
+#if 0/* unused */
+/**
+ * Formats a sort key number.
+ */
+static void formatSortKeySigned(char *psz, int64_t i64)
+{
+ static const char s_szDigits[] = "0123456789abcdef";
+
+ /* signed */
+ uint64_t u64;
+ if (i64 >= 0)
+ {
+ *psz++ = '+';
+ u64 = i64;
+ }
+ else
+ {
+ *psz++ = '-';
+ u64 = -i64;
+ }
+
+ /* 16 hex digits */
+ psz[16] = '\0';
+ unsigned i = 16;
+ while (i-- > 0)
+ {
+ if (u64)
+ {
+ const unsigned iDigit = u64 % 16;
+ u64 /= 16;
+ psz[i] = s_szDigits[iDigit];
+ }
+ else
+ psz[i] = '0';
+ }
+}
+#endif
+
+
+
+/*
+ *
+ * V B o x D b g S t a t s M o d e l
+ * V B o x D b g S t a t s M o d e l
+ * V B o x D b g S t a t s M o d e l
+ *
+ *
+ */
+
+
+VBoxDbgStatsModel::VBoxDbgStatsModel(QObject *a_pParent)
+ : QAbstractItemModel(a_pParent),
+ m_pRoot(NULL), m_iUpdateChild(UINT32_MAX), m_pUpdateParent(NULL), m_cchUpdateParent(0)
+{
+}
+
+
+
+VBoxDbgStatsModel::~VBoxDbgStatsModel()
+{
+ destroyTree(m_pRoot);
+ m_pRoot = NULL;
+}
+
+
+/*static*/ void
+VBoxDbgStatsModel::destroyTree(PDBGGUISTATSNODE a_pRoot)
+{
+ if (!a_pRoot)
+ return;
+ Assert(!a_pRoot->pParent);
+ Assert(!a_pRoot->iSelf);
+
+ destroyNode(a_pRoot);
+}
+
+
+/* static*/ void
+VBoxDbgStatsModel::destroyNode(PDBGGUISTATSNODE a_pNode)
+{
+ /* destroy all our children */
+ uint32_t i = a_pNode->cChildren;
+ while (i-- > 0)
+ {
+ destroyNode(a_pNode->papChildren[i]);
+ a_pNode->papChildren[i] = NULL;
+ }
+
+ /* free the resources we're using */
+ a_pNode->pParent = NULL;
+
+ RTMemFree(a_pNode->papChildren);
+ a_pNode->papChildren = NULL;
+
+ if (a_pNode->enmType == STAMTYPE_CALLBACK)
+ {
+ delete a_pNode->Data.pStr;
+ a_pNode->Data.pStr = NULL;
+ }
+
+ a_pNode->cChildren = 0;
+ a_pNode->iSelf = UINT32_MAX;
+ a_pNode->pszUnit = "";
+ a_pNode->enmType = STAMTYPE_INVALID;
+
+ RTMemFree(a_pNode->pszName);
+ a_pNode->pszName = NULL;
+
+ if (a_pNode->pDescStr)
+ {
+ delete a_pNode->pDescStr;
+ a_pNode->pDescStr = NULL;
+ }
+
+#ifdef VBOX_STRICT
+ /* poison it. */
+ a_pNode->pParent++;
+ a_pNode->Data.pStr++;
+ a_pNode->pDescStr++;
+ a_pNode->papChildren++;
+ a_pNode->cChildren = 8442;
+#endif
+
+ /* Finally ourselves */
+ a_pNode->enmState = kDbgGuiStatsNodeState_kInvalid;
+ RTMemFree(a_pNode);
+}
+
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::createRootNode(void)
+{
+ PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
+ if (!pRoot)
+ return NULL;
+ pRoot->iSelf = 0;
+ pRoot->enmType = STAMTYPE_INVALID;
+ pRoot->pszUnit = "";
+ pRoot->pszName = (char *)RTMemDup("/", sizeof("/"));
+ pRoot->cchName = 1;
+ pRoot->enmState = kDbgGuiStatsNodeState_kRoot;
+
+ return pRoot;
+}
+
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::createAndInsertNode(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
+{
+ /*
+ * Create it.
+ */
+ PDBGGUISTATSNODE pNode = (PDBGGUISTATSNODE)RTMemAllocZ(sizeof(DBGGUISTATSNODE));
+ if (!pNode)
+ return NULL;
+ pNode->iSelf = UINT32_MAX;
+ pNode->enmType = STAMTYPE_INVALID;
+ pNode->pszUnit = "";
+ pNode->pszName = (char *)RTMemDupEx(pszName, cchName, 1);
+ pNode->cchName = cchName;
+ pNode->enmState = kDbgGuiStatsNodeState_kVisible;
+
+ /*
+ * Do we need to expand the array?
+ */
+ if (!(pParent->cChildren & 31))
+ {
+ void *pvNew = RTMemRealloc(pParent->papChildren, sizeof(*pParent->papChildren) * (pParent->cChildren + 32));
+ if (!pvNew)
+ {
+ destroyNode(pNode);
+ return NULL;
+ }
+ pParent->papChildren = (PDBGGUISTATSNODE *)pvNew;
+ }
+
+ /*
+ * Insert it.
+ */
+ pNode->pParent = pParent;
+ if (iPosition >= pParent->cChildren)
+ /* Last. */
+ iPosition = pParent->cChildren;
+ else
+ {
+ /* Shift all the items after ours. */
+ uint32_t iShift = pParent->cChildren;
+ while (iShift-- > iPosition)
+ {
+ PDBGGUISTATSNODE pChild = pParent->papChildren[iShift];
+ pParent->papChildren[iShift + 1] = pChild;
+ pChild->iSelf = iShift + 1;
+ }
+ }
+
+ /* Insert ours */
+ pNode->iSelf = iPosition;
+ pParent->papChildren[iPosition] = pNode;
+ pParent->cChildren++;
+
+ return pNode;
+}
+
+
+PDBGGUISTATSNODE
+VBoxDbgStatsModel::createAndInsert(PDBGGUISTATSNODE pParent, const char *pszName, size_t cchName, uint32_t iPosition)
+{
+ PDBGGUISTATSNODE pNode;
+ if (m_fUpdateInsertRemove)
+ pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
+ else
+ {
+ beginInsertRows(createIndex(pParent->iSelf, 0, pParent), 0, 0);
+ pNode = createAndInsertNode(pParent, pszName, cchName, iPosition);
+ endInsertRows();
+ }
+ return pNode;
+}
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::removeNode(PDBGGUISTATSNODE pNode)
+{
+ PDBGGUISTATSNODE pParent = pNode->pParent;
+ if (pParent)
+ {
+ uint32_t iPosition = pNode->iSelf;
+ Assert(pParent->papChildren[iPosition] == pNode);
+ uint32_t const cChildren = --pParent->cChildren;
+ for (; iPosition < cChildren; iPosition++)
+ {
+ PDBGGUISTATSNODE pChild = pParent->papChildren[iPosition + 1];
+ pParent->papChildren[iPosition] = pChild;
+ pChild->iSelf = iPosition;
+ }
+#ifdef VBOX_STRICT /* poison */
+ pParent->papChildren[iPosition] = (PDBGGUISTATSNODE)0x42;
+#endif
+ }
+ return pNode;
+}
+
+
+/*static*/ void
+VBoxDbgStatsModel::removeAndDestroyNode(PDBGGUISTATSNODE pNode)
+{
+ removeNode(pNode);
+ destroyNode(pNode);
+}
+
+
+void
+VBoxDbgStatsModel::removeAndDestroy(PDBGGUISTATSNODE pNode)
+{
+ if (m_fUpdateInsertRemove)
+ removeAndDestroyNode(pNode);
+ else
+ {
+ /*
+ * Removing is fun since the docs are imprecise as to how persistent
+ * indexes are updated (or aren't). So, let try a few different ideas
+ * and see which works.
+ */
+#if 1
+ /* destroy the children first with the appropriate begin/endRemoveRows signals. */
+ DBGGUISTATSSTACK Stack;
+ Stack.a[0].pNode = pNode;
+ Stack.a[0].iChild = -1;
+ Stack.iTop = 0;
+ while (Stack.iTop >= 0)
+ {
+ /* get top element */
+ PDBGGUISTATSNODE pCurNode = Stack.a[Stack.iTop].pNode;
+ uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
+ if (iChild < pCurNode->cChildren)
+ {
+ /* push */
+ Stack.iTop++;
+ Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
+ Stack.a[Stack.iTop].pNode = pCurNode->papChildren[iChild];
+ Stack.a[Stack.iTop].iChild = 0;
+ }
+ else
+ {
+ /* pop and destroy all the children. */
+ Stack.iTop--;
+ uint32_t i = pCurNode->cChildren;
+ if (i)
+ {
+ beginRemoveRows(createIndex(pCurNode->iSelf, 0, pCurNode), 0, i - 1);
+ while (i-- > 0)
+ destroyNode(pCurNode->papChildren[i]);
+ pCurNode->cChildren = 0;
+ endRemoveRows();
+ }
+ }
+ }
+ Assert(!pNode->cChildren);
+
+ /* finally the node it self. */
+ PDBGGUISTATSNODE pParent = pNode->pParent;
+ beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
+ removeAndDestroyNode(pNode);
+ endRemoveRows();
+
+#elif 0
+ /* This ain't working, leaves invalid indexes behind. */
+ PDBGGUISTATSNODE pParent = pNode->pParent;
+ beginRemoveRows(createIndex(pParent->iSelf, 0, pParent), pNode->iSelf, pNode->iSelf);
+ removeAndDestroyNode(pNode);
+ endRemoveRows();
+#else
+ /* Force reset() of the model after the update. */
+ m_fUpdateInsertRemove = true;
+ removeAndDestroyNode(pNode);
+#endif
+ }
+}
+
+
+/*static*/ void
+VBoxDbgStatsModel::resetNode(PDBGGUISTATSNODE pNode)
+{
+ /* free and reinit the data. */
+ if (pNode->enmType == STAMTYPE_CALLBACK)
+ {
+ delete pNode->Data.pStr;
+ pNode->Data.pStr = NULL;
+ }
+ pNode->enmType = STAMTYPE_INVALID;
+
+ /* free the description. */
+ if (pNode->pDescStr)
+ {
+ delete pNode->pDescStr;
+ pNode->pDescStr = NULL;
+ }
+}
+
+
+/*static*/ int
+VBoxDbgStatsModel::initNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample,
+ const char *pszUnit, const char *pszDesc)
+{
+ /*
+ * Copy the data.
+ */
+ pNode->pszUnit = pszUnit;
+ Assert(pNode->enmType == STAMTYPE_INVALID);
+ pNode->enmType = enmType;
+ if (pszDesc)
+ pNode->pDescStr = new QString(pszDesc); /* ignore allocation failure (well, at least up to the point we can ignore it) */
+
+ switch (enmType)
+ {
+ case STAMTYPE_COUNTER:
+ pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
+ break;
+
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
+ break;
+
+ case STAMTYPE_RATIO_U32:
+ case STAMTYPE_RATIO_U32_RESET:
+ pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
+ break;
+
+ case STAMTYPE_CALLBACK:
+ {
+ const char *pszString = (const char *)pvSample;
+ pNode->Data.pStr = new QString(pszString);
+ break;
+ }
+
+ case STAMTYPE_U8:
+ case STAMTYPE_U8_RESET:
+ case STAMTYPE_X8:
+ case STAMTYPE_X8_RESET:
+ pNode->Data.u8 = *(uint8_t *)pvSample;
+ break;
+
+ case STAMTYPE_U16:
+ case STAMTYPE_U16_RESET:
+ case STAMTYPE_X16:
+ case STAMTYPE_X16_RESET:
+ pNode->Data.u16 = *(uint16_t *)pvSample;
+ break;
+
+ case STAMTYPE_U32:
+ case STAMTYPE_U32_RESET:
+ case STAMTYPE_X32:
+ case STAMTYPE_X32_RESET:
+ pNode->Data.u32 = *(uint32_t *)pvSample;
+ break;
+
+ case STAMTYPE_U64:
+ case STAMTYPE_U64_RESET:
+ case STAMTYPE_X64:
+ case STAMTYPE_X64_RESET:
+ pNode->Data.u64 = *(uint64_t *)pvSample;
+ break;
+
+ case STAMTYPE_BOOL:
+ case STAMTYPE_BOOL_RESET:
+ pNode->Data.f = *(bool *)pvSample;
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", enmType));
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+
+/*static*/ void
+VBoxDbgStatsModel::updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, const char *pszUnit, const char *pszDesc)
+{
+ /*
+ * Reset and init the node if the type changed.
+ */
+ if (enmType != pNode->enmType)
+ {
+ if (pNode->enmType != STAMTYPE_INVALID)
+ resetNode(pNode);
+ initNode(pNode, enmType, pvSample, pszUnit, pszDesc);
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ else
+ {
+ /*
+ * ASSUME that only the sample value will change and that the unit, visibility
+ * and description remains the same.
+ */
+
+ int64_t iDelta;
+ switch (enmType)
+ {
+ case STAMTYPE_COUNTER:
+ {
+ uint64_t cPrev = pNode->Data.Counter.c;
+ pNode->Data.Counter = *(PSTAMCOUNTER)pvSample;
+ iDelta = pNode->Data.Counter.c - cPrev;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ {
+ uint64_t cPrevPeriods = pNode->Data.Profile.cPeriods;
+ pNode->Data.Profile = *(PSTAMPROFILE)pvSample;
+ iDelta = pNode->Data.Profile.cPeriods - cPrevPeriods;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_RATIO_U32:
+ case STAMTYPE_RATIO_U32_RESET:
+ {
+ STAMRATIOU32 Prev = pNode->Data.RatioU32;
+ pNode->Data.RatioU32 = *(PSTAMRATIOU32)pvSample;
+ int32_t iDeltaA = pNode->Data.RatioU32.u32A - Prev.u32A;
+ int32_t iDeltaB = pNode->Data.RatioU32.u32B - Prev.u32B;
+ if (iDeltaA == 0 && iDeltaB == 0)
+ {
+ if (pNode->i64Delta)
+ {
+ pNode->i64Delta = 0;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ }
+ else
+ {
+ if (iDeltaA >= 0)
+ pNode->i64Delta = iDeltaA + (iDeltaB >= 0 ? iDeltaB : -iDeltaB);
+ else
+ pNode->i64Delta = iDeltaA + (iDeltaB < 0 ? iDeltaB : -iDeltaB);
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_CALLBACK:
+ {
+ const char *pszString = (const char *)pvSample;
+ if (!pNode->Data.pStr)
+ {
+ pNode->Data.pStr = new QString(pszString);
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ else if (*pNode->Data.pStr == pszString)
+ {
+ delete pNode->Data.pStr;
+ pNode->Data.pStr = new QString(pszString);
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_U8:
+ case STAMTYPE_U8_RESET:
+ case STAMTYPE_X8:
+ case STAMTYPE_X8_RESET:
+ {
+ uint8_t uPrev = pNode->Data.u8;
+ pNode->Data.u8 = *(uint8_t *)pvSample;
+ iDelta = (int32_t)pNode->Data.u8 - (int32_t)uPrev;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_U16:
+ case STAMTYPE_U16_RESET:
+ case STAMTYPE_X16:
+ case STAMTYPE_X16_RESET:
+ {
+ uint16_t uPrev = pNode->Data.u16;
+ pNode->Data.u16 = *(uint16_t *)pvSample;
+ iDelta = (int32_t)pNode->Data.u16 - (int32_t)uPrev;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_U32:
+ case STAMTYPE_U32_RESET:
+ case STAMTYPE_X32:
+ case STAMTYPE_X32_RESET:
+ {
+ uint32_t uPrev = pNode->Data.u32;
+ pNode->Data.u32 = *(uint32_t *)pvSample;
+ iDelta = (int64_t)pNode->Data.u32 - (int64_t)uPrev;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_U64:
+ case STAMTYPE_U64_RESET:
+ case STAMTYPE_X64:
+ case STAMTYPE_X64_RESET:
+ {
+ uint64_t uPrev = pNode->Data.u64;
+ pNode->Data.u64 = *(uint64_t *)pvSample;
+ iDelta = pNode->Data.u64 - uPrev;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ case STAMTYPE_BOOL:
+ case STAMTYPE_BOOL_RESET:
+ {
+ bool fPrev = pNode->Data.f;
+ pNode->Data.f = *(bool *)pvSample;
+ iDelta = pNode->Data.f - fPrev;
+ if (iDelta || pNode->i64Delta)
+ {
+ pNode->i64Delta = iDelta;
+ pNode->enmState = kDbgGuiStatsNodeState_kRefresh;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%d\n", enmType));
+ break;
+ }
+ }
+}
+
+
+/*static*/ ssize_t
+VBoxDbgStatsModel::getNodePath(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
+{
+ ssize_t off;
+ if (!pNode->pParent)
+ {
+ /* root - don't add it's slash! */
+ AssertReturn(cch >= 1, -1);
+ off = 0;
+ *psz = '\0';
+ }
+ else
+ {
+ cch -= pNode->cchName + 1;
+ AssertReturn(cch > 0, -1);
+ off = getNodePath(pNode->pParent, psz, cch);
+ if (off >= 0)
+ {
+ psz[off++] = '/';
+ memcpy(&psz[off], pNode->pszName, pNode->cchName + 1);
+ off += pNode->cchName;
+ }
+ }
+ return off;
+}
+
+
+/*static*/ char *
+VBoxDbgStatsModel::getNodePath2(PCDBGGUISTATSNODE pNode, char *psz, ssize_t cch)
+{
+ if (VBoxDbgStatsModel::getNodePath(pNode, psz, cch) < 0)
+ return NULL;
+ return psz;
+}
+
+
+
+/*static*/ bool
+VBoxDbgStatsModel::isNodeAncestorOf(PCDBGGUISTATSNODE pAncestor, PCDBGGUISTATSNODE pDescendant)
+{
+ while (pDescendant)
+ {
+ pDescendant = pDescendant->pParent;
+ if (pDescendant == pAncestor)
+ return true;
+ }
+ return false;
+}
+
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::nextNode(PDBGGUISTATSNODE pNode)
+{
+ if (!pNode)
+ return NULL;
+
+ /* descend to children. */
+ if (pNode->cChildren)
+ return pNode->papChildren[0];
+
+ PDBGGUISTATSNODE pParent = pNode->pParent;
+ if (!pParent)
+ return NULL;
+
+ /* next sibling. */
+ if (pNode->iSelf + 1 < pNode->pParent->cChildren)
+ return pParent->papChildren[pNode->iSelf + 1];
+
+ /* ascend and advanced to a parent's sibiling. */
+ for (;;)
+ {
+ uint32_t iSelf = pParent->iSelf;
+ pParent = pParent->pParent;
+ if (!pParent)
+ return NULL;
+ if (iSelf + 1 < pParent->cChildren)
+ return pParent->papChildren[iSelf + 1];
+ }
+}
+
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::nextDataNode(PDBGGUISTATSNODE pNode)
+{
+ do
+ pNode = nextNode(pNode);
+ while ( pNode
+ && pNode->enmType == STAMTYPE_INVALID);
+ return pNode;
+}
+
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::prevNode(PDBGGUISTATSNODE pNode)
+{
+ if (!pNode)
+ return NULL;
+ PDBGGUISTATSNODE pParent = pNode->pParent;
+ if (!pParent)
+ return NULL;
+
+ /* previous sibling's latest descendant (better expression anyone?). */
+ if (pNode->iSelf > 0)
+ {
+ pNode = pParent->papChildren[pNode->iSelf - 1];
+ while (pNode->cChildren)
+ pNode = pNode->papChildren[pNode->cChildren - 1];
+ return pNode;
+ }
+
+ /* ascend to the parent. */
+ return pParent;
+}
+
+
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::prevDataNode(PDBGGUISTATSNODE pNode)
+{
+ do
+ pNode = prevNode(pNode);
+ while ( pNode
+ && pNode->enmType == STAMTYPE_INVALID);
+ return pNode;
+}
+
+
+#if 0
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::createNewTree(IMachineDebugger *a_pIMachineDebugger)
+{
+ /** @todo */
+ return NULL;
+}
+#endif
+
+
+#if 0
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::createNewTree(const char *pszFilename)
+{
+ /** @todo */
+ return NULL;
+}
+#endif
+
+
+#if 0
+/*static*/ PDBGGUISTATSNODE
+VBoxDbgStatsModel::createDiffTree(PDBGGUISTATSNODE pTree1, PDBGGUISTATSNODE pTree2)
+{
+ /** @todo */
+ return NULL;
+}
+#endif
+
+
+PDBGGUISTATSNODE
+VBoxDbgStatsModel::updateCallbackHandleOutOfOrder(const char *pszName)
+{
+#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ char szStrict[1024];
+#endif
+
+ /*
+ * We might be inserting a new node between pPrev and pNode
+ * or we might be removing one or more nodes. Either case is
+ * handled in the same rough way.
+ *
+ * Might consider optimizing insertion at some later point since this
+ * is a normal occurrence (dynamic statistics in PATM, IOM, MM, ++).
+ */
+ Assert(pszName[0] == '/');
+ Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
+
+ /*
+ * Start with the current parent node and look for a common ancestor
+ * hoping that this is faster than going from the root (saves lookup).
+ */
+ PDBGGUISTATSNODE pNode = m_pUpdateParent->papChildren[m_iUpdateChild];
+ PDBGGUISTATSNODE const pPrev = prevDataNode(pNode);
+ AssertMsg(strcmp(pszName, getNodePath2(pNode, szStrict, sizeof(szStrict))), ("%s\n", szStrict));
+ AssertMsg(!pPrev || strcmp(pszName, getNodePath2(pPrev, szStrict, sizeof(szStrict))), ("%s\n", szStrict));
+ Log(("updateCallbackHandleOutOfOrder: pszName='%s' m_szUpdateParent='%s' m_cchUpdateParent=%u pNode='%s'\n",
+ pszName, m_szUpdateParent, m_cchUpdateParent, getNodePath2(pNode, szStrict, sizeof(szStrict))));
+
+ pNode = pNode->pParent;
+ while (pNode != m_pRoot)
+ {
+ if (!strncmp(pszName, m_szUpdateParent, m_cchUpdateParent))
+ break;
+ Assert(m_cchUpdateParent > pNode->cchName);
+ m_cchUpdateParent -= pNode->cchName + 1;
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+ Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u, removed '/%s' (%u)\n", m_szUpdateParent, m_cchUpdateParent, pNode->pszName, __LINE__));
+ pNode = pNode->pParent;
+ }
+ Assert(m_szUpdateParent[m_cchUpdateParent - 1] == '/');
+
+ /*
+ * Descend until we've found/created the node pszName indicates,
+ * modifying m_szUpdateParent as we go along.
+ */
+ while (pszName[m_cchUpdateParent - 1] == '/')
+ {
+ /* Find the end of this component. */
+ const char * const pszSubName = &pszName[m_cchUpdateParent];
+ const char *pszEnd = strchr(pszSubName, '/');
+ if (!pszEnd)
+ pszEnd = strchr(pszSubName, '\0');
+ size_t cchSubName = pszEnd - pszSubName;
+
+ /* Add the name to the path. */
+ memcpy(&m_szUpdateParent[m_cchUpdateParent], pszSubName, cchSubName);
+ m_cchUpdateParent += cchSubName;
+ m_szUpdateParent[m_cchUpdateParent++] = '/';
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+ Assert(m_cchUpdateParent < sizeof(m_szUpdateParent));
+ Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
+
+ if (!pNode->cChildren)
+ {
+ /* first child */
+ pNode = createAndInsert(pNode, pszSubName, cchSubName, 0);
+ AssertReturn(pNode, NULL);
+ }
+ else
+ {
+ /* binary search. */
+ int32_t iStart = 0;
+ int32_t iLast = pNode->cChildren - 1;
+ for (;;)
+ {
+ int32_t i = iStart + (iLast + 1 - iStart) / 2;
+ int iDiff;
+ size_t const cchCompare = RT_MIN(pNode->papChildren[i]->cchName, cchSubName);
+ iDiff = memcmp(pszSubName, pNode->papChildren[i]->pszName, cchCompare);
+ if (!iDiff)
+ {
+ iDiff = cchSubName == cchCompare ? 0 : cchSubName > cchCompare ? 1 : -1;
+ /* For cases when exisiting node name is same as new node name with additional characters. */
+ if (!iDiff)
+ iDiff = cchSubName == pNode->papChildren[i]->cchName ? 0 : cchSubName > pNode->papChildren[i]->cchName ? 1 : -1;
+ }
+ if (iDiff > 0)
+ {
+ iStart = i + 1;
+ if (iStart > iLast)
+ {
+ pNode = createAndInsert(pNode, pszSubName, cchSubName, iStart);
+ AssertReturn(pNode, NULL);
+ break;
+ }
+ }
+ else if (iDiff < 0)
+ {
+ iLast = i - 1;
+ if (iLast < iStart)
+ {
+ pNode = createAndInsert(pNode, pszSubName, cchSubName, i);
+ AssertReturn(pNode, NULL);
+ break;
+ }
+ }
+ else
+ {
+ pNode = pNode->papChildren[i];
+ break;
+ }
+ }
+ }
+ }
+ Assert( !memcmp(pszName, m_szUpdateParent, m_cchUpdateParent - 2)
+ && pszName[m_cchUpdateParent - 1] == '\0');
+
+ /*
+ * Remove all the nodes between pNode and pPrev but keep all
+ * of pNode's ancestors (or it'll get orphaned).
+ */
+ PDBGGUISTATSNODE pCur = prevNode(pNode);
+ while (pCur != pPrev)
+ {
+ PDBGGUISTATSNODE pAdv = prevNode(pCur); Assert(pAdv || !pPrev);
+ if (!isNodeAncestorOf(pCur, pNode))
+ {
+ Assert(pCur != m_pRoot);
+ removeAndDestroy(pCur);
+ }
+ pCur = pAdv;
+ }
+
+ /*
+ * Remove the data from all ancestors of pNode that it doesn't
+ * share them pPrev.
+ */
+ if (pPrev)
+ {
+ pCur = pNode->pParent;
+ while (!isNodeAncestorOf(pCur, pPrev))
+ {
+ resetNode(pNode);
+ pCur = pCur->pParent;
+ }
+ }
+
+ /*
+ * Finally, adjust the globals (szUpdateParent is one level too deep).
+ */
+ Assert(m_cchUpdateParent > pNode->cchName + 1);
+ m_cchUpdateParent -= pNode->cchName + 1;
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+ m_pUpdateParent = pNode->pParent;
+ m_iUpdateChild = pNode->iSelf;
+ Log2(("updateCallbackHandleOutOfOrder: m_szUpdateParent='%s' m_cchUpdateParent=%u (%u)\n", m_szUpdateParent, m_cchUpdateParent, __LINE__));
+
+ return pNode;
+}
+
+
+PDBGGUISTATSNODE
+VBoxDbgStatsModel::updateCallbackHandleTail(const char *pszName)
+{
+ /*
+ * Insert it at the end of the tree.
+ *
+ * Do the same as we're doing down in createNewTreeCallback, walk from the
+ * root and create whatever we need.
+ */
+ AssertReturn(*pszName == '/' && pszName[1] != '/', NULL);
+ PDBGGUISTATSNODE pNode = m_pRoot;
+ const char *pszCur = pszName + 1;
+ while (*pszCur)
+ {
+ /* Find the end of this component. */
+ const char *pszNext = strchr(pszCur, '/');
+ if (!pszNext)
+ pszNext = strchr(pszCur, '\0');
+ size_t cchCur = pszNext - pszCur;
+
+ /* Create it if it doesn't exist (it will be last if it exists). */
+ if ( !pNode->cChildren
+ || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
+ || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
+ {
+ pNode = createAndInsert(pNode, pszCur, pszNext - pszCur, pNode->cChildren);
+ AssertReturn(pNode, NULL);
+ }
+ else
+ pNode = pNode->papChildren[pNode->cChildren - 1];
+
+ /* Advance */
+ pszCur = *pszNext ? pszNext + 1 : pszNext;
+ }
+
+ return pNode;
+}
+
+
+void
+VBoxDbgStatsModel::updateCallbackAdvance(PDBGGUISTATSNODE pNode)
+{
+ /*
+ * Advance to the next node with data.
+ *
+ * ASSUMES a leaf *must* have data and again we're ASSUMING the sorting
+ * on slash separated sub-strings.
+ */
+ if (m_iUpdateChild != UINT32_MAX)
+ {
+#ifdef VBOX_STRICT
+ PDBGGUISTATSNODE const pCorrectNext = nextDataNode(pNode);
+#endif
+ PDBGGUISTATSNODE pParent = pNode->pParent;
+ if (pNode->cChildren)
+ {
+ /* descend to the first child. */
+ Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
+ memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
+ m_cchUpdateParent += pNode->cchName;
+ m_szUpdateParent[m_cchUpdateParent++] = '/';
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+
+ pNode = pNode->papChildren[0];
+ }
+ else if (pNode->iSelf + 1 < pParent->cChildren)
+ {
+ /* next sibling or one if its descendants. */
+ Assert(m_pUpdateParent == pParent);
+ pNode = pParent->papChildren[pNode->iSelf + 1];
+ }
+ else
+ {
+ /* move up and down- / on-wards */
+ for (;;)
+ {
+ /* ascend */
+ pNode = pParent;
+ pParent = pParent->pParent;
+ if (!pParent)
+ {
+ Assert(pNode == m_pRoot);
+ m_iUpdateChild = UINT32_MAX;
+ m_szUpdateParent[0] = '\0';
+ m_cchUpdateParent = 0;
+ m_pUpdateParent = NULL;
+ break;
+ }
+ Assert(m_cchUpdateParent > pNode->cchName + 1);
+ m_cchUpdateParent -= pNode->cchName + 1;
+
+ /* try advance */
+ if (pNode->iSelf + 1 < pParent->cChildren)
+ {
+ pNode = pParent->papChildren[pNode->iSelf + 1];
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+ break;
+ }
+ }
+ }
+
+ /* descend to a node containing data and finalize the globals. (ASSUMES leaf has data.) */
+ if (m_iUpdateChild != UINT32_MAX)
+ {
+ while ( pNode->enmType == STAMTYPE_INVALID
+ && pNode->cChildren > 0)
+ {
+ Assert(pNode->enmState == kDbgGuiStatsNodeState_kVisible);
+
+ Assert(m_cchUpdateParent + pNode->cchName + 2 < sizeof(m_szUpdateParent));
+ memcpy(&m_szUpdateParent[m_cchUpdateParent], pNode->pszName, pNode->cchName);
+ m_cchUpdateParent += pNode->cchName;
+ m_szUpdateParent[m_cchUpdateParent++] = '/';
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+
+ pNode = pNode->papChildren[0];
+ }
+ Assert(pNode->enmType != STAMTYPE_INVALID);
+ m_iUpdateChild = pNode->iSelf;
+ m_pUpdateParent = pNode->pParent;
+ Assert(pNode == pCorrectNext);
+ }
+ }
+ /* else: we're at the end */
+}
+
+
+/*static*/ DECLCALLBACK(int)
+VBoxDbgStatsModel::updateCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
+ const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
+{
+ VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser;
+ Log3(("updateCallback: %s\n", pszName));
+ RT_NOREF(enmUnit);
+
+ /*
+ * Skip the ones which shouldn't be visible in the GUI.
+ */
+ if (enmVisibility == STAMVISIBILITY_NOT_GUI)
+ return 0;
+
+ /*
+ * The default assumption is that nothing has changed.
+ * For now we'll reset the model when ever something changes.
+ */
+ PDBGGUISTATSNODE pNode;
+ if (pThis->m_iUpdateChild != UINT32_MAX)
+ {
+ pNode = pThis->m_pUpdateParent->papChildren[pThis->m_iUpdateChild];
+ if ( !strncmp(pszName, pThis->m_szUpdateParent, pThis->m_cchUpdateParent)
+ && !strcmp(pszName + pThis->m_cchUpdateParent, pNode->pszName))
+ /* got it! */;
+ else
+ {
+ /* insert/remove */
+ pNode = pThis->updateCallbackHandleOutOfOrder(pszName);
+ if (!pNode)
+ return VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ /* append */
+ pNode = pThis->updateCallbackHandleTail(pszName);
+ if (!pNode)
+ return VERR_NO_MEMORY;
+ }
+
+ /*
+ * Perform the update and advance to the next one.
+ */
+ updateNode(pNode, enmType, pvSample, pszUnit, pszDesc);
+ pThis->updateCallbackAdvance(pNode);
+
+ return VINF_SUCCESS;
+}
+
+
+bool
+VBoxDbgStatsModel::updatePrepare(void)
+{
+ /*
+ * Find the first child with data and set it up as the 'next'
+ * node to be updated.
+ */
+ Assert(m_pRoot);
+ Assert(m_pRoot->enmType == STAMTYPE_INVALID);
+ PDBGGUISTATSNODE pFirst = nextDataNode(m_pRoot);
+ if (pFirst)
+ {
+ m_iUpdateChild = pFirst->iSelf;
+ m_pUpdateParent = pFirst->pParent; Assert(m_pUpdateParent);
+ m_cchUpdateParent = getNodePath(m_pUpdateParent, m_szUpdateParent, sizeof(m_szUpdateParent) - 1);
+ AssertReturn(m_cchUpdateParent >= 1, false);
+ m_szUpdateParent[m_cchUpdateParent++] = '/';
+ m_szUpdateParent[m_cchUpdateParent] = '\0';
+ }
+ else
+ {
+ m_iUpdateChild = UINT32_MAX;
+ m_pUpdateParent = NULL;
+ m_szUpdateParent[0] = '\0';
+ m_cchUpdateParent = 0;
+ }
+
+ /*
+ * Set the flag and signal possible layout change.
+ */
+ m_fUpdateInsertRemove = false;
+ /* emit layoutAboutToBeChanged(); - debug this, it gets stuck... */
+ return true;
+}
+
+
+bool
+VBoxDbgStatsModel::updateDone(bool a_fSuccess)
+{
+ /*
+ * Remove any nodes following the last in the update (unless the update failed).
+ */
+ if ( a_fSuccess
+ && m_iUpdateChild != UINT32_MAX)
+ {
+ PDBGGUISTATSNODE const pLast = prevDataNode(m_pUpdateParent->papChildren[m_iUpdateChild]);
+ if (!pLast)
+ {
+ /* nuking the whole tree. */
+ setRootNode(createRootNode());
+ m_fUpdateInsertRemove = true;
+ }
+ else
+ {
+ PDBGGUISTATSNODE pNode;
+ while ((pNode = nextNode(pLast)))
+ {
+ Assert(pNode != m_pRoot);
+ removeAndDestroy(pNode);
+ }
+ }
+ }
+
+ /*
+ * We're done making layout changes (if I understood it correctly), so,
+ * signal this and then see what to do next. If we did too many removals
+ * we'll just reset the whole shebang.
+ */
+ if (m_fUpdateInsertRemove)
+ {
+ /* emit layoutChanged(); - hrmpf, doesn't work reliably... */
+ beginResetModel();
+ endResetModel();
+ }
+ else
+ {
+ /*
+ * Send dataChanged events.
+ *
+ * We do this here instead of from the updateCallback because it reduces
+ * the clutter in that method and allow us to emit bulk signals in an
+ * easier way because we can traverse the tree in a different fashion.
+ */
+ DBGGUISTATSSTACK Stack;
+ Stack.a[0].pNode = m_pRoot;
+ Stack.a[0].iChild = -1;
+ Stack.iTop = 0;
+
+ while (Stack.iTop >= 0)
+ {
+ /* get top element */
+ PDBGGUISTATSNODE pNode = Stack.a[Stack.iTop].pNode;
+ uint32_t iChild = ++Stack.a[Stack.iTop].iChild;
+ if (iChild < pNode->cChildren)
+ {
+ /* push */
+ Stack.iTop++;
+ Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
+ Stack.a[Stack.iTop].pNode = pNode->papChildren[iChild];
+ Stack.a[Stack.iTop].iChild = 0;
+ }
+ else
+ {
+ /* pop */
+ Stack.iTop--;
+
+ /* do the actual work. */
+ iChild = 0;
+ while (iChild < pNode->cChildren)
+ {
+ /* skip to the first needing updating. */
+ while ( iChild < pNode->cChildren
+ && pNode->papChildren[iChild]->enmState != kDbgGuiStatsNodeState_kRefresh)
+ iChild++;
+ if (iChild >= pNode->cChildren)
+ break;
+ QModelIndex TopLeft = createIndex(iChild, 0, pNode->papChildren[iChild]);
+ pNode->papChildren[iChild]->enmState = kDbgGuiStatsNodeState_kVisible;
+
+ /* any subsequent nodes that also needs refreshing? */
+ if ( ++iChild < pNode->cChildren
+ && pNode->papChildren[iChild]->enmState == kDbgGuiStatsNodeState_kRefresh)
+ {
+ do pNode->papChildren[iChild]->enmState = kDbgGuiStatsNodeState_kVisible;
+ while ( ++iChild < pNode->cChildren
+ && pNode->papChildren[iChild]->enmState == kDbgGuiStatsNodeState_kRefresh);
+ QModelIndex BottomRight = createIndex(iChild - 1, DBGGUI_STATS_COLUMNS - 1, pNode->papChildren[iChild - 1]);
+
+ /* emit the refresh signal */
+ emit dataChanged(TopLeft, BottomRight);
+ }
+ else
+ {
+ /* emit the refresh signal */
+ emit dataChanged(TopLeft, TopLeft);
+ }
+ }
+ }
+ }
+ /* emit layoutChanged(); - hrmpf, doesn't work reliably... */
+ }
+
+ return m_fUpdateInsertRemove;
+}
+
+
+bool
+VBoxDbgStatsModel::updateStatsByPattern(const QString &a_rPatStr)
+{
+ /* stub */
+ NOREF(a_rPatStr);
+ return false;
+}
+
+
+void
+VBoxDbgStatsModel::updateStatsByIndex(QModelIndex const &a_rIndex)
+{
+ /** @todo implement this based on updateStatsByPattern. */
+ NOREF(a_rIndex);
+}
+
+
+void
+VBoxDbgStatsModel::resetStatsByPattern(QString const &a_rPatStr)
+{
+ /* stub */
+ NOREF(a_rPatStr);
+}
+
+
+void
+VBoxDbgStatsModel::resetStatsByIndex(QModelIndex const &a_rIndex, bool fSubTree /*= true*/)
+{
+ PCDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
+ if (pNode == m_pRoot || !a_rIndex.isValid())
+ {
+ if (fSubTree)
+ {
+ /* everything from the root down. */
+ resetStatsByPattern(QString());
+ }
+ }
+ else if (pNode)
+ {
+ /* the node pattern. */
+ char szPat[1024+1024+4];
+ ssize_t cch = getNodePath(pNode, szPat, 1024);
+ AssertReturnVoid(cch >= 0);
+
+ /* the sub-tree pattern. */
+ if (fSubTree && pNode->cChildren)
+ {
+ char *psz = &szPat[cch];
+ *psz++ = '|';
+ memcpy(psz, szPat, cch);
+ psz += cch;
+ *psz++ = '/';
+ *psz++ = '*';
+ *psz++ = '\0';
+ }
+
+ resetStatsByPattern(szPat);
+ }
+}
+
+
+void
+VBoxDbgStatsModel::iterateStatsByPattern(QString const &a_rPatStr, VBoxDbgStatsModel::FNITERATOR *a_pfnCallback, void *a_pvUser,
+ bool a_fMatchChildren /*= true*/)
+{
+ const QByteArray &PatBytes = a_rPatStr.toUtf8();
+ const char * const pszPattern = PatBytes.constData();
+ size_t const cchPattern = strlen(pszPattern);
+
+ DBGGUISTATSSTACK Stack;
+ Stack.a[0].pNode = m_pRoot;
+ Stack.a[0].iChild = 0;
+ Stack.a[0].cchName = 0;
+ Stack.iTop = 0;
+
+ char szName[1024];
+ szName[0] = '\0';
+
+ while (Stack.iTop >= 0)
+ {
+ /* get top element */
+ PDBGGUISTATSNODE const pNode = Stack.a[Stack.iTop].pNode;
+ uint16_t cchName = Stack.a[Stack.iTop].cchName;
+ uint32_t const iChild = Stack.a[Stack.iTop].iChild++;
+ if (iChild < pNode->cChildren)
+ {
+ PDBGGUISTATSNODE pChild = pNode->papChildren[iChild];
+
+ /* Build the name and match the pattern. */
+ Assert(cchName + 1 + pChild->cchName < sizeof(szName));
+ szName[cchName++] = '/';
+ memcpy(&szName[cchName], pChild->pszName, pChild->cchName);
+ cchName += (uint16_t)pChild->cchName;
+ szName[cchName] = '\0';
+
+ if (RTStrSimplePatternMultiMatch(pszPattern, cchPattern, szName, cchName, NULL))
+ {
+ /* Do callback. */
+ QModelIndex const Index = createIndex(iChild, 0, pChild);
+ if (!a_pfnCallback(pChild, Index, szName, a_pvUser))
+ return;
+ if (!a_fMatchChildren)
+ continue;
+ }
+
+ /* push */
+ Stack.iTop++;
+ Assert(Stack.iTop < (int32_t)RT_ELEMENTS(Stack.a));
+ Stack.a[Stack.iTop].pNode = pChild;
+ Stack.a[Stack.iTop].iChild = 0;
+ Stack.a[Stack.iTop].cchName = cchName;
+ }
+ else
+ {
+ /* pop */
+ Stack.iTop--;
+ }
+ }
+}
+
+
+QModelIndex
+VBoxDbgStatsModel::getRootIndex(void) const
+{
+ if (!m_pRoot)
+ return QModelIndex();
+ return createIndex(0, 0, m_pRoot);
+}
+
+
+void
+VBoxDbgStatsModel::setRootNode(PDBGGUISTATSNODE a_pRoot)
+{
+ PDBGGUISTATSNODE pOldTree = m_pRoot;
+ m_pRoot = a_pRoot;
+ destroyTree(pOldTree);
+ beginResetModel();
+ endResetModel();
+}
+
+
+Qt::ItemFlags
+VBoxDbgStatsModel::flags(const QModelIndex &a_rIndex) const
+{
+ Qt::ItemFlags fFlags = QAbstractItemModel::flags(a_rIndex);
+ return fFlags;
+}
+
+
+int
+VBoxDbgStatsModel::columnCount(const QModelIndex &a_rParent) const
+{
+ NOREF(a_rParent);
+ return DBGGUI_STATS_COLUMNS;
+}
+
+
+int
+VBoxDbgStatsModel::rowCount(const QModelIndex &a_rParent) const
+{
+ PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
+ return pParent ? pParent->cChildren : 1 /* root */;
+}
+
+
+bool
+VBoxDbgStatsModel::hasChildren(const QModelIndex &a_rParent) const
+{
+ PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
+ return pParent ? pParent->cChildren > 0 : true /* root */;
+}
+
+
+QModelIndex
+VBoxDbgStatsModel::index(int iRow, int iColumn, const QModelIndex &a_rParent) const
+{
+ PDBGGUISTATSNODE pParent = nodeFromIndex(a_rParent);
+ if (!pParent)
+ {
+ if ( a_rParent.isValid()
+ || iRow
+ || (unsigned)iColumn < DBGGUI_STATS_COLUMNS)
+ {
+ Assert(!a_rParent.isValid());
+ Assert(!iRow);
+ Assert((unsigned)iColumn < DBGGUI_STATS_COLUMNS);
+ return QModelIndex();
+ }
+
+ /* root */
+ return createIndex(0, iColumn, m_pRoot);
+ }
+ if ((unsigned)iRow >= pParent->cChildren)
+ {
+ Log(("index: iRow=%d >= cChildren=%u (iColumn=%d)\n", iRow, (unsigned)pParent->cChildren, iColumn));
+ return QModelIndex(); /* bug? */
+ }
+ if ((unsigned)iColumn >= DBGGUI_STATS_COLUMNS)
+ {
+ Log(("index: iColumn=%d (iRow=%d)\n", iColumn, iRow));
+ return QModelIndex(); /* bug? */
+ }
+ PDBGGUISTATSNODE pChild = pParent->papChildren[iRow];
+ return createIndex(iRow, iColumn, pChild);
+}
+
+
+QModelIndex
+VBoxDbgStatsModel::parent(const QModelIndex &a_rChild) const
+{
+ PDBGGUISTATSNODE pChild = nodeFromIndex(a_rChild);
+ if (!pChild)
+ {
+ Log(("parent: invalid child\n"));
+ return QModelIndex(); /* bug */
+ }
+ PDBGGUISTATSNODE pParent = pChild->pParent;
+ if (!pParent)
+ return QModelIndex(); /* ultimate root */
+
+ return createIndex(pParent->iSelf, 0, pParent);
+}
+
+
+QVariant
+VBoxDbgStatsModel::headerData(int a_iSection, Qt::Orientation a_eOrientation, int a_eRole) const
+{
+ if ( a_eOrientation == Qt::Horizontal
+ && a_eRole == Qt::DisplayRole)
+ switch (a_iSection)
+ {
+ case 0: return tr("Name");
+ case 1: return tr("Unit");
+ case 2: return tr("Value/Times");
+ case 3: return tr("Min");
+ case 4: return tr("Average");
+ case 5: return tr("Max");
+ case 6: return tr("Total");
+ case 7: return tr("dInt");
+ case 8: return tr("Description");
+ default:
+ AssertCompile(DBGGUI_STATS_COLUMNS == 9);
+ return QVariant(); /* bug */
+ }
+ else if ( a_eOrientation == Qt::Horizontal
+ && a_eRole == Qt::TextAlignmentRole)
+ switch (a_iSection)
+ {
+ case 0:
+ case 1:
+ return QVariant();
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (int)(Qt::AlignRight | Qt::AlignVCenter);
+ case 8:
+ return QVariant();
+ default:
+ AssertCompile(DBGGUI_STATS_COLUMNS == 9);
+ return QVariant(); /* bug */
+ }
+
+ return QVariant();
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strUnit(PCDBGGUISTATSNODE pNode)
+{
+ return pNode->pszUnit;
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strValueTimes(PCDBGGUISTATSNODE pNode)
+{
+ char sz[128];
+
+ switch (pNode->enmType)
+ {
+ case STAMTYPE_COUNTER:
+ return formatNumber(sz, pNode->Data.Counter.c);
+
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ if (!pNode->Data.Profile.cPeriods)
+ return "0";
+ return formatNumber(sz, pNode->Data.Profile.cPeriods);
+
+ case STAMTYPE_RATIO_U32:
+ case STAMTYPE_RATIO_U32_RESET:
+ {
+ char szTmp[64];
+ char *psz = formatNumber(szTmp, pNode->Data.RatioU32.u32A);
+ size_t off = strlen(psz);
+ memcpy(sz, psz, off);
+ sz[off++] = ':';
+ strcpy(&sz[off], formatNumber(szTmp, pNode->Data.RatioU32.u32B));
+ return sz;
+ }
+
+ case STAMTYPE_CALLBACK:
+ return *pNode->Data.pStr;
+
+ case STAMTYPE_U8:
+ case STAMTYPE_U8_RESET:
+ return formatNumber(sz, pNode->Data.u8);
+
+ case STAMTYPE_X8:
+ case STAMTYPE_X8_RESET:
+ return formatHexNumber(sz, pNode->Data.u8, 2);
+
+ case STAMTYPE_U16:
+ case STAMTYPE_U16_RESET:
+ return formatNumber(sz, pNode->Data.u16);
+
+ case STAMTYPE_X16:
+ case STAMTYPE_X16_RESET:
+ return formatHexNumber(sz, pNode->Data.u16, 4);
+
+ case STAMTYPE_U32:
+ case STAMTYPE_U32_RESET:
+ return formatNumber(sz, pNode->Data.u32);
+
+ case STAMTYPE_X32:
+ case STAMTYPE_X32_RESET:
+ return formatHexNumber(sz, pNode->Data.u32, 8);
+
+ case STAMTYPE_U64:
+ case STAMTYPE_U64_RESET:
+ return formatNumber(sz, pNode->Data.u64);
+
+ case STAMTYPE_X64:
+ case STAMTYPE_X64_RESET:
+ return formatHexNumber(sz, pNode->Data.u64, 16);
+
+ case STAMTYPE_BOOL:
+ case STAMTYPE_BOOL_RESET:
+ return pNode->Data.f ? "true" : "false";
+
+ default:
+ AssertMsgFailed(("%d\n", pNode->enmType));
+ RT_FALL_THRU();
+ case STAMTYPE_INVALID:
+ return "";
+ }
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strMinValue(PCDBGGUISTATSNODE pNode)
+{
+ char sz[128];
+
+ switch (pNode->enmType)
+ {
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ if (!pNode->Data.Profile.cPeriods)
+ return "0";
+ return formatNumber(sz, pNode->Data.Profile.cTicksMin);
+ default:
+ return "";
+ }
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strAvgValue(PCDBGGUISTATSNODE pNode)
+{
+ char sz[128];
+
+ switch (pNode->enmType)
+ {
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ if (!pNode->Data.Profile.cPeriods)
+ return "0";
+ return formatNumber(sz, pNode->Data.Profile.cTicks / pNode->Data.Profile.cPeriods);
+ default:
+ return "";
+ }
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strMaxValue(PCDBGGUISTATSNODE pNode)
+{
+ char sz[128];
+
+ switch (pNode->enmType)
+ {
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ if (!pNode->Data.Profile.cPeriods)
+ return "0";
+ return formatNumber(sz, pNode->Data.Profile.cTicksMax);
+ default:
+ return "";
+ }
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strTotalValue(PCDBGGUISTATSNODE pNode)
+{
+ char sz[128];
+
+ switch (pNode->enmType)
+ {
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ if (!pNode->Data.Profile.cPeriods)
+ return "0";
+ return formatNumber(sz, pNode->Data.Profile.cTicks);
+ default:
+ return "";
+ }
+}
+
+
+/*static*/ QString
+VBoxDbgStatsModel::strDeltaValue(PCDBGGUISTATSNODE pNode)
+{
+ char sz[128];
+
+ switch (pNode->enmType)
+ {
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ if (!pNode->Data.Profile.cPeriods)
+ return "0";
+ RT_FALL_THRU();
+ case STAMTYPE_COUNTER:
+ case STAMTYPE_RATIO_U32:
+ case STAMTYPE_RATIO_U32_RESET:
+ case STAMTYPE_U8:
+ case STAMTYPE_U8_RESET:
+ case STAMTYPE_X8:
+ case STAMTYPE_X8_RESET:
+ case STAMTYPE_U16:
+ case STAMTYPE_U16_RESET:
+ case STAMTYPE_X16:
+ case STAMTYPE_X16_RESET:
+ case STAMTYPE_U32:
+ case STAMTYPE_U32_RESET:
+ case STAMTYPE_X32:
+ case STAMTYPE_X32_RESET:
+ case STAMTYPE_U64:
+ case STAMTYPE_U64_RESET:
+ case STAMTYPE_X64:
+ case STAMTYPE_X64_RESET:
+ case STAMTYPE_BOOL:
+ case STAMTYPE_BOOL_RESET:
+ return formatNumberSigned(sz, pNode->i64Delta);
+ default:
+ return "";
+ }
+}
+
+
+QVariant
+VBoxDbgStatsModel::data(const QModelIndex &a_rIndex, int a_eRole) const
+{
+ unsigned iCol = a_rIndex.column();
+ if (iCol >= DBGGUI_STATS_COLUMNS)
+ return QVariant();
+
+ if (a_eRole == Qt::DisplayRole)
+ {
+ PDBGGUISTATSNODE pNode = nodeFromIndex(a_rIndex);
+ if (!pNode)
+ return QVariant();
+
+ switch (iCol)
+ {
+ case 0:
+ return QString(pNode->pszName);
+ case 1:
+ return strUnit(pNode);
+ case 2:
+ return strValueTimes(pNode);
+ case 3:
+ return strMinValue(pNode);
+ case 4:
+ return strAvgValue(pNode);
+ case 5:
+ return strMaxValue(pNode);
+ case 6:
+ return strTotalValue(pNode);
+ case 7:
+ return strDeltaValue(pNode);
+ case 8:
+ return pNode->pDescStr ? QString(*pNode->pDescStr) : QString("");
+ default:
+ AssertCompile(DBGGUI_STATS_COLUMNS == 9);
+ return QVariant();
+ }
+ }
+ else if (a_eRole == Qt::TextAlignmentRole)
+ switch (iCol)
+ {
+ case 0:
+ case 1:
+ return QVariant();
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (int)(Qt::AlignRight | Qt::AlignVCenter);
+ case 8:
+ return QVariant();
+ default:
+ AssertCompile(DBGGUI_STATS_COLUMNS == 9);
+ return QVariant(); /* bug */
+ }
+ return QVariant();
+}
+
+
+/*static*/ void
+VBoxDbgStatsModel::stringifyNodeNoRecursion(PDBGGUISTATSNODE a_pNode, QString &a_rString)
+{
+ /*
+ * Get the path, padding it to 32-chars and add it to the string.
+ */
+ char szBuf[1024];
+ ssize_t off = getNodePath(a_pNode, szBuf, sizeof(szBuf) - 2);
+ AssertReturnVoid(off >= 0);
+ if (off < 32)
+ {
+ memset(&szBuf[off], ' ', 32 - off);
+ szBuf[32] = '\0';
+ off = 32;
+ }
+ szBuf[off++] = ' ';
+ szBuf[off] = '\0';
+ a_rString += szBuf;
+
+ /*
+ * The following is derived from stamR3PrintOne, except
+ * we print to szBuf, do no visibility checks and can skip
+ * the path bit.
+ */
+ switch (a_pNode->enmType)
+ {
+ case STAMTYPE_COUNTER:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.Counter.c, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_PROFILE:
+ case STAMTYPE_PROFILE_ADV:
+ {
+ uint64_t u64 = a_pNode->Data.Profile.cPeriods ? a_pNode->Data.Profile.cPeriods : 1;
+ RTStrPrintf(szBuf, sizeof(szBuf),
+ "%8llu %s (%12llu ticks, %7llu times, max %9llu, min %7lld)",
+ a_pNode->Data.Profile.cTicks / u64, a_pNode->pszUnit,
+ a_pNode->Data.Profile.cTicks, a_pNode->Data.Profile.cPeriods, a_pNode->Data.Profile.cTicksMax, a_pNode->Data.Profile.cTicksMin);
+ break;
+ }
+
+ case STAMTYPE_RATIO_U32:
+ case STAMTYPE_RATIO_U32_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf),
+ "%8u:%-8u %s",
+ a_pNode->Data.RatioU32.u32A, a_pNode->Data.RatioU32.u32B, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_CALLBACK:
+ if (a_pNode->Data.pStr)
+ a_rString += *a_pNode->Data.pStr;
+ RTStrPrintf(szBuf, sizeof(szBuf), " %s", a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_U8:
+ case STAMTYPE_U8_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_X8:
+ case STAMTYPE_X8_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_U16:
+ case STAMTYPE_U16_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_X16:
+ case STAMTYPE_X16_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_U32:
+ case STAMTYPE_U32_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_X32:
+ case STAMTYPE_X32_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_U64:
+ case STAMTYPE_U64_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_X64:
+ case STAMTYPE_X64_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, a_pNode->pszUnit);
+ break;
+
+ case STAMTYPE_BOOL:
+ case STAMTYPE_BOOL_RESET:
+ RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", a_pNode->pszUnit);
+ break;
+
+ default:
+ AssertMsgFailed(("enmType=%d\n", a_pNode->enmType));
+ return;
+ }
+
+ a_rString += szBuf;
+}
+
+
+/*static*/ void
+VBoxDbgStatsModel::stringifyNode(PDBGGUISTATSNODE a_pNode, QString &a_rString)
+{
+ /* this node (if it has data) */
+ if (a_pNode->enmType != STAMTYPE_INVALID)
+ {
+ if (!a_rString.isEmpty())
+ a_rString += "\n";
+ stringifyNodeNoRecursion(a_pNode, a_rString);
+ }
+
+ /* the children */
+ uint32_t const cChildren = a_pNode->cChildren;
+ for (uint32_t i = 0; i < cChildren; i++)
+ stringifyNode(a_pNode->papChildren[i], a_rString);
+}
+
+
+void
+VBoxDbgStatsModel::stringifyTree(QModelIndex &a_rRoot, QString &a_rString) const
+{
+ PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
+ if (pRoot)
+ stringifyNode(pRoot, a_rString);
+}
+
+
+void
+VBoxDbgStatsModel::copyTreeToClipboard(QModelIndex &a_rRoot) const
+{
+ QString String;
+ stringifyTree(a_rRoot, String);
+
+ QClipboard *pClipboard = QApplication::clipboard();
+ if (pClipboard)
+ pClipboard->setText(String, QClipboard::Clipboard);
+}
+
+
+/*static*/ void
+VBoxDbgStatsModel::logNode(PDBGGUISTATSNODE a_pNode, bool a_fReleaseLog)
+{
+ /* this node (if it has data) */
+ if (a_pNode->enmType != STAMTYPE_INVALID)
+ {
+ QString SelfStr;
+ stringifyNodeNoRecursion(a_pNode, SelfStr);
+ QByteArray SelfByteArray = SelfStr.toUtf8();
+ if (a_fReleaseLog)
+ RTLogRelPrintf("%s\n", SelfByteArray.constData());
+ else
+ RTLogPrintf("%s\n", SelfByteArray.constData());
+ }
+
+ /* the children */
+ uint32_t const cChildren = a_pNode->cChildren;
+ for (uint32_t i = 0; i < cChildren; i++)
+ logNode(a_pNode->papChildren[i], a_fReleaseLog);
+}
+
+
+void
+VBoxDbgStatsModel::logTree(QModelIndex &a_rRoot, bool a_fReleaseLog) const
+{
+ PDBGGUISTATSNODE pRoot = a_rRoot.isValid() ? nodeFromIndex(a_rRoot) : m_pRoot;
+ if (pRoot)
+ logNode(pRoot, a_fReleaseLog);
+}
+
+
+
+
+
+
+
+/*
+ *
+ * V B o x D b g S t a t s M o d e l V M
+ * V B o x D b g S t a t s M o d e l V M
+ * V B o x D b g S t a t s M o d e l V M
+ *
+ *
+ */
+
+
+VBoxDbgStatsModelVM::VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent, PCVMMR3VTABLE a_pVMM)
+ : VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui), m_pVMM(a_pVMM)
+{
+ /*
+ * Create a model containing the STAM entries matching the pattern.
+ * (The original idea was to get everything and rely on some hide/visible
+ * flag that it turned out didn't exist.)
+ */
+ PDBGGUISTATSNODE pTree = createNewTree(a_rPatStr);
+ setRootNode(pTree);
+}
+
+
+VBoxDbgStatsModelVM::~VBoxDbgStatsModelVM()
+{
+ /* nothing to do here. */
+}
+
+
+bool
+VBoxDbgStatsModelVM::updateStatsByPattern(const QString &a_rPatStr)
+{
+ /** @todo the way we update this stuff is independent of the source (XML, file, STAM), our only
+ * ASSUMPTION is that the input is strictly ordered by (fully slashed) name. So, all this stuff
+ * should really move up into the parent class. */
+ bool fRc = updatePrepare();
+ if (fRc)
+ {
+ int rc = stamEnum(a_rPatStr, updateCallback, this);
+ fRc = updateDone(RT_SUCCESS(rc));
+ }
+ return fRc;
+}
+
+
+void
+VBoxDbgStatsModelVM::resetStatsByPattern(QString const &a_rPatStr)
+{
+ stamReset(a_rPatStr);
+}
+
+
+/*static*/ DECLCALLBACK(int)
+VBoxDbgStatsModelVM::createNewTreeCallback(const char *pszName, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit,
+ const char *pszUnit, STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser)
+{
+ PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser;
+ Log3(("createNewTreeCallback: %s\n", pszName));
+ RT_NOREF(enmUnit);
+
+ /*
+ * Skip the ones which shouldn't be visible in the GUI.
+ */
+ if (enmVisibility == STAMVISIBILITY_NOT_GUI)
+ return 0;
+
+ /*
+ * Perform a mkdir -p like operation till we've walked / created the entire path down
+ * to the node specfied node. Remember the last node as that will be the one we will
+ * stuff the data into.
+ */
+ AssertReturn(*pszName == '/' && pszName[1] != '/', VERR_INTERNAL_ERROR);
+ PDBGGUISTATSNODE pNode = pRoot;
+ const char *pszCur = pszName + 1;
+ while (*pszCur)
+ {
+ /* find the end of this component. */
+ const char *pszNext = strchr(pszCur, '/');
+ if (!pszNext)
+ pszNext = strchr(pszCur, '\0');
+ size_t cchCur = pszNext - pszCur;
+
+ /* Create it if it doesn't exist (it will be last if it exists). */
+ if ( !pNode->cChildren
+ || strncmp(pNode->papChildren[pNode->cChildren - 1]->pszName, pszCur, cchCur)
+ || pNode->papChildren[pNode->cChildren - 1]->pszName[cchCur])
+ {
+ pNode = createAndInsertNode(pNode, pszCur, pszNext - pszCur, UINT32_MAX);
+ if (!pNode)
+ return VERR_NO_MEMORY;
+ }
+ else
+ pNode = pNode->papChildren[pNode->cChildren - 1];
+
+ /* Advance */
+ pszCur = *pszNext ? pszNext + 1 : pszNext;
+ }
+
+ /*
+ * Save the data.
+ */
+ return initNode(pNode, enmType, pvSample, pszUnit, pszDesc);
+}
+
+
+PDBGGUISTATSNODE
+VBoxDbgStatsModelVM::createNewTree(QString &a_rPatStr)
+{
+ PDBGGUISTATSNODE pRoot = createRootNode();
+ if (pRoot)
+ {
+ int rc = stamEnum(a_rPatStr, createNewTreeCallback, pRoot);
+ if (RT_SUCCESS(rc))
+ return pRoot;
+
+ /* failed, cleanup. */
+ destroyTree(pRoot);
+ }
+
+ return NULL;
+}
+
+
+
+
+
+
+
+
+/*
+ *
+ * V B o x D b g S t a t s V i e w
+ * V B o x D b g S t a t s V i e w
+ * V B o x D b g S t a t s V i e w
+ *
+ *
+ */
+
+
+VBoxDbgStatsView::VBoxDbgStatsView(VBoxDbgGui *a_pDbgGui, VBoxDbgStatsModel *a_pModel, VBoxDbgStats *a_pParent/* = NULL*/)
+ : QTreeView(a_pParent), VBoxDbgBase(a_pDbgGui), m_pModel(a_pModel), m_PatStr(), m_pParent(a_pParent),
+ m_pLeafMenu(NULL), m_pBranchMenu(NULL), m_pViewMenu(NULL), m_pCurMenu(NULL), m_CurIndex()
+
+{
+ /*
+ * Set the model and view defaults.
+ */
+ setRootIsDecorated(true);
+ setModel(m_pModel);
+ QModelIndex RootIdx = m_pModel->getRootIndex(); /* This should really be QModelIndex(), but Qt on darwin does wrong things then. */
+ setRootIndex(RootIdx);
+ setItemsExpandable(true);
+ setAlternatingRowColors(true);
+ setSelectionBehavior(SelectRows);
+ setSelectionMode(SingleSelection);
+ /// @todo sorting setSortingEnabled(true);
+
+ /*
+ * Create and setup the actions.
+ */
+ m_pExpandAct = new QAction("Expand Tree", this);
+ m_pCollapseAct = new QAction("Collapse Tree", this);
+ m_pRefreshAct = new QAction("&Refresh", this);
+ m_pResetAct = new QAction("Rese&t", this);
+ m_pCopyAct = new QAction("&Copy", this);
+ m_pToLogAct = new QAction("To &Log", this);
+ m_pToRelLogAct = new QAction("T&o Release Log", this);
+ m_pAdjColumns = new QAction("&Adjust Columns", this);
+
+ m_pCopyAct->setShortcut(QKeySequence::Copy);
+ m_pExpandAct->setShortcut(QKeySequence("Ctrl+E"));
+ m_pCollapseAct->setShortcut(QKeySequence("Ctrl+D"));
+ m_pRefreshAct->setShortcut(QKeySequence("Ctrl+R"));
+ m_pResetAct->setShortcut(QKeySequence("Alt+R"));
+ m_pToLogAct->setShortcut(QKeySequence("Ctrl+Z"));
+ m_pToRelLogAct->setShortcut(QKeySequence("Alt+Z"));
+ m_pAdjColumns->setShortcut(QKeySequence("Ctrl+A"));
+
+ addAction(m_pCopyAct);
+ addAction(m_pExpandAct);
+ addAction(m_pCollapseAct);
+ addAction(m_pRefreshAct);
+ addAction(m_pResetAct);
+ addAction(m_pToLogAct);
+ addAction(m_pToRelLogAct);
+ addAction(m_pAdjColumns);
+
+ connect(m_pExpandAct, SIGNAL(triggered(bool)), this, SLOT(actExpand()));
+ connect(m_pCollapseAct, SIGNAL(triggered(bool)), this, SLOT(actCollapse()));
+ connect(m_pRefreshAct, SIGNAL(triggered(bool)), this, SLOT(actRefresh()));
+ connect(m_pResetAct, SIGNAL(triggered(bool)), this, SLOT(actReset()));
+ connect(m_pCopyAct, SIGNAL(triggered(bool)), this, SLOT(actCopy()));
+ connect(m_pToLogAct, SIGNAL(triggered(bool)), this, SLOT(actToLog()));
+ connect(m_pToRelLogAct, SIGNAL(triggered(bool)), this, SLOT(actToRelLog()));
+ connect(m_pAdjColumns, SIGNAL(triggered(bool)), this, SLOT(actAdjColumns()));
+
+
+ /*
+ * Create the menus and populate them.
+ */
+ setContextMenuPolicy(Qt::DefaultContextMenu);
+
+ m_pLeafMenu = new QMenu();
+ m_pLeafMenu->addAction(m_pCopyAct);
+ m_pLeafMenu->addAction(m_pRefreshAct);
+ m_pLeafMenu->addAction(m_pResetAct);
+ m_pLeafMenu->addAction(m_pToLogAct);
+ m_pLeafMenu->addAction(m_pToRelLogAct);
+
+ m_pBranchMenu = new QMenu(this);
+ m_pBranchMenu->addAction(m_pCopyAct);
+ m_pBranchMenu->addAction(m_pRefreshAct);
+ m_pBranchMenu->addAction(m_pResetAct);
+ m_pBranchMenu->addAction(m_pToLogAct);
+ m_pBranchMenu->addAction(m_pToRelLogAct);
+ m_pBranchMenu->addSeparator();
+ m_pBranchMenu->addAction(m_pExpandAct);
+ m_pBranchMenu->addAction(m_pCollapseAct);
+
+ m_pViewMenu = new QMenu();
+ m_pViewMenu->addAction(m_pCopyAct);
+ m_pViewMenu->addAction(m_pRefreshAct);
+ m_pViewMenu->addAction(m_pResetAct);
+ m_pViewMenu->addAction(m_pToLogAct);
+ m_pViewMenu->addAction(m_pToRelLogAct);
+ m_pViewMenu->addSeparator();
+ m_pViewMenu->addAction(m_pExpandAct);
+ m_pViewMenu->addAction(m_pCollapseAct);
+ m_pViewMenu->addSeparator();
+ m_pViewMenu->addAction(m_pAdjColumns);
+
+ /* the header menu */
+ QHeaderView *pHdrView = header();
+ pHdrView->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(pHdrView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(headerContextMenuRequested(const QPoint &)));
+}
+
+
+VBoxDbgStatsView::~VBoxDbgStatsView()
+{
+ m_pParent = NULL;
+ m_pCurMenu = NULL;
+ m_CurIndex = QModelIndex();
+
+#define DELETE_IT(m) if (m) { delete m; m = NULL; } else do {} while (0)
+ DELETE_IT(m_pModel);
+
+ DELETE_IT(m_pLeafMenu);
+ DELETE_IT(m_pBranchMenu);
+ DELETE_IT(m_pViewMenu);
+
+ DELETE_IT(m_pExpandAct);
+ DELETE_IT(m_pCollapseAct);
+ DELETE_IT(m_pRefreshAct);
+ DELETE_IT(m_pResetAct);
+ DELETE_IT(m_pCopyAct);
+ DELETE_IT(m_pToLogAct);
+ DELETE_IT(m_pToRelLogAct);
+ DELETE_IT(m_pAdjColumns);
+#undef DELETE_IT
+}
+
+
+void
+VBoxDbgStatsView::updateStats(const QString &rPatStr)
+{
+ m_PatStr = rPatStr;
+ if (m_pModel->updateStatsByPattern(rPatStr))
+ setRootIndex(m_pModel->getRootIndex()); /* hack */
+}
+
+
+void
+VBoxDbgStatsView::resizeColumnsToContent()
+{
+ for (int i = 0; i <= 8; i++)
+ {
+ resizeColumnToContents(i);
+ /* Some extra room for distinguishing numbers better in Value, Min, Avg, Max, Total, dInt columns. */
+ if (i >= 2 && i <= 7)
+ setColumnWidth(i, columnWidth(i) + 10);
+ }
+}
+
+
+/*static*/ bool
+VBoxDbgStatsView::expandMatchingCallback(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex,
+ const char *pszFullName, void *pvUser)
+{
+ VBoxDbgStatsView *pThis = (VBoxDbgStatsView *)pvUser;
+
+ pThis->setExpanded(a_rIndex, true);
+
+ QModelIndex ParentIndex = pThis->m_pModel->parent(a_rIndex);
+ while (ParentIndex.isValid() && !pThis->isExpanded(ParentIndex))
+ {
+ pThis->setExpanded(ParentIndex, true);
+ ParentIndex = pThis->m_pModel->parent(ParentIndex);
+ }
+
+ RT_NOREF(pNode, pszFullName);
+ return true;
+}
+
+
+void
+VBoxDbgStatsView::expandMatching(const QString &rPatStr)
+{
+ m_pModel->iterateStatsByPattern(rPatStr, expandMatchingCallback, this);
+}
+
+
+void
+VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded)
+{
+ int cRows = m_pModel->rowCount(a_rIndex);
+ if (a_rIndex.model())
+ for (int i = 0; i < cRows; i++)
+ setSubTreeExpanded(a_rIndex.model()->index(i, 0, a_rIndex), a_fExpanded);
+ setExpanded(a_rIndex, a_fExpanded);
+}
+
+
+void
+VBoxDbgStatsView::contextMenuEvent(QContextMenuEvent *a_pEvt)
+{
+ /*
+ * Get the selected item.
+ * If it's a mouse event select the item under the cursor (if any).
+ */
+ QModelIndex Idx;
+ if (a_pEvt->reason() == QContextMenuEvent::Mouse)
+ {
+ Idx = indexAt(a_pEvt->pos());
+ if (Idx.isValid())
+ setCurrentIndex(Idx);
+ }
+ else
+ {
+ QModelIndexList SelIdx = selectedIndexes();
+ if (!SelIdx.isEmpty())
+ Idx = SelIdx.at(0);
+ }
+
+ /*
+ * Popup the corresponding menu.
+ */
+ QMenu *pMenu;
+ if (!Idx.isValid())
+ pMenu = m_pViewMenu;
+ else if (m_pModel->hasChildren(Idx))
+ pMenu = m_pBranchMenu;
+ else
+ pMenu = m_pLeafMenu;
+ if (pMenu)
+ {
+ m_pRefreshAct->setEnabled(!Idx.isValid() || Idx == m_pModel->getRootIndex());
+ m_CurIndex = Idx;
+ m_pCurMenu = pMenu;
+
+ pMenu->exec(a_pEvt->globalPos());
+
+ m_pCurMenu = NULL;
+ m_CurIndex = QModelIndex();
+ if (m_pRefreshAct)
+ m_pRefreshAct->setEnabled(true);
+ }
+ a_pEvt->accept();
+}
+
+
+void
+VBoxDbgStatsView::headerContextMenuRequested(const QPoint &a_rPos)
+{
+ /*
+ * Show the view menu.
+ */
+ if (m_pViewMenu)
+ {
+ m_pRefreshAct->setEnabled(true);
+ m_CurIndex = m_pModel->getRootIndex();
+ m_pCurMenu = m_pViewMenu;
+
+ m_pViewMenu->exec(header()->mapToGlobal(a_rPos));
+
+ m_pCurMenu = NULL;
+ m_CurIndex = QModelIndex();
+ if (m_pRefreshAct)
+ m_pRefreshAct->setEnabled(true);
+ }
+}
+
+
+void
+VBoxDbgStatsView::actExpand()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ if (Idx.isValid())
+ setSubTreeExpanded(Idx, true /* a_fExpanded */);
+}
+
+
+void
+VBoxDbgStatsView::actCollapse()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ if (Idx.isValid())
+ setSubTreeExpanded(Idx, false /* a_fExpanded */);
+}
+
+
+void
+VBoxDbgStatsView::actRefresh()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ if (!Idx.isValid() || Idx == m_pModel->getRootIndex())
+ {
+ if (m_pModel->updateStatsByPattern(m_PatStr))
+ setRootIndex(m_pModel->getRootIndex()); /* hack */
+ }
+ else
+ m_pModel->updateStatsByIndex(Idx);
+}
+
+
+void
+VBoxDbgStatsView::actReset()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ if (!Idx.isValid() || Idx == m_pModel->getRootIndex())
+ m_pModel->resetStatsByPattern(m_PatStr);
+ else
+ m_pModel->resetStatsByIndex(Idx);
+}
+
+
+void
+VBoxDbgStatsView::actCopy()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ m_pModel->copyTreeToClipboard(Idx);
+}
+
+
+void
+VBoxDbgStatsView::actToLog()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ m_pModel->logTree(Idx, false /* a_fReleaseLog */);
+}
+
+
+void
+VBoxDbgStatsView::actToRelLog()
+{
+ QModelIndex Idx = m_pCurMenu ? m_CurIndex : currentIndex();
+ m_pModel->logTree(Idx, true /* a_fReleaseLog */);
+}
+
+
+void
+VBoxDbgStatsView::actAdjColumns()
+{
+ resizeColumnsToContent();
+}
+
+
+
+
+
+
+/*
+ *
+ * V B o x D b g S t a t s
+ * V B o x D b g S t a t s
+ * V B o x D b g S t a t s
+ *
+ *
+ */
+
+
+VBoxDbgStats::VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszFilter /*= NULL*/, const char *pszExpand /*= NULL*/,
+ unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/)
+ : VBoxDbgBaseWindow(a_pDbgGui, pParent, "Statistics")
+ , m_PatStr(pszFilter), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL)
+{
+ /* Delete dialog on close: */
+ setAttribute(Qt::WA_DeleteOnClose);
+
+ /*
+ * On top, a horizontal box with the pattern field, buttons and refresh interval.
+ */
+ QHBoxLayout *pHLayout = new QHBoxLayout;
+
+ QLabel *pLabel = new QLabel(" Pattern ");
+ pHLayout->addWidget(pLabel);
+ pLabel->setMaximumSize(pLabel->sizeHint());
+ pLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+
+ m_pPatCB = new QComboBox();
+ m_pPatCB->setCompleter(0);
+ pHLayout->addWidget(m_pPatCB);
+ if (!m_PatStr.isEmpty())
+ m_pPatCB->addItem(m_PatStr);
+ m_pPatCB->setDuplicatesEnabled(false);
+ m_pPatCB->setEditable(true);
+ connect(m_pPatCB, SIGNAL(activated(const QString &)), this, SLOT(apply(const QString &)));
+
+ QPushButton *pPB = new QPushButton("&All");
+ pHLayout->addWidget(pPB);
+ pPB->setMaximumSize(pPB->sizeHint());
+ connect(pPB, SIGNAL(clicked()), this, SLOT(applyAll()));
+
+ pLabel = new QLabel(" Interval ");
+ pHLayout->addWidget(pLabel);
+ pLabel->setMaximumSize(pLabel->sizeHint());
+ pLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
+
+ QSpinBox *pSB = new QSpinBox();
+ pHLayout->addWidget(pSB);
+ pSB->setMinimum(0);
+ pSB->setMaximum(60);
+ pSB->setSingleStep(1);
+ pSB->setValue(uRefreshRate);
+ pSB->setSuffix(" s");
+ pSB->setWrapping(false);
+ pSB->setButtonSymbols(QSpinBox::PlusMinus);
+ pSB->setMaximumSize(pSB->sizeHint());
+ connect(pSB, SIGNAL(valueChanged(int)), this, SLOT(setRefresh(int)));
+
+ /*
+ * Create the tree view and setup the layout.
+ */
+ VBoxDbgStatsModelVM *pModel = new VBoxDbgStatsModelVM(a_pDbgGui, m_PatStr, NULL, a_pDbgGui->getVMMFunctionTable());
+ m_pView = new VBoxDbgStatsView(a_pDbgGui, pModel, this);
+
+ QWidget *pHBox = new QWidget;
+ pHBox->setLayout(pHLayout);
+
+ QVBoxLayout *pVLayout = new QVBoxLayout;
+ pVLayout->addWidget(pHBox);
+ pVLayout->addWidget(m_pView);
+ setLayout(pVLayout);
+
+ /*
+ * Resize the columns.
+ * Seems this has to be done with all nodes expanded.
+ */
+ m_pView->expandAll();
+ m_pView->resizeColumnsToContent();
+ m_pView->collapseAll();
+
+ if (pszExpand && *pszExpand)
+ m_pView->expandMatching(QString(pszExpand));
+
+ /*
+ * Create a refresh timer and start it.
+ */
+ m_pTimer = new QTimer(this);
+ connect(m_pTimer, SIGNAL(timeout()), this, SLOT(refresh()));
+ setRefresh(uRefreshRate);
+
+ /*
+ * And some shortcuts.
+ */
+ m_pFocusToPat = new QAction("", this);
+ m_pFocusToPat->setShortcut(QKeySequence("Ctrl+L"));
+ addAction(m_pFocusToPat);
+ connect(m_pFocusToPat, SIGNAL(triggered(bool)), this, SLOT(actFocusToPat()));
+}
+
+
+VBoxDbgStats::~VBoxDbgStats()
+{
+ if (m_pTimer)
+ {
+ delete m_pTimer;
+ m_pTimer = NULL;
+ }
+
+ if (m_pPatCB)
+ {
+ delete m_pPatCB;
+ m_pPatCB = NULL;
+ }
+
+ if (m_pView)
+ {
+ delete m_pView;
+ m_pView = NULL;
+ }
+}
+
+
+void
+VBoxDbgStats::closeEvent(QCloseEvent *a_pCloseEvt)
+{
+ a_pCloseEvt->accept();
+}
+
+
+void
+VBoxDbgStats::apply(const QString &Str)
+{
+ m_PatStr = Str;
+ refresh();
+}
+
+
+void
+VBoxDbgStats::applyAll()
+{
+ apply("");
+}
+
+
+
+void
+VBoxDbgStats::refresh()
+{
+ m_pView->updateStats(m_PatStr);
+}
+
+
+void
+VBoxDbgStats::setRefresh(int iRefresh)
+{
+ if ((unsigned)iRefresh != m_uRefreshRate)
+ {
+ if (!m_uRefreshRate || iRefresh)
+ m_pTimer->start(iRefresh * 1000);
+ else
+ m_pTimer->stop();
+ m_uRefreshRate = iRefresh;
+ }
+}
+
+
+void
+VBoxDbgStats::actFocusToPat()
+{
+ if (!m_pPatCB->hasFocus())
+ m_pPatCB->setFocus(Qt::ShortcutFocusReason);
+}
+
diff --git a/src/VBox/Debugger/VBoxDbgStatsQt.h b/src/VBox/Debugger/VBoxDbgStatsQt.h
new file mode 100644
index 00000000..18f73db0
--- /dev/null
+++ b/src/VBox/Debugger/VBoxDbgStatsQt.h
@@ -0,0 +1,272 @@
+/* $Id: VBoxDbgStatsQt.h $ */
+/** @file
+ * VBox Debugger GUI - Statistics.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#ifndef DEBUGGER_INCLUDED_SRC_VBoxDbgStatsQt_h
+#define DEBUGGER_INCLUDED_SRC_VBoxDbgStatsQt_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "VBoxDbgBase.h"
+
+#include <QTreeView>
+#include <QTimer>
+#include <QComboBox>
+#include <QMenu>
+
+class VBoxDbgStats;
+class VBoxDbgStatsModel;
+
+/** Pointer to a statistics sample. */
+typedef struct DBGGUISTATSNODE *PDBGGUISTATSNODE;
+/** Pointer to a const statistics sample. */
+typedef struct DBGGUISTATSNODE const *PCDBGGUISTATSNODE;
+
+
+/**
+ * The VM statistics tree view.
+ *
+ * A tree representation of the STAM statistics.
+ */
+class VBoxDbgStatsView : public QTreeView, public VBoxDbgBase
+{
+ Q_OBJECT;
+
+public:
+ /**
+ * Creates a VM statistics list view widget.
+ *
+ * @param a_pDbgGui Pointer to the debugger gui object.
+ * @param a_pModel The model. Will take ownership of this and delete it together
+ * with the view later
+ * @param a_pParent Parent widget.
+ */
+ VBoxDbgStatsView(VBoxDbgGui *a_pDbgGui, VBoxDbgStatsModel *a_pModel, VBoxDbgStats *a_pParent = NULL);
+
+ /** Destructor. */
+ virtual ~VBoxDbgStatsView();
+
+ /**
+ * Updates the view with current information from STAM.
+ * This will indirectly update the m_PatStr.
+ *
+ * @param rPatStr Selection pattern. NULL means everything, see STAM for further details.
+ */
+ void updateStats(const QString &rPatStr);
+
+ /**
+ * Resets the stats items matching the specified pattern.
+ * This pattern doesn't have to be the one used for update, thus m_PatStr isn't updated.
+ *
+ * @param rPatStr Selection pattern. NULL means everything, see STAM for further details.
+ */
+ void resetStats(const QString &rPatStr);
+
+ /**
+ * Resizes the columns to fit the content.
+ */
+ void resizeColumnsToContent();
+
+ /**
+ * Expands the trees matching the given expression.
+ *
+ * @param rPatStr Selection pattern.
+ */
+ void expandMatching(const QString &rPatStr);
+
+protected:
+ /**
+ * @callback_method_impl{VBoxDbgStatsModel::FNITERATOR,
+ * Worker for expandMatching}.
+ */
+ static bool expandMatchingCallback(PDBGGUISTATSNODE pNode, QModelIndex const &a_rIndex, const char *pszFullName, void *pvUser);
+
+ /**
+ * Expands or collapses a sub-tree.
+ *
+ * @param a_rIndex The root of the sub-tree.
+ * @param a_fExpanded Expand/collapse.
+ */
+ void setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded);
+
+ /**
+ * Popup context menu.
+ *
+ * @param a_pEvt The event.
+ */
+ virtual void contextMenuEvent(QContextMenuEvent *a_pEvt);
+
+protected slots:
+ /**
+ * Slot for handling the view/header context menu.
+ * @param a_rPos The mouse location.
+ */
+ void headerContextMenuRequested(const QPoint &a_rPos);
+
+ /** @name Action signal slots.
+ * @{ */
+ void actExpand();
+ void actCollapse();
+ void actRefresh();
+ void actReset();
+ void actCopy();
+ void actToLog();
+ void actToRelLog();
+ void actAdjColumns();
+ /** @} */
+
+
+protected:
+ /** Pointer to the data model. */
+ VBoxDbgStatsModel *m_pModel;
+ /** The current selection pattern. */
+ QString m_PatStr;
+ /** The parent widget. */
+ VBoxDbgStats *m_pParent;
+
+ /** Leaf item menu. */
+ QMenu *m_pLeafMenu;
+ /** Branch item menu. */
+ QMenu *m_pBranchMenu;
+ /** View menu. */
+ QMenu *m_pViewMenu;
+
+ /** The menu that's currently being executed. */
+ QMenu *m_pCurMenu;
+ /** The current index relating to the context menu.
+ * Considered invalid if m_pCurMenu is NULL. */
+ QModelIndex m_CurIndex;
+
+ /** Expand Tree action. */
+ QAction *m_pExpandAct;
+ /** Collapse Tree action. */
+ QAction *m_pCollapseAct;
+ /** Refresh Tree action. */
+ QAction *m_pRefreshAct;
+ /** Reset Tree action. */
+ QAction *m_pResetAct;
+ /** Copy (to clipboard) action. */
+ QAction *m_pCopyAct;
+ /** To Log action. */
+ QAction *m_pToLogAct;
+ /** To Release Log action. */
+ QAction *m_pToRelLogAct;
+ /** Adjust the columns. */
+ QAction *m_pAdjColumns;
+#if 0
+ /** Save Tree (to file) action. */
+ QAction *m_SaveFileAct;
+ /** Load Tree (from file) action. */
+ QAction *m_LoadFileAct;
+ /** Take Snapshot action. */
+ QAction *m_TakeSnapshotAct;
+ /** Load Snapshot action. */
+ QAction *m_LoadSnapshotAct;
+ /** Diff With Snapshot action. */
+ QAction *m_DiffSnapshotAct;
+#endif
+};
+
+
+
+/**
+ * The VM statistics window.
+ *
+ * This class displays the statistics of a VM. The UI contains
+ * a entry field for the selection pattern, a refresh interval
+ * spinbutton, and the tree view with the statistics.
+ */
+class VBoxDbgStats : public VBoxDbgBaseWindow
+{
+ Q_OBJECT;
+
+public:
+ /**
+ * Creates a VM statistics list view widget.
+ *
+ * @param a_pDbgGui Pointer to the debugger gui object.
+ * @param pszFilter Initial selection pattern. NULL means everything.
+ * (See STAM for details.)
+ * @param pszExpand Initial expansion pattern. NULL means nothing is
+ * expanded.
+ * @param uRefreshRate The refresh rate. 0 means not to refresh and is the default.
+ * @param pParent Parent widget.
+ */
+ VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszFilter = NULL, const char *pszExpand = NULL,
+ unsigned uRefreshRate = 0, QWidget *pParent = NULL);
+
+ /** Destructor. */
+ virtual ~VBoxDbgStats();
+
+protected:
+ /**
+ * Destroy the widget on close.
+ *
+ * @param a_pCloseEvt The close event.
+ */
+ virtual void closeEvent(QCloseEvent *a_pCloseEvt);
+
+protected slots:
+ /** Apply the activated combobox pattern. */
+ void apply(const QString &Str);
+ /** The "All" button was pressed. */
+ void applyAll();
+ /** Refresh the data on timer tick and pattern changed. */
+ void refresh();
+ /**
+ * Set the refresh rate.
+ *
+ * @param iRefresh The refresh interval in seconds.
+ */
+ void setRefresh(int iRefresh);
+
+ /**
+ * Change the focus to the pattern combo box.
+ */
+ void actFocusToPat();
+
+protected:
+
+ /** The current selection pattern. */
+ QString m_PatStr;
+ /** The pattern combo box. */
+ QComboBox *m_pPatCB;
+ /** The refresh rate in seconds.
+ * 0 means not to refresh. */
+ unsigned m_uRefreshRate;
+ /** The refresh timer .*/
+ QTimer *m_pTimer;
+ /** The tree view widget. */
+ VBoxDbgStatsView *m_pView;
+
+ /** Move to pattern field action. */
+ QAction *m_pFocusToPat;
+};
+
+
+#endif /* !DEBUGGER_INCLUDED_SRC_VBoxDbgStatsQt_h */
+
diff --git a/src/VBox/Debugger/testcase/Makefile.kup b/src/VBox/Debugger/testcase/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Debugger/testcase/Makefile.kup
diff --git a/src/VBox/Debugger/testcase/tstDBGCParser.cpp b/src/VBox/Debugger/testcase/tstDBGCParser.cpp
new file mode 100644
index 00000000..583f116a
--- /dev/null
+++ b/src/VBox/Debugger/testcase/tstDBGCParser.cpp
@@ -0,0 +1,1300 @@
+/* $Id: tstDBGCParser.cpp $ */
+/** @file
+ * DBGC Testcase - Command Parser.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/dbg.h>
+#include "../DBGCInternal.h"
+
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <VBox/err.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(bool) tstDBGCBackInput(PCDBGCIO pBack, uint32_t cMillies);
+static DECLCALLBACK(int) tstDBGCBackRead(PCDBGCIO pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead);
+static DECLCALLBACK(int) tstDBGCBackWrite(PCDBGCIO pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten);
+static DECLCALLBACK(void) tstDBGCBackSetReady(PCDBGCIO pBack, bool fReady);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The test handle. */
+static RTTEST g_hTest = NIL_RTTEST;
+
+/** The DBGC I/O structure for use in this testcase. */
+static DBGCIO g_tstBack =
+{
+ NULL, /**pfnDestroy*/
+ tstDBGCBackInput,
+ tstDBGCBackRead,
+ tstDBGCBackWrite,
+ NULL, /**pfnPktBegin*/
+ NULL, /**pfnPktEnd*/
+ tstDBGCBackSetReady
+};
+/** For keeping track of output prefixing. */
+static bool g_fPendingPrefix = true;
+/** Pointer to the current input position. */
+const char *g_pszInput = NULL;
+/** The output of the last command. */
+static char g_szOutput[1024];
+/** The current offset into g_szOutput. */
+static size_t g_offOutput = 0;
+
+
+/**
+ * Checks if there is input.
+ *
+ * @returns true if there is input ready.
+ * @returns false if there not input ready.
+ * @param pBack Pointer to the backend structure supplied by
+ * the backend. The backend can use this to find
+ * it's instance data.
+ * @param cMillies Number of milliseconds to wait on input data.
+ */
+static DECLCALLBACK(bool) tstDBGCBackInput(PCDBGCIO pBack, uint32_t cMillies)
+{
+ return g_pszInput != NULL
+ && *g_pszInput != '\0';
+}
+
+
+/**
+ * Read input.
+ *
+ * @returns VBox status code.
+ * @param pBack Pointer to the backend structure supplied by
+ * the backend. The backend can use this to find
+ * it's instance data.
+ * @param pvBuf Where to put the bytes we read.
+ * @param cbBuf Maximum nymber of bytes to read.
+ * @param pcbRead Where to store the number of bytes actually read.
+ * If NULL the entire buffer must be filled for a
+ * successful return.
+ */
+static DECLCALLBACK(int) tstDBGCBackRead(PCDBGCIO pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ if (g_pszInput && *g_pszInput)
+ {
+ size_t cb = strlen(g_pszInput);
+ if (cb > cbBuf)
+ cb = cbBuf;
+ *pcbRead = cb;
+ memcpy(pvBuf, g_pszInput, cb);
+ g_pszInput += cb;
+ }
+ else
+ *pcbRead = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Write (output).
+ *
+ * @returns VBox status code.
+ * @param pBack Pointer to the backend structure supplied by
+ * the backend. The backend can use this to find
+ * it's instance data.
+ * @param pvBuf What to write.
+ * @param cbBuf Number of bytes to write.
+ * @param pcbWritten Where to store the number of bytes actually written.
+ * If NULL the entire buffer must be successfully written.
+ */
+static DECLCALLBACK(int) tstDBGCBackWrite(PCDBGCIO pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ const char *pch = (const char *)pvBuf;
+ if (pcbWritten)
+ *pcbWritten = cbBuf;
+ while (cbBuf-- > 0)
+ {
+ /* screen/log output */
+ if (g_fPendingPrefix)
+ {
+ RTTestPrintfNl(g_hTest, RTTESTLVL_ALWAYS, "OUTPUT: ");
+ g_fPendingPrefix = false;
+ }
+ if (*pch == '\n')
+ g_fPendingPrefix = true;
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "%c", *pch);
+
+ /* buffer output */
+ if (g_offOutput < sizeof(g_szOutput) - 1)
+ {
+ g_szOutput[g_offOutput++] = *pch;
+ g_szOutput[g_offOutput] = '\0';
+ }
+
+ /* advance */
+ pch++;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Ready / busy notification.
+ *
+ * @param pBack Pointer to the backend structure supplied by
+ * the backend. The backend can use this to find
+ * it's instance data.
+ * @param fReady Whether it's ready (true) or busy (false).
+ */
+static DECLCALLBACK(void) tstDBGCBackSetReady(PCDBGCIO pBack, bool fReady)
+{
+}
+
+
+/**
+ * Completes the output, making sure that we're in
+ * the 1 position of a new line.
+ */
+static void tstCompleteOutput(void)
+{
+ if (!g_fPendingPrefix)
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "\n");
+ g_fPendingPrefix = true;
+}
+
+
+/**
+ * Checks if two DBGC variables are identical
+ *
+ * @returns
+ * @param pVar1 .
+ * @param pVar2 .
+ */
+bool DBGCVarAreIdentical(PCDBGCVAR pVar1, PCDBGCVAR pVar2)
+{
+ if (!pVar1)
+ return false;
+ if (pVar1 == pVar2)
+ return true;
+
+ if (pVar1->enmType != pVar2->enmType)
+ return false;
+ switch (pVar1->enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT:
+ if (pVar1->u.GCFlat != pVar2->u.GCFlat)
+ return false;
+ break;
+ case DBGCVAR_TYPE_GC_FAR:
+ if (pVar1->u.GCFar.off != pVar2->u.GCFar.off)
+ return false;
+ if (pVar1->u.GCFar.sel != pVar2->u.GCFar.sel)
+ return false;
+ break;
+ case DBGCVAR_TYPE_GC_PHYS:
+ if (pVar1->u.GCPhys != pVar2->u.GCPhys)
+ return false;
+ break;
+ case DBGCVAR_TYPE_HC_FLAT:
+ if (pVar1->u.pvHCFlat != pVar2->u.pvHCFlat)
+ return false;
+ break;
+ case DBGCVAR_TYPE_HC_PHYS:
+ if (pVar1->u.HCPhys != pVar2->u.HCPhys)
+ return false;
+ break;
+ case DBGCVAR_TYPE_NUMBER:
+ if (pVar1->u.u64Number != pVar2->u.u64Number)
+ return false;
+ break;
+ case DBGCVAR_TYPE_STRING:
+ case DBGCVAR_TYPE_SYMBOL:
+ if (RTStrCmp(pVar1->u.pszString, pVar2->u.pszString) != 0)
+ return false;
+ break;
+ default:
+ AssertFailedReturn(false);
+ }
+
+ if (pVar1->enmRangeType != pVar2->enmRangeType)
+ return false;
+ switch (pVar1->enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE:
+ break;
+
+ case DBGCVAR_RANGE_ELEMENTS:
+ case DBGCVAR_RANGE_BYTES:
+ if (pVar1->u64Range != pVar2->u64Range)
+ return false;
+ break;
+ default:
+ AssertFailedReturn(false);
+ }
+
+ return true;
+}
+
+/**
+ * Tries one command string.
+ * @param pDbgc Pointer to the debugger instance.
+ * @param pszCmds The command to test.
+ * @param rcCmd The expected result.
+ * @param fNoExecute When set, the command is not executed.
+ * @param pszExpected Expected output. This does not need to include all
+ * of the output, just the start of it. Thus the
+ * prompt can be omitted.
+ * @param cArgs The number of expected arguments. -1 if we don't
+ * want to check the parsed arguments.
+ * @param va Info about expected parsed arguments. For each
+ * argument a DBGCVARTYPE, value (depends on type),
+ * DBGCVARRANGETYPE and optionally range value.
+ */
+static void tstTryExV(PDBGC pDbgc, const char *pszCmds, int rcCmd, bool fNoExecute, const char *pszExpected,
+ int32_t cArgs, va_list va)
+{
+ RT_ZERO(g_szOutput);
+ g_offOutput = 0;
+ g_pszInput = pszCmds;
+ if (strchr(pszCmds, '\0')[-1] == '\n')
+ RTTestPrintfNl(g_hTest, RTTESTLVL_ALWAYS, "RUNNING: %s", pszCmds);
+ else
+ RTTestPrintfNl(g_hTest, RTTESTLVL_ALWAYS, "RUNNING: %s\n", pszCmds);
+
+ pDbgc->rcCmd = VERR_INTERNAL_ERROR;
+ dbgcProcessInput(pDbgc, fNoExecute);
+ tstCompleteOutput();
+
+ if (pDbgc->rcCmd != rcCmd)
+ RTTestFailed(g_hTest, "rcCmd=%Rrc expected =%Rrc\n", pDbgc->rcCmd, rcCmd);
+ else if ( !fNoExecute
+ && pszExpected
+ && strncmp(pszExpected, g_szOutput, strlen(pszExpected)))
+ RTTestFailed(g_hTest, "Wrong output - expected \"%s\"", pszExpected);
+
+ if (cArgs >= 0)
+ {
+ PCDBGCVAR paArgs = pDbgc->aArgs;
+ for (int32_t iArg = 0; iArg < cArgs; iArg++)
+ {
+ DBGCVAR ExpectedArg;
+ ExpectedArg.enmType = (DBGCVARTYPE)va_arg(va, int/*DBGCVARTYPE*/);
+ switch (ExpectedArg.enmType)
+ {
+ case DBGCVAR_TYPE_GC_FLAT: ExpectedArg.u.GCFlat = va_arg(va, RTGCPTR); break;
+ case DBGCVAR_TYPE_GC_FAR: ExpectedArg.u.GCFar.sel = va_arg(va, int /*RTSEL*/);
+ ExpectedArg.u.GCFar.off = va_arg(va, uint32_t); break;
+ case DBGCVAR_TYPE_GC_PHYS: ExpectedArg.u.GCPhys = va_arg(va, RTGCPHYS); break;
+ case DBGCVAR_TYPE_HC_FLAT: ExpectedArg.u.pvHCFlat = va_arg(va, void *); break;
+ case DBGCVAR_TYPE_HC_PHYS: ExpectedArg.u.HCPhys = va_arg(va, RTHCPHYS); break;
+ case DBGCVAR_TYPE_NUMBER: ExpectedArg.u.u64Number = va_arg(va, uint64_t); break;
+ case DBGCVAR_TYPE_STRING: ExpectedArg.u.pszString = va_arg(va, const char *); break;
+ case DBGCVAR_TYPE_SYMBOL: ExpectedArg.u.pszString = va_arg(va, const char *); break;
+ default:
+ RTTestFailed(g_hTest, "enmType=%u iArg=%u\n", ExpectedArg.enmType, iArg);
+ ExpectedArg.u.u64Number = 0;
+ break;
+ }
+ ExpectedArg.enmRangeType = (DBGCVARRANGETYPE)va_arg(va, int /*DBGCVARRANGETYPE*/);
+ switch (ExpectedArg.enmRangeType)
+ {
+ case DBGCVAR_RANGE_NONE: ExpectedArg.u64Range = 0; break;
+ case DBGCVAR_RANGE_ELEMENTS: ExpectedArg.u64Range = va_arg(va, uint64_t); break;
+ case DBGCVAR_RANGE_BYTES: ExpectedArg.u64Range = va_arg(va, uint64_t); break;
+ default:
+ RTTestFailed(g_hTest, "enmRangeType=%u iArg=%u\n", ExpectedArg.enmRangeType, iArg);
+ ExpectedArg.u64Range = 0;
+ break;
+ }
+
+ if (!DBGCVarAreIdentical(&ExpectedArg, &paArgs[iArg]))
+ RTTestFailed(g_hTest,
+ "Arg #%u\n"
+ "actual: enmType=%u u64=%#RX64 enmRangeType=%u u64Range=%#RX64\n"
+ "expected: enmType=%u u64=%#RX64 enmRangeType=%u u64Range=%#RX64\n",
+ iArg,
+ paArgs[iArg].enmType, paArgs[iArg].u.u64Number, paArgs[iArg].enmRangeType, paArgs[iArg].u64Range,
+ ExpectedArg.enmType, ExpectedArg.u.u64Number, ExpectedArg.enmRangeType, ExpectedArg.u64Range);
+ }
+ }
+}
+
+/**
+ * Tries one command string.
+ *
+ * @param pDbgc Pointer to the debugger instance.
+ * @param pszCmds The command to test.
+ * @param rcCmd The expected result.
+ * @param fNoExecute When set, the command is not executed.
+ * @param pszExpected Expected output. This does not need to include all
+ * of the output, just the start of it. Thus the
+ * prompt can be omitted.
+ * @param cArgs The number of expected arguments. -1 if we don't
+ * want to check the parsed arguments.
+ * @param ... Info about expected parsed arguments. For each
+ * argument a DBGCVARTYPE, value (depends on type),
+ * DBGCVARRANGETYPE and optionally range value.
+ */
+static void tstTryEx(PDBGC pDbgc, const char *pszCmds, int rcCmd, bool fNoExecute, const char *pszExpected, int32_t cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ tstTryExV(pDbgc, pszCmds, rcCmd, fNoExecute, pszExpected, cArgs, va);
+ va_end(va);
+}
+
+
+/**
+ * Tries one command string without executing it.
+ *
+ * @param pDbgc Pointer to the debugger instance.
+ * @param pszCmds The command to test.
+ * @param rcCmd The expected result.
+ */
+static void tstTry(PDBGC pDbgc, const char *pszCmds, int rcCmd)
+{
+ return tstTryEx(pDbgc, pszCmds, rcCmd, true /*fNoExecute*/, NULL, -1);
+}
+
+
+#ifdef SOME_UNUSED_FUNCTION
+/**
+ * Tries to execute one command string.
+ * @param pDbgc Pointer to the debugger instance.
+ * @param pszCmds The command to test.
+ * @param rcCmd The expected result.
+ * @param pszExpected Expected output. This does not need to include all
+ * of the output, just the start of it. Thus the
+ * prompt can be omitted.
+ */
+static void tstTryExec(PDBGC pDbgc, const char *pszCmds, int rcCmd, const char *pszExpected)
+{
+ return tstTryEx(pDbgc, pszCmds, rcCmd, false /*fNoExecute*/, pszExpected, -1);
+}
+#endif
+
+
+/**
+ * Test an operator on an expression resulting a plain number.
+ *
+ * @param pDbgc Pointer to the debugger instance.
+ * @param pszExpr The express to test.
+ * @param u64Expect The expected result.
+ */
+static void tstNumOp(PDBGC pDbgc, const char *pszExpr, uint64_t u64Expect)
+{
+ char szCmd[80];
+ RTStrPrintf(szCmd, sizeof(szCmd), "format %s\n", pszExpr);
+
+ char szExpected[80];
+ RTStrPrintf(szExpected, sizeof(szExpected),
+ "Number: hex %llx dec 0i%lld oct 0t%llo", u64Expect, u64Expect, u64Expect);
+
+ return tstTryEx(pDbgc, szCmd, VINF_SUCCESS, false /*fNoExecute*/, szExpected, -1);
+}
+
+
+/*
+ *
+ * CodeView emulation commands.
+ * CodeView emulation commands.
+ * CodeView emulation commands.
+ *
+ */
+
+
+static void testCodeView_ba(PDBGC pDbgc)
+{
+ RTTestISub("codeview - ba");
+ tstTry(pDbgc, "ba x 1 0f000:0000\n", VINF_SUCCESS);
+ tstTry(pDbgc, "ba x 1 0f000:0000 0\n", VINF_SUCCESS);
+ tstTry(pDbgc, "ba x 1 0f000:0000 0 ~0\n", VINF_SUCCESS);
+ tstTry(pDbgc, "ba x 1 0f000:0000 0 ~0 \"command\"\n", VINF_SUCCESS);
+ tstTry(pDbgc, "ba x 1 0f000:0000 0 ~0 \"command\" too_many\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "ba x 1\n", VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS);
+
+ tstTryEx(pDbgc, "ba x 1 0f000:1234 5 1000 \"command\"\n", VINF_SUCCESS,
+ true /*fNoExecute*/, NULL /*pszExpected*/, 6 /*cArgs*/,
+ DBGCVAR_TYPE_STRING, "x", DBGCVAR_RANGE_BYTES, UINT64_C(1),
+ DBGCVAR_TYPE_NUMBER, UINT64_C(1), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_GC_FAR, 0xf000, UINT32_C(0x1234), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_NUMBER, UINT64_C(0x5), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_NUMBER, UINT64_C(0x1000), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_STRING, "command", DBGCVAR_RANGE_BYTES, UINT64_C(7));
+
+ tstTryEx(pDbgc, "ba x 1 %0f000:1234 5 1000 \"command\"\n", VINF_SUCCESS,
+ true /*fNoExecute*/, NULL /*pszExpected*/, 6 /*cArgs*/,
+ DBGCVAR_TYPE_STRING, "x", DBGCVAR_RANGE_BYTES, UINT64_C(1),
+ DBGCVAR_TYPE_NUMBER, UINT64_C(1), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_GC_FLAT, UINT64_C(0xf1234), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_NUMBER, UINT64_C(0x5), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_NUMBER, UINT64_C(0x1000), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_STRING, "command", DBGCVAR_RANGE_BYTES, UINT64_C(7));
+
+ tstTry(pDbgc, "ba x 1 bad:bad 5 1000 \"command\"\n", VINF_SUCCESS);
+ tstTry(pDbgc, "ba x 1 %bad:bad 5 1000 \"command\"\n", VERR_DBGC_PARSE_CONVERSION_FAILED);
+
+ tstTryEx(pDbgc, "ba f 1 0f000:1234 5 1000 \"command\"\n", VINF_SUCCESS,
+ true /*fNoExecute*/, NULL /*pszExpected*/, 6 /*cArgs*/,
+ DBGCVAR_TYPE_STRING, "f", DBGCVAR_RANGE_BYTES, UINT64_C(1),
+ DBGCVAR_TYPE_NUMBER, UINT64_C(1), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_GC_FAR, 0xf000, UINT32_C(0x1234), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_NUMBER, UINT64_C(0x5), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_NUMBER, UINT64_C(0x1000), DBGCVAR_RANGE_NONE,
+ DBGCVAR_TYPE_STRING, "command", DBGCVAR_RANGE_BYTES, UINT64_C(7));
+
+ tstTry(pDbgc, "ba x 1 0f000:1234 qnx 1000 \"command\"\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "ba x 1 0f000:1234 5 qnx \"command\"\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "ba x qnx 0f000:1234 5 1000 \"command\"\n", VERR_DBGC_PARSE_INVALID_NUMBER);
+ tstTry(pDbgc, "ba x 1 qnx 5 1000 \"command\"\n", VERR_DBGC_PARSE_INVALID_NUMBER);
+}
+
+
+static void testCodeView_bc(PDBGC pDbgc)
+{
+ RTTestISub("codeview - bc");
+}
+
+
+static void testCodeView_bd(PDBGC pDbgc)
+{
+ RTTestISub("codeview - bc");
+}
+
+
+static void testCodeView_be(PDBGC pDbgc)
+{
+ RTTestISub("codeview - be");
+}
+
+
+static void testCodeView_bl(PDBGC pDbgc)
+{
+ RTTestISub("codeview - bl");
+}
+
+
+static void testCodeView_bp(PDBGC pDbgc)
+{
+ RTTestISub("codeview - bp");
+}
+
+
+static void testCodeView_br(PDBGC pDbgc)
+{
+ RTTestISub("codeview - br");
+}
+
+
+static void testCodeView_d(PDBGC pDbgc)
+{
+ RTTestISub("codeview - d");
+}
+
+
+static void testCodeView_da(PDBGC pDbgc)
+{
+ RTTestISub("codeview - da");
+}
+
+
+static void testCodeView_db(PDBGC pDbgc)
+{
+ RTTestISub("codeview - db");
+}
+
+
+static void testCodeView_dd(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dd");
+}
+
+
+static void testCodeView_dg(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dg");
+}
+
+
+static void testCodeView_dga(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dga");
+}
+
+
+static void testCodeView_di(PDBGC pDbgc)
+{
+ RTTestISub("codeview - di");
+}
+
+
+static void testCodeView_dia(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dia");
+}
+
+
+static void testCodeView_dl(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dl");
+}
+
+
+static void testCodeView_dla(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dla");
+}
+
+
+static void testCodeView_dpd(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dpd");
+}
+
+
+static void testCodeView_dpda(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dpda");
+}
+
+
+static void testCodeView_dpdb(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dpdb");
+}
+
+
+static void testCodeView_dpdg(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dpdg");
+}
+
+
+static void testCodeView_dpdh(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dpdh");
+}
+
+
+static void testCodeView_dph(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dph");
+}
+
+
+static void testCodeView_dphg(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dphg");
+}
+
+
+static void testCodeView_dphh(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dphh");
+}
+
+
+static void testCodeView_dq(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dq");
+}
+
+
+static void testCodeView_dt(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dt");
+}
+
+
+static void testCodeView_dt16(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dt16");
+}
+
+
+static void testCodeView_dt32(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dt32");
+}
+
+
+static void testCodeView_dt64(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dt64");
+}
+
+
+static void testCodeView_dw(PDBGC pDbgc)
+{
+ RTTestISub("codeview - dw");
+}
+
+
+static void testCodeView_eb(PDBGC pDbgc)
+{
+ RTTestISub("codeview - eb");
+}
+
+
+static void testCodeView_ew(PDBGC pDbgc)
+{
+ RTTestISub("codeview - ew");
+}
+
+
+static void testCodeView_ed(PDBGC pDbgc)
+{
+ RTTestISub("codeview - ed");
+}
+
+
+static void testCodeView_eq(PDBGC pDbgc)
+{
+ RTTestISub("codeview - eq");
+}
+
+
+static void testCodeView_g(PDBGC pDbgc)
+{
+ RTTestISub("codeview - g");
+}
+
+
+static void testCodeView_k(PDBGC pDbgc)
+{
+ RTTestISub("codeview - k");
+}
+
+
+static void testCodeView_kg(PDBGC pDbgc)
+{
+ RTTestISub("codeview - kg");
+}
+
+
+static void testCodeView_kh(PDBGC pDbgc)
+{
+ RTTestISub("codeview - kh");
+}
+
+
+static void testCodeView_lm(PDBGC pDbgc)
+{
+ RTTestISub("codeview - lm");
+}
+
+
+static void testCodeView_lmo(PDBGC pDbgc)
+{
+ RTTestISub("codeview - lmo");
+}
+
+
+static void testCodeView_ln(PDBGC pDbgc)
+{
+ RTTestISub("codeview - ln");
+}
+
+
+static void testCodeView_ls(PDBGC pDbgc)
+{
+ RTTestISub("codeview - ls");
+}
+
+
+static void testCodeView_m(PDBGC pDbgc)
+{
+ RTTestISub("codeview - m");
+}
+
+
+static void testCodeView_r(PDBGC pDbgc)
+{
+ RTTestISub("codeview - r");
+}
+
+
+static void testCodeView_rg(PDBGC pDbgc)
+{
+ RTTestISub("codeview - rg");
+}
+
+
+static void testCodeView_rg32(PDBGC pDbgc)
+{
+ RTTestISub("codeview - rg32");
+}
+
+
+static void testCodeView_rg64(PDBGC pDbgc)
+{
+ RTTestISub("codeview - rg64");
+}
+
+
+static void testCodeView_rh(PDBGC pDbgc)
+{
+ RTTestISub("codeview - rh");
+}
+
+
+static void testCodeView_rt(PDBGC pDbgc)
+{
+ RTTestISub("codeview - rt");
+}
+
+
+static void testCodeView_s(PDBGC pDbgc)
+{
+ RTTestISub("codeview - s");
+}
+
+
+static void testCodeView_sa(PDBGC pDbgc)
+{
+ RTTestISub("codeview - sa");
+}
+
+
+static void testCodeView_sb(PDBGC pDbgc)
+{
+ RTTestISub("codeview - sb");
+}
+
+
+static void testCodeView_sd(PDBGC pDbgc)
+{
+ RTTestISub("codeview - sd");
+}
+
+
+static void testCodeView_sq(PDBGC pDbgc)
+{
+ RTTestISub("codeview - sq");
+}
+
+
+static void testCodeView_su(PDBGC pDbgc)
+{
+ RTTestISub("codeview - su");
+}
+
+
+static void testCodeView_sw(PDBGC pDbgc)
+{
+ RTTestISub("codeview - sw");
+}
+
+
+static void testCodeView_t(PDBGC pDbgc)
+{
+ RTTestISub("codeview - t");
+}
+
+
+static void testCodeView_y(PDBGC pDbgc)
+{
+ RTTestISub("codeview - y");
+}
+
+
+static void testCodeView_u64(PDBGC pDbgc)
+{
+ RTTestISub("codeview - u64");
+}
+
+
+static void testCodeView_u32(PDBGC pDbgc)
+{
+ RTTestISub("codeview - u32");
+}
+
+
+static void testCodeView_u16(PDBGC pDbgc)
+{
+ RTTestISub("codeview - u16");
+}
+
+
+static void testCodeView_uv86(PDBGC pDbgc)
+{
+ RTTestISub("codeview - uv86");
+}
+
+
+/*
+ * Common commands.
+ */
+
+static void testCommon_bye_exit_quit(PDBGC pDbgc)
+{
+ RTTestISub("common - bye/exit/quit");
+ /* These have the same parameter descriptor and handler, the command really
+ just has a couple of aliases.*/
+ tstTry(pDbgc, "bye\n", VINF_SUCCESS);
+ tstTry(pDbgc, "bye x\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "bye 1\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "bye %bad:bad\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "exit\n", VINF_SUCCESS);
+ tstTry(pDbgc, "quit\n", VINF_SUCCESS);
+}
+
+
+static void testCommon_cpu(PDBGC pDbgc)
+{
+ RTTestISub("common - cpu");
+ tstTry(pDbgc, "cpu\n", VINF_SUCCESS);
+ tstTry(pDbgc, "cpu 1\n", VINF_SUCCESS);
+ tstTry(pDbgc, "cpu 1 1\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "cpu emt\n", VERR_DBGC_PARSE_INVALID_NUMBER);
+ tstTry(pDbgc, "cpu @eax\n", VINF_SUCCESS);
+ tstTry(pDbgc, "cpu %bad:bad\n", VERR_DBGC_PARSE_CONVERSION_FAILED);
+ tstTry(pDbgc, "cpu '1'\n", VERR_DBGC_PARSE_INVALID_NUMBER);
+}
+
+
+static void testCommon_echo(PDBGC pDbgc)
+{
+ RTTestISub("common - echo");
+ tstTry(pDbgc, "echo\n", VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS);
+ tstTry(pDbgc, "echo 1\n", VINF_SUCCESS);
+ tstTryEx(pDbgc, "echo 1 2 3 4 5 6\n", VINF_SUCCESS, false, "1 2 3 4 5 6", -1);
+
+ /* The idea here is that since the prefered input is a string, we
+ definitely won't be confused by the number like beginning. */
+ tstTryEx(pDbgc, "echo 1234567890abcdefghijklmn\n", VINF_SUCCESS, false, "1234567890abcdefghijklmn", -1);
+
+ /* The idea here is that we'll perform the + operation and then convert the
+ result to a string (hex). */
+ tstTryEx(pDbgc, "echo 1 + 1\n", VINF_SUCCESS, false, "2", -1);
+ tstTryEx(pDbgc, "echo \"1 + 1\"\n", VINF_SUCCESS, false, "1 + 1", -1);
+
+ tstTryEx(pDbgc, "echo 0i10 + 6\n", VINF_SUCCESS, false, "10", -1);
+ tstTryEx(pDbgc, "echo \"0i10 + 6\"\n", VINF_SUCCESS, false, "0i10 + 6", -1);
+
+ tstTryEx(pDbgc, "echo %f000:0010\n", VINF_SUCCESS, false, "%00000000000f0010", -1);
+ tstTryEx(pDbgc, "echo \"%f000:0010\"\n", VINF_SUCCESS, false, "%f000:0010", -1);
+
+ tstTry(pDbgc, "echo %bad:bad\n", VERR_DBGC_PARSE_CONVERSION_FAILED);
+}
+
+
+static void testCommon_format(PDBGC pDbgc)
+{
+ RTTestISub("common - format");
+}
+
+
+static void testCommon_detect(PDBGC pDbgc)
+{
+ RTTestISub("common - detect");
+}
+
+
+static void testCommon_harakiri(PDBGC pDbgc)
+{
+ RTTestISub("common - harakiri");
+}
+
+
+static void testCommon_help(PDBGC pDbgc)
+{
+ RTTestISub("common - help");
+}
+
+
+static void testCommon_info(PDBGC pDbgc)
+{
+ RTTestISub("common - info");
+ tstTry(pDbgc, "info 12fg\n", VINF_SUCCESS);
+ tstTry(pDbgc, "info fflags argument\n", VINF_SUCCESS);
+}
+
+
+static void testCommon_loadimage(PDBGC pDbgc)
+{
+ RTTestISub("common - loadimage");
+}
+
+
+static void testCommon_loadmap(PDBGC pDbgc)
+{
+ RTTestISub("common - loadmap");
+}
+
+
+static void testCommon_loadplugin(PDBGC pDbgc)
+{
+ RTTestISub("common - loadplugin");
+}
+
+
+static void testCommon_loadseg(PDBGC pDbgc)
+{
+ RTTestISub("common - loadseg");
+}
+
+
+static void testCommon_loadsyms(PDBGC pDbgc)
+{
+ RTTestISub("common - loadsyms");
+}
+
+
+static void testCommon_loadvars(PDBGC pDbgc)
+{
+ RTTestISub("common - loadvars");
+}
+
+
+static void testCommon_log(PDBGC pDbgc)
+{
+ RTTestISub("common - log");
+}
+
+
+static void testCommon_logdest(PDBGC pDbgc)
+{
+ RTTestISub("common - logdest");
+}
+
+
+static void testCommon_logflags(PDBGC pDbgc)
+{
+ RTTestISub("common - logflags");
+}
+
+
+static void testCommon_runscript(PDBGC pDbgc)
+{
+ RTTestISub("common - runscript");
+}
+
+
+static void testCommon_set(PDBGC pDbgc)
+{
+ RTTestISub("common - set");
+}
+
+
+static void testCommon_showplugins(PDBGC pDbgc)
+{
+ RTTestISub("common - showplugins");
+}
+
+
+static void testCommon_showvars(PDBGC pDbgc)
+{
+ RTTestISub("common - showvars");
+}
+
+
+static void testCommon_stop(PDBGC pDbgc)
+{
+ RTTestISub("common - stop");
+}
+
+
+static void testCommon_unloadplugin(PDBGC pDbgc)
+{
+ RTTestISub("common - unloadplugin");
+}
+
+
+static void testCommon_unset(PDBGC pDbgc)
+{
+ RTTestISub("common - unset");
+}
+
+
+static void testCommon_writecore(PDBGC pDbgc)
+{
+ RTTestISub("common - writecore");
+}
+
+
+
+/*
+ * Basic tests.
+ */
+
+static void testBasicsOddCases(PDBGC pDbgc)
+{
+ RTTestISub("Odd cases");
+ tstTry(pDbgc, "r @rax\n", VINF_SUCCESS);
+ tstTry(pDbgc, "r @eax\n", VINF_SUCCESS);
+ tstTry(pDbgc, "r @ah\n", VINF_SUCCESS);
+ tstTry(pDbgc, "r @notavalidregister\n", VERR_DBGF_REGISTER_NOT_FOUND);
+}
+
+
+static void testBasicsOperators(PDBGC pDbgc)
+{
+ RTTestISub("Operators");
+ tstNumOp(pDbgc, "1", 1);
+ tstNumOp(pDbgc, "1", 1);
+ tstNumOp(pDbgc, "1", 1);
+
+ tstNumOp(pDbgc, "+1", 1);
+ tstNumOp(pDbgc, "++++++1", 1);
+
+ tstNumOp(pDbgc, "-1", UINT64_MAX);
+ tstNumOp(pDbgc, "--1", 1);
+ tstNumOp(pDbgc, "---1", UINT64_MAX);
+ tstNumOp(pDbgc, "----1", 1);
+
+ tstNumOp(pDbgc, "~0", UINT64_MAX);
+ tstNumOp(pDbgc, "~1", UINT64_MAX-1);
+ tstNumOp(pDbgc, "~~0", 0);
+ tstNumOp(pDbgc, "~~1", 1);
+
+ tstNumOp(pDbgc, "!1", 0);
+ tstNumOp(pDbgc, "!0", 1);
+ tstNumOp(pDbgc, "!42", 0);
+ tstNumOp(pDbgc, "!!42", 1);
+ tstNumOp(pDbgc, "!!!42", 0);
+ tstNumOp(pDbgc, "!!!!42", 1);
+
+ tstNumOp(pDbgc, "1 +1", 2);
+ tstNumOp(pDbgc, "1 + 1", 2);
+ tstNumOp(pDbgc, "1+1", 2);
+ tstNumOp(pDbgc, "1+ 1", 2);
+
+ tstNumOp(pDbgc, "1 - 1", 0);
+ tstNumOp(pDbgc, "99 - 90", 9);
+
+ tstNumOp(pDbgc, "2 * 2", 4);
+
+ tstNumOp(pDbgc, "2 / 2", 1);
+ tstNumOp(pDbgc, "2 / 0", UINT64_MAX);
+ tstNumOp(pDbgc, "0i1024 / 0i4", 256);
+
+ tstNumOp(pDbgc, "8 mod 7", 1);
+
+ tstNumOp(pDbgc, "1<<1", 2);
+ tstNumOp(pDbgc, "1<<0i32", UINT64_C(0x0000000100000000));
+ tstNumOp(pDbgc, "1<<0i48", UINT64_C(0x0001000000000000));
+ tstNumOp(pDbgc, "1<<0i63", UINT64_C(0x8000000000000000));
+
+ tstNumOp(pDbgc, "fedcba0987654321>>0i04", UINT64_C(0x0fedcba098765432));
+ tstNumOp(pDbgc, "fedcba0987654321>>0i32", UINT64_C(0xfedcba09));
+ tstNumOp(pDbgc, "fedcba0987654321>>0i48", UINT64_C(0x0000fedc));
+
+ tstNumOp(pDbgc, "0ef & 4", 4);
+ tstNumOp(pDbgc, "01234567891 & fff", UINT64_C(0x00000000891));
+ tstNumOp(pDbgc, "01234567891 & ~fff", UINT64_C(0x01234567000));
+
+ tstNumOp(pDbgc, "1 | 1", 1);
+ tstNumOp(pDbgc, "0 | 4", 4);
+ tstNumOp(pDbgc, "4 | 0", 4);
+ tstNumOp(pDbgc, "4 | 4", 4);
+ tstNumOp(pDbgc, "1 | 4 | 2", 7);
+
+ tstNumOp(pDbgc, "1 ^ 1", 0);
+ tstNumOp(pDbgc, "1 ^ 0", 1);
+ tstNumOp(pDbgc, "0 ^ 1", 1);
+ tstNumOp(pDbgc, "3 ^ 1", 2);
+ tstNumOp(pDbgc, "7 ^ 3", 4);
+
+ tstNumOp(pDbgc, "7 || 3", 1);
+ tstNumOp(pDbgc, "1 || 0", 1);
+ tstNumOp(pDbgc, "0 || 1", 1);
+ tstNumOp(pDbgc, "0 || 0", 0);
+
+ tstNumOp(pDbgc, "0 && 0", 0);
+ tstNumOp(pDbgc, "1 && 0", 0);
+ tstNumOp(pDbgc, "0 && 1", 0);
+ tstNumOp(pDbgc, "1 && 1", 1);
+ tstNumOp(pDbgc, "4 && 1", 1);
+}
+
+
+static void testBasicsFundametalParsing(PDBGC pDbgc)
+{
+ RTTestISub("Fundamental parsing");
+ tstTry(pDbgc, "stop\n", VINF_SUCCESS);
+ tstTry(pDbgc, "format 1\n", VINF_SUCCESS);
+ tstTry(pDbgc, "format \n", VERR_DBGC_PARSE_TOO_FEW_ARGUMENTS);
+ tstTry(pDbgc, "format 0 1 23 4\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "format 'x'\n", VINF_SUCCESS);
+ tstTry(pDbgc, "format 'x' 'x'\n", VERR_DBGC_PARSE_TOO_MANY_ARGUMENTS);
+ tstTry(pDbgc, "format 'x''x'\n", VINF_SUCCESS);
+ tstTry(pDbgc, "format 'x'\"x\"\n", VERR_DBGC_PARSE_EXPECTED_BINARY_OP);
+ tstTry(pDbgc, "format 'x'1\n", VERR_DBGC_PARSE_EXPECTED_BINARY_OP);
+ tstTry(pDbgc, "format (1)1\n", VERR_DBGC_PARSE_EXPECTED_BINARY_OP);
+ tstTry(pDbgc, "format (1)(1)\n", VERR_DBGC_PARSE_EXPECTED_BINARY_OP);
+ tstTry(pDbgc, "format (1)''\n", VERR_DBGC_PARSE_EXPECTED_BINARY_OP);
+ tstTry(pDbgc, "format nosuchfunction(1)\n", VERR_DBGC_PARSE_FUNCTION_NOT_FOUND);
+ tstTry(pDbgc, "format nosuchfunction(1,2,3)\n", VERR_DBGC_PARSE_FUNCTION_NOT_FOUND);
+ tstTry(pDbgc, "format nosuchfunction()\n", VERR_DBGC_PARSE_FUNCTION_NOT_FOUND);
+ tstTry(pDbgc, "format randu32()\n", VINF_SUCCESS);
+ tstTryEx(pDbgc, "format %0\n", VINF_SUCCESS, false, "Guest flat address: %00000000", -1);
+ tstTryEx(pDbgc, "format %eax\n", VINF_SUCCESS, false, "Guest flat address: %cafebabe", -1);
+ tstTry(pDbgc, "sa 3 23 4 'q' \"21123123\" 'b' \n", VINF_SUCCESS);
+ tstTry(pDbgc, "sa 3,23, 4,'q' ,\"21123123\" , 'b' \n", VINF_SUCCESS);
+}
+
+
+int main()
+{
+ /*
+ * Init.
+ */
+ int rc = RTTestInitAndCreate("tstDBGCParser", &g_hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(g_hTest);
+
+ /*
+ * Create a DBGC instance.
+ */
+ RTTestSub(g_hTest, "dbgcCreate");
+ PDBGC pDbgc;
+ rc = dbgcCreate(&pDbgc, &g_tstBack, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgc->pVM = (PVM)pDbgc;
+ pDbgc->pUVM = (PUVM)pDbgc;
+ rc = dbgcProcessInput(pDbgc, true /* fNoExecute */);
+ tstCompleteOutput();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Perform basic tests first.
+ */
+ testBasicsFundametalParsing(pDbgc);
+ if (RTTestErrorCount(g_hTest) == 0)
+ testBasicsOperators(pDbgc);
+ if (RTTestErrorCount(g_hTest) == 0)
+ testBasicsOddCases(pDbgc);
+
+ /*
+ * Test commands.
+ */
+ if (RTTestErrorCount(g_hTest) == 0)
+ {
+ testCodeView_ba(pDbgc);
+ testCodeView_bc(pDbgc);
+ testCodeView_bd(pDbgc);
+ testCodeView_be(pDbgc);
+ testCodeView_bl(pDbgc);
+ testCodeView_bp(pDbgc);
+ testCodeView_br(pDbgc);
+ testCodeView_d(pDbgc);
+ testCodeView_da(pDbgc);
+ testCodeView_db(pDbgc);
+ testCodeView_dd(pDbgc);
+ testCodeView_dg(pDbgc);
+ testCodeView_dga(pDbgc);
+ testCodeView_di(pDbgc);
+ testCodeView_dia(pDbgc);
+ testCodeView_dl(pDbgc);
+ testCodeView_dla(pDbgc);
+ testCodeView_dpd(pDbgc);
+ testCodeView_dpda(pDbgc);
+ testCodeView_dpdb(pDbgc);
+ testCodeView_dpdg(pDbgc);
+ testCodeView_dpdh(pDbgc);
+ testCodeView_dph(pDbgc);
+ testCodeView_dphg(pDbgc);
+ testCodeView_dphh(pDbgc);
+ testCodeView_dq(pDbgc);
+ testCodeView_dt(pDbgc);
+ testCodeView_dt16(pDbgc);
+ testCodeView_dt32(pDbgc);
+ testCodeView_dt64(pDbgc);
+ testCodeView_dw(pDbgc);
+ testCodeView_eb(pDbgc);
+ testCodeView_ew(pDbgc);
+ testCodeView_ed(pDbgc);
+ testCodeView_eq(pDbgc);
+ testCodeView_g(pDbgc);
+ testCodeView_k(pDbgc);
+ testCodeView_kg(pDbgc);
+ testCodeView_kh(pDbgc);
+ testCodeView_lm(pDbgc);
+ testCodeView_lmo(pDbgc);
+ testCodeView_ln(pDbgc);
+ testCodeView_ls(pDbgc);
+ testCodeView_m(pDbgc);
+ testCodeView_r(pDbgc);
+ testCodeView_rg(pDbgc);
+ testCodeView_rg32(pDbgc);
+ testCodeView_rg64(pDbgc);
+ testCodeView_rh(pDbgc);
+ testCodeView_rt(pDbgc);
+ testCodeView_s(pDbgc);
+ testCodeView_sa(pDbgc);
+ testCodeView_sb(pDbgc);
+ testCodeView_sd(pDbgc);
+ testCodeView_sq(pDbgc);
+ testCodeView_su(pDbgc);
+ testCodeView_sw(pDbgc);
+ testCodeView_t(pDbgc);
+ testCodeView_y(pDbgc);
+ testCodeView_u64(pDbgc);
+ testCodeView_u32(pDbgc);
+ testCodeView_u16(pDbgc);
+ testCodeView_uv86(pDbgc);
+
+ testCommon_bye_exit_quit(pDbgc);
+ testCommon_cpu(pDbgc);
+ testCommon_echo(pDbgc);
+ testCommon_format(pDbgc);
+ testCommon_detect(pDbgc);
+ testCommon_harakiri(pDbgc);
+ testCommon_help(pDbgc);
+ testCommon_info(pDbgc);
+ testCommon_loadimage(pDbgc);
+ testCommon_loadmap(pDbgc);
+ testCommon_loadplugin(pDbgc);
+ testCommon_loadseg(pDbgc);
+ testCommon_loadsyms(pDbgc);
+ testCommon_loadvars(pDbgc);
+ testCommon_log(pDbgc);
+ testCommon_logdest(pDbgc);
+ testCommon_logflags(pDbgc);
+ testCommon_runscript(pDbgc);
+ testCommon_set(pDbgc);
+ testCommon_showplugins(pDbgc);
+ testCommon_showvars(pDbgc);
+ testCommon_stop(pDbgc);
+ testCommon_unloadplugin(pDbgc);
+ testCommon_unset(pDbgc);
+ testCommon_writecore(pDbgc);
+ }
+ }
+
+ dbgcDestroy(pDbgc);
+ }
+
+ /*
+ * Summary
+ */
+ return RTTestSummaryAndDestroy(g_hTest);
+}
diff --git a/src/VBox/Debugger/testcase/tstDBGCStubs.cpp b/src/VBox/Debugger/testcase/tstDBGCStubs.cpp
new file mode 100644
index 00000000..47092ac9
--- /dev/null
+++ b/src/VBox/Debugger/testcase/tstDBGCStubs.cpp
@@ -0,0 +1,862 @@
+/* $Id: tstDBGCStubs.cpp $ */
+/** @file
+ * DBGC Testcase - Command Parser, VMM Stub Functions.
+ */
+
+/*
+ * Copyright (C) 2006-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
+ */
+
+#include <VBox/err.h>
+#include <VBox/vmm/vmapi.h>
+#include <iprt/string.h>
+
+
+
+#include <VBox/vmm/dbgf.h>
+#include <VBox/vmm/dbgfflowtrace.h>
+VMMR3DECL(PDBGFADDRESS) DBGFR3AddrFromFlat(PUVM pUVM, PDBGFADDRESS pAddress, RTGCUINTPTR FlatPtr)
+{
+ return NULL;
+}
+
+VMMR3DECL(int) DBGFR3AddrFromSelOff(PUVM pUVM, VMCPUID idCpu, PDBGFADDRESS pAddress, RTSEL Sel, RTUINTPTR off)
+{
+ /* bad:bad -> provke error during parsing. */
+ if (Sel == 0xbad && off == 0xbad)
+ return VERR_OUT_OF_SELECTOR_BOUNDS;
+
+ /* real mode conversion. */
+ pAddress->FlatPtr = (uint32_t)(Sel << 4) | off;
+ pAddress->fFlags |= DBGFADDRESS_FLAGS_FLAT;
+ pAddress->Sel = DBGF_SEL_FLAT;
+ pAddress->off = pAddress->FlatPtr;
+ return VINF_SUCCESS;
+}
+
+VMMR3DECL(int) DBGFR3AddrToPhys(PUVM pUVM, VMCPUID idCpu, PCDBGFADDRESS pAddress, PRTGCPHYS pGCPhys)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) DBGFR3Attach(PUVM pUVM)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) DBGFR3BpClear(PUVM pUVM, RTUINT iBp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3BpDisable(PUVM pUVM, RTUINT iBp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3BpEnable(PUVM pUVM, RTUINT iBp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3BpEnum(PUVM pUVM, PFNDBGFBPENUM pfnCallback, void *pvUser)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3BpSetInt3(PUVM pUVM, VMCPUID idCpu, PCDBGFADDRESS pAddress, uint64_t iHitTrigger, uint64_t iHitDisable, PRTUINT piBp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3BpSetReg(PUVM pUVM, PCDBGFADDRESS pAddress, uint64_t iHitTrigger, uint64_t iHitDisable,
+ uint8_t fType, uint8_t cb, PRTUINT piBp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3BpSetREM(PUVM pUVM, PCDBGFADDRESS pAddress, uint64_t iHitTrigger, uint64_t iHitDisable, PRTUINT piBp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3QueryWaitable(PUVM pUVM)
+{
+ return VINF_SUCCESS;
+}
+VMMR3DECL(int) DBGFR3Detach(PUVM pUVM)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3DisasInstrEx(PUVM pUVM, VMCPUID idCpu, RTSEL Sel, RTGCPTR GCPtr, uint32_t fFlags,
+ char *pszOutput, uint32_t cchOutput, uint32_t *pcbInstr)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3EventWait(PUVM pUVM, RTMSINTERVAL cMillies, PDBGFEVENT pEvent)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3EventConfigEx(PUVM pUVM, PCDBGFEVENTCONFIG paConfigs, size_t cConfigs)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3InterruptConfigEx(PUVM pUVM, PCDBGFINTERRUPTCONFIG paConfigs, size_t cConfigs)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) DBGFR3Halt(PUVM pUVM, VMCPUID idCpu)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3Info(PUVM pUVM, const char *pszName, const char *pszArgs, PCDBGFINFOHLP pHlp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3InfoEx(PUVM pUVM, VMCPUID idCpu, const char *pszName, const char *pszArgs, PCDBGFINFOHLP pHlp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(void) DBGFR3InfoGenericGetOptError(PCDBGFINFOHLP pHlp, int rc, union RTGETOPTUNION *pValueUnion, struct RTGETOPTSTATE *pState)
+{
+}
+VMMR3DECL(bool) DBGFR3IsHalted(PUVM pUVM, VMCPUID idCpu)
+{
+ return true;
+}
+VMMR3DECL(int) DBGFR3LogModifyDestinations(PUVM pUVM, const char *pszDestSettings)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3LogModifyFlags(PUVM pUVM, const char *pszFlagSettings)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3LogModifyGroups(PUVM pUVM, const char *pszGroupSettings)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(RTDBGCFG) DBGFR3AsGetConfig(PUVM pUVM)
+{
+ return NIL_RTDBGCFG;
+}
+VMMR3DECL(int) DBGFR3AsLoadImage(PUVM pUVM, RTDBGAS hAS, const char *pszFilename, const char *pszModName, RTLDRARCH enmArch,
+ PCDBGFADDRESS pModAddress, RTDBGSEGIDX iModSeg, uint32_t fFlags)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3AsLoadMap(PUVM pUVM, RTDBGAS hAS, const char *pszFilename, const char *pszModName, PCDBGFADDRESS pModAddress, RTDBGSEGIDX iModSeg, RTGCUINTPTR uSubtrahend, uint32_t fFlags)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3AsUnlinkModuleByName(PUVM pUVM, RTDBGAS hDbgAs, const char *pszModName)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(RTDBGAS) DBGFR3AsResolveAndRetain(PUVM pUVM, RTDBGAS hAlias)
+{
+ return NIL_RTDBGAS;
+}
+VMMR3DECL(int) DBGFR3AsLineByAddr(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress,
+ PRTGCINTPTR poffDisp, PRTDBGLINE pLine, PRTDBGMOD phMod)
+{
+ return VERR_DBG_LINE_NOT_FOUND;
+}
+VMMR3DECL(int) DBGFR3Resume(PUVM pUVM, VMCPUID idCpu)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3StackWalkBegin(PUVM pUVM, VMCPUID idCpu, DBGFCODETYPE enmCodeType, PCDBGFSTACKFRAME *ppFirstFrame)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(PCDBGFSTACKFRAME) DBGFR3StackWalkNext(PCDBGFSTACKFRAME pCurrent)
+{
+ return NULL;
+}
+VMMR3DECL(void) DBGFR3StackWalkEnd(PCDBGFSTACKFRAME pFirstFrame)
+{
+}
+VMMR3DECL(int) DBGFR3StepEx(PUVM pUVM, VMCPUID idCpu, uint32_t fFlags, PCDBGFADDRESS pStopPcAddr,
+ PCDBGFADDRESS pStopPopAddr, RTGCUINTPTR cbStopPop, uint32_t cMaxSteps)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3AsSymbolByAddr(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress, uint32_t fFlags, PRTGCINTPTR poffDisplacement, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(PRTDBGSYMBOL) DBGFR3AsSymbolByAddrA(PUVM pUVM, RTDBGAS hDbgAs, PCDBGFADDRESS pAddress, uint32_t fFlags,
+ PRTGCINTPTR poffDisp, PRTDBGMOD phMod)
+{
+ return NULL;
+}
+VMMR3DECL(int) DBGFR3AsSymbolByName(PUVM pUVM, RTDBGAS hDbgAs, const char *pszSymbol, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3AsLinkModule(PUVM pUVM, RTDBGAS hDbgAs, RTDBGMOD hMod, PCDBGFADDRESS pModAddress, RTDBGSEGIDX iModSeg, uint32_t fFlags)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3ModInMem(PUVM pUVM, PCDBGFADDRESS pImageAddr, uint32_t fFlags, const char *pszName, const char *pszFilename,
+ RTLDRARCH enmArch, uint32_t cbImage, PRTDBGMOD phDbgMod, PRTERRINFO pErrInfo)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3MemScan(PUVM pUVM, VMCPUID idCpu, PCDBGFADDRESS pAddress, RTGCUINTPTR cbRange, RTGCUINTPTR uAlign, const void *pabNeedle, size_t cbNeedle, PDBGFADDRESS pHitAddress)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3MemRead(PUVM pUVM, VMCPUID idCpu, PCDBGFADDRESS pAddress, void *pvBuf, size_t cbRead)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3MemReadString(PUVM pUVM, VMCPUID idCpu, PCDBGFADDRESS pAddress, char *pszBuf, size_t cchBuf)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3MemWrite(PUVM pUVM, VMCPUID idCpu, PCDBGFADDRESS pAddress, const void *pvBuf, size_t cbRead)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMDECL(int) DBGFR3PagingDumpEx(PUVM pUVM, VMCPUID idCpu, uint32_t fFlags, uint64_t cr3, uint64_t u64FirstAddr,
+ uint64_t u64LastAddr, uint32_t cMaxDepth, PCDBGFINFOHLP pHlp)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegNmValidate(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg)
+{
+ if ( !strcmp(pszReg, "ah")
+ || !strcmp(pszReg, "ax")
+ || !strcmp(pszReg, "eax")
+ || !strcmp(pszReg, "rax"))
+ return VINF_SUCCESS;
+ return VERR_DBGF_REGISTER_NOT_FOUND;
+}
+VMMR3DECL(const char *) DBGFR3RegCpuName(PUVM pUVM, DBGFREG enmReg, DBGFREGVALTYPE enmType)
+{
+ return NULL;
+}
+VMMR3DECL(int) DBGFR3RegCpuQueryU8( PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint8_t *pu8)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegCpuQueryU16( PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint16_t *pu16)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegCpuQueryU32( PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint32_t *pu32)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegCpuQueryU64( PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint64_t *pu64)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegCpuQueryXdtr(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint64_t *pu64Base, uint16_t *pu16Limit)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegNmQuery(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, PDBGFREGVAL pValue, PDBGFREGVALTYPE penmType)
+{
+ if (idDefCpu == 0 || idDefCpu == DBGFREG_HYPER_VMCPUID)
+ {
+ if (!strcmp(pszReg, "ah"))
+ {
+ pValue->u16 = 0xf0;
+ *penmType = DBGFREGVALTYPE_U8;
+ return VINF_SUCCESS;
+ }
+ if (!strcmp(pszReg, "ax"))
+ {
+ pValue->u16 = 0xbabe;
+ *penmType = DBGFREGVALTYPE_U16;
+ return VINF_SUCCESS;
+ }
+ if (!strcmp(pszReg, "eax"))
+ {
+ pValue->u32 = 0xcafebabe;
+ *penmType = DBGFREGVALTYPE_U32;
+ return VINF_SUCCESS;
+ }
+ if (!strcmp(pszReg, "rax"))
+ {
+ pValue->u64 = UINT64_C(0x00beef00feedface);
+ *penmType = DBGFREGVALTYPE_U32;
+ return VINF_SUCCESS;
+ }
+ }
+ return VERR_DBGF_REGISTER_NOT_FOUND;
+}
+VMMR3DECL(int) DBGFR3RegPrintf(PUVM pUVM, VMCPUID idCpu, char *pszBuf, size_t cbBuf, const char *pszFormat, ...)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMDECL(ssize_t) DBGFR3RegFormatValue(char *pszBuf, size_t cbBuf, PCDBGFREGVAL pValue, DBGFREGVALTYPE enmType, bool fSpecial)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3RegNmSet(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, PCDBGFREGVAL pValue, DBGFREGVALTYPE enmType)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(PDBGFADDRESS) DBGFR3AddrFromPhys(PUVM pUVM, PDBGFADDRESS pAddress, RTGCPHYS PhysAddr)
+{
+ return NULL;
+}
+VMMR3DECL(int) DBGFR3AddrToHostPhys(PUVM pUVM, VMCPUID idCpu, PDBGFADDRESS pAddress, PRTHCPHYS pHCPhys)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3AddrToVolatileR3Ptr(PUVM pUVM, VMCPUID idCpu, PDBGFADDRESS pAddress, bool fReadOnly, void **ppvR3Ptr)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) DBGFR3OSRegister(PUVM pUVM, PCDBGFOSREG pReg)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3OSDetect(PUVM pUVM, char *pszName, size_t cchName)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3OSQueryNameAndVersion(PUVM pUVM, char *pszName, size_t cchName, char *pszVersion, size_t cchVersion)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(void *) DBGFR3OSQueryInterface(PUVM pUVM, DBGFOSINTERFACE enmIf)
+{
+ return NULL;
+}
+
+VMMR3DECL(int) DBGFR3SelQueryInfo(PUVM pUVM, VMCPUID idCpu, RTSEL Sel, uint32_t fFlags, PDBGFSELINFO pSelInfo)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(CPUMMODE) DBGFR3CpuGetMode(PUVM pUVM, VMCPUID idCpu)
+{
+ return CPUMMODE_INVALID;
+}
+VMMR3DECL(VMCPUID) DBGFR3CpuGetCount(PUVM pUVM)
+{
+ return 1;
+}
+VMMR3DECL(bool) DBGFR3CpuIsIn64BitCode(PUVM pUVM, VMCPUID idCpu)
+{
+ return false;
+}
+VMMR3DECL(bool) DBGFR3CpuIsInV86Code(PUVM pUVM, VMCPUID idCpu)
+{
+ return false;
+}
+
+VMMR3DECL(int) DBGFR3CoreWrite(PUVM pUVM, const char *pszFilename, bool fReplaceFile)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) DBGFR3PlugInLoad(PUVM pUVM, const char *pszPlugIn, char *pszActual, size_t cbActual, PRTERRINFO pErrInfo)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3PlugInUnload(PUVM pUVM, const char *pszName)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(void) DBGFR3PlugInLoadAll(PUVM pUVM)
+{
+}
+VMMR3DECL(int) DBGFR3TypeRegister( PUVM pUVM, uint32_t cTypes, PCDBGFTYPEREG paTypes)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3TypeDeregister(PUVM pUVM, const char *pszType)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3TypeQueryReg( PUVM pUVM, const char *pszType, PCDBGFTYPEREG *ppTypeReg)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3TypeQuerySize( PUVM pUVM, const char *pszType, size_t *pcbType)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3TypeSetSize( PUVM pUVM, const char *pszType, size_t cbType)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3TypeDumpEx( PUVM pUVM, const char *pszType, uint32_t fFlags,
+ uint32_t cLvlMax, PFNDBGFR3TYPEDUMP pfnDump, void *pvUser)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3TypeQueryValByType(PUVM pUVM, PCDBGFADDRESS pAddress, const char *pszType,
+ PDBGFTYPEVAL *ppVal)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(void) DBGFR3TypeValFree(PDBGFTYPEVAL pVal)
+{
+}
+VMMR3DECL(int) DBGFR3TypeValDumpEx(PUVM pUVM, PCDBGFADDRESS pAddress, const char *pszType, uint32_t fFlags,
+ uint32_t cLvlMax, FNDBGFR3TYPEVALDUMP pfnDump, void *pvUser)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) DBGFR3FlowCreate(PUVM pUVM, VMCPUID idCpu, PDBGFADDRESS pAddressStart, uint32_t cbDisasmMax,
+ uint32_t fFlagsFlow, uint32_t fFlagsDisasm, PDBGFFLOW phFlow)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowRetain(DBGFFLOW hFlow)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowRelease(DBGFFLOW hFlow)
+{
+ return 0;
+}
+VMMR3DECL(int) DBGFR3FlowQueryStartBb(DBGFFLOW hFlow, PDBGFFLOWBB phFlowBb)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowQueryBbByAddress(DBGFFLOW hFlow, PDBGFADDRESS pAddr, PDBGFFLOWBB phFlowBb)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowQueryBranchTblByAddress(DBGFFLOW hFlow, PDBGFADDRESS pAddr, PDBGFFLOWBRANCHTBL phFlowBranchTbl)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowGetBbCount(DBGFFLOW hFlow)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowGetBranchTblCount(DBGFFLOW hFlow)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBbRetain(DBGFFLOWBB hFlowBb)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBbRelease(DBGFFLOWBB hFlowBb)
+{
+ return 0;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowBbGetStartAddress(DBGFFLOWBB hFlowBb, PDBGFADDRESS pAddrStart)
+{
+ return NULL;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowBbGetEndAddress(DBGFFLOWBB hFlowBb, PDBGFADDRESS pAddrEnd)
+{
+ return NULL;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowBbGetBranchAddress(DBGFFLOWBB hFlowBb, PDBGFADDRESS pAddrTarget)
+{
+ return NULL;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowBbGetFollowingAddress(DBGFFLOWBB hFlowBb, PDBGFADDRESS pAddrFollow)
+{
+ return NULL;
+}
+VMMR3DECL(DBGFFLOWBBENDTYPE) DBGFR3FlowBbGetType(DBGFFLOWBB hFlowBb)
+{
+ return DBGFFLOWBBENDTYPE_INVALID;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBbGetInstrCount(DBGFFLOWBB hFlowBb)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBbGetFlags(DBGFFLOWBB hFlowBb)
+{
+ return 0;
+}
+VMMR3DECL(int) DBGFR3FlowBbQueryBranchTbl(DBGFFLOWBB hFlowBb, PDBGFFLOWBRANCHTBL phBranchTbl)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowBbQueryError(DBGFFLOWBB hFlowBb, const char **ppszErr)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowBbQueryInstr(DBGFFLOWBB hFlowBb, uint32_t idxInstr, PDBGFADDRESS pAddrInstr,
+ uint32_t *pcbInstr, const char **ppszInstr)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowBbQuerySuccessors(DBGFFLOWBB hFlowBb, PDBGFFLOWBB phFlowBbFollow,
+ PDBGFFLOWBB phFlowBbTarget)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBbGetRefBbCount(DBGFFLOWBB hFlowBb)
+{
+ return 0;
+}
+VMMR3DECL(int) DBGFR3FlowBbGetRefBb(DBGFFLOWBB hFlowBb, PDBGFFLOWBB pahFlowBbRef, uint32_t cRef)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBranchTblRetain(DBGFFLOWBRANCHTBL hFlowBranchTbl)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBranchTblRelease(DBGFFLOWBRANCHTBL hFlowBranchTbl)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowBranchTblGetSlots(DBGFFLOWBRANCHTBL hFlowBranchTbl)
+{
+ return 0;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowBranchTblGetStartAddress(DBGFFLOWBRANCHTBL hFlowBranchTbl, PDBGFADDRESS pAddrStart)
+{
+ return NULL;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowBranchTblGetAddrAtSlot(DBGFFLOWBRANCHTBL hFlowBranchTbl, uint32_t idxSlot, PDBGFADDRESS pAddrSlot)
+{
+ return NULL;
+}
+VMMR3DECL(int) DBGFR3FlowBranchTblQueryAddresses(DBGFFLOWBRANCHTBL hFlowBranchTbl, PDBGFADDRESS paAddrs, uint32_t cAddrs)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowItCreate(DBGFFLOW hFlow, DBGFFLOWITORDER enmOrder, PDBGFFLOWIT phFlowIt)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(void) DBGFR3FlowItDestroy(DBGFFLOWIT hFlowIt)
+{
+}
+VMMR3DECL(DBGFFLOWBB) DBGFR3FlowItNext(DBGFFLOWIT hFlowIt)
+{
+ return NULL;
+}
+VMMR3DECL(int) DBGFR3FlowItReset(DBGFFLOWIT hFlowIt)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowBranchTblItCreate(DBGFFLOW hFlow, DBGFFLOWITORDER enmOrder, PDBGFFLOWBRANCHTBLIT phFlowBranchTblIt)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(void) DBGFR3FlowBranchTblItDestroy(DBGFFLOWBRANCHTBLIT hFlowBranchTblIt)
+{
+}
+VMMR3DECL(DBGFFLOWBRANCHTBL) DBGFR3FlowBranchTblItNext(DBGFFLOWBRANCHTBLIT hFlowBranchTblIt)
+{
+ return NULL;
+}
+VMMR3DECL(int) DBGFR3FlowBranchTblItReset(DBGFFLOWBRANCHTBLIT hFlowBranchTblIt)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceModCreateFromFlowGraph(PUVM pUVM, VMCPUID idCpu, DBGFFLOW hFlow,
+ DBGFFLOWTRACEPROBE hFlowTraceProbeCommon,
+ DBGFFLOWTRACEPROBE hFlowTraceProbeEntry,
+ DBGFFLOWTRACEPROBE hFlowTraceProbeRegular,
+ DBGFFLOWTRACEPROBE hFlowTraceProbeExit,
+ PDBGFFLOWTRACEMOD phFlowTraceMod)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceModRetain(DBGFFLOWTRACEMOD hFlowTraceMod)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceModRelease(DBGFFLOWTRACEMOD hFlowTraceMod)
+{
+ return 0;
+}
+VMMR3DECL(int) DBGFR3FlowTraceModEnable(DBGFFLOWTRACEMOD hFlowTraceMod, uint32_t cHits, uint32_t cRecordsMax)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceModDisable(DBGFFLOWTRACEMOD hFlowTraceMod)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceModQueryReport(DBGFFLOWTRACEMOD hFlowTraceMod,
+ PDBGFFLOWTRACEREPORT phFlowTraceReport)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceModClear(DBGFFLOWTRACEMOD hFlowTraceMod)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceModAddProbe(DBGFFLOWTRACEMOD hFlowTraceMod, PCDBGFADDRESS pAddrProbe,
+ DBGFFLOWTRACEPROBE hFlowTraceProbe, uint32_t fFlags)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceProbeCreate(PUVM pUVM, const char *pszDescr, PDBGFFLOWTRACEPROBE phFlowTraceProbe)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceProbeRetain(DBGFFLOWTRACEPROBE hFlowTraceProbe)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceProbeRelease(DBGFFLOWTRACEPROBE hFlowTraceProbe)
+{
+ return 0;
+}
+VMMR3DECL(int) DBGFR3FlowTraceProbeEntriesAdd(DBGFFLOWTRACEPROBE hFlowTraceProbe,
+ PCDBGFFLOWTRACEPROBEENTRY paEntries, uint32_t cEntries)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceReportRetain(DBGFFLOWTRACEREPORT hFlowTraceReport)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceReportRelease(DBGFFLOWTRACEREPORT hFlowTraceReport)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceReportGetRecordCount(DBGFFLOWTRACEREPORT hFlowTraceReport)
+{
+ return 0;
+}
+VMMR3DECL(int) DBGFR3FlowTraceReportQueryRecord(DBGFFLOWTRACEREPORT hFlowTraceReport, uint32_t idxRec, PDBGFFLOWTRACERECORD phFlowTraceRec)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceReportQueryFiltered(DBGFFLOWTRACEREPORT hFlowTraceReport, uint32_t fFlags,
+ PDBGFFLOWTRACEREPORTFILTER paFilters, uint32_t cFilters,
+ DBGFFLOWTRACEREPORTFILTEROP enmOp,
+ PDBGFFLOWTRACEREPORT phFlowTraceReportFiltered)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) DBGFR3FlowTraceReportEnumRecords(DBGFFLOWTRACEREPORT hFlowTraceReport,
+ PFNDBGFFLOWTRACEREPORTENUMCLBK pfnEnum,
+ void *pvUser)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceRecordRetain(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return 0;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceRecordRelease(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return 0;
+}
+VMMR3DECL(uint64_t) DBGFR3FlowTraceRecordGetSeqNo(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return 0;
+}
+VMMR3DECL(uint64_t) DBGFR3FlowTraceRecordGetTimestamp(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return 0;
+}
+VMMR3DECL(PDBGFADDRESS) DBGFR3FlowTraceRecordGetAddr(DBGFFLOWTRACERECORD hFlowTraceRecord, PDBGFADDRESS pAddr)
+{
+ return NULL;
+}
+VMMR3DECL(DBGFFLOWTRACEPROBE) DBGFR3FlowTraceRecordGetProbe(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return NULL;
+}
+VMMR3DECL(uint32_t) DBGFR3FlowTraceRecordGetValCount(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return 0;
+}
+VMMR3DECL(PCDBGFFLOWTRACEPROBEVAL) DBGFR3FlowTraceRecordGetVals(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return NULL;
+}
+VMMR3DECL(PCDBGFFLOWTRACEPROBEVAL) DBGFR3FlowTraceRecordGetValsCommon(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return NULL;
+}
+VMMR3DECL(VMCPUID) DBGFR3FlowTraceRecordGetCpuId(DBGFFLOWTRACERECORD hFlowTraceRecord)
+{
+ return 0;
+}
+
+VMMR3DECL(int) DBGFR3FormatBugCheck(PUVM pUVM, char *pszDetails, size_t cbDetails,
+ uint64_t uP0, uint64_t uP1, uint64_t uP2, uint64_t uP3, uint64_t uP4)
+{
+ pszDetails[0] = '\0';
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(PDBGFADDRESS) DBGFR3AddrAdd(PDBGFADDRESS pAddress, RTGCUINTPTR uAddend)
+{
+ RT_NOREF(uAddend);
+ return pAddress;
+}
+
+#include <VBox/vmm/cfgm.h>
+VMMR3DECL(int) CFGMR3ValidateConfig(PCFGMNODE pNode, const char *pszNode,
+ const char *pszValidValues, const char *pszValidNodes,
+ const char *pszWho, uint32_t uInstance)
+{
+ return VINF_SUCCESS;
+}
+
+VMMR3DECL(PCFGMNODE) CFGMR3GetRootU(PUVM pUVM)
+{
+ return NULL;
+}
+
+VMMR3DECL(PCFGMNODE) CFGMR3GetChild(PCFGMNODE pNode, const char *pszPath)
+{
+ return NULL;
+}
+
+VMMR3DECL(int) CFGMR3QueryString(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString)
+{
+ *pszString = '\0';
+ return VINF_SUCCESS;
+}
+
+VMMR3DECL(int) CFGMR3QueryStringDef(PCFGMNODE pNode, const char *pszName, char *pszString, size_t cchString, const char *pszDef)
+{
+ *pszString = '\0';
+ return VINF_SUCCESS;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// The rest should eventually be replaced by DBGF calls and eliminated. //
+/////////////////////////////////////////////////////////////////////////
+
+
+#include <VBox/vmm/cpum.h>
+
+VMMDECL(uint64_t) CPUMGetGuestCR3(PCVMCPU pVCpu)
+{
+ return 0;
+}
+
+VMMDECL(uint64_t) CPUMGetGuestCR4(PCVMCPU pVCpu)
+{
+ return 0;
+}
+
+VMMDECL(RTSEL) CPUMGetGuestCS(PCVMCPU pVCpu)
+{
+ return 0;
+}
+
+VMMDECL(uint32_t) CPUMGetGuestEIP(PCVMCPU pVCpu)
+{
+ return 0;
+}
+
+VMMDECL(uint64_t) CPUMGetGuestRIP(PCVMCPU pVCpu)
+{
+ return 0;
+}
+
+VMMDECL(RTGCPTR) CPUMGetGuestIDTR(PCVMCPU pVCpu, uint16_t *pcbLimit)
+{
+ return 0;
+}
+
+VMMDECL(CPUMMODE) CPUMGetGuestMode(PVMCPU pVCpu)
+{
+ return CPUMMODE_INVALID;
+}
+
+VMMDECL(PCPUMCTX) CPUMQueryGuestCtxPtr(PVMCPU pVCpu)
+{
+ return NULL;
+}
+
+VMMDECL(bool) CPUMIsGuestIn64BitCode(PVMCPU pVCpu)
+{
+ return false;
+}
+
+VMMDECL(uint32_t) CPUMGetGuestEFlags(PCVMCPU pVCpu)
+{
+ return 2;
+}
+
+#include <VBox/vmm/hm.h>
+VMMR3DECL(bool) HMR3IsEnabled(PUVM pUVM)
+{
+ return true;
+}
+
+
+#include <VBox/vmm/nem.h>
+VMMR3DECL(bool) NEMR3IsEnabled(PUVM pUVM)
+{
+ return true;
+}
+
+
+#include <VBox/vmm/pgm.h>
+
+VMMDECL(RTHCPHYS) PGMGetHyperCR3(PVMCPU pVCpu)
+{
+ return 0;
+}
+
+VMMDECL(PGMMODE) PGMGetShadowMode(PVMCPU pVCpu)
+{
+ return PGMMODE_INVALID;
+}
+
+VMMR3DECL(int) PGMR3DbgR3Ptr2GCPhys(PUVM pUVM, RTR3PTR R3Ptr, PRTGCPHYS pGCPhys)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+VMMR3DECL(int) PGMR3DbgR3Ptr2HCPhys(PUVM pUVM, RTR3PTR R3Ptr, PRTHCPHYS pHCPhys)
+{
+ return VERR_INTERNAL_ERROR;
+}
+VMMR3DECL(int) PGMR3DbgHCPhys2GCPhys(PUVM pUVM, RTHCPHYS HCPhys, PRTGCPHYS pGCPhys)
+{
+ return VERR_INTERNAL_ERROR;
+}
+
+
+#include <VBox/vmm/vmm.h>
+
+VMMR3DECL(PVMCPU) VMMR3GetCpuByIdU(PUVM pUVM, RTCPUID idCpu)
+{
+ return NULL;
+}
+
+VMMR3DECL(PCVMMR3VTABLE) VMMR3GetVTable(void)
+{
+ return NULL;
+}
+
+VMMR3DECL(PVM) VMR3GetVM(PUVM pUVM)
+{
+ return NULL;
+}
+
+VMMR3DECL(VMSTATE) VMR3GetStateU(PUVM pUVM)
+{
+ return VMSTATE_DESTROYING;
+}