// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. /* ------------------------------------------------------------------------- * * DbgIPCEvents.h -- header file for private Debugger data shared by various // * debugger components. * ------------------------------------------------------------------------- */ #ifndef _DbgIPCEvents_h_ #define _DbgIPCEvents_h_ #include #include #include #include // for ICorDebugInfo::VarLocType & VarLoc #include #include "dbgtargetcontext.h" // Get version numbers for IPCHeader stamp #include "ndpversion.h" #include "dbgappdomain.h" #include "./common.h" //----------------------------------------------------------------------------- // V3 additions to IPC protocol between LS and RS. //----------------------------------------------------------------------------- // Special Exception code for LS to communicate with RS. // LS will raise this exception to communicate managed debug events to the RS. // Exception codes can't use bit 0x10000000, that's reserved by OS. #define CLRDBG_NOTIFICATION_EXCEPTION_CODE ((DWORD) 0x04242420) // 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. #define CLRDBG_EXCEPTION_DATA_CHECKSUM ((DWORD) 0x31415927) // Reasons for hijack. namespace EHijackReason { enum EHijackReason { kUnhandledException = 1, kM2UHandoff = 2, kFirstChanceSuspend = 3, kGenericHijack = 4, kMax }; inline bool IsValid(EHijackReason value) { SUPPORTS_DAC; return (value > 0) && (value < kMax); } } #define MAX_LOG_SWITCH_NAME_LEN 256 //----------------------------------------------------------------------------- // Versioning note: // This file describes the IPC communication protocol between the LS (mscorwks) // and the RS (mscordbi). For Desktop builds, it is private and can change on a // daily basis. The version of the LS will always match the version of the RS // (but see the discussion of CoreCLR below). They are like a single conceptual // DLL split across 2 processes. // The only restriction is that it should be flavor agnostic - so don't change // layout based off '#ifdef DEBUG'. This lets us drop a Debug flavor RS onto // a retail installation w/o any further installation woes. That's very useful // for debugging. //----------------------------------------------------------------------------- // We want this available for DbgInterface.h - put it here. typedef enum { IPC_TARGET_OUTOFPROC, IPC_TARGET_COUNT, } IpcTarget; // // Names of the setup sync event and shared memory used for IPC between the Left Side and the Right Side. NOTE: these // names must include a %d for the process id. The process id used is the process id of the debuggee. // #define CorDBIPCSetupSyncEventName W("CorDBIPCSetupSyncEvent_%d") // // This define controls whether we always pass first chance exceptions to the in-process first chance hijack filter // during interop debugging or if we try to short-circuit and make the decision out-of-process as much as possible. // #define CorDB_Short_Circuit_First_Chance_Ownership 1 // // Defines for current version numbers for the left and right sides // #define CorDB_LeftSideProtocolCurrent 2 #define CorDB_LeftSideProtocolMinSupported 2 #define CorDB_RightSideProtocolCurrent 2 #define CorDB_RightSideProtocolMinSupported 2 // // The remaining data structures in this file can be shared between two processes and for network transport // based debugging this can mean two different platforms as well. The two platforms that can share these // data structures must have identical layouts for them (each field must lie at the same offset and have the // same length). The MSLAYOUT macro should be applied to each structure to avoid any compiler packing differences. // // // DebuggerIPCRuntimeOffsets contains addresses and offsets of important global variables, functions, and fields in // Runtime objects. This is populated during Left Side initialization and is read by the Right Side. This struct is // mostly to facilitate unmanaged debugging support, but it may have some small uses for managed debugging. // struct MSLAYOUT DebuggerIPCRuntimeOffsets { #ifdef FEATURE_INTEROP_DEBUGGING void *m_genericHijackFuncAddr; void *m_signalHijackStartedBPAddr; void *m_excepForRuntimeHandoffStartBPAddr; void *m_excepForRuntimeHandoffCompleteBPAddr; void *m_signalHijackCompleteBPAddr; void *m_excepNotForRuntimeBPAddr; void *m_notifyRSOfSyncCompleteBPAddr; void *m_raiseExceptionAddr; // The address of kernel32!RaiseException in the debuggee #endif // FEATURE_INTEROP_DEBUGGING SIZE_T m_TLSIndex; // The TLS index the CLR is using to hold Thread objects SIZE_T m_TLSIsSpecialIndex; // The index into the Predef block of the the "IsSpecial" status for a thread. SIZE_T m_TLSCantStopIndex; // The index into the Predef block of the the Can't-Stop count. SIZE_T m_TLSIndexOfPredefs; // The TLS index of the Predef block. SIZE_T m_EEThreadStateOffset; // Offset of m_state in a Thread SIZE_T m_EEThreadStateNCOffset; // Offset of m_stateNC in a Thread SIZE_T m_EEThreadPGCDisabledOffset; // Offset of the bit for whether PGC is disabled or not in a Thread DWORD m_EEThreadPGCDisabledValue; // Value at m_EEThreadPGCDisabledOffset that equals "PGC disabled". SIZE_T m_EEThreadDebuggerWordOffset; // Offset of debugger word in a Thread SIZE_T m_EEThreadFrameOffset; // Offset of the Frame ptr in a Thread SIZE_T m_EEThreadMaxNeededSize; // Max memory to read to get what we need out of a Thread object DWORD m_EEThreadSteppingStateMask; // Mask for Thread::TSNC_DebuggerIsStepping DWORD m_EEMaxFrameValue; // The max Frame value SIZE_T m_EEThreadDebuggerFilterContextOffset; // Offset of debugger's filter context within a Thread Object. SIZE_T m_EEThreadCantStopOffset; // Offset of the can't stop count in a Thread SIZE_T m_EEFrameNextOffset; // Offset of the next ptr in a Frame DWORD m_EEIsManagedExceptionStateMask; // Mask for Thread::TSNC_DebuggerIsManagedException void *m_pPatches; // Addr of patch table BOOL *m_pPatchTableValid; // Addr of g_patchTableValid SIZE_T m_offRgData; // Offset of m_pcEntries SIZE_T m_offCData; // Offset of count of m_pcEntries SIZE_T m_cbPatch; // Size per patch entry SIZE_T m_offAddr; // Offset within patch of target addr SIZE_T m_offOpcode; // Offset within patch of target opcode SIZE_T m_cbOpcode; // Max size of opcode SIZE_T m_offTraceType; // Offset of the trace.type within a patch DWORD m_traceTypeUnmanaged; // TRACE_UNMANAGED DebuggerIPCRuntimeOffsets() { ZeroMemory(this, sizeof(DebuggerIPCRuntimeOffsets)); } }; // // The size of the send and receive IPC buffers. // These must be big enough to fit a DebuggerIPCEvent. Also, the bigger they are, the fewer events // it takes to send variable length stuff like the stack trace. // But for perf reasons, they need to be small enough to not just push us over a page boundary in an IPC block. // Unfortunately, there's a lot of other goo in the IPC block, so we can't use some clean formula. So we // have to resort to just tuning things. // // When using a network transport rather than shared memory buffers CorDBIPC_BUFFER_SIZE is the upper bound // for a single DebuggerIPCEvent structure. This now relates to the maximal size of a network message and is // orthogonal to the host's page size. Because of this we defer definition of CorDBIPC_BUFFER_SIZE until we've // declared DebuggerIPCEvent at the end of this header (and we can do so because in the transport case there // aren't any embedded buffers in the DebuggerIPCControlBlock). #if defined(DBG_TARGET_X86) || defined(DBG_TARGET_ARM) #define CorDBIPC_BUFFER_SIZE (2088) // hand tuned to ensure that ipc block in IPCHeader.h fits in 1 page. #else // !_TARGET_X86_ && !_TARGET_ARM_ // This is the size of a DebuggerIPCEvent. You will hit an assert in Cordb::Initialize() (DI\process.cpp) // if this is not defined correctly. AMD64 actually has a page size of 0x1000, not 0x2000. #define CorDBIPC_BUFFER_SIZE 4016 // (4016 + 6) * 2 + 148 = 8192 (two (DebuggerIPCEvent + alignment padding) + // other fields = page size) #endif // DBG_TARGET_X86 || DBG_TARGET_ARM // // DebuggerIPCControlBlock describes the layout of the shared memory shared between the Left Side and the Right // Side. This includes error information, handles for the IPC channel, and space for the send/receive buffers. // struct MSLAYOUT DebuggerIPCControlBlock { // Version data should be first in the control block to ensure that we can read it even if the control block // changes. SIZE_T m_DCBSize; // note this field is used as a semaphore to indicate the DCB is initialized ULONG m_verMajor; // CLR build number for the Left Side. ULONG m_verMinor; // CLR build number for the Left Side. // This next stuff fits in a DWORD. bool m_checkedBuild; // CLR build type for the Left Side. // using the first padding byte to indicate if hosted in fiber mode. // We actually just need one bit. So if needed, can turn this to a bit. // BYTE padding1; bool m_bHostingInFiber; BYTE padding2; BYTE padding3; ULONG m_leftSideProtocolCurrent; // Current protocol version for the Left Side. ULONG m_leftSideProtocolMinSupported; // Minimum protocol the Left Side can support. ULONG m_rightSideProtocolCurrent; // Current protocol version for the Right Side. ULONG m_rightSideProtocolMinSupported; // Minimum protocol the Right Side requires. HRESULT m_errorHR; unsigned int m_errorCode; #if defined(DBG_TARGET_WIN64) // 64-bit needs this padding to make the handles after this aligned. // But x86 can't have this padding b/c it breaks binary compatibility between v1.1 and v2.0. ULONG padding4; #endif // DBG_TARGET_WIN64 RemoteHANDLE m_rightSideEventAvailable; RemoteHANDLE m_rightSideEventRead; // @dbgtodo inspection - this is where LSEA and LSER used to be. We need to the padding to maintain binary compatibility. // Eventually, we expect to remove this whole block. RemoteHANDLE m_paddingObsoleteLSEA; RemoteHANDLE m_paddingObsoleteLSER; RemoteHANDLE m_rightSideProcessHandle; //............................................................................. // Everything above this point must have the exact same binary layout as v1.1. // See protocol details below. //............................................................................. RemoteHANDLE m_leftSideUnmanagedWaitEvent; // This is set immediately when the helper thread is created. // This will be set even if there's a temporary helper thread or if the real helper // thread is not yet pumping (eg, blocked on a loader lock). DWORD m_realHelperThreadId; // This is only published once the helper thread starts running in its main loop. // Thus we can use this field to see if the real helper thread is actually pumping. DWORD m_helperThreadId; // This is non-zero if the LS has a temporary helper thread. DWORD m_temporaryHelperThreadId; // ID of the Helper's canary thread. DWORD m_CanaryThreadId; DebuggerIPCRuntimeOffsets *m_pRuntimeOffsets; void *m_helperThreadStartAddr; void *m_helperRemoteStartAddr; DWORD *m_specialThreadList; BYTE m_receiveBuffer[CorDBIPC_BUFFER_SIZE]; BYTE m_sendBuffer[CorDBIPC_BUFFER_SIZE]; DWORD m_specialThreadListLength; bool m_shutdownBegun; bool m_rightSideIsWin32Debugger; // RS status bool m_specialThreadListDirty; bool m_rightSideShouldCreateHelperThread; // NOTE The Init method works since there are no virtual functions - don't add any virtual functions without // changing this! // Only initialized by the LS, opened by the RS. HRESULT Init( HANDLE rsea, HANDLE rser, HANDLE lsea, HANDLE lser, HANDLE lsuwe ); }; #if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) // We need an alternate definition for the control block if using the transport, because the control block has to be sent over the transport // In particular we can't nest the send/receive buffers inside of it and we don't use any of the remote handles struct MSLAYOUT DebuggerIPCControlBlockTransport { // Version data should be first in the control block to ensure that we can read it even if the control block // changes. SIZE_T m_DCBSize; // note this field is used as a semaphore to indicate the DCB is initialized ULONG m_verMajor; // CLR build number for the Left Side. ULONG m_verMinor; // CLR build number for the Left Side. // This next stuff fits in a DWORD. bool m_checkedBuild; // CLR build type for the Left Side. // using the first padding byte to indicate if hosted in fiber mode. // We actually just need one bit. So if needed, can turn this to a bit. // BYTE padding1; bool m_bHostingInFiber; BYTE padding2; BYTE padding3; ULONG m_leftSideProtocolCurrent; // Current protocol version for the Left Side. ULONG m_leftSideProtocolMinSupported; // Minimum protocol the Left Side can support. ULONG m_rightSideProtocolCurrent; // Current protocol version for the Right Side. ULONG m_rightSideProtocolMinSupported; // Minimum protocol the Right Side requires. HRESULT m_errorHR; unsigned int m_errorCode; #if defined(DBG_TARGET_WIN64) // 64-bit needs this padding to make the handles after this aligned. // But x86 can't have this padding b/c it breaks binary compatibility between v1.1 and v2.0. ULONG padding4; #endif // DBG_TARGET_WIN64 // This is set immediately when the helper thread is created. // This will be set even if there's a temporary helper thread or if the real helper // thread is not yet pumping (eg, blocked on a loader lock). DWORD m_realHelperThreadId; // This is only published once the helper thread starts running in its main loop. // Thus we can use this field to see if the real helper thread is actually pumping. DWORD m_helperThreadId; // This is non-zero if the LS has a temporary helper thread. DWORD m_temporaryHelperThreadId; // ID of the Helper's canary thread. DWORD m_CanaryThreadId; DebuggerIPCRuntimeOffsets *m_pRuntimeOffsets; void *m_helperThreadStartAddr; void *m_helperRemoteStartAddr; DWORD *m_specialThreadList; DWORD m_specialThreadListLength; bool m_shutdownBegun; bool m_rightSideIsWin32Debugger; // RS status bool m_specialThreadListDirty; bool m_rightSideShouldCreateHelperThread; // NOTE The Init method works since there are no virtual functions - don't add any virtual functions without // changing this! // Only initialized by the LS, opened by the RS. HRESULT Init(); }; #endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) #if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) #include "dbgtransportsession.h" #endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) #if defined(DBG_TARGET_X86) && !defined(FEATURE_CORESYSTEM) // We have an versioning requirement. // Certain portions of the v1.0 and v1.1 IPC block are shared. This is b/c a v1.1 debugger needs to be able // to look at a v2.0 app enough to recognize the version mismatch. // This check is only necessary for platforms that ran on v1.1 (Win32-x86) // Just to catch any potential illegal change in the IPC block, we assert the offsets against the offsets from v1.1. // The constants here are pulled from v1.1. // The RS will look at these versioning fields, so they absolutely must line up. static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_leftSideProtocolCurrent) == 0x10); static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_leftSideProtocolMinSupported) == 0x14); static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_rightSideProtocolCurrent) == 0x18); static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_rightSideProtocolMinSupported) == 0x1c); // Unfortunately, on detecting such failure, v1.1 will also null out LSEA, LSER and RSPH. // If these get adjusted, a version-mismatch attach will effectively null out random fields. static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_paddingObsoleteLSEA) == 0x30); static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_paddingObsoleteLSER) == 0x34); static_assert_no_msg(offsetof(DebuggerIPCControlBlock, m_rightSideProcessHandle) == 0x38); #endif #define INITIAL_APP_DOMAIN_INFO_LIST_SIZE 16 //----------------------------------------------------------------------------- // Provide some Type-safety in the IPC block when we pass remote pointers around. //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // This is the same in both the LS & RS. // Definitions on the LS & RS should be binary compatible. So all storage is // declared in GeneralLsPointer, and then the Ls & RS each have their own // derived accessors. //----------------------------------------------------------------------------- class MSLAYOUT GeneralLsPointer { protected: friend ULONG_PTR LsPtrToCookie(GeneralLsPointer p); void * m_ptr; public: bool IsNull() { return m_ptr == NULL; } }; class MSLAYOUT GeneralRsPointer { protected: UINT m_data; public: bool IsNull() { return m_data == 0; } }; // In some cases, we need to get a uuid from a pointer (ie, in a hash) inline ULONG_PTR LsPtrToCookie(GeneralLsPointer p) { return (ULONG_PTR) p.m_ptr; } #define VmPtrToCookie(vm) LsPtrToCookie((vm).ToLsPtr()) #ifdef RIGHT_SIDE_COMPILE //----------------------------------------------------------------------------- // Infrasturcture for RS Definitions //----------------------------------------------------------------------------- // On the RS, we don't have the LS classes defined, so we can't templatize that // in terms of , but we still want things to be unique. // So we create an empty enum for each LS type and then templatize it in terms // of the enum. template class MSLAYOUT LsPointer : public GeneralLsPointer { public: void Set(void * p) { m_ptr = p; } void * UnsafeGet() { return m_ptr; } static LsPointer NullPtr() { return MakePtr(NULL); } static LsPointer MakePtr(T* p) { #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't' #endif // _PREFAST_ LsPointer t; t.Set(p); return t; #ifdef _PREFAST_ #pragma warning(pop) #endif // _PREFAST_ } bool operator!= (void * p) { return m_ptr != p; } bool operator== (void * p) { return m_ptr == p; } bool operator==(LsPointer p) { return p.m_ptr == this->m_ptr; } // We should never UnWrap() them in the RS, so we don't define that here. }; class CordbProcess; template UINT AllocCookie(CordbProcess * pProc, T * p); template T * UnwrapCookie(CordbProcess * pProc, UINT cookie); UINT AllocCookieCordbEval(CordbProcess * pProc, class CordbEval * p); class CordbEval * UnwrapCookieCordbEval(CordbProcess * pProc, UINT cookie); template UINT AllocCookie(CordbProcess * pProc, CordbEval * p) { return AllocCookieCordbEval(pProc, p); } template CordbEval * UnwrapCookie(CordbProcess * pProc, UINT cookie) { return UnwrapCookieCordbEval(pProc, cookie); } // This is how the RS sees the pointers in the IPC block. template class MSLAYOUT RsPointer : public GeneralRsPointer { public: // Since we're being used inside a union, we can't have a ctor. static RsPointer NullPtr() { RsPointer t; t.m_data = 0; return t; } bool AllocHandle(CordbProcess *pProc, T* p) { // This will force validation. m_data = AllocCookie(pProc, p); return (m_data != 0); } bool operator==(RsPointer p) { return p.m_data == this->m_data; } T* UnWrapAndRemove(CordbProcess *pProc) { return UnwrapCookie(pProc, m_data); } protected: }; // Forward declare a class so that each type of LS pointer can have // its own type. We use the real class name to be compatible with VMPTRs. #define DEFINE_LSPTR_TYPE(ls_type, ptr_name) \ ls_type; \ typedef LsPointer ptr_name; #define DEFINE_RSPTR_TYPE(rs_type, ptr_name) \ class rs_type; \ typedef RsPointer ptr_name; #else // !RIGHT_SIDE_COMPILE //----------------------------------------------------------------------------- // Infrastructure for LS Definitions //----------------------------------------------------------------------------- // This is how the LS sees the pointers in the IPC block. template class MSLAYOUT LsPointer : public GeneralLsPointer { public: // Since we're being used inside a union, we can't have a ctor. //LsPointer() { } static LsPointer NullPtr() { return MakePtr(NULL); } static LsPointer MakePtr(T * p) { #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't' #endif // _PREFAST_ LsPointer t; t.Set(p); return t; #ifdef _PREFAST_ #pragma warning(pop) #endif // _PREFAST_ } bool operator!= (void * p) { return m_ptr != p; } bool operator== (void * p) { return m_ptr == p; } bool operator==(LsPointer p) { return p.m_ptr == this->m_ptr; } // @todo - we want to be able to swap out Set + Unwrap functions void Set(T * p) { SUPPORTS_DAC; // We could validate the pointer here. m_ptr = p; } T * UnWrap() { // If we wanted to validate the pointer, here's our chance. return static_cast(m_ptr); } }; template class MSLAYOUT RsPointer : public GeneralRsPointer { public: static RsPointer NullPtr() { RsPointer t; t.m_data = 0; return t; } bool operator==(RsPointer p) { return p.m_data == this->m_data; } // We should never UnWrap() them in the LS, so we don't define that here. }; #define DEFINE_LSPTR_TYPE(ls_type, ptr_name) \ ls_type; \ typedef LsPointer ptr_name; #define DEFINE_RSPTR_TYPE(rs_type, ptr_name) \ enum __RS__##rs_type { }; \ typedef RsPointer<__RS__##rs_type> ptr_name; #endif // !RIGHT_SIDE_COMPILE // We must be binary compatible w/ a pointer. static_assert_no_msg(sizeof(LsPointer) == sizeof(GeneralLsPointer)); static_assert_no_msg(sizeof(void*) == sizeof(GeneralLsPointer)); //----------------------------------------------------------------------------- // Definitions for Left-Side ptrs. // NOTE: Use VMPTR instead of LSPTR. Don't add new LSPTR types. // //----------------------------------------------------------------------------- DEFINE_LSPTR_TYPE(class Assembly, LSPTR_ASSEMBLY); DEFINE_LSPTR_TYPE(class DebuggerJitInfo, LSPTR_DJI); DEFINE_LSPTR_TYPE(class DebuggerMethodInfo, LSPTR_DMI); DEFINE_LSPTR_TYPE(class MethodDesc, LSPTR_METHODDESC); DEFINE_LSPTR_TYPE(class DebuggerBreakpoint, LSPTR_BREAKPOINT); DEFINE_LSPTR_TYPE(class DebuggerEval, LSPTR_DEBUGGEREVAL); DEFINE_LSPTR_TYPE(class DebuggerStepper, LSPTR_STEPPER); // Need to be careful not to annoy the compiler here since DT_CONTEXT is a typedef, not a struct. #if defined(RIGHT_SIDE_COMPILE) typedef LsPointer LSPTR_CONTEXT; #else // RIGHT_SIDE_COMPILE typedef LsPointer LSPTR_CONTEXT; #endif // RIGHT_SIDE_COMPILE DEFINE_LSPTR_TYPE(struct OBJECTHANDLE__, LSPTR_OBJECTHANDLE); DEFINE_LSPTR_TYPE(class TypeHandleDummyPtr, LSPTR_TYPEHANDLE); // TypeHandle in the LS is not a direct pointer. //----------------------------------------------------------------------------- // Definitions for Right-Side ptrs. //----------------------------------------------------------------------------- DEFINE_RSPTR_TYPE(CordbEval, RSPTR_CORDBEVAL); //--------------------------------------------------------------------------------------- // VMPTR_Base is the base type for an abstraction over pointers into the VM so // that DBI can treat them as opaque handles. Classes will derive from it to // provide type-safe Target pointers, which ICD will view as opaque handles. // // Lifetimes: // VMPTR_ objects survive across flushing the DAC cache. Therefore, the underlying // storage must be a target-pointer (and not a marshalled host pointer). // The RS must ensure they're still in sync with the LS (eg, by // tracking unload events). // // // Assumptions: // These handles are TADDR pointers and must not require any cleanup from DAC/DBI. // For direct untyped pointers into the VM, use CORDB_ADDRESS. // // Notes: // 1. This helps enforce that DBI goes through the primitives interface // for all access (and that it doesn't accidentally start calling // dac-ized methods on the objects) // 2. This isolates DBI from VM headers. // 3. This isolates DBI from the dac implementation (of DAC_Ptr) // 4. This is distinct from LSPTR because LSPTRs are truly opaque handles, whereas VMPtrs // move across VM, DAC, and DBI, exposing proper functionality in each component. // 5. VMPTRs are blittable because they are Target Addresses which act as opaque // handles outside of the Target / Dac-marshaller. // //--------------------------------------------------------------------------------------- template class MSLAYOUT VMPTR_Base { // Underlying pointer into Target address space. // Target pointers are blittable. // - In Target: can be used as normal local pointers. // - In DAC: must be marshalled to a host-pointer and then they can be used via DAC // - In RS: opaque handles. private: TADDR m_addr; public: typedef VMPTR_Base VMPTR_This; // For DBI, VMPTRs are opaque handles. // But the DAC side is allowed to inspect the handles to get at the raw pointer. #if defined(ALLOW_VMPTR_ACCESS) // // Case 1: Using in DAcDbi implementation // // DAC accessor TDacPtr GetDacPtr() const { SUPPORTS_DAC; return TDacPtr(m_addr); } // This will initialize the handle to a given target-pointer. // We choose TADDR to make it explicit that it's a target pointer and avoid the risk // of it accidentally getting marshalled to a host pointer. void SetDacTargetPtr(TADDR addr) { SUPPORTS_DAC; m_addr = addr; } void SetHostPtr(const TTargetPtr * pObject) { SUPPORTS_DAC; m_addr = PTR_HOST_TO_TADDR(pObject); } #elif !defined(RIGHT_SIDE_COMPILE) // // Case 2: Used in Left-side. Can get/set from local pointers. // // This will set initialize from a Target pointer. Since this is happening in the // Left-side (Target), the pointer is local. // This is commonly used by the Left-side to create a VMPTR_ for a notification event. void SetRawPtr(TTargetPtr * ptr) { m_addr = reinterpret_cast(ptr); } // This will get the raw underlying target pointer. // This can be used by inproc Left-side code to unwrap a VMPTR (Eg, for a func-eval // hijack or in-proc worker threads) TTargetPtr * GetRawPtr() { return reinterpret_cast(m_addr); } // Convenience for converting TTargetPtr --> VMPTR static VMPTR_This MakePtr(TTargetPtr * ptr) { #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't' #endif // _PREFAST_ VMPTR_This t; t.SetRawPtr(ptr); return t; #ifdef _PREFAST_ #pragma warning(pop) #endif // _PREFAST_ } #else // // Case 3: Used in RS. Opaque handles only. // #endif #ifndef DACCESS_COMPILE // For compatibility, these can be converted to LSPTRs on the RS or LS (case 2 and 3). We don't allow // this in the DAC case because it's a cast between address spaces which we're trying to eliminate // in the DAC code. // @dbgtodo inspection: LSPTRs will go away entirely once we've moved completely over to DAC LsPointer ToLsPtr() { return LsPointer::MakePtr( reinterpret_cast(m_addr)); } #endif // // Operators to emulate Pointer semantics. // bool IsNull() { SUPPORTS_DAC; return m_addr == NULL; } static VMPTR_This NullPtr() { SUPPORTS_DAC; #ifdef _PREFAST_ #pragma warning(push) #pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't' #endif // _PREFAST_ VMPTR_This dummy; dummy.m_addr = NULL; return dummy; #ifdef _PREFAST_ #pragma warning(pop) #endif // _PREFAST_ } bool operator!= (VMPTR_This vmOther) const { SUPPORTS_DAC; return this->m_addr != vmOther.m_addr; } bool operator== (VMPTR_This vmOther) const { SUPPORTS_DAC; return this->m_addr == vmOther.m_addr; } }; #if defined(ALLOW_VMPTR_ACCESS) // Helper macro to define a VMPTR. // This is used in the DAC case, so this definition connects the pointers up to their DAC values. #define DEFINE_VMPTR(ls_type, dac_ptr_type, ptr_name) \ ls_type; \ typedef VMPTR_Base ptr_name; #else // Helper macro to define a VMPTR. // This is used in the Right-side and Left-side (but not DAC) case. // This definition explicitly ignores dac_ptr_type to prevent accidental DAC usage. #define DEFINE_VMPTR(ls_type, dac_ptr_type, ptr_name) \ ls_type; \ typedef VMPTR_Base ptr_name; #endif // Declare VMPTRs. // The naming convention for instantiating a VMPTR is a 'vm' prefix. // // VM definition, DAC definition, pretty name for VMPTR DEFINE_VMPTR(class AppDomain, PTR_AppDomain, VMPTR_AppDomain); // Need to be careful not to annoy the compiler here since DT_CONTEXT is a typedef, not a struct. // DEFINE_VMPTR(struct _CONTEXT, PTR_CONTEXT, VMPTR_CONTEXT); #if defined(ALLOW_VMPTR_ACCESS) typedef VMPTR_Base VMPTR_CONTEXT; #else typedef VMPTR_Base VMPTR_CONTEXT; #endif // DomainFile is a base-class for a CLR module, with app-domain affinity. // For domain-neutral modules (like mscorlib), there is a DomainFile instance // for each appdomain the module lives in. // This is the canonical handle ICorDebug uses to a CLR module. DEFINE_VMPTR(class DomainFile, PTR_DomainFile, VMPTR_DomainFile); DEFINE_VMPTR(class Module, PTR_Module, VMPTR_Module); // DomainAssembly derives from DomainFile and represents a manifest module. DEFINE_VMPTR(class DomainAssembly, PTR_DomainAssembly, VMPTR_DomainAssembly); DEFINE_VMPTR(class Assembly, PTR_Assembly, VMPTR_Assembly); DEFINE_VMPTR(class PEFile, PTR_PEFile, VMPTR_PEFile); DEFINE_VMPTR(class MethodDesc, PTR_MethodDesc, VMPTR_MethodDesc); DEFINE_VMPTR(class FieldDesc, PTR_FieldDesc, VMPTR_FieldDesc); // ObjectHandle is a safe way to represent an object into the GC heap. It gets updated // when a GC occurs. DEFINE_VMPTR(struct OBJECTHANDLE__, TADDR, VMPTR_OBJECTHANDLE); DEFINE_VMPTR(class TypeHandle, PTR_TypeHandle, VMPTR_TypeHandle); // A VMPTR_Thread represents a thread that has entered the runtime at some point. // It may or may not have executed managed code yet; and it may or may not have managed code // on its callstack. DEFINE_VMPTR(class Thread, PTR_Thread, VMPTR_Thread); DEFINE_VMPTR(class Object, PTR_Object, VMPTR_Object); DEFINE_VMPTR(class CrstBase, PTR_Crst, VMPTR_Crst); DEFINE_VMPTR(class SimpleRWLock, PTR_SimpleRWLock, VMPTR_SimpleRWLock); DEFINE_VMPTR(class SimpleRWLock, PTR_SimpleRWLock, VMPTR_RWLock); DEFINE_VMPTR(struct ReJitInfo, PTR_ReJitInfo, VMPTR_ReJitInfo); DEFINE_VMPTR(struct SharedReJitInfo, PTR_SharedReJitInfo, VMPTR_SharedReJitInfo); typedef CORDB_ADDRESS GENERICS_TYPE_TOKEN; //----------------------------------------------------------------------------- // We pass some fixed size strings in the IPC block. // Helper class to wrap the buffer and protect against buffer overflows. // This should be binary compatible w/ a wchar[] array. //----------------------------------------------------------------------------- template class MSLAYOUT EmbeddedIPCString { public: // Set, caller responsibility that wcslen(pData) < nMaxLengthIncludingNull void SetString(const WCHAR * pData) { // If the string doesn't fit into the buffer, that's an issue (and so this is a real // assert, not just a simplifying assumption). To fix it, either: // - make the buffer larger // - don't pass the string as an embedded string in the IPC block. // This will truncate (rather than AV on the RS). int ret; ret = SafeCopy(pData); // See comment above - caller should guarantee that buffer is large enough. _ASSERTE(ret != STRUNCATE); } // Set a string from a substring. This will truncate if necessary. void SetStringTruncate(const WCHAR * pData) { // ignore return value because truncation is ok. SafeCopy(pData); } const WCHAR * GetString() { // For a null-termination just in case an issue in the debuggee process // yields a malformed string. m_data[nMaxLengthIncludingNull - 1] = W('\0'); return &m_data[0]; } int GetMaxSize() const { return nMaxLengthIncludingNull; } private: int SafeCopy(const WCHAR * pData) { return wcsncpy_s( m_data, nMaxLengthIncludingNull, pData, _TRUNCATE); } WCHAR m_data[nMaxLengthIncludingNull]; }; // // Types of events that can be sent between the Runtime Controller and // the Debugger Interface. Some of these events are one way only, while // others go both ways. The grouping of the event numbers is an attempt // to show this distinction and perhaps even allow generic operations // based on the type of the event. // enum DebuggerIPCEventType { #define IPC_EVENT_TYPE0(type, val) type = val, #define IPC_EVENT_TYPE1(type, val) type = val, #define IPC_EVENT_TYPE2(type, val) type = val, #include "dbgipceventtypes.h" #undef IPC_EVENT_TYPE2 #undef IPC_EVENT_TYPE1 #undef IPC_EVENT_TYPE0 }; #ifdef _DEBUG // This is a static debugging structure to help breaking at the right place. // Debug only. This is to track the number of events that have been happened so far. // User can choose to set break point base on the number of events. // Variables are named as the event name with prefix m_iDebugCount. For example // m_iDebugCount_DB_IPCE_BREAKPOINT if for event DB_IPCE_BREAKPOINT. struct MSLAYOUT DebugEventCounter { // we don't need the event type 0 #define IPC_EVENT_TYPE0(type, val) #define IPC_EVENT_TYPE1(type, val) int m_iDebugCount_##type; #define IPC_EVENT_TYPE2(type, val) int m_iDebugCount_##type; #include "dbgipceventtypes.h" #undef IPC_EVENT_TYPE2 #undef IPC_EVENT_TYPE1 #undef IPC_EVENT_TYPE0 }; #endif // _DEBUG #if !defined(DACCESS_COMPILE) struct MSLAYOUT IPCEventTypeNameMapping { DebuggerIPCEventType eventType; const char * eventName; }; extern const IPCEventTypeNameMapping DECLSPEC_SELECTANY DbgIPCEventTypeNames[] = { #define IPC_EVENT_TYPE0(type, val) { type, #type }, #define IPC_EVENT_TYPE1(type, val) { type, #type }, #define IPC_EVENT_TYPE2(type, val) { type, #type }, #include "dbgipceventtypes.h" #undef IPC_EVENT_TYPE2 #undef IPC_EVENT_TYPE1 #undef IPC_EVENT_TYPE0 { DB_IPCE_INVALID_EVENT, "DB_IPCE_Error" } }; const size_t nameCount = sizeof(DbgIPCEventTypeNames) / sizeof(DbgIPCEventTypeNames[0]); struct MSLAYOUT IPCENames // We use a class/struct so that the function can remain in a shared header file { static const DebuggerIPCEventType GetEventType(__in_z char * strEventType) { // pass in the string of event name and find the matching enum value // This is a linear search which is pretty slow. However, this is only used // at startup time when debug assert is turn on and with registry key set. So it is not that bad. // for (size_t i = 0; i < nameCount; i++) { if (_stricmp(DbgIPCEventTypeNames[i].eventName, strEventType) == 0) return DbgIPCEventTypeNames[i].eventType; } return DB_IPCE_INVALID_EVENT; } static const char * GetName(DebuggerIPCEventType eventType) { enum DbgIPCEventTypeNum { #define IPC_EVENT_TYPE0(type, val) type##_Num, #define IPC_EVENT_TYPE1(type, val) type##_Num, #define IPC_EVENT_TYPE2(type, val) type##_Num, #include "dbgipceventtypes.h" #undef IPC_EVENT_TYPE2 #undef IPC_EVENT_TYPE1 #undef IPC_EVENT_TYPE0 }; unsigned int i, lim; if (eventType < DB_IPCE_DEBUGGER_FIRST) { i = DB_IPCE_RUNTIME_FIRST_Num + 1; lim = DB_IPCE_DEBUGGER_FIRST_Num; } else { i = DB_IPCE_DEBUGGER_FIRST_Num + 1; lim = nameCount; } for (/**/; i < lim; i++) { if (DbgIPCEventTypeNames[i].eventType == eventType) return DbgIPCEventTypeNames[i].eventName; } return DbgIPCEventTypeNames[nameCount - 1].eventName; } }; #endif // !DACCESS_COMPILE // // NOTE: CPU-specific values below! // // DebuggerREGDISPLAY is very similar to the EE REGDISPLAY structure. It holds // register values that can be saved over calls for each frame in a stack // trace. // // DebuggerIPCE_FloatCount is the number of doubles in the processor's // floating point stack. // // Note: We used to just pass the values of the registers for each frame to the Right Side, but I had to add in the // address of each register, too, to support using enregistered variables on non-leaf frames as args to a func eval. Its // very, very possible that we would rework the entire code base to just use the register's address instead of passing // both, but its way, way too late in V1 to undertake that, so I'm just using these addresses to suppport our one func // eval case. Clearly, this needs to be cleaned up post V1. // // -- Fri Feb 09 11:21:24 2001 // struct MSLAYOUT DebuggerREGDISPLAY { #if defined(DBG_TARGET_X86) #define DebuggerIPCE_FloatCount 8 SIZE_T Edi; void *pEdi; SIZE_T Esi; void *pEsi; SIZE_T Ebx; void *pEbx; SIZE_T Edx; void *pEdx; SIZE_T Ecx; void *pEcx; SIZE_T Eax; void *pEax; SIZE_T FP; void *pFP; SIZE_T SP; SIZE_T PC; #elif defined(DBG_TARGET_AMD64) #define DebuggerIPCE_FloatCount 16 SIZE_T Rax; void *pRax; SIZE_T Rcx; void *pRcx; SIZE_T Rdx; void *pRdx; SIZE_T Rbx; void *pRbx; SIZE_T Rbp; void *pRbp; SIZE_T Rsi; void *pRsi; SIZE_T Rdi; void *pRdi; SIZE_T R8; void *pR8; SIZE_T R9; void *pR9; SIZE_T R10; void *pR10; SIZE_T R11; void *pR11; SIZE_T R12; void *pR12; SIZE_T R13; void *pR13; SIZE_T R14; void *pR14; SIZE_T R15; void *pR15; SIZE_T SP; SIZE_T PC; #elif defined(DBG_TARGET_ARM) #define DebuggerIPCE_FloatCount 32 SIZE_T R0; void *pR0; SIZE_T R1; void *pR1; SIZE_T R2; void *pR2; SIZE_T R3; void *pR3; SIZE_T R4; void *pR4; SIZE_T R5; void *pR5; SIZE_T R6; void *pR6; SIZE_T R7; void *pR7; SIZE_T R8; void *pR8; SIZE_T R9; void *pR9; SIZE_T R10; void *pR10; SIZE_T R11; void *pR11; SIZE_T R12; void *pR12; SIZE_T SP; void *pSP; SIZE_T LR; void *pLR; SIZE_T PC; void *pPC; #elif defined(DBG_TARGET_ARM64) #define DebuggerIPCE_FloatCount 32 SIZE_T X[29]; SIZE_T SP; SIZE_T FP; SIZE_T LR; SIZE_T PC; #else #define DebuggerIPCE_FloatCount 1 SIZE_T PC; SIZE_T FP; SIZE_T SP; void *pFP; #endif }; inline LPVOID GetSPAddress(const DebuggerREGDISPLAY * display) { return (LPVOID)&display->SP; } #if !defined(DBG_TARGET_AMD64) && !defined(DBG_TARGET_ARM) inline LPVOID GetFPAddress(const DebuggerREGDISPLAY * display) { return (LPVOID)&display->FP; } #endif // !DBG_TARGET_AMD64 class MSLAYOUT FramePointer { friend bool IsCloserToLeaf(FramePointer fp1, FramePointer fp2); friend bool IsCloserToRoot(FramePointer fp1, FramePointer fp2); friend bool IsEqualOrCloserToLeaf(FramePointer fp1, FramePointer fp2); friend bool IsEqualOrCloserToRoot(FramePointer fp1, FramePointer fp2); public: static FramePointer MakeFramePointer(LPVOID sp) { LIMITED_METHOD_DAC_CONTRACT; FramePointer fp; fp.m_sp = sp; return fp; } static FramePointer MakeFramePointer(UINT_PTR sp) { SUPPORTS_DAC; return MakeFramePointer((LPVOID)sp); } inline bool operator==(FramePointer fp) { return (m_sp == fp.m_sp); } inline bool operator!=(FramePointer fp) { return !(*this == fp); } // This is needed because on the RS, the m_id values of CordbFrame and // CordbChain are really FramePointers. LPVOID GetSPValue() const { return m_sp; } private: // Declare some private constructors which signatures matching common usage of FramePointer // to prevent people from accidentally assigning a pointer to a FramePointer(). FramePointer &operator=(LPVOID sp); FramePointer &operator=(BYTE* sp); FramePointer &operator=(const BYTE* sp); LPVOID m_sp; }; // For non-IA64 platforms, we use stack pointers as frame pointers. // (Stack grows towards smaller address.) #define LEAF_MOST_FRAME FramePointer::MakeFramePointer((LPVOID)NULL) #define ROOT_MOST_FRAME FramePointer::MakeFramePointer((LPVOID)-1) static_assert_no_msg(sizeof(FramePointer) == sizeof(void*)); inline bool IsCloserToLeaf(FramePointer fp1, FramePointer fp2) { return (fp1.m_sp < fp2.m_sp); } inline bool IsCloserToRoot(FramePointer fp1, FramePointer fp2) { return (fp1.m_sp > fp2.m_sp); } inline bool IsEqualOrCloserToLeaf(FramePointer fp1, FramePointer fp2) { return !IsCloserToRoot(fp1, fp2); } inline bool IsEqualOrCloserToRoot(FramePointer fp1, FramePointer fp2) { return !IsCloserToLeaf(fp1, fp2); } // struct DebuggerIPCE_FuncData: DebuggerIPCE_FuncData holds data // to describe a given function, its // class, and a little bit about the code for the function. This is used // in the stack trace result data to pass function information back that // may be needed. Its also used when getting data about a specific function. // // void* nativeStartAddressPtr: Ptr to CORDB_ADDRESS, which is // the address of the real start address of the native code. // This field will be NULL only if the method hasn't been JITted // yet (and thus no code is available). Otherwise, it will be // the adress of a CORDB_ADDRESS in the remote memory. This // CORDB_ADDRESS may be NULL, in which case the code is unavailable // has been pitched (return CORDBG_E_CODE_NOT_AVAILABLE) // // SIZE_T nVersion: The version of the code that this instance of the // function is using. struct MSLAYOUT DebuggerIPCE_FuncData { mdMethodDef funcMetadataToken; VMPTR_DomainFile vmDomainFile; mdTypeDef classMetadataToken; void* ilStartAddress; SIZE_T ilSize; SIZE_T currentEnCVersion; mdSignature localVarSigToken; }; // struct DebuggerIPCE_JITFuncData: DebuggerIPCE_JITFuncData holds // a little bit about the JITted code for the function. // // void* nativeStartAddressPtr: Ptr to CORDB_ADDRESS, which is // the address of the real start address of the native code. // This field will be NULL only if the method hasn't been JITted // yet (and thus no code is available). Otherwise, it will be // the address of a CORDB_ADDRESS in the remote memory. This // CORDB_ADDRESS may be NULL, in which case the code is unavailable // or has been pitched (return CORDBG_E_CODE_NOT_AVAILABLE) // // SIZE_T nativeSize: Size of the native code. // // SIZE_T nativeOffset: Offset from the beginning of the function, // in bytes. This may be non-zero even when nativeStartAddressPtr // is NULL // void * nativeCodeJITInfoToken: An opaque value to hand back to the left // side when fetching the JITInfo for the native code, i.e. the // IL->native maps for the variables. This may be NULL if no JITInfo is available. // void * nativeCodeMethodDescToken: An opaque value to hand back to the left // side when fetching the code. In addition this token can act as the // unique identity for the native code in the case where there are // multiple blobs of native code per IL method (i.e. if the method is // generic code of some kind) // BOOL isInstantiatedGeneric: Indicates if the method is // generic code of some kind. // BOOL jsutAfterILThrow: indicates that code just threw a software exception and // nativeOffset points to an instruction just after [call IL_Throw]. // This is being used to figure out a real offset of the exception origin. // By subtracting STACKWALK_CONTROLPC_ADJUST_OFFSET from nativeOffset you can get // an address somewhere inside [call IL_Throw] instruction. // void *ilToNativeMapAddr etc.: If nativeCodeJITInfoToken is not NULL then these // specify the table giving the mapping of IPs. struct MSLAYOUT DebuggerIPCE_JITFuncData { TADDR nativeStartAddressPtr; SIZE_T nativeHotSize; // If we have a cold region, need its size & the pointer to where starts. TADDR nativeStartAddressColdPtr; SIZE_T nativeColdSize; SIZE_T nativeOffset; LSPTR_DJI nativeCodeJITInfoToken; VMPTR_MethodDesc vmNativeCodeMethodDescToken; #ifdef WIN64EXCEPTIONS BOOL fIsFilterFrame; SIZE_T parentNativeOffset; FramePointer fpParentOrSelf; #endif // WIN64EXCEPTIONS // indicates if the MethodDesc is a generic function or a method inside a generic class (or // both!). BOOL isInstantiatedGeneric; // this is the version of the jitted code SIZE_T enCVersion; BOOL jsutAfterILThrow; }; // // DebuggerIPCE_STRData holds data for each stack frame or chain. This data is passed // from the RC to the DI during a stack walk. // #if defined(_MSC_VER) #pragma warning( push ) #pragma warning( disable:4324 ) // the compiler pads a structure to comply with alignment requirements #endif // ARM context structures have a 16-byte alignment requirement struct MSLAYOUT DebuggerIPCE_STRData { FramePointer fp; // @dbgtodo stackwalker/shim- Ideally we should be able to get rid of the DebuggerREGDISPLAY and just use the CONTEXT. DT_CONTEXT ctx; DebuggerREGDISPLAY rd; bool quicklyUnwound; VMPTR_AppDomain vmCurrentAppDomainToken; enum EType { cMethodFrame = 0, cChain, cStubFrame, cRuntimeNativeFrame } eType; union MSLAYOUT { // Data for a chain struct MSLAYOUT { CorDebugChainReason chainReason; bool managed; } u; // Data for a Method struct MSLAYOUT { struct DebuggerIPCE_FuncData funcData; struct DebuggerIPCE_JITFuncData jitFuncData; SIZE_T ILOffset; CorDebugMappingResult mapping; bool fVarArgs; // Indicates whether the managed method has any metadata. // Some dynamic methods such as IL stubs and LCG methods don't have any metadata. // This is used only by the V3 stackwalker, not the V2 one, because we only // expose dynamic methods as real stack frames in V3. bool fNoMetadata; TADDR taAmbientESP; GENERICS_TYPE_TOKEN exactGenericArgsToken; DWORD dwExactGenericArgsTokenIndex; } v; // Data for an Stub Frame. struct MSLAYOUT { mdMethodDef funcMetadataToken; VMPTR_DomainFile vmDomainFile; VMPTR_MethodDesc vmMethodDesc; CorDebugInternalFrameType frameType; } stubFrame; }; }; #if defined(_MSC_VER) #pragma warning( pop ) #endif // // DebuggerIPCE_BasicTypeData and DebuggerIPCE_ExpandedTypeData // hold data for each type sent across the // boundary, whether it be a constructed type List or a non-constructed // type such as String, Foo or Object. // // Logically speaking DebuggerIPCE_BasicTypeData might just be "typeHandle", as // we could then send further events to ask what the elementtype, typeToken and moduleToken // are for the type handle. But as // nearly all types are non-generic we send across even the basic type information in // the slightly expanded form shown below, sending the element type and the // tokens with the type handle itself. The fields debuggerModuleToken, metadataToken and typeHandle // are only used as follows: // elementType debuggerModuleToken metadataToken typeHandle // E_T_INT8 : E_T_INT8 No No No // Boxed E_T_INT8: E_T_CLASS No No No // E_T_CLASS, non-generic class: E_T_CLASS Yes Yes No // E_T_VALUETYPE, non-generic: E_T_VALUETYPE Yes Yes No // E_T_CLASS, generic class: E_T_CLASS Yes Yes Yes // E_T_VALUETYPE, generic class: E_T_VALUETYPE Yes Yes Yes // E_T_BYREF : E_T_BYREF No No Yes // E_T_PTR : E_T_PTR No No Yes // E_T_ARRAY etc. : E_T_ARRAY No No Yes // E_T_FNPTR etc. : E_T_FNPTR No No Yes // This allows us to always set "typeHandle" to NULL except when dealing with highly nested // types or function-pointer types (the latter are too complexe to transfer over in one hit). // struct MSLAYOUT DebuggerIPCE_BasicTypeData { CorElementType elementType; mdTypeDef metadataToken; VMPTR_Module vmModule; VMPTR_DomainFile vmDomainFile; VMPTR_TypeHandle vmTypeHandle; }; // DebuggerIPCE_ExpandedTypeData contains more information showing further // details for array types, byref types etc. // Whenever you fetch type information from the left-side // you get back one of these. These in turn contain further // DebuggerIPCE_BasicTypeData's and typeHandles which you can // then query to get further information about the type parameters. // This copes with the nested cases, e.g. jagged arrays, // String ****, &(String*), Pair> // and so on. // // So this type information is not "fully expanded", it's just a little // more detail then DebuggerIPCE_BasicTypeData. For type // instantiatons (e.g. List) and // function pointer types you will need to make further requests for // information about the type parameters. // For array types there is always only one type parameter so // we include that as part of the expanded data. // // struct MSLAYOUT DebuggerIPCE_ExpandedTypeData { CorElementType elementType; // Note this is _never_ E_T_VAR, E_T_WITH or E_T_MVAR union MSLAYOUT { // used for E_T_CLASS and E_T_VALUECLASS, E_T_PTR, E_T_BYREF etc. // For non-constructed E_T_CLASS or E_T_VALUECLASS the tokens will be set and the typeHandle will be NULL // For constructed E_T_CLASS or E_T_VALUECLASS the tokens will be set and the typeHandle will be non-NULL // For E_T_PTR etc. the tokens will be NULL and the typeHandle will be non-NULL. struct MSLAYOUT { mdTypeDef metadataToken; VMPTR_Module vmModule; VMPTR_DomainFile vmDomainFile; VMPTR_TypeHandle typeHandle; // if non-null then further fetches will be needed to get type arguments } ClassTypeData; // used for E_T_PTR, E_T_BYREF etc. struct MSLAYOUT { DebuggerIPCE_BasicTypeData unaryTypeArg; // used only when sending back to debugger } UnaryTypeData; // used for E_T_ARRAY etc. struct MSLAYOUT { DebuggerIPCE_BasicTypeData arrayTypeArg; // used only when sending back to debugger DWORD arrayRank; } ArrayTypeData; // used for E_T_FNPTR struct MSLAYOUT { VMPTR_TypeHandle typeHandle; // if non-null then further fetches needed to get type arguments } NaryTypeData; }; }; // DebuggerIPCE_TypeArgData is used when sending type arguments // across to a funceval. It contains the DebuggerIPCE_ExpandedTypeData describing the // essence of the type, but the typeHandle and other // BasicTypeData fields should be zero and will be ignored. // The DebuggerIPCE_ExpandedTypeData is then followed // by the required number of type arguments, each of which // will be a further DebuggerIPCE_TypeArgData record in the stream of // flattened type argument data. struct MSLAYOUT DebuggerIPCE_TypeArgData { DebuggerIPCE_ExpandedTypeData data; unsigned int numTypeArgs; // number of immediate children on the type tree }; // // DebuggerIPCE_ObjectData holds the results of a // GetAndSendObjectInfo, i.e., all the info about an object that the // Right Side would need to access it. (This include array, string, // and nstruct info.) // struct MSLAYOUT DebuggerIPCE_ObjectData { void *objRef; bool objRefBad; SIZE_T objSize; // Offset from the beginning of the object to the beginning of the first field SIZE_T objOffsetToVars; // The type of the object.... struct DebuggerIPCE_ExpandedTypeData objTypeData; union MSLAYOUT { struct MSLAYOUT { SIZE_T length; SIZE_T offsetToStringBase; } stringInfo; struct MSLAYOUT { SIZE_T rank; SIZE_T offsetToArrayBase; SIZE_T offsetToLowerBounds; // 0 if not present SIZE_T offsetToUpperBounds; // 0 if not present SIZE_T componentCount; SIZE_T elementSize; } arrayInfo; struct MSLAYOUT { struct DebuggerIPCE_BasicTypeData typedByrefType; // the type of the thing contained in a typedByref... } typedByrefInfo; }; }; // // Remote enregistered info used by CordbValues and for passing // variable homes between the left and right sides during a func eval. // enum RemoteAddressKind { RAK_NONE = 0, RAK_REG, RAK_REGREG, RAK_REGMEM, RAK_MEMREG, RAK_FLOAT, RAK_END }; const CORDB_ADDRESS kLeafFrameRegAddr = 0; const CORDB_ADDRESS kNonLeafFrameRegAddr = (CORDB_ADDRESS)(-1); struct MSLAYOUT RemoteAddress { RemoteAddressKind kind; void *frame; CorDebugRegister reg1; void *reg1Addr; SIZE_T reg1Value; // this is the actual value of the register union MSLAYOUT { struct MSLAYOUT { CorDebugRegister reg2; void *reg2Addr; SIZE_T reg2Value; // this is the actual value of the register } u; CORDB_ADDRESS addr; DWORD floatIndex; }; }; // // DebuggerIPCE_FuncEvalType specifies the type of a function // evaluation that will occur. // enum DebuggerIPCE_FuncEvalType { DB_IPCE_FET_NORMAL, DB_IPCE_FET_NEW_OBJECT, DB_IPCE_FET_NEW_OBJECT_NC, DB_IPCE_FET_NEW_STRING, DB_IPCE_FET_NEW_ARRAY, DB_IPCE_FET_RE_ABORT }; enum NameChangeType { APP_DOMAIN_NAME_CHANGE, THREAD_NAME_CHANGE }; // // DebuggerIPCE_FuncEvalArgData holds data for each argument to a // function evaluation. // struct MSLAYOUT DebuggerIPCE_FuncEvalArgData { RemoteAddress argHome; // enregistered variable home void *argAddr; // address if not enregistered CorElementType argElementType; unsigned int fullArgTypeNodeCount; // Pointer to LS (DebuggerIPCE_TypeArgData *) buffer holding full description of the argument type (if needed - only needed for struct types) void *fullArgType; // Pointer to LS (DebuggerIPCE_TypeArgData *) buffer holding full description of the argument type (if needed - only needed for struct types) BYTE argLiteralData[8]; // copy of generic value data bool argIsLiteral; // true if value is in argLiteralData bool argIsHandleValue; // true if argAddr is OBJECTHANDLE }; // // DebuggerIPCE_FuncEvalInfo holds info necessary to setup a func eval // operation. // struct MSLAYOUT DebuggerIPCE_FuncEvalInfo { VMPTR_Thread vmThreadToken; DebuggerIPCE_FuncEvalType funcEvalType; mdMethodDef funcMetadataToken; mdTypeDef funcClassMetadataToken; VMPTR_DomainFile vmDomainFile; RSPTR_CORDBEVAL funcEvalKey; bool evalDuringException; unsigned int argCount; unsigned int genericArgsCount; unsigned int genericArgsNodeCount; SIZE_T stringSize; SIZE_T arrayRank; }; // // Used in DebuggerIPCFirstChanceData. This tells the LS what action to take within the hijack // enum HijackAction { HIJACK_ACTION_EXIT_UNHANDLED, HIJACK_ACTION_EXIT_HANDLED, HIJACK_ACTION_WAIT }; // // DebuggerIPCFirstChanceData holds info communicated from the LS to the RS when signaling that an exception does not // belong to the runtime from a first chance hijack. This is used when Win32 debugging only. // struct MSLAYOUT DebuggerIPCFirstChanceData { LSPTR_CONTEXT pLeftSideContext; HijackAction action; UINT debugCounter; }; // // DebuggerIPCSecondChanceData holds info communicated from the RS // to the LS when setting up a second chance exception hijack. This is // used when Win32 debugging only. // struct MSLAYOUT DebuggerIPCSecondChanceData { DT_CONTEXT threadContext; }; //----------------------------------------------------------------------------- // This struct holds pointer from the LS and needs to copy to // the RS. We have to free the memory on the RS. // The transfer function is called when the RS first reads the event. At this point, // the LS is stopped while sending the event. Thus the LS pointers only need to be // valid while the LS is in SendIPCEvent. // // Since this data is in an IPC/Marshallable block, it can't have any Ctors (holders) // in it. //----------------------------------------------------------------------------- struct MSLAYOUT Ls_Rs_BaseBuffer { #ifdef RIGHT_SIDE_COMPILE protected: // copy data can happen on both LS and RS. In LS case, // ReadProcessMemory is really reading from its own process memory. // void CopyLSDataToRSWorker(ICorDebugDataTarget * pTargethProcess); // retrieve the RS data and own it BYTE *TransferRSDataWorker() { BYTE *pbRS = m_pbRS; m_pbRS = NULL; return pbRS; } public: void CleanUp() { if (m_pbRS != NULL) { delete [] m_pbRS; m_pbRS = NULL; } } #else public: // Only LS can call this API void SetLsData(BYTE *pbLS, DWORD cbSize) { m_pbRS = NULL; m_pbLS = pbLS; m_cbSize = cbSize; } #endif // RIGHT_SIDE_COMPILE public: // Common APIs. DWORD GetSize() { return m_cbSize; } protected: // Size of data in bytes DWORD m_cbSize; // If this is non-null, pointer into LS for buffer. // LS can free this after the debug event is continued. BYTE *m_pbLS; // @dbgtodo cross-plat- for cross-platform purposes, this should be a TADDR // If this is non-null, pointer into RS for buffer. RS must then free this. // This buffer was copied from the LS (via CopyLSDataToRSWorker). BYTE *m_pbRS; }; //----------------------------------------------------------------------------- // Byte wrapper around the buffer. //----------------------------------------------------------------------------- struct MSLAYOUT Ls_Rs_ByteBuffer : public Ls_Rs_BaseBuffer { #ifdef RIGHT_SIDE_COMPILE BYTE *GetRSPointer() { return m_pbRS; } void CopyLSDataToRS(ICorDebugDataTarget * pTarget); BYTE *TransferRSData() { return TransferRSDataWorker(); } #endif }; //----------------------------------------------------------------------------- // Wrapper around a Ls_rS_Buffer to get it as a string. // This can also do some sanity checking. //----------------------------------------------------------------------------- struct MSLAYOUT Ls_Rs_StringBuffer : public Ls_Rs_BaseBuffer { #ifdef RIGHT_SIDE_COMPILE const WCHAR * GetString() { return reinterpret_cast (m_pbRS); } // Copy over the string. void CopyLSDataToRS(ICorDebugDataTarget * pTarget); // Caller will pick up ownership. // Since caller will delete this data, we can't give back a constant pointer. WCHAR * TransferStringData() { return reinterpret_cast (TransferRSDataWorker()); } #endif }; // Data for an Managed Debug Assistant Probe (MDA). struct MSLAYOUT DebuggerMDANotification { Ls_Rs_StringBuffer szName; Ls_Rs_StringBuffer szDescription; Ls_Rs_StringBuffer szXml; DWORD dwOSThreadId; CorDebugMDAFlags flags; }; // The only remaining problem is that register number mappings are different for each platform. It turns out // that the debugger only uses REGNUM_SP and REGNUM_AMBIENT_SP though, so we can just virtualize these two for // the target platform. // Keep this is sync with the definitions in inc/corinfo.h. #if defined(DBG_TARGET_X86) #define DBG_TARGET_REGNUM_SP 4 #define DBG_TARGET_REGNUM_AMBIENT_SP 9 #ifdef _TARGET_X86_ static_assert_no_msg(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP); static_assert_no_msg(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); #endif // _TARGET_X86_ #elif defined(DBG_TARGET_AMD64) #define DBG_TARGET_REGNUM_SP 4 #define DBG_TARGET_REGNUM_AMBIENT_SP 17 #ifdef _TARGET_AMD64_ static_assert_no_msg(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP); static_assert_no_msg(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); #endif // _TARGET_AMD64_ #elif defined(DBG_TARGET_ARM) #define DBG_TARGET_REGNUM_SP 13 #define DBG_TARGET_REGNUM_AMBIENT_SP 17 #ifdef _TARGET_ARM_ C_ASSERT(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP); C_ASSERT(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); #endif // _TARGET_ARM_ #elif defined(DBG_TARGET_ARM64) #define DBG_TARGET_REGNUM_SP 31 #define DBG_TARGET_REGNUM_AMBIENT_SP 34 #ifdef _TARGET_ARM64_ C_ASSERT(DBG_TARGET_REGNUM_SP == ICorDebugInfo::REGNUM_SP); C_ASSERT(DBG_TARGET_REGNUM_AMBIENT_SP == ICorDebugInfo::REGNUM_AMBIENT_SP); #endif // _TARGET_ARM64_ #else #error Target registers are not defined for this platform #endif // // Event structure that is passed between the Runtime Controller and the // Debugger Interface. Some types of events are a fixed size and have // entries in the main union, while others are variable length and have // more specialized data structures that are attached to the end of this // structure. // struct MSLAYOUT DebuggerIPCEvent { DebuggerIPCEvent* next; DebuggerIPCEventType type; DWORD processId; VMPTR_AppDomain vmAppDomain; VMPTR_Thread vmThread; HRESULT hr; bool replyRequired; bool asyncSend; union MSLAYOUT { struct MSLAYOUT { // Pointer to a BOOL in the target. CORDB_ADDRESS pfBeingDebugged; } LeftSideStartupData; struct MSLAYOUT { // Module whos metadata is being updated // This tells the RS that the metadata for that module has become invalid. VMPTR_DomainFile vmDomainFile; } MetadataUpdateData; struct MSLAYOUT { // Handle to CLR's internal appdomain object. VMPTR_AppDomain vmAppDomain; } AppDomainData; struct MSLAYOUT { VMPTR_DomainAssembly vmDomainAssembly; } AssemblyData; #ifdef TEST_DATA_CONSISTENCY // information necessary for testing whether the LS holds a lock on data // the RS needs to inspect. See code:DataTest::TestDataSafety and // code:IDacDbiInterface::TestCrst for more information struct MSLAYOUT { // the lock to be tested VMPTR_Crst vmCrst; // indicates whether the LS holds the lock bool fOkToTake; } TestCrstData; // information necessary for testing whether the LS holds a lock on data // the RS needs to inspect. See code:DataTest::TestDataSafety and // code:IDacDbiInterface::TestCrst for more information struct MSLAYOUT { // the lock to be tested VMPTR_SimpleRWLock vmRWLock; // indicates whether the LS holds the lock bool fOkToTake; } TestRWLockData; #endif // TEST_DATA_CONSISTENCY // Debug event that a module has been loaded struct MSLAYOUT { // Module that was just loaded. VMPTR_DomainFile vmDomainFile; }LoadModuleData; struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; LSPTR_ASSEMBLY debuggerAssemblyToken; } UnloadModuleData; // The given module's pdb has been updated. // Queury PDB from OOP struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; } UpdateModuleSymsData; DebuggerMDANotification MDANotification; struct MSLAYOUT { LSPTR_BREAKPOINT breakpointToken; mdMethodDef funcMetadataToken; VMPTR_DomainFile vmDomainFile; bool isIL; SIZE_T offset; SIZE_T encVersion; LSPTR_METHODDESC nativeCodeMethodDescToken; // points to the MethodDesc if !isIL } BreakpointData; struct MSLAYOUT { LSPTR_BREAKPOINT breakpointToken; } BreakpointSetErrorData; struct MSLAYOUT { LSPTR_STEPPER stepperToken; VMPTR_Thread vmThreadToken; FramePointer frameToken; bool stepIn; bool rangeIL; bool IsJMCStop; unsigned int totalRangeCount; CorDebugStepReason reason; CorDebugUnmappedStop rgfMappingStop; CorDebugIntercept rgfInterceptStop; unsigned int rangeCount; COR_DEBUG_STEP_RANGE range; //note that this is an array } StepData; struct MSLAYOUT { // An unvalidated GC-handle VMPTR_OBJECTHANDLE GCHandle; } GetGCHandleInfo; struct MSLAYOUT { // An unvalidated GC-handle for which we're returning the results LSPTR_OBJECTHANDLE GCHandle; // The following are initialized by the LS in response to our query: VMPTR_AppDomain vmAppDomain; // AD that handle is in (only applicable if fValid). bool fValid; // Did the LS determine the GC handle to be valid? } GetGCHandleInfoResult; // Allocate memory on the left-side struct MSLAYOUT { ULONG bufSize; // number of bytes to allocate } GetBuffer; // Memory allocated on the left-side struct MSLAYOUT { void *pBuffer; // LS pointer to the buffer allocated HRESULT hr; // success / failure } GetBufferResult; // Free a buffer allocated on the left-side with GetBuffer struct MSLAYOUT { void *pBuffer; // Pointer previously returned in GetBufferResult } ReleaseBuffer; struct MSLAYOUT { HRESULT hr; } ReleaseBufferResult; // Apply an EnC edit struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; // Module to edit DWORD cbDeltaMetadata; // size of blob pointed to by pDeltaMetadata CORDB_ADDRESS pDeltaMetadata; // pointer to delta metadata in debuggee // it's the RS's responsibility to allocate and free // this (and pDeltaIL) using GetBuffer / ReleaseBuffer CORDB_ADDRESS pDeltaIL; // pointer to delta IL in debugee DWORD cbDeltaIL; // size of blob pointed to by pDeltaIL } ApplyChanges; struct MSLAYOUT { HRESULT hr; } ApplyChangesResult; struct MSLAYOUT { mdTypeDef classMetadataToken; VMPTR_DomainFile vmDomainFile; LSPTR_ASSEMBLY classDebuggerAssemblyToken; } LoadClass; struct MSLAYOUT { mdTypeDef classMetadataToken; VMPTR_DomainFile vmDomainFile; LSPTR_ASSEMBLY classDebuggerAssemblyToken; } UnloadClass; struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; bool flag; } SetClassLoad; struct MSLAYOUT { VMPTR_OBJECTHANDLE vmExceptionHandle; bool firstChance; bool continuable; } Exception; struct MSLAYOUT { VMPTR_Thread vmThreadToken; } ClearException; struct MSLAYOUT { void *address; } IsTransitionStub; struct MSLAYOUT { bool isStub; } IsTransitionStubResult; struct MSLAYOUT { CORDB_ADDRESS startAddress; bool fCanSetIPOnly; VMPTR_Thread vmThreadToken; VMPTR_DomainFile vmDomainFile; mdMethodDef mdMethod; VMPTR_MethodDesc vmMethodDesc; SIZE_T offset; bool fIsIL; void * firstExceptionHandler; } SetIP; // this is also used for CanSetIP struct MSLAYOUT { int iLevel; EmbeddedIPCString szCategory; Ls_Rs_StringBuffer szContent; } FirstLogMessage; struct MSLAYOUT { int iLevel; int iReason; EmbeddedIPCString szSwitchName; EmbeddedIPCString szParentSwitchName; } LogSwitchSettingMessage; // information needed to send to the RS as part of a custom notification from the target struct MSLAYOUT { // Domain file for the domain in which the notification occurred VMPTR_DomainFile vmDomainFile; // metadata token for the type of the CustomNotification object's type mdTypeDef classToken; } CustomNotification; struct MSLAYOUT { VMPTR_Thread vmThreadToken; CorDebugThreadState debugState; } SetAllDebugState; DebuggerIPCE_FuncEvalInfo FuncEval; struct MSLAYOUT { CORDB_ADDRESS argDataArea; LSPTR_DEBUGGEREVAL debuggerEvalKey; } FuncEvalSetupComplete; struct MSLAYOUT { RSPTR_CORDBEVAL funcEvalKey; bool successful; bool aborted; void *resultAddr; // AppDomain that the result is in. VMPTR_AppDomain vmAppDomain; VMPTR_OBJECTHANDLE vmObjectHandle; DebuggerIPCE_ExpandedTypeData resultType; } FuncEvalComplete; struct MSLAYOUT { LSPTR_DEBUGGEREVAL debuggerEvalKey; } FuncEvalAbort; struct MSLAYOUT { LSPTR_DEBUGGEREVAL debuggerEvalKey; } FuncEvalRudeAbort; struct MSLAYOUT { LSPTR_DEBUGGEREVAL debuggerEvalKey; } FuncEvalCleanup; struct MSLAYOUT { void *objectRefAddress; VMPTR_OBJECTHANDLE vmObjectHandle; void *newReference; } SetReference; struct MSLAYOUT { NameChangeType eventType; VMPTR_AppDomain vmAppDomain; VMPTR_Thread vmThread; } NameChange; struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; BOOL fAllowJitOpts; BOOL fEnableEnC; } JitDebugInfo; // EnC Remap opportunity struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; mdMethodDef funcMetadataToken ; // methodDef of function with remap opportunity SIZE_T currentVersionNumber; // version currently executing SIZE_T resumeVersionNumber; // latest version SIZE_T currentILOffset; // the IL offset of the current IP SIZE_T *resumeILOffset; // pointer into left-side where an offset to resume // to should be written if remap is desired. } EnCRemap; // EnC Remap has taken place struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; mdMethodDef funcMetadataToken; // methodDef of function that was remapped } EnCRemapComplete; // Notification that the LS is about to update a CLR data structure to account for a // specific edit made by EnC (function add/update or field add). struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; mdToken memberMetadataToken; // Either a methodDef token indicating the function that // was updated/added, or a fieldDef token indicating the // field which was added. mdTypeDef classMetadataToken; // TypeDef token of the class in which the update was made SIZE_T newVersionNumber; // The new function/module version } EnCUpdate; struct MSLAYOUT { void *oldData; void *newData; DebuggerIPCE_BasicTypeData type; } SetValueClass; // Event used to tell LS if a single function is user or non-user code. // Same structure used to get function status. // @todo - Perhaps we can bundle these up so we can set multiple funcs w/ 1 event? struct MSLAYOUT { VMPTR_DomainFile vmDomainFile; mdMethodDef funcMetadataToken; DWORD dwStatus; } SetJMCFunctionStatus; struct MSLAYOUT { TASKID taskid; } GetThreadForTaskId; struct MSLAYOUT { VMPTR_Thread vmThreadToken; } GetThreadForTaskIdResult; struct MSLAYOUT { CONNID connectionId; } ConnectionChange; struct MSLAYOUT { CONNID connectionId; EmbeddedIPCString wzConnectionName; } CreateConnection; struct MSLAYOUT { void *objectToken; BOOL fStrong; } CreateHandle; struct MSLAYOUT { VMPTR_OBJECTHANDLE vmObjectHandle; } CreateHandleResult; // used in DB_IPCE_DISPOSE_HANDLE event struct MSLAYOUT { VMPTR_OBJECTHANDLE vmObjectHandle; BOOL fStrong; } DisposeHandle; struct MSLAYOUT { FramePointer framePointer; SIZE_T nOffset; CorDebugExceptionCallbackType eventType; DWORD dwFlags; VMPTR_OBJECTHANDLE vmExceptionHandle; } ExceptionCallback2; struct MSLAYOUT { CorDebugExceptionUnwindCallbackType eventType; DWORD dwFlags; } ExceptionUnwind; struct MSLAYOUT { VMPTR_Thread vmThreadToken; FramePointer frameToken; } InterceptException; struct MSLAYOUT { VMPTR_Module vmModule; void * pMetadataStart; ULONG nMetadataSize; } MetadataUpdateRequest; }; }; // When using a network transport rather than shared memory buffers CorDBIPC_BUFFER_SIZE is the upper bound // for a single DebuggerIPCEvent structure. This now relates to the maximal size of a network message and is // orthogonal to the host's page size. Round the buffer size up to a multiple of 8 since MSVC seems more // aggressive in this regard than gcc. #define CorDBIPC_TRANSPORT_BUFFER_SIZE (((sizeof(DebuggerIPCEvent) + 7) / 8) * 8) // A DebuggerIPCEvent must fit in the send & receive buffers, which are CorDBIPC_BUFFER_SIZE bytes. static_assert_no_msg(sizeof(DebuggerIPCEvent) <= CorDBIPC_BUFFER_SIZE); static_assert_no_msg(CorDBIPC_TRANSPORT_BUFFER_SIZE <= CorDBIPC_BUFFER_SIZE); // 2*sizeof(WCHAR) for the two string terminating characters in the FirstLogMessage #define LOG_MSG_PADDING 4 #endif /* _DbgIPCEvents_h_ */