diff --git a/src/debugger/frames.cpp b/src/debugger/frames.cpp index 31e1cf92..7d4d6e06 100644 --- a/src/debugger/frames.cpp +++ b/src/debugger/frames.cpp @@ -342,7 +342,15 @@ HRESULT WalkFrames(ICorDebugThread *pThread, WalkFramesCallback cb) #endif // INTEROP_DEBUGGING ToRelease pILFrame; - IfFailRet(iCorFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame)); + if (FAILED(iCorFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame))) + { + ToRelease pInternalFrame; + if (SUCCEEDED(iCorFrame->QueryInterface(IID_ICorDebugInternalFrame, (LPVOID*) &pInternalFrame))) + IfFailRet(cb(FrameCLRInternal, GetIP(¤tCtx), iCorFrame, nullptr)); + else + IfFailRet(cb(FrameUnknown, GetIP(¤tCtx), iCorFrame, nullptr)); + continue; + } ULONG32 nOffset; CorDebugMappingResult mappingResult; diff --git a/test-suite/TestAppWinFormsStackTrace/Form1.cs b/test-suite/TestAppWinFormsStackTrace/Form1.cs new file mode 100644 index 00000000..8718e5fe --- /dev/null +++ b/test-suite/TestAppWinFormsStackTrace/Form1.cs @@ -0,0 +1,25 @@ +using System; +using System.Diagnostics; +using System.Windows.Forms; + +namespace TestAppWinFormsStackTrace +{ + public sealed class Form1 : Form + { + public Form1() + { + this.Load += Form1_Load; + } + + private void Form1_Load(object sender, EventArgs e) + { + var value = GetValue(); + Debug.WriteLine(value); + } + + private string GetValue() + { + return "Hello World"; + } + } +} diff --git a/test-suite/TestAppWinFormsStackTrace/Program.cs b/test-suite/TestAppWinFormsStackTrace/Program.cs new file mode 100644 index 00000000..f0dbf892 --- /dev/null +++ b/test-suite/TestAppWinFormsStackTrace/Program.cs @@ -0,0 +1,16 @@ +using System; +using System.Windows.Forms; + +namespace TestAppWinFormsStackTrace +{ + static class Program + { + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/test-suite/TestAppWinFormsStackTrace/TestAppWinFormsStackTrace.csproj b/test-suite/TestAppWinFormsStackTrace/TestAppWinFormsStackTrace.csproj new file mode 100644 index 00000000..057702ff --- /dev/null +++ b/test-suite/TestAppWinFormsStackTrace/TestAppWinFormsStackTrace.csproj @@ -0,0 +1,7 @@ + + + WinExe + net10.0-windows + true + + diff --git a/test-suite/VSCodeTestStackTraceWinForms/Program.cs b/test-suite/VSCodeTestStackTraceWinForms/Program.cs new file mode 100644 index 00000000..f1d6e3cd --- /dev/null +++ b/test-suite/VSCodeTestStackTraceWinForms/Program.cs @@ -0,0 +1,252 @@ +using System; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using NetcoreDbgTest; +using NetcoreDbgTest.VSCode; +using NetcoreDbgTest.Script; + +using Newtonsoft.Json; + +namespace NetcoreDbgTest.Script +{ + class Context + { + public Context(ControlInfo controlInfo, NetcoreDbgTestCore.DebuggerClient debuggerClient) + { + ControlInfo = controlInfo; + VSCodeDebugger = new VSCodeDebugger(debuggerClient); + ShouldRun = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } + + public bool ShouldRun { get; } + + public void Run(string caller_trace) + { + if (!ShouldRun) + return; + + ResolveTargetPaths(caller_trace); + BuildTarget(caller_trace); + Initialize(caller_trace); + Launch(caller_trace); + SetBreakpoints(caller_trace); + ConfigurationDone(caller_trace); + + WaitBreakpointAndAssertStack(caller_trace, ConstructorBreakpointLine, "constructor stop"); + Continue(caller_trace); + + WaitBreakpointAndAssertStack(caller_trace, LoadBreakpointLine, "load-event stop"); + Continue(caller_trace); + } + + public void WaitExit(string caller_trace) + { + if (!ShouldRun) + return; + + bool wasExited = false; + int? exitCode = null; + bool wasTerminated = false; + + Func filter = (resJSON) => + { + if (VSCodeDebugger.isResponseContainProperty(resJSON, "event", "exited")) + { + wasExited = true; + ExitedEvent exitedEvent = JsonConvert.DeserializeObject(resJSON); + exitCode = exitedEvent.body.exitCode; + } + if (VSCodeDebugger.isResponseContainProperty(resJSON, "event", "terminated")) + { + wasTerminated = true; + } + return wasExited && exitCode == 0 && wasTerminated; + }; + + Assert.True(VSCodeDebugger.IsEventReceived(filter), @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + public void AbortExecution(string caller_trace) + { + if (!ShouldRun) + return; + + TerminateRequest terminateRequest = new TerminateRequest(); + terminateRequest.arguments = new TerminateArguments(); + terminateRequest.arguments.restart = false; + Assert.True(VSCodeDebugger.Request(terminateRequest).Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + public void DebuggerExit(string caller_trace) + { + if (!ShouldRun) + return; + + DisconnectRequest disconnectRequest = new DisconnectRequest(); + disconnectRequest.arguments = new DisconnectArguments(); + disconnectRequest.arguments.restart = false; + Assert.True(VSCodeDebugger.Request(disconnectRequest).Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void ResolveTargetPaths(string caller_trace) + { + string[] sourceFiles = ControlInfo.SourceFilesPath.Split(';', StringSplitOptions.RemoveEmptyEntries); + Assert.True(sourceFiles.Length > 0, @"__FILE__:__LINE__" + "\n" + caller_trace); + + string harnessFilePath = sourceFiles[0]; + string harnessDir = Path.GetDirectoryName(harnessFilePath); + Assert.True(!String.IsNullOrEmpty(harnessDir), @"__FILE__:__LINE__" + "\n" + caller_trace); + + string suiteRoot = Path.GetFullPath(Path.Combine(harnessDir, "..")); + targetProjectDir = Path.Combine(suiteRoot, "TestAppWinFormsStackTrace"); + targetProjectPath = Path.Combine(targetProjectDir, "TestAppWinFormsStackTrace.csproj"); + targetProgramPath = Path.Combine(targetProjectDir, "bin", "Debug", "net10.0-windows", "TestAppWinFormsStackTrace.dll"); + targetSourcePath = Path.Combine(targetProjectDir, "Form1.cs"); + } + + private void BuildTarget(string caller_trace) + { + var process = new Process(); + process.StartInfo.FileName = "dotnet"; + process.StartInfo.Arguments = $"build \"{targetProjectPath}\" -c Debug"; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + + Assert.True(process.Start(), @"__FILE__:__LINE__" + "\n" + caller_trace); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new ResultNotSuccessException(@"__FILE__:__LINE__" + "\n" + caller_trace); + } + + Assert.True(File.Exists(targetProgramPath), @"__FILE__:__LINE__" + "\n" + caller_trace); + Assert.True(File.Exists(targetSourcePath), @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void Initialize(string caller_trace) + { + InitializeRequest initializeRequest = new InitializeRequest(); + initializeRequest.arguments.clientID = "vscode"; + initializeRequest.arguments.clientName = "Visual Studio Code"; + initializeRequest.arguments.adapterID = "coreclr"; + initializeRequest.arguments.pathFormat = "path"; + initializeRequest.arguments.linesStartAt1 = true; + initializeRequest.arguments.columnsStartAt1 = true; + initializeRequest.arguments.supportsVariableType = true; + initializeRequest.arguments.supportsVariablePaging = true; + initializeRequest.arguments.supportsRunInTerminalRequest = true; + initializeRequest.arguments.locale = "en-us"; + Assert.True(VSCodeDebugger.Request(initializeRequest).Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void Launch(string caller_trace) + { + LaunchRequest launchRequest = new LaunchRequest(); + launchRequest.arguments.name = ".NET Core Launch (winforms stackTrace)"; + launchRequest.arguments.type = "coreclr"; + launchRequest.arguments.preLaunchTask = "build"; + launchRequest.arguments.program = targetProgramPath; + launchRequest.arguments.cwd = targetProjectDir; + launchRequest.arguments.console = "internalConsole"; + launchRequest.arguments.stopAtEntry = false; + launchRequest.arguments.justMyCode = true; + launchRequest.arguments.internalConsoleOptions = "openOnSessionStart"; + launchRequest.arguments.__sessionId = Guid.NewGuid().ToString(); + Assert.True(VSCodeDebugger.Request(launchRequest).Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void SetBreakpoints(string caller_trace) + { + SetBreakpointsRequest setBreakpointsRequest = new SetBreakpointsRequest(); + setBreakpointsRequest.arguments.source.name = "Form1.cs"; + setBreakpointsRequest.arguments.source.path = targetSourcePath; + setBreakpointsRequest.arguments.lines.Add(ConstructorBreakpointLine); + setBreakpointsRequest.arguments.lines.Add(LoadBreakpointLine); + setBreakpointsRequest.arguments.breakpoints.Add(new SourceBreakpoint(ConstructorBreakpointLine)); + setBreakpointsRequest.arguments.breakpoints.Add(new SourceBreakpoint(LoadBreakpointLine)); + setBreakpointsRequest.arguments.sourceModified = false; + + var ret = VSCodeDebugger.Request(setBreakpointsRequest); + Assert.True(ret.Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void ConfigurationDone(string caller_trace) + { + ConfigurationDoneRequest configurationDoneRequest = new ConfigurationDoneRequest(); + Assert.True(VSCodeDebugger.Request(configurationDoneRequest).Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void WaitBreakpointAndAssertStack(string caller_trace, int expectedLine, string frameHint) + { + Func filter = (resJSON) => + { + if (VSCodeDebugger.isResponseContainProperty(resJSON, "event", "stopped") + && VSCodeDebugger.isResponseContainProperty(resJSON, "reason", "breakpoint")) + { + threadId = Convert.ToInt32(VSCodeDebugger.GetResponsePropertyValue(resJSON, "threadId")); + return true; + } + return false; + }; + + Assert.True(VSCodeDebugger.IsEventReceived(filter), @"__FILE__:__LINE__" + "\n" + caller_trace); + + StackTraceRequest stackTraceRequest = new StackTraceRequest(); + stackTraceRequest.arguments.threadId = threadId; + stackTraceRequest.arguments.startFrame = 0; + stackTraceRequest.arguments.levels = 20; + var ret = VSCodeDebugger.Request(stackTraceRequest); + Assert.True(ret.Success, @"__FILE__:__LINE__" + "\n" + caller_trace + "\n" + frameHint); + + StackTraceResponse stackTraceResponse = JsonConvert.DeserializeObject(ret.ResponseStr); + Assert.True(stackTraceResponse.body.stackFrames.Count > 0, @"__FILE__:__LINE__" + "\n" + caller_trace); + Assert.Equal(expectedLine, stackTraceResponse.body.stackFrames[0].line, @"__FILE__:__LINE__" + "\n" + caller_trace); + Assert.Equal(targetSourcePath, stackTraceResponse.body.stackFrames[0].source.path, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private void Continue(string caller_trace) + { + ContinueRequest continueRequest = new ContinueRequest(); + continueRequest.arguments.threadId = threadId; + Assert.True(VSCodeDebugger.Request(continueRequest).Success, @"__FILE__:__LINE__" + "\n" + caller_trace); + } + + private const int ConstructorBreakpointLine = 11; + private const int LoadBreakpointLine = 16; + + private readonly ControlInfo ControlInfo; + private readonly VSCodeDebugger VSCodeDebugger; + private int threadId = -1; + private string targetProjectDir; + private string targetProjectPath; + private string targetProgramPath; + private string targetSourcePath; + } +} + +namespace VSCodeTestStackTraceWinForms +{ + class Program + { + static void Main(string[] args) + { + Label.Checkpoint("init", "finish", (Object context) => + { + Context Context = (Context)context; + Context.Run(@"__FILE__:__LINE__"); + }); + + Label.Checkpoint("finish", "", (Object context) => + { + Context Context = (Context)context; + Context.AbortExecution(@"__FILE__:__LINE__"); + Context.WaitExit(@"__FILE__:__LINE__"); + Context.DebuggerExit(@"__FILE__:__LINE__"); + }); + } + } +} diff --git a/test-suite/VSCodeTestStackTraceWinForms/VSCodeTestStackTraceWinForms.csproj b/test-suite/VSCodeTestStackTraceWinForms/VSCodeTestStackTraceWinForms.csproj new file mode 100644 index 00000000..8bdc6dd7 --- /dev/null +++ b/test-suite/VSCodeTestStackTraceWinForms/VSCodeTestStackTraceWinForms.csproj @@ -0,0 +1,12 @@ + + + + + + + + Exe + netcoreapp3.1 + + + diff --git a/test-suite/run_tests.ps1 b/test-suite/run_tests.ps1 index 652116b4..e3012aa2 100644 --- a/test-suite/run_tests.ps1 +++ b/test-suite/run_tests.ps1 @@ -71,6 +71,7 @@ $ALL_TEST_NAMES = @( "VSCodeTestExtensionMethods" "VSCodeTestBreakpointWithoutStop" "VSCodeTestUnhandledException" + "VSCodeTestStackTraceWinForms" ) # Skipped tests: diff --git a/test-suite/run_tests.sh b/test-suite/run_tests.sh index dbca2e0d..a420373f 100755 --- a/test-suite/run_tests.sh +++ b/test-suite/run_tests.sh @@ -85,6 +85,7 @@ ALL_TEST_NAMES=( "VSCodeTestExtensionMethods" "VSCodeTestBreakpointWithoutStop" "VSCodeTestUnhandledException" + "VSCodeTestStackTraceWinForms" ) # Skipped tests: diff --git a/test-suite/sdb_run_tests.ps1 b/test-suite/sdb_run_tests.ps1 index e938d094..8eb7099f 100644 --- a/test-suite/sdb_run_tests.ps1 +++ b/test-suite/sdb_run_tests.ps1 @@ -62,6 +62,7 @@ $ALL_TEST_NAMES = @( "VSCodeTestExtensionMethods" "VSCodeTestBreakpointWithoutStop" "VSCodeTestUnhandledException" + "VSCodeTestStackTraceWinForms" ) # Skipped tests: diff --git a/test-suite/sdb_run_tests.sh b/test-suite/sdb_run_tests.sh index 713df967..016dbe69 100755 --- a/test-suite/sdb_run_tests.sh +++ b/test-suite/sdb_run_tests.sh @@ -108,6 +108,7 @@ ALL_TEST_NAMES=( "VSCodeTestExtensionMethods" "VSCodeTestBreakpointWithoutStop" "VSCodeTestUnhandledException" + "VSCodeTestStackTraceWinForms" ) # Skipped tests: