Skip to content

Commit a4e4aee

Browse files
max-charlambMax CharlambCopilot
authored
[cDAC] Use NativeCodeVersion entry for GetMethodVarInfo offset (#128154)
> [!NOTE] > This PR was authored with assistance from GitHub Copilot. ## Problem `runtime-diagnostics` build [1418267](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1418267) failed with: ``` Process terminated. Assertion failed. GetNumLocations cDAC: 0, DAC: 1 at Microsoft.Diagnostics.DataContractReader.Legacy.ClrDataValue...GetNumLocations(UInt32*) ``` The asserting frame was the `this` parameter of `ManualResetEventSlim.Wait` during `!ClrStack -a` against the WebApp3 dump. ## Root cause `DebugInfo_2.GetMethodVarInfo` computed the IP -> codeOffset relative to `MethodDesc.NativeCode`. That field tracks only the most recently-compiled tier. With tiered compilation, an older-tier frame still on the stack gets the wrong base, no `varInfo` ranges match, and cDAC returns an empty location list while the legacy DAC finds the entry. The legacy DAC (`src/coreclr/debug/daccess/daccess.cpp:5591`) instead uses `ExecutionManager::GetNativeCodeVersion(address).GetNativeCode()` -- the entry point of the specific NativeCodeVersion that owns the IP. This bug has been latent since #125463. dotnet/diagnostics#5767 (merged 2026-05-12) rebuilt the debuggees with the .NET 11 test SDK, which increased the chance of hot methods like `ManualResetEventSlim.Wait` straddling tier boundaries at dump-capture time and surfaced the existing bug. ## Fix Resolve the IP-specific `NativeCodeVersion` via the `ICodeVersions` contract (already mirrors `ExecutionManager::GetNativeCodeVersion` + `NativeCodeVersion::GetNativeCode` for versionable and non-versionable methods). Throw on an invalid `NativeCodeVersion`, matching the native DAC's `E_INVALIDARG` behavior. ### Type-correctness fix (uncovered while debugging an ARM32 regression) While validating the fix above, an ARM32 regression in `IXCLRDataValueDumpTests.GetSize_ReturnsExpectedSizes` revealed a long-standing mistype: - `IExecutionManager.GetStartAddress` / `GetFuncletStartAddress` declared `TargetCodePointer`, but the value stored in `CodeBlock` is the raw code-block start with no ARM32 thumb bit. That matches native -- `EECodeInfo::GetStartAddress` and `CodeHeader::GetCodeStartAddress` both return `TADDR` -- but the cdac type signature was lying about what the value represented. - On ARM32, `CodeVersions_1.GetSpecificNativeCodeVersion` compared `MethodDesc.NativeCode` (a `PCODE` with the thumb bit set) against the raw `TADDR` from `GetStartAddress`, so the equality check never matched and `GetNativeCodeVersionForIP` returned `Invalid` -- which broke the new `GetMethodVarInfo` path on ARM32. The first commit in this PR changes the return types to `TargetPointer` (matching native `TADDR` semantics), drops the now-redundant `AddressFromCodePointer` conversions at the callers, and at the one site that genuinely needs a `PCODE` for comparison uses `CodePointerFromAddress` (the cdac analogue of native `PINSTRToPCODE`). The second commit is the original `GetMethodVarInfo` change, now safe on ARM32. ## Validation - Built cdac Contracts and Legacy projects: clean. - `dotnet test` on the full cdac suite: 2133/2133 passed (16 unrelated skips). - runtime-diagnostics CI will validate end-to-end behavior on x64 and ARM32. --------- Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ce5ff45 commit a4e4aee

12 files changed

Lines changed: 46 additions & 34 deletions

File tree

docs/design/datacontracts/DebugInfo.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ Contracts used:
254254
| Contract Name |
255255
| --- |
256256
| `ExecutionManager` |
257+
| `CodeVersions` |
257258

258259
Constants:
259260
| Constant Name | Meaning | Value |

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IExecutionManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ public interface IExecutionManager : IContract
9797
static string IContract.Name { get; } = nameof(ExecutionManager);
9898
CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => throw new NotImplementedException();
9999
TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
100-
TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
101-
TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
100+
TargetPointer GetStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
101+
TargetPointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();
102102
void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) => throw new NotImplementedException();
103103
TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => throw new NotImplementedException();
104104
bool IsFunclet(CodeBlockHandle codeInfoHandle) => throw new NotImplementedException();

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointe
138138
}
139139
else
140140
{
141-
TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value);
141+
TargetCodePointer startAddress = CodePointerUtils.CodePointerFromAddress(
142+
executionManager.GetStartAddress(info.Value),
143+
_target);
142144
return GetSpecificNativeCodeVersion(rts, md, startAddress);
143145
}
144146
}

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_1.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ IEnumerable<OffsetMapping> IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode
3939
throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}.");
4040
TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool hasFlagByte);
4141

42-
TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh);
43-
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target));
42+
TargetPointer nativeCodeStart = _eman.GetStartAddress(cbh);
43+
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - nativeCodeStart);
4444

4545
if (debugInfo == TargetPointer.Null)
4646
return [];

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/DebugInfo/DebugInfo_2.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ IEnumerable<OffsetMapping> IDebugInfo.GetMethodNativeMap(TargetCodePointer pCode
4646
throw new InvalidOperationException($"No CodeBlockHandle found for native code {pCode}.");
4747
TargetPointer debugInfo = _eman.GetDebugInfo(cbh, out bool _);
4848

49-
TargetCodePointer nativeCodeStart = _eman.GetStartAddress(cbh);
50-
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target));
49+
TargetPointer nativeCodeStart = _eman.GetStartAddress(cbh);
50+
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - nativeCodeStart);
5151

5252
if (debugInfo == TargetPointer.Null)
5353
return [];
@@ -126,12 +126,13 @@ IEnumerable<DebugVarInfo> IDebugInfo.GetMethodVarInfo(TargetCodePointer pCode, o
126126
// Compute code offset from the method's native code entry point, not from the code block start.
127127
// GetStartAddress returns the start of the current code block (which may be a funclet for exception
128128
// handlers). Variable location offsets are always relative to the method entry point, so we must use
129-
// GetNativeCode from the MethodDesc — matching the native DAC's GetMethodVarInfo which uses
130-
// NativeCodeVersion::GetNativeCode() for this purpose.
131-
IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
132-
TargetPointer methodDescAddr = _eman.GetMethodDesc(cbh);
133-
MethodDescHandle mdh = rts.GetMethodDescHandle(methodDescAddr);
134-
TargetCodePointer nativeCodeStart = rts.GetNativeCode(mdh);
129+
// GetNativeCode from the NativeCodeVersion, matching the native DAC's GetMethodVarInfo which uses
130+
// NativeCodeVersion::GetNativeCode() for this purpose
131+
ICodeVersions cv = _target.Contracts.CodeVersions;
132+
NativeCodeVersionHandle ncvh = cv.GetNativeCodeVersionForIP(pCode);
133+
if (!ncvh.Valid)
134+
throw new InvalidOperationException($"No NativeCodeVersion found for native code {pCode}.");
135+
TargetCodePointer nativeCodeStart = cv.GetNativeCode(ncvh);
135136
codeOffset = (uint)(CodePointerUtils.AddressFromCodePointer(pCode, _target) - CodePointerUtils.AddressFromCodePointer(nativeCodeStart, _target));
136137

137138
if (debugInfo == TargetPointer.Null)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer
3939
if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader))
4040
return false;
4141

42-
info = new CodeBlock(codeStart.Value, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager);
42+
info = new CodeBlock(codeStart, realCodeHeader.MethodDesc, relativeOffset, rangeSection.Data!.JitManager);
4343
return true;
4444
}
4545

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,20 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer
4444
Data.RuntimeFunction function = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, index);
4545

4646
TargetPointer addr = CodePointerUtils.AddressFromCodePointer(jittedCodeAddress, Target);
47-
TargetCodePointer startAddress = imageBase + function.BeginAddress;
47+
// The R2R RUNTIME_FUNCTION.BeginAddress encodes thumb code with the thumb bit set on
48+
// ARM32. Native RUNTIME_FUNCTION__BeginAddress uses ThumbCodeToDataPointer to strip it
49+
// (clrnt.h, ARM32 branch). Mirror that so we store a raw TADDR-style start address.
50+
TargetPointer startAddress = CodePointerUtils.AddressFromCodePointer(
51+
new TargetCodePointer(imageBase.Value + function.BeginAddress), Target);
4852
TargetNUInt relativeOffset = new TargetNUInt(addr - startAddress);
4953

5054
// Take hot/cold splitting into account for the relative offset
5155
if (_hotCold.TryGetColdFunctionIndex(r2rInfo.NumHotColdMap, r2rInfo.HotColdMap, index, out uint coldFunctionIndex))
5256
{
5357
Debug.Assert(coldFunctionIndex < r2rInfo.NumRuntimeFunctions);
5458
Data.RuntimeFunction coldFunction = _runtimeFunctions.GetRuntimeFunction(r2rInfo.RuntimeFunctions, coldFunctionIndex);
55-
TargetPointer coldStart = imageBase + coldFunction.BeginAddress;
59+
TargetPointer coldStart = CodePointerUtils.AddressFromCodePointer(
60+
new TargetCodePointer(imageBase.Value + coldFunction.BeginAddress), Target);
5661
if (addr >= coldStart)
5762
{
5863
// If the address is in the cold part, the relative offset is the size of the
@@ -62,7 +67,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer
6267
}
6368
}
6469

65-
info = new CodeBlock(startAddress.Value, methodDesc, relativeOffset, rangeSection.Data!.JitManager);
70+
info = new CodeBlock(startAddress, methodDesc, relativeOffset, rangeSection.Data!.JitManager);
6671
return true;
6772
}
6873

@@ -336,7 +341,7 @@ public override void GetExceptionClauses(RangeSection range, CodeBlockHandle cbh
336341
private void GetMethodRVAAndRangeStart(CodeBlockHandle cbh, out TargetPointer methodStart, out TargetPointer rangeStart)
337342
{
338343
IExecutionManager executionManager = Target.Contracts.ExecutionManager;
339-
methodStart = CodePointerUtils.AddressFromCodePointer(executionManager.GetStartAddress(cbh), Target);
344+
methodStart = executionManager.GetStartAddress(cbh);
340345
rangeStart = executionManager.GetUnwindInfoBaseAddress(cbh);
341346
}
342347

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ public void Flush()
4242
// Note, because of RelativeOffset, this code info is per code pointer, not per method
4343
private sealed class CodeBlock
4444
{
45-
public TargetCodePointer StartAddress { get; }
45+
public TargetPointer StartAddress { get; }
4646
public TargetPointer MethodDescAddress { get; }
4747
public TargetPointer JitManagerAddress { get; }
4848
public TargetNUInt RelativeOffset { get; }
49-
public CodeBlock(TargetCodePointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress)
49+
public CodeBlock(TargetPointer startAddress, TargetPointer methodDesc, TargetNUInt relativeOffset, TargetPointer jitManagerAddress)
5050
{
5151
StartAddress = startAddress;
5252
MethodDescAddress = methodDesc;
@@ -235,15 +235,15 @@ TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle)
235235
return info.MethodDescAddress;
236236
}
237237

238-
TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle)
238+
TargetPointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle)
239239
{
240240
if (!_codeInfos.TryGetValue(codeInfoHandle.Address, out CodeBlock? info))
241241
throw new InvalidOperationException($"{nameof(CodeBlock)} not found for {codeInfoHandle.Address}");
242242

243243
return info.StartAddress;
244244
}
245245

246-
TargetCodePointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeInfoHandle)
246+
TargetPointer IExecutionManager.GetFuncletStartAddress(CodeBlockHandle codeInfoHandle)
247247
{
248248
RangeSection range = RangeSectionFromCodeBlockHandle(codeInfoHandle);
249249
if (range.Data == null)
@@ -319,12 +319,11 @@ TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer ent
319319
bool IExecutionManager.IsFunclet(CodeBlockHandle codeInfoHandle)
320320
{
321321
// Interpreter code has no native unwind info and therefore no funclets.
322-
TargetCodePointer startAddress = ((IExecutionManager)this).GetStartAddress(codeInfoHandle);
323-
if (((IExecutionManager)this).GetCodeKind(startAddress) == CodeKind.Interpreter)
322+
TargetPointer startAddress = ((IExecutionManager)this).GetStartAddress(codeInfoHandle);
323+
if (((IExecutionManager)this).GetCodeKind(new TargetCodePointer(startAddress.Value)) == CodeKind.Interpreter)
324324
return false;
325325

326-
return startAddress !=
327-
((IExecutionManager)this).GetFuncletStartAddress(codeInfoHandle);
326+
return startAddress != ((IExecutionManager)this).GetFuncletStartAddress(codeInfoHandle);
328327
}
329328

330329
bool IExecutionManager.IsFilterFunclet(CodeBlockHandle codeInfoHandle)
@@ -337,7 +336,7 @@ bool IExecutionManager.IsFilterFunclet(CodeBlockHandle codeInfoHandle)
337336
if (!eman.IsFunclet(codeInfoHandle))
338337
return false;
339338

340-
TargetPointer funcletStartAddress = eman.GetFuncletStartAddress(codeInfoHandle).AsTargetPointer;
339+
TargetPointer funcletStartAddress = eman.GetFuncletStartAddress(codeInfoHandle);
341340
uint funcletStartOffset = (uint)(funcletStartAddress - info.StartAddress);
342341

343342
List<ExceptionClauseInfo> clauses = eman.GetExceptionClauses(codeInfoHandle);

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ internal ExecutionManager_1(Target target)
1919

2020
public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip);
2121
public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle);
22-
public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle);
23-
public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle);
22+
public TargetPointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle);
23+
public TargetPointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle);
2424
public void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) => _executionManagerCore.GetMethodRegionInfo(codeInfoHandle, out hotSize, out coldStart, out coldSize);
2525
public TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => _executionManagerCore.NonVirtualEntry2MethodDesc(entrypoint);
2626
public bool IsFunclet(CodeBlockHandle codeInfoHandle) => _executionManagerCore.IsFunclet(codeInfoHandle);

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ internal ExecutionManager_2(Target target)
1919

2020
public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip);
2121
public TargetPointer GetMethodDesc(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetMethodDesc(codeInfoHandle);
22-
public TargetCodePointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle);
23-
public TargetCodePointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle);
22+
public TargetPointer GetStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetStartAddress(codeInfoHandle);
23+
public TargetPointer GetFuncletStartAddress(CodeBlockHandle codeInfoHandle) => _executionManagerCore.GetFuncletStartAddress(codeInfoHandle);
2424
public void GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out uint hotSize, out TargetPointer coldStart, out uint coldSize) => _executionManagerCore.GetMethodRegionInfo(codeInfoHandle, out hotSize, out coldStart, out coldSize);
2525
public TargetPointer NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) => _executionManagerCore.NonVirtualEntry2MethodDesc(entrypoint);
2626
public bool IsFunclet(CodeBlockHandle codeInfoHandle) => _executionManagerCore.IsFunclet(codeInfoHandle);

0 commit comments

Comments
 (0)