diff options
author | Mike McLaughlin <mikem@microsoft.com> | 2021-07-08 02:34:17 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-07-08 02:34:17 +0300 |
commit | 3d10d70a450e13d7cdbf1f843ff0b6bf768448cc (patch) | |
tree | 3a1577c4446ec728ecbda7ea8474e4fb9e69d620 /src/coreclr/pal | |
parent | 0ef56df4a7c0753296a736b071946d6bc833fdc2 (diff) |
Add crash report file for VS4Mac Watson (#54934)
Add crash report file for VS4Mac Watson
Add the --crashreport createdump command line option to enable this feature.
Add crash thread (--crashthread) and signal number (--signal) command line options. Use std::vector for g_argCreateDump.
Add the COMPlus_EnableCrashReport environment variable to enable this feature.
Add simple json writer (JsonWriter).
Remote unwinder: special case when encoding == 0 and IP after syscall opcode to popping return address
Remove unwinder: add functionStart return from remote unwind API
Add ModuleInfo struct containing the info about a native or managed module.
Add StackFrame struct containing the info about a native or managed stack frame.
Add signal number to PROCAbort.
Currently the Linux crash report is stubbed out (empty).
Better createdump logging.
Add CrashReportWriter class.
Diffstat (limited to 'src/coreclr/pal')
-rw-r--r-- | src/coreclr/pal/inc/pal.h | 2 | ||||
-rw-r--r-- | src/coreclr/pal/src/exception/remote-unwind.cpp | 82 | ||||
-rw-r--r-- | src/coreclr/pal/src/exception/signal.cpp | 11 | ||||
-rw-r--r-- | src/coreclr/pal/src/include/pal/process.h | 10 | ||||
-rw-r--r-- | src/coreclr/pal/src/thread/process.cpp | 128 |
5 files changed, 180 insertions, 53 deletions
diff --git a/src/coreclr/pal/inc/pal.h b/src/coreclr/pal/inc/pal.h index 532c3e82d3d..cd96f5753cc 100644 --- a/src/coreclr/pal/inc/pal.h +++ b/src/coreclr/pal/inc/pal.h @@ -2410,7 +2410,7 @@ typedef BOOL(*UnwindReadMemoryCallback)(PVOID address, PVOID buffer, SIZE_T size PALIMPORT BOOL PALAPI PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers); -PALIMPORT BOOL PALAPI PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback); +PALIMPORT BOOL PALAPI PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback); #define GetLogicalProcessorCacheSizeFromOS PAL_GetLogicalProcessorCacheSizeFromOS diff --git a/src/coreclr/pal/src/exception/remote-unwind.cpp b/src/coreclr/pal/src/exception/remote-unwind.cpp index b82960cc723..af0293ba5bc 100644 --- a/src/coreclr/pal/src/exception/remote-unwind.cpp +++ b/src/coreclr/pal/src/exception/remote-unwind.cpp @@ -164,6 +164,7 @@ typedef struct _libunwindInfo { SIZE_T BaseAddress; CONTEXT *Context; + ULONG64 FunctionStart; UnwindReadMemoryCallback ReadMemory; } libunwindInfo; @@ -957,7 +958,7 @@ ExtractProcInfoFromFde( static bool SearchCompactEncodingSection( - const libunwindInfo* info, + libunwindInfo* info, unw_word_t ip, unw_word_t compactUnwindSectionAddr, unw_proc_info_t *pip) @@ -966,7 +967,11 @@ SearchCompactEncodingSection( if (!info->ReadMemory((PVOID)compactUnwindSectionAddr, §ionHeader, sizeof(sectionHeader))) { return false; } - TRACE("Unwind ver %d common off: %08x common cnt: %d pers off: %08x pers cnt: %d index off: %08x index cnt: %d\n", + int32_t offset = ip - info->BaseAddress; + + TRACE("Unwind %p offset %08x ver %d common off: %08x common cnt: %d pers off: %08x pers cnt: %d index off: %08x index cnt: %d\n", + (void*)compactUnwindSectionAddr, + offset, sectionHeader.version, sectionHeader.commonEncodingsArraySectionOffset, sectionHeader.commonEncodingsArrayCount, @@ -979,7 +984,6 @@ SearchCompactEncodingSection( return false; } - int32_t offset = ip - info->BaseAddress; unwind_info_section_header_index_entry entry; unwind_info_section_header_index_entry entryNext; bool found; @@ -1060,6 +1064,8 @@ SearchCompactEncodingSection( TRACE("Second level compressed pageEntry not found start %p end %p\n", (void*)funcStart, (void*)funcEnd); } + TRACE("Second level compressed: funcStart %p funcEnd %p pageEntry %08x pageEntryNext %08x\n", (void*)funcStart, (void*)funcEnd, pageEntry, pageEntryNext); + if (ip < funcStart || ip > funcEnd) { ERROR("ip %p not in compressed second level\n", (void*)ip); return false; @@ -1073,7 +1079,7 @@ SearchCompactEncodingSection( if (!ReadValue32(info, &addr, &encoding)) { return false; } - TRACE("Second level compressed common table: %08x for offset %08x\n", encoding, pageOffset); + TRACE("Second level compressed common table: %08x for offset %08x encodingIndex %d\n", encoding, pageOffset, encodingIndex); } else { @@ -1134,6 +1140,7 @@ SearchCompactEncodingSection( } } + info->FunctionStart = funcStart; pip->start_ip = funcStart; pip->end_ip = funcEnd; pip->lsda = lsda; @@ -1171,6 +1178,7 @@ SearchDwarfSection( ERROR("ExtractFde FAILED for ip %p\n", (void*)ip); break; } + if (ip >= ipStart && ip < ipEnd) { if (!ExtractProcInfoFromFde(info, &fdeAddr, pip, need_unwind_info)) { ERROR("ExtractProcInfoFromFde FAILED for ip %p\n", (void*)ip); @@ -1185,7 +1193,7 @@ SearchDwarfSection( static bool -GetProcInfo(unw_word_t ip, unw_proc_info_t *pip, const libunwindInfo* info, bool* step, int need_unwind_info) +GetProcInfo(unw_word_t ip, unw_proc_info_t *pip, libunwindInfo* info, bool* step, int need_unwind_info) { memset(pip, 0, sizeof(*pip)); *step = false; @@ -1297,11 +1305,12 @@ GetProcInfo(unw_word_t ip, unw_proc_info_t *pip, const libunwindInfo* info, bool } } - ERROR("Unwind info not found for %p format %08x\n", (void*)ip, pip->format); + ERROR("Unwind info not found for %p format %08x ehframeSectionAddr %p ehframeSectionSize %p\n", (void*)ip, pip->format, (void*)ehframeSectionAddr, (void*)ehframeSectionSize); return false; } #if defined(TARGET_AMD64) + static bool StepWithCompactEncodingRBPFrame(const libunwindInfo* info, compact_unwind_encoding_t compactEncoding) { @@ -1370,9 +1379,49 @@ StepWithCompactEncodingRBPFrame(const libunwindInfo* info, compact_unwind_encodi compactEncoding, (void*)context->Rip, (void*)context->Rsp, (void*)context->Rbp); return true; } -#endif + +#define AMD64_SYSCALL_OPCODE 0x050f + +static bool +StepWithCompactNoEncoding(const libunwindInfo* info) +{ + // We get here because we found the function the IP is in the compact unwind info, but the encoding is 0. This + // usually ends the unwind but here we check that the function is a syscall "wrapper" and assume there is no + // frame and pop the return address. + uint16_t opcode; + unw_word_t addr = info->Context->Rip - sizeof(opcode); + if (!ReadValue16(info, &addr, &opcode)) { + return false; + } + // Is the IP pointing just after a "syscall" opcode? + if (opcode != AMD64_SYSCALL_OPCODE) { + // There are cases where the IP points one byte after the syscall; not sure why. + addr = info->Context->Rip - sizeof(opcode) + 1; + if (!ReadValue16(info, &addr, &opcode)) { + return false; + } + // Is the IP pointing just after a "syscall" opcode + 1? + if (opcode != AMD64_SYSCALL_OPCODE) { + ERROR("StepWithCompactNoEncoding: not in syscall wrapper function\n"); + return false; + } + } + // Pop the return address from the stack + uint64_t ip; + addr = info->Context->Rsp; + if (!ReadValue64(info, &addr, &ip)) { + return false; + } + info->Context->Rip = ip; + info->Context->Rsp += sizeof(uint64_t); + TRACE("StepWithCompactNoEncoding: SUCCESS new rip %p rsp %p\n", (void*)info->Context->Rip, (void*)info->Context->Rsp); + return true; +} + +#endif // TARGET_AMD64 #if defined(TARGET_ARM64) + inline static bool ReadCompactEncodingRegister(const libunwindInfo* info, unw_word_t* addr, DWORD64* reg) { @@ -1502,7 +1551,8 @@ StepWithCompactEncodingArm64(const libunwindInfo* info, compact_unwind_encoding_ compactEncoding, (void*)context->Pc, (void*)context->Sp, (void*)context->Fp, (void*)context->Lr); return true; } -#endif + +#endif // TARGET_ARM64 static bool StepWithCompactEncoding(const libunwindInfo* info, compact_unwind_encoding_t compactEncoding, unw_word_t functionStart) @@ -1510,8 +1560,7 @@ StepWithCompactEncoding(const libunwindInfo* info, compact_unwind_encoding_t com #if defined(TARGET_AMD64) if (compactEncoding == 0) { - TRACE("Compact unwind missing for %p\n", (void*)info->Context->Rip); - return false; + return StepWithCompactNoEncoding(info); } switch (compactEncoding & UNWIND_X86_64_MODE_MASK) { @@ -1817,7 +1866,7 @@ get_proc_name(unw_addr_space_t as, unw_word_t addr, char *bufp, size_t buf_len, static int find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pip, int need_unwind_info, void *arg) { - const auto *info = (libunwindInfo*)arg; + auto *info = (libunwindInfo*)arg; #ifdef __APPLE__ bool step; if (!GetProcInfo(ip, pip, info, &step, need_unwind_info)) { @@ -1999,6 +2048,7 @@ find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pip, int nee TRACE("ip %p not in range start_ip %p end_ip %p\n", ip, pip->start_ip, pip->end_ip); return -UNW_ENOINFO; } + info->FunctionStart = pip->start_ip; return UNW_ESUCCESS; #else return _OOP_find_proc_info(start_ip, end_ip, ehFrameHdrAddr, ehFrameHdrLen, exidxFrameHdrAddr, exidxFrameHdrLen, as, ip, pip, need_unwind_info, arg); @@ -2048,12 +2098,13 @@ Function: Parameters: context - the start context in the target contextPointers - the context of the next frame + functionStart - the pointer to return the starting address of the function or nullptr baseAddress - base address of the module to find the unwind info readMemoryCallback - reads memory from the target --*/ BOOL PALAPI -PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback) +PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback) { unw_addr_space_t addrSpace = 0; unw_cursor_t cursor; @@ -2063,6 +2114,7 @@ PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *cont info.BaseAddress = baseAddress; info.Context = context; + info.FunctionStart = 0; info.ReadMemory = readMemoryCallback; #ifdef __APPLE__ @@ -2113,6 +2165,10 @@ PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *cont result = TRUE; exit: + if (functionStart) + { + *functionStart = info.FunctionStart; + } if (addrSpace != 0) { unw_destroy_addr_space(addrSpace); @@ -2124,7 +2180,7 @@ exit: BOOL PALAPI -PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback) +PAL_VirtualUnwindOutOfProc(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers, PULONG64 functionStart, SIZE_T baseAddress, UnwindReadMemoryCallback readMemoryCallback) { return FALSE; } diff --git a/src/coreclr/pal/src/exception/signal.cpp b/src/coreclr/pal/src/exception/signal.cpp index 9ab675c8df6..14dc01dca6a 100644 --- a/src/coreclr/pal/src/exception/signal.cpp +++ b/src/coreclr/pal/src/exception/signal.cpp @@ -376,7 +376,7 @@ static void invoke_previous_action(struct sigaction* action, int code, siginfo_t if (signalRestarts) { // This signal mustn't be ignored because it will be restarted. - PROCAbort(); + PROCAbort(code); } return; } @@ -391,7 +391,7 @@ static void invoke_previous_action(struct sigaction* action, int code, siginfo_t { // We can't invoke the original handler because returning from the // handler doesn't restart the exception. - PROCAbort(); + PROCAbort(code); } } else @@ -403,7 +403,8 @@ static void invoke_previous_action(struct sigaction* action, int code, siginfo_t } PROCNotifyProcessShutdown(IsRunningOnAlternateStack(context)); - PROCCreateCrashDumpIfEnabled(); + + PROCCreateCrashDumpIfEnabled(code); } /*++ @@ -575,13 +576,13 @@ static void sigsegv_handler(int code, siginfo_t *siginfo, void *context) if (SwitchStackAndExecuteHandler(code | StackOverflowFlag, siginfo, context, (size_t)handlerStackTop)) { - PROCAbort(); + PROCAbort(SIGSEGV); } } else { (void)!write(STDERR_FILENO, StackOverflowMessage, sizeof(StackOverflowMessage) - 1); - PROCAbort(); + PROCAbort(SIGSEGV); } } diff --git a/src/coreclr/pal/src/include/pal/process.h b/src/coreclr/pal/src/include/pal/process.h index 575aa49caac..b1de472ad42 100644 --- a/src/coreclr/pal/src/include/pal/process.h +++ b/src/coreclr/pal/src/include/pal/process.h @@ -149,10 +149,13 @@ Function: Aborts the process after calling the shutdown cleanup handler. This function should be called instead of calling abort() directly. +Parameters: + signal - POSIX signal number + Does not return --*/ PAL_NORETURN -VOID PROCAbort(); +VOID PROCAbort(int signal = SIGABRT); /*++ Function: @@ -172,9 +175,12 @@ Function: Creates crash dump of the process (if enabled). Can be called from the unhandled native exception handler. +Parameters: + signal - POSIX signal number + (no return value) --*/ -VOID PROCCreateCrashDumpIfEnabled(); +VOID PROCCreateCrashDumpIfEnabled(int signal); /*++ Function: diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index 54e08b7abd9..0ae3f2eecaf 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -61,6 +61,7 @@ SET_DEFAULT_DEBUG_CHANNEL(PROCESS); // some headers have code with asserts, so d #include <stdint.h> #include <dlfcn.h> #include <limits.h> +#include <vector> #ifdef __linux__ #include <sys/syscall.h> // __NR_membarrier @@ -232,7 +233,7 @@ static_assert_no_msg(CLR_SEM_MAX_NAMELEN <= MAX_PATH); Volatile<PSHUTDOWN_CALLBACK> g_shutdownCallback = nullptr; // Crash dump generating program arguments. Initialized in PROCAbortInitialize(). -char* g_argvCreateDump[8] = { nullptr }; +std::vector<const char*> g_argvCreateDump; // // Key used for associating CPalThread's with the underlying pthread @@ -1334,9 +1335,10 @@ static BOOL PROCEndProcess(HANDLE hProcess, UINT uExitCode, BOOL bTerminateUncon { // abort() has the semantics that // (1) it doesn't run atexit handlers - // (2) can invoke CrashReporter or produce a coredump, - // which is appropriate for TerminateProcess calls - PROCAbort(); + // (2) can invoke CrashReporter or produce a coredump, which is appropriate for TerminateProcess calls + // TerminationRequestHandlingRoutine in synchmanager.cpp sets the exit code to this special value. The + // Watson analyzer needs to know that the process was terminated with a SIGTERM. + PROCAbort(uExitCode == (128 + SIGTERM) ? SIGTERM : SIGABRT); } else { @@ -3085,6 +3087,28 @@ PROCNotifyProcessShutdownDestructor() } /*++ +Function: + PROCFormatInt + + Helper function to format an ULONG32 as a string. + +--*/ +char* +PROCFormatInt(ULONG32 value) +{ + char* buffer = (char*)InternalMalloc(128); + if (buffer != nullptr) + { + if (sprintf_s(buffer, 128, "%d", value) == -1) + { + free(buffer); + buffer = nullptr; + } + } + return buffer; +} + +/*++ Function PROCBuildCreateDumpCommandLine @@ -3097,12 +3121,13 @@ Return --*/ BOOL PROCBuildCreateDumpCommandLine( - const char** argv, + std::vector<const char*>& argv, char** pprogram, char** ppidarg, char* dumpName, char* dumpType, - BOOL diag) + BOOL diag, + BOOL crashReport) { if (g_szCoreCLRPath == nullptr) { @@ -3132,50 +3157,51 @@ PROCBuildCreateDumpCommandLine( { return FALSE; } - char* pidarg = *ppidarg = (char*)InternalMalloc(128); - if (pidarg == nullptr) + *ppidarg = PROCFormatInt(gPID); + if (*ppidarg == nullptr) { return FALSE; } - if (sprintf_s(pidarg, 128, "%d", gPID) == -1) - { - return FALSE; - } - *argv++ = program; + argv.push_back(program); if (dumpName != nullptr) { - *argv++ = "--name"; - *argv++ = dumpName; + argv.push_back("--name"); + argv.push_back(dumpName); } if (dumpType != nullptr) { if (strcmp(dumpType, "1") == 0) { - *argv++ = "--normal"; + argv.push_back("--normal"); } else if (strcmp(dumpType, "2") == 0) { - *argv++ = "--withheap"; + argv.push_back("--withheap"); } else if (strcmp(dumpType, "3") == 0) { - *argv++ = "--triage"; + argv.push_back("--triage"); } else if (strcmp(dumpType, "4") == 0) { - *argv++ = "--full"; + argv.push_back("--full"); } } if (diag) { - *argv++ = "--diag"; + argv.push_back("--diag"); + } + + if (crashReport) + { + argv.push_back("--crashreport"); } - *argv++ = pidarg; - *argv = nullptr; + argv.push_back(*ppidarg); + argv.push_back(nullptr); return TRUE; } @@ -3190,7 +3216,7 @@ Function: (no return value) --*/ BOOL -PROCCreateCrashDump(char** argv) +PROCCreateCrashDump(std::vector<const char*>& argv) { // Fork the core dump child process. pid_t childpid = fork(); @@ -3204,7 +3230,7 @@ PROCCreateCrashDump(char** argv) else if (childpid == 0) { // Child process - if (execve(argv[0], argv, palEnvironment) == -1) + if (execve(argv[0], (char**)argv.data(), palEnvironment) == -1) { ERROR("PROCCreateCrashDump: execve() FAILED %d (%s)\n", errno, strerror(errno)); return false; @@ -3258,10 +3284,12 @@ PROCAbortInitialize() char* dumpType = getenv("COMPlus_DbgMiniDumpType"); char* diagStr = getenv("COMPlus_CreateDumpDiagnostics"); BOOL diag = diagStr != nullptr && strcmp(diagStr, "1") == 0; + char* crashReportStr = getenv("COMPlus_EnableCrashReport"); + BOOL crashReport = crashReportStr != nullptr && strcmp(crashReportStr, "1") == 0; char* program = nullptr; char* pidarg = nullptr; - if (!PROCBuildCreateDumpCommandLine((const char **)g_argvCreateDump, &program, &pidarg, dumpName, dumpType, diag)) + if (!PROCBuildCreateDumpCommandLine(g_argvCreateDump, &program, &pidarg, dumpName, dumpType, diag, crashReport)) { return FALSE; } @@ -3296,7 +3324,7 @@ PAL_GenerateCoreDump( INT dumpType, BOOL diag) { - char* argvCreateDump[8] = { nullptr }; + std::vector<const char*> argvCreateDump; char dumpTypeStr[16]; if (dumpType < 1 || dumpType > 4) @@ -3313,7 +3341,7 @@ PAL_GenerateCoreDump( } char* program = nullptr; char* pidarg = nullptr; - BOOL result = PROCBuildCreateDumpCommandLine((const char **)argvCreateDump, &program, &pidarg, (char*)dumpName, dumpTypeStr, diag); + BOOL result = PROCBuildCreateDumpCommandLine(argvCreateDump, &program, &pidarg, (char*)dumpName, dumpTypeStr, diag, false); if (result) { result = PROCCreateCrashDump(argvCreateDump); @@ -3330,15 +3358,48 @@ Function: Creates crash dump of the process (if enabled). Can be called from the unhandled native exception handler. +Parameters: + signal - POSIX signal number + (no return value) --*/ VOID -PROCCreateCrashDumpIfEnabled() +PROCCreateCrashDumpIfEnabled(int signal) { // If enabled, launch the create minidump utility and wait until it completes - if (g_argvCreateDump[0] != nullptr) + if (!g_argvCreateDump.empty()) { - PROCCreateCrashDump(g_argvCreateDump); + std::vector<const char*> argv(g_argvCreateDump); + char* signalArg = nullptr; + char* crashThreadArg = nullptr; + + if (signal != 0) + { + // Remove the terminating nullptr + argv.pop_back(); + + // Add the Windows exception code to the command line + signalArg = PROCFormatInt(signal); + if (signalArg != nullptr) + { + argv.push_back("--signal"); + argv.push_back(signalArg); + } + + // Add the current thread id to the command line. This function is always called on the crashing thread. + crashThreadArg = PROCFormatInt(THREADSilentGetCurrentThreadId()); + if (crashThreadArg != nullptr) + { + argv.push_back("--crashthread"); + argv.push_back(crashThreadArg); + } + argv.push_back(nullptr); + } + + PROCCreateCrashDump(argv); + + free(signalArg); + free(crashThreadArg); } } @@ -3349,16 +3410,19 @@ Function: Aborts the process after calling the shutdown cleanup handler. This function should be called instead of calling abort() directly. +Parameters: + signal - POSIX signal number + Does not return --*/ PAL_NORETURN VOID -PROCAbort() +PROCAbort(int signal) { // Do any shutdown cleanup before aborting or creating a core dump PROCNotifyProcessShutdown(); - PROCCreateCrashDumpIfEnabled(); + PROCCreateCrashDumpIfEnabled(signal); // Restore the SIGABORT handler to prevent recursion SEHCleanupAbort(); |