Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bfd964a

Browse files
[cdac] Synthesize a valid ECMA-335 image for read/write metadata instead of providing a separate metadata reader (#106164)
* Synthesize a valid ECMA-335 image for read/write metadata instead of providing a separate metadata reader * Remove copies * Add a comment * Create EcmaMetadata contract for handling all metadata retrieval and remove metadata handling completely from the Loader contract. * Add EcmaMetadata contract to CoreCLR's list of implemented contracts --------- Co-authored-by: Elinor Fung <[email protected]>
1 parent f9c0846 commit bfd964a

18 files changed

+815
-1902
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
# Contract EcmaMetadata
2+
3+
This contract provides methods to get a view of the ECMA-335 metadata for a given module.
4+
5+
## APIs of contract
6+
7+
```csharp
8+
TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle);
9+
System.Reflection.Metadata.MetadataReader? GetMetadata(ModuleHandle handle);
10+
```
11+
12+
Types from other contracts:
13+
14+
| Type | Contract |
15+
|------|----------|
16+
| ModuleHandle | [Loader](./Loader.md#apis-of-contract) |
17+
18+
## Version 1
19+
20+
21+
Data descriptors used:
22+
| Data Descriptor Name | Field | Meaning |
23+
| --- | --- | --- |
24+
| `Module` | `Base` | Pointer to start of PE file in memory |
25+
| `Module` | `DynamicMetadata` | Pointer to saved metadata for reflection emit modules |
26+
| `Module` | `FieldDefToDescMap` | Mapping table |
27+
| `DynamicMetadata` | `Size` | Size of the dynamic metadata blob (as a 32bit uint) |
28+
| `DynamicMetadata` | `Data` | Start of dynamic metadata data array |
29+
30+
31+
```csharp
32+
using System.IO;
33+
using System.Reflection.Metadata;
34+
using System.Runtime.InteropServices;
35+
36+
TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle)
37+
{
38+
TargetPointer baseAddress = Target.ReadPointer(handle.Address + /* Module::Base offset */);
39+
if (baseAddress == TargetPointer.Null)
40+
{
41+
return default;
42+
}
43+
44+
// Read CLR header per https://learn.microsoft.com/windows/win32/debug/pe-format
45+
ulong clrHeaderRVA = ...
46+
47+
// Read Metadata per ECMA-335 II.25.3.3 CLI Header
48+
ulong metadataDirectoryAddress = baseAddress + clrHeaderRva + /* offset to Metadata */
49+
int rva = Target.Read<int>(metadataDirectoryAddress);
50+
ulong size = Target.Read<int>(metadataDirectoryAddress + sizeof(int));
51+
return new(baseAddress + rva, size);
52+
}
53+
54+
MetadataReader? GetMetadata(ModuleHandle handle)
55+
{
56+
AvailableMetadataType type = GetAvailableMetadataType(handle);
57+
58+
switch (type)
59+
{
60+
case AvailableMetadataType.None:
61+
return null;
62+
case AvailableMetadataType.ReadOnly:
63+
{
64+
TargetSpan address = GetReadOnlyMetadataAddress(handle);
65+
byte[] data = new byte[address.Size];
66+
_target.ReadBuffer(address.Address, data);
67+
return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)).GetMetadataReader();
68+
}
69+
case AvailableMetadataType.ReadWriteSavedCopy:
70+
{
71+
TargetSpan address = GetReadWriteSavedMetadataAddress(handle);
72+
byte[] data = new byte[address.Size];
73+
_target.ReadBuffer(address.Address, data);
74+
return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)).GetMetadataReader();
75+
}
76+
case AvailableMetadataType.ReadWrite:
77+
{
78+
var targetEcmaMetadata = GetReadWriteMetadata(handle);
79+
80+
// From the multiple different target spans, we need to build a single
81+
// contiguous ECMA-335 metadata blob.
82+
BlobBuilder builder = new BlobBuilder();
83+
builder.WriteUInt32(0x424A5342);
84+
85+
// major version
86+
builder.WriteUInt16(1);
87+
88+
// minor version
89+
builder.WriteUInt16(1);
90+
91+
// reserved
92+
builder.WriteUInt32(0);
93+
94+
string version = targetEcmaMetadata.Schema.MetadataVersion;
95+
builder.WriteInt32(AlignUp(version.Length, 4));
96+
Write4ByteAlignedString(builder, version);
97+
98+
// reserved
99+
builder.WriteUInt16(0);
100+
101+
// number of streams
102+
ushort numStreams = 5; // #Strings, #US, #Blob, #GUID, #~ (metadata)
103+
if (targetEcmaMetadata.Schema.VariableSizedColumnsAreAll4BytesLong)
104+
{
105+
// We direct MetadataReader to use 4-byte encoding for all variable-sized columns
106+
// by providing the marker stream for a "minimal delta" image.
107+
numStreams++;
108+
}
109+
builder.WriteUInt16(numStreams);
110+
111+
// Write Stream headers
112+
if (targetEcmaMetadata.Schema.VariableSizedColumnsAreAll4BytesLong)
113+
{
114+
// Write the #JTD stream to indicate that all variable-sized columns are 4 bytes long.
115+
WriteStreamHeader(builder, "#JTD", 0).WriteInt32(builder.Count);
116+
}
117+
118+
BlobWriter stringsOffset = WriteStreamHeader(builder, "#Strings", (int)AlignUp(targetEcmaMetadata.StringHeap.Size, 4ul));
119+
BlobWriter blobOffset = WriteStreamHeader(builder, "#Blob", (int)targetEcmaMetadata.BlobHeap.Size);
120+
BlobWriter guidOffset = WriteStreamHeader(builder, "#GUID", (int)targetEcmaMetadata.GuidHeap.Size);
121+
BlobWriter userStringOffset = WriteStreamHeader(builder, "#US", (int)targetEcmaMetadata.UserStringHeap.Size);
122+
123+
// We'll use the "uncompressed" tables stream name as the runtime may have created the *Ptr tables
124+
// that are only present in the uncompressed tables stream.
125+
BlobWriter tablesOffset = WriteStreamHeader(builder, "#-", 0);
126+
127+
// Write the heap-style Streams
128+
129+
stringsOffset.WriteInt32(builder.Count);
130+
WriteTargetSpan(builder, targetEcmaMetadata.StringHeap);
131+
for (ulong i = targetEcmaMetadata.StringHeap.Size; i < AlignUp(targetEcmaMetadata.StringHeap.Size, 4ul); i++)
132+
{
133+
builder.WriteByte(0);
134+
}
135+
136+
blobOffset.WriteInt32(builder.Count);
137+
WriteTargetSpan(builder, targetEcmaMetadata.BlobHeap);
138+
139+
guidOffset.WriteInt32(builder.Count);
140+
WriteTargetSpan(builder, targetEcmaMetadata.GuidHeap);
141+
142+
userStringOffset.WriteInt32(builder.Count);
143+
WriteTargetSpan(builder, targetEcmaMetadata.UserStringHeap);
144+
145+
// Write tables stream
146+
tablesOffset.WriteInt32(builder.Count);
147+
148+
// Write tables stream header
149+
builder.WriteInt32(0); // reserved
150+
builder.WriteByte(2); // major version
151+
builder.WriteByte(0); // minor version
152+
uint heapSizes =
153+
(targetEcmaMetadata.Schema.LargeStringHeap ? 1u << 0 : 0) |
154+
(targetEcmaMetadata.Schema.LargeBlobHeap ? 1u << 1 : 0) |
155+
(targetEcmaMetadata.Schema.LargeGuidHeap ? 1u << 2 : 0);
156+
157+
builder.WriteByte((byte)heapSizes);
158+
builder.WriteByte(1); // reserved
159+
160+
ulong validTables = 0;
161+
for (int i = 0; i < targetEcmaMetadata.Schema.RowCount.Length; i++)
162+
{
163+
if (targetEcmaMetadata.Schema.RowCount[i] != 0)
164+
{
165+
validTables |= 1ul << i;
166+
}
167+
}
168+
169+
ulong sortedTables = 0;
170+
for (int i = 0; i < targetEcmaMetadata.Schema.IsSorted.Length; i++)
171+
{
172+
if (targetEcmaMetadata.Schema.IsSorted[i])
173+
{
174+
sortedTables |= 1ul << i;
175+
}
176+
}
177+
178+
builder.WriteUInt64(validTables);
179+
builder.WriteUInt64(sortedTables);
180+
181+
foreach (int rowCount in targetEcmaMetadata.Schema.RowCount)
182+
{
183+
if (rowCount > 0)
184+
{
185+
builder.WriteInt32(rowCount);
186+
}
187+
}
188+
189+
// Write the tables
190+
foreach (TargetSpan span in targetEcmaMetadata.Tables)
191+
{
192+
WriteTargetSpan(builder, span);
193+
}
194+
195+
MemoryStream metadataStream = new MemoryStream();
196+
builder.WriteContentTo(metadataStream);
197+
return MetadataReaderProvider.FromMetadataStream(metadataStream).GetMetadataReader();
198+
199+
void WriteTargetSpan(BlobBuilder builder, TargetSpan span)
200+
{
201+
Blob blob = builder.ReserveBytes(checked((int)span.Size));
202+
_target.ReadBuffer(span.Address, blob.GetBytes().AsSpan());
203+
}
204+
205+
static BlobWriter WriteStreamHeader(BlobBuilder builder, string name, int size)
206+
{
207+
BlobWriter offset = new(builder.ReserveBytes(4));
208+
builder.WriteInt32(size);
209+
Write4ByteAlignedString(builder, name);
210+
return offset;
211+
}
212+
213+
static void Write4ByteAlignedString(BlobBuilder builder, string value)
214+
{
215+
int bufferStart = builder.Count;
216+
builder.WriteUTF8(value);
217+
builder.WriteByte(0);
218+
int stringEnd = builder.Count;
219+
for (int i = stringEnd; i < bufferStart + AlignUp(value.Length, 4); i++)
220+
{
221+
builder.WriteByte(0);
222+
}
223+
}
224+
}
225+
}
226+
}
227+
```
228+
229+
### Helper Methods
230+
231+
``` csharp
232+
using System;
233+
using System.Numerics;
234+
235+
struct EcmaMetadataSchema
236+
{
237+
public EcmaMetadataSchema(string metadataVersion, bool largeStringHeap, bool largeBlobHeap, bool largeGuidHeap, int[] rowCount, bool[] isSorted, bool variableSizedColumnsAre4BytesLong)
238+
{
239+
MetadataVersion = metadataVersion;
240+
LargeStringHeap = largeStringHeap;
241+
LargeBlobHeap = largeBlobHeap;
242+
LargeGuidHeap = largeGuidHeap;
243+
244+
_rowCount = rowCount;
245+
_isSorted = isSorted;
246+
247+
VariableSizedColumnsAreAll4BytesLong = variableSizedColumnsAre4BytesLong;
248+
}
249+
250+
public readonly string MetadataVersion;
251+
252+
public readonly bool LargeStringHeap;
253+
public readonly bool LargeBlobHeap;
254+
public readonly bool LargeGuidHeap;
255+
256+
// Table data, these structures hold MetadataTable.Count entries
257+
private readonly int[] _rowCount;
258+
public readonly ReadOnlySpan<int> RowCount => _rowCount;
259+
260+
private readonly bool[] _isSorted;
261+
public readonly ReadOnlySpan<bool> IsSorted => _isSorted;
262+
263+
// In certain scenarios the size of the tables is forced to be the maximum size
264+
// Otherwise the size of columns should be computed based on RowSize/the various heap flags
265+
public readonly bool VariableSizedColumnsAreAll4BytesLong;
266+
}
267+
268+
class TargetEcmaMetadata
269+
{
270+
public TargetEcmaMetadata(EcmaMetadataSchema schema,
271+
TargetSpan[] tables,
272+
TargetSpan stringHeap,
273+
TargetSpan userStringHeap,
274+
TargetSpan blobHeap,
275+
TargetSpan guidHeap)
276+
{
277+
Schema = schema;
278+
_tables = tables;
279+
StringHeap = stringHeap;
280+
UserStringHeap = userStringHeap;
281+
BlobHeap = blobHeap;
282+
GuidHeap = guidHeap;
283+
}
284+
285+
public EcmaMetadataSchema Schema { get; init; }
286+
287+
private TargetSpan[] _tables;
288+
public ReadOnlySpan<TargetSpan> Tables => _tables;
289+
public TargetSpan StringHeap { get; init; }
290+
public TargetSpan UserStringHeap { get; init; }
291+
public TargetSpan BlobHeap { get; init; }
292+
public TargetSpan GuidHeap { get; init; }
293+
}
294+
295+
[Flags]
296+
enum AvailableMetadataType
297+
{
298+
None = 0,
299+
ReadOnly = 1,
300+
ReadWriteSavedCopy = 2,
301+
ReadWrite = 4
302+
}
303+
304+
AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle)
305+
{
306+
Data.Module module = new Data.Module(Target, handle.Address);
307+
308+
AvailableMetadataType flags = AvailableMetadataType.None;
309+
310+
TargetPointer dynamicMetadata = Target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */);
311+
312+
if (dynamicMetadata != TargetPointer.Null)
313+
flags |= AvailableMetadataType.ReadWriteSavedCopy;
314+
else
315+
flags |= AvailableMetadataType.ReadOnly;
316+
317+
return flags;
318+
}
319+
320+
TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle)
321+
{
322+
Data.Module module = new Data.Module(Target, handle.Address);
323+
TargetPointer dynamicMetadata = Target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */);
324+
325+
ulong size = Target.Read<uint>(handle.Address + /* DynamicMetadata::Size offset */);
326+
TargetPointer result = handle.Address + /* DynamicMetadata::Data offset */;
327+
return new(result, size);
328+
}
329+
330+
TargetEcmaMetadata GetReadWriteMetadata(ModuleHandle handle)
331+
{
332+
// [cdac] TODO.
333+
}
334+
335+
T AlignUp<T>(T input, T alignment)
336+
where T : IBinaryInteger<T>
337+
{
338+
return input + (alignment - T.One) & ~(alignment - T.One);
339+
}
340+
```

0 commit comments

Comments
 (0)