Skip to content

Commit 19a02d5

Browse files
authored
[cDAC] Handle non-mapped images correctly (#114688)
* allow cDAC to properly read both mapped and flat images using PEReader
1 parent 56a9de3 commit 19a02d5

File tree

4 files changed

+87
-59
lines changed

4 files changed

+87
-59
lines changed

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Numerics;
88
using System.Reflection.Metadata;
9+
using System.Reflection.PortableExecutable;
910
using System.Runtime.InteropServices;
1011

1112
namespace Microsoft.Diagnostics.DataContractReader.Contracts;
@@ -16,11 +17,22 @@ internal sealed class EcmaMetadata_1(Target target) : IEcmaMetadata
1617

1718
public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle)
1819
{
19-
Data.Module module = target.ProcessedData.GetOrAdd<Data.Module>(handle.Address);
20+
ILoader loader = target.Contracts.Loader;
21+
22+
if (!loader.TryGetLoadedImageContents(handle, out TargetPointer baseAddress, out uint size, out uint imageFlags))
23+
{
24+
throw new InvalidOperationException("Module is not loaded.");
25+
}
26+
bool isMapped = (imageFlags & 0x1) != 0; // FLAG_MAPPED = 0x1
27+
PEStreamOptions isLoaded = isMapped ? PEStreamOptions.IsLoadedImage : PEStreamOptions.Default;
28+
29+
TargetStream stream = new(target, baseAddress, size);
30+
using PEReader peReader = new PEReader(stream, PEStreamOptions.PrefetchMetadata | isLoaded);
2031

21-
TargetPointer baseAddress = module.GetLoadedMetadata(out ulong size);
32+
int metadataStartOffset = peReader.PEHeaders.MetadataStartOffset;
33+
int metadataSize = peReader.PEHeaders.MetadataSize;
2234

23-
return new TargetSpan(baseAddress, size);
35+
return new TargetSpan(baseAddress + (ulong)metadataStartOffset, (ulong)metadataSize);
2436
}
2537

2638
public MetadataReader? GetMetadata(ModuleHandle handle)

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ internal sealed class Module : IData<Module>
1010
static Module IData<Module>.Create(Target target, TargetPointer address)
1111
=> new Module(target, address);
1212

13-
private readonly Target _target;
14-
1513
public Module(Target target, TargetPointer address)
1614
{
17-
_target = target;
1815
Target.TypeInfo type = target.GetTypeInfo(DataType.Module);
1916

2017
Flags = target.Read<uint>(address + (ulong)type.Fields[nameof(Flags)].Offset);
@@ -55,54 +52,4 @@ public Module(Target target, TargetPointer address)
5552
public TargetPointer TypeDefToMethodTableMap { get; init; }
5653
public TargetPointer TypeRefToMethodTableMap { get; init; }
5754
public TargetPointer MethodDefToILCodeVersioningStateMap { get; init; }
58-
59-
private TargetPointer _metadataStart = TargetPointer.Null;
60-
private ulong _metadataSize;
61-
public TargetPointer GetLoadedMetadata(out ulong size)
62-
{
63-
if (Base != TargetPointer.Null && _metadataStart == TargetPointer.Null && _metadataSize == 0)
64-
{
65-
int peSignatureOffset = _target.Read<int>(Base + PEFormat.DosStub.PESignatureOffset);
66-
ulong headerOffset = Base + (ulong)peSignatureOffset;
67-
ushort magic = _target.Read<ushort>(headerOffset + PEFormat.PEHeader.MagicOffset);
68-
ulong clrHeaderOffset = magic == (ushort)PEMagic.PE32
69-
? PEFormat.PEHeader.CLRRuntimeHeader32Offset
70-
: PEFormat.PEHeader.CLRRuntimeHeader32PlusOffset;
71-
int corHeaderRva = _target.Read<int>(headerOffset + clrHeaderOffset);
72-
73-
// Read RVA and size of the metadata
74-
ulong metadataDirectoryAddress = Base + (ulong)corHeaderRva + PEFormat.CorHeader.MetadataOffset;
75-
_metadataStart = Base + (ulong)_target.Read<int>(metadataDirectoryAddress);
76-
_metadataSize = (ulong)_target.Read<int>(metadataDirectoryAddress + sizeof(int));
77-
}
78-
79-
size = _metadataSize;
80-
return _metadataStart;
81-
}
82-
83-
// https://learn.microsoft.com/windows/win32/debug/pe-format
84-
private static class PEFormat
85-
{
86-
private const int PESignatureSize = sizeof(int);
87-
private const int CoffHeaderSize = 20;
88-
89-
public static class DosStub
90-
{
91-
public const int PESignatureOffset = 0x3c;
92-
}
93-
94-
public static class PEHeader
95-
{
96-
private const ulong OptionalHeaderOffset = PESignatureSize + CoffHeaderSize;
97-
public const ulong MagicOffset = OptionalHeaderOffset;
98-
public const ulong CLRRuntimeHeader32Offset = OptionalHeaderOffset + 208;
99-
public const ulong CLRRuntimeHeader32PlusOffset = OptionalHeaderOffset + 224;
100-
}
101-
102-
// See ECMA-335 II.25.3.3 CLI Header
103-
public static class CorHeader
104-
{
105-
public const ulong MetadataOffset = 8;
106-
}
107-
}
10855
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
7+
namespace Microsoft.Diagnostics.DataContractReader;
8+
9+
internal sealed class TargetStream(Target target, ulong startPosition, long size) : Stream
10+
{
11+
private readonly ulong _startPosition = startPosition;
12+
private long _offset;
13+
private readonly long _size = size;
14+
private readonly Target _target = target;
15+
16+
private ulong GlobalPosition => _startPosition + (ulong)_offset;
17+
18+
public override bool CanRead => true;
19+
20+
public override bool CanSeek => true;
21+
22+
public override bool CanWrite => false;
23+
24+
public override long Length => _size;
25+
26+
public override long Position { get => _offset; set => _offset = value; }
27+
28+
public override unsafe int Read(byte[] buffer, int offset, int count)
29+
{
30+
Span<byte> span = buffer;
31+
return Read(span.Slice(start: offset, length: count));
32+
}
33+
public override unsafe int Read(Span<byte> buffer)
34+
{
35+
_target.ReadBuffer(GlobalPosition, buffer);
36+
_offset += buffer.Length;
37+
return buffer.Length;
38+
}
39+
public override long Seek(long offset, SeekOrigin origin)
40+
{
41+
switch (origin)
42+
{
43+
case SeekOrigin.Begin:
44+
_offset = offset;
45+
break;
46+
case SeekOrigin.Current:
47+
_offset += offset;
48+
break;
49+
case SeekOrigin.End:
50+
throw new NotSupportedException();
51+
}
52+
return _offset;
53+
}
54+
55+
public override void Flush() { } // No-op for read-only stream
56+
public override void SetLength(long value) => throw new NotSupportedException();
57+
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
58+
}

src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -886,9 +886,20 @@ int ISOSDacInterface.GetModuleData(ulong moduleAddr, DacpModuleData* data)
886886
data->dwTransientFlags = (uint)flags;
887887

888888
data->ilBase = contract.GetILBase(handle);
889-
TargetSpan readOnlyMetadata = _target.Contracts.EcmaMetadata.GetReadOnlyMetadataAddress(handle);
890-
data->metadataStart = readOnlyMetadata.Address;
891-
data->metadataSize = readOnlyMetadata.Size;
889+
890+
try
891+
{
892+
TargetSpan readOnlyMetadata = _target.Contracts.EcmaMetadata.GetReadOnlyMetadataAddress(handle);
893+
data->metadataStart = readOnlyMetadata.Address;
894+
data->metadataSize = readOnlyMetadata.Size;
895+
}
896+
catch (System.Exception)
897+
{
898+
// if we are unable to read the metadata, to match the DAC behavior
899+
// set metadataStart and metadataSize to 0
900+
data->metadataStart = 0;
901+
data->metadataSize = 0;
902+
}
892903

893904
data->LoaderAllocator = contract.GetLoaderAllocator(handle);
894905

0 commit comments

Comments
 (0)