From 748d894e82abcdfff7429cf745003e182c47f163 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Fri, 12 May 2023 11:28:35 -0700 Subject: [PATCH] win,process: write minidumps when sending SIGQUIT (#3840) This commit adds the ability to dump core when sending the `SIGQUIT` signal on Windows. The change reads in the current registry setting for local dumps, and attempts to write out to that location before killing the process. See [collecting-user-mode-dumps] for registry and pathing details. This behavior mimics that of the dumps created by the typical Windows Error Reporting mechanism. [collecting-user-mode-dumps]: https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps --- CMakeLists.txt | 5 ++- configure.ac | 2 +- include/uv/win.h | 1 + src/win/process.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 459ca080..e9d77520 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,7 +183,10 @@ if(WIN32) advapi32 iphlpapi userenv - ws2_32) + ws2_32 + dbghelp + ole32 + uuid) list(APPEND uv_sources src/win/async.c src/win/core.c diff --git a/configure.ac b/configure.ac index a64a3dd9..57465f2d 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ AM_CONDITIONAL([OS400], [AS_CASE([$host_os],[os400], [true], [false]) AM_CONDITIONAL([SUNOS], [AS_CASE([$host_os],[solaris*], [true], [false])]) AM_CONDITIONAL([WINNT], [AS_CASE([$host_os],[mingw*], [true], [false])]) AS_CASE([$host_os],[mingw*], [ - LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -lshell32 -luserenv -luser32" + LIBS="$LIBS -lws2_32 -lpsapi -liphlpapi -lshell32 -luserenv -luser32 -ldbghelp -lole32 -luuid" ]) AS_CASE([$host_os], [solaris2.10], [ CFLAGS="$CFLAGS -DSUNOS_NO_IFADDRS" diff --git a/include/uv/win.h b/include/uv/win.h index fbd26dbc..92a95fa1 100644 --- a/include/uv/win.h +++ b/include/uv/win.h @@ -91,6 +91,7 @@ typedef struct pollfd { * variants (Linux and Darwin) */ #define SIGHUP 1 +#define SIGQUIT 3 #define SIGKILL 9 #define SIGWINCH 28 diff --git a/src/win/process.c b/src/win/process.c index ed44adc6..3e46176f 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -32,6 +32,9 @@ #include "internal.h" #include "handle-inl.h" #include "req-inl.h" +#include +#include +#include /* GetModuleBaseNameW */ #define SIGKILL 9 @@ -1194,7 +1197,116 @@ static int uv__kill(HANDLE process_handle, int signum) { return UV_EINVAL; } + /* Create a dump file for the targeted process, if the registry key + * `HKLM:Software\Microsoft\Windows\Windows Error Reporting\LocalDumps` + * exists. The location of the dumps can be influenced by the `DumpFolder` + * sub-key, which has a default value of `%LOCALAPPDATA%\CrashDumps`, see [0] + * for more detail. Note that if the dump folder does not exist, we attempt + * to create it, to match behavior with WER itself. + * [0]: https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps */ + if (signum == SIGQUIT) { + HKEY registry_key; + DWORD pid, ret; + WCHAR basename[MAX_PATH]; + + /* Get target process name. */ + GetModuleBaseNameW(process_handle, NULL, &basename[0], sizeof(basename)); + + /* Get PID of target process. */ + pid = GetProcessId(process_handle); + + /* Get LocalDumps directory path. */ + ret = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps", + 0, + KEY_QUERY_VALUE, + ®istry_key); + if (ret == ERROR_SUCCESS) { + HANDLE hDumpFile = NULL; + WCHAR dump_folder[MAX_PATH], dump_name[MAX_PATH]; + DWORD dump_folder_len = sizeof(dump_folder), key_type = 0; + ret = RegGetValueW(registry_key, + NULL, + L"DumpFolder", + RRF_RT_ANY, + &key_type, + (PVOID) dump_folder, + &dump_folder_len); + if (ret != ERROR_SUCCESS) { + /* Default value for `dump_folder` is `%LOCALAPPDATA%\CrashDumps`. */ + WCHAR* localappdata; + SHGetKnownFolderPath(&FOLDERID_LocalAppData, 0, NULL, &localappdata); + _snwprintf_s(dump_folder, + sizeof(dump_folder), + _TRUNCATE, + L"%ls\\CrashDumps", + localappdata); + CoTaskMemFree(localappdata); + } + RegCloseKey(registry_key); + + /* Create dump folder if it doesn't already exist. */ + CreateDirectoryW(dump_folder, NULL); + + /* Construct dump filename from process name and PID. */ + _snwprintf_s(dump_name, + sizeof(dump_name), + _TRUNCATE, + L"%ls\\%ls.%d.dmp", + dump_folder, + basename, + pid); + + hDumpFile = CreateFileW(dump_name, + GENERIC_WRITE, + 0, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hDumpFile != INVALID_HANDLE_VALUE) { + DWORD dump_options, sym_options; + FILE_DISPOSITION_INFO DeleteOnClose = { TRUE }; + + /* If something goes wrong while writing it out, delete the file. */ + SetFileInformationByHandle(hDumpFile, + FileDispositionInfo, + &DeleteOnClose, + sizeof(DeleteOnClose)); + + /* Tell wine to dump ELF modules as well. */ + sym_options = SymGetOptions(); + SymSetOptions(sym_options | 0x40000000); + + /* We default to a fairly complete dump. In the future, we may want to + * allow clients to customize what kind of dump to create. */ + dump_options = MiniDumpWithFullMemory | + MiniDumpIgnoreInaccessibleMemory | + MiniDumpWithAvxXStateContext; + + if (MiniDumpWriteDump(process_handle, + pid, + hDumpFile, + dump_options, + NULL, + NULL, + NULL)) { + /* Don't delete the file on close if we successfully wrote it out. */ + FILE_DISPOSITION_INFO DontDeleteOnClose = { FALSE }; + SetFileInformationByHandle(hDumpFile, + FileDispositionInfo, + &DontDeleteOnClose, + sizeof(DontDeleteOnClose)); + } + SymSetOptions(sym_options); + CloseHandle(hDumpFile); + } + } + } + switch (signum) { + case SIGQUIT: case SIGTERM: case SIGKILL: case SIGINT: {