diff options
Diffstat (limited to 'src/VBox/Debugger')
48 files changed, 45858 insertions, 0 deletions
diff --git a/src/VBox/Debugger/.scm-settings b/src/VBox/Debugger/.scm-settings new file mode 100644 index 00000000..68ceabc0 --- /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-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +/*.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..8ffc56e8 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..ef8aae67 --- /dev/null +++ b/src/VBox/Debugger/DBGCCmdHlp.cpp @@ -0,0 +1,1484 @@ +/* $Id: DBGCCmdHlp.cpp $ */ +/** @file + * DBGC - Debugger Console, Command Helpers. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..c3b34853 --- /dev/null +++ b/src/VBox/Debugger/DBGCCmdWorkers.cpp @@ -0,0 +1,378 @@ +/* $Id: DBGCCmdWorkers.cpp $ */ +/** @file + * DBGC - Debugger Console, Command Worker Routines. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns nothing. + * @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..b22639b9 --- /dev/null +++ b/src/VBox/Debugger/DBGCCommands.cpp @@ -0,0 +1,2072 @@ +/* $Id: DBGCCommands.cpp $ */ +/** @file + * DBGC - Debugger Console, Native Commands. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..73042b80 --- /dev/null +++ b/src/VBox/Debugger/DBGCDumpImage.cpp @@ -0,0 +1,807 @@ +/* $Id: DBGCDumpImage.cpp $ */ +/** @file + * DBGC - Debugger Console, Native Commands. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..a102a13c --- /dev/null +++ b/src/VBox/Debugger/DBGCEmulateCodeView.cpp @@ -0,0 +1,6989 @@ +/* $Id: DBGCEmulateCodeView.cpp $ */ +/** @file + * DBGC - Debugger Console, CodeView / WinDbg Emulation. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns VBox status code. + * @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..be16fa81 --- /dev/null +++ b/src/VBox/Debugger/DBGCEval.cpp @@ -0,0 +1,1663 @@ +/* $Id: DBGCEval.cpp $ */ +/** @file + * DBGC - Debugger Console, command evaluator. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..9ca727f8 --- /dev/null +++ b/src/VBox/Debugger/DBGCFunctions.cpp @@ -0,0 +1,128 @@ +/* $Id: DBGCFunctions.cpp $ */ +/** @file + * DBGC - Debugger Console, Native Functions. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..e54d18cf --- /dev/null +++ b/src/VBox/Debugger/DBGCGdbRemoteStub.cpp @@ -0,0 +1,2868 @@ +/* $Id: DBGCGdbRemoteStub.cpp $ */ +/** @file + * DBGC - Debugger Console, GDB Remote Stub. + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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). + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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..46cc25cc --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..6a791fff --- /dev/null +++ b/src/VBox/Debugger/DBGCIo.cpp @@ -0,0 +1,612 @@ +/* $Id: DBGCIo.cpp $ */ +/** @file + * DBGC - Debugger Console, I/O provider handling. + */ + +/* + * Copyright (C) 2020-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns nothing. + * @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..3bec41ca --- /dev/null +++ b/src/VBox/Debugger/DBGCIoProvInternal.h @@ -0,0 +1,116 @@ +/* $Id: DBGCIoProvInternal.h $ */ +/** @file + * DBGC - Debugger Console, Internal I/O provider header file. + */ + +/* + * Copyright (C) 2020-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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. + * + * @returns nothing. + * @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..4ae34ede --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..1f207515 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..8e065a18 --- /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 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..16bdf11b --- /dev/null +++ b/src/VBox/Debugger/DBGCOps.cpp @@ -0,0 +1,1380 @@ +/* $Id: DBGCOps.cpp $ */ +/** @file + * DBGC - Debugger Console, Operators. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..5a9ae277 --- /dev/null +++ b/src/VBox/Debugger/DBGCRemoteKd.cpp @@ -0,0 +1,4557 @@ +/* $Id: DBGCRemoteKd.cpp $ */ +/** @file + * DBGC - Debugger Console, Windows Kd Remote Stub. + */ + +/* + * Copyright (C) 2020-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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 nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns Nothing. + * @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. + * + * @returns nothing. + * @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..417e898c --- /dev/null +++ b/src/VBox/Debugger/DBGCScreenAscii.cpp @@ -0,0 +1,446 @@ +/* $Id: DBGCScreenAscii.cpp $ */ +/** @file + * DBGC - Debugger Console, ASCII screen with optional coloring support. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns nothing. + * @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 nothing. + * @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..d6fd355d --- /dev/null +++ b/src/VBox/Debugger/DBGConsole.cpp @@ -0,0 +1,1378 @@ +/* $Id: DBGConsole.cpp $ */ +/** @file + * DBGC - Debugger Console. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_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..7e0ca9e1 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..88911433 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..4d8ef93e --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..4fe5715d --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..b3dd001b --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..1d373c34 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..9712fe77 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInLinux.cpp @@ -0,0 +1,3046 @@ +/* $Id: DBGPlugInLinux.cpp $ */ +/** @file + * DBGPlugInLinux - Debugger and Guest OS Digger Plugin For Linux. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns nothing. + * @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..6b60058b --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..ec8797cc --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + { 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..cc4593fb --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* + * 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..576a6fca --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..6e111730 --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..4ca4283e --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInWinNt.cpp @@ -0,0 +1,1778 @@ +/* $Id: DBGPlugInWinNt.cpp $ */ +/** @file + * DBGPlugInWindows - Debugger and Guest OS Digger Plugin For Windows NT. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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..09f8f07c --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..f8fec994 --- /dev/null +++ b/src/VBox/Debugger/Makefile.kmk @@ -0,0 +1,136 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for the VBox debugger. +# + +# +# Copyright (C) 2006-2022 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +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 = VBOXR3 +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 = VBOXR3 +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 = VBOXQTGUI +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..5042eb9c --- /dev/null +++ b/src/VBox/Debugger/VBoxDbg.cpp @@ -0,0 +1,275 @@ +/* $Id: VBoxDbg.cpp $ */ +/** @file + * VBox Debugger GUI. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..e3aa8300 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgBase.cpp @@ -0,0 +1,326 @@ +/* $Id: VBoxDbgBase.cpp $ */ +/** @file + * VBox Debugger GUI - Base classes. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..17d8f27e --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgBase.h @@ -0,0 +1,222 @@ +/* $Id: VBoxDbgBase.h $ */ +/** @file + * VBox Debugger GUI - Base classes. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..c5e2f430 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgConsole.cpp @@ -0,0 +1,1033 @@ +/* $Id: VBoxDbgConsole.cpp $ */ +/** @file + * VBox Debugger GUI - Console. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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)); +} + + +/** + * 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) +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..a39654ae --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgConsole.h @@ -0,0 +1,447 @@ +/* $Id: VBoxDbgConsole.h $ */ +/** @file + * VBox Debugger GUI - Console. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..5c14fb39 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgDisas.h @@ -0,0 +1,53 @@ +/* $Id: VBoxDbgDisas.h $ */ +/** @file + * VBox Debugger GUI - Disassembly View. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..8ebb831d --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgGui.cpp @@ -0,0 +1,313 @@ +/* $Id: VBoxDbgGui.cpp $ */ +/** @file + * VBox Debugger GUI - The Manager. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..e854c487 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgGui.h @@ -0,0 +1,225 @@ +/* $Id: VBoxDbgGui.h $ */ +/** @file + * VBox Debugger GUI - The Manager. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..57131f8d --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgStatsQt.cpp @@ -0,0 +1,3330 @@ +/* $Id: VBoxDbgStatsQt.cpp $ */ +/** @file + * VBox Debugger GUI - Statistics. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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. + * + * @returns The current node. + * @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..51a8b079 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgStatsQt.h @@ -0,0 +1,272 @@ +/* $Id: VBoxDbgStatsQt.h $ */ +/** @file + * VBox Debugger GUI - Statistics. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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..20862757 --- /dev/null +++ b/src/VBox/Debugger/testcase/tstDBGCParser.cpp @@ -0,0 +1,1300 @@ +/* $Id: tstDBGCParser.cpp $ */ +/** @file + * DBGC Testcase - Command Parser. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* 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..11ab1cfc --- /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-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#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; +} |