From da6d53f4bbd729be7a5be9fbce8b407837074507 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Fri, 24 Apr 2026 15:41:22 -0400 Subject: [PATCH] Fix GetMethodTableName to handle empty type names consistently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legacy DAC returned E_OUTOFMEMORY when TypeString::AppendType produced an empty string, while the cDAC returned S_OK with pNeeded=1. This mismatch caused the #if DEBUG validation assertion to crash SOS.WebApp3 in all runtime-diagnostics PR builds. The E_OUTOFMEMORY was incorrect — an empty type name is not an OOM condition. Fix the native DAC to write an empty string with pNeeded=1 (matching the cDAC's existing behavior) instead of returning a misleading error code. This was exposed by c835d0fc4f8 ('Change heap dumps to use HEAP2 as the default') which changed what memory regions are included in heap dumps, causing some type names to become unresolvable. Also: - Add ValidateOutputStringBuffer helper to DebugExtensions for future use in converting output buffer assertions to soft-fail - Change runtime-diagnostics pipeline to always publish test results and SOS logs (not just on failure) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/pipelines/runtime-diagnostics.yml | 6 ++-- src/coreclr/debug/daccess/request.cpp | 6 +++- .../DebugExtensions.cs | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/eng/pipelines/runtime-diagnostics.yml b/eng/pipelines/runtime-diagnostics.yml index 7adc3792d1375f..def35687c58b63 100644 --- a/eng/pipelines/runtime-diagnostics.yml +++ b/eng/pipelines/runtime-diagnostics.yml @@ -143,7 +143,7 @@ extends: artifactName: 'TestResults_cDAC_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_Attempt$(System.JobAttempt)' displayName: 'Publish Test Results and SOS Logs' continueOnError: true - condition: failed() + condition: always() - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/diagnostics/runtime-diag-job.yml @@ -197,7 +197,7 @@ extends: artifactName: 'TestResults_cDAC_no_fallback_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_Attempt$(System.JobAttempt)' displayName: 'Publish Test Results and SOS Logs' continueOnError: true - condition: failed() + condition: always() - template: /eng/pipelines/common/platform-matrix.yml parameters: jobTemplate: /eng/pipelines/diagnostics/runtime-diag-job.yml @@ -250,7 +250,7 @@ extends: artifactName: 'TestResults_DAC_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)_Attempt$(System.JobAttempt)' displayName: 'Publish Test Results and SOS Logs' continueOnError: true - condition: failed() + condition: always() # # cDAC Dump Tests — Build, generate dumps, and run tests on Helix (single-leg mode) diff --git a/src/coreclr/debug/daccess/request.cpp b/src/coreclr/debug/daccess/request.cpp index 60043181c1de6a..a04f36e4c101b5 100644 --- a/src/coreclr/debug/daccess/request.cpp +++ b/src/coreclr/debug/daccess/request.cpp @@ -1939,7 +1939,11 @@ ClrDataAccess::GetMethodTableName(CLRDATA_ADDRESS mt, unsigned int count, _Inout if (s.IsEmpty()) { - hr = E_OUTOFMEMORY; + if (pNeeded) + *pNeeded = 1; + + if (mtName && count) + mtName[0] = 0; } else { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/DebugExtensions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/DebugExtensions.cs index b5e348bdbf3d53..50ac0cf9803808 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/DebugExtensions.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/DebugExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -52,5 +53,35 @@ internal static void ValidateHResult( }; Debug.Assert(match, $"HResult mismatch - cDAC: 0x{unchecked((uint)cdacHr):X8}, DAC: 0x{unchecked((uint)dacHr):X8} ({Path.GetFileName(filePath)}:{lineNumber})"); } + + [Conditional("DEBUG")] + internal static unsafe void ValidateOutputStringBuffer( + char* cdacBuffer, + uint* cdacNeeded, + char[] dacBuffer, + uint dacNeeded, + uint count, + [CallerFilePath] string? filePath = null, + [CallerLineNumber] int lineNumber = 0) + { + string location = $"{Path.GetFileName(filePath)}:{lineNumber}"; + + if (cdacNeeded is not null && *cdacNeeded != dacNeeded) + { + int cdacLen = (int)Math.Min(*cdacNeeded, count); + string cdacStr = cdacBuffer is not null && cdacLen > 0 ? new string(cdacBuffer, 0, cdacLen - 1) : ""; + string dacStr = dacNeeded > 0 ? new string(dacBuffer, 0, (int)dacNeeded - 1) : ""; + Trace.TraceWarning($"Output buffer pNeeded mismatch ({location}) - cDAC: {*cdacNeeded} (\"{cdacStr}\"), DAC: {dacNeeded} (\"{dacStr}\")"); + } + else if (cdacBuffer is not null && dacNeeded > 0 && count >= dacNeeded) + { + var cdacSpan = new ReadOnlySpan(cdacBuffer, (int)dacNeeded - 1); + var dacSpan = new ReadOnlySpan(dacBuffer, 0, (int)dacNeeded - 1); + if (!cdacSpan.SequenceEqual(dacSpan)) + { + Trace.TraceWarning($"Output buffer content mismatch ({location}) - cDAC: \"{new string(cdacBuffer, 0, (int)dacNeeded - 1)}\", DAC: \"{new string(dacBuffer, 0, (int)dacNeeded - 1)}\""); + } + } + } } }