/// <summary> /// Start a debuggee under a native debugger returning a sos runner instance. /// </summary> /// <param name="config">test configuration</param> /// <param name="output">output instance</param> /// <param name="testName">name of test</param> /// <param name="debuggeeName">debuggee name</param> /// <param name="debuggeeArguments">optional args to pass to debuggee</param> /// <param name="options">dump options</param> /// <returns>sos runner instance</returns> public static async Task <SOSRunner> StartDebugger(TestConfiguration config, ITestOutputHelper output, string testName, string debuggeeName, string debuggeeArguments = null, Options options = Options.None) { TestRunner.OutputHelper outputHelper = null; SOSRunner sosRunner = null; // Figure out which native debugger to use NativeDebugger debugger = GetNativeDebuggerToUse(config, options); try { // Setup the logging from the options in the config file outputHelper = TestRunner.ConfigureLogging(config, output, testName); // Restore and build the debuggee. DebuggeeConfiguration debuggeeConfig = await DebuggeeCompiler.Execute(config, debuggeeName, outputHelper); outputHelper.WriteLine("SOSRunner processing {0}", testName); outputHelper.WriteLine("{"); var variables = GenerateVariables(config, debuggeeConfig, options); var scriptLogger = new ScriptLogger(debugger, outputHelper.IndentedOutput); if (options == Options.LoadDump || options == Options.LoadDumpWithDotNetDump) { if (!variables.TryGetValue("%DUMP_NAME%", out string dumpName) || !File.Exists(dumpName)) { throw new FileNotFoundException($"Dump file does not exist: {dumpName ?? ""}"); } } // Get the full debuggee launch command line (includes the host if required) var debuggeeCommandLine = new StringBuilder(); if (!string.IsNullOrWhiteSpace(config.HostExe)) { debuggeeCommandLine.Append(config.HostExe); debuggeeCommandLine.Append(" "); if (!string.IsNullOrWhiteSpace(config.HostArgs)) { debuggeeCommandLine.Append(config.HostArgs); debuggeeCommandLine.Append(" "); } } debuggeeCommandLine.Append(debuggeeConfig.BinaryExePath); if (!string.IsNullOrWhiteSpace(debuggeeArguments)) { debuggeeCommandLine.Append(" "); debuggeeCommandLine.Append(debuggeeArguments); } // Get the native debugger path string debuggerPath = GetNativeDebuggerPath(debugger, config); if (string.IsNullOrWhiteSpace(debuggerPath) || !File.Exists(debuggerPath)) { throw new FileNotFoundException($"Native debugger path not set or does not exist: {debuggerPath}"); } // Get the debugger arguments and commands to run initially List <string> initialCommands = new List <string>(); var arguments = new StringBuilder(); switch (debugger) { case NativeDebugger.Cdb: string helperExtension = config.CDBHelperExtension(); if (string.IsNullOrWhiteSpace(helperExtension) || !File.Exists(helperExtension)) { throw new ArgumentException($"CDB helper script path not set or does not exist: {helperExtension}"); } arguments.AppendFormat(@"-c "".load {0}""", helperExtension); if (options == Options.LoadDump) { arguments.Append(" -z %DUMP_NAME%"); } else { arguments.AppendFormat(" -Gsins {0}", debuggeeCommandLine); // disable stopping on integer divide-by-zero and integer overflow exceptions initialCommands.Add("sxd dz"); initialCommands.Add("sxd iov"); } initialCommands.Add(".sympath %DEBUG_ROOT%"); initialCommands.Add(".extpath " + Path.GetDirectoryName(config.SOSPath())); // Add the path to runtime so cdb/sos can find mscordbi. string runtimeSymbolsPath = config.RuntimeSymbolsPath; if (runtimeSymbolsPath != null) { initialCommands.Add(".sympath+ " + runtimeSymbolsPath); } // Turn off warnings that can happen in the middle of a command's output initialCommands.Add(".outmask- 4"); break; case NativeDebugger.Lldb: // Get the lldb python script file path necessary to capture the output of commands // by printing a prompt after all the command output is printed. string lldbHelperScript = config.LLDBHelperScript(); if (string.IsNullOrWhiteSpace(lldbHelperScript) || !File.Exists(lldbHelperScript)) { throw new ArgumentException("LLDB helper script path not set or does not exist: " + lldbHelperScript); } arguments.AppendFormat(@"--no-lldbinit -o ""settings set interpreter.prompt-on-quit false"" -o ""command script import {0}"" -o ""version""", lldbHelperScript); // Load the dump or launch the debuggee process if (options == Options.LoadDump) { initialCommands.Add($@"target create --core ""%DUMP_NAME%"" ""{config.HostExe}"""); } else { var sb = new StringBuilder("settings set -- target.run-args"); if (!string.IsNullOrWhiteSpace(config.HostArgs)) { string[] args = ReplaceVariables(variables, config.HostArgs).Trim().Split(' '); foreach (string arg in args) { sb.AppendFormat(@" ""{0}""", arg); } } sb.AppendFormat(@" ""{0}""", debuggeeConfig.BinaryExePath); if (!string.IsNullOrWhiteSpace(debuggeeArguments)) { string[] args = ReplaceVariables(variables, debuggeeArguments).Trim().Split(' '); foreach (string arg in args) { sb.AppendFormat(@" ""{0}""", arg); } } initialCommands.Add($@"target create ""{config.HostExe}"""); initialCommands.Add(sb.ToString()); initialCommands.Add("process launch -s"); // .NET Core 1.1 or less don't catch stack overflow and abort so need to catch SIGSEGV if (config.StackOverflowSIGSEGV) { initialCommands.Add("process handle -s true -n true -p true SIGSEGV"); } else { initialCommands.Add("process handle -s false -n false -p true SIGSEGV"); } initialCommands.Add("process handle -s false -n false -p true SIGFPE"); initialCommands.Add("process handle -s true -n true -p true SIGABRT"); } break; case NativeDebugger.Gdb: if (options == Options.LoadDump || options == Options.LoadDumpWithDotNetDump) { throw new ArgumentException("GDB not meant for loading core dumps"); } arguments.AppendFormat("--args {0}", debuggeeCommandLine); // .NET Core 1.1 or less don't catch stack overflow and abort so need to catch SIGSEGV if (config.StackOverflowSIGSEGV) { initialCommands.Add("handle SIGSEGV stop print"); } else { initialCommands.Add("handle SIGSEGV nostop noprint"); } initialCommands.Add("handle SIGFPE nostop noprint"); initialCommands.Add("handle SIGABRT stop print"); initialCommands.Add("set startup-with-shell off"); initialCommands.Add("set use-coredump-filter on"); initialCommands.Add("run"); break; case NativeDebugger.DotNetDump: if (options != Options.LoadDumpWithDotNetDump) { throw new ArgumentException($"{options} not supported for dotnet-dump testing"); } if (string.IsNullOrWhiteSpace(config.HostExe)) { throw new ArgumentException("No HostExe in configuration"); } arguments.Append(debuggerPath); arguments.Append(@" analyze %DUMP_NAME%"); debuggerPath = config.HostExe; break; } // Create the native debugger process running ProcessRunner processRunner = new ProcessRunner(debuggerPath, ReplaceVariables(variables, arguments.ToString())). WithLog(scriptLogger). WithTimeout(TimeSpan.FromMinutes(10)); // Create the sos runner instance sosRunner = new SOSRunner(debugger, config, outputHelper, variables, scriptLogger, processRunner, options == Options.LoadDump || options == Options.LoadDumpWithDotNetDump); // Start the native debugger processRunner.Start(); // Set the coredump_filter flags on the gdb process so the coredump it // takes of the target process contains everything the tests need. if (debugger == NativeDebugger.Gdb) { initialCommands.Insert(0, string.Format("shell echo 0x3F > /proc/{0}/coredump_filter", processRunner.ProcessId)); } // Execute the initial debugger commands await sosRunner.RunCommands(initialCommands); return(sosRunner); } catch (Exception ex) { // Log the exception outputHelper?.WriteLine(ex.ToString()); // The runner needs to kill the process and dispose of the file logger sosRunner?.Dispose(); // The file logging output helper needs to be disposed to close the file outputHelper?.Dispose(); throw; } }