Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 28 additions & 0 deletions docs/design/datacontracts/Debugger.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void SetSendExceptionsOutsideOfJMC(bool sendExceptionsOutsideOfJMC);
TargetPointer GetDebuggerControlBlockAddress();
void EnableGCNotificationEvents(bool fEnable);
HijackKind GetHijackKind(TargetCodePointer controlPC);
byte ReadInstructionByte(TargetPointer address);
TargetPointer PrepareExceptionHijack(byte[] context, TargetPointer vmThread, byte[]? exceptionRecord, int reason, TargetPointer userData)
```

Expand All @@ -44,6 +45,11 @@ TargetPointer PrepareExceptionHijack(byte[] context, TargetPointer vmThread, byt
| `Debugger` | `RgHijackFunction` | `pointer` | Pointer to the runtime's array of hijack-stub address ranges. |
| `Debugger` | `RSRequestedSync` | `int32` | Sync-at-event request flag |
| `Debugger` | `SendExceptionsOutsideOfJMC` | `int32` | Exception delivery policy flag |
| `DebuggerControllerPatch` | *(type size)* | `uint32` | Size in bytes of each DebuggerControllerPatch entry. Only available on AMD64. |
| `DebuggerControllerPatch` | `Address` | `pointer` | Address patched with a debugger breakpoint, or null for an inactive entry. Only available on AMD64. |
| `DebuggerControllerPatch` | `Opcode` | `nuint` | Original instruction value replaced by the debugger breakpoint. Only available on AMD64. |
| `DebuggerPatchTable` | `Count` | `uint32` | Capacity of the DebuggerControllerPatch backing array. Only available on AMD64. |
| `DebuggerPatchTable` | `Entries` | `pointer` | Pointer to the backing array of DebuggerControllerPatch entries. Only available on AMD64. |
| `DebuggerRCThread` | `DCB` | `pointer` | Pointer to DebuggerIPCControlBlock |
| `MemoryRange` | *(type size)* | `uint32` | Size of the data descriptor layout |
| `MemoryRange` | `Size` | `nuint` | Size of the range in bytes; the range covers [StartAddress, StartAddress + Size) |
Expand All @@ -56,6 +62,8 @@ TargetPointer PrepareExceptionHijack(byte[] context, TargetPointer vmThread, byt
| `CLRJitAttachState` | `pointer` | Pointer to the CLR JIT attach state flags |
| `CORDebuggerControlFlags` | `pointer` | Pointer to g_CORDebuggerControlFlags |
| `Debugger` | `pointer` | Address of the pointer to the Debugger instance (&g_pDebugger) |
| `DebuggerPatchTable` | `pointer` | Address of the pointer to the debugger breakpoint patch table. Only available on AMD64. |
| `DebuggerPatchTableValid` | `pointer` | Pointer to the flag indicating whether the debugger patch table can be inspected. Only available on AMD64. |
| `MaxHijackFunctions` | `uint32` | Number of entries in the hijack function array. |
| `MetadataUpdatesApplied` | `pointer` | Pointer to the g_metadataUpdatesApplied flag |

Expand Down Expand Up @@ -195,6 +203,26 @@ HijackKind GetHijackKind(TargetCodePointer controlPC)
return HijackKind.None;
}

byte ReadInstructionByte(TargetPointer address)
{
if (!target.TryReadGlobalPointer("DebuggerPatchTableValid", out TargetPointer patchTableValidAddress))
return target.Read<byte>(address);

if (target.Read<int>(patchTableValidAddress) == 0)
return target.Read<byte>(address);

Dictionary<TargetPointer, byte> patches =
cachedPatches ??= ReadActivePatches();
if (patches.TryGetValue(address, out byte opcode))
return opcode;

return target.Read<byte>(address);
}

// ReadActivePatches reads the patch table global and creates an address-indexed
// map from backing-array entries with nonzero addresses and opcodes. Clear
// cachedPatches for every contract flush scope.

private TargetPointer GetHijackAddress()
{
// Returns the start address of the unhandled-exception hijack function
Expand Down
7 changes: 7 additions & 0 deletions docs/design/datacontracts/data-descriptor-meanings.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
"DebuggerEval.EvalUsesHijack": "Flag used in processing FuncEvalFrame",
"DebuggerEval.MethodToken": "Metadata token of the method being evaluated",
"DebuggerEval.TargetContext": "Context saved inside DebuggerEval",
"DebuggerControllerPatch.Address": "Address patched with a debugger breakpoint, or null for an inactive entry. Only available on AMD64.",
"DebuggerControllerPatch.Opcode": "Original instruction value replaced by the debugger breakpoint. Only available on AMD64.",
"DebuggerControllerPatch.Size": "Size in bytes of each DebuggerControllerPatch entry. Only available on AMD64.",
"DebuggerPatchTable.Count": "Capacity of the DebuggerControllerPatch backing array. Only available on AMD64.",
"DebuggerPatchTable.Entries": "Pointer to the backing array of DebuggerControllerPatch entries. Only available on AMD64.",
"DebuggerRCThread.DCB": "Pointer to DebuggerIPCControlBlock",
"Delegate.ExtraData": "Invocation count for multicast, UnmanagedMarker for unmanaged, MethodDesc otherwise",
"Delegate.HelperObject": "Invocation list for multicast, MethodInfo otherwise",
Expand Down Expand Up @@ -731,6 +736,8 @@
"CurrentGCState": "c_gc_state enum value. Only available when GCIdentifiers contains background.",
"DebugDestroyedHandleValue": "Sentinel handle value used for destroyed handles",
"Debugger": "Address of the pointer to the Debugger instance (&g_pDebugger)",
"DebuggerPatchTable": "Address of the pointer to the debugger breakpoint patch table. Only available on AMD64.",
"DebuggerPatchTableValid": "Pointer to the flag indicating whether the debugger patch table can be inspected. Only available on AMD64.",
"DispatchThisPtrMask": "Used to mask low bits of CCW pointer to the nearest valid address from which to read a managed object wrapper",
"DynamicAdaptationMode": "GC heap dynamic adaptation mode. Only available when GCIdentifiers contains dynamic_heap.",
"EEConfig": "Pointer to the runtime configuration",
Expand Down
18 changes: 17 additions & 1 deletion src/coreclr/debug/ee/controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,8 @@ class DebuggerPatchTable : private CHashTableAndData<CNewZeroData>
{
VPTR_BASE_CONCRETE_VTABLE_CLASS(DebuggerPatchTable);

friend struct ::cdac_data<DebuggerPatchTable>;

public:
virtual ~DebuggerPatchTable() = default;

Expand Down Expand Up @@ -922,6 +924,13 @@ class DebuggerPatchTable : private CHashTableAndData<CNewZeroData>
int GetNumberOfPatches();
};

template<>
struct cdac_data<DebuggerPatchTable>
{
static constexpr size_t Entries = offsetof(DebuggerPatchTable, m_pcEntries);
static constexpr size_t Count = offsetof(DebuggerPatchTable, m_iEntries);
};

Comment thread
max-charlamb marked this conversation as resolved.
typedef VPTR(class DebuggerPatchTable) PTR_DebuggerPatchTable;


Expand Down Expand Up @@ -1055,6 +1064,7 @@ inline void VerifyExecutableAddress(const BYTE* address)
class DebuggerController
{
VPTR_BASE_CONCRETE_VTABLE_CLASS(DebuggerController);
friend struct ::cdac_data<DebuggerController>;

#if !defined(DACCESS_COMPILE)

Expand Down Expand Up @@ -1496,9 +1506,15 @@ class DebuggerController
#endif // !DACCESS_COMPILE
};


#if !defined(DACCESS_COMPILE)

template<>
struct cdac_data<DebuggerController>
{
static constexpr DebuggerPatchTable **PatchTable = &DebuggerController::g_patches;
static constexpr BOOL *PatchTableValid = &DebuggerController::g_patchTableValid;
Comment thread
max-charlamb marked this conversation as resolved.
Outdated
};

// this structure stores useful information about single-stepping over a call instruction
// it is used to communicate the patch skip opcode and current state between the controller on left side and HandleSetThreadContextNeeded on the right side
class DebuggerSteppingInfo
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

#include "virtualcallstub.h"
#include "../debug/ee/debugger.h"
#include "../debug/ee/walker.h"
#include "../debug/ee/controller.h"
#include "patchpointinfo.h"

#ifdef HAVE_GCCOVER
Expand Down
18 changes: 18 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,20 @@ CDAC_TYPE_INDETERMINATE(DebuggerRCThread)
CDAC_TYPE_FIELD(DebuggerRCThread, T_POINTER, DCB, cdac_data<DebuggerRCThread>::DCB)
CDAC_TYPE_END(DebuggerRCThread)

#if defined(TARGET_AMD64)
CDAC_TYPE_BEGIN(DebuggerPatchTable)
CDAC_TYPE_INDETERMINATE(DebuggerPatchTable)
CDAC_TYPE_FIELD(DebuggerPatchTable, T_POINTER, Entries, cdac_data<DebuggerPatchTable>::Entries)
CDAC_TYPE_FIELD(DebuggerPatchTable, T_UINT32, Count, cdac_data<DebuggerPatchTable>::Count)
CDAC_TYPE_END(DebuggerPatchTable)

CDAC_TYPE_BEGIN(DebuggerControllerPatch)
CDAC_TYPE_SIZE(sizeof(DebuggerControllerPatch))
CDAC_TYPE_FIELD(DebuggerControllerPatch, T_POINTER, Address, offsetof(DebuggerControllerPatch, address))
CDAC_TYPE_FIELD(DebuggerControllerPatch, T_NUINT, Opcode, offsetof(DebuggerControllerPatch, opcode))
CDAC_TYPE_END(DebuggerControllerPatch)
#endif // TARGET_AMD64

CDAC_TYPE_BEGIN(MemoryRange)
CDAC_TYPE_SIZE(sizeof(MemoryRange))
CDAC_TYPE_FIELD(MemoryRange, T_POINTER, StartAddress, cdac_data<MemoryRange>::StartAddress)
Expand Down Expand Up @@ -1690,6 +1704,10 @@ CDAC_GLOBAL_POINTER(ThePreStub, &g_cdacThePreStub)
#endif // !FEATURE_PORTABLE_ENTRYPOINTS
#if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM)
CDAC_GLOBAL_POINTER(Debugger, &::g_pDebugger)
#if defined(TARGET_AMD64)
CDAC_GLOBAL_POINTER(DebuggerPatchTable, cdac_data<DebuggerController>::PatchTable)
CDAC_GLOBAL_POINTER(DebuggerPatchTableValid, cdac_data<DebuggerController>::PatchTableValid)
#endif // TARGET_AMD64
CDAC_GLOBAL_POINTER(CLRJitAttachState, &::CLRJitAttachState)
CDAC_GLOBAL_POINTER(CORDebuggerControlFlags, &::g_CORDebuggerControlFlags)
CDAC_GLOBAL(MaxHijackFunctions, T_UINT32, cdac_data<Debugger>::MaxHijackFunctions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public interface IDebugger : IContract
TargetPointer GetDebuggerControlBlockAddress() => throw new NotImplementedException();
void EnableGCNotificationEvents(bool fEnable) => throw new NotImplementedException();
HijackKind GetHijackKind(TargetCodePointer controlPC) => throw new NotImplementedException();
byte ReadInstructionByte(TargetPointer address) => throw new NotImplementedException();
TargetPointer PrepareExceptionHijack(byte[] context, TargetPointer vmThread, byte[]? exceptionRecord, int reason, TargetPointer userData) => throw new NotImplementedException();
}
Comment thread
max-charlamb marked this conversation as resolved.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public static class Globals
public const string FunctionTableIndexRangeList = nameof(FunctionTableIndexRangeList);
public const string GCThread = nameof(GCThread);
public const string Debugger = nameof(Debugger);
public const string DebuggerPatchTable = nameof(DebuggerPatchTable);
public const string DebuggerPatchTableValid = nameof(DebuggerPatchTableValid);
public const string MaxHijackFunctions = nameof(MaxHijackFunctions);
public const string CLRJitAttachState = nameof(CLRJitAttachState);
public const string CORDebuggerControlFlags = nameof(CORDebuggerControlFlags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@

using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;

namespace Microsoft.Diagnostics.DataContractReader.Contracts;

internal readonly struct Debugger_1 : IDebugger
internal sealed class Debugger_1 : IDebugger
{
private enum DebuggerControlFlag_1 : uint
{
Expand All @@ -20,12 +21,18 @@ private enum DebuggerControlFlag_1 : uint
private const uint UnhandledExceptionHijackIndex = 0;

private readonly Target _target;
private Dictionary<TargetPointer, byte>? _patches;

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

public void Flush(FlushScope scope)
{
_patches = null;
}

private bool TryGetDebuggerAddress(out TargetPointer debuggerAddress)
{
debuggerAddress = TargetPointer.Null;
Expand Down Expand Up @@ -156,6 +163,68 @@ HijackKind IDebugger.GetHijackKind(TargetCodePointer controlPC)
return HijackKind.None;
}

byte IDebugger.ReadInstructionByte(TargetPointer address)
{
try
{
if (!_target.TryReadGlobalPointer(Constants.Globals.DebuggerPatchTableValid, out TargetPointer? patchTableValidAddress))
return _target.Read<byte>(address);

if (_target.Read<int>(patchTableValidAddress.Value) == 0)
return _target.Read<byte>(address);

Dictionary<TargetPointer, byte> patches = GetPatches();
if (patches.TryGetValue(address, out byte opcode))
{
return opcode;
}
}
catch (VirtualReadException)
{
// Patch metadata is optional. Fall back to reading the instruction directly.
}

return _target.Read<byte>(address);
}

private Dictionary<TargetPointer, byte> GetPatches()
{
Dictionary<TargetPointer, byte>? patches = _patches;
if (patches is not null)
return patches;

return _patches ??= ReadPatches();
}
Comment thread
max-charlamb marked this conversation as resolved.
Outdated

private Dictionary<TargetPointer, byte> ReadPatches()
{
if (!_target.TryReadGlobalPointer(Constants.Globals.DebuggerPatchTable, out TargetPointer? patchTablePointerAddress))
return [];

TargetPointer patchTableAddress = _target.ReadPointer(patchTablePointerAddress.Value);
if (patchTableAddress == TargetPointer.Null)
return [];

Data.DebuggerPatchTable patchTable = _target.ProcessedData.GetOrAdd<Data.DebuggerPatchTable>(patchTableAddress);
if (patchTable.Entries == TargetPointer.Null)
return [];

Dictionary<TargetPointer, byte> patches = [];
uint patchSize = Data.DebuggerControllerPatch.GetSize(_target);

for (uint i = 0; i < patchTable.Count; i++)
{
TargetPointer patchAddress = patchTable.Entries + ((ulong)i * patchSize);
Data.DebuggerControllerPatch patch = _target.ProcessedData.GetOrAdd<Data.DebuggerControllerPatch>(patchAddress);
if (patch.CodeAddress != TargetPointer.Null && patch.Opcode.Value != 0)
{
patches[patch.CodeAddress] = (byte)patch.Opcode.Value;
}
}

return patches;
}

private TargetPointer GetHijackAddress()
{
return TryGetHijackFunctionRange(UnhandledExceptionHijackIndex, out Data.MemoryRange? range)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ public bool Unwind(ref AMD64Context context)

if (unwindInfo.Version < 2)
{
byte ReadByteAt(TargetPointer address) => _target.Contracts.Debugger.ReadInstructionByte(address);

TargetPointer nextByte = controlPC;

//
Expand Down Expand Up @@ -1263,8 +1265,6 @@ private Data.RuntimeFunction LookupPrimaryFunctionEntry(Data.RuntimeFunction fun
#endregion
#region Helpers

private byte ReadByteAt(TargetPointer address) => _target.Read<byte>(address);

private static bool IsRexPrefix(byte b) => (b & 0xf0) == 0x40;

private static TargetPointer GetRegister(AMD64Context context, byte register)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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.Data;

[CdacType(nameof(DataType.DebuggerControllerPatch))]
internal sealed partial class DebuggerControllerPatch : IData<DebuggerControllerPatch>
{
[Field("Address")] public partial TargetPointer CodeAddress { get; }
[Field] public partial TargetNUInt Opcode { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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.Data;

[CdacType(nameof(DataType.DebuggerPatchTable))]
internal sealed partial class DebuggerPatchTable : IData<DebuggerPatchTable>
{
[Field] public partial TargetPointer Entries { get; }
[Field] public partial uint Count { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public enum DataType
AppDomain,
Debugger,
DebuggerRCThread,
DebuggerPatchTable,
DebuggerControllerPatch,
MemoryRange,
SystemDomain,
Assembly,
Expand Down
Loading
Loading