Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cdac] Read contract descriptor from target #101208

Merged
merged 9 commits into from
Apr 22, 2024
10 changes: 7 additions & 3 deletions src/coreclr/debug/daccess/daccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include <dactablerva.h>
#else
extern "C" bool TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress);
// cDAC depends on symbol lookup to find the contract descriptor
#define CAN_USE_CDAC
#endif

#include "dwbucketmanager.hpp"
Expand Down Expand Up @@ -5493,15 +5495,16 @@ ClrDataAccess::Initialize(void)
IfFailRet(GetDacGlobalValues());
IfFailRet(DacGetHostVtPtrs());

// TODO: [cdac] TryGetSymbol is only implemented for Linux, OSX, and Windows.
#ifdef CAN_USE_CDAC
CLRConfigNoCache enable = CLRConfigNoCache::Get("ENABLE_CDAC");
if (enable.IsSet())
{
DWORD val;
if (enable.TryAsInteger(10, val) && val == 1)
{
// TODO: [cdac] Get contract descriptor from exported symbol
uint64_t contractDescriptorAddr = 0;
//if (TryGetSymbol(m_pTarget, m_globalBase, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr))
if (TryGetSymbol(m_pTarget, m_globalBase, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr))
{
m_cdac = CDAC::Create(contractDescriptorAddr, m_pTarget);
if (m_cdac.IsValid())
Expand All @@ -5514,6 +5517,7 @@ ClrDataAccess::Initialize(void)
}
}
}
#endif

//
// DAC is now setup and ready to use
Expand Down Expand Up @@ -6946,7 +6950,7 @@ GetDacTableAddress(ICorDebugDataTarget* dataTarget, ULONG64 baseAddress, PULONG6
return E_INVALIDARG;
}
#endif
// On MacOS, FreeBSD or NetBSD use the RVA include file
// On FreeBSD, NetBSD, or SunOS use the RVA include file
*dacTableAddress = baseAddress + DAC_TABLE_RVA;
#else
// Otherwise, try to get the dac table address via the export symbol
Expand Down
21 changes: 18 additions & 3 deletions src/native/managed/cdacreader/src/ContractDescriptorParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand All @@ -23,13 +24,15 @@ public partial class ContractDescriptorParser
/// <summary>
/// Parses the "compact" representation of a contract descriptor.
/// </summary>
// Workaround for https://github.com/dotnet/runtime/issues/101205
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Root))]
public static ContractDescriptor? ParseCompact(ReadOnlySpan<byte> json)
{
return JsonSerializer.Deserialize(json, ContractDescriptorContext.Default.ContractDescriptor);
}

[JsonSerializable(typeof(ContractDescriptor))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(int?))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(Dictionary<string, int>))]
[JsonSerializable(typeof(Dictionary<string, TypeDescriptor>))]
Expand All @@ -38,11 +41,17 @@ public partial class ContractDescriptorParser
[JsonSerializable(typeof(TypeDescriptor))]
[JsonSerializable(typeof(FieldDescriptor))]
[JsonSerializable(typeof(GlobalDescriptor))]
[JsonSerializable(typeof(Dictionary<string, JsonElement>))]
[JsonSourceGenerationOptions(AllowTrailingCommas = true,
DictionaryKeyPolicy = JsonKnownNamingPolicy.Unspecified, // contracts, types and globals are case sensitive
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
ReadCommentHandling = JsonCommentHandling.Skip)]
ReadCommentHandling = JsonCommentHandling.Skip,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement,
Converters = [typeof(TypeDescriptorConverter),
typeof(FieldDescriptorConverter),
typeof(GlobalDescriptorConverter)])]
internal sealed partial class ContractDescriptorContext : JsonSerializerContext
{
}
Expand All @@ -58,7 +67,13 @@ public class ContractDescriptor
public Dictionary<string, GlobalDescriptor>? Globals { get; set; }

[JsonExtensionData]
public Dictionary<string, object?>? Extras { get; set; }
public Dictionary<string, JsonElement>? Extras { get; set; }

public override string ToString()
{
return $"Version: {Version}, Baseline: {Baseline}, Contracts: {Contracts?.Count}, Types: {Types?.Count}, Globals: {Globals?.Count}";
}

}

[JsonConverter(typeof(TypeDescriptorConverter))]
Expand Down
12 changes: 12 additions & 0 deletions src/native/managed/cdacreader/src/Root.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Serialization;

namespace Microsoft.Diagnostics.DataContractReader;

internal static class Root
{
// https://github.com/dotnet/runtime/issues/101205
public static JsonDerivedTypeAttribute[] R1 = new JsonDerivedTypeAttribute[] { null! };
}
130 changes: 119 additions & 11 deletions src/native/managed/cdacreader/src/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Buffers.Binary;
using System.Collections.Generic;

namespace Microsoft.Diagnostics.DataContractReader;

Expand All @@ -16,49 +17,156 @@ public struct TargetPointer

internal sealed unsafe class Target
{
private const int StackAllocByteThreshold = 1024;

private readonly delegate* unmanaged<ulong, byte*, uint, void*, int> _readFromTarget;
private readonly void* _readContext;

private bool _isLittleEndian;
private int _pointerSize;

public Target(ulong _, delegate* unmanaged<ulong, byte*, uint, void*, int> readFromTarget, void* readContext)
private TargetPointer[] _pointerData = [];
private IReadOnlyDictionary<string, int> _contracts = new Dictionary<string, int>();

public Target(ulong contractDescriptor, delegate* unmanaged<ulong, byte*, uint, void*, int> readFromTarget, void* readContext)
{
_readFromTarget = readFromTarget;
_readContext = readContext;

// TODO: [cdac] Populate from descriptor
_isLittleEndian = BitConverter.IsLittleEndian;
_pointerSize = IntPtr.Size;
ReadContractDescriptor(contractDescriptor);
}

// See docs/design/datacontracts/contract-descriptor.md
private void ReadContractDescriptor(ulong address)
{
// Magic - uint64_t
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
if (ReadFromTarget(address, buffer) < 0)
throw new InvalidOperationException("Failed to read magic.");

address += sizeof(ulong);
ReadOnlySpan<byte> magicLE = "DNCCDAC\0"u8;
ReadOnlySpan<byte> magicBE = "\0CADCCND"u8;
_isLittleEndian = buffer.SequenceEqual(magicLE);
if (!_isLittleEndian && !buffer.SequenceEqual(magicBE))
throw new InvalidOperationException("Invalid magic.");

// Flags - uint32_t
uint flags = ReadUInt32(address);
address += sizeof(uint);

// Bit 1 represents the pointer size. 0 = 64-bit, 1 = 32-bit.
_pointerSize = (int)(flags & 0x2) == 0 ? sizeof(ulong) : sizeof(uint);

// Descriptor size - uint32_t
uint descriptorSize = ReadUInt32(address);
address += sizeof(uint);

// Descriptor - char*
TargetPointer descriptor = ReadPointer(address);
address += (uint)_pointerSize;

// Pointer data count - uint32_t
uint pointerDataCount = ReadUInt32(address);
address += sizeof(uint);

// Padding
address += sizeof(uint);

// Pointer data - uintptr_t*
TargetPointer pointerData = ReadPointer(address);

// Read descriptor
Span<byte> descriptorBuffer = descriptorSize <= StackAllocByteThreshold
? stackalloc byte[(int)descriptorSize]
: new byte[(int)descriptorSize];
if (ReadFromTarget(descriptor.Value, descriptorBuffer) < 0)
throw new InvalidOperationException("Failed to read descriptor.");

ContractDescriptorParser.ContractDescriptor? targetDescriptor = ContractDescriptorParser.ParseCompact(descriptorBuffer);

if (targetDescriptor is null)
{
throw new InvalidOperationException("Failed to parse descriptor.");
}

// TODO: [cdac] Read globals and types
// note: we will probably want to store the globals and types into a more usable form
_contracts = targetDescriptor.Contracts ?? new Dictionary<string, int>();

// Read pointer data
_pointerData = new TargetPointer[pointerDataCount];
for (int i = 0; i < pointerDataCount; i++)
{
_pointerData[i] = ReadPointer(pointerData.Value + (uint)(i * _pointerSize));
}
}

public uint ReadUInt32(ulong address)
{
if (!TryReadUInt32(address, out uint value))
throw new InvalidOperationException($"Failed to read uint32 at 0x{address:x8}.");

return value;
}

public bool TryReadUInt32(ulong address, out uint value)
{
value = 0;

Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (ReadFromTarget(address, buffer) < 0)
return false;

value = _isLittleEndian
? BinaryPrimitives.ReadUInt32LittleEndian(buffer)
: BinaryPrimitives.ReadUInt32BigEndian(buffer);

return true;
}

public TargetPointer ReadPointer(ulong address)
{
if (!TryReadPointer(address, out TargetPointer pointer))
throw new InvalidOperationException($"Failed to read pointer at 0x{address:x8}.");

return pointer;
}

public bool TryReadPointer(ulong address, out TargetPointer pointer)
{
pointer = TargetPointer.Null;

byte* buffer = stackalloc byte[_pointerSize];
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(buffer, _pointerSize);
if (ReadFromTarget(address, buffer, (uint)_pointerSize) < 0)
Span<byte> buffer = stackalloc byte[_pointerSize];
if (ReadFromTarget(address, buffer) < 0)
return false;

if (_pointerSize == sizeof(uint))
{
pointer = new TargetPointer(
_isLittleEndian
? BinaryPrimitives.ReadUInt32LittleEndian(span)
: BinaryPrimitives.ReadUInt32BigEndian(span));
? BinaryPrimitives.ReadUInt32LittleEndian(buffer)
: BinaryPrimitives.ReadUInt32BigEndian(buffer));
}
else if (_pointerSize == sizeof(ulong))
{
pointer = new TargetPointer(
_isLittleEndian
? BinaryPrimitives.ReadUInt64LittleEndian(span)
: BinaryPrimitives.ReadUInt64BigEndian(span));
? BinaryPrimitives.ReadUInt64LittleEndian(buffer)
: BinaryPrimitives.ReadUInt64BigEndian(buffer));
}

return true;
}

private int ReadFromTarget(ulong address, Span<byte> buffer)
{
fixed (byte* bufferPtr = buffer)
{
return _readFromTarget(address, bufferPtr, (uint)buffer.Length, _readContext);
}
}

private int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead)
=> _readFromTarget(address, buffer, bytesToRead, _readContext);
}
1 change: 1 addition & 0 deletions src/native/managed/cdacreader/src/cdacreader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<!-- Do not produce a public package. This ships as part of the runtime -->
<IsShippingPackage>false</IsShippingPackage>
<InvariantGlobalization>true</InvariantGlobalization>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Text.Json;
using System.Text.Unicode;
using Xunit;

Expand All @@ -12,6 +13,7 @@ public class ContractDescriptorParserTests
[Fact]
public void ParsesEmptyContract()
{
Assert.False(JsonSerializer.IsReflectionEnabledByDefault);
ReadOnlySpan<byte> json = "{}"u8;
ContractDescriptorParser.ContractDescriptor descriptor = ContractDescriptorParser.ParseCompact(json);
Assert.Null(descriptor.Version);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>


Expand Down
Loading