From 8afd1eb71a25321734a0b370ae7aca4c094b3449 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Mon, 4 May 2026 17:10:41 +0200 Subject: [PATCH 1/2] [interp] Implement Managed Return Value support for the interpreter GetReturnValueLiveOffset returns the native offsets at which the return value of a call site is live, so the debugger can show the value a method just returned when stepping out. It walks the IL to native map looking for CALL_INSTRUCTION-tagged entries to find the post-call IP, then reads the value from the architectural return register. The interpreter does not emit CALL_INSTRUCTION map entries and does not return values in registers, so the feature did not work for any interpreted method. This change wires it up. EmitCodeIns now emits a CALL_INSTRUCTION-flagged map entry for each managed call opcode (INTOP_CALL, INTOP_CALLVIRT, INTOP_CALLI, etc.). On the DI side, CordbNativeCode::GetReturnValueLiveOffsetImpl recognizes these entries and computes the post-call IP using a per-opcode slot length table built from intops.def. A new CordbNativeCode::GetInterpreterCallDvarOffset decodes the call at that IP and returns the FP-relative byte offset of the call's destination var. CordbJITILFrame::GetReturnValueForILOffsetImpl then fabricates NativeVarInfo{VLT_STK, REGNUM_FP, dvarOffset} and routes through the existing GetNativeVariable, which already reads FP-relative slots correctly out of an interpreter frame. Two compiler-side cleanups come along with it. The IL to native map previously emitted entries only at IL offsets where the evaluation stack was empty. It now emits one entry per IL offset and tags STACK_EMPTY only when the stack actually was empty. The stepper only stops on STACK_EMPTY, so stepping is unchanged, but breakpoint binding and IP to source mapping now also work at non-empty-stack offsets. Separately, NewIns was burning m_isFirstInstForEmptyILStack on emit-nop pseudo-ops like INTOP_DEF, which left the first real IR at the same IL offset (the leading newobj of `var x = new Foo();` for example) untagged and made step-over skip past such statements. The flag is now consumed only when a real IR instruction is emitted. This fixes the following interpreter debugger test failures: - MRV.TestArrays - MRV.TestAsyncMethods - MRV.TestClasses - MRV.TestComplexGenerics - MRV.TestEnums - MRV.TestGenerics - MRV.TestMisc - MRV.TestNativeCalls - MRV.TestPointers - MRV.TestPrimitiveTypes - MRV.TestReflectionWithAbstractMethod - MRV.TestRefs - MRV.TestStructs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 5 + src/coreclr/debug/di/module.cpp | 110 ++++++++++++++++++++- src/coreclr/debug/di/rspriv.h | 24 ++++- src/coreclr/debug/di/rsthread.cpp | 18 ++++ src/coreclr/debug/inc/dacdbistructures.h | 3 + src/coreclr/debug/inc/dacdbistructures.inl | 2 + src/coreclr/interpreter/compiler.cpp | 35 +++++-- src/coreclr/interpreter/compiler.h | 5 +- 8 files changed, 185 insertions(+), 17 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 2785108723ebba..ebb88f712c611f 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -1255,6 +1255,10 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeInfo(VMPTR_Assembly if (pCodeInfo->m_rgCodeRegions[kHot].pAddress != (CORDB_ADDRESS)NULL) { pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); + { + EECodeInfo codeInfo(PINSTRToPCODE((TADDR)pCodeInfo->m_rgCodeRegions[kHot].pAddress)); + pCodeInfo->isInterpreted = codeInfo.IsInterpretedCode(); + } LookupEnCVersions(pModule, pCodeInfo->vmNativeCodeMethodDescToken, functionToken, @@ -1335,6 +1339,7 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetNativeCodeInfoForAddr(CORDB_AD vmMethodDesc.SetHostPtr(codeInfo.GetMethodDesc()); MethodDesc* pMethodDesc = vmMethodDesc.GetDacPtr(); pCodeInfo->isInstantiatedGeneric = pMethodDesc->HasClassOrMethodInstantiation(); + pCodeInfo->isInterpreted = codeInfo.IsInterpretedCode(); pCodeInfo->vmNativeCodeMethodDescToken = vmMethodDesc; SIZE_T unusedLatestEncVersion; diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 5249fa264bb343..26bbe33946ca94 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -17,6 +17,38 @@ #include "pedecoder.h" #include "memorystreams.h" +#ifdef FEATURE_INTERPRETER +#include "intopsshared.h" + +static const uint8_t s_interpOpLenTableForDI[] = +{ +#define OPDEF(a,b,c,d,e,f) c, +#include "intops.def" +#undef OPDEF +}; + +static int GetInterpreterCallOpcodeLengthInSlots(int32_t opcode) +{ + switch (opcode) + { + case INTOP_CALL: + case INTOP_CALL_NULLCHECK: + case INTOP_CALL_TAIL: + case INTOP_CALLDELEGATE: + case INTOP_CALLDELEGATE_TAIL: + case INTOP_CALLI: + case INTOP_CALLI_TAIL: + case INTOP_CALLVIRT: + case INTOP_CALLVIRT_TAIL: + case INTOP_CALL_PINVOKE: + _ASSERTE(opcode >= 0 && (size_t)opcode < ARRAY_SIZE(s_interpOpLenTableForDI)); + return s_interpOpLenTableForDI[opcode]; + default: + return -1; + } +} +#endif // FEATURE_INTERPRETER + //--------------------------------------------------------------------------------------- // Initialize a new CordbModule around a Module in the target. // @@ -3905,14 +3937,22 @@ HRESULT CordbVariableHome::GetOffset(LONG *pOffset) //----------------------------------------------------------------------------- CordbNativeCode::CordbNativeCode(CordbFunction * pFunction, const NativeCodeFunctionData * pJitData, - BOOL fIsInstantiatedGeneric) + BOOL fIsInstantiatedGeneric, + BOOL fIsInterpreted) : CordbCode(pFunction, (UINT_PTR)pJitData->m_rgCodeRegions[kHot].pAddress, pJitData->encVersion, FALSE), m_vmNativeCodeMethodDescToken(pJitData->vmNativeCodeMethodDescToken), m_fCodeAvailable(TRUE), m_fIsInstantiatedGeneric(fIsInstantiatedGeneric != FALSE) +#ifdef FEATURE_INTERPRETER + , m_fIsInterpreted(fIsInterpreted != FALSE) +#endif { _ASSERTE(GetVersion() >= CorDB_DEFAULT_ENC_FUNCTION_VERSION); +#ifndef FEATURE_INTERPRETER + (void)fIsInterpreted; +#endif + for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region) { m_rgCodeRegions[region] = pJitData->m_rgCodeRegions[region]; @@ -4356,6 +4396,19 @@ HRESULT CordbNativeCode::EnumerateVariableHomes(ICorDebugVariableHomeEnum **ppEn int CordbNativeCode::GetCallInstructionLength(BYTE *ip, ULONG32 count) { +#ifdef FEATURE_INTERPRETER + if (IsInterpreted()) + { + if (count < sizeof(int32_t)) + return -1; + int32_t interpOpcode; + memcpy(&interpOpcode, ip, sizeof(int32_t)); + int interpSlots = GetInterpreterCallOpcodeLengthInSlots(interpOpcode); + if (interpSlots <= 0) + return -1; + return interpSlots * (int)sizeof(int32_t); + } +#endif // FEATURE_INTERPRETER #if defined(TARGET_ARM) || defined(TARGET_RISCV64) if (Is32BitInstruction(*(WORD*)ip)) return 4; @@ -5028,7 +5081,9 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst // Get the length of the call instruction. int offset = GetCallInstructionLength(nativeBuffer, fetched); if (offset == -1) - return E_UNEXPECTED; // Could not decode instruction, this should never happen. + { + return E_UNEXPECTED; // Could not decode instruction, this should never happen. + } pOffsets[found] = pMap->nativeStartOffset + offset; } @@ -5051,6 +5106,55 @@ HRESULT CordbNativeCode::GetReturnValueLiveOffsetImpl(Instantiation *currentInst return S_OK; } +#ifdef FEATURE_INTERPRETER +HRESULT CordbNativeCode::GetInterpreterCallDvarOffset(ULONG32 ILoffset, ULONG32 postCallNativeOffset, int32_t* pDvarOffset) +{ + if (pDvarOffset == NULL) + return E_INVALIDARG; + + HRESULT hr = S_OK; + SequencePoints *pSP = GetSequencePoints(); + DebuggerILToNativeMap *pMap = pSP->GetCallsiteMapAddr(); + + for (ULONG32 i = 0; i < pSP->GetCallsiteEntryCount() && pMap; ++i, pMap++) + { + if (pMap->ilOffset != ILoffset || + (pMap->source & ICorDebugInfo::CALL_INSTRUCTION) != ICorDebugInfo::CALL_INSTRUCTION) + { + continue; + } + + // slot 0 = opcode, slot 1 = dvar byte offset + BYTE nativeBuffer[2 * sizeof(int32_t)]; + ULONG32 fetched = 0; + hr = GetCode(pMap->nativeStartOffset, + pMap->nativeStartOffset + ARRAY_SIZE(nativeBuffer), + ARRAY_SIZE(nativeBuffer), + nativeBuffer, + &fetched); + if (FAILED(hr) || fetched < ARRAY_SIZE(nativeBuffer)) + continue; + + int32_t interpOpcode; + memcpy(&interpOpcode, nativeBuffer, sizeof(int32_t)); + int interpSlots = GetInterpreterCallOpcodeLengthInSlots(interpOpcode); + if (interpSlots <= 0) + return S_FALSE; // Not an interpreter call. + + ULONG32 expectedPostCall = pMap->nativeStartOffset + interpSlots * (ULONG32)sizeof(int32_t); + if (expectedPostCall != postCallNativeOffset) + continue; + + int32_t dvarOffset; + memcpy(&dvarOffset, nativeBuffer + sizeof(int32_t), sizeof(int32_t)); + *pDvarOffset = dvarOffset; + return S_OK; + } + + return S_FALSE; +} +#endif // FEATURE_INTERPRETER + //----------------------------------------------------------------------------- // Creates a CordbNativeCode (if it's not already created) and adds it to the // hash table of CordbNativeCode instances belonging to this module. @@ -5105,7 +5209,7 @@ CordbNativeCode * CordbModule::LookupOrCreateNativeCode(mdMethodDef methodToken, pFunction->InitParentClassOfFunction(); // First, create a new CordbNativeCode instance--we'll need this to make the CordbJITInfo instance - pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0); + pNativeCode = new (nothrow)CordbNativeCode(pFunction, &codeInfo, codeInfo.isInstantiatedGeneric != 0, codeInfo.isInterpreted != 0); _ASSERTE(pNativeCode != NULL); m_nativeCodeTable.AddBaseOrThrow(pNativeCode); diff --git a/src/coreclr/debug/di/rspriv.h b/src/coreclr/debug/di/rspriv.h index 69f2876a8c1650..8bad53757245df 100644 --- a/src/coreclr/debug/di/rspriv.h +++ b/src/coreclr/debug/di/rspriv.h @@ -5771,7 +5771,8 @@ class CordbNativeCode : public CordbCode, public: CordbNativeCode(CordbFunction * pFunction, const NativeCodeFunctionData * pJitData, - BOOL fIsInstantiatedGeneric); + BOOL fIsInstantiatedGeneric, + BOOL fIsInterpreted); #ifdef _DEBUG const char * DbgGetName() { return "CordbNativeCode"; }; #endif // _DEBUG @@ -5842,6 +5843,11 @@ class CordbNativeCode : public CordbCode, // Worker function for GetReturnValueLiveOffset. HRESULT GetReturnValueLiveOffsetImpl(Instantiation *currentInstantiation, ULONG32 ILoffset, ULONG32 bufferSize, ULONG32 *pFetched, ULONG32 *pOffsets); +#ifdef FEATURE_INTERPRETER + // Returns the FP-relative offset of an interpreter call's destination var. + HRESULT GetInterpreterCallDvarOffset(ULONG32 ILoffset, ULONG32 postCallNativeOffset, int32_t* pDvarOffset); +#endif + // get total size of the code including both hot and cold regions ULONG32 GetSize(); @@ -5876,6 +5882,13 @@ class CordbNativeCode : public CordbCode, return m_fIsInstantiatedGeneric != 0; } +#ifdef FEATURE_INTERPRETER + BOOL IsInterpreted() + { + return m_fIsInterpreted != 0; + } +#endif + // Determine whether we have initialized the native variable and // sequence point offsets BOOL IsNativeCodeValid () @@ -5929,6 +5942,9 @@ class CordbNativeCode : public CordbCode, bool m_fCodeAvailable; // true iff the code has been jitted but not pitched bool m_fIsInstantiatedGeneric; // true iff this is an instantiated generic +#ifdef FEATURE_INTERPRETER + bool m_fIsInterpreted; // true iff compiled by the CoreCLR interpreter +#endif // information in the following two classes tracks native offsets and is initialized on demand. @@ -6764,11 +6780,11 @@ typedef std::function ValueGette class CordbValueEnum : public CordbBase, public ICorDebugValueEnum { public: - CordbValueEnum(CordbProcess* pProcess, - UINT maxCount, + CordbValueEnum(CordbProcess* pProcess, + UINT maxCount, ValueGetter valueGetter, NeuterList* pNeuterList); - + virtual ~CordbValueEnum(); virtual void Neuter(); diff --git a/src/coreclr/debug/di/rsthread.cpp b/src/coreclr/debug/di/rsthread.cpp index 5636d7bf48da3d..d057540e9d6e5e 100644 --- a/src/coreclr/debug/di/rsthread.cpp +++ b/src/coreclr/debug/di/rsthread.cpp @@ -8847,6 +8847,24 @@ HRESULT CordbJITILFrame::GetReturnValueForILOffsetImpl(ULONG32 ILoffset, ICorDeb CordbType *pType = 0; IfFailRet(BuildInstantiationForCallsite(GetModule(), types, inst, &m_genericArgs, targetClass, genericSig)); IfFailRet(CordbType::SigToType(GetModule(), &methodSig, &inst, &pType)); + +#ifdef FEATURE_INTERPRETER + if (pCode->IsInterpreted()) + { + // Interpreter return values live in an FP-relative dvar, not a register. + int32_t dvarOffset = 0; + IfFailRet(pCode->GetInterpreterCallDvarOffset(ILoffset, currentOffset, &dvarOffset)); + ICorDebugInfo::NativeVarInfo synthetic = {}; + synthetic.startOffset = currentOffset; + synthetic.endOffset = currentOffset; + synthetic.varNumber = 0; + synthetic.loc.vlType = ICorDebugInfo::VLT_STK; + synthetic.loc.vlStk.vlsBaseReg = ICorDebugInfo::REGNUM_FP; + synthetic.loc.vlStk.vlsOffset = dvarOffset; + return GetNativeVariable(pType, &synthetic, ppReturnValue); + } +#endif // FEATURE_INTERPRETER + return GetReturnValueForType(pType, ppReturnValue); } diff --git a/src/coreclr/debug/inc/dacdbistructures.h b/src/coreclr/debug/inc/dacdbistructures.h index aff80317c59b32..c833d568f8ceb0 100644 --- a/src/coreclr/debug/inc/dacdbistructures.h +++ b/src/coreclr/debug/inc/dacdbistructures.h @@ -505,6 +505,9 @@ class MSLAYOUT NativeCodeFunctionData // indicates whether the function is a generic function, or a method inside a generic class (or both). BOOL isInstantiatedGeneric; + // indicates whether the function is compiled by the CoreCLR interpreter rather than the JIT. + BOOL isInterpreted; + // MethodDesc for the function VMPTR_MethodDesc vmNativeCodeMethodDescToken; diff --git a/src/coreclr/debug/inc/dacdbistructures.inl b/src/coreclr/debug/inc/dacdbistructures.inl index 52987b5013403d..541e580fc1e5ec 100644 --- a/src/coreclr/debug/inc/dacdbistructures.inl +++ b/src/coreclr/debug/inc/dacdbistructures.inl @@ -571,6 +571,7 @@ NativeCodeFunctionData::NativeCodeFunctionData(DebuggerIPCE_JITFuncData * source // copy the other function information isInstantiatedGeneric = source->isInstantiatedGeneric; + isInterpreted = FALSE; vmNativeCodeMethodDescToken = source->vmNativeCodeMethodDescToken; encVersion = source->enCVersion; } @@ -587,6 +588,7 @@ inline void NativeCodeFunctionData::Clear() { isInstantiatedGeneric = FALSE; + isInterpreted = FALSE; encVersion = CorDB_DEFAULT_ENC_FUNCTION_VERSION; for (CodeBlobRegion region = kHot; region < MAX_REGIONS; ++region) { diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index bb5ee520be236c..4c49525d5ae355 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -324,7 +324,11 @@ InterpInst* InterpCompiler::NewIns(int opcode, int dataLen) // This is the first instruction we are emitting for this IL offset and the stack is empty, which // implies the IL stack is empty too. ins->flags |= INTERP_INST_FLAG_EMPTY_IL_STACK; - m_isFirstInstForEmptyILStack = false; + // Skip emit-nop instructions so the flag falls through to the first real IR. + if (!InterpOpIsEmitNop(opcode)) + { + m_isFirstInstForEmptyILStack = false; + } } m_pLastNewIns = ins; return ins; @@ -1054,9 +1058,8 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraynativeOffset); - // Only emit mapping entries at IL offsets where the evaluation stack is empty - if ((ins->flags & INTERP_INST_FLAG_EMPTY_IL_STACK) && - ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset))) + // Emit one entry per IL offset, tagging STACK_EMPTY only when the IL stack was empty. + if ((m_ILToNativeMapSize == 0) || (m_pILToNativeMap[m_ILToNativeMapSize - 1].ilOffset != ilOffset)) { // This code assumes that instructions for the same IL offset are emitted in a single run without // any other IL offsets in between and that they don't repeat again after the run ends. @@ -1072,13 +1075,23 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArrayflags & INTERP_INST_FLAG_EMPTY_IL_STACK) + ? ICorDebugInfo::STACK_EMPTY + : ICorDebugInfo::SOURCE_TYPE_INVALID; + m_ILToNativeMapSize++; + } + + if (ins->flags & INTERP_INST_FLAG_DBG_CALL_INSTRUCTION) + { + assert(m_ILToNativeMapSize < m_ILToNativeMapCapacity); m_pILToNativeMap[m_ILToNativeMapSize].ilOffset = ilOffset; m_pILToNativeMap[m_ILToNativeMapSize].nativeOffset = nativeOffset; - m_pILToNativeMap[m_ILToNativeMapSize].source = ICorDebugInfo::STACK_EMPTY; + m_pILToNativeMap[m_ILToNativeMapSize].source = ICorDebugInfo::CALL_INSTRUCTION; m_ILToNativeMapSize++; } } @@ -1178,7 +1191,9 @@ void InterpCompiler::EmitCode() // This will eventually be freed by the VM, using freeArray. // If we fail before handing them to the VM, there is logic in the InterpCompiler destructor to free it. - m_pILToNativeMap = (ICorDebugInfo::OffsetMapping*)m_compHnd->allocateArray(m_ILCodeSize * sizeof(ICorDebugInfo::OffsetMapping)); + // Each IL offset may produce up to two entries (STACK_EMPTY plus CALL_INSTRUCTION). + m_ILToNativeMapCapacity = m_ILCodeSize * 2; + m_pILToNativeMap = (ICorDebugInfo::OffsetMapping*)m_compHnd->allocateArray(m_ILToNativeMapCapacity * sizeof(ICorDebugInfo::OffsetMapping)); // For each BB, compute the number of EH clauses that overlap with it. for (unsigned int i = 0; i < getEHcount(m_methodInfo); i++) @@ -5564,6 +5579,8 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re m_pLastNewIns->SetSVar(CALL_ARGS_SVAR); m_pLastNewIns->flags |= INTERP_INST_FLAG_CALL; + if (!tailcall && !newObj) + m_pLastNewIns->flags |= INTERP_INST_FLAG_DBG_CALL_INSTRUCTION; m_pLastNewIns->info.pCallInfo = new (getAllocator(IMK_CallInfo)) InterpCallInfo(); m_pLastNewIns->info.pCallInfo->pCallArgs = callArgs; diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 485c55503ea977..44ed16354e5a32 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -241,7 +241,9 @@ enum InterpInstFlags // Flag used internally by the var offset allocator INTERP_INST_FLAG_ACTIVE_CALL = 0x02, // The IL stack is empty at this instruction - INTERP_INST_FLAG_EMPTY_IL_STACK = 0x04 + INTERP_INST_FLAG_EMPTY_IL_STACK = 0x04, + // Marks a user IL call site reportable to ICorDebugCode3::GetReturnValueLiveOffset + INTERP_INST_FLAG_DBG_CALL_INSTRUCTION = 0x08 }; struct InterpInst @@ -856,6 +858,7 @@ class InterpCompiler int32_t* m_pNativeMapIndexToILOffset = NULL; #endif int32_t m_ILToNativeMapSize = 0; + int32_t m_ILToNativeMapCapacity = 0; InterpBasicBlock* AllocBB(int32_t ilOffset); InterpBasicBlock* GetBB(int32_t ilOffset); From 23409b8a3d2ed7373a83065db746d63e641ba7f1 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Wed, 6 May 2026 13:55:21 +0200 Subject: [PATCH 2/2] Return E_UNEXPECTED instead of S_FALSE from GetInterpreterCallDvarOffset Caller uses IfFailRet which treats S_FALSE as success. With the IsInterpreted() gating in place, neither S_FALSE return path is reachable on the happy path, so propagate them as real errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/di/module.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/di/module.cpp b/src/coreclr/debug/di/module.cpp index 26bbe33946ca94..1060ff357a8c46 100644 --- a/src/coreclr/debug/di/module.cpp +++ b/src/coreclr/debug/di/module.cpp @@ -5139,7 +5139,7 @@ HRESULT CordbNativeCode::GetInterpreterCallDvarOffset(ULONG32 ILoffset, ULONG32 memcpy(&interpOpcode, nativeBuffer, sizeof(int32_t)); int interpSlots = GetInterpreterCallOpcodeLengthInSlots(interpOpcode); if (interpSlots <= 0) - return S_FALSE; // Not an interpreter call. + return E_UNEXPECTED; ULONG32 expectedPostCall = pMap->nativeStartOffset + interpSlots * (ULONG32)sizeof(int32_t); if (expectedPostCall != postCallNativeOffset) @@ -5151,7 +5151,7 @@ HRESULT CordbNativeCode::GetInterpreterCallDvarOffset(ULONG32 ILoffset, ULONG32 return S_OK; } - return S_FALSE; + return E_UNEXPECTED; } #endif // FEATURE_INTERPRETER