Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.DataContractReader;

/// <summary>
/// cDAC-specific <c>HRESULT</c>s surfaced when creating a data-access interface
/// for a target fails for a reason this cDAC can describe precisely. This covers two stages of
/// creation: locating and reading the target's contract descriptor (the <c>DESCRIPTOR</c> codes),
/// and eager validation that this cDAC can service the contracts the descriptor advertises (the
/// <c>CONTRACT</c> codes, see
/// <see cref="Microsoft.Diagnostics.DataContractReader.Contracts.CoreCLRContracts.ValidateForDataAccess"/>).
/// </summary>
/// <remarks>
/// The customer bit (bit 29, <c>0x20000000</c>) is set on every value so these codes can never
/// collide with system or CLR (corerror.h) <c>HRESULT</c>s. All values are failure codes, so
/// existing <c>FAILED(hr)</c> checks in native callers continue to behave correctly. Native code
/// and tooling that wants to distinguish the failure modes (for example, to decide whether to fall
/// back to the brittle DAC) can compare against the literal values documented here.
/// </remarks>
public static class CdacHResults
{
/// <summary>
/// A contract required to service the SOS interface is not advertised by the target's
/// contract descriptor. The target runtime predates the contract or does not expose it.
/// </summary>
public const int CDAC_E_CONTRACT_NOT_ADVERTISED = unchecked((int)0xA0DAC001);

/// <summary>
/// The target advertises a required contract at a version this cDAC does not recognize,
/// typically because the target runtime is newer than this cDAC.
/// </summary>
public const int CDAC_E_CONTRACT_UNRECOGNIZED = unchecked((int)0xA0DAC002);

/// <summary>
/// The target advertises a required contract at a version this cDAC recognizes but
/// intentionally does not implement, typically because the target runtime is too old.
/// </summary>
public const int CDAC_E_CONTRACT_UNSUPPORTED = unchecked((int)0xA0DAC003);

/// <summary>
/// No usable cDAC contract descriptor could be located for the target: the contract locator
/// did not produce a descriptor address, the descriptor header could not be read, or the bytes
/// at the descriptor address are not a contract descriptor (bad magic). This typically means the
/// target is not a cDAC-capable .NET runtime (for example, a non-.NET target or a runtime that
/// predates the cDAC contract descriptor), and tooling should fall back to the brittle DAC
/// rather than treat it as a hard error.
/// </summary>
public const int CDAC_E_DESCRIPTOR_NOT_FOUND = unchecked((int)0xA0DAC011);

/// <summary>
/// A cDAC contract descriptor was found but could not be consumed: a field could not be read,
/// the embedded JSON failed to parse, or the descriptor content is internally inconsistent
/// (for example, duplicate names or an out-of-range pointer-data index). This indicates a
/// corrupt dump or a descriptor format this cDAC cannot understand, and is distinct from a
/// recognized-but-unsupported contract version (<see cref="CDAC_E_CONTRACT_UNSUPPORTED"/>).
/// </summary>
public const int CDAC_E_DESCRIPTOR_MALFORMED = unchecked((int)0xA0DAC012);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.Diagnostics.DataContractReader;

/// <summary>
/// Base exception for failures to retrieve a data contract from a target.
/// </summary>
public abstract class ContractNotAvailableException : Exception
{
private const int E_NOTIMPL = unchecked((int)0x80004001);

/// <summary>
/// Initializes a new instance of the <see cref="ContractNotAvailableException"/> class.
/// </summary>
/// <param name="contractName">The name of the requested contract.</param>
/// <param name="contractVersion">The target-advertised version of the requested contract, or <see langword="null"/> if the target did not advertise the contract.</param>
/// <param name="message">The exception message.</param>
protected ContractNotAvailableException(string contractName, string? contractVersion, string message)
: base(message)
{
ContractName = contractName;
ContractVersion = contractVersion;
HResult = E_NOTIMPL;
}

/// <summary>
/// Gets the name of the requested contract.
/// </summary>
public string ContractName { get; }

/// <summary>
/// Gets the target-advertised version of the requested contract, or <see langword="null"/> if the target did not advertise the contract.
/// </summary>
public string? ContractVersion { get; }
}

/// <summary>
/// Exception thrown when the target's contract descriptor does not advertise the requested contract.
/// </summary>
public sealed class ContractMissingException : ContractNotAvailableException
{
/// <summary>
/// Initializes a new instance of the <see cref="ContractMissingException"/> class.
/// </summary>
/// <param name="contractName">The name of the requested contract.</param>
public ContractMissingException(string contractName)
: base(contractName, null, $"Contract '{contractName}' is not advertised by the target.")
{ }
}

/// <summary>
/// Exception thrown when the target advertises the requested contract, but this cDAC cannot provide an implementation for the advertised version.
/// </summary>
public abstract class ContractUnsupportedException : ContractNotAvailableException
{
/// <summary>
/// Initializes a new instance of the <see cref="ContractUnsupportedException"/> class.
/// </summary>
/// <param name="contractName">The name of the requested contract.</param>
/// <param name="contractVersion">The target-advertised version of the requested contract.</param>
/// <param name="message">The exception message.</param>
protected ContractUnsupportedException(string contractName, string contractVersion, string message)
: base(contractName, contractVersion, message)
{ }
}

/// <summary>
/// Exception thrown when the target advertises a contract version that this cDAC does not recognize, typically because the target runtime is newer than this cDAC.
/// </summary>
public sealed class ContractUnrecognizedException : ContractUnsupportedException
{
/// <summary>
/// Initializes a new instance of the <see cref="ContractUnrecognizedException"/> class.
/// </summary>
/// <param name="contractName">The name of the requested contract.</param>
/// <param name="contractVersion">The target-advertised version of the requested contract.</param>
public ContractUnrecognizedException(string contractName, string contractVersion)
: base(contractName, contractVersion, $"Contract '{contractName}' version {contractVersion} is advertised by the target but is not recognized by this cDAC.")
{ }
}

/// <summary>
/// Exception thrown when the target advertises a contract version that this cDAC recognizes but intentionally does not implement, typically because the target runtime is too old.
/// </summary>
public sealed class ContractObsoleteException : ContractUnsupportedException
{
/// <summary>
/// Initializes a new instance of the <see cref="ContractObsoleteException"/> class.
/// </summary>
/// <param name="contractName">The name of the requested contract.</param>
/// <param name="contractVersion">The target-advertised version of the requested contract.</param>
public ContractObsoleteException(string contractName, string contractVersion)
: base(contractName, contractVersion, $"Contract '{contractName}' version {contractVersion} is advertised by the target and recognized by this cDAC, but is intentionally not implemented.")
{ }
}

/// <summary>
/// Exception thrown when eager validation of the contracts required to service an SOS /
/// <c>IXCLRDataProcess</c> interface fails during creation. Unlike the lazy
/// <see cref="ContractNotAvailableException"/> hierarchy (which carries <c>E_NOTIMPL</c> so that
/// individual SOS APIs degrade gracefully), this exception carries a distinct <see cref="CdacHResults"/>
/// value identifying the failure category so the native loader can decide how to proceed.
/// </summary>
public sealed class ContractValidationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ContractValidationException"/> class.
/// </summary>
/// <param name="hResult">The <see cref="CdacHResults"/> value describing the validation failure category.</param>
/// <param name="inner">The underlying <see cref="ContractNotAvailableException"/> describing which contract failed and why.</param>
public ContractValidationException(int hResult, ContractNotAvailableException inner)
: base(inner.Message, inner)
{
HResult = hResult;
ContractName = inner.ContractName;
ContractVersion = inner.ContractVersion;
}

/// <summary>
/// Gets the name of the contract whose validation failed.
/// </summary>
public string ContractName { get; }

/// <summary>
/// Gets the target-advertised version of the contract whose validation failed, or <see langword="null"/> if the target did not advertise the contract.
/// </summary>
public string? ContractVersion { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,19 +157,19 @@ public abstract class ContractRegistry
/// <param name="contract">
/// When this method returns <see langword="true"/>, contains the requested contract instance; otherwise, <see langword="null"/>.
/// </param>
/// <param name="failureReason">
/// When this method returns <see langword="false"/>, contains a human-readable explanation of why the contract could not be retrieved; otherwise, <see langword="null"/>.
/// <param name="failureException">
/// When this method returns <see langword="false"/>, contains the exception that describes why the contract could not be retrieved; otherwise, <see langword="null"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the requested contract is present and was retrieved successfully; <see langword="false"/> if the contract is not present or registered"/>.
/// </returns>
public abstract bool TryGetContract<TContract>([NotNullWhen(true)] out TContract contract, out string? failureReason) where TContract : IContract;
public abstract bool TryGetContract<TContract>([NotNullWhen(true)] out TContract contract, [NotNullWhen(false)] out System.Exception? failureException) where TContract : IContract;
Comment on lines +160 to +166

public TContract GetContract<TContract>() where TContract : IContract
{
if (!TryGetContract(out TContract contract, out string? failureReason))
if (!TryGetContract(out TContract contract, out System.Exception? failureException))
{
throw new NotImplementedException($"Contract '{typeof(TContract).Name}' is not supported by the target. Reason: {failureReason ?? "no reason provided"}");
throw failureException ?? new ContractMissingException(TContract.Name);
}
return contract;
}
Expand All @@ -179,13 +179,44 @@ public bool TryGetContract<TContract>([NotNullWhen(true)] out TContract contract
return TryGetContract(out contract, out _);
}

/// <summary>
/// Determines whether this cDAC can provide the requested contract for the target. Implementations
/// should resolve the target-advertised version against the registered implementations only, without
/// instantiating the contract, so that validation performs no target memory reads (safe on partial or
/// triage dumps) and never triggers contract-to-contract chaining.
/// </summary>
/// <typeparam name="TContract">The contract type to validate.</typeparam>
/// <param name="failureException">
/// When this method returns <see langword="false"/>, contains the exception describing why the
/// contract cannot be provided; otherwise, <see langword="null"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the contract is advertised by the target and a matching implementation
/// is registered; otherwise, <see langword="false"/>.
/// </returns>
/// <remarks>
/// The default implementation delegates to <see cref="TryGetContract{TContract}(out TContract, out System.Exception?)"/>,
/// which instantiates the contract and therefore does read target memory. Registries that need the
/// no-instantiation guarantee above must override this method (<c>CachingContractRegistry</c> does).
/// </remarks>
public virtual bool TryValidate<TContract>([NotNullWhen(false)] out System.Exception? failureException) where TContract : IContract
{
return TryGetContract<TContract>(out _, out failureException);
}

/// <summary>
/// Register a contract implementation for a specific version.
/// External packages use this to add contract versions or entirely new contract interfaces.
/// </summary>
public abstract void Register<TContract>(string version, Func<Target, TContract> creator)
where TContract : IContract;

/// <summary>
/// Register a contract version that is recognized but intentionally not implemented.
/// </summary>
public abstract void RegisterUnsupported<TContract>(string version)
where TContract : IContract;

/// <summary>
/// Flush all cached data held by contracts in this registry for the given
/// <paramref name="scope"/>. Called when the target process state may have changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,70 @@ public static void Register(ContractRegistry registry)

registry.Register<IRuntimeMutableTypeSystem>("c1", static t => new RuntimeMutableTypeSystem_1(t));
}

/// <summary>
/// Eagerly validates that every contract required by the cDAC data-access interfaces can be
/// provided for the target, without instantiating any of them. This is intended to run at
/// interface-creation time so that an unsupported target fails fast with a distinct
/// <see cref="CdacHResults"/> code instead of failing lazily inside a later data-access call.
/// </summary>
/// <param name="registry">The contract registry for the target being validated.</param>
/// <exception cref="ContractValidationException">
/// Thrown for the first required contract that cannot be provided. The exception's
/// <see cref="ContractValidationException.HResult"/> is
/// <see cref="CdacHResults.CDAC_E_CONTRACT_NOT_ADVERTISED"/> if the target does not advertise the
/// contract, <see cref="CdacHResults.CDAC_E_CONTRACT_UNRECOGNIZED"/> if the advertised version is
/// unknown to this cDAC, or <see cref="CdacHResults.CDAC_E_CONTRACT_UNSUPPORTED"/> if the advertised
/// version is recognized but intentionally unimplemented.
/// </exception>
public static void ValidateForDataAccess(ContractRegistry registry)
{
// Direct contract accesses from SOSDacImpl.cs and SOSDacImpl.IXCLRDataProcess.cs.
// IObjectiveCMarshal is optional because SOS uses TryGetContract for Apple-only support.
Validate<IAuxiliarySymbols>(registry);
Validate<IBuiltInCOM>(registry);
Validate<ICodeNotifications>(registry);
Validate<ICodeVersions>(registry);
Validate<IComWrappers>(registry);
Validate<IDacStreams>(registry);
Validate<IEcmaMetadata>(registry);
Validate<IException>(registry);
Validate<IExecutionManager>(registry);
Validate<IGC>(registry);
Validate<IGCInfo>(registry);
Validate<ILoader>(registry);
Validate<INotifications>(registry);
Validate<IObject>(registry);
Validate<IPrecodeStubs>(registry);
Validate<IReJIT>(registry);
Validate<IRuntimeInfo>(registry);
Validate<IRuntimeTypeSystem>(registry);
Validate<ISignature>(registry);
Validate<IStackWalk>(registry);
Validate<IStressLog>(registry);
Validate<ISyncBlock>(registry);
Validate<IThread>(registry);

// Transitive contract accesses from the implementations above.
Validate<IConditionalWeakTable>(registry); // IComWrappers: ComWrappers_1.cs
Validate<IDebugger>(registry); // IStackWalk: StackWalk_1.cs
Validate<IPlatformMetadata>(registry); // IAuxiliarySymbols/IPrecodeStubs: CodePointerUtils.cs, PrecodeStubs_Common.cs
Validate<ISHash>(registry); // ILoader: Loader_1.cs

static void Validate<TContract>(ContractRegistry registry) where TContract : IContract
{
if (registry.TryValidate<TContract>(out System.Exception? failure))
{
return;
}

throw failure switch
{
ContractObsoleteException obsolete => new ContractValidationException(CdacHResults.CDAC_E_CONTRACT_UNSUPPORTED, obsolete),
ContractUnrecognizedException unrecognized => new ContractValidationException(CdacHResults.CDAC_E_CONTRACT_UNRECOGNIZED, unrecognized),
ContractNotAvailableException notAvailable => new ContractValidationException(CdacHResults.CDAC_E_CONTRACT_NOT_ADVERTISED, notAvailable),
_ => failure,
};
Comment on lines +140 to +146
}
}
}
Loading
Loading