// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#include "common.h"
#include "eetwain.h"
#include "dbginterface.h"
#include "gcenv.h"
#define RETURN_ADDR_OFFS 1 // in DWORDS
#ifdef USE_GC_INFO_DECODER
#include "gcinfodecoder.h"
#endif
#ifdef HAVE_GCCOVER
#include "gccover.h"
#endif // HAVE_GCCOVER
#include "argdestination.h"
#define X86_INSTR_W_TEST_ESP 0x4485 // test [esp+N], eax
#define X86_INSTR_TEST_ESP_SIB 0x24
#define X86_INSTR_PUSH_0 0x6A // push 00, entire instruction is 0x6A00
#define X86_INSTR_PUSH_IMM 0x68 // push NNNN,
#define X86_INSTR_W_PUSH_IND_IMM 0x35FF // push [NNNN]
#define X86_INSTR_CALL_REL32 0xE8 // call rel32
#define X86_INSTR_W_CALL_IND_IMM 0x15FF // call [addr32]
#define X86_INSTR_NOP 0x90 // nop
#define X86_INSTR_NOP2 0x9090 // 2-byte nop
#define X86_INSTR_NOP3_1 0x9090 // 1st word of 3-byte nop
#define X86_INSTR_NOP3_3 0x90 // 3rd byte of 3-byte nop
#define X86_INSTR_NOP4 0x90909090 // 4-byte nop
#define X86_INSTR_NOP5_1 0x90909090 // 1st dword of 5-byte nop
#define X86_INSTR_NOP5_5 0x90 // 5th byte of 5-byte nop
#define X86_INSTR_INT3 0xCC // int3
#define X86_INSTR_HLT 0xF4 // hlt
#define X86_INSTR_PUSH_EBP 0x55 // push ebp
#define X86_INSTR_W_MOV_EBP_ESP 0xEC8B // mov ebp, esp
#define X86_INSTR_POP_ECX 0x59 // pop ecx
#define X86_INSTR_RET 0xC2 // ret imm16
#define X86_INSTR_RETN 0xC3 // ret
#define X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET 0x658d // lea esp, [ebp-bOffset]
#define X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET 0xa58d // lea esp, [ebp-dwOffset]
#define X86_INSTR_JMP_NEAR_REL32 0xE9 // near jmp rel32
#define X86_INSTR_w_JMP_FAR_IND_IMM 0x25FF // far jmp [addr32]
#ifndef USE_GC_INFO_DECODER
#ifdef _DEBUG
// For dumping of verbose info.
#ifndef DACCESS_COMPILE
static bool trFixContext = false;
#endif
static bool trEnumGCRefs = false;
static bool dspPtr = false; // prints the live ptrs as reported
#endif
// NOTE: enabling compiler optimizations, even for debug builds.
// Comment this out in order to be able to fully debug methods here.
#if defined(_MSC_VER)
#pragma optimize("tg", on)
#endif
__forceinline unsigned decodeUnsigned(PTR_CBYTE& src)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
#ifdef DACCESS_COMPILE
PTR_CBYTE begin = src;
#endif
BYTE byte = *src++;
unsigned value = byte & 0x7f;
while (byte & 0x80)
{
#ifdef DACCESS_COMPILE
// In DAC builds, the target data may be corrupt. Rather than return incorrect data
// and risk wasting time in a potentially long loop, we want to fail early and gracefully.
// The data is encoded with 7 value-bits per byte, and so we may need to read a maximum
// of 5 bytes (7*5=35) to read a full 32-bit integer.
if ((src - begin) > 5)
{
DacError(CORDBG_E_TARGET_INCONSISTENT);
}
#endif
byte = *src++;
value <<= 7;
value += byte & 0x7f;
}
return value;
}
__forceinline int decodeSigned(PTR_CBYTE& src)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
#ifdef DACCESS_COMPILE
PTR_CBYTE begin = src;
#endif
BYTE byte = *src++;
BYTE first = byte;
int value = byte & 0x3f;
while (byte & 0x80)
{
#ifdef DACCESS_COMPILE
// In DAC builds, the target data may be corrupt. Rather than return incorrect data
// and risk wasting time in a potentially long loop, we want to fail early and gracefully.
// The data is encoded with 7 value-bits per byte, and so we may need to read a maximum
// of 5 bytes (7*5=35) to read a full 32-bit integer.
if ((src - begin) > 5)
{
DacError(CORDBG_E_TARGET_INCONSISTENT);
}
#endif
byte = *src++;
value <<= 7;
value += byte & 0x7f;
}
if (first & 0x40)
value = -value;
return value;
}
// Fast versions of the above, with one iteration of the loop unrolled
#define fastDecodeUnsigned(src) (((*(src) & 0x80) == 0) ? (unsigned) (*(src)++) : decodeUnsigned((src)))
#define fastDecodeSigned(src) (((*(src) & 0xC0) == 0) ? (unsigned) (*(src)++) : decodeSigned((src)))
// Fast skipping past encoded integers
#ifndef DACCESS_COMPILE
#define fastSkipUnsigned(src) { while ((*(src)++) & 0x80) { } }
#define fastSkipSigned(src) { while ((*(src)++) & 0x80) { } }
#else
// In DAC builds we want to trade-off a little perf in the common case for reliaiblity against corrupt data.
#define fastSkipUnsigned(src) (decodeUnsigned(src))
#define fastSkipSigned(src) (decodeSigned(src))
#endif
/*****************************************************************************
*
* Decodes the X86 GcInfo header and returns the decoded information
* in the hdrInfo struct.
* curOffset is the code offset within the active method used in the
* computation of PrologOffs/EpilogOffs.
* Returns the size of the header (number of bytes decoded).
*/
static size_t DecodeGCHdrInfo(GCInfoToken gcInfoToken,
unsigned curOffset,
hdrInfo * infoPtr)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
HOST_NOCALLS;
SUPPORTS_DAC;
} CONTRACTL_END;
PTR_CBYTE table = (PTR_CBYTE) gcInfoToken.Info;
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xFEEF);
#endif
infoPtr->methodSize = fastDecodeUnsigned(table);
_ASSERTE(curOffset >= 0);
_ASSERTE(curOffset <= infoPtr->methodSize);
/* Decode the InfoHdr */
InfoHdr header;
table = decodeHeader(table, gcInfoToken.Version, &header);
BOOL hasArgTabOffset = FALSE;
if (header.untrackedCnt == HAS_UNTRACKED)
{
hasArgTabOffset = TRUE;
header.untrackedCnt = fastDecodeUnsigned(table);
}
if (header.varPtrTableSize == HAS_VARPTR)
{
hasArgTabOffset = TRUE;
header.varPtrTableSize = fastDecodeUnsigned(table);
}
if (header.gsCookieOffset == HAS_GS_COOKIE_OFFSET)
{
header.gsCookieOffset = fastDecodeUnsigned(table);
}
if (header.syncStartOffset == HAS_SYNC_OFFSET)
{
header.syncStartOffset = decodeUnsigned(table);
header.syncEndOffset = decodeUnsigned(table);
_ASSERTE(header.syncStartOffset != INVALID_SYNC_OFFSET && header.syncEndOffset != INVALID_SYNC_OFFSET);
_ASSERTE(header.syncStartOffset < header.syncEndOffset);
}
if (header.revPInvokeOffset == HAS_REV_PINVOKE_FRAME_OFFSET)
{
header.revPInvokeOffset = fastDecodeUnsigned(table);
}
/* Some sanity checks on header */
_ASSERTE( header.prologSize +
(size_t)(header.epilogCount*header.epilogSize) <= infoPtr->methodSize);
_ASSERTE( header.epilogCount == 1 || !header.epilogAtEnd);
_ASSERTE( header.untrackedCnt <= header.argCount+header.frameSize);
_ASSERTE( header.ebpSaved || !(header.ebpFrame || header.doubleAlign));
_ASSERTE(!header.ebpFrame || !header.doubleAlign );
_ASSERTE( header.ebpFrame || !header.security );
_ASSERTE( header.ebpFrame || !header.handlers );
_ASSERTE( header.ebpFrame || !header.localloc );
_ASSERTE( header.ebpFrame || !header.editNcontinue); // : Esp frames NYI for EnC
/* Initialize the infoPtr struct */
infoPtr->argSize = header.argCount * 4;
infoPtr->ebpFrame = header.ebpFrame;
infoPtr->interruptible = header.interruptible;
infoPtr->returnKind = (ReturnKind) header.returnKind;
infoPtr->prologSize = header.prologSize;
infoPtr->epilogSize = header.epilogSize;
infoPtr->epilogCnt = header.epilogCount;
infoPtr->epilogEnd = header.epilogAtEnd;
infoPtr->untrackedCnt = header.untrackedCnt;
infoPtr->varPtrTableSize = header.varPtrTableSize;
infoPtr->gsCookieOffset = header.gsCookieOffset;
infoPtr->syncStartOffset = header.syncStartOffset;
infoPtr->syncEndOffset = header.syncEndOffset;
infoPtr->revPInvokeOffset = header.revPInvokeOffset;
infoPtr->doubleAlign = header.doubleAlign;
infoPtr->securityCheck = header.security;
infoPtr->handlers = header.handlers;
infoPtr->localloc = header.localloc;
infoPtr->editNcontinue = header.editNcontinue;
infoPtr->varargs = header.varargs;
infoPtr->profCallbacks = header.profCallbacks;
infoPtr->genericsContext = header.genericsContext;
infoPtr->genericsContextIsMethodDesc = header.genericsContextIsMethodDesc;
infoPtr->isSpeculativeStackWalk = false;
/* Are we within the prolog of the method? */
if (curOffset < infoPtr->prologSize)
{
infoPtr->prologOffs = curOffset;
}
else
{
infoPtr->prologOffs = hdrInfo::NOT_IN_PROLOG;
}
/* Assume we're not in the epilog of the method */
infoPtr->epilogOffs = hdrInfo::NOT_IN_EPILOG;
/* Are we within an epilog of the method? */
if (infoPtr->epilogCnt)
{
unsigned epilogStart;
if (infoPtr->epilogCnt > 1 || !infoPtr->epilogEnd)
{
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xFACE);
#endif
epilogStart = 0;
for (unsigned i = 0; i < infoPtr->epilogCnt; i++)
{
epilogStart += fastDecodeUnsigned(table);
if (curOffset > epilogStart &&
curOffset < epilogStart + infoPtr->epilogSize)
{
infoPtr->epilogOffs = curOffset - epilogStart;
}
}
}
else
{
epilogStart = infoPtr->methodSize - infoPtr->epilogSize;
if (curOffset > epilogStart &&
curOffset < epilogStart + infoPtr->epilogSize)
{
infoPtr->epilogOffs = curOffset - epilogStart;
}
}
infoPtr->syncEpilogStart = epilogStart;
}
unsigned argTabOffset = INVALID_ARGTAB_OFFSET;
if (hasArgTabOffset)
{
argTabOffset = fastDecodeUnsigned(table);
}
infoPtr->argTabOffset = argTabOffset;
size_t frameDwordCount = header.frameSize;
/* Set the rawStackSize to the number of bytes that it bumps ESP */
infoPtr->rawStkSize = (UINT)(frameDwordCount * sizeof(size_t));
/* Calculate the callee saves regMask and adjust stackSize to */
/* include the callee saves register spills */
unsigned savedRegs = RM_NONE;
unsigned savedRegsCount = 0;
if (header.ediSaved)
{
savedRegsCount++;
savedRegs |= RM_EDI;
}
if (header.esiSaved)
{
savedRegsCount++;
savedRegs |= RM_ESI;
}
if (header.ebxSaved)
{
savedRegsCount++;
savedRegs |= RM_EBX;
}
if (header.ebpSaved)
{
savedRegsCount++;
savedRegs |= RM_EBP;
}
infoPtr->savedRegMask = (RegMask)savedRegs;
infoPtr->savedRegsCountExclFP = savedRegsCount;
if (header.ebpFrame || header.doubleAlign)
{
_ASSERTE(header.ebpSaved);
infoPtr->savedRegsCountExclFP = savedRegsCount - 1;
}
frameDwordCount += savedRegsCount;
infoPtr->stackSize = (UINT)(frameDwordCount * sizeof(size_t));
_ASSERTE(infoPtr->gsCookieOffset == INVALID_GS_COOKIE_OFFSET ||
(infoPtr->gsCookieOffset < infoPtr->stackSize) &&
((header.gsCookieOffset % sizeof(void*)) == 0));
return table - PTR_CBYTE(gcInfoToken.Info);
}
/*****************************************************************************/
// We do a "pop eax; jmp eax" to return from a fault or finally handler
const size_t END_FIN_POP_STACK = sizeof(TADDR);
// The offset (in bytes) from EBP for the secutiy object on the stack
inline size_t GetSecurityObjectOffset(hdrInfo * info)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(info->securityCheck && info->ebpFrame);
unsigned position = info->savedRegsCountExclFP +
1;
return position * sizeof(TADDR);
}
inline
size_t GetLocallocSPOffset(hdrInfo * info)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(info->localloc && info->ebpFrame);
unsigned position = info->savedRegsCountExclFP +
info->securityCheck +
1;
return position * sizeof(TADDR);
}
inline
size_t GetParamTypeArgOffset(hdrInfo * info)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE((info->genericsContext || info->handlers) && info->ebpFrame);
unsigned position = info->savedRegsCountExclFP +
info->securityCheck +
info->localloc +
1; // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
return position * sizeof(TADDR);
}
inline size_t GetStartShadowSPSlotsOffset(hdrInfo * info)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(info->handlers && info->ebpFrame);
return GetParamTypeArgOffset(info) +
sizeof(TADDR); // Slot for end-of-last-executed-filter
}
/*****************************************************************************
* Returns the start of the hidden slots for the shadowSP for functions
* with exception handlers. There is one slot per nesting level starting
* near Ebp and is zero-terminated after the active slots.
*/
inline
PTR_TADDR GetFirstBaseSPslotPtr(TADDR ebp, hdrInfo * info)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(info->handlers && info->ebpFrame);
size_t offsetFromEBP = GetStartShadowSPSlotsOffset(info)
+ sizeof(TADDR); // to get to the *start* of the next slot
return PTR_TADDR(ebp - offsetFromEBP);
}
inline size_t GetEndShadowSPSlotsOffset(hdrInfo * info, unsigned maxHandlerNestingLevel)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(info->handlers && info->ebpFrame);
unsigned numberOfShadowSPSlots = maxHandlerNestingLevel +
1 + // For zero-termination
1; // For a filter (which can be active at the same time as a catch/finally handler
return GetStartShadowSPSlotsOffset(info) +
(numberOfShadowSPSlots * sizeof(TADDR));
}
/*****************************************************************************
* returns the base frame pointer corresponding to the target nesting level.
*/
inline
TADDR GetOutermostBaseFP(TADDR ebp, hdrInfo * info)
{
LIMITED_METHOD_DAC_CONTRACT;
// we are not taking into account double alignment. We are
// safe because the jit currently bails on double alignment if there
// are handles or localalloc
_ASSERTE(!info->doubleAlign);
if (info->localloc)
{
// If the function uses localloc we will fetch the ESP from the localloc
// slot.
PTR_TADDR pLocalloc = PTR_TADDR(ebp - GetLocallocSPOffset(info));
return (*pLocalloc);
}
else
{
// Default, go back all the method's local stack size
return ebp - info->stackSize + sizeof(int);
}
}
/*****************************************************************************
*
* For functions with handlers, checks if it is currently in a handler.
* Either of unwindESP or unwindLevel will specify the target nesting level.
* If unwindLevel is specified, info about the funclet at that nesting level
* will be returned. (Use if you are interested in a specific nesting level.)
* If unwindESP is specified, info for nesting level invoked before the stack
* reached unwindESP will be returned. (Use if you have a specific ESP value
* during stack walking.)
*
* *pBaseSP is set to the base SP (base of the stack on entry to
* the current funclet) corresponding to the target nesting level.
* *pNestLevel is set to the nesting level of the target nesting level (useful
* if unwindESP!=IGNORE_VAL
* *pHasInnerFilter will be set to true (only when unwindESP!=IGNORE_VAL) if a filter
* is currently active, but the target nesting level is an outer nesting level.
* *pHadInnerFilter - was the last use of the frame to execute a filter.
* This mainly affects GC lifetime reporting.
*/
enum FrameType
{
FR_NORMAL, // Normal method frame - no exceptions currently active
FR_FILTER, // Frame-let of a filter
FR_HANDLER, // Frame-let of a callable catch/fault/finally
FR_INVALID, // Invalid frame (for speculative stackwalks)
};
enum { IGNORE_VAL = -1 };
FrameType GetHandlerFrameInfo(hdrInfo * info,
TADDR frameEBP,
TADDR unwindESP,
DWORD unwindLevel,
TADDR * pBaseSP = NULL, /* OUT */
DWORD * pNestLevel = NULL, /* OUT */
bool * pHasInnerFilter = NULL, /* OUT */
bool * pHadInnerFilter = NULL) /* OUT */
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
HOST_NOCALLS;
SUPPORTS_DAC;
} CONTRACTL_END;
_ASSERTE(info->ebpFrame && info->handlers);
// One and only one of them should be IGNORE_VAL
_ASSERTE((unwindESP == (TADDR) IGNORE_VAL) !=
(unwindLevel == (DWORD) IGNORE_VAL));
_ASSERTE(pHasInnerFilter == NULL || unwindESP != (TADDR) IGNORE_VAL);
// Many of the conditions that we'd like to assert cannot be asserted in the case that we're
// in the middle of a stackwalk seeded by a profiler, since such seeds can't be trusted
// (profilers are external, untrusted sources). So during profiler walks, we test the condition
// and throw an exception if it's not met. Otherwise, we just assert the condition.
#define FAIL_IF_SPECULATIVE_WALK(condition) \
if (info->isSpeculativeStackWalk) \
{ \
if (!(condition)) \
{ \
return FR_INVALID; \
} \
} \
else \
{ \
_ASSERTE(condition); \
}
PTR_TADDR pFirstBaseSPslot = GetFirstBaseSPslotPtr(frameEBP, info);
TADDR baseSP = GetOutermostBaseFP(frameEBP, info);
bool nonLocalHandlers = false; // Are the funclets invoked by EE (instead of managed code itself)
bool hasInnerFilter = false;
bool hadInnerFilter = false;
/* Get the last non-zero slot >= unwindESP, or lvl curSlotVal ||
(baseSP == curSlotVal && pSlot == pFirstBaseSPslot));
if (curSlotVal == LCL_FINALLY_MARK)
{
// Locally called finally
baseSP -= sizeof(TADDR);
}
else
{
// Is this a funclet we unwound before (can only happen with filters) ?
// If unwindESP is specified, normally we expect it to be the last entry in the shadow slot array.
// Or, if there is a filter, we expect unwindESP to be the second last entry. However, this may
// not be the case in DAC builds. For example, the user can use .cxr in an EH clause to set a
// CONTEXT captured in the try clause. In this case, unwindESP will be the ESP of the parent
// function, but the shadow slot array will contain the SP of the EH clause, which is closer to
// the leaf than the parent method.
if (unwindESP != (TADDR) IGNORE_VAL &&
unwindESP > END_FIN_POP_STACK +
(curSlotVal & ~ICodeManager::SHADOW_SP_BITS))
{
// In non-DAC builds, the only time unwindESP is closer to the root than entries in the shadow
// slot array is when the last entry in the array is for a filter. Also, filters can't have
// nested handlers.
if ((pSlot[0] & ICodeManager::SHADOW_SP_IN_FILTER) &&
(pSlot[-1] == 0) &&
!(baseSP & ICodeManager::SHADOW_SP_IN_FILTER))
{
if (pSlot[0] & ICodeManager::SHADOW_SP_FILTER_DONE)
hadInnerFilter = true;
else
hasInnerFilter = true;
break;
}
else
{
#if defined(DACCESS_COMPILE)
// In DAC builds, this could happen. We just need to bail out of this loop early.
break;
#else // !DACCESS_COMPILE
// In non-DAC builds, this is an error.
FAIL_IF_SPECULATIVE_WALK(FALSE);
#endif // DACCESS_COMPILE
}
}
nonLocalHandlers = true;
baseSP = curSlotVal;
}
}
#endif // WIN64EXCEPTIONS
if (unwindESP != (TADDR) IGNORE_VAL)
{
FAIL_IF_SPECULATIVE_WALK(baseSP >= unwindESP ||
baseSP == unwindESP - sizeof(TADDR)); // About to locally call a finally
if (baseSP < unwindESP) // About to locally call a finally
baseSP = unwindESP;
}
else
{
FAIL_IF_SPECULATIVE_WALK(lvl == unwindLevel); // unwindLevel must be currently active on stack
}
if (pBaseSP)
*pBaseSP = baseSP & ~ICodeManager::SHADOW_SP_BITS;
if (pNestLevel)
{
*pNestLevel = (DWORD)lvl;
}
if (pHasInnerFilter)
*pHasInnerFilter = hasInnerFilter;
if (pHadInnerFilter)
*pHadInnerFilter = hadInnerFilter;
if (baseSP & ICodeManager::SHADOW_SP_IN_FILTER)
{
FAIL_IF_SPECULATIVE_WALK(!hasInnerFilter); // nested filters not allowed
return FR_FILTER;
}
else if (nonLocalHandlers)
{
return FR_HANDLER;
}
else
{
return FR_NORMAL;
}
#undef FAIL_IF_SPECULATIVE_WALK
}
// Returns the number of bytes at the beginning of the stack frame that shouldn't be
// modified by an EnC. This is everything except the space for locals and temporaries.
inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info)
{
WRAPPER_NO_CONTRACT;
// See comment above Compiler::lvaAssignFrameOffsets() in src\jit\il\lclVars.cpp
// for frame layout
// EnC supports increasing the maximum handler nesting level by always
// assuming that the max is MAX_EnC_HANDLER_NESTING_LEVEL. Methods with
// a higher max cannot be updated by EnC
// Take the offset (from EBP) of the last slot of the header, plus one for the EBP slot itself
// to get the total size of the header.
return sizeof(TADDR) +
GetEndShadowSPSlotsOffset(info, MAX_EnC_HANDLER_NESTING_LEVEL);
}
#endif // !USE_GC_INFO_DECODER
#ifndef DACCESS_COMPILE
#ifndef WIN64EXCEPTIONS
/*****************************************************************************
*
* Setup context to enter an exception handler (a 'catch' block).
* This is the last chance for the runtime support to do fixups in
* the context before execution continues inside a filter, catch handler,
* or finally.
*/
void EECodeManager::FixContext( ContextType ctxType,
EHContext *ctx,
EECodeInfo *pCodeInfo,
DWORD dwRelOffset,
DWORD nestingLevel,
OBJECTREF thrownObject,
CodeManState *pState,
size_t ** ppShadowSP,
size_t ** ppEndRegion)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE((ctxType == FINALLY_CONTEXT) == (thrownObject == NULL));
_ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
/* Extract the necessary information from the info block header */
stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(),
dwRelOffset,
&stateBuf->hdrInfoBody);
pState->dwIsSet = 1;
#ifdef _DEBUG
if (trFixContext) {
printf("FixContext [%s][%s] for %s.%s: ",
stateBuf->hdrInfoBody.ebpFrame?"ebp":" ",
stateBuf->hdrInfoBody.interruptible?"int":" ",
"UnknownClass","UnknownMethod");
fflush(stdout);
}
#endif
/* make sure that we have an ebp stack frame */
_ASSERTE(stateBuf->hdrInfoBody.ebpFrame);
_ASSERTE(stateBuf->hdrInfoBody.handlers); // @TODO : This will alway be set. Remove it
TADDR baseSP;
GetHandlerFrameInfo(&stateBuf->hdrInfoBody, ctx->Ebp,
ctxType == FILTER_CONTEXT ? ctx->Esp : IGNORE_VAL,
ctxType == FILTER_CONTEXT ? (DWORD) IGNORE_VAL : nestingLevel,
&baseSP,
&nestingLevel);
_ASSERTE((size_t)ctx->Ebp >= baseSP);
_ASSERTE(baseSP >= (size_t)ctx->Esp);
ctx->Esp = (DWORD)baseSP;
// EE will write Esp to **pShadowSP before jumping to handler
PTR_TADDR pBaseSPslots =
GetFirstBaseSPslotPtr(ctx->Ebp, &stateBuf->hdrInfoBody);
*ppShadowSP = (size_t *)&pBaseSPslots[-(int) nestingLevel ];
pBaseSPslots[-(int)(nestingLevel+1)] = 0; // Zero out the next slot
// EE will write the end offset of the filter
if (ctxType == FILTER_CONTEXT)
*ppEndRegion = (size_t *)pBaseSPslots + 1;
/* This is just a simple assigment of throwObject to ctx->Eax,
just pretend the cast goo isn't there.
*/
*((OBJECTREF*)&(ctx->Eax)) = thrownObject;
}
#endif // !WIN64EXCEPTIONS
/*****************************************************************************/
bool VarIsInReg(ICorDebugInfo::VarLoc varLoc)
{
LIMITED_METHOD_CONTRACT;
switch(varLoc.vlType)
{
case ICorDebugInfo::VLT_REG:
case ICorDebugInfo::VLT_REG_REG:
case ICorDebugInfo::VLT_REG_STK:
return true;
default:
return false;
}
}
#ifdef EnC_SUPPORTED
/*****************************************************************************
* Last chance for the runtime support to do fixups in the context
* before execution continues inside an EnC updated function.
* It also adjusts ESP and munges on the stack. So the caller has to make
* sure that that stack region isnt needed (by doing a localloc)
* Also, if this returns EnC_FAIL, we should not have munged the
* context ie. transcated commit
* The plan of attack is:
* 1) Error checking up front. If we get through here, everything
* else should work
* 2) Get all the info about current variables, registers, etc
* 3) zero out the stack frame - this'll initialize _all_ variables
* 4) Put the variables from step 3 into their new locations.
*
* Note that while we use the ShuffleVariablesGet/Set methods, they don't
* have any info/logic that's internal to the runtime: another codemanger
* could easily duplicate what they do, which is why we're calling into them.
*/
HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
EECodeInfo * pOldCodeInfo,
const ICorDebugInfo::NativeVarInfo * oldMethodVars,
SIZE_T oldMethodVarsCount,
EECodeInfo * pNewCodeInfo,
const ICorDebugInfo::NativeVarInfo * newMethodVars,
SIZE_T newMethodVarsCount)
{
CONTRACTL {
DISABLED(NOTHROW);
DISABLED(GC_NOTRIGGER);
} CONTRACTL_END;
HRESULT hr = S_OK;
// Grab a copy of the context before the EnC update.
T_CONTEXT oldCtx = *pCtx;
#if defined(_TARGET_X86_)
LOG((LF_CORDB, LL_INFO100, "EECM::FixContextForEnC\n"));
/* Extract the necessary information from the info block header */
hdrInfo oldInfo, newInfo;
DecodeGCHdrInfo(pOldCodeInfo->GetGCInfoToken(),
pOldCodeInfo->GetRelOffset(),
&oldInfo);
DecodeGCHdrInfo(pNewCodeInfo->GetGCInfoToken(),
pNewCodeInfo->GetRelOffset(),
&newInfo);
//1) Error checking up front. If we get through here, everything
// else should work
if (!oldInfo.editNcontinue || !newInfo.editNcontinue) {
LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC EnC_INFOLESS_METHOD\n"));
return CORDBG_E_ENC_INFOLESS_METHOD;
}
if (!oldInfo.ebpFrame || !newInfo.ebpFrame) {
LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC Esp frames NYI\n"));
return E_FAIL; // Esp frames NYI
}
if (pCtx->Esp != pCtx->Ebp - oldInfo.stackSize + sizeof(DWORD)) {
LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack should be empty\n"));
return E_FAIL; // stack should be empty - @TODO : Barring localloc
}
if (oldInfo.handlers)
{
bool hasInnerFilter;
TADDR baseSP;
FrameType frameType = GetHandlerFrameInfo(&oldInfo, pCtx->Ebp,
pCtx->Esp, IGNORE_VAL,
&baseSP, NULL, &hasInnerFilter);
_ASSERTE(frameType != FR_INVALID);
_ASSERTE(!hasInnerFilter); // FixContextForEnC() is called for bottommost funclet
// If the method is in a fuclet, and if the framesize grows, we are in trouble.
if (frameType != FR_NORMAL)
{
/* @TODO : What if the new method offset is in a fuclet,
and the old is not, or the nesting level changed, etc */
if (oldInfo.stackSize != newInfo.stackSize) {
LOG((LF_ENC, LL_INFO100, "**Error** EECM::FixContextForEnC stack size mismatch\n"));
return CORDBG_E_ENC_IN_FUNCLET;
}
}
}
/* @TODO: Check if we have grown out of space for locals, in the face of localloc */
_ASSERTE(!oldInfo.localloc && !newInfo.localloc);
// Always reserve space for the securityCheck slot
_ASSERTE(oldInfo.securityCheck && newInfo.securityCheck);
// @TODO: If nesting level grows above the MAX_EnC_HANDLER_NESTING_LEVEL,
// we should return EnC_NESTED_HANLDERS
_ASSERTE(oldInfo.handlers && newInfo.handlers);
LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: Checks out\n"));
#elif defined(_TARGET_AMD64_)
// Strategy for zeroing out the frame on x64:
//
// The stack frame looks like this (stack grows up)
//
// =======================================
// <--- RSP == RBP (invariant: localalloc disallowed before remap)
// Arguments for next call (if there is one)
// PSPSym (optional)
// JIT temporaries (if any)
// Security object (if any)
// Local variables (if any)
// ---------------------------------------
// Frame header (stuff we must preserve, such as bool for synchronized
// methods, saved RBP, etc.)
// Return address (also included in frame header)
// ---------------------------------------
// Arguments for this frame (that's getting remapped). Will naturally be preserved
// since fixed-frame size doesn't include this.
// =======================================
//
// Goal: Zero out everything AFTER (above) frame header.
//
// How do we find this stuff?
//
// EECodeInfo::GetFixedStackSize() gives us the full size from the top ("Arguments
// for next call") all the way down to and including Return Address.
//
// GetSizeOfEditAndContinuePreservedArea() gives us the size in bytes of the
// frame header at the bottom.
//
// So we start at RSP, and zero out:
// GetFixedStackSize() - GetSizeOfEditAndContinuePreservedArea() bytes.
//
// We'll need to restore PSPSym; location gotten from GCInfo.
// We'll need to copy security object; location gotten from GCInfo.
// GCInfo for old method
GcInfoDecoder oldGcDecoder(
pOldCodeInfo->GetGCInfoToken(),
GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE),
0 // Instruction offset (not needed)
);
// GCInfo for new method
GcInfoDecoder newGcDecoder(
pNewCodeInfo->GetGCInfoToken(),
GcInfoDecoderFlags(DECODE_SECURITY_OBJECT | DECODE_PSP_SYM | DECODE_EDIT_AND_CONTINUE),
0 // Instruction offset (not needed)
);
UINT32 oldSizeOfPreservedArea = oldGcDecoder.GetSizeOfEditAndContinuePreservedArea();
UINT32 newSizeOfPreservedArea = newGcDecoder.GetSizeOfEditAndContinuePreservedArea();
// This ensures the JIT generated EnC compliant code.
if ((oldSizeOfPreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA) ||
(newSizeOfPreservedArea == NO_SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA))
{
_ASSERTE(!"FixContextForEnC called on a non-EnC-compliant method frame");
return CORDBG_E_ENC_INFOLESS_METHOD;
}
// JIT is required to emit frame register for EnC-compliant code
_ASSERTE(pOldCodeInfo->HasFrameRegister());
_ASSERTE(pNewCodeInfo->HasFrameRegister());
TADDR oldStackBase = GetSP(&oldCtx);
// This verifies no localallocs were used in the old method. (RBP == RSP for
// EnC-compliant x64 code.)
if (oldStackBase != oldCtx.Rbp)
return E_FAIL;
// EnC remap inside handlers is not supported
if (pOldCodeInfo->IsFunclet() || pNewCodeInfo->IsFunclet())
return CORDBG_E_ENC_IN_FUNCLET;
if (oldSizeOfPreservedArea != newSizeOfPreservedArea)
{
_ASSERTE(!"FixContextForEnC called with method whose frame header size changed from old to new version.");
return E_FAIL;
}
// Note: we cannot assert anything about the relationship between oldFixedStackSize
// and newFixedStackSize. It's possible the edited frame grows (new locals) or
// shrinks (less temporaries).
DWORD oldFixedStackSize = pOldCodeInfo->GetFixedStackSize();
DWORD newFixedStackSize = pNewCodeInfo->GetFixedStackSize();
TADDR callerSP = oldStackBase + oldFixedStackSize;
// If the old code saved a security object, store the object's reference now.
OBJECTREF securityObject = NULL;
INT32 nOldSecurityObjectStackSlot = oldGcDecoder.GetSecurityObjectStackSlot();
if (nOldSecurityObjectStackSlot != NO_SECURITY_OBJECT)
{
securityObject = ObjectToOBJECTREF(*PTR_PTR_Object(callerSP + nOldSecurityObjectStackSlot));
}
#ifdef _DEBUG
// If the old method has a PSPSym, then its value should == FP
INT32 nOldPspSymStackSlot = oldGcDecoder.GetPSPSymStackSlot();
if (nOldPspSymStackSlot != NO_PSP_SYM)
{
// Read the PSP.
TADDR oldPSP = *PTR_TADDR(oldStackBase + nOldPspSymStackSlot);
// Now we're set up to assert that PSPSym's value == FP
_ASSERTE(oldPSP == GetFP(&oldCtx));
}
#endif // _DEBUG
#else
PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
#endif
// 2) Get all the info about current variables, registers, etc
const ICorDebugInfo::NativeVarInfo * pOldVar;
// sorted by varNumber
ICorDebugInfo::NativeVarInfo * oldMethodVarsSorted = NULL;
ICorDebugInfo::NativeVarInfo * oldMethodVarsSortedBase = NULL;
ICorDebugInfo::NativeVarInfo *newMethodVarsSorted = NULL;
ICorDebugInfo::NativeVarInfo *newMethodVarsSortedBase = NULL;
SIZE_T *rgVal1 = NULL;
SIZE_T *rgVal2 = NULL;
{
SIZE_T local;
// We'll need to sort the old native var info by variable number, since the
// order of them isn't necc. the same. We'll use the number as the key.
// We will assume we may have hidden arguments (which have negative values as the index)
unsigned oldNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM);
for (pOldVar = oldMethodVars, local = 0;
local < oldMethodVarsCount;
local++, pOldVar++)
{
DWORD varNumber = pOldVar->varNumber;
if (signed(varNumber) >= 0)
{
// This is an explicit (not special) var, so add its varNumber + 1 to our
// max count ("+1" because varNumber is zero-based).
oldNumVars = max(oldNumVars, unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) + varNumber + 1);
}
}
oldMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[oldNumVars];
if (!oldMethodVarsSortedBase)
{
hr = E_FAIL;
goto ErrExit;
}
oldMethodVarsSorted = oldMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM);
memset((void *)oldMethodVarsSortedBase, 0, oldNumVars * sizeof(ICorDebugInfo::NativeVarInfo));
for (local = 0; local < oldNumVars;local++)
oldMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID;
BYTE **rgVCs = NULL;
DWORD oldMethodOffset = pOldCodeInfo->GetRelOffset();
for (pOldVar = oldMethodVars, local = 0;
local < oldMethodVarsCount;
local++, pOldVar++)
{
DWORD varNumber = pOldVar->varNumber;
_ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars);
// Only care about old local variables alive at oldMethodOffset
if (pOldVar->startOffset <= oldMethodOffset &&
pOldVar->endOffset > oldMethodOffset)
{
oldMethodVarsSorted[varNumber] = *pOldVar;
}
}
// 3) Next sort the new var info by varNumber. We want to do this here, since
// we're allocating memory (which may fail) - do this before going to step 2
// First, count the new vars the same way we did the old vars above.
const ICorDebugInfo::NativeVarInfo * pNewVar;
unsigned newNumVars = unsigned(-ICorDebugInfo::UNKNOWN_ILNUM);
for (pNewVar = newMethodVars, local = 0;
local < newMethodVarsCount;
local++, pNewVar++)
{
DWORD varNumber = pNewVar->varNumber;
if (signed(varNumber) >= 0)
{
// This is an explicit (not special) var, so add its varNumber + 1 to our
// max count ("+1" because varNumber is zero-based).
newNumVars = max(newNumVars, unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) + varNumber + 1);
}
}
// sorted by varNumber
newMethodVarsSortedBase = new (nothrow) ICorDebugInfo::NativeVarInfo[newNumVars];
if (!newMethodVarsSortedBase)
{
hr = E_FAIL;
goto ErrExit;
}
newMethodVarsSorted = newMethodVarsSortedBase + (-ICorDebugInfo::UNKNOWN_ILNUM);
memset(newMethodVarsSortedBase, 0, newNumVars * sizeof(ICorDebugInfo::NativeVarInfo));
for (local = 0; local < newNumVars;local++)
newMethodVarsSortedBase[local].loc.vlType = ICorDebugInfo::VLT_INVALID;
DWORD newMethodOffset = pNewCodeInfo->GetRelOffset();
for (pNewVar = newMethodVars, local = 0;
local < newMethodVarsCount;
local++, pNewVar++)
{
DWORD varNumber = pNewVar->varNumber;
_ASSERTE(varNumber + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < newNumVars);
// Only care about new local variables alive at newMethodOffset
if (pNewVar->startOffset <= newMethodOffset &&
pNewVar->endOffset > newMethodOffset)
{
newMethodVarsSorted[varNumber] = *pNewVar;
}
}
_ASSERTE(newNumVars >= oldNumVars ||
!"Not allowed to reduce the number of locals between versions!");
LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: gathered info!\n"));
rgVal1 = new (nothrow) SIZE_T[newNumVars];
if (rgVal1 == NULL)
{
hr = E_FAIL;
goto ErrExit;
}
rgVal2 = new (nothrow) SIZE_T[newNumVars];
if (rgVal2 == NULL)
{
hr = E_FAIL;
goto ErrExit;
}
// 4) Next we'll zero them out, so any variables that aren't in scope
// in the old method, but are in scope in the new, will have the
// default, zero, value.
memset(rgVal1, 0, sizeof(SIZE_T) * newNumVars);
memset(rgVal2, 0, sizeof(SIZE_T) * newNumVars);
unsigned varsToGet = (oldNumVars > newNumVars) ? newNumVars
: oldNumVars;
// 2) Get all the info about current variables, registers, etc.
hr = g_pDebugInterface->GetVariablesFromOffset(pOldCodeInfo->GetMethodDesc(),
varsToGet,
oldMethodVarsSortedBase,
oldMethodOffset,
&oldCtx,
rgVal1,
rgVal2,
newNumVars,
&rgVCs);
if (FAILED(hr))
{
goto ErrExit;
}
LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: got vars!\n"));
/*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
* IMPORTANT : Once we start munging on the context, we cannot return
* EnC_FAIL, as this should be a transacted commit,
**=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*/
#if defined(_TARGET_X86_)
// Zero out all the registers as some may hold new variables.
pCtx->Eax = pCtx->Ecx = pCtx->Edx = pCtx->Ebx =
pCtx->Esi = pCtx->Edi = 0;
// 3) zero out the stack frame - this'll initialize _all_ variables
/*-------------------------------------------------------------------------
* Adjust the stack height
*/
pCtx->Esp -= (newInfo.stackSize - oldInfo.stackSize);
// Zero-init the local and tempory section of new stack frame being careful to avoid
// touching anything in the frame header.
// This is necessary to ensure that any JIT temporaries in the old version can't be mistaken
// for ObjRefs now.
size_t frameHeaderSize = GetSizeOfFrameHeaderForEnC( &newInfo );
_ASSERTE( frameHeaderSize <= oldInfo.stackSize );
_ASSERTE( GetSizeOfFrameHeaderForEnC( &oldInfo ) == frameHeaderSize );
#elif defined(_TARGET_AMD64_)
// Next few statements zero out all registers that may end up holding new variables.
// volatile int registers (JIT may use these to enregister variables)
pCtx->Rax = pCtx->Rcx = pCtx->Rdx = pCtx->R8 = pCtx->R9 = pCtx->R10 = pCtx->R11 = 0;
// volatile float registers
pCtx->Xmm1.High = pCtx->Xmm1.Low = 0;
pCtx->Xmm2.High = pCtx->Xmm2.Low = 0;
pCtx->Xmm3.High = pCtx->Xmm3.Low = 0;
pCtx->Xmm4.High = pCtx->Xmm4.Low = 0;
pCtx->Xmm5.High = pCtx->Xmm5.Low = 0;
// Any saved nonvolatile registers should also be zeroed out, but there are none
// in EnC-compliant x64 code. Yes, you read that right. Registers like RDI, RSI,
// RBX, etc., which are often saved in the prolog of non-EnC code are NOT saved in
// EnC code. EnC code instead just agrees never to use those registers so they
// remain pristine for the caller (except RBP, which is considered part of the frame
// header, and is thus not zeroed out by us).
// 3) zero out the stack frame - this'll initialize _all_ variables
/*-------------------------------------------------------------------------
* Adjust the stack height
*/
TADDR newStackBase = callerSP - newFixedStackSize;
SetSP(pCtx, newStackBase);
// We want to zero-out everything pushed after the frame header. This way we'll zero
// out locals (both old & new) and temporaries. This is necessary to ensure that any
// JIT temporaries in the old version can't be mistaken for ObjRefs now. (I am told
// this last point is less of an issue on x64 as it is on x86, but zeroing out the
// temporaries is still the cleanest, most robust way to go.)
size_t frameHeaderSize = newSizeOfPreservedArea;
_ASSERTE(frameHeaderSize <= oldFixedStackSize);
_ASSERTE(frameHeaderSize <= newFixedStackSize);
// For EnC-compliant x64 code, Rbp == Rsp. Since Rsp changed above, update Rbp now
pCtx->Rbp = newStackBase;
#else // !X86, !AMD64
PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
#endif
// Perform some debug-only sanity checks on stack variables. Some checks are
// performed differently between X86/AMD64.
#ifdef _DEBUG
for( unsigned i = 0; i < newNumVars; i++ )
{
// Make sure that stack variables existing in both old and new methods did not
// move. This matters if the address of a local is used in the remapped method.
// For example:
//
// static unsafe void Main(string[] args)
// {
// int x;
// int* p = &x;
// <- Edit made here - cannot move address of x
// *p = 5;
// }
//
if ((i + unsigned(-ICorDebugInfo::UNKNOWN_ILNUM) < oldNumVars) && // Does variable exist in old method?
(oldMethodVarsSorted[i].loc.vlType == ICorDebugInfo::VLT_STK) && // Is the variable on the stack?
(newMethodVarsSorted[i].loc.vlType == ICorDebugInfo::VLT_STK))
{
SIZE_T * pOldVarStackLocation = NativeVarStackAddr(oldMethodVarsSorted[i].loc, &oldCtx);
SIZE_T * pNewVarStackLocation = NativeVarStackAddr(newMethodVarsSorted[i].loc, pCtx);
_ASSERTE(pOldVarStackLocation == pNewVarStackLocation);
}
// Sanity-check that the range we're clearing contains all of the stack variables
#if defined(_TARGET_X86_)
const ICorDebugInfo::VarLoc &varLoc = newMethodVarsSortedBase[i].loc;
if( varLoc.vlType == ICorDebugInfo::VLT_STK )
{
// This is an EBP frame, all stack variables should be EBP relative
_ASSERTE( varLoc.vlStk.vlsBaseReg == ICorDebugInfo::REGNUM_EBP );
// Generic special args may show up as locals with positive offset from EBP, so skip them
if( varLoc.vlStk.vlsOffset <= 0 )
{
// Normal locals must occur after the header on the stack
_ASSERTE( unsigned(-varLoc.vlStk.vlsOffset) >= frameHeaderSize );
// Value must occur before the top of the stack
_ASSERTE( unsigned(-varLoc.vlStk.vlsOffset) < newInfo.stackSize );
}
// Ideally we'd like to verify that the stack locals (if any) start at exactly the end
// of the header. However, we can't easily determine the size of value classes here,
// and so (since the stack grows towards 0) can't easily determine where the end of
// the local lies.
}
#elif defined (_TARGET_AMD64_)
switch(newMethodVarsSortedBase[i].loc.vlType)
{
default:
// No validation here for non-stack locals
break;
case ICorDebugInfo::VLT_STK_BYREF:
{
// For byrefs, verify that the ptr will be zeroed out
SIZE_T regOffs = GetRegOffsInCONTEXT(newMethodVarsSortedBase[i].loc.vlStk.vlsBaseReg);
TADDR baseReg = *(TADDR *)(regOffs + (BYTE*)pCtx);
TADDR addrOfPtr = baseReg + newMethodVarsSortedBase[i].loc.vlStk.vlsOffset;
_ASSERTE(
// The ref must exist in the portion we'll zero-out
(
(newStackBase <= addrOfPtr) &&
(addrOfPtr < newStackBase + (newFixedStackSize - frameHeaderSize))
) ||
// OR in the caller's frame (for parameters)
(addrOfPtr >= newStackBase + newFixedStackSize));
// Deliberately fall through, so that we also verify that the value that the ptr
// points to will be zeroed out
// ...
}
case ICorDebugInfo::VLT_STK:
case ICorDebugInfo::VLT_STK2:
case ICorDebugInfo::VLT_REG_STK:
case ICorDebugInfo::VLT_STK_REG:
SIZE_T * pVarStackLocation = NativeVarStackAddr(newMethodVarsSortedBase[i].loc, pCtx);
_ASSERTE (pVarStackLocation != NULL);
_ASSERTE(
// The value must exist in the portion we'll zero-out
(
(newStackBase <= (TADDR) pVarStackLocation) &&
((TADDR) pVarStackLocation < newStackBase + (newFixedStackSize - frameHeaderSize))
) ||
// OR in the caller's frame (for parameters)
((TADDR) pVarStackLocation >= newStackBase + newFixedStackSize));
break;
}
#else // !X86, !X64
PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
#endif
}
#endif // _DEBUG
// Clear the local and temporary stack space
#if defined (_TARGET_X86_)
memset((void*)(size_t)(pCtx->Esp), 0, newInfo.stackSize - frameHeaderSize );
#elif defined (_TARGET_AMD64_)
memset((void*)newStackBase, 0, newFixedStackSize - frameHeaderSize);
// On AMD64, after zeroing out the stack, restore the security object and PSPSym...
// There is no relationship we can guarantee between the old code having a security
// object and the new code having a security object. If the new code does have a
// security object, then we copy over the old security object's reference if there
// was one (else we copy over NULL, which is fine). If the new code doesn't have a
// security object, we do nothing.
INT32 nNewSecurityObjectStackSlot = newGcDecoder.GetSecurityObjectStackSlot();
if (nNewSecurityObjectStackSlot != NO_SECURITY_OBJECT)
{
*PTR_PTR_Object(callerSP + nNewSecurityObjectStackSlot) = OBJECTREFToObject(securityObject);
}
// Restore PSPSym for the new function. Its value should be set to our new FP. But
// first, we gotta find PSPSym's location on the stack
INT32 nNewPspSymStackSlot = newGcDecoder.GetPSPSymStackSlot();
if (nNewPspSymStackSlot != NO_PSP_SYM)
{
*PTR_TADDR(newStackBase + nNewPspSymStackSlot) = GetFP(pCtx);
}
#else // !X86, !X64
PORTABILITY_ASSERT("Edit-and-continue not enabled on this platform.");
#endif
// 4) Put the variables from step 3 into their new locations.
LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: set vars!\n"));
// Move the old variables into their new places.
hr = g_pDebugInterface->SetVariablesAtOffset(pNewCodeInfo->GetMethodDesc(),
newNumVars,
newMethodVarsSortedBase,
newMethodOffset,
pCtx, // place them into the new context
rgVal1,
rgVal2,
rgVCs);
/*-----------------------------------------------------------------------*/
}
ErrExit:
if (oldMethodVarsSortedBase)
delete[] oldMethodVarsSortedBase;
if (newMethodVarsSortedBase)
delete[] newMethodVarsSortedBase;
if (rgVal1 != NULL)
delete[] rgVal1;
if (rgVal2 != NULL)
delete[] rgVal2;
LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: exiting!\n"));
return hr;
}
#endif // !EnC_SUPPORTED
#endif // #ifndef DACCESS_COMPILE
#ifdef USE_GC_INFO_DECODER
/*****************************************************************************
*
* Is the function currently at a "GC safe point" ?
*/
bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo,
DWORD dwRelOffset)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
DECODE_INTERRUPTIBILITY,
dwRelOffset
);
return gcInfoDecoder.IsInterruptible();
}
#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
bool EECodeManager::HasTailCalls( EECodeInfo *pCodeInfo)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
DECODE_HAS_TAILCALLS,
0
);
return gcInfoDecoder.HasTailCalls();
}
#endif // _TARGET_ARM_ || _TARGET_ARM64_
#if defined(_TARGET_AMD64_) && defined(_DEBUG)
struct FindEndOfLastInterruptibleRegionState
{
unsigned curOffset;
unsigned endOffset;
unsigned lastRangeOffset;
};
bool FindEndOfLastInterruptibleRegionCB (
UINT32 startOffset,
UINT32 stopOffset,
LPVOID hCallback)
{
FindEndOfLastInterruptibleRegionState *pState = (FindEndOfLastInterruptibleRegionState*)hCallback;
//
// If the current range doesn't overlap the given range, keep searching.
//
if ( startOffset >= pState->endOffset
|| stopOffset < pState->curOffset)
{
return false;
}
//
// If the range overlaps the end, then the last point is the end.
//
if ( stopOffset > pState->endOffset
/*&& startOffset < pState->endOffset*/)
{
// The ranges should be sorted in increasing order.
CONSISTENCY_CHECK(startOffset >= pState->lastRangeOffset);
pState->lastRangeOffset = pState->endOffset;
return true;
}
//
// See if the end of this range is the closet to the end that we've found
// so far.
//
if (stopOffset > pState->lastRangeOffset)
pState->lastRangeOffset = stopOffset;
return false;
}
/*
Locates the end of the last interruptible region in the given code range.
Returns 0 if the entire range is uninterruptible. Returns the end point
if the entire range is interruptible.
*/
unsigned EECodeManager::FindEndOfLastInterruptibleRegion(unsigned curOffset,
unsigned endOffset,
GCInfoToken gcInfoToken)
{
#ifndef DACCESS_COMPILE
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
DECODE_FOR_RANGES_CALLBACK
);
FindEndOfLastInterruptibleRegionState state;
state.curOffset = curOffset;
state.endOffset = endOffset;
state.lastRangeOffset = 0;
gcInfoDecoder.EnumerateInterruptibleRanges(&FindEndOfLastInterruptibleRegionCB, &state);
return state.lastRangeOffset;
#else
DacNotImpl();
return NULL;
#endif // #ifndef DACCESS_COMPILE
}
#endif // _TARGET_AMD64_ && _DEBUG
#else // !USE_GC_INFO_DECODER
/*****************************************************************************
*
* Is the function currently at a "GC safe point" ?
*/
bool EECodeManager::IsGcSafe( EECodeInfo *pCodeInfo,
DWORD dwRelOffset)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
hdrInfo info;
BYTE * table;
/* Extract the necessary information from the info block header */
table = (BYTE *)DecodeGCHdrInfo(pCodeInfo->GetGCInfoToken(),
dwRelOffset,
&info);
/* workaround: prevent interruption within prolog/epilog */
if (info.prologOffs != hdrInfo::NOT_IN_PROLOG || info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
return false;
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
#endif
return (info.interruptible);
}
/*****************************************************************************/
static
PTR_CBYTE skipToArgReg(const hdrInfo& info, PTR_CBYTE table)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
#ifdef _DEBUG
PTR_CBYTE tableStart = table;
#else
if (info.argTabOffset != INVALID_ARGTAB_OFFSET)
{
return table + info.argTabOffset;
}
#endif
unsigned count;
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
#endif
/* Skip over the untracked frame variable table */
count = info.untrackedCnt;
while (count-- > 0) {
fastSkipSigned(table);
}
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xCAFE);
#endif
/* Skip over the frame variable lifetime table */
count = info.varPtrTableSize;
while (count-- > 0) {
fastSkipUnsigned(table); fastSkipUnsigned(table); fastSkipUnsigned(table);
}
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *) == 0xBABE);
#endif
#ifdef _DEBUG
if (info.argTabOffset != INVALID_ARGTAB_OFFSET)
{
CONSISTENCY_CHECK_MSGF((info.argTabOffset == (unsigned) (table - tableStart)),
("table = %p, tableStart = %p, info.argTabOffset = %d", table, tableStart, info.argTabOffset));
}
#endif
return table;
}
/*****************************************************************************/
#define regNumToMask(regNum) RegMask(1<GetEbpLocation();
case REGI_EBX: return pContext->GetEbxLocation();
case REGI_ESI: return pContext->GetEsiLocation();
case REGI_EDI: return pContext->GetEdiLocation();
default: _ASSERTE(!"bad info.thisPtrResult"); return NULL;
}
}
/*****************************************************************************
These functions converts the bits in the GC encoding to RegMask
*/
inline
RegMask convertCalleeSavedRegsMask(unsigned inMask) // EBP,EBX,ESI,EDI
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE((inMask & 0x0F) == inMask);
unsigned outMask = RM_NONE;
if (inMask & 0x1) outMask |= RM_EDI;
if (inMask & 0x2) outMask |= RM_ESI;
if (inMask & 0x4) outMask |= RM_EBX;
if (inMask & 0x8) outMask |= RM_EBP;
return (RegMask) outMask;
}
inline
RegMask convertAllRegsMask(unsigned inMask) // EAX,ECX,EDX,EBX, EBP,ESI,EDI
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE((inMask & 0xEF) == inMask);
unsigned outMask = RM_NONE;
if (inMask & 0x01) outMask |= RM_EAX;
if (inMask & 0x02) outMask |= RM_ECX;
if (inMask & 0x04) outMask |= RM_EDX;
if (inMask & 0x08) outMask |= RM_EBX;
if (inMask & 0x20) outMask |= RM_EBP;
if (inMask & 0x40) outMask |= RM_ESI;
if (inMask & 0x80) outMask |= RM_EDI;
return (RegMask)outMask;
}
/*****************************************************************************
* scan the register argument table for the not fully interruptible case.
this function is called to find all live objects (pushed arguments)
and to get the stack base for EBP-less methods.
NOTE: If info->argTabResult is NULL, info->argHnumResult indicates
how many bits in argMask are valid
If info->argTabResult is non-NULL, then the argMask field does
not fit in 32-bits and the value in argMask meaningless.
Instead argHnum specifies the number of (variable-length) elements
in the array, and argTabBytes specifies the total byte size of the
array. [ Note this is an extremely rare case ]
*/
#ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:21000) // Suppress PREFast warning about overly large function
#endif
static
unsigned scanArgRegTable(PTR_CBYTE table,
unsigned curOffs,
hdrInfo * info)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
regNum thisPtrReg = REGI_NA;
#ifdef _DEBUG
bool isCall = false;
#endif
unsigned regMask = 0; // EBP,EBX,ESI,EDI
unsigned argMask = 0;
unsigned argHnum = 0;
PTR_CBYTE argTab = 0;
unsigned argTabBytes = 0;
unsigned stackDepth = 0;
unsigned iregMask = 0; // EBP,EBX,ESI,EDI
unsigned iargMask = 0;
unsigned iptrMask = 0;
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
#endif
unsigned scanOffs = 0;
_ASSERTE(scanOffs <= info->methodSize);
if (info->ebpFrame) {
/*
Encoding table for methods with an EBP frame and
that are not fully interruptible
The encoding used is as follows:
this pointer encodings:
01000000 this pointer in EBX
00100000 this pointer in ESI
00010000 this pointer in EDI
tiny encoding:
0bsdDDDD
requires code delta < 16 (4-bits)
requires pushed argmask == 0
where DDDD is code delta
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
small encoding:
1DDDDDDD bsdAAAAA
requires code delta < 120 (7-bits)
requires pushed argmask < 64 (5-bits)
where DDDDDDD is code delta
AAAAA is the pushed args mask
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
medium encoding
0xFD aaaaaaaa AAAAdddd bseDDDDD
requires code delta < 0x1000000000 (9-bits)
requires pushed argmask < 0x1000000000000 (12-bits)
where DDDDD is the upper 5-bits of the code delta
dddd is the low 4-bits of the code delta
AAAA is the upper 4-bits of the pushed arg mask
aaaaaaaa is the low 8-bits of the pushed arg mask
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
e indicates that register EDI is a live pointer
medium encoding with interior pointers
0xF9 DDDDDDDD bsdAAAAAA iiiIIIII
requires code delta < (8-bits)
requires pushed argmask < (5-bits)
where DDDDDDD is the code delta
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
AAAAA is the pushed arg mask
iii indicates that EBX,EDI,ESI are interior pointers
IIIII indicates that bits is the arg mask are interior
pointers
large encoding
0xFE [0BSD0bsd][32-bit code delta][32-bit argMask]
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
B indicates that register EBX is an interior pointer
S indicates that register ESI is an interior pointer
D indicates that register EDI is an interior pointer
requires pushed argmask < 32-bits
large encoding with interior pointers
0xFA [0BSD0bsd][32-bit code delta][32-bit argMask][32-bit interior pointer mask]
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
B indicates that register EBX is an interior pointer
S indicates that register ESI is an interior pointer
D indicates that register EDI is an interior pointer
requires pushed argmask < 32-bits
requires pushed iArgmask < 32-bits
huge encoding This is the only encoding that supports
a pushed argmask which is greater than
32-bits.
0xFB [0BSD0bsd][32-bit code delta]
[32-bit table count][32-bit table size]
[pushed ptr offsets table...]
b indicates that register EBX is a live pointer
s indicates that register ESI is a live pointer
d indicates that register EDI is a live pointer
B indicates that register EBX is an interior pointer
S indicates that register ESI is an interior pointer
D indicates that register EDI is an interior pointer
the list count is the number of entries in the list
the list size gives the byte-lenght of the list
the offsets in the list are variable-length
*/
while (scanOffs < curOffs)
{
iregMask = 0;
iargMask = 0;
argTab = NULL;
#ifdef _DEBUG
isCall = true;
#endif
/* Get the next byte and check for a 'special' entry */
unsigned encType = *table++;
#if defined(DACCESS_COMPILE)
// In this scenario, it is invalid to have a zero byte in the GC info encoding (refer to the
// comments above). At least one bit has to be set. For example, a byte can represent which
// register is the "this" pointer, and this byte has to be 0x10, 0x20, or 0x40. Having a zero
// byte indicates there is most likely some sort of DAC error, and it may lead to problems such as
// infinite loops. So we bail out early instead.
if (encType == 0)
{
DacError(CORDBG_E_TARGET_INCONSISTENT);
UNREACHABLE();
}
#endif // DACCESS_COMPILE
switch (encType)
{
unsigned val, nxt;
default:
/* A tiny or small call entry */
val = encType;
if ((val & 0x80) == 0x00) {
if (val & 0x0F) {
/* A tiny call entry */
scanOffs += (val & 0x0F);
regMask = (val & 0x70) >> 4;
argMask = 0;
argHnum = 0;
}
else {
/* This pointer liveness encoding */
regMask = (val & 0x70) >> 4;
if (regMask == 0x1)
thisPtrReg = REGI_EDI;
else if (regMask == 0x2)
thisPtrReg = REGI_ESI;
else if (regMask == 0x4)
thisPtrReg = REGI_EBX;
else
_ASSERTE(!"illegal encoding for 'this' pointer liveness");
}
}
else {
/* A small call entry */
scanOffs += (val & 0x7F);
val = *table++;
regMask = val >> 5;
argMask = val & 0x1F;
argHnum = 5;
}
break;
case 0xFD: // medium encoding
argMask = *table++;
val = *table++;
argMask |= ((val & 0xF0) << 4);
argHnum = 12;
nxt = *table++;
scanOffs += (val & 0x0F) + ((nxt & 0x1F) << 4);
regMask = nxt >> 5; // EBX,ESI,EDI
break;
case 0xF9: // medium encoding with interior pointers
scanOffs += *table++;
val = *table++;
argMask = val & 0x1F;
argHnum = 5;
regMask = val >> 5;
val = *table++;
iargMask = val & 0x1F;
iregMask = val >> 5;
break;
case 0xFE: // large encoding
case 0xFA: // large encoding with interior pointers
val = *table++;
regMask = val & 0x7;
iregMask = val >> 4;
scanOffs += *dac_cast(table); table += sizeof(DWORD);
argMask = *dac_cast(table); table += sizeof(DWORD);
argHnum = 31;
if (encType == 0xFA) // read iargMask
{
iargMask = *dac_cast(table); table += sizeof(DWORD);
}
break;
case 0xFB: // huge encoding This is the only partially interruptible
// encoding that supports a pushed ArgMask
// which is greater than 32-bits.
// The ArgMask is encoded using the argTab
val = *table++;
regMask = val & 0x7;
iregMask = val >> 4;
scanOffs += *dac_cast(table); table += sizeof(DWORD);
argHnum = *dac_cast(table); table += sizeof(DWORD);
argTabBytes = *dac_cast(table); table += sizeof(DWORD);
argTab = table; table += argTabBytes;
argMask = 0;
break;
case 0xFF:
scanOffs = curOffs + 1;
break;
} // end case
// iregMask & iargMask are subsets of regMask & argMask respectively
_ASSERTE((iregMask & regMask) == iregMask);
_ASSERTE((iargMask & argMask) == iargMask);
} // end while
}
else {
/*
* Encoding table for methods with an ESP frame and are not fully interruptible
* This encoding does not support a pushed ArgMask greater than 32
*
* The encoding used is as follows:
*
* push 000DDDDD ESP push one item with 5-bit delta
* push 00100000 [pushCount] ESP push multiple items
* reserved 0011xxxx
* skip 01000000 [Delta] Skip Delta, arbitrary sized delta
* skip 0100DDDD Skip small Delta, for call (DDDD != 0)
* pop 01CCDDDD ESP pop CC items with 4-bit delta (CC != 00)
* call 1PPPPPPP Call Pattern, P=[0..79]
* call 1101pbsd DDCCCMMM Call RegMask=pbsd,ArgCnt=CCC,
* ArgMask=MMM Delta=commonDelta[DD]
* call 1110pbsd [ArgCnt] [ArgMask] Call ArgCnt,RegMask=pbsd,[32-bit ArgMask]
* call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
* [32-bit PndCnt][32-bit PndSize][PndOffs...]
* iptr 11110000 [IPtrMask] Arbitrary 32-bit Interior Pointer Mask
* thisptr 111101RR This pointer is in Register RR
* 00=EDI,01=ESI,10=EBX,11=EBP
* reserved 111100xx xx != 00
* reserved 111110xx xx != 00
* reserved 11111xxx xxx != 000 && xxx != 111(EOT)
*
* The value 11111111 [0xFF] indicates the end of the table.
*
* An offset (at which stack-walking is performed) without an explicit encoding
* is assumed to be a trivial call-site (no GC registers, stack empty before and
* after) to avoid having to encode all trivial calls.
*
* Note on the encoding used for interior pointers
*
* The iptr encoding must immediately preceed a call encoding. It is used to
* transform a normal GC pointer addresses into an interior pointers for GC purposes.
* The mask supplied to the iptr encoding is read from the least signicant bit
* to the most signicant bit. (i.e the lowest bit is read first)
*
* p indicates that register EBP is a live pointer
* b indicates that register EBX is a live pointer
* s indicates that register ESI is a live pointer
* d indicates that register EDI is a live pointer
* P indicates that register EBP is an interior pointer
* B indicates that register EBX is an interior pointer
* S indicates that register ESI is an interior pointer
* D indicates that register EDI is an interior pointer
*
* As an example the following sequence indicates that EDI.ESI and the 2nd pushed pointer
* in ArgMask are really interior pointers. The pointer in ESI in a normal pointer:
*
* iptr 11110000 00010011 => read Interior Ptr, Interior Ptr, Normal Ptr, Normal Ptr, Interior Ptr
* call 11010011 DDCCC011 RRRR=1011 => read EDI is a GC-pointer, ESI is a GC-pointer. EBP is a GC-pointer
* MMM=0011 => read two GC-pointers arguments on the stack (nested call)
*
* Since the call instruction mentions 5 GC-pointers we list them in the required order:
* EDI, ESI, EBP, 1st-pushed pointer, 2nd-pushed pointer
*
* And we apply the Interior Pointer mask mmmm=10011 to the above five ordered GC-pointers
* we learn that EDI and ESI are interior GC-pointers and that the second push arg is an
* interior GC-pointer.
*/
#if defined(DACCESS_COMPILE)
DWORD cbZeroBytes = 0;
#endif // DACCESS_COMPILE
while (scanOffs <= curOffs)
{
unsigned callArgCnt;
unsigned skip;
unsigned newRegMask, inewRegMask;
unsigned newArgMask, inewArgMask;
unsigned oldScanOffs = scanOffs;
if (iptrMask)
{
// We found this iptrMask in the previous iteration.
// This iteration must be for a call. Set these variables
// so that they are available at the end of the loop
inewRegMask = iptrMask & 0x0F; // EBP,EBX,ESI,EDI
inewArgMask = iptrMask >> 4;
iptrMask = 0;
}
else
{
// Zero out any stale values.
inewRegMask = 0;
inewArgMask = 0;
}
/* Get the next byte and decode it */
unsigned val = *table++;
#if defined(DACCESS_COMPILE)
// In this scenario, a 0 means that there is a push at the current offset. For a struct with
// two double fields, the JIT may use two movq instructions to push the struct onto the stack, and
// the JIT will encode 4 pushes at the same code offset. This means that we can have up to 4
// consecutive bytes of 0 without changing the code offset. Having more than 4 consecutive bytes
// of zero indicates that there is most likely some sort of DAC error, and it may lead to problems
// such as infinite loops. So we bail out early instead.
if (val == 0)
{
cbZeroBytes += 1;
if (cbZeroBytes > 4)
{
DacError(CORDBG_E_TARGET_INCONSISTENT);
UNREACHABLE();
}
}
else
{
cbZeroBytes = 0;
}
#endif // DACCESS_COMPILE
#ifdef _DEBUG
if (scanOffs != curOffs)
isCall = false;
#endif
/* Check pushes, pops, and skips */
if (!(val & 0x80)) {
// iptrMask can immediately precede only calls
_ASSERTE(inewRegMask == 0);
_ASSERTE(inewArgMask == 0);
if (!(val & 0x40)) {
unsigned pushCount;
if (!(val & 0x20))
{
//
// push 000DDDDD ESP push one item, 5-bit delta
//
pushCount = 1;
scanOffs += val & 0x1f;
}
else
{
//
// push 00100000 [pushCount] ESP push multiple items
//
_ASSERTE(val == 0x20);
pushCount = fastDecodeUnsigned(table);
}
if (scanOffs > curOffs)
{
scanOffs = oldScanOffs;
goto FINISHED;
}
stackDepth += pushCount;
}
else if ((val & 0x3f) != 0) {
//
// pop 01CCDDDD pop CC items, 4-bit delta
//
scanOffs += val & 0x0f;
if (scanOffs > curOffs)
{
scanOffs = oldScanOffs;
goto FINISHED;
}
stackDepth -= (val & 0x30) >> 4;
} else if (scanOffs < curOffs) {
//
// skip 01000000 [Delta] Skip arbitrary sized delta
//
skip = fastDecodeUnsigned(table);
scanOffs += skip;
}
else // don't process a skip if we are already at curOffs
goto FINISHED;
/* reset regs and args state since we advance past last call site */
regMask = 0;
iregMask = 0;
argMask = 0;
iargMask = 0;
argHnum = 0;
}
else /* It must be a call, thisptr, or iptr */
{
switch ((val & 0x70) >> 4) {
default: // case 0-4, 1000xxxx through 1100xxxx
//
// call 1PPPPPPP Call Pattern, P=[0..79]
//
decodeCallPattern((val & 0x7f), &callArgCnt,
&newRegMask, &newArgMask, &skip);
// If we've already reached curOffs and the skip amount
// is non-zero then we are done
if ((scanOffs == curOffs) && (skip > 0))
goto FINISHED;
// otherwise process this call pattern
scanOffs += skip;
if (scanOffs > curOffs)
goto FINISHED;
#ifdef _DEBUG
isCall = true;
#endif
regMask = newRegMask;
argMask = newArgMask; argTab = NULL;
iregMask = inewRegMask;
iargMask = inewArgMask;
stackDepth -= callArgCnt;
argHnum = 2; // argMask is known to be <= 3
break;
case 5:
//
// call 1101RRRR DDCCCMMM Call RegMask=RRRR,ArgCnt=CCC,
// ArgMask=MMM Delta=commonDelta[DD]
//
newRegMask = val & 0xf; // EBP,EBX,ESI,EDI
val = *table++; // read next byte
skip = callCommonDelta[val>>6];
// If we've already reached curOffs and the skip amount
// is non-zero then we are done
if ((scanOffs == curOffs) && (skip > 0))
goto FINISHED;
// otherwise process this call encoding
scanOffs += skip;
if (scanOffs > curOffs)
goto FINISHED;
#ifdef _DEBUG
isCall = true;
#endif
regMask = newRegMask;
iregMask = inewRegMask;
callArgCnt = (val >> 3) & 0x7;
stackDepth -= callArgCnt;
argMask = (val & 0x7); argTab = NULL;
iargMask = inewArgMask;
argHnum = 3;
break;
case 6:
//
// call 1110RRRR [ArgCnt] [ArgMask]
// Call ArgCnt,RegMask=RRR,ArgMask
//
#ifdef _DEBUG
isCall = true;
#endif
regMask = val & 0xf; // EBP,EBX,ESI,EDI
iregMask = inewRegMask;
callArgCnt = fastDecodeUnsigned(table);
stackDepth -= callArgCnt;
argMask = fastDecodeUnsigned(table); argTab = NULL;
iargMask = inewArgMask;
argHnum = sizeof(argMask) * 8; // The size of argMask in bits
break;
case 7:
switch (val & 0x0C)
{
case 0x00:
//
// 0xF0 iptr 11110000 [IPtrMask] Arbitrary Interior Pointer Mask
//
iptrMask = fastDecodeUnsigned(table);
break;
case 0x04:
//
// 0xF4 thisptr 111101RR This pointer is in Register RR
// 00=EDI,01=ESI,10=EBX,11=EBP
//
{
static const regNum calleeSavedRegs[] =
{ REGI_EDI, REGI_ESI, REGI_EBX, REGI_EBP };
thisPtrReg = calleeSavedRegs[val&0x3];
}
break;
case 0x08:
//
// 0xF8 call 11111000 [PBSDpbsd][32-bit delta][32-bit ArgCnt]
// [32-bit PndCnt][32-bit PndSize][PndOffs...]
//
val = *table++;
skip = *dac_cast(table); table += sizeof(DWORD);
// [VSUQFE 4670]
// If we've already reached curOffs and the skip amount
// is non-zero then we are done
if ((scanOffs == curOffs) && (skip > 0))
goto FINISHED;
// [VSUQFE 4670]
scanOffs += skip;
if (scanOffs > curOffs)
goto FINISHED;
#ifdef _DEBUG
isCall = true;
#endif
regMask = val & 0xF;
iregMask = val >> 4;
callArgCnt = *dac_cast(table); table += sizeof(DWORD);
stackDepth -= callArgCnt;
argHnum = *dac_cast(table); table += sizeof(DWORD);
argTabBytes = *dac_cast(table); table += sizeof(DWORD);
argTab = table;
table += argTabBytes;
break;
case 0x0C:
//
// 0xFF end 11111111 End of table marker
//
_ASSERTE(val==0xff);
goto FINISHED;
default:
_ASSERTE(!"reserved GC encoding");
break;
}
break;
} // end switch
} // end else (!(val & 0x80))
// iregMask & iargMask are subsets of regMask & argMask respectively
_ASSERTE((iregMask & regMask) == iregMask);
_ASSERTE((iargMask & argMask) == iargMask);
} // end while
} // end else ebp-less frame
FINISHED:
// iregMask & iargMask are subsets of regMask & argMask respectively
_ASSERTE((iregMask & regMask) == iregMask);
_ASSERTE((iargMask & argMask) == iargMask);
if (scanOffs != curOffs)
{
/* must have been a boring call */
info->regMaskResult = RM_NONE;
info->argMaskResult = ptrArgTP(0);
info->iregMaskResult = RM_NONE;
info->iargMaskResult = ptrArgTP(0);
info->argHnumResult = 0;
info->argTabResult = NULL;
info->argTabBytes = 0;
}
else
{
info->regMaskResult = convertCalleeSavedRegsMask(regMask);
info->argMaskResult = ptrArgTP(argMask);
info->argHnumResult = argHnum;
info->iregMaskResult = convertCalleeSavedRegsMask(iregMask);
info->iargMaskResult = ptrArgTP(iargMask);
info->argTabResult = argTab;
info->argTabBytes = argTabBytes;
}
#ifdef _DEBUG
if (scanOffs != curOffs) {
isCall = false;
}
_ASSERTE(thisPtrReg == REGI_NA || (!isCall || (regNumToMask(thisPtrReg) & info->regMaskResult)));
#endif
info->thisPtrResult = thisPtrReg;
_ASSERTE(int(stackDepth) < INT_MAX); // check that it did not underflow
return (stackDepth * sizeof(unsigned));
}
#ifdef _PREFAST_
#pragma warning(pop)
#endif
/*****************************************************************************
* scan the register argument table for the fully interruptible case.
this function is called to find all live objects (pushed arguments)
and to get the stack base for fully interruptible methods.
Returns size of things pushed on the stack for ESP frames
Arguments:
table - The pointer table
curOffsRegs - The current code offset that should be used for reporting registers
curOffsArgs - The current code offset that should be used for reporting args
info - Incoming arg used to determine if there's a frame, and to save results
*/
static
unsigned scanArgRegTableI(PTR_CBYTE table,
unsigned curOffsRegs,
unsigned curOffsArgs,
hdrInfo * info)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
regNum thisPtrReg = REGI_NA;
unsigned ptrRegs = 0; // The mask of registers that contain pointers
unsigned iptrRegs = 0; // The subset of ptrRegs that are interior pointers
unsigned ptrOffs = 0; // The code offset of the table entry we are currently looking at
unsigned argCnt = 0; // The number of args that have been pushed
ptrArgTP ptrArgs(0); // The mask of stack values that contain pointers.
ptrArgTP iptrArgs(0); // The subset of ptrArgs that are interior pointers.
ptrArgTP argHigh(0); // The current mask position that corresponds to the top of the stack.
bool isThis = false;
bool iptr = false;
// The comment before the call to scanArgRegTableI in EnumGCRefs
// describes why curOffsRegs can be smaller than curOffsArgs.
_ASSERTE(curOffsRegs <= curOffsArgs);
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
#endif
bool hasPartialArgInfo;
#ifndef UNIX_X86_ABI
hasPartialArgInfo = info->ebpFrame;
#else
// For x86/Linux, interruptible code always has full arg info
//
// This should be aligned with emitFullArgInfo setting at
// emitter::emitEndCodeGen (in JIT)
hasPartialArgInfo = false;
#endif
/*
Encoding table for methods that are fully interruptible
The encoding used is as follows:
ptr reg dead 00RRRDDD [RRR != 100]
ptr reg live 01RRRDDD [RRR != 100]
non-ptr arg push 10110DDD [SSS == 110]
ptr arg push 10SSSDDD [SSS != 110] && [SSS != 111]
ptr arg pop 11CCCDDD [CCC != 000] && [CCC != 110] && [CCC != 111]
little delta skip 11000DDD [CCC == 000]
bigger delta skip 11110BBB [CCC == 110]
The values used in the encodings are as follows:
DDD code offset delta from previous entry (0-7)
BBB bigger delta 000=8,001=16,010=24,...,111=64
RRR register number (EAX=000,ECX=001,EDX=010,EBX=011,
EBP=101,ESI=110,EDI=111), ESP=100 is reserved
SSS argument offset from base of stack. This is
redundant for frameless methods as we can
infer it from the previous pushes+pops. However,
for EBP-methods, we only report GC pushes, and
so we need SSS
CCC argument count being popped (includes only ptrs for EBP methods)
The following are the 'large' versions:
large delta skip 10111000 [0xB8] , encodeUnsigned(delta)
large ptr arg push 11111000 [0xF8] , encodeUnsigned(pushCount)
large non-ptr arg push 11111001 [0xF9] , encodeUnsigned(pushCount)
large ptr arg pop 11111100 [0xFC] , encodeUnsigned(popCount)
large arg dead 11111101 [0xFD] , encodeUnsigned(popCount) for caller-pop args.
Any GC args go dead after the call,
but are still sitting on the stack
this pointer prefix 10111100 [0xBC] the next encoding is a ptr live
or a ptr arg push
and contains the this pointer
interior or by-ref 10111111 [0xBF] the next encoding is a ptr live
pointer prefix or a ptr arg push
and contains an interior
or by-ref pointer
The value 11111111 [0xFF] indicates the end of the table.
*/
#if defined(DACCESS_COMPILE)
bool fLastByteIsZero = false;
#endif // DACCESS_COMPILE
/* Have we reached the instruction we're looking for? */
while (ptrOffs <= curOffsArgs)
{
unsigned val;
int isPop;
unsigned argOfs;
unsigned regMask;
// iptrRegs & iptrArgs are subsets of ptrRegs & ptrArgs respectively
_ASSERTE((iptrRegs & ptrRegs) == iptrRegs);
_ASSERTE((iptrArgs & ptrArgs) == iptrArgs);
/* Now find the next 'life' transition */
val = *table++;
#if defined(DACCESS_COMPILE)
// In this scenario, a zero byte means that EAX is going dead at the current offset. Since EAX
// can't go dead more than once at any given offset, it's invalid to have two consecutive bytes
// of zero. If this were to happen, then it means that there is most likely some sort of DAC
// error, and it may lead to problems such as infinite loops. So we bail out early instead.
if ((val == 0) && fLastByteIsZero)
{
DacError(CORDBG_E_TARGET_INCONSISTENT);
UNREACHABLE();
}
fLastByteIsZero = (val == 0);
#endif // DACCESS_COMPILE
if (!(val & 0x80))
{
/* A small 'regPtr' encoding */
regNum reg;
ptrOffs += (val ) & 0x7;
if (ptrOffs > curOffsArgs) {
iptr = isThis = false;
goto REPORT_REFS;
}
else if (ptrOffs > curOffsRegs) {
iptr = isThis = false;
continue;
}
reg = (regNum)((val >> 3) & 0x7);
regMask = 1 << reg; // EAX,ECX,EDX,EBX,---,EBP,ESI,EDI
#if 0
printf("regMask = %04X -> %04X\n", ptrRegs,
(val & 0x40) ? (ptrRegs | regMask)
: (ptrRegs & ~regMask));
#endif
/* The register is becoming live/dead here */
if (val & 0x40)
{
/* Becomes Live */
_ASSERTE((ptrRegs & regMask) == 0);
ptrRegs |= regMask;
if (isThis)
{
thisPtrReg = reg;
}
if (iptr)
{
iptrRegs |= regMask;
}
}
else
{
/* Becomes Dead */
_ASSERTE((ptrRegs & regMask) != 0);
ptrRegs &= ~regMask;
if (reg == thisPtrReg)
{
thisPtrReg = REGI_NA;
}
if (iptrRegs & regMask)
{
iptrRegs &= ~regMask;
}
}
iptr = isThis = false;
continue;
}
/* This is probably an argument push/pop */
argOfs = (val & 0x38) >> 3;
/* 6 [110] and 7 [111] are reserved for other encodings */
if (argOfs < 6)
{
/* A small argument encoding */
ptrOffs += (val & 0x07);
if (ptrOffs > curOffsArgs) {
iptr = isThis = false;
goto REPORT_REFS;
}
isPop = (val & 0x40);
ARG:
if (isPop)
{
if (argOfs == 0)
continue; // little skip encoding
/* We remove (pop) the top 'argOfs' entries */
_ASSERTE(argOfs || argOfs <= argCnt);
/* adjust # of arguments */
argCnt -= argOfs;
_ASSERTE(argCnt < MAX_PTRARG_OFS);
// printf("[%04X] popping %u args: mask = %04X\n", ptrOffs, argOfs, (int)ptrArgs);
do
{
_ASSERTE(!isZero(argHigh));
/* Do we have an argument bit that's on? */
if (intersect(ptrArgs, argHigh))
{
/* Turn off the bit */
setDiff(ptrArgs, argHigh);
setDiff(iptrArgs, argHigh);
/* We've removed one more argument bit */
argOfs--;
}
else if (hasPartialArgInfo)
argCnt--;
else /* full arg info && not a ref */
argOfs--;
/* Continue with the next lower bit */
argHigh >>= 1;
}
while (argOfs);
_ASSERTE(!hasPartialArgInfo ||
isZero(argHigh) ||
(argHigh == CONSTRUCT_ptrArgTP(1, (argCnt-1))));
if (hasPartialArgInfo)
{
// We always leave argHigh pointing to the next ptr arg.
// So, while argHigh is non-zero, and not a ptrArg, we shift right (and subtract
// one arg from our argCnt) until it is a ptrArg.
while (!intersect(argHigh, ptrArgs) && (!isZero(argHigh)))
{
argHigh >>= 1;
argCnt--;
}
}
}
else
{
/* Add a new ptr arg entry at stack offset 'argOfs' */
if (argOfs >= MAX_PTRARG_OFS)
{
_ASSERTE_ALL_BUILDS("clr/src/VM/eetwain.cpp", !"scanArgRegTableI: args pushed 'too deep'");
}
else
{
/* Full arg info reports all pushes, and thus
argOffs has to be consistent with argCnt */
_ASSERTE(hasPartialArgInfo || argCnt == argOfs);
/* store arg count */
argCnt = argOfs + 1;
_ASSERTE((argCnt < MAX_PTRARG_OFS));
/* Compute the appropriate argument offset bit */
ptrArgTP argMask = CONSTRUCT_ptrArgTP(1, argOfs);
// printf("push arg at offset %02u --> mask = %04X\n", argOfs, (int)argMask);
/* We should never push twice at the same offset */
_ASSERTE(!intersect( ptrArgs, argMask));
_ASSERTE(!intersect(iptrArgs, argMask));
/* We should never push within the current highest offset */
// _ASSERTE(argHigh < argMask);
/* This is now the highest bit we've set */
argHigh = argMask;
/* Set the appropriate bit in the argument mask */
ptrArgs |= argMask;
if (iptr)
iptrArgs |= argMask;
}
iptr = isThis = false;
}
continue;
}
else if (argOfs == 6)
{
if (val & 0x40) {
/* Bigger delta 000=8,001=16,010=24,...,111=64 */
ptrOffs += (((val & 0x07) + 1) << 3);
}
else {
/* non-ptr arg push */
_ASSERTE(!hasPartialArgInfo);
ptrOffs += (val & 0x07);
if (ptrOffs > curOffsArgs) {
iptr = isThis = false;
goto REPORT_REFS;
}
argHigh = CONSTRUCT_ptrArgTP(1, argCnt);
argCnt++;
_ASSERTE(argCnt < MAX_PTRARG_OFS);
}
continue;
}
/* argOfs was 7 [111] which is reserved for the larger encodings */
_ASSERTE(argOfs==7);
switch (val)
{
case 0xFF:
iptr = isThis = false;
goto REPORT_REFS; // the method might loop !!!
case 0xB8:
val = fastDecodeUnsigned(table);
ptrOffs += val;
continue;
case 0xBC:
isThis = true;
break;
case 0xBF:
iptr = true;
break;
case 0xF8:
case 0xFC:
isPop = val & 0x04;
argOfs = fastDecodeUnsigned(table);
goto ARG;
case 0xFD: {
argOfs = fastDecodeUnsigned(table);
_ASSERTE(argOfs && argOfs <= argCnt);
// Kill the top "argOfs" pointers.
ptrArgTP argMask;
for(argMask = CONSTRUCT_ptrArgTP(1, argCnt); (argOfs != 0); argMask >>= 1)
{
_ASSERTE(!isZero(argMask) && !isZero(ptrArgs)); // there should be remaining pointers
if (intersect(ptrArgs, argMask))
{
setDiff(ptrArgs, argMask);
setDiff(iptrArgs, argMask);
argOfs--;
}
}
// For partial arg info, need to find the next higest pointer for argHigh
if (hasPartialArgInfo)
{
for(argHigh = ptrArgTP(0); !isZero(argMask); argMask >>= 1)
{
if (intersect(ptrArgs, argMask)) {
argHigh = argMask;
break;
}
}
}
} break;
case 0xF9:
argOfs = fastDecodeUnsigned(table);
argCnt += argOfs;
break;
default:
_ASSERTE(!"Unexpected special code %04X");
}
}
/* Report all live pointer registers */
REPORT_REFS:
_ASSERTE((iptrRegs & ptrRegs) == iptrRegs); // iptrRegs is a subset of ptrRegs
_ASSERTE((iptrArgs & ptrArgs) == iptrArgs); // iptrArgs is a subset of ptrArgs
/* Save the current live register, argument set, and argCnt */
info->regMaskResult = convertAllRegsMask(ptrRegs);
info->argMaskResult = ptrArgs;
info->argHnumResult = 0;
info->iregMaskResult = convertAllRegsMask(iptrRegs);
info->iargMaskResult = iptrArgs;
info->thisPtrResult = thisPtrReg;
_ASSERTE(thisPtrReg == REGI_NA || (regNumToMask(thisPtrReg) & info->regMaskResult));
if (hasPartialArgInfo)
{
return 0;
}
else
{
_ASSERTE(int(argCnt) < INT_MAX); // check that it did not underflow
return (argCnt * sizeof(unsigned));
}
}
/*****************************************************************************/
unsigned GetPushedArgSize(hdrInfo * info, PTR_CBYTE table, DWORD curOffs)
{
SUPPORTS_DAC;
unsigned sz;
if (info->interruptible)
{
sz = scanArgRegTableI(skipToArgReg(*info, table),
curOffs,
curOffs,
info);
}
else
{
sz = scanArgRegTable(skipToArgReg(*info, table),
curOffs,
info);
}
return sz;
}
/*****************************************************************************/
inline
void TRASH_CALLEE_UNSAVED_REGS(PREGDISPLAY pContext)
{
LIMITED_METHOD_DAC_CONTRACT;
#ifdef _DEBUG
/* This is not completely correct as we lose the current value, but
it should not really be useful to anyone. */
static DWORD s_badData = 0xDEADBEEF;
pContext->SetEaxLocation(&s_badData);
pContext->SetEcxLocation(&s_badData);
pContext->SetEdxLocation(&s_badData);
#endif //_DEBUG
}
/*****************************************************************************
* Sizes of certain i386 instructions which are used in the prolog/epilog
*/
// Can we use sign-extended byte to encode the imm value, or do we need a dword
#define CAN_COMPRESS(val) ((INT8)(val) == (INT32)(val))
#define SZ_ADD_REG(val) ( 2 + (CAN_COMPRESS(val) ? 1 : 4))
#define SZ_AND_REG(val) SZ_ADD_REG(val)
#define SZ_POP_REG 1
#define SZ_LEA(offset) SZ_ADD_REG(offset)
#define SZ_MOV_REG_REG 2
bool IsMarkerInstr(BYTE val)
{
SUPPORTS_DAC;
#ifdef _DEBUG
if (val == X86_INSTR_INT3)
{
return true;
}
#ifdef HAVE_GCCOVER
else // GcCover might have stomped on the instruction
{
if (GCStress::IsEnabled())
{
if (IsGcCoverageInterruptInstructionVal(val))
{
return true;
}
}
}
#endif // HAVE_GCCOVER
#endif // _DEBUG
return false;
}
/* Check if the given instruction opcode is the one we expect.
This is a "necessary" but not "sufficient" check as it ignores the check
if the instruction is one of our special markers (for debugging and GcStress) */
bool CheckInstrByte(BYTE val, BYTE expectedValue)
{
SUPPORTS_DAC;
return ((val == expectedValue) || IsMarkerInstr(val));
}
/* Similar to CheckInstrByte(). Use this to check a masked opcode (ignoring
optional bits in the opcode encoding).
valPattern is the masked out value.
expectedPattern is the mask value we expect.
val is the actual instruction opcode
*/
bool CheckInstrBytePattern(BYTE valPattern, BYTE expectedPattern, BYTE val)
{
SUPPORTS_DAC;
_ASSERTE((valPattern & val) == valPattern);
return ((valPattern == expectedPattern) || IsMarkerInstr(val));
}
/* Similar to CheckInstrByte() */
bool CheckInstrWord(WORD val, WORD expectedValue)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
return ((val == expectedValue) || IsMarkerInstr(val & 0xFF));
}
// Use this to check if the instruction at offset "walkOffset" has already
// been executed
// "actualHaltOffset" is the offset when the code was suspended
// It is assumed that there is linear control flow from offset 0 to "actualHaltOffset".
//
// This has been factored out just so that the intent of the comparison
// is clear (compared to the opposite intent)
bool InstructionAlreadyExecuted(unsigned walkOffset, unsigned actualHaltOffset)
{
SUPPORTS_DAC;
return (walkOffset < actualHaltOffset);
}
// skips past a "arith REG, IMM"
inline unsigned SKIP_ARITH_REG(int val, PTR_CBYTE base, unsigned offset)
{
LIMITED_METHOD_DAC_CONTRACT;
unsigned delta = 0;
if (val != 0)
{
#ifdef _DEBUG
// Confirm that arith instruction is at the correct place
_ASSERTE(CheckInstrBytePattern(base[offset ] & 0xFD, 0x81, base[offset]) &&
CheckInstrBytePattern(base[offset+1] & 0xC0, 0xC0, base[offset+1]));
// only use DWORD form if needed
_ASSERTE(((base[offset] & 2) != 0) == CAN_COMPRESS(val) ||
IsMarkerInstr(base[offset]));
#endif
delta = 2 + (CAN_COMPRESS(val) ? 1 : 4);
}
return(offset + delta);
}
inline unsigned SKIP_PUSH_REG(PTR_CBYTE base, unsigned offset)
{
LIMITED_METHOD_DAC_CONTRACT;
// Confirm it is a push instruction
_ASSERTE(CheckInstrBytePattern(base[offset] & 0xF8, 0x50, base[offset]));
return(offset + 1);
}
inline unsigned SKIP_POP_REG(PTR_CBYTE base, unsigned offset)
{
LIMITED_METHOD_DAC_CONTRACT;
// Confirm it is a pop instruction
_ASSERTE(CheckInstrBytePattern(base[offset] & 0xF8, 0x58, base[offset]));
return(offset + 1);
}
inline unsigned SKIP_MOV_REG_REG(PTR_CBYTE base, unsigned offset)
{
LIMITED_METHOD_DAC_CONTRACT;
// Confirm it is a move instruction
// Note that only the first byte may have been stomped on by IsMarkerInstr()
// So we can check the second byte directly
_ASSERTE(CheckInstrBytePattern(base[offset] & 0xFD, 0x89, base[offset]) &&
(base[offset+1] & 0xC0) == 0xC0);
return(offset + 2);
}
inline unsigned SKIP_LEA_ESP_EBP(int val, PTR_CBYTE base, unsigned offset)
{
LIMITED_METHOD_DAC_CONTRACT;
#ifdef _DEBUG
// Confirm it is the right instruction
// Note that only the first byte may have been stomped on by IsMarkerInstr()
// So we can check the second byte directly
WORD wOpcode = *(PTR_WORD)base;
_ASSERTE((CheckInstrWord(wOpcode, X86_INSTR_w_LEA_ESP_EBP_BYTE_OFFSET) &&
(val == *(PTR_SBYTE)(base+2)) &&
CAN_COMPRESS(val)) ||
(CheckInstrWord(wOpcode, X86_INSTR_w_LEA_ESP_EBP_DWORD_OFFSET) &&
(val == *(PTR_INT32)(base+2)) &&
!CAN_COMPRESS(val)));
#endif
unsigned delta = 2 + (CAN_COMPRESS(val) ? 1 : 4);
return(offset + delta);
}
unsigned SKIP_ALLOC_FRAME(int size, PTR_CBYTE base, unsigned offset)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
_ASSERTE(size != 0);
if (size == sizeof(void*))
{
// We do "push eax" instead of "sub esp,4"
return (SKIP_PUSH_REG(base, offset));
}
if (size >= (int)GetOsPageSize())
{
if (size < int(3 * GetOsPageSize()))
{
// add 7 bytes for one or two TEST EAX, [ESP+GetOsPageSize()]
offset += (size / GetOsPageSize()) * 7;
}
else
{
// xor eax, eax 2
// [nop] 0-3
// loop:
// test [esp + eax], eax 3
// sub eax, 0x1000 5
// cmp EAX, -size 5
// jge loop 2
offset += 2;
// NGEN images that support rejit may have extra nops we need to skip over
while (offset < 5)
{
if (CheckInstrByte(base[offset], X86_INSTR_NOP))
{
offset++;
}
else
{
break;
}
}
offset += 15;
}
}
// sub ESP, size
return (SKIP_ARITH_REG(size, base, offset));
}
#endif // !USE_GC_INFO_DECODER
#if defined(WIN64EXCEPTIONS) && !defined(CROSSGEN_COMPILE)
void EECodeManager::EnsureCallerContextIsValid( PREGDISPLAY pRD, StackwalkCacheEntry* pCacheEntry, EECodeInfo * pCodeInfo /*= NULL*/ )
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
}
CONTRACTL_END;
if( !pRD->IsCallerContextValid )
{
#if !defined(DACCESS_COMPILE) && defined(HAS_QUICKUNWIND)
if (pCacheEntry != NULL)
{
// lightened schema: take stack unwind info from stackwalk cache
QuickUnwindStackFrame(pRD, pCacheEntry, EnsureCallerStackFrameIsValid);
}
else
#endif // !DACCESS_COMPILE
{
// We need to make a copy here (instead of switching the pointers), in order to preserve the current context
*(pRD->pCallerContext) = *(pRD->pCurrentContext);
*(pRD->pCallerContextPointers) = *(pRD->pCurrentContextPointers);
Thread::VirtualUnwindCallFrame(pRD->pCallerContext, pRD->pCallerContextPointers, pCodeInfo);
}
pRD->IsCallerContextValid = TRUE;
}
_ASSERTE( pRD->IsCallerContextValid );
}
size_t EECodeManager::GetCallerSp( PREGDISPLAY pRD )
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
// Don't add usage of this field. This is only temporary.
// See ExceptionTracker::InitializeCrawlFrame() for more information.
if (!pRD->IsCallerSPValid)
{
EnsureCallerContextIsValid(pRD, NULL);
}
return GetSP(pRD->pCallerContext);
}
#endif // WIN64EXCEPTIONS && !CROSSGEN_COMPILE
#ifdef HAS_QUICKUNWIND
/*
* Light unwind the current stack frame, using provided cache entry.
* pPC, Esp and pEbp of pContext are updated.
*/
// static
void EECodeManager::QuickUnwindStackFrame(PREGDISPLAY pRD, StackwalkCacheEntry *pCacheEntry, QuickUnwindFlag flag)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(pCacheEntry);
_ASSERTE(GetControlPC(pRD) == (PCODE)(pCacheEntry->IP));
#if defined(_TARGET_X86_)
_ASSERTE(flag == UnwindCurrentStackFrame);
_ASSERTE(!pCacheEntry->fUseEbp || pCacheEntry->fUseEbpAsFrameReg);
if (pCacheEntry->fUseEbpAsFrameReg)
{
_ASSERTE(pCacheEntry->fUseEbp);
TADDR curEBP = (TADDR)*pRD->GetEbpLocation();
// EBP frame, update ESP through EBP, since ESPOffset may vary
pRD->SetEbpLocation(PTR_DWORD(curEBP));
pRD->SP = curEBP + sizeof(void*);
}
else
{
_ASSERTE(!pCacheEntry->fUseEbp);
// ESP frame, update up to retAddr using ESPOffset
pRD->SP += pCacheEntry->ESPOffset;
}
pRD->PCTAddr = (TADDR)pRD->SP;
pRD->ControlPC = *PTR_PCODE(pRD->PCTAddr);
pRD->SP += sizeof(void*) + pCacheEntry->argSize;
#elif defined(_TARGET_AMD64_)
if (pRD->IsCallerContextValid)
{
pRD->pCurrentContext->Rbp = pRD->pCallerContext->Rbp;
pRD->pCurrentContext->Rsp = pRD->pCallerContext->Rsp;
pRD->pCurrentContext->Rip = pRD->pCallerContext->Rip;
}
else
{
PCONTEXT pSourceCtx = NULL;
PCONTEXT pTargetCtx = NULL;
if (flag == UnwindCurrentStackFrame)
{
pTargetCtx = pRD->pCurrentContext;
pSourceCtx = pRD->pCurrentContext;
}
else
{
pTargetCtx = pRD->pCallerContext;
pSourceCtx = pRD->pCurrentContext;
}
// Unwind RBP. The offset is relative to the current sp.
if (pCacheEntry->RBPOffset == 0)
{
pTargetCtx->Rbp = pSourceCtx->Rbp;
}
else
{
pTargetCtx->Rbp = *(UINT_PTR*)(pSourceCtx->Rsp + pCacheEntry->RBPOffset);
}
// Adjust the sp. From this pointer onwards pCurrentContext->Rsp is the caller sp.
pTargetCtx->Rsp = pSourceCtx->Rsp + pCacheEntry->RSPOffset;
// Retrieve the return address.
pTargetCtx->Rip = *(UINT_PTR*)((pTargetCtx->Rsp) - sizeof(UINT_PTR));
}
if (flag == UnwindCurrentStackFrame)
{
SyncRegDisplayToCurrentContext(pRD);
pRD->IsCallerContextValid = FALSE;
pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary.
}
#else // !_TARGET_X86_ && !_TARGET_AMD64_
PORTABILITY_ASSERT("EECodeManager::QuickUnwindStackFrame is not implemented on this platform.");
#endif // !_TARGET_X86_ && !_TARGET_AMD64_
}
#endif // HAS_QUICKUNWIND
/*****************************************************************************/
#ifdef _TARGET_X86_ // UnwindStackFrame
/*****************************************************************************/
const RegMask CALLEE_SAVED_REGISTERS_MASK[] =
{
RM_EDI, // first register to be pushed
RM_ESI,
RM_EBX,
RM_EBP // last register to be pushed
};
static void SetLocation(PREGDISPLAY pRD, int ind, PDWORD loc)
{
#ifdef WIN64EXCEPTIONS
static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
{
offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Edi), // first register to be pushed
offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Esi),
offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Ebx),
offsetof(T_KNONVOLATILE_CONTEXT_POINTERS, Ebp), // last register to be pushed
};
SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
*(LPVOID*)(PBYTE(pRD->pCurrentContextPointers) + offsetOfRegPtr) = loc;
#else
static const SIZE_T OFFSET_OF_CALLEE_SAVED_REGISTERS[] =
{
offsetof(REGDISPLAY, pEdi), // first register to be pushed
offsetof(REGDISPLAY, pEsi),
offsetof(REGDISPLAY, pEbx),
offsetof(REGDISPLAY, pEbp), // last register to be pushed
};
SIZE_T offsetOfRegPtr = OFFSET_OF_CALLEE_SAVED_REGISTERS[ind];
*(LPVOID*)(PBYTE(pRD) + offsetOfRegPtr) = loc;
#endif
}
/*****************************************************************************/
void UnwindEspFrameEpilog(
PREGDISPLAY pContext,
hdrInfo * info,
PTR_CBYTE epilogBase,
unsigned flags)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
_ASSERTE(!info->ebpFrame && !info->doubleAlign);
_ASSERTE(info->epilogOffs > 0);
int offset = 0;
unsigned ESP = pContext->SP;
if (info->rawStkSize)
{
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
{
/* We have NOT executed the "ADD ESP, FrameSize",
so manually adjust stack pointer */
ESP += info->rawStkSize;
}
// We have already popped off the frame (excluding the callee-saved registers)
if (epilogBase[0] == X86_INSTR_POP_ECX)
{
// We may use "POP ecx" for doing "ADD ESP, 4",
// or we may not (in the case of JMP epilogs)
_ASSERTE(info->rawStkSize == sizeof(void*));
offset = SKIP_POP_REG(epilogBase, offset);
}
else
{
// "add esp, rawStkSize"
offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset);
}
}
/* Remaining callee-saved regs are at ESP. Need to update
regsMask as well to exclude registers which have already been popped. */
const RegMask regsMask = info->savedRegMask;
/* Increment "offset" in steps to see which callee-saved
registers have already been popped */
for (unsigned i = NumItems(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
{
RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
if (!(regMask & regsMask))
continue;
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
{
/* We have NOT yet popped off the register.
Get the value from the stack if needed */
if ((flags & UpdateAllRegs) || (regMask == RM_EBP))
{
SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
}
/* Adjust ESP */
ESP += sizeof(void*);
}
offset = SKIP_POP_REG(epilogBase, offset);
}
//CEE_JMP generates an epilog similar to a normal CEE_RET epilog except for the last instruction
_ASSERTE(CheckInstrBytePattern(epilogBase[offset] & X86_INSTR_RET, X86_INSTR_RET, epilogBase[offset]) //ret
|| CheckInstrBytePattern(epilogBase[offset], X86_INSTR_JMP_NEAR_REL32, epilogBase[offset]) //jmp ret32
|| CheckInstrWord(*PTR_WORD(epilogBase + offset), X86_INSTR_w_JMP_FAR_IND_IMM)); //jmp [addr32]
/* Finally we can set pPC */
pContext->PCTAddr = (TADDR)ESP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
pContext->SP = ESP;
}
/*****************************************************************************/
void UnwindEbpDoubleAlignFrameEpilog(
PREGDISPLAY pContext,
hdrInfo * info,
PTR_CBYTE epilogBase,
unsigned flags)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
_ASSERTE(info->ebpFrame || info->doubleAlign);
_ASSERTE(info->argSize < 0x10000); // "ret" only has a 2 byte operand
/* See how many instructions we have executed in the
epilog to determine which callee-saved registers
have already been popped */
int offset = 0;
unsigned ESP = pContext->SP;
bool needMovEspEbp = false;
if (info->doubleAlign)
{
// add esp, rawStkSize
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
ESP += info->rawStkSize;
_ASSERTE(info->rawStkSize != 0);
offset = SKIP_ARITH_REG(info->rawStkSize, epilogBase, offset);
// We also need "mov esp, ebp" after popping the callee-saved registers
needMovEspEbp = true;
}
else
{
bool needLea = false;
if (info->localloc)
{
// ESP may be variable if a localloc was actually executed. We will reset it.
// lea esp, [ebp-calleeSavedRegs]
needLea = true;
}
else if (info->savedRegsCountExclFP == 0)
{
// We will just generate "mov esp, ebp" and be done with it.
if (info->rawStkSize != 0)
{
needMovEspEbp = true;
}
}
else if (info->rawStkSize == 0)
{
// do nothing before popping the callee-saved registers
}
else if (info->rawStkSize == sizeof(void*))
{
// "pop ecx" will make ESP point to the callee-saved registers
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
ESP += sizeof(void*);
offset = SKIP_POP_REG(epilogBase, offset);
}
else
{
// We need to make ESP point to the callee-saved registers
// lea esp, [ebp-calleeSavedRegs]
needLea = true;
}
if (needLea)
{
// lea esp, [ebp-calleeSavedRegs]
unsigned calleeSavedRegsSize = info->savedRegsCountExclFP * sizeof(void*);
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
ESP = *pContext->GetEbpLocation() - calleeSavedRegsSize;
offset = SKIP_LEA_ESP_EBP(-int(calleeSavedRegsSize), epilogBase, offset);
}
}
for (unsigned i = NumItems(CALLEE_SAVED_REGISTERS_MASK) - 1; i > 0; i--)
{
RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
_ASSERTE(regMask != RM_EBP);
if ((info->savedRegMask & regMask) == 0)
continue;
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
{
if (flags & UpdateAllRegs)
{
SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
}
ESP += sizeof(void*);
}
offset = SKIP_POP_REG(epilogBase, offset);
}
if (needMovEspEbp)
{
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
ESP = *pContext->GetEbpLocation();
offset = SKIP_MOV_REG_REG(epilogBase, offset);
}
// Have we executed the pop EBP?
if (!InstructionAlreadyExecuted(offset, info->epilogOffs))
{
pContext->SetEbpLocation(PTR_DWORD(TADDR(ESP)));
ESP += sizeof(void*);
}
offset = SKIP_POP_REG(epilogBase, offset);
pContext->PCTAddr = (TADDR)ESP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
pContext->SP = ESP;
}
inline SIZE_T GetStackParameterSize(hdrInfo * info)
{
SUPPORTS_DAC;
return (info->varargs ? 0 : info->argSize); // Note varargs is caller-popped
}
//****************************************************************************
// This is the value ESP is incremented by on doing a "return"
inline SIZE_T ESPIncrOnReturn(hdrInfo * info)
{
SUPPORTS_DAC;
return sizeof(void *) + // pop off the return address
GetStackParameterSize(info);
}
/*****************************************************************************/
void UnwindEpilog(
PREGDISPLAY pContext,
hdrInfo * info,
PTR_CBYTE epilogBase,
unsigned flags)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(info->epilogOffs != hdrInfo::NOT_IN_EPILOG);
// _ASSERTE(flags & ActiveStackFrame); // Wont work for thread death
_ASSERTE(info->epilogOffs > 0);
if (info->ebpFrame || info->doubleAlign)
{
UnwindEbpDoubleAlignFrameEpilog(pContext, info, epilogBase, flags);
}
else
{
UnwindEspFrameEpilog(pContext, info, epilogBase, flags);
}
#ifdef _DEBUG
if (flags & UpdateAllRegs)
TRASH_CALLEE_UNSAVED_REGS(pContext);
#endif
/* Now adjust stack pointer */
pContext->SP += ESPIncrOnReturn(info);
}
/*****************************************************************************/
void UnwindEspFrameProlog(
PREGDISPLAY pContext,
hdrInfo * info,
PTR_CBYTE methodStart,
unsigned flags)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
/* we are in the middle of the prolog */
_ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG);
_ASSERTE(!info->ebpFrame && !info->doubleAlign);
unsigned offset = 0;
#ifdef _DEBUG
// If the first two instructions are 'nop, int3', then we will
// assume that is from a JitHalt operation and skip past it
if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
{
offset += 2;
}
#endif
const DWORD curOffs = info->prologOffs;
unsigned ESP = pContext->SP;
// Find out how many callee-saved regs have already been pushed
unsigned regsMask = RM_NONE;
PTR_DWORD savedRegPtr = PTR_DWORD((TADDR)ESP);
for (unsigned i = 0; i < NumItems(CALLEE_SAVED_REGISTERS_MASK); i++)
{
RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
if (!(info->savedRegMask & regMask))
continue;
if (InstructionAlreadyExecuted(offset, curOffs))
{
ESP += sizeof(void*);
regsMask |= regMask;
}
offset = SKIP_PUSH_REG(methodStart, offset);
}
if (info->rawStkSize)
{
offset = SKIP_ALLOC_FRAME(info->rawStkSize, methodStart, offset);
// Note that this assumes that only the last instruction in SKIP_ALLOC_FRAME
// actually updates ESP
if (InstructionAlreadyExecuted(offset, curOffs + 1))
{
savedRegPtr += (info->rawStkSize / sizeof(DWORD));
ESP += info->rawStkSize;
}
}
//
// Stack probe checks here
//
// Poison the value, we don't set it properly at the end of the prolog
INDEBUG(offset = 0xCCCCCCCC);
// Always restore EBP
if (regsMask & RM_EBP)
pContext->SetEbpLocation(savedRegPtr++);
if (flags & UpdateAllRegs)
{
if (regsMask & RM_EBX)
pContext->SetEbxLocation(savedRegPtr++);
if (regsMask & RM_ESI)
pContext->SetEsiLocation(savedRegPtr++);
if (regsMask & RM_EDI)
pContext->SetEdiLocation(savedRegPtr++);
TRASH_CALLEE_UNSAVED_REGS(pContext);
}
#if 0
// NOTE:
// THIS IS ONLY TRUE IF PROLOGSIZE DOES NOT INCLUDE REG-VAR INITIALIZATION !!!!
//
/* there is (potentially) only one additional
instruction in the prolog, (push ebp)
but if we would have been passed that instruction,
info->prologOffs would be hdrInfo::NOT_IN_PROLOG!
*/
_ASSERTE(offset == info->prologOffs);
#endif
pContext->SP = ESP;
}
/*****************************************************************************/
void UnwindEspFrame(
PREGDISPLAY pContext,
hdrInfo * info,
PTR_CBYTE table,
PTR_CBYTE methodStart,
DWORD curOffs,
unsigned flags)
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(!info->ebpFrame && !info->doubleAlign);
_ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG);
unsigned ESP = pContext->SP;
if (info->prologOffs != hdrInfo::NOT_IN_PROLOG)
{
if (info->prologOffs != 0) // Do nothing for the very start of the method
{
UnwindEspFrameProlog(pContext, info, methodStart, flags);
ESP = pContext->SP;
}
}
else
{
/* we are past the prolog, ESP has been set above */
// Are there any arguments pushed on the stack?
ESP += GetPushedArgSize(info, table, curOffs);
ESP += info->rawStkSize;
const RegMask regsMask = info->savedRegMask;
for (unsigned i = NumItems(CALLEE_SAVED_REGISTERS_MASK); i > 0; i--)
{
RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i - 1];
if ((regMask & regsMask) == 0)
continue;
SetLocation(pContext, i - 1, PTR_DWORD((TADDR)ESP));
ESP += sizeof(unsigned);
}
}
/* we can now set the (address of the) return address */
pContext->PCTAddr = (TADDR)ESP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
/* Now adjust stack pointer */
pContext->SP = ESP + ESPIncrOnReturn(info);
}
/*****************************************************************************/
void UnwindEbpDoubleAlignFrameProlog(
PREGDISPLAY pContext,
hdrInfo * info,
PTR_CBYTE methodStart,
unsigned flags)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE(info->prologOffs != hdrInfo::NOT_IN_PROLOG);
_ASSERTE(info->ebpFrame || info->doubleAlign);
DWORD offset = 0;
#ifdef _DEBUG
// If the first two instructions are 'nop, int3', then we will
// assume that is from a JitHalt operation and skip past it
if (methodStart[0] == X86_INSTR_NOP && methodStart[1] == X86_INSTR_INT3)
{
offset += 2;
}
#endif
/* Check for the case where EBP has not been updated yet. */
const DWORD curOffs = info->prologOffs;
// If we have still not excecuted "push ebp; mov ebp, esp", then we need to
// report the frame relative to ESP
if (!InstructionAlreadyExecuted(offset + 1, curOffs))
{
_ASSERTE(CheckInstrByte(methodStart [offset], X86_INSTR_PUSH_EBP) ||
CheckInstrWord(*PTR_WORD(methodStart + offset), X86_INSTR_W_MOV_EBP_ESP) ||
CheckInstrByte(methodStart [offset], X86_INSTR_JMP_NEAR_REL32)); // a rejit jmp-stamp
/* If we're past the "push ebp", adjust ESP to pop EBP off */
if (curOffs == (offset + 1))
pContext->SP += sizeof(TADDR);
/* Stack pointer points to return address */
pContext->PCTAddr = (TADDR)pContext->SP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
/* EBP and callee-saved registers still have the correct value */
return;
}
// We are atleast after the "push ebp; mov ebp, esp"
offset = SKIP_MOV_REG_REG(methodStart,
SKIP_PUSH_REG(methodStart, offset));
/* At this point, EBP has been set up. The caller's ESP and the return value
can be determined using EBP. Since we are still in the prolog,
we need to know our exact location to determine the callee-saved registers */
const unsigned curEBP = *pContext->GetEbpLocation();
if (flags & UpdateAllRegs)
{
PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP);
/* make sure that we align ESP just like the method's prolog did */
if (info->doubleAlign)
{
// "and esp,-8"
offset = SKIP_ARITH_REG(-8, methodStart, offset);
if (curEBP & 0x04)
{
pSavedRegs--;
#ifdef _DEBUG
if (dspPtr) printf("EnumRef: dblalign ebp: %08X\n", curEBP);
#endif
}
}
/* Increment "offset" in steps to see which callee-saved
registers have been pushed already */
for (unsigned i = 0; i < NumItems(CALLEE_SAVED_REGISTERS_MASK) - 1; i++)
{
RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
_ASSERTE(regMask != RM_EBP);
if ((info->savedRegMask & regMask) == 0)
continue;
if (InstructionAlreadyExecuted(offset, curOffs))
{
SetLocation(pContext, i, PTR_DWORD(--pSavedRegs));
}
// "push reg"
offset = SKIP_PUSH_REG(methodStart, offset) ;
}
TRASH_CALLEE_UNSAVED_REGS(pContext);
}
/* The caller's saved EBP is pointed to by our EBP */
pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP));
pContext->SP = DWORD((TADDR)(curEBP + sizeof(void *)));
/* Stack pointer points to return address */
pContext->PCTAddr = (TADDR)pContext->SP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
}
/*****************************************************************************/
bool UnwindEbpDoubleAlignFrame(
PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
hdrInfo *info,
PTR_CBYTE table,
PTR_CBYTE methodStart,
DWORD curOffs,
unsigned flags,
StackwalkCacheUnwindInfo *pUnwindInfo) // out-only, perf improvement
{
LIMITED_METHOD_CONTRACT;
SUPPORTS_DAC;
_ASSERTE(info->ebpFrame || info->doubleAlign);
const unsigned curESP = pContext->SP;
const unsigned curEBP = *pContext->GetEbpLocation();
/* First check if we are in a filter (which is obviously after the prolog) */
if (info->handlers && info->prologOffs == hdrInfo::NOT_IN_PROLOG)
{
TADDR baseSP;
#ifdef WIN64EXCEPTIONS
// Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
// Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
// TODO If funclet frame layout is changed from CodeGen::genFuncletProlog() and genFuncletEpilog(),
// we need to change here accordingly. It is likely to have changes when introducing PSPSym.
// TODO Currently we assume that ESP of funclet frames is always fixed but actually it could change.
if (pCodeInfo->IsFunclet())
{
baseSP = curESP;
// Set baseSP as initial SP
baseSP += GetPushedArgSize(info, table, curOffs);
// 16-byte stack alignment padding (allocated in genFuncletProlog)
// Current funclet frame layout (see CodeGen::genFuncletProlog() and genFuncletEpilog()):
// prolog: sub esp, 12
// epilog: add esp, 12
// ret
// SP alignment padding should be added for all instructions except the first one and the last one.
// Epilog may not exist (unreachable), so we need to check the instruction code.
const TADDR funcletStart = pCodeInfo->GetJitManager()->GetFuncletStartAddress(pCodeInfo);
if (funcletStart != pCodeInfo->GetCodeAddress() && methodStart[pCodeInfo->GetRelOffset()] != X86_INSTR_RETN)
baseSP += 12;
pContext->PCTAddr = baseSP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
return true;
}
#else // WIN64EXCEPTIONS
FrameType frameType = GetHandlerFrameInfo(info, curEBP,
curESP, (DWORD) IGNORE_VAL,
&baseSP);
/* If we are in a filter, we only need to unwind the funclet stack.
For catches/finallies, the normal handling will
cause the frame to be unwound all the way up to ebp skipping
other frames above it. This is OK, as those frames will be
dead. Also, the EE will detect that this has happened and it
will handle any EE frames correctly.
*/
if (frameType == FR_INVALID)
{
return false;
}
if (frameType == FR_FILTER)
{
pContext->PCTAddr = baseSP;
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
// pContext->pEbp = same as before;
#ifdef _DEBUG
/* The filter has to be called by the VM. So we dont need to
update callee-saved registers.
*/
if (flags & UpdateAllRegs)
{
static DWORD s_badData = 0xDEADBEEF;
pContext->SetEaxLocation(&s_badData);
pContext->SetEcxLocation(&s_badData);
pContext->SetEdxLocation(&s_badData);
pContext->SetEbxLocation(&s_badData);
pContext->SetEsiLocation(&s_badData);
pContext->SetEdiLocation(&s_badData);
}
#endif
if (pUnwindInfo)
{
// The filter funclet is like an ESP-framed-method.
pUnwindInfo->fUseEbp = FALSE;
pUnwindInfo->fUseEbpAsFrameReg = FALSE;
}
return true;
}
#endif // !WIN64EXCEPTIONS
}
//
// Prolog of an EBP method
//
if (info->prologOffs != hdrInfo::NOT_IN_PROLOG)
{
UnwindEbpDoubleAlignFrameProlog(pContext, info, methodStart, flags);
/* Now adjust stack pointer. */
pContext->SP += ESPIncrOnReturn(info);
return true;
}
if (flags & UpdateAllRegs)
{
// Get to the first callee-saved register
PTR_DWORD pSavedRegs = PTR_DWORD((TADDR)curEBP);
if (info->doubleAlign && (curEBP & 0x04))
pSavedRegs--;
for (unsigned i = 0; i < NumItems(CALLEE_SAVED_REGISTERS_MASK) - 1; i++)
{
RegMask regMask = CALLEE_SAVED_REGISTERS_MASK[i];
if ((info->savedRegMask & regMask) == 0)
continue;
SetLocation(pContext, i, --pSavedRegs);
}
}
/* The caller's ESP will be equal to EBP + retAddrSize + argSize. */
pContext->SP = (DWORD)(curEBP + sizeof(curEBP) + ESPIncrOnReturn(info));
/* The caller's saved EIP is right after our EBP */
pContext->PCTAddr = (TADDR)curEBP + RETURN_ADDR_OFFS * sizeof(TADDR);
pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
/* The caller's saved EBP is pointed to by our EBP */
pContext->SetEbpLocation(PTR_DWORD((TADDR)curEBP));
return true;
}
bool UnwindStackFrame(PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
unsigned flags,
CodeManState *pState,
StackwalkCacheUnwindInfo *pUnwindInfo /* out-only, perf improvement */)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
HOST_NOCALLS;
SUPPORTS_DAC;
} CONTRACTL_END;
// Address where the method has been interrupted
PCODE breakPC = pContext->ControlPC;
_ASSERTE(PCODEToPINSTR(breakPC) == pCodeInfo->GetCodeAddress());
PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode());
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
PTR_VOID methodInfoPtr = gcInfoToken.Info;
DWORD curOffs = pCodeInfo->GetRelOffset();
_ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
if (pState->dwIsSet == 0)
{
/* Extract the necessary information from the info block header */
stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
curOffs,
&stateBuf->hdrInfoBody);
}
PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf->hdrInfoSize;
hdrInfo * info = &stateBuf->hdrInfoBody;
info->isSpeculativeStackWalk = ((flags & SpeculativeStackwalk) != 0);
if (pUnwindInfo != NULL)
{
pUnwindInfo->securityObjectOffset = 0;
if (info->securityCheck)
{
_ASSERTE(info->ebpFrame);
SIZE_T securityObjectOffset = (GetSecurityObjectOffset(info) / sizeof(void*));
_ASSERTE(securityObjectOffset != 0);
pUnwindInfo->securityObjectOffset = DWORD(securityObjectOffset);
}
pUnwindInfo->fUseEbpAsFrameReg = info->ebpFrame;
pUnwindInfo->fUseEbp = ((info->savedRegMask & RM_EBP) != 0);
}
if (info->epilogOffs != hdrInfo::NOT_IN_EPILOG)
{
/*---------------------------------------------------------------------
* First, handle the epilog
*/
PTR_CBYTE epilogBase = (PTR_CBYTE) (breakPC - info->epilogOffs);
UnwindEpilog(pContext, info, epilogBase, flags);
}
else if (!info->ebpFrame && !info->doubleAlign)
{
/*---------------------------------------------------------------------
* Now handle ESP frames
*/
UnwindEspFrame(pContext, info, table, methodStart, curOffs, flags);
return true;
}
else
{
/*---------------------------------------------------------------------
* Now we know that have an EBP frame
*/
if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, table, methodStart, curOffs, flags, pUnwindInfo))
return false;
}
// TODO [DAVBR]: For the full fix for VsWhidbey 450273, all the below
// may be uncommented once isLegalManagedCodeCaller works properly
// with non-return address inputs, and with non-DEBUG builds
/*
// Ensure isLegalManagedCodeCaller succeeds for speculative stackwalks.
// (We just assert this below for non-speculative stackwalks.)
//
FAIL_IF_SPECULATIVE_WALK(isLegalManagedCodeCaller(GetControlPC(pContext)));
*/
return true;
}
#endif // _TARGET_X86_
#ifdef WIN64EXCEPTIONS
#ifdef _TARGET_X86_
size_t EECodeManager::GetResumeSp( PCONTEXT pContext )
{
PCODE currentPc = PCODE(pContext->Eip);
_ASSERTE(ExecutionManager::IsManagedCode(currentPc));
EECodeInfo codeInfo(currentPc);
PTR_CBYTE methodStart = PTR_CBYTE(codeInfo.GetSavedMethodCode());
GCInfoToken gcInfoToken = codeInfo.GetGCInfoToken();
PTR_VOID methodInfoPtr = gcInfoToken.Info;
DWORD curOffs = codeInfo.GetRelOffset();
CodeManStateBuf stateBuf;
stateBuf.hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
curOffs,
&stateBuf.hdrInfoBody);
PTR_CBYTE table = dac_cast(methodInfoPtr) + stateBuf.hdrInfoSize;
hdrInfo *info = &stateBuf.hdrInfoBody;
_ASSERTE(info->epilogOffs == hdrInfo::NOT_IN_EPILOG && info->prologOffs == hdrInfo::NOT_IN_PROLOG);
bool isESPFrame = !info->ebpFrame && !info->doubleAlign;
if (codeInfo.IsFunclet())
{
// Treat funclet's frame as ESP frame
isESPFrame = true;
}
if (isESPFrame)
{
const size_t curESP = (size_t)(pContext->Esp);
return curESP + GetPushedArgSize(info, table, curOffs);
}
const size_t curEBP = (size_t)(pContext->Ebp);
return GetOutermostBaseFP(curEBP, info);
}
#endif // _TARGET_X86_
#endif // WIN64EXCEPTIONS
#ifndef CROSSGEN_COMPILE
#ifndef WIN64EXCEPTIONS
/*****************************************************************************
*
* Unwind the current stack frame, i.e. update the virtual register
* set in pContext. This will be similar to the state after the function
* returns back to caller (IP points to after the call, Frame and Stack
* pointer has been reset, callee-saved registers restored (if UpdateAllRegs),
* callee-unsaved registers are trashed.
* Returns success of operation.
*/
bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
unsigned flags,
CodeManState *pState,
StackwalkCacheUnwindInfo *pUnwindInfo /* out-only, perf improvement */)
{
#ifdef _TARGET_X86_
return ::UnwindStackFrame(pContext, pCodeInfo, flags, pState, pUnwindInfo);
#else // _TARGET_X86_
PORTABILITY_ASSERT("EECodeManager::UnwindStackFrame");
return false;
#endif // _TARGET_???_
}
/*****************************************************************************/
#else // !WIN64EXCEPTIONS
/*****************************************************************************/
bool EECodeManager::UnwindStackFrame(PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
unsigned flags,
CodeManState *pState,
StackwalkCacheUnwindInfo *pUnwindInfo /* out-only, perf improvement */)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
#if defined(_TARGET_AMD64_)
// To avoid unnecessary computation, we only crack the unwind info if pUnwindInfo is not NULL, which only happens
// if the LIGHTUNWIND flag is passed to StackWalkFramesEx().
if (pUnwindInfo != NULL)
{
pCodeInfo->GetOffsetsFromUnwindInfo(&(pUnwindInfo->RSPOffsetFromUnwindInfo),
&(pUnwindInfo->RBPOffset));
}
#endif // _TARGET_AMD64_
_ASSERTE(pCodeInfo != NULL);
Thread::VirtualUnwindCallFrame(pContext, pCodeInfo);
return true;
}
/*****************************************************************************/
#endif // WIN64EXCEPTIONS
#endif // !CROSSGEN_COMPILE
/*****************************************************************************/
/* report args in 'msig' to the GC.
'argsStart' is start of the stack-based arguments
'varArgSig' describes the arguments
'ctx' has the GC reporting info
*/
void promoteVarArgs(PTR_BYTE argsStart, PTR_VASigCookie varArgSig, GCCONTEXT* ctx)
{
WRAPPER_NO_CONTRACT;
//Note: no instantiations needed for varargs
MetaSig msig(varArgSig->signature,
varArgSig->pModule,
NULL);
PTR_BYTE pFrameBase = argsStart - TransitionBlock::GetOffsetOfArgs();
ArgIterator argit(&msig);
#ifdef _TARGET_X86_
// For the X86 target the JIT does not report any of the fixed args for a varargs method
// So we report the fixed args via the promoteArgs call below
bool skipFixedArgs = false;
#else
// For other platforms the JITs do report the fixed args of a varargs method
// So we must tell promoteArgs to skip to the end of the fixed args
bool skipFixedArgs = true;
#endif
bool inVarArgs = false;
int argOffset;
while ((argOffset = argit.GetNextOffset()) != TransitionBlock::InvalidOffset)
{
if (msig.GetArgProps().AtSentinel())
inVarArgs = true;
// if skipFixedArgs is false we report all arguments
// otherwise we just report the varargs.
if (!skipFixedArgs || inVarArgs)
{
ArgDestination argDest(pFrameBase, argOffset, argit.GetArgLocDescForStructInRegs());
msig.GcScanRoots(&argDest, ctx->f, ctx->sc);
}
}
}
INDEBUG(void* forceStack1;)
#ifndef CROSSGEN_COMPILE
#ifndef USE_GC_INFO_DECODER
/*****************************************************************************
*
* Enumerate all live object references in that function using
* the virtual register set.
* Returns success of operation.
*/
bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
unsigned flags,
GCEnumCallback pCallBack,
LPVOID hCallBack,
DWORD relOffsetOverride)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
#ifdef WIN64EXCEPTIONS
if (flags & ParentOfFuncletStackFrame)
{
LOG((LF_GCROOTS, LL_INFO100000, "Not reporting this frame because it was already reported via another funclet.\n"));
return true;
}
#endif // WIN64EXCEPTIONS
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
unsigned curOffs = pCodeInfo->GetRelOffset();
unsigned EBP = *pContext->GetEbpLocation();
unsigned ESP = pContext->SP;
unsigned ptrOffs;
unsigned count;
hdrInfo info;
PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
#if 0
printf("EECodeManager::EnumGcRefs - EIP = %08x ESP = %08x offset = %x GC Info is at %08x\n", *pContext->pPC, ESP, curOffs, table);
#endif
/* Extract the necessary information from the info block header */
table += DecodeGCHdrInfo(gcInfoToken,
curOffs,
&info);
_ASSERTE( curOffs <= info.methodSize);
#ifdef _DEBUG
// if ((gcInfoToken.Info == (void*)0x37760d0) && (curOffs == 0x264))
// __asm int 3;
if (trEnumGCRefs) {
static unsigned lastESP = 0;
unsigned diffESP = ESP - lastESP;
if (diffESP > 0xFFFF) {
printf("------------------------------------------------------\n");
}
lastESP = ESP;
printf("EnumGCRefs [%s][%s] at %s.%s + 0x%03X:\n",
info.ebpFrame?"ebp":" ",
info.interruptible?"int":" ",
"UnknownClass","UnknownMethod", curOffs);
fflush(stdout);
}
#endif
/* Are we in the prolog or epilog of the method? */
if (info.prologOffs != hdrInfo::NOT_IN_PROLOG ||
info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
{
#if !DUMP_PTR_REFS
// Under normal circumstances the system will not suspend a thread
// if it is in the prolog or epilog of the function. However ThreadAbort
// exception or stack overflows can cause EH to happen in a prolog.
// Once in the handler, a GC can happen, so we can get to this code path.
// However since we are tearing down this frame, we don't need to report
// anything and we can simply return.
_ASSERTE(flags & ExecutionAborted);
#endif
return true;
}
#ifdef _DEBUG
#define CHK_AND_REPORT_REG(reg, doIt, iptr, regName) \
if (doIt) \
{ \
if (dspPtr) \
printf(" Live pointer register %s: ", #regName); \
pCallBack(hCallBack, \
(OBJECTREF*)(pContext->Get##regName##Location()), \
(iptr ? GC_CALL_INTERIOR : 0) \
| CHECK_APP_DOMAIN \
DAC_ARG(DacSlotLocation(reg, 0, false))); \
}
#else // !_DEBUG
#define CHK_AND_REPORT_REG(reg, doIt, iptr, regName) \
if (doIt) \
pCallBack(hCallBack, \
(OBJECTREF*)(pContext->Get##regName##Location()), \
(iptr ? GC_CALL_INTERIOR : 0) \
| CHECK_APP_DOMAIN \
DAC_ARG(DacSlotLocation(reg, 0, false)));
#endif // _DEBUG
/* What kind of a frame is this ? */
FrameType frameType = FR_NORMAL;
TADDR baseSP = 0;
if (info.handlers)
{
_ASSERTE(info.ebpFrame);
bool hasInnerFilter, hadInnerFilter;
frameType = GetHandlerFrameInfo(&info, EBP,
ESP, (DWORD) IGNORE_VAL,
&baseSP, NULL,
&hasInnerFilter, &hadInnerFilter);
_ASSERTE(frameType != FR_INVALID);
/* If this is the parent frame of a filter which is currently
executing, then the filter would have enumerated the frame using
the filter PC.
*/
if (hasInnerFilter)
return true;
/* If are in a try and we had a filter execute, we may have reported
GC refs from the filter (and not using the try's offset). So
we had better use the filter's end offset, as the try is
effectively dead and its GC ref's would be stale */
if (hadInnerFilter)
{
PTR_TADDR pFirstBaseSPslot = GetFirstBaseSPslotPtr(EBP, &info);
curOffs = (unsigned)pFirstBaseSPslot[1] - 1;
_ASSERTE(curOffs < info.methodSize);
/* Extract the necessary information from the info block header */
table = PTR_CBYTE(gcInfoToken.Info);
table += DecodeGCHdrInfo(gcInfoToken,
curOffs,
&info);
}
}
bool willContinueExecution = !(flags & ExecutionAborted);
unsigned pushedSize = 0;
/* if we have been interrupted we don't have to report registers/arguments
* because we are about to lose this context anyway.
* Alas, if we are in a ebp-less method we have to parse the table
* in order to adjust ESP.
*
* Note that we report "this" for all methods, even if
* noncontinuable, because because of the off chance they may be
* synchronized and we have to release the monitor on unwind. This
* could conceivably be optimized, but it turns out to be more
* expensive to check whether we're synchronized (which involves
* consulting metadata) than to just report "this" all the time in
* our most important scenarios.
*/
if (info.interruptible)
{
// If we are not on the active stack frame, we need to report gc registers
// that are live before the call. The reason is that the liveness of gc registers
// may change across a call to a method that does not return. In this case the instruction
// after the call may be a jump target and a register that didn't have a live gc pointer
// before the call may have a live gc pointer after the jump. To make sure we report the
// registers that have live gc pointers before the call we subtract 1 from curOffs.
unsigned curOffsRegs = (flags & ActiveStackFrame) != 0 ? curOffs : curOffs - 1;
pushedSize = scanArgRegTableI(skipToArgReg(info, table), curOffsRegs, curOffs, &info);
RegMask regs = info.regMaskResult;
RegMask iregs = info.iregMaskResult;
ptrArgTP args = info.argMaskResult;
ptrArgTP iargs = info.iargMaskResult;
_ASSERTE((isZero(args) || pushedSize != 0) || info.ebpFrame);
_ASSERTE((args & iargs) == iargs);
// Only synchronized methods and generic code that accesses
// the type context via "this" need to report "this".
// If its reported for other methods, its probably
// done incorrectly. So flag such cases.
_ASSERTE(info.thisPtrResult == REGI_NA ||
pCodeInfo->GetMethodDesc()->IsSynchronized() ||
pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis());
/* now report registers and arguments if we are not interrupted */
if (willContinueExecution)
{
/* Propagate unsafed registers only in "current" method */
/* If this is not the active method, then the callee wil
* trash these registers, and so we wont need to report them */
if (flags & ActiveStackFrame)
{
CHK_AND_REPORT_REG(REGI_EAX, regs & RM_EAX, iregs & RM_EAX, Eax);
CHK_AND_REPORT_REG(REGI_ECX, regs & RM_ECX, iregs & RM_ECX, Ecx);
CHK_AND_REPORT_REG(REGI_EDX, regs & RM_EDX, iregs & RM_EDX, Edx);
}
CHK_AND_REPORT_REG(REGI_EBX, regs & RM_EBX, iregs & RM_EBX, Ebx);
CHK_AND_REPORT_REG(REGI_EBP, regs & RM_EBP, iregs & RM_EBP, Ebp);
CHK_AND_REPORT_REG(REGI_ESI, regs & RM_ESI, iregs & RM_ESI, Esi);
CHK_AND_REPORT_REG(REGI_EDI, regs & RM_EDI, iregs & RM_EDI, Edi);
_ASSERTE(!(regs & RM_ESP));
/* Report any pending pointer arguments */
DWORD * pPendingArgFirst; // points **AT** first parameter
if (!info.ebpFrame)
{
// -sizeof(void*) because we want to point *AT* first parameter
pPendingArgFirst = (DWORD *)(size_t)(ESP + pushedSize - sizeof(void*));
}
else
{
_ASSERTE(willContinueExecution);
if (info.handlers)
{
// -sizeof(void*) because we want to point *AT* first parameter
pPendingArgFirst = (DWORD *)(size_t)(baseSP - sizeof(void*));
}
else if (info.localloc)
{
baseSP = *(DWORD *)(size_t)(EBP - GetLocallocSPOffset(&info));
// -sizeof(void*) because we want to point *AT* first parameter
pPendingArgFirst = (DWORD *)(size_t) (baseSP - sizeof(void*));
}
else
{
// Note that 'info.stackSize includes the size for pushing EBP, but EBP is pushed
// BEFORE EBP is set from ESP, thus (EBP - info.stackSize) actually points past
// the frame by one DWORD, and thus points *AT* the first parameter
pPendingArgFirst = (DWORD *)(size_t)(EBP - info.stackSize);
}
}
if (!isZero(args))
{
unsigned i = 0;
ptrArgTP b(1);
for (; !isZero(args) && (i < MAX_PTRARG_OFS); i += 1, b <<= 1)
{
if (intersect(args,b))
{
unsigned argAddr = (unsigned)(size_t)(pPendingArgFirst - i);
bool iptr = false;
setDiff(args, b);
if (intersect(iargs,b))
{
setDiff(iargs, b);
iptr = true;
}
#ifdef _DEBUG
if (dspPtr)
{
printf(" Pushed ptr arg [E");
if (info.ebpFrame)
printf("BP-%02XH]: ", EBP - argAddr);
else
printf("SP+%02XH]: ", argAddr - ESP);
}
#endif
_ASSERTE(true == GC_CALL_INTERIOR);
pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, (int)iptr | CHECK_APP_DOMAIN
DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
info.ebpFrame ? EBP - argAddr : argAddr - ESP,
true)));
}
}
}
}
else
{
// Is "this" enregistered. If so, report it as we might need to
// release the monitor for synchronized methods.
// Else, it is on the stack and will be reported below.
if (info.thisPtrResult != REGI_NA)
{
// Synchronized methods and methods satisfying
// MethodDesc::AcquiresInstMethodTableFromThis (i.e. those
// where "this" is reported in thisPtrResult) are
// not supported on value types.
_ASSERTE((regNumToMask(info.thisPtrResult) & info.iregMaskResult)== 0);
void * thisReg = getCalleeSavedReg(pContext, info.thisPtrResult);
pCallBack(hCallBack, (OBJECTREF *)thisReg, CHECK_APP_DOMAIN
DAC_ARG(DacSlotLocation(info.thisPtrResult, 0, false)));
}
}
}
else /* not interruptible */
{
pushedSize = scanArgRegTable(skipToArgReg(info, table), curOffs, &info);
RegMask regMask = info.regMaskResult;
RegMask iregMask = info.iregMaskResult;
ptrArgTP argMask = info.argMaskResult;
ptrArgTP iargMask = info.iargMaskResult;
unsigned argHnum = info.argHnumResult;
PTR_CBYTE argTab = info.argTabResult;
// Only synchronized methods and generic code that accesses
// the type context via "this" need to report "this".
// If its reported for other methods, its probably
// done incorrectly. So flag such cases.
_ASSERTE(info.thisPtrResult == REGI_NA ||
pCodeInfo->GetMethodDesc()->IsSynchronized() ||
pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis());
/* now report registers and arguments if we are not interrupted */
if (willContinueExecution)
{
/* Report all live pointer registers */
CHK_AND_REPORT_REG(REGI_EDI, regMask & RM_EDI, iregMask & RM_EDI, Edi);
CHK_AND_REPORT_REG(REGI_ESI, regMask & RM_ESI, iregMask & RM_ESI, Esi);
CHK_AND_REPORT_REG(REGI_EBX, regMask & RM_EBX, iregMask & RM_EBX, Ebx);
CHK_AND_REPORT_REG(REGI_EBP, regMask & RM_EBP, iregMask & RM_EBP, Ebp);
/* Esp cant be reported */
_ASSERTE(!(regMask & RM_ESP));
/* No callee-trashed registers */
_ASSERTE(!(regMask & RM_CALLEE_TRASHED));
/* EBP can't be reported unless we have an EBP-less frame */
_ASSERTE(!(regMask & RM_EBP) || !(info.ebpFrame));
/* Report any pending pointer arguments */
if (argTab != 0)
{
unsigned lowBits, stkOffs, argAddr, val;
// argMask does not fit in 32-bits
// thus arguments are reported via a table
// Both of these are very rare cases
do
{
val = fastDecodeUnsigned(argTab);
lowBits = val & OFFSET_MASK;
stkOffs = val & ~OFFSET_MASK;
_ASSERTE((lowBits == 0) || (lowBits == byref_OFFSET_FLAG));
argAddr = ESP + stkOffs;
#ifdef _DEBUG
if (dspPtr)
printf(" Pushed %sptr arg at [ESP+%02XH]",
lowBits ? "iptr " : "", stkOffs);
#endif
_ASSERTE(byref_OFFSET_FLAG == GC_CALL_INTERIOR);
pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, lowBits | CHECK_APP_DOMAIN
DAC_ARG(DacSlotLocation(REGI_ESP, stkOffs, true)));
}
while(--argHnum);
_ASSERTE(info.argTabResult + info.argTabBytes == argTab);
}
else
{
unsigned argAddr = ESP;
while (!isZero(argMask))
{
_ASSERTE(argHnum-- > 0);
if (toUnsigned(argMask) & 1)
{
bool iptr = false;
if (toUnsigned(iargMask) & 1)
iptr = true;
#ifdef _DEBUG
if (dspPtr)
printf(" Pushed ptr arg at [ESP+%02XH]",
argAddr - ESP);
#endif
_ASSERTE(true == GC_CALL_INTERIOR);
pCallBack(hCallBack, (OBJECTREF *)(size_t)argAddr, (int)iptr | CHECK_APP_DOMAIN
DAC_ARG(DacSlotLocation(REGI_ESP, argAddr - ESP, true)));
}
argMask >>= 1;
iargMask >>= 1;
argAddr += 4;
}
}
}
else
{
// Is "this" enregistered. If so, report it as we will need to
// release the monitor. Else, it is on the stack and will be
// reported below.
// For partially interruptible code, info.thisPtrResult will be
// the last known location of "this". So the compiler needs to
// generate information which is correct at every point in the code,
// not just at call sites.
if (info.thisPtrResult != REGI_NA)
{
// Synchronized methods on value types are not supported
_ASSERTE((regNumToMask(info.thisPtrResult) & info.iregMaskResult)== 0);
void * thisReg = getCalleeSavedReg(pContext, info.thisPtrResult);
pCallBack(hCallBack, (OBJECTREF *)thisReg, CHECK_APP_DOMAIN
DAC_ARG(DacSlotLocation(info.thisPtrResult, 0, false)));
}
}
} //info.interruptible
/* compute the argument base (reference point) */
unsigned argBase;
if (info.ebpFrame)
argBase = EBP;
else
argBase = ESP + pushedSize;
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
#endif
unsigned ptrAddr;
unsigned lowBits;
/* Process the untracked frame variable table */
#if defined(WIN64EXCEPTIONS) // funclets
// Filters are the only funclet that run during the 1st pass, and must have
// both the leaf and the parent frame reported. In order to avoid double
// reporting of the untracked variables, do not report them for the filter.
if (!pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo))
#endif // WIN64EXCEPTIONS
{
count = info.untrackedCnt;
int lastStkOffs = 0;
while (count-- > 0)
{
int stkOffs = fastDecodeSigned(table);
stkOffs = lastStkOffs - stkOffs;
lastStkOffs = stkOffs;
_ASSERTE(0 == ~OFFSET_MASK % sizeof(void*));
lowBits = OFFSET_MASK & stkOffs;
stkOffs &= ~OFFSET_MASK;
ptrAddr = argBase + stkOffs;
if (info.doubleAlign && stkOffs >= int(info.stackSize - sizeof(void*))) {
// We encode the arguments as if they were ESP based variables even though they aren't
// If this frame would have ben an ESP based frame, This fake frame is one DWORD
// smaller than the real frame because it did not push EBP but the real frame did.
// Thus to get the correct EBP relative offset we have to adjust by info.stackSize-sizeof(void*)
ptrAddr = EBP + (stkOffs-(info.stackSize - sizeof(void*)));
}
#ifdef _DEBUG
if (dspPtr)
{
printf(" Untracked %s%s local at [E",
(lowBits & pinned_OFFSET_FLAG) ? "pinned " : "",
(lowBits & byref_OFFSET_FLAG) ? "byref" : "");
int dspOffs = ptrAddr;
char frameType;
if (info.ebpFrame) {
dspOffs -= EBP;
frameType = 'B';
}
else {
dspOffs -= ESP;
frameType = 'S';
}
if (dspOffs < 0)
printf("%cP-%02XH]: ", frameType, -dspOffs);
else
printf("%cP+%02XH]: ", frameType, +dspOffs);
}
#endif
_ASSERTE((pinned_OFFSET_FLAG == GC_CALL_PINNED) &&
(byref_OFFSET_FLAG == GC_CALL_INTERIOR));
pCallBack(hCallBack, (OBJECTREF*)(size_t)ptrAddr, lowBits | CHECK_APP_DOMAIN
DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
info.ebpFrame ? EBP - ptrAddr : ptrAddr - ESP,
true)));
}
}
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xCAFE);
#endif
/* Process the frame variable lifetime table */
count = info.varPtrTableSize;
/* If we are not in the active method, we are currently pointing
* to the return address; at the return address stack variables
* can become dead if the call the last instruction of a try block
* and the return address is the jump around the catch block. Therefore
* we simply assume an offset inside of call instruction.
*/
unsigned newCurOffs;
if (willContinueExecution)
{
newCurOffs = (flags & ActiveStackFrame) ? curOffs // after "call"
: curOffs-1; // inside "call"
}
else
{
/* However if ExecutionAborted, then this must be one of the
* ExceptionFrames. Handle accordingly
*/
_ASSERTE(!(flags & AbortingCall) || !(flags & ActiveStackFrame));
newCurOffs = (flags & AbortingCall) ? curOffs-1 // inside "call"
: curOffs; // at faulting instr, or start of "try"
}
ptrOffs = 0;
while (count-- > 0)
{
int stkOffs;
unsigned begOffs;
unsigned endOffs;
stkOffs = fastDecodeUnsigned(table);
begOffs = ptrOffs + fastDecodeUnsigned(table);
endOffs = begOffs + fastDecodeUnsigned(table);
_ASSERTE(0 == ~OFFSET_MASK % sizeof(void*));
lowBits = OFFSET_MASK & stkOffs;
stkOffs &= ~OFFSET_MASK;
if (info.ebpFrame) {
stkOffs = -stkOffs;
_ASSERTE(stkOffs < 0);
}
else {
_ASSERTE(stkOffs >= 0);
}
ptrAddr = argBase + stkOffs;
/* Is this variable live right now? */
if (newCurOffs >= begOffs)
{
if (newCurOffs < endOffs)
{
#ifdef _DEBUG
if (dspPtr) {
printf(" Frame %s%s local at [E",
(lowBits & byref_OFFSET_FLAG) ? "byref " : "",
#ifndef WIN64EXCEPTIONS
(lowBits & this_OFFSET_FLAG) ? "this-ptr" : "");
#else
(lowBits & pinned_OFFSET_FLAG) ? "pinned" : "");
#endif
int dspOffs = ptrAddr;
char frameType;
if (info.ebpFrame) {
dspOffs -= EBP;
frameType = 'B';
}
else {
dspOffs -= ESP;
frameType = 'S';
}
if (dspOffs < 0)
printf("%cP-%02XH]: ", frameType, -dspOffs);
else
printf("%cP+%02XH]: ", frameType, +dspOffs);
}
#endif
unsigned flags = CHECK_APP_DOMAIN;
#ifndef WIN64EXCEPTIONS
// First Bit : byref
// Second Bit : this
// The second bit means `this` not `pinned`. So we ignore it.
flags |= lowBits & byref_OFFSET_FLAG;
#else
// First Bit : byref
// Second Bit : pinned
// Both bits are valid
flags |= lowBits;
#endif
_ASSERTE(byref_OFFSET_FLAG == GC_CALL_INTERIOR);
pCallBack(hCallBack, (OBJECTREF*)(size_t)ptrAddr, flags
DAC_ARG(DacSlotLocation(info.ebpFrame ? REGI_EBP : REGI_ESP,
info.ebpFrame ? EBP - ptrAddr : ptrAddr - ESP,
true)));
}
}
// exit loop early if start of live range is beyond PC, as ranges are sorted by lower bound
else break;
ptrOffs = begOffs;
}
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBABE);
#endif
#ifdef WIN64EXCEPTIONS // funclets
//
// If we're in a funclet, we do not want to report the incoming varargs. This is
// taken care of by the parent method and the funclet should access those arguments
// by way of the parent method's stack frame.
//
if(pCodeInfo->IsFunclet())
{
return true;
}
#endif // WIN64EXCEPTIONS
/* Are we a varargs function, if so we have to report all args
except 'this' (note that the GC tables created by the x86 jit
do not contain ANY arguments except 'this' (even if they
were statically declared */
if (info.varargs) {
LOG((LF_GCINFO, LL_INFO100, "Reporting incoming vararg GC refs\n"));
PTR_BYTE argsStart;
if (info.ebpFrame || info.doubleAlign)
argsStart = PTR_BYTE((size_t)EBP) + 2* sizeof(void*); // pushed EBP and retAddr
else
argsStart = PTR_BYTE((size_t)argBase) + info.stackSize + sizeof(void*); // ESP + locals + retAddr
#if defined(_DEBUG) && !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
// Note that I really want to say hCallBack is a GCCONTEXT, but this is pretty close
extern void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags);
_ASSERTE((void*) GcEnumObject == pCallBack);
#endif
GCCONTEXT *pCtx = (GCCONTEXT *) hCallBack;
// For varargs, look up the signature using the varArgSig token passed on the stack
PTR_VASigCookie varArgSig = *PTR_PTR_VASigCookie(argsStart);
promoteVarArgs(argsStart, varArgSig, pCtx);
}
return true;
}
#else // !USE_GC_INFO_DECODER
/*****************************************************************************
*
* Enumerate all live object references in that function using
* the virtual register set.
* Returns success of operation.
*/
bool EECodeManager::EnumGcRefs( PREGDISPLAY pRD,
EECodeInfo *pCodeInfo,
unsigned flags,
GCEnumCallback pCallBack,
LPVOID hCallBack,
DWORD relOffsetOverride)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
unsigned curOffs = pCodeInfo->GetRelOffset();
#ifdef _TARGET_ARM_
// On ARM, the low-order bit of an instruction pointer indicates Thumb vs. ARM mode.
// Mask this off; all instructions are two-byte aligned.
curOffs &= (~THUMB_CODE);
#endif // _TARGET_ARM_
#ifdef _DEBUG
// Get the name of the current method
const char * methodName = pCodeInfo->GetMethodDesc()->GetName();
LOG((LF_GCINFO, LL_INFO1000, "Reporting GC refs for %s at offset %04x.\n",
methodName, curOffs));
#endif
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
#if defined(STRESS_HEAP) && defined(PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED)
// When we simulate a hijack during gcstress
// we start with ActiveStackFrame and the offset
// after the call
// We need to make it look like a non-leaf frame
// so that it's treated like a regular hijack
if (flags & ActiveStackFrame)
{
GcInfoDecoder _gcInfoDecoder(
gcInfoToken,
DECODE_INTERRUPTIBILITY,
curOffs
);
if(!_gcInfoDecoder.IsInterruptible())
{
// This must be the offset after a call
#ifdef _DEBUG
GcInfoDecoder _safePointDecoder(gcInfoToken, (GcInfoDecoderFlags)0, 0);
_ASSERTE(_safePointDecoder.IsSafePoint(curOffs));
#endif
flags &= ~((unsigned)ActiveStackFrame);
}
}
#endif // STRESS_HEAP && PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
#ifdef _DEBUG
if (flags & ActiveStackFrame)
{
GcInfoDecoder _gcInfoDecoder(
gcInfoToken,
DECODE_INTERRUPTIBILITY,
curOffs
);
_ASSERTE(_gcInfoDecoder.IsInterruptible());
}
#endif
/* If we are not in the active method, we are currently pointing
* to the return address; at the return address stack variables
* can become dead if the call is the last instruction of a try block
* and the return address is the jump around the catch block. Therefore
* we simply assume an offset inside of call instruction.
* NOTE: The GcInfoDecoder depends on this; if you change it, you must
* revisit the GcInfoEncoder/Decoder
*/
if (!(flags & ExecutionAborted))
{
if (!(flags & ActiveStackFrame))
{
curOffs--;
LOG((LF_GCINFO, LL_INFO1000, "Adjusted GC reporting offset due to flags !ExecutionAborted && !ActiveStackFrame. Now reporting GC refs for %s at offset %04x.\n",
methodName, curOffs));
}
}
else
{
/* However if ExecutionAborted, then this must be one of the
* ExceptionFrames. Handle accordingly
*/
_ASSERTE(!(flags & AbortingCall) || !(flags & ActiveStackFrame));
if (flags & AbortingCall)
{
curOffs--;
LOG((LF_GCINFO, LL_INFO1000, "Adjusted GC reporting offset due to flags ExecutionAborted && AbortingCall. Now reporting GC refs for %s at offset %04x.\n",
methodName, curOffs));
}
}
// Check if we have been given an override value for relOffset
if (relOffsetOverride != NO_OVERRIDE_OFFSET)
{
// We've been given an override offset for GC Info
#ifdef _DEBUG
GcInfoDecoder _gcInfoDecoder(
gcInfoToken,
DECODE_CODE_LENGTH
);
// We only use override offset for wantsReportOnlyLeaf
_ASSERTE(_gcInfoDecoder.WantsReportOnlyLeaf());
#endif // _DEBUG
curOffs = relOffsetOverride;
#ifdef _TARGET_ARM_
// On ARM, the low-order bit of an instruction pointer indicates Thumb vs. ARM mode.
// Mask this off; all instructions are two-byte aligned.
curOffs &= (~THUMB_CODE);
#endif // _TARGET_ARM_
LOG((LF_GCINFO, LL_INFO1000, "Adjusted GC reporting offset to provided override offset. Now reporting GC refs for %s at offset %04x.\n",
methodName, curOffs));
}
#if defined(WIN64EXCEPTIONS) // funclets
if (pCodeInfo->GetJitManager()->IsFilterFunclet(pCodeInfo))
{
// Filters are the only funclet that run during the 1st pass, and must have
// both the leaf and the parent frame reported. In order to avoid double
// reporting of the untracked variables, do not report them for the filter.
flags |= NoReportUntracked;
}
#endif // WIN64EXCEPTIONS
bool reportScratchSlots;
// We report scratch slots only for leaf frames.
// A frame is non-leaf if we are executing a call, or a fault occurred in the function.
// The only case in which we need to report scratch slots for a non-leaf frame
// is when execution has to be resumed at the point of interruption (via ResumableFrame)
//Implement ResumableFrame
_ASSERTE( sizeof( BOOL ) >= sizeof( ActiveStackFrame ) );
reportScratchSlots = (flags & ActiveStackFrame) != 0;
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
GcInfoDecoderFlags (DECODE_GC_LIFETIMES | DECODE_SECURITY_OBJECT | DECODE_VARARG),
curOffs
);
if (!gcInfoDecoder.EnumerateLiveSlots(
pRD,
reportScratchSlots,
flags,
pCallBack,
hCallBack
))
{
return false;
}
#ifdef WIN64EXCEPTIONS // funclets
//
// If we're in a funclet, we do not want to report the incoming varargs. This is
// taken care of by the parent method and the funclet should access those arguments
// by way of the parent method's stack frame.
//
if(pCodeInfo->IsFunclet())
{
return true;
}
#endif // WIN64EXCEPTIONS
if (gcInfoDecoder.GetIsVarArg())
{
MethodDesc* pMD = pCodeInfo->GetMethodDesc();
_ASSERTE(pMD != NULL);
// This does not apply to x86 because of how it handles varargs (it never
// reports the arguments from the explicit method signature).
//
#ifndef _TARGET_X86_
//
// SPECIAL CASE:
// IL marshaling stubs have signatures that are marked as vararg,
// but they are callsite sigs that actually contain complete sig
// info. There are two reasons for this:
// 1) the stub callsites expect the method to be vararg
// 2) the marshaling stub must have full sig info so that
// it can do a ldarg.N on the arguments it needs to marshal.
// The result of this is that the code below will report the
// variable arguments twice--once from the va sig cookie and once
// from the explicit method signature (in the method's gc info).
//
// This fix to this is to early out of the va sig cookie reporting
// in this special case.
//
if (pMD->IsILStub())
{
return true;
}
#endif // !_TARGET_X86_
LOG((LF_GCINFO, LL_INFO100, "Reporting incoming vararg GC refs\n"));
// Find the offset of the VASigCookie. It's offsets are relative to
// the base of a FramedMethodFrame.
int VASigCookieOffset;
{
MetaSig msigFindVASig(pMD);
ArgIterator argit(&msigFindVASig);
VASigCookieOffset = argit.GetVASigCookieOffset() - TransitionBlock::GetOffsetOfArgs();
}
PTR_BYTE prevSP = dac_cast(GetCallerSp(pRD));
_ASSERTE(prevSP + VASigCookieOffset >= dac_cast(GetSP(pRD->pCurrentContext)));
#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
// Note that I really want to say hCallBack is a GCCONTEXT, but this is pretty close
extern void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags);
_ASSERTE((void*) GcEnumObject == pCallBack);
#endif // _DEBUG && !DACCESS_COMPILE
GCCONTEXT *pCtx = (GCCONTEXT *) hCallBack;
// For varargs, look up the signature using the varArgSig token passed on the stack
PTR_VASigCookie varArgSig = *PTR_PTR_VASigCookie(prevSP + VASigCookieOffset);
promoteVarArgs(prevSP, varArgSig, pCtx);
}
return true;
}
#endif // USE_GC_INFO_DECODER
#endif // !CROSSGEN_COMPILE
#ifdef _TARGET_X86_
/*****************************************************************************
*
* Return the address of the local security object reference
* using data that was previously cached before in UnwindStackFrame
* using StackwalkCacheUnwindInfo
*/
OBJECTREF* EECodeManager::GetAddrOfSecurityObjectFromCachedInfo(PREGDISPLAY pRD, StackwalkCacheUnwindInfo * stackwalkCacheUnwindInfo)
{
LIMITED_METHOD_CONTRACT;
size_t securityObjectOffset = stackwalkCacheUnwindInfo->securityObjectOffset;
_ASSERTE(securityObjectOffset != 0);
// We pretend that filters are ESP-based methods in UnwindEbpDoubleAlignFrame().
// Hence we cannot enforce this assert.
// _ASSERTE(stackwalkCacheUnwindInfo->fUseEbpAsFrameReg);
return (OBJECTREF *) (size_t) (*pRD->GetEbpLocation() - (securityObjectOffset * sizeof(void*)));
}
#endif // _TARGET_X86_
#if !defined(DACCESS_COMPILE) && !defined(CROSSGEN_COMPILE)
OBJECTREF* EECodeManager::GetAddrOfSecurityObject(CrawlFrame *pCF)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
REGDISPLAY* pRD = pCF->GetRegisterSet();
IJitManager* pJitMan = pCF->GetJitManager();
METHODTOKEN methodToken = pCF->GetMethodToken();
unsigned relOffset = pCF->GetRelOffset();
CodeManState* pState = pCF->GetCodeManState();
GCInfoToken gcInfoToken = pJitMan->GetGCInfoToken(methodToken);
_ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
#ifndef USE_GC_INFO_DECODER
CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
/* Extract the necessary information from the info block header */
stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, // truncation
relOffset,
&stateBuf->hdrInfoBody);
pState->dwIsSet = 1;
if (stateBuf->hdrInfoBody.securityCheck)
{
_ASSERTE(stateBuf->hdrInfoBody.ebpFrame);
if(stateBuf->hdrInfoBody.prologOffs == hdrInfo::NOT_IN_PROLOG &&
stateBuf->hdrInfoBody.epilogOffs == hdrInfo::NOT_IN_EPILOG)
{
return (OBJECTREF *)(size_t)(*pRD->GetEbpLocation() - GetSecurityObjectOffset(&stateBuf->hdrInfoBody));
}
}
#else // !USE_GC_INFO_DECODER
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
DECODE_SECURITY_OBJECT
);
INT32 spOffset = gcInfoDecoder.GetSecurityObjectStackSlot();
if( spOffset != NO_SECURITY_OBJECT )
{
UINT_PTR uCallerSP = GetCallerSp(pRD);
if (pCF->IsFunclet())
{
if (!pCF->IsFilterFunclet())
{
// Cannot retrieve the security object for a non-filter funclet.
return NULL;
}
DWORD dwParentOffset = 0;
UINT_PTR uParentCallerSP = 0;
// If this is a filter funclet, retrieve the information of the parent method
// and use that to find the security object.
ExceptionTracker::FindParentStackFrameEx(pCF, &dwParentOffset, &uParentCallerSP);
relOffset = dwParentOffset;
uCallerSP = uParentCallerSP;
}
// Security object is always live anyplace we can throw or take a GC
OBJECTREF* pSlot = (OBJECTREF*) (spOffset + uCallerSP);
return pSlot;
}
#endif // USE_GC_INFO_DECODER
return NULL;
}
#endif // !DACCESS_COMPILE && !CROSSGEN_COMPILE
#ifndef CROSSGEN_COMPILE
/*****************************************************************************
*
* Returns "this" pointer if it is a non-static method
* AND the object is still alive.
* Returns NULL in all other cases.
* Unfortunately, the semantics of this call currently depend on the architecture.
* On non-x86 architectures, where we use GcInfo{En,De}Coder, this returns NULL for
* all cases except the case where the GenericsContext is determined via "this." On x86,
* it will definitely return a non-NULL value in that case, and for synchronized methods;
* it may also return a non-NULL value for other cases, depending on how the method is compiled.
*/
OBJECTREF EECodeManager::GetInstance( PREGDISPLAY pContext,
EECodeInfo* pCodeInfo)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
SUPPORTS_DAC;
} CONTRACTL_END;
#ifndef USE_GC_INFO_DECODER
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
unsigned relOffset = pCodeInfo->GetRelOffset();
PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
hdrInfo info;
unsigned stackDepth;
TADDR taArgBase;
unsigned count;
/* Extract the necessary information from the info block header */
table += DecodeGCHdrInfo(gcInfoToken,
relOffset,
&info);
// We do not have accurate information in the prolog or the epilog
if (info.prologOffs != hdrInfo::NOT_IN_PROLOG ||
info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
{
return NULL;
}
if (info.interruptible)
{
stackDepth = scanArgRegTableI(skipToArgReg(info, table), relOffset, relOffset, &info);
}
else
{
stackDepth = scanArgRegTable (skipToArgReg(info, table), (unsigned)relOffset, &info);
}
if (info.ebpFrame)
{
_ASSERTE(stackDepth == 0);
taArgBase = GetRegdisplayFP(pContext);
}
else
{
taArgBase = pContext->SP + stackDepth;
}
// Only synchronized methods and generic code that accesses
// the type context via "this" need to report "this".
// If it's reported for other methods, it's probably
// done incorrectly. So flag such cases.
_ASSERTE(info.thisPtrResult == REGI_NA ||
pCodeInfo->GetMethodDesc()->IsSynchronized() ||
pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis());
if (info.thisPtrResult != REGI_NA)
{
// the register contains the Object pointer.
TADDR uRegValue = *(reinterpret_cast(getCalleeSavedReg(pContext, info.thisPtrResult)));
return ObjectToOBJECTREF(PTR_Object(uRegValue));
}
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *)++ == 0xBEEF);
#endif
#ifndef WIN64EXCEPTIONS
/* Parse the untracked frame variable table */
/* The 'this' pointer can never be located in the untracked table */
/* as we only allow pinned and byrefs in the untracked table */
count = info.untrackedCnt;
while (count-- > 0)
{
fastSkipSigned(table);
}
/* Look for the 'this' pointer in the frame variable lifetime table */
count = info.varPtrTableSize;
unsigned tmpOffs = 0;
while (count-- > 0)
{
unsigned varOfs = fastDecodeUnsigned(table);
unsigned begOfs = tmpOffs + fastDecodeUnsigned(table);
unsigned endOfs = begOfs + fastDecodeUnsigned(table);
_ASSERTE(!info.ebpFrame || (varOfs!=0));
/* Is this variable live right now? */
if (((unsigned)relOffset >= begOfs) && ((unsigned)relOffset < endOfs))
{
/* Does it contain the 'this' pointer */
if (varOfs & this_OFFSET_FLAG)
{
unsigned ofs = varOfs & ~OFFSET_MASK;
/* Tracked locals for EBP frames are always at negative offsets */
if (info.ebpFrame)
taArgBase -= ofs;
else
taArgBase += ofs;
return (OBJECTREF)(size_t)(*PTR_DWORD(taArgBase));
}
}
tmpOffs = begOfs;
}
#if VERIFY_GC_TABLES
_ASSERTE(*castto(table, unsigned short *) == 0xBABE);
#endif
#else // WIN64EXCEPTIONS
if (pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis()) // Generic Context is "this"
{
// Untracked table must have at least one entry - this pointer
_ASSERTE(info.untrackedCnt > 0);
// The first entry must be "this" pointer
int stkOffs = fastDecodeSigned(table);
taArgBase -= stkOffs & ~OFFSET_MASK;
return (OBJECTREF)(size_t)(*PTR_DWORD(taArgBase));
}
#endif // WIN64EXCEPTIONS
return NULL;
#else // !USE_GC_INFO_DECODER
PTR_VOID token = EECodeManager::GetExactGenericsToken(pContext, pCodeInfo);
OBJECTREF oRef = ObjectToOBJECTREF(PTR_Object(dac_cast(token)));
VALIDATEOBJECTREF(oRef);
return oRef;
#endif // USE_GC_INFO_DECODER
}
#endif // !CROSSGEN_COMPILE
GenericParamContextType EECodeManager::GetParamContextType(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo)
{
LIMITED_METHOD_DAC_CONTRACT;
#ifndef USE_GC_INFO_DECODER
/* Extract the necessary information from the info block header */
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
PTR_VOID methodInfoPtr = pCodeInfo->GetGCInfo();
unsigned relOffset = pCodeInfo->GetRelOffset();
hdrInfo info;
PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
table += DecodeGCHdrInfo(gcInfoToken,
relOffset,
&info);
if (!info.genericsContext ||
info.prologOffs != hdrInfo::NOT_IN_PROLOG ||
info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
{
return GENERIC_PARAM_CONTEXT_NONE;
}
if (info.genericsContextIsMethodDesc)
{
return GENERIC_PARAM_CONTEXT_METHODDESC;
}
return GENERIC_PARAM_CONTEXT_METHODTABLE;
// On x86 the generic param context parameter is never this.
#else // !USE_GC_INFO_DECODER
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
GcInfoDecoderFlags (DECODE_GENERICS_INST_CONTEXT)
);
INT32 spOffsetGenericsContext = gcInfoDecoder.GetGenericsInstContextStackSlot();
if (spOffsetGenericsContext != NO_GENERICS_INST_CONTEXT)
{
if (gcInfoDecoder.HasMethodDescGenericsInstContext())
{
return GENERIC_PARAM_CONTEXT_METHODDESC;
}
else if (gcInfoDecoder.HasMethodTableGenericsInstContext())
{
return GENERIC_PARAM_CONTEXT_METHODTABLE;
}
return GENERIC_PARAM_CONTEXT_THIS;
}
return GENERIC_PARAM_CONTEXT_NONE;
#endif // USE_GC_INFO_DECODER
}
#ifndef CROSSGEN_COMPILE
/*****************************************************************************
*
* Returns the extra argument passed to to shared generic code if it is still alive.
* Returns NULL in all other cases.
*/
PTR_VOID EECodeManager::GetParamTypeArg(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo)
{
LIMITED_METHOD_DAC_CONTRACT;
#ifndef USE_GC_INFO_DECODER
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
PTR_VOID methodInfoPtr = pCodeInfo->GetGCInfo();
unsigned relOffset = pCodeInfo->GetRelOffset();
/* Extract the necessary information from the info block header */
hdrInfo info;
PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
table += DecodeGCHdrInfo(gcInfoToken,
relOffset,
&info);
if (!info.genericsContext ||
info.prologOffs != hdrInfo::NOT_IN_PROLOG ||
info.epilogOffs != hdrInfo::NOT_IN_EPILOG)
{
return NULL;
}
TADDR fp = GetRegdisplayFP(pContext);
TADDR taParamTypeArg = *PTR_TADDR(fp - GetParamTypeArgOffset(&info));
return PTR_VOID(taParamTypeArg);
#else // !USE_GC_INFO_DECODER
return EECodeManager::GetExactGenericsToken(pContext, pCodeInfo);
#endif // USE_GC_INFO_DECODER
}
#endif // !CROSSGEN_COMPILE
#if defined(WIN64EXCEPTIONS) && defined(USE_GC_INFO_DECODER) && !defined(CROSSGEN_COMPILE)
/*
Returns the generics token. This is used by GetInstance() and GetParamTypeArg() on WIN64.
*/
//static
PTR_VOID EECodeManager::GetExactGenericsToken(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo)
{
LIMITED_METHOD_DAC_CONTRACT;
return EECodeManager::GetExactGenericsToken(GetCallerSp(pContext), pCodeInfo);
}
//static
PTR_VOID EECodeManager::GetExactGenericsToken(SIZE_T baseStackSlot,
EECodeInfo * pCodeInfo)
{
LIMITED_METHOD_DAC_CONTRACT;
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
GcInfoDecoderFlags (DECODE_PSP_SYM | DECODE_GENERICS_INST_CONTEXT)
);
INT32 spOffsetGenericsContext = gcInfoDecoder.GetGenericsInstContextStackSlot();
if (spOffsetGenericsContext != NO_GENERICS_INST_CONTEXT)
{
TADDR taSlot;
if (pCodeInfo->IsFunclet())
{
INT32 spOffsetPSPSym = gcInfoDecoder.GetPSPSymStackSlot();
_ASSERTE(spOffsetPSPSym != NO_PSP_SYM);
#ifdef _TARGET_AMD64_
// On AMD64 the spOffsetPSPSym is relative to the "Initial SP": the stack
// pointer at the end of the prolog before and dynamic allocations, so it
// can be the same for funclets and the main function.
// However, we have a caller SP, so we need to convert
baseStackSlot -= pCodeInfo->GetFixedStackSize();
#endif // _TARGET_AMD64_
// For funclets we have to do an extra dereference to get the PSPSym first.
TADDR newBaseStackSlot = *PTR_TADDR(baseStackSlot + spOffsetPSPSym);
#ifdef _TARGET_AMD64_
// On AMD64 the PSPSym stores the "Initial SP": the stack pointer at the end of
// prolog, before any dynamic allocations.
// However, the GenericsContext offset is relative to the caller SP for all
// platforms. So here we adjust to convert AMD64's initial sp to a caller SP.
// But we have to be careful to use the main function's EECodeInfo, not the
// funclet's EECodeInfo because they have different stack sizes!
newBaseStackSlot += pCodeInfo->GetMainFunctionInfo().GetFixedStackSize();
#endif // _TARGET_AMD64_
taSlot = (TADDR)( spOffsetGenericsContext + newBaseStackSlot );
}
else
{
taSlot = (TADDR)( spOffsetGenericsContext + baseStackSlot );
}
TADDR taExactGenericsToken = *PTR_TADDR(taSlot);
return PTR_VOID(taExactGenericsToken);
}
return NULL;
}
#endif // WIN64EXCEPTIONS && USE_GC_INFO_DECODER && !CROSSGEN_COMPILE
#ifndef CROSSGEN_COMPILE
/*****************************************************************************/
void * EECodeManager::GetGSCookieAddr(PREGDISPLAY pContext,
EECodeInfo * pCodeInfo,
CodeManState * pState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
_ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
unsigned relOffset = pCodeInfo->GetRelOffset();
#ifdef WIN64EXCEPTIONS
if (pCodeInfo->IsFunclet())
{
return NULL;
}
#endif
#ifndef USE_GC_INFO_DECODER
CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
/* Extract the necessary information from the info block header */
hdrInfo * info = &stateBuf->hdrInfoBody;
stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, // truncation
relOffset,
info);
pState->dwIsSet = 1;
if (info->prologOffs != hdrInfo::NOT_IN_PROLOG ||
info->epilogOffs != hdrInfo::NOT_IN_EPILOG ||
info->gsCookieOffset == INVALID_GS_COOKIE_OFFSET)
{
return NULL;
}
if (info->ebpFrame)
{
DWORD curEBP = GetRegdisplayFP(pContext);
return PVOID(SIZE_T(curEBP - info->gsCookieOffset));
}
else
{
PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info) + stateBuf->hdrInfoSize;
unsigned argSize = GetPushedArgSize(info, table, relOffset);
return PVOID(SIZE_T(pContext->SP + argSize + info->gsCookieOffset));
}
#else // !USE_GC_INFO_DECODER
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
DECODE_GS_COOKIE
);
INT32 spOffsetGSCookie = gcInfoDecoder.GetGSCookieStackSlot();
if (spOffsetGSCookie != NO_GS_COOKIE)
{
if(relOffset >= gcInfoDecoder.GetGSCookieValidRangeStart()
&& relOffset < gcInfoDecoder.GetGSCookieValidRangeEnd())
{
SIZE_T baseStackSlot = GetCallerSp(pContext);
return (LPVOID)( spOffsetGSCookie + baseStackSlot );
}
}
return NULL;
#endif // USE_GC_INFO_DECODER
}
#endif // !CROSSGEN_COMPILE
#ifndef USE_GC_INFO_DECODER
/*****************************************************************************
*
* Returns true if the given IP is in the given method's prolog or epilog.
*/
bool EECodeManager::IsInPrologOrEpilog(DWORD relPCoffset,
GCInfoToken gcInfoToken,
size_t* prologSize)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken, relPCoffset, &info);
if (prologSize)
*prologSize = info.prologSize;
return ((info.prologOffs != hdrInfo::NOT_IN_PROLOG) ||
(info.epilogOffs != hdrInfo::NOT_IN_EPILOG));
}
/*****************************************************************************
*
* Returns true if the given IP is in the synchronized region of the method (valid for synchronized functions only)
*/
bool EECodeManager::IsInSynchronizedRegion(DWORD relOffset,
GCInfoToken gcInfoToken,
unsigned flags)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken, relOffset, &info);
// We should be called only for synchronized methods
_ASSERTE(info.syncStartOffset != INVALID_SYNC_OFFSET && info.syncEndOffset != INVALID_SYNC_OFFSET);
_ASSERTE(info.syncStartOffset < info.syncEndOffset);
_ASSERTE(info.epilogCnt <= 1);
_ASSERTE(info.epilogCnt == 0 || info.syncEndOffset <= info.syncEpilogStart);
return (info.syncStartOffset < relOffset && relOffset < info.syncEndOffset) ||
(info.syncStartOffset == relOffset && (flags & (ActiveStackFrame|ExecutionAborted))) ||
// Synchronized methods have at most one epilog. The epilog does not have to be at the end of the method though.
// Everything after the epilog is also in synchronized region.
(info.epilogCnt != 0 && info.syncEpilogStart + info.epilogSize <= relOffset);
}
#endif // !USE_GC_INFO_DECODER
/*****************************************************************************
*
* Returns the size of a given function.
*/
size_t EECodeManager::GetFunctionSize(GCInfoToken gcInfoToken)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
#ifndef USE_GC_INFO_DECODER
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken, 0, &info);
return info.methodSize;
#else // !USE_GC_INFO_DECODER
GcInfoDecoder gcInfoDecoder(
gcInfoToken,
DECODE_CODE_LENGTH
);
UINT32 codeLength = gcInfoDecoder.GetCodeLength();
_ASSERTE( codeLength > 0 );
return codeLength;
#endif // USE_GC_INFO_DECODER
}
/*****************************************************************************
*
* Returns the size of a given function.
*/
ReturnKind EECodeManager::GetReturnKind(GCInfoToken gcInfoToken)
{
CONTRACTL{
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
if (!gcInfoToken.IsReturnKindAvailable())
{
return RT_Illegal;
}
#ifndef USE_GC_INFO_DECODER
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken, 0, &info);
return info.returnKind;
#else // !USE_GC_INFO_DECODER
GcInfoDecoder gcInfoDecoder(gcInfoToken, DECODE_RETURN_KIND);
return gcInfoDecoder.GetReturnKind();
#endif // USE_GC_INFO_DECODER
}
#ifndef USE_GC_INFO_DECODER
/*****************************************************************************
*
* Returns the size of the frame of the given function.
*/
unsigned int EECodeManager::GetFrameSize(GCInfoToken gcInfoToken)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken, 0, &info);
// currently only used by E&C callers need to know about doubleAlign
// in all likelyhood
_ASSERTE(!info.doubleAlign);
return info.stackSize;
}
#endif // USE_GC_INFO_DECODER
#ifndef DACCESS_COMPILE
/*****************************************************************************/
#ifndef WIN64EXCEPTIONS
const BYTE* EECodeManager::GetFinallyReturnAddr(PREGDISPLAY pReg)
{
LIMITED_METHOD_CONTRACT;
return *(const BYTE**)(size_t)(GetRegdisplaySP(pReg));
}
BOOL EECodeManager::IsInFilter(GCInfoToken gcInfoToken,
unsigned offset,
PCONTEXT pCtx,
DWORD curNestLevel)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
/* Extract the necessary information from the info block header */
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken,
offset,
&info);
/* make sure that we have an ebp stack frame */
_ASSERTE(info.ebpFrame);
_ASSERTE(info.handlers); // This will alway be set. Remove it
TADDR baseSP;
DWORD nestingLevel;
FrameType frameType = GetHandlerFrameInfo(&info, pCtx->Ebp,
pCtx->Esp, (DWORD) IGNORE_VAL,
&baseSP, &nestingLevel);
_ASSERTE(frameType != FR_INVALID);
// _ASSERTE(nestingLevel == curNestLevel);
return frameType == FR_FILTER;
}
BOOL EECodeManager::LeaveFinally(GCInfoToken gcInfoToken,
unsigned offset,
PCONTEXT pCtx)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken,
offset,
&info);
DWORD nestingLevel;
GetHandlerFrameInfo(&info, pCtx->Ebp, pCtx->Esp, (DWORD) IGNORE_VAL, NULL, &nestingLevel);
// Compute an index into the stack-based table of esp values from
// each level of catch block.
PTR_TADDR pBaseSPslots = GetFirstBaseSPslotPtr(pCtx->Ebp, &info);
PTR_TADDR pPrevSlot = pBaseSPslots - (nestingLevel - 1);
/* Currently, LeaveFinally() is not used if the finally is invoked in the
second pass for unwinding. So we expect the finally to be called locally */
_ASSERTE(*pPrevSlot == LCL_FINALLY_MARK);
*pPrevSlot = 0; // Zero out the previous shadow ESP
pCtx->Esp += sizeof(TADDR); // Pop the return value off the stack
return TRUE;
}
void EECodeManager::LeaveCatch(GCInfoToken gcInfoToken,
unsigned offset,
PCONTEXT pCtx)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
#ifdef _DEBUG
TADDR baseSP;
DWORD nestingLevel;
bool hasInnerFilter;
hdrInfo info;
DecodeGCHdrInfo(gcInfoToken, offset, &info);
GetHandlerFrameInfo(&info, pCtx->Ebp, pCtx->Esp, (DWORD) IGNORE_VAL,
&baseSP, &nestingLevel, &hasInnerFilter);
// _ASSERTE(frameType == FR_HANDLER);
// _ASSERTE(pCtx->Esp == baseSP);
#endif
return;
}
#endif // !WIN64EXCEPTIONS
#endif // #ifndef DACCESS_COMPILE
#ifdef DACCESS_COMPILE
void EECodeManager::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
DAC_ENUM_VTHIS();
}
#endif // #ifdef DACCESS_COMPILE
#ifdef _TARGET_X86_
/*
* GetAmbientSP
*
* This function computes the zero-depth stack pointer for the given nesting
* level within the method given. Nesting level is the the depth within
* try-catch-finally blocks, and is zero based. It is up to the caller to
* supply a valid nesting level value.
*
*/
TADDR EECodeManager::GetAmbientSP(PREGDISPLAY pContext,
EECodeInfo *pCodeInfo,
DWORD dwRelOffset,
DWORD nestingLevel,
CodeManState *pState)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
_ASSERTE(sizeof(CodeManStateBuf) <= sizeof(pState->stateBuf));
CodeManStateBuf * stateBuf = (CodeManStateBuf*)pState->stateBuf;
PTR_CBYTE table = PTR_CBYTE(gcInfoToken.Info);
/* Extract the necessary information from the info block header */
stateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken,
dwRelOffset,
&stateBuf->hdrInfoBody);
table += stateBuf->hdrInfoSize;
pState->dwIsSet = 1;
#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
if (trFixContext)
{
printf("GetAmbientSP [%s][%s] for %s.%s: ",
stateBuf->hdrInfoBody.ebpFrame?"ebp":" ",
stateBuf->hdrInfoBody.interruptible?"int":" ",
"UnknownClass","UnknownMethod");
fflush(stdout);
}
#endif // _DEBUG && !DACCESS_COMPILE
if ((stateBuf->hdrInfoBody.prologOffs != hdrInfo::NOT_IN_PROLOG) ||
(stateBuf->hdrInfoBody.epilogOffs != hdrInfo::NOT_IN_EPILOG))
{
return NULL;
}
/* make sure that we have an ebp stack frame */
if (stateBuf->hdrInfoBody.handlers)
{
_ASSERTE(stateBuf->hdrInfoBody.ebpFrame);
TADDR baseSP;
GetHandlerFrameInfo(&stateBuf->hdrInfoBody,
GetRegdisplayFP(pContext),
(DWORD) IGNORE_VAL,
nestingLevel,
&baseSP);
_ASSERTE((GetRegdisplayFP(pContext) >= baseSP) && (baseSP >= GetRegdisplaySP(pContext)));
return baseSP;
}
_ASSERTE(nestingLevel == 0);
if (stateBuf->hdrInfoBody.ebpFrame)
{
return GetOutermostBaseFP(GetRegdisplayFP(pContext), &stateBuf->hdrInfoBody);
}
TADDR baseSP = GetRegdisplaySP(pContext);
if (stateBuf->hdrInfoBody.interruptible)
{
baseSP += scanArgRegTableI(skipToArgReg(stateBuf->hdrInfoBody, table),
dwRelOffset,
dwRelOffset,
&stateBuf->hdrInfoBody);
}
else
{
baseSP += scanArgRegTable(skipToArgReg(stateBuf->hdrInfoBody, table),
dwRelOffset,
&stateBuf->hdrInfoBody);
}
return baseSP;
}
#endif // _TARGET_X86_
/*
Get the number of bytes used for stack parameters.
This is currently only used on x86.
*/
// virtual
ULONG32 EECodeManager::GetStackParameterSize(EECodeInfo * pCodeInfo)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
SUPPORTS_DAC;
} CONTRACTL_END;
#if defined(_TARGET_X86_)
#if defined(WIN64EXCEPTIONS)
if (pCodeInfo->IsFunclet())
{
// Funclet has no stack argument
return 0;
}
#endif // WIN64EXCEPTIONS
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();
unsigned dwOffset = pCodeInfo->GetRelOffset();
CodeManState state;
state.dwIsSet = 0;
_ASSERTE(sizeof(CodeManStateBuf) <= sizeof(state.stateBuf));
CodeManStateBuf * pStateBuf = reinterpret_cast(state.stateBuf);
hdrInfo * pHdrInfo = &(pStateBuf->hdrInfoBody);
pStateBuf->hdrInfoSize = (DWORD)DecodeGCHdrInfo(gcInfoToken, dwOffset, pHdrInfo);
// We need to subtract 4 here because ESPIncrOnReturn() includes the stack slot containing the return
// address.
return (ULONG32)::GetStackParameterSize(pHdrInfo);
#else
return 0;
#endif // _TARGET_X86_
}