Skip to content
Merged
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
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
82 changes: 82 additions & 0 deletions src/coreclr/clrdatadescriptors.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# cDAC contract descriptor

function(generate_data_descriptors)
set(options EXPORT_VISIBLE)
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_EXPORT_VISIBLE)
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")

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
target_link_libraries(${INTERMEDIARY_LIBRARY} PRIVATE ${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
# ${CONTRACT_NAME} 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 ${GENERATED_CDAC_DESCRIPTOR_DIR})

# inherit definitions, include directories, and dependencies from the INTERFACE target
target_link_libraries(${LIBRARY} PRIVATE ${DATA_DESCRIPTORS_INTERFACE_TARGET})
endif()
endfunction(generate_data_descriptors)
99 changes: 99 additions & 0 deletions src/coreclr/debug/datadescriptor-shared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# 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.

## 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
* **`EXPORT_VISIBLE`** (Optional) - Controls if the `CONTRACT_NAME` will be exported from the DLL

### Two-Phase Build Process

The build system uses a 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


## Current Implementation

For reference, see the current implementation in:
- **`src/coreclr/vm/datadescriptor/`** - 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
- **[Data Descriptor](../../../../docs/design/datacontracts/data_descriptor.md)** - Logical format specification
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,15 +21,18 @@ struct DotNetRuntimeContractDescriptor
const uintptr_t *pointer_data;
};

extern const uintptr_t contractDescriptorPointerData[];
// POINTER_DATA_NAME and CONTRACT_NAME are macros provided by
// contractconfiguration.h which is configured by CMake
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,22 @@ struct DotNetRuntimeContractDescriptor
const uintptr_t *pointer_data;
};

extern const uintptr_t contractDescriptorPointerData[];
// POINTER_DATA_NAME and CONTRACT_NAME are macros provided by
// contractconfiguration.h which is configured by CMake
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],
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "common.h"

#include <stddef.h>
#include <stdint.h>

#include "cdacplatformmetadata.hpp"
#include "threads.h"
#include "vars.hpp"
#include "datadescriptor.h"
#include "contractconfiguration.h"

extern "C"
{
// without an extern declaration, clang does not emit this global into the object file
extern const uintptr_t contractDescriptorPointerData[];
extern const uintptr_t POINTER_DATA_NAME[];

const uintptr_t contractDescriptorPointerData[] = {
const uintptr_t POINTER_DATA_NAME[] = {
(uintptr_t)0, // placeholder
#define CDAC_GLOBAL_POINTER(name,value) (uintptr_t)(value),
#include "datadescriptor.inc"
#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) (uintptr_t)(value),
#include "wrappeddatadescriptor.inc"
};

}
Loading
Loading