From 794f2f89399026246562181bc999a5027fb6583d Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Wed, 8 May 2019 00:46:51 -0700 Subject: [PATCH] Add generate crash dump command to diagnostics server (#24460) 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 | 2 + src/debug/createdump/crashinfo.cpp | 10 +- src/pal/inc/pal.h | 8 + src/pal/src/thread/process.cpp | 268 ++++++++++++++++-------- src/vm/CMakeLists.txt | 1 + src/vm/diagnosticprotocolhelper.cpp | 83 ++++++++ src/vm/diagnosticprotocolhelper.h | 28 +++ src/vm/diagnosticserver.cpp | 7 + src/vm/diagnosticserver.h | 7 +- 9 files changed, 319 insertions(+), 95 deletions(-) create mode 100644 src/vm/diagnosticprotocolhelper.cpp create mode 100644 src/vm/diagnosticprotocolhelper.h diff --git a/Documentation/botr/xplat-minidump-generation.md b/Documentation/botr/xplat-minidump-generation.md index 9c56c4c..3c27c52 100644 --- a/Documentation/botr/xplat-minidump-generation.md +++ b/Documentation/botr/xplat-minidump-generation.md @@ -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: diff --git a/src/debug/createdump/crashinfo.cpp b/src/debug/createdump/crashinfo.cpp index 5539a1f..d7a5b20 100644 --- a/src/debug/createdump/crashinfo.cpp +++ b/src/debug/createdump/crashinfo.cpp @@ -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)) diff --git a/src/pal/inc/pal.h b/src/pal/inc/pal.h index f4a726e..a2b15f3 100644 --- a/src/pal/inc/pal.h +++ b/src/pal/inc/pal.h @@ -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, diff --git a/src/pal/src/thread/process.cpp b/src/pal/src/thread/process.cpp index 445b75c..8b9fec4 100644 --- a/src/pal/src/thread/process.cpp +++ b/src/pal/src/thread/process.cpp @@ -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//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); + } } /*++ diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 8fb311e..76adac0 100644 --- a/src/vm/CMakeLists.txt +++ b/src/vm/CMakeLists.txt @@ -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 index 0000000..c7b07c8 --- /dev/null +++ b/src/vm/diagnosticprotocolhelper.cpp @@ -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 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 index 0000000..ca3149d --- /dev/null +++ b/src/vm/diagnosticprotocolhelper.h @@ -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__ diff --git a/src/vm/diagnosticserver.cpp b/src/vm/diagnosticserver.cpp index ba58121..9b9791f 100644 --- a/src/vm/diagnosticserver.cpp +++ b/src/vm/diagnosticserver.cpp @@ -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; diff --git a/src/vm/diagnosticserver.h b/src/vm/diagnosticserver.h index c3c2ca4..49f6981 100644 --- a/src/vm/diagnosticserver.h +++ b/src/vm/diagnosticserver.h @@ -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 -- 2.7.4