/* $Id: PATM.cpp 50575 2014-02-25 13:07:16Z vboxsync $ */ /** @file * PATM - Dynamic Guest OS Patching Manager * * @note Never ever reuse patch memory!! */ /* * Copyright (C) 2006-2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /******************************************************************************* * Header Files * *******************************************************************************/ #define LOG_GROUP LOG_GROUP_PATM #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "PATMInternal.h" #include "PATMPatch.h" #include #include #include #include #include #include #include #include #include #include "internal/pgm.h" #include #include "PATMA.h" //#define PATM_REMOVE_PATCH_ON_TOO_MANY_TRAPS //#define PATM_DISABLE_ALL /** * Refresh trampoline patch state. */ typedef struct PATMREFRESHPATCH { /** Pointer to the VM structure. */ PVM pVM; /** The trampoline patch record. */ PPATCHINFO pPatchTrampoline; /** The new patch we want to jump to. */ PPATCHINFO pPatchRec; } PATMREFRESHPATCH, *PPATMREFRESHPATCH; #define PATMREAD_RAWCODE 1 /* read code as-is */ #define PATMREAD_ORGCODE 2 /* read original guest opcode bytes; not the patched bytes */ #define PATMREAD_NOCHECK 4 /* don't check for patch conflicts */ /* * Private structure used during disassembly */ typedef struct { PVM pVM; PPATCHINFO pPatchInfo; R3PTRTYPE(uint8_t *) pbInstrHC; RTRCPTR pInstrGC; uint32_t fReadFlags; } PATMDISASM, *PPATMDISASM; /******************************************************************************* * Internal Functions * *******************************************************************************/ static int patmDisableUnusablePatch(PVM pVM, RTRCPTR pInstrGC, RTRCPTR pConflictAddr, PPATCHINFO pPatch); static int patmActivateInt3Patch(PVM pVM, PPATCHINFO pPatch); static int patmDeactivateInt3Patch(PVM pVM, PPATCHINFO pPatch); #ifdef LOG_ENABLED // keep gcc quiet static bool patmIsCommonIDTHandlerPatch(PVM pVM, RTRCPTR pInstrGC); #endif #ifdef VBOX_WITH_STATISTICS static const char *PATMPatchType(PVM pVM, PPATCHINFO pPatch); static void patmResetStat(PVM pVM, void *pvSample); static void patmPrintStat(PVM pVM, void *pvSample, char *pszBuf, size_t cchBuf); #endif #define patmPatchHCPtr2PatchGCPtr(pVM, pHC) (pVM->patm.s.pPatchMemGC + (pHC - pVM->patm.s.pPatchMemHC)) #define patmPatchGCPtr2PatchHCPtr(pVM, pGC) (pVM->patm.s.pPatchMemHC + (pGC - pVM->patm.s.pPatchMemGC)) static int patmReinit(PVM pVM); static DECLCALLBACK(int) RelocatePatches(PAVLOU32NODECORE pNode, void *pParam); static RTRCPTR patmR3GuestGCPtrToPatchGCPtrSimple(PVM pVM, RCPTRTYPE(uint8_t*) pInstrGC); static int patmR3MarkDirtyPatch(PVM pVM, PPATCHINFO pPatch); #ifdef VBOX_WITH_DEBUGGER static DECLCALLBACK(int) DisableAllPatches(PAVLOU32NODECORE pNode, void *pVM); static FNDBGCCMD patmr3CmdOn; static FNDBGCCMD patmr3CmdOff; /** Command descriptors. */ static const DBGCCMD g_aCmds[] = { /* pszCmd, cArgsMin, cArgsMax, paArgDesc, cArgDescs, fFlags, pfnHandler pszSyntax, ....pszDescription */ { "patmon", 0, 0, NULL, 0, 0, patmr3CmdOn, "", "Enable patching." }, { "patmoff", 0, 0, NULL, 0, 0, patmr3CmdOff, "", "Disable patching." }, }; #endif /* Don't want to break saved states, so put it here as a global variable. */ static unsigned int cIDTHandlersDisabled = 0; /** * Initializes the PATM. * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR3_INT_DECL(int) PATMR3Init(PVM pVM) { int rc; /* * We only need a saved state dummy loader if HM is enabled. */ if (HMIsEnabled(pVM)) { pVM->fPATMEnabled = false; return SSMR3RegisterStub(pVM, "PATM", 0); } /* * Raw-mode. */ Log(("PATMR3Init: Patch record size %d\n", sizeof(PATCHINFO))); /* These values can't change as they are hardcoded in patch code (old saved states!) */ AssertCompile(VMCPU_FF_TIMER == RT_BIT_32(2)); AssertCompile(VM_FF_REQUEST == VMCPU_FF_REQUEST); AssertCompile(VMCPU_FF_INTERRUPT_APIC == RT_BIT_32(0)); AssertCompile(VMCPU_FF_INTERRUPT_PIC == RT_BIT_32(1)); AssertReleaseMsg(PATMInterruptFlag == (VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC | VMCPU_FF_TIMER | VMCPU_FF_REQUEST), ("Interrupt flags out of sync!! PATMInterruptFlag=%#x expected %#x. broken assembler?\n", PATMInterruptFlag, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC | VMCPU_FF_TIMER | VMCPU_FF_REQUEST)); /* Allocate patch memory and GC patch state memory. */ pVM->patm.s.cbPatchMem = PATCH_MEMORY_SIZE; /* Add another page in case the generated code is much larger than expected. */ /** @todo bad safety precaution */ rc = MMR3HyperAllocOnceNoRel(pVM, PATCH_MEMORY_SIZE + PAGE_SIZE + PATM_STACK_TOTAL_SIZE + PAGE_SIZE + PATM_STAT_MEMSIZE, PAGE_SIZE, MM_TAG_PATM, (void **)&pVM->patm.s.pPatchMemHC); if (RT_FAILURE(rc)) { Log(("MMHyperAlloc failed with %Rrc\n", rc)); return rc; } pVM->patm.s.pPatchMemGC = MMHyperR3ToRC(pVM, pVM->patm.s.pPatchMemHC); /* PATM stack page for call instruction execution. (2 parts: one for our private stack and one to store the original return address */ pVM->patm.s.pGCStackHC = (RTRCPTR *)(pVM->patm.s.pPatchMemHC + PATCH_MEMORY_SIZE + PAGE_SIZE); pVM->patm.s.pGCStackGC = MMHyperR3ToRC(pVM, pVM->patm.s.pGCStackHC); patmR3DbgInit(pVM); /* * Hypervisor memory for GC status data (read/write) * * Note1: This is non-critical data; if trashed by the guest, then it will only cause problems for itself * Note2: This doesn't really belong here, but we need access to it for relocation purposes * */ Assert(sizeof(PATMGCSTATE) < PAGE_SIZE); /* Note: hardcoded dependencies on this exist. */ pVM->patm.s.pGCStateHC = (PPATMGCSTATE)((uint8_t *)pVM->patm.s.pGCStackHC + PATM_STACK_TOTAL_SIZE); pVM->patm.s.pGCStateGC = MMHyperR3ToRC(pVM, pVM->patm.s.pGCStateHC); /* Hypervisor memory for patch statistics */ pVM->patm.s.pStatsHC = (PSTAMRATIOU32)((uint8_t *)pVM->patm.s.pGCStateHC + PAGE_SIZE); pVM->patm.s.pStatsGC = MMHyperR3ToRC(pVM, pVM->patm.s.pStatsHC); /* Memory for patch lookup trees. */ rc = MMHyperAlloc(pVM, sizeof(*pVM->patm.s.PatchLookupTreeHC), 0, MM_TAG_PATM, (void **)&pVM->patm.s.PatchLookupTreeHC); AssertRCReturn(rc, rc); pVM->patm.s.PatchLookupTreeGC = MMHyperR3ToRC(pVM, pVM->patm.s.PatchLookupTreeHC); #ifdef RT_ARCH_AMD64 /* see patmReinit(). */ /* Check CFGM option. */ rc = CFGMR3QueryBool(CFGMR3GetRoot(pVM), "PATMEnabled", &pVM->fPATMEnabled); if (RT_FAILURE(rc)) # ifdef PATM_DISABLE_ALL pVM->fPATMEnabled = false; # else pVM->fPATMEnabled = true; # endif #endif rc = patmReinit(pVM); AssertRC(rc); if (RT_FAILURE(rc)) return rc; /* * Register save and load state notifiers. */ rc = SSMR3RegisterInternal(pVM, "PATM", 0, PATM_SSM_VERSION, sizeof(pVM->patm.s) + PATCH_MEMORY_SIZE + PAGE_SIZE + PATM_STACK_TOTAL_SIZE + PAGE_SIZE, NULL, NULL, NULL, NULL, patmR3Save, NULL, NULL, patmR3Load, NULL); AssertRCReturn(rc, rc); #ifdef VBOX_WITH_DEBUGGER /* * Debugger commands. */ static bool s_fRegisteredCmds = false; if (!s_fRegisteredCmds) { int rc2 = DBGCRegisterCommands(&g_aCmds[0], RT_ELEMENTS(g_aCmds)); if (RT_SUCCESS(rc2)) s_fRegisteredCmds = true; } #endif #ifdef VBOX_WITH_STATISTICS STAM_REG(pVM, &pVM->patm.s.StatNrOpcodeRead, STAMTYPE_COUNTER, "/PATM/OpcodeBytesRead", STAMUNIT_OCCURENCES, "The number of opcode bytes read by the recompiler."); STAM_REG(pVM, &pVM->patm.s.StatPATMMemoryUsed,STAMTYPE_COUNTER, "/PATM/MemoryUsed", STAMUNIT_OCCURENCES, "The amount of hypervisor heap used for patches."); STAM_REG(pVM, &pVM->patm.s.StatDisabled, STAMTYPE_COUNTER, "/PATM/Patch/Disabled", STAMUNIT_OCCURENCES, "Number of times patches were disabled."); STAM_REG(pVM, &pVM->patm.s.StatEnabled, STAMTYPE_COUNTER, "/PATM/Patch/Enabled", STAMUNIT_OCCURENCES, "Number of times patches were enabled."); STAM_REG(pVM, &pVM->patm.s.StatDirty, STAMTYPE_COUNTER, "/PATM/Patch/Dirty", STAMUNIT_OCCURENCES, "Number of times patches were marked dirty."); STAM_REG(pVM, &pVM->patm.s.StatUnusable, STAMTYPE_COUNTER, "/PATM/Patch/Unusable", STAMUNIT_OCCURENCES, "Number of unusable patches (conflicts)."); STAM_REG(pVM, &pVM->patm.s.StatInstalled, STAMTYPE_COUNTER, "/PATM/Patch/Installed", STAMUNIT_OCCURENCES, "Number of installed patches."); STAM_REG(pVM, &pVM->patm.s.StatInt3Callable, STAMTYPE_COUNTER, "/PATM/Patch/Int3Callable", STAMUNIT_OCCURENCES, "Number of cli patches turned into int3 patches."); STAM_REG(pVM, &pVM->patm.s.StatInt3BlockRun, STAMTYPE_COUNTER, "/PATM/Patch/Run/Int3", STAMUNIT_OCCURENCES, "Number of times an int3 block patch was executed."); STAMR3RegisterF(pVM, &pVM->patm.s.pGCStateHC->uPatchCalls, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Patch/Run/Normal"); STAM_REG(pVM, &pVM->patm.s.StatInstalledFunctionPatches, STAMTYPE_COUNTER, "/PATM/Patch/Installed/Function", STAMUNIT_OCCURENCES, "Number of installed function duplication patches."); STAM_REG(pVM, &pVM->patm.s.StatInstalledTrampoline, STAMTYPE_COUNTER, "/PATM/Patch/Installed/Trampoline", STAMUNIT_OCCURENCES, "Number of installed trampoline patches."); STAM_REG(pVM, &pVM->patm.s.StatInstalledJump, STAMTYPE_COUNTER, "/PATM/Patch/Installed/Jump", STAMUNIT_OCCURENCES, "Number of installed jump patches."); STAM_REG(pVM, &pVM->patm.s.StatOverwritten, STAMTYPE_COUNTER, "/PATM/Patch/Overwritten", STAMUNIT_OCCURENCES, "Number of overwritten patches."); STAM_REG(pVM, &pVM->patm.s.StatFixedConflicts,STAMTYPE_COUNTER, "/PATM/Patch/ConflictFixed", STAMUNIT_OCCURENCES, "Number of fixed conflicts."); STAM_REG(pVM, &pVM->patm.s.StatFlushed, STAMTYPE_COUNTER, "/PATM/Patch/Flushed", STAMUNIT_OCCURENCES, "Number of flushes of pages with patch jumps."); STAM_REG(pVM, &pVM->patm.s.StatMonitored, STAMTYPE_COUNTER, "/PATM/Patch/Monitored", STAMUNIT_OCCURENCES, "Number of patches in monitored patch pages."); STAM_REG(pVM, &pVM->patm.s.StatPageBoundaryCrossed, STAMTYPE_COUNTER, "/PATM/Patch/BoundaryCross", STAMUNIT_OCCURENCES, "Number of refused patches due to patch jump crossing page boundary."); STAM_REG(pVM, &pVM->patm.s.StatHandleTrap, STAMTYPE_PROFILE, "/PATM/HandleTrap", STAMUNIT_TICKS_PER_CALL, "Profiling of PATMR3HandleTrap"); STAM_REG(pVM, &pVM->patm.s.StatPushTrap, STAMTYPE_COUNTER, "/PATM/HandleTrap/PushWP", STAMUNIT_OCCURENCES, "Number of traps due to monitored stack pages."); STAM_REG(pVM, &pVM->patm.s.StatSwitchBack, STAMTYPE_COUNTER, "/PATM/SwitchBack", STAMUNIT_OCCURENCES, "Switch back to original guest code when IF=1 & executing PATM instructions"); STAM_REG(pVM, &pVM->patm.s.StatSwitchBackFail,STAMTYPE_COUNTER, "/PATM/SwitchBackFail", STAMUNIT_OCCURENCES, "Failed switch back to original guest code when IF=1 & executing PATM instructions"); STAM_REG(pVM, &pVM->patm.s.StatDuplicateREQFailed, STAMTYPE_COUNTER, "/PATM/Function/DupREQ/Failed", STAMUNIT_OCCURENCES, "Nr of failed PATMR3DuplicateFunctionRequest calls"); STAM_REG(pVM, &pVM->patm.s.StatDuplicateREQSuccess, STAMTYPE_COUNTER, "/PATM/Function/DupREQ/Success", STAMUNIT_OCCURENCES, "Nr of successful PATMR3DuplicateFunctionRequest calls"); STAM_REG(pVM, &pVM->patm.s.StatDuplicateUseExisting,STAMTYPE_COUNTER, "/PATM/Function/DupREQ/UseExist", STAMUNIT_OCCURENCES, "Nr of successful PATMR3DuplicateFunctionRequest calls when using an existing patch"); STAM_REG(pVM, &pVM->patm.s.StatFunctionLookupInsert, STAMTYPE_COUNTER, "/PATM/Function/Lookup/Insert", STAMUNIT_OCCURENCES, "Nr of successful function address insertions"); STAM_REG(pVM, &pVM->patm.s.StatFunctionLookupReplace, STAMTYPE_COUNTER, "/PATM/Function/Lookup/Replace", STAMUNIT_OCCURENCES, "Nr of successful function address replacements"); STAM_REG(pVM, &pVM->patm.s.StatU32FunctionMaxSlotsUsed, STAMTYPE_U32_RESET,"/PATM/Function/Lookup/MaxSlots", STAMUNIT_OCCURENCES, "Maximum nr of lookup slots used in all call patches"); STAM_REG(pVM, &pVM->patm.s.StatFunctionFound, STAMTYPE_COUNTER, "/PATM/Function/Found", STAMUNIT_OCCURENCES, "Nr of successful function patch lookups in GC"); STAM_REG(pVM, &pVM->patm.s.StatFunctionNotFound, STAMTYPE_COUNTER, "/PATM/Function/NotFound", STAMUNIT_OCCURENCES, "Nr of failed function patch lookups in GC"); STAM_REG(pVM, &pVM->patm.s.StatPatchWrite, STAMTYPE_PROFILE, "/PATM/Write/Handle", STAMUNIT_TICKS_PER_CALL, "Profiling of PATMR3PatchWrite"); STAM_REG(pVM, &pVM->patm.s.StatPatchWriteDetect, STAMTYPE_PROFILE, "/PATM/Write/Detect", STAMUNIT_TICKS_PER_CALL, "Profiling of PATMIsWriteToPatchPage"); STAM_REG(pVM, &pVM->patm.s.StatPatchWriteInterpreted, STAMTYPE_COUNTER, "/PATM/Write/Interpreted/Success", STAMUNIT_OCCURENCES, "Nr of interpreted patch writes."); STAM_REG(pVM, &pVM->patm.s.StatPatchWriteInterpretedFailed, STAMTYPE_COUNTER, "/PATM/Write/Interpreted/Failed", STAMUNIT_OCCURENCES, "Nr of failed interpreted patch writes."); STAM_REG(pVM, &pVM->patm.s.StatPatchRefreshSuccess, STAMTYPE_COUNTER, "/PATM/Refresh/Success", STAMUNIT_OCCURENCES, "Successful patch refreshes"); STAM_REG(pVM, &pVM->patm.s.StatPatchRefreshFailed, STAMTYPE_COUNTER, "/PATM/Refresh/Failure", STAMUNIT_OCCURENCES, "Failed patch refreshes"); STAM_REG(pVM, &pVM->patm.s.StatPatchPageInserted, STAMTYPE_COUNTER, "/PATM/Page/Inserted", STAMUNIT_OCCURENCES, "Nr of inserted guest pages that were patched"); STAM_REG(pVM, &pVM->patm.s.StatPatchPageRemoved, STAMTYPE_COUNTER, "/PATM/Page/Removed", STAMUNIT_OCCURENCES, "Nr of removed guest pages that were patched"); STAM_REG(pVM, &pVM->patm.s.StatInstrDirty, STAMTYPE_COUNTER, "/PATM/Instr/Dirty/Detected", STAMUNIT_OCCURENCES, "Number of times instructions were marked dirty."); STAM_REG(pVM, &pVM->patm.s.StatInstrDirtyGood, STAMTYPE_COUNTER, "/PATM/Instr/Dirty/Corrected", STAMUNIT_OCCURENCES, "Number of times instructions were marked dirty and corrected later on."); STAM_REG(pVM, &pVM->patm.s.StatInstrDirtyBad, STAMTYPE_COUNTER, "/PATM/Instr/Dirty/Failed", STAMUNIT_OCCURENCES, "Number of times instructions were marked dirty and we were not able to correct them."); STAM_REG(pVM, &pVM->patm.s.StatSysEnter, STAMTYPE_COUNTER, "/PATM/Emul/SysEnter", STAMUNIT_OCCURENCES, "Number of times sysenter was emulated."); STAM_REG(pVM, &pVM->patm.s.StatSysExit, STAMTYPE_COUNTER, "/PATM/Emul/SysExit" , STAMUNIT_OCCURENCES, "Number of times sysexit was emulated."); STAM_REG(pVM, &pVM->patm.s.StatEmulIret, STAMTYPE_COUNTER, "/PATM/Emul/Iret/Success", STAMUNIT_OCCURENCES, "Number of times iret was emulated."); STAM_REG(pVM, &pVM->patm.s.StatEmulIretFailed, STAMTYPE_COUNTER, "/PATM/Emul/Iret/Failed", STAMUNIT_OCCURENCES, "Number of times iret was emulated."); STAM_REG(pVM, &pVM->patm.s.StatGenRet, STAMTYPE_COUNTER, "/PATM/Gen/Ret" , STAMUNIT_OCCURENCES, "Number of generated ret instructions."); STAM_REG(pVM, &pVM->patm.s.StatGenRetReused, STAMTYPE_COUNTER, "/PATM/Gen/RetReused" , STAMUNIT_OCCURENCES, "Number of reused ret instructions."); STAM_REG(pVM, &pVM->patm.s.StatGenCall, STAMTYPE_COUNTER, "/PATM/Gen/Call", STAMUNIT_OCCURENCES, "Number of generated call instructions."); STAM_REG(pVM, &pVM->patm.s.StatGenJump, STAMTYPE_COUNTER, "/PATM/Gen/Jmp" , STAMUNIT_OCCURENCES, "Number of generated indirect jump instructions."); STAM_REG(pVM, &pVM->patm.s.StatGenPopf, STAMTYPE_COUNTER, "/PATM/Gen/Popf" , STAMUNIT_OCCURENCES, "Number of generated popf instructions."); STAM_REG(pVM, &pVM->patm.s.StatCheckPendingIRQ, STAMTYPE_COUNTER, "/PATM/GC/CheckIRQ" , STAMUNIT_OCCURENCES, "Number of traps that ask to check for pending irqs."); #endif /* VBOX_WITH_STATISTICS */ Log(("PATMCallRecord.size %d\n", PATMCallRecord.size)); Log(("PATMCallIndirectRecord.size %d\n", PATMCallIndirectRecord.size)); Log(("PATMRetRecord.size %d\n", PATMRetRecord.size)); Log(("PATMJumpIndirectRecord.size %d\n", PATMJumpIndirectRecord.size)); Log(("PATMPopf32Record.size %d\n", PATMPopf32Record.size)); Log(("PATMIretRecord.size %d\n", PATMIretRecord.size)); Log(("PATMStiRecord.size %d\n", PATMStiRecord.size)); Log(("PATMCheckIFRecord.size %d\n", PATMCheckIFRecord.size)); return rc; } /** * Finalizes HMA page attributes. * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR3_INT_DECL(int) PATMR3InitFinalize(PVM pVM) { if (HMIsEnabled(pVM)) return VINF_SUCCESS; /* The GC state, stack and statistics must be read/write for the guest (supervisor only of course). */ int rc = PGMMapSetPage(pVM, pVM->patm.s.pGCStateGC, PAGE_SIZE, X86_PTE_P | X86_PTE_A | X86_PTE_D | X86_PTE_RW); if (RT_FAILURE(rc)) Log(("PATMR3InitFinalize: PGMMapSetPage failed with %Rrc!!\n", rc)); rc = PGMMapSetPage(pVM, pVM->patm.s.pGCStackGC, PATM_STACK_TOTAL_SIZE, X86_PTE_P | X86_PTE_A | X86_PTE_D | X86_PTE_RW); if (RT_FAILURE(rc)) Log(("PATMR3InitFinalize: PGMMapSetPage failed with %Rrc!!\n", rc)); rc = PGMMapSetPage(pVM, pVM->patm.s.pStatsGC, PATM_STAT_MEMSIZE, X86_PTE_P | X86_PTE_A | X86_PTE_D | X86_PTE_RW); if (RT_FAILURE(rc)) Log(("PATMR3InitFinalize: PGMMapSetPage failed with %Rrc!!\n", rc)); return rc; } /** * (Re)initializes PATM * * @param pVM The VM. */ static int patmReinit(PVM pVM) { int rc; /* * Assert alignment and sizes. */ AssertRelease(!(RT_OFFSETOF(VM, patm.s) & 31)); AssertRelease(sizeof(pVM->patm.s) <= sizeof(pVM->patm.padding)); /* * Setup any fixed pointers and offsets. */ pVM->patm.s.offVM = RT_OFFSETOF(VM, patm); #ifndef RT_ARCH_AMD64 /* would be nice if this was changed everywhere. was driving me crazy on AMD64. */ #ifndef PATM_DISABLE_ALL pVM->fPATMEnabled = true; #endif #endif Assert(pVM->patm.s.pGCStateHC); memset(pVM->patm.s.pGCStateHC, 0, PAGE_SIZE); AssertReleaseMsg(pVM->patm.s.pGCStateGC, ("Impossible! MMHyperHC2GC(%p) failed!\n", pVM->patm.s.pGCStateGC)); Log(("Patch memory allocated at %p - %RRv\n", pVM->patm.s.pPatchMemHC, pVM->patm.s.pPatchMemGC)); pVM->patm.s.pGCStateHC->uVMFlags = X86_EFL_IF; Assert(pVM->patm.s.pGCStackHC); memset(pVM->patm.s.pGCStackHC, 0, PAGE_SIZE); AssertReleaseMsg(pVM->patm.s.pGCStackGC, ("Impossible! MMHyperHC2GC(%p) failed!\n", pVM->patm.s.pGCStackGC)); pVM->patm.s.pGCStateHC->Psp = PATM_STACK_SIZE; pVM->patm.s.pGCStateHC->fPIF = 1; /* PATM Interrupt Flag */ Assert(pVM->patm.s.pStatsHC); memset(pVM->patm.s.pStatsHC, 0, PATM_STAT_MEMSIZE); AssertReleaseMsg(pVM->patm.s.pStatsGC, ("Impossible! MMHyperHC2GC(%p) failed!\n", pVM->patm.s.pStatsGC)); Assert(pVM->patm.s.pPatchMemHC); Assert(pVM->patm.s.pPatchMemGC == MMHyperR3ToRC(pVM, pVM->patm.s.pPatchMemHC)); memset(pVM->patm.s.pPatchMemHC, 0, PATCH_MEMORY_SIZE); AssertReleaseMsg(pVM->patm.s.pPatchMemGC, ("Impossible! MMHyperHC2GC(%p) failed!\n", pVM->patm.s.pPatchMemHC)); /* Needed for future patching of sldt/sgdt/sidt/str etc. */ pVM->patm.s.pCPUMCtxGC = VM_RC_ADDR(pVM, CPUMQueryGuestCtxPtr(VMMGetCpu(pVM))); Assert(pVM->patm.s.PatchLookupTreeHC); Assert(pVM->patm.s.PatchLookupTreeGC == MMHyperR3ToRC(pVM, pVM->patm.s.PatchLookupTreeHC)); /* * (Re)Initialize PATM structure */ Assert(!pVM->patm.s.PatchLookupTreeHC->PatchTree); Assert(!pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr); Assert(!pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage); pVM->patm.s.offPatchMem = 16; /* don't start with zero here */ pVM->patm.s.uCurrentPatchIdx = 1; /* Index zero is a dummy */ pVM->patm.s.pvFaultMonitor = 0; pVM->patm.s.deltaReloc = 0; /* Lowest and highest patched instruction */ pVM->patm.s.pPatchedInstrGCLowest = ~0; pVM->patm.s.pPatchedInstrGCHighest = 0; pVM->patm.s.PatchLookupTreeHC->PatchTree = 0; pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr = 0; pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage = 0; pVM->patm.s.pfnSysEnterPatchGC = 0; pVM->patm.s.pfnSysEnterGC = 0; pVM->patm.s.fOutOfMemory = false; pVM->patm.s.pfnHelperCallGC = 0; patmR3DbgReset(pVM); /* Generate all global functions to be used by future patches. */ /* We generate a fake patch in order to use the existing code for relocation. */ rc = MMHyperAlloc(pVM, sizeof(PATMPATCHREC), 0, MM_TAG_PATM_PATCH, (void **)&pVM->patm.s.pGlobalPatchRec); if (RT_FAILURE(rc)) { Log(("Out of memory!!!!\n")); return VERR_NO_MEMORY; } pVM->patm.s.pGlobalPatchRec->patch.flags = PATMFL_GLOBAL_FUNCTIONS; pVM->patm.s.pGlobalPatchRec->patch.uState = PATCH_ENABLED; pVM->patm.s.pGlobalPatchRec->patch.pPatchBlockOffset = pVM->patm.s.offPatchMem; rc = patmPatchGenGlobalFunctions(pVM, &pVM->patm.s.pGlobalPatchRec->patch); AssertRC(rc); /* Update free pointer in patch memory. */ pVM->patm.s.offPatchMem += pVM->patm.s.pGlobalPatchRec->patch.uCurPatchOffset; /* Round to next 8 byte boundary. */ pVM->patm.s.offPatchMem = RT_ALIGN_32(pVM->patm.s.offPatchMem, 8); return rc; } /** * Applies relocations to data and code managed by this * component. This function will be called at init and * whenever the VMM need to relocate it self inside the GC. * * The PATM will update the addresses used by the switcher. * * @param pVM The VM. */ VMMR3_INT_DECL(void) PATMR3Relocate(PVM pVM) { if (HMIsEnabled(pVM)) return; RTRCPTR GCPtrNew = MMHyperR3ToRC(pVM, pVM->patm.s.pGCStateHC); RTRCINTPTR delta = GCPtrNew - pVM->patm.s.pGCStateGC; Log(("PATMR3Relocate from %RRv to %RRv - delta %08X\n", pVM->patm.s.pGCStateGC, GCPtrNew, delta)); if (delta) { PCPUMCTX pCtx; /* Update CPUMCTX guest context pointer. */ pVM->patm.s.pCPUMCtxGC += delta; pVM->patm.s.deltaReloc = delta; RTAvloU32DoWithAll(&pVM->patm.s.PatchLookupTreeHC->PatchTree, true, RelocatePatches, (void *)pVM); pCtx = CPUMQueryGuestCtxPtr(VMMGetCpu(pVM)); /* If we are running patch code right now, then also adjust EIP. */ if (PATMIsPatchGCAddr(pVM, pCtx->eip)) pCtx->eip += delta; pVM->patm.s.pGCStateGC = GCPtrNew; pVM->patm.s.pPatchMemGC = MMHyperR3ToRC(pVM, pVM->patm.s.pPatchMemHC); pVM->patm.s.pGCStackGC = MMHyperR3ToRC(pVM, pVM->patm.s.pGCStackHC); pVM->patm.s.pStatsGC = MMHyperR3ToRC(pVM, pVM->patm.s.pStatsHC); pVM->patm.s.PatchLookupTreeGC = MMHyperR3ToRC(pVM, pVM->patm.s.PatchLookupTreeHC); if (pVM->patm.s.pfnSysEnterPatchGC) pVM->patm.s.pfnSysEnterPatchGC += delta; /* Deal with the global patch functions. */ pVM->patm.s.pfnHelperCallGC += delta; pVM->patm.s.pfnHelperRetGC += delta; pVM->patm.s.pfnHelperIretGC += delta; pVM->patm.s.pfnHelperJumpGC += delta; RelocatePatches(&pVM->patm.s.pGlobalPatchRec->Core, (void *)pVM); } } /** * Terminates the PATM. * * Termination means cleaning up and freeing all resources, * the VM it self is at this point powered off or suspended. * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR3_INT_DECL(int) PATMR3Term(PVM pVM) { if (HMIsEnabled(pVM)) return VINF_SUCCESS; patmR3DbgTerm(pVM); /* Memory was all allocated from the two MM heaps and requires no freeing. */ return VINF_SUCCESS; } /** * PATM reset callback. * * @returns VBox status code. * @param pVM The VM which is reset. */ VMMR3_INT_DECL(int) PATMR3Reset(PVM pVM) { Log(("PATMR3Reset\n")); if (HMIsEnabled(pVM)) return VINF_SUCCESS; /* Free all patches. */ for (;;) { PPATMPATCHREC pPatchRec = (PPATMPATCHREC)RTAvloU32RemoveBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, 0, true); if (pPatchRec) patmR3RemovePatch(pVM, pPatchRec, true); else break; } Assert(!pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage); Assert(!pVM->patm.s.PatchLookupTreeHC->PatchTree); pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr = 0; pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage = 0; int rc = patmReinit(pVM); if (RT_SUCCESS(rc)) rc = PATMR3InitFinalize(pVM); /* paranoia */ return rc; } /** * @callback_method_impl{FNDISREADBYTES} */ static DECLCALLBACK(int) patmReadBytes(PDISCPUSTATE pDis, uint8_t offInstr, uint8_t cbMinRead, uint8_t cbMaxRead) { PATMDISASM *pDisInfo = (PATMDISASM *)pDis->pvUser; /** @todo change this to read more! */ /* * Trap/interrupt handler typically call common code on entry. Which might already have patches inserted. * As we currently don't support calling patch code from patch code, we'll let it read the original opcode bytes instead. */ /** @todo could change in the future! */ if (pDisInfo->fReadFlags & PATMREAD_ORGCODE) { size_t cbRead = cbMaxRead; RTUINTPTR uSrcAddr = pDis->uInstrAddr + offInstr; int rc = PATMR3ReadOrgInstr(pDisInfo->pVM, pDis->uInstrAddr + offInstr, &pDis->abInstr[offInstr], cbRead, &cbRead); if (RT_SUCCESS(rc)) { if (cbRead >= cbMinRead) { pDis->cbCachedInstr = offInstr + (uint8_t)cbRead; return VINF_SUCCESS; } cbMinRead -= (uint8_t)cbRead; cbMaxRead -= (uint8_t)cbRead; offInstr += (uint8_t)cbRead; uSrcAddr += cbRead; } #ifdef VBOX_STRICT if ( !(pDisInfo->pPatchInfo->flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_IDTHANDLER)) && !(pDisInfo->fReadFlags & PATMREAD_NOCHECK)) { Assert(PATMR3IsInsidePatchJump(pDisInfo->pVM, pDis->uInstrAddr + offInstr, NULL) == false); Assert(PATMR3IsInsidePatchJump(pDisInfo->pVM, pDis->uInstrAddr + offInstr + cbMinRead-1, NULL) == false); } #endif } int rc = VINF_SUCCESS; RTGCPTR32 uSrcAddr = (RTGCPTR32)pDis->uInstrAddr + offInstr; if ( !pDisInfo->pbInstrHC || ( PAGE_ADDRESS(pDisInfo->pInstrGC) != PAGE_ADDRESS(uSrcAddr + cbMinRead - 1) && !PATMIsPatchGCAddr(pDisInfo->pVM, uSrcAddr))) { Assert(!PATMIsPatchGCAddr(pDisInfo->pVM, uSrcAddr)); rc = PGMPhysSimpleReadGCPtr(&pDisInfo->pVM->aCpus[0], &pDis->abInstr[offInstr], uSrcAddr, cbMinRead); offInstr += cbMinRead; } else { /* * pbInstrHC is the base address; adjust according to the GC pointer. * * Try read the max number of bytes here. Since the disassembler only * ever uses these bytes for the current instruction, it doesn't matter * much if we accidentally read the start of the next instruction even * if it happens to be a patch jump or int3. */ uint8_t const *pbInstrHC = pDisInfo->pbInstrHC; AssertPtr(pbInstrHC); pbInstrHC += uSrcAddr - pDisInfo->pInstrGC; size_t cbMaxRead1 = PAGE_SIZE - (uSrcAddr & PAGE_OFFSET_MASK); size_t cbMaxRead2 = PAGE_SIZE - ((uintptr_t)pbInstrHC & PAGE_OFFSET_MASK); size_t cbToRead = RT_MIN(cbMaxRead1, RT_MAX(cbMaxRead2, cbMinRead)); if (cbToRead > cbMaxRead) cbToRead = cbMaxRead; memcpy(&pDis->abInstr[offInstr], pbInstrHC, cbToRead); offInstr += (uint8_t)cbToRead; } pDis->cbCachedInstr = offInstr; return rc; } DECLINLINE(bool) patmR3DisInstrToStr(PVM pVM, PPATCHINFO pPatch, RTGCPTR32 InstrGCPtr32, uint8_t *pbInstrHC, uint32_t fReadFlags, PDISCPUSTATE pCpu, uint32_t *pcbInstr, char *pszOutput, size_t cbOutput) { PATMDISASM disinfo; disinfo.pVM = pVM; disinfo.pPatchInfo = pPatch; disinfo.pbInstrHC = pbInstrHC; disinfo.pInstrGC = InstrGCPtr32; disinfo.fReadFlags = fReadFlags; return RT_SUCCESS(DISInstrToStrWithReader(InstrGCPtr32, (pPatch->flags & PATMFL_CODE32) ? DISCPUMODE_32BIT : DISCPUMODE_16BIT, patmReadBytes, &disinfo, pCpu, pcbInstr, pszOutput, cbOutput)); } DECLINLINE(bool) patmR3DisInstr(PVM pVM, PPATCHINFO pPatch, RTGCPTR32 InstrGCPtr32, uint8_t *pbInstrHC, uint32_t fReadFlags, PDISCPUSTATE pCpu, uint32_t *pcbInstr) { PATMDISASM disinfo; disinfo.pVM = pVM; disinfo.pPatchInfo = pPatch; disinfo.pbInstrHC = pbInstrHC; disinfo.pInstrGC = InstrGCPtr32; disinfo.fReadFlags = fReadFlags; return RT_SUCCESS(DISInstrWithReader(InstrGCPtr32, (pPatch->flags & PATMFL_CODE32) ? DISCPUMODE_32BIT : DISCPUMODE_16BIT, patmReadBytes, &disinfo, pCpu, pcbInstr)); } DECLINLINE(bool) patmR3DisInstrNoStrOpMode(PVM pVM, PPATCHINFO pPatch, RTGCPTR32 InstrGCPtr32, uint8_t *pbInstrHC, uint32_t fReadFlags, PDISCPUSTATE pCpu, uint32_t *pcbInstr) { PATMDISASM disinfo; disinfo.pVM = pVM; disinfo.pPatchInfo = pPatch; disinfo.pbInstrHC = pbInstrHC; disinfo.pInstrGC = InstrGCPtr32; disinfo.fReadFlags = fReadFlags; return RT_SUCCESS(DISInstrWithReader(InstrGCPtr32, pPatch->uOpMode, patmReadBytes, &disinfo, pCpu, pcbInstr)); } #ifdef LOG_ENABLED # define PATM_LOG_ORG_PATCH_INSTR(a_pVM, a_pPatch, a_szComment) \ PATM_LOG_PATCH_INSTR(a_pVM, a_pPatch, PATMREAD_ORGCODE, a_szComment, " patch:") # define PATM_LOG_RAW_PATCH_INSTR(a_pVM, a_pPatch, a_szComment) \ PATM_LOG_PATCH_INSTR(a_pVM, a_pPatch, PATMREAD_RAWCODE, a_szComment, " patch:") # define PATM_LOG_PATCH_INSTR(a_pVM, a_pPatch, a_fFlags, a_szComment1, a_szComment2) \ do { \ if (LogIsEnabled()) \ patmLogRawPatchInstr(a_pVM, a_pPatch, a_fFlags, a_szComment1, a_szComment2); \ } while (0) static void patmLogRawPatchInstr(PVM pVM, PPATCHINFO pPatch, uint32_t fFlags, const char *pszComment1, const char *pszComment2) { DISCPUSTATE DisState; char szOutput[128]; szOutput[0] = '\0'; patmR3DisInstrToStr(pVM, pPatch, pPatch->pPrivInstrGC, NULL, fFlags, &DisState, NULL, szOutput, sizeof(szOutput)); Log(("%s%s %s", pszComment1, pszComment2, szOutput)); } #else # define PATM_LOG_ORG_PATCH_INSTR(a_pVM, a_pPatch, a_szComment) do { } while (0) # define PATM_LOG_RAW_PATCH_INSTR(a_pVM, a_pPatch, a_szComment) do { } while (0) # define PATM_LOG_PATCH_INSTR(a_pVM, a_pPatch, a_fFlags, a_szComment1, a_szComment2) do { } while (0) #endif /** * Callback function for RTAvloU32DoWithAll * * Updates all fixups in the patches * * @returns VBox status code. * @param pNode Current node * @param pParam Pointer to the VM. */ static DECLCALLBACK(int) RelocatePatches(PAVLOU32NODECORE pNode, void *pParam) { PPATMPATCHREC pPatch = (PPATMPATCHREC)pNode; PVM pVM = (PVM)pParam; RTRCINTPTR delta; int rc; /* Nothing to do if the patch is not active. */ if (pPatch->patch.uState == PATCH_REFUSED) return 0; if (pPatch->patch.flags & PATMFL_PATCHED_GUEST_CODE) PATM_LOG_PATCH_INSTR(pVM, &pPatch->patch, PATMREAD_RAWCODE, "Org patch jump:", ""); Log(("Nr of fixups %d\n", pPatch->patch.nrFixups)); delta = (RTRCINTPTR)pVM->patm.s.deltaReloc; /* * Apply fixups */ PRELOCREC pRec = 0; AVLPVKEY key = 0; while (true) { /* Get the record that's closest from above */ pRec = (PRELOCREC)RTAvlPVGetBestFit(&pPatch->patch.FixupTree, key, true); if (pRec == 0) break; key = (AVLPVKEY)(pRec->pRelocPos + 1); /* search for the next record during the next round. */ switch (pRec->uType) { case FIXUP_ABSOLUTE: Log(("Absolute fixup at %RRv %RHv -> %RHv at %RRv\n", pRec->pSource, *(RTRCUINTPTR *)pRec->pRelocPos, *(RTRCINTPTR*)pRec->pRelocPos + delta, pRec->pRelocPos)); if ( !pRec->pSource || PATMIsPatchGCAddr(pVM, pRec->pSource)) { *(RTRCUINTPTR *)pRec->pRelocPos += delta; } else { uint8_t curInstr[15]; uint8_t oldInstr[15]; Assert(pRec->pSource && pPatch->patch.cbPrivInstr <= 15); Assert(!(pPatch->patch.flags & PATMFL_GLOBAL_FUNCTIONS)); memcpy(oldInstr, pPatch->patch.aPrivInstr, pPatch->patch.cbPrivInstr); *(RTRCPTR *)&oldInstr[pPatch->patch.cbPrivInstr - sizeof(RTRCPTR)] = pRec->pDest; rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), curInstr, pPatch->patch.pPrivInstrGC, pPatch->patch.cbPrivInstr); Assert(RT_SUCCESS(rc) || rc == VERR_PAGE_NOT_PRESENT || rc == VERR_PAGE_TABLE_NOT_PRESENT); pRec->pDest = (RTRCPTR)((RTRCUINTPTR)pRec->pDest + delta); if ( rc == VERR_PAGE_NOT_PRESENT || rc == VERR_PAGE_TABLE_NOT_PRESENT) { RTRCPTR pPage = pPatch->patch.pPrivInstrGC & PAGE_BASE_GC_MASK; Log(("PATM: Patch page not present -> check later!\n")); rc = PGMR3HandlerVirtualRegister(pVM, PGMVIRTHANDLERTYPE_ALL, pPage, pPage + (PAGE_SIZE - 1) /* inclusive! */, 0, patmVirtPageHandler, "PATMGCMonitorPage", 0, "PATMMonitorPatchJump"); Assert(RT_SUCCESS(rc) || rc == VERR_PGM_HANDLER_VIRTUAL_CONFLICT); } else if (memcmp(curInstr, oldInstr, pPatch->patch.cbPrivInstr)) { Log(("PATM: Patch was overwritten -> disabling patch!!\n")); /* * Disable patch; this is not a good solution */ /* @todo hopefully it was completely overwritten (if the read was successful)!!!! */ pPatch->patch.uState = PATCH_DISABLED; } else if (RT_SUCCESS(rc)) { *(RTRCPTR *)&curInstr[pPatch->patch.cbPrivInstr - sizeof(RTRCPTR)] = pRec->pDest; rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pRec->pSource, curInstr, pPatch->patch.cbPrivInstr); AssertRC(rc); } } break; case FIXUP_REL_JMPTOPATCH: { RTRCPTR pTarget = (RTRCPTR)((RTRCINTPTR)pRec->pDest + delta); if ( pPatch->patch.uState == PATCH_ENABLED && (pPatch->patch.flags & PATMFL_PATCHED_GUEST_CODE)) { uint8_t oldJump[SIZEOF_NEAR_COND_JUMP32]; uint8_t temp[SIZEOF_NEAR_COND_JUMP32]; RTRCPTR pJumpOffGC; RTRCINTPTR displ = (RTRCINTPTR)pTarget - (RTRCINTPTR)pRec->pSource; RTRCINTPTR displOld= (RTRCINTPTR)pRec->pDest - (RTRCINTPTR)pRec->pSource; #if 0 /** @todo '*(int32_t*)pRec->pRelocPos' crashes on restore of an XP VM here. pRelocPos=0x8000dbe2180a (bird) */ Log(("Relative fixup (g2p) %08X -> %08X at %08X (source=%08x, target=%08x)\n", *(int32_t*)pRec->pRelocPos, displ, pRec->pRelocPos, pRec->pSource, pRec->pDest)); #else Log(("Relative fixup (g2p) ???????? -> %08X at %08X (source=%08x, target=%08x)\n", displ, pRec->pRelocPos, pRec->pSource, pRec->pDest)); #endif Assert(pRec->pSource - pPatch->patch.cbPatchJump == pPatch->patch.pPrivInstrGC); #ifdef PATM_RESOLVE_CONFLICTS_WITH_JUMP_PATCHES if (pPatch->patch.cbPatchJump == SIZEOF_NEAR_COND_JUMP32) { Assert(pPatch->patch.flags & PATMFL_JUMP_CONFLICT); pJumpOffGC = pPatch->patch.pPrivInstrGC + 2; //two byte opcode oldJump[0] = pPatch->patch.aPrivInstr[0]; oldJump[1] = pPatch->patch.aPrivInstr[1]; *(RTRCUINTPTR *)&oldJump[2] = displOld; } else #endif if (pPatch->patch.cbPatchJump == SIZEOF_NEARJUMP32) { pJumpOffGC = pPatch->patch.pPrivInstrGC + 1; //one byte opcode oldJump[0] = 0xE9; *(RTRCUINTPTR *)&oldJump[1] = displOld; } else { AssertMsgFailed(("Invalid patch jump size %d\n", pPatch->patch.cbPatchJump)); continue; //this should never happen!! } Assert(pPatch->patch.cbPatchJump <= sizeof(temp)); /* * Read old patch jump and compare it to the one we previously installed */ rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), temp, pPatch->patch.pPrivInstrGC, pPatch->patch.cbPatchJump); Assert(RT_SUCCESS(rc) || rc == VERR_PAGE_NOT_PRESENT || rc == VERR_PAGE_TABLE_NOT_PRESENT); if ( rc == VERR_PAGE_NOT_PRESENT || rc == VERR_PAGE_TABLE_NOT_PRESENT) { RTRCPTR pPage = pPatch->patch.pPrivInstrGC & PAGE_BASE_GC_MASK; rc = PGMR3HandlerVirtualRegister(pVM, PGMVIRTHANDLERTYPE_ALL, pPage, pPage + (PAGE_SIZE - 1) /* inclusive! */, 0, patmVirtPageHandler, "PATMGCMonitorPage", 0, "PATMMonitorPatchJump"); Assert(RT_SUCCESS(rc) || rc == VERR_PGM_HANDLER_VIRTUAL_CONFLICT); } else if (memcmp(temp, oldJump, pPatch->patch.cbPatchJump)) { Log(("PATM: Patch jump was overwritten -> disabling patch!!\n")); /* * Disable patch; this is not a good solution */ /* @todo hopefully it was completely overwritten (if the read was successful)!!!! */ pPatch->patch.uState = PATCH_DISABLED; } else if (RT_SUCCESS(rc)) { rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pJumpOffGC, &displ, sizeof(displ)); AssertRC(rc); } else AssertMsgFailed(("Unexpected error %d from MMR3PhysReadGCVirt\n", rc)); } else Log(("Skip the guest jump to patch code for this disabled patch %RGv - %08X\n", pPatch->patch.pPrivInstrGC, pRec->pRelocPos)); pRec->pDest = pTarget; break; } case FIXUP_REL_JMPTOGUEST: { RTRCPTR pSource = (RTRCPTR)((RTRCINTPTR)pRec->pSource + delta); RTRCINTPTR displ = (RTRCINTPTR)pRec->pDest - (RTRCINTPTR)pSource; Assert(!(pPatch->patch.flags & PATMFL_GLOBAL_FUNCTIONS)); Log(("Relative fixup (p2g) %08X -> %08X at %08X (source=%08x, target=%08x)\n", *(int32_t*)pRec->pRelocPos, displ, pRec->pRelocPos, pRec->pSource, pRec->pDest)); *(RTRCUINTPTR *)pRec->pRelocPos = displ; pRec->pSource = pSource; break; } default: AssertMsg(0, ("Invalid fixup type!!\n")); return VERR_INVALID_PARAMETER; } } if (pPatch->patch.flags & PATMFL_PATCHED_GUEST_CODE) PATM_LOG_PATCH_INSTR(pVM, &pPatch->patch, PATMREAD_RAWCODE, "Rel patch jump:", ""); return 0; } /** * \#PF Handler callback for virtual access handler ranges. * * Important to realize that a physical page in a range can have aliases, and * for ALL and WRITE handlers these will also trigger. * * @returns VINF_SUCCESS if the handler have carried out the operation. * @returns VINF_PGM_HANDLER_DO_DEFAULT if the caller should carry out the access operation. * @param pVM Pointer to the VM. * @param GCPtr The virtual address the guest is writing to. (not correct if it's an alias!) * @param pvPtr The HC mapping of that address. * @param pvBuf What the guest is reading/writing. * @param cbBuf How much it's reading/writing. * @param enmAccessType The access type. * @param pvUser User argument. */ DECLCALLBACK(int) patmVirtPageHandler(PVM pVM, RTGCPTR GCPtr, void *pvPtr, void *pvBuf, size_t cbBuf, PGMACCESSTYPE enmAccessType, void *pvUser) { Assert(enmAccessType == PGMACCESSTYPE_WRITE); NOREF(enmAccessType); NOREF(pvPtr); NOREF(pvBuf); NOREF(cbBuf); NOREF(pvUser); /** @todo could be the wrong virtual address (alias) */ pVM->patm.s.pvFaultMonitor = GCPtr; PATMR3HandleMonitoredPage(pVM); return VINF_PGM_HANDLER_DO_DEFAULT; } #ifdef VBOX_WITH_DEBUGGER /** * Callback function for RTAvloU32DoWithAll * * Enables the patch that's being enumerated * * @returns 0 (continue enumeration). * @param pNode Current node * @param pVM Pointer to the VM. */ static DECLCALLBACK(int) EnableAllPatches(PAVLOU32NODECORE pNode, void *pVM) { PPATMPATCHREC pPatch = (PPATMPATCHREC)pNode; PATMR3EnablePatch((PVM)pVM, (RTRCPTR)pPatch->Core.Key); return 0; } /** * Callback function for RTAvloU32DoWithAll * * Disables the patch that's being enumerated * * @returns 0 (continue enumeration). * @param pNode Current node * @param pVM Pointer to the VM. */ static DECLCALLBACK(int) DisableAllPatches(PAVLOU32NODECORE pNode, void *pVM) { PPATMPATCHREC pPatch = (PPATMPATCHREC)pNode; PATMR3DisablePatch((PVM)pVM, (RTRCPTR)pPatch->Core.Key); return 0; } #endif /* VBOX_WITH_DEBUGGER */ #ifdef UNUSED_FUNCTIONS /** * Returns the host context pointer and size of the patch memory block * * @returns Host context pointer. * @param pVM Pointer to the VM. * @param pcb Size of the patch memory block * @internal */ VMMR3_INT_DECL(void *) PATMR3QueryPatchMemHC(PVM pVM, uint32_t *pcb) { AssertReturn(!HMIsEnabled(pVM), NULL); if (pcb) *pcb = pVM->patm.s.cbPatchMem; return pVM->patm.s.pPatchMemHC; } /** * Returns the guest context pointer and size of the patch memory block * * @returns Guest context pointer. * @param pVM Pointer to the VM. * @param pcb Size of the patch memory block */ VMMR3_INT_DECL(RTRCPTR) PATMR3QueryPatchMemGC(PVM pVM, uint32_t *pcb) { AssertReturn(!HMIsEnabled(pVM), NIL_RTRCPTR); if (pcb) *pcb = pVM->patm.s.cbPatchMem; return pVM->patm.s.pPatchMemGC; } #endif /* UNUSED_FUNCTIONS */ /** * Returns the host context pointer of the GC context structure * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR3_INT_DECL(PPATMGCSTATE) PATMR3QueryGCStateHC(PVM pVM) { AssertReturn(!HMIsEnabled(pVM), NULL); return pVM->patm.s.pGCStateHC; } #ifdef UNUSED_FUNCTION /** * Checks whether the HC address is part of our patch region * * @returns true/false. * @param pVM Pointer to the VM. * @param pAddrHC Host context ring-3 address to check. */ VMMR3_INT_DECL(bool) PATMR3IsPatchHCAddr(PVM pVM, void *pAddrHC) { return (uintptr_t)pAddrHC >= (uintptr_t)pVM->patm.s.pPatchMemHC && (uintptr_t)pAddrHC < (uintptr_t)pVM->patm.s.pPatchMemHC + pVM->patm.s.cbPatchMem; } #endif /** * Allows or disallow patching of privileged instructions executed by the guest OS * * @returns VBox status code. * @param pUVM The user mode VM handle. * @param fAllowPatching Allow/disallow patching */ VMMR3DECL(int) PATMR3AllowPatching(PUVM pUVM, bool fAllowPatching) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE); PVM pVM = pUVM->pVM; VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE); if (!HMIsEnabled(pVM)) pVM->fPATMEnabled = fAllowPatching; else Assert(!pVM->fPATMEnabled); return VINF_SUCCESS; } /** * Checks if the patch manager is enabled or not. * * @returns true if enabled, false if not (or if invalid handle). * @param pUVM The user mode VM handle. */ VMMR3DECL(bool) PATMR3IsEnabled(PUVM pUVM) { UVM_ASSERT_VALID_EXT_RETURN(pUVM, false); PVM pVM = pUVM->pVM; VM_ASSERT_VALID_EXT_RETURN(pVM, false); return PATMIsEnabled(pVM); } /** * Convert a GC patch block pointer to a HC patch pointer * * @returns HC pointer or NULL if it's not a GC patch pointer * @param pVM Pointer to the VM. * @param pAddrGC GC pointer */ VMMR3_INT_DECL(void *) PATMR3GCPtrToHCPtr(PVM pVM, RTRCPTR pAddrGC) { AssertReturn(!HMIsEnabled(pVM), NULL); if (pVM->patm.s.pPatchMemGC <= pAddrGC && pVM->patm.s.pPatchMemGC + pVM->patm.s.cbPatchMem > pAddrGC) return pVM->patm.s.pPatchMemHC + (pAddrGC - pVM->patm.s.pPatchMemGC); return NULL; } /** * Convert guest context address to host context pointer * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCacheRec Address conversion cache record * @param pGCPtr Guest context pointer * * @returns Host context pointer or NULL in case of an error * */ R3PTRTYPE(uint8_t *) patmR3GCVirtToHCVirt(PVM pVM, PPATMP2GLOOKUPREC pCacheRec, RCPTRTYPE(uint8_t *) pGCPtr) { int rc; R3PTRTYPE(uint8_t *) pHCPtr; uint32_t offset; if (PATMIsPatchGCAddr(pVM, pGCPtr)) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; Assert(pPatch); return PATCHCODE_PTR_HC(pPatch) + (pGCPtr - PATCHCODE_PTR_GC(pPatch)); } offset = pGCPtr & PAGE_OFFSET_MASK; if (pCacheRec->pGuestLoc == (pGCPtr & PAGE_BASE_GC_MASK)) return pCacheRec->pPageLocStartHC + offset; /* Release previous lock if any. */ if (pCacheRec->Lock.pvMap) { PGMPhysReleasePageMappingLock(pVM, &pCacheRec->Lock); pCacheRec->Lock.pvMap = NULL; } rc = PGMPhysGCPtr2CCPtrReadOnly(VMMGetCpu(pVM), pGCPtr, (const void **)&pHCPtr, &pCacheRec->Lock); if (rc != VINF_SUCCESS) { AssertMsg(rc == VINF_SUCCESS || rc == VERR_PAGE_NOT_PRESENT || rc == VERR_PAGE_TABLE_NOT_PRESENT, ("MMR3PhysGCVirt2HCVirtEx failed for %08X\n", pGCPtr)); return NULL; } pCacheRec->pPageLocStartHC = (R3PTRTYPE(uint8_t*))((RTHCUINTPTR)pHCPtr & PAGE_BASE_HC_MASK); pCacheRec->pGuestLoc = pGCPtr & PAGE_BASE_GC_MASK; return pHCPtr; } /** * Calculates and fills in all branch targets * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Current patch block pointer * */ static int patmr3SetBranchTargets(PVM pVM, PPATCHINFO pPatch) { int32_t displ; PJUMPREC pRec = 0; unsigned nrJumpRecs = 0; /* * Set all branch targets inside the patch block. * We remove all jump records as they are no longer needed afterwards. */ while (true) { RCPTRTYPE(uint8_t *) pInstrGC; RCPTRTYPE(uint8_t *) pBranchTargetGC = 0; pRec = (PJUMPREC)RTAvlPVRemoveBestFit(&pPatch->JumpTree, 0, true); if (pRec == 0) break; nrJumpRecs++; /* HC in patch block to GC in patch block. */ pInstrGC = patmPatchHCPtr2PatchGCPtr(pVM, pRec->pJumpHC); if (pRec->opcode == OP_CALL) { /* Special case: call function replacement patch from this patch block. */ PPATMPATCHREC pFunctionRec = patmQueryFunctionPatch(pVM, pRec->pTargetGC); if (!pFunctionRec) { int rc; if (PATMR3HasBeenPatched(pVM, pRec->pTargetGC) == false) rc = PATMR3InstallPatch(pVM, pRec->pTargetGC, PATMFL_CODE32 | PATMFL_DUPLICATE_FUNCTION); else rc = VERR_PATCHING_REFUSED; /* exists as a normal patch; can't use it */ if (RT_FAILURE(rc)) { uint8_t *pPatchHC; RTRCPTR pPatchGC; RTRCPTR pOrgInstrGC; pOrgInstrGC = PATMR3PatchToGCPtr(pVM, pInstrGC, 0); Assert(pOrgInstrGC); /* Failure for some reason -> mark exit point with int 3. */ Log(("Failed to install function replacement patch (at %x) for reason %Rrc\n", pOrgInstrGC, rc)); pPatchGC = patmGuestGCPtrToPatchGCPtr(pVM, pPatch, pOrgInstrGC); Assert(pPatchGC); pPatchHC = pVM->patm.s.pPatchMemHC + (pPatchGC - pVM->patm.s.pPatchMemGC); /* Set a breakpoint at the very beginning of the recompiled instruction */ *pPatchHC = 0xCC; continue; } } else { Log(("Patch block %RRv called as function\n", pFunctionRec->patch.pPrivInstrGC)); pFunctionRec->patch.flags |= PATMFL_CODE_REFERENCED; } pBranchTargetGC = PATMR3QueryPatchGCPtr(pVM, pRec->pTargetGC); } else pBranchTargetGC = patmGuestGCPtrToPatchGCPtr(pVM, pPatch, pRec->pTargetGC); if (pBranchTargetGC == 0) { AssertMsgFailed(("patmr3SetBranchTargets: patmGuestGCPtrToPatchGCPtr failed for %08X\n", pRec->pTargetGC)); return VERR_PATCHING_REFUSED; } /* Our jumps *always* have a dword displacement (to make things easier). */ Assert(sizeof(uint32_t) == sizeof(RTRCPTR)); displ = pBranchTargetGC - (pInstrGC + pRec->offDispl + sizeof(RTRCPTR)); *(RTRCPTR *)(pRec->pJumpHC + pRec->offDispl) = displ; Log(("Set branch target %d to %08X : %08x - (%08x + %d + %d)\n", nrJumpRecs, displ, pBranchTargetGC, pInstrGC, pRec->offDispl, sizeof(RTRCPTR))); } Assert(nrJumpRecs == pPatch->nrJumpRecs); Assert(pPatch->JumpTree == 0); return VINF_SUCCESS; } /** * Add an illegal instruction record * * @param pVM Pointer to the VM. * @param pPatch Patch structure ptr * @param pInstrGC Guest context pointer to privileged instruction * */ static void patmAddIllegalInstrRecord(PVM pVM, PPATCHINFO pPatch, RTRCPTR pInstrGC) { PAVLPVNODECORE pRec; pRec = (PAVLPVNODECORE)MMR3HeapAllocZ(pVM, MM_TAG_PATM_PATCH, sizeof(*pRec)); Assert(pRec); pRec->Key = (AVLPVKEY)(uintptr_t)pInstrGC; bool ret = RTAvlPVInsert(&pPatch->pTempInfo->IllegalInstrTree, pRec); Assert(ret); NOREF(ret); pPatch->pTempInfo->nrIllegalInstr++; } static bool patmIsIllegalInstr(PPATCHINFO pPatch, RTRCPTR pInstrGC) { PAVLPVNODECORE pRec; pRec = RTAvlPVGet(&pPatch->pTempInfo->IllegalInstrTree, (AVLPVKEY)(uintptr_t)pInstrGC); if (pRec) return true; else return false; } /** * Add a patch to guest lookup record * * @param pVM Pointer to the VM. * @param pPatch Patch structure ptr * @param pPatchInstrHC Guest context pointer to patch block * @param pInstrGC Guest context pointer to privileged instruction * @param enmType Lookup type * @param fDirty Dirty flag * * @note Be extremely careful with this function. Make absolutely sure the guest * address is correct! (to avoid executing instructions twice!) */ void patmR3AddP2GLookupRecord(PVM pVM, PPATCHINFO pPatch, uint8_t *pPatchInstrHC, RTRCPTR pInstrGC, PATM_LOOKUP_TYPE enmType, bool fDirty) { bool ret; PRECPATCHTOGUEST pPatchToGuestRec; PRECGUESTTOPATCH pGuestToPatchRec; uint32_t PatchOffset = pPatchInstrHC - pVM->patm.s.pPatchMemHC; /* Offset in memory reserved for PATM. */ LogFlowFunc(("pVM=%#p pPatch=%#p pPatchInstrHC=%#p pInstrGC=%#x enmType=%d fDirty=%RTbool\n", pVM, pPatch, pPatchInstrHC, pInstrGC, enmType, fDirty)); if (enmType == PATM_LOOKUP_PATCH2GUEST) { pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32Get(&pPatch->Patch2GuestAddrTree, PatchOffset); if (pPatchToGuestRec && pPatchToGuestRec->Core.Key == PatchOffset) return; /* already there */ Assert(!pPatchToGuestRec); } #ifdef VBOX_STRICT else { pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32Get(&pPatch->Patch2GuestAddrTree, PatchOffset); Assert(!pPatchToGuestRec); } #endif pPatchToGuestRec = (PRECPATCHTOGUEST)MMR3HeapAllocZ(pVM, MM_TAG_PATM_PATCH, sizeof(RECPATCHTOGUEST) + sizeof(RECGUESTTOPATCH)); Assert(pPatchToGuestRec); pPatchToGuestRec->Core.Key = PatchOffset; pPatchToGuestRec->pOrgInstrGC = pInstrGC; pPatchToGuestRec->enmType = enmType; pPatchToGuestRec->fDirty = fDirty; ret = RTAvlU32Insert(&pPatch->Patch2GuestAddrTree, &pPatchToGuestRec->Core); Assert(ret); /* GC to patch address */ if (enmType == PATM_LOOKUP_BOTHDIR) { pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32Get(&pPatch->Guest2PatchAddrTree, pInstrGC); if (!pGuestToPatchRec) { pGuestToPatchRec = (PRECGUESTTOPATCH)(pPatchToGuestRec+1); pGuestToPatchRec->Core.Key = pInstrGC; pGuestToPatchRec->PatchOffset = PatchOffset; ret = RTAvlU32Insert(&pPatch->Guest2PatchAddrTree, &pGuestToPatchRec->Core); Assert(ret); } } pPatch->nrPatch2GuestRecs++; } /** * Removes a patch to guest lookup record * * @param pVM Pointer to the VM. * @param pPatch Patch structure ptr * @param pPatchInstrGC Guest context pointer to patch block */ void patmr3RemoveP2GLookupRecord(PVM pVM, PPATCHINFO pPatch, RTRCPTR pPatchInstrGC) { PAVLU32NODECORE pNode; PAVLU32NODECORE pNode2; PRECPATCHTOGUEST pPatchToGuestRec; uint32_t PatchOffset = pPatchInstrGC - pVM->patm.s.pPatchMemGC; /* Offset in memory reserved for PATM. */ pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32Get(&pPatch->Patch2GuestAddrTree, PatchOffset); Assert(pPatchToGuestRec); if (pPatchToGuestRec) { if (pPatchToGuestRec->enmType == PATM_LOOKUP_BOTHDIR) { PRECGUESTTOPATCH pGuestToPatchRec = (PRECGUESTTOPATCH)(pPatchToGuestRec+1); Assert(pGuestToPatchRec->Core.Key); pNode2 = RTAvlU32Remove(&pPatch->Guest2PatchAddrTree, pGuestToPatchRec->Core.Key); Assert(pNode2); } pNode = RTAvlU32Remove(&pPatch->Patch2GuestAddrTree, pPatchToGuestRec->Core.Key); Assert(pNode); MMR3HeapFree(pPatchToGuestRec); pPatch->nrPatch2GuestRecs--; } } /** * RTAvlPVDestroy callback. */ static DECLCALLBACK(int) patmEmptyTreePVCallback(PAVLPVNODECORE pNode, void *) { MMR3HeapFree(pNode); return 0; } /** * Empty the specified tree (PV tree, MMR3 heap) * * @param pVM Pointer to the VM. * @param ppTree Tree to empty */ static void patmEmptyTree(PVM pVM, PAVLPVNODECORE *ppTree) { NOREF(pVM); RTAvlPVDestroy(ppTree, patmEmptyTreePVCallback, NULL); } /** * RTAvlU32Destroy callback. */ static DECLCALLBACK(int) patmEmptyTreeU32Callback(PAVLU32NODECORE pNode, void *) { MMR3HeapFree(pNode); return 0; } /** * Empty the specified tree (U32 tree, MMR3 heap) * * @param pVM Pointer to the VM. * @param ppTree Tree to empty */ static void patmEmptyTreeU32(PVM pVM, PPAVLU32NODECORE ppTree) { NOREF(pVM); RTAvlU32Destroy(ppTree, patmEmptyTreeU32Callback, NULL); } /** * Analyses the instructions following the cli for compliance with our heuristics for cli & pushf * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCpu CPU disassembly state * @param pInstrGC Guest context pointer to privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pCacheRec Cache record ptr * */ static int patmAnalyseBlockCallback(PVM pVM, DISCPUSTATE *pCpu, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; bool fIllegalInstr = false; /* * Preliminary heuristics: *- no call instructions without a fixed displacement between cli and sti/popf *- no jumps in the instructions following cli (4+ bytes; enough for the replacement jump (5 bytes)) *- no nested pushf/cli *- sti/popf should be the (eventual) target of all branches *- no near or far returns; no int xx, no into * * Note: Later on we can impose less stricter guidelines if the need arises */ /* Bail out if the patch gets too big. */ if (pPatch->cbPatchBlockSize >= MAX_PATCH_SIZE) { Log(("Code block too big (%x) for patch at %RRv!!\n", pPatch->cbPatchBlockSize, pCurInstrGC)); fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } else { /* No unconditional jumps or calls without fixed displacements. */ if ( (pCpu->pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) && (pCpu->pCurInstr->uOpcode == OP_JMP || pCpu->pCurInstr->uOpcode == OP_CALL) ) { Assert(pCpu->Param1.cb <= 4 || pCpu->Param1.cb == 6); if ( pCpu->Param1.cb == 6 /* far call/jmp */ || (pCpu->pCurInstr->uOpcode == OP_CALL && !(pPatch->flags & PATMFL_SUPPORT_CALLS)) || (OP_PARM_VTYPE(pCpu->pCurInstr->fParam1) != OP_PARM_J && !(pPatch->flags & PATMFL_SUPPORT_INDIRECT_CALLS)) ) { fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } } /* An unconditional (short) jump right after a cli is a potential problem; we will overwrite whichever function comes afterwards */ if (pPatch->opcode == OP_CLI && pCpu->pCurInstr->uOpcode == OP_JMP) { if ( pCurInstrGC > pPatch->pPrivInstrGC && pCurInstrGC + pCpu->cbInstr < pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32) /* hardcoded patch jump size; cbPatchJump is still zero */ { Log(("Dangerous unconditional jump ends in our generated patch jump!! (%x vs %x)\n", pCurInstrGC, pPatch->pPrivInstrGC)); /* We turn this one into a int 3 callable patch. */ pPatch->flags |= PATMFL_INT3_REPLACEMENT_BLOCK; } } else /* no nested pushfs just yet; nested cli is allowed for cli patches though. */ if (pPatch->opcode == OP_PUSHF) { if (pCurInstrGC != pInstrGC && pCpu->pCurInstr->uOpcode == OP_PUSHF) { fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } } /* no far returns */ if (pCpu->pCurInstr->uOpcode == OP_RETF) { pPatch->pTempInfo->nrRetInstr++; fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } else if ( pCpu->pCurInstr->uOpcode == OP_INT3 || pCpu->pCurInstr->uOpcode == OP_INT || pCpu->pCurInstr->uOpcode == OP_INTO) { /* No int xx or into either. */ fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } } pPatch->cbPatchBlockSize += pCpu->cbInstr; /* Illegal instruction -> end of analysis phase for this code block */ if (fIllegalInstr || patmIsIllegalInstr(pPatch, pCurInstrGC)) return VINF_SUCCESS; /* Check for exit points. */ switch (pCpu->pCurInstr->uOpcode) { case OP_SYSEXIT: return VINF_SUCCESS; /* duplicate it; will fault or emulated in GC. */ case OP_SYSENTER: case OP_ILLUD2: /* This appears to be some kind of kernel panic in Linux 2.4; no point to analyse more. */ Log(("Illegal opcode (0xf 0xb) -> return here\n")); return VINF_SUCCESS; case OP_STI: case OP_POPF: Assert(!(pPatch->flags & (PATMFL_DUPLICATE_FUNCTION))); /* If out exit point lies within the generated patch jump, then we have to refuse!! */ if (pCurInstrGC > pPatch->pPrivInstrGC && pCurInstrGC < pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32) /* hardcoded patch jump size; cbPatchJump is still zero */ { Log(("Exit point within patch jump itself!! (%x vs %x)\n", pCurInstrGC, pPatch->pPrivInstrGC)); return VERR_PATCHING_REFUSED; } if (pPatch->opcode == OP_PUSHF) { if (pCpu->pCurInstr->uOpcode == OP_POPF) { if (pPatch->cbPatchBlockSize >= SIZEOF_NEARJUMP32) return VINF_SUCCESS; /* Or else we need to duplicate more instructions, because we can't jump back yet! */ Log(("WARNING: End of block reached, but we need to duplicate some extra instruction to avoid a conflict with the patch jump\n")); pPatch->flags |= PATMFL_CHECK_SIZE; } break; /* sti doesn't mark the end of a pushf block; only popf does. */ } /* else: fall through. */ case OP_RETN: /* exit point for function replacement */ return VINF_SUCCESS; case OP_IRET: return VINF_SUCCESS; /* exitpoint */ case OP_CPUID: case OP_CALL: case OP_JMP: break; #ifdef VBOX_WITH_SAFE_STR /** @todo remove DISOPTYPE_PRIVILEGED_NOTRAP from disasm table */ case OP_STR: break; #endif default: if (pCpu->pCurInstr->fOpType & (DISOPTYPE_PRIVILEGED_NOTRAP)) { patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); return VINF_SUCCESS; /* exit point */ } break; } /* If single instruction patch, we've copied enough instructions *and* the current instruction is not a relative jump. */ if ((pPatch->flags & PATMFL_CHECK_SIZE) && pPatch->cbPatchBlockSize > SIZEOF_NEARJUMP32 && !(pCpu->pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW)) { /* The end marker for this kind of patch is any instruction at a location outside our patch jump. */ Log(("End of block at %RRv size %d\n", pCurInstrGC, pCpu->cbInstr)); return VINF_SUCCESS; } return VWRN_CONTINUE_ANALYSIS; } /** * Analyses the instructions inside a function for compliance * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCpu CPU disassembly state * @param pInstrGC Guest context pointer to privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pCacheRec Cache record ptr * */ static int patmAnalyseFunctionCallback(PVM pVM, DISCPUSTATE *pCpu, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; bool fIllegalInstr = false; NOREF(pInstrGC); //Preliminary heuristics: //- no call instructions //- ret ends a block Assert(pPatch->flags & (PATMFL_DUPLICATE_FUNCTION)); // bail out if the patch gets too big if (pPatch->cbPatchBlockSize >= MAX_PATCH_SIZE) { Log(("Code block too big (%x) for function patch at %RRv!!\n", pPatch->cbPatchBlockSize, pCurInstrGC)); fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } else { // no unconditional jumps or calls without fixed displacements if ( (pCpu->pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) && (pCpu->pCurInstr->uOpcode == OP_JMP || pCpu->pCurInstr->uOpcode == OP_CALL) ) { Assert(pCpu->Param1.cb <= 4 || pCpu->Param1.cb == 6); if ( pCpu->Param1.cb == 6 /* far call/jmp */ || (pCpu->pCurInstr->uOpcode == OP_CALL && !(pPatch->flags & PATMFL_SUPPORT_CALLS)) || (OP_PARM_VTYPE(pCpu->pCurInstr->fParam1) != OP_PARM_J && !(pPatch->flags & PATMFL_SUPPORT_INDIRECT_CALLS)) ) { fIllegalInstr = true; patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); } } else /* no far returns */ if (pCpu->pCurInstr->uOpcode == OP_RETF) { patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); fIllegalInstr = true; } else /* no int xx or into either */ if (pCpu->pCurInstr->uOpcode == OP_INT3 || pCpu->pCurInstr->uOpcode == OP_INT || pCpu->pCurInstr->uOpcode == OP_INTO) { patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); fIllegalInstr = true; } #if 0 ///@todo we can handle certain in/out and privileged instructions in the guest context if (pCpu->pCurInstr->fOpType & DISOPTYPE_PRIVILEGED && pCpu->pCurInstr->uOpcode != OP_STI) { Log(("Illegal instructions for function patch!!\n")); return VERR_PATCHING_REFUSED; } #endif } pPatch->cbPatchBlockSize += pCpu->cbInstr; /* Illegal instruction -> end of analysis phase for this code block */ if (fIllegalInstr || patmIsIllegalInstr(pPatch, pCurInstrGC)) { return VINF_SUCCESS; } // Check for exit points switch (pCpu->pCurInstr->uOpcode) { case OP_ILLUD2: //This appears to be some kind of kernel panic in Linux 2.4; no point to analyse more Log(("Illegal opcode (0xf 0xb) -> return here\n")); return VINF_SUCCESS; case OP_IRET: case OP_SYSEXIT: /* will fault or emulated in GC */ case OP_RETN: return VINF_SUCCESS; #ifdef VBOX_WITH_SAFE_STR /** @todo remove DISOPTYPE_PRIVILEGED_NOTRAP from disasm table */ case OP_STR: break; #endif case OP_POPF: case OP_STI: return VWRN_CONTINUE_ANALYSIS; default: if (pCpu->pCurInstr->fOpType & (DISOPTYPE_PRIVILEGED_NOTRAP)) { patmAddIllegalInstrRecord(pVM, pPatch, pCurInstrGC); return VINF_SUCCESS; /* exit point */ } return VWRN_CONTINUE_ANALYSIS; } return VWRN_CONTINUE_ANALYSIS; } /** * Recompiles the instructions in a code block * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCpu CPU disassembly state * @param pInstrGC Guest context pointer to privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pCacheRec Cache record ptr * */ static int patmRecompileCallback(PVM pVM, DISCPUSTATE *pCpu, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; int rc = VINF_SUCCESS; bool fInhibitIRQInstr = false; /* did the instruction cause PATMFL_INHIBITIRQS to be set? */ LogFlow(("patmRecompileCallback %RRv %RRv\n", pInstrGC, pCurInstrGC)); if ( patmGuestGCPtrToPatchGCPtr(pVM, pPatch, pCurInstrGC) != 0 && !(pPatch->flags & PATMFL_RECOMPILE_NEXT)) /* do not do this when the next instruction *must* be executed! */ { /* * Been there, done that; so insert a jump (we don't want to duplicate code) * no need to record this instruction as it's glue code that never crashes (it had better not!) */ Log(("patmRecompileCallback: jump to code we've recompiled before %RRv!\n", pCurInstrGC)); return patmPatchGenRelJump(pVM, pPatch, pCurInstrGC, OP_JMP, !!(pCpu->fPrefix & DISPREFIX_OPSIZE)); } if (pPatch->flags & (PATMFL_DUPLICATE_FUNCTION)) { rc = patmAnalyseFunctionCallback(pVM, pCpu, pInstrGC, pCurInstrGC, pCacheRec); } else rc = patmAnalyseBlockCallback(pVM, pCpu, pInstrGC, pCurInstrGC, pCacheRec); if (RT_FAILURE(rc)) return rc; /* Note: Never do a direct return unless a failure is encountered! */ /* Clear recompilation of next instruction flag; we are doing that right here. */ if (pPatch->flags & PATMFL_RECOMPILE_NEXT) pPatch->flags &= ~PATMFL_RECOMPILE_NEXT; /* Add lookup record for patch to guest address translation */ patmR3AddP2GLookupRecord(pVM, pPatch, PATCHCODE_PTR_HC(pPatch) + pPatch->uCurPatchOffset, pCurInstrGC, PATM_LOOKUP_BOTHDIR); /* Update lowest and highest instruction address for this patch */ if (pCurInstrGC < pPatch->pInstrGCLowest) pPatch->pInstrGCLowest = pCurInstrGC; else if (pCurInstrGC > pPatch->pInstrGCHighest) pPatch->pInstrGCHighest = pCurInstrGC + pCpu->cbInstr; /* Illegal instruction -> end of recompile phase for this code block. */ if (patmIsIllegalInstr(pPatch, pCurInstrGC)) { Log(("Illegal instruction at %RRv -> mark with int 3\n", pCurInstrGC)); rc = patmPatchGenIllegalInstr(pVM, pPatch); goto end; } /* For our first attempt, we'll handle only simple relative jumps (immediate offset coded in instruction). * Indirect calls are handled below. */ if ( (pCpu->pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) && (pCpu->pCurInstr->uOpcode != OP_CALL || (pPatch->flags & PATMFL_SUPPORT_CALLS)) && (OP_PARM_VTYPE(pCpu->pCurInstr->fParam1) == OP_PARM_J)) { RCPTRTYPE(uint8_t *) pTargetGC = PATMResolveBranch(pCpu, pCurInstrGC); if (pTargetGC == 0) { Log(("We don't support far jumps here!! (%08X)\n", pCpu->Param1.fUse)); return VERR_PATCHING_REFUSED; } if (pCpu->pCurInstr->uOpcode == OP_CALL) { Assert(!PATMIsPatchGCAddr(pVM, pTargetGC)); rc = patmPatchGenCall(pVM, pPatch, pCpu, pCurInstrGC, pTargetGC, false); if (RT_FAILURE(rc)) goto end; } else rc = patmPatchGenRelJump(pVM, pPatch, pTargetGC, pCpu->pCurInstr->uOpcode, !!(pCpu->fPrefix & DISPREFIX_OPSIZE)); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; goto end; } switch (pCpu->pCurInstr->uOpcode) { case OP_CLI: { /* If a cli is found while duplicating instructions for another patch, then it's of vital importance to continue * until we've found the proper exit point(s). */ if ( pCurInstrGC != pInstrGC && !(pPatch->flags & (PATMFL_DUPLICATE_FUNCTION)) ) { Log(("cli instruction found in other instruction patch block; force it to continue & find an exit point\n")); pPatch->flags &= ~(PATMFL_CHECK_SIZE | PATMFL_SINGLE_INSTRUCTION); } /* Set by irq inhibition; no longer valid now. */ pPatch->flags &= ~PATMFL_GENERATE_JUMPTOGUEST; rc = patmPatchGenCli(pVM, pPatch); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; } case OP_MOV: if (pCpu->pCurInstr->fOpType & DISOPTYPE_POTENTIALLY_DANGEROUS) { /* mov ss, src? */ if ( (pCpu->Param1.fUse & DISUSE_REG_SEG) && (pCpu->Param1.Base.idxSegReg == DISSELREG_SS)) { Log(("Force recompilation of next instruction for OP_MOV at %RRv\n", pCurInstrGC)); pPatch->flags |= PATMFL_RECOMPILE_NEXT; /** @todo this could cause a fault (ring 0 selector being loaded in ring 1) */ } #if 0 /* necessary for Haiku */ else if ( (pCpu->Param2.fUse & DISUSE_REG_SEG) && (pCpu->Param2.Base.idxSegReg == USE_REG_SS) && (pCpu->Param1.fUse & (DISUSE_REG_GEN32|DISUSE_REG_GEN16))) /** @todo memory operand must in theory be handled too */ { /* mov GPR, ss */ rc = patmPatchGenMovFromSS(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; } #endif } goto duplicate_instr; case OP_POP: /** @todo broken comparison!! should be if ((pCpu->Param1.fUse & DISUSE_REG_SEG) && (pCpu->Param1.Base.idxSegReg == DISSELREG_SS)) */ if (pCpu->pCurInstr->fParam1 == OP_PARM_REG_SS) { Assert(pCpu->pCurInstr->fOpType & DISOPTYPE_INHIBIT_IRQS); Log(("Force recompilation of next instruction for OP_MOV at %RRv\n", pCurInstrGC)); pPatch->flags |= PATMFL_RECOMPILE_NEXT; } goto duplicate_instr; case OP_STI: { RTRCPTR pNextInstrGC = 0; /* by default no inhibit irq */ /* In a sequence of instructions that inhibit irqs, only the first one actually inhibits irqs. */ if (!(pPatch->flags & PATMFL_INHIBIT_IRQS)) { pPatch->flags |= PATMFL_INHIBIT_IRQS | PATMFL_GENERATE_JUMPTOGUEST; fInhibitIRQInstr = true; pNextInstrGC = pCurInstrGC + pCpu->cbInstr; Log(("Inhibit irqs for instruction OP_STI at %RRv\n", pCurInstrGC)); } rc = patmPatchGenSti(pVM, pPatch, pCurInstrGC, pNextInstrGC); if (RT_SUCCESS(rc)) { DISCPUSTATE cpu = *pCpu; unsigned cbInstr; int disret; RCPTRTYPE(uint8_t *) pReturnInstrGC; pPatch->flags |= PATMFL_FOUND_PATCHEND; pNextInstrGC = pCurInstrGC + pCpu->cbInstr; { /* Force pNextInstrHC out of scope after using it */ uint8_t *pNextInstrHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pNextInstrGC); if (pNextInstrHC == NULL) { AssertFailed(); return VERR_PATCHING_REFUSED; } // Disassemble the next instruction disret = patmR3DisInstr(pVM, pPatch, pNextInstrGC, pNextInstrHC, PATMREAD_ORGCODE, &cpu, &cbInstr); } if (disret == false) { AssertMsgFailed(("STI: Disassembly failed (probably page not present) -> return to caller\n")); return VERR_PATCHING_REFUSED; } pReturnInstrGC = pNextInstrGC + cbInstr; if ( (pPatch->flags & (PATMFL_DUPLICATE_FUNCTION)) || pReturnInstrGC <= pInstrGC || pReturnInstrGC - pInstrGC >= SIZEOF_NEARJUMP32 ) { /* Not an exit point for function duplication patches */ if ( (pPatch->flags & PATMFL_DUPLICATE_FUNCTION) && RT_SUCCESS(rc)) { pPatch->flags &= ~PATMFL_GENERATE_JUMPTOGUEST; /* Don't generate a jump back */ rc = VWRN_CONTINUE_RECOMPILE; } else rc = VINF_SUCCESS; //exit point } else { Log(("PATM: sti occurred too soon; refusing patch!\n")); rc = VERR_PATCHING_REFUSED; //not allowed!! } } break; } case OP_POPF: { bool fGenerateJmpBack = (pCurInstrGC + pCpu->cbInstr - pInstrGC >= SIZEOF_NEARJUMP32); /* Not an exit point for IDT handler or function replacement patches */ /* Note: keep IOPL in mind when changing any of this!! (see comments in PATMA.asm, PATMPopf32Replacement) */ if (pPatch->flags & (PATMFL_IDTHANDLER|PATMFL_DUPLICATE_FUNCTION)) fGenerateJmpBack = false; rc = patmPatchGenPopf(pVM, pPatch, pCurInstrGC + pCpu->cbInstr, !!(pCpu->fPrefix & DISPREFIX_OPSIZE), fGenerateJmpBack); if (RT_SUCCESS(rc)) { if (fGenerateJmpBack == false) { /* Not an exit point for IDT handler or function replacement patches */ rc = VWRN_CONTINUE_RECOMPILE; } else { pPatch->flags |= PATMFL_FOUND_PATCHEND; rc = VINF_SUCCESS; /* exit point! */ } } break; } case OP_PUSHF: rc = patmPatchGenPushf(pVM, pPatch, !!(pCpu->fPrefix & DISPREFIX_OPSIZE)); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; case OP_PUSH: /** @todo broken comparison!! should be if ((pCpu->Param1.fUse & DISUSE_REG_SEG) && (pCpu->Param1.Base.idxSegReg == DISSELREG_SS)) */ if (pCpu->pCurInstr->fParam1 == OP_PARM_REG_CS) { rc = patmPatchGenPushCS(pVM, pPatch); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; } goto duplicate_instr; case OP_IRET: Log(("IRET at %RRv\n", pCurInstrGC)); rc = patmPatchGenIret(pVM, pPatch, pCurInstrGC, !!(pCpu->fPrefix & DISPREFIX_OPSIZE)); if (RT_SUCCESS(rc)) { pPatch->flags |= PATMFL_FOUND_PATCHEND; rc = VINF_SUCCESS; /* exit point by definition */ } break; case OP_ILLUD2: /* This appears to be some kind of kernel panic in Linux 2.4; no point to continue */ rc = patmPatchGenIllegalInstr(pVM, pPatch); if (RT_SUCCESS(rc)) rc = VINF_SUCCESS; /* exit point by definition */ Log(("Illegal opcode (0xf 0xb)\n")); break; case OP_CPUID: rc = patmPatchGenCpuid(pVM, pPatch, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; case OP_STR: #ifdef VBOX_WITH_SAFE_STR /* @todo remove DISOPTYPE_PRIVILEGED_NOTRAP from disasm table and move OP_STR into #ifndef */ /* Now safe because our shadow TR entry is identical to the guest's. */ goto duplicate_instr; #endif case OP_SLDT: rc = patmPatchGenSldtStr(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; case OP_SGDT: case OP_SIDT: rc = patmPatchGenSxDT(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; case OP_RETN: /* retn is an exit point for function patches */ rc = patmPatchGenRet(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VINF_SUCCESS; /* exit point by definition */ break; case OP_SYSEXIT: /* Duplicate it, so it can be emulated in GC (or fault). */ rc = patmPatchGenDuplicate(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VINF_SUCCESS; /* exit point by definition */ break; case OP_CALL: Assert(pPatch->flags & PATMFL_SUPPORT_INDIRECT_CALLS); /* In interrupt gate handlers it's possible to encounter jumps or calls when IF has been enabled again. * In that case we'll jump to the original instruction and continue from there. Otherwise an int 3 is executed. */ Assert(pCpu->Param1.cb == 4 || pCpu->Param1.cb == 6); if (pPatch->flags & PATMFL_SUPPORT_INDIRECT_CALLS && pCpu->Param1.cb == 4 /* no far calls! */) { rc = patmPatchGenCall(pVM, pPatch, pCpu, pCurInstrGC, (RTRCPTR)0xDEADBEEF, true); if (RT_SUCCESS(rc)) { rc = VWRN_CONTINUE_RECOMPILE; } break; } goto gen_illegal_instr; case OP_JMP: Assert(pPatch->flags & PATMFL_SUPPORT_INDIRECT_CALLS); /* In interrupt gate handlers it's possible to encounter jumps or calls when IF has been enabled again. * In that case we'll jump to the original instruction and continue from there. Otherwise an int 3 is executed. */ Assert(pCpu->Param1.cb == 4 || pCpu->Param1.cb == 6); if (pPatch->flags & PATMFL_SUPPORT_INDIRECT_CALLS && pCpu->Param1.cb == 4 /* no far jumps! */) { rc = patmPatchGenJump(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VINF_SUCCESS; /* end of branch */ break; } goto gen_illegal_instr; case OP_INT3: case OP_INT: case OP_INTO: goto gen_illegal_instr; case OP_MOV_DR: /* Note: currently we let DRx writes cause a trap d; our trap handler will decide to interpret it or not. */ if (pCpu->pCurInstr->fParam2 == OP_PARM_Dd) { rc = patmPatchGenMovDebug(pVM, pPatch, pCpu); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; } goto duplicate_instr; case OP_MOV_CR: /* Note: currently we let CRx writes cause a trap d; our trap handler will decide to interpret it or not. */ if (pCpu->pCurInstr->fParam2 == OP_PARM_Cd) { rc = patmPatchGenMovControl(pVM, pPatch, pCpu); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; break; } goto duplicate_instr; default: if (pCpu->pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW | DISOPTYPE_PRIVILEGED_NOTRAP)) { gen_illegal_instr: rc = patmPatchGenIllegalInstr(pVM, pPatch); if (RT_SUCCESS(rc)) rc = VINF_SUCCESS; /* exit point by definition */ } else { duplicate_instr: Log(("patmPatchGenDuplicate\n")); rc = patmPatchGenDuplicate(pVM, pPatch, pCpu, pCurInstrGC); if (RT_SUCCESS(rc)) rc = VWRN_CONTINUE_RECOMPILE; } break; } end: if ( !fInhibitIRQInstr && (pPatch->flags & PATMFL_INHIBIT_IRQS)) { int rc2; RTRCPTR pNextInstrGC = pCurInstrGC + pCpu->cbInstr; pPatch->flags &= ~PATMFL_INHIBIT_IRQS; Log(("Clear inhibit IRQ flag at %RRv\n", pCurInstrGC)); if (pPatch->flags & PATMFL_GENERATE_JUMPTOGUEST) { Log(("patmRecompileCallback: generate jump back to guest (%RRv) after fused instruction\n", pNextInstrGC)); rc2 = patmPatchGenJumpToGuest(pVM, pPatch, pNextInstrGC, true /* clear inhibit irq flag */); pPatch->flags &= ~PATMFL_GENERATE_JUMPTOGUEST; rc = VINF_SUCCESS; /* end of the line */ } else { rc2 = patmPatchGenClearInhibitIRQ(pVM, pPatch, pNextInstrGC); } if (RT_FAILURE(rc2)) rc = rc2; } if (RT_SUCCESS(rc)) { // If single instruction patch, we've copied enough instructions *and* the current instruction is not a relative jump if ( (pPatch->flags & PATMFL_CHECK_SIZE) && pCurInstrGC + pCpu->cbInstr - pInstrGC >= SIZEOF_NEARJUMP32 && !(pCpu->pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW) && !(pPatch->flags & PATMFL_RECOMPILE_NEXT) /* do not do this when the next instruction *must* be executed! */ ) { RTRCPTR pNextInstrGC = pCurInstrGC + pCpu->cbInstr; // The end marker for this kind of patch is any instruction at a location outside our patch jump Log(("patmRecompileCallback: end found for single instruction patch at %RRv cbInstr %d\n", pNextInstrGC, pCpu->cbInstr)); rc = patmPatchGenJumpToGuest(pVM, pPatch, pNextInstrGC); AssertRC(rc); } } return rc; } #ifdef LOG_ENABLED /** * Add a disasm jump record (temporary for prevent duplicate analysis) * * @param pVM Pointer to the VM. * @param pPatch Patch structure ptr * @param pInstrGC Guest context pointer to privileged instruction * */ static void patmPatchAddDisasmJump(PVM pVM, PPATCHINFO pPatch, RTRCPTR pInstrGC) { PAVLPVNODECORE pRec; pRec = (PAVLPVNODECORE)MMR3HeapAllocZ(pVM, MM_TAG_PATM_PATCH, sizeof(*pRec)); Assert(pRec); pRec->Key = (AVLPVKEY)(uintptr_t)pInstrGC; int ret = RTAvlPVInsert(&pPatch->pTempInfo->DisasmJumpTree, pRec); Assert(ret); } /** * Checks if jump target has been analysed before. * * @returns VBox status code. * @param pPatch Patch struct * @param pInstrGC Jump target * */ static bool patmIsKnownDisasmJump(PPATCHINFO pPatch, RTRCPTR pInstrGC) { PAVLPVNODECORE pRec; pRec = RTAvlPVGet(&pPatch->pTempInfo->DisasmJumpTree, (AVLPVKEY)(uintptr_t)pInstrGC); if (pRec) return true; return false; } /** * For proper disassembly of the final patch block * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCpu CPU disassembly state * @param pInstrGC Guest context pointer to privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pCacheRec Cache record ptr * */ int patmr3DisasmCallback(PVM pVM, DISCPUSTATE *pCpu, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; NOREF(pInstrGC); if (pCpu->pCurInstr->uOpcode == OP_INT3) { /* Could be an int3 inserted in a call patch. Check to be sure */ DISCPUSTATE cpu; RTRCPTR pOrgJumpGC; pOrgJumpGC = patmPatchGCPtr2GuestGCPtr(pVM, pPatch, pCurInstrGC); { /* Force pOrgJumpHC out of scope after using it */ uint8_t *pOrgJumpHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pOrgJumpGC); bool disret = patmR3DisInstr(pVM, pPatch, pOrgJumpGC, pOrgJumpHC, PATMREAD_ORGCODE, &cpu, NULL); if (!disret || cpu.pCurInstr->uOpcode != OP_CALL || cpu.Param1.cb != 4 /* only near calls */) return VINF_SUCCESS; } return VWRN_CONTINUE_ANALYSIS; } if ( pCpu->pCurInstr->uOpcode == OP_ILLUD2 && PATMIsPatchGCAddr(pVM, pCurInstrGC)) { /* the indirect call patch contains an 0xF/0xB illegal instr to call for assistance; check for this and continue */ return VWRN_CONTINUE_ANALYSIS; } if ( (pCpu->pCurInstr->uOpcode == OP_CALL && !(pPatch->flags & PATMFL_SUPPORT_CALLS)) || pCpu->pCurInstr->uOpcode == OP_INT || pCpu->pCurInstr->uOpcode == OP_IRET || pCpu->pCurInstr->uOpcode == OP_RETN || pCpu->pCurInstr->uOpcode == OP_RETF ) { return VINF_SUCCESS; } if (pCpu->pCurInstr->uOpcode == OP_ILLUD2) return VINF_SUCCESS; return VWRN_CONTINUE_ANALYSIS; } /** * Disassembles the code stream until the callback function detects a failure or decides everything is acceptable * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context pointer to the initial privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pfnPATMR3Disasm Callback for testing the disassembled instruction * @param pCacheRec Cache record ptr * */ int patmr3DisasmCode(PVM pVM, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PFN_PATMR3ANALYSE pfnPATMR3Disasm, PPATMP2GLOOKUPREC pCacheRec) { DISCPUSTATE cpu; PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; int rc = VWRN_CONTINUE_ANALYSIS; uint32_t cbInstr, delta; R3PTRTYPE(uint8_t *) pCurInstrHC = 0; bool disret; char szOutput[256]; Assert(pCurInstrHC != PATCHCODE_PTR_HC(pPatch) || pPatch->pTempInfo->DisasmJumpTree == 0); /* We need this to determine branch targets (and for disassembling). */ delta = pVM->patm.s.pPatchMemGC - (uintptr_t)pVM->patm.s.pPatchMemHC; while (rc == VWRN_CONTINUE_ANALYSIS) { pCurInstrHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pCurInstrGC); if (pCurInstrHC == NULL) { rc = VERR_PATCHING_REFUSED; goto end; } disret = patmR3DisInstrToStr(pVM, pPatch, pCurInstrGC, pCurInstrHC, PATMREAD_RAWCODE, &cpu, &cbInstr, szOutput, sizeof(szOutput)); if (PATMIsPatchGCAddr(pVM, pCurInstrGC)) { RTRCPTR pOrgInstrGC = patmPatchGCPtr2GuestGCPtr(pVM, pPatch, pCurInstrGC); if (pOrgInstrGC != pPatch->pTempInfo->pLastDisasmInstrGC) Log(("DIS %RRv<-%s", pOrgInstrGC, szOutput)); else Log(("DIS %s", szOutput)); pPatch->pTempInfo->pLastDisasmInstrGC = pOrgInstrGC; if (patmIsIllegalInstr(pPatch, pOrgInstrGC)) { rc = VINF_SUCCESS; goto end; } } else Log(("DIS: %s", szOutput)); if (disret == false) { Log(("Disassembly failed (probably page not present) -> return to caller\n")); rc = VINF_SUCCESS; goto end; } rc = pfnPATMR3Disasm(pVM, &cpu, pInstrGC, pCurInstrGC, pCacheRec); if (rc != VWRN_CONTINUE_ANALYSIS) { break; //done! } /* For our first attempt, we'll handle only simple relative jumps and calls (immediate offset coded in instruction) */ if ( (cpu.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) && (OP_PARM_VTYPE(cpu.pCurInstr->fParam1) == OP_PARM_J) && cpu.pCurInstr->uOpcode != OP_CALL /* complete functions are replaced; don't bother here. */ ) { RTRCPTR pTargetGC = PATMResolveBranch(&cpu, pCurInstrGC); RTRCPTR pOrgTargetGC; if (pTargetGC == 0) { Log(("We don't support far jumps here!! (%08X)\n", cpu.Param1.fUse)); rc = VERR_PATCHING_REFUSED; break; } if (!PATMIsPatchGCAddr(pVM, pTargetGC)) { //jump back to guest code rc = VINF_SUCCESS; goto end; } pOrgTargetGC = PATMR3PatchToGCPtr(pVM, pTargetGC, 0); if (patmIsCommonIDTHandlerPatch(pVM, pOrgTargetGC)) { rc = VINF_SUCCESS; goto end; } if (patmIsKnownDisasmJump(pPatch, pTargetGC) == false) { /* New jump, let's check it. */ patmPatchAddDisasmJump(pVM, pPatch, pTargetGC); if (cpu.pCurInstr->uOpcode == OP_CALL) pPatch->pTempInfo->nrCalls++; rc = patmr3DisasmCode(pVM, pInstrGC, pTargetGC, pfnPATMR3Disasm, pCacheRec); if (cpu.pCurInstr->uOpcode == OP_CALL) pPatch->pTempInfo->nrCalls--; if (rc != VINF_SUCCESS) { break; //done! } } if (cpu.pCurInstr->uOpcode == OP_JMP) { /* Unconditional jump; return to caller. */ rc = VINF_SUCCESS; goto end; } rc = VWRN_CONTINUE_ANALYSIS; } pCurInstrGC += cbInstr; } end: return rc; } /** * Disassembles the code stream until the callback function detects a failure or decides everything is acceptable * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context pointer to the initial privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pfnPATMR3Disasm Callback for testing the disassembled instruction * @param pCacheRec Cache record ptr * */ int patmr3DisasmCodeStream(PVM pVM, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PFN_PATMR3ANALYSE pfnPATMR3Disasm, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; int rc = patmr3DisasmCode(pVM, pInstrGC, pCurInstrGC, pfnPATMR3Disasm, pCacheRec); /* Free all disasm jump records. */ patmEmptyTree(pVM, &pPatch->pTempInfo->DisasmJumpTree); return rc; } #endif /* LOG_ENABLED */ /** * Detects it the specified address falls within a 5 byte jump generated for an active patch. * If so, this patch is permanently disabled. * * @param pVM Pointer to the VM. * @param pInstrGC Guest context pointer to instruction * @param pConflictGC Guest context pointer to check * * @note also checks for patch hints to make sure they can never be enabled if a conflict is present. * */ VMMR3_INT_DECL(int) PATMR3DetectConflict(PVM pVM, RTRCPTR pInstrGC, RTRCPTR pConflictGC) { AssertReturn(!HMIsEnabled(pVM), VERR_PATCH_NO_CONFLICT); PPATCHINFO pTargetPatch = patmFindActivePatchByEntrypoint(pVM, pConflictGC, true /* include patch hints */); if (pTargetPatch) return patmDisableUnusablePatch(pVM, pInstrGC, pConflictGC, pTargetPatch); return VERR_PATCH_NO_CONFLICT; } /** * Recompile the code stream until the callback function detects a failure or decides everything is acceptable * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context pointer to privileged instruction * @param pCurInstrGC Guest context pointer to the current instruction * @param pfnPATMR3Recompile Callback for testing the disassembled instruction * @param pCacheRec Cache record ptr * */ static int patmRecompileCodeStream(PVM pVM, RCPTRTYPE(uint8_t *) pInstrGC, RCPTRTYPE(uint8_t *) pCurInstrGC, PFN_PATMR3ANALYSE pfnPATMR3Recompile, PPATMP2GLOOKUPREC pCacheRec) { DISCPUSTATE cpu; PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; int rc = VWRN_CONTINUE_ANALYSIS; uint32_t cbInstr; R3PTRTYPE(uint8_t *) pCurInstrHC = 0; bool disret; #ifdef LOG_ENABLED char szOutput[256]; #endif while (rc == VWRN_CONTINUE_RECOMPILE) { pCurInstrHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pCurInstrGC); if (pCurInstrHC == NULL) { rc = VERR_PATCHING_REFUSED; /* fatal in this case */ goto end; } #ifdef LOG_ENABLED disret = patmR3DisInstrToStr(pVM, pPatch, pCurInstrGC, pCurInstrHC, PATMREAD_ORGCODE, &cpu, &cbInstr, szOutput, sizeof(szOutput)); Log(("Recompile: %s", szOutput)); #else disret = patmR3DisInstr(pVM, pPatch, pCurInstrGC, pCurInstrHC, PATMREAD_ORGCODE, &cpu, &cbInstr); #endif if (disret == false) { Log(("Disassembly failed (probably page not present) -> return to caller\n")); /* Add lookup record for patch to guest address translation */ patmR3AddP2GLookupRecord(pVM, pPatch, PATCHCODE_PTR_HC(pPatch) + pPatch->uCurPatchOffset, pCurInstrGC, PATM_LOOKUP_BOTHDIR); patmPatchGenIllegalInstr(pVM, pPatch); rc = VINF_SUCCESS; /* Note: don't fail here; we might refuse an important patch!! */ goto end; } rc = pfnPATMR3Recompile(pVM, &cpu, pInstrGC, pCurInstrGC, pCacheRec); if (rc != VWRN_CONTINUE_RECOMPILE) { /* If irqs are inhibited because of the current instruction, then we must make sure the next one is executed! */ if ( rc == VINF_SUCCESS && (pPatch->flags & PATMFL_INHIBIT_IRQS)) { DISCPUSTATE cpunext; uint32_t opsizenext; uint8_t *pNextInstrHC; RTRCPTR pNextInstrGC = pCurInstrGC + cbInstr; Log(("patmRecompileCodeStream: irqs inhibited by instruction %RRv\n", pNextInstrGC)); /* Certain instructions (e.g. sti) force the next instruction to be executed before any interrupts can occur. * Recompile the next instruction as well */ pNextInstrHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pNextInstrGC); if (pNextInstrHC == NULL) { rc = VERR_PATCHING_REFUSED; /* fatal in this case */ goto end; } disret = patmR3DisInstr(pVM, pPatch, pNextInstrGC, pNextInstrHC, PATMREAD_ORGCODE, &cpunext, &opsizenext); if (disret == false) { rc = VERR_PATCHING_REFUSED; /* fatal in this case */ goto end; } switch(cpunext.pCurInstr->uOpcode) { case OP_IRET: /* inhibit cleared in generated code */ case OP_SYSEXIT: /* faults; inhibit should be cleared in HC handling */ case OP_HLT: break; /* recompile these */ default: if (cpunext.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) { Log(("Unexpected control flow instruction after inhibit irq instruction\n")); rc = patmPatchGenJumpToGuest(pVM, pPatch, pNextInstrGC, true /* clear inhibit irq flag */); AssertRC(rc); pPatch->flags &= ~PATMFL_INHIBIT_IRQS; goto end; /** @todo should be ok to ignore instruction fusing in this case */ } break; } /* Note: after a cli we must continue to a proper exit point */ if (cpunext.pCurInstr->uOpcode != OP_CLI) { rc = pfnPATMR3Recompile(pVM, &cpunext, pInstrGC, pNextInstrGC, pCacheRec); if (RT_SUCCESS(rc)) { rc = VINF_SUCCESS; goto end; } break; } else rc = VWRN_CONTINUE_RECOMPILE; } else break; /* done! */ } /** @todo continue with the instructions following the jump and then recompile the jump target code */ /* For our first attempt, we'll handle only simple relative jumps and calls (immediate offset coded in instruction). */ if ( (cpu.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) && (OP_PARM_VTYPE(cpu.pCurInstr->fParam1) == OP_PARM_J) && cpu.pCurInstr->uOpcode != OP_CALL /* complete functions are replaced; don't bother here. */ ) { RCPTRTYPE(uint8_t *) addr = PATMResolveBranch(&cpu, pCurInstrGC); if (addr == 0) { Log(("We don't support far jumps here!! (%08X)\n", cpu.Param1.fUse)); rc = VERR_PATCHING_REFUSED; break; } Log(("Jump encountered target %RRv\n", addr)); /* We don't check if the branch target lies in a valid page as we've already done that in the analysis phase. */ if (!(cpu.pCurInstr->fOpType & DISOPTYPE_UNCOND_CONTROLFLOW)) { Log(("patmRecompileCodeStream continue passed conditional jump\n")); /* First we need to finish this linear code stream until the next exit point. */ rc = patmRecompileCodeStream(pVM, pInstrGC, pCurInstrGC+cbInstr, pfnPATMR3Recompile, pCacheRec); if (RT_FAILURE(rc)) { Log(("patmRecompileCodeStream fatal error %d\n", rc)); break; //fatal error } } if (patmGuestGCPtrToPatchGCPtr(pVM, pPatch, addr) == 0) { /* New code; let's recompile it. */ Log(("patmRecompileCodeStream continue with jump\n")); /* * If we are jumping to an existing patch (or within 5 bytes of the entrypoint), then we must temporarily disable * this patch so we can continue our analysis * * We rely on CSAM to detect and resolve conflicts */ PPATCHINFO pTargetPatch = patmFindActivePatchByEntrypoint(pVM, addr); if(pTargetPatch) { Log(("Found active patch at target %RRv (%RRv) -> temporarily disabling it!!\n", addr, pTargetPatch->pPrivInstrGC)); PATMR3DisablePatch(pVM, pTargetPatch->pPrivInstrGC); } if (cpu.pCurInstr->uOpcode == OP_CALL) pPatch->pTempInfo->nrCalls++; rc = patmRecompileCodeStream(pVM, pInstrGC, addr, pfnPATMR3Recompile, pCacheRec); if (cpu.pCurInstr->uOpcode == OP_CALL) pPatch->pTempInfo->nrCalls--; if(pTargetPatch) PATMR3EnablePatch(pVM, pTargetPatch->pPrivInstrGC); if (RT_FAILURE(rc)) { Log(("patmRecompileCodeStream fatal error %d\n", rc)); break; //done! } } /* Always return to caller here; we're done! */ rc = VINF_SUCCESS; goto end; } else if (cpu.pCurInstr->fOpType & DISOPTYPE_UNCOND_CONTROLFLOW) { rc = VINF_SUCCESS; goto end; } pCurInstrGC += cbInstr; } end: Assert(!(pPatch->flags & PATMFL_RECOMPILE_NEXT)); return rc; } /** * Generate the jump from guest to patch code * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record * @param pCacheRec Guest translation lookup cache record */ static int patmGenJumpToPatch(PVM pVM, PPATCHINFO pPatch, PPATMP2GLOOKUPREC pCacheRec, bool fAddFixup = true) { uint8_t temp[8]; uint8_t *pPB; int rc; Assert(pPatch->cbPatchJump <= sizeof(temp)); Assert(!(pPatch->flags & PATMFL_PATCHED_GUEST_CODE)); pPB = patmR3GCVirtToHCVirt(pVM, pCacheRec, pPatch->pPrivInstrGC); Assert(pPB); #ifdef PATM_RESOLVE_CONFLICTS_WITH_JUMP_PATCHES if (pPatch->flags & PATMFL_JUMP_CONFLICT) { Assert(pPatch->pPatchJumpDestGC); if (pPatch->cbPatchJump == SIZEOF_NEARJUMP32) { // jmp [PatchCode] if (fAddFixup) { if (patmPatchAddReloc32(pVM, pPatch, &pPB[1], FIXUP_REL_JMPTOPATCH, pPatch->pPrivInstrGC + pPatch->cbPatchJump, pPatch->pPatchJumpDestGC) != VINF_SUCCESS) { Log(("Relocation failed for the jump in the guest code!!\n")); return VERR_PATCHING_REFUSED; } } temp[0] = pPatch->aPrivInstr[0]; //jump opcode copied from original instruction *(uint32_t *)&temp[1] = (uint32_t)pPatch->pPatchJumpDestGC - ((uint32_t)pPatch->pPrivInstrGC + pPatch->cbPatchJump); //return address } else if (pPatch->cbPatchJump == SIZEOF_NEAR_COND_JUMP32) { // jmp [PatchCode] if (fAddFixup) { if (patmPatchAddReloc32(pVM, pPatch, &pPB[2], FIXUP_REL_JMPTOPATCH, pPatch->pPrivInstrGC + pPatch->cbPatchJump, pPatch->pPatchJumpDestGC) != VINF_SUCCESS) { Log(("Relocation failed for the jump in the guest code!!\n")); return VERR_PATCHING_REFUSED; } } temp[0] = pPatch->aPrivInstr[0]; //jump opcode copied from original instruction temp[1] = pPatch->aPrivInstr[1]; //jump opcode copied from original instruction *(uint32_t *)&temp[2] = (uint32_t)pPatch->pPatchJumpDestGC - ((uint32_t)pPatch->pPrivInstrGC + pPatch->cbPatchJump); //return address } else { Assert(0); return VERR_PATCHING_REFUSED; } } else #endif { Assert(pPatch->cbPatchJump == SIZEOF_NEARJUMP32); // jmp [PatchCode] if (fAddFixup) { if (patmPatchAddReloc32(pVM, pPatch, &pPB[1], FIXUP_REL_JMPTOPATCH, pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32, PATCHCODE_PTR_GC(pPatch)) != VINF_SUCCESS) { Log(("Relocation failed for the jump in the guest code!!\n")); return VERR_PATCHING_REFUSED; } } temp[0] = 0xE9; //jmp *(uint32_t *)&temp[1] = (RTRCUINTPTR)PATCHCODE_PTR_GC(pPatch) - ((RTRCUINTPTR)pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32); //return address } rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pPatch->pPrivInstrGC, temp, pPatch->cbPatchJump); AssertRC(rc); if (rc == VINF_SUCCESS) pPatch->flags |= PATMFL_PATCHED_GUEST_CODE; return rc; } /** * Remove the jump from guest to patch code * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record */ static int patmRemoveJumpToPatch(PVM pVM, PPATCHINFO pPatch) { #ifdef DEBUG DISCPUSTATE cpu; char szOutput[256]; uint32_t cbInstr, i = 0; bool disret; while (i < pPatch->cbPrivInstr) { disret = patmR3DisInstrToStr(pVM, pPatch, pPatch->pPrivInstrGC + i, NULL, PATMREAD_ORGCODE, &cpu, &cbInstr, szOutput, sizeof(szOutput)); if (disret == false) break; Log(("Org patch jump: %s", szOutput)); Assert(cbInstr); i += cbInstr; } #endif /* Restore original code (privileged instruction + following instructions that were overwritten because of the 5/6 byte jmp). */ int rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pPatch->pPrivInstrGC, pPatch->aPrivInstr, pPatch->cbPatchJump); #ifdef DEBUG if (rc == VINF_SUCCESS) { i = 0; while (i < pPatch->cbPrivInstr) { disret = patmR3DisInstrToStr(pVM, pPatch, pPatch->pPrivInstrGC + i, NULL, PATMREAD_ORGCODE, &cpu, &cbInstr, szOutput, sizeof(szOutput)); if (disret == false) break; Log(("Org instr: %s", szOutput)); Assert(cbInstr); i += cbInstr; } } #endif pPatch->flags &= ~PATMFL_PATCHED_GUEST_CODE; return rc; } /** * Generate the call from guest to patch code * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record * @param pInstrHC HC address where to insert the jump * @param pCacheRec Guest translation cache record */ static int patmGenCallToPatch(PVM pVM, PPATCHINFO pPatch, RTRCPTR pTargetGC, PPATMP2GLOOKUPREC pCacheRec, bool fAddFixup = true) { uint8_t temp[8]; uint8_t *pPB; int rc; Assert(pPatch->cbPatchJump <= sizeof(temp)); pPB = patmR3GCVirtToHCVirt(pVM, pCacheRec, pPatch->pPrivInstrGC); Assert(pPB); Assert(pPatch->cbPatchJump == SIZEOF_NEARJUMP32); // jmp [PatchCode] if (fAddFixup) { if (patmPatchAddReloc32(pVM, pPatch, &pPB[1], FIXUP_REL_JMPTOPATCH, pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32, pTargetGC) != VINF_SUCCESS) { Log(("Relocation failed for the jump in the guest code!!\n")); return VERR_PATCHING_REFUSED; } } Assert(pPatch->aPrivInstr[0] == 0xE8 || pPatch->aPrivInstr[0] == 0xE9); /* call or jmp */ temp[0] = pPatch->aPrivInstr[0]; *(uint32_t *)&temp[1] = (uint32_t)pTargetGC - ((uint32_t)pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32); //return address rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pPatch->pPrivInstrGC, temp, pPatch->cbPatchJump); AssertRC(rc); return rc; } /** * Patch cli/sti pushf/popf instruction block at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pInstrHC Host context point to privileged instruction * @param uOpcode Instruction opcode * @param uOpSize Size of starting instruction * @param pPatchRec Patch record * * @note returns failure if patching is not allowed or possible * */ static int patmR3PatchBlock(PVM pVM, RTRCPTR pInstrGC, R3PTRTYPE(uint8_t *) pInstrHC, uint32_t uOpcode, uint32_t uOpSize, PPATMPATCHREC pPatchRec) { PPATCHINFO pPatch = &pPatchRec->patch; int rc = VERR_PATCHING_REFUSED; uint32_t orgOffsetPatchMem = ~0; RTRCPTR pInstrStart; bool fInserted; NOREF(pInstrHC); NOREF(uOpSize); /* Save original offset (in case of failures later on) */ /** @todo use the hypervisor heap (that has quite a few consequences for save/restore though) */ orgOffsetPatchMem = pVM->patm.s.offPatchMem; Assert(!(pPatch->flags & (PATMFL_GUEST_SPECIFIC|PATMFL_USER_MODE|PATMFL_TRAPHANDLER))); switch (uOpcode) { case OP_MOV: break; case OP_CLI: case OP_PUSHF: /* We can 'call' a cli or pushf patch. It will either return to the original guest code when IF is set again, or fault. */ /* Note: special precautions are taken when disabling and enabling such patches. */ pPatch->flags |= PATMFL_CALLABLE_AS_FUNCTION; break; default: if (!(pPatch->flags & PATMFL_IDTHANDLER)) { AssertMsg(0, ("patmR3PatchBlock: Invalid opcode %x\n", uOpcode)); return VERR_INVALID_PARAMETER; } } if (!(pPatch->flags & (PATMFL_IDTHANDLER|PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT|PATMFL_SYSENTER|PATMFL_INT3_REPLACEMENT_BLOCK))) pPatch->flags |= PATMFL_MUST_INSTALL_PATCHJMP; /* If we're going to insert a patch jump, then the jump itself is not allowed to cross a page boundary. */ if ( (pPatch->flags & PATMFL_MUST_INSTALL_PATCHJMP) && PAGE_ADDRESS(pInstrGC) != PAGE_ADDRESS(pInstrGC + SIZEOF_NEARJUMP32) ) { STAM_COUNTER_INC(&pVM->patm.s.StatPageBoundaryCrossed); Log(("Patch jump would cross page boundary -> refuse!!\n")); rc = VERR_PATCHING_REFUSED; goto failure; } pPatch->nrPatch2GuestRecs = 0; pInstrStart = pInstrGC; #ifdef PATM_ENABLE_CALL pPatch->flags |= PATMFL_SUPPORT_CALLS | PATMFL_SUPPORT_INDIRECT_CALLS; #endif pPatch->pPatchBlockOffset = pVM->patm.s.offPatchMem; pPatch->uCurPatchOffset = 0; if ((pPatch->flags & (PATMFL_IDTHANDLER|PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT|PATMFL_SYSENTER)) == PATMFL_IDTHANDLER) { Assert(pPatch->flags & PATMFL_INTHANDLER); /* Install fake cli patch (to clear the virtual IF and check int xx parameters) */ rc = patmPatchGenIntEntry(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; } /***************************************************************************************************************************/ /* Note: We can't insert *any* code before a sysenter handler; some linux guests have an invalid stack at this point!!!!! */ /***************************************************************************************************************************/ #ifdef VBOX_WITH_STATISTICS if (!(pPatch->flags & PATMFL_SYSENTER)) { rc = patmPatchGenStats(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; } #endif PATMP2GLOOKUPREC cacheRec; RT_ZERO(cacheRec); cacheRec.pPatch = pPatch; rc = patmRecompileCodeStream(pVM, pInstrGC, pInstrGC, patmRecompileCallback, &cacheRec); /* Free leftover lock if any. */ if (cacheRec.Lock.pvMap) { PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); cacheRec.Lock.pvMap = NULL; } if (rc != VINF_SUCCESS) { Log(("PATMR3PatchCli: patmRecompileCodeStream failed with %d\n", rc)); goto failure; } /* Calculated during analysis. */ if (pPatch->cbPatchBlockSize < SIZEOF_NEARJUMP32) { /* Most likely cause: we encountered an illegal instruction very early on. */ /** @todo could turn it into an int3 callable patch. */ Log(("patmR3PatchBlock: patch block too small -> refuse\n")); rc = VERR_PATCHING_REFUSED; goto failure; } /* size of patch block */ pPatch->cbPatchBlockSize = pPatch->uCurPatchOffset; /* Update free pointer in patch memory. */ pVM->patm.s.offPatchMem += pPatch->cbPatchBlockSize; /* Round to next 8 byte boundary. */ pVM->patm.s.offPatchMem = RT_ALIGN_32(pVM->patm.s.offPatchMem, 8); /* * Insert into patch to guest lookup tree */ LogFlow(("Insert %RRv patch offset %RRv\n", pPatchRec->patch.pPrivInstrGC, pPatch->pPatchBlockOffset)); pPatchRec->CoreOffset.Key = pPatch->pPatchBlockOffset; fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, &pPatchRec->CoreOffset); AssertMsg(fInserted, ("RTAvlULInsert failed for %x\n", pPatchRec->CoreOffset.Key)); if (!fInserted) { rc = VERR_PATCHING_REFUSED; goto failure; } /* Note that patmr3SetBranchTargets can install additional patches!! */ rc = patmr3SetBranchTargets(pVM, pPatch); if (rc != VINF_SUCCESS) { Log(("PATMR3PatchCli: patmr3SetBranchTargets failed with %d\n", rc)); goto failure; } #ifdef LOG_ENABLED Log(("Patch code ----------------------------------------------------------\n")); patmr3DisasmCodeStream(pVM, PATCHCODE_PTR_GC(pPatch), PATCHCODE_PTR_GC(pPatch), patmr3DisasmCallback, &cacheRec); /* Free leftover lock if any. */ if (cacheRec.Lock.pvMap) { PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); cacheRec.Lock.pvMap = NULL; } Log(("Patch code ends -----------------------------------------------------\n")); #endif /* make a copy of the guest code bytes that will be overwritten */ pPatch->cbPatchJump = SIZEOF_NEARJUMP32; rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), pPatch->aPrivInstr, pPatch->pPrivInstrGC, pPatch->cbPatchJump); AssertRC(rc); if (pPatch->flags & PATMFL_INT3_REPLACEMENT_BLOCK) { /*uint8_t bASMInt3 = 0xCC; - unused */ Log(("patmR3PatchBlock %RRv -> int 3 callable patch.\n", pPatch->pPrivInstrGC)); /* Replace first opcode byte with 'int 3'. */ rc = patmActivateInt3Patch(pVM, pPatch); if (RT_FAILURE(rc)) goto failure; /* normal patch can be turned into an int3 patch -> clear patch jump installation flag. */ pPatch->flags &= ~PATMFL_MUST_INSTALL_PATCHJMP; pPatch->flags &= ~PATMFL_INSTR_HINT; STAM_COUNTER_INC(&pVM->patm.s.StatInt3Callable); } else if (pPatch->flags & PATMFL_MUST_INSTALL_PATCHJMP) { Assert(!(pPatch->flags & (PATMFL_IDTHANDLER|PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT|PATMFL_SYSENTER|PATMFL_INT3_REPLACEMENT_BLOCK))); /* now insert a jump in the guest code */ rc = patmGenJumpToPatch(pVM, pPatch, &cacheRec, true); AssertRC(rc); if (RT_FAILURE(rc)) goto failure; } patmR3DbgAddPatch(pVM, pPatchRec); PATM_LOG_RAW_PATCH_INSTR(pVM, pPatch, patmGetInstructionString(pPatch->opcode, pPatch->flags)); patmEmptyTree(pVM, &pPatch->pTempInfo->IllegalInstrTree); pPatch->pTempInfo->nrIllegalInstr = 0; Log(("Successfully installed %s patch at %RRv\n", patmGetInstructionString(pPatch->opcode, pPatch->flags), pInstrGC)); pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; failure: if (pPatchRec->CoreOffset.Key) RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, pPatchRec->CoreOffset.Key); patmEmptyTree(pVM, &pPatch->FixupTree); pPatch->nrFixups = 0; patmEmptyTree(pVM, &pPatch->JumpTree); pPatch->nrJumpRecs = 0; patmEmptyTree(pVM, &pPatch->pTempInfo->IllegalInstrTree); pPatch->pTempInfo->nrIllegalInstr = 0; /* Turn this cli patch into a dummy. */ pPatch->uState = PATCH_REFUSED; pPatch->pPatchBlockOffset = 0; // Give back the patch memory we no longer need Assert(orgOffsetPatchMem != (uint32_t)~0); pVM->patm.s.offPatchMem = orgOffsetPatchMem; return rc; } /** * Patch IDT handler * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param uOpSize Size of starting instruction * @param pPatchRec Patch record * @param pCacheRec Cache record ptr * * @note returns failure if patching is not allowed or possible * */ static int patmIdtHandler(PVM pVM, RTRCPTR pInstrGC, uint32_t uOpSize, PPATMPATCHREC pPatchRec, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = &pPatchRec->patch; bool disret; DISCPUSTATE cpuPush, cpuJmp; uint32_t cbInstr; RTRCPTR pCurInstrGC = pInstrGC; uint8_t *pCurInstrHC, *pInstrHC; uint32_t orgOffsetPatchMem = ~0; pInstrHC = pCurInstrHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pCurInstrGC); AssertReturn(pCurInstrHC, VERR_PAGE_NOT_PRESENT); /* * In Linux it's often the case that many interrupt handlers push a predefined value onto the stack * and then jump to a common entrypoint. In order not to waste a lot of memory, we will check for this * condition here and only patch the common entypoint once. */ disret = patmR3DisInstr(pVM, pPatch, pCurInstrGC, pCurInstrHC, PATMREAD_ORGCODE, &cpuPush, &cbInstr); Assert(disret); if (disret && cpuPush.pCurInstr->uOpcode == OP_PUSH) { RTRCPTR pJmpInstrGC; int rc; pCurInstrGC += cbInstr; disret = patmR3DisInstr(pVM, pPatch, pCurInstrGC, pCurInstrHC, PATMREAD_ORGCODE, &cpuJmp, &cbInstr); if ( disret && cpuJmp.pCurInstr->uOpcode == OP_JMP && (pJmpInstrGC = PATMResolveBranch(&cpuJmp, pCurInstrGC)) ) { bool fInserted; PPATMPATCHREC pJmpPatch = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pJmpInstrGC); if (pJmpPatch == 0) { /* Patch it first! */ rc = PATMR3InstallPatch(pVM, pJmpInstrGC, pPatch->flags | PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT); if (rc != VINF_SUCCESS) goto failure; pJmpPatch = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pJmpInstrGC); Assert(pJmpPatch); } if (pJmpPatch->patch.uState != PATCH_ENABLED) goto failure; /* save original offset (in case of failures later on) */ orgOffsetPatchMem = pVM->patm.s.offPatchMem; pPatch->pPatchBlockOffset = pVM->patm.s.offPatchMem; pPatch->uCurPatchOffset = 0; pPatch->nrPatch2GuestRecs = 0; #ifdef VBOX_WITH_STATISTICS rc = patmPatchGenStats(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; #endif /* Install fake cli patch (to clear the virtual IF) */ rc = patmPatchGenIntEntry(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; /* Add lookup record for patch to guest address translation (for the push) */ patmR3AddP2GLookupRecord(pVM, pPatch, PATCHCODE_PTR_HC(pPatch) + pPatch->uCurPatchOffset, pInstrGC, PATM_LOOKUP_BOTHDIR); /* Duplicate push. */ rc = patmPatchGenDuplicate(pVM, pPatch, &cpuPush, pInstrGC); if (RT_FAILURE(rc)) goto failure; /* Generate jump to common entrypoint. */ rc = patmPatchGenPatchJump(pVM, pPatch, pCurInstrGC, PATCHCODE_PTR_GC(&pJmpPatch->patch)); if (RT_FAILURE(rc)) goto failure; /* size of patch block */ pPatch->cbPatchBlockSize = pPatch->uCurPatchOffset; /* Update free pointer in patch memory. */ pVM->patm.s.offPatchMem += pPatch->cbPatchBlockSize; /* Round to next 8 byte boundary */ pVM->patm.s.offPatchMem = RT_ALIGN_32(pVM->patm.s.offPatchMem, 8); /* There's no jump from guest to patch code. */ pPatch->cbPatchJump = 0; #ifdef LOG_ENABLED Log(("Patch code ----------------------------------------------------------\n")); patmr3DisasmCodeStream(pVM, PATCHCODE_PTR_GC(pPatch), PATCHCODE_PTR_GC(pPatch), patmr3DisasmCallback, pCacheRec); Log(("Patch code ends -----------------------------------------------------\n")); #endif Log(("Successfully installed IDT handler patch at %RRv\n", pInstrGC)); /* * Insert into patch to guest lookup tree */ LogFlow(("Insert %RRv patch offset %RRv\n", pPatchRec->patch.pPrivInstrGC, pPatch->pPatchBlockOffset)); pPatchRec->CoreOffset.Key = pPatch->pPatchBlockOffset; fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, &pPatchRec->CoreOffset); AssertMsg(fInserted, ("RTAvlULInsert failed for %x\n", pPatchRec->CoreOffset.Key)); patmR3DbgAddPatch(pVM, pPatchRec); pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; } } failure: /* Give back the patch memory we no longer need */ if (orgOffsetPatchMem != (uint32_t)~0) pVM->patm.s.offPatchMem = orgOffsetPatchMem; return patmR3PatchBlock(pVM, pInstrGC, pInstrHC, OP_CLI, uOpSize, pPatchRec); } /** * Install a trampoline to call a guest trap handler directly * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pPatchRec Patch record * @param pCacheRec Cache record ptr * */ static int patmInstallTrapTrampoline(PVM pVM, RTRCPTR pInstrGC, PPATMPATCHREC pPatchRec, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = &pPatchRec->patch; int rc = VERR_PATCHING_REFUSED; uint32_t orgOffsetPatchMem = ~0; bool fInserted; // save original offset (in case of failures later on) orgOffsetPatchMem = pVM->patm.s.offPatchMem; pPatch->pPatchBlockOffset = pVM->patm.s.offPatchMem; pPatch->uCurPatchOffset = 0; pPatch->nrPatch2GuestRecs = 0; #ifdef VBOX_WITH_STATISTICS rc = patmPatchGenStats(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; #endif rc = patmPatchGenTrapEntry(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; /* size of patch block */ pPatch->cbPatchBlockSize = pPatch->uCurPatchOffset; /* Update free pointer in patch memory. */ pVM->patm.s.offPatchMem += pPatch->cbPatchBlockSize; /* Round to next 8 byte boundary */ pVM->patm.s.offPatchMem = RT_ALIGN_32(pVM->patm.s.offPatchMem, 8); /* There's no jump from guest to patch code. */ pPatch->cbPatchJump = 0; #ifdef LOG_ENABLED Log(("Patch code ----------------------------------------------------------\n")); patmr3DisasmCodeStream(pVM, PATCHCODE_PTR_GC(pPatch), PATCHCODE_PTR_GC(pPatch), patmr3DisasmCallback, pCacheRec); Log(("Patch code ends -----------------------------------------------------\n")); #endif PATM_LOG_ORG_PATCH_INSTR(pVM, pPatch, "TRAP handler"); Log(("Successfully installed Trap Trampoline patch at %RRv\n", pInstrGC)); /* * Insert into patch to guest lookup tree */ LogFlow(("Insert %RRv patch offset %RRv\n", pPatchRec->patch.pPrivInstrGC, pPatch->pPatchBlockOffset)); pPatchRec->CoreOffset.Key = pPatch->pPatchBlockOffset; fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, &pPatchRec->CoreOffset); AssertMsg(fInserted, ("RTAvlULInsert failed for %x\n", pPatchRec->CoreOffset.Key)); patmR3DbgAddPatch(pVM, pPatchRec); pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; failure: AssertMsgFailed(("Failed to install trap handler trampoline!!\n")); /* Turn this cli patch into a dummy. */ pPatch->uState = PATCH_REFUSED; pPatch->pPatchBlockOffset = 0; /* Give back the patch memory we no longer need */ Assert(orgOffsetPatchMem != (uint32_t)~0); pVM->patm.s.offPatchMem = orgOffsetPatchMem; return rc; } #ifdef LOG_ENABLED /** * Check if the instruction is patched as a common idt handler * * @returns true or false * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to the instruction * */ static bool patmIsCommonIDTHandlerPatch(PVM pVM, RTRCPTR pInstrGC) { PPATMPATCHREC pRec; pRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); if (pRec && pRec->patch.flags & PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT) return true; return false; } #endif //DEBUG /** * Duplicates a complete function * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pPatchRec Patch record * @param pCacheRec Cache record ptr * */ static int patmDuplicateFunction(PVM pVM, RTRCPTR pInstrGC, PPATMPATCHREC pPatchRec, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = &pPatchRec->patch; int rc = VERR_PATCHING_REFUSED; uint32_t orgOffsetPatchMem = ~0; bool fInserted; Log(("patmDuplicateFunction %RRv\n", pInstrGC)); /* Save original offset (in case of failures later on). */ orgOffsetPatchMem = pVM->patm.s.offPatchMem; /* We will not go on indefinitely with call instruction handling. */ if (pVM->patm.s.ulCallDepth > PATM_MAX_CALL_DEPTH) { Log(("patmDuplicateFunction: maximum callback depth reached!!\n")); return VERR_PATCHING_REFUSED; } pVM->patm.s.ulCallDepth++; #ifdef PATM_ENABLE_CALL pPatch->flags |= PATMFL_SUPPORT_CALLS | PATMFL_SUPPORT_INDIRECT_CALLS; #endif Assert(pPatch->flags & (PATMFL_DUPLICATE_FUNCTION)); pPatch->nrPatch2GuestRecs = 0; pPatch->pPatchBlockOffset = pVM->patm.s.offPatchMem; pPatch->uCurPatchOffset = 0; /* Note: Set the PATM interrupt flag here; it was cleared before the patched call. (!!!) */ rc = patmPatchGenSetPIF(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; #ifdef VBOX_WITH_STATISTICS rc = patmPatchGenStats(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; #endif rc = patmRecompileCodeStream(pVM, pInstrGC, pInstrGC, patmRecompileCallback, pCacheRec); if (rc != VINF_SUCCESS) { Log(("PATMR3PatchCli: patmRecompileCodeStream failed with %d\n", rc)); goto failure; } //size of patch block pPatch->cbPatchBlockSize = pPatch->uCurPatchOffset; //update free pointer in patch memory pVM->patm.s.offPatchMem += pPatch->cbPatchBlockSize; /* Round to next 8 byte boundary. */ pVM->patm.s.offPatchMem = RT_ALIGN_32(pVM->patm.s.offPatchMem, 8); pPatch->uState = PATCH_ENABLED; /* * Insert into patch to guest lookup tree */ LogFlow(("Insert %RRv patch offset %RRv\n", pPatchRec->patch.pPrivInstrGC, pPatch->pPatchBlockOffset)); pPatchRec->CoreOffset.Key = pPatch->pPatchBlockOffset; fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, &pPatchRec->CoreOffset); AssertMsg(fInserted, ("RTAvloU32Insert failed for %x\n", pPatchRec->CoreOffset.Key)); if (!fInserted) { rc = VERR_PATCHING_REFUSED; goto failure; } /* Note that patmr3SetBranchTargets can install additional patches!! */ rc = patmr3SetBranchTargets(pVM, pPatch); if (rc != VINF_SUCCESS) { Log(("PATMR3PatchCli: patmr3SetBranchTargets failed with %d\n", rc)); goto failure; } patmR3DbgAddPatch(pVM, pPatchRec); #ifdef LOG_ENABLED Log(("Patch code ----------------------------------------------------------\n")); patmr3DisasmCodeStream(pVM, PATCHCODE_PTR_GC(pPatch), PATCHCODE_PTR_GC(pPatch), patmr3DisasmCallback, pCacheRec); Log(("Patch code ends -----------------------------------------------------\n")); #endif Log(("Successfully installed function duplication patch at %RRv\n", pInstrGC)); patmEmptyTree(pVM, &pPatch->pTempInfo->IllegalInstrTree); pPatch->pTempInfo->nrIllegalInstr = 0; pVM->patm.s.ulCallDepth--; STAM_COUNTER_INC(&pVM->patm.s.StatInstalledFunctionPatches); return VINF_SUCCESS; failure: if (pPatchRec->CoreOffset.Key) RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, pPatchRec->CoreOffset.Key); patmEmptyTree(pVM, &pPatch->FixupTree); pPatch->nrFixups = 0; patmEmptyTree(pVM, &pPatch->JumpTree); pPatch->nrJumpRecs = 0; patmEmptyTree(pVM, &pPatch->pTempInfo->IllegalInstrTree); pPatch->pTempInfo->nrIllegalInstr = 0; /* Turn this cli patch into a dummy. */ pPatch->uState = PATCH_REFUSED; pPatch->pPatchBlockOffset = 0; // Give back the patch memory we no longer need Assert(orgOffsetPatchMem != (uint32_t)~0); pVM->patm.s.offPatchMem = orgOffsetPatchMem; pVM->patm.s.ulCallDepth--; Log(("patmDupicateFunction %RRv failed!!\n", pInstrGC)); return rc; } /** * Creates trampoline code to jump inside an existing patch * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pPatchRec Patch record * */ static int patmCreateTrampoline(PVM pVM, RTRCPTR pInstrGC, PPATMPATCHREC pPatchRec) { PPATCHINFO pPatch = &pPatchRec->patch; RTRCPTR pPage, pPatchTargetGC = 0; uint32_t orgOffsetPatchMem = ~0; int rc = VERR_PATCHING_REFUSED; PPATCHINFO pPatchToJmp = NULL; /**< Patch the trampoline jumps to. */ PTRAMPREC pTrampRec = NULL; /**< Trampoline record used to find the patch. */ bool fInserted = false; Log(("patmCreateTrampoline %RRv\n", pInstrGC)); /* Save original offset (in case of failures later on). */ orgOffsetPatchMem = pVM->patm.s.offPatchMem; /* First we check if the duplicate function target lies in some existing function patch already. Will save some space. */ /** @todo we already checked this before */ pPage = pInstrGC & PAGE_BASE_GC_MASK; PPATMPATCHPAGE pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, (RTRCPTR)pPage); if (pPatchPage) { uint32_t i; for (i=0;icCount;i++) { if (pPatchPage->papPatch[i]) { pPatchToJmp = pPatchPage->papPatch[i]; if ( (pPatchToJmp->flags & PATMFL_DUPLICATE_FUNCTION) && pPatchToJmp->uState == PATCH_ENABLED) { pPatchTargetGC = patmGuestGCPtrToPatchGCPtr(pVM, pPatchToJmp, pInstrGC); if (pPatchTargetGC) { uint32_t offsetPatch = pPatchTargetGC - pVM->patm.s.pPatchMemGC; PRECPATCHTOGUEST pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32GetBestFit(&pPatchToJmp->Patch2GuestAddrTree, offsetPatch, false); Assert(pPatchToGuestRec); pPatchToGuestRec->fJumpTarget = true; Assert(pPatchTargetGC != pPatchToJmp->pPrivInstrGC); Log(("patmCreateTrampoline: generating jump to code inside patch at %RRv (patch target %RRv)\n", pPatchToJmp->pPrivInstrGC, pPatchTargetGC)); break; } } } } } AssertReturn(pPatchPage && pPatchTargetGC && pPatchToJmp, VERR_PATCHING_REFUSED); /* * Only record the trampoline patch if this is the first patch to the target * or we recorded other patches already. * The goal is to refuse refreshing function duplicates if the guest * modifies code after a saved state was loaded because it is not possible * to save the relation between trampoline and target without changing the * saved satte version. */ if ( !(pPatchToJmp->flags & PATMFL_EXTERNAL_JUMP_INSIDE) || pPatchToJmp->pTrampolinePatchesHead) { pPatchToJmp->flags |= PATMFL_EXTERNAL_JUMP_INSIDE; pTrampRec = (PTRAMPREC)MMR3HeapAllocZ(pVM, MM_TAG_PATM_PATCH, sizeof(*pTrampRec)); if (!pTrampRec) return VERR_NO_MEMORY; /* or better return VERR_PATCHING_REFUSED to let the VM continue? */ pTrampRec->pPatchTrampoline = pPatchRec; } pPatch->nrPatch2GuestRecs = 0; pPatch->pPatchBlockOffset = pVM->patm.s.offPatchMem; pPatch->uCurPatchOffset = 0; /* Note: Set the PATM interrupt flag here; it was cleared before the patched call. (!!!) */ rc = patmPatchGenSetPIF(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; #ifdef VBOX_WITH_STATISTICS rc = patmPatchGenStats(pVM, pPatch, pInstrGC); if (RT_FAILURE(rc)) goto failure; #endif rc = patmPatchGenPatchJump(pVM, pPatch, pInstrGC, pPatchTargetGC); if (RT_FAILURE(rc)) goto failure; /* * Insert into patch to guest lookup tree */ LogFlow(("Insert %RRv patch offset %RRv\n", pPatchRec->patch.pPrivInstrGC, pPatch->pPatchBlockOffset)); pPatchRec->CoreOffset.Key = pPatch->pPatchBlockOffset; fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, &pPatchRec->CoreOffset); AssertMsg(fInserted, ("RTAvloU32Insert failed for %x\n", pPatchRec->CoreOffset.Key)); if (!fInserted) { rc = VERR_PATCHING_REFUSED; goto failure; } patmR3DbgAddPatch(pVM, pPatchRec); /* size of patch block */ pPatch->cbPatchBlockSize = pPatch->uCurPatchOffset; /* Update free pointer in patch memory. */ pVM->patm.s.offPatchMem += pPatch->cbPatchBlockSize; /* Round to next 8 byte boundary */ pVM->patm.s.offPatchMem = RT_ALIGN_32(pVM->patm.s.offPatchMem, 8); /* There's no jump from guest to patch code. */ pPatch->cbPatchJump = 0; /* Enable the patch. */ pPatch->uState = PATCH_ENABLED; /* We allow this patch to be called as a function. */ pPatch->flags |= PATMFL_CALLABLE_AS_FUNCTION; if (pTrampRec) { pTrampRec->pNext = pPatchToJmp->pTrampolinePatchesHead; pPatchToJmp->pTrampolinePatchesHead = pTrampRec; } STAM_COUNTER_INC(&pVM->patm.s.StatInstalledTrampoline); return VINF_SUCCESS; failure: if (pPatchRec->CoreOffset.Key) RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, pPatchRec->CoreOffset.Key); patmEmptyTree(pVM, &pPatch->FixupTree); pPatch->nrFixups = 0; patmEmptyTree(pVM, &pPatch->JumpTree); pPatch->nrJumpRecs = 0; patmEmptyTree(pVM, &pPatch->pTempInfo->IllegalInstrTree); pPatch->pTempInfo->nrIllegalInstr = 0; /* Turn this cli patch into a dummy. */ pPatch->uState = PATCH_REFUSED; pPatch->pPatchBlockOffset = 0; // Give back the patch memory we no longer need Assert(orgOffsetPatchMem != (uint32_t)~0); pVM->patm.s.offPatchMem = orgOffsetPatchMem; if (pTrampRec) MMR3HeapFree(pTrampRec); return rc; } /** * Patch branch target function for call/jump at specified location. * (in responds to a VINF_PATM_DUPLICATE_FUNCTION GC exit reason) * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCtx Pointer to the guest CPU context. * */ VMMR3_INT_DECL(int) PATMR3DuplicateFunctionRequest(PVM pVM, PCPUMCTX pCtx) { RTRCPTR pBranchTarget, pPage; int rc; RTRCPTR pPatchTargetGC = 0; AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); pBranchTarget = pCtx->edx; pBranchTarget = SELMToFlat(pVM, DISSELREG_CS, CPUMCTX2CORE(pCtx), pBranchTarget); /* First we check if the duplicate function target lies in some existing function patch already. Will save some space. */ pPage = pBranchTarget & PAGE_BASE_GC_MASK; PPATMPATCHPAGE pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, (RTRCPTR)pPage); if (pPatchPage) { uint32_t i; for (i=0;icCount;i++) { if (pPatchPage->papPatch[i]) { PPATCHINFO pPatch = pPatchPage->papPatch[i]; if ( (pPatch->flags & PATMFL_DUPLICATE_FUNCTION) && pPatch->uState == PATCH_ENABLED) { pPatchTargetGC = patmGuestGCPtrToPatchGCPtr(pVM, pPatch, pBranchTarget); if (pPatchTargetGC) { STAM_COUNTER_INC(&pVM->patm.s.StatDuplicateUseExisting); break; } } } } } if (pPatchTargetGC) { /* Create a trampoline that also sets PATM_INTERRUPTFLAG. */ rc = PATMR3InstallPatch(pVM, pBranchTarget, PATMFL_CODE32 | PATMFL_TRAMPOLINE); } else { rc = PATMR3InstallPatch(pVM, pBranchTarget, PATMFL_CODE32 | PATMFL_DUPLICATE_FUNCTION); } if (rc == VINF_SUCCESS) { pPatchTargetGC = PATMR3QueryPatchGCPtr(pVM, pBranchTarget); Assert(pPatchTargetGC); } if (pPatchTargetGC) { pCtx->eax = pPatchTargetGC; pCtx->eax = pCtx->eax - (RTRCUINTPTR)pVM->patm.s.pPatchMemGC; /* make it relative */ } else { /* We add a dummy entry into the lookup cache so we won't get bombarded with the same requests over and over again. */ pCtx->eax = 0; STAM_COUNTER_INC(&pVM->patm.s.StatDuplicateREQFailed); } Assert(PATMIsPatchGCAddr(pVM, pCtx->edi)); rc = patmAddBranchToLookupCache(pVM, pCtx->edi, pBranchTarget, pCtx->eax); AssertRC(rc); pCtx->eip += PATM_ILLEGAL_INSTR_SIZE; STAM_COUNTER_INC(&pVM->patm.s.StatDuplicateREQSuccess); return VINF_SUCCESS; } /** * Replaces a function call by a call to an existing function duplicate (or jmp -> jmp) * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCpu Disassembly CPU structure ptr * @param pInstrGC Guest context point to privileged instruction * @param pCacheRec Cache record ptr * */ static int patmReplaceFunctionCall(PVM pVM, DISCPUSTATE *pCpu, RTRCPTR pInstrGC, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; int rc = VERR_PATCHING_REFUSED; DISCPUSTATE cpu; RTRCPTR pTargetGC; PPATMPATCHREC pPatchFunction; uint32_t cbInstr; bool disret; Assert(pPatch->flags & PATMFL_REPLACE_FUNCTION_CALL); Assert((pCpu->pCurInstr->uOpcode == OP_CALL || pCpu->pCurInstr->uOpcode == OP_JMP) && pCpu->cbInstr == SIZEOF_NEARJUMP32); if ((pCpu->pCurInstr->uOpcode != OP_CALL && pCpu->pCurInstr->uOpcode != OP_JMP) || pCpu->cbInstr != SIZEOF_NEARJUMP32) { rc = VERR_PATCHING_REFUSED; goto failure; } pTargetGC = PATMResolveBranch(pCpu, pInstrGC); if (pTargetGC == 0) { Log(("We don't support far jumps here!! (%08X)\n", pCpu->Param1.fUse)); rc = VERR_PATCHING_REFUSED; goto failure; } pPatchFunction = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pTargetGC); if (pPatchFunction == NULL) { for(;;) { /* It could be an indirect call (call -> jmp dest). * Note that it's dangerous to assume the jump will never change... */ uint8_t *pTmpInstrHC; pTmpInstrHC = patmR3GCVirtToHCVirt(pVM, pCacheRec, pTargetGC); Assert(pTmpInstrHC); if (pTmpInstrHC == 0) break; disret = patmR3DisInstr(pVM, pPatch, pTargetGC, pTmpInstrHC, PATMREAD_ORGCODE, &cpu, &cbInstr); if (disret == false || cpu.pCurInstr->uOpcode != OP_JMP) break; pTargetGC = PATMResolveBranch(&cpu, pTargetGC); if (pTargetGC == 0) { break; } pPatchFunction = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pTargetGC); break; } if (pPatchFunction == 0) { AssertMsgFailed(("Unable to find duplicate function %RRv\n", pTargetGC)); rc = VERR_PATCHING_REFUSED; goto failure; } } // make a copy of the guest code bytes that will be overwritten pPatch->cbPatchJump = SIZEOF_NEARJUMP32; rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), pPatch->aPrivInstr, pPatch->pPrivInstrGC, pPatch->cbPatchJump); AssertRC(rc); /* Now replace the original call in the guest code */ rc = patmGenCallToPatch(pVM, pPatch, PATCHCODE_PTR_GC(&pPatchFunction->patch), pCacheRec, true); AssertRC(rc); if (RT_FAILURE(rc)) goto failure; /* Lowest and highest address for write monitoring. */ pPatch->pInstrGCLowest = pInstrGC; pPatch->pInstrGCHighest = pInstrGC + pCpu->cbInstr; PATM_LOG_ORG_PATCH_INSTR(pVM, pPatch, "Call"); Log(("Successfully installed function replacement patch at %RRv\n", pInstrGC)); pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; failure: /* Turn this patch into a dummy. */ pPatch->uState = PATCH_REFUSED; return rc; } /** * Replace the address in an MMIO instruction with the cached version. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pCpu Disassembly CPU structure ptr * @param pCacheRec Cache record ptr * * @note returns failure if patching is not allowed or possible * */ static int patmPatchMMIOInstr(PVM pVM, RTRCPTR pInstrGC, DISCPUSTATE *pCpu, PPATMP2GLOOKUPREC pCacheRec) { PPATCHINFO pPatch = (PPATCHINFO)pCacheRec->pPatch; uint8_t *pPB; int rc = VERR_PATCHING_REFUSED; Assert(pVM->patm.s.mmio.pCachedData); if (!pVM->patm.s.mmio.pCachedData) goto failure; if (pCpu->Param2.fUse != DISUSE_DISPLACEMENT32) goto failure; pPB = patmR3GCVirtToHCVirt(pVM, pCacheRec, pPatch->pPrivInstrGC); if (pPB == 0) goto failure; /* Add relocation record for cached data access. */ if (patmPatchAddReloc32(pVM, pPatch, &pPB[pCpu->cbInstr - sizeof(RTRCPTR)], FIXUP_ABSOLUTE, pPatch->pPrivInstrGC, pVM->patm.s.mmio.pCachedData) != VINF_SUCCESS) { Log(("Relocation failed for cached mmio address!!\n")); return VERR_PATCHING_REFUSED; } PATM_LOG_PATCH_INSTR(pVM, pPatch, PATMREAD_ORGCODE, "MMIO patch old instruction:", ""); /* Save original instruction. */ rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), pPatch->aPrivInstr, pPatch->pPrivInstrGC, pPatch->cbPrivInstr); AssertRC(rc); pPatch->cbPatchJump = pPatch->cbPrivInstr; /* bit of a misnomer in this case; size of replacement instruction. */ /* Replace address with that of the cached item. */ rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pInstrGC + pCpu->cbInstr - sizeof(RTRCPTR), &pVM->patm.s.mmio.pCachedData, sizeof(RTRCPTR)); AssertRC(rc); if (RT_FAILURE(rc)) { goto failure; } PATM_LOG_ORG_PATCH_INSTR(pVM, pPatch, "MMIO"); pVM->patm.s.mmio.pCachedData = 0; pVM->patm.s.mmio.GCPhys = 0; pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; failure: /* Turn this patch into a dummy. */ pPatch->uState = PATCH_REFUSED; return rc; } /** * Replace the address in an MMIO instruction with the cached version. (instruction is part of an existing patch) * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pPatch Patch record * * @note returns failure if patching is not allowed or possible * */ static int patmPatchPATMMMIOInstr(PVM pVM, RTRCPTR pInstrGC, PPATCHINFO pPatch) { DISCPUSTATE cpu; uint32_t cbInstr; bool disret; uint8_t *pInstrHC; AssertReturn(pVM->patm.s.mmio.pCachedData, VERR_INVALID_PARAMETER); /* Convert GC to HC address. */ pInstrHC = patmPatchGCPtr2PatchHCPtr(pVM, pInstrGC); AssertReturn(pInstrHC, VERR_PATCHING_REFUSED); /* Disassemble mmio instruction. */ disret = patmR3DisInstrNoStrOpMode(pVM, pPatch, pInstrGC, pInstrHC, PATMREAD_ORGCODE, &cpu, &cbInstr); if (disret == false) { Log(("Disassembly failed (probably page not present) -> return to caller\n")); return VERR_PATCHING_REFUSED; } AssertMsg(cbInstr <= MAX_INSTR_SIZE, ("privileged instruction too big %d!!\n", cbInstr)); if (cbInstr > MAX_INSTR_SIZE) return VERR_PATCHING_REFUSED; if (cpu.Param2.fUse != DISUSE_DISPLACEMENT32) return VERR_PATCHING_REFUSED; /* Add relocation record for cached data access. */ if (patmPatchAddReloc32(pVM, pPatch, &pInstrHC[cpu.cbInstr - sizeof(RTRCPTR)], FIXUP_ABSOLUTE) != VINF_SUCCESS) { Log(("Relocation failed for cached mmio address!!\n")); return VERR_PATCHING_REFUSED; } /* Replace address with that of the cached item. */ *(RTRCPTR *)&pInstrHC[cpu.cbInstr - sizeof(RTRCPTR)] = pVM->patm.s.mmio.pCachedData; /* Lowest and highest address for write monitoring. */ pPatch->pInstrGCLowest = pInstrGC; pPatch->pInstrGCHighest = pInstrGC + cpu.cbInstr; PATM_LOG_ORG_PATCH_INSTR(pVM, pPatch, "MMIO"); pVM->patm.s.mmio.pCachedData = 0; pVM->patm.s.mmio.GCPhys = 0; return VINF_SUCCESS; } /** * Activates an int3 patch * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record */ static int patmActivateInt3Patch(PVM pVM, PPATCHINFO pPatch) { uint8_t bASMInt3 = 0xCC; int rc; Assert(pPatch->flags & (PATMFL_INT3_REPLACEMENT|PATMFL_INT3_REPLACEMENT_BLOCK)); Assert(pPatch->uState != PATCH_ENABLED); /* Replace first opcode byte with 'int 3'. */ rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pPatch->pPrivInstrGC, &bASMInt3, sizeof(bASMInt3)); AssertRC(rc); pPatch->cbPatchJump = sizeof(bASMInt3); return rc; } /** * Deactivates an int3 patch * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record */ static int patmDeactivateInt3Patch(PVM pVM, PPATCHINFO pPatch) { uint8_t ASMInt3 = 0xCC; int rc; Assert(pPatch->flags & (PATMFL_INT3_REPLACEMENT|PATMFL_INT3_REPLACEMENT_BLOCK)); Assert(pPatch->uState == PATCH_ENABLED || pPatch->uState == PATCH_DIRTY); /* Restore first opcode byte. */ rc = PGMPhysSimpleDirtyWriteGCPtr(VMMGetCpu0(pVM), pPatch->pPrivInstrGC, pPatch->aPrivInstr, sizeof(ASMInt3)); AssertRC(rc); return rc; } /** * Replace an instruction with a breakpoint (0xCC), that is handled dynamically * in the raw-mode context. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pInstrHC Host context point to privileged instruction * @param pCpu Disassembly CPU structure ptr * @param pPatch Patch record * * @note returns failure if patching is not allowed or possible * */ int patmR3PatchInstrInt3(PVM pVM, RTRCPTR pInstrGC, R3PTRTYPE(uint8_t *) pInstrHC, DISCPUSTATE *pCpu, PPATCHINFO pPatch) { uint8_t bASMInt3 = 0xCC; int rc; /* Note: Do not use patch memory here! It might called during patch installation too. */ PATM_LOG_PATCH_INSTR(pVM, pPatch, PATMREAD_ORGCODE, "patmR3PatchInstrInt3:", ""); /* Save the original instruction. */ rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), pPatch->aPrivInstr, pPatch->pPrivInstrGC, pPatch->cbPrivInstr); AssertRC(rc); pPatch->cbPatchJump = sizeof(bASMInt3); /* bit of a misnomer in this case; size of replacement instruction. */ pPatch->flags |= PATMFL_INT3_REPLACEMENT; /* Replace first opcode byte with 'int 3'. */ rc = patmActivateInt3Patch(pVM, pPatch); if (RT_FAILURE(rc)) goto failure; /* Lowest and highest address for write monitoring. */ pPatch->pInstrGCLowest = pInstrGC; pPatch->pInstrGCHighest = pInstrGC + pCpu->cbInstr; pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; failure: /* Turn this patch into a dummy. */ return VERR_PATCHING_REFUSED; } #ifdef PATM_RESOLVE_CONFLICTS_WITH_JUMP_PATCHES /** * Patch a jump instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC Guest context point to privileged instruction * @param pInstrHC Host context point to privileged instruction * @param pCpu Disassembly CPU structure ptr * @param pPatchRec Patch record * * @note returns failure if patching is not allowed or possible * */ int patmPatchJump(PVM pVM, RTRCPTR pInstrGC, R3PTRTYPE(uint8_t *) pInstrHC, DISCPUSTATE *pCpu, PPATMPATCHREC pPatchRec) { PPATCHINFO pPatch = &pPatchRec->patch; int rc = VERR_PATCHING_REFUSED; pPatch->pPatchBlockOffset = 0; /* doesn't use patch memory */ pPatch->uCurPatchOffset = 0; pPatch->cbPatchBlockSize = 0; pPatch->flags |= PATMFL_SINGLE_INSTRUCTION; /* * Instruction replacements such as these should never be interrupted. I've added code to EM.cpp to * make sure this never happens. (unless a trap is triggered (intentionally or not)) */ switch (pCpu->pCurInstr->uOpcode) { case OP_JO: case OP_JNO: case OP_JC: case OP_JNC: case OP_JE: case OP_JNE: case OP_JBE: case OP_JNBE: case OP_JS: case OP_JNS: case OP_JP: case OP_JNP: case OP_JL: case OP_JNL: case OP_JLE: case OP_JNLE: case OP_JMP: Assert(pPatch->flags & PATMFL_JUMP_CONFLICT); Assert(pCpu->Param1.fUse & DISUSE_IMMEDIATE32_REL); if (!(pCpu->Param1.fUse & DISUSE_IMMEDIATE32_REL)) goto failure; Assert(pCpu->cbInstr == SIZEOF_NEARJUMP32 || pCpu->cbInstr == SIZEOF_NEAR_COND_JUMP32); if (pCpu->cbInstr != SIZEOF_NEARJUMP32 && pCpu->cbInstr != SIZEOF_NEAR_COND_JUMP32) goto failure; if (PAGE_ADDRESS(pInstrGC) != PAGE_ADDRESS(pInstrGC + pCpu->cbInstr)) { STAM_COUNTER_INC(&pVM->patm.s.StatPageBoundaryCrossed); AssertMsgFailed(("Patch jump would cross page boundary -> refuse!!\n")); rc = VERR_PATCHING_REFUSED; goto failure; } break; default: goto failure; } // make a copy of the guest code bytes that will be overwritten Assert(pCpu->cbInstr <= sizeof(pPatch->aPrivInstr)); Assert(pCpu->cbInstr >= SIZEOF_NEARJUMP32); pPatch->cbPatchJump = pCpu->cbInstr; rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), pPatch->aPrivInstr, pPatch->pPrivInstrGC, pPatch->cbPatchJump); AssertRC(rc); /* Now insert a jump in the guest code. */ /* * A conflict jump patch needs to be treated differently; we'll just replace the relative jump address with one that * references the target instruction in the conflict patch. */ RTRCPTR pJmpDest = patmR3GuestGCPtrToPatchGCPtrSimple(pVM, pInstrGC + pCpu->cbInstr + (int32_t)pCpu->Param1.uValue); AssertMsg(pJmpDest, ("patmR3GuestGCPtrToPatchGCPtrSimple failed for %RRv\n", pInstrGC + pCpu->cbInstr + (int32_t)pCpu->Param1.uValue)); pPatch->pPatchJumpDestGC = pJmpDest; PATMP2GLOOKUPREC cacheRec; RT_ZERO(cacheRec); cacheRec.pPatch = pPatch; rc = patmGenJumpToPatch(pVM, pPatch, &cacherec, true); /* Free leftover lock if any. */ if (cacheRec.Lock.pvMap) { PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); cacheRec.Lock.pvMap = NULL; } AssertRC(rc); if (RT_FAILURE(rc)) goto failure; pPatch->flags |= PATMFL_MUST_INSTALL_PATCHJMP; PATM_LOG_ORG_PATCH_INSTR(pVM, pPatch, patmGetInstructionString(pPatch->opcode, pPatch->flags)); Log(("Successfully installed %s patch at %RRv\n", patmGetInstructionString(pPatch->opcode, pPatch->flags), pInstrGC)); STAM_COUNTER_INC(&pVM->patm.s.StatInstalledJump); /* Lowest and highest address for write monitoring. */ pPatch->pInstrGCLowest = pInstrGC; pPatch->pInstrGCHighest = pInstrGC + pPatch->cbPatchJump; pPatch->uState = PATCH_ENABLED; return VINF_SUCCESS; failure: /* Turn this cli patch into a dummy. */ pPatch->uState = PATCH_REFUSED; return rc; } #endif /* PATM_RESOLVE_CONFLICTS_WITH_JUMP_PATCHES */ /** * Gives hint to PATM about supervisor guest instructions * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstr Guest context point to privileged instruction * @param flags Patch flags */ VMMR3_INT_DECL(int) PATMR3AddHint(PVM pVM, RTRCPTR pInstrGC, uint32_t flags) { Assert(pInstrGC); Assert(flags == PATMFL_CODE32); Log(("PATMR3AddHint %RRv\n", pInstrGC)); return PATMR3InstallPatch(pVM, pInstrGC, PATMFL_CODE32 | PATMFL_INSTR_HINT); } /** * Patch privileged instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstr Guest context point to privileged instruction (0:32 flat address) * @param flags Patch flags * * @note returns failure if patching is not allowed or possible */ VMMR3_INT_DECL(int) PATMR3InstallPatch(PVM pVM, RTRCPTR pInstrGC, uint64_t flags) { DISCPUSTATE cpu; R3PTRTYPE(uint8_t *) pInstrHC; uint32_t cbInstr; PPATMPATCHREC pPatchRec; PCPUMCTX pCtx = 0; bool disret; int rc; PVMCPU pVCpu = VMMGetCpu0(pVM); LogFlow(("PATMR3InstallPatch: %08x (%#llx)\n", pInstrGC, flags)); AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); if ( !pVM || pInstrGC == 0 || (flags & ~(PATMFL_CODE32|PATMFL_IDTHANDLER|PATMFL_INTHANDLER|PATMFL_SYSENTER|PATMFL_TRAPHANDLER|PATMFL_DUPLICATE_FUNCTION|PATMFL_REPLACE_FUNCTION_CALL|PATMFL_GUEST_SPECIFIC|PATMFL_INT3_REPLACEMENT|PATMFL_TRAPHANDLER_WITH_ERRORCODE|PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT|PATMFL_MMIO_ACCESS|PATMFL_TRAMPOLINE|PATMFL_INSTR_HINT|PATMFL_JUMP_CONFLICT))) { AssertFailed(); return VERR_INVALID_PARAMETER; } if (PATMIsEnabled(pVM) == false) return VERR_PATCHING_REFUSED; /* Test for patch conflict only with patches that actually change guest code. */ if (!(flags & (PATMFL_GUEST_SPECIFIC|PATMFL_IDTHANDLER|PATMFL_INTHANDLER|PATMFL_TRAMPOLINE))) { PPATCHINFO pConflictPatch = patmFindActivePatchByEntrypoint(pVM, pInstrGC); AssertReleaseMsg(pConflictPatch == 0, ("Unable to patch overwritten instruction at %RRv (%RRv)\n", pInstrGC, pConflictPatch->pPrivInstrGC)); if (pConflictPatch != 0) return VERR_PATCHING_REFUSED; } if (!(flags & PATMFL_CODE32)) { /** @todo Only 32 bits code right now */ AssertMsgFailed(("PATMR3InstallPatch: We don't support 16 bits code at this moment!!\n")); return VERR_NOT_IMPLEMENTED; } /* We ran out of patch memory; don't bother anymore. */ if (pVM->patm.s.fOutOfMemory == true) return VERR_PATCHING_REFUSED; #if 1 /* DONT COMMIT ENABLED! */ /* Blacklisted NT4SP1 areas - debugging why we sometimes crash early on, */ if ( 0 //|| (pInstrGC - 0x80010000U) < 0x10000U // NT4SP1 HAL //|| (pInstrGC - 0x80010000U) < 0x5000U // NT4SP1 HAL //|| (pInstrGC - 0x80013000U) < 0x2000U // NT4SP1 HAL //|| (pInstrGC - 0x80014000U) < 0x1000U // NT4SP1 HAL //|| (pInstrGC - 0x80014000U) < 0x800U // NT4SP1 HAL //|| (pInstrGC - 0x80014400U) < 0x400U // NT4SP1 HAL //|| (pInstrGC - 0x80014400U) < 0x200U // NT4SP1 HAL //|| (pInstrGC - 0x80014400U) < 0x100U // NT4SP1 HAL //|| (pInstrGC - 0x80014500U) < 0x100U // NT4SP1 HAL - negative //|| (pInstrGC - 0x80014400U) < 0x80U // NT4SP1 HAL //|| (pInstrGC - 0x80014400U) < 0x80U // NT4SP1 HAL //|| (pInstrGC - 0x80014440U) < 0x40U // NT4SP1 HAL //|| (pInstrGC - 0x80014440U) < 0x20U // NT4SP1 HAL || pInstrGC == 0x80014447 /* KfLowerIrql */ || 0) { Log(("PATMR3InstallPatch: %08x is blacklisted\n", pInstrGC)); return VERR_PATCHING_REFUSED; } #endif /* Make sure the code selector is wide open; otherwise refuse. */ pCtx = CPUMQueryGuestCtxPtr(pVCpu); if (CPUMGetGuestCPL(pVCpu) == 0) { RTRCPTR pInstrGCFlat = SELMToFlat(pVM, DISSELREG_CS, CPUMCTX2CORE(pCtx), pInstrGC); if (pInstrGCFlat != pInstrGC) { Log(("PATMR3InstallPatch: code selector not wide open: %04x:%RRv != %RRv eflags=%08x\n", pCtx->cs.Sel, pInstrGCFlat, pInstrGC, pCtx->eflags.u32)); return VERR_PATCHING_REFUSED; } } /* Note: the OpenBSD specific check will break if we allow additional patches to be installed (int 3)) */ if (!(flags & PATMFL_GUEST_SPECIFIC)) { /* New code. Make sure CSAM has a go at it first. */ CSAMR3CheckCode(pVM, pInstrGC); } /* Note: obsolete */ if ( PATMIsPatchGCAddr(pVM, pInstrGC) && (flags & PATMFL_MMIO_ACCESS)) { RTRCUINTPTR offset; void *pvPatchCoreOffset; /* Find the patch record. */ offset = pInstrGC - pVM->patm.s.pPatchMemGC; pvPatchCoreOffset = RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, offset, false); if (pvPatchCoreOffset == NULL) { AssertMsgFailed(("PATMR3InstallPatch: patch not found at address %RRv!!\n", pInstrGC)); return VERR_PATCH_NOT_FOUND; //fatal error } pPatchRec = PATM_PATCHREC_FROM_COREOFFSET(pvPatchCoreOffset); return patmPatchPATMMMIOInstr(pVM, pInstrGC, &pPatchRec->patch); } AssertReturn(!PATMIsPatchGCAddr(pVM, pInstrGC), VERR_PATCHING_REFUSED); pPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); if (pPatchRec) { Assert(!(flags & PATMFL_TRAMPOLINE)); /* Hints about existing patches are ignored. */ if (flags & PATMFL_INSTR_HINT) return VERR_PATCHING_REFUSED; if (pPatchRec->patch.uState == PATCH_DISABLE_PENDING) { Log(("PATMR3InstallPatch: disable operation is pending for patch at %RRv\n", pPatchRec->patch.pPrivInstrGC)); PATMR3DisablePatch(pVM, pPatchRec->patch.pPrivInstrGC); Assert(pPatchRec->patch.uState == PATCH_DISABLED); } if (pPatchRec->patch.uState == PATCH_DISABLED) { /* A patch, for which we previously received a hint, will be enabled and turned into a normal patch. */ if (pPatchRec->patch.flags & PATMFL_INSTR_HINT) { Log(("Enabling HINTED patch %RRv\n", pInstrGC)); pPatchRec->patch.flags &= ~PATMFL_INSTR_HINT; } else Log(("Enabling patch %RRv again\n", pInstrGC)); /** @todo we shouldn't disable and enable patches too often (it's relatively cheap, but pointless if it always happens) */ rc = PATMR3EnablePatch(pVM, pInstrGC); if (RT_SUCCESS(rc)) return VWRN_PATCH_ENABLED; return rc; } if ( pPatchRec->patch.uState == PATCH_ENABLED || pPatchRec->patch.uState == PATCH_DIRTY) { /* * The patch might have been overwritten. */ STAM_COUNTER_INC(&pVM->patm.s.StatOverwritten); if (pPatchRec->patch.uState != PATCH_REFUSED && pPatchRec->patch.uState != PATCH_UNUSABLE) { /* Patch must have been overwritten; remove it and pretend nothing happened. */ Log(("Patch an existing patched instruction?!? (%RRv)\n", pInstrGC)); if (pPatchRec->patch.flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_IDTHANDLER|PATMFL_MMIO_ACCESS|PATMFL_INT3_REPLACEMENT|PATMFL_INT3_REPLACEMENT_BLOCK)) { if (flags & PATMFL_IDTHANDLER) pPatchRec->patch.flags |= (flags & (PATMFL_IDTHANDLER|PATMFL_TRAPHANDLER|PATMFL_INTHANDLER)); /* update the type */ return VERR_PATM_ALREADY_PATCHED; /* already done once */ } } rc = PATMR3RemovePatch(pVM, pInstrGC); if (RT_FAILURE(rc)) return VERR_PATCHING_REFUSED; } else { AssertMsg(pPatchRec->patch.uState == PATCH_REFUSED || pPatchRec->patch.uState == PATCH_UNUSABLE, ("Patch an existing patched instruction?!? (%RRv, state=%d)\n", pInstrGC, pPatchRec->patch.uState)); /* already tried it once! */ return VERR_PATCHING_REFUSED; } } RTGCPHYS GCPhys; rc = PGMGstGetPage(pVCpu, pInstrGC, NULL, &GCPhys); if (rc != VINF_SUCCESS) { Log(("PGMGstGetPage failed with %Rrc\n", rc)); return rc; } /* Disallow patching instructions inside ROM code; complete function duplication is allowed though. */ if ( !(flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_TRAMPOLINE)) && !PGMPhysIsGCPhysNormal(pVM, GCPhys)) { Log(("Code at %RGv (phys %RGp) is in a ROM, MMIO or invalid page - refused\n", pInstrGC, GCPhys)); return VERR_PATCHING_REFUSED; } /* Initialize cache record for guest address translations. */ bool fInserted; PATMP2GLOOKUPREC cacheRec; RT_ZERO(cacheRec); pInstrHC = patmR3GCVirtToHCVirt(pVM, &cacheRec, pInstrGC); AssertReturn(pInstrHC, VERR_PATCHING_REFUSED); /* Allocate patch record. */ rc = MMHyperAlloc(pVM, sizeof(PATMPATCHREC), 0, MM_TAG_PATM_PATCH, (void **)&pPatchRec); if (RT_FAILURE(rc)) { Log(("Out of memory!!!!\n")); return VERR_NO_MEMORY; } pPatchRec->Core.Key = pInstrGC; pPatchRec->patch.uState = PATCH_REFUSED; /* default value */ /* Insert patch record into the lookup tree. */ fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTree, &pPatchRec->Core); Assert(fInserted); pPatchRec->patch.pPrivInstrGC = pInstrGC; pPatchRec->patch.flags = flags; pPatchRec->patch.uOpMode = (flags & PATMFL_CODE32) ? DISCPUMODE_32BIT : DISCPUMODE_16BIT; pPatchRec->patch.pTrampolinePatchesHead = NULL; pPatchRec->patch.pInstrGCLowest = pInstrGC; pPatchRec->patch.pInstrGCHighest = pInstrGC; if (!(pPatchRec->patch.flags & (PATMFL_DUPLICATE_FUNCTION | PATMFL_IDTHANDLER | PATMFL_SYSENTER | PATMFL_TRAMPOLINE))) { /* * Close proximity to an unusable patch is a possible hint that this patch would turn out to be dangerous too! */ PPATMPATCHREC pPatchNear = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, (pInstrGC + SIZEOF_NEARJUMP32 - 1), false); if (pPatchNear) { if (pPatchNear->patch.uState == PATCH_UNUSABLE && pInstrGC < pPatchNear->patch.pPrivInstrGC && pInstrGC + SIZEOF_NEARJUMP32 > pPatchNear->patch.pPrivInstrGC) { Log(("Dangerous patch; would overwrite the unusable patch at %RRv\n", pPatchNear->patch.pPrivInstrGC)); pPatchRec->patch.uState = PATCH_UNUSABLE; /* * Leave the new patch active as it's marked unusable; to prevent us from checking it over and over again */ return VERR_PATCHING_REFUSED; } } } pPatchRec->patch.pTempInfo = (PPATCHINFOTEMP)MMR3HeapAllocZ(pVM, MM_TAG_PATM_PATCH, sizeof(PATCHINFOTEMP)); if (pPatchRec->patch.pTempInfo == 0) { Log(("Out of memory!!!!\n")); return VERR_NO_MEMORY; } disret = patmR3DisInstrNoStrOpMode(pVM, &pPatchRec->patch, pInstrGC, NULL, PATMREAD_ORGCODE, &cpu, &cbInstr); if (disret == false) { Log(("Disassembly failed (probably page not present) -> return to caller\n")); return VERR_PATCHING_REFUSED; } AssertMsg(cbInstr <= MAX_INSTR_SIZE, ("privileged instruction too big %d!!\n", cbInstr)); if (cbInstr > MAX_INSTR_SIZE) return VERR_PATCHING_REFUSED; pPatchRec->patch.cbPrivInstr = cbInstr; pPatchRec->patch.opcode = cpu.pCurInstr->uOpcode; /* Restricted hinting for now. */ Assert(!(flags & PATMFL_INSTR_HINT) || cpu.pCurInstr->uOpcode == OP_CLI); /* Initialize cache record patch pointer. */ cacheRec.pPatch = &pPatchRec->patch; /* Allocate statistics slot */ if (pVM->patm.s.uCurrentPatchIdx < PATM_STAT_MAX_COUNTERS) { pPatchRec->patch.uPatchIdx = pVM->patm.s.uCurrentPatchIdx++; } else { Log(("WARNING: Patch index wrap around!!\n")); pPatchRec->patch.uPatchIdx = PATM_STAT_INDEX_DUMMY; } if (pPatchRec->patch.flags & PATMFL_TRAPHANDLER) { rc = patmInstallTrapTrampoline(pVM, pInstrGC, pPatchRec, &cacheRec); } else if (pPatchRec->patch.flags & (PATMFL_DUPLICATE_FUNCTION )) { rc = patmDuplicateFunction(pVM, pInstrGC, pPatchRec, &cacheRec); } else if (pPatchRec->patch.flags & PATMFL_TRAMPOLINE) { rc = patmCreateTrampoline(pVM, pInstrGC, pPatchRec); } else if (pPatchRec->patch.flags & PATMFL_REPLACE_FUNCTION_CALL) { rc = patmReplaceFunctionCall(pVM, &cpu, pInstrGC, &cacheRec); } else if (pPatchRec->patch.flags & PATMFL_INT3_REPLACEMENT) { rc = patmR3PatchInstrInt3(pVM, pInstrGC, pInstrHC, &cpu, &pPatchRec->patch); } else if (pPatchRec->patch.flags & PATMFL_MMIO_ACCESS) { rc = patmPatchMMIOInstr(pVM, pInstrGC, &cpu, &cacheRec); } else if (pPatchRec->patch.flags & (PATMFL_IDTHANDLER|PATMFL_SYSENTER)) { if (pPatchRec->patch.flags & PATMFL_SYSENTER) pPatchRec->patch.flags |= PATMFL_IDTHANDLER; /* we treat a sysenter handler as an IDT handler */ rc = patmIdtHandler(pVM, pInstrGC, cbInstr, pPatchRec, &cacheRec); #ifdef VBOX_WITH_STATISTICS if ( rc == VINF_SUCCESS && (pPatchRec->patch.flags & PATMFL_SYSENTER)) { pVM->patm.s.uSysEnterPatchIdx = pPatchRec->patch.uPatchIdx; } #endif } else if (pPatchRec->patch.flags & PATMFL_GUEST_SPECIFIC) { switch (cpu.pCurInstr->uOpcode) { case OP_SYSENTER: case OP_PUSH: rc = patmR3InstallGuestSpecificPatch(pVM, &cpu, pInstrGC, pInstrHC, pPatchRec); if (rc == VINF_SUCCESS) { if (rc == VINF_SUCCESS) Log(("PATMR3InstallPatch GUEST: %s %RRv code32=%d\n", patmGetInstructionString(pPatchRec->patch.opcode, pPatchRec->patch.flags), pInstrGC, (flags & PATMFL_CODE32) ? 1 : 0)); return rc; } break; default: rc = VERR_NOT_IMPLEMENTED; break; } } else { switch (cpu.pCurInstr->uOpcode) { case OP_SYSENTER: rc = patmR3InstallGuestSpecificPatch(pVM, &cpu, pInstrGC, pInstrHC, pPatchRec); if (rc == VINF_SUCCESS) { Log(("PATMR3InstallPatch GUEST: %s %RRv code32=%d\n", patmGetInstructionString(pPatchRec->patch.opcode, pPatchRec->patch.flags), pInstrGC, (flags & PATMFL_CODE32) ? 1 : 0)); return VINF_SUCCESS; } break; #ifdef PATM_RESOLVE_CONFLICTS_WITH_JUMP_PATCHES case OP_JO: case OP_JNO: case OP_JC: case OP_JNC: case OP_JE: case OP_JNE: case OP_JBE: case OP_JNBE: case OP_JS: case OP_JNS: case OP_JP: case OP_JNP: case OP_JL: case OP_JNL: case OP_JLE: case OP_JNLE: case OP_JECXZ: case OP_LOOP: case OP_LOOPNE: case OP_LOOPE: case OP_JMP: if (pPatchRec->patch.flags & PATMFL_JUMP_CONFLICT) { rc = patmPatchJump(pVM, pInstrGC, pInstrHC, &cpu, pPatchRec); break; } return VERR_NOT_IMPLEMENTED; #endif case OP_PUSHF: case OP_CLI: Log(("PATMR3InstallPatch %s %RRv code32=%d\n", patmGetInstructionString(pPatchRec->patch.opcode, pPatchRec->patch.flags), pInstrGC, (flags & PATMFL_CODE32) ? 1 : 0)); rc = patmR3PatchBlock(pVM, pInstrGC, pInstrHC, cpu.pCurInstr->uOpcode, cbInstr, pPatchRec); break; #ifndef VBOX_WITH_SAFE_STR case OP_STR: #endif case OP_SGDT: case OP_SLDT: case OP_SIDT: case OP_CPUID: case OP_LSL: case OP_LAR: case OP_SMSW: case OP_VERW: case OP_VERR: case OP_IRET: #ifdef VBOX_WITH_RAW_RING1 case OP_MOV: #endif rc = patmR3PatchInstrInt3(pVM, pInstrGC, pInstrHC, &cpu, &pPatchRec->patch); break; default: return VERR_NOT_IMPLEMENTED; } } if (rc != VINF_SUCCESS) { if (pPatchRec && pPatchRec->patch.nrPatch2GuestRecs) { patmEmptyTreeU32(pVM, &pPatchRec->patch.Patch2GuestAddrTree); pPatchRec->patch.nrPatch2GuestRecs = 0; } pVM->patm.s.uCurrentPatchIdx--; } else { rc = patmInsertPatchPages(pVM, &pPatchRec->patch); AssertRCReturn(rc, rc); /* Keep track upper and lower boundaries of patched instructions */ if (pPatchRec->patch.pInstrGCLowest < pVM->patm.s.pPatchedInstrGCLowest) pVM->patm.s.pPatchedInstrGCLowest = pPatchRec->patch.pInstrGCLowest; if (pPatchRec->patch.pInstrGCHighest > pVM->patm.s.pPatchedInstrGCHighest) pVM->patm.s.pPatchedInstrGCHighest = pPatchRec->patch.pInstrGCHighest; Log(("Patch lowest %RRv highest %RRv\n", pPatchRec->patch.pInstrGCLowest, pPatchRec->patch.pInstrGCHighest)); Log(("Global lowest %RRv highest %RRv\n", pVM->patm.s.pPatchedInstrGCLowest, pVM->patm.s.pPatchedInstrGCHighest)); STAM_COUNTER_ADD(&pVM->patm.s.StatInstalled, 1); STAM_COUNTER_ADD(&pVM->patm.s.StatPATMMemoryUsed, pPatchRec->patch.cbPatchBlockSize); rc = VINF_SUCCESS; /* Patch hints are not enabled by default. Only when the are actually encountered. */ if (pPatchRec->patch.flags & PATMFL_INSTR_HINT) { rc = PATMR3DisablePatch(pVM, pInstrGC); AssertRCReturn(rc, rc); } #ifdef VBOX_WITH_STATISTICS /* Register statistics counter */ if (PATM_STAT_INDEX_IS_VALID(pPatchRec->patch.uPatchIdx)) { STAMR3RegisterCallback(pVM, &pPatchRec->patch, STAMVISIBILITY_NOT_GUI, STAMUNIT_GOOD_BAD, patmResetStat, patmPrintStat, "Patch statistics", "/PATM/Stats/Patch/0x%RRv", pPatchRec->patch.pPrivInstrGC); #ifndef DEBUG_sandervl /* Full breakdown for the GUI. */ STAMR3RegisterF(pVM, &pVM->patm.s.pStatsHC[pPatchRec->patch.uPatchIdx], STAMTYPE_RATIO_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_GOOD_BAD, PATMPatchType(pVM, &pPatchRec->patch), "/PATM/Stats/PatchBD/0x%RRv", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.cbPatchBlockSize,STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, NULL, "/PATM/Stats/PatchBD/0x%RRv/cbPatchBlockSize", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.cbPatchJump, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, NULL, "/PATM/Stats/PatchBD/0x%RRv/cbPatchJump", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.cbPrivInstr, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, NULL, "/PATM/Stats/PatchBD/0x%RRv/cbPrivInstr", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.cCodeWrites, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Stats/PatchBD/0x%RRv/cCodeWrites", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.cInvalidWrites, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Stats/PatchBD/0x%RRv/cInvalidWrites", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.cTraps, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Stats/PatchBD/0x%RRv/cTraps", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.flags, STAMTYPE_X32, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, NULL, "/PATM/Stats/PatchBD/0x%RRv/flags", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.nrJumpRecs, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Stats/PatchBD/0x%RRv/nrJumpRecs", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.nrFixups, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Stats/PatchBD/0x%RRv/nrFixups", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.opcode, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, NULL, "/PATM/Stats/PatchBD/0x%RRv/opcode", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.uOldState, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, NULL, "/PATM/Stats/PatchBD/0x%RRv/uOldState", pPatchRec->patch.pPrivInstrGC); STAMR3RegisterF(pVM, &pPatchRec->patch.uOpMode, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, NULL, "/PATM/Stats/PatchBD/0x%RRv/uOpMode", pPatchRec->patch.pPrivInstrGC); /// @todo change the state to be a callback so we can get a state mnemonic instead. STAMR3RegisterF(pVM, &pPatchRec->patch.uState, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_NONE, NULL, "/PATM/Stats/PatchBD/0x%RRv/uState", pPatchRec->patch.pPrivInstrGC); #endif } #endif /* Add debug symbol. */ patmR3DbgAddPatch(pVM, pPatchRec); } /* Free leftover lock if any. */ if (cacheRec.Lock.pvMap) PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); return rc; } /** * Query instruction size * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record * @param pInstrGC Instruction address */ static uint32_t patmGetInstrSize(PVM pVM, PPATCHINFO pPatch, RTRCPTR pInstrGC) { uint8_t *pInstrHC; PGMPAGEMAPLOCK Lock; int rc = PGMPhysGCPtr2CCPtrReadOnly(VMMGetCpu(pVM), pInstrGC, (const void **)&pInstrHC, &Lock); if (rc == VINF_SUCCESS) { DISCPUSTATE cpu; bool disret; uint32_t cbInstr; disret = patmR3DisInstr(pVM, pPatch, pInstrGC, pInstrHC, PATMREAD_ORGCODE | PATMREAD_NOCHECK, &cpu, &cbInstr); PGMPhysReleasePageMappingLock(pVM, &Lock); if (disret) return cbInstr; } return 0; } /** * Add patch to page record * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPage Page address * @param pPatch Patch record */ int patmAddPatchToPage(PVM pVM, RTRCUINTPTR pPage, PPATCHINFO pPatch) { PPATMPATCHPAGE pPatchPage; int rc; Log(("patmAddPatchToPage: insert patch %RHv to page %RRv\n", pPatch, pPage)); pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, pPage); if (pPatchPage) { Assert(pPatchPage->cCount <= pPatchPage->cMaxPatches); if (pPatchPage->cCount == pPatchPage->cMaxPatches) { uint32_t cMaxPatchesOld = pPatchPage->cMaxPatches; PPATCHINFO *papPatchOld = pPatchPage->papPatch; pPatchPage->cMaxPatches += PATMPATCHPAGE_PREALLOC_INCREMENT; rc = MMHyperAlloc(pVM, sizeof(pPatchPage->papPatch[0]) * pPatchPage->cMaxPatches, 0, MM_TAG_PATM_PATCH, (void **)&pPatchPage->papPatch); if (RT_FAILURE(rc)) { Log(("Out of memory!!!!\n")); return VERR_NO_MEMORY; } memcpy(pPatchPage->papPatch, papPatchOld, cMaxPatchesOld * sizeof(pPatchPage->papPatch[0])); MMHyperFree(pVM, papPatchOld); } pPatchPage->papPatch[pPatchPage->cCount] = pPatch; pPatchPage->cCount++; } else { bool fInserted; rc = MMHyperAlloc(pVM, sizeof(PATMPATCHPAGE), 0, MM_TAG_PATM_PATCH, (void **)&pPatchPage); if (RT_FAILURE(rc)) { Log(("Out of memory!!!!\n")); return VERR_NO_MEMORY; } pPatchPage->Core.Key = pPage; pPatchPage->cCount = 1; pPatchPage->cMaxPatches = PATMPATCHPAGE_PREALLOC_INCREMENT; rc = MMHyperAlloc(pVM, sizeof(pPatchPage->papPatch[0]) * PATMPATCHPAGE_PREALLOC_INCREMENT, 0, MM_TAG_PATM_PATCH, (void **)&pPatchPage->papPatch); if (RT_FAILURE(rc)) { Log(("Out of memory!!!!\n")); MMHyperFree(pVM, pPatchPage); return VERR_NO_MEMORY; } pPatchPage->papPatch[0] = pPatch; fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, &pPatchPage->Core); Assert(fInserted); pVM->patm.s.cPageRecords++; STAM_COUNTER_INC(&pVM->patm.s.StatPatchPageInserted); } CSAMR3MonitorPage(pVM, pPage, CSAM_TAG_PATM); /* Get the closest guest instruction (from below) */ PRECGUESTTOPATCH pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32GetBestFit(&pPatch->Guest2PatchAddrTree, pPage, true); Assert(pGuestToPatchRec); if (pGuestToPatchRec) { LogFlow(("patmAddPatchToPage: lowest patch page address %RRv current lowest %RRv\n", pGuestToPatchRec->Core.Key, pPatchPage->pLowestAddrGC)); if ( pPatchPage->pLowestAddrGC == 0 || pPatchPage->pLowestAddrGC > (RTRCPTR)pGuestToPatchRec->Core.Key) { RTRCUINTPTR offset; pPatchPage->pLowestAddrGC = (RTRCPTR)pGuestToPatchRec->Core.Key; offset = pPatchPage->pLowestAddrGC & PAGE_OFFSET_MASK; /* If we're too close to the page boundary, then make sure an instruction from the previous page doesn't cross the boundary itself. */ if (offset && offset < MAX_INSTR_SIZE) { /* Get the closest guest instruction (from above) */ pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32GetBestFit(&pPatch->Guest2PatchAddrTree, pPage-1, false); if (pGuestToPatchRec) { uint32_t size = patmGetInstrSize(pVM, pPatch, (RTRCPTR)pGuestToPatchRec->Core.Key); if ((RTRCUINTPTR)pGuestToPatchRec->Core.Key + size > pPage) { pPatchPage->pLowestAddrGC = pPage; LogFlow(("patmAddPatchToPage: new lowest %RRv\n", pPatchPage->pLowestAddrGC)); } } } } } /* Get the closest guest instruction (from above) */ pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32GetBestFit(&pPatch->Guest2PatchAddrTree, pPage+PAGE_SIZE-1, false); Assert(pGuestToPatchRec); if (pGuestToPatchRec) { LogFlow(("patmAddPatchToPage: highest patch page address %RRv current highest %RRv\n", pGuestToPatchRec->Core.Key, pPatchPage->pHighestAddrGC)); if ( pPatchPage->pHighestAddrGC == 0 || pPatchPage->pHighestAddrGC <= (RTRCPTR)pGuestToPatchRec->Core.Key) { pPatchPage->pHighestAddrGC = (RTRCPTR)pGuestToPatchRec->Core.Key; /* Increase by instruction size. */ uint32_t size = patmGetInstrSize(pVM, pPatch, pPatchPage->pHighestAddrGC); //// Assert(size); pPatchPage->pHighestAddrGC += size; LogFlow(("patmAddPatchToPage: new highest %RRv\n", pPatchPage->pHighestAddrGC)); } } return VINF_SUCCESS; } /** * Remove patch from page record * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPage Page address * @param pPatch Patch record */ int patmRemovePatchFromPage(PVM pVM, RTRCUINTPTR pPage, PPATCHINFO pPatch) { PPATMPATCHPAGE pPatchPage; int rc; pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, pPage); Assert(pPatchPage); if (!pPatchPage) return VERR_INVALID_PARAMETER; Assert(pPatchPage->cCount <= pPatchPage->cMaxPatches); Log(("patmRemovePatchPage: remove patch %RHv from page %RRv\n", pPatch, pPage)); if (pPatchPage->cCount > 1) { uint32_t i; /* Used by multiple patches */ for (i = 0; i < pPatchPage->cCount; i++) { if (pPatchPage->papPatch[i] == pPatch) { /* close the gap between the remaining pointers. */ uint32_t cNew = --pPatchPage->cCount; if (i < cNew) pPatchPage->papPatch[i] = pPatchPage->papPatch[cNew]; pPatchPage->papPatch[cNew] = NULL; return VINF_SUCCESS; } } AssertMsgFailed(("Unable to find patch %RHv in page %RRv\n", pPatch, pPage)); } else { PPATMPATCHPAGE pPatchNode; Log(("patmRemovePatchFromPage %RRv\n", pPage)); STAM_COUNTER_INC(&pVM->patm.s.StatPatchPageRemoved); pPatchNode = (PPATMPATCHPAGE)RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, pPage); Assert(pPatchNode && pPatchNode == pPatchPage); Assert(pPatchPage->papPatch); rc = MMHyperFree(pVM, pPatchPage->papPatch); AssertRC(rc); rc = MMHyperFree(pVM, pPatchPage); AssertRC(rc); pVM->patm.s.cPageRecords--; } return VINF_SUCCESS; } /** * Insert page records for all guest pages that contain instructions that were recompiled for this patch * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record */ int patmInsertPatchPages(PVM pVM, PPATCHINFO pPatch) { int rc; RTRCUINTPTR pPatchPageStart, pPatchPageEnd, pPage; /* Insert the pages that contain patched instructions into a lookup tree for detecting self-modifying code. */ pPatchPageStart = (RTRCUINTPTR)pPatch->pInstrGCLowest & PAGE_BASE_GC_MASK; pPatchPageEnd = (RTRCUINTPTR)pPatch->pInstrGCHighest & PAGE_BASE_GC_MASK; /** @todo optimize better (large gaps between current and next used page) */ for(pPage = pPatchPageStart; pPage <= pPatchPageEnd; pPage += PAGE_SIZE) { /* Get the closest guest instruction (from above) */ PRECGUESTTOPATCH pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32GetBestFit(&pPatch->Guest2PatchAddrTree, pPage, true); if ( pGuestToPatchRec && PAGE_ADDRESS(pGuestToPatchRec->Core.Key) == PAGE_ADDRESS(pPage) ) { /* Code in page really patched -> add record */ rc = patmAddPatchToPage(pVM, pPage, pPatch); AssertRC(rc); } } pPatch->flags |= PATMFL_CODE_MONITORED; return VINF_SUCCESS; } /** * Remove page records for all guest pages that contain instructions that were recompiled for this patch * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record */ static int patmRemovePatchPages(PVM pVM, PPATCHINFO pPatch) { int rc; RTRCUINTPTR pPatchPageStart, pPatchPageEnd, pPage; /* Insert the pages that contain patched instructions into a lookup tree for detecting self-modifying code. */ pPatchPageStart = (RTRCUINTPTR)pPatch->pInstrGCLowest & PAGE_BASE_GC_MASK; pPatchPageEnd = (RTRCUINTPTR)pPatch->pInstrGCHighest & PAGE_BASE_GC_MASK; for(pPage = pPatchPageStart; pPage <= pPatchPageEnd; pPage += PAGE_SIZE) { /* Get the closest guest instruction (from above) */ PRECGUESTTOPATCH pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32GetBestFit(&pPatch->Guest2PatchAddrTree, pPage, true); if ( pGuestToPatchRec && PAGE_ADDRESS(pGuestToPatchRec->Core.Key) == PAGE_ADDRESS(pPage) /** @todo bird: PAGE_ADDRESS is for the current context really. check out these. */ ) { /* Code in page really patched -> remove record */ rc = patmRemovePatchFromPage(pVM, pPage, pPatch); AssertRC(rc); } } pPatch->flags &= ~PATMFL_CODE_MONITORED; return VINF_SUCCESS; } /** * Notifies PATM about a (potential) write to code that has been patched. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param GCPtr GC pointer to write address * @param cbWrite Nr of bytes to write * */ VMMR3_INT_DECL(int) PATMR3PatchWrite(PVM pVM, RTRCPTR GCPtr, uint32_t cbWrite) { RTRCUINTPTR pWritePageStart, pWritePageEnd, pPage; Log(("PATMR3PatchWrite %RRv %x\n", GCPtr, cbWrite)); Assert(VM_IS_EMT(pVM)); AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); /* Quick boundary check */ if ( GCPtr < pVM->patm.s.pPatchedInstrGCLowest || GCPtr > pVM->patm.s.pPatchedInstrGCHighest ) return VINF_SUCCESS; STAM_PROFILE_ADV_START(&pVM->patm.s.StatPatchWrite, a); pWritePageStart = (RTRCUINTPTR)GCPtr & PAGE_BASE_GC_MASK; pWritePageEnd = ((RTRCUINTPTR)GCPtr + cbWrite - 1) & PAGE_BASE_GC_MASK; for (pPage = pWritePageStart; pPage <= pWritePageEnd; pPage += PAGE_SIZE) { loop_start: PPATMPATCHPAGE pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, (RTRCPTR)pPage); if (pPatchPage) { uint32_t i; bool fValidPatchWrite = false; /* Quick check to see if the write is in the patched part of the page */ if ( pPatchPage->pLowestAddrGC > (RTRCPTR)((RTRCUINTPTR)GCPtr + cbWrite - 1) || pPatchPage->pHighestAddrGC < GCPtr) { break; } for (i=0;icCount;i++) { if (pPatchPage->papPatch[i]) { PPATCHINFO pPatch = pPatchPage->papPatch[i]; RTRCPTR pPatchInstrGC; //unused: bool fForceBreak = false; Assert(pPatchPage->papPatch[i]->flags & PATMFL_CODE_MONITORED); /** @todo inefficient and includes redundant checks for multiple pages. */ for (uint32_t j=0; jcbPatchJump && pGuestPtrGC >= pPatch->pPrivInstrGC && pGuestPtrGC < pPatch->pPrivInstrGC + pPatch->cbPatchJump) { /* The guest is about to overwrite the 5 byte jump to patch code. Remove the patch. */ Log(("PATMR3PatchWrite: overwriting jump to patch code -> remove patch.\n")); int rc = PATMR3RemovePatch(pVM, pPatch->pPrivInstrGC); if (rc == VINF_SUCCESS) /* Note: jump back to the start as the pPatchPage has been deleted or changed */ goto loop_start; continue; } /* Find the closest instruction from below; the above quick check ensured that we are indeed in patched code */ pPatchInstrGC = patmGuestGCPtrToPatchGCPtr(pVM, pPatch, pGuestPtrGC); if (!pPatchInstrGC) { RTRCPTR pClosestInstrGC; uint32_t size; pPatchInstrGC = patmGuestGCPtrToClosestPatchGCPtr(pVM, pPatch, pGuestPtrGC); if (pPatchInstrGC) { pClosestInstrGC = patmPatchGCPtr2GuestGCPtr(pVM, pPatch, pPatchInstrGC); Assert(pClosestInstrGC <= pGuestPtrGC); size = patmGetInstrSize(pVM, pPatch, pClosestInstrGC); /* Check if this is not a write into a gap between two patches */ if (pClosestInstrGC + size - 1 < pGuestPtrGC) pPatchInstrGC = 0; } } if (pPatchInstrGC) { uint32_t PatchOffset = pPatchInstrGC - pVM->patm.s.pPatchMemGC; /* Offset in memory reserved for PATM. */ fValidPatchWrite = true; PRECPATCHTOGUEST pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32Get(&pPatch->Patch2GuestAddrTree, PatchOffset); Assert(pPatchToGuestRec); if (pPatchToGuestRec && !pPatchToGuestRec->fDirty) { Log(("PATMR3PatchWrite: Found patched instruction %RRv -> %RRv\n", pGuestPtrGC, pPatchInstrGC)); if (++pPatch->cCodeWrites > PATM_MAX_CODE_WRITES) { LogRel(("PATM: Disable block at %RRv - write %RRv-%RRv\n", pPatch->pPrivInstrGC, pGuestPtrGC, pGuestPtrGC+cbWrite)); patmR3MarkDirtyPatch(pVM, pPatch); /* Note: jump back to the start as the pPatchPage has been deleted or changed */ goto loop_start; } else { /* Replace the patch instruction with a breakpoint; when it's hit, then we'll attempt to recompile the instruction again. */ uint8_t *pInstrHC = patmPatchGCPtr2PatchHCPtr(pVM, pPatchInstrGC); pPatchToGuestRec->u8DirtyOpcode = *pInstrHC; pPatchToGuestRec->fDirty = true; *pInstrHC = 0xCC; STAM_COUNTER_INC(&pVM->patm.s.StatInstrDirty); } } /* else already marked dirty */ } } } } /* for each patch */ if (fValidPatchWrite == false) { /* Write to a part of the page that either: * - doesn't contain any code (shared code/data); rather unlikely * - old code page that's no longer in active use. */ invalid_write_loop_start: pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, (RTRCPTR)pPage); if (pPatchPage) { for (i=0;icCount;i++) { PPATCHINFO pPatch = pPatchPage->papPatch[i]; if (pPatch->cInvalidWrites > PATM_MAX_INVALID_WRITES) { /* Note: possibly dangerous assumption that all future writes will be harmless. */ if (pPatch->flags & PATMFL_IDTHANDLER) { LogRel(("PATM: Stop monitoring IDT handler pages at %RRv - invalid write %RRv-%RRv (this is not a fatal error)\n", pPatch->pPrivInstrGC, GCPtr, GCPtr+cbWrite)); Assert(pPatch->flags & PATMFL_CODE_MONITORED); int rc = patmRemovePatchPages(pVM, pPatch); AssertRC(rc); } else { LogRel(("PATM: Disable block at %RRv - invalid write %RRv-%RRv \n", pPatch->pPrivInstrGC, GCPtr, GCPtr+cbWrite)); patmR3MarkDirtyPatch(pVM, pPatch); } /* Note: jump back to the start as the pPatchPage has been deleted or changed */ goto invalid_write_loop_start; } } /* for */ } } } } STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatPatchWrite, a); return VINF_SUCCESS; } /** * Disable all patches in a flushed page * * @returns VBox status code * @param pVM Pointer to the VM. * @param addr GC address of the page to flush * @note Currently only called by CSAMR3FlushPage; optimization to avoid * having to double check if the physical address has changed */ VMMR3_INT_DECL(int) PATMR3FlushPage(PVM pVM, RTRCPTR addr) { AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); addr &= PAGE_BASE_GC_MASK; PPATMPATCHPAGE pPatchPage = (PPATMPATCHPAGE)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPage, addr); if (pPatchPage) { int i; /* From top to bottom as the array is modified by PATMR3MarkDirtyPatch. */ for (i=(int)pPatchPage->cCount-1;i>=0;i--) { if (pPatchPage->papPatch[i]) { PPATCHINFO pPatch = pPatchPage->papPatch[i]; Log(("PATMR3FlushPage %RRv remove patch at %RRv\n", addr, pPatch->pPrivInstrGC)); patmR3MarkDirtyPatch(pVM, pPatch); } } STAM_COUNTER_INC(&pVM->patm.s.StatFlushed); } return VINF_SUCCESS; } /** * Checks if the instructions at the specified address has been patched already. * * @returns boolean, patched or not * @param pVM Pointer to the VM. * @param pInstrGC Guest context pointer to instruction */ VMMR3_INT_DECL(bool) PATMR3HasBeenPatched(PVM pVM, RTRCPTR pInstrGC) { Assert(!HMIsEnabled(pVM)); PPATMPATCHREC pPatchRec; pPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); if (pPatchRec && pPatchRec->patch.uState == PATCH_ENABLED) return true; return false; } /** * Query the opcode of the original code that was overwritten by the 5 bytes patch jump * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstrGC GC address of instr * @param pByte opcode byte pointer (OUT) * */ VMMR3DECL(int) PATMR3QueryOpcode(PVM pVM, RTRCPTR pInstrGC, uint8_t *pByte) { PPATMPATCHREC pPatchRec; /** @todo this will not work for aliased pages! (never has, but so far not a problem for us) */ /* Shortcut. */ if (!PATMIsEnabled(pVM)) return VERR_PATCH_NOT_FOUND; Assert(!HMIsEnabled(pVM)); if ( pInstrGC < pVM->patm.s.pPatchedInstrGCLowest || pInstrGC > pVM->patm.s.pPatchedInstrGCHighest) return VERR_PATCH_NOT_FOUND; pPatchRec = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC, false); // if the patch is enabled and the pointer lies within 5 bytes of this priv instr ptr, then we've got a hit! if ( pPatchRec && pPatchRec->patch.uState == PATCH_ENABLED && pInstrGC >= pPatchRec->patch.pPrivInstrGC && pInstrGC < pPatchRec->patch.pPrivInstrGC + pPatchRec->patch.cbPatchJump) { RTRCPTR offset = pInstrGC - pPatchRec->patch.pPrivInstrGC; *pByte = pPatchRec->patch.aPrivInstr[offset]; if (pPatchRec->patch.cbPatchJump == 1) { Log(("PATMR3QueryOpcode: returning opcode %2X for instruction at %RRv\n", *pByte, pInstrGC)); } STAM_COUNTER_ADD(&pVM->patm.s.StatNrOpcodeRead, 1); return VINF_SUCCESS; } return VERR_PATCH_NOT_FOUND; } /** * Read instruction bytes of the original code that was overwritten by the 5 * bytes patch jump. * * @returns VINF_SUCCESS or VERR_PATCH_NOT_FOUND. * @param pVM Pointer to the VM. * @param GCPtrInstr GC address of instr * @param pbDst The output buffer. * @param cbToRead The maximum number bytes to read. * @param pcbRead Where to return the acutal number of bytes read. */ VMMR3_INT_DECL(int) PATMR3ReadOrgInstr(PVM pVM, RTGCPTR32 GCPtrInstr, uint8_t *pbDst, size_t cbToRead, size_t *pcbRead) { /* Shortcut. */ if (!PATMIsEnabled(pVM)) return VERR_PATCH_NOT_FOUND; Assert(!HMIsEnabled(pVM)); if ( GCPtrInstr < pVM->patm.s.pPatchedInstrGCLowest || GCPtrInstr > pVM->patm.s.pPatchedInstrGCHighest) return VERR_PATCH_NOT_FOUND; /** @todo this will not work for aliased pages! (never has, but so far not a problem for us) */ /* * If the patch is enabled and the pointer lies within 5 bytes of this * priv instr ptr, then we've got a hit! */ RTGCPTR32 off; PPATMPATCHREC pPatchRec = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, GCPtrInstr, false /*fAbove*/); if ( pPatchRec && pPatchRec->patch.uState == PATCH_ENABLED && (off = GCPtrInstr - pPatchRec->patch.pPrivInstrGC) < pPatchRec->patch.cbPatchJump) { uint8_t const *pbSrc = &pPatchRec->patch.aPrivInstr[off]; uint32_t const cbMax = pPatchRec->patch.cbPatchJump - off; if (cbToRead > cbMax) cbToRead = cbMax; switch (cbToRead) { case 5: pbDst[4] = pbSrc[4]; case 4: pbDst[3] = pbSrc[3]; case 3: pbDst[2] = pbSrc[2]; case 2: pbDst[1] = pbSrc[1]; case 1: pbDst[0] = pbSrc[0]; break; default: memcpy(pbDst, pbSrc, cbToRead); } *pcbRead = cbToRead; if (pPatchRec->patch.cbPatchJump == 1) Log(("PATMR3ReadOrgInstr: returning opcode %.*Rhxs for instruction at %RX32\n", cbToRead, pbSrc, GCPtrInstr)); STAM_COUNTER_ADD(&pVM->patm.s.StatNrOpcodeRead, 1); return VINF_SUCCESS; } return VERR_PATCH_NOT_FOUND; } /** * Disable patch for privileged instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstr Guest context point to privileged instruction * * @note returns failure if patching is not allowed or possible * */ VMMR3_INT_DECL(int) PATMR3DisablePatch(PVM pVM, RTRCPTR pInstrGC) { PPATMPATCHREC pPatchRec; PPATCHINFO pPatch; Log(("PATMR3DisablePatch: %RRv\n", pInstrGC)); AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); pPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); if (pPatchRec) { int rc = VINF_SUCCESS; pPatch = &pPatchRec->patch; /* Already disabled? */ if (pPatch->uState == PATCH_DISABLED) return VINF_SUCCESS; /* Clear the IDT entries for the patch we're disabling. */ /* Note: very important as we clear IF in the patch itself */ /** @todo this needs to be changed */ if (pPatch->flags & PATMFL_IDTHANDLER) { uint32_t iGate; iGate = TRPMR3QueryGateByHandler(pVM, PATCHCODE_PTR_GC(pPatch)); if (iGate != (uint32_t)~0) { TRPMR3SetGuestTrapHandler(pVM, iGate, TRPM_INVALID_HANDLER); if (++cIDTHandlersDisabled < 256) LogRel(("PATM: Disabling IDT %x patch handler %RRv\n", iGate, pInstrGC)); } } /* Mark the entry with a breakpoint in case somebody else calls it later on (cli patch used as a function, function, trampoline or idt patches) */ if ( pPatch->pPatchBlockOffset && pPatch->uState == PATCH_ENABLED) { Log(("Invalidate patch at %RRv (HC=%RRv)\n", PATCHCODE_PTR_GC(pPatch), PATCHCODE_PTR_HC(pPatch))); pPatch->bDirtyOpcode = *PATCHCODE_PTR_HC(pPatch); *PATCHCODE_PTR_HC(pPatch) = 0xCC; } /* IDT or function patches haven't changed any guest code. */ if (pPatch->flags & PATMFL_PATCHED_GUEST_CODE) { Assert(pPatch->flags & PATMFL_MUST_INSTALL_PATCHJMP); Assert(!(pPatch->flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_IDTHANDLER|PATMFL_TRAMPOLINE|PATMFL_INT3_REPLACEMENT|PATMFL_INT3_REPLACEMENT_BLOCK))); if (pPatch->uState != PATCH_REFUSED) { uint8_t temp[16]; Assert(pPatch->cbPatchJump < sizeof(temp)); /* Let's first check if the guest code is still the same. */ rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), temp, pPatch->pPrivInstrGC, pPatch->cbPatchJump); Assert(rc == VINF_SUCCESS || rc == VERR_PAGE_TABLE_NOT_PRESENT || rc == VERR_PAGE_NOT_PRESENT); if (rc == VINF_SUCCESS) { RTRCINTPTR displ = (RTRCUINTPTR)PATCHCODE_PTR_GC(pPatch) - ((RTRCUINTPTR)pPatch->pPrivInstrGC + SIZEOF_NEARJUMP32); if ( temp[0] != 0xE9 /* jmp opcode */ || *(RTRCINTPTR *)(&temp[1]) != displ ) { Log(("PATMR3DisablePatch: Can't disable a patch who's guest code has changed!!\n")); STAM_COUNTER_INC(&pVM->patm.s.StatOverwritten); /* Remove it completely */ pPatch->uState = PATCH_DISABLED; /* don't call PATMR3DisablePatch again */ rc = PATMR3RemovePatch(pVM, pInstrGC); AssertRC(rc); return VWRN_PATCH_REMOVED; } patmRemoveJumpToPatch(pVM, pPatch); } else { Log(("PATMR3DisablePatch: unable to disable patch -> mark PATCH_DISABLE_PENDING\n")); pPatch->uState = PATCH_DISABLE_PENDING; } } else { AssertMsgFailed(("Patch was refused!\n")); return VERR_PATCH_ALREADY_DISABLED; } } else if (pPatch->flags & (PATMFL_INT3_REPLACEMENT|PATMFL_INT3_REPLACEMENT_BLOCK)) { uint8_t temp[16]; Assert(pPatch->cbPatchJump < sizeof(temp)); /* Let's first check if the guest code is still the same. */ rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), temp, pPatch->pPrivInstrGC, pPatch->cbPatchJump); Assert(rc == VINF_SUCCESS || rc == VERR_PAGE_TABLE_NOT_PRESENT || rc == VERR_PAGE_NOT_PRESENT); if (rc == VINF_SUCCESS) { if (temp[0] != 0xCC) { Log(("PATMR3DisablePatch: Can't disable a patch who's guest code has changed!!\n")); STAM_COUNTER_INC(&pVM->patm.s.StatOverwritten); /* Remove it completely */ pPatch->uState = PATCH_DISABLED; /* don't call PATMR3DisablePatch again */ rc = PATMR3RemovePatch(pVM, pInstrGC); AssertRC(rc); return VWRN_PATCH_REMOVED; } patmDeactivateInt3Patch(pVM, pPatch); } } if (rc == VINF_SUCCESS) { /* Save old state and mark this one as disabled (so it can be enabled later on). */ if (pPatch->uState == PATCH_DISABLE_PENDING) { /* Just to be safe, let's make sure this one can never be reused; the patch might be marked dirty already (int3 at start) */ pPatch->uState = PATCH_UNUSABLE; } else if (pPatch->uState != PATCH_DIRTY) { pPatch->uOldState = pPatch->uState; pPatch->uState = PATCH_DISABLED; } STAM_COUNTER_ADD(&pVM->patm.s.StatDisabled, 1); } Log(("PATMR3DisablePatch: disabled patch at %RRv\n", pInstrGC)); return VINF_SUCCESS; } Log(("Patch not found!\n")); return VERR_PATCH_NOT_FOUND; } /** * Permanently disable patch for privileged instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstr Guest context instruction pointer * @param pConflictAddr Guest context pointer which conflicts with specified patch * @param pConflictPatch Conflicting patch * */ static int patmDisableUnusablePatch(PVM pVM, RTRCPTR pInstrGC, RTRCPTR pConflictAddr, PPATCHINFO pConflictPatch) { NOREF(pConflictAddr); #ifdef PATM_RESOLVE_CONFLICTS_WITH_JUMP_PATCHES PATCHINFO patch; DISCPUSTATE cpu; R3PTRTYPE(uint8_t *) pInstrHC; uint32_t cbInstr; bool disret; int rc; RT_ZERO(patch); pInstrHC = patmR3GCVirtToHCVirt(pVM, &patch, pInstrGC); disret = patmR3DisInstr(pVM, &patch, pInstrGC, pInstrHC, PATMREAD_ORGCODE, &cpu, &cbInstr); /* * If it's a 5 byte relative jump, then we can work around the problem by replacing the 32 bits relative offset * with one that jumps right into the conflict patch. * Otherwise we must disable the conflicting patch to avoid serious problems. */ if ( disret == true && (pConflictPatch->flags & PATMFL_CODE32) && (cpu.pCurInstr->uOpcode == OP_JMP || (cpu.pCurInstr->fOpType & DISOPTYPE_COND_CONTROLFLOW)) && (cpu.Param1.fUse & DISUSE_IMMEDIATE32_REL)) { /* Hint patches must be enabled first. */ if (pConflictPatch->flags & PATMFL_INSTR_HINT) { Log(("Enabling HINTED patch %RRv\n", pConflictPatch->pPrivInstrGC)); pConflictPatch->flags &= ~PATMFL_INSTR_HINT; rc = PATMR3EnablePatch(pVM, pConflictPatch->pPrivInstrGC); Assert(rc == VINF_SUCCESS || rc == VERR_PATCH_NOT_FOUND); /* Enabling might fail if the patched code has changed in the meantime. */ if (rc != VINF_SUCCESS) return rc; } rc = PATMR3InstallPatch(pVM, pInstrGC, PATMFL_CODE32 | PATMFL_JUMP_CONFLICT); if (RT_SUCCESS(rc)) { Log(("PATM -> CONFLICT: Installed JMP patch for patch conflict at %RRv\n", pInstrGC)); STAM_COUNTER_INC(&pVM->patm.s.StatFixedConflicts); return VINF_SUCCESS; } } #endif if (pConflictPatch->opcode == OP_CLI) { /* Turn it into an int3 patch; our GC trap handler will call the generated code manually. */ Log(("PATM -> CONFLICT: Found active patch at instruction %RRv with target %RRv -> turn into int 3 patch!!\n", pInstrGC, pConflictPatch->pPrivInstrGC)); int rc = PATMR3DisablePatch(pVM, pConflictPatch->pPrivInstrGC); if (rc == VWRN_PATCH_REMOVED) return VINF_SUCCESS; if (RT_SUCCESS(rc)) { pConflictPatch->flags &= ~(PATMFL_MUST_INSTALL_PATCHJMP|PATMFL_INSTR_HINT); pConflictPatch->flags |= PATMFL_INT3_REPLACEMENT_BLOCK; rc = PATMR3EnablePatch(pVM, pConflictPatch->pPrivInstrGC); if (rc == VERR_PATCH_NOT_FOUND) return VINF_SUCCESS; /* removed already */ AssertRC(rc); if (RT_SUCCESS(rc)) { STAM_COUNTER_INC(&pVM->patm.s.StatInt3Callable); return VINF_SUCCESS; } } /* else turned into unusable patch (see below) */ } else { Log(("PATM -> CONFLICT: Found active patch at instruction %RRv with target %RRv -> DISABLING it!!\n", pInstrGC, pConflictPatch->pPrivInstrGC)); int rc = PATMR3DisablePatch(pVM, pConflictPatch->pPrivInstrGC); if (rc == VWRN_PATCH_REMOVED) return VINF_SUCCESS; } /* No need to monitor the code anymore. */ if (pConflictPatch->flags & PATMFL_CODE_MONITORED) { int rc = patmRemovePatchPages(pVM, pConflictPatch); AssertRC(rc); } pConflictPatch->uState = PATCH_UNUSABLE; STAM_COUNTER_INC(&pVM->patm.s.StatUnusable); return VERR_PATCH_DISABLED; } /** * Enable patch for privileged instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstr Guest context point to privileged instruction * * @note returns failure if patching is not allowed or possible * */ VMMR3_INT_DECL(int) PATMR3EnablePatch(PVM pVM, RTRCPTR pInstrGC) { PPATMPATCHREC pPatchRec; PPATCHINFO pPatch; Log(("PATMR3EnablePatch %RRv\n", pInstrGC)); AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); pPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); if (pPatchRec) { int rc = VINF_SUCCESS; pPatch = &pPatchRec->patch; if (pPatch->uState == PATCH_DISABLED) { if (pPatch->flags & PATMFL_MUST_INSTALL_PATCHJMP) { Assert(!(pPatch->flags & PATMFL_PATCHED_GUEST_CODE)); uint8_t temp[16]; Assert(pPatch->cbPatchJump < sizeof(temp)); /* Let's first check if the guest code is still the same. */ int rc2 = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), temp, pPatch->pPrivInstrGC, pPatch->cbPatchJump); AssertRC(rc2); if (rc2 == VINF_SUCCESS) { if (memcmp(temp, pPatch->aPrivInstr, pPatch->cbPatchJump)) { Log(("PATMR3EnablePatch: Can't enable a patch who's guest code has changed!!\n")); STAM_COUNTER_INC(&pVM->patm.s.StatOverwritten); /* Remove it completely */ rc = PATMR3RemovePatch(pVM, pInstrGC); AssertRC(rc); return VERR_PATCH_NOT_FOUND; } PATMP2GLOOKUPREC cacheRec; RT_ZERO(cacheRec); cacheRec.pPatch = pPatch; rc2 = patmGenJumpToPatch(pVM, pPatch, &cacheRec, false); /* Free leftover lock if any. */ if (cacheRec.Lock.pvMap) { PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); cacheRec.Lock.pvMap = NULL; } AssertRC(rc2); if (RT_FAILURE(rc2)) return rc2; #ifdef DEBUG { DISCPUSTATE cpu; char szOutput[256]; uint32_t cbInstr; uint32_t i = 0; bool disret; while(i < pPatch->cbPatchJump) { disret = patmR3DisInstrToStr(pVM, pPatch, pPatch->pPrivInstrGC + i, NULL, PATMREAD_ORGCODE, &cpu, &cbInstr, szOutput, sizeof(szOutput)); Log(("Renewed patch instr: %s", szOutput)); i += cbInstr; } } #endif } } else if (pPatch->flags & (PATMFL_INT3_REPLACEMENT|PATMFL_INT3_REPLACEMENT_BLOCK)) { uint8_t temp[16]; Assert(pPatch->cbPatchJump < sizeof(temp)); /* Let's first check if the guest code is still the same. */ int rc2 = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), temp, pPatch->pPrivInstrGC, pPatch->cbPatchJump); AssertRC(rc2); if (memcmp(temp, pPatch->aPrivInstr, pPatch->cbPatchJump)) { Log(("PATMR3EnablePatch: Can't enable a patch who's guest code has changed!!\n")); STAM_COUNTER_INC(&pVM->patm.s.StatOverwritten); rc = PATMR3RemovePatch(pVM, pInstrGC); AssertRC(rc); return VERR_PATCH_NOT_FOUND; } rc2 = patmActivateInt3Patch(pVM, pPatch); if (RT_FAILURE(rc2)) return rc2; } pPatch->uState = pPatch->uOldState; //restore state /* Restore the entry breakpoint with the original opcode (see PATMR3DisablePatch). */ if (pPatch->pPatchBlockOffset) *PATCHCODE_PTR_HC(pPatch) = pPatch->bDirtyOpcode; STAM_COUNTER_ADD(&pVM->patm.s.StatEnabled, 1); } else Log(("PATMR3EnablePatch: Unable to enable patch %RRv with state %d\n", pInstrGC, pPatch->uState)); return rc; } return VERR_PATCH_NOT_FOUND; } /** * Remove patch for privileged instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatchRec Patch record * @param fForceRemove Remove *all* patches */ int patmR3RemovePatch(PVM pVM, PPATMPATCHREC pPatchRec, bool fForceRemove) { PPATCHINFO pPatch; pPatch = &pPatchRec->patch; /* Strictly forbidden to remove such patches. There can be dependencies!! */ if (!fForceRemove && (pPatch->flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_CODE_REFERENCED))) { Log(("PATMRemovePatch %RRv REFUSED!\n", pPatch->pPrivInstrGC)); return VERR_ACCESS_DENIED; } Log(("PATMRemovePatch %RRv\n", pPatch->pPrivInstrGC)); /* Note: NEVER EVER REUSE PATCH MEMORY */ /* Note: PATMR3DisablePatch puts a breakpoint (0xCC) at the entry of this patch */ if (pPatchRec->patch.pPatchBlockOffset) { PAVLOU32NODECORE pNode; pNode = RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, pPatchRec->patch.pPatchBlockOffset); Assert(pNode); } if (pPatchRec->patch.flags & PATMFL_CODE_MONITORED) { int rc = patmRemovePatchPages(pVM, &pPatchRec->patch); AssertRC(rc); } #ifdef VBOX_WITH_STATISTICS if (PATM_STAT_INDEX_IS_VALID(pPatchRec->patch.uPatchIdx)) { STAMR3DeregisterF(pVM->pUVM, "/PATM/Stats/Patch/0x%RRv", pPatchRec->patch.pPrivInstrGC); STAMR3DeregisterF(pVM->pUVM, "/PATM/Stats/PatchBD/0x%RRv*", pPatchRec->patch.pPrivInstrGC); } #endif /* Note: no need to free Guest2PatchAddrTree as those records share memory with Patch2GuestAddrTree records. */ patmEmptyTreeU32(pVM, &pPatch->Patch2GuestAddrTree); pPatch->nrPatch2GuestRecs = 0; Assert(pPatch->Patch2GuestAddrTree == 0); patmEmptyTree(pVM, &pPatch->FixupTree); pPatch->nrFixups = 0; Assert(pPatch->FixupTree == 0); if (pPatchRec->patch.pTempInfo) MMR3HeapFree(pPatchRec->patch.pTempInfo); /* Note: might fail, because it has already been removed (e.g. during reset). */ RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pPatchRec->Core.Key); /* Free the patch record */ MMHyperFree(pVM, pPatchRec); return VINF_SUCCESS; } /** * RTAvlU32DoWithAll() worker. * Checks whether the current trampoline instruction is the jump to the target patch * and updates the displacement to jump to the new target. * * @returns VBox status code. * @retval VERR_ALREADY_EXISTS if the jump was found. * @param pNode The current patch to guest record to check. * @param pvUser The refresh state. */ static int patmR3PatchRefreshFindTrampolinePatch(PAVLU32NODECORE pNode, void *pvUser) { PRECPATCHTOGUEST pPatch2GuestRec = (PRECPATCHTOGUEST)pNode; PPATMREFRESHPATCH pRefreshPatchState = (PPATMREFRESHPATCH)pvUser; PVM pVM = pRefreshPatchState->pVM; uint8_t *pPatchInstr = (uint8_t *)(pVM->patm.s.pPatchMemHC + pPatch2GuestRec->Core.Key); /* * Check if the patch instruction starts with a jump. * ASSUMES that there is no other patch to guest record that starts * with a jump. */ if (*pPatchInstr == 0xE9) { /* Jump found, update the displacement. */ RTRCPTR pPatchTargetGC = patmGuestGCPtrToPatchGCPtr(pVM, pRefreshPatchState->pPatchRec, pRefreshPatchState->pPatchTrampoline->pPrivInstrGC); int32_t displ = pPatchTargetGC - (pVM->patm.s.pPatchMemGC + pPatch2GuestRec->Core.Key + SIZEOF_NEARJUMP32); LogFlow(("Updating trampoline patch new patch target %RRv, new displacment %d (old was %d)\n", pPatchTargetGC, displ, *(uint32_t *)&pPatchInstr[1])); *(uint32_t *)&pPatchInstr[1] = displ; return VERR_ALREADY_EXISTS; /** @todo better return code */ } return VINF_SUCCESS; } /** * Attempt to refresh the patch by recompiling its entire code block * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatchRec Patch record */ int patmR3RefreshPatch(PVM pVM, PPATMPATCHREC pPatchRec) { PPATCHINFO pPatch; int rc; RTRCPTR pInstrGC = pPatchRec->patch.pPrivInstrGC; PTRAMPREC pTrampolinePatchesHead = NULL; Log(("patmR3RefreshPatch: attempt to refresh patch at %RRv\n", pInstrGC)); pPatch = &pPatchRec->patch; AssertReturn(pPatch->flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_IDTHANDLER|PATMFL_TRAPHANDLER), VERR_PATCHING_REFUSED); if (pPatch->flags & PATMFL_EXTERNAL_JUMP_INSIDE) { if (!pPatch->pTrampolinePatchesHead) { /* * It is sometimes possible that there are trampoline patches to this patch * but they are not recorded (after a saved state load for example). * Refuse to refresh those patches. * Can hurt performance in theory if the patched code is modified by the guest * and is executed often. However most of the time states are saved after the guest * code was modified and is not updated anymore afterwards so this shouldn't be a * big problem. */ Log(("patmR3RefreshPatch: refused because external jumps to this patch exist but the jumps are not recorded\n")); return VERR_PATCHING_REFUSED; } Log(("patmR3RefreshPatch: external jumps to this patch exist, updating\n")); pTrampolinePatchesHead = pPatch->pTrampolinePatchesHead; } /* Note: quite ugly to enable/disable/remove/insert old and new patches, but there's no easy way around it. */ rc = PATMR3DisablePatch(pVM, pInstrGC); AssertRC(rc); /* Kick it out of the lookup tree to make sure PATMR3InstallPatch doesn't fail (hack alert) */ RTAvloU32Remove(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pPatchRec->Core.Key); #ifdef VBOX_WITH_STATISTICS if (PATM_STAT_INDEX_IS_VALID(pPatchRec->patch.uPatchIdx)) { STAMR3DeregisterF(pVM->pUVM, "/PATM/Stats/Patch/0x%RRv", pPatchRec->patch.pPrivInstrGC); STAMR3DeregisterF(pVM->pUVM, "/PATM/Stats/PatchBD/0x%RRv*", pPatchRec->patch.pPrivInstrGC); } #endif /** Note: We don't attempt to reuse patch memory here as it's quite common that the new code block requires more memory. */ /* Attempt to install a new patch. */ rc = PATMR3InstallPatch(pVM, pInstrGC, pPatch->flags & (PATMFL_CODE32|PATMFL_IDTHANDLER|PATMFL_INTHANDLER|PATMFL_TRAPHANDLER|PATMFL_DUPLICATE_FUNCTION|PATMFL_TRAPHANDLER_WITH_ERRORCODE|PATMFL_IDTHANDLER_WITHOUT_ENTRYPOINT)); if (RT_SUCCESS(rc)) { RTRCPTR pPatchTargetGC; PPATMPATCHREC pNewPatchRec; /* Determine target address in new patch */ pPatchTargetGC = PATMR3QueryPatchGCPtr(pVM, pInstrGC); Assert(pPatchTargetGC); if (!pPatchTargetGC) { rc = VERR_PATCHING_REFUSED; goto failure; } /* Reset offset into patch memory to put the next code blocks right at the beginning. */ pPatch->uCurPatchOffset = 0; /* insert jump to new patch in old patch block */ rc = patmPatchGenPatchJump(pVM, pPatch, pInstrGC, pPatchTargetGC, false /* no lookup record */); if (RT_FAILURE(rc)) goto failure; pNewPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); Assert(pNewPatchRec); /* can't fail */ /* Remove old patch (only do that when everything is finished) */ int rc2 = patmR3RemovePatch(pVM, pPatchRec, true /* force removal */); AssertRC(rc2); /* Put the new patch back into the tree, because removing the old one kicked this one out. (hack alert) */ bool fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTree, &pNewPatchRec->Core); Assert(fInserted); NOREF(fInserted); Log(("PATM: patmR3RefreshPatch: succeeded to refresh patch at %RRv \n", pInstrGC)); STAM_COUNTER_INC(&pVM->patm.s.StatPatchRefreshSuccess); /* Used by another patch, so don't remove it! */ pNewPatchRec->patch.flags |= PATMFL_CODE_REFERENCED; if (pTrampolinePatchesHead) { /* Update all trampoline patches to jump to the new patch. */ PTRAMPREC pTrampRec = NULL; PATMREFRESHPATCH RefreshPatch; RefreshPatch.pVM = pVM; RefreshPatch.pPatchRec = &pNewPatchRec->patch; pTrampRec = pTrampolinePatchesHead; while (pTrampRec) { PPATCHINFO pPatchTrampoline = &pTrampRec->pPatchTrampoline->patch; RefreshPatch.pPatchTrampoline = pPatchTrampoline; /* * We have to find the right patch2guest record because there might be others * for statistics. */ rc = RTAvlU32DoWithAll(&pPatchTrampoline->Patch2GuestAddrTree, true, patmR3PatchRefreshFindTrampolinePatch, &RefreshPatch); Assert(rc == VERR_ALREADY_EXISTS); rc = VINF_SUCCESS; pTrampRec = pTrampRec->pNext; } pNewPatchRec->patch.pTrampolinePatchesHead = pTrampolinePatchesHead; pNewPatchRec->patch.flags |= PATMFL_EXTERNAL_JUMP_INSIDE; /* Clear the list of trampoline patches for the old patch (safety precaution). */ pPatchRec->patch.pTrampolinePatchesHead = NULL; } } failure: if (RT_FAILURE(rc)) { LogRel(("PATM: patmR3RefreshPatch: failed to refresh patch at %RRv. Reactiving old one. \n", pInstrGC)); /* Remove the new inactive patch */ rc = PATMR3RemovePatch(pVM, pInstrGC); AssertRC(rc); /* Put the old patch back into the tree (or else it won't be saved) (hack alert) */ bool fInserted = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTree, &pPatchRec->Core); Assert(fInserted); NOREF(fInserted); /* Enable again in case the dirty instruction is near the end and there are safe code paths. */ int rc2 = PATMR3EnablePatch(pVM, pInstrGC); AssertRC(rc2); STAM_COUNTER_INC(&pVM->patm.s.StatPatchRefreshFailed); } return rc; } /** * Find patch for privileged instruction at specified location * * @returns Patch structure pointer if found; else NULL * @param pVM Pointer to the VM. * @param pInstr Guest context point to instruction that might lie within 5 bytes of an existing patch jump * @param fIncludeHints Include hinted patches or not * */ PPATCHINFO patmFindActivePatchByEntrypoint(PVM pVM, RTRCPTR pInstrGC, bool fIncludeHints) { PPATMPATCHREC pPatchRec = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC, false); /* if the patch is enabled, the pointer is not identical to the privileged patch ptr and it lies within 5 bytes of this priv instr ptr, then we've got a hit! */ if (pPatchRec) { if ( pPatchRec->patch.uState == PATCH_ENABLED && (pPatchRec->patch.flags & PATMFL_PATCHED_GUEST_CODE) && pInstrGC > pPatchRec->patch.pPrivInstrGC && pInstrGC < pPatchRec->patch.pPrivInstrGC + pPatchRec->patch.cbPatchJump) { Log(("Found active patch at %RRv (org %RRv)\n", pInstrGC, pPatchRec->patch.pPrivInstrGC)); return &pPatchRec->patch; } else if ( fIncludeHints && pPatchRec->patch.uState == PATCH_DISABLED && (pPatchRec->patch.flags & PATMFL_INSTR_HINT) && pInstrGC > pPatchRec->patch.pPrivInstrGC && pInstrGC < pPatchRec->patch.pPrivInstrGC + pPatchRec->patch.cbPatchJump) { Log(("Found HINT patch at %RRv (org %RRv)\n", pInstrGC, pPatchRec->patch.pPrivInstrGC)); return &pPatchRec->patch; } } return NULL; } /** * Checks whether the GC address is inside a generated patch jump * * @returns true -> yes, false -> no * @param pVM Pointer to the VM. * @param pAddr Guest context address. * @param pPatchAddr Guest context patch address (if true). */ VMMR3_INT_DECL(bool) PATMR3IsInsidePatchJump(PVM pVM, RTRCPTR pAddr, PRTGCPTR32 pPatchAddr) { RTRCPTR addr; PPATCHINFO pPatch; Assert(!HMIsEnabled(pVM)); if (PATMIsEnabled(pVM) == false) return false; if (pPatchAddr == NULL) pPatchAddr = &addr; *pPatchAddr = 0; pPatch = patmFindActivePatchByEntrypoint(pVM, pAddr); if (pPatch) *pPatchAddr = pPatch->pPrivInstrGC; return *pPatchAddr == 0 ? false : true; } /** * Remove patch for privileged instruction at specified location * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pInstr Guest context point to privileged instruction * * @note returns failure if patching is not allowed or possible * */ VMMR3_INT_DECL(int) PATMR3RemovePatch(PVM pVM, RTRCPTR pInstrGC) { PPATMPATCHREC pPatchRec; AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); pPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC); if (pPatchRec) { int rc = PATMR3DisablePatch(pVM, pInstrGC); if (rc == VWRN_PATCH_REMOVED) return VINF_SUCCESS; return patmR3RemovePatch(pVM, pPatchRec, false); } AssertFailed(); return VERR_PATCH_NOT_FOUND; } /** * Mark patch as dirty * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch record * * @note returns failure if patching is not allowed or possible * */ static int patmR3MarkDirtyPatch(PVM pVM, PPATCHINFO pPatch) { if (pPatch->pPatchBlockOffset) { Log(("Invalidate patch at %RRv (HC=%RRv)\n", PATCHCODE_PTR_GC(pPatch), PATCHCODE_PTR_HC(pPatch))); pPatch->bDirtyOpcode = *PATCHCODE_PTR_HC(pPatch); *PATCHCODE_PTR_HC(pPatch) = 0xCC; } STAM_COUNTER_INC(&pVM->patm.s.StatDirty); /* Put back the replaced instruction. */ int rc = PATMR3DisablePatch(pVM, pPatch->pPrivInstrGC); if (rc == VWRN_PATCH_REMOVED) return VINF_SUCCESS; /* Note: we don't restore patch pages for patches that are not enabled! */ /* Note: be careful when changing this behaviour!! */ /* The patch pages are no longer marked for self-modifying code detection */ if (pPatch->flags & PATMFL_CODE_MONITORED) { rc = patmRemovePatchPages(pVM, pPatch); AssertRCReturn(rc, rc); } pPatch->uState = PATCH_DIRTY; /* Paranoia; make sure this patch is not somewhere in the callchain, so prevent ret instructions from succeeding. */ CTXSUFF(pVM->patm.s.pGCState)->Psp = PATM_STACK_SIZE; return VINF_SUCCESS; } /** * Query the corresponding GC instruction pointer from a pointer inside the patch block itself * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pPatch Patch block structure pointer * @param pPatchGC GC address in patch block */ RTRCPTR patmPatchGCPtr2GuestGCPtr(PVM pVM, PPATCHINFO pPatch, RCPTRTYPE(uint8_t *) pPatchGC) { Assert(pPatch->Patch2GuestAddrTree); /* Get the closest record from below. */ PRECPATCHTOGUEST pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32GetBestFit(&pPatch->Patch2GuestAddrTree, pPatchGC - pVM->patm.s.pPatchMemGC, false); if (pPatchToGuestRec) return pPatchToGuestRec->pOrgInstrGC; return 0; } /** * Converts Guest code GC ptr to Patch code GC ptr (if found) * * @returns corresponding GC pointer in patch block * @param pVM Pointer to the VM. * @param pPatch Current patch block pointer * @param pInstrGC Guest context pointer to privileged instruction * */ RTRCPTR patmGuestGCPtrToPatchGCPtr(PVM pVM, PPATCHINFO pPatch, RCPTRTYPE(uint8_t*) pInstrGC) { if (pPatch->Guest2PatchAddrTree) { PRECGUESTTOPATCH pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32Get(&pPatch->Guest2PatchAddrTree, pInstrGC); if (pGuestToPatchRec) return pVM->patm.s.pPatchMemGC + pGuestToPatchRec->PatchOffset; } return 0; } /** * Converts Guest code GC ptr to Patch code GC ptr (if found) * * @returns corresponding GC pointer in patch block * @param pVM Pointer to the VM. * @param pInstrGC Guest context pointer to privileged instruction */ static RTRCPTR patmR3GuestGCPtrToPatchGCPtrSimple(PVM pVM, RCPTRTYPE(uint8_t*) pInstrGC) { PPATMPATCHREC pPatchRec = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pInstrGC, false); if (pPatchRec && pPatchRec->patch.uState == PATCH_ENABLED && pInstrGC >= pPatchRec->patch.pPrivInstrGC) return patmGuestGCPtrToPatchGCPtr(pVM, &pPatchRec->patch, pInstrGC); return NIL_RTRCPTR; } /** * Converts Guest code GC ptr to Patch code GC ptr (or nearest from below if no * identical match) * * @returns corresponding GC pointer in patch block * @param pVM Pointer to the VM. * @param pPatch Current patch block pointer * @param pInstrGC Guest context pointer to privileged instruction * */ RTRCPTR patmGuestGCPtrToClosestPatchGCPtr(PVM pVM, PPATCHINFO pPatch, RCPTRTYPE(uint8_t*) pInstrGC) { PRECGUESTTOPATCH pGuestToPatchRec = (PRECGUESTTOPATCH)RTAvlU32GetBestFit(&pPatch->Guest2PatchAddrTree, pInstrGC, false); if (pGuestToPatchRec) return pVM->patm.s.pPatchMemGC + pGuestToPatchRec->PatchOffset; return NIL_RTRCPTR; } /** * Query the corresponding GC instruction pointer from a pointer inside the patch block itself * * @returns original GC instruction pointer or 0 if not found * @param pVM Pointer to the VM. * @param pPatchGC GC address in patch block * @param pEnmState State of the translated address (out) * */ VMMR3_INT_DECL(RTRCPTR) PATMR3PatchToGCPtr(PVM pVM, RTRCPTR pPatchGC, PATMTRANSSTATE *pEnmState) { PPATMPATCHREC pPatchRec; void *pvPatchCoreOffset; RTRCPTR pPrivInstrGC; Assert(PATMIsPatchGCAddr(pVM, pPatchGC)); Assert(!HMIsEnabled(pVM)); pvPatchCoreOffset = RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, pPatchGC - pVM->patm.s.pPatchMemGC, false); if (pvPatchCoreOffset == 0) { Log(("PATMR3PatchToGCPtr failed for %RRv offset %x\n", pPatchGC, pPatchGC - pVM->patm.s.pPatchMemGC)); return 0; } pPatchRec = PATM_PATCHREC_FROM_COREOFFSET(pvPatchCoreOffset); pPrivInstrGC = patmPatchGCPtr2GuestGCPtr(pVM, &pPatchRec->patch, pPatchGC); if (pEnmState) { AssertMsg(pPrivInstrGC && ( pPatchRec->patch.uState == PATCH_ENABLED || pPatchRec->patch.uState == PATCH_DIRTY || pPatchRec->patch.uState == PATCH_DISABLE_PENDING || pPatchRec->patch.uState == PATCH_UNUSABLE), ("pPrivInstrGC=%RRv uState=%d\n", pPrivInstrGC, pPatchRec->patch.uState)); if ( !pPrivInstrGC || pPatchRec->patch.uState == PATCH_UNUSABLE || pPatchRec->patch.uState == PATCH_REFUSED) { pPrivInstrGC = 0; *pEnmState = PATMTRANS_FAILED; } else if (pVM->patm.s.pGCStateHC->GCPtrInhibitInterrupts == pPrivInstrGC) { *pEnmState = PATMTRANS_INHIBITIRQ; } else if ( pPatchRec->patch.uState == PATCH_ENABLED && !(pPatchRec->patch.flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_IDTHANDLER|PATMFL_TRAMPOLINE)) && pPrivInstrGC > pPatchRec->patch.pPrivInstrGC && pPrivInstrGC < pPatchRec->patch.pPrivInstrGC + pPatchRec->patch.cbPatchJump) { *pEnmState = PATMTRANS_OVERWRITTEN; } else if (patmFindActivePatchByEntrypoint(pVM, pPrivInstrGC)) { *pEnmState = PATMTRANS_OVERWRITTEN; } else if (pPrivInstrGC == pPatchRec->patch.pPrivInstrGC) { *pEnmState = PATMTRANS_PATCHSTART; } else *pEnmState = PATMTRANS_SAFE; } return pPrivInstrGC; } /** * Returns the GC pointer of the patch for the specified GC address * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pAddrGC Guest context address */ VMMR3_INT_DECL(RTRCPTR) PATMR3QueryPatchGCPtr(PVM pVM, RTRCPTR pAddrGC) { PPATMPATCHREC pPatchRec; Assert(!HMIsEnabled(pVM)); /* Find the patch record. */ pPatchRec = (PPATMPATCHREC)RTAvloU32Get(&pVM->patm.s.PatchLookupTreeHC->PatchTree, pAddrGC); /** @todo we should only use patches that are enabled! always did this, but it's incorrect! */ if (pPatchRec && (pPatchRec->patch.uState == PATCH_ENABLED || pPatchRec->patch.uState == PATCH_DIRTY)) return PATCHCODE_PTR_GC(&pPatchRec->patch); return NIL_RTRCPTR; } /** * Attempt to recover dirty instructions * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCtx Pointer to the guest CPU context. * @param pPatch Patch record. * @param pPatchToGuestRec Patch to guest address record. * @param pEip GC pointer of trapping instruction. */ static int patmR3HandleDirtyInstr(PVM pVM, PCPUMCTX pCtx, PPATMPATCHREC pPatch, PRECPATCHTOGUEST pPatchToGuestRec, RTRCPTR pEip) { DISCPUSTATE CpuOld, CpuNew; uint8_t *pPatchInstrHC, *pCurPatchInstrHC; int rc; RTRCPTR pCurInstrGC, pCurPatchInstrGC; uint32_t cbDirty; PRECPATCHTOGUEST pRec; RTRCPTR const pOrgInstrGC = pPatchToGuestRec->pOrgInstrGC; PVMCPU pVCpu = VMMGetCpu0(pVM); Log(("patmR3HandleDirtyInstr: dirty instruction at %RRv (%RRv)\n", pEip, pOrgInstrGC)); pRec = pPatchToGuestRec; pCurInstrGC = pOrgInstrGC; pCurPatchInstrGC = pEip; cbDirty = 0; pPatchInstrHC = patmPatchGCPtr2PatchHCPtr(pVM, pCurPatchInstrGC); /* Find all adjacent dirty instructions */ while (true) { if (pRec->fJumpTarget) { LogRel(("PATM: patmR3HandleDirtyInstr: dirty instruction at %RRv (%RRv) ignored, because instruction in function was reused as target of jump\n", pEip, pOrgInstrGC)); pRec->fDirty = false; return VERR_PATCHING_REFUSED; } /* Restore original instruction opcode byte so we can check if the write was indeed safe. */ pCurPatchInstrHC = patmPatchGCPtr2PatchHCPtr(pVM, pCurPatchInstrGC); *pCurPatchInstrHC = pRec->u8DirtyOpcode; /* Only harmless instructions are acceptable. */ rc = CPUMR3DisasmInstrCPU(pVM, pVCpu, pCtx, pCurPatchInstrGC, &CpuOld, 0); if ( RT_FAILURE(rc) || !(CpuOld.pCurInstr->fOpType & DISOPTYPE_HARMLESS)) { if (RT_SUCCESS(rc)) cbDirty += CpuOld.cbInstr; else if (!cbDirty) cbDirty = 1; break; } #ifdef DEBUG char szBuf[256]; DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, pCtx->cs.Sel, pCurPatchInstrGC, DBGF_DISAS_FLAGS_DEFAULT_MODE, szBuf, sizeof(szBuf), NULL); Log(("DIRTY: %s\n", szBuf)); #endif /* Mark as clean; if we fail we'll let it always fault. */ pRec->fDirty = false; /* Remove old lookup record. */ patmr3RemoveP2GLookupRecord(pVM, &pPatch->patch, pCurPatchInstrGC); pPatchToGuestRec = NULL; pCurPatchInstrGC += CpuOld.cbInstr; cbDirty += CpuOld.cbInstr; /* Let's see if there's another dirty instruction right after. */ pRec = (PRECPATCHTOGUEST)RTAvlU32GetBestFit(&pPatch->patch.Patch2GuestAddrTree, pCurPatchInstrGC - pVM->patm.s.pPatchMemGC, true); if (!pRec || !pRec->fDirty) break; /* no more dirty instructions */ /* In case of complex instructions the next guest instruction could be quite far off. */ pCurPatchInstrGC = pRec->Core.Key + pVM->patm.s.pPatchMemGC; } if ( RT_SUCCESS(rc) && (CpuOld.pCurInstr->fOpType & DISOPTYPE_HARMLESS) ) { uint32_t cbLeft; pCurPatchInstrHC = pPatchInstrHC; pCurPatchInstrGC = pEip; cbLeft = cbDirty; while (cbLeft && RT_SUCCESS(rc)) { bool fValidInstr; rc = CPUMR3DisasmInstrCPU(pVM, pVCpu, pCtx, pCurInstrGC, &CpuNew, 0); fValidInstr = !!(CpuNew.pCurInstr->fOpType & DISOPTYPE_HARMLESS); if ( !fValidInstr && (CpuNew.pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW) ) { RTRCPTR pTargetGC = PATMResolveBranch(&CpuNew, pCurInstrGC); if ( pTargetGC >= pOrgInstrGC && pTargetGC <= pOrgInstrGC + cbDirty ) { /* A relative jump to an instruction inside or to the end of the dirty block is acceptable. */ fValidInstr = true; } } /* If the instruction is completely harmless (which implies a 1:1 patch copy). */ if ( rc == VINF_SUCCESS && CpuNew.cbInstr <= cbLeft /* must still fit */ && fValidInstr ) { #ifdef DEBUG char szBuf[256]; DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, pCtx->cs.Sel, pCurInstrGC, DBGF_DISAS_FLAGS_DEFAULT_MODE, szBuf, sizeof(szBuf), NULL); Log(("NEW: %s\n", szBuf)); #endif /* Copy the new instruction. */ rc = PGMPhysSimpleReadGCPtr(VMMGetCpu0(pVM), pCurPatchInstrHC, pCurInstrGC, CpuNew.cbInstr); AssertRC(rc); /* Add a new lookup record for the duplicated instruction. */ patmR3AddP2GLookupRecord(pVM, &pPatch->patch, pCurPatchInstrHC, pCurInstrGC, PATM_LOOKUP_BOTHDIR); } else { #ifdef DEBUG char szBuf[256]; DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, pCtx->cs.Sel, pCurInstrGC, DBGF_DISAS_FLAGS_DEFAULT_MODE, szBuf, sizeof(szBuf), NULL); Log(("NEW: %s (FAILED)\n", szBuf)); #endif /* Restore the old lookup record for the duplicated instruction. */ patmR3AddP2GLookupRecord(pVM, &pPatch->patch, pCurPatchInstrHC, pCurInstrGC, PATM_LOOKUP_BOTHDIR); /** @todo in theory we need to restore the lookup records for the remaining dirty instructions too! */ rc = VERR_PATCHING_REFUSED; break; } pCurInstrGC += CpuNew.cbInstr; pCurPatchInstrHC += CpuNew.cbInstr; pCurPatchInstrGC += CpuNew.cbInstr; cbLeft -= CpuNew.cbInstr; /* Check if we expanded a complex guest instruction into a patch stream (e.g. call) */ if (!cbLeft) { /* If the next patch instruction doesn't correspond to the next guest instruction, then we have some extra room to fill. */ if (RTAvlU32Get(&pPatch->patch.Patch2GuestAddrTree, pCurPatchInstrGC - pVM->patm.s.pPatchMemGC) == NULL) { pRec = (PRECPATCHTOGUEST)RTAvlU32GetBestFit(&pPatch->patch.Patch2GuestAddrTree, pCurPatchInstrGC - pVM->patm.s.pPatchMemGC, true); if (pRec) { unsigned cbFiller = pRec->Core.Key + pVM->patm.s.pPatchMemGC - pCurPatchInstrGC; uint8_t *pPatchFillHC = patmPatchGCPtr2PatchHCPtr(pVM, pCurPatchInstrGC); Assert(!pRec->fDirty); Log(("Room left in patched instruction stream (%d bytes)\n", cbFiller)); if (cbFiller >= SIZEOF_NEARJUMP32) { pPatchFillHC[0] = 0xE9; *(uint32_t *)&pPatchFillHC[1] = cbFiller - SIZEOF_NEARJUMP32; #ifdef DEBUG char szBuf[256]; DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, pCtx->cs.Sel, pCurPatchInstrGC, DBGF_DISAS_FLAGS_DEFAULT_MODE, szBuf, sizeof(szBuf), NULL); Log(("FILL: %s\n", szBuf)); #endif } else { for (unsigned i = 0; i < cbFiller; i++) { pPatchFillHC[i] = 0x90; /* NOP */ #ifdef DEBUG char szBuf[256]; DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, pCtx->cs.Sel, pCurPatchInstrGC + i, DBGF_DISAS_FLAGS_DEFAULT_MODE, szBuf, sizeof(szBuf), NULL); Log(("FILL: %s\n", szBuf)); #endif } } } } } } } else rc = VERR_PATCHING_REFUSED; if (RT_SUCCESS(rc)) { STAM_COUNTER_INC(&pVM->patm.s.StatInstrDirtyGood); } else { STAM_COUNTER_INC(&pVM->patm.s.StatInstrDirtyBad); Assert(cbDirty); /* Mark the whole instruction stream with breakpoints. */ if (cbDirty) memset(pPatchInstrHC, 0xCC, cbDirty); if ( pVM->patm.s.fOutOfMemory == false && (pPatch->patch.flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_IDTHANDLER|PATMFL_TRAPHANDLER))) { rc = patmR3RefreshPatch(pVM, pPatch); if (RT_FAILURE(rc)) { LogRel(("PATM: Failed to refresh dirty patch at %RRv. Disabling it.\n", pPatch->patch.pPrivInstrGC)); } /* Even if we succeed, we must go back to the original instruction as the patched one could be invalid. */ rc = VERR_PATCHING_REFUSED; } } return rc; } /** * Handle trap inside patch code * * @returns VBox status code. * @param pVM Pointer to the VM. * @param pCtx Pointer to the guest CPU context. * @param pEip GC pointer of trapping instruction. * @param ppNewEip GC pointer to new instruction. */ VMMR3_INT_DECL(int) PATMR3HandleTrap(PVM pVM, PCPUMCTX pCtx, RTRCPTR pEip, RTGCPTR *ppNewEip) { PPATMPATCHREC pPatch = 0; void *pvPatchCoreOffset; RTRCUINTPTR offset; RTRCPTR pNewEip; int rc ; PRECPATCHTOGUEST pPatchToGuestRec = 0; PVMCPU pVCpu = VMMGetCpu0(pVM); AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); Assert(pVM->cCpus == 1); pNewEip = 0; *ppNewEip = 0; STAM_PROFILE_ADV_START(&pVM->patm.s.StatHandleTrap, a); /* Find the patch record. */ /* Note: there might not be a patch to guest translation record (global function) */ offset = pEip - pVM->patm.s.pPatchMemGC; pvPatchCoreOffset = RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, offset, false); if (pvPatchCoreOffset) { pPatch = PATM_PATCHREC_FROM_COREOFFSET(pvPatchCoreOffset); Assert(offset >= pPatch->patch.pPatchBlockOffset && offset < pPatch->patch.pPatchBlockOffset + pPatch->patch.cbPatchBlockSize); if (pPatch->patch.uState == PATCH_DIRTY) { Log(("PATMR3HandleTrap: trap in dirty patch at %RRv\n", pEip)); if (pPatch->patch.flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_CODE_REFERENCED)) { /* Function duplication patches set fPIF to 1 on entry */ pVM->patm.s.pGCStateHC->fPIF = 1; } } else if (pPatch->patch.uState == PATCH_DISABLED) { Log(("PATMR3HandleTrap: trap in disabled patch at %RRv\n", pEip)); if (pPatch->patch.flags & (PATMFL_DUPLICATE_FUNCTION|PATMFL_CODE_REFERENCED)) { /* Function duplication patches set fPIF to 1 on entry */ pVM->patm.s.pGCStateHC->fPIF = 1; } } else if (pPatch->patch.uState == PATCH_DISABLE_PENDING) { RTRCPTR pPrivInstrGC = pPatch->patch.pPrivInstrGC; Log(("PATMR3HandleTrap: disable operation is pending for patch at %RRv\n", pPatch->patch.pPrivInstrGC)); rc = PATMR3DisablePatch(pVM, pPatch->patch.pPrivInstrGC); AssertReleaseMsg(rc != VWRN_PATCH_REMOVED, ("PATMR3DisablePatch removed patch at %RRv\n", pPrivInstrGC)); AssertMsg(pPatch->patch.uState == PATCH_DISABLED || pPatch->patch.uState == PATCH_UNUSABLE, ("Unexpected failure to disable patch state=%d rc=%Rrc\n", pPatch->patch.uState, rc)); } pPatchToGuestRec = (PRECPATCHTOGUEST)RTAvlU32GetBestFit(&pPatch->patch.Patch2GuestAddrTree, offset, false); AssertReleaseMsg(pPatchToGuestRec, ("PATMR3HandleTrap: Unable to find corresponding guest address for %RRv (offset %x)\n", pEip, offset)); pNewEip = pPatchToGuestRec->pOrgInstrGC; pPatch->patch.cTraps++; PATM_STAT_FAULT_INC(&pPatch->patch); } else AssertReleaseMsg(pVM->patm.s.pGCStateHC->fPIF == 0, ("PATMR3HandleTrap: Unable to find translation record for %RRv (PIF=0)\n", pEip)); /* Check if we were interrupted in PATM generated instruction code. */ if (pVM->patm.s.pGCStateHC->fPIF == 0) { DISCPUSTATE Cpu; rc = CPUMR3DisasmInstrCPU(pVM, pVCpu, pCtx, pEip, &Cpu, "PIF Trap: "); AssertRC(rc); if ( rc == VINF_SUCCESS && ( Cpu.pCurInstr->uOpcode == OP_PUSHF || Cpu.pCurInstr->uOpcode == OP_PUSH || Cpu.pCurInstr->uOpcode == OP_CALL) ) { uint64_t fFlags; STAM_COUNTER_INC(&pVM->patm.s.StatPushTrap); if (Cpu.pCurInstr->uOpcode == OP_PUSH) { rc = PGMShwGetPage(pVCpu, pCtx->esp, &fFlags, NULL); if ( rc == VINF_SUCCESS && ((fFlags & (X86_PTE_P|X86_PTE_RW)) == (X86_PTE_P|X86_PTE_RW)) ) { /* The stack address is fine, so the push argument is a pointer -> emulate this instruction */ /* Reset the PATM stack. */ CTXSUFF(pVM->patm.s.pGCState)->Psp = PATM_STACK_SIZE; pVM->patm.s.pGCStateHC->fPIF = 1; Log(("Faulting push -> go back to the original instruction\n")); /* continue at the original instruction */ *ppNewEip = pNewEip - SELMToFlat(pVM, DISSELREG_CS, CPUMCTX2CORE(pCtx), 0); STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return VINF_SUCCESS; } } /* Typical pushf (most patches)/push (call patch) trap because of a monitored page. */ rc = PGMShwMakePageWritable(pVCpu, pCtx->esp, 0 /*fFlags*/); AssertMsgRC(rc, ("PGMShwModifyPage -> rc=%Rrc\n", rc)); if (rc == VINF_SUCCESS) { /* The guest page *must* be present. */ rc = PGMGstGetPage(pVCpu, pCtx->esp, &fFlags, NULL); if ( rc == VINF_SUCCESS && (fFlags & X86_PTE_P)) { STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return VINF_PATCH_CONTINUE; } } } else if (pPatch->patch.pPrivInstrGC == pNewEip) { /* Invalidated patch or first instruction overwritten. * We can ignore the fPIF state in this case. */ /* Reset the PATM stack. */ CTXSUFF(pVM->patm.s.pGCState)->Psp = PATM_STACK_SIZE; Log(("Call to invalidated patch -> go back to the original instruction\n")); pVM->patm.s.pGCStateHC->fPIF = 1; /* continue at the original instruction */ *ppNewEip = pNewEip - SELMToFlat(pVM, DISSELREG_CS, CPUMCTX2CORE(pCtx), 0); STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return VINF_SUCCESS; } char szBuf[256]; DBGFR3DisasInstrEx(pVM->pUVM, pVCpu->idCpu, pCtx->cs.Sel, pEip, DBGF_DISAS_FLAGS_DEFAULT_MODE, szBuf, sizeof(szBuf), NULL); /* Very bad. We crashed in emitted code. Probably stack? */ if (pPatch) { AssertReleaseMsg(pVM->patm.s.pGCStateHC->fPIF == 1, ("Crash in patch code %RRv (%RRv) esp=%RX32\nPatch state=%x flags=%RX64 fDirty=%d\n%s\n", pEip, pNewEip, CPUMGetGuestESP(pVCpu), pPatch->patch.uState, pPatch->patch.flags, pPatchToGuestRec->fDirty, szBuf)); } else AssertReleaseMsg(pVM->patm.s.pGCStateHC->fPIF == 1, ("Crash in patch code %RRv (%RRv) esp=%RX32\n%s\n", pEip, pNewEip, CPUMGetGuestESP(pVCpu), szBuf)); EMR3FatalError(pVCpu, VERR_INTERNAL_ERROR); } /* From here on, we must have a valid patch to guest translation. */ if (pvPatchCoreOffset == 0) { STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); AssertMsgFailed(("PATMR3HandleTrap: patch not found at address %RRv!!\n", pEip)); return VERR_PATCH_NOT_FOUND; } /* Take care of dirty/changed instructions. */ if (pPatchToGuestRec->fDirty) { Assert(pPatchToGuestRec->Core.Key == offset); Assert(pVM->patm.s.pGCStateHC->fPIF == 1); rc = patmR3HandleDirtyInstr(pVM, pCtx, pPatch, pPatchToGuestRec, pEip); if (RT_SUCCESS(rc)) { /* Retry the current instruction. */ pNewEip = pEip; rc = VINF_PATCH_CONTINUE; /* Continue at current patch instruction. */ } else { /* Reset the PATM stack. */ CTXSUFF(pVM->patm.s.pGCState)->Psp = PATM_STACK_SIZE; rc = VINF_SUCCESS; /* Continue at original instruction. */ } *ppNewEip = pNewEip - SELMToFlat(pVM, DISSELREG_CS, CPUMCTX2CORE(pCtx), 0); STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return rc; } #ifdef VBOX_STRICT if (pPatch->patch.flags & PATMFL_DUPLICATE_FUNCTION) { DISCPUSTATE cpu; bool disret; uint32_t cbInstr; PATMP2GLOOKUPREC cacheRec; RT_ZERO(cacheRec); cacheRec.pPatch = &pPatch->patch; disret = patmR3DisInstr(pVM, &pPatch->patch, pNewEip, patmR3GCVirtToHCVirt(pVM, &cacheRec, pNewEip), PATMREAD_RAWCODE, &cpu, &cbInstr); if (cacheRec.Lock.pvMap) PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); if (disret && cpu.pCurInstr->uOpcode == OP_RETN) { RTRCPTR retaddr; PCPUMCTX pCtx2; pCtx2 = CPUMQueryGuestCtxPtr(pVCpu); rc = PGMPhysSimpleReadGCPtr(pVCpu, &retaddr, pCtx2->esp, sizeof(retaddr)); AssertRC(rc); Log(("Return failed at %RRv (%RRv)\n", pEip, pNewEip)); Log(("Expected return address %RRv found address %RRv Psp=%x\n", pVM->patm.s.pGCStackHC[(pVM->patm.s.pGCStateHC->Psp+PATM_STACK_SIZE)/sizeof(RTRCPTR)], retaddr, pVM->patm.s.pGCStateHC->Psp)); } } #endif /* Return original address, correct by subtracting the CS base address. */ *ppNewEip = pNewEip - SELMToFlat(pVM, DISSELREG_CS, CPUMCTX2CORE(pCtx), 0); /* Reset the PATM stack. */ CTXSUFF(pVM->patm.s.pGCState)->Psp = PATM_STACK_SIZE; if (pVM->patm.s.pGCStateHC->GCPtrInhibitInterrupts == pNewEip) { /* Must be a faulting instruction after sti; currently only sysexit, hlt or iret */ Log(("PATMR3HandleTrap %RRv -> inhibit irqs set!\n", pEip)); #ifdef VBOX_STRICT DISCPUSTATE cpu; bool disret; uint32_t cbInstr; PATMP2GLOOKUPREC cacheRec; RT_ZERO(cacheRec); cacheRec.pPatch = &pPatch->patch; disret = patmR3DisInstr(pVM, &pPatch->patch, pNewEip, patmR3GCVirtToHCVirt(pVM, &cacheRec, pNewEip), PATMREAD_ORGCODE, &cpu, &cbInstr); if (cacheRec.Lock.pvMap) PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); if (disret && (cpu.pCurInstr->uOpcode == OP_SYSEXIT || cpu.pCurInstr->uOpcode == OP_HLT || cpu.pCurInstr->uOpcode == OP_INT3)) { disret = patmR3DisInstr(pVM, &pPatch->patch, pNewEip, patmR3GCVirtToHCVirt(pVM, &cacheRec, pNewEip), PATMREAD_RAWCODE, &cpu, &cbInstr); if (cacheRec.Lock.pvMap) PGMPhysReleasePageMappingLock(pVM, &cacheRec.Lock); Assert(cpu.pCurInstr->uOpcode == OP_SYSEXIT || cpu.pCurInstr->uOpcode == OP_HLT || cpu.pCurInstr->uOpcode == OP_IRET); } #endif EMSetInhibitInterruptsPC(pVCpu, pNewEip); pVM->patm.s.pGCStateHC->GCPtrInhibitInterrupts = 0; } Log2(("pPatchBlockGC %RRv - pEip %RRv corresponding GC address %RRv\n", PATCHCODE_PTR_GC(&pPatch->patch), pEip, pNewEip)); DBGFR3_DISAS_INSTR_LOG(pVCpu, pCtx->cs.Sel, pNewEip, "PATCHRET: "); if (pNewEip >= pPatch->patch.pPrivInstrGC && pNewEip < pPatch->patch.pPrivInstrGC + pPatch->patch.cbPatchJump) { /* We can't jump back to code that we've overwritten with a 5 byte jump! */ Log(("Disabling patch at location %RRv due to trap too close to the privileged instruction \n", pPatch->patch.pPrivInstrGC)); PATMR3DisablePatch(pVM, pPatch->patch.pPrivInstrGC); STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return VERR_PATCH_DISABLED; } #ifdef PATM_REMOVE_PATCH_ON_TOO_MANY_TRAPS /** @todo compare to nr of successful runs. add some aging algorithm and determine the best time to disable the patch */ if (pPatch->patch.cTraps > MAX_PATCH_TRAPS) { Log(("Disabling patch at location %RRv due to too many traps inside patch code\n", pPatch->patch.pPrivInstrGC)); //we are only wasting time, back out the patch PATMR3DisablePatch(pVM, pPatch->patch.pPrivInstrGC); pTrapRec->pNextPatchInstr = 0; STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return VERR_PATCH_DISABLED; } #endif STAM_PROFILE_ADV_STOP(&pVM->patm.s.StatHandleTrap, a); return VINF_SUCCESS; } /** * Handle page-fault in monitored page * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR3_INT_DECL(int) PATMR3HandleMonitoredPage(PVM pVM) { AssertReturn(!HMIsEnabled(pVM), VERR_PATM_HM_IPE); RTRCPTR addr = pVM->patm.s.pvFaultMonitor; addr &= PAGE_BASE_GC_MASK; int rc = PGMHandlerVirtualDeregister(pVM, addr); AssertRC(rc); NOREF(rc); PPATMPATCHREC pPatchRec = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, addr, false); if (pPatchRec && pPatchRec->patch.uState == PATCH_ENABLED && PAGE_ADDRESS(pPatchRec->patch.pPrivInstrGC) == PAGE_ADDRESS(addr)) { STAM_COUNTER_INC(&pVM->patm.s.StatMonitored); Log(("Renewing patch at %RRv\n", pPatchRec->patch.pPrivInstrGC)); rc = PATMR3DisablePatch(pVM, pPatchRec->patch.pPrivInstrGC); if (rc == VWRN_PATCH_REMOVED) return VINF_SUCCESS; PATMR3EnablePatch(pVM, pPatchRec->patch.pPrivInstrGC); if (addr == pPatchRec->patch.pPrivInstrGC) addr++; } for(;;) { pPatchRec = (PPATMPATCHREC)RTAvloU32GetBestFit(&pVM->patm.s.PatchLookupTreeHC->PatchTree, addr, true); if (!pPatchRec || PAGE_ADDRESS(pPatchRec->patch.pPrivInstrGC) != PAGE_ADDRESS(addr)) break; if (pPatchRec && pPatchRec->patch.uState == PATCH_ENABLED) { STAM_COUNTER_INC(&pVM->patm.s.StatMonitored); Log(("Renewing patch at %RRv\n", pPatchRec->patch.pPrivInstrGC)); PATMR3DisablePatch(pVM, pPatchRec->patch.pPrivInstrGC); PATMR3EnablePatch(pVM, pPatchRec->patch.pPrivInstrGC); } addr = pPatchRec->patch.pPrivInstrGC + 1; } pVM->patm.s.pvFaultMonitor = 0; return VINF_SUCCESS; } #ifdef VBOX_WITH_STATISTICS static const char *PATMPatchType(PVM pVM, PPATCHINFO pPatch) { if (pPatch->flags & PATMFL_SYSENTER) { return "SYSENT"; } else if (pPatch->flags & (PATMFL_TRAPHANDLER|PATMFL_INTHANDLER)) { static char szTrap[16]; uint32_t iGate; iGate = TRPMR3QueryGateByHandler(pVM, PATCHCODE_PTR_GC(pPatch)); if (iGate < 256) RTStrPrintf(szTrap, sizeof(szTrap), (pPatch->flags & PATMFL_INTHANDLER) ? "INT-%2X" : "TRAP-%2X", iGate); else RTStrPrintf(szTrap, sizeof(szTrap), (pPatch->flags & PATMFL_INTHANDLER) ? "INT-??" : "TRAP-??"); return szTrap; } else if (pPatch->flags & (PATMFL_DUPLICATE_FUNCTION)) return "DUPFUNC"; else if (pPatch->flags & PATMFL_REPLACE_FUNCTION_CALL) return "FUNCCALL"; else if (pPatch->flags & PATMFL_TRAMPOLINE) return "TRAMP"; else return patmGetInstructionString(pPatch->opcode, pPatch->flags); } static const char *PATMPatchState(PVM pVM, PPATCHINFO pPatch) { NOREF(pVM); switch(pPatch->uState) { case PATCH_ENABLED: return "ENA"; case PATCH_DISABLED: return "DIS"; case PATCH_DIRTY: return "DIR"; case PATCH_UNUSABLE: return "UNU"; case PATCH_REFUSED: return "REF"; case PATCH_DISABLE_PENDING: return "DIP"; default: AssertFailed(); return " "; } } /** * Resets the sample. * @param pVM Pointer to the VM. * @param pvSample The sample registered using STAMR3RegisterCallback. */ static void patmResetStat(PVM pVM, void *pvSample) { PPATCHINFO pPatch = (PPATCHINFO)pvSample; Assert(pPatch); pVM->patm.s.pStatsHC[pPatch->uPatchIdx].u32A = 0; pVM->patm.s.pStatsHC[pPatch->uPatchIdx].u32B = 0; } /** * Prints the sample into the buffer. * * @param pVM Pointer to the VM. * @param pvSample The sample registered using STAMR3RegisterCallback. * @param pszBuf The buffer to print into. * @param cchBuf The size of the buffer. */ static void patmPrintStat(PVM pVM, void *pvSample, char *pszBuf, size_t cchBuf) { PPATCHINFO pPatch = (PPATCHINFO)pvSample; Assert(pPatch); Assert(pPatch->uState != PATCH_REFUSED); Assert(!(pPatch->flags & (PATMFL_REPLACE_FUNCTION_CALL|PATMFL_MMIO_ACCESS))); RTStrPrintf(pszBuf, cchBuf, "size %04x ->%3s %8s - %08d - %08d", pPatch->cbPatchBlockSize, PATMPatchState(pVM, pPatch), PATMPatchType(pVM, pPatch), pVM->patm.s.pStatsHC[pPatch->uPatchIdx].u32A, pVM->patm.s.pStatsHC[pPatch->uPatchIdx].u32B); } /** * Returns the GC address of the corresponding patch statistics counter * * @returns Stat address * @param pVM Pointer to the VM. * @param pPatch Patch structure */ RTRCPTR patmPatchQueryStatAddress(PVM pVM, PPATCHINFO pPatch) { Assert(pPatch->uPatchIdx != PATM_STAT_INDEX_NONE); return pVM->patm.s.pStatsGC + sizeof(STAMRATIOU32) * pPatch->uPatchIdx + RT_OFFSETOF(STAMRATIOU32, u32A); } #endif /* VBOX_WITH_STATISTICS */ #ifdef VBOX_WITH_DEBUGGER /** * The '.patmoff' command. * * @returns VBox status. * @param pCmd Pointer to the command descriptor (as registered). * @param pCmdHlp Pointer to command helper functions. * @param pVM Pointer to the current VM (if any). * @param paArgs Pointer to (readonly) array of arguments. * @param cArgs Number of arguments in the array. */ static DECLCALLBACK(int) patmr3CmdOff(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs) { /* * Validate input. */ NOREF(cArgs); NOREF(paArgs); DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM); PVM pVM = pUVM->pVM; VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE); if (HMIsEnabled(pVM)) return DBGCCmdHlpPrintf(pCmdHlp, "PATM is permanently disabled by HM.\n"); RTAvloU32DoWithAll(&pVM->patm.s.PatchLookupTreeHC->PatchTree, true, DisableAllPatches, pVM); PATMR3AllowPatching(pVM->pUVM, false); return pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Patching disabled\n"); } /** * The '.patmon' command. * * @returns VBox status. * @param pCmd Pointer to the command descriptor (as registered). * @param pCmdHlp Pointer to command helper functions. * @param pVM Pointer to the current VM (if any). * @param paArgs Pointer to (readonly) array of arguments. * @param cArgs Number of arguments in the array. */ static DECLCALLBACK(int) patmr3CmdOn(PCDBGCCMD pCmd, PDBGCCMDHLP pCmdHlp, PUVM pUVM, PCDBGCVAR paArgs, unsigned cArgs) { /* * Validate input. */ NOREF(cArgs); NOREF(paArgs); DBGC_CMDHLP_REQ_UVM_RET(pCmdHlp, pCmd, pUVM); PVM pVM = pUVM->pVM; VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE); if (HMIsEnabled(pVM)) return DBGCCmdHlpPrintf(pCmdHlp, "PATM is permanently disabled by HM.\n"); PATMR3AllowPatching(pVM->pUVM, true); RTAvloU32DoWithAll(&pVM->patm.s.PatchLookupTreeHC->PatchTree, true, EnableAllPatches, pVM); return pCmdHlp->pfnPrintf(pCmdHlp, NULL, "Patching enabled\n"); } #endif /* VBOX_WITH_DEBUGGER */