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 c665f8f717dad8..81870292345540 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2641,7 +2641,70 @@ 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 rts = _target.Contracts.RuntimeTypeSystem; + Contracts.ILoader loader = _target.Contracts.Loader; + + TargetPointer mt = objectContract.GetMethodTableAddress(obj.ToTargetPointer(_target)); + Contracts.TypeHandle typeHandle = rts.GetTypeHandle(mt); + + TargetPointer modulePointer = rts.GetModule(typeHandle); + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(modulePointer); + if (!loader.TryGetLoadedImageContents(moduleHandle, out _, out _, out _)) + { + OutputBufferHelpers.CopyStringToBuffer(className, count, pNeeded, ""); + } + else + { + 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..ab613f2324f0f8 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,116 @@ public void ComData(MockTarget.Architecture arch) } }); } + + [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_UnloadedModule(MockTarget.Architecture arch) + { + TargetPointer TestObjectAddress = default; + TargetPointer TestMethodTableAddress = default; + ObjectContractHelper(arch, + (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(new ClrDataAddress(TestObjectAddress.Value), (uint)buffer.Length, ptr, &needed); + } + Assert.Equal(HResults.S_OK, hr); + Assert.Equal((uint)"".Length + 1, needed); + Assert.Equal("", new string(buffer, 0, (int)needed - 1)); + }); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetObjectClassName_NullBufferReturnsNeededSize(MockTarget.Architecture arch) + { + TargetPointer TestObjectAddress = default; + TargetPointer TestMethodTableAddress = default; + ObjectContractHelper(arch, + (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); + uint needed; + int hr = sosDac.GetObjectClassName(new ClrDataAddress(TestObjectAddress.Value), 0, null, &needed); + Assert.Equal(HResults.S_OK, hr); + Assert.Equal((uint)"".Length + 1, needed); + }); + } }