PATMSSM.cpp revision e0c9aba4992a8f5c36a410421844fb1a0c84dbcc
/* $Id$ */
/** @file
* PATMSSM - Dynamic Guest OS Patching Manager; Save and load state
*
* NOTE: CSAM assumes patch memory is never reused!!
*/
/*
* Copyright (C) 2006-2014 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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 "PATMInternal.h"
#include "PATMPatch.h"
#include "PATMA.h"
#include <VBox/disopcode.h>
/**
* Patch information - SSM version.
*
* the difference is the missing pTrampolinePatchesHead member
* to avoid changing the saved state version for now (will come later).
*/
typedef struct PATCHINFOSSM
{
/* GC pointer of privileged instruction */
R3PTRTYPE(uint8_t *) unusedHC; /* todo Can't remove due to structure size dependencies in saved states. */
/* Only valid for PATMFL_JUMP_CONFLICT patches */
#if HC_ARCH_BITS == 64
#endif
/**
* Lowest and highest patched GC instruction address. To optimize searches.
*/
/* Tree of fixup records for the patch. */
/* Tree of jumps inside the generated patch code. */
/**
* Lookup trees for determining the corresponding guest address of an
* instruction in the patch block.
*/
#if HC_ARCH_BITS == 64
#endif
/* Unused, but can't remove due to structure size dependencies in the saved state. */
/* Temporary information during patch creation. Don't waste hypervisor memory for this. */
/* Count the number of writes to the corresponding guest code. */
/* Count the number of invalid writes to pages monitored for the patch. */
//some statistics to determine if we should keep this patch activated
// Index into the uPatchRun and uPatchTrap arrays (0..MAX_PATCHES-1)
/* First opcode byte, that's overwritten when a patch is marked dirty. */
/**
* Lookup record for patches - SSM version.
*/
typedef struct PATMPATCHRECSSM
{
/** The key is a GC virtual address. */
/** The key is a patch offset. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static void patmCorrectFixup(PVM pVM, unsigned ulSSMVersion, PATM &patmInfo, PPATCHINFO pPatch, PRELOCREC pRec, int32_t offset, RTRCPTR *pFixup);
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/**
* SSM descriptor table for the PATM structure.
*/
static SSMFIELD const g_aPatmFields[] =
{
/** @todo there are a bunch more fields here which can be marked as ignored. */
};
/**
* SSM descriptor table for the PATM structure starting with r86139.
*/
static SSMFIELD const g_aPatmFields86139[] =
{
/** @todo there are a bunch more fields here which can be marked as ignored. */
};
/**
* SSM descriptor table for the PATMGCSTATE structure.
*/
static SSMFIELD const g_aPatmGCStateFields[] =
{
};
/**
* SSM descriptor table for the PATMPATCHREC structure.
*/
static SSMFIELD const g_aPatmPatchRecFields[] =
{
};
/**
* SSM descriptor table for the RELOCREC structure.
*/
static SSMFIELD const g_aPatmRelocRec[] =
{
SSMFIELD_ENTRY_PAD_HC_AUTO( 0, 4),
};
/**
* SSM descriptor table for the RECPATCHTOGUEST structure.
*/
static SSMFIELD const g_aPatmRecPatchToGuest[] =
{
SSMFIELD_ENTRY_PAD_HC_AUTO( 0, 4),
};
#ifdef VBOX_STRICT
/**
* Callback function for RTAvlPVDoWithAll
*
* Counts the number of patches in the tree
*
* @returns VBox status code.
* @param pNode Current node
* @param pcPatches Pointer to patch counter (uint32_t)
*/
{
return VINF_SUCCESS;
}
/**
* Callback function for RTAvlU32DoWithAll
*
* Counts the number of patches in the tree
*
* @returns VBox status code.
* @param pNode Current node
* @param pcPatches Pointer to patch counter (uint32_t)
*/
{
return VINF_SUCCESS;
}
#endif /* VBOX_STRICT */
/**
* Callback function for RTAvloU32DoWithAll
*
* Counts the number of patches in the tree
*
* @returns VBox status code.
* @param pNode Current node
* @param pcPatches Pointer to patch counter
*/
{
return VINF_SUCCESS;
}
/**
* Callback function for RTAvlU32DoWithAll
*
* Saves all patch to guest lookup records.
*
* @returns VBox status code.
* @param pNode Current node
* @param pVM1 Pointer to the VM
*/
{
/* Save the lookup record. */
int rc = SSMR3PutStructEx(pSSM, pPatchToGuestRec, sizeof(RECPATCHTOGUEST), 0 /*fFlags*/, &g_aPatmRecPatchToGuest[0], NULL);
return VINF_SUCCESS;
}
/**
* Callback function for RTAvlPVDoWithAll
*
* Saves all patch to guest lookup records.
*
* @returns VBox status code.
* @param pNode Current node
* @param pVM1 Pointer to the VM
*/
{
/* Convert pointer to an offset into patch memory. May not be applicable
to all fixup types, thus the UINT32_MAX. */
/* Zero rec.Core.Key since it's unused and may trigger SSM check due to the hack below. */
{
/* Core.Key abused to store the fixup type. */
else
else
else
else
}
/* Save the lookup record. */
return VINF_SUCCESS;
}
/**
* Converts a saved state patch record to the memory record.
*
* @returns nothing.
* @param pPatch The memory record.
* @param pPatchSSM The SSM version of the patch record.
*/
{
/*
* Only restore the patch part of the tree record; not the internal data (except the key of course)
*/
}
/**
* Converts a memory patch record to the saved state version.
*
* @returns nothing.
* @param pPatchSSM The saved state record.
* @param pPatch The memory version to save.
*/
{
}
/**
* Callback function for RTAvloU32DoWithAll
*
* Saves the state of the patch that's being enumerated
*
* @returns VBox status code.
* @param pNode Current node
* @param pVM1 Pointer to the VM
*/
{
int rc;
Log4(("patmSavePatchState: cbPatchJump=%u uCurPathOffset=%#x pInstrGCLowest/Higest=%#x/%#x nrFixups=%#x nrJumpRecs=%#x\n",
patch.patch.cbPatchJump, patch.patch.uCurPatchOffset, patch.patch.pInstrGCLowest, patch.patch.pInstrGCHighest,
/*
* Reset HC pointers that need to be recalculated when loading the state
*/
AssertMsg(patch.patch.uState == PATCH_REFUSED || (patch.patch.pPatchBlockOffset || (patch.patch.flags & (PATMFL_SYSENTER_XP|PATMFL_INT3_REPLACEMENT))),
("State = %x pPatchBlockHC=%08x flags=%x\n", patch.patch.uState, PATCHCODE_PTR_HC(&patch.patch), patch.patch.flags));
/* Save the patch record itself */
/*
* Reset HC pointers in fixup records and save them.
*/
#ifdef VBOX_STRICT
uint32_t nrFixupRecs = 0;
AssertMsg(nrFixupRecs == pPatch->patch.nrFixups, ("Fixup inconsistency! counted %d vs %d\n", nrFixupRecs, pPatch->patch.nrFixups));
#endif
#ifdef VBOX_STRICT
uint32_t nrLookupRecords = 0;
#endif
return VINF_SUCCESS;
}
/**
* Execute state save operation.
*
* @returns VBox status code.
* @param pVM Pointer to the VM.
* @param pSSM SSM operation handle.
*/
{
int rc;
/*
* Reset HC pointers that need to be recalculated when loading the state
*/
patmInfo.pGCStateHC = 0;
patmInfo.pvFaultMonitor = 0;
/*
* Count the number of patches in the tree (feeling lazy)
*/
RTAvloU32DoWithAll(&pVM->patm.s.PatchLookupTreeHC->PatchTree, true, patmCountPatch, &patmInfo.savedstate.cPatches);
/*
* Save PATM structure
*/
/*
* Save patch memory contents
*/
/*
* Save GC state memory
*/
rc = SSMR3PutStructEx(pSSM, pVM->patm.s.pGCStateHC, sizeof(PATMGCSTATE), 0 /*fFlags*/, &g_aPatmGCStateFields[0], NULL);
/*
* Save PATM stack page
*/
/*
* Save all patches
*/
/** @note patch statistics are not saved. */
return VINF_SUCCESS;
}
/**
* Execute state load operation.
*
* @returns VBox status code.
* @param pVM Pointer to the VM.
* @param pSSM SSM operation handle.
* @param uVersion Data layout version.
* @param uPass The data pass.
*/
{
int rc;
if ( uVersion != PATM_SAVED_STATE_VERSION
)
{
}
uint32_t const fStructRestoreFlags = uVersion <= PATM_SAVED_STATE_VERSION_MEM ? SSMSTRUCT_FLAGS_MEM_BAND_AID_RELAXED : 0;
/*
* Restore PATM structure
*/
if ( uVersion == PATM_SAVED_STATE_VERSION_MEM
&g_aPatmFields86139[0], NULL);
else
rc = SSMR3GetStructEx(pSSM, &patmInfo, sizeof(patmInfo), fStructRestoreFlags, &g_aPatmFields[0], NULL);
/* Relative calls are made to the helper functions. Therefor their relative location must not change! */
/* Note: we reuse the saved global helpers and assume they are identical, which is kind of dangerous. */
AssertLogRelReturn((pVM->patm.s.pfnHelperCallGC - pVM->patm.s.pPatchMemGC) == (patmInfo.pfnHelperCallGC - patmInfo.pPatchMemGC),
AssertLogRelReturn((pVM->patm.s.pfnHelperRetGC - pVM->patm.s.pPatchMemGC) == (patmInfo.pfnHelperRetGC - patmInfo.pPatchMemGC),
AssertLogRelReturn((pVM->patm.s.pfnHelperJumpGC - pVM->patm.s.pPatchMemGC) == (patmInfo.pfnHelperJumpGC - patmInfo.pPatchMemGC),
AssertLogRelReturn((pVM->patm.s.pfnHelperIretGC - pVM->patm.s.pPatchMemGC) == (patmInfo.pfnHelperIretGC - patmInfo.pPatchMemGC),
AssertLogRelReturn(pVM->patm.s.cbPatchMem == patmInfo.cbPatchMem, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
/* Lowest and highest patched instruction */
/* Sysenter handlers */
/** @note patch statistics are not restored. */
/*
* Restore patch memory contents
*/
/*
* Restore GC state memory
*/
rc = SSMR3GetStructEx(pSSM, pVM->patm.s.pGCStateHC, sizeof(PATMGCSTATE), fStructRestoreFlags, &g_aPatmGCStateFields[0], NULL);
/*
* Restore PATM stack page
*/
{
}
if (cbStack < PATM_STACK_TOTAL_SIZE)
/*
* Load all patches
*/
{
rc = SSMR3GetStructEx(pSSM, &patch, sizeof(patch), fStructRestoreFlags, &g_aPatmPatchRecFields[0], NULL);
Log4(("patmR3Load: cbPatchJump=%u uCurPathOffset=%#x pInstrGCLowest/Higest=%#x/%#x nrFixups=%#x nrJumpRecs=%#x\n",
patch.patch.cbPatchJump, patch.patch.uCurPatchOffset, patch.patch.pInstrGCLowest, patch.patch.pInstrGCHighest,
if (RT_FAILURE(rc))
{
AssertMsgFailed(("Out of memory!!!!\n"));
return VERR_NO_MEMORY;
}
/* Convert SSM version to memory. */
Log(("Restoring patch %RRv -> %RRv state %x\n", pPatchRec->patch.pPrivInstrGC, patmInfo.pPatchMemGC + pPatchRec->patch.pPatchBlockOffset, pPatchRec->patch.uState));
{
{
/* We actually generated code for this patch. */
ret = RTAvloU32Insert(&pVM->patm.s.PatchLookupTreeHC->PatchTreeByPatchAddr, &pPatchRec->CoreOffset);
AssertMsg(ret, ("Inserting patch %RRv offset %08RX32 failed!!\n", pPatchRec->patch.pPrivInstrGC, pPatchRec->CoreOffset.Key));
}
}
/* Set to zero as we don't need it anymore. */
/* Can fail due to page or page table not present. */
/*
* Restore fixup records and correct HC pointers in fixup records
*/
{
if (pPrivInstrHC)
{
/* rec.pRelocPos now contains the relative position inside the hypervisor area. */
/* Convert to HC pointer again. */
else
{
{
Assert(pPatchRec->patch.cbPatchJump == SIZEOF_NEARJUMP32 || pPatchRec->patch.cbPatchJump == SIZEOF_NEAR_COND_JUMP32);
}
}
}
}
/* Release previous lock if any. */
/* And all patch to guest lookup records */
Assert(pPatchRec->patch.nrPatch2GuestRecs || pPatchRec->patch.uState == PATCH_REFUSED || (pPatchRec->patch.flags & (PATMFL_SYSENTER_XP | PATMFL_IDTHANDLER | PATMFL_TRAPHANDLER | PATMFL_INT3_REPLACEMENT)));
{
for (uint32_t j=0;j<nrPatch2GuestRecs;j++)
{
rc = SSMR3GetStructEx(pSSM, &rec, sizeof(rec), fStructRestoreFlags, &g_aPatmRecPatchToGuest[0], NULL);
patmR3AddP2GLookupRecord(pVM, &pPatchRec->patch, (uintptr_t)rec.Core.Key + pVM->patm.s.pPatchMemHC, rec.pOrgInstrGC, rec.enmType, rec.fDirty);
}
}
{
/* Insert the guest page lookup records (for detection self-modifying code) */
}
#if 0 /* can fail def LOG_ENABLED */
{
pPatchRec->patch.pTempInfo = (PPATCHINFOTEMP)MMR3HeapAllocZ(pVM, MM_TAG_PATM_PATCH, sizeof(PATCHINFOTEMP));
Log(("Patch code ----------------------------------------------------------\n"));
patmr3DisasmCodeStream(pVM, PATCHCODE_PTR_GC(&pPatchRec->patch), PATCHCODE_PTR_GC(&pPatchRec->patch), patmr3DisasmCallback, &pPatchRec->patch);
Log(("Patch code ends -----------------------------------------------------\n"));
}
#endif
/* Remove the patch in case the gc mapping is not present. */
if ( !pPrivInstrHC
{
}
}
/*
* Correct absolute fixups in the global patch. (helper functions)
* Bit of a mess. Uses the new patch record, but restored patch functions.
*/
Log(("Correct fixups in global helper functions\n"));
while (true)
{
/* Get the record that's closest from above */
if (pRec == 0)
break;
/* rec.pRelocPos now contains the relative position inside the hypervisor area. */
/* Correct fixups that refer to PATM structures in the hypervisor region (their addresses might have changed). */
patmCorrectFixup(pVM, uVersion, patmInfo, &pVM->patm.s.pGlobalPatchRec->patch, pRec, offset, pFixup);
}
#ifdef VBOX_WITH_STATISTICS
/*
* Restore relevant old statistics
*/
#endif
return VINF_SUCCESS;
}
/**
* Correct fixups to predefined hypervisor PATM regions. (their addresses might have changed)
*
* @returns VBox status code.
* @param pVM Pointer to the VM.
* @param ulSSMVersion SSM version
* @param patmInfo Saved PATM structure
* @param pPatch Patch record
* @param pRec Relocation record
* @param pFixup Fixup address
*/
static void patmCorrectFixup(PVM pVM, unsigned ulSSMVersion, PATM &patmInfo, PPATCHINFO pPatch, PRELOCREC pRec, int32_t offset, RTRCPTR *pFixup)
{
{
case FIXUP_ABSOLUTE:
{
break;
{
LogFlow(("Changing absolute GCState at %RRv from %RRv to %RRv\n", patmInfo.pPatchMemGC + offset, *pFixup, (*pFixup - patmInfo.pGCStateGC) + pVM->patm.s.pGCStateGC));
}
else
{
LogFlow(("Changing absolute CPUMCTX at %RRv from %RRv to %RRv\n", patmInfo.pPatchMemGC + offset, *pFixup, (*pFixup - patmInfo.pCPUMCtxGC) + pVM->patm.s.pCPUMCtxGC));
/* The CPUMCTX structure has completely changed, so correct the offsets too. */
{
/* ''case RT_OFFSETOF()'' does not work as gcc refuses to use & as a constant expression.
* Defining RT_OFFSETOF as __builtin_offsetof for gcc would make this possible. But this
* function is not available in older gcc versions, at least not in gcc-3.3 */
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
else
}
else
}
else
{
LogFlow(("Changing absolute Stats at %RRv from %RRv to %RRv\n", patmInfo.pPatchMemGC + offset, *pFixup, (*pFixup - patmInfo.pStatsGC) + pVM->patm.s.pStatsGC));
}
else
{
LogFlow(("Changing absolute Stack at %RRv from %RRv to %RRv\n", patmInfo.pPatchMemGC + offset, *pFixup, (*pFixup - patmInfo.pGCStackGC) + pVM->patm.s.pGCStackGC));
}
else
{
LogFlow(("Changing absolute PatchMem at %RRv from %RRv to %RRv\n", patmInfo.pPatchMemGC + offset, *pFixup, (*pFixup - patmInfo.pPatchMemGC) + pVM->patm.s.pPatchMemGC));
}
else
/* Boldly ASSUMES:
* 1. That pCPUMCtxGC is in the VM structure and that its location is
* at the first page of the same 4 MB chunk.
* 2. That the forced actions were in the first 32 bytes of the VM
* structure.
* 3. That the CPUM leafs are less than 8KB into the structure. */
{
LogFlow(("Changing fLocalForcedActions fixup from %RRv to %RRv\n", *pFixup, pVM->pVMRC + RT_OFFSETOF(VM, aCpus[0].fLocalForcedActions)));
}
else
{
static int cCpuidFixup = 0;
#ifdef LOG_ENABLED
#endif
/* very dirty assumptions about the cpuid patch and cpuid ordering. */
switch(cCpuidFixup & 3)
{
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
cCpuidFixup++;
}
else
{
#ifdef LOG_ENABLED
#endif
/* Core.Key abused to store the type of fixup */
{
case PATM_FIXUP_CPU_FF_ACTION:
break;
case PATM_FIXUP_CPUID_DEFAULT:
break;
break;
break;
case PATM_FIXUP_CPUID_CENTAUR:
break;
default:
break;
}
}
#ifdef RT_OS_WINDOWS
#endif
break;
}
case FIXUP_REL_JMPTOPATCH:
{
{
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
#endif
{
oldJump[0] = 0xE9;
}
else
{
break;
}
/*
* Read old patch jump and compare it to the one we previously installed
*/
{
rc = PGMR3HandlerVirtualRegister(pVM, PGMVIRTHANDLERTYPE_ALL, pPage, pPage + (PAGE_SIZE - 1) /* inclusive! */, 0, patmVirtPageHandler, "PATMGCMonitorPage", 0, "PATMMonitorPatchJump");
}
else
{
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)!!!! */
}
else
if (RT_SUCCESS(rc))
{
}
else
}
else
break;
}
case FIXUP_REL_JMPTOGUEST:
{
Log(("Relative fixup (p2g) %08X -> %08X at %08X (source=%08x, target=%08x)\n", *(int32_t*)pRec->pRelocPos, displ, pRec->pRelocPos, pRec->pSource, pRec->pDest));
break;
}
}
}