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
6 changes: 6 additions & 0 deletions src/coreclr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ endif(CLR_CMAKE_HOST_WIN32)
#----------------------------------
include(clrdefinitions.cmake)

#--------------------------------
# Data descriptors mechanics
# - all clr specific data descriptor helpers should be included in this file
#----------------------------------
include(clrdatadescriptors.cmake)

if(FEATURE_STANDALONE_GC)
add_definitions(-DFEATURE_STANDALONE_GC)
endif(FEATURE_STANDALONE_GC)
Expand Down
97 changes: 97 additions & 0 deletions src/coreclr/clrdatadescriptors.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# cDAC contract descriptor

function(generate_data_descriptors)
set(options DLLEXPORT)
set(oneValueArgs LIBRARY_NAME CONTRACT_FILE CONTRACT_NAME INTERFACE_TARGET)
set(multiValueArgs "")
cmake_parse_arguments(DATA_DESCRIPTORS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGV})

# INTERMEDIARY_LIBRARY is used as part of the build and not linked into the final product.
set(INTERMEDIARY_LIBRARY ${DATA_DESCRIPTORS_LIBRARY_NAME}_temp)
set(LIBRARY ${DATA_DESCRIPTORS_LIBRARY_NAME})

set(DATA_DESCRIPTOR_SHARED_SOURCE_DIR "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/debug/datadescriptor-shared")
set(GENERATED_CDAC_DESCRIPTOR_DIR "${CMAKE_CURRENT_BINARY_DIR}/cdac-${LIBRARY}")

# configure contract export name
set(POINTER_DATA_NAME ${DATA_DESCRIPTORS_CONTRACT_NAME}PointerData)
set(CONTRACT_NAME ${DATA_DESCRIPTORS_CONTRACT_NAME})
if (DATA_DESCRIPTORS_DLLEXPORT)
set(EXPORT_CONTRACT 1)
else()
set(EXPORT_CONTRACT 0)
endif()
configure_file("${DATA_DESCRIPTOR_SHARED_SOURCE_DIR}/contractconfiguration.h.in" "${GENERATED_CDAC_DESCRIPTOR_DIR}/contractconfiguration.h")

if (NOT CDAC_BUILD_TOOL_BINARY_PATH)
# if CDAC_BUILD_TOOL_BINARY_PATH is unspecified (for example for a build without a .NET SDK or msbuild),
# link a stub contract descriptor into the runtime
add_library_clr(${LIBRARY} OBJECT "${DATA_DESCRIPTOR_SHARED_SOURCE_DIR}/contractdescriptorstub.c")
target_include_directories(${LIBRARY} PRIVATE ${GENERATED_CDAC_DESCRIPTOR_DIR})
message(STATUS "Using a stub cDAC contract descriptor")
else()
# generate a contract descriptor using cdac-build-tool from a data descriptor and contract json file

if(NOT EXISTS "${CDAC_BUILD_TOOL_BINARY_PATH}")
message(FATAL_ERROR "${CDAC_BUILD_TOOL_BINARY_PATH} does not exist")
endif()

add_library(${INTERMEDIARY_LIBRARY} OBJECT "${DATA_DESCRIPTOR_SHARED_SOURCE_DIR}/datadescriptor.cpp")
target_include_directories(${INTERMEDIARY_LIBRARY} PRIVATE ${DATA_DESCRIPTOR_SHARED_SOURCE_DIR})

if(CLR_CMAKE_TARGET_WIN32)
# turn off whole program optimization:
# 1. it creates object files that cdac-build-tool can't read
# 2. we never link INTERMEDIARY_LIBRARY into the final product - it's only job is to be scraped
set_target_properties(${INTERMEDIARY_LIBRARY} PROPERTIES
INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF
INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO OFF)
endif()

# inherit definitions, include directories, and dependencies from the INTERFACE target
add_interface_library(${INTERMEDIARY_LIBRARY} ${DATA_DESCRIPTORS_INTERFACE_TARGET})

set(CONTRACT_BASELINE_DIR "${CLR_REPO_ROOT_DIR}/docs/design/datacontracts/data")
set(CONTRACT_DESCRIPTOR_INPUT "${DATA_DESCRIPTOR_SHARED_SOURCE_DIR}/contract-descriptor.c.in")
set(CONTRACT_DESCRIPTOR_OUTPUT "${GENERATED_CDAC_DESCRIPTOR_DIR}/contract-descriptor.c")
set(CONTRACT_FILE "${DATA_DESCRIPTORS_CONTRACT_FILE}")

# generate the contract descriptor by running cdac-build-tool
# n.b. this just uses `dotnet` from the PATH. InitializeDotNetCli adds the appropriate directory
add_custom_command(
OUTPUT "${CONTRACT_DESCRIPTOR_OUTPUT}"
VERBATIM
COMMAND ${CLR_DOTNET_HOST_PATH} ${CDAC_BUILD_TOOL_BINARY_PATH} compose -i "${CONTRACT_DESCRIPTOR_INPUT}" -o "${CONTRACT_DESCRIPTOR_OUTPUT}" -b "${CONTRACT_BASELINE_DIR}" -c "${CONTRACT_FILE}" $<TARGET_OBJECTS:${INTERMEDIARY_LIBRARY}>
DEPENDS ${INTERMEDIARY_LIBRARY} ${DATA_DESCRIPTORS_DEPENDENCIES} $<TARGET_OBJECTS:${INTERMEDIARY_LIBRARY}> "${CONTRACT_FILE}" "${CONTRACT_DESCRIPTOR_INPUT}"
USES_TERMINAL
)

# It is important that LIBRARY is an object library;
# if it was static, linking it into the final dll would not export
# DotNetRuntimeContractDescriptor since it is not referenced anywhere.
add_library_clr(${LIBRARY} OBJECT
"${CONTRACT_DESCRIPTOR_OUTPUT}"
"${DATA_DESCRIPTOR_SHARED_SOURCE_DIR}/contractpointerdata.cpp"
)
add_dependencies(${LIBRARY} ${INTERMEDIARY_LIBRARY})

target_include_directories(${LIBRARY} PRIVATE
${DATA_DESCRIPTOR_SHARED_SOURCE_DIR}
${GENERATED_CDAC_DESCRIPTOR_DIR}
)

# inherit definitions, include directories, and dependencies from the INTERFACE target
add_interface_library(${LIBRARY} ${DATA_DESCRIPTORS_INTERFACE_TARGET})
endif()
endfunction(generate_data_descriptors)

# Links in an interface to a target with the interface include directories included
# before the targets include directories.
function(add_interface_library target_name interface_name)
get_target_property(target_includes ${target_name} INCLUDE_DIRECTORIES)
target_link_libraries(${target_name} PRIVATE ${interface_name})
set_target_properties(${target_name} PROPERTIES INCLUDE_DIRECTORIES "${target_includes}")

get_target_property(interface_includes ${interface_name} INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(${target_name} BEFORE PRIVATE ${interface_includes})
endfunction(add_interface_library)
187 changes: 187 additions & 0 deletions src/coreclr/debug/datadescriptor-shared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# Datadescriptor Implementation Infrastructure

This folder contains infrastructure to create data descriptors as defined in the [data_descriptor.md](../../../../docs/design/datacontracts/data_descriptor.md). Data descriptors enable diagnostic tooling (debuggers, profilers, etc.) to understand the internal layout and structure of .NET runtime objects without requiring intimate knowledge of implementation details.

<!-- ## Overview

### What are Data Descriptors?

Data descriptors are structured metadata that describe:
- **Type layouts**: Sizes and field offsets of internal runtime structures
- **Global values**: Important runtime constants and pointers
- **Algorithmic contracts**: Well-defined interfaces for diagnostic operations

This system is part of the broader [data contracts design](../../../../docs/design/datacontracts/datacontracts_design.md) that provides a stable, versioned interface between the .NET runtime and external diagnostic tools.

### How it Fits into the Diagnostic Ecosystem

```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Diagnostic Tool │───▶│ Contract │───▶│ Runtime Process │
│ (Debugger, etc.)│ │ Descriptor Blob │ │ Memory │
└─────────────────┘ └──────────────────┘ └─────────────────┘
┌──────────────┐
│ This System │
│ Generates │
└──────────────┘
```

The diagnostic tool reads the contract descriptor blob from the target process memory, which contains all the metadata needed to interpret runtime data structures safely and correctly. -->

## Getting Started

### Quick Example

Here's how to create a simple data descriptor for a new runtime component:

**1. Create the required files:**

```
your_component/
├── CMakeLists.txt
├── datadescriptor.h
├── datadescriptor.inc
└── contracts.jsonc
```

**2. Define your data descriptor (`datadescriptor.inc`):**

```cpp
CDAC_BASELINE("empty")
CDAC_TYPES_BEGIN()

CDAC_TYPE_BEGIN(MyRuntimeObject)
CDAC_TYPE_SIZE(sizeof(MyRuntimeObject))
CDAC_TYPE_FIELD(MyRuntimeObject, uint32, Id, offsetof(MyRuntimeObject, m_id))
CDAC_TYPE_FIELD(MyRuntimeObject, pointer, NextObject, offsetof(MyRuntimeObject, m_next))
CDAC_TYPE_END(MyRuntimeObject)

CDAC_TYPES_END()
CDAC_GLOBALS_BEGIN()

CDAC_GLOBAL(g_MyGlobalCounter, uint32, g_myGlobalCounter)

CDAC_GLOBALS_END()
```

**3. Create the header file (`datadescriptor.h`):**

```cpp
#include "my_runtime_object.h" // Your actual runtime structures
```

**4. Add CMake integration (`CMakeLists.txt`):**

```cmake
add_library(my_component_interface INTERFACE)
target_include_directories(my_component_interface INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}
# include dirs here)
generate_data_descriptors(
LIBRARY_NAME my_component_contract_descriptor
CONTRACT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/contracts.jsonc"
CONTRACT_NAME "MyComponentContractDescriptor"
INTERFACE_TARGET my_component_interface
DLLEXPORT
)
```

Then the output object library `my_component_contract_descriptor` can be linked into the shipping dll.

## CMake Integration and Build System

### Function Parameters

The `generate_data_descriptors` function defined in `clrdatadescriptors.cmake` takes the following arguments:

* **`LIBRARY_NAME`** (Required) - Sets the name of the target object being created
* **`CONTRACT_FILE`** (Required) - Path to the contract JSON file defining supported contracts
* **`CONTRACT_NAME`** (Required) - Name of the `ContractDescriptor` export symbol
* **`INTERFACE_TARGET`** (Required) - Interface target providing dependencies, include directories, and definitions
* **`DLLEXPORT`** (Optional) - Controls if the `CONTRACT_NAME` will be exported from the DLL

### Two-Phase Build Process

The build system uses a sophisticated two-phase approach:

**Phase 1: Intermediary Library**
- Compiles `datadescriptor.cpp` with your `datadescriptor.h` and `datadescriptor.inc`
- Creates object files that the `cdac-build-tool` can analyze
- Extracts type layout information and generates string pools

**Phase 2: Contract Descriptor Generation**
- Runs `cdac-build-tool` to process the intermediary object files
- Generates the final contract descriptor C source file
- Compiles this into the final library that gets linked into the runtime


## Macro Reference

### Structure Definition Macros

**`CDAC_BASELINE("identifier")`**
- Specifies the baseline data contract version
- Use `"empty"` for new descriptors
- Must appear before any other content

**`CDAC_TYPES_BEGIN()` / `CDAC_TYPES_END()`**
- Delimits the type definitions section
- Must contain all `CDAC_TYPE_*` macros

**`CDAC_TYPE_BEGIN(typeName)`**
- Starts a new type definition
- `typeName` must be globally unique within the descriptor

**`CDAC_TYPE_SIZE(sizeInBytes)`**
- Specifies the type has a determinate size
- Usually `sizeof(YourNativeType)`

**`CDAC_TYPE_INDETERMINATE(typeName)`**
- Specifies the type has indeterminate size
- Alternative to `CDAC_TYPE_SIZE`

**`CDAC_TYPE_FIELD(typeName, fieldType, fieldName, offset)`**
- Defines a field within the type
- `fieldType`: primitive type or another defined type
- `fieldName`: diagnostic-friendly name (use managed names for managed types)
- `offset`: byte offset, usually `offsetof()` or `cdac_data<T>::FieldName`

**`CDAC_TYPE_END(typeName)`**
- Closes the type definition
- `typeName` must match the corresponding `CDAC_TYPE_BEGIN`

### Global Value Macros

**`CDAC_GLOBALS_BEGIN()` / `CDAC_GLOBALS_END()`**
- Delimits the global values section

**`CDAC_GLOBAL(globalName, typeName, value)`**
- Defines a global literal value
- `value` must be a compile-time constant
- `typeName` can be a primitive type or defined type

**`CDAC_GLOBAL_POINTER(globalName, address)`**
- Defines a global pointer value
- `address` must be a compile-time constant pointer or `uintptr_t`

**`CDAC_GLOBAL_STRING(globalName, stringValue)`**
- Defines a global string value
- `stringValue` must be a compile-time string literal


## Reference Implementation

For comprehensive examples, see the current implementation in:
- **`src/coreclr/debug/runtimeinfo/`** - Complete real-world implementation
- `datadescriptor.h` - Headers and includes
- `datadescriptor.inc` - Full type definitions for runtime objects
- `contracts.jsonc` - Contract definitions
- `CMakeLists.txt` - Build integration

## Related Documentation

- **[Data Contracts Design](../../../../docs/design/datacontracts/datacontracts_design.md)** - Overall design and motivation
- **[Contract Descriptor](../../../../docs/design/datacontracts/contract-descriptor.md)** - Binary format specification

Check failure on line 186 in src/coreclr/debug/datadescriptor-shared/README.md

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces [Expected: 0; Actual: 2]
- **[Data Descriptor](../../../../docs/design/datacontracts/data_descriptor.md)** - Logical format specification
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include <stdint.h>
#include "contractconfiguration.h"

#ifdef _MSC_VER
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __attribute__((visibility("default")))
#endif

struct DotNetRuntimeContractDescriptor
struct ContractDescriptor
{
uint64_t magic;
uint32_t flags;
Expand All @@ -20,15 +20,16 @@ struct DotNetRuntimeContractDescriptor
const uintptr_t *pointer_data;
};

extern const uintptr_t contractDescriptorPointerData[];
extern const uintptr_t POINTER_DATA_NAME[];

DLLEXPORT struct DotNetRuntimeContractDescriptor DotNetRuntimeContractDescriptor;

DLLEXPORT struct DotNetRuntimeContractDescriptor DotNetRuntimeContractDescriptor = {
#if EXPORT_CONTRACT
DLLEXPORT
#endif // EXPORT_CONTRACT
struct ContractDescriptor CONTRACT_NAME = {
.magic = 0x0043414443434e44ull, // "DNCCDAC\0"
.flags = %%platformFlags%%,
.descriptor_size = %%jsonDescriptorSize%%,
.descriptor = "%%jsonDescriptor%%",
.pointer_data_count = %%pointerDataCount%%,
.pointer_data = &contractDescriptorPointerData[0],
};
.pointer_data = &POINTER_DATA_NAME[0],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

#define POINTER_DATA_NAME @POINTER_DATA_NAME@
#define CONTRACT_NAME @CONTRACT_NAME@

#define EXPORT_CONTRACT @EXPORT_CONTRACT@
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.

#include <stdint.h>
#include "contractconfiguration.h"

#ifdef _MSC_VER
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT __attribute__((visibility("default")))
#endif

struct DotNetRuntimeContractDescriptor
struct ContractDescriptor
{
uint64_t magic;
uint32_t flags;
Expand All @@ -20,20 +21,20 @@ struct DotNetRuntimeContractDescriptor
const uintptr_t *pointer_data;
};

extern const uintptr_t contractDescriptorPointerData[];
extern const uintptr_t POINTER_DATA_NAME[];

// just the placeholder pointer
const uintptr_t contractDescriptorPointerData[] = { (uintptr_t)0 };
const uintptr_t POINTER_DATA_NAME[] = { (uintptr_t)0 };

DLLEXPORT struct DotNetRuntimeContractDescriptor DotNetRuntimeContractDescriptor;
DLLEXPORT struct ContractDescriptor CONTRACT_NAME;

#define STUB_DESCRIPTOR "{\"version\":0,\"baseline\":\"empty\",\"contracts\":{},\"types\":{},\"globals\":{}}"

DLLEXPORT struct DotNetRuntimeContractDescriptor DotNetRuntimeContractDescriptor = {
DLLEXPORT struct ContractDescriptor CONTRACT_NAME = {
.magic = 0x0043414443434e44ull, // "DNCCDAC\0"
.flags = 0x1u & (sizeof(void*) == 4 ? 0x02u : 0x00u),
.descriptor_size = sizeof(STUB_DESCRIPTOR),
.descriptor = STUB_DESCRIPTOR,
.pointer_data_count = 1,
.pointer_data = &contractDescriptorPointerData[0],
};
.pointer_data = &POINTER_DATA_NAME[0],
};
Loading
Loading