diff options
author | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
---|---|---|
committer | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
commit | 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch) | |
tree | 98110734c91668dfdbb126fcc0e15ddbd93738ca /src/debug/inc | |
parent | fa45f57ed55137c75ac870356a1b8f76c84b229c (diff) | |
download | coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2 coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip |
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/debug/inc')
29 files changed, 10824 insertions, 0 deletions
diff --git a/src/debug/inc/.gitmirror b/src/debug/inc/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/debug/inc/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/debug/inc/amd64/.gitmirror b/src/debug/inc/amd64/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/debug/inc/amd64/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/debug/inc/amd64/primitives.h b/src/debug/inc/amd64/primitives.h new file mode 100644 index 0000000000..0b13670c4a --- /dev/null +++ b/src/debug/inc/amd64/primitives.h @@ -0,0 +1,257 @@ +// 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. +//***************************************************************************** +// File: primitives.h +// + +// +// Platform-specific debugger primitives +// +//***************************************************************************** + +#ifndef PRIMITIVES_H_ +#define PRIMITIVES_H_ + +#ifndef CORDB_ADDRESS_TYPE +typedef const BYTE CORDB_ADDRESS_TYPE; +typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; +#endif +//This is an abstraction to keep x86/ia64 patch data separate +#ifndef PRD_TYPE +#define PRD_TYPE DWORD_PTR +#endif + +typedef M128A FPRegister64; + +// From section 1.1 of AMD64 Programmers Manual Vol 3. +#define MAX_INSTRUCTION_LENGTH 15 + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to lend somewhere in a call instruction. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 1 + +#define CORDbg_BREAK_INSTRUCTION_SIZE 1 +#define CORDbg_BREAK_INSTRUCTION (BYTE)0xCC + +inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr) +{ + LIMITED_METHOD_DAC_CONTRACT; + return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE; +} + + +#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION +#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION) + +#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr)))); + +#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \ + CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))), (_opcode)); + +#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr)))); + + +SELECTANY const CorDebugRegister g_JITToCorDbgReg[] = +{ + REGISTER_AMD64_RAX, + REGISTER_AMD64_RCX, + REGISTER_AMD64_RDX, + REGISTER_AMD64_RBX, + REGISTER_AMD64_RSP, + REGISTER_AMD64_RBP, + REGISTER_AMD64_RSI, + REGISTER_AMD64_RDI, + REGISTER_AMD64_R8, + REGISTER_AMD64_R9, + REGISTER_AMD64_R10, + REGISTER_AMD64_R11, + REGISTER_AMD64_R12, + REGISTER_AMD64_R13, + REGISTER_AMD64_R14, + REGISTER_AMD64_R15 +}; + +// +// Mapping from ICorDebugInfo register numbers to CorDebugRegister +// numbers. Note: this must match the order in corinfo.h. +// +inline CorDebugRegister ConvertRegNumToCorDebugRegister(ICorDebugInfo::RegNum reg) +{ + _ASSERTE(reg >= 0); + _ASSERTE(static_cast<size_t>(reg) < _countof(g_JITToCorDbgReg)); + return g_JITToCorDbgReg[reg]; +} + + +// +// inline function to access/modify the CONTEXT +// +inline LPVOID CORDbgGetIP(DT_CONTEXT* context) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + return (LPVOID) context->Rip; +} + +inline void CORDbgSetIP(DT_CONTEXT* context, LPVOID rip) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + context->Rip = (DWORD64) rip; +} + +inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + return (LPVOID)context->Rsp; +} +inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID rsp) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + + PRECONDITION(CheckPointer(context)); + } + CONTRACTL_END; + + context->Rsp = (UINT_PTR)rsp; +} + +// AMD64 has no frame pointer stored in RBP +#define CORDbgSetFP(context, rbp) +#define CORDbgGetFP(context) 0 + +// compare the RIP, RSP, and RBP +inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) +{ + LIMITED_METHOD_DAC_CONTRACT; + + if ((pCtx1->Rip == pCtx2->Rip) && + (pCtx1->Rsp == pCtx2->Rsp) && + (pCtx1->Rbp == pCtx2->Rbp)) + { + return TRUE; + } + else + { + return FALSE; + } +} + +/* ========================================================================= */ +// +// Routines used by debugger support functions such as codepatch.cpp or +// exception handling code. +// +// GetInstruction, InsertBreakpoint, and SetInstruction all operate on +// a _single_ byte of memory. This is really important. If you only +// save one byte from the instruction stream before placing a breakpoint, +// you need to make sure to only replace one byte later on. +// + + +inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + return *address; // retrieving only one byte is important +} + +inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address) +{ + LIMITED_METHOD_CONTRACT; + + *((unsigned char*)address) = 0xCC; // int 3 (single byte patch) + FlushInstructionCache(GetCurrentProcess(), address, 1); + +} + +inline void CORDbgSetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address, + PRD_TYPE instruction) +{ + // In a DAC build, this function assumes the input is an host address. + LIMITED_METHOD_DAC_CONTRACT; + + *((unsigned char*)address) = + (unsigned char) instruction; // setting one byte is important + FlushInstructionCache(GetCurrentProcess(), address, 1); + +} + + +inline void CORDbgAdjustPCForBreakInstruction(DT_CONTEXT* pContext) +{ + LIMITED_METHOD_CONTRACT; + + pContext->Rip -= 1; +} + +inline bool AddressIsBreakpoint(CORDB_ADDRESS_TYPE *address) +{ + LIMITED_METHOD_CONTRACT; + + return *address == CORDbg_BREAK_INSTRUCTION; +} + +inline BOOL IsRunningOnWin95() { + return false; +} + +inline void SetSSFlag(DT_CONTEXT *pContext) +{ + _ASSERTE(pContext != NULL); + pContext->EFlags |= 0x100; +} + +inline void UnsetSSFlag(DT_CONTEXT *pContext) +{ + _ASSERTE(pContext != NULL); + pContext->EFlags &= ~0x100; +} + +inline bool IsSSFlagEnabled(DT_CONTEXT * context) +{ + _ASSERTE(context != NULL); + return (context->EFlags & 0x100) != 0; +} + + +inline bool PRDIsEqual(PRD_TYPE p1, PRD_TYPE p2){ + return p1 == p2; +} +inline void InitializePRD(PRD_TYPE *p1) { + *p1 = 0; +} + +inline bool PRDIsEmpty(PRD_TYPE p1) { + return p1 == 0; +} + +#endif // PRIMITIVES_H_ diff --git a/src/debug/inc/arm/.gitmirror b/src/debug/inc/arm/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/debug/inc/arm/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/debug/inc/arm/primitives.h b/src/debug/inc/arm/primitives.h new file mode 100644 index 0000000000..0bac542667 --- /dev/null +++ b/src/debug/inc/arm/primitives.h @@ -0,0 +1,179 @@ +// 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. +//***************************************************************************** +// File: primitives.h +// + +// +// Platform-specific debugger primitives +// +//***************************************************************************** + +#ifndef PRIMITIVES_H_ +#define PRIMITIVES_H_ + +#ifndef THUMB_CODE +#define THUMB_CODE 1 +#endif + +typedef const BYTE CORDB_ADDRESS_TYPE; +typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; + +//This is an abstraction to keep x86/ia64 patch data separate +#define PRD_TYPE USHORT + +#define MAX_INSTRUCTION_LENGTH 4 + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to lend somewhere in a call instruction. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 2 + +#define CORDbg_BREAK_INSTRUCTION_SIZE 2 +#define CORDbg_BREAK_INSTRUCTION (USHORT)0xdefe + +inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr) +{ + LIMITED_METHOD_DAC_CONTRACT; + return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE; +} + + +#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION +#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION) + +template <class T> +inline T _ClearThumbBit(T addr) +{ + return (T)(((DWORD)addr) & ~THUMB_CODE); +} + + +#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgGetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_ClearThumbBit(_patchAddr) - (_requestedAddr)))); + +#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \ + CORDbgSetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_ClearThumbBit(_patchAddr) - (_requestedAddr))), (_opcode)); + +#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgInsertBreakpointExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_ClearThumbBit(_patchAddr) - (_requestedAddr)))); + + +SELECTANY const CorDebugRegister g_JITToCorDbgReg[] = +{ + REGISTER_ARM_R0, + REGISTER_ARM_R1, + REGISTER_ARM_R2, + REGISTER_ARM_R3, + REGISTER_ARM_R4, + REGISTER_ARM_R5, + REGISTER_ARM_R6, + REGISTER_ARM_R7, + REGISTER_ARM_R8, + REGISTER_ARM_R9, + REGISTER_ARM_R10, + REGISTER_ARM_R11, + REGISTER_ARM_R12, + REGISTER_ARM_SP, + REGISTER_ARM_LR, + REGISTER_ARM_PC +}; + +// +// inline function to access/modify the CONTEXT +// +inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) { + LIMITED_METHOD_CONTRACT; + + context->Pc = (UINT32)(size_t)eip; +} + +inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) { + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(size_t)(context->Sp); +} + +inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID esp) { + LIMITED_METHOD_CONTRACT; + + context->Sp = (UINT32)(size_t)esp; +} + +inline void CORDbgSetFP(DT_CONTEXT *context, LPVOID ebp) { + LIMITED_METHOD_CONTRACT; + + _ASSERTE(FALSE); // @ARMTODO +} +inline LPVOID CORDbgGetFP(DT_CONTEXT* context) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(FALSE); // @ARMTODO + return (LPVOID)(UINT_PTR)0; +} + +// compare the EIP, ESP, and EBP +inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) +{ + LIMITED_METHOD_DAC_CONTRACT; + + // @ARMTODO: Sort out frame registers + + if ((pCtx1->Pc == pCtx2->Pc) && + (pCtx1->Sp == pCtx2->Sp)) + { + return TRUE; + } + + return FALSE; +} + +/* ========================================================================= */ +// +// Routines used by debugger support functions such as codepatch.cpp or +// exception handling code. +// +// GetInstruction, InsertBreakpoint, and SetInstruction all operate on +// a _single_ PRD_TYPE unit of memory. This is really important. If you only +// save one PRD_TYPE from the instruction stream before placing a breakpoint, +// you need to make sure to only replace one PRD_TYPE later on. +// +inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + ULONG ptraddr = dac_cast<ULONG>(address); + _ASSERTE(ptraddr & THUMB_CODE); + ptraddr &= ~THUMB_CODE; + return *(PRD_TYPE *)ptraddr; +} + +inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address, + PRD_TYPE instruction) +{ + // In a DAC build, this function assumes the input is an host address. + LIMITED_METHOD_DAC_CONTRACT; + + ULONG ptraddr = dac_cast<ULONG>(address); + _ASSERTE(ptraddr & THUMB_CODE); + ptraddr &= ~THUMB_CODE; + + *(PRD_TYPE *)ptraddr = instruction; + FlushInstructionCache(GetCurrentProcess(), + address, + sizeof(PRD_TYPE)); +} + +class Thread; +// Enable single stepping. +void SetSSFlag(DT_CONTEXT *pCtx, Thread *pThread); + +// Disable single stepping +void UnsetSSFlag(DT_CONTEXT *pCtx, Thread *pThread); + +// Check if single stepping is enabled. +bool IsSSFlagEnabled(DT_CONTEXT *pCtx, Thread *pThread); + +#include "arm_primitives.h" +#endif // PRIMITIVES_H_ diff --git a/src/debug/inc/arm64/.gitmirror b/src/debug/inc/arm64/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/debug/inc/arm64/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/debug/inc/arm64/primitives.h b/src/debug/inc/arm64/primitives.h new file mode 100644 index 0000000000..e9e04378f7 --- /dev/null +++ b/src/debug/inc/arm64/primitives.h @@ -0,0 +1,186 @@ +// 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. +//***************************************************************************** +// File: primitives.h +// + +// +// Platform-specific debugger primitives +// +//***************************************************************************** + +#ifndef PRIMITIVES_H_ +#define PRIMITIVES_H_ + +typedef NEON128 FPRegister64; +typedef const BYTE CORDB_ADDRESS_TYPE; +typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; + +#define MAX_INSTRUCTION_LENGTH 4 + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to land at the call instruction. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 4 + +#define PRD_TYPE LONG +#define CORDbg_BREAK_INSTRUCTION_SIZE 4 +#define CORDbg_BREAK_INSTRUCTION (LONG)0xD43E0000 + +#define NZCV_N 0x80000000 +#define NZCV_Z 0x40000000 +#define NZCV_C 0x20000000 +#define NZCV_V 0x10000000 + +#define NZCV_N_BIT 0x1f +#define NZCV_Z_BIT 0x1e +#define NZCV_C_BIT 0x1d +#define NZCV_V_BIT 0x1c + +inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr) +{ + LIMITED_METHOD_DAC_CONTRACT; + return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE; +} + +#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION +#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION) + + +#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgGetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_patchAddr) - (_requestedAddr))); + +#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \ + CORDbgSetInstructionExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_patchAddr) - (_requestedAddr)), (_opcode)); + +#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgInsertBreakpointExImpl((CORDB_ADDRESS_TYPE *)((_buffer) + (_patchAddr) - (_requestedAddr))); + + +SELECTANY const CorDebugRegister g_JITToCorDbgReg[] = +{ + REGISTER_ARM64_X0, + REGISTER_ARM64_X1, + REGISTER_ARM64_X2, + REGISTER_ARM64_X3, + REGISTER_ARM64_X4, + REGISTER_ARM64_X5, + REGISTER_ARM64_X6, + REGISTER_ARM64_X7, + REGISTER_ARM64_X8, + REGISTER_ARM64_X9, + REGISTER_ARM64_X10, + REGISTER_ARM64_X11, + REGISTER_ARM64_X12, + REGISTER_ARM64_X13, + REGISTER_ARM64_X14, + REGISTER_ARM64_X15, + REGISTER_ARM64_X16, + REGISTER_ARM64_X17, + REGISTER_ARM64_X18, + REGISTER_ARM64_X19, + REGISTER_ARM64_X20, + REGISTER_ARM64_X21, + REGISTER_ARM64_X22, + REGISTER_ARM64_X23, + REGISTER_ARM64_X24, + REGISTER_ARM64_X25, + REGISTER_ARM64_X26, + REGISTER_ARM64_X27, + REGISTER_ARM64_X28, + REGISTER_ARM64_FP, + REGISTER_ARM64_LR, + REGISTER_ARM64_SP, + REGISTER_ARM64_PC +}; + +inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) { + LIMITED_METHOD_CONTRACT; + + context->Pc = (DWORD64)eip; +} + +inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) { + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(size_t)(context->Sp); +} + +inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID esp) { + LIMITED_METHOD_CONTRACT; + + context->Sp = (DWORD64)esp; +} + +inline LPVOID CORDbgGetFP(const DT_CONTEXT * context) { + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(size_t)(context->Fp); +} + +inline void CORDbgSetFP(DT_CONTEXT *context, LPVOID fp) { + LIMITED_METHOD_CONTRACT; + + context->Fp = (DWORD64)fp; +} + + +inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) +{ + LIMITED_METHOD_DAC_CONTRACT; + + // @ARMTODO: Sort out frame registers + + if ((pCtx1->Pc == pCtx2->Pc) && + (pCtx1->Sp == pCtx2->Sp) && + (pCtx1->Fp == pCtx2->Fp)) + { + return TRUE; + } + + return FALSE; +} + +inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address, + PRD_TYPE instruction) +{ + // In a DAC build, this function assumes the input is an host address. + LIMITED_METHOD_DAC_CONTRACT; + + ULONGLONG ptraddr = dac_cast<ULONGLONG>(address); + *(PRD_TYPE *)ptraddr = instruction; + FlushInstructionCache(GetCurrentProcess(), + address, + sizeof(PRD_TYPE)); +} + +inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + ULONGLONG ptraddr = dac_cast<ULONGLONG>(address); + return *(PRD_TYPE *)ptraddr; +} + + +inline void SetSSFlag(DT_CONTEXT *pContext) +{ + _ASSERTE(pContext != NULL); + pContext->Cpsr |= 0x00200000; +} + +inline void UnsetSSFlag(DT_CONTEXT *pContext) +{ + _ASSERTE(pContext != NULL); + pContext->Cpsr &= ~0x00200000; +} + +inline bool IsSSFlagEnabled(DT_CONTEXT * pContext) +{ + _ASSERTE(pContext != NULL); + return (pContext->Cpsr & 0x00200000) != 0; +} + + +#include "arm_primitives.h" +#endif // PRIMITIVES_H_ diff --git a/src/debug/inc/arm_primitives.h b/src/debug/inc/arm_primitives.h new file mode 100644 index 0000000000..7c536353e5 --- /dev/null +++ b/src/debug/inc/arm_primitives.h @@ -0,0 +1,113 @@ +// 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. +//***************************************************************************** +// File: arm_primitives.h +// + +// +// ARM/ARM64-specific debugger primitives +// +//***************************************************************************** + +#ifndef ARM_PRIMITIVES_H_ +#define ARM_PRIMITIVES_H_ + +// +// Mapping from ICorDebugInfo register numbers to CorDebugRegister +// numbers. Note: this must match the order in corinfo.h. +// +inline CorDebugRegister ConvertRegNumToCorDebugRegister(ICorDebugInfo::RegNum reg) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(reg >= 0); + _ASSERTE(static_cast<size_t>(reg) < _countof(g_JITToCorDbgReg)); + return g_JITToCorDbgReg[reg]; +} + +inline LPVOID CORDbgGetIP(DT_CONTEXT *context) +{ + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(size_t)(context->Pc); +} + +inline void CORDbgSetInstructionExImpl(CORDB_ADDRESS_TYPE* address, + PRD_TYPE instruction) +{ + LIMITED_METHOD_DAC_CONTRACT; + + *(PRD_TYPE *)address = instruction; + FlushInstructionCache(GetCurrentProcess(), + address, + sizeof(PRD_TYPE)); +} + +inline PRD_TYPE CORDbgGetInstructionExImpl(UNALIGNED CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + return *(PRD_TYPE *)address; +} + +inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address) +{ + LIMITED_METHOD_CONTRACT; + + CORDbgSetInstruction(address, CORDbg_BREAK_INSTRUCTION); +} + +inline void CORDbgInsertBreakpointExImpl(UNALIGNED CORDB_ADDRESS_TYPE *address) +{ + LIMITED_METHOD_CONTRACT; + + CORDbgSetInstruction(address, CORDbg_BREAK_INSTRUCTION); +} + +// After a breakpoint exception, the CPU points to _after_ the break instruction. +// Adjust the IP so that it points at the break instruction. This lets us patch that +// opcode and re-excute what was underneath the bp. +inline void CORDbgAdjustPCForBreakInstruction(DT_CONTEXT* pContext) +{ + LIMITED_METHOD_CONTRACT; + + // @ARMTODO: ARM appears to leave the PC at the start of the breakpoint (at least according to Windbg, + // which may be adjusting the view). + return; +} + +inline bool AddressIsBreakpoint(CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + return CORDbgGetInstruction(address) == CORDbg_BREAK_INSTRUCTION; +} + +class Thread; +// Enable single stepping. +void SetSSFlag(DT_CONTEXT *pCtx, Thread *pThread); + +// Disable single stepping +void UnsetSSFlag(DT_CONTEXT *pCtx, Thread *pThread); + +// Check if single stepping is enabled. +bool IsSSFlagEnabled(DT_CONTEXT *pCtx, Thread *pThread); + +inline bool PRDIsEqual(PRD_TYPE p1, PRD_TYPE p2) +{ + return p1 == p2; +} + +inline void InitializePRD(PRD_TYPE *p1) +{ + *p1 = 0; +} + +inline bool PRDIsEmpty(PRD_TYPE p1) +{ + LIMITED_METHOD_CONTRACT; + + return p1 == 0; +} + +#endif // ARM_PRIMITIVES_H_ diff --git a/src/debug/inc/common.h b/src/debug/inc/common.h new file mode 100644 index 0000000000..77fe27a4b3 --- /dev/null +++ b/src/debug/inc/common.h @@ -0,0 +1,323 @@ +// 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. + +#ifndef DEBUGGER_COMMON_H +#define DEBUGGER_COMMON_H + +// +// Conversions between pointers and CORDB_ADDRESS +// These are 3gb safe - we use zero-extension for CORDB_ADDRESS. +// Note that this is a different semantics from CLRDATA_ADDRESS which is sign-extended. +// +// @dbgtodo : This confuses the host and target address spaces. Ideally we'd have +// conversions between PTR types (eg. DPTR) and CORDB_ADDRESS, and not need conversions +// from host pointer types to CORDB_ADDRESS. +// +#if defined(_TARGET_X86_) || defined(_TARGET_ARM_) +inline CORDB_ADDRESS PTR_TO_CORDB_ADDRESS(const void* ptr) +{ + SUPPORTS_DAC; + // Cast a void* to a ULONG is not 64-bit safe and triggers compiler warning C3411. + // But this is x86 only, so we know it's ok. Use PtrToUlong to do the conversion + // without invoking the error. + return (CORDB_ADDRESS)(PtrToUlong(ptr)); +} +inline CORDB_ADDRESS PTR_TO_CORDB_ADDRESS(UINT_PTR ptr) +{ + SUPPORTS_DAC; + // PtrToUlong + return (CORDB_ADDRESS)(ULONG)(ptr); +} +#else +#define PTR_TO_CORDB_ADDRESS(_ptr) (CORDB_ADDRESS)(ULONG_PTR)(_ptr) +#endif //_TARGET_X86_ || _TARGET_ARM_ + +#define CORDB_ADDRESS_TO_PTR(_cordb_addr) ((LPVOID)(SIZE_T)(_cordb_addr)) + + +// Determine if an exception record is for a CLR debug event, and get the payload. +CORDB_ADDRESS IsEventDebuggerNotification(const EXCEPTION_RECORD * pRecord, CORDB_ADDRESS pClrBaseAddress); +#if defined(FEATURE_DBGIPC_TRANSPORT_DI) || defined(FEATURE_DBGIPC_TRANSPORT_VM) +struct DebuggerIPCEvent; +void InitEventForDebuggerNotification(DEBUG_EVENT * pDebugEvent, + CORDB_ADDRESS pClrBaseAddress, + DebuggerIPCEvent * pIPCEvent); +#endif // (FEATURE_DBGIPC_TRANSPORT_DI || FEATURE_DBGIPC_TRANSPORT_VM) + + +void GetPidDecoratedName(__out_z __out_ecount(cBufSizeInChars) WCHAR * pBuf, + int cBufSizeInChars, + const WCHAR * pPrefix, + DWORD pid); + + +// +// This macro is used in CORDbgCopyThreadContext(). +// +// CORDbgCopyThreadContext() does an intelligent copy +// from pSrc to pDst, respecting the ContextFlags of both contexts. +// +#define CopyContextChunk(_t, _f, _end, _flag) \ +{ \ + LOG((LF_CORDB, LL_INFO1000000, \ + "CP::CTC: copying " #_flag ":" FMT_ADDR "<---" FMT_ADDR "(%d)\n", \ + DBG_ADDR(_t), DBG_ADDR(_f), ((UINT_PTR)(_end) - (UINT_PTR)_t))); \ + memcpy((_t), (_f), ((UINT_PTR)(_end) - (UINT_PTR)(_t))); \ +} + +// +// CORDbgCopyThreadContext() does an intelligent copy from pSrc to pDst, +// respecting the ContextFlags of both contexts. +// +struct DebuggerREGDISPLAY; + +extern void CORDbgCopyThreadContext(DT_CONTEXT* pDst, const DT_CONTEXT* pSrc); +extern void CORDbgSetDebuggerREGDISPLAYFromContext(DebuggerREGDISPLAY *pDRD, + DT_CONTEXT* pContext); + +//--------------------------------------------------------------------------------------- +// +// Return the size of the CONTEXT required for the specified context flags. +// +// Arguments: +// flags - this is the equivalent of the ContextFlags field of a CONTEXT +// +// Return Value: +// size of the CONTEXT required +// +// Notes: +// On WIN64 platforms this function will always return sizeof(CONTEXT). +// + +inline +ULONG32 ContextSizeForFlags(ULONG32 flags) +{ +#if defined(CONTEXT_EXTENDED_REGISTERS) && defined(_TARGET_X86_) + // Older platforms didn't have extended registers in + // the context definition so only enforce that size + // if the extended register flag is set. + if ((flags & CONTEXT_EXTENDED_REGISTERS) != CONTEXT_EXTENDED_REGISTERS) + { + return offsetof(T_CONTEXT, ExtendedRegisters); + } + else +#endif // _TARGET_X86_ + { + return sizeof(T_CONTEXT); + } +} + +//--------------------------------------------------------------------------------------- +// +// Given the size of a buffer and the context flags, check whether the buffer is sufficient large +// to hold the CONTEXT. +// +// Arguments: +// size - size of a buffer +// flags - this is the equivalent of the ContextFlags field of a CONTEXT +// +// Return Value: +// TRUE if the buffer is large enough to hold the CONTEXT +// + +inline +BOOL CheckContextSizeForFlags(ULONG32 size, ULONG32 flags) +{ + return (size >= ContextSizeForFlags(flags)); +} + +//--------------------------------------------------------------------------------------- +// +// Given the size of a buffer and the BYTE array representation of a CONTEXT, +// check whether the buffer is sufficient large to hold the CONTEXT. +// +// Arguments: +// size - size of a buffer +// flags - this is the equivalent of the ContextFlags field of a CONTEXT +// +// Return Value: +// TRUE if the buffer is large enough to hold the CONTEXT +// + +inline +BOOL CheckContextSizeForBuffer(ULONG32 size, const BYTE * pbBuffer) +{ + return ( ( size >= (offsetof(T_CONTEXT, ContextFlags) + sizeof(ULONG32)) ) && + CheckContextSizeForFlags(size, (reinterpret_cast<const T_CONTEXT *>(pbBuffer))->ContextFlags) ); +} + +/* ------------------------------------------------------------------------- * + * Constant declarations + * ------------------------------------------------------------------------- */ + +enum +{ + NULL_THREAD_ID = -1, + NULL_PROCESS_ID = -1 +}; + +/* ------------------------------------------------------------------------- * + * Macros + * ------------------------------------------------------------------------- */ + +// +// CANNOT USE IsBad*Ptr() methods here. They are *banned* APIs because of various +// reasons (see http://winweb/wincet/bannedapis.htm). +// + +#define VALIDATE_POINTER_TO_OBJECT(ptr, type) \ +if ((ptr) == NULL) \ +{ \ + return E_INVALIDARG; \ +} + +#define VALIDATE_POINTER_TO_OBJECT_OR_NULL(ptr, type) + +// +// CANNOT USE IsBad*Ptr() methods here. They are *banned* APIs because of various +// reasons (see http://winweb/wincet/bannedapis.htm). +// +#define VALIDATE_POINTER_TO_OBJECT_ARRAY(ptr, type, cElt, fRead, fWrite) \ +if ((ptr) == NULL) \ +{ \ + return E_INVALIDARG; \ +} + +#define VALIDATE_POINTER_TO_OBJECT_ARRAY_OR_NULL(ptr, type,cElt,fRead,fWrite) + +/* ------------------------------------------------------------------------- * + * Function Prototypes + * ------------------------------------------------------------------------- */ + + + +// Linear search through an array of NativeVarInfos, to find +// the variable of index dwIndex, valid at the given ip. +// +// returns CORDBG_E_IL_VAR_NOT_AVAILABLE if the variable isn't +// valid at the given ip. +// +// This should be inlined +HRESULT FindNativeInfoInILVariableArray(DWORD dwIndex, + SIZE_T ip, + ICorDebugInfo::NativeVarInfo **ppNativeInfo, + unsigned int nativeInfoCount, + ICorDebugInfo::NativeVarInfo *nativeInfo); + + +#define VALIDATE_HEAP +//HeapValidate(GetProcessHeap(), 0, NULL); + +// struct DebuggerILToNativeMap: Holds the IL to Native offset map +// Great pains are taken to ensure that this each entry corresponds to the +// first IL instruction in a source line. It isn't actually a mapping +// of _every_ IL instruction in a method, just those for source lines. +// SIZE_T ilOffset: IL offset of a source line. +// SIZE_T nativeStartOffset: Offset within the method where the native +// instructions corresponding to the IL offset begin. +// SIZE_T nativeEndOffset: Offset within the method where the native +// instructions corresponding to the IL offset end. +// +// Note: any changes to this struct need to be reflected in +// COR_DEBUG_IL_TO_NATIVE_MAP in CorDebug.idl. These structs must +// match exactly. +// +struct DebuggerILToNativeMap +{ + ULONG ilOffset; + ULONG nativeStartOffset; + ULONG nativeEndOffset; + ICorDebugInfo::SourceTypes source; +}; + +void ExportILToNativeMap(ULONG32 cMap, + COR_DEBUG_IL_TO_NATIVE_MAP mapExt[], + struct DebuggerILToNativeMap mapInt[], + SIZE_T sizeOfCode); + +#include <primitives.h> + +// ---------------------------------------------------------------------------- +// IsPatchInRequestedRange +// +// Description: +// This function checks if a patch falls (fully or partially) in the requested range of memory. +// +// Arguments: +// * requestedAddr - the address of the memory range +// * requestedSize - the size of the memory range +// * patchAddr - the address of the patch +// * pPRD - the opcode of the patch +// +// Return Value: +// Return TRUE if the patch is fully or partially in the requested memory range. +// +// Notes: +// Currently this function is called both from the RS (via code:CordbProcess.ReadMemory and +// code:CordbProcess.WriteMemory) and from DAC. When we DACize the two functions mentioned above, +// this function should be called from DAC only, and we should use a MemoryRange here. +// + +inline bool IsPatchInRequestedRange(CORDB_ADDRESS requestedAddr, + SIZE_T requestedSize, + CORDB_ADDRESS patchAddr) +{ + SUPPORTS_DAC; + + if (requestedAddr == 0) + return false; + + // Note that patchEnd points to the byte immediately AFTER the patch, so patchEnd is NOT + // part of the patch. + CORDB_ADDRESS patchEnd = GetPatchEndAddr(patchAddr); + + // We have three cases: + // 1) the entire patch is in the requested range + // 2) the beginning of the requested range is covered by the patch + // 3) the end of the requested range is covered by the patch + // + // Note that on x86, since the break instruction only takes up one byte, the following condition + // degenerates to case 1 only. + return (((requestedAddr <= patchAddr) && (patchEnd <= (requestedAddr + requestedSize))) || + ((patchAddr <= requestedAddr) && (requestedAddr < patchEnd)) || + ((patchAddr <= (requestedAddr + requestedSize - 1)) && ((requestedAddr + requestedSize - 1) < patchEnd))); +} + +inline CORDB_ADDRESS ALIGN_ADDRESS( CORDB_ADDRESS val, CORDB_ADDRESS alignment ) +{ + LIMITED_METHOD_DAC_CONTRACT; + + // alignment must be a power of 2 for this implementation to work (need modulo otherwise) + _ASSERTE( 0 == (alignment & (alignment - 1)) ); + CORDB_ADDRESS result = (val + (alignment - 1)) & ~(alignment - 1); + _ASSERTE( result >= val ); // check for overflow + return result; +} + +// +// Whenever a structure is marshalled between different platforms, we need to ensure the +// layout is the same in both cases. We tell GCC to use the MSVC-style packing with +// the following attribute. The main thing this appears to control is whether +// 8-byte values are aligned at 4-bytes (GCC default) or 8-bytes (MSVC default). +// This attribute affects only the immediate struct it is applied to, you must also apply +// it to any nested structs if you want their layout affected as well. You also must +// apply this to unions embedded in other structures, since it can influence the starting +// alignment. +// +// Note that there doesn't appear to be any disadvantage to applying this a little +// more agressively than necessary, so we generally use it on all classes / structures +// defined in a file that defines marshalled data types (eg. DacDbiStructures.h) +// The -mms-bitfields compiler option also does this for the whole file, but we don't +// want to go changing the layout of, for example, structures defined in OS header files +// so we explicitly opt-in with this attribute. +// +#ifdef __GNUC__ +#define MSLAYOUT __attribute__((__ms_struct__)) +#else +#define MSLAYOUT +#endif + +#include "dumpcommon.h" + +#endif //DEBUGGER_COMMON_H diff --git a/src/debug/inc/coreclrremotedebugginginterfaces.h b/src/debug/inc/coreclrremotedebugginginterfaces.h new file mode 100644 index 0000000000..ac7ddb68ab --- /dev/null +++ b/src/debug/inc/coreclrremotedebugginginterfaces.h @@ -0,0 +1,20 @@ +// 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. + + +// +// Defines interfaces shared between mscordbi and the Visual Studio debugger port supplier plugin that we +// provide for debugging remote CoreCLR instances on the Mac. +// + +#ifndef __PORT_SUPPLIER_INTERFACES_INCLUDED +#define __PORT_SUPPLIER_INTERFACES_INCLUDED + + + + +class ICoreClrDebugTarget; + + +#endif // __PORT_SUPPLIER_INTERFACES_INCLUDED diff --git a/src/debug/inc/dacdbiinterface.h b/src/debug/inc/dacdbiinterface.h new file mode 100644 index 0000000000..fe58724fc5 --- /dev/null +++ b/src/debug/inc/dacdbiinterface.h @@ -0,0 +1,2726 @@ +// 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. +//***************************************************************************** +// DacDbiInterface.h +// + +// +// Define the interface between the DAC and DBI. +//***************************************************************************** + +#ifndef _DACDBI_INTERFACE_H_ +#define _DACDBI_INTERFACE_H_ + +#include <metahost.h> + +// The DAC/DBI interface can use structures and LSPTR declarations from the +// existing V2 interfaces +#include "dbgipcevents.h" + +//----------------------------------------------------------------------------- +// Deallocation function for memory allocated with the global IAllocator object. +// +// Arguments: +// p - pointer to delete. Allocated with IAllocator::Alloc +// +// Notes: +// This should invoke the dtor and then call IAllocator::Free. +// In the DAC implementation, this will call via IAllocator. +// In the DBI implementation, this can directly call delete (assuming the IAllocator::Free +// directly called new). +template<class T> void DeleteDbiMemory(T *p); +// Need a class to serve as a tag that we can use to overload New/Delete. +class forDbiWorker {}; +#define forDbi (*(forDbiWorker *)NULL) +extern void * operator new(size_t lenBytes, const forDbiWorker &); +extern void * operator new[](size_t lenBytes, const forDbiWorker &); +extern void operator delete(void *p, const forDbiWorker &); +extern void operator delete[](void *p, const forDbiWorker &); + +// The dac exposes a way to walk all GC references in the process. This +// includes both strong references and weak references. This is done +// through a referece walk. +typedef void* * RefWalkHandle; + +#include "dacdbistructures.h" + +// This is the current format of code:DbiVersion. It needs to be rev'ed when we decide to store something +// else other than the product version of the DBI in DbiVersion (e.g. a timestamp). See +// code:CordbProcess::CordbProcess#DBIVersionChecking for more information. +const DWORD kCurrentDbiVersionFormat = 1; + +//----------------------------------------------------------------------------- +// This is a low-level interface between DAC and DBI. +// The DAC is the raw DAC-ized code from the EE. +// DBI is the implementation of ICorDebug on top of that. +// +// This interface should be: +// - Stateless: The DAC component should not have any persistent state. It should not have any resources +// that it needs to clean up. DBI can store all the state (eg, list of of modules). +// Using IAllocator/IStringHolder interfaces to allocate data to pass back out is ok because DBI owns +// the resources, not the DAC layer. +// - blittable: The types on the interface should be blittable. For example, use TIDs instead of OS Thread handles. +// Passing pointers to be used as out-parameters is ok. +// - lightweight: it will inevitably have many methods on it and should be very fluid to use. +// - very descriptive: heavily call out liabilities on the runtime. For example, don't just have a method like +// "GetName" where Name is ambiguous. Heavily comment exactly what Name is, when it may fail, if it's 0-length, +// if it's unique, etc. This serves two purposes: +// a) it helps ensure the right invariants flow up to the public API level. +// b) it helps ensure that the debugger is making the right assumptions about the runtime's behavior. +// +// #Marshaling: +// This interface should be marshalable such that the caller (the Right Side) can exist in one +// process, while the implementation of Dac could be on another machine. +// - All types need to be marshable. +// - Use OUT and OPTIONAL as defined in windef.h to guide the marshaler. Here are how types are marshaled: +// T : value-type, copied on input. +// T* : will be marshaled as non-null by-ref (copy on input, copy on return), +// const T*: non-null, copy on input only. +// OUT T*: non-null copy-on-return only. +// OPTIONAL T*: by-ref, could be null. +// - The marshaler has special knowledge of IStringHolder and DacDbiArrayList<T>. +// - You can write custom marshalers for non-blittable structures defined in DacDbiStructures.h. +// - There is custom handling for marshalling callbacks. +// +// +// Threading: The interface (and the underlying DataTarget) are free-threaded to leverage +// concurrency. +// +// Allocation: +// This interface can use IAllocator to allocate objects and hand them back. The allocated objects should be: +// - closed, serializable object graphs. +// - should have private fields and public accessors +// - have dtors that free any allocated the memory via calling DeleteDbiMemory. +// Objects can be declared in a header and shared between both dbi and dac. +// Consider using DacDbiArrayList<T> instead of custom allocations. + +// Error handling: +// Any call on the interface may fail. For example, the data-target may not have access to the necessary memory. +// Methods should throw on error. +// +// #Enumeration +// General rules about Enumerations: +// - Do not assume that enumerations exposed here are in any particular order. +// - many enumerations also correspond to Load/Unload events. Since load/unload aren't atomic with publishing +// in an enumeration, this is a Total Ordering of things: +// a) object shows up in enumeration +// b) load event. +// c) ... steady state ... +// d) object removed from DacDbi enumeration; +// Any existing handles we get beyond this are explicitly associated with a Cordb* object; which can be +// neutered on the unload event by Dbi. +// e) unload event. +// - Send after it's reachability from other objects is broken. (Eg, For AppDomain unload +// means no threads left in that appdomain) +// - Send before it's deleted (so VMPTR is still valid; not yet recycled). +// - Send early enough that property access can at least gracefully fail. (eg, +// Module::GetName should either return the name, or fail) +// +// Cordb must neuter any Cordb objects that have any pre-existing handles to the object. +// After this point, gauranteed that nobody can discover the VMPTR any more: +// - doesn't show up in enumerations (so can't be discoverered implicitly) +// - object should not be discoverable by other objects in VM. +// - any Cordb object that already had it would be neutered by Dbi. +// - Therefore nothing should even be asking Dac for it. +// f) object deleted. +// Think of it like this: The event occurs to let you know that the enumeration has been updated. +// +// A robust debugger should not rely on events for correctness. For example, +// a corrupt debuggee may send: +// 1) multiple load events. (if target repeats due to an issue) +// 2) no load event and only an unload event. (if target fails inbetween +// publish (a) and load (b), and then backout code sends the unload). +// 3) no unload event. (eg, if target is rudely killed) +// 4) multiple unload events (if target repeats due to bug) +// +// This satisfies the following rules: +// - once you get the load event, you can find the object via enumeration +// - once an item is discoverable, it must immediately show up in the enumeration. +// - once you get the unload event, the object is dead and can't be rediscovered via enumeration. +// +// This is an issue even for well-behaved targets. Imagine if a debugger attaches right after +// an unload event is sent. We don't want the debugger to enumerate and re-discover the +// unloaded object because now that the unload event is already sent, the debugger won't get +// any further notification of when the object is deleted in the target. +// Thus it's valuable for the debugger to have debug-only checks after unload events to assert +// that the object is no longer discoverable. +// +//............................................................................. +// The purpose of this object is to provide EE funcationality back to +// the debugger. This represents the entire set of EE functions used +// by the debugger. +// +// We will make this interface larger over time to grow the functionality +// between the EE and the Debugger. +// +// +//----------------------------------------------------------------------------- +class IDacDbiInterface +{ +public: + class IStringHolder; + + // The following tag tells the DD-marshalling tool to start scanning. + // BEGIN_MARSHAL + + //----------------------------------------------------------------------------- + // Functions to control the behavior of the DacDbi implementation itself. + //----------------------------------------------------------------------------- + + // + // Check whether the version of the DBI matches the version of the runtime. + // This is only called when we are remote debugging. On Windows, we should have checked all the + // versions before we call any API on the IDacDbiInterface. See + // code:CordbProcess::CordbProcess#DBIVersionChecking for more information on version checks. + // + // Return Value: + // S_OK on success. + // + // Notes: + // THIS MUST BE THE FIRST API ON THE INTERFACE! + // + virtual + HRESULT CheckDbiVersion(const DbiVersion * pVersion) = 0; + + // + // Flush the DAC cache. This should be called when target memory changes. + // + // + // Return Value: + // S_OK on success. + // + // Notes: + // If this fails, the interface is in an undefined state. + // This must be called anytime target memory changes, else all other functions + // (besides Destroy) may yield out-of-date or semantically incorrect results. + // + virtual + HRESULT FlushCache() = 0; + + // + // Control DAC's checking of the target's consistency. Specifically, if this is disabled then + // ASSERTs in VM code are ignored. The default is disabled, since DAC should do it's best to + // return results even with a corrupt or unsyncrhonized target. See + // code:ClrDataAccess::TargetConsistencyAssertsEnabled for more details. + // + // When testing with a non-corrupt and properly syncrhonized target, this should be enabled to + // help catch bugs. + // + // Arguments: + // fEnableAsserts - whether ASSERTs should be raised when consistency checks fail (_DEBUG + // builds only) + // + // Notes: + // In the future we may want to extend DAC target consistency checks to be retail checks + // (exceptions) as well. We'll also need a mechanism for disabling them (eg. when an advanced + // user wants to try to get a result anyway even though the target is inconsistent). In that + // case we'll want an additional argument here for enabling/disabling the throwing of + // consistency failures exceptions (this is independent from asserts - there are legitimate + // scenarios for all 4 combinations). + // + virtual + void DacSetTargetConsistencyChecks(bool fEnableAsserts) = 0; + + // + // Destroy the interface object. The client should call this when it's done + // with the IDacDbiInterface to free up any resources. + // + // Return Value: + // None. + // + // Notes: + // The client should not call anything else on this interface after Destroy. + // + virtual + void Destroy() = 0; + + //----------------------------------------------------------------------------- + // General purpose target inspection functions + //----------------------------------------------------------------------------- + + // + // Query if Left-side is started up? + // + // + // Return Value: + // BOOL whether Left-side is intialized. + // + // Notes: + // If the Left-side is not yet started up, then data in the LS is not yet initialized enough + // for us to make meaningful queries, but the runtime will fire "Startup Exception" when it is. + // + // If the left-side is started up, then data is ready. (Although data may be temporarily inconsistent, + // see DataSafe). We may still get a Startup Exception in these cases, but it can be ignored. + // + virtual + BOOL IsLeftSideInitialized() = 0; + + + // + // Get an LS Appdomain via an AppDomain unique ID. + // Fails if the AD is not found or if the ID is invalid. + // + // Arguments: + // appdomainId - "unique appdomain ID". Must be a valid Id. + // + // Return Value: + // VMPTR_AppDomain for the corresponding AppDomain ID. Else throws. + // + // Notes: + // This query is based off the lifespan of the AppDomain from the VM's perspective. + // The AppDomainId is most likely obtained from an AppDomain-Created debug events. + // An AppDomainId is unique for the lifetime of the VM. + // This is the inverse function of GetAppDomainId(). + // + virtual + VMPTR_AppDomain GetAppDomainFromId(ULONG appdomainId) = 0; + + + // + // Get the AppDomain ID for an AppDomain. + // + // Arguments: + // vmAppDomain - VM pointer to the AppDomain object of interest + // + // Return Value: + // AppDomain ID for appdomain. Else throws. + // + // Notes: + // An AppDomainId is unique for the lifetime of the VM. It is non-zero. + // + virtual + ULONG GetAppDomainId(VMPTR_AppDomain vmAppDomain) = 0; + + // + // Get the managed AppDomain object for an AppDomain. + // + // Arguments: + // vmAppDomain - VM pointer to the AppDomain object of interest + // + // Return Value: + // objecthandle for the managed app domain object or the Null VMPTR if there is no + // object created yet + // + // Notes: + // The AppDomain managed object is lazily constructed on the AppDomain the first time + // it is requested. It may be NULL. + // + virtual + VMPTR_OBJECTHANDLE GetAppDomainObject(VMPTR_AppDomain vmAppDomain) = 0; + + // + // Determine if the specified AppDomain is the default domain + // + // Arguments: + // vmAppDomain - VM pointer to the AppDomain ojbect of interest + // + // Return Value: + // TRUE if this is the default appdomain, else FALSE. + // + // Notes: + // The default domain is the only one which cannot be unloaded and exists for the life + // of the process. + // A well behaved target only has 1 default domain. + // + virtual + BOOL IsDefaultDomain(VMPTR_AppDomain vmAppDomain) = 0; + + + virtual + void GetAssemblyFromDomainAssembly(VMPTR_DomainAssembly vmDomainAssembly, OUT VMPTR_Assembly * vmAssembly) = 0; + + // + // Determines whether the runtime security system has assigned full-trust to this assembly. + // + // Arguments: + // vmDomainAssembly - VM pointer to the assembly in question. + // + // Return Value: + // Returns trust status for the assembly. + // Throws on error. + // + // Notes: + // Of course trusted malicious code in the process could always cause this API to lie. However, + // an assembly loaded without full-trust should have no way of causing this API to return true. + // + virtual + BOOL IsAssemblyFullyTrusted(VMPTR_DomainAssembly vmDomainAssembly) = 0; + + + // + // Get the full AD friendly name for the given EE AppDomain. + // + // Arguments: + // vmAppDomain - VM pointer to the AppDomain. + // pStrName - required out parameter where the name will be stored. + // + // Return Value: + // None. On success, sets the string via the holder. Throws on error. + // This either sets pStrName or Throws. It won't do both. + // + // Notes: + // AD names have an unbounded length. AppDomain friendly names can also change, and + // so callers should be prepared to listen for name-change events and requery. + // AD names are specified by the user. + // + virtual + void GetAppDomainFullName( + VMPTR_AppDomain vmAppDomain, + IStringHolder * pStrName) = 0; + + + // + // #ModuleNames + // + // Modules / Assemblies have many different naming schemes: + // + // 1) Metadata Scope name: All modules have metadata, and each metadata scope has a name assigned + // by the creator of that scope (eg, the compiler). This usually is similar to the filename, but could + // be arbitrary. + // eg: "Foo" + // + // 2) FileRecord: the File record entry in the manifest module's metadata (table 0x26) for this module. + // eg: "Foo" + // + // 3) Managed module path: This is path that the image was loaded from. Eg, "c:\foo.dll". For non-file + // based modules (like in-memory, dynamic), there is no file path. The specific path is determined by + // fusion / loader policy. + // eg: "c:\foo.dll" + // + // 4) GAC path: If the module is loaded from the GAC, this is the path on disk into the gac cache that + // the image was pulled from. + // eg: " + // + // 5) Ngen path: If the module was ngenned, this is the path on disk into the ngen cache that the image + // was pulled from. + // eg: + // + // 6) Fully Qualified Assembly Name: this is an abstract name, which the CLR (fusion / loader) will + // resolve (to a filename for file-based modules). Managed apps may need to deal in terms of FQN, + // but the debugging services generally avoid them. + // eg: "Foo, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL". + // + + + // + // Get the "simple name" of a module. This is a heuristic within the CLR to return a simple, + // not-well-specified, but meaningful, name for a module. + // + // Arguments: + // vmModule - module to query + // pStrFileName - string holder to get simple name. + // + // Return Value: + // None, but pStrFilename will be initialized upon return. + // Throws if there was a problem reading the data with DAC or if there is an OOM exception, + // in which case no string was stored into pStrFilename. + // + // Notes: + // See code:#ModuleNames for an overview on module names. + // + // This is really just using code:Module::GetSimpleName. + // This gives back a meaningful name, which is generally some combination of the metadata + // name of the FileRecord name. This is important because it's valid even when a module + // doesn't have a filename. + // + // The simple name does not have any meaning. It is not a filename, does not necessarily have any + // relationship to the filename, and it's not necesarily the metadata name. + // Do not use the simple name for anything other than as a pretty string to give the an end user. + // + virtual + void GetModuleSimpleName(VMPTR_Module vmModule, IStringHolder * pStrFilename) = 0; + + + // + // Get the full path and file name to the assembly's manifest module. + // + // Arguments: + // vmAssembly - VM pointer to the Assembly. + // pStrFilename - required out parameter where the filename will be stored. + // + // Return Value: + // TRUE on success, in which case the filename was stored into pStrFilename + // FALSE if the assembly has no filename (eg. for in-memory assemblies), in which + // case an empty string was stored into pStrFilename. + // Throws if there was a problem reading the data with DAC, in which case + // no string was stored into pStrFilename. + // + // Notes: + // See code:#ModuleNames for an overview on module names. + // + // Normally this is just the filename from which the dll containing the assembly was + // loaded. In the case of multi-module assemblies, this is the filename for the + // manifest module (the one containing the assembly manifest). For in-memory + // assemblies (eg. those loaded from a Byte[], and those created by Reflection.Emit + // which will not be saved to disk) there is no filename. In that case this API + // returns an empty string. + // + virtual + BOOL GetAssemblyPath(VMPTR_Assembly vmAssembly, + IStringHolder * pStrFilename) = 0; + + + // get a type def resolved across modules + // Arguments: + // input: pTypeRefInfo - domain file and type ref from the referencing module + // output: pTargetRefInfo - domain file and type def from the referenced type (this may + // come from a module other than the referencing module) + // Note: throws + virtual + void ResolveTypeReference(const TypeRefData * pTypeRefInfo, + TypeRefData * pTargetRefInfo) = 0; + // + // Get the full path and file name to the module (if any). + // + // Arguments: + // vmModule - VM pointer to the module. + // pStrFilename - required out parameter where the filename will be stored. + // + // Return Value: + // TRUE on success, in which case the filename was stored into pStrFilename + // FALSE the module has no filename (eg. for in-memory assemblies), in which + // case an empty string was stored into pStrFilename. + // Throws an exception if there was a problem reading the data with DAC, in which case + // no string was stored into pStrFilename. + // + // Notes: + // See code:#ModuleNames for an overview on module names. + // + // Normally this is just the filename from which the module was loaded. + // For in-memory module (eg. those loaded from a Byte[], and those created by Reflection.Emit + // which will not be saved to disk) there is no filename. In that case this API + // returns an empty string. Consider GetModuleSimpleName in those cases. + // + // We intentionally don't use the function name "GetModuleFileName" here because + // winbase #defines that token (along with many others) to have an A or W suffix. + // + virtual + BOOL GetModulePath(VMPTR_Module vmModule, + IStringHolder * pStrFilename) = 0; + + + // + // Get the full path and file name to the ngen image for the module (if any). + // + // Arguments: + // vmModule - VM pointer to the module. + // pStrFilename - required out parameter where the filename will be stored. + // + // Return Value: + // TRUE on success, in which case the filename was stored into pStrFilename + // FALSE the module has no filename (eg. for in-memory assemblies), in which + // case an empty string was stored into pStrFilename. + // Throws an exception if there was a problem reading the data with DAC, in which case + // no string was stored into pStrFilename. + // + // Notes: + // See code:#ModuleNames for an overview on module names. + // + virtual + BOOL GetModuleNGenPath(VMPTR_Module vmModule, + IStringHolder * pStrFilename) = 0; + + + + // Get the metadata for the target module + // + // Arguments: + // vmModule - target module to get metadata for. + // pTargetBuffer - Out parameter to get target-buffer for metadata. Gauranteed to be non-empty on + // return. This will throw CORDBG_E_MISSING_METADATA hr if the buffer is empty. + // This does not gaurantee that the buffer is readable. For example, in a minidump, buffer's + // memory may not be present. + // + // Notes: + // Each module's metadata exists as a raw buffer in the target. This finds that target buffer and + // returns it. The host can then use OpenScopeOnMemory to create an instance of the metadata in + // the host process space. + // + // For dynamic modules, the CLR will eagerly serialize the metadata at "debuggable" points. This + // could be after each type is loaded; or after a bulk update. + // For non-dynamic modules (both in-memory and file-based), the metadata exists in the PEFile's image. + // + // Failure cases: + // This should succeed in normal, live-debugging scenarios. However, common failure paths here would be: + // + // 1. Data structures are intact, but Unable to even find the TargetBuffer in the target. In this + // case Metadata is truly missing. Likely means: + // - target is in the middle of generating metadata for a large bulk operation. (For example, attach + // to a TypeLibConverter using Ref.Emit to emit a module for a very large .tlb file). + // - corrupted target, + // - or the target had some error(out-of-memory?) generating the metadata. + // This throws CORDBG_E_MISSING_METADATA. + // + // 2. Target buffer is found, but memory it describes is not present. Likely means a minidump + // scenario with missing memory. Client should use alternative metadata location techniques (such as + // an ImagePath to locate the original image and then pulling metadata from that file). + // + virtual + void GetMetadata(VMPTR_Module vmModule, OUT TargetBuffer * pTargetBuffer) = 0; + + + // Definitions for possible symbol formats + // This is equivalent to code:ESymbolFormat in the runtime + typedef enum + { + kSymbolFormatNone, // No symbols available + kSymbolFormatPDB, // PDB symbol format - use diasymreader.dll + kSymbolFormatILDB, // ILDB symbol format - use ildbsymlib + } SymbolFormat; + + // + // Get the in-memory symbol (PDB/ILDB) buffer in the target if present. + // + // Arguments: + // vmModule- module to query for. + // pTargetBuffer - out parameter to get buffer in target of symbols. If no symbols, pTargetBuffer is empty on return. + // pSymbolFormat - out parameter to get the format of the symbols. + // + // Returns: + // 1) If there are in-memory symbols for the given module, pTargetBuffer is set to the buffer describing + // the symbols and pSymbolFormat is set to indicate PDB or ILDB format. This buffer can then be read, + // converted into an IStream, and passed to ISymUnmanagedBinder::CreateReaderForStream. + // 2) If the target is valid, but there is no symbols for the module, then pTargetBuffer->IsEmpty() == true + // and *pSymbolFormat == kSymbolFormatNone. + // 3) Else, throws exception. + // + // + // Notes: + // For file-based modules, PDBs are normally on disk and the debugger retreieves them via a symbol + // path without any help from ICorDebug. + // However, in some cases, the PDB is stored in-memory and so the debugger needs ICorDebug. Common + // cases include: + // - dynamic modules generated with reflection-emit. + // - in-memory modules loaded by Load(Byte[],Byte[]), which provide the PDB as a byte[]. + // - hosted modules where the host (such as SQL) store the PDB. + // + // In all cases, this can commonly fail. Executable code does not need to have a PDB. + virtual + void GetSymbolsBuffer(VMPTR_Module vmModule, OUT TargetBuffer * pTargetBuffer, OUT SymbolFormat * pSymbolFormat) = 0; + + // + // Get properties for a module + // + // Arguments: + // vmModule - vm handle to a module + // pData - required out parameter which will be filled out with module properties + // + // Notes: + // See definition of DomainFileInfo for more details about what properties + // this gives back. + virtual + void GetModuleData(VMPTR_Module vmModule, OUT ModuleInfo * pData) = 0; + + + // + // Get properties for a DomainFile + // + // Arguments: + // vmDomainFile - vm handle to a DomainFile + // pData - required out parameter which will be filled out with module properties + // + // Notes: + // See definition of DomainFileInfo for more details about what properties + // this gives back. + virtual + void GetDomainFileData(VMPTR_DomainFile vmDomainFile, OUT DomainFileInfo * pData) = 0; + + virtual + void GetModuleForDomainFile(VMPTR_DomainFile vmDomainFile, OUT VMPTR_Module * pModule) = 0; + + //......................................................................... + // These methods were the methods that DBI was calling from IXClrData in V2. + // We imported them over to this V3 interface so that we can sever all ties between DBI and the + // old IXClrData. + // + // The exact semantics of these are whatever their V2 IXClrData counterpart did. + // We may eventually migrate these to their real V3 replacements. + //......................................................................... + + // "types" of addresses. This is taken exactly from the definition, but renamed to match + // CLR coding conventions. + typedef enum + { + kAddressUnrecognized, + kAddressManagedMethod, + kAddressRuntimeManagedCode, + kAddressRuntimeUnmanagedCode, + kAddressGcData, + kAddressRuntimeManagedStub, + kAddressRuntimeUnmanagedStub, + } AddressType; + + // + // Get the "type" of address. + // + // Arguments: + // address - address to query type. + // + // Return Value: + // Type of address. Throws on error. + // + // Notes: + // This is taken exactly from the IXClrData definition. + // This is provided for V3 compatibility to support Interop-debugging. + // This should eventually be deprecated. + // + virtual + AddressType GetAddressType(CORDB_ADDRESS address) = 0; + + + // + // Query if address is a CLR stub. + // + // Arguments: + // address - Target address to query for. + // + // + // Return Value: + // true if the address is a CLR stub. + // + // Notes: + // This is used to implement ICorDebugProcess::IsTransitionStub + // This yields true if the address is claimed by a CLR stub manager, or if the IP is in mscorwks. + // Conceptually, This should eventually be merged with GetAddressType(). + // + virtual + BOOL IsTransitionStub(CORDB_ADDRESS address) = 0; + + //......................................................................... + // Get the values of the JIT Optimization and EnC flags. + // + // Arguments: + // vmDomainFile - (input) VM DomainFile (module) for which we are retrieving flags + // pfAllowJITOpts - (mandatory output) true iff this is not compiled for debug, + // i.e., without optimization + // pfEnableEnc - (mandatory output) true iff this module has EnC enabled + // + // Return Value: + // Returns on success. Throws on failure. + // + // Notes: + // This is used to implement both ICorDebugModule2::GetJitCompilerFlags and + // ICorDebugCode2::GetCompilerFlags. + //......................................................................... + + virtual + void GetCompilerFlags( + VMPTR_DomainFile vmDomainFile, + OUT BOOL * pfAllowJITOpts, + OUT BOOL * pfEnableEnC) = 0; + + //......................................................................... + // Set the values of the JIT optimization and EnC flags. + // + // Arguments: + // vmDomainFile - (input) VM DomainFile (module) for which we are retrieving flags + // pfAllowJITOpts - (input) true iff this should not be compiled for debug, + // i.e., without optimization + // pfEnableEnc - (input) true iff this module should have EnC enabled. If this is + // false, no change is made to the EnC flags. In other words, once EnC is enabled, + // there is no way to disable it. + // + // Return Value: + // S_OK on success and all bits were set. + // CORDBG_S_NOT_ALL_BITS_SET - if not all bits are set. Must use GetCompileFlags to + // determine which bits were set. + // CORDBG_E_CANT_CHANGE_JIT_SETTING_FOR_ZAP_MODULE - if module is ngenned. + // Throw on other errors. + // + // Notes: + // Caller can only use this at module-load before any methods are jitted. + // This may be called multiple times. + // This is used to implement both ICorDebugModule2::SetJitCompilerFlags and + // ICorDebugModule::EnableJITDebugging. + //......................................................................... + + virtual + HRESULT SetCompilerFlags(VMPTR_DomainFile vmDomainFile, + BOOL fAllowJitOpts, + BOOL fEnableEnC) = 0; + + // + // Enumerate all AppDomains in the process. + // + // Arguments: + // fpCallback - callback to invoke on each appdomain + // pUserData - user data to supply for each callback. + // + // Return Value: + // Returns on success. Throws on error. + // + // Notes: + // Enumerates all appdomains in the process, including the Default-domain. + // Appdomains must show up in this list before the AD Load event is sent, and before + // that appdomain is discoverable from the debugger. + // See enumeration rules for details. + // + typedef void (*FP_APPDOMAIN_ENUMERATION_CALLBACK)(VMPTR_AppDomain vmAppDomain, CALLBACK_DATA pUserData); + virtual + void EnumerateAppDomains(FP_APPDOMAIN_ENUMERATION_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; + + + // + // Eunmerate all Assemblies in an appdomain. Enumerations is in load-order + // + // Arguments: + // vmAppDomain - domain in which to enumerate + // fpCallback - address to query type. + // pUserData - required out parameter for type of address. + // + // Return Value: + // Returns on success. Throws on error. + // + // Notes: + // Enumerates all executable assemblies (both shared and unshared) within an appdomain. + // This does not include inspection-only assemblies because those are just data and + // not executable (eg, they'll never show up on the stack and you can't set a breakpoint in them). + // This enumeration needs to be consistent with load/unload events. + // See enumeration rules for details. + // + // The order of the enumeration is the order the assemblies where loaded. + // Ultimately, the debugger needs to be able to tell the user the load + // order of assemblies (it can do this with native dlls). Since + // managed assembliees don't 1:1 correspond to native dlls, debuggers + // need this information from the runtime. + // + + typedef void (*FP_ASSEMBLY_ENUMERATION_CALLBACK)(VMPTR_DomainAssembly vmDomainAssembly, CALLBACK_DATA pUserData); + virtual + void EnumerateAssembliesInAppDomain(VMPTR_AppDomain vmAppDomain, + FP_ASSEMBLY_ENUMERATION_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; + + + + // + // Callback function for EnumerateModulesInAssembly + // + // This can throw on error. + // + // Arguments: + // vmModule - new module from the enumeration + // pUserData - user data passed to EnumerateModulesInAssembly + typedef void (*FP_MODULE_ENUMERATION_CALLBACK)(VMPTR_DomainFile vmModule, CALLBACK_DATA pUserData); + + // + // Enumerates all the code Modules in an assembly. + // + // Arguments: + // vmAssembly - assembly to enumerate within + // fpCallback - callback function to invoke on each module + // pUserData - arbitrary data passed to the callback + // + // Notes: + // This only enumerates "code" modules (ie, modules that have executable code in them). That + // includes normal file-based, ngenned, in-memory, and even dynamic modules. + // That excludes: + // - Resource modules (which have no code or metadata) + // - Inspection-only modules. These are viewed as pure data from the debugger's perspective. + // + virtual + void EnumerateModulesInAssembly( + VMPTR_DomainAssembly vmAssembly, + FP_MODULE_ENUMERATION_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; + + + + // + // When stopped at an event, request a synchronization. + // + // + // Return Value: + // Returns on success. Throws on error. + // + // Notes: + // Call this when an event is dispatched (eg, LoadModule) to request the runtime + // synchronize. This does a cooperative sync with the LS. This is not an async break + // and can not be called at arbitrary points. + // This primitive lets the LS always take the V3 codepath and defer decision making to the RS. + // The V2 behavior is to call this after every event (Since that's what V2 did). + // The V3 behavior is to never call this. + // + // If this is called, the LS will sync and we will get a SyncComplete. + // + // This is also like a precursor to "AsyncBreakAllOtherThreads" + // + virtual + void RequestSyncAtEvent() = 0; + + // Sets a flag inside LS.Debugger that indicates that + // 1. all "first chance exception" events should not be sent to the debugger + // 2. "exception handler found" events for exceptions never crossing JMC frames should not be sent to the debugger + // + // Arguments: + // sendExceptionsOutsideOfJMC - new value for the flag Debugger::m_sendExceptionsOutsideOfJMC. + // + // Return Value: + // Returns error code, never throws. + // + // Note: This call is used by ICorDebugProcess8.EnableExceptionCallbacksOutsideOfMyCode. + virtual + HRESULT SetSendExceptionsOutsideOfJMC(BOOL sendExceptionsOutsideOfJMC) = 0; + + // + // Notify the debuggee that a debugger atach is pending. + // + // Arguments: + // None + // + // Return Value: + // Returns on success. Throws on error. + // + // Notes: + // Attaching means that CORDebuggerPendingAttach() will now return true. + // This doesn't do anything else (eg, no fake events). + // + // @dbgtodo- still an open Feature-Crew decision how this is exposed publicly. + virtual + void MarkDebuggerAttachPending() = 0; + + // + // Notify the debuggee that a debugger is attached / detached. + // + // Arguments: + // fAttached - true if we're attaching, false if we're detaching. + // + // Return Value: + // Returns on success. Throws on error. + // + // Notes: + // Attaching means that CorDebuggerAttached() will now return true. + // This doesn't do anything else (eg, no fake events). + // This lets the V3 codepaths invade the LS to subscribe to events. + // + // @dbgtodo- still an open Feature-Crew decision how this is exposed publicly. + virtual + void MarkDebuggerAttached(BOOL fAttached) = 0; + + + + // + // Hijack a thread. This will effectively do a native func-eval of the thread to set the IP + // to a hijack stub and push the parameters. + // + // Arguments: + // dwThreadId - OS thread to hijack. This must be consistent with pRecord and pOriginalContext + // pRecord - optional pointer to Exception record. Required if this is hijacked at an exception. + // NULL if this is hijacked at a managed IP. + // pOriginalContext - optional pointer to buffer to receive the context that the thread is hijacked from. + // The caller can use this to either restore the hijack or walk the hijack. + // cbSizeContext - size in bytes of buffer pointed to by pContext + // reason - reason code for the hijack. The hijack stub can then delegate to the proper hijack. + // pUserData - arbitrary data passed through to hijack. This is reason-depedendent. + // pRemoteContextAddr - If non-NULL this receives the remote address where the CONTEXT was written in the + // in the debuggee. + // + // Assumptions: + // Caller must guarantee this is safe. + // This is intended to be used at a thread that either just had an exception or is at a managed IP. + // If this is hijacked at an exception, client must cancel the exception (gh / DBG_CONTINUE) + // so that the OS exception processing doesn't interfere with the hijack. + // + // Notes: + // Hijack is hard, so we want 1 hijack stub that handles all our hijacking needs. + // This lets us share: + // - assembly stubs (which are very platform specific) + // - hijacking / restoration mechanics, + // - making the hijack walkable via the stackwalker. + // + // Hijacking can be used to implement: func-eval, FE abort, Synchronizing, + // dispatching Unhandled Exception notifications. + // + // Nesting: Since Hijacking passes the key state off to the hijacked thread, (such as original + // context to be used with restoring the hijack), the raw hijacking nests just like function + // calls. However, the client may need to keep additional state to handle nesting. For example, + // nested hijacks will require the client to track multiple CONTEXT*. + // + // If the thread is in jitted code, then the hijack needs to cooperate with the in-process + // stackwalker that the GC uses. It must be in cooperative mode, and push a Frame on the + // frame chain to protect the managed frames it hijacked from before it goes to preemptive mode. + + virtual + void Hijack( + VMPTR_Thread vmThread, + ULONG32 dwThreadId, + const EXCEPTION_RECORD * pRecord, + T_CONTEXT * pOriginalContext, + ULONG32 cbSizeContext, + EHijackReason::EHijackReason reason, + void * pUserData, + CORDB_ADDRESS * pRemoteContextAddr) = 0; + + + // + // Callback function for connection enumeration. + // + // Arguments: + // id - the connection ID. + // pName - the name of the connection. + // pUserData - user data supplied to EnumerateConnections + typedef void (*FP_CONNECTION_CALLBACK)(DWORD id, LPCWSTR pName, CALLBACK_DATA pUserData); + + // + // Enumerate all the Connections in the process. + // + // Arguments: + // fpCallback - callback to invoke for each connection + // pUserData - random user data to pass to callback. + // + // Notes: + // This enumerates all the connections. The host notifies the debugger of Connections + // via the ICLRDebugManager interface. + // ICorDebug has no interest in connections. It's merely the transport between the host and the debugger. + // Ideally, that transport would be more general. + // + // V2 Attach would provide faked up CreateConnection, ChangeConnection events on attach. + // This enumeration ability allows V3 to emulate that behavior. + // +#ifdef FEATURE_INCLUDE_ALL_INTERFACES + virtual void EnumerateConnections(FP_CONNECTION_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0; +#endif //FEATURE_INCLUDE_ALL_INTERFACES + + // + // Enumerate all threads in the target. + // + // Arguments: + // fpCallback - callback function to invoke on each thread. + // pUserData - arbitrary user data supplied to each callback. + // + // Notes: + // This enumerates the ThreadStore in the target, which is all the Thread* objects. + // This includes threads that have entered the runtime. This may include threads + // even before that thread has executed IL and after that thread no longer has managed + // code on its stack. + + // Callback invoked for each thread. + typedef void (*FP_THREAD_ENUMERATION_CALLBACK)(VMPTR_Thread vmThread, CALLBACK_DATA pUserData); + + virtual + void EnumerateThreads(FP_THREAD_ENUMERATION_CALLBACK fpCallback, CALLBACK_DATA pUserData) = 0; + + + // Check if the thread is dead + // + // Arguments: + // vmThread - valid thread to check if it's dead. + // + // Returns: true if the thread is "dead", which means it can never call managed code again. + // + // Notes: + // #IsThreadMarkedDead + // Threads shutdown states are: + // 1) Thread is running managed code normally. Thread eventually exits all managed code and + // gets to a point where it will never call managed code again. + // 2) Thread is marked as dead. + // - For threads created outside of the runtime (such as a native thread that wanders into + // managed code), this mark can happen in DllMain(ThreadDetach) + // - For threads created by the runtime (eg, System.Threading.Thread.Start), this may be done + // at the top of the threads stack after it calls the user's Thread-Proc. + // 3) MAYBE Native thread exits at this point (or it may not). This would be the common case + // for threads created outside the runtime. + // 4) Thread exit event is sent. + // - For threads created by the runtime, this may be sent at the top of the thread's + // stack (or even when we know that the thread will never execute managed code again) + // - For threads created outside the runtime, this is more difficult. A thread can + // call into managed code and then return, and then call back into managed code at a + // later time (The finalizer does this!). So it's not clear when the native thread + // actually exits and will never call managed code again. The only hook we have for + // this is DllMain(Thread-Detach). We can mark bits in DllMain, but we can't send + // debugger notifications (too dangerous from such a restricted context). + // So we may mark the thread as dead, but then sweep later (perhaps on the finalizer + // thread), and thus send the Exit events later. + // 5) Native thread may exit at this point. This is the common case for threads created by + // the runtime. + // + // The underlying native thread may have exited at eitehr #3 or #5. Because of this + // flexibility, we don't want to rely on native thread exit events. + // This function checks if a Thread is passed state #2 (marked as dead). The key invariant + // is that once a thread is marked as dead: + // - it can never call managed code again. + // - it should not be discoverable by DacDbi enumerations. + // + // DBI should prefer relying on IsThreadMarkedDead rather than event notifications (either + // managed or native) because tracking events requires that DBI maintain state, which means + // that attach + dump cases may break. For example, we want a full dump at the ExitThread + // event to have the same view as a live process at the ExitThread event. + // + // We avoid relying on the native thread exit notifications because: + // - that's a specific feature of the Win32 debugging API that may not be available on other platforms. + // - the only native events the pipeline gets are Exceptions. + // + // Whether a thread is dead can be inferred from the ICorDebug API. However, we have this + // on DacDbi to ensure that this definition is consistent with the other DacDbi methods, + // especially the enumeration and discovery rules. + virtual + bool IsThreadMarkedDead(VMPTR_Thread vmThread) = 0; + + + // + // Return the handle of the specified thread. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the handle of the specified thread + // + // @dbgtodo- this should go away in V3. This is useless on a dump. + + virtual + HANDLE GetThreadHandle(VMPTR_Thread vmThread) = 0; + + // + // Return the object handle for the managed Thread object corresponding to the specified thread. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // This function returns the object handle for the managed Thread object corresponding to the + // specified thread. The return value may be NULL if a managed Thread object has not been created + // for the specified thread yet. + // + + virtual + VMPTR_OBJECTHANDLE GetThreadObject(VMPTR_Thread vmThread) = 0; + + // + // Set and reset the TSNC_DebuggerUserSuspend bit on the state of the specified thread + // according to the CorDebugThreadState. + // + // Arguments: + // vmThread - the specified thread + // debugState - the desired CorDebugThreadState + // + + virtual + void SetDebugState(VMPTR_Thread vmThread, + CorDebugThreadState debugState) = 0; + + // + // Returns TRUE if this thread has an unhandled exception + // + // Arguments: + // vmThread - the thread to query + // + // Return Value + // TRUE iff this thread has an unhandled exception + // + virtual + BOOL HasUnhandledException(VMPTR_Thread vmThread) = 0; + + // + // Return the user state of the specified thread. Most of the state are derived from + // the ThreadState of the specified thread, e.g. TS_Background, TS_Unstarted, etc. + // The exception is USER_UNSAFE_POINT, which we need to do a one-frame stackwalk to figure out. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the user state of the specified thread + // + + virtual + CorDebugUserState GetUserState(VMPTR_Thread vmThread) = 0; + + + // + // Returns most of the user state of the specified thread, + // i.e. flags which can be derived from the ThreadState: + // USER_STOP_REQUESTED, USER_SUSPEND_REQUESTED, USER_BACKGROUND, USER_UNSTARTED + // USER_STOPPED, USER_WAIT_SLEEP_JOIN, USER_SUSPENDED, USER_THREADPOOL + // + // Only USER_UNSAFE_POINT is always set to 0, since it takes additional stackwalk. + // If you need USER_UNSAFE_POINT, use GetUserState(VMPTR_Thread); + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the user state of the specified thread + // + virtual + CorDebugUserState GetPartialUserState(VMPTR_Thread vmThread) = 0; + + + // + // Return the connection ID of the specified thread. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the connection ID of the specified thread + // + + virtual + CONNID GetConnectionID(VMPTR_Thread vmThread) = 0; + + // + // Return the task ID of the specified thread. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the task ID of the specified thread + // + + virtual + TASKID GetTaskID(VMPTR_Thread vmThread) = 0; + + // + // Return the OS thread ID of the specified thread + // + // Arguments: + // vmThread - the specified thread; cannot be NULL + // + // Return Value: + // the OS thread ID of the specified thread. Returns 0 if not scheduled. + // + + virtual + DWORD TryGetVolatileOSThreadID(VMPTR_Thread vmThread) = 0; + + // + // Return the unique thread ID of the specified thread. The value used for the thread ID changes + // depending on whether the runtime is being hosted. In non-hosted scenarios, a managed thread will + // always be associated with the same native thread, and so we can use the OS thread ID as the thread ID + // for the managed thread. In hosted scenarios, however, a managed thread may run on multiple native + // threads. It may not even have a backing native thread if it's switched out. Therefore, we can't use + // the OS thread ID as the thread ID. Instead, we use the internal managed thread ID. + // + // Arguments: + // vmThread - the specified thread; cannot be NULL + // + // Return Value: + // Returns a stable and unique thread ID for the lifetime of the specified managed thread. + // + + virtual + DWORD GetUniqueThreadID(VMPTR_Thread vmThread) = 0; + + // + // Return the object handle to the managed Exception object of the current exception + // on the specified thread. The return value could be NULL if there is no current exception. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // This function returns the object handle to the managed Exception object of the current exception. + // The return value may be NULL if there is no exception being processed, or if the specified thread + // is an unmanaged thread which has entered and exited the runtime. + // + + virtual + VMPTR_OBJECTHANDLE GetCurrentException(VMPTR_Thread vmThread) = 0; + + // + // Return the object handle to the managed object for a given CCW pointer. + // + // Arguments: + // ccwPtr - the specified ccw pointer + // + // Return Value: + // This function returns the object handle to the managed object for a given CCW pointer. + // + + virtual + VMPTR_OBJECTHANDLE GetObjectForCCW(CORDB_ADDRESS ccwPtr) = 0; + + // + // Return the object handle to the managed CustomNotification object of the current notification + // on the specified thread. The return value could be NULL if there is no current notification. + // + // Arguments: + // vmThread - the specified thread on which the notification occurred + // + // Return Value: + // This function returns the object handle to the managed CustomNotification object of the current notification. + // The return value may be NULL if there is no current notification. + // + + virtual + VMPTR_OBJECTHANDLE GetCurrentCustomDebuggerNotification(VMPTR_Thread vmThread) = 0; + + + // + // Return the current appdomain the specified thread is in. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the current appdomain of the specified thread + // + // Notes: + // This function throws if the current appdomain is NULL for whatever reason. + // + + virtual + VMPTR_AppDomain GetCurrentAppDomain(VMPTR_Thread vmThread) = 0; + + + // + // Resolve an assembly + // + // Arguments: + // vmScope - module containing metadata that the token is scoped to. + // tkAssemblyRef - assembly ref token to lookup. + // + // Returns: + // Assembly that the loader/fusion has bound to the given assembly ref. + // Returns NULL if the assembly has not yet been loaded (a common case). + // Throws on error. + // + // Notes: + // A single module has metadata that specifies references via tokens. The + // loader/fusion goes through tremendous and random policy hoops to determine + // which specific file actually gets bound to the reference. This policy includes + // things like config files, registry settings, and many other knobs. + // + // The debugger can't duplicate this policy with 100% accuracy, and + // so we need DAC to lookup the assembly that was actually loaded. + virtual + VMPTR_DomainAssembly ResolveAssembly(VMPTR_DomainFile vmScope, mdToken tkAssemblyRef) = 0; + + //----------------------------------------------------------------------------- + // Interface for initializing the native/IL sequence points and native var info + // for a function. + // Arguments: + // input: + // vmMethodDesc MethodDesc of the function + // startAddr starting address of the function--this serves to + // differentiate various EnC versions of the function + // fCodePitched indicates whether code for the function has been pitched + // fJitComplete indicates whether the function has been jitted + // output: + // pNativeVarData space for the native code offset information for locals + // pSequencePoints space for the IL/native sequence points + // Return value: + // none, but may throw an exception + // Assumptions: + // vmMethodDesc, pNativeVarInfo and pSequencePoints are non-NULL + + // Notes: + //----------------------------------------------------------------------------- + + virtual + void GetNativeCodeSequencePointsAndVarInfo(VMPTR_MethodDesc vmMethodDesc, + CORDB_ADDRESS startAddress, + BOOL fCodeAvailabe, + OUT NativeVarData * pNativeVarData, + OUT SequencePoints * pSequencePoints) = 0; + + // + // Return the filter CONTEXT on the LS. Once we move entirely over to the new managed pipeline + // built on top of the Win32 debugging API, this won't be necessary. + // + // Arguments: + // vmThread - the specified thread + // + // Return Value: + // the filter CONTEXT of the specified thread + // + // Notes: + // This function should go away when everything is moved OOP and + // we don't have a filter CONTEXT on the LS anymore. + // + + virtual + VMPTR_CONTEXT GetManagedStoppedContext(VMPTR_Thread vmThread) = 0; + + typedef enum + { + kInvalid, + kManagedStackFrame, + kExplicitFrame, + kNativeStackFrame, + kNativeRuntimeUnwindableStackFrame, + kAtEndOfStack, + } FrameType; + + // The stackwalker functions allocate persistent state within DDImpl. Clients can hold onto + // this via an opaque StackWalkHandle. + typedef void* * StackWalkHandle; + + // + // Create a stackwalker on the specified thread and return a handle to it. + // Initially, the stackwalker is at the filter CONTEXT if there is one. + // Otherwise it is at the leaf CONTEXT. It DOES NOT fast forward to the first frame of interest. + // + // Arguments: + // vmThread - the specified thread + // pInternalContextBuffer - a CONTEXT buffer for the stackwalker to work with + // ppSFIHandle - out parameter; return a handle to the stackwalker + // + // Notes: + // Call DeleteStackWalk() to delete the stackwalk buffer. + // This is a special case that violates the 'no state' tenant. + // + + virtual + void CreateStackWalk(VMPTR_Thread vmThread, + DT_CONTEXT * pInternalContextBuffer, + OUT StackWalkHandle * ppSFIHandle) = 0; + + // Delete the stackwalk object created from CreateStackWalk. + virtual + void DeleteStackWalk(StackWalkHandle ppSFIHandle) = 0; + + // + // Get the CONTEXT of the current frame where the stackwalker is stopped at. + // + // Arguments: + // pSFIHandle - the handle to the stackwalker + // pContext - OUT: the CONTEXT to be filled out. The context control flags are ignored. + // + + virtual + void GetStackWalkCurrentContext(StackWalkHandle pSFIHandle, + DT_CONTEXT * pContext) = 0; + + // + // Set the stackwalker to the given CONTEXT. The CorDebugSetContextFlag indicates whether + // the CONTEXT is "active", meaning that the IP is point at the current instruction, + // not the return address of some function call. + // + // Arguments: + // vmThread - the current thread + // pSFIHandle - the handle to the stackwalker + // flag - flag to indicate whether the specified CONTEXT is "active" + // pContext - the specified CONTEXT. This may make correctional adjustments to the context's IP. + // + + virtual + void SetStackWalkCurrentContext(VMPTR_Thread vmThread, + StackWalkHandle pSFIHandle, + CorDebugSetContextFlag flag, + DT_CONTEXT * pContext) = 0; + + // + // Unwind the stackwalker to the next frame. The next frame could be any actual stack frame, + // explicit frame, native marker frame, etc. Call GetStackWalkCurrentFrameInfo() to find out + // more about the frame. + // + // Arguments: + // pSFIHandle - the handle to the stackwalker + // + // Return Value: + // Return TRUE if we successfully unwind to the next frame. + // Return FALSE if there is no more frames to walk. + // Throw on error. + // + + virtual + BOOL UnwindStackWalkFrame(StackWalkHandle pSFIHandle) = 0; + + // + // Check whether the specified CONTEXT is valid. The only check we perform right now is whether the + // SP in the specified CONTEXT is in the stack range of the thread. + // + // Arguments: + // vmThread - the specified thread + // pContext - the CONTEXT to be checked + // + // Return Value: + // Return S_OK if the CONTEXT passes our checks. + // Returns CORDBG_E_NON_MATCHING_CONTEXT if the SP in the specified CONTEXT doesn't fall in the stack + // range of the thread. + // Throws on error. + // + + virtual + HRESULT CheckContext(VMPTR_Thread vmThread, + const DT_CONTEXT * pContext) = 0; + + // + // Fill in the DebuggerIPCE_STRData structure with information about the current frame + // where the stackwalker is stopped at. + // + // Arguments: + // pSFIHandle - the handle to the stackwalker + // pFrameData - the DebuggerIPCE_STRData to be filled out; + // it can be NULL if you just want to know the frame type + // + // Return Value: + // Return the type of the current frame + // + + virtual + FrameType GetStackWalkCurrentFrameInfo(StackWalkHandle pSFIHandle, + OPTIONAL DebuggerIPCE_STRData * pFrameData) = 0; + + // + // Return the number of internal frames on the specified thread. + // + // Arguments: + // vmThread - the thread whose internal frames are being retrieved + // + // Return Value: + // Return the number of internal frames. + // + // Notes: + // Explicit frames are "marker objects" the runtime pushes on the stack to mark special places, e.g. + // appdomain transition, managed-to- unmanaged transition, etc. Internal frames are only a subset of + // explicit frames. Explicit frames which are not interesting to the debugger are not exposed (e.g. + // GCFrame). Internal frames are interesting to the debugger if they have a CorDebugInternalFrameType + // other than STUBFRAME_NONE. + // + // The user should call this function before code:IDacDbiInterface::EnumerateInternalFrames to figure + // out how many interesting internal frames there are. + // + + virtual + ULONG32 GetCountOfInternalFrames(VMPTR_Thread vmThread) = 0; + + // + // Enumerate the internal frames on the specified thread and invoke the provided callback on each of + // them. Information about the internal frame is stored in the DebuggerIPCE_STRData. + // + // Arguments: + // vmThread - the thread to be walked fpCallback - callback function invoked on each internal frame + // pUserData - user-specified custom data + // + // Notes: + // The user can call code:IDacDbiInterface::GetCountOfInternalFrames to figure out how many internal + // frames are on the thread before calling this function. Also, refer to the comment of that function + // to find out more about internal frames. + // + + typedef void (*FP_INTERNAL_FRAME_ENUMERATION_CALLBACK)(const DebuggerIPCE_STRData * pFrameData, CALLBACK_DATA pUserData); + + virtual + void EnumerateInternalFrames(VMPTR_Thread vmThread, + FP_INTERNAL_FRAME_ENUMERATION_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; + + // + // Given the FramePointer of the parent frame and the FramePointer of the current frame, + // check if the current frame is the parent frame. fpParent should have been returned + // previously by the DacDbiInterface via GetStackWalkCurrentFrameInfo(). + // + // Arguments: + // fpToCheck - the FramePointer of the current frame + // fpParent - the FramePointer of the parent frame; should have been returned earlier by the DDI + // + // Return Value: + // Return TRUE if the current frame is indeed the parent frame + // + // Note: + // Because of the complexity involved in checking for the parent frame, we should always + // ask the ExceptionTracker to do it. + // + + virtual + BOOL IsMatchingParentFrame(FramePointer fpToCheck, FramePointer fpParent) = 0; + + // + // Return the stack parameter size of a given method. This is necessary on x86 for unwinding. + // + // Arguments: + // controlPC - any address in the specified method; you can use the current PC of the stack frame + // + // Return Value: + // Return the size of the stack parameters of the given method. + // Return 0 for vararg methods. + // + // Assumptions: + // The callee stack parameter size is constant throughout a method. + // + + virtual + ULONG32 GetStackParameterSize(CORDB_ADDRESS controlPC) = 0; + + // + // Return the FramePointer of the current frame where the stackwalker is stopped at. + // + // Arguments: + // pSFIHandle - the handle to the stackwalker + // + // Return Value: + // the FramePointer of the current frame + // + // Notes: + // The FramePointer of a stack frame is: + // the stack address of the return address on x86, + // the current SP on AMD64, + // + // On x86, to get the stack address of the return address, we need to unwind one more frame + // and use the SP of the caller frame as the FramePointer of the callee frame. This + // function does NOT do that. It just returns the SP. The caller needs to handle the + // unwinding. + // + // The FramePointer of an explicit frame is just the stack address of the explicit frame. + // + + virtual + FramePointer GetFramePointer(StackWalkHandle pSFIHandle) = 0; + + // + // Check whether the specified CONTEXT is the CONTEXT of the leaf frame. This function doesn't care + // whether the leaf frame is native or managed. + // + // Arguments: + // vmThread - the specified thread + // pContext - the CONTEXT to check + // + // Return Value: + // Return TRUE if the specified CONTEXT is the leaf CONTEXT. + // + // Notes: + // Currently we check the specified CONTEXT against the filter CONTEXT first. + // This will be deprecated in V3. + // + + virtual + BOOL IsLeafFrame(VMPTR_Thread vmThread, + const DT_CONTEXT * pContext) = 0; + + // Get the context for a particular thread of the target process. + // Arguments: + // input: vmThread - the thread for which the context is required + // output: pContextBuffer - the address of the CONTEXT to be initialized. + // The memory for this belongs to the caller. It must not be NULL. + // Note: throws + virtual + void GetContext(VMPTR_Thread vmThread, DT_CONTEXT * pContextBuffer) = 0; + + // + // This is a simple helper function to convert a CONTEXT to a DebuggerREGDISPLAY. We need to do this + // inside DDI because the RS has no notion of REGDISPLAY. + // + // Arguments: + // pInContext - the CONTEXT to be converted + // pOutDRD - the converted DebuggerREGDISPLAY + // fActive - Indicate whether the CONTEXT is active or not. An active CONTEXT means that the + // IP is the next instruction to be executed, not the return address of a function call. + // The opposite of an active CONTEXT is an unwind CONTEXT, which is obtained from + // unwinding. + // + + virtual + void ConvertContextToDebuggerRegDisplay(const DT_CONTEXT * pInContext, + DebuggerREGDISPLAY * pOutDRD, + BOOL fActive) = 0; + + typedef enum + { + kNone, + kILStub, + kLCGMethod, + } DynamicMethodType; + + // + // Check whether the specified method is an IL stub or an LCG method. This answer determines if we + // need to expose the method in a V2-style stackwalk. + // + // Arguments: + // vmMethodDesc - the method to be checked + // + // Return Value: + // Return kNone if the method is neither an IL stub or an LCG method. + // Return kILStub if the method is an IL stub. + // Return kLCGMethod if the method is an LCG method. + // + + virtual + DynamicMethodType IsILStubOrLCGMethod(VMPTR_MethodDesc vmMethodDesc) = 0; + + // + // Return a TargetBuffer for the raw vararg signature. + // Also return the address of the first argument in the vararg signature. + // + // Arguments: + // VASigCookieAddr - the target address of the VASigCookie pointer (double indirection) + // pArgBase - out parameter; return the target address of the first word of the arguments + // + // Return Value: + // Return a TargetBuffer for the raw vararg signature. + // + // Notes: + // We can't take a VMPTR here because VASigCookieAddr does not come from the DDI. Instead, + // we use the native variable information to figure out which stack slot contains the + // VASigCookie pointer. So a remote address is all we have got. + // + // Ideally we should be able to return just a SigParser, but doing so has a not-so-trivial problem. + // The memory used for the signature pointed to by the SigParser cannot be allocated in the DAC cache, + // since it'll be used by mscordbi. We don't have a clean way to allocate memory in mscordbi without + // breaking the Signature abstraction. + // + // The other option would be to create a new sub-type like "SignatureCopy" which allocates and frees + // its own backing memory. Currently we don't want to share heaps between mscordacwks.dll and + // mscordbi.dll, and so we would have to jump through some hoops to allocate with an allocator + // in mscordbi.dll. + // + + virtual + TargetBuffer GetVarArgSig(CORDB_ADDRESS VASigCookieAddr, + OUT CORDB_ADDRESS * pArgBase) = 0; + + // + // Indicates if the specified type requires 8-byte alignment. + // + // Arguments: + // thExact - the exact TypeHandle of the type to query + // + // Return Value: + // TRUE if the type requires 8-byte alignment. + // + + virtual + BOOL RequiresAlign8(VMPTR_TypeHandle thExact) = 0; + + // + // Resolve the raw generics token to the real generics type token. The resolution is based on the + // given index. See Notes below. + // + // Arguments: + // dwExactGenericArgsTokenIndex - the variable index of the generics type token + // rawToken - the raw token to be resolved + // + // Return Value: + // Return the actual generics type token. + // + // Notes: + // DDI tells the RS which variable stores the generics type token, but DDI doesn't retrieve the value + // of the variable itself. Instead, the RS retrieves the value of the variable. However, + // in some cases, the variable value is not the generics type token. In this case, we need to + // "resolve" the variable value to the generics type token. The RS should call this API to do that. + // + // If the index is 0, then the generics type token is the MethodTable of the "this" object. + // rawToken will be the address of the "this" object. + // + // If the index is TYPECTXT_ILNUM, the generics type token is a secret argument. + // It could be a MethodDesc or a MethodTable, and in this case no resolution is actually necessary. + // rawToken will be the actual secret argument, and this API really is just a nop. + // + // However, we don't want the RS to know all this logic. + // + + virtual + GENERICS_TYPE_TOKEN ResolveExactGenericArgsToken(DWORD dwExactGenericArgsTokenIndex, + GENERICS_TYPE_TOKEN rawToken) = 0; + + //----------------------------------------------------------------------------- + // Functions to get information about code objects + //----------------------------------------------------------------------------- + + // GetILCodeAndSig returns the function's ILCode and SigToken given + // a module and a token. The info will come from a MethodDesc, if + // one exists or from metadata. + // + // Arguments: + // Input: + // vmDomainFile - module containing metadata for the method + // functionToken - metadata token for the function + // Output (required): + // codeInfo - start address and size of the IL + // pLocalSigToken - signature token for the method + virtual + void GetILCodeAndSig(VMPTR_DomainFile vmDomainFile, + mdToken functionToken, + OUT TargetBuffer * pCodeInfo, + OUT mdToken * pLocalSigToken) = 0; + + // Gets information about a native code blob: + // it's method desc, whether it's an instantiated generic, its EnC version number + // and hot and cold region information. + // Arguments: + // Input: + // vmDomainFile - module containing metadata for the method + // functionToken - token for the function for which we need code info + // Output (required): + // pCodeInfo - data structure describing the native code regions. + // Notes: If the function is unjitted, the method desc will be NULL and the + // output parameter will be invalid. In general, if the native start address + // is unavailable for any reason, the output parameter will also be + // invalid (i.e., pCodeInfo->IsValid is false). + + virtual + void GetNativeCodeInfo(VMPTR_DomainFile vmDomainFile, + mdToken functionToken, + OUT NativeCodeFunctionData * pCodeInfo) = 0; + + // Gets information about a native code blob: + // it's method desc, whether it's an instantiated generic, its EnC version number + // and hot and cold region information. + // This is similar to function above, just works from a different starting point + // Also this version can get info for any particular EnC version instance + // because they all have different start addresses whereas the above version gets + // the most recent one + // Arguments: + // Input: + // hotCodeStartAddr - the beginning of the code hot code region + // Output (required): + // pCodeInfo - data structure describing the native code regions. + + virtual + void GetNativeCodeInfoForAddr(VMPTR_MethodDesc vmMethodDesc, + CORDB_ADDRESS hotCodeStartAddr, + NativeCodeFunctionData * pCodeInfo) = 0; + + //----------------------------------------------------------------------------- + // Functions to get information about types + //----------------------------------------------------------------------------- + + // Determine if a type is a ValueType + // + // Arguments: + // input: vmTypeHandle - the type being checked (works even on unrestored types) + // + // Return: + // TRUE iff the type is a ValueType + + virtual + BOOL IsValueType (VMPTR_TypeHandle th) = 0; + + // Determine if a type has generic parameters + // + // Arguments: + // input: vmTypeHandle - the type being checked (works even on unrestored types) + // + // Return: + // TRUE iff the type has generic parameters + + virtual + BOOL HasTypeParams (VMPTR_TypeHandle th) = 0; + + // Get type information for a class + // + // Arguments: + // input: vmAppDomain - appdomain where we will fetch field data for the type + // thExact - exact type handle for type + // output: + // pData - structure containing information about the class and its + // fields + + virtual + void GetClassInfo (VMPTR_AppDomain vmAppDomain, + VMPTR_TypeHandle thExact, + ClassInfo * pData) = 0; + + // get field information and object size for an instantiated generic + // + // Arguments: + // input: vmDomainFile - module containing metadata for the type + // thExact - exact type handle for type (may be NULL) + // thApprox - approximate type handle for the type + // output: + // pFieldList - array of structures containing information about the fields. Clears any previous + // contents. Allocated and initialized by this function. + // pObjectSize - size of the instantiated object + // + virtual + void GetInstantiationFieldInfo (VMPTR_DomainFile vmDomainFile, + VMPTR_TypeHandle vmThExact, + VMPTR_TypeHandle vmThApprox, + OUT DacDbiArrayList<FieldData> * pFieldList, + OUT SIZE_T * pObjectSize) = 0; + + // use a type handle to get the information needed to create the corresponding RS CordbType instance + // + // Arguments: + // input: boxed - indicates what, if anything, is boxed. See code:AreValueTypesBoxed for more + // specific information + // vmAppDomain - module containing metadata for the type + // vmTypeHandle - type handle for the type + // output: pTypeInfo - holds information needed to build the corresponding CordbType + // + virtual + void TypeHandleToExpandedTypeInfo(AreValueTypesBoxed boxed, + VMPTR_AppDomain vmAppDomain, + VMPTR_TypeHandle vmTypeHandle, + DebuggerIPCE_ExpandedTypeData * pTypeInfo) = 0; + + virtual + void GetObjectExpandedTypeInfo(AreValueTypesBoxed boxed, + VMPTR_AppDomain vmAppDomain, + CORDB_ADDRESS addr, + OUT DebuggerIPCE_ExpandedTypeData * pTypeInfo) = 0; + + + virtual + void GetObjectExpandedTypeInfoFromID(AreValueTypesBoxed boxed, + VMPTR_AppDomain vmAppDomain, + COR_TYPEID id, + OUT DebuggerIPCE_ExpandedTypeData * pTypeInfo) = 0; + + + // Get type handle for a TypeDef token, if one exists. For generics this returns the open type. + // Note there is no guarantee the returned handle will be fully restored (in pre-jit scenarios), + // only that it exists. Later functions that use this type handle should fail if they require + // information not yet available at the current restoration level + // + // Arguments: + // input: vmModule - the module scope in which to look up the type def + // metadataToken - the type definition to retrieve + // + // Return value: the type handle if it exists or throws CORDBG_E_CLASS_NOT_LOADED if it isn't loaded + // + virtual + VMPTR_TypeHandle GetTypeHandle(VMPTR_Module vmModule, + mdTypeDef metadataToken) = 0; + + // Get the approximate type handle for an instantiated type. This may be identical to the exact type handle, + // but if we have code sharing for generics, it may differ in that it may have canonical type parameters. + // This will occur if we have not yet loaded an exact type but we have loaded the canonical form of the + // type. + // + // Arguments: + // input: pTypeData - information needed to get the type handle, this includes a list of type parameters + // and the number of entries in the list. Allocated and initialized by the caller. + // Return value: the approximate type handle + // + virtual + VMPTR_TypeHandle GetApproxTypeHandle(TypeInfoList * pTypeData) = 0; + + // Get the exact type handle from type data. + // Arguments: + // input: pTypeData - type information for the type. includes information about + // the top-level type as well as information + // about the element type for array types, the referent for + // pointer types, or actual parameters for generic class or + // valuetypes, as appropriate for the top-level type. + // pArgInfo - This is preallocated and initialized by the caller and contains two fields: + // genericArgsCount - number of type parameters (these may be actual type parameters + // for generics or they may represent the element type or referent + // type. + // pGenericArgData - list of type parameters + // vmTypeHandle - the exact type handle derived from the type information + // Return Value: an HRESULT indicating the result of the operation + virtual + HRESULT GetExactTypeHandle(DebuggerIPCE_ExpandedTypeData * pTypeData, + ArgInfoList * pArgInfo, + VMPTR_TypeHandle& vmTypeHandle) = 0; + + // + // Retrieve the generic type params for a given MethodDesc. This function is specifically + // for stackwalking because it requires the generic type token on the stack. + // + // Arguments: + // vmAppDomain - the appdomain of the MethodDesc + // vmMethodDesc - the method in question + // genericsToken - the generic type token in the stack frame owned by the method + // + // pcGenericClassTypeParams - out parameter; returns the number of type parameters for the class + // containing the method in question; must not be NULL + // pGenericTypeParams - out parameter; returns an array of type parameters and + // the count of the total number of type parameters; must not be NULL + // + // Notes: + // The memory for the array is allocated by this function on the Dbi heap. + // The caller is responsible for releasing it. + // + + virtual + void GetMethodDescParams(VMPTR_AppDomain vmAppDomain, + VMPTR_MethodDesc vmMethodDesc, + GENERICS_TYPE_TOKEN genericsToken, + OUT UINT32 * pcGenericClassTypeParams, + OUT TypeParamsList * pGenericTypeParams) = 0; + + // Get the target field address of a context or thread local static. + // Arguments: + // input: vmField - pointer to the field descriptor for the static field + // vmRuntimeThread - thread to which the static field belongs. This must + // NOT be NULL + // Return Value: The target address of the field if the field is allocated. + // NULL if the field storage is not yet allocated. + // + // Note: + // Static field storage is lazily allocated, so this may commonly return NULL. + // This is an inspection only method and can not allocate the static storage. + // Field storage is constant once allocated, so this value can be cached. + + virtual + CORDB_ADDRESS GetThreadOrContextStaticAddress(VMPTR_FieldDesc vmField, + VMPTR_Thread vmRuntimeThread) = 0; + + // Get the target field address of a collectible types static. + // Arguments: + // input: vmField - pointer to the field descriptor for the static field + // vmAppDomain - AppDomain to which the static field belongs. This must + // NOT be NULL + // Return Value: The target address of the field if the field is allocated. + // NULL if the field storage is not yet allocated. + // + // Note: + // Static field storage may not exist yet, so this may commonly return NULL. + // This is an inspection only method and can not allocate the static storage. + // Field storage is not constant once allocated so this value can not be cached + // across a Continue + + virtual + CORDB_ADDRESS GetCollectibleTypeStaticAddress(VMPTR_FieldDesc vmField, + VMPTR_AppDomain vmAppDomain) = 0; + + // Get information about a field added with Edit And Continue. + // Arguments: + // intput: pEnCFieldInfo - information about the EnC added field including: + // object to which it belongs (if this is null the field is static) + // the field token + // the class token for the class to which the field was added + // the offset to the fields + // the domain file + // an indication of the type: whether it's a class or value type + // output: pFieldData - information about the EnC added field + // pfStatic - flag to indicate whether the field is static + virtual + void GetEnCHangingFieldInfo(const EnCHangingFieldInfo * pEnCFieldInfo, + OUT FieldData * pFieldData, + OUT BOOL * pfStatic) = 0; + + + // GetTypeHandleParams gets the necessary data for a type handle, i.e. its + // type parameters, e.g. "String" and "List<int>" from the type handle + // for "Dict<String,List<int>>", and sends it back to the right side. + // Arguments: + // input: vmAppDomain - app domain to which the type belongs + // vmTypeHandle - type handle for the type + // output: pParams - list of instances of DebuggerIPCE_ExpandedTypeData, + // one for each type parameter. These will be used on the + // RS to build up an instantiation which will allow + // building an instance of CordbType for the top-level + // type. The memory for this list is allocated on the dbi + // heap in this function. + // This will not fail except for OOM + + virtual + void GetTypeHandleParams(VMPTR_AppDomain vmAppDomain, + VMPTR_TypeHandle vmTypeHandle, + OUT TypeParamsList * pParams) = 0; + + // GetSimpleType + // gets the metadata token and domain file corresponding to a simple type + // Arguments: + // input: vmAppDomain - Appdomain in which simpleType resides + // simpleType - CorElementType value corresponding to a simple type + // output: pMetadataToken - the metadata token corresponding to simpleType, + // in the scope of vmDomainFile. + // vmDomainFile - the domainFile for simpleType + // Notes: + // This is inspection-only. If the type is not yet loaded, it will throw CORDBG_E_CLASS_NOT_LOADED. + // It will not try to load a type. + // If the type has been loaded, vmDomainFile will be non-null unless the target is somehow corrupted. + // In that case, we will throw CORDBG_E_TARGET_INCONSISTENT. + + virtual + void GetSimpleType(VMPTR_AppDomain vmAppDomain, + CorElementType simpleType, + OUT mdTypeDef * pMetadataToken, + OUT VMPTR_Module * pVmModule, + OUT VMPTR_DomainFile * pVmDomainFile) = 0; + + // for the specified object returns TRUE if the object derives from System.Exception + virtual + BOOL IsExceptionObject(VMPTR_Object vmObject) = 0; + + // gets the list of raw stack frames for the specified exception object + virtual + void GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList<DacExceptionCallStackData>& dacStackFrames) = 0; + + // Returns true if the argument is a runtime callable wrapper + virtual + BOOL IsRcw(VMPTR_Object vmObject) = 0; + + // retrieves the list of COM interfaces implemented by vmObject, as it is known at + // the time of the call (the list may change as new interface types become available + // in the runtime) + virtual + void GetRcwCachedInterfaceTypes( + VMPTR_Object vmObject, + VMPTR_AppDomain vmAppDomain, + BOOL bIInspectableOnly, + OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pDacInterfaces) = 0; + + // retrieves the list of interfaces pointers implemented by vmObject, as it is known at + // the time of the call (the list may change as new interface types become available + // in the runtime) + virtual + void GetRcwCachedInterfacePointers( + VMPTR_Object vmObject, + BOOL bIInspectableOnly, + OUT DacDbiArrayList<CORDB_ADDRESS> * pDacItfPtrs) = 0; + + // retrieves a list of interface types corresponding to the passed in + // list of IIDs. the interface types are retrieved from an app domain + // IID / Type cache, that is updated as new types are loaded. will + // have NULL entries corresponding to unknown IIDs in "iids" + virtual + void GetCachedWinRTTypesForIIDs( + VMPTR_AppDomain vmAppDomain, + DacDbiArrayList<GUID> & iids, + OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes) = 0; + + // retrieves the whole app domain cache of IID / Type mappings. + virtual + void GetCachedWinRTTypes( + VMPTR_AppDomain vmAppDomain, + OUT DacDbiArrayList<GUID> * piids, + OUT DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> * pTypes) = 0; + + + // ---------------------------------------------------------------------------- + // functions to get information about reference/handle referents for ICDValue + // ---------------------------------------------------------------------------- + + // Get object information for a TypedByRef object. Initializes the objRef and typedByRefType fields of + // pObjectData (type info for the referent). + // Arguments: + // input: pTypedByRef - pointer to a TypedByRef struct + // vmAppDomain - AppDomain for the type of the object referenced + // output: pObjectData - information about the object referenced by pTypedByRef + // Note: Throws + virtual + void GetTypedByRefInfo(CORDB_ADDRESS pTypedByRef, + VMPTR_AppDomain vmAppDomain, + DebuggerIPCE_ObjectData * pObjectData) = 0; + + // Get the string length and offset to string base for a string object + // Arguments: + // input: objPtr - address of a string object + // output: pObjectData - fills in the string fields stringInfo.offsetToStringBase and + // stringInfo.length + // Note: throws + virtual + void GetStringData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData) = 0; + + // Get information for an array type referent of an objRef, including rank, upper and lower bounds, + // element size and type, and the number of elements. + // Arguments: + // input: objectAddress - the address of an array object + // output: pObjectData - fills in the array-related fields: + // arrayInfo.offsetToArrayBase, + // arrayInfo.offsetToLowerBounds, + // arrayInfo.offsetToUpperBounds, + // arrayInfo.componentCount, + // arrayInfo.rank, + // arrayInfo.elementSize, + // Note: throws + virtual + void GetArrayData(CORDB_ADDRESS objectAddress, DebuggerIPCE_ObjectData * pObjectData) = 0; + + // Get information about an object for which we have a reference, including the object size and + // type information. + // Arguments: + // input: objectAddress - address of the object for which we want information + // type - the basic type of the object (we may find more specific type + // information for the object) + // vmAppDomain - the appdomain to which the object belong + // output: pObjectData - fills in the size and type information fields + // Note: throws + virtual + void GetBasicObjectInfo(CORDB_ADDRESS objectAddress, + CorElementType type, + VMPTR_AppDomain vmAppDomain, + DebuggerIPCE_ObjectData * pObjectData) = 0; + + // -------------------------------------------------------------------------------------------- +#ifdef TEST_DATA_CONSISTENCY + // Determine whether a crst is held by the left side. When the DAC is executing VM code that takes a + // lock, we want to know whether the LS already holds that lock. If it does, we will assume the locked + // data is in an inconsistent state and will throw an exception, rather than relying on this data. This + // function is part of a self-test that will ensure we are correctly detecting when the LS holds a lock + // on data the RS is trying to inspect. + // Argument: + // input: vmCrst - the lock to test + // output: none + // Notes: + // Throws + // For this code to run, the environment variable TestDataConsistency must be set to 1. + virtual + void TestCrst(VMPTR_Crst vmCrst) = 0; + + // Determine whether a crst is held by the left side. When the DAC is executing VM code that takes a + // lock, we want to know whether the LS already holds that lock. If it does, we will assume the locked + // data is in an inconsistent state and will throw an exception, rather than relying on this data. This + // function is part of a self-test that will ensure we are correctly detecting when the LS holds a lock + // on data the RS is trying to inspect. + // Argument: + // input: vmRWLock - the lock to test + // output: none + // Notes: + // Throws + // For this code to run, the environment variable TestDataConsistency must be set to 1. + + virtual + void TestRWLock(VMPTR_SimpleRWLock vmRWLock) = 0; +#endif + // -------------------------------------------------------------------------------------------- + // Get the address of the Debugger control block on the helper thread. The debugger control block + // contains information about the status of the debugger, handles to various events and space to hold + // information sent back and forth between the debugger and the debuggee's helper thread. + // Arguments: none + // Return Value: The remote address of the Debugger control block allocated on the helper thread + // if it has been successfully allocated or NULL otherwise. + virtual + CORDB_ADDRESS GetDebuggerControlBlockAddress() = 0; + + // Creates a VMPTR of an Object. The Object is found by dereferencing ptr + // as though it is a target address to an OBJECTREF. This is similar to + // GetObject with another level of indirection. + // + // Arguments: + // ptr - A target address pointing to an OBJECTREF + // + // Return Value: + // A VMPTR to the Object which ptr points to + // + // Notes: + // The VMPTR this produces can be deconstructed by GetObjectContents. + // This function will throw if given a NULL or otherwise invalid pointer, + // but if given a valid address to an invalid pointer, it will produce + // a VMPTR_Object which points to invalid memory. + virtual + VMPTR_Object GetObjectFromRefPtr(CORDB_ADDRESS ptr) = 0; + + // Creates a VMPTR of an Object. The Object is assumed to be at the target + // address supplied by ptr + // + // Arguments: + // ptr - A target address to an Object + // + // Return Value: + // A VMPTR to the Object which was at ptr + // + // Notes: + // The VMPTR this produces can be deconstructed by GetObjectContents. + // This will produce a VMPTR_Object regardless of whether the pointer is + // valid or not. + virtual + VMPTR_Object GetObject(CORDB_ADDRESS ptr) = 0; + + // Sets state in the native binder. + // + // Arguments: + // ePolicy - the NGEN policy to change + // + // Return Value: + // HRESULT indicating if the state was successfully updated + // + virtual + HRESULT EnableNGENPolicy(CorDebugNGENPolicy ePolicy) = 0; + + // Sets the NGEN compiler flags. This restricts NGEN to only use images with certain + // types of pregenerated code. With respect to debugging this is used to specify that + // the NGEN image must be debuggable aka non-optimized code. Note that these flags + // are merged with other sources of configuration so it is possible that the final + // result retrieved from GetDesiredNGENCompilerFlags does not match what was specfied + // in this call. + // + // If an NGEN image of the appropriate type isn't available then one of two things happens: + // a) the NGEN image isn't loaded and CLR loads the MSIL image instead + // b) the NGEN image is loaded, but we don't use the pregenerated code it contains + // and instead use only the MSIL and metadata + // + // This function is only legal to call at app startup before any decisions have been + // made about NGEN image loading. Once we begin loading this configuration is immutable. + // + // + // Arguments: + // dwFlags - the new NGEN compiler flags that should go into effect + // + // Return Value: + // HRESULT indicating if the state was successfully updated. On error the + // current flags in effect will not have changed. + // + virtual + HRESULT SetNGENCompilerFlags(DWORD dwFlags) = 0; + + // Gets the NGEN compiler flags currently in effect. This accounts for settings that + // were caused by SetDesiredNGENCompilerFlags as well as other configuration sources. + // See SetDesiredNGENCompilerFlags for more info + // + // Arguments: + // pdwFlags - the NGEN compiler flags currently in effect + // + // Return Value: + // HRESULT indicating if the state was successfully retrieved. + // + virtual + HRESULT GetNGENCompilerFlags(DWORD *pdwFlags) = 0; + + // Create a VMPTR_OBJECTHANDLE from a CORDB_ADDRESS pointing to an object handle + // + // Arguments: + // handle: target address of a GC handle + // + // ReturnValue: + // returns a VMPTR_OBJECTHANDLE with the handle as the m_addr field + // + // Notes: + // This will produce a VMPTR_OBJECTHANDLE regardless of whether handle is + // valid. + // Ideally we'd be using only strongly-typed variables on the RS, and then this would be unnecessary + virtual + VMPTR_OBJECTHANDLE GetVmObjectHandle(CORDB_ADDRESS handleAddress) = 0; + + // Validate that the VMPTR_OBJECTHANDLE refers to a legitimate managed object + // + // Arguments: + // handle: the GC handle to be validated + // + // Return value: + // TRUE if the object appears to be valid (its a heuristic), FALSE if it definately is not valid + // + virtual + BOOL IsVmObjectHandleValid(VMPTR_OBJECTHANDLE vmHandle) = 0; + + // indicates if the specified module is a WinRT module + // + // Arguments: + // vmModule: the module to check + // isWinRT: out parameter indicating state of module + // + // Return value: + // S_OK indicating that the operation succeeded + // + virtual + HRESULT IsWinRTModule(VMPTR_Module vmModule, BOOL& isWinRT) = 0; + + // Determines the app domain id for the object refered to by a given VMPTR_OBJECTHANDLE + // + // Arguments: + // handle: the GC handle which refers to the object of interest + // + // Return value: + // The app domain id of the object of interest + // + // This may throw if the object handle is corrupt (it doesn't refer to a managed object) + virtual + ULONG GetAppDomainIdFromVmObjectHandle(VMPTR_OBJECTHANDLE vmHandle) = 0; + + + // Get the target address from a VMPTR_OBJECTHANDLE, i.e., the handle address + // Arguments: + // vmHandle - (input) the VMPTR_OBJECTHANDLE from which we need the target address + // Return value: the target address from the VMPTR_OBJECTHANDLE + // + virtual + CORDB_ADDRESS GetHandleAddressFromVmHandle(VMPTR_OBJECTHANDLE vmHandle) = 0; + + // Given a VMPTR to an Object return the target address + // + // Arguments: + // obj - the Object VMPTR to get the address from + // + // Return Value: + // Return the target address which obj is using + // + // Notes: + // The VMPTR this consumes can be reconstructed using GetObject and + // providing the address stored in the returned TargetBuffer. This has + // undefined behavior for invalid VMPTR_Objects. + + virtual + TargetBuffer GetObjectContents(VMPTR_Object obj) = 0; + + // The callback used to enumerate blocking objects + typedef void (*FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK)(DacBlockingObject blockingObject, + CALLBACK_DATA pUserData); + + // + // Enumerate all monitors blocking a thread + // + // Arguments: + // vmThread - the thread to get monitor data for + // fpCallback - callback to invoke on the blocking data for each monitor + // pUserData - user data to supply for each callback. + // + // Return Value: + // Returns on success. Throws on error. + // + // + virtual + void EnumerateBlockingObjects(VMPTR_Thread vmThread, + FP_BLOCKINGOBJECT_ENUMERATION_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; + + + + // + // Returns the thread which owns the monitor lock on an object and the acquisition + // count + // + // Arguments: + // vmObject - The object to check for ownership + + // + // Return Value: + // Throws on error. Inside the structure we have: + // pVmThread - the owning or thread or VMPTR_Thread::NullPtr() if unowned + // pAcquisitionCount - the number of times the lock would need to be released in + // order for it to be unowned + // + virtual + MonitorLockInfo GetThreadOwningMonitorLock(VMPTR_Object vmObject) = 0; + + // + // Enumerate all threads waiting on the monitor event for an object + // + // Arguments: + // vmObject - the object whose monitor event we are interested in + // fpCallback - callback to invoke on each thread in the queue + // pUserData - user data to supply for each callback. + // + // Return Value: + // Returns on success. Throws on error. + // + // + virtual + void EnumerateMonitorEventWaitList(VMPTR_Object vmObject, + FP_THREAD_ENUMERATION_CALLBACK fpCallback, + CALLBACK_DATA pUserData) = 0; + + // + // Returns the managed debugging flags for the process (a combination + // of the CLR_DEBUGGING_PROCESS_FLAGS flags). This function specifies, + // beyond whether or not a managed debug event is pending, also if the + // event (if one exists) is caused by a Debugger.Launch(). This is + // important b/c Debugger.Launch calls should *NOT* cause the debugger + // to terminate the process when the attach is canceled. + virtual + CLR_DEBUGGING_PROCESS_FLAGS GetAttachStateFlags() = 0; + + virtual + bool GetMetaDataFileInfoFromPEFile(VMPTR_PEFile vmPEFile, + DWORD & dwTimeStamp, + DWORD & dwImageSize, + bool & isNGEN, + IStringHolder* pStrFilename) = 0; + + virtual + bool GetILImageInfoFromNgenPEFile(VMPTR_PEFile vmPEFile, + DWORD & dwTimeStamp, + DWORD & dwSize, + IStringHolder* pStrFilename) = 0; + + + virtual + bool IsThreadSuspendedOrHijacked(VMPTR_Thread vmThread) = 0; + + + typedef void* * HeapWalkHandle; + + // Returns true if it is safe to walk the heap. If this function returns false, + // you could still create a heap walk and attempt to walk it, but there's no + // telling how much of the heap will be available. + virtual + bool AreGCStructuresValid() = 0; + + // Creates a HeapWalkHandle which can be used to walk the managed heap with the + // WalkHeap function. Note if this function completes successfully you will need + // to delete the handle by passing it into DeleteHeapWalk. + // + // Arguments: + // pHandle - the location to store the heap walk handle in + // + // Returns: + // S_OK on success, an error code on failure. + virtual + HRESULT CreateHeapWalk(OUT HeapWalkHandle * pHandle) = 0; + + + // Deletes the give HeapWalkHandle. Note you must call this function if + // CreateHeapWalk returns success. + virtual + void DeleteHeapWalk(HeapWalkHandle handle) = 0; + + // Walks the heap using the given heap walk handle, enumerating objects + // on the managed heap. Note that walking the heap requires that the GC + // data structures be in a valid state, which you can find by calling + // AreGCStructuresValid. + // + // Arguments: + // handle - a HeapWalkHandle obtained from CreateHeapWalk + // count - the number of object addresses to obtain; pValues must + // be at least as large as count + // objects - the location to stuff the object addresses found during + // the heap walk; this array should be at least "count" in + // length; this field must not be null + // pFetched - a location to store the actual number of values filled + // into pValues; this field must not be null + // + // Returns: + // S_OK on success, a failure HRESULT otherwise. + // + // Note: + // You should iteratively call WalkHeap requesting more values until + // *pFetched != count.. This signifies that we have reached the end + // of the heap walk. + virtual + HRESULT WalkHeap(HeapWalkHandle handle, + ULONG count, + OUT COR_HEAPOBJECT * objects, + OUT ULONG * pFetched) = 0; + + virtual + HRESULT GetHeapSegments(OUT DacDbiArrayList<COR_SEGMENT> * pSegments) = 0; + + virtual + bool IsValidObject(CORDB_ADDRESS obj) = 0; + + virtual + bool GetAppDomainForObject(CORDB_ADDRESS obj, OUT VMPTR_AppDomain * pApp, + OUT VMPTR_Module * pModule, + OUT VMPTR_DomainFile * pDomainFile) = 0; + + + // Reference Walking. + + // Creates a reference walk. + // Parameters: + // pHandle - out - the reference walk handle to create + // walkStacks - in - whether or not to report stack references + // walkFQ - in - whether or not to report references from the finalizer queue + // handleWalkMask - in - the types of handles report (see CorGCReferenceType, cordebug.idl) + // Returns: + // An HRESULT indicating whether it succeded or failed. + // Exceptions: + // Does not throw, but does not catch exceptions either. + virtual + HRESULT CreateRefWalk(OUT RefWalkHandle * pHandle, BOOL walkStacks, BOOL walkFQ, UINT32 handleWalkMask) = 0; + + // Deletes a reference walk. + // Parameters: + // handle - in - the handle of the reference walk to delete + // Excecptions: + // Does not throw, but does not catch exceptions either. + virtual + void DeleteRefWalk(RefWalkHandle handle) = 0; + + // Enumerates GC references in the process based on the parameters passed to CreateRefWalk. + // Parameters: + // handle - in - the RefWalkHandle to enumerate + // count - in - the capacity of "refs" + // refs - in/out - an array to write the references to + // pFetched - out - the number of references written + virtual + HRESULT WalkRefs(RefWalkHandle handle, ULONG count, OUT DacGcReference * refs, OUT ULONG * pFetched) = 0; + + virtual + HRESULT GetTypeID(CORDB_ADDRESS obj, COR_TYPEID * pType) = 0; + + virtual + HRESULT GetObjectFields(COR_TYPEID id, ULONG32 celt, OUT COR_FIELD * layout, OUT ULONG32 * pceltFetched) = 0; + + virtual + HRESULT GetTypeLayout(COR_TYPEID id, COR_TYPE_LAYOUT * pLayout) = 0; + + virtual + HRESULT GetArrayLayout(COR_TYPEID id, COR_ARRAY_LAYOUT * pLayout) = 0; + + virtual + void GetGCHeapInformation(OUT COR_HEAPINFO * pHeapInfo) = 0; + + // If a PEFile has an RW capable IMDInternalImport, this returns the address of the MDInternalRW + // object which implements it. + // + // + // Arguments: + // vmPEFile - target PEFile to get metadata MDInternalRW for. + // pAddrMDInternalRW - If a PEFile has an RW capable IMDInternalImport, this will be set to the address + // of the MDInternalRW object which implements it. Otherwise it will be NULL. + // + virtual + HRESULT GetPEFileMDInternalRW(VMPTR_PEFile vmPEFile, OUT TADDR* pAddrMDInternalRW) = 0; + + // Retrieves the active ReJitInfo for a given module/methodDef, if it exists. + // Active is defined as after GetReJitParameters returns from the profiler dll and + // no call to Revert has completed yet. + // + // + // Arguments: + // vmModule - The module to search in + // methodTk - The methodDef token indicates the method within the module to check + // pReJitInfo - [out] The RejitInfo request, if any, that is active on this method. If no request + // is active this will be pReJitInfo->IsNull() == TRUE. + // + // Returns: + // S_OK regardless of whether a rejit request is active or not, as long as the answer is certain + // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible + // + virtual + HRESULT GetReJitInfo(VMPTR_Module vmModule, mdMethodDef methodTk, OUT VMPTR_ReJitInfo* pReJitInfo) = 0; + + // Retrieves the active ReJitInfo for a given MethodDesc/code address, if it exists. + // Active is defined as after GetReJitParameters returns from the profiler dll and + // no call to Revert has completed yet. + // + // + // Arguments: + // vmMethod - The method to look for + // codeStartAddress - The code start address disambiguates between multiple rejitted instances + // of the method. + // pReJitInfo - [out] The RejitInfo request, if any, that is active on this method. If no request + // is active this will be pReJitInfo->IsNull() == TRUE. + // + // Returns: + // S_OK regardless of whether a rejit request is active or not, as long as the answer is certain + // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible + // + virtual + HRESULT GetReJitInfo(VMPTR_MethodDesc vmMethod, CORDB_ADDRESS codeStartAddress, OUT VMPTR_ReJitInfo* pReJitInfo) = 0; + + + // Retrieves the SharedReJitInfo for a given ReJitInfo. + // + // + // Arguments: + // vmReJitInfo - The ReJitInfo to inspect + // pSharedReJitInfo - [out] The SharedReJitInfo that is pointed to by vmReJitInfo. + // + // Returns: + // S_OK if no error + // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible + // + virtual + HRESULT GetSharedReJitInfo(VMPTR_ReJitInfo vmReJitInfo, VMPTR_SharedReJitInfo* pSharedReJitInfo) = 0; + + // Retrieves useful data from a SharedReJitInfo such as IL code and IL mapping. + // + // + // Arguments: + // sharedReJitInfo - The SharedReJitInfo to inspect + // pData - [out] Various properties of the SharedReJitInfo such as IL code and IL mapping. + // + // Returns: + // S_OK if no error + // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible + // + virtual + HRESULT GetSharedReJitInfoData(VMPTR_SharedReJitInfo sharedReJitInfo, DacSharedReJitInfo* pData) = 0; + + // Retrieves a bit field indicating which defines were in use when clr was built. This only includes + // defines that are specified in the Debugger::_Target_Defines enumeration, which is a small subset of + // all defines. + // + // + // Arguments: + // pDefines - [out] The set of defines clr.dll was built with. Bit offsets are encoded using the + // enumeration Debugger::_Target_Defines + // + // Returns: + // S_OK if no error + // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible + // + virtual + HRESULT GetDefinesBitField(ULONG32 *pDefines) = 0; + + // Retrieves a version number indicating the shape of the data structures used in the Metadata implementation + // inside clr.dll. This number changes anytime a datatype layout changes so that they can be correctly + // deserialized from out of process + // + // + // Arguments: + // pMDStructuresVersion - [out] The layout version number for metadata data structures. See + // Debugger::Debugger() in Debug\ee\Debugger.cpp for a description of the options. + // + // Returns: + // S_OK if no error + // error HRESULTs such as CORDBG_READ_VIRTUAL_FAILURE are possible + // + virtual + HRESULT GetMDStructuresVersion(ULONG32* pMDStructuresVersion) = 0; + + // The following tag tells the DD-marshalling tool to stop scanning. + // END_MARSHAL + + //----------------------------------------------------------------------------- + // Utility interface used for passing strings out of these APIs. The caller + // provides an implementation of this that uses whatever memory allocation + // strategy it desires, and IDacDbiInterface APIs will call AssignCopy in order + // to pass back the contents of strings. + // + // This permits the client and implementation of IDacDbiInterface to be in + // different DLLs with their own heap allocation mechanism, while avoiding + // the ugly and verbose 2-call C-style string passing API pattern. + //----------------------------------------------------------------------------- + class IStringHolder + { + public: + // + // Store a copy of of the provided string. + // + // Arguments: + // psz - The null-terminated unicode string to copy. + // + // Return Value: + // S_OK on success, typical HRESULT return values on failure. + // + // Notes: + // The underlying object is responsible for allocating and freeing the + // memory for this copy. The object must not store the value of psz, + // it is no longer valid after this call returns. + // + virtual + HRESULT AssignCopy(const WCHAR * psz) = 0; + }; + + + //----------------------------------------------------------------------------- + // Interface for allocations + // This lets DD allocate buffers to pass back to DBI; and thus avoids + // the common 2-step (query size/allocate/query data) pattern. + // + // Note that mscordacwks.dll and clients cannot share the same heap allocator, + // DAC statically links the CRT to avoid run-time dependencies on non-OS libraries. + //----------------------------------------------------------------------------- + class IAllocator + { + public: + // Allocate + // Expected to throw on error. + virtual + void * Alloc(SIZE_T lenBytes) = 0; + + // Free. This shouldn't throw. + virtual + void Free(void * p) = 0; + }; + + + //----------------------------------------------------------------------------- + // Callback interface to provide Metadata lookup. + //----------------------------------------------------------------------------- + class IMetaDataLookup + { + public: + // + // Lookup a metadata importer via PEFile. + // + // Returns: + // A IMDInternalImport used by dac-ized VM code. The object is NOT addref-ed. See lifespan notes below. + // Returns NULL if no importer is available. + // Throws on exceptional circumstances (eg, detects the debuggee is corrupted). + // + // Notes: + // IMDInternalImport is a property of PEFile. The DAC-ized code uses it as a weak reference, + // and so we avoid doing an AddRef() here because that would mean we need to add Release() calls + // in DAC-only paths. + // The metadata importers are not DAC-ized, and thus we have a local copy in the host. + // If it was dac-ized, then DAC would get the importer just like any other field. + // + // lifespan of returned object: + // - DBI owns the metadata importers. + // - DBI must not free the importer without calling Flush() on DAC first. + // - DAC will only invoke this when in a DD primitive, which was in turn invoked by DBI. + // - For performance reasons, we want to allow DAC to cache this between Flush() calls. + // - If DAC caches the importer, it will only use it when DBI invokes a DD primitive. + // - the reference count of the returned object is not adjusted. + // + virtual + IMDInternalImport * LookupMetaData(VMPTR_PEFile addressPEFile, bool &isILMetaDataForNGENImage) = 0; + }; + +}; // end IDacDbiInterface + + +#endif // _DACDBI_INTERFACE_H_ diff --git a/src/debug/inc/dacdbistructures.h b/src/debug/inc/dacdbistructures.h new file mode 100644 index 0000000000..d6f2c0b943 --- /dev/null +++ b/src/debug/inc/dacdbistructures.h @@ -0,0 +1,790 @@ +// 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. +//***************************************************************************** +// File: DacDbiStructures.h +// + +// +// Declarations and inline functions for data structures shared between by the +// DAC/DBI interface functions and the right side. +// +// Note that for MAC these structures are marshalled between Windows and Mac +// and so their layout and size must be identical in both builds. Use the +// MSLAYOUT macro on every structure to avoid compiler packing differences. +// +//***************************************************************************** + +#ifndef DACDBISTRUCTURES_H_ +#define DACDBISTRUCTURES_H_ + +#include "./common.h" + +//------------------------------------------------------------------------------- +// classes shared by the DAC/DBI interface functions and the right side +//------------------------------------------------------------------------------- + +// DacDbiArrayList encapsulates an array and the number of elements in the array. +// Notes: +// - storage is always on the DacDbi heap +// - this class owns the memory. Its dtor will free. +// - Operations that initialize list elements use the assignment +// operator defined for type T. If T is a pointer type or has pointer +// type components and no assignment operator override, this will make a shallow copy of +// the element. If T has an assignment operator override that makes a deep copy of pointer +// types, T must also have a destructor that will deallocate any memory allocated. +// - this is NOT thread safe!!! +// - the array elements are always mutable, but the number of elements is fixed between allocations +// - you can gain access to the array using &(list[0]) but this is NOT safe if the array is empty. You +// can call IsEmpty to determine if it is safe to access the array portion +// This list is not designed to have unused elements at the end of the array (extra space) nor to be growable + +// usage examples: +// typedef DacDbiArrayList<Bar> BarList; // handy typedef +// void GetAListOfBars(BarList * pBarList) +// { +// DacDbiArrayList<Foo> fooList; // fooList is an empty array of objects of type Foo +// int elementCount = GetNumberOfFoos(); +// Bar * pBars = new Bar[elementCount]; +// +// fooList.Alloc(elementCount); // get space for the list of Foo instances +// for (int i = 0; i < fooList.Count(); ++i) +// { +// fooList[i] = GetNextFoo(); // copy elements into the list +// } +// ConvertFoosToBars(pBars, &fooList); // always pass by reference +// pBarList->Init(pBars, fooList.Count()); // initialize a list +// } +// +// void ConvertFoosToBars(Bar * pBars, DacDbiArrayList<Foo> * pFooList) +// { +// for (int i = 0; i < pFooList->Count(); ++i) +// { +// if ((*pFooList)[i].IsBaz()) +// pBars [i] = ConvertBazToBar(&(*pFooList)[i]); +// else pBars [i] = (*pFooList)[i].barPart; +// } +// } +// +template<class T> +class MSLAYOUT DacDbiArrayList +{ +public: + // construct an empty list + DacDbiArrayList(); + + // deep copy constructor + DacDbiArrayList(const T * list, int count); + + // destructor--sets list to empty state + ~DacDbiArrayList(); + + // explicitly deallocate the list and set it back to the empty state + void Dealloc(); + + // allocate a list with space for nElements items + void Alloc(int nElements); + + // allocate and initialize a DacDbiArrayList from an array of type T and a count + void Init(const T * list, int count); + + // predicate to indicate if the list is empty + bool IsEmpty() { return m_nEntries == 0; } + + // read-only element accessor + const T & operator [](int index) const; + + // writeable element accessor + T & operator [](int index); + + + // returns the number of elements in the list + int Count() const; + + // @dbgtodo Mac - cleaner way to expose this for serialization? + void PrepareForDeserialize() + { + m_pList = NULL; + } +private: + // because these are private (and unimplemented), calls will generate a compiler (or linker) error. + // This prevents accidentally invoking the default (shallow) copy ctor or assignment operator. + // This prevents having multiple instances point to the same list memory (eg. due to passing by value), + // which would result in memory corruption when the first copy is destroyed and the list memory is deallocated. + DacDbiArrayList(const DacDbiArrayList<T> & sourceList); + T & operator = (const DacDbiArrayList<T> & rhs); + +// data members +protected: + T * m_pList; // the list + + // - the count is managed by the member functions and is not settable, so (m_pList == NULL) == (m_nEntries == 0) + // is always true. + int m_nEntries; // the number of items in the list + +}; + + +// Describes a buffer in the target +struct MSLAYOUT TargetBuffer +{ + TargetBuffer(); + TargetBuffer(CORDB_ADDRESS pBuffer, ULONG cbSizeInput); + + // @dbgtodo : This ctor form confuses target and host address spaces. This should probably be PTR_VOID instead of void* + TargetBuffer(void * pBuffer, ULONG cbSizeInput); + + // + // Helper methods + // + + // Return a sub-buffer that's starts at byteOffset within this buffer and runs to the end. + TargetBuffer SubBuffer(ULONG byteOffset) const; + + // Return a sub-buffer that starts at byteOffset within this buffer and is byteLength long. + TargetBuffer SubBuffer(ULONG byteOffset, ULONG byteLength) const; + + // Returns true if the buffer length is 0. + bool IsEmpty() const; + + // Sets address to NULL and size to 0 + // IsEmpty() will be true after this. + void Clear(); + + // Initialize fields + void Init(CORDB_ADDRESS address, ULONG size); + + // Target address of buffer + CORDB_ADDRESS pAddress; + + // Size of buffer in bytes + ULONG cbSize; +}; + +//=================================================================================== +// Module properties, retrieved by DAC. +// Describes a VMPTR_DomainFile representing a module. +// In the VM, a raw Module may be domain neutral and shared by many appdomains. +// Whereas a DomainFile is like a { AppDomain, Module} pair. DomainFile corresponds +// much more to ICorDebugModule (which also has appdomain affinity). +//=================================================================================== +struct MSLAYOUT DomainFileInfo +{ + // The appdomain that the DomainFile is associated with. + // Although VMPTR_Module may be shared across multiple domains, a DomainFile has appdomain affinity. + VMPTR_AppDomain vmAppDomain; + + // The assembly this module belongs to. All modules live in an assembly. + VMPTR_DomainAssembly vmDomainAssembly; +}; + +struct MSLAYOUT ModuleInfo +{ + // The non-domain specific assembly which this module resides in. + VMPTR_Assembly vmAssembly; + + // The PE Base address and size of the module. These may be 0 if there is no image + // (such as for a dynamic module that's not persisted to disk). + CORDB_ADDRESS pPEBaseAddress; + + // The PEFile associated with the module. Every module (even non-file-based ones) has a PEFile. + // This is critical because DAC may ask for a metadata importer via PE-file. + // a PEFile may have 1 or more PEImage child objects (1 for IL, 1 for native image, etc) + VMPTR_PEFile vmPEFile; + + // The PE Base address and size of the module. These may be 0 if there is no image + // (such as for a dynamic module that's not persisted to disk). + ULONG nPESize; + + // Is this a dynamic (reflection.emit) module? + // This means that new classes can be added to the module; and so + // the module's metadata and symbols can be updated. Debugger will have to do extra work + // to keep up with the updates. + // Dynamic modules may be transient (entirely in-memory) or persisted to disk (have a file associated with them). + BOOL fIsDynamic; + + // Is this an inmemory module? + // Assemblies can be instantiated purely in-memory from just a Byte[]. + // This means the module (and pdb) are not in files, and thus the debugger + // needs to do extra work to retrieve them from the Target's memory. + BOOL fInMemory; +}; + +// the following two classes track native offsets for local variables and sequence +// points. This information is initialized on demand. + + +//=================================================================================== +// NativeVarData holds a list of structs that provide the following information for +// each local variable and fixed argument in a function: the offsets between which the +// variable or argument lives in a particular location, the location itself, and the +// variable number (ID). This allows us to determine where a value is at any given IP. + +// Lifetime management of the list is the responsibility of the NativeVarData class. +// Callers that allocate memory for a new list should NOT maintain a separate pointer +// to the list. + +// The arguments we track are the "fixed" arguments, specifically, the explicit arguments +// that appear in the source code and the "this" pointer for non-static methods. +// Varargs and other implicit arguments, such as the generic handle are counted in +// CordbJITILFrame::m_allArgsCount. + +// Although logically, we really don't differentiate between arguments and locals when +// all we want to know is where to find a value, we need to have two +// separate counts. The full explanation is in the comment in rsthread.cpp in +// CordbJITILFrame::ILVariableToNative, but the short version is that it allows us to +// compute the correct ID for a value. + +// m_fixedArgsCount, accessed through GetFixedArgCount, is the actual number of fixed +// arguments. +// m_allArgsCount, accessed through GetAllArgsCount, is the number of fixed args plus the +// number of varargs. + +// The number of entries in m_offsetInfo, accessed through Count(), is NOT the +// number of locals, nor the number of locals plus the number of arguments. It is the +// number of entries in the list. Any particular value may have an arbitrary number of +// entries, depending on how many different places it is stored during the execution of +// the method. The list is not sorted, so searches for data within it must be linear. +//=================================================================================== +class MSLAYOUT NativeVarData +{ +public: + // constructor + NativeVarData(); + // destructor + ~NativeVarData(); + + + // initialize the list of native var information structures, including the starting address of the list + // (m_pOffsetInfo, the number of entries (m_count) and the number of fixed args (m_fixedArgsCount). + // NativeVarData will manage the lifetime of the allocated memory for the list, so the caller should not + // hold on to its address. + void InitVarDataList(ICorDebugInfo::NativeVarInfo * plistStart, int fixedArgCount, int entryCount); + +private: + // non-existent copy constructor to disable the (shallow) compiler-generated + // one. If you attempt to use this, you will get a compiler or linker error. + NativeVarData(const NativeVarData & rhs) {}; + + // non-existent assignment operator to disable the (shallow) compiler-generated + // one. If you attempt to use this, you will get a compiler or linker error. + NativeVarData & operator=(const NativeVarData & rhs); + +//---------------------------------------------------------------------------------- +// Accessor Functions +//---------------------------------------------------------------------------------- +public: + + // get the list of native offset info + const DacDbiArrayList<ICorDebugInfo::NativeVarInfo> * GetOffsetInfoList() const + { + _ASSERTE(m_fInitialized); + return &m_offsetInfo; + } + + // get the number of explicit arguments for this function--this + // includes the fixed arguments for vararg methods, but not the variable ones + ULONG32 GetFixedArgCount() + { + _ASSERTE(IsInitialized()); + // this count includes explicit arguments plus one for the "this" pointer + // but doesn't count varargs + return m_fixedArgsCount; + } + + // get the number of all arguments, including varargs + ULONG32 GetAllArgsCount() + { + _ASSERTE(IsInitialized()); + return m_allArgsCount; + } + + // set the number of all arguments, including varargs + void SetAllArgsCount(ULONG32 count) + { + m_allArgsCount = count; + } + + // determine whether we have successfully initialized this + BOOL IsInitialized() + { + return m_fInitialized == true; + } + + +//---------------------------------------------------------------------------------- +// Data Members +//---------------------------------------------------------------------------------- + +// @dbgtodo Mac - making this public for serializing for remote DAC on mac. Need to make this private again. +public: + // contains a list of structs providing information about the location of a local + // variable or argument between a pair of offsets and the number of entries in the list + DacDbiArrayList<ICorDebugInfo::NativeVarInfo> m_offsetInfo; + + // number of fixed arguments to the function i.e., the explicit arguments and "this" pointer + ULONG32 m_fixedArgsCount; + + // number of fixed arguments plus number of varargs + ULONG32 m_allArgsCount; + + // indicates whether an attempt has been made toinitialize the var data already + bool m_fInitialized; +}; // class NativeVarData + +//=================================================================================== +// SequencePoints holds a list of sequence points that map IL offsets to native offsets. In addition, +// it keeps track of the number of entries in the list and whether the list is sorted. +//=================================================================================== +class MSLAYOUT SequencePoints +{ +public: + SequencePoints(); + + ~SequencePoints(); + + // Initialize the m_pMap data member to the address of an allocated chunk + // of memory (or to NULL if the count is zero). Set m_count as the + // number of entries in the map. + void InitSequencePoints(ULONG32 count); + +private: + // non-existent copy constructor to disable the (shallow) compiler-generated + // one. If you attempt to use this, you will get a compiler or linker error. + SequencePoints(const SequencePoints & rhs) {}; + + // non-existent assignment operator to disable the (shallow) compiler-generated + // one. If you attempt to use this, you will get a compiler or linker error. + SequencePoints & operator=(const SequencePoints & rhs); + + //---------------------------------------------------------------------------------- + // class MapSortILMap: A template class that will sort an array of DebuggerILToNativeMap. + // This class is intended to be instantiated on the stack / in temporary storage, and used + // to reorder the sequence map. + //---------------------------------------------------------------------------------- + class MapSortILMap : public CQuickSort<DebuggerILToNativeMap> + { + public: + //Constructor + MapSortILMap(DebuggerILToNativeMap * map, + int count) + : CQuickSort<DebuggerILToNativeMap>(map, count) {} + + // secondary key comparison--if two IL offsets are the same, + // we determine order based on native offset + int CompareInternal(DebuggerILToNativeMap * first, + DebuggerILToNativeMap * second); + + //Comparison operator + int Compare(DebuggerILToNativeMap * first, + DebuggerILToNativeMap * second); + }; + +//---------------------------------------------------------------------------------- +// Accessor Functions +//---------------------------------------------------------------------------------- +public: + // @dbgtodo Microsoft inspection: It would be very nice not to need this at all. Ideally, + // it would be better to make ExportILToNativeMap expect a DacDbiArrayList instead of the + // array and size. At present, there's a call to ExportILToNativeMap in debugger.cpp where + // DacDbiArrayLists aren't available, so at present, we need to pass the array and size. + // We should be able to eliminate the debugger.cpp call when we get rid of in-proc + // inspection. At that point, we can delete this function too, as well as GetEntryCount. + // In the meantime, it would be great if no one else took a dependency on this. + + // get value of m_pMap + DebuggerILToNativeMap * GetMapAddr() + { + // Please don't call this function + _ASSERTE(m_fInitialized); + return &(m_map[0]); + } + + // get value of m_count + ULONG32 GetEntryCount() + { + _ASSERTE(m_fInitialized); + return m_mapCount; + } + + ULONG32 GetCallsiteEntryCount() + { + _ASSERTE(m_fInitialized); + return m_map.Count() - m_mapCount; //m_map.Count(); + } + + DebuggerILToNativeMap * GetCallsiteMapAddr() + { + // Please don't call this function + _ASSERTE(m_fInitialized); + + if (m_map.Count() == m_mapCount) + return NULL; + + return &(m_map[m_mapCount]); + } + + + + // determine whether we have initialized this + BOOL IsInitialized() + { + return m_fInitialized == true; + } + + // Copy data from the VM map data to our own map structure and sort. The + // information comes to us in a data structure that differs slightly from the + // one we use out of process, so we have to copy it to the right-side struct. + void CopyAndSortSequencePoints(const ICorDebugInfo::OffsetMapping mapCopy[]); + + + // Set the IL offset of the last sequence point before the epilog. + // If a native offset maps to the epilog, we will return the this IL offset. + void SetLastILOffset(ULONG32 lastILOffset) + { + _ASSERTE(m_fInitialized); + m_lastILOffset = lastILOffset; + } + + // Map the given native offset to IL offset. Also return the mapping type. + DWORD MapNativeOffsetToIL(DWORD dwNativeOffset, + CorDebugMappingResult *pMapType); + +//---------------------------------------------------------------------------------- +// Data Members +//---------------------------------------------------------------------------------- + + // @dbgtodo Mac - making this public for serializing for remote DAC on mac. Need to make this private again. +public: + + // map of IL to native offsets for sequence points + DacDbiArrayList<DebuggerILToNativeMap> m_map; + + // + ULONG32 m_mapCount; + + // the IL offset of the last sequence point before the epilog + ULONG32 m_lastILOffset; + // indicates whether an attempt has been made to initialize the sequence points already + bool m_fInitialized; +}; // class SequencePoints + +//---------------------------------------------------------------------------------- +// declarations needed for getting native code regions +//---------------------------------------------------------------------------------- + +// Code may be split into Hot & Cold regions, so we need an extra address & size. +// The jitter doesn't do this optimization w/ debuggable code, so we'll +// rarely see the cold region information as non-null values. + +// This enumeration provides symbolic indices into m_rgCodeRegions. +typedef enum {kHot = 0, kCold, MAX_REGIONS} CodeBlobRegion; + +// This contains the information we need to initialize a CordbNativeCode object +class MSLAYOUT NativeCodeFunctionData +{ +public: + // set all fields to default values (NULL, FALSE, or zero as appropriate) + NativeCodeFunctionData(); + + // conversion constructor to convert from an instance of DebuggerIPCE_JITFUncData to an instance of + // NativeCodeFunctionData. + NativeCodeFunctionData(DebuggerIPCE_JITFuncData * source); + + // The hot region start address could be NULL in the following circumstances: + // 1. We haven't yet tried to get the information + // 2. We tried to get the information, but the function hasn't been jitted yet + // 3. We tried to get the information, but the MethodDesc wasn't available yet (very early in + // module initialization), which implies that the code isn't available either. + // 4. We tried to get the information, but a method edit has reset the MethodDesc, but the + // method hasn't been jitted yet. + // In all cases, we can check the hot region start address to determine whether the rest of the + // the information is valid. + BOOL IsValid() { return (m_rgCodeRegions[kHot].pAddress != NULL); } + void Clear(); + + // data members + // start addresses and sizes of hot & cold regions + TargetBuffer m_rgCodeRegions[MAX_REGIONS]; + + // indicates whether the function is a generic function, or a method inside a generic class (or both). + BOOL isInstantiatedGeneric; + + // MethodDesc for the function + VMPTR_MethodDesc vmNativeCodeMethodDescToken; + + // EnC version number of the function + SIZE_T encVersion; +}; + +//---------------------------------------------------------------------------------- +// declarations needed for getting type information +//---------------------------------------------------------------------------------- + +// FieldData holds data for each field within a class or type. This data +// is passed from the DAC to the DI in response to a request for class info. +// This type is also used by CordbClass and CordbType to hold the list of fields for the +// class. +class MSLAYOUT FieldData +{ +public: +#ifndef RIGHT_SIDE_COMPILE + // initialize various fields of an instance of FieldData from information in a FieldDesc + void Initialize(BOOL fIsStatic, BOOL fIsPrimitive, mdFieldDef mdToken); +#else + HRESULT GetFieldSignature(class CordbModule * pModule, /*OUT*/ SigParser * pSigParser); +#endif + + // clear various fields for a new instance of FieldData + void ClearFields(); + + // Make sure it's okay to get or set an instance field offset. + BOOL OkToGetOrSetInstanceOffset(); + + // Make sure it's okay to get or set a static field address. + BOOL OkToGetOrSetStaticAddress(); + + // If this is an instance field, store its offset + void SetInstanceOffset( SIZE_T offset ); + + // If this is a "normal" static, store its absolute address + void SetStaticAddress( TADDR addr ); + + // If this is an instance field, return its offset + // Note that this offset is allways a real offset (possibly larger than 22 bits), which isn't + // necessarily the same as the overloaded FieldDesc.dwOffset field which can have + // some special FIELD_OFFSET tokens. + SIZE_T GetInstanceOffset(); + + // If this is a "normal" static, get its absolute address + // TLS and context-specific statics are "special". + TADDR GetStaticAddress(); + +// +// Data members +// + mdFieldDef m_fldMetadataToken; + // m_fFldStorageAvailable is true whenever the storage for this field is available. + // If this is a field that is newly added with EnC and hasn't had any storage + // allocated yet, then fldEnCAvailable will be false. + BOOL m_fFldStorageAvailable; + + // Bits that specify what type of field this is + bool m_fFldIsStatic; // true if static field, false if instance field + bool m_fFldIsRVA; // true if static relative to module address + bool m_fFldIsTLS; // true if thread-specific static + bool m_fFldIsContextStatic; // true if context-specific static + bool m_fFldIsPrimitive; // Only true if this is a value type masquerading as a primitive. + bool m_fFldIsCollectibleStatic; // true if this is a static field on a collectible type + +private: + // The m_fldInstanceOffset and m_pFldStaticAddress are mutually exclusive. Only one is ever set at a time. + SIZE_T m_fldInstanceOffset; // The offset of a field within an object instance + // For EnC fields, this isn't actually within the object instance, + // but has been cooked to still be relative to the beginning of + // the object. + TADDR m_pFldStaticAddress; // The absolute target address of a static field + + PCCOR_SIGNATURE m_fldSignatureCache; // This is passed across as null. It is a RS-only cache, and SHOULD + // NEVER BE ACCESSED DIRECTLY! + ULONG m_fldSignatureCacheSize; // This is passed across as 0. It is a RS-only cache, and SHOULD + // NEVER BE ACCESSED DIRECTLY! +public: + VMPTR_FieldDesc m_vmFieldDesc; + +}; // class FieldData + + +// ClassInfo holds information about a type (class or other structured type), including a list of its fields +class MSLAYOUT ClassInfo +{ +public: + ClassInfo(); + + ~ClassInfo(); + + void Clear(); + + // Size of object in bytes, for non-generic types. Note: this is NOT valid for constructed value types, + // e.g. value type Pair<DateTime,int>. Use CordbType::m_objectSize instead. + SIZE_T m_objectSize; + + // list of structs containing information about all the fields in this Class, along with the number of entries + // in the list. Does not include inherited fields. DON'T KEEP POINTERS TO ELEMENTS OF m_fieldList AROUND!! + // This may be deleted if the class gets EnC'd. + DacDbiArrayList<FieldData> m_fieldList; +}; // class ClassInfo + +// EnCHangingFieldInfo holds information describing a field added with Edit And Continue. This data +// is passed from the DAC to the DI in response to a request for EnC field info. +class MSLAYOUT EnCHangingFieldInfo +{ +public: + // Init will initialize fields, taking into account whether the field is static or not. + void Init(VMPTR_Object pObject, + SIZE_T offset, + mdFieldDef fieldToken, + CorElementType elementType, + mdTypeDef metadataToken, + VMPTR_DomainFile vmDomainFile); + + DebuggerIPCE_BasicTypeData GetObjectTypeData() const { return m_objectTypeData; }; + mdFieldDef GetFieldToken() const { return m_fldToken; }; + VMPTR_Object GetVmObject() const { return m_vmObject; }; + SIZE_T GetOffsetToVars() const { return m_offsetToVars; }; + +private: + DebuggerIPCE_BasicTypeData m_objectTypeData; // type data for the EnC field + VMPTR_Object m_vmObject; // object instance to which the field has been added--if the field is + // static, this will be NULL instead of pointing to an instance + SIZE_T m_offsetToVars; // offset to the beginning of variable storage in the object + mdFieldDef m_fldToken; // metadata token for the added field + +}; // EnCHangingFieldInfo + +// TypeHandleToExpandedTypeInfo returns different DebuggerIPCE_ExpandedTypeData objects +// depending on whether the object value that the TypeData corresponds to is +// boxed or not. Different parts of the API transfer objects in slightly different ways. +// AllBoxed: +// For GetAndSendObjectData all values are boxed, +// +// OnlyPrimitivesUnboxed: +// When returning results from FuncEval only "true" structs +// get boxed, i.e. primitives are unboxed. +// +// NoValueTypeBoxing: +// TypeHandleToExpandedTypeInfo is also used to report type parameters, +// and in this case none of the types are considered boxed ( +enum AreValueTypesBoxed { NoValueTypeBoxing, OnlyPrimitivesUnboxed, AllBoxed }; + +// TypeRefData is used for resolving a type reference (see code:CordbModule::ResolveTypeRef and +// code:DacDbiInterfaceImpl::ResolveTypeReference) to store relevant information about the type +typedef struct MSLAYOUT +{ + // domain file for the type + VMPTR_DomainFile vmDomainFile; + // metadata token for the type. This may be a typeRef (for requests) or a typeDef (for responses). + mdToken typeToken; +} TypeRefData; + +// @dbgtodo Microsoft inspection: get rid of IPCE type. +// TypeInfoList encapsulates a list of type data instances and the length of the list. +typedef DacDbiArrayList<DebuggerIPCE_TypeArgData> TypeInfoList; + +// ArgInfoList encapsulates a list of type data instances for arguments for a top-level +// type and the length of the list. +typedef DacDbiArrayList<DebuggerIPCE_BasicTypeData> ArgInfoList; + +// TypeParamsList encapsulate a list of type parameters and the length of the list +typedef DacDbiArrayList<DebuggerIPCE_ExpandedTypeData> TypeParamsList; + +// A struct for passing version information from DBI to DAC. +// See code:CordbProcess::CordbProcess#DBIVersionChecking for more information. +const DWORD kCurrentDacDbiProtocolBreakingChangeCounter = 1; + +struct DbiVersion +{ + DWORD m_dwFormat; // the format of this DbiVersion instance + DWORD m_dwDbiVersionMS; // version of the DBI DLL, in the convention used by VS_FIXEDFILEINFO + DWORD m_dwDbiVersionLS; + DWORD m_dwProtocolBreakingChangeCounter; // initially this was reserved and always set to 0 + // Now we use it as a counter to explicitly introduce breaking changes + // between DBI and DAC when we have our IPC transport in the middle + // If DBI and DAC don't agree on the same value CheckDbiVersion will return CORDBG_E_INCOMPATIBLE_PROTOCOL + // Please document every time this value changes + // 0 - initial value + // 1 - Indicates that the protocol now supports the GetRemoteInterfaceHashAndTimestamp message + // The message must have ID 2, with signature: + // OUT DWORD & hash1, OUT DWORD & hash2, OUT DWORD & hash3, OUT DWORD & hash4, OUT DWORD & timestamp1, OUT DWORD & timestamp2 + // The hash can be used as an indicator of many other breaking changes providing + // easier automated enforcement during development. It is NOT recommended to use + // the hash as a release versioning mechanism however. + DWORD m_dwReservedMustBeZero1; // reserved for future use +}; + +// The way in which a thread is blocking on an object +enum DacBlockingReason +{ + DacBlockReason_MonitorCriticalSection, + DacBlockReason_MonitorEvent +}; + +// Information about an object which is blocking a managed thread +struct DacBlockingObject +{ + VMPTR_Object vmBlockingObject; + VMPTR_AppDomain vmAppDomain; + DWORD dwTimeout; + DacBlockingReason blockingReason; +}; + +// Opaque user defined data used in callbacks +typedef void* CALLBACK_DATA; + +struct MonitorLockInfo +{ + VMPTR_Thread lockOwner; + DWORD acquisitionCount; +}; + +struct MSLAYOUT DacGcReference +{ + VMPTR_AppDomain vmDomain; // The AppDomain of the handle/object, may be null. + union + { + CORDB_ADDRESS pObject; // A managed object, with the low bit set. + VMPTR_OBJECTHANDLE objHnd; // A reference to the object, valid if (pAddress & 1) == 0 + }; + DWORD dwType; // Where the root came from. + + /* + DependentSource - for HandleDependent + RefCount - for HandleStrongRefCount + Size - for HandleSizedByref + */ + UINT64 i64ExtraData; +}; // struct DacGcReference + +struct MSLAYOUT DacExceptionCallStackData +{ + VMPTR_AppDomain vmAppDomain; + VMPTR_DomainFile vmDomainFile; + CORDB_ADDRESS ip; + mdMethodDef methodDef; + BOOL isLastForeignExceptionFrame; +}; + +// These represent the various states a SharedReJitInfo can be in. +enum DacSharedReJitInfoState +{ + // The profiler has requested a ReJit, so we've allocated stuff, but we haven't + // called back to the profiler to get any info or indicate that the ReJit has + // started. (This Info can be 'reused' for a new ReJit if the + // profiler calls RequestReJit again before we transition to the next state.) + kStateRequested = 0x00000000, + + // We have asked the profiler about this method via ICorProfilerFunctionControl, + // and have thus stored the IL and codegen flags the profiler specified. Can only + // transition to kStateReverted from this state. + kStateActive = 0x00000001, + + // The methoddef has been reverted, but not freed yet. It (or its instantiations + // for generics) *MAY* still be active on the stack someplace or have outstanding + // memory references. + kStateReverted = 0x00000002, + + + kStateMask = 0x0000000F, +}; + +struct MSLAYOUT DacSharedReJitInfo +{ + DWORD m_state; + CORDB_ADDRESS m_pbIL; + DWORD m_dwCodegenFlags; + ULONG m_cInstrumentedMapEntries; + CORDB_ADDRESS m_rgInstrumentedMapEntries; +}; + +#include "dacdbistructures.inl" +#endif // DACDBISTRUCTURES_H_ diff --git a/src/debug/inc/dacdbistructures.inl b/src/debug/inc/dacdbistructures.inl new file mode 100644 index 0000000000..48749135f0 --- /dev/null +++ b/src/debug/inc/dacdbistructures.inl @@ -0,0 +1,732 @@ +// 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. +//***************************************************************************** + +// +// File: DacDbiInterface.inl +// +// Inline functions for DacDbiStructures.h +// +//***************************************************************************** + +#ifndef DACDBISTRUCTURES_INL_ +#define DACDBISTRUCTURES_INL_ + +//----------------------------------------------------------------------------------- +// DacDbiArrayList member function implementations +//----------------------------------------------------------------------------------- + +// constructor--sets list to empty state +// Arguments: none +// Notes: this allocates no memory, so the list will not be ready to use +template<class T> +inline +DacDbiArrayList<T>::DacDbiArrayList(): + m_pList(NULL), + m_nEntries(0) + { + } + +// conversion constructor--takes a list of type T and a count and converts to a +// DacDbiArrayList +// Arguments: +// input: list - a consecutive list (array) of elements of type T +// count - the number of elements in list +// Notes: - Allocates memory and copies the elements of list into "this" +// - It is assumed that the list does NOT already have memory allocated; if it does, +// calling Init will cause a leak. +// - the element copy relies on the assignment operator for T +// - may throw OOM +template<class T> +inline +DacDbiArrayList<T>::DacDbiArrayList(const T * pList, int count): + m_pList(NULL), + m_nEntries(0) +{ + Init(pList, count); +} + +// destructor: deallocates memory and sets list back to empty state +// Arguments: none +template<class T> +inline +DacDbiArrayList<T>::~DacDbiArrayList() +{ + Dealloc(); +} + +// explicitly deallocate the list and set it back to the empty state +// Arguments: none +// Notes: - Dealloc can be called multiple times without danger, since it +// checks first that memory has been allocated +template<class T> +inline +void DacDbiArrayList<T>::Dealloc() +{ + CONTRACT_VOID + { + NOTHROW; + } + CONTRACT_END; + + if (m_pList != NULL) + { + delete [] m_pList; + m_pList = NULL; + } + m_nEntries = 0; + RETURN; +} + +// Alloc and Init are very similar. Both preallocate the array; but Alloc leaves the +// contents unintialized while Init provides initial values. The array contents are always +// mutable. + +// allocate space for the list--in some instances, we'll know the count first, and then +// we'll compute the elements one at a time. This (along with the array access operator +// overload) allows us to handle that situation +// Arguments: +// input: nElements - number of elements of type T for which we need space +// Notes: +// - Alloc can be called multiple times and will free previous arrays. +// - May throw OOM +// - The array is not expandable, so you must allocate for all the elements at once. +// - requesting an allocation of 0 or fewer bytes will not cause an error, but no memory is +// allocated +template<class T> +inline +void DacDbiArrayList<T>::Alloc(int nElements) +{ + Dealloc(); + if (nElements > 0) + { + m_pList = new(forDbi) T[(size_t)nElements]; + m_nEntries = nElements; + } +} + +// allocate and initialize a DacDbiArrayList from a list of type T and a count +// Arguments: +// input: list - consecutive list (array) of elements of type T to be copied into +// "this" +// count - number of elements in list +// Notes: +// - May throw OOM +// - Can be called multiple times with different lists, since this will deallocate +// previous arrays. +template<class T> +inline +void DacDbiArrayList<T>::Init(const T * pList, int count) +{ + _ASSERTE((m_pList == NULL) && (m_nEntries == 0)); + if (count > 0) + { + Alloc(count); + m_nEntries = count; + for (int index = 0; index < count; ++index) + { + m_pList[index] = pList[index]; + } + } +} + +// read-only list element access +template<class T> +inline +const T & DacDbiArrayList<T>::operator [](int i) const +{ + _ASSERTE(m_pList != NULL); + _ASSERTE((i >= 0) && (i < m_nEntries)); + return m_pList[i]; +} + +// writeable list element access +template<class T> +inline +T & DacDbiArrayList<T>::operator [](int i) +{ + _ASSERTE(m_pList != NULL); + _ASSERTE((i >= 0) && (i < m_nEntries)); + return m_pList[i]; +} + +// get the number of elements in the list +template<class T> +inline +int DacDbiArrayList<T>::Count() const +{ + return m_nEntries; +} + +//----------------------------------------------------------------------------- +// Target Buffer functions +//----------------------------------------------------------------------------- + +// Default ctor +inline +TargetBuffer::TargetBuffer() +{ + this->pAddress = NULL; + this->cbSize = 0; +} + +// Convenience Ctor to initialize around an (Address, size). +inline +TargetBuffer::TargetBuffer(CORDB_ADDRESS pBuffer, ULONG cbSizeInput) +{ + this->pAddress = pBuffer; + this->cbSize = cbSizeInput; +} + +// Convenience Ctor to initialize around an (Address, size). +inline +TargetBuffer::TargetBuffer(void * pBuffer, ULONG cbSizeInput) +{ + this->pAddress = PTR_TO_CORDB_ADDRESS(pBuffer); + this->cbSize = cbSizeInput; +} + +// Return a sub-buffer that's starts at byteOffset within this buffer and runs to the end. +// +// Arguments: +// byteOffset - offset in bytes within this buffer that the new buffer starts at. +// +// Returns: +// A new buffer that's a subset of the existing buffer. +inline +TargetBuffer TargetBuffer::SubBuffer(ULONG byteOffset) const +{ + _ASSERTE(byteOffset <= cbSize); + return TargetBuffer(pAddress + byteOffset, cbSize - byteOffset); +} + +// Return a sub-buffer that starts at byteOffset within this buffer and is byteLength long. +// +// Arguments: +// byteOffset - offset in bytes within this buffer that the new buffer starts at. +// byteLength - length in bytes of the new buffer. +// +// Returns: +// A new buffer that's a subset of the existing buffer. +inline +TargetBuffer TargetBuffer::SubBuffer(ULONG byteOffset, ULONG byteLength) const +{ + _ASSERTE(byteOffset + byteLength <= cbSize); + return TargetBuffer(pAddress + byteOffset, byteLength); +} + +// Sets address to NULL and size to 0 +inline +void TargetBuffer::Clear() +{ + pAddress = NULL; + cbSize = 0; +} + +// Initialize fields +inline +void TargetBuffer::Init(CORDB_ADDRESS address, ULONG size) +{ + pAddress = address; + cbSize = size; +} + + +// Returns true iff the buffer is empty. +inline +bool TargetBuffer::IsEmpty() const +{ + return (this->cbSize == 0); +} + +//----------------------------------------------------------------------------- +// NativeVarData member function implementations +//----------------------------------------------------------------------------- + +// Initialize a new instance of NativeVarData +inline NativeVarData::NativeVarData() : + m_allArgsCount(0), + m_fInitialized(false) +{ +} + +// destructor +inline NativeVarData::~NativeVarData() +{ + m_fInitialized = false; + } + +// initialize the list of native var information structures, including the starting address of the list, the number of +// entries and the number of fixed args. +inline void NativeVarData::InitVarDataList(ICorDebugInfo::NativeVarInfo * pListStart, + int fixedArgCount, + int entryCount) +{ + m_offsetInfo.Init(pListStart, entryCount); + m_fixedArgsCount = fixedArgCount; + m_fInitialized = true; +} + +//----------------------------------------------------------------------------- +// SequencePoints member function implementations +//----------------------------------------------------------------------------- + +// initializing constructor +inline SequencePoints::SequencePoints() : + m_mapCount(0), + m_lastILOffset(0), + m_fInitialized(false) +{ +} + +// destructor +inline SequencePoints::~SequencePoints() +{ + m_fInitialized = false; + } + +// Initialize the m_pMap data member to the address of an allocated chunk +// of memory (or to NULL if the count is zero). Set m_count as the +// number of entries in the map. +inline void SequencePoints::InitSequencePoints(ULONG32 count) +{ + m_map.Alloc(count), + m_fInitialized = true; +} + +// +// Map the given native offset to IL offset and return the mapping type. +// +// Arguments: +// dwNativeOffset - the native offset to be mapped +// pMapType - out parameter; return the mapping type +// +// Return Value: +// Return the IL offset corresponding to the given native offset. +// For a prolog, return 0. +// For an epilog, return the IL offset of the last sequence point before the epilog. +// If we can't map to an IL offset, then return 0, with a mapping type of MAPPING_NO_INFO. +// +// Assumptions: +// The sequence points are sorted. +// + +inline +DWORD SequencePoints::MapNativeOffsetToIL(DWORD dwNativeOffset, + CorDebugMappingResult *pMapType) +{ + //_ASSERTE(IsInitialized()); + if (!IsInitialized()) + { + (*pMapType) = MAPPING_NO_INFO; + return 0; + } + + _ASSERTE(pMapType != NULL); + + int i; + + for (i = 0; i < (int)m_mapCount; ++i) + { + // Check to determine if dwNativeOffset is within this sequence point. Checking the lower bound is trivial-- + // we just make sure that dwNativeOffset >= m_map[i].nativeStartOffset. + // Checking to be sure it's before the end of the range is a little trickier. We can have + // m_map[i].nativeEndOffset = 0 for two reasons: + // 1. We use an end offset of 0 to signify that this end offset is also the end of the method. + // 2. We could also have an end offset of 0 if the IL prologue doesn't translate to any native + // instructions. Thus, the first native instruction (which will not be in the prologue) is at an offset + // of 0. The end offset is always set to the start offset of the next sequence point, so this means + // that both the start and end offsets of the (non-existent) native instruction range for the + // prologue is also 0. + // If the end offset is 0, we want to check if we're in the prologue before concluding that the + // value of dwNativeOffset is out of range. + if ((dwNativeOffset >= m_map[i].nativeStartOffset) && + (((m_map[i].nativeEndOffset == 0) && (m_map[i].ilOffset != (ULONG)ICorDebugInfo::PROLOG)) || + (dwNativeOffset < m_map[i].nativeEndOffset))) + { + ULONG uILOffset = m_map[i].ilOffset; + + if (m_map[i].ilOffset == (ULONG)ICorDebugInfo::PROLOG) + { + uILOffset = 0; + (*pMapType) = MAPPING_PROLOG; + } + else if (m_map[i].ilOffset == (ULONG)ICorDebugInfo::NO_MAPPING) + { + uILOffset = 0; + (*pMapType) = MAPPING_UNMAPPED_ADDRESS; + } + else if (m_map[i].ilOffset == (ULONG)ICorDebugInfo::EPILOG) + { + uILOffset = m_lastILOffset; + (*pMapType) = MAPPING_EPILOG; + } + else if (dwNativeOffset == m_map[i].nativeStartOffset) + { + (*pMapType) = MAPPING_EXACT; + } + else + { + (*pMapType) = MAPPING_APPROXIMATE; + } + return uILOffset; + } + } + + (*pMapType) = MAPPING_NO_INFO; + return 0; +} + +// +// Copy data from the VM map data to our own map structure and sort. The +// information comes to us in a data structure that differs slightly from the +// one we use out of process, so we have to copy it to the right-side struct. +// Arguments +// input +// mapCopy sequence points +// output +// pSeqPoints.m_map is initialized with the correct right side representation of sequence points + +inline +void SequencePoints::CopyAndSortSequencePoints(const ICorDebugInfo::OffsetMapping mapCopy[]) +{ + // copy information to pSeqPoint and set end offsets + int i; + + ULONG32 lastILOffset = 0; + + const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION; + for (i = 0; i < m_map.Count(); i++) + { + m_map[i].ilOffset = mapCopy[i].ilOffset; + m_map[i].nativeStartOffset = mapCopy[i].nativeOffset; + + if (i < m_map.Count() - 1) + { + // We need to not use CALL_INSTRUCTION's IL start offset. + int j = i + 1; + while ((mapCopy[j].source & call_inst) == call_inst && j < m_map.Count()-1) + j++; + + m_map[i].nativeEndOffset = mapCopy[j].nativeOffset; + } + + m_map[i].source = mapCopy[i].source; + + // need to cast the offsets to signed values first because we do actually use + // special negative offsets such as ICorDebugInfo::PROLOG + if ((m_map[i].source & call_inst) != call_inst) + lastILOffset = max((int)lastILOffset, (int)m_map[i].ilOffset); + } + + if (m_map.Count() >= 1) + { + m_map[i - 1].nativeEndOffset = 0; + m_map[i - 1].source = + (ICorDebugInfo::SourceTypes)(m_map[i - 1].source | ICorDebugInfo::NATIVE_END_OFFSET_UNKNOWN); + } + + // sort the map + MapSortILMap mapSorter(&m_map[0], m_map.Count()); + mapSorter.Sort(); + + + m_mapCount = m_map.Count(); + while (m_mapCount > 0 && (m_map[m_mapCount-1].source & (call_inst)) == call_inst) + m_mapCount--; + + SetLastILOffset(lastILOffset); +} // CopyAndSortSequencePoints + +//----------------------------------------------------------------------------- +// member function implementations for MapSortILMap class to sort sequence points +// by IL offset +//----------------------------------------------------------------------------- + +// secondary key comparison--if two IL offsets are the same, +// we determine order based on native offset + +inline +int SequencePoints::MapSortILMap::CompareInternal(DebuggerILToNativeMap *first, + DebuggerILToNativeMap *second) +{ + LIMITED_METHOD_CONTRACT; + + if (first->nativeStartOffset == second->nativeStartOffset) + return 0; + else if (first->nativeStartOffset < second->nativeStartOffset) + return -1; + else + return 1; +} + +//Comparison operator +inline +int SequencePoints::MapSortILMap::Compare(DebuggerILToNativeMap * first, + DebuggerILToNativeMap * second) +{ + LIMITED_METHOD_CONTRACT; + const DWORD call_inst = (DWORD)ICorDebugInfo::CALL_INSTRUCTION; + + //PROLOGs go first + if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG && + second->ilOffset == (ULONG) ICorDebugInfo::PROLOG) + { + return CompareInternal(first, second); + } + else if (first->ilOffset == (ULONG) ICorDebugInfo::PROLOG) + { + return -1; + } + else if (second->ilOffset == (ULONG) ICorDebugInfo::PROLOG) + { + return 1; + } + // call_instruction goes at the very very end of the table. + else if ((first->source & call_inst) == call_inst + && (second->source & call_inst) == call_inst) + { + return CompareInternal(first, second); + } else if ((first->source & call_inst) == call_inst) + { + return 1; + } else if ((second->source & call_inst) == call_inst) + { + return -1; + } + //NO_MAPPING go last + else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING && + second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING) + { + return CompareInternal(first, second); + } + else if (first->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING) + { + return 1; + } + else if (second->ilOffset == (ULONG) ICorDebugInfo::NO_MAPPING) + { + return -1; + } + //EPILOGs go next-to-last + else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG && + second->ilOffset == (ULONG) ICorDebugInfo::EPILOG) + { + return CompareInternal(first, second); + } + else if (first->ilOffset == (ULONG) ICorDebugInfo::EPILOG) + { + return 1; + } + else if (second->ilOffset == (ULONG) ICorDebugInfo::EPILOG) + { + return -1; + } + //normal offsets compared otherwise + else if (first->ilOffset < second->ilOffset) + { + return -1; + } + else if (first->ilOffset == second->ilOffset) + { + return CompareInternal(first, second); + } + else + { + return 1; + } +} + +//----------------------------------------------------------------------------- +// NativeCodeFunctionData member function implementations +// (for getting native code regions) +//----------------------------------------------------------------------------- + +inline +CodeBlobRegion & operator++(CodeBlobRegion & rs) +{ + return rs = CodeBlobRegion(rs + 1); +} + +// Convert the data in an instance of DebuggerIPCE_JITFUncData to an instance of NativeCodeFunctionData. +// We need to have this latter type to look up or create a new CordbNativeCode object, but the stack walker is +// using the former type to gather information. +// Arguments: +// Input: +// source - an initialized instance of DebuggerIPCE_JITFuncData containing the information to +// be copied into this instance of NativeCodeFunctionData +// @dbgtodo dlaw: Once CordbThread::RefreshStack is fully DAC-ized, we can change the data structure that it uses +// to have a member of type NativeCodeFunctionData which we can pass without copying. At that point, +// this method can disappear. +inline +NativeCodeFunctionData::NativeCodeFunctionData(DebuggerIPCE_JITFuncData * source) +{ + // copy the code region information + m_rgCodeRegions[kHot].Init(CORDB_ADDRESS(source->nativeStartAddressPtr), (ULONG)source->nativeHotSize); + m_rgCodeRegions[kCold].Init(CORDB_ADDRESS(source->nativeStartAddressColdPtr), (ULONG)source->nativeColdSize); + + // copy the other function information + isInstantiatedGeneric = source->isInstantiatedGeneric; + vmNativeCodeMethodDescToken = source->vmNativeCodeMethodDescToken; + encVersion = source->enCVersion; +} + + +// set all fields to default values (NULL, FALSE, or zero as appropriate) +inline +NativeCodeFunctionData::NativeCodeFunctionData() +{ + Clear(); +} + +inline +void NativeCodeFunctionData::Clear() +{ + isInstantiatedGeneric = FALSE; + encVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; + for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region) + { + m_rgCodeRegions[region].Clear(); + } +} + +//----------------------------------------------------------------------------------- +// ClassInfo member functions +//----------------------------------------------------------------------------------- + +inline +ClassInfo::ClassInfo(): + m_objectSize(0) + {} + +// clear all fields +inline +void ClassInfo::Clear() +{ + m_objectSize = 0; + m_fieldList.Dealloc(); +} + +inline +ClassInfo::~ClassInfo() +{ + m_fieldList.Dealloc(); +} + +//----------------------------------------------------------------------------------- +// FieldData member functions +//----------------------------------------------------------------------------------- +#ifndef RIGHT_SIDE_COMPILE + +// initialize various fields of an instance of FieldData from information retrieved from a FieldDesc +inline +void FieldData::Initialize(BOOL fIsStatic, BOOL fIsPrimitive, mdFieldDef mdToken) +{ + ClearFields(); + m_fFldIsStatic = (fIsStatic == TRUE); + m_fFldIsPrimitive = (fIsPrimitive == TRUE); + // This is what tells the right side the field is unavailable due to EnC. + m_fldMetadataToken = mdToken; +} +#endif + +// clear various fields for a new instance of FieldData +inline +void FieldData::ClearFields() +{ + m_fldSignatureCache = NULL; + m_fldSignatureCacheSize = 0; + m_fldInstanceOffset = 0; + m_pFldStaticAddress = NULL; +} + +typedef ULONG_PTR SIZE_T; + +inline +BOOL FieldData::OkToGetOrSetInstanceOffset() +{ + return (!m_fFldIsStatic && !m_fFldIsRVA && !m_fFldIsTLS && !m_fFldIsContextStatic && + m_fFldStorageAvailable && (m_pFldStaticAddress == NULL)); +} + +// If this is an instance field, store its offset +inline +void FieldData::SetInstanceOffset(SIZE_T offset) +{ + _ASSERTE(!m_fFldIsStatic); + _ASSERTE(!m_fFldIsRVA); + _ASSERTE(!m_fFldIsTLS); + _ASSERTE(!m_fFldIsContextStatic); + _ASSERTE(m_fFldStorageAvailable); + _ASSERTE(m_pFldStaticAddress == NULL); + m_fldInstanceOffset = offset; +} + +inline +BOOL FieldData::OkToGetOrSetStaticAddress() +{ + return (m_fFldIsStatic && !m_fFldIsTLS && !m_fFldIsContextStatic && + m_fFldStorageAvailable && (m_fldInstanceOffset == 0)); +} + +// If this is a "normal" static, store its absolute address +inline +void FieldData::SetStaticAddress(TADDR addr) +{ + _ASSERTE(m_fFldIsStatic); + _ASSERTE(!m_fFldIsTLS); + _ASSERTE(!m_fFldIsContextStatic); + _ASSERTE(m_fFldStorageAvailable); + _ASSERTE(m_fldInstanceOffset == 0); + m_pFldStaticAddress = TADDR(addr); +} + +// Get the offset of a field +inline +SIZE_T FieldData::GetInstanceOffset() +{ + _ASSERTE(!m_fFldIsStatic); + _ASSERTE(!m_fFldIsRVA); + _ASSERTE(!m_fFldIsTLS); + _ASSERTE(!m_fFldIsContextStatic); + _ASSERTE(m_fFldStorageAvailable); + _ASSERTE(m_pFldStaticAddress == NULL); + return m_fldInstanceOffset; +} + +// Get the static address for a field +inline +TADDR FieldData::GetStaticAddress() +{ + _ASSERTE(m_fFldIsStatic); + _ASSERTE(!m_fFldIsTLS); + _ASSERTE(!m_fFldIsContextStatic); + _ASSERTE(m_fFldStorageAvailable || (m_pFldStaticAddress == NULL)); + _ASSERTE(m_fldInstanceOffset == 0); + return m_pFldStaticAddress; +} + +//----------------------------------------------------------------------------------- +// EnCHangingFieldInfo member functions +//----------------------------------------------------------------------------------- + +inline +void EnCHangingFieldInfo::Init(VMPTR_Object pObject, + SIZE_T offset, + mdFieldDef fieldToken, + CorElementType elementType, + mdTypeDef metadataToken, + VMPTR_DomainFile vmDomainFile) + { + m_vmObject = pObject; + m_offsetToVars = offset; + m_fldToken = fieldToken; + m_objectTypeData.elementType = elementType; + m_objectTypeData.metadataToken = metadataToken; + m_objectTypeData.vmDomainFile = vmDomainFile; + } + + + +#endif // DACDBISTRUCTURES_INL_ diff --git a/src/debug/inc/dbgappdomain.h b/src/debug/inc/dbgappdomain.h new file mode 100644 index 0000000000..70504c09ec --- /dev/null +++ b/src/debug/inc/dbgappdomain.h @@ -0,0 +1,388 @@ +// 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. + +#ifndef DbgAppDomain_H +#define DbgAppDomain_H + +// Forward declaration +class AppDomain; + + +void BeginThreadAffinityHelper(); +void EndThreadAffinityHelper(); + + +// AppDomainInfo contains information about an AppDomain +// All pointers are for the left side, and we do not own any of the memory +struct AppDomainInfo +{ + ULONG m_id; // unique identifier + int m_iNameLengthInBytes; + LPCWSTR m_szAppDomainName; + AppDomain *m_pAppDomain; // only used by LS + + // NOTE: These functions are just helpers and must not add a VTable + // to this struct (since we need to read this out-of-proc) + + // Provide a clean definition of an empty entry + inline bool IsEmpty() const + { + return m_szAppDomainName == NULL; + } + +#ifndef RIGHT_SIDE_COMPILE + // Mark this entry as empty. + inline void FreeEntry() + { + m_szAppDomainName = NULL; + } + + // Set the string name and length. + // If szName is null, it is adjusted to a global constant. + // This also causes the entry to be considered valid + inline void SetName(LPCWSTR szName) + { + if (szName != NULL) + m_szAppDomainName = szName; + else + m_szAppDomainName = W("<NoName>"); + + m_iNameLengthInBytes = (int) (wcslen(m_szAppDomainName) + 1) * sizeof(WCHAR); + } +#endif +}; + +// Enforce the AppDomain IPC block binary layout doesn't change between versions. +// Only an issue for x86 since that's the only platform w/ multiple versions. +#if defined(DBG_TARGET_X86) +static_assert_no_msg(offsetof(AppDomainInfo, m_id) == 0x0); +static_assert_no_msg(offsetof(AppDomainInfo, m_iNameLengthInBytes) == 0x4); +static_assert_no_msg(offsetof(AppDomainInfo, m_szAppDomainName) == 0x8); +static_assert_no_msg(offsetof(AppDomainInfo, m_pAppDomain) == 0xc); +#endif + + + +// The RemoteHANDLE encapsulates the PAL specific handling of handles to avoid PAL specific ifdefs +// everywhere else in the code. +// There are two common initialization patterns: +// +// 1. Publishing of local handle for other processes, the value of the wrapper is a local handle +// in *this* process at the end: +// - In this process, call SetLocal(hHandle) to initialize the handle. +// - In the other processes, call DuplicateToLocalProcess to get a local copy of the handle. +// +// 2. Injecting of local handle into other process, the value of the wrapper is a local handle +// in the *other* process at the end: +// - In this process, call DuplicateToRemoteProcess(hProcess, hHandle) to initialize the handle. +// - In the other process, call ImportToOtherProcess() to finish the initialization of the wrapper +// with a local copy of the handle. +// +// Once initialized, the wrapper can be used the same way as a regular HANDLE in the process +// it was initialized for. There is casting operator HANDLE to achieve that. + +struct RemoteHANDLE { + HANDLE m_hLocal; + + operator HANDLE& () + { + return m_hLocal; + } + + void Close() + { + HANDLE hHandle = m_hLocal; + if (hHandle != NULL) { + m_hLocal = NULL; + CloseHandle(hHandle); + } + } + + // Sets the local value of the handle. DuplicateToLocalProcess can be used later + // by the remote process to acquire the remote handle. + BOOL SetLocal(HANDLE hHandle) + { + m_hLocal = hHandle; + return TRUE; + } + + // Duplicates the current handle value to remote process. ImportToLocalProcess + // should be called in the remote process before the handle is used in the remote process. + // NOTE: right now this is used for duplicating the debugger's process handle into the LS so + // that the LS can know when the RS has exited; thus we are only specifying SYNCHRONIZE + // access to mitigate any security concerns. + BOOL DuplicateToRemoteProcess(HANDLE hProcess, HANDLE hHandle) + { + return DuplicateHandle(GetCurrentProcess(), hHandle, hProcess, &m_hLocal, + SYNCHRONIZE, FALSE, 0); + } + + // Duplicates the current handle value to local process. To be used in combination with SetLocal. + BOOL DuplicateToLocalProcess(HANDLE hProcess, HANDLE* pHandle) + { + return DuplicateHandle(hProcess, m_hLocal, GetCurrentProcess(), pHandle, + NULL, FALSE, DUPLICATE_SAME_ACCESS); + } + + void CloseInRemoteProcess(HANDLE hProcess) + { + HANDLE hHandle = m_hLocal; + m_hLocal = NULL; + + HANDLE hTmp; + if (DuplicateHandle(hProcess, hHandle, GetCurrentProcess(), &hTmp, + NULL, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) + { + CloseHandle(hTmp); + } + } + + // Imports the handle to local process. To be used in combination with DuplicateToRemoteProcess. + HANDLE ImportToLocalProcess() + { + return m_hLocal; + } +}; + + +// AppDomain publishing server support: +// Information about all appdomains in the process will be maintained +// in the shared memory block for use by the debugger, etc. +// This structure defines the layout of the information that will +// be maintained. +struct AppDomainEnumerationIPCBlock +{ + // !!! The binary format of this layout must remain the same across versions so that + // !!! a V2.0 publisher can inspect a v1.0 app. + + // lock for serialization while manipulating AppDomain list. + RemoteHANDLE m_hMutex; + + // Number of slots in AppDomainListElement array + int m_iTotalSlots; + int m_iNumOfUsedSlots; + int m_iLastFreedSlot; + int m_iSizeInBytes; // Size of AppDomainInfo in bytes + + // We can use psapi!GetModuleFileNameEx to get the module name. + // This provides an alternative. + int m_iProcessNameLengthInBytes; + WCHAR *m_szProcessName; + + AppDomainInfo *m_rgListOfAppDomains; + BOOL m_fLockInvalid; + + +#ifndef RIGHT_SIDE_COMPILE + /************************************************************************* + * Locks the list + *************************************************************************/ + BOOL Lock() + { + BeginThreadAffinityHelper(); + + DWORD dwRes = WaitForSingleObject(m_hMutex, 3000); + if (dwRes == WAIT_TIMEOUT) + { + // Nobody should get stuck holding this lock. + // If we timeout on the wait, then either: + // - it's a really bad race and somebody got preempted for a long time + // - perhaps somebody's doing a DOS attack and holding onto the mutex. + m_fLockInvalid = TRUE; + } + + + // The only time this can happen is if we're in shutdown and a thread + // that held this lock is killed. If this happens, assume that this + // IPC block is in an invalid state and return FALSE to indicate + // that people shouldn't do anything with the block anymore. + if (dwRes == WAIT_ABANDONED) + { + m_fLockInvalid = TRUE; + } + + if (m_fLockInvalid) + { + Unlock(); + } + + return (dwRes == WAIT_OBJECT_0 && !m_fLockInvalid); + } + + /************************************************************************* + * Unlocks the list + *************************************************************************/ + void Unlock() + { + // Lock may or may not be valid at this point. Thus Release may fail, + // but we'll just ignore that. + ReleaseMutex(m_hMutex); + EndThreadAffinityHelper(); + + } + + /************************************************************************* + * Gets a free AppDomainInfo entry, and will allocate room if there are + * no free slots left. + *************************************************************************/ + AppDomainInfo *GetFreeEntry() + { + // first check to see if there is space available. If not, then realloc. + if (m_iNumOfUsedSlots == m_iTotalSlots) + { + // need to realloc + AppDomainInfo *pTemp = + new (nothrow) AppDomainInfo [m_iTotalSlots*2]; + + if (pTemp == NULL) + { + return (NULL); + } + + memcpy (pTemp, m_rgListOfAppDomains, m_iSizeInBytes); + + delete [] m_rgListOfAppDomains; + + // Initialize the increased portion of the realloced memory + int iNewSlotSize = m_iTotalSlots * 2; + + for (int iIndex = m_iTotalSlots; iIndex < iNewSlotSize; iIndex++) + pTemp[iIndex].FreeEntry(); + + m_rgListOfAppDomains = pTemp; + m_iTotalSlots = iNewSlotSize; + m_iSizeInBytes *= 2; + } + + // Walk the list looking for an empty slot. Start from the last + // one which was freed. + { + int i = m_iLastFreedSlot; + + do + { + // Pointer to the entry being examined + AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]); + + // is the slot available? + if (pADInfo->IsEmpty()) + return (pADInfo); + + i = (i + 1) % m_iTotalSlots; + + } while (i != m_iLastFreedSlot); + } + + _ASSERTE(!"ADInfo::GetFreeEntry: should never get here."); + return (NULL); + } + + /************************************************************************* + * Returns an AppDomainInfo slot to the free list. + *************************************************************************/ + void FreeEntry(AppDomainInfo *pADInfo) + { + _ASSERTE(pADInfo >= m_rgListOfAppDomains && + pADInfo < m_rgListOfAppDomains + m_iSizeInBytes); + _ASSERTE(((size_t)pADInfo - (size_t)m_rgListOfAppDomains) % + sizeof(AppDomainInfo) == 0); + + // Mark this slot as free + pADInfo->FreeEntry(); + +#ifdef _DEBUG + memset(pADInfo, 0, sizeof(AppDomainInfo)); +#endif + + // decrement the used slot count + m_iNumOfUsedSlots--; + + // Save the last freed slot. + m_iLastFreedSlot = (int)((size_t)pADInfo - (size_t)m_rgListOfAppDomains) / + sizeof(AppDomainInfo); + } + + /************************************************************************* + * Finds an AppDomainInfo entry corresponding to the AppDomain pointer. + * Returns NULL if no such entry exists. + *************************************************************************/ + AppDomainInfo *FindEntry(AppDomain *pAD) + { + // Walk the list looking for a matching entry + for (int i = 0; i < m_iTotalSlots; i++) + { + AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]); + + if (!pADInfo->IsEmpty() && + pADInfo->m_pAppDomain == pAD) + return pADInfo; + } + + return (NULL); + } + + /************************************************************************* + * Returns the first AppDomainInfo entry in the list. Returns NULL if + * no such entry exists. + *************************************************************************/ + AppDomainInfo *FindFirst() + { + // Walk the list looking for a non-empty entry + for (int i = 0; i < m_iTotalSlots; i++) + { + AppDomainInfo *pADInfo = &(m_rgListOfAppDomains[i]); + + if (!pADInfo->IsEmpty()) + return pADInfo; + } + + return (NULL); + } + + /************************************************************************* + * Returns the next AppDomainInfo entry after pADInfo. Returns NULL if + * pADInfo was the last in the list. + *************************************************************************/ + AppDomainInfo *FindNext(AppDomainInfo *pADInfo) + { + _ASSERTE(pADInfo >= m_rgListOfAppDomains && + pADInfo < m_rgListOfAppDomains + m_iSizeInBytes); + _ASSERTE(((size_t)pADInfo - (size_t)m_rgListOfAppDomains) % + sizeof(AppDomainInfo) == 0); + + // Walk the list looking for the next non-empty entry + for (int i = (int)((size_t)pADInfo - (size_t)m_rgListOfAppDomains) + / sizeof(AppDomainInfo) + 1; + i < m_iTotalSlots; + i++) + { + AppDomainInfo *pADInfoTemp = &(m_rgListOfAppDomains[i]); + + if (!pADInfoTemp->IsEmpty()) + return pADInfoTemp; + } + + return (NULL); + } +#endif // RIGHT_SIDE_COMPILE +}; + +// Enforce the AppDomain IPC block binary layout doesn't change between versions. +// Only an issue for x86 since that's the only platform w/ multiple versions. +#if defined(DBG_TARGET_X86) +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_hMutex) == 0x0); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iTotalSlots) == 0x4); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iNumOfUsedSlots) == 0x8); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iLastFreedSlot) == 0xc); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iSizeInBytes) == 0x10); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_iProcessNameLengthInBytes) == 0x14); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_szProcessName) == 0x18); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_rgListOfAppDomains) == 0x1c); +static_assert_no_msg(offsetof(AppDomainEnumerationIPCBlock, m_fLockInvalid) == 0x20); +#endif + +#endif //DbgAppDomain_H + + + diff --git a/src/debug/inc/dbgipcevents.h b/src/debug/inc/dbgipcevents.h new file mode 100644 index 0000000000..6ace36e011 --- /dev/null +++ b/src/debug/inc/dbgipcevents.h @@ -0,0 +1,2360 @@ +// 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 <new.hpp> +#include <cor.h> +#include <cordebug.h> +#include <corjit.h> // for ICorDebugInfo::VarLocType & VarLoc +#include <specstrings.h> + +#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 <class T>, 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 <typename T> +class MSLAYOUT LsPointer : public GeneralLsPointer +{ +public: + void Set(void * p) + { + m_ptr = p; + } + void * UnsafeGet() + { + return m_ptr; + } + + static LsPointer<T> NullPtr() + { + return MakePtr(NULL); + } + + static LsPointer<T> MakePtr(T* p) + { +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't' +#endif // _PREFAST_ + + LsPointer<T> 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<T> 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 <class T> UINT AllocCookie(CordbProcess * pProc, T * p); +template <class T> T * UnwrapCookie(CordbProcess * pProc, UINT cookie); + +UINT AllocCookieCordbEval(CordbProcess * pProc, class CordbEval * p); +class CordbEval * UnwrapCookieCordbEval(CordbProcess * pProc, UINT cookie); + +template <class CordbEval> UINT AllocCookie(CordbProcess * pProc, CordbEval * p) +{ + return AllocCookieCordbEval(pProc, p); +} +template <class CordbEval> CordbEval * UnwrapCookie(CordbProcess * pProc, UINT cookie) +{ + return UnwrapCookieCordbEval(pProc, cookie); +} + + + +// This is how the RS sees the pointers in the IPC block. +template<class T> +class MSLAYOUT RsPointer : public GeneralRsPointer +{ +public: + // Since we're being used inside a union, we can't have a ctor. + + static RsPointer<T> NullPtr() + { + RsPointer<T> t; + t.m_data = 0; + return t; + } + + bool AllocHandle(CordbProcess *pProc, T* p) + { + // This will force validation. + m_data = AllocCookie<T>(pProc, p); + return (m_data != 0); + } + + bool operator==(RsPointer<T> p) { return p.m_data == this->m_data; } + + T* UnWrapAndRemove(CordbProcess *pProc) + { + return UnwrapCookie<T>(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<ls_type> ptr_name; + + +#define DEFINE_RSPTR_TYPE(rs_type, ptr_name) \ + class rs_type; \ + typedef RsPointer<rs_type> ptr_name; + +#else // !RIGHT_SIDE_COMPILE +//----------------------------------------------------------------------------- +// Infrastructure for LS Definitions +//----------------------------------------------------------------------------- + +// This is how the LS sees the pointers in the IPC block. +template<typename T> +class MSLAYOUT LsPointer : public GeneralLsPointer +{ +public: + // Since we're being used inside a union, we can't have a ctor. + //LsPointer() { } + + static LsPointer<T> NullPtr() + { + return MakePtr(NULL); + } + + static LsPointer<T> MakePtr(T * p) + { +#ifdef _PREFAST_ +#pragma warning(push) +#pragma warning(disable:6001) // PREfast warning: Using uninitialize memory 't' +#endif // _PREFAST_ + + LsPointer<T> 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<T> 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<T*>(m_ptr); + } +}; + +template <class n> +class MSLAYOUT RsPointer : public GeneralRsPointer +{ +public: + static RsPointer<n> NullPtr() + { + RsPointer<n> t; + t.m_data = 0; + return t; + } + + bool operator==(RsPointer<n> 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<ls_type> 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<void>) == 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<DT_CONTEXT> LSPTR_CONTEXT; +#else // RIGHT_SIDE_COMPILE +typedef LsPointer<DT_CONTEXT> 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 <typename TTargetPtr, typename TDacPtr> +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<TTargetPtr,TDacPtr> 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<TADDR>(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<TTargetPtr*>(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<TTargetPtr> ToLsPtr() + { + return LsPointer<TTargetPtr>::MakePtr( reinterpret_cast<TTargetPtr *>(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<ls_type, dac_ptr_type> 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<ls_type, void> 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<DT_CONTEXT, PTR_CONTEXT> VMPTR_CONTEXT; +#else +typedef VMPTR_Base<DT_CONTEXT, void > 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 <int nMaxLengthIncludingNull> +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. +// +// <TODO>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</TODO> +// + +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; + +#if defined(DBG_TARGET_WIN64) || defined(DBG_TARGET_ARM) + BOOL fIsFilterFrame; + SIZE_T parentNativeOffset; + FramePointer fpParentOrSelf; +#endif // DBG_TARGET_WIN64 || DBG_TARGET_ARM + + // 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<String> 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<String,Pair<String>> +// 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<int>) 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<const WCHAR*> (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<WCHAR*> (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<MAX_LOG_SWITCH_NAME_LEN + 1> szCategory; + Ls_Rs_StringBuffer szContent; + } FirstLogMessage; + + struct MSLAYOUT + { + int iLevel; + int iReason; + + EmbeddedIPCString<MAX_LOG_SWITCH_NAME_LEN + 1> szSwitchName; + EmbeddedIPCString<MAX_LOG_SWITCH_NAME_LEN + 1> 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<MAX_LONGPATH> 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_ */ diff --git a/src/debug/inc/dbgipceventtypes.h b/src/debug/inc/dbgipceventtypes.h new file mode 100644 index 0000000000..b538360e68 --- /dev/null +++ b/src/debug/inc/dbgipceventtypes.h @@ -0,0 +1,143 @@ +// 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. + +// +// Events that go both ways +IPC_EVENT_TYPE0(DB_IPCE_INVALID_EVENT ,0x0000) +IPC_EVENT_TYPE0(DB_IPCE_TYPE_MASK ,0x0FFF) + +// Some rules: +// 1. Type0 is for marking sections in the id range. +// Type1 is for events that go L->R, Type2 is for events that go R<-L. +// 2. All non-type 0 events should have a unique identifier & value +// 3. All type 1 events values should be in range [DB_IPCE_RUNTIME_FIRST, DB_IPCE_RUNTIME_LAST) +// All type 2 events values should be in range [DB_IPCE_DEBUGGER_FIRST, DB_IPCE_DEBUGGER_LAST) +// 4. All event values should be monotonically increasing, though we can skip values. +// 5. All values should be a subset of the bits specified by DB_IPCE_TYPE_MASK. +// +// These rules are enforced by a bunch of compile time checks (C_ASSERT) in +// the function DoCompileTimeCheckOnDbgIpcEventTypes. +// If you get compiler errors in this file, you are probably violating the rules above. + +// Events that travel from the RC to the DI (Left to Right) +IPC_EVENT_TYPE0(DB_IPCE_RUNTIME_FIRST ,0x0100) // change to TYPE0 because it is not really an event +IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT ,0x0100) +IPC_EVENT_TYPE1(DB_IPCE_SYNC_COMPLETE ,0x0102) +IPC_EVENT_TYPE1(DB_IPCE_THREAD_ATTACH ,0x0103) +IPC_EVENT_TYPE1(DB_IPCE_THREAD_DETACH ,0x0104) +IPC_EVENT_TYPE1(DB_IPCE_LOAD_MODULE ,0x0105) +IPC_EVENT_TYPE1(DB_IPCE_UNLOAD_MODULE ,0x0106) +IPC_EVENT_TYPE1(DB_IPCE_LOAD_CLASS ,0x0107) +IPC_EVENT_TYPE1(DB_IPCE_UNLOAD_CLASS ,0x0108) +IPC_EVENT_TYPE1(DB_IPCE_EXCEPTION ,0x0109) +IPC_EVENT_TYPE1(DB_IPCE_UNHANDLED_EXCEPTION ,0x010A) +IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT_ADD_RESULT ,0x010D) +IPC_EVENT_TYPE1(DB_IPCE_STEP_RESULT ,0x010E) +IPC_EVENT_TYPE1(DB_IPCE_STEP_COMPLETE ,0x010F) +IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT_REMOVE_RESULT ,0x0111) +IPC_EVENT_TYPE1(DB_IPCE_GET_BUFFER_RESULT ,0x0115) +IPC_EVENT_TYPE1(DB_IPCE_RELEASE_BUFFER_RESULT ,0x0116) +IPC_EVENT_TYPE1(DB_IPCE_ENC_ADD_FIELD ,0x0117) +IPC_EVENT_TYPE1(DB_IPCE_APPLY_CHANGES_RESULT ,0x0118) +IPC_EVENT_TYPE1(DB_IPCE_CUSTOM_NOTIFICATION ,0x011B) +IPC_EVENT_TYPE1(DB_IPCE_USER_BREAKPOINT ,0x011C) +IPC_EVENT_TYPE1(DB_IPCE_FIRST_LOG_MESSAGE ,0x011D) +// DB_IPCE_CONTINUED_LOG_MESSAGE = 0x11E, used to be here in v1.1, +// But we've removed that remove the v2.0 protocol +IPC_EVENT_TYPE1(DB_IPCE_LOGSWITCH_SET_MESSAGE ,0x011F) +IPC_EVENT_TYPE1(DB_IPCE_CREATE_APP_DOMAIN ,0x0120) +IPC_EVENT_TYPE1(DB_IPCE_EXIT_APP_DOMAIN ,0x0121) +IPC_EVENT_TYPE1(DB_IPCE_LOAD_ASSEMBLY ,0x0122) +IPC_EVENT_TYPE1(DB_IPCE_UNLOAD_ASSEMBLY ,0x0123) +IPC_EVENT_TYPE1(DB_IPCE_SET_DEBUG_STATE_RESULT ,0x0124) +IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_SETUP_RESULT ,0x0125) +IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_COMPLETE ,0x0126) +IPC_EVENT_TYPE1(DB_IPCE_SET_REFERENCE_RESULT ,0x0127) +IPC_EVENT_TYPE1(DB_IPCE_APP_DOMAIN_NAME_RESULT ,0x0128) +IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_ABORT_RESULT ,0x0129) +IPC_EVENT_TYPE1(DB_IPCE_NAME_CHANGE ,0x012a) +IPC_EVENT_TYPE1(DB_IPCE_UPDATE_MODULE_SYMS ,0x012c) +IPC_EVENT_TYPE1(DB_IPCE_CONTROL_C_EVENT ,0x012f) +IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_CLEANUP_RESULT ,0x0130) +IPC_EVENT_TYPE1(DB_IPCE_ENC_REMAP ,0x0131) +IPC_EVENT_TYPE1(DB_IPCE_SET_VALUE_CLASS_RESULT ,0x0133) +IPC_EVENT_TYPE1(DB_IPCE_BREAKPOINT_SET_ERROR ,0x0134) +IPC_EVENT_TYPE1(DB_IPCE_ENC_UPDATE_FUNCTION ,0x0137) +IPC_EVENT_TYPE1(DB_IPCE_SET_METHOD_JMC_STATUS_RESULT ,0x013a) +IPC_EVENT_TYPE1(DB_IPCE_GET_METHOD_JMC_STATUS_RESULT ,0x013b) +IPC_EVENT_TYPE1(DB_IPCE_SET_MODULE_JMC_STATUS_RESULT ,0x013c) +IPC_EVENT_TYPE1(DB_IPCE_GET_THREAD_FOR_TASKID_RESULT ,0x013d) +IPC_EVENT_TYPE1(DB_IPCE_CREATE_CONNECTION ,0x0141) +IPC_EVENT_TYPE1(DB_IPCE_DESTROY_CONNECTION ,0x0142) +IPC_EVENT_TYPE1(DB_IPCE_CHANGE_CONNECTION ,0x0143) +IPC_EVENT_TYPE1(DB_IPCE_FUNC_EVAL_RUDE_ABORT_RESULT ,0x0144) +IPC_EVENT_TYPE1(DB_IPCE_EXCEPTION_CALLBACK2 ,0x0147) +IPC_EVENT_TYPE1(DB_IPCE_EXCEPTION_UNWIND ,0x0148) +IPC_EVENT_TYPE1(DB_IPCE_INTERCEPT_EXCEPTION_RESULT ,0x0149) +IPC_EVENT_TYPE1(DB_IPCE_CREATE_HANDLE_RESULT ,0x014A) +IPC_EVENT_TYPE1(DB_IPCE_INTERCEPT_EXCEPTION_COMPLETE ,0x014B) +IPC_EVENT_TYPE1(DB_IPCE_ENC_REMAP_COMPLETE ,0x014C) +IPC_EVENT_TYPE1(DB_IPCE_CREATE_PROCESS ,0x014D) +IPC_EVENT_TYPE1(DB_IPCE_ENC_ADD_FUNCTION ,0x014E) +IPC_EVENT_TYPE1(DB_IPCE_GET_NGEN_COMPILER_FLAGS_RESULT,0x0151) +IPC_EVENT_TYPE1(DB_IPCE_SET_NGEN_COMPILER_FLAGS_RESULT,0x0152) +IPC_EVENT_TYPE1(DB_IPCE_MDA_NOTIFICATION ,0x0156) +IPC_EVENT_TYPE1(DB_IPCE_GET_GCHANDLE_INFO_RESULT ,0x0157) +IPC_EVENT_TYPE1(DB_IPCE_TEST_CRST ,0x0158) +IPC_EVENT_TYPE1(DB_IPCE_TEST_RWLOCK ,0x0159) +IPC_EVENT_TYPE1(DB_IPCE_LEFTSIDE_STARTUP ,0x015C) +IPC_EVENT_TYPE1(DB_IPCE_METADATA_UPDATE ,0x015D) +IPC_EVENT_TYPE1(DB_IPCE_RESOLVE_UPDATE_METADATA_1_RESULT,0x015E) +IPC_EVENT_TYPE1(DB_IPCE_RESOLVE_UPDATE_METADATA_2_RESULT,0x015F) +IPC_EVENT_TYPE0(DB_IPCE_RUNTIME_LAST ,0x0160) // The last event from runtime + + + +// Events that travel from the DI to the RC (Right to Left) +IPC_EVENT_TYPE0(DB_IPCE_DEBUGGER_FIRST ,0x0200) // change to TYPE0 because it is not really an event +IPC_EVENT_TYPE2(DB_IPCE_ASYNC_BREAK ,0x0200) +IPC_EVENT_TYPE2(DB_IPCE_CONTINUE ,0x0201) +IPC_EVENT_TYPE2(DB_IPCE_LIST_THREADS ,0x0202) +IPC_EVENT_TYPE2(DB_IPCE_SET_IP ,0x0205) +IPC_EVENT_TYPE2(DB_IPCE_SUSPEND_THREAD ,0x0206) +IPC_EVENT_TYPE2(DB_IPCE_RESUME_THREAD ,0x0207) +IPC_EVENT_TYPE2(DB_IPCE_BREAKPOINT_ADD ,0x0209) +IPC_EVENT_TYPE2(DB_IPCE_BREAKPOINT_REMOVE ,0x020A) +IPC_EVENT_TYPE2(DB_IPCE_STEP_CANCEL ,0x020B) +IPC_EVENT_TYPE2(DB_IPCE_STEP ,0x020C) +IPC_EVENT_TYPE2(DB_IPCE_STEP_OUT ,0x020D) +IPC_EVENT_TYPE2(DB_IPCE_GET_BUFFER ,0x0211) +IPC_EVENT_TYPE2(DB_IPCE_RELEASE_BUFFER ,0x0212) +IPC_EVENT_TYPE2(DB_IPCE_SET_CLASS_LOAD_FLAG ,0x0217) +IPC_EVENT_TYPE2(DB_IPCE_CONTINUE_EXCEPTION ,0x0219) +IPC_EVENT_TYPE2(DB_IPCE_ATTACHING ,0x021A) +IPC_EVENT_TYPE2(DB_IPCE_APPLY_CHANGES ,0x021B) +IPC_EVENT_TYPE2(DB_IPCE_SET_NGEN_COMPILER_FLAGS ,0x021F) +IPC_EVENT_TYPE2(DB_IPCE_GET_NGEN_COMPILER_FLAGS ,0x0220) +IPC_EVENT_TYPE2(DB_IPCE_IS_TRANSITION_STUB ,0x0221) +IPC_EVENT_TYPE2(DB_IPCE_IS_TRANSITION_STUB_RESULT ,0x0222) +IPC_EVENT_TYPE2(DB_IPCE_MODIFY_LOGSWITCH ,0x0223) +IPC_EVENT_TYPE2(DB_IPCE_ENABLE_LOG_MESSAGES ,0x0224) +IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL ,0x0225) +IPC_EVENT_TYPE2(DB_IPCE_SET_REFERENCE ,0x0228) +IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL_ABORT ,0x022c) +IPC_EVENT_TYPE2(DB_IPCE_DETACH_FROM_PROCESS ,0x022f) +IPC_EVENT_TYPE2(DB_IPCE_CONTROL_C_EVENT_RESULT ,0x0230) +IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL_CLEANUP ,0x0231) +IPC_EVENT_TYPE2(DB_IPCE_SET_ALL_DEBUG_STATE ,0x0232) +IPC_EVENT_TYPE2(DB_IPCE_SET_VALUE_CLASS ,0x0234) +IPC_EVENT_TYPE2(DB_IPCE_SET_METHOD_JMC_STATUS ,0x023a) +IPC_EVENT_TYPE2(DB_IPCE_GET_METHOD_JMC_STATUS ,0x023b) +IPC_EVENT_TYPE2(DB_IPCE_SET_MODULE_JMC_STATUS ,0x023c) +IPC_EVENT_TYPE2(DB_IPCE_GET_THREAD_FOR_TASKID ,0x023d) +IPC_EVENT_TYPE2(DB_IPCE_FUNC_EVAL_RUDE_ABORT ,0x0241) +IPC_EVENT_TYPE2(DB_IPCE_CREATE_HANDLE ,0x0244) +IPC_EVENT_TYPE2(DB_IPCE_DISPOSE_HANDLE ,0x0245) +IPC_EVENT_TYPE2(DB_IPCE_INTERCEPT_EXCEPTION ,0x0246) +IPC_EVENT_TYPE2(DB_IPCE_DEBUGGER_INVALID ,0x0249) // An invalid event type +IPC_EVENT_TYPE2(DB_IPCE_GET_GCHANDLE_INFO ,0x0251) +IPC_EVENT_TYPE2(DB_IPCE_RESOLVE_UPDATE_METADATA_1 ,0x0256) +IPC_EVENT_TYPE2(DB_IPCE_RESOLVE_UPDATE_METADATA_2 ,0x0257) +IPC_EVENT_TYPE0(DB_IPCE_DEBUGGER_LAST ,0x0258) // The last event from the debugger + diff --git a/src/debug/inc/dbgtargetcontext.h b/src/debug/inc/dbgtargetcontext.h new file mode 100644 index 0000000000..22b1c84096 --- /dev/null +++ b/src/debug/inc/dbgtargetcontext.h @@ -0,0 +1,450 @@ +// 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. + +#ifndef __DBG_TARGET_CONTEXT_INCLUDED +#define __DBG_TARGET_CONTEXT_INCLUDED + +#include <dbgportable.h> +#include <stddef.h> +#include "crosscomp.h" + +// +// The right side of the debugger can now be built to target multiple platforms. This means it is no longer +// safe to use the CONTEXT structure directly: the context of the platform we're building for might not match +// that of the one the debugger is targetting. So now all right side code will use the DT_CONTEXT abstraction +// instead. When the debugger target is the local platform this will just resolve back into CONTEXT, but cross +// platform we'll provide a hand-rolled version. +// + +// +// For cross platform cases we also need to provide a helper function for byte-swapping a context structure +// should the endian-ness of the debugger and debuggee platforms differ. This is called ByteSwapContext and is +// obviously a no-op for those cases where the left and right sides agree on storage format. +// +// NOTE: Any changes to the field layout of DT_CONTEXT must be tracked in the associated definition of +// ByteSwapContext. +// + +// For now, the only cross-platform CONTEXTs we support are x86/PAL and ARM/Win. Look in +// rotor/pal/inc/rotor_pal.h for the original PAL definitions. + +// +// **** NOTE: Keep these in sync with rotor/pal/inc/rotor_pal.h **** +// + +// This odd define pattern is needed because in DBI we set _TARGET_ to match the host and +// DBG_TARGET to control our targeting. In x-plat DBI DBG_TARGET won't match _TARGET_ and +// DBG_TARGET needs to take precedence +#if defined(DBG_TARGET_X86) +#define DTCONTEXT_IS_X86 +#elif defined (DBG_TARGET_AMD64) +#define DTCONTEXT_IS_AMD64 +#elif defined (DBG_TARGET_ARM) +#define DTCONTEXT_IS_ARM +#elif defined (DBG_TARGET_ARM64) +#define DTCONTEXT_IS_ARM64 +#elif defined (_TARGET_X86_) +#define DTCONTEXT_IS_X86 +#elif defined (_TARGET_AMD64_) +#define DTCONTEXT_IS_AMD64 +#elif defined (_TARGET_ARM_) +#define DTCONTEXT_IS_ARM +#elif defined (_TARGET_ARM64_) +#define DTCONTEXT_IS_ARM64 +#endif + +#if defined(DTCONTEXT_IS_X86) + +#define DT_SIZE_OF_80387_REGISTERS 80 + +#define DT_CONTEXT_i386 0x00010000 +#define DT_CONTEXT_CONTROL (DT_CONTEXT_i386 | 0x00000001L) // SS:SP, CS:IP, FLAGS, BP +#define DT_CONTEXT_INTEGER (DT_CONTEXT_i386 | 0x00000002L) // AX, BX, CX, DX, SI, DI +#define DT_CONTEXT_SEGMENTS (DT_CONTEXT_i386 | 0x00000004L) +#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_i386 | 0x00000008L) // 387 state +#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_i386 | 0x00000010L) + +#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_SEGMENTS) +#define DT_CONTEXT_EXTENDED_REGISTERS (DT_CONTEXT_i386 | 0x00000020L) + +#define DT_MAXIMUM_SUPPORTED_EXTENSION 512 + +typedef struct { + DWORD ControlWord; + DWORD StatusWord; + DWORD TagWord; + DWORD ErrorOffset; + DWORD ErrorSelector; + DWORD DataOffset; + DWORD DataSelector; + BYTE RegisterArea[DT_SIZE_OF_80387_REGISTERS]; + DWORD Cr0NpxState; +} DT_FLOATING_SAVE_AREA; + +typedef struct { + ULONG ContextFlags; + + ULONG Dr0; + ULONG Dr1; + ULONG Dr2; + ULONG Dr3; + ULONG Dr6; + ULONG Dr7; + + DT_FLOATING_SAVE_AREA FloatSave; + + ULONG SegGs; + ULONG SegFs; + ULONG SegEs; + ULONG SegDs; + + ULONG Edi; + ULONG Esi; + ULONG Ebx; + ULONG Edx; + ULONG Ecx; + ULONG Eax; + + ULONG Ebp; + ULONG Eip; + ULONG SegCs; + ULONG EFlags; + ULONG Esp; + ULONG SegSs; + + UCHAR ExtendedRegisters[DT_MAXIMUM_SUPPORTED_EXTENSION]; + +} DT_CONTEXT; + +// Since the target is little endian in this case we only have to provide a real implementation of +// ByteSwapContext if the platform we're building on is big-endian. +#ifdef BIGENDIAN +inline void ByteSwapContext(DT_CONTEXT *pContext) +{ + // Our job is simplified since the context has large contiguous ranges with fields of the same size. Keep + // the following logic in sync with the definition of DT_CONTEXT above. + BYTE *pbContext = (BYTE*)pContext; + + // The first span consists of 4 byte fields. + DWORD cbFields = (offsetof(DT_CONTEXT, FloatSave) + offsetof(DT_FLOATING_SAVE_AREA, RegisterArea)) / 4; + for (DWORD i = 0; i < cbFields; i++) + { + ByteSwapPrimitive(pbContext, pbContext, 4); + pbContext += 4; + } + + // Then there's a float save area containing 8 byte fields. + cbFields = sizeof(pContext->FloatSave.RegisterArea); + for (DWORD i = 0; i < cbFields; i++) + { + ByteSwapPrimitive(pbContext, pbContext, 8); + pbContext += 8; + } + + // Back to 4 byte fields. + cbFields = (offsetof(DT_CONTEXT, ExtendedRegisters) - offsetof(DT_CONTEXT, SegGs)) / 4; + for (DWORD i = 0; i < cbFields; i++) + { + ByteSwapPrimitive(pbContext, pbContext, 4); + pbContext += 4; + } + + // We don't know the formatting of the extended register area, but the debugger doesn't access this data + // on the left side, so just leave it in left-side format for now. + + // Validate that we converted up to where we think we did as a hedge against DT_CONTEXT layout changes. + _PASSERT((pbContext - ((BYTE*)pContext)) == (sizeof(DT_CONTEXT) - sizeof(pContext->ExtendedRegisters))); +} +#else // BIGENDIAN +inline void ByteSwapContext(DT_CONTEXT *pContext) +{ +} +#endif // BIGENDIAN + +#elif defined(DTCONTEXT_IS_AMD64) + +#define DT_CONTEXT_AMD64 0x00100000L + +#define DT_CONTEXT_CONTROL (DT_CONTEXT_AMD64 | 0x00000001L) +#define DT_CONTEXT_INTEGER (DT_CONTEXT_AMD64 | 0x00000002L) +#define DT_CONTEXT_SEGMENTS (DT_CONTEXT_AMD64 | 0x00000004L) +#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_AMD64 | 0x00000008L) +#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_AMD64 | 0x00000010L) + +#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT) +#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_SEGMENTS | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS) + +typedef struct { + ULONGLONG Low; + LONGLONG High; +} DT_M128A; + +typedef struct { + WORD ControlWord; + WORD StatusWord; + BYTE TagWord; + BYTE Reserved1; + WORD ErrorOpcode; + DWORD ErrorOffset; + WORD ErrorSelector; + WORD Reserved2; + DWORD DataOffset; + WORD DataSelector; + WORD Reserved3; + DWORD MxCsr; + DWORD MxCsr_Mask; + DT_M128A FloatRegisters[8]; + DT_M128A XmmRegisters[16]; + BYTE Reserved4[96]; +} DT_XMM_SAVE_AREA32; + +typedef struct DECLSPEC_ALIGN(16) { + + DWORD64 P1Home; + DWORD64 P2Home; + DWORD64 P3Home; + DWORD64 P4Home; + DWORD64 P5Home; + DWORD64 P6Home; + + DWORD ContextFlags; + DWORD MxCsr; + + WORD SegCs; + WORD SegDs; + WORD SegEs; + WORD SegFs; + WORD SegGs; + WORD SegSs; + DWORD EFlags; + + DWORD64 Dr0; + DWORD64 Dr1; + DWORD64 Dr2; + DWORD64 Dr3; + DWORD64 Dr6; + DWORD64 Dr7; + + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + + DWORD64 Rip; + + union { + DT_XMM_SAVE_AREA32 FltSave; + struct { + DT_M128A Header[2]; + DT_M128A Legacy[8]; + DT_M128A Xmm0; + DT_M128A Xmm1; + DT_M128A Xmm2; + DT_M128A Xmm3; + DT_M128A Xmm4; + DT_M128A Xmm5; + DT_M128A Xmm6; + DT_M128A Xmm7; + DT_M128A Xmm8; + DT_M128A Xmm9; + DT_M128A Xmm10; + DT_M128A Xmm11; + DT_M128A Xmm12; + DT_M128A Xmm13; + DT_M128A Xmm14; + DT_M128A Xmm15; + }; + }; + + DT_M128A VectorRegister[26]; + DWORD64 VectorControl; + + DWORD64 DebugControl; + DWORD64 LastBranchToRip; + DWORD64 LastBranchFromRip; + DWORD64 LastExceptionToRip; + DWORD64 LastExceptionFromRip; +} DT_CONTEXT; + +#elif defined(DTCONTEXT_IS_ARM) + +#define DT_CONTEXT_ARM 0x00200000L + +#define DT_CONTEXT_CONTROL (DT_CONTEXT_ARM | 0x1L) +#define DT_CONTEXT_INTEGER (DT_CONTEXT_ARM | 0x2L) +#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_ARM | 0x4L) +#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_ARM | 0x8L) + +#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT) +#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS) + +#define DT_ARM_MAX_BREAKPOINTS 8 +#define DT_ARM_MAX_WATCHPOINTS 1 + +typedef struct { + ULONGLONG Low; + LONGLONG High; +} DT_NEON128; + +typedef DECLSPEC_ALIGN(8) struct { + + // + // Control flags. + // + + DWORD ContextFlags; + + // + // Integer registers + // + + DWORD R0; + DWORD R1; + DWORD R2; + DWORD R3; + DWORD R4; + DWORD R5; + DWORD R6; + DWORD R7; + DWORD R8; + DWORD R9; + DWORD R10; + DWORD R11; + DWORD R12; + + // + // Control Registers + // + + DWORD Sp; + DWORD Lr; + DWORD Pc; + DWORD Cpsr; + + // + // Floating Point/NEON Registers + // + + DWORD Fpscr; + DWORD Padding; + union { + DT_NEON128 Q[16]; + ULONGLONG D[32]; + DWORD S[32]; + } DUMMYUNIONNAME; + + // + // Debug registers + // + + DWORD Bvr[DT_ARM_MAX_BREAKPOINTS]; + DWORD Bcr[DT_ARM_MAX_BREAKPOINTS]; + DWORD Wvr[DT_ARM_MAX_WATCHPOINTS]; + DWORD Wcr[DT_ARM_MAX_WATCHPOINTS]; + + DWORD Padding2[2]; + +} DT_CONTEXT; + +#elif defined(DTCONTEXT_IS_ARM64) + +#define DT_CONTEXT_ARM64 0x00400000L + +#define DT_CONTEXT_CONTROL (DT_CONTEXT_ARM64 | 0x1L) +#define DT_CONTEXT_INTEGER (DT_CONTEXT_ARM64 | 0x2L) +#define DT_CONTEXT_FLOATING_POINT (DT_CONTEXT_ARM64 | 0x4L) +#define DT_CONTEXT_DEBUG_REGISTERS (DT_CONTEXT_ARM64 | 0x8L) + +#define DT_CONTEXT_FULL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT) +#define DT_CONTEXT_ALL (DT_CONTEXT_CONTROL | DT_CONTEXT_INTEGER | DT_CONTEXT_FLOATING_POINT | DT_CONTEXT_DEBUG_REGISTERS) + +typedef DECLSPEC_ALIGN(16) struct { + // + // Control flags. + // + + /* +0x000 */ DWORD ContextFlags; + + // + // Integer registers + // + + /* +0x004 */ DWORD Cpsr; // NZVF + DAIF + CurrentEL + SPSel + /* +0x008 */ union { + struct { + DWORD64 X0; + DWORD64 X1; + DWORD64 X2; + DWORD64 X3; + DWORD64 X4; + DWORD64 X5; + DWORD64 X6; + DWORD64 X7; + DWORD64 X8; + DWORD64 X9; + DWORD64 X10; + DWORD64 X11; + DWORD64 X12; + DWORD64 X13; + DWORD64 X14; + DWORD64 X15; + DWORD64 X16; + DWORD64 X17; + DWORD64 X18; + DWORD64 X19; + DWORD64 X20; + DWORD64 X21; + DWORD64 X22; + DWORD64 X23; + DWORD64 X24; + DWORD64 X25; + DWORD64 X26; + DWORD64 X27; + DWORD64 X28; + }; + DWORD64 X[29]; + }; + /* +0x0f0 */ DWORD64 Fp; + /* +0x0f8 */ DWORD64 Lr; + /* +0x100 */ DWORD64 Sp; + /* +0x108 */ DWORD64 Pc; + + // + // Floating Point/NEON Registers + // + + /* +0x110 */ NEON128 V[32]; + /* +0x310 */ DWORD Fpcr; + /* +0x314 */ DWORD Fpsr; + + // + // Debug registers + // + + /* +0x318 */ DWORD Bcr[ARM64_MAX_BREAKPOINTS]; + /* +0x338 */ DWORD64 Bvr[ARM64_MAX_BREAKPOINTS]; + /* +0x378 */ DWORD Wcr[ARM64_MAX_WATCHPOINTS]; + /* +0x380 */ DWORD64 Wvr[ARM64_MAX_WATCHPOINTS]; + /* +0x390 */ + +} DT_CONTEXT; + +#else +#error Unsupported platform +#endif + + +#endif // __DBG_TARGET_CONTEXT_INCLUDED diff --git a/src/debug/inc/dbgtransportsession.h b/src/debug/inc/dbgtransportsession.h new file mode 100644 index 0000000000..5187202753 --- /dev/null +++ b/src/debug/inc/dbgtransportsession.h @@ -0,0 +1,849 @@ +// 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. + + +#ifndef __DBG_TRANSPORT_SESSION_INCLUDED +#define __DBG_TRANSPORT_SESSION_INCLUDED + +#ifndef RIGHT_SIDE_COMPILE +#include <utilcode.h> +#include <crst.h> + +#endif // !RIGHT_SIDE_COMPILE + +#if defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) + +#include <twowaypipe.h> + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + DbgTransportSession was originally designed around cross-machine debugging via sockets and it is supposed to + handle network interruptions. Right now we use pipes (see TwoWaypipe) and don't expect to have connection issues. + But there seem to be no good reason to try hard to get rid of existing working protocol even if it's a bit + cautious about connection quality. So please KEEP IN MIND THAT SOME COMMENTS REFERING TO NETWORK AND SOCKETS + CAN BE OUTDATED. + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// +// Provides a robust and secure transport session between a debugger and a debuggee that are potentially on +// different machines. +// +// The following terminology is used for the wire protocol. The smallest meaningful entity written to or read +// from the connection is a "message". This consists of one or maybe two "blocks" where a block is a +// contiguous region of memory in the host machine. The first block is always a "message header" which is +// fixed size (allowing the receiver to know how many bytes to read off the stream oriented connection) and +// has type codes and other fields which the receiver can use to determine if another block is part of the +// message (and if so, exactly how large that block is). Many management messages consist only of a message +// header block, while operations such as sending a debugger event structure involve a message header followed +// by a block containing the actual event structure. +// +// Message acknowledgement (sometimes abbreviated to ack) refers to a system of marking all messages with an +// ID and noting and reporting which IDs we've seen from our peer. We piggy back the highest seen ID on all +// outgoing messages and this is used by the infrastructure to communicate the fact that a sender can release +// its copy of an outbound message since it successfully made it across the communications channel and won't +// need to be resent in the case of a network failure. +// +// This file uses the debugger conventions for naming the two endpoints of the session: the left side or LS is +// the side with the runtime while the right side (RS) is the side with the debugger. +// + +// The structure of this file necessitates a certain number of forward references (particularly in the +// comments). If you see a term you don't understand please do a search for it further down the file, where +// hopefully you will find a detailed definition (and if not, please add one). + +struct DebuggerIPCEvent; +struct DbgEventBufferEntry; + +// Some simple ad-hoc debug only transport logging. This output is too chatty for an exisitng CLR logging +// channel (and we've run out of bits for an additional channel) and is likely to be of limited use to anyone +// besides the transport developer (and even then only occasionally). +// +// To enable use 'set|export COMPlus_DbgTransportLog=X' where X is 1 for RS logging, 2 for LS logging and 3 +// for both (default is disabled). Use 'set|export COMPlus_DbgTransportLogClass=X' where X is the hex +// representation of one or more DbgTransportLogClass flags defined below (default is all classes enabled). +// For instance, 'set COMPlus_DbgTransportLogClass=f' will enable only message send and receive logging (for +// all message types). +enum DbgTransportLogEnable +{ + LE_None = 0x00000000, + LE_LeftSide = 0x00000001, + LE_RightSide = 0x00000002, + LE_Unknown = 0xffffffff, +}; + +enum DbgTransportLogClass +{ + LC_None = 0x00000000, + LC_Events = 0x00000001, // Sending and receiving debugger events + LC_Session = 0x00000002, // Sending and receiving session messages + LC_Requests = 0x00000004, // Sending requests such as MT_GetDCB and receiving replies + LC_EventAcks = 0x00000008, // Sending and receiving debugger event acks (DEPRECATED) + LC_NetErrors = 0x00000010, // Network errors + LC_FaultInject = 0x00000020, // Artificially injected network faults + LC_Proxy = 0x00000040, // Proxy interactions + LC_All = 0xffffffff, + LC_Always = 0xffffffff, // Always log, regardless of class setting +}; + +// Status codes that can be returned by various APIs that indicate some conditions of the error that a caller +// might usefully pass on to a user (environmental factors that the user might have some control over). +enum ConnStatus +{ + SCS_Success, // The request succeeded + SCS_OutOfMemory, // The request failed due to a low memory situation + SCS_InvalidConfiguration, // Initialize() failed because the debugger settings were not configured or + // have become corrupt + SCS_UnknownTarget, // Connect() failed because the remote machine at the given address could not + // be found + SCS_NoListener, // Connect() failed because the remote machine was not listening for requests + // on the given port (most likely the remote machine is not configured for + // debugging) + SCS_NetworkFailure, // Connect() failed due to miscellaneous network error + SCS_MismatchedCerts, // Connect()/Accept() failed because the remote party was using a different + // cert +}; + + +// Multiple clients can use a single DbgTransportSession, but only one can act as the debugger. +// A valid DebugTicket is given to the client who is acting as the debugger. +struct DebugTicket +{ +friend class DbgTransportSession; + +public: + DebugTicket() { m_fValid = false; }; + + bool IsValid() { return m_fValid; }; + +protected: + void SetValid() { m_fValid = true; }; + void SetInvalid() { m_fValid = false; }; + +private: + // Tickets can't be copied around. Hide these definitions so as to enforce that. + // We still need the Copy ctor so that it can be passed in as a parameter. + void operator=(DebugTicket & other); + + bool m_fValid; +}; + +#ifdef RIGHT_SIDE_COMPILE +#define DBG_TRANSPORT_LOG_THIS_SIDE LE_RightSide +#define DBG_TRANSPORT_LOG_PREFIX "RS" +#else // RIGHT_SIDE_COMPILE +#define DBG_TRANSPORT_LOG_THIS_SIDE LE_LeftSide +#define DBG_TRANSPORT_LOG_PREFIX "LS" +#endif // RIGHT_SIDE_COMPILE + +// Method used to log an interesting event (of the given class). The message given will have any additional +// arguments inserted following 'printf' formatiing conventions and will be automatically prepended with a +// LS/RS indicator and suffixed with a newline. +inline void DbgTransportLog(DbgTransportLogClass eClass, const char *szFormat, ...) +{ +#ifdef _DEBUG + static DWORD s_dwLoggingEnabled = LE_Unknown; + static DWORD s_dwLoggingClass = LC_All; + + if (s_dwLoggingEnabled == LE_Unknown) + { + s_dwLoggingEnabled = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLog, LE_None); + s_dwLoggingClass = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_DbgTransportLogClass, LC_All); + } + + if ((s_dwLoggingEnabled & DBG_TRANSPORT_LOG_THIS_SIDE) && + ((s_dwLoggingClass & eClass) || eClass == LC_Always)) + { + char szOutput[256]; + va_list args; + + va_start(args, szFormat); + vsprintf_s(szOutput, sizeof(szOutput), szFormat, args); + va_end(args); + + printf("%s %04x: %s\n", DBG_TRANSPORT_LOG_PREFIX, GetCurrentThreadId(), szOutput); + fflush(stdout); + + char szDebugOutput[512]; + sprintf_s(szDebugOutput, sizeof(szDebugOutput), "%s: %s\n", DBG_TRANSPORT_LOG_PREFIX, szOutput); + OutputDebugStringA(szDebugOutput); + } +#endif // _DEBUG +} + +#ifdef _DEBUG +// +// Debug-only network fault injection (in order to help test the robust session code). Control is via a single +// DWORD read from the environment (COMPlus_DbgTransportFaultInject). This DWORD is treated as a set of bit +// fields as follows: +// +// +-------+-------+-------+----------------+-----------+ +// | Side | Op | State | Reserved | Frequency | +// +-------+-------+-------+----------------+-----------+ +// 31<->28 27<->24 23<->20 19<----------->8 7<------->0 +// +// The 'Side' field indicates whether the left or right side (or both) should have faults injected. See +// DbgTransportFaultSide below for values. +// +// The 'Op' field indicates which connection methods should simulate failures. See DbgTransportFaultOp. +// +// The 'State' field indicates the session states in which faults will be injected. See +// DbgTransportFaultState. Note that introducing too many failures into the Opening and Opening_NC states will +// cause the debugger to timeout and fail. +// +// The 'Reserved' field has no current meaning and should be left as zero. +// +// The 'Frequency' field indicates a percentage failure rate. Valid values are between 0 and 99, values beyond +// this range will be clamped to 99. +// +// For example: +// +// export COMPlus_DbgTransportFaultInject=1ff00001 +// --> Fail all network operations on the left side 1% of the time +// +// export COMPlus_DbgTransportFaultInject=34200063 +// --> Fail Send() calls on both sides while the session is Open 99% of the time +// + +#define DBG_TRANSPORT_FAULT_RATE_MASK 0x000000ff + +// Whether to inject faults to the left, right or both sides. +enum DbgTransportFaultSide +{ + FS_Left = 0x10000000, + FS_Right = 0x20000000, +}; + +// Network operations which are candiates for fault injection. +enum DbgTransportFaultOp +{ + FO_Connect = 0x01000000, + FO_Accept = 0x02000000, + FO_Send = 0x04000000, + FO_Receive = 0x08000000, +}; + +// Session states into which faults should be injected. +enum DbgTransportFaultState +{ + FS_Opening = 0x00100000, // Opening and Opening_NC + FS_Open = 0x00200000, + FS_Resync = 0x00400000, // Resync and Resync_NC +}; + +#ifdef RIGHT_SIDE_COMPILE +#define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Right +#else // RIGHT_SIDE_COMPILE +#define DBG_TRANSPORT_FAULT_THIS_SIDE FS_Left +#endif // RIGHT_SIDE_COMPILE + +// Macro to determine whether a fault should be injected for the given operation. +#define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) DbgTransportShouldInjectFault(FO_##_op, #_op) + +#else // _DEBUG +#define DBG_TRANSPORT_SHOULD_INJECT_FAULT(_op) false +#endif // _DEBUG + +// The PAL doesn't define htons (host-to-network-short) and friends. So provide our own versions here. +// winsock2.h defines BIGENDIAN to 0x0000 and LITTLEENDIAN to 0x0001, so we need to be careful with the +// #ifdef. +#if BIGENDIAN > 0 +#define DBGIPC_HTONS(x) (x) +#define DBGIPC_NTOHS(x) (x) +#define DBGIPC_HTONL(x) (x) +#define DBGIPC_NTOHL(x) (x) +#else +inline UINT16 DBGIPC_HTONS(UINT16 x) +{ + return (x >> 8) | (x << 8); +} +#define DBGIPC_NTOHS(x) DBGIPC_HTONS(x) +inline UINT32 DBGIPC_HTONL(UINT32 x) +{ + return (x >> 24) | + ((x >> 8) & 0x0000FF00L) | + ((x & 0x0000FF00L) << 8) | + (x << 24); +} +#define DBGIPC_NTOHL(x) DBGIPC_HTONL(x) + +#endif + +// Lock abstraction (we can't use the same lock implementation on LS and RS since we really want a Crst on the +// LS and this isn't available in the RS environment). +class DbgTransportLock +{ +public: + void Init(); + void Destroy(); + void Enter(); + void Leave(); + +private: +#ifdef RIGHT_SIDE_COMPILE + CRITICAL_SECTION m_sLock; +#else // RIGHT_SIDE_COMPILE + CrstExplicitInit m_sLock; +#endif // RIGHT_SIDE_COMPILE +}; + +// The transport has only one queue for IPC events, but each IPC event can be marked as one of two types. +// The transport will signal the handle corresponding to the type of each IPC event. (See +// code:DbgTransportSession::GetIPCEventReadyEvent and code:DbgTransportSession::GetDebugEventReadyEvent.) +// This is effectively a basic multiplexing scheme. The old-style IPC event are for all RS-to-LS IPC events +// and for all LS-to-RS replies. The other type is for LS-to-RS IPC events transported over the native +// pipeline. For more information, see the comments for the interface code:IEventChannel. +enum IPCEventType +{ + IPCET_OldStyle, + IPCET_DebugEvent, + IPCET_Max, +}; + +// The class that encapsulates all the state for a single session on either the right or left side. The left +// side supports only one instance of this class for a given runtime. The right side can support several (all +// connected to different LS instances of course). +class DbgTransportSession +{ +public: + // No real work done in the constructor. Use Init() instead. + DbgTransportSession(); + + // Cleanup what is allocated/created in Init() + ~DbgTransportSession(); + + // Allocates initial resources (including starting the transport thread). The session will start in the + // SS_Opening state. That is, the RS will immediately start trying to Connect() a connection while the + // LS will perform an Accept() to wait for a connection request. The RS needs an IP address and port + // number to initiate connections. These should be given in host byte order. The LS, on the other hand, + // requires the addresses of a couple of runtime data structures to service certain debugger requests that + // may be delivered once the session is established. +#ifdef RIGHT_SIDE_COMPILE + HRESULT Init(DWORD pid, HANDLE hProcessExited); +#else + HRESULT Init(DebuggerIPCControlBlock * pDCB, AppDomainEnumerationIPCBlock * pADB); +#endif // RIGHT_SIDE_COMPILE + + // Drive the session to the SS_Closed state, which will deallocate all remaining transport resources + // (including terminating the transport thread). If this is the RS and the session state is SS_Open at the + // time of this call a graceful disconnect will be attempted (which tells the LS to go back to SS_Opening + // to look for a new RS rather than interpreting the disconnection as a temporary error and going into + // SS_Resync). On either side the session will no longer be functional after this call returns (though + // Init() may be called again to start over from the beginning). + void Shutdown(); + +#ifdef RIGHT_SIDE_COMPILE + // Used by debugger side (RS) to cleanup the target (LS) named pipes + // and semaphores when the debugger detects the debuggee process exited. + void CleanupTargetProcess(); +#else + // Cleans up the named pipe connection so no tmp files are left behind. Does only + // the minimum and must be safe to call at any time. Called during PAL ExitProcess, + // TerminateProcess and for unhandled native exceptions and asserts. + void AbortConnection(); +#endif // RIGHT_SIDE_COMPILE + + LONG AddRef() + { + LONG ref = InterlockedIncrement(&m_ref); + return ref; + } + + LONG Release() + { + _ASSERTE(m_ref > 0); + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; + } + +#ifndef RIGHT_SIDE_COMPILE + // API used only by the LS to drive the transport into a state where it won't accept connections. This is + // used when no proxy is detected at startup but it's too late to shutdown all of the debugging system + // easily. It's mainly paranoia to increase the protection of your system when the proxy isn't started. + void Neuter(); +#endif // !RIGHT_SIDE_COMPILE + +#ifdef RIGHT_SIDE_COMPILE + // On the RS it may be useful to wait and see if the session can reach the SS_Open state. If the target + // runtime has terminated for some reason then we'll never reach the open state. So the method below gives + // the RS a way to try and establish a connection for a reasonable amount of time and to time out + // otherwise. They could then call Shutdown on the session and report an error back to the rest of the + // debugger. The method returns true if the session opened within the time given (in milliseconds) and + // false otherwise. + bool WaitForSessionToOpen(DWORD dwTimeout); + + // A valid ticket is returned if no other client is currently acting as the debugger. + bool UseAsDebugger(DebugTicket * pTicket); + + // A valid ticket is required in order for this function to succeed. After this function succeeds, + // another client can request to be the debugger. + bool StopUsingAsDebugger(DebugTicket * pTicket); +#endif // RIGHT_SIDE_COMPILE + + // Sends a pre-initialized event to the other side. + HRESULT SendEvent(DebuggerIPCEvent * pEvent); + HRESULT SendDebugEvent(DebuggerIPCEvent * pEvent); + + // Retrieves the auto-reset handle which is signalled by the session each time a new event is received + // from the other side. + HANDLE GetIPCEventReadyEvent(); + HANDLE GetDebugEventReadyEvent(); + + // Copies the last event received from the other side into the provided buffer. This should only be called + // (once) after the event returned from GetIPCEventReadyEvent()/GetDebugEventReadyEvent() has been signalled. + void GetNextEvent(DebuggerIPCEvent *pEvent, DWORD cbEvent); + +#ifdef RIGHT_SIDE_COMPILE + // Read and write memory on the LS from the RS. + HRESULT ReadMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer); + HRESULT WriteMemory(PBYTE pbRemoteAddress, PBYTE pbBuffer, SIZE_T cbBuffer); + HRESULT VirtualUnwind(DWORD threadId, ULONG32 contextSize, PBYTE context); + + // Read and write the debugger control block on the LS from the RS. + HRESULT GetDCB(DebuggerIPCControlBlock *pDCB); + HRESULT SetDCB(DebuggerIPCControlBlock *pDCB); + + // Read the AppDomain control block on the LS from the RS. + HRESULT GetAppDomainCB(AppDomainEnumerationIPCBlock *pADB); + +#endif // RIGHT_SIDE_COMPILE + +private: + + // Highest protocol version supported by this side of the session. See the + // m_dwMajorVersion/m_dwMinorVersion fields for a detailed explanation and the actual version being used + // by the session (if it is formed). + static const DWORD kCurrentMajorVersion = 2; + static const DWORD kCurrentMinorVersion = 0; + + // Session states. These determine which action is taken on a SendMessage (message is sent, queued or an + // error is raised) and which incoming messages are valid. + enum SessionState + { + SS_Closed, // No session and no attempt is being made to form one + SS_Opening_NC, // Session is being formed but no connection is established yet + SS_Opening, // Session is being formed, the low level connection is in place + SS_Open, // Session is fully formed and normal transport messages can be sent and received + SS_Resync_NC, // A low level connection error is occurred and we're attempting to re-form the link + SS_Resync, // We're trying to resynchronize high level state over the new connection + }; + + // Types of messages that can be sent over the transport connection. + enum MessageType + { + // Session management operations. These must come first and MT_SessionClose must be last in the group. + MT_SessionRequest, // RS -> LS : Request a new session be formed (optionally pass encrypted data key) + MT_SessionAccept, // LS -> RS : Accept new session + MT_SessionReject, // LS -> RS : Reject new session, give reason + MT_SessionResync, // RS <-> LS : Resync broken connection by informing other side which messages must be resent + MT_SessionClose, // RS -> LS : Gracefully terminate a session + + // Debugger events. + MT_Event, // RS <-> LS : A debugger event is being sent as the data block of the message + + // Misc management operations. + MT_ReadMemory, // RS <-> LS : RS wants to read LS memory block (or LS is replying to such a request) + MT_WriteMemory, // RS <-> LS : RS wants to write LS memory block (or LS is replying to such a request) + MT_VirtualUnwind, // RS <-> LS : RS wants to LS unwind a stack frame (or LS is replying to such a request) + MT_GetDCB, // RS <-> LS : RS wants to read LS DCB (or LS is replying to such a request) + MT_SetDCB, // RS <-> LS : RS wants to write LS DCB (or LS is replying to such a request) + MT_GetAppDomainCB, // RS <-> LS : RS wants to read LS AppDomainCB (or LS is replying to such a request) + }; + + // Reasons the LS can give for rejecting a session. These codes should *not* be changed other than by + // adding reasons to keep versioning possible. + enum RejectReason + { + RR_IncompatibleVersion, // LS doesn't support the major version asked for in the request. + RR_AlreadyAttached, // LS already has another session open (LS only supports one session at a time) + }; + + // Struct that defines the format of a message header block sent on the connection. Note that the size of + // this structure and the location/size of the m_eType field must *never* change to allow our versioning + // protocol to work properly (in particular any LS must be able to interpret at least the type and version + // number of an MT_SessionRequest and reply with a MT_SessionReject that any RS can interpret the type and + // version of). To help with this there is a padding field at the end for future expansion (this should be + // initialized to zero and not accessed in any other manner). + struct MessageHeader + { + Portable<MessageType> m_eType; // Type of message this is + Portable<DWORD> m_cbDataBlock; // Size of data block that immediately follows this header (can be zero) + Portable<DWORD> m_dwId; // Message ID assigned by the sender of this message + Portable<DWORD> m_dwReplyId; // Message ID that this is a reply to (used by messages such as MT_GetDCB) + Portable<DWORD> m_dwLastSeenId; // Message ID last seen by sender (receiver can discard up to here from send queue) + Portable<DWORD> m_dwReserved; // Reserved for future expansion (must be initialized to zero and + // never read) + + // The rest of the header varies depending on the message type (keep the maximum size of this union + // small since all messages will pay the overhead, large message type specific data should go in the + // following data block). + union + { + // Used by MT_SessionRequest / MT_SessionAccept. + struct + { + Portable<DWORD> m_dwMajorVersion; // Protocol version requested/accepted + Portable<DWORD> m_dwMinorVersion; + } VersionInfo; + + // Used by MT_SessionReject. + struct + { + Portable<RejectReason> m_eReason; // Reason for rejection. + Portable<DWORD> m_dwMajorVersion; // Highest protocol version the LS supports + Portable<DWORD> m_dwMinorVersion; + } SessionReject; + + // Used by MT_ReadMemory and MT_WriteMemory. + struct + { + Portable<PBYTE> m_pbLeftSideBuffer; // Address of memory to read/write on the LS + Portable<DWORD> m_cbLeftSideBuffer; // Size in bytes of memory to read/write + Portable<HRESULT> m_hrResult; // Result from LS (access can fail due to unmapped memory etc.) + } MemoryAccess; + + // Used by MT_Event. + struct + { + Portable<IPCEventType> m_eIPCEventType; // multiplexing type of this IPC event + Portable<DWORD> m_eType; // Event type (useful for debugging) + } Event; + + } TypeSpecificData; + + BYTE m_sMustBeZero[8]; // Set this to zero when initializing and never read the contents + }; + + // Struct defining the format of the data block sent with a SessionRequest. + struct SessionRequestData + { + GUID m_sSessionID; // Unique session ID. Treated as byte blob so no endian-ness + }; + + // Struct used to track a message that is being (or will soon be) sent but has not yet been acknowledged. + // These are usually found queued on the send queue. + struct Message + { + Message *m_pNext; // Next message in the queue + MessageHeader m_sHeader; // Inline message header + PBYTE m_pbDataBlock; // Pointer to optional message data block (or NULL) + DWORD m_cbDataBlock; // Count of bytes in above block if it's non-NULL + HANDLE m_hReplyEvent; // Optional event to signal if this message is replied to (or NULL) + PBYTE m_pbReplyBlock; // Optional buffer to place data block from reply into (or NULL) + DWORD m_cbReplyBlock; // Size in bytes of the above buffer if it is non-NULL + Message *m_pOrigMessage; // Used when we need to find the original message from a copy + bool m_fAborted; // True if this send was aborted due to session shutdown + + // Common initialization for messages. + void Init(MessageType eType, + PBYTE pbBufferIn = NULL, + DWORD cbBufferIn = 0, + PBYTE pbBufferOut = NULL, + DWORD cbBufferOut = 0) + { + memset(this, 0, sizeof(*this)); + m_sHeader.m_eType = eType; + m_sHeader.m_cbDataBlock = cbBufferIn; + m_pbDataBlock = pbBufferIn; + m_cbDataBlock = cbBufferIn; + m_pbReplyBlock = pbBufferOut; + m_cbReplyBlock = cbBufferOut; + } + }; + + // Holder class used to take a transport lock in a given scope and automatically release it once that + // scope is exited. + class TransportLockHolder + { + public: + TransportLockHolder(DbgTransportLock *pLock) + { + m_pLock = pLock; + m_pLock->Enter(); + } + + ~TransportLockHolder() + { + m_pLock->Leave(); + } + + private: + DbgTransportLock *m_pLock; + }; + +#ifdef _DEBUG + // Store statistics for various session activities that will be useful for performance analysis and tracking + // down bugs. + struct DbgStats + { + // Message type counts for sends. + LONG m_cSentSessionRequest; + LONG m_cSentSessionAccept; + LONG m_cSentSessionReject; + LONG m_cSentSessionResync; + LONG m_cSentSessionClose; + LONG m_cSentEvent; + LONG m_cSentReadMemory; + LONG m_cSentWriteMemory; + LONG m_cSentVirtualUnwind; + LONG m_cSentGetDCB; + LONG m_cSentSetDCB; + LONG m_cSentGetAppDomainCB; + LONG m_cSentDDMessage; + + // Message type counts for receives. + LONG m_cReceivedSessionRequest; + LONG m_cReceivedSessionAccept; + LONG m_cReceivedSessionReject; + LONG m_cReceivedSessionResync; + LONG m_cReceivedSessionClose; + LONG m_cReceivedEvent; + LONG m_cReceivedReadMemory; + LONG m_cReceivedWriteMemory; + LONG m_cReceivedVirtualUnwind; + LONG m_cReceivedGetDCB; + LONG m_cReceivedSetDCB; + LONG m_cReceivedGetAppDomainCB; + LONG m_cReceivedDDMessage; + + // Low level block counts. + LONG m_cSentBlocks; + LONG m_cReceivedBlocks; + + // Byte count summaries. + LONGLONG m_cbSentBytes; + LONGLONG m_cbReceivedBytes; + + // Errors and recovery + LONG m_cSendErrors; + LONG m_cReceiveErrors; + LONG m_cMiscErrors; + LONG m_cConnections; + LONG m_cResends; + + // Session counts. + LONG m_cSessions; + }; + + DbgStats m_sStats; + + // Macros to update the statistics. The increment version is thread safe, but the add version is assumed to be + // externally serialized since the 64-bit Interlocked operations are not available on all platforms and these + // stats are used for send and receive byte counts which are updated at locations that are serialized anyway. +#define DBG_TRANSPORT_INC_STAT(_name) InterlockedIncrement(&m_sStats.m_c##_name) +#define DBG_TRANSPORT_ADD_STAT(_name, _amount) m_sStats.m_cb##_name += (_amount) + +#else // _DEBUG + +#define DBG_TRANSPORT_INC_STAT(_name) +#define DBG_TRANSPORT_ADD_STAT(_name, _amount) + +#endif // _DEBUG + + // Reference count + LONG m_ref; + + // Some flags used to record how far we got in Init() (used for cleanup in Shutdown()). + bool m_fInitStateLock; +#ifndef RIGHT_SIDE_COMPILE + bool m_fInitWSA; +#endif // !RIGHT_SIDE_COMPILE + + // Protocol version. This consists of two parts. The major version is incremented on incompatible protocol + // updates. That is, a session between left and right sides that cannot use a protocol with the exact same + // major version cannot be formed. The minor version number is incremented on compatible protocol updates. + // These are usually associated with optional extensions to the protocol (e.g. a V1.2 endpoint might set + // previously unused fields in a message header to indicate some optional hint about the message that a + // V1.1 client won't notice at all). + // + // The right side has a hard-coded version number it sends in the SessionRequest message. The left side + // must support the same major version or reply with a SessionReject message containing the highest + // version it does support. For this reason the format of a SessionReject message can never change at all. + // On a SessionAccept the left side sends back the version number and can choose to lower the minor + // version to the highest it knows about. This gives the right side a hint as to the capabilities of the + // left side (though it must be prepared to interact with a left side with any minor version number). + // + // If necessary (and the SessionReject message sent by an incompatible left side indicates a major version + // the right side can also support), the right side can re-attempt a SessionRequest with a lower major + // version. + DWORD m_dwMajorVersion; + DWORD m_dwMinorVersion; + + // Session ID randomly allocated by the right side and sent over in the SessionRequest message. This + // serves to disambiguate a re-send of the SessionRequest due to a network error versus a SessionRequest + // from a different debugger. + GUID m_sSessionID; + + // Lock used to synchronize sending messages and updating the session state. This ensures message bytes + // don't become interleaved on the transport connection, the send queue is updated consistently across + // multiple threads and that we never attempt to use a connection that is being deallocated on another + // thread due to a state change. Receives don't need this since they're performed only on the transport + // thread (which is also the only thread allowed to deallocate the connection). + DbgTransportLock m_sStateLock; + + // Queue of messages that have been sent over the connection but not acknowledged yet or are waiting to be + // sent (because another message is using the connection or we're in a SessionResync state). You must hold + // m_sStateLock in order to access this queue. + Message *m_pSendQueueFirst; + Message *m_pSendQueueLast; + + // Message IDs. These are monotonically increasing numbers starting from 0 that are used to stamp each + // non-session management message sent on this session. If a low-level network error occurs and we must + // abandon and re-form the underlying transport connection the left and right sides send SessionResync + // messages with the ID of the last message they received (and processed). This allows us to determine + // which messages we still have in our send queue must be re-sent over the new transport connection. + // Allocate a new message ID by post incrementing m_dwNextMessageId under the state lock. + DWORD m_dwNextMessageId; // Next ID we'll give to a message we're sending + DWORD m_dwLastMessageIdSeen; // Last ID we saw in an incoming, fully received message + + // The current session state. This is updated atomically under m_sStateLock. + SessionState m_eState; + +#ifdef RIGHT_SIDE_COMPILE + // Manual reset event that is signalled whenever the session state is SS_Open or SS_Closed (after waiting + // on this event the caller should check to see which state it was). + HANDLE m_hSessionOpenEvent; +#endif // RIGHT_SIDE_COMPILE + + // Thread responsible for initial Connect()/Accept() on a low level transport connection and + // subsequently for all message reception on that connection. Any error will cause the thread to reset + // back into the Connect()/Accept() phase (along with the resulting session state change). + HANDLE m_hTransportThread; + + TwoWayPipe m_pipe; + +#ifdef RIGHT_SIDE_COMPILE + // On the RS the transport thread needs to know the IP address and port number to Connect() to. + DWORD m_pid; // Id of a process we're talking to. + + HANDLE m_hProcessExited; // event which will be signaled when the debuggee is terminated + + bool m_fDebuggerAttached; +#endif + + // Debugger event handling. To improve performance we allow the debugger to send as many events as it + // likes without acknowledgement from its peer. While not strictly adhering to the semantic provided by + // the shared memory buffer transport (where the buffer could not be written again until the receiver had + // explicitly released it) it turns out that no debugging code relies on this. In particular, the most + // common scenario where this makes sense is the left side sending large scale update events (such as the + // groups of appdomain create, module load etc. events sent during an attach). Here the right hand side + // queues the events for later processing and releases the buffers right away. + // We gain performance since its no longer necessary to send (or wait on) event acknowledgment messages. + // This lowers both network bandwidth and latency (especially when one side is trying to send a continuous + // stream of events). + // From the transport standpoint this design mainly impacts event receipt. We maintain a dynamically sized + // pool of event receipt buffers (the size is determined by the maximum number of unread events we've seen + // at any one time). The buffer is a circular array: clients read from the buffer at head index which is + // followed by some number of valid buffers (wrapping around to the start of the array if necessary). New + // events are added after these (and grow the array if the tail would touch the head otherwise). + DbgEventBufferEntry * m_pEventBuffers; // Pointer to array of incoming debugger events + DWORD m_cEventBuffers; // Size of the array above (in events) + DWORD m_cValidEventBuffers; // Number of events that actually contain data + DWORD m_idxEventBufferHead; // Index of the first valid event + DWORD m_idxEventBufferTail; // Index of the first invalid event + HANDLE m_rghEventReadyEvent[IPCET_Max]; // The event signalled when a new event arrives + +#ifndef RIGHT_SIDE_COMPILE + // The LS requires the addresses of a couple of runtime data structures in order to service MT_GetDCB etc. + // These are provided by the runtime at intialization time. + DebuggerIPCControlBlock *m_pDCB; + AppDomainEnumerationIPCBlock *m_pADB; +#endif // !RIGHT_SIDE_COMPILE + + HRESULT SendEventWorker(DebuggerIPCEvent * pEvent, IPCEventType type); + + // Sends a pre-formatted message (including the data block, if any). The fWaitsForReply indicates whether + // the caller is going to block until some sort of reply message is received (for instance an event that + // must be ack'd or a request such as MT_GetDCB that needs a reply). SendMessage() uses this to determine + // whether it needs to buffer the message before placing it on the send queue (since it may need to resend + // the message after a transitory network failure). + HRESULT SendMessage(Message *pMessage, bool fWaitsForReply); + + // Helper method for sending messages requiring a reply (such as MT_GetDCB) and waiting on the result. + HRESULT SendRequestMessageAndWait(Message *pMessage); + + // Sends a single contiguous buffer of host memory over the connection. The caller is responsible for + // holding the state lock and ensuring the session state is SS_Open. Returns false if the send failed (the + // error will have already caused the recovery logic to kick in, so handling it is not required, the + // boolean is just returned so that any further blocks in the message are not sent). + bool SendBlock(PBYTE pbBuffer, DWORD cbBuffer); + + // Receives a single contiguous buffer of host memory over the connection. No state lock needs to be + // held (receives are serialized by the fact they're only performed on the transport thread). Returns + // false if a network error is encountered (which will automatically transition the session into the + // correct retry state). + bool ReceiveBlock(PBYTE pbBuffer, DWORD cbBuffer); + + // Called upon encountering a network error (e.g. an error from Send() or Receive()). This handles pushing + // the session state into SS_Resync_NC in order to start the recovery process. + void HandleNetworkError(bool fCallerHoldsStateLock); + + // Scan the send queue and discard any messages which have been processed by the other side according to + // the specified ID). Messages waiting on a reply message (e.g. MT_GetDCB) will be retained until that + // reply is processed. FlushSendQueue will take the state lock. + void FlushSendQueue(DWORD dwLastProcessedId); + +#ifdef RIGHT_SIDE_COMPILE + // Perform processing required to complete a request (such as MT_GetDCB) once a reply comes in. This + // includes reading data from the connection into the output buffer, removing the original message from + // the send queue and signalling the completion event. Returns true if no network error was encountered. + bool ProcessReply(MessageHeader *pHeader); + + // Upon receiving a reply message, signal the event on the message to wake up the thread waiting for + // the reply message and close the handle to the event. + void SignalReplyEvent(Message * pMesssage); + + // Given a message ID, find the matching message in the send queue. If there is no match, return NULL. + // If there is a match, remove the message from the send queue and return it. + Message * RemoveMessageFromSendQueue(DWORD dwMessageId); +#endif + +#ifndef RIGHT_SIDE_COMPILE + // Check read and optionally write memory access to the specified range of bytes. Used to check + // ReadProcessMemory and WriteProcessMemory requests. + HRESULT CheckBufferAccess(PBYTE pbBuffer, DWORD cbBuffer, bool fWriteAccess); +#endif // !RIGHT_SIDE_COMPILE + + // Initialize all session state to correct starting values. Used during Init() and on the LS when we + // gracefully close one session and prepare for another. + void InitSessionState(); + + // The entry point of the transport worker thread. This one's static, so we immediately dispatch to an + // instance method version defined below for convenience in the implementation. + static DWORD WINAPI TransportWorkerStatic(LPVOID pvContext); + void TransportWorker(); + + // Given a fully initialized debugger event structure, return the size of the structure in bytes (this is + // not trivial since DebuggerIPCEvent contains a large union member which can cause the portion containing + // significant data to vary wildy from event to event). + DWORD GetEventSize(DebuggerIPCEvent *pEvent); + +#ifdef _DEBUG + // Debug helper which returns the name associated with a MessageType. + const char *MessageName(MessageType eType); + + // Debug logging helper which logs an incoming message of any type (as long as logging for that message + // class is currently enabled). + void DbgTransportLogMessageReceived(MessageHeader *pHeader); + + // Helper method used by the DBG_TRANSPORT_SHOULD_INJECT_FAULT macro. + bool DbgTransportShouldInjectFault(DbgTransportFaultOp eOp, const char *szOpName); +#else // _DEBUG +#define DbgTransportLogMessageReceived(x) +#endif // _DEBUG +}; + +#ifndef RIGHT_SIDE_COMPILE +// The one and only transport instance for the left side. Allocated and initialized during EE startup (from +// Debugger::Startup() in debugger.cpp). +extern DbgTransportSession *g_pDbgTransport; +#endif // !RIGHT_SIDE_COMPILE + +#define DBG_GET_LAST_WSA_ERROR() WSAGetLastError() + +#endif // defined(FEATURE_DBGIPC_TRANSPORT_VM) || defined(FEATURE_DBGIPC_TRANSPORT_DI) + +#endif // __DBG_TRANSPORT_SESSION_INCLUDED diff --git a/src/debug/inc/dbgutil.h b/src/debug/inc/dbgutil.h new file mode 100644 index 0000000000..8dae6d32ab --- /dev/null +++ b/src/debug/inc/dbgutil.h @@ -0,0 +1,93 @@ +// 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. +//***************************************************************************** +// dbgutil.h +// + +// +//***************************************************************************** + +#pragma once +#include <cor.h> +#include <cordebug.h> +#include <metahost.h> + +// +// Various common helpers used by multiple debug components. +// + +// Returns the RVA of the resource section for the module specified by the given data target and module base. +// Returns failure if the module doesn't have a resource section. +// +// Arguments +// pDataTarget - dataTarget for the process we are inspecting +// moduleBaseAddress - base address of a module we should inspect +// pwImageFileMachine - updated with the Machine from the IMAGE_FILE_HEADER +// pdwResourceSectionRVA - updated with the resultant RVA on success +HRESULT GetMachineAndResourceSectionRVA(ICorDebugDataTarget* pDataTarget, + ULONG64 moduleBaseAddress, + WORD* pwImageFileMachine, + DWORD* pdwResourceSectionRVA); + +HRESULT GetResourceRvaFromResourceSectionRva(ICorDebugDataTarget* pDataTarget, + ULONG64 moduleBaseAddress, + DWORD resourceSectionRva, + DWORD type, + DWORD name, + DWORD language, + DWORD* pResourceRva, + DWORD* pResourceSize); + +HRESULT GetResourceRvaFromResourceSectionRvaByName(ICorDebugDataTarget* pDataTarget, + ULONG64 moduleBaseAddress, + DWORD resourceSectionRva, + DWORD type, + LPCWSTR pwszName, + DWORD language, + DWORD* pResourceRva, + DWORD* pResourceSize); + +// Traverses down one level in the PE resource tree structure +// +// Arguments: +// pDataTarget - the data target for inspecting this process +// id - the id of the next node in the resource tree you want +// moduleBaseAddress - the base address of the module being inspected +// resourceDirectoryRVA - the base address of the beginning of the resource directory for this +// level of the tree +// pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry +// +// Returns: +// S_OK if succesful or an appropriate failing HRESULT +HRESULT GetNextLevelResourceEntryRVA(ICorDebugDataTarget* pDataTarget, + DWORD id, + ULONG64 moduleBaseAddress, + DWORD resourceDirectoryRVA, + DWORD* pNextLevelRVA); + +// Traverses down one level in the PE resource tree structure +// +// Arguments: +// pDataTarget - the data target for inspecting this process +// name - the name of the next node in the resource tree you want +// moduleBaseAddress - the base address of the module being inspected +// resourceDirectoryRVA - the base address of the beginning of the resource directory for this +// level of the tree +// resourceSectionRVA - the rva of the beginning of the resource section of the PE file +// pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry +// +// Returns: +// S_OK if succesful or an appropriate failing HRESULT +HRESULT GetNextLevelResourceEntryRVAByName(ICorDebugDataTarget* pDataTarget, + LPCWSTR pwzName, + ULONG64 moduleBaseAddress, + DWORD resourceDirectoryRva, + DWORD resourceSectionRva, + DWORD* pNextLevelRva); + +// A small wrapper that reads from the data target and throws on error +HRESULT ReadFromDataTarget(ICorDebugDataTarget* pDataTarget, + ULONG64 addr, + BYTE* pBuffer, + ULONG32 bytesToRead); diff --git a/src/debug/inc/ddmarshalutil.h b/src/debug/inc/ddmarshalutil.h new file mode 100644 index 0000000000..be24d4c7d5 --- /dev/null +++ b/src/debug/inc/ddmarshalutil.h @@ -0,0 +1,394 @@ +// 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. + +// DDMarshalUtil.cpp + + +#ifndef _DDMarshal_Util_h +#define _DDMarshal_Util_h + +#include "dacdbiinterface.h" +typedef IDacDbiInterface::StackWalkHandle StackWalkHandle; +typedef IDacDbiInterface::HeapWalkHandle HeapWalkHandle; +typedef IDacDbiInterface::IStringHolder IStringHolder; + +#include "stringcopyholder.h" + +// @dbgtodo Mac - cleanup the buffer classes here. (are there pre-existing classes we could use instead?) +// These ultimately get included in the signature for IDacDbiMarshalStub::DoRequest. +// Key is that this helps serialize remote sized structures like IStringHolder and DacDbiArrayList<T>. +class BaseBuffer +{ +public: + BaseBuffer() + { + m_idx = 0; + m_pBuffer = NULL; + m_size = 0; + } + +protected: + DWORD m_idx; + DWORD m_size; + BYTE * m_pBuffer; +}; + +class WriteBuffer : public BaseBuffer +{ +public: + friend class ReadBuffer; + + WriteBuffer() + { + // @dbgtodo Mac - do something smarter than always allocate 1000 bytes. + m_size = 1000; + m_pBuffer = new BYTE[m_size]; + } + ~WriteBuffer() + { + if (m_pBuffer != NULL) + { + delete [] m_pBuffer; + m_pBuffer = NULL; + } + } + + // Write to the buffer, and grow if needed. + void WriteBlob(const void * pData, DWORD cbLength) + { + _ASSERTE(m_pBuffer != NULL); + EnsureSize(cbLength); + memcpy(&m_pBuffer[m_idx], pData, cbLength); + m_idx += cbLength; + } + + void EnsureSize(DWORD cbLength) + { + DWORD cbSizeNeeded = m_idx + cbLength; + _ASSERTE(cbSizeNeeded <= 128000); // sanity checking... + while(cbSizeNeeded >= m_size) + { + DWORD cbNewSize = (m_size * 3) / 2; // grow by 1.5 + BYTE * pNewBuffer = new BYTE[cbNewSize]; + memcpy(pNewBuffer, m_pBuffer, m_idx); + + _ASSERTE(m_pBuffer != NULL); + delete [] m_pBuffer; + + m_size = cbNewSize; + m_pBuffer = pNewBuffer; + } + } + + void WriteString(const WCHAR * pString) + { + bool fIsNull = (pString == NULL ? true : false); + EnsureSize(sizeof(fIsNull)); + memcpy(&m_pBuffer[m_idx], &fIsNull, sizeof(fIsNull)); + m_idx += sizeof(fIsNull); + + if (!fIsNull) + { + _ASSERTE(pString != NULL); + DWORD len = (DWORD) wcslen(pString); + DWORD cbCopy = (len + 1) * sizeof(WCHAR); + + EnsureSize(cbCopy); + memcpy(&m_pBuffer[m_idx], pString, cbCopy); + m_idx += cbCopy; + } + } + + // Gets access to the raw buffer - does not transfer ownership of the memory + void GetRawPtr(PBYTE * ppBuffer, DWORD * pcbUsed) + { + *ppBuffer = m_pBuffer; + *pcbUsed = m_idx; + } +}; + +// Read-only stream access to memory blob. +class ReadBuffer : public BaseBuffer +{ +public: + ReadBuffer() + { + m_fDeleteOnClose = false; + } + ~ReadBuffer() + { + if (m_fDeleteOnClose) + { + delete [] m_pBuffer; + } + } + + // Create on existing stream + void Open(BYTE * pStream, DWORD cbLength) + { + _ASSERTE(m_pBuffer == NULL); + _ASSERTE(m_idx == 0); + m_pBuffer = pStream; + m_size = cbLength; + } + void OpenAndOwn(BYTE * pStream, DWORD cbLength) + { + Open(pStream, cbLength); + m_fDeleteOnClose = true; + } + + // Get a reader for the range written by a Writer + // This steal's the writer's buffer. The Writer object is dead after this. + void Open(WriteBuffer * pBuffer) + { + _ASSERTE(m_pBuffer == NULL); + _ASSERTE(m_idx == 0); + m_size = pBuffer->m_idx; + m_pBuffer = pBuffer->m_pBuffer; + + + pBuffer->m_pBuffer = NULL; + m_fDeleteOnClose = true; + } + + bool IsAtEnd() + { + return (m_idx == m_size); + } + void ReadBlob(void * pData, DWORD cbLength) + { + _ASSERTE(m_idx + cbLength <= m_size); + memcpy(pData, &m_pBuffer[m_idx], cbLength); + m_idx += cbLength; + } + DWORD Length() + { + return m_size; + } + + // Return a pointer to the string and mvoe the index. + const WCHAR * ReadString() + { + bool fIsNull = *reinterpret_cast<bool *>(&m_pBuffer[m_idx]); + m_idx += sizeof(fIsNull); + + if (fIsNull) + { + return NULL; + } + else + { + const WCHAR * pString = (WCHAR*) &m_pBuffer[m_idx]; + DWORD len = (DWORD) wcslen(pString); + m_idx += (len + 1) * sizeof(WCHAR); // skip past null + _ASSERTE(m_idx <= m_size); + return pString; + } + } + +protected: + bool m_fDeleteOnClose; +}; + + + +// +// Writers +// +template<class T> inline +void WriteToBuffer(WriteBuffer * p, T & data) +{ + p->WriteBlob(&data, sizeof(T)); +} + +inline +void WriteToBuffer(WriteBuffer * p, StackWalkHandle & h) +{ + p->WriteBlob(&h, sizeof(StackWalkHandle)); +} + +inline +void WriteCookie(WriteBuffer * p, BYTE cookie) +{ +#if _DEBUG + WriteToBuffer(p, cookie); +#endif +} + + +template<class T> inline +void WriteToBuffer(WriteBuffer * p, T * pData) +{ + p->WriteBlob(pData, sizeof(T)); +} + +inline +void WriteToBuffer(WriteBuffer * p, StringCopyHolder * pString) +{ + const WCHAR * pData = NULL; + if (pString->IsSet()) + { + pData = *pString; // gets raw data + } + p->WriteString(pData); + WriteCookie(p, 0x1F); +} + +template<class T> inline +void WriteToBuffer(WriteBuffer * p, DacDbiArrayList<T> * pList) +{ + _ASSERTE(pList != NULL); + WriteCookie(p, 0xCD); + + int count = pList->Count(); + WriteToBuffer(p, count); + + if (count == 0) return; + + // Write raw data. + for(int i = 0; i < count; i++) + { + const T * pElement = &((*pList)[i]); + WriteToBuffer(p, pElement); + } + WriteCookie(p, 0xAB); +} + +template<class T> inline +void WriteToBuffer(WriteBuffer * p, DacDbiArrayList<T> & list) +{ + WriteToBuffer(p, &list); +} + +inline +void WriteToBuffer(WriteBuffer * p, NativeVarData * pData) +{ + WriteCookie(p, 0xD1); + p->WriteBlob(pData, sizeof(NativeVarData)); + WriteToBuffer(p, pData->m_offsetInfo); +} + +inline +void WriteToBuffer(WriteBuffer * p, SequencePoints * pData) +{ + WriteCookie(p, 0xD2); + p->WriteBlob(pData, sizeof(SequencePoints)); + WriteToBuffer(p, pData->m_map); +} +inline +void WriteToBuffer(WriteBuffer * p, ClassInfo * pData) +{ + WriteCookie(p, 0xD3); + p->WriteBlob(pData, sizeof(ClassInfo)); + WriteToBuffer(p, pData->m_fieldList); +} + +//----------------------------------------------------------------------------- +// +// Readers +// +template<class T> inline +void ReadFromBuffer(ReadBuffer * p, T & data) +{ + p->ReadBlob(&data, sizeof(T)); +} + +inline +void ReadCookie(ReadBuffer * p, BYTE cookieExpected) +{ +#if _DEBUG + BYTE cookie; + ReadFromBuffer(p, cookie); + _ASSERTE(cookie = cookieExpected); +#endif +} + + +inline +void ReadFromBuffer(ReadBuffer * p, StackWalkHandle & h) +{ + p->ReadBlob(&h, sizeof(StackWalkHandle)); +} + +template<class T> inline +void ReadFromBuffer(ReadBuffer * p, T * pData) +{ + // Used to copy-back a By-ref / out parameter + p->ReadBlob(pData, sizeof(T)); +} + +inline +void ReadFromBuffer(ReadBuffer * p, IStringHolder * pString) +{ + const WCHAR *pData = p->ReadString(); + // AssignCopy() can handle a NULL string. + pString->AssignCopy(pData); + ReadCookie(p, 0x1F); +} + +template<class T> inline +void ReadFromBuffer(ReadBuffer * p, DacDbiArrayList<T> * pList) +{ + _ASSERTE(pList != NULL); + + ReadCookie(p, 0xCD); + + // Alloc() will attempt to free the old pointer. + // if this was blit copied, the pointer is trashed. So we need to safely clear that + // pointer to prepare it to be copied. + pList->PrepareForDeserialize(); + + int count; + ReadFromBuffer(p, count); + + pList->Alloc(count); + if (count == 0) + { + return; + } + + // Read raw data. + for(int i = 0; i < count; i++) + { + T * pElement = &((*pList)[i]); + ReadFromBuffer(p, pElement); + } + ReadCookie(p, 0xAB); +} + +template<class T> inline +void ReadFromBuffer(ReadBuffer * p, DacDbiArrayList<T> & list) +{ + ReadFromBuffer(p, &list); +} + +inline +void ReadFromBuffer(ReadBuffer * p, NativeVarData * pData) +{ + ReadCookie(p, 0xD1); + p->ReadBlob(pData, sizeof(NativeVarData)); + ReadFromBuffer(p, &pData->m_offsetInfo); +} + +inline +void ReadFromBuffer(ReadBuffer * p, SequencePoints * pData) +{ + ReadCookie(p, 0xD2); + p->ReadBlob(pData, sizeof(SequencePoints)); + ReadFromBuffer(p, &pData->m_map); +} + +inline +void ReadFromBuffer(ReadBuffer * p, ClassInfo * pData) +{ + ReadCookie(p, 0xD3); + p->ReadBlob(pData, sizeof(ClassInfo)); + ReadFromBuffer(p, &pData->m_fieldList); +} + + + + +#endif // _DDMarshal_Util_h + diff --git a/src/debug/inc/dump/.gitmirror b/src/debug/inc/dump/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/debug/inc/dump/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/debug/inc/dump/dumpcommon.h b/src/debug/inc/dump/dumpcommon.h new file mode 100644 index 0000000000..3e197ce29b --- /dev/null +++ b/src/debug/inc/dump/dumpcommon.h @@ -0,0 +1,108 @@ +// 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. + +#ifndef DEBUGGER_DUMPCOMMON_H +#define DEBUGGER_DUMPCOMMON_H + +#if defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE) + +// When debugging against minidumps, we frequently need to ignore errors +// due to the dump not having memory content. +// You should be VERY careful using these macros. Because our code does not +// distinguish target types, when you allow memory to be missing because a dump +// target may not have that memory content by-design you are also implicitly +// allowing that same data to be missing from a live debugging target. +// Also, be aware that these macros exist in code under vm\. You must be careful to +// only allow them to change execution for DAC and DBI. +// Be careful state is such that execution can continue if the target is missing +// memory. +// In general, there are two solutions to this problem: +// a) add the memory to all minidumps +// b) stop forcing the memory to always be present +// All decisions between a & b focus on cost. For a, cost is adding the memory & a complete +// path to locate it to the dump, both in terms of dump generation time and most +// especially in terms of dump size (we cannot make MiniDumpNormal many MB for trivial +// apps). +// For b, cost is that we lose some of our validation when we have to turn off asserts +// and other checks for targets that should always have the missing memory present +// because we have no concept of allowing it to be missing only from a dump. + +// This seemingly awkward try block starting tag is so that when the macro is used over +// multiple source lines we don't create a useless try/catch block. This is important +// when using the macros in vm\ code. +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY EX_TRY +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY \ + EX_CATCH \ + { \ + if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) ) \ + { \ + EX_RETHROW; \ + } \ + } \ + EX_END_CATCH(SwallowAllExceptions) + +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER EX_TRY +#define EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + EX_CATCH \ + { \ + if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) ) \ + { \ + EX_RETHROW; \ + } \ + else \ + +#define EX_TRY_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY EX_TRY +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY \ + EX_CATCH \ + { \ + if ((GET_EXCEPTION()->GetHR() != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_READVIRTUAL_FAILURE) && \ + (GET_EXCEPTION()->GetHR() != CORDBG_E_TARGET_INCONSISTENT)) \ + { \ + EX_RETHROW; \ + } \ + } \ + EX_END_CATCH(SwallowAllExceptions) + + +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + } \ + EX_END_CATCH(SwallowAllExceptions) + +// Only use this version for wrapping single source lines, or you'll make debugging +// painful. +#define ALLOW_DATATARGET_MISSING_MEMORY(sourceCode) \ + EX_TRY \ + { \ + sourceCode \ + } \ + EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY + +#define ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY(sourceCode) \ + EX_TRY \ + { \ + sourceCode \ + } \ + EX_END_CATCH_ALLOW_DATATARGET_MISSING_OR_INCONSISTENT_MEMORY + +#else +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY +#define EX_TRY_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + #error This macro is only intended for use in DAC code! +#define EX_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + #error This macro is only intended for use in DAC code! +#define EX_END_CATCH_ALLOW_DATATARGET_MISSING_MEMORY_WITH_HANDLER \ + #error This macro is only intended for use in DAC code! + + +#define ALLOW_DATATARGET_MISSING_MEMORY(sourceCode) \ + sourceCode + +#endif // defined(DACCESS_COMPILE) || defined(RIGHT_SIDE_COMPILE) + + +#endif //DEBUGGER_DUMPCOMMON_H diff --git a/src/debug/inc/eventredirection.h b/src/debug/inc/eventredirection.h new file mode 100644 index 0000000000..67ea271ea4 --- /dev/null +++ b/src/debug/inc/eventredirection.h @@ -0,0 +1,84 @@ +// 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. +//***************************************************************************** + +// +// NativePipeline.h +// +// define control block for redirecting events. +//***************************************************************************** + +#ifndef _EVENTREDIRECTION_H +#define _EVENTREDIRECTION_H + +//--------------------------------------------------------------------------------------- +// Control block for redirecting events. +// Motivation here is that only 1 process can be the real OS debugger. So if we want a windbg +// attached to an ICorDebug debuggee, then that windbg is the real debugger and it forwards events +// to the mdbg process. +// +// Terminology: +// Server: a windbg extension (StrikeRS) that is the real OS debugger, and it forwards native debug +// events (just exceptions currently) to the client +// Client: ICorDebug, which gets events via shimmed call to WaitForDebugEvent, etc. +// +// Control block lives in Client's process space. All handles are valid in client. +// Sever does Read/WriteProcessMemory +struct RedirectionBlock +{ + // Version of the control block. Initialized by client, verified by server. + // Latest value is EVENT_REDIRECTION_CURRENT_VERSION + DWORD m_versionCookie; + + // + // Counters. After each WFDE/CDE pair, these counters should be in sync. + // + + // increment after WFDE + DWORD m_counterAvailable; + DWORD m_counterConsumed; + + // + // Data for WaitForDebugEvent. (Server writes; Client reads) + // + DWORD m_dwProcessId; + DWORD m_dwThreadId; + + // Different sizes on different platforms + EXCEPTION_RECORD m_record; + BOOL m_dwFirstChance; + + // + // Data for ContinueDebugEvent. (Client writes, server reads) + // + + // Continuation status argument to ContinueDebugEvent + DWORD m_ContinuationStatus; + + + // + // Coordination events. These are handles in client space; server duplicates out. + // + + // Server signals when WFDE Data is ready. + HANDLE m_hEventAvailable; + + // Server signals when CDE data is ready. + HANDLE m_hEventConsumed; + + // Client signals before it deletes this block. This corresponds to client calling DebugActiveProcessStop. + // Thus server can check if signalled to know if accessing this block (which lives in client space) is safe. + // This is Synchronized because client only detaches if the debuggee is stopped, in which case the server + // isn't in the middle of sending an event. + HANDLE m_hDetachEvent; +}; + + +// Current version. +#define EVENT_REDIRECTION_CURRENT_VERSION ((DWORD) 4) + + + +#endif // _EVENTREDIRECTION_H + diff --git a/src/debug/inc/i386/.gitmirror b/src/debug/inc/i386/.gitmirror new file mode 100644 index 0000000000..f507630f94 --- /dev/null +++ b/src/debug/inc/i386/.gitmirror @@ -0,0 +1 @@ +Only contents of this folder, excluding subfolders, will be mirrored by the Git-TFS Mirror.
\ No newline at end of file diff --git a/src/debug/inc/i386/primitives.h b/src/debug/inc/i386/primitives.h new file mode 100644 index 0000000000..abad642bbd --- /dev/null +++ b/src/debug/inc/i386/primitives.h @@ -0,0 +1,223 @@ +// 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. +//***************************************************************************** +// File: primitives.h +// + +// +// Platform-specific debugger primitives +// +//***************************************************************************** + +#ifndef PRIMITIVES_H_ +#define PRIMITIVES_H_ + + +typedef const BYTE CORDB_ADDRESS_TYPE; +typedef DPTR(CORDB_ADDRESS_TYPE) PTR_CORDB_ADDRESS_TYPE; + +//This is an abstraction to keep x86/ia64 patch data separate +#define PRD_TYPE DWORD_PTR + +#define MAX_INSTRUCTION_LENGTH 16 + +// Given a return address retrieved during stackwalk, +// this is the offset by which it should be decremented to lend somewhere in a call instruction. +#define STACKWALK_CONTROLPC_ADJUST_OFFSET 1 + +#define CORDbg_BREAK_INSTRUCTION_SIZE 1 +#define CORDbg_BREAK_INSTRUCTION (BYTE)0xCC + +inline CORDB_ADDRESS GetPatchEndAddr(CORDB_ADDRESS patchAddr) +{ + LIMITED_METHOD_DAC_CONTRACT; + return patchAddr + CORDbg_BREAK_INSTRUCTION_SIZE; +} + + +#define InitializePRDToBreakInst(_pPRD) *(_pPRD) = CORDbg_BREAK_INSTRUCTION +#define PRDIsBreakInst(_pPRD) (*(_pPRD) == CORDbg_BREAK_INSTRUCTION) + +#define CORDbgGetInstructionEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgGetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr)))); + +#define CORDbgSetInstructionEx(_buffer, _requestedAddr, _patchAddr, _opcode, _dummy2) \ + CORDbgSetInstruction((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr))), (_opcode)); + +#define CORDbgInsertBreakpointEx(_buffer, _requestedAddr, _patchAddr, _dummy1, _dummy2) \ + CORDbgInsertBreakpoint((CORDB_ADDRESS_TYPE *)((_buffer) + ((_patchAddr) - (_requestedAddr)))); + + +SELECTANY const CorDebugRegister g_JITToCorDbgReg[] = +{ + REGISTER_X86_EAX, + REGISTER_X86_ECX, + REGISTER_X86_EDX, + REGISTER_X86_EBX, + REGISTER_X86_ESP, + REGISTER_X86_EBP, + REGISTER_X86_ESI, + REGISTER_X86_EDI +}; + +// +// Mapping from ICorDebugInfo register numbers to CorDebugRegister +// numbers. Note: this must match the order in corinfo.h. +// +inline CorDebugRegister ConvertRegNumToCorDebugRegister(ICorDebugInfo::RegNum reg) +{ + return g_JITToCorDbgReg[reg]; +} + + +// +// inline function to access/modify the CONTEXT +// +inline LPVOID CORDbgGetIP(DT_CONTEXT *context) { + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(size_t)(context->Eip); +} + +inline void CORDbgSetIP(DT_CONTEXT *context, LPVOID eip) { + LIMITED_METHOD_CONTRACT; + + context->Eip = (UINT32)(size_t)eip; +} + +inline LPVOID CORDbgGetSP(const DT_CONTEXT * context) { + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(size_t)(context->Esp); +} + +inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID esp) { + LIMITED_METHOD_CONTRACT; + + context->Esp = (UINT32)(size_t)esp; +} + +inline void CORDbgSetFP(DT_CONTEXT *context, LPVOID ebp) { + LIMITED_METHOD_CONTRACT; + + context->Ebp = (UINT32)(size_t)ebp; +} +inline LPVOID CORDbgGetFP(DT_CONTEXT* context) +{ + LIMITED_METHOD_CONTRACT; + + return (LPVOID)(UINT_PTR)context->Ebp; +} + +// compare the EIP, ESP, and EBP +inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) +{ + LIMITED_METHOD_DAC_CONTRACT; + + if ((pCtx1->Eip == pCtx2->Eip) && + (pCtx1->Esp == pCtx2->Esp) && + (pCtx1->Ebp == pCtx2->Ebp)) + { + return TRUE; + } + + return FALSE; +} + +/* ========================================================================= */ +// +// Routines used by debugger support functions such as codepatch.cpp or +// exception handling code. +// +// GetInstruction, InsertBreakpoint, and SetInstruction all operate on +// a _single_ byte of memory. This is really important. If you only +// save one byte from the instruction stream before placing a breakpoint, +// you need to make sure to only replace one byte later on. +// + + +inline PRD_TYPE CORDbgGetInstruction(UNALIGNED CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + return *address; // retrieving only one byte is important + +} + +inline void CORDbgInsertBreakpoint(UNALIGNED CORDB_ADDRESS_TYPE *address) +{ + LIMITED_METHOD_CONTRACT; + + *((unsigned char*)address) = 0xCC; // int 3 (single byte patch) + FlushInstructionCache(GetCurrentProcess(), address, 1); +} + +inline void CORDbgSetInstruction(CORDB_ADDRESS_TYPE* address, + DWORD instruction) +{ + // In a DAC build, this function assumes the input is an host address. + LIMITED_METHOD_DAC_CONTRACT; + + *((unsigned char*)address) + = (unsigned char) instruction; // setting one byte is important + FlushInstructionCache(GetCurrentProcess(), address, 1); +} + +// After a breakpoint exception, the CPU points to _after_ the break instruction. +// Adjust the IP so that it points at the break instruction. This lets us patch that +// opcode and re-excute what was underneath the bp. +inline void CORDbgAdjustPCForBreakInstruction(DT_CONTEXT* pContext) +{ + LIMITED_METHOD_CONTRACT; + + pContext->Eip -= 1; +} + +inline bool AddressIsBreakpoint(CORDB_ADDRESS_TYPE* address) +{ + LIMITED_METHOD_CONTRACT; + + return *address == CORDbg_BREAK_INSTRUCTION; +} + +// Set the hardware trace flag. +inline void SetSSFlag(DT_CONTEXT *context) +{ + _ASSERTE(context != NULL); + context->EFlags |= 0x100; +} + +// Unset the hardware trace flag. +inline void UnsetSSFlag(DT_CONTEXT *context) +{ + SUPPORTS_DAC; + _ASSERTE(context != NULL); + context->EFlags &= ~0x100; +} + +// return true if the hardware trace flag applied. +inline bool IsSSFlagEnabled(DT_CONTEXT * context) +{ + _ASSERTE(context != NULL); + return (context->EFlags & 0x100) != 0; +} + + + +inline bool PRDIsEqual(PRD_TYPE p1, PRD_TYPE p2){ + return p1 == p2; +} + +// On x86 opcode 0 is an 8-bit version of ADD. Do we really want to use 0 to mean empty? (see issue 366221). +inline void InitializePRD(PRD_TYPE *p1) { + *p1 = 0; +} +inline bool PRDIsEmpty(PRD_TYPE p1) { + LIMITED_METHOD_CONTRACT; + + return p1 == 0; +} + + +#endif // PRIMITIVES_H_ diff --git a/src/debug/inc/readonlydatatargetfacade.h b/src/debug/inc/readonlydatatargetfacade.h new file mode 100644 index 0000000000..95c12928c8 --- /dev/null +++ b/src/debug/inc/readonlydatatargetfacade.h @@ -0,0 +1,98 @@ +// 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. +//***************************************************************************** +// ReadOnlyDataTargetFacade.h +// + +// +//***************************************************************************** + +#ifndef READONLYDATATARGETFACADE_H_ +#define READONLYDATATARGETFACADE_H_ + +#include <cordebug.h> + +//--------------------------------------------------------------------------------------- +// ReadOnlyDataTargetFacade +// +// This class is designed to be used as an ICorDebugMutableDataTarget when none is +// supplied. All of the write APIs will fail with CORDBG_E_TARGET_READONLY as required +// by the data target spec when a write operation is invoked on a read-only data target. +// The desire here is to merge the error code paths for the case when a write fails, +// and the case when a write is requested but the data target supplied doesn't +// implement ICorDebugMutableDataTarget. +// +// Note that this is intended to be used only for the additional APIs defined by +// ICorDebugMutableDataTarget. Calling any of the base ICorDebugDataTarget APIs +// will ASSERT and fail. An alternative design would be to make this class a wrapper +// class (similar to DataTargetAdapter) over an existing ICorDebugDataTarget interface. +// In general, we'd like callers of the data target to differentiate between when they're +// using read-only APIs and mutation APIs since they need to be aware that the latter often +// won't be supported by the data target. Also, that design would have the draw-back +// of incuring an extra virtual dispatch on every read API call (makaing debugging more +// complex and possibly having a performance impact). +// +class ReadOnlyDataTargetFacade : public ICorDebugMutableDataTarget +{ +public: + ReadOnlyDataTargetFacade(); + virtual ~ReadOnlyDataTargetFacade() {} + + // + // IUnknown. + // + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID InterfaceId, + PVOID* Interface); + + virtual ULONG STDMETHODCALLTYPE AddRef(); + + virtual ULONG STDMETHODCALLTYPE Release(); + + // + // ICorDebugDataTarget. + // + + virtual HRESULT STDMETHODCALLTYPE GetPlatform( + CorDebugPlatform *pPlatform); + + virtual HRESULT STDMETHODCALLTYPE ReadVirtual( + CORDB_ADDRESS address, + BYTE * pBuffer, + ULONG32 request, + ULONG32 * pcbRead); + + virtual HRESULT STDMETHODCALLTYPE GetThreadContext( + DWORD dwThreadID, + ULONG32 contextFlags, + ULONG32 contextSize, + BYTE * context); + + // + // ICorDebugMutableDataTarget. + // + + virtual HRESULT STDMETHODCALLTYPE WriteVirtual( + CORDB_ADDRESS address, + const BYTE * pBuffer, + ULONG32 request); + + virtual HRESULT STDMETHODCALLTYPE SetThreadContext( + DWORD dwThreadID, + ULONG32 contextSize, + const BYTE * context); + + virtual HRESULT STDMETHODCALLTYPE ContinueStatusChanged( + DWORD dwThreadId, + CORDB_CONTINUE_STATUS dwContinueStatus); + +private: + // Reference count. + LONG m_ref; +}; + +#include "readonlydatatargetfacade.inl" + +#endif // READONLYDATATARGETFACADE_H_ + diff --git a/src/debug/inc/readonlydatatargetfacade.inl b/src/debug/inc/readonlydatatargetfacade.inl new file mode 100644 index 0000000000..df7e1d0e75 --- /dev/null +++ b/src/debug/inc/readonlydatatargetfacade.inl @@ -0,0 +1,139 @@ +// 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. +//***************************************************************************** +// File: ReadOnlyDataTargetFacade.inl +// + +// +//***************************************************************************** + + +//--------------------------------------------------------------------------------------- +// +// Ctor for ReadOnlyDataTargetFacade. Just initializes ref count to 0. +// +//--------------------------------------------------------------------------------------- +ReadOnlyDataTargetFacade::ReadOnlyDataTargetFacade() + : m_ref(0) +{ +} + +// Standard impl of IUnknown::QueryInterface +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::QueryInterface( + REFIID InterfaceId, + PVOID* pInterface + ) +{ + if (InterfaceId == IID_IUnknown) + { + *pInterface = static_cast<IUnknown *>(static_cast<ICorDebugDataTarget *>(this)); + } + else if (InterfaceId == IID_ICorDebugDataTarget) + { + *pInterface = static_cast<ICorDebugDataTarget *>(this); + } + else if (InterfaceId == IID_ICorDebugMutableDataTarget) + { + *pInterface = static_cast<ICorDebugMutableDataTarget *>(this); + } + else + { + *pInterface = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return S_OK; +} + +// Standard impl of IUnknown::AddRef +ULONG STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::AddRef() +{ + SUPPORTS_DAC; + LONG ref = InterlockedIncrement(&m_ref); + return ref; +} + +// Standard impl of IUnknown::Release +ULONG STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::Release() +{ + SUPPORTS_DAC; + LONG ref = InterlockedDecrement(&m_ref); + if (ref == 0) + { + delete this; + } + return ref; +} + +// impl of interface method ICorDebugDataTarget::GetPlatform +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::GetPlatform( + CorDebugPlatform *pPlatform) +{ + SUPPORTS_DAC; + _ASSERTE_MSG(false, "Unexpected call to read-API on read-only DataTarget facade"); + return E_UNEXPECTED; +} + +// impl of interface method ICorDebugDataTarget::ReadVirtual +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::ReadVirtual( + CORDB_ADDRESS address, + PBYTE pBuffer, + ULONG32 cbRequestSize, + ULONG32 *pcbRead) +{ + SUPPORTS_DAC; + _ASSERTE_MSG(false, "Unexpected call to read-API on read-only DataTarget facade"); + return E_UNEXPECTED; +} + +// impl of interface method ICorDebugDataTarget::GetThreadContext +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::GetThreadContext( + DWORD dwThreadID, + ULONG32 contextFlags, + ULONG32 contextSize, + BYTE * pContext) +{ + SUPPORTS_DAC; + _ASSERTE_MSG(false, "Unexpected call to read-API on read-only DataTarget facade"); + return E_UNEXPECTED; +} + +// impl of interface method ICorDebugMutableDataTarget::WriteVirtual +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::WriteVirtual( + CORDB_ADDRESS pAddress, + const BYTE * pBuffer, + ULONG32 cbRequestSize) +{ + SUPPORTS_DAC; + return CORDBG_E_TARGET_READONLY; +} + +// impl of interface method ICorDebugMutableDataTarget::SetThreadContext +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::SetThreadContext( + DWORD dwThreadID, + ULONG32 contextSize, + const BYTE * pContext) +{ + SUPPORTS_DAC; + return CORDBG_E_TARGET_READONLY; +} + +// Public implementation of ICorDebugMutableDataTarget::ContinueStatusChanged +HRESULT STDMETHODCALLTYPE +ReadOnlyDataTargetFacade::ContinueStatusChanged( + DWORD dwThreadId, + CORDB_CONTINUE_STATUS dwContinueStatus) +{ + SUPPORTS_DAC; + return CORDBG_E_TARGET_READONLY; +} diff --git a/src/debug/inc/stringcopyholder.h b/src/debug/inc/stringcopyholder.h new file mode 100644 index 0000000000..13a89d87fc --- /dev/null +++ b/src/debug/inc/stringcopyholder.h @@ -0,0 +1,59 @@ +// 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. + + + +#ifndef _StringCopyHolder_h_ +#define _StringCopyHolder_h_ + + +//----------------------------------------------------------------------------- +// Simple holder to keep a copy of a string. +// Implements IStringHolder so we can pass instances through IDacDbiInterface +// and have it fill in the contents. +//----------------------------------------------------------------------------- +class StringCopyHolder : public IDacDbiInterface::IStringHolder +{ +public: + StringCopyHolder(); + + // Free the memory allocated for the string contents + ~StringCopyHolder(); + + // Make a copy of the provided null-terminated unicode string + virtual HRESULT AssignCopy(const WCHAR * pCopy); + + // Reset the string to NULL and free memory + void Clear(); + + // Returns true if the string has been set to a non-NULL value + bool IsSet() + { + return (m_szData != NULL); + } + + // Returns true if an empty string is stored. IsSet must be true to call this. + bool IsEmpty() + { + _ASSERTE(m_szData != NULL); + return m_szData[0] == W('\0'); + } + + // Returns the pointer to the string contents + operator WCHAR* () const + { + return m_szData; + } + +private: + // Disallow copying (to prevent double-free) - no implementation + StringCopyHolder( const StringCopyHolder& rhs ); + StringCopyHolder& operator=( const StringCopyHolder& rhs ); + + WCHAR * m_szData; + +}; + + +#endif // StringCopyHolder diff --git a/src/debug/inc/twowaypipe.h b/src/debug/inc/twowaypipe.h new file mode 100644 index 0000000000..6bc0f9f39e --- /dev/null +++ b/src/debug/inc/twowaypipe.h @@ -0,0 +1,104 @@ +// 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. + + +#ifndef TwoWayPipe_H +#define TwoWayPipe_H + +#ifdef FEATURE_PAL +#define INVALID_PIPE -1 +#else +#define INVALID_PIPE INVALID_HANDLE_VALUE +#endif + +// This file contains definition of a simple IPC mechanism - bidirectional named pipe. +// It is implemented on top of two one-directional names pipes (fifos on UNIX) + +// One Windows it is possible to ask OS to create a bidirectional pipe, but it is not the case on UNIX. +// In order to unify implementation we use two pipes on all systems. + +// This all methods of this class are *NOT* thread safe: it is assumed the caller provides synchronization at a higher level. +class TwoWayPipe +{ +public: + enum State + { + NotInitialized, // Object didn't create or connect to any pipes. + Created, // Server side of the pipe has been created, but didn't bind it to a client. + ServerConnected, // Server side of the pipe is connected to a client + ClientConnected, // Client side of the pipe is connected to a server. + }; + + TwoWayPipe() + :m_state(NotInitialized), + m_inboundPipe(INVALID_PIPE), + m_outboundPipe(INVALID_PIPE) + {} + + + ~TwoWayPipe() + { + Disconnect(); + } + + // Creates a server side of the pipe. + // Id is used to create pipes names and uniquely identify the pipe on the machine. + // true - success, false - failure (use GetLastError() for more details) + bool CreateServer(DWORD id); + + // Connects to a previously opened server side of the pipe. + // Id is used to locate the pipe on the machine. + // true - success, false - failure (use GetLastError() for more details) + bool Connect(DWORD id); + + // Waits for incoming client connections, assumes GetState() == Created + // true - success, false - failure (use GetLastError() for more details) + bool WaitForConnection(); + + // Reads data from pipe. Returns number of bytes read or a negative number in case of an error. + // use GetLastError() for more details + int Read(void *buffer, DWORD bufferSize); + + // Writes data to pipe. Returns number of bytes written or a negative number in case of an error. + // use GetLastError() for more details + int Write(const void *data, DWORD dataSize); + + // Disconnects server or client side of the pipe. + // true - success, false - failure (use GetLastError() for more details) + bool Disconnect(); + + State GetState() + { + return m_state; + } + + // Used by debugger side (RS) to cleanup the target (LS) named pipes + // and semaphores when the debugger detects the debuggee process exited. + void CleanupTargetProcess(); + +private: + + State m_state; + +#ifdef FEATURE_PAL + + int m_id; // id that was passed to CreateServer() or Connect() + int m_inboundPipe, m_outboundPipe; // two one sided pipes used for communication + char m_inPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH]; // filename of the inbound pipe + char m_outPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH]; // filename of the outbound pipe + +#else + // Connects to a one sided pipe previously created by CreateOneWayPipe. + // In order to successfully connect id and inbound flag should be the same. + HANDLE OpenOneWayPipe(DWORD id, bool inbound); + + // Creates a one way pipe, id and inboud flag are used for naming. + // Created pipe is supposed to be connected to by OpenOneWayPipe. + HANDLE CreateOneWayPipe(DWORD id, bool inbound); + + HANDLE m_inboundPipe, m_outboundPipe; //two one sided pipes used for communication +#endif //FEATURE_PAL +}; + +#endif //TwoWayPipe_H |