Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
68 changes: 68 additions & 0 deletions docs/design/datacontracts/Debugger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Contract Debugger

This contract is for reading debugger state from the target process, including initialization status, configuration flags, metadata update state, and JIT attach state.

## APIs of contract

```csharp
record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion);
```

```csharp
bool TryGetDebuggerData(out DebuggerData data);
int GetAttachStateFlags();
bool MetadataUpdatesApplied();
```

## Version 1

The contract depends on the following globals

| Global Name | Type | Description |
| --- | --- | --- |
| `Debugger` | TargetPointer | Address of the pointer to the Debugger instance (`&g_pDebugger`) |
| `CLRJitAttachState` | TargetPointer | Pointer to the CLR JIT attach state flags |
| `MetadataUpdatesApplied` | TargetPointer | Pointer to the g_metadataUpdatesApplied flag |

Comment thread
rcj1 marked this conversation as resolved.
The contract additionally depends on these data descriptors

| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `Debugger` | `LeftSideInitialized` | Whether the left-side debugger infrastructure is initialized |
| `Debugger` | `Defines` | Bitfield of compile-time debugger feature defines |
| `Debugger` | `MDStructuresVersion` | Version of metadata data structures |

```csharp
bool TryGetDebuggerData(out DebuggerData data)
{
data = default;
// The Debugger global points to g_pDebugger (a pointer-to-pointer).
// First read gets the address of g_pDebugger, second dereferences it.
TargetPointer debuggerPtrPtr = target.ReadGlobalPointer("Debugger");
if (debuggerPtrPtr == TargetPointer.Null)
return false;
TargetPointer debuggerPtr = target.ReadPointer(debuggerPtrPtr);
if (debuggerPtr == TargetPointer.Null)
return false;
int leftSideInitialized = target.Read<int>(debuggerPtr + /* Debugger::LeftSideInitialized offset */);
if (leftSideInitialized == 0)
return false;
data = new DebuggerData(
DefinesBitField: target.Read<uint>(debuggerPtr + /* Debugger::Defines offset */),
MDStructuresVersion: target.Read<uint>(debuggerPtr + /* Debugger::MDStructuresVersion offset */));
return true;
}

int GetAttachStateFlags()
{
TargetPointer addr = target.ReadGlobalPointer("CLRJitAttachState");
return (int)target.Read<uint>(addr);
}

bool MetadataUpdatesApplied()
{
if (target.TryReadGlobalPointer("MetadataUpdatesApplied", out TargetPointer addr))
return target.Read<byte>(addr) != 0;
return false;
}
```
19 changes: 16 additions & 3 deletions src/coreclr/debug/daccess/cdac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,13 @@ namespace

int WriteToTargetCallback(uint64_t addr, const uint8_t* buff, uint32_t count, void* context)
{
ICorDebugMutableDataTarget* target = static_cast<ICorDebugMutableDataTarget*>(context);
HRESULT hr = target->WriteVirtual((CORDB_ADDRESS)addr, buff, count);
ICorDebugDataTarget* target = reinterpret_cast<ICorDebugDataTarget*>(context);
ICorDebugMutableDataTarget* mutableTarget = nullptr;
HRESULT hr = target->QueryInterface(__uuidof(ICorDebugMutableDataTarget), (void**)&mutableTarget);
if (FAILED(hr))
return hr;
hr = mutableTarget->WriteVirtual((CORDB_ADDRESS)addr, buff, count);
mutableTarget->Release();
if (FAILED(hr))
return hr;

Expand All @@ -75,7 +80,7 @@ namespace
}
}

CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugMutableDataTarget* target, IUnknown* legacyImpl)
CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl)
{
HMODULE cdacLib;
if (!TryLoadCDACLibrary(&cdacLib))
Expand Down Expand Up @@ -125,3 +130,11 @@ void CDAC::CreateSosInterface(IUnknown** sos)
int ret = createSosInterface(m_cdac_handle, m_legacyImpl, sos);
_ASSERTE(ret == 0);
}

void CDAC::CreateDacDbiInterface(IUnknown** dbi)
{
decltype(&cdac_reader_create_dacdbi_interface) createDacDbiInterface = reinterpret_cast<decltype(&cdac_reader_create_dacdbi_interface)>(::GetProcAddress(m_module, "cdac_reader_create_dacdbi_interface"));
_ASSERTE(createDacDbiInterface != nullptr);
int ret = createDacDbiInterface(m_cdac_handle, m_legacyImpl, dbi);
_ASSERTE(ret == 0);
}
3 changes: 2 additions & 1 deletion src/coreclr/debug/daccess/cdac.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class CDAC final
{
public: // static
static CDAC Create(uint64_t descriptorAddr, ICorDebugMutableDataTarget *pDataTarget, IUnknown* legacyImpl);
static CDAC Create(uint64_t descriptorAddr, ICorDebugDataTarget *pDataTarget, IUnknown* legacyImpl);

public:
CDAC() = default;
Expand Down Expand Up @@ -50,6 +50,7 @@ class CDAC final
}

void CreateSosInterface(IUnknown** sos);
void CreateDacDbiInterface(IUnknown** dbi);

private:
CDAC(HMODULE module, intptr_t handle, ICorDebugDataTarget* target, IUnknown* legacyImpl);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/debug/daccess/daccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6667,7 +6667,7 @@ CLRDataCreateInstance(REFIID iid,
HRESULT qiRes = pClrDataAccess->QueryInterface(IID_IUnknown, (void**)&thisImpl);
_ASSERTE(SUCCEEDED(qiRes));
CDAC& cdac = pClrDataAccess->m_cdac;
cdac = CDAC::Create(contractDescriptorAddr, pClrDataAccess->m_pMutableTarget, thisImpl);
cdac = CDAC::Create(contractDescriptorAddr, pClrDataAccess->m_pTarget, thisImpl);
if (cdac.IsValid())
{
// Get SOS interfaces from the cDAC if available.
Expand Down
60 changes: 56 additions & 4 deletions src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
#include "request_common.h"
#include "conditionalweaktable.h"

#ifndef USE_DAC_TABLE_RVA
extern "C" bool TryGetSymbol(ICorDebugDataTarget* dataTarget, uint64_t baseAddress, const char* symbolName, uint64_t* symbolAddress);
#include <clrconfignocache.h>
#define CAN_USE_CDAC
#endif

//-----------------------------------------------------------------------------
// Have standard enter and leave macros at the DacDbi boundary to enforce
// standard behavior.
Expand Down Expand Up @@ -274,14 +280,60 @@ DacDbiInterfaceInstance(

HRESULT hrStatus = pDac->Initialize();

if (SUCCEEDED(hrStatus))
if (FAILED(hrStatus))
{
*ppInterface = pDac;
delete pDac;
return hrStatus;
}
else

#ifdef CAN_USE_CDAC
CLRConfigNoCache enable = CLRConfigNoCache::Get("ENABLE_CDAC");
if (enable.IsSet())
{
delete pDac;
DWORD val;
if (enable.TryAsInteger(10, val) && val == 1)
{
uint64_t contractDescriptorAddr = 0;
if (TryGetSymbol(pDac->m_pTarget, pDac->m_globalBase, "DotNetRuntimeContractDescriptor", &contractDescriptorAddr))
{
IUnknown* legacyImpl;
HRESULT qiRes = pDac->QueryInterface(IID_IUnknown, (void**)&legacyImpl);
_ASSERTE(SUCCEEDED(qiRes));

CDAC& cdac = pDac->m_cdac;
cdac = CDAC::Create(contractDescriptorAddr, pDac->m_pTarget, legacyImpl);
if (cdac.IsValid())
{
NonVMComHolder<IUnknown> cdacInterface = nullptr;
cdac.CreateDacDbiInterface(&cdacInterface);
if (cdacInterface != nullptr)
{
IDacDbiInterface* pCDacDbi = nullptr;
HRESULT hr = cdacInterface->QueryInterface(__uuidof(IDacDbiInterface), (void**)&pCDacDbi);
if (SUCCEEDED(hr))
{
// Lifetime is now managed by cDAC implementation
pDac->Release();
// Release the AddRef from the QI for legacyImpl
pDac->Release();
*ppInterface = pCDacDbi;
return S_OK;
}
}
}

// Release the AddRef from the QI for legacyImpl
pDac->Release();
}

// If we requested to use the cDAC, but failed to create the cDAC interface, return failure
pDac->Release();
return E_FAIL;
}
}
#endif

*ppInterface = pDac;
return hrStatus;
}

Expand Down
19 changes: 19 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,15 @@ CDAC_TYPE_FIELD(SystemDomain, /*GlobalLoaderAllocator*/, GlobalLoaderAllocator,
CDAC_TYPE_FIELD(SystemDomain, /*pointer*/, SystemAssembly, cdac_data<SystemDomain>::SystemAssembly)
CDAC_TYPE_END(SystemDomain)

#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM)
CDAC_TYPE_BEGIN(Debugger)
CDAC_TYPE_INDETERMINATE(Debugger)
CDAC_TYPE_FIELD(Debugger, /*int32*/, LeftSideInitialized, offsetof(Debugger, m_fLeftSideInitialized))
CDAC_TYPE_FIELD(Debugger, /*uint32*/, Defines, offsetof(Debugger, m_defines))
CDAC_TYPE_FIELD(Debugger, /*uint32*/, MDStructuresVersion, offsetof(Debugger, m_mdDataStructureVersion))
CDAC_TYPE_END(Debugger)
#endif // DEBUGGING_SUPPORTED && !TARGET_WASM

CDAC_TYPE_BEGIN(ArrayListBase)
CDAC_TYPE_INDETERMINATE(ArrayListBase)
CDAC_TYPE_FIELD(ArrayListBase, /*uint32*/, Count, cdac_data<ArrayListBase>::Count)
Expand Down Expand Up @@ -1296,6 +1305,13 @@ CDAC_GLOBAL_POINTER(SystemDomain, cdac_data<SystemDomain>::SystemDomainPtr)
CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore)
CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread)
CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread)
#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM)
CDAC_GLOBAL_POINTER(Debugger, &::g_pDebugger)
CDAC_GLOBAL_POINTER(CLRJitAttachState, &::CLRJitAttachState)
#endif // DEBUGGING_SUPPORTED && !TARGET_WASM
#ifdef FEATURE_METADATA_UPDATER
CDAC_GLOBAL_POINTER(MetadataUpdatesApplied, &::g_metadataUpdatesApplied)
#endif

// Add FrameIdentifier for all defined Frame types. Used to differentiate Frame objects.
#define FRAME_TYPE_NAME(frameType) \
Expand Down Expand Up @@ -1444,6 +1460,9 @@ CDAC_GLOBAL_CONTRACT(ComWrappers, 1)
#endif // FEATURE_COMWRAPPERS
CDAC_GLOBAL_CONTRACT(ConditionalWeakTable, 1)
CDAC_GLOBAL_CONTRACT(DacStreams, 1)
#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM)
CDAC_GLOBAL_CONTRACT(Debugger, 1)
#endif // DEBUGGING_SUPPORTED && !TARGET_WASM
CDAC_GLOBAL_CONTRACT(DebugInfo, 2)
CDAC_GLOBAL_CONTRACT(EcmaMetadata, 1)
CDAC_GLOBAL_CONTRACT(Exception, 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public abstract class ContractRegistry
/// Gets an instance of the AuxiliarySymbols contract for the target.
/// </summary>
public virtual IAuxiliarySymbols AuxiliarySymbols => GetContract<IAuxiliarySymbols>();
/// <summary>
/// Gets an instance of the Debugger contract for the target.
/// </summary>
public virtual IDebugger Debugger => GetContract<IDebugger>();

public abstract TContract GetContract<TContract>() where TContract : IContract;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.Contracts;

public record struct DebuggerData(uint DefinesBitField, uint MDStructuresVersion);

public interface IDebugger : IContract
{
static string IContract.Name { get; } = nameof(Debugger);

bool TryGetDebuggerData(out DebuggerData data) => throw new NotImplementedException();
int GetAttachStateFlags() => throw new NotImplementedException();
bool MetadataUpdatesApplied() => throw new NotImplementedException();
}

public readonly struct Debugger : IDebugger
{
// Everything throws NotImplementedException
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Microsoft.Diagnostics.DataContractReader;

public static class CorDbgHResults
{
public const int CORDBG_E_NOTREADY = unchecked((int)0x80131c10);
public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49);
public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public enum DataType
Module,
ModuleLookupMap,
AppDomain,
Debugger,
SystemDomain,
Assembly,
LoaderAllocator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public static class Globals
public const string ThreadStore = nameof(ThreadStore);
public const string FinalizerThread = nameof(FinalizerThread);
public const string GCThread = nameof(GCThread);
public const string Debugger = nameof(Debugger);
public const string CLRJitAttachState = nameof(CLRJitAttachState);
public const string MetadataUpdatesApplied = nameof(MetadataUpdatesApplied);

public const string FeatureCOMInterop = nameof(FeatureCOMInterop);
public const string FeatureComWrappers = nameof(FeatureComWrappers);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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.Contracts;

public sealed class DebuggerFactory : IContractFactory<IDebugger>
{
IDebugger IContractFactory<IDebugger>.CreateContract(Target target, int version)
{
return version switch
{
1 => new Debugger_1(target),
_ => default(Debugger),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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.Contracts;

internal readonly struct Debugger_1 : IDebugger
{
private readonly Target _target;

internal Debugger_1(Target target)
{
_target = target;
}

bool IDebugger.TryGetDebuggerData(out DebuggerData data)
{
data = default;
TargetPointer debuggerPtrPtr = _target.ReadGlobalPointer(Constants.Globals.Debugger);
if (debuggerPtrPtr == TargetPointer.Null)
return false;

TargetPointer debuggerPtr = _target.ReadPointer(debuggerPtrPtr);
if (debuggerPtr == TargetPointer.Null)
return false;

Data.Debugger debugger = _target.ProcessedData.GetOrAdd<Data.Debugger>(debuggerPtr);
if (debugger.LeftSideInitialized == 0)
return false;

data = new DebuggerData(debugger.Defines, debugger.MDStructuresVersion);
return true;
}

int IDebugger.GetAttachStateFlags()
{
TargetPointer addr = _target.ReadGlobalPointer(Constants.Globals.CLRJitAttachState);
return (int)_target.Read<uint>(addr.Value);
}

bool IDebugger.MetadataUpdatesApplied()
{
if (_target.TryReadGlobalPointer(Constants.Globals.MetadataUpdatesApplied, out TargetPointer? addr))
{
return _target.Read<byte>(addr.Value.Value) != 0;
}
return false;
}
}
Loading
Loading