Index: trunk/src/VBox/Debugger/DBGPlugInWinNt.cpp
===================================================================
--- trunk/src/VBox/Debugger/DBGPlugInWinNt.cpp	(revision 96866)
+++ trunk/src/VBox/Debugger/DBGPlugInWinNt.cpp	(revision 96867)
@@ -35,4 +35,8 @@
 #include <VBox/vmm/cpumctx.h>
 #include <VBox/vmm/mm.h>
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+# include <VBox/vmm/vmapi.h>
+# include <VBox/dis.h>
+#endif
 #include <VBox/vmm/vmmr3vtable.h>
 #include <VBox/err.h>
@@ -270,4 +274,11 @@
     /** The Windows NT specifics interface. */
     DBGFOSIWINNT        IWinNt;
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+    /** Breakpoint owner handle for the DbgPrint/vDbgPrint{,Ex}... interception. */
+    DBGFBPOWNER         hBpOwnerDbgPrint;
+    /** Breakpoint handle for the DbgPrint/vDbgPrint{,Ex}... interception. */
+    DBGFBP              hBpDbgPrint;
+#endif
 } DBGDIGGERWINNT;
 /** Pointer to the linux guest OS digger instance data. */
@@ -344,4 +355,305 @@
 };
 
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+/**
+ * Queries the string from guest memory with the pointer in the given register, sanitizing it.
+ *
+ * @returns VBox status code.
+ * @param   pUVM            The user mode VM handle.
+ * @param   idCpu           The CPU ID.
+ * @param   enmReg          The register to query the string pointer from.
+ * @param   pszBuf          Where to store the sanitized string.
+ * @param   cbBuf           Size of the buffer in number of bytes.
+ */
+static int dbgDiggerWinNtDbgPrintQueryStringFromReg(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, char *pszBuf, size_t cbBuf)
+{
+    uint64_t u64RegPtr = 0;
+    int rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, enmReg, &u64RegPtr);
+    if (   rc == VINF_SUCCESS
+        || rc == VINF_DBGF_ZERO_EXTENDED_REGISTER) /* Being strict about what we expect here. */
+    {
+        DBGFADDRESS AddrStr;
+        DBGFR3AddrFromFlat(pUVM, &AddrStr, u64RegPtr);
+        rc = DBGFR3MemRead(pUVM, idCpu, &AddrStr, pszBuf, cbBuf);
+        if (RT_SUCCESS(rc))
+        {
+            /* Check that there is a zero terminator and purge invalid encoding (expecting UTF-8 here). */
+            size_t idx = 0;
+            for (idx = 0; idx < cbBuf; idx++)
+                if (pszBuf[idx] == '\0')
+                    break;
+
+            if (idx == cbBuf)
+                pszBuf[cbBuf - 1] = '\0'; /* Force terminator, truncating the string. */
+            else
+                memset(&pszBuf[idx], 0, cbBuf - idx); /* Clear everything afterwards. */
+
+            /* Purge the string encoding. */
+            RTStrPurgeEncoding(pszBuf);
+        }
+    }
+    else if (RT_SUCCESS(rc))
+        rc = VERR_INVALID_STATE;
+
+    return rc;
+}
+
+
+/**
+ * @copydoc{FNDBGFBPHIT, Breakpoint callback for the DbgPrint interception.}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) dbgDiggerWinNtDbgPrintHit(PVM pVM, VMCPUID idCpu, void *pvUserBp, DBGFBP hBp, PCDBGFBPPUB pBpPub, uint16_t fFlags)
+{
+    RT_NOREF(hBp, pBpPub, fFlags);
+    PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvUserBp;
+    PUVM pUVM = VMR3GetUVM(pVM);
+
+    /*
+     * The worker prototype looks like the following:
+     *      vDbgPrintExWorker(PCCH Prefix, ULONG ComponentId, ULONG Level, PCCH Format, va_list arglist, BOOL fUnknown)
+     *
+     * Depending on the bitness the parameters are grabbed from the appropriate registers and stack locations.
+     * For amd64 reading the following is recommended:
+     *     https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019
+     *     https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=vs-2019
+     *     https://docs.microsoft.com/en-us/cpp/build/stack-usage?view=vs-2019
+     *
+     * @todo 32bit
+     */
+    int rc = VINF_SUCCESS;
+    uint32_t idComponent = 0;
+    uint32_t iLevel = 0;
+    char aszPrefixStr[128]; /* Restricted size. */
+    char aszFmtStr[_1K]; /* Restricted size. */
+    DBGFADDRESS AddrVaList;
+    if (!pThis->f32Bit)
+    {
+        /*
+         * Grab the prefix, component, level, format string pointer from the registers and the argument list from the
+         * stack (mind the home area for the register arguments).
+         */
+        rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_RCX, &aszPrefixStr[0], sizeof(aszPrefixStr));
+        if (RT_SUCCESS(rc))
+            rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_RDX, &idComponent);
+        if (RT_SUCCESS(rc))
+            rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_R8, &iLevel);
+        if (RT_SUCCESS(rc))
+            rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_R9, &aszFmtStr[0], sizeof(aszFmtStr));
+        if (RT_SUCCESS(rc))
+        {
+            /* Grabbing the pointer to the va list. The stack layout when we are here looks like (each entry is 64bit):
+             *     +-------------+
+             *     |     ...     |
+             *     | VA list ptr |
+             *     |  (arg3/r9)  |
+             *     |  (arg2/r8)  |
+             *     |  (arg1/rdx) |
+             *     |  (arg0/rcx) |
+             *     | return RIP  |
+             *     +-------------+ <- RSP
+             */
+            uint64_t uRegRsp = 0;
+            rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_RSP, &uRegRsp);
+            if (rc == VINF_SUCCESS)
+            {
+                DBGFADDRESS AddrVaListPtr;
+                RTGCUINTPTR GCPtrVaList = 0;
+
+                DBGFR3AddrFromFlat(pUVM, &AddrVaListPtr, uRegRsp + 5 * sizeof(RTGCUINTPTR));
+                rc = DBGFR3MemRead(pUVM, idCpu, &AddrVaListPtr, &GCPtrVaList, sizeof(GCPtrVaList));
+                if (RT_SUCCESS(rc))
+                    DBGFR3AddrFromFlat(pUVM, &AddrVaList, GCPtrVaList);
+            }
+            else
+                rc = VERR_INVALID_STATE;
+        }
+    }
+    else
+        rc = VERR_NOT_IMPLEMENTED; /** @todo */
+
+    if (RT_SUCCESS(rc))
+    {
+        LogRel(("DigWinNt/DbgPrint: Queried arguments %s %#x %u %s %RGv\n", &aszPrefixStr[0], idComponent, iLevel, &aszFmtStr[0], AddrVaList.FlatPtr));
+        /** @todo Continue here. */
+    }
+    else
+        LogRel(("DigWinNt/DbgPrint: Failed to query all arguments with rc=%Rrc\n", rc));
+
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Disassembles the given instruction and checks whether it is a call with a fixed address.
+ *
+ * @returns Flag whether the insturction at the given address is a call.
+ * @param   pThis           The instance data.
+ * @param   pUVM            The user mode VM handle.
+ * @param   pAddrInsn       Guest address of the instruction.
+ * @param   pAddrCall       Where to store the destination if the instruction is a call.
+ */
+static bool dbgDiggerWinNtDbgPrintWrapperInsnIsCall(PDBGDIGGERWINNT pThis, PUVM pUVM, PCDBGFADDRESS pAddrInsn, PDBGFADDRESS pAddrCall)
+{
+    DISSTATE DisState;
+    RT_ZERO(DisState);
+
+    /* Prefetch the instruction. */
+    uint8_t abInstr[32];
+    int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrInsn, &abInstr[0], sizeof(abInstr));
+    if (RT_SUCCESS(rc))
+    {
+        uint32_t cbInsn = 0;
+        rc = DISInstr(&abInstr[0], pThis->f32Bit ? DISCPUMODE_32BIT : DISCPUMODE_64BIT, &DisState, &cbInsn);
+        if (   RT_SUCCESS(rc)
+            && DisState.pCurInstr->uOpcode == OP_CALL
+            && DisState.Param1.fUse & DISUSE_IMMEDIATE)
+        {
+            if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32 | DISUSE_IMMEDIATE64))
+                DBGFR3AddrFromFlat(pUVM, pAddrCall, DisState.Param1.uValue);
+            else if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32_REL | DISUSE_IMMEDIATE64_REL))
+            {
+                *pAddrCall = *pAddrInsn;
+                DBGFR3AddrAdd(pAddrCall, DisState.Param1.uValue + cbInsn);
+            }
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+/**
+ * Tries to find the single call instruction of the DbgPrint/etc. worker in the given control flow graph
+ * (single basic block assumed).
+ *
+ * @returns VBox status code.
+ * @param   pThis           The instance data.
+ * @param   pUVM            The user mode VM handle.
+ * @param   hFlow           The control flow graph handle.
+ * @param   pAddr           Where to store the worker address on success.
+ */
+static int dbgDiggerWinNtDbgPrintResolveWorker(PDBGDIGGERWINNT pThis, PUVM pUVM, DBGFFLOW hFlow, PDBGFADDRESS pAddr)
+{
+    DBGFFLOWBB hBb;
+    int rc = DBGFR3FlowQueryStartBb(hFlow, &hBb);
+    if (RT_SUCCESS(rc))
+    {
+        bool fCallFound = false;
+
+        for (uint32_t i = 0; i < DBGFR3FlowBbGetInstrCount(hBb) && RT_SUCCESS(rc); i++)
+        {
+            DBGFADDRESS AddrInsn;
+            uint32_t cbInsn;
+            rc = DBGFR3FlowBbQueryInstr(hBb, i, &AddrInsn, &cbInsn, NULL);
+            if (RT_SUCCESS(rc))
+            {
+                DBGFADDRESS AddrCall;
+                if (dbgDiggerWinNtDbgPrintWrapperInsnIsCall(pThis, pUVM, &AddrInsn, &AddrCall))
+                {
+                    if (!fCallFound)
+                    {
+                        *pAddr = AddrCall;
+                        fCallFound = true;
+                    }
+                    else
+                    {
+                        LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx contains multiple call instructions!\n"));
+                        rc = VERR_ALREADY_EXISTS;
+                    }
+                }
+            }
+        }
+
+        DBGFR3FlowBbRelease(hBb);
+    }
+
+    return rc;
+}
+
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+/**
+ * Tries to resolve and hook into the worker for all the DbgPrint like wrappers to be able
+ * to gather debug information from the system.
+ *
+ * @returns nothing.
+ * @param   pThis           The instance data.
+ * @param   pUVM            The user mode VM handle.
+ */
+static void dbgDiggerWinNtDbgPrintHook(PDBGDIGGERWINNT pThis, PUVM pUVM)
+{
+    /*
+     * This is a multi step process:
+     *     1. Try to resolve the address of vDbgPrint() (available since XP).
+     *     2. Create a control flow graph from the code and verify the following assumptions:
+     *         1. Only a single basic block.
+     *         2. Just one call instruction.
+     *         @todo More?
+     *     3. Get the address from the called worker
+     *     4. Set a hardware breakpoint with our callback.
+     */
+    RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
+    if (hAs != NIL_RTDBGAS)
+    {
+        RTDBGSYMBOL SymInfo;
+        int rc = RTDbgAsSymbolByName(hAs, "nt!vDbgPrintEx", &SymInfo, NULL /*phMod*/);
+        if (RT_SUCCESS(rc))
+        {
+            DBGFADDRESS Addr;
+            DBGFR3AddrFromFlat(pUVM, &Addr, (RTGCPTR)SymInfo.Value);
+
+            LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx resolved to %RGv\n", SymInfo.Value));
+
+            DBGFFLOW hCfg;
+            rc = DBGFR3FlowCreate(pUVM, 0 /*idCpu*/, &Addr, 512 /*cbDisasmMax*/,
+                                  0 /*fFlagsFlow*/, DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED | DBGF_DISAS_FLAGS_DEFAULT_MODE,
+                                  &hCfg);
+            if (RT_SUCCESS(rc))
+            {
+                /* Verify assumptions. */
+                if (DBGFR3FlowGetBbCount(hCfg) == 1)
+                {
+                    rc = dbgDiggerWinNtDbgPrintResolveWorker(pThis, pUVM, hCfg, &Addr);
+                    if (RT_SUCCESS(rc))
+                    {
+                        /* Try to hook the worker. */
+                        LogRel(("DigWinNt/DbgPrint: Worker for nt!vDbgPrintEx resolved to %RGv\n", Addr.FlatPtr));
+                        rc = DBGFR3BpOwnerCreate(pUVM, dbgDiggerWinNtDbgPrintHit, NULL /*pfnBpIoHit*/, &pThis->hBpOwnerDbgPrint);
+                        if (RT_SUCCESS(rc))
+                        {
+                            rc = DBGFR3BpSetInt3Ex(pUVM, pThis->hBpOwnerDbgPrint, pThis, 0 /*idCpu*/, &Addr, DBGF_BP_F_DEFAULT,
+                                                   0 /*iHitTrigger*/, 0 /*iHitDisable*/, &pThis->hBpDbgPrint);
+                            if (RT_SUCCESS(rc))
+                                LogRel(("DigWinNt/DbgPrint: Hooked nt!vDbgPrintEx worker hBp=%#x\n", pThis->hBpDbgPrint));
+                            else
+                            {
+                                LogRel(("DigWinNt/DbgPrint: Setting hardware breakpoint for nt!vDbgPrintEx worker failed with rc=%Rrc\n", rc));
+                                int rc2 = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint);
+                                pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
+                                AssertRC(rc2);
+                            }
+                        }
+                    }
+                    /* else LogRel() already done */
+                }
+                else
+                    LogRel(("DigWinNt/DbgPrint: Control flow graph for nt!vDbgPrintEx has more than one basic block (%u)\n",
+                            DBGFR3FlowGetBbCount(hCfg)));
+
+                DBGFR3FlowRelease(hCfg);
+            }
+            else
+                LogRel(("DigWinNt/DbgPrint: Failed to create control flow graph from nt!vDbgPrintEx rc=%Rrc\n", rc));
+        }
+        else
+            LogRel(("DigWinNt/DbgPrint: Failed to resolve nt!vDbgPrintEx -> rc=%Rrc\n", rc));
+        RTDbgAsRelease(hAs);
+    }
+    else
+        LogRel(("DigWinNt/DbgPrint: Failed to resolve kernel address space handle\n"));
+}
 
 
@@ -905,4 +1217,20 @@
     Assert(pThis->fValid);
 
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+    if (pThis->hBpDbgPrint != NIL_DBGFBP)
+    {
+        int rc = DBGFR3BpClear(pUVM, pThis->hBpDbgPrint);
+        AssertRC(rc);
+        pThis->hBpDbgPrint = NIL_DBGFBP;
+    }
+
+    if (pThis->hBpOwnerDbgPrint != NIL_DBGFBPOWNER)
+    {
+        int rc = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint);
+        AssertRC(rc);
+        pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
+    }
+#endif
+
     /*
      * As long as we're using our private LDR reader implementation,
@@ -1094,4 +1422,9 @@
     /* Try resolving the KPCR and KPCRB addresses for each vCPU. */
     dbgDiggerWinNtResolveKpcr(pThis, pUVM, pVMM);
+
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+    /* Try to hook into the DbgPrint/vDbgPrint... code so we can gather information from the drivers. */
+    dbgDiggerWinNtDbgPrintHook(pThis, pUVM);
+#endif
 
     pThis->fValid = true;
@@ -1418,4 +1751,9 @@
     pThis->IWinNt.u32EndMagic            = DBGFOSIWINNT_MAGIC;
 
+#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
+    pThis->hBpDbgPrint      = NIL_DBGFBP;
+    pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
+#endif
+
     return VINF_SUCCESS;
 }
