diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 03:01:46 +0000 |
commit | f8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch) | |
tree | 26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Debugger | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Debugger')
41 files changed, 33228 insertions, 0 deletions
diff --git a/src/VBox/Debugger/.scm-settings b/src/VBox/Debugger/.scm-settings new file mode 100644 index 00000000..035f3a5b --- /dev/null +++ b/src/VBox/Debugger/.scm-settings @@ -0,0 +1,19 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the Debugger. +# + +# +# Copyright (C) 2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +/*.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..12516800 --- /dev/null +++ b/src/VBox/Debugger/DBGCBuiltInSymbols.cpp @@ -0,0 +1,41 @@ +/* $Id: DBGCBuiltInSymbols.cpp $ */ +/** @file + * DBGC - Debugger Console, Built-In Symbols. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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..46d4b557 --- /dev/null +++ b/src/VBox/Debugger/DBGCCmdHlp.cpp @@ -0,0 +1,1381 @@ +/* $Id: DBGCCmdHlp.cpp $ */ +/** @file + * DBGC - Debugger Console, Command Helpers. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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->pBack->pfnWrite(pDbgc->pBack, pachChars, cbChars, NULL); + 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: + case DBGCVAR_TYPE_HC_FLAT: + { + DBGCVAR Var2; + rc = dbgcOpAddrFlat(pDbgc, &Var, DBGCVAR_CAT_ANY, &Var2); + if (RT_SUCCESS(rc)) + { + /** @todo protect this!!! */ + memcpy(pvBuffer, Var2.u.pvHCFlat, cb); + rc = 0; + } + else + rc = VERR_INVALID_POINTER; + 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_FLAT: + 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; + + /** @todo protect this!!! */ + 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; + } + + 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; + } + + 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->fRegCtxGuest) + { + 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 + } + else + enmMode = CPUMMODE_PROTECTED; + return enmMode; +} + + +/** + * 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.u32EndMarker = DBGCCMDHLP_MAGIC; +} + diff --git a/src/VBox/Debugger/DBGCCmdWorkers.cpp b/src/VBox/Debugger/DBGCCmdWorkers.cpp new file mode 100644 index 00000000..c9ce701d --- /dev/null +++ b/src/VBox/Debugger/DBGCCmdWorkers.cpp @@ -0,0 +1,229 @@ +/* $Id: DBGCCmdWorkers.cpp $ */ +/** @file + * DBGC - Debugger Console, Command Worker Routines. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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 = dbgcEvalCommand(pDbgc, pszScratch, pBp->cchCmd, false /* fNoExecute */); + + /* Restore the scratch state. */ + pDbgc->iArg = iArg; + pDbgc->pszScratch = pszScratch; + + return rc; +} + diff --git a/src/VBox/Debugger/DBGCCommands.cpp b/src/VBox/Debugger/DBGCCommands.cpp new file mode 100644 index 00000000..31de0c55 --- /dev/null +++ b/src/VBox/Debugger/DBGCCommands.cpp @@ -0,0 +1,1859 @@ +/* $Id: DBGCCommands.cpp $ */ +/** @file + * DBGC - Debugger Console, Native Commands. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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/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 dbgcCmdUnload; +static FNDBGCCMD dbgcCmdSet; +static FNDBGCCMD dbgcCmdUnset; +static FNDBGCCMD dbgcCmdLoadVars; +static FNDBGCCMD dbgcCmdShowVars; +static FNDBGCCMD dbgcCmdLoadPlugIn; +static FNDBGCCMD dbgcCmdUnloadPlugIn; +static FNDBGCCMD dbgcCmdHarakiri; +static FNDBGCCMD dbgcCmdEcho; +static FNDBGCCMD dbgcCmdRunScript; +static FNDBGCCMD dbgcCmdWriteCore; + + +/********************************************************************************************************************************* +* 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!)." } +}; + + +/** 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." }, +}; + +/** 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." }, +}; + + + +/** 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." }, + { "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." }, + { "stop", 0, 0, NULL, 0, 0, dbgcCmdStop, "", "Stop execution." }, + { "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." }, +}; +/** 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)); + 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, false, 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 '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) +{ + /* + * Check if the VM is halted or not before trying to halt it. + */ + int rc; + if (DBGFR3IsHalted(pUVM)) + rc = DBGCCmdHlpPrintf(pCmdHlp, "warning: The VM is already halted...\n"); + else + { + rc = DBGFR3Halt(pUVM); + if (RT_SUCCESS(rc)) + rc = VWRN_DBGC_CMD_PENDING; + else + rc = DBGCCmdHlpVBoxError(pCmdHlp, rc, "Executing DBGFR3Halt()."); + } + + NOREF(pCmd); NOREF(paArgs); NOREF(cArgs); + 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, 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, 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 = RTLogGetGroupSettings(NULL, szBuf, sizeof(szBuf)); + if (RT_FAILURE(rc)) + return DBGCCmdHlpVBoxError(pCmdHlp, rc, "RTLogGetDestinations(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 = RTLogGetDestinations(NULL, szBuf, sizeof(szBuf)); + if (RT_FAILURE(rc)) + return DBGCCmdHlpVBoxError(pCmdHlp, rc, "RTLogGetDestinations(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 = RTLogGetFlags(NULL, szBuf, sizeof(szBuf)); + if (RT_FAILURE(rc)) + return DBGCCmdHlpVBoxError(pCmdHlp, rc, "RTLogGetFlags(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 '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; +} + diff --git a/src/VBox/Debugger/DBGCDumpImage.cpp b/src/VBox/Debugger/DBGCDumpImage.cpp new file mode 100644 index 00000000..75818fa7 --- /dev/null +++ b/src/VBox/Debugger/DBGCDumpImage.cpp @@ -0,0 +1,491 @@ +/* $Id: DBGCDumpImage.cpp $ */ +/** @file + * DBGC - Debugger Console, Native Commands. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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 "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; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +extern FNDBGCCMD dbgcCmdDumpImage; /* See DBGCCommands.cpp. */ + + +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; +} + + +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; +} + + +/** + * @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; + } 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); + /* + * 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..a0811bfc --- /dev/null +++ b/src/VBox/Debugger/DBGCEmulateCodeView.cpp @@ -0,0 +1,6478 @@ +/* $Id: DBGCEmulateCodeView.cpp $ */ +/** @file + * DBGC - Debugger Console, CodeView / WinDbg Emulation. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGC +#include <VBox/dbg.h> +#include <VBox/vmm/dbgf.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 <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 dbgcCmdMemoryInfo; +static FNDBGCCMD dbgcCmdReg; +static FNDBGCCMD dbgcCmdRegGuest; +static FNDBGCCMD dbgcCmdRegHyper; +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; + + +/********************************************************************************************************************************* +* 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." }, +}; + + +/** '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." }, +}; + + +/** 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, 0, NULL, 0, 0, dbgcCmdGo, "", "Continue execution." }, + { "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." }, + { "rh", 0, 3, &g_aArgReg[0], RT_ELEMENTS(g_aArgReg), 0, dbgcCmdRegHyper, "[reg [[=] newval]]", "Show or set register(s) - hypervisor reg set." }, + { "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 ." }, + { "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." }, +}; + +/** 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_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); + + /* + * Check if the VM is halted or not before trying to resume it. + */ + if (!DBGFR3IsHalted(pUVM)) + return DBGCCmdHlpFail(pCmdHlp, pCmd, "The VM is already running"); + + int rc = DBGFR3Resume(pUVM); + if (RT_FAILURE(rc)) + return DBGCCmdHlpFailRc(pCmdHlp, pCmd, rc, "DBGFR3Resume"); + + NOREF(paArgs); NOREF(cArgs); + return VINF_SUCCESS; +} + + +/** + * @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 pBp Pointer to the breakpoint information. (readonly) + */ +static DECLCALLBACK(int) dbgcEnumBreakpointsCallback(PUVM pUVM, void *pvUser, PCDBGFBP pBp) +{ + PDBGC pDbgc = (PDBGC)pvUser; + PDBGCBP pDbgcBp = dbgcBpGet(pDbgc, pBp->iBp); + + /* + * BP type and size. + */ + DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "%#4x %c ", pBp->iBp, pBp->fEnabled ? 'e' : 'd'); + bool fHasAddress = false; + switch (pBp->enmType) + { + 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; + } + + case DBGFBPTYPE_REM: + DBGCCmdHlpPrintf(&pDbgc->CmdHlp, " r %RGv", pBp->u.Rem.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 = pBp->enmType == DBGFBPTYPE_PORT_IO ? pBp->u.PortIo.fAccess : pBp->u.Mmio.fAccess; + DBGCCmdHlpPrintf(&pDbgc->CmdHlp, pBp->enmType == 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 (pBp->enmType == 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!!", pBp->enmType); + 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 ( pDbgc->fRegCtxGuest + && 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 = pDbgc->fRegCtxGuest ? CPUMGetGuestEIP(pVCpu) : CPUMGetHyperEIP(pVCpu); + pDbgc->SourcePos.u.GCFar.sel = pDbgc->fRegCtxGuest ? CPUMGetGuestCS(pVCpu) : CPUMGetHyperCS(pVCpu); + if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE + && pDbgc->fRegCtxGuest + && (CPUMGetGuestEFlags(pVCpu) & X86_EFL_VM)) + { + fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK; + fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE; + } + } + + if (pDbgc->fRegCtxGuest) + fFlags |= DBGF_DISAS_FLAGS_CURRENT_GUEST; + else + fFlags |= DBGF_DISAS_FLAGS_CURRENT_HYPER | DBGF_DISAS_FLAGS_HYPER; + } + 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 | DBGF_DISAS_FLAGS_HYPER); + } + 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); + } + + if (CurAddr.fFlags & DBGFADDRESS_FLAGS_HMA) + fFlags |= DBGF_DISAS_FLAGS_HYPER; /* This crap is due to not using DBGFADDRESS as DBGFR3Disas* input. */ + 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 | DBGF_DISAS_FLAGS_CURRENT_HYPER); + + /* 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 ( pDbgc->fRegCtxGuest + && 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 = pDbgc->fRegCtxGuest ? CPUMGetGuestEIP(pVCpu) : CPUMGetHyperEIP(pVCpu); + pDbgc->SourcePos.u.GCFar.sel = pDbgc->fRegCtxGuest ? CPUMGetGuestCS(pVCpu) : CPUMGetHyperCS(pVCpu); + if ( (fFlags & DBGF_DISAS_FLAGS_MODE_MASK) == DBGF_DISAS_FLAGS_DEFAULT_MODE + && pDbgc->fRegCtxGuest + && (CPUMGetGuestEFlags(pVCpu) & X86_EFL_VM)) + { + fFlags &= ~DBGF_DISAS_FLAGS_MODE_MASK; + fFlags |= DBGF_DISAS_FLAGS_16BIT_REAL_MODE; + } + } + + if (pDbgc->fRegCtxGuest) + fFlags |= DBGF_DISAS_FLAGS_CURRENT_GUEST; + else + fFlags |= DBGF_DISAS_FLAGS_CURRENT_HYPER | DBGF_DISAS_FLAGS_HYPER; + } + 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 | DBGF_DISAS_FLAGS_HYPER); + } + 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 = pDbgc->fRegCtxGuest ? CPUMGetGuestEIP(pVCpu) : CPUMGetHyperEIP(pVCpu); + pDbgc->SourcePos.u.GCFar.sel = pDbgc->fRegCtxGuest ? CPUMGetGuestCS(pVCpu) : CPUMGetHyperCS(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) +{ + PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); + if (!pDbgc->fRegCtxGuest) + return dbgcCmdRegHyper(pCmd, pCmdHlp, pUVM, paArgs, 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)); + char szDisAndRegs[8192]; + int rc; + + if (pDbgc->fRegTerse) + { + if (f64BitMode) + rc = DBGFR3RegPrintf(pUVM, pDbgc->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(pUVM, pDbgc->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(pUVM, pDbgc->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(pUVM, pDbgc->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); + } + return dbgcCmdRegCommon(pCmd, pCmdHlp, pUVM, paArgs, cArgs, ""); +} + + +/** + * @callback_method_impl{FNDBGCCMD, The 'rh' command.} + */ +static DECLCALLBACK(int) dbgcCmdRegHyper(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs) +{ + /* + * Show all registers our selves. + */ + if (cArgs == 0) + { + PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); + char szDisAndRegs[8192]; + int rc; + + if (pDbgc->fRegTerse) + rc = DBGFR3RegPrintf(pUVM, pDbgc->idCpu | DBGFREG_HYPER_VMCPUID, szDisAndRegs, sizeof(szDisAndRegs), + "u %VR{cs}:%VR{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 + rc = DBGFR3RegPrintf(pUVM, pDbgc->idCpu | DBGFREG_HYPER_VMCPUID, 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} .dr6=%08VR{dr6}\n" + ".fs={%04VR{fs} base=%08VR{fs_base} limit=%08VR{fs_lim} flags=%04VR{fs_attr}} .cr3=%016VR{cr3}\n" + ".gs={%04VR{gs} base=%08VR{gs_base} limit=%08VR{gs_lim} flags=%04VR{gs_attr}}\n" + ".ss={%04VR{ss} base=%08VR{ss_base} limit=%08VR{ss_lim} flags=%04VR{ss_attr}}\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" + ); + 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. + */ + return pCmdHlp->pfnExec(pCmdHlp, "%s", szDisAndRegs); + } + 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 = pCmd->pszCmd[1] == 'g' + || (pCmd->pszCmd[1] != 'h' && pDbgc->fRegCtxGuest); + 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 = 0; + 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 + ? pDbgc->fRegCtxGuest + : 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 |= pDbgc->fRegCtxGuest ? DBGFPGDMP_FLAGS_GUEST : DBGFPGDMP_FLAGS_SHADOW; + 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 + ? pDbgc->fRegCtxGuest + : 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) +{ + PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); + if (!cArgs) + { + /* + * Current cs:eip symbol. + */ + DBGCVAR AddrVar; + const char *pszFmtExpr = pDbgc->fRegCtxGuest ? "%%(cs:eip)" : "%%(.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 (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{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..c8a5a595 --- /dev/null +++ b/src/VBox/Debugger/DBGCEval.cpp @@ -0,0 +1,1563 @@ +/* $Id: DBGCEval.cpp $ */ +/** @file + * DBGC - Debugger Console, command evaluator. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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 (exclusing + * 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, qutoed 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) +{ + char *pszCmdInput = pszCmd; + + /* + * Skip blanks. + */ + while (RT_C_IS_BLANK(*pszCmd)) + pszCmd++, cchCmd--; + + /* external command? */ + bool const fExternal = *pszCmd == '.'; + if (fExternal) + pszCmd++, cchCmd--; + + /* + * Find arguments. + */ + char *pszArgs = pszCmd; + while (RT_C_IS_ALNUM(*pszArgs) || *pszArgs == '_') + pszArgs++; + if ( (*pszArgs && !RT_C_IS_BLANK(*pszArgs)) + || !RT_C_IS_ALPHA(*pszCmd)) + { + 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, pszArgs - pszCmd, 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). + */ + 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: + { + PCRTSTATUSMSG pErr = RTErrGet(rc); + if (strncmp(pErr->pszDefine, RT_STR_TUPLE("Unknown Status"))) + rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: %s (%d) - %s\n", pErr->pszDefine, rc, pErr->pszMsgFull); + else + rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Unknown error %d (%#x)!\n", rc, rc); + break; + } + } + } + + return rc; +} + + +/** + * 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..933d0249 --- /dev/null +++ b/src/VBox/Debugger/DBGCFunctions.cpp @@ -0,0 +1,118 @@ +/* $Id: DBGCFunctions.cpp $ */ +/** @file + * DBGC - Debugger Console, Native Functions. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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..72025dc2 --- /dev/null +++ b/src/VBox/Debugger/DBGCGdbRemoteStub.cpp @@ -0,0 +1,25 @@ +/* $Id: DBGCGdbRemoteStub.cpp $ */ +/** @file + * DBGC - Debugger Console, GDB Remote Stub. + */ + +/* + * Copyright (C) 2010-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/dbg.h> +#include <VBox/vmm/dbgf.h> +#include <iprt/errcore.h> + diff --git a/src/VBox/Debugger/DBGCInternal.h b/src/VBox/Debugger/DBGCInternal.h new file mode 100644 index 00000000..ad5cef08 --- /dev/null +++ b/src/VBox/Debugger/DBGCInternal.h @@ -0,0 +1,614 @@ +/* $Id: DBGCInternal.h $ */ +/** @file + * DBGC - Debugger Console, Internal Header File. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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> + + +/******************************************************************************* +* 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 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 backend callback structure. */ + PDBGCBACK pBack; + + /** 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; + + /** Indicates whether we're in guest (true) or hypervisor (false) register context. */ + bool fRegCtxGuest; + /** Indicates whether the register are terse or sparse. */ + bool fRegTerse; + /** Whether to display registers when tracing. */ + bool fStepTraceRegs; + /** Counter use to suppress the printing of the headers. */ + uint8_t cPagingHierarchyDumps; + + /** 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; + + /** 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 DECLCALLBACK(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 DECLCALLBACK(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 DECLCALLBACK(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 DECLCALLBACK(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); + +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 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 DECLCALLBACK(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, PDBGCBACK pBack, unsigned fFlags); +int dbgcRun(PDBGC pDbgc); +int dbgcProcessInput(PDBGC pDbgc, bool fNoExecute); +void dbgcDestroy(PDBGC pDbgc); + + +/******************************************************************************* +* 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/DBGCOps.cpp b/src/VBox/Debugger/DBGCOps.cpp new file mode 100644 index 00000000..d209e497 --- /dev/null +++ b/src/VBox/Debugger/DBGCOps.cpp @@ -0,0 +1,1370 @@ +/* $Id: DBGCOps.cpp $ */ +/** @file + * DBGC - Debugger Console, Operators. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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.u63Fraction); +#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/DBGCScreenAscii.cpp b/src/VBox/Debugger/DBGCScreenAscii.cpp new file mode 100644 index 00000000..0cdde6d4 --- /dev/null +++ b/src/VBox/Debugger/DBGCScreenAscii.cpp @@ -0,0 +1,436 @@ +/* $Id: DBGCScreenAscii.cpp $ */ +/** @file + * DBGC - Debugger Console, ASCII screen with optional coloring support. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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/DBGCTcp.cpp b/src/VBox/Debugger/DBGCTcp.cpp new file mode 100644 index 00000000..15d41dbf --- /dev/null +++ b/src/VBox/Debugger/DBGCTcp.cpp @@ -0,0 +1,294 @@ +/* $Id: DBGCTcp.cpp $ */ +/** @file + * DBGC - Debugger Console, TCP backend. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/dbg.h> +#include <VBox/vmm/cfgm.h> +#include <VBox/err.h> + +#include <iprt/thread.h> +#include <iprt/tcp.h> +#include <VBox/log.h> +#include <iprt/assert.h> + +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Debug console TCP backend instance data. + */ +typedef struct DBGCTCP +{ + /** The I/O backend for the console. */ + DBGCBACK Back; + /** The socket of the connection. */ + RTSOCKET Sock; + /** Connection status. */ + bool fAlive; +} DBGCTCP; +/** Pointer to the instance data of the console TCP backend. */ +typedef DBGCTCP *PDBGCTCP; + +/** Converts a pointer to DBGCTCP::Back to a pointer to DBGCTCP. */ +#define DBGCTCP_BACK2DBGCTCP(pBack) ( (PDBGCTCP)((char *)(pBack) - RT_UOFFSETOF(DBGCTCP, Back)) ) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) dbgcTcpConnection(RTSOCKET Sock, void *pvUser); + + + +/** + * 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) dbgcTcpBackInput(PDBGCBACK pBack, uint32_t cMillies) +{ + PDBGCTCP pDbgcTcp = DBGCTCP_BACK2DBGCTCP(pBack); + if (!pDbgcTcp->fAlive) + return false; + int rc = RTTcpSelectOne(pDbgcTcp->Sock, cMillies); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + pDbgcTcp->fAlive = false; + return rc != VERR_TIMEOUT; +} + + +/** + * 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) dbgcTcpBackRead(PDBGCBACK pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead) +{ + PDBGCTCP pDbgcTcp = DBGCTCP_BACK2DBGCTCP(pBack); + if (!pDbgcTcp->fAlive) + return VERR_INVALID_HANDLE; + int rc = RTTcpRead(pDbgcTcp->Sock, pvBuf, cbBuf, pcbRead); + if (RT_SUCCESS(rc) && pcbRead != NULL && *pcbRead == 0) + rc = VERR_NET_SHUTDOWN; + if (RT_FAILURE(rc)) + pDbgcTcp->fAlive = false; + return rc; +} + +/** + * 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) dbgcTcpBackWrite(PDBGCBACK pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten) +{ + PDBGCTCP pDbgcTcp = DBGCTCP_BACK2DBGCTCP(pBack); + if (!pDbgcTcp->fAlive) + return VERR_INVALID_HANDLE; + + /* + * 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 = RTTcpWrite(pDbgcTcp->Sock, "\r\n", 2); + 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 = RTTcpWrite(pDbgcTcp->Sock, pvBuf, cb); + } + if (RT_FAILURE(rc)) + { + pDbgcTcp->fAlive = false; + break; + } + + /* advance */ + cbLeft -= cb; + pvBuf = (const char *)pvBuf + cb; + } + + /* + * Set returned value and return. + */ + if (pcbWritten) + *pcbWritten = cbBuf - cbLeft; + return rc; +} + +/** @copydoc FNDBGCBACKSETREADY */ +static DECLCALLBACK(void) dbgcTcpBackSetReady(PDBGCBACK pBack, bool fReady) +{ + /* stub */ + NOREF(pBack); + NOREF(fReady); +} + + +/** + * Serve a TCP Server connection. + * + * @returns VBox status code. + * @returns VERR_TCP_SERVER_STOP to terminate the server loop forcing + * the RTTcpCreateServer() call to return. + * @param Sock The socket which the client is connected to. + * The call will close this socket. + * @param pvUser The VM handle. + */ +static DECLCALLBACK(int) dbgcTcpConnection(RTSOCKET Sock, void *pvUser) +{ + LogFlow(("dbgcTcpConnection: connection! Sock=%d pvUser=%p\n", Sock, pvUser)); + + /* + * Start the console. + */ + DBGCTCP DbgcTcp; + DbgcTcp.Back.pfnInput = dbgcTcpBackInput; + DbgcTcp.Back.pfnRead = dbgcTcpBackRead; + DbgcTcp.Back.pfnWrite = dbgcTcpBackWrite; + DbgcTcp.Back.pfnSetReady = dbgcTcpBackSetReady; + DbgcTcp.fAlive = true; + DbgcTcp.Sock = Sock; + int rc = DBGCCreate((PUVM)pvUser, &DbgcTcp.Back, 0); + LogFlow(("dbgcTcpConnection: disconnect rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Spawns a new thread with a TCP based debugging console service. + * + * @returns VBox status code. + * @param pUVM The user mode VM handle. + * @param ppvData Where to store a pointer to the instance data. + */ +DBGDECL(int) DBGCTcpCreate(PUVM pUVM, void **ppvData) +{ + /* + * Check what the configuration says. + */ + PCFGMNODE pKey = CFGMR3GetChild(CFGMR3GetRootU(pUVM), "DBGC"); + bool fEnabled; + int rc = CFGMR3QueryBoolDef(pKey, "Enabled", &fEnabled, +#if defined(VBOX_WITH_DEBUGGER) && defined(VBOX_WITH_DEBUGGER_TCP_BY_DEFAULT) && !defined(__L4ENV__) && !defined(DEBUG_dmik) + 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; + } + + /* + * Get the port configuration. + */ + uint32_t u32Port; + rc = CFGMR3QueryU32Def(pKey, "Port", &u32Port, 5000); + if (RT_FAILURE(rc)) + return VM_SET_ERROR_U(pUVM, rc, "Configuration error: Failed querying \"DBGC/Port\""); + + /* + * Get the address configuration. + */ + char szAddress[512]; + rc = CFGMR3QueryStringDef(pKey, "Address", szAddress, sizeof(szAddress), ""); + if (RT_FAILURE(rc)) + return VM_SET_ERROR_U(pUVM, rc, "Configuration error: Failed querying \"DBGC/Address\""); + + /* + * Create the server (separate thread). + */ + PRTTCPSERVER pServer; + rc = RTTcpServerCreate(szAddress, u32Port, RTTHREADTYPE_DEBUGGER, "DBGC", dbgcTcpConnection, pUVM, &pServer); + if (RT_SUCCESS(rc)) + { + LogFlow(("DBGCTcpCreate: Created server on port %d %s\n", u32Port, szAddress)); + *ppvData = pServer; + return rc; + } + + LogFlow(("DBGCTcpCreate: returns %Rrc\n", rc)); + return VM_SET_ERROR_U(pUVM, rc, "Cannot start TCP-based debugging console service"); +} + + +/** + * Terminates any running TCP base debugger console service. + * + * @returns VBox status code. + * @param pUVM The user mode VM handle. + * @param pvData The data returned by DBGCTcpCreate. + */ +DBGDECL(int) DBGCTcpTerminate(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + + /* + * Destroy the server instance if any. + */ + if (pvData) + { + int rc = RTTcpServerDestroy((PRTTCPSERVER)pvData); + AssertRC(rc); + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/Debugger/DBGConsole.cpp b/src/VBox/Debugger/DBGConsole.cpp new file mode 100644 index 00000000..f79e8e79 --- /dev/null +++ b/src/VBox/Debugger/DBGConsole.cpp @@ -0,0 +1,1327 @@ +/* $Id: DBGConsole.cpp $ */ +/** @file + * DBGC - Debugger Console. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @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 ch; + char *psz = &pDbgc->achInput[pDbgc->iRead]; + char *pszTrg = &pDbgc->achScratch[0]; + while ((*pszTrg = ch = *psz++) != ';' && ch != '\n' ) + { + if (psz == &pDbgc->achInput[sizeof(pDbgc->achInput)]) + psz = &pDbgc->achInput[0]; + + 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] - 1, 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->pBack->pfnInput(pDbgc->pBack, 0)) + { + size_t cbRead; + int rc = pDbgc->pBack->pfnRead(pDbgc->pBack, &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->pBack->pfnRead(pDbgc->pBack, &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->pBack->pfnInput(pDbgc->pBack, 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->pBack->pfnSetReady(pDbgc->pBack, 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->pBack->pfnSetReady(pDbgc->pBack, 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. + */ +static 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. + */ +static 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; + switch (pEvent->enmType) + { + /* + * The first part is events we have initiated with commands. + */ + case DBGFEVENT_HALT_DONE: + { + rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: VM %p is halted! (%s)\n", + pDbgc->pVM, dbgcGetEventCtx(pEvent->enmCtx)); + pDbgc->fRegCtxGuest = true; /* we're always in guest context when halted. */ + if (RT_SUCCESS(rc)) + rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); + 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)); + pDbgc->fRegCtxGuest = false; /* fatal errors are always in hypervisor. */ + 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: + { + bool fRegCtxGuest = pDbgc->fRegCtxGuest; + pDbgc->fRegCtxGuest = pEvent->enmType != DBGFEVENT_BREAKPOINT_HYPER; + + rc = dbgcBpExec(pDbgc, pEvent->u.Bp.iBp); + 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.iBp, 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.iBp, 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.iBp, dbgcGetEventCtx(pEvent->enmCtx)); + break; + + default: + break; + } + if (RT_SUCCESS(rc) && DBGFR3IsHalted(pDbgc->pUVM)) + { + 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"); + } + else + pDbgc->fRegCtxGuest = fRegCtxGuest; + break; + } + + case DBGFEVENT_STEPPED: + case DBGFEVENT_STEPPED_HYPER: + { + pDbgc->fRegCtxGuest = pEvent->enmType == DBGFEVENT_STEPPED; + + rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Single step! (%s)\n", dbgcGetEventCtx(pEvent->enmCtx)); + if (RT_SUCCESS(rc)) + { + if (pDbgc->fStepTraceRegs) + rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); + else + { + char szCmd[80]; + if (!pDbgc->fRegCtxGuest) + rc = DBGFR3RegPrintf(pDbgc->pUVM, pDbgc->idCpu | DBGFREG_HYPER_VMCPUID, szCmd, sizeof(szCmd), + "u %VR{cs}:%VR{eip} L 0"); + else 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); + } + } + break; + } + + case DBGFEVENT_ASSERTION_HYPER: + { + pDbgc->fRegCtxGuest = false; + + 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: + { + pDbgc->fReady = false; + pDbgc->pBack->pfnSetReady(pDbgc->pBack, 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: %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; + } + } + + /* + * Prompt, anyone? + */ + if (fPrintPrompt && RT_SUCCESS(rc)) + { + rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "VBoxDbg> "); + pDbgc->fReady = true; + if (RT_SUCCESS(rc)) + pDbgc->pBack->pfnSetReady(pDbgc->pBack, true); + } + + 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->pBack->pfnSetReady(pDbgc->pBack, 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. + */ + PCDBGFEVENT pEvent; + rc = DBGFR3EventWait(pDbgc->pUVM, pDbgc->fLog ? 1 : 32, &pEvent); + if (RT_SUCCESS(rc)) + { + rc = dbgcProcessEvent(pDbgc, pEvent); + if (RT_FAILURE(rc)) + break; + } + else if (rc != VERR_TIMEOUT) + break; + + /* + * Check for input. + */ + if (pDbgc->pBack->pfnInput(pDbgc->pBack, 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->pBack->pfnInput(pDbgc->pBack, 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; +} + + + +/** + * Creates a a new instance. + * + * @returns VBox status code. + * @param ppDbgc Where to store the pointer to the instance data. + * @param pBack Pointer to the backend. + * @param fFlags The flags. + */ +int dbgcCreate(PDBGC *ppDbgc, PDBGCBACK pBack, unsigned fFlags) +{ + /* + * Validate input. + */ + AssertPtrReturn(pBack, 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->pBack = pBack; + 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->fRegCtxGuest = true; + 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; + //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 pBack Pointer to the backend 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, PDBGCBACK pBack, 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, pBack, 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..76f3db0d --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInCommonELF.cpp @@ -0,0 +1,86 @@ +/* $Id: DBGPlugInCommonELF.cpp $ */ +/** @file + * DBGPlugInCommonELF - Common code for dealing with ELF images. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugInCommonELF.h" + +#include <VBox/vmm/dbgf.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..0a8d3cf3 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInCommonELF.h @@ -0,0 +1,53 @@ +/* $Id: DBGPlugInCommonELF.h $ */ +/** @file + * DBGPlugInCommonELF - Common code for dealing with ELF images, Header. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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, 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, 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..d32950be --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInCommonELFTmpl.cpp.h @@ -0,0 +1,335 @@ +/* $Id: DBGPlugInCommonELFTmpl.cpp.h $ */ +/** @file + * DBGPlugInCommonELF - Code Template for dealing with one kind of ELF. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 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, 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(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 = DBGFR3AsResolveAndRetain(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..ea53c76a --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInDarwin.cpp @@ -0,0 +1,1003 @@ +/* $Id: DBGPlugInDarwin.cpp $ */ +/** @file + * DBGPlugInDarwin - Debugger and Guest OS Digger Plugin For Darwin / OS X. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include <VBox/vmm/dbgf.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> + + +/********************************************************************************************************************************* +* 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, void *pvData); + + + +/** + * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog} + */ +static DECLCALLBACK(int) dbgDiggerDarwinIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, 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 = DBGFR3AsResolveAndRetain(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, SymInfo.Value + pData->AddrKernel.FlatPtr), + &GCPtrMsgBufP, pData->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t)); + if (RT_FAILURE(rc)) + { + Log(("dbgDiggerDarwinIDmsg_QueryKernelLog: failed to read _msgbufp at %RGv: %Rrc\n", Addr.FlatPtr, rc)); + return VERR_NOT_FOUND; + } + if (!OSX_VALID_ADDRESS(pData->f64Bit, GCPtrMsgBufP)) + { + Log(("dbgDiggerDarwinIDmsg_QueryKernelLog: Invalid address for _msgbufp: %RGv\n", GCPtrMsgBufP)); + return VERR_NOT_FOUND; + } + } + else + { + rc = RTDbgModSymbolByName(hMod, "_msgbuf", &SymInfo); + if (RT_FAILURE(rc)) + { + Log(("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)) + { + Log(("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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrMsgBufP), + &MsgBuf, sizeof(MsgBuf) - (pData->f64Bit ? 0 : sizeof(uint32_t)) ); + if (RT_FAILURE(rc)) + { + Log(("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) ) + { + Log(("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) + { + Log(("dbgDiggerDarwinIDmsg_QueryKernelLog: Failed to allocate %#x bytes of memory for the log buffer\n", + MsgBuf.msg_size)); + return VERR_INVALID_STATE; + } + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 + Log(("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, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, + PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, + uint64_t *puScratch) +{ + RT_NOREF(pUVM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerDarwinQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF1(pUVM); + PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData; + switch (enmIf) + { + case DBGFOSINTERFACE_DMESG: + return &pThis->IDmesg; + + default: + return NULL; + } +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerDarwinQueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion) +{ + PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData; + Assert(pThis->fValid); + + /* + * It's all in the linux banner. + */ + int rc = DBGFR3MemReadString(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, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData; + + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerDarwinRefresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + /* + * For now we'll flush and reload everything. + */ + dbgDiggerDarwinTerm(pUVM, pvData); + return dbgDiggerDarwinInit(pUVM, pvData); +} + + +/** + * 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, 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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. + */ + RTDBGSEGMENT aSegs[24]; + uint32_t cSegs = 0; + RTUUID Uuid = RTUUID_INITIALIZE_NULL; + 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; + section_32_t const *pSect32; + section_64_t const *pSect64; + symtab_command_t const *pSymTab; + 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")) + 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")) + 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) + 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) + return VERR_ADDRESS_CONFLICT; + + /* + * Create a debug module. + */ + RTDBGMOD hMod; + rc = RTDbgModCreateFromMachOImage(&hMod, pszName, NULL, f64Bit ? RTLDRARCH_AMD64 : RTLDRARCH_X86_32, 0 /*cbImage*/, + cSegs, aSegs, &Uuid, DBGFR3AsGetConfig(pUVM), RTDBGMOD_F_NOT_DEFERRED); + + if (RT_FAILURE(rc)) + { + /* + * Final fallback is a container module. + */ + 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 = DBGFR3AsResolveAndRetain(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, void *pvData) +{ + PDBGDIGGERDARWIN pThis = (PDBGDIGGERDARWIN)pvData; + Assert(!pThis->fValid); + + /* + * Add the kernel module. + */ + bool f64Bit; + int rc = dbgDiggerDarwinAddModule(pThis, pUVM, 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 = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "mach_kernel!kmod", &SymInfo, NULL); + if (RT_FAILURE(rc)) + rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "mach_kernel!_kmod", &SymInfo, NULL); + if (RT_SUCCESS(rc)) + { + DBGFADDRESS AddrModInfo; + DBGFR3AddrFromFlat(pUVM, &AddrModInfo, SymInfo.Value); + + /* Read the variable. */ + RTUINT64U uKmodValue = { 0 }; + if (f64Bit) + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrModInfo, &uKmodValue.u, sizeof(uKmodValue.u)); + else + rc = DBGFR3MemRead (pUVM, 0 /*idCpu*/, &AddrModInfo, &uKmodValue.s.Lo, sizeof(uKmodValue.s.Lo)); + if (RT_SUCCESS(rc)) + { + DBGFR3AddrFromFlat(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)) + { + Log(("OSXDig: Invalid kmod_info pointer: %RGv\n", AddrModInfo.FlatPtr)); + break; + } + if (AddrModInfo.FlatPtr == uKmodValue.u && cIterations != 0) + { + Log(("OSXDig: kmod_info list looped back to the start.\n")); + break; + } + if (cIterations++ >= 2048) + { + Log(("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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrModInfo, &uMod, + f64Bit ? sizeof(uMod.Info64) : sizeof(uMod.Info32)); + if (RT_FAILURE(rc)) + { + Log(("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) + { + Log(("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) ) + { + Log(("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) ) + { + Log(("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) + { + Log(("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)) + { + Log(("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) + { + Log(("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) + { + Log(("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)) + { + Log(("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)) + { + Log(("OSXDig: kmod_info @%RGv: Bad stop function %#llx\n", AddrModInfo.FlatPtr, uStopAddr)); + break; + } + + /* + * Try add the module. + */ + Log(("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, uImageAddr, pszName, NULL); + + + /* + * Advance to the next kmod_info entry. + */ + DBGFR3AddrFromFlat(pUVM, &AddrModInfo, f64Bit ? uMod.Info64.next : uMod.Info32.next); + } + } + else + Log(("OSXDig: Error reading the 'kmod' variable: %Rrc\n", rc)); + } + else + Log(("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, 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 = DBGFR3CpuGetMode(pUVM, 0 /*idCpu*/) != CPUMMODE_LONG; + iRange < RT_ELEMENTS(s_aRanges); + iRange++) + { + DBGFADDRESS KernelAddr; + for (DBGFR3AddrFromFlat(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 = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, s_aRanges[iRange].uEnd - KernelAddr.FlatPtr, + 1, s_abNeedle, sizeof(s_abNeedle), &KernelAddr); + if (RT_FAILURE(rc)) + break; + DBGFR3AddrSub(&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 = DBGFR3MemRead(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 = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, 32*_1M, 1, RT_STR_TUPLE("Darwin Kernel Version"), + &pThis->AddrKernelVersion); + if (RT_FAILURE(rc)) + DBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelVersion, 0); + return true; + } + } + return false; +} + + +/** + * @copydoc DBGFOSREG::pfnDestruct + */ +static DECLCALLBACK(void) dbgDiggerDarwinDestruct(PUVM pUVM, void *pvData) +{ + RT_NOREF2(pUVM, pvData); + +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerDarwinConstruct(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + 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..3f807fb2 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInDiggers.cpp @@ -0,0 +1,77 @@ +/* $Id: DBGPlugInDiggers.cpp $ */ +/** @file + * DbfPlugInDiggers - Debugger and Guest OS Digger Plug-in. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGC +#include <VBox/dbg.h> +#include <VBox/vmm/dbgf.h> +#include "DBGPlugIns.h" +#include <VBox/version.h> +#include <iprt/errcore.h> + + +DECLEXPORT(int) DbgPlugInEntry(DBGFPLUGINOP enmOperation, PUVM pUVM, 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 = DBGFR3OSRegister(pUVM, s_aPlugIns[i]); + if (RT_FAILURE(rc)) + { + AssertRC(rc); + while (i-- > 0) + DBGFR3OSDeregister(pUVM, s_aPlugIns[i]); + return rc; + } + } + return VINF_SUCCESS; + } + + case DBGFPLUGINOP_TERM: + { + for (unsigned i = 0; i < RT_ELEMENTS(s_aPlugIns); i++) + { + int rc = DBGFR3OSDeregister(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..2bb4567c --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInFreeBsd.cpp @@ -0,0 +1,829 @@ +/* $Id: DBGPlugInFreeBsd.cpp $ */ +/** @file + * DBGPlugInFreeBsd - Debugger and Guest OS Digger Plugin For FreeBSD. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include "DBGPlugInCommonELF.h" +#include <VBox/vmm/dbgf.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 * +*********************************************************************************************************************************/ + +/** + * 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, 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 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, 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); + int rc = DBGFR3MemRead(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 = DBGFR3MemRead(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) + { + 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 = DBGFR3AsResolveAndRetain(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 pszName The image name. + */ +static void dbgDiggerFreeBsdProcessKernelImage(PDBGDIGGERFBSD pThis, PUVM pUVM, 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] - contiains 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 + * [.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. + * 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. + */ + + /* Calculate the start of the .hash section. */ + DBGFADDRESS AddrHashStart = pThis->AddrKernelInterp; + DBGFR3AddrAdd(&AddrHashStart, sizeof(g_abNeedleInterp)); + AddrHashStart.FlatPtr = RT_ALIGN_GCPT(AddrHashStart.FlatPtr, pThis->f64Bit ? 8 : 4, RTGCUINTPTR); + uint32_t au32Counters[2]; + int rc = DBGFR3MemRead(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; + + DBGFR3AddrAdd(&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 = DBGFR3MemRead(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) + { + DBGFR3AddrAdd(&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, 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. */ + DBGFR3AddrAdd(&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)); + } +} + + +/** + * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog} + */ +static DECLCALLBACK(int) dbgDiggerFreeBsdIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, uint32_t fFlags, uint32_t cMessages, + char *pszBuf, size_t cbBuf, size_t *pcbActual) +{ + RT_NOREF1(fFlags); + PDBGDIGGERFBSD pData = RT_FROM_MEMBER(pThis, DBGDIGGERFBSD, IDmesg); + + if (cMessages < 1) + return VERR_INVALID_PARAMETER; + + /* Resolve the message buffer address from the msgbufp symbol. */ + RTDBGSYMBOL SymInfo; + int rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "kernel!msgbufp", &SymInfo, NULL); + if (RT_SUCCESS(rc)) + { + DBGFADDRESS AddrMsgBuf; + + /* Read the message buffer pointer. */ + RTGCPTR GCPtrMsgBufP = 0; + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, + PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, + uint64_t *puScratch) +{ + RT_NOREF(pUVM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerFreeBsdQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF1(pUVM); + PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; + switch (enmIf) + { + case DBGFOSINTERFACE_DMESG: + return &pThis->IDmesg; + + default: + return NULL; + } +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerFreeBsdQueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion) +{ + PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; + Assert(pThis->fValid); RT_NOREF(pThis); + + RTDBGSYMBOL SymInfo; + int rc = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "kernel!version", &SymInfo, NULL); + if (RT_SUCCESS(rc)) + { + DBGFADDRESS AddrVersion; + DBGFR3AddrFromFlat(pUVM, &AddrVersion, SymInfo.Value); + + rc = DBGFR3MemReadString(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, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; + Assert(pThis->fValid); + + RT_NOREF1(pUVM); + + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerFreeBsdRefresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + dbgDiggerFreeBsdTerm(pUVM, pvData); + return dbgDiggerFreeBsdInit(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnInit + */ +static DECLCALLBACK(int) dbgDiggerFreeBsdInit(PUVM pUVM, void *pvData) +{ + PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; + Assert(!pThis->fValid); + + RT_NOREF1(pUVM); + + dbgDiggerFreeBsdProcessKernelImage(pThis, pUVM, "kernel"); + pThis->fValid = true; + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnProbe + */ +static DECLCALLBACK(bool) dbgDiggerFreeBsdProbe(PUVM pUVM, 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; + DBGFR3AddrFromFlat(pUVM, &KernelAddr, g_au64FreeBsdKernelAddresses[i]); + DBGFADDRESS HitAddr; + uint32_t cbLeft = FBSD_MAX_KERNEL_SIZE; + + while (cbLeft > X86_PAGE_4K_SIZE) + { + int rc = DBGFR3MemScan(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 = DBGFR3MemScan(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 = DBGFR3MemRead(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; + pThis->AddrKernelText.FlatPtr = 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; + DBGFR3AddrAdd(&KernelAddr, cbDistance); + } + } + return false; +} + + +/** + * @copydoc DBGFOSREG::pfnDestruct + */ +static DECLCALLBACK(void) dbgDiggerFreeBsdDestruct(PUVM pUVM, void *pvData) +{ + RT_NOREF2(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerFreeBsdConstruct(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; + + 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..4cb19178 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInLinux.cpp @@ -0,0 +1,2628 @@ +/* $Id: DBGPlugInLinux.cpp $ */ +/** @file + * DBGPlugInLinux - Debugger and Guest OS Digger Plugin For Linux. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include "DBGPlugInCommonELF.h" +#include <VBox/vmm/dbgf.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 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) + +/** 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) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) dbgDiggerLinuxInit(PUVM pUVM, 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 "; + +/** + * 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 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, 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; + DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrCur); + + /* Prefetch the instruction. */ + uint8_t abInstr[32]; + rc = DBGFR3MemRead(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(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 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, 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; + DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrCur); + + /* Prefetch the instruction. */ + uint8_t abInstr[32]; + rc = DBGFR3MemRead(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: + 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(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 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, 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, 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. + */ +static bool dbgDiggerLinuxLogBufferIsAsciiBuffer(PDBGDIGGERLINUX pThis, PUVM pUVM) +{ + char szTmp[128]; + char const *pszVer = &szTmp[sizeof(g_abLinuxVersion) - 1]; + + RT_ZERO(szTmp); + int rc = DBGFR3MemReadString(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 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, 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(pUVM, &Addr, (RTGCPTR)SymInfo.Value + pThis->AddrKernelBase.FlatPtr), + aSymbols[i].pvVar, aSymbols[i].cbGuest); + if (RT_SUCCESS(rc)) + continue; + Log(("dbgDiggerLinuxIDmsg_QueryKernelLog: Reading '%s' at %RGv: %Rrc\n", aSymbols[i].pszSymbol, Addr.FlatPtr, rc)); + } + else + Log(("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, 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)) + { + Log(("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) + { + Log(("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) + { + Log(("dbgDiggerLinuxIDmsg_QueryKernelLog: Failed to allocate %#x bytes for log buffer\n", cbLogBuf)); + return VERR_NO_MEMORY; + } + DBGFADDRESS Addr; + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrLogBuf), pbLogBuf, cbLogBuf); + if (RT_FAILURE(rc)) + { + Log(("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 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 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, RTDBGMOD hMod, + uint32_t fFlags, uint32_t cMessages, + char *pszBuf, size_t cbBuf, size_t *pcbActual) +{ + RT_NOREF1(fFlags); + 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(pUVM, &Addr, (RTGCPTR)SymInfo.Value + pThis->AddrKernelBase.FlatPtr), + aSymbols[i].pvVar, aSymbols[i].cbGuest); + if (RT_SUCCESS(rc)) + continue; + Log(("dbgDiggerLinuxIDmsg_QueryKernelLog: Reading '%s' at %RGv: %Rrc\n", aSymbols[i].pszSymbol, Addr.FlatPtr, rc)); + } + else + Log(("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, 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)) + { + Log(("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) + { + Log(("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) + { + Log(("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) + { + Log(("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) + { + Log(("dbgDiggerLinuxIDmsg_QueryKernelLog: Failed to allocate %#x bytes for log buffer\n", cbLogBuf)); + return VERR_NO_MEMORY; + } + DBGFADDRESS Addr; + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrLogBuf), pbLogBuf, cbLogBuf); + if (RT_FAILURE(rc)) + { + Log(("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) )) + { + Log(("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)); + rc = VERR_INVALID_STATE; + break; + } + + if (pHdr->cbText > 0) + cLogMsgs++; + + /* next */ + offCur += pHdr->cbTotal; + cbLeft -= pHdr->cbTotal; + } + if (RT_FAILURE(rc)) + { + RTMemFree(pbLogBuf); + return rc; + } + + /* + * Copy the messages into the output buffer. + */ + offCur = idxFirst; + cbLeft = cbUsed; + + /* Skip messages that the caller doesn't want. */ + if (cMessages < cLogMsgs) + { + uint32_t cToSkip = cLogMsgs - cMessages; + 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) + { + 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; + else + return VERR_BUFFER_OVERFLOW; +} + +/** + * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog} + */ +static DECLCALLBACK(int) dbgDiggerLinuxIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, 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 = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL); + RTDBGMOD hMod; + int rc = RTDbgAsModuleByName(hAs, "vmlinux", 0, &hMod); + if (RT_FAILURE(rc)) + return VERR_NOT_FOUND; + RTDbgAsRelease(hAs); + + size_t cbActual; + /* + * 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)) + rc = dbgDiggerLinuxLogBufferQueryAscii(pData, pUVM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual); + else + rc = dbgDiggerLinuxLogBufferQueryRecords(pData, pUVM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual); + + /* Release the module in any case. */ + RTDbgModRelease(hMod); + + 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, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, + PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, + uint64_t *puScratch) +{ + RT_NOREF(pUVM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerLinuxQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF1(pUVM); + PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData; + switch (enmIf) + { + case DBGFOSINTERFACE_DMESG: + return &pThis->IDmesg; + + default: + return NULL; + } +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerLinuxQueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion) +{ + PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData; + Assert(pThis->fValid); + + /* + * It's all in the linux banner. + */ + int rc = DBGFR3MemReadString(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, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData; + Assert(pThis->fValid); + + dbgDiggerLinuxCfgDbDestroy(pThis); + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerLinuxRefresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + /* + * For now we'll flush and reload everything. + */ + dbgDiggerLinuxTerm(pUVM, pvData); + return dbgDiggerLinuxInit(pUVM, pvData); +} + + +/** + * Worker for dbgDiggerLinuxFindStartOfNamesAndSymbolCount that update the + * digger data. + * + * @returns VINF_SUCCESS. + * @param pThis The Linux digger data to update. + * @param pAddrKernelNames The kallsyms_names address. + * @param cKernelSymbols The number of kernel symbol. + * @param cbAddress The guest address size. + */ +static int dbgDiggerLinuxFoundStartOfNames(PDBGDIGGERLINUX pThis, 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); + DBGFR3AddrSub(&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 pThis The Linux digger data. + * @param pHitAddr An address we think is inside kallsyms_names. + */ +static int dbgDiggerLinuxFindStartOfNamesAndSymbolCount(PUVM pUVM, 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; + DBGFR3AddrSub(&CurAddr, cbBuf); + cbBuf += sizeof(uint64_t) - 1; /* In case our kobj hit is in the first 4/8 bytes. */ + for (;;) + { + int rc = DBGFR3MemRead(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. + */ + if (pThis->f64Bit) + { + uint32_t i = cbBuf / sizeof(uint64_t); + 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrAdd(&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, + DBGFR3AddrAdd(&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, + DBGFR3AddrAdd(&CurAddr, (i + 1) * sizeof(uint64_t)), + (uint32_t)uBuf.au64[i], sizeof(uint64_t)); + } + } + } + else + { + uint32_t i = cbBuf / sizeof(uint32_t); + 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, + DBGFR3AddrAdd(&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, + DBGFR3AddrAdd(&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); + DBGFR3AddrSub(&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 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, 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; + DBGFR3AddrAdd(&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 pThis The Linux digger data. + * @param pHitAddr An address we think is inside kallsyms_names. + */ +static int dbgDiggerLinuxFindEndOfNamesAndMore(PUVM pUVM, 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; + DBGFR3AddrSub(&CurAddr, offBuf); + for (;;) + { + int rc = DBGFR3MemRead(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. + */ + if (pThis->f64Bit) + { + 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, DBGFR3AddrSub(&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, DBGFR3AddrAdd(&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, DBGFR3AddrSub(&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, DBGFR3AddrAdd(&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); + DBGFR3AddrAdd(&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 pThis The Linux digger data. + */ +static int dbgDiggerLinuxFindTokenIndex(PUVM pUVM, 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 = DBGFR3MemRead(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; + DBGFR3AddrAdd(&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 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, PDBGDIGGERLINUX pThis, RTGCUINTPTR uKernelStart, + RTGCUINTPTR cbKernel, RTGCUINTPTR *pauSymOff) +{ + uint8_t *pbNames = (uint8_t *)RTMemAllocZ(pThis->cbKernelNames); + int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelNames, pbNames, pThis->cbKernelNames); + if (RT_SUCCESS(rc)) + { + char *pszzTokens = (char *)RTMemAllocZ(pThis->cbKernelTokenTable); + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &pThis->AddrKernelTokenTable, pszzTokens, pThis->cbKernelTokenTable); + if (RT_SUCCESS(rc)) + { + uint16_t *paoffTokens = (uint16_t *)RTMemAllocZ(256 * sizeof(uint16_t)); + rc = DBGFR3MemRead(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 = DBGFR3AsResolveAndRetain(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 pThis The Linux digger data. + */ +static int dbgDiggerLinuxLoadKernelSymbolsAbsolute(PUVM pUVM, 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 = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelBase, uKernelStart); + Log(("dbgDiggerLinuxLoadKernelSymbolsAbsolute: uKernelStart=%RGv cbKernel=%#x\n", uKernelStart, cbKernel)); + + rc = dbgDiggerLinuxLoadKernelSymbolsWorker(pUVM, 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 pThis The Linux digger data. + */ +static int dbgDiggerLinuxLoadKernelSymbolsRelative(PUVM pUVM, 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 = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelBase, uKernelStart); + Log(("dbgDiggerLinuxLoadKernelSymbolsRelative: uKernelStart=%RGv cbKernel=%#x\n", uKernelStart, cbKernel)); + + rc = dbgDiggerLinuxLoadKernelSymbolsWorker(pUVM, 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 pThis The Linux digger data. + */ +static int dbgDiggerLinuxLoadKernelSymbols(PUVM pUVM, PDBGDIGGERLINUX pThis) +{ + if (pThis->fRelKrnlAddr) + return dbgDiggerLinuxLoadKernelSymbolsRelative(pUVM, pThis); + else + return dbgDiggerLinuxLoadKernelSymbolsAbsolute(pUVM, pThis); +} + +/** + * 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 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, 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; + DBGFR3AddrSub(&ReadAddr, 2); + int rc = DBGFR3MemRead(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 pabNeedle The needle to use for searching. + * @param cbNeedle Size of the needle in bytes. + */ +static int dbgDiggerLinuxFindSymbolTableFromNeedle(PDBGDIGGERLINUX pThis, PUVM pUVM, uint8_t const *pabNeedle, uint8_t cbNeedle) +{ + int rc = VINF_SUCCESS; + + /* + * 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. + */ + DBGFADDRESS CurAddr = pThis->AddrLinuxBanner; + uint32_t cbLeft = LNX_MAX_KERNEL_SIZE; + while (cbLeft > 4096) + { + DBGFADDRESS HitAddr; + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &CurAddr, cbLeft, 1 /*uAlign*/, + pabNeedle, cbNeedle, &HitAddr); + if (RT_FAILURE(rc)) + break; + if (dbgDiggerLinuxIsLikelyNameFragment(pUVM, &HitAddr, pabNeedle, cbNeedle)) + { + /* There will be another hit near by. */ + DBGFR3AddrAdd(&HitAddr, 1); + rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, LNX_MAX_KALLSYMS_NAMES_SIZE, 1 /*uAlign*/, + pabNeedle, cbNeedle, &HitAddr); + if ( RT_SUCCESS(rc) + && dbgDiggerLinuxIsLikelyNameFragment(pUVM, &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, pThis, &HitAddr); + if (RT_SUCCESS(rc)) + rc = dbgDiggerLinuxFindEndOfNamesAndMore(pUVM, pThis, &HitAddr); + if (RT_SUCCESS(rc)) + rc = dbgDiggerLinuxFindTokenIndex(pUVM, pThis); + if (RT_SUCCESS(rc)) + rc = dbgDiggerLinuxLoadKernelSymbols(pUVM, pThis); + if (RT_SUCCESS(rc)) + break; + } + } + + /* + * Advance. + */ + RTGCUINTPTR cbDistance = HitAddr.FlatPtr - CurAddr.FlatPtr + cbNeedle; + if (RT_UNLIKELY(cbDistance >= cbLeft)) + { + Log(("dbgDiggerLinuxInit: Failed to find kallsyms\n")); + break; + } + cbLeft -= cbDistance; + DBGFR3AddrAdd(&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 pAddrStart The start address of the compressed config. + * @param cbCfgComp The size of the compressed config. + */ +static int dbgDiggerLinuxCfgDecode(PDBGDIGGERLINUX pThis, PUVM pUVM, + PCDBGFADDRESS pAddrStart, size_t cbCfgComp) +{ + int rc = VINF_SUCCESS; + uint8_t *pbCfgComp = (uint8_t *)RTMemTmpAlloc(cbCfgComp); + if (!pbCfgComp) + return VERR_NO_MEMORY; + + rc = DBGFR3MemRead(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. + */ +static int dbgDiggerLinuxCfgFind(PDBGDIGGERLINUX pThis, PUVM pUVM) +{ + int rc = VINF_SUCCESS; + + /* + * 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"; + DBGFADDRESS CurAddr = pThis->AddrLinuxBanner; + uint32_t cbLeft = LNX_MAX_KERNEL_SIZE; + while (cbLeft > 4096) + { + DBGFADDRESS HitAddrStart; + rc = DBGFR3MemScan(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. */ + DBGFR3AddrAdd(&HitAddrStart, sizeof(s_abCfgNeedleStart) - 1); + DBGFADDRESS HitAddrEnd; + rc = DBGFR3MemScan(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, &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; + DBGFR3AddrAdd(&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 uAddrStart The address to start scanning at. + * @param cbScan How much to scan. + */ +static bool dbgDiggerLinuxProbeWithAddr(PDBGDIGGERLINUX pThis, PUVM pUVM, 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; + DBGFR3AddrFromFlat(pUVM, &KernelAddr, uAddrStart); + DBGFADDRESS HitAddr; + int rc = DBGFR3MemScan(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 = DBGFR3MemReadString(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 uAddrKernelStart The first address the kernel is expected at. + */ +static bool dbgDiggerLinuxProbeKaslr(PDBGDIGGERLINUX pThis, PUVM pUVM, RTGCUINTPTR uAddrKernelStart) +{ + /** + * 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, uAddrKernelStart, _1G + LNX_MAX_KERNEL_SIZE)) + return true; + + return false; +} + +/** + * @copydoc DBGFOSREG::pfnInit + */ +static DECLCALLBACK(int) dbgDiggerLinuxInit(PUVM pUVM, void *pvData) +{ + PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData; + Assert(!pThis->fValid); + + /* + * 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. + */ + int rc = dbgDiggerLinuxCfgFind(pThis, pUVM); + 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, 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, 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, s_abNeedleOSuseX86, sizeof(s_abNeedleOSuseX86) - 1); + } + } + + pThis->fValid = true; + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnProbe + */ +static DECLCALLBACK(bool) dbgDiggerLinuxProbe(PUVM pUVM, void *pvData) +{ + PDBGDIGGERLINUX pThis = (PDBGDIGGERLINUX)pvData; + + for (unsigned i = 0; i < RT_ELEMENTS(g_au64LnxKernelAddresses); i++) + { + if (dbgDiggerLinuxProbeWithAddr(pThis, pUVM, g_au64LnxKernelAddresses[i], LNX_MAX_KERNEL_SIZE)) + return true; + } + + /* Maybe the kernel uses KASLR. */ + if (dbgDiggerLinuxProbeKaslr(pThis, pUVM, LNX32_KERNEL_ADDRESS_START)) + return true; + + if (dbgDiggerLinuxProbeKaslr(pThis, pUVM, LNX64_KERNEL_ADDRESS_START)) + return true; + + return false; +} + + +/** + * @copydoc DBGFOSREG::pfnDestruct + */ +static DECLCALLBACK(void) dbgDiggerLinuxDestruct(PUVM pUVM, void *pvData) +{ + RT_NOREF2(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerLinuxConstruct(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + 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/DBGPlugInOS2.cpp b/src/VBox/Debugger/DBGPlugInOS2.cpp new file mode 100644 index 00000000..82d25408 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInOS2.cpp @@ -0,0 +1,1244 @@ +/* $Id: DBGPlugInOS2.cpp $ */ +/** @file + * DBGPlugInOS2 - Debugger and Guest OS Digger Plugin For OS/2. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include <VBox/vmm/dbgf.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/stream.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** @name Internal OS/2 structures */ + +/** @} */ + + +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; + + /** 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, 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 = DBGFR3SelQueryInfo(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 pThis = (PDBGDIGGEROS2)pvUser; + DBGFSELINFO SelInfo; + int rc = DBGFR3SelQueryInfo(pThis->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 = DBGFR3MemRead(pThis->pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pThis->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 = DBGFR3MemRead(pThis->pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pThis->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 pThis = (PDBGDIGGEROS2)pvUser; + 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 = DBGFR3MemRead(pThis->pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pThis->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 pThis = (PDBGDIGGEROS2)pvUser; + 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 = DBGFR3MemRead(pThis->pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pThis->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 pThis = (PDBGDIGGEROS2)pvUser; + DBGFADDRESS HitAddr; + int rc = DBGFR3MemScan(pThis->pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pThis->pUVM, &HitAddr, pThis->uKernelAddr), + pThis->cbKernel, 1, RT_STR_TUPLE("Exception in module:"), &HitAddr); + if (RT_FAILURE(rc)) + rc = DBGFR3MemScan(pThis->pUVM, 0 /*idCpu&*/, DBGFR3AddrFromFlat(pThis->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 = DBGFR3MemRead(pThis->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, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, + PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, + uint64_t *puScratch) +{ + RT_NOREF(pUVM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerOS2QueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF3(pUVM, pvData, enmIf); + return NULL; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerOS2QueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion) +{ + RT_NOREF1(pUVM); + PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData; + 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, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData; + Assert(pThis->fValid); + + DBGFR3InfoDeregisterExternal(pUVM, "sas"); + DBGFR3InfoDeregisterExternal(pUVM, "gis"); + DBGFR3InfoDeregisterExternal(pUVM, "lis"); + DBGFR3InfoDeregisterExternal(pUVM, "panic"); + + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerOS2Refresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + /* + * For now we'll flush and reload everything. + */ + RTDBGAS hDbgAs = DBGFR3AsResolveAndRetain(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, pvData); + return dbgDiggerOS2Init(pUVM, 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, 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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, 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 = DBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, pThis->selGis, 0x15); + if (RT_FAILURE(rc)) + return VERR_NOT_SUPPORTED; + rc = DBGFR3MemRead(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 = DBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, 0x70, 0x00); + if (RT_SUCCESS(rc)) + { + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &uBuf.sas, sizeof(uBuf.sas)); + if (RT_SUCCESS(rc)) + { + rc = DBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, 0x70, uBuf.sas.SAS_vm_data); + if (RT_SUCCESS(rc)) + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &Addr, &uBuf.sasvm, sizeof(uBuf.sasvm)); + if (RT_SUCCESS(rc)) + { + /* + * Work the module list. + */ + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3AsGetConfig(pUVM); /* (don't release this) */ + RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_GLOBAL); + + char szCacheSubDir[24]; + RTStrPrintf(szCacheSubDir, sizeof(szCacheSubDir), "os2-%u.%u", pThis->OS2MajorVersion, pThis->OS2MinorVersion); + + DBGFR3AddrFromFlat(pUVM, &Addr, uBuf.au32[0]); + while (Addr.FlatPtr != 0 && Addr.FlatPtr != UINT32_MAX) + { + rc = DBGFR3MemRead(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; + + DBGFR3AddrFromFlat(pUVM, &Addr, uBuf.mte.mte_link); + dbgdiggerOS2ProcessModule(pUVM, 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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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, pThis, &uBuf, szCacheSubDir, hAs, hDbgCfg); + } + } + + RTDbgAsRelease(hAs); + } + } + } + } + + /* + * Register info handlers. + */ + DBGFR3InfoRegisterExternal(pUVM, "sas", "Dumps the OS/2 system anchor block (SAS).", dbgDiggerOS2InfoSas, pThis); + DBGFR3InfoRegisterExternal(pUVM, "gis", "Dumps the OS/2 global info segment (GIS).", dbgDiggerOS2InfoGis, pThis); + DBGFR3InfoRegisterExternal(pUVM, "lis", "Dumps the OS/2 local info segment (current process).", dbgDiggerOS2InfoLis, pThis); + DBGFR3InfoRegisterExternal(pUVM, "panic", "Dumps the OS/2 system panic message.", dbgDiggerOS2InfoPanic, pThis); + + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnProbe + */ +static DECLCALLBACK(bool) dbgDiggerOS2Probe(PUVM pUVM, 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 contains '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 = DBGFR3AddrFromSelOff(pUVM, 0 /*idCpu*/, &Addr, 0x70, 0x00); + if (RT_FAILURE(rc)) + break; + rc = DBGFR3MemRead(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, void *pvData) +{ + RT_NOREF2(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerOS2Construct(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGEROS2 pThis = (PDBGDIGGEROS2)pvData; + pThis->fValid = false; + pThis->f32Bit = false; + pThis->enmVer = DBGDIGGEROS2VER_UNKNOWN; + pThis->pUVM = pUVM; + 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..87e4d6d7 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInSolaris.cpp @@ -0,0 +1,1141 @@ +/* $Id: DBGPlugInSolaris.cpp $ */ +/** @file + * DBGPlugInSolaris - Debugger and Guest OS Digger Plugin For Solaris. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include "DBGPlugInCommonELF.h" +#include <VBox/vmm/dbgf.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, void *pvData); + + + +/** + * @copydoc DBGFOSREG::pfnStackUnwindAssist + */ +static DECLCALLBACK(int) dbgDiggerSolarisStackUnwindAssist(PUVM pUVM, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, + PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, + uint64_t *puScratch) +{ + RT_NOREF(pUVM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerSolarisQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF3(pUVM, pvData, enmIf); + return NULL; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerSolarisQueryVersion(PUVM pUVM, 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 = DBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "utsname", &SymUtsName, NULL); + if (RT_SUCCESS(rc)) + rc = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemScan(pUVM, 0, &pThis->AddrUnixData, SOL_UNIX_MAX_DATA_SEG_SIZE, 1, + &UtsName.sysname[0], sizeof(UtsName.sysname), &Addr); + if (RT_SUCCESS(rc)) + rc = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(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 pThis Our instance data. + * @param pModCtl Pointer to the modctl structure. + */ +static void dbgDiggerSolarisProcessModCtl32(PUVM pUVM, 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 = DBGFR3MemReadString(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemReadString(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(pUVM, &Addr, Module.shdrs), paShdrs, cb); + if (RT_SUCCESS(rc)) + { + void *pvSymSpace = RTMemTmpAlloc(Module.symsize + 1); + if (pvSymSpace) + { + rc = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(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, 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 pThis Our instance data. + * @param pModCtl Pointer to the modctl structure. + */ +static void dbgDiggerSolarisProcessModCtl64(PUVM pUVM, 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 = DBGFR3MemReadString(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemReadString(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(pUVM, &Addr, Module.shdrs), paShdrs, cb); + if (RT_SUCCESS(rc)) + { + void *pvSymSpace = RTMemTmpAlloc(Module.symsize + 1); + if (pvSymSpace) + { + rc = DBGFR3MemRead(pUVM, 0, DBGFR3AddrFromFlat(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, 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, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData; + Assert(pThis->fValid); + + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerSolarisRefresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + /* + * For now we'll flush and reload everything. + */ + RTDBGAS hDbgAs = DBGFR3AsResolveAndRetain(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, pvData); + return dbgDiggerSolarisInit(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnInit + */ +static DECLCALLBACK(int) dbgDiggerSolarisInit(PUVM pUVM, 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. + */ + DBGFR3AsSetAlias(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. + */ + DBGFR3AddrFromFlat(pUVM, &pThis->AddrUnixModCtl, 0); + + DBGFADDRESS CurAddr = pThis->AddrUnixData; + DBGFADDRESS MaxAddr; + DBGFR3AddrFromFlat(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 = DBGFR3MemScan(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) + { + DBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL32v11_modctl_t, mod_text)); + SOL64v11_modctl_t ModCtlv11; + rc = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv11.mod_modname); + rc = DBGFR3MemRead(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 + { + DBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL32v11_modctl_t, mod_text)); + SOL32v11_modctl_t ModCtlv11; + rc = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv11.mod_modname); + rc = DBGFR3MemRead(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) + { + DBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL64v9_modctl_t, mod_text)); + SOL64v9_modctl_t ModCtlv9; + rc = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv9.mod_modname); + rc = DBGFR3MemRead(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 + { + DBGFR3AddrFromFlat(pUVM, &ModCtlAddr, HitAddr.FlatPtr - RT_OFFSETOF(SOL32v9_modctl_t, mod_text)); + SOL32v9_modctl_t ModCtlv9; + rc = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &NameAddr, ModCtlv9.mod_modname); + rc = DBGFR3MemRead(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 */ + DBGFR3AddrFromFlat(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 = DBGFR3MemRead(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, pThis, &ModCtl); + else + dbgDiggerSolarisProcessModCtl32(pUVM, 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; + } + DBGFR3AddrFromFlat(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; + } + DBGFR3AddrFromFlat(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, void *pvData) +{ + PDBGDIGGERSOLARIS pThis = (PDBGDIGGERSOLARIS)pvData; + + /* + * Look for "SunOS Release" in the text segment. + */ + DBGFADDRESS Addr; + bool f64Bit = false; + + /* 32-bit search range. */ + DBGFR3AddrFromFlat(pUVM, &Addr, 0xfe800000); + RTGCUINTPTR cbRange = 0xfec00000 - 0xfe800000; + + DBGFADDRESS HitAddr; + static const uint8_t s_abSunRelease[] = "SunOS Release "; + int rc = DBGFR3MemScan(pUVM, 0, &Addr, cbRange, 1, s_abSunRelease, sizeof(s_abSunRelease) - 1, &HitAddr); + if (RT_FAILURE(rc)) + { + /* 64-bit.... */ + DBGFR3AddrFromFlat(pUVM, &Addr, UINT64_C(0xfffffffffb800000)); + cbRange = UINT64_C(0xfffffffffbd00000) - UINT64_C(0xfffffffffb800000); + rc = DBGFR3MemScan(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 = DBGFR3MemScan(pUVM, 0, &Addr, cbRange, 1, s_abSMI, sizeof(s_abSMI) - 1, &HitAddr); + if (RT_FAILURE(rc)) + { + /* Try the alternate copyright string. */ + rc = DBGFR3MemScan(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; + DBGFR3AddrAdd(&Addr, SOL_UNIX_MAX_CODE_SEG_SIZE); + pThis->AddrUnixData = Addr; + pThis->f64Bit = f64Bit; + + return true; +} + + +/** + * @copydoc DBGFOSREG::pfnDestruct + */ +static DECLCALLBACK(void) dbgDiggerSolarisDestruct(PUVM pUVM, void *pvData) +{ + RT_NOREF2(pUVM, pvData); + +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerSolarisConstruct(PUVM pUVM, void *pvData) +{ + RT_NOREF2(pUVM, 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..2a9f595c --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInWinNt.cpp @@ -0,0 +1,1093 @@ +/* $Id: DBGPlugInWinNt.cpp $ */ +/** @file + * DBGPlugInWindows - Debugger and Guest OS Digger Plugin For Windows NT. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. +#include "DBGPlugIns.h" +#include <VBox/vmm/dbgf.h> +#include <VBox/vmm/cpumctx.h> +#include <VBox/vmm/mm.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[7]; + 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; + +/** @} */ + + + +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; + + /** 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; +} 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, 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' } +}; + + + +/** + * Process a PE image found in guest memory. + * + * @param pThis The instance data. + * @param pUVM The user mode VM handle. + * @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, 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 = DBGFR3ModInMem(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 = DBGFR3AsResolveAndRetain(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; +} + + +/** + * @copydoc DBGFOSREG::pfnStackUnwindAssist + */ +static DECLCALLBACK(int) dbgDiggerWinNtStackUnwindAssist(PUVM pUVM, 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)MMR3HeapRealloc(pFrame->paSureRegs, iReg * sizeof(paSureRegs[0])); + else + paSureRegs = (PDBGFREGVALEX)MMR3HeapAllocU(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, pvData, idCpu, hAs, pInitialCtx, puScratch); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryInterface + */ +static DECLCALLBACK(void *) dbgDiggerWinNtQueryInterface(PUVM pUVM, void *pvData, DBGFOSINTERFACE enmIf) +{ + RT_NOREF3(pUVM, pvData, enmIf); + return NULL; +} + + +/** + * @copydoc DBGFOSREG::pfnQueryVersion + */ +static DECLCALLBACK(int) dbgDiggerWinNtQueryVersion(PUVM pUVM, void *pvData, char *pszVersion, size_t cchVersion) +{ + RT_NOREF1(pUVM); + 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", pThis->NtMajorVersion, pThis->NtMinorVersion, + pThis->f32Bit ? "x86" : "AMD64", pszNtProductType); + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnTerm + */ +static DECLCALLBACK(void) dbgDiggerWinNtTerm(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + Assert(pThis->fValid); + + /* + * As long as we're using our private LDR reader implementation, + * we must unlink and ditch the modules we created. + */ + RTDBGAS hDbgAs = DBGFR3AsResolveAndRetain(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); + } + + pThis->fValid = false; +} + + +/** + * @copydoc DBGFOSREG::pfnRefresh + */ +static DECLCALLBACK(int) dbgDiggerWinNtRefresh(PUVM pUVM, void *pvData) +{ + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + NOREF(pThis); + Assert(pThis->fValid); + + /* + * For now we'll flush and reload everything. + */ + dbgDiggerWinNtTerm(pUVM, pvData); + + return dbgDiggerWinNtInit(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnInit + */ +static DECLCALLBACK(int) dbgDiggerWinNtInit(PUVM pUVM, 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. + */ + DBGFR3AddrFromFlat(pUVM, &Addr, pThis->f32Bit ? NTKUSERSHAREDDATA_WINNT32 : NTKUSERSHAREDDATA_WINNT64); + rc = DBGFR3MemRead(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; + } + else if (pThis->fNt31) + { + pThis->NtProductType = kNtProductType_WinNt; + pThis->NtMajorVersion = 3; + pThis->NtMinorVersion = 1; + } + 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 = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &AddrName, WINNT_UNION(pThis, &Mte, FullDllName.Buffer)); + uint16_t cbName = WINNT_UNION(pThis, &Mte, FullDllName.Length); + if (cbName < sizeof(u)) + rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrName, &u, cbName); + else + rc = VERR_OUT_OF_RANGE; + if (RT_FAILURE(rc)) + { + DBGFR3AddrFromFlat(pUVM, &AddrName, WINNT_UNION(pThis, &Mte, BaseDllName.Buffer)); + cbName = WINNT_UNION(pThis, &Mte, BaseDllName.Length); + if (cbName < sizeof(u)) + rc = DBGFR3MemRead(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; + DBGFR3AddrFromFlat(pUVM, &ImageAddr, WINNT_UNION(pThis, &Mte, DllBase)); + dbgDiggerWinNtProcessImage(pThis, pUVM, pszModName, pszFilename, &ImageAddr, cbImageMte); + RTStrFree(pszFilename); + } + } + + /* next */ + AddrPrev = Addr; + DBGFR3AddrFromFlat(pUVM, &Addr, WINNT_UNION(pThis, &Mte, InLoadOrderLinks.Flink)); + } while ( Addr.FlatPtr != pThis->KernelMteAddr.FlatPtr + && Addr.FlatPtr != pThis->PsLoadedModuleListAddr.FlatPtr); + + pThis->fValid = true; + return VINF_SUCCESS; +} + + +/** + * @copydoc DBGFOSREG::pfnProbe + */ +static DECLCALLBACK(bool) dbgDiggerWinNtProbe(PUVM pUVM, 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]; + X86DESC64GATE 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 = DBGFR3CpuGetMode(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 = DBGFR3RegCpuQueryXdtr(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = u.a32Gates[X86_XCPT_PF].u16OffsetLow + | ((uint32_t)u.a64Gates[X86_XCPT_PF].u16OffsetHigh << 16); + 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 (DBGFR3AddrFromFlat(pUVM, &KernelAddr, uKrnlStart); + KernelAddr.FlatPtr < uKrnlEnd; + KernelAddr.FlatPtr += PAGE_SIZE) + { + bool fNt31 = false; + DBGFADDRESS const RetryAddress = KernelAddr; + rc = DBGFR3MemScan(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 = DBGFR3MemScan(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; + DBGFR3AddrSub(&KernelAddr, KernelAddr.FlatPtr & PAGE_OFFSET_MASK); + + /* MZ + PE header. */ + rc = DBGFR3MemRead(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 = DBGFR3MemScan(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrSub(&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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(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 */ + DBGFR3AddrAdd(&HitAddr, 4); + if (HitAddr.FlatPtr < uEnd) + rc = DBGFR3MemScan(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 = DBGFR3MemScan(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrSub(&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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(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 = DBGFR3MemRead(pUVM, 0 /*idCpu*/, + DBGFR3AddrFromFlat(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 */ + DBGFR3AddrAdd(&HitAddr, 8); + if (HitAddr.FlatPtr < uEnd) + rc = DBGFR3MemScan(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, void *pvData) +{ + RT_NOREF2(pUVM, pvData); +} + + +/** + * @copydoc DBGFOSREG::pfnConstruct + */ +static DECLCALLBACK(int) dbgDiggerWinNtConstruct(PUVM pUVM, void *pvData) +{ + RT_NOREF1(pUVM); + PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvData; + pThis->fValid = false; + pThis->f32Bit = false; + pThis->enmVer = DBGDIGGERWINNTVER_UNKNOWN; + 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..3f846539 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugIns.h @@ -0,0 +1,41 @@ +/* $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-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..ae414d7c --- /dev/null +++ b/src/VBox/Debugger/Makefile.kmk @@ -0,0 +1,148 @@ +# $Id: Makefile.kmk $ +## @file +# Makefile for the VBox debugger. +# + +# +# Copyright (C) 2006-2019 Oracle Corporation +# +# This file is part of VirtualBox Open Source Edition (OSE), as +# available from http://www.virtualbox.org. This file is free software; +# you can redistribute it and/or modify it under the terms of the GNU +# General Public License (GPL) as published by the Free Software +# Foundation, in version 2 as it comes in the "COPYING" file of the +# VirtualBox OSE distribution. VirtualBox OSE is distributed in the +# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +# + +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 \ + DBGCTcp.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) \ + $(if-expr "$(LIB_VMM)" == "$(VBOX_LIB_VMM_LAZY)",$(LIB_REM),) \ + $(VBOX_LIB_VMM_LAZY) \ + $(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). +# +USES += qt5 +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_LIBS = \ + $(VBOX_LIB_VMM_LAZY) +VBoxDbg_LDFLAGS.darwin = \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxDbg.dylib +$(call VBOX_SET_VER_INFO_DLL,VBoxDbg,VirtualBox Debugger GUI) + + ifdef VBOX_WITH_TESTCASES +# +# The VBoxDbg testcase (Qt). +# +PROGRAMS += tstVBoxDbg +tstVBoxDbg_TEMPLATE = VBOXQTGUIEXE +tstVBoxDbg_USES = qt5 +tstVBoxDbg_QTTOOL = QT5 +tstVBoxDbg_QT_MODULES = Core Gui Widgets +tstVBoxDbg_LIBS.linux += xcb +tstVBoxDbg_LIBS.solaris += xcb +tstVBoxDbg_LIBS.freebsd += xcb +tstVBoxDbg_SOURCES = testcase/tstVBoxDbg.cpp +tstVBoxDbg_LIBS = \ + $(LIB_VMM) \ + $(LIB_REM) \ + $(LIB_RUNTIME) + ifeq ($(KBUILD_TARGET),win) +tstVBoxDbg_LIBS += \ + $(PATH_STAGE_LIB)/VBoxDbg.lib + else +tstVBoxDbg_LIBS += \ + $(PATH_STAGE_BIN)/VBoxDbg$(VBOX_SUFF_DLL) + endif + endif # TESTCASES +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..9b53c46a --- /dev/null +++ b/src/VBox/Debugger/VBoxDbg.cpp @@ -0,0 +1,259 @@ +/* $Id: VBoxDbg.cpp $ */ +/** @file + * VBox Debugger GUI. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGG +#define VBOX_COM_NO_ATL +#ifdef RT_OS_WINDOWS +# include <VirtualBox.h> +#else /* !RT_OS_WINDOWS */ +# include <VirtualBox_XPCOM.h> +#endif /* !RT_OS_WINDOWS */ +#include <VBox/dbggui.h> +#include <VBox/vmm/vm.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 ppGui See DBGGuiCreate. + * @param ppGuiVT See DBGGuiCreate. + */ +static int dbgGuiCreate(ISession *pSession, PUVM pUVM, 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); + 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, ppGui, ppGuiVT); +} + + +/** + * Creates the debugger GUI given a VM handle. + * + * @returns VBox status code. + * @param pUVM The VM handle. + * @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, PDBGGUI *ppGui, PCDBGGUIVT *ppGuiVT) +{ + AssertPtrReturn(pUVM, VERR_INVALID_POINTER); + AssertPtrReturn(VMR3RetainUVM(pUVM) != UINT32_MAX, VERR_INVALID_POINTER); + + int rc = dbgGuiCreate(NULL, pUVM, ppGui, ppGuiVT); + + VMR3ReleaseUVM(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(). + */ +DBGDECL(int) DBGGuiShowStatistics(PDBGGUI pGui) +{ + AssertReturn(pGui, VERR_INVALID_PARAMETER); + AssertMsgReturn(pGui->u32Magic == DBGGUI_MAGIC, ("u32Magic=%#x\n", pGui->u32Magic), VERR_INVALID_PARAMETER); + return pGui->pVBoxDbgGui->showStatistics(); +} + + +/** + * 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..13f0129b --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgBase.cpp @@ -0,0 +1,276 @@ +/* $Id: VBoxDbgBase.cpp $ */ +/** @file + * VBox Debugger GUI - Base classes. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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_hGUIThread(RTThreadNativeSelf()) +{ + NOREF(m_pDbgGui); /* shut up warning. */ + + /* + * Register + */ + m_pUVM = a_pDbgGui->getUvmHandle(); + if (m_pUVM) + { + VMR3RetainUVM(m_pUVM); + + int rc = VMR3AtStateRegister(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); + if (pUVM) + { + int rc = VMR3AtStateDeregister(pUVM, atStateChange, this); + AssertRC(rc); + + VMR3ReleaseUVM(pUVM); + } +} + + +int +VBoxDbgBase::stamReset(const QString &rPat) +{ + QByteArray Utf8Array = rPat.toUtf8(); + const char *pszPat = !rPat.isEmpty() ? Utf8Array.constData() : NULL; + PUVM pUVM = m_pUVM; + if ( pUVM + && VMR3GetStateU(pUVM) < VMSTATE_DESTROYING) + return STAMR3Reset(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; + if ( pUVM + && VMR3GetStateU(pUVM) < VMSTATE_DESTROYING) + return STAMR3Enum(pUVM, pszPat, pfnEnum, pvUser); + return VERR_INVALID_HANDLE; +} + + +int +VBoxDbgBase::dbgcCreate(PDBGCBACK pBack, unsigned fFlags) +{ + PUVM pUVM = m_pUVM; + if ( pUVM + && VMR3GetStateU(pUVM) < VMSTATE_DESTROYING) + return DBGCCreate(pUVM, pBack, fFlags); + return VERR_INVALID_HANDLE; +} + + +/*static*/ DECLCALLBACK(void) +VBoxDbgBase::atStateChange(PUVM pUVM, 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); + if (pUVM2) + { + Assert(pUVM2 == pUVM); + pThis->sigTerminated(); + VMR3ReleaseUVM(pUVM2); + } + break; + } + + case VMSTATE_DESTROYING: + pThis->sigDestroying(); + break; + + default: + break; + } +} + + +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) + : QWidget(a_pParent, Qt::Window), VBoxDbgBase(a_pDbgGui), m_fPolished(false), + m_x(INT_MAX), m_y(INT_MAX), m_cx(0), m_cy(0) +{ +} + + +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); + vPolishSizeAndPos(); + return fRc; +} + + +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) */ + /* only once. */ + if (!m_cxBorder && !m_cyBorder) + { + + /* 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. The code + * is based on the idea taken from QDialog::adjustPositionInternal(). */ + + int extraw = 0, extrah = 0; + + QWidgetList list = QApplication::topLevelWidgets(); + QListIterator<QWidget*> it (list); + while ((extraw == 0 || extrah == 0) && it.hasNext()) + { + int framew, frameh; + QWidget *current = it.next(); + if (!current->isVisible()) + continue; + + framew = current->frameGeometry().width() - current->width(); + frameh = current->frameGeometry().height() - current->height(); + + extraw = qMax (extraw, framew); + extrah = qMax (extrah, frameh); + } + + if (extraw || extrah) + { + m_cxBorder = extraw; + m_cyBorder = extrah; + } + } +#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..fa5cf873 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgBase.h @@ -0,0 +1,199 @@ +/* $Id: VBoxDbgBase.h $ */ +/** @file + * VBox Debugger GUI - Base classes. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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/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(PDBGCBACK pBack, 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, 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 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. + */ + VBoxDbgBaseWindow(VBoxDbgGui *a_pDbgGui, QWidget *a_pParent); + + /** + * 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); + + /** + * Internal worker for polishing the size and position (X11 hacks). + */ + void vPolishSizeAndPos(); + + /** + * Internal worker that guesses the border sizes. + */ + QSize vGuessBorderSizes(); + + +private: + /** 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..8f0a7935 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgConsole.cpp @@ -0,0 +1,1018 @@ +/* $Id: VBoxDbgConsole.cpp $ */ +/** @file + * VBox Debugger GUI - Console. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGG +#include "VBoxDbgConsole.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); + setAutoCompletion(false); + 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), 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) +{ + setWindowTitle("VBoxDbg - Console"); + + /* + * 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; +} + + +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(PDBGCBACK pBack, uint32_t cMillies) +{ + VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(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(PDBGCBACK pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead) +{ + VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(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(PDBGCBACK pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten) +{ + VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(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(PDBGCBACK pBack, bool fReady) +{ + VBoxDbgConsole *pThis = VBOXDBGCONSOLE_FROM_DBGCBACK(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(); + delete this; + } +} + + +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..dc61bc1e --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgConsole.h @@ -0,0 +1,436 @@ +/* $Id: VBoxDbgConsole.h $ */ +/** @file + * VBox Debugger GUI - Console. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 <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(PDBGCBACK pBack, 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(PDBGCBACK pBack, 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(PDBGCBACK pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten); + + /** + * @copydoc FNDBGCBACKSETREADY + */ + static DECLCALLBACK(void) backSetReady(PDBGCBACK pBack, 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_DBGCBACK to convert the DBGCBACK pointer to a object pointer. */ + struct VBoxDbgConsoleBack + { + DBGCBACK 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_DBGCBACK(pBack) ( ((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..aea9425c --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgDisas.h @@ -0,0 +1,43 @@ +/* $Id: VBoxDbgDisas.h $ */ +/** @file + * VBox Debugger GUI - Disassembly View. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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..718a6d98 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgGui.cpp @@ -0,0 +1,268 @@ +/* $Id: VBoxDbgGui.cpp $ */ +/** @file + * VBox Debugger GUI - The Manager. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DBGG +#define VBOX_COM_NO_ATL +#include <VBox/com/defs.h> +#include <VBox/vmm/vm.h> +#include <iprt/errcore.h> + +#include "VBoxDbgGui.h" +#include <QDesktopWidget> +#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_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) +{ + /* + * Set the VM handle and update the desktop size. + */ + m_pUVM = pUVM; /* Note! This eats the incoming reference to the handle! */ + 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 llVM; + hrc = m_pMachineDebugger->COMGETTER(VM)(&llVM); + if (SUCCEEDED(hrc)) + { + PUVM pUVM = (PUVM)(intptr_t)llVM; + rc = init(pUVM); + if (RT_SUCCESS(rc)) + return rc; + + VMR3ReleaseUVM(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) + { + VMR3ReleaseUVM(m_pUVM); + m_pUVM = NULL; + } +} + +void +VBoxDbgGui::setParent(QWidget *pParent) +{ + m_pParent = pParent; +} + + +void +VBoxDbgGui::setMenu(QMenu *pMenu) +{ + m_pMenu = pMenu; +} + + +int +VBoxDbgGui::showStatistics() +{ + if (!m_pDbgStats) + { + m_pDbgStats = new VBoxDbgStats(this, "*", 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); + QDesktopWidget *pDesktop = QApplication::desktop(); + if (pDesktop) + Rct = pDesktop->availableGeometry(QPoint(m_x, m_y)); + 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. */ + if (cx < 640) + 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); +} + + +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..895b182c --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgGui.h @@ -0,0 +1,196 @@ +/* $Id: VBoxDbgGui.h $ */ +/** @file + * VBox Debugger GUI - The Manager. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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 <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. + */ + int init(PUVM pUVM); + + /** + * 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. + */ + int showStatistics(); + + /** + * 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; + } + + +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 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..ef05cd94 --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgStatsQt.cpp @@ -0,0 +1,3224 @@ +/* $Id: VBoxDbgStatsQt.cpp $ */ +/** @file + * VBox Debugger GUI - Statistics. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_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> + + +/********************************************************************************************************************************* +* 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. */ + STAMUNIT enmUnit; + /** 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; + } 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); + + /** + * 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, STAMUNIT enmUnit, const char *pszDesc); + + /** + * Updates (or reinitializes if you like) a node. + */ + static void updateNode(PDBGGUISTATSNODE pNode, STAMTYPE enmType, void *pvSample, STAMUNIT enmUnit, 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, + 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. + */ + VBoxDbgStatsModelVM(VBoxDbgGui *a_pDbgGui, QString &a_rPatStr, QObject *a_pParent); + + /** 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, + 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); +}; + + +/********************************************************************************************************************************* +* 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->enmUnit = STAMUNIT_INVALID; + 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->enmUnit = STAMUNIT_INVALID; + 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->enmUnit = STAMUNIT_INVALID; + 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 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 and destroy all the children. */ + Stack.iTop--; + uint32_t i = pNode->cChildren; + if (i) + { + beginRemoveRows(createIndex(pNode->iSelf, 0, pNode), 0, i - 1); + while (i-- > 0) + destroyNode(pNode->papChildren[i]); + pNode->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, STAMUNIT enmUnit, const char *pszDesc) +{ + /* + * Copy the data. + */ + pNode->enmUnit = enmUnit; + 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, STAMUNIT enmUnit, 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, enmUnit, 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, + STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser) +{ + VBoxDbgStatsModelVM *pThis = (VBoxDbgStatsModelVM *)pvUser; + Log3(("updateCallback: %s\n", pszName)); + + /* + * 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, enmUnit, 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); + } +} + + +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) +{ + if (pNode->enmUnit == STAMUNIT_INVALID) + return ""; + return STAMR3GetUnit(pNode->enmUnit); +} + + +/*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, STAMR3GetUnit(a_pNode->enmUnit)); + 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, STAMR3GetUnit(a_pNode->enmUnit), + 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, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_CALLBACK: + if (a_pNode->Data.pStr) + a_rString += *a_pNode->Data.pStr; + RTStrPrintf(szBuf, sizeof(szBuf), " %s", STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_U8: + case STAMTYPE_U8_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u8, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_X8: + case STAMTYPE_X8_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u8, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_U16: + case STAMTYPE_U16_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u16, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_X16: + case STAMTYPE_X16_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u16, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_U32: + case STAMTYPE_U32_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8u %s", a_pNode->Data.u32, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_X32: + case STAMTYPE_X32_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8x %s", a_pNode->Data.u32, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_U64: + case STAMTYPE_U64_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8llu %s", a_pNode->Data.u64, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_X64: + case STAMTYPE_X64_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%8llx %s", a_pNode->Data.u64, STAMR3GetUnit(a_pNode->enmUnit)); + break; + + case STAMTYPE_BOOL: + case STAMTYPE_BOOL_RESET: + RTStrPrintf(szBuf, sizeof(szBuf), "%s %s", a_pNode->Data.f ? "true " : "false ", STAMR3GetUnit(a_pNode->enmUnit)); + 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) + : VBoxDbgStatsModel(a_pParent), VBoxDbgBase(a_pDbgGui) +{ + /* + * 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, + STAMVISIBILITY enmVisibility, const char *pszDesc, void *pvUser) +{ + PDBGGUISTATSNODE pRoot = (PDBGGUISTATSNODE)pvUser; + Log3(("createNewTreeCallback: %s\n", pszName)); + + /* + * 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, enmUnit, 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); + } +} + + +void +VBoxDbgStatsView::setSubTreeExpanded(QModelIndex const &a_rIndex, bool a_fExpanded) +{ + int cRows = m_pModel->rowCount(a_rIndex); + for (int i = 0; i < cRows; i++) + setSubTreeExpanded(a_rIndex.child(i, 0), 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 *pszPat/* = NULL*/, unsigned uRefreshRate/* = 0*/, QWidget *pParent/* = NULL*/) + : VBoxDbgBaseWindow(a_pDbgGui, pParent), m_PatStr(pszPat), m_pPatCB(NULL), m_uRefreshRate(0), m_pTimer(NULL), m_pView(NULL) +{ + /* Assign window-title: */ + if (parent()) + { + setWindowTitle(QString("%1 - Statistics").arg(parentWidget()->windowTitle())); + parent()->installEventFilter(this); + } + else + setWindowTitle("VBoxDbg - Statistics"); + + /* + * 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->setAutoCompletion(false); + 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); + 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(); + + /* + * 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(); + delete this; /** @todo This is wrong! We get more events after this one and end up using memory after freeing it in vPolishSizeAndPos(). (Qt3 holdover?) */ +} + + +bool VBoxDbgStats::eventFilter(QObject *pWatched, QEvent *pEvent) +{ + /* Skip events which are not related to our parent: */ + if (pWatched != parent()) + return VBoxDbgBaseWindow::eventFilter(pWatched, pEvent); + + /* Depending on event-type: */ + switch (pEvent->type()) + { + case QEvent::WindowTitleChange: setWindowTitle(QString("%1 - Statistics").arg(parentWidget()->windowTitle())); break; + default: break; + } + + /* Call to base-class: */ + return VBoxDbgBaseWindow::eventFilter(pWatched, pEvent); +} + + +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..705c909c --- /dev/null +++ b/src/VBox/Debugger/VBoxDbgStatsQt.h @@ -0,0 +1,253 @@ +/* $Id: VBoxDbgStatsQt.h $ */ +/** @file + * VBox Debugger GUI - Statistics. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#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(); + +protected: + /** + * 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 pszPat Initial selection pattern. NULL means everything. (See STAM for details.) + * @param uRefreshRate The refresh rate. 0 means not to refresh and is the default. + * @param pParent Parent widget. + */ + VBoxDbgStats(VBoxDbgGui *a_pDbgGui, const char *pszPat = 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); + + /** + * Event filter for various purposes. + * + * @param pWatched The object event came to. + * @param pEvent The event being handled. + */ + virtual bool eventFilter(QObject *pWatched, QEvent *pEvent); + +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..3ea5bf95 --- /dev/null +++ b/src/VBox/Debugger/testcase/tstDBGCParser.cpp @@ -0,0 +1,1286 @@ +/* $Id: tstDBGCParser.cpp $ */ +/** @file + * DBGC Testcase - Command Parser. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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(PDBGCBACK pBack, uint32_t cMillies); +static DECLCALLBACK(int) tstDBGCBackRead(PDBGCBACK pBack, void *pvBuf, size_t cbBuf, size_t *pcbRead); +static DECLCALLBACK(int) tstDBGCBackWrite(PDBGCBACK pBack, const void *pvBuf, size_t cbBuf, size_t *pcbWritten); +static DECLCALLBACK(void) tstDBGCBackSetReady(PDBGCBACK pBack, bool fReady); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The test handle. */ +static RTTEST g_hTest = NIL_RTTEST; + +/** The DBGC backend structure for use in this testcase. */ +static DBGCBACK g_tstBack = +{ + tstDBGCBackInput, + tstDBGCBackRead, + tstDBGCBackWrite, + 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(PDBGCBACK 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(PDBGCBACK 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(PDBGCBACK 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(PDBGCBACK 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; + 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..0e75733a --- /dev/null +++ b/src/VBox/Debugger/testcase/tstDBGCStubs.cpp @@ -0,0 +1,728 @@ +/* $Id: tstDBGCStubs.cpp $ */ +/** @file + * DBGC Testcase - Command Parser, VMM Stub Functions. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <VBox/err.h> +#include <VBox/vmm/vm.h> +#include <iprt/string.h> + + + +#include <VBox/vmm/dbgf.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, PCDBGFEVENT *ppEvent) +{ + 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) +{ + 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(bool) DBGFR3IsHalted(PUVM pUVM) +{ + 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) +{ + 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) 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; +} + +#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(PVMCPU pVCpu) +{ + return 0; +} + +VMMDECL(uint64_t) CPUMGetGuestCR4(PVMCPU pVCpu) +{ + return 0; +} + +VMMDECL(RTSEL) CPUMGetGuestCS(PVMCPU pVCpu) +{ + return 0; +} + +VMMDECL(PCCPUMCTXCORE) CPUMGetGuestCtxCore(PVMCPU pVCpu) +{ + return NULL; +} + +VMMDECL(uint32_t) CPUMGetGuestEIP(PVMCPU pVCpu) +{ + return 0; +} + +VMMDECL(uint64_t) CPUMGetGuestRIP(PVMCPU pVCpu) +{ + return 0; +} + +VMMDECL(RTGCPTR) CPUMGetGuestIDTR(PVMCPU pVCpu, uint16_t *pcbLimit) +{ + return 0; +} + +VMMDECL(CPUMMODE) CPUMGetGuestMode(PVMCPU pVCpu) +{ + return CPUMMODE_INVALID; +} + +VMMDECL(RTSEL) CPUMGetHyperCS(PVMCPU pVCpu) +{ + return 0xfff8; +} + +VMMDECL(uint32_t) CPUMGetHyperEIP(PVMCPU pVCpu) +{ + return 0; +} + +VMMDECL(PCPUMCTX) CPUMQueryGuestCtxPtr(PVMCPU pVCpu) +{ + return NULL; +} + +VMMDECL(bool) CPUMIsGuestIn64BitCode(PVMCPU pVCpu) +{ + return false; +} + +VMMDECL(uint32_t) CPUMGetGuestEFlags(PVMCPU 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(PVM) VMR3GetVM(PUVM pUVM) +{ + return NULL; +} + +VMMR3DECL(VMSTATE) VMR3GetStateU(PUVM pUVM) +{ + return VMSTATE_DESTROYING; +} diff --git a/src/VBox/Debugger/testcase/tstVBoxDbg.cpp b/src/VBox/Debugger/testcase/tstVBoxDbg.cpp new file mode 100644 index 00000000..ecf90e97 --- /dev/null +++ b/src/VBox/Debugger/testcase/tstVBoxDbg.cpp @@ -0,0 +1,119 @@ +/* $Id: tstVBoxDbg.cpp $ */ +/** @file + * VBox Debugger GUI, dummy testcase. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <qapplication.h> +#include <VBox/dbggui.h> +#include <VBox/vmm/vm.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> + + +#define TESTCASE "tstVBoxDbg" + + +int main(int argc, char **argv) +{ + int cErrors = 0; /* error count. */ + + RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); + RTPrintf(TESTCASE ": TESTING...\n"); + + /* + * Create empty VM. + */ + PVM pVM; + PUVM pUVM; + int rc = VMR3Create(1, NULL, NULL, NULL, NULL, NULL, &pVM, &pUVM); + if (RT_SUCCESS(rc)) + { + /* + * Instantiate the debugger GUI bits and run them. + */ + QApplication App(argc, argv); + PDBGGUI pGui; + PCDBGGUIVT pGuiVT; + rc = DBGGuiCreateForVM(pUVM, &pGui, &pGuiVT); + if (RT_SUCCESS(rc)) + { + if (argc <= 1 || argc == 2) + { + RTPrintf(TESTCASE ": calling pfnShowCommandLine...\n"); + rc = pGuiVT->pfnShowCommandLine(pGui); + if (RT_FAILURE(rc)) + { + RTPrintf(TESTCASE ": error: pfnShowCommandLine failed! rc=%Rrc\n", rc); + cErrors++; + } + } + + if (argc <= 1 || argc == 3) + { + RTPrintf(TESTCASE ": calling pfnShowStatistics...\n"); + pGuiVT->pfnShowStatistics(pGui); + if (RT_FAILURE(rc)) + { + RTPrintf(TESTCASE ": error: pfnShowStatistics failed! rc=%Rrc\n", rc); + cErrors++; + } + } + + pGuiVT->pfnAdjustRelativePos(pGui, 0, 0, 640, 480); + RTPrintf(TESTCASE ": calling App.exec()...\n"); + App.exec(); + } + else + { + RTPrintf(TESTCASE ": error: DBGGuiCreateForVM failed! rc=%Rrc\n", rc); + cErrors++; + } + + /* + * Cleanup. + */ + rc = VMR3Destroy(pUVM); + if (!RT_SUCCESS(rc)) + { + RTPrintf(TESTCASE ": error: failed to destroy vm! rc=%Rrc\n", rc); + cErrors++; + } + VMR3ReleaseUVM(pUVM); + } + else + { + RTPrintf(TESTCASE ": fatal error: failed to create vm! rc=%Rrc\n", rc); + cErrors++; + } + + /* + * Summary and exit. + */ + if (!cErrors) + RTPrintf(TESTCASE ": SUCCESS\n"); + else + RTPrintf(TESTCASE ": FAILURE - %d errors\n", cErrors); + return !!cErrors; +} + |