/* $Id: DBGCGdbRemoteStub.cpp 103433 2024-02-19 12:11:34Z vboxsync $ */ /** @file * DBGC - Debugger Console, GDB Remote Stub. */ /* * Copyright (C) 2010-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include /* VMR3GetVM() */ #include /* HMR3IsEnabled */ #include /* NEMR3IsEnabled */ #include #include #include #include #include #include #include "DBGCInternal.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** Character indicating the start of a packet. */ #define GDBSTUB_PKT_START '$' /** Character indicating the end of a packet (excluding the checksum). */ #define GDBSTUB_PKT_END '#' /** The escape character. */ #define GDBSTUB_PKT_ESCAPE '{' /** The out-of-band interrupt character. */ #define GDBSTUB_OOB_INTERRUPT 0x03 /** Indicate support for the 'qXfer:features:read' packet to support the target description. */ #define GDBSTUBCTX_FEATURES_F_TGT_DESC RT_BIT(0) /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Trace point type. */ typedef enum GDBSTUBTPTYPE { /** Invalid type, do not use. */ GDBSTUBTPTYPE_INVALID = 0, /** An instruction software trace point. */ GDBSTUBTPTYPE_EXEC_SW, /** An instruction hardware trace point. */ GDBSTUBTPTYPE_EXEC_HW, /** A memory read trace point. */ GDBSTUBTPTYPE_MEM_READ, /** A memory write trace point. */ GDBSTUBTPTYPE_MEM_WRITE, /** A memory access trace point. */ GDBSTUBTPTYPE_MEM_ACCESS, /** 32bit hack. */ GDBSTUBTPTYPE_32BIT_HACK = 0x7fffffff } GDBSTUBTPTYPE; /** * GDB stub receive state. */ typedef enum GDBSTUBRECVSTATE { /** Invalid state. */ GDBSTUBRECVSTATE_INVALID = 0, /** Waiting for the start character. */ GDBSTUBRECVSTATE_PACKET_WAIT_FOR_START, /** Reiceiving the packet body up until the END character. */ GDBSTUBRECVSTATE_PACKET_RECEIVE_BODY, /** Receiving the checksum. */ GDBSTUBRECVSTATE_PACKET_RECEIVE_CHECKSUM, /** Blow up the enum to 32bits for easier alignment of members in structs. */ GDBSTUBRECVSTATE_32BIT_HACK = 0x7fffffff } GDBSTUBRECVSTATE; /** * GDB target register descriptor. */ typedef struct GDBREGDESC { /** Register name. */ const char *pszName; /** DBGF register index. */ DBGFREG enmReg; /** Bitsize */ uint32_t cBits; /** Type. */ const char *pszType; /** Group. */ const char *pszGroup; } GDBREGDESC; /** Pointer to a GDB target register descriptor. */ typedef GDBREGDESC *PGDBREGDESC; /** Pointer to a const GDB target register descriptor. */ typedef const GDBREGDESC *PCGDBREGDESC; /** * A tracepoint descriptor. */ typedef struct GDBSTUBTP { /** List node for the list of tracepoints. */ RTLISTNODE NdTps; /** The breakpoint number from the DBGF API. */ uint32_t iBp; /** The tracepoint type for identification. */ GDBSTUBTPTYPE enmTpType; /** The tracepoint address for identification. */ uint64_t GdbTgtAddr; /** The tracepoint kind for identification. */ uint64_t uKind; } GDBSTUBTP; /** Pointer to a tracepoint. */ typedef GDBSTUBTP *PGDBSTUBTP; /** * GDB stub context data. */ typedef struct GDBSTUBCTX { /** Internal debugger console data. */ DBGC Dbgc; /** The current state when receiving a new packet. */ GDBSTUBRECVSTATE enmState; /** Maximum number of bytes the packet buffer can hold. */ size_t cbPktBufMax; /** Current offset into the packet buffer. */ size_t offPktBuf; /** The size of the packet (minus the start, end characters and the checksum). */ size_t cbPkt; /** Pointer to the packet buffer data. */ uint8_t *pbPktBuf; /** Number of bytes left for the checksum. */ size_t cbChksumRecvLeft; /** Send packet checksum. */ uint8_t uChkSumSend; /** Feature flags supported we negotiated with the remote end. */ uint32_t fFeatures; /** Pointer to the XML target description. */ char *pachTgtXmlDesc; /** Size of the XML target description. */ size_t cbTgtXmlDesc; /** Pointer to the selected GDB register set. */ PCGDBREGDESC paRegs; /** Number of entries in the register set. */ uint32_t cRegs; /** Flag whether the stub is in extended mode. */ bool fExtendedMode; /** Flag whether was something was output using the 'O' packet since it was reset last. */ bool fOutput; /** List of registered trace points. * GDB removes breakpoints/watchpoints using the parameters they were * registered with while we only use the BP number form DBGF internally. * Means we have to track all registration so we can remove them later on. */ RTLISTANCHOR LstTps; /** Flag whether a ThreadInfo query was started. */ bool fInThrdInfoQuery; /** Next ID to return in the current ThreadInfo query. */ VMCPUID idCpuNextThrdInfoQuery; } GDBSTUBCTX; /** Pointer to the GDB stub context data. */ typedef GDBSTUBCTX *PGDBSTUBCTX; /** Pointer to const GDB stub context data. */ typedef const GDBSTUBCTX *PCGDBSTUBCTX; /** Pointer to a GDB stub context data pointer. */ typedef PGDBSTUBCTX *PPGDBSTUBCTX; /** * Specific query packet processor callback. * * @returns Status code. * @param pThis The GDB stub context. * @param pbVal Pointer to the remaining value. * @param cbVal Size of the remaining value in bytes. */ typedef DECLCALLBACKTYPE(int, FNGDBSTUBQPKTPROC,(PGDBSTUBCTX pThis, const uint8_t *pbVal, size_t cbVal)); typedef FNGDBSTUBQPKTPROC *PFNGDBSTUBQPKTPROC; /** * 'q' packet processor. */ typedef struct GDBSTUBQPKTPROC { /** Name */ const char *pszName; /** Length of name in characters (without \0 terminator). */ uint32_t cchName; /** The callback to call for processing the particular query. */ PFNGDBSTUBQPKTPROC pfnProc; } GDBSTUBQPKTPROC; /** Pointer to a 'q' packet processor entry. */ typedef GDBSTUBQPKTPROC *PGDBSTUBQPKTPROC; /** Pointer to a const 'q' packet processor entry. */ typedef const GDBSTUBQPKTPROC *PCGDBSTUBQPKTPROC; /** * 'v' packet processor. */ typedef struct GDBSTUBVPKTPROC { /** Name */ const char *pszName; /** Length of name in characters (without \0 terminator). */ uint32_t cchName; /** Replay to a query packet (ends with ?). */ const char *pszReplyQ; /** Length of the query reply (without \0 terminator). */ uint32_t cchReplyQ; /** The callback to call for processing the particular query. */ PFNGDBSTUBQPKTPROC pfnProc; } GDBSTUBVPKTPROC; /** Pointer to a 'q' packet processor entry. */ typedef GDBSTUBVPKTPROC *PGDBSTUBVPKTPROC; /** Pointer to a const 'q' packet processor entry. */ typedef const GDBSTUBVPKTPROC *PCGDBSTUBVPKTPROC; /** * Feature callback. * * @returns Status code. * @param pThis The GDB stub context. * @param pbVal Pointer to the value. * @param cbVal Size of the value in bytes. */ typedef DECLCALLBACKTYPE(int, FNGDBSTUBFEATHND,(PGDBSTUBCTX pThis, const uint8_t *pbVal, size_t cbVal)); typedef FNGDBSTUBFEATHND *PFNGDBSTUBFEATHND; /** * GDB feature descriptor. */ typedef struct GDBSTUBFEATDESC { /** Feature name */ const char *pszName; /** Length of the feature name in characters (without \0 terminator). */ uint32_t cchName; /** The callback to call for processing the particular feature. */ PFNGDBSTUBFEATHND pfnHandler; /** Flag whether the feature requires a value. */ bool fVal; } GDBSTUBFEATDESC; /** Pointer to a GDB feature descriptor. */ typedef GDBSTUBFEATDESC *PGDBSTUBFEATDESC; /** Pointer to a const GDB feature descriptor. */ typedef const GDBSTUBFEATDESC *PCGDBSTUBFEATDESC; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ /** * Tries to find a trace point with the given parameters in the list of registered trace points. * * @returns Pointer to the trace point registration record if found or NULL if none was found. * @param pThis The GDB stub context. * @param enmTpType The trace point type. * @param GdbTgtAddr Target address given by GDB. * @param uKind Trace point kind. */ static PGDBSTUBTP dbgcGdbStubTpFind(PGDBSTUBCTX pThis, GDBSTUBTPTYPE enmTpType, uint64_t GdbTgtAddr, uint64_t uKind) { PGDBSTUBTP pTpCur = NULL; RTListForEach(&pThis->LstTps, pTpCur, GDBSTUBTP, NdTps) { if ( pTpCur->enmTpType == enmTpType && pTpCur->GdbTgtAddr == GdbTgtAddr && pTpCur->uKind == uKind) return pTpCur; } return NULL; } /** * Registers a new trace point. * * @returns VBox status code. * @param pThis The GDB stub context. * @param enmTpType The trace point type. * @param GdbTgtAddr Target address given by GDB. * @param uKind Trace point kind. * @param iBp The internal DBGF breakpoint ID this trace point was registered with. */ static int dbgcGdbStubTpRegister(PGDBSTUBCTX pThis, GDBSTUBTPTYPE enmTpType, uint64_t GdbTgtAddr, uint64_t uKind, uint32_t iBp) { int rc = VERR_ALREADY_EXISTS; /* Can't register a tracepoint with the same parameters twice or we can't decide whom to remove later on. */ PGDBSTUBTP pTp = dbgcGdbStubTpFind(pThis, enmTpType, GdbTgtAddr, uKind); if (!pTp) { pTp = (PGDBSTUBTP)RTMemAllocZ(sizeof(*pTp)); if (pTp) { pTp->enmTpType = enmTpType; pTp->GdbTgtAddr = GdbTgtAddr; pTp->uKind = uKind; pTp->iBp = iBp; RTListAppend(&pThis->LstTps, &pTp->NdTps); rc = VINF_SUCCESS; } else rc = VERR_NO_MEMORY; } return rc; } /** * Deregisters the given trace point (needs to be unregistered from DBGF by the caller before). * * @param pTp The trace point to deregister. */ static void dbgcGdbStubTpDeregister(PGDBSTUBTP pTp) { RTListNodeRemove(&pTp->NdTps); RTMemFree(pTp); } /** * Converts a given to the hexadecimal value if valid. * * @returns The hexadecimal value the given character represents 0-9,a-f,A-F or 0xff on error. * @param ch The character to convert. */ DECLINLINE(uint8_t) dbgcGdbStubCtxChrToHex(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; if (ch >= 'A' && ch <= 'F') return ch - 'A' + 0xa; if (ch >= 'a' && ch <= 'f') return ch - 'a' + 0xa; return 0xff; } /** * Converts a 4bit hex number to the appropriate character. * * @returns Character representing the 4bit hex number. * @param uHex The 4 bit hex number. */ DECLINLINE(char) dbgcGdbStubCtxHexToChr(uint8_t uHex) { if (uHex < 0xa) return '0' + uHex; if (uHex <= 0xf) return 'A' + uHex - 0xa; return 'X'; } /** * Wrapper for the I/O interface write callback. * * @returns Status code. * @param pThis The GDB stub context. * @param pvPkt The packet data to send. * @param cbPkt Size of the packet in bytes. */ DECLINLINE(int) dbgcGdbStubCtxWrite(PGDBSTUBCTX pThis, const void *pvPkt, size_t cbPkt) { return pThis->Dbgc.pIo->pfnWrite(pThis->Dbgc.pIo, pvPkt, cbPkt, NULL /*pcbWritten*/); } /** * Starts transmission of a new reply packet. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxReplySendBegin(PGDBSTUBCTX pThis) { pThis->uChkSumSend = 0; uint8_t chPktStart = GDBSTUB_PKT_START; return dbgcGdbStubCtxWrite(pThis, &chPktStart, sizeof(chPktStart)); } /** * Sends the given data in the reply. * * @returns Status code. * @param pThis The GDB stub context. * @param pvReplyData The reply data to send. * @param cbReplyData Size of the reply data in bytes. */ static int dbgcGdbStubCtxReplySendData(PGDBSTUBCTX pThis, const void *pvReplyData, size_t cbReplyData) { /* Update checksum. */ const uint8_t *pbData = (const uint8_t *)pvReplyData; for (uint32_t i = 0; i < cbReplyData; i++) pThis->uChkSumSend += pbData[i]; return dbgcGdbStubCtxWrite(pThis, pvReplyData, cbReplyData); } /** * Finishes transmission of the current reply by sending the packet end character and the checksum. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxReplySendEnd(PGDBSTUBCTX pThis) { uint8_t achPktEnd[3]; achPktEnd[0] = GDBSTUB_PKT_END; achPktEnd[1] = dbgcGdbStubCtxHexToChr(pThis->uChkSumSend >> 4); achPktEnd[2] = dbgcGdbStubCtxHexToChr(pThis->uChkSumSend & 0xf); return dbgcGdbStubCtxWrite(pThis, &achPktEnd[0], sizeof(achPktEnd)); } /** * Sends the given reply packet, doing the framing, checksumming, etc. in one call. * * @returns Status code. * @param pThis The GDB stub context. * @param pvReplyPkt The reply packet to send. * @param cbReplyPkt Size of the reply packet in bytes. */ static int dbgcGdbStubCtxReplySend(PGDBSTUBCTX pThis, const void *pvReplyPkt, size_t cbReplyPkt) { int rc = dbgcGdbStubCtxReplySendBegin(pThis); if (RT_SUCCESS(rc)) { rc = dbgcGdbStubCtxReplySendData(pThis, pvReplyPkt, cbReplyPkt); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendEnd(pThis); } return rc; } /** * Encodes the given buffer as a hexstring string it into the given destination buffer. * * @returns Status code. * @param pbDst Where store the resulting hex string on success. * @param cbDst Size of the destination buffer in bytes. * @param pvSrc The data to encode. * @param cbSrc Number of bytes to encode. */ DECLINLINE(int) dbgcGdbStubCtxEncodeBinaryAsHex(uint8_t *pbDst, size_t cbDst, const void *pvSrc, size_t cbSrc) { return RTStrPrintHexBytes((char *)pbDst, cbDst, pvSrc, cbSrc, RTSTRPRINTHEXBYTES_F_UPPER); } /** * Decodes the given ASCII hexstring as binary data up until the given separator is found or the end of the string is reached. * * @returns Status code. * @param pbBuf The buffer containing the hexstring to convert. * @param cbBuf Size of the buffer in bytes. * @param puVal Where to store the decoded integer. * @param chSep The character to stop conversion at. * @param ppbSep Where to store the pointer in the buffer where the separator was found, optional. */ static int dbgcGdbStubCtxParseHexStringAsInteger(const uint8_t *pbBuf, size_t cbBuf, uint64_t *puVal, uint8_t chSep, const uint8_t **ppbSep) { uint64_t uVal = 0; while ( cbBuf && *pbBuf != chSep) { uVal = uVal * 16 + dbgcGdbStubCtxChrToHex(*pbBuf++); cbBuf--; } *puVal = uVal; if (ppbSep) *ppbSep = pbBuf; return VINF_SUCCESS; } /** * Decodes the given ASCII hexstring as a byte buffer up until the given separator is found or the end of the string is reached. * * @returns Status code. * @param pbBuf The buffer containing the hexstring to convert. * @param cbBuf Size of the buffer in bytes. * @param pvDst Where to store the decoded data. * @param cbDst Maximum buffer size in bytes. * @param pcbDecoded Where to store the number of consumed bytes from the input. */ DECLINLINE(int) dbgcGdbStubCtxParseHexStringAsByteBuf(const uint8_t *pbBuf, size_t cbBuf, void *pvDst, size_t cbDst, size_t *pcbDecoded) { size_t cbDecode = RT_MIN(cbBuf, cbDst * 2); if (pcbDecoded) *pcbDecoded = cbDecode; return RTStrConvertHexBytes((const char *)pbBuf, pvDst, cbDecode, 0 /* fFlags*/); } #if 0 /*unused for now*/ /** * Sends a 'OK' part of a reply packet only (packet start and end needs to be handled separately). * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxReplySendOkData(PGDBSTUBCTX pThis) { char achOk[2] = { 'O', 'K' }; return dbgcGdbStubCtxReplySendData(pThis, &achOk[0], sizeof(achOk)); } #endif /** * Sends a 'OK' reply packet. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxReplySendOk(PGDBSTUBCTX pThis) { char achOk[2] = { 'O', 'K' }; return dbgcGdbStubCtxReplySend(pThis, &achOk[0], sizeof(achOk)); } #if 0 /*unused for now*/ /** * Sends a 'E NN' part of a reply packet only (packet start and end needs to be handled separately). * * @returns Status code. * @param pThis The GDB stub context. * @param uErr The error code to send. */ static int dbgcGdbStubCtxReplySendErrData(PGDBSTUBCTX pThis, uint8_t uErr) { char achErr[3] = { 'E', 0, 0 }; achErr[1] = dbgcGdbStubCtxHexToChr(uErr >> 4); achErr[2] = dbgcGdbStubCtxHexToChr(uErr & 0xf); return dbgcGdbStubCtxReplySendData(pThis, &achErr[0], sizeof(achErr)); } #endif /** * Sends a 'E NN' reply packet. * * @returns Status code. * @param pThis The GDB stub context. * @param uErr The error code to send. */ static int dbgcGdbStubCtxReplySendErr(PGDBSTUBCTX pThis, uint8_t uErr) { char achErr[3] = { 'E', 0, 0 }; achErr[1] = dbgcGdbStubCtxHexToChr(uErr >> 4); achErr[2] = dbgcGdbStubCtxHexToChr(uErr & 0xf); return dbgcGdbStubCtxReplySend(pThis, &achErr[0], sizeof(achErr)); } /** * Sends a signal trap (S 05) packet to indicate that the target has stopped. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxReplySendSigTrap(PGDBSTUBCTX pThis) { char achReply[32]; ssize_t cchStr = RTStrPrintf2(&achReply[0], sizeof(achReply), "T05thread:%02x;", pThis->Dbgc.idCpu + 1); return dbgcGdbStubCtxReplySend(pThis, &achReply[0], cchStr); } /** * Sends a GDB stub status code indicating an error using the error reply packet. * * @returns Status code. * @param pThis The GDB stub context. * @param rc The status code to send. */ static int dbgcGdbStubCtxReplySendErrSts(PGDBSTUBCTX pThis, int rc) { /** @todo convert error codes maybe. */ return dbgcGdbStubCtxReplySendErr(pThis, (-rc) & 0xff); } /** * Ensures that there is at least the given amount of bytes of free space left in the packet buffer. * * @returns Status code (error when increasing the buffer failed). * @param pThis The GDB stub context. * @param cbSpace Number of bytes required. */ static int dbgcGdbStubCtxEnsurePktBufSpace(PGDBSTUBCTX pThis, size_t cbSpace) { if (pThis->cbPktBufMax - pThis->offPktBuf >= cbSpace) return VINF_SUCCESS; /* Slow path allocate new buffer and copy content over. */ int rc = VINF_SUCCESS; size_t cbPktBufMaxNew = pThis->cbPktBufMax + cbSpace; void *pvNew = RTMemRealloc(pThis->pbPktBuf, cbPktBufMaxNew); if (pvNew) { pThis->pbPktBuf = (uint8_t *)pvNew; pThis->cbPktBufMax = cbPktBufMaxNew; } else rc = VERR_NO_MEMORY; return rc; } /** * Parses the arguments of a 'Z' and 'z' packet. * * @returns Status code. * @param pbArgs Pointer to the start of the first argument. * @param cbArgs Number of argument bytes. * @param penmTpType Where to store the tracepoint type on success. * @param pGdbTgtAddr Where to store the address on success. * @param puKind Where to store the kind argument on success. */ static int dbgcGdbStubCtxParseTpPktArgs(const uint8_t *pbArgs, size_t cbArgs, GDBSTUBTPTYPE *penmTpType, uint64_t *pGdbTgtAddr, uint64_t *puKind) { const uint8_t *pbPktSep = NULL; uint64_t uType = 0; int rc = dbgcGdbStubCtxParseHexStringAsInteger(pbArgs, cbArgs, &uType, ',', &pbPktSep); if (RT_SUCCESS(rc)) { cbArgs -= (uintptr_t)(pbPktSep - pbArgs) - 1; rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, cbArgs, pGdbTgtAddr, ',', &pbPktSep); if (RT_SUCCESS(rc)) { cbArgs -= (uintptr_t)(pbPktSep - pbArgs) - 1; rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, cbArgs, puKind, GDBSTUB_PKT_END, NULL); if (RT_SUCCESS(rc)) { switch (uType) { case 0: *penmTpType = GDBSTUBTPTYPE_EXEC_SW; break; case 1: *penmTpType = GDBSTUBTPTYPE_EXEC_HW; break; case 2: *penmTpType = GDBSTUBTPTYPE_MEM_WRITE; break; case 3: *penmTpType = GDBSTUBTPTYPE_MEM_READ; break; case 4: *penmTpType = GDBSTUBTPTYPE_MEM_ACCESS; break; default: rc = VERR_INVALID_PARAMETER; break; } } } } return rc; } /** * Processes the 'TStatus' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryTStatus(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { RT_NOREF(pbArgs, cbArgs); char achReply[2] = { 'T', '0' }; return dbgcGdbStubCtxReplySend(pThis, &achReply[0], sizeof(achReply)); } /** * @copydoc FNGDBSTUBQPKTPROC */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessFeatXmlRegs(PGDBSTUBCTX pThis, const uint8_t *pbVal, size_t cbVal) { /* * xmlRegisters contain a list of supported architectures delimited by ','. * Check that the architecture is in the supported list. */ while (cbVal) { /* Find the next delimiter. */ size_t cbThisVal = cbVal; const uint8_t *pbDelim = (const uint8_t *)memchr(pbVal, ',', cbVal); if (pbDelim) cbThisVal = pbDelim - pbVal; const size_t cchArch64 = sizeof("i386:x86-64") - 1; const size_t cchArch32 = sizeof("i386") - 1; if ( !memcmp(pbVal, "i386:x86-64", RT_MIN(cbVal, cchArch64)) || !memcmp(pbVal, "i386", RT_MIN(cbVal, cchArch32))) { /* Set the flag to support the qXfer:features:read packet. */ pThis->fFeatures |= GDBSTUBCTX_FEATURES_F_TGT_DESC; break; } cbVal -= cbThisVal + (pbDelim ? 1 : 0); pbVal = pbDelim + (pbDelim ? 1 : 0); } return VINF_SUCCESS; } /** * Features which can be reported by the remote GDB which we might support. * * @note The sorting matters for features which start the same, the longest must come first. */ static const GDBSTUBFEATDESC g_aGdbFeatures[] = { #define GDBSTUBFEATDESC_INIT(a_Name, a_pfnHnd, a_fVal) { a_Name, sizeof(a_Name) - 1, a_pfnHnd, a_fVal } GDBSTUBFEATDESC_INIT("xmlRegisters", dbgcGdbStubCtxPktProcessFeatXmlRegs, true), #undef GDBSTUBFEATDESC_INIT }; /** * Calculates the feature length of the next feature pointed to by the given arguments buffer. * * @returns Status code. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. * @param pcbArg Where to store the size of the argument in bytes on success (excluding the delimiter). * @param pfTerminator Whereto store the flag whether the packet terminator (#) was seen as a delimiter. */ static int dbgcGdbStubCtxQueryPktQueryFeatureLen(const uint8_t *pbArgs, size_t cbArgs, size_t *pcbArg, bool *pfTerminator) { const uint8_t *pbArgCur = pbArgs; while ( cbArgs && *pbArgCur != ';' && *pbArgCur != GDBSTUB_PKT_END) { cbArgs--; pbArgCur++; } if ( !cbArgs && *pbArgCur != ';' && *pbArgCur != GDBSTUB_PKT_END) return VERR_NET_PROTOCOL_ERROR; *pcbArg = pbArgCur - pbArgs; *pfTerminator = *pbArgCur == GDBSTUB_PKT_END ? true : false; return VINF_SUCCESS; } /** * Sends the reply to the 'qSupported' packet. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxPktProcessQuerySupportedReply(PGDBSTUBCTX pThis) { /** @todo Enhance. */ if (pThis->fFeatures & GDBSTUBCTX_FEATURES_F_TGT_DESC) return dbgcGdbStubCtxReplySend(pThis, "qXfer:features:read+;vContSupported+", sizeof("qXfer:features:read+;vContSupported+") - 1); return dbgcGdbStubCtxReplySend(pThis, NULL, 0); } /** * Processes the 'Supported' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQuerySupported(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { /* Skip the : following the qSupported start. */ if ( cbArgs < 1 || pbArgs[0] != ':') return VERR_NET_PROTOCOL_ERROR; cbArgs--; pbArgs++; /* * Each feature but the last one are separated by ; and the last one is delimited by the # packet end symbol. * We first determine the boundaries of the reported feature and pass it to the appropriate handler. */ int rc = VINF_SUCCESS; while ( cbArgs && RT_SUCCESS(rc)) { bool fTerminator = false; size_t cbArg = 0; rc = dbgcGdbStubCtxQueryPktQueryFeatureLen(pbArgs, cbArgs, &cbArg, &fTerminator); if (RT_SUCCESS(rc)) { /* Search for the feature handler. */ for (uint32_t i = 0; i < RT_ELEMENTS(g_aGdbFeatures); i++) { PCGDBSTUBFEATDESC pFeatDesc = &g_aGdbFeatures[i]; if ( cbArg > pFeatDesc->cchName /* At least one character must come after the feature name ('+', '-' or '='). */ && !memcmp(pFeatDesc->pszName, pbArgs, pFeatDesc->cchName)) { /* Found, execute handler after figuring out whether there is a value attached. */ const uint8_t *pbVal = pbArgs + pFeatDesc->cchName; size_t cbVal = cbArg - pFeatDesc->cchName; if (pFeatDesc->fVal) { if ( *pbVal == '=' && cbVal > 1) { pbVal++; cbVal--; } else rc = VERR_NET_PROTOCOL_ERROR; } else if ( cbVal != 1 || ( *pbVal != '+' && *pbVal != '-')) /* '+' and '-' are allowed to indicate support for a particular feature. */ rc = VERR_NET_PROTOCOL_ERROR; if (RT_SUCCESS(rc)) rc = pFeatDesc->pfnHandler(pThis, pbVal, cbVal); break; } } cbArgs -= cbArg; pbArgs += cbArg; if (!fTerminator) { cbArgs--; pbArgs++; } else break; } } /* If everything went alright send the reply with our supported features. */ if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxPktProcessQuerySupportedReply(pThis); return rc; } /** * Sends the reply to a 'qXfer:object:read:...' request. * * @returns Status code. * @param pThis The GDB stub context. * @param offRead Where to start reading from within the object. * @param cbRead How much to read. * @param pbObj The start of the object. * @param cbObj Size of the object. */ static int dbgcGdbStubCtxQueryXferReadReply(PGDBSTUBCTX pThis, uint32_t offRead, size_t cbRead, const uint8_t *pbObj, size_t cbObj) { int rc = VINF_SUCCESS; if (offRead < cbObj) { /** @todo Escaping */ size_t cbThisRead = offRead + cbRead < cbObj ? cbRead : cbObj - offRead; rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbThisRead + 1); if (RT_SUCCESS(rc)) { uint8_t *pbPktBuf = pThis->pbPktBuf; *pbPktBuf++ = cbThisRead < cbRead ? 'l' : 'm'; memcpy(pbPktBuf, pbObj + offRead, cbThisRead); rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbThisRead + 1); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NO_MEMORY); } else if (offRead == cbObj) rc = dbgcGdbStubCtxReplySend(pThis, "l", sizeof("l") - 1); else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); return rc; } /** * Parses the annex:offset,length part of a 'qXfer:object:read:...' request. * * @returns Status code. * @param pbArgs Start of the arguments beginning with annex. * @param cbArgs Number of bytes remaining for the arguments. * @param ppchAnnex Where to store the pointer to the beginning of the annex on success. * @param pcchAnnex Where to store the number of characters for the annex on success. * @param poffRead Where to store the offset on success. * @param pcbRead Where to store the length on success. */ static int dbgcGdbStubCtxPktProcessQueryXferParseAnnexOffLen(const uint8_t *pbArgs, size_t cbArgs, const char **ppchAnnex, size_t *pcchAnnex, uint32_t *poffRead, size_t *pcbRead) { int rc = VINF_SUCCESS; const uint8_t *pbSep = (const uint8_t *)memchr(pbArgs, ':', cbArgs); if (pbSep) { *ppchAnnex = (const char *)pbArgs; *pcchAnnex = pbSep - pbArgs; pbSep++; cbArgs -= *pcchAnnex + 1; uint64_t u64Tmp = 0; const uint8_t *pbLenStart = NULL; rc = dbgcGdbStubCtxParseHexStringAsInteger(pbSep, cbArgs, &u64Tmp, ',', &pbLenStart); if ( RT_SUCCESS(rc) && (uint32_t)u64Tmp == u64Tmp) { *poffRead = (uint32_t)u64Tmp; cbArgs -= pbLenStart - pbSep; rc = dbgcGdbStubCtxParseHexStringAsInteger(pbLenStart + 1, cbArgs, &u64Tmp, '#', &pbLenStart); if ( RT_SUCCESS(rc) && (size_t)u64Tmp == u64Tmp) *pcbRead = (size_t)u64Tmp; else rc = VERR_NET_PROTOCOL_ERROR; } else rc = VERR_NET_PROTOCOL_ERROR; } else rc = VERR_NET_PROTOCOL_ERROR; return rc; } #define DBGREG_DESC_INIT_INT64(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 64, "int64", NULL } #define DBGREG_DESC_INIT_INT32(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "int32", NULL } #define DBGREG_DESC_INIT_DATA_PTR64(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 64, "data_ptr", NULL } #define DBGREG_DESC_INIT_CODE_PTR64(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 64, "code_ptr", NULL } #define DBGREG_DESC_INIT_DATA_PTR32(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "data_ptr", NULL } #define DBGREG_DESC_INIT_CODE_PTR32(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "code_ptr", NULL } #define DBGREG_DESC_INIT_X87(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 80, "i387_ext", NULL } #define DBGREG_DESC_INIT_X87_CTRL(a_Name, a_enmDbgfReg) { a_Name, a_enmDbgfReg, 32, "int", "float" } /** * amd64 GDB register set. */ static const GDBREGDESC g_aGdbRegs64[] = { DBGREG_DESC_INIT_INT64( "rax", DBGFREG_RAX), DBGREG_DESC_INIT_INT64( "rbx", DBGFREG_RBX), DBGREG_DESC_INIT_INT64( "rcx", DBGFREG_RCX), DBGREG_DESC_INIT_INT64( "rdx", DBGFREG_RDX), DBGREG_DESC_INIT_INT64( "rsi", DBGFREG_RSI), DBGREG_DESC_INIT_INT64( "rdi", DBGFREG_RDI), DBGREG_DESC_INIT_DATA_PTR64("rbp", DBGFREG_RBP), DBGREG_DESC_INIT_DATA_PTR64("rsp", DBGFREG_RSP), DBGREG_DESC_INIT_INT64( "r8", DBGFREG_R8), DBGREG_DESC_INIT_INT64( "r9", DBGFREG_R9), DBGREG_DESC_INIT_INT64( "r10", DBGFREG_R10), DBGREG_DESC_INIT_INT64( "r11", DBGFREG_R11), DBGREG_DESC_INIT_INT64( "r12", DBGFREG_R12), DBGREG_DESC_INIT_INT64( "r13", DBGFREG_R13), DBGREG_DESC_INIT_INT64( "r14", DBGFREG_R14), DBGREG_DESC_INIT_INT64( "r15", DBGFREG_R15), DBGREG_DESC_INIT_CODE_PTR64("rip", DBGFREG_RIP), DBGREG_DESC_INIT_INT32( "eflags", DBGFREG_FLAGS), DBGREG_DESC_INIT_INT32( "cs", DBGFREG_CS), DBGREG_DESC_INIT_INT32( "ss", DBGFREG_SS), DBGREG_DESC_INIT_INT32( "ds", DBGFREG_DS), DBGREG_DESC_INIT_INT32( "es", DBGFREG_ES), DBGREG_DESC_INIT_INT32( "fs", DBGFREG_FS), DBGREG_DESC_INIT_INT32( "gs", DBGFREG_GS), DBGREG_DESC_INIT_X87( "st0", DBGFREG_ST0), DBGREG_DESC_INIT_X87( "st1", DBGFREG_ST1), DBGREG_DESC_INIT_X87( "st2", DBGFREG_ST2), DBGREG_DESC_INIT_X87( "st3", DBGFREG_ST3), DBGREG_DESC_INIT_X87( "st4", DBGFREG_ST4), DBGREG_DESC_INIT_X87( "st5", DBGFREG_ST5), DBGREG_DESC_INIT_X87( "st6", DBGFREG_ST6), DBGREG_DESC_INIT_X87( "st7", DBGFREG_ST7), DBGREG_DESC_INIT_X87_CTRL( "fctrl", DBGFREG_FCW), DBGREG_DESC_INIT_X87_CTRL( "fstat", DBGFREG_FSW), DBGREG_DESC_INIT_X87_CTRL( "ftag", DBGFREG_FTW), DBGREG_DESC_INIT_X87_CTRL( "fop", DBGFREG_FOP), DBGREG_DESC_INIT_X87_CTRL( "fioff", DBGFREG_FPUIP), DBGREG_DESC_INIT_X87_CTRL( "fiseg", DBGFREG_FPUCS), DBGREG_DESC_INIT_X87_CTRL( "fooff", DBGFREG_FPUDP), DBGREG_DESC_INIT_X87_CTRL( "foseg", DBGFREG_FPUDS) }; /** * i386 GDB register set. */ static const GDBREGDESC g_aGdbRegs32[] = { DBGREG_DESC_INIT_INT32( "eax", DBGFREG_EAX), DBGREG_DESC_INIT_INT32( "ebx", DBGFREG_EBX), DBGREG_DESC_INIT_INT32( "ecx", DBGFREG_ECX), DBGREG_DESC_INIT_INT32( "edx", DBGFREG_EDX), DBGREG_DESC_INIT_INT32( "esi", DBGFREG_ESI), DBGREG_DESC_INIT_INT32( "edi", DBGFREG_EDI), DBGREG_DESC_INIT_DATA_PTR32("ebp", DBGFREG_EBP), DBGREG_DESC_INIT_DATA_PTR32("esp", DBGFREG_ESP), DBGREG_DESC_INIT_CODE_PTR32("eip", DBGFREG_EIP), DBGREG_DESC_INIT_INT32( "eflags", DBGFREG_FLAGS), DBGREG_DESC_INIT_INT32( "cs", DBGFREG_CS), DBGREG_DESC_INIT_INT32( "ss", DBGFREG_SS), DBGREG_DESC_INIT_INT32( "ds", DBGFREG_DS), DBGREG_DESC_INIT_INT32( "es", DBGFREG_ES), DBGREG_DESC_INIT_INT32( "fs", DBGFREG_FS), DBGREG_DESC_INIT_INT32( "gs", DBGFREG_GS), DBGREG_DESC_INIT_X87( "st0", DBGFREG_ST0), DBGREG_DESC_INIT_X87( "st1", DBGFREG_ST1), DBGREG_DESC_INIT_X87( "st2", DBGFREG_ST2), DBGREG_DESC_INIT_X87( "st3", DBGFREG_ST3), DBGREG_DESC_INIT_X87( "st4", DBGFREG_ST4), DBGREG_DESC_INIT_X87( "st5", DBGFREG_ST5), DBGREG_DESC_INIT_X87( "st6", DBGFREG_ST6), DBGREG_DESC_INIT_X87( "st7", DBGFREG_ST7), DBGREG_DESC_INIT_X87_CTRL( "fctrl", DBGFREG_FCW), DBGREG_DESC_INIT_X87_CTRL( "fstat", DBGFREG_FSW), DBGREG_DESC_INIT_X87_CTRL( "ftag", DBGFREG_FTW), DBGREG_DESC_INIT_X87_CTRL( "fop", DBGFREG_FOP), DBGREG_DESC_INIT_X87_CTRL( "fioff", DBGFREG_FPUIP), DBGREG_DESC_INIT_X87_CTRL( "fiseg", DBGFREG_FPUCS), DBGREG_DESC_INIT_X87_CTRL( "fooff", DBGFREG_FPUDP), DBGREG_DESC_INIT_X87_CTRL( "foseg", DBGFREG_FPUDS) }; #undef DBGREG_DESC_INIT_CODE_PTR64 #undef DBGREG_DESC_INIT_DATA_PTR64 #undef DBGREG_DESC_INIT_CODE_PTR32 #undef DBGREG_DESC_INIT_DATA_PTR32 #undef DBGREG_DESC_INIT_INT32 #undef DBGREG_DESC_INIT_INT64 #undef DBGREG_DESC_INIT_X87 #undef DBGREG_DESC_INIT_X87_CTRL /** * Creates the target XML description. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxTgtXmlDescCreate(PGDBSTUBCTX pThis) { static const char s_szXmlTgtHdr64[] = "\n" "\n" "\n" " i386:x86-64\n" " \n"; static const char s_szXmlTgtHdr32[] = "\n" "\n" "\n" " i386\n" " \n"; static const char s_szXmlTgtFooter[] = " \n" "\n"; int rc = VINF_SUCCESS; pThis->pachTgtXmlDesc = (char *)RTStrAlloc(_32K); if (pThis->pachTgtXmlDesc) { size_t cbLeft = _32K; char *pachXmlCur = pThis->pachTgtXmlDesc; pThis->cbTgtXmlDesc = cbLeft; rc = RTStrCatP(&pachXmlCur, &cbLeft, pThis->paRegs == &g_aGdbRegs64[0] ? &s_szXmlTgtHdr64[0] : &s_szXmlTgtHdr32[0]); if (RT_SUCCESS(rc)) { /* Register */ for (uint32_t i = 0; i < pThis->cRegs && RT_SUCCESS(rc); i++) { const struct GDBREGDESC *pReg = &pThis->paRegs[i]; ssize_t cchStr = 0; if (pReg->pszGroup) cchStr = RTStrPrintf2(pachXmlCur, cbLeft, "\n", pReg->pszName, pReg->cBits, i, pReg->pszType, pReg->pszGroup); else cchStr = RTStrPrintf2(pachXmlCur, cbLeft, "\n", pReg->pszName, pReg->cBits, i, pReg->pszType); if (cchStr > 0) { pachXmlCur += cchStr; cbLeft -= cchStr; } else rc = VERR_BUFFER_OVERFLOW; } } if (RT_SUCCESS(rc)) rc = RTStrCatP(&pachXmlCur, &cbLeft, &s_szXmlTgtFooter[0]); pThis->cbTgtXmlDesc -= cbLeft; } else rc = VERR_NO_MEMORY; return rc; } /** * Returns the GDB register descriptor describing the given DBGF register enum. * * @returns Pointer to the GDB register descriptor or NULL if not found. * @param pThis The GDB stub context. * @param idxReg The register to look for. */ static const GDBREGDESC *dbgcGdbStubRegGet(PGDBSTUBCTX pThis, uint32_t idxReg) { if (RT_LIKELY(idxReg < pThis->cRegs)) return &pThis->paRegs[idxReg]; return NULL; } /** * Processes the 'C' query (query current thread ID). * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadId(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { RT_NOREF(pbArgs, cbArgs); int rc = VERR_BUFFER_OVERFLOW; char achReply[32]; ssize_t cchStr = RTStrPrintf(&achReply[0], sizeof(achReply), "QC %02x", pThis->Dbgc.idCpu + 1); if (cchStr > 0) rc = dbgcGdbStubCtxReplySend(pThis, &achReply[0], cchStr); return rc; } /** * Processes the 'Attached' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryAttached(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { RT_NOREF(pbArgs, cbArgs); /* We always report attached so that the VM doesn't get killed when GDB quits. */ uint8_t bAttached = '1'; return dbgcGdbStubCtxReplySend(pThis, &bAttached, sizeof(bAttached)); } /** * Processes the 'Xfer:features:read' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryXferFeatRead(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { /* Skip the : following the Xfer:features:read start. */ if ( cbArgs < 1 || pbArgs[0] != ':') return VERR_NET_PROTOCOL_ERROR; cbArgs--; pbArgs++; int rc = VINF_SUCCESS; if (pThis->fFeatures & GDBSTUBCTX_FEATURES_F_TGT_DESC) { /* Create the target XML description if not existing. */ if (!pThis->pachTgtXmlDesc) rc = dbgcGdbStubCtxTgtXmlDescCreate(pThis); if (RT_SUCCESS(rc)) { /* Parse annex, offset and length and return the data. */ const char *pchAnnex = NULL; size_t cchAnnex = 0; uint32_t offRead = 0; size_t cbRead = 0; rc = dbgcGdbStubCtxPktProcessQueryXferParseAnnexOffLen(pbArgs, cbArgs, &pchAnnex, &cchAnnex, &offRead, &cbRead); if (RT_SUCCESS(rc)) { /* Check whether the annex is supported. */ if ( cchAnnex == sizeof("target.xml") - 1 && !memcmp(pchAnnex, "target.xml", cchAnnex)) rc = dbgcGdbStubCtxQueryXferReadReply(pThis, offRead, cbRead, (const uint8_t *)pThis->pachTgtXmlDesc, pThis->cbTgtXmlDesc); else rc = dbgcGdbStubCtxReplySendErr(pThis, 0); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0); /* Not supported. */ return rc; } /** * Processes the 'Rcmd' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryRcmd(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { /* Skip the , following the qRcmd start. */ if ( cbArgs < 1 || pbArgs[0] != ',') return VERR_NET_PROTOCOL_ERROR; cbArgs--; pbArgs++; /* Decode the command. */ /** @todo Make this dynamic. */ char szCmd[_4K]; RT_ZERO(szCmd); if (cbArgs / 2 >= sizeof(szCmd)) return VERR_NET_PROTOCOL_ERROR; size_t cbDecoded = 0; int rc = RTStrConvertHexBytesEx((const char *)pbArgs, &szCmd[0], sizeof(szCmd), 0 /*fFlags*/, NULL /* ppszNext */, &cbDecoded); if (rc == VWRN_TRAILING_CHARS) rc = VINF_SUCCESS; if (RT_SUCCESS(rc)) { szCmd[cbDecoded] = '\0'; /* Ensure zero termination. */ pThis->fOutput = false; rc = dbgcEvalCommand(&pThis->Dbgc, &szCmd[0], cbDecoded - 1, false /*fNoExecute*/); dbgcGdbStubCtxReplySendOk(pThis); if ( rc != VERR_DBGC_QUIT && rc != VWRN_DBGC_CMD_PENDING) rc = VINF_SUCCESS; /* ignore other statuses */ } return rc; } /** * Worker for both 'qfThreadInfo' and 'qsThreadInfo'. * * @returns VBox status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxPktProcessQueryThreadInfoWorker(PGDBSTUBCTX pThis) { int rc = dbgcGdbStubCtxReplySendBegin(pThis); if (RT_SUCCESS(rc)) { uint8_t bReplyStart = { 'm' }; rc = dbgcGdbStubCtxReplySendData(pThis, &bReplyStart, sizeof(bReplyStart)); if (RT_SUCCESS(rc)) { char achReply[32]; ssize_t cchStr = RTStrPrintf(&achReply[0], sizeof(achReply), "%02x", pThis->idCpuNextThrdInfoQuery + 1); if (cchStr <= 0) rc = VERR_BUFFER_OVERFLOW; if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendData(pThis, &achReply[0], cchStr); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendEnd(pThis); pThis->idCpuNextThrdInfoQuery++; } } return rc; } /** * Processes the 'fThreadInfo' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadInfoStart(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { RT_NOREF(pbArgs, cbArgs); pThis->idCpuNextThrdInfoQuery = 0; pThis->fInThrdInfoQuery = true; return dbgcGdbStubCtxPktProcessQueryThreadInfoWorker(pThis); } /** * Processes the 'fThreadInfo' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadInfoCont(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { RT_NOREF(pbArgs, cbArgs); /* If we are in a thread info query we just send the end of list specifier (all thread IDs where sent previously already). */ if (!pThis->fInThrdInfoQuery) return dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); VMCPUID cCpus = DBGFR3CpuGetCount(pThis->Dbgc.pUVM); if (pThis->idCpuNextThrdInfoQuery == cCpus) { pThis->fInThrdInfoQuery = false; uint8_t bEoL = 'l'; return dbgcGdbStubCtxReplySend(pThis, &bEoL, sizeof(bEoL)); } return dbgcGdbStubCtxPktProcessQueryThreadInfoWorker(pThis); } /** * Processes the 'ThreadExtraInfo' query. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessQueryThreadExtraInfo(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { /* Skip the , following the qThreadExtraInfo start. */ if ( cbArgs < 1 || pbArgs[0] != ',') return VERR_NET_PROTOCOL_ERROR; /*cbArgs--;*/ /* Not used */ pbArgs++; /* We know there is an # character denoting the end so the following must return with VWRN_TRAILING_CHARS. */ VMCPUID idCpu; int rc = RTStrToUInt32Ex((const char *)pbArgs, NULL /*ppszNext*/, 16, &idCpu); if ( rc == VWRN_TRAILING_CHARS && idCpu > 0) { idCpu--; VMCPUID cCpus = DBGFR3CpuGetCount(pThis->Dbgc.pUVM); if (idCpu < cCpus) { const char *pszCpuState = DBGFR3CpuGetState(pThis->Dbgc.pUVM, idCpu); size_t cchCpuState = strlen(pszCpuState); if (!pszCpuState) pszCpuState = "DBGFR3CpuGetState() -> NULL"; rc = dbgcGdbStubCtxReplySendBegin(pThis); if (RT_SUCCESS(rc)) { /* Convert the characters to hex. */ const char *pachCur = pszCpuState; while ( cchCpuState && RT_SUCCESS(rc)) { uint8_t achHex[512 + 1]; size_t cbThisSend = RT_MIN((sizeof(achHex) - 1) / 2, cchCpuState); /* Each character needs two bytes. */ rc = dbgcGdbStubCtxEncodeBinaryAsHex(&achHex[0], cbThisSend * 2 + 1, pachCur, cbThisSend); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendData(pThis, &achHex[0], cbThisSend * 2); pachCur += cbThisSend; cchCpuState -= cbThisSend; } dbgcGdbStubCtxReplySendEnd(pThis); } } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); } else if ( RT_SUCCESS(rc) || !idCpu) rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); return rc; } /** * List of supported query packets. */ static const GDBSTUBQPKTPROC g_aQPktProcs[] = { #define GDBSTUBQPKTPROC_INIT(a_Name, a_pfnProc) { a_Name, sizeof(a_Name) - 1, a_pfnProc } GDBSTUBQPKTPROC_INIT("C", dbgcGdbStubCtxPktProcessQueryThreadId), GDBSTUBQPKTPROC_INIT("Attached", dbgcGdbStubCtxPktProcessQueryAttached), GDBSTUBQPKTPROC_INIT("TStatus", dbgcGdbStubCtxPktProcessQueryTStatus), GDBSTUBQPKTPROC_INIT("Supported", dbgcGdbStubCtxPktProcessQuerySupported), GDBSTUBQPKTPROC_INIT("Xfer:features:read", dbgcGdbStubCtxPktProcessQueryXferFeatRead), GDBSTUBQPKTPROC_INIT("Rcmd", dbgcGdbStubCtxPktProcessQueryRcmd), GDBSTUBQPKTPROC_INIT("fThreadInfo", dbgcGdbStubCtxPktProcessQueryThreadInfoStart), GDBSTUBQPKTPROC_INIT("sThreadInfo", dbgcGdbStubCtxPktProcessQueryThreadInfoCont), GDBSTUBQPKTPROC_INIT("ThreadExtraInfo", dbgcGdbStubCtxPktProcessQueryThreadExtraInfo), #undef GDBSTUBQPKTPROC_INIT }; /** * Processes a 'q' packet, sending the appropriate reply. * * @returns Status code. * @param pThis The GDB stub context. * @param pbQuery The query packet data (without the 'q'). * @param cbQuery Size of the remaining query packet in bytes. */ static int dbgcGdbStubCtxPktProcessQuery(PGDBSTUBCTX pThis, const uint8_t *pbQuery, size_t cbQuery) { /* Search the query and execute the processor or return an empty reply if not supported. */ for (uint32_t i = 0; i < RT_ELEMENTS(g_aQPktProcs); i++) { size_t cbCmp = g_aQPktProcs[i].cchName < cbQuery ? g_aQPktProcs[i].cchName : cbQuery; if (!memcmp(pbQuery, g_aQPktProcs[i].pszName, cbCmp)) return g_aQPktProcs[i].pfnProc(pThis, pbQuery + cbCmp, cbQuery - cbCmp); } return dbgcGdbStubCtxReplySend(pThis, NULL, 0); } /** * Processes a 'vCont[;action[:thread-id]]' packet. * * @returns Status code. * @param pThis The GDB stub context. * @param pbArgs Pointer to the start of the arguments in the packet. * @param cbArgs Size of arguments in bytes. */ static DECLCALLBACK(int) dbgcGdbStubCtxPktProcessVCont(PGDBSTUBCTX pThis, const uint8_t *pbArgs, size_t cbArgs) { int rc = VINF_SUCCESS; /* Skip the ; following the identifier. */ if ( cbArgs < 2 || pbArgs[0] != ';') return dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); pbArgs++; /*cbArgs--;*/ /* Not used */ /** @todo For now we don't care about multiple threads and ignore thread IDs and multiple actions. */ switch (pbArgs[0]) { case 'c': { if (DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL)) DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL); break; } case 's': { PDBGFADDRESS pStackPop = NULL; RTGCPTR cbStackPop = 0; rc = DBGFR3StepEx(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGF_STEP_F_INTO, NULL, pStackPop, cbStackPop, 1 /*cMaxSteps*/); if (RT_FAILURE(rc)) dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 't': { if (!DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL)) rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL); /* The reply will be send in the event loop. */ break; } default: rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); } return rc; } /** * List of supported 'v' packets. */ static const GDBSTUBVPKTPROC g_aVPktProcs[] = { #define GDBSTUBVPKTPROC_INIT(a_Name, a_pszReply, a_pfnProc) { a_Name, sizeof(a_Name) - 1, a_pszReply, sizeof(a_pszReply) - 1, a_pfnProc } GDBSTUBVPKTPROC_INIT("Cont", "vCont;s;c;t", dbgcGdbStubCtxPktProcessVCont) #undef GDBSTUBVPKTPROC_INIT }; /** * Processes a 'v' packet, sending the appropriate reply. * * @returns Status code. * @param pThis The GDB stub context. * @param pbPktRem The remaining packet data (without the 'v'). * @param cbPktRem Size of the remaining packet in bytes. */ static int dbgcGdbStubCtxPktProcessV(PGDBSTUBCTX pThis, const uint8_t *pbPktRem, size_t cbPktRem) { /* Determine the end of the identifier, delimiters are '?', ';' or end of packet. */ bool fQuery = false; const uint8_t *pbDelim = (const uint8_t *)memchr(pbPktRem, '?', cbPktRem); if (!pbDelim) pbDelim = (const uint8_t *)memchr(pbPktRem, ';', cbPktRem); else fQuery = true; size_t cchId = 0; if (pbDelim) /* Delimiter found, calculate length. */ cchId = pbDelim - pbPktRem; else /* Not found, size goes till end of packet. */ cchId = cbPktRem; /* Search the query and execute the processor or return an empty reply if not supported. */ for (uint32_t i = 0; i < RT_ELEMENTS(g_aVPktProcs); i++) { PCGDBSTUBVPKTPROC pVProc = &g_aVPktProcs[i]; if ( pVProc->cchName == cchId && !memcmp(pbPktRem, pVProc->pszName, cchId)) { /* Just send the static reply for a query and execute the processor for everything else. */ if (fQuery) return dbgcGdbStubCtxReplySend(pThis, pVProc->pszReplyQ, pVProc->cchReplyQ); /* Execute the handler. */ return pVProc->pfnProc(pThis, pbPktRem + cchId, cbPktRem - cchId); } } return dbgcGdbStubCtxReplySend(pThis, NULL, 0); } /** * Processes a 'H' packet, sending the appropriate reply. * * @returns Status code. * @param pThis The GDB stub context. * @param pbPktRem The remaining packet data (without the 'H'). * @param cbPktRem Size of the remaining packet in bytes. */ static int dbgcGdbStubCtxPktProcessH(PGDBSTUBCTX pThis, const uint8_t *pbPktRem, size_t cbPktRem) { int rc = VINF_SUCCESS; if (*pbPktRem == 'g') { /*Unused: cbPktRem--;*/ RT_NOREF(cbPktRem); pbPktRem++; /* We know there is an # character denoting the end so the following must return with VWRN_TRAILING_CHARS. */ VMCPUID idCpu; rc = RTStrToUInt32Ex((const char *)pbPktRem, NULL /*ppszNext*/, 16, &idCpu); if ( rc == VWRN_TRAILING_CHARS && idCpu > 0) { idCpu--; VMCPUID cCpus = DBGFR3CpuGetCount(pThis->Dbgc.pUVM); if (idCpu < cCpus) { pThis->Dbgc.idCpu = idCpu; rc = dbgcGdbStubCtxReplySendOk(pThis); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); } else /* Do not support the 'c' operation for now (will be handled through vCont later on anyway). */ rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0); return rc; } /** * Processes a completely received packet. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxPktProcess(PGDBSTUBCTX pThis) { int rc = VINF_SUCCESS; if (pThis->cbPkt >= 1) { switch (pThis->pbPktBuf[1]) { case '!': /* Enabled extended mode. */ { pThis->fExtendedMode = true; rc = dbgcGdbStubCtxReplySendOk(pThis); break; } case '?': { /* Return signal state. */ rc = dbgcGdbStubCtxReplySendSigTrap(pThis); break; } case 's': /* Single step, response will be sent in the event loop. */ { PDBGFADDRESS pStackPop = NULL; RTGCPTR cbStackPop = 0; rc = DBGFR3StepEx(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, DBGF_STEP_F_INTO, NULL, pStackPop, cbStackPop, 1 /*cMaxSteps*/); if (RT_FAILURE(rc)) dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'c': /* Continue, no response */ { if (DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL)) DBGFR3Resume(pThis->Dbgc.pUVM, VMCPUID_ALL); break; } case 'H': { rc = dbgcGdbStubCtxPktProcessH(pThis, &pThis->pbPktBuf[2], pThis->cbPkt - 1); break; } case 'T': { rc = dbgcGdbStubCtxReplySendOk(pThis); break; } case 'g': /* Read general registers. */ { uint32_t idxRegMax = 0; size_t cbRegs = 0; for (;;) { const GDBREGDESC *pReg = &pThis->paRegs[idxRegMax++]; cbRegs += pReg->cBits / 8; if (pReg->enmReg == DBGFREG_SS) /* Up to this seems to belong to the general register set. */ break; } size_t cbReplyPkt = cbRegs * 2 + 1; /* One byte needs two characters. */ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbReplyPkt); if (RT_SUCCESS(rc)) { size_t cbLeft = cbReplyPkt; uint8_t *pbReply = pThis->pbPktBuf; for (uint32_t i = 0; i < idxRegMax && RT_SUCCESS(rc); i++) { const GDBREGDESC *pReg = &pThis->paRegs[i]; size_t cbReg = pReg->cBits / 8; union { uint32_t u32; uint64_t u64; uint8_t au8[8]; } RegVal; if (pReg->cBits == 32) rc = DBGFR3RegCpuQueryU32(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->enmReg, &RegVal.u32); else rc = DBGFR3RegCpuQueryU64(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->enmReg, &RegVal.u64); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxEncodeBinaryAsHex(pbReply, cbLeft, &RegVal.au8[0], cbReg); pbReply += cbReg * 2; cbLeft -= cbReg * 2; } if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbReplyPkt); else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } break; } case 'm': /* Read memory. */ { uint64_t GdbTgtAddr = 0; const uint8_t *pbPktSep = NULL; rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &GdbTgtAddr, ',', &pbPktSep); if (RT_SUCCESS(rc)) { size_t cbProcessed = pbPktSep - &pThis->pbPktBuf[2]; uint64_t cbRead = 0; rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, pThis->cbPkt - 1 - cbProcessed - 1, &cbRead, GDBSTUB_PKT_END, NULL); if (RT_SUCCESS(rc)) { size_t cbReplyPkt = cbRead * 2 + 1; /* One byte needs two characters. */ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbReplyPkt); if (RT_SUCCESS(rc)) { uint8_t *pbPktBuf = pThis->pbPktBuf; size_t cbPktBufLeft = cbReplyPkt; DBGFADDRESS AddrRead; DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrRead, GdbTgtAddr); while ( cbRead && RT_SUCCESS(rc)) { uint8_t abTmp[_4K]; size_t cbThisRead = RT_MIN(cbRead, sizeof(abTmp)); rc = DBGFR3MemRead(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrRead, &abTmp[0], cbThisRead); if (RT_FAILURE(rc)) break; rc = dbgcGdbStubCtxEncodeBinaryAsHex(pbPktBuf, cbPktBufLeft, &abTmp[0], cbThisRead); if (RT_FAILURE(rc)) break; DBGFR3AddrAdd(&AddrRead, cbThisRead); cbRead -= cbThisRead; pbPktBuf += cbThisRead; cbPktBufLeft -= cbThisRead; } if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbReplyPkt); else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'M': /* Write memory. */ { uint64_t GdbTgtAddr = 0; const uint8_t *pbPktSep = NULL; rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &GdbTgtAddr, ',', &pbPktSep); if (RT_SUCCESS(rc)) { size_t cbProcessed = pbPktSep - &pThis->pbPktBuf[2]; uint64_t cbWrite = 0; rc = dbgcGdbStubCtxParseHexStringAsInteger(pbPktSep + 1, pThis->cbPkt - 1 - cbProcessed - 1, &cbWrite, ':', &pbPktSep); if (RT_SUCCESS(rc)) { cbProcessed = pbPktSep - &pThis->pbPktBuf[2]; const uint8_t *pbDataCur = pbPktSep + 1; size_t cbDataLeft = pThis->cbPkt - 1 - cbProcessed - 1 - 1; DBGFADDRESS AddrWrite; DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &AddrWrite, GdbTgtAddr); while ( cbWrite && RT_SUCCESS(rc)) { uint8_t abTmp[_4K]; size_t cbThisWrite = RT_MIN(cbWrite, sizeof(abTmp)); size_t cbDecoded = 0; rc = dbgcGdbStubCtxParseHexStringAsByteBuf(pbDataCur, cbDataLeft, &abTmp[0], cbThisWrite, &cbDecoded); if (!rc) rc = DBGFR3MemWrite(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &AddrWrite, &abTmp[0], cbThisWrite); DBGFR3AddrAdd(&AddrWrite, cbThisWrite); cbWrite -= cbThisWrite; pbDataCur += cbDecoded; cbDataLeft -= cbDecoded; } if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendOk(pThis); else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'p': /* Read a single register */ { uint64_t uReg = 0; rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &uReg, GDBSTUB_PKT_END, NULL); if (RT_SUCCESS(rc)) { DBGFREGVAL RegVal; DBGFREGVALTYPE enmType; const GDBREGDESC *pReg = dbgcGdbStubRegGet(pThis, uReg); if (RT_LIKELY(pReg)) { rc = DBGFR3RegNmQuery(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->pszName, &RegVal, &enmType); if (RT_SUCCESS(rc)) { size_t cbReg = pReg->cBits / 8; size_t cbReplyPkt = cbReg * 2 + 1; /* One byte needs two characters. */ /* Encode data and send. */ rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, cbReplyPkt); if (RT_SUCCESS(rc)) { rc = dbgcGdbStubCtxEncodeBinaryAsHex(pThis->pbPktBuf, pThis->cbPktBufMax, &RegVal.au8[0], cbReg); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySend(pThis, pThis->pbPktBuf, cbReplyPkt); else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'P': /* Write a single register */ { uint64_t uReg = 0; const uint8_t *pbPktSep = NULL; rc = dbgcGdbStubCtxParseHexStringAsInteger(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &uReg, '=', &pbPktSep); if (RT_SUCCESS(rc)) { const GDBREGDESC *pReg = dbgcGdbStubRegGet(pThis, uReg); if (pReg) { DBGFREGVAL RegVal; DBGFREGVALTYPE enmValType = pReg->cBits == 64 ? DBGFREGVALTYPE_U64 : DBGFREGVALTYPE_U32; size_t cbProcessed = pbPktSep - &pThis->pbPktBuf[2]; rc = dbgcGdbStubCtxParseHexStringAsByteBuf(pbPktSep + 1, pThis->cbPkt - 1 - cbProcessed - 1, &RegVal.au8[0], pReg->cBits / 8, NULL); if (RT_SUCCESS(rc)) { rc = DBGFR3RegNmSet(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, pReg->pszName, &RegVal, enmValType); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendOk(pThis); else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NET_PROTOCOL_ERROR); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'Z': /* Insert a breakpoint/watchpoint. */ { GDBSTUBTPTYPE enmTpType = GDBSTUBTPTYPE_INVALID; uint64_t GdbTgtTpAddr = 0; uint64_t uKind = 0; rc = dbgcGdbStubCtxParseTpPktArgs(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &enmTpType, &GdbTgtTpAddr, &uKind); if (RT_SUCCESS(rc)) { uint32_t iBp = 0; DBGFADDRESS BpAddr; DBGFR3AddrFromFlat(pThis->Dbgc.pUVM, &BpAddr, GdbTgtTpAddr); switch (enmTpType) { case GDBSTUBTPTYPE_EXEC_SW: { rc = DBGFR3BpSetInt3(pThis->Dbgc.pUVM, pThis->Dbgc.idCpu, &BpAddr, 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/, &iBp); break; } case GDBSTUBTPTYPE_EXEC_HW: { rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &BpAddr, 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/, X86_DR7_RW_EO, 1 /*cb*/, &iBp); break; } case GDBSTUBTPTYPE_MEM_ACCESS: case GDBSTUBTPTYPE_MEM_READ: { rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &BpAddr, 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/, X86_DR7_RW_RW, uKind /*cb*/, &iBp); break; } case GDBSTUBTPTYPE_MEM_WRITE: { rc = DBGFR3BpSetReg(pThis->Dbgc.pUVM, &BpAddr, 1 /*iHitTrigger*/, UINT64_MAX /*iHitDisable*/, X86_DR7_RW_WO, uKind /*cb*/, &iBp); break; } default: AssertMsgFailed(("Invalid trace point type %d\n", enmTpType)); } if (RT_SUCCESS(rc)) { rc = dbgcBpAdd(&pThis->Dbgc, iBp, NULL /*pszCmd*/); if (RT_SUCCESS(rc)) { rc = dbgcGdbStubTpRegister(pThis, enmTpType, GdbTgtTpAddr, uKind, iBp); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendOk(pThis); else dbgcBpDelete(&pThis->Dbgc, iBp); } if (RT_FAILURE(rc)) { DBGFR3BpClear(pThis->Dbgc.pUVM, iBp); rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'z': /* Remove a breakpoint/watchpoint. */ { GDBSTUBTPTYPE enmTpType = GDBSTUBTPTYPE_INVALID; uint64_t GdbTgtTpAddr = 0; uint64_t uKind = 0; rc = dbgcGdbStubCtxParseTpPktArgs(&pThis->pbPktBuf[2], pThis->cbPkt - 1, &enmTpType, &GdbTgtTpAddr, &uKind); if (RT_SUCCESS(rc)) { PGDBSTUBTP pTp = dbgcGdbStubTpFind(pThis, enmTpType, GdbTgtTpAddr, uKind); if (pTp) { int rc2 = DBGFR3BpClear(pThis->Dbgc.pUVM, pTp->iBp); if (RT_SUCCESS(rc2) || rc2 == VERR_DBGF_BP_NOT_FOUND) dbgcBpDelete(&pThis->Dbgc, pTp->iBp); if (RT_SUCCESS(rc2)) { dbgcGdbStubTpDeregister(pTp); rc = dbgcGdbStubCtxReplySendOk(pThis); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, VERR_NOT_FOUND); } else rc = dbgcGdbStubCtxReplySendErrSts(pThis, rc); break; } case 'q': /* Query packet */ { rc = dbgcGdbStubCtxPktProcessQuery(pThis, &pThis->pbPktBuf[2], pThis->cbPkt - 1); break; } case 'v': /* Multiletter identifier (verbose?) */ { rc = dbgcGdbStubCtxPktProcessV(pThis, &pThis->pbPktBuf[2], pThis->cbPkt - 1); break; } case 'R': /* Restart target. */ { rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0); break; } case 'k': /* Kill target. */ { /* This is what the 'harakiri' command is doing. */ for (;;) exit(126); break; } case 'D': /* Detach */ { rc = dbgcGdbStubCtxReplySendOk(pThis); if (RT_SUCCESS(rc)) rc = VERR_DBGC_QUIT; break; } default: /* Not supported, send empty reply. */ rc = dbgcGdbStubCtxReplySend(pThis, NULL, 0); } } return rc; } /** * Resets the packet buffer. * * @param pThis The GDB stub context. */ static void dbgcGdbStubCtxPktBufReset(PGDBSTUBCTX pThis) { pThis->offPktBuf = 0; pThis->cbPkt = 0; pThis->cbChksumRecvLeft = 2; } /** * Resets the given GDB stub context to the initial state. * * @param pThis The GDB stub context. */ static void dbgcGdbStubCtxReset(PGDBSTUBCTX pThis) { pThis->enmState = GDBSTUBRECVSTATE_PACKET_WAIT_FOR_START; dbgcGdbStubCtxPktBufReset(pThis); } /** * Searches for the start character in the current data buffer. * * @returns Status code. * @param pThis The GDB stub context. * @param cbData Number of new bytes in the packet buffer. * @param pcbProcessed Where to store the amount of bytes processed. */ static int dbgcGdbStubCtxPktBufSearchStart(PGDBSTUBCTX pThis, size_t cbData, size_t *pcbProcessed) { int rc = VINF_SUCCESS; const uint8_t *pbStart = (const uint8_t *)memchr(pThis->pbPktBuf, GDBSTUB_PKT_START, cbData); if (pbStart) { /* Found the start character, align the start to the beginning of the packet buffer and advance the state machine. */ memmove(pThis->pbPktBuf, pbStart, cbData - (pbStart - pThis->pbPktBuf)); pThis->enmState = GDBSTUBRECVSTATE_PACKET_RECEIVE_BODY; *pcbProcessed = (uintptr_t)(pbStart - pThis->pbPktBuf); pThis->offPktBuf = 0; } else { /* Check for out of band characters. */ if (memchr(pThis->pbPktBuf, GDBSTUB_OOB_INTERRUPT, cbData) != NULL) { /* Stop target and send packet to indicate the target has stopped. */ if (!DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL)) rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL); /* The reply will be send in the event loop. */ } /* Not found, ignore the received data and reset the packet buffer. */ dbgcGdbStubCtxPktBufReset(pThis); *pcbProcessed = cbData; } return rc; } /** * Searches for the end character in the current data buffer. * * @returns Status code. * @param pThis The GDB stub context. * @param cbData Number of new bytes in the packet buffer. * @param pcbProcessed Where to store the amount of bytes processed. */ static int dbgcGdbStubCtxPktBufSearchEnd(PGDBSTUBCTX pThis, size_t cbData, size_t *pcbProcessed) { const uint8_t *pbEnd = (const uint8_t *)memchr(&pThis->pbPktBuf[pThis->offPktBuf], GDBSTUB_PKT_END, cbData); if (pbEnd) { /* Found the end character, next comes the checksum. */ pThis->enmState = GDBSTUBRECVSTATE_PACKET_RECEIVE_CHECKSUM; *pcbProcessed = (uintptr_t)(pbEnd - &pThis->pbPktBuf[pThis->offPktBuf]) + 1; pThis->offPktBuf += *pcbProcessed; pThis->cbPkt = pThis->offPktBuf - 1; /* Don't account for the start and end character. */ } else { /* Not found, still in the middle of a packet. */ /** @todo Look for out of band characters. */ *pcbProcessed = cbData; pThis->offPktBuf += cbData; } return VINF_SUCCESS; } /** * Processes the checksum. * * @returns Status code. * @param pThis The GDB stub context. * @param cbData Number of new bytes in the packet buffer. * @param pcbProcessed Where to store the amount of bytes processed. */ static int dbgcGdbStubCtxPktBufProcessChksum(PGDBSTUBCTX pThis, size_t cbData, size_t *pcbProcessed) { int rc = VINF_SUCCESS; size_t cbChksumProcessed = (cbData < pThis->cbChksumRecvLeft) ? cbData : pThis->cbChksumRecvLeft; pThis->cbChksumRecvLeft -= cbChksumProcessed; if (!pThis->cbChksumRecvLeft) { /* Verify checksum of the whole packet. */ uint8_t uChkSum = dbgcGdbStubCtxChrToHex(pThis->pbPktBuf[pThis->offPktBuf]) << 4 | dbgcGdbStubCtxChrToHex(pThis->pbPktBuf[pThis->offPktBuf + 1]); uint8_t uSum = 0; for (size_t i = 1; i < pThis->cbPkt; i++) uSum += pThis->pbPktBuf[i]; if (uSum == uChkSum) { /* Checksum matches, send acknowledge and continue processing the complete payload. */ char chAck = '+'; rc = dbgcGdbStubCtxWrite(pThis, &chAck, sizeof(chAck)); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxPktProcess(pThis); } else { /* Send NACK and reset for the next packet. */ char chAck = '-'; rc = dbgcGdbStubCtxWrite(pThis, &chAck, sizeof(chAck)); } dbgcGdbStubCtxReset(pThis); } *pcbProcessed += cbChksumProcessed; return rc; } /** * Process read data in the packet buffer based on the current state. * * @returns Status code. * @param pThis The GDB stub context. * @param cbData Number of new bytes in the packet buffer. */ static int dbgcGdbStubCtxPktBufProcess(PGDBSTUBCTX pThis, size_t cbData) { int rc = VINF_SUCCESS; while ( cbData && RT_SUCCESS(rc)) { size_t cbProcessed = 0; switch (pThis->enmState) { case GDBSTUBRECVSTATE_PACKET_WAIT_FOR_START: { rc = dbgcGdbStubCtxPktBufSearchStart(pThis, cbData, &cbProcessed); break; } case GDBSTUBRECVSTATE_PACKET_RECEIVE_BODY: { rc = dbgcGdbStubCtxPktBufSearchEnd(pThis, cbData, &cbProcessed); break; } case GDBSTUBRECVSTATE_PACKET_RECEIVE_CHECKSUM: { rc = dbgcGdbStubCtxPktBufProcessChksum(pThis, cbData, &cbProcessed); break; } default: /* Should never happen. */ rc = VERR_INTERNAL_ERROR; } cbData -= cbProcessed; } return rc; } /** * Receive data and processes complete packets. * * @returns Status code. * @param pThis The GDB stub context. */ static int dbgcGdbStubCtxRecv(PGDBSTUBCTX pThis) { /* * Read in 32 bytes chunks for now (need some peek API to get the amount of bytes actually available * to make it a bit more optimized). */ int rc = dbgcGdbStubCtxEnsurePktBufSpace(pThis, 32); if (RT_SUCCESS(rc)) { size_t cbThisRead = 32; rc = pThis->Dbgc.pIo->pfnRead(pThis->Dbgc.pIo, &pThis->pbPktBuf[pThis->offPktBuf], cbThisRead, &cbThisRead); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxPktBufProcess(pThis, cbThisRead); } return rc; } /** * Processes debugger events. * * @returns VBox status code. * @param pThis The GDB stub context data. * @param pEvent Pointer to event data. */ static int dbgcGdbStubCtxProcessEvent(PGDBSTUBCTX pThis, PCDBGFEVENT pEvent) { /* * Process the event. */ PDBGC pDbgc = &pThis->Dbgc; pThis->Dbgc.pszScratch = &pThis->Dbgc.achInput[0]; pThis->Dbgc.iArg = 0; int rc = VINF_SUCCESS; switch (pEvent->enmType) { /* * The first part is events we have initiated with commands. */ case DBGFEVENT_HALT_DONE: { rc = dbgcGdbStubCtxReplySendSigTrap(pThis); break; } /* * The second part is events which can occur at any time. */ case DBGFEVENT_FATAL_ERROR: { rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbf event: Fatal error! (%s)\n", dbgcGetEventCtx(pEvent->enmCtx)); if (RT_SUCCESS(rc)) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); break; } case DBGFEVENT_BREAKPOINT: case DBGFEVENT_BREAKPOINT_IO: case DBGFEVENT_BREAKPOINT_MMIO: case DBGFEVENT_BREAKPOINT_HYPER: { rc = dbgcBpExec(pDbgc, pEvent->u.Bp.hBp); switch (rc) { case VERR_DBGC_BP_NOT_FOUND: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Unknown breakpoint %u! (%s)\n", pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx)); break; case VINF_DBGC_BP_NO_COMMAND: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Breakpoint %u! (%s)\n", pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx)); break; case VINF_BUFFER_OVERFLOW: rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Breakpoint %u! Command too long to execute! (%s)\n", pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx)); break; default: break; } if (RT_SUCCESS(rc) && DBGFR3IsHalted(pDbgc->pUVM, VMCPUID_ALL)) { rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); /* Set the resume flag to ignore the breakpoint when resuming execution. */ if ( RT_SUCCESS(rc) && pEvent->enmType == DBGFEVENT_BREAKPOINT) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r eflags.rf = 1"); } if (RT_FAILURE(rc)) pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Executing the breakpoint %u (%s) failed with %Rrc!\n", pEvent->u.Bp.hBp, dbgcGetEventCtx(pEvent->enmCtx), rc); rc = dbgcGdbStubCtxReplySendSigTrap(pThis); break; } case DBGFEVENT_STEPPED: case DBGFEVENT_STEPPED_HYPER: { rc = dbgcGdbStubCtxReplySendSigTrap(pThis); break; } case DBGFEVENT_ASSERTION_HYPER: { rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: Hypervisor Assertion! (%s)\n" "%s" "%s" "\n", dbgcGetEventCtx(pEvent->enmCtx), pEvent->u.Assert.pszMsg1, pEvent->u.Assert.pszMsg2); if (RT_SUCCESS(rc)) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); break; } case DBGFEVENT_DEV_STOP: { rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\n" "dbgf event: DBGFSTOP (%s)\n" "File: %s\n" "Line: %d\n" "Function: %s\n", dbgcGetEventCtx(pEvent->enmCtx), pEvent->u.Src.pszFile, pEvent->u.Src.uLine, pEvent->u.Src.pszFunction); if (RT_SUCCESS(rc) && pEvent->u.Src.pszMessage && *pEvent->u.Src.pszMessage) rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "Message: %s\n", pEvent->u.Src.pszMessage); if (RT_SUCCESS(rc)) rc = pDbgc->CmdHlp.pfnExec(&pDbgc->CmdHlp, "r"); break; } case DBGFEVENT_INVALID_COMMAND: { rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Invalid command event!\n"); break; } case DBGFEVENT_POWERING_OFF: { pThis->Dbgc.fReady = false; pThis->Dbgc.pIo->pfnSetReady(pThis->Dbgc.pIo, false); rc = VERR_GENERAL_FAILURE; break; } default: { /* * Probably a generic event. Look it up to find its name. */ PCDBGCSXEVT pEvtDesc = dbgcEventLookup(pEvent->enmType); if (pEvtDesc) { if (pEvtDesc->enmKind == kDbgcSxEventKind_Interrupt) { Assert(pEvtDesc->pszDesc); Assert(pEvent->u.Generic.cArgs == 1); rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s no %#llx! (%s)\n", pEvtDesc->pszDesc, pEvent->u.Generic.auArgs[0], pEvtDesc->pszName); } else if (pEvtDesc->fFlags & DBGCSXEVT_F_BUGCHECK) { Assert(pEvent->u.Generic.cArgs >= 5); char szDetails[512]; DBGFR3FormatBugCheck(pDbgc->pUVM, szDetails, sizeof(szDetails), pEvent->u.Generic.auArgs[0], pEvent->u.Generic.auArgs[1], pEvent->u.Generic.auArgs[2], pEvent->u.Generic.auArgs[3], pEvent->u.Generic.auArgs[4]); rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s %s%s!\n%s", pEvtDesc->pszName, pEvtDesc->pszDesc ? "- " : "", pEvtDesc->pszDesc ? pEvtDesc->pszDesc : "", szDetails); } else if ( (pEvtDesc->fFlags & DBGCSXEVT_F_TAKE_ARG) || pEvent->u.Generic.cArgs > 1 || ( pEvent->u.Generic.cArgs == 1 && pEvent->u.Generic.auArgs[0] != 0)) { if (pEvtDesc->pszDesc) rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s - %s!", pEvtDesc->pszName, pEvtDesc->pszDesc); else rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s!", pEvtDesc->pszName); if (pEvent->u.Generic.cArgs <= 1) rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " arg=%#llx\n", pEvent->u.Generic.auArgs[0]); else { for (uint32_t i = 0; i < pEvent->u.Generic.cArgs; i++) rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, " args[%u]=%#llx", i, pEvent->u.Generic.auArgs[i]); rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\n"); } } else { if (pEvtDesc->pszDesc) rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s - %s!\n", pEvtDesc->pszName, pEvtDesc->pszDesc); else rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf event: %s!\n", pEvtDesc->pszName); } } else rc = pDbgc->CmdHlp.pfnPrintf(&pDbgc->CmdHlp, NULL, "\ndbgf/dbgc error: Unknown event %d!\n", pEvent->enmType); break; } } return rc; } /** * Run the debugger console. * * @returns VBox status code. * @param pThis Pointer to the GDB stub context. */ static int dbgcGdbStubRun(PGDBSTUBCTX pThis) { /* Select the register set based on the CPU mode. */ CPUMMODE enmMode = DBGCCmdHlpGetCpuMode(&pThis->Dbgc.CmdHlp); switch (enmMode) { case CPUMMODE_PROTECTED: pThis->paRegs = &g_aGdbRegs32[0]; pThis->cRegs = RT_ELEMENTS(g_aGdbRegs32); break; case CPUMMODE_LONG: pThis->paRegs = &g_aGdbRegs64[0]; pThis->cRegs = RT_ELEMENTS(g_aGdbRegs64); break; case CPUMMODE_REAL: default: return DBGCCmdHlpPrintf(&pThis->Dbgc.CmdHlp, "error: Invalid CPU mode %d.\n", enmMode); } /* * We're ready for commands now. */ pThis->Dbgc.fReady = true; pThis->Dbgc.pIo->pfnSetReady(pThis->Dbgc.pIo, true); /* * Main Debugger Loop. * * This loop will either block on waiting for input or on waiting on * debug events. If we're forwarding the log we cannot wait for long * before we must flush the log. */ int rc; for (;;) { rc = VERR_SEM_OUT_OF_TURN; if (pThis->Dbgc.pUVM) rc = DBGFR3QueryWaitable(pThis->Dbgc.pUVM); if (RT_SUCCESS(rc)) { /* * Wait for a debug event. */ DBGFEVENT Event; rc = DBGFR3EventWait(pThis->Dbgc.pUVM, 32, &Event); if (RT_SUCCESS(rc)) { rc = dbgcGdbStubCtxProcessEvent(pThis, &Event); if (RT_FAILURE(rc)) break; } else if (rc != VERR_TIMEOUT) break; /* * Check for input. */ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, 0)) { rc = dbgcGdbStubCtxRecv(pThis); if (RT_FAILURE(rc)) break; } } else if (rc == VERR_SEM_OUT_OF_TURN) { /* * Wait for input. */ if (pThis->Dbgc.pIo->pfnInput(pThis->Dbgc.pIo, 1000)) { rc = dbgcGdbStubCtxRecv(pThis); if (RT_FAILURE(rc)) break; } } else break; } return rc; } /** * @copydoc DBGC::pfnOutput */ static DECLCALLBACK(int) dbgcOutputGdb(void *pvUser, const char *pachChars, size_t cbChars) { PGDBSTUBCTX pThis = (PGDBSTUBCTX)pvUser; pThis->fOutput = true; int rc = dbgcGdbStubCtxReplySendBegin(pThis); if (RT_SUCCESS(rc)) { uint8_t chConOut = 'O'; rc = dbgcGdbStubCtxReplySendData(pThis, &chConOut, sizeof(chConOut)); if (RT_SUCCESS(rc)) { /* Convert the characters to hex. */ const char *pachCur = pachChars; while ( cbChars && RT_SUCCESS(rc)) { uint8_t achHex[512 + 1]; size_t cbThisSend = RT_MIN((sizeof(achHex) - 1) / 2, cbChars); /* Each character needs two bytes. */ rc = dbgcGdbStubCtxEncodeBinaryAsHex(&achHex[0], cbThisSend * 2 + 1, pachCur, cbThisSend); if (RT_SUCCESS(rc)) rc = dbgcGdbStubCtxReplySendData(pThis, &achHex[0], cbThisSend * 2); pachCur += cbThisSend; cbChars -= cbThisSend; } } dbgcGdbStubCtxReplySendEnd(pThis); } return rc; } /** * Creates a GDB stub context instance with the given backend. * * @returns VBox status code. * @param ppGdbStubCtx Where to store the pointer to the GDB stub context instance on success. * @param pIo Pointer to the I/O callback table. * @param fFlags Flags controlling the behavior. */ static int dbgcGdbStubCtxCreate(PPGDBSTUBCTX ppGdbStubCtx, PCDBGCIO pIo, unsigned fFlags) { /* * Validate input. */ AssertPtrReturn(pIo, VERR_INVALID_POINTER); AssertMsgReturn(!fFlags, ("%#x", fFlags), VERR_INVALID_PARAMETER); /* * Allocate and initialize. */ PGDBSTUBCTX pThis = (PGDBSTUBCTX)RTMemAllocZ(sizeof(*pThis)); if (!pThis) return VERR_NO_MEMORY; dbgcInitCmdHlp(&pThis->Dbgc); /* * This is compied from the native debug console (will be used for monitor commands) * in DBGCConsole.cpp. Try to keep both functions in sync. */ pThis->Dbgc.pIo = pIo; pThis->Dbgc.pfnOutput = dbgcOutputGdb; pThis->Dbgc.pvOutputUser = pThis; pThis->Dbgc.pVM = NULL; pThis->Dbgc.pUVM = NULL; pThis->Dbgc.idCpu = 0; pThis->Dbgc.hDbgAs = DBGF_AS_GLOBAL; pThis->Dbgc.pszEmulation = "CodeView/WinDbg"; pThis->Dbgc.paEmulationCmds = &g_aCmdsCodeView[0]; pThis->Dbgc.cEmulationCmds = g_cCmdsCodeView; pThis->Dbgc.paEmulationFuncs = &g_aFuncsCodeView[0]; pThis->Dbgc.cEmulationFuncs = g_cFuncsCodeView; //pThis->Dbgc.fLog = false; pThis->Dbgc.fRegTerse = true; pThis->Dbgc.fStepTraceRegs = true; //pThis->Dbgc.cPagingHierarchyDumps = 0; //pThis->Dbgc.DisasmPos = {0}; //pThis->Dbgc.SourcePos = {0}; //pThis->Dbgc.DumpPos = {0}; pThis->Dbgc.pLastPos = &pThis->Dbgc.DisasmPos; //pThis->Dbgc.cbDumpElement = 0; //pThis->Dbgc.cVars = 0; //pThis->Dbgc.paVars = NULL; //pThis->Dbgc.pPlugInHead = NULL; //pThis->Dbgc.pFirstBp = NULL; //pThis->Dbgc.abSearch = {0}; //pThis->Dbgc.cbSearch = 0; pThis->Dbgc.cbSearchUnit = 1; pThis->Dbgc.cMaxSearchHits = 1; //pThis->Dbgc.SearchAddr = {0}; //pThis->Dbgc.cbSearchRange = 0; //pThis->Dbgc.uInputZero = 0; //pThis->Dbgc.iRead = 0; //pThis->Dbgc.iWrite = 0; //pThis->Dbgc.cInputLines = 0; //pThis->Dbgc.fInputOverflow = false; pThis->Dbgc.fReady = true; pThis->Dbgc.pszScratch = &pThis->Dbgc.achScratch[0]; //pThis->Dbgc.iArg = 0; //pThis->Dbgc.rcOutput = 0; //pThis->Dbgc.rcCmd = 0; //pThis->Dbgc.pszHistoryFile = NULL; //pThis->Dbgc.pszGlobalInitScript = NULL; //pThis->Dbgc.pszLocalInitScript = NULL; dbgcEvalInit(); /* Init the GDB stub specific parts. */ pThis->cbPktBufMax = 0; pThis->pbPktBuf = NULL; pThis->fFeatures = GDBSTUBCTX_FEATURES_F_TGT_DESC; pThis->pachTgtXmlDesc = NULL; pThis->cbTgtXmlDesc = 0; pThis->fExtendedMode = false; pThis->fOutput = false; pThis->fInThrdInfoQuery = false; RTListInit(&pThis->LstTps); dbgcGdbStubCtxReset(pThis); *ppGdbStubCtx = pThis; return VINF_SUCCESS; } /** * Destroys the given GDB stub context. * * @param pThis The GDB stub context to destroy. */ static void dbgcGdbStubDestroy(PGDBSTUBCTX pThis) { AssertPtr(pThis); /* Detach from the VM. */ if (pThis->Dbgc.pUVM) DBGFR3Detach(pThis->Dbgc.pUVM); /* Free config strings. */ RTStrFree(pThis->Dbgc.pszGlobalInitScript); pThis->Dbgc.pszGlobalInitScript = NULL; RTStrFree(pThis->Dbgc.pszLocalInitScript); pThis->Dbgc.pszLocalInitScript = NULL; RTStrFree(pThis->Dbgc.pszHistoryFile); pThis->Dbgc.pszHistoryFile = NULL; /* Finally, free the instance memory. */ RTMemFree(pThis); } DECL_HIDDEN_CALLBACK(int) dbgcGdbStubRunloop(PUVM pUVM, PCDBGCIO pIo, unsigned fFlags) { /* * Validate input. */ AssertPtrNullReturn(pUVM, VERR_INVALID_VM_HANDLE); PVM pVM = NULL; if (pUVM) { pVM = VMR3GetVM(pUVM); AssertPtrReturn(pVM, VERR_INVALID_VM_HANDLE); } /* * Allocate and initialize instance data */ PGDBSTUBCTX pThis; int rc = dbgcGdbStubCtxCreate(&pThis, pIo, fFlags); if (RT_FAILURE(rc)) return rc; if (!HMR3IsEnabled(pUVM) && !NEMR3IsEnabled(pUVM)) pThis->Dbgc.hDbgAs = DBGF_AS_RC_AND_GC_GLOBAL; /* * Attach to the specified VM. */ if (RT_SUCCESS(rc) && pUVM) { rc = DBGFR3Attach(pUVM); if (RT_SUCCESS(rc)) { pThis->Dbgc.pVM = pVM; pThis->Dbgc.pUVM = pUVM; pThis->Dbgc.idCpu = 0; } else rc = pThis->Dbgc.CmdHlp.pfnVBoxError(&pThis->Dbgc.CmdHlp, rc, "When trying to attach to VM %p\n", pThis->Dbgc.pVM); } /* * Load plugins. */ if (RT_SUCCESS(rc)) { if (pVM) DBGFR3PlugInLoadAll(pThis->Dbgc.pUVM); dbgcEventInit(&pThis->Dbgc); //dbgcRunInitScripts(pDbgc); Not yet if (!DBGFR3IsHalted(pThis->Dbgc.pUVM, VMCPUID_ALL)) rc = DBGFR3Halt(pThis->Dbgc.pUVM, VMCPUID_ALL); /* * Run the debugger main loop. */ rc = dbgcGdbStubRun(pThis); dbgcEventTerm(&pThis->Dbgc); } /* * Cleanup console debugger session. */ dbgcGdbStubDestroy(pThis); return rc == VERR_DBGC_QUIT ? VINF_SUCCESS : rc; }