/* $Id: DBGCEval.cpp 35696 2011-01-24 18:03:33Z vboxsync $ */ /** @file * DBGC - Debugger Console, command evaluator. */ /* * Copyright (C) 2006-2011 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 #include #include #include #include #include #include #include "DBGCInternal.h" /******************************************************************************* * Global Variables * *******************************************************************************/ /** Bitmap where set bits indicates the characters the may start an operator name. */ static uint32_t g_bmOperatorChars[256 / (4*8)]; /** * Initializes g_bmOperatorChars. */ void dbgcEvalInit(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 VINF_SUCCESS; } 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 VINF_SUCCESS; } /** * 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: case DBGCVAR_CAT_POINTER_NUMBER_NO_RANGE: if (pVar->enmRangeType != DBGCVAR_RANGE_NONE) return VERR_PARSE_NO_RANGE_ALLOWED; /* fallthru */ case DBGCVAR_CAT_POINTER: case DBGCVAR_CAT_POINTER_NUMBER: 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_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 (RT_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: if ( pVarDesc->enmCategory != DBGCVAR_CAT_POINTER_NUMBER && pVarDesc->enmCategory != DBGCVAR_CAT_POINTER_NUMBER_NO_RANGE) { 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_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 (RT_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 (RT_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 VBox status code. Modified @a paVars on success. */ 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 (RT_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 VINF_SUCCESS; } /** * 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_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) rc = VERR_PARSE_EMPTY_ARGUMENT; else { 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 = 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] == ')' && RT_C_IS_ALPHA(*pszFun)) { pszFunEnd = pszExpr + 1; while (*pszFunEnd != '(' && RT_C_IS_ALNUM(*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 0 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, enmCategory, &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 rc = VERR_NOT_IMPLEMENTED; #endif } else if ( enmCategory == DBGCVAR_CAT_STRING || enmCategory == DBGCVAR_CAT_SYMBOL) rc = dbgcEvalSubString(pDbgc, pszExpr, cchExpr, 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 (RT_C_IS_XDIGIT(*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 VBox status code. * * @param pDbgc Debugger console instance data. * @param pszExpr 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_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 && RT_C_IS_BLANK(pszExpr[cchExpr - 1])) pszExpr[--cchExpr] = '\0'; while (RT_C_IS_BLANK(*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 && !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_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, 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)) /* 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), pOpSplit->enmCatArg1, &Arg); if (RT_SUCCESS(rc)) /* apply the operator. */ rc = pOpSplit->pfnHandlerUnary(pDbgc, &Arg, pResult); } else /* plain expression or using unary operators perhaps with parentheses. */ rc = dbgcEvalSubUnary(pDbgc, pszExpr, cchExpr, enmCategory, pResult); return rc; } /** * 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 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 (ab)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 && RT_C_IS_BLANK(*pszArgs)) pszArgs++; if (!*pszArgs) { if (!pCmd->cArgsMin) return VINF_SUCCESS; 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 || pCmd->paArgDescs[0].enmCategory == DBGCVAR_CAT_SYMBOL) && 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 occurrence. */ 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 (RT_C_IS_BLANK(*psz)) { pszEnd = psz++; /* just in case. */ while (RT_C_IS_BLANK(*psz)) psz++; 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(*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 (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) */ /* * Parse and evaluate the argument. */ int rc = dbgcEvalSub(pDbgc, pszArgs, strlen(pszArgs), DBGCVAR_CAT_ANY, pArg); if (RT_FAILURE(rc)) return rc; /* * Next. */ pArg++; (*pcArgs)++; pszArgs = psz; while (*pszArgs && RT_C_IS_BLANK(*pszArgs)) pszArgs++; } while (*pszArgs); /* * Match the arguments. */ return dbgcEvalSubMatchVars(pDbgc, pCmd->cArgsMin, pCmd->cArgsMax, pCmd->paArgDescs, pCmd->cArgDescs, pArg0, pArg - pArg0); } /** * 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. * * @todo Change pszCmd into argc/argv? */ 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++; if (*pszArgs && (!RT_C_IS_BLANK(*pszArgs) || pszArgs == pszCmd)) { DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Invalid command '%s'!\n", pszCmdInput); return pDbgc->rcCmd = VINF_PARSE_INVALD_COMMAND_NAME; } /* * Find the command. */ PCDBGCCMD pCmd = dbgcRoutineLookup(pDbgc, pszCmd, pszArgs - pszCmd, fExternal); if (!pCmd) { DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Unknown command '%s'!\n", pszCmdInput); return pDbgc->rcCmd = VINF_PARSE_COMMAND_NOT_FOUND; } /* * Parse arguments (if any). */ unsigned cArgs; int rc = dbgcProcessArguments(pDbgc, pCmd, pszArgs, &pDbgc->aArgs[pDbgc->iArg], RT_ELEMENTS(pDbgc->aArgs) - pDbgc->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->pVM, &pDbgc->aArgs[0], cArgs); pDbgc->rcCmd = rc; if (rc == VERR_DBGC_COMMAND_FAILED) rc = VINF_SUCCESS; } else { pDbgc->rcCmd = rc; /* report parse / eval error. */ switch (rc) { case VERR_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_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_PARSE_ARGUMENT_OVERFLOW: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Too many arguments.\n"); break; case VERR_PARSE_UNBALANCED_QUOTE: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Unbalanced quote (argument %d).\n", cArgs); break; case VERR_PARSE_UNBALANCED_PARENTHESIS: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Unbalanced parenthesis (argument %d).\n", cArgs); break; case VERR_PARSE_EMPTY_ARGUMENT: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: An argument or subargument contains nothing useful (argument %d).\n", cArgs); break; case VERR_PARSE_UNEXPECTED_OPERATOR: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Syntax error: Invalid operator usage (argument %d).\n", cArgs); break; case VERR_PARSE_INVALID_NUMBER: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "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 = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Numeric overflow (argument %d).\n", cArgs); break; case VERR_PARSE_INVALID_OPERATION: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Invalid operation attempted (argument %d).\n", cArgs); break; case VERR_PARSE_FUNCTION_NOT_FOUND: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Function not found (argument %d).\n", cArgs); break; case VERR_PARSE_NOT_A_FUNCTION: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: The function specified is not a function (argument %d).\n", cArgs); break; case VERR_PARSE_NO_MEMORY: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Out memory in the regular heap! Expect odd stuff to happen...\n", cArgs); break; case VERR_PARSE_INCORRECT_ARG_TYPE: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Incorrect argument type (argument %d?).\n", cArgs); break; case VERR_PARSE_VARIABLE_NOT_FOUND: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: An undefined variable was referenced (argument %d).\n", cArgs); break; case VERR_PARSE_CONVERSION_FAILED: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: A conversion between two types failed (argument %d).\n", cArgs); break; case VERR_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_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_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: rc = DBGCCmdHlpPrintf(&pDbgc->CmdHlp, "Error: Unknown error %d!\n", rc); break; } } return rc; }