diff --git a/docs/design/datacontracts/ExecutionManager.md b/docs/design/datacontracts/ExecutionManager.md index 9513b50818058c..8c2414d1da7d31 100644 --- a/docs/design/datacontracts/ExecutionManager.md +++ b/docs/design/datacontracts/ExecutionManager.md @@ -617,7 +617,7 @@ Unwind info (`RUNTIME_FUNCTION`) uses relative addressing. For managed code, the * For Jitted code (`EEJitManager`) a pointer to the `DebugInfo` is stored on the `RealCodeHeader` which is accessed in the same way as `GetMethodInfo` described above. `hasFlagByte` is `true` if either the global `FeatureOnStackReplacement` is `true` or `StoreRichDebugInfo` is `true` on the `EEJitManager`. -* For R2R code (`ReadyToRunJitManager`) the `DebugInfo` is stored as part of the R2R image. The relevant `ReadyToRunInfo` stores a pointer to the an `ImageDataDirectory` representing the `DebugInfo` directory. Read the `VirtualAddress` of this data directory as a `NativeArray` containing the `DebugInfos`. To find the specific `DebugInfo`, index into the array using the `index` of the beginning of the R2R function as found like in `GetMethodInfo` above. This yields an offset `offset` value relative to the image base. Read the first variable length uint at `imageBase + offset`, `lookBack`. If `lookBack != 0`, return `imageBase + offset - lookback`. Otherwise return `offset + size of reading lookback`. +* For R2R code (`ReadyToRunJitManager`) the `DebugInfo` is stored as part of the R2R image. The relevant `ReadyToRunInfo` stores a pointer to the an `ImageDataDirectory` representing the `DebugInfo` directory. If this pointer is null (the image was compiled without debug info, e.g. with `--strip-debug-info`), return `TargetPointer.Null`. Otherwise, read the `VirtualAddress` of this data directory as a `NativeArray` containing the `DebugInfos`. To find the specific `DebugInfo`, index into the array using the `index` of the beginning of the R2R function as found like in `GetMethodInfo` above. This yields an offset `offset` value relative to the image base. Read the first variable length uint at `imageBase + offset`, `lookBack`. If `lookBack != 0`, return `imageBase + offset - lookback`. Otherwise return `offset + size of reading lookback`. For R2R images, `hasFlagByte` is always `false`. * For interpreted code (`InterpreterJitManager`), a pointer to the `DebugInfo` is stored on the `InterpreterRealCodeHeader` which is accessed in the same way as the EE JitManager's `GetMethodInfo` (nibble map lookup followed by code header read). `hasFlagByte` is always `false`. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs index be7c32c517bce9..f73dd851d5c382 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.ReadyToRunJitManager.cs @@ -125,6 +125,11 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode // ReadyToRunJitManager::GetDebugInfo Data.ReadyToRunInfo r2rInfo = GetReadyToRunInfo(rangeSection); + + // Images compiled with --strip-debug-info have no DebugInfo section + if (r2rInfo.DebugInfoSection == TargetPointer.Null) + return TargetPointer.Null; + if (!GetRuntimeFunction(rangeSection, r2rInfo, jittedCodeAddress, out TargetPointer codeBase, out uint index)) return TargetPointer.Null; diff --git a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs index ef4ba1257978aa..aff57ebe5864e9 100644 --- a/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ExecutionManager/ExecutionManagerTests.cs @@ -332,6 +332,73 @@ public void GetMethodDesc_R2R_OneRuntimeFunction(string version, MockTarget.Arch } } + [Theory] + [MemberData(nameof(StdArchAllVersions))] + public void GetDebugInfo_R2R_NoDebugInfoSection_ReturnsNull(string version, MockTarget.Architecture arch) + { + const ulong CodeRangeStart = 0x0a0a_0000u; + const uint CodeRangeSize = 0xc000u; + const ulong JitManagerAddress = 0x000b_ff00; + const ulong MethodDescAddress = 0x0101_aaa0; + const uint RuntimeFunction = 0x100; + const uint CodeOffset = 4; + // Make the low addresses readable, as linear address 0 is on wasm, so that dereferencing + // a null DebugInfoSection decodes zeros instead of failing the read. + const ulong ReadableZeroPageSize = 0x1000; + + MockExecutionManagerBuilder emBuilder = new(version, arch, MockExecutionManagerBuilder.DefaultAllocationRange); + MockExecutionManagerBuilder.JittedCodeRange jittedCode = emBuilder.AllocateJittedCodeRange(CodeRangeStart, CodeRangeSize); + MockReadyToRunInfo r2rInfo = emBuilder.AddReadyToRunInfo([RuntimeFunction], []); + new MockHashMapBuilder(emBuilder.Builder).PopulatePtrMap( + r2rInfo.EntryPointToMethodDescMapAddress, + [(jittedCode.RangeStart + RuntimeFunction, MethodDescAddress)]); + MockLoaderModule r2rModule = emBuilder.AddReadyToRunModule(r2rInfo.Address); + MockRangeSection rangeSection = emBuilder.AddReadyToRunRangeSection(jittedCode, JitManagerAddress, r2rModule.Address); + _ = emBuilder.AddRangeSectionFragment(jittedCode, rangeSection.Address); + Assert.Equal(0ul, r2rInfo.DebugInfoSection); + + TargetCodePointer methodStart = new(CodeRangeStart + RuntimeFunction); + TargetCodePointer pCode = new(CodeRangeStart + RuntimeFunction + CodeOffset); + NativeCodeVersionHandle nativeCodeVersion = NativeCodeVersionHandle.CreateSynthetic(new TargetPointer(MethodDescAddress)); + Mock codeVersions = new(); + codeVersions.Setup(c => c.GetNativeCodeVersionForIP(pCode)).Returns(nativeCodeVersion); + codeVersions.Setup(c => c.GetNativeCode(nativeCodeVersion)).Returns(methodStart); + + MockMemorySpace.MemoryContext memoryContext = emBuilder.Builder.GetMemoryContext(); + Target target = CreateTarget(emBuilder, configureTarget: targetBuilder => targetBuilder + .UseReader(ReadWithReadableZeroPage) + .AddContract(version: "c1") + .AddMockContract(codeVersions)); + IExecutionManager em = target.Contracts.ExecutionManager; + + CodeBlockHandle? handle = em.GetCodeBlockHandle(pCode); + Assert.NotNull(handle); + TargetPointer debugInfo = em.GetDebugInfo(handle.Value, out bool hasFlagByte); + Assert.Equal(TargetPointer.Null, debugInfo); + Assert.False(hasFlagByte); + + IDebugInfo debugInfoContract = target.Contracts.DebugInfo; + Assert.False(debugInfoContract.HasDebugInfo(pCode)); + Assert.Empty(debugInfoContract.GetMethodVarInfo(pCode, out uint varInfoCodeOffset)); + Assert.Equal(CodeOffset, varInfoCodeOffset); + Assert.Empty(debugInfoContract.GetMethodNativeMap(pCode, preferUninstrumented: false, out uint nativeMapCodeOffset)); + Assert.Equal(CodeOffset, nativeMapCodeOffset); + + int ReadWithReadableZeroPage(ulong address, Span buffer) + { + if (address < ReadableZeroPageSize) + { + if (address + (ulong)buffer.Length > ReadableZeroPageSize) + return -1; + + buffer.Clear(); + return 0; + } + + return memoryContext.ReadFromTarget(address, buffer); + } + } + [Fact] public void GetMethodDesc_R2R_WasmVirtualIPRangeList_ResolvesCapturedShape() {