diff options
-rw-r--r-- | Documentation/botr/xplat-minidump-generation.md | 2 | ||||
-rw-r--r-- | src/debug/createdump/crashinfo.cpp | 10 | ||||
-rw-r--r-- | src/pal/inc/pal.h | 8 | ||||
-rw-r--r-- | src/pal/src/thread/process.cpp | 268 | ||||
-rw-r--r-- | src/vm/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/vm/diagnosticprotocolhelper.cpp | 83 | ||||
-rw-r--r-- | src/vm/diagnosticprotocolhelper.h | 28 | ||||
-rw-r--r-- | src/vm/diagnosticserver.cpp | 7 | ||||
-rw-r--r-- | src/vm/diagnosticserver.h | 7 |
9 files changed, 319 insertions, 95 deletions
diff --git a/Documentation/botr/xplat-minidump-generation.md b/Documentation/botr/xplat-minidump-generation.md index 9c56c4cf8d..3c27c52159 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 5539a1f9e5..d7a5b20961 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 f4a726e89f..a2b15f3d12 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 445b75cc68..8b9fec421b 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/<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); + } } /*++ diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt index 8fb311ed2d..76adac0362 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 0000000000..c7b07c87f8 --- /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<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 index 0000000000..ca3149d878 --- /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 ba5812176b..9b9791ffec 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 c3c2ca477d..49f6981247 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 |