diff --git a/src/Native/Runtime/AsmOffsets.cpp b/src/Native/Runtime/AsmOffsets.cpp new file mode 100644 index 00000000000..218637855d0 --- /dev/null +++ b/src/Native/Runtime/AsmOffsets.cpp @@ -0,0 +1,19 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifdef _ARM_ + +#define PLAT_ASM_OFFSET(offset, cls, member) OFFSETOF__##cls##__##member equ 0x##offset +#define PLAT_ASM_SIZEOF(size, cls ) SIZEOF__##cls equ 0x##size +#define PLAT_ASM_CONST(constant, expr) expr equ 0x##constant + +#else + +#define PLAT_ASM_OFFSET(offset, cls, member) OFFSETOF__##cls##__##member equ 0##offset##h +#define PLAT_ASM_SIZEOF(size, cls ) SIZEOF__##cls equ 0##size##h +#define PLAT_ASM_CONST(constant, expr) expr equ 0##constant##h + +#endif + +#include "AsmOffsets.h" diff --git a/src/Native/Runtime/AsmOffsets.h b/src/Native/Runtime/AsmOffsets.h new file mode 100644 index 00000000000..17b0a9289d2 --- /dev/null +++ b/src/Native/Runtime/AsmOffsets.h @@ -0,0 +1,171 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// This file is used by AsmOffsets.cpp to validate that our +// assembly-code offsets always match their C++ counterparts. +// +// You must #define PLAT_ASM_OFFSET and PLAT_ASM_SIZEOF before you #include this file +// + +#if defined(_X86_) +#define ASM_OFFSET(x86_offset, arm_offset, amd64_offset, cls, member) PLAT_ASM_OFFSET(x86_offset, cls, member) +#define ASM_SIZEOF(x86_offset, arm_offset, amd64_offset, cls ) PLAT_ASM_SIZEOF(x86_offset, cls) +#define ASM_CONST(x86_const, arm_const, amd64_const, expr) PLAT_ASM_CONST(x86_const, expr) +#elif defined(_AMD64_) +#define ASM_OFFSET(x86_offset, arm_offset, amd64_offset, cls, member) PLAT_ASM_OFFSET(amd64_offset, cls, member) +#define ASM_SIZEOF(x86_offset, arm_offset, amd64_offset, cls ) PLAT_ASM_SIZEOF(amd64_offset, cls) +#define ASM_CONST(x86_const, arm_const, amd64_const, expr) PLAT_ASM_CONST(amd64_const, expr) +#elif defined(_ARM_) +#define ASM_OFFSET(x86_offset, arm_offset, amd64_offset, cls, member) PLAT_ASM_OFFSET(arm_offset, cls, member) +#define ASM_SIZEOF(x86_offset, arm_offset, amd64_offset, cls ) PLAT_ASM_SIZEOF(arm_offset, cls) +#define ASM_CONST(x86_const, arm_const, amd64_const, expr) PLAT_ASM_CONST(arm_const, expr) +#else +#error unknown architecture +#endif + +#if !defined(USE_PORTABLE_HELPERS) // no asm helpers to keep in sync with + +// +// NOTE: the offsets MUST be in hex notation WITHOUT the 0x prefix +// +// x86, arm,amd64, constant symbol +ASM_CONST(14c08,14c08,14c08, RH_LARGE_OBJECT_SIZE) +ASM_CONST( 400, 400, 800, CLUMP_SIZE) +ASM_CONST( a, a, b, LOG2_CLUMP_SIZE) + +// x86, arm,amd64, class, member + +ASM_OFFSET( 0, 0, 0, Object, m_pEEType) + +ASM_OFFSET( 4, 4, 8, Array, m_Length) + +ASM_OFFSET( 0, 0, 0, EEType, m_usComponentSize) +ASM_OFFSET( 2, 2, 2, EEType, m_usFlags) +ASM_OFFSET( 4, 4, 4, EEType, m_uBaseSize) +ASM_OFFSET( 14, 14, 18, EEType, m_VTable) + +ASM_OFFSET( 0, 0, 0, Thread, m_rgbAllocContextBuffer) +ASM_OFFSET( 28, 28, 38, Thread, m_ThreadStateFlags) +ASM_OFFSET( 2c, 2c, 40, Thread, m_pTransitionFrame) +ASM_OFFSET( 30, 30, 48, Thread, m_pHackPInvokeTunnel) +ASM_OFFSET( 40, 40, 68, Thread, m_ppvHijackedReturnAddressLocation) +ASM_OFFSET( 44, 44, 70, Thread, m_pvHijackedReturnAddress) +ASM_OFFSET( 48, 48, 78, Thread, m_pExInfoStackHead) + +ASM_SIZEOF( 14, 14, 20, EHEnum) + +ASM_SIZEOF( b0, 128, 250, ExInfo) +ASM_OFFSET( 0, 0, 0, ExInfo, m_pPrevExInfo) +ASM_OFFSET( 4, 4, 8, ExInfo, m_pExContext) +ASM_OFFSET( 8, 8, 10, ExInfo, m_exception) +ASM_OFFSET( 0c, 0c, 18, ExInfo, m_kind) +ASM_OFFSET( 0d, 0d, 19, ExInfo, m_passNumber) +ASM_OFFSET( 10, 10, 1c, ExInfo, m_idxCurClause) +ASM_OFFSET( 14, 18, 20, ExInfo, m_frameIter) +ASM_OFFSET( ac, 120, 240, ExInfo, m_notifyDebuggerSP) + +ASM_OFFSET( 0, 0, 0, alloc_context, alloc_ptr) +ASM_OFFSET( 4, 4, 8, alloc_context, alloc_limit) + + +ASM_OFFSET( 4, 4, 8, RuntimeInstance, m_pThreadStore) + +ASM_OFFSET( 0, 4, 0, PInvokeTransitionFrame, m_RIP) +ASM_OFFSET( 4, 8, 8, PInvokeTransitionFrame, m_FramePointer) +ASM_OFFSET( 8, 0C, 10, PInvokeTransitionFrame, m_pThread) +ASM_OFFSET( 0C, 10, 18, PInvokeTransitionFrame, m_dwFlags) +ASM_OFFSET( 10, 14, 20, PInvokeTransitionFrame, m_PreservedRegs) + +ASM_SIZEOF( 98, 108, 220, StackFrameIterator) +ASM_OFFSET( 08, 08, 10, StackFrameIterator, m_FramePointer) +ASM_OFFSET( 0C, 0C, 18, StackFrameIterator, m_ControlPC) +ASM_OFFSET( 10, 10, 20, StackFrameIterator, m_RegDisplay) + +ASM_SIZEOF( 1c, 70, 100, PAL_LIMITED_CONTEXT) +ASM_OFFSET( 0, 24, 0, PAL_LIMITED_CONTEXT, IP) +#ifdef _ARM_ +ASM_OFFSET( 0, 0, 0, PAL_LIMITED_CONTEXT, R0) +ASM_OFFSET( 0, 4, 0, PAL_LIMITED_CONTEXT, R4) +ASM_OFFSET( 0, 8, 0, PAL_LIMITED_CONTEXT, R5) +ASM_OFFSET( 0, 0c, 0, PAL_LIMITED_CONTEXT, R6) +ASM_OFFSET( 0, 10, 0, PAL_LIMITED_CONTEXT, R7) +ASM_OFFSET( 0, 14, 0, PAL_LIMITED_CONTEXT, R8) +ASM_OFFSET( 0, 18, 0, PAL_LIMITED_CONTEXT, R9) +ASM_OFFSET( 0, 1c, 0, PAL_LIMITED_CONTEXT, R10) +ASM_OFFSET( 0, 20, 0, PAL_LIMITED_CONTEXT, R11) +ASM_OFFSET( 0, 28, 0, PAL_LIMITED_CONTEXT, SP) +ASM_OFFSET( 0, 2c, 0, PAL_LIMITED_CONTEXT, LR) +#else // _ARM_ +ASM_OFFSET( 4, 0, 8, PAL_LIMITED_CONTEXT, Rsp) +ASM_OFFSET( 8, 0, 10, PAL_LIMITED_CONTEXT, Rbp) +ASM_OFFSET( 0c, 0, 18, PAL_LIMITED_CONTEXT, Rdi) +ASM_OFFSET( 10, 0, 20, PAL_LIMITED_CONTEXT, Rsi) +ASM_OFFSET( 14, 0, 28, PAL_LIMITED_CONTEXT, Rax) +ASM_OFFSET( 18, 0, 30, PAL_LIMITED_CONTEXT, Rbx) +#ifdef _AMD64_ +ASM_OFFSET( 0, 0, 38, PAL_LIMITED_CONTEXT, R12) +ASM_OFFSET( 0, 0, 40, PAL_LIMITED_CONTEXT, R13) +ASM_OFFSET( 0, 0, 48, PAL_LIMITED_CONTEXT, R14) +ASM_OFFSET( 0, 0, 50, PAL_LIMITED_CONTEXT, R15) +ASM_OFFSET( 0, 0, 60, PAL_LIMITED_CONTEXT, Xmm6) +ASM_OFFSET( 0, 0, 70, PAL_LIMITED_CONTEXT, Xmm7) +ASM_OFFSET( 0, 0, 80, PAL_LIMITED_CONTEXT, Xmm8) +ASM_OFFSET( 0, 0, 90, PAL_LIMITED_CONTEXT, Xmm9) +ASM_OFFSET( 0, 0, 0a0, PAL_LIMITED_CONTEXT, Xmm10) +ASM_OFFSET( 0, 0, 0b0, PAL_LIMITED_CONTEXT, Xmm11) +ASM_OFFSET( 0, 0, 0c0, PAL_LIMITED_CONTEXT, Xmm12) +ASM_OFFSET( 0, 0, 0d0, PAL_LIMITED_CONTEXT, Xmm13) +ASM_OFFSET( 0, 0, 0e0, PAL_LIMITED_CONTEXT, Xmm14) +ASM_OFFSET( 0, 0, 0f0, PAL_LIMITED_CONTEXT, Xmm15) +#endif // _AMD64_ +#endif // _ARM_ + +ASM_SIZEOF( 28, 88, 130, REGDISPLAY) +ASM_OFFSET( 1c, 38, 78, REGDISPLAY, SP) +#ifdef _ARM_ +ASM_OFFSET( 0, 10, 0, REGDISPLAY, pR4) +ASM_OFFSET( 0, 14, 0, REGDISPLAY, pR5) +ASM_OFFSET( 0, 18, 0, REGDISPLAY, pR6) +ASM_OFFSET( 0, 1c, 0, REGDISPLAY, pR7) +ASM_OFFSET( 0, 20, 0, REGDISPLAY, pR8) +ASM_OFFSET( 0, 24, 0, REGDISPLAY, pR9) +ASM_OFFSET( 0, 28, 0, REGDISPLAY, pR10) +ASM_OFFSET( 0, 2c, 0, REGDISPLAY, pR11) +ASM_OFFSET( 0, 48, 0, REGDISPLAY, D) +#else // _ARM_ +ASM_OFFSET( 0c, 0, 18, REGDISPLAY, pRbx) +ASM_OFFSET( 10, 0, 20, REGDISPLAY, pRbp) +ASM_OFFSET( 14, 0, 28, REGDISPLAY, pRsi) +ASM_OFFSET( 18, 0, 30, REGDISPLAY, pRdi) +#ifdef _AMD64_ +ASM_OFFSET( 0, 0, 58, REGDISPLAY, pR12) +ASM_OFFSET( 0, 0, 60, REGDISPLAY, pR13) +ASM_OFFSET( 0, 0, 68, REGDISPLAY, pR14) +ASM_OFFSET( 0, 0, 70, REGDISPLAY, pR15) +ASM_OFFSET( 0, 0, 90, REGDISPLAY, Xmm) +#endif // _AMD64_ +#endif // _ARM_ + +#ifdef FEATURE_CACHED_INTERFACE_DISPATCH +ASM_OFFSET( 4, 4, 8, InterfaceDispatchCell, m_pCache) +#ifndef _AMD64_ +ASM_OFFSET( 8, 8, 10, InterfaceDispatchCache, m_pCell) +#endif +ASM_OFFSET( 10, 10, 20, InterfaceDispatchCache, m_rgEntries) +#endif + +ASM_OFFSET( 4, 4, 8, StaticClassConstructionContext, m_initialized) + +#ifdef FEATURE_DYNAMIC_CODE +ASM_OFFSET( 0, 0, 0, CallDescrData, pSrc) +ASM_OFFSET( 4, 4, 8, CallDescrData, numStackSlots) +ASM_OFFSET( 8, 8, C, CallDescrData, fpReturnSize) +ASM_OFFSET( C, C, 10, CallDescrData, pArgumentRegisters) +ASM_OFFSET( 10, 10, 18, CallDescrData, pFloatArgumentRegisters) +ASM_OFFSET( 14, 14, 20, CallDescrData, pTarget) +ASM_OFFSET( 18, 18, 28, CallDescrData, pReturnBuffer) +#endif +#endif // !defined(USE_PORTABLE_HELPERS) diff --git a/src/Native/Runtime/AsmOffsetsVerify.cpp b/src/Native/Runtime/AsmOffsetsVerify.cpp new file mode 100644 index 00000000000..65b7f0fb12f --- /dev/null +++ b/src/Native/Runtime/AsmOffsetsVerify.cpp @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "gcrhenvbase.h" +#include "eventtrace.h" +#include "gc.h" +#include "assert.h" +#include "redhawkwarnings.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "rhbinder.h" +#include "holder.h" +#include "crst.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "cachedinterfacedispatch.h" +#include "module.h" +#include "calldescr.h" + +class AsmOffsets +{ +#define PLAT_ASM_OFFSET(offset, cls, member) \ + static_assert((offsetof(cls, member) == 0x##offset) || (offsetof(cls, member) > 0x##offset), "Bad asm offset for '" #cls "." #member "', the actual offset is smaller than 0x" #offset "."); \ + static_assert((offsetof(cls, member) == 0x##offset) || (offsetof(cls, member) < 0x##offset), "Bad asm offset for '" #cls "." #member "', the actual offset is larger than 0x" #offset "."); + +#define PLAT_ASM_SIZEOF(size, cls ) \ + static_assert((sizeof(cls) == 0x##size) || (sizeof(cls) > 0x##size), "Bad asm size for '" #cls "', the actual size is smaller than 0x" #size "."); \ + static_assert((sizeof(cls) == 0x##size) || (sizeof(cls) < 0x##size), "Bad asm size for '" #cls "', the actual size is larger than 0x" #size "."); + +#define PLAT_ASM_CONST(constant, expr) \ + static_assert(((expr) == 0x##constant) || ((expr) > 0x##constant), "Bad asm constant for '" #expr "', the actual value is smaller than 0x" #constant "."); \ + static_assert(((expr) == 0x##constant) || ((expr) < 0x##constant), "Bad asm constant for '" #expr "', the actual value is larger than 0x" #constant "."); + +#include "AsmOffsets.h" + +}; diff --git a/src/Native/Runtime/CachedInterfaceDispatch.h b/src/Native/Runtime/CachedInterfaceDispatch.h new file mode 100644 index 00000000000..596c0d7051f --- /dev/null +++ b/src/Native/Runtime/CachedInterfaceDispatch.h @@ -0,0 +1,48 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// ==--== +// +// Shared (non-architecture specific) portions of a mechanism to perform interface dispatch using an alternate +// mechanism to VSD that does not require runtime generation of code. +// +// ============================================================================ + +#ifdef FEATURE_CACHED_INTERFACE_DISPATCH + +bool InitializeInterfaceDispatch(); +void ReclaimUnusedInterfaceDispatchCaches(); + +// Interface dispatch caches contain an array of these entries. An instance of a cache is paired with a stub +// that implicitly knows how many entries are contained. These entries must be aligned to twice the alignment +// of a pointer due to the synchonization mechanism used to update them at runtime. +struct InterfaceDispatchCacheEntry +{ + EEType * m_pInstanceType; // Potential type of the object instance being dispatched on + void * m_pTargetCode; // Method to dispatch to if the actual instance type matches the above +}; + +// The interface dispatch cache itself. As well as the entries we include the cache size (since logic such as +// cache miss processing needs to determine this value in a synchronized manner, so it can't be contained in +// the owning interface dispatch indirection cell) and a list entry used to link the caches in one of a couple +// of lists related to cache reclamation. +#pragma warning(push) +#pragma warning(disable:4200) // nonstandard extension used: zero-sized array in struct/union +struct InterfaceDispatchCell; +struct InterfaceDispatchCache +{ + InterfaceDispatchCacheHeader m_cacheHeader; + union + { + InterfaceDispatchCache * m_pNextFree; // next in free list +#ifndef _AMD64_ + InterfaceDispatchCell * m_pCell; // pointer back to interface dispatch cell - not used for AMD64 +#endif + }; + UInt32 m_cEntries; + InterfaceDispatchCacheEntry m_rgEntries[]; +}; +#pragma warning(pop) + +#endif // FEATURE_CACHED_INTERFACE_DISPATCH diff --git a/src/Native/Runtime/CallDescr.h b/src/Native/Runtime/CallDescr.h new file mode 100644 index 00000000000..505f3e37885 --- /dev/null +++ b/src/Native/Runtime/CallDescr.h @@ -0,0 +1,14 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +struct CallDescrData +{ + BYTE* pSrc; + int numStackSlots; + int fpReturnSize; + BYTE* pArgumentRegisters; + BYTE* pFloatArgumentRegisters; + void* pTarget; + void* pReturnBuffer; +}; diff --git a/src/Native/Runtime/CommonMacros.h b/src/Native/Runtime/CommonMacros.h new file mode 100644 index 00000000000..ec58f0d8335 --- /dev/null +++ b/src/Native/Runtime/CommonMacros.h @@ -0,0 +1,173 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// Some of our header files are shared with the binder, which needs the TARGET_* macros defined +#if defined(TARGET_X64) +#elif defined(TARGET_X86) +#elif defined(TARGET_ARM) +#else +#error Unsupported architecture +#endif + +#define EXTERN_C extern "C" +#define FASTCALL __fastcall +#define STDCALL __stdcall +#define REDHAWK_API +#define REDHAWK_CALLCONV __fastcall + +#ifdef _MSC_VER + +#define MSVC_SAVE_WARNING_STATE() __pragma(warning(push)) +#define MSVC_DISABLE_WARNING(warn_num) __pragma(warning(disable: warn_num)) +#define MSVC_RESTORE_WARNING_STATE() __pragma(warning(pop)) + +#else + +#define MSVC_SAVE_WARNING_STATE() +#define MSVC_DISABLE_WARNING(warn_num) +#define MSVC_RESTORE_WARNING_STATE() + +#endif // _MSC_VER + +#ifndef COUNTOF +template +char (*COUNTOF_helper(_CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray]; +#define COUNTOF(_Array) sizeof(*COUNTOF_helper(_Array)) +#endif // COUNTOF + +#ifndef offsetof +#define offsetof(s,m) (UIntNative)( (IntNative)&reinterpret_cast((((s *)0)->m)) ) +#endif // offsetof + +#define FORCEINLINE __forceinline + +inline UIntNative ALIGN_UP(UIntNative val, UIntNative alignment); +template +inline T* ALIGN_UP(T* val, UIntNative alignment); + +inline UIntNative ALIGN_DOWN(UIntNative val, UIntNative alignment); +template +inline T* ALIGN_DOWN(T* val, UIntNative alignment); + +inline bool IS_ALIGNED(UIntNative val, UIntNative alignment); +template +inline bool IS_ALIGNED(T* val, UIntNative alignment); + +#ifndef DACCESS_COMPILE +// +// Basic memory copying/clearing functionality (rather than depend on the CRT). All our current compilers +// actually provide these as intrinsics so use those for now (and provide non-CRT names for them as well). +// + +EXTERN_C void * __cdecl memcpy(void *, const void *, size_t); +#pragma intrinsic(memcpy) +#ifndef CopyMemory +#define CopyMemory(_dst, _src, _size) memcpy((_dst), (_src), (_size)) +#endif + +EXTERN_C void * __cdecl memset(void *, int, size_t); +#pragma intrinsic(memset) +#ifndef FillMemory +#define FillMemory(_dst, _size, _fill) memset((_dst), (_fill), (_size)) +#endif +#ifndef ZeroMemory +#define ZeroMemory(_dst, _size) memset((_dst), 0, (_size)) +#endif + +EXTERN_C int __cdecl memcmp(const void *,const void *,size_t); +#pragma intrinsic(memcmp) + +//------------------------------------------------------------------------------------------------- +// min/max + +#ifndef min +#define min(_a, _b) ((_a) < (_b) ? (_a) : (_b)) +#endif +#ifndef max +#define max(_a, _b) ((_a) < (_b) ? (_b) : (_a)) +#endif + +#endif // !DACCESS_COMPILE + +//------------------------------------------------------------------------------------------------- +// Platform-specific defines + +#if defined(_AMD64_) + +#define DATA_ALIGNMENT 8 +#define OS_PAGE_SIZE 0x1000 +#define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) // 0x10000 (64 KB) +#define LOG2_PTRSIZE 3 +#define POINTER_SIZE 8 + +#elif defined(_X86_) + +#define DATA_ALIGNMENT 4 +#ifndef OS_PAGE_SIZE +#define OS_PAGE_SIZE 0x1000 +#endif +#define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) // 0x10000 (64 KB) +#define LOG2_PTRSIZE 2 +#define POINTER_SIZE 4 + +#elif defined(_ARM_) + +#define DATA_ALIGNMENT 4 +#ifndef OS_PAGE_SIZE +#define OS_PAGE_SIZE 0x1000 +#endif +#define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) // 0x10000 (64 KB) +#define LOG2_PTRSIZE 2 +#define POINTER_SIZE 4 + +#else +#error Unsupported target architecture +#endif + +// +// Define an unmanaged function called from managed code that needs to execute in co-operative GC mode. (There +// should be very few of these, most such functions will be simply p/invoked). +// +#define COOP_PINVOKE_HELPER(_rettype, _method, _args) EXTERN_C REDHAWK_API _rettype __fastcall _method _args +#ifdef _X86_ +// We have helpers that act like memcpy and memset from the CRT, so they need to be __cdecl. +#define COOP_PINVOKE_CDECL_HELPER(_rettype, _method, _args) EXTERN_C REDHAWK_API _rettype __cdecl _method _args +#else +#define COOP_PINVOKE_CDECL_HELPER COOP_PINVOKE_HELPER +#endif + +#ifndef DACCESS_COMPILE +#define IN_DAC(x) +#define NOT_IN_DAC(x) x +#else +#define IN_DAC(x) x +#define NOT_IN_DAC(x) +#endif + +#define INLINE inline + +enum STARTUP_TIMELINE_EVENT_ID +{ + PROCESS_ATTACH_BEGIN = 0, + NONGC_INIT_COMPLETE, + GC_INIT_COMPLETE, + PROCESS_ATTACH_COMPLETE, + + NUM_STARTUP_TIMELINE_EVENTS +}; + +#ifdef PROFILE_STARTUP +extern unsigned __int64 g_startupTimelineEvents[NUM_STARTUP_TIMELINE_EVENTS]; +#define STARTUP_TIMELINE_EVENT(eventid) PalQueryPerformanceCounter((LARGE_INTEGER*)&g_startupTimelineEvents[eventid]); +#else // PROFILE_STARTUP +#define STARTUP_TIMELINE_EVENT(eventid) +#endif // PROFILE_STARTUP + +bool inline FitsInI4(__int64 val) +{ + return val == (__int64)(__int32)val; +} + +#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] diff --git a/src/Native/Runtime/CommonMacros.inl b/src/Native/Runtime/CommonMacros.inl new file mode 100644 index 00000000000..96833c2c756 --- /dev/null +++ b/src/Native/Runtime/CommonMacros.inl @@ -0,0 +1,48 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +inline UIntNative ALIGN_UP( UIntNative val, UIntNative alignment ) +{ + // alignment must be a power of 2 for this implementation to work (need modulo otherwise) + ASSERT( 0 == (alignment & (alignment - 1)) ); + UIntNative result = (val + (alignment - 1)) & ~(alignment - 1); + ASSERT( result >= val ); // check for overflow + + return result; +} + +template +inline T* ALIGN_UP(T* val, UIntNative alignment) +{ + return reinterpret_cast(ALIGN_UP(reinterpret_cast(val), alignment)); +} + +inline UIntNative ALIGN_DOWN( UIntNative val, UIntNative alignment ) +{ + // alignment must be a power of 2 for this implementation to work (need modulo otherwise) + ASSERT( 0 == (alignment & (alignment - 1)) ); + UIntNative result = val & ~(alignment - 1); + return result; +} + +template +inline T* ALIGN_DOWN(T* val, UIntNative alignment) +{ + return reinterpret_cast(ALIGN_DOWN(reinterpret_cast(val), alignment)); +} + +inline bool IS_ALIGNED(UIntNative val, UIntNative alignment) +{ + ASSERT(0 == (alignment & (alignment - 1))); + return 0 == (val & (alignment - 1)); +} + +template +inline bool IS_ALIGNED(T* val, UIntNative alignment) +{ + ASSERT(0 == (alignment & (alignment - 1))); + return IS_ALIGNED(reinterpret_cast(val), alignment); +} + diff --git a/src/Native/Runtime/CommonTypes.h b/src/Native/Runtime/CommonTypes.h new file mode 100644 index 00000000000..042273c1f54 --- /dev/null +++ b/src/Native/Runtime/CommonTypes.h @@ -0,0 +1,54 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef __COMMON_TYPES_H__ +#define __COMMON_TYPES_H__ + +// +// These type names are chosen to match the C# types +// +typedef signed char Int8; +typedef signed short Int16; +typedef signed int Int32; +typedef signed __int64 Int64; +typedef unsigned char UInt8; +typedef unsigned short UInt16; +typedef unsigned int UInt32; +typedef unsigned __int64 UInt64; +#if defined(TARGET_X64) +typedef signed __int64 IntNative; // intentional deviation from C# IntPtr +typedef unsigned __int64 UIntNative; // intentional deviation from C# UIntPtr +#else +typedef __w64 signed int IntNative; // intentional deviation from C# IntPtr +typedef __w64 unsigned int UIntNative; // intentional deviation from C# UIntPtr +#endif +typedef wchar_t WCHAR; +typedef void * HANDLE; + +typedef unsigned char Boolean; +#define Boolean_false 0 +#define Boolean_true 1 + +typedef UInt32 UInt32_BOOL; // windows 4-byte BOOL, 0 -> false, everything else -> true +#define UInt32_FALSE 0 +#define UInt32_TRUE 1 + +#define UNREFERENCED_PARAMETER(P) (P) + +#define NULL 0 + +#define UInt16_MAX ((UInt16)0xffffU) +#define UInt16_MIN ((UInt16)0x0000U) + +#define UInt32_MAX ((UInt32)0xffffffffU) +#define UInt32_MIN ((UInt32)0x00000000U) + +#define Int32_MAX ((Int32)0x7fffffff) +#define Int32_MIN ((Int32)0x80000000) + +#define UInt64_MAX ((UInt64)0xffffffffffffffffUL) +#define UInt64_MIN ((UInt64)0x0000000000000000UL) + +#endif // __COMMON_TYPES_H__ diff --git a/src/Native/Runtime/Crst.cpp b/src/Native/Runtime/Crst.cpp new file mode 100644 index 00000000000..53ad5b97576 --- /dev/null +++ b/src/Native/Runtime/Crst.cpp @@ -0,0 +1,79 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "holder.h" +#include "crst.h" +#endif // !DACCESS_COMPILE + +void CrstStatic::Init(CrstType eType, CrstFlags eFlags) +{ +#ifndef DACCESS_COMPILE +#if defined(_DEBUG) + m_uiOwnerId = UNOWNED; +#endif // _DEBUG + PalInitializeCriticalSectionEx(&m_sCritSec, 0, 0); +#else + UNREFERENCED_PARAMETER(eType); + UNREFERENCED_PARAMETER(eFlags); +#endif // !DACCESS_COMPILE +} + +void CrstStatic::Destroy() +{ +#ifndef DACCESS_COMPILE + PalDeleteCriticalSection(&m_sCritSec); +#endif // !DACCESS_COMPILE +} + +// static +void CrstStatic::Enter(CrstStatic *pCrst) +{ +#ifndef DACCESS_COMPILE + PalEnterCriticalSection(&pCrst->m_sCritSec); +#if defined(_DEBUG) + pCrst->m_uiOwnerId = PalGetCurrentThreadId(); +#endif // _DEBUG +#else + UNREFERENCED_PARAMETER(pCrst); +#endif // !DACCESS_COMPILE +} + +// static +void CrstStatic::Leave(CrstStatic *pCrst) +{ +#ifndef DACCESS_COMPILE +#if defined(_DEBUG) + pCrst->m_uiOwnerId = UNOWNED; +#endif // _DEBUG + PalLeaveCriticalSection(&pCrst->m_sCritSec); +#else + UNREFERENCED_PARAMETER(pCrst); +#endif // !DACCESS_COMPILE +} + +#if defined(_DEBUG) +bool CrstStatic::OwnedByCurrentThread() +{ +#ifndef DACCESS_COMPILE + return m_uiOwnerId == PalGetCurrentThreadId(); +#else + return false; +#endif +} + +EEThreadId CrstStatic::GetHolderThreadId() +{ + return EEThreadId(m_uiOwnerId); +} +#endif // _DEBUG diff --git a/src/Native/Runtime/Crst.h b/src/Native/Runtime/Crst.h new file mode 100644 index 00000000000..b8806673080 --- /dev/null +++ b/src/Native/Runtime/Crst.h @@ -0,0 +1,92 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// Abstracted thread ID. This doesn't really belong in this file, but there is not currently any better place +// for it. +class EEThreadId +{ +public: + EEThreadId(UInt32 uiId) : m_uiId(uiId) {} +#ifndef DACCESS_COMPILE + bool IsSameThread() { return PalGetCurrentThreadId() == m_uiId; } +#endif + +private: + UInt32 m_uiId; +}; + + +// +// ----------------------------------------------------------------------------------------------------------- +// +// Minimal Crst implementation based on CRITICAL_SECTION. Doesn't support much except for the basic locking +// functionality (in particular there is no rank violation checking, but then again there's only one Crst type +// used currently). +// + +enum CrstType +{ + CrstHandleTable, + CrstInstanceStore, + CrstThreadStore, + CrstDispatchCache, + CrstAllocHeap, + CrstModuleList, + CrstGenericInstHashtab, + CrstMemAccessMgr, + CrstInterfaceDispatchGlobalLists, + CrstStressLog, + CrstRestrictedCallouts, + CrstGcStressControl, +}; + +enum CrstFlags +{ + CRST_DEFAULT = 0x0, +}; + +// Static version of Crst with no default constructor (user must call Init() before use). +class CrstStatic +{ +public: + void Init(CrstType eType, CrstFlags eFlags = CRST_DEFAULT); + bool InitNoThrow(CrstType eType, CrstFlags eFlags = CRST_DEFAULT) { Init(eType, eFlags); return true; } + void Destroy(); + static void Enter(CrstStatic *pCrst); + static void Leave(CrstStatic *pCrst); +#if defined(_DEBUG) + bool OwnedByCurrentThread(); + EEThreadId GetHolderThreadId(); +#endif // _DEBUG + +private: + CRITICAL_SECTION m_sCritSec; +#if defined(_DEBUG) + UInt32 m_uiOwnerId; + static const UInt32 UNOWNED = 0; +#endif // _DEBUG +}; + +// Non-static version that will initialize itself during construction. +class Crst : public CrstStatic +{ +public: + Crst(CrstType eType, CrstFlags eFlags = CRST_DEFAULT) + : CrstStatic() + { Init(eType, eFlags); } +}; + +// Holder for a Crst instance. +class CrstHolder : public Holder +{ +public: + CrstHolder(CrstStatic *pCrst, bool fTake = true) : Holder(pCrst, fTake) {} + ~CrstHolder() {} +}; + +// The CLR has split the Crst holders into CrstHolder which only supports acquire on construction/release on +// destruction semantics and CrstHolderWithState, with the old, fully flexible semantics. We don't support the +// split yet so both types are equivalent. +typedef CrstHolder CrstHolderWithState; diff --git a/src/Native/Runtime/DebugEventSource.cpp b/src/Native/Runtime/DebugEventSource.cpp new file mode 100644 index 00000000000..53d66e94935 --- /dev/null +++ b/src/Native/Runtime/DebugEventSource.cpp @@ -0,0 +1,209 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE +#if !defined(DACCESS_COMPILE) +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "type_traits.hpp" +#include "slist.h" +#include "holder.h" +#include "crst.h" +#include "instancestore.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "gcrhinterface.h" +#include "module.h" +#include "DebugEventSource.h" + +#include "slist.inl" + +#include "DebugEvents.h" +#endif //!DACCESS_COMPILE + +GVAL_IMPL_INIT(UInt32, g_DebuggerEventsFilter, 0); + +#ifndef DACCESS_COMPILE + +bool EventEnabled(DebugEventType eventType) +{ + return ((int)eventType > 0) && + ((g_DebuggerEventsFilter & (1 << ((int)eventType-1))) != 0); +} + +void DebugEventSource::SendModuleLoadEvent(Module* pModule) +{ + if(!EventEnabled(DEBUG_EVENT_TYPE_LOAD_MODULE)) + return; + DebugEventPayload payload; + payload.type = DEBUG_EVENT_TYPE_LOAD_MODULE; + payload.ModuleLoadUnload.pModuleHeader = (CORDB_ADDRESS)pModule->GetModuleHeader(); + SendRawEvent(&payload); +} + +void DebugEventSource::SendModuleUnloadEvent(Module* pModule) +{ + if(!EventEnabled(DEBUG_EVENT_TYPE_UNLOAD_MODULE)) + return; + DebugEventPayload payload; + payload.type = DEBUG_EVENT_TYPE_UNLOAD_MODULE; + payload.ModuleLoadUnload.pModuleHeader = (CORDB_ADDRESS)pModule->GetModuleHeader(); + SendRawEvent(&payload); +} + +void DebugEventSource::SendExceptionThrownEvent(CORDB_ADDRESS faultingIP, CORDB_ADDRESS faultingFrameSP) +{ + if(!EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_THROWN)) + return; + DebugEventPayload payload; + payload.type = DEBUG_EVENT_TYPE_EXCEPTION_THROWN; + payload.Exception.ip = faultingIP; + payload.Exception.sp = faultingFrameSP; + SendRawEvent(&payload); +} + +void DebugEventSource::SendExceptionCatchHandlerFoundEvent(CORDB_ADDRESS handlerIP, CORDB_ADDRESS HandlerFrameSP) +{ + if(!EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_CATCH_HANDLER_FOUND)) + return; + DebugEventPayload payload; + payload.type = DEBUG_EVENT_TYPE_EXCEPTION_CATCH_HANDLER_FOUND; + payload.Exception.ip = handlerIP; + payload.Exception.sp = HandlerFrameSP; + SendRawEvent(&payload); +} + +void DebugEventSource::SendExceptionUnhandledEvent() +{ + if(!EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_UNHANDLED)) + return; + DebugEventPayload payload; + payload.type = DEBUG_EVENT_TYPE_EXCEPTION_UNHANDLED; + payload.Exception.ip = (CORDB_ADDRESS)0; + payload.Exception.sp = (CORDB_ADDRESS)0; + SendRawEvent(&payload); +} + +void DebugEventSource::SendExceptionFirstPassFrameEnteredEvent(CORDB_ADDRESS ipInFrame, CORDB_ADDRESS frameSP) +{ + if(!EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_FIRST_PASS_FRAME_ENTER)) + return; + DebugEventPayload payload; + payload.type = DEBUG_EVENT_TYPE_EXCEPTION_FIRST_PASS_FRAME_ENTER; + payload.Exception.ip = ipInFrame; + payload.Exception.sp = frameSP; + SendRawEvent(&payload); +} + +//--------------------------------------------------------------------------------------- +// +// Sends a raw managed debug event to the debugger. +// +// Arguments: +// pPayload - managed debug event data +// +// +// Notes: +// The entire process will get frozen by the debugger once we send. The debugger +// needs to resume the process. It may detach as well. +// See CordbProcess::DecodeEvent in mscordbi for decoding this event. These methods must stay in sync. +// +//--------------------------------------------------------------------------------------- +void DebugEventSource::SendRawEvent(DebugEventPayload* pPayload) +{ + // We get to send an array of void* as data with the notification. + // The debugger can then use ReadProcessMemory to read through this array. + UInt64 rgData [] = { + (UInt64) CLRDBG_EXCEPTION_DATA_CHECKSUM, + (UInt64) GetRuntimeInstance()->GetPalInstance(), + (UInt64) pPayload + }; + + // + // Physically send the event via an OS Exception. We're using exceptions as a notification + // mechanism on top of the OS native debugging pipeline. + // + __try + { + const UInt32 dwFlags = 0; // continuable (eg, Debugger can continue GH) + // RaiseException treats arguments as pointer sized values, but we encoded 3 QWORDS. + // On 32 bit platforms we have 6 elements, on 64 bit platforms we have 3 elements + RaiseException(CLRDBG_NOTIFICATION_EXCEPTION_CODE, dwFlags, 3*sizeof(UInt64)/sizeof(UInt32*), (UInt32*)rgData); + + // If debugger continues "GH" (DBG_CONTINUE), then we land here. + // This is the expected path for a well-behaved ICorDebug debugger. + } + __except(1) + { + // We can get here if: + // An ICorDebug aware debugger enabled the debug events AND + // a) the debugger detached during the event OR + // b) the debugger continues "GN" (DBG_EXCEPTION_NOT_HANDLED) - this would be considered a badly written debugger + // + // there is no great harm in reaching here but it is a needless perf-cost + } +} + +//keep these synced with the enumeration in exceptionhandling.cs +enum ExceptionEventKind +{ + EEK_Thrown=1, + EEK_CatchHandlerFound=2, + EEK_Unhandled=4, + EEK_FirstPassFrameEntered=8 +}; + +//Called by the C# exception dispatch code with events to send to the debugger +EXTERN_C REDHAWK_API void __cdecl RhpSendExceptionEventToDebugger(ExceptionEventKind eventKind, void* ip, void* sp) +{ + CORDB_ADDRESS cordbIP = (CORDB_ADDRESS)ip; + CORDB_ADDRESS cordbSP = (CORDB_ADDRESS)sp; +#if _ARM_ + // clear the THUMB-bit from IP + cordbIP &= ~1; +#endif + + if(eventKind == EEK_Thrown) + { + DebugEventSource::SendExceptionThrownEvent(cordbIP, cordbSP); + } + else if(eventKind == EEK_CatchHandlerFound) + { + DebugEventSource::SendExceptionCatchHandlerFoundEvent(cordbIP, cordbSP); + } + else if(eventKind == EEK_Unhandled) + { + DebugEventSource::SendExceptionUnhandledEvent(); + } + else if(eventKind == EEK_FirstPassFrameEntered) + { + DebugEventSource::SendExceptionFirstPassFrameEnteredEvent(cordbIP, cordbSP); + } +} + +// Called to cache the current events the debugger is listening for in the C# implemented exception layer +// Filtering in managed code prevents making unneeded p/invokes +COOP_PINVOKE_HELPER(ExceptionEventKind, RhpGetRequestedExceptionEvents, ()) +{ + int mask = 0; + if(EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_THROWN)) + mask |= EEK_Thrown; + if(EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_CATCH_HANDLER_FOUND)) + mask |= EEK_CatchHandlerFound; + if(EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_UNHANDLED)) + mask |= EEK_Unhandled; + if(EventEnabled(DEBUG_EVENT_TYPE_EXCEPTION_FIRST_PASS_FRAME_ENTER)) + mask |= EEK_FirstPassFrameEntered; + return (ExceptionEventKind)mask; +} + +#endif //!DACCESS_COMPILE \ No newline at end of file diff --git a/src/Native/Runtime/DebugEventSource.h b/src/Native/Runtime/DebugEventSource.h new file mode 100644 index 00000000000..1e1496d5693 --- /dev/null +++ b/src/Native/Runtime/DebugEventSource.h @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// ----------------------------------------------------------------------------------------------------------- +// Support for emitting debug events with particular payloads that a managed-aware debugger can listen for. +// The events are generated using 1st chance SEH exceptions that the debugger should immediately continue +// so the exception never dispatches back into runtime code. However just in case the debugger disconnects +// or doesn't behave well we've got a backstop catch handler that will prevent it from escaping the code in +// DebugEventSource. +// ----------------------------------------------------------------------------------------------------------- + +#ifndef __DEBUG_EVENT_SOURCE_H_ +#define __DEBUG_EVENT_SOURCE_H_ + +// This global is set from out of process using the debugger. It controls which events are emitted. +GVAL_DECL(UInt32, g_DebuggerEventsFilter); + +#ifndef DACCESS_COMPILE + +// this is also defined in cordebug.h, but I don't want to pull that header into redhawk +// runtime build. +typedef UInt64 CORDB_ADDRESS; + +struct DebugEventPayload; + +class DebugEventSource +{ +public: + static void SendModuleLoadEvent(Module* pModule); + static void SendModuleUnloadEvent(Module* pModule); + static void SendExceptionThrownEvent(CORDB_ADDRESS faultingIP, CORDB_ADDRESS faultingFrameSP); + static void SendExceptionCatchHandlerFoundEvent(CORDB_ADDRESS handlerIP, CORDB_ADDRESS HandlerFrameSP); + static void SendExceptionUnhandledEvent(); + static void SendExceptionFirstPassFrameEnteredEvent(CORDB_ADDRESS ipInFrame, CORDB_ADDRESS frameSP); + +private: + static void SendRawEvent(DebugEventPayload* payload); +}; + + +#endif //!DACCESS_COMPILE + + +#endif // __DEBUG_EVENT_SOURCE_H_ \ No newline at end of file diff --git a/src/Native/Runtime/DebugEvents.h b/src/Native/Runtime/DebugEvents.h new file mode 100644 index 00000000000..49701879394 --- /dev/null +++ b/src/Native/Runtime/DebugEvents.h @@ -0,0 +1,62 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// ----------------------------------------------------------------------------------------------------------- +// This defines the payload of debug events that are emited by Redhawk runtime and +// received by the debugger. These payloads are referenced by 1st chance SEH exceptions + + +// ----------------------------------------------------------------------------------------------------------- +// This version of holder does not have a default constructor. +#ifndef __DEBUG_EVENTS_H_ +#define __DEBUG_EVENTS_H_ + +// Special Exception code for RH to communicate to debugger +// RH will raise this exception to communicate managed debug events. +// Exception codes can't use bit 0x10000000, that's reserved by OS. +// NOTE: This is intentionally different than CLR's exception code (0x04242420) +// Perhaps it is because now we are in building 40? Who would know +#define CLRDBG_NOTIFICATION_EXCEPTION_CODE ((int) 0x04040400) + +// This is exception argument 0 included in debugger notification events. +// The debugger uses this as a sanity check. +// This could be very volatile data that changes between builds. +// NOTE: Again intentionally different than CLR's checksum (0x31415927) +// It doesn't have to be, but if anyone is manually looking at these +// exception payloads I am trying to make it obvious that they aren't +// the same. +#define CLRDBG_EXCEPTION_DATA_CHECKSUM ((int) 0x27182818) + +typedef enum +{ + DEBUG_EVENT_TYPE_INVALID = 0, + DEBUG_EVENT_TYPE_LOAD_MODULE = 1, + DEBUG_EVENT_TYPE_UNLOAD_MODULE = 2, + DEBUG_EVENT_TYPE_EXCEPTION_THROWN = 3, + DEBUG_EVENT_TYPE_EXCEPTION_FIRST_PASS_FRAME_ENTER = 4, + DEBUG_EVENT_TYPE_EXCEPTION_CATCH_HANDLER_FOUND = 5, + DEBUG_EVENT_TYPE_EXCEPTION_UNHANDLED = 6, + DEBUG_EVENT_TYPE_MAX = 7 +} DebugEventType; + +struct DebugEventPayload +{ + DebugEventType type; + union + { + struct + { + CORDB_ADDRESS pModuleHeader; //ModuleHeader* + } ModuleLoadUnload; + struct + { + CORDB_ADDRESS ip; + CORDB_ADDRESS sp; + } Exception; + }; +}; + + +#endif // __DEBUG_EVENTS_H_ \ No newline at end of file diff --git a/src/Native/Runtime/DebugMacrosExt.h b/src/Native/Runtime/DebugMacrosExt.h new file mode 100644 index 00000000000..ecce1a829e3 --- /dev/null +++ b/src/Native/Runtime/DebugMacrosExt.h @@ -0,0 +1,47 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// DebugMacrosExt.h +// +// Simple debugging macros that take no dependencies on CLR headers. +// This header can be used from outside the CLR. +// +//***************************************************************************** + +#ifndef __DebugMacrosExt_h__ +#define __DebugMacrosExt_h__ + +#if !defined(_DEBUG_IMPL) && defined(_DEBUG) && !defined(DACCESS_COMPILE) +#define _DEBUG_IMPL 1 +#endif + +#ifdef _DEBUG +// A macro to execute a statement only in _DEBUG. +#define DEBUG_STMT(stmt) stmt +#define INDEBUG(x) x +#define INDEBUG_COMMA(x) x, +#define COMMA_INDEBUG(x) ,x +#define NOT_DEBUG(x) +#else +#define DEBUG_STMT(stmt) +#define INDEBUG(x) +#define INDEBUG_COMMA(x) +#define COMMA_INDEBUG(x) +#define NOT_DEBUG(x) x +#endif + + +#ifdef _DEBUG_IMPL +#define INDEBUGIMPL(x) x +#define INDEBUGIMPL_COMMA(x) x, +#define COMMA_INDEBUGIMPL(x) ,x +#else +#define INDEBUGIMPL(x) +#define INDEBUGIMPL_COMMA(x) +#define COMMA_INDEBUGIMPL(x) +#endif + + +#endif diff --git a/src/Native/Runtime/EHHelpers.cpp b/src/Native/Runtime/EHHelpers.cpp new file mode 100644 index 00000000000..a377594d605 --- /dev/null +++ b/src/Native/Runtime/EHHelpers.cpp @@ -0,0 +1,466 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "module.h" +#include "varint.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "holder.h" +#include "crst.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "event.h" +#include "threadstore.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "stresslog.h" + +// Find the module containing the given address, which is a return address from a managed function. The +// address may be to another managed function, or it may be to an unmanaged function, or it may be to a GC +// hijack. The address may also refer to an EEType if we've been called from RhpGetClasslibFunction. If it is +// a GC hijack, we will recgonize that and use the real return address, updating the address passed in. +static Module * FindModuleRespectingReturnAddressHijacks(void ** pAddress) +{ + RuntimeInstance * pRI = GetRuntimeInstance(); + + // Try looking up the module assuming the address is for code first. Fall back to a read-only data looukp + // if that fails. If we have data indicating that the data case is more common then we can reverse the + // order of checks. Finally check read/write data: generic EETypes live there since they need to be fixed + // up at runtime to support unification. + Module * pModule = pRI->FindModuleByCodeAddress(*pAddress); + if (pModule == NULL) + { + pModule = pRI->FindModuleByReadOnlyDataAddress(*pAddress); + + if (pModule == NULL) + pModule = pRI->FindModuleByDataAddress(*pAddress); + + if (pModule == NULL) + { + // Hmmm... we didn't find a managed module for the given PC. We have a return address in unmanaged + // code, but it could be because the thread is hijacked for GC suspension. If it is then we should + // get the real return address and try again. + Thread * pCurThread = ThreadStore::GetCurrentThread(); + + if (!pCurThread->IsHijacked()) + { + // The PC isn't in a managed module, and there is no hijack in place, so we have no EH info. + return NULL; + } + + // Update the PC passed in to reflect the correct return address. + *pAddress = pCurThread->GetHijackedReturnAddress(); + + pModule = pRI->FindModuleByCodeAddress(*pAddress); + } + } + + return pModule; +} + +COOP_PINVOKE_HELPER(Boolean, RhpEHEnumInitFromStackFrameIterator, (StackFrameIterator* pFrameIter, void ** pMethodStartAddressOut, EHEnum* pEHEnum)) +{ + ICodeManager * pCodeManager = pFrameIter->GetCodeManager(); + pEHEnum->m_pCodeManager = pCodeManager; + + return pCodeManager->EHEnumInit(pFrameIter->GetMethodInfo(), pMethodStartAddressOut, &pEHEnum->m_state); +} + +COOP_PINVOKE_HELPER(Boolean, RhpEHEnumNext, (EHEnum* pEHEnum, EHClause* pEHClause)) +{ + return pEHEnum->m_pCodeManager->EHEnumNext(&pEHEnum->m_state, pEHClause); +} + +// The EH dispatch code needs to know the original return address of a method, even if it has been hijacked. +// We provide this here without modifying the hijack and the EHJump helper will honor the hijack when it +// attempts to dispatch up the stack. +COOP_PINVOKE_HELPER(void*, RhpGetUnhijackedReturnAddress, (void** ppvReturnAddressLocation)) +{ + return ThreadStore::GetCurrentThread()->GetUnhijackedReturnAddress(ppvReturnAddressLocation); +} + +//------------------------------------------------------------------------------------------------------------ +// @TODO:EXCEPTIONS: the code below is related to throwing exceptions out of Rtm. If we did not have to throw +// out of Rtm, then we would note have to have the code below to get a classlib exception object given +// an exception id, or the special functions to back up the MDIL THROW_* instructions, or the allocation +// failure helper. If we could move to a world where we never throw out of Rtm, perhaps by moving parts +// of Rtm that do need to throw out to Bartok- or Binder-generated functions, then we could remove all of this. +//------------------------------------------------------------------------------------------------------------ + + +// Constants used with RhpGetClasslibFunction, to indicate which classlib function +// we are interested in. +// Note: make sure you change the def in rtm\System\Runtime\exceptionhandling.cs if you change this! +enum ClasslibFunctionId +{ + GetRuntimeException = 0, + FailFast = 1, + UnhandledExceptionHandler = 2, + AppendExceptionStackFrame = 3, +}; + +// Unmanaged helper to locate one of two classlib-provided functions that the runtime needs to +// implement throwing of exceptions out of Rtm, and fail-fast. This may return NULL if the classlib +// found via the provided address does not have the necessary exports. +COOP_PINVOKE_HELPER(void *, RhpGetClasslibFunction, (void * address, ClasslibFunctionId functionId)) +{ + // Find the module contianing the given address, which is an address into some managed module. It could + // be code, or it could be an EEType. No matter what, it's an address into a managed module in some non-Rtm + // type system. + Module * pModule = FindModuleRespectingReturnAddressHijacks(&address); + + // If the address isn't in a managed module then we have no classlib function. + if (pModule == NULL) + { + return NULL; + } + + // Now, find the classlib module that the first module was compiled against. This one will contain definitions + // for the classlib functions we need here at runtime. + Module * pClasslibModule = pModule->GetClasslibModule(); + ASSERT(pClasslibModule != NULL); + + // Lookup the method and return it. If we don't find it, we just return NULL. + void * pMethod = NULL; + + if (functionId == GetRuntimeException) + { + pMethod = pClasslibModule->GetClasslibRuntimeExceptionHelper(); + } + else if (functionId == AppendExceptionStackFrame) + { + pMethod = pClasslibModule->GetClasslibAppendExceptionStackFrameHelper(); + } + else if (functionId == FailFast) + { + pMethod = pClasslibModule->GetClasslibFailFastHelper(); + } + else if (functionId == UnhandledExceptionHandler) + { + pMethod = pClasslibModule->GetClasslibUnhandledExceptionHandlerHelper(); + } + + return pMethod; +} + +COOP_PINVOKE_HELPER(void, RhpValidateExInfoStack, ()) +{ + Thread * pThisThread = ThreadStore::GetCurrentThread(); + pThisThread->ValidateExInfoStack(); +} + +COOP_PINVOKE_HELPER(void, RhpClearThreadDoNotTriggerGC, ()) +{ + Thread * pThisThread = ThreadStore::GetCurrentThread(); + + if (!pThisThread->IsDoNotTriggerGcSet()) + RhFailFast(); + + pThisThread->ClearDoNotTriggerGc(); +} + +COOP_PINVOKE_HELPER(void, RhpSetThreadDoNotTriggerGC, ()) +{ + Thread * pThisThread = ThreadStore::GetCurrentThread(); + + if (pThisThread->IsDoNotTriggerGcSet()) + RhFailFast(); + + pThisThread->SetDoNotTriggerGc(); +} + +COOP_PINVOKE_HELPER(Int32, RhGetModuleFileName, (HANDLE moduleHandle, _Out_ wchar_t** pModuleNameOut)) +{ + return PalGetModuleFileName(pModuleNameOut, moduleHandle); +} + +COOP_PINVOKE_HELPER(void, RhpCopyContextFromExInfo, + (void * pOSContext, Int32 cbOSContext, PAL_LIMITED_CONTEXT * pPalContext)) +{ + ASSERT(cbOSContext >= sizeof(CONTEXT)); + CONTEXT* pContext = (CONTEXT *)pOSContext; +#ifdef _AMD64_ + pContext->Rip = pPalContext->IP; + pContext->Rsp = pPalContext->Rsp; + pContext->Rbp = pPalContext->Rbp; + pContext->Rdi = pPalContext->Rdi; + pContext->Rsi = pPalContext->Rsi; + pContext->Rax = pPalContext->Rax; + pContext->Rbx = pPalContext->Rbx; + pContext->R12 = pPalContext->R12; + pContext->R13 = pPalContext->R13; + pContext->R14 = pPalContext->R14; + pContext->R15 = pPalContext->R15; +#elif defined(_X86_) + pContext->Eip = pPalContext->IP; + pContext->Esp = pPalContext->Rsp; + pContext->Ebp = pPalContext->Rbp; + pContext->Edi = pPalContext->Rdi; + pContext->Esi = pPalContext->Rsi; + pContext->Eax = pPalContext->Rax; + pContext->Ebx = pPalContext->Rbx; +#elif defined(_ARM_) + pContext->R0 = pPalContext->R0; + pContext->R4 = pPalContext->R4; + pContext->R5 = pPalContext->R5; + pContext->R6 = pPalContext->R6; + pContext->R7 = pPalContext->R7; + pContext->R8 = pPalContext->R8; + pContext->R9 = pPalContext->R9; + pContext->R10 = pPalContext->R10; + pContext->R11 = pPalContext->R11; + pContext->Sp = pPalContext->SP; + pContext->Lr = pPalContext->LR; + pContext->Pc = pPalContext->IP; +#else +#error Not Implemented for this architecture -- RhpCopyContextFromExInfo +#endif +} + + +#if defined(FEATURE_CLR_EH) && (defined(_AMD64_) || defined(_ARM_) || defined(_X86_)) +struct DISPATCHER_CONTEXT +{ + UIntNative ControlPc; + // N.B. There is more here (so this struct isn't the right size), but we ignore everything else +}; + +EXTERN_C void REDHAWK_CALLCONV RhpFailFastForPInvokeExceptionPreemp(IntNative PInvokeCallsiteReturnAddr, + void* pExceptionRecord, void* pContextRecord); +EXTERN_C void REDHAWK_CALLCONV RhpFailFastForPInvokeExceptionCoop(IntNative PInvokeCallsiteReturnAddr, + void* pExceptionRecord, void* pContextRecord); +Int32 __stdcall RhpVectoredExceptionHandler(PEXCEPTION_POINTERS pExPtrs); + +EXTERN_C Int32 __stdcall RhpPInvokeExceptionGuard(PEXCEPTION_RECORD pExceptionRecord, + UIntNative MemoryStackFp, + PCONTEXT pContextRecord, + DISPATCHER_CONTEXT * pDispatcherContext) +{ +#ifdef APP_LOCAL_RUNTIME + // + // When running on Windows 8.1 RTM, we cannot register our vectored exception handler, because that + // version of MRT100.dll does not support it. However, the binder sets this function as the personality + // routine for every reverse p/invoke, so we can handle hardware exceptions from managed code here. + // + EXCEPTION_POINTERS pointers; + pointers.ExceptionRecord = pExceptionRecord; + pointers.ContextRecord = pContextRecord; + + if (RhpVectoredExceptionHandler(&pointers) == EXCEPTION_CONTINUE_EXECUTION) + return ExceptionContinueExecution; +#endif //APP_LOCAL_RUNTIME + + Thread * pThread = ThreadStore::GetCurrentThread(); + + // If the thread is currently in the "do not trigger GC" mode, we must not allocate, we must not reverse pinvoke, or + // return from a pinvoke. All of these things will deadlock with the GC and they all become increasingly likely as + // exception dispatch kicks off. So we just nip this in the bud as early as possible with a FailFast. The most + // likely case where this occurs is in our GC-callouts for Jupiter lifetime management -- in that case, we have + // managed code that calls to native code (without pinvoking) which might have a bug that causes an AV. + if (pThread->IsDoNotTriggerGcSet()) + RhFailFast(); + + // We promote exceptions that were not converted to managed exceptions to a FailFast. However, we have to + // be careful because we got here via OS SEH infrastructure and, therefore, don't know what GC mode we're + // currently in. As a result, since we're calling back into managed code to handle the FailFast, we must + // correctly call either a NativeCallable or a RuntimeExport version of the same method. + if (pThread->IsCurrentThreadInCooperativeMode()) + RhpFailFastForPInvokeExceptionCoop((IntNative)pDispatcherContext->ControlPc, pExceptionRecord, pContextRecord); + else + RhpFailFastForPInvokeExceptionPreemp((IntNative)pDispatcherContext->ControlPc, pExceptionRecord, pContextRecord); + + return 0; +} +#else +EXTERN_C Int32 RhpPInvokeExceptionGuard() +{ +#ifdef FEATURE_CLR_EH + ASSERT_UNCONDITIONALLY("RhpPInvokeExceptionGuard NYI for this architecture!"); +#else + ASSERT_UNCONDITIONALLY("RhpPInvokeExceptionGuard NYI for this version of Redhawk!"); +#endif + RhFailFast(); + return 0; +} +#endif // FEATURE_CLR_EH && (_AMD64_ || _ARM_) + +#ifdef FEATURE_CLR_EH +#if defined(_AMD64_) || defined(_ARM_) || defined(_X86_) +EXTERN_C REDHAWK_API void __fastcall RhpThrowHwEx(); +#else +COOP_PINVOKE_HELPER(void, RhpThrowHwEx, ()) +{ + ASSERT_UNCONDITIONALLY("RhpThrowHwEx NYI for this architecture!"); +} +COOP_PINVOKE_HELPER(void, RhpThrowEx, ()) +{ + ASSERT_UNCONDITIONALLY("RhpThrowEx NYI for this architecture!"); +} +COOP_PINVOKE_HELPER(void, RhpCallCatchFunclet, ()) +{ + ASSERT_UNCONDITIONALLY("RhpCallCatchFunclet NYI for this architecture!"); +} +COOP_PINVOKE_HELPER(void, RhpCallFinallyFunclet, ()) +{ + ASSERT_UNCONDITIONALLY("RhpCallFinallyFunclet NYI for this architecture!"); +} +COOP_PINVOKE_HELPER(void, RhpCallFilterFunclet, ()) +{ + ASSERT_UNCONDITIONALLY("RhpCallFilterFunclet NYI for this architecture!"); +} +COOP_PINVOKE_HELPER(void, RhpRethrow, ()) +{ + ASSERT_UNCONDITIONALLY("RhpRethrow NYI for this architecture!"); +} + +EXTERN_C void* RhpCallCatchFunclet2 = NULL; +EXTERN_C void* RhpCallFinallyFunclet2 = NULL; +EXTERN_C void* RhpCallFilterFunclet2 = NULL; +EXTERN_C void* RhpThrowEx2 = NULL; +EXTERN_C void* RhpThrowHwEx2 = NULL; +EXTERN_C void* RhpRethrow2 = NULL; +#endif +#endif // FEATURE_CLR_EH + +#if defined(_AMD64_) || defined(_X86_) +EXTERN_C void __fastcall RhpAssignRefEDX(); +EXTERN_C void __fastcall RhpCheckedAssignRefEDX(); +#define RhpAssignRefAvLocationEDX RhpAssignRefEDX +#define RhpCheckedAssignRefAvLocationEDX RhpCheckedAssignRefEDX +#define APPEND_REG1_TO_NAME(name) name##EDX +#elif defined(_ARM_) +EXTERN_C void __fastcall RhpAssignRefAvLocationR1(); +EXTERN_C void __fastcall RhpCheckedAssignRefAvLocationR1(); +#define APPEND_REG1_TO_NAME(name) name##R1 +#else +#error "Unknown Architecture" +#endif +EXTERN_C void * RhpCheckedLockCmpXchgAVLocation; +EXTERN_C void * RhpCheckedXchgAVLocation; +EXTERN_C void * RhpCopyMultibyteDestAVLocation; +EXTERN_C void * RhpCopyMultibyteSrcAVLocation; +EXTERN_C void * RhpCopyMultibyteNoGCRefsDestAVLocation; +EXTERN_C void * RhpCopyMultibyteNoGCRefsSrcAVLocation; + +static bool InWriteBarrierHelper(UIntNative faultingIP) +{ + static UIntNative writeBarrierAVLocations[] = + { + (UIntNative)&APPEND_REG1_TO_NAME(RhpAssignRefAvLocation), + (UIntNative)&APPEND_REG1_TO_NAME(RhpCheckedAssignRefAvLocation), + (UIntNative)&RhpCheckedLockCmpXchgAVLocation, + (UIntNative)&RhpCheckedXchgAVLocation, + (UIntNative)&RhpCopyMultibyteDestAVLocation, + (UIntNative)&RhpCopyMultibyteSrcAVLocation, + (UIntNative)&RhpCopyMultibyteNoGCRefsDestAVLocation, + (UIntNative)&RhpCopyMultibyteNoGCRefsSrcAVLocation, + }; + + // compare the IP against the list of known possible AV locations in the write barrier helpers + for (size_t i = 0; i < sizeof(writeBarrierAVLocations)/sizeof(writeBarrierAVLocations[0]); i++) + { + if (writeBarrierAVLocations[i] == faultingIP) + return true; + } + return false; +} + +static UIntNative UnwindWriteBarrierToCaller(_CONTEXT * pContext) +{ +#if defined(_DEBUG) + UIntNative faultingIP = pContext->GetIP(); + ASSERT(InWriteBarrierHelper(faultingIP)); +#endif +#if defined(_AMD64_) || defined(_X86_) + // simulate a ret instruction + UIntNative sp = pContext->GetSP(); // get the stack pointer + UIntNative adjustedFaultingIP = *(UIntNative *)sp - 5; // call instruction will be 6 bytes - act as if start of call instruction + 1 were the faulting IP + pContext->SetSP(sp+sizeof(UIntNative)); // pop the stack +#elif defined(_ARM_) + UIntNative adjustedFaultingIP = pContext->GetLR() - 2; // bl instruction will be 4 bytes - act as if start of call instruction + 2 were the faulting IP +#else +#error "Unknown Architecture" +#endif + return adjustedFaultingIP; +} + +Int32 __stdcall RhpVectoredExceptionHandler(PEXCEPTION_POINTERS pExPtrs) +{ + UIntNative faultingIP = pExPtrs->ContextRecord->GetIP(); +#ifdef FEATURE_CLR_EH + ICodeManager * pCodeManager = GetRuntimeInstance()->FindCodeManagerByAddress((PTR_VOID)faultingIP); + UIntNative faultCode = pExPtrs->ExceptionRecord->ExceptionCode; + if ((pCodeManager != NULL) || (faultCode == STATUS_ACCESS_VIOLATION && InWriteBarrierHelper(faultingIP))) + { + if (faultCode == STATUS_ACCESS_VIOLATION) + { + if (pExPtrs->ExceptionRecord->ExceptionInformation[1] < NULL_AREA_SIZE) + faultCode = STATUS_REDHAWK_NULL_REFERENCE; + if (pCodeManager == NULL) + { + // we were AV-ing in a write barrier helper - unwind our way to our caller + faultingIP = UnwindWriteBarrierToCaller(pExPtrs->ContextRecord); + } + } + else if (faultCode == STATUS_STACK_OVERFLOW) + { + ASSERT_UNCONDITIONALLY("managed stack overflow"); + RhFailFast2(pExPtrs->ExceptionRecord, pExPtrs->ContextRecord); + } + + pExPtrs->ContextRecord->SetIP((UIntNative)&RhpThrowHwEx); + pExPtrs->ContextRecord->SetArg0Reg(faultCode); + pExPtrs->ContextRecord->SetArg1Reg(faultingIP); + + return EXCEPTION_CONTINUE_EXECUTION; + } + else +#endif // FEATURE_CLR_EH + { + static UInt8 *s_pbRuntimeModuleLower = NULL; + static UInt8 *s_pbRuntimeModuleUpper = NULL; + + // If this is the first time through this path then calculate the upper and lower bounds of the + // runtime module. Note we could be racing to calculate this but it doesn't matter since the results + // should always agree. + if ((s_pbRuntimeModuleLower == NULL) || (s_pbRuntimeModuleUpper == NULL)) + { + // Get the module handle for this runtime. Do this by passing an address definitely within the + // module (the address of this function) to GetModuleHandleEx with the "from address" flag. + HANDLE hRuntimeModule = PalGetModuleHandleFromPointer(RhpVectoredExceptionHandler); + if (!hRuntimeModule) + { + ASSERT_UNCONDITIONALLY("Failed to locate our own module handle"); + RhFailFast(); + } + + PalGetModuleBounds(hRuntimeModule, &s_pbRuntimeModuleLower, &s_pbRuntimeModuleUpper); + } + + if (((UInt8*)faultingIP >= s_pbRuntimeModuleLower) && ((UInt8*)faultingIP < s_pbRuntimeModuleUpper)) + { + // Generally any form of hardware exception within the runtime itself is considered a fatal error. + // Note this includes the managed code within the runtime. Eventually the range check above will + // have to get a little more complex to deal with A/Vs in runtime helpers that we want to + // translate into managed exceptions (e.g. null ref exceptions in the write barriers or interface + // invocation stubs). Until FEATURE_CLR_EH comes fully online this simpler range check is + // sufficient. + ASSERT_UNCONDITIONALLY("Hardware exception raised inside the runtime."); + RhFailFast2(pExPtrs->ExceptionRecord, pExPtrs->ContextRecord); + } + + return EXCEPTION_CONTINUE_SEARCH; + } +} + + +#endif // !DACCESS_COMPILE diff --git a/src/Native/Runtime/EtwEvents.h b/src/Native/Runtime/EtwEvents.h new file mode 100644 index 00000000000..07a5ecaaea7 --- /dev/null +++ b/src/Native/Runtime/EtwEvents.h @@ -0,0 +1,800 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// **** This file is auto-generated. Do not edit by hand. **** +// +// Instead ensure this file and EtwEvents.man are checked-out from source code control, locate the PUCLR ETW +// manifest file (it should be in puclr\ndp\clr\src\VM\ClrEtwAll.man), copy it into the rh\src\rtetw +// directory and run the following command from an rhenv window: +// perl EtwImportClrEvents.pl +// +// This script consults EtwEventFilter.txt to determine which events to extract from the CLR manifest. It then +// merges any additional Redhawk-specific events from EtwRedhawkEvents.xml. The result is an updated version +// of this header file plus EtwEvents.man, a new ETW manifest file describing the final Redhawk events which +// can be registered with the system via the following command: +// wevtutil im EtwEvents.man +// + +#ifndef __RH_ETW_DEFS_INCLUDED +#define __RH_ETW_DEFS_INCLUDED + +#if defined(FEATURE_ETW) && !defined(DACCESS_COMPILE) + +#ifndef RH_ETW_INLINE +#define RH_ETW_INLINE __declspec(noinline) __inline +#endif + +struct RH_ETW_CONTEXT +{ + TRACEHANDLE RegistrationHandle; + TRACEHANDLE Logger; + UInt64 MatchAnyKeyword; + UInt64 MatchAllKeyword; + EVENT_FILTER_DESCRIPTOR * FilterData; + UInt32 Flags; + UInt32 IsEnabled; + UInt8 Level; + UInt8 Reserve; +}; + +UInt32 EtwCallback(UInt32 IsEnabled, RH_ETW_CONTEXT * CallbackContext); + +__declspec(noinline) __inline void __stdcall +RhEtwControlCallback(GUID * SourceId, UInt32 IsEnabled, UInt8 Level, UInt64 MatchAnyKeyword, UInt64 MatchAllKeyword, EVENT_FILTER_DESCRIPTOR * FilterData, void * CallbackContext) +{ + RH_ETW_CONTEXT * Ctx = (RH_ETW_CONTEXT*)CallbackContext; + if (Ctx == NULL) + return; + Ctx->Level = Level; + Ctx->MatchAnyKeyword = MatchAnyKeyword; + Ctx->MatchAllKeyword = MatchAllKeyword; + Ctx->FilterData = FilterData; + Ctx->IsEnabled = IsEnabled; + EtwCallback(IsEnabled, (RH_ETW_CONTEXT*)CallbackContext); +} + +__declspec(noinline) __inline bool __stdcall + RhEventTracingEnabled(RH_ETW_CONTEXT * EnableInfo, + const EVENT_DESCRIPTOR * EventDescriptor) +{ + if (!EnableInfo) + return false; + if ((EventDescriptor->Level <= EnableInfo->Level) || (EnableInfo->Level == 0)) + { + if ((EventDescriptor->Keyword == (ULONGLONG)0) || + ((EventDescriptor->Keyword & EnableInfo->MatchAnyKeyword) && + ((EventDescriptor->Keyword & EnableInfo->MatchAllKeyword) == EnableInfo->MatchAllKeyword))) + return true; + } + return false; +} + +#define ETW_EVENT_ENABLED(Context, EventDescriptor) (Context.IsEnabled && RhEventTracingEnabled(&Context, &EventDescriptor)) + +extern "C" __declspec(selectany) const GUID MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER = {0x1095638c, 0x8748, 0x4c7a, {0xb3, 0x9e, 0xba, 0xea, 0x27, 0xb9, 0xc5, 0x89}}; + +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGC1stConEnd = {0xd, 0x0, 0x10, 0x4, 0x1b, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGC1stNonConEnd = {0xc, 0x0, 0x10, 0x4, 0x1a, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGC2ndConBegin = {0x10, 0x0, 0x10, 0x4, 0x1e, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGC2ndConEnd = {0x11, 0x0, 0x10, 0x4, 0x1f, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGC2ndNonConBegin = {0xe, 0x0, 0x10, 0x4, 0x1c, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGC2ndNonConEnd = {0xf, 0x0, 0x10, 0x4, 0x1d, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCAllocWaitBegin = {0x17, 0x0, 0x10, 0x4, 0x25, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCAllocWaitEnd = {0x18, 0x0, 0x10, 0x4, 0x26, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCBegin = {0xb, 0x0, 0x10, 0x4, 0x19, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCDrainMark = {0x14, 0x0, 0x10, 0x4, 0x22, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCOverflow = {0x16, 0x0, 0x10, 0x4, 0x24, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCPlanEnd = {0x12, 0x0, 0x10, 0x4, 0x20, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCRevisit = {0x15, 0x0, 0x10, 0x4, 0x23, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BGCSweepEnd = {0x13, 0x0, 0x10, 0x4, 0x21, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCFullNotify_V1 = {0x19, 0x1, 0x10, 0x4, 0x13, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCGlobalHeapHistory_V1 = {0x5, 0x1, 0x10, 0x4, 0x12, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCJoin_V1 = {0x6, 0x1, 0x10, 0x5, 0x14, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCOptimized_V1 = {0x3, 0x1, 0x10, 0x5, 0x10, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCPerHeapHistory = {0x4, 0x2, 0x10, 0x4, 0x11, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCSettings = {0x2, 0x0, 0x10, 0x4, 0xe, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR PrvDestroyGCHandle = {0xc3, 0x0, 0x10, 0x5, 0x2b, 0x1, 0x8000000000004000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR PrvGCMarkCards_V1 = {0xa, 0x1, 0x10, 0x4, 0x18, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR PrvGCMarkFinalizeQueueRoots_V1 = {0x8, 0x1, 0x10, 0x4, 0x16, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR PrvGCMarkHandles_V1 = {0x9, 0x1, 0x10, 0x4, 0x17, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR PrvGCMarkStackRoots_V1 = {0x7, 0x1, 0x10, 0x4, 0x15, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR PrvSetGCHandle = {0xc2, 0x0, 0x10, 0x5, 0x2a, 0x1, 0x8000000000004000}; + +extern "C" __declspec(selectany) REGHANDLE Microsoft_Windows_Redhawk_GC_PrivateHandle; +extern "C" __declspec(selectany) RH_ETW_CONTEXT MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context; + +#define RH_ETW_REGISTER_Microsoft_Windows_Redhawk_GC_Private() do { PalEventRegister(&MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER, RhEtwControlCallback, &MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context, &Microsoft_Windows_Redhawk_GC_PrivateHandle); } while (false) +#define RH_ETW_UNREGISTER_Microsoft_Windows_Redhawk_GC_Private() do { PalEventUnregister(Microsoft_Windows_Redhawk_GC_PrivateHandle); } while (false) + +#define FireEtwBGC1stConEnd(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC1stConEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC1stConEnd, ClrInstanceID) : 0 + +#define FireEtwBGC1stNonConEnd(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC1stNonConEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC1stNonConEnd, ClrInstanceID) : 0 + +#define FireEtwBGC2ndConBegin(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndConBegin)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndConBegin, ClrInstanceID) : 0 + +#define FireEtwBGC2ndConEnd(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndConEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndConEnd, ClrInstanceID) : 0 + +#define FireEtwBGC2ndNonConBegin(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndNonConBegin)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndNonConBegin, ClrInstanceID) : 0 + +#define FireEtwBGC2ndNonConEnd(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndNonConEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGC2ndNonConEnd, ClrInstanceID) : 0 + +#define FireEtwBGCAllocWaitBegin(Reason, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCAllocWaitBegin)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCAllocWait(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCAllocWaitBegin, Reason, ClrInstanceID) : 0 + +#define FireEtwBGCAllocWaitEnd(Reason, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCAllocWaitEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCAllocWait(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCAllocWaitEnd, Reason, ClrInstanceID) : 0 + +#define FireEtwBGCBegin(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCBegin)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCBegin, ClrInstanceID) : 0 + +#define FireEtwBGCDrainMark(Objects, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCDrainMark)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCDrainMark(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCDrainMark, Objects, ClrInstanceID) : 0 + +#define FireEtwBGCOverflow(Min, Max, Objects, IsLarge, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCOverflow)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCOverflow(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCOverflow, Min, Max, Objects, IsLarge, ClrInstanceID) : 0 + +#define FireEtwBGCPlanEnd(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCPlanEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCPlanEnd, ClrInstanceID) : 0 + +#define FireEtwBGCRevisit(Pages, Objects, IsLarge, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCRevisit)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCRevisit(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCRevisit, Pages, Objects, IsLarge, ClrInstanceID) : 0 + +#define FireEtwBGCSweepEnd(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCSweepEnd)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PrivateHandle, &BGCSweepEnd, ClrInstanceID) : 0 + +#define FireEtwGCFullNotify_V1(GenNumber, IsAlloc, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCFullNotify_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCFullNotify_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCFullNotify_V1, GenNumber, IsAlloc, ClrInstanceID) : 0 + +#define FireEtwGCGlobalHeapHistory_V1(FinalYoungestDesired, NumHeaps, CondemnedGeneration, Gen0ReductionCount, Reason, GlobalMechanisms, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCGlobalHeapHistory_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCGlobalHeap_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCGlobalHeapHistory_V1, FinalYoungestDesired, NumHeaps, CondemnedGeneration, Gen0ReductionCount, Reason, GlobalMechanisms, ClrInstanceID) : 0 + +#define FireEtwGCJoin_V1(Heap, JoinTime, JoinType, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCJoin_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCJoin_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCJoin_V1, Heap, JoinTime, JoinType, ClrInstanceID) : 0 + +#define FireEtwGCOptimized_V1(DesiredAllocation, NewAllocation, GenerationNumber, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCOptimized_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCOptimized_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCOptimized_V1, DesiredAllocation, NewAllocation, GenerationNumber, ClrInstanceID) : 0 + +#define FireEtwGCPerHeapHistory() (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCPerHeapHistory)) ? TemplateEventDescriptor(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCPerHeapHistory) : 0 + +#define FireEtwGCSettings(SegmentSize, LargeObjectSegmentSize, ServerGC) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCSettings)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCSettings(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCSettings, SegmentSize, LargeObjectSegmentSize, ServerGC) : 0 + +#define FireEtwPrvDestroyGCHandle(HandleID, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvDestroyGCHandle)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvDestroyGCHandle(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvDestroyGCHandle, HandleID, ClrInstanceID) : 0 + +#define FireEtwPrvGCMarkCards_V1(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkCards_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvGCMark_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkCards_V1, HeapNum, ClrInstanceID) : 0 + +#define FireEtwPrvGCMarkFinalizeQueueRoots_V1(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkFinalizeQueueRoots_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvGCMark_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkFinalizeQueueRoots_V1, HeapNum, ClrInstanceID) : 0 + +#define FireEtwPrvGCMarkHandles_V1(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkHandles_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvGCMark_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkHandles_V1, HeapNum, ClrInstanceID) : 0 + +#define FireEtwPrvGCMarkStackRoots_V1(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkStackRoots_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvGCMark_V1(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvGCMarkStackRoots_V1, HeapNum, ClrInstanceID) : 0 + +#define FireEtwPrvSetGCHandle(HandleID, ObjectID, Kind, Generation, AppDomainID, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvSetGCHandle)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvSetGCHandle(Microsoft_Windows_Redhawk_GC_PrivateHandle, &PrvSetGCHandle, HandleID, ObjectID, Kind, Generation, AppDomainID, ClrInstanceID) : 0 + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCAllocWait(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Reason, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &Reason, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCDrainMark(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 Objects, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &Objects, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCOverflow(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 Min, UInt64 Max, UInt64 Objects, UInt32 IsLarge, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[5]; + EventDataDescCreate(&EventData[0], &Min, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &Max, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &Objects, sizeof(UInt64)); + EventDataDescCreate(&EventData[3], &IsLarge, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 5, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_BGCRevisit(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 Pages, UInt64 Objects, UInt32 IsLarge, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[4]; + EventDataDescCreate(&EventData[0], &Pages, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &Objects, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &IsLarge, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCFullNotify_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 GenNumber, UInt32 IsAlloc, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[3]; + EventDataDescCreate(&EventData[0], &GenNumber, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &IsAlloc, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCGlobalHeap_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 FinalYoungestDesired, Int32 NumHeaps, UInt32 CondemnedGeneration, UInt32 Gen0ReductionCount, UInt32 Reason, UInt32 GlobalMechanisms, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[7]; + EventDataDescCreate(&EventData[0], &FinalYoungestDesired, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &NumHeaps, sizeof(Int32)); + EventDataDescCreate(&EventData[2], &CondemnedGeneration, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &Gen0ReductionCount, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &Reason, sizeof(UInt32)); + EventDataDescCreate(&EventData[5], &GlobalMechanisms, sizeof(UInt32)); + EventDataDescCreate(&EventData[6], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 7, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCJoin_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Heap, UInt32 JoinTime, UInt32 JoinType, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[4]; + EventDataDescCreate(&EventData[0], &Heap, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &JoinTime, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &JoinType, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCNoUserData(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[1]; + EventDataDescCreate(&EventData[0], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 1, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCOptimized_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 DesiredAllocation, UInt64 NewAllocation, UInt32 GenerationNumber, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[4]; + EventDataDescCreate(&EventData[0], &DesiredAllocation, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &NewAllocation, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &GenerationNumber, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_GCSettings(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 SegmentSize, UInt64 LargeObjectSegmentSize, UInt32_BOOL ServerGC) +{ + EVENT_DATA_DESCRIPTOR EventData[3]; + EventDataDescCreate(&EventData[0], &SegmentSize, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &LargeObjectSegmentSize, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &ServerGC, sizeof(UInt32_BOOL)); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvDestroyGCHandle(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, void* HandleID, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &HandleID, sizeof(void*)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvGCMark_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 HeapNum, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &HeapNum, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_PrvSetGCHandle(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, void* HandleID, void* ObjectID, UInt32 Kind, UInt32 Generation, UInt64 AppDomainID, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[6]; + EventDataDescCreate(&EventData[0], &HandleID, sizeof(void*)); + EventDataDescCreate(&EventData[1], &ObjectID, sizeof(void*)); + EventDataDescCreate(&EventData[2], &Kind, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &Generation, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &AppDomainID, sizeof(UInt64)); + EventDataDescCreate(&EventData[5], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 6, EventData); +} + +extern "C" __declspec(selectany) const GUID MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER = {0x47c3ba0c, 0x77f1, 0x4eb0, {0x8d, 0x4d, 0xae, 0xf4, 0x47, 0xf1, 0x6a, 0x85}}; + +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR BulkType = {0xf, 0x0, 0x10, 0x4, 0xa, 0x15, 0x8000000000080000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR DestroyGCHandle = {0x1f, 0x0, 0x10, 0x4, 0x22, 0x1, 0x8000000000000002}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR ExceptionThrown_V1 = {0x50, 0x1, 0x10, 0x2, 0x1, 0x7, 0x8000000000008000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCAllocationTick_V1 = {0xa, 0x1, 0x10, 0x5, 0xb, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCAllocationTick_V2 = {0xa, 0x2, 0x10, 0x5, 0xb, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCAllocationTick_V3 = {0xa, 0x3, 0x10, 0x5, 0xb, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkEdge = {0x13, 0x0, 0x10, 0x4, 0x17, 0x1, 0x8000000000100000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkMovedObjectRanges = {0x16, 0x0, 0x10, 0x4, 0x1a, 0x1, 0x8000000000400000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkNode = {0x12, 0x0, 0x10, 0x4, 0x16, 0x1, 0x8000000000100000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkRCW = {0x25, 0x0, 0x10, 0x4, 0x27, 0x1, 0x8000000000100000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkRootCCW = {0x24, 0x0, 0x10, 0x4, 0x26, 0x1, 0x8000000000100000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkRootConditionalWeakTableElementEdge = {0x11, 0x0, 0x10, 0x4, 0x15, 0x1, 0x8000000000100000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkRootEdge = {0x10, 0x0, 0x10, 0x4, 0x14, 0x1, 0x8000000000100000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCBulkSurvivingObjectRanges = {0x15, 0x0, 0x10, 0x4, 0x19, 0x1, 0x8000000000400000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCCreateConcurrentThread_V1 = {0xb, 0x1, 0x10, 0x4, 0xc, 0x1, 0x8000000000010001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCCreateSegment_V1 = {0x5, 0x1, 0x10, 0x4, 0x86, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCEnd_V1 = {0x2, 0x1, 0x10, 0x4, 0x2, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCFreeSegment_V1 = {0x6, 0x1, 0x10, 0x4, 0x87, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCGenerationRange = {0x17, 0x0, 0x10, 0x4, 0x1b, 0x1, 0x8000000000400000}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCHeapStats_V1 = {0x4, 0x1, 0x10, 0x4, 0x85, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCMarkFinalizeQueueRoots = {0x1a, 0x0, 0x10, 0x4, 0x1d, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCMarkHandles = {0x1b, 0x0, 0x10, 0x4, 0x1e, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCMarkOlderGenerationRoots = {0x1c, 0x0, 0x10, 0x4, 0x1f, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCMarkStackRoots = {0x19, 0x0, 0x10, 0x4, 0x1c, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCRestartEEBegin_V1 = {0x7, 0x1, 0x10, 0x4, 0x88, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCRestartEEEnd_V1 = {0x3, 0x1, 0x10, 0x4, 0x84, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCStart_V1 = {0x1, 0x1, 0x10, 0x4, 0x1, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCStart_V2 = {0x1, 0x2, 0x10, 0x4, 0x1, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCSuspendEEBegin_V1 = {0x9, 0x1, 0x10, 0x4, 0xa, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCSuspendEEEnd_V1 = {0x8, 0x1, 0x10, 0x4, 0x89, 0x1, 0x8000000000000001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR GCTerminateConcurrentThread_V1 = {0xc, 0x1, 0x10, 0x4, 0xd, 0x1, 0x8000000000010001}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR ModuleLoad_V2 = {0x98, 0x2, 0x10, 0x4, 0x21, 0xa, 0x8000000020000008}; +extern "C" __declspec(selectany) const EVENT_DESCRIPTOR SetGCHandle = {0x1e, 0x0, 0x10, 0x4, 0x21, 0x1, 0x8000000000000002}; + +extern "C" __declspec(selectany) REGHANDLE Microsoft_Windows_Redhawk_GC_PublicHandle; +extern "C" __declspec(selectany) RH_ETW_CONTEXT MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context; + +#define RH_ETW_REGISTER_Microsoft_Windows_Redhawk_GC_Public() do { PalEventRegister(&MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER, RhEtwControlCallback, &MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context, &Microsoft_Windows_Redhawk_GC_PublicHandle); } while (false) +#define RH_ETW_UNREGISTER_Microsoft_Windows_Redhawk_GC_Public() do { PalEventUnregister(Microsoft_Windows_Redhawk_GC_PublicHandle); } while (false) + +#define FireEtwBulkType(Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &BulkType)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_BulkType(Microsoft_Windows_Redhawk_GC_PublicHandle, &BulkType, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwDestroyGCHandle(HandleID, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &DestroyGCHandle)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_DestroyGCHandle(Microsoft_Windows_Redhawk_GC_PublicHandle, &DestroyGCHandle, HandleID, ClrInstanceID) : 0 + +#define FireEtwExceptionThrown_V1(ExceptionType, ExceptionMessage, ExceptionEIP, ExceptionHRESULT, ExceptionFlags, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &ExceptionThrown_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Exception(Microsoft_Windows_Redhawk_GC_PublicHandle, &ExceptionThrown_V1, ExceptionType, ExceptionMessage, ExceptionEIP, ExceptionHRESULT, ExceptionFlags, ClrInstanceID) : 0 + +#define FireEtwGCAllocationTick_V1(AllocationAmount, AllocationKind, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCAllocationTick_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCAllocationTick_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCAllocationTick_V1, AllocationAmount, AllocationKind, ClrInstanceID) : 0 + +#define FireEtwGCAllocationTick_V2(AllocationAmount, AllocationKind, ClrInstanceID, AllocationAmount64, TypeID, TypeName, HeapIndex) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCAllocationTick_V2)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCAllocationTick_V2(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCAllocationTick_V2, AllocationAmount, AllocationKind, ClrInstanceID, AllocationAmount64, TypeID, TypeName, HeapIndex) : 0 + +#define FireEtwGCAllocationTick_V3(AllocationAmount, AllocationKind, ClrInstanceID, AllocationAmount64, TypeID, TypeName, HeapIndex, Address) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCAllocationTick_V3)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCAllocationTick_V3(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCAllocationTick_V3, AllocationAmount, AllocationKind, ClrInstanceID, AllocationAmount64, TypeID, TypeName, HeapIndex, Address) : 0 + +#define FireEtwGCBulkEdge(Index, Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkEdge)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkEdge(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkEdge, Index, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkMovedObjectRanges(Index, Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkMovedObjectRanges)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkMovedObjectRanges(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkMovedObjectRanges, Index, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkNode(Index, Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkNode)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkNode(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkNode, Index, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkRCW(Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRCW)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRCW(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRCW, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkRootCCW(Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRootCCW)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRootCCW(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRootCCW, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkRootConditionalWeakTableElementEdge(Index, Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRootConditionalWeakTableElementEdge)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRootConditionalWeakTableElementEdge(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRootConditionalWeakTableElementEdge, Index, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkRootEdge(Index, Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRootEdge)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRootEdge(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkRootEdge, Index, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCBulkSurvivingObjectRanges(Index, Count, ClrInstanceID, Values_Len_, Values) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkSurvivingObjectRanges)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkSurvivingObjectRanges(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCBulkSurvivingObjectRanges, Index, Count, ClrInstanceID, Values_Len_, Values) : 0 + +#define FireEtwGCCreateConcurrentThread_V1(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCCreateConcurrentThread_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCCreateConcurrentThread(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCCreateConcurrentThread_V1, ClrInstanceID) : 0 + +#define FireEtwGCCreateSegment_V1(Address, Size, Type, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCCreateSegment_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCCreateSegment_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCCreateSegment_V1, Address, Size, Type, ClrInstanceID) : 0 + +#define FireEtwGCEnd_V1(Count, Depth, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCEnd_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCEnd_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCEnd_V1, Count, Depth, ClrInstanceID) : 0 + +#define FireEtwGCFreeSegment_V1(Address, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCFreeSegment_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCFreeSegment_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCFreeSegment_V1, Address, ClrInstanceID) : 0 + +#define FireEtwGCGenerationRange(Generation, RangeStart, RangeUsedLength, RangeReservedLength, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCGenerationRange)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCGenerationRange(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCGenerationRange, Generation, RangeStart, RangeUsedLength, RangeReservedLength, ClrInstanceID) : 0 + +#define FireEtwGCHeapStats_V1(GenerationSize0, TotalPromotedSize0, GenerationSize1, TotalPromotedSize1, GenerationSize2, TotalPromotedSize2, GenerationSize3, TotalPromotedSize3, FinalizationPromotedSize, FinalizationPromotedCount, PinnedObjectCount, SinkBlockCount, GCHandleCount, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCHeapStats_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCHeapStats_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCHeapStats_V1, GenerationSize0, TotalPromotedSize0, GenerationSize1, TotalPromotedSize1, GenerationSize2, TotalPromotedSize2, GenerationSize3, TotalPromotedSize3, FinalizationPromotedSize, FinalizationPromotedCount, PinnedObjectCount, SinkBlockCount, GCHandleCount, ClrInstanceID) : 0 + +#define FireEtwGCMarkFinalizeQueueRoots(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkFinalizeQueueRoots)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCMark(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkFinalizeQueueRoots, HeapNum, ClrInstanceID) : 0 + +#define FireEtwGCMarkHandles(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkHandles)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCMark(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkHandles, HeapNum, ClrInstanceID) : 0 + +#define FireEtwGCMarkOlderGenerationRoots(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkOlderGenerationRoots)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCMark(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkOlderGenerationRoots, HeapNum, ClrInstanceID) : 0 + +#define FireEtwGCMarkStackRoots(HeapNum, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkStackRoots)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCMark(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCMarkStackRoots, HeapNum, ClrInstanceID) : 0 + +#define FireEtwGCRestartEEBegin_V1(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCRestartEEBegin_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCRestartEEBegin_V1, ClrInstanceID) : 0 + +#define FireEtwGCRestartEEEnd_V1(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCRestartEEEnd_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCRestartEEEnd_V1, ClrInstanceID) : 0 + +#define FireEtwGCStart_V1(Count, Depth, Reason, Type, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCStart_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCStart_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCStart_V1, Count, Depth, Reason, Type, ClrInstanceID) : 0 + +#define FireEtwGCStart_V2(Count, Depth, Reason, Type, ClrInstanceID, ClientSequenceNumber) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCStart_V2)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCStart_V2(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCStart_V2, Count, Depth, Reason, Type, ClrInstanceID, ClientSequenceNumber) : 0 + +#define FireEtwGCSuspendEEBegin_V1(Reason, Count, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCSuspendEEBegin_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCSuspendEE_V1(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCSuspendEEBegin_V1, Reason, Count, ClrInstanceID) : 0 + +#define FireEtwGCSuspendEEEnd_V1(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCSuspendEEEnd_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCNoUserData(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCSuspendEEEnd_V1, ClrInstanceID) : 0 + +#define FireEtwGCTerminateConcurrentThread_V1(ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCTerminateConcurrentThread_V1)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCTerminateConcurrentThread(Microsoft_Windows_Redhawk_GC_PublicHandle, &GCTerminateConcurrentThread_V1, ClrInstanceID) : 0 + +#define FireEtwModuleLoad_V2(ModuleID, AssemblyID, ModuleFlags, Reserved1, ModuleILPath, ModuleNativePath, ClrInstanceID, ManagedPdbSignature, ManagedPdbAge, ManagedPdbBuildPath, NativePdbSignature, NativePdbAge, NativePdbBuildPath) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &ModuleLoad_V2)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_ModuleLoadUnload_V2(Microsoft_Windows_Redhawk_GC_PublicHandle, &ModuleLoad_V2, ModuleID, AssemblyID, ModuleFlags, Reserved1, ModuleILPath, ModuleNativePath, ClrInstanceID, ManagedPdbSignature, ManagedPdbAge, ManagedPdbBuildPath, NativePdbSignature, NativePdbAge, NativePdbBuildPath) : 0 + +#define FireEtwSetGCHandle(HandleID, ObjectID, Kind, Generation, AppDomainID, ClrInstanceID) (MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PublicHandle, &SetGCHandle)) ? Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_SetGCHandle(Microsoft_Windows_Redhawk_GC_PublicHandle, &SetGCHandle, HandleID, ObjectID, Kind, Generation, AppDomainID, ClrInstanceID) : 0 + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_BulkType(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[11]; + EventDataDescCreate(&EventData[0], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[2], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_DestroyGCHandle(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, void* HandleID, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &HandleID, sizeof(void*)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Exception(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, LPCWSTR ExceptionType, LPCWSTR ExceptionMessage, void* ExceptionEIP, UInt32 ExceptionHRESULT, UInt16 ExceptionFlags, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[6]; + EventDataDescCreate(&EventData[0], (ExceptionType != NULL) ? ExceptionType : L"", (ExceptionType != NULL) ? (ULONG)((wcslen(ExceptionType) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[1], (ExceptionMessage != NULL) ? ExceptionMessage : L"", (ExceptionMessage != NULL) ? (ULONG)((wcslen(ExceptionMessage) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[2], &ExceptionEIP, sizeof(void*)); + EventDataDescCreate(&EventData[3], &ExceptionHRESULT, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &ExceptionFlags, sizeof(UInt16)); + EventDataDescCreate(&EventData[5], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 6, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCAllocationTick_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 AllocationAmount, UInt32 AllocationKind, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[3]; + EventDataDescCreate(&EventData[0], &AllocationAmount, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &AllocationKind, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCAllocationTick_V2(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 AllocationAmount, UInt32 AllocationKind, UInt16 ClrInstanceID, UInt64 AllocationAmount64, void* TypeID, LPCWSTR TypeName, UInt32 HeapIndex) +{ + EVENT_DATA_DESCRIPTOR EventData[7]; + EventDataDescCreate(&EventData[0], &AllocationAmount, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &AllocationKind, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], &AllocationAmount64, sizeof(UInt64)); + EventDataDescCreate(&EventData[4], &TypeID, sizeof(void*)); + EventDataDescCreate(&EventData[5], (TypeName != NULL) ? TypeName : L"", (TypeName != NULL) ? (ULONG)((wcslen(TypeName) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[6], &HeapIndex, sizeof(UInt32)); + return PalEventWrite(RegHandle, Descriptor, 7, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCAllocationTick_V3(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 AllocationAmount, UInt32 AllocationKind, UInt16 ClrInstanceID, UInt64 AllocationAmount64, void* TypeID, LPCWSTR TypeName, UInt32 HeapIndex, void* Address) +{ + EVENT_DATA_DESCRIPTOR EventData[8]; + EventDataDescCreate(&EventData[0], &AllocationAmount, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &AllocationKind, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], &AllocationAmount64, sizeof(UInt64)); + EventDataDescCreate(&EventData[4], &TypeID, sizeof(void*)); + EventDataDescCreate(&EventData[5], (TypeName != NULL) ? TypeName : L"", (TypeName != NULL) ? (ULONG)((wcslen(TypeName) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[6], &HeapIndex, sizeof(UInt32)); + EventDataDescCreate(&EventData[7], &Address, sizeof(void*)); + return PalEventWrite(RegHandle, Descriptor, 8, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkEdge(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Index, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[6]; + EventDataDescCreate(&EventData[0], &Index, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkMovedObjectRanges(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Index, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[7]; + EventDataDescCreate(&EventData[0], &Index, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkNode(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Index, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[8]; + EventDataDescCreate(&EventData[0], &Index, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRCW(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[9]; + EventDataDescCreate(&EventData[0], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[2], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRootCCW(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[10]; + EventDataDescCreate(&EventData[0], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[2], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRootConditionalWeakTableElementEdge(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Index, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[7]; + EventDataDescCreate(&EventData[0], &Index, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkRootEdge(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Index, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[8]; + EventDataDescCreate(&EventData[0], &Index, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCBulkSurvivingObjectRanges(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Index, UInt32 Count, UInt16 ClrInstanceID, ULONG Values_Len_, const PVOID Values) +{ + EVENT_DATA_DESCRIPTOR EventData[6]; + EventDataDescCreate(&EventData[0], &Index, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[3], Values, Count * Values_Len_); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCCreateConcurrentThread(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[1]; + EventDataDescCreate(&EventData[0], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 1, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCCreateSegment_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 Address, UInt64 Size, UInt32 Type, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[4]; + EventDataDescCreate(&EventData[0], &Address, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &Size, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &Type, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 4, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCEnd_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Count, UInt32 Depth, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[3]; + EventDataDescCreate(&EventData[0], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Depth, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCFreeSegment_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 Address, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &Address, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCGenerationRange(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt8 Generation, void* RangeStart, UInt64 RangeUsedLength, UInt64 RangeReservedLength, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[5]; + EventDataDescCreate(&EventData[0], &Generation, sizeof(UInt8)); + EventDataDescCreate(&EventData[1], &RangeStart, sizeof(void*)); + EventDataDescCreate(&EventData[2], &RangeUsedLength, sizeof(UInt64)); + EventDataDescCreate(&EventData[3], &RangeReservedLength, sizeof(UInt64)); + EventDataDescCreate(&EventData[4], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 5, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCHeapStats_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 GenerationSize0, UInt64 TotalPromotedSize0, UInt64 GenerationSize1, UInt64 TotalPromotedSize1, UInt64 GenerationSize2, UInt64 TotalPromotedSize2, UInt64 GenerationSize3, UInt64 TotalPromotedSize3, UInt64 FinalizationPromotedSize, UInt64 FinalizationPromotedCount, UInt32 PinnedObjectCount, UInt32 SinkBlockCount, UInt32 GCHandleCount, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[14]; + EventDataDescCreate(&EventData[0], &GenerationSize0, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &TotalPromotedSize0, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &GenerationSize1, sizeof(UInt64)); + EventDataDescCreate(&EventData[3], &TotalPromotedSize1, sizeof(UInt64)); + EventDataDescCreate(&EventData[4], &GenerationSize2, sizeof(UInt64)); + EventDataDescCreate(&EventData[5], &TotalPromotedSize2, sizeof(UInt64)); + EventDataDescCreate(&EventData[6], &GenerationSize3, sizeof(UInt64)); + EventDataDescCreate(&EventData[7], &TotalPromotedSize3, sizeof(UInt64)); + EventDataDescCreate(&EventData[8], &FinalizationPromotedSize, sizeof(UInt64)); + EventDataDescCreate(&EventData[9], &FinalizationPromotedCount, sizeof(UInt64)); + EventDataDescCreate(&EventData[10], &PinnedObjectCount, sizeof(UInt32)); + EventDataDescCreate(&EventData[11], &SinkBlockCount, sizeof(UInt32)); + EventDataDescCreate(&EventData[12], &GCHandleCount, sizeof(UInt32)); + EventDataDescCreate(&EventData[13], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 14, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCMark(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 HeapNum, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[2]; + EventDataDescCreate(&EventData[0], &HeapNum, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 2, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCNoUserData(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[1]; + EventDataDescCreate(&EventData[0], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 1, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCStart_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Count, UInt32 Depth, UInt32 Reason, UInt32 Type, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[5]; + EventDataDescCreate(&EventData[0], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Depth, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &Reason, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &Type, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 5, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCStart_V2(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Count, UInt32 Depth, UInt32 Reason, UInt32 Type, UInt16 ClrInstanceID, UInt64 ClientSequenceNumber) +{ + EVENT_DATA_DESCRIPTOR EventData[6]; + EventDataDescCreate(&EventData[0], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Depth, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &Reason, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &Type, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[5], &ClientSequenceNumber, sizeof(UInt64)); + return PalEventWrite(RegHandle, Descriptor, 6, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCSuspendEE_V1(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt32 Reason, UInt32 Count, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[3]; + EventDataDescCreate(&EventData[0], &Reason, sizeof(UInt32)); + EventDataDescCreate(&EventData[1], &Count, sizeof(UInt32)); + EventDataDescCreate(&EventData[2], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 3, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_GCTerminateConcurrentThread(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[1]; + EventDataDescCreate(&EventData[0], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 1, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_ModuleLoadUnload_V2(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, UInt64 ModuleID, UInt64 AssemblyID, UInt32 ModuleFlags, UInt32 Reserved1, LPCWSTR ModuleILPath, LPCWSTR ModuleNativePath, UInt16 ClrInstanceID, const GUID* ManagedPdbSignature, UInt32 ManagedPdbAge, LPCWSTR ManagedPdbBuildPath, const GUID* NativePdbSignature, UInt32 NativePdbAge, LPCWSTR NativePdbBuildPath) +{ + EVENT_DATA_DESCRIPTOR EventData[13]; + EventDataDescCreate(&EventData[0], &ModuleID, sizeof(UInt64)); + EventDataDescCreate(&EventData[1], &AssemblyID, sizeof(UInt64)); + EventDataDescCreate(&EventData[2], &ModuleFlags, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &Reserved1, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], (ModuleILPath != NULL) ? ModuleILPath : L"", (ModuleILPath != NULL) ? (ULONG)((wcslen(ModuleILPath) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[5], (ModuleNativePath != NULL) ? ModuleNativePath : L"", (ModuleNativePath != NULL) ? (ULONG)((wcslen(ModuleNativePath) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[6], &ClrInstanceID, sizeof(UInt16)); + EventDataDescCreate(&EventData[7], ManagedPdbSignature, sizeof(*(ManagedPdbSignature))); + EventDataDescCreate(&EventData[8], &ManagedPdbAge, sizeof(UInt32)); + EventDataDescCreate(&EventData[9], (ManagedPdbBuildPath != NULL) ? ManagedPdbBuildPath : L"", (ManagedPdbBuildPath != NULL) ? (ULONG)((wcslen(ManagedPdbBuildPath) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + EventDataDescCreate(&EventData[10], NativePdbSignature, sizeof(*(NativePdbSignature))); + EventDataDescCreate(&EventData[11], &NativePdbAge, sizeof(UInt32)); + EventDataDescCreate(&EventData[12], (NativePdbBuildPath != NULL) ? NativePdbBuildPath : L"", (NativePdbBuildPath != NULL) ? (ULONG)((wcslen(NativePdbBuildPath) + 1) * sizeof(WCHAR)) : (ULONG)sizeof(L"")); + return PalEventWrite(RegHandle, Descriptor, 13, EventData); +} + +RH_ETW_INLINE UInt32 +Template_MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_SetGCHandle(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor, void* HandleID, void* ObjectID, UInt32 Kind, UInt32 Generation, UInt64 AppDomainID, UInt16 ClrInstanceID) +{ + EVENT_DATA_DESCRIPTOR EventData[6]; + EventDataDescCreate(&EventData[0], &HandleID, sizeof(void*)); + EventDataDescCreate(&EventData[1], &ObjectID, sizeof(void*)); + EventDataDescCreate(&EventData[2], &Kind, sizeof(UInt32)); + EventDataDescCreate(&EventData[3], &Generation, sizeof(UInt32)); + EventDataDescCreate(&EventData[4], &AppDomainID, sizeof(UInt64)); + EventDataDescCreate(&EventData[5], &ClrInstanceID, sizeof(UInt16)); + return PalEventWrite(RegHandle, Descriptor, 6, EventData); +} + +RH_ETW_INLINE UInt32 +TemplateEventDescriptor(REGHANDLE RegHandle, const EVENT_DESCRIPTOR * Descriptor) +{ + return PalEventWrite(RegHandle, Descriptor, 0, NULL); +} + +#else // FEATURE_ETW + +#define ETW_EVENT_ENABLED(Context, EventDescriptor) false + +#define FireEtwBGC1stConEnd(ClrInstanceID) +#define FireEtwBGC1stNonConEnd(ClrInstanceID) +#define FireEtwBGC2ndConBegin(ClrInstanceID) +#define FireEtwBGC2ndConEnd(ClrInstanceID) +#define FireEtwBGC2ndNonConBegin(ClrInstanceID) +#define FireEtwBGC2ndNonConEnd(ClrInstanceID) +#define FireEtwBGCAllocWaitBegin(Reason, ClrInstanceID) +#define FireEtwBGCAllocWaitEnd(Reason, ClrInstanceID) +#define FireEtwBGCBegin(ClrInstanceID) +#define FireEtwBGCDrainMark(Objects, ClrInstanceID) +#define FireEtwBGCOverflow(Min, Max, Objects, IsLarge, ClrInstanceID) +#define FireEtwBGCPlanEnd(ClrInstanceID) +#define FireEtwBGCRevisit(Pages, Objects, IsLarge, ClrInstanceID) +#define FireEtwBGCSweepEnd(ClrInstanceID) +#define FireEtwGCFullNotify_V1(GenNumber, IsAlloc, ClrInstanceID) +#define FireEtwGCGlobalHeapHistory_V1(FinalYoungestDesired, NumHeaps, CondemnedGeneration, Gen0ReductionCount, Reason, GlobalMechanisms, ClrInstanceID) +#define FireEtwGCJoin_V1(Heap, JoinTime, JoinType, ClrInstanceID) +#define FireEtwGCOptimized_V1(DesiredAllocation, NewAllocation, GenerationNumber, ClrInstanceID) +#define FireEtwGCPerHeapHistory() +#define FireEtwGCSettings(SegmentSize, LargeObjectSegmentSize, ServerGC) +#define FireEtwPrvDestroyGCHandle(HandleID, ClrInstanceID) +#define FireEtwPrvGCMarkCards_V1(HeapNum, ClrInstanceID) +#define FireEtwPrvGCMarkFinalizeQueueRoots_V1(HeapNum, ClrInstanceID) +#define FireEtwPrvGCMarkHandles_V1(HeapNum, ClrInstanceID) +#define FireEtwPrvGCMarkStackRoots_V1(HeapNum, ClrInstanceID) +#define FireEtwPrvSetGCHandle(HandleID, ObjectID, Kind, Generation, AppDomainID, ClrInstanceID) + +#define FireEtwBulkType(Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwDestroyGCHandle(HandleID, ClrInstanceID) +#define FireEtwExceptionThrown_V1(ExceptionType, ExceptionMessage, ExceptionEIP, ExceptionHRESULT, ExceptionFlags, ClrInstanceID) +#define FireEtwGCAllocationTick_V1(AllocationAmount, AllocationKind, ClrInstanceID) +#define FireEtwGCAllocationTick_V2(AllocationAmount, AllocationKind, ClrInstanceID, AllocationAmount64, TypeID, TypeName, HeapIndex) +#define FireEtwGCAllocationTick_V3(AllocationAmount, AllocationKind, ClrInstanceID, AllocationAmount64, TypeID, TypeName, HeapIndex, Address) +#define FireEtwGCBulkEdge(Index, Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkMovedObjectRanges(Index, Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkNode(Index, Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkRCW(Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkRootCCW(Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkRootConditionalWeakTableElementEdge(Index, Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkRootEdge(Index, Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCBulkSurvivingObjectRanges(Index, Count, ClrInstanceID, Values_Len_, Values) +#define FireEtwGCCreateConcurrentThread_V1(ClrInstanceID) +#define FireEtwGCCreateSegment_V1(Address, Size, Type, ClrInstanceID) +#define FireEtwGCEnd_V1(Count, Depth, ClrInstanceID) +#define FireEtwGCFreeSegment_V1(Address, ClrInstanceID) +#define FireEtwGCGenerationRange(Generation, RangeStart, RangeUsedLength, RangeReservedLength, ClrInstanceID) +#define FireEtwGCHeapStats_V1(GenerationSize0, TotalPromotedSize0, GenerationSize1, TotalPromotedSize1, GenerationSize2, TotalPromotedSize2, GenerationSize3, TotalPromotedSize3, FinalizationPromotedSize, FinalizationPromotedCount, PinnedObjectCount, SinkBlockCount, GCHandleCount, ClrInstanceID) +#define FireEtwGCMarkFinalizeQueueRoots(HeapNum, ClrInstanceID) +#define FireEtwGCMarkHandles(HeapNum, ClrInstanceID) +#define FireEtwGCMarkOlderGenerationRoots(HeapNum, ClrInstanceID) +#define FireEtwGCMarkStackRoots(HeapNum, ClrInstanceID) +#define FireEtwGCRestartEEBegin_V1(ClrInstanceID) +#define FireEtwGCRestartEEEnd_V1(ClrInstanceID) +#define FireEtwGCStart_V1(Count, Depth, Reason, Type, ClrInstanceID) +#define FireEtwGCStart_V2(Count, Depth, Reason, Type, ClrInstanceID, ClientSequenceNumber) +#define FireEtwGCSuspendEEBegin_V1(Reason, Count, ClrInstanceID) +#define FireEtwGCSuspendEEEnd_V1(ClrInstanceID) +#define FireEtwGCTerminateConcurrentThread_V1(ClrInstanceID) +#define FireEtwModuleLoad_V2(ModuleID, AssemblyID, ModuleFlags, Reserved1, ModuleILPath, ModuleNativePath, ClrInstanceID, ManagedPdbSignature, ManagedPdbAge, ManagedPdbBuildPath, NativePdbSignature, NativePdbAge, NativePdbBuildPath) +#define FireEtwSetGCHandle(HandleID, ObjectID, Kind, Generation, AppDomainID, ClrInstanceID) + +#endif // FEATURE_ETW + +#endif // !__RH_ETW_DEFS_INCLUDED diff --git a/src/Native/Runtime/FinalizerHelpers.cpp b/src/Native/Runtime/FinalizerHelpers.cpp new file mode 100644 index 00000000000..716de5411ce --- /dev/null +++ b/src/Native/Runtime/FinalizerHelpers.cpp @@ -0,0 +1,137 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Unmanaged helpers called by the managed finalizer thread. +// + +#include "gcrhenv.h" +#include "RuntimeInstance.h" +#include "module.h" + +// Block the current thread until at least one object needs to be finalized (returns true) or memory is low +// (returns false and the finalizer thread should initiate a garbage collection). +EXTERN_C REDHAWK_API UInt32_BOOL __cdecl RhpWaitForFinalizerRequest() +{ + // We can wait for two events; finalization queue has been populated and low memory resource notification. + // But if the latter is signalled we shouldn't wait on it again immediately -- if the garbage collection + // the finalizer thread initiates as a result is not sufficient to remove the low memory condition the + // event will still be signalled and we'll end up looping doing cpu intensive collections, which won't + // help the situation at all and could make it worse. So we remember whether the last event we reported + // was low memory and if so we'll wait at least two seconds (the CLR value) on just a finalization + // request. + static bool fLastEventWasLowMemory = false; + + GCHeap * pHeap = GCHeap::GetGCHeap(); + + // Wait in a loop because we may have to retry if we decide to only wait for finalization events but the + // two second timeout expires. + do + { + HANDLE rgWaitHandles[] = { pHeap->GetFinalizerEvent(), pHeap->GetLowMemoryNotificationEvent() }; + UInt32 cWaitHandles = (fLastEventWasLowMemory || pHeap->GetLowMemoryNotificationEvent() == NULL) ? 1 : 2; + UInt32 uTimeout = fLastEventWasLowMemory ? 2000 : INFINITE; + + UInt32 uResult = PalWaitForMultipleObjectsEx(cWaitHandles, rgWaitHandles, FALSE, uTimeout, FALSE); + switch (uResult) + { + case WAIT_OBJECT_0: + // At least one object is ready for finalization. + return TRUE; + + case WAIT_OBJECT_0 + 1: + // Memory is low, tell the finalizer thread to garbage collect. + ASSERT(!fLastEventWasLowMemory); + fLastEventWasLowMemory = true; + return FALSE; + + case WAIT_TIMEOUT: + // We were waiting only for finalization events but didn't get one within the timeout period. Go + // back to waiting for any event. + ASSERT(fLastEventWasLowMemory); + fLastEventWasLowMemory = false; + break; + + default: + ASSERT(!"Unexpected PalWaitForMultipleObjectsEx() result"); + return FALSE; + } + } while (true); +} + +// Indicate that the current round of finalizations is complete. +EXTERN_C REDHAWK_API void __cdecl RhpSignalFinalizationComplete() +{ + GCHeap::GetGCHeap()->SignalFinalizationDone(TRUE); +} + +// Enable a last pass of the finalizer during (clean) runtime shutdown. Specify the number of milliseconds +// we'll wait before giving up a proceeding with the shutdown (INFINITE is an allowable value). +COOP_PINVOKE_HELPER(void, RhEnableShutdownFinalization, (UInt32 uiTimeout)) +{ + g_fPerformShutdownFinalization = true; + g_uiShutdownFinalizationTimeout = uiTimeout; +} + +// Returns true when shutdown has started and it is no longer safe to access other objects from finalizers. +COOP_PINVOKE_HELPER(UInt8, RhHasShutdownStarted, ()) +{ + return g_fShutdownHasStarted ? 1 : 0; +} + +// +// The following helpers are special in that they interact with internal GC state or directly manipulate +// managed references so they're called with a special co-operative p/invoke. +// + +// Fetch next object which needs finalization or return null if we've reached the end of the list. +COOP_PINVOKE_HELPER(OBJECTREF, RhpGetNextFinalizableObject, ()) +{ + while (true) + { + // Get the next finalizable object. If we get back NULL we've reached the end of the list. + OBJECTREF refNext = GCHeap::GetGCHeap()->GetNextFinalizable(); + if (refNext == NULL) + return NULL; + + // The queue may contain objects which have been marked as finalized already (via GC.SuppressFinalize() + // for instance). Skip finalization for these but reset the flag so that the object can be put back on + // the list with RegisterForFinalization(). + if (refNext->GetHeader()->GetBits() & BIT_SBLK_FINALIZER_RUN) + { + refNext->GetHeader()->ClrBit(BIT_SBLK_FINALIZER_RUN); + continue; + } + + // We've found the first finalizable object, return it to the caller. + return refNext; + } +} + +// This function walks the list of modules looking for any module that is a class library and has not yet +// had its finalizer init callback invoked. It gets invoked in a loop, so it's technically O(n*m), but the +// number of classlibs subscribing to this callback is almost certainly going to be 1. +COOP_PINVOKE_HELPER(void *, RhpGetNextFinalizerInitCallback, ()) +{ + FOREACH_MODULE(pModule) + { + if (pModule->IsClasslibModule() + && !pModule->IsFinalizerInitComplete()) + { + pModule->SetFinalizerInitComplete(); + void * retval = pModule->GetClasslibInitializeFinalizerThread(); + + // The caller loops until we return null, so we should only be returning null if we've walked all + // the modules and found no callbacks yet to be made. + if (retval != NULL) + { + return retval; + } + } + } + END_FOREACH_MODULE; + + return NULL; +} diff --git a/src/Native/Runtime/GCHelpers.cpp b/src/Native/Runtime/GCHelpers.cpp new file mode 100644 index 00000000000..25b091ec350 --- /dev/null +++ b/src/Native/Runtime/GCHelpers.cpp @@ -0,0 +1,114 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Unmanaged helpers exposed by the System.GC managed class. +// + +#include "gcrhenv.h" +#include "restrictedcallouts.h" + +COOP_PINVOKE_HELPER(void, RhSuppressFinalize, (OBJECTREF refObj)) +{ + if (!refObj->get_EEType()->HasFinalizer()) + return; + + GCHeap::GetGCHeap()->SetFinalizationRun(refObj); +} + +EXTERN_C REDHAWK_API void __cdecl RhWaitForPendingFinalizers(BOOL allowReentrantWait) +{ + // This must be called via p/invoke rather than RuntimeImport since it blocks and could starve the GC if + // called in cooperative mode. + ASSERT(!GetThread()->PreemptiveGCDisabled()); + + GCHeap::GetGCHeap()->FinalizerThreadWait(INFINITE, allowReentrantWait); +} + +COOP_PINVOKE_HELPER(Int32, RhGetMaxGcGeneration, ()) +{ + return GCHeap::GetGCHeap()->GetMaxGeneration(); +} + +COOP_PINVOKE_HELPER(Int32, RhGetGcCollectionCount, (Int32 generation, Boolean getSpecialGCCount)) +{ + return GCHeap::GetGCHeap()->CollectionCount(generation, getSpecialGCCount); +} + +COOP_PINVOKE_HELPER(Int32, RhGetGeneration, (OBJECTREF obj)) +{ + return GCHeap::GetGCHeap()->WhichGeneration(obj); +} + +COOP_PINVOKE_HELPER(void, RhReRegisterForFinalizeHelper, (OBJECTREF obj)) +{ + if (obj->get_EEType()->HasFinalizer()) + GCHeap::GetGCHeap()->RegisterForFinalization(-1, obj); +} + +COOP_PINVOKE_HELPER(Int32, RhGetGcLatencyMode, ()) +{ + return GCHeap::GetGCHeap()->GetGcLatencyMode(); +} + +COOP_PINVOKE_HELPER(void, RhSetGcLatencyMode, (Int32 newLatencyMode)) +{ + GCHeap::GetGCHeap()->SetGcLatencyMode(newLatencyMode); +} + +COOP_PINVOKE_HELPER(Boolean, RhIsServerGc, ()) +{ + return GCHeap::IsServerHeap(); +} + +COOP_PINVOKE_HELPER(Int64, RhGetGcTotalMemoryHelper, ()) +{ + return GCHeap::GetGCHeap()->GetTotalBytesInUse(); +} + +COOP_PINVOKE_HELPER(Boolean, RhRegisterGcCallout, (GcRestrictedCalloutKind eKind, void * pCallout)) +{ + return RestrictedCallouts::RegisterGcCallout(eKind, pCallout); +} + +COOP_PINVOKE_HELPER(void, RhUnregisterGcCallout, (GcRestrictedCalloutKind eKind, void * pCallout)) +{ + RestrictedCallouts::UnregisterGcCallout(eKind, pCallout); +} + +COOP_PINVOKE_HELPER(Boolean, RhIsPromoted, (OBJECTREF obj)) +{ + return GCHeap::GetGCHeap()->IsPromoted(obj); +} + +COOP_PINVOKE_HELPER(Int32, RhGetLohCompactionMode, ()) +{ + return GCHeap::GetGCHeap()->GetLOHCompactionMode(); +} + +COOP_PINVOKE_HELPER(void, RhSetLohCompactionMode, (Int32 newLohCompactionMode)) +{ + GCHeap::GetGCHeap()->SetLOHCompactionMode(newLohCompactionMode); +} + +COOP_PINVOKE_HELPER(Int64, RhGetCurrentObjSize, ()) +{ + return GCHeap::GetGCHeap()->GetCurrentObjSize(); +} + +COOP_PINVOKE_HELPER(Int64, RhGetGCNow, ()) +{ + return GCHeap::GetGCHeap()->GetNow(); +} + +COOP_PINVOKE_HELPER(Int64, RhGetLastGCStartTime, (Int32 generation)) +{ + return GCHeap::GetGCHeap()->GetLastGCStartTime(generation); +} + +COOP_PINVOKE_HELPER(Int64, RhGetLastGCDuration, (Int32 generation)) +{ + return GCHeap::GetGCHeap()->GetLastGCDuration(generation); +} diff --git a/src/Native/Runtime/GcStressControl.cpp b/src/Native/Runtime/GcStressControl.cpp new file mode 100644 index 00000000000..73c710cb099 --- /dev/null +++ b/src/Native/Runtime/GcStressControl.cpp @@ -0,0 +1,184 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" + +#if defined(FEATURE_GC_STRESS) & !defined(DACCESS_COMPILE) + + +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "holder.h" +#include "crst.h" +#include "rhconfig.h" +#include "gcrhinterface.h" +#include "slist.h" +#include "varint.h" +#include "regdisplay.h" +#include "forward_declarations.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "event.h" +#include "rwlock.h" +#include "threadstore.h" +#include "shash.h" +#include "shash.inl" +#include "gcstresscontrol.h" + + +class GcStressControl +{ +public: + static bool ShouldHijack(UIntNative CallsiteIP, HijackType ht) + { + if (s_initState != isInited) + Initialize(); + + // don't hijack for GC stress if we're in a "no GC stress" region + Thread * pCurrentThread = ThreadStore::GetCurrentThread(); + if (pCurrentThread->IsSuppressGcStressSet()) + return false; + + if (g_pRhConfig->GetGcStressThrottleMode() == 0) + { + return true; + } + if (g_pRhConfig->GetGcStressThrottleMode() & gcstm_TriggerRandom) + { + if (GcStressTriggerRandom(CallsiteIP, ht, pCurrentThread)) + return true; + } + if (g_pRhConfig->GetGcStressThrottleMode() & gcstm_TriggerOnFirstHit) + { + if (GcStressTriggerFirstHit(CallsiteIP, ht)) + return true; + } + return false; + } + +private: + enum InitState { isNotInited, isIniting, isInited }; + + static void Initialize() + { + volatile InitState is = (InitState) PalInterlockedCompareExchange((volatile Int32*)(&s_initState), isIniting, isNotInited); + if (is == isNotInited) + { + s_lock.InitNoThrow(CrstGcStressControl); + + if (g_pRhConfig->GetGcStressSeed()) + s_lGcStressRNGSeed = g_pRhConfig->GetGcStressSeed(); + else + s_lGcStressRNGSeed = PalGetTickCount(); + + if (g_pRhConfig->GetGcStressFreqDenom()) + s_lGcStressFreqDenom = g_pRhConfig->GetGcStressFreqDenom(); + else + s_lGcStressFreqDenom = 10000; + + s_initState = isInited; + } + else + { + while (s_initState != isInited) + ; + } + } + + // returns true if no entry was found for CallsiteIP, false otherwise + static bool GcStressTrackAtIP(UIntNative CallsiteIP, HijackType ht, bool bForceGC) + { + // do this under a lock, as the underlying SHash might be "grown" by + // operations on other threads + + CrstHolder lh(&s_lock); + + const CallsiteCountEntry * pEntry = s_callsites.LookupPtr(CallsiteIP); + size_t hits; + + if (pEntry == NULL) + { + hits = 1; + CallsiteCountEntry e = {CallsiteIP, 1, 1, ht}; + s_callsites.AddOrReplace(e); + } + else + { + hits = ++(const_cast(pEntry)->countHit); + if (bForceGC) + { + ++(const_cast(pEntry)->countForced); + } + } + + return pEntry == NULL; + } + + static bool GcStressTriggerFirstHit(UIntNative CallsiteIP, HijackType ht) + { + return GcStressTrackAtIP(CallsiteIP, ht, false); + } + + static UInt32 GcStressRNG(UInt32 uMaxValue, Thread *pCurrentThread) + { + if (!pCurrentThread->IsRandInited()) + { + pCurrentThread->SetRandomSeed(s_lGcStressRNGSeed); + } + + return pCurrentThread->NextRand() % uMaxValue; + } + + static bool GcStressTriggerRandom(UIntNative CallsiteIP, HijackType ht, Thread *pCurrentThread) + { + bool bRes = false; + if (ht == htLoop) + { + bRes = GcStressRNG(s_lGcStressFreqDenom , pCurrentThread) < g_pRhConfig->GetGcStressFreqLoop(); + } + else if (ht == htCallsite) + { + bRes = GcStressRNG(s_lGcStressFreqDenom , pCurrentThread) < g_pRhConfig->GetGcStressFreqCallsite(); + } + if (bRes) + { + // if we're about to trigger a GC, track this in s_callsites + GcStressTrackAtIP(CallsiteIP, ht, true); + } + return bRes; + } + +private: + static CrstStatic s_lock; + static UInt32 s_lGcStressRNGSeed; + static UInt32 s_lGcStressFreqDenom; + static volatile InitState s_initState; + +public: + static CallsiteCountSHash s_callsites; // exposed to the DAC +}; + +// public interface: + +CallsiteCountSHash GcStressControl::s_callsites; +CrstStatic GcStressControl::s_lock; +UInt32 GcStressControl::s_lGcStressRNGSeed = 0; +UInt32 GcStressControl::s_lGcStressFreqDenom = 0; +volatile GcStressControl::InitState GcStressControl::s_initState = GcStressControl::isNotInited; + +GPTR_IMPL_INIT(CallsiteCountSHash, g_pCallsites, &GcStressControl::s_callsites); + +bool ShouldHijackForGcStress(UIntNative CallsiteIP, HijackType ht) +{ + return GcStressControl::ShouldHijack(CallsiteIP, ht); +} + +#endif // FEATURE_GC_STRESS & !DACCESS_COMPILE + + diff --git a/src/Native/Runtime/GcStressControl.h b/src/Native/Runtime/GcStressControl.h new file mode 100644 index 00000000000..a24cbc31cf7 --- /dev/null +++ b/src/Native/Runtime/GcStressControl.h @@ -0,0 +1,53 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifndef __GcStressControl_h__ +#define __GcStressControl_h__ + + +enum HijackType { htLoop, htCallsite }; +bool ShouldHijackForGcStress(UIntNative CallsiteIP, HijackType ht); + + +enum GcStressThrottleMode { + gcstm_TriggerAlways = 0x0000, // trigger a GC every time we hit a GC safe point + gcstm_TriggerOnFirstHit = 0x0001, // trigger a GC the first time a GC safe point is hit + gcstm_TriggerRandom = 0x0002, // trigger a GC randomly, as defined by GcStressFreqCallsite/GcStressFreqLoop/GcStressSeed +}; + +struct CallsiteCountEntry +{ + UIntNative callsiteIP; + UIntNative countHit; + UIntNative countForced; + HijackType ht; +}; + +typedef DPTR(CallsiteCountEntry) PTR_CallsiteCountEntry; + +class CallsiteCountTraits: public NoRemoveSHashTraits< DefaultSHashTraits < CallsiteCountEntry > > +{ +public: + typedef UIntNative key_t; + + static UIntNative GetKey(const CallsiteCountEntry & e) { return e.callsiteIP; } + + static count_t Hash(UIntNative k) + { return (count_t) k; } + + static bool Equals(UIntNative k1, UIntNative k2) + { return k1 == k2; } + + static CallsiteCountEntry Null() + { CallsiteCountEntry e; e.callsiteIP = 0; return e; } + + static bool IsNull(const CallsiteCountEntry & e) + { return e.callsiteIP == 0; } +}; + +typedef SHash < CallsiteCountTraits > CallsiteCountSHash; +typedef DPTR(CallsiteCountSHash) PTR_CallsiteCountSHash; + + +#endif // __GcStressControl_h__ diff --git a/src/Native/Runtime/GenericInstance.cpp b/src/Native/Runtime/GenericInstance.cpp new file mode 100644 index 00000000000..d96e462a85f --- /dev/null +++ b/src/Native/Runtime/GenericInstance.cpp @@ -0,0 +1,63 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "assert.h" +#include "rhbinder.h" +#include "eetype.h" +#include "genericinstance.h" +#else +#include "gcrhenv.h" +#endif + +bool UnifiedGenericInstance::Equals(GenericInstanceDesc * pLocalGid) +{ + GenericInstanceDesc * pCanonicalGid = GetGid(); + UInt32 cTypeVars = pCanonicalGid->GetArity(); + + // If the number of type arguments is different, we can never have a match. + if (cTypeVars != pLocalGid->GetArity()) + return false; + + // Compare the generic type itself. + if (pCanonicalGid->GetGenericTypeDef().GetValue() != pLocalGid->GetGenericTypeDef().GetValue()) + return false; + + // Compare the type arguments of the instantiation. + for (UInt32 i = 0; i < cTypeVars; i++) + { + EEType * pUnifiedType = pCanonicalGid->GetParameterType(i).GetValue(); + EEType * pLocalType = pLocalGid->GetParameterType(i).GetValue(); + if (pUnifiedType != pLocalType) + { + // Direct pointer comparison failed, but there are a couple of cases where converting the local + // generic instantiation to the unified version had to update the type variable EEType to avoid + // including a pointer to an arbitrary module (one not related to the generic instantiation via a + // direct type dependence). + // * Cloned types were converted to their underlying canonical types. + // * Some array types were re-written to use a module-neutral definition. + if (pLocalType->IsCanonical()) + return false; + if (pLocalType->IsCloned()) + { + if (pUnifiedType != pLocalType->get_CanonicalEEType()) + return false; + else + continue; // type parameter matches + } + ASSERT(pLocalType->IsParameterizedType()); + if (!pUnifiedType->IsParameterizedType()) + return false; + if (pUnifiedType->get_RelatedParameterType() != pLocalType->get_RelatedParameterType()) + return false; + if (pUnifiedType->get_ParameterizedTypeShape() != pLocalType->get_ParameterizedTypeShape()) + return false; + } + } + + return true; +} diff --git a/src/Native/Runtime/GenericInstance.h b/src/Native/Runtime/GenericInstance.h new file mode 100644 index 00000000000..594413b9e10 --- /dev/null +++ b/src/Native/Runtime/GenericInstance.h @@ -0,0 +1,38 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// Structure used to wrap a GenericInstanceDesc if we're not in standalone mode (a single exe with no further +// Redhawk dependencies). +// +// In such cases we might have several modules with local definitions of the same generic instantiation and in +// order to make these disjoint EETypes type compatible we have to unify them somehow. This is achieved by +// comparing all the generic instantiations a module contributes when it loads with the existing generic +// instantiations, using the data in the GenericInstanceDesc to determine type identity. When a new +// instantiation is found we allocate a new EEType and GenericInstanceDesc to represent the canonical version +// of the type (we allocate new versions rather that utilizing the version baked into the introducing module +// so as to support the module unload scenario). When a module contributes a duplicate generic instantiation +// it finds these existing definitions and is unified to use them for certain operations that require the +// unique instantiation property (e.g. casting or access to static field data). The mechanism for the unifying +// redirect for EETypes is cloning (all module local generic EETypes become clones of the runtime allocated +// canonical EEType). For GenericInstanceDesc we use the following structure to track the canonical, +// runtime-allocated GenericInstanceDesc and also update fields in each module local GenericInstanceDesc that +// serve as indirection cells for static field lookup to match the values in the canonical version. +// +// A UnifiedGenericInstance structure is always immediately followed by a variable sized GenericInstanceDesc +// (the canonical copy). +// +// In the standalone case we never unify generic types; the single module continues to use the local +// non-cloned EEType and GenericInstanceDesc with their binder created values and we never allocate any +// UnifiedGenericInstance structures or EEType or GenericInstanceDesc copies. +// +// We determine which mode we're in (standlone or not) via a flag in the module header. +struct UnifiedGenericInstance +{ + UnifiedGenericInstance * m_pNext; // Next entry in the hash table chain + UInt32 m_cRefs; // Number of modules which have published this type + + bool Equals(GenericInstanceDesc * pInst); + GenericInstanceDesc * GetGid() { return (GenericInstanceDesc*)(this + 1); } +}; diff --git a/src/Native/Runtime/GenericInstanceDescFields.h b/src/Native/Runtime/GenericInstanceDescFields.h new file mode 100644 index 00000000000..51fd323fc6c --- /dev/null +++ b/src/Native/Runtime/GenericInstanceDescFields.h @@ -0,0 +1,388 @@ + // + // Copyright (c) Microsoft Corporation. All rights reserved. + // Licensed under the MIT license. See LICENSE file in the project root for full license information. + // + + // + // **** This is an automatically generated file. Do not edit by hand. **** + // Instead see GenericInstanceDescFields.src and the WriteOptionalFieldsCode.pl script. + // + // This file defines accessors for various optional fields. Each field has three methods defined: + // UInt32 GetOffset() + // T Get() + // void Set(T value) + // + // If the field is an array an additional UInt32 index parameter is added to each of these. + // + // The following fields are handled in this header: + // + // If GID_Instantiation flag is set: + // EEType : TgtPTR_EEType + // Arity : UInt32 + // GenericTypeDef : EETypeRef + // ParameterType : EETypeRef[Arity] + // + // If GID_Variance flag is set: + // ParameterVariance : GenericVarianceType[Arity] + // + // If GID_GcStaticFields flag is set: + // GcStaticFieldData : TgtPTR_UInt8 + // GcStaticFieldDesc : TgtPTR_StaticGcDesc + // + // If GID_GcRoots flag is set: + // NextGidWithGcRoots : TgtPTR_GenericInstanceDesc + // + // If GID_Unification flag is set: + // SizeOfNonGcStaticFieldData : UInt32 + // SizeOfGcStaticFieldData : UInt32 + // + // If GID_ThreadStaticFields flag is set: + // ThreadStaticFieldTlsIndex : UInt32 + // ThreadStaticFieldStartOffset : UInt32 + // ThreadStaticFieldDesc : TgtPTR_StaticGcDesc + // + // If GID_NonGcStaticFields flag is set: + // NonGcStaticFieldData : TgtPTR_UInt8 + // + // Additionally two variants of a method to calculate the byte size of a GenericInstanceDesc are provided. + // A static version which determines the size from its arguments and an instance version which needs no + // arguments. + // + + enum _OptionalFieldTypes : UInt8 + { + GID_NoFields = 0x0, + GID_Instantiation = 0x1, + GID_Variance = 0x2, + GID_GcStaticFields = 0x4, + GID_GcRoots = 0x8, + GID_Unification = 0x10, + GID_ThreadStaticFields = 0x20, + GID_NonGcStaticFields = 0x40, + GID_AllFields = 0x7f + }; + typedef UInt8 OptionalFieldTypes; + + OptionalFieldTypes m_Flags; + + void Init(OptionalFieldTypes flags) { m_Flags = flags; } + OptionalFieldTypes GetFlags() { return m_Flags; } + + bool HasInstantiation() { return (m_Flags & GID_Instantiation) != 0; } + bool HasVariance() { return (m_Flags & GID_Variance) != 0; } + bool HasGcStaticFields() { return (m_Flags & GID_GcStaticFields) != 0; } + bool HasGcRoots() { return (m_Flags & GID_GcRoots) != 0; } + bool HasUnification() { return (m_Flags & GID_Unification) != 0; } + bool HasThreadStaticFields() { return (m_Flags & GID_ThreadStaticFields) != 0; } + bool HasNonGcStaticFields() { return (m_Flags & GID_NonGcStaticFields) != 0; } + + static UInt32 GetSize(OptionalFieldTypes flags, UInt32 arity) + { + return GetBaseSize(flags) + ((flags & GID_Instantiation) ? ((sizeof(EETypeRef) * arity)) : 0) + ((flags & GID_Variance) ? ((sizeof(GenericVarianceType) * arity)) : 0); + } + + UInt32 GetSize() + { + return GetBaseSize(m_Flags) + ((m_Flags & GID_Instantiation) ? ((sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0); + } + + UInt32 GetEETypeOffset() + { + ASSERT(HasInstantiation()); + return sizeof(UInt8); + } + + TgtPTR_EEType GetEEType() + { + ASSERT(HasInstantiation()); + return *dac_cast(dac_cast(this) + GetEETypeOffset()); + } + +#ifndef DACCESS_COMPILE + void SetEEType(TgtPTR_EEType value) + { + ASSERT(HasInstantiation()); + *(TgtPTR_EEType*)((UInt8*)this + GetEETypeOffset()) = value; + } +#endif + + UInt32 GetArityOffset() + { + ASSERT(HasInstantiation()); + return sizeof(UInt8) + sizeof(TgtPTR_EEType); + } + + UInt32 GetArity() + { + ASSERT(HasInstantiation()); + return *dac_cast(dac_cast(this) + GetArityOffset()); + } + +#ifndef DACCESS_COMPILE + void SetArity(UInt32 value) + { + ASSERT(HasInstantiation()); + *(UInt32*)((UInt8*)this + GetArityOffset()) = value; + } +#endif + + UInt32 GetGenericTypeDefOffset() + { + ASSERT(HasInstantiation()); + return sizeof(UInt8) + sizeof(TgtPTR_EEType) + sizeof(UInt32); + } + + EETypeRef GetGenericTypeDef() + { + ASSERT(HasInstantiation()); + return *dac_cast(dac_cast(this) + GetGenericTypeDefOffset()); + } + +#ifndef DACCESS_COMPILE + void SetGenericTypeDef(EETypeRef value) + { + ASSERT(HasInstantiation()); + *(EETypeRef*)((UInt8*)this + GetGenericTypeDefOffset()) = value; + } +#endif + + UInt32 GetParameterTypeOffset(UInt32 index) + { + ASSERT(HasInstantiation()); + return sizeof(UInt8) + sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (index * sizeof(EETypeRef)); + } + + EETypeRef GetParameterType(UInt32 index) + { + ASSERT(HasInstantiation()); + return *dac_cast(dac_cast(this) + GetParameterTypeOffset(index)); + } + +#ifndef DACCESS_COMPILE + void SetParameterType(UInt32 index, EETypeRef value) + { + ASSERT(HasInstantiation()); + *(EETypeRef*)((UInt8*)this + GetParameterTypeOffset(index)) = value; + } +#endif + + UInt32 GetParameterVarianceOffset(UInt32 index) + { + ASSERT(HasVariance()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + (index * sizeof(GenericVarianceType)); + } + + GenericVarianceType GetParameterVariance(UInt32 index) + { + ASSERT(HasVariance()); + return *dac_cast(dac_cast(this) + GetParameterVarianceOffset(index)); + } + +#ifndef DACCESS_COMPILE + void SetParameterVariance(UInt32 index, GenericVarianceType value) + { + ASSERT(HasVariance()); + *(GenericVarianceType*)((UInt8*)this + GetParameterVarianceOffset(index)) = value; + } +#endif + + UInt32 GetGcStaticFieldDataOffset() + { + ASSERT(HasGcStaticFields()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0); + } + + TgtPTR_UInt8 GetGcStaticFieldData() + { + ASSERT(HasGcStaticFields()); + return *dac_cast(dac_cast(this) + GetGcStaticFieldDataOffset()); + } + +#ifndef DACCESS_COMPILE + void SetGcStaticFieldData(TgtPTR_UInt8 value) + { + ASSERT(HasGcStaticFields()); + *(TgtPTR_UInt8*)((UInt8*)this + GetGcStaticFieldDataOffset()) = value; + } +#endif + + UInt32 GetGcStaticFieldDescOffset() + { + ASSERT(HasGcStaticFields()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + sizeof(TgtPTR_UInt8); + } + + TgtPTR_StaticGcDesc GetGcStaticFieldDesc() + { + ASSERT(HasGcStaticFields()); + return *dac_cast(dac_cast(this) + GetGcStaticFieldDescOffset()); + } + +#ifndef DACCESS_COMPILE + void SetGcStaticFieldDesc(TgtPTR_StaticGcDesc value) + { + ASSERT(HasGcStaticFields()); + *(TgtPTR_StaticGcDesc*)((UInt8*)this + GetGcStaticFieldDescOffset()) = value; + } +#endif + + UInt32 GetNextGidWithGcRootsOffset() + { + ASSERT(HasGcRoots()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0); + } + + TgtPTR_GenericInstanceDesc GetNextGidWithGcRoots() + { + ASSERT(HasGcRoots()); + return *dac_cast(dac_cast(this) + GetNextGidWithGcRootsOffset()); + } + +#ifndef DACCESS_COMPILE + void SetNextGidWithGcRoots(TgtPTR_GenericInstanceDesc value) + { + ASSERT(HasGcRoots()); + *(TgtPTR_GenericInstanceDesc*)((UInt8*)this + GetNextGidWithGcRootsOffset()) = value; + } +#endif + + UInt32 GetSizeOfNonGcStaticFieldDataOffset() + { + ASSERT(HasUnification()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0) + ((m_Flags & GID_GcRoots) ? (sizeof(TgtPTR_GenericInstanceDesc)) : 0); + } + + UInt32 GetSizeOfNonGcStaticFieldData() + { + ASSERT(HasUnification()); + return *dac_cast(dac_cast(this) + GetSizeOfNonGcStaticFieldDataOffset()); + } + +#ifndef DACCESS_COMPILE + void SetSizeOfNonGcStaticFieldData(UInt32 value) + { + ASSERT(HasUnification()); + *(UInt32*)((UInt8*)this + GetSizeOfNonGcStaticFieldDataOffset()) = value; + } +#endif + + UInt32 GetSizeOfGcStaticFieldDataOffset() + { + ASSERT(HasUnification()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0) + ((m_Flags & GID_GcRoots) ? (sizeof(TgtPTR_GenericInstanceDesc)) : 0) + sizeof(UInt32); + } + + UInt32 GetSizeOfGcStaticFieldData() + { + ASSERT(HasUnification()); + return *dac_cast(dac_cast(this) + GetSizeOfGcStaticFieldDataOffset()); + } + +#ifndef DACCESS_COMPILE + void SetSizeOfGcStaticFieldData(UInt32 value) + { + ASSERT(HasUnification()); + *(UInt32*)((UInt8*)this + GetSizeOfGcStaticFieldDataOffset()) = value; + } +#endif + + UInt32 GetThreadStaticFieldTlsIndexOffset() + { + ASSERT(HasThreadStaticFields()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0) + ((m_Flags & GID_GcRoots) ? (sizeof(TgtPTR_GenericInstanceDesc)) : 0) + ((m_Flags & GID_Unification) ? (sizeof(UInt32) + sizeof(UInt32)) : 0); + } + + UInt32 GetThreadStaticFieldTlsIndex() + { + ASSERT(HasThreadStaticFields()); + return *dac_cast(dac_cast(this) + GetThreadStaticFieldTlsIndexOffset()); + } + +#ifndef DACCESS_COMPILE + void SetThreadStaticFieldTlsIndex(UInt32 value) + { + ASSERT(HasThreadStaticFields()); + *(UInt32*)((UInt8*)this + GetThreadStaticFieldTlsIndexOffset()) = value; + } +#endif + + UInt32 GetThreadStaticFieldStartOffsetOffset() + { + ASSERT(HasThreadStaticFields()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0) + ((m_Flags & GID_GcRoots) ? (sizeof(TgtPTR_GenericInstanceDesc)) : 0) + ((m_Flags & GID_Unification) ? (sizeof(UInt32) + sizeof(UInt32)) : 0) + sizeof(UInt32); + } + + UInt32 GetThreadStaticFieldStartOffset() + { + ASSERT(HasThreadStaticFields()); + return *dac_cast(dac_cast(this) + GetThreadStaticFieldStartOffsetOffset()); + } + +#ifndef DACCESS_COMPILE + void SetThreadStaticFieldStartOffset(UInt32 value) + { + ASSERT(HasThreadStaticFields()); + *(UInt32*)((UInt8*)this + GetThreadStaticFieldStartOffsetOffset()) = value; + } +#endif + + UInt32 GetThreadStaticFieldDescOffset() + { + ASSERT(HasThreadStaticFields()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0) + ((m_Flags & GID_GcRoots) ? (sizeof(TgtPTR_GenericInstanceDesc)) : 0) + ((m_Flags & GID_Unification) ? (sizeof(UInt32) + sizeof(UInt32)) : 0) + sizeof(UInt32) + sizeof(UInt32); + } + + TgtPTR_StaticGcDesc GetThreadStaticFieldDesc() + { + ASSERT(HasThreadStaticFields()); + return *dac_cast(dac_cast(this) + GetThreadStaticFieldDescOffset()); + } + +#ifndef DACCESS_COMPILE + void SetThreadStaticFieldDesc(TgtPTR_StaticGcDesc value) + { + ASSERT(HasThreadStaticFields()); + *(TgtPTR_StaticGcDesc*)((UInt8*)this + GetThreadStaticFieldDescOffset()) = value; + } +#endif + + UInt32 GetNonGcStaticFieldDataOffset() + { + ASSERT(HasNonGcStaticFields()); + return sizeof(UInt8) + ((m_Flags & GID_Instantiation) ? (sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef) + (sizeof(EETypeRef) * GetArity())) : 0) + ((m_Flags & GID_Variance) ? ((sizeof(GenericVarianceType) * GetArity())) : 0) + ((m_Flags & GID_GcStaticFields) ? (sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc)) : 0) + ((m_Flags & GID_GcRoots) ? (sizeof(TgtPTR_GenericInstanceDesc)) : 0) + ((m_Flags & GID_Unification) ? (sizeof(UInt32) + sizeof(UInt32)) : 0) + ((m_Flags & GID_ThreadStaticFields) ? (sizeof(UInt32) + sizeof(UInt32) + sizeof(TgtPTR_StaticGcDesc)) : 0); + } + + TgtPTR_UInt8 GetNonGcStaticFieldData() + { + ASSERT(HasNonGcStaticFields()); + return *dac_cast(dac_cast(this) + GetNonGcStaticFieldDataOffset()); + } + +#ifndef DACCESS_COMPILE + void SetNonGcStaticFieldData(TgtPTR_UInt8 value) + { + ASSERT(HasNonGcStaticFields()); + *(TgtPTR_UInt8*)((UInt8*)this + GetNonGcStaticFieldDataOffset()) = value; + } +#endif + + enum _FieldBaseSizes + { + kBaseSizeInstantiation = sizeof(TgtPTR_EEType) + sizeof(UInt32) + sizeof(EETypeRef), + kBaseSizeVariance = 0, + kBaseSizeGcStaticFields = sizeof(TgtPTR_UInt8) + sizeof(TgtPTR_StaticGcDesc), + kBaseSizeGcRoots = sizeof(TgtPTR_GenericInstanceDesc), + kBaseSizeUnification = sizeof(UInt32) + sizeof(UInt32), + kBaseSizeThreadStaticFields = sizeof(UInt32) + sizeof(UInt32) + sizeof(TgtPTR_StaticGcDesc), + kBaseSizeNonGcStaticFields = sizeof(TgtPTR_UInt8), + }; + + static inline UInt32 GetBaseSize(OptionalFieldTypes flags) + { + static const UInt32 s_rgSizeTable[] = + { + sizeof(UInt8), sizeof(UInt8) + kBaseSizeInstantiation, sizeof(UInt8) + kBaseSizeVariance, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance, sizeof(UInt8) + kBaseSizeGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields, sizeof(UInt8) + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots, sizeof(UInt8) + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification, sizeof(UInt8) + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields, sizeof(UInt8) + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, sizeof(UInt8) + kBaseSizeInstantiation + kBaseSizeVariance + kBaseSizeGcStaticFields + kBaseSizeGcRoots + kBaseSizeUnification + kBaseSizeThreadStaticFields + kBaseSizeNonGcStaticFields, + }; + ASSERT(flags <= GID_AllFields); + return s_rgSizeTable[flags]; + } diff --git a/src/Native/Runtime/GenericInstanceDescFields.src b/src/Native/Runtime/GenericInstanceDescFields.src new file mode 100644 index 00000000000..99d50e5e61c --- /dev/null +++ b/src/Native/Runtime/GenericInstanceDescFields.src @@ -0,0 +1,47 @@ +; +; Copyright (c) Microsoft Corporation. All rights reserved. +; Licensed under the MIT license. See LICENSE file in the project root for full license information. +; + +; +; Defines optional fields for GenericInstanceDesc. +; +; Each line describes a single field; the name (used to create the names of the generated accessors to get the +; field offset, get or set the value), the field type, the name of a flag that determines whether this field +; is present in a particular GenericInstanceDesc or not (GID_ will be prepended) and finally an optional value +; that if provided indicates the field is an array of the given type whose size is determined by the field +; named by the value (which obviously must be non-optional for all the cases where the array field is +; non-optional). +; +; This file is input to the WriteOptionalFieldsCode.pl script in rh\tools which will in turn write the result +; into rh\src\inc\GenericInstanceDescFields.h (the script requires no parameters currently, everything is +; hardcoded and all that's required is that you check out GenericInstanceDescFields.h from source code control +; first -- these fields will change seldom enough that we don't run the script during build and instead +; updates are manual). +; + +; +; Field name Field type Flag that determines For array fields, field +; field presence that determines size of array +; ========== ========== ==================== ============================= + +EEType, TgtPTR_EEType, Instantiation +Arity, UInt32, Instantiation +GenericTypeDef, EETypeRef, Instantiation +ParameterType, EETypeRef, Instantiation, Arity + +ParameterVariance, GenericVarianceType, Variance, Arity + +GcStaticFieldData, TgtPTR_UInt8, GcStaticFields +GcStaticFieldDesc, TgtPTR_StaticGcDesc, GcStaticFields + +NextGidWithGcRoots, TgtPTR_GenericInstanceDesc, GcRoots + +SizeOfNonGcStaticFieldData, UInt32, Unification +SizeOfGcStaticFieldData, UInt32, Unification + +ThreadStaticFieldTlsIndex, UInt32, ThreadStaticFields +ThreadStaticFieldStartOffset, UInt32, ThreadStaticFields +ThreadStaticFieldDesc, TgtPTR_StaticGcDesc, ThreadStaticFields + +NonGcStaticFieldData, TgtPTR_UInt8, NonGcStaticFields diff --git a/src/Native/Runtime/HandleTableHelpers.cpp b/src/Native/Runtime/HandleTableHelpers.cpp new file mode 100644 index 00000000000..7f84e093a0b --- /dev/null +++ b/src/Native/Runtime/HandleTableHelpers.cpp @@ -0,0 +1,81 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Helper functions that are p/invoked from redhawkm in order to expose handle table functionality to managed +// code. These p/invokes are special in that the handle table code requires we remain in co-operative mode +// (since these routines mutate the handle tables which are also accessed during garbage collections). The +// binder has special knowledge of these methods and doesn't generate the normal code to transition out of the +// runtime prior to the call. +// + +#include "gcrhenv.h" +#include "restrictedcallouts.h" + +COOP_PINVOKE_HELPER(OBJECTHANDLE, RhpHandleAlloc, (Object *pObject, int type)) +{ + return CreateTypedHandle(g_HandleTableMap.pBuckets[0]->pTable[GetCurrentThreadHomeHeapNumber()], pObject, type); +} + +COOP_PINVOKE_HELPER(OBJECTHANDLE, RhpHandleAllocDependent, (Object *pPrimary, Object *pSecondary)) +{ + return CreateDependentHandle(g_HandleTableMap.pBuckets[0]->pTable[GetCurrentThreadHomeHeapNumber()], pPrimary, pSecondary); +} + +COOP_PINVOKE_HELPER(void, RhHandleFree, (OBJECTHANDLE handle)) +{ + DestroyTypedHandle(handle); +} + +COOP_PINVOKE_HELPER(Object *, RhHandleGet, (OBJECTHANDLE handle)) +{ + return ObjectFromHandle(handle); +} + +COOP_PINVOKE_HELPER(Object *, RhHandleGetDependent, (OBJECTHANDLE handle, Object **ppSecondary)) +{ + *ppSecondary = GetDependentHandleSecondary(handle); + return ObjectFromHandle(handle); +} + +COOP_PINVOKE_HELPER(void, RhHandleSetDependentSecondary, (OBJECTHANDLE handle, Object *pSecondary)) +{ + SetDependentHandleSecondary(handle, pSecondary); +} + +COOP_PINVOKE_HELPER(void, RhHandleSet, (OBJECTHANDLE handle, Object *pObject)) +{ + StoreObjectInHandle(handle, pObject); +} + +COOP_PINVOKE_HELPER(Boolean, RhRegisterRefCountedHandleCallback, (void * pCallout, EEType * pTypeFilter)) +{ + return RestrictedCallouts::RegisterRefCountedHandleCallback(pCallout, pTypeFilter); +} + +COOP_PINVOKE_HELPER(void, RhUnregisterRefCountedHandleCallback, (void * pCallout, EEType * pTypeFilter)) +{ + RestrictedCallouts::UnregisterRefCountedHandleCallback(pCallout, pTypeFilter); +} + +COOP_PINVOKE_HELPER(OBJECTHANDLE, RhpHandleAllocVariable, (Object * pObject, UInt32 type)) +{ + return CreateVariableHandle(g_HandleTableMap.pBuckets[0]->pTable[GetCurrentThreadHomeHeapNumber()], pObject, type); +} + +COOP_PINVOKE_HELPER(UInt32, RhHandleGetVariableType, (OBJECTHANDLE handle)) +{ + return GetVariableHandleType(handle); +} + +COOP_PINVOKE_HELPER(void, RhHandleSetVariableType, (OBJECTHANDLE handle, UInt32 type)) +{ + UpdateVariableHandleType(handle, type); +} + +COOP_PINVOKE_HELPER(UInt32, RhHandleCompareExchangeVariableType, (OBJECTHANDLE handle, UInt32 oldType, UInt32 newType)) +{ + return CompareExchangeVariableHandleType(handle, oldType, newType); +} diff --git a/src/Native/Runtime/ICodeManager.h b/src/Native/Runtime/ICodeManager.h new file mode 100644 index 00000000000..9cbf0f0af59 --- /dev/null +++ b/src/Native/Runtime/ICodeManager.h @@ -0,0 +1,111 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#pragma once + +// TODO: Debugger/DAC support (look for TODO: JIT) + +struct REGDISPLAY; + +#define GC_CALL_INTERIOR 0x1 +#define GC_CALL_PINNED 0x2 +#define GC_CALL_CHECK_APP_DOMAIN 0x4 +#define GC_CALL_STATIC 0x8 + +typedef void (*GCEnumCallback)( + void * hCallback, // callback data + PTR_PTR_VOID pObject, // address of object-reference we are reporting + UInt32 flags // is this a pinned and/or interior pointer +); + +struct GCEnumContext +{ + GCEnumCallback pCallback; +}; + +enum GCRefKind +{ + GCRK_Scalar = 0x00, + GCRK_Object = 0x01, + GCRK_Byref = 0x02, + GCRK_Unknown = 0xFF, +}; + +// +// MethodInfo is placeholder type used to allocate space for MethodInfo. Maximum size +// of the actual method should be less or equal to the placeholder size. +// It avoids memory allocation during stackwalk. +// +class MethodInfo +{ + TADDR dummyPtrs[6]; + Int32 dummyInts[6]; +}; + +class EHEnumState +{ + TADDR dummyPtrs[2]; + Int32 dummyInts[2]; +}; + +enum EHClauseKind +{ + EH_CLAUSE_TYPED = 0, + EH_CLAUSE_FAULT = 1, + + // Local Exceptions + EH_CLAUSE_METHOD_BOUNDARY = 2, + EH_CLAUSE_FAIL_FAST = 3, + + // CLR Exceptions + EH_CLAUSE_FILTER = 2, + EH_CLAUSE_UNUSED = 3, +}; + +struct EHClause +{ + EHClauseKind m_clauseKind; + UInt32 m_tryStartOffset; + UInt32 m_tryEndOffset; + UInt32 m_filterOffset; + UInt32 m_handlerOffset; + void* m_pTargetType; +}; + +class ICodeManager +{ +public: + virtual bool FindMethodInfo(PTR_VOID ControlPC, + MethodInfo * pMethodInfoOut, + UInt32 * pCodeOffset) = 0; + + virtual bool IsFunclet(MethodInfo * pMethodInfo) = 0; + + virtual PTR_VOID GetFramePointer(MethodInfo * pMethodInfo, + REGDISPLAY * pRegisterSet) = 0; + + virtual void EnumGcRefs(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + GCEnumContext * hCallback) = 0; + + virtual bool UnwindStackFrame(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, // in/out + PTR_VOID * ppPreviousTransitionFrame) = 0; // out + + virtual bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, // in + PTR_PTR_VOID * ppvRetAddrLocation, // out + GCRefKind * pRetValueKind) = 0; // out + + virtual void UnsynchronizedHijackMethodLoops(MethodInfo * pMethodInfo) = 0; + + virtual void RemapHardwareFaultToGCSafePoint(MethodInfo * pMethodInfo, UInt32 * pCodeOffset) = 0; + + virtual bool EHEnumInit(MethodInfo * pMethodInfo, PTR_VOID * pMethodStartAddress, EHEnumState * pEHEnumState) = 0; + + virtual bool EHEnumNext(EHEnumState * pEHEnumState, EHClause * pEHClause) = 0; +}; diff --git a/src/Native/Runtime/InstanceStore.cpp b/src/Native/Runtime/InstanceStore.cpp new file mode 100644 index 00000000000..6b6e1a057c9 --- /dev/null +++ b/src/Native/Runtime/InstanceStore.cpp @@ -0,0 +1,65 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "type_traits.hpp" +#include "slist.h" +#include "holder.h" +#include "crst.h" +#include "instancestore.h" +#include "rwlock.h" +#include "runtimeinstance.h" + +#include "slist.inl" + +InstanceStore::InstanceStore() +{ +} + +InstanceStore::~InstanceStore() +{ +} + +// static +InstanceStore * InstanceStore::Create() +{ + NewHolder pInstanceStore = new InstanceStore(); + + pInstanceStore->m_Crst.Init(CrstInstanceStore); + + pInstanceStore.SuppressRelease(); + return pInstanceStore; +} + +void InstanceStore::Destroy() +{ + delete this; +} + +void InstanceStore::Insert(RuntimeInstance * pRuntimeInstance) +{ + CrstHolder ch(&m_Crst); + + m_InstanceList.PushHead(pRuntimeInstance); +} + +RuntimeInstance * InstanceStore::GetRuntimeInstance(HANDLE hPalInstance) +{ + CrstHolder ch(&m_Crst); + + for (SList::Iterator it = m_InstanceList.Begin(); it != m_InstanceList.End(); ++it) + { + if (it->GetPalInstance() == hPalInstance) + { + return *it; + } + } + return NULL; +} diff --git a/src/Native/Runtime/InstanceStore.h b/src/Native/Runtime/InstanceStore.h new file mode 100644 index 00000000000..26875ae65d9 --- /dev/null +++ b/src/Native/Runtime/InstanceStore.h @@ -0,0 +1,24 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +class RuntimeInstance; + +class InstanceStore +{ + SList m_InstanceList; + CrstStatic m_Crst; + +private: + InstanceStore(); + +public: + ~InstanceStore(); + static InstanceStore * Create(); + void Destroy(); + + RuntimeInstance * GetRuntimeInstance(HANDLE hPalInstance); + + void Insert(RuntimeInstance * pRuntimeInstance); +}; + diff --git a/src/Native/Runtime/MathHelpers.cpp b/src/Native/Runtime/MathHelpers.cpp new file mode 100644 index 00000000000..1ddfd3219fe --- /dev/null +++ b/src/Native/Runtime/MathHelpers.cpp @@ -0,0 +1,264 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "commonmacros.h" +#include "assert.h" + +// +// Floating point and 64-bit integer math helpers. +// + +EXTERN_C REDHAWK_API Int32 REDHAWK_CALLCONV RhpDbl2IntOvf(double val, Boolean* pShouldThrow) +{ + const double two31 = 2147483648.0; + *pShouldThrow = Boolean_false; + + // Note that this expression also works properly for val = NaN case + if (val > -two31 - 1 && val < two31) + return((int)val); + + *pShouldThrow = Boolean_true; + return 0; +} + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpDbl2LngOvf(double val, Boolean* pShouldThrow) +{ + const double two63 = 2147483648.0 * 4294967296.0; + *pShouldThrow = Boolean_false; + + // Note that this expression also works properly for val = NaN case + // We need to compare with the very next double to two63. 0x402 is epsilon to get us there. + if (val > -two63 - 0x402 && val < two63) + return((Int64)val); + + *pShouldThrow = Boolean_true; + return 0; +} + +EXTERN_C REDHAWK_API UInt64 REDHAWK_CALLCONV RhpDbl2ULngOvf(double val, Boolean* pShouldThrow) +{ + const double two64 = 2.0* 2147483648.0 * 4294967296.0; + *pShouldThrow = Boolean_false; + + // Note that this expression also works properly for val = NaN case + if (val < two64) + return((Int64)val); + + *pShouldThrow = Boolean_true; + return 0; +} + +EXTERN_C REDHAWK_API Int32 REDHAWK_CALLCONV RhpFlt2IntOvf(float val, Boolean* pShouldThrow) +{ + const double two31 = 2147483648.0; + *pShouldThrow = Boolean_false; + + // Note that this expression also works properly for val = NaN case + if (val > -two31 - 1 && val < two31) + return((int)val); + + *pShouldThrow = Boolean_true; + return 0; +} + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpFlt2LngOvf(float val, Boolean* pShouldThrow) +{ + const double two63 = 2147483648.0 * 4294967296.0; + *pShouldThrow = Boolean_false; + + // Note that this expression also works properly for val = NaN case + // We need to compare with the very next double to two63. 0x402 is epsilon to get us there. + if (val > -two63 - 0x402 && val < two63) + return((Int64)val); + + *pShouldThrow = Boolean_true; + return 0; +} + +EXTERN_C REDHAWK_API UInt64 REDHAWK_CALLCONV RhpDbl2ULng(double val) +{ + return((UInt64)val); +} + +#ifdef _ARM_ +EXTERN_C REDHAWK_API Int32 REDHAWK_CALLCONV RhpIDiv(Int32 i, Int32 j) +{ + return i / j; +} + +EXTERN_C REDHAWK_API Int32 REDHAWK_CALLCONV RhpIMod(Int32 i, Int32 j) +{ + return i % j; +} + +EXTERN_C REDHAWK_API UInt32 REDHAWK_CALLCONV RhpUDiv(UInt32 i, UInt32 j) +{ + return i / j; +} + +EXTERN_C REDHAWK_API UInt32 REDHAWK_CALLCONV RhpUMod(UInt32 i, UInt32 j) +{ + return i % j; +} + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpLMul(Int64 i, Int64 j) +{ + return i * j; +} + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpLDiv(Int64 i, Int64 j) +{ + return i / j; +} + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpLMod(Int64 i, Int64 j) +{ + return i % j; +} + +EXTERN_C REDHAWK_API UInt64 REDHAWK_CALLCONV RhpULMul(UInt64 i, UInt64 j) +{ + return i * j; +} + +EXTERN_C REDHAWK_API UInt64 REDHAWK_CALLCONV RhpULDiv(UInt64 i, UInt64 j) +{ + return i / j; +} + +EXTERN_C REDHAWK_API UInt64 REDHAWK_CALLCONV RhpULMod(UInt64 i, UInt64 j) +{ + return i % j; +} + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpDbl2Lng(double val) +{ + return (Int64)val; +} + +EXTERN_C REDHAWK_API double REDHAWK_CALLCONV RhpLng2Dbl(Int64 val) +{ + return (double)val; +} + +EXTERN_C REDHAWK_API double REDHAWK_CALLCONV RhpULng2Dbl(UInt64 val) +{ + return (double)val; +} + +#endif // _ARM_ + +// int/uint divide and remainder for ARM +// +// All the rest are X86 and ARM only +// +#if defined(_X86_) || defined(_ARM_) +// +// helper macro to multiply two 32-bit uints +// +#define Mul32x32To64(a, b) ((UInt64)((UInt32)(a)) * (UInt64)((UInt32)(b))) + +// +// helper macro to get high 32-bit of 64-bit int +// +#define Hi32Bits(a) ((UInt32)((UInt64)(a) >> 32)) + +EXTERN_C REDHAWK_API Int64 REDHAWK_CALLCONV RhpLMulOvf(Int64 i, Int64 j, Boolean* pShouldThrow) +{ + Int64 ret; + *pShouldThrow = Boolean_false; + + // Remember the sign of the result + Int32 sign = Hi32Bits(i) ^ Hi32Bits(j); + + // Convert to unsigned multiplication + if (i < 0) i = -i; + if (j < 0) j = -j; + + // Get the upper 32 bits of the numbers + UInt32 val1High = Hi32Bits(i); + UInt32 val2High = Hi32Bits(j); + + UInt64 valMid; + + if (val1High == 0) { + // Compute the 'middle' bits of the long multiplication + valMid = Mul32x32To64(val2High, i); + } + else { + if (val2High != 0) + goto ThrowExcep; + // Compute the 'middle' bits of the long multiplication + valMid = Mul32x32To64(val1High, j); + } + + // See if any bits after bit 32 are set + if (Hi32Bits(valMid) != 0) + goto ThrowExcep; + + ret = Mul32x32To64(i, j) + (((UInt64)(UInt32)valMid) << 32); + + // check for overflow + if (Hi32Bits(ret) < (UInt32)valMid) + goto ThrowExcep; + + if (sign >= 0) { + // have we spilled into the sign bit? + if (ret < 0) + goto ThrowExcep; + } + else { + ret = -ret; + // have we spilled into the sign bit? + if (ret > 0) + goto ThrowExcep; + } + return ret; + +ThrowExcep: + *pShouldThrow = Boolean_true; + return 0; +} +EXTERN_C REDHAWK_API UInt64 REDHAWK_CALLCONV RhpULMulOvf(UInt64 i, UInt64 j, Boolean* pShouldThrow) +{ + Int64 ret; + *pShouldThrow = Boolean_false; + + // Get the upper 32 bits of the numbers + Int32 val1High = Hi32Bits(i); + Int32 val2High = Hi32Bits(j); + + Int64 valMid; + + if (val1High == 0) { + if (val2High == 0) + return Mul32x32To64(i, j); + // Compute the 'middle' bits of the long multiplication + valMid = Mul32x32To64(val2High, i); + } + else { + if (val2High != 0) + goto ThrowExcep; + // Compute the 'middle' bits of the long multiplication + valMid = Mul32x32To64(val1High, j); + } + + // See if any bits after bit 32 are set + if (Hi32Bits(valMid) != 0) + goto ThrowExcep; + + ret = Mul32x32To64(i, j) + (((UInt64)valMid) << 32); + + // check for overflow + if (Hi32Bits(ret) < (UInt32)valMid) + goto ThrowExcep; + return ret; + +ThrowExcep: + *pShouldThrow = Boolean_true; + return 0; +} + +#endif // defined(_X86_) || defined(_ARM_) \ No newline at end of file diff --git a/src/Native/Runtime/MiscHelpers.cpp b/src/Native/Runtime/MiscHelpers.cpp new file mode 100644 index 00000000000..83883c3a33b --- /dev/null +++ b/src/Native/Runtime/MiscHelpers.cpp @@ -0,0 +1,737 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Miscellaneous unmanaged helpers called by managed code. +// + +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "slist.h" +#include "holder.h" +#include "crst.h" +#include "rhbinder.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "regdisplay.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "event.h" +#include "threadstore.h" +#include "gcrhinterface.h" +#include "module.h" +#include "eetype.h" +#include "objectlayout.h" +#include "genericinstance.h" +#include "slist.inl" +#include "eetype.inl" +#include "commonmacros.inl" + +// Busy spin for the given number of iterations. +COOP_PINVOKE_HELPER(void, RhSpinWait, (Int32 iterations)) +{ + for(int i = 0; i < iterations; i++) + PalYieldProcessor(); +} + +// Yield the cpu to another thread ready to process, if one is available. +COOP_PINVOKE_HELPER(UInt32_BOOL, RhYield, ()) +{ + return PalSwitchToThread(); +} + +// Get the rarely used (optional) flags of an EEType. If they're not present 0 will be returned. +COOP_PINVOKE_HELPER(UInt32, RhpGetEETypeRareFlags, (EEType * pEEType)) +{ + return pEEType->get_RareFlags(); +} + +// For an ICastable type return a pointer to code that implements ICastable.IsInstanceOfInterface. +COOP_PINVOKE_HELPER(UIntNative, RhpGetICastableIsInstanceOfInterfaceMethod, (EEType * pEEType)) +{ + ASSERT(pEEType->IsICastable()); + return (UIntNative)pEEType->get_ICastableIsInstanceOfInterfaceMethod(); +} + +// For an ICastable type return a pointer to code that implements ICastable.ICastableGetImplType. +COOP_PINVOKE_HELPER(UIntNative, RhpGetICastableGetImplTypeMethod, (EEType * pEEType)) +{ + ASSERT(pEEType->IsICastable()); + return (UIntNative)pEEType->get_ICastableGetImplTypeMethod(); +} + +// Return the unboxed size of a value type. +COOP_PINVOKE_HELPER(UInt32, RhGetValueTypeSize, (EEType * pEEType)) +{ + ASSERT(pEEType->get_IsValueType()); + + // get_BaseSize returns the GC size including space for the sync block index field, the EEType* and + // padding for GC heap alignment. Must subtract all of these to get the size used for locals, array + // elements or fields of another type. + return pEEType->get_BaseSize() - (sizeof(ObjHeader) + sizeof(EEType*) + pEEType->get_ValueTypeFieldPadding()); +} + +// Return the DispatchMap pointer of a type +COOP_PINVOKE_HELPER(DispatchMap*, RhGetDispatchMapForType, (EEType * pEEType)) +{ + return pEEType->GetDispatchMap(); +} + +// Get the list of currently loaded Redhawk modules (as OS HMODULE handles). The caller provides a reference +// to an array of pointer-sized elements and we return the total number of modules currently loaded (whether +// that is less than, equal to or greater than the number of elements in the array). If there are more modules +// loaded than the array will hold then the array is filled to capacity and the caller can tell further +// modules are available based on the return count. It is also possible to call this method without an array, +// in which case just the module count is returned (note that it's still possible for the module count to +// increase between calls to this method). +COOP_PINVOKE_HELPER(UInt32, RhGetLoadedModules, (Array * pResultArray)) +{ + // Note that we depend on the fact that this is a COOP helper to make writing into an unpinned array safe. + + // If a result array is passed then it should be an array type with pointer-sized components that are not + // GC-references. + ASSERT(!pResultArray || pResultArray->get_EEType()->IsArray()); + ASSERT(!pResultArray || !pResultArray->get_EEType()->HasReferenceFields()); + ASSERT(!pResultArray || pResultArray->get_EEType()->get_ComponentSize() == sizeof(void*)); + + UInt32 cResultArrayElements = pResultArray ? pResultArray->GetArrayLength() : 0; + HANDLE * pResultElements = pResultArray ? (HANDLE*)(pResultArray + 1) : NULL; + + UInt32 cModules = 0; + + FOREACH_MODULE(pModule) + { + if (pResultArray && (cModules < cResultArrayElements)) + pResultElements[cModules] = pModule->GetOsModuleHandle(); + + cModules++; + } + END_FOREACH_MODULE + + return cModules; +} + +COOP_PINVOKE_HELPER(HANDLE, RhGetModuleFromPointer, (PTR_VOID pPointerVal)) +{ + Module * pModule = GetRuntimeInstance()->FindModuleByAddress(pPointerVal); + + if (pModule != NULL) + return pModule->GetOsModuleHandle(); + + return NULL; +} + +COOP_PINVOKE_HELPER(HANDLE, RhGetModuleFromEEType, (EEType * pEEType)) +{ + // Runtime allocated EETypes have no associated module, but class libraries shouldn't be able to get to + // any of these since they're currently only used for the canonical version of a generic EEType and we + // provide no means to go from the cloned version to the canonical version. + ASSERT(!pEEType->IsRuntimeAllocated()); + + // For dynamically created types, return the module handle that contains the template type + if (pEEType->IsDynamicType()) + pEEType = pEEType->get_DynamicTemplateType(); + + FOREACH_MODULE(pModule) + { + if (pModule->ContainsReadOnlyDataAddress(pEEType) || pModule->ContainsDataAddress(pEEType)) + return pModule->GetOsModuleHandle(); + } + END_FOREACH_MODULE + + // We should never get here (an EEType not located in any module) so fail fast to indicate the bug. + RhFailFast(); + return NULL; +} + +COOP_PINVOKE_HELPER(Boolean, RhFindBlob, (HANDLE hOsModule, UInt32 blobId, UInt8 ** ppbBlob, UInt32 * pcbBlob)) +{ + // Search for the Redhawk module contained by the OS module. + FOREACH_MODULE(pModule) + { + if (pModule->GetOsModuleHandle() == hOsModule) + { + // Found a module match. Look through the blobs for one with a matching ID. + UInt32 cbBlobs; + BlobHeader * pBlob = pModule->GetReadOnlyBlobs(&cbBlobs); + + while (cbBlobs) + { + UInt32 cbTotalBlob = sizeof(BlobHeader) + pBlob->m_size; + ASSERT(cbBlobs >= cbTotalBlob); + + if (pBlob->m_id == blobId) + { + // Found the matching blob, return it. + *ppbBlob = (UInt8*)(pBlob + 1); + *pcbBlob = pBlob->m_size; + return TRUE; + } + + cbBlobs -= cbTotalBlob; + pBlob = (BlobHeader*)((UInt8*)pBlob + cbTotalBlob); + } + + // If we get here then we found a module match but didn't find a blob with a matching ID. That's a + // non-catastrophic error. + *ppbBlob = NULL; + *pcbBlob = 0; + return FALSE; + } + } + END_FOREACH_MODULE + + // If we get here we were passed a bad module handle and should fail fast since this indicates a nasty bug + // (which could lead to the wrong blob being returned in some cases). + RhFailFast(); + + return FALSE; +} + +// This helper is not called directly but is used by the implementation of RhpCheckCctor to locate the +// CheckStaticClassConstruction classlib callback. It must not trigger a GC. The return address passed points +// to code in the caller's module and can be used in the lookup. +COOP_PINVOKE_HELPER(void *, GetClasslibCCtorCheck, (void * pReturnAddress)) +{ + // Locate the calling module from the context structure address (which is in writeable memory in the + // module image). + Module * pModule = GetRuntimeInstance()->FindModuleByCodeAddress(pReturnAddress); + ASSERT(pModule); + + // Locate the classlib module from the calling module. + Module * pClasslibModule = pModule->GetClasslibModule(); + ASSERT(pClasslibModule); + + // Lookup the callback registered by the classlib. + void * pCallback = pClasslibModule->GetClasslibCheckStaticClassConstruction(); + + // We have no fallback path if we got here but the classlib doesn't implement the callback. + if (pCallback == NULL) + RhFailFast(); + + return pCallback; +} + +COOP_PINVOKE_HELPER(UInt8, RhpGetNullableEETypeValueOffset, (EEType * pEEType)) +{ + return pEEType->GetNullableValueOffset(); +} + +COOP_PINVOKE_HELPER(EEType *, RhpGetNullableEEType, (EEType * pEEType)) +{ + return pEEType->GetNullableType(); +} + +COOP_PINVOKE_HELPER(Boolean, RhpHasDispatchMap, (EEType * pEEType)) +{ + return pEEType->HasDispatchMap(); +} + +COOP_PINVOKE_HELPER(DispatchMap *, RhpGetDispatchMap, (EEType * pEEType)) +{ + return pEEType->GetDispatchMap(); +} + +COOP_PINVOKE_HELPER(EEType *, RhpGetArrayBaseType, (EEType * pEEType)) +{ + return pEEType->GetArrayBaseType(); +} + +COOP_PINVOKE_HELPER(PTR_Code, RhpGetSealedVirtualSlot, (EEType * pEEType, UInt16 slot)) +{ + return pEEType->get_SealedVirtualSlot(slot); +} + +// Obtain the address of a thread static field for the current thread given the enclosing type and a field cookie +// obtained from a fixed up binder blob field record. +COOP_PINVOKE_HELPER(UInt8 *, RhGetThreadStaticFieldAddress, (EEType * pEEType, ThreadStaticFieldOffsets* pFieldCookie)) +{ + RuntimeInstance * pRuntimeInstance = GetRuntimeInstance(); + + // We need two pieces of information to locate a thread static field for the current thread: a TLS index + // (one assigned per module) and an offset into the block of data allocated for each thread for that TLS + // index. + UInt32 uiTlsIndex; + UInt32 uiFieldOffset; + + if (pEEType->IsDynamicType()) + { + // Special case for thread static fields on dynamic types: the TLS storage is managed by the runtime + // for each dynamically created type with thread statics. The TLS storage size allocated for each type + // is the size of all the thread statics on that type. We use the field offset to get the thread static + // data for that field on the current thread. + GenericInstanceDesc * pGID = pRuntimeInstance->LookupGenericInstance(pEEType); + ASSERT(pGID != NULL); + UInt8* pTlsStorage = ThreadStore::GetCurrentThread()->GetThreadLocalStorageForDynamicType(pGID->GetThreadStaticFieldStartOffset()); + ASSERT(pTlsStorage != NULL); + return (pFieldCookie != NULL ? pTlsStorage + pFieldCookie->FieldOffset : pTlsStorage); + } + else if (!pRuntimeInstance->IsInStandaloneExeMode() && pEEType->IsGeneric()) + { + // The tricky case is a thread static field on a generic type when we're not in standalone mode. In that + // case we need to lookup the GenericInstanceDesc for the type to locate its TLS static base + // offset (which unlike the case below has already been fixed up to account for COFF mode linking). The + // cookie then contains an offset from that base. + GenericInstanceDesc * pGID = pRuntimeInstance->LookupGenericInstance(pEEType); + uiFieldOffset = pGID->GetThreadStaticFieldStartOffset() + pFieldCookie->FieldOffset; + + // The TLS index in the GenericInstanceDesc will always be 0 (unless we perform GID unification, which + // we don't today), so we'll need to get the TLS index from the header of the type's containing module. + Module * pModule = pRuntimeInstance->FindModuleByReadOnlyDataAddress(pEEType); + ASSERT(pModule != NULL); + uiTlsIndex = *pModule->GetModuleHeader()->PointerToTlsIndex; + } + else + { + // In all other cases the field cookie contains an offset from the base of all Redhawk thread statics + // to the field. The TLS index and offset adjustment (in cases where the module was linked with native + // code using .tls) is that from the exe module. + Module * pModule = pRuntimeInstance->FindModuleByReadOnlyDataAddress(pEEType); + if (pModule == NULL) + pModule = pRuntimeInstance->FindModuleByDataAddress(pEEType); + ASSERT(pModule != NULL); + ModuleHeader * pExeModuleHeader = pModule->GetModuleHeader(); + + uiTlsIndex = *pExeModuleHeader->PointerToTlsIndex; + uiFieldOffset = pExeModuleHeader->TlsStartOffset + pFieldCookie->StartingOffsetInTlsBlock + pFieldCookie->FieldOffset; + } + + // Now look at the current thread and retrieve the address of the field. + return ThreadStore::GetCurrentThread()->GetThreadLocalStorage(uiTlsIndex, uiFieldOffset); +} + +#if TARGET_ARM +//***************************************************************************** +// Extract the 16-bit immediate from ARM Thumb2 Instruction (format T2_N) +//***************************************************************************** +static FORCEINLINE UInt16 GetThumb2Imm16(UInt16 * p) +{ + return ((p[0] << 12) & 0xf000) | + ((p[0] << 1) & 0x0800) | + ((p[1] >> 4) & 0x0700) | + ((p[1] >> 0) & 0x00ff); +} + +//***************************************************************************** +// Extract the 32-bit immediate from movw/movt sequence +//***************************************************************************** +inline UInt32 GetThumb2Mov32(UInt16 * p) +{ + // Make sure we are decoding movw/movt sequence + ASSERT((*(p + 0) & 0xFBF0) == 0xF240); + ASSERT((*(p + 2) & 0xFBF0) == 0xF2C0); + + return (UInt32)GetThumb2Imm16(p) + ((UInt32)GetThumb2Imm16(p + 2) << 16); +} + +//***************************************************************************** +// Extract the 24-bit distance from a B/BL instruction +//***************************************************************************** +inline Int32 GetThumb2BlRel24(UInt16 * p) +{ + UInt16 Opcode0 = p[0]; + UInt16 Opcode1 = p[1]; + + UInt32 S = Opcode0 >> 10; + UInt32 J2 = Opcode1 >> 11; + UInt32 J1 = Opcode1 >> 13; + + Int32 ret = + ((S << 24) & 0x1000000) | + (((J1 ^ S ^ 1) << 23) & 0x0800000) | + (((J2 ^ S ^ 1) << 22) & 0x0400000) | + ((Opcode0 << 12) & 0x03FF000) | + ((Opcode1 << 1) & 0x0000FFE); + + // Sign-extend and return + return (ret << 7) >> 7; +} +#endif // TARGET_ARM + +// Given a pointer to code, find out if this points to an import stub +// or unboxing stub, and if so, return the address that stub jumps to +COOP_PINVOKE_HELPER(UInt8 *, RhGetCodeTarget, (UInt8 * pCodeOrg)) +{ + // Search for the module containing the code + FOREACH_MODULE(pModule) + { + // If the code pointer doesn't point to a module's stub range, + // it can't be pointing to a stub + if (!pModule->ContainsStubAddress(pCodeOrg)) + continue; + + bool unboxingStub = false; + +#ifdef TARGET_AMD64 + UInt8 * pCode = pCodeOrg; + + // is this "add rcx,8"? + if (pCode[0] == 0x48 && pCode[1] == 0x83 && pCode[2] == 0xc1 && pCode[3] == 0x08) + { + // unboxing sequence + unboxingStub = true; + pCode += 4; + } + // is this an indirect jump? + if (pCode[0] == 0xff && pCode[1] == 0x25) + { + // normal import stub - dist to IAT cell is relative to the point *after* the instruction + Int32 distToIatCell = *(Int32 *)&pCode[2]; + UInt8 ** pIatCell = (UInt8 **)(pCode + 6 + distToIatCell); + ASSERT(pModule->ContainsDataAddress(pIatCell)); + return *pIatCell; + } + // is this an unboxing stub followed by a relative jump? + else if (unboxingStub && pCode[0] == 0xe9) + { + // relatie jump - dist is relative to the point *after* the instruction + Int32 distToTarget = *(Int32 *)&pCode[1]; + UInt8 * target = pCode + 5 + distToTarget; + return target; + } + return pCodeOrg; + +#elif TARGET_X86 + UInt8 * pCode = pCodeOrg; + + // is this "add ecx,4"? + if (pCode[0] == 0x83 && pCode[1] == 0xc1 && pCode[2] == 0x04) + { + // unboxing sequence + unboxingStub = true; + pCode += 3; + } + // is this an indirect jump? + if (pCode[0] == 0xff && pCode[1] == 0x25) + { + // normal import stub - address of IAT follows + UInt8 **pIatCell = *(UInt8 ***)&pCode[2]; + ASSERT(pModule->ContainsDataAddress(pIatCell)); + return *pIatCell; + } + // is this an unboxing stub followed by a relative jump? + else if (unboxingStub && pCode[0] == 0xe9) + { + // relatie jump - dist is relative to the point *after* the instruction + Int32 distToTarget = *(Int32 *)&pCode[1]; + UInt8 * pTarget = pCode + 5 + distToTarget; + return pTarget; + } + return pCodeOrg; + +#elif TARGET_ARM + const UInt16 THUMB_BIT = 1; + UInt16 * pCode = (UInt16 *)((size_t)pCodeOrg & ~THUMB_BIT); + // is this "adds r0,4"? + if (pCode[0] == 0x3004) + { + // unboxing sequence + unboxingStub = true; + pCode += 1; + } + // is this movw r12,#imm16; movt r12,#imm16; ldr pc,[r12]? + if ((pCode[0] & 0xfbf0) == 0xf240 && (pCode[1] & 0x0f00) == 0x0c00 + && (pCode[2] & 0xfbf0) == 0xf2c0 && (pCode[3] & 0x0f00) == 0x0c00 + && pCode[4] == 0xf8dc && pCode[5] == 0xf000) + { + UInt8 **pIatCell = (UInt8 **)GetThumb2Mov32(pCode); + return *pIatCell; + } + // is this an unboxing stub followed by a relative jump? + else if (unboxingStub && (pCode[0] & 0xf800) == 0xf000 && (pCode[1] & 0xd000) == 0x9000) + { + Int32 distToTarget = GetThumb2BlRel24(pCode); + UInt8 * pTarget = (UInt8 *)(pCode + 2) + distToTarget + THUMB_BIT; + return (UInt8 *)pTarget; + } +#else +#error 'Unsupported Architecture' +#endif + } + END_FOREACH_MODULE; + + return pCodeOrg; +} + +FORCEINLINE void ForwardGCSafeCopy(void * dest, const void *src, size_t len) +{ + // All parameters must be pointer-size-aligned + ASSERT(IS_ALIGNED(dest, sizeof(size_t))); + ASSERT(IS_ALIGNED(src, sizeof(size_t))); + ASSERT(IS_ALIGNED(len, sizeof(size_t))); + + size_t size = len; + UInt8 * dmem = (UInt8 *)dest; + UInt8 * smem = (UInt8 *)src; + + // regions must be non-overlapping + ASSERT(dmem <= smem || smem + size <= dmem); + + // copy 4 pointers at a time + while (size >= 4 * sizeof(size_t)) + { + size -= 4 * sizeof(size_t); + ((size_t *)dmem)[0] = ((size_t *)smem)[0]; + ((size_t *)dmem)[1] = ((size_t *)smem)[1]; + ((size_t *)dmem)[2] = ((size_t *)smem)[2]; + ((size_t *)dmem)[3] = ((size_t *)smem)[3]; + smem += 4 * sizeof(size_t); + dmem += 4 * sizeof(size_t); + } + + // copy 2 trailing pointers, if needed + if ((size & (2 * sizeof(size_t))) != 0) + { + ((size_t *)dmem)[0] = ((size_t *)smem)[0]; + ((size_t *)dmem)[1] = ((size_t *)smem)[1]; + smem += 2 * sizeof(size_t); + dmem += 2 * sizeof(size_t); + } + + // finish with one pointer, if needed + if ((size & sizeof(size_t)) != 0) + { + ((size_t *)dmem)[0] = ((size_t *)smem)[0]; + } +} + +FORCEINLINE void BackwardGCSafeCopy(void * dest, const void *src, size_t len) +{ + // All parameters must be pointer-size-aligned + ASSERT(IS_ALIGNED(dest, sizeof(size_t))); + ASSERT(IS_ALIGNED(src, sizeof(size_t))); + ASSERT(IS_ALIGNED(len, sizeof(size_t))); + + size_t size = len; + UInt8 * dmem = (UInt8 *)dest + len; + UInt8 * smem = (UInt8 *)src + len; + + // regions must be non-overlapping + ASSERT(smem <= dmem || dmem + size <= smem); + + // copy 4 pointers at a time + while (size >= 4 * sizeof(size_t)) + { + size -= 4 * sizeof(size_t); + smem -= 4 * sizeof(size_t); + dmem -= 4 * sizeof(size_t); + ((size_t *)dmem)[3] = ((size_t *)smem)[3]; + ((size_t *)dmem)[2] = ((size_t *)smem)[2]; + ((size_t *)dmem)[1] = ((size_t *)smem)[1]; + ((size_t *)dmem)[0] = ((size_t *)smem)[0]; + } + + // copy 2 trailing pointers, if needed + if ((size & (2 * sizeof(size_t))) != 0) + { + smem -= 2 * sizeof(size_t); + dmem -= 2 * sizeof(size_t); + ((size_t *)dmem)[1] = ((size_t *)smem)[1]; + ((size_t *)dmem)[0] = ((size_t *)smem)[0]; + } + + // finish with one pointer, if needed + if ((size & sizeof(size_t)) != 0) + { + smem -= sizeof(size_t); + dmem -= sizeof(size_t); + ((size_t *)dmem)[0] = ((size_t *)smem)[0]; + } +} + +// This function fills a piece of memory in a GC safe way. It makes the guarantee +// that it will fill memory in at least pointer sized chunks whenever possible. +// Unaligned memory at the beginning and remaining bytes at the end are written bytewise. +// We must make this guarantee whenever we clear memory in the GC heap that could contain +// object references. The GC or other user threads can read object references at any time, +// clearing them bytewise can result in a read on another thread getting incorrect data. +FORCEINLINE void GCSafeFillMemory(void * mem, size_t size, size_t pv) +{ + UInt8 * memBytes = (UInt8 *)mem; + UInt8 * endBytes = &memBytes[size]; + + // handle unaligned bytes at the beginning + while (!IS_ALIGNED(memBytes, sizeof(void *)) && (memBytes < endBytes)) + *memBytes++ = (UInt8)pv; + + // now write pointer sized pieces + size_t nPtrs = (endBytes - memBytes) / sizeof(void *); + UIntNative* memPtr = (UIntNative*)memBytes; + for (size_t i = 0; i < nPtrs; i++) + *memPtr++ = pv; + + // handle remaining bytes at the end + memBytes = (UInt8*)memPtr; + while (memBytes < endBytes) + *memBytes++ = (UInt8)pv; +} + +// This is a GC-safe variant of memcpy. It guarantees that the object references in the GC heap are updated atomically. +// This is required for type safety and proper operation of the background GC. +// +// USAGE: 1) The caller is responsible for performing the appropriate bulk write barrier. +// 2) The caller is responsible for hoisting any null reference exceptions to a place where the hardware +// exception can be properly translated to a managed exception. This is handled by RhpCopyMultibyte. +// 3) The caller must ensure that all three parameters are pointer-size-aligned. This should be the case for +// value types which contain GC refs anyway, so if you want to copy structs without GC refs which might be +// unaligned, then you must use RhpCopyMultibyteNoGCRefs. +COOP_PINVOKE_CDECL_HELPER(void *, memcpyGCRefs, (void * dest, const void *src, size_t len)) +{ + // null pointers are not allowed (they are checked by RhpCopyMultibyte) + ASSERT(dest != nullptr); + ASSERT(src != nullptr); + + ForwardGCSafeCopy(dest, src, len); + + // memcpy returns the destination buffer + return dest; +} + +// This function clears a piece of memory in a GC safe way. It makes the guarantee that it will clear memory in at +// least pointer sized chunks whenever possible. Unaligned memory at the beginning and remaining bytes at the end are +// written bytewise. We must make this guarantee whenever we clear memory in the GC heap that could contain object +// references. The GC or other user threads can read object references at any time, clearing them bytewise can result +// in a read on another thread getting incorrect data. +// +// USAGE: The caller is responsible for hoisting any null reference exceptions to a place where the hardware exception +// can be properly translated to a managed exception. +COOP_PINVOKE_CDECL_HELPER(void *, RhpInitMultibyte, (void * mem, int c, size_t size)) +{ + // The caller must do the null-check because we cannot take an AV in the runtime and translate it to managed. + ASSERT(mem != nullptr); + + UIntNative bv = (UInt8)c; + UIntNative pv = 0; + + if (bv != 0) + { + pv = +#if (POINTER_SIZE == 8) + bv << 7*8 | bv << 6*8 | bv << 5*8 | bv << 4*8 | +#endif + bv << 3*8 | bv << 2*8 | bv << 1*8 | bv; + } + + GCSafeFillMemory(mem, size, pv); + + // memset returns the destination buffer + return mem; +} + +EXTERN_C void * __cdecl memmove(void *, const void *, size_t); +EXTERN_C void REDHAWK_CALLCONV RhpBulkWriteBarrier(void* pMemStart, UInt32 cbMemSize); + +// +// Return true if the array slice is valid +// +FORCEINLINE bool CheckArraySlice(Array * pArray, Int32 index, Int32 length) +{ + Int32 arrayLength = pArray->GetArrayLength(); + + return (0 <= index) && (index <= arrayLength) && + (0 <= length) && (length <= arrayLength) && + (length <= arrayLength - index); +} + +// +// This function handles all cases of Array.Copy that do not require conversions or casting. It returns false if the copy cannot be performed, leaving +// the handling of the complex cases or throwing appropriate exception to the higher level framework. +// +COOP_PINVOKE_HELPER(Boolean, RhpArrayCopy, (Array * pSourceArray, Int32 sourceIndex, Array * pDestinationArray, Int32 destinationIndex, Int32 length)) +{ + if (pSourceArray == NULL || pDestinationArray == NULL) + return false; + + EEType* pArrayType = pSourceArray->get_EEType(); + EEType* pDestinationArrayType = pDestinationArray->get_EEType(); + if (pArrayType != pDestinationArrayType) + { + if (!pArrayType->IsEquivalentTo(pDestinationArrayType)) + return false; + } + + size_t componentSize = pArrayType->get_ComponentSize(); + if (componentSize == 0) // Not an array + return false; + + if (!CheckArraySlice(pSourceArray, sourceIndex, length)) + return false; + + if (!CheckArraySlice(pDestinationArray, destinationIndex, length)) + return false; + + if (length == 0) + return true; + + UInt8 * pSourceData = (UInt8 *)pSourceArray->GetArrayData() + sourceIndex * componentSize; + UInt8 * pDestinationData = (UInt8 *)pDestinationArray->GetArrayData() + destinationIndex * componentSize; + size_t size = length * componentSize; + + if (pArrayType->HasReferenceFields()) + { + if (pDestinationData <= pSourceData || pSourceData + size <= pDestinationData) + ForwardGCSafeCopy(pDestinationData, pSourceData, size); + else + BackwardGCSafeCopy(pDestinationData, pSourceData, size); + + RhpBulkWriteBarrier(pDestinationData, (UInt32)size); + } + else + { + memmove(pDestinationData, pSourceData, size); + } + + return true; +} + +// +// This function handles all cases of Array.Clear that do not require conversions. It returns false if the operation cannot be performed, leaving +// the handling of the complex cases or throwing apppropriate exception to the higher level framework. It is only allowed to return false for illegal +// calls as the BCL side has fallback for "complex cases" only. +// +COOP_PINVOKE_HELPER(Boolean, RhpArrayClear, (Array * pArray, Int32 index, Int32 length)) +{ + if (pArray == NULL) + return false; + + EEType* pArrayType = pArray->get_EEType(); + + size_t componentSize = pArrayType->get_ComponentSize(); + if (componentSize == 0) // Not an array + return false; + + if (!CheckArraySlice(pArray, index, length)) + return false; + + if (length == 0) + return true; + + GCSafeFillMemory((UInt8 *)pArray->GetArrayData() + index * componentSize, length * componentSize, 0); + + return true; +} + +// Get the universal transition thunk. If the universal transition stub is called through +// the normal PE static linkage model, a jump stub would be used which may interfere with +// the custom calling convention of the universal transition thunk. So instead, a special +// api just for getting the thunk address is needed. +// TODO: On ARM this may still result in a jump stub that trashes R12. Determine if anything +// needs to be done about that when we implement the stub for ARM. +extern "C" void RhpUniversalTransition(); +COOP_PINVOKE_HELPER(void*, RhGetUniversalTransitionThunk, ()) +{ + return (void*)RhpUniversalTransition; +} \ No newline at end of file diff --git a/src/Native/Runtime/ObjectLayout.cpp b/src/Native/Runtime/ObjectLayout.cpp new file mode 100644 index 00000000000..d2e988ed2f2 --- /dev/null +++ b/src/Native/Runtime/ObjectLayout.cpp @@ -0,0 +1,78 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Implementations of functions dealing with object layout related types. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "assert.h" +#include "redhawkwarnings.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "targetptrs.h" +#include "eetype.h" +#include "objectlayout.h" +#endif // !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +void Object::InitEEType(EEType * pEEType) +{ + ASSERT(NULL == m_pEEType); + m_pEEType = pEEType; +} +#endif + +UInt32 Array::GetArrayLength() +{ + return m_Length; +} + +void* Array::GetArrayData() +{ + UInt8* pData = (UInt8*)this; + pData += (get_EEType()->get_BaseSize() - sizeof(ObjHeader)); + return pData; +} + +#ifndef DACCESS_COMPILE +void Array::InitArrayLength(UInt32 length) +{ + ASSERT(NULL == m_Length); + m_Length = length; +} + +void ObjHeader::SetBit(UInt32 uBit) +{ + PalInterlockedOr(&m_uSyncBlockValue, uBit); +} + +void ObjHeader::ClrBit(UInt32 uBit) +{ + PalInterlockedAnd(&m_uSyncBlockValue, ~uBit); +} + +size_t Object::GetSize() +{ + EEType * pEEType = get_EEType(); + + // strings have component size2, all other non-arrays should have 0 + ASSERT(( pEEType->get_ComponentSize() <= 2) || pEEType->IsArray()); + + size_t s = pEEType->get_BaseSize(); + UInt16 componentSize = pEEType->get_ComponentSize(); + if (componentSize > 0) + s += ((Array*)this)->GetArrayLength() * componentSize; + return s; +} + +#endif diff --git a/src/Native/Runtime/ObjectLayout.h b/src/Native/Runtime/ObjectLayout.h new file mode 100644 index 00000000000..fd53e6ae275 --- /dev/null +++ b/src/Native/Runtime/ObjectLayout.h @@ -0,0 +1,81 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Low-level types describing GC object layouts. +// + +// Bits stolen from the sync block index that the GC/HandleTable knows about (currently these are at the same +// positions as the mainline runtime but we can change this below when it becomes apparent how Redhawk will +// handle sync blocks). +#define BIT_SBLK_GC_RESERVE 0x20000000 +#define BIT_SBLK_FINALIZER_RUN 0x40000000 + +// The sync block index header (small structure that immediately precedes every object in the GC heap). Only +// the GC uses this so far, and only to store a couple of bits of information. +class ObjHeader +{ +private: +#if defined(_WIN64) + UInt32 m_uAlignpad; +#endif // _WIN64 + UInt32 m_uSyncBlockValue; + +public: + UInt32 GetBits() { return m_uSyncBlockValue; } + void SetBit(UInt32 uBit); + void ClrBit(UInt32 uBit); + void SetGCBit() { m_uSyncBlockValue |= BIT_SBLK_GC_RESERVE; } + void ClrGCBit() { m_uSyncBlockValue &= ~BIT_SBLK_GC_RESERVE; } +}; + +//------------------------------------------------------------------------------------------------- +static UIntNative const SYNC_BLOCK_SKEW = sizeof(void *); + +//------------------------------------------------------------------------------------------------- +class Object +{ + friend class AsmOffsets; + + PTR_EEType m_pEEType; +public: + EEType * get_EEType() const + { return m_pEEType; } + EEType * get_SafeEEType() const + { return dac_cast((dac_cast(m_pEEType)) & ~((UIntNative)3)); } + ObjHeader * GetHeader() { return dac_cast(dac_cast(this) - SYNC_BLOCK_SKEW); } +#ifndef DACCESS_COMPILE + void set_EEType(EEType * pEEType) + { m_pEEType = pEEType; } + void InitEEType(EEType * pEEType); + + size_t GetSize(); +#endif +}; +typedef DPTR(Object) PTR_Object; +typedef DPTR(PTR_Object) PTR_PTR_Object; + +//------------------------------------------------------------------------------------------------- +static UIntNative const MIN_OBJECT_SIZE = (2 * sizeof(void*)) + sizeof(ObjHeader); + +//------------------------------------------------------------------------------------------------- +static UIntNative const REFERENCE_SIZE = sizeof(Object *); + +//------------------------------------------------------------------------------------------------- +class Array : public Object +{ + friend class AsmOffsets; + + UInt32 m_Length; +#if defined(_WIN64) + UInt32 m_uAlignpad; +#endif // _WIN64 +public: + UInt32 GetArrayLength(); + void InitArrayLength(UInt32 length); + void* GetArrayData(); +}; +typedef DPTR(Array) PTR_Array; + diff --git a/src/Native/Runtime/OptionalFieldDefinitions.h b/src/Native/Runtime/OptionalFieldDefinitions.h new file mode 100644 index 00000000000..9a4033fccf4 --- /dev/null +++ b/src/Native/Runtime/OptionalFieldDefinitions.h @@ -0,0 +1,33 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// This file is designed to be included multiple times with different definitions of the +// DEFINE_INLINE_OPTIONAL_FIELD and DEFINE_OUTLINE_OPTIONAL_FIELD macros in order to build data structures +// related to each type of EEType optional field we support (see OptionalFields.h for details). +// + +// The order of definition of the fields is somewhat important: for types that require multiple optional +// fields the fields are laid out in the order of definition. Thus access to the fields defined first will be +// slightly faster than the later fields. + +#ifndef DEFINE_INLINE_OPTIONAL_FIELD +#error Must define DEFINE_INLINE_OPTIONAL_FIELD before including this file +#endif + +#ifndef DEFINE_OUTLINE_OPTIONAL_FIELD +#error Must define DEFINE_OUTLINE_OPTIONAL_FIELD before including this file +#endif + +// Field name Field type +DEFINE_INLINE_OPTIONAL_FIELD (RareFlags, UInt32) +DEFINE_INLINE_OPTIONAL_FIELD (ICastableIsInstSlot, UInt16) +DEFINE_INLINE_OPTIONAL_FIELD (DispatchMap, UInt32) +DEFINE_INLINE_OPTIONAL_FIELD (ValueTypeFieldPadding, UInt32) +DEFINE_INLINE_OPTIONAL_FIELD (ICastableGetImplTypeSlot, UInt16) +DEFINE_INLINE_OPTIONAL_FIELD (NullableValueOffset, UInt8) + +#undef DEFINE_INLINE_OPTIONAL_FIELD +#undef DEFINE_OUTLINE_OPTIONAL_FIELD diff --git a/src/Native/Runtime/OptionalFields.h b/src/Native/Runtime/OptionalFields.h new file mode 100644 index 00000000000..b8f31b6763e --- /dev/null +++ b/src/Native/Runtime/OptionalFields.h @@ -0,0 +1,404 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Support for optional fields attached out-of-line to EETypes (or any other data structure for that matter). +// These should be used for attributes that exist for only a small subset of EETypes or are accessed only +// rarely. The idea is to avoid bloating the size of the most common EETypes and to move some of the colder +// data out-of-line to improve the density of the hot data. The basic idea is that the EEType contains a +// pointer to an OptionalFields structure (which may be NULL) and that structure contains a somewhat +// compressed version of the optional fields. +// +// For each OptionalFields instance we encode only the fields that are present so that the structure is as +// small as possible while retaining reasonable access costs. +// +// This implies some tricky tradeoffs: +// * The more we compress the data the greater the access costs in terms of CPU. +// * More effective compression schemes tend to lead to the payload data being unaligned. This itself can +// result in overhead but on some architectures it's worse than that and the unaligned nature of the data +// requires special handling in client code. Generally it would be more robust and clean not to leak out +// such requirements to our callers. For small fields we can imagine copying the data into aligned storage +// (and indeed that might be a natural part of the decompression process). It might be more problematic for +// larger data items. +// +// In order to get the best of both worlds we employ a hybrid approach. Small values (typically single small +// integers) get encoded inline in a compressed format. Decoding them will automatically copy them into +// aligned storage. Larger values (such as complex data structures) will be stored out-of-line, naturally +// aligned and uncompressed (at least by this layer of the software). The entry in the optional field record +// will instead contain a reference to this out-of-line structure. +// +// Pointers are large (especially on 64-bit) and incur overhead in terms of base relocs and complexity (since +// the locations requiring relocs may not be aligned). To mitigate this we can encode references to these +// out-of-line records as deltas from a base address and by carefully ordering the layout of the out-of-line +// records we can share the same base address amongst multiple OptionalFields structures. +// +// Taking this to one end of the logical extreme we could store a single base address such as the module base +// address and encode all OptionalFields references as offsets from this; basically RVAs. This is cheap in the +// respect that we only need one base address (and associated reloc) but the majority of OptionalFields +// references will encode as fairly large deltas. As we'll touch on later our mechanism for compressing inline +// values in OptionalRecords is based on discarding insignificant leading zero bits; i.e. we encode small +// integers more effectively. So ideally we want to store multiple base addresses so we can lower the average +// encoding cost of the deltas. +// +// An additional concern is how these base addresses are located. Take the module base address example: we +// have no direct means of locating this based on an OptionalFields (or even the EEType that owns it). To +// obtain this value we're likely to have to perform some operation akin to a range lookup and there are +// interesting edge cases such as EETypes for generic types, which don't reside in modules. +// +// The approach taken here addresses several of the concerns above. The algorithm stores base addresses +// interleaved with the OptionalFields. They are located at well-known locations by aligning their addresses +// to a specific value (we can tune this but assume for the purposes of this explanation that the value is 64 +// bytes). This implies that the address requiring a base reloc is always aligned plus it can be located +// cheaply from an OptionalFields address by masking off the low-order bits of that address. +// +// As OptionalFields are added any out-of-line data they reference is stored linearly in the same order (this +// does imply that all out-of-line records must live in the same section and thus must have the same access +// attributes). This provides locality: adjacent OptionalFields may encode deltas to different out-of-line +// records but since the out-of-line records are adjacent (or nearly so) as well, both deltas will be about +// the same size. Once we've filled in the space between stored base addresses (some padding might be needed +// near the end where a full OptionalField won't fit, but this should be small given good compression of +// OptionalFields) then we write out a new base address. This is chosen based on the first out-of-line record +// referenced by the next OptionalField (i.e. it will make the first delta zero and keep the subsequent ones +// small). +// +// Consider the following example where for the sake of simplicity we assume each OptionalFields structure has +// precisely one out-of-line reference: +// +// +-----------------+ Out-of-line Records +// | Base Address |----------------------> +--------------------+ +// +-----------------+ | #1 | +// | OptionalFields | +--------------------+ +// | Record #1 | | #2 | +// | | | | +// +-----------------+ +--------------------+ +// | OptionalFields | | #3 | +// | Record #2 | /------------> +--------------------+ +// | | / | #4 | +// +-----------------+ / | | +// | OptionalFields | / | | +// | Record #3 | / +--------------------+ +// | | / | #5 | +// +-----------------+ / | | +// | Padding | / +--------------------+ +// +-----------------+ / : : +// | Base Address |- +// +-----------------+ +// | OptionalFields | +// | Record #4 | +// | | +// +-----------------+ +// | OptionalFields | +// | Record #5 | +// : : +// +// Each optional field uses the base address defined above it (at the lower memory address determined by +// masking off the alignment bits). No matter which out-of-line records they reference the deltas will be as +// small as we can make them. +// +// Lowering the alignment requirement introduces more base addresses and as a result also lowers the number of +// OptionalFields that share the same base address, leading to smaller encodings for out-of-line deltas. But +// at the same time it increases the number of pointers (and associated base relocs) that we must store. +// Additionally the compression of the deltas is not completely linear: certain ranges of delta magnitude will +// result in exactly the same storage being used when compressed. See the details of the delta encoding below +// to see how we can use this to our advantage when tuning the alignment of base addresses. +// +// We optimize the case where OptionalFields structs don't contain any out-of-line references. We collect +// those together and emit them in a single run with no interleaved base addresses. +// +// The OptionalFields record encoding itself is a byte stream representing one or more fields. The first byte +// is a field header: it contains a field type tag in the low-order 7 bits (giving us 128 possible field +// types) and the most significant bit indicates whether this is the last field of the structure. The field +// value (a 32-bit unsigned number) is encoded using the existing VarInt support which encodes the value in +// byte chunks taking between 1 and 5 bytes to do so. +// +// If the field value is out-of-line we decode the delta from the base address in much the same way as for +// inline field values. Before adding the delta to the base address, however, we scale it based on the natural +// alignment of the out-of-line data record it references. Since the out-of-line data is aligned on the same +// basis this scaling avoids encoding bits that will always be zero and thus allows us to reference a greater +// range of memory with a delta that encodes using less bytes. +// +// The value compression algorithm above gives us the non-linearity of compression referenced earlier. 32-bit +// values will encode in a given number of bytes based on the having a given number of significant +// (non-leading zero) bits: +// 5 bytes : 25 - 32 significant bits +// 4 bytes : 18 - 24 significant bits +// 3 bytes : 11 - 17 significant bits +// 2 bytes : 4 - 10 significant bits +// 1 byte : 0 - 3 significant bits +// +// We can use this to our advantage when choosing an alignment at which to store base addresses. Assuming that +// most out-of-line data will have an alignment requirement of at least 4 bytes we note that the 2 byte +// encoding already gives us an addressable range of 2^10 * 4 == 4KB which is likely to be enough for the vast +// majority of cases. That is we can raise the granularity of base addresses until the average amount of +// out-of-line data addressed begins to approach 4KB which lowers the cost of storing the base addresses while +// not impacting the encoding size of deltas at all (there's no point in storing base addresses more +// frequently because it won't make the encodings of deltas any smaller). +// +// Trying to tune for one byte deltas all the time is probably not worth it. The addressability range (again +// assuming 4 byte alignment) is only 32 bytes and unless we start storing a lot of small data structures +// out-of-line tuning for this will involve placing the base addresses very frequently and our costs will be +// dominated by the size of the base address pointers and their relocs. +// + +// Define enumeration of optional field tags. +enum OptionalFieldTag +{ +#define DEFINE_INLINE_OPTIONAL_FIELD(_name, _type) OFT_##_name, +#define DEFINE_OUTLINE_OPTIONAL_FIELD(_name, _type) OFT_##_name, +#include "OptionalFieldDefinitions.h" + OFT_Count // Number of field types we support +}; + +// Array that indicates whether a given field type is inline (true) or out-of-line (false). +static bool g_rgOptionalFieldTypeIsInline[OFT_Count] = { +#define DEFINE_INLINE_OPTIONAL_FIELD(_name, _type) true, +#define DEFINE_OUTLINE_OPTIONAL_FIELD(_name, _type) false, +#include "OptionalFieldDefinitions.h" +}; + +// Various random global constants we can tweak for performance tuning. +enum OptionalFieldConstants +{ + // Constants determining how often we interleave a "header" containing a base address for out-of-line + // records into the stream of OptionalFields structures. These will occur at some power of 2 alignment of + // memory address. The alignment must at least exceed that of a pointer (since we'll store a pointer in + // the header and we need room for at least one OptionalFields record between each header). As the + // alignment goes up we store less headers but may impose a larger one-time padding cost at the start of + // the optional fields memory block as well as increasing the average encoding size for out-of-line record + // deltas in each optional field record. + // + // Note that if you change these constants you must be sure to modify the alignment of the optional field + // virtual section in ZapImage.cpp as well as ensuring the alignment of the containing physical section is + // at least as high (this latter cases matters for the COFF output case only, when we're generating PE + // images directly the physical section will get page alignment). + OFC_HeaderAlignmentShift = 7, + OFC_HeaderAlignmentBytes = 1 << OFC_HeaderAlignmentShift, + OFC_HeaderAlignmentMask = OFC_HeaderAlignmentBytes - 1, +}; + +// Support for some simple statistics gathering. Only used for performance tweaking and debugging of the +// algorithm so left disabled most of the time. +#ifdef BINDER +//#define FEATURE_OPTIONAL_FIELD_STATS 1 +#endif +#ifdef FEATURE_OPTIONAL_FIELD_STATS +struct OptionalFieldStats +{ + UInt32 m_cOptionalFieldsStructs; + UInt32 m_rgFieldCounts[OFT_Count]; + UInt32 m_rgSizeDist[8]; + UInt32 m_cHeaders; + UInt32 m_cbPadding; +}; +extern "C" OptionalFieldStats g_sOFStats; +#define OFS_COUNTER_INC(_name) g_sOFStats.m_c##_name++; +#else +#define OFS_COUNTER_INC(_name) +#endif + +#ifndef RHDUMP +typedef DPTR(class OptionalFields) PTR_OptionalFields; +typedef DPTR(PTR_OptionalFields) PTR_PTR_OptionalFields; +#endif // !RHDUMP + + +class OptionalFields +{ +public: + + // Return the number of bytes necessary to encode the given integer. + static UInt32 EncodingSize(UInt32 uiValue); + + // Encode the given field type and integer into the buffer provided (which is guaranteed to have enough + // space). Update the pointer into the buffer to point just past the newly encoded bytes. Note that any + // processing of the value for use with out-of-line records has already been performed; we're given the + // raw value to encode. + static void EncodeField(UInt8 ** ppFields, OptionalFieldTag eTag, bool fLastField, UInt32 uiValue); + +#ifndef BINDER + + // Define accessors for each field type. +#define DEFINE_INLINE_OPTIONAL_FIELD(_name, _type) \ + _type Get##_name(_type defaultValue) \ + { \ + return (_type)GetInlineField(OFT_##_name, (UInt32)defaultValue); \ + } + +#define DEFINE_OUTLINE_OPTIONAL_FIELD(_name, _type) \ + _type * Get##_name() \ + { \ + return (_type*)GetOutlineField(OFT_##_name, _alignof(_type)); \ + } + +#include "OptionalFieldDefinitions.h" + +private: + friend struct OptionalFieldsRuntimeBuilder; + + // Reads a field value (or the basis for an out-of-line record delta) starting from the first byte after + // the field header. Advances the field location to the start of the next field. + static OptionalFieldTag DecodeFieldTag(PTR_UInt8 * ppFields, bool *pfLastField); + + // Reads a field value (or the basis for an out-of-line record delta) starting from the first byte of a + // field description. Advances the field location to the start of the next field. + static UInt32 DecodeFieldValue(PTR_UInt8 * ppFields); + + UInt32 GetInlineField(OptionalFieldTag eTag, UInt32 uiDefaultValue); +#if 0 + // Currently not used for ProjectN, implementation needs to be "DAC'ified" if needed again + void * GetOutlineField(OptionalFieldTag eTag, size_t cbValueAlignment); +#endif + +#endif // !BINDER +}; + +#ifdef BINDER + +class OptionalFieldsManager; + +// +// Binder support for laying out OptionalFields structures. Note that this is relatively simple currently since +// none of the optional fields contain pointers. This will have to be revisited to support fixups and +// understand ZapNodes if pointers are introduced. +// +// Mostly this class just caches the fields declared for a particular OptionalFields and the resulting ZapNode +// once those fields have been encoded. The layout of OptionalFields in memory is handled by +// OptionalFieldsManager while the details of the encoding of each individual field are handled by the +// OptionalFields class itself. +// +class OptionalFieldsBuilder +{ +public: + // At construction time associate this build with a manager. + OptionalFieldsBuilder(OptionalFieldsManager *pManager); + + // Once all fields have been added the ZapNode representing the OptionalFields structure can be retrieved + // (and not before; this will be asserted in debug builds). + ZapNode *GetNode(); + + // Define setters for each field type. +#define DEFINE_INLINE_OPTIONAL_FIELD(_name, _type) \ + void Add##_name(_type value) \ + { \ + AddInlineField(OFT_##_name, (UInt32)value); \ + } + + // Note that the ZapBlob provided here must not have been placed since the OptionalFieldsManager will + // place it later in a specific order within a special section. +#define DEFINE_OUTLINE_OPTIONAL_FIELD(_name, _type) \ + void Add##_name(ZapBlob * pValueBlob) \ + { \ + assert(!pValueBlob->IsPlaced()); \ + AddOutlineField(OFT_##_name, pValueBlob); \ + } + +#include "OptionalFieldDefinitions.h" + +private: + friend class OptionalFieldsManager; + + void AddInlineField(OptionalFieldTag eTag, UInt32 uiValue); + void AddOutlineField(OptionalFieldTag eTag, ZapBlob * pValueBlob); + + // Cached field values specficied by one of the Add* methods above. + struct OptionalField + { + bool m_fPresent; // This field was added (we keep an array of all possible + // field types). + UInt32 m_uiOffset; // Offset of image copy of the value from the base of the + // out-of-line record section. Set by manager during field + // encoding and used only for out-of-line fields. + union + { + UInt32 m_uiValue; // Inline value. + ZapBlob * m_pValueBlob; // Out-of-line value. + }; + }; + + OptionalFieldsManager * m_pManager; + ZapNode * m_pNode; + UInt32 m_cFields; + bool m_fContainsOutOfLineFields; + OptionalField m_rgFields[OFT_Count]; +}; + +// The OptionalFieldsManager takes care of the layout of the two virtual sections used by OptionalFields: the +// OptionalFields themselves (possibly with base addresses interleaved) and the out-of-line data records. +class OptionalFieldsManager +{ +public: + OptionalFieldsManager(ZapImage * pZapImage); + + // Encode all the fields for one OptionalFields description cached in the given OptionalFieldsBuilder. + ZapNode * EncodeFields(OptionalFieldsBuilder * pBuilder); + + // Place all the nodes created by this manager once all OptionalFields creation is complete. + void Place(); + +private: + // When we collect all the out-of-line records for later placement we also wish to cache the offset into + // the virtual section at which each node lies. We could work this out from the nodes themselves but that + // would be very expensive. This information is used to calculate the deltas between OptionalFields + // out-of-line data references and the last out-of-line data record that was recorded as a base address. + struct OutOfLineRecord + { + ZapNode * m_pNode; + UInt32 m_uiOffset; + }; + + // Given a builder calculate the size of the encoded version of the OptionalFields structure given the + // current state of the OptionalFieldsManager (i.e. which base address is current etc.). + UInt32 PlanEncoding(OptionalFieldsBuilder * pBuilder); + + // Actually encode the builder into an OptionalFields structure. The size of the encoding must have been + // calculated by a previous call to PlanEncoding with no intervening manager state updates. + ZapNode * PerformEncoding(OptionalFieldsBuilder * pBuilder, UInt32 cbEncoding); + + // Emit a new base address record using the given out-of-line data record as the new base. It's assumed + // that sufficient padding has already been emitted such that the optional fields section is properly + // aligned for this header record. + void AddNewBaseAddressHeader(UInt32 idxBaseOutOfLineRecord); + + // Go through the builder looking for out-of-line records (it's assumed there is at least one if this is + // called) adding copies of the data to the out-of-line records section. Returns the index of the first + // record referenced by the builder which is the record that should be used as the base address if this is + // the first OptionalFields emitted after a base address header. + UInt32 AddOutOfLineRecords(OptionalFieldsBuilder * pBuilder); + + ZapImage * m_pZapImage; + vector m_vecSimpleFields; // OptionalFields without out-of-line records + vector m_vecComplexFields; // OptionalFields with at least one out-of-line record + vector m_vecOutOfLineRecords; // Out-of-line records referenced by above + UInt32 m_cbNextOutOfLineRecordOffset; // Offset into section the next OOL record will occupy + UInt32 m_idxCurrentBaseOutOfLineRecord;// Index of OOL record currently being used as base + UInt32 m_cbFreeSpaceInCurrentGroup; // Count of bytes left before next header is emitted + bool m_fHeaderEmitted; // Has at least one base address header been emitted? +}; + +#endif // BINDER + +// +// Optional field encoder/decoder for dynamic types built at runtime +// +struct OptionalFieldsRuntimeBuilder +{ + void Decode(OptionalFields * pOptionalFields); + + UInt32 EncodingSize(); + + UInt32 Encode(OptionalFields * pOptionalFields); + + struct OptionalField + { + bool m_fPresent; + union + { + UInt32 m_uiValue; + void * m_pValueBlob; + }; + }; + + OptionalField m_rgFields[OFT_Count]; +}; diff --git a/src/Native/Runtime/OptionalFields.inl b/src/Native/Runtime/OptionalFields.inl new file mode 100644 index 00000000000..db56fb7a125 --- /dev/null +++ b/src/Native/Runtime/OptionalFields.inl @@ -0,0 +1,96 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// Return the number of bytes necessary to encode the given integer. +/*static*/ UInt32 OptionalFields::EncodingSize(UInt32 uiValue) +{ + // One byte for the field header plus whatever VarInt takes to encode the value. + return (UInt32)(1 + VarInt::WriteUnsigned(NULL, uiValue)); +} + +// Encode the given field type and integer into the buffer provided (which is guaranteed to have enough +// space). Update the pointer into the buffer to point just past the newly encoded bytes. Note that any +// processing of the value for use with out-of-line records has already been performed; we're given the raw +// value to encode. +/*static*/ void OptionalFields::EncodeField(UInt8 ** ppFields, OptionalFieldTag eTag, bool fLastField, UInt32 uiValue) +{ + // Encode the header byte: most significant bit indicates whether this is the last field, remaining bits + // the field type. + **ppFields = (UInt8)((fLastField ? 0x80 : 0x00) | eTag); + (*ppFields)++; + + // Have VarInt encode the value. + *ppFields += VarInt::WriteUnsigned(*ppFields, uiValue); +} + +#ifndef BINDER +void OptionalFieldsRuntimeBuilder::Decode(OptionalFields * pOptionalFields) +{ + ZeroMemory(m_rgFields, sizeof(m_rgFields)); + + if (pOptionalFields == NULL) + return; + + // Point at start of encoding stream. + UInt8 * pFields = (UInt8*)pOptionalFields; + + for (;;) + { + // Read field tag, an indication of whether this is the last field and the field value (we always read + // the value, even if the tag is not a match because decoding the value advances the field pointer to + // the next field). + bool fLastField; + OptionalFieldTag eCurrentTag = OptionalFields::DecodeFieldTag(&pFields, &fLastField); + UInt32 uiCurrentValue = OptionalFields::DecodeFieldValue(&pFields); + + // If we found a tag match return the current value. + m_rgFields[eCurrentTag].m_fPresent = true; + m_rgFields[eCurrentTag].m_uiValue = uiCurrentValue; + + // If this was the last field we're done as well. + if (fLastField) + break; + } +} + +UInt32 OptionalFieldsRuntimeBuilder::EncodingSize() +{ + UInt32 size = 0; + for (int eTag = 0; eTag < OFT_Count; eTag++) + { + if (!m_rgFields[eTag].m_fPresent) + continue; + + size += OptionalFields::EncodingSize(m_rgFields[eTag].m_uiValue); + } + return size; +} + + +UInt32 OptionalFieldsRuntimeBuilder::Encode(OptionalFields * pOptionalFields) +{ + int eLastTag; + for (eLastTag = OFT_Count - 1; eLastTag >= 0; eLastTag--) + { + if (m_rgFields[eLastTag].m_fPresent) + break; + } + + if (eLastTag < 0) + return 0; + + UInt8 * pFields = (UInt8*)pOptionalFields; + + for (int eTag = 0; eTag <= eLastTag; eTag++) + { + if (!m_rgFields[eTag].m_fPresent) + continue; + + OptionalFields::EncodeField(&pFields, (OptionalFieldTag)eTag, eTag == eLastTag, m_rgFields[eTag].m_uiValue); + } + + return (UInt32)(pFields - (UInt8*)pOptionalFields); +} +#endif diff --git a/src/Native/Runtime/OptionalFieldsRuntime.cpp b/src/Native/Runtime/OptionalFieldsRuntime.cpp new file mode 100644 index 00000000000..f1d2af990fb --- /dev/null +++ b/src/Native/Runtime/OptionalFieldsRuntime.cpp @@ -0,0 +1,124 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Implementations of methods of OptionalFields which are used only at runtime (i.e. reading field values). +// + +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#else // DACCESS_COMPILE + +#ifndef RHDUMP +#include "commontypes.h" +#include "commonmacros.h" +#include "daccess.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "rhbinder.h" +#include "eetype.h" +#include "objectlayout.h" +#include "varint.h" +#endif + +#endif + +// Reads the field type from the current byte of the stream and indicates whether this represents the last +// field. +/*static*/ OptionalFieldTag OptionalFields::DecodeFieldTag(PTR_UInt8 * ppFields, bool *pfLastField) +{ + UInt8 tagByte; + tagByte = **ppFields; + + // The last field has the most significant bit of the byte set. + *pfLastField = (tagByte & 0x80) != 0; + + // The remaining 7 bits encode the field type. + OptionalFieldTag eTag = (OptionalFieldTag)(tagByte & 0x7f); + + // Advance the pointer past the header. + (*ppFields)++; + + return eTag; +} + +// Reads a field value (or the basis for an out-of-line record delta) starting from the first byte after the +// field header. Advances the field location to the start of the next field. +UInt32 OptionalFields::DecodeFieldValue(PTR_UInt8 * ppFields) +{ + // VarInt is used to encode the field value (and updates the field pointer in doing so). + return VarInt::ReadUnsigned(*ppFields); +} + +/*static*/ UInt32 OptionalFields::GetInlineField(OptionalFieldTag eTag, UInt32 uiDefaultValue) +{ + // Point at start of encoding stream. + PTR_UInt8 pFields = dac_cast(this); + + for (;;) + { + // Read field tag, an indication of whether this is the last field and the field value (we always read + // the value, even if the tag is not a match because decoding the value advances the field pointer to + // the next field). + bool fLastField; + OptionalFieldTag eCurrentTag = DecodeFieldTag(&pFields, &fLastField); + UInt32 uiCurrentValue = DecodeFieldValue(&pFields); + + // If we found a tag match return the current value. + if (eCurrentTag == eTag) + return uiCurrentValue; + + // If this was the last field we're done as well. + if (fLastField) + break; + } + + // Reached end of stream without getting a match. Field is not present so return default value. + return uiDefaultValue; +} + +#if 0 +// Currently not used for ProjectN, implementation needs to be "DAC'ified" if needed again +void * OptionalFields::GetOutlineField(OptionalFieldTag eTag, size_t cbValueAlignment) +{ + // Point at start of encoding stream. + UInt8 * pFields = (UInt8*)this; + + for (;;) + { + // Read field tag, an indication of whether this is the last field and the field value (we always read + // the value, even if the tag is not a match because decoding the value advances the field pointer to + // the next field). + bool fLastField; + OptionalFieldTag eCurrentTag = DecodeFieldTag(&pFields, &fLastField); + UInt32 uiCurrentValue = DecodeFieldValue(&pFields); + + // If we found a tag match then we need to compute the out-of-line record address to return. + if (eCurrentTag == eTag) + { + // Scale the value (actually a delta from the base address) based on the alignment of the target + // record. + uiCurrentValue *= (UInt32)cbValueAlignment; + + // Locate the address where the base address is stored. They are stored periodically at aligned + // addresses relative to OptionalFields records. We can locate them by masking off bits from the + // least significant portion of this records address. + UInt8 ** pBaseAddress = (UInt8**)((size_t)this & ~(size_t)OFC_HeaderAlignmentMask); + + return *pBaseAddress + uiCurrentValue; + } + + // If this was the last field we're done as well. + if (fLastField) + break; + } + + // Reached end of stream without getting a match. Field is not present so return NULL. + return NULL; +} + +#endif \ No newline at end of file diff --git a/src/Native/Runtime/PalRedhawk.h b/src/Native/Runtime/PalRedhawk.h new file mode 100644 index 00000000000..d58c99b2ea4 --- /dev/null +++ b/src/Native/Runtime/PalRedhawk.h @@ -0,0 +1,922 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Provides declarations for external resources consumed by Redhawk. This comprises functionality +// normally exported from Win32 libraries such as KERNEL32 and MSVCRT. When hosted on Win32 calls to these +// functions become simple pass throughs to the native implementation via export forwarding entries in a PAL +// (Platform Abstraction Layer) library. On other platforms the PAL library has actual code to emulate the +// functionality of these same APIs. +// +// In order to make it both obvious and intentional where Redhawk consumes an external API, such functions are +// decorated with an 'Pal' prefix. Ideally the associated supporting types, constants etc. would be +// similarly isolated from their concrete Win32 definitions, making the extent of platform dependence within +// the core explicit. For now that is too big a work item and we'll settle for manually restricting the use of +// external header files to within this header. +// + +#include + +#ifndef PAL_REDHAWK_INCLUDED +#define PAL_REDHAWK_INCLUDED + +/* Adapted from intrin.h - For compatibility with , some intrinsics are __cdecl except on x64 */ +#if defined (_M_X64) +#define __PN__MACHINECALL_CDECL_OR_DEFAULT +#else +#define __PN__MACHINECALL_CDECL_OR_DEFAULT __cdecl +#endif + +#ifndef DACCESS_COMPILE + +#define CALLBACK __stdcall +#define WINAPI __stdcall +#define WINBASEAPI __declspec(dllimport) + +// There are some fairly primitive type definitions below but don't pull them into the rest of Redhawk unless +// we have to (in which case these definitions will move to CommonTypes.h). +typedef WCHAR * LPWSTR; +typedef const WCHAR * LPCWSTR; +typedef void * HINSTANCE; + +typedef void * LPSECURITY_ATTRIBUTES; +typedef void * LPOVERLAPPED; + +typedef void(__stdcall *PFLS_CALLBACK_FUNCTION) (void* lpFlsData); +#define FLS_OUT_OF_INDEXES ((UInt32)0xFFFFFFFF) + +union LARGE_INTEGER +{ + struct + { + UInt32 LowPart; + Int32 HighPart; + } u; + Int64 QuadPart; +}; + +struct GUID +{ + UInt32 Data1; + UInt16 Data2; + UInt16 Data3; + UInt8 Data4[8]; +}; + +#define DECLARE_HANDLE(_name) typedef HANDLE _name + +#endif // !DACCESS_COMPILE + +#if !defined(GCRH_WINDOWS_INCLUDED) + +struct CRITICAL_SECTION +{ + void * DebugInfo; + Int32 LockCount; + Int32 RecursionCount; + HANDLE OwningThread; + HANDLE LockSemaphore; + UIntNative SpinCount; +}; + +#endif //!GCRH_WINDOWS_INCLUDED + +#ifndef DACCESS_COMPILE + +struct SYSTEM_INFO +{ + union + { + UInt32 dwOemId; + struct { + UInt16 wProcessorArchitecture; + UInt16 wReserved; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + UInt32 dwPageSize; + void * lpMinimumApplicationAddress; + void * lpMaximumApplicationAddress; + UIntNative dwActiveProcessorMask; + UInt32 dwNumberOfProcessors; + UInt32 dwProcessorType; + UInt32 dwAllocationGranularity; + UInt16 wProcessorLevel; + UInt16 wProcessorRevision; +}; + +struct OSVERSIONINFOEXW +{ + UInt32 dwOSVersionInfoSize; + UInt32 dwMajorVersion; + UInt32 dwMinorVersion; + UInt32 dwBuildNumber; + UInt32 dwPlatformId; + WCHAR szCSDVersion[128]; + UInt16 wServicePackMajor; + UInt16 wServicePackMinor; + UInt16 wSuiteMask; + UInt8 wProductType; + UInt8 wReserved; +}; + +#endif //!DACCESS_COMPILE + +#if !defined(GCRH_WINDOWS_INCLUDED) + +struct FILETIME +{ + UInt32 dwLowDateTime; + UInt32 dwHighDateTime; +}; + +#endif + +#ifndef DACCESS_COMPILE + +enum MEMORY_RESOURCE_NOTIFICATION_TYPE +{ + LowMemoryResourceNotification, + HighMemoryResourceNotification +}; + +enum LOGICAL_PROCESSOR_RELATIONSHIP +{ + RelationProcessorCore, + RelationNumaNode, + RelationCache, + RelationProcessorPackage +}; + +#define LTP_PC_SMT 0x1 + +enum PROCESSOR_CACHE_TYPE +{ + CacheUnified, + CacheInstruction, + CacheData, + CacheTrace +}; + +struct CACHE_DESCRIPTOR +{ + UInt8 Level; + UInt8 Associativity; + UInt16 LineSize; + UInt32 Size; + PROCESSOR_CACHE_TYPE Type; +}; + +struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION +{ + UIntNative ProcessorMask; + LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + union + { + struct + { + UInt8 Flags; + } ProcessorCore; + struct + { + UInt32 NodeNumber; + } NumaNode; + CACHE_DESCRIPTOR Cache; + UInt64 Reserved[2]; + }; +}; + + +#ifdef _AMD64_ + +typedef struct DECLSPEC_ALIGN(16) _XSAVE_FORMAT { + UInt16 ControlWord; + UInt16 StatusWord; + UInt8 TagWord; + UInt8 Reserved1; + UInt16 ErrorOpcode; + UInt32 ErrorOffset; + UInt16 ErrorSelector; + UInt16 Reserved2; + UInt32 DataOffset; + UInt16 DataSelector; + UInt16 Reserved3; + UInt32 MxCsr; + UInt32 MxCsr_Mask; + Fp128 FloatRegisters[8]; +#if defined(_WIN64) + Fp128 XmmRegisters[16]; + UInt8 Reserved4[96]; +#else + Fp128 XmmRegisters[8]; + UInt8 Reserved4[220]; + UInt32 Cr0NpxState; +#endif +} XSAVE_FORMAT, *PXSAVE_FORMAT; + + +typedef XSAVE_FORMAT XMM_SAVE_AREA32, *PXMM_SAVE_AREA32; + +typedef struct DECLSPEC_ALIGN(16) _CONTEXT { + UInt64 P1Home; + UInt64 P2Home; + UInt64 P3Home; + UInt64 P4Home; + UInt64 P5Home; + UInt64 P6Home; + UInt32 ContextFlags; + UInt32 MxCsr; + UInt16 SegCs; + UInt16 SegDs; + UInt16 SegEs; + UInt16 SegFs; + UInt16 SegGs; + UInt16 SegSs; + UInt32 EFlags; + UInt64 Dr0; + UInt64 Dr1; + UInt64 Dr2; + UInt64 Dr3; + UInt64 Dr6; + UInt64 Dr7; + UInt64 Rax; + UInt64 Rcx; + UInt64 Rdx; + UInt64 Rbx; + UInt64 Rsp; + UInt64 Rbp; + UInt64 Rsi; + UInt64 Rdi; + UInt64 R8; + UInt64 R9; + UInt64 R10; + UInt64 R11; + UInt64 R12; + UInt64 R13; + UInt64 R14; + UInt64 R15; + UInt64 Rip; + union { + XMM_SAVE_AREA32 FltSave; + struct { + Fp128 Header[2]; + Fp128 Legacy[8]; + Fp128 Xmm0; + Fp128 Xmm1; + Fp128 Xmm2; + Fp128 Xmm3; + Fp128 Xmm4; + Fp128 Xmm5; + Fp128 Xmm6; + Fp128 Xmm7; + Fp128 Xmm8; + Fp128 Xmm9; + Fp128 Xmm10; + Fp128 Xmm11; + Fp128 Xmm12; + Fp128 Xmm13; + Fp128 Xmm14; + Fp128 Xmm15; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + Fp128 VectorRegister[26]; + UInt64 VectorControl; + UInt64 DebugControl; + UInt64 LastBranchToRip; + UInt64 LastBranchFromRip; + UInt64 LastExceptionToRip; + UInt64 LastExceptionFromRip; + + void SetIP(UIntNative ip) { Rip = ip; } + void SetSP(UIntNative sp) { Rsp = sp; } + void SetArg0Reg(UIntNative val) { Rcx = val; } + void SetArg1Reg(UIntNative val) { Rdx = val; } + UIntNative GetIP() { return Rip; } + UIntNative GetSP() { return Rsp; } +} CONTEXT, *PCONTEXT; +#elif defined(_ARM_) + +#define ARM_MAX_BREAKPOINTS 8 +#define ARM_MAX_WATCHPOINTS 1 + +typedef struct DECLSPEC_ALIGN(8) _CONTEXT { + UInt32 ContextFlags; + UInt32 R0; + UInt32 R1; + UInt32 R2; + UInt32 R3; + UInt32 R4; + UInt32 R5; + UInt32 R6; + UInt32 R7; + UInt32 R8; + UInt32 R9; + UInt32 R10; + UInt32 R11; + UInt32 R12; + UInt32 Sp; + UInt32 Lr; + UInt32 Pc; + UInt32 Cpsr; + UInt32 Fpscr; + UInt32 Padding; + union { + Fp128 Q[16]; + UInt64 D[32]; + UInt32 S[32]; + } DUMMYUNIONNAME; + UInt32 Bvr[ARM_MAX_BREAKPOINTS]; + UInt32 Bcr[ARM_MAX_BREAKPOINTS]; + UInt32 Wvr[ARM_MAX_WATCHPOINTS]; + UInt32 Wcr[ARM_MAX_WATCHPOINTS]; + UInt32 Padding2[2]; + + void SetIP(UIntNative ip) { Pc = ip; } + void SetArg0Reg(UIntNative val) { R0 = val; } + void SetArg1Reg(UIntNative val) { R1 = val; } + UIntNative GetIP() { return Pc; } + UIntNative GetLR() { return Lr; } +} CONTEXT, *PCONTEXT; + +#elif defined(_X86_) +#define SIZE_OF_80387_REGISTERS 80 +#define MAXIMUM_SUPPORTED_EXTENSION 512 + +typedef struct _FLOATING_SAVE_AREA { + UInt32 ControlWord; + UInt32 StatusWord; + UInt32 TagWord; + UInt32 ErrorOffset; + UInt32 ErrorSelector; + UInt32 DataOffset; + UInt32 DataSelector; + UInt8 RegisterArea[SIZE_OF_80387_REGISTERS]; + UInt32 Cr0NpxState; +} FLOATING_SAVE_AREA; + +#include "pshpack4.h" +typedef struct _CONTEXT { + UInt32 ContextFlags; + UInt32 Dr0; + UInt32 Dr1; + UInt32 Dr2; + UInt32 Dr3; + UInt32 Dr6; + UInt32 Dr7; + FLOATING_SAVE_AREA FloatSave; + UInt32 SegGs; + UInt32 SegFs; + UInt32 SegEs; + UInt32 SegDs; + UInt32 Edi; + UInt32 Esi; + UInt32 Ebx; + UInt32 Edx; + UInt32 Ecx; + UInt32 Eax; + UInt32 Ebp; + UInt32 Eip; + UInt32 SegCs; + UInt32 EFlags; + UInt32 Esp; + UInt32 SegSs; + UInt8 ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; + + void SetIP(UIntNative ip) { Eip = ip; } + void SetSP(UIntNative sp) { Esp = sp; } + void SetArg0Reg(UIntNative val) { Ecx = val; } + void SetArg1Reg(UIntNative val) { Edx = val; } + UIntNative GetIP() { return Eip; } + UIntNative GetSP() { return Esp; } +} CONTEXT, *PCONTEXT; +#include "poppack.h" + +#endif + + +#define EXCEPTION_MAXIMUM_PARAMETERS 15 // maximum number of exception parameters + +typedef struct _EXCEPTION_RECORD32 { + UInt32 ExceptionCode; + UInt32 ExceptionFlags; + UIntNative ExceptionRecord; + UIntNative ExceptionAddress; + UInt32 NumberParameters; + UIntNative ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; +} EXCEPTION_RECORD, *PEXCEPTION_RECORD; + +typedef struct _EXCEPTION_POINTERS { + PEXCEPTION_RECORD ExceptionRecord; + PCONTEXT ContextRecord; +} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; + +typedef Int32 (__stdcall *PVECTORED_EXCEPTION_HANDLER)( + PEXCEPTION_POINTERS ExceptionInfo + ); + +#define EXCEPTION_CONTINUE_EXECUTION (-1) +#define EXCEPTION_CONTINUE_SEARCH (0) +#define EXCEPTION_EXECUTE_HANDLER (1) + +typedef enum _EXCEPTION_DISPOSITION { + ExceptionContinueExecution, + ExceptionContinueSearch, + ExceptionNestedException, + ExceptionCollidedUnwind +} EXCEPTION_DISPOSITION; + +#define STATUS_ACCESS_VIOLATION ((UInt32 )0xC0000005L) +#define STATUS_STACK_OVERFLOW ((UInt32 )0xC00000FDL) +#define STATUS_REDHAWK_NULL_REFERENCE ((UInt32 )0x00000000L) + +#define NULL_AREA_SIZE (64*1024) + +#define GetExceptionCode _exception_code +#define GetExceptionInformation (struct _EXCEPTION_POINTERS *)_exception_info +EXTERN_C unsigned long __cdecl _exception_code(void); +EXTERN_C void * __cdecl _exception_info(void); + +#endif // !DACCESS_COMPILE + +#ifdef FEATURE_ETW + +typedef UInt64 REGHANDLE; +typedef UInt64 TRACEHANDLE; + +struct EVENT_DATA_DESCRIPTOR +{ + UInt64 Ptr; + UInt32 Size; + UInt32 Reserved; +}; + +struct EVENT_DESCRIPTOR +{ + UInt16 Id; + UInt8 Version; + UInt8 Channel; + UInt8 Level; + UInt8 Opcode; + UInt16 Task; + UInt64 Keyword; + +}; + +struct EVENT_FILTER_DESCRIPTOR +{ + UInt64 Ptr; + UInt32 Size; + UInt32 Type; + +}; + +__forceinline +void +EventDataDescCreate(_Out_ EVENT_DATA_DESCRIPTOR * EventDataDescriptor, _In_opt_ const void * DataPtr, UInt32 DataSize) +{ + EventDataDescriptor->Ptr = (UInt64)DataPtr; + EventDataDescriptor->Size = DataSize; + EventDataDescriptor->Reserved = 0; +} + +#endif // FEATURE_ETW + +#ifndef DACCESS_COMPILE + +typedef UInt32 (WINAPI *PTHREAD_START_ROUTINE)(_In_opt_ void* lpThreadParameter); +typedef IntNative (WINAPI *FARPROC)(); + +#define TRUE 1 +#define FALSE 0 + +#define DLL_PROCESS_ATTACH 1 +#define DLL_THREAD_ATTACH 2 +#define DLL_THREAD_DETACH 3 +#define DLL_PROCESS_DETACH 0 +#define DLL_PROCESS_VERIFIER 4 + +#define INFINITE 0xFFFFFFFF + +#define INVALID_HANDLE_VALUE ((HANDLE)(IntNative)-1) + +#define DUPLICATE_CLOSE_SOURCE 0x00000001 +#define DUPLICATE_SAME_ACCESS 0x00000002 + +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 +#define GENERIC_EXECUTE 0x20000000 +#define GENERIC_ALL 0x10000000 + +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define FILE_SHARE_DELETE 0x00000004 + +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#define FILE_ATTRIBUTE_HIDDEN 0x00000002 +#define FILE_ATTRIBUTE_SYSTEM 0x00000004 +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 +#define FILE_ATTRIBUTE_DEVICE 0x00000040 +#define FILE_ATTRIBUTE_NORMAL 0x00000080 +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 +#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 +#define FILE_ATTRIBUTE_COMPRESSED 0x00000800 +#define FILE_ATTRIBUTE_OFFLINE 0x00001000 +#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 +#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 + +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define TRUNCATE_EXISTING 5 + +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 + +#define PAGE_NOACCESS 0x01 +#define PAGE_READONLY 0x02 +#define PAGE_READWRITE 0x04 +#define PAGE_WRITECOPY 0x08 +#define PAGE_EXECUTE 0x10 +#define PAGE_EXECUTE_READ 0x20 +#define PAGE_EXECUTE_READWRITE 0x40 +#define PAGE_EXECUTE_WRITECOPY 0x80 +#define PAGE_GUARD 0x100 +#define PAGE_NOCACHE 0x200 +#define PAGE_WRITECOMBINE 0x400 +#define MEM_COMMIT 0x1000 +#define MEM_RESERVE 0x2000 +#define MEM_DECOMMIT 0x4000 +#define MEM_RELEASE 0x8000 +#define MEM_FREE 0x10000 +#define MEM_PRIVATE 0x20000 +#define MEM_MAPPED 0x40000 +#define MEM_RESET 0x80000 +#define MEM_TOP_DOWN 0x100000 +#define MEM_WRITE_WATCH 0x200000 +#define MEM_PHYSICAL 0x400000 +#define MEM_LARGE_PAGES 0x20000000 +#define MEM_4MB_PAGES 0x80000000 + +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define WAIT_FAILED 0xFFFFFFFF + +#define CREATE_SUSPENDED 0x00000004 +#define THREAD_PRIORITY_NORMAL 0 +#define THREAD_PRIORITY_HIGHEST 2 + +#define NOERROR 0x0 + +#define TLS_OUT_OF_INDEXES 0xFFFFFFFF +#define TLS_NUM_INLINE_SLOTS 64 + +#define SUSPENDTHREAD_FAILED 0xFFFFFFFF +#define RESUMETHREAD_FAILED 0xFFFFFFFF + +#define ERROR_INSUFFICIENT_BUFFER 122 +#define ERROR_TIMEOUT 1460 +#define ERROR_ALREADY_EXISTS 183 + +#define GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT 0x00000002 +#define GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS 0x00000004 + +#endif // !DACCESS_COMPILE + +#define REDHAWK_PALIMPORT EXTERN_C + +#ifndef DACCESS_COMPILE + +#ifdef _DEBUG +#define CaptureStackBackTrace RtlCaptureStackBackTrace +#endif + + +// Include the list of external functions we wish to access. If we do our job 100% then it will be +// possible to link without any direct reference to any Win32 library. +#include "PalRedhawkFunctions.h" + +#endif + +// The Redhawk PAL must be initialized before any of its exports can be called. Returns true for a successful +// initialization and false on failure. +REDHAWK_PALIMPORT bool __stdcall PalInit(); + +// Given a mask of capabilities return true if all of them are supported by the current PAL. +REDHAWK_PALIMPORT bool __stdcall PalHasCapability(PalCapability capability); + +#ifndef APP_LOCAL_RUNTIME +// Have to define this manually because Win32 doesn't always define this API (and the automatic inclusion +// above will generate direct forwarding entries to the OS libraries on Win32). Use the capability interface +// defined above to determine whether this API is callable on your platform. +REDHAWK_PALIMPORT UInt32 __stdcall PalGetCurrentProcessorNumber(); +#endif + +// Given the OS handle of a loaded module, compute the upper and lower virtual address bounds (inclusive). +REDHAWK_PALIMPORT void __stdcall PalGetModuleBounds(HANDLE hOsHandle, _Out_ UInt8 ** ppLowerBound, _Out_ UInt8 ** ppUpperBound); + +#ifdef DACCESS_COMPILE +typedef struct _GUID GUID; +#else +struct GUID; +#endif // DACCESS_COMPILE +REDHAWK_PALIMPORT void __stdcall PalGetPDBInfo(HANDLE hOsHandle, _Out_ GUID * pGuidSignature, _Out_ UInt32 * pdwAge, _Out_writes_z_(cchPath) WCHAR * wszPath, Int32 cchPath); + +#ifndef APP_LOOCAL_RUNTIME +REDHAWK_PALIMPORT bool __stdcall PalGetThreadContext(HANDLE hThread, _Out_ PAL_LIMITED_CONTEXT * pCtx); +#endif + +REDHAWK_PALIMPORT Int32 __stdcall PalGetProcessCpuCount(); + +REDHAWK_PALIMPORT UInt32 __stdcall PalReadFileContents(_In_ const WCHAR *, _Out_ char * buff, _In_ UInt32 maxBytesToRead); + +REDHAWK_PALIMPORT void __stdcall PalYieldProcessor(); + +// Retrieves the entire range of memory dedicated to the calling thread's stack. This does +// not get the current dynamic bounds of the stack, which can be significantly smaller than +// the maximum bounds. +REDHAWK_PALIMPORT bool __stdcall PalGetMaximumStackBounds(_Out_ void** ppStackLowOut, _Out_ void** ppStackHighOut); + +// Return value: number of characters in name string +REDHAWK_PALIMPORT Int32 PalGetModuleFileName(_Out_ wchar_t** pModuleNameOut, HANDLE moduleBase); + +// Various intrinsic declarations needed for the PalGetCurrentTEB implementation below. +#if defined(_X86_) +EXTERN_C unsigned long __readfsdword(unsigned long Offset); +#pragma intrinsic(__readfsdword) +#elif defined(_AMD64_) +EXTERN_C unsigned __int64 __readgsqword(unsigned long Offset); +#pragma intrinsic(__readgsqword) +#elif defined(_ARM_) +EXTERN_C unsigned int _MoveFromCoprocessor(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int); +#pragma intrinsic(_MoveFromCoprocessor) +#else +#error Unsupported architecture +#endif + +// Retrieves the OS TEB for the current thread. +inline UInt8 * PalNtCurrentTeb() +{ +#if defined(_X86_) + return (UInt8*)__readfsdword(0x18); +#elif defined(_AMD64_) + return (UInt8*)__readgsqword(0x30); +#elif defined(_ARM_) + return (UInt8*)_MoveFromCoprocessor(15, 0, 13, 0, 2); +#else +#error Unsupported architecture +#endif +} + +// Offsets of ThreadLocalStoragePointer in the TEB. +#if defined(_X86_) || defined(_ARM_) +#define OFFSETOF__TEB__ThreadLocalStoragePointer 0x2c +#elif defined(_AMD64_) +#define OFFSETOF__TEB__ThreadLocalStoragePointer 0x58 +#else +#error Unsupported architecture +#endif + +// +// Compiler intrinsic definitions. In the interest of performance the PAL doesn't provide exports of these +// (that would defeat the purpose of having an intrinsic in the first place). Instead we place the necessary +// compiler linkage directly inline in this header. As a result this section may have platform specific +// conditional compilation (upto and including defining an export of functionality that isn't a supported +// intrinsic on that platform). +// + +EXTERN_C void * __cdecl _alloca(size_t); +#pragma intrinsic(_alloca) + +EXTERN_C long __cdecl _InterlockedIncrement(long volatile *); +#pragma intrinsic(_InterlockedIncrement) +FORCEINLINE Int32 PalInterlockedIncrement(_Inout_ _Interlocked_operand_ Int32 volatile *pDst) +{ + return _InterlockedIncrement((long volatile *)pDst); +} + +EXTERN_C long __cdecl _InterlockedDecrement(long volatile *); +#pragma intrinsic(_InterlockedDecrement) +FORCEINLINE Int32 PalInterlockedDecrement(_Inout_ _Interlocked_operand_ Int32 volatile *pDst) +{ + return _InterlockedDecrement((long volatile *)pDst); +} + +EXTERN_C long _InterlockedOr(long volatile *, long); +#pragma intrinsic(_InterlockedOr) +FORCEINLINE UInt32 PalInterlockedOr(_Inout_ _Interlocked_operand_ UInt32 volatile *pDst, UInt32 iValue) +{ + return _InterlockedOr((long volatile *)pDst, iValue); +} + +EXTERN_C long _InterlockedAnd(long volatile *, long); +#pragma intrinsic(_InterlockedAnd) +FORCEINLINE UInt32 PalInterlockedAnd(_Inout_ _Interlocked_operand_ UInt32 volatile *pDst, UInt32 iValue) +{ + return _InterlockedAnd((long volatile *)pDst, iValue); +} + +EXTERN_C long __PN__MACHINECALL_CDECL_OR_DEFAULT _InterlockedExchange(long volatile *, long); +#pragma intrinsic(_InterlockedExchange) +FORCEINLINE Int32 PalInterlockedExchange(_Inout_ _Interlocked_operand_ Int32 volatile *pDst, Int32 iValue) +{ + return _InterlockedExchange((long volatile *)pDst, iValue); +} + +EXTERN_C long __PN__MACHINECALL_CDECL_OR_DEFAULT _InterlockedCompareExchange(long volatile *, long, long); +#pragma intrinsic(_InterlockedCompareExchange) +FORCEINLINE Int32 PalInterlockedCompareExchange(_Inout_ _Interlocked_operand_ Int32 volatile *pDst, Int32 iValue, Int32 iComperand) +{ + return _InterlockedCompareExchange((long volatile *)pDst, iValue, iComperand); +} + +EXTERN_C Int64 _InterlockedCompareExchange64(Int64 volatile *, Int64, Int64); +#pragma intrinsic(_InterlockedCompareExchange64) +FORCEINLINE Int64 PalInterlockedCompareExchange64(_Inout_ _Interlocked_operand_ Int64 volatile *pDst, Int64 iValue, Int64 iComperand) +{ + return _InterlockedCompareExchange64(pDst, iValue, iComperand); +} + +#if defined(_AMD64_) +EXTERN_C UInt8 _InterlockedCompareExchange128(Int64 volatile *, Int64, Int64, Int64 *); +#pragma intrinsic(_InterlockedCompareExchange128) +FORCEINLINE UInt8 PalInterlockedCompareExchange128(_Inout_ _Interlocked_operand_ Int64 volatile *pDst, Int64 iValueHigh, Int64 iValueLow, Int64 *pComperand) +{ + return _InterlockedCompareExchange128(pDst, iValueHigh, iValueLow, pComperand); +} +#endif // _AMD64_ + +#if defined(_X86_) || defined(_ARM_) + +#define PalInterlockedExchangePointer(_pDst, _pValue) \ + ((void *)_InterlockedExchange((long volatile *)(_pDst), (long)(size_t)(_pValue))) + +#define PalInterlockedCompareExchangePointer(_pDst, _pValue, _pComperand) \ + ((void *)_InterlockedCompareExchange((long volatile *)(_pDst), (long)(size_t)(_pValue), (long)(size_t)(_pComperand))) + +#elif defined(_AMD64_) + +EXTERN_C void * _InterlockedExchangePointer(void * volatile *, void *); +#pragma intrinsic(_InterlockedExchangePointer) +FORCEINLINE void * PalInterlockedExchangePointer(_Inout_ _Interlocked_operand_ void * volatile *pDst, _In_ void *pValue) +{ + return _InterlockedExchangePointer((void * volatile *)pDst, pValue); +} + +EXTERN_C void * _InterlockedCompareExchangePointer(void * volatile *, void *, void *); +#pragma intrinsic(_InterlockedCompareExchangePointer) +FORCEINLINE void * PalInterlockedCompareExchangePointer(_Inout_ _Interlocked_operand_ void * volatile *pDst, _In_ void *pValue, _In_ void *pComperand) +{ + return _InterlockedCompareExchangePointer((void * volatile *)pDst, pValue, pComperand); +} + +#else +#error Unsupported architecture +#endif + + +#if defined(_X86_) + +#define PalYieldProcessor() __asm { rep nop } + +FORCEINLINE void PalMemoryBarrier() +{ + Int32 Barrier; + __asm { + xchg Barrier, eax + } +} + +#elif defined(_AMD64_) + +EXTERN_C void _mm_pause(); +#pragma intrinsic(_mm_pause) +#define PalYieldProcessor() _mm_pause() + +EXTERN_C void __faststorefence(); +#pragma intrinsic(__faststorefence) +#define PalMemoryBarrier() __faststorefence() + +#elif defined(_ARM_) + +FORCEINLINE void PalYieldProcessor() {} + +EXTERN_C void __emit(const unsigned __int32 opcode); +#pragma intrinsic(__emit) +#define PalMemoryBarrier() { __emit(0xF3BF); __emit(0x8F5F); } + +#else +#error Unsupported architecture +#endif + +// +// Export PAL blessed versions of *printf functions we use (mostly debug only). +// +typedef char * va_list; +REDHAWK_PALIMPORT void __cdecl PalPrintf(_In_z_ _Printf_format_string_ const char * szFormat, ...); +REDHAWK_PALIMPORT void __cdecl PalFlushStdout(); + +#ifndef DACCESS_COMPILE +REDHAWK_PALIMPORT int __cdecl PalSprintf(_Out_writes_z_(cchBuffer) char * szBuffer, size_t cchBuffer, _In_z_ _Printf_format_string_ const char * szFormat, ...); +REDHAWK_PALIMPORT int __cdecl PalVSprintf(_Out_writes_z_(cchBuffer) char * szBuffer, size_t cchBuffer, _In_z_ _Printf_format_string_ const char * szFormat, va_list args); +#else +#define PalSprintf sprintf_s +#define PalVSprintf vsprintf_s +#endif + +#ifndef DACCESS_COMPILE + +// An annoying side-effect of enabling full compiler warnings is that it complains about constant expressions +// in control flow predicates. These happen to be useful in certain macros, such as the va_start definitions +// below. The following macros will allow the warning to be turned off for the duration of the macro expansion +// only. If this finds broader use we can consider moving them to a more global location. +#define ALLOW_CONSTANT_EXPR_BEGIN __pragma(warning(push)) __pragma(warning(disable:4127)) +#define ALLOW_CONSTANT_EXPR_END __pragma(warning(pop)) + +// Inline a definition for the only varargs related functionality we need, va_start, in order to avoid +// including any CRT header file at all. It's pretty simple for x86 (especially since we're only supporting +// printf-like varargs). If and when we need to support other platforms (where it gets more complex) we can +// revisit whether we need this functionality at all or whether we could move the asbtraction boundary to put +// all the vararg related code inside the PAL (we only use this for debugging purposes currently). +#ifdef _X86_ +#define va_start(_va_list, _format) \ + ALLOW_CONSTANT_EXPR_BEGIN \ + do { _va_list = (va_list)&(_format) + sizeof(char*); } while (false) \ + ALLOW_CONSTANT_EXPR_END + +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_arg(ap, t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) + +#elif defined(_AMD64_) +EXTERN_C void __cdecl __va_start(va_list *, ...); +#pragma intrinsic(__va_start) +#define va_start(_va_list, _format) __va_start(&_va_list, _format) +#define va_arg(ap, t) \ + ( ( sizeof(t) > sizeof(__int64) || ( sizeof(t) & (sizeof(t) - 1) ) != 0 ) \ + ? **(t **)( ( ap += sizeof(__int64) ) - sizeof(__int64) ) \ + : *(t *)( ( ap += sizeof(__int64) ) - sizeof(__int64) ) ) + +#elif defined(_ARM_) +#define _VA_ALIGN 4 +#define _SLOTSIZEOF(t) ( (sizeof(t) + _VA_ALIGN - 1) & ~(_VA_ALIGN - 1) ) +#define _APALIGN(t,ap) ( ((va_list)0 - (ap)) & (__alignof(t) - 1) ) +#define va_start(_va_list, _format) \ + ALLOW_CONSTANT_EXPR_BEGIN \ + do { _va_list = (va_list)&_format + _SLOTSIZEOF(_format); } while (false) \ + ALLOW_CONSTANT_EXPR_END +#define va_arg(ap,t) (*(t *)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) \ + - _SLOTSIZEOF(t))) +#endif // !_X86_ + + +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalGlobalMemoryStatusEx(_Out_ PAL_MEMORY_STATUS* pBuffer); +REDHAWK_PALIMPORT _Ret_maybenull_ _Post_writable_byte_size_(size) void* __stdcall PalVirtualAlloc(_In_opt_ void* pAddress, UIntNative size, UInt32 allocationType, UInt32 protect); +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalVirtualFree(_In_ void* pAddress, UIntNative size, UInt32 freeType); +REDHAWK_PALIMPORT void __stdcall PalSleep(UInt32 milliseconds); +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalSwitchToThread(); +REDHAWK_PALIMPORT HANDLE __stdcall PalCreateMutexW(_In_opt_ LPSECURITY_ATTRIBUTES pMutexAttributes, UInt32_BOOL initialOwner, _In_opt_z_ LPCWSTR pName); +REDHAWK_PALIMPORT HANDLE __stdcall PalCreateEventW(_In_opt_ LPSECURITY_ATTRIBUTES pMutexAttributes, UInt32_BOOL manualReset, UInt32_BOOL initialState, _In_opt_z_ LPCWSTR pName); +REDHAWK_PALIMPORT UInt32 __stdcall PalGetTickCount(); +REDHAWK_PALIMPORT HANDLE __stdcall PalCreateFileW(_In_z_ LPCWSTR pFileName, UInt32 desiredAccess, UInt32 shareMode, _In_opt_ void* pSecurityAttributes, UInt32 creationDisposition, UInt32 flagsAndAttributes, HANDLE hTemplateFile); +REDHAWK_PALIMPORT UInt32 __stdcall PalGetWriteWatch(UInt32 flags, _In_ void* pBaseAddress, UIntNative regionSize, _Out_writes_to_opt_(*pCount, *pCount) void** pAddresses, _Inout_opt_ UIntNative* pCount, _Inout_opt_ UInt32* pGranularity); +REDHAWK_PALIMPORT UInt32 __stdcall PalResetWriteWatch(void* pBaseAddress, UIntNative regionSize); +REDHAWK_PALIMPORT HANDLE __stdcall PalCreateLowMemoryNotification(); +REDHAWK_PALIMPORT void __stdcall PalTerminateCurrentProcess(UInt32 exitCode); +REDHAWK_PALIMPORT HANDLE __stdcall PalGetModuleHandleFromPointer(_In_ void* pointer); + +#ifndef APP_LOCAL_RUNTIME +REDHAWK_PALIMPORT void* __stdcall PalAddVectoredExceptionHandler(UInt32 firstHandler, _In_ PVECTORED_EXCEPTION_HANDLER vectoredHandler); +#endif + +typedef UInt32 (__stdcall *BackgroundCallback)(_In_opt_ void* pCallbackContext); +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalStartBackgroundGCThread(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext); +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalStartFinalizerThread(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext); + +typedef UInt32 (__stdcall *PalHijackCallback)(HANDLE hThread, _In_ PAL_LIMITED_CONTEXT* pThreadContext, _In_opt_ void* pCallbackContext); +REDHAWK_PALIMPORT UInt32 __stdcall PalHijack(HANDLE hThread, _In_ PalHijackCallback callback, _In_opt_ void* pCallbackContext); + +#ifdef FEATURE_ETW +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalEventEnabled(REGHANDLE regHandle, _In_ const EVENT_DESCRIPTOR* eventDescriptor); +#endif + +inline void PalDebugBreak() { __debugbreak(); } + +REDHAWK_PALIMPORT UInt32 __stdcall PalGetLogicalCpuCount(); +REDHAWK_PALIMPORT size_t __stdcall PalGetLargestOnDieCacheSize(UInt32_BOOL bTrueSize); + +REDHAWK_PALIMPORT _Ret_maybenull_ void* __stdcall PalSetWerDataBuffer(_In_ void* pNewBuffer); + +REDHAWK_PALIMPORT UInt32_BOOL __stdcall PalAllocateThunksFromTemplate(_In_ HANDLE hTemplateModule, UInt32 templateRva, size_t templateSize, _Outptr_result_bytebuffer_(templateSize) void** newThunksOut); + +REDHAWK_PALIMPORT UInt32 __stdcall PalCompatibleWaitAny(UInt32_BOOL alertable, UInt32 timeout, UInt32 count, HANDLE* pHandles, UInt32_BOOL allowReentrantWait); + +REDHAWK_PALIMPORT size_t __cdecl wcslen(const wchar_t *str); + +REDHAWK_PALIMPORT Int32 __cdecl _wcsicmp(const wchar_t *string1, const wchar_t *string2); +#endif // !DACCESS_COMPILE + +#endif // !PAL_REDHAWK_INCLUDED \ No newline at end of file diff --git a/src/Native/Runtime/PalRedhawkCommon.cpp b/src/Native/Runtime/PalRedhawkCommon.cpp new file mode 100644 index 00000000000..4682cea6401 --- /dev/null +++ b/src/Native/Runtime/PalRedhawkCommon.cpp @@ -0,0 +1,453 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Implementation of the portions of the Redhawk Platform Abstraction Layer (PAL) library that are common among +// multiple PAL variants. +// +// Note that in general we don't want to assume that Windows and Redhawk global definitions can co-exist. +// Since this code must include Windows headers to do its job we can't therefore safely include general +// Redhawk header files. +// + +#include +#include +#include +#include +#include +#include "commontypes.h" +#include "daccess.h" +#include +#include +#include "commonmacros.h" +#include "assert.h" + +#ifdef USE_PORTABLE_HELPERS +#define assert(expr) ASSERT(expr) +#endif + +#define REDHAWK_PALEXPORT extern "C" +#define REDHAWK_PALAPI __stdcall + + +#ifdef APP_LOCAL_RUNTIME +#ifdef _DEBUG +EXTERN_C { + HANDLE WINAPI GetStdHandle( + _In_ DWORD nStdHandle + ); +} +#endif // _DEBUG +#endif // APP_LOCAL_RUNTIME + +REDHAWK_PALEXPORT void __cdecl PalPrintf(_In_z_ _Printf_format_string_ const char * szFormat, ...) +{ +#if defined(_DEBUG) + char buffer[8*1024]; + + va_list args; + va_start(args, szFormat); + int cch = _vsprintf_s_l(buffer, COUNTOF(buffer), szFormat, NULL, args); + + // we have to use WriteConsole directly because the "app" CRT doesn't allow us to print to the console + DWORD cchWritten; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buffer, (DWORD)cch, &cchWritten, NULL); +#endif +} + +REDHAWK_PALEXPORT void __cdecl PalFlushStdout() +{ +#if defined(_DEBUG) + FlushFileBuffers(GetStdHandle(STD_OUTPUT_HANDLE)); +#endif +} + +REDHAWK_PALEXPORT int __cdecl PalSprintf(_Out_writes_z_(cchBuffer) char * szBuffer, size_t cchBuffer, _In_z_ _Printf_format_string_ const char * szFormat, ...) +{ + va_list args; + va_start(args, szFormat); + return _vsprintf_s_l(szBuffer, cchBuffer, szFormat, NULL, args); +} + +REDHAWK_PALEXPORT int __cdecl PalVSprintf(_Out_writes_z_(cchBuffer) char * szBuffer, size_t cchBuffer, _In_z_ _Printf_format_string_ const char * szFormat, va_list args) +{ + return _vsprintf_s_l(szBuffer, cchBuffer, szFormat, NULL, args); +} + +// Given the OS handle of a loaded module, compute the upper and lower virtual address bounds (inclusive). +REDHAWK_PALEXPORT void REDHAWK_PALAPI PalGetModuleBounds(HANDLE hOsHandle, _Out_ UInt8 ** ppLowerBound, _Out_ UInt8 ** ppUpperBound) +{ + BYTE *pbModule = (BYTE*)hOsHandle; + DWORD cbModule; + + IMAGE_NT_HEADERS *pNtHeaders = (IMAGE_NT_HEADERS*)(pbModule + ((IMAGE_DOS_HEADER*)hOsHandle)->e_lfanew); + if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + cbModule = ((IMAGE_OPTIONAL_HEADER32*)&pNtHeaders->OptionalHeader)->SizeOfImage; + else + cbModule = ((IMAGE_OPTIONAL_HEADER64*)&pNtHeaders->OptionalHeader)->SizeOfImage; + + *ppLowerBound = pbModule; + *ppUpperBound = pbModule + cbModule - 1; +} + +// Reads through the PE header of the specified module, and returns +// the module's matching PDB's signature GUID, age, and build path by +// fishing them out of the last IMAGE_DEBUG_DIRECTORY of type +// IMAGE_DEBUG_TYPE_CODEVIEW. Used when sending the ModuleLoad event +// to help profilers find matching PDBs for loaded modules. +// +// Arguments: +// +// [in] hOsHandle - OS Handle for module from which to get PDB info +// [out] pGuidSignature - PDB's signature GUID to be placed here +// [out] pdwAge - PDB's age to be placed here +// [out] wszPath - PDB's build path to be placed here +// [in] cchPath - Number of wide characters allocated in wszPath, including NULL terminator +// +// This is a simplification of similar code in desktop CLR's GetCodeViewInfo +// in eventtrace.cpp. +REDHAWK_PALEXPORT void REDHAWK_PALAPI PalGetPDBInfo(HANDLE hOsHandle, _Out_ GUID * pGuidSignature, _Out_ UInt32 * pdwAge, _Out_writes_z_(cchPath) WCHAR * wszPath, Int32 cchPath) +{ + // Zero-init [out]-params + ZeroMemory(pGuidSignature, sizeof(*pGuidSignature)); + *pdwAge = 0; + if (cchPath <= 0) + return; + wszPath[0] = L'\0'; + + BYTE *pbModule = (BYTE*)hOsHandle; + + IMAGE_NT_HEADERS const * pNtHeaders = (IMAGE_NT_HEADERS*)(pbModule + ((IMAGE_DOS_HEADER*)hOsHandle)->e_lfanew); + IMAGE_DATA_DIRECTORY const * rgDataDirectory = NULL; + if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) + rgDataDirectory = ((IMAGE_OPTIONAL_HEADER32 const *)&pNtHeaders->OptionalHeader)->DataDirectory; + else + rgDataDirectory = ((IMAGE_OPTIONAL_HEADER64 const *)&pNtHeaders->OptionalHeader)->DataDirectory; + + IMAGE_DATA_DIRECTORY const * pDebugDataDirectory = &rgDataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; + + // In Redhawk, modules are loaded as MAPPED, so we don't have to worry about dealing + // with FLAT files (with padding missing), so header addresses can be used as is + IMAGE_DEBUG_DIRECTORY const *rgDebugEntries = (IMAGE_DEBUG_DIRECTORY const *) (pbModule + pDebugDataDirectory->VirtualAddress); + DWORD cbDebugEntries = pDebugDataDirectory->Size; + if (cbDebugEntries < sizeof(IMAGE_DEBUG_DIRECTORY)) + return; + + // Since rgDebugEntries is an array of IMAGE_DEBUG_DIRECTORYs, cbDebugEntries + // should be a multiple of sizeof(IMAGE_DEBUG_DIRECTORY). + if (cbDebugEntries % sizeof(IMAGE_DEBUG_DIRECTORY) != 0) + return; + + // CodeView RSDS debug information -> PDB 7.00 + struct CV_INFO_PDB70 + { + DWORD magic; + GUID signature; // unique identifier + DWORD age; // an always-incrementing value + _Field_z_ char path[MAX_PATH]; // zero terminated string with the name of the PDB file + }; + + // Temporary storage for a CV_INFO_PDB70 and its size (which could be less than + // sizeof(CV_INFO_PDB70); see below). + struct PdbInfo + { + CV_INFO_PDB70 * m_pPdb70; + ULONG m_cbPdb70; + }; + + // Grab module bounds so we can do some rough sanity checking before we follow any + // RVAs + UInt8 * pbModuleLowerBound = NULL; + UInt8 * pbModuleUpperBound = NULL; + PalGetModuleBounds(hOsHandle, &pbModuleLowerBound, &pbModuleUpperBound); + + // Iterate through all debug directory entries. The convention is that debuggers & + // profilers typically just use the very last IMAGE_DEBUG_TYPE_CODEVIEW entry. Treat raw + // bytes we read as untrusted. + PdbInfo pdbInfoLast = {0}; + int cEntries = cbDebugEntries / sizeof(IMAGE_DEBUG_DIRECTORY); + for (int i = 0; i < cEntries; i++) + { + if ((UInt8 *)(&rgDebugEntries[i]) + sizeof(rgDebugEntries[i]) >= pbModuleUpperBound) + { + // Bogus pointer + return; + } + + if (rgDebugEntries[i].Type != IMAGE_DEBUG_TYPE_CODEVIEW) + continue; + + // Get raw data pointed to by this IMAGE_DEBUG_DIRECTORY + + // AddressOfRawData is generally set properly for Redhawk modules, so we don't + // have to worry about using PointerToRawData and converting it to an RVA + if (rgDebugEntries[i].AddressOfRawData == NULL) + continue; + + DWORD rvaOfRawData = rgDebugEntries[i].AddressOfRawData; + ULONG cbDebugData = rgDebugEntries[i].SizeOfData; + if (cbDebugData < size_t(&((CV_INFO_PDB70*)0)->magic) + sizeof(((CV_INFO_PDB70*)0)->magic)) + { + // raw data too small to contain magic number at expected spot, so its format + // is not recognizeable. Skip + continue; + } + + // Verify the magic number is as expected + const DWORD CV_SIGNATURE_RSDS = 0x53445352; + CV_INFO_PDB70 * pPdb70 = (CV_INFO_PDB70 *) (pbModule + rvaOfRawData); + if ((UInt8 *)(pPdb70) + cbDebugData >= pbModuleUpperBound) + { + // Bogus pointer + return; + } + + if (pPdb70->magic != CV_SIGNATURE_RSDS) + { + // Unrecognized magic number. Skip + continue; + } + + // From this point forward, the format should adhere to the expected layout of + // CV_INFO_PDB70. If we find otherwise, then assume the IMAGE_DEBUG_DIRECTORY is + // outright corrupt. + + // Verify sane size of raw data + if (cbDebugData > sizeof(CV_INFO_PDB70)) + return; + + // cbDebugData actually can be < sizeof(CV_INFO_PDB70), since the "path" field + // can be truncated to its actual data length (i.e., fewer than MAX_PATH chars + // may be present in the PE file). In some cases, though, cbDebugData will + // include all MAX_PATH chars even though path gets null-terminated well before + // the MAX_PATH limit. + + // Gotta have at least one byte of the path + if (cbDebugData < offsetof(CV_INFO_PDB70, path) + sizeof(char)) + return; + + // How much space is available for the path? + size_t cchPathMaxIncludingNullTerminator = (cbDebugData - offsetof(CV_INFO_PDB70, path)) / sizeof(char); + assert(cchPathMaxIncludingNullTerminator >= 1); // Guaranteed above + + // Verify path string fits inside the declared size + size_t cchPathActualExcludingNullTerminator = strnlen_s(pPdb70->path, cchPathMaxIncludingNullTerminator); + if (cchPathActualExcludingNullTerminator == cchPathMaxIncludingNullTerminator) + { + // This is how strnlen indicates failure--it couldn't find the null + // terminator within the buffer size specified + return; + } + + // Looks valid. Remember it. + pdbInfoLast.m_pPdb70 = pPdb70; + pdbInfoLast.m_cbPdb70 = cbDebugData; + } + + // Take the last IMAGE_DEBUG_TYPE_CODEVIEW entry we saw, and return it to the caller + if (pdbInfoLast.m_pPdb70 != NULL) + { + memcpy(pGuidSignature, &pdbInfoLast.m_pPdb70->signature, sizeof(GUID)); + *pdwAge = pdbInfoLast.m_pPdb70->age; + + // Convert build path from ANSI to UNICODE + errno_t ret; + size_t cchConverted; + ret = mbstowcs_s( + &cchConverted, + wszPath, + cchPath, + pdbInfoLast.m_pPdb70->path, + _countof(pdbInfoLast.m_pPdb70->path) - 1); + if ((ret != 0) && (ret != STRUNCATE)) + { + // PDB path isn't essential. An empty string will do if we hit an error. + assert(cchPath > 0); // Guaranteed at top of function + wszPath[0] = L'\0'; + } + } +} + +REDHAWK_PALEXPORT Int32 PalGetProcessCpuCount() +{ + static int CpuCount = 0; + + if (CpuCount != 0) + return CpuCount; + else + { + // The concept of process CPU affinity is going away and so CoreSystem obsoletes the APIs used to + // fetch this information. Instead we'll just return total cpu count. + SYSTEM_INFO sysInfo; +#ifndef APP_LOCAL_RUNTIME + ::GetSystemInfo(&sysInfo); +#else + ::GetNativeSystemInfo(&sysInfo); +#endif + CpuCount = sysInfo.dwNumberOfProcessors; + return sysInfo.dwNumberOfProcessors; + } +} + +//Reads the entire contents of the file into the specified buffer, buff +//returns the number of bytes read if the file is successfully read +//returns 0 if the file is not found, size is greater than maxBytesToRead or the file couldn't be opened or read +REDHAWK_PALEXPORT UInt32 PalReadFileContents(_In_z_ WCHAR* fileName, _Out_writes_all_(cchBuff) char* buff, _In_ UInt32 cchBuff) +{ + WIN32_FILE_ATTRIBUTE_DATA attrData; + + BOOL getAttrSuccess = GetFileAttributesExW(fileName, GetFileExInfoStandard, &attrData); + + //if we weren't able to get the file attributes, or the file is larger than maxBytesToRead, or the file size is zero + if ((!getAttrSuccess) || (attrData.nFileSizeHigh != 0) || (attrData.nFileSizeLow > (DWORD)cchBuff) || (attrData.nFileSizeLow == 0)) + { + return 0; + } + + HANDLE hFile = CreateFile2(fileName, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ, OPEN_EXISTING, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + return 0; + } + + UInt32 bytesRead; + + BOOL readSuccess = ReadFile(hFile, buff, (DWORD)cchBuff, (DWORD*)&bytesRead, NULL); + + CloseHandle(hFile); + + if (!readSuccess) + { + return 0; + } + + return bytesRead; +} + + +// Retrieves the entire range of memory dedicated to the calling thread's stack. This does +// not get the current dynamic bounds of the stack, which can be significantly smaller than +// the maximum bounds. +REDHAWK_PALEXPORT bool PalGetMaximumStackBounds(_Out_ void** ppStackLowOut, _Out_ void** ppStackHighOut) +{ + // VirtualQuery on the address of a local variable to get the allocation + // base of the stack. Then use the StackBase field in the TEB to give + // the highest address of the stack region. + MEMORY_BASIC_INFORMATION mbi = { 0 }; + SIZE_T cb = VirtualQuery(&mbi, &mbi, sizeof(mbi)); + if (cb != sizeof(mbi)) + return false; + + NT_TIB* pTib = (NT_TIB*)NtCurrentTeb(); + *ppStackHighOut = pTib->StackBase; // stack base is the highest address + *ppStackLowOut = mbi.AllocationBase; // allocation base is the lowest address + return true; +} + +#ifndef USE_PORTABLE_HELPERS +#ifdef APP_LOCAL_RUNTIME + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING *PUNICODE_STRING; +typedef const UNICODE_STRING *PCUNICODE_STRING; + +typedef struct _PEB_LDR_DATA { + BYTE Reserved1[8]; + PVOID Reserved2[3]; + LIST_ENTRY InMemoryOrderModuleList; +} PEB_LDR_DATA, *PPEB_LDR_DATA; + +typedef struct _LDR_DATA_TABLE_ENTRY { + PVOID Reserved1[2]; + LIST_ENTRY InMemoryOrderLinks; + PVOID Reserved2[2]; + PVOID DllBase; + PVOID Reserved3[2]; + UNICODE_STRING FullDllName; + BYTE Reserved4[8]; + PVOID Reserved5[3]; + union { + ULONG CheckSum; + PVOID Reserved6; + } DUMMYUNIONNAME; + ULONG TimeDateStamp; +} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; + +typedef struct _PEB { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + PVOID Reserved3[2]; + PPEB_LDR_DATA Ldr; + PVOID /*PRTL_USER_PROCESS_PARAMETERS*/ ProcessParameters; + PVOID Reserved4[3]; + PVOID AtlThunkSListPtr; + PVOID Reserved5; + ULONG Reserved6; + PVOID Reserved7; + ULONG Reserved8; + ULONG AtlThunkSListPtr32; + PVOID Reserved9[45]; + BYTE Reserved10[96]; + PVOID /*PPS_POST_PROCESS_INIT_ROUTINE*/ PostProcessInitRoutine; + BYTE Reserved11[128]; + PVOID Reserved12[1]; + ULONG SessionId; +} PEB, *PPEB; + +typedef struct _TEB { + PVOID Reserved1[12]; + PPEB ProcessEnvironmentBlock; + PVOID Reserved2[399]; + BYTE Reserved3[1952]; + PVOID TlsSlots[64]; + BYTE Reserved4[8]; + PVOID Reserved5[26]; + PVOID ReservedForOle; // Windows 2000 only + PVOID Reserved6[4]; + PVOID TlsExpansionSlots; +} TEB, *PTEB; + +#endif // APP_LOCAL_RUNTIME +#endif // USE_PORTABLE_HELPERS + +// retrieves the full path to the specified module, if moduleBase is NULL retreieves the full path to the +// executable module of the current process. +// +// Return value: number of characters in name string +// +//NOTE: This implementation exists because calling GetModuleFileName is not wack compliant. if we later decide +// that the framework package containing mrt100_app no longer needs to be wack compliant, this should be +// removed and the windows implementation of GetModuleFileName should be substitued on windows. +REDHAWK_PALEXPORT Int32 PalGetModuleFileName(_Out_ wchar_t** pModuleNameOut, HANDLE moduleBase) +{ + TEB* pTEB = NtCurrentTeb(); + LIST_ENTRY* pStartLink = &(pTEB->ProcessEnvironmentBlock->Ldr->InMemoryOrderModuleList); + LIST_ENTRY* pCurLink = pStartLink->Flink; + + do + { + LDR_DATA_TABLE_ENTRY* pEntry = CONTAINING_RECORD(pCurLink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + + //null moduleBase will result in the first module being returned + //since the module list is ordered this is the executable module of the current process + if ((pEntry->DllBase == moduleBase) || (moduleBase == NULL)) + { + *pModuleNameOut = pEntry->FullDllName.Buffer; + return pEntry->FullDllName.Length / 2; + } + pCurLink = pCurLink->Flink; + } + while (pCurLink != pStartLink); + + *pModuleNameOut = NULL; + return 0; +} + diff --git a/src/Native/Runtime/PalRedhawkCommon.h b/src/Native/Runtime/PalRedhawkCommon.h new file mode 100644 index 00000000000..51e0c8d7230 --- /dev/null +++ b/src/Native/Runtime/PalRedhawkCommon.h @@ -0,0 +1,111 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Provide common definitions between the Redhawk and the Redhawk PAL implementation. This header file is used +// (rather than PalRedhawk.h) since the PAL implementation is built in a different environment than Redhawk +// code. For instance both environments may provide a definition of various common macros such as NULL. +// +// This header contains only environment neutral definitions (i.e. using only base C++ types and compositions +// of those types) and can thus be included from either environment without issue. +// + +#ifndef __PAL_REDHAWK_COMMON_INCLUDED +#define __PAL_REDHAWK_COMMON_INCLUDED + +// We define the notion of capabilities: optional functionality that the PAL may expose. Use +// PalHasCapability() with the constants below to determine what is supported at runtime. +enum PalCapability +{ + WriteWatchCapability = 0x00000001, // GetWriteWatch() and friends + LowMemoryNotificationCapability = 0x00000002, // CreateMemoryResourceNotification() and friends + GetCurrentProcessorNumberCapability = 0x00000004, // GetCurrentProcessorNumber() +}; + +#define DECLSPEC_ALIGN(x) __declspec(align(x)) + +#ifdef _AMD64_ +#define AMD64_ALIGN_16 DECLSPEC_ALIGN(16) +#else // _AMD64_ +#define AMD64_ALIGN_16 +#endif // _AMD64_ + +struct AMD64_ALIGN_16 Fp128 { + UInt64 Low; + Int64 High; +}; + +struct PAL_MEMORY_STATUS +{ + UInt32 dwLength; + UInt32 dwMemoryLoad; +#ifndef APP_LOCAL_RUNTIME + UInt64 ullTotalPhys; + UInt64 ullAvailPhys; + UInt64 ullTotalPageFile; + UInt64 ullAvailPageFile; +#endif + UInt64 ullTotalVirtual; +#ifndef APP_LOCAL_RUNTIME + UInt64 ullAvailVirtual; + UInt64 ullAvailExtendedVirtual; +#endif +}; + +struct PAL_LIMITED_CONTEXT +{ +#ifdef TARGET_ARM + UIntNative R0; + UIntNative R4; + UIntNative R5; + UIntNative R6; + UIntNative R7; + UIntNative R8; + UIntNative R9; + UIntNative R10; + UIntNative R11; + + UIntNative IP; + UIntNative SP; + UIntNative LR; + + UInt64 D[16-8]; // D8 .. D15 registers (D16 .. D31 are volatile according to the ABI spec) + + UIntNative GetIp() const { return IP; } + UIntNative GetSp() const { return SP; } + UIntNative GetFp() const { return R7; } +#else // _ARM_ + UIntNative IP; + UIntNative Rsp; + UIntNative Rbp; + UIntNative Rdi; + UIntNative Rsi; + UIntNative Rax; + UIntNative Rbx; +#ifdef TARGET_AMD64 + UIntNative R12; + UIntNative R13; + UIntNative R14; + UIntNative R15; + UIntNative __explicit_padding__; + Fp128 Xmm6; + Fp128 Xmm7; + Fp128 Xmm8; + Fp128 Xmm9; + Fp128 Xmm10; + Fp128 Xmm11; + Fp128 Xmm12; + Fp128 Xmm13; + Fp128 Xmm14; + Fp128 Xmm15; +#endif // _AMD64_ + + UIntNative GetIp() const { return IP; } + UIntNative GetSp() const { return Rsp; } + UIntNative GetFp() const { return Rbp; } +#endif // _ARM_ +}; + +#endif // __PAL_REDHAWK_COMMON_INCLUDED diff --git a/src/Native/Runtime/PalRedhawkMinWin.cpp b/src/Native/Runtime/PalRedhawkMinWin.cpp new file mode 100644 index 00000000000..3aa68b5c832 --- /dev/null +++ b/src/Native/Runtime/PalRedhawkMinWin.cpp @@ -0,0 +1,1242 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Implementation of the Redhawk Platform Abstraction Layer (PAL) library when MinWin is the platform. In this +// case most or all of the import requirements which Redhawk has can be satisfied via a forwarding export to +// some native MinWin library. Therefore most of the work is done in the .def file and there is very little +// code here. +// +// Note that in general we don't want to assume that Windows and Redhawk global definitions can co-exist. +// Since this code must include Windows headers to do its job we can't therefore safely include general +// Redhawk header files. +// + +#include +#include +#include +#include +#include +#include "commontypes.h" +#include "daccess.h" +#include +#include +#include "commonmacros.h" +#include "assert.h" + +#ifdef USE_PORTABLE_HELPERS +#define assert(expr) ASSERT(expr) +#endif + +#define REDHAWK_PALEXPORT extern "C" +#define REDHAWK_PALAPI __stdcall + +extern "C" UInt32 __stdcall NtGetCurrentProcessorNumber(); + +static DWORD g_dwPALCapabilities; + +extern bool PalQueryProcessorTopology(); +REDHAWK_PALEXPORT void __cdecl PalPrintf(_In_z_ _Printf_format_string_ const char * szFormat, ...); + +// The Redhawk PAL must be initialized before any of its exports can be called. Returns true for a successful +// initialization and false on failure. +REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalInit() +{ + g_dwPALCapabilities = WriteWatchCapability | GetCurrentProcessorNumberCapability | LowMemoryNotificationCapability; + + if (!PalQueryProcessorTopology()) + return false; + + return true; +} + +// Given a mask of capabilities return true if all of them are supported by the current PAL. +REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalHasCapability(PalCapability capability) +{ + return (g_dwPALCapabilities & (DWORD)capability) == (DWORD)capability; +} + +REDHAWK_PALEXPORT unsigned int REDHAWK_PALAPI PalGetCurrentProcessorNumber() +{ + return GetCurrentProcessorNumber(); +} + +#define SUPPRESS_WARNING_4127 \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) /* conditional expression is constant*/ + +#define POP_WARNING_STATE \ + __pragma(warning(pop)) + +#define WHILE_0 \ + SUPPRESS_WARNING_4127 \ + while(0) \ + POP_WARNING_STATE \ + +#define RETURN_RESULT(success) \ + do \ + { \ + if (success) \ + return S_OK; \ + else \ + { \ + DWORD lasterror = GetLastError(); \ + if (lasterror == 0) \ + return E_FAIL; \ + return HRESULT_FROM_WIN32(lasterror); \ + } \ + } \ + WHILE_0; + +extern "C" int __stdcall PalGetModuleFileName(_Out_ wchar_t** pModuleNameOut, HANDLE moduleBase); + +HRESULT STDMETHODCALLTYPE AllocateThunksFromTemplate( + _In_ HANDLE hTemplateModule, + DWORD templateRva, + SIZE_T templateSize, + __RPC__out_ecount(templateSize) /* [retval][out] */ PVOID * newThunksOut) +{ +#ifdef XBOX_ONE + return E_NOTIMPL; +#else + bool success = false; + HANDLE hMap = NULL, hFile = INVALID_HANDLE_VALUE; + + WCHAR * wszModuleFileName = NULL; + if (PalGetModuleFileName(&wszModuleFileName, hTemplateModule) == 0 || wszModuleFileName == NULL) + return E_FAIL; + + hFile = CreateFileW(wszModuleFileName, GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + goto cleanup; + + hMap = CreateFileMapping(hFile, NULL, SEC_IMAGE | PAGE_READONLY, 0, 0, NULL); + if (hMap == NULL) + goto cleanup; + + *newThunksOut = MapViewOfFile(hMap, 0, 0, templateRva, templateSize); + success = ((*newThunksOut) != NULL); + +cleanup: + CloseHandle(hMap); + CloseHandle(hFile); + + RETURN_RESULT(success); +#endif +} + +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI PalAllocateThunksFromTemplate(HANDLE hTemplateModule, DWORD templateRva, SIZE_T templateSize, void** newThunksOut) +{ + return (AllocateThunksFromTemplate(hTemplateModule, templateRva, templateSize, newThunksOut) == S_OK); +} + +REDHAWK_PALEXPORT DWORD REDHAWK_PALAPI PalCompatibleWaitAny(BOOL alertable, DWORD timeout, ULONG handleCount, HANDLE* pHandles, BOOL allowReentrantWait) +{ + DWORD index; + SetLastError(ERROR_SUCCESS); // recommended by MSDN. + HRESULT hr = CoWaitForMultipleHandles(alertable ? COWAIT_ALERTABLE : 0, timeout, handleCount, pHandles, &index); + + switch (hr) + { + case S_OK: + return index; + + case RPC_S_CALLPENDING: + return WAIT_TIMEOUT; + + default: + SetLastError(HRESULT_CODE(hr)); + return WAIT_FAILED; + } +} + +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI PalGlobalMemoryStatusEx(_Inout_ MEMORYSTATUSEX* pBuffer) +{ + assert(pBuffer->dwLength == sizeof(MEMORYSTATUSEX)); + return GlobalMemoryStatusEx(pBuffer); +} + +REDHAWK_PALEXPORT void REDHAWK_PALAPI PalSleep(DWORD milliseconds) +{ + return Sleep(milliseconds); +} + +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI __stdcall PalSwitchToThread() +{ + return SwitchToThread(); +} + +REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalCreateMutexW(_In_opt_ LPSECURITY_ATTRIBUTES pMutexAttributes, BOOL initialOwner, _In_opt_z_ LPCWSTR pName) +{ + return CreateMutexW(pMutexAttributes, initialOwner, pName); +} + +REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalCreateEventW(_In_opt_ LPSECURITY_ATTRIBUTES pEventAttributes, UInt32_BOOL manualReset, UInt32_BOOL initialState, _In_opt_z_ LPCWSTR pName) +{ + return CreateEventW(pEventAttributes, manualReset, initialState, pName); +} + +REDHAWK_PALEXPORT _Success_(return) bool REDHAWK_PALAPI PalGetThreadContext(HANDLE hThread, _Out_ PAL_LIMITED_CONTEXT * pCtx) +{ + CONTEXT win32ctx; + + win32ctx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_EXCEPTION_REQUEST; + + if (!GetThreadContext(hThread, &win32ctx)) + return false; + + // The CONTEXT_SERVICE_ACTIVE and CONTEXT_EXCEPTION_ACTIVE output flags indicate we suspended the thread + // at a point where the kernel cannot guarantee a completely accurate context. We'll fail the request in + // this case (which should force our caller to resume the thread and try again -- since this is a fairly + // narrow window we're highly likely to succeed next time). + if ((win32ctx.ContextFlags & CONTEXT_EXCEPTION_REPORTING) && + (win32ctx.ContextFlags & (CONTEXT_SERVICE_ACTIVE | CONTEXT_EXCEPTION_ACTIVE))) + return false; + +#ifdef _X86_ + pCtx->IP = win32ctx.Eip; + pCtx->Rsp = win32ctx.Esp; + pCtx->Rbp = win32ctx.Ebp; + pCtx->Rdi = win32ctx.Edi; + pCtx->Rsi = win32ctx.Esi; + pCtx->Rax = win32ctx.Eax; + pCtx->Rbx = win32ctx.Ebx; +#elif defined(_AMD64_) + pCtx->IP = win32ctx.Rip; + pCtx->Rsp = win32ctx.Rsp; + pCtx->Rbp = win32ctx.Rbp; + pCtx->Rdi = win32ctx.Rdi; + pCtx->Rsi = win32ctx.Rsi; + pCtx->Rax = win32ctx.Rax; + pCtx->Rbx = win32ctx.Rbx; + pCtx->R12 = win32ctx.R12; + pCtx->R13 = win32ctx.R13; + pCtx->R14 = win32ctx.R14; + pCtx->R15 = win32ctx.R15; +#elif defined(_ARM_) + pCtx->IP = win32ctx.Pc; + pCtx->R0 = win32ctx.R0; + pCtx->R4 = win32ctx.R4; + pCtx->R5 = win32ctx.R5; + pCtx->R6 = win32ctx.R6; + pCtx->R7 = win32ctx.R7; + pCtx->R8 = win32ctx.R8; + pCtx->R9 = win32ctx.R9; + pCtx->R10 = win32ctx.R10; + pCtx->R11 = win32ctx.R11; + pCtx->SP = win32ctx.Sp; + pCtx->LR = win32ctx.Lr; +#else +#error Unsupported platform +#endif + return true; +} + +typedef BOOL(__stdcall *HijackCallback)(HANDLE hThread, _In_ PAL_LIMITED_CONTEXT* pThreadContext, _In_opt_ void* pCallbackContext); + +REDHAWK_PALEXPORT HRESULT REDHAWK_PALAPI PalHijack(HANDLE hThread, _In_ HijackCallback callback, _In_opt_ void* pCallbackContext) +{ + if (hThread == INVALID_HANDLE_VALUE) + { + return E_INVALIDARG; + } + + if (SuspendThread(hThread) == (DWORD)-1) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + + PAL_LIMITED_CONTEXT ctx; + HRESULT result; + if (!PalGetThreadContext(hThread, &ctx)) + { + result = HRESULT_FROM_WIN32(GetLastError()); + } + else + { + result = callback(hThread, &ctx, pCallbackContext) ? S_OK : E_FAIL; + } + + ResumeThread(hThread); + + return result; +} + +typedef UInt32(__stdcall *BackgroundCallback)(_In_opt_ void* pCallbackContext); + +REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalStartBackgroundWork(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext, BOOL highPriority) +{ + HANDLE hThread = CreateThread( + NULL, + 0, + (LPTHREAD_START_ROUTINE)callback, + pCallbackContext, + highPriority ? CREATE_SUSPENDED : 0, + NULL); + + if (hThread == NULL) + return NULL; + + if (highPriority) + { + SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST); + ResumeThread(hThread); + } + + return hThread; +} + +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI PalStartBackgroundGCThread(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext) +{ + return PalStartBackgroundWork(callback, pCallbackContext, FALSE) != NULL; +} + +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI PalStartFinalizerThread(_In_ BackgroundCallback callback, _In_opt_ void* pCallbackContext) +{ + return PalStartBackgroundWork(callback, pCallbackContext, TRUE) != NULL; +} + +REDHAWK_PALEXPORT DWORD REDHAWK_PALAPI PalGetTickCount() +{ +#pragma warning(push) +#pragma warning(disable: 28159) // Consider GetTickCount64 instead + return GetTickCount(); +#pragma warning(pop) +} + +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI PalEventEnabled(REGHANDLE regHandle, _In_ const EVENT_DESCRIPTOR* eventDescriptor) +{ + return EventEnabled(regHandle, eventDescriptor); +} + +REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalCreateFileW(_In_z_ LPCWSTR pFileName, DWORD desiredAccess, DWORD shareMode, _In_opt_ LPSECURITY_ATTRIBUTES pSecurityAttributes, DWORD creationDisposition, DWORD flagsAndAttributes, HANDLE hTemplateFile) +{ + return CreateFileW(pFileName, desiredAccess, shareMode, pSecurityAttributes, creationDisposition, flagsAndAttributes, hTemplateFile); +} + +REDHAWK_PALEXPORT _Success_(return == 0) +UINT REDHAWK_PALAPI PalGetWriteWatch(_In_ DWORD flags, _In_ void* pBaseAddress, _In_ SIZE_T regionSize, _Out_writes_to_opt_(*pCount, *pCount) void** pAddresses, _Inout_opt_ ULONG_PTR* pCount, _Out_opt_ ULONG* pGranularity) +{ + return GetWriteWatch(flags, pBaseAddress, regionSize, pAddresses, pCount, pGranularity); +} + +REDHAWK_PALEXPORT UINT REDHAWK_PALAPI PalResetWriteWatch(_In_ void* pBaseAddress, SIZE_T regionSize) +{ + return ResetWriteWatch(pBaseAddress, regionSize); +} + +REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalCreateLowMemoryNotification() +{ + return CreateMemoryResourceNotification(LowMemoryResourceNotification); +} + +REDHAWK_PALEXPORT HANDLE REDHAWK_PALAPI PalGetModuleHandleFromPointer(_In_ void* pointer) +{ + HMODULE module; + if (!GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)pointer, + &module)) + { + return NULL; + } + + return (HANDLE)module; +} + +REDHAWK_PALEXPORT PVOID REDHAWK_PALAPI PalAddVectoredExceptionHandler(ULONG firstHandler, _In_ PVECTORED_EXCEPTION_HANDLER vectoredHandler) +{ + return AddVectoredExceptionHandler(firstHandler, vectoredHandler); +} + +// +// ----------------------------------------------------------------------------------------------------------- +// +// Some more globally initialized data (in InitializeSubsystems), this time internal and used to cache +// information returned by various GC support routines. +// +static UInt32 g_cLogicalCpus = 0; +static size_t g_cbLargestOnDieCache = 0; +static size_t g_cbLargestOnDieCacheAdjusted = 0; + + + +#if (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) +EXTERN_C DWORD __fastcall getcpuid(DWORD arg, unsigned char result[16]); +EXTERN_C DWORD __fastcall getextcpuid(DWORD arg1, DWORD arg2, unsigned char result[16]); + +void QueryAMDCacheInfo(_Out_ UInt32* pcbCache, _Out_ UInt32* pcbCacheAdjusted) +{ + unsigned char buffer[16]; + + if (getcpuid(0x80000000, buffer) >= 0x80000006) + { + UInt32* pdwBuffer = (UInt32*)buffer; + + getcpuid(0x80000006, buffer); + + UInt32 dwL2CacheBits = pdwBuffer[2]; + UInt32 dwL3CacheBits = pdwBuffer[3]; + + *pcbCache = (size_t)((dwL2CacheBits >> 16) * 1024); // L2 cache size in ECX bits 31-16 + + getcpuid(0x1, buffer); + UInt32 dwBaseFamily = (pdwBuffer[0] & (0xF << 8)) >> 8; + UInt32 dwExtFamily = (pdwBuffer[0] & (0xFF << 20)) >> 20; + UInt32 dwFamily = dwBaseFamily >= 0xF ? dwBaseFamily + dwExtFamily : dwBaseFamily; + + if (dwFamily >= 0x10) + { + BOOL bSkipAMDL3 = FALSE; + + if (dwFamily == 0x10) // are we running on a Barcelona (Family 10h) processor? + { + // check model + UInt32 dwBaseModel = (pdwBuffer[0] & (0xF << 4)) >> 4; + UInt32 dwExtModel = (pdwBuffer[0] & (0xF << 16)) >> 16; + UInt32 dwModel = dwBaseFamily >= 0xF ? (dwExtModel << 4) | dwBaseModel : dwBaseModel; + + switch (dwModel) + { + case 0x2: + // 65nm parts do not benefit from larger Gen0 + bSkipAMDL3 = TRUE; + break; + + case 0x4: + default: + bSkipAMDL3 = FALSE; + } + } + + if (!bSkipAMDL3) + { + // 45nm Greyhound parts (and future parts based on newer northbridge) benefit + // from increased gen0 size, taking L3 into account + getcpuid(0x80000008, buffer); + UInt32 dwNumberOfCores = (pdwBuffer[2] & (0xFF)) + 1; // NC is in ECX bits 7-0 + + UInt32 dwL3CacheSize = (size_t)((dwL3CacheBits >> 18) * 512 * 1024); // L3 size in EDX bits 31-18 * 512KB + // L3 is shared between cores + dwL3CacheSize = dwL3CacheSize / dwNumberOfCores; + *pcbCache += dwL3CacheSize; // due to exclusive caches, add L3 size (possibly zero) to L2 + // L1 is too small to worry about, so ignore it + } + } + } + *pcbCacheAdjusted = *pcbCache; +} + +#ifdef _DEBUG +#define CACHE_WAY_BITS 0xFFC00000 // number of cache WAYS-Associativity is returned in EBX[31:22] (10 bits) using cpuid function 4 +#define CACHE_PARTITION_BITS 0x003FF000 // number of cache Physical Partitions is returned in EBX[21:12] (10 bits) using cpuid function 4 +#define CACHE_LINESIZE_BITS 0x00000FFF // Linesize returned in EBX[11:0] (12 bits) using cpuid function 4 +#define LIMITED_METHOD_CONTRACT + +size_t CLR_GetIntelDeterministicCacheEnum() +{ + LIMITED_METHOD_CONTRACT; + size_t retVal = 0; + unsigned char buffer[16]; + + DWORD maxCpuid = getextcpuid(0, 0, buffer); + + DWORD* dwBuffer = (DWORD*)buffer; + + if ((maxCpuid > 3) && (maxCpuid < 0x80000000)) // Deterministic Cache Enum is Supported + { + DWORD dwCacheWays, dwCachePartitions, dwLineSize, dwSets; + DWORD retEAX = 0; + DWORD loopECX = 0; + size_t maxSize = 0; + size_t curSize = 0; + + // Make First call to getextcpuid with loopECX=0. loopECX provides an index indicating which level to return information about. + // The second parameter is input EAX=4, to specify we want deterministic cache parameter leaf information. + // getextcpuid with EAX=4 should be executed with loopECX = 0,1, ... until retEAX [4:0] contains 00000b, indicating no more + // cache levels are supported. + + getextcpuid(loopECX, 4, buffer); + retEAX = dwBuffer[0]; // get EAX + + int i = 0; + while (retEAX & 0x1f) // Crack cache enums and loop while EAX > 0 + { + + dwCacheWays = (dwBuffer[1] & CACHE_WAY_BITS) >> 22; + dwCachePartitions = (dwBuffer[1] & CACHE_PARTITION_BITS) >> 12; + dwLineSize = dwBuffer[1] & CACHE_LINESIZE_BITS; + dwSets = dwBuffer[2]; // ECX + + curSize = (dwCacheWays + 1)*(dwCachePartitions + 1)*(dwLineSize + 1)*(dwSets + 1); + + if (maxSize < curSize) + maxSize = curSize; + + loopECX++; + getextcpuid(loopECX, 4, buffer); + retEAX = dwBuffer[0]; // get EAX[4:0]; + i++; + if (i > 16) // prevent infinite looping + return 0; + } + retVal = maxSize; + } + + return retVal; +} + +// The following function uses CPUID function 2 with descriptor values to determine the cache size. This requires a-priori +// knowledge of the descriptor values. This works on gallatin and prior processors (already released processors). +// If successful, this function returns the cache size in bytes of the highest level on-die cache. Returns 0 on failure. + +size_t CLR_GetIntelDescriptorValuesCache() +{ + LIMITED_METHOD_CONTRACT; + size_t size = 0; + size_t maxSize = 0; + unsigned char buffer[16]; + + getextcpuid(0, 2, buffer); // call CPUID with EAX function 2H to obtain cache descriptor values + + for (int i = buffer[0]; --i >= 0;) + { + int j; + for (j = 3; j < 16; j += 4) + { + // if the information in a register is marked invalid, set to null descriptors + if (buffer[j] & 0x80) + { + buffer[j - 3] = 0; + buffer[j - 2] = 0; + buffer[j - 1] = 0; + buffer[j - 0] = 0; + } + } + + for (j = 1; j < 16; j++) + { + switch (buffer[j]) // need to add descriptor values for 8M and 12M when they become known + { + case 0x41: + case 0x79: + size = 128 * 1024; + break; + + case 0x42: + case 0x7A: + case 0x82: + size = 256 * 1024; + break; + + case 0x22: + case 0x43: + case 0x7B: + case 0x83: + case 0x86: + size = 512 * 1024; + break; + + case 0x23: + case 0x44: + case 0x7C: + case 0x84: + case 0x87: + size = 1024 * 1024; + break; + + case 0x25: + case 0x45: + case 0x85: + size = 2 * 1024 * 1024; + break; + + case 0x29: + size = 4 * 1024 * 1024; + break; + } + if (maxSize < size) + maxSize = size; + } + + if (i > 0) + getextcpuid(0, 2, buffer); + } + return maxSize; +} + +size_t CLR_GetLargestOnDieCacheSizeX86(UInt32_BOOL bTrueSize) +{ + + static size_t maxSize; + static size_t maxTrueSize; + + if (maxSize) + { + // maxSize and maxTrueSize cached + if (bTrueSize) + { + return maxTrueSize; + } + else + { + return maxSize; + } + } + + __try + { + unsigned char buffer[16]; + DWORD* dwBuffer = (DWORD*)buffer; + + DWORD maxCpuId = getcpuid(0, buffer); + + if (dwBuffer[1] == 'uneG') + { + if (dwBuffer[3] == 'Ieni') + { + if (dwBuffer[2] == 'letn') + { + size_t tempSize = 0; + if (maxCpuId >= 2) // cpuid support for cache size determination is available + { + tempSize = CLR_GetIntelDeterministicCacheEnum(); // try to use use deterministic cache size enumeration + if (!tempSize) + { // deterministic enumeration failed, fallback to legacy enumeration using descriptor values + tempSize = CLR_GetIntelDescriptorValuesCache(); + } + } + + // update maxSize once with final value + maxTrueSize = tempSize; + +#ifdef _WIN64 + if (maxCpuId >= 2) + { + // If we're running on a Prescott or greater core, EM64T tests + // show that starting with a gen0 larger than LLC improves performance. + // Thus, start with a gen0 size that is larger than the cache. The value of + // 3 is a reasonable tradeoff between workingset and performance. + maxSize = maxTrueSize * 3; + } + else +#endif + { + maxSize = maxTrueSize; + } + } + } + } + + if (dwBuffer[1] == 'htuA') { + if (dwBuffer[3] == 'itne') { + if (dwBuffer[2] == 'DMAc') { + + if (getcpuid(0x80000000, buffer) >= 0x80000006) + { + getcpuid(0x80000006, buffer); + + DWORD dwL2CacheBits = dwBuffer[2]; + DWORD dwL3CacheBits = dwBuffer[3]; + + maxTrueSize = (size_t)((dwL2CacheBits >> 16) * 1024); // L2 cache size in ECX bits 31-16 + + getcpuid(0x1, buffer); + DWORD dwBaseFamily = (dwBuffer[0] & (0xF << 8)) >> 8; + DWORD dwExtFamily = (dwBuffer[0] & (0xFF << 20)) >> 20; + DWORD dwFamily = dwBaseFamily >= 0xF ? dwBaseFamily + dwExtFamily : dwBaseFamily; + + if (dwFamily >= 0x10) + { + BOOL bSkipAMDL3 = FALSE; + + if (dwFamily == 0x10) // are we running on a Barcelona (Family 10h) processor? + { + // check model + DWORD dwBaseModel = (dwBuffer[0] & (0xF << 4)) >> 4; + DWORD dwExtModel = (dwBuffer[0] & (0xF << 16)) >> 16; + DWORD dwModel = dwBaseFamily >= 0xF ? (dwExtModel << 4) | dwBaseModel : dwBaseModel; + + switch (dwModel) + { + case 0x2: + // 65nm parts do not benefit from larger Gen0 + bSkipAMDL3 = TRUE; + break; + + case 0x4: + default: + bSkipAMDL3 = FALSE; + } + } + + if (!bSkipAMDL3) + { + // 45nm Greyhound parts (and future parts based on newer northbridge) benefit + // from increased gen0 size, taking L3 into account + getcpuid(0x80000008, buffer); + DWORD dwNumberOfCores = (dwBuffer[2] & (0xFF)) + 1; // NC is in ECX bits 7-0 + + DWORD dwL3CacheSize = (size_t)((dwL3CacheBits >> 18) * 512 * 1024); // L3 size in EDX bits 31-18 * 512KB + // L3 is shared between cores + dwL3CacheSize = dwL3CacheSize / dwNumberOfCores; + maxTrueSize += dwL3CacheSize; // due to exclusive caches, add L3 size (possibly zero) to L2 + // L1 is too small to worry about, so ignore it + } + } + + + maxSize = maxTrueSize; + } + } + } + } + } + __except (1) + { + } + + if (bTrueSize) + return maxTrueSize; + else + return maxSize; +} + +DWORD CLR_GetLogicalCpuCountFromOS(_In_reads_opt_(nEntries) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pslpi, DWORD nEntries); + +// This function returns the number of logical processors on a given physical chip. If it cannot +// determine the number of logical cpus, or the machine is not populated uniformly with the same +// type of processors, this function returns 1. +DWORD CLR_GetLogicalCpuCountX86(_In_reads_opt_(nEntries) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pslpi, DWORD nEntries) +{ + // No CONTRACT possible because GetLogicalCpuCount uses SEH + + static DWORD val = 0; + + // cache value for later re-use + if (val) + { + return val; + } + + DWORD retVal = 1; + + __try + { + unsigned char buffer[16]; + + DWORD maxCpuId = getcpuid(0, buffer); + + if (maxCpuId < 1) + goto lDone; + + DWORD* dwBuffer = (DWORD*)buffer; + + if (dwBuffer[1] == 'uneG') { + if (dwBuffer[3] == 'Ieni') { + if (dwBuffer[2] == 'letn') { // get SMT/multicore enumeration for Intel EM64T + + // TODO: Currently GetLogicalCpuCountFromOS() and GetLogicalCpuCountFallback() are broken on + // multi-core processor, but we never call into those two functions since we don't halve the + // gen0size when it's prescott and above processor. We keep the old version here for earlier + // generation system(Northwood based), perf data suggests on those systems, halve gen0 size + // still boost the performance(ex:Biztalk boosts about 17%). So on earlier systems(Northwood) + // based, we still go ahead and halve gen0 size. The logic in GetLogicalCpuCountFromOS() + // and GetLogicalCpuCountFallback() works fine for those earlier generation systems. + // If it's a Prescott and above processor or Multi-core, perf data suggests not to halve gen0 + // size at all gives us overall better performance. + // This is going to be fixed with a new version in orcas time frame. + + if ((maxCpuId > 3) && (maxCpuId < 0x80000000)) + goto lDone; + + val = CLR_GetLogicalCpuCountFromOS(pslpi, nEntries); //try to obtain HT enumeration from OS API + if (val) + { + retVal = val; // OS API HT enumeration successful, we are Done + goto lDone; + } + + // val = GetLogicalCpuCountFallback(); // OS API failed, Fallback to HT enumeration using CPUID + // if( val ) + // retVal = val; + } + } + } + lDone:; + } + __except (1) + { + } + + if (val == 0) + { + val = retVal; + } + + return retVal; +} + +#endif // _DEBUG +#endif // (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + + +#ifdef _DEBUG +DWORD CLR_GetLogicalCpuCountFromOS(_In_reads_opt_(nEntries) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pslpi, DWORD nEntries) +{ + // No CONTRACT possible because GetLogicalCpuCount uses SEH + + static DWORD val = 0; + DWORD retVal = 0; + + if (pslpi == NULL) + { + // GetLogicalProcessorInformation no supported + goto lDone; + } + + DWORD prevcount = 0; + DWORD count = 1; + + for (DWORD j = 0; j < nEntries; j++) + { + if (pslpi[j].Relationship == RelationProcessorCore) + { + // LTP_PC_SMT indicates HT or SMT + if (pslpi[j].ProcessorCore.Flags == LTP_PC_SMT) + { + SIZE_T pmask = pslpi[j].ProcessorMask; + + // Count the processors in the mask + // + // These are not the fastest bit counters. There may be processor intrinsics + // (which would be best), but there are variants faster than these: + // See http://en.wikipedia.org/wiki/Hamming_weight. + // This is the naive implementation. +#if !_WIN64 + count = (pmask & 0x55555555) + ((pmask >> 1) & 0x55555555); + count = (count & 0x33333333) + ((count >> 2) & 0x33333333); + count = (count & 0x0F0F0F0F) + ((count >> 4) & 0x0F0F0F0F); + count = (count & 0x00FF00FF) + ((count >> 8) & 0x00FF00FF); + count = (count & 0x0000FFFF) + ((count >> 16) & 0x0000FFFF); +#else + pmask = (pmask & 0x5555555555555555ull) + ((pmask >> 1) & 0x5555555555555555ull); + pmask = (pmask & 0x3333333333333333ull) + ((pmask >> 2) & 0x3333333333333333ull); + pmask = (pmask & 0x0f0f0f0f0f0f0f0full) + ((pmask >> 4) & 0x0f0f0f0f0f0f0f0full); + pmask = (pmask & 0x00ff00ff00ff00ffull) + ((pmask >> 8) & 0x00ff00ff00ff00ffull); + pmask = (pmask & 0x0000ffff0000ffffull) + ((pmask >> 16) & 0x0000ffff0000ffffull); + pmask = (pmask & 0x00000000ffffffffull) + ((pmask >> 32) & 0x00000000ffffffffull); + count = static_cast(pmask); +#endif // !_WIN64 else + assert(count > 0); + + if (prevcount) + { + if (count != prevcount) + { + retVal = 1; // masks are not symmetric + goto lDone; + } + } + + prevcount = count; + } + } + } + + retVal = count; + +lDone: + return retVal; +} + +// This function returns the size of highest level cache on the physical chip. If it cannot +// determine the cachesize this function returns 0. +size_t CLR_GetLogicalProcessorCacheSizeFromOS(_In_reads_opt_(nEntries) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pslpi, DWORD nEntries) +{ + size_t cache_size = 0; + + // Try to use GetLogicalProcessorInformation API and get a valid pointer to the SLPI array if successful. Returns NULL + // if API not present or on failure. + + if (pslpi == NULL) + { + // GetLogicalProcessorInformation not supported or failed. + goto Exit; + } + + // Crack the information. Iterate through all the SLPI array entries for all processors in system. + // Will return the greatest of all the processor cache sizes or zero + + size_t last_cache_size = 0; + + for (DWORD i = 0; i < nEntries; i++) + { + if (pslpi[i].Relationship == RelationCache) + { + last_cache_size = max(last_cache_size, pslpi[i].Cache.Size); + } + } + cache_size = last_cache_size; +Exit: + + return cache_size; +} + +DWORD CLR_GetLogicalCpuCount(_In_reads_opt_(nEntries) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pslpi, DWORD nEntries) +{ +#if (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + return CLR_GetLogicalCpuCountX86(pslpi, nEntries); +#else + return CLR_GetLogicalCpuCountFromOS(pslpi, nEntries); +#endif +} + +size_t CLR_GetLargestOnDieCacheSize(UInt32_BOOL bTrueSize, _In_reads_opt_(nEntries) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pslpi, DWORD nEntries) +{ +#if (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + return CLR_GetLargestOnDieCacheSizeX86(bTrueSize); +#else + return CLR_GetLogicalProcessorCacheSizeFromOS(pslpi, nEntries); +#endif +} +#endif // _DEBUG + + + +enum CpuVendor +{ + CpuUnknown, + CpuIntel, + CpuAMD, +}; + +CpuVendor GetCpuVendor(_Out_ UInt32* puMaxCpuId) +{ +#if (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + unsigned char buffer[16]; + *puMaxCpuId = getcpuid(0, buffer); + + UInt32* pdwBuffer = (UInt32*)buffer; + + if (pdwBuffer[1] == 'uneG' + && pdwBuffer[3] == 'Ieni' + && pdwBuffer[2] == 'letn') + { + return CpuIntel; + } + else if (pdwBuffer[1] == 'htuA' + && pdwBuffer[3] == 'itne' + && pdwBuffer[2] == 'DMAc') + { + return CpuAMD; + } +#else + *puMaxCpuId = 0; +#endif + return CpuUnknown; +} + +// Count set bits in a bitfield. +UInt32 CountBits(size_t bfBitfield) +{ + UInt32 cBits = 0; + + // This is not the fastest algorithm possible but it's simple and the performance is not critical. + for (UInt32 i = 0; i < (sizeof(size_t) * 8); i++) + { + cBits += (bfBitfield & 1) ? 1 : 0; + bfBitfield >>= 1; + } + + return cBits; +} + +// +// Enable TRACE_CACHE_TOPOLOGY to get a dump of the info provided by the OS as well as a comparison of the +// 'answers' between the current implementation and the CLR implementation. +// +//#define TRACE_CACHE_TOPOLOGY +#ifdef _DEBUG +void DumpCacheTopology(_In_reads_(cRecords) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pProcInfos, UInt32 cRecords) +{ + PalPrintf("----------------\n"); + for (UInt32 i = 0; i < cRecords; i++) + { + switch (pProcInfos[i].Relationship) + { + case RelationProcessorCore: + PalPrintf(" [%2d] Core: %d threads 0x%04x mask, flags = %d\n", + i, CountBits(pProcInfos[i].ProcessorMask), pProcInfos[i].ProcessorMask, + pProcInfos[i].ProcessorCore.Flags); + break; + + case RelationCache: + char* pszCacheType; + switch (pProcInfos[i].Cache.Type) { + case CacheUnified: pszCacheType = "[Unified]"; break; + case CacheInstruction: pszCacheType = "[Instr ]"; break; + case CacheData: pszCacheType = "[Data ]"; break; + case CacheTrace: pszCacheType = "[Trace ]"; break; + default: pszCacheType = "[Unk ]"; break; + } + PalPrintf(" [%2d] Cache: %s 0x%08x bytes 0x%04x mask\n", i, pszCacheType, + pProcInfos[i].Cache.Size, pProcInfos[i].ProcessorMask); + break; + + case RelationNumaNode: + PalPrintf(" [%2d] NumaNode: #%02d 0x%04x mask\n", + i, pProcInfos[i].NumaNode.NodeNumber, pProcInfos[i].ProcessorMask); + break; + case RelationProcessorPackage: + PalPrintf(" [%2d] Package: 0x%04x mask\n", + i, pProcInfos[i].ProcessorMask); + break; + case RelationAll: + case RelationGroup: + default: + PalPrintf(" [%2d] unknown: %d\n", i, pProcInfos[i].Relationship); + break; + } + } + PalPrintf("----------------\n"); +} +void DumpCacheTopologyResults(UInt32 maxCpuId, CpuVendor cpuVendor, _In_reads_(cRecords) SYSTEM_LOGICAL_PROCESSOR_INFORMATION * pProcInfos, UInt32 cRecords) +{ + DumpCacheTopology(pProcInfos, cRecords); + PalPrintf("maxCpuId: %d, %s\n", maxCpuId, (cpuVendor == CpuIntel) ? "CpuIntel" : ((cpuVendor == CpuAMD) ? "CpuAMD" : "CpuUnknown")); + PalPrintf(" g_cLogicalCpus: %d %d :CLR_GetLogicalCpuCount\n", g_cLogicalCpus, CLR_GetLogicalCpuCount(pProcInfos, cRecords)); + PalPrintf(" g_cbLargestOnDieCache: 0x%08x 0x%08x :CLR_LargestOnDieCache(TRUE)\n", g_cbLargestOnDieCache, CLR_GetLargestOnDieCacheSize(TRUE, pProcInfos, cRecords)); + PalPrintf("g_cbLargestOnDieCacheAdjusted: 0x%08x 0x%08x :CLR_LargestOnDieCache(FALSE)\n", g_cbLargestOnDieCacheAdjusted, CLR_GetLargestOnDieCacheSize(FALSE, pProcInfos, cRecords)); +} +#endif // _DEBUG + +// Method used to initialize the above values. +bool PalQueryProcessorTopology() +{ + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *pProcInfos = NULL; + DWORD cbBuffer = 0; + bool fError = false; + + for (;;) + { + // Ask for processor information with an insufficient buffer initially. The function will tell us how + // much memory we need and we'll try again. + if (!GetLogicalProcessorInformation(pProcInfos, &cbBuffer)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + if (pProcInfos) + delete[](UInt8*)pProcInfos; + pProcInfos = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION*)new UInt8[cbBuffer]; + if (pProcInfos == NULL) + { + // Ran out of memory. + fError = true; + break; + } + } + else + { + // Unexpected error from GetLogicalProcessorInformation(). + fError = true; + break; + } + } + else + { + // Successfully read processor information, stop looping. + break; + } + } + + // If there was no error retrieving the data parse the result. GetLogicalProcessorInformation() returns an + // array of structures each of which describes some attribute of a given group of logical processors. + // Fields in the structure describe which processors and which attributes are being described and the + // structures come in no particular order. Therefore we just iterate over all of them accumulating the + // data we're interested in as we go. + if (!fError && pProcInfos != NULL) + { + // Some explanation of the following logic is required. The GC queries information via two APIs: + // 1) GetLogicalCpuCount() + // 2) GetLargestOnDieCacheSize() + // + // These were once unambiguous queries; logical CPUs only existed when a physical CPU supported + // threading (e.g. Intel's HyperThreading technology) and caches were always shared across an entire + // physical processor. + // + // Unfortunately for us actual processor topologies are getting ever more complex (and divergent even + // between otherwise near-identical architectures such as Intel and AMD). A single physical processor + // (or package, the thing that fits in a socket on the motherboard) can now have multiple classes of + // logical processors within it with differing relationships between the other logical processors + // (e.g. which share functional units or caches). It's technically feasible to build systems with + // non-symmetric topologies as well (where the number of logical processors or cache differs between + // physical processors for instance). + // + // The GetLogicalProcessorInformation() reflects this in the potential complexity of its output. For + // large-multi CPU systems it can generate quite a few output records effectively drawing a tree of + // logical processors and their relationships within cores and packages and to various levels of + // cache. + // + // Out of this complexity we have to distill the simple answers required above. It may well prove true + // in the future that we will have to ask more complex questions, but until then this function will + // utilize the following semantics for each of the queries: + // 1) We will report logical processors as the average number of threads per core. (For the likely + // case, a symmetric system, this average will be the exact number of threads per core). + // 2) We will report the largest cache on-die as the average largest cache per-core. + // + // We will calculate the first value by counting the number of core records returned and the number of + // threads running on those cores (each core record supplies a bitmask of processors running on that + // core and by definition each of those processor sets must be disjoint, so we can simply accumulate a + // count of processors seen for each core so far). For now we will count all processors on a core as a + // thread (even if the HT/SMT flag is not set for the core) until we have data that suggests we should + // treat non-HT processors as cores in their own right. We can then simply divide the thread total by + // the core total to get a thread per core average. + // + // The second is harder since we have to discard caches that are superceded by a larger cache + // servicing the same logical processor. For instance, on a typical Intel system we wish to sum the + // sizes of all the L2 caches but ignore all the L1 caches. Since performance is not a huge issue here + // (this is a one time operation and we cache the results) we'll use a linear algorithm that, when + // presented with a cache information record, re-scans all the records for another cache entry which + // is of larger size and has at least one logical processor in common. If found, the current cache + // record can be ignored. + // + // Once we have to total sizes of all the largest level caches on the system we can divide it by the + // previously computed total cores to get average largest cache size per core. + + // Count info records returned by GetLogicalProcessorInformation(). + UInt32 cRecords = cbBuffer / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); + + UInt32 maxCpuId; + CpuVendor cpuVendor = GetCpuVendor(&maxCpuId); + + bool isAsymmetric = false; + UInt32 cLogicalCpus = 0; + UInt32 cbCache = 0; + UInt32 cbCacheAdjusted = 0; + + for (UInt32 i = 0; i < cRecords; i++) + { + switch (pProcInfos[i].Relationship) + { + case RelationProcessorCore: + if (pProcInfos[i].ProcessorCore.Flags == LTP_PC_SMT) + { + UInt32 thisCount = CountBits(pProcInfos[i].ProcessorMask); + if (!cLogicalCpus) + cLogicalCpus = thisCount; + else if (thisCount != cLogicalCpus) + isAsymmetric = true; + } + break; + + case RelationCache: + cbCache = max(cbCache, pProcInfos[i].Cache.Size); + break; + + default: + break; + } + } + + cbCacheAdjusted = cbCache; + if (cLogicalCpus == 0) + cLogicalCpus = 1; + +#if (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + // Apply some experimentally-derived policy to the number of logical CPUs in the same way CLR does. + if ((maxCpuId < 1) + || (cpuVendor != CpuIntel) + || ((maxCpuId > 3) && (maxCpuId < 0x80000000)) // This is a strange one. + || isAsymmetric) + { + cLogicalCpus = 1; + } + + // Apply some experimentally-derived policy to the cache size in the same way CLR does. + if (cpuVendor == CpuIntel) + { +#ifdef _WIN64 + if (maxCpuId >= 2) + { + // If we're running on a Prescott or greater core, EM64T tests + // show that starting with a gen0 larger than LLC improves performance. + // Thus, start with a gen0 size that is larger than the cache. The value of + // 3 is a reasonable tradeoff between workingset and performance. + cbCacheAdjusted = cbCache * 3; + } +#endif // _WIN64 + } + else if (cpuVendor == CpuAMD) + { + QueryAMDCacheInfo(&cbCache, &cbCacheAdjusted); + } +#else // (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + cpuVendor; // avoid unused variable warnings. + maxCpuId; +#endif // (defined(TARGET_AMD64) || defined (TARGET_X86)) && !defined(USE_PORTABLE_HELPERS) + + g_cLogicalCpus = cLogicalCpus; + g_cbLargestOnDieCache = cbCache; + g_cbLargestOnDieCacheAdjusted = cbCacheAdjusted; + +#ifdef _DEBUG +#ifdef TRACE_CACHE_TOPOLOGY + DumpCacheTopologyResults(maxCpuId, cpuVendor, pProcInfos, cRecords); +#endif // TRACE_CACHE_TOPOLOGY + if ((CLR_GetLargestOnDieCacheSize(TRUE, pProcInfos, cRecords) != g_cbLargestOnDieCache) || + (CLR_GetLargestOnDieCacheSize(FALSE, pProcInfos, cRecords) != g_cbLargestOnDieCacheAdjusted) || + (CLR_GetLogicalCpuCount(pProcInfos, cRecords) != g_cLogicalCpus)) + { + DumpCacheTopologyResults(maxCpuId, cpuVendor, pProcInfos, cRecords); + assert(!"QueryProcessorTopology doesn't match CLR's results. See stdout for more info."); + } +#endif // TRACE_CACHE_TOPOLOGY + } + + if (pProcInfos) + delete[](UInt8*)pProcInfos; + + return !fError; +} + +// Functions called by the GC to obtain our cached values for number of logical processors and cache size. +REDHAWK_PALEXPORT DWORD REDHAWK_PALAPI PalGetLogicalCpuCount() +{ + return g_cLogicalCpus; +} + +REDHAWK_PALEXPORT size_t REDHAWK_PALAPI PalGetLargestOnDieCacheSize(UInt32_BOOL bTrueSize) +{ + return bTrueSize ? g_cbLargestOnDieCache + : g_cbLargestOnDieCacheAdjusted; +} + +REDHAWK_PALEXPORT _Ret_maybenull_ _Post_writable_byte_size_(size) void* REDHAWK_PALAPI PalVirtualAlloc(_In_opt_ void* pAddress, SIZE_T size, DWORD allocationType, DWORD protect) +{ + return VirtualAlloc(pAddress, size, allocationType, protect); +} + +#pragma warning (push) +#pragma warning (disable:28160) // warnings about invalid potential parameter combinations that would cause VirtualFree to fail - those are asserted for below +REDHAWK_PALEXPORT BOOL REDHAWK_PALAPI PalVirtualFree(_In_ void* pAddress, SIZE_T size, DWORD freeType) +{ + assert(((freeType & MEM_RELEASE) != MEM_RELEASE) || size == 0); + assert((freeType & (MEM_RELEASE | MEM_DECOMMIT)) != (MEM_RELEASE | MEM_DECOMMIT)); + assert(freeType != 0); + + return VirtualFree(pAddress, size, freeType); +} +#pragma warning (pop) + +REDHAWK_PALEXPORT _Ret_maybenull_ void* REDHAWK_PALAPI PalSetWerDataBuffer(_In_ void* pNewBuffer) +{ + static void* pBuffer; + return InterlockedExchangePointer(&pBuffer, pNewBuffer); +} + + + + diff --git a/src/Native/Runtime/Profiling.cpp b/src/Native/Runtime/Profiling.cpp new file mode 100644 index 00000000000..be8e23ebf1e --- /dev/null +++ b/src/Native/Runtime/Profiling.cpp @@ -0,0 +1,148 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "slist.h" +#include "holder.h" +#include "crst.h" +#include "rhbinder.h" // for GenericInstanceDesc +#include "rwlock.h" +#include "runtimeinstance.h" +#include "gcrhinterface.h" +#include "module.h" +#else +#include "rhbinder.h" +#include "runtimeinstance.h" +#include "gcrhinterface.h" +#include "module.h" +#endif + +// Macro nonsense to get around limitations of the C++ preprocessor. +#define MAKE_WIDE_STRING(_str) L ## _str +#define WIDE_STRING(_str) MAKE_WIDE_STRING(_str) + +#define MAIN_RH_MODULE_NAME_W WIDE_STRING(RH_BASE_NAME) + + + +#ifdef FEATURE_PROFILING +#ifndef APP_LOCAL_RUNTIME //need to sort out how to get this thread started, where to log, etc., without violating the WACK +UInt32 __stdcall ProfileThread(void *pv) +{ + RuntimeInstance *runtimeInstance = (RuntimeInstance *)pv; + for (;;) + { + PalSleep(10*1000); + runtimeInstance->WriteProfileInfo(); + } +} +#endif + +void RuntimeInstance::InitProfiling(ModuleHeader *pModuleHeader) +{ +#ifndef APP_LOCAL_RUNTIME //need to sort out how to get this thread started, where to log, etc., without violating the WACK + if (!m_fProfileThreadCreated && pModuleHeader->GetProfilingEntries() != NULL) + { + // this module has profile data, and we don't have a profile-writing thread yet + // so let's create one + UInt32 threadId; + PalCreateThread(NULL, 4096, ProfileThread, this, 0, &threadId); + + m_fProfileThreadCreated = true; + } +#endif +} + + +void RuntimeInstance::WriteProfileInfo() +{ +#ifndef APP_LOCAL_RUNTIME //need to sort out how to get this thread started, where to log, etc., without violating the WACK + FOREACH_MODULE(pModule) + { + PTR_ModuleHeader pModuleHeader = pModule->GetModuleHeader(); + if (pModuleHeader->GetProfilingEntries() != NULL) + { + // our general error handling strategy is just to give up writing the profile + // if we encounter any errors or the names get insanely long + WCHAR *wzModuleFileName; + const size_t MAX_PATH = 260; + size_t moduleFileNameLength = PalGetModuleFileName(&wzModuleFileName, pModule->GetOsModuleHandle()); + if (moduleFileNameLength >= MAX_PATH) + continue; + const UInt32 BUFFER_SIZE = 512; + WCHAR profileName[BUFFER_SIZE]; + WCHAR *basicName = wcsrchr(wzModuleFileName, L'\\'); + if (basicName == NULL) + basicName = wzModuleFileName; + else + basicName += 1; // skip past the '\' + size_t basicNameLength = wcslen(basicName); + size_t dirNameLength = PalGetEnvironmentVariableW(L"LOCALAPPDATA", profileName, MAX_PATH); + + // make sure the names are not so long as to cause trouble + const size_t MAX_SAFE_LENGTH = MAX_PATH - 50; + if ( basicNameLength >= MAX_SAFE_LENGTH + || dirNameLength >= MAX_SAFE_LENGTH + || basicNameLength + dirNameLength >= MAX_SAFE_LENGTH) + { + continue; + } + + // make sure %LOCALAPPDATA%\Microsoft\mrt100\ProfileData exists + wcscat_s(profileName, L"\\Microsoft"); + if (!PalCreateDirectoryW(profileName, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) + continue; + + wcscat_s(profileName, L"\\"); + wcscat_s(profileName, MAIN_RH_MODULE_NAME_W); + if (!PalCreateDirectoryW(profileName, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) + continue; + + wcscat_s(profileName, L"\\ProfileData"); + if (!PalCreateDirectoryW(profileName, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) + continue; + + // final filename is %LOCALAPPDATA%\Microsoft\mrt100\ProfileData\.profile + wcscat_s(profileName, L"\\"); + wcscat_s(profileName, basicName); + wcscat_s(profileName, L".profile"); + HANDLE fileHandle = PalCreateFileW(profileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + if (fileHandle == INVALID_HANDLE_VALUE) + { + continue; + } + else + { + ProfilingEntry *profilingEntry = (ProfilingEntry *)pModuleHeader->GetProfilingEntries(); + ProfilingEntry *profilingEntryLast = profilingEntry + pModuleHeader->CountOfProfilingEntries; + for( ; profilingEntry < profilingEntryLast; profilingEntry++) + { + if (profilingEntry->m_count != 0) + { + UInt32 bytesWritten = 0; + if (!PalWriteFile(fileHandle, profilingEntry, sizeof(ProfilingEntry), &bytesWritten, NULL)) + break; + } + } + PalCloseHandle(fileHandle); + } + } + } + END_FOREACH_MODULE; +#endif //!APP_LOCAL_RUNTIME +} +#endif // FEATURE_PROFILING + diff --git a/src/Native/Runtime/RHCodeMan.cpp b/src/Native/Runtime/RHCodeMan.cpp new file mode 100644 index 00000000000..ced11157717 --- /dev/null +++ b/src/Native/Runtime/RHCodeMan.cpp @@ -0,0 +1,2191 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "commonmacros.h" +#include "daccess.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "commonmacros.inl" +#include "regdisplay.h" +#include "targetptrs.h" +#include "eetype.h" +#include "objectlayout.h" +#include "varint.h" +#endif + +#include "gcinfo.h" +#include "rhcodeman.h" + +#include "ICodeManager.h" + + +// Ensure that EEMethodInfo fits into the space reserved by MethodInfo +STATIC_ASSERT(sizeof(EEMethodInfo) <= sizeof(MethodInfo)); + +EEMethodInfo * GetEEMethodInfo(MethodInfo * pMethodInfo) +{ + return (EEMethodInfo *)pMethodInfo; +} + +inline void ReportObject(GCEnumContext * hCallback, PTR_PTR_Object p, UInt32 flags) +{ + (hCallback->pCallback)(hCallback, (PTR_PTR_VOID)p, flags); +} + +// +// This template is used to map from the CalleeSavedRegNum enum to the correct field in the REGDISPLAY struct. +// It should compile away to simply an inlined field access. Since we intentionally have conditionals that +// are constant at compile-time, we need to disable the level-4 warning related to that. +// +#ifdef TARGET_ARM + +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant +template +PTR_PTR_Object GetRegObjectAddr(REGDISPLAY * pContext) +{ + switch (regNum) + { + case CSR_NUM_R4: return (PTR_PTR_Object)pContext->pR4; + case CSR_NUM_R5: return (PTR_PTR_Object)pContext->pR5; + case CSR_NUM_R6: return (PTR_PTR_Object)pContext->pR6; + case CSR_NUM_R7: return (PTR_PTR_Object)pContext->pR7; + case CSR_NUM_R8: return (PTR_PTR_Object)pContext->pR8; + case CSR_NUM_R9: return (PTR_PTR_Object)pContext->pR9; + case CSR_NUM_R10: return (PTR_PTR_Object)pContext->pR10; + case CSR_NUM_R11: return (PTR_PTR_Object)pContext->pR11; + // NOTE: LR is omitted because it may not be live except as a 'scratch' reg + } + UNREACHABLE_MSG("unexpected CalleeSavedRegNum"); +} +#pragma warning(pop) + +PTR_PTR_Object GetRegObjectAddr(CalleeSavedRegNum regNum, REGDISPLAY * pContext) +{ + switch (regNum) + { + case CSR_NUM_R4: return (PTR_PTR_Object)pContext->pR4; + case CSR_NUM_R5: return (PTR_PTR_Object)pContext->pR5; + case CSR_NUM_R6: return (PTR_PTR_Object)pContext->pR6; + case CSR_NUM_R7: return (PTR_PTR_Object)pContext->pR7; + case CSR_NUM_R8: return (PTR_PTR_Object)pContext->pR8; + case CSR_NUM_R9: return (PTR_PTR_Object)pContext->pR9; + case CSR_NUM_R10: return (PTR_PTR_Object)pContext->pR10; + case CSR_NUM_R11: return (PTR_PTR_Object)pContext->pR11; + // NOTE: LR is omitted because it may not be live except as a 'scratch' reg + } + UNREACHABLE_MSG("unexpected CalleeSavedRegNum"); +} + +PTR_PTR_Object GetScratchRegObjectAddr(ScratchRegNum regNum, REGDISPLAY * pContext) +{ + switch (regNum) + { + case SR_NUM_R0: return (PTR_PTR_Object)pContext->pR0; + case SR_NUM_R1: return (PTR_PTR_Object)pContext->pR1; + case SR_NUM_R2: return (PTR_PTR_Object)pContext->pR2; + case SR_NUM_R3: return (PTR_PTR_Object)pContext->pR3; + case SR_NUM_R12: return (PTR_PTR_Object)pContext->pR12; + case SR_NUM_LR: return (PTR_PTR_Object)pContext->pLR; + } + UNREACHABLE_MSG("unexpected ScratchRegNum"); +} + +void ReportRegisterSet(UInt8 regSet, REGDISPLAY * pContext, GCEnumContext * hCallback) +{ + // 2. 00lRRRRR - normal "register set" encoding, pinned and interior attributes both false + // a. l - this is the last descriptor + // b. RRRRR - this is the register mask for { r4, r5, r6, r7, r8 } + + if (regSet & CSR_MASK_R4) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_R5) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_R6) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_R7) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_R8) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } +} + + + +#else // TARGET_ARM + +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant +template +PTR_PTR_Object GetRegObjectAddr(REGDISPLAY * pContext) +{ + switch (regNum) + { + case CSR_NUM_RBX: return (PTR_PTR_Object)pContext->pRbx; + case CSR_NUM_RSI: return (PTR_PTR_Object)pContext->pRsi; + case CSR_NUM_RDI: return (PTR_PTR_Object)pContext->pRdi; + case CSR_NUM_RBP: return (PTR_PTR_Object)pContext->pRbp; +#ifdef TARGET_AMD64 + case CSR_NUM_R12: return (PTR_PTR_Object)pContext->pR12; + case CSR_NUM_R13: return (PTR_PTR_Object)pContext->pR13; + case CSR_NUM_R14: return (PTR_PTR_Object)pContext->pR14; + case CSR_NUM_R15: return (PTR_PTR_Object)pContext->pR15; +#endif // TARGET_AMD64 + } + UNREACHABLE_MSG("unexpected CalleeSavedRegNum"); +} +#pragma warning(pop) + +PTR_PTR_Object GetRegObjectAddr(CalleeSavedRegNum regNum, REGDISPLAY * pContext) +{ + switch (regNum) + { + case CSR_NUM_RBX: return (PTR_PTR_Object)pContext->pRbx; + case CSR_NUM_RSI: return (PTR_PTR_Object)pContext->pRsi; + case CSR_NUM_RDI: return (PTR_PTR_Object)pContext->pRdi; + case CSR_NUM_RBP: return (PTR_PTR_Object)pContext->pRbp; +#ifdef TARGET_AMD64 + case CSR_NUM_R12: return (PTR_PTR_Object)pContext->pR12; + case CSR_NUM_R13: return (PTR_PTR_Object)pContext->pR13; + case CSR_NUM_R14: return (PTR_PTR_Object)pContext->pR14; + case CSR_NUM_R15: return (PTR_PTR_Object)pContext->pR15; +#endif // TARGET_AMD64 + } + UNREACHABLE_MSG("unexpected CalleeSavedRegNum"); +} + +PTR_PTR_Object GetScratchRegObjectAddr(ScratchRegNum regNum, REGDISPLAY * pContext) +{ + switch (regNum) + { + case SR_NUM_RAX: return (PTR_PTR_Object)pContext->pRax; + case SR_NUM_RCX: return (PTR_PTR_Object)pContext->pRcx; + case SR_NUM_RDX: return (PTR_PTR_Object)pContext->pRdx; +#ifdef TARGET_AMD64 + case SR_NUM_R8 : return (PTR_PTR_Object)pContext->pR8; + case SR_NUM_R9 : return (PTR_PTR_Object)pContext->pR9; + case SR_NUM_R10: return (PTR_PTR_Object)pContext->pR10; + case SR_NUM_R11: return (PTR_PTR_Object)pContext->pR11; +#endif // TARGET_AMD64 + } + UNREACHABLE_MSG("unexpected ScratchRegNum"); +} + +void ReportRegisterSet(UInt8 regSet, REGDISPLAY * pContext, GCEnumContext * hCallback) +{ + // 2. 00lRRRRR - normal "register set" encoding, pinned and interior attributes both false + // a. l - this is the last descriptor + // b. RRRRR - this is the register mask for { rbx, rsi, rdi, rbp, r12 } + + if (regSet & CSR_MASK_RBX) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_RSI) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_RDI) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } + if (regSet & CSR_MASK_RBP) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } +#ifdef TARGET_AMD64 + if (regSet & CSR_MASK_R12) { ReportObject(hCallback, GetRegObjectAddr(pContext), 0); } +#endif +} + +#endif // TARGET_ARM + +void ReportRegister(UInt8 regEnc, REGDISPLAY * pContext, GCEnumContext * hCallback) +{ + // 3. 01liprrr - more general register encoding with pinned and interior attributes + // a. l - last descriptor + // b. i - interior + // c. p - pinned + // d. rrr - register number { rbx, rsi, rdi, rbp, r12, r13, r14, r15 }, ARM = { r4-r11 } + + UInt32 flags = 0; + if (regEnc & 0x08) { flags |= GC_CALL_PINNED; } + if (regEnc & 0x10) { flags |= GC_CALL_INTERIOR; } + + PTR_PTR_Object pRoot = GetRegObjectAddr((CalleeSavedRegNum)(regEnc & 0x07), pContext); + ReportObject(hCallback, pRoot, flags); +} + +void ReportLocalSlot(UInt32 slotNum, REGDISPLAY * pContext, GCEnumContext * hCallback, GCInfoHeader * pHeader) +{ + // In order to map from a 'local slot' to a frame pointer offset, we need to consult the GCInfoHeader of + // the main code body, but all we have is the GCInfoHeader of the funclet. So, for now, this is + // disallowed. A larger encoding must be used. + ASSERT_MSG(!pHeader->IsFunclet(), "A 'local slot' encoding should not be used in a funclet."); + + if (pHeader->HasFramePointer()) + { + Int32 rbpOffset; +#ifdef TARGET_ARM + // ARM places the FP at the top of the locals area. + rbpOffset = pHeader->GetFrameSize() - ((slotNum + 1) * sizeof(void *)); +#else +# ifdef TARGET_AMD64 + if (pHeader->GetFramePointerOffset() != 0) + rbpOffset = (slotNum * sizeof(void *)); + else +# endif // TARGET_AMD64 + rbpOffset = -pHeader->GetPreservedRegsSaveSize() - (slotNum * sizeof(void *)); +#endif + PTR_PTR_Object pRoot = (PTR_PTR_Object)(pContext->GetFP() + rbpOffset); + ReportObject(hCallback, pRoot, 0); + } + else + { +#ifdef TARGET_X86 + // @TODO: X86: need to pass in current stack level + UNREACHABLE_MSG("NYI - ESP frames"); +#endif // TARGET_X86 + + Int32 rspOffset = pHeader->GetFrameSize() - ((slotNum + 1) * sizeof(void *)); + PTR_PTR_Object pRoot = (PTR_PTR_Object)(pContext->GetSP() + rspOffset); + ReportObject(hCallback, pRoot, 0); + } +} + +void ReportStackSlot(bool framePointerBased, Int32 offset, UInt32 gcFlags, REGDISPLAY * pContext, + GCEnumContext * hCallback, bool hasDynamicAlignment) +{ + UIntNative basePointer; + if (framePointerBased) + { +#ifdef TARGET_X86 + if (hasDynamicAlignment && offset >= 0) + basePointer = pContext->GetPP(); + else +#else + // avoid warning about unused parameter + hasDynamicAlignment; +#endif // TARGET_X86 + basePointer = pContext->GetFP(); + } + else + { + basePointer = pContext->GetSP(); + } + PTR_PTR_Object pRoot = (PTR_PTR_Object)(basePointer + offset); + ReportObject(hCallback, pRoot, gcFlags); +} + +void ReportLocalSlots(UInt8 localsEnc, REGDISPLAY * pContext, GCEnumContext * hCallback, GCInfoHeader * pHeader) +{ + if (localsEnc & 0x10) + { + // 4. 10l1SSSS - "local stack slot set" encoding, pinned and interior attributes both false + // a. l - last descriptor + // b. SSSS - set of "local slots" #0 - #3 - local slot 0 is at offset -8 from the last pushed + // callee saved register, local slot 1 is at offset - 16, etc - in other words, these are the + // slots normally used for locals + if (localsEnc & 0x01) { ReportLocalSlot(0, pContext, hCallback, pHeader); } + if (localsEnc & 0x02) { ReportLocalSlot(1, pContext, hCallback, pHeader); } + if (localsEnc & 0x04) { ReportLocalSlot(2, pContext, hCallback, pHeader); } + if (localsEnc & 0x08) { ReportLocalSlot(3, pContext, hCallback, pHeader); } + } + else + { + // 5. 10l0ssss - "local slot" encoding, pinned and interior attributes are both false + // a. l - last descriptor + // b. ssss - "local slot" #4 - #19 + UInt32 localNum = (localsEnc & 0xF) + 4; + ReportLocalSlot(localNum, pContext, hCallback, pHeader); + } +} + +void ReportStackSlots(UInt8 firstEncByte, REGDISPLAY * pContext, GCEnumContext * hCallback, PTR_UInt8 & pCursor, bool hasDynamicAlignment) +{ + // 6. 11lipfsm {offset} [mask] - [multiple] stack slot encoding + // a. l - last descriptor + // b. i - interior attribute + // c. p - pinned attribute + // d. f - 1: frame pointer relative, 0: sp relative + // e. s - offset sign + // f. m - mask follows + // g. offset - variable length unsigned integer + // h. mask - variable length unsigned integer (only present if m-bit is 1) - this can describe + // multiple stack locations with the same attributes. E.g., if you want to describe stack + // locations 0x20, 0x28, 0x38, you would give a (starting) offset of 0x20 and a mask of + // 000000101 = 0x05. Up to 33 stack locations can be described. + + UInt32 flags = 0; + if (firstEncByte & 0x08) { flags |= GC_CALL_PINNED; } + if (firstEncByte & 0x10) { flags |= GC_CALL_INTERIOR; } + + bool framePointerBased = (firstEncByte & 0x04); + bool isNegative = (firstEncByte & 0x02); + bool hasMask = (firstEncByte & 0x01); + + Int32 offset = (Int32) VarInt::ReadUnsigned(pCursor); + ASSERT(offset >= 0); + + ReportStackSlot(framePointerBased, (isNegative ? -offset : offset), flags, + pContext, hCallback, hasDynamicAlignment); + + if (hasMask) + { + UInt32 mask = VarInt::ReadUnsigned(pCursor); + while (mask != 0) + { + offset += sizeof(void *); + if (mask & 0x01) + { + ReportStackSlot(framePointerBased, (isNegative ? -offset : offset), flags, + pContext, hCallback, hasDynamicAlignment); + } + mask >>= 1; + } + } +} + +void ReportScratchRegs(UInt8 firstEncByte, REGDISPLAY * pContext, GCEnumContext * hCallback, PTR_UInt8 & pCursor) +{ + // 7. 11lip010 0RRRRRRR [0IIIIIII] [0PPPPPPP] - live scratch reg reporting, this uses the SP-xxx encoding + // from #6 since we cannot have stack locations at negative + // offsets from SP. + // a. l - last descriptor + // b. i - interior byte present + // c. p - pinned byte present + // d. RRRRRRR - scratch register mask for { rax, rcx, rdx, r8, r9, r10, r11 }, ARM = { r0-r3, r12 } + // e. IIIIIII - interior scratch register mask for { rax, rcx, rdx, r8, r9, r10, r11 } iff 'i' is 1 + // f. PPPPPPP - pinned scratch register mask for { rax, rcx, rdx, r8, r9, r10, r11 } iff 'p' is 1 + // + + UInt8 regs = *pCursor++; + UInt8 byrefRegs = (firstEncByte & 0x10) ? *pCursor++ : 0; + UInt8 pinnedRegs = (firstEncByte & 0x08) ? *pCursor++ : 0; + + for (UInt32 reg = 0; reg < RBM_SCRATCH_REG_COUNT; reg++) + { + UInt8 regMask = (1 << reg); + + if (regs & regMask) + { + UInt32 flags = 0; + if (pinnedRegs & regMask) { flags |= GC_CALL_PINNED; } + if (byrefRegs & regMask) { flags |= GC_CALL_INTERIOR; } + + PTR_PTR_Object pRoot = GetScratchRegObjectAddr((ScratchRegNum)reg, pContext); + if (pRoot != NULL) + ReportObject(hCallback, pRoot, flags); + } + } +} + +// Enumerate all live object references in that function using the virtual register set. Same reference +// location cannot be enumerated multiple times (but all differenct references pointing to the same object +// have to be individually enumerated). +// Returns success of operation. +void EECodeManager::EnumGcRefs(EEMethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pContext, + GCEnumContext * hCallback, + PTR_UInt8 pbCallsiteStringBlob, + PTR_UInt8 pbDeltaShortcutTable) +{ + PTR_UInt8 pCursor = pMethodInfo->GetGCInfo(); + + // Early-out for the common case of no callsites + if (*pCursor == 0xFF) + return; + + + // ------------------------------------------------------------------------------------------------------- + // Decode the method GC info + // ------------------------------------------------------------------------------------------------------- + // + // This loop scans through the 'method info' to find a callsite offset which matches the incoming code + // offset. Once it's found, we break out and have a pointer into the 'callsite info blob' which will + // point at a string describing the roots that must be reported at this particular callsite. This loop + // needs to be fast because it's linear with respect to the number of callsites in a method. + // + // ------------------------------------------------------------------------------------------------------- + // + // 0ddddccc -- SMALL ENCODING + // + // -- dddd is an index into the delta shortcut table + // -- ccc is an offset into the callsite strings blob + // + // 1ddddddd { info offset } -- BIG ENCODING + // + // -- ddddddd is a 7-bit delta + // -- { info offset } is a variable-length unsigned encoding of the offset into the callsite + // strings blob for this callsite. + // + // 10000000 { delta } -- FORWARDER + // + // -- { delta } is a variable-length unsigned encoding of the offset to the next callsite + // + // 11111111 -- STRING TERMINATOR + // + + UInt32 callCodeOffset = codeOffset; + UInt32 curCodeOffset = 0; + IntNative infoOffset = 0; + + while (curCodeOffset < callCodeOffset) + { +ContinueUnconditionally: + UInt8 b = *pCursor++; + + if ((b & 0x80) == 0) + { + // SMALL ENCODING + infoOffset = (b & 0x7); + curCodeOffset += pbDeltaShortcutTable[b >> 3]; + } + else + { + UInt8 lowBits = (b & 0x7F); + // FORWARDER + if (lowBits == 0) + { + curCodeOffset += VarInt::ReadUnsigned(pCursor); + // N.B. a forwarder entry is always followed by another 'real' entry. The curCodeOffset that + // results from consuming the forwarder entry is an INTERMEDIATE VALUE and doesn't represent + // a code offset of an actual callsite-with-GC-info. But this intermediate value could + // inadvertently match some other callsite between the last callsite-with-GC-info and the next + // callsite-with-GC-info. To prevent this inadvertent match from happening, we must bypass + // the loop termination-condition test. Therefore, 'continue' cannot be used here and we must + // use a goto. + goto ContinueUnconditionally; + } + else + if (lowBits == 0x7F) // STRING TERMINATOR + break; + + // BIG ENCODING + curCodeOffset += lowBits; + + // N.B. this returns the negative of the length of the unsigned! + infoOffset = VarInt::SkipUnsigned(pCursor); + } + } + + // If we reached the end of the scan loop without finding a matching callsite offset, then there must not + // be any roots to report to the GC. + if (curCodeOffset != callCodeOffset) + return; + + // If we were in the BIG ENCODING case, the infoOffset wil be negative. So we backup pCursor and actually + // decode the unsigned here. This keeps the main loop above tighter by removing the conditional and + // decode from the body of the loop. + if (infoOffset < 0) + { + pCursor += infoOffset; + infoOffset = VarInt::ReadUnsigned(pCursor); + } + + // + // ------------------------------------------------------------------------------------------------------- + // Decode the callsite root string + // ------------------------------------------------------------------------------------------------------- + // + // 1. Call sites with nothing to report are not encoded + // + // 2. 00lRRRRR - normal "register set" encoding, pinned and interior attributes both false + // a. l - this is the last descriptor + // b. RRRRR - this is the register mask for { rbx, rsi, rdi, rbp, r12 }, ARM = { r4-r8 } + // + // 3. 01liprrr - more general register encoding with pinned and interior attributes + // a. l - last descriptor + // b. i - interior + // c. p - pinned + // d. rrr - register number { rbx, rsi, rdi, rbp, r12, r13, r14, r15 }, ARM = { r4-r11 } + // + // 4. 10l1SSSS - "local stack slot set" encoding, pinned and interior attributes both false + // a. l - last descriptor + // b. SSSS - set of "local slots" #0 - #3 - local slot 0 is at offset -8 from the last pushed + // callee saved register, local slot 1 is at offset - 16, etc - in other words, these are the + // slots normally used for locals + // + // 5. 10l0ssss - "local slot" encoding + // a. l - last descriptor + // b. ssss - "local slot" #4 - #19 + // + // 6. 11lipfsm {offset} [mask] - [multiple] stack slot encoding + // a. l - last descriptor + // b. i - interior attribute + // c. p - pinned attribute + // d. f - 1: frame pointer relative, 0: sp relative + // e. s - offset sign + // f. m - mask follows + // g. offset - variable length unsigned integer + // h. mask - variable length unsigned integer (only present if m-bit is 1) - this can describe + // multiple stack locations with the same attributes. E.g., if you want to describe stack + // locations 0x20, 0x28, 0x38, you would give a (starting) offset of 0x20 and a mask of + // 000000101 = 0x05. Up to 33 stack locations can be described. + // + // 7. 11lip010 0RRRRRRR [0IIIIIII] [0PPPPPPP] - live scratch reg reporting, this uses the SP-xxx encoding + // from #6 since we cannot have stack locations at negative + // offsets from SP. + // a. l - last descriptor + // b. i - interior byte present + // c. p - pinned byte present + // d. RRRRRRR - scratch register mask for { rax, rcx, rdx, r8, r9, r10, r11 }, ARM = { r0-r3, r12 } + // e. IIIIIII - interior scratch register mask for { rax, rcx, rdx, r8, r9, r10, r11 } iff 'i' is 1 + // f. PPPPPPP - pinned scratch register mask for { rax, rcx, rdx, r8, r9, r10, r11 } iff 'p' is 1 + // + PTR_UInt8 pbCallsiteString = pbCallsiteStringBlob + (int)infoOffset; + + bool isLastEncoding; + pCursor = pbCallsiteString; + do + { + UInt8 b = *pCursor++; + isLastEncoding = ((b & 0x20) == 0x20); + + switch (b & 0xC0) + { + case 0x00: + // case 2 -- "register set" + ReportRegisterSet(b, pContext, hCallback); + break; + case 0x40: + // case 3 -- "register" + ReportRegister(b, pContext, hCallback); + break; + case 0x80: + // case 4 -- "local slot set" + // case 5 -- "local slot" + ReportLocalSlots(b, pContext, hCallback, pMethodInfo->GetGCInfoHeader()); + break; + case 0xC0: + if ((b & 0xC7) == 0xC2) + // case 7 -- "scratch reg reporting" + ReportScratchRegs(b, pContext, hCallback, pCursor); + else + { + bool hasDynamicAlignment = pMethodInfo->GetGCInfoHeader()->HasDynamicAlignment(); +#ifdef TARGET_X86 + ASSERT_MSG(!hasDynamicAlignment || pMethodInfo->GetGCInfoHeader()->GetParamPointerReg() == RN_EBX, "NYI: non-EBX param pointer"); +#endif + // case 6 -- "stack slot" / "stack slot set" + ReportStackSlots(b, pContext, hCallback, pCursor, hasDynamicAlignment); + } + break; + } + } + while (!isLastEncoding); + + return; +} + +#ifdef DACCESS_COMPILE +#define ASSERT_OR_DAC_RETURN_FALSE(x) if(!(x)) return false; +#else +#define ASSERT_OR_DAC_RETURN_FALSE(x) ASSERT(x) +#endif + +// 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, callee-UNsaved registers are trashed) +// Returns success of operation. +bool EECodeManager::UnwindStackFrame(EEMethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pContext) +{ + GCInfoHeader * pInfoHeader = pMethodInfo->GetGCInfoHeader(); + + // We could implement this unwind if we wanted, but there really isn't any reason + ASSERT(pInfoHeader->GetReturnKind() != GCInfoHeader::MRK_ReturnsToNative); + +#if defined(_DEBUG) || defined(DACCESS_COMPILE) + // unwinding in the prolog is unsupported + ASSERT_OR_DAC_RETURN_FALSE(codeOffset >= pInfoHeader->GetPrologSize()); + + // unwinding in the epilog is unsupported + UInt32 epilogOffset = 0; + UInt32 epilogSize = 0; + ASSERT_OR_DAC_RETURN_FALSE(!GetEpilogOffset(pMethodInfo, codeOffset, &epilogOffset, &epilogSize)); +#endif + + bool ebpFrame = pInfoHeader->HasFramePointer(); + +#ifdef TARGET_X86 + // @TODO .. ESP-based methods with stack changes + ASSERT_MSG(ebpFrame || !pInfoHeader->HasStackChanges(), "NYI -- ESP-based methods with stack changes"); +#endif // TARGET_X86 + + // + // Just unwind based on the info header + // + Int32 saveSize = pInfoHeader->GetPreservedRegsSaveSize(); + UIntNative rawRSP; + if (ebpFrame) + { +#ifdef TARGET_ARM + rawRSP = pContext->GetFP() + pInfoHeader->GetFrameSize(); +#else + saveSize -= sizeof(void *); // don't count RBP + Int32 framePointerOffset = 0; +#ifdef TARGET_AMD64 + framePointerOffset = pInfoHeader->GetFramePointerOffset(); +#endif + rawRSP = pContext->GetFP() - saveSize - framePointerOffset; +#endif + } + else + { + rawRSP = pContext->GetSP() + pInfoHeader->GetFrameSize(); + } + PTR_UIntNative RSP = (PTR_UIntNative)rawRSP; + +#if defined(TARGET_AMD64) + if (pInfoHeader->HasSavedXmmRegs()) + { + typedef DPTR(Fp128) PTR_Fp128; + PTR_Fp128 xmmSaveArea = (PTR_Fp128)(rawRSP & ~0xf); + UInt32 savedXmmRegMask = pInfoHeader->GetSavedXmmRegMask(); + // should be a subset of xmm6-xmm15 + ASSERT((savedXmmRegMask & 0xffff003f) == 0); + savedXmmRegMask >>= 6; + for (int regIndex = 0; savedXmmRegMask != 0; regIndex++, savedXmmRegMask >>= 1) + { + if (savedXmmRegMask & 1) + { + --xmmSaveArea; + pContext->Xmm[regIndex] = *xmmSaveArea; + } + } + } +#elif defined(TARGET_ARM) + UInt8 vfpRegPushedCount = pInfoHeader->GetVfpRegPushedCount(); + UInt8 vfpRegFirstPushed = pInfoHeader->GetVfpRegFirstPushed(); + UInt32 regIndex = vfpRegFirstPushed - 8; + while (vfpRegPushedCount-- > 0) + { + ASSERT(regIndex < 8); + pContext->D[regIndex] = *(PTR_UInt64)RSP; + regIndex++; + RSP = (PTR_UIntNative)((PTR_UInt8)RSP + sizeof(UInt64)); + } +#endif + +#if defined(TARGET_X86) + int registerSaveDisplacement = 0; + // registers saved at bottom of frame in Project N + registerSaveDisplacement = pInfoHeader->GetFrameSize(); +#endif + + if (saveSize > 0) + { + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); +#ifdef TARGET_AMD64 + if (regMask & CSR_MASK_R15) { pContext->pR15 = RSP++; } + if (regMask & CSR_MASK_R14) { pContext->pR14 = RSP++; } + if (regMask & CSR_MASK_R13) { pContext->pR13 = RSP++; } + if (regMask & CSR_MASK_R12) { pContext->pR12 = RSP++; } + if (regMask & CSR_MASK_RDI) { pContext->pRdi = RSP++; } + if (regMask & CSR_MASK_RSI) { pContext->pRsi = RSP++; } + if (regMask & CSR_MASK_RBX) { pContext->pRbx = RSP++; } +#elif defined(TARGET_X86) + ASSERT_MSG(ebpFrame || !(regMask & CSR_MASK_RBP), "We should never use EBP as a preserved register"); + ASSERT_MSG(!(regMask & CSR_MASK_RBX) || !pInfoHeader->HasDynamicAlignment(), "Can't have EBX as preserved regster and dynamic alignment frame pointer") + if (regMask & CSR_MASK_RBX) { pContext->pRbx = (PTR_UIntNative)((PTR_UInt8)RSP - registerSaveDisplacement); ++RSP; } // registers saved at bottom of frame + if (regMask & CSR_MASK_RSI) { pContext->pRsi = (PTR_UIntNative)((PTR_UInt8)RSP - registerSaveDisplacement); ++RSP; } // registers saved at bottom of frame + if (regMask & CSR_MASK_RDI) { pContext->pRdi = (PTR_UIntNative)((PTR_UInt8)RSP - registerSaveDisplacement); ++RSP; } // registers saved at bottom of frame +#elif defined(TARGET_ARM) + if (regMask & CSR_MASK_R4) { pContext->pR4 = RSP++; } + if (regMask & CSR_MASK_R5) { pContext->pR5 = RSP++; } + if (regMask & CSR_MASK_R6) { pContext->pR6 = RSP++; } + if (regMask & CSR_MASK_R7) { pContext->pR7 = RSP++; } + if (regMask & CSR_MASK_R8) { pContext->pR8 = RSP++; } + if (regMask & CSR_MASK_R9) { pContext->pR9 = RSP++; } + if (regMask & CSR_MASK_R10) { pContext->pR10 = RSP++; } + if (regMask & CSR_MASK_R11) { pContext->pR11 = RSP++; } +#endif // TARGET_AMD64 + } + +#ifndef TARGET_ARM + if (ebpFrame) + pContext->pRbp = RSP++; +#endif + + // handle dynamic frame alignment + if (pInfoHeader->HasDynamicAlignment()) + { +#ifdef TARGET_X86 + ASSERT_MSG(pInfoHeader->GetParamPointerReg() == RN_EBX, "NYI: non-EBX param pointer"); + // For x86 dynamically-aligned frames, we have two frame pointers, like this: + // + // esp -> [main frame] + // ebp -> ebp save + // return address (copy) + // [variable-sized alignment allocation] + // ebx -> ebx save + // Return Address + // + // We've unwound the stack to the copy of the return address. We must continue to unwind the stack + // and restore EBX. Because of the variable sized space on the stack, the only way to get at EBX's + // saved location is to read it from the current value of EBX. EBX points at the stack location to + // which previous EBX was saved. + RSP = (PTR_UIntNative)*(pContext->pRbx); // RSP now points to EBX save location + pContext->pRbx = RSP++; // RSP now points to original caller pushed return address. +#else + UNREACHABLE_MSG("Dynamic frame alignment not supported on this platform"); +#endif + } + + pContext->SetAddrOfIP((PTR_PCODE)RSP); // save off the return address location + pContext->SetIP(*RSP++); // pop the return address +#ifdef TARGET_X86 + // pop the callee-popped args + RSP += (pInfoHeader->GetReturnPopSize() / sizeof(UIntNative)); +#endif + +#ifdef TARGET_ARM + RSP += pInfoHeader->ParmRegsPushedCount(); +#endif + + pContext->SetSP((UIntNative) dac_cast(RSP)); + return true; +} + +PTR_VOID EECodeManager::GetReversePInvokeSaveFrame(EEMethodInfo * pMethodInfo, REGDISPLAY * pContext) +{ + GCInfoHeader * pHeader = pMethodInfo->GetGCInfoHeader(); + + if (pHeader->GetReturnKind() != GCInfoHeader::MRK_ReturnsToNative) + return NULL; + + Int32 frameOffset = pHeader->GetReversePinvokeFrameOffset(); + + return *(PTR_PTR_VOID)(pContext->GetFP() + frameOffset); +} + +PTR_VOID EECodeManager::GetFramePointer(EEMethodInfo * pMethodInfo, + REGDISPLAY * pContext) +{ + GCInfoHeader* pUnwindInfo = pMethodInfo->GetGCInfoHeader(); + return (pUnwindInfo->HasFramePointer() || pUnwindInfo->IsFunclet()) + ? (PTR_VOID)pContext->GetFP() + : NULL; +} + +#ifndef DACCESS_COMPILE + +PTR_PTR_VOID EECodeManager::GetReturnAddressLocationForHijack(EEMethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pContext) +{ + GCInfoHeader * pHeader = pMethodInfo->GetGCInfoHeader(); + + // We *could* hijack a reverse-pinvoke method, but it doesn't get us much because we already synchronize + // with the GC on the way back to native code. + if (pHeader->GetReturnKind() == GCInfoHeader::MRK_ReturnsToNative) + return NULL; + + if (pHeader->IsFunclet()) + return NULL; + + if (codeOffset < pHeader->GetPrologSize()) + { + // @TODO: NYI -- hijack in prolog + return NULL; + } + +#ifdef _ARM_ + // We cannot get the return addres unless LR has + // be saved in the prolog. + if (!pHeader->IsRegSaved(CSR_MASK_LR)) + return NULL; +#endif // _ARM_ + + void ** ppvResult; + + UInt32 epilogOffset = 0; + UInt32 epilogSize = 0; + if (GetEpilogOffset(pMethodInfo, codeOffset, &epilogOffset, &epilogSize)) + { +#ifdef _ARM_ + // Disable hijacking from epilogs on ARM until we implement GetReturnAddressLocationFromEpilog. + return NULL; +#else + ppvResult = GetReturnAddressLocationFromEpilog(pHeader, pContext, epilogOffset, epilogSize); + // Early out if GetReturnAddressLocationFromEpilog indicates a non-hijackable epilog (e.g. exception + // throw epilog or tail call). + if (ppvResult == NULL) + return NULL; + goto Finished; +#endif + } + +#ifdef _ARM_ + // ARM always sets up R11 as an OS frame chain pointer to enable fast ETW stack walking (except in the + // case where LR is not pushed, but that was handled above). The protocol specifies that the return + // address is pushed at [r11, #4]. + ppvResult = (void **)((*pContext->pR11) + sizeof(void *)); + goto Finished; +#else + // We are in the body of the method, so just find the return address using the unwind info. + if (pHeader->HasFramePointer()) + { +#ifdef _X86_ + if (pHeader->HasDynamicAlignment()) + { + // In this case, we have the normal EBP frame pointer, but also an EBX frame pointer. Use the EBX + // one, because the return address associated with that frame pointer is the one we're actually + // going to return to. The other one (next to EBP) is only for EBP-chain-walking. + ppvResult = (void **)((*pContext->pRbx) + sizeof(void *)); + goto Finished; + } +#endif + + Int32 framePointerOffset = 0; +#ifdef _AMD64_ + framePointerOffset = pHeader->GetFramePointerOffset(); +#endif + ppvResult = (void **) ((*pContext->pRbp) + sizeof(void *) - framePointerOffset); + goto Finished; + } + + // We do not have a frame pointer, but we are also not in the prolog or epilog + + UInt8 * RSP = (UInt8 *)pContext->GetSP(); + RSP += pHeader->GetFrameSize(); + RSP += pHeader->GetPreservedRegsSaveSize(); + + // RSP should point to the return address now. + ppvResult = (void**)RSP; + goto Finished; +#endif + + Finished: + return ppvResult; +} + +#endif + +GCRefKind EECodeManager::GetReturnValueKind(EEMethodInfo * pMethodInfo) +{ + STATIC_ASSERT(GCInfoHeader::MRK_ReturnsScalar == GCRK_Scalar); + STATIC_ASSERT(GCInfoHeader::MRK_ReturnsObject == GCRK_Object); + STATIC_ASSERT(GCInfoHeader::MRK_ReturnsByref == GCRK_Byref); + + GCInfoHeader::MethodReturnKind retKind = pMethodInfo->GetGCInfoHeader()->GetReturnKind(); + switch (retKind) + { + case GCInfoHeader::MRK_ReturnsScalar: + case GCInfoHeader::MRK_ReturnsToNative: + return GCRK_Scalar; + case GCInfoHeader::MRK_ReturnsObject: + return GCRK_Object; + case GCInfoHeader::MRK_ReturnsByref: + return GCRK_Byref; + } + UNREACHABLE_MSG("unexpected return kind"); +} + +bool EECodeManager::GetEpilogOffset(EEMethodInfo * pMethodInfo, UInt32 codeOffset, UInt32 * epilogOffsetOut, UInt32 * epilogSizeOut) +{ + GCInfoHeader * pInfoHeader = pMethodInfo->GetGCInfoHeader(); + + UInt32 epilogStart; + + if (pInfoHeader->IsEpilogAtEnd()) + { + ASSERT(pInfoHeader->GetEpilogCount() == 1); + UInt32 epilogSize = pInfoHeader->GetFixedEpilogSize(); + + epilogStart = pMethodInfo->GetCodeSize() - epilogSize; + + // If we're at offset 0, it's equivalent to being in the body of the method + if (codeOffset > epilogStart) + { + *epilogOffsetOut = codeOffset - epilogStart; + ASSERT(pInfoHeader->IsValidEpilogOffset(*epilogOffsetOut, epilogSize)); + *epilogSizeOut = epilogSize; + return true; + } + return false; + } + + PTR_UInt8 pbEpilogTable = pMethodInfo->GetEpilogTable(); + epilogStart = 0; + bool hasVaryingEpilogSizes = pInfoHeader->HasVaryingEpilogSizes(); + for (UInt32 idx = 0; idx < pInfoHeader->GetEpilogCount(); idx++) + { + epilogStart += VarInt::ReadUnsigned(pbEpilogTable); + UInt32 epilogSize = hasVaryingEpilogSizes ? VarInt::ReadUnsigned(pbEpilogTable) : pInfoHeader->GetFixedEpilogSize(); + + // If we're at offset 0, it's equivalent to being in the body of the method + if ((epilogStart < codeOffset) && (codeOffset < (epilogStart + epilogSize))) + { + *epilogOffsetOut = codeOffset - epilogStart; + ASSERT(pInfoHeader->IsValidEpilogOffset(*epilogOffsetOut, epilogSize)); + *epilogSizeOut = epilogSize; + return true; + } + } + return false; +} + +#ifndef DACCESS_COMPILE + +void ** EECodeManager::GetReturnAddressLocationFromEpilog(GCInfoHeader * pInfoHeader, REGDISPLAY * pContext, + UInt32 epilogOffset, UInt32 epilogSize) +{ + ASSERT(pInfoHeader->IsValidEpilogOffset(epilogOffset, epilogSize)); + UInt8 * pbCurrentIP = (UInt8 *) pContext->GetIP(); + UInt8 * pbEpilogStart = pbCurrentIP - epilogOffset; + + //ASSERT(VerifyEpilogBytes(pInfoHeader, (Code *)pbEpilogStart)); + // We could find the return address of a native-callable method, but it's not very useful at the moment. + ASSERT(pInfoHeader->GetReturnKind() != GCInfoHeader::MRK_ReturnsToNative); + UInt8 * pbEpilog = pbEpilogStart; + +#ifdef _X86_ + + if (pInfoHeader->HasFramePointer()) + { + { + // New Project N frames + + int frameSize = pInfoHeader->GetFrameSize(); + Int32 saveSize = pInfoHeader->GetPreservedRegsSaveSize() - sizeof(void*); + int distance = frameSize + saveSize; + + if (saveSize > 0 || (0x8D == *pbEpilog) /* localloc frame */ ) + { + // regenerate original sp + + // lea esp, [ebp-xxx] + ASSERT_MSG(0x8D == *pbEpilog, "expected lea esp, [ebp-frame size]"); + + if (distance <= 128) + { + // short format (constant as 8-bit integer + ASSERT_MSG(0x65 == *(pbEpilog + 1), "expected lea esp, [ebp-frame size]"); + ASSERT_MSG((UInt8)(-distance) == *(pbEpilog + 2), "expected lea esp, [ebp-frame size]"); + pbEpilog += 3; + } + else + { + // long formant (constant as 32-bit integer) + ASSERT_MSG(0xA5 == *(pbEpilog + 1), "expected lea esp, [ebp-frame size]"); + ASSERT_MSG(-distance == *(Int32*)(pbEpilog + 2), "expected lea esp, [ebp-frame size]"); + pbEpilog += 6; + } + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + if (regMask & CSR_MASK_RBX) pbEpilog++; // pop ebx -- 5B + if (regMask & CSR_MASK_RSI) pbEpilog++; // pop esi -- 5E + if (regMask & CSR_MASK_RDI) pbEpilog++; // pop edi -- 5F + } + + if (frameSize > 0) + { + // set esp to to EBP frame chain location + ASSERT_MSG(0x8B == *pbEpilog, "expected 'mov esp, ebp'"); + ASSERT_MSG(0xE5 == *(pbEpilog + 1), "expected 'mov esp, ebp'"); + pbEpilog += 2; + } + + ASSERT_MSG(0x5d == *pbEpilog, "expected 'pop ebp'"); + + // Just use the EBP frame if we haven't popped it yet + if (pbCurrentIP <= pbEpilog) + return (void **)((*(pContext->pRbp)) + sizeof(void *)); + + ++pbEpilog; // advance past 'pop ebp' + + if (pInfoHeader->HasDynamicAlignment()) + { + // For x86 dynamically-aligned frames, we have two frame pointers, like this: + // + // esp -> [main frame] + // ebp -> ebp save + // return address + // [variable-sized alignment allocation] + // ebx -> ebx save + // Return Address + // + // The epilog looks like this, with the corresponding changes to the return address location. + // + // Correct return address location + // -------------------------------- + // -------------------------------> ebp + 4 (or ebx + 4) + // lea esp, [ebp-XXX] + // pop esi + // mov esp, ebp + // pop ebp + // -------------------------------> ebx + 4 + // mov esp, ebx + // pop ebx + // -------------------------------> esp + // ret + + ASSERT_MSG(pInfoHeader->GetParamPointerReg() == RN_EBX, "NYI: non-EBX param pointer"); + + ASSERT_MSG(0x8B == *pbEpilog, "expected 'mov esp, ebx'"); + ASSERT_MSG(0xE3 == *(pbEpilog + 1), "expected 'mov esp, ebx'"); + + // At this point the return address is at EBX+4, we fall-through to the code below since it's + // the same there as well. + + pbEpilog += 2; // advance past 'mov esp, ebx' + + ASSERT_MSG(0x5b == *pbEpilog, "expected 'pop ebx'"); + + // at this point the return address is at EBX+4 + if (pbCurrentIP == pbEpilog) + return (void **)((*(pContext->pRbx)) + sizeof(void *)); + + ++pbEpilog; // advance past 'pop ebx' + } + + // EBP has been popped, dynamic alignment has been undone, so ESP points at the return address + return (void **)(pContext->SP); + } + } + else + { + ASSERT_MSG(!pInfoHeader->HasStackChanges(), "NYI -- dynamic push/pop"); + + UIntNative RSP = pContext->SP; + + int frameSize = pInfoHeader->GetFrameSize(); + + if (pbCurrentIP <= pbEpilog) + RSP += frameSize; + + if (frameSize == sizeof(void*)) + pbEpilog++; // 0x59, pop ecx + else if ((Int8)frameSize == frameSize) + pbEpilog += 3; // add esp, imm8 -- 83 c4 BYTE(frameSize) + else + pbEpilog += 6; // add esp, imm32 -- 81 c4 DWORD(frameSize) + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + + ASSERT_MSG(!(regMask & CSR_MASK_RBP), + "We only expect RBP to be used as the frame pointer, never as a free preserved reg"); + + if (regMask & CSR_MASK_RBX) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 1; // pop ebx -- 5B + } + + if (regMask & CSR_MASK_RSI) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 1; // pop esi -- 5E + } + + if (regMask & CSR_MASK_RDI) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 1; // pop edi -- 5F + } + + return (void **)(RSP); + } + +#elif defined(_AMD64_) + + int frameSize = pInfoHeader->GetFrameSize(); + if (pInfoHeader->HasFramePointer()) + { + bool isNewStyleFP = pInfoHeader->IsFramePointerOffsetFromSP(); + int preservedRegSize = pInfoHeader->GetPreservedRegsSaveSize(); + + int encodedFPOffset = isNewStyleFP ? frameSize - pInfoHeader->GetFramePointerOffsetFromSP() + : -preservedRegSize + sizeof(void*); + + // 'lea rsp, [rbp + offset]' // 48 8d 65 xx + // 48 8d a5 xx xx xx xx + if ((encodedFPOffset > 127) || (encodedFPOffset < -128)) + pbEpilog += 7; + else + pbEpilog += 4; + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + + if (regMask & CSR_MASK_R15) pbEpilog += 2; // pop r15 -- 41 5F + if (regMask & CSR_MASK_R14) pbEpilog += 2; // pop r14 -- 41 5E + if (regMask & CSR_MASK_R13) pbEpilog += 2; // pop r13 -- 41 5D + if (regMask & CSR_MASK_R12) pbEpilog += 2; // pop r12 -- 41 5C + if (regMask & CSR_MASK_RDI) pbEpilog++; // pop rdi -- 5F + if (regMask & CSR_MASK_RSI) pbEpilog++; // pop rsi -- 5E + if (regMask & CSR_MASK_RBX) pbEpilog++; // pop rbx -- 5B + + ASSERT_MSG(0x5d == *pbEpilog, "expected pop ebp"); + + // If RBP hasn't been popped yet, we can calculate the return address location from RBP. + if (pbCurrentIP <= pbEpilog) + return (void **)(*(pContext->pRbp) + encodedFPOffset + preservedRegSize); + + // EBP has been popped, so RSP points at the return address + return (void **) (pContext->SP); + } + else + { + UIntNative RSP = pContext->SP; + + if (frameSize) + { + if (pbCurrentIP <= pbEpilog) + RSP += frameSize; + + if (frameSize < 128) + { + // 'add rsp, frameSize' // 48 83 c4 xx + pbEpilog += 4; + } + else + { + // 'add rsp, frameSize' // 48 81 c4 xx xx xx xx + pbEpilog += 7; + } + } + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + + ASSERT_MSG(!(regMask & CSR_MASK_RBP), + "We only expect RBP to be used as the frame pointer, never as a free preserved reg"); + + if (regMask & CSR_MASK_R15) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 2; // pop r15 -- 41 5F + } + + if (regMask & CSR_MASK_R14) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 2; // pop r14 -- 41 5E + } + + if (regMask & CSR_MASK_R13) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 2; // pop r13 -- 41 5D + } + + if (regMask & CSR_MASK_R12) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 2; // pop r12 -- 41 5C + } + + if (regMask & CSR_MASK_RDI) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 1; // pop rdi -- 5F + } + + if (regMask & CSR_MASK_RSI) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 1; // pop rsi -- 5E + } + + if (regMask & CSR_MASK_RBX) + { + if (pbCurrentIP <= pbEpilog) { RSP += sizeof(void*); } + pbEpilog += 1; // pop rbx -- 5B + } + + return (void **) (RSP); + } + +#elif defined(_ARM_) + + UInt16 * pwEpilog = (UInt16*)pbEpilog; + + if (pwEpilog[0] == 0x46bd) + { + // mov sp, fp + ASSERT(pInfoHeader->HasFramePointer()); + pwEpilog++; + } + + if (pInfoHeader->HasFramePointer() || pInfoHeader->GetFrameSize() > 0) + { + if ((pwEpilog[0] & 0xff80) == 0xb000) + { + // add sp, sp, #frameSize + pwEpilog++; + } + else if (((pwEpilog[0] & 0xfbf0) == 0xf200) && ((pwEpilog[1] & 0x8f00) == 0x0d00)) + { + // add sp, reg, #imm12 + pwEpilog += 2; + } + else if (((pwEpilog[0] & 0xfbf0) == 0xf240) && ((pwEpilog[1] & 0x8f00) == 0x0c00)) + { + // movw r12, #imm16 + pwEpilog += 2; + + if (((pwEpilog[0] & 0xfbf0) == 0xf2c0) && ((pwEpilog[1] & 0x8f00) == 0x0c00)) + { + // movt r12, #imm16 + pwEpilog += 2; + } + + // add sp, sp, r12 + ASSERT((pwEpilog[0] == 0xeb0d) && (pwEpilog[1] == 0x0d0c)); + pwEpilog += 2; + } + } + + // vpop {...} + while (((pwEpilog[0] & ~(1<<6)) == 0xecbd) && ((pwEpilog[1] & 0x0f01) == 0x0b00)) + pwEpilog += 2; + + // pop {...} + UInt16 wPopRegs = 0; + if ((pwEpilog[0] & 0xfe00) == 0xbc00) + { + // 16-bit pop. + wPopRegs = pwEpilog[0] & 0xff; + if ((pwEpilog[0] & 0x100) != 0) + wPopRegs |= 1<<15; + pwEpilog++; + } + else if (pwEpilog[0] == 0xe8bd) + { + // 32-bit pop. + wPopRegs = pwEpilog[1]; + pwEpilog += 2; + } + else if ((pwEpilog[0] == 0xf85d) && ((pwEpilog[1] & 0x0fff) == 0xb04)) + { + // Single register pop. + int reg = pwEpilog[1] >> 12; + wPopRegs |= 1 << reg; + pwEpilog += 2; + } + + if (wPopRegs & (1 << 11)) + { + // Popped r11 (the OS frame chain pointer). If we pushed this then we were required to push lr + // immediately under it. (Can't directly assert that LR is popped since there are several ways we + // might do this). + if (pbCurrentIP < (UInt8*)pwEpilog) + { + // Executing in epilog prior to pop, so the return address is at [r11, #4]. + return (void**)((*pContext->pR11) + 4); + } + } + else + { + // We didn't push r11 so therefore we didn't push lr (the invariant is that both or neither are + // pushed). So it doesn't matter where in the epilog we're executing, the return address has always + // been in lr. + return (void**)pContext->pLR; + } + + if (wPopRegs & (1 << 15)) + { + // Popped pc. This is a direct result of pushing lr and we only ever push lr if and only if we're also + // pushing r11 to form an OS frame chain. If we didn't return above that means we somehow popped r11 + // and lr into pc and somehow landed up at the next instruction (i.e. past the end of the epilog). So + // this case is an error. + ASSERT_UNCONDITIONALLY("Walked off end of epilog"); + return NULL; + } + + if ((pwEpilog[0] == 0xf85d) && ((pwEpilog[1] & 0xff00) == 0xfb00)) + { + // ldr pc, [sp], #imm8 + // Case where lr was pushed but we couldn't pop it with the other registers because we had some + // additional stack to clean up (homed argument registers). Return address is at the top of the stack + // in this case. + return (void**)pContext->SP; + } + + if ((pwEpilog[0] & 0xff80) == 0xb000) + { + // add sp, sp, #imm7 + // Case where we have stack cleanup (homed argument registers) but we need to return via a branch for + // some reason (such as tail calls). + pwEpilog++; + } + + if ((pwEpilog[0] & 0xff87) == 0x4700) + { + // bx + // Branch via register. This is a simple return if is lr, otherwise assume it's an EH throw and + // return NULL to indicate do not hijack. + if (((pwEpilog[0] & 0x0078) >> 3) == 14) + return (void**)pContext->pLR; + return NULL; + } + + if (((pwEpilog[0] & 0xf800) == 0xf000) && ((pwEpilog[1] & 0xd000) == 0x9000)) + { + // b + // Direct branch. Looks like a tail call. These aren't hijackable (without writing the instruction + // stream) so return NULL to indicate do not hijack here. + return NULL; + } + + // Shouldn't be any other instructions in the epilog. + UNREACHABLE_MSG("Unknown epilog instruction"); + return NULL; +#endif // _X86_ +} + +#ifdef _DEBUG + +bool EECodeManager::FindNextEpilog(GCInfoHeader * pInfoHeader, UInt32 methodSize, PTR_UInt8 pbEpilogTable, + Int32 * pEpilogStartOffsetInOut, UInt32 * pEpilogSizeOut) +{ + Int32 startOffset = *pEpilogStartOffsetInOut; + Int32 thisOffset = 0; + + if (pInfoHeader->IsEpilogAtEnd()) + { + ASSERT(pInfoHeader->GetEpilogCount() == 1); + UInt32 epilogSize = pInfoHeader->GetFixedEpilogSize(); + thisOffset = methodSize - epilogSize; + *pEpilogStartOffsetInOut = thisOffset; + *pEpilogSizeOut = epilogSize; + return (thisOffset > startOffset); + } + + bool hasVaryingEpilogSizes = pInfoHeader->HasVaryingEpilogSizes(); + for (UInt32 idx = 0; idx < pInfoHeader->GetEpilogCount(); idx++) + { + thisOffset += VarInt::ReadUnsigned(pbEpilogTable); + UInt32 epilogSize = hasVaryingEpilogSizes ? VarInt::ReadUnsigned(pbEpilogTable) : pInfoHeader->GetFixedEpilogSize(); + if (thisOffset > startOffset) + { + *pEpilogStartOffsetInOut = thisOffset; + *pEpilogSizeOut = epilogSize; + return true; + } + } + + return false; +} + +#ifdef _ARM_ +#define IS_FRAMELESS() ((pInfoHeader->GetSavedRegs() & CSR_MASK_LR) == 0) +#else +#define IS_FRAMELESS() (!pInfoHeader->HasFramePointer()) +#endif + +void CheckHijackInEpilog(GCInfoHeader * pInfoHeader, Code * pEpilog, Code * pEpilogStart, UInt32 epilogSize) +{ + ASSERT(pInfoHeader->GetReturnKind() != GCInfoHeader::MRK_ReturnsToNative); + if (IS_FRAMELESS()) + return; + + UIntNative SUCCESS_VAL = 0x22222200; + UIntNative RSP_TEST_VAL = SUCCESS_VAL; + UIntNative RBP_TEST_VAL = (RSP_TEST_VAL - sizeof(void *)); + + REGDISPLAY context; +#if defined(_X86_) + context.pRbx = &RBP_TEST_VAL; + context.pRbp = &RBP_TEST_VAL; + context.SP = RSP_TEST_VAL; +#elif defined(_AMD64_) + + int frameSize = pInfoHeader->GetFrameSize(); + bool isNewStyleFP = pInfoHeader->IsFramePointerOffsetFromSP(); + int preservedRegSize = pInfoHeader->GetPreservedRegsSaveSize(); + + int encodedFPOffset = isNewStyleFP ? frameSize - pInfoHeader->GetFramePointerOffsetFromSP() + : -preservedRegSize + sizeof(void*); + + RBP_TEST_VAL = SUCCESS_VAL - encodedFPOffset - preservedRegSize; + + context.pRbp = &RBP_TEST_VAL; + context.SP = RSP_TEST_VAL; +#elif defined(_ARM_) + context.pR11 = &RBP_TEST_VAL; + context.SP = RSP_TEST_VAL; +#endif + + context.SetIP((PCODE)pEpilog); + + void ** result = EECodeManager::GetReturnAddressLocationFromEpilog(pInfoHeader, &context, + (UInt32)((Code*)pEpilog - pEpilogStart), epilogSize); + + ASSERT(SUCCESS_VAL == (UIntNative)result || NULL == result); +} + +#define CHECK_HIJACK_IN_EPILOG() CheckHijackInEpilog(pInfoHeader, (Code *)pEpilog, (Code *)pEpilogStart, epilogSize) + +#define VERIFY_FAILURE() \ +{ \ + ASSERT_UNCONDITIONALLY("VERIFY_FAILURE"); \ + return false; \ +} \ + +#ifdef _X86_ +bool VerifyEpilogBytesX86(GCInfoHeader * pInfoHeader, Code * pEpilogStart, UInt32 epilogSize) +{ + Code * pEpilog = pEpilogStart; + + // NativeCallable methods aren't return-address-hijacked, so we don't care about the epilog format. + bool returnsToNative = (pInfoHeader->GetReturnKind() == GCInfoHeader::MRK_ReturnsToNative); + if (returnsToNative) + return true; + + if (pInfoHeader->HasFramePointer()) + { + { + // ProjectN frames + + CHECK_HIJACK_IN_EPILOG(); + + int frameSize = pInfoHeader->GetFrameSize(); + Int32 saveSize = pInfoHeader->GetPreservedRegsSaveSize() - sizeof(void*); // don't count EBP + int distance = frameSize + saveSize; + + if (saveSize > 0 || (*pEpilog==0x8d) /* localloc frame */ ) + { + // lea esp, [ebp-xxx] + + if (*pEpilog++ != 0x8d) + VERIFY_FAILURE(); + + if (distance <= 128) + { + if (*pEpilog++ != 0x65) + VERIFY_FAILURE(); + if (*pEpilog++ != ((UInt8)-distance)) + VERIFY_FAILURE(); + } + else + { + if (*pEpilog++ != 0xa5) + VERIFY_FAILURE(); + if (*((Int32*&)pEpilog)++ != -distance) + VERIFY_FAILURE(); + } + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RBX) + if (*pEpilog++ != 0x5b) // pop ebx + VERIFY_FAILURE(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RSI) + if (*pEpilog++ != 0x5e) // pop esi + VERIFY_FAILURE(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RDI) + if (*pEpilog++ != 0x5f) // pop edi + VERIFY_FAILURE(); + } + + // Reset ESP if necessary + if (frameSize > 0) + { + // 'mov esp, ebp' + CHECK_HIJACK_IN_EPILOG(); + if (*pEpilog++ != 0x8b) + VERIFY_FAILURE(); + if (*pEpilog++ != 0xE5) + VERIFY_FAILURE(); + } + + // pop ebp + CHECK_HIJACK_IN_EPILOG(); + if (*pEpilog++ != 0x5d) + VERIFY_FAILURE(); + + if (pInfoHeader->HasDynamicAlignment()) + { + ASSERT_MSG(pInfoHeader->GetParamPointerReg() == RN_EBX, "Expecting EBX as param pointer reg"); + ASSERT_MSG(!(pInfoHeader->GetSavedRegs() & CSR_MASK_RBX), "Not expecting param pointer reg to be saved explicitly"); + + // expect 'mov esp, ebx' + CHECK_HIJACK_IN_EPILOG(); + if (*pEpilog++ != 0x8b || *pEpilog++ != 0xE3) + { + VERIFY_FAILURE(); + } + + // pop ebx + CHECK_HIJACK_IN_EPILOG(); + if (*pEpilog++ != 0x5b) + VERIFY_FAILURE(); + } + } + } + else + { + CHECK_HIJACK_IN_EPILOG(); + int frameSize = pInfoHeader->GetFrameSize(); + if (frameSize == 0) + { + } + else if (frameSize == sizeof(void*)) + { + if (*pEpilog++ != 0x59) // pop ecx + VERIFY_FAILURE(); + } + else if ((Int8)frameSize == frameSize) + { + // add esp, imm8 + if (*pEpilog++ != 0x83) + VERIFY_FAILURE(); + if (*pEpilog++ != 0xc4) + VERIFY_FAILURE(); + if (*pEpilog++ != frameSize) + VERIFY_FAILURE(); + } + else + { + // add esp, imm32 + if (*pEpilog++ != 0x81) + VERIFY_FAILURE(); + if (*pEpilog++ != 0xc4) + VERIFY_FAILURE(); + if ((*((Int32*)pEpilog))++ != frameSize) + VERIFY_FAILURE(); + } + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + +#if 1 + ASSERT_MSG(!(pInfoHeader->GetSavedRegs() & CSR_MASK_RBP), + "We only expect RBP to be used as the frame pointer, never as a free preserved reg"); +#else + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RBP) + if (*pEpilog++ != 0x5d) // pop ebp + VERIFY_FAILURE(); +#endif + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RBX) + if (*pEpilog++ != 0x5b) // pop ebx + VERIFY_FAILURE(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RSI) + if (*pEpilog++ != 0x5e) // pop esi + VERIFY_FAILURE(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RDI) + if (*pEpilog++ != 0x5f) // pop edi + VERIFY_FAILURE(); + } + + CHECK_HIJACK_IN_EPILOG(); + + // Note: the last instruction of the epilog may be one of many possibilities: ret, rep ret, jmp offset, + // or jmp [offset]. Each is a different size, but still just one instruction, which is just fine. + // Therefore, from here down, pEpilog may be beyond "epilog start + size". + + if (*pEpilog == 0xE9) + { + pEpilog += 5; // jmp offset (tail call direct) + } + else if (*pEpilog == 0xFF) + { + pEpilog += 6; // jmp [offset] (tail call indirect) + } + else + { + if (*pEpilog == 0xf3) // optional: rep prefix + pEpilog++; + + UInt32 retPopSize = pInfoHeader->GetReturnPopSize(); + if (retPopSize == 0) + { + if (*pEpilog++ != 0xC3) // ret + VERIFY_FAILURE(); + } + else + { + if (*pEpilog++ != 0xC2) // ret NNNN + VERIFY_FAILURE(); + if (*((UInt16 *)pEpilog) != retPopSize) + VERIFY_FAILURE(); + pEpilog += 2; + } + } + + return true; +} +#endif // _X86_ +#ifdef _AMD64_ +bool VerifyEpilogBytesAMD64(GCInfoHeader * pInfoHeader, Code * pEpilogStart, UInt32 epilogSize) +{ + Code * pEpilog = pEpilogStart; + + // NativeCallable methods aren't return-address-hijacked, so we don't care about the epilog format. + bool returnsToNative = (pInfoHeader->GetReturnKind() == GCInfoHeader::MRK_ReturnsToNative); + if (returnsToNative) + return true; + + CHECK_HIJACK_IN_EPILOG(); + + bool ebpFrame = pInfoHeader->HasFramePointer(); + int frameSize = pInfoHeader->GetFrameSize(); + if (ebpFrame) + { + ASSERT(RN_EBP == pInfoHeader->GetFramePointerReg()); + + bool isNewStyleFP = pInfoHeader->IsFramePointerOffsetFromSP(); + int preservedRegSize = pInfoHeader->GetPreservedRegsSaveSize(); + + Int32 offset = isNewStyleFP ? frameSize - pInfoHeader->GetFramePointerOffsetFromSP() + : -preservedRegSize + sizeof(void*); + + // 'lea rsp, [rbp - offset]' + if (*pEpilog++ != 0x48) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x8d) + VERIFY_FAILURE(); + + if ((offset > 127) || (offset < -128)) + { + if (*pEpilog++ != 0xA5) + VERIFY_FAILURE(); + if (*((Int32*&)pEpilog)++ != offset) + VERIFY_FAILURE(); + } + else + { + if (*pEpilog++ != 0x65) + VERIFY_FAILURE(); + if (((Int8)*pEpilog++) != offset) + VERIFY_FAILURE(); + } + } + else if (frameSize) + { + if (frameSize < 128) + { + // 'add rsp, frameSize' // 48 83 c4 xx + if (*pEpilog++ != 0x48) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x83) + VERIFY_FAILURE(); + if (*pEpilog++ != 0xc4) + VERIFY_FAILURE(); + if (*pEpilog++ != ((UInt8)frameSize)) + VERIFY_FAILURE(); + } + else + { + // 'add rsp, frameSize' // 48 81 c4 xx xx xx xx + if (*pEpilog++ != 0x48) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x81) + VERIFY_FAILURE(); + if (*pEpilog++ != 0xc4) + VERIFY_FAILURE(); + if (*((Int32*&)pEpilog)++ != frameSize) + VERIFY_FAILURE(); + } + } + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_R15) + { + // pop r15 + if (*pEpilog++ != 0x41) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x5f) + VERIFY_FAILURE(); + } + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_R14) + { + // pop r14 + if (*pEpilog++ != 0x41) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x5e) + VERIFY_FAILURE(); + } + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_R13) + { + // pop r13 + if (*pEpilog++ != 0x41) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x5d) + VERIFY_FAILURE(); + } + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_R12) + { + // pop r12 + if (*pEpilog++ != 0x41) + VERIFY_FAILURE(); + if (*pEpilog++ != 0x5c) + VERIFY_FAILURE(); + } + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RDI) + if (*pEpilog++ != 0x5f) // pop rdi + VERIFY_FAILURE(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RSI) + if (*pEpilog++ != 0x5e) // pop rsi + VERIFY_FAILURE(); + + CHECK_HIJACK_IN_EPILOG(); + if (regMask & CSR_MASK_RBX) + if (*pEpilog++ != 0x5b) // pop rbx + VERIFY_FAILURE(); + + if (ebpFrame) + { + CHECK_HIJACK_IN_EPILOG(); + if (*pEpilog++ != 0x5d) // pop rbp + VERIFY_FAILURE(); + } + + CHECK_HIJACK_IN_EPILOG(); + + // Note: the last instruction of the epilog may be one of many possibilities: ret, rep ret, rex jmp rax. + // Each is a different size, but still just one instruction, which is just fine. Therefore, from here + // down, pEpilog may be beyond "epilog start + size". + + if (*pEpilog == 0x48) + { + // rex jmp rax (tail call) + pEpilog++; + + if (*pEpilog++ != 0xff) + VERIFY_FAILURE(); + if (*pEpilog++ != 0xe0) + VERIFY_FAILURE(); + } + else + { + // rep (OPTIONAL) + if (*pEpilog == 0xf3) + pEpilog++; + // ret + if (*pEpilog++ != 0xc3) + VERIFY_FAILURE(); + } + + return true; +} +#endif // _AMD64_ + +#ifdef _ARM_ +bool VerifyEpilogBytesARM(GCInfoHeader * pInfoHeader, Code * pEpilogStart, UInt32 epilogSize) +{ + if (((size_t)pEpilogStart) & 1) + pEpilogStart--; + + UInt16 * pEpilog = (UInt16 *)pEpilogStart; + + // NativeCallable methods aren't return-address-hijacked, so we don't care about the epilog format. + bool returnsToNative = (pInfoHeader->GetReturnKind() == GCInfoHeader::MRK_ReturnsToNative); + if (returnsToNative) + return true; + + CHECK_HIJACK_IN_EPILOG(); + + int stackPopSize = 0; + bool r7Cleanup = false; + + int frameSize = pInfoHeader->GetFrameSize(); + bool r7Frame = pInfoHeader->HasFramePointer(); + + if (pEpilog[0] == 0x46bd) + { + // 'mov sp,fp' + if (!r7Frame) + VERIFY_FAILURE(); + r7Cleanup = true; + pEpilog++; + } + + CHECK_HIJACK_IN_EPILOG(); + + if (frameSize > 0 || r7Frame) + { + if ((pEpilog[0] & 0xff80) == 0xb000) + { + // 'add sp, sp, #frameSize' // b0xx + stackPopSize = (*pEpilog & 0x7f) << 2; + pEpilog++; + } + else if ((pEpilog[0] & 0xfbf0) == 0xf200 && (pEpilog[1] & 0x8f00) == 0x0d00) + { + // 'add sp,reg,#imm12 + int reg = pEpilog[0] & 0x000f; + if (reg == 0xd) + ; + else if (reg == 0x7 && r7Frame) + r7Cleanup = true; + else + VERIFY_FAILURE(); + stackPopSize = (((pEpilog[0] >> 10) & 0x1) << 11) + (((pEpilog[1] >> 12) & 0x07) << 8) + (pEpilog[1] & 0xff); + pEpilog += 2; + } + else if ((pEpilog[0] & 0xfbf0) == 0xf240 && (pEpilog[1] & 0x8f00) == 0x0c00) + { + // movw r12,imm16 + stackPopSize = ((pEpilog[0] & 0xf) << 12) + (((pEpilog[0] >> 10) & 0x1) << 11) + (((pEpilog[1] >> 12) & 0x07) << 8) + (pEpilog[1] & 0xff); + pEpilog += 2; + + // movt present as well? + if ((pEpilog[0] & 0xfbf0) == 0xf2c0 && (pEpilog[1] & 0x8f00) == 0x0c00) + { + int highWord = ((pEpilog[0] & 0xf) << 12) + (((pEpilog[0] >> 10) & 0x1) << 11) + (((pEpilog[1] >> 12) & 0x07) << 8) + (pEpilog[1] & 0xff); + stackPopSize += highWord << 16; + pEpilog += 2; + } + + // expect add sp,sp,r12 + if (pEpilog[0] != 0xeb0d || pEpilog[1] != 0x0d0c) + VERIFY_FAILURE(); + pEpilog += 2; + } + } + + CHECK_HIJACK_IN_EPILOG(); + + // check for vpop instructions to match what's in the info hdr + Int32 vfpRegFirstPushedExpected = pInfoHeader->GetVfpRegFirstPushed(); + Int32 vfpRegPushedCountExpected = pInfoHeader->GetVfpRegPushedCount(); + while ((pEpilog[0] & ~(1<<6)) == 0xecbd && (pEpilog[1] & 0x0f01) == 0x0b00) + { + Int32 vfpRegFirstPushedActual = (((pEpilog[0] >> 6) & 1) << 4) | (pEpilog[1] >> 12); + Int32 vfpRegPushedCountActual = (pEpilog[1] & 0xff) >> 1; + if (vfpRegFirstPushedExpected == 0 && vfpRegPushedCountExpected == 0) + { + VERIFY_FAILURE(); + } + else + { + if (vfpRegFirstPushedActual != vfpRegFirstPushedExpected || vfpRegPushedCountActual > vfpRegPushedCountExpected) + VERIFY_FAILURE(); + + // if we are still here, there are more than 16 registers to pop, so we expect another vpop + // adjust the "expected" variables accordingly + vfpRegFirstPushedExpected += vfpRegPushedCountActual; + vfpRegPushedCountExpected -= vfpRegPushedCountActual; + } + + pEpilog += 2; + + CHECK_HIJACK_IN_EPILOG(); + } + if (vfpRegPushedCountExpected != 0) + VERIFY_FAILURE(); + + CalleeSavedRegMask regMask = pInfoHeader->GetSavedRegs(); + + // figure out what set of registers should be popped + int shouldPopRegMask = 0; + if (regMask & CSR_MASK_R4) + shouldPopRegMask |= 1<<4; + if (regMask & CSR_MASK_R5) + shouldPopRegMask |= 1<<5; + if (regMask & CSR_MASK_R6) + shouldPopRegMask |= 1<<6; + if (regMask & CSR_MASK_R7) + shouldPopRegMask |= 1<<7; + if (regMask & CSR_MASK_R8) + shouldPopRegMask |= 1<<8; + if (regMask & CSR_MASK_R9) + shouldPopRegMask |= 1<<9; + if (regMask & CSR_MASK_R10) + shouldPopRegMask |= 1<<10; + if (regMask & CSR_MASK_R11) + shouldPopRegMask |= 1<<11; + if (regMask & CSR_MASK_LR) + shouldPopRegMask |= 1<<15; + + // figure out what set of registers is actually popped + int actuallyPopRegMask = 0; + if ((pEpilog[0] & 0xfe00) == 0xbc00) + { + actuallyPopRegMask = pEpilog[0] & 0xff; + if ((pEpilog[0] & 0x100) != 0) + actuallyPopRegMask |= 1<<15; + pEpilog++; + } + else if (pEpilog[0] == 0xe8bd) + { + // 32-bit instruction + actuallyPopRegMask = pEpilog[1]; + pEpilog += 2; + } + else if (pEpilog[0] == 0xf85d && (pEpilog[1] & 0x0fff) == 0xb04) + { + // we just pop one register + int reg = pEpilog[1] >> 12; + actuallyPopRegMask |= 1 << reg; + pEpilog += 2; + } + + // have we popped some low registers to clean up the stack? + if (stackPopSize == 0 && (actuallyPopRegMask & 0x0f) != 0) + { + // the low registers count towards the stack pop size + if (actuallyPopRegMask & 0x1) + stackPopSize += POINTER_SIZE; + if (actuallyPopRegMask & 0x2) + stackPopSize += POINTER_SIZE; + if (actuallyPopRegMask & 0x4) + stackPopSize += POINTER_SIZE; + if (actuallyPopRegMask & 0x8) + stackPopSize += POINTER_SIZE; + + // remove the bits now accounted for + actuallyPopRegMask &= ~0x0f; + } + + if (r7Cleanup) + { + if (stackPopSize != frameSize) + VERIFY_FAILURE(); + } + else + { + if (r7Frame) + { + // in this case the whole frame size may be larger than the r7 frame size we know about + if (stackPopSize < frameSize) + VERIFY_FAILURE(); + } + else + { + if (stackPopSize != frameSize) + VERIFY_FAILURE(); + } + } + + UInt16 stackCleanupWords = pInfoHeader->ParmRegsPushedCount(); + + if (shouldPopRegMask == actuallyPopRegMask) + { + // we got what we expected + + if ((actuallyPopRegMask & (1<<15)) != 0) + { + // if we popped pc, then this is the end of the epilog + + // however, if we still have pushed argument registers to cleanup, + // we shouldn't get here + if (pInfoHeader->AreParmRegsPushed()) + VERIFY_FAILURE(); + + return true; + } + } + else + { + // does this work out if we assume it's a call that pops + // lr instead of pc and then terminates in a jump to reg? + shouldPopRegMask ^= (1<<15)|(1<<14); + if (shouldPopRegMask == actuallyPopRegMask) + { + // fine + } + else if (shouldPopRegMask == actuallyPopRegMask + (1<<14)) + { + // we expected the epilog to pop lr, but it didn't + // this may be a return with an additional stack cleanup + // or a throw epilog that doesn't need lr anymore + stackCleanupWords += 1; + } + else + { + VERIFY_FAILURE(); + } + } + + if (stackCleanupWords) + { + CHECK_HIJACK_IN_EPILOG(); + + // we may have "ldr pc,[sp],#stackCleanupWords*4" + if (pEpilog[0] == 0xf85d && pEpilog[1] == 0xfb00 + stackCleanupWords*4) + { + // fine, and end of the epilog + pEpilog += 2; + return true; + } + // otherwise we should just have "add sp,#stackCleanupWords*4" + else if (*pEpilog == 0xb000 + stackCleanupWords) + { + pEpilog += 1; + } + else + { + // + VERIFY_FAILURE(); + } + } + + CHECK_HIJACK_IN_EPILOG(); + + // we are satisfied if we see indirect jump through a register here + // may be lr for normal return, or another register for tail calls + if ((*pEpilog & 0xff87) == 0x4700) + return true; + + // otherwise we expect to see a 32-bit branch + if ((pEpilog[0] & 0xf800) == 0xf000 && (pEpilog[1] & 0xd000) == 0x9000) + return true; + + VERIFY_FAILURE(); + + return false; +} +#endif // _ARM_ + +bool EECodeManager::VerifyEpilogBytes(GCInfoHeader * pInfoHeader, Code * pEpilogStart, UInt32 epilogSize) +{ +#ifdef _X86_ + return VerifyEpilogBytesX86(pInfoHeader, pEpilogStart, epilogSize); +#endif // _X86_ +#ifdef _AMD64_ + return VerifyEpilogBytesAMD64(pInfoHeader, pEpilogStart, epilogSize); +#endif // _AMD64_ +#ifdef _ARM_ + return VerifyEpilogBytesARM(pInfoHeader, pEpilogStart, epilogSize); +#endif +} + +void EECodeManager::VerifyProlog(EEMethodInfo * pMethodInfo) +{ +} + +void EECodeManager::VerifyEpilog(EEMethodInfo * pMethodInfo) +{ + // @TODO: verify epilogs of funclets + GCInfoHeader * pHeader = pMethodInfo->GetGCInfoHeader(); + + Int32 epilogStart = -1; + UInt32 epilogCount = 0; + UInt32 epilogSize = 0; + + while (FindNextEpilog(pHeader, pMethodInfo->GetCodeSize(), + pMethodInfo->GetEpilogTable(), &epilogStart, &epilogSize)) + { + ASSERT(epilogStart >= 0); + epilogCount++; + Int32 codeOffset = epilogStart; + Code * ip = ((Code *)pMethodInfo->GetCode()) + codeOffset; + + ASSERT(VerifyEpilogBytes(pHeader, ip, epilogSize)); + } + + ASSERT(epilogCount == pHeader->GetEpilogCount()); +} + +#include "gcdump.h" +void EECodeManager::DumpGCInfo(EEMethodInfo * pMethodInfo, + UInt8 * pbDeltaShortcutTable, + UInt8 * pbUnwindInfoBlob, + UInt8 * pbCallsiteInfoBlob) +{ + GCDump gcd; + GCInfoHeader hdr; + + UInt8 * pbRawGCInfo = pMethodInfo->GetRawGCInfo(); + + GCDump::Tables tables = { pbDeltaShortcutTable, pbUnwindInfoBlob, pbCallsiteInfoBlob }; + + size_t cbHdr = gcd.DumpInfoHeader(pbRawGCInfo, &tables, &hdr); + gcd.DumpGCTable(pbRawGCInfo + cbHdr, &tables, hdr); +} + +#endif // _DEBUG +#endif // !DACCESS_COMPILE + + +// The controlPC parameter is used to decode the right GCInfoHeader in the case of an EH funclet +void EEMethodInfo::Init(PTR_VOID pvCode, UInt32 cbCodeSize, PTR_UInt8 pbRawGCInfo, PTR_VOID pvEHInfo) +{ + m_pvCode = pvCode; + m_cbCodeSize = cbCodeSize; + m_pbRawGCInfo = pbRawGCInfo; + m_pvEHInfo = pvEHInfo; + + m_pbGCInfo = (PTR_UInt8)(size_t)-1; + + m_infoHdr.Init(); +} + +void EEMethodInfo::DecodeGCInfoHeader(UInt32 methodOffset, PTR_UInt8 pbUnwindInfoBlob) +{ + PTR_UInt8 pbGcInfo = m_pbRawGCInfo; + PTR_UInt8 pbStackChangeString; + PTR_UInt8 pbUnwindInfo; + + UInt32 unwindInfoBlobOffset = VarInt::ReadUnsigned(pbGcInfo); + bool inlineUnwindInfo = (unwindInfoBlobOffset == 0); + + if (inlineUnwindInfo) + { + // it is inline.. + pbUnwindInfo = pbGcInfo; + size_t headerSize; + pbStackChangeString = m_infoHdr.DecodeHeader(methodOffset, pbUnwindInfo, &headerSize); + pbGcInfo += headerSize; + } + else + { + // The offset was adjusted by 1 to reserve the 0 encoding for the inline case, so we re-adjust it to + // the actual offset here. + pbUnwindInfo = pbUnwindInfoBlob + unwindInfoBlobOffset - 1; + pbStackChangeString = m_infoHdr.DecodeHeader(methodOffset, pbUnwindInfo, NULL); + } + + m_pbEpilogTable = pbGcInfo; + + // + // skip past epilog table + // + if (!m_infoHdr.IsEpilogAtEnd()) + { + for (UInt32 i = 0; i < m_infoHdr.GetEpilogCount(); i++) + { + VarInt::SkipUnsigned(pbGcInfo); + if (m_infoHdr.HasVaryingEpilogSizes()) + VarInt::SkipUnsigned(pbGcInfo); + } + } + + m_pbGCInfo = pbGcInfo; +} + +PTR_UInt8 EEMethodInfo::GetGCInfo() +{ + ASSERT_MSG(m_pbGCInfo != (PTR_UInt8)(size_t)-1, + "You must call DecodeGCInfoHeader first"); + + ASSERT(m_pbGCInfo != NULL); + return m_pbGCInfo; +} + +PTR_UInt8 EEMethodInfo::GetEpilogTable() +{ + ASSERT_MSG(m_pbGCInfo != (PTR_UInt8)(size_t)-1, + "You must call DecodeGCInfoHeader first"); + + ASSERT(m_pbEpilogTable != NULL); + return m_pbEpilogTable; +} + +GCInfoHeader * EEMethodInfo::GetGCInfoHeader() +{ + ASSERT_MSG(m_pbGCInfo != (PTR_UInt8)(size_t)-1, + "You must call DecodeGCInfoHeader first"); + + return &m_infoHdr; +} diff --git a/src/Native/Runtime/RHCodeMan.h b/src/Native/Runtime/RHCodeMan.h new file mode 100644 index 00000000000..3f3fabbd3f4 --- /dev/null +++ b/src/Native/Runtime/RHCodeMan.h @@ -0,0 +1,101 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +struct REGDISPLAY; +struct GCInfoHeader; +struct GCEnumContext; +class MethodInfo; +enum GCRefKind; + +class EEMethodInfo +{ + PTR_VOID m_pvCode; + PTR_UInt8 m_pbRawGCInfo; + PTR_UInt8 m_pbGCInfo; + PTR_UInt8 m_pbEpilogTable; + PTR_VOID m_pvEHInfo; + UInt32 m_cbCodeSize; + GCInfoHeader m_infoHdr; + +public: + void Init(PTR_VOID pvCode, UInt32 cbCodeSize, PTR_UInt8 pbRawGCInfo, PTR_VOID pvEHInfo); + + void DecodeGCInfoHeader(UInt32 methodOffset, PTR_UInt8 pbUnwindInfoBlob); + + GCInfoHeader * GetGCInfoHeader(); + PTR_VOID GetCode() { return m_pvCode; } + PTR_UInt8 GetRawGCInfo() { return m_pbRawGCInfo; } + PTR_UInt8 GetGCInfo(); + PTR_UInt8 GetEpilogTable(); + PTR_VOID GetEHInfo() { return m_pvEHInfo; } + UInt32 GetCodeSize() { return m_cbCodeSize; } + +}; + +EEMethodInfo * GetEEMethodInfo(MethodInfo * pMethodInfo); + +class EECodeManager +{ +public: + /* + Enumerate all live object references in that function using + the virtual register set. Same reference location cannot be enumerated + multiple times (but all differenct references pointing to the same + object have to be individually enumerated). + */ + static void EnumGcRefs(EEMethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pContext, + GCEnumContext * hCallback, + PTR_UInt8 pbCallsiteStringBlob, + PTR_UInt8 pbDeltaShortcutTable); + + /* + 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, callee-UNsaved + registers are trashed) + Returns success of operation. + */ + static bool UnwindStackFrame(EEMethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pContext); + + static PTR_VOID GetReversePInvokeSaveFrame(EEMethodInfo * pMethodInfo, + REGDISPLAY * pContext); + + static PTR_VOID GetFramePointer(EEMethodInfo * pMethodInfo, + REGDISPLAY * pContext); + + static PTR_PTR_VOID GetReturnAddressLocationForHijack(EEMethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pContext); + + static GCRefKind GetReturnValueKind(EEMethodInfo * pMethodInfo); + + static bool GetEpilogOffset(EEMethodInfo * pMethodInfo, UInt32 codeOffset, UInt32 * epilogOffsetOut, UInt32 * epilogSizeOut); + + static void ** GetReturnAddressLocationFromEpilog(GCInfoHeader * pInfoHeader, REGDISPLAY * pContext, + UInt32 epilogOffset, UInt32 epilogSize); + +#ifdef _DEBUG +public: + static void DumpGCInfo(EEMethodInfo * pMethodInfo, + UInt8 * pbDeltaShortcutTable, + UInt8 * pbUnwindInfoBlob, + UInt8 * pbCallsiteInfoBlob); + + static void VerifyProlog(EEMethodInfo * pMethodInfo); + static void VerifyEpilog(EEMethodInfo * pMethodInfo); + +private: + // This will find the first epilog after the code offset passed in via pEpilogStartOffsetInOut. + // If found, it returns true, false otherwise + static bool FindNextEpilog(GCInfoHeader * pInfoHeader, UInt32 methodSize, PTR_UInt8 pbEpilogTable, + Int32 * pEpilogStartOffsetInOut, UInt32 * pEpilogSizeOut); + + static bool VerifyEpilogBytes(GCInfoHeader * pInfoHeader, Code * pEpilogStart, UInt32 epilogSize); +#endif // _DEBUG +}; diff --git a/src/Native/Runtime/RWLock.cpp b/src/Native/Runtime/RWLock.cpp new file mode 100644 index 00000000000..8c8a47134a7 --- /dev/null +++ b/src/Native/Runtime/RWLock.cpp @@ -0,0 +1,299 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// RWLock.cpp -- adapted from CLR SimpleRWLock.cpp +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "event.h" +#include "rwlock.h" +#include "threadstore.h" +#include "runtimeinstance.h" +#endif // !DACCESS_COMPILE + +// Configurable constants used across our spin locks +// Initialization here is necessary so that we have meaningful values before the runtime is started +// These initial values were selected to match the defaults, but anything reasonable is close enough +struct SpinConstants +{ + UInt32 uInitialDuration; + UInt32 uMaximumDuration; + UInt32 uBackoffFactor; + UInt32 uRepetitions; +} g_SpinConstants = { + 50, // dwInitialDuration + 40000, // dwMaximumDuration - ideally (20000 * max(2, numProc)) + 3, // dwBackoffFactor + 10 // dwRepetitions +}; + +ReaderWriterLock::ReadHolder::ReadHolder(ReaderWriterLock * pLock, bool fAcquireLock) : + m_pLock(pLock) +{ +#ifndef DACCESS_COMPILE + m_fLockAcquired = fAcquireLock; + if (fAcquireLock) + m_pLock->AcquireReadLock(); +#else + UNREFERENCED_PARAMETER(fAcquireLock); +#endif // !DACCESS_COMPILE +} + +ReaderWriterLock::ReadHolder::~ReadHolder() +{ +#ifndef DACCESS_COMPILE + if (m_fLockAcquired) + m_pLock->ReleaseReadLock(); +#endif // !DACCESS_COMPILE +} + +ReaderWriterLock::WriteHolder::WriteHolder(ReaderWriterLock * pLock, bool fAcquireLock) : + m_pLock(pLock) +{ +#ifndef DACCESS_COMPILE + m_fLockAcquired = fAcquireLock; + if (fAcquireLock) + m_pLock->AcquireWriteLock(); +#else + UNREFERENCED_PARAMETER(fAcquireLock); +#endif // !DACCESS_COMPILE +} + +ReaderWriterLock::WriteHolder::~WriteHolder() +{ +#ifndef DACCESS_COMPILE + if (m_fLockAcquired) + m_pLock->ReleaseWriteLock(); +#endif // !DACCESS_COMPILE +} + +ReaderWriterLock::ReaderWriterLock() : + m_RWLock(0) +#if 0 + , m_WriterWaiting(false) +#endif +{ + m_spinCount = ( +#ifndef DACCESS_COMPILE + (PalGetProcessCpuCount() == 1) ? 0 : +#endif + 4000); +} + + +#ifndef DACCESS_COMPILE + +// defined in gcrhenv.cpp +UInt32_BOOL __SwitchToThread(UInt32 dwSleepMSec, UInt32 dwSwitchCount); +extern SYSTEM_INFO g_SystemInfo; + +// Attempt to take the read lock, but do not wait if a writer has the lock. +// Release the lock if successfully acquired. Returns true if the lock was +// taken and released. Returns false if a writer had the lock. +// +// BEWARE: Because this method returns after releasing the lock, you can't +// infer the state of the lock based on the return value. This is currently +// only used to detect if a suspended thread owns the write lock to prevent +// deadlock with the Hijack logic during GC suspension. +// +bool ReaderWriterLock::DangerousTryPulseReadLock() +{ + if (TryAcquireReadLock()) + { + ReleaseReadLock(); + return true; + } + return false; +} + +bool ReaderWriterLock::TryAcquireReadLock() +{ + Int32 RWLock; + + do + { + RWLock = m_RWLock; + if (RWLock == -1) + return false; + ASSERT(RWLock >= 0); + } + while (RWLock != PalInterlockedCompareExchange(&m_RWLock, RWLock+1, RWLock)); + + return true; +} + +void ReaderWriterLock::AcquireReadLock() +{ + if (TryAcquireReadLock()) + return; + + AcquireReadLockWorker(); +} + +void ReaderWriterLock::AcquireReadLockWorker() +{ + UInt32 uSwitchCount = 0; + + for (;;) + { +#if 0 + // @TODO: Validate that we never re-enter the reader lock from a thread that + // already holds it. This scenario will deadlock if there are outstanding + // writers. + + // prevent writers from being starved. This assumes that writers are rare and + // dont hold the lock for a long time. + while (m_WriterWaiting) + { + Int32 spinCount = m_spinCount; + while (spinCount > 0) { + spinCount--; + PalYieldProcessor(); + } + __SwitchToThread(0, ++uSwitchCount); + } +#endif + + if (TryAcquireReadLock()) + return; + + UInt32 uDelay = g_SpinConstants.uInitialDuration; + do + { + if (TryAcquireReadLock()) + return; + + if (g_SystemInfo.dwNumberOfProcessors <= 1) + break; + + // Delay by approximately 2*i clock cycles (Pentium III). + // This is brittle code - future processors may of course execute this + // faster or slower, and future code generators may eliminate the loop altogether. + // The precise value of the delay is not critical, however, and I can't think + // of a better way that isn't machine-dependent - petersol. + int sum = 0; + for (int delayCount = uDelay; --delayCount; ) + { + sum += delayCount; + PalYieldProcessor(); // indicate to the processor that we are spining + } + if (sum == 0) + { + // never executed, just to fool the compiler into thinking sum is live here, + // so that it won't optimize away the loop. + static char dummy; + dummy++; + } + // exponential backoff: wait a factor longer in the next iteration + uDelay *= g_SpinConstants.uBackoffFactor; + } + while (uDelay < g_SpinConstants.uMaximumDuration); + + __SwitchToThread(0, ++uSwitchCount); + } +} + +void ReaderWriterLock::ReleaseReadLock() +{ + Int32 RWLock; + RWLock = PalInterlockedDecrement(&m_RWLock); + ASSERT(RWLock >= 0); +} + + +bool ReaderWriterLock::TryAcquireWriteLock() +{ + Int32 RWLock = PalInterlockedCompareExchange(&m_RWLock, -1, 0); + + ASSERT(RWLock >= 0 || RWLock == -1); + + if (RWLock) + return false; + +#if 0 + m_WriterWaiting = false; +#endif + + return true; +} + +void ReaderWriterLock::AcquireWriteLock() +{ + UInt32 uSwitchCount = 0; + + for (;;) + { + if (TryAcquireWriteLock()) + return; + +#if 0 + // Set the writer waiting word, if not already set, to notify potential readers to wait. + m_WriterWaiting = true; +#endif + + UInt32 uDelay = g_SpinConstants.uInitialDuration; + do + { + if (TryAcquireWriteLock()) + return; + + if (g_SystemInfo.dwNumberOfProcessors <= 1) + { + break; + } + // Delay by approximately 2*i clock cycles (Pentium III). + // This is brittle code - future processors may of course execute this + // faster or slower, and future code generators may eliminate the loop altogether. + // The precise value of the delay is not critical, however, and I can't think + // of a better way that isn't machine-dependent - petersol. + int sum = 0; + for (int delayCount = uDelay; --delayCount; ) + { + sum += delayCount; + PalYieldProcessor(); // indicate to the processor that we are spining + } + if (sum == 0) + { + // never executed, just to fool the compiler into thinking sum is live here, + // so that it won't optimize away the loop. + static char dummy; + dummy++; + } + // exponential backoff: wait a factor longer in the next iteration + uDelay *= g_SpinConstants.uBackoffFactor; + } + while (uDelay < g_SpinConstants.uMaximumDuration); + + __SwitchToThread(0, ++uSwitchCount); + } +} + +void ReaderWriterLock::ReleaseWriteLock() +{ + Int32 RWLock; + RWLock = PalInterlockedExchange(&m_RWLock, 0); + ASSERT(RWLock == -1); +} +#endif // DACCESS_COMPILE diff --git a/src/Native/Runtime/RWLock.h b/src/Native/Runtime/RWLock.h new file mode 100644 index 00000000000..1684813fa1c --- /dev/null +++ b/src/Native/Runtime/RWLock.h @@ -0,0 +1,54 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +class ReaderWriterLock +{ + volatile Int32 m_RWLock; // lock used for R/W synchronization + Int32 m_spinCount; // spin count for a reader waiting for a writer to release the lock + +#if 0 + // used to prevent writers from being starved by readers + // we currently do not prevent writers from starving readers since writers + // are supposed to be rare. + bool m_WriterWaiting; +#endif + + bool TryAcquireReadLock(); + bool TryAcquireWriteLock(); + +public: + class ReadHolder + { + ReaderWriterLock * m_pLock; + bool m_fLockAcquired; + public: + ReadHolder(ReaderWriterLock * pLock, bool fAcquireLock = true); + ~ReadHolder(); + }; + + class WriteHolder + { + ReaderWriterLock * m_pLock; + bool m_fLockAcquired; + public: + WriteHolder(ReaderWriterLock * pLock, bool fAcquireLock = true); + ~WriteHolder(); + }; + + ReaderWriterLock(); + + void AcquireReadLock(); + void ReleaseReadLock(); + + bool DangerousTryPulseReadLock(); + +protected: + void AcquireWriteLock(); + void ReleaseWriteLock(); + + void AcquireReadLockWorker(); + +}; + diff --git a/src/Native/Runtime/Range.h b/src/Native/Runtime/Range.h new file mode 100644 index 00000000000..74b2fba3211 --- /dev/null +++ b/src/Native/Runtime/Range.h @@ -0,0 +1,141 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#pragma once + +namespace rh { namespace util +{ + //--------------------------------------------------------------------------------------------- + // Represents value range [a,b), and provides various convenience methods. + + template + class Range + { + typedef Range THIS_T; + + public: + //----------------------------------------------------------------------------------------- + // Construction + + Range() + : m_start(0), + m_end(0) + {} + + Range(Range const & range) + : m_start(range.m_start), + m_end(range.m_end) + {} + + template + static Range CreateWithEndpoint(VALUE_TYPE start, + VALUE_TYPE end) + { return Range(start, end); } + + template + static Range CreateWithLength(VALUE_TYPE start, LENGTH_TYPE len) + { return Range(start, start + len); } + + //----------------------------------------------------------------------------------------- + // Operations + + THIS_T& operator=(THIS_T const & range) + { m_start = range.m_start; m_end = range.m_end; return *this; } + + bool Equals(THIS_T const & range) const + { return GetStart() == range.GetStart() && GetEnd() == range.GetEnd(); } + + bool operator==(THIS_T const & range) const + { return Equals(range); } + + bool operator!=(THIS_T const & range) const + { return !Equals(range); } + + VALUE_TYPE GetStart() const + { return m_start; } + + VALUE_TYPE GetEnd() const + { return m_end; } + + LENGTH_TYPE GetLength() const + { return m_end - m_start; } + + bool IntersectsWith(THIS_T const &range) const + { return range.GetStart() < GetEnd() && range.GetEnd() > GetStart(); } + + bool IntersectsWith(VALUE_TYPE start, + VALUE_TYPE end) const + { return IntersectsWith(THIS_T(start, end)); } + + bool Contains(THIS_T const &range) const + { return GetStart() <= range.GetStart() && range.GetEnd() <= GetEnd(); } + + bool IsAdjacentTo(THIS_T const &range) const + { return GetEnd() == range.GetStart() || range.GetEnd() == GetStart(); } + + protected: + Range(VALUE_TYPE start, VALUE_TYPE end) + : m_start(start), + m_end(end) + { ASSERT(start <= end); } + + VALUE_TYPE m_start; + VALUE_TYPE m_end; + }; + + //--------------------------------------------------------------------------------------------- + // Represents address range [a,b), and provides various convenience methods. + + class MemRange : public Range + { + typedef Range BASE_T; + + public: + //----------------------------------------------------------------------------------------- + // Construction + + MemRange() + : BASE_T() + {} + + MemRange(void* pvMemStart, + UIntNative cbMemLen) + : BASE_T(reinterpret_cast(pvMemStart), reinterpret_cast(pvMemStart) + cbMemLen) + {} + + MemRange(void* pvMemStart, + void* pvMemEnd) + : BASE_T(reinterpret_cast(pvMemStart), reinterpret_cast(pvMemEnd)) + {} + + MemRange(MemRange const & range) + : BASE_T(range) + { } + + //----------------------------------------------------------------------------------------- + // Operations + + MemRange& operator=(MemRange const & range) + { BASE_T::operator=(range); return *this; } + + UIntNative GetPageCount() const + { + UInt8 *pCurPage = ALIGN_DOWN(GetStart(), OS_PAGE_SIZE); + UInt8 *pEndPage = ALIGN_UP(GetEnd(), OS_PAGE_SIZE); + return (pEndPage - pCurPage) / OS_PAGE_SIZE; + } + + UInt8* GetStartPage() const + { return ALIGN_DOWN(GetStart(), OS_PAGE_SIZE); } + + // The page immediately following the last page contained by this range. + UInt8* GetEndPage() const + { return ALIGN_UP(GetEnd(), OS_PAGE_SIZE); } + + MemRange GetPageRange() const + { return MemRange(GetStartPage(), GetEndPage()); } + }; +}// namespace util +}// namespace rh + diff --git a/src/Native/Runtime/RedhawkWarnings.h b/src/Native/Runtime/RedhawkWarnings.h new file mode 100644 index 00000000000..deb12c64c1f --- /dev/null +++ b/src/Native/Runtime/RedhawkWarnings.h @@ -0,0 +1,10 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Disable some commonly ignored warnings +// + +MSVC_DISABLE_WARNING(4200) // nonstandard extension used : zero-sized array in struct/union \ No newline at end of file diff --git a/src/Native/Runtime/RestrictedCallouts.cpp b/src/Native/Runtime/RestrictedCallouts.cpp new file mode 100644 index 00000000000..3b5943cc34a --- /dev/null +++ b/src/Native/Runtime/RestrictedCallouts.cpp @@ -0,0 +1,248 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Callouts from the unmanaged portion of the runtime to C# helpers made during garbage collections. See +// RestrictedCallouts.h for more detail. +// + +#include "commontypes.h" +#include "commonmacros.h" +#include "daccess.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "holder.h" +#include "gcrhinterface.h" +#include "module.h" +#include "rhbinder.h" +#include "crst.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "eetype.h" +#include "objectlayout.h" +#include "event.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "threadstore.h" +#include "restrictedcallouts.h" + +// The head of the chains of GC callouts, one per callout type. +RestrictedCallouts::GcRestrictedCallout * RestrictedCallouts::s_rgGcRestrictedCallouts[GCRC_Count] = { 0 }; + +// The head of the chain of HandleTable callouts. +RestrictedCallouts::HandleTableRestrictedCallout * RestrictedCallouts::s_pHandleTableRestrictedCallouts = NULL; + +// Lock protecting access to s_rgGcRestrictedCallouts and s_pHandleTableRestrictedCallouts during registration +// and unregistration (not used during actual callbacks since everything is single threaded then). +CrstStatic RestrictedCallouts::s_sLock; + +// One time startup initialization. +bool RestrictedCallouts::Initialize() +{ + s_sLock.Init(CrstRestrictedCallouts, CRST_DEFAULT); + + return true; +} + +// Register callback of the given type to the method with the given address. The most recently registered +// callbacks are called first. Returns true on success, false if insufficient memory was available for the +// registration. +bool RestrictedCallouts::RegisterGcCallout(GcRestrictedCalloutKind eKind, void * pCalloutMethod) +{ + // Validate callout kind. + if (eKind >= GCRC_Count) + { + ASSERT_UNCONDITIONALLY("Invalid GC restricted callout kind."); + RhFailFast(); + } + + GcRestrictedCallout * pCallout = new GcRestrictedCallout(); + if (pCallout == NULL) + return false; + + pCallout->m_pCalloutMethod = pCalloutMethod; + + CrstHolder lh(&s_sLock); + + // Link new callout to head of the chain according to its type. + pCallout->m_pNext = s_rgGcRestrictedCallouts[eKind]; + s_rgGcRestrictedCallouts[eKind] = pCallout; + + return true; +} + +// Unregister a previously registered callout. Removes the first registration that matches on both callout +// kind and address. Causes a fail fast if the registration doesn't exist. +void RestrictedCallouts::UnregisterGcCallout(GcRestrictedCalloutKind eKind, void * pCalloutMethod) +{ + // Validate callout kind. + if (eKind >= GCRC_Count) + { + ASSERT_UNCONDITIONALLY("Invalid GC restricted callout kind."); + RhFailFast(); + } + + CrstHolder lh(&s_sLock); + + GcRestrictedCallout * pCurrCallout = s_rgGcRestrictedCallouts[eKind]; + GcRestrictedCallout * pPrevCallout = NULL; + + while (pCurrCallout) + { + if (pCurrCallout->m_pCalloutMethod == pCalloutMethod) + { + // Found a matching entry, remove it from the chain. + if (pPrevCallout) + pPrevCallout->m_pNext = pCurrCallout->m_pNext; + else + s_rgGcRestrictedCallouts[eKind] = pCurrCallout->m_pNext; + + delete pCurrCallout; + + return; + } + + pPrevCallout = pCurrCallout; + pCurrCallout = pCurrCallout->m_pNext; + } + + // If we get here we didn't find a matching registration, indicating a bug on the part of the caller. + ASSERT_UNCONDITIONALLY("Attempted to unregister restricted callout that wasn't registered."); + RhFailFast(); +} + +// Register callback for the "is alive" property of ref counted handles with objects of the given type (the +// type match must be exact). The most recently registered callbacks are called first. Returns true on +// success, false if insufficient memory was available for the registration. +bool RestrictedCallouts::RegisterRefCountedHandleCallback(void * pCalloutMethod, EEType * pTypeFilter) +{ + HandleTableRestrictedCallout * pCallout = new HandleTableRestrictedCallout(); + if (pCallout == NULL) + return false; + + pCallout->m_pCalloutMethod = pCalloutMethod; + pCallout->m_pTypeFilter = pTypeFilter; + + CrstHolder lh(&s_sLock); + + // Link new callout to head of the chain. + pCallout->m_pNext = s_pHandleTableRestrictedCallouts; + s_pHandleTableRestrictedCallouts = pCallout; + + return true; +} + +// Unregister a previously registered callout. Removes the first registration that matches on both callout +// address and filter type. Causes a fail fast if the registration doesn't exist. +void RestrictedCallouts::UnregisterRefCountedHandleCallback(void * pCalloutMethod, EEType * pTypeFilter) +{ + CrstHolder lh(&s_sLock); + + HandleTableRestrictedCallout * pCurrCallout = s_pHandleTableRestrictedCallouts; + HandleTableRestrictedCallout * pPrevCallout = NULL; + + while (pCurrCallout) + { + if ((pCurrCallout->m_pCalloutMethod == pCalloutMethod) && + (pCurrCallout->m_pTypeFilter == pTypeFilter)) + { + // Found a matching entry, remove it from the chain. + if (pPrevCallout) + pPrevCallout->m_pNext = pCurrCallout->m_pNext; + else + s_pHandleTableRestrictedCallouts = pCurrCallout->m_pNext; + + delete pCurrCallout; + + return; + } + + pPrevCallout = pCurrCallout; + pCurrCallout = pCurrCallout->m_pNext; + } + + // If we get here we didn't find a matching registration, indicating a bug on the part of the caller. + ASSERT_UNCONDITIONALLY("Attempted to unregister restricted callout that wasn't registered."); + RhFailFast(); +} + +// Invoke all the registered GC callouts of the given kind. The condemned generation of the current collection +// is passed along to the callouts. +void RestrictedCallouts::InvokeGcCallouts(GcRestrictedCalloutKind eKind, UInt32 uiCondemnedGeneration) +{ + ASSERT(eKind < GCRC_Count); + + // It is illegal for any of the callouts to trigger a GC. + Thread * pThread = ThreadStore::GetCurrentThread(); + pThread->SetDoNotTriggerGc(); + + // Due to the above we have better suppress GC stress. + bool fGcStressWasSuppressed = pThread->IsSuppressGcStressSet(); + if (!fGcStressWasSuppressed) + pThread->SetSuppressGcStress(); + + GcRestrictedCallout * pCurrCallout = s_rgGcRestrictedCallouts[eKind]; + while (pCurrCallout) + { + // Make the callout. + ((GcRestrictedCallbackFunction)pCurrCallout->m_pCalloutMethod)(uiCondemnedGeneration); + + pCurrCallout = pCurrCallout->m_pNext; + } + + // Revert GC stress mode if we changed it. + if (!fGcStressWasSuppressed) + pThread->ClearSuppressGcStress(); + + pThread->ClearDoNotTriggerGc(); +} + +// Invoke all the registered ref counted handle callouts for the given object extracted from the handle. The +// result is the union of the results for all the handlers that matched the object type (i.e. if one of them +// returned true the overall result is true otherwise false is returned (which includes the case where no +// handlers matched)). Since there should be no other side-effects of the callout, the invocations cease as +// soon as a handler returns true. +bool RestrictedCallouts::InvokeRefCountedHandleCallbacks(Object * pObject) +{ + bool fResult = false; + + // It is illegal for any of the callouts to trigger a GC. + Thread * pThread = ThreadStore::GetCurrentThread(); + pThread->SetDoNotTriggerGc(); + + // Due to the above we have better suppress GC stress. + bool fGcStressWasSuppressed = pThread->IsSuppressGcStressSet(); + if (!fGcStressWasSuppressed) + pThread->SetSuppressGcStress(); + + HandleTableRestrictedCallout * pCurrCallout = s_pHandleTableRestrictedCallouts; + while (pCurrCallout) + { + if (pObject->get_SafeEEType() == pCurrCallout->m_pTypeFilter) + { + // Make the callout. Return true to our caller as soon as we see a true result here. + if (((HandleTableRestrictedCallbackFunction)pCurrCallout->m_pCalloutMethod)(pObject)) + { + fResult = true; + goto Done; + } + } + + pCurrCallout = pCurrCallout->m_pNext; + } + + Done: + // Revert GC stress mode if we changed it. + if (!fGcStressWasSuppressed) + pThread->ClearSuppressGcStress(); + + pThread->ClearDoNotTriggerGc(); + + return fResult; +} diff --git a/src/Native/Runtime/RestrictedCallouts.h b/src/Native/Runtime/RestrictedCallouts.h new file mode 100644 index 00000000000..456d5516f6a --- /dev/null +++ b/src/Native/Runtime/RestrictedCallouts.h @@ -0,0 +1,104 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Restricted callouts refer to calls to classlib defined code written in C# made from the runtime during a +// garbage collection. As such these C# methods are constrained in what they can do and must be written very +// carefully. The most obvious restriction is that they cannot trigger a GC (by attempting to allocate memory +// for example) since that would lead to an immediate deadlock. +// +// Known constraints: +// * No triggering of GCs (new, boxing value types, foreach over a type that allocates for its IEnumerator, +// calling GC.Collect etc.). +// * No exceptions can leak out of the callout. +// * No blocking (or expensive) operations that could starve the GC or potentially lead to deadlocks. +// * No use of runtime facilities that check whether a GC is in progress, these will deadlock. The big +// example we know about so far is making a p/invoke call. +// * For the AfterMarkPhase callout special attention must be paid to avoid any action that reads the EEType* +// from an object header (e.g. casting). At this point the GC may have mark bits set in the the pointer. +// + +class EEType; + +// Enum for the various GC callouts available. The values and their meanings are a contract with the classlib +// so be careful altering these. +enum GcRestrictedCalloutKind +{ + GCRC_StartCollection = 0, // Collection is about to begin + GCRC_EndCollection = 1, // Collection has completed + GCRC_AfterMarkPhase = 2, // All live objects are marked (not including ready for finalization + // objects), no handles have been cleared + GCRC_Count // Maximum number of callout types +}; + +class RestrictedCallouts +{ +public: + // One time startup initialization. + static bool Initialize(); + + // Register callback of the given type to the method with the given address. The most recently registered + // callbacks are called first. Returns true on success, false if insufficient memory was available for the + // registration. + static bool RegisterGcCallout(GcRestrictedCalloutKind eKind, void * pCalloutMethod); + + // Unregister a previously registered callout. Removes the first registration that matches on both callout + // kind and address. Causes a fail fast if the registration doesn't exist. + static void UnregisterGcCallout(GcRestrictedCalloutKind eKind, void * pCalloutMethod); + + // Register callback for the "is alive" property of ref counted handles with objects of the given type + // (the type match must be exact). The most recently registered callbacks are called first. Returns true + // on success, false if insufficient memory was available for the registration. + static bool RegisterRefCountedHandleCallback(void * pCalloutMethod, EEType * pTypeFilter); + + // Unregister a previously registered callout. Removes the first registration that matches on both callout + // address and filter type. Causes a fail fast if the registration doesn't exist. + static void UnregisterRefCountedHandleCallback(void * pCalloutMethod, EEType * pTypeFilter); + + // Invoke all the registered GC callouts of the given kind. The condemned generation of the current + // collection is passed along to the callouts. + static void InvokeGcCallouts(GcRestrictedCalloutKind eKind, UInt32 uiCondemnedGeneration); + + // Invoke all the registered ref counted handle callouts for the given object extracted from the handle. + // The result is the union of the results for all the handlers that matched the object type (i.e. if one + // of them returned true the overall result is true otherwise false is returned (which includes the case + // where no handlers matched)). Since there should be no other side-effects of the callout, the + // invocations cease as soon as a handler returns true. + static bool InvokeRefCountedHandleCallbacks(Object * pObject); + +private: + // Context struct used to record which GC callbacks are registered to be made (we allow multiple + // registrations). + struct GcRestrictedCallout + { + GcRestrictedCallout * m_pNext; // Next callout to make or NULL + void * m_pCalloutMethod; // Address of code to call + }; + + // The head of the chains of GC callouts, one per callout type. + static GcRestrictedCallout * s_rgGcRestrictedCallouts[GCRC_Count]; + + // The handle table only has one callout type, for ref-counted handles. But it allows the client to + // specify a type filter: i.e. only handles with an object of the exact type specified will have the + // callout invoked. + struct HandleTableRestrictedCallout + { + HandleTableRestrictedCallout * m_pNext; // Next callout to make or NULL + void * m_pCalloutMethod; // Address of code to call + EEType * m_pTypeFilter; // Type of object for which callout will be made + }; + + // The head of the chain of HandleTable callouts. + static HandleTableRestrictedCallout * s_pHandleTableRestrictedCallouts; + + // Lock protecting access to s_rgGcRestrictedCallouts and s_pHandleTableRestrictedCallouts during + // registration and unregistration (not used during actual callbacks since everything is single threaded + // then). + static CrstStatic s_sLock; + + // Prototypes for the callouts. + typedef void (__fastcall * GcRestrictedCallbackFunction)(UInt32 uiCondemnedGeneration); + typedef Boolean (__fastcall * HandleTableRestrictedCallbackFunction)(Object * pObject); +}; diff --git a/src/Native/Runtime/RhConfig.cpp b/src/Native/Runtime/RhConfig.cpp new file mode 100644 index 00000000000..e2aa9e43029 --- /dev/null +++ b/src/Native/Runtime/RhConfig.cpp @@ -0,0 +1,302 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "event.h" +#include "rwlock.h" +#include "threadstore.h" +#include "runtimeinstance.h" +#include "module.h" +#include "RhConfig.h" + + +UInt32 RhConfig::ReadConfigValue(_In_z_ WCHAR *wszName) +{ + WCHAR wszBuffer[CONFIG_VAL_MAXLEN + 1]; // 8 hex digits plus a nul terminator. + const UInt32 cchBuffer = sizeof(wszBuffer) / sizeof(wszBuffer[0]); + + UInt32 cchResult = 0; + +#ifdef RH_ENVIRONMENT_VARIABLE_CONFIG_ENABLED + cchResult = PalGetEnvironmentVariableW(wszName, wszBuffer, cchBuffer); +#endif // RH_ENVIRONMENT_VARIABLE_CONFIG_ENABLED + + //if the config key wasn't found in the environment + if ((cchResult == 0) || (cchResult >= cchBuffer)) + cchResult = GetIniVariable(wszName, wszBuffer, cchBuffer); + + if ((cchResult == 0) || (cchResult >= cchBuffer)) + return 0; + + UInt32 uiResult = 0; + + for (UInt32 i = 0; i < cchResult; i++) + { + uiResult <<= 4; + + WCHAR ch = wszBuffer[i]; + if ((ch >= L'0') && (ch <= L'9')) + uiResult += ch - L'0'; + else if ((ch >= L'a') && (ch <= L'f')) + uiResult += (ch - L'a') + 10; + else if ((ch >= L'A') && (ch <= L'F')) + uiResult += (ch - L'A') + 10; + else + return 0; + } + + return uiResult; +} + +//reads a config value from rhconfig.ini into outputBuffer buffer returning the length of the value. +//lazily reads the file so if the file is not yet read, it will read it on first called +//if the file is not avaliable, or unreadable zero will always be returned +//cchOuputBuffer is the maximum number of characters to write to outputBuffer +//cchOutputBuffer must be a size >= CONFIG_VAL_MAXLEN + 1 +UInt32 RhConfig::GetIniVariable(_In_z_ WCHAR* configName, _Out_writes_all_(cchBuff) WCHAR* outputBuffer, _In_ UInt32 cchOuputBuffer) +{ + //the buffer needs to be big enough to read the value buffer + null terminator + if (cchOuputBuffer < CONFIG_VAL_MAXLEN + 1) + { + return 0; + } + + //if we haven't read the config yet try to read + if (g_iniSettings == NULL) + { + ReadConfigIni(); + } + + //if the config wasn't read or reading failed return 0 immediately + if (g_iniSettings == CONFIG_INI_NOT_AVAIL) + { + return 0; + } + + //find the first name which matches (case insensitive to be compat with environment variable counterpart) + for (int iSettings = 0; iSettings < RCV_Count; iSettings++) + { + if (_wcsicmp(configName, ((ConfigPair*)g_iniSettings)[iSettings].Key) == 0) + { + bool nullTerm = FALSE; + + UInt32 iValue; + + for (iValue = 0; (iValue < CONFIG_VAL_MAXLEN + 1) && (iValue < (Int32)cchOuputBuffer); iValue++) + { + outputBuffer[iValue] = ((ConfigPair*)g_iniSettings)[iSettings].Value[iValue]; + + if (outputBuffer[iValue] == '\0') + { + nullTerm = true; + break; + } + } + + //return the length of the config value if null terminated else return zero + return nullTerm ? iValue : 0; + } + } + + //if the config key was not found return 0 + return 0; +} + +//reads the configuration values from rhconfig.ini and updates g_iniSettings +//if the file is read succesfully and g_iniSettings will be set to a valid ConfigPair[] of length RCV_Count. +//if the file does not exist or reading the file fails, g_iniSettings is set to CONFIG_INI_NOT_AVAIL +//NOTE: all return paths must set g_iniSettings +void RhConfig::ReadConfigIni() +{ + if (g_iniSettings == NULL) + { + WCHAR* configPath = GetConfigPath(); + + //if we couldn't determine the path to the config set g_iniSettings to CONGIF_NOT_AVAIL + if (configPath == NULL) + { + //only set if another thread hasn't initialized the buffer yet, otherwise ignore and let the first setter win + PalInterlockedCompareExchangePointer(&g_iniSettings, CONFIG_INI_NOT_AVAIL, NULL); + + return; + } + + //buffer is max file size + 1 for null terminator if needed + char buff[CONFIG_FILE_MAXLEN + 1]; + + //if the file read failed or the file is bigger than the specified buffer this will return zero + UInt32 fSize = PalReadFileContents(configPath, buff, CONFIG_FILE_MAXLEN); + + //ensure the buffer is null terminated + buff[fSize] = '\0'; + + //delete the configPath + delete[] configPath; + + //if reading the file contents failed set g_iniSettings to CONFIG_INI_NOT_AVAIL + if (fSize == 0) + { + //only set if another thread hasn't initialized the buffer yet, otherwise ignore and let the first setter win + PalInterlockedCompareExchangePointer(&g_iniSettings, CONFIG_INI_NOT_AVAIL, NULL); + + return; + } + + ConfigPair* iniBuff = new ConfigPair[RCV_Count]; + + UInt32 iBuff = 0; + UInt32 iIniBuff = 0; + char* currLine; + + //while we haven't reached the max number of config pairs, or the end of the file, read the next line + while (iIniBuff < RCV_Count && iBuff < fSize) + { + //'trim' the leading whitespace + while (priv_isspace(buff[iBuff]) && (iBuff < fSize)) + iBuff++; + + currLine = &buff[iBuff]; + + //find the end of the line + while ((buff[iBuff] != '\n') && (buff[iBuff] != '\r') && (iBuff < fSize)) + iBuff++; + + //null terminate the line + buff[iBuff] = '\0'; + + //parse the line + //only increment iIniBuff if the parsing succeeded otherwise reuse the config struct + if (ParseConfigLine(&iniBuff[iIniBuff], currLine)) + { + iIniBuff++; + } + + //advance to the next line; + iBuff++; + } + + //initialize the remaining config pairs to "\0" + while (iIniBuff < RCV_Count) + { + iniBuff[iIniBuff].Key[0] = '\0'; + iniBuff[iIniBuff].Value[0] = '\0'; + iIniBuff++; + } + + //if another thread initialized first let the first setter win + //delete the iniBuff to avoid leaking memory + if (PalInterlockedCompareExchangePointer(&g_iniSettings, iniBuff, NULL) != NULL) + { + delete[] iniBuff; + } + } + + return; +} + +//returns the path to the runtime configuration ini +_Ret_maybenull_z_ WCHAR* RhConfig::GetConfigPath() +{ + + WCHAR* exePathBuff; + + //get the path to rhconfig.ini, this file is expected to live along side the app + //to build the path get the process executable module full path strip off the file name and + //append rhconfig.ini + Int32 pathLen = PalGetModuleFileName(&exePathBuff, NULL); + + if (pathLen <= 0) + { + return NULL; + } + UInt32 iLastBackslash = 0; + + for (UInt32 iPath = pathLen - 1; iPath > 0; iPath--) + { + if (exePathBuff[iPath] == '\\') + { + iLastBackslash = iPath; + break; + } + } + + if (iLastBackslash == 0) + { + return NULL; + } + + WCHAR* configPath = new WCHAR[iLastBackslash + 1 + wcslen(CONFIG_INI_FILENAME) + 1]; + + //copy the path base and file name + for (UInt32 i = 0; i <= iLastBackslash; i++) + { + configPath[i] = exePathBuff[i]; + } + + for (UInt32 i = 0; i <= wcslen(CONFIG_INI_FILENAME); i++) + { + configPath[i + iLastBackslash + 1] = CONFIG_INI_FILENAME[i]; + } + + return configPath; +} + +//Parses one line of rhconfig.ini and populates values in the passed in configPair +//returns: true if the parsing was successful, false if the parsing failed. +//NOTE: if the method fails configPair is left in an unitialized state +bool RhConfig::ParseConfigLine(_Out_ ConfigPair* configPair, _In_z_ const char * line) +{ + UInt32 iLine = 0; + UInt32 iKey = 0; + UInt32 iVal = 0; + + //while we haven't reached the end of the key signalled by '=', or the end of the line, or the key maxlen + while (line[iLine] != '=' && line[iLine] != '\0' && iKey < CONFIG_KEY_MAXLEN) + { + configPair->Key[iKey++] = line[iLine++]; + } + + //if the current char is not '=' we reached the key maxlen, or the line ended return false + if (line[iLine] != '=') + { + return FALSE; + } + + configPair->Key[iKey] = '\0'; + + //increment to start of the value + iLine++; + + //while we haven't reached the end of the line, or val maxlen + while (line[iLine] != '\0' && iVal < CONFIG_VAL_MAXLEN) + { + configPair->Value[iVal++] = line[iLine++]; + } + + //if the current char is not '\0' we didn't reach the end of the line return false + if (line[iLine] != '\0') + { + return FALSE; + } + + configPair->Value[iVal] = '\0'; + + return TRUE; +} + +#endif diff --git a/src/Native/Runtime/RhConfig.h b/src/Native/Runtime/RhConfig.h new file mode 100644 index 00000000000..4045db0b4b3 --- /dev/null +++ b/src/Native/Runtime/RhConfig.h @@ -0,0 +1,130 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Provides simple configuration support through environment variables. Each variable is lazily inspected on +// first query and the resulting value cached for future use. To keep things simple we support reading only +// 32-bit hex quantities and a zero value is considered equivalent to the environment variable not being +// defined. We can get more sophisticated if needs be, but the hope is that very few configuration values are +// exposed in this manner. +// +// Values can also be configured through an rhconfig.ini file. The file must be and ASCII text file, must be +// placed next to the executing assembly, and be named rhconfig.ini. The file consists of one config entry per line +// in the format: = +// example: +// RH_HeapVerify=1 +// RH_BreakOnAssert=1 +// + + +#ifndef DACCESS_COMPILE + +#if defined(_DEBUG) || !defined(APP_LOCAL_RUNTIME) +#define RH_ENVIRONMENT_VARIABLE_CONFIG_ENABLED +#endif + +class RhConfig +{ + +#define CONFIG_INI_FILENAME L"rhconfig.ini" +#define CONFIG_INI_NOT_AVAIL (void*)0x1 //signal for ini file failed to load +#define CONFIG_KEY_MAXLEN 50 //arbitrary max length of config keys increase if needed +#define CONFIG_VAL_MAXLEN 8 //32 bit uint in hex + +private: + struct ConfigPair + { + public: + WCHAR Key[CONFIG_KEY_MAXLEN + 1]; //maxlen + null terminator + WCHAR Value[CONFIG_VAL_MAXLEN + 1]; //maxlen + null terminator + }; + + //g_iniSettings is a buffer of ConfigPair structs which when initialized is of length RCV_Count + //the first N settings which are set in rhconfig.ini will be initialized and the remainder with have + //empty string "\0" as a Key and Value + // + //if the buffer has not been initialized (ie the ini file has not been read) the value will be NULL + //if we already attempted to initialize the file and could not find or read the contents the + //value will be CONFIG_INI_NOT_AVAIL to distinguish from the unitialized buffer. + // + //NOTE: g_iniSettings is only set in ReadConfigIni and must be set atomically only once + // using PalInterlockedCompareExchangePointer to avoid races when initializing +private: + void* volatile g_iniSettings = NULL; + +public: + +#define DEFINE_VALUE_ACCESSOR(_name) \ + UInt32 Get##_name() \ + { \ + if (m_uiConfigValuesRead & (1 << RCV_##_name)) \ + return m_uiConfigValues[RCV_##_name]; \ + UInt32 uiValue = ReadConfigValue(L"RH_" L ## #_name); \ + m_uiConfigValues[RCV_##_name] = uiValue; \ + m_uiConfigValuesRead |= 1 << RCV_##_name; \ + return uiValue; \ + } + + +#ifdef _DEBUG +#define DEBUG_CONFIG_VALUE(_name) DEFINE_VALUE_ACCESSOR(_name) +#else +#define DEBUG_CONFIG_VALUE(_name) +#endif +#define RETAIL_CONFIG_VALUE(_name) DEFINE_VALUE_ACCESSOR(_name) +#include "RhConfigValues.h" +#undef DEBUG_CONFIG_VALUE +#undef RETAIL_CONFIG_VALUE + +private: + + UInt32 ReadConfigValue(_In_z_ WCHAR *wszName); + + enum RhConfigValue + { +#define DEBUG_CONFIG_VALUE(_name) RCV_##_name, +#define RETAIL_CONFIG_VALUE(_name) RCV_##_name, +#include "RhConfigValues.h" +#undef DEBUG_CONFIG_VALUE +#undef RETAIL_CONFIG_VALUE + RCV_Count + }; + +//accomidate for the maximum number of config values plus sizable buffer for whitespace 2K +#define CONFIG_FILE_MAXLEN RCV_Count * sizeof(ConfigPair) + 2000 + +private: + _Ret_maybenull_z_ WCHAR* RhConfig::GetConfigPath(); + + //Parses one line of rhconfig.ini and populates values in the passed in configPair + //returns: true if the parsing was successful, false if the parsing failed. + //NOTE: if the method fails configPair is left in an unitialized state + bool ParseConfigLine(_Out_ ConfigPair* configPair, _In_z_ const char * line); + + //reads the configuration values from rhconfig.ini and updates g_iniSettings + //if the file is read succesfully and g_iniSettings will be set to a valid ConfigPair[] of length RCV_Count. + //if the file does not exist or reading the file fails, g_iniSettings is set to CONFIG_INI_NOT_AVAIL + //NOTE: all return paths must set g_iniSettings + void ReadConfigIni(); + + //reads a config value from rhconfig.ini into outputBuffer buffer returning the length of the value. + //lazily reads the file so if the file is not yet read, it will read it on first called + //if the file is not avaliable, or unreadable zero will always be returned + //cchOuputBuffer is the maximum number of characters to write to outputBuffer + UInt32 GetIniVariable(_In_z_ WCHAR* configName, _Out_writes_all_(cchBuff) WCHAR* outputBuffer, _In_ UInt32 cchOuputBuffer); + + static bool priv_isspace(char c) + { + return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'); + } + + + UInt32 m_uiConfigValuesRead; + UInt32 m_uiConfigValues[RCV_Count]; +}; + +extern RhConfig * g_pRhConfig; + +#endif //!DACCESS_COMPILE \ No newline at end of file diff --git a/src/Native/Runtime/RhConfigValues.h b/src/Native/Runtime/RhConfigValues.h new file mode 100644 index 00000000000..f4f18d545f2 --- /dev/null +++ b/src/Native/Runtime/RhConfigValues.h @@ -0,0 +1,25 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Definitions of each configuration value used by the RhConfig class. +// +// Each variable is lazily inspected on first query and the resulting value cached for future use. To keep +// things simple we support reading only 32-bit hex quantities and a zero value is considered equivalent to +// the environment variable not being defined. We can get more sophisticated if needs be, but the hope is that +// very few configuration values are exposed in this manner. +// + +DEBUG_CONFIG_VALUE(BreakOnAssert) +RETAIL_CONFIG_VALUE(HeapVerify) +RETAIL_CONFIG_VALUE(StressLogLevel) +RETAIL_CONFIG_VALUE(TotalStressLogSize) +RETAIL_CONFIG_VALUE(DisableBGC) +DEBUG_CONFIG_VALUE(DisallowRuntimeServicesFallback) +DEBUG_CONFIG_VALUE(GcStressThrottleMode) // gcstm_TriggerAlways / gcstm_TriggerOnFirstHit / gcstm_TriggerRandom +DEBUG_CONFIG_VALUE(GcStressFreqCallsite) // Number of times to force GC out of GcStressFreqDenom (for GCSTM_RANDOM) +DEBUG_CONFIG_VALUE(GcStressFreqLoop) // Number of times to force GC out of GcStressFreqDenom (for GCSTM_RANDOM) +DEBUG_CONFIG_VALUE(GcStressFreqDenom) // Denominator defining frequencies above, 10,000 used when left unspecified (for GCSTM_RANDOM) +DEBUG_CONFIG_VALUE(GcStressSeed) // Specify Seed for random generator (for GCSTM_RANDOM) diff --git a/src/Native/Runtime/RuntimeInstance.cpp b/src/Native/Runtime/RuntimeInstance.cpp new file mode 100644 index 00000000000..65bd659c4c7 --- /dev/null +++ b/src/Native/Runtime/RuntimeInstance.cpp @@ -0,0 +1,1737 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "slist.h" +#include "holder.h" +#include "crst.h" +#include "rhbinder.h" // for GenericInstanceDesc +#include "rwlock.h" +#include "runtimeinstance.h" +#include "event.h" +#include "threadstore.h" +#include "gcrhinterface.h" +#include "module.h" +#include "eetype.h" +#include "genericinstance.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "debugeventsource.h" + +#include "commonmacros.inl" +#include "slist.inl" +#include "eetype.inl" +#include "optionalfields.inl" + +#ifdef FEATURE_GC_STRESS +enum HijackType { htLoop, htCallsite }; +bool ShouldHijackForGcStress(UIntNative CallsiteIP, HijackType ht); +#endif // FEATURE_GC_STRESS +#else +#include "rhbinder.h" +#include "runtimeinstance.h" +#include "gcrhinterface.h" +#include "module.h" +#include "genericinstance.h" + +#include "slist.inl" +#endif + +#include "shash.h" +#include "shash.inl" + +#ifndef DACCESS_COMPILE +COOP_PINVOKE_HELPER(UInt8 *, RhSetErrorInfoBuffer, (UInt8 * pNewBuffer)) +{ + return (UInt8 *) PalSetWerDataBuffer(pNewBuffer); +} +#endif // DACCESS_COMPILE + +RuntimeInstance::ModuleIterator::ModuleIterator() : + m_readHolder(&GetRuntimeInstance()->m_ModuleListLock), + m_pCurrentPosition(GetRuntimeInstance()->GetModuleList()->GetHead()) +{ +} + +SList* RuntimeInstance::GetModuleList() +{ + return dac_cast)>( dac_cast(this) + offsetof(RuntimeInstance, m_ModuleList)); +} + +RuntimeInstance::ModuleIterator::~ModuleIterator() +{ +} + +PTR_Module RuntimeInstance::ModuleIterator::GetNext() +{ + PTR_Module pResult = m_pCurrentPosition; + if (NULL != pResult) + m_pCurrentPosition = pResult->m_pNext; + return pResult; +} + + +ThreadStore * RuntimeInstance::GetThreadStore() +{ + return m_pThreadStore; +} + +Module * RuntimeInstance::FindModuleByAddress(PTR_VOID pvAddress) +{ + FOREACH_MODULE(pModule) + { + if (pModule->ContainsCodeAddress(pvAddress) || + pModule->ContainsDataAddress(pvAddress) || + pModule->ContainsReadOnlyDataAddress(pvAddress) || + pModule->ContainsStubAddress(pvAddress)) + { + return pModule; + } + } + END_FOREACH_MODULE; + + return NULL; +} + +Module * RuntimeInstance::FindModuleByCodeAddress(PTR_VOID pvAddress) +{ + FOREACH_MODULE(pModule) + { + if (pModule->ContainsCodeAddress(pvAddress)) + return pModule; + } + END_FOREACH_MODULE; + + return NULL; +} + +Module * RuntimeInstance::FindModuleByDataAddress(PTR_VOID pvAddress) +{ + FOREACH_MODULE(pModule) + { + if (pModule->ContainsDataAddress(pvAddress)) + return pModule; + } + END_FOREACH_MODULE; + + return NULL; +} + +Module * RuntimeInstance::FindModuleByReadOnlyDataAddress(PTR_VOID pvAddress) +{ + FOREACH_MODULE(pModule) + { + if (pModule->ContainsReadOnlyDataAddress(pvAddress)) + return pModule; + } + END_FOREACH_MODULE; + + return NULL; +} + +void RuntimeInstance::EnumerateModulesUnderLock(EnumerateModulesCallbackPFN pCallback, void *pvContext) +{ + ASSERT(pCallback != NULL); + + FOREACH_MODULE(pModule) + { + (*pCallback)(pModule, pvContext); + } + END_FOREACH_MODULE; +} + +COOP_PINVOKE_HELPER(UInt8 *, RhFindMethodStartAddress, (void * codeAddr)) +{ + return dac_cast(GetRuntimeInstance()->FindMethodStartAddress(dac_cast(codeAddr))); +} + +PTR_UInt8 RuntimeInstance::FindMethodStartAddress(PTR_VOID ControlPC) +{ + FOREACH_MODULE(pModule) + { + if (pModule->ContainsCodeAddress(ControlPC)) + { + return pModule->FindMethodStartAddress(ControlPC); + } + } + END_FOREACH_MODULE; + + return NULL; +} + +ICodeManager * RuntimeInstance::FindCodeManagerByAddress(PTR_VOID pvAddress) +{ + ReaderWriterLock::ReadHolder read(&m_ModuleListLock); + + for (Module * pModule = m_ModuleList.GetHead(); pModule != NULL; pModule = pModule->m_pNext) + { + if (pModule->ContainsCodeAddress(pvAddress)) + return pModule; + } + + // TODO: JIT support in DAC +#ifndef DACCESS_COMPILE +#ifdef FEATURE_DYNAMIC_CODE + for (CodeManagerEntry * pEntry = m_CodeManagerList.GetHead(); pEntry != NULL; pEntry = pEntry->m_pNext) + { + if (dac_cast(pvAddress) - dac_cast(pEntry->m_pvStartRange) < pEntry->m_cbRange) + return pEntry->m_pCodeManager; + } +#endif +#endif + + return NULL; +} + +GPTR_DECL(RuntimeInstance, g_pTheRuntimeInstance); +PTR_RuntimeInstance GetRuntimeInstance() +{ + return g_pTheRuntimeInstance; +} + +void RuntimeInstance::EnumGenericStaticGCRefs(PTR_GenericInstanceDesc pInst, void * pfnCallback, void * pvCallbackData, Module *pModule) +{ + while (pInst) + { + if (pInst->HasGcStaticFields()) + Module::EnumStaticGCRefsBlock(pfnCallback, pvCallbackData, + pInst->GetGcStaticFieldDesc(), pInst->GetGcStaticFieldData()); + + // Thread local statics. + if (pInst->HasThreadStaticFields()) + { + // Special case for dynamic types: TLS storage managed manually by runtime + UInt32 uiFieldsStartOffset = pInst->GetThreadStaticFieldStartOffset(); + if (uiFieldsStartOffset & DYNAMIC_TYPE_TLS_OFFSET_FLAG) + { + FOREACH_THREAD(pThread) + { + PTR_UInt8 pTLSStorage = pThread->GetThreadLocalStorageForDynamicType(uiFieldsStartOffset); + if (pTLSStorage != NULL) + { + Module::EnumStaticGCRefsBlock(pfnCallback, pvCallbackData, pInst->GetThreadStaticFieldDesc(), pTLSStorage); + } + } + END_FOREACH_THREAD + } + else + { + // See RhGetThreadStaticFieldAddress for details on where TLS fields live. + + UInt32 uiTlsIndex; + UInt32 uiFieldOffset; + + if (pModule != NULL) + { + ModuleHeader * pModuleHeader = pModule->GetModuleHeader(); + uiTlsIndex = *pModuleHeader->PointerToTlsIndex; + uiFieldOffset = pModuleHeader->TlsStartOffset + uiFieldsStartOffset; + } + else + { + uiTlsIndex = pInst->GetThreadStaticFieldTlsIndex(); + uiFieldOffset = uiFieldsStartOffset; + } + + FOREACH_THREAD(pThread) + { + Module::EnumStaticGCRefsBlock(pfnCallback, pvCallbackData, + pInst->GetThreadStaticFieldDesc(), + pThread->GetThreadLocalStorage(uiTlsIndex, uiFieldOffset)); + } + END_FOREACH_THREAD + } + } + + pInst = pInst->GetNextGidWithGcRoots(); + } +} + +void RuntimeInstance::EnumAllStaticGCRefs(void * pfnCallback, void * pvCallbackData) +{ + FOREACH_MODULE(pModule) + { + pModule->EnumStaticGCRefs(pfnCallback, pvCallbackData); + + // If there is no generic unification, we report generic instantiation statics directly from each module (the + // runtime and the binary/binaries for the library and the app) since they haven't been unified. + if (m_genericInstHashtabCount == 0) + EnumGenericStaticGCRefs(pModule->GetGidsWithGcRootsList(), pfnCallback, pvCallbackData, pModule); + } + END_FOREACH_MODULE + + EnumGenericStaticGCRefs(m_genericInstReportList, pfnCallback, pvCallbackData, NULL); +} + +static UInt32 HashEETypeByPointerValue(PTR_EEType pEEType) +{ + return (UInt32)dac_cast(pEEType) >> 3; +} + +struct GenericTypeTraits : public DefaultSHashTraits +{ + typedef PTR_EEType key_t; + + static key_t GetKey(const element_t e) + { + return e->GetEEType(); + } + + static bool Equals(key_t k1, key_t k2) + { + return (k1 == k2); + } + + static count_t Hash(key_t k) + { + return HashEETypeByPointerValue(k); + } + + static bool IsNull(const element_t e) + { + return (e == NULL); + } + + static const element_t Null() + { + return NULL; + } +}; + +class GenericTypeHashTable : public SHash< NoRemoveSHashTraits < GenericTypeTraits > > +{ +}; + +#ifndef DACCESS_COMPILE + +bool RuntimeInstance::BuildGenericTypeHashTable() +{ + UInt32 nTotalCount = 0; + + FOREACH_MODULE(pModule) + { + nTotalCount += pModule->GetGenericInstanceDescCount(Module::GenericInstanceDescKind::VariantGenericInstances); + } + END_FOREACH_MODULE; + + GenericTypeHashTable * pTable = new GenericTypeHashTable(); + if (pTable == NULL) + return false; + + // Preallocate the table to make rehashing unnecessary + if(!pTable->CheckGrowth(nTotalCount)) + { + delete pTable; + return false; + } + + FOREACH_MODULE(pModule) + { + Module::GenericInstanceDescEnumerator gidEnumerator(pModule, Module::GenericInstanceDescKind::VariantGenericInstances); + GenericInstanceDesc * pGid; + while ((pGid = gidEnumerator.Next()) != NULL) + { + if (!pTable->Add(pGid)) + { + delete pTable; + return false; + } + } + } + END_FOREACH_MODULE; + + // The hash table is initialized. Attempt to publish this version of the table to other threads. If we + // lose (another thread has already updated m_pGenericTypeHashTable) then we deallocate our version and + // use theirs for the lookup. + if (PalInterlockedCompareExchangePointer((void**)&m_pGenericTypeHashTable, + pTable, + NULL) != NULL) + { + delete pTable; + } + + return true; +} + +Module * RuntimeInstance::FindModuleByOsHandle(HANDLE hOsHandle) +{ + FOREACH_MODULE(pModule) + { + if (pModule->IsContainedBy(hOsHandle)) + return pModule; + } + END_FOREACH_MODULE; + + return NULL; +} + +RuntimeInstance::RuntimeInstance() : + m_pThreadStore(NULL), + m_fStandaloneExeMode(false), + m_pStandaloneExeModule(NULL), + m_pGenericTypeHashTable(NULL), + m_conservativeStackReportingEnabled(false) +{ +} + +RuntimeInstance::~RuntimeInstance() +{ + if (m_pGenericTypeHashTable != NULL) + { + delete m_pGenericTypeHashTable; + m_pGenericTypeHashTable = NULL; + } + + if (NULL != m_pThreadStore) + { + delete m_pThreadStore; + m_pThreadStore = NULL; + } + + m_genericInstHashtabLock.Destroy(); +} + +HANDLE RuntimeInstance::GetPalInstance() +{ + return m_hPalInstance; +} + +bool RuntimeInstance::EnableConservativeStackReporting() +{ + m_conservativeStackReportingEnabled = true; + return true; +} + +EXTERN_C void REDHAWK_CALLCONV RhpSetHaveNewClasslibs(); + +bool RuntimeInstance::RegisterModule(ModuleHeader *pModuleHeader) +{ + // Determine whether we're in standalone exe mode. If we are we'll see the runtime module load followed by + // exactly one additional module (the exe itself). The exe module will have a standalone flag set in its + // header. + ASSERT(m_fStandaloneExeMode == false); + if (pModuleHeader->Flags & ModuleHeader::StandaloneExe) + m_fStandaloneExeMode = true; + + CreateHolder pModule = Module::Create(pModuleHeader); + + if (NULL == pModule) + return false; + + { + // WARNING: This region must be kept small and must not callout + // to arbitrary code. See Thread::Hijack for more details. + ReaderWriterLock::WriteHolder write(&m_ModuleListLock); + m_ModuleList.PushHead(pModule); + } + + if (m_fStandaloneExeMode) + m_pStandaloneExeModule = pModule; + + if (pModule->IsClasslibModule()) + RhpSetHaveNewClasslibs(); + +#ifdef FEATURE_PROFILING + InitProfiling(pModuleHeader); +#endif // FEATURE_PROFILING + + pModule.SuppressRelease(); + // This event must occur after the module is added to the enumeration + DebugEventSource::SendModuleLoadEvent(pModule); + return true; +} + +bool RuntimeInstance::RegisterSimpleModule(SimpleModuleHeader *pModuleHeader) +{ + CreateHolder pModule = Module::Create(pModuleHeader); + + if (NULL == pModule) + return false; + + { + // WARNING: This region must be kept small and must not callout + // to arbitrary code. See Thread::Hijack for more details. + ReaderWriterLock::WriteHolder write(&m_ModuleListLock); + m_ModuleList.PushHead(pModule); + } + + pModule.SuppressRelease(); + // This event must occur after the module is added to the enumeration + DebugEventSource::SendModuleLoadEvent(pModule); + return true; +} + + +void RuntimeInstance::UnregisterModule(Module *pModule) +{ + { + // WARNING: This region must be kept small and must not callout + // to arbitrary code. See Thread::Hijack for more details. + ReaderWriterLock::WriteHolder write(&m_ModuleListLock); + ASSERT(rh::std::count(m_ModuleList.Begin(), m_ModuleList.End(), pModule) == 1); + m_ModuleList.RemoveFirst(pModule); + } + + // This event needs to occur after the module has been removed from enumeration. + // However it should come before the data is destroyed to make certain the pointer doesn't get recycled. + DebugEventSource::SendModuleUnloadEvent(pModule); + + pModule->Destroy(); +} + +#ifdef FEATURE_DYNAMIC_CODE +bool RuntimeInstance::RegisterCodeManager(ICodeManager * pCodeManager, PTR_VOID pvStartRange, UInt32 cbRange) +{ + CodeManagerEntry * pEntry = new CodeManagerEntry(); + if (NULL == pEntry) + return false; + + pEntry->m_pvStartRange = pvStartRange; + pEntry->m_cbRange = cbRange; + pEntry->m_pCodeManager = pCodeManager; + + { + ReaderWriterLock::WriteHolder write(&m_ModuleListLock); + + m_CodeManagerList.PushHead(pEntry); + } + + return true; +} + +void RuntimeInstance::UnregisterCodeManager(ICodeManager * pCodeManager) +{ + CodeManagerEntry * pEntry = NULL; + + { + ReaderWriterLock::WriteHolder write(&m_ModuleListLock); + + for (CodeManagerList::Iterator i = m_CodeManagerList.Begin(), end = m_CodeManagerList.End(); i != end; i++) + { + if (i->m_pCodeManager == pCodeManager) + { + pEntry = *i; + + m_CodeManagerList.Remove(i); + break; + } + } + } + + ASSERT(pEntry != NULL); + delete pEntry; +} + +extern "C" bool __stdcall RegisterCodeManager(ICodeManager * pCodeManager, PTR_VOID pvStartRange, UInt32 cbRange) +{ + return GetRuntimeInstance()->RegisterCodeManager(pCodeManager, pvStartRange, cbRange); +} + +extern "C" void __stdcall UnregisterCodeManager(ICodeManager * pCodeManager) +{ + return GetRuntimeInstance()->UnregisterCodeManager(pCodeManager); +} +#endif + +// static +RuntimeInstance * RuntimeInstance::Create(HANDLE hPalInstance) +{ + NewHolder pRuntimeInstance = new RuntimeInstance(); + if (NULL == pRuntimeInstance) + return NULL; + + CreateHolder pThreadStore = ThreadStore::Create(pRuntimeInstance); + if (NULL == pThreadStore) + return NULL; + +#ifdef FEATURE_VSD + VirtualCallStubManager * pVSD; + if (!CreateVSD(&pVSD)) + return NULL; +#endif + + pThreadStore.SuppressRelease(); + pRuntimeInstance->m_pThreadStore = pThreadStore; + pRuntimeInstance->m_hPalInstance = hPalInstance; + +#ifdef FEATURE_VSD + pRuntimeInstance->m_pVSDManager = pVSD; +#endif + + pRuntimeInstance->m_genericInstHashtab = NULL; + pRuntimeInstance->m_genericInstHashtabCount = 0; + pRuntimeInstance->m_genericInstHashtabEntries = 0; + pRuntimeInstance->m_genericInstHashtabLock.Init(CrstGenericInstHashtab); +#ifdef _DEBUG + pRuntimeInstance->m_genericInstHashUpdateInProgress = false; +#endif + + pRuntimeInstance->m_genericInstReportList = NULL; + +#ifdef FEATURE_PROFILING + pRuntimeInstance->m_fProfileThreadCreated = false; +#endif + + pRuntimeInstance.SuppressRelease(); + + return pRuntimeInstance; +} + + +void RuntimeInstance::Destroy() +{ + delete this; +} + +bool RuntimeInstance::ShouldHijackLoopForGcStress(UIntNative CallsiteIP) +{ +#if defined(FEATURE_GC_STRESS) & !defined(DACCESS_COMPILE) + return ShouldHijackForGcStress(CallsiteIP, htLoop); +#else // FEATURE_GC_STRESS & !DACCESS_COMPILE + return false; +#endif // FEATURE_GC_STRESS & !DACCESS_COMPILE +} + +bool RuntimeInstance::ShouldHijackCallsiteForGcStress(UIntNative CallsiteIP) +{ +#if defined(FEATURE_GC_STRESS) & !defined(DACCESS_COMPILE) + return ShouldHijackForGcStress(CallsiteIP, htCallsite); +#else // FEATURE_GC_STRESS & !DACCESS_COMPILE + return false; +#endif // FEATURE_GC_STRESS & !DACCESS_COMPILE +} + +// For the given generic instantiation remove any type indirections via IAT entries. This is used during +// generic instantiation type unification to remove any arbitrary dependencies on the module which happened to +// publish the instantiation first (an IAT indirection is a module dependency since the IAT cell lives in the +// module image itself). The indirections are removed by inspecting the current value of the IAT entry and +// moving the value up into the parent data structure (adjusting whatever flags are necessary to indicate that +// the datum is no longer accessed via the IAT). We can do this since generic instantiation type unification +// is performed at runtime after all the IAT entries have been bound to their final values. +static bool FlattenGenericInstance(UnifiedGenericInstance * pInst) +{ + GenericInstanceDesc * pGid = pInst->GetGid(); + UInt32 cTypeVars = pGid->GetArity(); + + // Flatten the instantiated type itself. + pGid->GetEEType()->Flatten(); + + // Flatten the generic type definition. Not much else to do for this case since the generic type + // definition isn't a real EEType (it has virtually no use at runtime). + pGid->GetGenericTypeDef().Flatten(); + + // Flatten each of the type arguments. + for (UInt32 i = 0; i < cTypeVars; i++) + { + // Note that if a type reference stored in the GenericInstanceDesc was flattened then we're done with + // that entry. That's because if the reference was indirected in the first place we can be sure there + // are no further (arbitrary) references to the module; the entry refered to a different module and + // did so because there was a non-arbitrary dependency on that module. + if (!pGid->GetParameterType(i).Flatten()) + { + EETypeRef typeVarRef = pGid->GetParameterType(i); + EEType * pTypeVar = typeVarRef.GetValue(); + + // The type reference above wasn't indirected to another module. Examine the type to see whether + // it contains any arbitrary references to the module which provided it. + switch (pTypeVar->get_Kind()) + { + case EEType::CanonicalEEType: + // Nothing to do here; a canonical type means this instantiation has a direct, non-arbitrary + // dependency on the module anyway. Therefore we don't care if there are any arbitrary + // dependencies to the same module as well. + break; + + case EEType::GenericTypeDefEEType: + // Nothing to do here. GenericTypeDefinitions are local to their defining module + break; + + case EEType::ClonedEEType: + // For cloned types we can simply replace the type argument with the corresponding canonical + // type. + typeVarRef.pEEType = pTypeVar->get_CanonicalEEType(); + pGid->SetParameterType(i, typeVarRef); + break; + + case EEType::ParameterizedEEType: + // Array types are tricky. They're always declared locally and unified at runtime (during cast + // operations) since there's a high degree of structural equivalence and only ever one type + // variable. This puts us in the nasty situation where we might have to allocate a whole new + // array type which is equivalent but doesn't reside in any one module (e.g. we allocate it + // from the NT heap). We can avoid this in the sub-case where the element type is bound to the + // providing module (i.e. the module defines the element type) since that would place a + // (non-arbitrary) dependence on the module for this generic instantiation anyway). + if (pTypeVar->IsRelatedTypeViaIAT()) + { + // The element type of this array wasn't defined by the module directly. Therefore it is + // likely that continuing to use this definition of the array type would place an + // arbitrary dependence on the module. We need to create a new, module neutral, type in + // this case. + + // Luckily we only have to create a fairly simplistic type. Since this is only used to + // establish identity between generic instances (i.e. type checks) we only need the base + // EEType, no GC desc, interface map or interface dispatch map. + EEType *pArrayType = new EEType(); + if (pArrayType == NULL) + return false; + + // Initialize the type as an array of the element type we extracted from the original + // array type. + pArrayType->InitializeAsArrayType(pTypeVar->get_RelatedParameterType(), pTypeVar->get_BaseSize()); + + // Mark the type as runtime allocated so we can identify and deallocate it when we no + // longer need it. + pArrayType->SetRuntimeAllocated(); + + // Patch the type variable to point to the module-neutral version of the array type. + typeVarRef.pEEType = pArrayType; + pGid->SetParameterType(i, typeVarRef); + } + break; + + default: + UNREACHABLE(); + } + } + } + + return true; +} + +// List of primes used to size the generic instantiation hash table bucket array. +static const UInt32 s_rgPrimes[] = { 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, + 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, + 4861, 5839, 7013, 8419, 10103 }; + +// The cInstances parameter is optional and only used for the first call to StartGenericUnification to +// determine the initial number of hash chain buckets. +bool RuntimeInstance::StartGenericUnification(UInt32 cInstances) +{ + ASSERT(!m_fStandaloneExeMode); + + // We take the hash table lock here and release it in EndGenericUnification. This avoids re-taking the + // lock for every generic instantiation, of which there can be many. + Crst::Enter(&m_genericInstHashtabLock); + +#ifdef _DEBUG + ASSERT(!m_genericInstHashUpdateInProgress); + m_genericInstHashUpdateInProgress = true; +#endif + + // We lazily allocate the hash table. + if (m_genericInstHashtab == NULL) + { + // Base the initial hash table bucket array size on the number of generic instantiations in the first + // module that registers such instantiations (this should be the class library since rtm does use any + // generic instantiations). This is an arbitrary number but at least in the cases we've seen so far + // it's the dominant one (System.Private.CoreLib, roslyn). This way we're at least slightly pay-for-play (the old + // scheme had a fixed constant size and we'd either allocate too many buckets for generics light + // scenarios such as WCL or too few for heavy scenarios such as System.Private.CoreLib or roslyn). + // + // Note that we don't yet support dynamically re-sizing the hash table, mainly because the resulting + // re-hash has working set implications of its own. + // + // Round up the number of hash buckets to some prime number (up to some reasonable ceiling). + cInstances = max(17, cInstances); + UInt32 cHashBuckets = 0; + + UInt32 cPrimes = sizeof(s_rgPrimes) / sizeof(s_rgPrimes[0]); + for (UInt32 i = 0; i < cPrimes; i++) + { + if (s_rgPrimes[i] >= cInstances) + { + cHashBuckets = s_rgPrimes[i]; + break; + } + } + if (cHashBuckets == 0) + cHashBuckets = s_rgPrimes[cPrimes - 1]; + + m_genericInstHashtabCount = cHashBuckets; + + // Allocate a second set of buckets used during updates to temporarily store the newly added items. + // This avoids having to search those new entries during subsequent additions within the same update + // (since a module will never publish two identical generic instantiations these extra equality checks + // are unnecessary as well as expensive). + m_genericInstHashtab = new UnifiedGenericInstance*[cHashBuckets * 2]; + m_genericInstHashtabUpdates = m_genericInstHashtab + cHashBuckets; + if (m_genericInstHashtab == NULL) + { + Crst::Leave(&m_genericInstHashtabLock); + return false; + } + memset(m_genericInstHashtab, 0, m_genericInstHashtabCount * sizeof(m_genericInstHashtab[0]) * 2); + } + + // Initialize the temporary update buckets to point to the head of each live bucket. As we unify + // instantiations we'll add to the heads of these update buckets so we'll record the new entries without + // publishing them in the real table (until EndGenericUnification is called). + for (UInt32 i = 0; i < m_genericInstHashtabCount; i++) + m_genericInstHashtabUpdates[i] = m_genericInstHashtab[i]; + + return true; +} + +UnifiedGenericInstance *RuntimeInstance::UnifyGenericInstance(GenericInstanceDesc * pLocalGid, UInt32 uiLocalTlsIndex) +{ + EEType * pLocalEEType = pLocalGid->GetEEType(); + UnifiedGenericInstance * pCanonicalInst = NULL; + GenericInstanceDesc * pCanonicalGid = NULL; + + UInt32 hashCode = pLocalGid->GetHashCode() % m_genericInstHashtabCount; + ASSERT(hashCode < m_genericInstHashtabCount); + for (pCanonicalInst = m_genericInstHashtab[hashCode]; + pCanonicalInst != NULL; + pCanonicalInst = pCanonicalInst->m_pNext) + { + if (pCanonicalInst->Equals(pLocalGid)) + { + pCanonicalGid = pCanonicalInst->GetGid(); + + // Increment count of modules which depend on this type. + pCanonicalInst->m_cRefs++; + + goto Done; + } + } + + // No module has previously registered this generic instantiation. We need to allocate and create a new + // unified canonical representation for this type. + + // Allocate enough memory for the UnifiedGenericInstance, canonical GenericInstanceDesc, canonical generic + // instantiation EEType and static fields (GC and non-GC). Note that we don't have to allocate space for a + // GC descriptor, vtable or interface dispatch map for the EEType since this type will never appear in an + // object header on the GC heap (we always use module-local EETypes for this so that virtual dispatch is + // bound back to the local module). + UInt32 cbGid = pLocalGid->GetSize(); + UInt32 cbPaddedGid = (UInt32)(ALIGN_UP(cbGid, sizeof(void*))); + UInt32 cbEEType = EEType::GetSizeofEEType(0, // # of virtuals (no vtable) + pLocalEEType->GetNumInterfaces(), + false, // HasFinalizer (we don't care) + false, // RequiresOptionalFields (we don't care) + false, // IsNullable (we don't care) + false); // fHasSealedVirtuals (we don't care) + UInt32 cbNonGcStaticFields = pLocalGid->GetSizeOfNonGcStaticFieldData(); + UInt32 cbGcStaticFields = pLocalGid->GetSizeOfGcStaticFieldData(); + PTR_StaticGcDesc pLocalGcStaticDesc = cbGcStaticFields ? pLocalGid->GetGcStaticFieldDesc() : NULL; + UInt32 cbGcDesc = pLocalGcStaticDesc ? pLocalGcStaticDesc->GetSize() : 0; + + // for performance and correctness reasons (at least on ARM), we wish to align the static areas on a + // multiple of STATIC_FIELD_ALIGNMENT + const UInt32 STATIC_FIELD_ALIGNMENT = 8; + UInt32 cbMemory = (UInt32)ALIGN_UP(sizeof(UnifiedGenericInstance) + cbPaddedGid + cbEEType, STATIC_FIELD_ALIGNMENT) + + (UInt32)ALIGN_UP(cbNonGcStaticFields, STATIC_FIELD_ALIGNMENT) + + cbGcStaticFields + + cbGcDesc; + // Note: Generic instance unification is not a product feature that we ship in ProjectN, so there is no need to + // use safe integers when computing the value of cbMemory. + UInt8 * pMemory = new UInt8[cbMemory]; + if (pMemory == NULL) + return NULL; + + // Determine the start of the various individual data structures in the monolithic chunk of memory we + // allocated. + + pCanonicalInst = (UnifiedGenericInstance*)pMemory; + pMemory += sizeof(UnifiedGenericInstance); + + pCanonicalGid = (GenericInstanceDesc*)pMemory; + pMemory += cbPaddedGid; + + EEType * pCanonicalType = (EEType*)pMemory; + pMemory = ALIGN_UP(pMemory + cbEEType, STATIC_FIELD_ALIGNMENT); + + UInt8 * pStaticData = pMemory; + pMemory += ALIGN_UP(cbNonGcStaticFields, STATIC_FIELD_ALIGNMENT); + + UInt8 * pGcStaticData = pMemory; + pMemory += cbGcStaticFields; + + StaticGcDesc * pStaticGcDesc = (StaticGcDesc*)pMemory; + pMemory += cbGcDesc; + + // Copy local GenericInstanceDesc. + memcpy(pCanonicalGid, pLocalGid, cbGid); + + // Copy local definition of the generic instantiation EEType (no vtable). + memcpy(pCanonicalType, pLocalEEType, sizeof(EEType)); + + // Set the type as runtime allocated (just for debugging purposes at the moment). + pCanonicalType->SetRuntimeAllocated(); + + // Copy the interface map directly after the EEType (if there are any interfaces). + if (pLocalEEType->HasInterfaces()) + memcpy(pCanonicalType + 1, + pLocalEEType->GetInterfaceMap().GetRawPtr(), + pLocalEEType->GetNumInterfaces() * sizeof(EEInterfaceInfo)); + + // Copy initial static data from the module. + if (cbNonGcStaticFields) + memcpy(pStaticData, pLocalGid->GetNonGcStaticFieldData(), cbNonGcStaticFields); + if (cbGcStaticFields) + memcpy(pGcStaticData, pLocalGid->GetGcStaticFieldData(), cbGcStaticFields); + + // If we have any GC static data then we need to copy over GC descriptors for it. + if (cbGcDesc) + memcpy(pStaticGcDesc, pLocalGcStaticDesc, cbGcDesc); + + // Because we don't store the vtable with our canonical EEType it throws the calculation of the interface + // map (which is still required for cast operations) off. We need to clear the count of virtual methods in + // the EEType to correct this (this field should not be required for the canonical type). + pCanonicalType->SetNumVtableSlots(0); + + // Initialize the UnifiedGenericInstance. + pCanonicalInst->m_pNext = m_genericInstHashtabUpdates[hashCode]; + pCanonicalInst->m_cRefs = 1; + + // Update canonical GenericInstanceDesc with any values that are no longer local to the module. + pCanonicalGid->SetEEType(pCanonicalType); + if (cbNonGcStaticFields) + pCanonicalGid->SetNonGcStaticFieldData(pStaticData); + if (cbGcStaticFields) + pCanonicalGid->SetGcStaticFieldData(pGcStaticData); + if (cbGcDesc) + pCanonicalGid->SetGcStaticFieldDesc(pStaticGcDesc); + + // Any generic types with thread static fields need to know the TLS index assigned to the module by the OS + // loader for the module that ends up "owning" the unified instance. Note that this breaks the module + // unload scenario since when the arbitrarily chosen owning module is unloaded it's TLS index will be + // released. Since the OS doesn't provide access to the TLS allocation mechanism used by .tls support + // (it's a different system than that used by TlsAlloc) our only alternative here would be to allocate TLS + // slots manually and managed the storage ourselves, which is both complicated and would result in lower + // performance at the thread static access site (since at a minimum regular TlsAlloc'd TLS indices need to + // be range checked to determine how they are used with a TEB). + if (pCanonicalGid->HasThreadStaticFields()) + pCanonicalGid->SetThreadStaticFieldTlsIndex(uiLocalTlsIndex); + + // Attempt to remove any arbitrary dependencies on the module that provided the instantiation. Here + // arbitrary refers to references to the module that exist purely because the module used an IAT + // indirection to point to non-local types. We can remove most of these in-place by performing the IAT + // lookup now and copying the direct pointer up one level of the data structures (see + // FlattenGenericInstance above for more details). Unfortunately one edge case in particular might require + // us to allocate memory (some generic instantiations over array types) so the call below can fail. So + // don't modify any global state (the unification hash table) until this call has succeeded. + if (!FlattenGenericInstance(pCanonicalInst)) + { + delete [] pMemory; + return NULL; + } + + // If this generic instantiation has GC fields to report add it to the list we traverse during garbage + // collections. + if (cbGcStaticFields || pLocalGid->HasThreadStaticFields()) + { + pCanonicalGid->SetNextGidWithGcRoots(m_genericInstReportList); + m_genericInstReportList = pCanonicalGid; + } + + // We've built the new unified generic instantiation, publish it in the hash table. But don't put it on + // the real bucket chain yet otherwise further additions as part of this same update will needlessly + // search it. Instead add it to the head of the update bucket. All updated chains will be published back + // to the real buckets at the end of the update. + m_genericInstHashtabEntries += 1; + m_genericInstHashtabUpdates[hashCode] = pCanonicalInst; + + Done: + + // Get here whether we found an existing match for the type or had to create a new entry. All that's left + // to do is perform any updates to the module local data structures necessary to reflect the unification. + + // Update the module local EEType to be a cloned type refering back to the unified EEType. + EEType ** ppCanonicalType = (EEType**)((UInt8*)pCanonicalGid + pCanonicalGid->GetEETypeOffset()); + pLocalEEType->MakeClonedType(ppCanonicalType); + + // Update the module local GenericInstanceDesc fields that that module local code still refers to but + // which need to be redirected to their unified versions. + + if (pLocalGid->HasNonGcStaticFields()) + pLocalGid->SetNonGcStaticFieldData(pCanonicalGid->GetNonGcStaticFieldData()); + if (pLocalGid->HasGcStaticFields()) + pLocalGid->SetGcStaticFieldData(pCanonicalGid->GetGcStaticFieldData()); + if (pLocalGid->HasThreadStaticFields()) + { + pLocalGid->SetThreadStaticFieldTlsIndex(pCanonicalGid->GetThreadStaticFieldTlsIndex()); + pLocalGid->SetThreadStaticFieldStartOffset(pCanonicalGid->GetThreadStaticFieldStartOffset()); + } + + return pCanonicalInst; +} + +void RuntimeInstance::EndGenericUnification() +{ +#ifdef _DEBUG + ASSERT(m_genericInstHashUpdateInProgress); + m_genericInstHashUpdateInProgress = false; +#endif + + // The update buckets now hold the complete hash chain (since we initialized them to point to the head of + // the old chains and we always add at the head). Publish these chain heads back into the real hash table + // buckets to make all updates visible. + for (UInt32 i = 0; i < m_genericInstHashtabCount; i++) + m_genericInstHashtab[i] = m_genericInstHashtabUpdates[i]; + + Crst::Leave(&m_genericInstHashtabLock); +} + +// Release one module's interest in the given generic instantiation. If no modules require the instantiation +// any more release any resources associated with it. +void RuntimeInstance::ReleaseGenericInstance(GenericInstanceDesc * pInst) +{ + CrstHolder hashLock(&m_genericInstHashtabLock); + + // Find the hash chain containing the target instantiation. + UInt32 hashCode = pInst->GetHashCode() % m_genericInstHashtabCount; + UnifiedGenericInstance *pGlobalInst = m_genericInstHashtab[hashCode]; + UnifiedGenericInstance *pPrevInst = NULL; + + // Iterate down the hash chain looking for the target instantiation. + while (pGlobalInst) + { + if (pGlobalInst->Equals(pInst)) + { + // Found it, decrement the count of modules interested in this instantiation. If there are still + // modules with a reference we can return right now. + if (--pGlobalInst->m_cRefs > 0) + return; + + // Unlink the GenericInstanceDesc from the hash chain. + if (pPrevInst) + pPrevInst->m_pNext = pGlobalInst->m_pNext; + else + m_genericInstHashtab[hashCode] = pGlobalInst->m_pNext; + + GenericInstanceDesc * pGlobalGid = pGlobalInst->GetGid(); + + // If the instantiation has GC reference static fields then this descriptor has also been linked + // on the global list of instantiations we report to the GC. This list is protected from being + // updated by m_genericInstHashtabLock which we already hold. + if (pGlobalGid->HasGcStaticFields()) + { + GenericInstanceDesc *pPrevReportGid = NULL; + GenericInstanceDesc *pCurrReportGid = m_genericInstReportList; + while (pCurrReportGid != NULL) + { + if (pCurrReportGid == pGlobalGid) + { + if (pPrevReportGid == NULL) + m_genericInstReportList = pCurrReportGid->GetNextGidWithGcRoots(); + else + pPrevReportGid->SetNextGidWithGcRoots(pCurrReportGid->GetNextGidWithGcRoots()); + break; + } + + pPrevReportGid = pCurrReportGid; + pCurrReportGid = pCurrReportGid->GetNextGidWithGcRoots(); + } + ASSERT(pCurrReportGid == pGlobalGid); + } + + // Nobody references the GenericInstanceDesc (and it's associated data such as the EEType and + // static data), so we can deallocate it. Most data was allocated in one monolithic block and can + // be deallocated with a single call, but we might have also allocated some module-neutral array + // types in the instantiation type arguments. + for (UInt32 i = 0; i < pGlobalGid->GetArity(); i++) + { + EEType * pTypeVar = pGlobalGid->GetParameterType(i).GetValue(); + if (pTypeVar->IsRuntimeAllocated()) + delete pTypeVar; + } + delete [] (UInt8*)pGlobalInst; + + return; + } + + pPrevInst = pGlobalInst; + pGlobalInst = pGlobalInst->m_pNext; + } + + // We couldn't find the instantiation in the hash table. This should never happen. + UNREACHABLE(); +} + +// This method should only be called during DllMain for modules with GcStress enabled. The locking done by +// the loader is used to make it OK to call UnsynchronizedHijackAllLoops. +void RuntimeInstance::EnableGcPollStress() +{ + FOREACH_MODULE(pModule) + { + pModule->UnsynchronizedHijackAllLoops(); + } + END_FOREACH_MODULE; +} + +// Only called from thread suspension code while all threads are still synchronized. +void RuntimeInstance::UnsychronizedResetHijackedLoops() +{ + FOREACH_MODULE(pModule) + { + pModule->UnsynchronizedResetHijackedLoops(); + } + END_FOREACH_MODULE; +} + +// Given the EEType* for an instantiated generic type retrieve the GenericInstanceDesc associated with that +// type. This is legal only for types that are guaranteed to have this metadata at runtime; generic types +// which have variance over one or more of their type parameters and generic interfaces on array). +GenericInstanceDesc * RuntimeInstance::LookupGenericInstance(EEType * pEEType) +{ + // EETypes we will attempt to match against will always be the canonical version. Canonicalize our input + // EEType as well if required. + if (pEEType->IsCloned()) + pEEType = pEEType->get_CanonicalEEType(); + + if (m_genericInstHashtabCount == 0) + { + if (m_pGenericTypeHashTable == NULL) + { + if (!BuildGenericTypeHashTable()) + { + // We failed the allocation but we don't want to fail the call (because we build this table lazily + // we're doing the allocation at a point the caller doesn't expect can fail). So fall back to the + // slow linear scan of all variant GIDs in this case. + + FOREACH_MODULE(pModule) + { + Module::GenericInstanceDescEnumerator gidEnumerator(pModule, Module::GenericInstanceDescKind::VariantGenericInstances); + GenericInstanceDesc * pGid; + while ((pGid = gidEnumerator.Next()) != NULL) + { + if (pGid->GetEEType() == pEEType) + return pGid; + } + } + END_FOREACH_MODULE; + + // It is not legal to call this API unless you know there is a matching GenericInstanceDesc. + UNREACHABLE(); + } + } + + ReaderWriterLock::ReadHolder read(&m_GenericHashTableLock); + + const PTR_GenericInstanceDesc * ppGid = m_pGenericTypeHashTable->LookupPtr(pEEType); + if (ppGid != NULL) + return *ppGid; + } + else + { + // @TODO: In the multi-module case we can't ask the modules to do the lookups due to generic type + // unification. If we want to gain the perf benefit here we'll need to build a similar hash table over + // the unified GIDs (it will be a little more complex and less compact than the standalone version + // since this hash will be dynamically sized). For now we perform a linear search through all the + // unified GIDs. + CrstHolder hashLock(&m_genericInstHashtabLock); + + UnifiedGenericInstance * pInst = NULL; + + for (UInt32 i = 0; i < m_genericInstHashtabCount; i++) + { + pInst = m_genericInstHashtab[i]; + for (; pInst; pInst = pInst->m_pNext) + { + if (pInst->GetGid()->GetEEType() == pEEType) + return pInst->GetGid(); + } + } + } + + // It is not legal to call this API unless you know there is a matching GenericInstanceDesc. + UNREACHABLE(); +} + +// Given the EEType* for an instantiated generic type retrieve instantiation information (generic type +// definition EEType, arity, type arguments and variance info for each type parameter). Has the same +// limitations on usage as LookupGenericInstance above. +EEType * RuntimeInstance::GetGenericInstantiation(EEType * pEEType, + UInt32 * pArity, + EEType *** ppInstantiation, + GenericVarianceType ** ppVarianceInfo) +{ + GenericInstanceDesc * pInst = LookupGenericInstance(pEEType); + + ASSERT(pInst != NULL && pInst->HasInstantiation()); + + *pArity = pInst->GetArity(); + *ppInstantiation = (EEType**)((UInt8*)pInst + pInst->GetParameterTypeOffset(0)); + + if (pInst->HasVariance()) + *ppVarianceInfo = (GenericVarianceType*)((UInt8*)pInst + pInst->GetParameterVarianceOffset(0)); + else + *ppVarianceInfo = NULL; + + return pInst->GetGenericTypeDef().GetValue(); +} + +bool RuntimeInstance::CreateGenericInstanceDesc(EEType * pEEType, + EEType * pTemplateType, + UInt32 arity, + UInt32 nonGcStaticDataSize, + UInt32 nonGCStaticDataOffset, + UInt32 gcStaticDataSize, + UInt32 threadStaticOffset, + StaticGcDesc * pGcStaticsDesc, + StaticGcDesc * pThreadStaticsDesc, + UInt32* pGenericVarianceFlags) +{ + if (m_pGenericTypeHashTable == NULL) + { + if (!BuildGenericTypeHashTable()) + return false; + } + + GenericInstanceDesc::OptionalFieldTypes flags = GenericInstanceDesc::GID_Instantiation; + + if (pTemplateType->HasGenericVariance()) + flags |= GenericInstanceDesc::GID_Variance; + if (gcStaticDataSize > 0) + flags |= GenericInstanceDesc::GID_GcStaticFields | GenericInstanceDesc::GID_GcRoots; + if (nonGcStaticDataSize > 0) + flags |= GenericInstanceDesc::GID_NonGcStaticFields; + if (threadStaticOffset != 0) + flags |= GenericInstanceDesc::GID_ThreadStaticFields | GenericInstanceDesc::GID_GcRoots; + + // Note: arity is limited to a maximum value of 65535 on the managed layer before CreateGenericInstanceDesc + // gets called. With this value, cbGidSize will not exceed 600K, so no need to use safe integers + size_t cbGidSize = GenericInstanceDesc::GetSize(flags, arity); + + NewArrayHolder pGidMemory = new UInt8[cbGidSize]; + if (pGidMemory == NULL) + return false; + + GenericInstanceDesc * pGid = (GenericInstanceDesc *)(UInt8 *)pGidMemory; + memset(pGid, 0, cbGidSize); + + pGid->Init(flags); + pGid->SetEEType(pEEType); + pGid->SetArity(arity); + + NewArrayHolder pNonGcStaticData; + if (nonGcStaticDataSize > 0) + { + // The value of nonGcStaticDataSize is read from native layout info in the managed layer, where + // there is also a check that it does not exceed the max value of a signed Int32 + ASSERT(nonGCStaticDataOffset <= nonGcStaticDataSize); + pNonGcStaticData = new UInt8[nonGcStaticDataSize]; + if (pNonGcStaticData == NULL) + return false; + memset(pNonGcStaticData, 0, nonGcStaticDataSize); + pGid->SetNonGcStaticFieldData(pNonGcStaticData + nonGCStaticDataOffset); + } + + NewArrayHolder pGcStaticData; + if (gcStaticDataSize > 0) + { + // The value of gcStaticDataSize is read from native layout info in the managed layer, where + // there is also a check that it does not exceed the max value of a signed Int32 + pGcStaticData = new UInt8[gcStaticDataSize]; + if (pGcStaticData == NULL) + return false; + memset(pGcStaticData, 0, gcStaticDataSize); + pGid->SetGcStaticFieldData(pGcStaticData); + pGid->SetGcStaticFieldDesc(pGcStaticsDesc); + } + + if (threadStaticOffset != 0) + { + // Note: TLS index not used for dynamically created types + pGid->SetThreadStaticFieldTlsIndex(0); + pGid->SetThreadStaticFieldStartOffset(threadStaticOffset); + + // Note: pThreadStaticsDesc can possibly be NULL if the type doesn't have any thread-static reference types + pGid->SetThreadStaticFieldDesc(pThreadStaticsDesc); + } + + if (pTemplateType->HasGenericVariance()) + { + ASSERT(pGenericVarianceFlags != NULL); + + for (UInt32 i = 0; i < arity; i++) + { + GenericVarianceType variance = (GenericVarianceType)pGenericVarianceFlags[i]; + pGid->SetParameterVariance(i, variance); + } + } + + ReaderWriterLock::WriteHolder write(&m_GenericHashTableLock); + + if (!m_pGenericTypeHashTable->Add(pGid)) + return false; + + if (gcStaticDataSize > 0 || pGid->HasThreadStaticFields()) + { + pGid->SetNextGidWithGcRoots(m_genericInstReportList); + m_genericInstReportList = pGid; + } + + pGidMemory.SuppressRelease(); + pNonGcStaticData.SuppressRelease(); + pGcStaticData.SuppressRelease(); + return true; +} + +bool RuntimeInstance::SetGenericInstantiation(EEType * pEEType, + EEType * pEETypeDef, + UInt32 arity, + EEType ** pInstantiation) +{ + ASSERT(pEEType->IsGeneric()); + ASSERT(pEEType->IsDynamicType()); + ASSERT(m_pGenericTypeHashTable != NULL) + + GenericInstanceDesc * pGid = LookupGenericInstance(pEEType); + ASSERT(pGid != NULL); + + pGid->SetGenericTypeDef((EETypeRef&)pEETypeDef); + + // Arity should have been set during the GID creation time + ASSERT(pGid->GetArity() == arity); + + for (UInt32 iArg = 0; iArg < arity; iArg++) + pGid->SetParameterType(iArg, (EETypeRef&)pInstantiation[iArg]); + + return true; +} + +COOP_PINVOKE_HELPER(EEType *, RhGetGenericInstantiation, (EEType * pEEType, + UInt32 * pArity, + EEType *** ppInstantiation, + GenericVarianceType ** ppVarianceInfo)) +{ + return GetRuntimeInstance()->GetGenericInstantiation(pEEType, + pArity, + ppInstantiation, + ppVarianceInfo); +} + +COOP_PINVOKE_HELPER(bool, RhSetGenericInstantiation, (EEType * pEEType, + EEType * pEETypeDef, + UInt32 arity, + EEType ** pInstantiation)) +{ + return GetRuntimeInstance()->SetGenericInstantiation(pEEType, + pEETypeDef, + arity, + pInstantiation); +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(PTR_VOID *, RhGetDictionary, (EEType * pEEType)) +{ + // Dictionary slot is the first vtable slot + + EEType * pBaseType = pEEType->get_BaseType(); + UInt16 dictionarySlot = (pBaseType != NULL) ? pBaseType->GetNumVtableSlots() : 0; + + return (PTR_VOID *)(*(PTR_VOID *)(pEEType->get_SlotPtr(dictionarySlot))); +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(void, RhSetDictionary, (EEType * pEEType, + EEType * pEETypeOfDictionary, + PTR_VOID * pDictionary)) +{ + ASSERT(pEEType->IsDynamicType()); + + // Update the base type's vtable slot in pEEType's vtable to point to the + // new dictionary + EEType * pBaseType = pEETypeOfDictionary->get_BaseType(); + UInt16 dictionarySlot = (pBaseType != NULL) ? pBaseType->GetNumVtableSlots() : 0; + *(PTR_VOID *)(pEEType->get_SlotPtr(dictionarySlot)) = pDictionary; +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(EEType *, RhCloneType, (EEType * pTemplate, + UInt32 arity, + UInt32 nonGcStaticDataSize, + UInt32 nonGCStaticDataOffset, + UInt32 gcStaticDataSize, + UInt32 threadStaticsOffset, + StaticGcDesc * pGcStaticsDesc, + StaticGcDesc * pThreadStaticsDesc, + UInt32 hashcode)) +{ + // In some situations involving arrays we can find as a template a dynamically generated type. + // In that case, the correct template would be the template used to create the dynamic type in the first + // place. + if (pTemplate->IsDynamicType()) + { + pTemplate = pTemplate->get_DynamicTemplateType(); + } + + OptionalFieldsRuntimeBuilder optionalFields; + + optionalFields.Decode(pTemplate->get_OptionalFields()); + + optionalFields.m_rgFields[OFT_RareFlags].m_fPresent = true; + optionalFields.m_rgFields[OFT_RareFlags].m_uiValue |= EEType::IsDynamicTypeFlag; + + // Remove the NullableTypeViaIATFlag flag + optionalFields.m_rgFields[OFT_RareFlags].m_uiValue &= ~EEType::NullableTypeViaIATFlag; + + optionalFields.m_rgFields[OFT_DispatchMap].m_fPresent = false; // Dispatch map is fetched from template + + // Compute size of optional fields encoding + UInt32 cbOptionalFieldsSize = optionalFields.EncodingSize(); + + UInt32 cbEEType = EEType::GetSizeofEEType(pTemplate->GetNumVtableSlots(), + pTemplate->GetNumInterfaces(), + pTemplate->HasFinalizer(), + true /* optional fields are always present */, + pTemplate->IsNullable(), + false /* sealed virtual slots come from template */); + + UInt32 cbGCDesc = RedhawkGCInterface::GetGCDescSize(pTemplate); + + UInt32 cbGCDescAligned = (UInt32)ALIGN_UP(cbGCDesc, sizeof(void *)); + + // Safe arithmetics note: + // - cbGCDescAligned should typically not exceed 16 MB, which is certainly big enough for anything that actually works + // - cbEEType should never exceed 1 GB (number based on type with 65535 interface, 65535 virtual method slots, and 64-bit pointer sizes) + // - cbOptionalFieldsSize is quite small (6 flags to encode, at most 5 bytes per flag). + // Adding up these numbers should never exceed the max value of a signed Int32, so there is no need to use safe integers here. + // A simple check is enough to catch out of range values. + // Note: this function will be removed soon after RTM and moved to the managed size, where checked integers can be used + if (cbOptionalFieldsSize >= 200 || cbEEType >= 2000000000 || cbGCDescAligned >= 0x1000000) + { + ASSERT_UNCONDITIONALLY("Invalid sizes for dynamic type detected."); + RhFailFast(); + } + + NewArrayHolder pEETypeMemory = new UInt8[cbGCDescAligned + cbEEType + sizeof(EEType *)+cbOptionalFieldsSize]; + if (pEETypeMemory == NULL) + return NULL; + + EEType * pEEType = (EEType *)((UInt8*)pEETypeMemory + cbGCDescAligned); + + UInt32 cbTemplate = EEType::GetSizeofEEType(pTemplate->GetNumVtableSlots(), + pTemplate->GetNumInterfaces(), + pTemplate->HasFinalizer(), + false /* optional fields will be updated later - no need to copy it from template */, + false /* nullable type will be updated later - no need to copy it from template */, + false /* sealed virtual slots are not present on dynamic types */); + + memcpy(((UInt8 *)pEEType) - cbGCDesc, ((UInt8 *)pTemplate) - cbGCDesc, cbGCDesc + cbTemplate); + + OptionalFields * pOptionalFields = (OptionalFields *)((UInt8 *)pEEType + cbEEType + sizeof(EEType *)); + + // Encode the optional fields for real + UInt32 cbActualOptionalFieldsSize; + cbActualOptionalFieldsSize = optionalFields.Encode(pOptionalFields); + ASSERT(cbActualOptionalFieldsSize == cbOptionalFieldsSize); + + pEEType->set_OptionalFields(pOptionalFields); + + pEEType->set_DynamicTemplateType(pTemplate); + + pEEType->SetHashCode(hashcode); + + if (pEEType->IsGeneric()) + { + NewArrayHolder pGenericVarianceFlags = NULL; + + if (pTemplate->HasGenericVariance()) + { + GenericInstanceDesc * pTemplateGid = GetRuntimeInstance()->LookupGenericInstance(pTemplate); + ASSERT(pTemplateGid != NULL && pTemplateGid->HasInstantiation() && pTemplateGid->HasVariance()); + + pGenericVarianceFlags = new UInt32[arity]; + if (pGenericVarianceFlags == NULL) return NULL; + + for (UInt32 i = 0; i < arity; i++) + ((UInt32*)pGenericVarianceFlags)[i] = (UInt32)pTemplateGid->GetParameterVariance(i); + } + + if (!GetRuntimeInstance()->CreateGenericInstanceDesc(pEEType, pTemplate, arity, nonGcStaticDataSize, nonGCStaticDataOffset, gcStaticDataSize, threadStaticsOffset, pGcStaticsDesc, pThreadStaticsDesc, (UInt32*)pGenericVarianceFlags)) + return NULL; + } + + pEETypeMemory.SuppressRelease(); + + return pEEType; +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(PTR_VOID, RhAllocateMemory, (UInt32 size)) +{ + // Generic memory allocation function, for use by managed code + // Note: all callers to RhAllocateMemory on the managed side use checked integer arithmetics to catch overflows, + // so there is no need to use safe integers here. + PTR_VOID pMemory = new UInt8[size]; + if (pMemory == NULL) + return NULL; + +#ifdef _DEBUG + memset(pMemory, 0, size * sizeof(UInt8)); +#endif + + return pMemory; +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(void, RhSetRelatedParameterType, (EEType * pEEType, + EEType * pRelatedParamterType)) +{ + pEEType->set_RelatedParameterType(pRelatedParamterType); +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(void, RhSetNullableType, (EEType * pEEType, EEType * pTheT)) +{ + pEEType->SetNullableType(pTheT); +} + +/// FUNCTION IS OBSOLETE AND NOT EXPECTED TO BE USED IN NEW CODE +COOP_PINVOKE_HELPER(bool , RhCreateGenericInstanceDescForType, (EEType * pEEType, + UInt32 arity, + UInt32 nonGcStaticDataSize, + UInt32 nonGCStaticDataOffset, + UInt32 gcStaticDataSize, + UInt32 threadStaticsOffset, + StaticGcDesc * pGcStaticsDesc, + StaticGcDesc * pThreadStaticsDesc)) +{ + ASSERT(pEEType->IsDynamicType()); + + EEType * pTemplateType = pEEType->get_DynamicTemplateType(); + + NewArrayHolder pGenericVarianceFlags = NULL; + + if (pTemplateType->HasGenericVariance()) + { + GenericInstanceDesc * pTemplateGid = GetRuntimeInstance()->LookupGenericInstance(pTemplateType); + ASSERT(pTemplateGid != NULL && pTemplateGid->HasInstantiation() && pTemplateGid->HasVariance()); + + pGenericVarianceFlags = new UInt32[arity]; + if (pGenericVarianceFlags == NULL) return NULL; + + for (UInt32 i = 0; i < arity; i++) + ((UInt32*)pGenericVarianceFlags)[i] = (UInt32)pTemplateGid->GetParameterVariance(i); + } + + return GetRuntimeInstance()->CreateGenericInstanceDesc(pEEType, pTemplateType, arity, nonGcStaticDataSize, nonGCStaticDataOffset, gcStaticDataSize, + threadStaticsOffset, pGcStaticsDesc, pThreadStaticsDesc, pGenericVarianceFlags); +} + +COOP_PINVOKE_HELPER(bool, RhCreateGenericInstanceDescForType2, (EEType * pEEType, + UInt32 arity, + UInt32 nonGcStaticDataSize, + UInt32 nonGCStaticDataOffset, + UInt32 gcStaticDataSize, + UInt32 threadStaticsOffset, + StaticGcDesc * pGcStaticsDesc, + StaticGcDesc * pThreadStaticsDesc, + UInt32* pGenericVarianceFlags)) +{ + ASSERT(pEEType->IsDynamicType()); + + EEType * pTemplateType = pEEType->get_DynamicTemplateType(); + + return GetRuntimeInstance()->CreateGenericInstanceDesc(pEEType, pTemplateType, arity, nonGcStaticDataSize, nonGCStaticDataOffset, gcStaticDataSize, + threadStaticsOffset, pGcStaticsDesc, pThreadStaticsDesc, pGenericVarianceFlags); +} + +COOP_PINVOKE_HELPER(UInt32, RhGetGCDescSize, (EEType* pEEType)) +{ + return RedhawkGCInterface::GetGCDescSize(pEEType); +} + + +// Keep in sync with ndp\fxcore\src\System.Private.CoreLib\system\runtime\runtimeimports.cs +enum RuntimeHelperKind +{ + AllocateObject, + IsInst, + CastClass, + AllocateArray, + CheckArrayElementType, +}; + +// The dictionary codegen expects a pointer that points at a memory location that points to the method pointer +// Create indirections for all helpers used below + +#define DECLARE_INDIRECTION(HELPER_NAME) \ + EXTERN_C void * HELPER_NAME; \ + const PTR_VOID indirection_##HELPER_NAME = (PTR_VOID)&HELPER_NAME + +#define INDIRECTION(HELPER_NAME) ((PTR_VOID)&indirection_##HELPER_NAME) + +DECLARE_INDIRECTION(RhpNewFast); +DECLARE_INDIRECTION(RhpNewFinalizable); + +DECLARE_INDIRECTION(RhpNewArray); + +DECLARE_INDIRECTION(RhTypeCast_IsInstanceOfClass); +DECLARE_INDIRECTION(RhTypeCast_CheckCastClass); +DECLARE_INDIRECTION(RhTypeCast_IsInstanceOfArray); +DECLARE_INDIRECTION(RhTypeCast_CheckCastArray); +DECLARE_INDIRECTION(RhTypeCast_IsInstanceOfInterface); +DECLARE_INDIRECTION(RhTypeCast_CheckCastInterface); + +DECLARE_INDIRECTION(RhTypeCast_CheckVectorElemAddr); + +#ifdef _ARM_ +DECLARE_INDIRECTION(RhpNewFinalizableAlign8); +DECLARE_INDIRECTION(RhpNewFastMisalign); +DECLARE_INDIRECTION(RhpNewFastAlign8); + +DECLARE_INDIRECTION(RhpNewArrayAlign8); +#endif + +COOP_PINVOKE_HELPER(PTR_VOID, RhGetRuntimeHelperForType, (EEType * pEEType, int helperKind)) +{ + // This implementation matches what the binder does (MetaDataEngine::*() in rh\src\tools\rhbind\MetaDataEngine.cpp) + // If you change the binder's behavior, change this implementation too + + switch (helperKind) + { + case RuntimeHelperKind::AllocateObject: +#ifdef _ARM_ + if ((pEEType->get_RareFlags() & EEType::RareFlags::RequiresAlign8Flag) == EEType::RareFlags::RequiresAlign8Flag) + { + if (pEEType->HasFinalizer()) + return INDIRECTION(RhpNewFinalizableAlign8); + else if (pEEType->get_IsValueType()) // returns true for enum types as well + return INDIRECTION(RhpNewFastMisalign); + else + return INDIRECTION(RhpNewFastAlign8); + } +#endif + if (pEEType->HasFinalizer()) + return INDIRECTION(RhpNewFinalizable); + else + return INDIRECTION(RhpNewFast); + + case RuntimeHelperKind::IsInst: + if (pEEType->IsArray()) + return INDIRECTION(RhTypeCast_IsInstanceOfArray); + else if (pEEType->IsInterface()) + return INDIRECTION(RhTypeCast_IsInstanceOfInterface); + else + return INDIRECTION(RhTypeCast_IsInstanceOfClass); + + case RuntimeHelperKind::CastClass: + if (pEEType->IsArray()) + return INDIRECTION(RhTypeCast_CheckCastArray); + else if (pEEType->IsInterface()) + return INDIRECTION(RhTypeCast_CheckCastInterface); + else + return INDIRECTION(RhTypeCast_CheckCastClass); + + case RuntimeHelperKind::AllocateArray: +#ifdef _ARM_ + if (pEEType->RequiresAlign8()) + return INDIRECTION(RhpNewArrayAlign8); +#endif + return INDIRECTION(RhpNewArray); + + case RuntimeHelperKind::CheckArrayElementType: + return INDIRECTION(RhTypeCast_CheckVectorElemAddr); + + default: + UNREACHABLE(); + } +} + +#undef DECLARE_INDIRECTION +#undef INDIRECTION + +#ifdef FEATURE_CACHED_INTERFACE_DISPATCH +EXTERN_C void * RhpInitialDynamicInterfaceDispatch; + +COOP_PINVOKE_HELPER(void *, RhNewInterfaceDispatchCell, (EEType * pInterface, Int32 slotNumber)) +{ + InterfaceDispatchCell * pCell = new InterfaceDispatchCell[2]; + if (pCell == NULL) + return NULL; + + // Due to the synchronization mechanism used to update this indirection cell we must ensure the cell's alignment is twice that of a pointer. + // Fortunately, Windows heap guarantees this aligment. + ASSERT(IS_ALIGNED(pCell, 2 * POINTER_SIZE)); + ASSERT(IS_ALIGNED(pInterface, (InterfaceDispatchCell::IDC_CachePointerMask + 1))); + + pCell[0].m_pStub = (UIntNative)&RhpInitialDynamicInterfaceDispatch; + pCell[0].m_pCache = ((UIntNative)pInterface) | InterfaceDispatchCell::IDC_CachePointerIsInterfacePointer; + pCell[1].m_pStub = 0; + pCell[1].m_pCache = (UIntNative)slotNumber; + + return pCell; +} +#endif // FEATURE_CACHED_INTERFACE_DISPATCH + +COOP_PINVOKE_HELPER(PTR_UInt8, RhGetThreadLocalStorageForDynamicType, (UInt32 uOffset, UInt32 tlsStorageSize, UInt32 numTlsCells)) +{ + Thread * pCurrentThread = ThreadStore::GetCurrentThread(); + + PTR_UInt8 pResult = pCurrentThread->GetThreadLocalStorageForDynamicType(uOffset); + if (pResult != NULL || tlsStorageSize == 0 || numTlsCells == 0) + return pResult; + + ASSERT(tlsStorageSize > 0 && numTlsCells > 0); + return pCurrentThread->AllocateThreadLocalStorageForDynamicType(uOffset, tlsStorageSize, numTlsCells); +} + +COOP_PINVOKE_HELPER(void *, RhGetNonGcStaticFieldData, (EEType * pEEType)) +{ + // We shouldn't be attempting to get the gc/non-gc statics data for non-dynamic types... + // For non-dynamic types, that info should have been hashed in a table and stored in its corresponding blob in the image. + // The reason we don't want to do the lookup for non-dynamic types is that LookupGenericInstance will do the lookup in + // a hashtable that *only* has the GIDs with variance. If we were to store all GIDs in that hashtable, we'd be violating + // pay-for-play principles + ASSERT(pEEType->IsDynamicType()); + + GenericInstanceDesc * pGid = GetRuntimeInstance()->LookupGenericInstance(pEEType); + ASSERT(pGid != NULL); + + if (pGid->HasNonGcStaticFields()) + { + return dac_cast(pGid + pGid->GetNonGcStaticFieldDataOffset()); + } + + return NULL; +} + +COOP_PINVOKE_HELPER(void *, RhGetGcStaticFieldData, (EEType * pEEType)) +{ + // We shouldn't be attempting to get the gc/non-gc statics data for non-dynamic types... + // For non-dynamic types, that info should have been hashed in a table and stored in its corresponding blob in the image. + // The reason we don't want to do the lookup for non-dynamic types is that LookupGenericInstance will do the lookup in + // a hashtable that *only* has the GIDs with variance. If we were to store all GIDs in that hashtable, we'd be violating + // pay-for-play principles + ASSERT(pEEType->IsDynamicType()); + + GenericInstanceDesc * pGid = GetRuntimeInstance()->LookupGenericInstance(pEEType); + ASSERT(pGid != NULL); + + if (pGid->HasGcStaticFields()) + { + return dac_cast(pGid + pGid->GetGcStaticFieldDataOffset()); + } + + return NULL; +} + +COOP_PINVOKE_HELPER(void *, RhAllocateThunksFromTemplate, (PTR_UInt8 moduleBase, UInt32 templateRva, UInt32 templateSize)) +{ + void* pThunkMap = NULL; + if (PalAllocateThunksFromTemplate((HANDLE)moduleBase, templateRva, templateSize, &pThunkMap) == FALSE) + return NULL; + + return pThunkMap; +} + +#endif diff --git a/src/Native/Runtime/RuntimeInstance.h b/src/Native/Runtime/RuntimeInstance.h new file mode 100644 index 00000000000..9ab374a05dd --- /dev/null +++ b/src/Native/Runtime/RuntimeInstance.h @@ -0,0 +1,226 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +class ThreadStore; +typedef DPTR(ThreadStore) PTR_ThreadStore; +class Module; +typedef DPTR(Module) PTR_Module; +class ICodeManager; +class VirtualCallStubManager; +struct GenericInstanceDesc; +typedef SPTR(GenericInstanceDesc) PTR_GenericInstanceDesc; +struct ModuleHeader; +enum GenericVarianceType : UInt8; +struct UnifiedGenericInstance; +class GenericTypeHashTable; +typedef DPTR(GenericTypeHashTable) PTR_GenericTypeHashTable; +struct StaticGcDesc; +struct SimpleModuleHeader; + +class RuntimeInstance +{ + friend class AsmOffsets; + friend struct DefaultSListTraits; + friend class Thread; + + PTR_RuntimeInstance m_pNext; + PTR_ThreadStore m_pThreadStore; + HANDLE m_hPalInstance; // this is the HANDLE passed into DllMain + SList m_ModuleList; + ReaderWriterLock m_ModuleListLock; + +#ifdef FEATURE_DYNAMIC_CODE + struct CodeManagerEntry; + typedef DPTR(CodeManagerEntry) PTR_CodeManagerEntry; + + struct CodeManagerEntry + { + PTR_CodeManagerEntry m_pNext; + PTR_VOID m_pvStartRange; + UInt32 m_cbRange; + ICodeManager * m_pCodeManager; + }; + + typedef SList CodeManagerList; + CodeManagerList m_CodeManagerList; +#endif + +#ifdef FEATURE_VSD + VirtualCallStubManager * m_pVSDManager; +#endif + + // Indicates whether the runtime is in standalone exe mode where the only Redhawk module that will be + // loaded into the process (besides the runtime's own module) is the exe itself. The most important aspect + // of this is that generic types don't need to be unified. This flag will be correctly initialized once + // the exe module has loaded. + bool m_fStandaloneExeMode; + + // If m_fStandaloneExeMode is set this contains a pointer to the exe module. Otherwise it's null. + Module * m_pStandaloneExeModule; + +#ifdef FEATURE_PROFILING + // The thread writing the profile data is created lazily, whenever + // a module with a profile section is registered. + // To avoid starting the thread more than once, this flag indicates + // whether the thread has been created already. + bool m_fProfileThreadCreated; +#endif + + // Generic type unification. Used only if we're not in single standalone exe mode. + UnifiedGenericInstance ** m_genericInstHashtab; + UnifiedGenericInstance ** m_genericInstHashtabUpdates; + UInt32 m_genericInstHashtabCount; + UInt32 m_genericInstHashtabEntries; + CrstStatic m_genericInstHashtabLock; +#ifdef _DEBUG + bool m_genericInstHashUpdateInProgress; +#endif + + // List of generic instances that have GC references to report. This list is updated under the hash table + // lock above and enumerated without lock during garbage collections (when updates cannot occur). This + // list is only used in non-standalone exe mode, i.e. when we're unifying generic types. In standalone + // mode we report the GenericInstanceDescs directly from the module itself. + PTR_GenericInstanceDesc m_genericInstReportList; + + ReaderWriterLock m_GenericHashTableLock; + + // This is used (in standalone mode only) to build an on-demand hash tables of all generic instantiations + PTR_GenericTypeHashTable m_pGenericTypeHashTable; + + bool m_conservativeStackReportingEnabled; + + RuntimeInstance(); + +#ifdef FEATURE_VSD + static bool CreateVSD(VirtualCallStubManager ** ppVSD); +#endif + + SList* GetModuleList(); + + bool BuildGenericTypeHashTable(); + +public: + class ModuleIterator + { + ReaderWriterLock::ReadHolder m_readHolder; + PTR_Module m_pCurrentPosition; + public: + ModuleIterator(); + ~ModuleIterator(); + PTR_Module GetNext(); + }; + + ~RuntimeInstance(); + ThreadStore * GetThreadStore(); + HANDLE GetPalInstance(); + + bool RegisterModule(ModuleHeader *pModuleHeader); + bool RegisterSimpleModule(SimpleModuleHeader *pModuleHeader); + void UnregisterModule(Module *pModule); + Module * FindModuleByAddress(PTR_VOID pvAddress); + Module * FindModuleByCodeAddress(PTR_VOID ControlPC); + Module * FindModuleByDataAddress(PTR_VOID Data); + Module * FindModuleByReadOnlyDataAddress(PTR_VOID Data); + Module * FindModuleByOsHandle(HANDLE hOsHandle); + PTR_UInt8 FindMethodStartAddress(PTR_VOID ControlPC); + bool EnableConservativeStackReporting(); + bool IsConservativeStackReportingEnabled() { return m_conservativeStackReportingEnabled; } + +#ifdef FEATURE_DYNAMIC_CODE + bool RegisterCodeManager(ICodeManager * pCodeManager, PTR_VOID pvStartRange, UInt32 cbRange); + void UnregisterCodeManager(ICodeManager * pCodeManager); +#endif + ICodeManager * FindCodeManagerByAddress(PTR_VOID ControlPC); + + // This will hold the module list lock over each callback. Make sure + // the callback will not trigger any operation that needs to make use + // of the module list. + typedef void (* EnumerateModulesCallbackPFN)(Module *pModule, void *pvContext); + void EnumerateModulesUnderLock(EnumerateModulesCallbackPFN pCallback, void *pvContext); + + static RuntimeInstance * Create(HANDLE hPalInstance); + void Destroy(); + + void EnumGenericStaticGCRefs(PTR_GenericInstanceDesc pInst, void * pfnCallback, void * pvCallbackData, Module *pModule); + void EnumAllStaticGCRefs(void * pfnCallback, void * pvCallbackData); + +#ifdef FEATURE_VSD + VirtualCallStubManager * GetVSDManager() { return m_pVSDManager; } +#endif + + bool ShouldHijackCallsiteForGcStress(UIntNative CallsiteIP); + bool ShouldHijackLoopForGcStress(UIntNative CallsiteIP); + + bool StartGenericUnification(UInt32 cInstances); + UnifiedGenericInstance *UnifyGenericInstance(GenericInstanceDesc *genericInstance, UInt32 uiLocalTlsIndex); + void EndGenericUnification(); + + void ReleaseGenericInstance(GenericInstanceDesc * pInst); + + void EnableGcPollStress(); + void UnsychronizedResetHijackedLoops(); + + // Given the EEType* for an instantiated generic type retrieve the GenericInstanceDesc associated with + // that type. This is legal only for types that are guaranteed to have this metadata at runtime; generic + // types which have variance over one or more of their type parameters and generic interfaces on array). + GenericInstanceDesc * LookupGenericInstance(EEType * pEEType); + + // Given the EEType* for an instantiated generic type retrieve instantiation information (generic type + // definition EEType, arity, type arguments and variance info for each type parameter). Has the same + // limitations on usage as LookupGenericInstance above. + EEType * GetGenericInstantiation(EEType * pEEType, + UInt32 * pArity, + EEType *** ppInstantiation, + GenericVarianceType ** ppVarianceInfo); + + bool SetGenericInstantiation(EEType * pEEType, + EEType * pEETypeDef, + UInt32 arity, + EEType ** pInstantiation); + + bool CreateGenericInstanceDesc(EEType * pEEType, + EEType * pTemplateType, + UInt32 arity, + UInt32 nonGcStaticDataSize, + UInt32 nonGCStaticDataOffset, + UInt32 gcStaticDataSize, + UInt32 threadStaticOffset, + StaticGcDesc * pGcStaticsDesc, + StaticGcDesc * pThreadStaticsDesc, + UInt32* pGenericVarianceFlags); + +#ifdef FEATURE_PROFILING + void InitProfiling(ModuleHeader *pModuleHeader); + void WriteProfileInfo(); +#endif // FEATURE_PROFILING + + bool IsInStandaloneExeMode() + { + return m_fStandaloneExeMode; + } + + Module * GetStandaloneExeModule() + { + ASSERT(IsInStandaloneExeMode()); + return m_pStandaloneExeModule; + } +}; +typedef DPTR(RuntimeInstance) PTR_RuntimeInstance; + + +PTR_RuntimeInstance GetRuntimeInstance(); + + +#define FOREACH_MODULE(p_module_name) \ +{ \ + RuntimeInstance::ModuleIterator __modules; \ + Module * p_module_name; \ + while ((p_module_name = __modules.GetNext()) != NULL) \ + { \ + +#define END_FOREACH_MODULE \ + } \ +} \ + + diff --git a/src/Native/Runtime/SectionMethodList.cpp b/src/Native/Runtime/SectionMethodList.cpp new file mode 100644 index 00000000000..3b3268e5e98 --- /dev/null +++ b/src/Native/Runtime/SectionMethodList.cpp @@ -0,0 +1,311 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "commonmacros.h" +#include "daccess.h" +#include "assert.h" + +#include "commonmacros.inl" +#endif + +#include "rhbinder.h" +#include "sectionmethodlist.h" + + + +#ifndef DACCESS_COMPILE + +SectionMethodList::SectionMethodList() : + m_uFlags(0), + m_pbPageList(NULL), + m_pbMethodList(NULL), + m_pbGCInfoList(NULL), + m_pbGCInfoBlob(NULL), + m_pbEHInfoList(NULL), + m_pbEHInfoBlob(NULL) +#ifdef _DEBUG + , m_uPageListCountDEBUG(0) + , m_uMethodListCountDEBUG(0) +#endif // _DEBUG +{ +} + +#ifndef RHDUMP +bool SectionMethodList::Init(ModuleHeader * pHdr) +{ + // Initialize our flags with the subset from the ModuleHeader that translate directly. + // This gets us the entry size for the page list, GC info list, and EH info list. + UInt32 uFlags = pHdr->Flags & ModuleHeader::FlagsMatchingSMLFlagsMask; + return Init(uFlags, pHdr->CountOfMethods, pHdr->GetCodeMapInfo(), pHdr->GetEHInfo()); +} +#endif // RHDUMP + +bool SectionMethodList::Init(UInt32 uFlags, UInt32 numMethods, UInt8 * pbCodeMapInfo, UInt8 * pbEHInfo) +{ + m_uFlags = uFlags; + + // Locate the page list. + UInt8 * pbEncodedData = pbCodeMapInfo; + + // @TODO: we could move the count of pages to the MethodHeader, too, and avoid this page read. + UInt32 uNumPages = *(UInt32*)pbEncodedData; + +#ifdef _DEBUG + m_uPageListCountDEBUG = uNumPages; +#endif // _DEBUG + + UInt32 uSizeofEntry = sizeof(UInt32); + pbEncodedData += sizeof(UInt32); + m_pbPageList = pbEncodedData; + + if (m_uFlags & SmallPageListEntriesFlag) + { + uSizeofEntry = sizeof(UInt16); + } + + m_pbMethodList = pbEncodedData + (uNumPages * uSizeofEntry); + + UInt32 uMethodListCount = numMethods + 2; // include the 'fake method' entry and the sentinel entry +#ifdef _DEBUG + m_uMethodListCountDEBUG = uMethodListCount; +#endif // _DEBUG + + // Locate the GC info list, which is just past the method list. + m_pbGCInfoList = (UInt8 *) ALIGN_UP(m_pbMethodList + (uMethodListCount * sizeof(UInt8)), sizeof(UInt16)); + + UInt32 gcInfoListEntrySize = (m_uFlags & SmallGCInfoListEntriesFlag) ? 2 : 4; + UInt32 ehInfoListEntrySize = (m_uFlags & SmallEHInfoListEntriesFlag) ? 2 : 4; + + // Locate the EH info list, which is just past the GC info list. + m_pbEHInfoList = m_pbGCInfoList + (numMethods * gcInfoListEntrySize); + + // Locate the GC info blob, which is just past the EH info list. + // At the start of the gc info blob is the delta shortcut table, which we need to skip. + m_pbGCInfoBlob = m_pbEHInfoList + (numMethods * ehInfoListEntrySize) + + ModuleHeader::DELTA_SHORTCUT_TABLE_SIZE; + + // Locate the EH info blob + m_pbEHInfoBlob = pbEHInfo; + + return true; +} + +PTR_UInt8 SectionMethodList::GetDeltaShortcutTablePtr() +{ + return m_pbGCInfoBlob - ModuleHeader::DELTA_SHORTCUT_TABLE_SIZE; +} + +#endif // !DACCESS_COMPILE + +UInt32 SectionMethodList::GetGCInfoOffset(UInt32 uMethodIndex) +{ + // The gc info offset array is a parallel array to the method list + ASSERT(uMethodIndex < m_uMethodListCountDEBUG); + + if (m_uFlags & SmallGCInfoListEntriesFlag) + { + return (dac_cast(m_pbGCInfoList))[uMethodIndex]; + } + + return (dac_cast(m_pbGCInfoList))[uMethodIndex]; +} + +PTR_UInt8 SectionMethodList::GetGCInfo(UInt32 uMethodIndex) +{ + return m_pbGCInfoBlob + GetGCInfoOffset(uMethodIndex); +} + +PTR_VOID SectionMethodList::GetEHInfo(UInt32 uMethodIndex) +{ + // The EH info offset array is a parallel array to the method list. + ASSERT(uMethodIndex < m_uMethodListCountDEBUG); + + // Some methods do not have EH info. These are marked with an offset of -1. + // @TODO: consider using a sentinel EHInfo that contains zero clauses to reduce the path length in here. + if (m_uFlags & SmallEHInfoListEntriesFlag) + { + UInt16 offset = (dac_cast(m_pbEHInfoList))[uMethodIndex]; + + if (offset != 0xFFFF) + { + return m_pbEHInfoBlob + offset; + } + } + else + { + UInt32 offset = (dac_cast(m_pbEHInfoList))[uMethodIndex]; + + if (offset != 0xFFFFFFFF) + { + return m_pbEHInfoBlob + offset; + } + } + + return NULL; +} + +PageEntry SectionMethodList::GetPageListEntry(UInt32 idx) +{ + ASSERT(idx < m_uPageListCountDEBUG); + + if (m_uFlags & SmallPageListEntriesFlag) + return PageEntry(dac_cast(m_pbPageList)[idx]); + + return PageEntry((dac_cast(m_pbPageList))[idx]); +} + +void SectionMethodList::GetMethodInfo(UInt32 uSectionOffset, UInt32 * puMethodIndex, + UInt32 * puMethodStartSectionOffset, UInt32 * puMethodSize) +{ + UInt32 uPageNumber = SectionOffsetToPageNumber(uSectionOffset); + UInt32 uPageOffset = SectionOffsetToPageOffset(uSectionOffset); + + PageEntry page = GetPageListEntry(uPageNumber); + + UInt32 idxCurMethod = page.GetMethodIndex(); + + if (page.IsCoveredByOneMethod() || + (uPageOffset < GetMethodPageOffset(idxCurMethod))) + { + // This page is covered completely by a method that started on a previous page. The index in this + // entry is the index of the method following the spanning method, so the correct method index is + // one less than the index in the entry. + // + // OR + // + // The current page offset falls before the page offset of the first method that begins on the page. + // Therefore, we must look for the last method on the previous page. The correct method index is + // one less than the index in the entry. + + idxCurMethod = idxCurMethod - 1; + + // Now search for the first prior page which isn't completely covered by a method. This will be the + // page on which this method starts. + + for (;;) + { + // Since we think the method starts on a previous page, we must not be at page 0 already. + ASSERT(uPageNumber > 0); + uPageNumber--; + + page = GetPageListEntry(uPageNumber); + if (!page.IsCoveredByOneMethod()) + break; + } + + } + else + { + // This works because we always have an extra page at the end of the pageList which holds an index of + // methodCount, additionally, for pages which are spanning they contain the method index of the method + // following the spanning method. + UInt32 idxMaxMethod = GetPageListEntry(uPageNumber + 1).GetMethodIndex() - 1; + + // At this point, we know the method is one of the set [idxCurMethod, idxMaxMethod]. + + // Linear search -- @TODO: also implement binary search if the number of methods to scan is large. + for (; idxCurMethod < idxMaxMethod; idxCurMethod++) + { + if (uPageOffset < GetMethodPageOffset(idxCurMethod + 1)) + { + break; + } + } + } + + *puMethodIndex = idxCurMethod; + *puMethodStartSectionOffset = (uPageNumber * SECTION_METHOD_LIST_PAGE_SIZE) + GetMethodPageOffset(idxCurMethod); + + if (puMethodSize != NULL) + { + // + // Find the page that the next method starts on... + // + UInt32 idxNextMethod = idxCurMethod + 1; + + UInt32 idxNextPage = uPageNumber + 1; + PageEntry nextPage = GetPageListEntry(idxNextPage); + UInt32 uEndPageNumber; + + if (nextPage.GetMethodIndex() == idxNextMethod) + { + // The current method extends up to and possibly beyond the boundary between this page and the next. + + // If it covers the entire next page, keep going until we find the end. + while (nextPage.IsCoveredByOneMethod()) + { + idxNextPage++; + nextPage = GetPageListEntry(idxNextPage); + } + + uEndPageNumber = idxNextPage; + } + else + { + // The current method ends on the page it starts on. + uEndPageNumber = uPageNumber; + } + + UInt32 uMethodEndSectionOffset = (uEndPageNumber * SECTION_METHOD_LIST_PAGE_SIZE) + + GetMethodPageOffset(idxNextMethod); + ASSERT(uMethodEndSectionOffset > *puMethodStartSectionOffset); + + *puMethodSize = uMethodEndSectionOffset - *puMethodStartSectionOffset; + } +} + +UInt32 SectionMethodList::GetMethodPageOffset(UInt32 idxMethod) +{ + ASSERT(idxMethod < m_uMethodListCountDEBUG); + + return (m_pbMethodList[idxMethod] * METHOD_ALIGNMENT_IN_BYTES); +} + +// returns the section page number from the byte offset within a section +UInt32 SectionMethodList::SectionOffsetToPageNumber(UInt32 uSectionOffset) +{ + return uSectionOffset / SECTION_METHOD_LIST_PAGE_SIZE; +} + +// returns the byte offset within a page from the byte offset within a section +UInt32 SectionMethodList::SectionOffsetToPageOffset(UInt32 uSectionOffset) +{ + return uSectionOffset % SECTION_METHOD_LIST_PAGE_SIZE; +} + +#ifdef _DEBUG +UInt32 SectionMethodList::GetNumMethodsDEBUG() +{ + ASSERT(m_uMethodListCountDEBUG > 0); + return (m_uMethodListCountDEBUG - 2); // -1 to account for the 'dummy method' that fills up the last page + // -1 to account for the sentinel entry at the end +} +#endif // _DEBUG + +PageEntry::PageEntry(UInt32 uPageEntry) : + m_uPageEntry(uPageEntry) +{ +} + +bool PageEntry::IsCoveredByOneMethod() +{ + // If the method index is the index of a method starting on some previous page, then + // this page is completely covered by that one method. + return m_uPageEntry & METHOD_STARTS_ON_PREV_PAGE_FLAG; +} + +// There are two meanings for this method index: +// -- if (!IsCoveredByOneMethod()) { This is the index of the first method that begins on that page. } +// -- else { This is the index of the method that follows the one covering this page. } +UInt32 PageEntry::GetMethodIndex() +{ + return m_uPageEntry >> METHOD_INDEX_SHIFT_AMOUNT; +} \ No newline at end of file diff --git a/src/Native/Runtime/SectionMethodList.h b/src/Native/Runtime/SectionMethodList.h new file mode 100644 index 00000000000..748ffe11804 --- /dev/null +++ b/src/Native/Runtime/SectionMethodList.h @@ -0,0 +1,79 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +class PageEntry +{ + UInt32 m_uPageEntry; + + static UInt32 const MAX_METHOD_INDEX = 0x7FFFFFFF; // reserve one bit for a flag + static UInt32 const MAX_METHOD_INDEX_SMALLENTRIES = 0x7FFF; // reserve one bit for a flag + static UInt32 const METHOD_INDEX_SHIFT_AMOUNT = 1; // shift by one for flag + static UInt32 const METHOD_STARTS_ON_PREV_PAGE_FLAG = 0x00000001; // used in page list entries + +public: + PageEntry(UInt32 uPageEntry); + + bool IsCoveredByOneMethod(); + UInt32 GetMethodIndex(); +}; + +#ifndef RHDUMP +struct ModuleHeader; +typedef DPTR(ModuleHeader) PTR_ModuleHeader; +#endif // RHDUMP + +class SectionMethodList +{ + UInt32 m_uFlags; + + PTR_UInt8 m_pbPageList; + PTR_UInt8 m_pbMethodList; + PTR_UInt8 m_pbGCInfoList; + PTR_UInt8 m_pbGCInfoBlob; + PTR_UInt8 m_pbEHInfoList; + PTR_UInt8 m_pbEHInfoBlob; + +#ifdef _DEBUG + UInt32 m_uPageListCountDEBUG; + UInt32 m_uMethodListCountDEBUG; +#endif // _DEBUG + + PageEntry GetPageListEntry(UInt32 idx); + UInt32 GetMethodPageOffset(UInt32 idxMethod); + + UInt32 SectionOffsetToPageNumber(UInt32 uSectionOffset); + UInt32 SectionOffsetToPageOffset(UInt32 uSectionOffset); + + // A subset of these flags match those that come from the module header, written by the binder. This set must be kept + // in sync with the definitions in ModuleHeader::ModuleHeaderFlags + enum SectionMethodListFlags + { + SmallPageListEntriesFlag = 0x00000001, // if set, 2-byte page list entries, 4-byte otherwise + SmallGCInfoListEntriesFlag = 0x00000002, // if set, 2-byte gc info list entries, 4-byte otherwise + SmallEHInfoListEntriesFlag = 0x00000004, // if set, 2-byte EH info list entries, 4-byte otherwise + }; + + static UInt32 const SECTION_METHOD_LIST_PAGE_SIZE = 1024; + static UInt32 const METHOD_ALIGNMENT_IN_BYTES = 4; + +public: + SectionMethodList(); + +#ifndef RHDUMP + bool Init(ModuleHeader * pHdr); +#endif // RHDUMP + bool Init(UInt32 uFlags, UInt32 numMethods, UInt8 * pbCodeMapInfo, UInt8 * pbEHInfo); + void GetMethodInfo(UInt32 uSectionOffset, UInt32 * puMethodIndex, + UInt32 * puMethodStartSectionOffset, UInt32 * puMethodSize); + + UInt32 GetGCInfoOffset(UInt32 uMethodIndex); + PTR_UInt8 GetGCInfo(UInt32 uMethodIndex); + PTR_VOID GetEHInfo(UInt32 uMethodIndex); + PTR_UInt8 GetDeltaShortcutTablePtr(); + +#ifdef _DEBUG + UInt32 GetNumMethodsDEBUG(); +#endif // _DEBUG +}; \ No newline at end of file diff --git a/src/Native/Runtime/SpinLock.h b/src/Native/Runtime/SpinLock.h new file mode 100644 index 00000000000..35e3afac003 --- /dev/null +++ b/src/Native/Runtime/SpinLock.h @@ -0,0 +1,75 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifndef __SPINLOCK_H__ +#define __SPINLOCK_H__ + +UInt32_BOOL __SwitchToThread(UInt32 dwSleepMSec, UInt32 dwSwitchCount); + +// #SwitchToThreadSpinning +// +// If you call __SwitchToThread in a loop waiting for a condition to be met, +// it is critical that you insert periodic sleeps. This is because the thread +// you are waiting for to set that condition may need your CPU, and simply +// calling __SwitchToThread(0) will NOT guarantee that it gets a chance to run. +// If there are other runnable threads of higher priority, or even if there +// aren't and it is in another processor's queue, you will be spinning a very +// long time. +// +// To force all callers to consider this issue and to avoid each having to +// duplicate the same backoff code, __SwitchToThread takes a required second +// parameter. If you want it to handle backoff for you, this parameter should +// be the number of successive calls you have made to __SwitchToThread (a loop +// count). If you want to take care of backing off yourself, you can pass +// CALLER_LIMITS_SPINNING. There are three valid cases for doing this: +// +// - You count iterations and induce a sleep periodically +// - The number of consecutive __SwitchToThreads is limited +// - Your call to __SwitchToThread includes a non-zero sleep duration +// +// Lastly, to simplify this requirement for the following common coding pattern: +// +// while (!condition) +// SwitchToThread +// +// you can use the YIELD_WHILE macro. + +#define YIELD_WHILE(condition) \ + { \ + UInt32 __dwSwitchCount = 0; \ + while (condition) \ + { \ + __SwitchToThread(0, ++__dwSwitchCount); \ + } \ + } + +class SpinLock +{ +private: + enum LOCK_STATE + { + UNLOCKED = 0, + LOCKED = 1 + }; + + volatile Int32 m_lock; + + static void Lock(SpinLock& lock) + { YIELD_WHILE (PalInterlockedExchange(&lock.m_lock, LOCKED) == LOCKED); } + + static void Unlock(SpinLock& lock) + { PalInterlockedExchange(&lock.m_lock, UNLOCKED); } + +public: + SpinLock() + : m_lock(UNLOCKED) { } + + typedef HolderNoDefaultValue + Holder; +}; + +#endif + diff --git a/src/Native/Runtime/StackFrameIterator.cpp b/src/Native/Runtime/StackFrameIterator.cpp new file mode 100644 index 00000000000..49df0cd5b10 --- /dev/null +++ b/src/Native/Runtime/StackFrameIterator.cpp @@ -0,0 +1,1195 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "redhawkwarnings.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "rwlock.h" +#include "static_check.h" +#include "event.h" +#include "threadstore.h" +#include "stresslog.h" +#endif + +#include "module.h" +#include "runtimeinstance.h" +#include "rhbinder.h" + +// warning C4061: enumerator '{blah}' in switch of enum '{blarg}' is not explicitly handled by a case label +#pragma warning(disable:4061) + +#if !defined(USE_PORTABLE_HELPERS) // these are (currently) only implemented in assembly helpers +// When we use a thunk to call out to managed code from the runtime the following label is the instruction +// immediately following the thunk's call instruction. As such it can be used to identify when such a callout +// has occured as we are walking the stack. +EXTERN_C void * ReturnFromManagedCallout2; +GVAL_IMPL_INIT(PTR_VOID, g_ReturnFromManagedCallout2Addr, &ReturnFromManagedCallout2); + +#if defined(FEATURE_DYNAMIC_CODE) +EXTERN_C void * ReturnFromUniversalTransition; +GVAL_IMPL_INIT(PTR_VOID, g_ReturnFromUniversalTransitionAddr, &ReturnFromUniversalTransition); + +EXTERN_C void * ReturnFromCallDescrThunk; +GVAL_IMPL_INIT(PTR_VOID, g_ReturnFromCallDescrThunkAddr, &ReturnFromCallDescrThunk); +#endif + +#ifdef FEATURE_CLR_EH +#ifdef TARGET_X86 +EXTERN_C void * RhpCallFunclet2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpCallFunclet2Addr, &RhpCallFunclet2); +#endif +EXTERN_C void * RhpCallCatchFunclet2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpCallCatchFunclet2Addr, &RhpCallCatchFunclet2); +EXTERN_C void * RhpCallFinallyFunclet2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpCallFinallyFunclet2Addr, &RhpCallFinallyFunclet2); +EXTERN_C void * RhpCallFilterFunclet2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpCallFilterFunclet2Addr, &RhpCallFilterFunclet2); +EXTERN_C void * RhpThrowEx2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpThrowEx2Addr, &RhpThrowEx2); +EXTERN_C void * RhpThrowHwEx2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpThrowHwEx2Addr, &RhpThrowHwEx2); +EXTERN_C void * RhpRethrow2; +GVAL_IMPL_INIT(PTR_VOID, g_RhpRethrow2Addr, &RhpRethrow2); +#endif +#endif //!defined(USE_PORTABLE_HELPERS) + +// Addresses of functions in the DAC won't match their runtime counterparts so we +// assign them to globals. However it is more performant in the runtime to compare +// against immediates than to fetch the global. This macro hides the difference. +#ifdef DACCESS_COMPILE +#define EQUALS_CODE_ADDRESS(x, func_name) ((x) == g_ ## func_name ## Addr) +#else +#define EQUALS_CODE_ADDRESS(x, func_name) ((x) == &func_name) +#endif + + +// The managed callout thunk above stashes a transition frame pointer in its FP frame. The following constant +// is the offset from the FP at which this pointer is stored. +#define MANAGED_CALLOUT_THUNK_TRANSITION_FRAME_POINTER_OFFSET (-(Int32)sizeof(UIntNative)) + +PTR_PInvokeTransitionFrame GetPInvokeTransitionFrame(PTR_VOID pTransitionFrame) +{ + return static_cast(pTransitionFrame); +} + + +StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PTR_VOID pInitialTransitionFrame) +{ + STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ GC ]\n"); + ASSERT(!pThreadToWalk->DangerousCrossThreadIsHijacked()); + InternalInit(pThreadToWalk, GetPInvokeTransitionFrame(pInitialTransitionFrame)); +} + +StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx) +{ + STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ hijack ]\n"); + InternalInit(pThreadToWalk, pCtx, 0); +} + +void StackFrameIterator::ResetNextExInfoForSP(UIntNative SP) +{ + while (m_pNextExInfo && (SP > (UIntNative)dac_cast(m_pNextExInfo))) + m_pNextExInfo = m_pNextExInfo->m_pPrevExInfo; +} + +void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PTR_PInvokeTransitionFrame pFrame) +{ + m_pThread = pThreadToWalk; + m_pInstance = GetRuntimeInstance(); + m_pCodeManager = NULL; + m_pHijackedReturnValue = NULL; + m_HijackedReturnValueKind = GCRK_Unknown; + m_pConservativeStackRangeLowerBound = NULL; + m_pConservativeStackRangeUpperBound = NULL; + m_dwFlags = CollapseFunclets | RemapHardwareFaultsToSafePoint; // options for GC stack walk + m_pNextExInfo = pThreadToWalk->GetCurExInfo(); + + if (pFrame == TOP_OF_STACK_MARKER) + { + m_ControlPC = 0; + return; + } + + memset(&m_RegDisplay, 0, sizeof(m_RegDisplay)); + + // We need to walk the ExInfo chain in parallel with the stackwalk so that we know when we cross over + // exception throw points. So we must find our initial point in the ExInfo chain here so that we can + // properly walk it in parallel. + ResetNextExInfoForSP((UIntNative)dac_cast(pFrame)); + + m_RegDisplay.SetIP((PCODE)pFrame->m_RIP); + m_RegDisplay.SetAddrOfIP((PTR_PCODE)PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_RIP)); + + PTR_UIntNative pPreservedRegsCursor = (PTR_UIntNative)PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_PreservedRegs); + +#ifdef TARGET_ARM + m_RegDisplay.pLR = (PTR_UIntNative)PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_RIP); + m_RegDisplay.pR11 = (PTR_UIntNative)PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_ChainPointer); + + if (pFrame->m_dwFlags & PTFF_SAVE_R4) { m_RegDisplay.pR4 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R5) { m_RegDisplay.pR5 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R6) { m_RegDisplay.pR6 = pPreservedRegsCursor++; } + ASSERT(!(pFrame->m_dwFlags & PTFF_SAVE_R7)); // R7 should never contain a GC ref because we require + // a frame pointer for methods with pinvokes + if (pFrame->m_dwFlags & PTFF_SAVE_R8) { m_RegDisplay.pR8 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R9) { m_RegDisplay.pR9 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R10) { m_RegDisplay.pR10 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_SP) { m_RegDisplay.SP = *pPreservedRegsCursor++; } + + m_RegDisplay.pR7 = (PTR_UIntNative) PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_FramePointer); + + if (pFrame->m_dwFlags & PTFF_SAVE_R0) { m_RegDisplay.pR0 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R1) { m_RegDisplay.pR1 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R2) { m_RegDisplay.pR2 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R3) { m_RegDisplay.pR3 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_LR) { m_RegDisplay.pLR = pPreservedRegsCursor++; } + + if (pFrame->m_dwFlags & PTFF_R0_IS_GCREF) + { + m_pHijackedReturnValue = (PTR_RtuObjectRef) m_RegDisplay.pR0; + m_HijackedReturnValueKind = GCRK_Object; + } + if (pFrame->m_dwFlags & PTFF_R0_IS_BYREF) + { + m_pHijackedReturnValue = (PTR_RtuObjectRef) m_RegDisplay.pR0; + m_HijackedReturnValueKind = GCRK_Byref; + } + + m_ControlPC = dac_cast(*(m_RegDisplay.pIP)); +#else // TARGET_ARM + if (pFrame->m_dwFlags & PTFF_SAVE_RBX) { m_RegDisplay.pRbx = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_RSI) { m_RegDisplay.pRsi = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_RDI) { m_RegDisplay.pRdi = pPreservedRegsCursor++; } + ASSERT(!(pFrame->m_dwFlags & PTFF_SAVE_RBP)); // RBP should never contain a GC ref because we require + // a frame pointer for methods with pinvokes +#ifdef TARGET_AMD64 + if (pFrame->m_dwFlags & PTFF_SAVE_R12) { m_RegDisplay.pR12 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R13) { m_RegDisplay.pR13 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R14) { m_RegDisplay.pR14 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R15) { m_RegDisplay.pR15 = pPreservedRegsCursor++; } +#endif // TARGET_AMD64 + + m_RegDisplay.pRbp = (PTR_UIntNative) PTR_HOST_MEMBER(PInvokeTransitionFrame, pFrame, m_FramePointer); + + if (pFrame->m_dwFlags & PTFF_SAVE_RSP) { m_RegDisplay.SP = *pPreservedRegsCursor++; } + + if (pFrame->m_dwFlags & PTFF_SAVE_RAX) { m_RegDisplay.pRax = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_RCX) { m_RegDisplay.pRcx = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_RDX) { m_RegDisplay.pRdx = pPreservedRegsCursor++; } +#ifdef TARGET_AMD64 + if (pFrame->m_dwFlags & PTFF_SAVE_R8 ) { m_RegDisplay.pR8 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R9 ) { m_RegDisplay.pR9 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R10) { m_RegDisplay.pR10 = pPreservedRegsCursor++; } + if (pFrame->m_dwFlags & PTFF_SAVE_R11) { m_RegDisplay.pR11 = pPreservedRegsCursor++; } +#endif // TARGET_AMD64 + + if (pFrame->m_dwFlags & PTFF_RAX_IS_GCREF) + { + m_pHijackedReturnValue = (PTR_RtuObjectRef) m_RegDisplay.pRax; + m_HijackedReturnValueKind = GCRK_Object; + } + if (pFrame->m_dwFlags & PTFF_RAX_IS_BYREF) + { + m_pHijackedReturnValue = (PTR_RtuObjectRef) m_RegDisplay.pRax; + m_HijackedReturnValueKind = GCRK_Byref; + } + + m_ControlPC = dac_cast(*(m_RegDisplay.pIP)); +#endif // TARGET_ARM + + // @TODO: currently, we always save all registers -- how do we handle the onese we don't save once we + // start only saving those that weren't already saved? + + // If our control PC indicates that we're in one of the thunks we use to make managed callouts from the + // runtime we need to adjust the frame state to that of the managed method that previously called into the + // runtime (i.e. skip the intervening unmanaged frames). + HandleManagedCalloutThunk(); + + STRESS_LOG1(LF_STACKWALK, LL_INFO10000, " %p\n", m_ControlPC); +} + +#ifndef DACCESS_COMPILE + +void StackFrameIterator::InternalInitForEH(Thread * pThreadToWalk, PAL_LIMITED_CONTEXT * pCtx) +{ + STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ EH ]\n"); + StackFrameIterator::InternalInit(pThreadToWalk, pCtx, ApplyReturnAddressAdjustment); + + STRESS_LOG1(LF_STACKWALK, LL_INFO10000, " %p\n", m_ControlPC); +} + +void StackFrameIterator::InternalInitForStackTrace() +{ + STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ StackTrace ]\n"); + Thread * pThreadToWalk = ThreadStore::GetCurrentThread(); + PTR_VOID pFrame = pThreadToWalk->GetTransitionFrameForStackTrace(); + InternalInit(pThreadToWalk, GetPInvokeTransitionFrame(pFrame)); +} + +#endif //!DACCESS_COMPILE + +void StackFrameIterator::InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx, UInt32 dwFlags) +{ + ASSERT((dwFlags & MethodStateCalculated) == 0); + + m_pThread = pThreadToWalk; + m_pInstance = GetRuntimeInstance(); + m_ControlPC = 0; + m_pCodeManager = NULL; + m_pHijackedReturnValue = NULL; + m_HijackedReturnValueKind = GCRK_Unknown; + m_pConservativeStackRangeLowerBound = NULL; + m_pConservativeStackRangeUpperBound = NULL; + m_dwFlags = dwFlags; + m_pNextExInfo = pThreadToWalk->GetCurExInfo(); + + // We need to walk the ExInfo chain in parallel with the stackwalk so that we know when we cross over + // exception throw points. So we must find our initial point in the ExInfo chain here so that we can + // properly walk it in parallel. + ResetNextExInfoForSP(pCtx->GetSp()); + + PTR_VOID ControlPC = dac_cast(pCtx->GetIp()); + if (dwFlags & ApplyReturnAddressAdjustment) + ControlPC = AdjustReturnAddressBackward(ControlPC); + + // If our control PC indicates that we're in one of the thunks we use to make managed callouts from the + // runtime we need to adjust the frame state to that of the managed method that previously called into the + // runtime (i.e. skip the intervening unmanaged frames). + HandleManagedCalloutThunk(ControlPC, pCtx->GetFp()); + + // This codepath is used by the hijack stackwalk and we can get arbitrary ControlPCs from there. If this + // context has a non-managed control PC, then we're done. + if (!m_pInstance->FindCodeManagerByAddress(ControlPC)) + return; + + // + // control state + // + m_ControlPC = ControlPC; + m_RegDisplay.SP = pCtx->GetSp(); + m_RegDisplay.IP = pCtx->GetIp(); + m_RegDisplay.pIP = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, IP); + +#ifdef TARGET_ARM + // + // preserved regs + // + m_RegDisplay.pR4 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R4); + m_RegDisplay.pR5 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R5); + m_RegDisplay.pR6 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R6); + m_RegDisplay.pR7 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R7); + m_RegDisplay.pR8 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R8); + m_RegDisplay.pR9 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R9); + m_RegDisplay.pR10 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R10); + m_RegDisplay.pR11 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R11); + m_RegDisplay.pLR = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, LR); + + // + // preserved vfp regs + // + for (Int32 i = 0; i < 16 - 8; i++) + { + m_RegDisplay.D[i] = pCtx->D[i]; + } + // + // scratch regs + // + m_RegDisplay.pR0 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R0); +#else // TARGET_ARM + // + // preserved regs + // + m_RegDisplay.pRbp = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, Rbp); + m_RegDisplay.pRsi = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, Rsi); + m_RegDisplay.pRdi = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, Rdi); + m_RegDisplay.pRbx = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, Rbx); +#ifdef TARGET_AMD64 + m_RegDisplay.pR12 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R12); + m_RegDisplay.pR13 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R13); + m_RegDisplay.pR14 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R14); + m_RegDisplay.pR15 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, R15); + // + // preserved xmm regs + // + memcpy(m_RegDisplay.Xmm, &pCtx->Xmm6, sizeof(m_RegDisplay.Xmm)); +#endif // TARGET_AMD64 + + // + // scratch regs + // + m_RegDisplay.pRax = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pCtx, Rax); + m_RegDisplay.pRcx = NULL; + m_RegDisplay.pRdx = NULL; +#ifdef TARGET_AMD64 + m_RegDisplay.pR8 = NULL; + m_RegDisplay.pR9 = NULL; + m_RegDisplay.pR10 = NULL; + m_RegDisplay.pR11 = NULL; +#endif // TARGET_AMD64 +#endif // TARGET_ARM +} + +PTR_VOID StackFrameIterator::HandleExCollide(PTR_ExInfo pExInfo, PTR_VOID collapsingTargetFrame) +{ + STRESS_LOG3(LF_STACKWALK, LL_INFO10000, " [ ex collide ] kind = %d, pass = %d, idxCurClause = %d\n", + pExInfo->m_kind, pExInfo->m_passNumber, pExInfo->m_idxCurClause); + + UInt32 curFlags = m_dwFlags; + + // If we aren't invoking a funclet (i.e. idxCurClause == -1), and we're doing a GC stackwalk, we don't + // want the 2nd-pass collided behavior because that behavior assumes that the previous frame was a + // funclet, which isn't the case when taking a GC at some points in the EH dispatch code. So we treat it + // as if the 2nd pass hasn't actually started yet. + if ((pExInfo->m_passNumber == 1) || + (pExInfo->m_idxCurClause == 0xFFFFFFFF)) + { + ASSERT_MSG(!(curFlags & ApplyReturnAddressAdjustment), + "did not expect to collide with a 1st-pass ExInfo during a EH stackwalk"); + InternalInit(m_pThread, pExInfo->m_pExContext, curFlags); + m_pNextExInfo = pExInfo->m_pPrevExInfo; + CalculateCurrentMethodState(); + ASSERT(IsValid()); + + if ((pExInfo->m_kind == EK_HardwareFault) && (curFlags & RemapHardwareFaultsToSafePoint)) + GetCodeManager()->RemapHardwareFaultToGCSafePoint(&m_methodInfo, &m_codeOffset); + } + else + { + // + // Copy our state from the previous StackFrameIterator + // + this->UpdateFromExceptionDispatch((PTR_StackFrameIterator)&pExInfo->m_frameIter); + + // Sync our 'current' ExInfo with the updated state (we may have skipped other dispatches) + ResetNextExInfoForSP(m_RegDisplay.GetSP()); + + if ((m_dwFlags & ApplyReturnAddressAdjustment) && (curFlags & ApplyReturnAddressAdjustment)) + { + // Counteract our pre-adjusted m_ControlPC, since the caller of this routine will apply the + // adjustment again once we return. + m_ControlPC = AdjustReturnAddressForward(m_ControlPC); + } + m_dwFlags = curFlags; + if ((m_ControlPC != 0) && // the dispatch in ExInfo could have gone unhandled + (m_dwFlags & CollapseFunclets)) + { + CalculateCurrentMethodState(); + ASSERT(IsValid()); + if (GetCodeManager()->IsFunclet(&m_methodInfo)) + { + // We just unwound out of a funclet, now we need to keep unwinding until we find the 'main + // body' associated with this funclet and then unwind out of that. + collapsingTargetFrame = m_FramePointer; + } + else + { + // We found the main body, now unwind out of that and we're done. + + // In the case where the caller *was* the main body, we didn't need to set + // collapsingTargetFrame, so it is zero in that case. + ASSERT(!collapsingTargetFrame || (collapsingTargetFrame == m_FramePointer)); + NextInternal(); + collapsingTargetFrame = 0; + } + } + } + return collapsingTargetFrame; +} + +void StackFrameIterator::UpdateFromExceptionDispatch(PTR_StackFrameIterator pSourceIterator) +{ + PreservedRegPtrs thisFuncletPtrs = this->m_funcletPtrs; + + // Blast over 'this' with everything from the 'source'. + *this = *pSourceIterator; + + // Then, put back the pointers to the funclet's preserved registers (since those are the correct values + // until the funclet completes, at which point the values will be copied back to the ExInfo's REGDISPLAY). + +#ifdef TARGET_ARM + m_RegDisplay.pR4 = thisFuncletPtrs.pR4 ; + m_RegDisplay.pR5 = thisFuncletPtrs.pR5 ; + m_RegDisplay.pR6 = thisFuncletPtrs.pR6 ; + m_RegDisplay.pR7 = thisFuncletPtrs.pR7 ; + m_RegDisplay.pR8 = thisFuncletPtrs.pR8 ; + m_RegDisplay.pR9 = thisFuncletPtrs.pR9 ; + m_RegDisplay.pR10 = thisFuncletPtrs.pR10; + m_RegDisplay.pR11 = thisFuncletPtrs.pR11; +#else + // Save the preserved regs portion of the REGDISPLAY across the unwind through the C# EH dispatch code. + m_RegDisplay.pRbp = thisFuncletPtrs.pRbp; + m_RegDisplay.pRdi = thisFuncletPtrs.pRdi; + m_RegDisplay.pRsi = thisFuncletPtrs.pRsi; + m_RegDisplay.pRbx = thisFuncletPtrs.pRbx; +#ifdef TARGET_AMD64 + m_RegDisplay.pR12 = thisFuncletPtrs.pR12; + m_RegDisplay.pR13 = thisFuncletPtrs.pR13; + m_RegDisplay.pR14 = thisFuncletPtrs.pR14; + m_RegDisplay.pR15 = thisFuncletPtrs.pR15; +#endif // TARGET_AMD64 +#endif // TARGET_ARM +} + + +// The invoke of a funclet is a bit special and requires an assembly thunk, but we don't want to break the +// stackwalk due to this. So this routine will unwind through the assembly thunks used to invoke funclets. +// It's also used to disambiguate exceptionally- and non-exceptionally-invoked funclets. +bool StackFrameIterator::HandleFuncletInvokeThunk() +{ +#if defined(FEATURE_CLR_EH) && !defined(USE_PORTABLE_HELPERS) // @TODO: Currently no funclet invoke defined in a portable way + + ASSERT((m_dwFlags & MethodStateCalculated) == 0); + + if ( +#ifdef TARGET_X86 + !EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallFunclet2) +#else + !EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallCatchFunclet2) && + !EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallFinallyFunclet2) && + !EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallFilterFunclet2) +#endif + ) + { + return false; + } + + PTR_UIntNative SP; + +#ifdef TARGET_X86 + // First, unwind RhpCallFunclet + SP = (PTR_UIntNative)(m_RegDisplay.SP + 0x4); // skip the saved assembly-routine-EBP + m_RegDisplay.SetAddrOfIP(SP); + m_RegDisplay.SetIP(*SP++); + m_RegDisplay.SetSP((UIntNative)dac_cast(SP)); + m_ControlPC = dac_cast(*(m_RegDisplay.pIP)); + + ASSERT( + EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallCatchFunclet2) || + EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallFinallyFunclet2) || + EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallFilterFunclet2) + ); +#endif + +#ifdef TARGET_AMD64 + // Save the preserved regs portion of the REGDISPLAY across the unwind through the C# EH dispatch code. + m_funcletPtrs.pRbp = m_RegDisplay.pRbp; + m_funcletPtrs.pRdi = m_RegDisplay.pRdi; + m_funcletPtrs.pRsi = m_RegDisplay.pRsi; + m_funcletPtrs.pRbx = m_RegDisplay.pRbx; + m_funcletPtrs.pR12 = m_RegDisplay.pR12; + m_funcletPtrs.pR13 = m_RegDisplay.pR13; + m_funcletPtrs.pR14 = m_RegDisplay.pR14; + m_funcletPtrs.pR15 = m_RegDisplay.pR15; + + SP = (PTR_UIntNative)(m_RegDisplay.SP + 0x28); + + m_RegDisplay.pRbp = SP++; + m_RegDisplay.pRdi = SP++; + m_RegDisplay.pRsi = SP++; + m_RegDisplay.pRbx = SP++; + m_RegDisplay.pR12 = SP++; + m_RegDisplay.pR13 = SP++; + m_RegDisplay.pR14 = SP++; + m_RegDisplay.pR15 = SP++; + + // RhpCallCatchFunclet puts a couple of extra things on the stack that aren't put there by the other two + // thunks, but we don't need to know what they are here, so we just skip them. + if (EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallCatchFunclet2)) + SP += 2; +#elif defined(TARGET_X86) + // Save the preserved regs portion of the REGDISPLAY across the unwind through the C# EH dispatch code. + m_funcletPtrs.pRbp = m_RegDisplay.pRbp; + m_funcletPtrs.pRdi = m_RegDisplay.pRdi; + m_funcletPtrs.pRsi = m_RegDisplay.pRsi; + m_funcletPtrs.pRbx = m_RegDisplay.pRbx; + + SP = (PTR_UIntNative)(m_RegDisplay.SP + 0x4); + + m_RegDisplay.pRdi = SP++; + m_RegDisplay.pRsi = SP++; + m_RegDisplay.pRbx = SP++; + m_RegDisplay.pRbp = SP++; + +#elif defined(TARGET_ARM) + // RhpCallCatchFunclet puts a couple of extra things on the stack that aren't put there by the other two + // thunks, but we don't need to know what they are here, so we just skip them. + UIntNative uOffsetToR4 = EQUALS_CODE_ADDRESS(m_ControlPC, RhpCallCatchFunclet2) ? 0xC : 0x4; + + // Save the preserved regs portion of the REGDISPLAY across the unwind through the C# EH dispatch code. + m_funcletPtrs.pR4 = m_RegDisplay.pR4; + m_funcletPtrs.pR5 = m_RegDisplay.pR5; + m_funcletPtrs.pR6 = m_RegDisplay.pR6; + m_funcletPtrs.pR7 = m_RegDisplay.pR7; + m_funcletPtrs.pR8 = m_RegDisplay.pR8; + m_funcletPtrs.pR9 = m_RegDisplay.pR9; + m_funcletPtrs.pR10 = m_RegDisplay.pR10; + m_funcletPtrs.pR11 = m_RegDisplay.pR11; + + SP = (PTR_UIntNative)(m_RegDisplay.SP + uOffsetToR4); + + m_RegDisplay.pR4 = SP++; + m_RegDisplay.pR5 = SP++; + m_RegDisplay.pR6 = SP++; + m_RegDisplay.pR7 = SP++; + m_RegDisplay.pR8 = SP++; + m_RegDisplay.pR9 = SP++; + m_RegDisplay.pR10 = SP++; + m_RegDisplay.pR11 = SP++; +#else + SP = (PTR_UIntNative)(m_RegDisplay.SP); + ASSERT_UNCONDITIONALLY("NYI for this arch"); +#endif + m_RegDisplay.SetAddrOfIP((PTR_PCODE)SP); + m_RegDisplay.SetIP(*SP++); + m_RegDisplay.SetSP((UIntNative)dac_cast(SP)); + m_ControlPC = dac_cast(*(m_RegDisplay.pIP)); + + // We expect to be called by the runtime's C# EH implementation, and since this function's notion of how + // to unwind through the stub is brittle relative to the stub itself, we want to check as soon as we can. + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC) && "unwind from funclet invoke stub failed"); + + return true; + +#else // FEATURE_CLR_EH + + return false; + +#endif // FEATURE_CLR_EH +} + +#ifdef _AMD64_ +#define STACK_ALIGN_SIZE 16 +#elif defined(_ARM_) +#define STACK_ALIGN_SIZE 8 +#elif defined(_X86_) +#define STACK_ALIGN_SIZE 4 +#endif + +#ifdef TARGET_AMD64 +struct CALL_DESCR_CONTEXT +{ + UIntNative Rbp; + UIntNative Rsi; + UIntNative Rbx; + UIntNative IP; +}; +#elif defined(TARGET_ARM) +struct CALL_DESCR_CONTEXT +{ + UIntNative R4; + UIntNative R5; + UIntNative R7; + UIntNative IP; +}; +#elif defined(TARGET_X86) +struct CALL_DESCR_CONTEXT +{ + UIntNative Rbx; + UIntNative Rbp; + UIntNative IP; +}; +#else +#error NYI - For this arch +#endif + +typedef DPTR(CALL_DESCR_CONTEXT) PTR_CALL_DESCR_CONTEXT; + +bool StackFrameIterator::HandleCallDescrThunk() +{ + ASSERT((m_dwFlags & MethodStateCalculated) == 0); + +#if defined(USE_PORTABLE_HELPERS) // Corresponding helper code is only defined in assembly code + return false; +#else // defined(USE_PORTABLE_HELPERS) + if (true +#if defined(FEATURE_DYNAMIC_CODE) + && !EQUALS_CODE_ADDRESS(m_ControlPC, ReturnFromCallDescrThunk) +#endif + ) + { + return false; + } + + UIntNative newSP; +#ifdef TARGET_AMD64 + // RBP points to the SP that we want to capture. (This arrangement allows for + // the arguments from this function to be loaded into memory with an adjustment + // to SP, like an alloca + newSP = *(PTR_UIntNative)m_RegDisplay.pRbp; + + PTR_CALL_DESCR_CONTEXT pContext = (PTR_CALL_DESCR_CONTEXT)newSP; + + m_RegDisplay.pRbp = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, Rbp); + m_RegDisplay.pRsi = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, Rsi); + m_RegDisplay.pRbx = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, Rbx); + + // And adjust SP to be the state that it should be in just after returning from + // the CallDescrFunction + newSP += sizeof(CALL_DESCR_CONTEXT); +#elif defined(TARGET_ARM) + // R7 points to the SP that we want to capture. (This arrangement allows for + // the arguments from this function to be loaded into memory with an adjustment + // to SP, like an alloca + newSP = *(PTR_UIntNative)m_RegDisplay.pR7; + PTR_CALL_DESCR_CONTEXT pContext = (PTR_CALL_DESCR_CONTEXT)newSP; + + m_RegDisplay.pR4 = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, R4); + m_RegDisplay.pR5 = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, R5); + m_RegDisplay.pR7 = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, R7); + + // And adjust SP to be the state that it should be in just after returning from + // the CallDescrFunction + newSP += sizeof(CALL_DESCR_CONTEXT); +#elif defined(TARGET_X86) + // RBP points to the SP that we want to capture. (This arrangement allows for + // the arguments from this function to be loaded into memory with an adjustment + // to SP, like an alloca + newSP = *(PTR_UIntNative)m_RegDisplay.pRbp; + + PTR_CALL_DESCR_CONTEXT pContext = (PTR_CALL_DESCR_CONTEXT)(newSP - offsetof(CALL_DESCR_CONTEXT, Rbp)); + + m_RegDisplay.pRbp = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, Rbp); + m_RegDisplay.pRbx = PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, Rbx); + + // And adjust SP to be the state that it should be in just after returning from + // the CallDescrFunction + newSP += sizeof(CALL_DESCR_CONTEXT) - offsetof(CALL_DESCR_CONTEXT, Rbp); +#else + ASSERT_UNCONDITIONALLY("NYI for this arch"); +#endif + + m_RegDisplay.SetAddrOfIP(PTR_TO_MEMBER(CALL_DESCR_CONTEXT, pContext, IP)); + m_RegDisplay.SetIP(pContext->IP); + m_RegDisplay.SetSP(newSP); + m_ControlPC = dac_cast(pContext->IP); + + // We expect the call site to be in managed code, and since this function's notion of how to unwind + // through the stub is brittle relative to the stub itself, we want to check as soon as we can. + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC) && "unwind from CallDescrThunkStub failed"); + + return true; +#endif // defined(USE_PORTABLE_HELPERS) +} + +bool StackFrameIterator::HandleThrowSiteThunk() +{ + ASSERT((m_dwFlags & MethodStateCalculated) == 0); + +#if defined(FEATURE_CLR_EH) && !defined(USE_PORTABLE_HELPERS) // @TODO: no portable version of throw helpers + if (!EQUALS_CODE_ADDRESS(m_ControlPC, RhpThrowEx2) && + !EQUALS_CODE_ADDRESS(m_ControlPC, RhpThrowHwEx2) && + !EQUALS_CODE_ADDRESS(m_ControlPC, RhpRethrow2)) + { + return false; + } + + const UIntNative STACKSIZEOF_ExInfo = ((sizeof(ExInfo) + (STACK_ALIGN_SIZE-1)) & ~(STACK_ALIGN_SIZE-1)); +#ifdef TARGET_AMD64 + const UIntNative SIZEOF_OutgoingScratch = 0x20; +#else + const UIntNative SIZEOF_OutgoingScratch = 0; +#endif + + PTR_PAL_LIMITED_CONTEXT pContext = (PTR_PAL_LIMITED_CONTEXT) + (m_RegDisplay.SP + SIZEOF_OutgoingScratch + STACKSIZEOF_ExInfo); + +#ifdef TARGET_AMD64 + m_RegDisplay.pRbp = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rbp); + m_RegDisplay.pRdi = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rdi); + m_RegDisplay.pRsi = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rsi); + m_RegDisplay.pRbx = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rbx); + m_RegDisplay.pR12 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R12); + m_RegDisplay.pR13 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R13); + m_RegDisplay.pR14 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R14); + m_RegDisplay.pR15 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R15); +#elif defined(TARGET_ARM) + m_RegDisplay.pR4 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R4); + m_RegDisplay.pR5 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R5); + m_RegDisplay.pR6 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R6); + m_RegDisplay.pR7 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R7); + m_RegDisplay.pR8 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R8); + m_RegDisplay.pR9 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R9); + m_RegDisplay.pR10 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R10); + m_RegDisplay.pR11 = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, R11); +#elif defined(TARGET_X86) + m_RegDisplay.pRbp = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rbp); + m_RegDisplay.pRdi = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rdi); + m_RegDisplay.pRsi = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rsi); + m_RegDisplay.pRbx = PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, Rbx); +#else + ASSERT_UNCONDITIONALLY("NYI for this arch"); +#endif + + m_RegDisplay.SetAddrOfIP(PTR_TO_MEMBER(PAL_LIMITED_CONTEXT, pContext, IP)); + m_RegDisplay.SetIP(pContext->IP); + m_RegDisplay.SetSP(pContext->GetSp()); + m_ControlPC = dac_cast(pContext->IP); + + // We expect the throw site to be in managed code, and since this function's notion of how to unwind + // through the stub is brittle relative to the stub itself, we want to check as soon as we can. + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC) && "unwind from throw site stub failed"); + + return true; + +#else // FEATURE_CLR_EH + + return false; + +#endif // FEATURE_CLR_EH +} + +// If our control PC indicates that we're in one of the thunks we use to make managed callouts from the +// runtime we need to adjust the frame state to that of the managed method that previously called into the +// runtime (i.e. skip the intervening unmanaged frames). Returns true if such a sequence of unmanaged frames +// was skipped. + +bool StackFrameIterator::HandleManagedCalloutThunk() +{ + return HandleManagedCalloutThunk(m_ControlPC, m_RegDisplay.GetFP()); +} + + +bool StackFrameIterator::HandleManagedCalloutThunk(PTR_VOID controlPC, UIntNative framePointer) +{ +#if !defined(USE_PORTABLE_HELPERS) // @TODO: no portable version of managed callout defined + if (EQUALS_CODE_ADDRESS(controlPC,ReturnFromManagedCallout2) + +#if defined(FEATURE_DYNAMIC_CODE) + || EQUALS_CODE_ADDRESS(controlPC, ReturnFromUniversalTransition) +#endif + + ) + { + // We're in a special thunk we use to call into managed code from unmanaged code in the runtime. This + // thunk sets up an FP frame with a pointer to a PInvokeTransitionFrame erected by the managed method + // which called into the runtime in the first place (actually a stub called by that managed method). + // Thus we can unwind from one managed method to the previous one, skipping all the unmanaged frames + // in the middle. + // + // On all architectures this transition frame pointer is pushed at a well-known offset from FP. + PTR_VOID pEntryToRuntimeFrame = *(PTR_PTR_VOID)(framePointer + + MANAGED_CALLOUT_THUNK_TRANSITION_FRAME_POINTER_OFFSET); + InternalInit(m_pThread, GetPInvokeTransitionFrame(pEntryToRuntimeFrame)); + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC)); + + // Additionally the initial managed method (the one that called into the runtime) may have pushed some + // arguments containing GC references on the stack. Since the managed callout initiated by the runtime + // has an unrelated signature, there's nobody reporting any of these references to the GC. To avoid + // having to store signature information for what might be potentially a lot of methods (we use this + // mechanism for certain edge cases in interface invoke) we conservatively report a range of the stack + // that might contain GC references. Such references will be in either the outgoing stack argument + // slots of the calling method or in argument registers spilled to the stack in the prolog of the stub + // they use to call into the runtime. + // + // The lower bound of this range we define as the transition frame itself. We just computed this + // address and it's guaranteed to be lower than (but quite close to) that of any spilled argument + // register (see comments in the various versions of RhpInterfaceDispatchSlow). The upper bound we + // can't quite compute just yet. Because the managed method may not have an FP frame it's difficult to + // put a bound on the location of its outgoing argument area. Instead we'll wait until the next frame + // and use the caller's SP at the point of the call into this method. + ASSERT(m_pConservativeStackRangeLowerBound == NULL); + ASSERT(m_pConservativeStackRangeUpperBound == NULL); + m_pConservativeStackRangeLowerBound = (PTR_RtuObjectRef)pEntryToRuntimeFrame; + + return true; + } +#if defined(FEATURE_DYNAMIC_CODE) + else if (EQUALS_CODE_ADDRESS(controlPC, ReturnFromCallDescrThunk)) + { + HandleCallDescrThunk(); + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC)); + + // RhCallDescrWorker is called from library code (called from RuntimeAugments.CallDescrWorker), not user code + // It does not need conservative reporting. + // CallDescrWorker takes a fixed set of simple and known arguments (not arbitrary, like the arguments + // to the universal thunk) and, therefore, does not need conservative scanning + + ASSERT(m_pConservativeStackRangeLowerBound == NULL); + ASSERT(m_pConservativeStackRangeUpperBound == NULL); + + return true; + } +#endif +#endif // !defined(USE_PORTABLE_HELPERS) + + return false; +} + +bool StackFrameIterator::IsValid() +{ + return (m_ControlPC != 0); +} + +#ifdef DACCESS_COMPILE +#define FAILFAST_OR_DAC_FAIL(x) if(!(x)) { DacError(E_FAIL); } +#else +#define FAILFAST_OR_DAC_FAIL(x) if(!(x)) { ASSERT_UNCONDITIONALLY(#x); RhFailFast(); } +#endif + +void StackFrameIterator::Next() +{ + NextInternal(); + STRESS_LOG1(LF_STACKWALK, LL_INFO10000, " %p\n", m_ControlPC); +} + +void StackFrameIterator::NextInternal() +{ + PTR_VOID collapsingTargetFrame = NULL; +KeepUnwinding: + m_dwFlags &= ~(ExCollide|MethodStateCalculated|UnwoundReversePInvoke); + ASSERT(IsValid()); + + m_pHijackedReturnValue = NULL; + m_HijackedReturnValueKind = GCRK_Unknown; + +#ifdef _DEBUG + m_ControlPC = dac_cast((void*)666); +#endif // _DEBUG + + bool fJustComputedConservativeLowerStackBound = false; + + // If we published a stack range to report to the GC conservatively in the last frame enumeration clear it + // now to make way for building another one if required. + if ((m_pConservativeStackRangeLowerBound != NULL) && (m_pConservativeStackRangeUpperBound != NULL)) + { + m_pConservativeStackRangeLowerBound = NULL; + m_pConservativeStackRangeUpperBound = NULL; + } + +#if defined(_DEBUG) && !defined(DACCESS_COMPILE) + UIntNative DEBUG_preUnwindSP = m_RegDisplay.GetSP(); +#endif + + PTR_VOID pPreviousTransitionFrame; + FAILFAST_OR_DAC_FAIL(GetCodeManager()->UnwindStackFrame(&m_methodInfo, m_codeOffset, &m_RegDisplay, &pPreviousTransitionFrame)); + + if (pPreviousTransitionFrame != NULL) + { + if (pPreviousTransitionFrame == TOP_OF_STACK_MARKER) + { + m_ControlPC = 0; + } + else + { + InternalInit(m_pThread, GetPInvokeTransitionFrame(pPreviousTransitionFrame)); + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC)); + } + m_dwFlags |= UnwoundReversePInvoke; + } + else + { + // if the thread is safe to walk, it better not have a hijack in place. + ASSERT((ThreadStore::GetCurrentThread() == m_pThread) || !m_pThread->DangerousCrossThreadIsHijacked()); + + m_ControlPC = dac_cast(*(m_RegDisplay.GetAddrOfIP())); + + // + // BEWARE: these side-effect the current m_RegDisplay and m_ControlPC + // + HandleCallDescrThunk(); + bool atThrowSiteThunk = HandleThrowSiteThunk(); + bool isExceptionallyInvokedFunclet = HandleFuncletInvokeThunk(); + ASSERT(!isExceptionallyInvokedFunclet || GetCodeManager()->IsFunclet(&m_methodInfo)); + + UIntNative postUnwindSP = m_RegDisplay.SP; + + bool exCollide = (m_dwFlags & CollapseFunclets) + ? m_pNextExInfo && (postUnwindSP > ((UIntNative)dac_cast(m_pNextExInfo))) + : isExceptionallyInvokedFunclet; + + // If our control PC indicates that we're in one of the thunks we use to make managed callouts from + // the runtime we need to adjust the frame state to that of the managed method that previously called + // into the runtime (i.e. skip the intervening unmanaged frames). + if (HandleManagedCalloutThunk()) + { + // Set this flag so we don't immediately try to compute the upper bound from this frame in the + // code below. + fJustComputedConservativeLowerStackBound = true; + } + else if (exCollide) + { + // OK, so we just hit (collided with) an exception throw point. We continue by consulting the + // ExInfo. + + // Double-check that we collide only at boundaries where we would have walked off into unmanaged + // code frames. In the GC stackwalk, this means walking all the way off the end of the managed + // exception dispatch code to the throw site. In the EH stackwalk, this means hitting the special + // funclet invoke ASM thunks. + ASSERT(atThrowSiteThunk || isExceptionallyInvokedFunclet); + atThrowSiteThunk; // reference the variable so that retail builds don't see warnings-as-errors. + + // Double-check that when we are 'collapsing' funclets, we always see the same frame pointer. If + // we don't, then we will be missing frames we should be reporting. + ASSERT(!collapsingTargetFrame || collapsingTargetFrame == m_FramePointer); + + // Double-check that the ExInfo that is being consulted is at or below the 'current' stack pointer + ASSERT(DEBUG_preUnwindSP <= (UIntNative)m_pNextExInfo); + + collapsingTargetFrame = HandleExCollide(m_pNextExInfo, collapsingTargetFrame); + if (collapsingTargetFrame != 0) + { + STRESS_LOG1(LF_STACKWALK, LL_INFO10000, "[ KeepUnwinding, target FP = %p ]\n", collapsingTargetFrame); + goto KeepUnwinding; + } + + m_dwFlags |= ExCollide; + } + else + { + ASSERT(m_pInstance->FindCodeManagerByAddress(m_ControlPC)); + } + + if (m_dwFlags & ApplyReturnAddressAdjustment) + m_ControlPC = AdjustReturnAddressBackward(m_ControlPC); + } + + if ((m_pConservativeStackRangeLowerBound != NULL) && !fJustComputedConservativeLowerStackBound) + { + // See comment above where we set m_pConservativeStackRangeLowerBound. In the previous frame + // we started computing a stack range to report to the GC conservatively. Now we've unwound we + // can use the current value of SP as the upper bound. Setting this value will cause + // HasStackRangeToReportConservatively() to return true, which will cause our caller to call + // GetStackRangeToReportConservatively() to retrieve the range values. + // + // The only case where we can't do this is when we fell off the end of the stack (m_ControlPC == 0). + // This happens only after a reverse p/invoke method (since that's the only way we could have gotten + // into managed code to begin with). Luckily those cases require an FP frame so we can compute the + // upper bound from that. The odd case here is ARM where the FP register can end up pointing into the + // middle of the outgoing argument area of the frame. In this case we'll use the OS frame pointer + // (r11) which acts very much like ebp/rbp on the other architectures. + ASSERT(m_pConservativeStackRangeUpperBound == NULL); + + if (m_ControlPC != 0) + { + m_pConservativeStackRangeUpperBound = (PTR_RtuObjectRef)m_RegDisplay.GetSP(); + } + else + { +#ifdef TARGET_ARM + m_pConservativeStackRangeUpperBound = (PTR_RtuObjectRef)*m_RegDisplay.pR11; +#else + m_pConservativeStackRangeUpperBound = (PTR_RtuObjectRef)m_RegDisplay.GetFP(); +#endif + } + } +} + +REGDISPLAY * StackFrameIterator::GetRegisterSet() +{ + ASSERT(IsValid()); + return &m_RegDisplay; +} + +UInt32 StackFrameIterator::GetCodeOffset() +{ + ASSERT(IsValid()); + return m_codeOffset; +} + +ICodeManager * StackFrameIterator::GetCodeManager() +{ + ASSERT(IsValid()); + return m_pCodeManager; +} + +MethodInfo * StackFrameIterator::GetMethodInfo() +{ + ASSERT(IsValid()); + return &m_methodInfo; +} + +#ifdef DACCESS_COMPILE +#define FAILFAST_OR_DAC_RETURN_FALSE(x) if(!(x)) return false; +#else +#define FAILFAST_OR_DAC_RETURN_FALSE(x) if(!(x)) { ASSERT_UNCONDITIONALLY(#x); RhFailFast(); } +#endif + +void StackFrameIterator::CalculateCurrentMethodState() +{ + if (m_dwFlags & MethodStateCalculated) + return; + + // Assume that the caller is likely to be in the same module + if (m_pCodeManager == NULL || !m_pCodeManager->FindMethodInfo(m_ControlPC, &m_methodInfo, &m_codeOffset)) + { + m_pCodeManager = m_pInstance->FindCodeManagerByAddress(m_ControlPC); + FAILFAST_OR_DAC_FAIL(m_pCodeManager); + + FAILFAST_OR_DAC_FAIL(m_pCodeManager->FindMethodInfo(m_ControlPC, &m_methodInfo, &m_codeOffset)); + } + + m_FramePointer = GetCodeManager()->GetFramePointer(&m_methodInfo, &m_RegDisplay); + + m_dwFlags |= MethodStateCalculated; +} + +bool StackFrameIterator::GetHijackedReturnValueLocation(PTR_RtuObjectRef * pLocation, GCRefKind * pKind) +{ + if (GCRK_Unknown == m_HijackedReturnValueKind) + return false; + + ASSERT((GCRK_Object == m_HijackedReturnValueKind) || (GCRK_Byref == m_HijackedReturnValueKind)); + + *pLocation = m_pHijackedReturnValue; + *pKind = m_HijackedReturnValueKind; + return true; +} + +bool StackFrameIterator::IsValidReturnAddress(PTR_VOID pvAddress) +{ +#if !defined(USE_PORTABLE_HELPERS) // @TODO: no portable version of these helpers defined + // These are return addresses into functions that call into managed (non-funclet) code, so we might see + // them as hijacked return addresses. + + if (EQUALS_CODE_ADDRESS(pvAddress, ReturnFromManagedCallout2)) + return true; + +#if defined(FEATURE_DYNAMIC_CODE) + if (EQUALS_CODE_ADDRESS(pvAddress, ReturnFromUniversalTransition) || + EQUALS_CODE_ADDRESS(pvAddress, ReturnFromCallDescrThunk)) + { + return true; + } +#endif + +#ifdef FEATURE_CLR_EH + if (EQUALS_CODE_ADDRESS(pvAddress, RhpThrowEx2) || + EQUALS_CODE_ADDRESS(pvAddress, RhpThrowHwEx2) || + EQUALS_CODE_ADDRESS(pvAddress, RhpRethrow2)) + { + return true; + } +#endif // FEATURE_CLR_EH +#endif // !defined(USE_PORTABLE_HELPERS) + + return (NULL != GetRuntimeInstance()->FindCodeManagerByAddress(pvAddress)); +} + +// Support for conservatively reporting GC references in a stack range. This is used when managed methods with +// an unknown signature potentially including GC references call into the runtime and we need to let a GC +// proceed (typically because we call out into managed code again). Instead of storing signature metadata for +// every possible managed method that might make such a call we identify a small range of the stack that might +// contain outgoing arguments. We then report every pointer that looks like it might refer to the GC heap as a +// fixed interior reference. +// +// We discover the lower and upper bounds of this region over the processing of two frames: the lower bound +// first as we discover the transition frame of the method that entered the runtime (typically as a result or +// enumerating from the managed method that the runtime subsequently called out to) and the upper bound as we +// unwind that method back to its caller. We could do it in one frame if we could guarantee that the call into +// the runtime originated from a managed method with a frame pointer, but we can't make that guarantee (the +// current usage of this mechanism involves methods that simply make an interface call, on the slow path where +// we might have to make a managed callout on the ICastable interface). Thus we need to wait for one more +// unwind to use the caller's SP as a conservative estimate of the upper bound. + +bool StackFrameIterator::HasStackRangeToReportConservatively() +{ + // When there's no range to report both the lower and upper bounds will be NULL. When we start to build + // the range the lower bound will become non-NULL first, followed by the upper bound on the next frame, at + // which point we have a range to report. + return m_pConservativeStackRangeUpperBound != NULL; +} + +void StackFrameIterator::GetStackRangeToReportConservatively(PTR_RtuObjectRef * ppLowerBound, PTR_RtuObjectRef * ppUpperBound) +{ + ASSERT(HasStackRangeToReportConservatively()); + *ppLowerBound = m_pConservativeStackRangeLowerBound; + *ppUpperBound = m_pConservativeStackRangeUpperBound; +} + +// helpers to ApplyReturnAddressAdjustment +// The adjustment is made by EH to ensure that the ControlPC of a callsite stays within the containing try region. +// We adjust by the minimum instruction size on the target-architecture (1-byte on x86 and AMD64, 2-bytes on ARM) +PTR_VOID StackFrameIterator::AdjustReturnAddressForward(PTR_VOID controlPC) +{ +#ifdef TARGET_ARM + return (PTR_VOID)(((PTR_UInt8)controlPC) + 2); +#else + return (PTR_VOID)(((PTR_UInt8)controlPC) + 1); +#endif +} +PTR_VOID StackFrameIterator::AdjustReturnAddressBackward(PTR_VOID controlPC) +{ +#ifdef TARGET_ARM + return (PTR_VOID)(((PTR_UInt8)controlPC) - 2); +#else + return (PTR_VOID)(((PTR_UInt8)controlPC) - 1); +#endif +} + +#ifndef DACCESS_COMPILE + +COOP_PINVOKE_HELPER(Boolean, RhpSfiInit, (StackFrameIterator* pThis, PAL_LIMITED_CONTEXT* pStackwalkCtx)) +{ + Thread * pCurThread = ThreadStore::GetCurrentThread(); + + // The stackwalker is intolerant to hijacked threads, as it is largely expecting to be called from C++ + // where the hijack state of the thread is invariant. Because we've exposed the iterator out to C#, we + // need to unhijack every time we callback into C++ because the thread could have been hijacked during our + // time exectuing C#. + pCurThread->Unhijack(); + + // Passing NULL is a special-case to request a standard managed stack trace for the current thread. + if (pStackwalkCtx == NULL) + pThis->InternalInitForStackTrace(); + else + pThis->InternalInitForEH(pCurThread, pStackwalkCtx); + + bool isValid = pThis->IsValid(); + if (isValid) + pThis->CalculateCurrentMethodState(); + return isValid ? Boolean_true : Boolean_false; +} + +COOP_PINVOKE_HELPER(Boolean, RhpSfiNext, (StackFrameIterator* pThis, UInt32* puExCollideClauseIdx, Boolean* pfUnwoundReversePInvoke)) +{ + // The stackwalker is intolerant to hijacked threads, as it is largely expecting to be called from C++ + // where the hijack state of the thread is invariant. Because we've exposed the iterator out to C#, we + // need to unhijack every time we callback into C++ because the thread could have been hijacked during our + // time exectuing C#. + ThreadStore::GetCurrentThread()->Unhijack(); + + const UInt32 MaxTryRegionIdx = 0xFFFFFFFF; + + ExInfo * pCurExInfo = pThis->m_pNextExInfo; + pThis->Next(); + bool isValid = pThis->IsValid(); + if (isValid) + pThis->CalculateCurrentMethodState(); + + if (pThis->m_dwFlags & StackFrameIterator::ExCollide) + { + ASSERT(pCurExInfo->m_idxCurClause != MaxTryRegionIdx); + *puExCollideClauseIdx = pCurExInfo->m_idxCurClause; + pCurExInfo->m_kind = (ExKind)(pCurExInfo->m_kind | EK_SuperscededFlag); + } + else + { + *puExCollideClauseIdx = MaxTryRegionIdx; + } + + *pfUnwoundReversePInvoke = (pThis->m_dwFlags & StackFrameIterator::UnwoundReversePInvoke) + ? Boolean_true + : Boolean_false; + return isValid; +} + +#endif // !DACCESS_COMPILE diff --git a/src/Native/Runtime/StackFrameIterator.h b/src/Native/Runtime/StackFrameIterator.h new file mode 100644 index 00000000000..b0efcae133f --- /dev/null +++ b/src/Native/Runtime/StackFrameIterator.h @@ -0,0 +1,177 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "ICodeManager.h" + +struct ExInfo; +typedef DPTR(ExInfo) PTR_ExInfo; +enum ExKind : UInt8 +{ + EK_HardwareFault = 2, + EK_SuperscededFlag = 8, +}; + +struct EHEnum +{ + ICodeManager * m_pCodeManager; + EHEnumState m_state; +}; + +EXTERN_C Boolean FASTCALL RhpSfiInit(StackFrameIterator* pThis, PAL_LIMITED_CONTEXT* pStackwalkCtx); +EXTERN_C Boolean FASTCALL RhpSfiNext(StackFrameIterator* pThis, UInt32* puExCollideClauseIdx, Boolean* pfUnwoundReversePInvoke); + +struct PInvokeTransitionFrame; +typedef DPTR(PInvokeTransitionFrame) PTR_PInvokeTransitionFrame; +typedef DPTR(PAL_LIMITED_CONTEXT) PTR_PAL_LIMITED_CONTEXT; + +class StackFrameIterator +{ + friend class AsmOffsets; + friend Boolean FASTCALL RhpSfiInit(StackFrameIterator* pThis, PAL_LIMITED_CONTEXT* pStackwalkCtx); + friend Boolean FASTCALL RhpSfiNext(StackFrameIterator* pThis, UInt32* puExCollideClauseIdx, Boolean* pfUnwoundReversePInvoke); + +public: + StackFrameIterator() {} + StackFrameIterator(Thread * pThreadToWalk, PTR_VOID pInitialTransitionFrame); + StackFrameIterator(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx); + + + bool IsValid(); + void CalculateCurrentMethodState(); + void Next(); + UInt32 GetCodeOffset(); + REGDISPLAY * GetRegisterSet(); + ICodeManager * GetCodeManager(); + MethodInfo * GetMethodInfo(); + bool GetHijackedReturnValueLocation(PTR_RtuObjectRef * pLocation, GCRefKind * pKind); + + static bool IsValidReturnAddress(PTR_VOID pvAddress); + + // Support for conservatively reporting GC references in a stack range. This is used when managed methods + // with an unknown signature potentially including GC references call into the runtime and we need to let + // a GC proceed (typically because we call out into managed code again). Instead of storing signature + // metadata for every possible managed method that might make such a call we identify a small range of the + // stack that might contain outgoing arguments. We then report every pointer that looks like it might + // refer to the GC heap as a fixed interior reference. + // + // We discover the lower and upper bounds of this region over the processing of two frames: the lower + // bound first as we discover the transition frame of the method that entered the runtime (typically as a + // result or enumerating from the managed method that the runtime subsequently called out to) and the + // upper bound as we unwind that method back to its caller. We could do it in one frame if we could + // guarantee that the call into the runtime originated from a managed method with a frame pointer, but we + // can't make that guarantee (the current usage of this mechanism involves methods that simply make an + // interface call, on the slow path where we might have to make a managed callout on the ICastable + // interface). Thus we need to wait for one more unwind to use the caller's SP as a conservative estimate + // of the upper bound. + bool HasStackRangeToReportConservatively(); + void GetStackRangeToReportConservatively(PTR_RtuObjectRef * ppLowerBound, PTR_RtuObjectRef * ppUpperBound); + +private: + // If our control PC indicates that we're in one of the thunks we use to make managed callouts from the + // runtime we need to adjust the frame state to that of the managed method that previously called into the + // runtime (i.e. skip the intervening unmanaged frames). + bool HandleManagedCalloutThunk(); + bool HandleManagedCalloutThunk(PTR_VOID controlPC, UIntNative framePointer); + + // The invoke of a funclet is a bit special and requires an assembly thunk, but we don't want to break the + // stackwalk due to this. So this routine will unwind through the assembly thunks used to invoke funclets. + // It's also used to disambiguate exceptionally- and non-exceptionally-invoked funclets. + bool HandleFuncletInvokeThunk(); + bool HandleThrowSiteThunk(); + + // If our control PC indicates that we're in the call descr thunk that we use to call an arbitrary managed + // function with an arbitrary signature from a normal managed function handle the stack walk specially. + bool HandleCallDescrThunk(); + + void InternalInit(Thread * pThreadToWalk, PTR_PInvokeTransitionFrame pFrame); // GC stackwalk + void InternalInit(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx, UInt32 dwFlags); // EH and hijack stackwalk, and collided unwind + void InternalInitForEH(Thread * pThreadToWalk, PAL_LIMITED_CONTEXT * pCtx); // EH stackwalk + void InternalInitForStackTrace(); // Environment.StackTrace + + PTR_VOID HandleExCollide(PTR_ExInfo pExInfo, PTR_VOID collapsingTargetFrame); + void NextInternal(); + + // This will walk m_pNextExInfo from its current value until it finds the next ExInfo at a higher address + // than the SP reference value passed in. This is useful when 'restarting' the stackwalk from a + // particular PInvokeTransitionFrame or after we have a 'collided unwind' that may skip over ExInfos. + void ResetNextExInfoForSP(UIntNative SP); + + void UpdateStateForRemappedGCSafePoint(UInt32 funcletStartOffset); + void RemapHardwareFaultToGCSafePoint(); + + void UpdateFromExceptionDispatch(PTR_StackFrameIterator pSourceIterator); + + // helpers to ApplyReturnAddressAdjustment + PTR_VOID AdjustReturnAddressForward(PTR_VOID controlPC); + PTR_VOID AdjustReturnAddressBackward(PTR_VOID controlPC); + + enum Flags + { + // If this flag is set, each unwind will apply a -1 to the ControlPC. This is used by EH to ensure + // that the ControlPC of a callsite stays within the containing try region. + ApplyReturnAddressAdjustment = 1, + + // Used by the GC stackwalk, this flag will ensure that multiple funclet frames for a given method + // activation will be given only one callback. The one callback is given for the most nested physical + // stack frame of a given activation of a method. (i.e. the leafmost funclet) + CollapseFunclets = 2, + + // This is a state returned by Next() which indicates that we just crossed an ExInfo in our unwind. + ExCollide = 4, + + // If a hardware fault frame is encountered, report its control PC at the binder-inserted GC safe + // point immediately after the prolog of the most nested enclosing try-region's handler. + RemapHardwareFaultsToSafePoint = 8, + + MethodStateCalculated = 0x10, + + // This is a state returned by Next() which indicates that we just unwound a reverse pinvoke method + UnwoundReversePInvoke = 0x20, + }; + + struct PreservedRegPtrs + { +#ifdef TARGET_ARM + PTR_UIntNative pR4; + PTR_UIntNative pR5; + PTR_UIntNative pR6; + PTR_UIntNative pR7; + PTR_UIntNative pR8; + PTR_UIntNative pR9; + PTR_UIntNative pR10; + PTR_UIntNative pR11; +#else + PTR_UIntNative pRbp; + PTR_UIntNative pRdi; + PTR_UIntNative pRsi; + PTR_UIntNative pRbx; +#ifdef TARGET_AMD64 + PTR_UIntNative pR12; + PTR_UIntNative pR13; + PTR_UIntNative pR14; + PTR_UIntNative pR15; +#endif // _AMD64_ +#endif // _ARM_ + }; + +protected: + Thread * m_pThread; + RuntimeInstance * m_pInstance; + PTR_VOID m_FramePointer; + PTR_VOID m_ControlPC; + REGDISPLAY m_RegDisplay; + ICodeManager * m_pCodeManager; + MethodInfo m_methodInfo; + UInt32 m_codeOffset; + PTR_RtuObjectRef m_pHijackedReturnValue; + GCRefKind m_HijackedReturnValueKind; + PTR_RtuObjectRef m_pConservativeStackRangeLowerBound; + PTR_RtuObjectRef m_pConservativeStackRangeUpperBound; + UInt32 m_dwFlags; + PTR_ExInfo m_pNextExInfo; + PreservedRegPtrs m_funcletPtrs; // @TODO: Placing the 'scratch space' in the StackFrameIterator is not + // preferred because not all StackFrameIterators require this storage + // space. However, the implementation simpler by doing it this way. +}; + diff --git a/src/Native/Runtime/SyncClean.cpp b/src/Native/Runtime/SyncClean.cpp new file mode 100644 index 00000000000..785a5f8ba13 --- /dev/null +++ b/src/Native/Runtime/SyncClean.cpp @@ -0,0 +1,37 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "daccess.h" +#include "forward_declarations.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "holder.h" +#include "spinlock.h" +#include "rhbinder.h" +#ifdef FEATURE_VSD +#include "virtualcallstub.h" +#endif // FEATURE_VSD +#include "cachedinterfacedispatch.h" + +#include "syncclean.hpp" + +void SyncClean::Terminate() +{ + CleanUp(); +} + +void SyncClean::CleanUp () +{ +#ifdef FEATURE_VSD + // Give others we want to reclaim during the GC sync point a chance to do it + VirtualCallStubManager::ReclaimAll(); +#elif defined(FEATURE_CACHED_INTERFACE_DISPATCH) + // Update any interface dispatch caches that were unsafe to modify outside of this GC. + ReclaimUnusedInterfaceDispatchCaches(); +#endif +} diff --git a/src/Native/Runtime/SyncClean.hpp b/src/Native/Runtime/SyncClean.hpp new file mode 100644 index 00000000000..611d61aa510 --- /dev/null +++ b/src/Native/Runtime/SyncClean.hpp @@ -0,0 +1,19 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef _SYNCCLEAN_HPP_ +#define _SYNCCLEAN_HPP_ + +// We keep a list of memory blocks to be freed at the end of GC, but before we resume EE. +// To make this work, we need to make sure that these data are accessed in cooperative GC +// mode. + +class SyncClean { +public: + static void Terminate (); + static void CleanUp (); +}; + +#endif diff --git a/src/Native/Runtime/TargetPtrs.h b/src/Native/Runtime/TargetPtrs.h new file mode 100644 index 00000000000..467934e882b --- /dev/null +++ b/src/Native/Runtime/TargetPtrs.h @@ -0,0 +1,128 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifndef _TARGETPTRS_H_ +#define _TARGETPTRS_H_ + + +#if defined(BINDER) + +#ifdef TARGET_X64 +typedef UInt64 UIntTarget; +#elif defined(TARGET_X86) +typedef UInt32 UIntTarget; +#elif defined(TARGET_THUMB2) +typedef UInt32 UIntTarget; +#else +#error unexpected target architecture +#endif + +// +// Primitive pointer wrapper class very much like __DPtr from daccess.h. +// +template +class TargetPtr +{ + union + { + type* m_ptr; + UIntTarget m_doNotUse; + }; + +public: + TargetPtr< type >(void) { } + + explicit TargetPtr< type >(type * host) { m_ptr = host; } + + TargetPtr& operator=(const TargetPtr& ptr) + { + m_ptr = ptr.GetAddr(); + return *this; + } + TargetPtr& operator=(type* ptr) + { + m_ptr = ptr; + return *this; + } + + + operator type*() const + { + return m_ptr; + } + type* operator->() const + { + return m_ptr; + } + + + type* GetAddr(void) const + { + return m_ptr; + } + type* SetAddr(type* ptr) + { + m_ptr = ptr; + return ptr; + } +}; + +typedef TargetPtr TgtPTR_UInt8; +typedef TargetPtr TgtPTR_UInt32; +typedef TargetPtr TgtPTR_Void; +typedef TargetPtr TgtPTR_EEType; +typedef TargetPtr TgtPTR_GenericInstanceDesc; +typedef TargetPtr TgtPTR_Thread; +typedef TargetPtr TgtPTR_CORINFO_Object; +typedef TargetPtr TgtPTR_StaticGcDesc; + +#elif defined(RHDUMP) +#ifdef TARGET_X64 +typedef UInt64 UIntTarget; +#elif defined(TARGET_X86) +typedef UInt32 UIntTarget; +#elif defined(TARGET_THUMB2) +typedef UInt32 UIntTarget; +#else +#error unexpected target architecture +#endif + +typedef UIntTarget TgtPTR_UInt8; +typedef UIntTarget TgtPTR_UInt32; +typedef UIntTarget TgtPTR_Void; +typedef UIntTarget TgtPTR_EEType; +typedef UIntTarget TgtPTR_GenericInstanceDesc; +typedef UIntTarget TgtPTR_Thread; +typedef UIntTarget TgtPTR_CORINFO_Object; +typedef UIntTarget TgtPTR_StaticGcDesc; + +#else + +typedef DPTR(class EEType) PTR_EEType; +typedef SPTR(struct GenericInstanceDesc) PTR_GenericInstanceDesc; +typedef SPTR(struct StaticGcDesc) PTR_StaticGcDesc; + +#ifdef TARGET_X64 +typedef UInt64 UIntTarget; +#elif defined(TARGET_X86) +typedef UInt32 UIntTarget; +#elif defined(TARGET_THUMB2) +typedef UInt32 UIntTarget; +#else +#error unexpected target architecture +#endif + + +typedef PTR_UInt8 TgtPTR_UInt8; +typedef PTR_UInt32 TgtPTR_UInt32; +typedef void * TgtPTR_Void; +typedef PTR_EEType TgtPTR_EEType; +typedef PTR_GenericInstanceDesc TgtPTR_GenericInstanceDesc; +typedef class Thread * TgtPTR_Thread; +typedef struct CORINFO_Object * TgtPTR_CORINFO_Object; +typedef PTR_StaticGcDesc TgtPTR_StaticGcDesc; + +#endif // BINDER + +#endif // !_TARGETPTRS_H_ diff --git a/src/Native/Runtime/Volatile.h b/src/Native/Runtime/Volatile.h new file mode 100644 index 00000000000..e5c3c5a2468 --- /dev/null +++ b/src/Native/Runtime/Volatile.h @@ -0,0 +1,248 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Provides the Volatile class as a replacement for the C++ volatile keyword where it's important that +// acquire/release semantics are always observed. +// +// In particular on the ARM platform with the C++ compiler options we're using raw volatile will not preserve +// this semantic and additional memory barriers are required. +// + +#if defined(_ARM_) && _ISO_VOLATILE +// ARM has a very weak memory model and very few tools to control that model. We're forced to perform a full +// memory barrier to preserve the volatile semantics. Technically this is only necessary on MP systems but we +// currently don't have a cheap way to determine the number of CPUs from this header file. Revisit this if it +// turns out to be a performance issue for the uni-proc case. +#define VOLATILE_MEMORY_BARRIER() PalMemoryBarrier() +#else +// +// On VC++, reorderings at the compiler and machine level are prevented by the use of the +// "volatile" keyword in VolatileLoad and VolatileStore. This should work on any CPU architecture +// targeted by VC++ with /iso_volatile-. +// +#define VOLATILE_MEMORY_BARRIER() +#endif + +// +// VolatileLoad loads a T from a pointer to T. It is guaranteed that this load will not be optimized +// away by the compiler, and that any operation that occurs after this load, in program order, will +// not be moved before this load. In general it is not guaranteed that the load will be atomic, though +// this is the case for most aligned scalar data types. If you need atomic loads or stores, you need +// to consult the compiler and CPU manuals to find which circumstances allow atomicity. +// +template +inline +T VolatileLoad(T const * pt) +{ +#ifndef DACCESS_COMPILE + T val = *(T volatile const *)pt; + VOLATILE_MEMORY_BARRIER(); +#else + T val = *pt; +#endif + return val; +} + +template class Volatile; + +template +inline +T VolatileLoad(Volatile const * pt) +{ + return pt->Load(); +} + +// +// VolatileStore stores a T into the target of a pointer to T. Is is guaranteed that this store will +// not be optimized away by the compiler, and that any operation that occurs before this store, in program +// order, will not be moved after this store. In general, it is not guaranteed that the store will be +// atomic, though this is the case for most aligned scalar data types. If you need atomic loads or stores, +// you need to consult the compiler and CPU manuals to find which circumstances allow atomicity. +// +template +inline +void VolatileStore(T* pt, T val) +{ +#ifndef DACCESS_COMPILE + VOLATILE_MEMORY_BARRIER(); + *(T volatile *)pt = val; +#else + *pt = val; +#endif +} + +// +// Volatile implements accesses with our volatile semantics over a variable of type T. +// Wherever you would have used a "volatile Foo" or, equivalently, "Foo volatile", use Volatile +// instead. If Foo is a pointer type, use VolatilePtr. +// +// Note that there are still some things that don't work with a Volatile, +// that would have worked with a "volatile T". For example, you can't cast a Volatile to a float. +// You must instead cast to an int, then to a float. Or you can call Load on the Volatile, and +// cast the result to a float. In general, calling Load or Store explicitly will work around +// any problems that can't be solved by operator overloading. +// +template +class Volatile +{ +private: + // + // The data which we are treating as volatile + // + T m_val; + +public: + // + // Default constructor. Results in an unitialized value! + // + inline Volatile() + { + } + + // + // Allow initialization of Volatile from a T + // + inline Volatile(const T& val) + { + ((volatile T &)m_val) = val; + } + + // + // Copy constructor + // + inline Volatile(const Volatile& other) + { + ((volatile T &)m_val) = other.Load(); + } + + // + // Loads the value of the volatile variable. See code:VolatileLoad for the semantics of this operation. + // + inline T Load() const + { + return VolatileLoad(&m_val); + } + + // + // Loads the value of the volatile variable atomically without erecting the memory barrier. + // + inline T LoadWithoutBarrier() const + { + return ((volatile T &)m_val); + } + + // + // Stores a new value to the volatile variable. See code:VolatileStore for the semantics of this + // operation. + // + inline void Store(const T& val) + { + VolatileStore(&m_val, val); + } + + + // + // Stores a new value to the volatile variable atomically without erecting the memory barrier. + // + inline void StoreWithoutBarrier(const T& val) const + { + ((volatile T &)m_val) = val; + } + + + // + // Gets a pointer to the volatile variable. This is dangerous, as it permits the variable to be + // accessed without using Load and Store, but it is necessary for passing Volatile to APIs like + // InterlockedIncrement. + // + inline volatile T* GetPointer() { return (volatile T*)&m_val; } + + + // + // Gets the raw value of the variable. This is dangerous, as it permits the variable to be + // accessed without using Load and Store + // + inline T& RawValue() { return m_val; } + + // + // Allow casts from Volatile to T. Note that this allows implicit casts, so you can + // pass a Volatile directly to a method that expects a T. + // + inline operator T() const + { + return this->Load(); + } + + // + // Assignment from T + // + inline Volatile& operator=(T val) {Store(val); return *this;} + + // + // Get the address of the volatile variable. This is dangerous, as it allows the value of the + // volatile variable to be accessed directly, without going through Load and Store, but it is + // necessary for passing Volatile to APIs like InterlockedIncrement. Note that we are returning + // a pointer to a volatile T here, so we cannot accidentally pass this pointer to an API that + // expects a normal pointer. + // + inline T volatile * operator&() {return this->GetPointer();} + inline T volatile const * operator&() const {return this->GetPointer();} + + // + // Comparison operators + // + template + inline bool operator==(const TOther& other) const {return this->Load() == other;} + + template + inline bool operator!=(const TOther& other) const {return this->Load() != other;} + + // + // Miscellaneous operators. Add more as necessary. + // + inline Volatile& operator+=(T val) {Store(this->Load() + val); return *this;} + inline Volatile& operator-=(T val) {Store(this->Load() - val); return *this;} + inline Volatile& operator|=(T val) {Store(this->Load() | val); return *this;} + inline Volatile& operator&=(T val) {Store(this->Load() & val); return *this;} + inline bool operator!() const { return !this->Load();} + + // + // Prefix increment + // + inline Volatile& operator++() {this->Store(this->Load()+1); return *this;} + + // + // Postfix increment + // + inline T operator++(int) {T val = this->Load(); this->Store(val+1); return val;} + + // + // Prefix decrement + // + inline Volatile& operator--() {this->Store(this->Load()-1); return *this;} + + // + // Postfix decrement + // + inline T operator--(int) {T val = this->Load(); this->Store(val-1); return val;} +}; + +#define RAW_KEYWORD(x) x + +#ifdef DACCESS_COMPILE +// No need to use volatile in DAC builds - DAC is single-threaded and the target +// process is suspended. +#define VOLATILE(T) T +#else + +// Disable use of Volatile for GC/HandleTable code except on platforms where it's absolutely necessary. +#if defined(_MSC_VER) && !defined(_ARM_) +#define VOLATILE(T) T RAW_KEYWORD(volatile) +#else +#define VOLATILE(T) Volatile +#endif + +#endif // DACCESS_COMPILE diff --git a/src/Native/Runtime/WellKnownEntryPoints.h b/src/Native/Runtime/WellKnownEntryPoints.h new file mode 100644 index 00000000000..4e3d190cb4b --- /dev/null +++ b/src/Native/Runtime/WellKnownEntryPoints.h @@ -0,0 +1,32 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Provide support for "well known methods" that are identifiable by tokens from the compiler. +// + +#ifndef _PROJECTNWELLKNOWNMETHODS_H +#define _PROJECTNWELLKNOWNMETHODS_H + +// Keep in sync with src\Nutc\inc\WellKnownEntryPoints.h + +enum WellKnownEntryPoint +{ + WKM_CLASSWITHMISSINGCONSTRUCTOR, // Fallback default constructor for types with no default constructor + WKM_GETTHREADSTATICSFORDYNAMICTYPE, // TypeLoader's helper to get thread static pointer for dynamic type + WKM_ACTIVATORCREATEINSTANCEANY, // Allocates and initializes value types or reference types for universal generic types + WKM_GENERICLOOKUP, // Perform a generic lookup using the type loader + WKM_GENERICLOOKUPANDALLOCOBJECT, // Perform a generic lookup for a method and call it + WKM_GENERICLOOKUPANDCALLCTOR, // Perform a generic lookup for a method and call it + WKM_GENERICLOOKUPANDALLOCARRAY, // Perform a generic lookup and call a method passing the lookup result as an argument + WKM_GENERICLOOKUPANDCAST, // Perform a generic lookup and call a method passing the lookup result as an argument + WKM_GENERICLOOKUPANDCHECKARRAYELEMTYPE, // Perform a generic lookup and call a method passing the lookup result as an argument + WKM_BINDERINTRINSIC_GCSTRESS_FINALIZE, // The GCStress objects Finalize method + WKM_BINDERINTRINSIC_DEBUGBREAK, // Inject a breakpoint instead of actually calling this method + WKM_BINDERINTRINSIC_GETRETURNADDRESS, // Get the return address from the function that called this method + WKM_BINDERINTRINSIC_TAILCALL_RHPTHROWEX,// Tail-call the throw helper. +}; + +#endif diff --git a/src/Native/Runtime/WellKnownMethodList.h b/src/Native/Runtime/WellKnownMethodList.h new file mode 100644 index 00000000000..fa8af70b9ff --- /dev/null +++ b/src/Native/Runtime/WellKnownMethodList.h @@ -0,0 +1,11 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +DEFINE_WELL_KNOWN_METHOD(GetRuntimeException) +DEFINE_WELL_KNOWN_METHOD(FailFast) +DEFINE_WELL_KNOWN_METHOD(UnhandledExceptionHandler) +DEFINE_WELL_KNOWN_METHOD(AppendExceptionStackFrame) +DEFINE_WELL_KNOWN_METHOD(CheckStaticClassConstruction) +DEFINE_WELL_KNOWN_METHOD(InitializeFinalizerThread) diff --git a/src/Native/Runtime/WellKnownMethods.h b/src/Native/Runtime/WellKnownMethods.h new file mode 100644 index 00000000000..2bdd7f603d1 --- /dev/null +++ b/src/Native/Runtime/WellKnownMethods.h @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Provide support for "well known methods". These are methods known to the binder and runtime and identified +// purely by a native callable name. If your module defines a native callable method with one of these names +// then we expect it to conform to the corresponding contract. See WellKnownMethodList.h for a list of these +// names. +// + +#ifndef __WELLKNOWNMETHODS_INCLUDED +#define __WELLKNOWNMETHODS_INCLUDED + +// Define enum constants of the form WKM_ for each well known method. WKM_COUNT is defined as the number +// of methods we currently know about. +#define DEFINE_WELL_KNOWN_METHOD(_name) WKM_##_name, +enum WellKnownMethodIds +{ +#include "WellKnownMethodList.h" + WKM_COUNT +}; +#undef DEFINE_WELL_KNOWN_METHOD + +// Define an array of well known method names which are indexed by the enums defined above. +#define DEFINE_WELL_KNOWN_METHOD(_name) #_name, +extern __declspec(selectany) const char *g_rgWellKnownMethodNames[] = +{ +#include "WellKnownMethodList.h" +}; +#undef DEFINE_WELL_KNOWN_METHOD + +#endif // !__WELLKNOWNMETHODS_INCLUDED diff --git a/src/Native/Runtime/allocheap.cpp b/src/Native/Runtime/allocheap.cpp new file mode 100644 index 00000000000..3f97fe0b31b --- /dev/null +++ b/src/Native/Runtime/allocheap.cpp @@ -0,0 +1,374 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "debugmacrosext.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "slist.h" +#include "holder.h" +#include "crst.h" +#include "range.h" +#ifdef FEATURE_RWX_MEMORY +#include "memaccessmgr.h" +#endif +#include "allocheap.h" + +#include "commonmacros.inl" +#include "slist.inl" + +using namespace rh::util; + +//------------------------------------------------------------------------------------------------- +AllocHeap::AllocHeap() + : m_blockList(), + m_rwProtectType(PAGE_READWRITE), + m_roProtectType(PAGE_READWRITE), +#ifdef FEATURE_RWX_MEMORY + m_pAccessMgr(NULL), + m_hCurPageRW(), +#endif // FEATURE_RWX_MEMORY + m_pNextFree(NULL), + m_pFreeCommitEnd(NULL), + m_pFreeReserveEnd(NULL), + m_pbInitialMem(NULL), + m_fShouldFreeInitialMem(false), + m_lock(CrstAllocHeap) + COMMA_INDEBUG(m_fIsInit(false)) +{ + ASSERT(!_UseAccessManager()); +} + +#ifdef FEATURE_RWX_MEMORY +//------------------------------------------------------------------------------------------------- +AllocHeap::AllocHeap( + UInt32 rwProtectType, + UInt32 roProtectType, + MemAccessMgr* pAccessMgr) + : m_blockList(), + m_rwProtectType(rwProtectType), + m_roProtectType(roProtectType == 0 ? rwProtectType : roProtectType), + m_pAccessMgr(pAccessMgr), + m_hCurPageRW(), + m_pNextFree(NULL), + m_pFreeCommitEnd(NULL), + m_pFreeReserveEnd(NULL), + m_pbInitialMem(NULL), + m_fShouldFreeInitialMem(false), + m_lock(CrstAllocHeap) + COMMA_INDEBUG(m_fIsInit(false)) +{ + ASSERT(!_UseAccessManager() || (m_rwProtectType != m_roProtectType && m_pAccessMgr != NULL)); +} +#endif // FEATURE_RWX_MEMORY + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::Init() +{ + ASSERT(!m_fIsInit); + INDEBUG(m_fIsInit = true;) + + return true; +} + +//------------------------------------------------------------------------------------------------- +// This is for using pre-allocated memory on heap construction. +// Should never use this more than once, and should always follow construction of heap. + +bool AllocHeap::Init( + UInt8 * pbInitialMem, + UIntNative cbInitialMemCommit, + UIntNative cbInitialMemReserve, + bool fShouldFreeInitialMem) +{ + ASSERT(!m_fIsInit); + +#ifdef FEATURE_RWX_MEMORY + // Manage the committed portion of memory + if (_UseAccessManager()) + { + m_pAccessMgr->ManageMemoryRange(MemRange(pbInitialMem, cbInitialMemCommit), true); + } +#endif // FEATURE_RWX_MEMORY + + BlockListElem *pBlock = new BlockListElem(pbInitialMem, cbInitialMemReserve); + if (pBlock == NULL) + return false; + m_blockList.PushHead(pBlock); + + if (!_UpdateMemPtrs(pbInitialMem, + pbInitialMem + cbInitialMemCommit, + pbInitialMem + cbInitialMemReserve)) + { + return false; + } + + m_pbInitialMem = pbInitialMem; + m_fShouldFreeInitialMem = fShouldFreeInitialMem; + + INDEBUG(m_fIsInit = true;) + return true; +} + +//------------------------------------------------------------------------------------------------- +AllocHeap::~AllocHeap() +{ + while (!m_blockList.IsEmpty()) + { + BlockListElem *pCur = m_blockList.PopHead(); + if (pCur->GetStart() != m_pbInitialMem || m_fShouldFreeInitialMem) + PalVirtualFree(pCur->GetStart(), pCur->GetLength(), MEM_RELEASE); + delete pCur; + } +} + +//------------------------------------------------------------------------------------------------- +UInt8 * AllocHeap::_Alloc( + UIntNative cbMem, + UIntNative alignment + WRITE_ACCESS_HOLDER_ARG + ) +{ +#ifndef FEATURE_RWX_MEMORY + const void* pRWAccessHolder = NULL; +#endif // FEATURE_RWX_MEMORY + + ASSERT((alignment & (alignment - 1)) == 0); // Power of 2 only. + ASSERT(alignment <= OS_PAGE_SIZE); // Can't handle this right now. + ASSERT((m_rwProtectType == m_roProtectType) == (pRWAccessHolder == NULL)); + ASSERT(!_UseAccessManager() || pRWAccessHolder != NULL); + + if (_UseAccessManager() && pRWAccessHolder == NULL) + return NULL; + + CrstHolder lock(&m_lock); + + UInt8 * pbMem = _AllocFromCurBlock(cbMem, alignment PASS_WRITE_ACCESS_HOLDER_ARG); + if (pbMem != NULL) + return pbMem; + + // Must allocate new block + if (!_AllocNewBlock(cbMem)) + return NULL; + + pbMem = _AllocFromCurBlock(cbMem, alignment PASS_WRITE_ACCESS_HOLDER_ARG); + ASSERT_MSG(pbMem != NULL, "AllocHeap::Alloc: failed to alloc mem after new block alloc"); + + return pbMem; +} + +//------------------------------------------------------------------------------------------------- +UInt8 * AllocHeap::Alloc( + UIntNative cbMem + WRITE_ACCESS_HOLDER_ARG) +{ + return _Alloc(cbMem, 1 PASS_WRITE_ACCESS_HOLDER_ARG); +} + +//------------------------------------------------------------------------------------------------- +UInt8 * AllocHeap::AllocAligned( + UIntNative cbMem, + UIntNative alignment + WRITE_ACCESS_HOLDER_ARG) +{ + return _Alloc(cbMem, alignment PASS_WRITE_ACCESS_HOLDER_ARG); +} + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::Contains(void* pvMem, UIntNative cbMem) +{ + MemRange range(pvMem, cbMem); + for (BlockList::Iterator it = m_blockList.Begin(); it != m_blockList.End(); ++it) + { + if (it->Contains(range)) + { + return true; + } + } + return false; +} + +#ifdef FEATURE_RWX_MEMORY +//------------------------------------------------------------------------------------------------- +bool AllocHeap::_AcquireWriteAccess( + UInt8* pvMem, + UIntNative cbMem, + WriteAccessHolder* pHolder) +{ + ASSERT(!_UseAccessManager() || m_pAccessMgr != NULL); + + if (_UseAccessManager()) + return m_pAccessMgr->AcquireWriteAccess(MemRange(pvMem, cbMem), m_hCurPageRW, pHolder); + else + return true; +} + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::AcquireWriteAccess( + void* pvMem, + UIntNative cbMem, + WriteAccessHolder* pHolder) +{ + return _AcquireWriteAccess(static_cast(pvMem), cbMem, pHolder); +} +#endif // FEATURE_RWX_MEMORY + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::_UpdateMemPtrs(UInt8* pNextFree, UInt8* pFreeCommitEnd, UInt8* pFreeReserveEnd) +{ + ASSERT(MemRange(pNextFree, pFreeReserveEnd).Contains(MemRange(pNextFree, pFreeCommitEnd))); + ASSERT(ALIGN_DOWN(pFreeCommitEnd, OS_PAGE_SIZE) == pFreeCommitEnd); + ASSERT(ALIGN_DOWN(pFreeReserveEnd, OS_PAGE_SIZE) == pFreeReserveEnd); + +#ifdef FEATURE_RWX_MEMORY + // See if we need to update current allocation holder or protect committed pages. + if (_UseAccessManager()) + { + if (pFreeCommitEnd - pNextFree > 0) + { +#ifndef STRESS_MEMACCESSMGR + // Create or update the alloc cache, used to speed up new allocations. + // If there is available commited memory and either m_pNextFree is + // being updated past a page boundary or the current cache is empty, + // then update the cache. + if (ALIGN_DOWN(m_pNextFree, OS_PAGE_SIZE) != ALIGN_DOWN(pNextFree, OS_PAGE_SIZE) || + m_hCurPageRW.GetRange().GetLength() == 0) + { + // Update current alloc page write access holder. + if (!_AcquireWriteAccess(ALIGN_DOWN(pNextFree, OS_PAGE_SIZE), + OS_PAGE_SIZE, + &m_hCurPageRW)) + { + return false; + } + } +#endif // STRESS_MEMACCESSMGR + + } + else + { // No available committed memory. Release the cache. + m_hCurPageRW.Release(); + } + } +#endif // FEATURE_RWX_MEMORY + + m_pNextFree = pNextFree; + m_pFreeCommitEnd = pFreeCommitEnd; + m_pFreeReserveEnd = pFreeReserveEnd; + return true; +} + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::_UpdateMemPtrs(UInt8* pNextFree, UInt8* pFreeCommitEnd) +{ + return _UpdateMemPtrs(pNextFree, pFreeCommitEnd, m_pFreeReserveEnd); +} + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::_UpdateMemPtrs(UInt8* pNextFree) +{ + return _UpdateMemPtrs(pNextFree, m_pFreeCommitEnd); +} + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::_AllocNewBlock(UIntNative cbMem) +{ + cbMem = ALIGN_UP(max(cbMem, s_minBlockSize), OS_PAGE_SIZE);; + + UInt8 * pbMem = reinterpret_cast + (PalVirtualAlloc(NULL, cbMem, MEM_COMMIT, m_roProtectType)); + + if (pbMem == NULL) + return false; + + BlockListElem *pBlockListElem = new BlockListElem(pbMem, cbMem); + if (pBlockListElem == NULL) + { + PalVirtualFree(pbMem, 0, MEM_RELEASE); + return false; + } + + // Add to the list. While there is no race for writers (we hold the lock) we have the + // possibility of simultaneous readers, and using the interlocked version creates a + // memory barrier to make sure any reader sees a consistent list. + m_blockList.PushHeadInterlocked(pBlockListElem); + + return _UpdateMemPtrs(pbMem, pbMem + cbMem, pbMem + cbMem); +} + +//------------------------------------------------------------------------------------------------- +UInt8 * AllocHeap::_AllocFromCurBlock( + UIntNative cbMem, + UIntNative alignment + WRITE_ACCESS_HOLDER_ARG) +{ + UInt8 * pbMem = NULL; + + cbMem += (UInt8 *)ALIGN_UP(m_pNextFree, alignment) - m_pNextFree; + + if (m_pNextFree + cbMem <= m_pFreeCommitEnd || + _CommitFromCurBlock(cbMem)) + { + ASSERT(cbMem + m_pNextFree <= m_pFreeCommitEnd); +#ifdef FEATURE_RWX_MEMORY + if (pRWAccessHolder != NULL) + { + if (!_AcquireWriteAccess(m_pNextFree, cbMem, pRWAccessHolder)) + return NULL; + } +#endif // FEATURE_RWX_MEMORY + pbMem = ALIGN_UP(m_pNextFree, alignment); + + if (!_UpdateMemPtrs(m_pNextFree + cbMem)) + return NULL; + } + + return pbMem; +} + +//------------------------------------------------------------------------------------------------- +bool AllocHeap::_CommitFromCurBlock(UIntNative cbMem) +{ + ASSERT(m_pFreeCommitEnd < m_pNextFree + cbMem); + + if (m_pNextFree + cbMem <= m_pFreeReserveEnd) + { + UIntNative cbMemToCommit = ALIGN_UP(cbMem, OS_PAGE_SIZE); + +#ifdef FEATURE_RWX_MEMORY + if (_UseAccessManager()) + { + if (!m_pAccessMgr->ManageMemoryRange(MemRange(m_pFreeCommitEnd, cbMemToCommit), false)) + return false; + } + else + { + UInt32 oldProtectType; + if (!PalVirtualProtect(m_pFreeCommitEnd, cbMemToCommit, m_roProtectType, &oldProtectType)) + return false; + } +#endif // FEATURE_RWX_MEMORY + + return _UpdateMemPtrs(m_pNextFree, m_pFreeCommitEnd + cbMemToCommit); + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +void * __cdecl operator new(UIntNative n, AllocHeap * alloc) +{ + return alloc->Alloc(n); +} + +//------------------------------------------------------------------------------------------------- +void * __cdecl operator new[](UIntNative n, AllocHeap * alloc) +{ + return alloc->Alloc(n); +} + diff --git a/src/Native/Runtime/allocheap.h b/src/Native/Runtime/allocheap.h new file mode 100644 index 00000000000..52330f00164 --- /dev/null +++ b/src/Native/Runtime/allocheap.h @@ -0,0 +1,124 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "forward_declarations.h" + +#ifdef FEATURE_RWX_MEMORY +#define WRITE_ACCESS_HOLDER_ARG , rh::util::WriteAccessHolder *pRWAccessHolder +#define WRITE_ACCESS_HOLDER_ARG_NULL_DEFAULT , rh::util::WriteAccessHolder *pRWAccessHolder = NULL +#define PASS_WRITE_ACCESS_HOLDER_ARG , pRWAccessHolder +#else // FEATURE_RWX_MEMORY +#define WRITE_ACCESS_HOLDER_ARG +#define WRITE_ACCESS_HOLDER_ARG_NULL_DEFAULT +#define PASS_WRITE_ACCESS_HOLDER_ARG +#endif // FEATURE_RWX_MEMORY + +class AllocHeap +{ + public: + AllocHeap(); + +#ifdef FEATURE_RWX_MEMORY + // If pAccessMgr is non-NULL, it will be used to manage R/W access to the memory allocated. + AllocHeap(UInt32 rwProtectType = PAGE_READWRITE, + UInt32 roProtectType = 0, // 0 indicates "same as rwProtectType" + rh::util::MemAccessMgr* pAccessMgr = NULL); +#endif // FEATURE_RWX_MEMORY + + bool Init(); + + bool Init(UInt8 * pbInitialMem, + UIntNative cbInitialMemCommit, + UIntNative cbInitialMemReserve, + bool fShouldFreeInitialMem); + + ~AllocHeap(); + + // If AllocHeap was created with a MemAccessMgr, pRWAccessHolder must be non-NULL. + // On return, the holder will permit R/W access to the allocated memory until it + // is destructed. + UInt8 * Alloc(UIntNative cbMem WRITE_ACCESS_HOLDER_ARG_NULL_DEFAULT); + + // If AllocHeap was created with a MemAccessMgr, pRWAccessHolder must be non-NULL. + // On return, the holder will permit R/W access to the allocated memory until it + // is destructed. + UInt8 * AllocAligned(UIntNative cbMem, + UIntNative alignment + WRITE_ACCESS_HOLDER_ARG_NULL_DEFAULT); + + // Returns true if this AllocHeap owns the memory range [pvMem, pvMem+cbMem) + bool Contains(void * pvMem, + UIntNative cbMem); + +#ifdef FEATURE_RWX_MEMORY + // Used with previously-allocated memory for which RW access is needed again. + // Returns true on success. R/W access will be granted until the holder is + // destructed. + bool AcquireWriteAccess(void* pvMem, + UIntNative cbMem, + rh::util::WriteAccessHolder* pHolder); +#endif // FEATURE_RWX_MEMORY + + private: + // Allocation Helpers + UInt8* _Alloc(UIntNative cbMem, UIntNative alignment WRITE_ACCESS_HOLDER_ARG); + bool _AllocNewBlock(UIntNative cbMem); + UInt8* _AllocFromCurBlock(UIntNative cbMem, UIntNative alignment WRITE_ACCESS_HOLDER_ARG); + bool _CommitFromCurBlock(UIntNative cbMem); + + // Access protection helpers +#ifdef FEATURE_RWX_MEMORY + bool _AcquireWriteAccess(UInt8* pvMem, UIntNative cbMem, rh::util::WriteAccessHolder* pHolder); +#endif // FEATURE_RWX_MEMORY + bool _UpdateMemPtrs(UInt8* pNextFree, UInt8* pFreeCommitEnd, UInt8* pFreeReserveEnd); + bool _UpdateMemPtrs(UInt8* pNextFree, UInt8* pFreeCommitEnd); + bool _UpdateMemPtrs(UInt8* pNextFree); + bool _UseAccessManager() { return m_rwProtectType != m_roProtectType; } + + static const UIntNative s_minBlockSize = OS_PAGE_SIZE; + + typedef rh::util::MemRange Block; + typedef DPTR(Block) PTR_Block; + struct BlockListElem : public Block + { + BlockListElem(Block const & block) + : Block(block) + {} + + BlockListElem(UInt8 * pbMem, UIntNative cbMem) + : Block(pbMem, cbMem) + {} + + Block m_block; + PTR_Block m_pNext; + }; + + typedef SList BlockList; + BlockList m_blockList; + + UInt32 m_rwProtectType; // READ/WRITE/EXECUTE/etc + UInt32 m_roProtectType; // What to do with fully allocated and initialized pages. + +#ifdef FEATURE_RWX_MEMORY + rh::util::MemAccessMgr* m_pAccessMgr; + rh::util::WriteAccessHolder m_hCurPageRW; // Used to hold RW access to the current allocation page + // Passed as pHint to MemAccessMgr::AcquireWriteAccess. +#endif // FEATURE_RWX_MEMORY + UInt8 * m_pNextFree; + UInt8 * m_pFreeCommitEnd; + UInt8 * m_pFreeReserveEnd; + + UInt8 * m_pbInitialMem; + bool m_fShouldFreeInitialMem; + + Crst m_lock; + + INDEBUG(bool m_fIsInit;) +}; +typedef DPTR(AllocHeap) PTR_AllocHeap; + +//------------------------------------------------------------------------------------------------- +void * __cdecl operator new(UIntNative n, AllocHeap * alloc); +void * __cdecl operator new[](UIntNative n, AllocHeap * alloc); + diff --git a/src/Native/Runtime/assert.cpp b/src/Native/Runtime/assert.cpp new file mode 100644 index 00000000000..33c627ce917 --- /dev/null +++ b/src/Native/Runtime/assert.cpp @@ -0,0 +1,99 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#include "commontypes.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" + + +#include "RhConfig.h" + +#ifdef _DEBUG + +#define MB_ABORTRETRYIGNORE 0x00000002L +#define IDABORT 3 +#define IDRETRY 4 +#define IDIGNORE 5 + +void Assert(const char * expr, const char * file, UInt32 line_num, const char * message) +{ +#ifndef DACCESS_COMPILE +#ifdef NO_UI_ASSERT + PalDebugBreak(); +#else + if (g_pRhConfig->GetBreakOnAssert()) + { + PalPrintf( + "--------------------------------------------------\n" + "Debug Assertion Violation\n\n" + "%s%s%s" + "Expression: '%s'\n\n" + "File: %s, Line: %u\n" + "--------------------------------------------------\n", + message ? ("Message: ") : (""), + message ? (message) : (""), + message ? ("\n\n") : (""), + expr, file, line_num); + + // Flush standard output before failing fast to make sure the assertion failure message + // is retained when tests are being run with redirected stdout. + PalFlushStdout(); + + // If there's no debugger attached, we just FailFast + if (!PalIsDebuggerPresent()) + PalRaiseFailFastException(NULL, NULL, FAIL_FAST_GENERATE_EXCEPTION_ADDRESS); + + // If there is a debugger attached, we break and then allow continuation. + PalDebugBreak(); + return; + } + + char buffer[4096]; + + PalSprintf(buffer, COUNTOF(buffer), + "--------------------------------------------------\n" + "Debug Assertion Violation\n\n" + "%s%s%s" + "Expression: '%s'\n\n" + "File: %s, Line: %u\n" + "--------------------------------------------------\n" + "Abort: Exit Immediately\n" + "Retry: DebugBreak()\n" + "Ignore: Keep Going\n" + "--------------------------------------------------\n", + message ? ("Message: ") : (""), + message ? (message) : (""), + message ? ("\n\n") : (""), + expr, file, line_num); + + HANDLE hMod = PalLoadLibraryExW(L"user32.dll", NULL, 0); + Int32 (* pfn)(HANDLE, char *, char *, UInt32) = + (Int32 (*)(HANDLE, char *, char *, UInt32))PalGetProcAddress(hMod, "MessageBoxA"); + + Int32 result = pfn(NULL, buffer, "Redhawk Assert", MB_ABORTRETRYIGNORE); + + switch (result) + { + case IDABORT: + PalTerminateProcess(PalGetCurrentProcess(), 666); + break; + case IDRETRY: + PalDebugBreak(); + break; + case IDIGNORE: + break; + } +#endif +#else + UNREFERENCED_PARAMETER(expr); + UNREFERENCED_PARAMETER(file); + UNREFERENCED_PARAMETER(line_num); + UNREFERENCED_PARAMETER(message); +#endif //!DACCESS_COMPILE +} + +#endif // _DEBUG diff --git a/src/Native/Runtime/assert.h b/src/Native/Runtime/assert.h new file mode 100644 index 00000000000..879885e0cb9 --- /dev/null +++ b/src/Native/Runtime/assert.h @@ -0,0 +1,58 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#ifdef _MSC_VER +#define ASSUME(expr) __assume(expr) +#else // _MSC_VER +#define ASSUME(expr) +#endif // _MSC_VER + +#if defined(_DEBUG) && !defined(DACCESS_COMPILE) + +#define ASSERT(expr) \ + { \ + if (!(expr)) { Assert(#expr, __FILE__, __LINE__, NULL); } \ + } \ + +#define ASSERT_MSG(expr, msg) \ + { \ + if (!(expr)) { Assert(#expr, __FILE__, __LINE__, msg); } \ + } \ + +#define VERIFY(expr) ASSERT((expr)) + +#define ASSERT_UNCONDITIONALLY(message) \ + Assert("ASSERT_UNCONDITIONALLY", __FILE__, __LINE__, message); \ + +void Assert(const char * expr, const char * file, UInt32 line_num, const char * message); + +#else + +#define ASSERT(expr) + +#define ASSERT_MSG(expr, msg) + +#define VERIFY(expr) (expr) + +#define ASSERT_UNCONDITIONALLY(message) + +#endif + +#define UNREACHABLE() \ + ASSERT_UNCONDITIONALLY("UNREACHABLE"); \ + ASSUME(0); \ + +#define UNREACHABLE_MSG(message) \ + ASSERT_UNCONDITIONALLY(message); \ + ASSUME(0); \ + +#define FAIL_FAST_GENERATE_EXCEPTION_ADDRESS 0x1 + +#define RhFailFast() RhFailFast2(NULL, NULL) + +#define RhFailFast2(pExRec, pExCtx) \ +{ \ + ASSERT_UNCONDITIONALLY("FailFast"); \ + PalRaiseFailFastException((pExRec), (pExCtx), (pExRec)==NULL ? FAIL_FAST_GENERATE_EXCEPTION_ADDRESS : 0); \ +} diff --git a/src/Native/Runtime/common.h b/src/Native/Runtime/common.h new file mode 100644 index 00000000000..fa25c09c632 --- /dev/null +++ b/src/Native/Runtime/common.h @@ -0,0 +1,16 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// This file is here because we share some common code with the CLR and that platform uses common.h as a +// precompiled header. Due to limitations on precompilation (a precompiled header must be included first +// and must not be preceded by any other preprocessor directive) we cannot conditionally include common.h, +// so the simplest solution is to maintain this empty header under Redhawk. +// + +// +// For our DAC build, we precompile gcrhenv.h because it is extremely large (~3MB of text). For non-DAC +// builds, we do not do this because the majority of the files have more constrained #includes. +// \ No newline at end of file diff --git a/src/Native/Runtime/daccess.h b/src/Native/Runtime/daccess.h new file mode 100644 index 00000000000..bcc8e15d17c --- /dev/null +++ b/src/Native/Runtime/daccess.h @@ -0,0 +1,2397 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// File: daccess.h +// +// Support for external access of runtime data structures. These +// macros and templates hide the details of pointer and data handling +// so that data structures and code can be compiled to work both +// in-process and through a special memory access layer. +// +// This code assumes the existence of two different pieces of code, +// the target, the runtime code that is going to be examined, and +// the host, the code that's doing the examining. Access to the +// target is abstracted so the target may be a live process on the +// same machine, a live process on a different machine, a dump file +// or whatever. No assumptions should be made about accessibility +// of the target. +// +// This code assumes that the data in the target is static. Any +// time the target's data changes the interfaces must be reset so +// that potentially stale data is discarded. +// +// This code is intended for read access and there is no +// way to write data back currently. +// +// DAC-ized code: +// - is read-only (non-invasive). So DACized codepaths can not trigger a GC. +// - has no Thread* object. In reality, DAC-ized codepaths are +// ReadProcessMemory calls from out-of-process. Conceptually, they +// are like a pure-native (preemptive) thread. +//// +// This means that in particular, you cannot DACize a GCTRIGGERS function. +// Neither can you DACize a function that throws if this will involve +// allocating a new exception object. There may be +// exceptions to these rules if you can guarantee that the DACized +// part of the code path cannot cause a garbage collection (see +// EditAndContinueModule::ResolveField for an example). +// If you need to DACize a function that may trigger +// a GC, it is probably best to refactor the function so that the DACized +// part of the code path is in a separate function. For instance, +// functions with GetOrCreate() semantics are hard to DAC-ize because +// they the Create portion is inherently invasive. Instead, consider refactoring +// into a GetOrFail() function that DAC can call; and then make GetOrCreate() +// a wrapper around that. + +// +// This code works by hiding the details of access to target memory. +// Access is divided into two types: +// 1. DPTR - access to a piece of data. +// 2. VPTR - access to a class with a vtable. The class can only have +// a single vtable pointer at the beginning of the class instance. +// Things only need to be declared as VPTRs when it is necessary to +// call virtual functions in the host. In that case the access layer +// must do extra work to provide a host vtable for the object when +// it is retrieved so that virtual functions can be called. +// +// When compiling with DACCESS_COMPILE the macros turn into templates +// which replace pointers with smart pointers that know how to fetch +// data from the target process and provide a host process version of it. +// Normal data structure access will transparently receive a host copy +// of the data and proceed, so code such as +// typedef DPTR(Class) PTR_Class; +// PTR_Class cls; +// int val = cls->m_Int; +// will work without modification. The appropriate operators are overloaded +// to provide transparent access, such as the -> operator in this case. +// Note that the convention is to create an appropriate typedef for +// each type that will be accessed. This hides the particular details +// of the type declaration and makes the usage look more like regular code. +// +// The ?PTR classes also have an implicit base type cast operator to +// produce a host-pointer instance of the given type. For example +// Class* cls = PTR_Class(addr); +// works by implicit conversion from the PTR_Class created by wrapping +// to a host-side Class instance. Again, this means that existing code +// can work without modification. +// +// Code Example: +// +// typedef struct _rangesection +// { +// PTR_IJitManager pjit; +// PTR_RangeSection pright; +// PTR_RangeSection pleft; +// ... Other fields omitted ... +// } RangeSection; +// +// RangeSection* pRS = m_RangeTree; +// +// while (pRS != NULL) +// { +// if (currentPC < pRS->LowAddress) +// pRS=pRS->pleft; +// else if (currentPC > pRS->HighAddress) +// pRS=pRS->pright; +// else +// { +// return pRS->pjit; +// } +// } +// +// This code does not require any modifications. The global reference +// provided by m_RangeTree will be a host version of the RangeSection +// instantiated by conversion. The references to pRS->pleft and +// pRS->pright will refer to DPTRs due to the modified declaration. +// In the assignment statement the compiler will automatically use +// the implicit conversion from PTR_RangeSection to RangeSection*, +// causing a host instance to be created. Finally, if an appropriate +// section is found the use of pRS->pjit will cause an implicit +// conversion from PTR_IJitManager to IJitManager. The VPTR code +// will look at target memory to determine the actual derived class +// for the JitManager and instantiate the right class in the host so +// that host virtual functions can be used just as they would in +// the target. +// +// There are situations where code modifications are required, though. +// +// 1. Any time the actual value of an address matters, such as using +// it as a search key in a tree, the target address must be used. +// +// An example of this is the RangeSection tree used to locate JIT +// managers. A portion of this code is shown above. Each +// RangeSection node in the tree describes a range of addresses +// managed by the JitMan. These addresses are just being used as +// values, not to dereference through, so there are not DPTRs. When +// searching the range tree for an address the address used in the +// search must be a target address as that's what values are kept in +// the RangeSections. In the code shown above, currentPC must be a +// target address as the RangeSections in the tree are all target +// addresses. Use dac_cast to retrieve the target address +// of a ?PTR, as well as to convert a host address to the +// target address used to retrieve that particular instance. Do not +// use dac_cast with any raw target pointer types (such as BYTE*). +// +// 2. Any time an address is modified, such as by address arithmetic, +// the arithmetic must be performed on the target address. +// +// When a host instance is created it is created for the type in use. +// There is no particular relation to any other instance, so address +// arithmetic cannot be used to get from one instance to any other +// part of memory. For example +// char* Func(Class* cls) +// { +// // String follows the basic Class data. +// return (char*)(cls + 1); +// } +// does not work with external access because the Class* used would +// have retrieved only a Class worth of data. There is no string +// following the host instance. Instead, this code should use +// dac_cast to get the target address of the Class +// instance, add sizeof(*cls) and then create a new ?PTR to access +// the desired data. Note that the newly retrieved data will not +// be contiguous with the Class instance, so address arithmetic +// will still not work. +// +// Previous Code: +// +// BOOL IsTarget(LPVOID ip) +// { +// StubCallInstrs* pStubCallInstrs = GetStubCallInstrs(); +// +// if (ip == (LPVOID) &(pStubCallInstrs->m_op)) +// { +// return TRUE; +// } +// +// Modified Code: +// +// BOOL IsTarget(LPVOID ip) +// { +// StubCallInstrs* pStubCallInstrs = GetStubCallInstrs(); +// +// if ((TADDR)ip == dac_cast(pStubCallInstrs) + +// (TADDR)offsetof(StubCallInstrs, m_op)) +// { +// return TRUE; +// } +// +// The parameter ip is a target address, so the host pStubCallInstrs +// cannot be used to derive an address from. The member & reference +// has to be replaced with a conversion from host to target address +// followed by explicit offsetting for the field. +// +// PTR_HOST_MEMBER_TADDR is a convenience macro that encapsulates +// these two operations, so the above code could also be: +// +// if ((TADDR)ip == +// PTR_HOST_MEMBER_TADDR(StubCallInstrs, pStubCallInstrs, m_op)) +// +// 3. Any time the amount of memory referenced through an address +// changes, such as by casting to a different type, a new ?PTR +// must be created. +// +// Host instances are created and stored based on both the target +// address and size of access. The access code has no way of knowing +// all possible ways that data will be retrieved for a given address +// so if code changes the way it accesses through an address a new +// ?PTR must be used, which may lead to a difference instance and +// different host address. This means that pointer identity does not hold +// across casts, so code like +// Class* cls = PTR_Class(addr); +// Class2* cls2 = PTR_Class2(addr); +// return cls == cls2; +// will fail because the host-side instances have no relation to each +// other. That isn't a problem, since by rule #1 you shouldn't be +// relying on specific host address values. +// +// Previous Code: +// +// return (ArrayClass *) m_pMethTab->GetClass(); +// +// Modified Code: +// +// return PTR_ArrayClass(m_pMethTab->GetClass()); +// +// The ?PTR templates have an implicit conversion from a host pointer +// to a target address, so the cast above constructs a new +// PTR_ArrayClass by implicitly converting the host pointer result +// from GetClass() to its target address and using that as the address +// of the new PTR_ArrayClass. As mentioned, the actual host-side +// pointer values may not be the same. +// +// Host pointer identity can be assumed as long as the type of access +// is the same. In the example above, if both accesses were of type +// Class then the host pointer will be the same, so it is safe to +// retrieve the target address of an instance and then later get +// a new host pointer for the target address using the same type as +// the host pointer in that case will be the same. This is enabled +// by caching all of the retrieved host instances. This cache is searched +// by the addr:size pair and when there's a match the existing instance +// is reused. This increases performance and also allows simple +// pointer identity to hold. It does mean that host memory grows +// in proportion to the amount of target memory being referenced, +// so retrieving extraneous data should be avoided. +// The host-side data cache grows until the Flush() method is called, +// at which point all host-side data is discarded. No host +// instance pointers should be held across a Flush(). +// +// Accessing into an object can lead to some unusual behavior. For +// example, the SList class relies on objects to contain an SLink +// instance that it uses for list maintenance. This SLink can be +// embedded anywhere in the larger object. The SList access is always +// purely to an SLink, so when using the access layer it will only +// retrieve an SLink's worth of data. The SList template will then +// do some address arithmetic to determine the start of the real +// object and cast the resulting pointer to the final object type. +// When using the access layer this results in a new ?PTR being +// created and used, so a new instance will result. The internal +// SLink instance will have no relation to the new object instance +// even though in target address terms one is embedded in the other. +// The assumption of data stability means that this won't cause +// a problem, but care must be taken with the address arithmetic, +// as layed out in rules #2 and #3. +// +// 4. Global address references cannot be used. Any reference to a +// global piece of code or data, such as a function address, global +// variable or class static variable, must be changed. +// +// The external access code may load at a different base address than +// the target process code. Global addresses are therefore not +// meaningful and must be replaced with something else. There isn't +// a single solution, so replacements must be done on a case-by-case +// basis. +// +// The simplest case is a global or class static variable. All +// declarations must be replaced with a special declaration that +// compiles into a modified accessor template value when compiled for +// external data access. Uses of the variable automatically are fixed +// up by the template instance. Note that assignment to the global +// must be independently ifdef'ed as the external access layer should +// not make any modifications. +// +// Macros allow for simple declaration of a class static and global +// values that compile into an appropriate templated value. +// +// Previous Code: +// +// static RangeSection* m_RangeTree; +// RangeSection* ExecutionManager::m_RangeTree; +// +// extern ThreadStore* g_pThreadStore; +// ThreadStore* g_pThreadStore = &StaticStore; +// class SystemDomain : public BaseDomain { +// ... +// ArrayListStatic m_appDomainIndexList; +// ... +// } +// +// SystemDomain::m_appDomainIndexList; +// +// extern DWORD gThreadTLSIndex; +// +// DWORD gThreadTLSIndex = TLS_OUT_OF_INDEXES; +// +// Modified Code: +// +// typedef DPTR(RangeSection) PTR_RangeSection; +// SPTR_DECL(RangeSection, m_RangeTree); +// SPTR_IMPL(RangeSection, ExecutionManager, m_RangeTree); +// +// typedef DPTR(ThreadStore) PTR_ThreadStore +// GPTR_DECL(ThreadStore, g_pThreadStore); +// GPTR_IMPL_INIT(ThreadStore, g_pThreadStore, &StaticStore); +// +// class SystemDomain : public BaseDomain { +// ... +// SVAL_DECL(ArrayListStatic; m_appDomainIndexList); +// ... +// } +// +// SVAL_IMPL(ArrayListStatic, SystemDomain, m_appDomainIndexList); +// +// GVAL_DECL(DWORD, gThreadTLSIndex); +// +// GVAL_IMPL_INIT(DWORD, gThreadTLSIndex, TLS_OUT_OF_INDEXES); +// +// When declaring the variable, the first argument declares the +// variable's type and the second argument declares the variable's +// name. When defining the variable the arguments are similar, with +// an extra class name parameter for the static class variable case. +// If an initializer is needed the IMPL_INIT macro should be used. +// +// Things get slightly more complicated when declaring an embedded +// array. In this case the data element is not a single element and +// therefore cannot be represented by a ?PTR. In the case of a global +// array, you should use the GARY_DECL and GARY_IMPL macros. +// We durrently have no support for declaring static array data members +// or initialized arrays. Array data members that are dynamically allocated +// need to be treated as pointer members. To reference individual elements +// you must use pointer arithmetic (see rule 2 above). An array declared +// as a local variable within a function does not need to be DACized. +// +// +// All uses of ?VAL_DECL must have a corresponding entry given in the +// DacGlobals structure in src\inc\dacvars.h. For SVAL_DECL the entry +// is class__name. For GVAL_DECL the entry is dac__name. You must add +// these entries in dacvars.h using the DEFINE_DACVAR macro. Note that +// these entries also are used for dumping memory in mini dumps and +// heap dumps. If it's not appropriate to dump a variable, (e.g., +// it's an array or some other value that is not important to have +// in a minidump) a second macro, DEFINE_DACVAR_NO_DUMP, will allow +// you to make the required entry in the DacGlobals structure without +// dumping its value. +// +// For convenience, here is a list of the various variable declaration and +// initialization macros: +// SVAL_DECL(type, name) static non-pointer data class MyClass +// member declared within { +// the class declaration // static int i; +// SVAL_DECL(int, i); +// } +// +// SVAL_IMPL(type, cls, name) static non-pointer data // int MyClass::i; +// member defined outside SVAL_IMPL(int, MyClass, i); +// the class declaration +// +// SVAL_IMPL_INIT(type, cls, static non-pointer data // int MyClass::i = 0; +// name, val) member defined and SVAL_IMPL_INIT(int, MyClass, i, 0); +// initialized outside the +// class declaration +// ------------------------------------------------------------------------------------------------ +// SPTR_DECL(type, name) static pointer data class MyClass +// member declared within { +// the class declaration // static int * pInt; +// SPTR_DECL(int, pInt); +// } +// +// SPTR_IMPL(type, cls, name) static pointer data // int * MyClass::pInt; +// member defined outside SPTR_IMPL(int, MyClass, pInt); +// the class declaration +// +// SPTR_IMPL_INIT(type, cls, static pointer data // int * MyClass::pInt = NULL; +// name, val) member defined and SPTR_IMPL_INIT(int, MyClass, pInt, NULL); +// initialized outside the +// class declaration +// ------------------------------------------------------------------------------------------------ +// GVAL_DECL(type, name) extern declaration of // extern int g_i +// global non-pointer GVAL_DECL(int, g_i); +// variable +// +// GVAL_IMPL(type, name) declaration of a // int g_i +// global non-pointer GVAL_IMPL(int, g_i); +// variable +// +// GVAL_IMPL_INIT (type, declaration and // int g_i = 0; +// name, initialization of a GVAL_IMPL_INIT(int, g_i, 0); +// val) global non-pointer +// variable +// ****Note**** +// If you use GVAL_? to declare a global variable of a structured type and you need to +// access a member of the type, you cannot use the dot operator. Instead, you must take the +// address of the variable and use the arrow operator. For example: +// struct +// { +// int x; +// char ch; +// } MyStruct; +// GVAL_IMPL(MyStruct, g_myStruct); +// int i = (&g_myStruct)->x; +// ------------------------------------------------------------------------------------------------ +// GPTR_DECL(type, name) extern declaration of // extern int * g_pInt +// global pointer GPTR_DECL(int, g_pInt); +// variable +// +// GPTR_IMPL(type, name) declaration of a // int * g_pInt +// global pointer GPTR_IMPL(int, g_pInt); +// variable +// +// GPTR_IMPL_INIT (type, declaration and // int * g_pInt = 0; +// name, initialization of a GPTR_IMPL_INIT(int, g_pInt, NULL); +// val) global pointer +// variable +// ------------------------------------------------------------------------------------------------ +// GARY_DECL(type, name) extern declaration of // extern int g_rgIntList[MAX_ELEMENTS]; +// a global array GPTR_DECL(int, g_rgIntList, MAX_ELEMENTS); +// variable +// +// GARY_IMPL(type, name) declaration of a // int g_rgIntList[MAX_ELEMENTS]; +// global pointer GPTR_IMPL(int, g_rgIntList, MAX_ELEMENTS); +// variable +// +// +// Certain pieces of code, such as the stack walker, rely on identifying +// an object from its vtable address. As the target vtable addresses +// do not necessarily correspond to the vtables used in the host, these +// references must be translated. The access layer maintains translation +// tables for all classes used with VPTR and can return the target +// vtable pointer for any host vtable in the known list of VPTR classes. +// +// ----- Errors: +// +// All errors in the access layer are reported via exceptions. The +// formal access layer methods catch all such exceptions and turn +// them into the appropriate error, so this generally isn't visible +// to users of the access layer. +// +// ----- DPTR Declaration: +// +// Create a typedef for the type with typedef DPTR(type) PTR_type; +// Replace type* with PTR_type. +// +// ----- VPTR Declaration: +// +// VPTR can only be used on classes that have a single vtable +// pointer at the beginning of the object. This should be true +// for a normal single-inheritance object. +// +// All of the classes that may be instantiated need to be identified +// and marked. In the base class declaration add either +// VPTR_BASE_VTABLE_CLASS if the class is abstract or +// VPTR_BASE_CONCRETE_VTABLE_CLASS if the class is concrete. In each +// derived class add VPTR_VTABLE_CLASS. If you end up with compile or +// link errors for an unresolved method called VPtrSize you missed a +// derived class declaration. +// +// As described above, dac can only handle classes with a single +// vtable. However, there's a special case for multiple inheritance +// situations when only one of the classes is needed for dac. If +// the base class needed is the first class in the derived class's +// layout then it can be used with dac via using the VPTR_MULTI_CLASS +// macros. Use with extreme care. +// +// All classes to be instantiated must be listed in src\inc\vptr_list.h. +// +// Create a typedef for the type with typedef VPTR(type) PTR_type; +// When using a VPTR, replace Class* with PTR_Class. +// +// ----- Specific Macros: +// +// PTR_TO_TADDR(ptr) +// Retrieves the raw target address for a ?PTR. +// See code:dac_cast for the preferred alternative +// +// PTR_HOST_TO_TADDR(host) +// Given a host address of an instance produced by a ?PTR reference, +// return the original target address. The host address must +// be an exact match for an instance. +// See code:dac_cast for the preferred alternative +// +// PTR_HOST_INT_TO_TADDR(host) +// Given a host address which resides somewhere within an instance +// produced by a ?PTR reference (a host interior pointer) return the +// corresponding target address. This is useful for evaluating +// relative pointers (e.g. RelativePointer) where calculating the +// target address requires knowledge of the target address of the +// relative pointer field itself. This lookup is slower than that for +// a non-interior host pointer so use it sparingly. +// +// VPTR_HOST_VTABLE_TO_TADDR(host) +// Given the host vtable pointer for a known VPTR class, return +// the target vtable pointer. +// +// PTR_HOST_MEMBER_TADDR(type, host, memb) +// Retrieves the target address of a host instance pointer and +// offsets it by the given member's offset within the type. +// +// PTR_HOST_INT_MEMBER_TADDR(type, host, memb) +// As above but will work for interior host pointers (see the +// description of PTR_HOST_INT_TO_TADDR for an explanation of host +// interior pointers). +// +// PTR_READ(addr, size) +// Reads a block of memory from the target and returns a host +// pointer for it. Useful for reading blocks of data from the target +// whose size is only known at runtime, such as raw code for a jitted +// method. If the data being read is actually an object, use SPTR +// instead to get better type semantics. +// +// DAC_EMPTY() +// DAC_EMPTY_ERR() +// DAC_EMPTY_RET(retVal) +// DAC_UNEXPECTED() +// Provides an empty method implementation when compiled +// for DACCESS_COMPILE. For example, use to stub out methods needed +// for vtable entries but otherwise unused. +// +// These macros are designed to turn into normal code when compiled +// without DACCESS_COMPILE. +// +//***************************************************************************** +// See code:EEStartup#TableOfContents for EE overview + +#ifndef __daccess_h__ +#define __daccess_h__ + +#ifndef __in +#include +#endif + +#define DACCESS_TABLE_RESOURCE L"COREXTERNALDATAACCESSRESOURCE" + +#include "type_traits.hpp" + +#ifdef DACCESS_COMPILE + +//#include "switches.h" +#include "safemath.h" +#include "corerror.h" + +#ifdef TARGET_X64 +typedef UInt64 UIntTarget; +#elif defined(TARGET_X86) +typedef UInt32 UIntTarget; +#elif defined(TARGET_THUMB2) +typedef UInt32 UIntTarget; +#else +#error unexpected target architecture +#endif + +// +// This version of things wraps pointer access in +// templates which understand how to retrieve data +// through an access layer. In this case no assumptions +// can be made that the current compilation processor or +// pointer types match the target's processor or pointer types. +// + +// Define TADDR as a non-pointer value so use of it as a pointer +// will not work properly. Define it as unsigned so +// pointer comparisons aren't affected by sign. +// This requires special casting to ULONG64 to sign-extend if necessary. +// XXX drewb - Cheating right now by not supporting cross-plat. +typedef UIntTarget TADDR; + +// TSIZE_T used for counts or ranges that need to span the size of a +// target pointer. For cross-plat, this may be different than SIZE_T +// which reflects the host pointer size. +typedef UIntTarget TSIZE_T; + +// Information stored in the DAC table of interest to the DAC implementation +// Note that this information is shared between all instantiations of ClrDataAccess, so initialize +// it just once in code:ClrDataAccess.GetDacGlobals (rather than use fields in ClrDataAccess); +struct DacTableInfo +{ + // On Windows, the first DWORD is the 32-bit timestamp read out of the runtime dll's debug directory. + // The remaining 3 DWORDS must all be 0. + // On Mac, this is the 16-byte UUID of the runtime dll. + // It is used to validate that mscorwks is the same version as mscordacwks + UInt32 dwID0; + UInt32 dwID1; + UInt32 dwID2; + UInt32 dwID3; +}; +extern DacTableInfo g_dacTableInfo; + +// +// The following table contains all the global information that data access needs to begin +// operation. All of the values stored here are RVAs. DacGlobalBase() returns the current +// base address to combine with to get a full target address. +// + +typedef struct _DacGlobals +{ +// These will define all of the dac related mscorwks static and global variables +// TODO: update DacTableGen to parse "UInt32" instead of "ULONG32" for the ids +#ifdef DAC_CLR_ENVIRONMENT +#define DEFINE_DACVAR(id_type, size, id) id_type id; +#define DEFINE_DACVAR_NO_DUMP(id_type, size, id) id_type id; +#else +#define DEFINE_DACVAR(id_type, size, id) UInt32 id; +#define DEFINE_DACVAR_NO_DUMP(id_type, size, id) UInt32 id; +#endif +#include "dacvars.h" +#undef DEFINE_DACVAR_NO_DUMP +#undef DEFINE_DACVAR + +/* + // Global functions. + ULONG fn__QueueUserWorkItemCallback; + ULONG fn__ThreadpoolMgr__AsyncCallbackCompletion; + ULONG fn__ThreadpoolMgr__AsyncTimerCallbackCompletion; + ULONG fn__DACNotifyCompilationFinished; +#ifdef _X86_ + ULONG fn__NativeDelayFixupAsmStub; + ULONG fn__NativeDelayFixupAsmStubRet; +#endif // _X86_ + ULONG fn__PInvokeCalliReturnFromCall; + ULONG fn__NDirectGenericStubReturnFromCall; + ULONG fn__DllImportForDelegateGenericStubReturnFromCall; +*/ + +} DacGlobals; + +extern DacGlobals g_dacGlobals; + +#ifdef __cplusplus +extern "C" { +#endif + +// These two functions are largely just for marking code +// that is not fully converted. DacWarning prints a debug +// message, while DacNotImpl throws a not-implemented exception. +void __cdecl DacWarning(__in __in_z char* format, ...); +void DacNotImpl(void); +void DacError(HRESULT err); +void __declspec(noreturn) DacError_NoRet(HRESULT err); +TADDR DacGlobalBase(void); +#ifdef DAC_CLR_ENVIRONMENT +HRESULT DacReadAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx); +HRESULT DacWriteAll(TADDR addr, PVOID buffer, ULONG32 size, bool throwEx); +HRESULT DacAllocVirtual(TADDR addr, ULONG32 size, + ULONG32 typeFlags, ULONG32 protectFlags, + bool throwEx, TADDR* mem); +HRESULT DacFreeVirtual(TADDR mem, ULONG32 size, ULONG32 typeFlags, + bool throwEx); + +#endif // DAC_CLR_ENVIRONMENT + +/* We are simulating a tiny bit of memory existing in the debuggee address space that really isn't there. + The memory appears to exist in the last 1KB of the memory space to make minimal risk that + it collides with any legitimate debuggee memory. When the DAC uses + DacInstantiateTypeByAddressHelper on these high addresses instead of getting back a pointer + in the DAC_INSTANCE cache it will get back a pointer to specifically configured block of + debugger memory. + + Rationale: + This method was invented to solve a problem when doing stack walking in the DAC. When + running in-process the register context has always been written to memory somewhere before + the stackwalker begins to operate. The stackwalker doesn't track the registers themselves, + but rather the storage locations where registers were written. + When the DAC runs the registers haven't been saved anywhere - there is no memory address + that refers to them. It would be easy to store the registers in the debugger's memory space + but the Regdisplay is typed as PTR_UIntNative, not UIntNative*. We could change REGDISPLAY + to point at debugger local addresses, but then we would have the opposite problem, being unable + to refer to stack addresses that are in the debuggee memory space. Options we could do: + 1) Add discriminant bits to REGDISPLAY fields to record whether the pointer is local or remote + a) Do it in the runtime definition - adds size and complexity to mrt100 for a debug only scenario + b) Do it only in the DAC definition - breaks marshalling for types that are or contain REGDISPLAY + (ie StackFrameIterator). + 2) Add a new DebuggerREGDISPLAY type that can hold local or remote addresses, and then create + parallel DAC stackwalking code that uses it. This is a bunch of work and + has higher maintenance cost to keep both code paths operational and functionally identical. + 3) Allocate space in debuggee that will be used to stash the registers when doing a debug stackwalk - + increases runtime working set for debug only scenario and won't work for dumps + 4) Same as #3, but don't actually allocate the space at runtime, just simulate that it was allocated + within the debugger - risk of colliding with real runtime allocations, adds complexity to the + DAC. + + #4 seems the best option to me, so we wound up here. +*/ + +// This address is picked to be very unlikely to collide with any real memory usage in the target +#define SIMULATED_DEBUGGEE_MEMORY_BASE_ADDRESS ((TADDR) -1024) +// The byte at ((TADDR)-1) isn't addressable at all, so we only have 1023 bytes of usable space +// At the moment we only need 256 bytes at most. +#define SIMULATED_DEBUGGEE_MEMORY_MAX_SIZE 1023 + +// Sets the simulated debuggee memory region, or clears it if pSimulatedDebuggeeMemory = NULL +// See large comment above for more details. +void SetSimulatedDebuggeeMemory(void* pSimulatedDebuggeeMemory, UInt32 cbSimulatedDebuggeeMemory); + +void* DacInstantiateTypeByAddress(TADDR addr, UInt32 size, bool throwEx); +void* DacInstantiateTypeByAddressNoReport(TADDR addr, UInt32 size, bool throwEx); +void* DacInstantiateClassByVTable(TADDR addr, UInt32 minSize, bool throwEx); + +// This method should not be used casually. Make sure simulatedTargetAddr does not cause collisions. See comment in dacfn.cpp for more details. +void* DacInstantiateTypeAtSimulatedAddress(TADDR simulatedTargetAddr, UInt32 size, void* pLocalBuffer, bool throwEx); + +// Copy a null-terminated ascii or unicode string from the target to the host. +// Note that most of the work here is to find the null terminator. If you know the exact length, +// then you can also just call DacInstantiateTypebyAddress. +char* DacInstantiateStringA(TADDR addr, UInt32 maxChars, bool throwEx); +wchar_t* DacInstantiateStringW(TADDR addr, UInt32 maxChars, bool throwEx); + +TADDR DacGetTargetAddrForHostAddr(const void* ptr, bool throwEx); +TADDR DacGetTargetAddrForHostInteriorAddr(const void* ptr, bool throwEx); +TADDR DacGetTargetVtForHostVt(const void* vtHost, bool throwEx); +wchar_t* DacGetVtNameW(TADDR targetVtable); + +#ifdef DAC_CLR_ENVIRONMENT + +// Report a region of memory to the debugger +void DacEnumMemoryRegion(TADDR addr, TSIZE_T size, bool fExpectSuccess = true); + +HRESULT DacWriteHostInstance(PVOID host, bool throwEx); + +// Occasionally it's necessary to allocate some host memory for +// instance data that's created on the fly and so doesn't directly +// correspond to target memory. These are held and freed on flush +// like other instances but can't be looked up by address. +PVOID DacAllocHostOnlyInstance(ULONG32 size, bool throwEx); + +// Determines whether ASSERTs should be raised when inconsistencies in the target are detected +bool DacTargetConsistencyAssertsEnabled(); + +// Host instances can be marked as they are enumerated in +// order to break cycles. This function returns true if +// the instance is already marked, otherwise it marks the +// instance and returns false. +bool DacHostPtrHasEnumMark(LPCVOID host); + +// Determines if EnumMemoryRegions has been called on a method descriptor. +// This helps perf for minidumps of apps with large managed stacks. +bool DacHasMethodDescBeenEnumerated(LPCVOID pMD); + +// Sets a flag indicating that EnumMemoryRegions on a method desciptor +// has been successfully called. The function returns true if +// this flag had been previously set. +bool DacSetMethodDescEnumerated(LPCVOID pMD); + +// Determines if a method descriptor is valid +BOOL DacValidateMD(LPCVOID pMD); + +// Enumerate the instructions around a call site to help debugger stack walking heuristics +void DacEnumCodeForStackwalk(TADDR taCallEnd); + +// Given the address and the size of a memory range which is stored in the buffer, replace all the patches +// in the buffer with the real opcodes. This is especially important on X64 where the unwinder needs to +// disassemble the native instructions. +class MemoryRange; +HRESULT DacReplacePatchesInHostMemory(MemoryRange range, PVOID pBuffer); + +// +// Convenience macros for EnumMemoryRegions implementations. +// + +// Enumerate the given host instance and return +// true if the instance hasn't already been enumerated. +#define DacEnumHostDPtrMem(host) \ + (!DacHostPtrHasEnumMark(host) ? \ + (DacEnumMemoryRegion(PTR_HOST_TO_TADDR(host), sizeof(*host)), \ + true) : false) +#define DacEnumHostSPtrMem(host, type) \ + (!DacHostPtrHasEnumMark(host) ? \ + (DacEnumMemoryRegion(PTR_HOST_TO_TADDR(host), \ + type::DacSize(PTR_HOST_TO_TADDR(host))), \ + true) : false) +#define DacEnumHostVPtrMem(host) \ + (!DacHostPtrHasEnumMark(host) ? \ + (DacEnumMemoryRegion(PTR_HOST_TO_TADDR(host), (host)->VPtrSize()), \ + true) : false) + +// Check enumeration of 'this' and return if this has already been +// enumerated. Making this the first line of an object's EnumMemoryRegions +// method will prevent cycles. +#define DAC_CHECK_ENUM_THIS() \ + if (DacHostPtrHasEnumMark(this)) return +#define DAC_ENUM_DTHIS() \ + if (!DacEnumHostDPtrMem(this)) return +#define DAC_ENUM_STHIS(type) \ + if (!DacEnumHostSPtrMem(this, type)) return +#define DAC_ENUM_VTHIS() \ + if (!DacEnumHostVPtrMem(this)) return + +#endif // DAC_CLR_ENVIRONMENT + +#ifdef __cplusplus +} + +#if defined(_WIN64) && defined GCRH_WINDOWS_INCLUDED +struct _UNWIND_INFO * DacGetUnwindInfo(TADDR taUnwindInfo); + +// virtually unwind a CONTEXT out-of-process +BOOL DacUnwindStackFrame(CONTEXT * pContext); +#endif // defined(_WIN64) + +// +// Computes (taBase + (dwIndex * dwElementSize()), with overflow checks. +// +// Arguments: +// taBase the base TADDR value +// dwIndex the index of the offset +// dwElementSize the size of each element (to multiply the offset by) +// +// Return value: +// The resulting TADDR, or throws CORDB_E_TARGET_INCONSISTENT on overlow. +// +// Notes: +// The idea here is that overflows during address arithmetic suggest that we're operating on corrupt +// pointers. It helps to improve reliability to detect the cases we can (like overflow) and fail. Note +// that this is just a heuristic, not a security measure. We can't trust target data regardless - +// failing on overflow is just one easy case of corruption to detect. There is no need to use checked +// arithmetic everywhere in the DAC infrastructure, this is intended just for the places most likely to +// help catch bugs (eg. __DPtr::operator[]). +// +inline TADDR DacTAddrOffset( TADDR taBase, TSIZE_T dwIndex, TSIZE_T dwElementSize ) +{ +#ifdef DAC_CLR_ENVIRONMENT + ClrSafeInt t(taBase); + t += ClrSafeInt(dwIndex) * ClrSafeInt(dwElementSize); + if( t.IsOverflow() ) + { + // Pointer arithmetic overflow - probably due to corrupt target data + //DacError(CORDBG_E_TARGET_INCONSISTENT); + DacError(E_FAIL); + } + return t.Value(); +#else // TODO: port safe math + return taBase + (dwIndex*dwElementSize); +#endif +} + +// Base pointer wrapper which provides common behavior. +class __TPtrBase +{ +public: + __TPtrBase() + { + // Make uninitialized pointers obvious. + m_addr = (TADDR)-1; + } + explicit __TPtrBase(TADDR addr) + { + m_addr = addr; + } + + bool operator!() const + { + return m_addr == 0; + } + // We'd like to have an implicit conversion to bool here since the C++ + // standard says all pointer types are implicitly converted to bool. + // Unfortunately, that would cause ambiguous overload errors for uses + // of operator== and operator!=. Instead callers will have to compare + // directly against NULL. + + bool operator==(TADDR addr) const + { + return m_addr == addr; + } + bool operator!=(TADDR addr) const + { + return m_addr != addr; + } + bool operator<(TADDR addr) const + { + return m_addr < addr; + } + bool operator>(TADDR addr) const + { + return m_addr > addr; + } + bool operator<=(TADDR addr) const + { + return m_addr <= addr; + } + bool operator>=(TADDR addr) const + { + return m_addr >= addr; + } + + TADDR GetAddr(void) const + { + return m_addr; + } + TADDR SetAddr(TADDR addr) + { + m_addr = addr; + return addr; + } + +protected: + TADDR m_addr; +}; + +// Adds comparison operations +// Its possible we just want to merge these into __TPtrBase, but SPtr isn't comparable with +// other types right now and I would rather stay conservative +class __ComparableTPtrBase : public __TPtrBase +{ +protected: + __ComparableTPtrBase(void) : __TPtrBase() + {} + + explicit __ComparableTPtrBase(TADDR addr) : __TPtrBase(addr) + {} + +public: + bool operator==(const __ComparableTPtrBase& ptr) const + { + return m_addr == ptr.m_addr; + } + bool operator!=(const __ComparableTPtrBase& ptr) const + { + return !operator==(ptr); + } + bool operator<(const __ComparableTPtrBase& ptr) const + { + return m_addr < ptr.m_addr; + } + bool operator>(const __ComparableTPtrBase& ptr) const + { + return m_addr > ptr.m_addr; + } + bool operator<=(const __ComparableTPtrBase& ptr) const + { + return m_addr <= ptr.m_addr; + } + bool operator>=(const __ComparableTPtrBase& ptr) const + { + return m_addr >= ptr.m_addr; + } +}; + +// Pointer wrapper base class for various forms of normal data. +// This has the common functionality between __DPtr and __ArrayDPtr. +// The DPtrType type parameter is the actual derived type in use. This is necessary so that +// inhereted functions preserve exact return types. +template +class __DPtrBase : public __ComparableTPtrBase +{ +public: + typedef type _Type; + typedef type* _Ptr; + +protected: + // Constructors + // All protected - this type should not be used directly - use one of the derived types instead. + __DPtrBase< type, DPtrType >(void) : __ComparableTPtrBase() + {} + + explicit __DPtrBase< type, DPtrType >(TADDR addr) : __ComparableTPtrBase(addr) + {} + + explicit __DPtrBase(__TPtrBase addr) + { + m_addr = addr.GetAddr(); + } + explicit __DPtrBase(type const * host) + { + m_addr = DacGetTargetAddrForHostAddr(host, true); + } + +public: + DPtrType& operator=(const __TPtrBase& ptr) + { + m_addr = ptr.GetAddr(); + return DPtrType(m_addr); + } + DPtrType& operator=(TADDR addr) + { + m_addr = addr; + return DPtrType(m_addr); + } + + type& operator*(void) const + { + return *(type*)DacInstantiateTypeByAddress(m_addr, sizeof(type), true); + } + + + using __ComparableTPtrBase::operator==; + using __ComparableTPtrBase::operator!=; + using __ComparableTPtrBase::operator<; + using __ComparableTPtrBase::operator>; + using __ComparableTPtrBase::operator<=; + using __ComparableTPtrBase::operator>=; + bool operator==(TADDR addr) const + { + return m_addr == addr; + } + bool operator!=(TADDR addr) const + { + return m_addr != addr; + } + + // Array index operator + // we want an operator[] for all possible numeric types (rather than rely on + // implicit numeric conversions on the argument) to prevent ambiguity with + // DPtr's implicit conversion to type* and the built-in operator[]. + // @dbgtodo rbyers: we could also use this technique to simplify other operators below. + template + type& operator[](indexType index) + { + // Compute the address of the element. + TADDR elementAddr; + if( index >= 0 ) + { + elementAddr = DacTAddrOffset(m_addr, index, sizeof(type)); + } + else + { + // Don't bother trying to do overflow checking for negative indexes - they are rare compared to + // positive ones. ClrSafeInt doesn't support signed datatypes yet (although we should be able to add it + // pretty easily). + elementAddr = m_addr + index * sizeof(type); + } + + // Marshal over a single instance and return a reference to it. + return *(type*) DacInstantiateTypeByAddress(elementAddr, sizeof(type), true); + } + + template + type const & operator[](indexType index) const + { + return (*const_cast<__DPtrBase*>(this))[index]; + } + + //------------------------------------------------------------------------- + // operator+ + + DPtrType operator+(unsigned short val) + { + return DPtrType(DacTAddrOffset(m_addr, val, sizeof(type))); + } + DPtrType operator+(short val) + { + return DPtrType(m_addr + val * sizeof(type)); + } + // size_t is unsigned int on Win32, so we need + // to ifdef here to make sure the unsigned int + // and size_t overloads don't collide. size_t + // is marked __w64 so a simple unsigned int + // will not work on Win32, it has to be size_t. + DPtrType operator+(size_t val) + { + return DPtrType(DacTAddrOffset(m_addr, val, sizeof(type))); + } +#if (!defined (_X86_) && !defined(_SPARC_) && !defined(_ARM_)) || (defined(_X86_) && defined(__APPLE__)) + DPtrType operator+(unsigned int val) + { + return DPtrType(DacTAddrOffset(m_addr, val, sizeof(type))); + } +#endif // (!defined (_X86_) && !defined(_SPARC_) && !defined(_ARM_)) || (defined(_X86_) && defined(__APPLE__)) + DPtrType operator+(int val) + { + return DPtrType(m_addr + val * sizeof(type)); + } +#ifndef PLATFORM_UNIX // for now, everything else is 32 bit + DPtrType operator+(unsigned long val) + { + return DPtrType(DacTAddrOffset(m_addr, val, sizeof(type))); + } + DPtrType operator+(long val) + { + return DPtrType(m_addr + val * sizeof(type)); + } +#endif // !PLATFORM_UNIX // for now, everything else is 32 bit +#if !defined(_ARM_) && !defined(_X86_) + DPtrType operator+(IntNative val) + { + return DPtrType(m_addr + val * sizeof(type)); + } +#endif + + //------------------------------------------------------------------------- + // operator- + + DPtrType operator-(unsigned short val) + { + return DPtrType(m_addr - val * sizeof(type)); + } + DPtrType operator-(short val) + { + return DPtrType(m_addr - val * sizeof(type)); + } + // size_t is unsigned int on Win32, so we need + // to ifdef here to make sure the unsigned int + // and size_t overloads don't collide. size_t + // is marked __w64 so a simple unsigned int + // will not work on Win32, it has to be size_t. + DPtrType operator-(size_t val) + { + return DPtrType(m_addr - val * sizeof(type)); + } + DPtrType operator-(signed __int64 val) + { + return DPtrType(m_addr - val * sizeof(type)); + } +#if !defined (_X86_) && !defined(_SPARC_) && !defined(_ARM_) + DPtrType operator-(unsigned int val) + { + return DPtrType(m_addr - val * sizeof(type)); + } +#endif // !defined (_X86_) && !defined(_SPARC_) && !defined(_ARM_) + DPtrType operator-(int val) + { + return DPtrType(m_addr - val * sizeof(type)); + } +#ifdef _MSC_VER // for now, everything else is 32 bit + DPtrType operator-(unsigned long val) + { + return DPtrType(m_addr - val * sizeof(type)); + } + DPtrType operator-(long val) + { + return DPtrType(m_addr - val * sizeof(type)); + } +#endif // _MSC_VER // for now, everything else is 32 bit + size_t operator-(const DPtrType& val) + { + return (size_t)((m_addr - val.m_addr) / sizeof(type)); + } + + //------------------------------------------------------------------------- + + DPtrType& operator+=(size_t val) + { + m_addr += val * sizeof(type); + return static_cast(*this); + } + DPtrType& operator-=(size_t val) + { + m_addr -= val * sizeof(type); + return static_cast(*this); + } + + DPtrType& operator++() + { + m_addr += sizeof(type); + return static_cast(*this); + } + DPtrType& operator--() + { + m_addr -= sizeof(type); + return static_cast(*this); + } + DPtrType operator++(int postfix) + { + UNREFERENCED_PARAMETER(postfix); + DPtrType orig = DPtrType(*this); + m_addr += sizeof(type); + return orig; + } + DPtrType operator--(int postfix) + { + UNREFERENCED_PARAMETER(postfix); + DPtrType orig = DPtrType(*this); + m_addr -= sizeof(type); + return orig; + } + + bool IsValid(void) const + { + return m_addr && + DacInstantiateTypeByAddress(m_addr, sizeof(type), + false) != NULL; + } + void EnumMem(void) const + { + DacEnumMemoryRegion(m_addr, sizeof(type)); + } +}; + +// Pointer wrapper for objects which are just plain data +// and need no special handling. +template +class __DPtr : public __DPtrBase > +{ +#ifdef __GNUC__ +protected: + //there seems to be a bug in GCC's inference logic. It can't find m_addr. + using __DPtrBase >::m_addr; +#endif // __GNUC__ +public: + // constructors - all chain to __DPtrBase constructors + __DPtr< type >(void) : __DPtrBase >() {} + __DPtr< type >(TADDR addr) : __DPtrBase >(addr) {} + + // construct const from non-const + typedef typename type_traits::remove_const::type mutable_type; + __DPtr< type >(__DPtr const & rhs) : __DPtrBase >(rhs.GetAddr()) {} + + explicit __DPtr< type >(__TPtrBase addr) : __DPtrBase >(addr) {} + explicit __DPtr< type >(type const * host) : __DPtrBase >(host) {} + + operator type*() const + { + return (type*)DacInstantiateTypeByAddress(m_addr, sizeof(type), true); + } + type* operator->() const + { + return (type*)DacInstantiateTypeByAddress(m_addr, sizeof(type), true); + } +}; + +#define DPTR(type) __DPtr< type > + +// A restricted form of DPtr that doesn't have any conversions to pointer types. +// This is useful for pointer types that almost always represent arrays, as opposed +// to pointers to single instances (eg. PTR_BYTE). In these cases, allowing implicit +// conversions to (for eg.) BYTE* would usually result in incorrect usage (eg. pointer +// arithmetic and array indexing), since only a single instance has been marshalled to the host. +// If you really must marshal a single instance (eg. converting T* to PTR_T is too painful for now), +// then use code:DacUnsafeMarshalSingleElement so we can identify such unsafe code. +template +class __ArrayDPtr : public __DPtrBase > +{ +public: + // constructors - all chain to __DPtrBase constructors + __ArrayDPtr< type >(void) : __DPtrBase >() {} + __ArrayDPtr< type >(TADDR addr) : __DPtrBase >(addr) {} + + // construct const from non-const + typedef typename type_traits::remove_const::type mutable_type; + __ArrayDPtr< type >(__ArrayDPtr const & rhs) : __DPtrBase >(rhs.GetAddr()) {} + + explicit __ArrayDPtr< type >(__TPtrBase addr) : __DPtrBase >(addr) {} + + // Note that there is also no explicit constructor from host instances (type*). + // Going this direction is less problematic, but often still represents risky coding. +}; + +#define ArrayDPTR(type) __ArrayDPtr< type > + + +// Pointer wrapper for objects which are just plain data +// but whose size is not the same as the base type size. +// This can be used for prefetching data for arrays or +// for cases where an object has a variable size. +template +class __SPtr : public __TPtrBase +{ +public: + typedef type _Type; + typedef type* _Ptr; + + __SPtr< type >(void) : __TPtrBase() {} + __SPtr< type >(TADDR addr) : __TPtrBase(addr) {} + explicit __SPtr< type >(__TPtrBase addr) + { + m_addr = addr.GetAddr(); + } + explicit __SPtr< type >(type* host) + { + m_addr = DacGetTargetAddrForHostAddr(host, true); + } + + __SPtr< type >& operator=(const __TPtrBase& ptr) + { + m_addr = ptr.m_addr; + return *this; + } + __SPtr< type >& operator=(TADDR addr) + { + m_addr = addr; + return *this; + } + + operator type*() const + { + if (m_addr) + { + return (type*)DacInstantiateTypeByAddress(m_addr, + type::DacSize(m_addr), + true); + } + else + { + return (type*)NULL; + } + } + type* operator->() const + { + if (m_addr) + { + return (type*)DacInstantiateTypeByAddress(m_addr, + type::DacSize(m_addr), + true); + } + else + { + return (type*)NULL; + } + } + type& operator*(void) const + { + if (!m_addr) + { + DacError(E_INVALIDARG); + } + + return *(type*)DacInstantiateTypeByAddress(m_addr, + type::DacSize(m_addr), + true); + } + + bool IsValid(void) const + { + return m_addr && + DacInstantiateTypeByAddress(m_addr, type::DacSize(m_addr), + false) != NULL; + } + void EnumMem(void) const + { + if (m_addr) + { + DacEnumMemoryRegion(m_addr, type::DacSize(m_addr)); + } + } +}; + +#define SPTR(type) __SPtr< type > + +// Pointer wrapper for objects which have a single leading +// vtable, such as objects in a single-inheritance tree. +// The base class of all such trees must have use +// VPTR_BASE_VTABLE_CLASS in their declaration and all +// instantiable members of the tree must be listed in vptr_list.h. +template +class __VPtr : public __TPtrBase +{ +public: + // VPtr::_Type has to be a pointer as + // often the type is an abstract class. + // This type is not expected to be used anyway. + typedef type* _Type; + typedef type* _Ptr; + + __VPtr< type >(void) : __TPtrBase() {} + __VPtr< type >(TADDR addr) : __TPtrBase(addr) {} + explicit __VPtr< type >(__TPtrBase addr) + { + m_addr = addr.GetAddr(); + } + explicit __VPtr< type >(type* host) + { + m_addr = DacGetTargetAddrForHostAddr(host, true); + } + + __VPtr< type >& operator=(const __TPtrBase& ptr) + { + m_addr = ptr.m_addr; + return *this; + } + __VPtr< type >& operator=(TADDR addr) + { + m_addr = addr; + return *this; + } + + operator type*() const + { + return (type*)DacInstantiateClassByVTable(m_addr, sizeof(type), true); + } + type* operator->() const + { + return (type*)DacInstantiateClassByVTable(m_addr, sizeof(type), true); + } + + bool operator==(const __VPtr< type >& ptr) const + { + return m_addr == ptr.m_addr; + } + bool operator==(TADDR addr) const + { + return m_addr == addr; + } + bool operator!=(const __VPtr< type >& ptr) const + { + return !operator==(ptr); + } + bool operator!=(TADDR addr) const + { + return m_addr != addr; + } + + bool IsValid(void) const + { + return m_addr && + DacInstantiateClassByVTable(m_addr, sizeof(type), false) != NULL; + } + void EnumMem(void) const + { + if (IsValid()) + { + DacEnumMemoryRegion(m_addr, (operator->())->VPtrSize()); + } + } +}; + +#define VPTR(type) __VPtr< type > + +// Pointer wrapper for 8-bit strings. +#ifdef DAC_CLR_ENVIRONMENT +template +#else +template +#endif +class __Str8Ptr : public __DPtr +{ +public: + typedef type _Type; + typedef type* _Ptr; + + __Str8Ptr< type, maxChars >(void) : __DPtr() {} + __Str8Ptr< type, maxChars >(TADDR addr) : __DPtr(addr) {} + explicit __Str8Ptr< type, maxChars >(__TPtrBase addr) + { + m_addr = addr.GetAddr(); + } + explicit __Str8Ptr< type, maxChars >(type* host) + { + m_addr = DacGetTargetAddrForHostAddr(host, true); + } + + __Str8Ptr< type, maxChars >& operator=(const __TPtrBase& ptr) + { + m_addr = ptr.m_addr; + return *this; + } + __Str8Ptr< type, maxChars >& operator=(TADDR addr) + { + m_addr = addr; + return *this; + } + + operator type*() const + { + return (type*)DacInstantiateStringA(m_addr, maxChars, true); + } + + bool IsValid(void) const + { + return m_addr && + DacInstantiateStringA(m_addr, maxChars, false) != NULL; + } + void EnumMem(void) const + { + char* str = DacInstantiateStringA(m_addr, maxChars, false); + if (str) + { + DacEnumMemoryRegion(m_addr, strlen(str) + 1); + } + } +}; + +#define S8PTR(type) __Str8Ptr< type > +#define S8PTRMAX(type, maxChars) __Str8Ptr< type, maxChars > + +// Pointer wrapper for 16-bit strings. +#ifdef DAC_CLR_ENVIRONMENT +template +#else +template +#endif +class __Str16Ptr : public __DPtr +{ +public: + typedef type _Type; + typedef type* _Ptr; + + __Str16Ptr< type, maxChars >(void) : __DPtr() {} + __Str16Ptr< type, maxChars >(TADDR addr) : __DPtr(addr) {} + explicit __Str16Ptr< type, maxChars >(__TPtrBase addr) + { + m_addr = addr.GetAddr(); + } + explicit __Str16Ptr< type, maxChars >(type* host) + { + m_addr = DacGetTargetAddrForHostAddr(host, true); + } + + __Str16Ptr< type, maxChars >& operator=(const __TPtrBase& ptr) + { + m_addr = ptr.m_addr; + return *this; + } + __Str16Ptr< type, maxChars >& operator=(TADDR addr) + { + m_addr = addr; + return *this; + } + + operator type*() const + { + return (type*)DacInstantiateStringW(m_addr, maxChars, true); + } + + bool IsValid(void) const + { + return m_addr && + DacInstantiateStringW(m_addr, maxChars, false) != NULL; + } + void EnumMem(void) const + { + char* str = DacInstantiateStringW(m_addr, maxChars, false); + if (str) + { + DacEnumMemoryRegion(m_addr, strlen(str) + 1); + } + } +}; + +#define S16PTR(type) __Str16Ptr< type > +#define S16PTRMAX(type, maxChars) __Str16Ptr< type, maxChars > + +template +class __GlobalVal +{ +public: +#ifdef DAC_CLR_ENVIRONMENT + __GlobalVal< type >(PULONG rvaPtr) +#else + __GlobalVal< type >(UInt32* rvaPtr) +#endif + { + m_rvaPtr = rvaPtr; + } + + operator type() const + { + return (type)*__DPtr< type >(DacGlobalBase() + *m_rvaPtr); + } + + __DPtr< type > operator&() const + { + return __DPtr< type >(DacGlobalBase() + *m_rvaPtr); + } + + // @dbgtodo rbyers dac support: This updates values in the host. This seems extremely dangerous + // to do silently. I'd prefer that a specific (searchable) write function + // was used. Try disabling this and see what fails... + type & operator=(type & val) + { + type* ptr = __DPtr< type >(DacGlobalBase() + *m_rvaPtr); + // Update the host copy; + *ptr = val; + // Write back to the target. + DacWriteHostInstance(ptr, true); + return val; + } + + bool IsValid(void) const + { + return __DPtr< type >(DacGlobalBase() + *m_rvaPtr).IsValid(); + } + void EnumMem(void) const + { + TADDR p = DacGlobalBase() + *m_rvaPtr; + __DPtr< type >(p).EnumMem(); + } + +private: +#ifdef DAC_CLR_ENVIRONMENT + PULONG m_rvaPtr; +#else + UInt32* m_rvaPtr; +#endif +}; + +template +class __GlobalArray +{ +public: +#ifdef DAC_CLR_ENVIRONMENT + __GlobalArray< type, size >(PULONG rvaPtr) +#else + __GlobalArray< type, size >(UInt32* rvaPtr) +#endif + { + m_rvaPtr = rvaPtr; + } + + __DPtr< type > operator&() const + { + return __DPtr< type >(DacGlobalBase() + *m_rvaPtr); + } + + type& operator[](unsigned int index) const + { + return __DPtr< type >(DacGlobalBase() + *m_rvaPtr)[index]; + } + + bool IsValid(void) const + { + // Only validates the base pointer, not the full array range. + return __DPtr< type >(DacGlobalBase() + *m_rvaPtr).IsValid(); + } + void EnumMem(void) const + { + DacEnumMemoryRegion(DacGlobalBase() + *m_rvaPtr, sizeof(type) * size); + } + +private: +#ifdef DAC_CLR_ENVIRONMENT + PULONG m_rvaPtr; +#else + UInt32* m_rvaPtr; +#endif +}; + +template +class __GlobalPtr +{ +public: +#ifdef DAC_CLR_ENVIRONMENT + __GlobalPtr< acc_type, store_type >(PULONG rvaPtr) +#else + __GlobalPtr< acc_type, store_type >(UInt32* rvaPtr) +#endif + { + m_rvaPtr = rvaPtr; + } + + __DPtr< store_type > operator&() const + { + return __DPtr< store_type >(DacGlobalBase() + *m_rvaPtr); + } + + store_type & operator=(store_type & val) + { + store_type* ptr = __DPtr< store_type >(DacGlobalBase() + *m_rvaPtr); + // Update the host copy; + *ptr = val; + // Write back to the target. + DacWriteHostInstance(ptr, true); + return val; + } + + acc_type operator->() const + { + return (acc_type)*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr); + } + operator acc_type() const + { + return (acc_type)*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr); + } + operator store_type() const + { + return *__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr); + } + bool operator!() const + { + return !*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr); + } + + typename store_type::_Type operator[](int index) + { + return (*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr))[index]; + } + + typename store_type::_Type operator[](unsigned int index) + { + return (*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr))[index]; + } + + TADDR GetAddr() const + { + return (*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr)).GetAddr(); + } + + TADDR GetAddrRaw () const + { + return DacGlobalBase() + *m_rvaPtr; + } + + // This is only testing the the pointer memory is available but does not verify + // the memory that it points to. + // + bool IsValidPtr(void) const + { + return __DPtr< store_type >(DacGlobalBase() + *m_rvaPtr).IsValid(); + } + + bool IsValid(void) const + { + return __DPtr< store_type >(DacGlobalBase() + *m_rvaPtr).IsValid() && + (*__DPtr< store_type >(DacGlobalBase() + *m_rvaPtr)).IsValid(); + } + void EnumMem(void) const + { + __DPtr< store_type > ptr(DacGlobalBase() + *m_rvaPtr); + ptr.EnumMem(); + if (ptr.IsValid()) + { + (*ptr).EnumMem(); + } + } + +#ifdef DAC_CLR_ENVIRONMENT + PULONG m_rvaPtr; +#else + UInt32* m_rvaPtr; +#endif +}; + +template +inline bool operator==(const __GlobalPtr& gptr, + acc_type host) +{ + return DacGetTargetAddrForHostAddr(host, true) == + *__DPtr< TADDR >(DacGlobalBase() + *gptr.m_rvaPtr); +} +template +inline bool operator!=(const __GlobalPtr& gptr, + acc_type host) +{ + return !operator==(gptr, host); +} + +template +inline bool operator==(acc_type host, + const __GlobalPtr& gptr) +{ + return DacGetTargetAddrForHostAddr(host, true) == + *__DPtr< TADDR >(DacGlobalBase() + *gptr.m_rvaPtr); +} +template +inline bool operator!=(acc_type host, + const __GlobalPtr& gptr) +{ + return !operator==(host, gptr); +} + + +// +// __VoidPtr is a type that behaves like void* but for target pointers. +// Behavior of PTR_VOID: +// * has void* semantics. Will compile to void* in non-DAC builds (just like +// other PTR types. Unlike TADDR, we want pointer semantics. +// * NOT assignable from host pointer types or convertible to host pointer +// types - ensures we can't confuse host and target pointers (we'll get +// compiler errors if we try and cast between them). +// * like void*, no pointer arithmetic or dereferencing is allowed +// * like TADDR, can be used to construct any __DPtr / __VPtr instance +// * representation is the same as a void* (for marshalling / casting) +// +// One way in which __VoidPtr is unlike void* is that it can't be cast to +// pointer or integer types. On the one hand, this is a good thing as it forces +// us to keep target pointers separate from other data types. On the other hand +// in practice this means we have to use dac_cast in places where we used +// to use a (TADDR) cast. Unfortunately C++ provides us no way to allow the +// explicit cast to primitive types without also allowing implicit conversions. +// +// This is very similar in spirit to TADDR. The primary difference is that +// PTR_VOID has pointer semantics, where TADDR has integer semantics. When +// dacizing uses of void* to TADDR, casts must be inserted everywhere back to +// pointer types. If we switch a use of TADDR to PTR_VOID, those casts in +// DACCESS_COMPILE regions no longer compile (see above). Also, TADDR supports +// pointer arithmetic, but that might not be necessary (could use PTR_BYTE +// instead etc.). Ideally we'd probably have just one type for this purpose +// (named TADDR but with the semantics of PTR_VOID), but outright conversion +// would require too much work. +// + +template <> +class __DPtr : public __ComparableTPtrBase +{ +public: + __DPtr(void) : __ComparableTPtrBase() {} + __DPtr(TADDR addr) : __ComparableTPtrBase(addr) {} + + // Note, unlike __DPtr, this ctor form is not explicit. We allow implicit + // conversions from any pointer type (just like for void*). + __DPtr(__TPtrBase addr) + { + m_addr = addr.GetAddr(); + } + + // Like TPtrBase, VoidPtrs can also be created impicitly from all GlobalPtrs + template + __DPtr(__GlobalPtr globalPtr) + { + m_addr = globalPtr.GetAddr(); + } + + // Note, unlike __DPtr, there is no explicit conversion from host pointer + // types. Since void* cannot be marshalled, there is no such thing as + // a void* DAC instance in the host. + + // Also, we don't want an implicit conversion to TADDR because then the + // compiler will allow pointer arithmetic (which it wouldn't allow for + // void*). Instead, callers can use dac_cast if they want. + + // Note, unlike __DPtr, any pointer type can be assigned to a __DPtr + // This is to mirror the assignability of any pointer type to a void* + __DPtr& operator=(const __TPtrBase& ptr) + { + m_addr = ptr.GetAddr(); + return *this; + } + __DPtr& operator=(TADDR addr) + { + m_addr = addr; + return *this; + } + + // note, no marshalling operators (type* conversion, operator ->, operator*) + // A void* can't be marshalled because we don't know how much to copy + + // PTR_Void can be compared to any other pointer type (because conceptually, + // any other pointer type should be implicitly convertible to void*) + using __ComparableTPtrBase::operator==; + using __ComparableTPtrBase::operator!=; + using __ComparableTPtrBase::operator<; + using __ComparableTPtrBase::operator>; + using __ComparableTPtrBase::operator<=; + using __ComparableTPtrBase::operator>=; + bool operator==(TADDR addr) const + { + return m_addr == addr; + } + bool operator!=(TADDR addr) const + { + return m_addr != addr; + } +}; + +typedef __DPtr __VoidPtr; +typedef __VoidPtr PTR_VOID; +typedef DPTR(PTR_VOID) PTR_PTR_VOID; + +// For now we treat pointers to const and non-const void the same in DAC +// builds. In general, DAC is read-only anyway and so there isn't a danger of +// writing to these pointers. Also, the non-dac builds will ensure +// const-correctness. However, if we wanted to support true void* / const void* +// behavior, we could probably build the follow functionality by templating +// __VoidPtr: +// * A PTR_VOID would be implicitly convertable to PTR_CVOID +// * An explicit coercion (ideally const_cast) would be required to convert a +// PTR_CVOID to a PTR_VOID +// * Similarily, an explicit coercion would be required to convert a cost PTR +// type (eg. PTR_CBYTE) to a PTR_VOID. +typedef __VoidPtr PTR_CVOID; + + +// The special empty ctor declared here allows the whole +// class hierarchy to be instantiated easily by the +// external access code. The actual class body will be +// read externally so no members should be initialized. + +// Safe access for retrieving the target address of a PTR. +#define PTR_TO_TADDR(ptr) ((ptr).GetAddr()) + +#define GFN_TADDR(name) (DacGlobalBase() + g_dacGlobals.fn__ ## name) + +// ROTORTODO - g++ 3 doesn't like the use of the operator& in __GlobalVal +// here. Putting GVAL_ADDR in to get things to compile while I discuss +// this matter with the g++ authors. + +#define GVAL_ADDR(g) \ + ((g).operator&()) + +// +// References to class static and global data. +// These all need to be redirected through the global +// data table. +// + +#define _SPTR_DECL(acc_type, store_type, var) \ + static __GlobalPtr< acc_type, store_type > var +#define _SPTR_IMPL(acc_type, store_type, cls, var) \ + __GlobalPtr< acc_type, store_type > cls::var(&g_dacGlobals.cls##__##var) +#define _SPTR_IMPL_INIT(acc_type, store_type, cls, var, init) \ + __GlobalPtr< acc_type, store_type > cls::var(&g_dacGlobals.cls##__##var) +#define _SPTR_IMPL_NS(acc_type, store_type, ns, cls, var) \ + __GlobalPtr< acc_type, store_type > cls::var(&g_dacGlobals.ns##__##cls##__##var) +#define _SPTR_IMPL_NS_INIT(acc_type, store_type, ns, cls, var, init) \ + __GlobalPtr< acc_type, store_type > cls::var(&g_dacGlobals.ns##__##cls##__##var) + +#define _GPTR_DECL(acc_type, store_type, var) \ + extern __GlobalPtr< acc_type, store_type > var +#define _GPTR_IMPL(acc_type, store_type, var) \ + __GlobalPtr< acc_type, store_type > var(&g_dacGlobals.dac__##var) +#define _GPTR_IMPL_INIT(acc_type, store_type, var, init) \ + __GlobalPtr< acc_type, store_type > var(&g_dacGlobals.dac__##var) + +#define SVAL_DECL(type, var) \ + static __GlobalVal< type > var +#define SVAL_IMPL(type, cls, var) \ + __GlobalVal< type > cls::var(&g_dacGlobals.cls##__##var) +#define SVAL_IMPL_INIT(type, cls, var, init) \ + __GlobalVal< type > cls::var(&g_dacGlobals.cls##__##var) +#define SVAL_IMPL_NS(type, ns, cls, var) \ + __GlobalVal< type > cls::var(&g_dacGlobals.ns##__##cls##__##var) +#define SVAL_IMPL_NS_INIT(type, ns, cls, var, init) \ + __GlobalVal< type > cls::var(&g_dacGlobals.ns##__##cls##__##var) + +#define GVAL_DECL(type, var) \ + extern __GlobalVal< type > var +#define GVAL_IMPL(type, var) \ + __GlobalVal< type > var(&g_dacGlobals.dac__##var) +#define GVAL_IMPL_INIT(type, var, init) \ + __GlobalVal< type > var(&g_dacGlobals.dac__##var) + +#define GARY_DECL(type, var, size) \ + extern __GlobalArray< type, size > var +#define GARY_IMPL(type, var, size) \ + __GlobalArray< type, size > var(&g_dacGlobals.dac__##var) + +// Translation from a host pointer back to the target address +// that was used to retrieve the data for the host pointer. +#define PTR_HOST_TO_TADDR(host) DacGetTargetAddrForHostAddr(host, true) + +// Translation from a host interior pointer back to the corresponding +// target address. The host address must reside within a previously +// retrieved instance. +#define PTR_HOST_INT_TO_TADDR(host) DacGetTargetAddrForHostInteriorAddr(host, true) + +// Construct a pointer to a member of the given type. +#define PTR_HOST_MEMBER_TADDR(type, host, memb) \ + (PTR_HOST_TO_TADDR(host) + (TADDR)offsetof(type, memb)) + +// in the DAC build this is still typed TADDR, but in the runtime +// build it preserves the member type. +#define PTR_HOST_MEMBER(type, host, memb) \ + (PTR_HOST_TO_TADDR(host) + (TADDR)offsetof(type, memb)) + +// Construct a pointer to a member of the given type given an interior +// host address. +#define PTR_HOST_INT_MEMBER_TADDR(type, host, memb) \ + (PTR_HOST_INT_TO_TADDR(host) + (TADDR)offsetof(type, memb)) + +#define PTR_TO_MEMBER_TADDR(type, ptr, memb) \ + (PTR_TO_TADDR(ptr) + (TADDR)offsetof(type, memb)) + +// in the DAC build this is still typed TADDR, but in the runtime +// build it preserves the member type. +#define PTR_TO_MEMBER(type, ptr, memb) \ + (PTR_TO_TADDR(ptr) + (TADDR)offsetof(type, memb)) + +// Constructs an arbitrary data instance for a piece of +// memory in the target. +#define PTR_READ(addr, size) \ + DacInstantiateTypeByAddress(addr, size, true) + +// This value is used to intiailize target pointers to NULL. We want this to be TADDR type +// (as opposed to, say, __TPtrBase) so that it can be used in the non-explicit ctor overloads, +// eg. as an argument default value. +// We can't always just use NULL because that's 0 which (in C++) can be any integer or pointer +// type (causing an ambiguous overload compiler error when used in explicit ctor forms). +#define PTR_NULL ((TADDR)0) + +// Provides an empty method implementation when compiled +// for DACCESS_COMPILE. For example, use to stub out methods needed +// for vtable entries but otherwise unused. +// Note that these functions are explicitly NOT marked SUPPORTS_DAC so that we'll get a +// DacCop warning if any calls to them are detected. +// @dbgtodo rbyers: It's probably almost always wrong to call any such function, so +// we should probably throw a better error (DacNotImpl), and ideally mark the function +// DECLSPEC_NORETURN so we don't have to deal with fabricating return values and we can +// get compiler warnings (unreachable code) anytime functions marked this way are called. +#define DAC_EMPTY() { LEAF_CONTRACT; } +#define DAC_EMPTY_ERR() { LEAF_CONTRACT; DacError(E_UNEXPECTED); } +#define DAC_EMPTY_RET(retVal) { LEAF_CONTRACT; DacError(E_UNEXPECTED); return retVal; } +#define DAC_UNEXPECTED() { LEAF_CONTRACT; DacError_NoRet(E_UNEXPECTED); } + +#endif // __cplusplus + +HRESULT DacGetTargetAddrForHostAddr(const void* ptr, TADDR * pTADDR); + +// Implementation details for dac_cast, should never be accessed directly. +// See code:dac_cast for details and discussion. +namespace dac_imp +{ + //--------------------------------------------- + // Conversion to TADDR + + // Forward declarations. + template + struct conversionHelper; + + template + TADDR getTaddr(T&& val); + + // Helper structs to get the target address of specific types + + // This non-specialized struct handles all instances of asTADDR that don't + // take partially-specialized arguments. + template + struct conversionHelper + { + inline static TADDR asTADDR(__TPtrBase const & tptr) + { return PTR_TO_TADDR(tptr); } + + inline static TADDR asTADDR(TADDR addr) + { return addr; } + }; + + // Handles + template + struct conversionHelper + { + inline static TADDR asTADDR(TypeT * src) + { + TADDR addr = 0; + if (DacGetTargetAddrForHostAddr(src, &addr) != S_OK) + addr = DacGetTargetAddrForHostInteriorAddr(src, true); + return addr; + } + }; + + template + struct conversionHelper<__GlobalPtr const & > + { + inline static TADDR asTADDR(__GlobalPtr const & gptr) + { return PTR_TO_TADDR(gptr); } + }; + + // It is an error to try dac_cast on a __GlobalVal or a __GlobalArray. + template + struct conversionHelper< __GlobalVal const & > + { + inline static TADDR asTADDR(__GlobalVal const & gval) + { static_assert(false, "Cannot use dac_cast on a __GlobalVal; first you must get its address using the '&' operator."); } + }; + + template + struct conversionHelper< __GlobalArray const & > + { + inline static TADDR asTADDR(__GlobalArray const & garr) + { static_assert(false, "Cannot use dac_cast on a __GlobalArray; first you must get its address using the '&' operator."); } + }; + + // This is the main helper function, and it delegates to the above helper functions. + // NOTE: this works because of C++0x reference collapsing rules for rvalue reference + // arguments in template functions. + template + TADDR getTaddr(T&& val) + { return conversionHelper::asTADDR(val); } + + //--------------------------------------------- + // Conversion to DAC instance + + // Helper class to instantiate DAC instances from a TADDR + // The default implementation assumes we want to create an instance of a PTR type + template + struct makeDacInst + { + // First constructing a __TPtrBase and then constructing the target type + // ensures that the target type can construct itself from a __TPtrBase. + // This also prevents unknown user conversions from producing incorrect + // results (since __TPtrBase can only be constructed from TADDR values). + static inline T fromTaddr(TADDR addr) + { return T(__TPtrBase(addr)); } + }; + + // Specialization for creating TADDRs from TADDRs. + template<> struct makeDacInst + { + static inline TADDR fromTaddr(TADDR addr) { return addr; } + }; + + // Partial specialization for creating host instances. + template + struct makeDacInst + { + static inline T * fromTaddr(TADDR addr) + { return makeDacInst::fromTaddr(addr); } + }; + + /* + struct Yes { char c[2]; }; + struct No { char c; }; + Yes& HasTPtrBase(__TPtrBase const *, ); + No& HasTPtrBase(...); + + template + typename rh::std::enable_if< + sizeof(HasTPtrBase(typename rh::std::remove_reference::type *)) == sizeof(Yes), + T>::type + makeDacInst(TADDR addr) + */ + +} // namespace dac_imp + +// DacCop in-line exclusion mechanism + +// Warnings - official home is DacCop\Shared\Warnings.cs, but we want a way for users to indicate +// warning codes in a way that is descriptive to readers (not just code numbers). The names here +// don't matter - DacCop just looks at the value +enum DacCopWarningCode +{ + // General Rules + FieldAccess = 1, + PointerArith = 2, + PointerComparison = 3, + InconsistentMarshalling = 4, + CastBetweenAddressSpaces = 5, + CastOfMarshalledType = 6, + VirtualCallToNonVPtr = 7, + UndacizedGlobalVariable = 8, + + // Function graph related + CallUnknown = 701, + CallNonDac = 702, + CallVirtualUnknown = 704, + CallVirtualNonDac = 705, +}; + +// DACCOP_IGNORE is a mechanism to suppress DacCop violations from within the source-code. +// See the DacCop wiki for guidance on how best to use this: http://mswikis/clr/dev/Pages/DacCop.aspx +// +// DACCOP_IGNORE will suppress a DacCop violation for the following (non-compound) statement. +// For example: +// // The "dual-mode DAC problem" occurs in a few places where a class is used both +// // in the host, and marshalled from the target ... +// DACCOP_IGNORE(CastBetweenAddressSpaces,"SBuffer has the dual-mode DAC problem"); +// TADDR bufAddr = (TADDR)m_buffer; +// +// A call to DACCOP_IGNORE must occur as it's own statement, and can apply only to following +// single-statements (not to compound statement blocks). Occasionally it is necessary to hoist +// violation-inducing code out to its own statement (e.g., if it occurs in the conditional of an +// if). +// +// Arguments: +// code: a literal value from DacCopWarningCode indicating which violation should be suppressed. +// szReasonString: a short description of why this exclusion is necessary. This is intended just +// to help readers of the code understand the source of the problem, and what would be required +// to fix it. More details can be provided in comments if desired. +// +inline void DACCOP_IGNORE(DacCopWarningCode code, const char * szReasonString) +{ + UNREFERENCED_PARAMETER(code); + UNREFERENCED_PARAMETER(szReasonString); + // DacCop detects calls to this function. No implementation is necessary. +} + +#else // !DACCESS_COMPILE + +// +// This version of the macros turns into normal pointers +// for unmodified in-proc compilation. + +// ******************************************************* +// !!!!!!!!!!!!!!!!!!!!!!!!!NOTE!!!!!!!!!!!!!!!!!!!!!!!!!! +// +// Please search this file for the type name to find the +// DAC versions of these definitions +// +// !!!!!!!!!!!!!!!!!!!!!!!!!NOTE!!!!!!!!!!!!!!!!!!!!!!!!!! +// ******************************************************* + +// Declare TADDR as a non-pointer type so that arithmetic +// can be done on it directly, as with the DACCESS_COMPILE definition. +// This also helps expose pointer usage that may need to be changed. +typedef UIntNative TADDR; + +typedef void* PTR_VOID; +typedef void** PTR_PTR_VOID; + +#define DPTR(type) type* +#define ArrayDPTR(type) type* +#define SPTR(type) type* +#define VPTR(type) type* +#define S8PTR(type) type* +#define S8PTRMAX(type, maxChars) type* +#define S16PTR(type) type* +#define S16PTRMAX(type, maxChars) type* + +#define PTR_TO_TADDR(ptr) (reinterpret_cast(ptr)) +#define GFN_TADDR(name) (reinterpret_cast(&(name))) + +#define GVAL_ADDR(g) (&(g)) +#define _SPTR_DECL(acc_type, store_type, var) \ + static store_type var +#define _SPTR_IMPL(acc_type, store_type, cls, var) \ + store_type cls::var +#define _SPTR_IMPL_INIT(acc_type, store_type, cls, var, init) \ + store_type cls::var = init +#define _SPTR_IMPL_NS(acc_type, store_type, ns, cls, var) \ + store_type cls::var +#define _SPTR_IMPL_NS_INIT(acc_type, store_type, ns, cls, var, init) \ + store_type cls::var = init +#define _GPTR_DECL(acc_type, store_type, var) \ + extern store_type var +#define _GPTR_IMPL(acc_type, store_type, var) \ + store_type var +#define _GPTR_IMPL_INIT(acc_type, store_type, var, init) \ + store_type var = init +#define SVAL_DECL(type, var) \ + static type var +#define SVAL_IMPL(type, cls, var) \ + type cls::var +#define SVAL_IMPL_INIT(type, cls, var, init) \ + type cls::var = init +#define SVAL_IMPL_NS(type, ns, cls, var) \ + type cls::var +#define SVAL_IMPL_NS_INIT(type, ns, cls, var, init) \ + type cls::var = init +#define GVAL_DECL(type, var) \ + extern type var +#define GVAL_IMPL(type, var) \ + type var +#define GVAL_IMPL_INIT(type, var, init) \ + type var = init +#define GARY_DECL(type, var, size) \ + extern type var[size] +#define GARY_IMPL(type, var, size) \ + type var[size] +#define PTR_HOST_TO_TADDR(host) (reinterpret_cast(host)) +#define PTR_HOST_INT_TO_TADDR(host) ((TADDR)(host)) +#define VPTR_HOST_VTABLE_TO_TADDR(host) (reinterpret_cast(host)) +#define PTR_HOST_MEMBER_TADDR(type, host, memb) (reinterpret_cast(&(host)->memb)) +#define PTR_HOST_MEMBER(type, host, memb) (&((host)->memb)) +#define PTR_HOST_INT_MEMBER_TADDR(type, host, memb) ((TADDR)&(host)->memb) +#define PTR_TO_MEMBER_TADDR(type, ptr, memb) (reinterpret_cast(&((ptr)->memb))) +#define PTR_TO_MEMBER(type, ptr, memb) (&((ptr)->memb)) +#define PTR_READ(addr, size) (reinterpret_cast(addr)) + +#define PTR_NULL NULL + +#define DAC_EMPTY() +#define DAC_EMPTY_ERR() +#define DAC_EMPTY_RET(retVal) +#define DAC_UNEXPECTED() + +#define DACCOP_IGNORE(warningCode, reasonString) + +#endif // !DACCESS_COMPILE + +//---------------------------------------------------------------------------- +// dac_cast +// Casting utility, to be used for casting one class pointer type to another. +// Use as you would use static_cast +// +// dac_cast is designed to act just as static_cast does when +// dealing with pointers and their DAC abstractions. Specifically, +// it handles these coversions: +// +// dac_cast(SourceTypeVal) +// +// where TargetType <- SourceTypeVal are +// +// ?PTR(Tgt) <- TADDR - Create PTR type (DPtr etc.) from TADDR +// ?PTR(Tgt) <- ?PTR(Src) - Convert one PTR type to another +// ?PTR(Tgt) <- Src * - Create PTR type from dac host object instance +// TADDR <- ?PTR(Src) - Get TADDR of PTR object (DPtr etc.) +// TADDR <- Src * - Get TADDR of dac host object instance +// +// Note that there is no direct convertion to other host-pointer types (because we don't +// know if you want a DPTR or VPTR etc.). However, due to the implicit DAC conversions, +// you can just use dac_cast and assign that to a Foo*. +// +// The beauty of this syntax is that it is consistent regardless +// of source and target casting types. You just use dac_cast +// and the partial template specialization will do the right thing. +// +// One important thing to realise is that all "Foo *" types are +// assumed to be pointers to host instances that were marshalled by DAC. This should +// fail at runtime if it's not the case. +// +// Some examples would be: +// +// - Host pointer of one type to a related host pointer of another +// type, i.e., MethodDesc * <-> InstantiatedMethodDesc * +// Syntax: with MethodDesc *pMD, InstantiatedMethodDesc *pInstMD +// pInstMd = dac_cast(pMD) +// pMD = dac_cast(pInstMD) +// +// - (D|V)PTR of one encapsulated pointer type to a (D|V)PTR of +// another type, i.e., PTR_AppDomain <-> PTR_BaseDomain +// Syntax: with PTR_AppDomain pAD, PTR_BaseDomain pBD +// dac_cast(pBD) +// dac_cast(pAD) +// +// Example comparsions of some old and new syntax, where +// h is a host pointer, such as "Foo *h;" +// p is a DPTR, such as "PTR_Foo p;" +// +// PTR_HOST_TO_TADDR(h) ==> dac_cast(h) +// PTR_TO_TADDR(p) ==> dac_cast(p) +// PTR_Foo(PTR_HOST_TO_TADDR(h)) ==> dac_cast(h) +// +//---------------------------------------------------------------------------- +template +inline Tgt dac_cast(Src src) +{ +#ifdef DACCESS_COMPILE + // In DAC builds, first get a TADDR for the source, then create the + // appropriate destination instance. + TADDR addr = dac_imp::getTaddr(src); + return dac_imp::makeDacInst::fromTaddr(addr); +#else // !DACCESS_COMPILE + // In non-DAC builds, dac_cast is the same as a C-style cast because we need to support: + // - casting away const + // - conversions between pointers and TADDR + // Perhaps we should more precisely restrict it's usage, but we get the precise + // restrictions in DAC builds, so it wouldn't buy us much. + return (Tgt)(src); +#endif // !DACCESS_COMPILE +} + +//---------------------------------------------------------------------------- +// +// Convenience macros which work for either mode. +// +//---------------------------------------------------------------------------- + +#define SPTR_DECL(type, var) _SPTR_DECL(type*, PTR_##type, var) +#define SPTR_IMPL(type, cls, var) _SPTR_IMPL(type*, PTR_##type, cls, var) +#define SPTR_IMPL_INIT(type, cls, var, init) _SPTR_IMPL_INIT(type*, PTR_##type, cls, var, init) +#define SPTR_IMPL_NS(type, ns, cls, var) _SPTR_IMPL_NS(type*, PTR_##type, ns, cls, var) +#define SPTR_IMPL_NS_INIT(type, ns, cls, var, init) _SPTR_IMPL_NS_INIT(type*, PTR_##type, ns, cls, var, init) +#define GPTR_DECL(type, var) _GPTR_DECL(type*, PTR_##type, var) +#define GPTR_IMPL(type, var) _GPTR_IMPL(type*, PTR_##type, var) +#define GPTR_IMPL_INIT(type, var, init) _GPTR_IMPL_INIT(type*, PTR_##type, var, init) + + +// If you want to marshal a single instance of an ArrayDPtr over to the host and +// return a pointer to it, you can use this function. However, this is unsafe because +// users of value may assume they can do pointer arithmetic on it. This is exactly +// the bugs ArrayDPtr is designed to prevent. See code:__ArrayDPtr for details. +template +inline type* DacUnsafeMarshalSingleElement( ArrayDPTR(type) arrayPtr ) +{ + return (DPTR(type))(arrayPtr); +} + +typedef DPTR(Int8) PTR_Int8; +typedef DPTR(Int16) PTR_Int16; +typedef DPTR(Int32) PTR_Int32; +typedef DPTR(Int64) PTR_Int64; +typedef ArrayDPTR(UInt8) PTR_UInt8; +typedef DPTR(PTR_UInt8) PTR_PTR_UInt8; +typedef DPTR(PTR_PTR_UInt8) PTR_PTR_PTR_UInt8; +typedef DPTR(UInt16) PTR_UInt16; +typedef DPTR(UInt32) PTR_UInt32; +typedef DPTR(UInt64) PTR_UInt64; +typedef DPTR(UIntNative) PTR_UIntNative; + +typedef DPTR(size_t) PTR_size_t; + +typedef UInt8 Code; +typedef DPTR(Code) PTR_Code; +typedef DPTR(PTR_Code) PTR_PTR_Code; + +#if defined(DACCESS_COMPILE) && defined(DAC_CLR_ENVIRONMENT) +#include +#include +//#include +#endif // defined(DACCESS_COMPILE) && defined(DAC_CLR_ENVIRONMENT) + +//---------------------------------------------------------------------------- +// PCODE is pointer to any executable code. +typedef TADDR PCODE; +typedef DPTR(TADDR) PTR_PCODE; + +//---------------------------------------------------------------------------- +// +// The access code compile must compile data structures that exactly +// match the real structures for access to work. The access code +// doesn't want all of the debugging validation code, though, so +// distinguish between _DEBUG, for declaring general debugging data +// and always-on debug code, and _DEBUG_IMPL, for debugging code +// which will be disabled when compiling for external access. +// +//---------------------------------------------------------------------------- + +#if !defined(_DEBUG_IMPL) && defined(_DEBUG) && !defined(DACCESS_COMPILE) +#define _DEBUG_IMPL 1 +#endif + +// Helper macro for tracking EnumMemoryRegions progress. +#if 0 +#define EMEM_OUT(args) DacWarning args +#else // !0 +#define EMEM_OUT(args) +#endif // !0 + +// TARGET_CONSISTENCY_CHECK represents a condition that should not fail unless the DAC target is corrupt. +// This is in contrast to ASSERTs in DAC infrastructure code which shouldn't fail regardless of the memory +// read from the target. At the moment we treat these the same, but in the future we will want a mechanism +// for disabling just the target consistency checks (eg. for tests that intentionally use corrupted targets). +// @dbgtodo rbyers: Separating asserts and target consistency checks is tracked by DevDiv Bugs 31674 +#define TARGET_CONSISTENCY_CHECK(expr,msg) _ASSERTE_MSG(expr,msg) + +#ifdef DACCESS_COMPILE +#define NO_DAC() static_assert(false, "Cannot use this method in builds DAC: " __FILE__ ":" __LINE__) +#else +#define NO_DAC() do {} while (0) +#endif + +#endif // !__daccess_h__ diff --git a/src/Native/Runtime/dllmain.cpp b/src/Native/Runtime/dllmain.cpp new file mode 100644 index 00000000000..12295eab337 --- /dev/null +++ b/src/Native/Runtime/dllmain.cpp @@ -0,0 +1,62 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "gcrhinterface.h" + +#include "assert.h" +#include "slist.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "event.h" + +bool InitDLL(HANDLE hPalInstance); +bool UninitDLL(HANDLE hPalInstance); +void DllThreadAttach(HANDLE hPalInstance); +void DllThreadDetach(); + +EXTERN_C UInt32_BOOL WINAPI RtuDllMain(HANDLE hPalInstance, UInt32 dwReason, void* pvReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + { + STARTUP_TIMELINE_EVENT(PROCESS_ATTACH_BEGIN); + + if (!InitDLL(hPalInstance)) + return FALSE; + + DllThreadAttach(hPalInstance); + STARTUP_TIMELINE_EVENT(PROCESS_ATTACH_COMPLETE); + return TRUE; + } + break; + + case DLL_PROCESS_DETACH: + UninitDLL(hPalInstance); + break; + + case DLL_THREAD_ATTACH: + DllThreadAttach(hPalInstance); + break; + + case DLL_THREAD_DETACH: + DllThreadDetach(); + break; + + } + + return TRUE; +} + + + diff --git a/src/Native/Runtime/eetype.cpp b/src/Native/Runtime/eetype.cpp new file mode 100644 index 00000000000..6259a7518de --- /dev/null +++ b/src/Native/Runtime/eetype.cpp @@ -0,0 +1,149 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "assert.h" +#include "rhbinder.h" +#include "eetype.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#endif + +#pragma warning(disable:4127) // C4127: conditional expression is constant + +// Validate an EEType extracted from an object. +bool EEType::Validate(bool assertOnFail /* default: true */) +{ +#define REPORT_FAILURE() do { if (assertOnFail) { ASSERT_UNCONDITIONALLY("EEType::Validate check failed"); } return false; } while (false) + + // Deal with the most common case of a bad pointer without an exception. + if (this == NULL) + REPORT_FAILURE(); + + // EEType structures should be at least pointer aligned. + if (dac_cast(this) & (sizeof(TADDR)-1)) + REPORT_FAILURE(); + + // Verify object size is bigger than min_obj_size + size_t minObjSize = get_BaseSize(); + if (get_ComponentSize() != 0) + { + // If it is an array, we will align the size to the nearest pointer alignment, even if there are + // zero elements. Our strings take advantage of this. + minObjSize = (size_t)ALIGN_UP(minObjSize, sizeof(TADDR)); + } + if (minObjSize < (3 * sizeof(TADDR))) + REPORT_FAILURE(); + + switch (get_Kind()) + { + case CanonicalEEType: + { + // If the parent type is NULL this had better look like Object. + if (m_RelatedType.m_pBaseType == NULL) + { + if (IsRelatedTypeViaIAT() || + get_IsValueType() || + HasFinalizer() || + HasReferenceFields() || + IsRuntimeAllocated() || + HasGenericVariance()) + { + REPORT_FAILURE(); + } + } + break; + } + + case ClonedEEType: + { + // Cloned types must have a related type. + if (m_RelatedType.m_ppCanonicalTypeViaIAT == NULL) + REPORT_FAILURE(); + + // Either we're dealing with a clone of String or a generic type. We can tell the difference based + // on the component size. + switch (get_ComponentSize()) + { + case 0: + { + // Cloned generic type. + if (!IsRelatedTypeViaIAT() || + IsRuntimeAllocated()) + { + REPORT_FAILURE(); + } + break; + } + + case 2: + { + // Cloned string. + if (!IsRelatedTypeViaIAT() || + get_IsValueType() || + HasFinalizer() || + HasReferenceFields() || + IsRuntimeAllocated() || + HasGenericVariance()) + { + REPORT_FAILURE(); + } + + break; + } + + default: + // Apart from cloned strings we don't expected cloned types to have a component size. + REPORT_FAILURE(); + } + break; + } + + case ParameterizedEEType: + { + // The only parameter EETypes that can exist on the heap are arrays + + // Array types must have a related type. + if (m_RelatedType.m_pRelatedParameterType == NULL) + REPORT_FAILURE(); + + // Component size cannot be zero in this case. + if (get_ComponentSize() == 0) + REPORT_FAILURE(); + + if (get_IsValueType() || + HasFinalizer() || + IsRuntimeAllocated() || + HasGenericVariance()) + { + REPORT_FAILURE(); + } + + break; + } + + case GenericTypeDefEEType: + { + // We should never see uninstantiated generic type definitions here + // since we should never construct an object instance around them. + REPORT_FAILURE(); + } + + default: + // Should be unreachable. + REPORT_FAILURE(); + } + +#undef REPORT_FAILURE + + return true; +} diff --git a/src/Native/Runtime/eetype.h b/src/Native/Runtime/eetype.h new file mode 100644 index 00000000000..c21fb998403 --- /dev/null +++ b/src/Native/Runtime/eetype.h @@ -0,0 +1,639 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// Fundamental runtime type representation + +#pragma warning(push) +#pragma warning(disable:4200) // nonstandard extension used : zero-sized array in struct/union +//------------------------------------------------------------------------------------------------- +// Forward declarations + +class MdilModule; +class EEType; +class OptionalFields; + +//------------------------------------------------------------------------------------------------- +// Array of these represents the interfaces implemented by a type + +class EEInterfaceInfo +{ + friend class MdilModule; + + public: + EEType * GetInterfaceEEType() + { + return ((UIntTarget)m_pInterfaceEEType & ((UIntTarget)1)) ? + *(EEType**)((UIntTarget)m_ppInterfaceEETypeViaIAT & ~((UIntTarget)1)) : + m_pInterfaceEEType; + } + + // If the interface type is referenced indirectly (via the IAT) update this info to a direct reference. + // This is only possible at runtime once the IAT has been updated and is currently used only for generics, + // when unifying a generic instantiation and cutting any arbitrary dependencies to the module which first + // published this instantiation. + void Flatten() + { + m_pInterfaceEEType = GetInterfaceEEType(); + } + +#ifndef RHDUMP + private: +#endif + union + { + EEType * m_pInterfaceEEType; // m_uFlags == InterfaceFlagNormal + EEType ** m_ppInterfaceEETypeViaIAT; // m_uFlags == InterfaceViaIATFlag +#if defined(RHDUMP) || defined(BINDER) + UIntTarget m_ptrVal; // ensure this structure is the right size in cross-build scenarios +#endif // defined(RHDUMP) || defined(BINDER) + }; +}; + +//------------------------------------------------------------------------------------------------- +class EEInterfaceInfoMap +{ + friend class EEType; + + public: + EEInterfaceInfoMap(EEInterfaceInfoMap const & other) + : m_pMap(m_pMap), m_cMap(m_cMap) + { + UNREFERENCED_PARAMETER(other); + } + + EEInterfaceInfo & operator[](UInt16 idx); + + typedef EEInterfaceInfo * Iterator; + + UIntNative GetLength() + { return m_cMap; } + + Iterator Begin() + { return Iterator(m_pMap); } + + Iterator BeginAt(UInt16 idx) + { return Iterator(&operator[](idx)); } + + Iterator End() + { return Iterator(m_pMap + m_cMap); } + + EEInterfaceInfo * GetRawPtr() + { return m_pMap; } + + private: + EEInterfaceInfoMap(EEInterfaceInfo * pMap, UInt16 cMap) + : m_pMap(pMap), m_cMap(cMap) + {} + + EEInterfaceInfo * m_pMap; + UInt16 m_cMap; +}; + +//------------------------------------------------------------------------------------------------- +// use a non-compressed encoding for easier debugging for now... + +struct DispatchMapEntry +{ + UInt16 m_usInterfaceIndex; + UInt16 m_usInterfaceMethodSlot; + UInt16 m_usImplMethodSlot; +}; + +//------------------------------------------------------------------------------------------------- +// Represents the contributions that a type makes to its interface implementations. +class DispatchMap +{ + friend class CompactTypeBuilder; + friend class MdilModule; +#ifdef RHDUMP + friend struct Image; +#endif +private: + UInt32 m_entryCount; + DispatchMapEntry m_dispatchMap[0]; // at least one entry if any interfaces defined +public: + bool IsEmpty() + { return m_entryCount == 0; } + + size_t ComputeSize() + { return sizeof(m_entryCount) + sizeof(m_dispatchMap[0])*m_entryCount; } + + typedef DispatchMapEntry * Iterator; + + Iterator Begin() + { return &m_dispatchMap[0]; } + + Iterator End() + { return &m_dispatchMap[m_entryCount]; } +}; + +#if !defined(BINDER) && !defined(DACCESS_COMPILE) +//------------------------------------------------------------------------------------------------- +// The subset of CLR-style CorElementTypes that Redhawk knows about at runtime (just the primitives and a +// special case for ELEMENT_TYPE_ARRAY used to mark the System.Array EEType). +enum CorElementType +{ + ELEMENT_TYPE_END = 0x0, + + ELEMENT_TYPE_BOOLEAN = 0x2, + ELEMENT_TYPE_CHAR = 0x3, + ELEMENT_TYPE_I1 = 0x4, + ELEMENT_TYPE_U1 = 0x5, + ELEMENT_TYPE_I2 = 0x6, + ELEMENT_TYPE_U2 = 0x7, + ELEMENT_TYPE_I4 = 0x8, + ELEMENT_TYPE_U4 = 0x9, + ELEMENT_TYPE_I8 = 0xa, + ELEMENT_TYPE_U8 = 0xb, + ELEMENT_TYPE_R4 = 0xc, + ELEMENT_TYPE_R8 = 0xd, + + ELEMENT_TYPE_ARRAY = 0x14, + + ELEMENT_TYPE_I = 0x18, + ELEMENT_TYPE_U = 0x19, +}; +#endif // !BINDER && !DACCESS_COMPILE + +//------------------------------------------------------------------------------------------------- +// Support for encapsulating the location of fields in the EEType that have variable offsets or may be +// optional. +// +// The following enumaration gives symbolic names for these fields and is used with the GetFieldPointer() and +// GetFieldOffset() APIs. +enum EETypeField +{ + ETF_InterfaceMap, + ETF_Finalizer, + ETF_OptionalFieldsPtr, + ETF_NullableType, + ETF_SealedVirtualSlots, + ETF_DynamicTemplateType, + ETF_DynamicDispatchMap, +}; + +//------------------------------------------------------------------------------------------------- +// Fundamental runtime type representation +#ifndef RHDUMP +typedef DPTR(class EEType) PTR_EEType; +typedef DPTR(PTR_EEType) PTR_PTR_EEType; +typedef DPTR(class OptionalFields) PTR_OptionalFields; +typedef DPTR(PTR_OptionalFields) PTR_PTR_OptionalFields; +#endif // !RHDUMP +class EEType +{ + friend class AsmOffsets; + friend class MdilModule; + friend class MetaDataEngine; + friend class LimitedEEType; + +#ifdef RHDUMP +public: +#else +private: +#endif + struct RelatedTypeUnion + { + union + { + // Kinds.CanonicalEEType + EEType* m_pBaseType; + EEType** m_ppBaseTypeViaIAT; + + // Kinds.ClonedEEType + EEType** m_ppCanonicalTypeViaIAT; + + // Kinds.ParameterizedEEType + EEType* m_pRelatedParameterType; + EEType** m_ppRelatedParameterTypeViaIAT; + +#if defined(RHDUMP) || defined(BINDER) + UIntTarget m_ptrVal; // ensure this structure is the right size in cross-build scenarios +#endif // defined(RHDUMP) || defined(BINDER) + }; + }; + + UInt16 m_usComponentSize; + UInt16 m_usFlags; + UInt32 m_uBaseSize; + RelatedTypeUnion m_RelatedType; + UInt16 m_usNumVtableSlots; + UInt16 m_usNumInterfaces; + UInt32 m_uHashCode; + + TgtPTR_Void m_VTable[]; // make this explicit so the binder gets the right alignment + + // after the m_usNumVtableSlots vtable slots, we have m_usNumInterfaces slots of + // EEInterfaceInfo, and after that a couple of additional pointers based on whether the type is + // finalizable (the address of the finalizer code) or has optional fields (pointer to the compacted + // fields). + + enum Flags + { + // There are four kinds of EETypes, the three of them regular types that use the full EEType encoding + // plus a fourth kind used as a grab bag of unusual edge cases which are encoded in a smaller, + // simplified version of EEType. See LimitedEEType definition below. + EETypeKindMask = 0x0003, + + // This flag is set when m_pRelatedType is in a different module. In that case, m_pRelatedType + // actually points to a 'fake' EEType whose m_pRelatedType field lines up with an IAT slot in this + // module, which then points to the desired EEType. In other words, there is an extra indirection + // through m_pRelatedType to get to the related type in the other module. + RelatedTypeViaIATFlag = 0x0004, + + // This EEType represents a value type + ValueTypeFlag = 0x0008, + + // This EEType represents a type which requires finalization + HasFinalizerFlag = 0x0010, + + // This type contain gc pointers + HasPointersFlag = 0x0020, + + // This type instance was allocated at runtime (rather than being embedded in a module image) + RuntimeAllocatedFlag = 0x0040, + + // This type is generic and one or more of it's type parameters is co- or contra-variant. This only + // applies to interface and delegate types. + GenericVarianceFlag = 0x0080, + + // This type has optional fields present. + OptionalFieldsFlag = 0x0100, + + // This EEType represents an interface. + IsInterfaceFlag = 0x0200, + + // This type is generic. + IsGenericFlag = 0x0400, + + // We are storing a CorElementType in the upper bits for unboxing enums + CorElementTypeMask = 0xf800, + CorElementTypeShift = 11, + }; + +public: + + // These are flag values that are rarely set for types. If any of them are set then an optional field will + // be associated with the EEType to represent them. + enum RareFlags + { + // This type requires 8-byte alignment for its fields on certain platforms (only ARM currently). + RequiresAlign8Flag = 0x00000001, + + // Type implements ICastable to allow dynamic resolution of interface casts. + ICastableFlag = 0x00000002, + + // Type is an instantiation of Nullable. + IsNullableFlag = 0x00000004, + + // Nullable target type stashed in the EEType is indirected via the IAT. + NullableTypeViaIATFlag = 0x00000008, + + // This EEType was created by dynamic type loader + IsDynamicTypeFlag = 0x00000010, + + // This EEType has a Class Constructor + HasCctorFlag = 0x0000020, + + // This EEType has sealed vtable entries (note that this flag is only used for + // dynamically created types because they always have an optional field (hence the + // very explicit flag name). + IsDynamicTypeWithSealedVTableEntriesFlag = 0x00000040, + + // This EEType was constructed from a universal canonical template, and has + // its own dynamically created DispatchMap (does not use the DispatchMap of its template type) + HasDynamicallyAllocatedDispatchMapFlag = 0x00000080, + + // This EEType represents a structure that is an HFA (only ARM currently) + IsHFAFlag = 0x00000100, + }; + + // These masks and paddings have been chosen so that the ValueTypePadding field can always fit in a byte of data. + // if the alignment is 8 bytes or less. If the alignment is higher then there may be a need for more bits to hold + // the rest of the padding data. + // If paddings of greater than 7 bytes are necessary, then the high bits of the field represent that padding + enum ValueTypePaddingConstants + { + ValueTypePaddingLowMask = 0x7, + ValueTypePaddingHighMask = 0xFFFFFF00ul, + ValueTypePaddingMax = 0x07FFFFFF, + ValueTypePaddingHighShift = 8, + ValueTypePaddingAlignmentMask = 0xF8, + ValueTypePaddingAlignmentShift = 3, + }; + +public: + + enum Kinds + { + CanonicalEEType = 0x0000, + ClonedEEType = 0x0001, + ParameterizedEEType = 0x0002, + GenericTypeDefEEType = 0x0003, + }; + +#ifndef RHDUMP + UInt32 get_BaseSize() + { return m_uBaseSize; } + + UInt16 get_ComponentSize() + { return m_usComponentSize; } + + PTR_Code get_Slot(UInt16 slotNumber); + + PTR_PTR_Code get_SlotPtr(UInt16 slotNumber); + + PTR_Code get_SealedVirtualSlot(UInt16 slotNumber); + void set_SealedVirtualSlot(PTR_Code pValue, UInt16 slotNumber); + + Kinds get_Kind(); + + bool IsCloned() + { return get_Kind() == ClonedEEType; } + + bool IsRelatedTypeViaIAT() + { return ((m_usFlags & (UInt16)RelatedTypeViaIATFlag) != 0); } + + bool IsArray() + { return IsParameterizedType() && get_ParameterizedTypeShape() != 0; } + + bool IsPointerType() + { return IsParameterizedType() && get_ParameterizedTypeShape() == 0; } + + bool IsParameterizedType() + { return (get_Kind() == ParameterizedEEType); } + + bool IsGenericTypeDefinition() + { return (get_Kind() == GenericTypeDefEEType); } + + bool IsCanonical() + { return get_Kind() == CanonicalEEType; } + + bool IsInterface() + { return ((m_usFlags & (UInt16)IsInterfaceFlag) != 0); } + + EEType * get_CanonicalEEType(); + + EEType * get_BaseType(); + + EEType * get_RelatedParameterType(); + + // A parameterized type shape is 0 to indicate that it is a pointer type, + // and non-zero to indicate that it is an array type + UInt32 get_ParameterizedTypeShape() { return m_uBaseSize; } + + void set_RelatedParameterType(EEType * pParameterType); + + bool get_IsValueType() + { return ((m_usFlags & (UInt16)ValueTypeFlag) != 0); } + + bool HasFinalizer() + { + return (m_usFlags & HasFinalizerFlag) != 0; + } + + bool HasReferenceFields() + { + return (m_usFlags & HasPointersFlag) != 0; + } + + bool HasOptionalFields() + { + return (m_usFlags & OptionalFieldsFlag) != 0; + } + + bool IsEquivalentTo(EEType * pOtherEEType) + { + if (this == pOtherEEType) + return true; + + EEType * pThisEEType = this; + + if (pThisEEType->IsCloned()) + pThisEEType = pThisEEType->get_CanonicalEEType(); + + if (pOtherEEType->IsCloned()) + pOtherEEType = pOtherEEType->get_CanonicalEEType(); + + if (pThisEEType == pOtherEEType) + return true; + + if (pThisEEType->IsParameterizedType() && pOtherEEType->IsParameterizedType()) + { + return pThisEEType->get_RelatedParameterType()->IsEquivalentTo(pOtherEEType->get_RelatedParameterType()) && + pThisEEType->get_ParameterizedTypeShape() == pOtherEEType->get_ParameterizedTypeShape(); + } + + return false; + } + + // How many vtable slots are there? + UInt16 GetNumVtableSlots() + { return m_usNumVtableSlots; } + void SetNumVtableSlots(UInt16 usNumSlots) + { m_usNumVtableSlots = usNumSlots; } + + // How many entries are in the interface map after the vtable slots? + UInt16 GetNumInterfaces() + { return m_usNumInterfaces; } + + // Does this class (or its base classes) implement any interfaces? + bool HasInterfaces() + { return GetNumInterfaces() != 0; } + + EEInterfaceInfoMap GetInterfaceMap(); + + bool HasDispatchMap(); + + bool IsGeneric() + { return (m_usFlags & IsGenericFlag) != 0; } + +#ifndef BINDER + DispatchMap *GetDispatchMap(); + +#endif // !BINDER + + // Used only by GC initialization, this initializes the EEType used to mark free entries in the GC heap. + // It should be an array type with a component size of one (so the GC can easily size it as appropriate) + // and should be marked as not containing any references. The rest of the fields don't matter: the GC does + // not query them and the rest of the runtime will never hold a reference to free object. + inline void InitializeAsGcFreeType(); + + // Initialize an existing EEType as an array type with specific element type. This is another specialized + // method used only during the unification of generic instantiation types. It might need modification if + // needed in any other scenario. + inline void InitializeAsArrayType(EEType * pElementType, UInt32 baseSize); + +#ifdef DACCESS_COMPILE + bool DacVerify(); + static bool DacVerifyWorker(EEType* pThis); +#endif // DACCESS_COMPILE + + + // Transform a canonical type into a cloned type pointing to the given type as the canonical type. Used + // when unifying generic instantiation types. + inline void MakeClonedType(EEType ** ppCanonicalType); + + // If any part of this type is referenced indirectly (via IAT entries) resolve these references to direct + // pointers. This is only possible at runtime once the IAT has been updated and is currently used only for generics, + // when unifying a generic instantiation and cutting any arbitrary dependencies to the module which first + // published this instantiation. + inline void Flatten(); + + // Mark or determine that a type instance was allocated at runtime (currently only used for unification of + // generic instantiations). This is sometimes important for memory management or debugging purposes. + bool IsRuntimeAllocated() + { return (m_usFlags & RuntimeAllocatedFlag) != 0; } + void SetRuntimeAllocated() + { m_usFlags |= RuntimeAllocatedFlag; } + + // Mark or determine that a type is generic and one or more of it's type parameters is co- or + // contra-variant. This only applies to interface and delegate types. + bool HasGenericVariance() + { return (m_usFlags & GenericVarianceFlag) != 0; } + void SetHasGenericVariance() + { m_usFlags |= GenericVarianceFlag; } + + // Is this type specialized System.Object? We use the fact that only System.Object and interfaces have no + // parent type. + bool IsSystemObject() + { return !IsParameterizedType() && !IsInterface() && get_BaseType() == NULL; } + + CorElementType GetCorElementType() + { return (CorElementType)((m_usFlags & CorElementTypeMask) >> CorElementTypeShift); } + + // Is this type specifically System.Array? + bool IsSystemArray() + { return GetCorElementType() == ELEMENT_TYPE_ARRAY; } + +#ifndef BINDER + // Determine whether a type requires 8-byte alignment for its fields (required only on certain platforms, + // only ARM so far). + bool RequiresAlign8() + { return (get_RareFlags() & RequiresAlign8Flag) != 0; } + + // Determine whether a type supports ICastable. + bool IsICastable() + { return (get_RareFlags() & ICastableFlag) != 0; } + + // Retrieve the address of the method that implements ICastable.IsInstanceOfInterface for + // ICastable types. + inline PTR_Code get_ICastableIsInstanceOfInterfaceMethod(); + + // Retrieve the vtable slot number of the method that implements ICastable.GetImplType for ICastable + // types. + inline PTR_Code get_ICastableGetImplTypeMethod(); + + // Determine whether a type is an instantiation of Nullable. + bool IsNullable() + { return (get_RareFlags() & IsNullableFlag) != 0; } + + // Indicates whether the target type associated with a Nullable instantiation is indirected via the + // IAT. + bool IsNullableTypeViaIAT() + { return (get_RareFlags() & NullableTypeViaIATFlag) != 0; } + + // Retrieve the value type T from a Nullable. + EEType * GetNullableType(); + + // Set the value of type T for dynamic instantiations of Nullable + void SetNullableType(EEType * pEEType); + + // Retrieve the offset of the value embedded in a Nullable. + UInt8 GetNullableValueOffset(); + + // Determine whether a type was created by dynamic type loader + bool IsDynamicType() + { return (get_RareFlags() & IsDynamicTypeFlag) != 0; } + + // Determine whether a *dynamic* type has a dynamically allocated DispatchMap + bool HasDynamicallyAllocatedDispatchMap() + { return (get_RareFlags() & HasDynamicallyAllocatedDispatchMapFlag) != 0; } + + // Retrieve template used to create the dynamic type + EEType * get_DynamicTemplateType(); + void set_DynamicTemplateType(EEType * pTemplate); + + void SetHashCode(UInt32 value); + UInt32 GetHashCode(); + + // Retrieve optional fields associated with this EEType. May be NULL if no such fields exist. + inline PTR_OptionalFields get_OptionalFields(); + void set_OptionalFields(OptionalFields * pOptionalFields); + + // Retrieve the amount of padding added to value type fields in order to align them for boxed allocation + // on the GC heap. This value to can be used along with the result of get_BaseSize to determine the size + // of a value type embedded in the stack, and array or another type. + inline UInt32 get_ValueTypeFieldPadding(); + + // Retrieve the alignment of this valuetype + inline UInt32 get_ValueTypeFieldAlignment(); + + // Get flags that are less commonly set on EETypes. + inline UInt32 get_RareFlags(); +#endif // !BINDER + + static inline UInt32 ComputeValueTypeFieldPaddingFieldValue(UInt32 padding, UInt32 alignment); + + // Helper methods that deal with EEType topology (size and field layout). These are useful since as we + // optimize for pay-for-play we increasingly want to customize exactly what goes into an EEType on a + // per-type basis. The rules that govern this can be both complex and volatile and we risk sprinkling + // various layout rules through the binder and runtime that obscure the basic meaning of the code and are + // brittle: easy to overlook when one of the rules changes. + // + // The following methods can in some cases have fairly complex argument lists of their own and in that way + // they expose more of the implementation details than we'd ideally like. But regardless they still serve + // an arguably more useful purpose: they identify all the places that rely on the EEType layout. As we + // change layout rules we might have to change the arguments to the methods below but in doing so we will + // instantly identify all the other parts of the binder and runtime that need to be updated. + +#ifdef BINDER + // Determine whether a particular EEType will need optional fields. Binder only at the moment since it's + // less useful at runtime and far easier to specify in terms of a binder MethodTable. + static inline bool RequiresOptionalFields(MethodTable * pMT); +#endif + + // Calculate the size of an EEType including vtable, interface map and optional pointers (though not any + // optional fields stored out-of-line). Does not include the size of GC series information. + static inline UInt32 GetSizeofEEType(UInt32 cVirtuals, + UInt32 cInterfaces, + bool fHasFinalizer, + bool fRequiresOptionalFields, + bool fRequiresNullableType, + bool fHasSealedVirtuals); + +#ifdef BINDER + // Version of the above usable from the binder where all the type layout information can be gleaned from a + // MethodTable. + static inline UInt32 GetSizeofEEType(MethodTable *pMT); +#endif // BINDER + + // Calculate the offset of a field of the EEType that has a variable offset. + inline UInt32 GetFieldOffset(EETypeField eField); + +#ifdef BINDER + // Version of the above usable from the binder where all the type layout information can be gleaned from a + // MethodTable. + static inline UInt32 GetFieldOffset(EETypeField eField, + MethodTable * pMT); +#endif // BINDER + +#ifndef BINDER + // Validate an EEType extracted from an object. + bool Validate(bool assertOnFail = true); +#endif // !BINDER + +#if !defined(BINDER) && !defined(DACCESS_COMPILE) + // get the base type of an array EEType - this is special because the base type of arrays is not explicitly + // represented - instead the classlib has a common one for all arrays + EEType * GetArrayBaseType(); +#endif // !defined(BINDER) && !defined(DACCESS_COMPILE) + +#endif // !RHDUMP +}; + +#pragma warning(pop) + +#include "optionalfields.h" diff --git a/src/Native/Runtime/eetype.inl b/src/Native/Runtime/eetype.inl new file mode 100644 index 00000000000..66069139136 --- /dev/null +++ b/src/Native/Runtime/eetype.inl @@ -0,0 +1,796 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#ifndef __eetype_inl__ +#define __eetype_inl__ +//----------------------------------------------------------------------------------------------------------- +#ifndef BINDER +inline void EEType::SetHashCode(UInt32 value) +{ + ASSERT(IsDynamicType()); + m_uHashCode = value; +} +inline UInt32 EEType::GetHashCode() +{ + return m_uHashCode; +} +#endif + +//----------------------------------------------------------------------------------------------------------- +inline EEInterfaceInfo & EEInterfaceInfoMap::operator[](UInt16 idx) +{ + ASSERT(idx < m_cMap); + return m_pMap[idx]; +} + +//----------------------------------------------------------------------------------------------------------- +inline PTR_Code EEType::get_Slot(UInt16 slotNumber) +{ + ASSERT(slotNumber < m_usNumVtableSlots); + return *get_SlotPtr(slotNumber); +} + +//----------------------------------------------------------------------------------------------------------- +inline PTR_PTR_Code EEType::get_SlotPtr(UInt16 slotNumber) +{ + ASSERT(slotNumber < m_usNumVtableSlots); + return dac_cast(dac_cast(this) + offsetof(EEType, m_VTable)) + slotNumber; +} + +//----------------------------------------------------------------------------------------------------------- +#if !defined(BINDER) && !defined(DACCESS_COMPILE) +inline PTR_UInt8 FollowRelativePointer(const Int32 *pDist) +{ + Int32 dist = *pDist; + + PTR_UInt8 result = (PTR_UInt8)pDist + dist; + + return result; +} + +inline PTR_Code EEType::get_SealedVirtualSlot(UInt16 slotNumber) +{ + ASSERT(!IsNullable()); + + if (IsDynamicType()) + { + if ((get_RareFlags() & IsDynamicTypeWithSealedVTableEntriesFlag) != 0) + { + UInt32 cbSealedVirtualSlotsTypeOffset = GetFieldOffset(ETF_SealedVirtualSlots); + + PTR_PTR_Code pSealedVirtualsSlotTable = *(PTR_PTR_Code*)((PTR_UInt8)this + cbSealedVirtualSlotsTypeOffset); + + return pSealedVirtualsSlotTable[slotNumber]; + } + else + { + return get_DynamicTemplateType()->get_SealedVirtualSlot(slotNumber); + } + } + + UInt32 cbSealedVirtualSlotsTypeOffset = GetFieldOffset(ETF_SealedVirtualSlots); + + PTR_Int32 pSealedVirtualsSlotTable = (PTR_Int32)FollowRelativePointer((PTR_Int32)((PTR_UInt8)this + cbSealedVirtualSlotsTypeOffset)); + + PTR_Code result = FollowRelativePointer(&pSealedVirtualsSlotTable[slotNumber]); + + return result; +} + +inline void EEType::set_SealedVirtualSlot(PTR_Code pValue, UInt16 slotNumber) +{ + ASSERT(IsDynamicType()); + + UInt32 cbSealedVirtualSlotsTypeOffset = GetFieldOffset(ETF_SealedVirtualSlots); + + PTR_PTR_Code pSealedVirtualsSlotTable = *(PTR_PTR_Code*)((PTR_UInt8)this + cbSealedVirtualSlotsTypeOffset); + + pSealedVirtualsSlotTable[slotNumber] = pValue; +} +#endif // !BINDER && !DACCESS_COMPILE + +//----------------------------------------------------------------------------------------------------------- +inline EEType::Kinds EEType::get_Kind() +{ + return (Kinds)(m_usFlags & (UInt16)EETypeKindMask); +} + +//----------------------------------------------------------------------------------------------------------- +inline EEType * EEType::get_BaseType() +{ +#ifdef DACCESS_COMPILE + // Easy way to cope with the get_BaseType calls throughout the DACCESS code; better than chasing down + // all uses and changing them to check for array. + if (IsParameterizedType()) + return NULL; +#endif + +#if defined(BINDER) + // Does not yet handle arrays properly. + ASSERT(!IsParameterizedType()); +#endif + + if (IsCloned()) + { + return get_CanonicalEEType()->get_BaseType(); + } + +#if !defined(BINDER) && !defined(DACCESS_COMPILE) + if (IsParameterizedType()) + { + if (IsArray()) + return GetArrayBaseType(); + else + return NULL; + } +#endif + + ASSERT(IsCanonical()); + + if (IsRelatedTypeViaIAT()) + { + return *PTR_PTR_EEType(reinterpret_cast(m_RelatedType.m_ppBaseTypeViaIAT)); + } + + return PTR_EEType(reinterpret_cast(m_RelatedType.m_pBaseType)); +} + +#if !defined(BINDER) && !defined(DACCESS_COMPILE) +//----------------------------------------------------------------------------------------------------------- +inline bool EEType::HasDispatchMap() +{ + if (!HasInterfaces()) + return false; + OptionalFields *optionalFields = get_OptionalFields(); + if (optionalFields == NULL) + return false; + UInt32 idxDispatchMap = optionalFields->GetDispatchMap(0xffffffff); + if (idxDispatchMap == 0xffffffff) + { + if (HasDynamicallyAllocatedDispatchMap()) + return true; + else if (IsDynamicType()) + return get_DynamicTemplateType()->HasDispatchMap(); + return false; + } + return true; +} + +inline DispatchMap * EEType::GetDispatchMap() +{ + if (!HasInterfaces()) + return NULL; + + // Get index of DispatchMap pointer in the lookup table stored in this EEType's module. + OptionalFields *optionalFields = get_OptionalFields(); + if (optionalFields == NULL) + return NULL; + UInt32 idxDispatchMap = get_OptionalFields()->GetDispatchMap(0xffffffff); + if ((idxDispatchMap == 0xffffffff) && IsDynamicType()) + { + if (HasDynamicallyAllocatedDispatchMap()) + return *(DispatchMap **)((UInt8*)this + GetFieldOffset(ETF_DynamicDispatchMap)); + else + return get_DynamicTemplateType()->GetDispatchMap(); + } + + // Determine this EEType's module. + RuntimeInstance * pRuntimeInstance = GetRuntimeInstance(); + Module * pModule = pRuntimeInstance->FindModuleByReadOnlyDataAddress(this); + if (pModule == NULL) + pModule = pRuntimeInstance->FindModuleByDataAddress(this); + ASSERT(pModule != NULL); + + return pModule->GetDispatchMapLookupTable()[idxDispatchMap]; +} +#endif // !BINDER && !DACCESS_COMPILE + +//----------------------------------------------------------------------------------------------------------- +inline EEInterfaceInfoMap EEType::GetInterfaceMap() +{ + UInt32 cbInterfaceMapOffset = GetFieldOffset(ETF_InterfaceMap); + + return EEInterfaceInfoMap(reinterpret_cast((UInt8*)this + cbInterfaceMapOffset), + GetNumInterfaces()); +} + +//----------------------------------------------------------------------------------------------------------- +inline EEType * EEType::get_CanonicalEEType() +{ + // cloned EETypes must always refer to types in other modules + ASSERT(IsCloned()); + ASSERT(IsRelatedTypeViaIAT()); + + return *PTR_PTR_EEType(reinterpret_cast(m_RelatedType.m_ppCanonicalTypeViaIAT)); +} + +//----------------------------------------------------------------------------------------------------------- +inline EEType * EEType::get_RelatedParameterType() +{ + ASSERT(IsParameterizedType()); + + if (IsRelatedTypeViaIAT()) + return *PTR_PTR_EEType(reinterpret_cast(m_RelatedType.m_ppRelatedParameterTypeViaIAT)); + else + return PTR_EEType(reinterpret_cast(m_RelatedType.m_pRelatedParameterType)); +} + +#ifdef DACCESS_COMPILE +inline bool EEType::DacVerify() +{ + // Use a separate static worker because the worker validates + // the whole chain of EETypes and we don't want to accidentally + // answer questions from 'this' that should have come from the + // 'current' EEType. + return DacVerifyWorker(this); +} +// static +inline bool EEType::DacVerifyWorker(EEType* pThis) +{ + //********************************************************************* + //**** ASSUMES MAX TYPE HIERARCHY DEPTH OF 1024 TYPES **** + //********************************************************************* + const int MAX_SANE_RELATED_TYPES = 1024; + //********************************************************************* + //**** ASSUMES MAX OF 200 INTERFACES IMPLEMENTED ON ANY GIVEN TYPE **** + //********************************************************************* + const int MAX_SANE_NUM_INSTANCES = 200; + + + PTR_EEType pCurrentType = dac_cast(pThis); + for (int i = 0; i < MAX_SANE_RELATED_TYPES; i++) + { + // Verify interface map + if (pCurrentType->GetNumInterfaces() > MAX_SANE_NUM_INSTANCES) + return false; + + // Validate the current type + if (!pCurrentType->Validate(false)) + return false; + + // + // Now on to the next type in the hierarchy. + // + + if (pCurrentType->IsRelatedTypeViaIAT()) + pCurrentType = *dac_cast(reinterpret_cast(pCurrentType->m_RelatedType.m_ppBaseTypeViaIAT)); + else + pCurrentType = dac_cast(reinterpret_cast(pCurrentType->m_RelatedType.m_pBaseType)); + + if (pCurrentType == NULL) + break; + } + + if (pCurrentType != NULL) + return false; // assume we found an infinite loop + + return true; +} +#endif + +//----------------------------------------------------------------------------------------------------------- +// Transform a (possibly canonical) type into a cloned type pointing to the given type as the canonical type. +// Used when unifying generic instantiation types. +inline void EEType::MakeClonedType(EEType ** ppCanonicalType) +{ + if (IsCanonical()) + { + m_usFlags &= ~EETypeKindMask; + m_usFlags |= ClonedEEType; + m_usFlags |= RelatedTypeViaIATFlag; + } + else + { + ASSERT(IsRelatedTypeViaIAT()); + } + + m_RelatedType.m_ppCanonicalTypeViaIAT = ppCanonicalType; + + ASSERT(IsCloned()); + ASSERT(get_CanonicalEEType() == *ppCanonicalType); +} + +//----------------------------------------------------------------------------------------------------------- +// If any part of this type is referenced indirectly (via IAT entries) resolve these references to direct +// pointers. This is only possible at runtime once the IAT has been updated and is currently used only for +// generics, when unifying a generic instantiation and cutting any arbitrary dependencies to the module which +// first published this instantiation. +inline void EEType::Flatten() +{ + if (IsRelatedTypeViaIAT()) + { + m_RelatedType.m_pBaseType = *m_RelatedType.m_ppBaseTypeViaIAT; + m_usFlags &= ~RelatedTypeViaIATFlag; + } + + if (HasInterfaces()) + { + EEInterfaceInfoMap itfMap = GetInterfaceMap(); + for (UInt16 i = 0; i < GetNumInterfaces(); i++) + itfMap[i].Flatten(); + } +} + +// Initialize an existing EEType as an array type with specific element type. This is another specialized +// method used only during the unification of generic instantiation types. It might need modification if +// needed in any other scenario. +inline void EEType::InitializeAsArrayType(EEType * pElementType, UInt32 baseSize) +{ + // This type will never appear in an object header on the heap (or otherwise be made available to the GC). + // It is used only when signature matching generic type instantiations. Only a subset of the type fields + // need to be filled in correctly as a result. + m_usComponentSize = 0; + m_usFlags = ParameterizedEEType; + m_uBaseSize = baseSize; + m_RelatedType.m_pRelatedParameterType = pElementType; + m_usNumVtableSlots = 0; + m_usNumInterfaces = 0; +} + +/* static */ +inline UInt32 EEType::ComputeValueTypeFieldPaddingFieldValue(UInt32 padding, UInt32 alignment) +{ + // For the default case, return 0 + if ((padding == 0) && (alignment == POINTER_SIZE)) + return 0; + + UInt32 alignmentLog2 = 0; + ASSERT(alignment != 0); + + while ((alignment & 1) == 0) + { + alignmentLog2++; + alignment = alignment >> 1; + } + ASSERT(alignment == 1); + + ASSERT(ValueTypePaddingMax >= padding); + + alignmentLog2++; // Our alignment values here are adjusted by one to allow for a default of 0 + + UInt32 paddingLowBits = padding & ValueTypePaddingLowMask; + UInt32 paddingHighBits = ((padding & ~ValueTypePaddingLowMask) >> ValueTypePaddingAlignmentShift) << ValueTypePaddingHighShift; + UInt32 alignmentLog2Bits = alignmentLog2 << ValueTypePaddingAlignmentShift; + ASSERT((alignmentLog2Bits & ~ValueTypePaddingAlignmentMask) == 0); + return paddingLowBits | paddingHighBits | alignmentLog2Bits; +} + +#ifndef BINDER +// Retrieve optional fields associated with this EEType. May be NULL if no such fields exist. +inline PTR_OptionalFields EEType::get_OptionalFields() +{ + if ((m_usFlags & OptionalFieldsFlag) == 0) + return NULL; + + // Runtime allocated EETypes don't copy over optional fields. We should be careful to avoid operations + // that require them on paths that can handle such cases. + ASSERT(!IsRuntimeAllocated()); + + UInt32 cbOptionalFieldsOffset = GetFieldOffset(ETF_OptionalFieldsPtr); +#if defined(DACCESS_COMPILE) + // this construct creates a "host address" for the optional field blob + return *(PTR_PTR_OptionalFields)((dac_cast(this)) + cbOptionalFieldsOffset); +#else + return *(OptionalFields**)((UInt8*)this + cbOptionalFieldsOffset); + +#endif +} + +inline void EEType::set_OptionalFields(OptionalFields * pOptionalFields) +{ + m_usFlags |= OptionalFieldsFlag; + + UInt32 cbOptionalFieldsOffset = GetFieldOffset(ETF_OptionalFieldsPtr); + + *(OptionalFields**)((UInt8*)this + cbOptionalFieldsOffset) = pOptionalFields; +} + +// Retrieve the amount of padding added to value type fields in order to align them for boxed allocation on +// the GC heap. This value to can be used along with the result of get_BaseSize to determine the size of a +// value type embedded in the stack, and array or another type. +inline UInt32 EEType::get_ValueTypeFieldPadding() +{ + OptionalFields * pOptFields = get_OptionalFields(); + + // If there are no optional fields then the padding must have been the default, 0. + if (!pOptFields) + return 0; + + // Get the value from the optional fields. The default is zero if that particular field was not included. + // The low bits of this field is the ValueType field padding, the rest of the byte is the alignment if present + UInt32 ValueTypeFieldPaddingData = pOptFields->GetValueTypeFieldPadding(0); + UInt32 padding = ValueTypeFieldPaddingData & ValueTypePaddingLowMask; + // If there is additional padding, the other bits have that data + padding |= (ValueTypeFieldPaddingData & ValueTypePaddingHighMask) >> (ValueTypePaddingHighShift - ValueTypePaddingAlignmentShift); + return padding; +} + +// Retrieve the alignment of this valuetype +inline UInt32 EEType::get_ValueTypeFieldAlignment() +{ + OptionalFields * pOptFields = get_OptionalFields(); + + // If there are no optional fields then the alignment must have been the default, POINTER_SIZE. + if (!pOptFields) + return POINTER_SIZE; + + // Get the value from the optional fields. The default is zero if that particular field was not included. + // The low bits of this field is the ValueType field padding, the rest of the byte is the alignment if present + UInt32 alignmentValue = (pOptFields->GetValueTypeFieldPadding(0) & ValueTypePaddingAlignmentMask) >> ValueTypePaddingAlignmentShift;; + + // Alignment is stored as 1 + the log base 2 of the alignment, except a 0 indicates standard pointer alignment. + if (alignmentValue == 0) + return POINTER_SIZE; + else + return 1 << (alignmentValue - 1); +} + +// Get flags that are less commonly set on EETypes. +inline UInt32 EEType::get_RareFlags() +{ + OptionalFields * pOptFields = get_OptionalFields(); + + // If there are no optional fields then none of the rare flags have been set. + if (!pOptFields) + return 0; + + // Get the flags from the optional fields. The default is zero if that particular field was not included. + return pOptFields->GetRareFlags(0); +} + +// Retrieve the vtable slot number of the method that implements ICastableFlag.IsInstanceOfInterface for +// ICastable types. +inline PTR_Code EEType::get_ICastableIsInstanceOfInterfaceMethod() +{ + EEType * eeType = this; + do + { + ASSERT(eeType->IsICastable()); + + OptionalFields * pOptFields = eeType->get_OptionalFields(); + ASSERT(pOptFields); + + UInt16 uiSlot = pOptFields->GetICastableIsInstSlot(0xffff); + if (uiSlot != 0xffff) + { + if (uiSlot < eeType->m_usNumVtableSlots) + return this->get_Slot(uiSlot); + else + return eeType->get_SealedVirtualSlot(uiSlot - eeType->m_usNumVtableSlots); + } + eeType = eeType->get_BaseType(); + } + while (eeType != NULL); + + ASSERT(!"get_ICastableIsInstanceOfInterfaceMethod"); + + return NULL; +} + +// Retrieve the vtable slot number of the method that implements ICastableFlag.GetImplType for ICastable +// types. +inline PTR_Code EEType::get_ICastableGetImplTypeMethod() +{ + EEType * eeType = this; + + do + { + ASSERT(eeType->IsICastable()); + + OptionalFields * pOptFields = eeType->get_OptionalFields(); + ASSERT(pOptFields); + + UInt16 uiSlot = pOptFields->GetICastableGetImplTypeSlot(0xffff); + if (uiSlot != 0xffff) + { + if (uiSlot < eeType->m_usNumVtableSlots) + return this->get_Slot(uiSlot); + else + return eeType->get_SealedVirtualSlot(uiSlot - eeType->m_usNumVtableSlots); + } + eeType = eeType->get_BaseType(); + } + while (eeType != NULL); + + ASSERT(!"get_ICastableGetImplTypeMethod"); + + return NULL; +} + +// Retrieve the value type T from a Nullable. +inline EEType * EEType::GetNullableType() +{ + ASSERT(IsNullable()); + + UInt32 cbNullableTypeOffset = GetFieldOffset(ETF_NullableType); + + // The type pointer may be indirected via the IAT if the type is defined in another module. + if (IsNullableTypeViaIAT()) + return **(EEType***)((UInt8*)this + cbNullableTypeOffset); + else + return *(EEType**)((UInt8*)this + cbNullableTypeOffset); +} + +// Set the value type T from a Nullable for dynamically created instantiations. +inline void EEType::SetNullableType(EEType * pTheT) +{ + ASSERT(IsNullable() && IsDynamicType() && !IsNullableTypeViaIAT()); + + UInt32 cbNullableTypeOffset = GetFieldOffset(ETF_NullableType); + *((EEType**)((UInt8*)this + cbNullableTypeOffset)) = pTheT; +} + +// Retrieve the offset of the value embedded in a Nullable. +inline UInt8 EEType::GetNullableValueOffset() +{ + ASSERT(IsNullable()); + + // Grab optional fields. If there aren't any then the offset was the default of 1 (immediately after the + // Nullable's boolean flag). + OptionalFields * pOptFields = get_OptionalFields(); + if (pOptFields == NULL) + return 1; + + // The offset is never zero (Nullable has a boolean there indicating whether the value is valid). So the + // offset is encoded - 1 to save space. The zero below is the default value if the field wasn't encoded at + // all. + return pOptFields->GetNullableValueOffset(0) + 1; +} + +inline EEType * EEType::get_DynamicTemplateType() +{ + ASSERT(IsDynamicType()); + + UInt32 cbOffset = GetFieldOffset(ETF_DynamicTemplateType); + +#if defined(DACCESS_COMPILE) + return *(PTR_PTR_EEType)((dac_cast(this)) + cbOffset); +#else + return *(EEType**)((UInt8*)this + cbOffset); + +#endif +} + +inline void EEType::set_DynamicTemplateType(EEType * pTemplate) +{ + ASSERT(IsDynamicType()); + + UInt32 cbOffset = GetFieldOffset(ETF_DynamicTemplateType); + + *(EEType**)((UInt8*)this + cbOffset) = pTemplate; +} + +inline void EEType::set_RelatedParameterType(EEType * pParameterType) +{ + ASSERT(IsParameterizedType()); + + m_usFlags &= ~RelatedTypeViaIATFlag; + m_RelatedType.m_pRelatedParameterType = pParameterType; +} + + +#endif // !BINDER + +#ifdef BINDER +// Determine whether a particular EEType will need optional fields. Binder only at the moment since it's +// less useful at runtime and far easier to specify in terms of a binder MethodTable. +/*static*/ inline bool EEType::RequiresOptionalFields(MethodTable * pMT) +{ + MethodTable * pElementMT = pMT->IsArray() ? + ((ArrayClass*)pMT->GetClass())->GetApproxArrayElementTypeHandle().AsMethodTable() : + NULL; + + return + // Do we need a padding size for value types that could be unboxed? + (pMT->IsValueTypeOrEnum() && + (((pMT->GetBaseSize() - SYNC_BLOCK_SKEW) - pMT->GetClass()->GetNumInstanceFieldBytes()) > 0)) || + // Do we need a alignment for value types? + (pMT->IsValueTypeOrEnum() && + (pMT->GetClass()->GetAlignmentRequirement() != POINTER_SIZE)) || +#ifdef TARGET_THUMB2 + // Do we need a rare flags field for a class or structure that requires 64-bit alignment on ARM? + (pMT->GetClass()->GetAlignmentRequirement() > 4) || + (pMT->IsArray() && pElementMT->IsValueTypeOrEnum() && (pElementMT->GetClass()->GetAlignmentRequirement() > 4)) || + (pMT->IsHFA()) || +#endif + // Do we need a DispatchMap? + (pMT->GetDispatchMap() != NULL && !pMT->GetDispatchMap()->IsEmpty()) || + // Do we need to cache ICastable method vtable slots? + (pMT->IsICastable()) || + // Is the class a Nullable instantiation (need to store the flag and possibly a field offset)? + pMT->IsNullable() || + (pMT->HasStaticClassConstructor() && !pMT->HasEagerStaticClassConstructor()); +} +#endif + +// Calculate the size of an EEType including vtable, interface map and optional pointers (though not any +// optional fields stored out-of-line). Does not include the size of GC series information. +/*static*/ inline UInt32 EEType::GetSizeofEEType(UInt32 cVirtuals, + UInt32 cInterfaces, + bool fHasFinalizer, + bool fRequiresOptionalFields, + bool fRequiresNullableType, + bool fHasSealedVirtuals) +{ + // We don't support nullables with sealed virtuals at this time - + // the issue is that if both the nullable eetype and the sealed virtuals may be present, + // we need to detect the presence of at least one of them by looking at the EEType. + // In the case of nullable, we'd need to fetch the rare flags, which is annoying, + // an in the case of the sealed virtual slots, the information is implicit in the dispatch + // map, which is even more annoying. + // So as long as nullables don't have sealed virtual slots, it's better to make that + // an invariant and *not* test for nullable at run time. + ASSERT(!(fRequiresNullableType && fHasSealedVirtuals)); + + return offsetof(EEType, m_VTable) + + (sizeof(UIntTarget) * cVirtuals) + + (sizeof(EEInterfaceInfo) * cInterfaces) + + (fHasFinalizer ? sizeof(UIntTarget) : 0) + + (fRequiresOptionalFields ? sizeof(UIntTarget) : 0) + + (fRequiresNullableType ? sizeof(UIntTarget) : 0) + + (fHasSealedVirtuals ? sizeof(Int32) : 0); +} + +#if !defined(BINDER) && !defined(DACCESS_COMPILE) +// get the base type of an array EEType - this is special because the base type of arrays is not explicitly +// represented - instead the classlib has a common one for all arrays +inline EEType * EEType::GetArrayBaseType() +{ + RuntimeInstance * pRuntimeInstance = GetRuntimeInstance(); + Module * pModule = NULL; + if (pRuntimeInstance->IsInStandaloneExeMode()) + { + // With dynamically created types, there is no home module to use to find System.Array. That's okay + // for now, but when we support multi-module, we'll have to do something more clever here. + pModule = pRuntimeInstance->GetStandaloneExeModule(); + } + else + { + EEType *pEEType = this; + if (pEEType->IsDynamicType()) + pEEType = pEEType->get_DynamicTemplateType(); + pModule = GetRuntimeInstance()->FindModuleByReadOnlyDataAddress(pEEType); + } + EEType * pArrayBaseType = pModule->GetArrayBaseType(); + return pArrayBaseType; +} +#endif // !defined(BINDER) && !defined(DACCESS_COMPILE) + +#ifdef BINDER +// Version of the above usable from the binder where all the type layout information can be gleaned from a +// MethodTable. +/*static*/ inline UInt32 EEType::GetSizeofEEType(MethodTable *pMT) +{ + bool fHasSealedVirtuals = pMT->GetNumVirtuals() < (pMT->GetNumVtableSlots() + pMT->GetNumAdditionalVtableSlots()); + return GetSizeofEEType(pMT->IsInterface() ? (pMT->HasPerInstInfo() ? 1 : 0) : pMT->GetNumVirtuals(), + pMT->GetNumInterfaces(), + pMT->HasFinalizer(), + EEType::RequiresOptionalFields(pMT), + pMT->IsNullable(), + fHasSealedVirtuals); +} +#endif // BINDER + +// Calculate the offset of a field of the EEType that has a variable offset. +/*static*/ __forceinline UInt32 EEType::GetFieldOffset(EETypeField eField) +{ + // First part of EEType consists of the fixed portion followed by the vtable. + UInt32 cbOffset = offsetof(EEType, m_VTable) + (sizeof(UIntTarget) * m_usNumVtableSlots); + + // Then we have the interface map. + if (eField == ETF_InterfaceMap) + { + ASSERT(GetNumInterfaces() > 0); + return cbOffset; + } + cbOffset += sizeof(EEInterfaceInfo) * GetNumInterfaces(); + + // Followed by the pointer to the finalizer method. + if (eField == ETF_Finalizer) + { + ASSERT(HasFinalizer()); + return cbOffset; + } + if (HasFinalizer()) + cbOffset += sizeof(UIntTarget); + + // Followed by the pointer to the optional fields. + if (eField == ETF_OptionalFieldsPtr) + { + ASSERT(HasOptionalFields()); + return cbOffset; + } + if (HasOptionalFields()) + cbOffset += sizeof(UIntTarget); + + // Followed by the pointer to the type target of a Nullable. + if (eField == ETF_NullableType) + { +#ifndef BINDER + ASSERT(IsNullable()); +#endif + return cbOffset; + } + + // OR, followed by the pointer to the sealed virtual slots + if (eField == ETF_SealedVirtualSlots) + return cbOffset; + + // Binder does not use DynamicTemplateType +#ifndef BINDER + if (IsNullable() || ((get_RareFlags() & IsDynamicTypeWithSealedVTableEntriesFlag) != 0)) + cbOffset += sizeof(UIntTarget); + + if (eField == ETF_DynamicDispatchMap) + { + ASSERT(IsDynamicType()); + return cbOffset; + } + if ((get_RareFlags() & HasDynamicallyAllocatedDispatchMapFlag) != 0) + cbOffset += sizeof(UIntTarget); + + if (eField == ETF_DynamicTemplateType) + { + ASSERT(IsDynamicType()); + return cbOffset; + } +#endif + + ASSERT(!"Unknown EEType field type"); + return 0; +} + +#ifdef BINDER +// Version of the above usable from the binder where all the type layout information can be gleaned from a +// MethodTable. +/*static*/ inline UInt32 EEType::GetFieldOffset(EETypeField eField, + MethodTable * pMT) +{ + UInt32 numVTableSlots = pMT->IsInterface() ? (pMT->HasPerInstInfo() ? 1 : 0) : pMT->GetNumVirtuals(); + + // First part of EEType consists of the fixed portion followed by the vtable. + UInt32 cbOffset = offsetof(EEType, m_VTable) + (sizeof(UIntTarget) * numVTableSlots); + + // Then we have the interface map. + if (eField == ETF_InterfaceMap) + { + return cbOffset; + } + cbOffset += sizeof(EEInterfaceInfo) * pMT->GetNumInterfaces(); + + // Followed by the pointer to the finalizer method. + if (eField == ETF_Finalizer) + { + return cbOffset; + } + if (pMT->HasFinalizer()) + cbOffset += sizeof(UIntTarget); + + // Followed by the pointer to the optional fields. + if (eField == ETF_OptionalFieldsPtr) + { + return cbOffset; + } + if (EEType::RequiresOptionalFields(pMT)) + cbOffset += sizeof(UIntTarget); + + // Followed by the pointer to the type target of a Nullable. + if (eField == ETF_NullableType) + { + return cbOffset; +} + + // OR, followed by the pointer to the sealed virtual slots + if (eField == ETF_SealedVirtualSlots) + return cbOffset; + + // Binder does not use DynamicTemplateType + + ASSERT(!"Unknown EEType field type"); + return 0; +} +#endif + +#endif __eetype_inl__ diff --git a/src/Native/Runtime/event.cpp b/src/Native/Runtime/event.cpp new file mode 100644 index 00000000000..0bc07055984 --- /dev/null +++ b/src/Native/Runtime/event.cpp @@ -0,0 +1,116 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "event.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "rwlock.h" +#include "threadstore.h" + +// +// ----------------------------------------------------------------------------------------------------------- +// +// CLR wrapper around events. This version directly uses Win32 events (there's no support for host +// interception). +// + +void CLREventStatic::CreateManualEvent(bool bInitialState) +{ + m_hEvent = PalCreateEventW(NULL, TRUE, bInitialState, NULL); + m_fInitialized = true; +} + +void CLREventStatic::CreateAutoEvent(bool bInitialState) +{ + m_hEvent = PalCreateEventW(NULL, FALSE, bInitialState, NULL); + m_fInitialized = true; +} + +void CLREventStatic::CreateOSManualEvent(bool bInitialState) +{ + m_hEvent = PalCreateEventW(NULL, TRUE, bInitialState, NULL); + m_fInitialized = true; +} + +void CLREventStatic::CreateOSAutoEvent (bool bInitialState) +{ + m_hEvent = PalCreateEventW(NULL, FALSE, bInitialState, NULL); + m_fInitialized = true; +} + +void CLREventStatic::CloseEvent() +{ + if (m_fInitialized && m_hEvent != INVALID_HANDLE_VALUE) + { + PalCloseHandle(m_hEvent); + m_hEvent = INVALID_HANDLE_VALUE; + } +} + +bool CLREventStatic::IsValid() const +{ + return m_fInitialized && m_hEvent != INVALID_HANDLE_VALUE; +} + +bool CLREventStatic::Set() +{ + if (!m_fInitialized) + return false; + return PalSetEvent(m_hEvent); +} + +bool CLREventStatic::Reset() +{ + if (!m_fInitialized) + return false; + return PalResetEvent(m_hEvent); +} + +UInt32 CLREventStatic::Wait(UInt32 dwMilliseconds, bool bAlertable, bool bAllowReentrantWait) +{ + UInt32 result = WAIT_FAILED; + + if (m_fInitialized) + { + bool disablePreemptive = false; + Thread * pCurThread = ThreadStore::GetCurrentThreadIfAvailable(); + + if (NULL != pCurThread) + { + if (pCurThread->PreemptiveGCDisabled()) + { + pCurThread->EnablePreemptiveGC(); + disablePreemptive = true; + } + } + + result = PalCompatibleWaitAny(bAlertable, dwMilliseconds, 1, &m_hEvent, bAllowReentrantWait); + + if (disablePreemptive) + { + pCurThread->DisablePreemptiveGC(); + } + } + + return result; +} + +HANDLE CLREventStatic::GetOSEvent() +{ + if (!m_fInitialized) + return INVALID_HANDLE_VALUE; + return m_hEvent; +} diff --git a/src/Native/Runtime/event.h b/src/Native/Runtime/event.h new file mode 100644 index 00000000000..0fdc256da70 --- /dev/null +++ b/src/Native/Runtime/event.h @@ -0,0 +1,22 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +class CLREventStatic +{ +public: + void CreateManualEvent(bool bInitialState); + void CreateAutoEvent(bool bInitialState); + void CreateOSManualEvent(bool bInitialState); + void CreateOSAutoEvent (bool bInitialState); + void CloseEvent(); + bool IsValid() const; + bool Set(); + bool Reset(); + UInt32 Wait(UInt32 dwMilliseconds, bool bAlertable, bool bAllowReentrantWait = false); + HANDLE GetOSEvent(); + +private: + HANDLE m_hEvent; + bool m_fInitialized; +}; diff --git a/src/Native/Runtime/eventtrace.h b/src/Native/Runtime/eventtrace.h new file mode 100644 index 00000000000..c322bc30a73 --- /dev/null +++ b/src/Native/Runtime/eventtrace.h @@ -0,0 +1,338 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// +// File: eventtrace.h +// Abstract: This module implements Event Tracing support. This includes +// eventtracebase.h, and adds VM-specific ETW helpers to support features like type +// logging, allocation logging, and gc heap walk logging. +// +// #EventTracing +// Windows +// ETW (Event Tracing for Windows) is a high-performance, low overhead and highly scalable +// tracing facility provided by the Windows Operating System. ETW is available on Win2K and above. There are +// four main types of components in ETW: event providers, controllers, consumers, and event trace sessions. +// An event provider is a logical entity that writes events to ETW sessions. The event provider must register +// a provider ID with ETW through the registration API. A provider first registers with ETW and writes events +// from various points in the code by invoking the ETW logging API. When a provider is enabled dynamically by +// the ETW controller application, calls to the logging API sends events to a specific trace session +// designated by the controller. Each event sent by the event provider to the trace session consists of a +// fixed header that includes event metadata and additional variable user-context data. CLR is an event +// provider. + +// Mac +// DTrace is similar to ETW and has been made to look like ETW at most of the places. +// For convenience, it is called ETM (Event Tracing for Mac) and exists only on the Mac Leopard OS +// ============================================================================ + +#ifndef _VMEVENTTRACE_H_ +#define _VMEVENTTRACE_H_ + +#include "eventtracebase.h" + + +namespace ETW +{ +#ifndef FEATURE_REDHAWK + + class LoggedTypesFromModule; + + // We keep a hash of these to keep track of: + // * Which types have been logged through ETW (so we can avoid logging dupe Type + // events), and + // * GCSampledObjectAllocation stats to help with "smart sampling" which + // dynamically adjusts sampling rate of objects by type. + // See code:LoggedTypesFromModuleTraits + struct TypeLoggingInfo + { + public: + TypeLoggingInfo(TypeHandle thParam) + { + Init(thParam); + } + + TypeLoggingInfo() + { + Init(TypeHandle()); + } + + void Init(TypeHandle thParam) + { + th = thParam; + dwTickOfCurrentTimeBucket = 0; + dwAllocCountInCurrentBucket = 0; + flAllocPerMSec = 0; + + dwAllocsToSkipPerSample = 0; + dwAllocsSkippedForSample = 0; + cbIgnoredSizeForSample = 0; + }; + + // The type this TypeLoggingInfo represents + TypeHandle th; + + // Smart sampling + + // These bucket values remember stats of a particular time slice that are used to + // help adjust the sampling rate + DWORD dwTickOfCurrentTimeBucket; + DWORD dwAllocCountInCurrentBucket; + float flAllocPerMSec; + + // The number of data points to ignore before taking a "sample" (i.e., logging a + // GCSampledObjectAllocation ETW event for this type) + DWORD dwAllocsToSkipPerSample; + + // The current number of data points actually ignored for the current sample + DWORD dwAllocsSkippedForSample; + + // The current count of bytes of objects of this type actually allocated (and + // ignored) for the current sample + SIZE_T cbIgnoredSizeForSample; + }; + + // Class to wrap all type system logic for ETW + class TypeSystemLog + { + private: + static AllLoggedTypes * s_pAllLoggedTypes; + + // See code:ETW::TypeSystemLog::PostRegistrationInit + static BOOL s_fHeapAllocEventEnabledOnStartup; + static BOOL s_fHeapAllocHighEventEnabledNow; + static BOOL s_fHeapAllocLowEventEnabledNow; + + // If COMPLUS_UNSUPPORTED_ETW_ObjectAllocationEventsPerTypePerSec is set, then + // this is used to determine the event frequency, overriding + // s_nDefaultMsBetweenEvents above (regardless of which + // GCSampledObjectAllocation*Keyword was used) + static int s_nCustomMsBetweenEvents; + + public: + // This customizes the type logging behavior in LogTypeAndParametersIfNecessary + enum TypeLogBehavior + { + // Take lock, and consult hash table to see if this is the first time we've + // encountered the type, in which case, log it + kTypeLogBehaviorTakeLockAndLogIfFirstTime, + + // Caller has already taken lock, so just directly consult hash table to see + // if this is the first time we've encountered the type, in which case, log + // it + kTypeLogBehaviorAssumeLockAndLogIfFirstTime, + + // Don't take lock, don't consult hash table. Just log the type. (This is + // used in cases when checking for dupe type logging isn't worth it, such as + // when logging the finalization of an object.) + kTypeLogBehaviorAlwaysLog, + + // When logging the type for GCSampledObjectAllocation events, we don't need + // the lock (as it's already held by the code doing the stats for smart + // sampling), and we already know we need to log the type (since we already + // looked it up in the hash). But we would still need to consult the hash + // for any type parameters, so kTypeLogBehaviorAlwaysLog isn't appropriate, + // and this is used instead. + kTypeLogBehaviorAssumeLockAndAlwaysLogTopLevelType, + }; + + static HRESULT PreRegistrationInit(); + static void PostRegistrationInit(); + static BOOL IsHeapAllocEventEnabled(); + static void SendObjectAllocatedEvent(Object * pObject); + static CrstBase * GetHashCrst(); + static VOID LogTypeAndParametersIfNecessary(BulkTypeEventLogger * pBulkTypeEventLogger, ULONGLONG thAsAddr, TypeLogBehavior typeLogBehavior); + static VOID OnModuleUnload(Module * pModule); + static void OnKeywordsChanged(); + + private: + static BOOL ShouldLogType(TypeHandle th); + static BOOL ShouldLogTypeNoLock(TypeHandle th); + static TypeLoggingInfo LookupOrCreateTypeLoggingInfo(TypeHandle th, BOOL * pfCreatedNew, LoggedTypesFromModule ** ppLoggedTypesFromModule = NULL); + static BOOL AddOrReplaceTypeLoggingInfo(ETW::LoggedTypesFromModule * pLoggedTypesFromModule, const ETW::TypeLoggingInfo * pTypeLoggingInfo); + static int GetDefaultMsBetweenEvents(); + static VOID OnTypesKeywordTurnedOff(); + }; + +#endif // FEATURE_REDHAWK + + + // Class to wrap all GC logic for ETW + class GCLog + { + private: + // When WPA triggers a GC, it gives us this unique number to append to our + // GCStart event so WPA can correlate the CLR's GC with the JScript GC they + // triggered at the same time. + // + // We set this value when the GC is triggered, and then retrieve the value on the + // first subsequent FireGcStart() method call for a full, induced GC, assuming + // that that's the GC that WPA triggered. This is imperfect, and if we were in + // the act of beginning another full, induced GC (for some other reason), then + // we'll attach this sequence number to that GC instead of to the WPA-induced GC, + // but who cares? When parsing ETW logs later on, it's indistinguishable if both + // GCs really were induced at around the same time. +#ifdef FEATURE_REDHAWK + static volatile LONGLONG s_l64LastClientSequenceNumber; +#else // FEATURE_REDHAWK + static Volatile s_l64LastClientSequenceNumber; +#endif // FEATURE_REDHAWK + + public: + typedef union st_GCEventInfo { + typedef struct _GenerationInfo { + ULONGLONG GenerationSize; + ULONGLONG TotalPromotedSize; + } GenerationInfo; + + struct { + GenerationInfo GenInfo[4]; // the heap info on gen0, gen1, gen2 and the large object heap. + ULONGLONG FinalizationPromotedSize; //not available per generation + ULONGLONG FinalizationPromotedCount; //not available per generation + ULONG PinnedObjectCount; + ULONG SinkBlockCount; + ULONG GCHandleCount; + } HeapStats; + + typedef enum _HeapType { + SMALL_OBJECT_HEAP, LARGE_OBJECT_HEAP, READ_ONLY_HEAP + } HeapType; + struct { + ULONGLONG Address; + ULONGLONG Size; + HeapType Type; + } GCCreateSegment; + + struct { + ULONGLONG Address; + } GCFreeSegment; + struct { + ULONG Count; + ULONG Depth; + } GCEnd; + + typedef enum _AllocationKind { + AllocationSmall = 0, + AllocationLarge + }AllocationKind; + struct { + ULONG Allocation; + AllocationKind Kind; + } AllocationTick; + + // These values are gotten from the gc_reason + // in gcimpl.h + typedef enum _GC_REASON { + GC_ALLOC_SOH = 0 , + GC_INDUCED = 1 , + GC_LOWMEMORY = 2, + GC_EMPTY = 3, + GC_ALLOC_LOH = 4, + GC_OOS_SOH = 5, + GC_OOS_LOH = 6, + GC_INDUCED_NOFORCE = 7 + } GC_REASON; + typedef enum _GC_TYPE { + GC_NGC = 0 , GC_BGC = 1 , GC_FGC = 2 + } GC_TYPE; + struct { + ULONG Count; + ULONG Depth; + GC_REASON Reason; + GC_TYPE Type; + } GCStart; + + struct { + ULONG Count; // how many finalizers we called. + } GCFinalizers; + + struct { + ULONG Reason; + // This is only valid when SuspendEE is called by GC (ie, Reason is either + // SUSPEND_FOR_GC or SUSPEND_FOR_GC_PREP. + ULONG GcCount; + } SuspendEE; + + struct { + ULONG HeapNum; + } GCMark; + + struct { + ULONGLONG SegmentSize; + ULONGLONG LargeObjectSegmentSize; + BOOL ServerGC; // TRUE means it’s server GC; FALSE means it’s workstation. + } GCSettings; + + struct { + // The generation that triggered this notification. + ULONG Count; + // 1 means the notification was due to allocation; 0 means it was due to other factors. + ULONG Alloc; + } GCFullNotify; + } ETW_GC_INFO, *PETW_GC_INFO; + +#ifdef FEATURE_EVENT_TRACE + static VOID GCSettingsEvent(); +#else + static VOID GCSettingsEvent() {}; +#endif // FEATURE_EVENT_TRACE + + static BOOL ShouldWalkHeapObjectsForEtw(); + static BOOL ShouldWalkHeapRootsForEtw(); + static BOOL ShouldTrackMovementForEtw(); + static HRESULT ForceGCForDiagnostics(); + static VOID ForceGC(LONGLONG l64ClientSequenceNumber); + static VOID FireGcStartAndGenerationRanges(ETW_GC_INFO * pGcInfo); + static VOID FireGcEndAndGenerationRanges(ULONG Count, ULONG Depth); + static VOID FireSingleGenerationRangeEvent( + void * /* context */, + int generation, + BYTE * rangeStart, + BYTE * rangeEnd, + BYTE * rangeEndReserved); + static VOID RootReference( + LPVOID pvHandle, + Object * pRootedNode, + Object * pSecondaryNodeForDependentHandle, + BOOL fDependentHandle, + ProfilingScanContext * profilingScanContext, + DWORD dwGCFlags, + DWORD rootFlags); + static VOID ObjectReference( + ProfilerWalkHeapContext * profilerWalkHeapContext, + Object * pObjReferenceSource, + ULONGLONG typeID, + ULONGLONG cRefs, + Object ** rgObjReferenceTargets); + static VOID EndHeapDump(ProfilerWalkHeapContext * profilerWalkHeapContext); + static VOID BeginMovedReferences(size_t * pProfilingContext); + static VOID MovedReference(BYTE * pbMemBlockStart, BYTE * pbMemBlockEnd, ptrdiff_t cbRelocDistance, size_t profilingContext, BOOL fCompacting); + static VOID EndMovedReferences(size_t profilingContext); +#ifndef FEATURE_REDHAWK + static VOID SendFinalizeObjectEvent(MethodTable * pMT, Object * pObj); +#endif // FEATURE_REDHAWK + }; +}; + +#ifndef FEATURE_ETW +inline BOOL ETW::GCLog::ShouldWalkHeapObjectsForEtw() { return FALSE; } +inline BOOL ETW::GCLog::ShouldWalkHeapRootsForEtw() { return FALSE; } +inline BOOL ETW::GCLog::ShouldTrackMovementForEtw() { return FALSE; } +inline VOID ETW::GCLog::FireGcStartAndGenerationRanges(ETW_GC_INFO * pGcInfo) { } +inline VOID ETW::GCLog::FireGcEndAndGenerationRanges(ULONG Count, ULONG Depth) { } +inline VOID ETW::GCLog::EndHeapDump(ProfilerWalkHeapContext * profilerWalkHeapContext) { } +inline VOID ETW::GCLog::BeginMovedReferences(size_t * pProfilingContext) { } +inline VOID ETW::GCLog::MovedReference(BYTE * pbMemBlockStart, BYTE * pbMemBlockEnd, ptrdiff_t cbRelocDistance, size_t profilingContext, BOOL fCompacting) { } +inline VOID ETW::GCLog::EndMovedReferences(size_t profilingContext) { } +inline VOID ETW::GCLog::RootReference( + LPVOID pvHandle, + Object * pRootedNode, + Object * pSecondaryNodeForDependentHandle, + BOOL fDependentHandle, + ProfilingScanContext * profilingScanContext, + DWORD dwGCFlags, + DWORD rootFlags) { } +#endif + + +#endif //_VMEVENTTRACE_H_ \ No newline at end of file diff --git a/src/Native/Runtime/eventtracebase.h b/src/Native/Runtime/eventtracebase.h new file mode 100644 index 00000000000..8ca5b5b6831 --- /dev/null +++ b/src/Native/Runtime/eventtracebase.h @@ -0,0 +1,1118 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// File: eventtracebase.h +// Abstract: This module implements base Event Tracing support (excluding some of the +// CLR VM-specific ETW helpers). +// +// #EventTracing +// Windows +// ETW (Event Tracing for Windows) is a high-performance, low overhead and highly scalable +// tracing facility provided by the Windows Operating System. ETW is available on Win2K and above. There are +// four main types of components in ETW: event providers, controllers, consumers, and event trace sessions. +// An event provider is a logical entity that writes events to ETW sessions. The event provider must register +// a provider ID with ETW through the registration API. A provider first registers with ETW and writes events +// from various points in the code by invoking the ETW logging API. When a provider is enabled dynamically by +// the ETW controller application, calls to the logging API sends events to a specific trace session +// designated by the controller. Each event sent by the event provider to the trace session consists of a +// fixed header that includes event metadata and additional variable user-context data. CLR is an event +// provider. + +// Mac +// DTrace is similar to ETW and has been made to look like ETW at most of the places. +// For convenience, it is called ETM (Event Tracing for Mac) and exists only on the Mac Leopard OS +// ============================================================================ + +#ifndef _ETWTRACER_HXX_ +#define _ETWTRACER_HXX_ + +struct EventStructTypeData; +void InitializeEventTracing(); + +#if (!defined(FEATURE_PAL) || defined(FEATURE_DTRACE) || defined(FEATURE_REDHAWK)) && !defined(CROSSGEN_COMPILE) +#define FEATURE_EVENT_TRACE 1 +#endif + +#ifdef FEATURE_EVENT_TRACE + +// !!!!!!! NOTE !!!!!!!! +// The flags must match those in the ETW manifest exactly +// !!!!!!! NOTE !!!!!!!! + +enum EtwGCRootFlags +{ + kEtwGCRootFlagsPinning = 0x1, + kEtwGCRootFlagsWeakRef = 0x2, + kEtwGCRootFlagsInterior = 0x4, + kEtwGCRootFlagsRefCounted = 0x8, +}; + +enum EtwGCRootKind +{ + kEtwGCRootKindStack = 0, + kEtwGCRootKindFinalizer = 1, + kEtwGCRootKindHandle = 2, + kEtwGCRootKindOther = 3, + kEtwGCRootStatic = 4, +}; + +enum EtwTypeFlags +{ + kEtwTypeFlagsDelegate = 0x1, + kEtwTypeFlagsFinalizable = 0x2, + kEtwTypeFlagsExternallyImplementedCOMObject = 0x4, + kEtwTypeFlagsArray = 0x8, + kEtwTypeFlagsModuleBaseAddress = 0x10, +}; + +enum EtwThreadFlags +{ + kEtwThreadFlagGCSpecial = 0x00000001, + kEtwThreadFlagFinalizer = 0x00000002, + kEtwThreadFlagThreadPoolWorker = 0x00000004, +}; + + +// During a heap walk, this is the storage for keeping track of all the nodes and edges +// being batched up by ETW, and for remembering whether we're also supposed to call into +// a profapi profiler. This is allocated toward the end of a GC and passed to us by the +// GC heap walker. +struct ProfilerWalkHeapContext +{ +public: + ProfilerWalkHeapContext(BOOL fProfilerPinnedParam, LPVOID pvEtwContextParam) + { + fProfilerPinned = fProfilerPinnedParam; + pvEtwContext = pvEtwContextParam; + } + + BOOL fProfilerPinned; + LPVOID pvEtwContext; +}; + +class Object; + +/******************************/ +/* CLR ETW supported versions */ +/******************************/ +#define ETW_SUPPORTED_MAJORVER 5 // ETW is supported on win2k and above +#define ETW_ENABLED_MAJORVER 6 // OS versions >= to this we enable ETW registration by default, since on XP and Windows 2003, registration is too slow. + +/***************************************/ +/* Tracing levels supported by CLR ETW */ +/***************************************/ +#define ETWMAX_TRACE_LEVEL 6 // Maximum Number of Trace Levels supported +#define TRACE_LEVEL_NONE 0 // Tracing is not on +#define TRACE_LEVEL_FATAL 1 // Abnormal exit or termination +#define TRACE_LEVEL_ERROR 2 // Severe errors that need logging +#define TRACE_LEVEL_WARNING 3 // Warnings such as allocation failure +#define TRACE_LEVEL_INFORMATION 4 // Includes non-error cases such as Entry-Exit +#define TRACE_LEVEL_VERBOSE 5 // Detailed traces from intermediate steps + +struct ProfilingScanContext; + +// +// Use this macro to check if ETW is initialized and the event is enabled +// +#define ETW_TRACING_ENABLED(Context, EventDescriptor) \ + (Context.IsEnabled && ETW_TRACING_INITIALIZED(Context.RegistrationHandle) && ETW_EVENT_ENABLED(Context, EventDescriptor)) + +// +// Using KEYWORDZERO means when checking the events category ignore the keyword +// +#define KEYWORDZERO 0x0 + +// +// Use this macro to check if ETW is initialized and the category is enabled +// +#define ETW_TRACING_CATEGORY_ENABLED(Context, Level, Keyword) \ + (ETW_TRACING_INITIALIZED(Context.RegistrationHandle) && ETW_CATEGORY_ENABLED(Context, Level, Keyword)) + +#ifdef FEATURE_DTRACE + #define ETWOnStartup(StartEventName, EndEventName) \ + ETWTraceStartup trace(StartEventName, EndEventName); + #define ETWFireEvent(EventName) \ + FireEtw##EventName(GetClrInstanceId()); +#else + #define ETWOnStartup(StartEventName, EndEventName) \ + ETWTraceStartup trace##StartEventName##(Microsoft_Windows_DotNETRuntimePrivateHandle, &StartEventName, &StartupId, &EndEventName, &StartupId); + #define ETWFireEvent(EventName) \ + ETWTraceStartup::StartupTraceEvent(Microsoft_Windows_DotNETRuntimePrivateHandle, &EventName, &StartupId); +#endif // FEATURE_DTRACE + +#ifndef FEATURE_REDHAWK + +// Headers +#ifndef FEATURE_PAL +#include +#include +#include +#include +#if !defined(DONOT_DEFINE_ETW_CALLBACK) && !defined(DACCESS_COMPILE) +#define GetVersionEx(Version) (GetOSVersion((LPOSVERSIONINFOW)Version)) +#else +#define GetVersionEx(Version) (WszGetVersionEx((LPOSVERSIONINFOW)Version)) +#endif // !DONOT_DEFINE_ETW_CALLBACK && !DACCESS_COMPILE +#endif // !FEATURE_PAL + +#if FEATURE_DTRACE +#include "clrdtrace.h" +#endif + +#endif //!FEATURE_REDHAWK + + +#else // FEATURE_EVENT_TRACE + +#include "etmdummy.h" +#endif // FEATURE_EVENT_TRACE + +#ifndef FEATURE_REDHAWK + +#if defined(FEATURE_CORECLR) && !defined(FEATURE_CORESYSTEM) +// For Silverlight non-CoreSys builds we still use an older toolset, +// headers/libs, and a different value for WINVER. We use this symbol +// to distinguish between whether we built the ETW header files from +// the ETW manifest using the -mof command line or not. +#define WINXP_AND_WIN2K3_BUILD_SUPPORT +#endif +#include "corprof.h" + +// g_nClrInstanceId is defined in Utilcode\Util.cpp. The definition goes into Utilcode.lib. +// This enables both the VM and Utilcode to raise ETW events. +extern UINT32 g_nClrInstanceId; +extern BOOL g_fEEManagedEXEStartup; +extern BOOL g_fEEIJWStartup; + +#define GetClrInstanceId() (static_cast(g_nClrInstanceId)) + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) +// Callback and stack support +#if !defined(DONOT_DEFINE_ETW_CALLBACK) && !defined(DACCESS_COMPILE) +extern "C" { + /* ETW control callback + * Desc: This function handles the ETW control + * callback. + * Ret: success or failure + ***********************************************/ + VOID EtwCallback( + _In_ LPCGUID SourceId, + _In_ ULONG ControlCode, + _In_ UCHAR Level, + _In_ ULONGLONG MatchAnyKeyword, + _In_ ULONGLONG MatchAllKeyword, + _In_opt_ PEVENT_FILTER_DESCRIPTOR FilterData, + _Inout_opt_ PVOID CallbackContext); +} + +// +// User defined callback +// +#define MCGEN_PRIVATE_ENABLE_CALLBACK(RequestCode, Context, InOutBufferSize, Buffer) \ + EtwCallback(NULL /* SourceId */, (RequestCode==WMI_ENABLE_EVENTS) ? EVENT_CONTROL_CODE_ENABLE_PROVIDER : EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0 /* Level */, 0 /* MatchAnyKeyword */, 0 /* MatchAllKeyword */, NULL /* FilterData */, Context) + +// +// User defined callback2 +// +#define MCGEN_PRIVATE_ENABLE_CALLBACK_V2(SourceId, ControlCode, Level, MatchAnyKeyword, MatchAllKeyword, FilterData, CallbackContext) \ + EtwCallback(SourceId, ControlCode, Level, MatchAnyKeyword, MatchAllKeyword, FilterData, CallbackContext) + +extern "C" { + /* ETW callout + * Desc: This function handles the ETW callout + * Ret: success or failure + ***********************************************/ + VOID EtwCallout( + REGHANDLE RegHandle, + PCEVENT_DESCRIPTOR Descriptor, + ULONG ArgumentCount, + PEVENT_DATA_DESCRIPTOR EventData); +} + +// +// Call user defined callout +// +#define MCGEN_CALLOUT(RegHandle, Descriptor, NumberOfArguments, EventData) \ + EtwCallout(RegHandle, Descriptor, NumberOfArguments, EventData) +#endif //!DONOT_DEFINE_ETW_CALLBACK && !DACCESS_COMPILE + +#include +// The bulk type event is too complex for MC.exe to auto-generate proper code. +// Use code:BulkTypeEventLogger instead. +#ifdef FireEtwBulkType +#undef FireEtwBulkType +#endif // FireEtwBulkType +#endif // FEATURE_EVENT_TRACE && !FEATURE_PAL + +/**************************/ +/* CLR ETW infrastructure */ +/**************************/ +// #CEtwTracer +// On Windows Vista, ETW has gone through a major upgrade, and one of the most significant changes is the +// introduction of the unified event provider model and APIs. The older architecture used the classic ETW +// events. The new ETW architecture uses the manifest based events. To support both types of events at the +// same time, we use the manpp tool for generating event macros that can be directly used to fire ETW events +// from various components within the CLR. +// (http://diagnostics/sites/etw/Lists/Announcements/DispForm.aspx?ID=10&Source=http%3A%2F%2Fdiagnostics%2Fsites%2Fetw%2Fdefault%2Easpx) +// Every ETW provider has to Register itself to the system, so that when enabled, it is capable of firing +// ETW events. file:../VM/eventtrace.cpp#Registration is where the actual Provider Registration takes place. +// At process shutdown, a registered provider need to be unregistered. +// file:../VM/eventtrace.cpp#Unregistration. Since ETW can also be enabled at any instant after the process +// has started, one may want to do something useful when that happens (e.g enumerate all the loaded modules +// in the system). To enable this, we have to implement a callback routine. +// file:../VM/eventtrace.cpp#EtwCallback is CLR's implementation of the callback. +// + +#include "daccess.h" +class Module; +class Assembly; +class MethodDesc; +class MethodTable; +class BaseDomain; +class AppDomain; +class SString; +class CrawlFrame; +class LoaderAllocator; +class AssemblyLoaderAllocator; +struct AllLoggedTypes; +class CrstBase; +class BulkTypeEventLogger; +class TypeHandle; +class Thread; + + +// All ETW helpers must be a part of this namespace +// We have auto-generated macros to directly fire the events +// but in some cases, gathering the event payload information involves some work +// and it can be done in a relevant helper class like the one's in this namespace +namespace ETW +{ + // Class to wrap the ETW infrastructure logic + class CEtwTracer + { +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) + ULONG RegGuids(LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle); +#endif // !FEATURE_PAL + + public: +#ifdef FEATURE_EVENT_TRACE + // Registers all the Event Tracing providers + HRESULT Register(); + + // Unregisters all the Event Tracing providers + HRESULT UnRegister(); +#else + HRESULT Register() + { + return S_OK; + } + HRESULT UnRegister() + { + return S_OK; + } +#endif // FEATURE_EVENT_TRACE + }; + + class LoaderLog; + class MethodLog; + // Class to wrap all the enumeration logic for ETW + class EnumerationLog + { + friend class ETW::LoaderLog; + friend class ETW::MethodLog; +#ifdef FEATURE_EVENT_TRACE + static VOID SendThreadRundownEvent(); + static VOID IterateDomain(BaseDomain *pDomain, DWORD enumerationOptions); + static VOID IterateAppDomain(AppDomain * pAppDomain, DWORD enumerationOptions); + static VOID IterateCollectibleLoaderAllocator(AssemblyLoaderAllocator *pLoaderAllocator, DWORD enumerationOptions); + static VOID IterateAssembly(Assembly *pAssembly, DWORD enumerationOptions); + static VOID IterateModule(Module *pModule, DWORD enumerationOptions); + static VOID EnumerationHelper(Module *moduleFilter, BaseDomain *domainFilter, DWORD enumerationOptions); + static DWORD GetEnumerationOptionsFromRuntimeKeywords(); + public: + typedef union _EnumerationStructs + { + typedef enum _EnumerationOptions + { + None= 0x00000000, + DomainAssemblyModuleLoad= 0x00000001, + DomainAssemblyModuleUnload= 0x00000002, + DomainAssemblyModuleDCStart= 0x00000004, + DomainAssemblyModuleDCEnd= 0x00000008, + JitMethodLoad= 0x00000010, + JitMethodUnload= 0x00000020, + JitMethodDCStart= 0x00000040, + JitMethodDCEnd= 0x00000080, + NgenMethodLoad= 0x00000100, + NgenMethodUnload= 0x00000200, + NgenMethodDCStart= 0x00000400, + NgenMethodDCEnd= 0x00000800, + ModuleRangeLoad= 0x00001000, + ModuleRangeDCStart= 0x00002000, + ModuleRangeDCEnd= 0x00004000, + ModuleRangeLoadPrivate= 0x00008000, + MethodDCStartILToNativeMap= 0x00010000, + MethodDCEndILToNativeMap= 0x00020000, + JitMethodILToNativeMap= 0x00040000, + TypeUnload= 0x00080000, + + // Helpers + ModuleRangeEnabledAny = ModuleRangeLoad | ModuleRangeDCStart | ModuleRangeDCEnd | ModuleRangeLoadPrivate, + JitMethodLoadOrDCStartAny = JitMethodLoad | JitMethodDCStart | MethodDCStartILToNativeMap, + JitMethodUnloadOrDCEndAny = JitMethodUnload | JitMethodDCEnd | MethodDCEndILToNativeMap, + }EnumerationOptions; + }EnumerationStructs; + + static VOID ProcessShutdown(); + static VOID ModuleRangeRundown(); + static VOID StartRundown(); + static VOID EndRundown(); + static VOID EnumerateForCaptureState(); +#else + public: + static VOID ProcessShutdown() {}; + static VOID StartRundown() {}; + static VOID EndRundown() {}; +#endif // FEATURE_EVENT_TRACE + }; + + + // Class to wrap all the sampling logic for ETW + class SamplingLog + { + // StackWalk available only when !FEATURE_PAL +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) + public: + typedef enum _EtwStackWalkStatus + { + Completed = 0, + UnInitialized = 1, + InProgress = 2 + } EtwStackWalkStatus; + private: + static const UINT8 s_MaxStackSize=100; + UINT32 m_FrameCount; + SIZE_T m_EBPStack[SamplingLog::s_MaxStackSize]; + VOID Append(SIZE_T currentFrame); + EtwStackWalkStatus SaveCurrentStack(int skipTopNFrames=1); + public: + static ULONG SendStackTrace(MCGEN_TRACE_CONTEXT TraceContext, PCEVENT_DESCRIPTOR Descriptor, LPCGUID EventGuid); + EtwStackWalkStatus GetCurrentThreadsCallStack(UINT32 *frameCount, PVOID **Stack); +#endif // FEATURE_EVENT_TRACE && !FEATURE_PAL + }; + + // Class to wrap all Loader logic for ETW + class LoaderLog + { + friend class ETW::EnumerationLog; +#ifdef FEATURE_EVENT_TRACE + static VOID SendModuleEvent(Module *pModule, DWORD dwEventOptions, BOOL bFireDomainModuleEvents=FALSE); +#if !defined(FEATURE_PAL) + static ULONG SendModuleRange(Module *pModule, DWORD dwEventOptions); +#endif // !FEATURE_PAL + static VOID SendAssemblyEvent(Assembly *pAssembly, DWORD dwEventOptions); + static VOID SendDomainEvent(BaseDomain *pBaseDomain, DWORD dwEventOptions, LPCWSTR wszFriendlyName=NULL); + public: + typedef union _LoaderStructs + { + typedef enum _AppDomainFlags + { + DefaultDomain=0x1, + ExecutableDomain=0x2, + SharedDomain=0x4 + }AppDomainFlags; + + typedef enum _AssemblyFlags + { + DomainNeutralAssembly=0x1, + DynamicAssembly=0x2, + NativeAssembly=0x4, + CollectibleAssembly=0x8, + }AssemblyFlags; + + typedef enum _ModuleFlags + { + DomainNeutralModule=0x1, + NativeModule=0x2, + DynamicModule=0x4, + ManifestModule=0x8, + IbcOptimized=0x10 + }ModuleFlags; + + typedef enum _RangeFlags + { + HotRange=0x0 + }RangeFlags; + + }LoaderStructs; + + static VOID DomainLoadReal(BaseDomain *pDomain, __in_opt LPWSTR wszFriendlyName=NULL); + + static VOID DomainLoad(BaseDomain *pDomain, __in_opt LPWSTR wszFriendlyName = NULL) + { +#ifndef FEATURE_PAL + if (MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context.IsEnabled) +#endif + { + DomainLoadReal(pDomain, wszFriendlyName); + } + } + + static VOID DomainUnload(AppDomain *pDomain); + static VOID CollectibleLoaderAllocatorUnload(AssemblyLoaderAllocator *pLoaderAllocator); + static VOID ModuleLoad(Module *pModule, LONG liReportedSharedModule); +#else + public: + static VOID DomainLoad(BaseDomain *pDomain, __in_opt LPWSTR wszFriendlyName=NULL) {}; + static VOID DomainUnload(AppDomain *pDomain) {}; + static VOID CollectibleLoaderAllocatorUnload(AssemblyLoaderAllocator *pLoaderAllocator) {}; + static VOID ModuleLoad(Module *pModule, LONG liReportedSharedModule) {}; +#endif // FEATURE_EVENT_TRACE + }; + + // Class to wrap all Method logic for ETW + class MethodLog + { + friend class ETW::EnumerationLog; +#ifdef FEATURE_EVENT_TRACE + static VOID SendEventsForJitMethods(BaseDomain *pDomainFilter, LoaderAllocator *pLoaderAllocatorFilter, DWORD dwEventOptions); + static VOID SendEventsForNgenMethods(Module *pModule, DWORD dwEventOptions); + static VOID SendMethodJitStartEvent(MethodDesc *pMethodDesc, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL); +#ifndef WINXP_AND_WIN2K3_BUILD_SUPPORT + static VOID SendMethodILToNativeMapEvent(MethodDesc * pMethodDesc, DWORD dwEventOptions, ReJITID rejitID); +#endif // WINXP_AND_WIN2K3_BUILD_SUPPORT + static VOID SendMethodEvent(MethodDesc *pMethodDesc, DWORD dwEventOptions, BOOL bIsJit, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL, SIZE_T pCode = 0, ReJITID rejitID = 0); + static VOID SendHelperEvent(ULONGLONG ullHelperStartAddress, ULONG ulHelperSize, LPCWSTR pHelperName); + public: + typedef union _MethodStructs + { + typedef enum _MethodFlags + { + DynamicMethod=0x1, + GenericMethod=0x2, + SharedGenericCode=0x4, + JittedMethod=0x8, + JitHelperMethod=0x10 + }MethodFlags; + + typedef enum _MethodExtent + { + HotSection=0x00000000, + ColdSection=0x10000000 + }MethodExtent; + + }MethodStructs; + + static VOID MethodJitting(MethodDesc *pMethodDesc, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL); + static VOID MethodJitted(MethodDesc *pMethodDesc, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL, SIZE_T pCode = 0, ReJITID rejitID = 0); + static VOID StubInitialized(ULONGLONG ullHelperStartAddress, LPCWSTR pHelperName); + static VOID StubsInitialized(PVOID *pHelperStartAddresss, PVOID *pHelperNames, LONG ulNoOfHelpers); + static VOID MethodRestored(MethodDesc * pMethodDesc); + static VOID MethodTableRestored(MethodTable * pMethodTable); + static VOID DynamicMethodDestroyed(MethodDesc *pMethodDesc); +#else // FEATURE_EVENT_TRACE + public: + static VOID MethodJitting(MethodDesc *pMethodDesc, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL) {}; + static VOID MethodJitted(MethodDesc *pMethodDesc, SString *namespaceOrClassName=NULL, SString *methodName=NULL, SString *methodSignature=NULL, SIZE_T pCode = 0, ReJITID rejitID = 0) {}; + static VOID StubInitialized(ULONGLONG ullHelperStartAddress, LPCWSTR pHelperName) {}; + static VOID StubsInitialized(PVOID *pHelperStartAddresss, PVOID *pHelperNames, LONG ulNoOfHelpers) {}; + static VOID MethodRestored(MethodDesc * pMethodDesc) {}; + static VOID MethodTableRestored(MethodTable * pMethodTable) {}; + static VOID DynamicMethodDestroyed(MethodDesc *pMethodDesc) {}; +#endif // FEATURE_EVENT_TRACE + }; + + // Class to wrap all Security logic for ETW + class SecurityLog + { +#ifdef FEATURE_EVENT_TRACE + public: + static VOID StrongNameVerificationStart(DWORD dwInFlags, __in LPWSTR strFullyQualifiedAssemblyName); + static VOID StrongNameVerificationStop(DWORD dwInFlags,ULONG result, __in LPWSTR strFullyQualifiedAssemblyName); + + static void FireFieldTransparencyComputationStart(LPCWSTR wszFieldName, + LPCWSTR wszModuleName, + DWORD dwAppDomain); + static void FireFieldTransparencyComputationEnd(LPCWSTR wszFieldName, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsCritical, + BOOL fIsTreatAsSafe); + + static void FireMethodTransparencyComputationStart(LPCWSTR wszMethodName, + LPCWSTR wszModuleName, + DWORD dwAppDomain); + static void FireMethodTransparencyComputationEnd(LPCWSTR wszMethodName, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsCritical, + BOOL fIsTreatAsSafe); + + static void FireModuleTransparencyComputationStart(LPCWSTR wszModuleName, DWORD dwAppDomain); + static void FireModuleTransparencyComputationEnd(LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsAllCritical, + BOOL fIsAllTransparent, + BOOL fIsTreatAsSafe, + BOOL fIsOpportunisticallyCritical, + DWORD dwSecurityRuleSet); + + static void FireTokenTransparencyComputationStart(DWORD dwToken, + LPCWSTR wszModuleName, + DWORD dwAppDomain); + static void FireTokenTransparencyComputationEnd(DWORD dwToken, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsCritical, + BOOL fIsTreatAsSafe); + + static void FireTypeTransparencyComputationStart(LPCWSTR wszTypeName, + LPCWSTR wszModuleName, + DWORD dwAppDomain); + static void FireTypeTransparencyComputationEnd(LPCWSTR wszTypeName, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsAllCritical, + BOOL fIsAllTransparent, + BOOL fIsCritical, + BOOL fIsTreatAsSafe); +#else + public: + static VOID StrongNameVerificationStart(DWORD dwInFlags,LPWSTR strFullyQualifiedAssemblyName) {}; + static VOID StrongNameVerificationStop(DWORD dwInFlags,ULONG result, LPWSTR strFullyQualifiedAssemblyName) {}; + + static void FireFieldTransparencyComputationStart(LPCWSTR wszFieldName, + LPCWSTR wszModuleName, + DWORD dwAppDomain) {}; + static void FireFieldTransparencyComputationEnd(LPCWSTR wszFieldName, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsCritical, + BOOL fIsTreatAsSafe) {}; + + static void FireMethodTransparencyComputationStart(LPCWSTR wszMethodName, + LPCWSTR wszModuleName, + DWORD dwAppDomain) {}; + static void FireMethodTransparencyComputationEnd(LPCWSTR wszMethodName, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsCritical, + BOOL fIsTreatAsSafe) {}; + + static void FireModuleTransparencyComputationStart(LPCWSTR wszModuleName, DWORD dwAppDomain) {}; + static void FireModuleTransparencyComputationEnd(LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsAllCritical, + BOOL fIsAllTransparent, + BOOL fIsTreatAsSafe, + BOOL fIsOpportunisticallyCritical, + DWORD dwSecurityRuleSet) {}; + + static void FireTokenTransparencyComputationStart(DWORD dwToken, + LPCWSTR wszModuleName, + DWORD dwAppDomain) {}; + static void FireTokenTransparencyComputationEnd(DWORD dwToken, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsCritical, + BOOL fIsTreatAsSafe) {}; + + static void FireTypeTransparencyComputationStart(LPCWSTR wszTypeName, + LPCWSTR wszModuleName, + DWORD dwAppDomain) {}; + static void FireTypeTransparencyComputationEnd(LPCWSTR wszTypeName, + LPCWSTR wszModuleName, + DWORD dwAppDomain, + BOOL fIsAllCritical, + BOOL fIsAllTransparent, + BOOL fIsCritical, + BOOL fIsTreatAsSafe) {}; +#endif // FEATURE_EVENT_TRACE + }; + + // Class to wrap all Binder logic for ETW + class BinderLog + { + public: + typedef union _BinderStructs { + typedef enum _NGENBINDREJECT_REASON { + NGEN_BIND_START_BIND = 0, + NGEN_BIND_NO_INDEX = 1, + NGEN_BIND_SYSTEM_ASSEMBLY_NOT_AVAILABLE = 2, + NGEN_BIND_NO_NATIVE_IMAGE = 3, + NGEN_BIND_REJECT_CONFIG_MASK = 4, + NGEN_BIND_FAIL = 5, + NGEN_BIND_INDEX_CORRUPTION = 6, + NGEN_BIND_REJECT_TIMESTAMP = 7, + NGEN_BIND_REJECT_NATIVEIMAGE_NOT_FOUND = 8, + NGEN_BIND_REJECT_IL_SIG = 9, + NGEN_BIND_REJECT_LOADER_EVAL_FAIL = 10, + NGEN_BIND_MISSING_FOUND = 11, + NGEN_BIND_REJECT_HOSTASM = 12, + NGEN_BIND_REJECT_IL_NOT_FOUND = 13, + NGEN_BIND_REJECT_APPBASE_NOT_FILE = 14, + NGEN_BIND_BIND_DEPEND_REJECT_REF_DEF_MISMATCH = 15, + NGEN_BIND_BIND_DEPEND_REJECT_NGEN_SIG = 16, + NGEN_BIND_APPLY_EXTERNAL_RELOCS_FAILED = 17, + NGEN_BIND_SYSTEM_ASSEMBLY_NATIVEIMAGE_NOT_AVAILABLE = 18, + NGEN_BIND_ASSEMBLY_HAS_DIFFERENT_GRANT = 19, + NGEN_BIND_ASSEMBLY_NOT_DOMAIN_NEUTRAL = 20, + NGEN_BIND_NATIVEIMAGE_VERSION_MISMATCH = 21, + NGEN_BIND_LOADFROM_NOT_ALLOWED = 22, + NGEN_BIND_DEPENDENCY_HAS_DIFFERENT_IDENTITY = 23 + } NGENBINDREJECT_REASON; + } BinderStructs; + }; + + // Class to wrap all Exception logic for ETW + class ExceptionLog + { + public: +#ifdef FEATURE_EVENT_TRACE + static VOID ExceptionThrown(CrawlFrame *pCf, BOOL bIsReThrownException, BOOL bIsNewException); +#else + static VOID ExceptionThrown(CrawlFrame *pCf, BOOL bIsReThrownException, BOOL bIsNewException) {}; +#endif // FEATURE_EVENT_TRACE + typedef union _ExceptionStructs + { + typedef enum _ExceptionThrownFlags + { + HasInnerException=0x1, + IsNestedException=0x2, + IsReThrownException=0x4, + IsCSE=0x8, + IsCLSCompliant=0x10 + }ExceptionThrownFlags; + }ExceptionStructs; + }; + // Class to wrap all Contention logic for ETW + class ContentionLog + { + public: + typedef union _ContentionStructs + { + typedef enum _ContentionFlags { + ManagedContention=0, + NativeContention=1 + } ContentionFlags; + } ContentionStructs; + }; + // Class to wrap all Interop logic for ETW + class InteropLog + { + public: + }; + + // Class to wrap all Information logic for ETW + class InfoLog + { + public: + typedef union _InfoStructs + { + typedef enum _StartupMode + { + ManagedExe=0x1, + HostedCLR=0x2, + IJW=0x4, + COMActivated=0x8, + Other=0x10 + }StartupMode; + + typedef enum _Sku + { + DesktopCLR=0x1, + CoreCLR=0x2 + }Sku; + + typedef enum _EtwMode + { + Normal=0x0, + Callback=0x1 + }EtwMode; + }InfoStructs; + +#ifdef FEATURE_EVENT_TRACE + static VOID RuntimeInformation(INT32 type); +#else + static VOID RuntimeInformation(INT32 type) {}; +#endif // FEATURE_EVENT_TRACE + }; +}; + + +// +// The ONE and only ONE global instantiation of this class +// +extern ETW::CEtwTracer * g_pEtwTracer; +#define ETW_IS_TRACE_ON(level) ( FALSE ) // for fusion which is eventually going to get removed +#define ETW_IS_FLAG_ON(flag) ( FALSE ) // for fusion which is eventually going to get removed + +// Commonly used constats for ETW Assembly Loader and Assembly Binder events. +#define ETWLoadContextNotAvailable (LOADCTX_TYPE_HOSTED + 1) +#define ETWAppDomainIdNotAvailable 0 // Valid AppDomain IDs start from 1 + +#define ETWFieldUnused 0 // Indicates that a particular field in the ETW event payload template is currently unused. + +#define ETWLoaderLoadTypeNotAvailable 0 // Static or Dynamic Load is only valid at LoaderPhaseStart and LoaderPhaseEnd events - for other events, 0 indicates "not available" +#define ETWLoaderStaticLoad 0 // Static reference load +#define ETWLoaderDynamicLoad 1 // Dynamic assembly load + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) && !defined(WINXP_AND_WIN2K3_BUILD_SUPPORT) +// "mc.exe -MOF" already generates this block for XP-suported builds inside ClrEtwAll.h; +// on Vista+ builds, mc is run without -MOF, and we still have code that depends on it, so +// we manually place it here. +FORCEINLINE +BOOLEAN __stdcall +McGenEventTracingEnabled( + __in PMCGEN_TRACE_CONTEXT EnableInfo, + __in PCEVENT_DESCRIPTOR EventDescriptor + ) +{ + + if(!EnableInfo){ + return FALSE; + } + + + // + // Check if the event Level is lower than the level at which + // the channel is enabled. + // If the event Level is 0 or the channel is enabled at level 0, + // all levels are enabled. + // + + if ((EventDescriptor->Level <= EnableInfo->Level) || // This also covers the case of Level == 0. + (EnableInfo->Level == 0)) { + + // + // Check if Keyword is enabled + // + + if ((EventDescriptor->Keyword == (ULONGLONG)0) || + ((EventDescriptor->Keyword & EnableInfo->MatchAnyKeyword) && + ((EventDescriptor->Keyword & EnableInfo->MatchAllKeyword) == EnableInfo->MatchAllKeyword))) { + return TRUE; + } + } + + return FALSE; +} +#endif // defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) && !defined(WINXP_AND_WIN2K3_BUILD_SUPPORT) + + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) +ETW_INLINE +ULONG +ETW::SamplingLog::SendStackTrace( + MCGEN_TRACE_CONTEXT TraceContext, + PCEVENT_DESCRIPTOR Descriptor, + LPCGUID EventGuid) +{ +#define ARGUMENT_COUNT_CLRStackWalk 5 + ULONG Result = ERROR_SUCCESS; +typedef struct _MCGEN_TRACE_BUFFER { + EVENT_TRACE_HEADER Header; + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_CLRStackWalk]; +} MCGEN_TRACE_BUFFER; + + REGHANDLE RegHandle = TraceContext.RegistrationHandle; + if(!TraceContext.IsEnabled || !McGenEventTracingEnabled(&TraceContext, Descriptor)) + { + return Result; + } + + PVOID *Stack = NULL; + UINT32 FrameCount = 0; + ETW::SamplingLog stackObj; + if(stackObj.GetCurrentThreadsCallStack(&FrameCount, &Stack) == ETW::SamplingLog::Completed) + { + UCHAR Reserved1=0, Reserved2=0; + UINT16 ClrInstanceId = GetClrInstanceId(); + MCGEN_TRACE_BUFFER TraceBuf; + PEVENT_DATA_DESCRIPTOR EventData = TraceBuf.EventData; + + EventDataDescCreate(&EventData[0], &ClrInstanceId, sizeof(const UINT16) ); + + EventDataDescCreate(&EventData[1], &Reserved1, sizeof(const UCHAR) ); + + EventDataDescCreate(&EventData[2], &Reserved2, sizeof(const UCHAR) ); + + EventDataDescCreate(&EventData[3], &FrameCount, sizeof(const unsigned int) ); + + EventDataDescCreate(&EventData[4], Stack, sizeof(PVOID) * FrameCount ); + +#ifdef WINXP_AND_WIN2K3_BUILD_SUPPORT + if (!McGenPreVista) + { + return PfnEventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_CLRStackWalk, EventData); + } + else + { + const MCGEN_TRACE_CONTEXT* Context = (const MCGEN_TRACE_CONTEXT*)(ULONG_PTR)RegHandle; + // + // Fill in header fields + // + + TraceBuf.Header.GuidPtr = (ULONGLONG)EventGuid; + TraceBuf.Header.Flags = WNODE_FLAG_TRACED_GUID |WNODE_FLAG_USE_GUID_PTR|WNODE_FLAG_USE_MOF_PTR; + TraceBuf.Header.Class.Version = (SHORT)Descriptor->Version; + TraceBuf.Header.Class.Level = Descriptor->Level; + TraceBuf.Header.Class.Type = Descriptor->Opcode; + TraceBuf.Header.Size = sizeof(MCGEN_TRACE_BUFFER); + + return TraceEvent(Context->Logger, &TraceBuf.Header); + } +#else // !WINXP_AND_WIN2K3_BUILD_SUPPORT + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_CLRStackWalk, EventData); +#endif // WINXP_AND_WIN2K3_BUILD_SUPPORT + } + return Result; +}; + +#endif // FEATURE_EVENT_TRACE && !FEATURE_PAL + +#ifdef FEATURE_EVENT_TRACE +#ifdef _TARGET_X86_ +struct CallStackFrame +{ + struct CallStackFrame* m_Next; + SIZE_T m_ReturnAddress; +}; +#endif // _TARGET_X86_ +#endif // FEATURE_EVENT_TRACE + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) +FORCEINLINE +BOOLEAN __stdcall +McGenEventProviderEnabled( + __in PMCGEN_TRACE_CONTEXT Context, + __in UCHAR Level, + __in ULONGLONG Keyword + ) +{ + if(!Context) { + return FALSE; + } + +#ifdef WINXP_AND_WIN2K3_BUILD_SUPPORT + if(McGenPreVista){ + return ( ((Level <= Context->Level) || (Context->Level == 0)) && + (((ULONG)(Keyword & 0xFFFFFFFF) == 0) || ((ULONG)(Keyword & 0xFFFFFFFF) & Context->Flags))); + } +#endif // WINXP_AND_WIN2K3_BUILD_SUPPORT + + // + // Check if the event Level is lower than the level at which + // the channel is enabled. + // If the event Level is 0 or the channel is enabled at level 0, + // all levels are enabled. + // + + if ((Level <= Context->Level) || // This also covers the case of Level == 0. + (Context->Level == 0)) { + + // + // Check if Keyword is enabled + // + + if ((Keyword == (ULONGLONG)0) || + ((Keyword & Context->MatchAnyKeyword) && + ((Keyword & Context->MatchAllKeyword) == Context->MatchAllKeyword))) { + return TRUE; + } + } + return FALSE; +} +#endif // FEATURE_EVENT_TRACE && !FEATURE_PAL + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) + +// This macro only checks if a provider is enabled +// It does not check the flags and keywords for which it is enabled +#define ETW_PROVIDER_ENABLED(ProviderSymbol) \ + ProviderSymbol##_Context.IsEnabled + +#define FireEtwGCPerHeapHistorySpecial(DataPerHeap, DataSize, ClrInstanceId)\ + MCGEN_ENABLE_CHECK(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, GCPerHeapHistory) ?\ + Etw_GCDataPerHeapSpecial(&GCPerHeapHistory, &GarbageCollectionPrivateId, DataPerHeap, DataSize, ClrInstanceId)\ + : ERROR_SUCCESS\ + +// The GC uses this macro around its heap walk so the TypeSystemLog's crst can be held +// for the duration of the walk (if the ETW client has requested type information). +#define ETW_HEAP_WALK_HOLDER(__fShouldWalkHeapRootsForEtw, __fShouldWalkHeapObjectsForEtw) \ + CrstHolderWithState __crstHolderWithState(ETW::TypeSystemLog::GetHashCrst(), ((__fShouldWalkHeapRootsForEtw) || (__fShouldWalkHeapObjectsForEtw))) + +#else + +// For ETM, we rely on DTrace to do the checking +#define ETW_PROVIDER_ENABLED(ProviderSymbol) TRUE +#define FireEtwGCPerHeapHistorySpecial(DataPerHeap, DataSize, ClrInstanceId) 0 + +#endif // FEATURE_EVENT_TRACE && !FEATURE_PAL + +#endif // !FEATURE_REDHAWK +// These parts of the ETW namespace are common for both FEATURE_REDHAWK and +// !FEATURE_REDHAWK builds. + + +struct ProfilingScanContext; +struct ProfilerWalkHeapContext; +class Object; + +namespace ETW +{ + // Class to wrap the logging of threads (runtime and rundown providers) + class ThreadLog + { + private: + static DWORD GetEtwThreadFlags(Thread * pThread); + + public: + static VOID FireThreadCreated(Thread * pThread); + static VOID FireThreadDC(Thread * pThread); + }; +}; + +#ifndef FEATURE_REDHAWK + +#ifdef FEATURE_EVENT_TRACE + +// +// Use this macro at the least before calling the Event Macros +// + +#define ETW_TRACING_INITIALIZED(RegHandle) \ + (g_pEtwTracer && RegHandle) + +// +// Use this macro to check if an event is enabled +// if the fields in the event are not cheap to calculate +// +#define ETW_EVENT_ENABLED(Context, EventDescriptor) \ + (MCGEN_ENABLE_CHECK(Context, EventDescriptor)) + +// +// Use this macro to check if a category of events is enabled +// + +#define ETW_CATEGORY_ENABLED(Context, Level, Keyword) \ + (Context.IsEnabled && McGenEventProviderEnabled(&Context, Level, Keyword)) + + + +// +// Special Handling of Startup events +// + +#if defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) && !defined(WINXP_AND_WIN2K3_BUILD_SUPPORT) +// "mc.exe -MOF" already generates this block for XP-suported builds inside ClrEtwAll.h; +// on Vista+ builds, mc is run without -MOF, and we still have code that depends on it, so +// we manually place it here. +ETW_INLINE +ULONG +CoMofTemplate_h( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCGUID EventGuid, + __in const unsigned short ClrInstanceID + ) +{ +#define ARGUMENT_COUNT_h 1 + ULONG Error = ERROR_SUCCESS; +typedef struct _MCGEN_TRACE_BUFFER { + EVENT_TRACE_HEADER Header; + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_h]; +} MCGEN_TRACE_BUFFER; + + MCGEN_TRACE_BUFFER TraceBuf; + PEVENT_DATA_DESCRIPTOR EventData = TraceBuf.EventData; + + EventDataDescCreate(&EventData[0], &ClrInstanceID, sizeof(const unsigned short) ); + + + { + Error = EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_h, EventData); + + } + +#ifdef MCGEN_CALLOUT +MCGEN_CALLOUT(RegHandle, + Descriptor, + ARGUMENT_COUNT_h, + EventData); +#endif + + return Error; +} +#endif // defined(FEATURE_EVENT_TRACE) && !defined(FEATURE_PAL) && !defined(WINXP_AND_WIN2K3_BUILD_SUPPORT) + +class ETWTraceStartup { +#ifndef FEATURE_DTRACE + REGHANDLE TraceHandle; + PCEVENT_DESCRIPTOR EventStartDescriptor; + LPCGUID EventStartGuid; + PCEVENT_DESCRIPTOR EventEndDescriptor; + LPCGUID EventEndGuid; +public: + ETWTraceStartup(REGHANDLE _TraceHandle, PCEVENT_DESCRIPTOR _EventStartDescriptor, LPCGUID _EventStartGuid, PCEVENT_DESCRIPTOR _EventEndDescriptor, LPCGUID _EventEndGuid) { + TraceHandle = _TraceHandle; + EventStartDescriptor = _EventStartDescriptor; + EventEndDescriptor = _EventEndDescriptor; + EventStartGuid = _EventStartGuid; + EventEndGuid = _EventEndGuid; + StartupTraceEvent(TraceHandle, EventStartDescriptor, EventStartGuid); + } + ~ETWTraceStartup() { + StartupTraceEvent(TraceHandle, EventEndDescriptor, EventEndGuid); + } + static void StartupTraceEvent(REGHANDLE _TraceHandle, PCEVENT_DESCRIPTOR _EventDescriptor, LPCGUID _EventGuid) { + EVENT_DESCRIPTOR desc = *_EventDescriptor; + if(ETW_TRACING_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, desc)) + { +#ifndef FEATURE_PAL + CoMofTemplate_h(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context.RegistrationHandle, _EventDescriptor, _EventGuid, GetClrInstanceId()); +#endif // !FEATURE_PAL + } + } +#else //!FEATURE_DTRACE + void (*startFP)(); + void (*endFP)(); +public: + ETWTraceStartup(void (*sFP)(), void (*eFP)()) : startFP (sFP), endFP(eFP) { + (*startFP)(); + } + ~ETWTraceStartup() { + (*endFP)(); + } +#endif //!FEATURE_DTRACE +}; + + + +#else // FEATURE_EVENT_TRACE + +#define ETWOnStartup(StartEventName, EndEventName) +#define ETWFireEvent(EventName) + +// Use this macro at the least before calling the Event Macros +#define ETW_TRACING_INITIALIZED(RegHandle) (FALSE) + +// Use this macro to check if an event is enabled +// if the fields in the event are not cheap to calculate +#define ETW_EVENT_ENABLED(Context, EventDescriptor) (FALSE) + +// Use this macro to check if a category of events is enabled +#define ETW_CATEGORY_ENABLED(Context, Level, Keyword) (FALSE) + +// Use this macro to check if ETW is initialized and the event is enabled +#define ETW_TRACING_ENABLED(Context, EventDescriptor) (FALSE) + +// Use this macro to check if ETW is initialized and the category is enabled +#define ETW_TRACING_CATEGORY_ENABLED(Context, Level, Keyword) (FALSE) + +#endif // FEATURE_EVENT_TRACE + +#endif // FEATURE_REDHAWK + +#endif //_ETWTRACER_HXX_ diff --git a/src/Native/Runtime/eventtracepriv.h b/src/Native/Runtime/eventtracepriv.h new file mode 100644 index 00000000000..2324485e9cb --- /dev/null +++ b/src/Native/Runtime/eventtracepriv.h @@ -0,0 +1,211 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// +// File: eventtracepriv.h +// +// Contains some private definitions used by eventrace.cpp, but that aren't needed by +// clients of eventtrace.cpp, and thus don't belong in eventtrace.h. Also, since +// inclusions of this file are tightly controlled (basically just by eventtrace.cpp), we +// can assume some classes are defined that aren't necessarily defined when eventtrace.h +// is #included (e.g., StackSString and StackSArray). +// +// ============================================================================ + +#ifndef __EVENTTRACEPRIV_H__ +#define __EVENTTRACEPRIV_H__ + +#ifndef _countof +#define _countof(_array) (sizeof(_array)/sizeof(_array[0])) +#endif + +const UINT cbMaxEtwEvent = 64 * 1024; + +//--------------------------------------------------------------------------------------- +// C++ copies of ETW structures +//--------------------------------------------------------------------------------------- + +// !!!!!!! NOTE !!!!!!!! +// The EventStruct* structs are described in the ETW manifest event templates, and the +// LAYOUT MUST MATCH THE MANIFEST EXACTLY! +// !!!!!!! NOTE !!!!!!!! + +#pragma pack(push, 1) + +struct EventStructGCBulkRootEdgeValue +{ + LPVOID RootedNodeAddress; + BYTE GCRootKind; + DWORD GCRootFlag; + LPVOID GCRootID; +}; + +struct EventStructGCBulkRootConditionalWeakTableElementEdgeValue +{ + LPVOID GCKeyNodeID; + LPVOID GCValueNodeID; + LPVOID GCRootID; +}; + +struct EventStructGCBulkNodeValue +{ + LPVOID Address; + ULONGLONG Size; + ULONGLONG TypeID; + ULONGLONG EdgeCount; +}; + +struct EventStructGCBulkEdgeValue +{ + LPVOID Value; + ULONG ReferencingFieldID; +}; + +struct EventStructGCBulkSurvivingObjectRangesValue +{ + LPVOID RangeBase; + ULONGLONG RangeLength; +}; + +struct EventStructGCBulkMovedObjectRangesValue +{ + LPVOID OldRangeBase; + LPVOID NewRangeBase; + ULONGLONG RangeLength; +}; + +// This only contains the fixed-size data at the top of each struct in +// the bulk type event. These fields must still match exactly the initial +// fields of the struct described in the manifest. +struct EventStructBulkTypeFixedSizedData +{ + ULONGLONG TypeID; + ULONGLONG ModuleID; + ULONG TypeNameID; + ULONG Flags; + BYTE CorElementType; +}; + +#pragma pack(pop) + + + +// Represents one instance of the Value struct inside a single BulkType event +class BulkTypeValue +{ +public: + BulkTypeValue(); + void Clear(); + + // How many bytes will this BulkTypeValue take up when written into the actual ETW + // event? + int GetByteCountInEvent() + { + return + sizeof(fixedSizedData) + + sizeof(cTypeParameters) + +#ifdef FEATURE_REDHAWK + sizeof(WCHAR) + // No name in event, so just the null terminator + cTypeParameters * sizeof(ULONGLONG); // Type parameters +#else + (sName.GetCount() + 1) * sizeof(WCHAR) + // Size of name, including null terminator + rgTypeParameters.GetCount() * sizeof(ULONGLONG);// Type parameters +#endif + } + + EventStructBulkTypeFixedSizedData fixedSizedData; + + // Below are the remainder of each struct in the bulk type event (i.e., the + // variable-sized data). The var-sized fields are copied into the event individually + // (not directly), so they don't need to have the same layout as in the ETW manifest + + // This is really a denorm of the size already stored in rgTypeParameters, but we + // need a persistent place to stash this away so EventDataDescCreate & EventWrite + // have a reliable place to copy it from. This is filled in at the last minute, + // when sending the event. (On ProjectN, which doesn't have StackSArray, this is + // filled in earlier and used in more places.) + ULONG cTypeParameters; + +#ifdef FEATURE_REDHAWK + // If > 1 type parameter, this is an array of their EEType*'s + NewArrayHolder rgTypeParameters; + + // If exactly one type parameter, this is its EEType*. (If != 1 type parameter, + // this is 0.) + ULONGLONG ullSingleTypeParameter; +#else // FEATURE_REDHAWK + StackSString sName; + StackSArray rgTypeParameters; +#endif // FEATURE_REDHAWK +}; + +// Encapsulates all the type event batching we need to do. This is used by +// ETW::TypeSystemLog, which calls LogTypeAndParameters for each type to be logged. +// BulkTypeEventLogger will batch each type and its generic type parameters, and flush to +// ETW as necessary. ETW::TypeSystemLog also calls FireBulkTypeEvent directly to force a +// flush (e.g., once at end of GC heap traversal, or on each object allocation). +class BulkTypeEventLogger +{ +private: + + // Estimate of how many bytes we can squeeze in the event data for the value struct + // array. (Intentionally overestimate the size of the non-array parts to keep it safe.) + static const int kMaxBytesTypeValues = (cbMaxEtwEvent - 0x30); + + // Estimate of how many type value elements we can put into the struct array, while + // staying under the ETW event size limit. Note that this is impossible to calculate + // perfectly, since each element of the struct array has variable size. + // + // In addition to the byte-size limit per event, Windows always forces on us a + // max-number-of-descriptors per event, which in the case of BulkType, will kick in + // far sooner. There's a max number of 128 descriptors allowed per event. 2 are used + // for Count + ClrInstanceID. Then 4 per batched value. (Might actually be 3 if there + // are no type parameters to log, but let's overestimate at 4 per value). + static const int kMaxCountTypeValues = (128 - 2) / 4; + // Note: This results in a relatively small batch (about 31 types per event). We + // could increase this substantially by creating a single, contiguous buffer, which + // would let us max out the number of type values to batch by allowing the byte-size + // limit to kick in before the max-descriptor limit. We could esimate that as + // follows: + // + // static const int kMaxCountTypeValues = kMaxBytesTypeValues / + // (sizeof(EventStructBulkTypeFixedSizedData) + + // 200 * sizeof(WCHAR) + // Assume 199 + 1 terminating-NULL character in type name + // sizeof(UINT) + // Type parameter count + // 10 * sizeof(ULONGLONG)); // Assume 10 type parameters + // + // The downside, though, is that we would have to do a lot more copying to fill out + // that buffer before sending the event. It's unclear that increasing the batch size + // is enough of a win to offset all the extra buffer copying. So for now, we'll keep + // the batch size low and avoid extra copying. + + // How many types have we batched? + int m_nBulkTypeValueCount; + + // What is the byte size of all the types we've batched? + int m_nBulkTypeValueByteCount; + + // List of types we've batched. + BulkTypeValue m_rgBulkTypeValues[kMaxCountTypeValues]; + +#ifdef FEATURE_REDHAWK + int LogSingleType(EEType * pEEType); +#else + int LogSingleType(TypeHandle th); +#endif + +public: + BulkTypeEventLogger() : + m_nBulkTypeValueCount(0), + m_nBulkTypeValueByteCount(0) + { + LIMITED_METHOD_CONTRACT; + } + + void LogTypeAndParameters(ULONGLONG thAsAddr, ETW::TypeSystemLog::TypeLogBehavior typeLogBehavior); + void FireBulkTypeEvent(); + void Cleanup(); +}; + +#endif // __EVENTTRACEPRIV_H__ \ No newline at end of file diff --git a/src/Native/Runtime/forward_declarations.h b/src/Native/Runtime/forward_declarations.h new file mode 100644 index 00000000000..71db77bb8fc --- /dev/null +++ b/src/Native/Runtime/forward_declarations.h @@ -0,0 +1,57 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// This file may be included by header files to forward declare common +// public types. The intent here is that .CPP files should need to +// include fewer header files. + +#define FWD_DECL(x) \ + class x; \ + typedef DPTR(x) PTR_##x; + +// rtu +FWD_DECL(AllocHeap) +FWD_DECL(CObjectHeader) +FWD_DECL(CLREventStatic) +FWD_DECL(CrstHolder) +FWD_DECL(CrstStatic) +FWD_DECL(EEMethodInfo) +FWD_DECL(EECodeManager) +FWD_DECL(EEThreadId) +FWD_DECL(InstanceStore) +FWD_DECL(MethodInfo) +FWD_DECL(Module) +FWD_DECL(Object) +FWD_DECL(OBJECTHANDLEHolder) +FWD_DECL(PageEntry) +FWD_DECL(PAL_EnterHolder) +FWD_DECL(PAL_LeaveHolder) +FWD_DECL(SpinLock) +FWD_DECL(RCOBJECTHANDLEHolder) +FWD_DECL(RedhawkGCInterface) +FWD_DECL(RtuObjectRef) +FWD_DECL(RuntimeInstance) +FWD_DECL(SectionMethodList) +FWD_DECL(StackFrameIterator) +FWD_DECL(SyncClean) +FWD_DECL(SyncState) +FWD_DECL(Thread) +FWD_DECL(ThreadStore) +FWD_DECL(VirtualCallStubManager) + +#ifdef FEATURE_RWX_MEMORY +namespace rh { + namespace util { + FWD_DECL(MemRange) + FWD_DECL(MemAccessMgr) + FWD_DECL(WriteAccessHolder) + } +} +#endif // FEATURE_RWX_MEMORY + +// inc +FWD_DECL(EEInterfaceInfo) +FWD_DECL(EEType) + diff --git a/src/Native/Runtime/gcdump.cpp b/src/Native/Runtime/gcdump.cpp new file mode 100644 index 00000000000..b374d1587ec --- /dev/null +++ b/src/Native/Runtime/gcdump.cpp @@ -0,0 +1,454 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +/***************************************************************************** + * GCDump.cpp + * + * Defines functions to display the GCInfo as defined by the GC-encoding + * spec. The GC information may be either dynamically created by a + * Just-In-Time compiler conforming to the standard code-manager spec, + * or may be persisted by a managed native code compiler conforming + * to the standard code-manager spec. + */ +#include "common.h" + +#if defined(_DEBUG) || defined(DACCESS_COMPILE) + +#include "gcrhenv.h" // @TODO: move off of gcrhenv.h +#include "gcinfo.h" +#include "gcdump.h" + +/*****************************************************************************/ + +#ifdef DACCESS_COMPILE +static void DacNullPrintf(const char* , ...) {} +#endif + +GCDump::GCDump() +{ +#ifndef DACCESS_COMPILE + // By default, use the standard printf function to dump + GCDump::gcPrintf = (printfFtn) ::printf; +#else + // Default for DAC is a no-op. + GCDump::gcPrintf = DacNullPrintf; +#endif +} + + + +/*****************************************************************************/ + +static char * calleeSaveRegMaskBitNumberToName[] = +{ +#ifdef _ARM_ + "R4", + "R5", + "R6", + "R7", + "R8", + "R9", + "R10", + "R11", + "LR", +#else // _ARM_ + "EBX", + "ESI", + "EDI", + "EBP", + "R12", + "R13", + "R14", + "R15" +#endif // _ARM_ +}; + +size_t FASTCALL GCDump::DumpInfoHeader (PTR_UInt8 gcInfo, + Tables * pTables, + GCInfoHeader * pHeader /* OUT */ + ) +{ + size_t headerSize = 0; + PTR_UInt8 gcInfoStart = gcInfo; + PTR_UInt8 pbStackChanges = 0; + PTR_UInt8 pbUnwindInfo = 0; + + unsigned unwindInfoBlobOffset = VarInt::ReadUnsigned(gcInfo); + bool inlineUnwindInfo = (unwindInfoBlobOffset == 0); + + if (inlineUnwindInfo) + { + // it is inline.. + pbUnwindInfo = gcInfo; + } + else + { + // The offset was adjusted by 1 to reserve the 0 encoding for the inline case, so we re-adjust it to + // the actual offset here. + pbUnwindInfo = pTables->pbUnwindInfoBlob + unwindInfoBlobOffset - 1; + } + + // @TODO: decode all funclet headers as well. + pbStackChanges = pHeader->DecodeHeader(0, pbUnwindInfo, &headerSize ); + + if (inlineUnwindInfo) + gcInfo += headerSize; + + unsigned epilogCount = pHeader->GetEpilogCount(); + bool epilogAtEnd = pHeader->IsEpilogAtEnd(); + + gcPrintf(" prologSize: %d\r\n", pHeader->GetPrologSize()); + if (pHeader->HasVaryingEpilogSizes()) + gcPrintf(" epilogSize: (varies)\r\n"); + else + gcPrintf(" epilogSize: %d\r\n", pHeader->GetFixedEpilogSize()); + + gcPrintf(" epilogCount: %d %s\r\n", epilogCount, epilogAtEnd ? "[end]" : ""); + char * returnKind = "????"; + unsigned reversePinvokeFrameOffset = 0; // it can't be 0 because [ebp+0] is the previous ebp + switch (pHeader->GetReturnKind()) + { + case GCInfoHeader::MRK_ReturnsScalar: returnKind = "scalar"; break; + case GCInfoHeader::MRK_ReturnsObject: returnKind = "object"; break; + case GCInfoHeader::MRK_ReturnsByref: returnKind = "byref"; break; + case GCInfoHeader::MRK_ReturnsToNative: + returnKind = "to native"; + reversePinvokeFrameOffset = pHeader->GetReversePinvokeFrameOffset(); + break; + } + gcPrintf(" returnKind: %s\r\n", returnKind); + gcPrintf(" frameKind: %s", pHeader->HasFramePointer() ? "EBP" : "ESP"); +#ifdef TARGET_AMD64 + if (pHeader->HasFramePointer()) + gcPrintf(" offset: %d", pHeader->GetFramePointerOffset()); +#endif // _AMD64_ + gcPrintf("\r\n"); + gcPrintf(" frameSize: %d\r\n", pHeader->GetFrameSize()); + + if (pHeader->HasDynamicAlignment()) { + gcPrintf(" alignment: %d\r\n", (1 << pHeader->GetDynamicAlignment())); + if (pHeader->GetParamPointerReg() != RN_NONE) { + gcPrintf(" paramReg: %d\r\n", pHeader->GetParamPointerReg()); + } + } + + gcPrintf(" savedRegs: "); + CalleeSavedRegMask savedRegs = pHeader->GetSavedRegs(); + CalleeSavedRegMask mask = (CalleeSavedRegMask) 1; + for (int i = 0; i < RBM_CALLEE_SAVED_REG_COUNT; i++) + { + if (savedRegs & mask) + { + gcPrintf("%s ", calleeSaveRegMaskBitNumberToName[i]); + } + mask = (CalleeSavedRegMask)(mask << 1); + } + gcPrintf("\r\n"); + +#ifdef TARGET_ARM + gcPrintf(" parmRegsPushedCount: %d\r\n", pHeader->ParmRegsPushedCount()); +#endif + +#ifdef TARGET_X86 + gcPrintf(" returnPopSize: %d\r\n", pHeader->GetReturnPopSize()); + if (pHeader->HasStackChanges()) + { + // @TODO: need to read the stack changes string that follows + ASSERT(!"NYI -- stack changes for ESP frames"); + } +#endif + + if (reversePinvokeFrameOffset != 0) + { + gcPrintf(" reversePinvokeFrameOffset: 0x%02x\r\n", reversePinvokeFrameOffset); + } + + + if (!epilogAtEnd || (epilogCount > 2)) + { + gcPrintf(" epilog offsets: "); + unsigned previousOffset = 0; + for (unsigned idx = 0; idx < epilogCount; idx++) + { + unsigned newOffset = previousOffset + VarInt::ReadUnsigned(gcInfo); + gcPrintf("0x%04x ", newOffset); + if (pHeader->HasVaryingEpilogSizes()) + gcPrintf("(%u bytes) ", VarInt::ReadUnsigned(gcInfo)); + previousOffset = newOffset; + } + gcPrintf("\r\n"); + } + + return gcInfo - gcInfoStart; +} + +void GCDump::PrintLocalSlot(UInt32 slotNum, GCInfoHeader const * pHeader) +{ + // @TODO: print both EBP/ESP offsets where appropriate +#ifdef TARGET_ARM + gcPrintf("local slot 0n%d, [R7+%02X] \r\n", slotNum, + ((GCInfoHeader*)pHeader)->GetFrameSize() - ((slotNum + 1) * POINTER_SIZE)); +#else + char* regAndSign = "EBP-"; + size_t offset = pHeader->GetPreservedRegsSaveSize() + (slotNum * POINTER_SIZE); +# ifdef TARGET_AMD64 + if (((GCInfoHeader*)pHeader)->GetFramePointerOffset() == 0) + { + regAndSign = "RBP-"; + } + else + { + regAndSign = "RBP+"; + offset = (slotNum * POINTER_SIZE); + } +# endif + gcPrintf("local slot 0n%d, [%s%02X] \r\n", slotNum, regAndSign, offset); +#endif +} + +void GCDump::DumpCallsiteString(UInt32 callsiteOffset, PTR_UInt8 pbCallsiteString, + GCInfoHeader const * pHeader) +{ + gcPrintf("%04x: ", callsiteOffset); + + int count = 0; + UInt8 b; + PTR_UInt8 pCursor = pbCallsiteString; + + bool last = false; + bool first = true; + + do + { + if (!first) + gcPrintf(" "); + + first = false; + + b = *pCursor++; + last = ((b & 0x20) == 0x20); + + switch (b & 0xC0) + { + case 0x00: + { + // case 2 -- "register set" + gcPrintf("%02x | 2 ", b); +#ifdef TARGET_ARM + if (b & CSR_MASK_R4) { gcPrintf("R4 "); count++; } + if (b & CSR_MASK_R5) { gcPrintf("R5 "); count++; } + if (b & CSR_MASK_R6) { gcPrintf("R6 "); count++; } + if (b & CSR_MASK_R7) { gcPrintf("R7 "); count++; } + if (b & CSR_MASK_R8) { gcPrintf("R8 "); count++; } +#else // _ARM_ + if (b & CSR_MASK_RBX) { gcPrintf("RBX "); count++; } + if (b & CSR_MASK_RSI) { gcPrintf("RSI "); count++; } + if (b & CSR_MASK_RDI) { gcPrintf("RDI "); count++; } + if (b & CSR_MASK_RBP) { gcPrintf("RBP "); count++; } + if (b & CSR_MASK_R12) { gcPrintf("R12 "); count++; } +#endif // _ARM_ + gcPrintf("\r\n"); + } + break; + + case 0x40: + { + // case 3 -- "register" + char* regName = "???"; + char* interior = (b & 0x10) ? "+" : ""; + char* pinned = (b & 0x08) ? "!" : ""; + + switch (b & 0x7) + { +#ifdef TARGET_ARM + case CSR_NUM_R4: regName = "R4"; break; + case CSR_NUM_R5: regName = "R5"; break; + case CSR_NUM_R6: regName = "R6"; break; + case CSR_NUM_R7: regName = "R7"; break; + case CSR_NUM_R8: regName = "R8"; break; + case CSR_NUM_R9: regName = "R9"; break; + case CSR_NUM_R10: regName = "R10"; break; + case CSR_NUM_R11: regName = "R11"; break; +#else // _ARM_ + case CSR_NUM_RBX: regName = "RBX"; break; + case CSR_NUM_RSI: regName = "RSI"; break; + case CSR_NUM_RDI: regName = "RDI"; break; + case CSR_NUM_RBP: regName = "RBP"; break; + case CSR_NUM_R12: regName = "R12"; break; + case CSR_NUM_R13: regName = "R13"; break; + case CSR_NUM_R14: regName = "R14"; break; + case CSR_NUM_R15: regName = "R15"; break; +#endif // _ARM_ + } + gcPrintf("%02x | 3 %s%s%s \r\n", b, regName, interior, pinned); + count++; + } + break; + + case 0x80: + { + if (b & 0x10) + { + // case 4 -- "local slot set" + gcPrintf("%02x | 4 ", b); + bool isFirst = true; + + int mask = 0x01; + int slotNum = 0; + while (mask <= 0x08) + { + if (b & mask) + { + if (!isFirst) + { + if (!first) + gcPrintf(" "); + gcPrintf(" | "); + } + + PrintLocalSlot(slotNum, pHeader); + + isFirst = false; + count++; + } + mask <<= 1; + slotNum++; + } + } + else + { + // case 5 -- "local slot" + int slotNum = (int)(b & 0xF) + 4; + gcPrintf("%02x | 5 ", b); + PrintLocalSlot(slotNum, pHeader); + + count++; + } + } + break; + case 0xC0: + { + gcPrintf("%02x ", b); + unsigned mask = 0; + PTR_UInt8 pInts = pCursor; + unsigned offset = VarInt::ReadUnsigned(pCursor); + char* interior = (b & 0x10) ? "+" : ""; + char* pinned = (b & 0x08) ? "!" : ""; +#ifdef TARGET_ARM + char* baseReg = (b & 0x04) ? "R7" : "SP"; +#else + char* baseReg = (b & 0x04) ? "EBP" : "ESP"; +#endif + char* sign = (b & 0x02) ? "-" : "+"; + if (b & 0x01) + { + mask = VarInt::ReadUnsigned(pCursor); + } + + int c = 1; + while (pInts != pCursor) + { + gcPrintf("%02x ", *pInts++); + c++; + } + + for (; c < 4; c++) + { + gcPrintf(" "); + } + + gcPrintf("| 6 [%s%s%02X]%s%s\r\n", baseReg, sign, offset, interior, pinned); + count++; + + while (mask > 0) + { + offset += POINTER_SIZE; + if (mask & 1) + { + if (!first) + gcPrintf(" "); + + gcPrintf(" | [%s%s%02X]%s%s\r\n", baseReg, sign, offset, interior, pinned); + count++; + } + mask >>= 1; + } + } + break; + } + } + while (!last); + + //gcPrintf("\r\n"); +} + + + + +size_t FASTCALL GCDump::DumpGCTable (PTR_UInt8 gcInfo, + Tables * pTables, + const GCInfoHeader& header) +{ + // + // Decode the method GC info + // + // 0ddddccc -- SMALL ENCODING + // + // -- dddd is an index into the delta shortcut table + // -- ccc is an offset into the callsite strings blob + // + // 1ddddddd { info offset } -- BIG ENCODING + // + // -- ddddddd is a 7-bit delta + // -- { info offset } is a variable-length unsigned encoding of the offset into the callsite + // strings blob for this callsite. + // + // 10000000 { delta } -- FORWARDER + // + // -- { delta } is a variable-length unsigned encoding of the offset to the next callsite + // + // 11111111 -- STRING TERMINATOR + // + + PTR_UInt8 pCursor = gcInfo; + UInt32 curOffset = 0; + + for (;;) + { + UInt8 b = *pCursor++; + unsigned infoOffset; + + if (b & 0x80) + { + UInt8 lowBits = (b & 0x7F); + // FORWARDER + if (lowBits == 0) + { + curOffset += VarInt::ReadUnsigned(pCursor); + continue; + } + else + if (lowBits == 0x7F) // STRING TERMINATOR + break; + + // BIG ENCODING + curOffset += lowBits; + infoOffset = VarInt::ReadUnsigned(pCursor); + } + else + { + // SMALL ENCODING + infoOffset = (b & 0x7); + curOffset += pTables->pbDeltaShortcutTable[b >> 3]; + } + + DumpCallsiteString(curOffset, pTables->pbCallsiteInfoBlob + infoOffset, &header); + } + + gcPrintf("-------\r\n"); + + return 0; +} + +#endif // _DEBUG || DACCESS_COMPILE \ No newline at end of file diff --git a/src/Native/Runtime/gcdump.h b/src/Native/Runtime/gcdump.h new file mode 100644 index 00000000000..1ec5eb1f87c --- /dev/null +++ b/src/Native/Runtime/gcdump.h @@ -0,0 +1,79 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +/***************************************************************************** + * GCDump.h + * + * Defines functions to display the GCInfo as defined by the GC-encoding + * spec. The GC information may be either dynamically created by a + * Just-In-Time compiler conforming to the standard code-manager spec, + * or may be persisted by a managed native code compiler conforming + * to the standard code-manager spec. + */ + +/*****************************************************************************/ +#ifndef __GCDUMP_H__ +#define __GCDUMP_H__ +/*****************************************************************************/ + +struct GCInfoHeader; + +#ifndef FASTCALL +#define FASTCALL __fastcall +#endif + + +class GCDump +{ +public: + + struct Tables + { + PTR_UInt8 pbDeltaShortcutTable; + PTR_UInt8 pbUnwindInfoBlob; + PTR_UInt8 pbCallsiteInfoBlob; + }; + + + GCDump (); + + /*------------------------------------------------------------------------- + * Dumps the GCInfoHeader to 'stdout' + * gcInfo : Start of the GC info block + * Return value : Size in bytes of the header encoding + */ + + size_t FASTCALL DumpInfoHeader(PTR_UInt8 gcInfo, + Tables * pTables, + GCInfoHeader * header /* OUT */ + ); + + /*------------------------------------------------------------------------- + * Dumps the GC tables to 'stdout' + * gcInfo : Ptr to the start of the table part of the GC info. + * This immediately follows the GCinfo header + * Return value : Size in bytes of the GC table encodings + */ + + size_t FASTCALL DumpGCTable(PTR_UInt8 gcInfo, + Tables * pTables, + const GCInfoHeader& header + ); + + + typedef void (*printfFtn)(const char* fmt, ...); + printfFtn gcPrintf; + + + + //------------------------------------------------------------------------- +protected: + + void PrintLocalSlot(UInt32 slotNum, GCInfoHeader const * pHeader); + void DumpCallsiteString(UInt32 callsiteOffset, PTR_UInt8 pbCallsiteString, GCInfoHeader const * pHeader); +}; + +/*****************************************************************************/ +#endif // __GC_DUMP_H__ +/*****************************************************************************/ diff --git a/src/Native/Runtime/gcinfo.h b/src/Native/Runtime/gcinfo.h new file mode 100644 index 00000000000..f18f9bb991a --- /dev/null +++ b/src/Native/Runtime/gcinfo.h @@ -0,0 +1,1356 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +/*****************************************************************************/ +#ifndef _GCINFO_H_ +#define _GCINFO_H_ +/*****************************************************************************/ + +#ifdef TARGET_ARM + +#define NUM_PRESERVED_REGS 9 + +enum RegMask +{ + RBM_R0 = 0x0001, + RBM_R1 = 0x0002, + RBM_R2 = 0x0004, + RBM_R3 = 0x0008, + RBM_R4 = 0x0010, // callee saved + RBM_R5 = 0x0020, // callee saved + RBM_R6 = 0x0040, // callee saved + RBM_R7 = 0x0080, // callee saved + RBM_R8 = 0x0100, // callee saved + RBM_R9 = 0x0200, // callee saved + RBM_R10 = 0x0400, // callee saved + RBM_R11 = 0x0800, // callee saved + RBM_R12 = 0x1000, + RBM_SP = 0x2000, + RBM_LR = 0x4000, // callee saved, but not valid to be alive across a call! + RBM_PC = 0x8000, + RBM_RETVAL = RBM_R0, + RBM_CALLEE_SAVED_REGS = (RBM_R4|RBM_R5|RBM_R6|RBM_R7|RBM_R8|RBM_R9|RBM_R10|RBM_R11|RBM_LR), + RBM_CALLEE_SAVED_REG_COUNT = 9, + // Special case: LR is callee saved, but may not appear as a live GC ref except + // in the leaf frame because calls will trash it. Therefore, we ALSO consider + // it a scratch register. + RBM_SCRATCH_REGS = (RBM_R0|RBM_R1|RBM_R2|RBM_R3|RBM_R12|RBM_LR), + RBM_SCRATCH_REG_COUNT = 6, +}; + +enum RegNumber +{ + RN_R0 = 0, + RN_R1 = 1, + RN_R2 = 2, + RN_R3 = 3, + RN_R4 = 4, + RN_R5 = 5, + RN_R6 = 6, + RN_R7 = 7, + RN_R8 = 8, + RN_R9 = 9, + RN_R10 = 10, + RN_R11 = 11, + RN_R12 = 12, + RN_SP = 13, + RN_LR = 14, + RN_PC = 15, + + RN_NONE = 16, +}; + +enum CalleeSavedRegNum +{ + CSR_NUM_R4 = 0x00, + CSR_NUM_R5 = 0x01, + CSR_NUM_R6 = 0x02, + CSR_NUM_R7 = 0x03, + CSR_NUM_R8 = 0x04, + CSR_NUM_R9 = 0x05, + CSR_NUM_R10 = 0x06, + CSR_NUM_R11 = 0x07, + // NOTE: LR is omitted because it may not be live except as a 'scratch' reg +}; + +enum CalleeSavedRegMask +{ + CSR_MASK_NONE = 0x00, + CSR_MASK_R4 = 0x001, + CSR_MASK_R5 = 0x002, + CSR_MASK_R6 = 0x004, + CSR_MASK_R7 = 0x008, + CSR_MASK_R8 = 0x010, + CSR_MASK_R9 = 0x020, + CSR_MASK_R10 = 0x040, + CSR_MASK_R11 = 0x080, + CSR_MASK_LR = 0x100, + + CSR_MASK_ALL = 0x1ff, + CSR_MASK_HIGHEST = 0x100, +}; + +enum ScratchRegNum +{ + SR_NUM_R0 = 0x00, + SR_NUM_R1 = 0x01, + SR_NUM_R2 = 0x02, + SR_NUM_R3 = 0x03, + SR_NUM_R12 = 0x04, + SR_NUM_LR = 0x05, +}; + +enum ScratchRegMask +{ + SR_MASK_NONE = 0x00, + SR_MASK_R0 = 0x01, + SR_MASK_R1 = 0x02, + SR_MASK_R2 = 0x04, + SR_MASK_R3 = 0x08, + SR_MASK_R12 = 0x10, + SR_MASK_LR = 0x20, +}; + +#else // TARGET_ARM + +#ifdef TARGET_X64 +#define NUM_PRESERVED_REGS 8 +#else +#define NUM_PRESERVED_REGS 4 +#endif + +enum RegMask +{ + RBM_EAX = 0x0001, + RBM_ECX = 0x0002, + RBM_EDX = 0x0004, + RBM_EBX = 0x0008, // callee saved + RBM_ESP = 0x0010, + RBM_EBP = 0x0020, // callee saved + RBM_ESI = 0x0040, // callee saved + RBM_EDI = 0x0080, // callee saved + + RBM_R8 = 0x0100, + RBM_R9 = 0x0200, + RBM_R10 = 0x0400, + RBM_R11 = 0x0800, + RBM_R12 = 0x1000, // callee saved + RBM_R13 = 0x2000, // callee saved + RBM_R14 = 0x4000, // callee saved + RBM_R15 = 0x8000, // callee saved + + RBM_RETVAL = RBM_EAX, + +#ifdef TARGET_X64 + RBM_CALLEE_SAVED_REGS = (RBM_EDI|RBM_ESI|RBM_EBX|RBM_EBP|RBM_R12|RBM_R13|RBM_R14|RBM_R15), + RBM_CALLEE_SAVED_REG_COUNT = 8, + RBM_SCRATCH_REGS = (RBM_EAX|RBM_ECX|RBM_EDX|RBM_R8|RBM_R9|RBM_R10|RBM_R11), + RBM_SCRATCH_REG_COUNT = 7, +#else + RBM_CALLEE_SAVED_REGS = (RBM_EDI|RBM_ESI|RBM_EBX|RBM_EBP), + RBM_CALLEE_SAVED_REG_COUNT = 4, + RBM_SCRATCH_REGS = (RBM_EAX|RBM_ECX|RBM_EDX), + RBM_SCRATCH_REG_COUNT = 3, +#endif // TARGET_X64 +}; + +enum RegNumber +{ + RN_EAX = 0, + RN_ECX = 1, + RN_EDX = 2, + RN_EBX = 3, + RN_ESP = 4, + RN_EBP = 5, + RN_ESI = 6, + RN_EDI = 7, + RN_R8 = 8, + RN_R9 = 9, + RN_R10 = 10, + RN_R11 = 11, + RN_R12 = 12, + RN_R13 = 13, + RN_R14 = 14, + RN_R15 = 15, + + RN_NONE = 16, +}; + +enum CalleeSavedRegNum +{ + CSR_NUM_RBX = 0x00, + CSR_NUM_RSI = 0x01, + CSR_NUM_RDI = 0x02, + CSR_NUM_RBP = 0x03, + CSR_NUM_R12 = 0x04, + CSR_NUM_R13 = 0x05, + CSR_NUM_R14 = 0x06, + CSR_NUM_R15 = 0x07, +}; + +enum CalleeSavedRegMask +{ + CSR_MASK_NONE = 0x00, + CSR_MASK_RBX = 0x01, + CSR_MASK_RSI = 0x02, + CSR_MASK_RDI = 0x04, + CSR_MASK_RBP = 0x08, + CSR_MASK_R12 = 0x10, + CSR_MASK_R13 = 0x20, + CSR_MASK_R14 = 0x40, + CSR_MASK_R15 = 0x80, + +#ifdef TARGET_X64 + CSR_MASK_ALL = 0xFF, + CSR_MASK_HIGHEST = 0x80, +#else + CSR_MASK_ALL = 0x0F, + CSR_MASK_HIGHEST = 0x08, +#endif +}; + +enum ScratchRegNum +{ + SR_NUM_RAX = 0x00, + SR_NUM_RCX = 0x01, + SR_NUM_RDX = 0x02, + SR_NUM_R8 = 0x03, + SR_NUM_R9 = 0x04, + SR_NUM_R10 = 0x05, + SR_NUM_R11 = 0x06, +}; + +enum ScratchRegMask +{ + SR_MASK_NONE = 0x00, + SR_MASK_RAX = 0x01, + SR_MASK_RCX = 0x02, + SR_MASK_RDX = 0x04, + SR_MASK_R8 = 0x08, + SR_MASK_R9 = 0x10, + SR_MASK_R10 = 0x20, + SR_MASK_R11 = 0x40, +}; + +#endif // TARGET_ARM + +struct GCInfoHeader +{ +private: + UInt16 prologSize : 6; // 0 [0:5] // @TODO: define an 'overflow' encoding for big prologs? + UInt16 hasFunclets : 1; // 0 [6] + UInt16 fixedEpilogSize : 6; // 0 [7] + 1 [0:4] '0' encoding implies that epilog size varies and is encoded for each epilog + UInt16 epilogCountSmall : 2; // 1 [5:6] '3' encoding implies the number of epilogs is encoded separately + UInt16 dynamicAlign : 1; // 1 [7] + +#ifdef TARGET_ARM + UInt16 returnKind : 2; // 2 [0:1] one of: MethodReturnKind enum + UInt16 ebpFrame : 1; // 2 [2] on x64, this means "has frame pointer and it is RBP", on ARM R7 + UInt16 epilogAtEnd : 1; // 2 [3] + UInt16 hasFrameSize : 1; // 2 [4] 1: frame size is encoded below, 0: frame size is 0 + UInt16 calleeSavedRegMask : NUM_PRESERVED_REGS; // 2 [5:7] 3 [0:5] + UInt16 arm_areParmOrVfpRegsPushed:1; // 1: pushed parm register set from R0-R3 and pushed fp reg start and count is encoded below, 0: no pushed parm or fp registers +#else // TARGET_ARM + UInt8 returnKind : 2; // 2 [0:1] one of: MethodReturnKind enum + UInt8 ebpFrame : 1; // 2 [2] on x64, this means "has frame pointer and it is RBP", on ARM R7 + UInt8 epilogAtEnd : 1; // 2 [3] +#ifdef TARGET_X64 + UInt8 hasFrameSize : 1; // 2 [4] 1: frame size is encoded below, 0: frame size is 0 + UInt8 x64_framePtrOffsetSmall : 2; // 2 [5:6] 00: framePtrOffset = 0x20 + // 01: framePtrOffset = 0x30 + // 10: framePtrOffset = 0x40 + // 11: a variable-length integer 'x64_frameOffset' follows. + UInt8 x64_hasSavedXmmRegs : 1; // 2 [7] any saved xmm registers? +#endif + // X86 X64 + UInt8 calleeSavedRegMask : NUM_PRESERVED_REGS; // 2 [4:7] 3 [0:7] + +#ifndef TARGET_X64 + UInt8 x86_argCountLow : 5; // 3 [0-4] expressed in pointer-sized units // @TODO: steal more bits here? + UInt8 x86_argCountIsLarge : 1; // 3 [5] if this bit is set, then the high 8 bits are encoded in x86_argCountHigh + UInt8 x86_hasStackChanges : 1; // 3 [6] x86-only, !ebpFrame-only, this method has pushes + // and pops in it, and a string follows this header + // which describes them + UInt8 hasFrameSize : 1; // 3 [7] 1: frame size is encoded below, 0: frame size is 0 +#endif +#endif // TARGET_ARM + + // + // OPTIONAL FIELDS FOLLOW + // + // The following values are encoded with variable-length integers on disk, but are decoded into these + // fields in memory. + // + UInt32 frameSize; // expressed in pointer-sized units, only encoded if hasFrameSize==1 + + + // OPTIONAL: only encoded if returnKind = MRK_ReturnsToNative + UInt32 reversePinvokeFrameOffset; // expressed in pointer-sized units away from the frame pointer + +#ifdef TARGET_X64 + // OPTIONAL: only encoded if x64_framePtrOffsetSmall = 11 + // + // ENCODING NOTE: In the encoding, the variable-sized unsigned will be 7 less than the total number + // of 16-byte units that make up the frame pointer offset. + // + // In memory, this value will always be set and will always be the total number of 16-byte units that make + // up the frame pointer offset. + UInt8 x64_framePtrOffset; // expressed in 16-byte unit + + // OPTIONAL: only encoded using a variable-sized unsigned if x64_hasSavedXmmRegs is set. + // + // An additional optimization is possible because registers xmm0 .. xmm5 should never be saved, + // so they are not encoded in the variable-sized unsigned - instead the mask is shifted right 6 bits + // for encoding. Thus, any subset of registers xmm6 .. xmm12 can be represented using one byte + // - this covers the most frequent cases. + // + // The shift applies to decoding/encoding only though - the actual header field below uses the + // straightforward mapping where bit 0 corresponds to xmm0, bit 1 corresponds to xmm1 and so on. + // + UInt16 x64_savedXmmRegMask; // which xmm regs were saved +#elif defined(TARGET_X86) + // OPTIONAL: only encoded if x86_argCountIsLarge = 1 + // NOTE: because we are using pointer-sized units, only 14 bits are required to represent the entire range + // that can be expressed by a 'ret NNNN' instruction. Therefore, with 6 in the 'low' field and 8 in the + // 'high' field, we are not losing any range here. (Although the need for that full range is debatable.) + UInt8 x86_argCountHigh; +#endif + +#ifdef TARGET_ARM + UInt8 arm_parmRegsPushedSet; + UInt8 arm_vfpRegFirstPushed; + UInt8 arm_vfpRegPushedCount; +#endif + // + // OPTIONAL: only encoded if dynamicAlign = 1 + UInt8 logStackAlignment; + UInt8 paramPointerReg; + + // OPTIONAL: only encoded if epilogCountSmall == 3 + UInt16 epilogCount; + + // + // OPTIONAL: only encoded if hasFunclets = 1 + // {numFunclets} // encoded as variable-length unsigned + // {start-funclet0} // offset from start of previous funclet, encoded as variable-length unsigned + // {start-funclet1} // + // {start-funclet2} + // ... + // {sizeof-funclet(N-1)} // numFunclets == N (i.e. there are N+1 sizes here) + // ----------------- + // {GCInfoHeader-funclet0} // encoded as normal, must not have 'hasFunclets' set. + // {GCInfoHeader-funclet1} + // ... + // {GCInfoHeader-funclet(N-1)} + + // WARNING: + // WARNING: Do not add fields to the file-format after the funclet header encodings -- these are decoded + // WARNING: recursively and 'in-place' when looking for the info associated with a funclet. Therefore, + // WARNING: in that case, we cannot easily continue to decode things associated with the main body + // WARNING: GCInfoHeader once we start this recursive decode. + // WARNING: + + // ------------------------------------------------------------------------------------------------------- + // END of file-encoding-related-fields + // ------------------------------------------------------------------------------------------------------- + + // The following fields are not encoded in the file format, they are just used as convenience placeholders + // for decode state. + UInt32 funcletOffset; // non-zero indicates that this GCInfoHeader is for a funclet + +#if defined(BINDER) +public: + UInt32 cbThisCodeBody; + GCInfoHeader * pNextFunclet; +private: + ; +#endif // BINDER + + +public: + // + // CONSTANTS / STATIC STUFF + // + + enum MethodReturnKind + { + MRK_ReturnsScalar = 0, + MRK_ReturnsObject = 1, + MRK_ReturnsByref = 2, + MRK_ReturnsToNative = 3, + MRK_Unknown = 4, + }; + + enum EncodingConstants + { + EC_SizeOfFixedHeader = 4, + EC_MaxFrameByteSize = 10*1024*1024, + EC_MaxReversePInvokeFrameByteOffset = 10*1024*1024, + EC_MaxX64FramePtrByteOffset = UInt16_MAX * 0x10, + EC_MaxEpilogCountSmall = 3, + EC_MaxEpilogCount = 64*1024 - 1, + }; + + // + // MEMBER FUNCTIONS + // + + void Init() + { + memset(this, 0, sizeof(GCInfoHeader)); + } + + // + // SETTERS + // + + void SetPrologSize(UInt32 sizeInBytes) + { + prologSize = sizeInBytes; + ASSERT(prologSize == sizeInBytes); + } + + void SetHasFunclets(bool fHasFunclets) + { + hasFunclets = fHasFunclets ? 1 : 0; + } + + void SetFixedEpilogSize(UInt32 sizeInBytes, bool varyingSizes) + { + if (varyingSizes) + fixedEpilogSize = 0; + else + { + ASSERT(sizeInBytes != 0); + fixedEpilogSize = sizeInBytes; + ASSERT(fixedEpilogSize == sizeInBytes); + } + } + + void SetEpilogCount(UInt32 count, bool isAtEnd) + { + epilogCount = ToUInt16(count); + epilogAtEnd = isAtEnd ? 1 : 0; + + ASSERT(epilogCount == count); + ASSERT((count == 1) || !isAtEnd); + epilogCountSmall = count < EC_MaxEpilogCountSmall ? count : EC_MaxEpilogCountSmall; + } + + void SetReturnKind(MethodReturnKind kind) + { + ASSERT(kind < MRK_Unknown); // not enough bits to encode 'unknown' + returnKind = kind; + } + + void SetDynamicAlignment(UInt8 logByteAlignment) + { +#ifdef TARGET_X86 + ASSERT(logByteAlignment >= 3); // 4 byte aligned frames +#else + ASSERT(logByteAlignment >= 4); // 8 byte aligned frames +#endif + + dynamicAlign = 1; + logStackAlignment = logByteAlignment; + paramPointerReg = RN_NONE; + } + + void SetParamPointer(RegNumber regNum, UInt32 offsetInBytes, bool isOffsetFromSP = false) + { + UNREFERENCED_PARAMETER(offsetInBytes); + UNREFERENCED_PARAMETER(isOffsetFromSP); + ASSERT(dynamicAlign==1); // only expected for dynamic aligned frames + ASSERT(offsetInBytes==0); // not yet supported + + paramPointerReg = (UInt8)regNum; + } + + void SetFramePointer(RegNumber regNum, UInt32 offsetInBytes, bool isOffsetFromSP = false) + { + UNREFERENCED_PARAMETER(offsetInBytes); + UNREFERENCED_PARAMETER(isOffsetFromSP); + + if (regNum == RN_NONE) + { + ebpFrame = 0; + } + else + { +#ifdef TARGET_ARM + ASSERT(regNum == RN_R7); +#else + ASSERT(regNum == RN_EBP); +#endif + ebpFrame = 1; + } + ASSERT(offsetInBytes == 0 || isOffsetFromSP); + +#ifdef TARGET_X64 + if (isOffsetFromSP) + offsetInBytes += SKEW_FOR_OFFSET_FROM_SP; + + ASSERT((offsetInBytes % 0x10) == 0); + UInt32 offsetInSlots = offsetInBytes / 0x10; + if (offsetInSlots >= 3 && offsetInSlots <= 3 + 2) + { + x64_framePtrOffsetSmall = offsetInSlots - 3; + } + else + { + x64_framePtrOffsetSmall = 3; + } + x64_framePtrOffset = (UInt8)offsetInSlots; + ASSERT(x64_framePtrOffset == offsetInSlots); +#else + ASSERT(offsetInBytes == 0 && !isOffsetFromSP); +#endif // TARGET_X64 + } + + void SetFrameSize(UInt32 frameSizeInBytes) + { + ASSERT(0 == (frameSizeInBytes % POINTER_SIZE)); + frameSize = (frameSizeInBytes / POINTER_SIZE); + ASSERT(frameSize == (frameSizeInBytes / POINTER_SIZE)); + if (frameSize != 0) + { + hasFrameSize = 1; + } + } + + void SetSavedRegs(CalleeSavedRegMask regMask) + { + calleeSavedRegMask = regMask; + } + + void SetRegSaved(CalleeSavedRegMask regMask) + { + calleeSavedRegMask |= regMask; + } + + void SetReversePinvokeFrameOffset(int offsetInBytes) + { + ASSERT(HasFramePointer()); + ASSERT((offsetInBytes % POINTER_SIZE) == 0); + ASSERT(GetReturnKind() == MRK_ReturnsToNative); + +#if defined(TARGET_ARM) || defined(TARGET_X64) + // The offset can be either positive or negative on ARM and x64. + bool isNeg = (offsetInBytes < 0); + UInt32 uOffsetInBytes = isNeg ? -offsetInBytes : offsetInBytes; + UInt32 uEncodedVal = ((uOffsetInBytes / POINTER_SIZE) << 1) | (isNeg ? 1 : 0); + reversePinvokeFrameOffset = uEncodedVal; + ASSERT(reversePinvokeFrameOffset == uEncodedVal); +#else + // Use a positive number because it encodes better and + // the offset is always negative on x86. + ASSERT(offsetInBytes < 0); + reversePinvokeFrameOffset = (-offsetInBytes / POINTER_SIZE); + ASSERT(reversePinvokeFrameOffset == (UInt32)(-offsetInBytes / POINTER_SIZE)); +#endif + } + +#ifdef TARGET_X86 + void SetReturnPopSize(UInt32 popSizeInBytes) + { + ASSERT(0 == (popSizeInBytes % POINTER_SIZE)); + ASSERT(GetReturnPopSize() == 0 || GetReturnPopSize() == (int)popSizeInBytes); + + UInt32 argCount = popSizeInBytes / POINTER_SIZE; + x86_argCountLow = argCount & 0x1F; + if (argCount != x86_argCountLow) + { + x86_argCountIsLarge = 1; + x86_argCountHigh = (UInt8)(argCount >> 5); + } + } + + void SetHasStackChanges() + { + x86_hasStackChanges = 1; + } +#endif // TARGET_X86 + +#ifdef TARGET_ARM + void SetParmRegsPushed(ScratchRegMask pushedParmRegs) + { + // should be a subset of {RO-R3} + ASSERT((pushedParmRegs & ~(SR_MASK_R0|SR_MASK_R1|SR_MASK_R2|SR_MASK_R3)) == 0); + arm_areParmOrVfpRegsPushed = pushedParmRegs != 0 || arm_vfpRegPushedCount != 0; + arm_parmRegsPushedSet = (UInt8)pushedParmRegs; + } + + void SetVfpRegsPushed(UInt8 vfpRegFirstPushed, UInt8 vfpRegPushedCount) + { + // mrt100.dll really only supports pushing a subinterval of d8-d15 + // these are the preserved floating point registers according to the ABI spec + ASSERT(8 <= vfpRegFirstPushed && vfpRegFirstPushed + vfpRegPushedCount <= 16 || vfpRegPushedCount == 0); + arm_vfpRegFirstPushed = vfpRegFirstPushed; + arm_vfpRegPushedCount = vfpRegPushedCount; + arm_areParmOrVfpRegsPushed = arm_parmRegsPushedSet != 0 || vfpRegPushedCount != 0; + } +#endif + +#ifdef TARGET_X64 + void SetSavedXmmRegs(UInt32 savedXmmRegMask) + { + // any subset of xmm6-xmm15 may be saved, but no registers in xmm0-xmm5 should be present + ASSERT((savedXmmRegMask & 0xffff003f) == 0); + x64_hasSavedXmmRegs = savedXmmRegMask != 0; + x64_savedXmmRegMask = (UInt16)savedXmmRegMask; + } +#endif // TARGET_X64 + + // + // GETTERS + // + UInt32 GetPrologSize() + { + return prologSize; + } + + bool HasFunclets() + { + return (hasFunclets != 0); + } + + bool HasVaryingEpilogSizes() + { + return fixedEpilogSize == 0; + } + + UInt32 GetFixedEpilogSize() + { + ASSERT(!HasVaryingEpilogSizes()); + return fixedEpilogSize; + } + + UInt32 GetEpilogCount() + { + return epilogCount; + } + + bool IsEpilogAtEnd() + { + return (epilogAtEnd != 0); + } + + MethodReturnKind GetReturnKind() + { + return (MethodReturnKind)returnKind; + } + + bool ReturnsToNative() + { + return (GetReturnKind() == MRK_ReturnsToNative); + } + + bool HasFramePointer() + { + return !!ebpFrame; + } + + bool IsFunclet() + { + return funcletOffset != 0; + } + + UInt32 GetFuncletOffset() + { + return funcletOffset; + } + + int GetPreservedRegsSaveSize() const // returned in bytes + { + UInt32 count = 0; + UInt32 mask = calleeSavedRegMask; + while (mask != 0) + { + count += mask & 1; + mask >>= 1; + } + + return count * POINTER_SIZE; + } + + int GetParamPointerReg() + { + return paramPointerReg; + } + + bool HasDynamicAlignment() + { + return dynamicAlign; + } + + UInt32 GetDynamicAlignment() + { + return 1 << logStackAlignment; + } + +#if defined(RHDUMP) && !defined(TARGET_X64) + // Due to the wackiness of RhDump, we need this method defined, even though it won't ever be called. + int GetFramePointerOffset() { ASSERT(!"UNREACHABLE"); __assume(0); } +#endif // defined(RHDUMP) && !defined(TARGET_X64) + +#ifdef TARGET_X64 + static const UInt32 SKEW_FOR_OFFSET_FROM_SP = 0x10; + + int GetFramePointerOffset() // returned in bytes + { + // traditional frames where FP points to the pushed FP have fp offset == 0 + if (x64_framePtrOffset == 0) + return 0; + + // otherwise it's an x64 style frame where the fp offset is measured from the sp + // at the end of the prolog + int offsetFromSP = GetFramePointerOffsetFromSP(); + + int preservedRegsSaveSize = GetPreservedRegsSaveSize(); + + // we when called from the binder, rbp isn't set to be a preserved reg, + // when called from the runtime, it is - compensate for this inconsistency + if (IsRegSaved(CSR_MASK_RBP)) + preservedRegsSaveSize -= POINTER_SIZE; + + return offsetFromSP - preservedRegsSaveSize - GetFrameSize(); + } + + bool IsFramePointerOffsetFromSP() + { + return x64_framePtrOffset != 0; + } + + int GetFramePointerOffsetFromSP() + { + ASSERT(IsFramePointerOffsetFromSP()); + int offsetFromSP; + offsetFromSP = x64_framePtrOffset * 0x10; + ASSERT(offsetFromSP >= SKEW_FOR_OFFSET_FROM_SP); + offsetFromSP -= SKEW_FOR_OFFSET_FROM_SP; + + return offsetFromSP; + } + + int GetFramePointerReg() + { + return RN_EBP; + } + + bool HasSavedXmmRegs() + { + return x64_hasSavedXmmRegs != 0; + } + + UInt16 GetSavedXmmRegMask() + { + ASSERT(x64_hasSavedXmmRegs); + return x64_savedXmmRegMask; + } +#elif defined(TARGET_X86) + int GetReturnPopSize() // returned in bytes + { + if (!x86_argCountIsLarge) + { + return x86_argCountLow * POINTER_SIZE; + } + return ((x86_argCountHigh << 5) | x86_argCountLow) * POINTER_SIZE; + } + + bool HasStackChanges() + { + return !!x86_hasStackChanges; + } +#endif + + int GetFrameSize() + { + return frameSize * POINTER_SIZE; + } + + + int GetReversePinvokeFrameOffset() + { +#if defined(TARGET_ARM) || defined(TARGET_X64) + // The offset can be either positive or negative on ARM. + Int32 offsetInBytes; + UInt32 uEncodedVal = reversePinvokeFrameOffset; + bool isNeg = ((uEncodedVal & 1) == 1); + offsetInBytes = (uEncodedVal >> 1) * POINTER_SIZE; + offsetInBytes = isNeg ? -offsetInBytes : offsetInBytes; + return offsetInBytes; +#else + // it's always at "EBP - something", so we encode it as a positive + // number and then apply the negative here. + int unsignedOffset = reversePinvokeFrameOffset * POINTER_SIZE; + return -unsignedOffset; +#endif + } + + CalleeSavedRegMask GetSavedRegs() + { + return (CalleeSavedRegMask) calleeSavedRegMask; + } + + bool IsRegSaved(CalleeSavedRegMask reg) + { + return (0 != (calleeSavedRegMask & reg)); + } + +#ifdef TARGET_ARM + bool AreParmRegsPushed() + { + return arm_parmRegsPushedSet != 0; + } + + UInt16 ParmRegsPushedCount() + { + UInt8 set = arm_parmRegsPushedSet; + UInt8 count = 0; + while (set != 0) + { + count += set & 1; + set >>= 1; + } + return count; + } + + UInt8 GetVfpRegFirstPushed() + { + return arm_vfpRegFirstPushed; + } + + UInt8 GetVfpRegPushedCount() + { + return arm_vfpRegPushedCount; + } +#endif + + // + // ENCODING HELPERS + // +#ifndef DACCESS_COMPILE + size_t EncodeHeader(UInt8 * & pDest) + { +#ifdef _DEBUG + UInt8 * pStart = pDest; +#endif // _DEBUG + size_t size = EC_SizeOfFixedHeader; + if (pDest) + { + memcpy(pDest, this, EC_SizeOfFixedHeader); + pDest += EC_SizeOfFixedHeader; + } + + if (hasFrameSize) + size += WriteUnsigned(pDest, frameSize); + + if (returnKind == MRK_ReturnsToNative) + size += WriteUnsigned(pDest, reversePinvokeFrameOffset); + +#ifdef TARGET_X64 + if (x64_framePtrOffsetSmall == 0x3) + size += WriteUnsigned(pDest, x64_framePtrOffset); + if (x64_hasSavedXmmRegs) + { + ASSERT((x64_savedXmmRegMask & 0x3f) == 0); + UInt32 encodedValue = x64_savedXmmRegMask >> 6; + size += WriteUnsigned(pDest, encodedValue); + } +#elif defined(TARGET_X86) + if (x86_argCountIsLarge) + { + size += 1; + if (pDest) + { + *pDest = x86_argCountHigh; + pDest++; + } + } +#endif + +#ifdef TARGET_ARM + if (arm_areParmOrVfpRegsPushed) + { + // we encode a bit field where the low 4 bits represent the pushed parameter register + // set, the next 8 bits are the number of pushed floating point registers, and the highest + // bits are the first pushed floating point register plus 1. + // The 0 encoding means the first floating point register is 8 as this is the most frequent. + UInt32 encodedValue = arm_parmRegsPushedSet | (arm_vfpRegPushedCount << 4); + // usually, the first pushed floating point register is d8 + if (arm_vfpRegFirstPushed != 8) + encodedValue |= (arm_vfpRegFirstPushed+1) << (8+4); + size += WriteUnsigned(pDest, encodedValue); + } +#endif + + // encode dynamic alignment information + if (dynamicAlign) + { + size += WriteUnsigned(pDest, logStackAlignment); + size += WriteUnsigned(pDest, paramPointerReg); + } + + if (epilogCountSmall == EC_MaxEpilogCountSmall) + { + size += WriteUnsigned(pDest, epilogCount); + } + + // WARNING: + // WARNING: Do not add fields to the file-format after the funclet header encodings -- these are + // WARNING: decoded recursively and 'in-place' when looking for the info associated with a funclet. + // WARNING: Therefore, in that case, we cannot easily continue to decode things associated with the + // WARNING: main body GCInfoHeader once we start this recursive decode. + // WARNING: + size += EncodeFuncletInfo(pDest); + +#ifdef _DEBUG + ASSERT(!pDest || (size == (size_t)(pDest - pStart))); +#endif // _DEBUG + + return size; + } + + size_t WriteUnsigned(UInt8 * & pDest, UInt32 value) + { + size_t size = (size_t)VarInt::WriteUnsigned(pDest, value); + pDest = pDest ? (pDest + size) : pDest; + return size; + } + + size_t EncodeFuncletInfo(UInt8 * & pDest) + { + size_t size = 0; +#if defined(BINDER) + if (hasFunclets) + { + UInt32 nFunclets = 0; + for (GCInfoHeader * pCur = pNextFunclet; pCur != NULL; pCur = pCur->pNextFunclet) + nFunclets++; + + // first write out the number of funclets + size += WriteUnsigned(pDest, nFunclets); + + // cbThisCodeBody is the size, but what we end up encoding is the size of all the code bodies + // except for the last one (because the last one's size can be implicitly figured out by the size + // of the method). So we have to save the size of the 'main body' and not the size of the last + // funclet. In the encoding, this will look like the offset of a given funclet from the start of + // the previous code body. We like relative offsets because they'll encode to be smaller. + for (GCInfoHeader * pCur = this; pCur->pNextFunclet != NULL; pCur = pCur->pNextFunclet) + size += WriteUnsigned(pDest, pCur->cbThisCodeBody); + + // now encode all the funclet headers + for (GCInfoHeader * pCur = pNextFunclet; pCur != NULL; pCur = pCur->pNextFunclet) + size += pCur->EncodeHeader(pDest); + } +#else // BINDER + ASSERT(!"NOT REACHABLE"); +#endif // BINDER + return size; + } +#endif // DACCESS_COMPILE + + UInt16 ToUInt16(UInt32 val) + { + UInt16 result = (UInt16)val; + ASSERT(val == result); + return result; + } + + UInt8 ToUInt8(UInt32 val) + { + UInt8 result = (UInt8)val; + ASSERT(val == result); + return result; + } + + // + // DECODING HELPERS + // + // Returns a pointer to the 'stack change string' on x86. + PTR_UInt8 DecodeHeader(UInt32 methodOffset, PTR_UInt8 pbHeaderEncoding, size_t* pcbHeader) + { + PTR_UInt8 pbStackChangeString = NULL; + + TADDR pbTemp = PTR_TO_TADDR(pbHeaderEncoding); + memcpy(this, PTR_READ(pbTemp, EC_SizeOfFixedHeader), EC_SizeOfFixedHeader); + + PTR_UInt8 pbDecode = pbHeaderEncoding + EC_SizeOfFixedHeader; + frameSize = hasFrameSize + ? VarInt::ReadUnsigned(pbDecode) + : 0; + + reversePinvokeFrameOffset = (returnKind == MRK_ReturnsToNative) + ? VarInt::ReadUnsigned(pbDecode) + : 0; + +#ifdef TARGET_X64 + x64_framePtrOffset = (x64_framePtrOffsetSmall == 0x3) + ? ToUInt8(VarInt::ReadUnsigned(pbDecode)) + : x64_framePtrOffsetSmall + 3; + + + x64_savedXmmRegMask = 0; + if (x64_hasSavedXmmRegs) + { + UInt32 encodedValue = VarInt::ReadUnsigned(pbDecode); + ASSERT((encodedValue & ~0x3ff) == 0); + x64_savedXmmRegMask = ToUInt16(encodedValue << 6); + } + +#elif defined(TARGET_X86) + if (x86_argCountIsLarge) + x86_argCountHigh = *pbDecode++; + else + x86_argCountHigh = 0; + + if (x86_hasStackChanges) + { + pbStackChangeString = pbDecode; + + bool last = false; + while (!last) + { + UInt8 b = *pbDecode++; + // 00111111 {delta} forwarder + // 00dddddd push 1, dddddd = delta + // nnnldddd pop nnn-1, l = last, dddd = delta (nnn=0 and nnn=1 are disallowed) + if (b == 0x3F) + { + // 00111111 {delta} forwarder + VarInt::ReadUnsigned(pbDecode); + } + else if (0 != (b & 0xC0)) + { + // nnnldddd pop nnn-1, l = last, dddd = delta (nnn=0 and nnn=1 are disallowed) + last = ((b & 0x10) == 0x10); + } + } + } +#elif defined(TARGET_ARM) + arm_parmRegsPushedSet = 0; + arm_vfpRegPushedCount = 0; + arm_vfpRegFirstPushed = 0; + if (arm_areParmOrVfpRegsPushed) + { + UInt32 encodedValue = VarInt::ReadUnsigned(pbDecode); + arm_parmRegsPushedSet = encodedValue & 0x0f; + arm_vfpRegPushedCount = (UInt8)(encodedValue >> 4); + UInt32 vfpRegFirstPushed = encodedValue >> (8 + 4); + if (vfpRegFirstPushed == 0) + arm_vfpRegFirstPushed = 8; + else + arm_vfpRegFirstPushed = (UInt8)(vfpRegFirstPushed - 1); + } +#endif + + logStackAlignment = dynamicAlign ? ToUInt8(VarInt::ReadUnsigned(pbDecode)) : 0; + paramPointerReg = dynamicAlign ? ToUInt8(VarInt::ReadUnsigned(pbDecode)) : (UInt8)RN_NONE; + + epilogCount = epilogCountSmall < EC_MaxEpilogCountSmall ? epilogCountSmall : ToUInt16(VarInt::ReadUnsigned(pbDecode)); + + this->funcletOffset = 0; + if (hasFunclets) + { + // WORKAROUND: Epilog tables are still per-method instead of per-funclet, but we don't deal with + // them here. So we will simply overwrite the funclet's epilogAtEnd and epilogCount + // with the values from the main code body -- these were the values used to generate + // the per-method epilog table, so at least we're consistent with what is encoded. + UInt8 mainEpilogAtEnd = epilogAtEnd; + UInt16 mainEpilogCount = epilogCount; + UInt16 mainFixedEpilogSize = fixedEpilogSize; + // ------- + + int nFunclets = (int)VarInt::ReadUnsigned(pbDecode); + int idxFunclet = -2; + UInt32 offsetFunclet = 0; + // Decode the funclet start offsets, remembering which one is of interest. + UInt32 prevFuncletStart = 0; + for (int i = 0; i < nFunclets; i++) + { + UInt32 offsetThisFunclet = prevFuncletStart + VarInt::ReadUnsigned(pbDecode); + if ((idxFunclet == -2) && (methodOffset < offsetThisFunclet)) + { + idxFunclet = (i - 1); + offsetFunclet = prevFuncletStart; + } + prevFuncletStart = offsetThisFunclet; + } + if ((idxFunclet == -2) && (methodOffset >= prevFuncletStart)) + { + idxFunclet = (nFunclets - 1); + offsetFunclet = prevFuncletStart; + } + + // Now decode headers until we find the one we want. Keep decoding if we need to report a size. + if (pcbHeader || (idxFunclet >= 0)) + { + for (int i = 0; i < nFunclets; i++) + { + size_t hdrSize; + if (i == idxFunclet) + { + this->DecodeHeader(methodOffset, pbDecode, &hdrSize); + pbDecode += hdrSize; + this->funcletOffset = offsetFunclet; + if (!pcbHeader) // if nobody is going to look at the header size, we don't need to keep going + break; + } + else + { + // keep decoding into a temp just to get the right header size + GCInfoHeader tmp; + tmp.DecodeHeader(methodOffset, pbDecode, &hdrSize); + pbDecode += hdrSize; + } + } + } + + // WORKAROUND: see above + this->epilogAtEnd = mainEpilogAtEnd; + this->epilogCount = mainEpilogCount; + this->fixedEpilogSize = mainFixedEpilogSize; + + // ------- + } + + // WARNING: + // WARNING: Do not add fields to the file-format after the funclet header encodings -- these are + // WARNING: decoded recursively and 'in-place' when looking for the info associated with a funclet. + // WARNING: Therefore, in that case, we cannot easily continue to decode things associated with the + // WARNING: main body GCInfoHeader once we start this recursive decode. + // WARNING: + + if (pcbHeader) + *pcbHeader = pbDecode - pbHeaderEncoding; + + return pbStackChangeString; + } + + void GetFuncletInfo(PTR_UInt8 pbHeaderEncoding, UInt32* pnFuncletsOut, PTR_UInt8* pEncodedFuncletStartOffsets) + { + ASSERT(hasFunclets); + + PTR_UInt8 pbDecode = pbHeaderEncoding + EC_SizeOfFixedHeader; + if (hasFrameSize) { VarInt::SkipUnsigned(pbDecode); } + if (returnKind == MRK_ReturnsToNative) { VarInt::SkipUnsigned(pbDecode); } + if (dynamicAlign) { VarInt::SkipUnsigned(pbDecode); VarInt::SkipUnsigned(pbDecode); } + +#ifdef TARGET_X64 + if (x64_framePtrOffsetSmall == 0x3) { VarInt::SkipUnsigned(pbDecode); } +#elif defined(TARGET_X86) + if (x86_argCountIsLarge) + pbDecode++; + + if (x86_hasStackChanges) + { + bool last = false; + while (!last) + { + UInt8 b = *pbDecode++; + // 00111111 {delta} forwarder + // 00dddddd push 1, dddddd = delta + // nnnldddd pop nnn-1, l = last, dddd = delta (nnn=0 and nnn=1 are disallowed) + if (b == 0x3F) + { + // 00111111 {delta} forwarder + VarInt::SkipUnsigned(pbDecode); + } + else if (0 != (b & 0xC0)) + { + // nnnldddd pop nnn-1, l = last, dddd = delta (nnn=0 and nnn=1 are disallowed) + last = ((b & 0x10) == 0x10); + } + } + } +#elif defined(TARGET_ARM) + if (arm_areParmOrVfpRegsPushed) { VarInt::SkipUnsigned(pbDecode); } +#endif + + *pnFuncletsOut = VarInt::ReadUnsigned(pbDecode); + *pEncodedFuncletStartOffsets = pbDecode; + } + +#ifdef BINDER + bool IsOffsetInFunclet(UInt32 offset) + { + if (!hasFunclets) + return false; + + return (offset >= cbThisCodeBody); + } +#endif // BINDER + + bool IsValidEpilogOffset(UInt32 epilogOffset, UInt32 epilogSize) + { + if (!this->HasVaryingEpilogSizes()) + return (epilogOffset < this->fixedEpilogSize); + else + return (epilogOffset < epilogSize); + } + +#ifdef RHDUMP + char const * GetBoolStr(bool val) { return val ? " true" : "false"; } + char const * GetRetKindStr(int k) + { + switch (k) + { + case MRK_ReturnsScalar: return "scalar"; + case MRK_ReturnsObject: return "object"; + case MRK_ReturnsByref: return " byref"; + case MRK_ReturnsToNative: return "native"; + default: return "unknwn"; + } + } +#define PRINT_CALLEE_SAVE(name, mask, val) {if ((val) & (mask)) { printf(name); }} + void PrintCalleeSavedRegs(UInt32 calleeSavedRegMask) + { +#ifdef TARGET_X64 + PRINT_CALLEE_SAVE(" rbx", CSR_MASK_RBX, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" rsi", CSR_MASK_RSI, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" rdi", CSR_MASK_RDI, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" rbp", CSR_MASK_RBP, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r12", CSR_MASK_R12, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r13", CSR_MASK_R13, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r14", CSR_MASK_R14, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r15", CSR_MASK_R15, calleeSavedRegMask); +#endif // TARGET_X64 +#ifdef TARGET_X86 + PRINT_CALLEE_SAVE(" ebx", CSR_MASK_RBX, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" esi", CSR_MASK_RSI, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" edi", CSR_MASK_RDI, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" ebp", CSR_MASK_RBP, calleeSavedRegMask); +#endif // TARGET_X86 +#ifdef TARGET_ARM + PRINT_CALLEE_SAVE(" r4" , CSR_MASK_R4 , calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r5" , CSR_MASK_R5 , calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r6" , CSR_MASK_R6 , calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r7" , CSR_MASK_R7 , calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r8" , CSR_MASK_R8 , calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r9" , CSR_MASK_R9 , calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r10", CSR_MASK_R10, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" r11", CSR_MASK_R11, calleeSavedRegMask); + PRINT_CALLEE_SAVE(" lr" , CSR_MASK_LR , calleeSavedRegMask); +#endif // TARGET_ARM + } + + void PrintRegNumber(UInt8 regNumber) + { + switch (regNumber) + { + default: printf("???"); break; +#ifdef TARGET_ARM + case RN_R0: printf(" r0"); break; + case RN_R1: printf(" r1"); break; + case RN_R2: printf(" r2"); break; + case RN_R3: printf(" r3"); break; + case RN_R4: printf(" r4"); break; + case RN_R5: printf(" r5"); break; + case RN_R6: printf(" r6"); break; + case RN_R7: printf(" r7"); break; + case RN_R8: printf(" r8"); break; + case RN_R9: printf(" r9"); break; + case RN_R10: printf("r10"); break; + case RN_R11: printf("r11"); break; + case RN_R12: printf("r12"); break; + case RN_SP: printf(" sp"); break; + case RN_LR: printf(" lr"); break; + case RN_PC: printf(" pc"); break; +#elif defined(TARGET_X86) + case RN_EAX: printf("eax"); break; + case RN_ECX: printf("ecx"); break; + case RN_EDX: printf("edx"); break; + case RN_EBX: printf("ebx"); break; + case RN_ESP: printf("esp"); break; + case RN_EBP: printf("ebp"); break; + case RN_ESI: printf("esi"); break; + case RN_EDI: printf("edi"); break; +#elif defined(TARGET_X64) + case RN_EAX: printf("rax"); break; + case RN_ECX: printf("rcx"); break; + case RN_EDX: printf("rdx"); break; + case RN_EBX: printf("rbx"); break; + case RN_ESP: printf("rsp"); break; + case RN_EBP: printf("rbp"); break; + case RN_ESI: printf("rsi"); break; + case RN_EDI: printf("rdi"); break; + case RN_R8: printf(" r8"); break; + case RN_R9: printf(" r9"); break; + case RN_R10: printf("r10"); break; + case RN_R11: printf("r11"); break; + case RN_R12: printf("r12"); break; + case RN_R13: printf("r13"); break; + case RN_R14: printf("r14"); break; + case RN_R15: printf("r15"); break; +#else +#error unknown architecture +#endif + } + } + + void Dump() + { + printf(" | prologSize: %02X"" | epilogSize: %02X"" | epilogCount: %02X"" | epilogAtEnd: %s\n", + prologSize, fixedEpilogSize, epilogCount, GetBoolStr(epilogAtEnd)); + printf(" | frameSize: %04X"" | ebpFrame: %s"" | hasFunclets: %s"" | returnKind: %s\n", + GetFrameSize(), GetBoolStr(ebpFrame), GetBoolStr(hasFunclets), GetRetKindStr(returnKind)); + printf(" | regMask: %04X" " {", calleeSavedRegMask); + PrintCalleeSavedRegs(calleeSavedRegMask); + printf(" }\n"); + if (dynamicAlign) + { + printf(" | stackAlign: %02X"" | paramPtrReg: ", 1< 1) + printf("-d%d", arm_vfpRegFirstPushed + arm_vfpRegPushedCount - 1); + printf(" }\n"); + } + } +#elif defined(TARGET_X64) + if (x64_hasSavedXmmRegs) + { + printf(" | xmmRegs: %04X {", x64_savedXmmRegMask); + for (int reg = 6; reg < 16; reg++) + { + if (x64_savedXmmRegMask & (1< +class HolderNoDefaultValue +{ +public: + HolderNoDefaultValue(TYPE value, bool fTake = true) : m_value(value), m_held(false) + { if (fTake) { ACQUIRE_FUNC(value); m_held = true; } } + + ~HolderNoDefaultValue() { if (m_held) RELEASE_FUNC(m_value); } + + TYPE GetValue() { return m_value; } + + void Acquire() { ACQUIRE_FUNC(m_value); m_held = true; } + void Release() { if (m_held) { RELEASE_FUNC(m_value); m_held = false; } } + void SuppressRelease() { m_held = false; } + TYPE Extract() { m_held = false; return GetValue(); } + +protected: + TYPE m_value; + bool m_held; + +private: + // No one should be copying around holder types. + HolderNoDefaultValue & operator=(const HolderNoDefaultValue & other); + HolderNoDefaultValue(const HolderNoDefaultValue & other); +}; + +// ----------------------------------------------------------------------------------------------------------- +template +class Holder : public HolderNoDefaultValue +{ + typedef HolderNoDefaultValue MY_PARENT; +public: + Holder() : MY_PARENT(DEFAULTVALUE, false) {} + Holder(TYPE value, bool fTake = true) : MY_PARENT(value, fTake) {} + +private: + // No one should be copying around holder types. + Holder & operator=(const Holder & other); + Holder(const Holder & other); +}; + +// ----------------------------------------------------------------------------------------------------------- +template +class Wrapper : public Holder +{ + typedef Holder MY_PARENT; + +public: + Wrapper() : MY_PARENT() {} + Wrapper(TYPE value, bool fTake = true) : MY_PARENT(value, fTake) {} + + FORCEINLINE TYPE& operator=(TYPE const & value) + { + Release(); + m_value = value; + Acquire(); + return m_value; + } + + FORCEINLINE const TYPE &operator->() { return m_value; } + FORCEINLINE const TYPE &operator*() { return m_value; } + FORCEINLINE operator TYPE() { return m_value; } + +private: + // No one should be copying around wrapper types. + Wrapper & operator=(const Wrapper & other); + Wrapper(const Wrapper & other); +}; + +// ----------------------------------------------------------------------------------------------------------- +template +FORCEINLINE void DoNothing(TYPE value) +{ +} + +// ----------------------------------------------------------------------------------------------------------- +template +FORCEINLINE void Delete(TYPE *value) +{ + delete value; +} + +// ----------------------------------------------------------------------------------------------------------- +template , + void (*RELEASE_FUNC)(PTR_TYPE) = Delete, + PTR_TYPE NULL_VAL = 0, + typename BASE = Wrapper > +class NewHolder : public BASE +{ +public: + NewHolder(PTR_TYPE p = NULL_VAL) : BASE(p) + { } + + PTR_TYPE& operator=(PTR_TYPE p) + { return BASE::operator=(p); } + + bool IsNull() + { return BASE::GetValue() == NULL_VAL; } +}; + +//----------------------------------------------------------------------------- +// NewArrayHolder : New []'ed pointer holder +// { +// NewArrayHolder foo = new Foo [30]; +// } // delete [] foo on out of scope +//----------------------------------------------------------------------------- + +template +FORCEINLINE void DeleteArray(TYPE *value) +{ + delete [] value; + value = NULL; +} + +template , + void (*RELEASE_FUNC)(PTR_TYPE) = DeleteArray, + PTR_TYPE NULL_VAL = 0, + typename BASE = Wrapper > +class NewArrayHolder : public BASE +{ +public: + NewArrayHolder(PTR_TYPE p = NULL_VAL) : BASE(p) + { } + + PTR_TYPE& operator=(PTR_TYPE p) + { return BASE::operator=(p); } + + bool IsNull() + { return BASE::GetValue() == NULL_VAL; } +}; + +// ----------------------------------------------------------------------------------------------------------- +template +FORCEINLINE void Destroy(TYPE * value) +{ + value->Destroy(); +} + +// ----------------------------------------------------------------------------------------------------------- +template , + void (*RELEASE_FUNC)(PTR_TYPE) = Destroy, + PTR_TYPE NULL_VAL = 0, + typename BASE = Wrapper > +class CreateHolder : public BASE +{ +public: + CreateHolder(PTR_TYPE p = NULL_VAL) : BASE(p) + { } + + PTR_TYPE& operator=(PTR_TYPE p) + { return BASE::operator=(p); } +}; + + diff --git a/src/Native/Runtime/loglf.h b/src/Native/Runtime/loglf.h new file mode 100644 index 00000000000..d4aa64bc7e3 --- /dev/null +++ b/src/Native/Runtime/loglf.h @@ -0,0 +1,19 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// The code in sos.DumpStressLog depends on the facility codes +// being bit flags sorted in increasing order. +// See code:EEStartup#TableOfContents for EE overview +DEFINE_LOG_FACILITY(LF_GC ,0x00000001) +DEFINE_LOG_FACILITY(LF_GCINFO ,0x00000002) +DEFINE_LOG_FACILITY(LF_GCALLOC ,0x00000004) +DEFINE_LOG_FACILITY(LF_GCROOTS ,0x00000008) +DEFINE_LOG_FACILITY(LF_STARTUP ,0x00000010) // Log startup and shutdown failures +DEFINE_LOG_FACILITY(LF_STACKWALK ,0x00000020) +// LF_ALWAYS 0x80000000 // make certain you don't try to use this bit for a real facility +// LF_ALL 0xFFFFFFFF +// +#undef DEFINE_LOG_FACILITY + diff --git a/src/Native/Runtime/module.cpp b/src/Native/Runtime/module.cpp new file mode 100644 index 00000000000..7d9a3976a57 --- /dev/null +++ b/src/Native/Runtime/module.cpp @@ -0,0 +1,1412 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "commonmacros.h" +#include "daccess.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "holder.h" +#include "gcrhinterface.h" +#include "module.h" +#include "varint.h" +#include "rhbinder.h" +#include "crst.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "event.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "eetype.h" +#include "objectlayout.h" +#include "genericinstance.h" +#include "threadstore.h" + +#include "commonmacros.inl" +#include "slist.inl" +#else +#include "gcrhinterface.h" +#include "module.h" +#include "rhbinder.h" +#endif + +#include "gcinfo.h" +#include "rhcodeman.h" + +#include "rheventtrace.h" + +// #define LOG_MODULE_LOAD_VERIFICATION + +#ifndef DACCESS_COMPILE + +EXTERN_C UInt32_BOOL g_fGcStressStarted; + +Module::Module(ModuleHeader *pModuleHeader) : + m_pModuleHeader(pModuleHeader), + m_pNext(), + m_MethodList(), + m_pbDeltaShortcutTable(NULL), + m_fFinalizerInitComplete(false) +{ +} + +Module * Module::Create(SimpleModuleHeader *pModuleHeader) +{ + NewHolder pNewModule = new Module(nullptr); + if (NULL == pNewModule) + return NULL; + + pNewModule->m_pSimpleModuleHeader = pModuleHeader; + pNewModule->m_pEHTypeTable = nullptr; + pNewModule->m_pbDeltaShortcutTable = nullptr; + pNewModule->m_FrozenSegment = nullptr; + pNewModule->m_pStaticsGCInfo = dac_cast(pModuleHeader->m_pStaticsGcInfo); + pNewModule->m_pStaticsGCDataSection = dac_cast((UInt8*)pModuleHeader->m_pStaticsGcDataSection); + pNewModule->m_pThreadStaticsGCInfo = nullptr; + + pNewModule->m_hOsModuleHandle = PalGetModuleHandleFromPointer(pModuleHeader); + + pNewModule.SuppressRelease(); + return pNewModule; +} + +Module * Module::Create(ModuleHeader *pModuleHeader) +{ + // There's only one module header version for now. If we ever need to change it in a breaking fashion this + // is where we could put some code to try and handle downlevel modules with some form of compatibility + // mode (or just fail the module creation). + ASSERT(pModuleHeader->Version == ModuleHeader::CURRENT_VERSION); + + NewHolder pNewModule = new Module(pModuleHeader); + if (NULL == pNewModule) + return NULL; + + if (!pNewModule->m_MethodList.Init(pModuleHeader)) + return NULL; + + pNewModule->m_pEHTypeTable = pModuleHeader->GetEHInfo(); + pNewModule->m_pbDeltaShortcutTable = pNewModule->m_MethodList.GetDeltaShortcutTablePtr(); + pNewModule->m_pStaticsGCInfo = dac_cast(pModuleHeader->GetStaticsGCInfo()); + pNewModule->m_pStaticsGCDataSection = pModuleHeader->GetStaticsGCDataSection(); + pNewModule->m_pThreadStaticsGCInfo = dac_cast(pModuleHeader->GetThreadStaticsGCInfo()); + + if (pModuleHeader->RraFrozenObjects != ModuleHeader::NULL_RRA) + { + ASSERT(pModuleHeader->SizeFrozenObjects != 0); + pNewModule->m_FrozenSegment = RedhawkGCInterface::RegisterFrozenSection( + pModuleHeader->GetFrozenObjects(), pModuleHeader->SizeFrozenObjects); + if (pNewModule->m_FrozenSegment == NULL) + return NULL; + } + + // Determine OS module handle. This assumes that only one Redhawk module can exist in a given PE image, + // which is true for now. It's also exposed by a number of exports (RhCanUnloadModule, + // RhGetModuleFromEEType etc.) so if we ever rethink this then the public contract needs to change as + // well. + pNewModule->m_hOsModuleHandle = PalGetModuleHandleFromPointer(pModuleHeader); + if (!pNewModule->m_hOsModuleHandle) + { + ASSERT_UNCONDITIONALLY("Failed to locate our own module handle"); + return NULL; + } + +#ifdef FEATURE_CUSTOM_IMPORTS + Module::DoCustomImports(pModuleHeader); +#endif // FEATURE_CUSTOM_IMPORTS + +#ifdef FEATURE_VSD + // VirtualCallStubManager::ApplyPartialPolymorphicCallSiteResetForModule relies on being able to + // multiply CountVSDIndirectionCells by up to 100. Instead of trying to handle overflow gracefully + // we reject modules that would cause such an overflow. This limits the number of indirection + // cells to 1GB in number, which is perfectly reasonable given that this limit implies we'll also + // hit (or exceed in the 64bit case) the PE image 4GB file size limit. + if (pModuleHeader->CountVSDIndirectionCells > (UInt32_MAX / 100)) + { + return NULL; + } +#endif // FEATURE_VSD + +#ifdef _DEBUG +#ifdef LOG_MODULE_LOAD_VERIFICATION + PalPrintf("\r\nModule: 0x%p\r\n", pNewModule->m_hOsModuleHandle); +#endif // LOG_MODULE_LOAD_VERIFICATION + // + // Run through every byte of every method in the module and do some sanity-checking. Exclude stub code. + // + UInt32 textLength = pModuleHeader->RegionSize[ModuleHeader::TEXT_REGION] - pModuleHeader->SizeStubCode; + UInt8 * pbText = pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION]; + + UInt32 uMethodSize = 0; + UInt32 uMethodIndex = 0; + UInt32 uMethodStartSectionOffset = 0; + UInt32 uExpectedMethodIndex = 0; + UInt32 uExpectedMethodStartSectionOffset = 0; + UInt32 uTextSectionOffset = 0; + UInt32 nMethods = pNewModule->m_MethodList.GetNumMethodsDEBUG(); + + UInt32 nIndirCells = pNewModule->m_pModuleHeader->CountOfLoopIndirCells; + UIntNative * pShadowBuffer = new UIntNative[nIndirCells]; + UIntNative * pIndirCells = (UIntNative *)pNewModule->m_pModuleHeader->GetLoopIndirCells(); + memcpy(pShadowBuffer, pIndirCells, nIndirCells * sizeof(UIntNative)); + + EEMethodInfo methodInfo; + + for (; uTextSectionOffset < textLength; uTextSectionOffset += uMethodSize) + { + pNewModule->m_MethodList.GetMethodInfo( + uTextSectionOffset, &uMethodIndex, &uMethodStartSectionOffset, &uMethodSize); + + +#ifdef LOG_MODULE_LOAD_VERIFICATION + PalPrintf("0x%08x: %3d 0x%08x 0x%08x\r\n", + uTextSectionOffset, uMethodIndex, uMethodStartSectionOffset, uMethodSize); +#endif // LOG_MODULE_LOAD_VERIFICATION + + ASSERT(uExpectedMethodStartSectionOffset == uMethodStartSectionOffset); + uExpectedMethodStartSectionOffset += uMethodSize; + + ASSERT(uExpectedMethodIndex == uMethodIndex); + uExpectedMethodIndex++; + + // + // verify that every offset in the method gives the same result + // *every* offsets turns out to be too slow - try 10 offsets in the method + // + UInt32 step = max(uMethodSize/10, 1); + for (UInt32 i = 0; i < uMethodSize; i += step) + { + UInt32 uMI; + UInt32 uMSSO; + UInt32 uMS; + + pNewModule->m_MethodList.GetMethodInfo(uTextSectionOffset + i, &uMI, &uMSSO, &uMS); + + ASSERT(uMI == uMethodIndex); + ASSERT(uMSSO == uMethodStartSectionOffset); + ASSERT(uMS == uMethodSize); + } + + // + // calculate the method info + // + + UInt8 * pbMethod = pbText + uMethodStartSectionOffset; + UInt8 * pbGCInfo = pNewModule->m_MethodList.GetGCInfo(uMethodIndex); + void * pvEHInfo = pNewModule->m_MethodList.GetEHInfo(uMethodIndex); + + methodInfo.Init(pbMethod, uMethodSize, pbGCInfo, pvEHInfo); + + methodInfo.DecodeGCInfoHeader(0, pNewModule->GetUnwindInfoBlob()); + + // + // do some verifications.. + // +#ifdef LOG_MODULE_LOAD_VERIFICATION + EECodeManager::DumpGCInfo(&methodInfo, + pNewModule->GetDeltaShortcutTable(), + pNewModule->GetUnwindInfoBlob(), + pNewModule->GetCallsiteStringBlob()); +#endif // LOG_MODULE_LOAD_VERIFICATION + + EECodeManager::VerifyProlog(&methodInfo); + EECodeManager::VerifyEpilog(&methodInfo); + + pNewModule->UnsynchronizedHijackMethodLoops((MethodInfo *)&methodInfo); + + if (uExpectedMethodIndex >= nMethods) + break; + } + + for (UInt32 i = 0; i < nIndirCells; i++) + { + ASSERT(pShadowBuffer[i] != pIndirCells[i]); // make sure we hijacked all of them + } + + pNewModule->UnsynchronizedResetHijackedLoops(); + + if (!g_fGcStressStarted) // UnsynchronizedResetHijackedLoops won't do anything under gcstress + { + for (UInt32 i = 0; i < nIndirCells; i++) + { + ASSERT(pShadowBuffer[i] == pIndirCells[i]); // make sure we reset them properly + } + } + + delete[] pShadowBuffer; + + if (g_fGcStressStarted) + pNewModule->UnsynchronizedHijackAllLoops(); + +#ifdef LOG_MODULE_LOAD_VERIFICATION + PalPrintf("0x%08x: --- 0x%08x \r\n", (uTextSectionOffset + uMethodSize), + (uMethodStartSectionOffset + uMethodSize)); +#endif // LOG_MODULE_LOAD_VERIFICATION +#endif // _DEBUG + +#ifdef FEATURE_ETW + ETW::LoaderLog::SendModuleEvent(pNewModule); +#endif // FEATURE_ETW + + // Run any initialization functions for native code that was linked into the image using the binder's + // /nativelink option. + if (pNewModule->m_pModuleHeader->RraNativeInitFunctions != ModuleHeader::NULL_RRA) + { + typedef void (* NativeInitFunctionPtr)(); + UInt32 cInitFunctions = pNewModule->m_pModuleHeader->CountNativeInitFunctions; + NativeInitFunctionPtr * pInitFunctions = (NativeInitFunctionPtr*)(pNewModule->m_pModuleHeader->RegionPtr[ModuleHeader::RDATA_REGION] + + pNewModule->m_pModuleHeader->RraNativeInitFunctions); + for (UInt32 i = 0; i < cInitFunctions; i++) + pInitFunctions[i](); + } + + pNewModule.SuppressRelease(); + return pNewModule; +} + +void Module::Destroy() +{ + delete this; +} + +Module::~Module() +{ +} + +#endif // !DACCESS_COMPILE + + +PTR_ModuleHeader Module::GetModuleHeader() +{ + return m_pModuleHeader; +} + +PTR_GenericInstanceDesc Module::GetGidsWithGcRootsList() +{ + if (m_pModuleHeader == NULL) + return NULL; + + return dac_cast(m_pModuleHeader->GetGidsWithGcRootsList()); +} + + +// We have three separate range checks for the data regions we might be interested in. We do this rather than +// have a single, all-in-one, method to force callers to consider which ranges are applicable. In many cases +// the caller knows an address can only legally lie in one specific range and we'd rather force them to +// specify that than pay for redundant range checks in many cases. +bool Module::ContainsCodeAddress(PTR_VOID pvAddr) +{ + // We explicitly omit the stub code from this check. Use ContainsStubAddress to determine if + // an address belongs to the stub portion of the module's TEXT_REGION. + TADDR pAddr = dac_cast(pvAddr); + TADDR pSectionStart = dac_cast(m_pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION]); + TADDR pSectionLimit = pSectionStart + m_pModuleHeader->RegionSize[ModuleHeader::TEXT_REGION] + - m_pModuleHeader->SizeStubCode; + return (pAddr >= pSectionStart) && (pAddr < pSectionLimit); +} + +bool Module::ContainsDataAddress(PTR_VOID pvAddr) +{ + TADDR pAddr = dac_cast(pvAddr); + TADDR pSectionStart = dac_cast(m_pModuleHeader->RegionPtr[ModuleHeader::DATA_REGION]); + TADDR pSectionLimit = pSectionStart + m_pModuleHeader->RegionSize[ModuleHeader::DATA_REGION]; + return (pAddr >= pSectionStart) && (pAddr < pSectionLimit); +} + +bool Module::ContainsReadOnlyDataAddress(PTR_VOID pvAddr) +{ + TADDR pAddr = dac_cast(pvAddr); + TADDR pSectionStart = dac_cast(m_pModuleHeader->RegionPtr[ModuleHeader::RDATA_REGION]); + TADDR pSectionLimit = pSectionStart + m_pModuleHeader->RegionSize[ModuleHeader::RDATA_REGION]; + return (pAddr >= pSectionStart) && (pAddr < pSectionLimit); +} + +bool Module::ContainsStubAddress(PTR_VOID pvAddr) +{ + // Determines if the address belongs to the stub portion of the TEXT_REGION section. + TADDR pAddr = dac_cast(pvAddr); + TADDR pSectionStart = dac_cast(m_pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION]) + + m_pModuleHeader->RegionSize[ModuleHeader::TEXT_REGION] + - m_pModuleHeader->SizeStubCode; + TADDR pSectionLimit = pSectionStart + m_pModuleHeader->SizeStubCode; + return (pAddr >= pSectionStart) && (pAddr < pSectionLimit); +} + +PTR_UInt8 Module::FindMethodStartAddress(PTR_VOID ControlPC) +{ + if (!ContainsCodeAddress(ControlPC)) + return NULL; + + PTR_UInt8 pbControlPC = dac_cast(ControlPC); + + UInt32 uMethodSize; + UInt32 uMethodIndex; + UInt32 uMethodStartSectionOffset; + + PTR_UInt8 pbTextSectionStart = m_pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION]; + UInt32 uTextSectionOffset = (UInt32)(pbControlPC - pbTextSectionStart); + m_MethodList.GetMethodInfo(uTextSectionOffset, &uMethodIndex, &uMethodStartSectionOffset, &uMethodSize); + + PTR_UInt8 methodStartAddr = pbTextSectionStart + uMethodStartSectionOffset; + return methodStartAddr; +} + +bool Module::FindMethodInfo(PTR_VOID ControlPC, + MethodInfo * pMethodInfoOut, + UInt32 * pCodeOffset) +{ + if (!ContainsCodeAddress(ControlPC)) + return false; + + PTR_UInt8 pbControlPC = dac_cast(ControlPC); + + UInt32 uMethodSize; + UInt32 uMethodIndex; + UInt32 uMethodStartSectionOffset; + + PTR_UInt8 pbTextSectionStart = m_pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION]; + UInt32 uTextSectionOffset = (UInt32)(pbControlPC - pbTextSectionStart); + m_MethodList.GetMethodInfo(uTextSectionOffset, &uMethodIndex, &uMethodStartSectionOffset, &uMethodSize); + + PTR_UInt8 pbGCInfo = (PTR_UInt8) m_MethodList.GetGCInfo(uMethodIndex); + PTR_VOID pvEHInfo = m_MethodList.GetEHInfo(uMethodIndex); + + EEMethodInfo * pEEMethodInfo = (EEMethodInfo *)pMethodInfoOut; + + pEEMethodInfo->Init(pbTextSectionStart + uMethodStartSectionOffset, uMethodSize, pbGCInfo, pvEHInfo); + + UInt32 codeOffset = (UInt32)(pbControlPC - (PTR_UInt8)pEEMethodInfo->GetCode()); +#ifdef _ARM_ + codeOffset &= ~1; +#endif + *pCodeOffset = codeOffset; + + pEEMethodInfo->DecodeGCInfoHeader(codeOffset, GetUnwindInfoBlob()); + + return true; +} + +PTR_UInt8 Module::GetUnwindInfoBlob() +{ + return m_pModuleHeader->GetUnwindInfoBlob(); +} + +PTR_UInt8 Module::GetCallsiteStringBlob() +{ + return m_pModuleHeader->GetCallsiteInfoBlob(); +} + +PTR_UInt8 Module::GetDeltaShortcutTable() +{ + return m_pbDeltaShortcutTable; +} + +void Module::EnumStaticGCRefsBlock(void * pfnCallback, void * pvCallbackData, PTR_StaticGcDesc pStaticGcInfo, PTR_UInt8 pbStaticData) +{ + if (pStaticGcInfo == NULL) + return; + + for (UInt32 idxSeries = 0; idxSeries < pStaticGcInfo->m_numSeries; idxSeries++) + { + PTR_StaticGcDescGCSeries pSeries = dac_cast(dac_cast(pStaticGcInfo) + + offsetof(StaticGcDesc, m_series) + + (idxSeries * sizeof(StaticGcDesc::GCSeries))); + + ASSERT(IS_ALIGNED(dac_cast(pbStaticData), sizeof(RtuObjectRef))); + ASSERT(IS_ALIGNED(pSeries->m_startOffset, sizeof(RtuObjectRef))); + ASSERT(IS_ALIGNED(pSeries->m_size, sizeof(RtuObjectRef))); + + PTR_RtuObjectRef pRefLocation = dac_cast(pbStaticData + pSeries->m_startOffset); + UInt32 numObjects = pSeries->m_size / sizeof(RtuObjectRef); + + RedhawkGCInterface::BulkEnumGcObjRef(pRefLocation, numObjects, pfnCallback, pvCallbackData); + } +} + +void Module::EnumStaticGCRefs(void * pfnCallback, void * pvCallbackData) +{ + // Regular statics. + EnumStaticGCRefsBlock(pfnCallback, pvCallbackData, m_pStaticsGCInfo, m_pStaticsGCDataSection); + + // Thread local statics. + if (m_pThreadStaticsGCInfo != NULL) + { + FOREACH_THREAD(pThread) + { + // To calculate the address of the data for each thread's TLS fields we need two values: + // 1) The TLS slot index allocated for this module by the OS loader. We keep a pointer to this + // value in the module header. + // 2) The offset into the TLS block at which Redhawk-specific data begins. This is zero for + // modules generated by the binder in PE mode, but maybe something else for COFF-mode modules + // (if some of the native code we're linked with also uses thread locals). We keep this offset + // in the module header as well. + EnumStaticGCRefsBlock(pfnCallback, pvCallbackData, m_pThreadStaticsGCInfo, + pThread->GetThreadLocalStorage(*m_pModuleHeader->PointerToTlsIndex, + m_pModuleHeader->TlsStartOffset)); + } + END_FOREACH_THREAD + } +} + +bool Module::IsFunclet(MethodInfo * pMethodInfo) +{ + return GetEEMethodInfo(pMethodInfo)->GetGCInfoHeader()->IsFunclet(); +} + +PTR_VOID Module::GetFramePointer(MethodInfo * pMethodInfo, + REGDISPLAY * pRegisterSet) +{ + return EECodeManager::GetFramePointer(GetEEMethodInfo(pMethodInfo), pRegisterSet); +} + +void Module::EnumGcRefs(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + GCEnumContext * hCallback) +{ + EECodeManager::EnumGcRefs(GetEEMethodInfo(pMethodInfo), + codeOffset, + pRegisterSet, + hCallback, + GetCallsiteStringBlob(), + GetDeltaShortcutTable()); +} + +bool Module::UnwindStackFrame(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + PTR_VOID * ppPreviousTransitionFrame) +{ + EEMethodInfo * pEEMethodInfo = GetEEMethodInfo(pMethodInfo); + + *ppPreviousTransitionFrame = EECodeManager::GetReversePInvokeSaveFrame(pEEMethodInfo, pRegisterSet); + if (*ppPreviousTransitionFrame != NULL) + return true; + + return EECodeManager::UnwindStackFrame(pEEMethodInfo, codeOffset, pRegisterSet); +} + +bool Module::GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + PTR_PTR_VOID * ppvRetAddrLocation, + GCRefKind * pRetValueKind) +{ +#ifdef DACCESS_COMPILE + UNREFERENCED_PARAMETER(pMethodInfo); + UNREFERENCED_PARAMETER(codeOffset); + UNREFERENCED_PARAMETER(pRegisterSet); + UNREFERENCED_PARAMETER(ppvRetAddrLocation); + UNREFERENCED_PARAMETER(pRetValueKind); + return false; +#else + EEMethodInfo * pEEMethodInfo = GetEEMethodInfo(pMethodInfo); + + PTR_PTR_VOID pRetAddr = EECodeManager::GetReturnAddressLocationForHijack(pEEMethodInfo, + codeOffset, + pRegisterSet); + if (pRetAddr == NULL) + return false; + + *ppvRetAddrLocation = pRetAddr; + *pRetValueKind = EECodeManager::GetReturnValueKind(pEEMethodInfo); + + return true; +#endif +} + +struct EEEHEnumState +{ + PTR_UInt8 pEHInfo; + UInt32 uClause; + UInt32 nClauses; +}; + +// Ensure that EEEHEnumState fits into the space reserved by EHEnumState +STATIC_ASSERT(sizeof(EEEHEnumState) <= sizeof(EHEnumState)); + +#if 1 // only needed for local-exception model +bool Module::EHEnumInitFromReturnAddress(PTR_VOID ControlPC, PTR_VOID * pMethodStartAddressOut, EHEnumState * pEHEnumStateOut) +{ + ASSERT(ContainsCodeAddress(ControlPC)); + + PTR_UInt8 pbControlPC = dac_cast(ControlPC); + + UInt32 uMethodIndex; + UInt32 uMethodStartSectionOffset; + + PTR_UInt8 pbTextSectionStart = m_pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION]; + UInt32 uTextSectionOffset = (UInt32)(pbControlPC - pbTextSectionStart); + + m_MethodList.GetMethodInfo(uTextSectionOffset, &uMethodIndex, &uMethodStartSectionOffset, NULL); + + *pMethodStartAddressOut = pbTextSectionStart + uMethodStartSectionOffset; + + PTR_VOID pEHInfo = m_MethodList.GetEHInfo(uMethodIndex); + if (pEHInfo == NULL) + return false; + + EEEHEnumState * pEnumState = (EEEHEnumState *)pEHEnumStateOut; + pEnumState->pEHInfo = (PTR_UInt8)pEHInfo; + pEnumState->uClause = 0; + pEnumState->nClauses = VarInt::ReadUnsigned(pEnumState->pEHInfo); + + return true; +} +#endif // 1 + +bool Module::EHEnumInit(MethodInfo * pMethodInfo, PTR_VOID * pMethodStartAddressOut, EHEnumState * pEHEnumStateOut) +{ + EEMethodInfo * pInfo = GetEEMethodInfo(pMethodInfo); + + PTR_VOID pEHInfo = pInfo->GetEHInfo(); + if (pEHInfo == NULL) + return false; + + *pMethodStartAddressOut = pInfo->GetCode(); + + EEEHEnumState * pEnumState = (EEEHEnumState *)pEHEnumStateOut; + pEnumState->pEHInfo = (PTR_UInt8)pEHInfo; + pEnumState->uClause = 0; + pEnumState->nClauses = VarInt::ReadUnsigned(pEnumState->pEHInfo); + + return true; +} + +bool Module::EHEnumNext(EHEnumState * pEHEnumState, EHClause * pEHClauseOut) +{ + EEEHEnumState * pEnumState = (EEEHEnumState *)pEHEnumState; + + if (pEnumState->uClause >= pEnumState->nClauses) + return false; + pEnumState->uClause++; + + pEHClauseOut->m_tryStartOffset = VarInt::ReadUnsigned(pEnumState->pEHInfo); + + UInt32 tryEndDeltaAndClauseKind = VarInt::ReadUnsigned(pEnumState->pEHInfo); + pEHClauseOut->m_clauseKind = (EHClauseKind)(tryEndDeltaAndClauseKind & 0x3); + pEHClauseOut->m_tryEndOffset = pEHClauseOut->m_tryStartOffset + (tryEndDeltaAndClauseKind >> 2); + + // For each clause, we have up to 4 integers: + // 1) try start offset + // 2) (try length << 2) | clauseKind + // + // Local exceptions + // 3) if (typed || fault) { handler start offset } + // 4) if (typed) { index into type table } + // + // CLR exceptions + // 3) if (typed || fault || filter) { handler start offset } + // 4a) if (typed) { index into type table } + // 4b) if (filter) { filter start offset } + // + // The first two integers have already been decoded + + switch (pEHClauseOut->m_clauseKind) + { + case EH_CLAUSE_TYPED: + pEHClauseOut->m_handlerOffset = VarInt::ReadUnsigned(pEnumState->pEHInfo); + + { + UInt32 typeIndex = VarInt::ReadUnsigned(pEnumState->pEHInfo); + + void * pvTargetType = ((void **) m_pEHTypeTable)[typeIndex]; + + // We distinguish between these two cases by inspecting the low bit + // of the EHTypeTable entry. If it is set, the entry points to an + // indirection cell. + if ((((TADDR)pvTargetType) & 1) == 1) + pvTargetType = *(void**)(((UInt8*)pvTargetType) - 1); + + pEHClauseOut->m_pTargetType = pvTargetType; + } + break; + case EH_CLAUSE_FAULT: + pEHClauseOut->m_handlerOffset = VarInt::ReadUnsigned(pEnumState->pEHInfo); + break; + case EH_CLAUSE_FILTER: + pEHClauseOut->m_handlerOffset = VarInt::ReadUnsigned(pEnumState->pEHInfo); + pEHClauseOut->m_filterOffset = VarInt::ReadUnsigned(pEnumState->pEHInfo); + break; + } + + return true; +} + +static void UpdateStateForRemappedGCSafePoint(Module * pModule, EEMethodInfo * pInfo, UInt32 funcletStart, UInt32 * pRemappedCodeOffset) +{ + // The binder will encode a GC safe point (as appropriate) at the first code offset after the + // prolog to represent the "incoming" GC references. This safe point is 'special' because it + // doesn't occur at an offset that would otherwise be a safe point. Additionally, it doesn't + // report any scratch registers that might actually be live at that point in the funclet code + // (namely the incoming Exception object). In other words, this is just a convenient way to reuse + // the existing infrastructure to get our GC roots reported for a hardware fault at a non-GC-safe + // point. + + // N.B. - we cannot side-effect the current m_methodInfo or other state variables other than + // m_ControlPC and m_codeOffset because, although we've remapped the control PC, it's not really + // where we are unwinding from. We're just pretending that we're in the funclet for GC reporting + // purposes, but the unwind needs to happen from the original location. + + EEMethodInfo tempInfo; + + tempInfo.Init(pInfo->GetCode(), pInfo->GetCodeSize(), pInfo->GetRawGCInfo(), pInfo->GetEHInfo()); + + tempInfo.DecodeGCInfoHeader(funcletStart, pModule->GetUnwindInfoBlob()); + + GCInfoHeader * pHeader = tempInfo.GetGCInfoHeader(); + UInt32 cbProlog = pHeader->GetPrologSize(); + UInt32 codeOffset = funcletStart + cbProlog; +#ifdef _ARM_ + codeOffset &= ~1; +#endif + + *pRemappedCodeOffset = codeOffset; +} + +void Module::RemapHardwareFaultToGCSafePoint(MethodInfo * pMethodInfo, UInt32 * pCodeOffset) +{ + EEMethodInfo * pInfo = GetEEMethodInfo(pMethodInfo); + + EHEnumState ehEnum; + PTR_VOID pMethodStartAddress; + if (!EHEnumInit(pMethodInfo, &pMethodStartAddress, &ehEnum)) + return; + + EHClause ehClause; + while (EHEnumNext(&ehEnum, &ehClause)) + { + if ((ehClause.m_tryStartOffset <= *pCodeOffset) && (*pCodeOffset < ehClause.m_tryEndOffset)) + { + UpdateStateForRemappedGCSafePoint(this, pInfo, ehClause.m_handlerOffset, pCodeOffset); + return; + } + } + + // We didn't find a try region covering our PC. However, if the PC is in a funclet, we must do more work. + GCInfoHeader * pThisFuncletUnwindInfo = pInfo->GetGCInfoHeader(); + if (!pThisFuncletUnwindInfo->IsFunclet()) + return; + + // For funclets, we must correlate the funclet to its corresponding try region and check for enclosing try + // regions that might catch the exception as it "escapes" the funclet. + + UInt32 thisFuncletOffset = pThisFuncletUnwindInfo->GetFuncletOffset(); + + UInt32 tryRegionStart = 0; + UInt32 tryRegionEnd = 0; + bool foundTryRegion = false; + + EHEnumInit(pMethodInfo, &pMethodStartAddress, &ehEnum); + + while (EHEnumNext(&ehEnum, &ehClause)) + { + if (foundTryRegion && (ehClause.m_tryStartOffset <= tryRegionStart) && (tryRegionEnd <= ehClause.m_tryEndOffset)) + { + // the regions aren't nested if they have exactly the same range. + if ((ehClause.m_tryStartOffset != tryRegionStart) || (tryRegionEnd != ehClause.m_tryEndOffset)) + { + UpdateStateForRemappedGCSafePoint(this, pInfo, ehClause.m_handlerOffset, pCodeOffset); + return; + } + } + + if (ehClause.m_handlerOffset == thisFuncletOffset) + { + tryRegionStart = ehClause.m_tryStartOffset; + tryRegionEnd = ehClause.m_tryEndOffset; + foundTryRegion = true; + // After we find the target region, we can just keep looking without reseting our iterator. This + // is because the clauses are emitted in an "inside-out" order, so we know that enclosing clauses + // may only appear after the target clause. + } + } + ASSERT(foundTryRegion); +} + +#ifndef DACCESS_COMPILE + +#ifdef FEATURE_VSD + +IndirectionCell * Module::GetIndirectionCellArray() +{ + return (IndirectionCell*)m_pModuleHeader->GetVSDIndirectionCells(); +} + +UInt32 Module::GetIndirectionCellArrayCount() +{ + return m_pModuleHeader->CountVSDIndirectionCells; +} + +VSDInterfaceTargetInfo * Module::GetInterfaceTargetInfoArray() +{ + return (VSDInterfaceTargetInfo*)m_pModuleHeader->GetVSDInterfaceTargetInfos(); +} + +#endif // FEATURE_VSD + +//------------------------------------------------------------------------------------------------------------ +// @TODO: the following functions are related to throwing exceptions out of Rtm. If we did not have to throw +// out of Rtm, then we would note have to have the code below to get a classlib exception object given +// an exception id, or the special functions to back up the MDIL THROW_* instructions, or the allocation +// failure helper. If we could move to a world where we never throw out of Rtm, perhaps by moving parts +// of Rtm that do need to throw out to Bartok- or Binder-generated functions, then we could remove all of this. +//------------------------------------------------------------------------------------------------------------ + +// Return the Module that is the "classlib module" for this Module. This is the module that was supplied as +// the classlib when this module was bound. This module typically defines System.Object and other base types. +// The classlib module is also required to export two functions needed by the runtime to implement exception +// handling and fail fast. +Module * Module::GetClasslibModule() +{ + // Every non-classlib module has a RVA to a IAT entry for System.Object in the classlib module it + // was compiled against. Therefore, we can use that address to locate the Module for the classlib module. + // If this is a classlib module, then we can just return it. + if (IsClasslibModule()) + { + return this; + } + + void ** ppSystemObjectEEType = (void**)(m_pModuleHeader->RegionPtr[ModuleHeader::IAT_REGION] + + m_pModuleHeader->RraSystemObjectEEType); + + return GetRuntimeInstance()->FindModuleByReadOnlyDataAddress(*ppSystemObjectEEType); +} + +bool Module::IsClasslibModule() +{ + return (m_pModuleHeader->RraSystemObjectEEType == ModuleHeader::NULL_RRA); +} + +// Array eetypes have a common base type defined by the classlib module +EEType * Module::GetArrayBaseType() +{ + // find the class lib module + Module * pClasslibModule = GetClasslibModule(); + + // find the System.Array EEType + EEType * pArrayBaseType = (EEType *)(pClasslibModule->m_pModuleHeader->RegionPtr[ModuleHeader::RDATA_REGION] + + pClasslibModule->m_pModuleHeader->RraArrayBaseEEType); + + // we expect to find a canonical type (not cloned, not array, not "other") + ASSERT(pArrayBaseType->IsCanonical()); + + return pArrayBaseType; +} + +// Return the classlib-defined GetRuntimeException helper. Returns NULL if this is not a classlib module, or +// if this classlib module fails to export the helper. +void * Module::GetClasslibRuntimeExceptionHelper() +{ + return m_pModuleHeader->Get_GetRuntimeException(); +} + +// Return the classlib-defined FailFast helper. Returns NULL if this is not a classlib module, or +// if this classlib module fails to export the helper. +void * Module::GetClasslibFailFastHelper() +{ + return m_pModuleHeader->Get_FailFast(); +} + +void * Module::GetClasslibUnhandledExceptionHandlerHelper() +{ + return m_pModuleHeader->Get_UnhandledExceptionHandler(); +} + +void * Module::GetClasslibAppendExceptionStackFrameHelper() +{ + return m_pModuleHeader->Get_AppendExceptionStackFrame(); +} + +// Get classlib-defined helper for running deferred static class constructors. Returns NULL if this is not the +// classlib module or the classlib doesn't implement this callback. +void * Module::GetClasslibCheckStaticClassConstruction() +{ + return m_pModuleHeader->Get_CheckStaticClassConstruction(); +} + +// Returns the classlib-defined helper for initializing the finalizer thread. The contract is that it will be +// run before any object based on that classlib is finalized. +void * Module::GetClasslibInitializeFinalizerThread() +{ + return m_pModuleHeader->Get_InitializeFinalizerThread(); +} + +// Remove from the system any generic instantiations published by this module and not required by any other +// module currently loaded. +void Module::UnregisterGenericInstances() +{ + RuntimeInstance *pRuntimeInstance = GetRuntimeInstance(); + + // There can be up to three segments of GenericInstanceDescs, separated to improve locality. + const UInt32 cInstSections = 3; + GenericInstanceDesc * rgInstPointers[cInstSections]; + UInt32 rgInstCounts[cInstSections]; + rgInstPointers[0] = (GenericInstanceDesc*)m_pModuleHeader->GetGenericInstances(); + rgInstCounts[0] = m_pModuleHeader->CountGenericInstances; + rgInstPointers[1] = (GenericInstanceDesc*)m_pModuleHeader->GetGcRootGenericInstances(); + rgInstCounts[1] = m_pModuleHeader->CountGcRootGenericInstances; + rgInstPointers[2] = (GenericInstanceDesc*)m_pModuleHeader->GetVariantGenericInstances(); + rgInstCounts[2] = m_pModuleHeader->CountVariantGenericInstances; + + for (UInt32 idxSection = 0; idxSection < cInstSections; idxSection++) + { + GenericInstanceDesc *pGid = rgInstPointers[idxSection]; + + for (UInt32 i = 0; i < rgInstCounts[idxSection]; i++) + { + // Skip GIDs without an instantiation, they're just padding used to avoid base relocs straddling + // page boundaries (which is bad for perf). They're also not included in the GID count, so adjust + // that as we see them. + if (pGid->HasInstantiation()) + pRuntimeInstance->ReleaseGenericInstance(pGid); + else + { + ASSERT(pGid->GetFlags() == GenericInstanceDesc::GID_NoFields); + rgInstCounts[idxSection]++; + } + + pGid = (GenericInstanceDesc *)((UInt8*)pGid + pGid->GetSize()); + } + } +} + +bool Module::RegisterGenericInstances() +{ + bool fSuccess = true; + + RuntimeInstance *runtimeInstance = GetRuntimeInstance(); + + // There can be up to three segments of GenericInstanceDescs, separated to improve locality. + const UInt32 cInstSections = 3; + GenericInstanceDesc * rgInstPointers[cInstSections]; + UInt32 rgInstCounts[cInstSections]; + rgInstPointers[0] = (GenericInstanceDesc*)m_pModuleHeader->GetGenericInstances(); + rgInstCounts[0] = m_pModuleHeader->CountGenericInstances; + rgInstPointers[1] = (GenericInstanceDesc*)m_pModuleHeader->GetGcRootGenericInstances(); + rgInstCounts[1] = m_pModuleHeader->CountGcRootGenericInstances; + rgInstPointers[2] = (GenericInstanceDesc*)m_pModuleHeader->GetVariantGenericInstances(); + rgInstCounts[2] = m_pModuleHeader->CountVariantGenericInstances; + + // Registering generic instances with the runtime is performed as a transaction. This allows for some + // efficiencies (for instance, no need to continually retake hash table locks around each unification). + if (!runtimeInstance->StartGenericUnification(rgInstCounts[0] + rgInstCounts[1] + rgInstCounts[2])) + return false; + + UInt32 uiLocalTlsIndex = m_pModuleHeader->PointerToTlsIndex ? *m_pModuleHeader->PointerToTlsIndex : TLS_OUT_OF_INDEXES; + + for (UInt32 idxSection = 0; idxSection < cInstSections; idxSection++) + { + GenericInstanceDesc *pGid = rgInstPointers[idxSection]; + + for (UInt32 i = 0; i < rgInstCounts[idxSection]; i++) + { + // We can get padding GenericInstanceDescs every so often that are inserted to ensure none of the + // base relocs associated with a GID straddle a page boundary (which is very inefficient). These + // don't have instantiations. They're also not included in the GID count, so adjust that as we see + // them. + if (pGid->HasInstantiation()) + { + if (!runtimeInstance->UnifyGenericInstance(pGid, uiLocalTlsIndex)) + { + fSuccess = false; + goto Finished; + } + } + else + { + ASSERT(pGid->GetFlags() == GenericInstanceDesc::GID_NoFields); + rgInstCounts[idxSection]++; + } + + pGid = (GenericInstanceDesc *)((UInt8*)pGid + pGid->GetSize()); + } + } + + Finished: + runtimeInstance->EndGenericUnification(); + + return fSuccess; +} + + +// Returns true if this module is part of the OS module specified by hOsHandle. +bool Module::IsContainedBy(HANDLE hOsHandle) +{ + return m_hOsModuleHandle == hOsHandle; +} + +// NULL out any GC references held by statics in this module. Note that this is unsafe unless we know that no +// code is making (or can make) any reference to these statics. Generally this is only true when we are about +// to unload the module. +void Module::ClearStaticRoots() +{ + StaticGcDesc * pStaticGcInfo = (StaticGcDesc*)m_pModuleHeader->GetStaticsGCInfo(); + if (!pStaticGcInfo) + return; + + UInt8 * pGcStaticsSection = m_pModuleHeader->GetStaticsGCDataSection(); + + for (UInt32 idxSeries = 0; idxSeries < pStaticGcInfo->m_numSeries; idxSeries++) + { + StaticGcDesc::GCSeries * pSeries = &pStaticGcInfo->m_series[idxSeries]; + + Object ** pRefLocation = (Object**)(pGcStaticsSection + pSeries->m_startOffset); + UInt32 numObjects = pSeries->m_size / sizeof(Object*); + + for (UInt32 idxObj = 0; idxObj < numObjects; idxObj++) + pRefLocation[idxObj] = NULL; + } +} + +void Module::UnregisterFrozenSection() +{ + RedhawkGCInterface::UnregisterFrozenSection(m_FrozenSegment); +} + +// +// Hijack the loops within the method referred to by pMethodInfo. +// WARNING: Only one thread may call this at a time (i.e. the thread performing suspension of all others). +void Module::UnsynchronizedHijackMethodLoops(MethodInfo * pMethodInfo) +{ + void ** ppvIndirCells = (void **)m_pModuleHeader->GetLoopIndirCells(); + UInt32 nIndirCells = m_pModuleHeader->CountOfLoopIndirCells; + if (nIndirCells == 0) + return; + + EEMethodInfo * pEEMethodInfo = GetEEMethodInfo(pMethodInfo); + + void * pvMethodStart = pEEMethodInfo->GetCode(); + void * pvMethodEnd = ((UInt8 *)pvMethodStart) + pEEMethodInfo->GetCodeSize(); + + void * pvRedirStubsStart = m_pModuleHeader->GetLoopRedirTargets(); + void * pvRedirStubsEnd = ((UInt8 *)pvRedirStubsStart) + GcPollInfo::EntryIndexToStubOffset(nIndirCells); + +#ifdef TARGET_ARM + // on ARM, there is just one redir stub, because we can compute the indir cell index + // from the indir cell pointer left in r12 + // to make the range tests below work, bump up the end by one byte + ASSERT(pvRedirStubsStart == pvRedirStubsEnd); + pvRedirStubsEnd = (void *)(((UInt8 *)pvRedirStubsEnd)+1); +#endif // TARGET_ARM + + + void ** ppvStart = &ppvIndirCells[0]; + void ** ppvEnd = &ppvIndirCells[nIndirCells]; + void ** ppvTest; + + while ((ppvStart + 1) < ppvEnd) + { + ppvTest = ppvStart + ((ppvEnd - ppvStart)/2); + void * cellContents = *ppvTest; + + // look to see if the cell has already been hijacked + if ((pvRedirStubsStart <= cellContents) && (cellContents < pvRedirStubsEnd)) + { + void ** ppvCur = ppvTest; + // try incrementing ppvTest until it hits ppvEnd + while (++ppvCur < ppvEnd) + { + cellContents = *ppvCur; + if ((pvRedirStubsStart > cellContents) || (cellContents >= pvRedirStubsEnd)) + break; + } + if (ppvCur == ppvEnd) + { + // We hit the end and didn't find any non-hijacked cells, + // so let's shrink the range and start over. + ppvEnd = ppvTest; + continue; + } + } + + if (pvMethodStart >= cellContents) + { + ppvStart = ppvTest; + } + else if (pvMethodStart < cellContents) + { + ppvEnd = ppvTest; + } + } + ppvTest = ppvStart; + + // At this point start and end are pointing to consecutive entries + ASSERT((ppvStart + 1) == ppvEnd); + + // Reset start and end. + ppvStart = &ppvIndirCells[0]; + ppvEnd = &ppvIndirCells[nIndirCells]; + + // We shouldn't have walked off the end of the array + ASSERT((ppvStart <= ppvTest) && (ppvTest < ppvEnd)); + + // ppvTest may point the the cell before the first cell in the method or to the first cell in the method. + // So we must test it separately to see whether or not to hijack it. + if (*ppvTest < pvMethodStart) + ppvTest++; + + UInt8 * pbDirtyBitmap = m_pModuleHeader->GetLoopIndirCellChunkBitmap();; + + // now hijack all the entries to the end of the method + for (;;) + { + void * cellContents = *ppvTest; + + // skip already hijacked cells + while ((pvRedirStubsStart <= cellContents) && (cellContents < pvRedirStubsEnd) && (ppvTest < ppvEnd)) + { + ppvTest++; + cellContents = *ppvTest; + } + if (ppvTest >= ppvEnd) // walked off the end of the array + break; + if (cellContents >= pvMethodEnd) // walked off the end of the method + break; + + UInt32 entryIndex = (UInt32)(ppvTest - ppvIndirCells); + + UnsynchronizedHijackLoop(ppvTest, entryIndex, pvRedirStubsStart, pbDirtyBitmap); + + ppvTest++; + } +} + +// WARNING: Caller must perform synchronization! +void Module::UnsynchronizedResetHijackedLoops() +{ + if (g_fGcStressStarted) + return; // don't ever reset loop hijacks when GC stress is enabled + + if (m_pModuleHeader == nullptr) // @TODO: simple modules and loop hijacking + return; + + void ** ppvIndirCells = (void **)m_pModuleHeader->GetLoopIndirCells(); + UInt32 nIndirCells = m_pModuleHeader->CountOfLoopIndirCells; + if (nIndirCells == 0) + return; + + UInt8 * pbDirtyBitmapStart = m_pModuleHeader->GetLoopIndirCellChunkBitmap(); + UInt32 cellsPerByte = (GcPollInfo::indirCellsPerBitmapBit * 8); + UInt32 nBitmapBytes = (nIndirCells + (cellsPerByte - 1)) / cellsPerByte; // round up to the next byte + UInt8 * pbDirtyBitmapEnd = pbDirtyBitmapStart + nBitmapBytes; + + void ** ppvCurIndirCell = &ppvIndirCells[0]; + void ** ppvIndirCellsEnd = &ppvIndirCells[nIndirCells]; + + UInt8 * pbTargetsInfoStart = m_pModuleHeader->GetLoopTargets(); + UInt8 * pbCurrentChunkPtr = pbTargetsInfoStart; + + for (UInt8 * pbBitmapCursor = pbDirtyBitmapStart; pbBitmapCursor < pbDirtyBitmapEnd; pbBitmapCursor++) + { + UInt8 currentByte = *pbBitmapCursor; + + for (UInt8 mask = 0x80; mask > 0; mask >>= 1) + { + if (currentByte & mask) + { + UInt32 currentChunkOffset = VarInt::ReadUnsigned(pbCurrentChunkPtr); + UInt8 * pbChunkInfo = pbTargetsInfoStart + currentChunkOffset; + UInt32 targetOffset = VarInt::ReadUnsigned(pbChunkInfo); + + for (void ** ppvTemp = ppvCurIndirCell; + ppvTemp < (ppvCurIndirCell + GcPollInfo::indirCellsPerBitmapBit); + ppvTemp++) + { + if (ppvTemp >= ppvIndirCellsEnd) + return; // the last byte was only partially populated + + *ppvTemp = m_pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION] + targetOffset; + targetOffset += VarInt::ReadUnsigned(pbChunkInfo); + } + + // WARNING: This not synchronized! -- We expect to perform these actions only when + // all threads are suspended for GC. + currentByte ^= mask; // reset the bit in the bitmap + ASSERT((currentByte & mask) == 0); + } + else + { + VarInt::SkipUnsigned(pbCurrentChunkPtr); + } + ppvCurIndirCell += GcPollInfo::indirCellsPerBitmapBit; + } + } +} + +EXTERN_C void * FASTCALL RecoverLoopHijackTarget(UInt32 entryIndex, ModuleHeader * pModuleHeader) +{ + UInt8 * pbTargetsInfoStart = pModuleHeader->GetLoopTargets(); + UInt8 * pbCurrentChunkPtr = pbTargetsInfoStart; + + UInt32 bitIndex = entryIndex / GcPollInfo::indirCellsPerBitmapBit; + for (UInt32 idx = 0; idx < bitIndex; idx++) + { + VarInt::SkipUnsigned(pbCurrentChunkPtr); + } + + UInt32 currentChunkOffset = VarInt::ReadUnsigned(pbCurrentChunkPtr); + UInt8 * pbCurrentInfo = pbTargetsInfoStart + currentChunkOffset; + UInt32 targetOffset = VarInt::ReadUnsigned(pbCurrentInfo); + + for (UInt32 chunkSubIndex = entryIndex - (bitIndex * GcPollInfo::indirCellsPerBitmapBit); + chunkSubIndex > 0; + chunkSubIndex--) + { + targetOffset += VarInt::ReadUnsigned(pbCurrentInfo); + } + + return pModuleHeader->RegionPtr[ModuleHeader::TEXT_REGION] + targetOffset;; +} + +void Module::UnsynchronizedHijackAllLoops() +{ + void ** ppvIndirCells = (void **)m_pModuleHeader->GetLoopIndirCells(); + UInt32 nIndirCells = m_pModuleHeader->CountOfLoopIndirCells; + if (nIndirCells == 0) + return; + + void * pvRedirStubsStart = m_pModuleHeader->GetLoopRedirTargets(); + UInt8 * pbDirtyBitmap = m_pModuleHeader->GetLoopIndirCellChunkBitmap(); + + for (UInt32 idx = 0; idx < nIndirCells; idx++) + { + UnsynchronizedHijackLoop(&ppvIndirCells[idx], idx, pvRedirStubsStart, pbDirtyBitmap); + } +} + +// static +void Module::UnsynchronizedHijackLoop(void ** ppvIndirectionCell, UInt32 cellIndex, + void * pvRedirStubsStart, UInt8 * pbDirtyBitmap) +{ + // + // set the dirty bit + // + UInt32 bitmapByteIndex = cellIndex / (GcPollInfo::indirCellsPerBitmapBit * 8); + UInt32 bitmapBitIndex = (cellIndex / GcPollInfo::indirCellsPerBitmapBit) % 8; + UInt8 bitMask = 1 << (7 - bitmapBitIndex); + UInt8 * pBitmapByte = &pbDirtyBitmap[bitmapByteIndex]; + + // WARNING: The assumption here is that there is only one thread ever updating this bitmap (i.e. the + // thread performing the suspension of all other threads). If this assumption is violated, then this + // code is broken because it does a read-modify-write which could overwrite other writers' updates. + UInt8 newByte = (*pBitmapByte) | bitMask; + *((UInt8 *)pBitmapByte) = newByte; + + // + // hijack the loop's indirection cell + // + *ppvIndirectionCell = ((UInt8 *)pvRedirStubsStart) + GcPollInfo::EntryIndexToStubOffset(cellIndex); +} + +DispatchMap ** Module::GetDispatchMapLookupTable() +{ + return (DispatchMap**)(m_pModuleHeader->RegionPtr[ModuleHeader::RDATA_REGION] + + m_pModuleHeader->RraDispatchMapLookupTable); +} + +HANDLE Module::GetOsModuleHandle() +{ + return m_hOsModuleHandle; +} + +BlobHeader * Module::GetReadOnlyBlobs(UInt32 * pcbBlobs) +{ + *pcbBlobs = m_pModuleHeader->SizeReadOnlyBlobs; + return (BlobHeader*)m_pModuleHeader->GetReadOnlyBlobs(); +} + +Module::GenericInstanceDescEnumerator::GenericInstanceDescEnumerator(Module * pModule, GenericInstanceDescKind gidKind) + : m_pModule(pModule), m_pCurrent(NULL), m_iCurrent(0), m_nCount(0), m_iSection(0), m_gidEnumKind(gidKind) +{ +} + +GenericInstanceDesc * Module::GenericInstanceDescEnumerator::Next() +{ + m_iCurrent++; + + if (m_iCurrent >= m_nCount) + { + ModuleHeader * pModuleHeader = m_pModule->m_pModuleHeader; + m_nCount = 0; + + for (;;) + { + // There can be up to three segments of GenericInstanceDescs, separated to improve locality. + switch (m_iSection) + { + case 0: + if ((m_gidEnumKind & GenericInstanceDescKind::GenericInstances) != 0) + { + m_pCurrent = (GenericInstanceDesc*)pModuleHeader->GetGenericInstances(); + m_nCount = pModuleHeader->CountGenericInstances; + } + break; + case 1: + if ((m_gidEnumKind & GenericInstanceDescKind::GcRootGenericInstances) != 0) + { + m_pCurrent = (GenericInstanceDesc*)pModuleHeader->GetGcRootGenericInstances(); + m_nCount = pModuleHeader->CountGcRootGenericInstances; + } + break; + case 2: + if ((m_gidEnumKind & GenericInstanceDescKind::VariantGenericInstances) != 0) + { + m_pCurrent = (GenericInstanceDesc*)pModuleHeader->GetVariantGenericInstances(); + m_nCount = pModuleHeader->CountVariantGenericInstances; + } + break; + default: + return NULL; + } + + m_iSection++; + + if (m_nCount > 0) + break; + } + + m_iCurrent = 0; + + if (m_pCurrent->HasInstantiation()) + return m_pCurrent; + } + + for (;;) + { + m_pCurrent = (GenericInstanceDesc *)((UInt8*)m_pCurrent + m_pCurrent->GetSize()); + + if (m_pCurrent->HasInstantiation()) + return m_pCurrent; + + // We can get padding GenericInstanceDescs every so often that are inserted to ensure none of the + // base relocs associated with a GID straddle a page boundary (which is very inefficient). These + // don't have instantiations. They're also not included in the GID count. + ASSERT(m_pCurrent->GetFlags() == GenericInstanceDesc::GID_NoFields); + } +} + +UInt32 Module::GetGenericInstanceDescCount(GenericInstanceDescKind gidKind) +{ + UInt32 count = 0; + if ((gidKind & GenericInstanceDescKind::GenericInstances) != 0) + count += m_pModuleHeader->CountGenericInstances; + if ((gidKind & GenericInstanceDescKind::GcRootGenericInstances) != 0) + count += m_pModuleHeader->CountGcRootGenericInstances; + if ((gidKind & GenericInstanceDescKind::VariantGenericInstances) != 0) + count += m_pModuleHeader->CountVariantGenericInstances; + return count; +} + +#ifdef FEATURE_CUSTOM_IMPORTS + +#define IMAGE_ORDINAL_FLAG64 0x8000000000000000 +#define IMAGE_ORDINAL_FLAG32 0x80000000 + +#ifdef TARGET_X64 +#define TARGET_IMAGE_ORDINAL_FLAG IMAGE_ORDINAL_FLAG64 +#else +#define TARGET_IMAGE_ORDINAL_FLAG IMAGE_ORDINAL_FLAG32 +#endif + +/*static*/ +void Module::DoCustomImports(ModuleHeader * pModuleHeader) +{ + CustomImportDescriptor *customImportTable = (CustomImportDescriptor *)pModuleHeader->GetCustomImportDescriptors(); + UInt32 countCustomImports = pModuleHeader->CountCustomImportDescriptors; + + // obtain base address for this module + PTR_UInt8 thisBaseAddress = (PTR_UInt8)PalGetModuleHandleFromPointer(pModuleHeader); + + for (UInt32 i = 0; i < countCustomImports; i++) + { + // obtain address of indirection cell pointing to the EAT for the exporting module + UInt32 **ptrPtrEAT = (UInt32 **)(thisBaseAddress + customImportTable[i].RvaEATAddr); + + // obtain the EAT by derefencing + UInt32 *ptrEAT = *ptrPtrEAT; + + // obtain the exporting module + HANDLE hExportingModule = PalGetModuleHandleFromPointer(ptrEAT); + + // obtain the base address of the exporting module + PTR_UInt8 targetBaseAddress = (PTR_UInt8)hExportingModule; + + // obtain the address of the IAT and the number of entries + UIntTarget *ptrIAT = (UIntTarget *)(thisBaseAddress + customImportTable[i].RvaIAT); + UInt32 countIAT = customImportTable[i].CountIAT; + + if (i == 0) + { + // the first entry is a dummy entry that points to a flag + UInt32 *pFlag = (UInt32 *)ptrIAT; + + // the ptr to the EAT indirection cell also points to the flag + ASSERT((UInt32 *)ptrPtrEAT == pFlag); + + // the number of IAT entries should be zero + ASSERT(countIAT == 0); + + // if the flag is set, it means we have fixed up this module already + // this is our check against infinite recursion + if (*pFlag == TRUE) + return; + + // if the flag is not set, it must be clear + ASSERT(*pFlag == FALSE); + + // set the flag + *pFlag = TRUE; + } + else + { + // iterate over the IAT, replacing ordinals with real addresses + for (UInt32 j = 0; j < countIAT; j++) + { + // obtain the ordinal + UIntTarget ordinal = ptrIAT[j]; + + // the ordinals should have the high bit set + ASSERT((ordinal & TARGET_IMAGE_ORDINAL_FLAG) != 0); + + // the ordinals should be in increasing order, for perf reasons + ASSERT(j+1 == countIAT || ordinal < ptrIAT[j+1]); + + ordinal &= ~TARGET_IMAGE_ORDINAL_FLAG; + + // sanity check: limit ordinals to < 1 Million + ASSERT(ordinal < 1024 * 1024); + + // obtain the target RVA + UInt32 targetRVA = ptrEAT[ordinal]; + + // obtain the target address by adding the base address of the exporting module + UIntTarget targetAddr = (UIntTarget)(targetBaseAddress + targetRVA); + + // write the target address to the IAT slot, overwriting the ordinal + ptrIAT[j] = targetAddr; + } + // find the module header of the target module - this is a bit of a hack + // as we assume the header is at the start of the first section + // currently this is true for ProjectN files unless it's built by the native + // linker from COFF files + ModuleHeader *pTargetModuleHeader = (ModuleHeader *)(targetBaseAddress + 0x1000); + + // recursively fixup the target module as well - this is because our eager cctors may call + // methods in the target module, which again may call imports of the target module + DoCustomImports(pTargetModuleHeader); + } + } +} +#endif // FEATURE_CUSTOM_IMPORTS + +#endif // DACCESS_COMPILE diff --git a/src/Native/Runtime/module.h b/src/Native/Runtime/module.h new file mode 100644 index 00000000000..19e067db763 --- /dev/null +++ b/src/Native/Runtime/module.h @@ -0,0 +1,206 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "ICodeManager.h" + +#include "SectionMethodList.h" + +struct StaticGcDesc; +typedef SPTR(StaticGcDesc) PTR_StaticGcDesc; +struct IndirectionCell; +struct VSDInterfaceTargetInfo; +class DispatchMap; +struct BlobHeader; +struct GenericInstanceDesc; +typedef SPTR(struct GenericInstanceDesc) PTR_GenericInstanceDesc; +struct SimpleModuleHeader; + +class Module +#ifndef DACCESS_COMPILE + // TODO: JIT support in DAC + : public ICodeManager +#endif +{ +#ifdef DACCESS_COMPILE + // The DAC does not support registration of dynamic code managers yet, but we need a space for the vtable used at runtime. + // TODO: JIT support in DAC + TADDR m_vptr; +#endif + + friend class AsmOffsets; + friend struct DefaultSListTraits; + friend class RuntimeInstance; +public: + ~Module(); + + static Module * Create(ModuleHeader *pModuleHeader); + static Module * Create(SimpleModuleHeader *pModuleHeader); + + void Destroy(); + + bool ContainsCodeAddress(PTR_VOID pvAddr); + bool ContainsDataAddress(PTR_VOID pvAddr); + bool ContainsReadOnlyDataAddress(PTR_VOID pvAddr); + bool ContainsStubAddress(PTR_VOID pvAddr); + + static void EnumStaticGCRefsBlock(void * pfnCallback, void * pvCallbackData, PTR_StaticGcDesc pStaticGcInfo, PTR_UInt8 pbStaticData); + void EnumStaticGCRefs(void * pfnCallback, void * pvCallbackData); + +#ifdef FEATURE_VSD + + // + // VSD support + // + IndirectionCell * GetIndirectionCellArray(); + UInt32 GetIndirectionCellArrayCount(); + VSDInterfaceTargetInfo * GetInterfaceTargetInfoArray(); + +#endif // FEATURE_VSD + + // Get the classlib module that this module was compiled against. + Module * GetClasslibModule(); + + // Is this a classlib module? + bool IsClasslibModule(); + + // Get classlib-defined helpers for the exception system. + void * GetClasslibRuntimeExceptionHelper(); + void * GetClasslibFailFastHelper(); + void * GetClasslibUnhandledExceptionHandlerHelper(); + void * GetClasslibAppendExceptionStackFrameHelper(); + + // Get classlib-defined helper for running deferred static class constructors. + void * GetClasslibCheckStaticClassConstruction(); + + // Returns the classlib-defined helper for initializing the finalizer thread. The contract is that it + // will be run before any object based on that classlib is finalized. + void * GetClasslibInitializeFinalizerThread(); + + // Returns a pointer to the unwind info blob for the module + PTR_UInt8 GetUnwindInfoBlob(); + PTR_UInt8 GetCallsiteStringBlob(); + PTR_UInt8 GetDeltaShortcutTable(); + + // Returns true if this module is part of the OS module specified by hOsHandle. + bool IsContainedBy(HANDLE hOsHandle); + + // NULL out any GC references held by statics in this module. Note that this is unsafe unless we know that + // no code is making (or can make) any reference to these statics. Generally this is only true when we are + // about to unload the module. + void ClearStaticRoots(); + + void UnregisterFrozenSection(); + + // Remove from the system any generic instantiations published by this module and not required by any + // other module currently loaded. + void UnregisterGenericInstances(); + + PTR_UInt8 FindMethodStartAddress(PTR_VOID ControlPC); + + bool FindMethodInfo(PTR_VOID ControlPC, + MethodInfo * pMethodInfoOut, + UInt32 * pCodeOffset); + + bool IsFunclet(MethodInfo * pMethodInfo); + + PTR_VOID GetFramePointer(MethodInfo * pMethodInfo, + REGDISPLAY * pRegisterSet); + + void EnumGcRefs(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + GCEnumContext * hCallback); + + bool UnwindStackFrame(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + PTR_VOID * ppPreviousTransitionFrame); + + bool GetReturnAddressHijackInfo(MethodInfo * pMethodInfo, + UInt32 codeOffset, + REGDISPLAY * pRegisterSet, + PTR_PTR_VOID * ppvRetAddrLocation, + GCRefKind * pRetValueKind); + + PTR_GenericInstanceDesc GetGidsWithGcRootsList(); + + // BEWARE: care must be taken when using these Unsynchronized methods. Only one thread may call this at a time. + void UnsynchronizedHijackMethodLoops(MethodInfo * pMethodInfo); + void UnsynchronizedResetHijackedLoops(); + void UnsynchronizedHijackAllLoops(); + + bool EHEnumInitFromReturnAddress(PTR_VOID ControlPC, PTR_VOID * pMethodStartAddressOut, EHEnumState * pEHEnumStateOut); + + bool EHEnumInit(MethodInfo * pMethodInfo, PTR_VOID * pMethodStartAddressOut, EHEnumState * pEHEnumStateOut); + bool EHEnumNext(EHEnumState * pEHEnumState, EHClause * pEHClauseOut); + + void RemapHardwareFaultToGCSafePoint(MethodInfo * pMethodInfo, UInt32 * pCodeOffset); + + DispatchMap ** GetDispatchMapLookupTable(); + + PTR_ModuleHeader GetModuleHeader(); + + HANDLE GetOsModuleHandle(); + + BlobHeader * GetReadOnlyBlobs(UInt32 * pcbBlobs); + + EEType * GetArrayBaseType(); + + enum GenericInstanceDescKind + { + GenericInstances = 1, + GcRootGenericInstances = 2, + VariantGenericInstances = 4, + All = GenericInstances | GcRootGenericInstances | VariantGenericInstances + }; + + class GenericInstanceDescEnumerator + { + Module * m_pModule; + + GenericInstanceDesc * m_pCurrent; + GenericInstanceDescKind m_gidEnumKind; + UInt32 m_iCurrent; + UInt32 m_nCount; + + Int32 m_iSection; + + public: + GenericInstanceDescEnumerator(Module * pModule, GenericInstanceDescKind gidKind); + GenericInstanceDesc * Next(); + }; + + UInt32 GetGenericInstanceDescCount(GenericInstanceDescKind gidKind); + + bool IsFinalizerInitComplete() { return m_fFinalizerInitComplete; } + void SetFinalizerInitComplete() { m_fFinalizerInitComplete = true; } + +private: + Module(ModuleHeader * pModuleHeader); + bool RegisterGenericInstances(); +#ifdef FEATURE_CUSTOM_IMPORTS + static void DoCustomImports(ModuleHeader * pModuleHeader); + PTR_UInt8 GetBaseAddress() { return (PTR_UInt8)(size_t)GetOsModuleHandle(); } +#endif // FEATURE_CUSTOM_IMPORTS + + static void UnsynchronizedHijackLoop(void ** ppvIndirectionCell, UInt32 cellIndex, + void * pvRedirStubsStart, UInt8 * pbDirtyBitmap); + + PTR_Module m_pNext; + + PTR_UInt8 m_pbDeltaShortcutTable; // 16-byte array of the most popular deltas + + PTR_ModuleHeader m_pModuleHeader; + SimpleModuleHeader * m_pSimpleModuleHeader; + void * m_pEHTypeTable; + SectionMethodList m_MethodList; + GcSegmentHandle m_FrozenSegment; + HANDLE m_hOsModuleHandle; + bool m_fFinalizerInitComplete; // used only by classlib modules + + PTR_StaticGcDesc m_pStaticsGCInfo; + PTR_StaticGcDesc m_pThreadStaticsGCInfo; + PTR_UInt8 m_pStaticsGCDataSection; +}; + diff --git a/src/Native/Runtime/new.h b/src/Native/Runtime/new.h new file mode 100644 index 00000000000..26b330bafd7 --- /dev/null +++ b/src/Native/Runtime/new.h @@ -0,0 +1,53 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// Overload new and delete operators to provide the required Redhawk semantics. +// + +#ifndef __NEW_INCLUDED +#define __NEW_INCLUDED + +#ifndef DACCESS_COMPILE + +__declspec(selectany) HANDLE g_hHeap = NULL; + +inline void * BaseNew(size_t cbSize) +{ + // + // @TODO: revisit this implementation + // + if (NULL == g_hHeap) + { + // NOTE: GetProcessHeap is indempotent, so all threads racing to initialize this global will + // initialize it with the same value. + g_hHeap = PalGetProcessHeap(); + } + return PalHeapAlloc(g_hHeap, 0, cbSize); +} + +inline void BaseDelete(void * pvMemory) +{ + // + // @TODO: revisit this implementation + // + + //ASSERT(g_hHeap != NULL); + PalHeapFree(g_hHeap, 0, pvMemory); +} + +// +// All 'operator new' variations have the same contract, which is to return NULL when out of memory. +// +inline void * __cdecl operator new(size_t cbSize) { return BaseNew(cbSize); } // normal +inline void * __cdecl operator new[](size_t cbSize) { return BaseNew(cbSize); } // array +inline void * __cdecl operator new(size_t cbSize, void * pvWhere) { return pvWhere; } // placement +inline void __cdecl operator delete(void * pvMemory) { BaseDelete(pvMemory); } // normal +inline void __cdecl operator delete[](void * pvMemory) { BaseDelete(pvMemory); } // array +inline void __cdecl operator delete(void * pvMemory, void * pvWhere) { } // placement + +#endif // DACCESS_COMPILE + +#endif // !__NEW_INCLUDED diff --git a/src/Native/Runtime/portable.cpp b/src/Native/Runtime/portable.cpp new file mode 100644 index 00000000000..26e4239ef3e --- /dev/null +++ b/src/Native/Runtime/portable.cpp @@ -0,0 +1,253 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" + +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" + +#include "slist.h" +#include "gcrhinterface.h" +#include "module.h" +#include "varint.h" +#include "holder.h" +#include "rhbinder.h" +#include "crst.h" +#include "rwlock.h" +#include "runtimeinstance.h" +#include "event.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "threadstore.h" + +#include "eetype.h" +#include "objectlayout.h" + +EXTERN_C REDHAWK_API void* REDHAWK_CALLCONV RhpPublishObject(void* pObject, UIntNative cbSize); + +#if defined(FEATURE_SVR_GC) +namespace SVR { + class GCHeap; +} +#endif // defined(FEATURE_SVR_GC) + +struct alloc_context +{ + UInt8* alloc_ptr; + UInt8* alloc_limit; + __int64 alloc_bytes; //Number of bytes allocated on SOH by this context + __int64 alloc_bytes_loh; //Number of bytes allocated on LOH by this context +#if defined(FEATURE_SVR_GC) + SVR::GCHeap* alloc_heap; + SVR::GCHeap* home_heap; +#endif // defined(FEATURE_SVR_GC) + int alloc_count; +}; + +// +// PInvoke +// +COOP_PINVOKE_HELPER(void, RhpReversePInvoke2, (ReversePInvokeFrame* pFrame)) +{ + Thread* pCurThread = ThreadStore::RawGetCurrentThread(); + pFrame->m_savedThread = pCurThread; + if (pCurThread->TryFastReversePInvoke(pFrame)) + return; + + pCurThread->ReversePInvoke(pFrame); +} + +COOP_PINVOKE_HELPER(void, RhpReversePInvokeReturn, (ReversePInvokeFrame* pFrame)) +{ + pFrame->m_savedThread->ReversePInvokeReturn(pFrame); +} + +// +// Allocations +// +// runtimeexports.cs -- @TODO: use C# implementation +COOP_PINVOKE_HELPER(Object *, RhNewObject, (EEType* pEEType)) +{ + ASSERT_MSG(!pEEType->RequiresAlign8() && !pEEType->HasFinalizer(), "NYI"); + + Thread * pCurThread = ThreadStore::GetCurrentThread(); + alloc_context * acontext = pCurThread->GetAllocContext(); + Object * pObject; + + size_t size = pEEType->get_BaseSize(); + + UInt8* result = acontext->alloc_ptr; + UInt8* advance = result + size; + if (advance <= acontext->alloc_limit) + { + acontext->alloc_ptr = advance; + pObject = (Object *)result; + pObject->set_EEType(pEEType); + return pObject; + } + + pObject = (Object *)RedhawkGCInterface::Alloc(pCurThread, size, 0, pEEType); + if (pObject == nullptr) + { + ASSERT_UNCONDITIONALLY("NYI"); // TODO: Throw OOM + } + pObject->set_EEType(pEEType); + + if (size >= RH_LARGE_OBJECT_SIZE) + RhpPublishObject(pObject, size); + + return pObject; +} +// runtimeexports.cs -- @TODO: use C# implementation +COOP_PINVOKE_HELPER(Array *, RhNewArray, (EEType * pArrayEEType, int numElements)) +{ + ASSERT_MSG(!pArrayEEType->RequiresAlign8(), "NYI"); + + Thread * pCurThread = ThreadStore::GetCurrentThread(); + alloc_context * acontext = pCurThread->GetAllocContext(); + Array * pObject; + + // TODO: Overflow checks + size_t size = 3 * sizeof(UIntNative) + (numElements * pArrayEEType->get_ComponentSize()); + // Align up + size = (size + (sizeof(UIntNative) - 1)) & ~(sizeof(UIntNative) - 1); + + UInt8* result = acontext->alloc_ptr; + UInt8* advance = result + size; + if (advance <= acontext->alloc_limit) + { + acontext->alloc_ptr = advance; + pObject = (Array *)result; + pObject->set_EEType(pArrayEEType); + pObject->InitArrayLength((UInt32)numElements); + return pObject; + } + + pObject = (Array *)RedhawkGCInterface::Alloc(pCurThread, size, 0, pArrayEEType); + if (pObject == nullptr) + { + ASSERT_UNCONDITIONALLY("NYI"); // TODO: Throw OOM + } + pObject->set_EEType(pArrayEEType); + pObject->InitArrayLength((UInt32)numElements); + + if (size >= RH_LARGE_OBJECT_SIZE) + RhpPublishObject(pObject, size); + + return pObject; +} + +COOP_PINVOKE_HELPER(void, RhpNewFast, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +COOP_PINVOKE_HELPER(void, RhpNewFinalizable, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +COOP_PINVOKE_HELPER(void, RhpNewArray, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +COOP_PINVOKE_HELPER(void, RhpInitialDynamicInterfaceDispatch, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + + +// finalizer.cs +COOP_PINVOKE_HELPER(void, RhpSetHaveNewClasslibs, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +// finalizer.cs +COOP_PINVOKE_HELPER(void, ProcessFinalizers, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +// runtimeexports.cs +COOP_PINVOKE_HELPER(void, RhpReversePInvokeBadTransition, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +// +// Return address hijacking +// +COOP_PINVOKE_HELPER(void, RhpGcProbeHijackScalar, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhpGcProbeHijackObject, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhpGcProbeHijackByref, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhpGcStressHijackScalar, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhpGcStressHijackObject, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhpGcStressHijackByref, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +// +// Write barriers +// + +// WriteBarriers.asm +COOP_PINVOKE_HELPER(void, RhpBulkWriteBarrier, (void* pMemStart, UInt32 cbMemSize)) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} + +// +// type cast stuff from TypeCast.cs +// +COOP_PINVOKE_HELPER(void, RhTypeCast_IsInstanceOfClass, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhTypeCast_CheckCastClass, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhTypeCast_IsInstanceOfArray, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhTypeCast_CheckCastArray, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhTypeCast_IsInstanceOfInterface, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhTypeCast_CheckCastInterface, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} +COOP_PINVOKE_HELPER(void, RhTypeCast_CheckVectorElemAddr, ()) +{ + ASSERT_UNCONDITIONALLY("NYI"); +} diff --git a/src/Native/Runtime/profheapwalkhelper.cpp b/src/Native/Runtime/profheapwalkhelper.cpp new file mode 100644 index 00000000000..cc6986028ff --- /dev/null +++ b/src/Native/Runtime/profheapwalkhelper.cpp @@ -0,0 +1,216 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// On desktop CLR, GC ETW event firing borrows heavily from code in the profiling API, +// as the GC already called hooks in the profapi to notify it of roots & references. +// This file shims up that profapi code the GC expects, though only for the purpose of +// firing ETW events (not for getting a full profapi up on redhawk). +// + +#if defined(FEATURE_EVENT_TRACE) + +#include "commontypes.h" +#include "daccess.h" +#include "debugmacrosext.h" +#include "palredhawkcommon.h" +#include "gcrhenv.h" + + +//--------------------------------------------------------------------------------------- +// +// Callback of type promote_func called by GC while scanning roots (in GCProfileWalkHeap, +// called after the collection). Wrapper around EEToProfInterfaceImpl::RootReference2, +// which does the real work. +// +// Arguments: +// ppObject - Object reference encountered +/// ppObjectRef - Address that references ppObject +// pSC - ProfilingScanContext * containing the root kind and GCReferencesData used +// by RootReference2 +// dwFlags - Properties of the root as GC_CALL* constants (this function converts +// to COR_PRF_GC_ROOT_FLAGS. +// + +void ScanRootsHelper(Object** ppObject, Object** ppObjectRef, ScanContext *pSC, DWORD dwFlags) +{ + ProfilingScanContext *pPSC = (ProfilingScanContext *)pSC; + + DWORD dwEtwRootFlags = 0; + if (dwFlags & GC_CALL_INTERIOR) + dwEtwRootFlags |= kEtwGCRootFlagsInterior; + if (dwFlags & GC_CALL_PINNED) + dwEtwRootFlags |= kEtwGCRootFlagsPinning; + + void *rootID = ppObjectRef; + + if (pPSC->dwEtwRootKind == kEtwGCRootKindFinalizer) + ppObject = ppObjectRef; + + // Notify ETW of the root + + if (ETW::GCLog::ShouldWalkHeapRootsForEtw()) + { + ETW::GCLog::RootReference( + rootID, // root address + *ppObject, // object being rooted + NULL, // pSecondaryNodeForDependentHandle is NULL, cuz this isn't a dependent handle + FALSE, // is dependent handle + pPSC, + dwFlags, // dwGCFlags + dwEtwRootFlags); + } +} + +//--------------------------------------------------------------------------------------- +// +// Callback of type walk_fn used by GCHeap::WalkObject. Keeps a count of each +// object reference found. +// +// Arguments: +// pBO - Object reference encountered in walk +// context - running count of object references encountered +// +// Return Value: +// Always returns TRUE to object walker so it walks the entire object +// + +BOOL CountContainedObjectRef(Object * pBO, void * context) +{ + LIMITED_METHOD_CONTRACT; + // Increase the count + (*((size_t *)context))++; + + return TRUE; +} + +//--------------------------------------------------------------------------------------- +// +// Callback of type walk_fn used by GCHeap::WalkObject. Stores each object reference +// encountered into an array. +// +// Arguments: +// pBO - Object reference encountered in walk +// context - Array of locations within the walked object that point to other +// objects. On entry, (*context) points to the next unfilled array +// entry. On exit, that location is filled, and (*context) is incremented +// to point to the next entry. +// +// Return Value: +// Always returns TRUE to object walker so it walks the entire object +// + +BOOL SaveContainedObjectRef(Object * pBO, void * context) +{ + LIMITED_METHOD_CONTRACT; + // Assign the value + **((Object ***)context) = pBO; + + // Now increment the array pointer + // + // Note that HeapWalkHelper has already walked the references once to count them up, + // and then allocated an array big enough to hold those references. First time this + // callback is called for a given object, (*context) points to the first entry in the + // array. So "blindly" incrementing (*context) here and using it next time around + // for the next reference, over and over again, should be safe. + (*((Object ***)context))++; + + return TRUE; +} + +//--------------------------------------------------------------------------------------- +// +// Callback of type walk_fn used by the GC when walking the heap, to help profapi +// track objects. This guy orchestrates the use of the above callbacks which dig +// into object references contained each object encountered by this callback. +// +// Arguments: +// pBO - Object reference encountered on the heap +// +// Return Value: +// BOOL indicating whether the heap walk should continue. +// TRUE=continue +// FALSE=stop +// + +BOOL HeapWalkHelper(Object * pBO, void * pvContext) +{ + OBJECTREF * arrObjRef = NULL; + size_t cNumRefs = 0; + bool bOnStack = false; + //MethodTable * pMT = pBO->GetMethodTable(); + + ProfilerWalkHeapContext * pProfilerWalkHeapContext = (ProfilerWalkHeapContext *) pvContext; + + //if (pMT->ContainsPointersOrCollectible()) + { + // First round through calculates the number of object refs for this class + GCHeap::GetGCHeap()->WalkObject(pBO, &CountContainedObjectRef, (void *)&cNumRefs); + + if (cNumRefs > 0) + { + // Create an array to contain all of the refs for this object + bOnStack = cNumRefs <= 32 ? true : false; + + if (bOnStack) + { + // It's small enough, so just allocate on the stack + arrObjRef = (OBJECTREF *)_alloca(cNumRefs * sizeof(OBJECTREF)); + } + else + { + // Otherwise, allocate from the heap + arrObjRef = new (nothrow) OBJECTREF[cNumRefs]; + + if (!arrObjRef) + { + return FALSE; + } + } + + // Second round saves off all of the ref values + OBJECTREF * pCurObjRef = arrObjRef; + GCHeap::GetGCHeap()->WalkObject(pBO, &SaveContainedObjectRef, (void *)&pCurObjRef); + } + } + + HRESULT hr = E_FAIL; + +#ifdef FEATURE_ETW + if (ETW::GCLog::ShouldWalkHeapObjectsForEtw()) + { + ETW::GCLog::ObjectReference( + pProfilerWalkHeapContext, + pBO, + ULONGLONG(pBO->get_SafeEEType()), + cNumRefs, + (Object **) arrObjRef); + } +#endif // FEATURE_ETW + + // If the data was not allocated on the stack, need to clean it up. + if ((arrObjRef != NULL) && !bOnStack) + { + delete [] arrObjRef; + } + + // Return TRUE iff we want to the heap walk to continue. The only way we'd abort the + // heap walk is if we're issuing profapi callbacks, and the profapi profiler + // intentionally returned a failed HR (as its request that we stop the walk). There's + // a potential conflict here. If a profapi profiler and an ETW profiler are both + // monitoring the heap dump, and the profapi profiler requests to abort the walk (but + // the ETW profiler may not want to abort the walk), then what do we do? The profapi + // profiler gets precedence. We don't want to accidentally send more callbacks to a + // profapi profiler that explicitly requested an abort. The ETW profiler will just + // have to deal. In theory, I could make the code more complex by remembering that a + // profapi profiler requested to abort the dump but an ETW profiler is still + // attached, and then intentionally inhibit the remainder of the profapi callbacks + // for this GC. But that's unnecessary complexity. In practice, it should be + // extremely rare that a profapi profiler is monitoring heap dumps AND an ETW + // profiler is also monitoring heap dumps. + return TRUE; +} + +#endif // defined(FEATURE_EVENT_TRACE) diff --git a/src/Native/Runtime/regdisplay.h b/src/Native/Runtime/regdisplay.h new file mode 100644 index 00000000000..0267e97e5cd --- /dev/null +++ b/src/Native/Runtime/regdisplay.h @@ -0,0 +1,91 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +#if defined(TARGET_X86) || defined(TARGET_AMD64) + +struct REGDISPLAY +{ + PTR_UIntNative pRax; + PTR_UIntNative pRcx; + PTR_UIntNative pRdx; + PTR_UIntNative pRbx; + // pEsp; + PTR_UIntNative pRbp; + PTR_UIntNative pRsi; + PTR_UIntNative pRdi; +#ifdef TARGET_AMD64 + PTR_UIntNative pR8; + PTR_UIntNative pR9; + PTR_UIntNative pR10; + PTR_UIntNative pR11; + PTR_UIntNative pR12; + PTR_UIntNative pR13; + PTR_UIntNative pR14; + PTR_UIntNative pR15; +#endif TARGET_AMD64 + + UIntNative SP; + PTR_PCODE pIP; + PCODE IP; + +#ifdef TARGET_AMD64 + Fp128 Xmm[16-6]; // preserved xmm6..xmm15 regs for EH stackwalk + // these need to be unwound during a stack walk + // for EH, but not adjusted, so we only need + // their values, not their addresses +#endif TARGET_AMD64 + + inline PCODE GetIP() { return IP; } + inline PTR_PCODE GetAddrOfIP() { return pIP; } + inline UIntNative GetSP() { return SP; } + inline UIntNative GetFP() { return *pRbp; } + inline UIntNative GetPP() { return *pRbx; } + + inline void SetIP(PCODE IP) { this->IP = IP; } + inline void SetAddrOfIP(PTR_PCODE pIP) { this->pIP = pIP; } + inline void SetSP(UIntNative SP) { this->SP = SP; } +}; + +#elif defined(TARGET_ARM) + +struct REGDISPLAY +{ + PTR_UIntNative pR0; + PTR_UIntNative pR1; + PTR_UIntNative pR2; + PTR_UIntNative pR3; + PTR_UIntNative pR4; + PTR_UIntNative pR5; + PTR_UIntNative pR6; + PTR_UIntNative pR7; + PTR_UIntNative pR8; + PTR_UIntNative pR9; + PTR_UIntNative pR10; + PTR_UIntNative pR11; + PTR_UIntNative pR12; + PTR_UIntNative pLR; + + UIntNative SP; + PTR_PCODE pIP; + PCODE IP; + + UInt64 D[16-8]; // preserved D registers D8..D15 (note that D16-D31 are not preserved according to the ABI spec) + // these need to be unwound during a stack walk + // for EH, but not adjusted, so we only need + // their values, not their addresses + + inline PCODE GetIP() { return IP; } + inline PTR_PCODE GetAddrOfIP() { return pIP; } + inline UIntNative GetSP() { return SP; } + inline UIntNative GetFP() { return *pR7; } + + inline void SetIP(PCODE IP) { this->IP = IP; } + inline void SetAddrOfIP(PTR_PCODE pIP) { this->pIP = pIP; } + inline void SetSP(UIntNative SP) { this->SP = SP; } +}; + +#endif // _X86_ || _AMD64_ + +typedef REGDISPLAY * PREGDISPLAY; diff --git a/src/Native/Runtime/rhbinder.h b/src/Native/Runtime/rhbinder.h new file mode 100644 index 00000000000..af1a9f4518d --- /dev/null +++ b/src/Native/Runtime/rhbinder.h @@ -0,0 +1,809 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// This header contains binder-generated data structures that the runtime consumes. +// +#ifndef RHDUMP_TARGET_NEUTRAL +#include "targetptrs.h" +#endif +#ifndef RHDUMP +#include "wellknownmethods.h" +#endif +#if !defined(RHDUMP) || defined(RHDUMP_TARGET_NEUTRAL) +// +// Region Relative Addresses (RRAs) +// +// Now that RH code can be emitted as a regular object file which can be linked with arbitrary native code, +// the module header (or any RH code or data) no longer has access to the OS module handle for which it is a +// part. As a result it's not a good idea to encode pointers as RVAs (relative virtual addresses) in the +// header or other RH metadata since without the OS module handle we have no mechanism to derive a VA (virtual +// address) from these RVAs. +// +// It's still desirable to utilize relative addresses since this saves space on 64-bit machines. So instead of +// RVAs we introduce the concept of RRAs (Region Relative Addresses). These are 32-bit offsets from the start +// of one of several "regions" defined by the Redhawk ModuleHeader. See the RegionTypes enum below for the +// currently defined regions. These are all contiguous regions of memory emitted by the binder (e.g. the text +// section which contains all RH method code). +// +// To recover a VA from an RRA you simply add the base VA of the correct region to the RRA. One weakness of +// the current mechanism is that there's no strong type checking to ensure that you use the correct region to +// interpret a given RRA. Possibly something could be done with templates here. For now we have a relatively +// small set of RRAs and as much as possible I've tried to abstract access to these via macros and other +// techniques to limit the places where mistakes can be made. If this turns out to be a bug farm we can +// revisit the issue. +// + +#ifdef RHDUMP +// Always use RVAs +typedef UInt32 RegionPtr; +#else +typedef TgtPTR_UInt8 RegionPtr; +#endif + +struct ModuleHeader +{ + // A subset of these flags match those that we need in the SectionMethodList at runtime. This set must be kept + // in sync with the definitions in SectionMethodList::SectionMethodListFlags + enum ModuleHeaderFlags + { + SmallPageListEntriesFlag = 0x00000001, // if set, 2-byte page list entries, 4-byte otherwise + SmallGCInfoListEntriesFlag = 0x00000002, // if set, 2-byte gc info list entries, 4-byte otherwise + SmallEHInfoListEntriesFlag = 0x00000004, // if set, 2-byte EH info list entries, 4-byte otherwise + FlagsMatchingSMLFlagsMask = 0x00000007, // Mask for flags that match those in SectionMethodList at runtime + UsesClrEHFlag = 0x00000008, // set if module expects CLR EH model, clear if module expects RH EH model. + StandaloneExe = 0x00000010, // this module represents the only (non-runtime) module in the process + }; + + enum ModuleHeaderConstants : UInt32 + { + CURRENT_VERSION = 1, // Version of the module header protocol. Increment on + // breaking changes + DELTA_SHORTCUT_TABLE_SIZE = 16, + MAX_REGIONS = 8, // Max number of regions described by the Regions array + MAX_WELL_KNOWN_METHODS = 8, // Max number of methods described by the WellKnownMethods array + NULL_RRA = 0xffffffff, // NULL value for region relative addresses (0 is often a + // legal RRA) + }; + + // The region types defined so far. Each module has at most one of each of these regions. + enum RegionTypes + { + TEXT_REGION = 0, // Code + DATA_REGION = 1, // Read/write data + RDATA_REGION = 2, // Read-only data + IAT_REGION = 3, // Import Address Table + }; + + UInt32 Version; + UInt32 Flags; // Various flags passed from the binder to the runtime (See ModuleHeaderFlags below). + UInt32 CountOfMethods; // Count of method bodies in this module. This count is used by the SectionMethodList as the size of its various arrays + UInt32 RraCodeMapInfo; // RRA to SectionMethodList, includes ip-to-method map and method gc info + UInt32 RraStaticsGCDataSection; // RRA to region containing GC statics + UInt32 RraStaticsGCInfo; // RRA to GC info for module statics (an array of StaticGcDesc structs) + UInt32 RraThreadStaticsGCInfo; // RRA to GC info for module thread statics (an array of StaticGcDesc structs) + UInt32 RraGidsWithGcRootsList; // RRA to head of list of GenericInstanceDescs which report GC roots +#ifdef FEATURE_VSD + UInt32 RraVSDIndirectionCells; // RRA to indirection cell array + UInt32 CountVSDIndirectionCells; // Number of (pointer sized) elements in the array pointed to by RraVSDIndirectionCells + UInt32 RraVSDInterfaceTargetInfos; // RRA to array of VSDInterfaceTargetInfo, parallel to indirection cell array +#elif defined(FEATURE_CACHED_INTERFACE_DISPATCH) + UInt32 RraInterfaceDispatchCells; // RRA to array of cache data structures used to dispatch interface calls + UInt32 CountInterfaceDispatchCells;// Number of elements in above array +#endif + UInt32 RraFrozenObjects; // RRA to the image's string literals (used for object ref verification of frozen strings) + UInt32 SizeFrozenObjects; // size, in bytes, of string literals + UInt32 RraEHInfo; // RRA to the EH info, which is past the variable length GC info. + UInt32 SizeEHTypeTable; // The EH info starts with a table of types used by the clauses, this is the size (in bytes) of that table. + UInt32 RraSystemObjectEEType; // RRA to the IAT entry for the classlib's System.Object EEType. Zero if this is the classlib itself. + UInt32 RraUnwindInfoBlob; // RRA to blob used for unwind infos that are referenced by the method GC info + UInt32 RraCallsiteInfoBlob; // RRA to blob used for callsite GC root strings that are referenced by the method GC info + UInt32 RraGenericInstances; // RRA to the list of regular generic instances contained in the module + UInt32 CountGenericInstances; // count of generic instances in the above list + UInt32 RraGcRootGenericInstances; // RRA to the list of generic instances with GC roots to report contained in the module + UInt32 CountGcRootGenericInstances;// count of generic instances in the above list + UInt32 RraVariantGenericInstances; // RRA to the list of generic instances with variant type parameters contained in the module + UInt32 CountVariantGenericInstances; // count of generic instances in the above list + UInt32 SizeStubCode; // size, in bytes, of stub code at the end of the TEXT_REGION. See ZapImage::SaveModuleHeader for details. + UInt32 RraReadOnlyBlobs; // RRA to list of read-only opaque data blobs + UInt32 SizeReadOnlyBlobs; // size, in bytes, of the read-only data blobs above + UInt32 RraNativeInitFunctions; // RRA to table of function pointers for initialization functions from linked in native code + UInt32 CountNativeInitFunctions; // count of the number of entries in the table above + // info for loop hijacking { + UInt32 RraLoopIndirCells; // RRA to start of loop hijacking indirection cells + UInt32 RraLoopIndirCellChunkBitmap;// RRA to a bitmap which tracks redirected loop hijack indirection cell chunks + UInt32 RraLoopRedirTargets; // RRA to start of code block which implements the redirected targets for hijacking loops + UInt32 RraLoopTargets; // RRA to start of compressed info describing the original loop targets (prior to redirection) + UInt32 CountOfLoopIndirCells; // count of loop hijacking indirection cells + // } // end info for loop hijacking + UInt32 RraDispatchMapLookupTable; // RRA of table of pointers to DispatchMaps + + UInt32 WellKnownMethods[MAX_WELL_KNOWN_METHODS]; // Array of methods with well known semantics defined + // in this module + + // These two arrays, RegionSize and RegionPtr are parallel arrays. They are not simply an array of + // structs because that would waste space on 64-bit due to the natural alignment requirement of the + // pointers. + UInt32 RegionSize[MAX_REGIONS]; // sizes of each region in the module + RegionPtr RegionPtr[MAX_REGIONS]; // Base addresses for the RRAs above + + TgtPTR_UInt32 PointerToTlsIndex; // Pointer to TLS index if this module uses thread statics (cannot be + // RRA because it's fixed up by the OS loader) + UInt32 TlsStartOffset; // Offset into TLS section at which this module's thread statics begin + +#ifdef FEATURE_PROFILING + UInt32 RraProfilingEntries; // RRA to start of profile info + UInt32 CountOfProfilingEntries; // count of profile info records +#endif // FEATURE_PROFILING + + UInt32 RraArrayBaseEEType; // RRA to the classlib's array base type EEType (usually System.Array), zero if this is not the classlib + +#ifdef FEATURE_CUSTOM_IMPORTS + UInt32 RraCustomImportDescriptors; // RRA to an array of CustomImportDescriptors + UInt32 CountCustomImportDescriptors; // count of entries in the above array +#endif // FEATURE_CUSTOM_IMPORTS + + // Macro to generate an inline accessor for RRA-based fields. +#ifdef RHDUMP +#define DEFINE_GET_ACCESSOR(_field, _region)\ + inline UInt64 Get##_field() { return Rra##_field == NULL_RRA ? NULL : RegionPtr[_region] + Rra##_field; } +#else +#define DEFINE_GET_ACCESSOR(_field, _region)\ + inline PTR_UInt8 Get##_field() { return Rra##_field == NULL_RRA ? NULL : RegionPtr[_region] + Rra##_field; } +#endif + + // Similar macro to DEFINE_GET_ACCESSOR that handles data that is read-write normally but read-only if the + // module is in standalone exe mode. +#ifdef RHDUMP +#define DEFINE_GET_ACCESSOR_RO_OR_RW_DATA(_field)\ + inline UInt64 Get##_field() { return Rra##_field == NULL_RRA ? NULL : RegionPtr[(Flags & StandaloneExe) ? RDATA_REGION : DATA_REGION] + Rra##_field; } +#else +#define DEFINE_GET_ACCESSOR_RO_OR_RW_DATA(_field)\ + inline PTR_UInt8 Get##_field() { return Rra##_field == NULL_RRA ? NULL : RegionPtr[(Flags & StandaloneExe) ? RDATA_REGION : DATA_REGION] + Rra##_field; } +#endif + + DEFINE_GET_ACCESSOR(SystemObjectEEType, IAT_REGION); + + DEFINE_GET_ACCESSOR(CodeMapInfo, RDATA_REGION); + DEFINE_GET_ACCESSOR(StaticsGCInfo, RDATA_REGION); + DEFINE_GET_ACCESSOR(ThreadStaticsGCInfo, RDATA_REGION); + DEFINE_GET_ACCESSOR_RO_OR_RW_DATA(GidsWithGcRootsList); +#ifdef FEATURE_VSD + DEFINE_GET_ACCESSOR(VSDInterfaceTargetInfos, RDATA_REGION); +#endif + DEFINE_GET_ACCESSOR(EHInfo, RDATA_REGION); + DEFINE_GET_ACCESSOR(UnwindInfoBlob, RDATA_REGION); + DEFINE_GET_ACCESSOR(CallsiteInfoBlob, RDATA_REGION); + + DEFINE_GET_ACCESSOR(StaticsGCDataSection, DATA_REGION); +#ifdef FEATURE_VSD + DEFINE_GET_ACCESSOR(VSDIndirectionCells, DATA_REGION); +#elif defined(FEATURE_CACHED_INTERFACE_DISPATCH) + DEFINE_GET_ACCESSOR(InterfaceDispatchCells, DATA_REGION); +#endif + DEFINE_GET_ACCESSOR(FrozenObjects, DATA_REGION); + DEFINE_GET_ACCESSOR_RO_OR_RW_DATA(GenericInstances); + DEFINE_GET_ACCESSOR_RO_OR_RW_DATA(GcRootGenericInstances); + DEFINE_GET_ACCESSOR_RO_OR_RW_DATA(VariantGenericInstances); + + DEFINE_GET_ACCESSOR(LoopIndirCells, DATA_REGION); + DEFINE_GET_ACCESSOR(LoopIndirCellChunkBitmap, DATA_REGION); + DEFINE_GET_ACCESSOR(LoopRedirTargets, TEXT_REGION); + DEFINE_GET_ACCESSOR(LoopTargets, RDATA_REGION); + + DEFINE_GET_ACCESSOR(DispatchMapLookupTable, RDATA_REGION); + +#ifdef FEATURE_PROFILING + DEFINE_GET_ACCESSOR(ProfilingEntries, DATA_REGION); +#endif // FEATURE_PROFILING + + DEFINE_GET_ACCESSOR(ReadOnlyBlobs, RDATA_REGION); + + DEFINE_GET_ACCESSOR(NativeInitFunctions, RDATA_REGION); + +#ifdef FEATURE_CUSTOM_IMPORTS + DEFINE_GET_ACCESSOR(CustomImportDescriptors, RDATA_REGION); +#endif // FEATURE_CUSTOM_IMPORTS + +#ifndef RHDUMP + // Macro to generate an inline accessor for well known methods (these are all TEXT-based RRAs since they + // point to code). +#define DEFINE_WELL_KNOWN_METHOD(_name) \ + inline PTR_VOID Get_##_name() \ + { \ + return WellKnownMethods[WKM_##_name] == NULL_RRA ? NULL : RegionPtr[TEXT_REGION] + WellKnownMethods[WKM_##_name]; \ + } +#include "WellKnownMethodList.h" +#undef DEFINE_WELL_KNOWN_METHOD +#endif // !RHDUMP +}; +#ifndef RHDUMP +typedef DPTR(ModuleHeader) PTR_ModuleHeader; +#endif // !RHDUMP + +#if !defined(RHDUMP) || !defined(RHDUMP_TARGET_NEUTRAL) // due to dependency on StaticGcDesc +struct SimpleModuleHeader +{ + void* m_pStaticsGcDataSection; + StaticGcDesc* m_pStaticsGcInfo; + StaticGcDesc* m_pThreadStaticsGcInfo; +}; +#endif // !defined(RHDUMP) || !defined(RHDUMP_TARGET_NEUTRAL) + + +class GcPollInfo +{ +public: + +#ifndef RHDUMP + static const UInt32 indirCellsPerBitmapBit = 64 / POINTER_SIZE; // one cache line per bit +#endif // !RHDUMP + + static const UInt32 cbChunkCommonCode_X64 = 17; + static const UInt32 cbChunkCommonCode_X86 = 16; + static const UInt32 cbChunkCommonCode_ARM = 32; +#ifdef TARGET_ARM + // on ARM, the index of the indirection cell can be computed + // from the pointer to the indirection cell left in R12, + // thus we need only one entry point on ARM, + // thus entries take no space, and you can have as many as you want + static const UInt32 cbEntry = 0; + static const UInt32 cbBundleCommonCode = 0; + static const UInt32 entriesPerBundle = 0x7fffffff; + static const UInt32 bundlesPerChunk = 0x7fffffff; + static const UInt32 entriesPerChunk = 0x7fffffff; +#else + static const UInt32 cbEntry = 4; // push imm8 / jmp rel8 + static const UInt32 cbBundleCommonCode = 5; // jmp rel32 + + static const UInt32 entriesPerSubBundlePos = 32; // for the half with forward jumps + static const UInt32 entriesPerSubBundleNeg = 30; // for the half with negative jumps + static const UInt32 entriesPerBundle = entriesPerSubBundlePos + entriesPerSubBundleNeg; + static const UInt32 bundlesPerChunk = 4; + static const UInt32 entriesPerChunk = bundlesPerChunk * entriesPerBundle; +#endif + + static const UInt32 cbFullBundle = cbBundleCommonCode + + (entriesPerBundle * cbEntry); + +#ifndef RHDUMP + static UInt32 EntryIndexToStubOffset(UInt32 entryIndex) + { +# if defined(TARGET_ARM) + return EntryIndexToStubOffset(entryIndex, cbChunkCommonCode_ARM); +# elif defined(TARGET_X64) + return EntryIndexToStubOffset(entryIndex, cbChunkCommonCode_X64); +# else + return EntryIndexToStubOffset(entryIndex, cbChunkCommonCode_X86); +# endif + } +#endif + + static UInt32 EntryIndexToStubOffset(UInt32 entryIndex, UInt32 cbChunkCommonCode) + { +# if defined(TARGET_ARM) + UNREFERENCED_PARAMETER(entryIndex); + UNREFERENCED_PARAMETER(cbChunkCommonCode); + + return 0; +# else + UInt32 cbFullChunk = cbChunkCommonCode + + (bundlesPerChunk * cbBundleCommonCode) + + (entriesPerChunk * cbEntry); + + UInt32 numFullChunks = entryIndex / entriesPerChunk; + UInt32 numEntriesInLastChunk = entryIndex - (numFullChunks * entriesPerChunk); + + UInt32 numFullBundles = numEntriesInLastChunk / entriesPerBundle; + UInt32 numEntriesInLastBundle = numEntriesInLastChunk - (numFullBundles * entriesPerBundle); + + UInt32 offset = (numFullChunks * cbFullChunk) + + cbChunkCommonCode + + (numFullBundles * cbFullBundle) + + (numEntriesInLastBundle * cbEntry); + + if (numEntriesInLastBundle >= entriesPerSubBundlePos) + offset += cbBundleCommonCode; + + return offset; +# endif + } +}; +#endif // !defined(RHDUMP) || defined(RHDUMP_TARGET_NEUTRAL) +#if !defined(RHDUMP) || !defined(RHDUMP_TARGET_NEUTRAL) + + + +struct StaticGcDesc +{ + struct GCSeries + { + UInt32 m_size; + UInt32 m_startOffset; + }; + + UInt32 m_numSeries; + GCSeries m_series[1]; + + UInt32 GetSize() + { + return (UInt32)(offsetof(StaticGcDesc, m_series) + (m_numSeries * sizeof(GCSeries))); + } + +#ifdef DACCESS_COMPILE + static UInt32 DacSize(TADDR addr); +#endif +}; + +#ifdef RHDUMP +typedef StaticGcDesc * PTR_StaticGcDesc; +typedef StaticGcDesc::GCSeries * PTR_StaticGcDescGCSeries; +#else +typedef SPTR(StaticGcDesc) PTR_StaticGcDesc; +typedef DPTR(StaticGcDesc::GCSeries) PTR_StaticGcDescGCSeries; +#endif + +class EEType; + +#ifdef FEATURE_VSD +//------------------------------------------------------------------------------------------------- +// Information about the target interface method of an interface call site. +// +// @TODO: consider replacing the EEType* with a RVA to either the EEType in the image, or to the +// IAT entry within the image for EETypes in a different image. This would keep this struct at 8 bytes +// on WIN64 rather than 12. At runtime, we would need to resolve the RVA to a real EEType* in the +// resolve stub in order to confirm the cache hit. One possible way to do this is to store these in +// chunks, with a RVA to the head of the module at the head of each chunk and chunks on some power of +// two boundary. We would take the address of the struct, mask off some number of low bits to recover +// the chunk head, then use the RVA there to recover the module head, and ultimatley change the RVA +// into a EEType* or EEType**. + +#include // Stub dispatch relies on the layout of this struct. + +struct VSDInterfaceTargetInfo +{ + // IMPORTANT: This union must be the first element; this is depended on by the + // resolve stub. + union + { + EEType * m_pItf; + EEType ** m_ppItf; +#if defined(RHDUMP) || defined(BINDER) + UIntTarget m_ptrVal; // ensure this structure is the right size in cross-build scenarios +#endif // defined(RHDUMP) || defined(BINDER) + }; + + // IMPORTANT: This field must be the second element; this is depended on by the + // resolve stub. + UInt16 m_slotNumber; + + enum EETypePtrType + { + EETypeIsPtr = 0x0, + EETypeIsPtrPtr = 0x1, + }; + + enum FlagsMasks + { + EETypePtrTypeMask = 0x1, + }; + + UInt16 m_flags; + +#if !defined(RHDUMP) && !defined(BINDER) + VSDInterfaceTargetInfo() + : m_flags(EETypeIsPtr), m_pItf(NULL), m_slotNumber((UInt16)(-1)) + {} + + VSDInterfaceTargetInfo(EEType *pItf, UInt16 slotNumber) + : m_flags(EETypeIsPtr), m_pItf(pItf), m_slotNumber(slotNumber) + {} + + EETypePtrType GetEETypePtrType() const + { return (EETypePtrType)(m_flags & EETypePtrTypeMask); } + + EEType * GetInterfaceType() const + { + switch (GetEETypePtrType()) + { + case EETypeIsPtr: + return m_pItf; + break; + case EETypeIsPtrPtr: + return *m_ppItf; + break; + default: + UNREACHABLE(); + return NULL; + break; + } + } + + UInt16 GetSlotNumber() const + { return m_slotNumber; } + + static bool Equal(const VSDInterfaceTargetInfo& lhs, const VSDInterfaceTargetInfo& rhs) + { + return lhs.GetInterfaceType() == rhs.GetInterfaceType() && + lhs.m_slotNumber == rhs.m_slotNumber; + } + + UIntNative Hash() const + { + return (reinterpret_cast(GetInterfaceType()) >> 2) ^ + static_cast(GetSlotNumber()); + } +#endif +}; + +#include + +#elif defined(FEATURE_CACHED_INTERFACE_DISPATCH) + +struct InterfaceDispatchCacheHeader +{ + EEType * m_pInterfaceType; // EEType of interface to dispatch on + UInt16 m_slotIndex; // Which slot on the interface should be dispatched on. +}; + +// One of these is allocated per interface call site. It holds the stub to call, data to pass to that stub +// (cache information) and the interface contract, i.e. the interface type and slot being called. +struct InterfaceDispatchCell +{ + // The first two fields must remain together and at the beginning of the structure. This is due to the + // synchronization requirements of the code that updates these at runtime and the instructions generated + // by the binder for interface call sites. + UIntTarget m_pStub; // Call this code to execute the interface dispatch + volatile UIntTarget m_pCache; // Context used by the stub above (one or both of the low two bits are set + // for initial dispatch, and if not set, using this as a cache pointer.) + + enum Flags + { + // The low 2 bits of the m_pCache pointer are treated specially so that we can avoid the need for + // extra fields on this type. + IDC_CachePointerIsInterfaceRelativePointer = 0x3, + IDC_CachePointerIsIndirectedInterfaceRelativePointer = 0x2, + IDC_CachePointerIsInterfacePointer = 0x1, + IDC_CachePointerPointsAtCache = 0x0, + IDC_CachePointerMask = 0x3, + }; + +#if !defined(RHDUMP) && !defined(BINDER) + EEType * GetInterfaceType() const + { + // Capture m_pCache into a local for safe access (this is a volatile read of a value that may be + // modified on another thread while this function is executing.) + UIntTarget cachePointerValue = m_pCache; + switch (cachePointerValue & IDC_CachePointerMask) + { + case IDC_CachePointerPointsAtCache: + return ((InterfaceDispatchCacheHeader*)cachePointerValue)->m_pInterfaceType; + case IDC_CachePointerIsInterfacePointer: + return (EEType*)(cachePointerValue & ~IDC_CachePointerMask); + case IDC_CachePointerIsInterfaceRelativePointer: + case IDC_CachePointerIsIndirectedInterfaceRelativePointer: + { + UIntTarget interfacePointerValue = (UIntTarget)&m_pCache + cachePointerValue; + interfacePointerValue &= ~IDC_CachePointerMask; + if ((cachePointerValue & IDC_CachePointerMask) == IDC_CachePointerIsInterfaceRelativePointer) + { + return (EEType*)interfacePointerValue; + } + else + { + return *(EEType**)interfacePointerValue; + } + } + } + return nullptr; + } + + static bool IsCache(UIntTarget value) + { + if ((value & IDC_CachePointerMask) != 0) + { + return false; + } + else + { + return true; + } + } + + InterfaceDispatchCacheHeader* GetCache() const + { + // Capture m_pCache into a local for safe access (this is a volatile read of a value that may be + // modified on another thread while this function is executing.) + UIntTarget cachePointerValue = m_pCache; + if (IsCache(cachePointerValue)) + { + return (InterfaceDispatchCacheHeader*)cachePointerValue; + } + else + { + return 0; + } + } + + UInt16 GetSlotNumber() const + { + // Only call GetCache once, subsequent calls are not garaunteed to return equal results + InterfaceDispatchCacheHeader* cache = GetCache(); + + // If we have a cache, use it instead as its faster to access + if (cache != nullptr) + { + return cache->m_slotIndex; + } + + // The slot number for an interface dispatch cell is encoded once per run of InterfaceDispatchCells + // The run is terminated by having an interface dispatch cell with a null stub pointer. + const InterfaceDispatchCell *currentCell = this; + while (currentCell->m_pStub != 0) + { + currentCell = currentCell + 1; + } + + return (UInt16)currentCell->m_pCache; + } +#endif // !RHDUMP && !BINDER +}; + +#endif // FEATURE_CACHED_INTERFACE_DISPATCH + +#ifdef TARGET_ARM +// Note for ARM: try and keep the flags in the low 16-bits, since they're not easy to load into a register in +// a single instruction within our stubs. +enum PInvokeTransitionFrameFlags +{ + // standard preserved registers + PTFF_SAVE_R4 = 0x00000001, + PTFF_SAVE_R5 = 0x00000002, + PTFF_SAVE_R6 = 0x00000004, + PTFF_SAVE_R7 = 0x00000008, // should never be used, we require FP frames for methods with + // pinvoke and it is saved into the frame pointer field instead + PTFF_SAVE_R8 = 0x00000010, + PTFF_SAVE_R9 = 0x00000020, + PTFF_SAVE_R10 = 0x00000040, + PTFF_SAVE_SP = 0x00000100, // Used for 'coop pinvokes' in runtime helper routines. Methods with + // PInvokes are required to have a frame pointers, but methods which + // call runtime helpers are not. Therefore, methods that call runtime + // helpers may need SP to seed the stackwalk. + + // scratch registers + PTFF_SAVE_R0 = 0x00000200, + PTFF_SAVE_R1 = 0x00000400, + PTFF_SAVE_R2 = 0x00000800, + PTFF_SAVE_R3 = 0x00001000, + PTFF_SAVE_LR = 0x00002000, // this is useful for the case of loop hijacking where we need both + // a return address pointing into the hijacked method and that method's + // lr register, which may hold a gc pointer + + PTFF_R0_IS_GCREF = 0x00004000, // used by hijack handler to report return value of hijacked method + PTFF_R0_IS_BYREF = 0x00008000, // used by hijack handler to report return value of hijacked method +}; +#else // TARGET_ARM +enum PInvokeTransitionFrameFlags +{ + // standard preserved registers + PTFF_SAVE_RBX = 0x00000001, + PTFF_SAVE_RSI = 0x00000002, + PTFF_SAVE_RDI = 0x00000004, + PTFF_SAVE_RBP = 0x00000008, // should never be used, we require RBP frames for methods with + // pinvoke and it is saved into the frame pointer field instead + PTFF_SAVE_R12 = 0x00000010, + PTFF_SAVE_R13 = 0x00000020, + PTFF_SAVE_R14 = 0x00000040, + PTFF_SAVE_R15 = 0x00000080, + + PTFF_SAVE_RSP = 0x00008000, // Used for 'coop pinvokes' in runtime helper routines. Methods with + // PInvokes are required to have a frame pointers, but methods which + // call runtime helpers are not. Therefore, methods that call runtime + // helpers may need RSP to seed the stackwalk. + // + // NOTE: despite the fact that this flag's bit is out of order, it is + // still expected to be saved here after the preserved registers and + // before the scratch registers + PTFF_SAVE_RAX = 0x00000100, + PTFF_SAVE_RCX = 0x00000200, + PTFF_SAVE_RDX = 0x00000400, + PTFF_SAVE_R8 = 0x00000800, + PTFF_SAVE_R9 = 0x00001000, + PTFF_SAVE_R10 = 0x00002000, + PTFF_SAVE_R11 = 0x00004000, + + PTFF_RAX_IS_GCREF = 0x00010000, // used by hijack handler to report return value of hijacked method + PTFF_RAX_IS_BYREF = 0x00020000, // used by hijack handler to report return value of hijacked method +}; +#endif // TARGET_ARM + +#pragma warning(push) +#pragma warning(disable:4200) // nonstandard extension used: zero-sized array in struct/union +class Thread; +struct PInvokeTransitionFrame +{ +#ifdef TARGET_ARM + TgtPTR_Void m_ChainPointer; // R11, used by OS to walk stack quickly +#endif + TgtPTR_Void m_RIP; + TgtPTR_Void m_FramePointer; + TgtPTR_Thread m_pThread; // unused by stack crawler, this is so GetThread is only called once per method + UInt32 m_dwFlags; // PInvokeTransitionFrameFlags +#ifdef TARGET_X64 + UInt32 m_dwAlignPad2; +#endif + UIntTarget m_PreservedRegs[]; +}; +#pragma warning(pop) + +#ifdef TARGET_X64 +// RBX, RSI, RDI, R12, R13, R14, R15, RAX, RSP +#define PInvokeTransitionFrame_SaveRegs_count 9 +#elif defined(TARGET_X86) +// RBX, RSI, RDI, RAX, RSP +#define PInvokeTransitionFrame_SaveRegs_count 5 +#elif defined(TARGET_ARM) +// R4-R6,R8-R10, R0, SP +#define PInvokeTransitionFrame_SaveRegs_count 8 +#endif +#define PInvokeTransitionFrame_MAX_SIZE (sizeof(PInvokeTransitionFrame) + (POINTER_SIZE * PInvokeTransitionFrame_SaveRegs_count)) + +#ifdef TARGET_X64 +#define OFFSETOF__Thread__m_pTransitionFrame 0x40 +#elif defined(TARGET_X86) +#define OFFSETOF__Thread__m_pTransitionFrame 0x2c +#elif defined(TARGET_ARM) +#define OFFSETOF__Thread__m_pTransitionFrame 0x2c +#endif + +#ifdef RHDUMP +typedef EEType * PTR_EEType; +typedef PTR_EEType * PTR_PTR_EEType; +#else +typedef DPTR(EEType) PTR_EEType; +typedef DPTR(PTR_EEType) PTR_PTR_EEType; +#endif + +struct EETypeRef +{ + union + { + EEType * pEEType; + EEType ** ppEEType; + UInt8 * rawPtr; + UIntTarget rawTargetPtr; // x86_amd64: keeps union big enough for target-platform pointer + }; + + static const size_t DOUBLE_INDIR_FLAG = 1; + + PTR_EEType GetValue() + { + if (dac_cast(rawTargetPtr) & DOUBLE_INDIR_FLAG) + return *dac_cast(rawTargetPtr - DOUBLE_INDIR_FLAG); + else + return dac_cast(rawTargetPtr); + } + + // If this type is referenced indirectly (via an IAT entry) resolve the reference to a direct pointer. + // This is only possible at runtime once the IAT has been updated and is used when unifying a generic + // instantiation and cutting any arbitrary dependencies to the module which first published this + // instantiation. Returns true if the type ref did need to be flattened. + bool Flatten() + { + if ((size_t)pEEType & DOUBLE_INDIR_FLAG) + { + pEEType = *(EEType **)(rawPtr - DOUBLE_INDIR_FLAG); + return true; + } + return false; + } +}; + +// Generic type parameter variance type (these are allowed only on generic interfaces or delegates). The type +// values must correspond to those defined in the CLR as CorGenericParamAttr (see corhdr.h). +enum GenericVarianceType : UInt8 +{ + GVT_NonVariant = 0, + GVT_Covariant = 1, + GVT_Contravariant = 2, + GVT_ArrayCovariant = 0x20, +}; + +// The GenericInstanceDesc structure holds additional type information associated with generic EETypes. The +// amount of data can potentially get quite large but many of the items can be omitted for most types. +// Therefore, rather than representing the data as a regular C++ struct with fixed fields, we include one +// fixed field, a bitmask indicating what sort of data is encoded, and then encode only the required data in a +// packed form. +// +// While this is straightforward enough, we have a lot of fields that now all require accessor methods (get +// offset of value, get value, set value) and the offset calculations, though simple, are messy and easy to +// get wrong. In light of this we use a script to write the accessor code. See +// rh\tools\WriteOptionalFieldsCode.pl for the script and rh\src\inc\GenericInstanceDescFields.src for the +// definitions of the fields that it takes as input. + +struct GenericInstanceDesc +{ +#include "GenericInstanceDescFields.h" + +#ifdef DACCESS_COMPILE + static UInt32 DacSize(TADDR addr); +#endif + + UInt32 GetHashCode() + { + UInt32 hash = 0; + const UInt32 HASH_MULT = 1220703125; // 5**13 + hash ^= (UInt32)dac_cast(this->GetGenericTypeDef().GetValue()); + for (UInt32 i = 0; i < this->GetArity(); i++) + { + hash *= HASH_MULT; + hash ^= (UInt32)dac_cast(this->GetParameterType(i).GetValue()); + } + return hash; + } +}; + +// Blobs are opaque data passed from the compiler, through the binder and into the native image. At runtime we +// provide a simple API to retrieve these blobs (they're keyed by a simple integer ID). Blobs are passed to +// the binder from the compiler and stored in native images by the binder in a sequential stream, each blob +// having the following header. +struct BlobHeader +{ + UInt32 m_flags; // Flags describing the blob (used by the binder only at the moment) + UInt32 m_id; // Unique identifier of the blob (used to access the blob at runtime) + // also used by BlobTypeFieldPreInit to identify (at bind time) which field to pre-init. + UInt32 m_size; // Size of the individual blob excluding this header (DWORD aligned) +}; + +// Structure used in the runtime initialization of deferred static class constructors. Deferred here means +// executed during normal code execution just prior to a static field on the type being accessed (as opposed +// to eager cctors, which are run at module load time). This is the fixed portion of the context structure, +// class libraries can add their own fields to the end. +struct StaticClassConstructionContext +{ + // Pointer to the code for the static class constructor method. This is initialized by the + // binder/runtime. + TgtPTR_Void m_cctorMethodAddress; + + // Initialization state of the class. This is initialized to 0. Every time managed code checks the + // cctor state the runtime will call the classlibrary's CheckStaticClassConstruction with this context + // structure unless initialized == 1. This check is specific to allow the classlibrary to store more + // than a binary state for each cctor if it so desires. + Int32 m_initialized; +}; + +#endif // !defined(RHDUMP) || !defined(RHDUMP_TARGET_NEUTRAL) + +#ifdef FEATURE_CUSTOM_IMPORTS +struct CustomImportDescriptor +{ + UInt32 RvaEATAddr; // RVA of the indirection cell of the address of the EAT for that module + UInt32 RvaIAT; // RVA of IAT array for that module + UInt32 CountIAT; // Count of entries in the above array +}; +#endif // FEATURE_CUSTOM_IMPORTS + +enum RhEHClauseKind +{ + RH_EH_CLAUSE_TYPED = 0, + RH_EH_CLAUSE_FAULT = 1, + + // local exceptions + RH_EH_CLAUSE_METHOD_BOUNDARY = 2, // /eh:rh + RH_EH_CLAUSE_FAIL_FAST = 3, // /eh:rh + + // CLR exceptions + RH_EH_CLAUSE_FILTER = 2, // /eh:clr + RH_EH_CLAUSE_UNUSED = 3, // /eh:clr +}; + +// Structure used to store offsets information of thread static fields, and mainly used +// by Reflection to get the address of that field in the TLS block +struct ThreadStaticFieldOffsets +{ + UInt32 StartingOffsetInTlsBlock; // Offset in the TLS block containing the thread static fields of a given type + UInt32 FieldOffset; // Offset of a thread static field from the start of its containing type's TLS fields block + // (in other words, the address of a field is 'TLS block + StartingOffsetInTlsBlock + FieldOffset') +}; diff --git a/src/Native/Runtime/rheventtrace.h b/src/Native/Runtime/rheventtrace.h new file mode 100644 index 00000000000..5d2014de845 --- /dev/null +++ b/src/Native/Runtime/rheventtrace.h @@ -0,0 +1,191 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// +// This header provides Redhawk-specific ETW code and macros, to allow sharing of common +// ETW code between Redhawk and desktop CLR. +// +#ifndef __RHEVENTTRACE_INCLUDED +#define __RHEVENTTRACE_INCLUDED + + +#ifdef FEATURE_ETW + +// FireEtwGCPerHeapHistorySpecial() has to be defined manually rather than via the manifest because it does +// not have a standard signature. +#define FireEtwGCPerHeapHistorySpecial(DataPerHeap, DataSize, ClrId) (MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context.IsEnabled && PalEventEnabled(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCPerHeapHistory)) ? Template_GCPerHeapHistorySpecial(Microsoft_Windows_Redhawk_GC_PrivateHandle, &GCPerHeapHistory, DataPerHeap, DataSize, ClrId) : 0 + +// Map the CLR private provider to our version so we can avoid inserting more #ifdef's in the code. +#define MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context MICROSOFT_WINDOWS_REDHAWK_GC_PRIVATE_PROVIDER_Context +#define MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_Context MICROSOFT_WINDOWS_REDHAWK_GC_PUBLIC_PROVIDER_Context +#define Microsoft_Windows_DotNETRuntimeHandle Microsoft_Windows_Redhawk_GC_PublicHandle + +#define CLR_GC_KEYWORD 0x1 +#define CLR_FUSION_KEYWORD 0x4 +#define CLR_LOADER_KEYWORD 0x8 +#define CLR_JIT_KEYWORD 0x10 +#define CLR_NGEN_KEYWORD 0x20 +#define CLR_STARTENUMERATION_KEYWORD 0x40 +#define CLR_ENDENUMERATION_KEYWORD 0x80 +#define CLR_SECURITY_KEYWORD 0x400 +#define CLR_APPDOMAINRESOURCEMANAGEMENT_KEYWORD 0x800 +#define CLR_JITTRACING_KEYWORD 0x1000 +#define CLR_INTEROP_KEYWORD 0x2000 +#define CLR_CONTENTION_KEYWORD 0x4000 +#define CLR_EXCEPTION_KEYWORD 0x8000 +#define CLR_THREADING_KEYWORD 0x10000 +#define CLR_JITTEDMETHODILTONATIVEMAP_KEYWORD 0x20000 +#define CLR_OVERRIDEANDSUPPRESSNGENEVENTS_KEYWORD 0x40000 +#define CLR_TYPE_KEYWORD 0x80000 +#define CLR_GCHEAPDUMP_KEYWORD 0x100000 +#define CLR_GCHEAPALLOC_KEYWORD 0x200000 +#define CLR_GCHEAPSURVIVALANDMOVEMENT_KEYWORD 0x400000 +#define CLR_GCHEAPCOLLECT_KEYWORD 0x800000 +#define CLR_GCHEAPANDTYPENAMES_KEYWORD 0x1000000 +#define CLR_PERFTRACK_KEYWORD 0x20000000 +#define CLR_STACK_KEYWORD 0x40000000 +#ifndef ERROR_SUCCESS +#define ERROR_SUCCESS 0 +#endif + +#undef ETW_TRACING_INITIALIZED +#define ETW_TRACING_INITIALIZED(RegHandle) (RegHandle != NULL) + +#undef ETW_CATEGORY_ENABLED +#define ETW_CATEGORY_ENABLED(Context, LevelParam, Keyword) \ + (Context.IsEnabled && \ + ( \ + (LevelParam <= ((Context).Level)) || \ + ((Context.Level) == 0) \ + ) && \ + ( \ + (Keyword == (ULONGLONG)0) || \ + ( \ + (Keyword & (Context.MatchAnyKeyword)) && \ + ( \ + (Keyword & (Context.MatchAllKeyword)) == (Context.MatchAllKeyword) \ + ) \ + ) \ + ) \ + ) + +class EEType; +class Module; +class BulkTypeEventLogger; + +namespace ETW +{ + // Class to wrap all type system logic for ETW + class TypeSystemLog + { + public: + // This enum is unused on Redhawk, but remains here to keep Redhawk / desktop CLR + // code shareable. + enum TypeLogBehavior + { + kTypeLogBehaviorTakeLockAndLogIfFirstTime, + kTypeLogBehaviorAssumeLockAndLogIfFirstTime, + kTypeLogBehaviorAlwaysLog, + }; + + static void LogTypeAndParametersIfNecessary(BulkTypeEventLogger * pLogger, UInt64 thAsAddr, TypeLogBehavior typeLogBehavior); + }; + + class LoaderLog + { + public: + static void SendModuleEvent(Module *pModule); + }; +}; + +struct EventRCWEntry +{ + UInt64 ObjectID; + UInt64 TypeID; + UInt64 IUnk; + UInt64 VTable; + UInt32 RefCount; + UInt32 Flags; +}; + +#pragma pack(push, 1) +struct EventCCWEntry +{ + UInt64 RootID; + UInt64 ObjectID; + UInt64 TypeID; + UInt64 IUnk; + UInt32 RefCount; + UInt32 JupiterRefCount; + UInt32 Flags; +}; + +C_ASSERT(sizeof(EventCCWEntry) == 44); +#pragma pack(pop) + +const UInt32 cbComMaxEtwEvent = 64 * 1024; + +// Does all logging for RCWs and CCWs in the process. +class BulkComLogger +{ +public: + // Returns true is gc heap collection is on. + static bool ShouldReportComForGCHeapEtw(); + + // Write one CCW to the CCW buffer. + static void WriteCCW(void* CCWGCHandle, void* objectID, void* typeRawValue, void* IUnknown, long comRefCount, long jupiterRefCount, long flags); + + // Write one RCW to the RCW buffer. + static void WriteRCW(void* objectID, void* typeRawValue, void* IUnknown, void* VTable, long refCount, long flags); + + // Gets or creates a unique BulkComLogger instance + static BulkComLogger* GetInstance(); + + // Write the remaining events and deletes the static instance. + static void FlushComETW(); + +private: + BulkComLogger(); + ~BulkComLogger(); + + // Forces a flush of all ETW events not yet fired. + void FireBulkComEvent(); + + // Writes one RCW to the RCW buffer. May or may not fire the event. + void WriteRcw(const EventRCWEntry& rcw); + + // Writes one CCW to the CCW buffer. May or may not fire the event. + void WriteCcw(const EventCCWEntry& ccw); + + // Forces a flush of all RCW ETW events not yet fired. + void FlushRcw(); + + // Forces a flush of all CCW ETW events not yet fired. + void FlushCcw(); + + // Distroys the unique instance and forces a flush for all ETW events not yet fired. + void Cleanup(); + +private: + // The maximum number of RCW/CCW events we can batch up based on the max size of an ETW event. + static const int kMaxRcwCount = (cbComMaxEtwEvent - 0x30) / sizeof(EventRCWEntry); + static const int kMaxCcwCount = (cbComMaxEtwEvent - 0x30) / sizeof(EventCCWEntry); + + int m_currRcw; // The current number of batched (but not emitted) RCW events. + int m_currCcw; // The current number of batched (but not emitted) CCW events. + + BulkTypeEventLogger *m_typeLogger; // Type logger to emit type data for. + + EventRCWEntry *m_etwRcwData; // RCW buffer. + EventCCWEntry *m_etwCcwData; // CCW buffer. + + static BulkComLogger* s_comLogger; +}; + +#else +#define FireEtwGCPerHeapHistorySpecial(DataPerHeap, DataSize, ClrId) +#endif + +#endif //__RHEVENTTRACE_INCLUDED diff --git a/src/Native/Runtime/shash.h b/src/Native/Runtime/shash.h new file mode 100644 index 00000000000..b36b2f37203 --- /dev/null +++ b/src/Native/Runtime/shash.h @@ -0,0 +1,633 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// SHash is a templated closed chaining hash table of pointers. It provides +// for multiple entries under the same key, and also for deleting elements. + +// Synchronization: +// Synchronization requirements depend on use. There are several properties to take into account: +// +// - Lookups may be asynchronous with each other +// - Lookups must be exclusive with Add operations +// (@todo: this can be remedied by delaying destruction of old tables during reallocation, e.g. during GC) +// - Remove operations may be asynchronous with Lookup/Add, unless elements are also deallocated. (In which +// case full synchronization is required) + + +// A SHash is templated by a class of TRAITS. These traits define the various specifics of the +// particular hash table. +// The required traits are: +// +// element_t Type of elements in the hash table. These elements are stored +// by value in the hash table. Elements must look more or less +// like primitives - they must support assignment relatively +// efficiently. There are 2 required sentinel values: +// Null and Deleted (described below). (Note that element_t is +// very commonly a pointer type.) +// +// The key must be derivable from the element; if your +// table's keys are independent of the stored values, element_t +// should be a key/value pair. +// +// key_t Type of the lookup key. The key is used for identity +// comparison between elements, and also as a key for lookup. +// This is also used by value and should support +// efficient assignment. +// +// count_t integral type for counts. Typically inherited by default +// Traits (count_t). +// +// static key_t GetKey(const element_t &e) Get key from element. Should be stable for a given e. +// static bool Equals(key_t k1, key_t k2) Compare 2 keys for equality. Again, should be stable. +// static count_t Hash(key_t k) Compute hash from a key. For efficient operation, the hashes +// for a set of elements should have random uniform distribution. +// +// static element_t Null() Return the Null sentinel value. May be inherited from +// default traits if it can be assigned from 0. +// static element_t Deleted() Return the Deleted sentinel value. May be inherited from the +// default traits if it can be assigned from -1. +// static bool IsNull(const ELEMENT &e) Compare element with Null sentinel value. May be inherited from +// default traits if it can be assigned from 0. +// static bool IsDeleted(const ELEMENT &e) Compare element with Deleted sentinel value. May be inherited +// from the default traits if it can be assigned from -1. +// static void OnFailure(FailureType failureType) Called when a failure occurs during SHash operation +// +// s_growth_factor_numerator +// s_growth_factor_denominator Factor to grow allocation (numerator/denominator). +// Typically inherited from default traits (3/2) +// +// s_density_factor_numerator +// s_density_factor_denominator Maximum occupied density of table before growth +// occurs (num/denom). Typically inherited (3/4). +// +// s_minimum_allocation Minimum table allocation count (size on first growth.) It is +// probably preferable to call Reallocate on initialization rather +// than override his from the default traits. (7) +// +// s_supports_remove Set to false for a slightly faster implementation that does not +// support deletes. There is a downside to the s_supports_remove flag, +// in that there may be more copies of the template instantiated through +// the system as different variants are used. + + +// disable the "Conditional expression is constant" warning +#pragma warning(disable:4127) + + +enum FailureType { ftAllocation, ftOverflow }; + +// DefaultHashTraits provides defaults for seldomly customized values in traits classes. + +template < typename ELEMENT, typename COUNT_T = UInt32 > +class DefaultSHashTraits +{ + public: + typedef COUNT_T count_t; + typedef ELEMENT element_t; + typedef DPTR(element_t) PTR_element_t; // by default SHash is DAC-aware. For RS + // only SHash use NonDacAwareSHashTraits + // (which typedefs element_t* PTR_element_t) + static const count_t s_growth_factor_numerator = 3; + static const count_t s_growth_factor_denominator = 2; + + static const count_t s_density_factor_numerator = 3; + static const count_t s_density_factor_denominator = 4; + + static const count_t s_minimum_allocation = 7; + + static const bool s_supports_remove = true; + + static const ELEMENT Null() { return (const ELEMENT) 0; } + static const ELEMENT Deleted() { return (const ELEMENT) -1; } + static bool IsNull(const ELEMENT &e) { return e == (const ELEMENT) 0; } + static bool IsDeleted(const ELEMENT &e) { return e == (const ELEMENT) -1; } + + static void OnFailure(FailureType ft) { } + + // No defaults - must specify: + // + // typedef key_t; + // static key_t GetKey(const element_t &i); + // static bool Equals(key_t k1, key_t k2); + // static count_t Hash(key_t k); +}; + +// Hash table class definition + +template +class SHash : public TRAITS +{ + private: + class Index; + friend class Index; + + class KeyIndex; + friend class KeyIndex; + + public: + // explicitly declare local typedefs for these traits types, otherwise + // the compiler may get confused + typedef typename TRAITS::element_t element_t; + typedef typename TRAITS::PTR_element_t PTR_element_t; + typedef typename TRAITS::key_t key_t; + typedef typename TRAITS::count_t count_t; + + class Iterator; + class KeyIterator; + + // Constructor/destructor. SHash tables always start out empty, with no + // allocation overhead. Call Reallocate to prime with an initial size if + // desired. + + SHash(); + ~SHash(); + + // Lookup an element in the table by key. Returns NULL if no element in the table + // has the given key. Note that multiple entries for the same key may be stored - + // this will return the first element added. Use KeyIterator to find all elements + // with a given key. + + element_t Lookup(key_t key) const; + + // Pointer-based flavor of Lookup (allows efficient access to tables of structures) + + const element_t* LookupPtr(key_t key) const; + + // Add an element to the hash table. This will never replace an element; multiple + // elements may be stored with the same key. + // + // Returns 'true' on success, 'false' on failure. + + bool Add(const element_t &element); + + // Add a new element to the hash table, if no element with the same key is already + // there. Otherwise, it will replace the existing element. This has the effect of + // updating an element rather than adding a duplicate. + // + // Returns 'true' on success, 'false' on failure. + + bool AddOrReplace(const element_t & element); + + // Remove the first element matching the key from the hash table. + + void Remove(key_t key); + + // Remove the specific element. + + void Remove(Iterator& i); + void Remove(KeyIterator& i); + + // Pointer-based flavor of Remove (allows efficient access to tables of structures) + + void RemovePtr(element_t * element); + + // Remove all elements in the hashtable + + void RemoveAll(); + + // Begin and End pointers for iteration over entire table. + + Iterator Begin() const; + Iterator End() const; + + // Begin and End pointers for iteration over all elements with a given key. + + KeyIterator Begin(key_t key) const; + KeyIterator End(key_t key) const; + + // Return the number of elements currently stored in the table + + count_t GetCount() const; + + // Return the number of elements that the table is capable storing currently + + count_t GetCapacity() const; + + // Reallocates a hash table to a specific size. The size must be big enough + // to hold all elements in the table appropriately. + // + // Note that the actual table size must always be a prime number; the number + // passed in will be upward adjusted if necessary. + // + // Returns 'true' on success, 'false' on failure. + + bool Reallocate(count_t newTableSize); + + // See if it is OK to grow the hash table by one element. If not, reallocate + // the hash table. + // + // Returns 'true' on success, 'false' on failure. + + bool CheckGrowth(); + + // See if it is OK to grow the hash table by N elementsone element. If not, reallocate + // the hash table. + + bool CheckGrowth(count_t newElements); + +private: + + // Resizes a hash table for growth. The new size is computed based + // on the current population, growth factor, and maximum density factor. + // + // Returns 'true' on success, 'false' on failure. + + bool Grow(); + + // Utility function to add a new element to the hash table. Note that + // it is perfectly find for the element to be a duplicate - if so it + // is added an additional time. Returns true if a new empty spot was used; + // false if an existing deleted slot. + + static bool Add(element_t *table, count_t tableSize, const element_t &element); + + // Utility function to add a new element to the hash table, if no element with the same key + // is already there. Otherwise, it will replace the existing element. This has the effect of + // updating an element rather than adding a duplicate. + + void AddOrReplace(element_t *table, count_t tableSize, const element_t &element); + + // Utility function to find the first element with the given key in + // the hash table. + + static const element_t* Lookup(PTR_element_t table, count_t tableSize, key_t key); + + // Utility function to remove the first element with the given key + // in the hash table. + + void Remove(element_t *table, count_t tableSize, key_t key); + + // Utility function to remove the specific element. + + void RemoveElement(element_t *table, count_t tableSize, element_t *element); + + // + // Enumerator, provides a template to produce an iterator on an existing class + // with a single iteration variable. + // + + template + class Enumerator + { + private: + const SUBTYPE *This() const + { + return (const SUBTYPE *) this; + } + + SUBTYPE *This() + { + return (SUBTYPE *)this; + } + + public: + + Enumerator() + { + } + + const element_t &operator*() const + { + return This()->Get(); + } + const element_t *operator->() const + { + return &(This()->Get()); + } + SUBTYPE &operator++() + { + This()->Next(); + return *This(); + } + SUBTYPE operator++(int) + { + SUBTYPE i = *This(); + This()->Next(); + return i; + } + bool operator==(const SUBTYPE &i) const + { + return This()->Equal(i); + } + bool operator!=(const SUBTYPE &i) const + { + return !This()->Equal(i); + } + }; + + // + // Index for whole table iterator. This is also the base for the keyed iterator. + // + + class Index + { + friend class SHash; + friend class Iterator; + friend class Enumerator; + + // The methods implementation has to be here for portability + // Some compilers won't compile the separate implementation in shash.inl + protected: + + PTR_element_t m_table; + count_t m_tableSize; + count_t m_index; + + Index(const SHash *hash, bool begin) + : m_table(hash->m_table), + m_tableSize(hash->m_tableSize), + m_index(begin ? 0 : m_tableSize) + { + } + + const element_t &Get() const + { + return m_table[m_index]; + } + + void First() + { + if (m_index < m_tableSize) + if (IsNull(m_table[m_index]) || IsDeleted(m_table[m_index])) + Next(); + } + + void Next() + { + if (m_index >= m_tableSize) + return; + + for (;;) + { + m_index++; + if (m_index >= m_tableSize) + break; + if (!IsNull(m_table[m_index]) && !IsDeleted(m_table[m_index])) + break; + } + } + + bool Equal(const Index &i) const + { + return i.m_index == m_index; + } + }; + + class Iterator : public Index, public Enumerator + { + friend class SHash; + + public: + Iterator(const SHash *hash, bool begin) + : Index(hash, begin) + { + } + }; + + // + // Index for iterating elements with a given key. + // Note that the m_index field is artificially bumped to m_tableSize when the end + // of iteration is reached. This allows a canonical End iterator to be used. + // + + class KeyIndex : public Index + { + friend class SHash; + friend class KeyIterator; + friend class Enumerator; + + // The methods implementation has to be here for portability + // Some compilers won't compile the separate implementation in shash.inl + protected: + key_t m_key; + count_t m_increment; + + KeyIndex(const SHash *hash, bool begin) + : Index(hash, begin), + m_increment(0) + { + } + + void SetKey(key_t key) + { + if (m_tableSize > 0) + { + m_key = key; + count_t hash = Hash(key); + + m_index = hash % m_tableSize; + m_increment = (hash % (m_tableSize-1)) + 1; + + // Find first valid element + if (IsNull(m_table[m_index])) + m_index = m_tableSize; + else if (IsDeleted(m_table[m_index]) + || !Equals(m_key, GetKey(m_table[m_index]))) + Next(); + } + } + + void Next() + { + while (true) + { + m_index += m_increment; + if (m_index >= m_tableSize) + m_index -= m_tableSize; + + if (IsNull(m_table[m_index])) + { + m_index = m_tableSize; + break; + } + + if (!IsDeleted(m_table[m_index]) + && Equals(m_key, GetKey(m_table[m_index]))) + { + break; + } + } + } + }; + + class KeyIterator : public KeyIndex, public Enumerator + { + friend class SHash; + + public: + + operator Iterator &() + { + return *(Iterator*)this; + } + + operator const Iterator &() + { + return *(const Iterator*)this; + } + + KeyIterator(const SHash *hash, bool begin) + : KeyIndex(hash, begin) + { + } + }; + + // Test for prime number. + static bool IsPrime(count_t number); + + // Find the next prime number >= the given value. + + static count_t NextPrime(count_t number); + + // Instance members + + PTR_element_t m_table; // pointer to table + count_t m_tableSize; // allocated size of table + count_t m_tableCount; // number of elements in table + count_t m_tableOccupied; // number, includes deleted slots + count_t m_tableMax; // maximum occupied count before reallocating +}; + +// disables support for DAC marshaling. Useful for defining right-side only SHashes + +template +class NonDacAwareSHashTraits : public PARENT +{ +public: + typedef typename PARENT::element_t element_t; + typedef element_t * PTR_element_t; +}; + +// disables support for removing elements - produces slightly faster implementation + +template +class NoRemoveSHashTraits : public PARENT +{ +public: + // explicitly declare local typedefs for these traits types, otherwise + // the compiler may get confused + typedef typename PARENT::element_t element_t; + typedef typename PARENT::count_t count_t; + + static const bool s_supports_remove = false; + static const element_t Deleted() { UNREACHABLE(); } + static bool IsDeleted(const element_t &e) { UNREFERENCED_PARAMETER(e); return false; } +}; + +// PtrHashTraits is a template to provides useful defaults for pointer hash tables +// It relies on methods GetKey and Hash defined on ELEMENT + +template +class PtrSHashTraits : public DefaultSHashTraits +{ + public: + + // explicitly declare local typedefs for these traits types, otherwise + // the compiler may get confused + typedef DefaultSHashTraits PARENT; + typedef typename PARENT::element_t element_t; + typedef typename PARENT::count_t count_t; + + typedef KEY key_t; + + static key_t GetKey(const element_t &e) + { + return e->GetKey(); + } + static bool Equals(key_t k1, key_t k2) + { + return k1 == k2; + } + static count_t Hash(key_t k) + { + return ELEMENT::Hash(k); + } +}; + +template +class PtrSHash : public SHash< PtrSHashTraits > +{ +}; + +template +class KeyValuePair { + KEY key; + VALUE value; + +public: + KeyValuePair() + { + } + + KeyValuePair(const KEY& k, const VALUE& v) + : key(k), value(v) + { + } + + KEY const & Key() const + { + return key; + } + + VALUE const & Value() const + { + return value; + } +}; + +template +class MapSHashTraits : public DefaultSHashTraits< KeyValuePair > +{ +public: + // explicitly declare local typedefs for these traits types, otherwise + // the compiler may get confused + typedef typename DefaultSHashTraits< KeyValuePair >::element_t element_t; + typedef typename DefaultSHashTraits< KeyValuePair >::count_t count_t; + + typedef KEY key_t; + + static key_t GetKey(element_t e) + { + return e.Key(); + } + static bool Equals(key_t k1, key_t k2) + { + return k1 == k2; + } + static count_t Hash(key_t k) + { + return (count_t)(size_t)k; + } + + static const element_t Null() { return element_t((KEY)0,(VALUE)0); } + static bool IsNull(const element_t &e) { return e.Key() == (KEY)0; } +}; + +template +class MapSHash : public SHash< NoRemoveSHashTraits< MapSHashTraits > > +{ + typedef SHash< NoRemoveSHashTraits< MapSHashTraits > > PARENT; + +public: + void Add(KEY key, VALUE value) + { + PARENT::Add(KeyValuePair(key, value)); + } + + bool Lookup(KEY key, VALUE* pValue) + { + const KeyValuePair *pRet = PARENT::LookupPtr(key); + if (pRet == NULL) + return false; + + *pValue = pRet->Value(); + return true; + } +}; + + +// restore "Conditional expression is constant" warning to default value +#pragma warning(default:4127) + diff --git a/src/Native/Runtime/shash.inl b/src/Native/Runtime/shash.inl new file mode 100644 index 00000000000..1b31d9b66d4 --- /dev/null +++ b/src/Native/Runtime/shash.inl @@ -0,0 +1,472 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +// disable the "Conditional expression is constant" warning +#pragma warning(disable:4127) + + +template +SHash::SHash() + : m_table(TADDR(NULL)), + m_tableSize(0), + m_tableCount(0), + m_tableOccupied(0), + m_tableMax(0) +{ + C_ASSERT(s_growth_factor_numerator > s_growth_factor_denominator); + C_ASSERT(s_density_factor_numerator < s_density_factor_denominator); +} + +template +SHash::~SHash() +{ + delete [] m_table; +} + +template +typename SHash::count_t SHash::GetCount() const +{ + return m_tableCount; +} + +template +typename SHash::count_t SHash::GetCapacity() const +{ + return m_tableMax; +} + +template +typename SHash< TRAITS>::element_t SHash::Lookup(key_t key) const +{ + const element_t *pRet = Lookup(m_table, m_tableSize, key); + return ((pRet != NULL) ? (*pRet) : Null()); +} + +template +const typename SHash< TRAITS>::element_t* SHash::LookupPtr(key_t key) const +{ + return Lookup(m_table, m_tableSize, key); +} + +template +bool SHash::Add(const element_t &element) +{ + if (!CheckGrowth()) + return false; + + if (Add(m_table, m_tableSize, element)) + m_tableOccupied++; + m_tableCount++; + + return true; +} + +template +bool SHash::AddOrReplace(const element_t &element) +{ + if (!CheckGrowth()) + return false; + + AddOrReplace(m_table, m_tableSize, element); + return true; +} + +template +void SHash::Remove(key_t key) +{ + Remove(m_table, m_tableSize, key); +} + +template +void SHash::Remove(Iterator& i) +{ + RemoveElement(m_table, m_tableSize, (element_t*)&(*i)); +} + +template +void SHash::Remove(KeyIterator& i) +{ + RemoveElement(m_table, m_tableSize, (element_t*)&(*i)); +} + +template +void SHash::RemovePtr(element_t * p) +{ + RemoveElement(m_table, m_tableSize, p); +} + +template +void SHash::RemoveAll() +{ + delete [] m_table; + + m_table = NULL; + m_tableSize = 0; + m_tableCount = 0; + m_tableOccupied = 0; + m_tableMax = 0; +} + +template +typename SHash::Iterator SHash::Begin() const +{ + Iterator i(this, true); + i.First(); + return i; +} + +template +typename SHash::Iterator SHash::End() const +{ + return Iterator(this, false); +} + +template +typename SHash::KeyIterator SHash::Begin(key_t key) const +{ + KeyIterator k(this, true); + k.SetKey(key); + return k; +} + +template +typename SHash::KeyIterator SHash::End(key_t key) const +{ + return KeyIterator(this, false); +} + +template +bool SHash::CheckGrowth() +{ + if (m_tableOccupied == m_tableMax) + { + return Grow(); + } + + return true; +} + +template +bool SHash::Grow() +{ + count_t newSize = (count_t) (m_tableCount + * s_growth_factor_numerator / s_growth_factor_denominator + * s_density_factor_denominator / s_density_factor_numerator); + if (newSize < s_minimum_allocation) + newSize = s_minimum_allocation; + + // handle potential overflow + if (newSize < m_tableCount) + { + OnFailure(ftOverflow); + return false; + } + + return Reallocate(newSize); +} + +template +bool SHash::CheckGrowth(count_t newElements) +{ + count_t newCount = (m_tableCount + newElements); + + // handle potential overflow + if (newCount < newElements) + { + OnFailure(ftOverflow); + return false; + } + + // enough space in the table? + if (newCount < m_tableMax) + return true; + + count_t newSize = (count_t) (newCount * s_density_factor_denominator / s_density_factor_numerator) + 1; + + // handle potential overflow + if (newSize < newCount) + { + OnFailure(ftOverflow); + return false; + } + + // accelerate the growth to avoid unnecessary rehashing + count_t newSize2 = (m_tableCount * s_growth_factor_numerator / s_growth_factor_denominator + * s_density_factor_denominator / s_density_factor_numerator); + + if (newSize < newSize2) + newSize = newSize2; + + if (newSize < s_minimum_allocation) + newSize = s_minimum_allocation; + + return Reallocate(newSize); +} + +template +bool SHash::Reallocate(count_t newTableSize) +{ + ASSERT(newTableSize >= + (count_t) (GetCount() * s_density_factor_denominator / s_density_factor_numerator)); + + // Allocation size must be a prime number. This is necessary so that hashes uniformly + // distribute to all indices, and so that chaining will visit all indices in the hash table. + newTableSize = NextPrime(newTableSize); + if (newTableSize == 0) + { + OnFailure(ftOverflow); + return false; + } + + element_t *newTable = new element_t [newTableSize]; + if (newTable == NULL) + { + OnFailure(ftAllocation); + return false; + } + + element_t *p = newTable, *pEnd = newTable + newTableSize; + while (p < pEnd) + { + *p = Null(); + p++; + } + + // Move all entries over to new table. + + for (Iterator i = Begin(), end = End(); i != end; i++) + { + const element_t & cur = (*i); + if (!IsNull(cur) && !IsDeleted(cur)) + Add(newTable, newTableSize, cur); + } + + // @todo: + // We might want to try to delay this cleanup to allow asynchronous readers + + delete [] m_table; + + m_table = PTR_element_t(newTable); + m_tableSize = newTableSize; + m_tableMax = (count_t) (newTableSize * s_density_factor_numerator / s_density_factor_denominator); + m_tableOccupied = m_tableCount; + + return true; +} + +template +const typename SHash::element_t * SHash::Lookup(PTR_element_t table, count_t tableSize, key_t key) +{ + if (tableSize == 0) + return NULL; + + count_t hash = Hash(key); + count_t index = hash % tableSize; + count_t increment = 0; // delay computation + + while (true) + { + element_t& current = table[index]; + + if (IsNull(current)) + return NULL; + + if (!IsDeleted(current) + && Equals(key, GetKey(current))) + { + return ¤t; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + index += increment; + if (index >= tableSize) + index -= tableSize; + } +} + +template +bool SHash::Add(element_t *table, count_t tableSize, const element_t &element) +{ + key_t key = GetKey(element); + + count_t hash = Hash(key); + count_t index = hash % tableSize; + count_t increment = 0; // delay computation + + while (true) + { + element_t& current = table[index]; + + if (IsNull(current)) + { + table[index] = element; + return true; + } + + if (IsDeleted(current)) + { + table[index] = element; + return false; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + index += increment; + if (index >= tableSize) + index -= tableSize; + } +} + +template +void SHash::AddOrReplace(element_t *table, count_t tableSize, const element_t &element) +{ + ASSERT(!s_supports_remove); + + key_t key = GetKey(element); + + count_t hash = Hash(key); + count_t index = hash % tableSize; + count_t increment = 0; // delay computation + + while (true) + { + element_t& current = table[index]; + ASSERT(!IsDeleted(current)); + + if (IsNull(current)) + { + table[index] = element; + m_tableCount++; + m_tableOccupied++; + return; + } + else if (Equals(key, GetKey(current))) + { + table[index] = element; + return; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + index += increment; + if (index >= tableSize) + index -= tableSize; + } +} + +#ifdef _MSC_VER +#pragma warning (disable: 4702) // Workaround bogus unreachable code warning +#endif +template +void SHash::Remove(element_t *table, count_t tableSize, key_t key) +{ + ASSERT(s_supports_remove); + ASSERT(Lookup(table, tableSize, key) != NULL); + + count_t hash = Hash(key); + count_t index = hash % tableSize; + count_t increment = 0; // delay computation + + while (true) + { + element_t& current = table[index]; + + if (IsNull(current)) + return; + + if (!IsDeleted(current) + && Equals(key, GetKey(current))) + { + table[index] = Deleted(); + m_tableCount--; + return; + } + + if (increment == 0) + increment = (hash % (tableSize-1)) + 1; + + index += increment; + if (index >= tableSize) + index -= tableSize; + } +} +#ifdef _MSC_VER +#pragma warning (default: 4702) +#endif + +template +void SHash::RemoveElement(element_t *table, count_t tableSize, element_t *element) +{ + ASSERT(s_supports_remove); + ASSERT(table <= element && element < table + tableSize); + ASSERT(!IsNull(*element) && !IsDeleted(*element)); + + *element = Deleted(); + m_tableCount--; +} + +template +bool SHash::IsPrime(count_t number) +{ + // This is a very low-tech check for primality, which doesn't scale very well. + // There are more efficient tests if this proves to be burdensome for larger + // tables. + + if ((number&1) == 0) + return false; + + count_t factor = 3; + while (factor * factor <= number) + { + if ((number % factor) == 0) + return false; + factor += 2; + } + + return true; +} + +namespace +{ + extern __declspec(selectany) const UInt32 g_shash_primes[] = { + 11,17,23,29,37,47,59,71,89,107,131,163,197,239,293,353,431,521,631,761,919, + 1103,1327,1597,1931,2333,2801,3371,4049,4861,5839,7013,8419,10103,12143,14591, + 17519,21023,25229,30293,36353,43627,52361,62851,75431,90523, 108631, 130363, + 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, + 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, + 4999559, 5999471, 7199369 }; +} + + +// Returns a prime larger than 'number' or 0, in case of overflow +template +typename SHash::count_t SHash::NextPrime(typename SHash::count_t number) +{ + for (int i = 0; i < (int) (sizeof(g_shash_primes) / sizeof(g_shash_primes[0])); i++) + { + if (g_shash_primes[i] >= number) + return (typename SHash::count_t)(g_shash_primes[i]); + } + + if ((number&1) == 0) + number++; + + while (number != 1) + { + if (IsPrime(number)) + return number; + number += 2; + } + + return 0; +} + +// restore "Conditional expression is constant" warning to default value +#pragma warning(default:4127) + diff --git a/src/Native/Runtime/slist.h b/src/Native/Runtime/slist.h new file mode 100644 index 00000000000..f975cddd89f --- /dev/null +++ b/src/Native/Runtime/slist.h @@ -0,0 +1,126 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "forward_declarations.h" + +MSVC_SAVE_WARNING_STATE() +MSVC_DISABLE_WARNING(4127) // conditional expression is constant -- it's intentionally constant + +struct DoNothingFailFastPolicy +{ + static inline void FailFast(); +}; + +template +struct DefaultSListTraits : public FailFastPolicy +{ + typedef DPTR(T) PTR_T; + typedef DPTR(PTR_T) PTR_PTR_T; + + static inline PTR_PTR_T GetNextPtr(PTR_T pT); + static inline bool Equals(PTR_T pA, PTR_T pB); +}; + +//------------------------------------------------------------------------------------------------------------ +// class SList, to use a singly linked list. +// +// To use, either expose a field DPTR(T) m_pNext by adding DefaultSListTraits as a friend class, or +// define a new Traits class derived from DefaultSListTraits and override the GetNextPtr function. +// +// SList supports lockless head insert and Remove methods. However, PushHeadInterlocked and +// PopHeadInterlocked must be used very carefully, as the rest of the mutating methods are not +// interlocked. In general, code must be careful to ensure that it will never use more than one +// synchronization mechanism at any given time to control access to a resource, and this is no +// exception. In particular, if synchronized access to other SList operations (such as FindAndRemove) +// are required, than a separate synchronization mechanism (such as a critical section) must be used. +//------------------------------------------------------------------------------------------------------------ +template > +class SList : public Traits +{ +protected: + typedef typename Traits::PTR_T PTR_T; + typedef typename Traits::PTR_PTR_T PTR_PTR_T; + +public: + SList(); + + // Returns true if there are no entries in the list. + bool IsEmpty(); + + // Returns the value of (but does not remove) the first element in the list. + PTR_T GetHead(); + + // Inserts pItem at the front of the list. See class header for more information. + void PushHead(PTR_T pItem); + void PushHeadInterlocked(PTR_T pItem); + + // Removes and returns the first entry in the list. See class header for more information. + PTR_T PopHead(); +#ifdef FEATURE_VSD + // This API is currently used only by VSD and it has a race condition. Possibly not worth fixing since + // it's hard and we may be getting rid of VSD entirely. + PTR_T PopHeadInterlocked(); +#endif + + class Iterator + { + friend SList; + + public: + Iterator(Iterator const &it); + Iterator& operator=(Iterator const &it); + + PTR_T operator->(); + PTR_T operator*(); + + Iterator & operator++(); + Iterator operator++(int); + + bool operator==(Iterator const &rhs); + bool operator==(PTR_T pT); + bool operator!=(Iterator const &rhs); + + private: + Iterator(PTR_PTR_T ppItem); + + Iterator Insert(PTR_T pItem); + Iterator Remove(); + + static Iterator End(); + PTR_PTR_T m_ppCur; +#ifdef _DEBUG + mutable bool m_fIsValid; +#endif + + PTR_T _Value() const; + + enum e_ValidateOperation + { + e_CanCompare, // Will assert in debug if m_fIsValid == false. + e_CanInsert, // i.e., not the fake End() value of m_ppCur == NULL + e_HasValue, // i.e., m_ppCur != NULL && *m_ppCur != NULL + }; + void _Validate(e_ValidateOperation op) const; + }; + + Iterator Begin(); + Iterator End(); + + // Returns iterator to first list item matching pItem + Iterator FindFirst(PTR_T pItem); + bool RemoveFirst(PTR_T pItem); + + // Inserts pItem *before* it. Returns iterator pointing to inserted item. + Iterator Insert(Iterator & it, PTR_T pItem); + + // Removes item pointed to by it from the list. Returns iterator pointing + // to following item. + Iterator Remove(Iterator & it); + +private: + PTR_T m_pHead; +}; + +MSVC_RESTORE_WARNING_STATE() + diff --git a/src/Native/Runtime/slist.inl b/src/Native/Runtime/slist.inl new file mode 100644 index 00000000000..9255af95501 --- /dev/null +++ b/src/Native/Runtime/slist.inl @@ -0,0 +1,408 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +MSVC_SAVE_WARNING_STATE() +MSVC_DISABLE_WARNING(4127) // conditional expression is constant -- + // while (true) loops and compile time template constants cause this. + + +//------------------------------------------------------------------------------------------------- +namespace rh { namespace std +{ + // Specialize rh::std::find for SList iterators so that it will use _Traits::Equals. + template + inline + typename SList<_Tx, _Traits>::Iterator find( + typename SList<_Tx, _Traits>::Iterator _First, + typename SList<_Tx, _Traits>::Iterator _Last, + const _Ty& _Val) + { // find first matching _Val + for (; _First != _Last; ++_First) + if (_Traits::Equals(*_First, _Val)) + break; + return (_First); + } +} // namespace std +} // namespace rh + +//------------------------------------------------------------------------------------------------- +inline +void DoNothingFailFastPolicy::FailFast() +{ + // Intentionally a no-op. +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename DefaultSListTraits::PTR_PTR_T DefaultSListTraits::GetNextPtr( + PTR_T pT) +{ + ASSERT(pT != NULL); + return dac_cast(dac_cast(pT) + offsetof(T, m_pNext)); +} + +//------------------------------------------------------------------------------------------------- +template +inline +bool DefaultSListTraits::Equals( + PTR_T pA, + PTR_T pB) +{ // Default is pointer comparison + return pA == pB; +} + +//------------------------------------------------------------------------------------------------- +template +inline +SList::SList() + : m_pHead(NULL) +{ +} + +//------------------------------------------------------------------------------------------------- +template +inline +bool SList::IsEmpty() +{ + return Begin() == End(); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::PTR_T SList::GetHead() +{ + return m_pHead; +} + +//------------------------------------------------------------------------------------------------- +template +inline +void SList::PushHead( + PTR_T pItem) +{ + NO_DAC(); + Begin().Insert(pItem); +} + +//------------------------------------------------------------------------------------------------- +template +inline +void SList::PushHeadInterlocked( + PTR_T pItem) +{ + NO_DAC(); + ASSERT(pItem != NULL); + ASSERT(IS_ALIGNED(&m_pHead, sizeof(void*))); + + while (true) + { + *GetNextPtr(pItem) = *reinterpret_cast(&m_pHead); + if (PalInterlockedCompareExchangePointer( + reinterpret_cast(&m_pHead), + reinterpret_cast(pItem), + reinterpret_cast(*GetNextPtr(pItem))) == reinterpret_cast(*GetNextPtr(pItem))) + { + break; + } + } +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::PTR_T SList::PopHead() +{ + NO_DAC(); + PTR_T pRet = *Begin(); + Begin().Remove(); + return pRet; +} + +#ifdef FEATURE_VSD +//------------------------------------------------------------------------------------------------- +// This API is currently used only by VSD and it has a race condition. Possibly not worth fixing since it's +// hard and we may be getting rid of VSD entirely. +template +inline +typename SList::PTR_T SList::PopHeadInterlocked() +{ + NO_DAC(); + ASSERT(IS_ALIGNED(&m_pHead, sizeof(void*))); + + T* pRetItem = NULL; + while (true) + { + // The read of m_pHead->m_pNext must be volatile to ensure the compiler reads + // the value only once. + pRetItem = *reinterpret_cast(&m_pHead); + + // It is impossible for + // pRetItem->m_pNext to be modified until the link is successfully removed from + // the list. If another thread beats us to the remove, then the interlocked operation + // will fail (since the value of m_pHead->m_pNext will have been updated by that thread's + // interlocked compare exchange), and we'll update pRetItem with the new value and + // try again, failing if there are no elements in the list. + + // The above logic has a flaw: we don't guard against the head element being popped, another element + // pushed and then the original element being re-pushed in between our initial read of the head + // element's next pointer and the interlocked compare exchange operation. Such a sequence would drop + // an element on the floor. The OS SLIST implementation uses a much more complex list head to avoid + // this problem and imposes double-pointer alignment requirements on both the list head and entries as + // a result. + + if (pRetItem == NULL || + PalInterlockedCompareExchangePointer( + reinterpret_cast(&m_pHead), + reinterpret_cast(*GetNextPtr(pRetItem)), + reinterpret_cast(pRetItem)) == reinterpret_cast(pRetItem)) + { + break; + } + } + return pRetItem; +} +#endif // FEATURE_VSD + +//------------------------------------------------------------------------------------------------- +template +inline +SList::Iterator::Iterator( + Iterator const &it) + : m_ppCur(it.m_ppCur) +#ifdef _DEBUG + , m_fIsValid(it.m_fIsValid) +#endif +{ +} + +//------------------------------------------------------------------------------------------------- +template +inline +SList::Iterator::Iterator( + PTR_PTR_T ppItem) + : m_ppCur(ppItem) +#ifdef _DEBUG + , m_fIsValid(true) +#endif +{ +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator& SList::Iterator::operator=( + Iterator const &it) +{ + m_ppCur = it.m_ppCur; +#ifdef _DEBUG + m_fIsValid = it.m_fIsValid; +#endif + return *this; +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::PTR_T SList::Iterator::operator->() +{ + _Validate(e_HasValue); + return _Value(); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::PTR_T SList::Iterator::operator*() +{ + _Validate(e_HasValue); + return _Value(); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator & SList::Iterator::operator++() +{ + _Validate(e_HasValue); // Having a value means we're not at the end. + m_ppCur = Traits::GetNextPtr(_Value()); + return *this; +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::Iterator::operator++( + int) +{ + _Validate(e_HasValue); // Having a value means we're not at the end. + PTR_PTR_T ppRet = m_ppCur; + ++(*this); + return Iterator(ppRet); +} + +//------------------------------------------------------------------------------------------------- +template +inline +bool SList::Iterator::operator==( + Iterator const &rhs) +{ + _Validate(e_CanCompare); + rhs._Validate(e_CanCompare); + return Traits::Equals(_Value(), rhs._Value()); +} + +//------------------------------------------------------------------------------------------------- +template +inline +bool SList::Iterator::operator==( + PTR_T pT) +{ + _Validate(e_CanCompare); + return Traits::Equals(_Value(), pT); +} + +//------------------------------------------------------------------------------------------------- +template +inline +bool SList::Iterator::operator!=( + Iterator const &rhs) +{ + return !operator==(rhs); +} + +//------------------------------------------------------------------------------------------------- +template +inline /*static*/ +typename SList::Iterator SList::Iterator::End() +{ + return Iterator(NULL); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::Iterator::Insert( + PTR_T pItem) +{ + NO_DAC(); + _Validate(e_CanInsert); + *Traits::GetNextPtr(pItem) = *m_ppCur; + *m_ppCur = pItem; + Iterator itRet(m_ppCur); + ++(*this); + return itRet; +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::Iterator::Remove() +{ + NO_DAC(); + _Validate(e_HasValue); + *m_ppCur = *Traits::GetNextPtr(*m_ppCur); + PTR_PTR_T ppRet = m_ppCur; + // Set it to End, so that subsequent misuse of this iterator will + // result in an AV rather than possible memory corruption. + *this = End(); + return Iterator(ppRet); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::PTR_T SList::Iterator::_Value() const +{ + ASSERT(m_fIsValid); + return dac_cast(m_ppCur == NULL ? NULL : *m_ppCur); +} + +//------------------------------------------------------------------------------------------------- +template +inline +void SList::Iterator::_Validate(e_ValidateOperation op) const +{ + ASSERT(m_fIsValid); + ASSERT(op == e_CanCompare || op == e_CanInsert || op == e_HasValue); + + if ((op != e_CanCompare && m_ppCur == NULL) || + (op == e_HasValue && *m_ppCur == NULL)) + { + // NOTE: Default of DoNothingFailFastPolicy is a no-op, and so this function will be + // eliminated in retail builds. This is ok, as the subsequent operation will cause + // an AV, which will itself trigger a FailFast. Provide a different policy to get + // different behavior. + ASSERT_MSG(false, "Invalid SList::Iterator use."); + Traits::FailFast(); +#ifdef _DEBUG + m_fIsValid = false; +#endif + } +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::Begin() +{ + typedef SList T_THIS; + return Iterator(dac_cast( + dac_cast(this) + offsetof(T_THIS, m_pHead))); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::End() +{ + return Iterator::End(); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::FindFirst(PTR_T pItem) +{ + return rh::std::find(Begin(), End(), pItem); +} + +//------------------------------------------------------------------------------------------------- +template +inline +bool SList::RemoveFirst(PTR_T pItem) +{ + NO_DAC(); + Iterator it = FindFirst(pItem); + if (it != End()) + { + it.Remove(); + return true; + } + else + { + return false; + } +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::Insert(Iterator & it, PTR_T pItem) +{ + return it.Insert(pItem); +} + +//------------------------------------------------------------------------------------------------- +template +inline +typename SList::Iterator SList::Remove(Iterator & it) +{ + return it.Remove(); +} + + +MSVC_RESTORE_WARNING_STATE() + diff --git a/src/Native/Runtime/startup.cpp b/src/Native/Runtime/startup.cpp new file mode 100644 index 00000000000..a5a71f7a550 --- /dev/null +++ b/src/Native/Runtime/startup.cpp @@ -0,0 +1,357 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "event.h" +#include "rwlock.h" +#include "threadstore.h" +#include "runtimeinstance.h" +#include "instancestore.h" +#include "rhbinder.h" +#include "cachedinterfacedispatch.h" +#include "rhconfig.h" +#include "stresslog.h" +#include "restrictedcallouts.h" +#endif !DACCESS_COMPILE + +#ifndef DACCESS_COMPILE + +#ifdef PROFILE_STARTUP +unsigned __int64 g_startupTimelineEvents[NUM_STARTUP_TIMELINE_EVENTS] = { 0 }; +#endif // PROFILE_STARTUP + +HANDLE RtuCreateRuntimeInstance(HANDLE hPalInstance); + +#ifdef FEATURE_VSD +bool RtuInitializeVSD(); +#endif + +#ifndef FEATURE_DECLSPEC_THREAD +EXTERN_C UInt32 _tls_index; +#endif // FEATURE_DECLSPEC_THREAD + +UInt32 _fls_index = FLS_OUT_OF_INDEXES; + + +Int32 __stdcall RhpVectoredExceptionHandler(PEXCEPTION_POINTERS pExPtrs); +void __stdcall FiberDetach(void* lpFlsData); +void CheckForPalFallback(); + +extern RhConfig * g_pRhConfig; + +bool InitDLL(HANDLE hPalInstance) +{ + CheckForPalFallback(); + +#ifdef FEATURE_VSD + // + // init VSD + // + if (!RtuInitializeVSD()) + return false; +#endif + +#ifdef FEATURE_CACHED_INTERFACE_DISPATCH + // + // Initialize interface dispatch. + // + if (!InitializeInterfaceDispatch()) + return false; +#endif + + // + // Initialize support for registering GC and HandleTable callouts. + // + if (!RestrictedCallouts::Initialize()) + return false; + +#ifndef APP_LOCAL_RUNTIME + PalAddVectoredExceptionHandler(1, RhpVectoredExceptionHandler); +#endif + + // + // init per-instance state + // + HANDLE hRuntimeInstance = RtuCreateRuntimeInstance(hPalInstance); + if (NULL == hRuntimeInstance) + return false; + STARTUP_TIMELINE_EVENT(NONGC_INIT_COMPLETE); + +#ifdef FEATURE_DECLSPEC_THREAD +#if !defined(MODERN_OS) && !defined(APP_LOCAL_RUNTIME) + OSVERSIONINFOEXW osvi; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (!PalGetVersionExW(&osvi)) + return false; + + if (osvi.dwMajorVersion < 6) + { + ASSERT_MSG(osvi.dwMajorVersion >= 6, "NT version 6 or greater is required. (i.e. Vista or Server 2008)"); + return false; + } +#endif // !MODERN_OS && !APP_LOCAL_RUNTIME +#else + _tls_index = PalTlsAlloc(); + if (_tls_index > TLS_NUM_INLINE_SLOTS) + return false; +#endif // FEATURE_DECLSPEC_THREAD + + _fls_index = PalFlsAlloc(FiberDetach); + if (_fls_index == FLS_OUT_OF_INDEXES) + return false; + + // @TODO: currently we're always forcing a workstation GC. + // @TODO: GC per-instance vs per-DLL state separation + if (!RedhawkGCInterface::InitializeSubsystems(RedhawkGCInterface::GCType_Workstation)) + return false; + STARTUP_TIMELINE_EVENT(GC_INIT_COMPLETE); + +#ifdef STRESS_LOG + UInt32 dwTotalStressLogSize = g_pRhConfig->GetTotalStressLogSize(); + UInt32 dwStressLogLevel = g_pRhConfig->GetStressLogLevel(); + + unsigned facility = (unsigned)LF_ALL; +#ifdef _DEBUG + if (dwTotalStressLogSize == 0) + dwTotalStressLogSize = 1024 * STRESSLOG_CHUNK_SIZE; + if (dwStressLogLevel == 0) + dwStressLogLevel = LL_INFO1000; +#endif + unsigned dwPerThreadChunks = (dwTotalStressLogSize / 24) / STRESSLOG_CHUNK_SIZE; + if (dwTotalStressLogSize != 0) + { + StressLog::Initialize(facility, dwStressLogLevel, + dwPerThreadChunks * STRESSLOG_CHUNK_SIZE, + (unsigned)dwTotalStressLogSize, hPalInstance); + } +#endif // STRESS_LOG + + return true; +} + +void CheckForPalFallback() +{ +#ifdef _DEBUG + UInt32 disallowSetting = g_pRhConfig->GetDisallowRuntimeServicesFallback(); + if (disallowSetting == 0) + return; + + // The fallback provider doesn't implement write watch, so we check for the write watch capability as a + // proxy for whether or not we're using the fallback provider since we don't have direct access to this + // information from here. + + if (disallowSetting == 1) + { + // If RH_DisallowRuntimeServicesFallback is set to 1, we want to fail fast if we discover that we're + // running against the fallback provider. + if (!PalHasCapability(WriteWatchCapability)) + RhFailFast(); + } + else if (disallowSetting == 2) + { + // If RH_DisallowRuntimeServicesFallback is set to 2, we want to fail fast if we discover that we're + // NOT running against the fallback provider. + if (PalHasCapability(WriteWatchCapability)) + RhFailFast(); + } +#endif // _DEBUG +} + +#ifdef PROFILE_STARTUP +#define STD_OUTPUT_HANDLE ((UInt32)-11) + +struct RegisterModuleTrace +{ + LARGE_INTEGER Begin; + LARGE_INTEGER End; +}; + +const int NUM_REGISTER_MODULE_TRACES = 16; +int g_registerModuleCount = 0; + +RegisterModuleTrace g_registerModuleTraces[NUM_REGISTER_MODULE_TRACES] = { 0 }; + +void AppendInt64(char * pBuffer, UInt32* pLen, UInt64 value) +{ + char localBuffer[20]; + int cch = 0; + + do + { + localBuffer[cch++] = '0' + (value % 10); + value = value / 10; + } while (value); + + for (int i = 0; i < cch; i++) + { + pBuffer[(*pLen)++] = localBuffer[cch - i - 1]; + } + + pBuffer[(*pLen)++] = ','; + pBuffer[(*pLen)++] = ' '; +} +#endif // PROFILE_STARTUP + +bool UninitDLL(HANDLE hModDLL) +{ +#ifdef PROFILE_STARTUP + char buffer[1024]; + + UInt32 len = 0; + + AppendInt64(buffer, &len, g_startupTimelineEvents[PROCESS_ATTACH_BEGIN]); + AppendInt64(buffer, &len, g_startupTimelineEvents[NONGC_INIT_COMPLETE]); + AppendInt64(buffer, &len, g_startupTimelineEvents[GC_INIT_COMPLETE]); + AppendInt64(buffer, &len, g_startupTimelineEvents[PROCESS_ATTACH_COMPLETE]); + + for (int i = 0; i < g_registerModuleCount; i++) + { + AppendInt64(buffer, &len, g_registerModuleTraces[i].Begin.QuadPart); + AppendInt64(buffer, &len, g_registerModuleTraces[i].End.QuadPart); + } + + buffer[len++] = '\r'; + buffer[len++] = '\n'; + + UInt32 cchWritten; + PalWriteFile(PalGetStdHandle(STD_OUTPUT_HANDLE), buffer, len, &cchWritten, NULL); +#endif // PROFILE_STARTUP + return true; +} + +void DllThreadAttach(HANDLE hPalInstance) +{ + // We do not call ThreadStore::AttachThread from here because the loader lock is held. Instead, the + // threads themselves will do this on their first reverse pinvoke. +} + +void DllThreadDetach() +{ + // BEWARE: loader lock is held here! + + // Should have already received a call to FiberDetach for this thread's "home" fiber. + Thread* pCurrentThread = ThreadStore::GetCurrentThreadIfAvailable(); + if (pCurrentThread != NULL && !pCurrentThread->IsDetached()) + { + ASSERT_UNCONDITIONALLY("Detaching thread whose home fiber has not been detached"); + RhFailFast(); + } +} + +void __stdcall FiberDetach(void* lpFlsData) +{ + // Note: loader lock is *not* held here! + + ASSERT(lpFlsData == PalFlsGetValue(_fls_index)); + + ThreadStore::DetachCurrentThreadIfHomeFiber(); +} + +COOP_PINVOKE_HELPER(UInt32_BOOL, RhpRegisterModule, (ModuleHeader *pModuleHeader)) +{ +#ifdef PROFILE_STARTUP + if (g_registerModuleCount < NUM_REGISTER_MODULE_TRACES) + { + PalQueryPerformanceCounter(&g_registerModuleTraces[g_registerModuleCount].Begin); + } +#endif // PROFILE_STARTUP + + RuntimeInstance * pInstance = GetRuntimeInstance(); + + if (!pInstance->RegisterModule(pModuleHeader)) + return UInt32_FALSE; + +#ifdef PROFILE_STARTUP + if (g_registerModuleCount < NUM_REGISTER_MODULE_TRACES) + { + PalQueryPerformanceCounter(&g_registerModuleTraces[g_registerModuleCount].End); + g_registerModuleCount++; + } +#endif // PROFILE_STARTUP + + return UInt32_TRUE; +} + + +COOP_PINVOKE_HELPER(UInt32_BOOL, RhpRegisterSimpleModule, (SimpleModuleHeader *pModuleHeader)) +{ + RuntimeInstance * pInstance = GetRuntimeInstance(); + + if (!pInstance->RegisterSimpleModule(pModuleHeader)) + return UInt32_FALSE; + + return UInt32_TRUE; +} + +COOP_PINVOKE_HELPER(UInt32_BOOL, RhpEnableConservativeStackReporting, ()) +{ + RuntimeInstance * pInstance = GetRuntimeInstance(); + if (!pInstance->EnableConservativeStackReporting()) + return UInt32_FALSE; + + return UInt32_TRUE; +} + +#endif // !DACCESS_COMPILE + +GPTR_IMPL_INIT(RuntimeInstance, g_pTheRuntimeInstance, NULL); + +#ifndef DACCESS_COMPILE + +// +// Creates a new runtime instance. +// +// @TODO: EXPORT +HANDLE RtuCreateRuntimeInstance(HANDLE hPalInstance) +{ + CreateHolder pRuntimeInstance = RuntimeInstance::Create(hPalInstance); + if (NULL == pRuntimeInstance) + return NULL; + + ASSERT_MSG(g_pTheRuntimeInstance == NULL, "multi-instances are not supported"); + g_pTheRuntimeInstance = pRuntimeInstance; + + pRuntimeInstance.SuppressRelease(); + return (HANDLE) pRuntimeInstance; +} + +// +// Currently called only from a managed executable once Main returns, this routine does whatever is needed to +// cleanup managed state before exiting. There's not a lot here at the moment since we're always about to let +// the OS tear the process down anyway. +// +// @TODO: Eventually we'll probably have a hosting API and explicit shutdown request. When that happens we'll +// something more sophisticated here since we won't be able to rely on the OS cleaning up after us. +// +COOP_PINVOKE_HELPER(void, RhpShutdownHelper, (UInt32 uExitCode)) +{ + // If the classlib has requested it perform a last pass of the finalizer thread. + RedhawkGCInterface::ShutdownFinalization(); + +#ifdef FEATURE_PROFILING + GetRuntimeInstance()->WriteProfileInfo(); +#endif // FEATURE_PROFILING +} + +#endif // !DACCESS_COMPILE + diff --git a/src/Native/Runtime/static_check.h b/src/Native/Runtime/static_check.h new file mode 100644 index 00000000000..714a6a45541 --- /dev/null +++ b/src/Native/Runtime/static_check.h @@ -0,0 +1,21 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// --------------------------------------------------------------------------- +// static_check.h +// +// Static assertion checking infrastructure +// --------------------------------------------------------------------------- + +#ifndef __STATIC_CHECK_H__ +#define __STATIC_CHECK_H__ + +// +// Static assert. Now uses the built-in compiler "static_assert" function. +// + +#define STATIC_ASSERT_MSG( cond, msg ) static_assert( cond, #msg ) +#define STATIC_ASSERT( cond ) STATIC_ASSERT_MSG(cond, static_assert_failure) + +#endif // __STATIC_CHECK_H__ diff --git a/src/Native/Runtime/stressLog.cpp b/src/Native/Runtime/stressLog.cpp new file mode 100644 index 00000000000..7bb96a8a8e4 --- /dev/null +++ b/src/Native/Runtime/stressLog.cpp @@ -0,0 +1,597 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// --------------------------------------------------------------------------- +// StressLog.cpp +// +// StressLog infrastructure +// --------------------------------------------------------------------------- + +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#include "sospriv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "daccess.h" +#include "stresslog.h" +#include "holder.h" +#include "crst.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "rwlock.h" +#include "event.h" +#include "threadstore.h" + +template inline T VolatileLoad(T const * pt) { return *(T volatile const *)pt; } +template inline void VolatileStore(T* pt, T val) { *(T volatile *)pt = val; } +#endif + + +#ifdef STRESS_LOG + +typedef DPTR(StressLog) PTR_StressLog; +GPTR_IMPL(StressLog, g_pStressLog /*, &StressLog::theLog*/); + +#ifndef DACCESS_COMPILE + +HANDLE StressLogChunk::s_LogChunkHeap = NULL; + +/*********************************************************************************/ +#if defined(_X86_) + +/* This is like QueryPerformanceCounter but a lot faster. On machines with + variable-speed CPUs (for power management), this is not accurate, but may + be good enough. +*/ +inline __declspec(naked) unsigned __int64 getTimeStamp() { + + __asm { + RDTSC // read time stamp counter + ret + }; +} + +#else // _X86_ +unsigned __int64 getTimeStamp() { + + LARGE_INTEGER ret; + ZeroMemory(&ret, sizeof(LARGE_INTEGER)); + + PalQueryPerformanceCounter(&ret); + + return ret.QuadPart; +} + +#endif // _X86_ else + +/*********************************************************************************/ +/* Get the the frequency cooresponding to 'getTimeStamp'. For non-x86 + architectures, this is just the performance counter frequency. +*/ +unsigned __int64 getTickFrequency() +{ + LARGE_INTEGER ret; + ZeroMemory(&ret, sizeof(LARGE_INTEGER)); + PalQueryPerformanceFrequency(&ret); + return ret.QuadPart; +} + +#endif // DACCESS_COMPILE + +StressLog StressLog::theLog = { 0, 0, 0, 0, 0, 0 }; +const static unsigned __int64 RECYCLE_AGE = 0x40000000L; // after a billion cycles, we can discard old threads + +/*********************************************************************************/ + +#ifndef DACCESS_COMPILE + +void StressLog::Initialize(unsigned facilities, unsigned level, unsigned maxBytesPerThread, + unsigned maxBytesTotal, HANDLE hMod) +{ +#if !defined(USE_PORTABLE_HELPERS) // @TODO: disabled because of assumption that hMod is a module base address in stress log code + if (theLog.MaxSizePerThread != 0) + { + // guard ourself against multiple initialization. First init wins. + return; + } + + g_pStressLog = &theLog; + + theLog.pLock = new CrstStatic(); + theLog.pLock->Init(CrstStressLog); + if (maxBytesPerThread < STRESSLOG_CHUNK_SIZE) + { + maxBytesPerThread = STRESSLOG_CHUNK_SIZE; + } + theLog.MaxSizePerThread = maxBytesPerThread; + + if (maxBytesTotal < STRESSLOG_CHUNK_SIZE * 256) + { + maxBytesTotal = STRESSLOG_CHUNK_SIZE * 256; + } + theLog.MaxSizeTotal = maxBytesTotal; + theLog.totalChunk = 0; + theLog.facilitiesToLog = facilities | LF_ALWAYS; + theLog.levelToLog = level; + theLog.deadCount = 0; + + theLog.tickFrequency = getTickFrequency(); + + PalGetSystemTimeAsFileTime (&theLog.startTime); + theLog.startTimeStamp = getTimeStamp(); + + theLog.moduleOffset = (size_t)hMod; // HMODULES are base addresses. + +#ifndef APP_LOCAL_RUNTIME + StressLogChunk::s_LogChunkHeap = PalHeapCreate (0, STRESSLOG_CHUNK_SIZE * 128, 0); + if (StressLogChunk::s_LogChunkHeap == NULL) +#endif + { + StressLogChunk::s_LogChunkHeap = PalGetProcessHeap (); + } + _ASSERTE (StressLogChunk::s_LogChunkHeap); +#endif // !defined(USE_PORTABLE_HELPERS) +} + +/*********************************************************************************/ +/* create a new thread stress log buffer associated with pThread */ + +ThreadStressLog* StressLog::CreateThreadStressLog(Thread * pThread) { + + if (theLog.facilitiesToLog == 0) + return NULL; + + if (pThread == NULL) + pThread = ThreadStore::GetCurrentThread(); + + ThreadStressLog* msgs = reinterpret_cast(pThread->GetThreadStressLog()); + if (msgs != NULL) + { + return msgs; + } + + // if it looks like we won't be allowed to allocate a new chunk, exit early + if (VolatileLoad(&theLog.deadCount) == 0 && !AllowNewChunk (0)) + { + return NULL; + } + + CrstHolder holder(theLog.pLock); + + msgs = CreateThreadStressLogHelper(pThread); + + return msgs; +} + +ThreadStressLog* StressLog::CreateThreadStressLogHelper(Thread * pThread) { + + bool skipInsert = FALSE; + ThreadStressLog* msgs = NULL; + + // See if we can recycle a dead thread + if (VolatileLoad(&theLog.deadCount) > 0) + { + unsigned __int64 recycleStamp = getTimeStamp() - RECYCLE_AGE; + msgs = VolatileLoad(&theLog.logs); + //find out oldest dead ThreadStressLog in case we can't find one within + //recycle age but can't create a new chunk + ThreadStressLog * oldestDeadMsg = NULL; + + while(msgs != 0) + { + if (msgs->isDead) + { + bool hasTimeStamp = msgs->curPtr != (StressMsg *)msgs->chunkListTail->EndPtr(); + if (hasTimeStamp && msgs->curPtr->timeStamp < recycleStamp) + { + skipInsert = TRUE; + PalInterlockedDecrement(&theLog.deadCount); + break; + } + + if (!oldestDeadMsg) + { + oldestDeadMsg = msgs; + } + else if (hasTimeStamp && oldestDeadMsg->curPtr->timeStamp > msgs->curPtr->timeStamp) + { + oldestDeadMsg = msgs; + } + } + + msgs = msgs->next; + } + + //if the total stress log size limit is already passed and we can't add new chunk, + //always reuse the oldest dead msg + if (!AllowNewChunk (0) && !msgs) + { + msgs = oldestDeadMsg; + skipInsert = TRUE; + PalInterlockedDecrement(&theLog.deadCount); + } + } + + if (msgs == 0) { + msgs = new ThreadStressLog(); + + if (msgs == 0 ||!msgs->IsValid ()) + { + delete msgs; + msgs = 0; + goto LEAVE; + } + } + + msgs->Activate (pThread); + + if (!skipInsert) { +#ifdef _DEBUG + ThreadStressLog* walk = VolatileLoad(&theLog.logs); + while (walk) + { + _ASSERTE (walk != msgs); + walk = walk->next; + } +#endif + // Put it into the stress log + msgs->next = VolatileLoad(&theLog.logs); + VolatileStore(&theLog.logs, msgs); + } + +LEAVE: + ; + return msgs; +} + +/*********************************************************************************/ +/* static */ +void StressLog::ThreadDetach(ThreadStressLog *msgs) { + + if (msgs == 0) + { + return; + } + + // We should write this message to the StressLog for deleted fiber. + msgs->LogMsg (LF_STARTUP, 0, "******* DllMain THREAD_DETACH called Thread dying *******\n"); + + msgs->isDead = TRUE; + PalInterlockedIncrement(&theLog.deadCount); +} + +bool StressLog::AllowNewChunk (long numChunksInCurThread) +{ + _ASSERTE (numChunksInCurThread <= VolatileLoad(&theLog.totalChunk)); + UInt32 perThreadLimit = theLog.MaxSizePerThread; + + if (numChunksInCurThread == 0 /*&& IsSuspendEEThread()*/) + return TRUE; + + if (ThreadStore::GetCurrentThread()->IsGCSpecial()) + { + perThreadLimit *= GC_STRESSLOG_MULTIPLY; + } + + if ((UInt32)numChunksInCurThread * STRESSLOG_CHUNK_SIZE >= perThreadLimit) + { + return FALSE; + } + + return (UInt32)VolatileLoad(&theLog.totalChunk) * STRESSLOG_CHUNK_SIZE < theLog.MaxSizeTotal; +} + +bool StressLog::ReserveStressLogChunks (unsigned chunksToReserve) +{ + Thread *pThread = ThreadStore::GetCurrentThread(); + ThreadStressLog* msgs = reinterpret_cast(pThread->GetThreadStressLog()); + if (msgs == 0) + { + msgs = CreateThreadStressLog(pThread); + + if (msgs == 0) + return FALSE; + } + + if (chunksToReserve == 0) + { + chunksToReserve = (theLog.MaxSizePerThread + STRESSLOG_CHUNK_SIZE - 1) / STRESSLOG_CHUNK_SIZE; + } + + long numTries = (long)chunksToReserve - msgs->chunkListLength; + for (long i = 0; i < numTries; i++) + { + msgs->GrowChunkList (); + } + + return msgs->chunkListLength >= (long)chunksToReserve; +} + +/*********************************************************************************/ +/* fetch a buffer that can be used to write a stress message, it is thread safe */ + +void ThreadStressLog::LogMsg ( UInt32 facility, int cArgs, const char* format, va_list Args) +{ + + // Asserts in this function cause infinite loops in the asserting mechanism. + // Just use debug breaks instead. + + ASSERT( cArgs >= 0 && cArgs <= StressMsg::maxArgCnt ); + + size_t offs = ((size_t)format - StressLog::theLog.moduleOffset); + + ASSERT(offs < StressMsg::maxOffset); + if (offs >= StressMsg::maxOffset) + { + // Set it to this string instead. + offs = +#ifdef _DEBUG + (size_t)""; +#else // _DEBUG + 0; // a 0 offset is ignored by StressLog::Dump +#endif // _DEBUG else + } + + // Get next available slot + StressMsg* msg = AdvanceWrite(cArgs); + + msg->timeStamp = getTimeStamp(); + msg->facility = facility; + msg->formatOffset = offs; + msg->numberOfArgs = cArgs; + + for ( int i = 0; i < cArgs; ++i ) + { + void* data = va_arg(Args, void*); + msg->args[i] = data; + } + + ASSERT(IsValid() && threadId == GetCurrentThreadId ()); +} + + +void ThreadStressLog::Activate (Thread * pThread) +{ + _ASSERTE(pThread != NULL); + //there is no need to zero buffers because we could handle garbage contents + threadId = PalGetCurrentThreadId (); + isDead = FALSE; + curWriteChunk = chunkListTail; + curPtr = (StressMsg *)curWriteChunk->EndPtr (); + writeHasWrapped = FALSE; + this->pThread = pThread; + ASSERT(pThread->GetPalThreadId() == threadId); +} + +/* static */ +void StressLog::LogMsg (unsigned facility, int cArgs, const char* format, ... ) +{ + _ASSERTE ( cArgs >= 0 && cArgs <= StressMsg::maxArgCnt ); + + va_list Args; + va_start(Args, format); + + Thread *pThread = ThreadStore::GetCurrentThread(); + if (pThread == NULL) + return; + + ThreadStressLog* msgs = reinterpret_cast(pThread->GetThreadStressLog()); + + if (msgs == 0) { + msgs = CreateThreadStressLog(pThread); + + if (msgs == 0) + return; + } + msgs->LogMsg (facility, cArgs, format, Args); +} + +#ifdef _DEBUG + +/* static */ +void StressLog::LogCallStack(const char *const callTag){ + + size_t CallStackTrace[MAX_CALL_STACK_TRACE]; + UInt32 hash; + unsigned short stackTraceCount = PalCaptureStackBackTrace (2, MAX_CALL_STACK_TRACE, (void**)CallStackTrace, &hash); + if (stackTraceCount > MAX_CALL_STACK_TRACE) + stackTraceCount = MAX_CALL_STACK_TRACE; + LogMsgOL("Start of %s stack \n", callTag); + unsigned short i = 0; + for (;i < stackTraceCount; i++) + { + LogMsgOL("(%s stack)%pK\n", callTag, CallStackTrace[i]); + } + LogMsgOL("End of %s stack\n", callTag); +} + +#endif //_DEBUG + +#else // DACCESS_COMPILE + +bool StressLog::Initialize() +{ + ThreadStressLog* logs = 0; + + ThreadStressLog* curThreadStressLog = this->logs; + unsigned __int64 lastTimeStamp = 0; // timestamp of last log entry + while(curThreadStressLog != 0) + { + if (!curThreadStressLog->IsReadyForRead()) + { + if (curThreadStressLog->origCurPtr == NULL) + curThreadStressLog->origCurPtr = curThreadStressLog->curPtr; + + // avoid repeated calls into this function + StressLogChunk * head = curThreadStressLog->chunkListHead; + StressLogChunk * curChunk = head; + BOOL curPtrInitialized = FALSE; + do + { + if (!curChunk->IsValid ()) + { + // TODO: Report corrupt chunk PTR_HOST_TO_TADDR(curChunk) + } + + if (!curPtrInitialized && curChunk == curThreadStressLog->curWriteChunk) + { + // adjust curPtr to the debugger's address space + curThreadStressLog->curPtr = (StressMsg *)((BYTE *)curChunk + ((BYTE *)curThreadStressLog->curPtr - (BYTE *)PTR_HOST_TO_TADDR(curChunk))); + curPtrInitialized = TRUE; + } + + curChunk = curChunk->next; + } while (curChunk != head); + + if (!curPtrInitialized) + { + delete curThreadStressLog; + return FALSE; + } + + // adjust readPtr and curPtr if needed + curThreadStressLog->Activate (NULL); + } + curThreadStressLog = curThreadStressLog->next; + } + return TRUE; +} + +void StressLog::ResetForRead() +{ + ThreadStressLog* curThreadStressLog = this->logs; + while(curThreadStressLog != 0) + { + curThreadStressLog->readPtr = NULL; + curThreadStressLog->curPtr = curThreadStressLog->origCurPtr; + curThreadStressLog = curThreadStressLog->next; + } +} + +// Initialization of the ThreadStressLog when dumping the log +inline void ThreadStressLog::Activate (Thread * /*pThread*/) +{ + // avoid repeated calls into this function + if (IsReadyForRead()) + return; + + curReadChunk = curWriteChunk; + readPtr = curPtr; + readHasWrapped = false; + // the last written log, if it wrapped around may have partially overwritten + // a previous record. Update curPtr to reflect the last safe beginning of a record, + // but curPtr shouldn't wrap around, otherwise it'll break our assumptions about stress + // log + curPtr = (StressMsg*)((char*)curPtr - StressMsg::maxMsgSize()); + if (curPtr < (StressMsg*)curWriteChunk->StartPtr()) + { + curPtr = (StressMsg *)curWriteChunk->StartPtr(); + } + // corner case: the log is empty + if (readPtr == (StressMsg *)curReadChunk->EndPtr ()) + { + AdvReadPastBoundary(); + } +} + +ThreadStressLog* StressLog::FindLatestThreadLog() const +{ + const ThreadStressLog* latestLog = 0; + for (const ThreadStressLog* ptr = this->logs; ptr != NULL; ptr = ptr->next) + { + if (ptr->readPtr != NULL) + if (latestLog == 0 || ptr->readPtr->timeStamp > latestLog->readPtr->timeStamp) + latestLog = ptr; + } + return const_cast(latestLog); +} + +// Can't refer to the types in sospriv.h because it drags in windows.h +void StressLog::EnumerateStressMsgs(/*STRESSMSGCALLBACK*/void* smcbWrapper, /*ENDTHREADLOGCALLBACK*/void* etcbWrapper, void *token) +{ + STRESSMSGCALLBACK smcb = (STRESSMSGCALLBACK)smcbWrapper; + ENDTHREADLOGCALLBACK etcb = (ENDTHREADLOGCALLBACK) etcbWrapper; + void *argsCopy[StressMsg::maxArgCnt]; + + for (;;) + { + ThreadStressLog* latestLog = this->FindLatestThreadLog(); + + if (latestLog == 0) + { + break; + } + StressMsg* latestMsg = latestLog->readPtr; + if (latestMsg->formatOffset != 0 && !latestLog->CompletedDump()) + { + char format[256]; + TADDR taFmt = (latestMsg->formatOffset) + (TADDR)(this->moduleOffset); + HRESULT hr = DacReadAll(taFmt, format, _countof(format), false); + if (hr != S_OK) + strcpy_s(format, _countof(format), "Could not read address of format string"); + + double deltaTime = ((double) (latestMsg->timeStamp - this->startTimeStamp)) / this->tickFrequency; + + // Pass a copy of the args to the callback to avoid foreign code overwriting the stress log + // entries (this was the case for %s arguments) + memcpy_s(argsCopy, sizeof(argsCopy), latestMsg->args, (latestMsg->numberOfArgs)*sizeof(void*)); + if (!smcb(latestLog->threadId, deltaTime, latestMsg->facility, format, argsCopy, token)) + break; + } + + latestLog->readPtr = latestLog->AdvanceRead(); + if (latestLog->CompletedDump()) + { + latestLog->readPtr = NULL; + if (!etcb(latestLog->threadId, token)) + break; + } + } +} + +// Can't refer to the types in sospriv.h because it drags in windows.h +void StressLog::EnumStressLogMemRanges(/*STRESSLOGMEMRANGECALLBACK*/void* slmrcbWrapper, void *token) +{ + STRESSLOGMEMRANGECALLBACK slmrcb = (STRESSLOGMEMRANGECALLBACK)slmrcbWrapper; + + // we go to extreme lengths to ensure we don't read in the whole memory representation + // of the stress log, but only the ranges... + // + + size_t ThreadStressLogAddr = *dac_cast(PTR_HOST_MEMBER_TADDR(StressLog, this, logs)); + while(ThreadStressLogAddr != NULL) + { + size_t ChunkListHeadAddr = *dac_cast(ThreadStressLogAddr + offsetof(ThreadStressLog, chunkListHead)); + size_t StressLogChunkAddr = ChunkListHeadAddr; + + do + { + slmrcb(StressLogChunkAddr, sizeof (StressLogChunk), token); + StressLogChunkAddr = *dac_cast(StressLogChunkAddr + offsetof (StressLogChunk, next)); + if (StressLogChunkAddr == NULL) + { + return; + } + } while (StressLogChunkAddr != ChunkListHeadAddr); + + ThreadStressLogAddr = *dac_cast(ThreadStressLogAddr + offsetof(ThreadStressLog, next)); + } +} + + +#endif // !DACCESS_COMPILE + +#endif // STRESS_LOG + diff --git a/src/Native/Runtime/stressLog.h b/src/Native/Runtime/stressLog.h new file mode 100644 index 00000000000..319a56d10b9 --- /dev/null +++ b/src/Native/Runtime/stressLog.h @@ -0,0 +1,846 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// --------------------------------------------------------------------------- +// StressLog.h +// +// StressLog infrastructure +// +// The StressLog is a binary, memory based circular queue of logging messages. +// It is intended to be used in retail builds during stress runs (activated +// by registry key), to help find bugs that only turn up during stress runs. +// +// Differently from the desktop implementation the RH implementation of the +// stress log will log all facilities, and only filter on logging level. +// +// The log has a very simple structure, and is meant to be dumped from an NTSD +// extention (eg. strike). +// +// debug\rhsos\stresslogdump.cpp contains the dumper utility that parses this +// log. +// --------------------------------------------------------------------------- + +#ifndef StressLog_h +#define StressLog_h 1 + +#define SUPPRESS_WARNING_4127 \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) /* conditional expression is constant*/ + +#define POP_WARNING_STATE \ + __pragma(warning(pop)) + +#define WHILE_0 \ + SUPPRESS_WARNING_4127 \ + while(0) \ + POP_WARNING_STATE \ + + +// let's keep STRESS_LOG defined always... +#if !defined(STRESS_LOG) && !defined(NO_STRESS_LOG) +#define STRESS_LOG +#endif + +#if defined(STRESS_LOG) + +// +// Logging levels and facilities +// +#define DEFINE_LOG_FACILITY(logname, value) logname = value, + +enum LogFacilitiesEnum: unsigned int { +#include "loglf.h" + LF_ALWAYS = 0x80000000u, // Log message irrepespective of LogFacility (if the level matches) + LF_ALL = 0xFFFFFFFFu, // Used only to mask bits. Never use as LOG((LF_ALL, ...)) +}; + + +#define LL_EVERYTHING 10 +#define LL_INFO1000000 9 // can be expected to generate 1,000,000 logs per small but not trival run +#define LL_INFO100000 8 // can be expected to generate 100,000 logs per small but not trival run +#define LL_INFO10000 7 // can be expected to generate 10,000 logs per small but not trival run +#define LL_INFO1000 6 // can be expected to generate 1,000 logs per small but not trival run +#define LL_INFO100 5 // can be expected to generate 100 logs per small but not trival run +#define LL_INFO10 4 // can be expected to generate 10 logs per small but not trival run +#define LL_WARNING 3 +#define LL_ERROR 2 +#define LL_FATALERROR 1 +#define LL_ALWAYS 0 // impossible to turn off (log level never negative) + +// +// +// + +#ifndef _ASSERTE +#define _ASSERTE(expr) +#endif + + +#ifndef DACCESS_COMPILE + + +//========================================================================================== +// The STRESS_LOG* macros +// +// The STRESS_LOG* macros work like printf. In fact the use printf in their implementation +// so all printf format specifications work. In addition the Stress log dumper knows +// about certain suffixes for the %p format specification (normally used to print a pointer) +// +// %pM // The pointer is a MethodInfo -- not supported yet (use %pK instead) +// %pT // The pointer is a type (EEType) +// %pV // The pointer is a C++ Vtable pointer +// %pK // The pointer is a code address (used for call stacks or method names) +// + +// STRESS_LOG_VA was added to allow sending GC trace output to the stress log. msg must be enclosed +// in ()'s and contain a format string followed by 0 - 4 arguments. The arguments must be numbers or +// string literals. LogMsgOL is overloaded so that all of the possible sets of parameters are covered. +// This was done becasue GC Trace uses dprintf which dosen't contain info on how many arguments are +// getting passed in and using va_args would require parsing the format string during the GC +// + +#define STRESS_LOG_VA(msg) do { \ + if (StressLog::StressLogOn(LF_GC, LL_ALWAYS)) \ + StressLog::LogMsgOL msg; \ + } WHILE_0 + +#define STRESS_LOG0(facility, level, msg) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 0, msg); \ + } WHILE_0 \ + +#define STRESS_LOG1(facility, level, msg, data1) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 1, msg, (void*)(size_t)(data1)); \ + } WHILE_0 + +#define STRESS_LOG2(facility, level, msg, data1, data2) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 2, msg, \ + (void*)(size_t)(data1), (void*)(size_t)(data2)); \ + } WHILE_0 + +#define STRESS_LOG3(facility, level, msg, data1, data2, data3) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 3, msg, \ + (void*)(size_t)(data1),(void*)(size_t)(data2),(void*)(size_t)(data3)); \ + } WHILE_0 + +#define STRESS_LOG4(facility, level, msg, data1, data2, data3, data4) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 4, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4)); \ + } WHILE_0 + +#define STRESS_LOG5(facility, level, msg, data1, data2, data3, data4, data5) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 5, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4), \ + (void*)(size_t)(data5)); \ + } WHILE_0 + +#define STRESS_LOG6(facility, level, msg, data1, data2, data3, data4, data5, data6) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 6, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4), \ + (void*)(size_t)(data5), (void*)(size_t)(data6)); \ + } WHILE_0 + +#define STRESS_LOG7(facility, level, msg, data1, data2, data3, data4, data5, data6, data7) do { \ + if (StressLog::StressLogOn(facility, level)) \ + StressLog::LogMsg(facility, 7, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4), \ + (void*)(size_t)(data5), (void*)(size_t)(data6), (void*)(size_t)(data7)); \ + } WHILE_0 + +#define STRESS_LOG_COND0(facility, level, msg) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 0, msg); \ + } WHILE_0 + +#define STRESS_LOG_COND1(facility, level, cond, msg, data1) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 1, msg, (void*)(size_t)(data1)); \ + } WHILE_0 + +#define STRESS_LOG_COND2(facility, level, cond, msg, data1, data2) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 2, msg, \ + (void*)(size_t)(data1), (void*)(size_t)(data2)); \ + } WHILE_0 + +#define STRESS_LOG_COND3(facility, level, cond, msg, data1, data2, data3) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 3, msg, \ + (void*)(size_t)(data1),(void*)(size_t)(data2),(void*)(size_t)(data3)); \ + } WHILE_0 + +#define STRESS_LOG_COND4(facility, level, cond, msg, data1, data2, data3, data4) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 4, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4)); \ + } WHILE_0 + +#define STRESS_LOG_COND5(facility, level, cond, msg, data1, data2, data3, data4, data5) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 5, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4), \ + (void*)(size_t)(data5)); \ + } WHILE_0 + +#define STRESS_LOG_COND6(facility, level, cond, msg, data1, data2, data3, data4, data5, data6) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 6, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4), \ + (void*)(size_t)(data5), (void*)(size_t)(data6)); \ + } WHILE_0 + +#define STRESS_LOG_COND7(facility, level, cond, msg, data1, data2, data3, data4, data5, data6, data7) do { \ + if (StressLog::StressLogOn(facility, level) && (cond)) \ + StressLog::LogMsg(facility, 7, msg, (void*)(size_t)(data1), \ + (void*)(size_t)(data2),(void*)(size_t)(data3),(void*)(size_t)(data4), \ + (void*)(size_t)(data5), (void*)(size_t)(data6), (void*)(size_t)(data7)); \ + } WHILE_0 + +#define STRESS_LOG_RESERVE_MEM(numChunks) do { \ + if (StressLog::StressLogOn(LF_ALL, LL_ALWAYS)) \ + {StressLog::ReserveStressLogChunks (numChunks);} \ + } WHILE_0 + +// !!! WARNING !!! +// !!! DO NOT ADD STRESS_LOG8, as the stress log infrastructure supports a maximum of 7 arguments +// !!! WARNING !!! + +#define STRESS_LOG_PLUG_MOVE(plug_start, plug_end, plug_delta) do { \ + if (StressLog::StressLogOn(LF_GC, LL_INFO1000)) \ + StressLog::LogMsg(LF_GC, 3, ThreadStressLog::gcPlugMoveMsg(), \ + (void*)(size_t)(plug_start), (void*)(size_t)(plug_end), (void*)(size_t)(plug_delta)); \ + } WHILE_0 + +#define STRESS_LOG_ROOT_PROMOTE(root_addr, objPtr, methodTable) do { \ + if (StressLog::StressLogOn(LF_GC|LF_GCROOTS, LL_INFO1000)) \ + StressLog::LogMsg(LF_GC|LF_GCROOTS, 3, ThreadStressLog::gcRootPromoteMsg(), \ + (void*)(size_t)(root_addr), (void*)(size_t)(objPtr), (void*)(size_t)(methodTable)); \ + } WHILE_0 + +#define STRESS_LOG_ROOT_RELOCATE(root_addr, old_value, new_value, methodTable) do { \ + if (StressLog::StressLogOn(LF_GC|LF_GCROOTS, LL_INFO1000) && ((size_t)(old_value) != (size_t)(new_value))) \ + StressLog::LogMsg(LF_GC|LF_GCROOTS, 4, ThreadStressLog::gcRootMsg(), \ + (void*)(size_t)(root_addr), (void*)(size_t)(old_value), \ + (void*)(size_t)(new_value), (void*)(size_t)(methodTable)); \ + } WHILE_0 + +#define STRESS_LOG_GC_START(gcCount, Gen, collectClasses) do { \ + if (StressLog::StressLogOn(LF_GCROOTS|LF_GC|LF_GCALLOC, LL_INFO10)) \ + StressLog::LogMsg(LF_GCROOTS|LF_GC|LF_GCALLOC, 3, ThreadStressLog::gcStartMsg(), \ + (void*)(size_t)(gcCount), (void*)(size_t)(Gen), (void*)(size_t)(collectClasses)); \ + } WHILE_0 + +#define STRESS_LOG_GC_END(gcCount, Gen, collectClasses) do { \ + if (StressLog::StressLogOn(LF_GCROOTS|LF_GC|LF_GCALLOC, LL_INFO10)) \ + StressLog::LogMsg(LF_GCROOTS|LF_GC|LF_GCALLOC, 3, ThreadStressLog::gcEndMsg(),\ + (void*)(size_t)(gcCount), (void*)(size_t)(Gen), (void*)(size_t)(collectClasses), 0);\ + } WHILE_0 + +#if defined(_DEBUG) +#define MAX_CALL_STACK_TRACE 20 +#define STRESS_LOG_OOM_STACK(size) do { \ + if (StressLog::StressLogOn(LF_ALWAYS, LL_ALWAYS)) \ + { \ + StressLog::LogMsgOL("OOM on alloc of size %x \n", (void*)(size_t)(size)); \ + StressLog::LogCallStack ("OOM"); \ + } \ + } WHILE_0 +#define STRESS_LOG_GC_STACK do { \ + if (StressLog::StressLogOn(LF_GC |LF_GCINFO, LL_ALWAYS)) \ + { \ + StressLog::LogMsgOL("GC is triggered \n"); \ + StressLog::LogCallStack ("GC"); \ + } \ + } WHILE_0 +#else //_DEBUG +#define STRESS_LOG_OOM_STACK(size) +#define STRESS_LOG_GC_STACK +#endif //_DEBUG + +#endif // DACCESS_COMPILE + +// +// forward declarations: +// +class CrstStatic; +class Thread; +typedef DPTR(Thread) PTR_Thread; +class StressLog; +typedef DPTR(StressLog) PTR_StressLog; +class ThreadStressLog; +typedef DPTR(ThreadStressLog) PTR_ThreadStressLog; +struct StressLogChunk; +typedef DPTR(StressLogChunk) PTR_StressLogChunk; +struct DacpStressLogEnumCBArgs; + + +//========================================================================================== +// StressLog - per-thread circular queue of stresslog messages +// +class StressLog { +public: +// private: + unsigned facilitiesToLog; // Bitvector of facilities to log (see loglf.h) + unsigned levelToLog; // log level + unsigned MaxSizePerThread; // maximum number of bytes each thread should have before wrapping + unsigned MaxSizeTotal; // maximum memory allowed for stress log + Int32 totalChunk; // current number of total chunks allocated + PTR_ThreadStressLog logs; // the list of logs for every thread. + Int32 deadCount; // count of dead threads in the log + CrstStatic *pLock; // lock + unsigned __int64 tickFrequency; // number of ticks per second + unsigned __int64 startTimeStamp; // start time from when tick counter started + FILETIME startTime; // time the application started + size_t moduleOffset; // Used to compute format strings. + +#ifndef DACCESS_COMPILE +public: + static void Initialize(unsigned facilities, unsigned level, unsigned maxBytesPerThread, + unsigned maxBytesTotal, HANDLE hMod); + // Called at DllMain THREAD_DETACH to recycle thread's logs + static void ThreadDetach(ThreadStressLog *msgs); + static long NewChunk () { return PalInterlockedIncrement (&theLog.totalChunk); } + static long ChunkDeleted () { return PalInterlockedDecrement (&theLog.totalChunk); } + + //the result is not 100% accurate. If multiple threads call this funciton at the same time, + //we could allow the total size be bigger than required. But the memory won't grow forever + //and this is not critical so we don't try to fix the race + static bool AllowNewChunk (long numChunksInCurThread); + + //preallocate Stress log chunks for current thread. The memory we could preallocate is still + //bounded by per thread size limit and total size limit. If chunksToReserve is 0, we will try to + //preallocate up to per thread size limit + static bool ReserveStressLogChunks (unsigned int chunksToReserve); + +// private: + static ThreadStressLog* CreateThreadStressLog(Thread * pThread); + static ThreadStressLog* CreateThreadStressLogHelper(Thread * pThread); + +#else // DACCESS_COMPILE +public: + bool Initialize(); + + // Can't refer to the types in sospriv.h because it drags in windows.h + void EnumerateStressMsgs(/*STRESSMSGCALLBACK*/ void* smcb, /*ENDTHREADLOGCALLBACK*/ void* etcb, + void *token); + void EnumStressLogMemRanges(/*STRESSLOGMEMRANGECALLBACK*/ void* slmrcb, void *token); + + // Called while dumping logs after operations are completed, to ensure DAC-caches + // allow the stress logs to be dumped again + void ResetForRead(); + + ThreadStressLog* FindLatestThreadLog() const; + + friend class ClrDataAccess; + +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +public: + FORCEINLINE static bool StressLogOn(unsigned facility, unsigned level) + { + #if defined(DACCESS_COMPILE) + return FALSE; + #else + // In Redhawk we have rationalized facility codes and have much + // fewer compared to desktop, as such we'll log all facilities and + // limit the filtering to the log level... + return + // (theLog.facilitiesToLog & facility) + // && + (level <= theLog.levelToLog); + #endif + } + + static void LogMsg(unsigned facility, int cArgs, const char* format, ... ); + + // Support functions for STRESS_LOG_VA + // We disable the warning "conversion from 'type' to 'type' of greater size" since everything will + // end up on the stack, and LogMsg will know the size of the variable based on the format string. + #ifdef _MSC_VER + #pragma warning( push ) + #pragma warning( disable : 4312 ) + #endif + static void LogMsgOL(const char* format) + { LogMsg(LF_GC, 0, format); } + + template < typename T1 > + static void LogMsgOL(const char* format, T1 data1) + { + C_ASSERT(sizeof(T1) <= sizeof(void*)); + LogMsg(LF_GC, 1, format, (void*)data1); + } + + template < typename T1, typename T2 > + static void LogMsgOL(const char* format, T1 data1, T2 data2) + { + C_ASSERT(sizeof(T1) <= sizeof(void*) && sizeof(T2) <= sizeof(void*)); + LogMsg(LF_GC, 2, format, (void*)data1, (void*)data2); + } + + template < typename T1, typename T2, typename T3 > + static void LogMsgOL(const char* format, T1 data1, T2 data2, T3 data3) + { + C_ASSERT(sizeof(T1) <= sizeof(void*) && sizeof(T2) <= sizeof(void*) && sizeof(T3) <= sizeof(void*)); + LogMsg(LF_GC, 3, format, (void*)data1, (void*)data2, (void*)data3); + } + + template < typename T1, typename T2, typename T3, typename T4 > + static void LogMsgOL(const char* format, T1 data1, T2 data2, T3 data3, T4 data4) + { + C_ASSERT(sizeof(T1) <= sizeof(void*) && sizeof(T2) <= sizeof(void*) && sizeof(T3) <= sizeof(void*) && sizeof(T4) <= sizeof(void*)); + LogMsg(LF_GC, 4, format, (void*)data1, (void*)data2, (void*)data3, (void*)data4); + } + + template < typename T1, typename T2, typename T3, typename T4, typename T5 > + static void LogMsgOL(const char* format, T1 data1, T2 data2, T3 data3, T4 data4, T5 data5) + { + C_ASSERT(sizeof(T1) <= sizeof(void*) && sizeof(T2) <= sizeof(void*) && sizeof(T3) <= sizeof(void*) && sizeof(T4) <= sizeof(void*) && sizeof(T5) <= sizeof(void*)); + LogMsg(LF_GC, 5, format, (void*)data1, (void*)data2, (void*)data3, (void*)data4, (void*)data5); + } + + template < typename T1, typename T2, typename T3, typename T4, typename T5, typename T6 > + static void LogMsgOL(const char* format, T1 data1, T2 data2, T3 data3, T4 data4, T5 data5, T6 data6) + { + C_ASSERT(sizeof(T1) <= sizeof(void*) && sizeof(T2) <= sizeof(void*) && sizeof(T3) <= sizeof(void*) && sizeof(T4) <= sizeof(void*) && sizeof(T5) <= sizeof(void*) && sizeof(T6) <= sizeof(void*)); + LogMsg(LF_GC, 6, format, (void*)data1, (void*)data2, (void*)data3, (void*)data4, (void*)data5, (void*)data6); + } + + template < typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7 > + static void LogMsgOL(const char* format, T1 data1, T2 data2, T3 data3, T4 data4, T5 data5, T6 data6, T7 data7) + { + C_ASSERT(sizeof(T1) <= sizeof(void*) && sizeof(T2) <= sizeof(void*) && sizeof(T3) <= sizeof(void*) && sizeof(T4) <= sizeof(void*) && sizeof(T5) <= sizeof(void*) && sizeof(T6) <= sizeof(void*) && sizeof(T7) <= sizeof(void*)); + LogMsg(LF_GC, 7, format, (void*)data1, (void*)data2, (void*)data3, (void*)data4, (void*)data5, (void*)data6, (void*)data7); + } + + #ifdef _MSC_VER + #pragma warning( pop ) + #endif + +// We can only log the stacktrace on DEBUG builds! +#ifdef _DEBUG + static void LogCallStack(const char *const callTag); +#endif //_DEBUG + +#endif // DACCESS_COMPILE + +// private: // static variables + static StressLog theLog; // We only have one log, and this is it +}; + + +//========================================================================================== +// Private classes +// + +#if defined(_MSC_VER) +// don't warn about 0 sized array below or unnamed structures +#pragma warning(disable:4200 4201) +#endif + +//========================================================================================== +// StressMsg +// +// The order of fields is important. Keep the prefix length as the first field. +// And make sure the timeStamp field is naturally aligned, so we don't waste +// space on 32-bit platforms +// +struct StressMsg { + union { + struct { + UInt32 numberOfArgs : 3; // at most 7 arguments + UInt32 formatOffset : 29; // offset of string in mscorwks + }; + UInt32 fmtOffsCArgs; // for optimized access + }; + UInt32 facility; // facility used to log the entry + unsigned __int64 timeStamp; // time when mssg was logged + void* args[0]; // size given by numberOfArgs + + static const size_t maxArgCnt = 7; + static const size_t maxOffset = 0x20000000; + static size_t maxMsgSize () + { return sizeof(StressMsg) + maxArgCnt*sizeof(void*); } + + friend class ThreadStressLog; + friend class StressLog; +}; + +#ifdef _WIN64 +#define STRESSLOG_CHUNK_SIZE (32 * 1024) +#else //_WIN64 +#define STRESSLOG_CHUNK_SIZE (16 * 1024) +#endif //_WIN64 +#define GC_STRESSLOG_MULTIPLY (5) + +//========================================================================================== +// StressLogChunk +// +// A chunk of contiguous memory containing instances of StressMsg +// +struct StressLogChunk +{ + PTR_StressLogChunk prev; + PTR_StressLogChunk next; + char buf[STRESSLOG_CHUNK_SIZE]; + UInt32 dwSig1; + UInt32 dwSig2; + +#ifndef DACCESS_COMPILE + static HANDLE s_LogChunkHeap; + + void * operator new (size_t) + { + _ASSERTE (s_LogChunkHeap != NULL); + //no need to zero memory because we could handle garbage contents + return PalHeapAlloc (s_LogChunkHeap, 0, sizeof (StressLogChunk)); + } + + void operator delete (void * chunk) + { + _ASSERTE (s_LogChunkHeap != NULL); + PalHeapFree (s_LogChunkHeap, 0, chunk); + } + + StressLogChunk (PTR_StressLogChunk p = NULL, PTR_StressLogChunk n = NULL) + :prev (p), next (n), dwSig1 (0xCFCFCFCF), dwSig2 (0xCFCFCFCF) + {} + +#endif //!DACCESS_COMPILE + + char * StartPtr () + { + return buf; + } + + char * EndPtr () + { + return buf + STRESSLOG_CHUNK_SIZE; + } + + bool IsValid () const + { + return dwSig1 == 0xCFCFCFCF && dwSig2 == 0xCFCFCFCF; + } +}; + +//========================================================================================== +// ThreadStressLog +// +// This class implements a circular stack of variable sized elements +// .The buffer between startPtr-endPtr is used in a circular manner +// to store instances of the variable-sized struct StressMsg. +// The StressMsg are always aligned to endPtr, while the space +// left between startPtr and the last element is 0-padded. +// .curPtr points to the most recently written log message +// .readPtr points to the next log message to be dumped +// .hasWrapped is TRUE while dumping the log, if we had wrapped +// past the endPtr marker, back to startPtr +// The AdvanceRead/AdvanceWrite operations simply update the +// readPtr / curPtr fields. thecaller is responsible for reading/writing +// to the corresponding field +class ThreadStressLog { + PTR_ThreadStressLog next; // we keep a linked list of these + unsigned threadId; // the id for the thread using this buffer + bool isDead; // Is this thread dead + StressMsg* curPtr; // where packets are being put on the queue + StressMsg* readPtr; // where we are reading off the queue (used during dumping) + bool readHasWrapped; // set when read ptr has passed chunListTail + bool writeHasWrapped; // set when write ptr has passed chunListHead + PTR_StressLogChunk chunkListHead; //head of a list of stress log chunks + PTR_StressLogChunk chunkListTail; //tail of a list of stress log chunks + PTR_StressLogChunk curReadChunk; //the stress log chunk we are currently reading + PTR_StressLogChunk curWriteChunk; //the stress log chunk we are currently writing + long chunkListLength; // how many stress log chunks are in this stress log + PTR_Thread pThread; // thread associated with these stress logs + StressMsg * origCurPtr; // this holds the original curPtr before we start the dump + + friend class StressLog; + +#ifndef DACCESS_COMPILE +public: + inline ThreadStressLog (); + inline ~ThreadStressLog (); + + void LogMsg ( UInt32 facility, int cArgs, const char* format, ... ) + { + va_list Args; + va_start(Args, format); + LogMsg (facility, cArgs, format, Args); + } + + void LogMsg ( UInt32 facility, int cArgs, const char* format, va_list Args); + +private: + FORCEINLINE StressMsg* AdvanceWrite(int cArgs); + inline StressMsg* AdvWritePastBoundary(int cArgs); + FORCEINLINE bool GrowChunkList (); + +#else // DACCESS_COMPILE +public: + friend class ClrDataAccess; + + // Called while dumping. Returns true after all messages in log were dumped + FORCEINLINE bool CompletedDump (); + +private: + FORCEINLINE bool IsReadyForRead() { return readPtr != NULL; } + FORCEINLINE StressMsg* AdvanceRead(); + inline StressMsg* AdvReadPastBoundary(); +#endif //!DACCESS_COMPILE + +public: + void Activate (Thread * pThread); + + bool IsValid () const + { + return chunkListHead != NULL && (!curWriteChunk || curWriteChunk->IsValid ()); + } + + static const char* gcStartMsg() + { + return "{ =========== BEGINGC %d, (requested generation = %lu, collect_classes = %lu) ==========\n"; + } + + static const char* gcEndMsg() + { + return "========== ENDGC %d (gen = %lu, collect_classes = %lu) ===========}\n"; + } + + static const char* gcRootMsg() + { + return " GC Root %p RELOCATED %p -> %p MT = %pT\n"; + } + + static const char* gcRootPromoteMsg() + { + return " GCHeap::Promote: Promote GC Root *%p = %p MT = %pT\n"; + } + + static const char* gcPlugMoveMsg() + { + return "GC_HEAP RELOCATING Objects in heap within range [%p %p) by -0x%x bytes\n"; + } + +}; + + +//========================================================================================== +// Inline implementations: +// + +#ifdef DACCESS_COMPILE + +//------------------------------------------------------------------------------------------ +// Called while dumping. Returns true after all messages in log were dumped +FORCEINLINE bool ThreadStressLog::CompletedDump () +{ + return readPtr->timeStamp == 0 + //if read has passed end of list but write has not passed head of list yet, we are done + //if write has also wrapped, we are at the end if read pointer passed write pointer + || (readHasWrapped && + (!writeHasWrapped || (curReadChunk == curWriteChunk && readPtr >= curPtr))); +} + +//------------------------------------------------------------------------------------------ +// Called when dumping the log (by StressLog::Dump()) +// Updates readPtr to point to next stress messaage to be dumped +inline StressMsg* ThreadStressLog::AdvanceRead() { + // advance the marker + readPtr = (StressMsg*)((char*)readPtr + sizeof(StressMsg) + readPtr->numberOfArgs*sizeof(void*)); + // wrap around if we need to + if (readPtr >= (StressMsg *)curReadChunk->EndPtr ()) + { + AdvReadPastBoundary(); + } + return readPtr; +} + +//------------------------------------------------------------------------------------------ +// The factored-out slow codepath for AdvanceRead(), only called by AdvanceRead(). +// Updates readPtr to and returns the first stress message >= startPtr +inline StressMsg* ThreadStressLog::AdvReadPastBoundary() { + //if we pass boundary of tail list, we need to set has Wrapped + if (curReadChunk == chunkListTail) + { + readHasWrapped = true; + //If write has not wrapped, we know the contents from list head to + //cur pointer is garbage, we don't need to read them + if (!writeHasWrapped) + { + return readPtr; + } + } + curReadChunk = curReadChunk->next; + void** p = (void**)curReadChunk->StartPtr(); + while (*p == NULL && (size_t)(p-(void**)curReadChunk->StartPtr ()) < (StressMsg::maxMsgSize()/sizeof(void*))) + { + ++p; + } + // if we failed to find a valid start of a StressMsg fallback to startPtr (since timeStamp==0) + if (*p == NULL) + { + p = (void**) curReadChunk->StartPtr (); + } + readPtr = (StressMsg*)p; + + return readPtr; +} + +#else // DACCESS_COMPILE + +//------------------------------------------------------------------------------------------ +// Initialize a ThreadStressLog +inline ThreadStressLog::ThreadStressLog() +{ + chunkListHead = chunkListTail = curWriteChunk = NULL; + StressLogChunk * newChunk =new StressLogChunk; + //OOM or in cantalloc region + if (newChunk == NULL) + { + return; + } + StressLog::NewChunk (); + + newChunk->prev = newChunk; + newChunk->next = newChunk; + + chunkListHead = chunkListTail = newChunk; + + next = NULL; + threadId = 0; + isDead = TRUE; + curPtr = NULL; + readPtr = NULL; + writeHasWrapped = FALSE; + curReadChunk = NULL; + curWriteChunk = NULL; + chunkListLength = 1; + origCurPtr = NULL; +} + +inline ThreadStressLog::~ThreadStressLog () +{ + //no thing to do if the list is empty (failed to initialize) + if (chunkListHead == NULL) + { + return; + } + + StressLogChunk * chunk = chunkListHead; + + do + { + StressLogChunk * tmp = chunk; + chunk = chunk->next; + delete tmp; + StressLog::ChunkDeleted (); + } while (chunk != chunkListHead); +} + +//------------------------------------------------------------------------------------------ +// Called when logging, checks if we can increase the number of stress log chunks associated +// with the current thread +FORCEINLINE bool ThreadStressLog::GrowChunkList () +{ + _ASSERTE (chunkListLength >= 1); + if (!StressLog::AllowNewChunk (chunkListLength)) + { + return FALSE; + } + StressLogChunk * newChunk = new StressLogChunk (chunkListTail, chunkListHead); + if (newChunk == NULL) + { + return FALSE; + } + StressLog::NewChunk (); + chunkListLength++; + chunkListHead->prev = newChunk; + chunkListTail->next = newChunk; + chunkListHead = newChunk; + + return TRUE; +} + +//------------------------------------------------------------------------------------------ +// Called at runtime when writing the log (by StressLog::LogMsg()) +// Updates curPtr to point to the next spot in the log where we can write +// a stress message with cArgs arguments +// For convenience it returns a pointer to the empty slot where we can +// write the next stress message. +// cArgs is the number of arguments in the message to be written. +inline StressMsg* ThreadStressLog::AdvanceWrite(int cArgs) { + // _ASSERTE(cArgs <= StressMsg::maxArgCnt); + // advance the marker + StressMsg* p = (StressMsg*)((char*)curPtr - sizeof(StressMsg) - cArgs*sizeof(void*)); + + //past start of current chunk + //wrap around if we need to + if (p < (StressMsg*)curWriteChunk->StartPtr ()) + { + curPtr = AdvWritePastBoundary(cArgs); + } + else + { + curPtr = p; + } + + return curPtr; +} + +//------------------------------------------------------------------------------------------ +// This is the factored-out slow codepath for AdvanceWrite() and is only called by +// AdvanceWrite(). +// Returns the stress message flushed against endPtr +// In addition it writes NULLs b/w the startPtr and curPtr +inline StressMsg* ThreadStressLog::AdvWritePastBoundary(int cArgs) { + //zeroed out remaining buffer + memset (curWriteChunk->StartPtr (), 0, (char *)curPtr - (char *)curWriteChunk->StartPtr ()); + + //if we are already at head of the list, try to grow the list + if (curWriteChunk == chunkListHead) + { + GrowChunkList (); + } + + curWriteChunk = curWriteChunk->prev; + if (curWriteChunk == chunkListTail) + { + writeHasWrapped = TRUE; + } + curPtr = (StressMsg*)((char*)curWriteChunk->EndPtr () - sizeof(StressMsg) - cArgs * sizeof(void*)); + return curPtr; +} + +#endif // DACCESS_COMPILE + +#endif // STRESS_LOG + +#if !defined(STRESS_LOG) || defined (DACCESS_COMPILE) +#define STRESS_LOG_VA(msg) do { } WHILE_0 +#define STRESS_LOG0(facility, level, msg) do { } WHILE_0 +#define STRESS_LOG1(facility, level, msg, data1) do { } WHILE_0 +#define STRESS_LOG2(facility, level, msg, data1, data2) do { } WHILE_0 +#define STRESS_LOG3(facility, level, msg, data1, data2, data3) do { } WHILE_0 +#define STRESS_LOG4(facility, level, msg, data1, data2, data3, data4) do { } WHILE_0 +#define STRESS_LOG5(facility, level, msg, data1, data2, data3, data4, data5) do { } WHILE_0 +#define STRESS_LOG6(facility, level, msg, data1, data2, data3, data4, data5, data6) do { } WHILE_0 +#define STRESS_LOG7(facility, level, msg, data1, data2, data3, data4, data5, data6, data7) do { } WHILE_0 +#define STRESS_LOG_PLUG_MOVE(plug_start, plug_end, plug_delta) do { } WHILE_0 +#define STRESS_LOG_ROOT_PROMOTE(root_addr, objPtr, methodTable) do { } WHILE_0 +#define STRESS_LOG_ROOT_RELOCATE(root_addr, old_value, new_value, methodTable) do { } WHILE_0 +#define STRESS_LOG_GC_START(gcCount, Gen, collectClasses) do { } WHILE_0 +#define STRESS_LOG_GC_END(gcCount, Gen, collectClasses) do { } WHILE_0 +#define STRESS_LOG_OOM_STACK(size) do { } WHILE_0 +#define STRESS_LOG_GC_STACK do { } WHILE_0 +#define STRESS_LOG_RESERVE_MEM(numChunks) do { } WHILE_0 +#endif // !STRESS_LOG || DACCESS_COMPILE + +#endif // StressLog_h diff --git a/src/Native/Runtime/thread.cpp b/src/Native/Runtime/thread.cpp new file mode 100644 index 00000000000..3e82770241c --- /dev/null +++ b/src/Native/Runtime/thread.cpp @@ -0,0 +1,1125 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "event.h" +#include "rwlock.h" +#include "threadstore.h" +#include "runtimeinstance.h" +#include "module.h" +#include "new.h" +#include "rhbinder.h" +#include "stresslog.h" +#include "rhconfig.h" +#endif // !DACCESS_COMPILE + + +#ifndef DACCESS_COMPILE + +extern "C" void _ReadWriteBarrier(void); +#pragma intrinsic(_ReadWriteBarrier) +#endif //!DACCESS_COMPILE + +PTR_VOID Thread::GetTransitionFrame() +{ + if (ThreadStore::GetSuspendingThread() == this) + { + // This thread is in cooperative mode, so we grab the transition frame + // from the 'tunnel' location, which will have the frame from the most + // recent 'cooperative pinvoke' transition that brought us here. + ASSERT(m_pHackPInvokeTunnel != NULL); + return m_pHackPInvokeTunnel; + } + + ASSERT(m_pCachedTransitionFrame != NULL); + return m_pCachedTransitionFrame; +} + +#ifndef DACCESS_COMPILE + +PTR_VOID Thread::GetTransitionFrameForStackTrace() +{ + ASSERT_MSG(ThreadStore::GetSuspendingThread() == NULL, "Not allowed when suspended for GC."); + ASSERT_MSG(this == ThreadStore::GetCurrentThread(), "Only supported for current thread."); + ASSERT(Thread::IsCurrentThreadInCooperativeMode()); + ASSERT(m_pHackPInvokeTunnel != NULL); + return m_pHackPInvokeTunnel; +} + +void Thread::LeaveRendezVous(void * pTransitionFrame) +{ + ASSERT(ThreadStore::GetCurrentThread() == this); + + // ORDERING -- this write must occur before checking the trap + m_pTransitionFrame = pTransitionFrame; + + // We need to prevent compiler reordering between above write and below read. Both the read and the write + // are volatile, so it's possible that the particular semantic for volatile that MSVC provides is enough, + // but if not, this barrier would be required. If so, it won't change anything to add the barrier. + _ReadWriteBarrier(); + + if (ThreadStore::IsTrapThreadsRequested()) + { + Unhijack(); + GetThreadStore()->WaitForSuspendComplete(); + } +} + +bool Thread::TryReturnRendezVous(void * pTransitionFrame) +{ + ASSERT(ThreadStore::GetCurrentThread() == this); + + // ORDERING -- this write must occur before checking the trap + m_pTransitionFrame = 0; + + // We need to prevent compiler reordering between above write and below read. Both the read and the write + // are volatile, so it's possible that the particular semantic for volatile that MSVC provides is enough, + // but if not, this barrier would be required. If so, it won't change anything to add the barrier. + _ReadWriteBarrier(); + + if (ThreadStore::IsTrapThreadsRequested() && (this != ThreadStore::GetSuspendingThread())) + { + // Oops, a suspend request is pending. Go back to preemptive mode and wait. + m_pTransitionFrame = pTransitionFrame; + RedhawkGCInterface::WaitForGCCompletion(); + // a retry is now required + return false; + } + return true; +} + +// +// This is used by the suspension code when driving all threads to unmanaged code. It is performed after +// the FlushProcessWriteBuffers call so that we know that once the thread reaches unmanaged code, it won't +// reenter managed code. Therefore, the m_pTransitionFrame is stable. Except that it isn't. The return-to- +// managed sequence will temporarily overwrite the m_pTransitionFrame to be 0. As a result, we need to cache +// the non-zero m_pTransitionFrame value that we saw during suspend so that stackwalks can read this value +// without concern of sometimes reading a 0, as would be the case if they read m_pTransitionFrame directly. +// +// Returns true if it sucessfully cached the transition frame (i.e. the thread was in unmanaged). +// Returns false otherwise. +// +bool Thread::CacheTransitionFrameForSuspend() +{ + if (m_pCachedTransitionFrame != NULL) + return true; + + PTR_VOID temp = m_pTransitionFrame; // volatile read + if (temp == NULL) + return false; + + m_pCachedTransitionFrame = temp; + return true; +} + +void Thread::ResetCachedTransitionFrame() +{ + // @TODO: I don't understand this assert because ResumeAllThreads is clearly written + // to be reseting other threads' cached transition frames. + + //ASSERT((ThreadStore::GetCurrentThreadIfAvailable() == this) || + // (m_pCachedTransitionFrame != NULL)); + m_pCachedTransitionFrame = NULL; +} + +// This function simulates a PInvoke transition using a frame pointer from somewhere further up the stack that +// was passed in via the m_pHackPInvokeTunnel field. It is used to allow us to grandfather-in the set of GC +// code that runs in cooperative mode without having to rewrite it in managed code. The result is that the +// code that calls into this special mode must spill preserved registeres as if it's going to PInvoke, but +// record its transition frame pointer in m_pHackPInvokeTunnel and leave the thread in the SS_ManagedRunning +// state. Later on, when this function is called, we effect the state transition to 'unmanaged' using the +// previously setup transtion frame. +void Thread::HackEnablePreemptiveMode() +{ + ASSERT(ThreadStore::GetCurrentThread() == this); + ASSERT(m_pHackPInvokeTunnel != NULL); + + Unhijack(); + + LeaveRendezVous(m_pHackPInvokeTunnel); +} + +void Thread::HackDisablePreemptiveMode() +{ + ASSERT(ThreadStore::GetCurrentThread() == this); + + bool success = false; + + do + { + success = TryReturnRendezVous(m_pHackPInvokeTunnel); + } + while (!success); +} +#endif // !DACCESS_COMPILE + +bool Thread::IsCurrentThreadInCooperativeMode() +{ +#ifndef DACCESS_COMPILE + ASSERT(ThreadStore::GetCurrentThread() == this); +#endif // !DACCESS_COMPILE + return (m_pTransitionFrame == NULL); +} + + + +PTR_UInt8 Thread::GetTEB() +{ + return m_pTEB; +} + +#ifndef DACCESS_COMPILE +void Thread::SetThreadStressLog(void * ptsl) +{ + m_pThreadStressLog = ptsl; +} +#endif // DACCESS_COMPILE + +PTR_VOID Thread::GetThreadStressLog() const +{ + return m_pThreadStressLog; +} + +#if defined(FEATURE_GC_STRESS) & !defined(DACCESS_COMPILE) +void Thread::SetRandomSeed(UInt32 seed) +{ + ASSERT(!IsStateSet(TSF_IsRandSeedSet)); + m_uRand = seed; + SetState(TSF_IsRandSeedSet); +} + +// Generates pseudo random numbers in the range [0, 2^31) +// using only multiplication and addition +UInt32 Thread::NextRand() +{ + // Uses Carta's algorithm for Park-Miller's PRNG: + // x_{k+1} = 16807 * x_{k} mod (2^31-1) + + UInt32 hi,lo; + + // (high word of seed) * 16807 - at most 31 bits + hi = 16807 * (m_uRand >> 16); + // (low word of seed) * 16807 - at most 31 bits + lo = 16807 * (m_uRand & 0xFFFF); + + // Proof that below operations (multiplication and addition only) + // are equivalent to the original formula: + // x_{k+1} = 16807 * x_{k} mod (2^31-1) + // We denote hi2 as the low 15 bits in hi, + // and hi1 as the remaining 16 bits in hi: + // (hi * 2^16 + lo) mod (2^31-1) = + // ((hi1 * 2^15 + hi2) * 2^16 + lo) mod (2^31-1) = + // ( hi1 * 2^31 + hi2 * 2^16 + lo) mod (2^31-1) = + // ( hi1 * (2^31-1) + hi1 + hi2 * 2^16 + lo) mod (2^31-1) = + // ( hi2 * 2^16 + hi1 + lo ) mod (2^31-1) + + // lo + (hi2 * 2^16) + lo += (hi & 0x7FFF) << 16; + // lo + (hi2 * 2^16) + hi1 + lo += (hi >> 15); + // modulo (2^31-1) + if (lo > 0x7fffFFFF) + lo -= 0x7fffFFFF; + + m_uRand = lo; + + return m_uRand; +} + +bool Thread::IsRandInited() +{ + return IsStateSet(TSF_IsRandSeedSet); +} +#endif // FEATURE_GC_STRESS & !DACCESS_COMPILE + +PTR_ExInfo Thread::GetCurExInfo() +{ + ValidateExInfoStack(); + return m_pExInfoStackHead; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef DACCESS_COMPILE + +void Thread::Construct() +{ + C_ASSERT(OFFSETOF__Thread__m_pTransitionFrame == + (offsetof(Thread, m_pTransitionFrame))); + + m_numDynamicTypesTlsCells = 0; + m_pDynamicTypesTlsCells = NULL; + + // NOTE: We do not explicitly defer to the GC implementation to initialize the alloc_context. The + // alloc_context will be initialized to 0 via the static initialization of tls_CurrentThread. If the + // alloc_context ever needs different initialization, a matching change to the tls_CurrentThread + // static initialization will need to be made. + + m_uPalThreadId = PalGetCurrentThreadId(); + + HANDLE curProcessPseudo = PalGetCurrentProcess(); + HANDLE curThreadPseudo = PalGetCurrentThread(); + + // This can fail! Users of m_hPalThread must be able to handle INVALID_HANDLE_VALUE!! + PalDuplicateHandle(curProcessPseudo, curThreadPseudo, curProcessPseudo, &m_hPalThread, + 0, // ignored + FALSE, // inherit + DUPLICATE_SAME_ACCESS); + + if (!PalGetMaximumStackBounds(&m_pStackLow, &m_pStackHigh)) + RhFailFast(); + + m_pTEB = PalNtCurrentTeb(); + +#ifdef STRESS_LOG + if (StressLog::StressLogOn(~0ul, 0)) + m_pThreadStressLog = StressLog::CreateThreadStressLog(this); +#endif // STRESS_LOG +} + +bool Thread::IsInitialized() +{ + return (m_ThreadStateFlags != TSF_Unknown); +} + +#endif // !DACCESS_COMPILE + +alloc_context * Thread::GetAllocContext() // @TODO: I would prefer to not expose this in this way +{ + return dac_cast(dac_cast(this) + offsetof(Thread, m_rgbAllocContextBuffer)); +} + + +// ----------------------------------------------------------------------------------------------------------- +// LEGACY APIs: do not use except from GC itself +// + +bool Thread::PreemptiveGCDisabled() +{ + return IsCurrentThreadInCooperativeMode(); +} + +void Thread::EnablePreemptiveGC() +{ +#ifndef DACCESS_COMPILE + HackEnablePreemptiveMode(); +#endif +} + +void Thread::DisablePreemptiveGC() +{ +#ifndef DACCESS_COMPILE + HackDisablePreemptiveMode(); +#endif +} + +#ifndef DACCESS_COMPILE +void Thread::PulseGCMode() +{ + HackEnablePreemptiveMode(); + HackDisablePreemptiveMode(); +} + +void Thread::SetGCSpecial(bool isGCSpecial) +{ + if (isGCSpecial) + SetState(TSF_IsGcSpecialThread); + else + ClearState(TSF_IsGcSpecialThread); +} + +bool Thread::IsGCSpecial() +{ + return IsStateSet(TSF_IsGcSpecialThread); +} + +bool Thread::CatchAtSafePoint() +{ + // This is only called by the GC on a background GC worker thread that's explicitly interested in letting + // a foreground GC proceed at that point. So it's always safe to return true. + ASSERT(IsGCSpecial()); + return true; +} +#endif // !DACCESS_COMPILE + +// END LEGACY APIs +// ----------------------------------------------------------------------------------------------------------- + +#ifndef DACCESS_COMPILE + +UInt32 Thread::GetPalThreadId() +{ + return m_uPalThreadId; +} + +void Thread::Destroy() +{ + if (m_hPalThread != INVALID_HANDLE_VALUE) + PalCloseHandle(m_hPalThread); + + if (m_pDynamicTypesTlsCells != NULL) + { + for (UInt32 i = 0; i < m_numDynamicTypesTlsCells; i++) + { + if (m_pDynamicTypesTlsCells[i] != NULL) + delete[] m_pDynamicTypesTlsCells[i]; + } + delete[] m_pDynamicTypesTlsCells; + } + + RedhawkGCInterface::ReleaseAllocContext(GetAllocContext()); + +#if _DEBUG + memset(this, 0x06, sizeof(this)); +#endif // _DEBUG + + // Thread::Destroy is called when the thread's "home" fiber dies. We mark the thread as "detached" here + // so that we can validate, in our DLL_THREAD_DETACH handler, that the thread was already destroyed at that + // point. + SetDetached(); + + // @TODO: !FEATURE_DECLSPEC_THREAD: delete TLS buffer +} + +void Thread::GcScanRoots(void * pfnEnumCallback, void * pvCallbackData) +{ + StackFrameIterator frameIterator(this, GetTransitionFrame()); + GcScanRootsWorker(pfnEnumCallback, pvCallbackData, frameIterator); +} + +#endif // !DACCESS_COMPILE + +#ifdef DACCESS_COMPILE +// A trivial wrapper that unpacks the ScanCallbackData and calls the callback provided to GcScanRoots +void GcScanRootsCallbackWrapper(PTR_RtuObjectRef ppObject, Thread::ScanCallbackData* callbackData, UInt32 flags) +{ + callbackData->pfnUserCallback(ppObject, callbackData->token, flags); +} + +bool Thread::GcScanRoots(GcScanRootsCallbackFunc * pfnEnumCallback, void * token, PTR_PAL_LIMITED_CONTEXT pInitialContext) +{ + ScanCallbackData callbackDataWrapper; + callbackDataWrapper.thread_under_crawl = this; + callbackDataWrapper.promotion = true; + callbackDataWrapper.token = token; + callbackDataWrapper.pfnUserCallback = pfnEnumCallback; + //When debugging we might be trying to enumerate with or without a transition frame + //on top of the stack. If there is one use it, otherwise the debugger provides a set of initial registers + //to use. + PTR_VOID pTransitionFrame = GetTransitionFrame(); + if(pTransitionFrame != NULL) + { + StackFrameIterator frameIterator(this, GetTransitionFrame()); + GcScanRootsWorker(&GcScanRootsCallbackWrapper, &callbackDataWrapper, frameIterator); + } + else + { + if(pInitialContext == NULL) + return false; + StackFrameIterator frameIterator(this, pInitialContext); + GcScanRootsWorker(&GcScanRootsCallbackWrapper, &callbackDataWrapper, frameIterator); + } + return true; +} +#endif //DACCESS_COMPILE + +void Thread::GcScanRootsWorker(void * pfnEnumCallback, void * pvCallbackData, StackFrameIterator & frameIterator) +{ + PTR_RtuObjectRef pHijackedReturnValue = NULL; + GCRefKind ReturnValueKind = GCRK_Unknown; + + if (frameIterator.GetHijackedReturnValueLocation(&pHijackedReturnValue, &ReturnValueKind)) + { + RedhawkGCInterface::EnumGcRef(pHijackedReturnValue, ReturnValueKind, pfnEnumCallback, pvCallbackData); + } + +#ifndef DACCESS_COMPILE + if (GetRuntimeInstance()->IsConservativeStackReportingEnabled()) + { + if (frameIterator.IsValid()) + { + PTR_RtuObjectRef pLowerBound = dac_cast(frameIterator.GetRegisterSet()->GetSP()); + PTR_RtuObjectRef pUpperBound = dac_cast(m_pStackHigh); + RedhawkGCInterface::EnumGcRefsInRegionConservatively( + pLowerBound, + pUpperBound, + pfnEnumCallback, + pvCallbackData); + } + } + else +#endif // !DACCESS_COMPILE + { + while (frameIterator.IsValid()) + { + frameIterator.CalculateCurrentMethodState(); + + STRESS_LOG1(LF_GCROOTS, LL_INFO1000, "Scanning method %pK\n", frameIterator.GetRegisterSet()->IP); + + RedhawkGCInterface::EnumGcRefs(frameIterator.GetCodeManager(), + frameIterator.GetMethodInfo(), + frameIterator.GetCodeOffset(), + frameIterator.GetRegisterSet(), + pfnEnumCallback, + pvCallbackData); + + frameIterator.Next(); + + // Iterating to the next frame may reveal a stack range we need to report conservatively (every + // pointer aligned value that looks like it might be a GC reference is reported as a pinned interior + // reference). This occurs in an edge case where a managed method whose signature the runtime is not + // aware of calls into the runtime which subsequently calls back out into managed code (allowing the + // possibility of a garbage collection). This can happen in certain interface invocation slow paths for + // instance. Since the original managed call may have passed GC references which are unreported by + // any managed method on the stack at the time of the GC we identify (again conservatively) the range + // of the stack that might contain these references and report everything. Since it should be a very + // rare occurance indeed that we actually have to do this this, it's considered a better trade-off + // than storing signature metadata for every potential callsite of the type described above. + // + // Due to the way this situation is detected and the stack range calculated, we will determine the + // stack range to report one frame later than you might expect (as we iterate to the method that + // called the method that called into the runtime). As such we'll never have a range to report for the + // first frame on the stack and we might have one to report after we've reached the base of the stack + // (i.e. when frameIterator.IsValid() == false). Hence the placement of this check. + if (frameIterator.HasStackRangeToReportConservatively()) + { + PTR_RtuObjectRef pLowerBound; + PTR_RtuObjectRef pUpperBound; + frameIterator.GetStackRangeToReportConservatively(&pLowerBound, &pUpperBound); + RedhawkGCInterface::EnumGcRefsInRegionConservatively(pLowerBound, + pUpperBound, + pfnEnumCallback, + pvCallbackData); + } + } + } + + // ExInfos hold exception objects that are not reported by anyone else. In fact, sometimes they are in + // logically dead parts of the stack that the typical GC stackwalk skips. (This happens in the case where + // one exception dispatch supersceded a previous one.) We keep them alive as long as they are in the + // ExInfo chain to aid in post-mortem debugging. SOS will access them through the DAC and the exported + // API, RhGetExceptionsForCurrentThread, will access them at runtime to gather additional information to + // add to a dump file during FailFast. + for (PTR_ExInfo curExInfo = GetCurExInfo(); curExInfo != NULL; curExInfo = curExInfo->m_pPrevExInfo) + { + PTR_RtuObjectRef pExceptionObj = dac_cast(&curExInfo->m_exception); + RedhawkGCInterface::EnumGcRef(pExceptionObj, GCRK_Object, pfnEnumCallback, pvCallbackData); + } +} + +#ifndef DACCESS_COMPILE + +EXTERN_C void FASTCALL RhpGcProbeHijackScalar(); +EXTERN_C void FASTCALL RhpGcProbeHijackObject(); +EXTERN_C void FASTCALL RhpGcProbeHijackByref(); + +static void* NormalHijackTargets[3] = +{ + RhpGcProbeHijackScalar, // GCRK_Scalar = 0, + RhpGcProbeHijackObject, // GCRK_Object = 1, + RhpGcProbeHijackByref // GCRK_Byref = 2, +}; + +#ifdef FEATURE_GC_STRESS +EXTERN_C void FASTCALL RhpGcStressHijackScalar(); +EXTERN_C void FASTCALL RhpGcStressHijackObject(); +EXTERN_C void FASTCALL RhpGcStressHijackByref(); + +static void* GcStressHijackTargets[3] = +{ + RhpGcStressHijackScalar, // GCRK_Scalar = 0, + RhpGcStressHijackObject, // GCRK_Object = 1, + RhpGcStressHijackByref // GCRK_Byref = 2, +}; +#endif // FEATURE_GC_STRESS + +bool Thread::Hijack() +{ + ASSERT(ThreadStore::GetCurrentThread() == ThreadStore::GetSuspendingThread()); + + ASSERT_MSG(ThreadStore::GetSuspendingThread() != this, "You may not hijack a thread from itself."); + + if (m_hPalThread == INVALID_HANDLE_VALUE) + { + // cannot proceed + return false; + } + + // requires THREAD_SUSPEND_RESUME / THREAD_GET_CONTEXT / THREAD_SET_CONTEXT permissions + + return PalHijack(m_hPalThread, HijackCallback, this) <= 0; +} + +UInt32_BOOL Thread::HijackCallback(HANDLE hThread, PAL_LIMITED_CONTEXT* pThreadContext, void* pCallbackContext) +{ + Thread* pThread = (Thread*) pCallbackContext; + + // + // WARNING: The hijack operation will take a read lock on the RuntimeInstance's module list. + // (This is done to find a Module based on an IP.) Therefore, if the thread we've just + // suspended owns the write lock on the module list, we'll deadlock with it when we try to + // take the read lock below. So we must attempt a non-blocking acquire of the read lock + // early and fail the hijack if we can't get it. This will cause us to simply retry later. + // + if (GetRuntimeInstance()->m_ModuleListLock.DangerousTryPulseReadLock()) + { + if (pThread->CacheTransitionFrameForSuspend()) + { + // IMPORTANT: GetThreadContext should not be trusted arbitrarily. We are careful here to recheck + // the thread's state flag that indicates whether or not it has made it to unmanaged code. If + // it has reached unmanaged code (even our own wait helper routines), then we cannot trust the + // context returned by it. This is due to various races that occur updating the reported context + // during syscalls. + return TRUE; + } + else + { + return pThread->InternalHijack(pThreadContext, NormalHijackTargets) ? TRUE : FALSE; + } + } + + return FALSE; +} + +#ifdef FEATURE_GC_STRESS +// This is a helper called from RhpHijackForGcStress which will place a GC Stress +// hijack on this thread's call stack. This is never called from another thread. +// static +void Thread::HijackForGcStress(PAL_LIMITED_CONTEXT * pSuspendCtx) +{ + Thread * pCurrentThread = ThreadStore::GetCurrentThread(); + + // don't hijack for GC stress if we're in a "no GC stress" region + if (pCurrentThread->IsSuppressGcStressSet()) + return; + + RuntimeInstance * pInstance = GetRuntimeInstance(); + + UIntNative ip = pSuspendCtx->GetIp(); + + bool bForceGC = g_pRhConfig->GetGcStressThrottleMode() == 0; + // we enable collecting statistics by callsite even for stochastic-only + // stress mode. this will force a stack walk, but it's worthwhile for + // collecting data (we only actually need the IP when + // (g_pRhConfig->GetGcStressThrottleMode() & 1) != 0) + if (!bForceGC) + { + StackFrameIterator sfi(pCurrentThread, pSuspendCtx); + if (sfi.IsValid()) + { + pCurrentThread->Unhijack(); + sfi.CalculateCurrentMethodState(); + // unwind to method below the one whose epilog set up the hijack + sfi.Next(); + if (sfi.IsValid()) + { + ip = sfi.GetRegisterSet()->GetIP(); + } + } + } + if (bForceGC || pInstance->ShouldHijackCallsiteForGcStress(ip)) + { + pCurrentThread->InternalHijack(pSuspendCtx, GcStressHijackTargets); + } +} +#endif // FEATURE_GC_STRESS + +// This function is called in one of two scenarios: +// 1) from a thread to place a return hijack onto its own stack. This is only done for GC stress cases +// via Thread::HijackForGcStress above. +// 2) from another thread to place a return hijack onto this thread's stack. In this case the target +// thread is OS suspended someplace in managed code. The only constraint on the suspension is that the +// stack be crawlable enough to yield the location of the return address. +bool Thread::InternalHijack(PAL_LIMITED_CONTEXT * pSuspendCtx, void* HijackTargets[3]) +{ + bool fSuccess = false; + + if (IsStateSet(TSF_DoNotTriggerGc)) + return false; + + StackFrameIterator frameIterator(this, pSuspendCtx); + + if (frameIterator.IsValid()) + { + CrossThreadUnhijack(); + + frameIterator.CalculateCurrentMethodState(); + + frameIterator.GetCodeManager()->UnsynchronizedHijackMethodLoops(frameIterator.GetMethodInfo()); + + PTR_PTR_VOID ppvRetAddrLocation; + GCRefKind retValueKind; + + if (frameIterator.GetCodeManager()->GetReturnAddressHijackInfo(frameIterator.GetMethodInfo(), + frameIterator.GetCodeOffset(), + frameIterator.GetRegisterSet(), + &ppvRetAddrLocation, + &retValueKind)) + { + void* pvRetAddr = *ppvRetAddrLocation; + ASSERT(ppvRetAddrLocation != NULL); + ASSERT(pvRetAddr != NULL); + + ASSERT(StackFrameIterator::IsValidReturnAddress(pvRetAddr)); + + m_ppvHijackedReturnAddressLocation = ppvRetAddrLocation; + m_pvHijackedReturnAddress = pvRetAddr; + *ppvRetAddrLocation = HijackTargets[retValueKind]; + + fSuccess = true; + } + } + + STRESS_LOG3(LF_STACKWALK, LL_INFO10000, "InternalHijack: TgtThread = %x, IP = %p, result = %d\n", + GetPalThreadId(), pSuspendCtx->GetIp(), fSuccess); + + return fSuccess; +} + +// This is the standard Unhijack, which is only allowed to be called on your own thread. +// Note that all the asm-implemented Unhijacks should also only be operating on their +// own thread. +void Thread::Unhijack() +{ + ASSERT(ThreadStore::GetCurrentThread() == this); + UnhijackWorker(); +} + +// This unhijack routine is only called from Thread::InternalHijack() to undo a possibly existing +// hijack before placing a new one. Although there are many code sequences (here and in asm) to +// perform an unhijack operation, they will never execute concurrently. A thread may unhijack itself +// at any time so long as it does so from unmanaged code. This ensures that another thread will not +// suspend it and attempt to unhijack it, since we only suspend threads that are executing managed +// code. +void Thread::CrossThreadUnhijack() +{ + ASSERT((ThreadStore::GetCurrentThread() == this) || DebugIsSuspended()); + UnhijackWorker(); +} + +// This is the hijack worker routine which merely implements the hijack mechanism. +// DO NOT USE DIRECTLY. Use Unhijack() or CrossThreadUnhijack() instead. +void Thread::UnhijackWorker() +{ + if (m_pvHijackedReturnAddress == NULL) + { + ASSERT(m_ppvHijackedReturnAddressLocation == NULL); + return; + } + + // Restore the original return address. + ASSERT(m_ppvHijackedReturnAddressLocation != NULL); + *m_ppvHijackedReturnAddressLocation = m_pvHijackedReturnAddress; + + // Clear the hijack state. + m_ppvHijackedReturnAddressLocation = NULL; + m_pvHijackedReturnAddress = NULL; +} + +#if _DEBUG +bool Thread::DebugIsSuspended() +{ + ASSERT(ThreadStore::GetCurrentThread() != this); +#if 0 + PalSuspendThread(m_hPalThread); + UInt32 suspendCount = PalResumeThread(m_hPalThread); + return (suspendCount > 0); +#else + // @TODO: I don't trust the above implementation, so I want to implement this myself + // by marking the thread state as "yes, we suspended it" and checking that state here. + return true; +#endif +} +#endif + +// @TODO: it would be very, very nice if we did not have to bleed knowledge of hijacking +// and hijack state to other components in the runtime. For now, these are only used +// when getting EH info during exception dispatch. We should find a better way to encapsulate +// this. +bool Thread::IsHijacked() +{ + // Note: this operation is only valid from the current thread. If one thread invokes + // this on another then it may be racing with other changes to the thread's hijack state. + ASSERT(ThreadStore::GetCurrentThread() == this); + + return m_pvHijackedReturnAddress != NULL; +} + +// +// WARNING: This method must ONLY be called during stackwalks when we believe that all threads are +// synchronized and there is no other thread racing with us trying to apply hijacks. +// +bool Thread::DangerousCrossThreadIsHijacked() +{ + // If we have a CachedTransitionFrame available, then we're in the proper state. Otherwise, this method + // was called from an improper state. + ASSERT(GetTransitionFrame() != NULL); + return m_pvHijackedReturnAddress != NULL; +} + +void * Thread::GetHijackedReturnAddress() +{ + // Note: this operation is only valid from the current thread. If one thread invokes + // this on another then it may be racing with other changes to the thread's hijack state. + ASSERT(IsHijacked()); + ASSERT(ThreadStore::GetCurrentThread() == this); + + return m_pvHijackedReturnAddress; +} + +void * Thread::GetUnhijackedReturnAddress(void ** ppvReturnAddressLocation) +{ + ASSERT(ThreadStore::GetCurrentThread() == this); + + void * pvReturnAddress; + if (m_ppvHijackedReturnAddressLocation == ppvReturnAddressLocation) + pvReturnAddress = m_pvHijackedReturnAddress; + else + pvReturnAddress = *ppvReturnAddressLocation; + + ASSERT(NULL != GetRuntimeInstance()->FindCodeManagerByAddress(pvReturnAddress)); + return pvReturnAddress; +} + +void Thread::SetState(ThreadStateFlags flags) +{ + PalInterlockedOr(&m_ThreadStateFlags, flags); +} + +void Thread::ClearState(ThreadStateFlags flags) +{ + PalInterlockedAnd(&m_ThreadStateFlags, ~flags); +} + +bool Thread::IsStateSet(ThreadStateFlags flags) +{ + return ((m_ThreadStateFlags & flags) == (UInt32) flags); +} + +bool Thread::IsSuppressGcStressSet() +{ + return IsStateSet(TSF_SuppressGcStress); +} + +void Thread::SetSuppressGcStress() +{ + ASSERT(!IsStateSet(TSF_SuppressGcStress)); + SetState(TSF_SuppressGcStress); +} + +void Thread::ClearSuppressGcStress() +{ + ASSERT(IsStateSet(TSF_SuppressGcStress)); + ClearState(TSF_SuppressGcStress); +} + +#endif //!DACCESS_COMPILE + +bool Thread::IsWithinStackBounds(PTR_VOID p) +{ + ASSERT((m_pStackLow != 0) && (m_pStackHigh != 0)); + return (m_pStackLow <= p) && (p < m_pStackHigh); +} + +#ifndef DACCESS_COMPILE +#ifdef FEATURE_GC_STRESS +#ifdef _X86_ // the others are implemented in assembly code to avoid trashing the argument registers +EXTERN_C void FASTCALL RhpSuppressGcStress() +{ + ThreadStore::GetCurrentThread()->SetSuppressGcStress(); +} +#endif // _X86_ + +EXTERN_C void FASTCALL RhpUnsuppressGcStress() +{ + ThreadStore::GetCurrentThread()->ClearSuppressGcStress(); +} +#else +EXTERN_C void FASTCALL RhpSuppressGcStress() +{ +} +EXTERN_C void FASTCALL RhpUnsuppressGcStress() +{ +} +#endif // FEATURE_GC_STRESS + +EXTERN_C void FASTCALL RhpPInvokeWaitEx(Thread * pThread) +{ +#ifdef _DEBUG + // PInvoke must not trash win32 last error. The wait operations below never should trash the last error, + // but we keep some debug-time checks around to guard against future changes to the code. In general, the + // wait operations will call out to Win32 to do the waiting, but the API used will only modify the last + // error in an error condition, in which case we will fail fast. + UInt32 uLastErrorOnEntry = PalGetLastError(); +#endif // _DEBUG + + pThread->Unhijack(); + GetThreadStore()->WaitForSuspendComplete(); + + ASSERT_MSG(uLastErrorOnEntry == PalGetLastError(), "Unexpectedly trashed last error on PInvoke path!"); +} + +EXTERN_C void FASTCALL RhpPInvokeReturnWaitEx(Thread * pThread) +{ +#ifdef _DEBUG + // PInvoke must not trash win32 last error. The wait operations below never should trash the last error, + // but we keep some debug-time checks around to guard against future changes to the code. In general, the + // wait operations will call out to Win32 to do the waiting, but the API used will only modify the last + // error in an error condition, in which case we will fail fast. + UInt32 uLastErrorOnEntry = PalGetLastError(); +#endif // _DEBUG + + pThread->Unhijack(); + if (!pThread->IsDoNotTriggerGcSet()) + { + RedhawkGCInterface::WaitForGCCompletion(); + } + + ASSERT_MSG(uLastErrorOnEntry == PalGetLastError(), "Unexpectedly trashed last error on PInvoke path!"); +} + +void Thread::PushExInfo(ExInfo * pExInfo) +{ + ValidateExInfoStack(); + + pExInfo->m_pPrevExInfo = m_pExInfoStackHead; + m_pExInfoStackHead = pExInfo; +} + +void Thread::ValidateExInfoPop(ExInfo * pExInfo, void * limitSP) +{ +#ifdef _DEBUG + ValidateExInfoStack(); + ASSERT_MSG(pExInfo == m_pExInfoStackHead, "not popping the head element"); + pExInfo = pExInfo->m_pPrevExInfo; + + while (pExInfo && pExInfo < limitSP) + { + ASSERT_MSG(pExInfo->m_kind & EK_SuperscededFlag, "popping a non-supersceded ExInfo"); + pExInfo = pExInfo->m_pPrevExInfo; + } +#endif // _DEBUG +} + +bool Thread::IsDoNotTriggerGcSet() +{ + return IsStateSet(TSF_DoNotTriggerGc); +} + +void Thread::SetDoNotTriggerGc() +{ + ASSERT(!IsStateSet(TSF_DoNotTriggerGc)); + SetState(TSF_DoNotTriggerGc); +} + +void Thread::ClearDoNotTriggerGc() +{ + // Allowing unmatched clears simplifies the EH dispatch code, so we do not assert anything here. + ClearState(TSF_DoNotTriggerGc); +} + +bool Thread::IsDetached() +{ + return IsStateSet(TSF_Detached); +} + +void Thread::SetDetached() +{ + ASSERT(!IsStateSet(TSF_Detached)); + SetState(TSF_Detached); +} + +#endif // !DACCESS_COMPILE + +void Thread::ValidateExInfoStack() +{ +#ifndef DACCESS_COMPILE +#ifdef _DEBUG + ExInfo temp; + + ExInfo* pCur = m_pExInfoStackHead; + while (pCur) + { + ASSERT_MSG((this != ThreadStore::GetCurrentThread()) || (pCur > &temp), "an entry in the ExInfo chain points into dead stack"); + ASSERT_MSG(pCur < m_pStackHigh, "an entry in the ExInfo chain isn't on this stack"); + pCur = pCur->m_pPrevExInfo; + } +#endif // _DEBUG +#endif // !DACCESS_COMPILE +} + + + +// Retrieve the start of the TLS storage block allocated for the given thread for a specific module identified +// by the TLS slot index allocated to that module and the offset into the OS allocated block at which +// Redhawk-specific data is stored. +PTR_UInt8 Thread::GetThreadLocalStorage(UInt32 uTlsIndex, UInt32 uTlsStartOffset) +{ +#if 0 + return (*(UInt8***)(m_pTEB + OFFSETOF__TEB__ThreadLocalStoragePointer))[uTlsIndex] + uTlsStartOffset; +#else + return (*dac_cast(dac_cast(m_pTEB) + OFFSETOF__TEB__ThreadLocalStoragePointer))[uTlsIndex] + uTlsStartOffset; +#endif +} + +PTR_UInt8 Thread::GetThreadLocalStorageForDynamicType(UInt32 uTlsTypeOffset) +{ + // Note: When called from GC root enumeration, no changes can be made by the AllocateThreadLocalStorageForDynamicType to + // the 2 variables accessed here because AllocateThreadLocalStorageForDynamicType is called in cooperative mode. + + uTlsTypeOffset &= ~DYNAMIC_TYPE_TLS_OFFSET_FLAG; + return dac_cast(uTlsTypeOffset < m_numDynamicTypesTlsCells ? m_pDynamicTypesTlsCells[uTlsTypeOffset] : NULL); +} + +#ifndef DACCESS_COMPILE +PTR_UInt8 Thread::AllocateThreadLocalStorageForDynamicType(UInt32 uTlsTypeOffset, UInt32 tlsStorageSize, UInt32 numTlsCells) +{ + uTlsTypeOffset &= ~DYNAMIC_TYPE_TLS_OFFSET_FLAG; + + if (m_pDynamicTypesTlsCells == NULL || m_numDynamicTypesTlsCells <= uTlsTypeOffset) + { + // Keep at least a 2x grow so that we don't have to reallocate everytime a new type with TLS statics is created + if (numTlsCells < 2 * m_numDynamicTypesTlsCells) + numTlsCells = 2 * m_numDynamicTypesTlsCells; + + PTR_UInt8* pTlsCells = new PTR_UInt8[numTlsCells]; + if (pTlsCells == NULL) + return NULL; + + memset(&pTlsCells[m_numDynamicTypesTlsCells], 0, sizeof(PTR_UInt8) * (numTlsCells - m_numDynamicTypesTlsCells)); + + if (m_pDynamicTypesTlsCells != NULL) + { + memcpy(pTlsCells, m_pDynamicTypesTlsCells, sizeof(PTR_UInt8) * m_numDynamicTypesTlsCells); + delete[] m_pDynamicTypesTlsCells; + } + + m_pDynamicTypesTlsCells = pTlsCells; + m_numDynamicTypesTlsCells = numTlsCells; + } + + ASSERT(uTlsTypeOffset < m_numDynamicTypesTlsCells); + + if (m_pDynamicTypesTlsCells[uTlsTypeOffset] == NULL) + { + UInt8* pTlsStorage = new UInt8[tlsStorageSize]; + if (pTlsStorage == NULL) + return NULL; + + // Initialize storage to 0's before returning it + memset(pTlsStorage, 0, tlsStorageSize); + + m_pDynamicTypesTlsCells[uTlsTypeOffset] = pTlsStorage; + } + + return m_pDynamicTypesTlsCells[uTlsTypeOffset]; +} + +EXTERN_C REDHAWK_API UInt32 __cdecl RhCompatibleReentrantWaitAny(UInt32_BOOL alertable, UInt32 timeout, UInt32 count, HANDLE* pHandles) +{ + return PalCompatibleWaitAny(alertable, timeout, count, pHandles, /*allowReentrantWait:*/ TRUE); +} + + +EXTERN_C volatile UInt32 RhpTrapThreads; + +bool Thread::TryFastReversePInvoke(ReversePInvokeFrame * pFrame) +{ + // Do we need to attach the thread? + if (!IsStateSet(TSF_Attached)) + return false; // thread is not attached + + // If the thread is already in cooperative mode, this is a bad transition that will be a fail fast unless we are in + // a do not trigger mode. The exception to the rule allows us to have [NativeCallable] methods that are called via + // the "restricted GC callouts" as well as from native, which is necessary because the methods are CCW vtable + // methods on interfaces passed to native. + if (IsCurrentThreadInCooperativeMode() && !IsStateSet(TSF_DoNotTriggerGc)) + return false; // bad transition + + // save the previous transition frame + pFrame->m_savedPInvokeTransitionFrame = m_pTransitionFrame; + + // set our mode to cooperative + m_pTransitionFrame = NULL; + + // now check if we need to trap the thread + if (RhpTrapThreads != 0) + { + // put the previous frame back (sets us back to preemptive mode) + m_pTransitionFrame = pFrame->m_savedPInvokeTransitionFrame; + return false; // need to trap the thread + } + + return true; +} + +EXTERN_C void REDHAWK_CALLCONV RhpReversePInvokeBadTransition(); + +void Thread::ReversePInvoke(ReversePInvokeFrame * pFrame) +{ + if (!IsStateSet(TSF_Attached)) + ThreadStore::AttachCurrentThread(); + + // If the thread is already in cooperative mode, this is a bad transition that will be a fail fast unless we are in + // a do not trigger mode. The exception to the rule allows us to have [NativeCallable] methods that are called via + // the "restricted GC callouts" as well as from native, which is necessary because the methods are CCW vtable + // methods on interfaces passed to native. + if (IsCurrentThreadInCooperativeMode() && !IsStateSet(TSF_DoNotTriggerGc)) + RhpReversePInvokeBadTransition(); + + for (;;) + { + // save the previous transition frame + pFrame->m_savedPInvokeTransitionFrame = m_pTransitionFrame; + + // set our mode to cooperative + m_pTransitionFrame = NULL; + + // now check if we need to trap the thread + if (RhpTrapThreads == 0) + { + break; + } + else + { + // put the previous frame back (sets us back to preemptive mode) + m_pTransitionFrame = pFrame->m_savedPInvokeTransitionFrame; + // wait + RhpPInvokeReturnWaitEx(this); + // now try again + } + } +} + +void Thread::ReversePInvokeReturn(ReversePInvokeFrame * pFrame) +{ + m_pTransitionFrame = pFrame->m_savedPInvokeTransitionFrame; + if (RhpTrapThreads != 0) + { + RhpPInvokeWaitEx(this); + } +} + +#endif // !DACCESS_COMPILE diff --git a/src/Native/Runtime/thread.h b/src/Native/Runtime/thread.h new file mode 100644 index 00000000000..939fa218ceb --- /dev/null +++ b/src/Native/Runtime/thread.h @@ -0,0 +1,257 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "forward_declarations.h" + +struct alloc_context; +class RuntimeInstance; +class ThreadStore; +class CLREventStatic; +class Thread; + +#if defined(_X86_) || defined(_ARM_) +# ifdef FEATURE_SVR_GC +# define SIZEOF_ALLOC_CONTEXT 40 +# else // FEATURE_SVR_GC +# define SIZEOF_ALLOC_CONTEXT 28 +# endif // FEATURE_SVR_GC +#elif defined(_AMD64_) +# ifdef FEATURE_SVR_GC +# define SIZEOF_ALLOC_CONTEXT 56 +# else // FEATURE_SVR_GC +# define SIZEOF_ALLOC_CONTEXT 40 +# endif // FEATURE_SVR_GC +#endif // _AMD64_ + +#define TOP_OF_STACK_MARKER ((PTR_VOID)(UIntNative)(IntNative)-1) + +#define DYNAMIC_TYPE_TLS_OFFSET_FLAG 0x80000000 + + +enum SyncRequestResult +{ + TryAgain, + SuccessUnmanaged, + SuccessManaged, +}; + +typedef DPTR(PAL_LIMITED_CONTEXT) PTR_PAL_LIMITED_CONTEXT; + +struct ExInfo; +typedef DPTR(ExInfo) PTR_ExInfo; + + +// also defined in ExceptionHandling.cs, layouts must match +struct ExInfo +{ + + PTR_ExInfo m_pPrevExInfo; + PTR_PAL_LIMITED_CONTEXT m_pExContext; + PTR_Object m_exception; // actual object reference, specially reported by GcScanRootsWorker + ExKind m_kind; + UInt8 m_passNumber; + UInt32 m_idxCurClause; + StackFrameIterator m_frameIter; + volatile void* m_notifyDebuggerSP; +}; + + + +struct ThreadBuffer +{ + UInt8 m_rgbAllocContextBuffer[SIZEOF_ALLOC_CONTEXT]; + UInt32 volatile m_ThreadStateFlags; // see Thread::ThreadStateFlags enum +#if DACCESS_COMPILE + PTR_VOID m_pTransitionFrame; +#else + PTR_VOID volatile m_pTransitionFrame; +#endif + PTR_VOID m_pHackPInvokeTunnel; // see Thread::HackEnablePreemptiveMode + PTR_VOID m_pCachedTransitionFrame; + PTR_Thread m_pNext; // used by ThreadStore's SList + HANDLE m_hPalThread; // WARNING: this may legitimately be INVALID_HANDLE_VALUE + void ** m_ppvHijackedReturnAddressLocation; + void * m_pvHijackedReturnAddress; + PTR_ExInfo m_pExInfoStackHead; + PTR_VOID m_pStackLow; + PTR_VOID m_pStackHigh; + PTR_UInt8 m_pTEB; // Pointer to OS TEB structure for this thread + UInt32 m_uPalThreadId; // @TODO: likely debug-only + PTR_VOID m_pThreadStressLog; // pointer to head of thread's StressLogChunks +#ifdef FEATURE_GC_STRESS + UInt32 m_uRand; // current per-thread random number +#endif // FEATURE_GC_STRESS + + // Thread Statics Storage for dynamic types + UInt32 m_numDynamicTypesTlsCells; + PTR_UInt8* m_pDynamicTypesTlsCells; +}; + +struct ReversePInvokeFrame +{ + void* m_savedPInvokeTransitionFrame; + Thread* m_savedThread; +}; + +class Thread : private ThreadBuffer +{ + friend class AsmOffsets; + friend struct DefaultSListTraits; + friend class ThreadStore; + IN_DAC(friend class ClrDataAccess;) + +public: + enum ThreadStateFlags + { + TSF_Unknown = 0x00000000, // Threads are created in this state + TSF_Attached = 0x00000001, // Thread was inited by first U->M transition on this thread + TSF_Detached = 0x00000002, // Thread was detached by DllMain + TSF_SuppressGcStress = 0x00000008, // Do not allow gc stress on this thread, used in DllMain + // ...and on the Finalizer thread + TSF_DoNotTriggerGc = 0x00000010, // Do not allow hijacking of this thread, also intended to + // ...be checked during allocations in debug builds. + TSF_IsGcSpecialThread = 0x00000020, // Set to indicate a GC worker thread used for background GC +#ifdef FEATURE_GC_STRESS + TSF_IsRandSeedSet = 0x00000040, // set to indicate the random number generator for GCStress was inited +#endif // FEATURE_GC_STRESS + }; +private: + + void Construct(); + + void SetState(ThreadStateFlags flags); + void ClearState(ThreadStateFlags flags); + bool IsStateSet(ThreadStateFlags flags); + + static UInt32_BOOL HijackCallback(HANDLE hThread, PAL_LIMITED_CONTEXT* pThreadContext, void* pCallbackContext); + bool InternalHijack(PAL_LIMITED_CONTEXT * pCtx, void* HijackTargets[3]); + + bool CacheTransitionFrameForSuspend(); + void ResetCachedTransitionFrame(); + void CrossThreadUnhijack(); + void UnhijackWorker(); +#ifdef _DEBUG + bool DebugIsSuspended(); +#endif + + // + // SyncState members + // + PTR_VOID GetTransitionFrame(); + // --------------------------------------------------------------------------------------------------- + // Synchronous state transitions -- these must occur on the thread whose state is changing + // + void LeaveRendezVous(void * pTransitionFrame); + bool TryReturnRendezVous(void * pTransitionFrame); + + // begin { // the set of operations used to support unmanaged code running in cooperative mode + void HackEnablePreemptiveMode(); + void HackDisablePreemptiveMode(); + // } end + // ------------------------------------------------------------------------------------------------------- + + void GcScanRootsWorker(void * pfnEnumCallback, void * pvCallbackData, StackFrameIterator & sfIter); + +public: + + + void Destroy(); + + bool IsInitialized(); + + alloc_context * GetAllocContext(); // @TODO: I would prefer to not expose this in this way + UInt32 GetPalThreadId(); + +#ifndef DACCESS_COMPILE + void GcScanRoots(void * pfnEnumCallback, void * pvCallbackData); +#else + + typedef void GcScanRootsCallbackFunc(PTR_RtuObjectRef ppObject, void* token, UInt32 flags); + bool GcScanRoots(GcScanRootsCallbackFunc * pfnCallback, void * token, PTR_PAL_LIMITED_CONTEXT pInitialContext); + + // Ideally we wouldn't need this wrapper, but PromoteCarefully needs access to the + // thread and a promotion field. We aren't assuming the user's token will have this data. + struct ScanCallbackData + { + Thread* thread_under_crawl; // the thread being scanned + bool promotion; // are we emulating the GC promote phase or relocate phase? + // different references are reported for each + void* token; // the callback data passed to GCScanRoots + GcScanRootsCallbackFunc* pfnUserCallback; // the callback passed in to GcScanRoots + }; + +#endif + + bool Hijack(); + void Unhijack(); +#ifdef FEATURE_GC_STRESS + static void HijackForGcStress(PAL_LIMITED_CONTEXT * pCtx); +#endif // FEATURE_GC_STRESS + bool IsHijacked(); + void * GetHijackedReturnAddress(); + void * GetUnhijackedReturnAddress(void** ppvReturnAddressLocation); + bool DangerousCrossThreadIsHijacked(); + + bool IsSuppressGcStressSet(); + void SetSuppressGcStress(); + void ClearSuppressGcStress(); + bool IsWithinStackBounds(PTR_VOID p); + + PTR_UInt8 AllocateThreadLocalStorageForDynamicType(UInt32 uTlsTypeOffset, UInt32 tlsStorageSize, UInt32 numTlsCells); + PTR_UInt8 GetThreadLocalStorageForDynamicType(UInt32 uTlsTypeOffset); + PTR_UInt8 GetThreadLocalStorage(UInt32 uTlsIndex, UInt32 uTlsStartOffset); + PTR_UInt8 GetTEB(); + + void PushExInfo(ExInfo * pExInfo); + void ValidateExInfoPop(ExInfo * pExInfo, void * limitSP); + void ValidateExInfoStack(); + bool IsDoNotTriggerGcSet(); + void SetDoNotTriggerGc(); + void ClearDoNotTriggerGc(); + + bool IsDetached(); + void SetDetached(); + + PTR_VOID GetThreadStressLog() const; +#ifndef DACCESS_COMPILE + void SetThreadStressLog(void * ptsl); +#endif // DACCESS_COMPILE +#ifdef FEATURE_GC_STRESS + void SetRandomSeed(UInt32 seed); + UInt32 NextRand(); + bool IsRandInited(); +#endif // FEATURE_GC_STRESS + PTR_ExInfo GetCurExInfo(); + + bool IsCurrentThreadInCooperativeMode(); + + PTR_VOID GetTransitionFrameForStackTrace(); + + // ------------------------------------------------------------------------------------------------------- + // LEGACY APIs: do not use except from GC itself + // + bool PreemptiveGCDisabled(); + void EnablePreemptiveGC(); + void DisablePreemptiveGC(); + void PulseGCMode(); + void SetGCSpecial(bool isGCSpecial); + bool IsGCSpecial(); + bool CatchAtSafePoint(); + // END LEGACY APIs + // ------------------------------------------------------------------------------------------------------- + + // Nothing to do. + bool HaveExtraWorkForFinalizer() { return false; } + + // We have chosen not to eagerly commit thread stacks. + static bool CommitThreadStack(Thread* pThreadOptional) + { + UNREFERENCED_PARAMETER(pThreadOptional); + return true; + } + + bool TryFastReversePInvoke(ReversePInvokeFrame * pFrame); + void ReversePInvoke(ReversePInvokeFrame * pFrame); + void ReversePInvokeReturn(ReversePInvokeFrame * pFrame); +}; diff --git a/src/Native/Runtime/threadstore.cpp b/src/Native/Runtime/threadstore.cpp new file mode 100644 index 00000000000..17ae10d03a9 --- /dev/null +++ b/src/Native/Runtime/threadstore.cpp @@ -0,0 +1,569 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +#include "common.h" +#ifdef DACCESS_COMPILE +#include "gcrhenv.h" +#endif // DACCESS_COMPILE + +#ifndef DACCESS_COMPILE +#include "commontypes.h" +#include "daccess.h" +#include "commonmacros.h" +#include "palredhawkcommon.h" +#include "palredhawk.h" +#include "assert.h" +#include "static_check.h" +#include "slist.h" +#include "gcrhinterface.h" +#include "varint.h" +#include "regdisplay.h" +#include "stackframeiterator.h" +#include "thread.h" +#include "holder.h" +#include "crst.h" +#include "event.h" +#include "rwlock.h" +#include "threadstore.h" +#include "runtimeinstance.h" +#include "new.h" +#include "ObjectLayout.h" +#include "TargetPtrs.h" +#include "EEType.h" + +#include "slist.inl" +#else +#include "runtimeinstance.h" +#include "slist.inl" +#endif + +EXTERN_C volatile UInt32 RhpTrapThreads = 0; + +GVAL_IMPL_INIT(PTR_Thread, RhpSuspendingThread, 0); + +ThreadStore * GetThreadStore() +{ + return GetRuntimeInstance()->GetThreadStore(); +} + +ThreadStore::Iterator::Iterator() : + m_readHolder(&GetThreadStore()->m_Lock), + m_pCurrentPosition(GetThreadStore()->m_ThreadList.GetHead()) +{ +} + +ThreadStore::Iterator::~Iterator() +{ +} + +PTR_Thread ThreadStore::Iterator::GetNext() +{ + PTR_Thread pResult = m_pCurrentPosition; + if (NULL != pResult) + m_pCurrentPosition = pResult->m_pNext; + return pResult; +} + + +#ifndef DACCESS_COMPILE + + +ThreadStore::ThreadStore() : + m_ThreadList(), + m_Lock() +{ +} + +ThreadStore::~ThreadStore() +{ + // @TODO: For now, the approach will be to cleanup everything we can, even in the face of failure in + // individual operations within this method. We're faced with a difficult situation -- what is the caller + // supposed to do on failure? Wait and try again? Do nothing? We will assume they do nothing and + // attempt to free as many of our resources as we can. If any of those fail, we only leak those parts. + // Whereas if we were to fail on the first operation and then return to the caller without doing anymore, + // we would have leaked much more. + +} + +// static +ThreadStore * ThreadStore::Create(RuntimeInstance * pRuntimeInstance) +{ + NewHolder pNewThreadStore = new ThreadStore(); + if (NULL == pNewThreadStore) + return NULL; + + pNewThreadStore->m_SuspendCompleteEvent.CreateManualEvent(TRUE); + + pNewThreadStore->m_pRuntimeInstance = pRuntimeInstance; + + pNewThreadStore.SuppressRelease(); + return pNewThreadStore; +} + +void ThreadStore::Destroy() +{ + delete this; +} + +#endif //!DACCESS_COMPILE + +//static +PTR_Thread ThreadStore::GetSuspendingThread() +{ + return (RhpSuspendingThread); +} +#ifndef DACCESS_COMPILE + +GPTR_DECL(RuntimeInstance, g_pTheRuntimeInstance); + +extern UInt32 _fls_index; + + +// static +void ThreadStore::AttachCurrentThread(bool fAcquireThreadStoreLock) +{ + // + // step 1: ThreadStore::InitCurrentThread + // step 2: add this thread to the ThreadStore + // + + // The thread has been constructed, during which some data is initialized (like which RuntimeInstance the + // thread belongs to), but it hasn't been added to the thread store because doing so takes a lock, which + // we want to avoid at construction time because the loader lock is held then. + Thread * pAttachingThread = RawGetCurrentThread(); + +#ifndef FEATURE_DECLSPEC_THREAD + if (pAttachingThread == NULL) + pAttachingThread = (Thread *)CreateCurrentThreadBuffer(); +#else + // On CHK build, validate that our GetThread assembly implementation matches the C++ implementation using + // TLS. + CreateCurrentThreadBuffer(); +#endif + + ASSERT(_fls_index != FLS_OUT_OF_INDEXES); + Thread* pThreadFromCurrentFiber = (Thread*)PalFlsGetValue(_fls_index); + + if (pAttachingThread->IsInitialized()) + { + if (pThreadFromCurrentFiber != pAttachingThread) + { + ASSERT_UNCONDITIONALLY("Multiple fibers encountered on a single thread"); + RhFailFast(); + } + + return; + } + + if (pThreadFromCurrentFiber != NULL) + { + ASSERT_UNCONDITIONALLY("Multiple threads encountered from a single fiber"); + RhFailFast(); + } + + // + // Init the thread buffer + // + pAttachingThread->Construct(); + ASSERT(pAttachingThread->m_ThreadStateFlags == Thread::TSF_Unknown); + + ThreadStore* pTS = GetThreadStore(); + ReaderWriterLock::WriteHolder write(&pTS->m_Lock, fAcquireThreadStoreLock); + + // + // Set thread state to be attached + // + ASSERT(pAttachingThread->m_ThreadStateFlags == Thread::TSF_Unknown); + pAttachingThread->m_ThreadStateFlags = Thread::TSF_Attached; + + pTS->m_ThreadList.PushHead(pAttachingThread); + + // + // Associate the current fiber with the current thread. This makes the current fiber the thread's "home" + // fiber. This fiber is the only fiber allowed to execute managed code on this thread. When this fiber + // is destroyed, we consider the thread to be destroyed. + // + PalFlsSetValue(_fls_index, pAttachingThread); +} + +// static +void ThreadStore::AttachCurrentThread() +{ + AttachCurrentThread(true); +} + +void ThreadStore::DetachCurrentThreadIfHomeFiber() +{ + // + // Note: we call this when each *fiber* is destroyed, because we receive that notification outside + // of the Loader Lock. This allows us to safely acquire the ThreadStore lock. However, we have to be + // extra careful to avoid cleaning up a thread unless the fiber being destroyed is the thread's "home" + // fiber, as recorded in AttachCurrentThread. + // + + // The thread may not have been initialized because it may never have run managed code before. + Thread * pDetachingThread = RawGetCurrentThread(); +#ifndef FEATURE_DECLSPEC_THREAD + if (NULL == pDetachingThread) + return; +#endif // FEATURE_DECLSPEC_THREAD + + ASSERT(_fls_index != FLS_OUT_OF_INDEXES); + Thread* pThreadFromCurrentFiber = (Thread*)PalFlsGetValue(_fls_index); + + if (!pDetachingThread->IsInitialized()) + { + if (pThreadFromCurrentFiber != NULL) + { + ASSERT_UNCONDITIONALLY("Detaching a fiber from an unknown thread"); + RhFailFast(); + } + return; + } + + if (pThreadFromCurrentFiber == NULL) + { + // we've seen this thread, but not this fiber. It must be a "foreign" fiber that was + // borrowing this thread. + return; + } + + if (pThreadFromCurrentFiber != pDetachingThread) + { + ASSERT_UNCONDITIONALLY("Detaching a thread from the wrong fiber"); + RhFailFast(); + } + +#ifdef STRESS_LOG + ThreadStressLog * ptsl = reinterpret_cast( + pDetachingThread->GetThreadStressLog()); + StressLog::ThreadDetach(ptsl); +#endif // STRESS_LOG + + ThreadStore* pTS = GetThreadStore(); + ReaderWriterLock::WriteHolder write(&pTS->m_Lock); + ASSERT(rh::std::count(pTS->m_ThreadList.Begin(), pTS->m_ThreadList.End(), pDetachingThread) == 1); + pTS->m_ThreadList.RemoveFirst(pDetachingThread); + pDetachingThread->Destroy(); +} + +// Used by GC to prevent new threads during a GC. New threads must take a write lock to +// modify the list, but they won't be allowed to until all outstanding read locks are +// released. This way, the GC always enumerates a consistent set of threads each time +// it enumerates threads between SuspendAllThreads and ResumeAllThreads. +// +// @TODO: Investigate if this requirement is actually necessary. Threads already may +// not enter managed code during GC, so if new threads are added to the thread store, +// but haven't yet entered managed code, is that really a problem? +// +// @TODO: Investigate the suspend/resume algorithm's dependence on this lock's side- +// effect of being a memory barrier. +void ThreadStore::LockThreadStore() +{ + m_Lock.AcquireReadLock(); +} +void ThreadStore::UnlockThreadStore() +{ + m_Lock.ReleaseReadLock(); +} + +// defined in gcrhenv.cpp +extern SYSTEM_INFO g_SystemInfo; + +void ThreadStore::SuspendAllThreads(CLREventStatic* pCompletionEvent) +{ + Thread * pThisThread = GetCurrentThreadIfAvailable(); + + LockThreadStore(); + + RhpSuspendingThread = pThisThread; + + pCompletionEvent->Reset(); + m_SuspendCompleteEvent.Reset(); + + // set the global trap for pinvoke leave and return + RhpTrapThreads = 1; + + // Our lock-free algorithm depends on flushing write buffers of all processors running RH code. The + // reason for this is that we essentially implement Decker's algorithm, which requires write ordering. + PalFlushProcessWriteBuffers(); + + bool keepWaiting; + do + { + keepWaiting = false; + FOREACH_THREAD(pTargetThread) + { + if (pTargetThread == pThisThread) + continue; + + if (!pTargetThread->CacheTransitionFrameForSuspend()) + { + // We drive all threads to preemptive mode by hijacking them with both a + // return-address hijack and loop hijacks. + keepWaiting = true; + pTargetThread->Hijack(); + } + else if (pTargetThread->DangerousCrossThreadIsHijacked()) + { + // Once a thread is safely in preemptive mode, we must wait until it is also + // unhijacked. This is done because, otherwise, we might race on into the + // stackwalk and find the hijack still on the stack, which will cause the + // stackwalking code to crash. + keepWaiting = true; + } + } + END_FOREACH_THREAD + + if (keepWaiting) + { + if (PalSwitchToThread() == 0 && g_SystemInfo.dwNumberOfProcessors > 1) + { + // No threads are scheduled on this processor. Perhaps we're waiting for a thread + // that's scheduled on another processor. If so, let's give it a little time + // to make forward progress. + // Note that we do not call Sleep, because the minimum granularity of Sleep is much + // too long (we probably don't need a 15ms wait here). Instead, we'll just burn some + // cycles. + // @TODO: need tuning for spin + for (int i = 0; i < 10000; i++) + PalYieldProcessor(); + } + } + + } while (keepWaiting); + + m_SuspendCompleteEvent.Set(); +} + +void ThreadStore::ResumeAllThreads(CLREventStatic* pCompletionEvent) +{ + m_pRuntimeInstance->UnsychronizedResetHijackedLoops(); + + FOREACH_THREAD(pTargetThread) + { + pTargetThread->ResetCachedTransitionFrame(); + } + END_FOREACH_THREAD + + RhpTrapThreads = 0; + RhpSuspendingThread = NULL; + pCompletionEvent->Set(); + UnlockThreadStore(); +} // ResumeAllThreads + +// static +bool ThreadStore::IsTrapThreadsRequested() +{ + return (RhpTrapThreads != 0); +} + +void ThreadStore::WaitForSuspendComplete() +{ + UInt32 waitResult = m_SuspendCompleteEvent.Wait(INFINITE, false); + if (waitResult == WAIT_FAILED) + RhFailFast(); +} + +C_ASSERT(sizeof(Thread) == sizeof(ThreadBuffer)); + +EXTERN_C Thread * FASTCALL RhpGetThread(); +#ifdef FEATURE_DECLSPEC_THREAD +__declspec(thread) ThreadBuffer tls_CurrentThread = +{ + { 0 }, // m_rgbAllocContextBuffer + Thread::TSF_Unknown, // m_ThreadStateFlags + TOP_OF_STACK_MARKER, // m_pTransitionFrame + TOP_OF_STACK_MARKER, // m_pHackPInvokeTunnel + 0, // m_pCachedTransitionFrame + 0, // m_pNext + INVALID_HANDLE_VALUE, // m_hPalThread + 0, // m_ppvHijackedReturnAddressLocation + 0, // m_pvHijackedReturnAddress + 0, // m_pStackLow + 0, // m_pStackHigh + 0, // m_uPalThreadId +}; +#else // FEATURE_DECLSPEC_THREAD +EXTERN_C UInt32 _tls_index; + +struct TlsSectionStruct +{ + TlsSectionStruct() + { + // The codegen for __declspec(thread) has two indirections and we'd like to maintain that here, so + // we use the slack space in this structure as a place to simluate the ThreadLocalStoragePointer of + // the TEB. + ThreadLocalStoragePointer = this; + } + + void * ThreadLocalStoragePointer; // simulate __declspec(thread) + UInt32 padding; // make the offset of tls_CurrentThread match between mechanisms + ThreadBuffer tls_CurrentThread; +}; +#endif // FEATURE_DECLSPEC_THREAD + +// static +void * ThreadStore::CreateCurrentThreadBuffer() +{ + void * pvBuffer; + +#ifdef FEATURE_DECLSPEC_THREAD + + pvBuffer = &tls_CurrentThread; + +#else // FEATURE_DECLSPEC_THREAD + + ASSERT(_tls_index < 64); + ASSERT(NULL == PalTlsGetValue(_tls_index)); + + TlsSectionStruct * pTlsBlock = new TlsSectionStruct(); + ASSERT(NULL != pTlsBlock); // we require NT6's __declspec(thread) support for reliability + + PalTlsSetValue(_tls_index, pTlsBlock); + + pvBuffer = &pTlsBlock->tls_CurrentThread; + +#endif // FEATURE_DECLSPEC_THREAD +#if !defined(USE_PORTABLE_HELPERS) // No assembly routine defined to verify against. + ASSERT(RhpGetThread() == pvBuffer); +#endif // !defined(USE_PORTABLE_HELPERS) + return pvBuffer; +} + +// static +Thread * ThreadStore::RawGetCurrentThread() +{ +#ifdef FEATURE_DECLSPEC_THREAD + return (Thread *) &tls_CurrentThread; +#else + return RhpGetThread(); +#endif +} + +// static +Thread * ThreadStore::GetCurrentThread() +{ + Thread * pCurThread = RawGetCurrentThread(); + + // If this assert fires, and you only need the Thread pointer if the thread has ever previously + // entered the runtime, then you should be using GetCurrentThreadIfAvailable instead. + ASSERT(pCurThread->IsInitialized()); + return pCurThread; +}; + +// static +Thread * ThreadStore::GetCurrentThreadIfAvailable() +{ + Thread * pCurThread = RawGetCurrentThread(); + if (pCurThread->IsInitialized()) + return pCurThread; + + return NULL; +} +#endif // !DACCESS_COMPILE + +// Keep a global variable in the target process which contains +// the address of _tls_index. This is the breadcrumb needed +// by DAC to read _tls_index since we don't control the +// declaration of _tls_index directly. +EXTERN_C UInt32 _tls_index; +GPTR_IMPL_INIT(UInt32, p_tls_index, &_tls_index); + +#ifndef DACCESS_COMPILE +// We must prevent the linker from removing the unused global variable +// that DAC will be looking at to find _tls_index. +#ifdef _WIN64 +#pragma comment(linker, "/INCLUDE:?p_tls_index@@3PEAIEA") +#else +#pragma comment(linker, "/INCLUDE:?p_tls_index@@3PAIA") +#endif + +#else +#if defined(_WIN64) +#define OFFSETOF__TLS__tls_CurrentThread 0x20 +#elif defined(_ARM_) +#define OFFSETOF__TLS__tls_CurrentThread 0x10 +#else +#define OFFSETOF__TLS__tls_CurrentThread 0x08 +#endif + +// +// This routine supports the !Thread debugger extension routine +// +typedef DPTR(TADDR) PTR_TADDR; +// static +PTR_Thread ThreadStore::GetThreadFromTEB(TADDR pTEB) +{ + if (pTEB == NULL) + return NULL; + + UInt32 tlsIndex = *p_tls_index; + TADDR pTls = *(PTR_TADDR)(pTEB + OFFSETOF__TEB__ThreadLocalStoragePointer); + if (pTls == NULL) + return NULL; + + TADDR pOurTls = *(PTR_TADDR)(pTls + (tlsIndex * sizeof(void*))); + if (pOurTls == NULL) + return NULL; + + return (PTR_Thread)(pOurTls + OFFSETOF__TLS__tls_CurrentThread); +} +#endif + +#ifndef DACCESS_COMPILE +EXTERN_C void REDHAWK_CALLCONV RhpBulkWriteBarrier(void* pMemStart, UInt32 cbMemSize); + +// internal static extern unsafe bool RhGetExceptionsForCurrentThread(Exception[] outputArray, out int writtenCountOut); +COOP_PINVOKE_HELPER(Boolean, RhGetExceptionsForCurrentThread, (Array* pOutputArray, Int32* pWrittenCountOut)) +{ + return GetThreadStore()->GetExceptionsForCurrentThread(pOutputArray, pWrittenCountOut); +} + +Boolean ThreadStore::GetExceptionsForCurrentThread(Array* pOutputArray, Int32* pWrittenCountOut) +{ + Int32 countWritten = 0; + + Thread * pThread = GetCurrentThread(); + + for (PTR_ExInfo pInfo = pThread->m_pExInfoStackHead; pInfo != NULL; pInfo = pInfo->m_pPrevExInfo) + { + if (pInfo->m_exception == NULL) + continue; + + countWritten++; + } + + // No input array provided, or it was of the wrong kind. We'll fill out the count and return false. + if ((pOutputArray == NULL) || (pOutputArray->get_EEType()->get_ComponentSize() != POINTER_SIZE)) + goto Error; + + // Input array was not big enough. We don't even partially fill it. + if (pOutputArray->GetArrayLength() < (UInt32)countWritten) + goto Error; + + *pWrittenCountOut = countWritten; + + // Success, but nothing to report. + if (countWritten == 0) + return Boolean_true; + + Object** pArrayElements = (Object**)pOutputArray->GetArrayData(); + for (PTR_ExInfo pInfo = pThread->m_pExInfoStackHead; pInfo != NULL; pInfo = pInfo->m_pPrevExInfo) + { + if (pInfo->m_exception == NULL) + continue; + + *pArrayElements = pInfo->m_exception; + pArrayElements++; + } + + RhpBulkWriteBarrier(pArrayElements, countWritten * POINTER_SIZE); + return Boolean_true; + +Error: + *pWrittenCountOut = countWritten; + return Boolean_false; +} +#endif // DACCESS_COMPILE \ No newline at end of file diff --git a/src/Native/Runtime/threadstore.h b/src/Native/Runtime/threadstore.h new file mode 100644 index 00000000000..36051afe0b7 --- /dev/null +++ b/src/Native/Runtime/threadstore.h @@ -0,0 +1,71 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +class Thread; +class CLREventStatic; +class RuntimeInstance; +class Array; +typedef DPTR(RuntimeInstance) PTR_RuntimeInstance; + +class ThreadStore +{ + SList m_ThreadList; + PTR_RuntimeInstance m_pRuntimeInstance; + CLREventStatic m_SuspendCompleteEvent; + ReaderWriterLock m_Lock; + +private: + ThreadStore(); + static void * CreateCurrentThreadBuffer(); + void LockThreadStore(); + void UnlockThreadStore(); + +public: + class Iterator + { + ReaderWriterLock::ReadHolder m_readHolder; + PTR_Thread m_pCurrentPosition; + public: + Iterator(); + ~Iterator(); + PTR_Thread GetNext(); + }; + + ~ThreadStore(); + static ThreadStore * Create(RuntimeInstance * pRuntimeInstance); + static Thread * RawGetCurrentThread(); + static Thread * GetCurrentThread(); + static Thread * GetCurrentThreadIfAvailable(); + static PTR_Thread GetSuspendingThread(); + static void AttachCurrentThread(); + static void AttachCurrentThread(bool fAcquireThreadStoreLock); + static void DetachCurrentThreadIfHomeFiber(); +#ifdef DACCESS_COMPILE + static PTR_Thread GetThreadFromTEB(TADDR pvTEB); +#endif // DACCESS_COMPILE + Boolean GetExceptionsForCurrentThread(Array* pOutputArray, Int32* pWrittenCountOut); + + void Destroy(); + void SuspendAllThreads(CLREventStatic* pCompletionEvent); + void ResumeAllThreads(CLREventStatic* pCompletionEvent); + + static bool IsTrapThreadsRequested(); + void WaitForSuspendComplete(); +}; +typedef DPTR(ThreadStore) PTR_ThreadStore; + +ThreadStore * GetThreadStore(); + + +#define FOREACH_THREAD(p_thread_name) \ +{ \ + ThreadStore::Iterator __threads; \ + Thread * p_thread_name; \ + while ((p_thread_name = __threads.GetNext()) != NULL) \ + { \ + +#define END_FOREACH_THREAD \ + } \ +} \ + diff --git a/src/Native/Runtime/type_traits.hpp b/src/Native/Runtime/type_traits.hpp new file mode 100644 index 00000000000..72e6de98121 --- /dev/null +++ b/src/Native/Runtime/type_traits.hpp @@ -0,0 +1,314 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// +// type_traits.hpp +// +// Type trait metaprogramming utilities. +// + +#ifndef __TYPE_TRAITS_HPP__ +#define __TYPE_TRAITS_HPP__ + +#include "static_check.h" +#include "CommonTypes.h" + +namespace type_traits +{ + +namespace imp +{ + +struct true_type { static const bool value = true; }; +struct false_type { static const bool value = false; }; + +//////////////////////////////////////////////////////////////////////////////// +// Helper types Small and Big - guarantee that sizeof(Small) < sizeof(Big) +// + +template +struct conversion_helper +{ + typedef char Small; + struct Big { char dummy[2]; }; + static Big Test(...); + static Small Test(U); + static T MakeT(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// class template conversion +// Figures out the conversion relationships between two types +// Invocations (T and U are types): +// a) conversion::exists +// returns (at compile time) true if there is an implicit conversion from T +// to U (example: Derived to Base) +// b) conversion::exists2Way +// returns (at compile time) true if there are both conversions from T +// to U and from U to T (example: int to char and back) +// c) conversion::sameType +// returns (at compile time) true if T and U represent the same type +// +// NOTE: might not work if T and U are in a private inheritance hierarchy. +// + +template +struct conversion +{ + typedef imp::conversion_helper H; + static const bool exists = sizeof(typename H::Small) == sizeof((H::Test(H::MakeT()))); + static const bool exists2Way = exists && conversion::exists; + static const bool sameType = false; +}; + +template +struct conversion +{ + static const bool exists = true; + static const bool exists2Way = true; + static const bool sameType = true; +}; + +template +struct conversion +{ + static const bool exists = false; + static const bool exists2Way = false; + static const bool sameType = false; +}; + +template +struct conversion +{ + static const bool exists = false; + static const bool exists2Way = false; + static const bool sameType = false; +}; + +template <> +struct conversion +{ + static const bool exists = true; + static const bool exists2Way = true; + static const bool sameType = true; +}; + +template +struct is_base_of_helper; + +template <> +struct is_base_of_helper : public true_type {} ; + +template <> +struct is_base_of_helper : public false_type {} ; + +}// imp + +//////////////////////////////////////////////////////////////////////////////// +// is_base_of::value is typedefed to be true if TDerived derives from TBase +// and false otherwise. +// +// +// NOTE: use TR1 type_traits::is_base_of when available. +// +#ifdef _MSC_VER + +template +struct is_base_of : public imp::is_base_of_helper<__is_base_of( TBase, TDerived)> {}; + +#else + +// Note that we need to compare pointer types here, since conversion of types by-value +// just tells us whether or not an implicit conversion constructor exists. We handle +// type parameters that are already pointers specially; see below. +template +struct is_base_of : public imp::is_base_of_helper::exists> {}; + +// Specialization to handle type parameters that are already pointers. +template +struct is_base_of : public imp::is_base_of_helper::exists> {}; + +// Specialization to handle invalid mixing of pointer types. +template +struct is_base_of : public imp::false_type {}; + +// Specialization to handle invalid mixing of pointer types. +template +struct is_base_of : public imp::false_type {}; + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Remove const qualifications, if any. Access using remove_const::type +// +template struct remove_const { typedef T type; }; +template struct remove_const { typedef T type; }; + +//////////////////////////////////////////////////////////////////////////////// +// is_signed::value is true if T is a signed integral type, false otherwise. +// +template +struct is_signed { static const bool value = (static_cast(-1) < 0); }; + +} + +//////////////////////////////////////////////////////////////////////////////// +// These are related to type traits, but they are more like asserts of type +// traits in that the result is that either the compiler does or does not +// produce an error. +// +namespace type_constraints +{ + +//////////////////////////////////////////////////////////////////////////////// +// derived_from will produce a compiler error if TDerived does not +// derive from TBase. +// +// NOTE: use TR1 type_traits::is_base_of when available. +// + +template struct is_base_of +{ + is_base_of() + { + STATIC_ASSERT_MSG((type_traits::is_base_of::value), + is_base_of_constraint_violation); + } +}; + +}; // namespace type_constraints + +namespace rh { namespace std +{ + // Import some select components of the STL + + // TEMPLATE FUNCTION for_each + template + inline + _Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func) + { // perform function for each element + for (; _First != _Last; ++_First) + _Func(*_First); + return (_Func); + } + + template + inline + _InIt find(_InIt _First, _InIt _Last, const _Ty& _Val) + { // find first matching _Val + for (; _First != _Last; ++_First) + if (*_First == _Val) + break; + return (_First); + } + + template + inline + _InIt find_if(_InIt _First, _InIt _Last, _Pr _Pred) + { // find first satisfying _Pred + for (; _First != _Last; ++_First) + if (_Pred(*_First)) + break; + return (_First); + } + + template + inline + bool exists(_InIt _First, _InIt _Last, const _Ty& _Val) + { + return find(_First, _Last, _Val) != _Last; + } + + template + inline + bool exists_if(_InIt _First, _InIt _Last, _Pr _Pred) + { + return find_if(_First, _Last, _Pred) != _Last; + } + + template + inline + UIntNative count(_InIt _First, _InIt _Last, const _Ty& _Val) + { + UIntNative _Ret = 0; + for (; _First != _Last; _First++) + if (*_First == _Val) + ++_Ret; + return _Ret; + } + + template + inline + UIntNative count_if(_InIt _First, _InIt _Last, _Pr _Pred) + { + UIntNative _Ret = 0; + for (; _First != _Last; _First++) + if (_Pred(*_First)) + ++_Ret; + return _Ret; + } + + // Forward declaration, each collection requires specialization + template + inline + _FwdIt remove(_FwdIt _First, _FwdIt _Last, const _Ty& _Val); +} // namespace std +} // namespace rh + +#if 0 + +// ----------------------------------------------------------------- +// Holding place for unused-but-possibly-useful-in-the-future code. + +// ------------------------------------------------- +// This belongs in type_traits.hpp + +// +// is_pointer::value is true if the type is a pointer, false otherwise +// +template struct is_pointer : public false_type {}; +template struct is_pointer : public true_type {}; + +// +// Remove pointer from type, if it has one. Use remove_pointer::type +// Further specialized in daccess.h +// +template struct remove_pointer { typedef T type; }; +template struct remove_pointer { typedef T type; }; + +// ------------------------------------------------- +// This belongs in daccess.h + +namespace type_traits +{ + +// +// is_pointer::value is true if the type is a pointer, false otherwise +// specialized from type_traits.hpp +// +template struct is_pointer > : public type_traits::true_type {}; + +// +// remove_pointer::type is T with one less pointer qualification, if it had one. +// specialized from type_traits.hpp +// +template struct remove_pointer > { typedef T type; }; + +} // type_traits + +namespace dac +{ + +// +// is_dptr::value is true if T is a __DPtr, false otherwise. +// This is a partial specialization case for the positive case. +// +//template struct is_dptr > : public type_traits::true_type {}; + +} + +#endif + +#endif + diff --git a/src/Native/Runtime/varint.h b/src/Native/Runtime/varint.h new file mode 100644 index 00000000000..d12897e2d88 --- /dev/null +++ b/src/Native/Runtime/varint.h @@ -0,0 +1,146 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +class VarInt +{ +public: + static UInt32 ReadUnsigned(PTR_UInt8 & pbEncoding) + { + UIntNative lengthBits = *pbEncoding & 0x0F; + size_t negLength = s_negLengthTab[lengthBits]; + UIntNative shift = s_shiftTab[lengthBits]; + UInt32 result = *(PTR_UInt32)(pbEncoding - negLength - 4); + + result >>= shift; + pbEncoding -= negLength; + + return result; + } + + // + // WARNING: This method returns the negative of the length of the value that it just skipped! + // + // This was helpful in the GC info scan loop because it allowed us to always skip past unsigned values in + // the body of the loop. At the end of loop, we use this negative sign to distinguish between two cases + // and that allows us to decode the unsigned value that we need outside of the loop. Note that we encode + // the negatives in the s_negLengthTable to avoid any additional operations in the body of the GC scan + // loop. + // + static IntNative SkipUnsigned(PTR_UInt8 & pbEncoding) + { + UIntNative lengthBits = *pbEncoding & 0x0F; + size_t negLength = s_negLengthTab[lengthBits]; + pbEncoding -= negLength; + return negLength; + } + + static UIntNative WriteUnsigned(PTR_UInt8 pbDest, UInt32 value) + { + if (pbDest == NULL) + { + if (value < 128) + return 1; + + if (value < 128*128) + return 2; + + if (value < 128*128*128) + return 3; + + if (value < 128*128*128*128) + return 4; + + return 5; + } + + if (value < 128) + { + *pbDest++ = (UInt8)(value*2 + 0); + return 1; + } + + if (value < 128*128) + { + *pbDest++ = (UInt8)(value*4 + 1); + *pbDest++ = (UInt8)(value >> 6); + return 2; + } + + if (value < 128*128*128) + { + *pbDest++ = (UInt8)(value*8 + 3); + *pbDest++ = (UInt8)(value >> 5); + *pbDest++ = (UInt8)(value >> 13); + return 3; + } + + if (value < 128*128*128*128) + { + *pbDest++ = (UInt8)(value*16 + 7); + *pbDest++ = (UInt8)(value >> 4); + *pbDest++ = (UInt8)(value >> 12); + *pbDest++ = (UInt8)(value >> 20); + return 4; + } + + *pbDest++ = 15; + *pbDest++ = (UInt8)value; + *pbDest++ = (UInt8)(value >> 8); + *pbDest++ = (UInt8)(value >> 16); + *pbDest++ = (UInt8)(value >> 24); + return 5; + } + +private: + static Int8 s_negLengthTab[16]; + static UInt8 s_shiftTab[16]; +}; + +__declspec(selectany) +Int8 VarInt::s_negLengthTab[16] = +{ + -1, // 0 + -2, // 1 + -1, // 2 + -3, // 3 + + -1, // 4 + -2, // 5 + -1, // 6 + -4, // 7 + + -1, // 8 + -2, // 9 + -1, // 10 + -3, // 11 + + -1, // 12 + -2, // 13 + -1, // 14 + -5, // 15 +}; + +__declspec(selectany) +UInt8 VarInt::s_shiftTab[16] = +{ + 32-7*1, // 0 + 32-7*2, // 1 + 32-7*1, // 2 + 32-7*3, // 3 + + 32-7*1, // 4 + 32-7*2, // 5 + 32-7*1, // 6 + 32-7*4, // 7 + + 32-7*1, // 8 + 32-7*2, // 9 + 32-7*1, // 10 + 32-7*3, // 11 + + 32-7*1, // 12 + 32-7*2, // 13 + 32-7*1, // 14 + 0, // 15 +}; \ No newline at end of file