diff --git a/docs/design/datacontracts/RuntimeInfo.md b/docs/design/datacontracts/RuntimeInfo.md new file mode 100644 index 00000000000000..e3c5e0c14cbeb3 --- /dev/null +++ b/docs/design/datacontracts/RuntimeInfo.md @@ -0,0 +1,43 @@ +# Contract RuntimeInfo + +This contract encapsulates support for fetching information about the target runtime. + +## APIs of contract + +```csharp +public enum RuntimeInfoArchitecture : uint +{ + Unknown = 0, + X86, + Arm32, + X64, + Arm64, + LoongArch64, + RISCV, +} + +public enum RuntimeInfoOperatingSystem : uint +{ + Unknown = 0, + Win, + Unix, +} +``` + +```csharp +// Gets the targets architecture. If this information is not available returns Unknown. +RuntimeInfoArchitecture GetTargetArchitecture(); + +// Gets the targets operating system. If this information is not available returns Unknown. +RuntimeInfoOperatingSystem GetTargetOperatingSystem(); +``` + +## Version 1 + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| Architecture | string | Target architecture | +| OperatingSystem | string | Target operating system | + +The contract implementation simply returns the contract descriptor global values parsed as the respective enum case-insensitively. If these globals are not available, the contract returns Unknown. diff --git a/docs/design/datacontracts/contract-descriptor.md b/docs/design/datacontracts/contract-descriptor.md index fbd58eb33eb9a5..b2388b1ec7d071 100644 --- a/docs/design/datacontracts/contract-descriptor.md +++ b/docs/design/datacontracts/contract-descriptor.md @@ -83,7 +83,8 @@ a JSON integer constant. "globals": { "FEATURE_COMINTEROP": 0, - "s_pThreadStore": [ 0 ] // indirect from pointer data offset 0 + "s_pThreadStore": [ 0 ], // indirect from pointer data offset 0 + "RuntimeID": "win-x64" // string value }, "contracts": {"Thread": 1, "GCHandle": 1, "ThreadStore": 1} } diff --git a/docs/design/datacontracts/data_descriptor.md b/docs/design/datacontracts/data_descriptor.md index 1338e1ae87aa60..9880f225c7412e 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -212,26 +212,60 @@ The global values will be in an array, with each value described by a dictionary * `"name": "global value name"` the name of the global value * `"type": "type name"` the type of the global value -* optional `"value": VALUE | [ int ] | "unknown"` the value of the global value, or an offset in an auxiliary array containing the value or "unknown". +* optional `"value": ` where `` is defined below + + +Numeric constants must be within the range of the type of the global value. If a constant is out of range, behavior is undefined. -The `VALUE` may be a JSON numeric constant integer or a string containing a signed or unsigned -decimal or hex (with prefix `0x` or `0X`) integer constant. The constant must be within the range -of the type of the global value. **Compact format**: The global values will be in a dictionary, with each key being the name of a global and the values being one of: -* `[VALUE | [int], "type name"]` the type and value of a global -* `VALUE | [int]` just the value of a global +* `[, "type name"]` the type and value of a global +* `` just the value of a global -As in the regular format, `VALUE` is a numeric constant or a string containing an integer constant. +Where `` is defined as below. + +Numeric constants must be within the range of the type of the global value. If a constant is out of range, behavior is undefined. Note that a two element array is unambiguously "type and value", whereas a one-element array is unambiguously "indirect value". + **Both formats** +#### Specification Appendix + +``` + ::= | + ::= [ ] + ::= | + ::= | | + + is any JSON string element + is any JSON number element + is a which can be parsed as a hexadecimal number prefixed with "0x" or "0X" + is a which can be parsed as a decimal number. +``` + +#### Parsing Rules +`` is parsed as a numeric value. +`` and `` can be parsed as either a string or numeric value. +`` (that does not form a valid hex or decimal number) is parsed as a string. + +Example using compact format: +```json +{ + "int" : 1234, // Can only be parsed as numeric constant 1234 + "stringyInt" : "1234", // Can be parsed as 1234 or "1234" + "stringyHex" : "0x1234", // Can be parsed as 4660 (0x1234 in decimal) or "0x1234" + "stringValue" : "Hello World" // Can only be parsed as "Hello World" +} +``` + +#### Typing + For pointer and nuint globals, the value may be assumed to fit in a 64-bit unsigned integer. For nint globals, the value may be assumed to fit in a 64-bit signed integer. @@ -239,6 +273,8 @@ Note that the logical descriptor does not contain "unknown" values: it is expect in-memory data descriptor will augment the baseline with a known offset for all fields in the baseline. +#### Indirect Types + If the value is given as a single-element array `[ int ]` then the value is stored in an auxiliary array that is part of the data contract descriptor. Only in-memory data descriptors may have indirect values; baseline data descriptors may not have indirect values. @@ -251,7 +287,6 @@ The indirection array is not part of the data descriptor spec. It is part of th descriptor](./contract_descriptor.md#Contract_descriptor). - ## Example This is an example of a baseline descriptor for a 64-bit architecture. Suppose it has the name `"example-64"` @@ -288,7 +323,7 @@ The baseline is given in the "regular" format. ], "globals": [ { "name": "FEATURE_EH_FUNCLETS", "type": "uint8", "value": "0" }, // baseline defaults value to 0 - { "name": "FEATURE_COMINTEROP", "type", "uint8", "value": "1"}, + { "name": "FEATURE_COMINTEROP", "type": "uint8", "value": "1"}, { "name": "s_pThreadStore", "type": "pointer" } // no baseline value ] } @@ -308,7 +343,8 @@ The following is an example of an in-memory descriptor that references the above "globals": { "FEATURE_COMINTEROP": 0, - "s_pThreadStore": [ 0 ] // indirect from aux data offset 0 + "s_pThreadStore": [ 0 ], // indirect from aux data offset 0 + "RuntimeID": "windows-x64" } } ``` @@ -332,6 +368,7 @@ And the globals will be: | FEATURE_COMINTEROP | uint8 | 0 | | FEATURE_EH_FUNCLETS | uint8 | 0 | | s_pThreadStore | pointer | 0x0100ffe0 | +| RuntimeID | string |"windows-x64"| The `FEATURE_EH_FUNCLETS` global's value comes from the baseline - not the in-memory data descriptor. By contrast, `FEATURE_COMINTEROP` comes from the in-memory data descriptor - with the diff --git a/src/coreclr/debug/daccess/cdac.cpp b/src/coreclr/debug/daccess/cdac.cpp index 02663789ea4d4b..b66c608e2478db 100644 --- a/src/coreclr/debug/daccess/cdac.cpp +++ b/src/coreclr/debug/daccess/cdac.cpp @@ -52,16 +52,6 @@ namespace return S_OK; } - - int GetPlatform(uint32_t* platform, void* context) - { - ICorDebugDataTarget* target = reinterpret_cast(context); - HRESULT hr = target->GetPlatform((CorDebugPlatform*)platform); - if (FAILED(hr)) - return hr; - - return S_OK; - } } CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown* legacyImpl) @@ -74,7 +64,7 @@ CDAC CDAC::Create(uint64_t descriptorAddr, ICorDebugDataTarget* target, IUnknown _ASSERTE(init != nullptr); intptr_t handle; - if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, &GetPlatform, target, &handle) != 0) + if (init(descriptorAddr, &ReadFromTargetCallback, &ReadThreadContext, target, &handle) != 0) { ::FreeLibrary(cdacLib); return {}; diff --git a/src/coreclr/debug/runtimeinfo/CMakeLists.txt b/src/coreclr/debug/runtimeinfo/CMakeLists.txt index 791e0ec370479d..ed82a22611ae22 100644 --- a/src/coreclr/debug/runtimeinfo/CMakeLists.txt +++ b/src/coreclr/debug/runtimeinfo/CMakeLists.txt @@ -42,6 +42,11 @@ install_clr(TARGETS runtimeinfo DESTINATIONS lib COMPONENT runtime) # cDAC contract descriptor +if("${CLR_DOTNET_RID}" STREQUAL "") + message(FATAL_ERROR "CLR_DOTNET_RID is not set. Please ensure it is being set to the portable RID of the target platform by runtime.proj.") +endif() +configure_file(configure.h.in ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + if (NOT CDAC_BUILD_TOOL_BINARY_PATH) # if CDAC_BUILD_TOOL_BINARY_PATH is unspecified (for example for a build without a .NET SDK or msbuild), # link a stub contract descriptor into the runtime diff --git a/src/coreclr/debug/runtimeinfo/configure.h.in b/src/coreclr/debug/runtimeinfo/configure.h.in new file mode 100644 index 00000000000000..efe0ede365c644 --- /dev/null +++ b/src/coreclr/debug/runtimeinfo/configure.h.in @@ -0,0 +1,6 @@ +#ifndef RUNTIME_INFO_CONFIGURE_H_INCLUDED +#define RUNTIME_INFO_CONFIGURE_H_INCLUDED + +#define RID_STRING @CLR_DOTNET_RID@ + +#endif // RUNTIME_INFO_CONFIGURE_H_INCLUDED diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index dc20297b01bed1..bcc2daae3830e3 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -19,6 +19,7 @@ "PlatformMetadata": 1, "PrecodeStubs": 2, "ReJIT": 1, + "RuntimeInfo": 1, "RuntimeTypeSystem": 1, "StackWalk": 1, "StressLog": 2, diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index c4d0aa3d42b645..f8b1ed38ed05dc 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -13,6 +13,8 @@ #include "methodtable.h" #include "threads.h" +#include "configure.h" + #include "../debug/ee/debugger.h" #ifdef HAVE_GCCOVER @@ -51,6 +53,12 @@ struct GlobalPointerSpec uint32_t PointerDataIndex; }; +struct GlobalStringSpec +{ + uint32_t Name; + uint32_t StringValue; +}; + #define CONCAT(token1,token2) token1 ## token2 #define CONCAT4(token1, token2, token3, token4) token1 ## token2 ## token3 ## token4 @@ -59,6 +67,10 @@ struct GlobalPointerSpec #define MAKE_FIELDTYPELEN_NAME(tyname,membername) CONCAT4(cdac_string_pool_membertypename__, tyname, __, membername) #define MAKE_GLOBALLEN_NAME(globalname) CONCAT(cdac_string_pool_globalname__, globalname) #define MAKE_GLOBALTYPELEN_NAME(globalname) CONCAT(cdac_string_pool_globaltypename__, globalname) +#define MAKE_GLOBALVALUELEN_NAME(globalname) CONCAT(cdac_string_pool_globalvalue__, globalname) + +// used to stringify the result of a macros expansion +#define STRINGIFY(x) #x // 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 @@ -71,6 +83,8 @@ struct CDacStringPoolSizes #define CDAC_TYPE_BEGIN(name) DECL_LEN(MAKE_TYPELEN_NAME(name), sizeof(#name)) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(MAKE_FIELDLEN_NAME(tyname,membername), sizeof(#membername)) \ DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(#membertyname)) +#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(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname)) @@ -84,6 +98,7 @@ struct CDacStringPoolSizes #define GET_FIELDTYPE_NAME(tyname,membername) offsetof(struct CDacStringPoolSizes, MAKE_FIELDTYPELEN_NAME(tyname,membername)) #define GET_GLOBAL_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALLEN_NAME(globalname)) #define GET_GLOBALTYPE_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALTYPELEN_NAME(globalname)) +#define GET_GLOBALSTRING_VALUE(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALVALUELEN_NAME(globalname)) // count the types enum @@ -123,6 +138,15 @@ enum #include "datadescriptor.h" }; +// count the global strings +enum +{ + CDacBlobGlobalStringsCount = +#define CDAC_GLOBALS_BEGIN() 0 +#define CDAC_GLOBAL_STRING(name,value) + 1 +#include "datadescriptor.h" +}; + #define MAKE_TYPEFIELDS_TYNAME(tyname) CONCAT(CDacFieldsPoolTypeStart__, tyname) @@ -197,6 +221,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesPoolStart; uint32_t TypeCount; @@ -204,6 +229,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -211,6 +237,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; uint32_t PlatformFlags; uint32_t BaselineName; @@ -218,6 +245,7 @@ struct BinaryBlobDataDescriptor struct FieldSpec FieldsPool[CDacBlobFieldsPoolCount]; struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; uint8_t EndMagic[4]; }; @@ -242,16 +270,19 @@ struct MagicAndBlob BlobDataDescriptor = { /* .FieldsPoolStart = */ offsetof(struct BinaryBlobDataDescriptor, FieldsPool), /* .GlobalLiteralValuesStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalLiteralValues), /* .GlobalPointersStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalPointerValues), + /* .GlobalStringValuesStart = */ offsetof(struct BinaryBlobDataDescriptor, GlobalStringValues), /* .NamesPoolStart = */ offsetof(struct BinaryBlobDataDescriptor, NamesPool), /* .TypeCount = */ CDacBlobTypesCount, /* .FieldsPoolCount = */ CDacBlobFieldsPoolCount, /* .GlobalLiteralValuesCount = */ CDacBlobGlobalLiteralsCount, /* .GlobalPointerValuesCount = */ CDacBlobGlobalPointersCount, + /* .GlobalStringValuesCount = */ CDacBlobGlobalStringsCount, /* .NamesPoolCount = */ sizeof(struct CDacStringPoolSizes), /* .TypeSpecSize = */ sizeof(struct TypeSpec), /* .FieldSpecSize = */ sizeof(struct FieldSpec), /* .GlobalLiteralSpecSize = */ sizeof(struct GlobalLiteralSpec), /* .GlobalPointerSpecSize = */ sizeof(struct GlobalPointerSpec), + /* .GlobalStringSpecSize = */ sizeof(struct GlobalStringSpec) }, /* .PlatformFlags = */ (sizeof(void*) == 4 ? 0x02 : 0) | 0x01, /* .BaselineName = */ offsetof(struct CDacStringPoolSizes, cdac_string_pool_baseline_), @@ -287,10 +318,16 @@ struct MagicAndBlob BlobDataDescriptor = { #include "datadescriptor.h" }, + /* .GlobalStringValues = */ { +#define CDAC_GLOBAL_STRING(name,value) { /* .Name = */ GET_GLOBAL_NAME(name), /* .Value = */ GET_GLOBALSTRING_VALUE(name) }, +#include "datadescriptor.h" + }, + /* .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(name,tyname,value) #name "\0" #tyname "\0" #include "datadescriptor.h" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index d9a0556b9742b1..9ba445b8fba267 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -98,6 +98,9 @@ #ifndef CDAC_GLOBAL_POINTER #define CDAC_GLOBAL_POINTER(globalname,addr) #endif +#ifndef CDAC_GLOBAL_STRING +#define CDAC_GLOBAL_STRING(globalname,stringval) +#endif #ifndef CDAC_GLOBALS_END #define CDAC_GLOBALS_END() #endif @@ -757,6 +760,33 @@ CDAC_TYPE_END(CalleeSavedRegisters) CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() + +#if defined(TARGET_UNIX) +CDAC_GLOBAL_STRING(OperatingSystem, unix) +#elif defined(TARGET_WINDOWS) +CDAC_GLOBAL_STRING(OperatingSystem, windows) +#else +#error TARGET_{OS} define is not recognized by the cDAC. Update this switch and the enum values in IRuntimeInfo.cs +#endif + +#if defined(TARGET_X86) +CDAC_GLOBAL_STRING(Architecture, x86) +#elif defined(TARGET_AMD64) +CDAC_GLOBAL_STRING(Architecture, x64) +#elif defined(TARGET_ARM) +CDAC_GLOBAL_STRING(Architecture, arm) +#elif defined(TARGET_ARM64) +CDAC_GLOBAL_STRING(Architecture, arm64) +#elif defined(TARGET_LOONGARCH64) +CDAC_GLOBAL_STRING(Architecture, loongarch64) +#elif defined(TARGET_RISCV64) +CDAC_GLOBAL_STRING(Architecture, riscv64) +#else +#error TARGET_{ARCH} define is not recognized by the cDAC. Update this switch and the enum values in IRuntimeInfo.cs +#endif + +CDAC_GLOBAL_STRING(RID, RID_STRING) + CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) @@ -831,4 +861,5 @@ CDAC_GLOBALS_END() #undef CDAC_GLOBALS_BEGIN #undef CDAC_GLOBAL #undef CDAC_GLOBAL_POINTER +#undef CDAC_GLOBAL_STRING #undef CDAC_GLOBALS_END diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj index 9da9af3cc09923..0c6fed7fe61fc6 100644 --- a/src/coreclr/runtime.proj +++ b/src/coreclr/runtime.proj @@ -47,6 +47,9 @@ <_CoreClrBuildArg Condition="'$(EnableNativeSanitizers)' != ''" Include="-fsanitize $(EnableNativeSanitizers)" /> <_CoreClrBuildArg Condition="'$(HostCrossOS)' != ''" Include="-hostos $(HostCrossOS)" /> <_CoreClrBuildArg Include="-outputrid $(OutputRID)" /> + + <_CoreClrBuildArg Condition="'$(BaseOS)' == ''" Include="-cmakeargs "-DCLR_DOTNET_RID=$(OutputRID)"" /> + <_CoreClrBuildArg Condition="'$(BaseOS)' != ''" Include="-cmakeargs "-DCLR_DOTNET_RID=$(BaseOS)"" /> <_CoreClrBuildArg Condition="'$(BuildSubdirectory)' != ''" Include="-subdir $(BuildSubdirectory)" /> <_CoreClrBuildArg Include="-cmakeargs "-DCLR_DOTNET_HOST_PATH=$(DOTNET_HOST_PATH)"" /> <_CoreClrBuildArg Condition="'$(HasCdacBuildTool)' == 'true'" Include="-cmakeargs "-DCDAC_BUILD_TOOL_BINARY_PATH=$(RuntimeBinDir)cdac-build-tool\cdac-build-tool.dll"" /> diff --git a/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs b/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs index 55aa16283c4fa1..14ee0988ea2b8b 100644 --- a/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs +++ b/src/coreclr/tools/cdac-build-tool/DataDescriptorModel.cs @@ -24,7 +24,7 @@ public class DataDescriptorModel 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.Indirect); + public int PointerDataCount => 1 + Globals.Values.Count(g => g.Value.Kind == GlobalValue.KindEnum.Indirect); private DataDescriptorModel(string baseline, IReadOnlyDictionary types, IReadOnlyDictionary globals, IReadOnlyDictionary contracts, uint platformFlags) { @@ -36,6 +36,7 @@ private DataDescriptorModel(string baseline, IReadOnlyDictionary { - public bool Indirect { get; private init; } - public ulong Value { get; } - public static GlobalValue MakeDirect(ulong value) => new GlobalValue(value); - public static GlobalValue MakeIndirect(uint auxDataIdx) => new GlobalValue((ulong)auxDataIdx) { Indirect = true }; - private GlobalValue(ulong value) { Value = value; } + public enum KindEnum + { + Direct, + Indirect, + String + } + + public KindEnum Kind { get; private init; } + public ulong NumericValue { get; } + public string StringValue { get; } + public static GlobalValue MakeDirect(ulong value) => new GlobalValue(value) { Kind = KindEnum.Direct }; + public static GlobalValue MakeIndirect(uint auxDataIdx) => new GlobalValue((ulong)auxDataIdx) { Kind = KindEnum.Indirect }; + public static GlobalValue MakeString(string value) => new GlobalValue(value) { Kind = KindEnum.String }; + private GlobalValue(ulong value) { NumericValue = value; StringValue = string.Empty;} + private GlobalValue(string value) { StringValue = value; } - public static bool operator ==(GlobalValue left, GlobalValue right) => left.Value == right.Value && left.Indirect == right.Indirect; + public static bool operator ==(GlobalValue left, GlobalValue right) => left.Equals(right); public static bool operator !=(GlobalValue left, GlobalValue right) => !(left == right); - public bool Equals(GlobalValue other) => this == other; - public override bool Equals(object? obj) => obj is GlobalValue value && this == value; - public override int GetHashCode() => HashCode.Combine(Value, Indirect); - public override string ToString() => Indirect ? $"Indirect({Value})" : $"0x{Value:x}"; + public bool Equals(GlobalValue other) => other.Kind == Kind && other.NumericValue == NumericValue && other.StringValue == StringValue; + public override bool Equals(object? obj) => obj is GlobalValue value && Equals(value); + public override int GetHashCode() => HashCode.Combine(Kind, NumericValue, StringValue); + public override string ToString() + { + return Kind switch + { + KindEnum.Direct => $"0x{NumericValue:x}", + KindEnum.Indirect => $"Indirect({NumericValue})", + KindEnum.String => $"'{StringValue}'", + _ => throw new InvalidOperationException("Unknown GlobalValue type") + }; + } } [JsonConverter(typeof(GlobalModelJsonConverter))] diff --git a/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs b/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs index 429f6cc6979284..40ff3a67bab42e 100644 --- a/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs +++ b/src/coreclr/tools/cdac-build-tool/JsonConverter/GlobalValueJsonConverter.cs @@ -15,18 +15,25 @@ public override DataDescriptorModel.GlobalValue Read(ref Utf8JsonReader reader, public override void Write(Utf8JsonWriter writer, DataDescriptorModel.GlobalValue value, JsonSerializerOptions options) { - if (!value.Indirect) + switch (value.Kind) { - // no type: just write value as a number. - // we always write as a string containing a hex number - writer.WriteStringValue($"0x{value.Value:x}"); - } - else - { - // pointer data index. write as a 1-element array containing a decimal number - writer.WriteStartArray(); - writer.WriteNumberValue(value.Value); - writer.WriteEndArray(); + case DataDescriptorModel.GlobalValue.KindEnum.Direct: + // no type: just write value as a number. + // we always write as a string containing a hex number + writer.WriteStringValue($"0x{value.NumericValue:x}"); + break; + case DataDescriptorModel.GlobalValue.KindEnum.Indirect: + // pointer data index. write as a 1-element array containing a decimal number + writer.WriteStartArray(); + writer.WriteNumberValue(value.NumericValue); + writer.WriteEndArray(); + break; + case DataDescriptorModel.GlobalValue.KindEnum.String: + // string data. write as a JSON string value + writer.WriteStringValue(value.StringValue); + break; + default: + throw new InvalidOperationException("Unknown GlobalValue type"); } } } diff --git a/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs b/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs index 24e7e2827d457b..1c379a7e61dd05 100644 --- a/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs +++ b/src/coreclr/tools/cdac-build-tool/ObjectFileScraper.cs @@ -160,6 +160,7 @@ private struct HeaderDirectory public uint GlobalLiteralValuesStart; public uint GlobalPointersStart; + public uint GlobalStringValuesStart; public uint NamesStart; public uint TypesCount; @@ -167,6 +168,7 @@ private struct HeaderDirectory public uint GlobalLiteralValuesCount; public uint GlobalPointerValuesCount; + public uint GlobalStringValuesCount; public uint NamesPoolCount; @@ -174,6 +176,7 @@ private struct HeaderDirectory public byte FieldSpecSize; public byte GlobalLiteralSpecSize; public byte GlobalPointerSpecSize; + public byte GlobalStringSpecSize; }; private static void DumpHeaderDirectory(HeaderDirectory headerDirectory) @@ -186,12 +189,14 @@ private static void DumpHeaderDirectory(HeaderDirectory headerDirectory) 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} """); @@ -207,6 +212,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) var globalLiteralValuesStart = state.ReadUInt32(); var globalPointersStart = state.ReadUInt32(); + var globalStringValuesStart = state.ReadUInt32(); var namesStart = state.ReadUInt32(); var typeCount = state.ReadUInt32(); @@ -214,6 +220,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) var globalLiteralValuesCount = state.ReadUInt32(); var globalPointerValuesCount = state.ReadUInt32(); + var GlobalStringValuesCount = state.ReadUInt32(); var namesPoolCount = state.ReadUInt32(); @@ -221,6 +228,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) var fieldSpecSize = state.ReadByte(); var globalLiteralSpecSize = state.ReadByte(); var globalPointerSpecSize = state.ReadByte(); + var globalStringSpecSize = state.ReadByte(); return new HeaderDirectory { FlagsAndBaselineStart = baselineStart, @@ -228,6 +236,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) FieldsPoolStart = fieldPoolStart, GlobalLiteralValuesStart = globalLiteralValuesStart, GlobalPointersStart = globalPointersStart, + GlobalStringValuesStart = globalStringValuesStart, NamesStart = namesStart, TypesCount = typeCount, @@ -235,6 +244,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) GlobalLiteralValuesCount = globalLiteralValuesCount, GlobalPointerValuesCount = globalPointerValuesCount, + GlobalStringValuesCount = GlobalStringValuesCount, NamesPoolCount = namesPoolCount, @@ -242,6 +252,7 @@ private static HeaderDirectory ReadHeader(ScraperState state) FieldSpecSize = fieldSpecSize, GlobalLiteralSpecSize = globalLiteralSpecSize, GlobalPointerSpecSize = globalPointerSpecSize, + GlobalStringSpecSize = globalStringSpecSize, }; } @@ -280,6 +291,12 @@ private struct GlobalPointerSpec public uint AuxDataIdx; } + private struct GlobalStringSpec + { + public uint NameIdx; + public uint ValueIdx; + } + private sealed class Content { public required bool Verbose {get; init; } @@ -289,6 +306,7 @@ private sealed class Content public required IReadOnlyList FieldSpecs { get; init; } public required IReadOnlyList GlobaLiteralSpecs { get; init; } public required IReadOnlyList GlobalPointerSpecs { get; init; } + public required IReadOnlyList GlobalStringSpecs { get; init; } public required ReadOnlyMemory NamesPool { get; init; } internal string GetPoolString(uint stringIdx) @@ -360,6 +378,14 @@ public void AddToModel(DataDescriptorModel.Builder builder) builder.AddOrUpdateGlobal(globalName, DataDescriptorModel.PointerTypeName, globalValue); WriteVerbose($"Global pointer {globalName} has index {globalValue}"); } + + foreach (var globalString in GlobalStringSpecs) + { + var globalName = GetPoolString(globalString.NameIdx); + var globalValue = DataDescriptorModel.GlobalValue.MakeString(GetPoolString(globalString.ValueIdx)); + builder.AddOrUpdateGlobal(globalName, DataDescriptorModel.StringTypeName, globalValue); + WriteVerbose($"Global string {globalName} has value {globalValue}"); + } } private void WriteVerbose(string msg) @@ -381,6 +407,7 @@ private Content ReadContent(ScraperState state, HeaderDirectory header) FieldSpec[] fieldSpecs = ReadFieldSpecs(state, header); GlobalLiteralSpec[] globalLiteralSpecs = ReadGlobalLiteralSpecs(state, header); GlobalPointerSpec[] globalPointerSpecs = ReadGlobalPointerSpecs(state, header); + GlobalStringSpec[] globalStringSpecs = ReadGlobalStringSpecs(state, header); byte[] namesPool = ReadNamesPool(state, header); byte[] endMagic = new byte[4]; @@ -406,6 +433,7 @@ private Content ReadContent(ScraperState state, HeaderDirectory header) FieldSpecs = fieldSpecs, GlobaLiteralSpecs = globalLiteralSpecs, GlobalPointerSpecs = globalPointerSpecs, + GlobalStringSpecs = globalStringSpecs, NamesPool = namesPool }; } @@ -502,6 +530,26 @@ private static GlobalPointerSpec[] ReadGlobalPointerSpecs(ScraperState state, He return globalSpecs; } + private static GlobalStringSpec[] ReadGlobalStringSpecs(ScraperState state, HeaderDirectory header) + { + GlobalStringSpec[] globalSpecs = new GlobalStringSpec[header.GlobalStringValuesCount]; + state.ResetPosition(state.HeaderStart + (long)header.GlobalStringValuesStart); + for (int i = 0; i < header.GlobalStringValuesCount; i++) + { + int bytesRead = 0; + globalSpecs[i].NameIdx = state.ReadUInt32(); + bytesRead += sizeof(uint); + globalSpecs[i].ValueIdx = state.ReadUInt32(); + bytesRead += sizeof(uint); + // skip padding + if (bytesRead < header.GlobalStringSpecSize) + { + state.Skip(header.GlobalStringSpecSize - bytesRead); + } + } + return globalSpecs; + } + private static byte[] ReadNamesPool(ScraperState state, HeaderDirectory header) { byte[] namesPool = new byte[header.NamesPoolCount]; diff --git a/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md b/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md index b7321edd12c991..f04765d9f13db4 100644 --- a/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md +++ b/src/coreclr/tools/cdac-build-tool/data-descriptor-blob.md @@ -88,6 +88,14 @@ struct GlobalPointerSpec uint32_t Name; uint32_t PointerDataIndex; }; + +// A string global value. +// We record the name and the value, both as a string. +struct GlobalStringSpec +{ + uint32_t Name; + uint32_t valueIndex; +} ``` The main data we want to emit to the object file is an instance of the following structure: @@ -108,6 +116,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesStart; uint32_t TypeCount; @@ -115,6 +124,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -122,6 +132,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; // Platform flags (primarily pointer size) uint32_t PlatformFlags; @@ -136,6 +147,8 @@ struct BinaryBlobDataDescriptor struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; // an array of pointer globals struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; + // an array of string globals + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; // all of the names that might be referenced from elsewhere in BinaryBlobDataDescriptor, // delimited by "\0" uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; @@ -178,12 +191,15 @@ in a contiguous subsequence and are terminated by a marker `FieldSpec` with a `N For each field there is a name that gives an offset in the name pool and an offset indicating the field's offset. -The global constants are given as a sequence of `GlobalLiteralSpec` elements. Each global has a +The numeric global constants are given as a sequence of `GlobalLiteralSpec` elements. Each global has a name, type and a value. Globals that are the addresses in target memory, are in `GlobalPointerSpec` elements. Each pointer element has a name and an index in a separately compiled pointer structure that is linked into runtime . See [contract-descriptor.md](/docs/design/datacontracts/contract-descriptor.md) +Strings can be passed as `GlobalStringSpec` elements. Each string global has a name and value which +are passed as offsets into the `NamesPool`. + The `NamesPool` is a single sequence of utf-8 bytes comprising the concatenation of all the type field and global names including a terminating nul byte for each name. The same name may occur multiple times. The names could be referenced by multiple type or multiple fields. (That is, a diff --git a/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c b/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c index b90b7eca0e932a..09641ef3531a21 100644 --- a/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c +++ b/src/coreclr/tools/cdac-build-tool/sample/sample.blob.c @@ -49,6 +49,12 @@ struct GlobalPointerSpec uint32_t AuxIndex; }; +struct GlobalStringSpec +{ + uint32_t Name; + uint32_t StringValue; +}; + #define CONCAT(token1,token2) token1 ## token2 #define CONCAT4(token1, token2, token3, token4) token1 ## token2 ## token3 ## token4 @@ -57,6 +63,7 @@ struct GlobalPointerSpec #define MAKE_FIELDTYPELEN_NAME(tyname,membername) CONCAT4(cdac_string_pool_membertypename__, tyname, __, membername) #define MAKE_GLOBALLEN_NAME(globalname) CONCAT(cdac_string_pool_globalname__, globalname) #define MAKE_GLOBALTYPELEN_NAME(globalname) CONCAT(cdac_string_pool_globaltypename__, globalname) +#define MAKE_GLOBALVALUELEN_NAME(globalname) CONCAT(cdac_string_pool_globalvalue__, globalname) // 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 @@ -66,33 +73,16 @@ struct CDacStringPoolSizes char cdac_string_pool_nil; // make the first real string start at offset 1 #define DECL_LEN(membername,len) char membername[(len)]; #define CDAC_BASELINE(name) DECL_LEN(cdac_string_pool_baseline_, (sizeof(name))) -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) DECL_LEN(MAKE_TYPELEN_NAME(name), sizeof(#name)) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(MAKE_FIELDLEN_NAME(tyname,membername), sizeof(#membername)) \ DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(#membertyname)) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() +#define CDAC_GLOBAL_STRING(name, stringval) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \ + DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(#stringval)) #define CDAC_GLOBAL_POINTER(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)) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END #undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }; #define GET_TYPE_NAME(name) offsetof(struct CDacStringPoolSizes, MAKE_TYPELEN_NAME(name)) @@ -100,38 +90,15 @@ struct CDacStringPoolSizes #define GET_FIELDTYPE_NAME(tyname,membername) offsetof(struct CDacStringPoolSizes, MAKE_FIELDTYPELEN_NAME(tyname,membername)) #define GET_GLOBAL_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALLEN_NAME(globalname)) #define GET_GLOBALTYPE_NAME(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALTYPELEN_NAME(globalname)) +#define GET_GLOBALSTRING_VALUE(globalname) offsetof(struct CDacStringPoolSizes, MAKE_GLOBALVALUELEN_NAME(globalname)) // count the types enum { CDacBlobTypesCount = #define CDAC_BASELINE(name) 0 -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) + 1 -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , }; // count the field pool size. @@ -140,32 +107,9 @@ enum { CDacBlobFieldsPoolCount = #define CDAC_BASELINE(name) 1 -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) + 1 #define CDAC_TYPE_END(name) + 1 -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , }; // count the literal globals @@ -173,32 +117,8 @@ enum { CDacBlobGlobalLiteralsCount = #define CDAC_BASELINE(name) 0 -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) #define CDAC_GLOBAL(name,tyname,value) + 1 -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , }; // count the aux vector globals @@ -206,32 +126,17 @@ enum { CDacBlobGlobalPointersCount = #define CDAC_BASELINE(name) 0 -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) + 1 -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END - , +}; + +// count the global strings +enum +{ + CDacBlobGlobalStringsCount = +#define CDAC_GLOBALS_BEGIN() 0 +#define CDAC_GLOBAL_STRING(name,value) + 1 +#include "sample.data.h" }; @@ -257,32 +162,11 @@ struct CDacFieldsPoolSizes { #define DECL_LEN(membername) char membername; #define CDAC_BASELINE(name) DECL_LEN(cdac_fields_pool_start_placeholder__) -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) struct MAKE_TYPEFIELDS_TYNAME(name) { -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(CONCAT4(cdac_fields_pool_member__, tyname, __, membername)) #define CDAC_TYPE_END(name) DECL_LEN(CONCAT4(cdac_fields_pool_member__, tyname, _, endmarker)) \ } MAKE_TYPEFIELDS_TYNAME(name); -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END #undef DECL_LEN }; @@ -303,31 +187,9 @@ struct CDacGlobalPointerIndex { #define DECL_LEN(membername) char membername; #define CDAC_BASELINE(name) DECL_LEN(cdac_global_pointer_index_start_placeholder__) -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(CONCAT(cdac_global_pointer_index__, name)) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END #undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }; #define GET_GLOBAL_POINTER_INDEX(name) offsetof(struct CDacGlobalPointerIndex, CONCAT(cdac_global_pointer_index__, name)) @@ -343,6 +205,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesPoolStart; uint32_t TypeCount; @@ -350,6 +213,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -357,6 +221,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; uint32_t PlatformFlags; uint32_t BaselineName; @@ -364,6 +229,7 @@ struct BinaryBlobDataDescriptor struct FieldSpec FieldsPool[CDacBlobFieldsPoolCount]; struct GlobalLiteralSpec GlobalLiteralValues[CDacBlobGlobalLiteralsCount]; struct GlobalPointerSpec GlobalPointerValues[CDacBlobGlobalPointersCount]; + struct GlobalStringSpec GlobalStringValues[CDacBlobGlobalStringsCount]; uint8_t NamesPool[sizeof(struct CDacStringPoolSizes)]; uint8_t EndMagic[4]; }; @@ -382,16 +248,19 @@ const struct MagicAndBlob Blob = { .FieldsPoolStart = offsetof(struct BinaryBlobDataDescriptor, FieldsPool), .GlobalLiteralValuesStart = offsetof(struct BinaryBlobDataDescriptor, GlobalLiteralValues), .GlobalPointersStart = offsetof(struct BinaryBlobDataDescriptor, GlobalPointerValues), + .GlobalStringValuesStart = offsetof(struct BinaryBlobDataDescriptor, GlobalStringValues), .NamesPoolStart = offsetof(struct BinaryBlobDataDescriptor, NamesPool), .TypeCount = CDacBlobTypesCount, .FieldsPoolCount = CDacBlobFieldsPoolCount, .GlobalLiteralValuesCount = CDacBlobGlobalLiteralsCount, .GlobalPointerValuesCount = CDacBlobGlobalPointersCount, + .GlobalStringValuesCount = CDacBlobGlobalStringsCount, .NamesPoolCount = sizeof(struct CDacStringPoolSizes), .TypeSpecSize = sizeof(struct TypeSpec), .FieldSpecSize = sizeof(struct FieldSpec), .GlobalLiteralSpecSize = sizeof(struct GlobalLiteralSpec), .GlobalPointerSpecSize = sizeof(struct GlobalPointerSpec), + .GlobalStringSpecSize = sizeof(struct GlobalStringSpec), }, .EndMagic = { 0x01, 0x02, 0x03, 0x04 }, .PlatformFlags = 0x01 | (sizeof(void*) == 4 ? 0x02 : 0), @@ -399,153 +268,47 @@ const struct MagicAndBlob Blob = { .NamesPool = ("\0" // starts with a nul #define CDAC_BASELINE(name) name "\0" -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) #name "\0" -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" #membertyname "\0" -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) #name "\0" #define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0" -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END ), .FieldsPool = { #define CDAC_BASELINE(name) {0,}, -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) #define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) { \ .Name = GET_FIELD_NAME(tyname,membername), \ .TypeName = GET_FIELDTYPE_NAME(tyname,membername), \ .FieldOffset = offset, \ }, #define CDAC_TYPE_END(name) { 0, }, -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }, .Types = { -#define CDAC_BASELINE(name) -#define CDAC_TYPES_BEGIN() #define CDAC_TYPE_BEGIN(name) { \ .Name = GET_TYPE_NAME(name), \ .Fields = GET_TYPE_FIELDS(name), #define CDAC_TYPE_INDETERMINATE(name) .Size = 0, #define CDAC_TYPE_SIZE(size) .Size = size, -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #define CDAC_TYPE_END(name) }, -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }, .GlobalLiteralValues = { -#define CDAC_BASELINE(name) -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() -#define CDAC_GLOBAL_POINTER(name,value) #define CDAC_GLOBAL(name,tyname,value) { .Name = GET_GLOBAL_NAME(name), .TypeName = GET_GLOBALTYPE_NAME(name), .Value = value }, -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END }, .GlobalPointerValues = { -#define CDAC_BASELINE(name) -#define CDAC_TYPES_BEGIN() -#define CDAC_TYPE_BEGIN(name) -#define CDAC_TYPE_INDETERMINATE(name) -#define CDAC_TYPE_SIZE(size) -#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) -#define CDAC_TYPE_END(name) -#define CDAC_TYPES_END() -#define CDAC_GLOBALS_BEGIN() #define CDAC_GLOBAL_POINTER(name,value) { .Name = GET_GLOBAL_NAME(name), .AuxIndex = GET_GLOBAL_POINTER_INDEX(name) }, -#define CDAC_GLOBAL(name,tyname,value) -#define CDAC_GLOBALS_END() #include "sample.data.h" -#undef CDAC_BASELINE -#undef CDAC_TYPES_BEGIN -#undef CDAC_TYPES_END -#undef CDAC_TYPE_BEGIN -#undef CDAC_TYPE_INDETERMINATE -#undef CDAC_TYPE_SIZE -#undef CDAC_TYPE_FIELD -#undef CDAC_TYPE_END -#undef DECL_LEN -#undef CDAC_GLOBALS_BEGIN -#undef CDAC_GLOBAL_POINTER -#undef CDAC_GLOBAL -#undef CDAC_GLOBALS_END + }, + + .GlobalStringValues = { +#define CDAC_GLOBAL_STRING(name, value) { .Name = GET_GLOBAL_NAME(name), .StringValue = GET_GLOBALSTRING_VALUE(name) }, +#include "sample.data.h" }, } }; diff --git a/src/coreclr/tools/cdac-build-tool/sample/sample.data.h b/src/coreclr/tools/cdac-build-tool/sample/sample.data.h index e4b8bff98b5e43..58ed59d02de4a1 100644 --- a/src/coreclr/tools/cdac-build-tool/sample/sample.data.h +++ b/src/coreclr/tools/cdac-build-tool/sample/sample.data.h @@ -1,3 +1,45 @@ +#ifndef CDAC_BASELINE +#define CDAC_BASELINE(identifier) +#endif +#ifndef CDAC_TYPES_BEGIN +#define CDAC_TYPES_BEGIN() +#endif +#ifndef CDAC_TYPE_BEGIN +#define CDAC_TYPE_BEGIN(tyname) +#endif +#ifndef CDAC_TYPE_SIZE +#define CDAC_TYPE_SIZE(k) +#endif +#ifndef CDAC_TYPE_INDETERMINATE +#define CDAC_TYPE_INDETERMINATE(tyname) +#endif +#ifndef CDAC_TYPE_FIELD +#define CDAC_TYPE_FIELD(tyname,fieldtyname,fieldname,off) +#endif +#ifndef CDAC_TYPE_END +#define CDAC_TYPE_END(tyname) +#endif +#ifndef CDAC_TYPES_END +#define CDAC_TYPES_END() +#endif +#ifndef CDAC_GLOBALS_BEGIN +#define CDAC_GLOBALS_BEGIN() +#endif +#ifndef CDAC_GLOBAL +#define CDAC_GLOBAL(globalname,tyname,val) +#endif +#ifndef CDAC_GLOBAL_POINTER +#define CDAC_GLOBAL_POINTER(globalname,addr) +#endif +#ifndef CDAC_GLOBAL_STRING +#define CDAC_GLOBAL_STRING(globalname,stringval) +#endif +#ifndef CDAC_GLOBALS_END +#define CDAC_GLOBALS_END() +#endif + + + CDAC_BASELINE("empty") CDAC_TYPES_BEGIN() @@ -21,4 +63,21 @@ CDAC_GLOBAL(FeatureEHFunclets, uint8, 1) CDAC_GLOBAL(FeatureEHFunclets, uint8, 0) #endif CDAC_GLOBAL(SomeMagicNumber, uint32, 42) +CDAC_GLOBAL_STRING(RuntimeIdentifier, "windows-x64") CDAC_GLOBALS_END() + + + +#undef CDAC_BASELINE +#undef CDAC_TYPES_BEGIN +#undef CDAC_TYPE_BEGIN +#undef CDAC_TYPE_INDETERMINATE +#undef CDAC_TYPE_SIZE +#undef CDAC_TYPE_FIELD +#undef CDAC_TYPE_END +#undef CDAC_TYPES_END +#undef CDAC_GLOBALS_BEGIN +#undef CDAC_GLOBAL +#undef CDAC_GLOBAL_POINTER +#undef CDAC_GLOBAL_STRING +#undef CDAC_GLOBALS_END diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 42e0d38c545249..53faad2d53f99b 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -63,4 +63,8 @@ public abstract class ContractRegistry /// Gets an instance of the StackWalk contract for the target. /// public abstract IStackWalk StackWalk { get; } + /// + /// Gets an instance of the RuntimeInfo contract for the target. + /// + public abstract IRuntimeInfo RuntimeInfo { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs new file mode 100644 index 00000000000000..31e80c9ddc5430 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeInfo.cs @@ -0,0 +1,41 @@ +// 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; + +// Values are similar to System.Runtime.InteropServices.Architecture +public enum RuntimeInfoArchitecture : uint +{ + Unknown = 0, + X86, + X64, + Arm, + Arm64, + Wasm, + S390x, + LoongArch64, + Armv6, + Ppc64le, + RiscV64, +} + +public enum RuntimeInfoOperatingSystem : uint +{ + Unknown = 0, + Windows, + Unix, +} + +public interface IRuntimeInfo : IContract +{ + static string IContract.Name { get; } = nameof(RuntimeInfo); + RuntimeInfoArchitecture GetTargetArchitecture() => throw new NotImplementedException(); + RuntimeInfoOperatingSystem GetTargetOperatingSystem() => throw new NotImplementedException(); +} + +public readonly struct RuntimeInfo : IRuntimeInfo +{ + +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index a9a73560c8b9b7..887e5faedbe9f3 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -18,27 +18,6 @@ namespace Microsoft.Diagnostics.DataContractReader; /// public abstract class Target { - /// - /// CorDebugPlatform represents the platform of the target. - /// - public enum CorDebugPlatform : int - { - CORDB_PLATFORM_WINDOWS_X86 = 0, - CORDB_PLATFORM_WINDOWS_AMD64 = 1, - CORDB_PLATFORM_WINDOWS_IA64 = 2, - CORDB_PLATFORM_MAC_PPC = 3, - CORDB_PLATFORM_MAC_X86 = 4, - CORDB_PLATFORM_WINDOWS_ARM = 5, - CORDB_PLATFORM_MAC_AMD64 = 6, - CORDB_PLATFORM_WINDOWS_ARM64 = 7, - CORDB_PLATFORM_POSIX_AMD64 = 8, - CORDB_PLATFORM_POSIX_X86 = 9, - CORDB_PLATFORM_POSIX_ARM = 10, - CORDB_PLATFORM_POSIX_ARM64 = 11, - CORDB_PLATFORM_POSIX_LOONGARCH64 = 12, - CORDB_PLATFORM_POSIX_RISCV64 = 13, - } - /// /// Pointer size of the target /// @@ -48,11 +27,6 @@ public enum CorDebugPlatform : int /// public abstract bool IsLittleEndian { get; } - /// - /// Platform of the target - /// - public abstract CorDebugPlatform Platform { get; } - /// /// Fills a buffer with the context of the given thread /// @@ -119,6 +93,21 @@ public enum CorDebugPlatform : int /// Value read from the target public abstract TargetNUInt ReadNUInt(ulong address); + /// + /// Read a well known global from the target process as a string + /// + /// The name of the global + /// The value of the global if found + /// True if a global is found, false otherwise + public abstract bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value); + + /// + /// Read a well known global from the target process as a string + /// + /// The name of the global + /// A string value + public abstract string ReadGlobalString(string name); + /// /// Read a well known global from the target process as a number in the target endianness /// diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 0e76ef91d7de39..ad050a57835632 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -56,6 +56,9 @@ public static class Globals public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); public const string HashMapValueMask = nameof(HashMapValueMask); + + public const string Architecture = nameof(Architecture); + public const string OperatingSystem = nameof(OperatingSystem); } public static class FieldNames { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs new file mode 100644 index 00000000000000..de468ffc96dc75 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfoFactory.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class RuntimeInfoFactory : IContractFactory +{ + IRuntimeInfo IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new RuntimeInfo_1(target), + _ => default(RuntimeInfo), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs new file mode 100644 index 00000000000000..5b6e3508bdc360 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeInfo_1.cs @@ -0,0 +1,42 @@ +// 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; + +internal struct RuntimeInfo_1 : IRuntimeInfo +{ + internal readonly Target _target; + + public RuntimeInfo_1(Target target) + { + _target = target; + } + + readonly RuntimeInfoArchitecture IRuntimeInfo.GetTargetArchitecture() + { + if (_target.TryReadGlobalString(Constants.Globals.Architecture, out string? arch)) + { + if (Enum.TryParse(arch, ignoreCase: true, out RuntimeInfoArchitecture parsedArch)) + { + return parsedArch; + } + } + + return RuntimeInfoArchitecture.Unknown; + } + + readonly RuntimeInfoOperatingSystem IRuntimeInfo.GetTargetOperatingSystem() + { + if (_target.TryReadGlobalString(Constants.Globals.OperatingSystem, out string? os)) + { + if (Enum.TryParse(os, ignoreCase: true, out RuntimeInfoOperatingSystem parsedOS)) + { + return parsedOS; + } + } + + return RuntimeInfoOperatingSystem.Unknown; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index 8a82caf4cb2abd..cf9aac6ac54507 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -25,17 +25,13 @@ public interface IPlatformAgnosticContext public static IPlatformAgnosticContext GetContextForPlatform(Target target) { - switch (target.Platform) + IRuntimeInfo runtimeInfo = target.Contracts.RuntimeInfo; + return runtimeInfo.GetTargetArchitecture() switch { - case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: - case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: - case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: - return new ContextHolder(); - case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: - case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: - return new ContextHolder(); - default: - throw new InvalidOperationException($"Unsupported platform {target.Platform}"); - } + RuntimeInfoArchitecture.X64 => new ContextHolder(), + RuntimeInfoArchitecture.Arm64 => new ContextHolder(), + RuntimeInfoArchitecture.Unknown => throw new InvalidOperationException($"Processor architecture is required for creating a platform specific context and is not provided by the target"), + _ => throw new InvalidOperationException($"Unsupported architecture {runtimeInfo.GetTargetArchitecture()}"), + }; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index d49ceb0eeaacd2..7b339281951cbe 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -38,6 +38,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), [typeof(IReJIT)] = new ReJITFactory(), [typeof(IStackWalk)] = new StackWalkFactory(), + [typeof(IRuntimeInfo)] = new RuntimeInfoFactory(), }; configureFactories?.Invoke(_factories); } @@ -55,6 +56,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IPrecodeStubs PrecodeStubs => GetContract(); public override IReJIT ReJIT => GetContract(); public override IStackWalk StackWalk => GetContract(); + public override IRuntimeInfo RuntimeInfo => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs index 9d351328daafe2..1f15838912670f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/ContractDescriptorParser.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Unicode; namespace Microsoft.Diagnostics.DataContractReader; @@ -37,6 +39,7 @@ public partial class ContractDescriptorParser [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(TypeDescriptor))] [JsonSerializable(typeof(FieldDescriptor))] [JsonSerializable(typeof(GlobalDescriptor))] @@ -65,12 +68,14 @@ public class ContractDescriptor public Dictionary? Globals { get; set; } + public Dictionary? GlobalStrings { 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}"; + return $"Version: {Version}, Baseline: {Baseline}, Contracts: {Contracts?.Count}, Types: {Types?.Count}, Globals: {Globals?.Count}, GlobalStrings: {GlobalStrings?.Count}"; } } @@ -92,9 +97,13 @@ public class FieldDescriptor [JsonConverter(typeof(GlobalDescriptorConverter))] public class GlobalDescriptor { - public string? Type { get; set; } - public ulong Value { get; set; } + [MemberNotNullWhen(true, nameof(NumericValue))] public bool Indirect { get; set; } + public string? Type { get; set; } + + // When the descriptor is indirect, NumericValue must be non-null to point to the actual data + public ulong? NumericValue { get; set; } + public string? StringValue { get; set; } } internal sealed class TypeDescriptorConverter : JsonConverter @@ -191,35 +200,38 @@ internal sealed class GlobalDescriptorConverter : JsonConverter _contracts = []; - private readonly IReadOnlyDictionary _globals = new Dictionary(); + private readonly IReadOnlyDictionary _globals = new Dictionary(); private readonly Dictionary _knownTypes = []; private readonly Dictionary _types = []; @@ -43,8 +43,7 @@ private readonly struct Configuration public override DataCache ProcessedData { get; } public delegate int ReadFromTargetDelegate(ulong address, Span bufferToFill); - public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, uint contextSize, Span bufferToFill); - public delegate int GetTargetPlatformDelegate(out int platform); + public delegate int GetTargetThreadContextDelegate(uint threadId, uint contextFlags, Span bufferToFill); /// /// Create a new target instance from a contract descriptor embedded in the target memory. @@ -52,17 +51,15 @@ private readonly struct Configuration /// The offset of the contract descriptor in the target memory /// A callback to read memory blocks at a given address from the target /// A callback to fetch a thread's context - /// A callback to fetch the target's platform /// The target object. /// If a target instance could be created, true; otherwise, false. public static bool TryCreate( ulong contractDescriptor, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatformDelegate getTargetPlatform, - out ContractDescriptorTarget? target) + [NotNullWhen(true)] out ContractDescriptorTarget? target) { - Reader reader = new Reader(readFromTarget, getThreadContext, getTargetPlatform); + Reader reader = new Reader(readFromTarget, getThreadContext); if (TryReadContractDescriptor( contractDescriptor, reader, @@ -85,7 +82,6 @@ public static bool TryCreate( /// The values for any global pointers specified in the contract descriptor. /// A callback to read memory blocks at a given address from the target /// A callback to fetch a thread's context - /// A callback to fetch the target's platform /// Whether the target is little-endian /// The size of a pointer in bytes in the target process. /// The target object. @@ -94,7 +90,6 @@ public static ContractDescriptorTarget Create( TargetPointer[] globalPointerValues, ReadFromTargetDelegate readFromTarget, GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatformDelegate getTargetPlatform, bool isLittleEndian, int pointerSize) { @@ -102,7 +97,7 @@ public static ContractDescriptorTarget Create( new Configuration { IsLittleEndian = isLittleEndian, PointerSize = pointerSize }, contractDescriptor, globalPointerValues, - new Reader(readFromTarget, getThreadContext, getTargetPlatform)); + new Reader(readFromTarget, getThreadContext)); } private ContractDescriptorTarget(Configuration config, ContractDescriptorParser.ContractDescriptor descriptor, TargetPointer[] pointerData, Reader reader) @@ -152,25 +147,43 @@ private ContractDescriptorTarget(Configuration config, ContractDescriptorParser. // Read globals and map indirect values to pointer data if (descriptor.Globals is not null) { - Dictionary globals = []; + Dictionary globalValues = new(descriptor.Globals.Count); foreach ((string name, ContractDescriptorParser.GlobalDescriptor global) in descriptor.Globals) { - ulong value = global.Value; if (global.Indirect) { - if (value >= (ulong)pointerData.Length) - throw new InvalidOperationException($"Invalid pointer data index {value}."); + if (global.NumericValue.Value >= (ulong)pointerData.Length) + throw new InvalidOperationException($"Invalid pointer data index {global.NumericValue.Value}."); - value = pointerData[value].Value; + globalValues[name] = new GlobalValue + { + NumericValue = pointerData[global.NumericValue.Value].Value, + StringValue = global.StringValue, + Type = global.Type + }; + } + else // direct + { + globalValues[name] = new GlobalValue + { + NumericValue = global.NumericValue, + StringValue = global.StringValue, + Type = global.Type + }; } - - globals[name] = (value, global.Type); } - _globals = globals; + _globals = globalValues.AsReadOnly(); } } + private struct GlobalValue + { + public ulong? NumericValue; + public string? StringValue; + public string? Type; + } + // See docs/design/datacontracts/contract-descriptor.md private static bool TryReadContractDescriptor( ulong address, @@ -263,19 +276,11 @@ private static DataType GetDataType(string type) public override int PointerSize => _config.PointerSize; public override bool IsLittleEndian => _config.IsLittleEndian; - public override CorDebugPlatform Platform - { - get - { - _reader.GetTargetPlatform(out int platform); - return (CorDebugPlatform)platform; - } - } public override bool TryGetThreadContext(ulong threadId, uint contextFlags, Span buffer) { // Underlying API only supports 32-bit thread IDs, mask off top 32 bits - int hr = _reader.GetThreadContext((uint)(threadId & uint.MaxValue), contextFlags, (uint)buffer.Length, buffer); + int hr = _reader.GetThreadContext((uint)(threadId & uint.MaxValue), contextFlags, buffer); return hr == 0; } @@ -496,6 +501,8 @@ public bool IsAlignedToPointerSize(ulong value) public override bool IsAlignedToPointerSize(TargetPointer pointer) => IsAligned(pointer.Value, _config.PointerSize); + #region reading globals + public override bool TryReadGlobal(string name, [NotNullWhen(true)] out T? value) => TryReadGlobal(name, out value, out _); @@ -503,12 +510,13 @@ public bool TryReadGlobal(string name, [NotNullWhen(true)] out T? value, out { value = null; type = null; - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!_globals.TryGetValue(name, out GlobalValue global) || global.NumericValue is null) { + // Not found or does not contain a numeric value return false; } type = global.Type; - value = T.CreateChecked(global.Value); + value = T.CreateChecked(global.NumericValue.Value); return true; } @@ -517,11 +525,10 @@ public override T ReadGlobal(string name) public T ReadGlobal(string name, out string? type) where T : struct, INumber { - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!TryReadGlobal(name, out T? value, out type)) throw new InvalidOperationException($"Failed to read global {typeof(T)} '{name}'."); - type = global.Type; - return T.CreateChecked(global.Value); + return value.Value; } public override bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out TargetPointer? value) @@ -530,12 +537,10 @@ public override bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out T public bool TryReadGlobalPointer(string name, [NotNullWhen(true)] out TargetPointer? value, out string? type) { value = null; - type = null; - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!TryReadGlobal(name, out ulong? innerValue, out type)) return false; - type = global.Type; - value = new TargetPointer(global.Value); + value = new TargetPointer(innerValue.Value); return true; } @@ -544,13 +549,42 @@ public override TargetPointer ReadGlobalPointer(string name) public TargetPointer ReadGlobalPointer(string name, out string? type) { - if (!_globals.TryGetValue(name, out (ulong Value, string? Type) global)) + if (!TryReadGlobalPointer(name, out TargetPointer? value, out type)) throw new InvalidOperationException($"Failed to read global pointer '{name}'."); + return value.Value; + } + + public override string ReadGlobalString(string name) + => ReadStringGlobal(name, out _); + + public string ReadStringGlobal(string name, out string? type) + { + if (!TryReadStringGlobal(name, out string? value, out type)) + throw new InvalidOperationException($"Failed to read string global '{name}'."); + + return value; + } + + public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value) + => TryReadStringGlobal(name, out value, out _); + + public bool TryReadStringGlobal(string name, [NotNullWhen(true)] out string? value, out string? type) + { + value = null; + type = null; + if (!_globals.TryGetValue(name, out GlobalValue global) || global.StringValue is null) + { + // Not found or does not contain a string value + return false; + } type = global.Type; - return new TargetPointer(global.Value); + value = global.StringValue; + return true; } + #endregion + public override TypeInfo GetTypeInfo(DataType type) { if (!_knownTypes.TryGetValue(type, out Target.TypeInfo typeInfo)) @@ -625,8 +659,7 @@ public void Clear() private readonly struct Reader( ReadFromTargetDelegate readFromTarget, - GetTargetThreadContextDelegate getThreadContext, - GetTargetPlatformDelegate getTargetPlatform) + GetTargetThreadContextDelegate getThreadContext) { public int ReadFromTarget(ulong address, Span buffer) { @@ -636,14 +669,9 @@ public int ReadFromTarget(ulong address, Span buffer) public int ReadFromTarget(ulong address, byte* buffer, uint bytesToRead) => readFromTarget(address, new Span(buffer, checked((int)bytesToRead))); - public int GetTargetPlatform(out int platform) - { - return getTargetPlatform(out platform); - } - - public int GetThreadContext(uint threadId, uint contextFlags, uint contextSize, Span buffer) + public int GetThreadContext(uint threadId, uint contextFlags, Span buffer) { - return getThreadContext(threadId, contextFlags, contextSize, buffer); + return getThreadContext(threadId, contextFlags, buffer); } } } diff --git a/src/native/managed/cdacreader/inc/cdac_reader.h b/src/native/managed/cdacreader/inc/cdac_reader.h index 9bf5ddb409090e..aa011df71487d7 100644 --- a/src/native/managed/cdacreader/inc/cdac_reader.h +++ b/src/native/managed/cdacreader/inc/cdac_reader.h @@ -13,14 +13,12 @@ extern "C" // descriptor: the address of the descriptor in the target process // read_from_target: a callback that reads memory from the target process // read_thread_context: a callback that reads the context of a thread in the target process -// get_platform: a callback that reads the platform of the target process // read_context: a context pointer that will be passed to callbacks // handle: returned opaque the handle to the reader. This should be passed to other functions in this API. int cdac_reader_init( uint64_t descriptor, int(*read_from_target)(uint64_t, uint8_t*, uint32_t, void*), int(*read_thread_context)(uint32_t, uint32_t, uint32_t, uint8_t*, void*), - int(*get_platform)(uint32_t*, void*), void* read_context, /*out*/ intptr_t* handle); diff --git a/src/native/managed/cdacreader/src/Entrypoints.cs b/src/native/managed/cdacreader/src/Entrypoints.cs index 015247efc0a9c4..db02e3531875fd 100644 --- a/src/native/managed/cdacreader/src/Entrypoints.cs +++ b/src/native/managed/cdacreader/src/Entrypoints.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Legacy; namespace Microsoft.Diagnostics.DataContractReader; @@ -16,7 +17,6 @@ private static unsafe int Init( ulong descriptor, delegate* unmanaged readFromTarget, delegate* unmanaged readThreadContext, - delegate* unmanaged getPlatform, void* readContext, IntPtr* handle) { @@ -30,18 +30,11 @@ private static unsafe int Init( return readFromTarget(address, bufferPtr, (uint)buffer.Length, readContext); } }, - (threadId, contextFlags, contextSize, buffer) => + (threadId, contextFlags, buffer) => { fixed (byte* bufferPtr = buffer) { - return readThreadContext(threadId, contextFlags, contextSize, bufferPtr, readContext); - } - }, - (out int platform) => - { - fixed (int* platformPtr = &platform) - { - return getPlatform(platformPtr, readContext); + return readThreadContext(threadId, contextFlags, (uint)buffer.Length, bufferPtr, readContext); } }, out ContractDescriptorTarget? target)) @@ -83,4 +76,74 @@ private static unsafe int CreateSosInterface(IntPtr handle, IntPtr legacyImplPtr *obj = ptr; return 0; } + + [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstanceWithFallback")] + private static unsafe int CLRDataCreateInstanceWithFallback(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface) + { + return CLRDataCreateInstanceImpl(pIID, pLegacyTarget, pLegacyImpl, iface); + } + + // Same export name and signature as DAC CLRDataCreateInstance in daccess.cpp + [UnmanagedCallersOnly(EntryPoint = "CLRDataCreateInstance")] + private static unsafe int CLRDataCreateInstance(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, void** iface) + { + return CLRDataCreateInstanceImpl(pIID, pLegacyTarget, IntPtr.Zero, iface); + } + + private static unsafe int CLRDataCreateInstanceImpl(Guid* pIID, IntPtr /*ICLRDataTarget*/ pLegacyTarget, IntPtr pLegacyImpl, void** iface) + { + if (pLegacyTarget == IntPtr.Zero || iface == null) + return HResults.E_INVALIDARG; + *iface = null; + + ComWrappers cw = new StrategyBasedComWrappers(); + object legacyTarget = cw.GetOrCreateObjectForComInstance(pLegacyTarget, CreateObjectFlags.None); + object? legacyImpl = pLegacyImpl != IntPtr.Zero ? + cw.GetOrCreateObjectForComInstance(pLegacyImpl, CreateObjectFlags.None) : null; + + ICLRDataTarget dataTarget = legacyTarget as ICLRDataTarget ?? throw new ArgumentException( + $"{nameof(pLegacyTarget)} does not implement {nameof(ICLRDataTarget)}", nameof(pLegacyTarget)); + ICLRContractLocator contractLocator = legacyTarget as ICLRContractLocator ?? throw new ArgumentException( + $"{nameof(pLegacyTarget)} does not implement {nameof(ICLRContractLocator)}", nameof(pLegacyTarget)); + + ulong contractAddress; + int hr = contractLocator.GetContractDescriptor(&contractAddress); + if (hr != 0) + { + throw new InvalidOperationException( + $"{nameof(ICLRContractLocator)} failed to fetch the contract descriptor with HRESULT: 0x{hr:x}."); + } + + if (!ContractDescriptorTarget.TryCreate( + contractAddress, + (address, buffer) => + { + fixed (byte* bufferPtr = buffer) + { + uint bytesRead; + return dataTarget.ReadVirtual(address, bufferPtr, (uint)buffer.Length, &bytesRead); + } + }, + (threadId, contextFlags, bufferToFill) => + { + fixed (byte* bufferPtr = bufferToFill) + { + return dataTarget.GetThreadContext(threadId, contextFlags, (uint)bufferToFill.Length, bufferPtr); + } + }, + out ContractDescriptorTarget? target)) + { + return -1; + } + + Legacy.SOSDacImpl impl = new(target, legacyImpl); + nint ccw = cw.GetOrCreateComInterfaceForObject(impl, CreateComInterfaceFlags.None); + Marshal.QueryInterface(ccw, *pIID, out nint ptrToIface); + *iface = (void*)ptrToIface; + + // Decrement reference count on ccw because QI incremented it + Marshal.Release(ccw); + + return 0; + } } diff --git a/src/native/managed/cdacreader/src/Legacy/ICLRData.cs b/src/native/managed/cdacreader/src/Legacy/ICLRData.cs index 5b33d833beb516..56748a4f08f89d 100644 --- a/src/native/managed/cdacreader/src/Legacy/ICLRData.cs +++ b/src/native/managed/cdacreader/src/Legacy/ICLRData.cs @@ -17,3 +17,49 @@ internal unsafe partial interface ICLRDataEnumMemoryRegions [PreserveSig] int EnumMemoryRegions(/*ICLRDataEnumMemoryRegionsCallback*/ void* callback, uint miniDumpFlags, /*CLRDataEnumMemoryFlags*/ int clrFlags); } + +[GeneratedComInterface] +[Guid("3e11ccee-d08b-43e5-af01-32717a64da03")] +internal unsafe partial interface ICLRDataTarget +{ + [PreserveSig] + int GetMachineType(uint* machineType); + + [PreserveSig] + int GetPointerSize(uint* pointerSize); + + [PreserveSig] + int GetImageBase([MarshalAs(UnmanagedType.LPWStr)] string imagePath, ulong* baseAddress); + + [PreserveSig] + int ReadVirtual(ulong address, byte* buffer, uint bytesRequested, uint* bytesRead); + + [PreserveSig] + int WriteVirtual(ulong address, byte* buffer, uint bytesRequested, uint* bytesWritten); + + [PreserveSig] + int GetTLSValue(uint threadID, uint index, ulong* value); + + [PreserveSig] + int SetTLSValue(uint threadID, uint index, ulong value); + + [PreserveSig] + int GetCurrentThreadID(uint* threadID); + + [PreserveSig] + int GetThreadContext(uint threadID, uint contextFlags, uint contextSize, byte* context); + + [PreserveSig] + int SetThreadContext(uint threadID, uint contextSize, byte* context); + + [PreserveSig] + int Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer); +} + +[GeneratedComInterface] +[Guid("17d5b8c6-34a9-407f-af4f-a930201d4e02")] +internal unsafe partial interface ICLRContractLocator +{ + [PreserveSig] + int GetContractDescriptor(ulong* contractAddress); +} diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index ff941454038e43..3b5d4de5972e96 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs @@ -22,7 +22,7 @@ internal class ContractDescriptorBuilder : MockMemorySpace.Builder private IReadOnlyCollection _contracts; private IDictionary _types; - private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> _globals; + private IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> _globals; private IReadOnlyCollection _indirectValues; public ContractDescriptorBuilder(TargetTestHelpers targetTestHelpers) @@ -51,12 +51,23 @@ public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ul 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.TypeName)).ToArray(); + _globals = globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, (string?)null, g.TypeName)).ToArray(); _indirectValues = null; return this; } - public ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? TypeName)> globals, IReadOnlyCollection indirectValues) + 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 ContractDescriptorBuilder SetGlobals(IReadOnlyCollection<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? TypeName)> globals, IReadOnlyCollection indirectValues) { if (_created) throw new InvalidOperationException("Context already created"); @@ -168,13 +179,6 @@ public bool TryCreateTarget([NotNullWhen(true)] out ContractDescriptorTarget? ta throw new InvalidOperationException("Context already created"); ulong contractDescriptorAddress = CreateDescriptorFragments(); MockMemorySpace.ReadContext context = GetReadContext(); - ContractDescriptorTarget.GetTargetPlatformDelegate getTargetPlatform = (out int platform) => - { - platform = TargetTestHelpers.Arch.Is64Bit ? - (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64 : - (int)Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_X86; - return 0; - }; - return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, getTargetPlatform, out target); + return ContractDescriptorTarget.TryCreate(contractDescriptorAddress, context.ReadFromTarget, null, out target); } } diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs index d17759a92c6aee..f9ba9241d41384 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorHelpers.cs @@ -119,14 +119,14 @@ public static string MakeTypesJson(IDictionary types) public static string MakeGlobalsJson(IEnumerable<(string Name, ulong Value, string? Type)> globals) { - return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, g.Type))); + return MakeGlobalsJson(globals.Select(g => (g.Name, (ulong?)g.Value, (uint?)null, (string?)null, g.Type))); } - public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? Type)> globals) + public static string MakeGlobalsJson(IEnumerable<(string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type)> globals) { return string.Join(',', globals.Select(FormatGlobal)); - static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? Type) global) + static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type) global) { if (global.Value is ulong value) { @@ -136,6 +136,10 @@ static string FormatGlobal((string Name, ulong? Value, uint? IndirectIndex, stri { return $"\"{global.Name}\": {FormatIndirect(index, global.Type)}"; } + else if (global.StringValue is string stringValue) + { + return $"\"{global.Name}\": {FormatString(stringValue, global.Type)}"; + } else { throw new InvalidOperationException("Global must have a value or indirect index"); @@ -150,6 +154,10 @@ static string FormatIndirect(uint value, string? type) { return type is null ? $"[{value}]" : $"[[{value}],\"{type}\"]"; } + static string FormatString(string value, string? type) + { + return type is null ? $"\"{value}\"" : $"[\"{value}\",\"{type}\"]"; + } } #endregion JSON formatting diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs index a74167437a240a..bb4b36774ff3c5 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/ParserTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Text.Json; using Xunit; @@ -130,6 +131,12 @@ public void ParsesGlobals() "globalTypedPtr": [[4], "uintptr"], "globalHex": "0x1234", "globalNegative": -2, + "globalTypedNegative": [-5, "int32"], + "globalString": "Hello", + "globalTypedString": ["World", "string"], + "globalStringSpecialChars" : "\"Hello World\"", + "globalIntLarge": 18446744073709551615, + "globalIntLargeNegative": -9223372036854775808, "globalStringyInt": "17", "globalStringyNegative": "-2", "globalNegativeHex": "-0xff", @@ -145,37 +152,93 @@ public void ParsesGlobals() Assert.Equal("empty", descriptor.Baseline); Assert.Empty(descriptor.Contracts); Assert.Empty(descriptor.Types); - Assert.Equal(13, descriptor.Globals.Count); - Assert.Equal((ulong)1, descriptor.Globals["globalInt"].Value); - Assert.False(descriptor.Globals["globalInt"].Indirect); - Assert.Equal((ulong)2, descriptor.Globals["globalPtr"].Value); - Assert.True(descriptor.Globals["globalPtr"].Indirect); - Assert.Equal((ulong)3, descriptor.Globals["globalTypedInt"].Value); - Assert.False(descriptor.Globals["globalTypedInt"].Indirect); + + Assert.Equal(19, descriptor.Globals.Count); + + Assert.Equal((ulong)1, descriptor.Globals["globalInt"].NumericValue); + Assert.Null(descriptor.Globals["globalInt"].StringValue); + AssertDirect(descriptor.Globals["globalInt"]); + + Assert.Equal((ulong)2, descriptor.Globals["globalPtr"].NumericValue); + Assert.Null(descriptor.Globals["globalPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalPtr"]); + + Assert.Equal((ulong)3, descriptor.Globals["globalTypedInt"].NumericValue); + Assert.Null(descriptor.Globals["globalTypedInt"].StringValue); + AssertDirect(descriptor.Globals["globalTypedInt"]); Assert.Equal("uint8", descriptor.Globals["globalTypedInt"].Type); - Assert.Equal((ulong)4, descriptor.Globals["globalTypedPtr"].Value); - Assert.True(descriptor.Globals["globalTypedPtr"].Indirect); + + Assert.Equal((ulong)4, descriptor.Globals["globalTypedPtr"].NumericValue); + Assert.Null(descriptor.Globals["globalTypedPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalTypedPtr"]); Assert.Equal("uintptr", descriptor.Globals["globalTypedPtr"].Type); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalHex"].Value); - Assert.False(descriptor.Globals["globalHex"].Indirect); - Assert.Equal((ulong)0xfffffffffffffffe, descriptor.Globals["globalNegative"].Value); - Assert.False(descriptor.Globals["globalNegative"].Indirect); - Assert.Equal((ulong)17, descriptor.Globals["globalStringyInt"].Value); - Assert.False(descriptor.Globals["globalStringyInt"].Indirect); - Assert.Equal((ulong)0xfffffffffffffffe, descriptor.Globals["globalStringyNegative"].Value); - Assert.False(descriptor.Globals["globalStringyNegative"].Indirect); - Assert.Equal((ulong)0xffffffffffffff01, descriptor.Globals["globalNegativeHex"].Value); - Assert.False(descriptor.Globals["globalNegativeHex"].Indirect); - Assert.Equal((ulong)0x123456789abcdef, descriptor.Globals["globalBigStringyInt"].Value); - Assert.False(descriptor.Globals["globalBigStringyInt"].Indirect); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalStringyPtr"].Value); - Assert.True(descriptor.Globals["globalStringyPtr"].Indirect); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalHex"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalHex"].StringValue); + AssertDirect(descriptor.Globals["globalHex"]); + + Assert.Equal(unchecked((ulong)-2), descriptor.Globals["globalNegative"].NumericValue); + Assert.Null(descriptor.Globals["globalNegative"].StringValue); + AssertDirect(descriptor.Globals["globalNegative"]); + + Assert.Equal(unchecked((ulong)-5), descriptor.Globals["globalTypedNegative"].NumericValue); + Assert.Null(descriptor.Globals["globalTypedNegative"].StringValue); + AssertDirect(descriptor.Globals["globalTypedNegative"]); + Assert.Equal("int32", descriptor.Globals["globalTypedNegative"].Type); + + Assert.Equal("Hello", descriptor.Globals["globalString"].StringValue); + AssertDirect(descriptor.Globals["globalString"]); + + Assert.Equal("World", descriptor.Globals["globalTypedString"].StringValue); + AssertDirect(descriptor.Globals["globalTypedString"]); + Assert.Equal("string", descriptor.Globals["globalTypedString"].Type); + + Assert.Equal("\"Hello World\"", descriptor.Globals["globalStringSpecialChars"].StringValue); + AssertDirect(descriptor.Globals["globalStringSpecialChars"]); + + Assert.Equal(ulong.MaxValue, descriptor.Globals["globalIntLarge"].NumericValue); + Assert.Null(descriptor.Globals["globalIntLarge"].StringValue); + AssertDirect(descriptor.Globals["globalIntLarge"]); + + Assert.Equal(unchecked((ulong)long.MinValue), descriptor.Globals["globalIntLargeNegative"].NumericValue); + Assert.Null(descriptor.Globals["globalIntLargeNegative"].StringValue); + AssertDirect(descriptor.Globals["globalIntLargeNegative"]); + + Assert.Equal((ulong)17, descriptor.Globals["globalStringyInt"].NumericValue); + Assert.Equal("17", descriptor.Globals["globalStringyInt"].StringValue); + AssertDirect(descriptor.Globals["globalStringyInt"]); + + Assert.Equal(unchecked((ulong)-2), descriptor.Globals["globalStringyNegative"].NumericValue); + Assert.Equal("-2", descriptor.Globals["globalStringyNegative"].StringValue); + AssertDirect(descriptor.Globals["globalStringyNegative"]); + + Assert.Equal(unchecked((ulong)-0xff), descriptor.Globals["globalNegativeHex"].NumericValue); + Assert.Equal("-0xff", descriptor.Globals["globalNegativeHex"].StringValue); + AssertDirect(descriptor.Globals["globalNegativeHex"]); + + Assert.Equal((ulong)0x123456789abcdef, descriptor.Globals["globalBigStringyInt"].NumericValue); + Assert.Equal("0x123456789abcdef", descriptor.Globals["globalBigStringyInt"].StringValue); + AssertDirect(descriptor.Globals["globalBigStringyInt"]); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalStringyPtr"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalStringyPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalStringyPtr"]); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyInt"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalTypedStringyInt"].StringValue); + AssertDirect(descriptor.Globals["globalTypedStringyInt"]); Assert.Equal("int", descriptor.Globals["globalTypedStringyInt"].Type); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyInt"].Value); - Assert.False(descriptor.Globals["globalTypedStringyInt"].Indirect); + + Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyPtr"].NumericValue); + Assert.Equal("0x1234", descriptor.Globals["globalTypedStringyPtr"].StringValue); + AssertIndirect(descriptor.Globals["globalTypedStringyPtr"]); Assert.Equal("int", descriptor.Globals["globalTypedStringyPtr"].Type); - Assert.Equal((ulong)0x1234, descriptor.Globals["globalTypedStringyPtr"].Value); - Assert.True(descriptor.Globals["globalTypedStringyPtr"].Indirect); + + void AssertIndirect(ContractDescriptorParser.GlobalDescriptor descriptor) + => Assert.True(descriptor.Indirect); + + void AssertDirect(ContractDescriptorParser.GlobalDescriptor descriptor) + => Assert.False(descriptor.Indirect); } [Fact] diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs index 2c7cc3e5d454a5..6ba88e9246e6e3 100644 --- a/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs +++ b/src/native/managed/cdacreader/tests/ContractDescriptor/TargetTests.cs @@ -122,9 +122,42 @@ public void ReadIndirectGlobalValue(MockTarget.Architecture arch) ValidateGlobals(target, expected); - static (string Name, ulong? Value, uint? IndirectIndex, string? Type) MakeGlobalToIndirect((string Name, ulong Value, string? Type) global, int index) + static (string Name, ulong? Value, uint? IndirectIndex, string? StringValue, string? Type) MakeGlobalToIndirect((string Name, ulong Value, string? Type) global, int index) { - return (global.Name, null, (uint?)index, global.Type); + return (global.Name, null, (uint?)index, null, global.Type); + } + } + + private static readonly (string Name, string Value, ulong? NumericValue)[] GlobalStringyValues = + [ + ("value", "testString", null), + ("emptyString", "", null), + ("specialChars", "string with spaces & special chars ✓", null), + ("longString", new string('a', 1024), null), + ("hexString", "0x1234", 0x1234), + ("decimalString", "123456", 123456), + ("negativeString", "-1234", unchecked((ulong)-1234)), + ("negativeHexString", "-0x1234", unchecked((ulong)-0x1234)), + ]; + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ReadGlobalStringyValues(MockTarget.Architecture arch) + { + TargetTestHelpers targetTestHelpers = new(arch); + ContractDescriptorBuilder builder = new(targetTestHelpers); + builder.SetTypes(new Dictionary()) + .SetContracts([]) + .SetGlobals(GlobalStringyValues.Select(MakeGlobalsToStrings).ToArray()); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + ValidateGlobalStrings(target, GlobalStringyValues); + + static (string Name, ulong? Value, string? StringValue, string? Type) MakeGlobalsToStrings((string Name, string Value, ulong? NumericValue) global) + { + return (global.Name, null, global.Value, "string"); } } @@ -252,4 +285,56 @@ void AssertEqualsWithCallerInfo(T expected, T actual) Assert.True((expected is null && actual is null) || expected.Equals(actual), $"Expected: {expected}. Actual: {actual}. [test case: {caller} in {filePath}:{lineNumber}]"); } } + + private static void ValidateGlobalStrings( + ContractDescriptorTarget target, + (string Name, string Value, ulong? NumericValue)[] globals, + [CallerMemberName] string caller = "", + [CallerFilePath] string filePath = "", + [CallerLineNumber] int lineNumber = 0) + { + foreach (var (name, value, numericValue) in globals) + { + string actualString; + + Assert.True(target.TryReadGlobalString(name, out actualString)); + AssertEqualsWithCallerInfo(value, actualString); + + actualString = target.ReadGlobalString(name); + AssertEqualsWithCallerInfo(value, actualString); + + if (numericValue != null) + { + ulong? actualNumericValue; + + Assert.True(target.TryReadGlobal(name, out actualNumericValue)); + AssertEqualsWithCallerInfo(numericValue.Value, actualNumericValue.Value); + + actualNumericValue = target.ReadGlobal(name); + AssertEqualsWithCallerInfo(numericValue.Value, actualNumericValue.Value); + + TargetPointer? actualPointer; + + Assert.True(target.TryReadGlobalPointer(name, out actualPointer)); + AssertEqualsWithCallerInfo(numericValue.Value, actualPointer.Value.Value); + + actualPointer = target.ReadGlobalPointer(name); + AssertEqualsWithCallerInfo(numericValue.Value, actualPointer.Value.Value); + } + else + { + // if there is no numeric value, assert that reading as numeric fails + Assert.False(target.TryReadGlobal(name, out ulong? _)); + Assert.ThrowsAny(() => target.ReadGlobal(name)); + Assert.False(target.TryReadGlobalPointer(name, out TargetPointer? _)); + Assert.ThrowsAny(() => target.ReadGlobalPointer(name)); + } + } + + void AssertEqualsWithCallerInfo(T expected, T actual) + { + Assert.True((expected is null && actual is null) || expected.Equals(actual), $"Expected: {expected}. Actual: {actual}. [test case: {caller} in {filePath}:{lineNumber}]"); + } + } + } diff --git a/src/native/managed/cdacreader/tests/RuntimeInfoTests.cs b/src/native/managed/cdacreader/tests/RuntimeInfoTests.cs new file mode 100644 index 00000000000000..01130068d7001e --- /dev/null +++ b/src/native/managed/cdacreader/tests/RuntimeInfoTests.cs @@ -0,0 +1,102 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class RuntimeInfoTests +{ + internal static Target CreateTarget( + MockTarget.Architecture arch, + (string Name, string Value)[] globalStrings) + { + MockMemorySpace.Builder builder = new MockMemorySpace.Builder(new TargetTestHelpers(arch)); + TestPlaceholderTarget target = new TestPlaceholderTarget(arch, builder.GetReadContext().ReadFromTarget, [], [], globalStrings); + + IContractFactory runtimeInfoFactory = new RuntimeInfoFactory(); + + ContractRegistry reg = Mock.Of( + c => c.RuntimeInfo == runtimeInfoFactory.CreateContract(target, 1)); + target.SetContracts(reg); + return target; + } + + public static IEnumerable StdArchAllTargetArchitectures() + { + foreach(object[] arr in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + + foreach(RuntimeInfoArchitecture targetArch in (RuntimeInfoArchitecture[])Enum.GetValues(typeof(RuntimeInfoArchitecture))) + { + // Skip Unknown architecture + if (targetArch == RuntimeInfoArchitecture.Unknown) + continue; + + yield return new object[] { arch, targetArch.ToString().ToLowerInvariant(), targetArch }; + } + + yield return new object[] { arch, "notATargetArch", RuntimeInfoArchitecture.Unknown }; + } + } + + [Theory] + [MemberData(nameof(StdArchAllTargetArchitectures))] + public void GetTargetArchitectureTest( + MockTarget.Architecture arch, + string architecture, + RuntimeInfoArchitecture expectedArchitecture) + { + var target = CreateTarget(arch, [(Constants.Globals.Architecture, architecture)]); + + // TEST + + var runtimeInfo = target.Contracts.RuntimeInfo; + Assert.NotNull(runtimeInfo); + + var actualArchitecture = runtimeInfo.GetTargetArchitecture(); + Assert.Equal(expectedArchitecture, actualArchitecture); + } + + public static IEnumerable StdArchAllTargetOS() + { + foreach(object[] arr in new MockTarget.StdArch()) + { + MockTarget.Architecture arch = (MockTarget.Architecture)arr[0]; + + foreach(RuntimeInfoOperatingSystem targetArch in (RuntimeInfoOperatingSystem[])Enum.GetValues(typeof(RuntimeInfoOperatingSystem))) + { + // Skip Unknown architecture + if (targetArch == RuntimeInfoOperatingSystem.Unknown) + continue; + + yield return new object[] { arch, targetArch.ToString().ToLowerInvariant(), targetArch }; + } + + yield return new object[] { arch, "notAnOperatingSystem", RuntimeInfoOperatingSystem.Unknown }; + } + } + + [Theory] + [MemberData(nameof(StdArchAllTargetOS))] + public void GetTargetOperatingSystemTest( + MockTarget.Architecture arch, + string os, + RuntimeInfoOperatingSystem expectedOS) + { + var target = CreateTarget(arch, [(Constants.Globals.OperatingSystem, os)]); + + // TEST + + var runtimeInfo = target.Contracts.RuntimeInfo; + Assert.NotNull(runtimeInfo); + + var actualArchitecture = runtimeInfo.GetTargetOperatingSystem(); + Assert.Equal(expectedOS, actualArchitecture); + } +} diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 6a5ca9328f39ee..d61e862d2ed7a4 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -20,21 +20,22 @@ internal class TestPlaceholderTarget : Target private readonly Target.IDataCache _dataCache; private readonly Dictionary _typeInfoCache; private readonly (string Name, ulong Value)[] _globals; + private readonly (string Name, string Value)[] _globalStrings; internal delegate int ReadFromTargetDelegate(ulong address, Span buffer); private readonly ReadFromTargetDelegate _dataReader; - public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null) + public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null) { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; - Platform = Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64; _contractRegistry = new Mock().Object; _dataCache = new DefaultDataCache(this); _typeInfoCache = types ?? []; _dataReader = reader; _globals = globals ?? []; + _globalStrings = globalStrings ?? []; } internal void SetContracts(ContractRegistry contracts) @@ -44,7 +45,6 @@ internal void SetContracts(ContractRegistry contracts) public override int PointerSize { get; } public override bool IsLittleEndian { get; } - public override CorDebugPlatform Platform { get; } public override bool IsAlignedToPointerSize(TargetPointer pointer) { @@ -132,6 +132,33 @@ public override T ReadGlobal(string name) throw new NotImplementedException(); } + public override string ReadGlobalString(string name) + { + if (TryReadGlobalString(name, out string? value)) + { + return value; + } + + throw new NotImplementedException(); + } + + public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out string? value) + { + value = null; + + // first check global strings + foreach (var global in _globalStrings) + { + if (global.Name == name) + { + value = global.Value; + return true; + } + } + + return false; + } + public override T Read(ulong address) => DefaultRead(address); #region subclass reader helpers diff --git a/src/tools/StressLogAnalyzer/src/Program.cs b/src/tools/StressLogAnalyzer/src/Program.cs index e475d0547b2a14..00b4105e1d0f3b 100644 --- a/src/tools/StressLogAnalyzer/src/Program.cs +++ b/src/tools/StressLogAnalyzer/src/Program.cs @@ -492,8 +492,7 @@ ContractDescriptorTarget CreateTarget() => ContractDescriptorTarget.Create( GetDescriptor(contractVersion), [TargetPointer.Null, new TargetPointer(header->memoryBase + (nuint)((byte*)&header->moduleTable - (byte*)header))], (address, buffer) => ReadFromMemoryMappedLog(address, buffer, header), - (threadId, contextFlags, contextSize, bufferToFill) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetThreadContext implementation"), - (out platform) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetPlatform implementation"), + (threadId, contextFlags, bufferToFill) => throw new NotImplementedException("StressLogAnalyzer does not provide GetTargetThreadContext implementation"), true, nuint.Size); }