From 5174f829d7326a7a166b0afb600de532981bec4b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:22:48 +0000 Subject: [PATCH 1/5] Initial plan From e44c5305c7e9403396cb87a1a3900ec292e12d95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:03:15 +0000 Subject: [PATCH 2/5] Implement ISOSDacInterface::GetObjectClassName in SOSDacImpl.cs with unit and dump tests Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../SOSDacImpl.cs | 72 ++++++++++++++++++- .../DumpTests/RuntimeTypeSystemDumpTests.cs | 40 +++++++++++ src/native/managed/cdac/tests/ObjectTests.cs | 67 +++++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index f1ee4780f5968e..144d3f467e65df 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2609,7 +2609,77 @@ int ISOSDacInterface.GetNestedExceptionData(ClrDataAddress exception, ClrDataAdd } int ISOSDacInterface.GetObjectClassName(ClrDataAddress obj, uint count, char* className, uint* pNeeded) - => _legacyImpl is not null ? _legacyImpl.GetObjectClassName(obj, count, className, pNeeded) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if (obj == 0) + throw new ArgumentException(); + + Contracts.IObject objectContract = _target.Contracts.Object; + Contracts.IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem; + Contracts.ILoader loader = _target.Contracts.Loader; + + TargetPointer mt = objectContract.GetMethodTableAddress(obj.ToTargetPointer(_target)); + Contracts.TypeHandle typeHandle = typeSystemContract.GetTypeHandle(mt); + + if (typeSystemContract.IsFreeObjectMethodTable(typeHandle)) + { + OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, "Free"); + } + else + { + TargetPointer modulePointer = typeSystemContract.GetModule(typeHandle); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer); + if (!loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _)) + { + OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, ""); + } + else + { + System.Text.StringBuilder classNameBuilder = new(); + try + { + TypeNameBuilder.AppendType(_target, classNameBuilder, typeHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); + } + catch + { + string? fallbackName = _target.Contracts.DacStreams.StringFromEEAddress(mt); + if (fallbackName != null) + { + classNameBuilder.Clear(); + classNameBuilder.Append(fallbackName); + } + } + OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, classNameBuilder.ToString()); + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + char[] classNameLocal = new char[count]; + uint neededLocal; + int hrLocal; + fixed (char* ptr = classNameLocal) + { + hrLocal = _legacyImpl.GetObjectClassName(obj, count, ptr, &neededLocal); + } + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(pNeeded == null || *pNeeded == neededLocal); + Debug.Assert(className == null || new ReadOnlySpan(classNameLocal, 0, (int)neededLocal - 1).SequenceEqual(new string(className))); + } + } +#endif + return hr; + } int ISOSDacInterface.GetObjectData(ClrDataAddress objAddr, DacpObjectData* data) { diff --git a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs index dc71afdb62002d..3cb35c06d05d77 100644 --- a/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs @@ -253,4 +253,44 @@ public void RuntimeTypeSystem_ObjectMethodTableHasIntroducedMethods(TestConfigur Assert.Equal(0x06000000u, token & 0xFF000000u); } } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void RuntimeTypeSystem_ObjectMethodTableHasLoadedModule(TestConfiguration config) + { + InitializeDumpTest(config); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + ILoader loader = Target.Contracts.Loader; + + TargetPointer objectMTGlobal = Target.ReadGlobalPointer("ObjectMethodTable"); + TargetPointer objectMT = Target.ReadPointer(objectMTGlobal); + TypeHandle handle = rts.GetTypeHandle(objectMT); + + TargetPointer modulePointer = rts.GetModule(handle); + Assert.NotEqual(TargetPointer.Null, modulePointer); + + ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer); + bool isLoaded = loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _); + Assert.True(isLoaded, "System.Object's module should have loaded image contents"); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void RuntimeTypeSystem_StringMethodTableHasLoadedModule(TestConfiguration config) + { + InitializeDumpTest(config); + IRuntimeTypeSystem rts = Target.Contracts.RuntimeTypeSystem; + ILoader loader = Target.Contracts.Loader; + + TargetPointer stringMTGlobal = Target.ReadGlobalPointer("StringMethodTable"); + TargetPointer stringMT = Target.ReadPointer(stringMTGlobal); + TypeHandle handle = rts.GetTypeHandle(stringMT); + + TargetPointer modulePointer = rts.GetModule(handle); + Assert.NotEqual(TargetPointer.Null, modulePointer); + + ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer); + bool isLoaded = loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _); + Assert.True(isLoaded, "System.String's module should have loaded image contents"); + } } diff --git a/src/native/managed/cdac/tests/ObjectTests.cs b/src/native/managed/cdac/tests/ObjectTests.cs index 7ccc5960a96507..83a2e7115f237c 100644 --- a/src/native/managed/cdac/tests/ObjectTests.cs +++ b/src/native/managed/cdac/tests/ObjectTests.cs @@ -3,6 +3,7 @@ using System; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Legacy; using Moq; using Xunit; @@ -153,4 +154,70 @@ public void ComData(MockTarget.Architecture arch) } }); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetObjectClassName_FreeObject(MockTarget.Architecture arch) + { + TargetPointer TestObjectAddress = default; + ObjectContractHelper(arch, + (objectBuilder) => + { + TestObjectAddress = objectBuilder.AddObject(objectBuilder.RTSBuilder.FreeObjectMethodTableAddress); + }, + (target) => + { + ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null); + char[] buffer = new char[256]; + uint needed; + int hr; + fixed (char* ptr = buffer) + { + hr = sosDac.GetObjectClassName(new ClrDataAddress(TestObjectAddress.Value), (uint)buffer.Length, ptr, &needed); + } + Assert.Equal(HResults.S_OK, hr); + Assert.Equal((uint)"Free".Length + 1, needed); + Assert.Equal("Free", new string(buffer, 0, (int)needed - 1)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetObjectClassName_ZeroAddress(MockTarget.Architecture arch) + { + ObjectContractHelper(arch, + (objectBuilder) => { }, + (target) => + { + ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null); + char[] buffer = new char[256]; + uint needed; + int hr; + fixed (char* ptr = buffer) + { + hr = sosDac.GetObjectClassName(default, (uint)buffer.Length, ptr, &needed); + } + Assert.NotEqual(HResults.S_OK, hr); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetObjectClassName_NullBufferReturnsNeededSize(MockTarget.Architecture arch) + { + TargetPointer TestObjectAddress = default; + ObjectContractHelper(arch, + (objectBuilder) => + { + TestObjectAddress = objectBuilder.AddObject(objectBuilder.RTSBuilder.FreeObjectMethodTableAddress); + }, + (target) => + { + ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null); + uint needed; + int hr = sosDac.GetObjectClassName(new ClrDataAddress(TestObjectAddress.Value), 0, null, &needed); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal((uint)"Free".Length + 1, needed); + }); + } } From 0239f9fc4e8582522a218c00f12cb64536315e28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:41:40 +0000 Subject: [PATCH 3/5] Address review feedback: rename variables, remove free object special case, update tests Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../SOSDacImpl.cs | 41 ++++------ src/native/managed/cdac/tests/ObjectTests.cs | 78 +++++++++++++++---- 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 144d3f467e65df..271f7a1141a9da 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2616,43 +2616,36 @@ int ISOSDacInterface.GetObjectClassName(ClrDataAddress obj, uint count, char* cl if (obj == 0) throw new ArgumentException(); - Contracts.IObject objectContract = _target.Contracts.Object; - Contracts.IRuntimeTypeSystem typeSystemContract = _target.Contracts.RuntimeTypeSystem; + Contracts.IObject @object = _target.Contracts.Object; + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; Contracts.ILoader loader = _target.Contracts.Loader; - TargetPointer mt = objectContract.GetMethodTableAddress(obj.ToTargetPointer(_target)); - Contracts.TypeHandle typeHandle = typeSystemContract.GetTypeHandle(mt); + TargetPointer mt = @object.GetMethodTableAddress(obj.ToTargetPointer(_target)); + Contracts.TypeHandle typeHandle = rts.GetTypeHandle(mt); - if (typeSystemContract.IsFreeObjectMethodTable(typeHandle)) + TargetPointer modulePointer = rts.GetModule(typeHandle); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer); + if (!loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _)) { - OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, "Free"); + OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, ""); } else { - TargetPointer modulePointer = typeSystemContract.GetModule(typeHandle); - Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer); - if (!loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _)) + System.Text.StringBuilder classNameBuilder = new(); + try { - OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, ""); + TypeNameBuilder.AppendType(_target, classNameBuilder, typeHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); } - else + catch { - System.Text.StringBuilder classNameBuilder = new(); - try - { - TypeNameBuilder.AppendType(_target, classNameBuilder, typeHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst); - } - catch + string? fallbackName = _target.Contracts.DacStreams.StringFromEEAddress(mt); + if (fallbackName != null) { - string? fallbackName = _target.Contracts.DacStreams.StringFromEEAddress(mt); - if (fallbackName != null) - { - classNameBuilder.Clear(); - classNameBuilder.Append(fallbackName); - } + classNameBuilder.Clear(); + classNameBuilder.Append(fallbackName); } - OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, classNameBuilder.ToString()); } + OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, classNameBuilder.ToString()); } } catch (System.Exception ex) diff --git a/src/native/managed/cdac/tests/ObjectTests.cs b/src/native/managed/cdac/tests/ObjectTests.cs index 83a2e7115f237c..ab613f2324f0f8 100644 --- a/src/native/managed/cdac/tests/ObjectTests.cs +++ b/src/native/managed/cdac/tests/ObjectTests.cs @@ -157,14 +157,10 @@ public void ComData(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetObjectClassName_FreeObject(MockTarget.Architecture arch) + public void GetObjectClassName_ZeroAddress(MockTarget.Architecture arch) { - TargetPointer TestObjectAddress = default; ObjectContractHelper(arch, - (objectBuilder) => - { - TestObjectAddress = objectBuilder.AddObject(objectBuilder.RTSBuilder.FreeObjectMethodTableAddress); - }, + (objectBuilder) => { }, (target) => { ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null); @@ -173,31 +169,58 @@ public void GetObjectClassName_FreeObject(MockTarget.Architecture arch) int hr; fixed (char* ptr = buffer) { - hr = sosDac.GetObjectClassName(new ClrDataAddress(TestObjectAddress.Value), (uint)buffer.Length, ptr, &needed); + hr = sosDac.GetObjectClassName(default, (uint)buffer.Length, ptr, &needed); } - Assert.Equal(HResults.S_OK, hr); - Assert.Equal((uint)"Free".Length + 1, needed); - Assert.Equal("Free", new string(buffer, 0, (int)needed - 1)); + Assert.NotEqual(HResults.S_OK, hr); }); } [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetObjectClassName_ZeroAddress(MockTarget.Architecture arch) + public void GetObjectClassName_UnloadedModule(MockTarget.Architecture arch) { + TargetPointer TestObjectAddress = default; + TargetPointer TestMethodTableAddress = default; ObjectContractHelper(arch, - (objectBuilder) => { }, + (objectBuilder) => + { + TargetPointer eeClass = objectBuilder.RTSBuilder.AddEEClass("TestClass", attr: 0, numMethods: 0, numNonVirtualSlots: 0); + TestMethodTableAddress = objectBuilder.RTSBuilder.AddMethodTable("TestClass", + mtflags: default, mtflags2: default, baseSize: objectBuilder.Builder.TargetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: 0); + objectBuilder.RTSBuilder.SetEEClassAndCanonMTRefs(eeClass, TestMethodTableAddress); + TestObjectAddress = objectBuilder.AddObject(TestMethodTableAddress); + }, (target) => { + var mockRts = new Mock(); + TypeHandle handle = new TypeHandle(TestMethodTableAddress); + mockRts.Setup(r => r.GetTypeHandle(TestMethodTableAddress)).Returns(handle); + mockRts.Setup(r => r.GetModule(handle)).Returns(TargetPointer.Null); + + var mockLoader = new Mock(); + mockLoader.Setup(l => l.GetModuleHandleFromModulePtr(It.IsAny())).Returns(default(Contracts.ModuleHandle)); + mockLoader.Setup(l => l.TryGetLoadedImageContents(It.IsAny(), out It.Ref.IsAny, out It.Ref.IsAny, out It.Ref.IsAny)).Returns(false); + + var mockObject = new Mock(); + mockObject.Setup(o => o.GetMethodTableAddress(It.IsAny())).Returns(TestMethodTableAddress); + + ((TestPlaceholderTarget)target).SetContracts(Mock.Of( + c => c.Object == mockObject.Object + && c.RuntimeTypeSystem == mockRts.Object + && c.Loader == mockLoader.Object)); + ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null); char[] buffer = new char[256]; uint needed; int hr; fixed (char* ptr = buffer) { - hr = sosDac.GetObjectClassName(default, (uint)buffer.Length, ptr, &needed); + hr = sosDac.GetObjectClassName(new ClrDataAddress(TestObjectAddress.Value), (uint)buffer.Length, ptr, &needed); } - Assert.NotEqual(HResults.S_OK, hr); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal((uint)"".Length + 1, needed); + Assert.Equal("", new string(buffer, 0, (int)needed - 1)); }); } @@ -206,18 +229,41 @@ public void GetObjectClassName_ZeroAddress(MockTarget.Architecture arch) public void GetObjectClassName_NullBufferReturnsNeededSize(MockTarget.Architecture arch) { TargetPointer TestObjectAddress = default; + TargetPointer TestMethodTableAddress = default; ObjectContractHelper(arch, (objectBuilder) => { - TestObjectAddress = objectBuilder.AddObject(objectBuilder.RTSBuilder.FreeObjectMethodTableAddress); + TargetPointer eeClass = objectBuilder.RTSBuilder.AddEEClass("TestClass", attr: 0, numMethods: 0, numNonVirtualSlots: 0); + TestMethodTableAddress = objectBuilder.RTSBuilder.AddMethodTable("TestClass", + mtflags: default, mtflags2: default, baseSize: objectBuilder.Builder.TargetTestHelpers.ObjectBaseSize, + module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: 0); + objectBuilder.RTSBuilder.SetEEClassAndCanonMTRefs(eeClass, TestMethodTableAddress); + TestObjectAddress = objectBuilder.AddObject(TestMethodTableAddress); }, (target) => { + var mockRts = new Mock(); + TypeHandle handle = new TypeHandle(TestMethodTableAddress); + mockRts.Setup(r => r.GetTypeHandle(TestMethodTableAddress)).Returns(handle); + mockRts.Setup(r => r.GetModule(handle)).Returns(TargetPointer.Null); + + var mockLoader = new Mock(); + mockLoader.Setup(l => l.GetModuleHandleFromModulePtr(It.IsAny())).Returns(default(Contracts.ModuleHandle)); + mockLoader.Setup(l => l.TryGetLoadedImageContents(It.IsAny(), out It.Ref.IsAny, out It.Ref.IsAny, out It.Ref.IsAny)).Returns(false); + + var mockObject = new Mock(); + mockObject.Setup(o => o.GetMethodTableAddress(It.IsAny())).Returns(TestMethodTableAddress); + + ((TestPlaceholderTarget)target).SetContracts(Mock.Of( + c => c.Object == mockObject.Object + && c.RuntimeTypeSystem == mockRts.Object + && c.Loader == mockLoader.Object)); + ISOSDacInterface sosDac = new SOSDacImpl(target, legacyObj: null); uint needed; int hr = sosDac.GetObjectClassName(new ClrDataAddress(TestObjectAddress.Value), 0, null, &needed); Assert.Equal(HResults.S_OK, hr); - Assert.Equal((uint)"Free".Length + 1, needed); + Assert.Equal((uint)"".Length + 1, needed); }); } } From 7e6f8234eedfca5183df03f1a07589b25372b16d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:51:04 +0000 Subject: [PATCH 4/5] Revert @object back to objectContract variable name Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../SOSDacImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 271f7a1141a9da..a83b6d955db5d7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2616,11 +2616,11 @@ int ISOSDacInterface.GetObjectClassName(ClrDataAddress obj, uint count, char* cl if (obj == 0) throw new ArgumentException(); - Contracts.IObject @object = _target.Contracts.Object; + Contracts.IObject objectContract = _target.Contracts.Object; Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; Contracts.ILoader loader = _target.Contracts.Loader; - TargetPointer mt = @object.GetMethodTableAddress(obj.ToTargetPointer(_target)); + TargetPointer mt = objectContract.GetMethodTableAddress(obj.ToTargetPointer(_target)); Contracts.TypeHandle typeHandle = rts.GetTypeHandle(mt); TargetPointer modulePointer = rts.GetModule(typeHandle); From 89f489fa29807435a1af6bbb7234a843618f08a7 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Tue, 24 Feb 2026 17:47:49 -0500 Subject: [PATCH 5/5] fix --- .../SOSDacImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index a83b6d955db5d7..9463edb3676715 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2631,7 +2631,7 @@ int ISOSDacInterface.GetObjectClassName(ClrDataAddress obj, uint count, char* cl } else { - System.Text.StringBuilder classNameBuilder = new(); + StringBuilder classNameBuilder = new(); try { TypeNameBuilder.AppendType(_target, classNameBuilder, typeHandle, TypeNameFormat.FormatNamespace | TypeNameFormat.FormatFullInst);