/** $Id: DBGConsole.cpp 5675 2007-11-11 05:42:00Z vboxsync $ */ /** @file * DBGC - Debugger Console. */ /* * Copyright (C) 2006-2007 innotek GmbH * * 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 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 a first attempt to make some interactive * debugging facilities for the VirtualBox backend (i.e. the VM). At a later * stage we'll make a fancy gui around this, but for the present a telnet (or * serial terminal) will have to suffice. * * The debugger is only built into the VM with debug builds or when * VBOX_WITH_DEBUGGER is defined. There might be need for \#ifdef'ing on this * define to enable special debugger hooks, but the general approach is to * make generic interfaces. The individual components also can register * external commands, and such code must be within \#ifdef. * * * @section sec_dbgc_op Operation (intentions) * * 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. * * * @subsection sec_dbg_op_numbers Numbers * * Numbers are hexadecimal unless specified with a prefix indicating * elsewise. Prefixes: * - '0x' - hexadecimal. * - '0i' - decimal * - '0t' - octal. * - '0y' - binary. * * * @subsection sec_dbg_op_address Addressing modes * * - Default is flat. For compatability '%' 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. * * * @subsection sec_dbg_op_evalution Evaluation * * As time permits support will be implemented support for a subset of the C * binary operators, starting with '+', '-', '*' and '/'. Support for variables * are provided thru commands 'set' and 'unset' and the unary operator '$'. The * unary '@' operator will indicate function calls. The debugger needs a set of * memory read functions, but we might later extend this to allow registration of * external functions too. * * A special command '?' will then be added which evalutates a given expression * and prints it in all the different formats. * * * @subsection sec_dbg_op_registers Registers * * Registers are addressed using their name. Some registers which have several fields * (like gdtr) will have separate names indicating the different fields. The default * register set is the guest one. To access the hypervisor register one have to * prefix the register names with '.'. * * * @subsection sec_dbg_op_commands Commands * * The commands are all lowercase, case sensitive, and starting with a letter. We will * later add some special commands ('?' for evaulation) and perhaps command classes ('.', '!') * * * @section sec_dbg_tasks Tasks * * To implement DBGT and instrument VMM for basic state inspection and log * viewing, the follwing task must be executed: * * -# Basic threading layer in RT. * -# Basic tcpip server abstration in RT. * -# Write DBGC. * -# Write DBCTCP. * -# Integrate with VMM and the rest. * -# Start writing DBGF (VMM). */ /******************************************************************************* * Header Files * *******************************************************************************/ #define LOG_GROUP LOG_GROUP_DBGC #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DBGCInternal.h" /******************************************************************************* * Internal Functions * *******************************************************************************/ static int dbgcEvalSub(PDBGC pDbgc, char *pszExpr, size_t cchExpr, PDBGCVAR pResult); static int dbgcProcessCommand(PDBGC pDbgc, char *pszCmd, size_t cchCmd); /******************************************************************************* * Global Variables * *******************************************************************************/ /** Bitmap where set bits indicates the characters the may start an operator name. */ static uint32_t g_bmOperatorChars[256 / (4*8)]; //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// // // // C a l l b a c k H e l p e r s // // //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// /** * Command helper for writing text to the debug console. * * @returns VBox status. * @param pCmdHlp Pointer to the command callback structure. * @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) dbgcHlpWrite(PDBGCCMDHLP pCmdHlp, const void *pvBuf, size_t cbBuf, size_t *pcbWritten) { PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); return pDbgc->pBack->pfnWrite(pDbgc->pBack, pvBuf, cbBuf, pcbWritten); } /** * Command helper for writing formatted text to the debug console. * * @returns VBox status. * @param pCmdHlp Pointer to the command callback structure. * @param pcb Where to store the number of bytes written. * @param pszFormat The format string. * This is using the log formatter, so it's format extensions can be used. * @param ... Arguments specified in the format string. */ 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; } /** * Callback to format non-standard format specifiers. * * @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, "%%%VGv", 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, "%%%%%VGp", pVar->u.GCPhys); case DBGCVAR_TYPE_HC_FLAT: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%#%VHv", (uintptr_t)pVar->u.pvHCFlat); case DBGCVAR_TYPE_HC_FAR: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "#%04x:%08x", pVar->u.HCFar.sel, pVar->u.HCFar.off); case DBGCVAR_TYPE_HC_PHYS: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "#%%%%%VHp", pVar->u.HCPhys); case DBGCVAR_TYPE_STRING: return pfnOutput(pvArgOutput, pVar->u.pszString, (size_t)pVar->u64Range); case DBGCVAR_TYPE_NUMBER: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%llx", pVar->u.u64Number); 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, "%%%VGv%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, "%%%%%VGp%s", pVar->u.GCPhys, szRange); case DBGCVAR_TYPE_HC_FLAT: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%%#%VHv%s", (uintptr_t)pVar->u.pvHCFlat, szRange); case DBGCVAR_TYPE_HC_FAR: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "#%04x:%08x%s", pVar->u.HCFar.sel, pVar->u.HCFar.off, szRange); case DBGCVAR_TYPE_HC_PHYS: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "#%%%%%VHp%s", pVar->u.HCPhys, szRange); case DBGCVAR_TYPE_STRING: return pfnOutput(pvArgOutput, pVar->u.pszString, (size_t)pVar->u64Range); case DBGCVAR_TYPE_NUMBER: return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%llx%s", pVar->u.u64Number, szRange); case DBGCVAR_TYPE_UNKNOWN: default: return pfnOutput(pvArgOutput, "??", 2); } } default: AssertMsgFailed(("Invalid format type '%s'!\n", **ppszFormat)); return 0; } } /** * Output callback. * * @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 (VBOX_FAILURE(rc)) { pDbgc->rcOutput = rc; cbChars = 0; } } return cbChars; } /** * Command helper for writing formatted text to the debug console. * * @returns VBox status. * @param pCmdHlp Pointer to the command callback structure. * @param pcb Where to store the number of bytes written. * @param pszFormat The format string. * This is using the log formatter, so it's format extensions can be used. * @param args Arguments specified in the format string. */ 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; } /** * Reports an error from a DBGF call. * * @returns VBox status code appropriate to return from a command. * @param pCmdHlp Pointer to command helpers. * @param rc The VBox status code returned by a DBGF call. * @param pszFormat Format string for additional messages. Can be NULL. * @param ... Format arguments, optional. */ 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: %Vrc: %s", rc, pszFormat ? " " : "\n"); if (VBOX_SUCCESS(rc) && pszFormat) rc = pCmdHlp->pfnPrintfV(pCmdHlp, NULL, pszFormat, args); break; } return rc; } /** * Reports an error from a DBGF call. * * @returns VBox status code appropriate to return from a command. * @param pCmdHlp Pointer to command helpers. * @param rc The VBox status code returned by a DBGF call. * @param pszFormat Format string for additional messages. Can be NULL. * @param ... Format arguments, optional. */ 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; } /** * Command helper for reading memory specified by a DBGC variable. * * @returns VBox status code appropriate to return from a command. * @param pCmdHlp Pointer to the command callback structure. * @param pVM VM handle if GC or physical HC address. * @param pvBuffer Where to store the read data. * @param cbRead Number of bytes to read. * @param pVarPointer DBGC variable specifying where to start reading. * @param pcbRead Where to store the number of bytes actually read. * This optional, but it's useful when read GC virtual memory where a * page in the requested range might not be present. * If not specified not-present failure or end of a HC physical page * will cause failure. */ static DECLCALLBACK(int) dbgcHlpMemRead(PDBGCCMDHLP pCmdHlp, PVM pVM, void *pvBuffer, size_t cbRead, PCDBGCVAR pVarPointer, size_t *pcbRead) { PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); /* * 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->pVM); DBGFADDRESS Address; int rc = DBGFR3AddrFromSelOff(pDbgc->pVM, &Address, Var.u.GCFar.sel, Var.u.GCFar.off); if (VBOX_FAILURE(rc)) return rc; /* don't bother with flat selectors (for now). */ if (!DBGFADDRESS_IS_FLAT(&Address)) { SELMSELINFO SelInfo; rc = SELMR3GetSelectorInfo(pDbgc->pVM, Address.Sel, &SelInfo); if (VBOX_SUCCESS(rc)) { RTGCUINTPTR cb; /* -1 byte */ if (SELMSelInfoIsExpandDown(&SelInfo)) { if ( !SelInfo.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.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; case DBGCVAR_TYPE_HC_FAR: /* not supported yet! */ 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. */ int rc; switch (Var.enmType) { case DBGCVAR_TYPE_GC_FLAT: rc = MMR3ReadGCVirt(pVM, pvBuffer, Var.u.GCFlat, cb); break; case DBGCVAR_TYPE_GC_PHYS: rc = PGMPhysReadGCPhys(pVM, pvBuffer, Var.u.GCPhys, cb); break; case DBGCVAR_TYPE_HC_PHYS: case DBGCVAR_TYPE_HC_FLAT: case DBGCVAR_TYPE_HC_FAR: { DBGCVAR Var2; rc = dbgcOpAddrFlat(pDbgc, &Var, &Var2); if (VBOX_SUCCESS(rc)) { /** @todo protect this!!! */ memcpy(pvBuffer, Var2.u.pvHCFlat, cb); rc = 0; } else rc = VERR_INVALID_POINTER; break; } default: rc = VERR_PARSE_INCORRECT_ARG_TYPE; } /* * Check for failure. */ if (VBOX_FAILURE(rc)) { if (pcbRead && (*pcbRead = cbRead - cbLeft) > 0) return VINF_SUCCESS; return rc; } /* * Next. */ cbLeft -= cb; if (!cbLeft) break; pvBuffer = (char *)pvBuffer + cb; rc = pCmdHlp->pfnEval(pCmdHlp, &Var, "%DV + %d", &Var, cb); if (VBOX_FAILURE(rc)) { if (pcbRead && (*pcbRead = cbRead - cbLeft) > 0) return VINF_SUCCESS; return rc; } } /* * Done */ if (pcbRead) *pcbRead = cbRead; return 0; } /** * Command helper for writing memory specified by a DBGC variable. * * @returns VBox status code appropriate to return from a command. * @param pCmdHlp Pointer to the command callback structure. * @param pVM VM handle if GC or physical HC address. * @param pvBuffer What to write. * @param cbWrite Number of bytes to write. * @param pVarPointer DBGC variable specifying where to start reading. * @param pcbWritten Where to store the number of bytes written. * This is optional. If NULL be aware that some of the buffer * might have been written to the specified address. */ static DECLCALLBACK(int) dbgcHlpMemWrite(PDBGCCMDHLP pCmdHlp, PVM pVM, const void *pvBuffer, size_t cbWrite, PCDBGCVAR pVarPointer, size_t *pcbWritten) { NOREF(pCmdHlp); NOREF(pVM); NOREF(pvBuffer); NOREF(cbWrite); NOREF(pVarPointer); NOREF(pcbWritten); return VERR_NOT_IMPLEMENTED; } /** * Evaluates an expression. * (Hopefully the parser and functions are fully reentrant.) * * @returns VBox status code appropriate to return from a command. * @param pCmdHlp Pointer to the command callback structure. * @param pResult Where to store the result. * @param pszExpr The expression. Format string with the format DBGC extensions. * @param ... Format arguments. */ static DECLCALLBACK(int) dbgcHlpEval(PDBGCCMDHLP pCmdHlp, PDBGCVAR pResult, const char *pszExpr, ...) { PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); /* * Format the expression. */ char szExprFormatted[2048]; va_list args; va_start(args, pszExpr); size_t cb = RTStrPrintfExV(dbgcStringFormatter, pDbgc, szExprFormatted, sizeof(szExprFormatted), pszExpr, args); va_end(args); /* ignore overflows. */ return dbgcEvalSub(pDbgc, &szExprFormatted[0], cb, pResult); } /** * Executes one command expression. * (Hopefully the parser and functions are fully reentrant.) * * @returns VBox status code appropriate to return from a command. * @param pCmdHlp Pointer to the command callback structure. * @param pszExpr The expression. Format string with the format DBGC extensions. * @param ... Format arguments. */ 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 = dbgcProcessCommand(pDbgc, pszScratch, cb); /* Restore the scratch state. */ pDbgc->iArg = iArg; pDbgc->pszScratch = pszScratch; return rc; } /** * Converts a DBGC variable to a DBGF address structure. * * @returns VBox status code. * @param pCmdHlp Pointer to the command callback structure. * @param pVar The variable to convert. * @param pAddress The target address. */ static DECLCALLBACK(int) dbgcHlpVarToDbgfAddr(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, PDBGFADDRESS pAddress) { PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); return dbgcVarToDbgfAddr(pDbgc, pVar, pAddress); } /** * Converts a DBGC variable to a boolean. * * @returns VBox status code. * @param pCmdHlp Pointer to the command callback structure. * @param pVar The variable to convert. * @param pf Where to store the boolean. */ static DECLCALLBACK(int) dbgcHlpVarToBool(PDBGCCMDHLP pCmdHlp, PCDBGCVAR pVar, bool *pf) { PDBGC pDbgc = DBGC_CMDHLP2DBGC(pCmdHlp); NOREF(pDbgc); switch (pVar->enmType) { case DBGCVAR_TYPE_STRING: /** @todo add strcasecmp / stricmp wrappers to iprt/string.h. */ if ( !strcmp(pVar->u.pszString, "true") || !strcmp(pVar->u.pszString, "True") || !strcmp(pVar->u.pszString, "TRUE") || !strcmp(pVar->u.pszString, "on") || !strcmp(pVar->u.pszString, "On") || !strcmp(pVar->u.pszString, "oN") || !strcmp(pVar->u.pszString, "ON") || !strcmp(pVar->u.pszString, "enabled") || !strcmp(pVar->u.pszString, "Enabled") || !strcmp(pVar->u.pszString, "DISABLED")) { *pf = true; return VINF_SUCCESS; } if ( !strcmp(pVar->u.pszString, "false") || !strcmp(pVar->u.pszString, "False") || !strcmp(pVar->u.pszString, "FALSE") || !strcmp(pVar->u.pszString, "off") || !strcmp(pVar->u.pszString, "Off") || !strcmp(pVar->u.pszString, "OFF") || !strcmp(pVar->u.pszString, "disabled") || !strcmp(pVar->u.pszString, "Disabled") || !strcmp(pVar->u.pszString, "DISABLED")) { *pf = false; return VINF_SUCCESS; } return VERR_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_HC_FAR: case DBGCVAR_TYPE_GC_FAR: case DBGCVAR_TYPE_SYMBOL: default: return VERR_PARSE_INCORRECT_ARG_TYPE; } } //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// // // // V a r i a b l e M a n i p u l a t i o n // // //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// /** @todo move me!*/ void dbgcVarSetGCFlat(PDBGCVAR pVar, RTGCPTR GCFlat) { if (pVar) { pVar->enmType = DBGCVAR_TYPE_GC_FLAT; pVar->u.GCFlat = GCFlat; pVar->enmRangeType = DBGCVAR_RANGE_NONE; pVar->u64Range = 0; } } /** @todo move me!*/ void dbgcVarSetGCFlatByteRange(PDBGCVAR pVar, RTGCPTR GCFlat, uint64_t cb) { if (pVar) { pVar->enmType = DBGCVAR_TYPE_GC_FLAT; pVar->u.GCFlat = GCFlat; pVar->enmRangeType = DBGCVAR_RANGE_BYTES; pVar->u64Range = cb; } } /** @todo move me!*/ void dbgcVarSetVar(PDBGCVAR pVar, PCDBGCVAR pVar2) { if (pVar) { if (pVar2) *pVar = *pVar2; else { pVar->enmType = DBGCVAR_TYPE_UNKNOWN; memset(&pVar->u, 0, sizeof(pVar->u)); pVar->enmRangeType = DBGCVAR_RANGE_NONE; pVar->u64Range = 0; } } } /** @todo move me!*/ void dbgcVarSetByteRange(PDBGCVAR pVar, uint64_t cb) { if (pVar) { pVar->enmRangeType = DBGCVAR_RANGE_BYTES; pVar->u64Range = cb; } } /** @todo move me!*/ void dbgcVarSetNoRange(PDBGCVAR pVar) { if (pVar) { pVar->enmRangeType = DBGCVAR_RANGE_NONE; pVar->u64Range = 0; } } /** * Converts a DBGC variable to a DBGF address. * * @returns VBox status code. * @param pDbgc The DBGC instance. * @param pVar The variable. * @param pAddress Where to store the address. */ int dbgcVarToDbgfAddr(PDBGC pDbgc, PCDBGCVAR pVar, PDBGFADDRESS pAddress) { AssertReturn(pVar, VERR_INVALID_PARAMETER); switch (pVar->enmType) { case DBGCVAR_TYPE_GC_FLAT: DBGFR3AddrFromFlat(pDbgc->pVM, pAddress, pVar->u.GCFlat); return VINF_SUCCESS; case DBGCVAR_TYPE_NUMBER: DBGFR3AddrFromFlat(pDbgc->pVM, pAddress, (RTGCUINTPTR)pVar->u.u64Number); return VINF_SUCCESS; case DBGCVAR_TYPE_GC_FAR: return DBGFR3AddrFromSelOff(pDbgc->pVM, pAddress, pVar->u.GCFar.sel, pVar->u.GCFar.sel); case DBGCVAR_TYPE_STRING: case DBGCVAR_TYPE_SYMBOL: { DBGCVAR Var; int rc = pDbgc->CmdHlp.pfnEval(&pDbgc->CmdHlp, &Var, "%%(%DV)", pVar); if (VBOX_FAILURE(rc)) return rc; return dbgcVarToDbgfAddr(pDbgc, &Var, pAddress); } case DBGCVAR_TYPE_GC_PHYS: case DBGCVAR_TYPE_HC_FLAT: case DBGCVAR_TYPE_HC_FAR: case DBGCVAR_TYPE_HC_PHYS: default: return VERR_PARSE_CONVERSION_FAILED; } } //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// // // // 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_OFFSETOF(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 dbgcProcessCommand() other wise. * @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 = dbgcProcessCommand(pDbgc, pszScratch, pBp->cchCmd); /* Restore the scratch state. */ pDbgc->iArg = iArg; pDbgc->pszScratch = pszScratch; return rc; } //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// // // // I n p u t , p a r s i n g a n d l o g g i n g // // //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\// /** * Prints any log lines from the log buffer. * * The caller must not call function this unless pDbgc->fLog is set. * * @returns VBox status. (output related) * @param pDbgc Debugger console instance data. */ static int dbgcProcessLog(PDBGC pDbgc) { /** @todo */ NOREF(pDbgc); return 0; } /** * Handle input buffer overflow. * * Will read any available input looking for a '\n' to reset the buffer on. * * @returns VBox status. * @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 (VBOX_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. * 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 (VBOX_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; } 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; } /** * 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. * @param pResult Where to store the result. */ int dbgcSymbolGet(PDBGC pDbgc, const char *pszSymbol, DBGCVARTYPE enmType, PDBGCVAR pResult) { /* * Builtin? */ PCDBGCSYM pSymDesc = dbgcLookupRegisterSymbol(pDbgc, pszSymbol); if (pSymDesc) { if (!pSymDesc->pfnGet) return VERR_PARSE_WRITEONLY_SYMBOL; return pSymDesc->pfnGet(pSymDesc, &pDbgc->CmdHlp, enmType, pResult); } /* * Ask PDM. */ /** @todo resolve symbols using PDM. */ /* * Ask the debug info manager. */ DBGFSYMBOL Symbol; int rc = DBGFR3SymbolByName(pDbgc->pVM, pszSymbol, &Symbol); if (VBOX_SUCCESS(rc)) { /* * Default return is a flat gc address. */ memset(pResult, 0, sizeof(*pResult)); pResult->enmRangeType = Symbol.cb ? DBGCVAR_RANGE_BYTES : DBGCVAR_RANGE_NONE; pResult->u64Range = Symbol.cb; pResult->enmType = DBGCVAR_TYPE_GC_FLAT; pResult->u.GCFlat = Symbol.Value; DBGCVAR VarTmp; switch (enmType) { /* nothing to do. */ case DBGCVAR_TYPE_GC_FLAT: case DBGCVAR_TYPE_GC_FAR: case DBGCVAR_TYPE_ANY: return VINF_SUCCESS; /* 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: VarTmp = *pResult; return dbgcOpAddrPhys(pDbgc, &VarTmp, pResult); case DBGCVAR_TYPE_HC_FAR: case DBGCVAR_TYPE_HC_FLAT: VarTmp = *pResult; return dbgcOpAddrHost(pDbgc, &VarTmp, pResult); case DBGCVAR_TYPE_HC_PHYS: VarTmp = *pResult; return dbgcOpAddrHostPhys(pDbgc, &VarTmp, pResult); default: AssertMsgFailed(("Internal error enmType=%d\n", enmType)); return VERR_INVALID_PARAMETER; } } return VERR_PARSE_NOT_IMPLEMENTED; } /** * Initalizes g_bmOperatorChars. */ static void dbgcInitOpCharBitMap(void) { memset(g_bmOperatorChars, 0, sizeof(g_bmOperatorChars)); for (unsigned iOp = 0; iOp < g_cOps; iOp++) ASMBitSet(&g_bmOperatorChars[0], (uint8_t)g_aOps[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); } static int dbgcEvalSubString(PDBGC pDbgc, char *pszExpr, size_t cchExpr, PDBGCVAR pArg) { Log2(("dbgcEvalSubString: cchExpr=%d pszExpr=%s\n", cchExpr, pszExpr)); /* * Removing any quoting and escapings. */ char ch = *pszExpr; if (ch == '"' || ch == '\'' || ch == '`') { if (pszExpr[--cchExpr] != ch) return VERR_PARSE_UNBALANCED_QUOTE; cchExpr--; pszExpr++; /** @todo string unescaping. */ } pszExpr[cchExpr] = '\0'; /* * Make the argument. */ pArg->pDesc = NULL; pArg->pNext = NULL; pArg->enmType = DBGCVAR_TYPE_STRING; pArg->u.pszString = pszExpr; pArg->enmRangeType = DBGCVAR_RANGE_BYTES; pArg->u64Range = cchExpr; NOREF(pDbgc); return 0; } static int dbgcEvalSubNum(char *pszExpr, unsigned uBase, PDBGCVAR pArg) { Log2(("dbgcEvalSubNum: uBase=%d pszExpr=%s\n", uBase, pszExpr)); /* * Convert to number. */ uint64_t u64 = 0; char ch; while ((ch = *pszExpr) != '\0') { 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_PARSE_INVALID_NUMBER; /* check for overflow - ARG!!! How to detect overflow correctly!?!?!? */ if (u64Prev != u64 / uBase) return VERR_PARSE_NUMBER_TOO_BIG; /* next */ pszExpr++; } /* * 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 0; } /** * Match variable and variable descriptor, promoting the variable if necessary. * * @returns VBox status code. * @param pDbgc Debug console instanace. * @param pVar Variable. * @param pVarDesc Variable descriptor. */ static int dbgcEvalSubMatchVar(PDBGC pDbgc, PDBGCVAR pVar, PCDBGCVARDESC pVarDesc) { /* * (If match or promoted to match, return, else break.) */ switch (pVarDesc->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: if (pVar->enmRangeType != DBGCVAR_RANGE_NONE) return VERR_PARSE_NO_RANGE_ALLOWED; /* fallthru */ case DBGCVAR_CAT_POINTER: switch (pVar->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_FAR: case DBGCVAR_TYPE_HC_PHYS: return VINF_SUCCESS; case DBGCVAR_TYPE_SYMBOL: case DBGCVAR_TYPE_STRING: { DBGCVAR Var; int rc = dbgcSymbolGet(pDbgc, pVar->u.pszString, DBGCVAR_TYPE_GC_FLAT, &Var); if (VBOX_SUCCESS(rc)) { /* deal with range */ if (pVar->enmRangeType != DBGCVAR_RANGE_NONE) { Var.enmRangeType = pVar->enmRangeType; Var.u64Range = pVar->u64Range; } else if (pVarDesc->enmCategory == DBGCVAR_CAT_POINTER_NO_RANGE) Var.enmRangeType = DBGCVAR_RANGE_NONE; *pVar = Var; return rc; } break; } case DBGCVAR_TYPE_NUMBER: { RTGCPTR GCPtr = (RTGCPTR)pVar->u.u64Number; pVar->enmType = DBGCVAR_TYPE_GC_FLAT; pVar->u.GCFlat = GCPtr; return VINF_SUCCESS; } default: break; } break; /* * 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 (pVar->enmRangeType != DBGCVAR_RANGE_NONE) return VERR_PARSE_NO_RANGE_ALLOWED; /* fallthru */ case DBGCVAR_CAT_GC_POINTER: switch (pVar->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_FAR: case DBGCVAR_TYPE_HC_PHYS: return VERR_PARSE_CONVERSION_FAILED; case DBGCVAR_TYPE_SYMBOL: case DBGCVAR_TYPE_STRING: { DBGCVAR Var; int rc = dbgcSymbolGet(pDbgc, pVar->u.pszString, DBGCVAR_TYPE_GC_FLAT, &Var); if (VBOX_SUCCESS(rc)) { /* deal with range */ if (pVar->enmRangeType != DBGCVAR_RANGE_NONE) { Var.enmRangeType = pVar->enmRangeType; Var.u64Range = pVar->u64Range; } else if (pVarDesc->enmCategory == DBGCVAR_CAT_POINTER_NO_RANGE) Var.enmRangeType = DBGCVAR_RANGE_NONE; *pVar = Var; return rc; } break; } case DBGCVAR_TYPE_NUMBER: { RTGCPTR GCPtr = (RTGCPTR)pVar->u.u64Number; pVar->enmType = DBGCVAR_TYPE_GC_FLAT; pVar->u.GCFlat = GCPtr; return VINF_SUCCESS; } default: break; } break; /* * 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 (pVar->enmRangeType != DBGCVAR_RANGE_NONE) return VERR_PARSE_NO_RANGE_ALLOWED; /* fallthru */ case DBGCVAR_CAT_NUMBER: switch (pVar->enmType) { case DBGCVAR_TYPE_NUMBER: return VINF_SUCCESS; case DBGCVAR_TYPE_SYMBOL: case DBGCVAR_TYPE_STRING: { DBGCVAR Var; int rc = dbgcSymbolGet(pDbgc, pVar->u.pszString, DBGCVAR_TYPE_NUMBER, &Var); if (VBOX_SUCCESS(rc)) { *pVar = Var; return rc; } break; } default: break; } break; /* * Strings can easily be made from symbols (and of course strings). * We could consider reformatting the addresses and numbers into strings later... */ case DBGCVAR_CAT_STRING: switch (pVar->enmType) { case DBGCVAR_TYPE_SYMBOL: pVar->enmType = DBGCVAR_TYPE_STRING; /* fallthru */ case DBGCVAR_TYPE_STRING: return VINF_SUCCESS; default: break; } break; /* * Symol is pretty much the same thing as a string (at least until we actually implement it). */ case DBGCVAR_CAT_SYMBOL: switch (pVar->enmType) { case DBGCVAR_TYPE_STRING: pVar->enmType = DBGCVAR_TYPE_SYMBOL; /* fallthru */ case DBGCVAR_TYPE_SYMBOL: return VINF_SUCCESS; default: break; } break; /* * Anything else is illegal. */ default: AssertMsgFailed(("enmCategory=%d\n", pVar->enmType)); break; } return VERR_PARSE_NO_ARGUMENT_MATCH; } /** * Matches a set of variables with a description set. * * This is typically used for routine arguments before a call. The effects in * addition to the validation, is that some variables might be propagated to * other types in order to match the description. The following transformations * are supported: * - String reinterpreted as a symbol and resolved to a number or pointer. * - Number to a pointer. * - Pointer to a number. * @returns 0 on success with paVars. * @returns VBox error code for match errors. */ static int dbgcEvalSubMatchVars(PDBGC pDbgc, unsigned cVarsMin, unsigned cVarsMax, PCDBGCVARDESC paVarDescs, unsigned cVarDescs, PDBGCVAR paVars, unsigned cVars) { /* * Just do basic min / max checks first. */ if (cVars < cVarsMin) return VERR_PARSE_TOO_FEW_ARGUMENTS; if (cVars > cVarsMax) return VERR_PARSE_TOO_MANY_ARGUMENTS; /* * Match the descriptors and actual variables. */ PCDBGCVARDESC pPrevDesc = NULL; unsigned cCurDesc = 0; unsigned iVar = 0; unsigned iVarDesc = 0; while (iVar < cVars) { /* walk the descriptors */ if (iVarDesc >= cVarDescs) return VERR_PARSE_TOO_MANY_ARGUMENTS; if ( ( paVarDescs[iVarDesc].fFlags & DBGCVD_FLAGS_DEP_PREV && &paVarDescs[iVarDesc - 1] != pPrevDesc) || cCurDesc >= paVarDescs[iVarDesc].cTimesMax) { iVarDesc++; if (iVarDesc >= cVarDescs) return VERR_PARSE_TOO_MANY_ARGUMENTS; cCurDesc = 0; } /* * Skip thru optional arguments until we find something which matches * or can easily be promoted to what the descriptor want. */ for (;;) { int rc = dbgcEvalSubMatchVar(pDbgc, &paVars[iVar], &paVarDescs[iVarDesc]); if (VBOX_SUCCESS(rc)) { paVars[iVar].pDesc = &paVarDescs[iVarDesc]; cCurDesc++; break; } /* can we advance? */ if (paVarDescs[iVarDesc].cTimesMin > cCurDesc) return VERR_PARSE_ARGUMENT_TYPE_MISMATCH; if (++iVarDesc >= cVarDescs) return VERR_PARSE_ARGUMENT_TYPE_MISMATCH; cCurDesc = 0; } /* next var */ iVar++; } /* * Check that the rest of the descriptors are optional. */ while (iVarDesc < cVarDescs) { if (paVarDescs[iVarDesc].cTimesMin > cCurDesc) return VERR_PARSE_TOO_FEW_ARGUMENTS; cCurDesc = 0; /* next */ iVarDesc++; } return 0; } /** * Evaluates one argument with respect to unary operators. * * @returns 0 on success. pResult contains the result. * @returns VBox error code on parse or other evaluation error. * * @param pDbgc Debugger console instance data. * @param pszExpr The expression string. * @param pResult Where to store the result of the expression evaluation. */ static int dbgcEvalSubUnary(PDBGC pDbgc, char *pszExpr, size_t cchExpr, 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 = 0; PCDBGCOP pOp = dbgcOperatorLookup(pDbgc, pszExpr, false, ' '); if (pOp) { /* binary operators means syntax error. */ if (pOp->fBinary) return VERR_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 (isblank(*pszExpr2)) pszExpr2++; if (!*pszExpr2) rc = VERR_PARSE_EMPTY_ARGUMENT; else { DBGCVAR Arg; if (*pszExpr2 == '(') rc = dbgcEvalSub(pDbgc, pszExpr2, cchExpr - (pszExpr2 - pszExpr), &Arg); else rc = dbgcEvalSubUnary(pDbgc, pszExpr2, cchExpr - (pszExpr2 - pszExpr), &Arg); if (VBOX_SUCCESS(rc)) rc = pOp->pfnHandlerUnary(pDbgc, &Arg, pResult); } } else { /* * Didn't find any operators, so it we have to check if this can be an * function call before assuming numeric or string expression. * * (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 over * the remaining part of the expression. */ bool fExternal = *pszExpr == '.'; char *pszFun = fExternal ? pszExpr + 1 : pszExpr; char *pszFunEnd = NULL; if (pszExpr[cchExpr - 1] == ')' && isalpha(*pszFun)) { pszFunEnd = pszExpr + 1; while (*pszFunEnd != '(' && isalnum(*pszFunEnd)) pszFunEnd++; if (*pszFunEnd != '(') pszFunEnd = NULL; } if (pszFunEnd) { /* * Ok, it's a function call. */ if (fExternal) pszExpr++, cchExpr--; PCDBGCCMD pFun = dbgcRoutineLookup(pDbgc, pszExpr, pszFunEnd - pszExpr, fExternal); if (!pFun) return VERR_PARSE_FUNCTION_NOT_FOUND; if (!pFun->pResultDesc) return VERR_PARSE_NOT_A_FUNCTION; /* * Parse the expression in parenthesis. */ cchExpr -= pszFunEnd - pszExpr; pszExpr = pszFunEnd; /** @todo implement multiple arguments. */ DBGCVAR Arg; rc = dbgcEvalSub(pDbgc, pszExpr, cchExpr, &Arg); if (!rc) { rc = dbgcEvalSubMatchVars(pDbgc, pFun->cArgsMin, pFun->cArgsMax, pFun->paArgDescs, pFun->cArgDescs, &Arg, 1); if (!rc) rc = pFun->pfnHandler(pFun, &pDbgc->CmdHlp, pDbgc->pVM, &Arg, 1, pResult); } else if (rc == VERR_PARSE_EMPTY_ARGUMENT && pFun->cArgsMin == 0) rc = pFun->pfnHandler(pFun, &pDbgc->CmdHlp, pDbgc->pVM, NULL, 0, pResult); } else { /* * Didn't find any operators, so it must be a plain expression. * This might be numeric or a string expression. */ char ch = pszExpr[0]; char ch2 = pszExpr[1]; if (ch == '0' && (ch2 == 'x' || ch2 == 'X')) rc = dbgcEvalSubNum(pszExpr + 2, 16, pResult); else if (ch == '0' && (ch2 == 'i' || ch2 == 'i')) rc = dbgcEvalSubNum(pszExpr + 2, 10, pResult); else if (ch == '0' && (ch2 == 't' || ch2 == 'T')) rc = dbgcEvalSubNum(pszExpr + 2, 8, pResult); /// @todo 0b doesn't work as a binary prefix, we confuse it with 0bf8:0123 and stuff. //else if (ch == '0' && (ch2 == 'b' || ch2 == 'b')) // rc = dbgcEvalSubNum(pszExpr + 2, 2, pResult); else { /* * Hexadecimal number or a string? */ char *psz = pszExpr; while (isxdigit(*psz)) psz++; if (!*psz) rc = dbgcEvalSubNum(pszExpr, 16, pResult); else if ((*psz == 'h' || *psz == 'H') && !psz[1]) { *psz = '\0'; rc = dbgcEvalSubNum(pszExpr, 16, pResult); } else rc = dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult); } } } return rc; } /** * Evaluates one argument. * * @returns 0 on success. pResult contains the result. * @returns VBox error code on parse or other evaluation error. * * @param pDbgc Debugger console instance data. * @param pszExpr The expression string. * @param pResult Where to store the result of the expression evaluation. */ static int dbgcEvalSub(PDBGC pDbgc, char *pszExpr, size_t cchExpr, 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 && isblank(pszExpr[cchExpr - 1])) pszExpr[--cchExpr] = '\0'; while (isblank(*pszExpr)) pszExpr++, cchExpr--; if (!*pszExpr) return VERR_PARSE_EMPTY_ARGUMENT; /* it there is any kind of quoting in the expression, it's string meat. */ if (strpbrk(pszExpr, "\"'`")) return dbgcEvalSubString(pDbgc, pszExpr, cchExpr, pResult); /* * 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_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 && isblank(pszExpr[cchExpr - 1])) pszExpr[--cchExpr] = '\0'; while (isblank(*pszExpr)) pszExpr++, cchExpr--; if (!*pszExpr) return VERR_PARSE_EMPTY_ARGUMENT; } while (pszExpr[0] == '(' && pszExpr[cchExpr - 1] == ')'); } /* tabs to spaces. */ char *psz = pszExpr; while ((psz = strchr(psz, '\t')) != NULL) *psz = ' '; /* * 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; char ch; char chPrev = ' '; bool fBinary = false; psz = pszExpr; while ((ch = *psz) != '\0') { //Log2(("ch=%c cPar=%d fBinary=%d\n", ch, cPar, fBinary)); /* * Parenthesis. */ if (ch == '(') { cPar++; fBinary = false; } else if (ch == ')') { if (cPar <= 0) return VERR_PARSE_UNBALANCED_PARENTHESIS; cPar--; fBinary = true; } /* * Potential operator. */ else if (cPar == 0 && !isblank(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_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; } else fBinary = true; } /* next */ psz++; chPrev = ch; } /* parse loop. */ /* * 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, &Arg1); if (VBOX_SUCCESS(rc)) { /* process 2nd sub expression. */ char *psz2 = pszOpSplit + pOpSplit->cchName; DBGCVAR Arg2; rc = dbgcEvalSub(pDbgc, psz2, cchExpr - (psz2 - pszExpr), &Arg2); if (VBOX_SUCCESS(rc)) /* apply the operator. */ 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), &Arg); if (VBOX_SUCCESS(rc)) /* apply the operator. */ rc = pOpSplit->pfnHandlerUnary(pDbgc, &Arg, pResult); } else /* plain expression or using unary operators perhaps with paratheses. */ rc = dbgcEvalSubUnary(pDbgc, pszExpr, cchExpr, pResult); return rc; } /** * Parses the arguments of one command. * * @returns 0 on success. * @returns VBox error code on parse error with *pcArg containing the argument causing trouble. * @param pDbgc Debugger console instance data. * @param pCmd Pointer to the command descriptor. * @param pszArg Pointer to the arguments to parse. * @param paArgs Where to store the parsed arguments. * @param cArgs Size of the paArgs array. * @param pcArgs Where to store the number of arguments. * In the event of an error this is used to store the index of the offending argument. */ static int dbgcProcessArguments(PDBGC pDbgc, PCDBGCCMD pCmd, char *pszArgs, PDBGCVAR paArgs, unsigned cArgs, unsigned *pcArgs) { Log2(("dbgcProcessArguments: pCmd=%s pszArgs='%s'\n", pCmd->pszCmd, pszArgs)); /* * Check if we have any argument and if the command takes any. */ *pcArgs = 0; /* strip leading blanks. */ while (*pszArgs && isblank(*pszArgs)) pszArgs++; if (!*pszArgs) { if (!pCmd->cArgsMin) return 0; return VERR_PARSE_TOO_FEW_ARGUMENTS; } /** @todo fixme - foo() doesn't work. */ if (!pCmd->cArgsMax) return VERR_PARSE_TOO_MANY_ARGUMENTS; /* * This is a hack, it's "temporary" and should go away "when" the parser is * modified to match arguments while parsing. */ if ( pCmd->cArgsMax == 1 && pCmd->cArgsMin == 1 && pCmd->cArgDescs == 1 && pCmd->paArgDescs[0].enmCategory == DBGCVAR_CAT_STRING && cArgs >= 1) { *pcArgs = 1; RTStrStripR(pszArgs); return dbgcEvalSubString(pDbgc, pszArgs, strlen(pszArgs), &paArgs[0]); } /* * The parse loop. */ PDBGCVAR pArg0 = &paArgs[0]; PDBGCVAR pArg = pArg0; *pcArgs = 0; do { /* * Can we have another argument? */ if (*pcArgs >= pCmd->cArgsMax) return VERR_PARSE_TOO_MANY_ARGUMENTS; if (pArg >= &paArgs[cArgs]) return VERR_PARSE_ARGUMENT_OVERFLOW; /* * Find the end of the argument. */ 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_PARSE_UNBALANCED_QUOTE; if (cPar) return VERR_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 occurence. */ else if (ch == '\'' || ch == '"' || ch == '`') { if (chQuote) { /* end quote? */ if (ch == chQuote) { if (psz[1] == ch) psz++; /* skip the escaped quote char */ else chQuote = '\0'; /* end of quoted string. */ } } else chQuote = ch; /* open new quote */ } /* * Parenthesis can of course be nested. */ else if (ch == '(') { cPar++; fBinary = false; } else if (ch == ')') { if (!cPar) return VERR_PARSE_UNBALANCED_PARENTHESIS; cPar--; fBinary = true; } else if (!chQuote && !cPar) { /* * Encountering blanks may mean the end of it all. A binary operator * will force continued parsing. */ if (isblank(*psz)) { pszEnd = psz++; /* just in case. */ while (isblank(*psz)) psz++; PCDBGCOP pOp = dbgcOperatorLookup(pDbgc, psz, fBinary, ' '); if (!pOp || pOp->fBinary != fBinary) break; /* the end. */ psz += pOp->cchName; while (isblank(*psz)) /* skip blanks so we don't get here again */ psz++; fBinary = false; continue; } /* * Look for operators without a space up front. */ if (dbgcIsOpChar(*psz)) { 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 (isblank(*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) */ /* * Parse and evaluate the argument. */ int rc = dbgcEvalSub(pDbgc, pszArgs, strlen(pszArgs), pArg); if (VBOX_FAILURE(rc)) return rc; /* * Next. */ pArg++; (*pcArgs)++; pszArgs = psz; while (*pszArgs && isblank(*pszArgs)) pszArgs++; } while (*pszArgs); /* * Match the arguments. */ return dbgcEvalSubMatchVars(pDbgc, pCmd->cArgsMin, pCmd->cArgsMax, pCmd->paArgDescs, pCmd->cArgDescs, pArg0, pArg - pArg0); } /** * Process one command. * * @returns VBox status code. Any error indicates the termination of the console session. * @param pDbgc Debugger console instance data. * @param pszCmd Pointer to the command. * @param cchCmd Length of the command. */ static int dbgcProcessCommand(PDBGC pDbgc, char *pszCmd, size_t cchCmd) { char *pszCmdInput = pszCmd; /* * Skip blanks. */ while (isblank(*pszCmd)) pszCmd++, cchCmd--; /* external command? */ bool fExternal = *pszCmd == '.'; if (fExternal) pszCmd++, cchCmd--; /* * Find arguments. */ char *pszArgs = pszCmd; while (isalnum(*pszArgs)) pszArgs++; if (*pszArgs && (!isblank(*pszArgs) || pszArgs == pszCmd)) { pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error in command '%s'!\n", pszCmdInput); return 0; } /* * Find the command. */ PCDBGCCMD pCmd = dbgcRoutineLookup(pDbgc, pszCmd, pszArgs - pszCmd, fExternal); if (!pCmd || (pCmd->fFlags & DBGCCMD_FLAGS_FUNCTION)) return pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Unknown command '%s'!\n", pszCmdInput); /* * Parse arguments (if any). */ unsigned cArgs; int rc = dbgcProcessArguments(pDbgc, pCmd, pszArgs, &pDbgc->aArgs[pDbgc->iArg], ELEMENTS(pDbgc->aArgs) - pDbgc->iArg, &cArgs); /* * Execute the command. */ if (!rc) { rc = pCmd->pfnHandler(pCmd, &pDbgc->CmdHlp, pDbgc->pVM, &pDbgc->aArgs[0], cArgs, NULL); } else { /* report parse / eval error. */ switch (rc) { case VERR_PARSE_TOO_FEW_ARGUMENTS: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Too few arguments. Minimum is %d for command '%s'.\n", pCmd->cArgsMin, pCmd->pszCmd); break; case VERR_PARSE_TOO_MANY_ARGUMENTS: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Too many arguments. Maximum is %d for command '%s'.\n", pCmd->cArgsMax, pCmd->pszCmd); break; case VERR_PARSE_ARGUMENT_OVERFLOW: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Too many arguments.\n"); break; case VERR_PARSE_UNBALANCED_QUOTE: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Unbalanced quote (argument %d).\n", cArgs); break; case VERR_PARSE_UNBALANCED_PARENTHESIS: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Unbalanced parenthesis (argument %d).\n", cArgs); break; case VERR_PARSE_EMPTY_ARGUMENT: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: An argument or subargument contains nothing useful (argument %d).\n", cArgs); break; case VERR_PARSE_UNEXPECTED_OPERATOR: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Invalid operator usage (argument %d).\n", cArgs); break; case VERR_PARSE_INVALID_NUMBER: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Syntax error: Ivalid numeric value (argument %d). If a string was the intention, then quote it.\n", cArgs); break; case VERR_PARSE_NUMBER_TOO_BIG: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Numeric overflow (argument %d).\n", cArgs); break; case VERR_PARSE_INVALID_OPERATION: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Invalid operation attempted (argument %d).\n", cArgs); break; case VERR_PARSE_FUNCTION_NOT_FOUND: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Function not found (argument %d).\n", cArgs); break; case VERR_PARSE_NOT_A_FUNCTION: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: The function specified is not a function (argument %d).\n", cArgs); break; case VERR_PARSE_NO_MEMORY: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Out memory in the regular heap! Expect odd stuff to happen...\n", cArgs); break; case VERR_PARSE_INCORRECT_ARG_TYPE: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Incorrect argument type (argument %d?).\n", cArgs); break; case VERR_PARSE_VARIABLE_NOT_FOUND: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: An undefined variable was referenced (argument %d).\n", cArgs); break; case VERR_PARSE_CONVERSION_FAILED: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: A conversion between two types failed (argument %d).\n", cArgs); break; case VERR_PARSE_NOT_IMPLEMENTED: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: You hit a debugger feature which isn't implemented yet (argument %d).\n", cArgs); break; case VERR_PARSE_BAD_RESULT_TYPE: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Couldn't satisfy a request for a specific result type (argument %d). (Usually applies to symbols)\n", cArgs); break; case VERR_PARSE_WRITEONLY_SYMBOL: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Cannot get symbol, it's set only (argument %d).\n", cArgs); break; default: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Error: Unknown error %d!\n", rc); return rc; } /* * Parse errors are non fatal. */ if (rc >= VERR_PARSE_FIRST && rc < VERR_PARSE_LAST) rc = 0; } return rc; } /** * Process all commands current in the buffer. * * @returns VBox status code. Any error indicates the termination of the console session. * @param pDbgc Debugger console instance data. */ static int dbgcProcessCommands(PDBGC pDbgc) { int rc = 0; while (pDbgc->cInputLines) { /* * Empty the log buffer if we're hooking the log. */ if (pDbgc->fLog) { rc = dbgcProcessLog(pDbgc); if (VBOX_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 = psz; pDbgc->iArg = 0; rc = dbgcProcessCommand(pDbgc, &pDbgc->achScratch[0], psz - &pDbgc->achScratch[0] - 1); if (rc) break; } return rc; } /** * Reads input, parses it and executes commands on '\n'. * * @returns VBox status. * @param pDbgc Debugger console instance data. */ static int dbgcProcessInput(PDBGC pDbgc) { /* * We know there's input ready, so let's read it first. */ int rc = dbgcInputRead(pDbgc); if (VBOX_FAILURE(rc)) return rc; /* * Now execute any ready commands. */ if (pDbgc->cInputLines) { /** @todo this fReady stuff is broken. */ pDbgc->fReady = false; rc = dbgcProcessCommands(pDbgc); if (VBOX_SUCCESS(rc) && rc != VWRN_DBGC_CMD_PENDING) pDbgc->fReady = true; if ( VBOX_SUCCESS(rc) && pDbgc->iRead == pDbgc->iWrite && pDbgc->fReady) rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "VBoxDbg> "); } 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_HWACCL: 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!"; } } /** * Processes debugger events. * * @returns VBox status. * @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 (VBOX_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 (VBOX_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 (VBOX_SUCCESS(rc)) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); break; } case DBGFEVENT_BREAKPOINT: case DBGFEVENT_BREAKPOINT_HYPER: { bool fRegCtxGuest = pDbgc->fRegCtxGuest; pDbgc->fRegCtxGuest = pEvent->enmType == DBGFEVENT_BREAKPOINT; 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 (VBOX_SUCCESS(rc) && DBGFR3IsHalted(pDbgc->pVM)) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); 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 (VBOX_SUCCESS(rc)) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); 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 (VBOX_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 (VBOX_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 (VBOX_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"); fPrintPrompt = !pDbgc->fReady; break; } case DBGFEVENT_TERMINATING: { pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\nVM is terminating!\n"); rc = VERR_GENERAL_FAILURE; break; } default: { rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Unknown event %d!\n", pEvent->enmType); fPrintPrompt = !pDbgc->fReady; break; } } /* * Prompt, anyone? */ if (fPrintPrompt && VBOX_SUCCESS(rc)) { rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "VBoxDbg> "); } return rc; } /** * 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 pVM 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. * @remark A forced termination of the console is easiest done by forcing the * callbacks to return fatal failures. */ DBGDECL(int) DBGCCreate(PVM pVM, PDBGCBACK pBack, unsigned fFlags) { /* * Validate input. */ AssertReturn(VALID_PTR(pVM), VERR_INVALID_PARAMETER); AssertReturn(VALID_PTR(pBack), VERR_INVALID_PARAMETER); AssertMsgReturn(!fFlags, ("%#x", fFlags), VERR_INVALID_PARAMETER); /* * Allocate and initialize instance data */ PDBGC pDbgc = (PDBGC)RTMemAllocZ(sizeof(*pDbgc)); if (!pDbgc) return VERR_NO_MEMORY; pDbgc->CmdHlp.pfnWrite = dbgcHlpWrite; pDbgc->CmdHlp.pfnPrintfV = dbgcHlpPrintfV; pDbgc->CmdHlp.pfnPrintf = dbgcHlpPrintf; pDbgc->CmdHlp.pfnVBoxErrorV = dbgcHlpVBoxErrorV; pDbgc->CmdHlp.pfnVBoxError = dbgcHlpVBoxError; pDbgc->CmdHlp.pfnMemRead = dbgcHlpMemRead; pDbgc->CmdHlp.pfnMemWrite = dbgcHlpMemWrite; pDbgc->CmdHlp.pfnEval = dbgcHlpEval; pDbgc->CmdHlp.pfnExec = dbgcHlpExec; pDbgc->CmdHlp.pfnVarToDbgfAddr = dbgcHlpVarToDbgfAddr; pDbgc->CmdHlp.pfnVarToBool = dbgcHlpVarToBool; pDbgc->pBack = pBack; pDbgc->pVM = NULL; pDbgc->pszEmulation = "CodeView/WinDbg"; pDbgc->paEmulationCmds = &g_aCmdsCodeView[0]; pDbgc->cEmulationCmds = g_cCmdsCodeView; //pDbgc->fLog = false; pDbgc->fRegCtxGuest = true; pDbgc->fRegTerse = true; //pDbgc->DisasmPos = {0}; //pDbgc->SourcePos = {0}; //pDbgc->DumpPos = {0}; //pDbgc->cbDumpElement = 0; //pDbgc->cVars = 0; //pDbgc->paVars = NULL; //pDbgc->pFirstBp = NULL; //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; dbgcInitOpCharBitMap(); /* * Print welcome message. */ int rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Welcome to the VirtualBox Debugger!\n"); if (VBOX_FAILURE(rc)) goto l_failure; /* * Attach to the VM. */ rc = DBGFR3Attach(pVM); if (VBOX_FAILURE(rc)) { rc = pDbgc->CmdHlp.pfnVBoxError(&pDbgc->CmdHlp, rc, "When trying to attach to VM %p\n", pDbgc->pVM); goto l_failure; } pDbgc->pVM = pVM; /* * Print commandline and auto select result. */ rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Current VM is %08x\n" /** @todo get and print the VM name! */ "VBoxDbg> ", pDbgc->pVM); if (VBOX_FAILURE(rc)) goto l_failure; /* * 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. */ for (rc = 0;;) { if (pDbgc->pVM && DBGFR3CanWait(pDbgc->pVM)) { /* * Wait for a debug event. */ PCDBGFEVENT pEvent; rc = DBGFR3EventWait(pDbgc->pVM, pDbgc->fLog ? 1 : 32, &pEvent); if (VBOX_SUCCESS(rc)) { rc = dbgcProcessEvent(pDbgc, pEvent); if (VBOX_FAILURE(rc)) break; } else if (rc != VERR_TIMEOUT) break; /* * Check for input. */ if (pBack->pfnInput(pDbgc->pBack, 0)) { rc = dbgcProcessInput(pDbgc); if (VBOX_FAILURE(rc)) break; } } else { /* * Wait for input. If Logging is enabled we'll only wait very briefly. */ if (pBack->pfnInput(pDbgc->pBack, pDbgc->fLog ? 1 : 1000)) { rc = dbgcProcessInput(pDbgc); if (VBOX_FAILURE(rc)) break; } } /* * Forward log output. */ if (pDbgc->fLog) { rc = dbgcProcessLog(pDbgc); if (VBOX_FAILURE(rc)) break; } } l_failure: /* * Cleanup console debugger session. */ /* Disable log hook. */ if (pDbgc->fLog) { } /* Detach from the VM. */ if (pDbgc->pVM) DBGFR3Detach(pDbgc->pVM); /* finally, free the instance memory. */ RTMemFree(pDbgc); return rc; }