/* $Id: DBGFReg.cpp 44691 2013-02-14 15:33:24Z vboxsync $ */ /** @file * DBGF - Debugger Facility, Register Methods. */ /* * Copyright (C) 2010-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /******************************************************************************* * Header Files * *******************************************************************************/ #define LOG_GROUP LOG_GROUP_DBGF #include #include "DBGFInternal.h" #include #include #include #include #include #include #include #include #include /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** Locks the register database for writing. */ #define DBGF_REG_DB_LOCK_WRITE(pUVM) \ do { \ int rcSem = RTSemRWRequestWrite((pUVM)->dbgf.s.hRegDbLock, RT_INDEFINITE_WAIT); \ AssertRC(rcSem); \ } while (0) /** Unlocks the register database after writing. */ #define DBGF_REG_DB_UNLOCK_WRITE(pUVM) \ do { \ int rcSem = RTSemRWReleaseWrite((pUVM)->dbgf.s.hRegDbLock); \ AssertRC(rcSem); \ } while (0) /** Locks the register database for reading. */ #define DBGF_REG_DB_LOCK_READ(pUVM) \ do { \ int rcSem = RTSemRWRequestRead((pUVM)->dbgf.s.hRegDbLock, RT_INDEFINITE_WAIT); \ AssertRC(rcSem); \ } while (0) /** Unlocks the register database after reading. */ #define DBGF_REG_DB_UNLOCK_READ(pUVM) \ do { \ int rcSem = RTSemRWReleaseRead((pUVM)->dbgf.s.hRegDbLock); \ AssertRC(rcSem); \ } while (0) /** The max length of a set, register or sub-field name. */ #define DBGF_REG_MAX_NAME 40 /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** * Register set registration record type. */ typedef enum DBGFREGSETTYPE { /** Invalid zero value. */ DBGFREGSETTYPE_INVALID = 0, /** CPU record. */ DBGFREGSETTYPE_CPU, /** Device record. */ DBGFREGSETTYPE_DEVICE, /** End of valid record types. */ DBGFREGSETTYPE_END } DBGFREGSETTYPE; /** * Register set registration record. */ typedef struct DBGFREGSET { /** String space core. */ RTSTRSPACECORE Core; /** The registration record type. */ DBGFREGSETTYPE enmType; /** The user argument for the callbacks. */ union { /** The CPU view. */ PVMCPU pVCpu; /** The device view. */ PPDMDEVINS pDevIns; /** The general view. */ void *pv; } uUserArg; /** The register descriptors. */ PCDBGFREGDESC paDescs; /** The number of register descriptors. */ uint32_t cDescs; /** Array of lookup records. * The first part of the array runs parallel to paDescs, the rest are * covering for aliases and bitfield variations. It's done this way to * simplify the query all operations. */ struct DBGFREGLOOKUP *paLookupRecs; /** The number of lookup records. */ uint32_t cLookupRecs; /** The register name prefix. */ char szPrefix[1]; } DBGFREGSET; /** Pointer to a register registration record. */ typedef DBGFREGSET *PDBGFREGSET; /** Pointer to a const register registration record. */ typedef DBGFREGSET const *PCDBGFREGSET; /** * Register lookup record. */ typedef struct DBGFREGLOOKUP { /** The string space core. */ RTSTRSPACECORE Core; /** Pointer to the set. */ PCDBGFREGSET pSet; /** Pointer to the register descriptor. */ PCDBGFREGDESC pDesc; /** If an alias this points to the alias descriptor, NULL if not. */ PCDBGFREGALIAS pAlias; /** If a sub-field this points to the sub-field descriptor, NULL if not. */ PCDBGFREGSUBFIELD pSubField; } DBGFREGLOOKUP; /** Pointer to a register lookup record. */ typedef DBGFREGLOOKUP *PDBGFREGLOOKUP; /** Pointer to a const register lookup record. */ typedef DBGFREGLOOKUP const *PCDBGFREGLOOKUP; /** * Argument packet from DBGFR3RegNmQueryAll to dbgfR3RegNmQueryAllWorker. */ typedef struct DBGFR3REGNMQUERYALLARGS { /** The output register array. */ PDBGFREGENTRYNM paRegs; /** The number of entries in the output array. */ size_t cRegs; /** The current register number when enumerating the string space. * @remarks Only used by EMT(0). */ size_t iReg; } DBGFR3REGNMQUERYALLARGS; /** Pointer to a dbgfR3RegNmQueryAllWorker argument packet. */ typedef DBGFR3REGNMQUERYALLARGS *PDBGFR3REGNMQUERYALLARGS; /** * Argument packet passed by DBGFR3RegPrintfV to dbgfR3RegPrintfCbOutput and * dbgfR3RegPrintfCbFormat. */ typedef struct DBGFR3REGPRINTFARGS { /** The user mode VM handle. */ PUVM pUVM; /** The target CPU. */ VMCPUID idCpu; /** Set if we're looking at guest registers. */ bool fGuestRegs; /** The output buffer. */ char *pszBuf; /** The format string. */ const char *pszFormat; /** The va list with format arguments. */ va_list va; /** The current buffer offset. */ size_t offBuf; /** The amount of buffer space left, not counting the terminator char. */ size_t cchLeftBuf; /** The status code of the whole operation. First error is return, * subsequent ones are suppressed. */ int rc; } DBGFR3REGPRINTFARGS; /** Pointer to a DBGFR3RegPrintfV argument packet. */ typedef DBGFR3REGPRINTFARGS *PDBGFR3REGPRINTFARGS; /** * Initializes the register database. * * @returns VBox status code. * @param pUVM The user mode VM handle. */ int dbgfR3RegInit(PUVM pUVM) { int rc = VINF_SUCCESS; if (!pUVM->dbgf.s.fRegDbInitialized) { rc = RTSemRWCreate(&pUVM->dbgf.s.hRegDbLock); pUVM->dbgf.s.fRegDbInitialized = RT_SUCCESS(rc); } return rc; } /** * Terminates the register database. * * @param pUVM The user mode VM handle. */ void dbgfR3RegTerm(PUVM pUVM) { RTSemRWDestroy(pUVM->dbgf.s.hRegDbLock); pUVM->dbgf.s.hRegDbLock = NIL_RTSEMRW; pUVM->dbgf.s.fRegDbInitialized = false; } /** * Validates a register name. * * This is used for prefixes, aliases and field names. * * @returns true if valid, false if not. * @param pszName The register name to validate. * @param chDot Set to '.' if accepted, otherwise 0. */ static bool dbgfR3RegIsNameValid(const char *pszName, char chDot) { const char *psz = pszName; if (!RT_C_IS_ALPHA(*psz)) return false; char ch; while ((ch = *++psz)) if ( !RT_C_IS_LOWER(ch) && !RT_C_IS_DIGIT(ch) && ch != '_' && ch != chDot) return false; if (psz - pszName > DBGF_REG_MAX_NAME) return false; return true; } /** * Common worker for registering a register set. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param paRegisters The register descriptors. * @param enmType The set type. * @param pvUserArg The user argument for the callbacks. * @param pszPrefix The name prefix. * @param iInstance The instance number to be appended to @a * pszPrefix when creating the set name. */ static int dbgfR3RegRegisterCommon(PUVM pUVM, PCDBGFREGDESC paRegisters, DBGFREGSETTYPE enmType, void *pvUserArg, const char *pszPrefix, uint32_t iInstance) { /* * Validate input. */ /* The name components. */ AssertMsgReturn(dbgfR3RegIsNameValid(pszPrefix, 0), ("%s\n", pszPrefix), VERR_INVALID_NAME); const char *psz = RTStrEnd(pszPrefix, RTSTR_MAX); bool const fNeedUnderscore = RT_C_IS_DIGIT(psz[-1]); size_t const cchPrefix = psz - pszPrefix + fNeedUnderscore; AssertMsgReturn(cchPrefix < RT_SIZEOFMEMB(DBGFREGSET, szPrefix) - 4 - 1, ("%s\n", pszPrefix), VERR_INVALID_NAME); AssertMsgReturn(iInstance <= 9999, ("%d\n", iInstance), VERR_INVALID_NAME); /* The descriptors. */ uint32_t cLookupRecs = 0; uint32_t iDesc; for (iDesc = 0; paRegisters[iDesc].pszName != NULL; iDesc++) { AssertMsgReturn(dbgfR3RegIsNameValid(paRegisters[iDesc].pszName, 0), ("%s (#%u)\n", paRegisters[iDesc].pszName, iDesc), VERR_INVALID_NAME); if (enmType == DBGFREGSETTYPE_CPU) AssertMsgReturn((unsigned)paRegisters[iDesc].enmReg == iDesc && iDesc < (unsigned)DBGFREG_END, ("%d iDesc=%d\n", paRegisters[iDesc].enmReg, iDesc), VERR_INVALID_PARAMETER); else AssertReturn(paRegisters[iDesc].enmReg == DBGFREG_END, VERR_INVALID_PARAMETER); AssertReturn( paRegisters[iDesc].enmType > DBGFREGVALTYPE_INVALID && paRegisters[iDesc].enmType < DBGFREGVALTYPE_END, VERR_INVALID_PARAMETER); AssertMsgReturn(!(paRegisters[iDesc].fFlags & ~DBGFREG_FLAGS_READ_ONLY), ("%#x (#%u)\n", paRegisters[iDesc].fFlags, iDesc), VERR_INVALID_PARAMETER); AssertPtrReturn(paRegisters[iDesc].pfnGet, VERR_INVALID_PARAMETER); AssertReturn(RT_VALID_PTR(paRegisters[iDesc].pfnSet) || (paRegisters[iDesc].fFlags & DBGFREG_FLAGS_READ_ONLY), VERR_INVALID_PARAMETER); uint32_t iAlias = 0; PCDBGFREGALIAS paAliases = paRegisters[iDesc].paAliases; if (paAliases) { AssertPtrReturn(paAliases, VERR_INVALID_PARAMETER); for (; paAliases[iAlias].pszName; iAlias++) { AssertMsgReturn(dbgfR3RegIsNameValid(paAliases[iAlias].pszName, 0), ("%s (%s)\n", paAliases[iAlias].pszName, paRegisters[iDesc].pszName), VERR_INVALID_NAME); AssertReturn( paAliases[iAlias].enmType > DBGFREGVALTYPE_INVALID && paAliases[iAlias].enmType < DBGFREGVALTYPE_END, VERR_INVALID_PARAMETER); } } uint32_t iSubField = 0; PCDBGFREGSUBFIELD paSubFields = paRegisters[iDesc].paSubFields; if (paSubFields) { AssertPtrReturn(paSubFields, VERR_INVALID_PARAMETER); for (; paSubFields[iSubField].pszName; iSubField++) { AssertMsgReturn(dbgfR3RegIsNameValid(paSubFields[iSubField].pszName, '.'), ("%s (%s)\n", paSubFields[iSubField].pszName, paRegisters[iDesc].pszName), VERR_INVALID_NAME); AssertReturn(paSubFields[iSubField].iFirstBit + paSubFields[iSubField].cBits <= 128, VERR_INVALID_PARAMETER); AssertReturn(paSubFields[iSubField].cBits + paSubFields[iSubField].cShift <= 128, VERR_INVALID_PARAMETER); AssertPtrNullReturn(paSubFields[iSubField].pfnGet, VERR_INVALID_POINTER); AssertPtrNullReturn(paSubFields[iSubField].pfnSet, VERR_INVALID_POINTER); } } cLookupRecs += (1 + iAlias) * (1 + iSubField); } /* Check the instance number of the CPUs. */ AssertReturn(enmType != DBGFREGSETTYPE_CPU || iInstance < pUVM->cCpus, VERR_INVALID_CPU_ID); /* * Allocate a new record and all associated lookup records. */ size_t cbRegSet = RT_OFFSETOF(DBGFREGSET, szPrefix[cchPrefix + 4 + 1]); cbRegSet = RT_ALIGN_Z(cbRegSet, 32); size_t const offLookupRecArray = cbRegSet; cbRegSet += cLookupRecs * sizeof(DBGFREGLOOKUP); PDBGFREGSET pRegSet = (PDBGFREGSET)MMR3HeapAllocZU(pUVM, MM_TAG_DBGF_REG, cbRegSet); if (!pRegSet) return VERR_NO_MEMORY; /* * Initialize the new record. */ pRegSet->Core.pszString = pRegSet->szPrefix; pRegSet->enmType = enmType; pRegSet->uUserArg.pv = pvUserArg; pRegSet->paDescs = paRegisters; pRegSet->cDescs = iDesc; pRegSet->cLookupRecs = cLookupRecs; pRegSet->paLookupRecs = (PDBGFREGLOOKUP)((uintptr_t)pRegSet + offLookupRecArray); if (fNeedUnderscore) RTStrPrintf(pRegSet->szPrefix, cchPrefix + 4 + 1, "%s_%u", pszPrefix, iInstance); else RTStrPrintf(pRegSet->szPrefix, cchPrefix + 4 + 1, "%s%u", pszPrefix, iInstance); /* * Initialize the lookup records. See DBGFREGSET::paLookupRecs. */ char szName[DBGF_REG_MAX_NAME * 3 + 16]; strcpy(szName, pRegSet->szPrefix); char *pszReg = strchr(szName, '\0'); *pszReg++ = '.'; /* Array parallel to the descriptors. */ int rc = VINF_SUCCESS; PDBGFREGLOOKUP pLookupRec = &pRegSet->paLookupRecs[0]; for (iDesc = 0; paRegisters[iDesc].pszName != NULL && RT_SUCCESS(rc); iDesc++) { strcpy(pszReg, paRegisters[iDesc].pszName); pLookupRec->Core.pszString = MMR3HeapStrDupU(pUVM, MM_TAG_DBGF_REG, szName); if (!pLookupRec->Core.pszString) rc = VERR_NO_STR_MEMORY; pLookupRec->pSet = pRegSet; pLookupRec->pDesc = &paRegisters[iDesc]; pLookupRec->pAlias = NULL; pLookupRec->pSubField = NULL; pLookupRec++; } /* Aliases and sub-fields. */ for (iDesc = 0; paRegisters[iDesc].pszName != NULL && RT_SUCCESS(rc); iDesc++) { PCDBGFREGALIAS pCurAlias = NULL; /* first time we add sub-fields for the real name. */ PCDBGFREGALIAS pNextAlias = paRegisters[iDesc].paAliases; const char *pszRegName = paRegisters[iDesc].pszName; while (RT_SUCCESS(rc)) { /* Add sub-field records. */ PCDBGFREGSUBFIELD paSubFields = paRegisters[iDesc].paSubFields; if (paSubFields) { size_t cchReg = strlen(pszRegName); memcpy(pszReg, pszRegName, cchReg); char *pszSub = &pszReg[cchReg]; *pszSub++ = '.'; for (uint32_t iSubField = 0; paSubFields[iSubField].pszName && RT_SUCCESS(rc); iSubField++) { strcpy(pszSub, paSubFields[iSubField].pszName); pLookupRec->Core.pszString = MMR3HeapStrDupU(pUVM, MM_TAG_DBGF_REG, szName); if (!pLookupRec->Core.pszString) rc = VERR_NO_STR_MEMORY; pLookupRec->pSet = pRegSet; pLookupRec->pDesc = &paRegisters[iDesc]; pLookupRec->pAlias = pCurAlias; pLookupRec->pSubField = &paSubFields[iSubField]; pLookupRec++; } } /* Advance to the next alias. */ pCurAlias = pNextAlias++; if (!pCurAlias) break; pszRegName = pCurAlias->pszName; if (!pszRegName) break; /* The alias record. */ strcpy(pszReg, pszRegName); pLookupRec->Core.pszString = MMR3HeapStrDupU(pUVM, MM_TAG_DBGF_REG, szName); if (!pLookupRec->Core.pszString) rc = VERR_NO_STR_MEMORY; pLookupRec->pSet = pRegSet; pLookupRec->pDesc = &paRegisters[iDesc]; pLookupRec->pAlias = pCurAlias; pLookupRec->pSubField = NULL; pLookupRec++; } } Assert(pLookupRec == &pRegSet->paLookupRecs[pRegSet->cLookupRecs]); if (RT_SUCCESS(rc)) { /* * Insert the record into the register set string space and optionally into * the CPU register set cache. */ DBGF_REG_DB_LOCK_WRITE(pUVM); bool fInserted = RTStrSpaceInsert(&pUVM->dbgf.s.RegSetSpace, &pRegSet->Core); if (fInserted) { pUVM->dbgf.s.cRegs += pRegSet->cDescs; if (enmType == DBGFREGSETTYPE_CPU) { if (pRegSet->cDescs > DBGFREG_ALL_COUNT) pUVM->dbgf.s.cRegs -= pRegSet->cDescs - DBGFREG_ALL_COUNT; if (!strcmp(pszPrefix, "cpu")) pUVM->aCpus[iInstance].dbgf.s.pGuestRegSet = pRegSet; else pUVM->aCpus[iInstance].dbgf.s.pHyperRegSet = pRegSet; } PDBGFREGLOOKUP paLookupRecs = pRegSet->paLookupRecs; uint32_t iLookupRec = pRegSet->cLookupRecs; while (iLookupRec-- > 0) { bool fInserted2 = RTStrSpaceInsert(&pUVM->dbgf.s.RegSpace, &paLookupRecs[iLookupRec].Core); AssertMsg(fInserted2, ("'%s'", paLookupRecs[iLookupRec].Core.pszString)); NOREF(fInserted2); } DBGF_REG_DB_UNLOCK_WRITE(pUVM); return VINF_SUCCESS; } DBGF_REG_DB_UNLOCK_WRITE(pUVM); rc = VERR_DUPLICATE; } /* * Bail out. */ for (uint32_t i = 0; i < pRegSet->cLookupRecs; i++) MMR3HeapFree((char *)pRegSet->paLookupRecs[i].Core.pszString); MMR3HeapFree(pRegSet); return rc; } /** * Registers a set of registers for a CPU. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pVCpu Pointer to the VMCPU. * @param paRegisters The register descriptors. * @param fGuestRegs Set if it's the guest registers, clear if * hypervisor registers. */ VMMR3_INT_DECL(int) DBGFR3RegRegisterCpu(PVM pVM, PVMCPU pVCpu, PCDBGFREGDESC paRegisters, bool fGuestRegs) { PUVM pUVM = pVM->pUVM; if (!pUVM->dbgf.s.fRegDbInitialized) { int rc = dbgfR3RegInit(pUVM); if (RT_FAILURE(rc)) return rc; } return dbgfR3RegRegisterCommon(pUVM, paRegisters, DBGFREGSETTYPE_CPU, pVCpu, fGuestRegs ? "cpu" : "hypercpu", pVCpu->idCpu); } /** * Registers a set of registers for a device. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param paRegisters The register descriptors. * @param pDevIns The device instance. This will be the callback user * argument. * @param pszPrefix The device name. * @param iInstance The device instance. */ VMMR3_INT_DECL(int) DBGFR3RegRegisterDevice(PVM pVM, PCDBGFREGDESC paRegisters, PPDMDEVINS pDevIns, const char *pszPrefix, uint32_t iInstance) { AssertPtrReturn(paRegisters, VERR_INVALID_POINTER); AssertPtrReturn(pDevIns, VERR_INVALID_POINTER); AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER); return dbgfR3RegRegisterCommon(pVM->pUVM, paRegisters, DBGFREGSETTYPE_DEVICE, pDevIns, pszPrefix, iInstance); } /** * Clears the register value variable. * * @param pValue The variable to clear. */ DECLINLINE(void) dbgfR3RegValClear(PDBGFREGVAL pValue) { pValue->au64[0] = 0; pValue->au64[1] = 0; } /** * Sets a 80-bit floating point variable to a 64-bit unsigned interger value. * * @param pValue The value. * @param u64 The integer value. */ DECLINLINE(void) dbgfR3RegValR80SetU64(PDBGFREGVAL pValue, uint64_t u64) { /** @todo fixme */ pValue->r80.s.fSign = 0; pValue->r80.s.uExponent = 16383; pValue->r80.s.u64Mantissa = u64; } /** * Sets a 80-bit floating point variable to a 64-bit unsigned interger value. * * @param pValue The value. * @param u128 The integer value. */ DECLINLINE(void) dbgfR3RegValR80SetU128(PDBGFREGVAL pValue, RTUINT128U u128) { /** @todo fixme */ pValue->r80.s.fSign = 0; pValue->r80.s.uExponent = 16383; pValue->r80.s.u64Mantissa = u128.s.Lo; } /** * Get a 80-bit floating point variable as a 64-bit unsigned integer. * * @returns 64-bit unsigned integer. * @param pValue The value. */ DECLINLINE(uint64_t) dbgfR3RegValR80GetU64(PCDBGFREGVAL pValue) { /** @todo stupid, stupid MSC. */ return pValue->r80.s.u64Mantissa; } /** * Get a 80-bit floating point variable as a 128-bit unsigned integer. * * @returns 128-bit unsigned integer. * @param pValue The value. */ DECLINLINE(RTUINT128U) dbgfR3RegValR80GetU128(PCDBGFREGVAL pValue) { /** @todo stupid, stupid MSC. */ RTUINT128U uRet; #if 0 uRet.s.Lo = (uint64_t)InVal.lrd; uRet.s.Hi = (uint64_t)InVal.lrd / _4G / _4G; #else uRet.s.Lo = pValue->r80.s.u64Mantissa; uRet.s.Hi = 0; #endif return uRet; } /** * Performs a cast between register value types. * * @retval VINF_SUCCESS * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VERR_DBGF_UNSUPPORTED_CAST * * @param pValue The value to cast (input + output). * @param enmFromType The input value. * @param enmToType The desired output value. */ static int dbgfR3RegValCast(PDBGFREGVAL pValue, DBGFREGVALTYPE enmFromType, DBGFREGVALTYPE enmToType) { DBGFREGVAL const InVal = *pValue; dbgfR3RegValClear(pValue); /* Note! No default cases here as gcc warnings about missing enum values are desired. */ switch (enmFromType) { case DBGFREGVALTYPE_U8: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = InVal.u8; return VINF_SUCCESS; case DBGFREGVALTYPE_U16: pValue->u16 = InVal.u8; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_U32: pValue->u32 = InVal.u8; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_U64: pValue->u64 = InVal.u8; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_U128: pValue->u128.s.Lo = InVal.u8; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_R80: dbgfR3RegValR80SetU64(pValue, InVal.u8); return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_DTR: return VERR_DBGF_UNSUPPORTED_CAST; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_U16: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = InVal.u16; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U16: pValue->u16 = InVal.u16; return VINF_SUCCESS; case DBGFREGVALTYPE_U32: pValue->u32 = InVal.u16; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_U64: pValue->u64 = InVal.u16; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_U128: pValue->u128.s.Lo = InVal.u16; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_R80: dbgfR3RegValR80SetU64(pValue, InVal.u16); return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_DTR: return VERR_DBGF_UNSUPPORTED_CAST; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_U32: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = InVal.u32; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U16: pValue->u16 = InVal.u32; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U32: pValue->u32 = InVal.u32; return VINF_SUCCESS; case DBGFREGVALTYPE_U64: pValue->u64 = InVal.u32; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_U128: pValue->u128.s.Lo = InVal.u32; return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_R80: dbgfR3RegValR80SetU64(pValue, InVal.u32); return VINF_DBGF_ZERO_EXTENDED_REGISTER; case DBGFREGVALTYPE_DTR: return VERR_DBGF_UNSUPPORTED_CAST; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_U64: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = InVal.u64; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U16: pValue->u16 = InVal.u64; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U32: pValue->u32 = InVal.u64; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U64: pValue->u64 = InVal.u64; return VINF_SUCCESS; case DBGFREGVALTYPE_U128: pValue->u128.s.Lo = InVal.u64; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_R80: dbgfR3RegValR80SetU64(pValue, InVal.u64); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_DTR: return VERR_DBGF_UNSUPPORTED_CAST; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_U128: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = InVal.u128.s.Lo; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U16: pValue->u16 = InVal.u128.s.Lo; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U32: pValue->u32 = InVal.u128.s.Lo; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U64: pValue->u64 = InVal.u128.s.Lo; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U128: pValue->u128 = InVal.u128; return VINF_SUCCESS; case DBGFREGVALTYPE_R80: dbgfR3RegValR80SetU128(pValue, InVal.u128); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_DTR: return VERR_DBGF_UNSUPPORTED_CAST; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_R80: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = (uint8_t )dbgfR3RegValR80GetU64(&InVal); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U16: pValue->u16 = (uint16_t)dbgfR3RegValR80GetU64(&InVal); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U32: pValue->u32 = (uint32_t)dbgfR3RegValR80GetU64(&InVal); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U64: pValue->u64 = (uint64_t)dbgfR3RegValR80GetU64(&InVal); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U128: pValue->u128 = dbgfR3RegValR80GetU128(&InVal); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_R80: pValue->r80 = InVal.r80; return VINF_SUCCESS; case DBGFREGVALTYPE_DTR: return VERR_DBGF_UNSUPPORTED_CAST; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_DTR: switch (enmToType) { case DBGFREGVALTYPE_U8: pValue->u8 = InVal.dtr.u64Base; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U16: pValue->u16 = InVal.dtr.u64Base; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U32: pValue->u32 = InVal.dtr.u64Base; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U64: pValue->u64 = InVal.dtr.u64Base; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_U128: pValue->u128.s.Lo = InVal.dtr.u64Base; return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_R80: dbgfR3RegValR80SetU64(pValue, InVal.dtr.u64Base); return VINF_DBGF_TRUNCATED_REGISTER; case DBGFREGVALTYPE_DTR: pValue->dtr = InVal.dtr; return VINF_SUCCESS; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; } break; case DBGFREGVALTYPE_INVALID: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_32BIT_HACK: break; } AssertMsgFailed(("%d / %d\n", enmFromType, enmToType)); return VERR_DBGF_UNSUPPORTED_CAST; } /** * Worker for the CPU register queries. * * @returns VBox status code. * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idCpu The virtual CPU ID. * @param enmReg The register to query. * @param enmType The desired return type. * @param fGuestRegs Query guest CPU registers if set (true), * hypervisor CPU registers if clear (false). * @param pValue Where to return the register value. */ static DECLCALLBACK(int) dbgfR3RegCpuQueryWorkerOnCpu(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, DBGFREGVALTYPE enmType, bool fGuestRegs, PDBGFREGVAL pValue) { int rc = VINF_SUCCESS; DBGF_REG_DB_LOCK_READ(pUVM); /* * Look up the register set of the specified CPU. */ PDBGFREGSET pSet = fGuestRegs ? pUVM->aCpus[idCpu].dbgf.s.pGuestRegSet : pUVM->aCpus[idCpu].dbgf.s.pHyperRegSet; if (RT_LIKELY(pSet)) { /* * Look up the register and get the register value. */ if (RT_LIKELY(pSet->cDescs > (size_t)enmReg)) { PCDBGFREGDESC pDesc = &pSet->paDescs[enmReg]; pValue->au64[0] = pValue->au64[1] = 0; rc = pDesc->pfnGet(pSet->uUserArg.pv, pDesc, pValue); if (RT_SUCCESS(rc)) { /* * Do the cast if the desired return type doesn't match what * the getter returned. */ if (pDesc->enmType == enmType) rc = VINF_SUCCESS; else rc = dbgfR3RegValCast(pValue, pDesc->enmType, enmType); } } else rc = VERR_DBGF_REGISTER_NOT_FOUND; } else rc = VERR_INVALID_CPU_ID; DBGF_REG_DB_UNLOCK_READ(pUVM); return rc; } /** * Internal worker for the CPU register query functions. * * @returns VBox status code. * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idCpu The virtual CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param enmReg The register to query. * @param enmType The desired return type. * @param pValue Where to return the register value. */ static int dbgfR3RegCpuQueryWorker(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, DBGFREGVALTYPE enmType, PDBGFREGVAL pValue) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); AssertMsgReturn(enmReg >= DBGFREG_AL && enmReg <= DBGFREG_END, ("%d\n", enmReg), VERR_INVALID_PARAMETER); bool const fGuestRegs = !(idCpu & DBGFREG_HYPER_VMCPUID); idCpu &= ~DBGFREG_HYPER_VMCPUID; AssertReturn(idCpu < pUVM->cCpus, VERR_INVALID_CPU_ID); return VMR3ReqPriorityCallWaitU(pUVM, idCpu, (PFNRT)dbgfR3RegCpuQueryWorkerOnCpu, 6, pUVM, idCpu, enmReg, enmType, fGuestRegs, pValue); } /** * Queries a 8-bit CPU register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * * @param pUVM The user mode VM handle. * @param idCpu The target CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param enmReg The register that's being queried. * @param pu8 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegCpuQueryU8(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint8_t *pu8) { DBGFREGVAL Value; int rc = dbgfR3RegCpuQueryWorker(pUVM, idCpu, enmReg, DBGFREGVALTYPE_U8, &Value); if (RT_SUCCESS(rc)) *pu8 = Value.u8; else *pu8 = 0; return rc; } /** * Queries a 16-bit CPU register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idCpu The target CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param enmReg The register that's being queried. * @param pu16 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegCpuQueryU16(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint16_t *pu16) { DBGFREGVAL Value; int rc = dbgfR3RegCpuQueryWorker(pUVM, idCpu, enmReg, DBGFREGVALTYPE_U16, &Value); if (RT_SUCCESS(rc)) *pu16 = Value.u16; else *pu16 = 0; return rc; } /** * Queries a 32-bit CPU register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idCpu The target CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param enmReg The register that's being queried. * @param pu32 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegCpuQueryU32(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint32_t *pu32) { DBGFREGVAL Value; int rc = dbgfR3RegCpuQueryWorker(pUVM, idCpu, enmReg, DBGFREGVALTYPE_U32, &Value); if (RT_SUCCESS(rc)) *pu32 = Value.u32; else *pu32 = 0; return rc; } /** * Queries a 64-bit CPU register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idCpu The target CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param enmReg The register that's being queried. * @param pu64 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegCpuQueryU64(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, uint64_t *pu64) { DBGFREGVAL Value; int rc = dbgfR3RegCpuQueryWorker(pUVM, idCpu, enmReg, DBGFREGVALTYPE_U64, &Value); if (RT_SUCCESS(rc)) *pu64 = Value.u64; else *pu64 = 0; return rc; } #if 0 /* rewrite / remove */ /** * Wrapper around CPUMQueryGuestMsr for dbgfR3RegCpuQueryBatchWorker. * * @retval VINF_SUCCESS * @retval VERR_DBGF_REGISTER_NOT_FOUND * * @param pVCpu The current CPU. * @param pReg The where to store the register value and * size. * @param idMsr The MSR to get. */ static void dbgfR3RegGetMsrBatch(PVMCPU pVCpu, PDBGFREGENTRY pReg, uint32_t idMsr) { pReg->enmType = DBGFREGVALTYPE_U64; int rc = CPUMQueryGuestMsr(pVCpu, idMsr, &pReg->Val.u64); if (RT_FAILURE(rc)) { AssertMsg(rc == VERR_CPUM_RAISE_GP_0, ("%Rrc\n", rc)); pReg->Val.u64 = 0; } } static DECLCALLBACK(int) dbgfR3RegCpuQueryBatchWorker(PUVM pUVM, VMCPUID idCpu, PDBGFREGENTRY paRegs, size_t cRegs) { #if 0 PVMCPU pVCpu = &pUVM->pVM->aCpus[idCpu]; PCCPUMCTX pCtx = CPUMQueryGuestCtxPtr(pVCpu); PDBGFREGENTRY pReg = paRegs - 1; while (cRegs-- > 0) { pReg++; pReg->Val.au64[0] = 0; pReg->Val.au64[1] = 0; DBGFREG const enmReg = pReg->enmReg; AssertMsgReturn(enmReg >= 0 && enmReg <= DBGFREG_END, ("%d (%#x)\n", enmReg, enmReg), VERR_DBGF_REGISTER_NOT_FOUND); if (enmReg != DBGFREG_END) { PCDBGFREGDESC pDesc = &g_aDbgfRegDescs[enmReg]; if (!pDesc->pfnGet) { PCRTUINT128U pu = (PCRTUINT128U)((uintptr_t)pCtx + pDesc->offCtx); pReg->enmType = pDesc->enmType; switch (pDesc->enmType) { case DBGFREGVALTYPE_U8: pReg->Val.u8 = pu->au8[0]; break; case DBGFREGVALTYPE_U16: pReg->Val.u16 = pu->au16[0]; break; case DBGFREGVALTYPE_U32: pReg->Val.u32 = pu->au32[0]; break; case DBGFREGVALTYPE_U64: pReg->Val.u64 = pu->au64[0]; break; case DBGFREGVALTYPE_U128: pReg->Val.au64[0] = pu->au64[0]; pReg->Val.au64[1] = pu->au64[1]; break; case DBGFREGVALTYPE_R80: pReg->Val.au64[0] = pu->au64[0]; pReg->Val.au16[5] = pu->au16[5]; break; default: AssertMsgFailedReturn(("%s %d\n", pDesc->pszName, pDesc->enmType), VERR_IPE_NOT_REACHED_DEFAULT_CASE); } } else { int rc = pDesc->pfnGet(pVCpu, pDesc, pCtx, &pReg->Val.u); if (RT_FAILURE(rc)) return rc; } } } return VINF_SUCCESS; #else return VERR_NOT_IMPLEMENTED; #endif } /** * Query a batch of registers. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * * @param pUVM The user mode VM handle. * @param idCpu The target CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param paRegs Pointer to an array of @a cRegs elements. On * input the enmReg members indicates which * registers to query. On successful return the * other members are set. DBGFREG_END can be used * as a filler. * @param cRegs The number of entries in @a paRegs. */ VMMR3DECL(int) DBGFR3RegCpuQueryBatch(PUVM pUVM, VMCPUID idCpu, PDBGFREGENTRY paRegs, size_t cRegs) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, NULL); AssertReturn(idCpu < pUVM->cCpus, VERR_INVALID_CPU_ID); if (!cRegs) return VINF_SUCCESS; AssertReturn(cRegs < _1M, VERR_OUT_OF_RANGE); AssertPtrReturn(paRegs, VERR_INVALID_POINTER); size_t iReg = cRegs; while (iReg-- > 0) { DBGFREG enmReg = paRegs[iReg].enmReg; AssertMsgReturn(enmReg < DBGFREG_END && enmReg >= DBGFREG_AL, ("%d (%#x)", enmReg, enmReg), VERR_DBGF_REGISTER_NOT_FOUND); } return VMR3ReqCallWaitU(pUVM, idCpu, (PFNRT)dbgfR3RegCpuQueryBatchWorker, 4, pUVM, idCpu, paRegs, cRegs); } /** * Query all registers for a Virtual CPU. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * * @param pUVM The user mode VM handle. * @param idCpu The target CPU ID. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param paRegs Pointer to an array of @a cRegs elements. * These will be filled with the CPU register * values. Overflowing entries will be set to * DBGFREG_END. The returned registers can be * accessed by using the DBGFREG values as index. * @param cRegs The number of entries in @a paRegs. The * recommended value is DBGFREG_ALL_COUNT. */ VMMR3DECL(int) DBGFR3RegCpuQueryAll(PUVM pUVM, VMCPUID idCpu, PDBGFREGENTRY paRegs, size_t cRegs) { /* * Validate input. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, NULL); AssertReturn(idCpu < pUVM->cCpus, VERR_INVALID_CPU_ID); if (!cRegs) return VINF_SUCCESS; AssertReturn(cRegs < _1M, VERR_OUT_OF_RANGE); AssertPtrReturn(paRegs, VERR_INVALID_POINTER); /* * Convert it into a batch query (lazy bird). */ unsigned iReg = 0; while (iReg < cRegs && iReg < DBGFREG_ALL_COUNT) { paRegs[iReg].enmReg = (DBGFREG)iReg; iReg++; } while (iReg < cRegs) paRegs[iReg++].enmReg = DBGFREG_END; return VMR3ReqCallWaitU(pUVM, idCpu, (PFNRT)dbgfR3RegCpuQueryBatchWorker, 4, pUVM, idCpu, paRegs, cRegs); } #endif /* rewrite or remove? */ /** * Gets the name of a register. * * @returns Pointer to read-only register name (lower case). NULL if the * parameters are invalid. * * @param pUVM The user mode VM handle. * @param enmReg The register identifier. * @param enmType The register type. This is for sort out * aliases. Pass DBGFREGVALTYPE_INVALID to get * the standard name. */ VMMR3DECL(const char *) DBGFR3RegCpuName(PUVM pUVM, DBGFREG enmReg, DBGFREGVALTYPE enmType) { AssertReturn(enmReg >= DBGFREG_AL && enmReg < DBGFREG_END, NULL); AssertReturn(enmType >= DBGFREGVALTYPE_INVALID && enmType < DBGFREGVALTYPE_END, NULL); UVM_ASSERT_VALID_EXT_RETURN(pUVM, NULL); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, NULL); PCDBGFREGSET pSet = pUVM->aCpus[0].dbgf.s.pGuestRegSet; if (RT_UNLIKELY(!pSet)) return NULL; PCDBGFREGDESC pDesc = &pSet->paDescs[enmReg]; PCDBGFREGALIAS pAlias = pDesc->paAliases; if ( pAlias && pDesc->enmType != enmType && enmType != DBGFREGVALTYPE_INVALID) { while (pAlias->pszName) { if (pAlias->enmType == enmType) return pAlias->pszName; pAlias++; } } return pDesc->pszName; } /** * Fold the string to lower case and copy it into the destination buffer. * * @returns Number of folder characters, -1 on overflow. * @param pszSrc The source string. * @param cchSrc How much to fold and copy. * @param pszDst The output buffer. * @param cbDst The size of the output buffer. */ static ssize_t dbgfR3RegCopyToLower(const char *pszSrc, size_t cchSrc, char *pszDst, size_t cbDst) { ssize_t cchFolded = 0; char ch; while (cchSrc-- > 0 && (ch = *pszSrc++)) { if (RT_UNLIKELY(cbDst <= 1)) return -1; cbDst--; char chLower = RT_C_TO_LOWER(ch); cchFolded += chLower != ch; *pszDst++ = chLower; } if (RT_UNLIKELY(!cbDst)) return -1; *pszDst = '\0'; return cchFolded; } /** * Resolves the register name. * * @returns Lookup record. * @param pUVM The user mode VM handle. * @param idDefCpu The default CPU ID set. * @param pszReg The register name. * @param fGuestRegs Default to guest CPU registers if set, the * hypervisor CPU registers if clear. */ static PCDBGFREGLOOKUP dbgfR3RegResolve(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, bool fGuestRegs) { DBGF_REG_DB_LOCK_READ(pUVM); /* Try looking up the name without any case folding or cpu prefixing. */ PRTSTRSPACE pRegSpace = &pUVM->dbgf.s.RegSpace; PCDBGFREGLOOKUP pLookupRec = (PCDBGFREGLOOKUP)RTStrSpaceGet(pRegSpace, pszReg); if (!pLookupRec) { char szName[DBGF_REG_MAX_NAME * 4 + 16]; /* Lower case it and try again. */ ssize_t cchFolded = dbgfR3RegCopyToLower(pszReg, RTSTR_MAX, szName, sizeof(szName) - DBGF_REG_MAX_NAME); if (cchFolded > 0) pLookupRec = (PCDBGFREGLOOKUP)RTStrSpaceGet(pRegSpace, szName); if ( !pLookupRec && cchFolded >= 0 && idDefCpu != VMCPUID_ANY) { /* Prefix it with the specified CPU set. */ size_t cchCpuSet = RTStrPrintf(szName, sizeof(szName), fGuestRegs ? "cpu%u." : "hypercpu%u.", idDefCpu); dbgfR3RegCopyToLower(pszReg, RTSTR_MAX, &szName[cchCpuSet], sizeof(szName) - cchCpuSet); pLookupRec = (PCDBGFREGLOOKUP)RTStrSpaceGet(pRegSpace, szName); } } DBGF_REG_DB_UNLOCK_READ(pUVM); return pLookupRec; } /** * Validates the register name. * * @returns VBox status code. * @retval VINF_SUCCESS if the register was found. * @retval VERR_DBGF_REGISTER_NOT_FOUND if not found. * * @param pUVM The user mode VM handle. * @param idDefCpu The default CPU. * @param pszReg The registe name. */ VMMR3DECL(int) DBGFR3RegNmValidate(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg) { /* * Validate input. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); AssertReturn((idDefCpu & ~DBGFREG_HYPER_VMCPUID) < pUVM->cCpus || idDefCpu == VMCPUID_ANY, VERR_INVALID_CPU_ID); AssertPtrReturn(pszReg, VERR_INVALID_POINTER); /* * Resolve the register. */ bool const fGuestRegs = !(idDefCpu & DBGFREG_HYPER_VMCPUID) && idDefCpu != VMCPUID_ANY; PCDBGFREGLOOKUP pLookupRec = dbgfR3RegResolve(pUVM, idDefCpu, pszReg, fGuestRegs); if (!pLookupRec) return VERR_DBGF_REGISTER_NOT_FOUND; return VINF_SUCCESS; } /** * On CPU worker for the register queries, used by dbgfR3RegNmQueryWorker and * dbgfR3RegPrintfCbFormatNormal. * * @returns VBox status code. * * @param pUVM The user mode VM handle. * @param pLookupRec The register lookup record. * @param enmType The desired return type. * @param pValue Where to return the register value. * @param penmType Where to store the register value type. * Optional. */ static DECLCALLBACK(int) dbgfR3RegNmQueryWorkerOnCpu(PUVM pUVM, PCDBGFREGLOOKUP pLookupRec, DBGFREGVALTYPE enmType, PDBGFREGVAL pValue, PDBGFREGVALTYPE penmType) { PCDBGFREGDESC pDesc = pLookupRec->pDesc; PCDBGFREGSET pSet = pLookupRec->pSet; PCDBGFREGSUBFIELD pSubField = pLookupRec->pSubField; DBGFREGVALTYPE enmValueType = pDesc->enmType; int rc; NOREF(pUVM); /* * Get the register or sub-field value. */ dbgfR3RegValClear(pValue); if (!pSubField) { rc = pDesc->pfnGet(pSet->uUserArg.pv, pDesc, pValue); if ( pLookupRec->pAlias && pLookupRec->pAlias->enmType != enmValueType && RT_SUCCESS(rc)) { rc = dbgfR3RegValCast(pValue, enmValueType, pLookupRec->pAlias->enmType); enmValueType = pLookupRec->pAlias->enmType; } } else { if (pSubField->pfnGet) { rc = pSubField->pfnGet(pSet->uUserArg.pv, pSubField, &pValue->u128); enmValueType = DBGFREGVALTYPE_U128; } else { rc = pDesc->pfnGet(pSet->uUserArg.pv, pDesc, pValue); if ( pLookupRec->pAlias && pLookupRec->pAlias->enmType != enmValueType && RT_SUCCESS(rc)) { rc = dbgfR3RegValCast(pValue, enmValueType, pLookupRec->pAlias->enmType); enmValueType = pLookupRec->pAlias->enmType; } if (RT_SUCCESS(rc)) { rc = dbgfR3RegValCast(pValue, enmValueType, DBGFREGVALTYPE_U128); if (RT_SUCCESS(rc)) { RTUInt128AssignShiftLeft(&pValue->u128, -pSubField->iFirstBit); RTUInt128AssignAndNFirstBits(&pValue->u128, pSubField->cBits); if (pSubField->cShift) RTUInt128AssignShiftLeft(&pValue->u128, pSubField->cShift); } } } if (RT_SUCCESS(rc)) { unsigned const cBits = pSubField->cBits + pSubField->cShift; if (cBits <= 8) enmValueType = DBGFREGVALTYPE_U8; else if (cBits <= 16) enmValueType = DBGFREGVALTYPE_U16; else if (cBits <= 32) enmValueType = DBGFREGVALTYPE_U32; else if (cBits <= 64) enmValueType = DBGFREGVALTYPE_U64; else enmValueType = DBGFREGVALTYPE_U128; rc = dbgfR3RegValCast(pValue, DBGFREGVALTYPE_U128, enmValueType); } } if (RT_SUCCESS(rc)) { /* * Do the cast if the desired return type doesn't match what * the getter returned. */ if ( enmValueType == enmType || enmType == DBGFREGVALTYPE_END) { rc = VINF_SUCCESS; if (penmType) *penmType = enmValueType; } else { rc = dbgfR3RegValCast(pValue, enmValueType, enmType); if (penmType) *penmType = RT_SUCCESS(rc) ? enmType : enmValueType; } } return rc; } /** * Worker for the register queries. * * @returns VBox status code. * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The virtual CPU ID for the default CPU register * set. Can be OR'ed with DBGFREG_HYPER_VMCPUID. * @param pszReg The register to query. * @param enmType The desired return type. * @param pValue Where to return the register value. * @param penmType Where to store the register value type. * Optional. */ static int dbgfR3RegNmQueryWorker(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, DBGFREGVALTYPE enmType, PDBGFREGVAL pValue, PDBGFREGVALTYPE penmType) { /* * Validate input. */ UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); VM_ASSERT_VALID_EXT_RETURN(pUVM->pVM, VERR_INVALID_VM_HANDLE); AssertReturn((idDefCpu & ~DBGFREG_HYPER_VMCPUID) < pUVM->cCpus || idDefCpu == VMCPUID_ANY, VERR_INVALID_CPU_ID); AssertPtrReturn(pszReg, VERR_INVALID_POINTER); Assert(enmType > DBGFREGVALTYPE_INVALID && enmType <= DBGFREGVALTYPE_END); AssertPtr(pValue); /* * Resolve the register and call the getter on the relevant CPU. */ bool const fGuestRegs = !(idDefCpu & DBGFREG_HYPER_VMCPUID) && idDefCpu != VMCPUID_ANY; PCDBGFREGLOOKUP pLookupRec = dbgfR3RegResolve(pUVM, idDefCpu, pszReg, fGuestRegs); if (pLookupRec) { if (pLookupRec->pSet->enmType == DBGFREGSETTYPE_CPU) idDefCpu = pLookupRec->pSet->uUserArg.pVCpu->idCpu; else if (idDefCpu != VMCPUID_ANY) idDefCpu &= ~DBGFREG_HYPER_VMCPUID; return VMR3ReqPriorityCallWaitU(pUVM, idDefCpu, (PFNRT)dbgfR3RegNmQueryWorkerOnCpu, 5, pUVM, pLookupRec, enmType, pValue, penmType); } return VERR_DBGF_REGISTER_NOT_FOUND; } /** * Queries a descriptor table register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pValue Where to store the register value. * @param penmType Where to store the register value type. */ VMMR3DECL(int) DBGFR3RegNmQuery(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, PDBGFREGVAL pValue, PDBGFREGVALTYPE penmType) { return dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_END, pValue, penmType); } /** * Queries a 8-bit register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pu8 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegNmQueryU8(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, uint8_t *pu8) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_U8, &Value, NULL); if (RT_SUCCESS(rc)) *pu8 = Value.u8; else *pu8 = 0; return rc; } /** * Queries a 16-bit register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pu16 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegNmQueryU16(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, uint16_t *pu16) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_U16, &Value, NULL); if (RT_SUCCESS(rc)) *pu16 = Value.u16; else *pu16 = 0; return rc; } /** * Queries a 32-bit register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pu32 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegNmQueryU32(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, uint32_t *pu32) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_U32, &Value, NULL); if (RT_SUCCESS(rc)) *pu32 = Value.u32; else *pu32 = 0; return rc; } /** * Queries a 64-bit register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pu64 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegNmQueryU64(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, uint64_t *pu64) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_U64, &Value, NULL); if (RT_SUCCESS(rc)) *pu64 = Value.u64; else *pu64 = 0; return rc; } /** * Queries a 128-bit register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pu128 Where to store the register value. */ VMMR3DECL(int) DBGFR3RegNmQueryU128(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, PRTUINT128U pu128) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_U128, &Value, NULL); if (RT_SUCCESS(rc)) *pu128 = Value.u128; else pu128->s.Hi = pu128->s.Lo = 0; return rc; } #if 0 /** * Queries a long double register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param plrd Where to store the register value. */ VMMR3DECL(int) DBGFR3RegNmQueryLrd(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, long double *plrd) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_R80, &Value, NULL); if (RT_SUCCESS(rc)) *plrd = Value.lrd; else *plrd = 0; return rc; } #endif /** * Queries a descriptor table register value. * * @retval VINF_SUCCESS * @retval VERR_INVALID_VM_HANDLE * @retval VERR_INVALID_CPU_ID * @retval VERR_DBGF_REGISTER_NOT_FOUND * @retval VERR_DBGF_UNSUPPORTED_CAST * @retval VINF_DBGF_TRUNCATED_REGISTER * @retval VINF_DBGF_ZERO_EXTENDED_REGISTER * * @param pUVM The user mode VM handle. * @param idDefCpu The default target CPU ID, VMCPUID_ANY if not * applicable. Can be OR'ed with * DBGFREG_HYPER_VMCPUID. * @param pszReg The register that's being queried. Except for * CPU registers, this must be on the form * "set.reg[.sub]". * @param pu64Base Where to store the register base value. * @param pu32Limit Where to store the register limit value. */ VMMR3DECL(int) DBGFR3RegNmQueryXdtr(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, uint64_t *pu64Base, uint32_t *pu32Limit) { DBGFREGVAL Value; int rc = dbgfR3RegNmQueryWorker(pUVM, idDefCpu, pszReg, DBGFREGVALTYPE_DTR, &Value, NULL); if (RT_SUCCESS(rc)) { *pu64Base = Value.dtr.u64Base; *pu32Limit = Value.dtr.u32Limit; } else { *pu64Base = 0; *pu32Limit = 0; } return rc; } /// @todo VMMR3DECL(int) DBGFR3RegNmQueryBatch(PUVM pUVM,VMCPUID idDefCpu, DBGFREGENTRYNM paRegs, size_t cRegs); /** * Gets the number of registers returned by DBGFR3RegNmQueryAll. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param pcRegs Where to return the register count. */ VMMR3DECL(int) DBGFR3RegNmQueryAllCount(PUVM pUVM, size_t *pcRegs) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); *pcRegs = pUVM->dbgf.s.cRegs; return VINF_SUCCESS; } /** * Pad register entries. * * @param paRegs The output array. * @param cRegs The size of the output array. * @param iReg The first register to pad. * @param cRegsToPad The number of registers to pad. */ static void dbgfR3RegNmQueryAllPadEntries(PDBGFREGENTRYNM paRegs, size_t cRegs, size_t iReg, size_t cRegsToPad) { if (iReg < cRegs) { size_t iEndReg = iReg + cRegsToPad; if (iEndReg > cRegs) iEndReg = cRegs; while (iReg < iEndReg) { paRegs[iReg].pszName = NULL; paRegs[iReg].enmType = DBGFREGVALTYPE_END; dbgfR3RegValClear(&paRegs[iReg].Val); iReg++; } } } /** * Query all registers in a set. * * @param pSet The set. * @param cRegsToQuery The number of registers to query. * @param paRegs The output array. * @param cRegs The size of the output array. */ static void dbgfR3RegNmQueryAllInSet(PCDBGFREGSET pSet, size_t cRegsToQuery, PDBGFREGENTRYNM paRegs, size_t cRegs) { if (cRegsToQuery > pSet->cDescs) cRegsToQuery = pSet->cDescs; if (cRegsToQuery > cRegs) cRegsToQuery = cRegs; for (size_t iReg = 0; iReg < cRegsToQuery; iReg++) { paRegs[iReg].enmType = pSet->paDescs[iReg].enmType; paRegs[iReg].pszName = pSet->paLookupRecs[iReg].Core.pszString; dbgfR3RegValClear(&paRegs[iReg].Val); int rc2 = pSet->paDescs[iReg].pfnGet(pSet->uUserArg.pv, &pSet->paDescs[iReg], &paRegs[iReg].Val); AssertRCSuccess(rc2); if (RT_FAILURE(rc2)) dbgfR3RegValClear(&paRegs[iReg].Val); } } /** * @callback_method_impl{FNRTSTRSPACECALLBACK, Worker used by * dbgfR3RegNmQueryAllWorker} */ static DECLCALLBACK(int) dbgfR3RegNmQueryAllEnum(PRTSTRSPACECORE pStr, void *pvUser) { PCDBGFREGSET pSet = (PCDBGFREGSET)pStr; if (pSet->enmType != DBGFREGSETTYPE_CPU) { PDBGFR3REGNMQUERYALLARGS pArgs = (PDBGFR3REGNMQUERYALLARGS)pvUser; if (pArgs->iReg < pArgs->cRegs) dbgfR3RegNmQueryAllInSet(pSet, pSet->cDescs, &pArgs->paRegs[pArgs->iReg], pArgs->cRegs - pArgs->iReg); pArgs->iReg += pSet->cDescs; } return 0; } /** * @callback_method_impl{FNVMMEMTRENDEZVOUS, Worker used by DBGFR3RegNmQueryAll} */ static DECLCALLBACK(VBOXSTRICTRC) dbgfR3RegNmQueryAllWorker(PVM pVM, PVMCPU pVCpu, void *pvUser) { PDBGFR3REGNMQUERYALLARGS pArgs = (PDBGFR3REGNMQUERYALLARGS)pvUser; PDBGFREGENTRYNM paRegs = pArgs->paRegs; size_t const cRegs = pArgs->cRegs; PUVM pUVM = pVM->pUVM; PUVMCPU pUVCpu = pVCpu->pUVCpu; DBGF_REG_DB_LOCK_READ(pUVM); /* * My guest CPU registers. */ size_t iCpuReg = pVCpu->idCpu * DBGFREG_ALL_COUNT; if (pUVCpu->dbgf.s.pGuestRegSet) { if (iCpuReg < cRegs) dbgfR3RegNmQueryAllInSet(pUVCpu->dbgf.s.pGuestRegSet, DBGFREG_ALL_COUNT, &paRegs[iCpuReg], cRegs - iCpuReg); } else dbgfR3RegNmQueryAllPadEntries(paRegs, cRegs, iCpuReg, DBGFREG_ALL_COUNT); /* * My hypervisor CPU registers. */ iCpuReg = pUVM->cCpus * DBGFREG_ALL_COUNT + pUVCpu->idCpu * DBGFREG_ALL_COUNT; if (pUVCpu->dbgf.s.pHyperRegSet) { if (iCpuReg < cRegs) dbgfR3RegNmQueryAllInSet(pUVCpu->dbgf.s.pHyperRegSet, DBGFREG_ALL_COUNT, &paRegs[iCpuReg], cRegs - iCpuReg); } else dbgfR3RegNmQueryAllPadEntries(paRegs, cRegs, iCpuReg, DBGFREG_ALL_COUNT); /* * The primary CPU does all the other registers. */ if (pUVCpu->idCpu == 0) { pArgs->iReg = pUVM->cCpus * DBGFREG_ALL_COUNT * 2; RTStrSpaceEnumerate(&pUVM->dbgf.s.RegSetSpace, dbgfR3RegNmQueryAllEnum, pArgs); dbgfR3RegNmQueryAllPadEntries(paRegs, cRegs, pArgs->iReg, cRegs); } DBGF_REG_DB_UNLOCK_READ(pUVM); return VINF_SUCCESS; /* Ignore errors. */ } /** * Queries all register. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param paRegs The output register value array. The register * name string is read only and shall not be freed * or modified. * @param cRegs The number of entries in @a paRegs. The * correct size can be obtained by calling * DBGFR3RegNmQueryAllCount. */ VMMR3DECL(int) DBGFR3RegNmQueryAll(PUVM pUVM, PDBGFREGENTRYNM paRegs, size_t cRegs) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); PVM pVM = pUVM->pVM; VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE); AssertPtrReturn(paRegs, VERR_INVALID_POINTER); AssertReturn(cRegs > 0, VERR_OUT_OF_RANGE); DBGFR3REGNMQUERYALLARGS Args; Args.paRegs = paRegs; Args.cRegs = cRegs; return VMMR3EmtRendezvous(pVM, VMMEMTRENDEZVOUS_FLAGS_TYPE_ALL_AT_ONCE, dbgfR3RegNmQueryAllWorker, &Args); } VMMR3DECL(int) DBGFR3RegNmSet(PUVM pUVM, VMCPUID idDefCpu, const char *pszReg, PCDBGFREGVAL pValue, DBGFREGVALTYPE enmType) { NOREF(pUVM); NOREF(idDefCpu); NOREF(pszReg); NOREF(pValue); NOREF(enmType); return VERR_NOT_IMPLEMENTED; } /** * Internal worker for DBGFR3RegFormatValue, cbTmp is sufficent. * * @copydoc DBGFR3RegFormatValue */ DECLINLINE(ssize_t) dbgfR3RegFormatValueInt(char *pszTmp, size_t cbTmp, PCDBGFREGVAL pValue, DBGFREGVALTYPE enmType, unsigned uBase, signed int cchWidth, signed int cchPrecision, uint32_t fFlags) { switch (enmType) { case DBGFREGVALTYPE_U8: return RTStrFormatU8(pszTmp, cbTmp, pValue->u8, uBase, cchWidth, cchPrecision, fFlags); case DBGFREGVALTYPE_U16: return RTStrFormatU16(pszTmp, cbTmp, pValue->u16, uBase, cchWidth, cchPrecision, fFlags); case DBGFREGVALTYPE_U32: return RTStrFormatU32(pszTmp, cbTmp, pValue->u32, uBase, cchWidth, cchPrecision, fFlags); case DBGFREGVALTYPE_U64: return RTStrFormatU64(pszTmp, cbTmp, pValue->u64, uBase, cchWidth, cchPrecision, fFlags); case DBGFREGVALTYPE_U128: return RTStrFormatU128(pszTmp, cbTmp, &pValue->u128, uBase, cchWidth, cchPrecision, fFlags); case DBGFREGVALTYPE_R80: return RTStrFormatR80u2(pszTmp, cbTmp, &pValue->r80Ex, cchWidth, cchPrecision, fFlags); case DBGFREGVALTYPE_DTR: { ssize_t cch = RTStrFormatU64(pszTmp, cbTmp, pValue->dtr.u64Base, 16, 2+16, 0, RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD); AssertReturn(cch > 0, VERR_DBGF_REG_IPE_1); pszTmp[cch++] = ':'; cch += RTStrFormatU64(&pszTmp[cch], cbTmp - cch, pValue->dtr.u32Limit, 16, 4, 0, RTSTR_F_ZEROPAD | RTSTR_F_32BIT); return cch; } case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; /* no default, want gcc warnings */ } RTStrPrintf(pszTmp, cbTmp, "!enmType=%d!", enmType); return VERR_DBGF_REG_IPE_2; } /** * Format a register value, extended version. * * @returns The number of bytes returned, VERR_BUFFER_OVERFLOW on failure. * @param pszBuf The output buffer. * @param cbBuf The size of the output buffer. * @param pValue The value to format. * @param enmType The value type. * @param uBase The base (ignored if not applicable). * @param cchWidth The width if RTSTR_F_WIDTH is set, otherwise * ignored. * @param cchPrecision The width if RTSTR_F_PRECISION is set, otherwise * ignored. * @param fFlags String formatting flags, RTSTR_F_XXX. */ VMMR3DECL(ssize_t) DBGFR3RegFormatValueEx(char *pszBuf, size_t cbBuf, PCDBGFREGVAL pValue, DBGFREGVALTYPE enmType, unsigned uBase, signed int cchWidth, signed int cchPrecision, uint32_t fFlags) { /* * Format to temporary buffer using worker shared with dbgfR3RegPrintfCbFormatNormal. */ char szTmp[160]; ssize_t cchOutput = dbgfR3RegFormatValueInt(szTmp, sizeof(szTmp), pValue, enmType, uBase, cchWidth, cchPrecision, fFlags); if (cchOutput > 0) { if ((size_t)cchOutput < cbBuf) memcpy(pszBuf, szTmp, cchOutput + 1); else { if (cbBuf) { memcpy(pszBuf, szTmp, cbBuf - 1); pszBuf[cbBuf - 1] = '\0'; } cchOutput = VERR_BUFFER_OVERFLOW; } } return cchOutput; } /** * Format a register value as hexadecimal and with default width according to * the type. * * @returns The number of bytes returned, VERR_BUFFER_OVERFLOW on failure. * @param pszBuf The output buffer. * @param cbBuf The size of the output buffer. * @param pValue The value to format. * @param enmType The value type. * @param fSpecial Same as RTSTR_F_SPECIAL. */ VMMR3DECL(ssize_t) DBGFR3RegFormatValue(char *pszBuf, size_t cbBuf, PCDBGFREGVAL pValue, DBGFREGVALTYPE enmType, bool fSpecial) { int cchWidth = 0; switch (enmType) { case DBGFREGVALTYPE_U8: cchWidth = 2 + fSpecial*2; break; case DBGFREGVALTYPE_U16: cchWidth = 4 + fSpecial*2; break; case DBGFREGVALTYPE_U32: cchWidth = 8 + fSpecial*2; break; case DBGFREGVALTYPE_U64: cchWidth = 16 + fSpecial*2; break; case DBGFREGVALTYPE_U128: cchWidth = 32 + fSpecial*2; break; case DBGFREGVALTYPE_R80: cchWidth = 0; break; case DBGFREGVALTYPE_DTR: cchWidth = 16+1+4 + fSpecial*2; break; case DBGFREGVALTYPE_32BIT_HACK: case DBGFREGVALTYPE_END: case DBGFREGVALTYPE_INVALID: break; /* no default, want gcc warnings */ } uint32_t fFlags = RTSTR_F_ZEROPAD; if (fSpecial) fFlags |= RTSTR_F_SPECIAL; if (cchWidth != 0) fFlags |= RTSTR_F_WIDTH; return DBGFR3RegFormatValueEx(pszBuf, cbBuf, pValue, enmType, 16, cchWidth, 0, fFlags); } /** * Format a register using special hacks as well as sub-field specifications * (the latter isn't implemented yet). */ static size_t dbgfR3RegPrintfCbFormatField(PDBGFR3REGPRINTFARGS pThis, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, PCDBGFREGLOOKUP pLookupRec, int cchWidth, int cchPrecision, unsigned fFlags) { char szTmp[160]; NOREF(cchWidth); NOREF(cchPrecision); NOREF(fFlags); /* * Retrieve the register value. */ DBGFREGVAL Value; DBGFREGVALTYPE enmType; int rc = dbgfR3RegNmQueryWorkerOnCpu(pThis->pUVM, pLookupRec, DBGFREGVALTYPE_END, &Value, &enmType); if (RT_FAILURE(rc)) { PCRTSTATUSMSG pErr = RTErrGet(rc); if (pErr) return pfnOutput(pvArgOutput, pErr->pszDefine, strlen(pErr->pszDefine)); return pfnOutput(pvArgOutput, szTmp, RTStrPrintf(szTmp, sizeof(szTmp), "rc=%d", rc)); } char *psz = szTmp; /* * Special case: Format eflags. */ if ( pLookupRec->pSet->enmType == DBGFREGSETTYPE_CPU && pLookupRec->pDesc->enmReg == DBGFREG_RFLAGS && pLookupRec->pSubField == NULL) { rc = dbgfR3RegValCast(&Value, enmType, DBGFREGVALTYPE_U32); AssertRC(rc); uint32_t const efl = Value.u32; /* the iopl */ psz += RTStrPrintf(psz, sizeof(szTmp) / 2, "iopl=%u ", X86_EFL_GET_IOPL(efl)); /* add flags */ static const struct { const char *pszSet; const char *pszClear; uint32_t fFlag; } aFlags[] = { { "vip",NULL, X86_EFL_VIP }, { "vif",NULL, X86_EFL_VIF }, { "ac", NULL, X86_EFL_AC }, { "vm", NULL, X86_EFL_VM }, { "rf", NULL, X86_EFL_RF }, { "nt", NULL, X86_EFL_NT }, { "ov", "nv", X86_EFL_OF }, { "dn", "up", X86_EFL_DF }, { "ei", "di", X86_EFL_IF }, { "tf", NULL, X86_EFL_TF }, { "ng", "pl", X86_EFL_SF }, { "zr", "nz", X86_EFL_ZF }, { "ac", "na", X86_EFL_AF }, { "po", "pe", X86_EFL_PF }, { "cy", "nc", X86_EFL_CF }, }; for (unsigned i = 0; i < RT_ELEMENTS(aFlags); i++) { const char *pszAdd = aFlags[i].fFlag & efl ? aFlags[i].pszSet : aFlags[i].pszClear; if (pszAdd) { *psz++ = *pszAdd++; *psz++ = *pszAdd++; if (*pszAdd) *psz++ = *pszAdd++; *psz++ = ' '; } } /* drop trailing space */ psz--; } else { /* * General case. */ AssertMsgFailed(("Not implemented: %s\n", pLookupRec->Core.pszString)); return pfnOutput(pvArgOutput, pLookupRec->Core.pszString, pLookupRec->Core.cchString); } /* Output the string. */ return pfnOutput(pvArgOutput, szTmp, psz - &szTmp[0]); } /** * Formats a register having parsed up to the register name. */ static size_t dbgfR3RegPrintfCbFormatNormal(PDBGFR3REGPRINTFARGS pThis, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, PCDBGFREGLOOKUP pLookupRec, unsigned uBase, int cchWidth, int cchPrecision, unsigned fFlags) { char szTmp[160]; /* * Get the register value. */ DBGFREGVAL Value; DBGFREGVALTYPE enmType; int rc = dbgfR3RegNmQueryWorkerOnCpu(pThis->pUVM, pLookupRec, DBGFREGVALTYPE_END, &Value, &enmType); if (RT_FAILURE(rc)) { PCRTSTATUSMSG pErr = RTErrGet(rc); if (pErr) return pfnOutput(pvArgOutput, pErr->pszDefine, strlen(pErr->pszDefine)); return pfnOutput(pvArgOutput, szTmp, RTStrPrintf(szTmp, sizeof(szTmp), "rc=%d", rc)); } /* * Format the value. */ ssize_t cchOutput = dbgfR3RegFormatValueInt(szTmp, sizeof(szTmp), &Value, enmType, uBase, cchWidth, cchPrecision, fFlags); if (RT_UNLIKELY(cchOutput <= 0)) { AssertFailed(); return pfnOutput(pvArgOutput, "internal-error", sizeof("internal-error") - 1); } return pfnOutput(pvArgOutput, szTmp, cchOutput); } /** * @callback_method_impl{FNSTRFORMAT} */ static DECLCALLBACK(size_t) dbgfR3RegPrintfCbFormat(void *pvArg, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char **ppszFormat, va_list *pArgs, int cchWidth, int cchPrecision, unsigned fFlags, char chArgSize) { NOREF(pArgs); NOREF(chArgSize); /* * Parse the format type and hand the job to the appropriate worker. */ PDBGFR3REGPRINTFARGS pThis = (PDBGFR3REGPRINTFARGS)pvArg; const char *pszFormat = *ppszFormat; if ( pszFormat[0] != 'V' || pszFormat[1] != 'R') { AssertMsgFailed(("'%s'\n", pszFormat)); return 0; } unsigned offCurly = 2; if (pszFormat[offCurly] != '{') { AssertMsgReturn(pszFormat[offCurly], ("'%s'\n", pszFormat), 0); offCurly++; AssertMsgReturn(pszFormat[offCurly] == '{', ("'%s'\n", pszFormat), 0); } const char *pachReg = &pszFormat[offCurly + 1]; /* * The end and length of the register. */ const char *pszEnd = strchr(pachReg, '}'); AssertMsgReturn(pszEnd, ("Missing closing curly bracket: '%s'\n", pszFormat), 0); size_t const cchReg = pszEnd - pachReg; /* * Look up the register - same as dbgfR3RegResolve, except for locking and * input string termination. */ PRTSTRSPACE pRegSpace = &pThis->pUVM->dbgf.s.RegSpace; /* Try looking up the name without any case folding or cpu prefixing. */ PCDBGFREGLOOKUP pLookupRec = (PCDBGFREGLOOKUP)RTStrSpaceGetN(pRegSpace, pachReg, cchReg); if (!pLookupRec) { /* Lower case it and try again. */ char szName[DBGF_REG_MAX_NAME * 4 + 16]; ssize_t cchFolded = dbgfR3RegCopyToLower(pachReg, cchReg, szName, sizeof(szName) - DBGF_REG_MAX_NAME); if (cchFolded > 0) pLookupRec = (PCDBGFREGLOOKUP)RTStrSpaceGet(pRegSpace, szName); if ( !pLookupRec && cchFolded >= 0 && pThis->idCpu != VMCPUID_ANY) { /* Prefix it with the specified CPU set. */ size_t cchCpuSet = RTStrPrintf(szName, sizeof(szName), pThis->fGuestRegs ? "cpu%u." : "hypercpu%u.", pThis->idCpu); dbgfR3RegCopyToLower(pachReg, cchReg, &szName[cchCpuSet], sizeof(szName) - cchCpuSet); pLookupRec = (PCDBGFREGLOOKUP)RTStrSpaceGet(pRegSpace, szName); } } AssertMsgReturn(pLookupRec, ("'%s'\n", pszFormat), 0); AssertMsgReturn( pLookupRec->pSet->enmType != DBGFREGSETTYPE_CPU || pLookupRec->pSet->uUserArg.pVCpu->idCpu == pThis->idCpu, ("'%s' idCpu=%u, pSet/cpu=%u\n", pszFormat, pThis->idCpu, pLookupRec->pSet->uUserArg.pVCpu->idCpu), 0); /* * Commit the parsed format string. Up to this point it is nice to know * what register lookup failed and such, so we've delayed comitting. */ *ppszFormat = pszEnd + 1; /* * Call the responsible worker. */ switch (pszFormat[offCurly - 1]) { case 'R': /* %VR{} */ case 'X': /* %VRX{} */ return dbgfR3RegPrintfCbFormatNormal(pThis, pfnOutput, pvArgOutput, pLookupRec, 16, cchWidth, cchPrecision, fFlags); case 'U': return dbgfR3RegPrintfCbFormatNormal(pThis, pfnOutput, pvArgOutput, pLookupRec, 10, cchWidth, cchPrecision, fFlags); case 'O': return dbgfR3RegPrintfCbFormatNormal(pThis, pfnOutput, pvArgOutput, pLookupRec, 8, cchWidth, cchPrecision, fFlags); case 'B': return dbgfR3RegPrintfCbFormatNormal(pThis, pfnOutput, pvArgOutput, pLookupRec, 2, cchWidth, cchPrecision, fFlags); case 'F': return dbgfR3RegPrintfCbFormatField(pThis, pfnOutput, pvArgOutput, pLookupRec, cchWidth, cchPrecision, fFlags); default: AssertFailed(); return 0; } } /** * @callback_method_impl{FNRTSTROUTPUT} */ static DECLCALLBACK(size_t) dbgfR3RegPrintfCbOutput(void *pvArg, const char *pachChars, size_t cbChars) { PDBGFR3REGPRINTFARGS pArgs = (PDBGFR3REGPRINTFARGS)pvArg; size_t cbToCopy = cbChars; if (cbToCopy >= pArgs->cchLeftBuf) { if (RT_SUCCESS(pArgs->rc)) pArgs->rc = VERR_BUFFER_OVERFLOW; cbToCopy = pArgs->cchLeftBuf; } if (cbToCopy > 0) { memcpy(&pArgs->pszBuf[pArgs->offBuf], pachChars, cbToCopy); pArgs->offBuf += cbToCopy; pArgs->cchLeftBuf -= cbToCopy; pArgs->pszBuf[pArgs->offBuf] = '\0'; } return cbToCopy; } /** * On CPU worker for the register formatting, used by DBGFR3RegPrintfV. * * @returns VBox status code. * * @param pArgs The argument package and state. */ static DECLCALLBACK(int) dbgfR3RegPrintfWorkerOnCpu(PDBGFR3REGPRINTFARGS pArgs) { DBGF_REG_DB_LOCK_READ(pArgs->pUVM); RTStrFormatV(dbgfR3RegPrintfCbOutput, pArgs, dbgfR3RegPrintfCbFormat, pArgs, pArgs->pszFormat, pArgs->va); DBGF_REG_DB_UNLOCK_READ(pArgs->pUVM); return pArgs->rc; } /** * Format a registers. * * This is restricted to registers from one CPU, that specified by @a idCpu. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param idCpu The CPU ID of any CPU registers that may be * printed, pass VMCPUID_ANY if not applicable. * @param pszBuf The output buffer. * @param cbBuf The size of the output buffer. * @param pszFormat The format string. Register names are given by * %VR{name}, they take no arguments. * @param va Other format arguments. */ VMMR3DECL(int) DBGFR3RegPrintfV(PUVM pUVM, VMCPUID idCpu, char *pszBuf, size_t cbBuf, const char *pszFormat, va_list va) { AssertPtrReturn(pszBuf, VERR_INVALID_POINTER); AssertReturn(cbBuf > 0, VERR_BUFFER_OVERFLOW); *pszBuf = '\0'; UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); AssertReturn((idCpu & ~DBGFREG_HYPER_VMCPUID) < pUVM->cCpus || idCpu == VMCPUID_ANY, VERR_INVALID_CPU_ID); AssertPtrReturn(pszFormat, VERR_INVALID_POINTER); /* * Set up an argument package and execute the formatting on the * specified CPU. */ DBGFR3REGPRINTFARGS Args; Args.pUVM = pUVM; Args.idCpu = idCpu != VMCPUID_ANY ? idCpu & ~DBGFREG_HYPER_VMCPUID : idCpu; Args.fGuestRegs = idCpu != VMCPUID_ANY && !(idCpu & DBGFREG_HYPER_VMCPUID); Args.pszBuf = pszBuf; Args.pszFormat = pszFormat; va_copy(Args.va, va); Args.offBuf = 0; Args.cchLeftBuf = cbBuf - 1; Args.rc = VINF_SUCCESS; int rc = VMR3ReqPriorityCallWaitU(pUVM, Args.idCpu, (PFNRT)dbgfR3RegPrintfWorkerOnCpu, 1, &Args); va_end(Args.va); return rc; } /** * Format a registers. * * This is restricted to registers from one CPU, that specified by @a idCpu. * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param idCpu The CPU ID of any CPU registers that may be * printed, pass VMCPUID_ANY if not applicable. * @param pszBuf The output buffer. * @param cbBuf The size of the output buffer. * @param pszFormat The format string. Register names are given by * %VR{name}, %VRU{name}, %VRO{name} and * %VRB{name}, which are hexadecimal, (unsigned) * decimal, octal and binary representation. None * of these types takes any arguments. * @param ... Other format arguments. */ VMMR3DECL(int) DBGFR3RegPrintf(PUVM pUVM, VMCPUID idCpu, char *pszBuf, size_t cbBuf, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); int rc = DBGFR3RegPrintfV(pUVM, idCpu, pszBuf, cbBuf, pszFormat, va); va_end(va); return rc; }