Add generate crash dump command to diagnostics server (#24460)
authorMike McLaughlin <mikem@microsoft.com>
Wed, 8 May 2019 07:46:51 +0000 (00:46 -0700)
committerGitHub <noreply@github.com>
Wed, 8 May 2019 07:46:51 +0000 (00:46 -0700)
Add the DiagnosticProtocolHelper class to deserialize and dispatch
the new GenerateCoreDump command.

Refactor the PAL createdump launch on unhandled exception code to
used by a new PAL_GenerateCoreDump method that doesn't depend on
the complus dump environment variables.

Changed the "full" createdump not to include the uncommitted pages and
removed the "add module metadata" workaround for SOS clrstack !UNKNOWN
problem now that is fixed in SOS (crashinfo.cpp).

Documentation/botr/xplat-minidump-generation.md
src/debug/createdump/crashinfo.cpp
src/pal/inc/pal.h
src/pal/src/thread/process.cpp
src/vm/CMakeLists.txt
src/vm/diagnosticprotocolhelper.cpp [new file with mode: 0644]
src/vm/diagnosticprotocolhelper.h [new file with mode: 0644]
src/vm/diagnosticserver.cpp
src/vm/diagnosticserver.h

index 9c56c4c..3c27c52 100644 (file)
@@ -46,6 +46,8 @@ Gathering the crash information on OS X will be quite a bit different than Linux
 
 # Configuration/Policy #
 
+NOTE: Core dump generation in docker containers require the ptrace capability (--cap-add=SYS_PTRACE or --privileged run/exec options).
+
 Any configuration or policy is set with environment variables which are passed as options to the _createdump_ utility.
 
 Environment variables supported:
index 5539a1f..d7a5b20 100644 (file)
@@ -191,16 +191,16 @@ CrashInfo::GatherCrashInfo(MINIDUMP_TYPE minidumpType)
         }
         for (const MemoryRegion& region : m_otherMappings)
         {
-            InsertMemoryBackedRegion(region);
+            // Don't add uncommitted pages to the full dump
+            if ((region.Permissions() & (PF_R | PF_W | PF_X)) != 0)
+            {
+                InsertMemoryBackedRegion(region);
+            }
         }
     }
     // Add all the heap (read/write) memory regions (m_otherMappings contains the heaps)
     else if (minidumpType & MiniDumpWithPrivateReadWriteMemory)
     {
-        for (const MemoryRegion& region : m_moduleMappings)
-        {
-            InsertMemoryBackedRegion(region);
-        }
         for (const MemoryRegion& region : m_otherMappings)
         {
             if (region.Permissions() == (PF_R | PF_W))
index f4a726e..a2b15f3 100644 (file)
@@ -450,6 +450,14 @@ PALAPI
 PAL_SetShutdownCallback(
     IN PSHUTDOWN_CALLBACK callback);
 
+PALIMPORT
+BOOL
+PALAPI
+PAL_GenerateCoreDump(
+    IN LPCSTR dumpName,
+    IN INT dumpType,
+    IN BOOL diag);
+
 typedef VOID (*PPAL_STARTUP_CALLBACK)(
     char *modulePath,
     HMODULE hModule,
index 445b75c..8b9fec4 100644 (file)
@@ -3206,119 +3206,107 @@ PROCNotifyProcessShutdown()
 
 /*++
 Function
-  PROCAbortInitialize()
+  PROCBuildCreateDumpCommandLine
 
 Abstract
-  Initialize the process abort crash dump program file path and
-  name. Doing all of this ahead of time so nothing is allocated
-  or copied in PROCAbort/signal handler.
+  Builds the createdump command line from the arguments.
 
 Return
   TRUE - succeeds, FALSE - fails
 
 --*/
 BOOL
-PROCAbortInitialize()
+PROCBuildCreateDumpCommandLine(const char** argv, char* dumpName, char* dumpType, BOOL diag)
 {
-    char* enabled = getenv("COMPlus_DbgEnableMiniDump");
-    if (enabled != nullptr && _stricmp(enabled, "1") == 0)
+    if (g_szCoreCLRPath == nullptr)
     {
-        if (g_szCoreCLRPath == nullptr)
-        {
-            return FALSE;
-        }
-        const char* DumpGeneratorName = "createdump";
-        int programLen = strlen(g_szCoreCLRPath) + strlen(DumpGeneratorName) + 1;
-        char* program = (char*)InternalMalloc(programLen);
-        if (program == nullptr)
-        {
-            return FALSE;
-        }
-        if (strcpy_s(program, programLen, g_szCoreCLRPath) != SAFECRT_SUCCESS)
-        {
-            return FALSE;
-        }
-        char *last = strrchr(program, '/');
-        if (last != nullptr)
-        {
-            *(last + 1) = '\0';
-        }
-        else
-        {
-            program[0] = '\0';
-        }
-        if (strcat_s(program, programLen, DumpGeneratorName) != SAFECRT_SUCCESS)
+        return FALSE;
+    }
+    const char* DumpGeneratorName = "createdump";
+    int programLen = strlen(g_szCoreCLRPath) + strlen(DumpGeneratorName) + 1;
+    char* program = (char*)InternalMalloc(programLen);
+    if (program == nullptr)
+    {
+        return FALSE;
+    }
+    if (strcpy_s(program, programLen, g_szCoreCLRPath) != SAFECRT_SUCCESS)
+    {
+        return FALSE;
+    }
+    char *last = strrchr(program, '/');
+    if (last != nullptr)
+    {
+        *(last + 1) = '\0';
+    }
+    else
+    {
+        program[0] = '\0';
+    }
+    if (strcat_s(program, programLen, DumpGeneratorName) != SAFECRT_SUCCESS)
+    {
+        return FALSE;
+    }
+    char* pidarg = (char*)InternalMalloc(128);
+    if (pidarg == nullptr)
+    {
+        return FALSE;
+    }
+    if (sprintf_s(pidarg, 128, "%d", gPID) == -1)
+    {
+        return FALSE;
+    }
+    *argv++ = program;
+
+    if (dumpName != nullptr)
+    {
+        *argv++ = "--name";
+        *argv++ = dumpName;
+    }
+
+    if (dumpType != nullptr)
+    {
+        if (strcmp(dumpType, "1") == 0)
         {
-            return FALSE;
+            *argv++ = "--normal";
         }
-        char* pidarg = (char*)InternalMalloc(128);
-        if (pidarg == nullptr)
+        else if (strcmp(dumpType, "2") == 0)
         {
-            return FALSE;
+            *argv++ = "--withheap";
         }
-        if (sprintf_s(pidarg, 128, "%d", gPID) == -1)
+        else if (strcmp(dumpType, "3") == 0)
         {
-            return FALSE;
+            *argv++ = "--triage";
         }
-        const char** argv = (const char**)g_argvCreateDump;
-        *argv++ = program;
-
-        char* envvar = getenv("COMPlus_DbgMiniDumpName");
-        if (envvar != nullptr)
+        else if (strcmp(dumpType, "4") == 0)
         {
-            *argv++ = "--name";
-            *argv++ = envvar;
+            *argv++ = "--full";
         }
+    }
 
-        envvar = getenv("COMPlus_DbgMiniDumpType");
-        if (envvar != nullptr)
-        {
-            if (strcmp(envvar, "1") == 0)
-            {
-                *argv++ = "--normal";
-            }
-            else if (strcmp(envvar, "2") == 0)
-            {
-                *argv++ = "--withheap";
-            }
-            else if (strcmp(envvar, "3") == 0)
-            {
-                *argv++ = "--triage";
-            }
-            else if (strcmp(envvar, "4") == 0)
-            {
-                *argv++ = "--full";
-            }
-        }
+    if (diag)
+    {
+        *argv++ = "--diag";
+    }
 
-        envvar = getenv("COMPlus_CreateDumpDiagnostics");
-        if (envvar != nullptr && strcmp(envvar, "1") == 0)
-        {
-            *argv++ = "--diag";
-        }
+    *argv++ = pidarg;
+    *argv = nullptr;
 
-        *argv++ = pidarg;
-        *argv = nullptr;
-    }
     return TRUE;
 }
 
 /*++
 Function:
-  PROCCreateCrashDumpIfEnabled
+  PROCCreateCrashDump
 
-  Creates crash dump of the process (if enabled). Can be
-  called from the unhandled native exception handler.
+  Creates crash dump of the process. Can be called from the 
+  unhandled native exception handler.
 
 (no return value)
 --*/
-VOID
-PROCCreateCrashDumpIfEnabled()
+BOOL
+PROCCreateCrashDump(char** argv)
 {
 #if HAVE_PRCTL_H && HAVE_PR_SET_PTRACER
-    // If enabled, launch the create minidump utility and wait until it completes
-    if (g_argvCreateDump[0] == nullptr)
-        return;
 
     // Fork the core dump child process.
     pid_t childpid = fork();
@@ -3326,14 +3314,16 @@ PROCCreateCrashDumpIfEnabled()
     // If error, write an error to trace log and abort
     if (childpid == -1)
     {
-        ERROR("PROCAbort: fork() FAILED %d (%s)\n", errno, strerror(errno));
+        ERROR("PROCCreateCrashDump: fork() FAILED %d (%s)\n", errno, strerror(errno));
+        return false;
     }
     else if (childpid == 0)
     {
         // Child process
-        if (execve(g_argvCreateDump[0], g_argvCreateDump, palEnvironment) == -1)
+        if (execve(argv[0], argv, palEnvironment) == -1)
         {
-            ERROR("PROCAbort: execve FAILED %d (%s)\n", errno, strerror(errno));
+            ERROR("PPROCCreateCrashDump: execve FAILED %d (%s)\n", errno, strerror(errno));
+            return false;
         }
     }
     else
@@ -3341,18 +3331,122 @@ PROCCreateCrashDumpIfEnabled()
         // Gives the child process permission to use /proc/<pid>/mem and ptrace
         if (prctl(PR_SET_PTRACER, childpid, 0, 0, 0) == -1)
         {
-            ERROR("PROCAbort: prctl() FAILED %d (%s)\n", errno, strerror(errno));
+            ERROR("PPROCCreateCrashDump: prctl() FAILED %d (%s)\n", errno, strerror(errno));
+            return false;
         }
         // Parent waits until the child process is done
-        int wstatus;
+        int wstatus = 0;
         int result = waitpid(childpid, &wstatus, 0);
         if (result != childpid)
         {
-            ERROR("PROCAbort: waitpid FAILED result %d wstatus %d errno %d (%s)\n",
+            ERROR("PPROCCreateCrashDump: waitpid FAILED result %d wstatus %d errno %d (%s)\n",
                 result, wstatus, errno, strerror(errno));
+            return false;
         }
+        return !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) == 0;
     }
 #endif // HAVE_PRCTL_H && HAVE_PR_SET_PTRACER
+    return true;
+}
+
+/*++
+Function
+  PROCAbortInitialize()
+
+Abstract
+  Initialize the process abort crash dump program file path and
+  name. Doing all of this ahead of time so nothing is allocated
+  or copied in PROCAbort/signal handler.
+
+Return
+  TRUE - succeeds, FALSE - fails
+
+--*/
+BOOL
+PROCAbortInitialize()
+{
+    char* enabled = getenv("COMPlus_DbgEnableMiniDump");
+    if (enabled != nullptr && _stricmp(enabled, "1") == 0)
+    {
+        char* dumpName = getenv("COMPlus_DbgMiniDumpName");
+        char* dumpType = getenv("COMPlus_DbgMiniDumpType");
+        char* diagStr = getenv("COMPlus_CreateDumpDiagnostics");
+        BOOL diag = diagStr != nullptr && strcmp(diagStr, "1") == 0;
+
+        if (!PROCBuildCreateDumpCommandLine((const char **)g_argvCreateDump, dumpName, dumpType, diag))
+        {
+            return FALSE;
+        }
+    }
+    return TRUE;
+}
+
+/*++
+Function:
+  PAL_GenerateCoreDump
+
+Abstract:
+  Public entry point to create a crash dump of the process.
+
+Parameters:
+    dumpName
+    dumpType:
+        Normal = 1,
+        WithHeap = 2,
+        Triage = 3,
+        Full = 4
+    diag
+        true - log createdump diagnostics to console
+
+Return:
+    TRUE success
+    FALSE failed
+--*/
+BOOL
+PAL_GenerateCoreDump(
+    LPCSTR dumpName, 
+    INT dumpType, 
+    BOOL diag)
+{
+    char* argvCreateDump[8] = { nullptr };
+    char dumpTypeStr[16];
+
+    if (dumpType < 1 || dumpType > 4)
+    {
+        return FALSE;
+    }
+    if (dumpName != nullptr && dumpName[0] == '\0')
+    {
+        dumpName = nullptr;
+    }
+    if (_itoa_s(dumpType, dumpTypeStr, sizeof(dumpTypeStr), 10) != 0)
+    {
+        return FALSE;
+    }
+    if (!PROCBuildCreateDumpCommandLine((const char **)argvCreateDump, (char*)dumpName, dumpTypeStr, diag))
+    {
+        return FALSE;
+    }
+    return PROCCreateCrashDump(argvCreateDump);
+}
+
+/*++
+Function:
+  PROCCreateCrashDumpIfEnabled
+
+  Creates crash dump of the process (if enabled). Can be
+  called from the unhandled native exception handler.
+
+(no return value)
+--*/
+VOID
+PROCCreateCrashDumpIfEnabled()
+{
+    // If enabled, launch the create minidump utility and wait until it completes
+    if (g_argvCreateDump[0] != nullptr)
+    {
+        PROCCreateCrashDump(g_argvCreateDump);
+    }
 }
 
 /*++
index 8fb311e..76adac0 100644 (file)
@@ -58,6 +58,7 @@ set(VM_SOURCES_DAC_AND_WKS_COMMON
     debughelp.cpp
     debuginfostore.cpp
     decodemd.cpp
+    diagnosticprotocolhelper.cpp
     disassembler.cpp
     dllimport.cpp
     domainfile.cpp
diff --git a/src/vm/diagnosticprotocolhelper.cpp b/src/vm/diagnosticprotocolhelper.cpp
new file mode 100644 (file)
index 0000000..c7b07c8
--- /dev/null
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "common.h"
+#include "fastserializer.h"
+#include "diagnosticprotocolhelper.h"
+#include "diagnosticsipc.h"
+#include "diagnosticsprotocol.h"
+
+#ifdef FEATURE_PERFTRACING
+#ifdef FEATURE_PAL
+
+static void WriteStatus(uint64_t result, IpcStream* pStream)
+{
+    uint32_t nBytesWritten = 0;
+    bool fSuccess = pStream->Write(&result, sizeof(result), nBytesWritten);
+    if (fSuccess)
+    {
+        fSuccess = pStream->Flush();
+    }
+}
+
+void DiagnosticProtocolHelper::GenerateCoreDump(IpcStream* pStream)
+{
+    CONTRACTL
+    {
+        THROWS;
+        GC_TRIGGERS;
+        MODE_PREEMPTIVE;
+        PRECONDITION(pStream != nullptr);
+    }
+    CONTRACTL_END;
+
+    if (pStream == nullptr)
+        return;
+
+    HRESULT hr = S_OK;
+
+    // TODO: Read within a loop.
+    uint8_t buffer[IpcStreamReadBufferSize] { };
+    uint32_t nNumberOfBytesRead = 0;
+    bool fSuccess = pStream->Read(buffer, sizeof(buffer), nNumberOfBytesRead);
+    if (fSuccess)
+    {
+        // The protocol buffer is defined as:
+        //   string - dumpName (array<char> where the last char must = 0) or (length = 0)
+        //   int - dumpType
+        //   int - diagnostics
+        // returns
+        //   ulong - status
+        LPCSTR dumpName;
+        INT dumpType;
+        INT diagnostics;
+
+        uint8_t *pBufferCursor = buffer;
+        uint32_t bufferLen = nNumberOfBytesRead;
+
+        if (TryParseString(pBufferCursor, bufferLen, dumpName) &&
+            TryParse(pBufferCursor, bufferLen, dumpType) &&
+            TryParse(pBufferCursor, bufferLen, diagnostics))
+        {
+            if (!PAL_GenerateCoreDump(dumpName, dumpType, diagnostics))
+            {
+                hr = E_FAIL;
+            }
+        }
+        else
+        {
+            hr = E_INVALIDARG;
+        }
+    }
+    else 
+    {
+        hr = E_UNEXPECTED;
+    }
+
+    WriteStatus(hr, pStream);
+    delete pStream;
+}
+
+#endif // FEATURE_PAL
+#endif // FEATURE_PERFTRACING
diff --git a/src/vm/diagnosticprotocolhelper.h b/src/vm/diagnosticprotocolhelper.h
new file mode 100644 (file)
index 0000000..ca3149d
--- /dev/null
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef __DIAGNOSTIC_PROTOCOL_HELPER_H__
+#define __DIAGNOSTIC_PROTOCOL_HELPER_H__
+
+#ifdef FEATURE_PERFTRACING
+
+#include "common.h"
+
+class IpcStream;
+
+class DiagnosticProtocolHelper
+{
+public:
+    // IPC event handlers.
+#ifdef FEATURE_PAL
+    static void GenerateCoreDump(IpcStream *pStream); // `dotnet-dump collect`
+#endif
+
+private:
+    const static uint32_t IpcStreamReadBufferSize = 8192;
+};
+
+#endif // FEATURE_PERFTRACING
+
+#endif // __DIAGNOSTIC_PROTOCOL_HELPER_H__
index ba58121..9b9791f 100644 (file)
@@ -5,6 +5,7 @@
 #include "common.h"
 #include "diagnosticserver.h"
 #include "eventpipeprotocolhelper.h"
+#include "diagnosticprotocolhelper.h"
 
 #ifdef FEATURE_PAL
 #include "pal.h"
@@ -65,6 +66,12 @@ static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter)
                 EventPipeProtocolHelper::CollectTracing(pStream);
                 break;
 
+#ifdef FEATURE_PAL
+            case DiagnosticMessageType::GenerateCoreDump:
+                DiagnosticProtocolHelper::GenerateCoreDump(pStream);
+                break;
+#endif
+
             default:
                 STRESS_LOG1(LF_DIAGNOSTICS_PORT, LL_WARNING, "Received unknown request type (%d)\n", header.RequestType);
                 delete pStream;
index c3c2ca4..49f6981 100644 (file)
@@ -15,12 +15,13 @@ enum class DiagnosticMessageType : uint32_t
 {
     ///////////////////////////////////////////////////////////////////////////
     // Debug = 0
+    GenerateCoreDump = 1,           // Initiates core dump generation
 
     ///////////////////////////////////////////////////////////////////////////
-    // EventPipe
-    StartEventPipeTracing = 1024, // To file
+    // EventPipe = 1024
+    StartEventPipeTracing = 1024,   // To file
     StopEventPipeTracing,
-    CollectEventPipeTracing, // To IPC
+    CollectEventPipeTracing,        // To IPC
 
     ///////////////////////////////////////////////////////////////////////////
     // Profiler = 2048