From a205749a4815e26da140f75b2a5e5b6bbfb2473f Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 21 Jan 2026 13:26:36 -0500 Subject: [PATCH 1/6] add PInvokeStubManager --- src/coreclr/inc/vptr_list.h | 1 + src/coreclr/vm/appdomain.cpp | 1 + src/coreclr/vm/stubmgr.cpp | 73 ++++++++++++++++++++++++++++++++++++ src/coreclr/vm/stubmgr.h | 48 ++++++++++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/src/coreclr/inc/vptr_list.h b/src/coreclr/inc/vptr_list.h index 7970df9be3b935..f45d6b693aa21a 100644 --- a/src/coreclr/inc/vptr_list.h +++ b/src/coreclr/inc/vptr_list.h @@ -35,6 +35,7 @@ VPTR_CLASS(JumpStubStubManager) #endif // FEATURE_JIT VPTR_CLASS(RangeSectionStubManager) VPTR_CLASS(ILStubManager) +VPTR_CLASS(PInvokeStubManager) VPTR_CLASS(InteropDispatchStubManager) #if defined(TARGET_X86) && !defined(UNIX_X86_ABI) VPTR_CLASS(TailCallStubManager) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index efdc75e8d099d6..4990924377cf3d 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -727,6 +727,7 @@ void SystemDomain::Attach() #endif // FEATURE_JIT RangeSectionStubManager::Init(); ILStubManager::Init(); + PInvokeStubManager::Init(); InteropDispatchStubManager::Init(); StubLinkStubManager::Init(); TailCallStubManager::Init(); diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index cbae7c8a2f2045..0c4cf95fc0d531 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1861,6 +1861,70 @@ BOOL ILStubManager::TraceManager(Thread *thread, } #endif //!DACCESS_COMPILE +// +// This is the stub manager for PInvoke stubs. +// + +#ifndef DACCESS_COMPILE + +/* static */ +void PInvokeStubManager::Init() +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END + + StubManager::AddStubManager(new PInvokeStubManager()); +} + +#endif // #ifndef DACCESS_COMPILE + +BOOL PInvokeStubManager::CheckIsStub_Internal(PCODE stubStartAddress) +{ + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + MethodDesc *pMD = ExecutionManager::GetCodeMethodDesc(stubStartAddress); + + return (pMD != NULL) && pMD->IsPInvoke(); +} + +BOOL PInvokeStubManager::DoTraceStub(PCODE stubStartAddress, + TraceDestination *trace) +{ + LIMITED_METHOD_CONTRACT; + + LOG((LF_CORDB, LL_EVERYTHING, "PInvokeStubManager::DoTraceStub called\n")); + +#ifndef DACCESS_COMPILE + + MethodDesc* pMD = ExecutionManager::GetCodeMethodDesc(stubStartAddress); + if (pMD == NULL || !pMD->IsPInvoke()) + { + LOG((LF_CORDB, LL_INFO1000, "PISM::DoTraceStub: Not a PInvoke stub\n")); + return FALSE; + } + + PInvokeMethodDesc* pNMD = reinterpret_cast(pMD); + PCODE target = (PCODE)pNMD->GetPInvokeTarget(); + LOG((LF_CORDB, LL_INFO10000, "PISM::DoTraceStub: PInvoke target 0x%p\n", target)); + trace->InitForUnmanaged(target); + + LOG_TRACE_DESTINATION(trace, target, "PInvokeStubManager::DoTraceStub"); + + return TRUE; + +#else // !DACCESS_COMPILE + trace->InitForOther((PCODE)NULL); + return FALSE; + +#endif // !DACCESS_COMPILE +} + // This is used to recognize GenericCLRToCOMCallStub, VarargPInvokeStub, and GenericPInvokeCalliHelper. #ifndef DACCESS_COMPILE @@ -2211,6 +2275,15 @@ ILStubManager::DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags) EMEM_OUT(("MEM: %p ILStubManager\n", dac_cast(this))); } +void +PInvokeStubManager::DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags) +{ + SUPPORTS_DAC; + WRAPPER_NO_CONTRACT; + DAC_ENUM_VTHIS(); + EMEM_OUT(("MEM: %p PInvokeStubManager\n", dac_cast(this))); +} + void InteropDispatchStubManager::DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags) { diff --git a/src/coreclr/vm/stubmgr.h b/src/coreclr/vm/stubmgr.h index 47eb5d2f8aa20b..29b10b5a93f639 100644 --- a/src/coreclr/vm/stubmgr.h +++ b/src/coreclr/vm/stubmgr.h @@ -643,6 +643,54 @@ class ILStubManager : public StubManager #endif }; +// +// This is the stub manager for PInvoke stubs. +// It handles addresses that map to a PInvokeMethodDesc. +// +typedef VPTR(class PInvokeStubManager) PTR_PInvokeStubManager; + +class PInvokeStubManager : public StubManager +{ + VPTR_VTABLE_CLASS(PInvokeStubManager, StubManager) + + public: + static void Init(); + +#ifndef DACCESS_COMPILE + PInvokeStubManager() : StubManager() {WRAPPER_NO_CONTRACT;} + ~PInvokeStubManager() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + CAN_TAKE_LOCK; // StubManager::UnlinkStubManager uses a crst + } + CONTRACTL_END; + } +#endif + + public: + +#ifdef _DEBUG + virtual const char * DbgGetName() { LIMITED_METHOD_CONTRACT; return "PInvokeStubManager"; } +#endif + + virtual BOOL CheckIsStub_Internal(PCODE stubStartAddress); + + private: + + virtual BOOL DoTraceStub(PCODE stubStartAddress, TraceDestination *trace); + +#ifdef DACCESS_COMPILE + virtual void DoEnumMemoryRegions(CLRDataEnumMemoryFlags flags); + + protected: + virtual LPCWSTR GetStubManagerName(PCODE addr) + { LIMITED_METHOD_CONTRACT; return W("PInvokeStub"); } +#endif +}; + // This is used to recognize // GenericCLRToCOMCallStub() // VarargPInvokeStub() From 1fba14ee2daeeed43a3379e38406945072b7344c Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:10:40 -0500 Subject: [PATCH 2/6] update assertions to accomdate PInvoke ILStubs --- src/coreclr/debug/ee/controller.cpp | 9 +++++---- src/coreclr/debug/ee/frameinfo.cpp | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index 14b14ff97e7b90..c990d5ddc2e6ac 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -3948,7 +3948,7 @@ bool DebuggerController::DispatchTraceCall(Thread *thread, _ASSERTE(info.HasReturnFrame()); // This check makes sure that we don't do this logic for inlined frames. - if (info.GetReturnFrame().md->IsILStub()) + if (info.GetReturnFrame().md->IsILStub() || info.GetReturnFrame().md->IsPInvoke()) { // Make sure that the frame pointer of the active frame is actually // the address of an exit frame. @@ -7029,7 +7029,7 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, MethodDesc * pMD = g_pEEInterface->GetNativeCodeMethodDesc(GetIP(context)); if (pMD != NULL) { - fIsILStub = pMD->IsILStub(); + fIsILStub = pMD->IsILStub() || pMD->IsPInvoke(); } } LOG((LF_CORDB, LL_INFO10000, "DS::S - fIsILStub = %d\n", fIsILStub)); @@ -7358,7 +7358,8 @@ TP_RESULT DebuggerStepper::TriggerPatch(DebuggerControllerPatch *patch, { // We're hitting this code path with MC++ assemblies // that have an unmanaged entry point so the stub returns to CallDescrWorker. - _ASSERTE(g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(patch->address))->IsILStub()); + _ASSERTE(g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(patch->address))->IsILStub() || + g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(patch->address))->IsPInvoke()); } } @@ -7625,7 +7626,7 @@ bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip) // a step out, or if we step-next off the end of a method called by an IL stub. In either case, // we'll get a single step in an IL stub, which we want to ignore. We also want to enable trace // call here, just in case this IL stub is about to call the managed target (in the reverse interop case). - if (fd->IsILStub()) + if (fd->IsILStub() || fd->IsPInvoke()) { LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n")); if (this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER) diff --git a/src/coreclr/debug/ee/frameinfo.cpp b/src/coreclr/debug/ee/frameinfo.cpp index 04afb80cbe61b8..a170e23746813f 100644 --- a/src/coreclr/debug/ee/frameinfo.cpp +++ b/src/coreclr/debug/ee/frameinfo.cpp @@ -1560,6 +1560,13 @@ StackWalkAction DebuggerWalkStackProc(CrawlFrame *pCF, void *data) } } else + // We ignore PInvoke methods with inlined stubs in our stackwalking. + // These are similar to IL stubs but use PInvokeMethodDesc instead of DynamicMethodDesc. + if ((md != NULL) && md->IsPInvoke() && pCF->IsFrameless()) + { + LOG((LF_CORDB, LL_INFO100000, "DWSP: Skip frameless PInvoke stub.\n")); + } + else // For frames w/o method data, send them as an internal stub frame. if ((md != NULL) && md->IsDynamicMethod()) { From 2cdf2c7d459eeef26d60b29e71d847506761fd90 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:36:14 +0000 Subject: [PATCH 3/6] Add IsInteropStub() method to simplify interop stub checks Add MethodDesc::IsInteropStub() that returns true for both IL stubs and PInvoke methods. Updated debug code to use this new helper method instead of checking both conditions separately. Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- src/coreclr/debug/ee/controller.cpp | 9 ++++----- src/coreclr/vm/method.hpp | 8 ++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index c990d5ddc2e6ac..b76008236e806c 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -3948,7 +3948,7 @@ bool DebuggerController::DispatchTraceCall(Thread *thread, _ASSERTE(info.HasReturnFrame()); // This check makes sure that we don't do this logic for inlined frames. - if (info.GetReturnFrame().md->IsILStub() || info.GetReturnFrame().md->IsPInvoke()) + if (info.GetReturnFrame().md->IsInteropStub()) { // Make sure that the frame pointer of the active frame is actually // the address of an exit frame. @@ -7029,7 +7029,7 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, MethodDesc * pMD = g_pEEInterface->GetNativeCodeMethodDesc(GetIP(context)); if (pMD != NULL) { - fIsILStub = pMD->IsILStub() || pMD->IsPInvoke(); + fIsILStub = pMD->IsInteropStub(); } } LOG((LF_CORDB, LL_INFO10000, "DS::S - fIsILStub = %d\n", fIsILStub)); @@ -7358,8 +7358,7 @@ TP_RESULT DebuggerStepper::TriggerPatch(DebuggerControllerPatch *patch, { // We're hitting this code path with MC++ assemblies // that have an unmanaged entry point so the stub returns to CallDescrWorker. - _ASSERTE(g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(patch->address))->IsILStub() || - g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(patch->address))->IsPInvoke()); + _ASSERTE(g_pEEInterface->GetNativeCodeMethodDesc(dac_cast(patch->address))->IsInteropStub()); } } @@ -7626,7 +7625,7 @@ bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip) // a step out, or if we step-next off the end of a method called by an IL stub. In either case, // we'll get a single step in an IL stub, which we want to ignore. We also want to enable trace // call here, just in case this IL stub is about to call the managed target (in the reverse interop case). - if (fd->IsILStub() || fd->IsPInvoke()) + if (fd->IsInteropStub()) { LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n")); if (this->GetDCType() == DEBUGGER_CONTROLLER_STEPPER) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index c6f39f0c8c7b8c..dda224cb6588f8 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -758,6 +758,14 @@ class MethodDesc return mcPInvoke == GetClassification(); } + // Returns true if this MethodDesc represents an interop method call. + // This includes IL stubs (DynamicMethodDesc) and PInvoke methods (PInvokeMethodDesc). + inline bool IsInteropStub() + { + WRAPPER_NO_CONTRACT; + return IsILStub() || IsPInvoke(); + } + inline DWORD IsInterface() { WRAPPER_NO_CONTRACT; From f11f49bcef019e0c9ba9c480d3f6384ed530ccf7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 23:04:34 +0000 Subject: [PATCH 4/6] Rename fIsILStub to fIsInteropStub for clarity The variable now properly reflects that it checks for interop stubs (both IL stubs and PInvoke methods) using IsInteropStub(). Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- src/coreclr/debug/ee/controller.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index b76008236e806c..2b90224d83f0ee 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -7022,17 +7022,17 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, // ControllerStackInfo doesn't report IL stubs, so if we are in an IL stub, we need // to handle the single-step specially. There are probably other problems when we stop // in an IL stub. We need to revisit this later. - bool fIsILStub = false; + bool fIsInteropStub = false; if ((context != NULL) && g_pEEInterface->IsManagedNativeCode(reinterpret_cast(GetIP(context)))) { MethodDesc * pMD = g_pEEInterface->GetNativeCodeMethodDesc(GetIP(context)); if (pMD != NULL) { - fIsILStub = pMD->IsInteropStub(); + fIsInteropStub = pMD->IsInteropStub(); } } - LOG((LF_CORDB, LL_INFO10000, "DS::S - fIsILStub = %d\n", fIsILStub)); + LOG((LF_CORDB, LL_INFO10000, "DS::S - fIsInteropStub = %d\n", fIsInteropStub)); ControllerStackInfo info; @@ -7097,7 +7097,7 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, rangeCount = 0; } - if (fIsILStub) + if (fIsInteropStub) { // Don't use the ControllerStackInfo if we are in an IL stub. m_fp = fp; @@ -7117,7 +7117,7 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, LOG((LF_CORDB,LL_INFO10000,"DS::Step %p STEP_NORMAL\n",this)); m_reason = STEP_NORMAL; //assume it'll be a normal step & set it to //something else if we walk over it - if (fIsILStub) + if (fIsInteropStub) { LOG((LF_CORDB, LL_INFO10000, "DS::Step: stepping in an IL stub\n")); From 851dbf87ac8a47d0d1cd1e6f764a1ca31ec4dd0e Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:50:15 -0500 Subject: [PATCH 5/6] nits --- src/coreclr/debug/ee/controller.cpp | 18 +++++++++--------- src/coreclr/vm/method.hpp | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/coreclr/debug/ee/controller.cpp b/src/coreclr/debug/ee/controller.cpp index f913611c956c9a..ed8a1fee81d9af 100644 --- a/src/coreclr/debug/ee/controller.cpp +++ b/src/coreclr/debug/ee/controller.cpp @@ -7070,9 +7070,9 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, Thread *thread = GetThread(); CONTEXT *context = g_pEEInterface->GetThreadFilterContext(thread); - // ControllerStackInfo doesn't report IL stubs, so if we are in an IL stub, we need - // to handle the single-step specially. There are probably other problems when we stop - // in an IL stub. We need to revisit this later. + // ControllerStackInfo doesn't report interop stubs (IL stubs and P/Invokes), so if we are + // in an interop stub, we need to handle the single-step specially. There are probably other + // problems when we stop in an interop stub. We need to revisit this later. bool fIsInteropStub = false; if ((context != NULL) && g_pEEInterface->IsManagedNativeCode(reinterpret_cast(GetIP(context)))) @@ -7150,7 +7150,7 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, if (fIsInteropStub) { - // Don't use the ControllerStackInfo if we are in an IL stub. + // Don't use the ControllerStackInfo if we are in an interop stub. m_fp = fp; } else @@ -7170,7 +7170,7 @@ bool DebuggerStepper::Step(FramePointer fp, bool in, //something else if we walk over it if (fIsInteropStub) { - LOG((LF_CORDB, LL_INFO10000, "DS::Step: stepping in an IL stub\n")); + LOG((LF_CORDB, LL_INFO10000, "DS::Step: stepping in an interop stub\n")); // Enable the right triggers if the user wants to step in. if (in) @@ -7725,11 +7725,11 @@ bool DebuggerStepper::TriggerSingleStep(Thread *thread, const BYTE *ip) StackTraceTicket ticket(ip); info.GetStackInfo(ticket, GetThread(), LEAF_MOST_FRAME, NULL); - // This is a special case where we return from a managed method back to an IL stub. This can + // This is a special case where we return from a managed method back to an interop stub. This can // only happen if there's no more managed method frames closer to the root and we want to perform - // a step out, or if we step-next off the end of a method called by an IL stub. In either case, - // we'll get a single step in an IL stub, which we want to ignore. We also want to enable trace - // call here, just in case this IL stub is about to call the managed target (in the reverse interop case). + // a step out, or if we step-next off the end of a method called by an interop stub. In either case, + // we'll get a single step in an interop stub, which we want to ignore. We also want to enable trace + // call here, just in case this stub is about to call the managed target (in the reverse interop case). if (fd->IsInteropStub()) { LOG((LF_CORDB,LL_INFO10000, "DS::TSS: not in managed code, Returning false (case 0)!\n")); diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 552b090edd1fb5..33315b25a2ef0f 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -759,7 +759,7 @@ class MethodDesc // This includes IL stubs (DynamicMethodDesc) and PInvoke methods (PInvokeMethodDesc). inline bool IsInteropStub() { - WRAPPER_NO_CONTRACT; + LIMITED_METHOD_DAC_CONTRACT; return IsILStub() || IsPInvoke(); } From 7f00551041058d7a695ab7f75ed1e10e88f20ab0 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:41:39 -0500 Subject: [PATCH 6/6] address comments --- src/coreclr/vm/method.hpp | 11 ++++------- src/coreclr/vm/method.inl | 26 ++++++++++++++++++++++++++ src/coreclr/vm/stubmgr.cpp | 5 +++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 33315b25a2ef0f..e49117d7bc9739 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -755,13 +755,10 @@ class MethodDesc return mcPInvoke == GetClassification(); } - // Returns true if this MethodDesc represents an interop method call. - // This includes IL stubs (DynamicMethodDesc) and PInvoke methods (PInvokeMethodDesc). - inline bool IsInteropStub() - { - LIMITED_METHOD_DAC_CONTRACT; - return IsILStub() || IsPInvoke(); - } + // Returns true if this MethodDesc represents an interop stub. + // This includes interop IL stubs (PInvoke, COM, reverse PInvoke, struct marshal) + // and PInvoke methods (PInvokeMethodDesc). + inline bool IsInteropStub(); inline DWORD IsInterface() { diff --git a/src/coreclr/vm/method.inl b/src/coreclr/vm/method.inl index 4c22811b5f41eb..9331543274d7a5 100644 --- a/src/coreclr/vm/method.inl +++ b/src/coreclr/vm/method.inl @@ -133,6 +133,32 @@ inline bool MethodDesc::IsILStub() return ((mcDynamic == GetClassification()) && dac_cast(this)->IsILStub()); } +inline bool MethodDesc::IsInteropStub() +{ + WRAPPER_NO_CONTRACT; + + if (IsPInvoke()) + return true; + + if (!IsILStub()) + return false; + + switch (AsDynamicMethodDesc()->GetILStubType()) + { + case DynamicMethodDesc::StubPInvoke: + case DynamicMethodDesc::StubPInvokeDelegate: + case DynamicMethodDesc::StubPInvokeCalli: + case DynamicMethodDesc::StubPInvokeVarArg: + case DynamicMethodDesc::StubReversePInvoke: + case DynamicMethodDesc::StubCLRToCOMInterop: + case DynamicMethodDesc::StubCOMToCLRInterop: + case DynamicMethodDesc::StubStructMarshalInterop: + return true; + default: + return false; + } +} + // This method is intended to identify methods that aren't shown in diagnostic introspection (stacktraces, // code viewing, stepping, etc). Partly this is a user experience consideration to preserve the // abstraction users would expect based on source code and assembly contents. Partly it is also a technical diff --git a/src/coreclr/vm/stubmgr.cpp b/src/coreclr/vm/stubmgr.cpp index 0e6441ec5d8268..6a075ef17c43e3 100644 --- a/src/coreclr/vm/stubmgr.cpp +++ b/src/coreclr/vm/stubmgr.cpp @@ -1910,6 +1910,11 @@ BOOL PInvokeStubManager::DoTraceStub(PCODE stubStartAddress, } PInvokeMethodDesc* pNMD = reinterpret_cast(pMD); + // Note: The PInvoke target may not yet be resolved if it uses lazy binding + // (PRECODE_PINVOKE_IMPORT). In that case, GetPInvokeTarget() returns the + // precode address. There is a narrow race where the target gets resolved + // between this read and the debugger setting a breakpoint, but this is + // low priority to address. PCODE target = (PCODE)pNMD->GetPInvokeTarget(); LOG((LF_CORDB, LL_INFO10000, "PISM::DoTraceStub: PInvoke target 0x%p\n", target)); trace->InitForUnmanaged(target);