Add cdb extensions that prints a reliable prompt. (#75)
authorMike McLaughlin <mikem@microsoft.com>
Mon, 24 Sep 2018 17:49:44 +0000 (10:49 -0700)
committerGitHub <noreply@github.com>
Mon, 24 Sep 2018 17:49:44 +0000 (10:49 -0700)
To fix issue #59.

src/SOS/CMakeLists.txt
src/SOS/SOS.UnitTests/ConfigFiles/Windows/Debugger.Tests.Config.txt
src/SOS/SOS.UnitTests/SOSRunner.cs
src/SOS/runcommand/CMakeLists.txt [new file with mode: 0644]
src/SOS/runcommand/runcommand.cpp [new file with mode: 0644]

index 1530948cfefb3ee04d6438decbcfb532e9c15b05..4d567398e6b0c6b2f70809a6bbee19691e8bd20b 100644 (file)
@@ -9,6 +9,8 @@ endif(CLR_CMAKE_PLATFORM_UNIX)
 if(WIN32)
   add_compile_options(/FIWarningControl.h) # force include of WarningControl.h
   add_compile_options(/Zl) # omit default library name in .OBJ
+
+  add_subdirectory(runcommand)
 endif(WIN32)
 
 add_definitions(-D_SECURE_SCL=0)
index ea70a5bf2841985e2f79621a5d9dc26ea237db51..c73a0dabf911c3eababbb3464c7ed00ba889fcd5 100644 (file)
@@ -18,6 +18,7 @@
   <LogDir>$(RootBinDir)\$(TargetConfiguration)\TestResults\sos.unittests_$(Timestamp)</LogDir>
   <DumpDir>$(RootBinDir)\$(TargetConfiguration)\tmp\dumps</DumpDir>
   <CDBPath>$(NuGetPackageCacheDir)\cdb-sos\1.1.0\runtimes\win-$(TargetArchitecture)\native\cdb.exe</CDBPath>
+  <CDBHelperExtension>$(InstallDir)\runcommand.dll</CDBHelperExtension>
   
   <DebuggeeSourceRoot>$(RepoRootDir)\src\SOS\SOS.UnitTests\Debuggees</DebuggeeSourceRoot>
   <DebuggeeRootDir>$(RootBinDir)\Debuggees</DebuggeeRootDir>
index 8374e0ed4c0597e07621eb81fdd2ea8831cbe07f..fc51b5bca0e456ca90c37bbbf83e16145791c234 100644 (file)
@@ -238,25 +238,33 @@ public class SOSRunner : IDisposable
 
             // Get the debugger arguments and commands to run initially
             List<string> initialCommands = new List<string>();
-            string arguments = null;
+            var arguments = new StringBuilder();
 
             switch (debugger)
             {
                 case NativeDebugger.Cdb:
-                    initialCommands.Add(".sympath %DEBUG_ROOT%");
-                    initialCommands.Add(".extpath " + Path.GetDirectoryName(config.SOSPath()));
+                    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}");
+                    }
+                    arguments.AppendFormat(@"-c "".load {0}""", helperExtension);
+
                     if (loadDump)
                     {
-                        arguments = "-z %DUMP_NAME%";
+                        arguments.Append(" -z %DUMP_NAME%");
                     }
                     else
                     {
-                        arguments = "-Gsins " + debuggeeCommandLine;
+                        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)
@@ -274,7 +282,7 @@ public class SOSRunner : IDisposable
                     {
                         throw new Exception("LLDB helper script path not set or does not exist: " + lldbHelperScript);
                     }
-                    arguments = string.Format(@"--no-lldbinit -o ""settings set interpreter.prompt-on-quit false"" -o ""command script import {0}"" -o ""version""", 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)
@@ -323,7 +331,7 @@ public class SOSRunner : IDisposable
                     {
                         throw new Exception("GDB not meant for loading core dumps");
                     }
-                    arguments = "--args " + debuggeeCommandLine;
+                    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)
@@ -343,7 +351,7 @@ public class SOSRunner : IDisposable
             }
 
             // Create the native debugger process running
-            ProcessRunner processRunner = new ProcessRunner(debuggerPath, ReplaceVariables(variables, arguments)).
+            ProcessRunner processRunner = new ProcessRunner(debuggerPath, ReplaceVariables(variables, arguments.ToString())).
                 WithLog(scriptLogger).
                 WithTimeout(TimeSpan.FromMinutes(10));
 
@@ -492,7 +500,8 @@ public class SOSRunner : IDisposable
         {
             case NativeDebugger.Cdb:
                 commands.Add($".load {sosPath}");
-                commands.Add(".lines; .reload");
+                commands.Add(".lines");
+                commands.Add(".reload");
                 if (sosHostRuntime != null)
                 {
                     commands.Add($"!SetHostRuntime {sosHostRuntime}");
@@ -523,10 +532,14 @@ public class SOSRunner : IDisposable
     public async Task ContinueExecution()
     {
         string command = null;
+        bool addPrefix = true;
         switch (Debugger)
         {
             case NativeDebugger.Cdb:
                 command = "g";
+                // Don't add the !runcommand prefix because it gets printed when cdb stops
+                // again because the helper extension used .pcmd to set a stop command.
+                addPrefix = false;
                 break;
             case NativeDebugger.Lldb:
                 command = "process continue";
@@ -535,7 +548,7 @@ public class SOSRunner : IDisposable
                 command = "continue";
                 break;
         }
-        if (!await RunCommand(command))
+        if (!await RunCommand(command, addPrefix))
         {
             throw new Exception($"'{command}' FAILED");
         }
@@ -568,13 +581,13 @@ public class SOSRunner : IDisposable
         }
     }
 
-    public async Task<bool> RunCommand(string command)
+    public async Task<bool> RunCommand(string command, bool addPrefix = true)
     {
         if (string.IsNullOrWhiteSpace(command))
         {
             throw new Exception("Debugger command empty or null");
         }
-        return await HandleCommand(command);
+        return await HandleCommand(command, addPrefix);
     }
 
     public async Task QuitDebugger()
@@ -680,7 +693,7 @@ public class SOSRunner : IDisposable
         return null;
     }
 
-    private async Task<bool> HandleCommand(string input)
+    private async Task<bool> HandleCommand(string input, bool addPrefix)
     {
         if (!await _scriptLogger.WaitForCommandPrompt())
         {
@@ -736,7 +749,12 @@ public class SOSRunner : IDisposable
                 input = input.Substring(0, firstPOUT) + poutMatchResult + input.Substring(secondPOUT + poutTag.Length);
             }
         }
-        _processRunner.StandardInputWriteLine(_scriptLogger.ProcessCommand(ReplaceVariables(input)));
+        string command = ReplaceVariables(input);
+        if (addPrefix)
+        {
+            command = _scriptLogger.ProcessCommand(command);
+        }
+        _processRunner.StandardInputWriteLine(command);
 
         ScriptLogger.CommandResult result = await _scriptLogger.WaitForCommandOutput();
         _lastCommandOutput = result.CommandOutput;
@@ -905,9 +923,15 @@ public class SOSRunner : IDisposable
 
         public string ProcessCommand(string command)
         {
-            if (_debugger == NativeDebugger.Lldb)
+            switch (_debugger)
             {
-                command = string.Format("runcommand {0}", command);
+                case NativeDebugger.Cdb:
+                    command = string.Format("!runcommand {0}", command);
+                    break;
+
+                case NativeDebugger.Lldb:
+                    command = string.Format("runcommand {0}", command);
+                    break;
             }
             return command;
         }
@@ -928,15 +952,6 @@ public class SOSRunner : IDisposable
                     switch (_debugger)
                     {
                         case NativeDebugger.Cdb:
-                            // Some commands like DumpStack have ===> or -> in the output that looks 
-                            // like the cdb prompt. Using a regex here to better match the cdb prompt
-                            // is way to slow. 
-                            if (lastCommandOutput.EndsWith("=> ") || lastCommandOutput.EndsWith("-> "))
-                            {
-                                return;
-                            }
-                            commandEnd = lastCommandOutput.EndsWith("> ");
-                            break;
                         case NativeDebugger.Lldb:
                             commandError = lastCommandOutput.EndsWith("<END_COMMAND_ERROR>");
                             commandEnd = commandError || lastCommandOutput.EndsWith("<END_COMMAND_OUTPUT>");
@@ -991,6 +1006,11 @@ public static class TestConfigurationExtensions
         return TestConfiguration.MakeCanonicalExePath(config.GetValue("CDBPath"));
     }
 
+    public static string CDBHelperExtension(this TestConfiguration config)
+    {
+        return TestConfiguration.MakeCanonicalPath(config.GetValue("CDBHelperExtension"));
+    }
+
     public static string LLDBHelperScript(this TestConfiguration config)
     {
         return TestConfiguration.MakeCanonicalPath(config.GetValue("LLDBHelperScript"));
diff --git a/src/SOS/runcommand/CMakeLists.txt b/src/SOS/runcommand/CMakeLists.txt
new file mode 100644 (file)
index 0000000..64a82eb
--- /dev/null
@@ -0,0 +1,39 @@
+project(runcommand)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+include_directories(inc)
+include_directories("$ENV{VSInstallDir}/DIA SDK/include")
+
+add_definitions(-DUSE_STL)
+
+#use static crt
+add_definitions(-MT) 
+
+set(RUNCOMMAND_SOURCES
+    runcommand.cpp
+)
+  
+set(RUNCOMMAND_LIBRARY
+    ${STATIC_MT_CRT_LIB}
+    ${STATIC_MT_CPP_LIB}
+    ${STATIC_MT_VCRT_LIB}
+    kernel32.lib
+    user32.lib
+    ole32.lib
+    oleaut32.lib
+    dbghelp.lib
+    uuid.lib
+    version.lib
+    dbgeng.lib
+    advapi32.lib
+    psapi.lib
+    ntdll.lib
+)
+
+add_library_clr(runcommand SHARED ${RUNCOMMAND_SOURCES})
+
+target_link_libraries(runcommand ${RUNCOMMAND_LIBRARY})
+
+# add the install targets
+install_clr(runcommand)
diff --git a/src/SOS/runcommand/runcommand.cpp b/src/SOS/runcommand/runcommand.cpp
new file mode 100644 (file)
index 0000000..fd46d67
--- /dev/null
@@ -0,0 +1,109 @@
+//--------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// Description: contains an entry points required by WinDbg
+//  
+//--------------------------------------------------------------------
+
+// Including SDKDDKVer.h defines the highest available Windows platform.
+
+// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
+// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
+
+#include <sdkddkver.h>
+
+// Windows Header Files
+#include <windows.h>
+#include <stdio.h>
+
+//
+// Define KDEXT_64BIT to make all wdbgexts APIs recognize 64 bit addresses
+// It is recommended for extensions to use 64 bit headers from wdbgexts so
+// the extensions could support 64 bit targets.
+//
+#define KDEXT_64BIT
+#include <dbgeng.h>
+
+#include <tchar.h>
+#include <strsafe.h>
+#include <dbghelp.h>
+
+#define DBGEXT_DEF extern "C" __declspec(dllexport) HRESULT __cdecl
+
+PDEBUG_CLIENT         g_DebugClient = NULL;
+PDEBUG_CONTROL4       g_DebugControl = NULL;
+
+EXTERN_C __declspec(dllexport) void __cdecl DebugExtensionUninitialize(void);
+extern void __cdecl dprintf(PCSTR Format, ...);
+
+// DbgEng requires all extensions to implement this function.
+EXTERN_C __declspec(dllexport) HRESULT __cdecl 
+DebugExtensionInitialize(
+    PULONG version,
+    PULONG flags)
+{
+    HRESULT hr;
+
+    *version = DEBUG_EXTENSION_VERSION(1, 0);
+    *flags = 0;
+
+    g_DebugClient = NULL;
+    g_DebugControl = NULL;
+
+    hr = DebugCreate(__uuidof(IDebugClient), (void **)&g_DebugClient);
+    if (FAILED(hr)) {
+        goto exit;
+    }
+    hr = g_DebugClient->QueryInterface(__uuidof(IDebugControl4), (void **)&g_DebugControl);
+    if (FAILED(hr)) {
+        goto exit;
+    }
+    hr = g_DebugControl->Execute(DEBUG_OUTCTL_IGNORE, ".pcmd -s \".echo <END_COMMAND_OUTPUT>\"", 0);
+    if (FAILED(hr)) {
+        goto exit;
+    }
+    dprintf("<END_COMMAND_OUTPUT>\n");
+exit:
+    if (FAILED(hr)) {
+        dprintf("<END_COMMAND_ERROR>\n");
+        DebugExtensionUninitialize();
+    }
+    return hr;
+}
+
+// WinDbg requires all extensions to implement this function.
+EXTERN_C __declspec(dllexport) void __cdecl 
+DebugExtensionUninitialize(void)
+{
+    if (g_DebugControl != NULL) {
+        g_DebugControl->Release();
+        g_DebugControl = NULL;
+    }
+    if (g_DebugClient != NULL) {
+        g_DebugClient->Release();
+        g_DebugClient = NULL;
+    }
+}
+
+DBGEXT_DEF runcommand(__in PDEBUG_CLIENT4 client, __in PCSTR args)
+{
+    HRESULT hr = g_DebugControl->Execute(DEBUG_OUTCTL_ALL_CLIENTS, args, 0);
+    if (hr == S_OK) {
+        dprintf("<END_COMMAND_OUTPUT>\n");
+    }
+    else {
+        dprintf("<END_COMMAND_ERROR>\n");
+    }
+    return hr;
+}
+
+void __cdecl
+dprintf(PCSTR Format, ...)
+{
+    va_list Args;
+
+    va_start(Args, Format);
+    g_DebugControl->OutputVaList(DEBUG_OUTPUT_ERROR, Format, Args);
+    va_end(Args);
+}