diff --git a/src/SOS/SOS.UnitTests/Debuggees/AsyncMain/AsyncMain.cs b/src/SOS/SOS.UnitTests/Debuggees/AsyncMain/AsyncMain.cs new file mode 100644 index 0000000000..993cdc9c2a --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/AsyncMain/AsyncMain.cs @@ -0,0 +1,32 @@ +// 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.Threading.Tasks; + +public class AsyncMainTest +{ + public static async Task Main(string[] args) + { + DivideData data = new() + { + Numerator = 16, + Denominator = 0, + }; + + Console.WriteLine($"{data.Numerator}/{data.Denominator} = {await DivideAsync(data)}"); + return 0; + } + + static async Task DivideAsync(DivideData data) + { + await Task.Delay(10); + return data.Numerator / data.Denominator; + } +} + +public class DivideData +{ + public int Numerator { get; set; } + public int Denominator { get; set; } +} diff --git a/src/SOS/SOS.UnitTests/Debuggees/AsyncMain/AsyncMain.csproj b/src/SOS/SOS.UnitTests/Debuggees/AsyncMain/AsyncMain.csproj new file mode 100644 index 0000000000..644ea7fa32 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Debuggees/AsyncMain/AsyncMain.csproj @@ -0,0 +1,7 @@ + + + Exe + $(BuildProjectFramework) + $(SupportedSubProcessTargetFrameworks) + + diff --git a/src/SOS/SOS.UnitTests/SOS.cs b/src/SOS/SOS.UnitTests/SOS.cs index 3a66a4046f..08e8f1b93a 100644 --- a/src/SOS/SOS.UnitTests/SOS.cs +++ b/src/SOS/SOS.UnitTests/SOS.cs @@ -415,6 +415,12 @@ public async Task SimpleThrow(TestConfiguration config) await SOSTestHelpers.RunTest(config, debuggeeName: "SimpleThrow", scriptName: "SimpleThrow.script", Output, testTriage: true); } + [SkippableTheory, MemberData(nameof(Configurations))] + public async Task AsyncMain(TestConfiguration config) + { + await SOSTestHelpers.RunTest(config, debuggeeName: "AsyncMain", scriptName: "AsyncMain.script", Output, testTriage: true); + } + [SkippableTheory, MemberData(nameof(Configurations))] public async Task LineNums(TestConfiguration config) { diff --git a/src/SOS/SOS.UnitTests/Scripts/AsyncMain.script b/src/SOS/SOS.UnitTests/Scripts/AsyncMain.script new file mode 100644 index 0000000000..deca836ea5 --- /dev/null +++ b/src/SOS/SOS.UnitTests/Scripts/AsyncMain.script @@ -0,0 +1,14 @@ +# +# Tests that ClrStack properly displays async Main method names with angle brackets +# + +CONTINUE + +LOADSOS + +# Verify that ClrStack with no options works and displays async method name correctly +SOSCOMMAND:ClrStack +VERIFY:.*OS Thread Id:\s+0x\s+.* +VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+ +# Verify that the method name contains
and is not trimmed to Main> +VERIFY:.*AsyncMainTest\.
\(.*\).* diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 4e3f74e9e5..025da72483 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -10942,7 +10942,10 @@ class ClrStackImpl out.WriteColumn(1, bFull ? String("") : NativePtr(ip)); // This is a clr!Frame. - out.WriteColumn(2, GetFrameFromAddress(TO_TADDR(FrameData.frameAddr), pStackWalk, bFull)); + WString frameName = GetFrameFromAddress(TO_TADDR(FrameData.frameAddr), pStackWalk, bFull); + if (IsDMLEnabled()) + frameName = DmlEscape(frameName); + out.WriteColumn(2, frameName); // Print out gc references for the Frame. for (unsigned int i = 0; i < refCount; ++i) @@ -10976,7 +10979,10 @@ class ClrStackImpl // The unmodified IP is displayed which points after the exception in most cases. This means that the // printed IP and the printed line number often will not map to one another and this is intentional. out.WriteColumn(1, InstructionPtr(ip)); - out.WriteColumn(2, MethodNameFromIP(ip, bSuppressLines, bFull, bFull, bAdjustIPForLineNumber)); + WString methodName = MethodNameFromIP(ip, bSuppressLines, bFull, bFull, bAdjustIPForLineNumber); + if (IsDMLEnabled()) + methodName = DmlEscape(methodName); + out.WriteColumn(2, methodName); // Print out gc references. refCount will be zero if bGC is false (or if we // failed to fetch gc reference information). diff --git a/src/SOS/Strike/util.cpp b/src/SOS/Strike/util.cpp index a4731d5cba..cbe0e494e5 100644 --- a/src/SOS/Strike/util.cpp +++ b/src/SOS/Strike/util.cpp @@ -5518,6 +5518,37 @@ WString MethodNameFromIP(CLRDATA_ADDRESS ip, BOOL bSuppressLines, BOOL bAssembly return methodOutput; } +WString DmlEscape(const WString &input) +{ + const WCHAR *str = input.c_str(); + size_t len = input.GetLength(); + WString result; + + for (size_t i = 0; i < len; i++) + { + if (str[i] == L'<') + { + result += W("<"); + } + else if (str[i] == L'>') + { + result += W(">"); + } + else if (str[i] == L'&') + { + result += W("&"); + } + else + { + // Append single character + WCHAR temp[2] = { str[i], L'\0' }; + result += temp; + } + } + + return result; +} + HRESULT GetGCRefs(ULONG osID, SOSStackRefData **ppRefs, unsigned int *pRefCnt, SOSStackRefError **ppErrors, unsigned int *pErrCount) { if (ppRefs == NULL || pRefCnt == NULL) diff --git a/src/SOS/Strike/util.h b/src/SOS/Strike/util.h index 3598ee7879..e86e881ed0 100644 --- a/src/SOS/Strike/util.h +++ b/src/SOS/Strike/util.h @@ -2131,6 +2131,7 @@ WString BuildRegisterOutput(const SOSStackRefData &ref, bool printObj = true); WString MethodNameFromIP(CLRDATA_ADDRESS methodDesc, BOOL bSuppressLines = FALSE, BOOL bAssemblyName = FALSE, BOOL bDisplacement = FALSE, BOOL bAdjustIPForLineNumber = FALSE); HRESULT GetGCRefs(ULONG osID, SOSStackRefData **ppRefs, unsigned int *pRefCnt, SOSStackRefError **ppErrors, unsigned int *pErrCount); WString GetFrameFromAddress(TADDR frameAddr, IXCLRDataStackWalk *pStackwalk = NULL, BOOL bAssemblyName = FALSE); +WString DmlEscape(const WString &input); HRESULT PreferCanonMTOverEEClass(CLRDATA_ADDRESS eeClassPtr, BOOL *preferCanonMT, CLRDATA_ADDRESS *outCanonMT = NULL);