diff --git a/docs/design/datacontracts/contract-descriptor.md b/docs/design/datacontracts/contract-descriptor.md index fbd58eb33eb9a5..b99150c78becdd 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": "windows-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..06b3fa2bfc7f60 100644 --- a/docs/design/datacontracts/data_descriptor.md +++ b/docs/design/datacontracts/data_descriptor.md @@ -212,11 +212,11 @@ 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": VALUE | [ int ] ` the value of the global value, or an offset in an auxiliary array containing the value. + +The `VALUE` may be either a number of string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. +Numeric constants must be within the range of the type of the global value. -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**: @@ -225,7 +225,8 @@ The global values will be in a dictionary, with each key being the name of a glo * `[VALUE | [int], "type name"]` the type and value of a global * `VALUE | [int]` just the value of a global -As in the regular format, `VALUE` is a numeric constant or a string containing an integer constant. +`VALUE` may be either a number of string. JSON numeric constants are always parsed as numbers. JSON strings are always parsed as strings and may additionally parse as a hex (with prefix `0x` or `0X`) or decimal number. +Numeric constants must be within the range of the type of the global value. Note that a two element array is unambiguously "type and value", whereas a one-element array is unambiguously "indirect value". @@ -288,7 +289,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 +309,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 +334,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/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index c4d0aa3d42b645..ab5dd10aa55740 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -51,6 +51,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 +65,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 @@ -71,6 +78,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(#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 +93,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 +133,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 +216,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesStart; uint32_t GlobalPointersStart; + uint32_t GlobalStringValuesStart; uint32_t NamesPoolStart; uint32_t TypeCount; @@ -204,6 +224,7 @@ struct BinaryBlobDataDescriptor uint32_t GlobalLiteralValuesCount; uint32_t GlobalPointerValuesCount; + uint32_t GlobalStringValuesCount; uint32_t NamesPoolCount; @@ -211,6 +232,7 @@ struct BinaryBlobDataDescriptor uint8_t FieldSpecSize; uint8_t GlobalLiteralSpecSize; uint8_t GlobalPointerSpecSize; + uint8_t GlobalStringSpecSize; } Directory; uint32_t PlatformFlags; uint32_t BaselineName; @@ -218,6 +240,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 +265,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 +313,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" #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..c6417f7f3a79bf 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,9 @@ CDAC_TYPE_END(CalleeSavedRegisters) CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() + +CDAC_GLOBAL_STRING(Architecture, TEST) + CDAC_GLOBAL_POINTER(AppDomain, &AppDomain::m_pTheAppDomain) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) @@ -831,4 +837,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/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/Target.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index a9a73560c8b9b7..f4023a8ffa53e6 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -119,6 +119,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 TryReadStringGlobal(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 ReadStringGlobal(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/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 = []; @@ -152,25 +152,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, @@ -496,6 +514,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 +523,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 +538,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 +550,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 +562,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 ReadStringGlobal(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 TryReadStringGlobal(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)) diff --git a/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs b/src/native/managed/cdacreader/tests/ContractDescriptor/ContractDescriptorBuilder.cs index ff941454038e43..51798d57f1b8d7 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"); @@ -104,7 +115,7 @@ private string MakeContractsJson() "baseline": "empty", "contracts": { {{interpolatedContracts}} }, "types": { {{metadataTypesJson}} }, - "globals": { {{metadataGlobalsJson}} } + "globals": { {{metadataGlobalsJson}} }, } """); MockMemorySpace.HeapFragment json = new() 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..6a231f19dbc766 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,11 @@ public void ParsesGlobals() "globalTypedPtr": [[4], "uintptr"], "globalHex": "0x1234", "globalNegative": -2, + "globalTypedNegative": [-5, "int32"], + "globalString": "Hello", + "globalTypedString": ["World", "string"], + "globalIntLarge": 18446744073709551615, + "globalIntLargeNegative": -9223372036854775808, "globalStringyInt": "17", "globalStringyNegative": "-2", "globalNegativeHex": "-0xff", @@ -145,37 +151,90 @@ 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(18, 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(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..01b5c1c5a3309d 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.TryReadStringGlobal(name, out actualString)); + AssertEqualsWithCallerInfo(value, actualString); + + actualString = target.ReadStringGlobal(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/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 6a5ca9328f39ee..d2fd334dc0859a 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -20,12 +20,13 @@ 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; @@ -35,6 +36,7 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat _typeInfoCache = types ?? []; _dataReader = reader; _globals = globals ?? []; + _globalStrings = globalStrings ?? []; } internal void SetContracts(ContractRegistry contracts) @@ -132,6 +134,33 @@ public override T ReadGlobal(string name) throw new NotImplementedException(); } + public override string ReadStringGlobal(string name) + { + if (TryReadStringGlobal(name, out string? value)) + { + return value; + } + + throw new NotImplementedException(); + } + + public override bool TryReadStringGlobal(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