From de1cecb48c875f342e8ebe441615f49494b34ebe Mon Sep 17 00:00:00 2001
From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com>
Date: Thu, 16 Apr 2026 16:17:33 -0400
Subject: [PATCH] [NativeAOT] Add cDAC data descriptor infrastructure
Add the native cDAC data descriptor for NativeAOT, enabling diagnostic
tools (cDAC reader, SOS) to inspect NativeAOT runtime state through the
same contract-based mechanism used by CoreCLR.
Includes:
- datadescriptor.inc with Thread, ThreadStore, MethodTable, ExInfo,
EEAllocContext, GCAllocContext, RuntimeInstance types and globals
- CMake integration using shared clrdatadescriptors.cmake infrastructure
- GC sub-descriptor for workstation and server GC
- Contract declarations: Thread (1001), Exception (1), RuntimeTypeSystem (1001)
- Symbol export via --export-dynamic-symbol in build targets
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Microsoft.NETCore.Native.targets | 2 +
src/coreclr/nativeaot/Runtime/CMakeLists.txt | 1 +
.../nativeaot/Runtime/Full/CMakeLists.txt | 4 +-
.../nativeaot/Runtime/RuntimeInstance.h | 6 +
.../Runtime/datadescriptor/CMakeLists.txt | 48 ++++++
.../Runtime/datadescriptor/datadescriptor.h | 27 ++++
.../Runtime/datadescriptor/datadescriptor.inc | 140 ++++++++++++++++++
.../nativeaot/Runtime/inc/MethodTable.h | 23 +++
src/coreclr/nativeaot/Runtime/threadstore.h | 7 +
9 files changed, 256 insertions(+), 2 deletions(-)
create mode 100644 src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
create mode 100644 src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
create mode 100644 src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index 2c1e91afc8ba16..b3ce3dc5f56cf1 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -236,6 +236,8 @@ The .NET Foundation licenses this file to you under the MIT license.
+
+
diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
index 9f3a80c702358e..b4f63dc5faca0d 100644
--- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt
@@ -338,6 +338,7 @@ convert_to_absolute_path(VXSORT_SOURCES ${VXSORT_SOURCES})
convert_to_absolute_path(DUMMY_VXSORT_SOURCES ${DUMMY_VXSORT_SOURCES})
if(NOT CLR_CMAKE_TARGET_ARCH_WASM)
+ add_subdirectory(datadescriptor)
add_subdirectory(Full)
else()
add_subdirectory(Portable)
diff --git a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
index 74cdeca700a1ae..56fc4a3d7d6e0e 100644
--- a/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
+++ b/src/coreclr/nativeaot/Runtime/Full/CMakeLists.txt
@@ -24,11 +24,11 @@ endif (CLR_CMAKE_TARGET_WIN32)
add_library(Runtime.WorkstationGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.WorkstationGC aot_eventing_headers)
-target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal)
+target_link_libraries(Runtime.WorkstationGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_wks_descriptor)
add_library(Runtime.ServerGC STATIC ${COMMON_RUNTIME_SOURCES} ${FULL_RUNTIME_SOURCES} ${SERVER_GC_SOURCES} ${RUNTIME_ARCH_ASM_OBJECTS})
add_dependencies(Runtime.ServerGC aot_eventing_headers)
-target_link_libraries(Runtime.ServerGC PRIVATE aotminipal)
+target_link_libraries(Runtime.ServerGC PRIVATE aotminipal nativeaot_cdac_contract_descriptor nativeaot_gc_svr_descriptor)
add_library(standalonegc-disabled STATIC ${STANDALONEGC_DISABLED_SOURCES})
add_dependencies(standalonegc-disabled aot_eventing_headers)
diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
index 7f6b7ac6195c8e..43ad527ed8bf94 100644
--- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
+++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h
@@ -11,6 +11,7 @@ class TypeManager;
enum GenericVarianceType : uint8_t;
#include "ICodeManager.h"
+#include "cdacdata.h"
extern "C" void PopulateDebugHeaders();
@@ -20,6 +21,7 @@ class RuntimeInstance
friend struct DefaultSListTraits;
friend class Thread;
friend void PopulateDebugHeaders();
+ friend struct ::cdac_data;
PTR_ThreadStore m_pThreadStore;
HANDLE m_hPalInstance; // this is the HANDLE passed into DllMain
@@ -114,6 +116,10 @@ class RuntimeInstance
};
typedef DPTR(RuntimeInstance) PTR_RuntimeInstance;
+template<> struct cdac_data
+{
+ static constexpr size_t ThreadStore = offsetof(RuntimeInstance, m_pThreadStore);
+};
PTR_RuntimeInstance GetRuntimeInstance();
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
new file mode 100644
index 00000000000000..feabf0b62d2c2a
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/CMakeLists.txt
@@ -0,0 +1,48 @@
+set(CMAKE_INCLUDE_CURRENT_DIR OFF)
+
+# cDAC contract descriptor for NativeAOT
+#
+# This uses the shared datadescriptor infrastructure from
+# src/coreclr/debug/datadescriptor-shared/ to generate a
+# DotNetRuntimeContractDescriptor symbol in the NativeAOT runtime.
+#
+# Include directories and compile definitions are inherited from the parent
+# Runtime/CMakeLists.txt directory scope. The interface targets below only
+# need to add the datadescriptor-specific include path.
+
+include(${CLR_DIR}/clrdatadescriptors.cmake)
+
+add_library(nativeaot_descriptor_interface INTERFACE)
+target_include_directories(nativeaot_descriptor_interface INTERFACE
+ ${CMAKE_CURRENT_SOURCE_DIR})
+add_dependencies(nativeaot_descriptor_interface Runtime.WorkstationGC)
+generate_data_descriptors(
+ LIBRARY_NAME nativeaot_cdac_contract_descriptor
+ CONTRACT_NAME "DotNetRuntimeContractDescriptor"
+ INTERFACE_TARGET nativeaot_descriptor_interface
+ EXPORT_VISIBLE)
+
+# GC contract descriptors (workstation + server).
+# The GC has its own data descriptor exposed as a sub-descriptor via gc_descriptor in GcDacVars.
+set(GC_DESCRIPTOR_DIR "${CLR_DIR}/gc/datadescriptor")
+
+add_library(nativeaot_gc_wks_descriptor_interface INTERFACE)
+target_include_directories(nativeaot_gc_wks_descriptor_interface INTERFACE
+ ${GC_DESCRIPTOR_DIR}
+ ${GC_DIR})
+add_dependencies(nativeaot_gc_wks_descriptor_interface Runtime.WorkstationGC)
+generate_data_descriptors(
+ LIBRARY_NAME nativeaot_gc_wks_descriptor
+ CONTRACT_NAME "GCContractDescriptorWKS"
+ INTERFACE_TARGET nativeaot_gc_wks_descriptor_interface)
+
+add_library(nativeaot_gc_svr_descriptor_interface INTERFACE)
+target_include_directories(nativeaot_gc_svr_descriptor_interface INTERFACE
+ ${GC_DESCRIPTOR_DIR}
+ ${GC_DIR})
+add_dependencies(nativeaot_gc_svr_descriptor_interface Runtime.WorkstationGC)
+target_compile_definitions(nativeaot_gc_svr_descriptor_interface INTERFACE -DSERVER_GC)
+generate_data_descriptors(
+ LIBRARY_NAME nativeaot_gc_svr_descriptor
+ CONTRACT_NAME "GCContractDescriptorSVR"
+ INTERFACE_TARGET nativeaot_gc_svr_descriptor_interface)
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
new file mode 100644
index 00000000000000..31f9f945e7f2cb
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+// This header provides the includes needed for datadescriptor.inc to use
+// offsetof() on NativeAOT runtime data structures.
+//
+// Note: Some NativeAOT types have private members that offsetof() cannot access
+// from this compilation unit. For those types, we use known offset constants
+// validated at build time by AsmOffsetsVerify.cpp and DebugHeader.cpp static_asserts.
+
+#include "common.h"
+#include "gcenv.h"
+#include "gcheaputilities.h"
+#include "gcinterface.dac.h"
+#include "rhassert.h"
+#include "TargetPtrs.h"
+#include "PalLimitedContext.h"
+#include "Pal.h"
+#include "holder.h"
+#include "RuntimeInstance.h"
+#include "regdisplay.h"
+#include "StackFrameIterator.h"
+#include "thread.h"
+#include "threadstore.h"
+
+#include
+#include
diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
new file mode 100644
index 00000000000000..fa71924c6120f0
--- /dev/null
+++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc
@@ -0,0 +1,140 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+// No include guards. This file is included multiple times.
+//
+// NativeAOT data descriptor declarations for the cDAC contract system.
+// This file defines the types, fields, and globals that diagnostic tools
+// need to inspect NativeAOT runtime state.
+//
+// When modifying this file (adding/removing types, fields, or globals), you must also:
+// 1. Update the corresponding contract doc in docs/design/datacontracts/.md
+// 2. Update the managed data class in src/native/managed/cdac/.../Data/.cs
+// 3. Update the contract implementation in src/native/managed/cdac/.../Contracts/.cs
+// 4. Update the mock descriptors and tests in src/native/managed/cdac/tests/.
+
+CDAC_BASELINE("empty")
+CDAC_TYPES_BEGIN()
+
+// ========================
+// Thread and ThreadStore
+// ========================
+
+CDAC_TYPE_BEGIN(Thread)
+CDAC_TYPE_INDETERMINATE(Thread)
+CDAC_TYPE_FIELD(Thread, T_UINT64, OSId, offsetof(RuntimeThreadLocals, m_threadId))
+CDAC_TYPE_FIELD(Thread, T_UINT32, State, offsetof(RuntimeThreadLocals, m_ThreadStateFlags))
+CDAC_TYPE_FIELD(Thread, T_POINTER, LinkNext, offsetof(RuntimeThreadLocals, m_pNext))
+CDAC_TYPE_FIELD(Thread, T_POINTER, ExceptionTracker, offsetof(RuntimeThreadLocals, m_pExInfoStackHead))
+CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackBase, offsetof(RuntimeThreadLocals, m_pStackHigh))
+CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackLimit, offsetof(RuntimeThreadLocals, m_pStackLow))
+CDAC_TYPE_FIELD(Thread, TYPE(RuntimeThreadLocals), RuntimeThreadLocals, offsetof(RuntimeThreadLocals, m_eeAllocContext))
+CDAC_TYPE_FIELD(Thread, T_POINTER, TransitionFrame, offsetof(RuntimeThreadLocals, m_pTransitionFrame))
+CDAC_TYPE_END(Thread)
+
+CDAC_TYPE_BEGIN(ThreadStore)
+CDAC_TYPE_INDETERMINATE(ThreadStore)
+CDAC_TYPE_FIELD(ThreadStore, T_POINTER, FirstThreadLink, cdac_data::FirstThreadLink)
+CDAC_TYPE_END(ThreadStore)
+
+CDAC_TYPE_BEGIN(RuntimeThreadLocals)
+CDAC_TYPE_INDETERMINATE(RuntimeThreadLocals)
+CDAC_TYPE_FIELD(RuntimeThreadLocals, TYPE(EEAllocContext), AllocContext, offsetof(RuntimeThreadLocals, m_eeAllocContext))
+CDAC_TYPE_END(RuntimeThreadLocals)
+
+// ========================
+// Allocation Context
+// ========================
+
+CDAC_TYPE_BEGIN(EEAllocContext)
+CDAC_TYPE_INDETERMINATE(EEAllocContext)
+CDAC_TYPE_FIELD(EEAllocContext, TYPE(GCAllocContext), GCAllocationContext, offsetof(ee_alloc_context, m_rgbAllocContextBuffer))
+CDAC_TYPE_END(EEAllocContext)
+
+CDAC_TYPE_BEGIN(GCAllocContext)
+CDAC_TYPE_INDETERMINATE(GCAllocContext)
+CDAC_TYPE_FIELD(GCAllocContext, T_POINTER, Pointer, offsetof(gc_alloc_context, alloc_ptr))
+CDAC_TYPE_FIELD(GCAllocContext, T_POINTER, Limit, offsetof(gc_alloc_context, alloc_limit))
+CDAC_TYPE_FIELD(GCAllocContext, T_INT64, AllocBytes, offsetof(gc_alloc_context, alloc_bytes))
+CDAC_TYPE_FIELD(GCAllocContext, T_INT64, AllocBytesLoh, offsetof(gc_alloc_context, alloc_bytes_uoh))
+CDAC_TYPE_END(GCAllocContext)
+
+// ========================
+// MethodTable (EEType)
+// ========================
+
+CDAC_TYPE_BEGIN(MethodTable)
+CDAC_TYPE_INDETERMINATE(MethodTable)
+CDAC_TYPE_FIELD(MethodTable, T_UINT32, Flags, cdac_data::Flags)
+CDAC_TYPE_FIELD(MethodTable, T_UINT32, BaseSize, cdac_data::BaseSize)
+CDAC_TYPE_FIELD(MethodTable, T_POINTER, RelatedType, cdac_data::RelatedType)
+CDAC_TYPE_FIELD(MethodTable, T_UINT16, NumVtableSlots, cdac_data::NumVtableSlots)
+CDAC_TYPE_FIELD(MethodTable, T_UINT16, NumInterfaces, cdac_data::NumInterfaces)
+CDAC_TYPE_FIELD(MethodTable, T_UINT32, HashCode, cdac_data::HashCode)
+CDAC_TYPE_FIELD(MethodTable, T_POINTER, VTable, cdac_data::VTable)
+CDAC_TYPE_END(MethodTable)
+
+// ========================
+// Exception Info
+// ========================
+
+CDAC_TYPE_BEGIN(ExInfo)
+CDAC_TYPE_INDETERMINATE(ExInfo)
+CDAC_TYPE_FIELD(ExInfo, T_POINTER, PreviousNestedInfo, offsetof(ExInfo, m_pPrevExInfo))
+CDAC_TYPE_FIELD(ExInfo, T_POINTER, ThrownObject, offsetof(ExInfo, m_exception))
+CDAC_TYPE_END(ExInfo)
+
+// ========================
+// RuntimeInstance
+// ========================
+
+CDAC_TYPE_BEGIN(RuntimeInstance)
+CDAC_TYPE_INDETERMINATE(RuntimeInstance)
+CDAC_TYPE_FIELD(RuntimeInstance, T_POINTER, ThreadStore, cdac_data::ThreadStore)
+CDAC_TYPE_END(RuntimeInstance)
+
+CDAC_TYPES_END()
+
+// ========================
+// Globals
+// ========================
+
+CDAC_GLOBALS_BEGIN()
+
+CDAC_GLOBAL_POINTER(RuntimeInstance, &g_pTheRuntimeInstance)
+
+CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType)
+
+CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address)
+CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address)
+
+// NativeAOT MethodTable flag constants (accessed via cdac_data friend)
+CDAC_GLOBAL(MethodTableEETypeKindMask, uint32, cdac_data::EETypeKindMask)
+CDAC_GLOBAL(MethodTableHasComponentSizeFlag, uint32, cdac_data::HasComponentSizeFlag)
+CDAC_GLOBAL(MethodTableHasFinalizerFlag, uint32, cdac_data::HasFinalizerFlag)
+CDAC_GLOBAL(MethodTableHasPointersFlag, uint32, cdac_data::HasPointersFlag)
+CDAC_GLOBAL(MethodTableIsGenericFlag, uint32, cdac_data::IsGenericFlag)
+CDAC_GLOBAL(MethodTableElementTypeMask, uint32, cdac_data::ElementTypeMask)
+CDAC_GLOBAL(MethodTableElementTypeShift, uint32, cdac_data::ElementTypeShift)
+
+// Thread state flag constants
+CDAC_GLOBAL(ThreadStateFlagAttached, uint32, 0x00000001)
+CDAC_GLOBAL(ThreadStateFlagDetached, uint32, 0x00000002)
+
+// Object contract globals
+#ifdef TARGET_64BIT
+CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 7)
+#else
+CDAC_GLOBAL(ObjectToMethodTableUnmask, uint8, 3)
+#endif
+
+// Contracts: declare which contracts this runtime supports
+CDAC_GLOBAL_CONTRACT(Thread, 1001)
+CDAC_GLOBAL_CONTRACT(Exception, 1)
+CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, 1001)
+
+// GC sub-descriptor: the GC populates gc_descriptor during GC_Initialize.
+// It is important for subdescriptor pointers to be the last pointers.
+CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor))
+
+CDAC_GLOBALS_END()
diff --git a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
index 89ff445f6174d9..77c2ceec275a4b 100644
--- a/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
+++ b/src/coreclr/nativeaot/Runtime/inc/MethodTable.h
@@ -12,6 +12,9 @@ class MethodTable;
class TypeManager;
struct TypeManagerHandle;
+// cdac_data template for exposing private members to the cDAC data descriptor.
+#include "cdacdata.h"
+
//-------------------------------------------------------------------------------------------------
// The subset of TypeFlags that NativeAOT knows about at runtime
// This should match the TypeFlags enum in the managed type system.
@@ -86,6 +89,7 @@ class MethodTable
{
friend class AsmOffsets;
friend void PopulateDebugHeaders();
+ friend struct ::cdac_data;
private:
struct RelatedTypeUnion
@@ -346,4 +350,23 @@ class MethodTable
UInt32_BOOL SanityCheck() { return Validate(); }
};
+template<> struct cdac_data
+{
+ static constexpr size_t Flags = offsetof(MethodTable, m_uFlags);
+ static constexpr size_t BaseSize = offsetof(MethodTable, m_uBaseSize);
+ static constexpr size_t RelatedType = offsetof(MethodTable, m_RelatedType);
+ static constexpr size_t NumVtableSlots = offsetof(MethodTable, m_usNumVtableSlots);
+ static constexpr size_t NumInterfaces = offsetof(MethodTable, m_usNumInterfaces);
+ static constexpr size_t HashCode = offsetof(MethodTable, m_uHashCode);
+ static constexpr size_t VTable = offsetof(MethodTable, m_VTable);
+
+ static constexpr uint32_t EETypeKindMask = MethodTable::EETypeKindMask;
+ static constexpr uint32_t HasComponentSizeFlag = MethodTable::HasComponentSizeFlag;
+ static constexpr uint32_t HasFinalizerFlag = MethodTable::HasFinalizerFlag;
+ static constexpr uint32_t HasPointersFlag = MethodTable::HasPointersFlag;
+ static constexpr uint32_t IsGenericFlag = MethodTable::IsGenericFlag;
+ static constexpr uint32_t ElementTypeMask = MethodTable::ElementTypeMask;
+ static constexpr uint32_t ElementTypeShift = MethodTable::ElementTypeShift;
+};
+
#pragma warning(pop)
diff --git a/src/coreclr/nativeaot/Runtime/threadstore.h b/src/coreclr/nativeaot/Runtime/threadstore.h
index d2347f9a631ffa..f87730e08fc574 100644
--- a/src/coreclr/nativeaot/Runtime/threadstore.h
+++ b/src/coreclr/nativeaot/Runtime/threadstore.h
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
#include "Crst.h"
+#include "cdacdata.h"
class Thread;
class CLREventStatic;
@@ -20,6 +21,7 @@ extern "C" void PopulateDebugHeaders();
class ThreadStore
{
friend void PopulateDebugHeaders();
+ friend struct ::cdac_data;
SList m_ThreadList;
PTR_RuntimeInstance m_pRuntimeInstance;
@@ -68,6 +70,11 @@ class ThreadStore
};
typedef DPTR(ThreadStore) PTR_ThreadStore;
+template<> struct cdac_data
+{
+ static constexpr size_t FirstThreadLink = offsetof(ThreadStore, m_ThreadList);
+};
+
ThreadStore * GetThreadStore();
#define FOREACH_THREAD(p_thread_name) \