diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md new file mode 100644 index 00000000000000..2dbc60a30f6f9e --- /dev/null +++ b/docs/design/datacontracts/GC.md @@ -0,0 +1,83 @@ +# Contract GC + +This contract is for getting information about the garbage collector configuration and state. + +## APIs of contract + + +```csharp + // Return an array of strings identifying the GC type. + // Current return values can include: + // "workstation" or "server" + // "segments" or "regions" + string[] GetGCIdentifiers(); + // Return the number of GC heaps + uint GetGCHeapCount(); + // Return true if the GC structure is valid, otherwise return false + bool GetGCStructuresValid(); + // Return the maximum generation of the current GC + uint GetMaxGeneration(); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| _(none)_ | | | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `GCIdentifiers` | string | CSV string containing identifiers of the GC. Current values are "server", "workstation", "regions", and "segments" | +| `NumHeaps` | TargetPointer | Pointer to the number of heaps for server GC (int) | +| `StructureInvalidCount` | TargetPointer | Pointer to the count of invalid GC structures (int) | +| `MaxGeneration` | TargetPointer | Pointer to the maximum generation number (uint) | + +Contracts used: +| Contract Name | +| --- | +| _(none)_ | + + +Constants used: +| Name | Type | Purpose | Value | +| --- | --- | --- | --- | +| `WRK_HEAP_COUNT` | uint | The number of heaps in the `workstation` GC type | `1` | + +```csharp +GCHeapType GetGCIdentifiers() +{ + string gcIdentifiers = _target.ReadGlobalString("GCIdentifiers"); + return gcIdentifiers.Split(", "); +} + +uint GetGCHeapCount() +{ + string[] gcIdentifiers = GetGCIdentifiers() + if (gcType.Contains("workstation")) + { + return WRK_HEAP_COUNT; + } + if (gcType.Contains("server")) + { + TargetPointer pNumHeaps = target.ReadGlobalPointer("NumHeaps"); + return (uint)target.Read(pNumHeaps); + } + + throw new NotImplementedException("Unknown GC heap type"); +} + +bool GetGCStructuresValid() +{ + TargetPointer pInvalidCount = target.ReadGlobalPointer("StructureInvalidCount"); + int invalidCount = target.Read(pInvalidCount); + return invalidCount == 0; // Structures are valid if the count of invalid structures is zero +} + +uint GetMaxGeneration() +{ + TargetPointer pMaxGeneration = target.ReadGlobalPointer("MaxGeneration"); + return target.Read(pMaxGeneration); +} +``` diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index ece4f875dcb0ee..5dba165212f0d4 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -170,6 +170,7 @@ if (FEATURE_ENABLE_NO_ADDRESS_SPACE_RANDOMIZATION) add_definitions(-DFEATURE_ENABLE_NO_ADDRESS_SPACE_RANDOMIZATION) endif(FEATURE_ENABLE_NO_ADDRESS_SPACE_RANDOMIZATION) if (NOT CLR_CMAKE_HOST_ANDROID) + set(FEATURE_SVR_GC 1) add_definitions(-DFEATURE_SVR_GC) endif(NOT CLR_CMAKE_HOST_ANDROID) add_definitions(-DFEATURE_SYMDIFF) diff --git a/src/coreclr/debug/datadescriptor-shared/README.md b/src/coreclr/debug/datadescriptor-shared/README.md index b0bb9e8e3dc1d3..07b2748d4d208a 100644 --- a/src/coreclr/debug/datadescriptor-shared/README.md +++ b/src/coreclr/debug/datadescriptor-shared/README.md @@ -82,6 +82,12 @@ The build system uses a two-phase approach: - Defines a global string value - `stringValue` must be a compile-time string literal +**`CDAC_GLOBAL_SUB_DESCRIPTOR(globalName, address)`** +- Defines a reference to another contract descriptor +- `address` must be a compile-time constant pointer to a pointer to a contract descriptor +- Used for multi-contract scenarios where one contract references another +- Example: `CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor))` + ## Current Implementation diff --git a/src/coreclr/debug/datadescriptor-shared/contractpointerdata.cpp b/src/coreclr/debug/datadescriptor-shared/contractpointerdata.cpp index 443c7f182c001f..cefd0b04f0d963 100644 --- a/src/coreclr/debug/datadescriptor-shared/contractpointerdata.cpp +++ b/src/coreclr/debug/datadescriptor-shared/contractpointerdata.cpp @@ -14,4 +14,3 @@ extern "C" #include "wrappeddatadescriptor.inc" }; }; - diff --git a/src/coreclr/debug/datadescriptor-shared/datadescriptor.cpp b/src/coreclr/debug/datadescriptor-shared/datadescriptor.cpp index 0e3332539df9ec..e5ef9f3722d8d1 100644 --- a/src/coreclr/debug/datadescriptor-shared/datadescriptor.cpp +++ b/src/coreclr/debug/datadescriptor-shared/datadescriptor.cpp @@ -3,6 +3,14 @@ #include "datadescriptor.h" +#ifndef DLLEXPORT +#ifdef _MSC_VER +#define DLLEXPORT __declspec(dllexport) +#else +#define DLLEXPORT __attribute__ ((visibility ("default"))) +#endif // _MSC_VER +#endif // DLLEXPORT + // begin blob definition extern "C" @@ -52,7 +60,8 @@ struct GlobalStringSpec #define MAKE_GLOBALVALUELEN_NAME(globalname) CONCAT(cdac_string_pool_globalvalue__, globalname) // used to stringify the result of a macros expansion -#define STRINGIFY(x) #x +// __VA_ARGS__ is the argument list comma separated +#define STRINGIFY(...) #__VA_ARGS__ // define a struct where the size of each field is the length of some string. we will use offsetof to get // the offset of each struct element, which will be equal to the offset of the beginning of that string in the @@ -68,6 +77,7 @@ struct CDacStringPoolSizes #define CDAC_GLOBAL_STRING(name, stringval) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(STRINGIFY(stringval))) #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) +#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) #define CDAC_GLOBAL(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname)) #include "wrappeddatadescriptor.inc" @@ -129,6 +139,15 @@ enum #include "wrappeddatadescriptor.inc" }; +// count the global sub-descriptors +enum +{ + CDacBlobGlobalSubDescriptorsCount = +#define CDAC_GLOBALS_BEGIN() 0 +#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) + 1 +#include "wrappeddatadescriptor.inc" +}; + #define MAKE_TYPEFIELDS_TYNAME(tyname) CONCAT(CDacFieldsPoolTypeStart__, tyname) @@ -178,6 +197,7 @@ struct CDacGlobalPointerIndex #define DECL_LEN(membername) char membername; #define CDAC_GLOBALS_BEGIN() DECL_LEN(cdac_global_pointer_index_start_placeholder__) #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(CONCAT(cdac_global_pointer_index__, name)) +#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) DECL_LEN(CONCAT(cdac_global_pointer_index__, name)) #include "wrappeddatadescriptor.inc" #undef DECL_LEN }; @@ -204,6 +224,8 @@ struct BinaryBlobDataDescriptor uint32_t GlobalPointersStart; uint32_t GlobalStringValuesStart; + + uint32_t GlobalSubDescriptorsStart; uint32_t NamesPoolStart; uint32_t TypeCount; @@ -212,6 +234,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; uint32_t GlobalStringValuesCount; + uint32_t GlobalSubDescriptorsCount; uint32_t NamesPoolCount; @@ -223,11 +246,13 @@ struct BinaryBlobDataDescriptor } Directory; uint32_t PlatformFlags; uint32_t BaselineName; - struct TypeSpec Types[CDacBlobTypesCount]; - struct FieldSpec FieldsPool[CDacBlobFieldsPoolCount]; - struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; - struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; - struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; + // cpp does not allow zero-length arrays, so we add one extra element to allow having zero of a given type of descriptor + struct TypeSpec Types[CDacBlobTypesCount + 1]; + struct FieldSpec FieldsPool[CDacBlobFieldsPoolCount + 1]; + struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount + 1]; + struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount + 1]; + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount + 1]; + struct GlobalPointerSpec GlobalSubDescriptorValues[CDacBlobGlobalSubDescriptorsCount + 1]; uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; uint8_t EndMagic[4]; }; @@ -253,12 +278,14 @@ struct MagicAndBlob BlobDataDescriptor = { /* .GlobalLiteralValuesStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalLiteralValues), /* .GlobalPointersStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalPointerValues), /* .GlobalStringValuesStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalStringValues), + /* .GlobalSubDescriptorsStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalSubDescriptorValues), /* .NamesPoolStart = */ offsetof(struct BinaryBlobDataDescriptor, NamesPool), /* .TypeCount = */ CDacBlobTypesCount, /* .FieldsPoolCount = */ CDacBlobFieldsPoolCount, /* .GlobalLiteralValuesCount = */ CDacBlobGlobalLiteralsCount, /* .GlobalPointerValuesCount = */ CDacBlobGlobalPointersCount, /* .GlobalStringValuesCount = */ CDacBlobGlobalStringsCount, + /* .GlobalSubDescriptorsCount = */ CDacBlobGlobalSubDescriptorsCount, /* .NamesPoolCount = */ sizeof(struct CDacStringPoolSizes), /* .TypeSpecSize = */ sizeof(struct TypeSpec), /* .FieldSpecSize = */ sizeof(struct FieldSpec), @@ -305,12 +332,18 @@ struct MagicAndBlob BlobDataDescriptor = { #include "wrappeddatadescriptor.inc" }, + /* .GlobalSubDescriptorValues = */ { +#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) { /* .Name = */ GET_GLOBAL_NAME(name), /* .PointerDataIndex = */ GET_GLOBAL_POINTER_INDEX(name) }, +#include "wrappeddatadescriptor.inc" + }, + /* .NamesPool = */ ("\0" // starts with a nul #define CDAC_BASELINE(name) name "\0" #define CDAC_TYPE_BEGIN(name) #name "\0" #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" #membertyname "\0" #define CDAC_GLOBAL_STRING(name,value) #name "\0" STRINGIFY(value) "\0" #define CDAC_GLOBAL_POINTER(name,value) #name "\0" +#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) #name "\0" #define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0" #include "wrappeddatadescriptor.inc" ), diff --git a/src/coreclr/debug/datadescriptor-shared/wrappeddatadescriptor.inc b/src/coreclr/debug/datadescriptor-shared/wrappeddatadescriptor.inc index 7500ac93388803..abb73293885cb9 100644 --- a/src/coreclr/debug/datadescriptor-shared/wrappeddatadescriptor.inc +++ b/src/coreclr/debug/datadescriptor-shared/wrappeddatadescriptor.inc @@ -40,6 +40,9 @@ #ifndef CDAC_GLOBAL_STRING #define CDAC_GLOBAL_STRING(globalname,stringval) #endif +#ifndef CDAC_GLOBAL_SUB_DESCRIPTOR +#define CDAC_GLOBAL_SUB_DESCRIPTOR(globalname,addr) +#endif #ifndef CDAC_GLOBALS_END #define CDAC_GLOBALS_END() #endif @@ -58,4 +61,5 @@ #undef CDAC_GLOBAL #undef CDAC_GLOBAL_POINTER #undef CDAC_GLOBAL_STRING +#undef CDAC_GLOBAL_SUB_DESCRIPTOR #undef CDAC_GLOBALS_END diff --git a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt index 6c2e0a48b74af8..ebaecf8d40a740 100644 --- a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt @@ -73,6 +73,8 @@ if (NOT CLR_CMAKE_HOST_ARCH_WASM) set(LIB_CORDBEE cordbee_wks) set(LIB_INTEROP interop) set(LIB_CDAC_CONTRACT_DESCRIPTOR cdac_contract_descriptor) + set(LIB_CDAC_GC_WKS_DESCRIPTOR gc_wks_descriptor) + set(LIB_CDAC_GC_SVR_DESCRIPTOR gc_svr_descriptor) endif(NOT CLR_CMAKE_HOST_ARCH_WASM) if (CLR_CMAKE_HOST_UNIX AND NOT CLR_CMAKE_TARGET_ARCH_WASM) @@ -104,8 +106,15 @@ set(CORECLR_LIBRARIES coreclrminipal gc_pal ${LIB_CDAC_CONTRACT_DESCRIPTOR} + ${LIB_CDAC_GC_WKS_DESCRIPTOR} ) +if (FEATURE_SVR_GC) + list(APPEND CORECLR_LIBRARIES + ${LIB_CDAC_GC_SVR_DESCRIPTOR} + ) +endif(FEATURE_SVR_GC) + if(CLR_CMAKE_TARGET_ARCH_AMD64) list(APPEND CORECLR_LIBRARIES gc_vxsort diff --git a/src/coreclr/gc/CMakeLists.txt b/src/coreclr/gc/CMakeLists.txt index 3885bb978688a6..38ac8c8601aab2 100644 --- a/src/coreclr/gc/CMakeLists.txt +++ b/src/coreclr/gc/CMakeLists.txt @@ -41,6 +41,7 @@ endif (CLR_CMAKE_TARGET_ARCH_AMD64) if (CLR_CMAKE_TARGET_WIN32) set(GC_HEADERS + env/cdacdata.h env/common.h env/etmdummy.h env/gcenv.base.h @@ -104,19 +105,31 @@ list(APPEND GC_SOURCES ${GC_HEADERS}) convert_to_absolute_path(GC_SOURCES ${GC_SOURCES}) if(FEATURE_STANDALONE_GC) - # clrgcexp is build with standalone+regions if (CLR_CMAKE_TARGET_ARCH_ARM64 OR CLR_CMAKE_TARGET_ARCH_AMD64) + set(BUILD_EXP_GC 1) + endif() + + # clrgcexp is build with standalone+regions + if (BUILD_EXP_GC) add_library_clr(clrgcexp SHARED ${GC_SOURCES}) add_dependencies(clrgcexp eventing_headers) target_link_libraries(clrgcexp PRIVATE ${GC_LINK_LIBRARIES}) + target_link_libraries(clrgcexp PRIVATE gcexp_dll_wks_descriptor) + if (FEATURE_SVR_GC) + target_link_libraries(clrgcexp PRIVATE gcexp_dll_svr_descriptor) + endif() target_compile_definitions(clrgcexp PRIVATE -DUSE_REGIONS) install_clr(TARGETS clrgcexp DESTINATIONS . COMPONENT runtime) - endif (CLR_CMAKE_TARGET_ARCH_ARM64 OR CLR_CMAKE_TARGET_ARCH_AMD64) + endif (BUILD_EXP_GC) # clrgc is build with standalone+segments add_library_clr(clrgc SHARED ${GC_SOURCES}) add_dependencies(clrgc eventing_headers) target_link_libraries(clrgc PRIVATE ${GC_LINK_LIBRARIES}) + target_link_libraries(clrgc PRIVATE gc_dll_wks_descriptor) + if(FEATURE_SVR_GC) + target_link_libraries(clrgc PRIVATE gc_dll_svr_descriptor) + endif() install_clr(TARGETS clrgc DESTINATIONS . COMPONENT runtime) add_definitions(-DBUILD_AS_STANDALONE) @@ -134,6 +147,9 @@ if(FEATURE_STANDALONE_GC) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/env) + add_definitions(-DGC_DESCRIPTOR) + add_subdirectory(datadescriptor) + install_clr(TARGETS clrgc DESTINATIONS . sharedFramework COMPONENT runtime) if (CLR_CMAKE_TARGET_ARCH_ARM64 OR CLR_CMAKE_TARGET_ARCH_AMD64) install_clr(TARGETS clrgcexp DESTINATIONS . sharedFramework COMPONENT runtime) diff --git a/src/coreclr/gc/datadescriptor/.editorconfig b/src/coreclr/gc/datadescriptor/.editorconfig new file mode 100644 index 00000000000000..ce2ce7b8d1ec54 --- /dev/null +++ b/src/coreclr/gc/datadescriptor/.editorconfig @@ -0,0 +1,2 @@ +[contracts.jsonc] +indent_size = 2 diff --git a/src/coreclr/gc/datadescriptor/CMakeLists.txt b/src/coreclr/gc/datadescriptor/CMakeLists.txt new file mode 100644 index 00000000000000..786969587fef6e --- /dev/null +++ b/src/coreclr/gc/datadescriptor/CMakeLists.txt @@ -0,0 +1,57 @@ +# cDAC GC contract descriptor + +# Up to four separate descriptors can be generated: +# 1. gc_dll_wks_descriptor - WKS GC contract descriptor for the GC DLL +# 2. gc_dll_svr_descriptor - SVR GC contract descriptor for the GC DLL (if FEATURE_SVR_GC is enabled) +# 3. gcexp_dll_wks_descriptor - WKS GC contract descriptor for the GC EXP DLL (if BUILD_EXP_GC is enabled) +# 4. gcexp_dll_svr_descriptor - SVR GC contract descriptor for the GC EXP DLL (if BUILD_EXP_GC and FEATURE_SVR_GC are enabled) + +add_library(gc_dll_wks_descriptor_interface INTERFACE) +target_include_directories(gc_dll_wks_descriptor_interface INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(gc_dll_wks_descriptor_interface eventing_headers) +generate_data_descriptors( + LIBRARY_NAME gc_dll_wks_descriptor + CONTRACT_NAME "GCContractDescriptorWKS" + CONTRACT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/contracts.jsonc" + INTERFACE_TARGET gc_dll_wks_descriptor_interface) + +if (FEATURE_SVR_GC) + add_library(gc_dll_svr_descriptor_interface INTERFACE) + target_include_directories(gc_dll_svr_descriptor_interface INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies(gc_dll_svr_descriptor_interface eventing_headers) + target_compile_definitions(gc_dll_svr_descriptor_interface INTERFACE -DSERVER_GC) + generate_data_descriptors( + LIBRARY_NAME gc_dll_svr_descriptor + CONTRACT_NAME "GCContractDescriptorSVR" + CONTRACT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/contracts.jsonc" + INTERFACE_TARGET gc_dll_svr_descriptor_interface) +endif() + +if(BUILD_EXP_GC) + add_library(gcexp_dll_wks_descriptor_interface INTERFACE) + target_include_directories(gcexp_dll_wks_descriptor_interface INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies(gcexp_dll_wks_descriptor_interface eventing_headers) + target_compile_definitions(gcexp_dll_wks_descriptor_interface INTERFACE -DUSE_REGIONS) + generate_data_descriptors( + LIBRARY_NAME gcexp_dll_wks_descriptor + CONTRACT_NAME "GCContractDescriptorWKS" + CONTRACT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/contracts.jsonc" + INTERFACE_TARGET gcexp_dll_wks_descriptor_interface) + + if (FEATURE_SVR_GC) + add_library(gcexp_dll_svr_descriptor_interface INTERFACE) + target_include_directories(gcexp_dll_svr_descriptor_interface INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies(gcexp_dll_svr_descriptor_interface eventing_headers) + target_compile_definitions(gcexp_dll_svr_descriptor_interface INTERFACE -DUSE_REGIONS) + target_compile_definitions(gcexp_dll_svr_descriptor_interface INTERFACE -DSERVER_GC) + generate_data_descriptors( + LIBRARY_NAME gcexp_dll_svr_descriptor + CONTRACT_NAME "GCContractDescriptorSVR" + CONTRACT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/contracts.jsonc" + INTERFACE_TARGET gcexp_dll_svr_descriptor_interface) + endif() +endif() diff --git a/src/coreclr/gc/datadescriptor/contracts.jsonc b/src/coreclr/gc/datadescriptor/contracts.jsonc new file mode 100644 index 00000000000000..99ece99f677fa1 --- /dev/null +++ b/src/coreclr/gc/datadescriptor/contracts.jsonc @@ -0,0 +1,13 @@ +//algorithmic contracts for coreclr +// The format of this file is: JSON with comments +// { +// "CONTRACT NAME": VERSION, +// ... +// } +// CONTRACT NAME is an arbitrary string, VERSION is an integer +// +// cdac-build-tool can take multiple "-c contract_file" arguments +// so to conditionally include contracts, put additional contracts in a separate file +{ + "GC": 1 +} diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.h b/src/coreclr/gc/datadescriptor/datadescriptor.h new file mode 100644 index 00000000000000..8eb2474ff71547 --- /dev/null +++ b/src/coreclr/gc/datadescriptor/datadescriptor.h @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include +#include + +#include "common.h" +#include "gcenv.h" +#include "gc.h" +#include "gcscan.h" +#include "gchandletableimpl.h" +#include "gceventstatus.h" + +#ifdef SERVER_GC +#define GC_NAMESPACE SVR +#else // SERVER_GC +#define GC_NAMESPACE WKS +#endif // SERVER_GC + +// These files are designed to be used inside of the GC namespace. +// Without the namespace (WKS/SVR) there are naming conflicts. +namespace GC_NAMESPACE { + +#include "gcimpl.h" +#include "gcpriv.h" + +} + +// On non-MSVC builds explicit specializations must be declared in the namespace the template was defined. +// Due to the gc being built into coreclr, cdac_data must be defined in the global scope. +template<> +struct cdac_data +{ +#ifdef MULTIPLE_HEAPS + static constexpr GC_NAMESPACE::gc_heap*** Heaps = &GC_NAMESPACE::gc_heap::g_heaps; +#endif // MULTIPLE_HEAPS +}; diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc new file mode 100644 index 00000000000000..82b93b35dd44d2 --- /dev/null +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -0,0 +1,43 @@ +// 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. +// +// This file is compiled using the target architecture. Preprocessor defines for the target +// platform will be available. It is ok to use `#ifdef`. + + +CDAC_BASELINE("empty") +CDAC_TYPES_BEGIN() + +CDAC_TYPES_END() + +CDAC_GLOBALS_BEGIN() + + +#ifdef SERVER_GC +#define GC_TYPE server +#else // SERVER_GC +#define GC_TYPE workstation +#endif // SERVER_GC + +#ifdef USE_REGIONS +#define HEAP_TYPE regions +#else // USE_REGIONS +#define HEAP_TYPE segments +#endif // USE_REGIONS + +// CDAC_GLOBAL_STRING takes a single value argument. +// To avoid issues with commas in the string we wrap the input string in a macro. +#define GC_IDENTIFIER(...) __VA_ARGS__ // GC_IDENTIFIER(gc, heap) expands to: gc, heap +CDAC_GLOBAL_STRING(GCIdentifiers, GC_IDENTIFIER(GC_TYPE, HEAP_TYPE)) + +CDAC_GLOBAL_POINTER(MaxGeneration, &::g_max_generation) +CDAC_GLOBAL_POINTER(StructureInvalidCount, &GCScan::m_GcStructuresInvalidCnt) + +#ifdef SERVER_GC +CDAC_GLOBAL_POINTER(NumHeaps, &GC_NAMESPACE::gc_heap::n_heaps) +CDAC_GLOBAL_POINTER(Heaps, cdac_data::Heaps) +#endif // SERVER_GC + +CDAC_GLOBALS_END() diff --git a/src/coreclr/gc/env/cdacdata.h b/src/coreclr/gc/env/cdacdata.h new file mode 100644 index 00000000000000..a0ef5deca55f3f --- /dev/null +++ b/src/coreclr/gc/env/cdacdata.h @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef CDACDATA_H__ +#define CDACDATA_H__ + +// See datadescriptor.h +// +// This struct enables exposing information that is private to a class to the cDAC. For example, +// if class C has private information that must be provided, declare cdac_data as a friend of C +// where D is the specialization of cdac_data that will expose the information. Then provide a +// specialization cdac_data with constexpr members exposing the information. +// +// Note: in the common case, type D will be type C. +// +// For example, if the offset of field F in class C is required: +// +// class C { +// private: +// int F; +// friend struct ::cdac_data; +// }; +// template<> struct cdac_data { +// static constexpr size_t F_Offset = offsetof(C, F); +// }; +// +template +struct cdac_data +{ +}; + +#endif// CDACDATA_H__ diff --git a/src/coreclr/gc/env/volatile.h b/src/coreclr/gc/env/volatile.h index e1c014543139a0..c15dcb1ca66448 100644 --- a/src/coreclr/gc/env/volatile.h +++ b/src/coreclr/gc/env/volatile.h @@ -364,7 +364,7 @@ class Volatile // accessed without using Load and Store, but it is necessary for passing Volatile to APIs like // InterlockedIncrement. // - inline volatile T* GetPointer() { return (volatile T*)&m_val; } + inline constexpr volatile T* GetPointer() { return (volatile T*)&m_val; } // @@ -394,8 +394,8 @@ class Volatile // a pointer to a volatile T here, so we cannot accidentally pass this pointer to an API that // expects a normal pointer. // - inline T volatile * operator&() {return this->GetPointer();} - inline T volatile const * operator&() const {return this->GetPointer();} + inline constexpr T volatile * operator&() {return this->GetPointer();} + inline constexpr T volatile const * operator&() const {return this->GetPointer();} // // Comparison operators diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 238f6986efd7cd..4b1c475621e4f5 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -53616,10 +53616,22 @@ bool GCHeap::IsConcurrentGCEnabled() #endif //BACKGROUND_GC } +#ifdef GC_DESCRIPTOR +extern "C" +{ + struct ContractDescriptor; + extern ContractDescriptor GCContractDescriptorWKS; +#if FEATURE_SVR_GC + extern ContractDescriptor GCContractDescriptorSVR; +#endif // FEATURE_SVR_GC +} +#endif // GC_DESCRIPTOR + void PopulateDacVars(GcDacVars *gcDacVars) { bool v2 = gcDacVars->minor_version_number >= 2; bool v4 = gcDacVars->minor_version_number >= 4; + bool v6 = gcDacVars->minor_version_number >= 6; #define DEFINE_FIELD(field_name, field_type) offsetof(CLASS_NAME, field_name), #define DEFINE_DPTR_FIELD(field_name, field_type) offsetof(CLASS_NAME, field_name), @@ -53757,6 +53769,16 @@ void PopulateDacVars(GcDacVars *gcDacVars) gcDacVars->dynamic_adaptation_mode = nullptr; #endif //DYNAMIC_HEAP_COUNT } + if (v6) + { +#ifdef GC_DESCRIPTOR +#ifdef MULTIPLE_HEAPS + gcDacVars->gc_descriptor = (void*)&GCContractDescriptorSVR; +#else // MULTIPLE_HEAPS + gcDacVars->gc_descriptor = (void*)&GCContractDescriptorWKS; +#endif // MULTIPLE_HEAPS +#endif // GC_DESCRIPTOR + } } int GCHeap::RefreshMemoryLimit() diff --git a/src/coreclr/gc/gc.h b/src/coreclr/gc/gc.h index 05ddbf909e21c2..5d92a577e1651a 100644 --- a/src/coreclr/gc/gc.h +++ b/src/coreclr/gc/gc.h @@ -30,6 +30,8 @@ Module Name: #endif // BUILD_AS_STANDALONE #include "gcconfig.h" +#include "cdacdata.h" + /* * Promotion Function Prototypes */ diff --git a/src/coreclr/gc/gcinterface.dacvars.def b/src/coreclr/gc/gcinterface.dacvars.def index fc071e881faef8..80a1dbb6c0cc91 100644 --- a/src/coreclr/gc/gcinterface.dacvars.def +++ b/src/coreclr/gc/gcinterface.dacvars.def @@ -91,6 +91,9 @@ GC_DAC_VAL (size_t, card_table_info_size) // Here is where v5.4 fields starts GC_DAC_VAR (int, dynamic_adaptation_mode) +// Here is where v5.6 fields start +GC_DAC_VAL (void*, gc_descriptor) + #undef GC_DAC_VAR #undef GC_DAC_ARRAY_VAR #undef GC_DAC_PTR_VAR diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 425820ab1a1183..dc319277c413f8 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -11,7 +11,7 @@ // The minor version of the IGCHeap interface. Non-breaking changes are required // to bump the minor version number. GCs and EEs with minor version number // mismatches can still interoperate correctly, with some care. -#define GC_INTERFACE_MINOR_VERSION 5 +#define GC_INTERFACE_MINOR_VERSION 6 // The major version of the IGCToCLR interface. Breaking changes to this interface // require bumps in the major version number. diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index 79a2dd1b2d4ba5..49a540bebc74f6 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -1547,6 +1547,8 @@ class gc_heap friend class mark_queue_t; + friend struct ::cdac_data; + #ifdef MULTIPLE_HEAPS typedef void (gc_heap::* card_fn) (uint8_t**, int); #define call_fn(this_arg,fn) (this_arg->*fn) diff --git a/src/coreclr/inc/volatile.h b/src/coreclr/inc/volatile.h index 5f186352044000..90fd1a766cb29a 100644 --- a/src/coreclr/inc/volatile.h +++ b/src/coreclr/inc/volatile.h @@ -422,7 +422,7 @@ class Volatile // accessed without using Load and Store, but it is necessary for passing Volatile to APIs like // InterlockedIncrement. // - inline volatile T* GetPointer() { return (volatile T*)&m_val; } + inline constexpr volatile T* GetPointer() { return (volatile T*)&m_val; } // @@ -453,8 +453,8 @@ class Volatile // a pointer to a volatile T here, so we cannot accidentally pass this pointer to an API that // expects a normal pointer. // - inline T volatile * operator&() {return this->GetPointer();} - inline T volatile const * operator&() const {return this->GetPointer();} + inline constexpr T volatile * operator&() {return this->GetPointer();} + inline constexpr T volatile const * operator&() const {return this->GetPointer();} // // Comparison operators diff --git a/src/coreclr/scripts/genEtwProvider.py b/src/coreclr/scripts/genEtwProvider.py index 85411828473a8f..09a141be6e0752 100644 --- a/src/coreclr/scripts/genEtwProvider.py +++ b/src/coreclr/scripts/genEtwProvider.py @@ -164,7 +164,7 @@ def genEtwMacroHeader(manifest, exclusion_filename, intermediate): header_file.write("#define NO_OF_ETW_PROVIDERS " + str(numOfProviders) + "\n") header_file.write("#define MAX_BYTES_PER_ETW_PROVIDER " + str(nMaxEventBytesPerProvider) + "\n") - header_file.write("EXTERN_C constexpr BYTE etwStackSupportedEvents[NO_OF_ETW_PROVIDERS][MAX_BYTES_PER_ETW_PROVIDER] = \n{\n") + header_file.write("EXTERN_C inline constexpr BYTE etwStackSupportedEvents[NO_OF_ETW_PROVIDERS][MAX_BYTES_PER_ETW_PROVIDER] = \n{\n") for providerNode in tree.getElementsByTagName('provider'): stackSupportedEvents = [0]*nMaxEventBytesPerProvider diff --git a/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs b/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs index 14ee0988ea2b8b..730185a4efd6ca 100644 --- a/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs +++ b/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs @@ -14,23 +14,25 @@ namespace Microsoft.DotNet.Diagnostics.DataContract.BuildTool; public class DataDescriptorModel { - public int Version => 0; + public int Version => 1; [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public string Baseline { get; } public IReadOnlyDictionary Types { get; } public IReadOnlyDictionary Globals { get; } + public IReadOnlyDictionary SubDescriptors { get; } public IReadOnlyDictionary Contracts { get; } [JsonIgnore] public uint PlatformFlags { get; } // The number of indirect globals plus 1 for the placeholder at index 0 [JsonIgnore] - public int PointerDataCount => 1 + Globals.Values.Count(g => g.Value.Kind == GlobalValue.KindEnum.Indirect); + public int PointerDataCount => 1 + Globals.Values.Count(g => g.Value.Kind == GlobalValue.KindEnum.Indirect) + SubDescriptors.Values.Count(s => s.Value.Kind == GlobalValue.KindEnum.Indirect); - private DataDescriptorModel(string baseline, IReadOnlyDictionary types, IReadOnlyDictionary globals, IReadOnlyDictionary contracts, uint platformFlags) + private DataDescriptorModel(string baseline, IReadOnlyDictionary types, IReadOnlyDictionary globals, IReadOnlyDictionary subDescriptors, IReadOnlyDictionary contracts, uint platformFlags) { Baseline = baseline; Types = types; Globals = globals; + SubDescriptors = subDescriptors; Contracts = contracts; PlatformFlags = platformFlags; } @@ -63,6 +65,12 @@ internal void DumpModel() Console.WriteLine($" Type: {global.Type}"); Console.WriteLine($" Value: {global.Value}"); } + foreach (var (subDescriptorName, subDescriptor) in SubDescriptors) + { + Console.WriteLine($"Sub-Descriptor: {subDescriptorName}"); + Console.WriteLine($" Type: {subDescriptor.Type}"); + Console.WriteLine($" Value: {subDescriptor.Value}"); + } foreach (var (contractName, contract) in Contracts) { Console.WriteLine($"Contract: {contractName}"); @@ -89,6 +97,7 @@ public class Builder private bool _baselineParsed; private readonly Dictionary _types = new(); private readonly Dictionary _globals = new(); + private readonly Dictionary _subDescriptors = new(); private readonly Dictionary _contracts = new(); public Builder(string baselinesDir) { @@ -130,6 +139,22 @@ public GlobalBuilder AddOrUpdateGlobal(string name, string type, GlobalValue? va return global; } + public GlobalBuilder AddOrUpdateSubDescriptor(string name, string type, GlobalValue? value) + { + if (!_baselineParsed) + { + throw new InvalidOperationException("Baseline must be set before adding globals"); + } + if (!_subDescriptors.TryGetValue(name, out var subDescriptor)) + { + subDescriptor = new GlobalBuilder(); + _subDescriptors[name] = subDescriptor; + } + subDescriptor.Type = type; + subDescriptor.Value = value; + return subDescriptor; + } + public void AddOrUpdateContract(string name, int version) { if (!_contracts.TryGetValue(name, out var contract)) @@ -196,12 +221,22 @@ public DataDescriptorModel Build() } globals[globalName] = new GlobalModel { Type = globalBuilder.Type, Value = v.Value }; } + var subDescriptors = new Dictionary(); + foreach (var (subDescriptorName, subDescriptorBuilder) in _subDescriptors) + { + GlobalValue? v = subDescriptorBuilder.Value; + if (v == null) + { + throw new InvalidOperationException($"Value must be set for sub-descriptor {subDescriptorName}"); + } + subDescriptors[subDescriptorName] = new GlobalModel { Type = subDescriptorBuilder.Type, Value = v.Value }; + } var contracts = new Dictionary(); foreach (var (contractName, contractBuilder) in _contracts) { contracts[contractName] = contractBuilder.Build(); } - return new DataDescriptorModel(_baseline, types, globals, contracts, PlatformFlags); + return new DataDescriptorModel(_baseline, types, globals, subDescriptors, contracts, PlatformFlags); } } diff --git a/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs b/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs index 1c379a7e61dd05..59f492e5a97253 100644 --- a/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs +++ b/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs @@ -161,6 +161,8 @@ private struct HeaderDirectory public uint GlobalPointersStart; public uint GlobalStringValuesStart; + + public uint GlobalSubDescriptorsStart; public uint NamesStart; public uint TypesCount; @@ -169,6 +171,7 @@ private struct HeaderDirectory public uint GlobalLiteralValuesCount; public uint GlobalPointerValuesCount; public uint GlobalStringValuesCount; + public uint GlobalSubDescriptorsCount; public uint NamesPoolCount; @@ -184,20 +187,22 @@ private static void DumpHeaderDirectory(HeaderDirectory headerDirectory) Console.WriteLine($""" Scaped Header Directory: - Baseline Start = 0x{headerDirectory.FlagsAndBaselineStart:x8} - Types Start = 0x{headerDirectory.TypesStart:x8} - Fields Pool Start = 0x{headerDirectory.FieldsPoolStart:x8} - Global Literals Start = 0x{headerDirectory.GlobalLiteralValuesStart:x8} - Global Pointers Start = 0x{headerDirectory.GlobalPointersStart:x8} - Global Strings Start = 0x{headerDirectory.GlobalStringValuesStart:x8} - Names Pool Start = 0x{headerDirectory.NamesStart:x8} - - Types Count = {headerDirectory.TypesCount} - Fields Pool Count = {headerDirectory.FieldsPoolCount} - Global Literal Values Count = {headerDirectory.GlobalLiteralValuesCount} - Global Pointer Values Count = {headerDirectory.GlobalPointerValuesCount} - Global String Values count = {headerDirectory.GlobalStringValuesCount} - Names Pool Count = {headerDirectory.NamesPoolCount} + Baseline Start = 0x{headerDirectory.FlagsAndBaselineStart:x8} + Types Start = 0x{headerDirectory.TypesStart:x8} + Fields Pool Start = 0x{headerDirectory.FieldsPoolStart:x8} + Global Literals Start = 0x{headerDirectory.GlobalLiteralValuesStart:x8} + Global Pointers Start = 0x{headerDirectory.GlobalPointersStart:x8} + Global Strings Start = 0x{headerDirectory.GlobalStringValuesStart:x8} + Global Sub-Descriptors Start = 0x{headerDirectory.GlobalSubDescriptorsStart:x8} + Names Pool Start = 0x{headerDirectory.NamesStart:x8} + + Types Count = {headerDirectory.TypesCount} + Fields Pool Count = {headerDirectory.FieldsPoolCount} + Global Literal Values Count = {headerDirectory.GlobalLiteralValuesCount} + Global Pointer Values Count = {headerDirectory.GlobalPointerValuesCount} + Global String Values Count = {headerDirectory.GlobalStringValuesCount} + Global Sub-Descriptors Count = {headerDirectory.GlobalSubDescriptorsCount} + Names Pool Count = {headerDirectory.NamesPoolCount} """); } @@ -213,6 +218,8 @@ private static HeaderDirectory ReadHeader(ScraperState state) var globalPointersStart = state.ReadUInt32(); var globalStringValuesStart = state.ReadUInt32(); + + var globalSubDescriptorsStart = state.ReadUInt32(); var namesStart = state.ReadUInt32(); var typeCount = state.ReadUInt32(); @@ -220,7 +227,9 @@ private static HeaderDirectory ReadHeader(ScraperState state) var globalLiteralValuesCount = state.ReadUInt32(); var globalPointerValuesCount = state.ReadUInt32(); - var GlobalStringValuesCount = state.ReadUInt32(); + + var globalStringValuesCount = state.ReadUInt32(); + var globalSubDescriptorsCount = state.ReadUInt32(); var namesPoolCount = state.ReadUInt32(); @@ -237,6 +246,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) GlobalLiteralValuesStart = globalLiteralValuesStart, GlobalPointersStart = globalPointersStart, GlobalStringValuesStart = globalStringValuesStart, + GlobalSubDescriptorsStart = globalSubDescriptorsStart, NamesStart = namesStart, TypesCount = typeCount, @@ -244,7 +254,9 @@ private static HeaderDirectory ReadHeader(ScraperState state) GlobalLiteralValuesCount = globalLiteralValuesCount, GlobalPointerValuesCount = globalPointerValuesCount, - GlobalStringValuesCount = GlobalStringValuesCount, + + GlobalStringValuesCount = globalStringValuesCount, + GlobalSubDescriptorsCount = globalSubDescriptorsCount, NamesPoolCount = namesPoolCount, @@ -304,9 +316,10 @@ private sealed class Content public required uint Baseline { get; init; } public required IReadOnlyList TypeSpecs { get; init; } public required IReadOnlyList FieldSpecs { get; init; } - public required IReadOnlyList GlobaLiteralSpecs { get; init; } + public required IReadOnlyList GlobalLiteralSpecs { get; init; } public required IReadOnlyList GlobalPointerSpecs { get; init; } public required IReadOnlyList GlobalStringSpecs { get; init; } + public required IReadOnlyList GlobalSubDescriptorSpecs { get; init; } public required ReadOnlyMemory NamesPool { get; init; } internal string GetPoolString(uint stringIdx) @@ -361,7 +374,7 @@ public void AddToModel(DataDescriptorModel.Builder builder) } } - foreach (var globalSpec in GlobaLiteralSpecs) + foreach (var globalSpec in GlobalLiteralSpecs) { var globalName = GetPoolString(globalSpec.NameIdx); var globalType = GetPoolString(globalSpec.TypeNameIdx); @@ -386,6 +399,15 @@ public void AddToModel(DataDescriptorModel.Builder builder) builder.AddOrUpdateGlobal(globalName, DataDescriptorModel.StringTypeName, globalValue); WriteVerbose($"Global string {globalName} has value {globalValue}"); } + + foreach (var subDescriptor in GlobalSubDescriptorSpecs) + { + var globalName = GetPoolString(subDescriptor.NameIdx); + var auxDataIdx = subDescriptor.AuxDataIdx; + var globalValue = DataDescriptorModel.GlobalValue.MakeIndirect(auxDataIdx); + builder.AddOrUpdateSubDescriptor(globalName, DataDescriptorModel.PointerTypeName, globalValue); + WriteVerbose($"Global sub-descriptor {globalName} has index {globalValue}"); + } } private void WriteVerbose(string msg) @@ -408,6 +430,7 @@ private Content ReadContent(ScraperState state, HeaderDirectory header) GlobalLiteralSpec[] globalLiteralSpecs = ReadGlobalLiteralSpecs(state, header); GlobalPointerSpec[] globalPointerSpecs = ReadGlobalPointerSpecs(state, header); GlobalStringSpec[] globalStringSpecs = ReadGlobalStringSpecs(state, header); + GlobalPointerSpec[] globalSubDescriptorSpecs = ReadGlobalSubDescriptorSpecs(state, header); byte[] namesPool = ReadNamesPool(state, header); byte[] endMagic = new byte[4]; @@ -431,9 +454,10 @@ private Content ReadContent(ScraperState state, HeaderDirectory header) Baseline = baselineNameIdx, TypeSpecs = typeSpecs, FieldSpecs = fieldSpecs, - GlobaLiteralSpecs = globalLiteralSpecs, + GlobalLiteralSpecs = globalLiteralSpecs, GlobalPointerSpecs = globalPointerSpecs, GlobalStringSpecs = globalStringSpecs, + GlobalSubDescriptorSpecs = globalSubDescriptorSpecs, NamesPool = namesPool }; } @@ -550,6 +574,26 @@ private static GlobalStringSpec[] ReadGlobalStringSpecs(ScraperState state, Head return globalSpecs; } + private static GlobalPointerSpec[] ReadGlobalSubDescriptorSpecs(ScraperState state, HeaderDirectory header) + { + GlobalPointerSpec[] globalSpecs = new GlobalPointerSpec[header.GlobalSubDescriptorsCount]; + state.ResetPosition(state.HeaderStart + (long)header.GlobalSubDescriptorsStart); + for (int i = 0; i < header.GlobalSubDescriptorsCount; i++) + { + int bytesRead = 0; + globalSpecs[i].NameIdx = state.ReadUInt32(); + bytesRead += sizeof(uint); + globalSpecs[i].AuxDataIdx = state.ReadUInt32(); + bytesRead += sizeof(uint); + // skip padding + if (bytesRead < header.GlobalPointerSpecSize) + { + state.Skip(header.GlobalPointerSpecSize - bytesRead); + } + } + return globalSpecs; + } + private static byte[] ReadNamesPool(ScraperState state, HeaderDirectory header) { byte[] namesPool = new byte[header.NamesPoolCount]; diff --git a/src/coreclr/vm/datadescriptor/CMakeLists.txt b/src/coreclr/vm/datadescriptor/CMakeLists.txt index 3114d94b23d3b7..c7eab6d807c10d 100644 --- a/src/coreclr/vm/datadescriptor/CMakeLists.txt +++ b/src/coreclr/vm/datadescriptor/CMakeLists.txt @@ -1,3 +1,5 @@ +set(CMAKE_INCLUDE_CURRENT_DIR OFF) + # cDAC contract descriptor if(CDAC_BUILD_TOOL_BINARY_PATH AND "${CLR_DOTNET_RID}" STREQUAL "") @@ -16,3 +18,29 @@ generate_data_descriptors( CONTRACT_NAME "DotNetRuntimeContractDescriptor" INTERFACE_TARGET runtime_descriptor_interface EXPORT_VISIBLE) + +set(GC_DESCRIPTOR_DIR "${CLR_DIR}/gc/datadescriptor") +add_library(gc_wks_descriptor_interface INTERFACE) +target_include_directories(gc_wks_descriptor_interface INTERFACE + ${GC_DESCRIPTOR_DIR} + ${CLR_DIR}/gc) +add_dependencies(gc_wks_descriptor_interface cee_wks_core) +generate_data_descriptors( + LIBRARY_NAME gc_wks_descriptor + CONTRACT_NAME "GCContractDescriptorWKS" + CONTRACT_FILE "${GC_DESCRIPTOR_DIR}/contracts.jsonc" + INTERFACE_TARGET gc_wks_descriptor_interface) + +if (FEATURE_SVR_GC) + add_library(gc_svr_descriptor_interface INTERFACE) + target_include_directories(gc_svr_descriptor_interface INTERFACE + ${GC_DESCRIPTOR_DIR} + ${CLR_DIR}/gc) + add_dependencies(gc_svr_descriptor_interface cee_wks_core) + target_compile_definitions(gc_svr_descriptor_interface INTERFACE -DSERVER_GC) + generate_data_descriptors( + LIBRARY_NAME gc_svr_descriptor + CONTRACT_NAME "GCContractDescriptorSVR" + CONTRACT_FILE "${GC_DESCRIPTOR_DIR}/contracts.jsonc" + INTERFACE_TARGET gc_svr_descriptor_interface) +endif() diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index ea2cb7672df558..31f2130e9e29b9 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -996,4 +996,6 @@ CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable) +CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor)) + CDAC_GLOBALS_END() diff --git a/src/coreclr/vm/wks/CMakeLists.txt b/src/coreclr/vm/wks/CMakeLists.txt index 280665e8b47d17..e607298d5b9e63 100644 --- a/src/coreclr/vm/wks/CMakeLists.txt +++ b/src/coreclr/vm/wks/CMakeLists.txt @@ -52,6 +52,10 @@ add_dependencies(cee_wks_mergeable precompiled_asm) target_compile_definitions(cee_wks_mergeable PUBLIC FEATURE_STATICALLY_LINKED) target_compile_definitions(cee_wks_mergeable PUBLIC CORECLR_EMBEDDED) +if (NOT CLR_CMAKE_HOST_ARCH_WASM) + target_compile_definitions(cee_wks_core PRIVATE -DGC_DESCRIPTOR) +endif (NOT CLR_CMAKE_HOST_ARCH_WASM) + if (CLR_CMAKE_HOST_WIN32) link_natvis_sources_for_target(cee_wks INTERFACE ../vm.natvis) link_natvis_sources_for_target(cee_wks_mergeable INTERFACE ../vm.natvis) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 61ca9fc81f842a..4dd941006db544 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -67,8 +67,11 @@ public abstract class ContractRegistry /// Gets an instance of the RuntimeInfo contract for the target. /// public abstract IRuntimeInfo RuntimeInfo { get; } - /// /// Gets an instance of the DebugInfo contract for the target. /// public abstract IDebugInfo DebugInfo { get; } + /// + /// Gets an instance of the GC contract for the target. + /// + public abstract IGC GC { get; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs new file mode 100644 index 00000000000000..0516eebe95c1a4 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public class GCIdentifiers +{ + public const string Server = "server"; + public const string Workstation = "workstation"; + + public const string Regions = "regions"; + public const string Segments = "segments"; +} + +public interface IGC : IContract +{ + static string IContract.Name { get; } = nameof(GC); + + string[] GetGCIdentifiers() => throw new NotImplementedException(); + uint GetGCHeapCount() => throw new NotImplementedException(); + bool GetGCStructuresValid() => throw new NotImplementedException(); + uint GetMaxGeneration() => throw new NotImplementedException(); + IEnumerable GetGCHeaps() => throw new NotImplementedException(); +} + +public readonly struct GC : IGC +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index e8a7e41e6a4afe..5d184703853b41 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -7,7 +7,7 @@ public static class Constants { public static class Globals { - // See src/coreclr/debug/runtimeinfo/datadescriptor.h + // See src/coreclr/debug/runtimeinfo/datadescriptor.inc public const string AppDomain = nameof(AppDomain); public const string SystemDomain = nameof(SystemDomain); public const string ThreadStore = nameof(ThreadStore); @@ -74,6 +74,14 @@ public static class Globals public const string OperatingSystem = nameof(OperatingSystem); public const string GCInfoVersion = nameof(GCInfoVersion); + + // Globals found on GCDescriptor + // see src/coreclr/gc/datadescriptors/datadescriptor.inc + public const string GCIdentifiers = nameof(GCIdentifiers); + public const string MaxGeneration = nameof(MaxGeneration); + public const string StructureInvalidCount = nameof(StructureInvalidCount); + public const string NumHeaps = nameof(NumHeaps); + public const string Heaps = nameof(Heaps); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCFactory.cs new file mode 100644 index 00000000000000..9eb2570226ed47 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCFactory.cs @@ -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 GCFactory : IContractFactory +{ + IGC IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new GC_1(target), + _ => default(GC), + }; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs new file mode 100644 index 00000000000000..3b669a412c3436 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC_1.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct GC_1 : IGC +{ + private const uint WRK_HEAP_COUNT = 1; + + private enum GCType + { + Unknown, + Workstation, + Server, + } + + private readonly Target _target; + + internal GC_1(Target target) + { + _target = target; + } + + string[] IGC.GetGCIdentifiers() + { + string gcIdentifiers = _target.ReadGlobalString(Constants.Globals.GCIdentifiers); + return gcIdentifiers.Split(", "); + } + + uint IGC.GetGCHeapCount() + { + switch (GetGCType()) + { + case GCType.Workstation: + return WRK_HEAP_COUNT; // Workstation GC has a single heap + case GCType.Server: + TargetPointer pNumHeaps = _target.ReadGlobalPointer(Constants.Globals.NumHeaps); + return (uint)_target.Read(pNumHeaps); + default: + throw new NotImplementedException("Unknown GC type"); + } + } + + bool IGC.GetGCStructuresValid() + { + TargetPointer pInvalidCount = _target.ReadGlobalPointer(Constants.Globals.StructureInvalidCount); + int invalidCount = _target.Read(pInvalidCount); + return invalidCount == 0; // Structures are valid if the count of invalid structures is zero + } + + uint IGC.GetMaxGeneration() + { + TargetPointer pMaxGeneration = _target.ReadGlobalPointer(Constants.Globals.MaxGeneration); + return _target.Read(pMaxGeneration); + } + + IEnumerable IGC.GetGCHeaps() + { + if (GetGCType() != GCType.Server) + { + yield break; // Only server GC has multiple heaps + } + + uint heapCount = ((IGC)this).GetGCHeapCount(); + TargetPointer ppHeapTable = _target.ReadGlobalPointer(Constants.Globals.Heaps); + TargetPointer pHeapTable = _target.ReadPointer(ppHeapTable); + for (uint i = 0; i < heapCount; i++) + { + yield return _target.ReadPointer(pHeapTable + (i * (uint)_target.PointerSize)); + } + } + + private GCType GetGCType() + { + string[] identifiers = ((IGC)this).GetGCIdentifiers(); + if (identifiers.Contains(GCIdentifiers.Workstation)) + { + return GCType.Workstation; + } + else if (identifiers.Contains(GCIdentifiers.Server)) + { + return GCType.Server; + } + else + { + return GCType.Unknown; // Unknown or unsupported GC type + } + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index d979a8f7705727..715aa19b202b72 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -41,6 +41,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IStackWalk)] = new StackWalkFactory(), [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), [typeof(IDebugInfo)] = new DebugInfoFactory(), + [typeof(IGC)] = new GCFactory(), }; configureFactories?.Invoke(_factories); } @@ -60,6 +61,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IStackWalk StackWalk => GetContract(); public override IRuntimeInfo RuntimeInfo => GetContract(); public override IDebugInfo DebugInfo => GetContract(); + public override IGC GC => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs index 1f15838912670f..57051512656cc7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs @@ -68,14 +68,14 @@ public class ContractDescriptor public Dictionary? Globals { get; set; } - public Dictionary? GlobalStrings { get; set; } + public Dictionary? SubDescriptors { get; set; } [JsonExtensionData] public Dictionary? Extras { get; set; } public override string ToString() { - return $"Version: {Version}, Baseline: {Baseline}, Contracts: {Contracts?.Count}, Types: {Types?.Count}, Globals: {Globals?.Count}, GlobalStrings: {GlobalStrings?.Count}"; + return $"Version: {Version}, Baseline: {Baseline}, Contracts: {Contracts?.Count}, Types: {Types?.Count}, Globals: {Globals?.Count}, SubDescriptors: {SubDescriptors?.Count}"; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index ece156f6dfbbca..0cf147e142f872 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -62,14 +62,12 @@ public static bool TryCreate( [NotNullWhen(true)] out ContractDescriptorTarget? target) { DataTargetDelegates dataTargetDelegates = new DataTargetDelegates(readFromTarget, writeToTarget, getThreadContext); - if (TryReadContractDescriptor( + if (TryReadAllContractDescriptors( contractDescriptor, dataTargetDelegates, - out Configuration config, - out ContractDescriptorParser.ContractDescriptor? descriptor, - out TargetPointer[] pointerData)) + out Descriptor[] descriptors)) { - target = new ContractDescriptorTarget(config, descriptor!, pointerData, dataTargetDelegates); + target = new ContractDescriptorTarget(descriptors, dataTargetDelegates); return true; } @@ -97,82 +95,120 @@ public static ContractDescriptorTarget Create( int pointerSize) { return new ContractDescriptorTarget( - new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }, - contractDescriptor, - globalPointerValues, + [ + new Descriptor + { + Config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }, + ContractDescriptor = contractDescriptor, + PointerData = globalPointerValues + } + ], new DataTargetDelegates(readFromTarget, writeToTarget, getThreadContext)); } - private ContractDescriptorTarget(Configuration config, ContractDescriptorParser.ContractDescriptor descriptor, TargetPointer[] pointerData, DataTargetDelegates dataTargetDelegates) + private ContractDescriptorTarget(Descriptor[] descriptors, DataTargetDelegates dataTargetDelegates) { Contracts = new CachingContractRegistry(this, this.TryGetContractVersion); ProcessedData = new DataCache(this); - _config = config; + _config = descriptors[0].Config; _dataTargetDelegates = dataTargetDelegates; - _contracts = descriptor.Contracts ?? []; + _contracts = []; // Set pointer type size _knownTypes[DataType.pointer] = new TypeInfo { Size = (uint)_config.PointerSize }; - // Read types and map to known data types - if (descriptor.Types is not null) + HashSet seenTypeNames = new HashSet(); + HashSet seenGlobalNames = new HashSet(); + + Dictionary globalValues = []; + + + foreach (Descriptor descriptor in descriptors) { - foreach ((string name, ContractDescriptorParser.TypeDescriptor type) in descriptor.Types) + if (descriptor.Config.IsLittleEndian != _config.IsLittleEndian || + descriptor.Config.PointerSize != _config.PointerSize) + throw new InvalidOperationException("All descriptors must have the same endianness and pointer size."); + + // Read contracts and add to map + foreach ((string name, int version) in descriptor.ContractDescriptor.Contracts ?? []) { - Dictionary fieldInfos = []; - if (type.Fields is not null) + if (_contracts.ContainsKey(name)) { - foreach ((string fieldName, ContractDescriptorParser.FieldDescriptor field) in type.Fields) + throw new InvalidOperationException($"Duplicate contract name '{name}' found in contract descriptor."); + } + _contracts[name] = version; + } + + // Read types and map to known data types + if (descriptor.ContractDescriptor.Types is not null) + { + foreach ((string name, ContractDescriptorParser.TypeDescriptor type) in descriptor.ContractDescriptor.Types) + { + Dictionary fieldInfos = []; + if (type.Fields is not null) { - fieldInfos[fieldName] = new Target.FieldInfo() + foreach ((string fieldName, ContractDescriptorParser.FieldDescriptor field) in type.Fields) { - Offset = field.Offset, - Type = field.Type is null ? DataType.Unknown : GetDataType(field.Type), - TypeName = field.Type - }; + fieldInfos[fieldName] = new Target.FieldInfo() + { + Offset = field.Offset, + Type = field.Type is null ? DataType.Unknown : GetDataType(field.Type), + TypeName = field.Type + }; + } } - } - Target.TypeInfo typeInfo = new() { Size = type.Size, Fields = fieldInfos }; + Target.TypeInfo typeInfo = new() { Size = type.Size, Fields = fieldInfos }; - DataType dataType = GetDataType(name); - if (dataType is not DataType.Unknown) - { - _knownTypes[dataType] = typeInfo; - } - else - { - _types[name] = typeInfo; + if (seenTypeNames.Contains(name)) + { + throw new InvalidOperationException($"Duplicate type name '{name}' found in contract descriptor."); + } + seenTypeNames.Add(name); + + DataType dataType = GetDataType(name); + if (dataType is not DataType.Unknown) + { + _knownTypes[dataType] = typeInfo; + } + else + { + _types[name] = typeInfo; + } } } - } - // Read globals and map indirect values to pointer data - if (descriptor.Globals is not null) - { - Dictionary globalValues = new(descriptor.Globals.Count); - foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.Globals) + // Read globals and map indirect values to pointer data + if (descriptor.ContractDescriptor.Globals is not null) { - if (global.Indirect) + foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.ContractDescriptor.Globals) { - if (global.NumericValue.Value >= (ulong)pointerData.Length) - throw new VirtualReadException($"Invalid pointer data index {global.NumericValue.Value}."); + if (seenGlobalNames.Contains(name)) + throw new InvalidOperationException($"Duplicate global name '{name}' found in contract descriptor."); - globalValues[name] = new GlobalValue + seenGlobalNames.Add(name); + + if (global.Indirect) { - NumericValue = pointerData[global.NumericValue.Value].Value, - StringValue = global.StringValue, - Type = global.Type - }; - } - else // direct - { - globalValues[name] = new GlobalValue + if (global.NumericValue.Value >= (ulong)descriptor.PointerData.Length) + throw new InvalidOperationException($"Invalid pointer data index {global.NumericValue.Value}."); + + globalValues[name] = new GlobalValue + { + NumericValue = descriptor.PointerData[global.NumericValue.Value].Value, + StringValue = global.StringValue, + Type = global.Type + }; + } + else // direct { - NumericValue = global.NumericValue, - StringValue = global.StringValue, - Type = global.Type - }; + globalValues[name] = new GlobalValue + { + NumericValue = global.NumericValue, + StringValue = global.StringValue, + Type = global.Type + }; + } } } @@ -187,17 +223,70 @@ private struct GlobalValue public string? Type; } + private struct Descriptor + { + public Configuration Config { get; init; } + public ContractDescriptorParser.ContractDescriptor ContractDescriptor { get; init; } + public TargetPointer[] PointerData { get; init; } + } + + private static IEnumerable GetSubDescriptors(Descriptor descriptor) + { + foreach (KeyValuePair subDescriptor in descriptor.ContractDescriptor?.SubDescriptors ?? []) + { + if (subDescriptor.Value.Indirect) + { + if (subDescriptor.Value.NumericValue.Value >= (ulong)descriptor.PointerData.Length) + throw new InvalidOperationException($"Invalid pointer data index {subDescriptor.Value.NumericValue.Value}."); + + yield return descriptor.PointerData[(int)subDescriptor.Value.NumericValue]; + } + } + } + + private static bool TryReadAllContractDescriptors( + ulong address, + DataTargetDelegates dataTargetDelegates, + out Descriptor[] descriptors) + { + if (!TryReadContractDescriptor(address, dataTargetDelegates, out Descriptor mainDescriptor)) + { + descriptors = []; + return false; + } + + List allDescriptors = [mainDescriptor]; + + foreach (TargetPointer pSubDescriptor in GetSubDescriptors(mainDescriptor)) + { + if (pSubDescriptor == TargetPointer.Null) + continue; + + if (!TryReadPointer(pSubDescriptor.Value, mainDescriptor.Config, dataTargetDelegates, out TargetPointer subDescriptorAddress)) + continue; + + if (subDescriptorAddress == TargetPointer.Null) + continue; + + TryReadAllContractDescriptors( + subDescriptorAddress.Value, + dataTargetDelegates, + out Descriptor[] subDescriptors); + + allDescriptors.AddRange(subDescriptors); + } + + descriptors = [.. allDescriptors]; + return true; + } + // See docs/design/datacontracts/contract-descriptor.md private static bool TryReadContractDescriptor( ulong address, DataTargetDelegates dataTargetDelegates, - out Configuration config, - out ContractDescriptorParser.ContractDescriptor? descriptor, - out TargetPointer[] pointerData) + out Descriptor descriptor) { - config = default; - descriptor = null; - pointerData = []; + descriptor = default; // Magic - uint64_t Span buffer = stackalloc byte[sizeof(ulong)]; @@ -220,7 +309,7 @@ private static bool TryReadContractDescriptor( // Bit 1 represents the pointer size. 0 = 64-bit, 1 = 32-bit. int pointerSize = (int)(flags & 0x2) == 0 ? sizeof(ulong) : sizeof(uint); - config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }; + Configuration config = new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }; // Descriptor size - uint32_t if (!TryRead(address, config.IsLittleEndian, dataTargetDelegates, out uint descriptorSize)) @@ -254,18 +343,25 @@ private static bool TryReadContractDescriptor( if (dataTargetDelegates.ReadFromTarget(descriptorAddr.Value, descriptorBuffer) < 0) return false; - descriptor = ContractDescriptorParser.ParseCompact(descriptorBuffer); - if (descriptor is null) + ContractDescriptorParser.ContractDescriptor? contractDescriptor = ContractDescriptorParser.ParseCompact(descriptorBuffer); + if (contractDescriptor is null) return false; // Read pointer data - pointerData = new TargetPointer[pointerDataCount]; + TargetPointer[] pointerData = new TargetPointer[pointerDataCount]; for (int i = 0; i < pointerDataCount; i++) { if (!TryReadPointer(pointerDataAddr.Value + (uint)(i * pointerSize), config, dataTargetDelegates, out pointerData[i])) return false; } + descriptor = new Descriptor + { + Config = config, + ContractDescriptor = contractDescriptor, + PointerData = pointerData + }; + return true; } diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs index e68d2fe1e482d8..7421d473847421 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ISOSDacInterface.cs @@ -272,6 +272,14 @@ internal struct DacpMethodTableTransparencyData public int bIsTreatAsSafe; } +internal struct DacpGcHeapData +{ + public int bServerMode; + public int bGcStructuresValid; + public uint HeapCount; + public uint g_max_generation; +} + [GeneratedComInterface] [Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] internal unsafe partial interface ISOSDacInterface @@ -395,7 +403,7 @@ internal unsafe partial interface ISOSDacInterface // GC [PreserveSig] - int GetGCHeapData(/*struct DacpGcHeapData*/ void* data); + int GetGCHeapData(DacpGcHeapData* data); [PreserveSig] int GetGCHeapList(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] ClrDataAddress[] heaps, uint* pNeeded); // svr only [PreserveSig] diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs index e170dfc2666b12..7a5362231cd48c 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.cs @@ -724,12 +724,119 @@ int ISOSDacInterface.GetFrameName(ClrDataAddress vtable, uint count, char* frame return hr; } - int ISOSDacInterface.GetGCHeapData(void* data) - => _legacyImpl is not null ? _legacyImpl.GetGCHeapData(data) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetGCHeapData(DacpGcHeapData* data) + { + int hr = HResults.S_OK; + + if (data == null) + { + return HResults.E_INVALIDARG; + } + + try + { + IGC gc = _target.Contracts.GC; + string[] heapType = gc.GetGCIdentifiers(); + if (!heapType.Contains(GCIdentifiers.Workstation) && !heapType.Contains(GCIdentifiers.Server)) + { + // If the GC type is not recognized, we cannot provide heap data + hr = HResults.E_FAIL; + } + else + { + data->g_max_generation = gc.GetMaxGeneration(); + data->bServerMode = heapType.Contains(GCIdentifiers.Server) ? 1 : 0; + data->bGcStructuresValid = gc.GetGCStructuresValid() ? 1 : 0; + data->HeapCount = gc.GetGCHeapCount(); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + DacpGcHeapData dataLocal = default; + int hrLocal = _legacyImpl.GetGCHeapData(&dataLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(data->bServerMode == dataLocal.bServerMode, $"cDAC: {data->bServerMode}, DAC: {dataLocal.bServerMode}"); + Debug.Assert(data->bGcStructuresValid == dataLocal.bGcStructuresValid, $"cDAC: {data->bGcStructuresValid}, DAC: {dataLocal.bGcStructuresValid}"); + Debug.Assert(data->HeapCount == dataLocal.HeapCount, $"cDAC: {data->HeapCount}, DAC: {dataLocal.HeapCount}"); + Debug.Assert(data->g_max_generation == dataLocal.g_max_generation, $"cDAC: {data->g_max_generation}, DAC: {dataLocal.g_max_generation}"); + } + } +#endif + + return hr; + } + int ISOSDacInterface.GetGCHeapList(uint count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[] heaps, uint* pNeeded) + { + int hr = HResults.S_OK; + + try + { + IGC gc = _target.Contracts.GC; + string[] heapType = gc.GetGCIdentifiers(); + if (!heapType.Contains(GCIdentifiers.Server)) + { + // If GC type is not server, this API is not supported + hr = HResults.E_FAIL; + } + else + { + uint heapCount = gc.GetGCHeapCount(); + if (pNeeded is not null) + { + *pNeeded = heapCount; + } + + if (heaps.Length == heapCount) + { + List gcHeaps = gc.GetGCHeaps().ToList(); + Debug.Assert(gcHeaps.Count == heapCount, "Expected the number of GC heaps to match the count returned by GetGCHeapCount"); + for (uint i = 0; i < heapCount; i++) + { + heaps[i] = gcHeaps[(int)i].ToClrDataAddress(_target); + } + } + else if (heaps.Length != 0) + { + hr = HResults.E_INVALIDARG; + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + ClrDataAddress[] heapsLocal = new ClrDataAddress[count]; + uint neededLocal; + int hrLocal = _legacyImpl.GetGCHeapList(count, heapsLocal, &neededLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(pNeeded == null || *pNeeded == neededLocal); + // in theory, these don't need to be in the same order, but for consistency it is + // easiest for consumers and verification if the DAC and cDAC return the same order + for (int i = 0; i < neededLocal; i++) + { + Debug.Assert(heaps[i] == heapsLocal[i], $"cDAC: {heaps[i]:x}, DAC: {heapsLocal[i]:x}"); + } + } + } +#endif + return hr; + } int ISOSDacInterface.GetGCHeapDetails(ClrDataAddress heap, void* details) => _legacyImpl is not null ? _legacyImpl.GetGCHeapDetails(heap, details) : HResults.E_NOTIMPL; - int ISOSDacInterface.GetGCHeapList(uint count, [In, MarshalUsing(CountElementName = "count"), Out] ClrDataAddress[] heaps, uint* pNeeded) - => _legacyImpl is not null ? _legacyImpl.GetGCHeapList(count, heaps, pNeeded) : HResults.E_NOTIMPL; int ISOSDacInterface.GetGCHeapStaticData(void* data) => _legacyImpl is not null ? _legacyImpl.GetGCHeapStaticData(data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetHandleEnum(void** ppHandleEnum) diff --git a/src/native/managed/cdac/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdac/tests/ContractDescriptor/ContractDescriptorBuilder.cs index cd15f10f0bebf6..19e0db7c404314 100644 --- a/src/native/managed/cdac/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdac/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -14,170 +14,196 @@ internal class ContractDescriptorBuilder : MockMemorySpace.Builder { // These addresses are arbitrary and are used to store the contract descriptor components. // They should not overlap with any other heap fragment addresses. - private const ulong ContractDescriptorAddr = 0xaaaaaaaa; + private const uint ContractDescriptorAddr = 0xaaaaaaaa; private const uint JsonDescriptorAddr = 0xdddddddd; private const uint ContractPointerDataAddr = 0xeeeeeeee; - private bool _created = false; - - private IReadOnlyCollection _contracts; - private IDictionary _types; - private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> _globals; - private IReadOnlyCollection _indirectValues; + bool _created = false; public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers) : base(targetTestHelpers) { } - public ContractDescriptorBuilder SetContracts(IReadOnlyCollection contracts) + public class DescriptorBuilder(ContractDescriptorBuilder parent) { - if (_created) - throw new InvalidOperationException("Context already created"); - _contracts = contracts; - return this; - } + private bool _created = false; + private readonly ContractDescriptorBuilder _parent = parent; - public ContractDescriptorBuilder SetTypes(IDictionary types) - { - if (_created) - throw new InvalidOperationException("Context already created"); - _types = types; - return this; - } + private IReadOnlyCollection _contracts; + private IDictionary _types; + private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> _globals; + private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> _subDescriptors; + private IReadOnlyCollection _indirectValues; - public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals) - { - if (_created) - throw new InvalidOperationException("Context already created"); - if (_globals != null) - throw new InvalidOperationException("Globals already set"); - _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, (string?)null, g.TypeName)).ToArray(); - _indirectValues = null; - return this; - } + public DescriptorBuilder SetContracts(IReadOnlyCollection contracts) + { + _contracts = contracts; + return this; + } - public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, string? StringValue, string? TypeName)> globals) - { - if (_created) - throw new InvalidOperationException("Context already created"); - if (_globals != null) - throw new InvalidOperationException("Globals already set"); - _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.StringValue, g.TypeName)).ToArray(); - _indirectValues = null; - return this; - } + public DescriptorBuilder SetTypes(IDictionary types) + { + _types = types; + return this; + } - public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> globals, IReadOnlyCollection indirectValues) - { - if (_created) - throw new InvalidOperationException("Context already created"); - if (_globals != null) - throw new InvalidOperationException("Globals already set"); - _globals = globals; - _indirectValues = indirectValues; - return this; - } + public DescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong Value, string? TypeName)> globals) + { + if (_globals != null) + throw new InvalidOperationException("Globals already set"); + _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, (string?)null, g.TypeName)).ToArray(); + return this; + } - private MockMemorySpace.HeapFragment CreateContractDescriptor(int jsonLength, int pointerDataCount) - { - byte[] descriptor = new byte[ContractDescriptorHelpers.Size(TargetTestHelpers.Arch.Is64Bit)]; - ContractDescriptorHelpers.Fill(descriptor, TargetTestHelpers.Arch, jsonLength, JsonDescriptorAddr, pointerDataCount, ContractPointerDataAddr); - return new MockMemorySpace.HeapFragment + public DescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, string? StringValue, string? TypeName)> globals) { - Address = ContractDescriptorAddr, - Data = descriptor, - Name = "ContractDescriptor" - }; - } + if (_globals != null) + throw new InvalidOperationException("Globals already set"); + _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.StringValue, g.TypeName)).ToArray(); + return this; + } - private string MakeContractsJson() - { - if (_contracts.Count == 0) - return string.Empty; - StringBuilder sb = new(); - foreach (var c in _contracts) + public DescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> globals) { - sb.Append($"\"{c}\": 1,"); + if (_globals != null) + throw new InvalidOperationException("Globals already set"); + _globals = globals; + return this; } - Debug.Assert(sb.Length > 0); - sb.Length--; // remove trailing comma - return sb.ToString(); - } - private (MockMemorySpace.HeapFragment json, MockMemorySpace.HeapFragment pointerData) CreateDataDescriptor() - { - string metadataTypesJson = _types is not null ? ContractDescriptorHelpers.MakeTypesJson(_types) : string.Empty; - string metadataGlobalsJson = _globals is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_globals) : string.Empty; - string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty; - byte[] jsonBytes = Encoding.UTF8.GetBytes($$""" + public DescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> globals, IReadOnlyCollection indirectValues) { - "version": 0, - "baseline": "empty", - "contracts": { {{interpolatedContracts}} }, - "types": { {{metadataTypesJson}} }, - "globals": { {{metadataGlobalsJson}} } + SetGlobals(globals); + SetIndirectValues(indirectValues); + return this; } - """); - MockMemorySpace.HeapFragment json = new() + + public DescriptorBuilder SetSubDescriptors(IReadOnlyCollection<(string Name, uint IndirectIndex)> subDescriptors) { - Address = JsonDescriptorAddr, - Data = jsonBytes, - Name = "JsonDescriptor" - }; + if (_subDescriptors != null) + throw new InvalidOperationException("Sub descriptors already set"); + _subDescriptors = subDescriptors.Select<(string Name, uint IndirectIndex), (string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)>(s => (s.Name, null, s.IndirectIndex, null, null)).ToList(); + return this; + } - MockMemorySpace.HeapFragment pointerData; - if (_indirectValues != null) + public DescriptorBuilder SetIndirectValues(IReadOnlyCollection indirectValues) { - int pointerSize = TargetTestHelpers.PointerSize; - byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize]; - int offset = 0; - foreach (var value in _indirectValues) - { - TargetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value); - offset += pointerSize; - } - pointerData = new MockMemorySpace.HeapFragment - { - Address = ContractPointerDataAddr, - Data = pointerDataBytes, - Name = "PointerData" - }; + if (_indirectValues != null) + throw new InvalidOperationException("Indirect values already set"); + _indirectValues = indirectValues; + return this; + } + + + public ulong CreateSubDescriptor(uint contractDescriptorAddress, uint jsonAddress, uint pointerDataAddress) + { + if (_created) + throw new InvalidOperationException("Context already created"); + + (var json, var pointerData) = CreateDataDescriptor(jsonAddress, pointerDataAddress); + int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / _parent.TargetTestHelpers.PointerSize; + MockMemorySpace.HeapFragment descriptor = CreateContractDescriptor( + contractDescriptorAddress, + jsonAddress, + pointerDataAddress, + json.Data.Length, + pointerDataCount); + + _parent.AddHeapFragment(descriptor); + _parent.AddHeapFragment(json); + if (pointerData.Data.Length > 0) + _parent.AddHeapFragment(pointerData); + + _created = true; + return descriptor.Address; } - else + + private MockMemorySpace.HeapFragment CreateContractDescriptor(uint contractDescriptorAddress, uint jsonAddress, uint pointerDataAddress, int jsonLength, int pointerDataCount) { - pointerData = new MockMemorySpace.HeapFragment + byte[] descriptor = new byte[ContractDescriptorHelpers.Size(_parent.TargetTestHelpers.Arch.Is64Bit)]; + ContractDescriptorHelpers.Fill(descriptor, _parent.TargetTestHelpers.Arch, jsonLength, jsonAddress, pointerDataCount, pointerDataAddress); + return new MockMemorySpace.HeapFragment { - Address = ContractPointerDataAddr, - Data = Array.Empty(), - Name = "PointerData" + Address = contractDescriptorAddress, + Data = descriptor, + Name = "ContractDescriptor" }; } - return (json, pointerData); - } - - private ulong CreateDescriptorFragments() - { - if (_created) - throw new InvalidOperationException("Context already created"); - (var json, var pointerData) = CreateDataDescriptor(); - int pointerDataCount = pointerData.Data is null ? 0 : pointerData.Data.Length / TargetTestHelpers.PointerSize; - MockMemorySpace.HeapFragment descriptor = CreateContractDescriptor(json.Data.Length, pointerDataCount); + private string MakeContractsJson() + { + if (_contracts.Count == 0) + return string.Empty; + StringBuilder sb = new(); + foreach (var c in _contracts) + { + sb.Append($"\"{c}\": 1,"); + } + Debug.Assert(sb.Length > 0); + sb.Length--; // remove trailing comma + return sb.ToString(); + } - AddHeapFragment(descriptor); - AddHeapFragment(json); - if (pointerData.Data.Length > 0) - AddHeapFragment(pointerData); + protected (MockMemorySpace.HeapFragment json, MockMemorySpace.HeapFragment pointerData) CreateDataDescriptor(ulong jsonAddress, ulong pointerDataAddress) + { + string metadataTypesJson = _types is not null ? ContractDescriptorHelpers.MakeTypesJson(_types) : string.Empty; + string metadataGlobalsJson = _globals is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_globals) : string.Empty; + string metadataSubDescriptorJson = _subDescriptors is not null ? ContractDescriptorHelpers.MakeGlobalsJson(_subDescriptors) : string.Empty; + string interpolatedContracts = _contracts is not null ? MakeContractsJson() : string.Empty; + byte[] jsonBytes = Encoding.UTF8.GetBytes($$""" + { + "version": 0, + "baseline": "empty", + "contracts": { {{interpolatedContracts}} }, + "types": { {{metadataTypesJson}} }, + "globals": { {{metadataGlobalsJson}} }, + "subDescriptors": { {{metadataSubDescriptorJson}} }, + } + """); + MockMemorySpace.HeapFragment json = new() + { + Address = jsonAddress, + Data = jsonBytes, + Name = "JsonDescriptor" + }; - _created = true; - return descriptor.Address; + MockMemorySpace.HeapFragment pointerData; + if (_indirectValues != null) + { + int pointerSize = _parent.TargetTestHelpers.PointerSize; + byte[] pointerDataBytes = new byte[_indirectValues.Count * pointerSize]; + int offset = 0; + foreach (var value in _indirectValues) + { + _parent.TargetTestHelpers.WritePointer(pointerDataBytes.AsSpan(offset, pointerSize), value); + offset += pointerSize; + } + pointerData = new MockMemorySpace.HeapFragment + { + Address = pointerDataAddress, + Data = pointerDataBytes, + Name = "PointerData" + }; + } + else + { + pointerData = new MockMemorySpace.HeapFragment + { + Address = pointerDataAddress, + Data = Array.Empty(), + Name = "PointerData" + }; + } + return (json, pointerData); + } } - public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? target) + public bool TryCreateTarget(DescriptorBuilder descriptor, [NotNullWhen(true)] out ContractDescriptorTarget? target) { if (_created) throw new InvalidOperationException("Context already created"); - ulong contractDescriptorAddress = CreateDescriptorFragments(); + _created = true; + ulong contractDescriptorAddress = descriptor.CreateSubDescriptor(ContractDescriptorAddr, JsonDescriptorAddr, ContractPointerDataAddr); MockMemorySpace.MemoryContext memoryContext = GetMemoryContext(); return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, memoryContext.ReadFromTarget, memoryContext.WriteToTarget, null, out target); } diff --git a/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.SubDescriptors.cs b/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.SubDescriptors.cs new file mode 100644 index 00000000000000..80c8bd64cd7ec6 --- /dev/null +++ b/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.SubDescriptors.cs @@ -0,0 +1,235 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor; + +public unsafe partial class TargetTests +{ + const uint SubDescriptorAddr = 0x12345678; + const uint SubDescriptorJsonAddr = 0x12445678; + const uint SubDescriptorPointerDataAddr = 0x12545678; + + private static readonly Dictionary SubDescriptorTypes = new() + { + // Size and fields + [DataType.AppDomain] = new() + { + Size = 56, + Fields = new Dictionary { + { "Field1", new(){ Offset = 8, Type = DataType.uint16, TypeName = DataType.uint16.ToString() }}, + { "Field2", new(){ Offset = 16, Type = DataType.GCHandle, TypeName = DataType.GCHandle.ToString() }}, + { "Field3", new(){ Offset = 32 }} + } + }, + // Fields only + [DataType.SystemDomain] = new() + { + Fields = new Dictionary { + { "Field1", new(){ Offset = 0, TypeName = "FieldType" }}, + { "Field2", new(){ Offset = 8 }} + } + }, + // Size only + [DataType.ArrayClass] = new() + { + Size = 8 + } + }; + + private static readonly (string Name, ulong Value, string? Type)[] SubDescriptorGlobals = + [ + ("subValue", (ulong)sbyte.MaxValue, null), + ("subInt8Value", 0x13, "int8"), + ("subUInt8Value", 0x13, "uint8"), + ("subInt16Value", 0x1235, "int16"), + ("subUInt16Value", 0x1235, "uint16"), + ("subInt32Value", 0x12345679, "int32"), + ("subUInt32Value", 0x12345679, "uint32"), + ("subInt64Value", 0x123456789abcdef1, "int64"), + ("subUInt64Value", 0x123456789abcdef1, "uint64"), + ("subNintValue", 0xabcdef1, "nint"), + ("subNuintValue", 0xabcdef1, "nuint"), + ("subPointerValue", 0xabcdef1, "pointer"), + ]; + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SubDescriptor_TypesAndGlobals(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + ContractDescriptorBuilder builder = new(targetTestHelpers); + + ContractDescriptorBuilder.DescriptorBuilder subDescriptor = new(builder); + + subDescriptor.SetTypes(SubDescriptorTypes) + .SetGlobals(SubDescriptorGlobals) + .SetContracts([]); + subDescriptor.CreateSubDescriptor(SubDescriptorAddr, SubDescriptorJsonAddr, SubDescriptorPointerDataAddr); + + uint subDescriptorPointerAddr = 0x12465312; + byte[] pointerDataBytes = new byte[targetTestHelpers.PointerSize]; + targetTestHelpers.WritePointer(pointerDataBytes, SubDescriptorAddr); + MockMemorySpace.HeapFragment pointerData = new() + { + Address = subDescriptorPointerAddr, + Data = pointerDataBytes, + Name = "SubDescriptorPointerData" + }; + builder.AddHeapFragment(pointerData); + + ContractDescriptorBuilder.DescriptorBuilder primaryDescriptor = new(builder); + primaryDescriptor.SetTypes(TestTypes) + .SetGlobals([.. TestGlobals.Select(GlobalToIndirectFormat)]) + .SetSubDescriptors([("GC", 1u)]) + .SetIndirectValues([0, subDescriptorPointerAddr]) + .SetContracts([]); + + bool success = builder.TryCreateTarget(primaryDescriptor, out ContractDescriptorTarget? target); + Assert.True(success); + + ValidateTypes(target, TestTypes); + ValidateTypes(target, SubDescriptorTypes); + + ValidateGlobals(target, TestGlobals); + ValidateGlobals(target, SubDescriptorGlobals); + + static (string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type) GlobalToIndirectFormat((string Name, ulong Value, string? Type) global) + { + return (global.Name, global.Value, null, null, global.Type); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SubDescriptor_Multiple_Nested(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + ContractDescriptorBuilder builder = new(targetTestHelpers); + + uint subDescriptorAddr = 0x4004_0000; + uint subDescriptorJsonAddr = 0x4104_0000; + uint subDescriptorPointerDataAddr = 0x4204_0000; + uint subDescriptorPointerAddr = 0x4304_0000; + + const int START_DEPTH = 4; + + Dictionary expectedGlobals = []; + + for (int depth = START_DEPTH; depth >= 0; depth--) + { + ContractDescriptorBuilder.DescriptorBuilder subDescriptor = new(builder); + + if (depth != START_DEPTH) + { + subDescriptor + .SetSubDescriptors([($"SubDescriptorDepth{depth + 1}", 1u)]) + .SetIndirectValues([0, subDescriptorPointerAddr]); + + subDescriptorAddr += 0x1000; + subDescriptorJsonAddr += 0x1000; + subDescriptorPointerDataAddr += 0x1000; + subDescriptorPointerAddr += 0x1000; + } + + string globalName = $"SubDescriptorDepth{depth}"; + expectedGlobals.Add(globalName, globalName); + subDescriptor + .SetGlobals([(globalName, null, globalName, null)]) + .CreateSubDescriptor(subDescriptorAddr, subDescriptorJsonAddr, subDescriptorPointerDataAddr); + + byte[] pointerDataBytes = new byte[targetTestHelpers.PointerSize]; + targetTestHelpers.WritePointer(pointerDataBytes, subDescriptorAddr); + MockMemorySpace.HeapFragment pointerData = new() + { + Address = subDescriptorPointerAddr, + Data = pointerDataBytes, + Name = $"SubDescriptorPointerData_Depth{depth}" + }; + builder.AddHeapFragment(pointerData); + } + + + ContractDescriptorBuilder.DescriptorBuilder primaryDescriptor = new(builder); + primaryDescriptor.SetTypes(TestTypes) + .SetSubDescriptors([("SubDescriptorDepth0", 1u)]) + .SetIndirectValues([0, subDescriptorPointerAddr]); + + bool success = builder.TryCreateTarget(primaryDescriptor, out ContractDescriptorTarget? target); + Assert.True(success); + + foreach ((string globalName, string expectedValue) in expectedGlobals) + { + Assert.True(target.TryReadGlobalString(globalName, out string? globalStringValue)); + Assert.Equal(expectedValue, globalStringValue); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SubDescriptor_Multiple_Breadth(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + ContractDescriptorBuilder builder = new(targetTestHelpers); + + uint subDescriptorAddr = 0x4004_0000; + uint subDescriptorJsonAddr = 0x4104_0000; + uint subDescriptorPointerDataAddr = 0x4204_0000; + uint subDescriptorPointerAddr = 0x4304_0000; + + Dictionary expectedGlobals = []; + + List<(string Name, uint IndirectIndex)> subDescriptors = []; + List indirectValues = [0]; + + for (int i = 1; i < 5; i++) + { + ContractDescriptorBuilder.DescriptorBuilder subDescriptor = new(builder); + + string globalName = $"SubDescriptor_Global_{i}"; + expectedGlobals.Add(globalName, globalName); + subDescriptor + .SetGlobals([(globalName, null, globalName, null)]) + .CreateSubDescriptor(subDescriptorAddr, subDescriptorJsonAddr, subDescriptorPointerDataAddr); + + byte[] pointerDataBytes = new byte[targetTestHelpers.PointerSize]; + targetTestHelpers.WritePointer(pointerDataBytes, subDescriptorAddr); + MockMemorySpace.HeapFragment pointerData = new() + { + Address = subDescriptorPointerAddr, + Data = pointerDataBytes, + Name = $"SubDescriptorPointerData_{i}" + }; + builder.AddHeapFragment(pointerData); + + subDescriptors.Add(($"SubDescriptor{i}", (uint)indirectValues.Count)); + indirectValues.Add(subDescriptorPointerAddr); + + subDescriptorAddr += 0x1000; + subDescriptorJsonAddr += 0x1000; + subDescriptorPointerDataAddr += 0x1000; + subDescriptorPointerAddr += 0x1000; + } + + + ContractDescriptorBuilder.DescriptorBuilder primaryDescriptor = new(builder); + primaryDescriptor.SetTypes(TestTypes) + .SetSubDescriptors(subDescriptors) + .SetIndirectValues(indirectValues); + + bool success = builder.TryCreateTarget(primaryDescriptor, out ContractDescriptorTarget? target); + Assert.True(success); + + foreach ((string globalName, string expectedValue) in expectedGlobals) + { + Assert.True(target.TryReadGlobalString(globalName, out string? globalStringValue)); + Assert.Equal(expectedValue, globalStringValue); + } + } +} diff --git a/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.cs b/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.cs index 516c0d3237df49..0512e19b9a36ef 100644 --- a/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.cs +++ b/src/native/managed/cdac/tests/ContractDescriptor/TargetTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests.ContractDescriptor; -public unsafe class TargetTests +public unsafe partial class TargetTests { private static readonly Dictionary TestTypes = new() { @@ -45,28 +45,15 @@ public void GetTypeInfo(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); - builder.SetTypes(TestTypes) + ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); + descriptorBuilder.SetTypes(TestTypes) .SetGlobals(Array.Empty<(string, ulong, string?)>()) .SetContracts(Array.Empty()); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target); Assert.True(success); - foreach ((DataType type, Target.TypeInfo info) in TestTypes) - { - { - // By known type - Target.TypeInfo actual = target.GetTypeInfo(type); - Assert.Equal(info.Size, actual.Size); - Assert.Equal(info.Fields, actual.Fields); - } - { - // By name - Target.TypeInfo actual = target.GetTypeInfo(type.ToString()); - Assert.Equal(info.Size, actual.Size); - Assert.Equal(info.Fields, actual.Fields); - } - } + ValidateTypes(target, TestTypes); } private static readonly (string Name, ulong Value, string? Type)[] TestGlobals = @@ -91,11 +78,12 @@ public void ReadGlobalValue(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); - builder.SetTypes(new Dictionary()) + ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); + descriptorBuilder.SetTypes(new Dictionary()) .SetGlobals(TestGlobals) .SetContracts([]); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target); Assert.True(success); ValidateGlobals(target, TestGlobals); @@ -107,12 +95,13 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); - builder.SetTypes(new Dictionary()) + ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); + descriptorBuilder.SetTypes(new Dictionary()) .SetContracts([]) .SetGlobals(TestGlobals.Select(MakeGlobalToIndirect).ToArray(), TestGlobals.Select((g) => g.Value).ToArray()); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target); Assert.True(success); // Indirect values are pointer-sized, so max 32-bits for a 32-bit target @@ -146,11 +135,12 @@ public void ReadGlobalStringyValues(MockTarget.Architecture arch) { TargetTestHelpers targetTestHelpers = new(arch); ContractDescriptorBuilder builder = new(targetTestHelpers); - builder.SetTypes(new Dictionary()) + ContractDescriptorBuilder.DescriptorBuilder descriptorBuilder = new(builder); + descriptorBuilder.SetTypes(new Dictionary()) .SetContracts([]) .SetGlobals(GlobalStringyValues.Select(MakeGlobalsToStrings).ToArray()); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(descriptorBuilder, out ContractDescriptorTarget? target); Assert.True(success); ValidateGlobalStrings(target, GlobalStringyValues); @@ -176,7 +166,7 @@ public void ReadUtf8String(MockTarget.Architecture arch) fragment.Data[^1] = 0; builder.AddHeapFragment(fragment); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(new(builder), out ContractDescriptorTarget? target); Assert.True(success); string actual = target.ReadUtf8String(addr); @@ -195,7 +185,7 @@ public void WriteValue(MockTarget.Architecture arch) MockMemorySpace.HeapFragment fragment = new() { Address = addr, Data = new byte[4] }; builder.AddHeapFragment(fragment); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(new(builder), out ContractDescriptorTarget? target); Assert.True(success); target.Write(addr, expected); Assert.Equal(expected, target.Read(addr)); @@ -213,7 +203,7 @@ public void WriteBuffer(MockTarget.Architecture arch) MockMemorySpace.HeapFragment fragment = new() { Address = addr, Data = new byte[4] }; builder.AddHeapFragment(fragment); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(new(builder), out ContractDescriptorTarget? target); Assert.True(success); target.WriteBuffer(addr, expected); Span data = stackalloc byte[4]; @@ -236,7 +226,7 @@ public void ReadUtf16String(MockTarget.Architecture arch) targetTestHelpers.WriteUtf16String(fragment.Data, expected); builder.AddHeapFragment(fragment); - bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + bool success = builder.TryCreateTarget(new(builder), out ContractDescriptorTarget? target); Assert.True(success); string actual = target.ReadUtf16String(addr); @@ -375,4 +365,27 @@ void AssertEqualsWithCallerInfo(T expected, T actual) } } + private static void ValidateTypes( + ContractDescriptorTarget target, + Dictionary types, + [CallerMemberName] string caller = "", + [CallerFilePath] string filePath = "", + [CallerLineNumber] int lineNumber = 0) + { + foreach ((DataType type, Target.TypeInfo info) in types) + { + { + // By known type + Target.TypeInfo actual = target.GetTypeInfo(type); + Assert.Equal(info.Size, actual.Size); + Assert.Equal(info.Fields, actual.Fields); + } + { + // By name + Target.TypeInfo actual = target.GetTypeInfo(type.ToString()); + Assert.Equal(info.Size, actual.Size); + Assert.Equal(info.Fields, actual.Fields); + } + } + } }