From: Mike McLaughlin Date: Thu, 14 Mar 2019 23:06:01 +0000 (-0700) Subject: Add "dotnet-dump analyze" tests using the existing SOS tests and SOS runner (#136) X-Git-Tag: submit/tizen/20190813.035844~51 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=38de52e6dfefa548221444ed1a64ad3401c14514;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add "dotnet-dump analyze" tests using the existing SOS tests and SOS runner (#136) Add "dotnet-dump analyze" tests using the existing SOS tests and SOS runner. Removed "dumpstack" and "eestack" from dotnet-dump. They don't work well at all in the hosted environment. Added "gcwhere". Changed the SOSHost interop code from using "out" parameters in most cases to using IntPtr/Marshal.Write* so the pointer can be checked for null. --- diff --git a/eng/build-native.sh b/eng/build-native.sh index 0ece72f29..e2c623474 100755 --- a/eng/build-native.sh +++ b/eng/build-native.sh @@ -415,6 +415,8 @@ initHostDistroRid # Init the target distro name initTargetDistroRid +echo "RID: $__DistroRid" + if [ "$__HostOS" == "OSX" ]; then export LLDB_H=$__ProjectRoot/src/SOS/lldbplugin/swift-4.0 export LLDB_LIB=/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/LLDB @@ -442,6 +444,7 @@ if [ "$__HostOS" == "OSX" ]; then python --version fi + # Build native components if [ $__Build == true ]; then if [[ $__CI == true ]]; then @@ -458,14 +461,21 @@ if [ $__Build == true ]; then fi build_native "$__BuildArch" "$__IntermediatesDir" "$__ExtraCmakeArgs" +fi +if [[ $__Build == true || $__Test == true ]]; then # Copy the native SOS binaries to where these tools expect for testing __dotnet_sos=$__RootBinDir/bin/dotnet-sos/$__BuildType/netcoreapp2.1/publish/$__DistroRid __dotnet_dump=$__RootBinDir/bin/dotnet-dump/$__BuildType/netcoreapp2.1/publish/$__DistroRid + mkdir -p "$__dotnet_sos" mkdir -p "$__dotnet_dump" + cp "$__BinDir"/* "$__dotnet_sos" + echo "Copied SOS to $__dotnet_sos" + cp "$__BinDir"/* "$__dotnet_dump" + echo "Copied SOS to $__dotnet_dump" fi # Run SOS/lldbplugin tests diff --git a/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs b/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs index d4f9bbd2b..35554e60e 100644 --- a/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs +++ b/src/Microsoft.Diagnostic.Repl/Console/ConsoleProvider.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.CommandLine; +using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -26,6 +27,7 @@ namespace Microsoft.Diagnostic.Repl private CancellationTokenSource m_interruptExecutingCommand; private string m_clearLine; + private bool m_interactiveConsole; private bool m_refreshingLine; private StringBuilder m_activeLine; @@ -76,12 +78,26 @@ namespace Microsoft.Diagnostic.Repl { m_lastCommandLine = null; m_shutdown = false; + m_interactiveConsole = !Console.IsInputRedirected; RefreshLine(); // Start keyboard processing while (!m_shutdown) { - ConsoleKeyInfo keyInfo = Console.ReadKey(true); - await ProcessKeyInfo(keyInfo, dispatchCommand); + if (m_interactiveConsole) + { + ConsoleKeyInfo keyInfo = Console.ReadKey(true); + await ProcessKeyInfo(keyInfo, dispatchCommand); + } + else + { + // The input has been redirected (i.e. testing or in script) + WriteLine(OutputType.Normal, ""); + string line = Console.ReadLine(); + if (string.IsNullOrEmpty(line)) { + continue; + } + await Dispatch(line, dispatchCommand); + } } } @@ -173,7 +189,7 @@ namespace Microsoft.Diagnostic.Repl /// private void OnCtrlBreakKeyPress(object sender, ConsoleCancelEventArgs e) { - if (!m_shutdown) { + if (!m_shutdown && m_interactiveConsole) { if (m_interruptExecutingCommand != null) { m_interruptExecutingCommand.Cancel(); } @@ -198,6 +214,10 @@ namespace Microsoft.Diagnostic.Repl private void ClearLine() { + if (!m_interactiveConsole) { + return; + } + if (m_commandExecuting != 0) { return; } @@ -212,6 +232,10 @@ namespace Microsoft.Diagnostic.Repl private void PrintActiveLine() { + if (!m_interactiveConsole) { + return; + } + if (m_shutdown) { return; } @@ -399,7 +423,7 @@ namespace Microsoft.Diagnostic.Repl // ctrl-c interrupted the command m_lastCommandLine = null; } - catch (Exception ex) when (!(ex is NullReferenceException || ex is ArgumentNullException)) + catch (Exception ex) when (!(ex is NullReferenceException || ex is ArgumentNullException || ex is ArgumentException)) { WriteLine(OutputType.Error, "ERROR: {0}", ex.Message); m_lastCommandLine = null; diff --git a/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs b/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs index 74670f59c..a733335d5 100644 --- a/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs +++ b/src/SOS/SOS.Hosting/LLDBServicesWrapper.cs @@ -314,13 +314,8 @@ namespace SOS uint descriptionSize, out uint descriptionUsed) { - type = 0; - processId = 0; - threadId = 0; - extraInformationSize = 0; - extraInformationUsed = 0; - descriptionUsed = 0; - return E_NOTIMPL; + // Should never be called. This exception will take down the program. + throw new NotImplementedException("GetLastEventInformation"); } int Disassemble( @@ -329,11 +324,12 @@ namespace SOS DEBUG_DISASM flags, StringBuilder buffer, uint bufferSize, - out uint disassemblySize, - out ulong endOffset) + IntPtr pdisassemblySize, // uint + IntPtr pendOffset) // ulong { - disassemblySize = 0; - endOffset = 0; + buffer.Clear(); + WriteUInt32(pdisassemblySize, 0); + WriteUInt64(pendOffset, offset); return E_NOTIMPL; } @@ -346,9 +342,11 @@ namespace SOS IntPtr frameContexts, uint frameContextsSize, uint frameContextsEntrySize, - IntPtr pframesFilled) + IntPtr pframesFilled) // uint { - return E_NOTIMPL; + // Don't fail, but always return 0 native frames so "clrstack -f" still prints the managed frames + WriteUInt32(pframesFilled, 0); + return S_OK; } int ReadVirtual( @@ -356,14 +354,11 @@ namespace SOS ulong address, IntPtr buffer, int bytesRequested, - IntPtr pbytesRead) + IntPtr pbytesRead) // uint { if (_dataReader.ReadMemory(address, buffer, bytesRequested, out int bytesRead)) { - if (pbytesRead != IntPtr.Zero) - { - Marshal.WriteInt32(pbytesRead, bytesRead); - } + WriteUInt32(pbytesRead, (uint)bytesRead); return S_OK; } return E_FAIL; @@ -377,9 +372,7 @@ namespace SOS IntPtr pbytesWritten) { // This gets used by MemoryBarrier() calls in the dac, which really shouldn't matter what we do here. - if (pbytesWritten != IntPtr.Zero) { - Marshal.WriteInt32(pbytesWritten, (int)bytesRequested); - } + WriteUInt32(pbytesWritten, bytesRequested); return S_OK; } @@ -396,11 +389,12 @@ namespace SOS ulong offset, StringBuilder nameBuffer, uint nameBufferSize, - out uint nameSize, - out ulong displacement) + IntPtr pnameSize, // uint + IntPtr pdisplacement) // ulong { - nameSize = 0; - displacement = 0; + nameBuffer.Clear(); + WriteUInt32(pnameSize, 0); + WriteUInt64(pdisplacement, 0); return E_NOTIMPL; } @@ -420,7 +414,6 @@ namespace SOS out ulong baseAddress) { baseAddress = 0; - try { ModuleInfo module = _dataReader.EnumerateModules().ElementAt((int)index); @@ -434,7 +427,6 @@ namespace SOS { return E_FAIL; } - return S_OK; } @@ -450,7 +442,6 @@ namespace SOS Debug.Assert(startIndex == 0); baseAddress = 0; - foreach (ModuleInfo module in _dataReader.EnumerateModules()) { if (string.Equals(Path.GetFileName(module.FileName), name)) @@ -466,11 +457,11 @@ namespace SOS IntPtr self, ulong offset, uint startIndex, - out uint index, - out ulong baseAddress) + IntPtr pindex, // uint + IntPtr pbaseAddress) // ulong { - index = 0; - baseAddress = 0; + WriteUInt32(pindex, 0); + WriteUInt64(pbaseAddress, 0); return E_NOTIMPL; } @@ -480,32 +471,32 @@ namespace SOS ulong baseAddress, StringBuilder imageNameBuffer, uint imageNameBufferSize, - out uint imageNameSize, + IntPtr pimageNameSize, // uint StringBuilder moduleNameBuffer, uint ModuleNameBufferSize, - out uint moduleNameSize, + IntPtr pmoduleNameSize, // uint StringBuilder loadedImageNameBuffer, uint loadedImageNameBufferSize, - out uint loadedImageNameSize) + IntPtr ploadedImageNameSize) // uint { - imageNameSize = 0; - moduleNameSize = 0; - loadedImageNameSize = 0; + WriteUInt32(pimageNameSize, 0); + WriteUInt32(pmoduleNameSize, 0); + WriteUInt32(ploadedImageNameSize, 0); return E_NOTIMPL; } int GetLineByOffset( IntPtr self, ulong offset, - out uint line, + IntPtr pline, // uint StringBuilder fileBuffer, uint fileBufferSize, - out uint fileSize, - out ulong displacement) + IntPtr pfileSize, // uint + IntPtr pdisplacement) // ulong { - line = 0; - fileSize = 0; - displacement = 0; + WriteUInt32(pline, 0); + WriteUInt32(pfileSize, 0); + WriteUInt64(pdisplacement, 0); return E_NOTIMPL; } @@ -514,9 +505,9 @@ namespace SOS string file, ulong[] buffer, uint bufferLines, - out uint fileLines) + IntPtr pfileLines) // uint { - fileLines = 0; + WriteUInt32(pfileLines, 0); return E_NOTIMPL; } @@ -525,13 +516,13 @@ namespace SOS uint startElement, string file, uint flags, - out uint foundElement, + IntPtr pfoundElement, // uint StringBuilder buffer, uint bufferSize, - out uint foundSize) + IntPtr pfoundSize) // uint { - foundElement = 0; - foundSize = 0; + WriteUInt32(pfoundElement, 0); + WriteUInt32(pfoundSize, 0); return E_NOTIMPL; } @@ -644,6 +635,20 @@ namespace SOS #endregion + void WriteUInt32(IntPtr pointer, uint value) + { + if (pointer != IntPtr.Zero) { + Marshal.WriteInt32(pointer, unchecked((int)value)); + } + } + + void WriteUInt64(IntPtr pointer, ulong value) + { + if (pointer != IntPtr.Zero) { + Marshal.WriteInt64(pointer, unchecked((long)value)); + } + } + // TODO: Support other architectures int GetRegister(string register, out ulong value) { @@ -853,8 +858,8 @@ namespace SOS DEBUG_DISASM flags, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer, uint bufferSize, - out uint disassemblySize, - out ulong endOffset); + IntPtr pdisassemblySize, + IntPtr pendOffset); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetContextStackTraceDelegate( @@ -895,8 +900,8 @@ namespace SOS ulong offset, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder nameBuffer, uint nameBufferSize, - out uint nameSize, - out ulong displacement); + IntPtr pnameSize, + IntPtr pdisplacement); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetNumberModulesDelegate( @@ -923,8 +928,8 @@ namespace SOS IntPtr self, ulong offset, uint startIndex, - out uint index, - out ulong baseAddress); + IntPtr pindex, + IntPtr pbaseAddress); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetModuleNamesDelegate( @@ -933,23 +938,23 @@ namespace SOS ulong baseAddress, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder imageNameBuffer, uint imageNameBufferSize, - out uint imageNameSize, + IntPtr pimageNameSize, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder moduleNameBuffer, uint ModuleNameBufferSize, - out uint moduleNameSize, + IntPtr pmoduleNameSize, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder loadedImageNameBuffer, uint loadedImageNameBufferSize, - out uint loadedImageNameSize); + IntPtr ploadedImageNameSize); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetLineByOffsetDelegate( IntPtr self, ulong offset, - out uint line, + IntPtr line, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder fileBuffer, uint fileBufferSize, - out uint fileSize, - out ulong displacement); + IntPtr fileSize, + IntPtr displacement); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetSourceFileLineOffsetsDelegate( @@ -957,7 +962,7 @@ namespace SOS [In, MarshalAs(UnmanagedType.LPStr)] string file, [Out, MarshalAs(UnmanagedType.LPArray)] ulong[] buffer, uint bufferLines, - out uint fileLines); + IntPtr fileLines); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int FindSourceFileDelegate( @@ -965,10 +970,10 @@ namespace SOS uint startElement, [In, MarshalAs(UnmanagedType.LPStr)] string file, uint flags, - out uint foundElement, + IntPtr foundElement, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder buffer, uint bufferSize, - out uint foundSize); + IntPtr foundSize); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetCurrentProcessIdDelegate( diff --git a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt index a5c35194a..d845fabbe 100644 --- a/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt +++ b/src/SOS/SOS.UnitTests/ConfigFiles/Unix/Debugger.Tests.Config.txt @@ -67,6 +67,7 @@ diff --git a/src/SOS/SOS.UnitTests/SOS.cs b/src/SOS/SOS.UnitTests/SOS.cs index 70d222f63..01cf14562 100644 --- a/src/SOS/SOS.UnitTests/SOS.cs +++ b/src/SOS/SOS.UnitTests/SOS.cs @@ -54,18 +54,29 @@ public class SOS await runner.RunScript(scriptName); } - // Against a crash dump. + // Generate a crash dump. if (IsCreateDumpConfig(config)) { await SOSRunner.CreateDump(config, Output, testName, debuggeeName, debuggeeArguments, useCreateDump); } + // Test against a crash dump. if (IsOpenDumpConfig(config)) { - using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments, loadDump: true)) + // With cdb (Windows) or lldb (Linux or OSX) + using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments, SOSRunner.Options.LoadDump)) { await runner.RunScript(scriptName); } + + // With the dotnet-dump analyze tool + if (OS.Kind == OSKind.Linux) + { + using (SOSRunner runner = await SOSRunner.StartDebugger(config, Output, testName, debuggeeName, debuggeeArguments, SOSRunner.Options.LoadDumpWithDotNetDump)) + { + await runner.RunScript(scriptName); + } + } } } diff --git a/src/SOS/SOS.UnitTests/SOSRunner.cs b/src/SOS/SOS.UnitTests/SOSRunner.cs index fc51b5bca..ac83e210d 100644 --- a/src/SOS/SOS.UnitTests/SOSRunner.cs +++ b/src/SOS/SOS.UnitTests/SOSRunner.cs @@ -25,12 +25,21 @@ public class SOSRunner : IDisposable string _lastCommandOutput; string _previousCommandCapture; + public enum Options + { + None, + GenerateDump, + LoadDump, + LoadDumpWithDotNetDump, + } + public enum NativeDebugger { Unknown, Cdb, Lldb, - Gdb + Gdb, + DotNetDump, } public const string HexValueRegEx = "[A-Fa-f0-9]+(`[A-Fa-f0-9]+)?"; @@ -71,7 +80,7 @@ public class SOSRunner : IDisposable if (!config.CreateDumpExists || !useCreateDump || config.GenerateDumpWithLLDB() || config.GenerateDumpWithGDB()) { - using (SOSRunner runner = await SOSRunner.StartDebugger(config, output, testName, debuggeeName, debuggeeArguments, loadDump: false, generateDump: true)) + using (SOSRunner runner = await SOSRunner.StartDebugger(config, output, testName, debuggeeName, debuggeeArguments, Options.GenerateDump)) { try { @@ -142,7 +151,7 @@ public class SOSRunner : IDisposable // Run the debuggee with the createdump environment variables set to generate a coredump on unhandled exception var testLogger = new TestRunner.TestLogger(outputHelper.IndentedOutput); - var variables = GenerateVariables(config, debuggeeConfig, generateDump: true); + var variables = GenerateVariables(config, debuggeeConfig, Options.GenerateDump); ProcessRunner processRunner = new ProcessRunner(exePath, ReplaceVariables(variables, arguments.ToString())). WithLog(testLogger). WithTimeout(TimeSpan.FromMinutes(5)). @@ -176,17 +185,16 @@ public class SOSRunner : IDisposable /// name of test /// debuggee name /// optional args to pass to debuggee - /// if true, generate dump with native debugger - /// if true, load dump with native debugger + /// dump options /// sos runner instance public static async Task StartDebugger(TestConfiguration config, ITestOutputHelper output, string testName, string debuggeeName, - string debuggeeArguments = null, bool loadDump = false, bool generateDump = false) + 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, generateDump); + NativeDebugger debugger = GetNativeDebuggerToUse(config, options); try { @@ -199,14 +207,14 @@ public class SOSRunner : IDisposable outputHelper.WriteLine("SOSRunner processing {0}", testName); outputHelper.WriteLine("{"); - var variables = GenerateVariables(config, debuggeeConfig, generateDump); + var variables = GenerateVariables(config, debuggeeConfig, options); var scriptLogger = new ScriptLogger(debugger, outputHelper.IndentedOutput); - if (loadDump) + if (options == Options.LoadDump || options == Options.LoadDumpWithDotNetDump) { if (!variables.TryGetValue("%DUMP_NAME%", out string dumpName) || !File.Exists(dumpName)) { - throw new Exception($"Dump file does not exist: {dumpName ?? ""}"); + throw new FileNotFoundException($"Dump file does not exist: {dumpName ?? ""}"); } } @@ -233,7 +241,7 @@ public class SOSRunner : IDisposable string debuggerPath = GetNativeDebuggerPath(debugger, config); if (string.IsNullOrWhiteSpace(debuggerPath) || !File.Exists(debuggerPath)) { - throw new Exception($"Native debugger path not set or does not exist: {debuggerPath}"); + throw new FileNotFoundException($"Native debugger path not set or does not exist: {debuggerPath}"); } // Get the debugger arguments and commands to run initially @@ -246,11 +254,11 @@ public class SOSRunner : IDisposable string helperExtension = config.CDBHelperExtension(); if (string.IsNullOrWhiteSpace(helperExtension) || !File.Exists(helperExtension)) { - throw new Exception($"CDB helper script path not set or does not exist: {helperExtension}"); + throw new ArgumentException($"CDB helper script path not set or does not exist: {helperExtension}"); } arguments.AppendFormat(@"-c "".load {0}""", helperExtension); - if (loadDump) + if (options == Options.LoadDump) { arguments.Append(" -z %DUMP_NAME%"); } @@ -280,12 +288,12 @@ public class SOSRunner : IDisposable string lldbHelperScript = config.LLDBHelperScript(); if (string.IsNullOrWhiteSpace(lldbHelperScript) || !File.Exists(lldbHelperScript)) { - throw new Exception("LLDB helper script path not set or does not exist: " + 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 (loadDump) + if (options == Options.LoadDump) { initialCommands.Add($@"target create --core ""%DUMP_NAME%"" ""{config.HostExe}"""); } @@ -327,9 +335,9 @@ public class SOSRunner : IDisposable } break; case NativeDebugger.Gdb: - if (loadDump) + if (options == Options.LoadDump || options == Options.LoadDumpWithDotNetDump) { - throw new Exception("GDB not meant for loading core dumps"); + throw new ArgumentException("GDB not meant for loading core dumps"); } arguments.AppendFormat("--args {0}", debuggeeCommandLine); @@ -348,6 +356,20 @@ public class SOSRunner : IDisposable 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 @@ -356,7 +378,7 @@ public class SOSRunner : IDisposable WithTimeout(TimeSpan.FromMinutes(10)); // Create the sos runner instance - sosRunner = new SOSRunner(debugger, config, outputHelper, variables, scriptLogger, processRunner, loadDump); + sosRunner = new SOSRunner(debugger, config, outputHelper, variables, scriptLogger, processRunner, options == Options.LoadDump || options == Options.LoadDumpWithDotNetDump); // Start the native debugger processRunner.Start(); @@ -392,7 +414,7 @@ public class SOSRunner : IDisposable string scriptFile = Path.Combine(_config.ScriptRootDir, scriptRelativePath); if (!File.Exists(scriptFile)) { - throw new Exception("Script file does not exist: " + scriptFile); + throw new FileNotFoundException("Script file does not exist: " + scriptFile); } HashSet enabledDefines = GetEnabledDefines(); LogProcessingReproInfo(scriptFile, enabledDefines); @@ -496,6 +518,7 @@ public class SOSRunner : IDisposable string sosHostRuntime = _config.SOSHostRuntime(); string sosPath = _config.SOSPath(); List commands = new List(); + switch (Debugger) { case NativeDebugger.Cdb: @@ -513,20 +536,29 @@ public class SOSRunner : IDisposable { commands.Add($"sos SetHostRuntime {sosHostRuntime}"); } - if (_isDump) - { - // lldb doesn't load dump with the initial thread set to one with - // the exception. This SOS command looks for a thread with a managed - // exception and set the current thread to it. - commands.Add("clrthreads -managedexception"); - } + SwitchToExceptionThread(); break; case NativeDebugger.Gdb: break; + case NativeDebugger.DotNetDump: + SwitchToExceptionThread(); + break; default: throw new Exception($"{DebuggerToString} cannot load sos extension"); } await RunCommands(commands); + + // Helper function to switch to the thread with an exception + void SwitchToExceptionThread() + { + if (_isDump) + { + // lldb/dotnet-dump don't load dump with the initial thread set to one + // with the exception. This SOS command looks for a thread with a managed + // exception and set the current thread to it. + commands.Add("clrthreads -managedexception"); + } + } } public async Task ContinueExecution() @@ -547,10 +579,15 @@ public class SOSRunner : IDisposable case NativeDebugger.Gdb: command = "continue"; break; + case NativeDebugger.DotNetDump: + break; } - if (!await RunCommand(command, addPrefix)) + if (command != null) { - throw new Exception($"'{command}' FAILED"); + if (!await RunCommand(command, addPrefix)) + { + throw new Exception($"'{command}' FAILED"); + } } } @@ -564,8 +601,19 @@ public class SOSRunner : IDisposable case NativeDebugger.Lldb: command = "sos " + command; break; + case NativeDebugger.DotNetDump: + int index = command.IndexOf(' '); + if (index != -1) { + // lowercase just the command name not the rest of the command line + command = command.Substring(0, index).ToLowerInvariant() + command.Substring(index); + } + else { + // it is only the command name + command = command.ToLowerInvariant(); + } + break; default: - throw new Exception(DebuggerToString + " cannot execute sos command"); + throw new ArgumentException(DebuggerToString + " cannot execute sos command"); } return await RunCommand(command); } @@ -585,7 +633,7 @@ public class SOSRunner : IDisposable { if (string.IsNullOrWhiteSpace(command)) { - throw new Exception("Debugger command empty or null"); + throw new ArgumentException("Debugger command empty or null"); } return await HandleCommand(command, addPrefix); } @@ -602,6 +650,7 @@ public class SOSRunner : IDisposable command = "q"; break; case NativeDebugger.Lldb: + case NativeDebugger.DotNetDump: command = "quit"; break; } @@ -628,9 +677,9 @@ public class SOSRunner : IDisposable } } - public static string GenerateDumpFileName(TestConfiguration config, string debuggeeName, bool generateDump) + public static string GenerateDumpFileName(TestConfiguration config, string debuggeeName, Options options) { - string dumpRoot = generateDump ? config.DebuggeeDumpOutputRootDir() : config.DebuggeeDumpInputRootDir(); + string dumpRoot = options == Options.GenerateDump ? config.DebuggeeDumpOutputRootDir() : config.DebuggeeDumpInputRootDir(); if (dumpRoot != null) { return Path.Combine(dumpRoot, Path.GetFileNameWithoutExtension(debuggeeName) + ".dmp"); @@ -660,7 +709,7 @@ public class SOSRunner : IDisposable _outputHelper.Dispose(); } - private static NativeDebugger GetNativeDebuggerToUse(TestConfiguration config, bool generateDump) + private static NativeDebugger GetNativeDebuggerToUse(TestConfiguration config, Options options) { switch (OS.Kind) { @@ -669,7 +718,14 @@ public class SOSRunner : IDisposable case OSKind.Linux: case OSKind.OSX: - return generateDump ? (config.GenerateDumpWithLLDB() ? NativeDebugger.Lldb : NativeDebugger.Gdb) : NativeDebugger.Lldb; + switch (options) { + case Options.GenerateDump: + return config.GenerateDumpWithLLDB() ? NativeDebugger.Lldb : NativeDebugger.Gdb; + case Options.LoadDumpWithDotNetDump: + return NativeDebugger.DotNetDump; + default: + return NativeDebugger.Lldb; + } default: throw new Exception(OS.Kind.ToString() + " not supported"); @@ -688,6 +744,9 @@ public class SOSRunner : IDisposable case NativeDebugger.Gdb: return config.GDBPath(); + + case NativeDebugger.DotNetDump: + return config.DotNetDumpPath(); } return null; @@ -820,11 +879,11 @@ public class SOSRunner : IDisposable return true; } - private static Dictionary GenerateVariables(TestConfiguration config, DebuggeeConfiguration debuggeeConfig, bool generateDump) + private static Dictionary GenerateVariables(TestConfiguration config, DebuggeeConfiguration debuggeeConfig, Options options) { - Dictionary vars = new Dictionary(); + var vars = new Dictionary(); string debuggeeExe = debuggeeConfig.BinaryExePath; - string dumpFileName = GenerateDumpFileName(config, Path.GetFileNameWithoutExtension(debuggeeExe), generateDump); + string dumpFileName = GenerateDumpFileName(config, Path.GetFileNameWithoutExtension(debuggeeExe), options); vars.Add("%DEBUGGEE_EXE%", debuggeeExe); if (dumpFileName != null) @@ -953,6 +1012,7 @@ public class SOSRunner : IDisposable { case NativeDebugger.Cdb: case NativeDebugger.Lldb: + case NativeDebugger.DotNetDump: commandError = lastCommandOutput.EndsWith(""); commandEnd = commandError || lastCommandOutput.EndsWith(""); break; @@ -1036,6 +1096,12 @@ public static class TestConfigurationExtensions return TestConfiguration.MakeCanonicalPath(gdbPath); } + public static string DotNetDumpPath(this TestConfiguration config) + { + string dotnetDumpPath = config.GetValue("DotNetDumpPath"); + return TestConfiguration.MakeCanonicalPath(dotnetDumpPath); + } + public static string SOSPath(this TestConfiguration config) { return TestConfiguration.MakeCanonicalPath(config.GetValue("SOSPath")); diff --git a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script index 5382109ba..becb47a11 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackAndOtherTests.script @@ -11,7 +11,9 @@ ENDIF:LIVE # Verify that ClrStack with no options works SOSCOMMAND:SetSymbolServer -ms +!IFDEF:DOTNETDUMP SOSCOMMAND:SetHostRuntime +ENDIF:DOTNETDUMP SOSCOMMAND:ClrStack VERIFY:.*OS Thread Id:\s+0x\s+.* VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+ @@ -110,28 +112,28 @@ VERIFY:.*\s+\s+\s+System\.String.* VERIFY:.*\s+\s+\s+System\.String\[\].* ENDIF:PROJECTK -# Verify DumpStack works +!IFDEF:DOTNETDUMP IFDEF:PROJECTK + +# Verify DumpStack works SOSCOMMAND:DumpStack VERIFY:.*OS Thread Id:\s+0x\s+.* VERIFY:.*Child(-SP|EBP)\s+RetAddr\s+Caller, Callee\s+ VERIFY:(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+)?SymbolTestApp\.Program\.Foo4\(System\.String\)\),\s+calling.*\s+)|(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+)?SymbolTestApp\.Program\.Foo2\(Int32, System\.String\)\),\s+calling.*\s+) -ENDIF:PROJECTK # Verify DumpStack -EE works -IFDEF:PROJECTK SOSCOMMAND:DumpStack -EE VERIFY:.*OS Thread Id:\s+0x\s+.* VERIFY:.*Child(-SP|EBP)\s+RetAddr\s+Caller, Callee\s+ VERIFY:(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+)?SymbolTestApp\.Program\.Foo4\(System\.String\)\)\s+)|(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+)?SymbolTestApp\.Program\.Foo2\(Int32, System\.String\)\)\s+) -ENDIF:PROJECTK # Verify EEStack works -IFDEF:PROJECTK SOSCOMMAND:EEStack VERIFY:.*Child(-SP|EBP)\s+RetAddr\s+Caller, Callee\s+ VERIFY:(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+)?SymbolTestApp\.Program\.Foo4\(System\.String\)\),\s+calling.*\s+)|(.*\s+\s+\s+\(MethodDesc\s+\s+(\+\s*0x\s+)?SymbolTestApp\.Program\.Foo2\(Int32, System\.String\)\),\s+calling.*\s+) + ENDIF:PROJECTK +ENDIF:DOTNETDUMP # Verify that IP2MD works (uses IP from ClrStack) SOSCOMMAND:ClrStack @@ -139,31 +141,31 @@ SOSCOMMAND:IP2MD .*\s+()\s+SymbolTestApp\.Program\.Foo4.*\s+ VERIFY:.*\s+Method Name:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:.*\s+Source file:\s+(?i:.*[\\|/]SymbolTestApp\.cs) @ 54\s+ -# Verify that "u" works (depends on the IP2MD right above) +!IFDEF:DOTNETDUMP IFDEF:PROJECTK + +# Verify that "u" works (depends on the IP2MD right above) SOSCOMMAND:u \s*MethodDesc:\s+()\s* VERIFY:\s*Normal JIT generated code\s+ VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+Begin\s+,\s+size\s+\s+ VERIFY:\s+(?i:.*[\\|/]SymbolTestApp\.cs) @ 54:\s+ -ENDIF:PROJECTK # Verify that "u" with no line info works -IFDEF:PROJECTK SOSCOMMAND:u -n VERIFY:\s*Normal JIT generated code\s+ VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+Begin\s+,\s+size\s+\s+ -ENDIF:PROJECTK # Verify that "u" with offsets info works -IFDEF:PROJECTK SOSCOMMAND:u -o VERIFY:\s*Normal JIT generated code\s+ VERIFY:\s+SymbolTestApp\.Program\.Foo4\(System\.String\)\s+ VERIFY:\s+Begin\s+,\s+size\s+\s+ VERIFY:\s+(?i:.*[\\|/]SymbolTestApp\.cs) @ 54:\s+ + ENDIF:PROJECTK +ENDIF:DOTNETDUMP # Verify that Name2EE works IFDEF:PROJECTK @@ -175,7 +177,12 @@ VERIFY:\s+JITTED Code Address:\s+\s+ ENDIF:PROJECTK # Verify that Threads (clrthreads) works +IFDEF:DOTNETDUMP +SOSCOMMAND:clrthreads +ENDIF:DOTNETDUMP +!IFDEF:DOTNETDUMP SOSCOMMAND:Threads +ENDIF:DOTNETDUMP VERIFY:\s*ThreadCount:\s+\s+ VERIFY:\s+UnstartedThread:\s+\s+ VERIFY:\s+BackgroundThread:\s+\s+ diff --git a/src/SOS/SOS.UnitTests/Scripts/StackTests.script b/src/SOS/SOS.UnitTests/Scripts/StackTests.script index f79d76514..4805c84f0 100644 --- a/src/SOS/SOS.UnitTests/Scripts/StackTests.script +++ b/src/SOS/SOS.UnitTests/Scripts/StackTests.script @@ -19,7 +19,9 @@ ENDIF:64BIT # 2) Verifying that ClrStack with managed/native mixed works IFDEF:PROJECTK SOSCOMMAND:SetSymbolServer -ms -loadsymbols +!IFDEF:DOTNETDUMP SOSCOMMAND:SetHostRuntime +ENDIF:DOTNETDUMP SOSCOMMAND:ClrStack -f VERIFY:.*OS Thread Id:\s+0x\s+.* VERIFY:\s+Child\s+SP\s+IP\s+Call Site\s+ @@ -108,6 +110,8 @@ VERIFY:.*\s+\s+\s+System\.InvalidOperationException\s+ VERIFY:.*\s+\s+\s+System\.String.* ENDIF:PROJECTK +!IFDEF:DOTNETDUMP + # 9) Verify DumpStack works SOSCOMMAND:DumpStack VERIFY:.*OS Thread Id:\s+0x\s+.* @@ -124,3 +128,5 @@ VERIFY:.*\s+\s+\s+\(MethodDesc\s+\s+\+\s*0x\s+Ne SOSCOMMAND:EEStack VERIFY:.*Child(-SP|EBP)\s+RetAddr\s+Caller, Callee\s+ VERIFY:.*\s+\s+\s+\(MethodDesc\s+\s+\+\s*0x\s+NestedExceptionTest\.Program\.Main\(System\.String\[\]\)\),\s+calling.* + +ENDIF:DOTNETDUMP diff --git a/src/SOS/Strike/sosdocsunix.txt b/src/SOS/Strike/sosdocsunix.txt index 301558436..e37142379 100644 --- a/src/SOS/Strike/sosdocsunix.txt +++ b/src/SOS/Strike/sosdocsunix.txt @@ -1595,6 +1595,23 @@ possibility is eliminated, consider contacting Microsoft Product Support for help. \\ +COMMAND: gcwhere. +GCWhere + +!GCWhere displays the location in the GC heap of the argument passed in. + + 0:002> !GCWhere 02800038 + Address Gen Heap segment begin allocated size + 02800038 2 0 02800000 02800038 0282b740 12 + +When the argument lies in the managed heap, but is not a valid *object* address +the "size" is displayed as 0: + + 0:002> !GCWhere 0280003c + Address Gen Heap segment begin allocated size + 0280003c 2 0 02800000 02800038 0282b740 0 +\\ + COMMAND: dumplog. DumpLog [-addr ] [] diff --git a/src/SOS/lldbplugin/soscommand.cpp b/src/SOS/lldbplugin/soscommand.cpp index cea564f8d..a65b7ce05 100644 --- a/src/SOS/lldbplugin/soscommand.cpp +++ b/src/SOS/lldbplugin/soscommand.cpp @@ -151,6 +151,7 @@ sosCommandInitialize(lldb::SBDebugger debugger) interpreter.AddCommand("eestack", new sosCommand("EEStack"), "Runs dumpstack on all threads in the process."); interpreter.AddCommand("finalizequeue", new sosCommand("FinalizeQueue"), "Displays all objects registered for finalization."); interpreter.AddCommand("gcroot", new sosCommand("GCRoot"), "Displays info about references (or roots) to an object at the specified address."); + interpreter.AddCommand("gcwhere", new sosCommand("GCWhere"), "Displays the location in the GC heap of the argument passed in."); interpreter.AddCommand("ip2md", new sosCommand("IP2MD"), "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled."); interpreter.AddCommand("loadsymbols", new sosCommand("SetSymbolServer", "-loadsymbols"), "Load the .NET Core native module symbols."); interpreter.AddCommand("name2ee", new sosCommand("Name2EE"), "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module."); diff --git a/src/Tools/dotnet-dump/Commands/SOSCommand.cs b/src/Tools/dotnet-dump/Commands/SOSCommand.cs index 36e53b7c7..0c8954d14 100644 --- a/src/Tools/dotnet-dump/Commands/SOSCommand.cs +++ b/src/Tools/dotnet-dump/Commands/SOSCommand.cs @@ -23,15 +23,16 @@ namespace Microsoft.Diagnostic.Tools.Dump [Command(Name = "dumpmodule", AliasExpansion = "DumpModule", Help = "Displays information about a EE module structure at the specified address.")] [Command(Name = "dumpmt", AliasExpansion = "DumpMT", Help = "Displays information about a method table at the specified address.")] [Command(Name = "dumpobj", AliasExpansion = "DumpObj", Help = "Displays info about an object at the specified address.")] - [Command(Name = "dumpstack", AliasExpansion = "DumpStack", Help = "Displays a native and managed stack trace.")] - [Command(Name = "dso", AliasExpansion = "DumpStackObjects", Help = "Displays all managed objects found within the bounds of the current stack.")] + [Command(Name = "dumpstackobjects", AliasExpansion = "DumpStackObjects", Help = "Displays all managed objects found within the bounds of the current stack.")] + [Command(Name = "dso")] [Command(Name = "eeheap", AliasExpansion = "EEHeap", Help = "Displays info about process memory consumed by internal runtime data structures.")] - [Command(Name = "eestack", AliasExpansion = "EEStack", Help = "Runs dumpstack on all threads in the process.")] [Command(Name = "finalizequeue", AliasExpansion = "FinalizeQueue", Help = "Displays all objects registered for finalization.")] [Command(Name = "gcroot", AliasExpansion = "GCRoot", Help = "Displays info about references (or roots) to an object at the specified address.")] + [Command(Name = "gcwhere", AliasExpansion = "GCWhere", Help = "Displays the location in the GC heap of the argument passed in.")] [Command(Name = "ip2md", AliasExpansion = "IP2MD", Help = "Displays the MethodDesc structure at the specified address in code that has been JIT-compiled.")] [Command(Name = "name2ee", AliasExpansion = "Name2EE", Help = "Displays the MethodTable structure and EEClass structure for the specified type or method in the specified module.")] - [Command(Name = "pe", AliasExpansion = "PrintException", Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")] + [Command(Name = "printexception", AliasExpansion = "PrintException", Help = "Displays and formats fields of any object derived from the Exception class at the specified address.")] + [Command(Name = "pe")] [Command(Name = "syncblk", AliasExpansion = "SyncBlk", Help = "Displays the SyncBlock holder info.")] [Command(Name = "histclear", AliasExpansion = "HistClear", Help = "Releases any resources used by the family of Hist commands.")] [Command(Name = "histinit", AliasExpansion = "HistInit", Help = "Initializes the SOS structures from the stress log saved in the debuggee.")]