VirtualBox

Changeset 96867 in vbox for trunk/src/VBox/Debugger


Ignore:
Timestamp:
Sep 26, 2022 2:51:41 PM (2 years ago)
Author:
vboxsync
Message:

Debugger/DBGPlugInWinNt: Some unfinished code to hook into the guests DbgPrint/vDbgPrint API to get at kernel log messages (disabled by default), bugref:1098

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/VBox/Debugger/DBGPlugInWinNt.cpp

    r96407 r96867  
    3535#include <VBox/vmm/cpumctx.h>
    3636#include <VBox/vmm/mm.h>
     37#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     38# include <VBox/vmm/vmapi.h>
     39# include <VBox/dis.h>
     40#endif
    3741#include <VBox/vmm/vmmr3vtable.h>
    3842#include <VBox/err.h>
     
    270274    /** The Windows NT specifics interface. */
    271275    DBGFOSIWINNT        IWinNt;
     276
     277#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     278    /** Breakpoint owner handle for the DbgPrint/vDbgPrint{,Ex}... interception. */
     279    DBGFBPOWNER         hBpOwnerDbgPrint;
     280    /** Breakpoint handle for the DbgPrint/vDbgPrint{,Ex}... interception. */
     281    DBGFBP              hBpDbgPrint;
     282#endif
    272283} DBGDIGGERWINNT;
    273284/** Pointer to the linux guest OS digger instance data. */
     
    344355};
    345356
     357
     358#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     359/**
     360 * Queries the string from guest memory with the pointer in the given register, sanitizing it.
     361 *
     362 * @returns VBox status code.
     363 * @param   pUVM            The user mode VM handle.
     364 * @param   idCpu           The CPU ID.
     365 * @param   enmReg          The register to query the string pointer from.
     366 * @param   pszBuf          Where to store the sanitized string.
     367 * @param   cbBuf           Size of the buffer in number of bytes.
     368 */
     369static int dbgDiggerWinNtDbgPrintQueryStringFromReg(PUVM pUVM, VMCPUID idCpu, DBGFREG enmReg, char *pszBuf, size_t cbBuf)
     370{
     371    uint64_t u64RegPtr = 0;
     372    int rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, enmReg, &u64RegPtr);
     373    if (   rc == VINF_SUCCESS
     374        || rc == VINF_DBGF_ZERO_EXTENDED_REGISTER) /* Being strict about what we expect here. */
     375    {
     376        DBGFADDRESS AddrStr;
     377        DBGFR3AddrFromFlat(pUVM, &AddrStr, u64RegPtr);
     378        rc = DBGFR3MemRead(pUVM, idCpu, &AddrStr, pszBuf, cbBuf);
     379        if (RT_SUCCESS(rc))
     380        {
     381            /* Check that there is a zero terminator and purge invalid encoding (expecting UTF-8 here). */
     382            size_t idx = 0;
     383            for (idx = 0; idx < cbBuf; idx++)
     384                if (pszBuf[idx] == '\0')
     385                    break;
     386
     387            if (idx == cbBuf)
     388                pszBuf[cbBuf - 1] = '\0'; /* Force terminator, truncating the string. */
     389            else
     390                memset(&pszBuf[idx], 0, cbBuf - idx); /* Clear everything afterwards. */
     391
     392            /* Purge the string encoding. */
     393            RTStrPurgeEncoding(pszBuf);
     394        }
     395    }
     396    else if (RT_SUCCESS(rc))
     397        rc = VERR_INVALID_STATE;
     398
     399    return rc;
     400}
     401
     402
     403/**
     404 * @copydoc{FNDBGFBPHIT, Breakpoint callback for the DbgPrint interception.}
     405 */
     406static DECLCALLBACK(VBOXSTRICTRC) dbgDiggerWinNtDbgPrintHit(PVM pVM, VMCPUID idCpu, void *pvUserBp, DBGFBP hBp, PCDBGFBPPUB pBpPub, uint16_t fFlags)
     407{
     408    RT_NOREF(hBp, pBpPub, fFlags);
     409    PDBGDIGGERWINNT pThis = (PDBGDIGGERWINNT)pvUserBp;
     410    PUVM pUVM = VMR3GetUVM(pVM);
     411
     412    /*
     413     * The worker prototype looks like the following:
     414     *      vDbgPrintExWorker(PCCH Prefix, ULONG ComponentId, ULONG Level, PCCH Format, va_list arglist, BOOL fUnknown)
     415     *
     416     * Depending on the bitness the parameters are grabbed from the appropriate registers and stack locations.
     417     * For amd64 reading the following is recommended:
     418     *     https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019
     419     *     https://docs.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=vs-2019
     420     *     https://docs.microsoft.com/en-us/cpp/build/stack-usage?view=vs-2019
     421     *
     422     * @todo 32bit
     423     */
     424    int rc = VINF_SUCCESS;
     425    uint32_t idComponent = 0;
     426    uint32_t iLevel = 0;
     427    char aszPrefixStr[128]; /* Restricted size. */
     428    char aszFmtStr[_1K]; /* Restricted size. */
     429    DBGFADDRESS AddrVaList;
     430    if (!pThis->f32Bit)
     431    {
     432        /*
     433         * Grab the prefix, component, level, format string pointer from the registers and the argument list from the
     434         * stack (mind the home area for the register arguments).
     435         */
     436        rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_RCX, &aszPrefixStr[0], sizeof(aszPrefixStr));
     437        if (RT_SUCCESS(rc))
     438            rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_RDX, &idComponent);
     439        if (RT_SUCCESS(rc))
     440            rc = DBGFR3RegCpuQueryU32(pUVM, idCpu, DBGFREG_R8, &iLevel);
     441        if (RT_SUCCESS(rc))
     442            rc = dbgDiggerWinNtDbgPrintQueryStringFromReg(pUVM, idCpu, DBGFREG_R9, &aszFmtStr[0], sizeof(aszFmtStr));
     443        if (RT_SUCCESS(rc))
     444        {
     445            /* Grabbing the pointer to the va list. The stack layout when we are here looks like (each entry is 64bit):
     446             *     +-------------+
     447             *     |     ...     |
     448             *     | VA list ptr |
     449             *     |  (arg3/r9)  |
     450             *     |  (arg2/r8)  |
     451             *     |  (arg1/rdx) |
     452             *     |  (arg0/rcx) |
     453             *     | return RIP  |
     454             *     +-------------+ <- RSP
     455             */
     456            uint64_t uRegRsp = 0;
     457            rc = DBGFR3RegCpuQueryU64(pUVM, idCpu, DBGFREG_RSP, &uRegRsp);
     458            if (rc == VINF_SUCCESS)
     459            {
     460                DBGFADDRESS AddrVaListPtr;
     461                RTGCUINTPTR GCPtrVaList = 0;
     462
     463                DBGFR3AddrFromFlat(pUVM, &AddrVaListPtr, uRegRsp + 5 * sizeof(RTGCUINTPTR));
     464                rc = DBGFR3MemRead(pUVM, idCpu, &AddrVaListPtr, &GCPtrVaList, sizeof(GCPtrVaList));
     465                if (RT_SUCCESS(rc))
     466                    DBGFR3AddrFromFlat(pUVM, &AddrVaList, GCPtrVaList);
     467            }
     468            else
     469                rc = VERR_INVALID_STATE;
     470        }
     471    }
     472    else
     473        rc = VERR_NOT_IMPLEMENTED; /** @todo */
     474
     475    if (RT_SUCCESS(rc))
     476    {
     477        LogRel(("DigWinNt/DbgPrint: Queried arguments %s %#x %u %s %RGv\n", &aszPrefixStr[0], idComponent, iLevel, &aszFmtStr[0], AddrVaList.FlatPtr));
     478        /** @todo Continue here. */
     479    }
     480    else
     481        LogRel(("DigWinNt/DbgPrint: Failed to query all arguments with rc=%Rrc\n", rc));
     482
     483    return VINF_SUCCESS;
     484}
     485
     486
     487/**
     488 * Disassembles the given instruction and checks whether it is a call with a fixed address.
     489 *
     490 * @returns Flag whether the insturction at the given address is a call.
     491 * @param   pThis           The instance data.
     492 * @param   pUVM            The user mode VM handle.
     493 * @param   pAddrInsn       Guest address of the instruction.
     494 * @param   pAddrCall       Where to store the destination if the instruction is a call.
     495 */
     496static bool dbgDiggerWinNtDbgPrintWrapperInsnIsCall(PDBGDIGGERWINNT pThis, PUVM pUVM, PCDBGFADDRESS pAddrInsn, PDBGFADDRESS pAddrCall)
     497{
     498    DISSTATE DisState;
     499    RT_ZERO(DisState);
     500
     501    /* Prefetch the instruction. */
     502    uint8_t abInstr[32];
     503    int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrInsn, &abInstr[0], sizeof(abInstr));
     504    if (RT_SUCCESS(rc))
     505    {
     506        uint32_t cbInsn = 0;
     507        rc = DISInstr(&abInstr[0], pThis->f32Bit ? DISCPUMODE_32BIT : DISCPUMODE_64BIT, &DisState, &cbInsn);
     508        if (   RT_SUCCESS(rc)
     509            && DisState.pCurInstr->uOpcode == OP_CALL
     510            && DisState.Param1.fUse & DISUSE_IMMEDIATE)
     511        {
     512            if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32 | DISUSE_IMMEDIATE64))
     513                DBGFR3AddrFromFlat(pUVM, pAddrCall, DisState.Param1.uValue);
     514            else if (DisState.Param1.fUse & (DISUSE_IMMEDIATE32_REL | DISUSE_IMMEDIATE64_REL))
     515            {
     516                *pAddrCall = *pAddrInsn;
     517                DBGFR3AddrAdd(pAddrCall, DisState.Param1.uValue + cbInsn);
     518            }
     519
     520            return true;
     521        }
     522    }
     523
     524    return false;
     525}
     526
     527
     528/**
     529 * Tries to find the single call instruction of the DbgPrint/etc. worker in the given control flow graph
     530 * (single basic block assumed).
     531 *
     532 * @returns VBox status code.
     533 * @param   pThis           The instance data.
     534 * @param   pUVM            The user mode VM handle.
     535 * @param   hFlow           The control flow graph handle.
     536 * @param   pAddr           Where to store the worker address on success.
     537 */
     538static int dbgDiggerWinNtDbgPrintResolveWorker(PDBGDIGGERWINNT pThis, PUVM pUVM, DBGFFLOW hFlow, PDBGFADDRESS pAddr)
     539{
     540    DBGFFLOWBB hBb;
     541    int rc = DBGFR3FlowQueryStartBb(hFlow, &hBb);
     542    if (RT_SUCCESS(rc))
     543    {
     544        bool fCallFound = false;
     545
     546        for (uint32_t i = 0; i < DBGFR3FlowBbGetInstrCount(hBb) && RT_SUCCESS(rc); i++)
     547        {
     548            DBGFADDRESS AddrInsn;
     549            uint32_t cbInsn;
     550            rc = DBGFR3FlowBbQueryInstr(hBb, i, &AddrInsn, &cbInsn, NULL);
     551            if (RT_SUCCESS(rc))
     552            {
     553                DBGFADDRESS AddrCall;
     554                if (dbgDiggerWinNtDbgPrintWrapperInsnIsCall(pThis, pUVM, &AddrInsn, &AddrCall))
     555                {
     556                    if (!fCallFound)
     557                    {
     558                        *pAddr = AddrCall;
     559                        fCallFound = true;
     560                    }
     561                    else
     562                    {
     563                        LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx contains multiple call instructions!\n"));
     564                        rc = VERR_ALREADY_EXISTS;
     565                    }
     566                }
     567            }
     568        }
     569
     570        DBGFR3FlowBbRelease(hBb);
     571    }
     572
     573    return rc;
     574}
     575
     576
     577#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     578/**
     579 * Tries to resolve and hook into the worker for all the DbgPrint like wrappers to be able
     580 * to gather debug information from the system.
     581 *
     582 * @returns nothing.
     583 * @param   pThis           The instance data.
     584 * @param   pUVM            The user mode VM handle.
     585 */
     586static void dbgDiggerWinNtDbgPrintHook(PDBGDIGGERWINNT pThis, PUVM pUVM)
     587{
     588    /*
     589     * This is a multi step process:
     590     *     1. Try to resolve the address of vDbgPrint() (available since XP).
     591     *     2. Create a control flow graph from the code and verify the following assumptions:
     592     *         1. Only a single basic block.
     593     *         2. Just one call instruction.
     594     *         @todo More?
     595     *     3. Get the address from the called worker
     596     *     4. Set a hardware breakpoint with our callback.
     597     */
     598    RTDBGAS hAs = DBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL);
     599    if (hAs != NIL_RTDBGAS)
     600    {
     601        RTDBGSYMBOL SymInfo;
     602        int rc = RTDbgAsSymbolByName(hAs, "nt!vDbgPrintEx", &SymInfo, NULL /*phMod*/);
     603        if (RT_SUCCESS(rc))
     604        {
     605            DBGFADDRESS Addr;
     606            DBGFR3AddrFromFlat(pUVM, &Addr, (RTGCPTR)SymInfo.Value);
     607
     608            LogRel(("DigWinNt/DbgPrint: nt!vDbgPrintEx resolved to %RGv\n", SymInfo.Value));
     609
     610            DBGFFLOW hCfg;
     611            rc = DBGFR3FlowCreate(pUVM, 0 /*idCpu*/, &Addr, 512 /*cbDisasmMax*/,
     612                                  0 /*fFlagsFlow*/, DBGF_DISAS_FLAGS_UNPATCHED_BYTES | DBGF_DISAS_FLAGS_ANNOTATE_PATCHED | DBGF_DISAS_FLAGS_DEFAULT_MODE,
     613                                  &hCfg);
     614            if (RT_SUCCESS(rc))
     615            {
     616                /* Verify assumptions. */
     617                if (DBGFR3FlowGetBbCount(hCfg) == 1)
     618                {
     619                    rc = dbgDiggerWinNtDbgPrintResolveWorker(pThis, pUVM, hCfg, &Addr);
     620                    if (RT_SUCCESS(rc))
     621                    {
     622                        /* Try to hook the worker. */
     623                        LogRel(("DigWinNt/DbgPrint: Worker for nt!vDbgPrintEx resolved to %RGv\n", Addr.FlatPtr));
     624                        rc = DBGFR3BpOwnerCreate(pUVM, dbgDiggerWinNtDbgPrintHit, NULL /*pfnBpIoHit*/, &pThis->hBpOwnerDbgPrint);
     625                        if (RT_SUCCESS(rc))
     626                        {
     627                            rc = DBGFR3BpSetInt3Ex(pUVM, pThis->hBpOwnerDbgPrint, pThis, 0 /*idCpu*/, &Addr, DBGF_BP_F_DEFAULT,
     628                                                   0 /*iHitTrigger*/, 0 /*iHitDisable*/, &pThis->hBpDbgPrint);
     629                            if (RT_SUCCESS(rc))
     630                                LogRel(("DigWinNt/DbgPrint: Hooked nt!vDbgPrintEx worker hBp=%#x\n", pThis->hBpDbgPrint));
     631                            else
     632                            {
     633                                LogRel(("DigWinNt/DbgPrint: Setting hardware breakpoint for nt!vDbgPrintEx worker failed with rc=%Rrc\n", rc));
     634                                int rc2 = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint);
     635                                pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
     636                                AssertRC(rc2);
     637                            }
     638                        }
     639                    }
     640                    /* else LogRel() already done */
     641                }
     642                else
     643                    LogRel(("DigWinNt/DbgPrint: Control flow graph for nt!vDbgPrintEx has more than one basic block (%u)\n",
     644                            DBGFR3FlowGetBbCount(hCfg)));
     645
     646                DBGFR3FlowRelease(hCfg);
     647            }
     648            else
     649                LogRel(("DigWinNt/DbgPrint: Failed to create control flow graph from nt!vDbgPrintEx rc=%Rrc\n", rc));
     650        }
     651        else
     652            LogRel(("DigWinNt/DbgPrint: Failed to resolve nt!vDbgPrintEx -> rc=%Rrc\n", rc));
     653        RTDbgAsRelease(hAs);
     654    }
     655    else
     656        LogRel(("DigWinNt/DbgPrint: Failed to resolve kernel address space handle\n"));
     657}
    346658
    347659
     
    9051217    Assert(pThis->fValid);
    9061218
     1219#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     1220    if (pThis->hBpDbgPrint != NIL_DBGFBP)
     1221    {
     1222        int rc = DBGFR3BpClear(pUVM, pThis->hBpDbgPrint);
     1223        AssertRC(rc);
     1224        pThis->hBpDbgPrint = NIL_DBGFBP;
     1225    }
     1226
     1227    if (pThis->hBpOwnerDbgPrint != NIL_DBGFBPOWNER)
     1228    {
     1229        int rc = DBGFR3BpOwnerDestroy(pUVM, pThis->hBpOwnerDbgPrint);
     1230        AssertRC(rc);
     1231        pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
     1232    }
     1233#endif
     1234
    9071235    /*
    9081236     * As long as we're using our private LDR reader implementation,
     
    10941422    /* Try resolving the KPCR and KPCRB addresses for each vCPU. */
    10951423    dbgDiggerWinNtResolveKpcr(pThis, pUVM, pVMM);
     1424
     1425#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     1426    /* Try to hook into the DbgPrint/vDbgPrint... code so we can gather information from the drivers. */
     1427    dbgDiggerWinNtDbgPrintHook(pThis, pUVM);
     1428#endif
    10961429
    10971430    pThis->fValid = true;
     
    14181751    pThis->IWinNt.u32EndMagic            = DBGFOSIWINNT_MAGIC;
    14191752
     1753#ifdef VBOX_DEBUGGER_WITH_WIN_DBG_PRINT_HOOKING
     1754    pThis->hBpDbgPrint      = NIL_DBGFBP;
     1755    pThis->hBpOwnerDbgPrint = NIL_DBGFBPOWNER;
     1756#endif
     1757
    14201758    return VINF_SUCCESS;
    14211759}
Note: See TracChangeset for help on using the changeset viewer.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette