Skip to content

Commit 2d335f3

Browse files
authored
[cdac] Read/store globals from contract descriptor (#101450)
- Map indirect global values to corresponding values in `pointer_data` array from contract descriptor - Add `[Try]Read<T>`, `[Try]ReadPointer`, `[Try]ReadGlobal<T>` and `[Try]ReadGlobalPointer` to `Target` - Make cDAC implementation of `ISOSDacInterface9.GetBreakingChangeVersion` read value from globals - Create test helpers for mocking out reading from the target and providing a contract descriptor for different bitness/endianness - Add unit tests for reading global values (direct and indirect)
1 parent 264023e commit 2d335f3

File tree

8 files changed

+507
-54
lines changed

8 files changed

+507
-54
lines changed

docs/design/datacontracts/contract-descriptor.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ reserved bits should be written as zero. Diagnostic tooling may ignore non-zero
4545

4646
The `descriptor` is a pointer to a UTF-8 JSON string described in [data descriptor physical layout](./data_descriptor.md#Physical_JSON_descriptor). The total number of bytes is given by `descriptor_size`.
4747

48-
The auxiliary data for the JSON descriptor is stored at the location `aux_data` in `aux_data_count` pointer-sized slots.
48+
The auxiliary data for the JSON descriptor is stored at the location `pointer_data` in `pointer_data_count` pointer-sized slots.
4949

5050
### Architecture properties
5151

@@ -83,7 +83,7 @@ a JSON integer constant.
8383
"globals":
8484
{
8585
"FEATURE_COMINTEROP": 0,
86-
"s_pThreadStore": [ 0 ] // indirect from aux data offset 0
86+
"s_pThreadStore": [ 0 ] // indirect from pointer data offset 0
8787
},
8888
"contracts": {"Thread": 1, "GCHandle": 1, "ThreadStore": 1}
8989
}

src/coreclr/debug/daccess/cdac.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,31 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target)
4141
{
4242
HMODULE cdacLib;
4343
if (!TryLoadCDACLibrary(&cdacLib))
44-
return CDAC::Invalid();
44+
return {};
4545

46-
return CDAC{cdacLib, descriptorAddr, target};
46+
decltype(&cdac_reader_init) init = reinterpret_cast<decltype(&cdac_reader_init)>(::GetProcAddress(cdacLib, "cdac_reader_init"));
47+
_ASSERTE(init != nullptr);
48+
49+
intptr_t handle;
50+
if (init(descriptorAddr, &ReadFromTargetCallback, target, &handle) != 0)
51+
{
52+
::FreeLibrary(cdacLib);
53+
return {};
54+
}
55+
56+
return CDAC{cdacLib, handle, target};
4757
}
4858

49-
CDAC::CDAC(HMODULE module, uint64_t descriptorAddr, ICorDebugDataTarget* target)
50-
: m_module(module)
59+
CDAC::CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target)
60+
: m_module{module}
61+
, m_cdac_handle{handle}
5162
, m_target{target}
5263
{
53-
if (m_module == NULL)
54-
{
55-
m_cdac_handle = 0;
56-
return;
57-
}
64+
_ASSERTE(m_module != NULL && m_cdac_handle != 0 && m_target != NULL);
5865

5966
m_target->AddRef();
60-
decltype(&cdac_reader_init) init = reinterpret_cast<decltype(&cdac_reader_init)>(::GetProcAddress(m_module, "cdac_reader_init"));
6167
decltype(&cdac_reader_get_sos_interface) getSosInterface = reinterpret_cast<decltype(&cdac_reader_get_sos_interface)>(::GetProcAddress(m_module, "cdac_reader_get_sos_interface"));
62-
_ASSERTE(init != nullptr && getSosInterface != nullptr);
63-
64-
init(descriptorAddr, &ReadFromTargetCallback, m_target, &m_cdac_handle);
68+
_ASSERTE(getSosInterface != nullptr);
6569
getSosInterface(m_cdac_handle, &m_sos);
6670
}
6771

src/coreclr/debug/daccess/cdac.h

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@ class CDAC final
99
public: // static
1010
static CDAC Create(uint64_t descriptorAddr, ICorDebugDataTarget *pDataTarget);
1111

12-
static CDAC Invalid()
13-
{
14-
return CDAC{nullptr, 0, nullptr};
15-
}
16-
1712
public:
13+
CDAC() = default;
14+
1815
CDAC(const CDAC&) = delete;
1916
CDAC& operator=(const CDAC&) = delete;
2017

@@ -56,7 +53,7 @@ class CDAC final
5653
IUnknown* SosInterface();
5754

5855
private:
59-
CDAC(HMODULE module, uint64_t descriptorAddr, ICorDebugDataTarget* target);
56+
CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target);
6057

6158
private:
6259
HMODULE m_module;

src/coreclr/debug/daccess/daccess.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3038,7 +3038,7 @@ class DacStreamManager
30383038
//----------------------------------------------------------------------------
30393039

30403040
ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLegacyTarget/*=0*/)
3041-
: m_cdac{CDAC::Invalid()}
3041+
: m_cdac{}
30423042
{
30433043
SUPPORTS_DAC_HOST_ONLY; // ctor does no marshalling - don't check with DacCop
30443044

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
namespace Microsoft.Diagnostics.DataContractReader;
5+
6+
internal static class Constants
7+
{
8+
internal static class Globals
9+
{
10+
// See src/coreclr/debug/runtimeinfo/datadescriptor.h
11+
internal const string SOSBreakingChangeVersion = nameof(SOSBreakingChangeVersion);
12+
}
13+
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ public SOSDacImpl(Target target)
3535

3636
public int GetBreakingChangeVersion()
3737
{
38-
// TODO: Return non-hard-coded version
39-
return 4;
38+
return _target.ReadGlobal<byte>(Constants.Globals.SOSBreakingChangeVersion);
4039
}
4140

4241
public unsafe int GetCCWData(ulong ccw, void* data) => HResults.E_NOTIMPL;

src/native/managed/cdacreader/src/Target.cs

Lines changed: 112 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System;
55
using System.Buffers.Binary;
66
using System.Collections.Generic;
7+
using System.Numerics;
8+
using System.Runtime.CompilerServices;
79

810
namespace Microsoft.Diagnostics.DataContractReader;
911

@@ -15,7 +17,7 @@ public struct TargetPointer
1517
public TargetPointer(ulong value) => Value = value;
1618
}
1719

18-
internal sealed unsafe class Target
20+
public sealed unsafe class Target
1921
{
2022
private const int StackAllocByteThreshold = 1024;
2123

@@ -29,7 +31,7 @@ private readonly struct Configuration
2931
private readonly Reader _reader;
3032

3133
private readonly IReadOnlyDictionary<string, int> _contracts = new Dictionary<string, int>();
32-
private readonly TargetPointer[] _pointerData = [];
34+
private readonly IReadOnlyDictionary<string, (ulong Value, string? Type)> _globals = new Dictionary<string, (ulong, string?)>();
3335

3436
public static bool TryCreate(ulong contractDescriptor, delegate* unmanaged<ulong, byte*, uint, void*, int> readFromTarget, void* readContext, out Target? target)
3537
{
@@ -49,11 +51,30 @@ private Target(Configuration config, ContractDescriptorParser.ContractDescriptor
4951
_config = config;
5052
_reader = reader;
5153

52-
// TODO: [cdac] Read globals and types
54+
// TODO: [cdac] Read types
5355
// note: we will probably want to store the globals and types into a more usable form
5456
_contracts = descriptor.Contracts ?? [];
5557

56-
_pointerData = pointerData;
58+
// Read globals and map indirect values to pointer data
59+
if (descriptor.Globals is not null)
60+
{
61+
Dictionary<string, (ulong Value, string? Type)> globals = [];
62+
foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.Globals)
63+
{
64+
ulong value = global.Value;
65+
if (global.Indirect)
66+
{
67+
if (value >= (ulong)pointerData.Length)
68+
throw new InvalidOperationException($"Invalid pointer data index {value}.");
69+
70+
value = pointerData[value].Value;
71+
}
72+
73+
globals[name] = (value, global.Type);
74+
}
75+
76+
_globals = globals;
77+
}
5778
}
5879

5980
// See docs/design/datacontracts/contract-descriptor.md
@@ -81,7 +102,7 @@ private static bool TryReadContractDescriptor(
81102
return false;
82103

83104
// Flags - uint32_t
84-
if (!TryReadUInt32(address, isLittleEndian, reader, out uint flags))
105+
if (!TryRead(address, isLittleEndian, reader, out uint flags))
85106
return false;
86107

87108
address += sizeof(uint);
@@ -92,7 +113,7 @@ private static bool TryReadContractDescriptor(
92113
config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize };
93114

94115
// Descriptor size - uint32_t
95-
if (!TryReadUInt32(address, config.IsLittleEndian, reader, out uint descriptorSize))
116+
if (!TryRead(address, config.IsLittleEndian, reader, out uint descriptorSize))
96117
return false;
97118

98119
address += sizeof(uint);
@@ -104,7 +125,7 @@ private static bool TryReadContractDescriptor(
104125
address += (uint)pointerSize;
105126

106127
// Pointer data count - uint32_t
107-
if (!TryReadUInt32(address, config.IsLittleEndian, reader, out uint pointerDataCount))
128+
if (!TryRead(address, config.IsLittleEndian, reader, out uint pointerDataCount))
108129
return false;
109130

110131
address += sizeof(uint);
@@ -138,30 +159,33 @@ private static bool TryReadContractDescriptor(
138159
return true;
139160
}
140161

141-
public uint ReadUInt32(ulong address)
162+
public T Read<T>(ulong address, out T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
142163
{
143-
if (!TryReadUInt32(address, out uint value))
144-
throw new InvalidOperationException($"Failed to read uint32 at 0x{address:x8}.");
164+
if (!TryRead(address, out value))
165+
throw new InvalidOperationException($"Failed to read {typeof(T)} at 0x{address:x8}.");
145166

146167
return value;
147168
}
148169

149-
public bool TryReadUInt32(ulong address, out uint value)
150-
=> TryReadUInt32(address, _config.IsLittleEndian, _reader, out value);
170+
public bool TryRead<T>(ulong address, out T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
171+
=> TryRead(address, _config.IsLittleEndian, _reader, out value);
151172

152-
private static bool TryReadUInt32(ulong address, bool isLittleEndian, Reader reader, out uint value)
173+
private static bool TryRead<T>(ulong address, bool isLittleEndian, Reader reader, out T value) where T : unmanaged, IBinaryInteger<T>, IMinMaxValue<T>
153174
{
154-
value = 0;
155-
156-
Span<byte> buffer = stackalloc byte[sizeof(uint)];
175+
value = default;
176+
Span<byte> buffer = stackalloc byte[sizeof(T)];
157177
if (reader.ReadFromTarget(address, buffer) < 0)
158178
return false;
159179

160-
value = isLittleEndian
161-
? BinaryPrimitives.ReadUInt32LittleEndian(buffer)
162-
: BinaryPrimitives.ReadUInt32BigEndian(buffer);
180+
return isLittleEndian
181+
? T.TryReadLittleEndian(buffer, !IsSigned<T>(), out value)
182+
: T.TryReadBigEndian(buffer, !IsSigned<T>(), out value);
183+
}
163184

164-
return true;
185+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
186+
private static bool IsSigned<T>() where T : struct, INumberBase<T>, IMinMaxValue<T>
187+
{
188+
return T.IsNegative(T.MinValue);
165189
}
166190

167191
public TargetPointer ReadPointer(ulong address)
@@ -183,21 +207,79 @@ private static bool TryReadPointer(ulong address, Configuration config, Reader r
183207
if (reader.ReadFromTarget(address, buffer) < 0)
184208
return false;
185209

186-
if (config.PointerSize == sizeof(uint))
210+
if (config.PointerSize == sizeof(uint)
211+
&& TryRead(address, config.IsLittleEndian, reader, out uint value32))
187212
{
188-
pointer = new TargetPointer(
189-
config.IsLittleEndian
190-
? BinaryPrimitives.ReadUInt32LittleEndian(buffer)
191-
: BinaryPrimitives.ReadUInt32BigEndian(buffer));
213+
pointer = new TargetPointer(value32);
214+
return true;
192215
}
193-
else if (config.PointerSize == sizeof(ulong))
216+
else if (config.PointerSize == sizeof(ulong)
217+
&& TryRead(address, config.IsLittleEndian, reader, out ulong value64))
194218
{
195-
pointer = new TargetPointer(
196-
config.IsLittleEndian
197-
? BinaryPrimitives.ReadUInt64LittleEndian(buffer)
198-
: BinaryPrimitives.ReadUInt64BigEndian(buffer));
219+
pointer = new TargetPointer(value64);
220+
return true;
199221
}
200222

223+
return false;
224+
}
225+
226+
public T ReadGlobal<T>(string name) where T : struct, INumber<T>
227+
{
228+
if (!TryReadGlobal(name, out T value))
229+
throw new InvalidOperationException($"Failed to read global {typeof(T)} '{name}'.");
230+
231+
return value;
232+
}
233+
234+
public bool TryReadGlobal<T>(string name, out T value) where T : struct, INumber<T>, INumberBase<T>
235+
{
236+
value = default;
237+
if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global))
238+
return false;
239+
240+
// TODO: [cdac] Move type validation out of the read such that it does not have to happen for every read
241+
if (global.Type is not null)
242+
{
243+
string? expectedType = Type.GetTypeCode(typeof(T)) switch
244+
{
245+
TypeCode.SByte => "int8",
246+
TypeCode.Byte => "uint8",
247+
TypeCode.Int16 => "int16",
248+
TypeCode.UInt16 => "uint16",
249+
TypeCode.Int32 => "int32",
250+
TypeCode.UInt32 => "uint32",
251+
TypeCode.Int64 => "int64",
252+
TypeCode.UInt64 => "uint64",
253+
_ => null,
254+
};
255+
if (expectedType is null || global.Type != expectedType)
256+
{
257+
return false;
258+
}
259+
}
260+
261+
value = T.CreateChecked(global.Value);
262+
return true;
263+
}
264+
265+
public TargetPointer ReadGlobalPointer(string name)
266+
{
267+
if (!TryReadGlobalPointer(name, out TargetPointer pointer))
268+
throw new InvalidOperationException($"Failed to read global pointer '{name}'.");
269+
270+
return pointer;
271+
}
272+
273+
public bool TryReadGlobalPointer(string name, out TargetPointer pointer)
274+
{
275+
pointer = TargetPointer.Null;
276+
if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global))
277+
return false;
278+
279+
if (global.Type is not null && Array.IndexOf(["pointer", "nint", "nuint"], global.Type) == -1)
280+
return false;
281+
282+
pointer = new TargetPointer(global.Value);
201283
return true;
202284
}
203285

0 commit comments

Comments
 (0)