Line data Source code
1 : // Copyright (c) 2014-2025 The Dash Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #if defined(HAVE_CONFIG_H)
6 : #include <config/bitcoin-config.h>
7 : #endif // HAVE_CONFIG_H
8 :
9 : #include <stacktraces.h>
10 : #include <fs.h>
11 : #include <logging.h>
12 : #include <streams.h>
13 : #include <threadsafety.h>
14 : #include <util/strencodings.h>
15 :
16 : #include <map>
17 : #include <vector>
18 : #include <memory>
19 : #include <atomic>
20 :
21 : #if defined(WIN32)
22 : #include <windows.h>
23 : #include <dbghelp.h>
24 : #include <thread>
25 : #else
26 : #ifdef ENABLE_STACKTRACES
27 : #include <execinfo.h>
28 : #endif
29 : #include <unistd.h>
30 : #include <csignal>
31 : #endif
32 :
33 : #if !defined(WIN32)
34 : #include <dlfcn.h>
35 : #if !defined(__APPLE__)
36 : #include <link.h>
37 : #endif
38 : #endif
39 :
40 : #if defined(__APPLE__)
41 : #include <mach-o/dyld.h>
42 : #include <mach/mach_init.h>
43 : #include <sys/sysctl.h>
44 : #include <mach/mach_vm.h>
45 : #endif
46 :
47 : #ifdef ENABLE_STACKTRACES
48 : #include <backtrace.h>
49 : #endif
50 :
51 : #include <cstring>
52 :
53 0 : std::string DemangleSymbol(const std::string& name)
54 : {
55 : #if defined(__GNUC__) || defined(__clang__)
56 0 : int status = -4; // some arbitrary value to eliminate the compiler warning
57 0 : char* str = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
58 0 : if (status != 0) {
59 0 : free(str);
60 0 : return name;
61 : }
62 0 : std::string ret = str;
63 0 : free(str);
64 0 : return ret;
65 : #else
66 : // TODO other platforms/compilers
67 : return name;
68 : #endif
69 0 : }
70 :
71 : // set to true when the abort signal should not handled
72 : // this is the case when the terminate handler or an assert already handled the exception
73 : static std::atomic<bool> skipAbortSignal(false);
74 :
75 223 : static ssize_t GetExeFileNameImpl(char* buf, size_t bufSize)
76 : {
77 : #if defined(WIN32)
78 : std::vector<TCHAR> tmp(bufSize);
79 : DWORD len = GetModuleFileName(nullptr, tmp.data(), bufSize);
80 : if (len >= bufSize) {
81 : return len;
82 : }
83 : for (size_t i = 0; i < len; i++) {
84 : buf[i] = (char)tmp[i];
85 : }
86 : return len;
87 : #elif defined(__APPLE__)
88 223 : uint32_t bufSize2 = (uint32_t)bufSize;
89 223 : if (_NSGetExecutablePath(buf, &bufSize2) != 0) {
90 : // it's not entirely clear if the value returned by _NSGetExecutablePath includes the null character
91 0 : return bufSize2 + 1;
92 : }
93 : // TODO do we have to call realpath()? The path returned by _NSGetExecutablePath may include ., .. symbolic links
94 223 : return strlen(buf);
95 : #else
96 : ssize_t len = readlink("/proc/self/exe", buf, bufSize - 1);
97 : if (len == -1) {
98 : return -1;
99 : }
100 : return len;
101 : #endif
102 223 : }
103 :
104 223 : static std::string GetExeFileName()
105 : {
106 223 : std::vector<char> buf(1024);
107 223 : while (true) {
108 223 : ssize_t len = GetExeFileNameImpl(buf.data(), buf.size());
109 223 : if (len < 0) {
110 0 : return "";
111 : }
112 223 : if (len < int64_t(buf.size())) {
113 223 : return std::string(buf.begin(), buf.begin() + len);
114 : }
115 0 : buf.resize(buf.size() * 2);
116 : }
117 223 : }
118 :
119 223 : static std::string g_exeFileName = GetExeFileName();
120 223 : static std::string g_exeFileBaseName = fs::PathToString(fs::PathFromString(g_exeFileName).filename());
121 :
122 : #ifdef ENABLE_STACKTRACES
123 : static void my_backtrace_error_callback (void *data, const char *msg,
124 : int errnum)
125 : {
126 : }
127 :
128 : static backtrace_state* GetLibBacktraceState()
129 : {
130 : #if defined(WIN32)
131 : // libbacktrace is not able to handle the DWARF debuglink in the .exe
132 : // but luckily we can just specify the .dbg file here as it's a valid PE/XCOFF file
133 : static std::string debugFileName = g_exeFileName + ".dbg";
134 : static const char* exeFileNamePtr = fs::exists(fs::absolute(fs::PathFromString(debugFileName))) ? debugFileName.c_str() : g_exeFileName.c_str();
135 : #else
136 : static const char* exeFileNamePtr = g_exeFileName.empty() ? nullptr : g_exeFileName.c_str();
137 : #endif
138 : static backtrace_state* st = backtrace_create_state(exeFileNamePtr, 1, my_backtrace_error_callback, nullptr);
139 : return st;
140 : }
141 : #endif // ENABLE_STACKTRACES
142 :
143 : #if defined(WIN32)
144 : static uint64_t GetBaseAddress()
145 : {
146 : return 0;
147 : }
148 :
149 : #ifdef ENABLE_STACKTRACES
150 : // PC addresses returned by StackWalk64 are in the real mapped space, while libbacktrace expects them to be in the
151 : // default mapped space starting at 0x400000. This method converts the address.
152 : // TODO this is probably the same reason libbacktrace is not able to gather the stacktrace on Windows (returns pointers like 0x1 or 0xfffffff)
153 : // If they ever fix this problem, we might end up converting to invalid addresses here
154 : static uint64_t ConvertAddress(uint64_t addr)
155 : {
156 : MEMORY_BASIC_INFORMATION mbi;
157 :
158 : if (!VirtualQuery((PVOID)addr, &mbi, sizeof(mbi)))
159 : return 0;
160 :
161 : uint64_t hMod = (uint64_t)mbi.AllocationBase;
162 : uint64_t offset = addr - hMod;
163 : return 0x400000 + offset;
164 : }
165 : #endif // ENABLE_STACKTRACES
166 :
167 : static __attribute__((noinline)) std::vector<uint64_t> GetStackFrames(size_t skip, size_t max_frames, const CONTEXT* pContext = nullptr)
168 : {
169 : #ifdef ENABLE_STACKTRACES
170 : volatile size_t skip_frames = skip;
171 :
172 : // We can't use libbacktrace for stack unwinding on Windows as it returns invalid addresses (like 0x1 or 0xffffffff)
173 : // dbghelp is not thread safe
174 : static StdMutex m;
175 : StdLockGuard l(m);
176 :
177 : HANDLE process = GetCurrentProcess();
178 : HANDLE thread = GetCurrentThread();
179 :
180 : CONTEXT context;
181 : if (!pContext) {
182 : memset(&context, 0, sizeof(CONTEXT));
183 : context.ContextFlags = CONTEXT_FULL;
184 : RtlCaptureContext(&context);
185 : } else {
186 : memcpy(&context, pContext, sizeof(CONTEXT));
187 : }
188 :
189 : DWORD image;
190 : STACKFRAME64 stackframe;
191 : ZeroMemory(&stackframe, sizeof(STACKFRAME64));
192 :
193 : #if defined(__i386__)
194 : image = IMAGE_FILE_MACHINE_I386;
195 : stackframe.AddrPC.Offset = context.Eip;
196 : stackframe.AddrPC.Mode = AddrModeFlat;
197 : stackframe.AddrFrame.Offset = context.Ebp;
198 : stackframe.AddrFrame.Mode = AddrModeFlat;
199 : stackframe.AddrStack.Offset = context.Esp;
200 : stackframe.AddrStack.Mode = AddrModeFlat;
201 : #elif defined(__x86_64__)
202 : image = IMAGE_FILE_MACHINE_AMD64;
203 : stackframe.AddrPC.Offset = context.Rip;
204 : stackframe.AddrPC.Mode = AddrModeFlat;
205 : stackframe.AddrFrame.Offset = context.Rbp;
206 : stackframe.AddrFrame.Mode = AddrModeFlat;
207 : stackframe.AddrStack.Offset = context.Rsp;
208 : stackframe.AddrStack.Mode = AddrModeFlat;
209 : if (!pContext) {
210 : skip_frames = skip_frames + 1; // skip this method
211 : }
212 : #else
213 : #error unsupported architecture
214 : #endif
215 :
216 : std::vector<uint64_t> ret;
217 :
218 : size_t i = 0;
219 : while (ret.size() < max_frames) {
220 : BOOL result = StackWalk64(
221 : image, process, thread,
222 : &stackframe, &context, nullptr,
223 : SymFunctionTableAccess64, SymGetModuleBase64, nullptr);
224 :
225 : if (!result) {
226 : break;
227 : }
228 : if (i >= skip_frames) {
229 : uint64_t pc = ConvertAddress(stackframe.AddrPC.Offset);
230 : if (pc == 0) {
231 : pc = stackframe.AddrPC.Offset;
232 : }
233 : ret.emplace_back(pc);
234 : }
235 : i++;
236 : }
237 :
238 : return ret;
239 : #else
240 : return {};
241 : #endif // ENABLE_STACKTRACES
242 : }
243 : #else
244 :
245 : #if defined(__APPLE__)
246 0 : static uint64_t GetBaseAddress()
247 : {
248 : mach_port_name_t target_task;
249 : vm_map_offset_t vmoffset;
250 : vm_map_size_t vmsize;
251 0 : uint32_t nesting_depth = 0;
252 : struct vm_region_submap_info_64 vbr;
253 0 : mach_msg_type_number_t vbrcount = 16;
254 : kern_return_t kr;
255 :
256 0 : kr = task_for_pid(mach_task_self(), getpid(), &target_task);
257 0 : if (kr != KERN_SUCCESS) {
258 0 : return 0;
259 : }
260 :
261 0 : kr = mach_vm_region_recurse(target_task, &vmoffset, &vmsize, &nesting_depth, (vm_region_recurse_info_t)&vbr, &vbrcount);
262 0 : if (kr != KERN_SUCCESS) {
263 0 : return 0;
264 : }
265 :
266 0 : return vmoffset;
267 0 : }
268 : #else
269 : static int dl_iterate_callback(struct dl_phdr_info* info, size_t s, void* data)
270 : {
271 : uint64_t* p = reinterpret_cast<uint64_t*>(data);
272 : if (info->dlpi_name == nullptr || info->dlpi_name[0] == '\0') {
273 : *p = info->dlpi_addr;
274 : }
275 : return 0;
276 : }
277 :
278 : static uint64_t GetBaseAddress()
279 : {
280 : uint64_t basePtr = 0;
281 : dl_iterate_phdr(dl_iterate_callback, &basePtr);
282 : return basePtr;
283 : }
284 : #endif
285 :
286 0 : static __attribute__((noinline)) std::vector<uint64_t> GetStackFrames(size_t skip, size_t max_frames)
287 : {
288 : #ifdef ENABLE_STACKTRACES
289 : // FYI, this is not using libbacktrace, but "backtrace()" from <execinfo.h>
290 : std::vector<void*> buf(max_frames);
291 : int count = backtrace(buf.data(), (int)buf.size());
292 : if (count == 0) {
293 : return {};
294 : }
295 : buf.resize((size_t)count);
296 :
297 : std::vector<uint64_t> ret;
298 : ret.reserve(count);
299 : for (size_t i = skip + 1; i < buf.size(); i++) {
300 : ret.emplace_back((uint64_t) buf[i]);
301 : }
302 : return ret;
303 : #else
304 0 : return {};
305 : #endif // ENABLE_STACKTRACES
306 : }
307 : #endif
308 :
309 0 : struct stackframe_info {
310 0 : uint64_t pc{0};
311 : std::string filename;
312 0 : int lineno{-1};
313 : std::string function;
314 :
315 0 : SERIALIZE_METHODS(stackframe_info, obj)
316 : {
317 0 : READWRITE(obj.pc, obj.filename, obj.lineno, obj.function);
318 0 : }
319 : };
320 :
321 : #ifdef ENABLE_STACKTRACES
322 : static int my_backtrace_full_callback (void *data, uintptr_t pc, const char *filename, int lineno, const char *function)
323 : {
324 : auto sis = (std::vector<stackframe_info>*)data;
325 : stackframe_info si;
326 : si.pc = pc;
327 : si.lineno = lineno;
328 : if (filename) {
329 : si.filename = filename;
330 : }
331 : if (function) {
332 : si.function = DemangleSymbol(function);
333 : }
334 : sis->emplace_back(si);
335 : if (sis->size() >= 128) {
336 : // abort
337 : return 1;
338 : }
339 : if (si.function == "mainCRTStartup" ||
340 : si.function == "pthread_create_wrapper" ||
341 : si.function == "__tmainCRTStartup") {
342 : // on Windows, stack frames are unwinded into invalid PCs after entry points
343 : // this doesn't catch all cases unfortunately
344 : return 1;
345 : }
346 : return 0;
347 : }
348 :
349 : static std::vector<stackframe_info> GetStackFrameInfos(const std::vector<uint64_t>& stackframes)
350 : {
351 : std::vector<stackframe_info> infos;
352 : infos.reserve(stackframes.size());
353 :
354 : for (uint64_t stackframe : stackframes) {
355 : if (backtrace_pcinfo(GetLibBacktraceState(), stackframe, my_backtrace_full_callback, my_backtrace_error_callback, &infos)) {
356 : break;
357 : }
358 : }
359 :
360 : return infos;
361 : }
362 : #else
363 0 : static std::vector<stackframe_info> GetStackFrameInfos(const std::vector<uint64_t>& stackframes)
364 : {
365 0 : return {};
366 : }
367 : #endif // ENABLE_STACKTRACES
368 :
369 0 : struct crash_info_header
370 : {
371 : std::string magic;
372 0 : uint16_t version{0};
373 : std::string exeFileName;
374 :
375 0 : SERIALIZE_METHODS(crash_info_header, obj)
376 : {
377 0 : READWRITE(obj.magic, obj.version, obj.exeFileName);
378 0 : }
379 : };
380 :
381 0 : struct crash_info
382 : {
383 : std::string crashDescription;
384 : std::vector<uint64_t> stackframes;
385 : std::vector<stackframe_info> stackframeInfos;
386 :
387 0 : SERIALIZE_METHODS(crash_info, obj)
388 : {
389 0 : READWRITE(obj.crashDescription, obj.stackframes, obj.stackframeInfos);
390 0 : }
391 :
392 0 : void ConvertAddresses(int64_t offset)
393 : {
394 0 : for (auto& sf : stackframes) {
395 0 : sf += offset;
396 : }
397 0 : for (auto& sfi : stackframeInfos) {
398 0 : sfi.pc += offset;
399 : }
400 0 : }
401 : };
402 :
403 0 : static std::string GetCrashInfoStrNoDebugInfo(crash_info ci)
404 : {
405 0 : static uint64_t basePtr = GetBaseAddress();
406 :
407 0 : CDataStream ds(SER_DISK, 0);
408 :
409 0 : crash_info_header hdr;
410 0 : hdr.magic = "DashCrashInfo";
411 0 : hdr.version = 1;
412 0 : hdr.exeFileName = g_exeFileBaseName;
413 0 : ds << hdr;
414 :
415 0 : ci.ConvertAddresses(-(int64_t)basePtr);
416 0 : ds << ci;
417 :
418 0 : auto ciStr = EncodeBase32(ds.str());
419 0 : std::string s = ci.crashDescription + "\n";
420 0 : s += strprintf("No debug information available for stacktrace. You should add debug information and then run:\n"
421 : "%s -printcrashinfo=%s\n", g_exeFileBaseName, ciStr);
422 0 : return s;
423 0 : }
424 :
425 : static std::string GetCrashInfoStr(const crash_info& ci, size_t spaces = 2);
426 :
427 0 : std::string GetCrashInfoStrFromSerializedStr(const std::string& ciStr)
428 : {
429 0 : static uint64_t basePtr = GetBaseAddress();
430 :
431 0 : auto opt_buf = DecodeBase32(ciStr);
432 0 : if (!opt_buf.has_value() || opt_buf->empty()) {
433 0 : return "Error while deserializing crash info";
434 : }
435 :
436 0 : CDataStream ds(*opt_buf, SER_DISK, 0);
437 :
438 0 : crash_info_header hdr;
439 : try {
440 0 : ds >> hdr;
441 0 : } catch (...) {
442 0 : return "Error while deserializing crash info header";
443 0 : }
444 :
445 0 : if (hdr.magic != "DashCrashInfo") {
446 0 : return "Invalid magic string";
447 : }
448 0 : if (hdr.version != 1) {
449 0 : return "Unsupported version";
450 : }
451 0 : if (hdr.exeFileName != g_exeFileBaseName) {
452 0 : return "Crash info is not for this executable";
453 : }
454 :
455 0 : crash_info ci;
456 : try {
457 0 : ds >> ci;
458 0 : } catch (...) {
459 0 : return "Error while deserializing crash info";
460 0 : }
461 :
462 0 : ci.ConvertAddresses(basePtr);
463 :
464 0 : if (ci.stackframeInfos.empty()) {
465 0 : std::vector<uint64_t> stackframes(ci.stackframes.begin(), ci.stackframes.end());
466 0 : ci.stackframeInfos = GetStackFrameInfos(stackframes);
467 0 : }
468 :
469 0 : return GetCrashInfoStr(ci);
470 0 : }
471 :
472 0 : static std::string GetCrashInfoStr(const crash_info& ci, size_t spaces)
473 : {
474 : // Check if we have any useful debug information at all
475 : // libbacktrace may return stackframe_info entries but with empty filenames and functions
476 : // when it can find the binary but can't resolve symbols
477 0 : bool hasUsefulInfo = std::any_of(ci.stackframeInfos.begin(), ci.stackframeInfos.end(),
478 0 : [](const auto& si) { return !si.filename.empty() || !si.function.empty(); });
479 :
480 0 : if (!hasUsefulInfo) {
481 0 : return GetCrashInfoStrNoDebugInfo(ci);
482 : }
483 :
484 0 : std::string sp;
485 0 : for (size_t i = 0; i < spaces; i++) {
486 0 : sp += " ";
487 0 : }
488 :
489 0 : std::vector<std::string> lstrs;
490 0 : lstrs.reserve(ci.stackframeInfos.size());
491 :
492 0 : for (const auto& si : ci.stackframeInfos) {
493 0 : std::string lstr;
494 0 : if (!si.filename.empty()) {
495 0 : lstr += fs::PathToString(fs::PathFromString(si.filename).filename());
496 0 : } else {
497 0 : lstr += "<unknown-file>";
498 : }
499 0 : if (si.lineno != 0) {
500 0 : lstr += strprintf(":%d", si.lineno);
501 0 : }
502 :
503 0 : lstrs.emplace_back(lstr);
504 0 : }
505 :
506 : // get max "filename:line" length so we can better format it
507 0 : size_t lstrlen = std::max_element(lstrs.begin(), lstrs.end(), [](const std::string& a, const std::string& b) { return a.size() < b.size(); })->size();
508 :
509 0 : std::string fmtStr = strprintf("%%2d#: (0x%%08X) %%-%ds - %%s\n", lstrlen);
510 :
511 0 : std::string s = ci.crashDescription + "\n";
512 0 : for (size_t i = 0; i < ci.stackframeInfos.size(); i++) {
513 0 : auto& si = ci.stackframeInfos[i];
514 :
515 0 : auto& lstr = lstrs[i];
516 :
517 0 : std::string fstr;
518 0 : if (!si.function.empty()) {
519 0 : fstr = si.function;
520 0 : } else {
521 0 : fstr = "???";
522 : }
523 :
524 0 : std::string s2 = strprintf(fmtStr, i, si.pc, lstr, fstr);
525 :
526 0 : s += sp;
527 0 : s += s2;
528 0 : }
529 0 : return s;
530 0 : }
531 :
532 0 : static void PrintCrashInfo(const crash_info& ci)
533 : {
534 0 : auto str = GetCrashInfoStr(ci);
535 0 : LogPrintf("%s", str); /* Continued */
536 0 : tfm::format(std::cerr, "%s", str);
537 0 : fflush(stderr);
538 0 : }
539 :
540 : #ifdef ENABLE_CRASH_HOOKS
541 : static StdMutex g_stacktraces_mutex;
542 : static std::map<void*, std::shared_ptr<std::vector<uint64_t>>> g_stacktraces;
543 :
544 : #ifdef CRASH_HOOKS_WRAPPED_CXX_ABI
545 : // These come in through -Wl,-wrap
546 : extern "C" void* __real___cxa_allocate_exception(size_t thrown_size);
547 : extern "C" void __real___cxa_free_exception(void * thrown_exception);
548 : #if defined(WIN32)
549 : extern "C" void __real__assert(const char *assertion, const char *file, unsigned int line);
550 : extern "C" void __real__wassert(const wchar_t *assertion, const wchar_t *file, unsigned int line);
551 : #else
552 : extern "C" void __real___assert_fail(const char *assertion, const char *file, unsigned int line, const char *function);
553 : #endif
554 : #else
555 : // Clang does not support -Wl,-wrap, so we must use dlsym
556 : // This is ok because at the same time Clang only supports dynamic linking to libc/libc++
557 : extern "C" void* __real___cxa_allocate_exception(size_t thrown_size)
558 : {
559 : static auto f = (void*(*)(size_t))dlsym(RTLD_NEXT, "__cxa_allocate_exception");
560 : return f(thrown_size);
561 : }
562 : extern "C" void __real___cxa_free_exception(void * thrown_exception)
563 : {
564 : static auto f = (void(*)(void*))dlsym(RTLD_NEXT, "__cxa_free_exception");
565 : return f(thrown_exception);
566 : }
567 : #if defined(__clang__) && defined(__APPLE__)
568 : extern "C" void __attribute__((noreturn)) __real___assert_rtn(const char *function, const char *file, int line, const char *assertion)
569 : {
570 : static auto f = (void(__attribute__((noreturn)) *) (const char*, const char*, int, const char*))dlsym(RTLD_NEXT, "__assert_rtn");
571 : f(function, file, line, assertion);
572 : }
573 : #elif defined(WIN32)
574 : #error not supported on WIN32 (no dlsym support)
575 : #else
576 : extern "C" void __real___assert_fail(const char *assertion, const char *file, unsigned int line, const char *function)
577 : {
578 : static auto f = (void(*)(const char*, const char*, unsigned int, const char*))dlsym(RTLD_NEXT, "__assert_fail");
579 : f(assertion, file, line, function);
580 : }
581 : #endif
582 : #endif
583 :
584 : #ifdef CRASH_HOOKS_WRAPPED_CXX_ABI
585 : #define WRAPPED_NAME(x) __wrap_##x
586 : #else
587 : #define WRAPPED_NAME(x) x
588 : #endif
589 :
590 : extern "C" void* __attribute__((noinline)) WRAPPED_NAME(__cxa_allocate_exception)(size_t thrown_size)
591 : {
592 : // grab the current stack trace and store it in the global exception->stacktrace map
593 : auto localSt = GetStackFrames(1, 16);
594 :
595 : // WARNING keep this as it is and don't try to optimize it (no std::move, no std::make_shared, ...)
596 : // trying to optimize this will cause the optimizer to move the GetStackFrames() call deep into the stl libs
597 : std::shared_ptr<std::vector<uint64_t>> st(new std::vector<uint64_t>(localSt));
598 :
599 : void* p = __real___cxa_allocate_exception(thrown_size);
600 :
601 : StdLockGuard l(g_stacktraces_mutex);
602 : g_stacktraces.emplace(p, st);
603 : return p;
604 : }
605 :
606 : extern "C" void __attribute__((noinline)) WRAPPED_NAME(__cxa_free_exception)(void * thrown_exception)
607 : {
608 : __real___cxa_free_exception(thrown_exception);
609 :
610 : StdLockGuard l(g_stacktraces_mutex);
611 : g_stacktraces.erase(thrown_exception);
612 : }
613 :
614 : static __attribute__((noinline)) crash_info GetCrashInfoFromAssertion(const char* assertion, const char* file, int line, const char* function)
615 : {
616 : crash_info ci;
617 : ci.stackframes = GetStackFrames(1, 16);
618 : ci.crashDescription = "Assertion failure:";
619 : if (assertion) {
620 : ci.crashDescription += strprintf("\n assertion: %s", assertion);
621 : }
622 : if (file) {
623 : ci.crashDescription += strprintf("\n file: %s, line: %d", file, line);
624 : }
625 : if (function) {
626 : ci.crashDescription += strprintf("\n function: %s", function);
627 : }
628 : ci.stackframeInfos = GetStackFrameInfos(ci.stackframes);
629 : return ci;
630 : }
631 :
632 : #if defined(__clang__) && defined(__APPLE__)
633 : extern "C" void __attribute__((noinline)) WRAPPED_NAME(__assert_rtn)(const char *function, const char *file, int line, const char *assertion)
634 : {
635 : auto ci = GetCrashInfoFromAssertion(assertion, file, line, function);
636 : PrintCrashInfo(ci);
637 : skipAbortSignal = true;
638 : __real___assert_rtn(function, file, line, assertion);
639 : }
640 : #elif defined(WIN32)
641 : extern "C" void __attribute__((noinline)) WRAPPED_NAME(_assert)(const char *assertion, const char *file, unsigned int line)
642 : {
643 : auto ci = GetCrashInfoFromAssertion(assertion, file, line, nullptr);
644 : PrintCrashInfo(ci);
645 : skipAbortSignal = true;
646 : __real__assert(assertion, file, line);
647 : }
648 : extern "C" void __attribute__((noinline)) WRAPPED_NAME(_wassert)(const wchar_t *assertion, const wchar_t *file, unsigned int line)
649 : {
650 : auto ci = GetCrashInfoFromAssertion(
651 : assertion ? std::string(assertion, assertion + wcslen(assertion)).c_str() : nullptr,
652 : file ? std::string(file, file + wcslen(file)).c_str() : nullptr,
653 : line, nullptr);
654 : PrintCrashInfo(ci);
655 : skipAbortSignal = true;
656 : __real__wassert(assertion, file, line);
657 : }
658 : #else
659 : extern "C" void __attribute__((noinline)) WRAPPED_NAME(__assert_fail)(const char *assertion, const char *file, unsigned int line, const char *function)
660 : {
661 : auto ci = GetCrashInfoFromAssertion(assertion, file, line, function);
662 : PrintCrashInfo(ci);
663 : skipAbortSignal = true;
664 : __real___assert_fail(assertion, file, line, function);
665 : }
666 : #endif
667 : #endif //ENABLE_CRASH_HOOKS
668 :
669 0 : static std::shared_ptr<std::vector<uint64_t>> GetExceptionStacktrace(const std::exception_ptr& e)
670 : {
671 : #ifdef ENABLE_CRASH_HOOKS
672 : void* p = *(void**)&e;
673 :
674 : StdLockGuard l(g_stacktraces_mutex);
675 : auto it = g_stacktraces.find(p);
676 : if (it == g_stacktraces.end()) {
677 : return nullptr;
678 : }
679 : return it->second;
680 : #else
681 0 : return {};
682 : #endif
683 : }
684 :
685 0 : crash_info GetCrashInfoFromException(const std::exception_ptr& e)
686 : {
687 0 : crash_info ci;
688 0 : ci.crashDescription = "Exception: ";
689 :
690 0 : if (!e) {
691 0 : ci.crashDescription += "<null>";
692 0 : return ci;
693 : }
694 :
695 0 : std::string type;
696 0 : std::string what;
697 :
698 0 : auto getExceptionType = [&]() -> std::string {
699 0 : auto type = abi::__cxa_current_exception_type();
700 0 : if (type && (strlen(type->name()) > 0)) {
701 0 : return DemangleSymbol(type->name());
702 : }
703 0 : return "<unknown>";
704 0 : };
705 :
706 : try {
707 : // rethrow and catch the exception as there is no other way to reliably cast to the real type (not possible with RTTI)
708 0 : std::rethrow_exception(e);
709 0 : } catch (const std::exception& e2) {
710 0 : type = getExceptionType();
711 0 : what = GetExceptionWhat(e2);
712 0 : } catch (const std::string& e2) {
713 0 : type = getExceptionType();
714 0 : what = GetExceptionWhat(e2);
715 0 : } catch (const char* e2) {
716 0 : type = getExceptionType();
717 0 : what = GetExceptionWhat(e2);
718 0 : } catch (int e2) {
719 0 : type = getExceptionType();
720 0 : what = GetExceptionWhat(e2);
721 0 : } catch (...) {
722 0 : type = getExceptionType();
723 0 : what = "<unknown>";
724 0 : }
725 :
726 0 : ci.crashDescription += strprintf("type=%s, what=\"%s\"", type, what);
727 :
728 0 : auto stackframes = GetExceptionStacktrace(e);
729 0 : if (stackframes) {
730 0 : ci.stackframes = *stackframes;
731 0 : ci.stackframeInfos = GetStackFrameInfos(ci.stackframes);
732 0 : }
733 :
734 0 : return ci;
735 0 : }
736 :
737 0 : std::string GetPrettyExceptionStr(const std::exception_ptr& e)
738 : {
739 0 : return GetCrashInfoStr(GetCrashInfoFromException(e));
740 0 : }
741 :
742 0 : static void terminate_handler()
743 : {
744 0 : auto exc = std::current_exception();
745 :
746 0 : crash_info ci;
747 :
748 0 : if (exc) {
749 0 : auto ci2 = GetCrashInfoFromException(exc);
750 0 : ci.crashDescription = strprintf("std::terminate() called due to unhandled exception\n%s", ci2.crashDescription);
751 0 : ci.stackframes = std::move(ci2.stackframes);
752 0 : ci.stackframeInfos = std::move(ci2.stackframeInfos);
753 0 : } else {
754 0 : ci.crashDescription = "std::terminate() called due unknown reason";
755 0 : ci.stackframes = GetStackFrames(0, 16);
756 0 : ci.stackframeInfos = GetStackFrameInfos(ci.stackframes);
757 : }
758 :
759 0 : PrintCrashInfo(ci);
760 :
761 0 : skipAbortSignal = true;
762 0 : std::abort();
763 0 : }
764 :
765 72 : void RegisterPrettyTerminateHander()
766 : {
767 72 : std::set_terminate(terminate_handler);
768 72 : }
769 :
770 : #if !defined(WIN32)
771 0 : static void HandlePosixSignal(int s)
772 : {
773 0 : if (s == SIGABRT && skipAbortSignal) {
774 0 : return;
775 : }
776 :
777 0 : const char* name = strsignal(s);
778 0 : if (!name) {
779 0 : name = "UNKNOWN";
780 0 : }
781 :
782 0 : crash_info ci;
783 0 : ci.crashDescription = strprintf("Posix Signal: %s", name);
784 0 : ci.stackframes = GetStackFrames(0, 16);
785 0 : ci.stackframeInfos = GetStackFrameInfos(ci.stackframes);
786 0 : PrintCrashInfo(ci);
787 :
788 : // avoid a signal loop
789 0 : skipAbortSignal = true;
790 0 : std::abort();
791 0 : }
792 : #else
793 : static void DoHandleWindowsException(EXCEPTION_POINTERS * ExceptionInfo)
794 : {
795 : std::string excType;
796 : switch(ExceptionInfo->ExceptionRecord->ExceptionCode)
797 : {
798 : case EXCEPTION_ACCESS_VIOLATION: excType = "EXCEPTION_ACCESS_VIOLATION"; break;
799 : case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: excType = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; break;
800 : case EXCEPTION_BREAKPOINT: excType = "EXCEPTION_BREAKPOINT"; break;
801 : case EXCEPTION_DATATYPE_MISALIGNMENT: excType = "EXCEPTION_DATATYPE_MISALIGNMENT"; break;
802 : case EXCEPTION_FLT_DENORMAL_OPERAND: excType = "EXCEPTION_FLT_DENORMAL_OPERAND"; break;
803 : case EXCEPTION_FLT_DIVIDE_BY_ZERO: excType = "EXCEPTION_FLT_DIVIDE_BY_ZERO"; break;
804 : case EXCEPTION_FLT_INEXACT_RESULT: excType = "EXCEPTION_FLT_INEXACT_RESULT"; break;
805 : case EXCEPTION_FLT_INVALID_OPERATION: excType = "EXCEPTION_FLT_INVALID_OPERATION"; break;
806 : case EXCEPTION_FLT_OVERFLOW: excType = "EXCEPTION_FLT_OVERFLOW"; break;
807 : case EXCEPTION_FLT_STACK_CHECK: excType = "EXCEPTION_FLT_STACK_CHECK"; break;
808 : case EXCEPTION_FLT_UNDERFLOW: excType = "EXCEPTION_FLT_UNDERFLOW"; break;
809 : case EXCEPTION_ILLEGAL_INSTRUCTION: excType = "EXCEPTION_ILLEGAL_INSTRUCTION"; break;
810 : case EXCEPTION_IN_PAGE_ERROR: excType = "EXCEPTION_IN_PAGE_ERROR"; break;
811 : case EXCEPTION_INT_DIVIDE_BY_ZERO: excType = "EXCEPTION_INT_DIVIDE_BY_ZERO"; break;
812 : case EXCEPTION_INT_OVERFLOW: excType = "EXCEPTION_INT_OVERFLOW"; break;
813 : case EXCEPTION_INVALID_DISPOSITION: excType = "EXCEPTION_INVALID_DISPOSITION"; break;
814 : case EXCEPTION_NONCONTINUABLE_EXCEPTION: excType = "EXCEPTION_NONCONTINUABLE_EXCEPTION"; break;
815 : case EXCEPTION_PRIV_INSTRUCTION: excType = "EXCEPTION_PRIV_INSTRUCTION"; break;
816 : case EXCEPTION_SINGLE_STEP: excType = "EXCEPTION_SINGLE_STEP"; break;
817 : case EXCEPTION_STACK_OVERFLOW: excType = "EXCEPTION_STACK_OVERFLOW"; break;
818 : default: excType = "UNKNOWN"; break;
819 : }
820 :
821 : crash_info ci;
822 : ci.crashDescription = strprintf("Windows Exception: %s", excType);
823 : ci.stackframes = GetStackFrames(0, 16, ExceptionInfo->ContextRecord);
824 : ci.stackframeInfos = GetStackFrameInfos(ci.stackframes);
825 :
826 : PrintCrashInfo(ci);
827 : }
828 :
829 : LONG WINAPI HandleWindowsException(EXCEPTION_POINTERS * ExceptionInfo)
830 : {
831 : if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
832 : // We can't directly do the exception handling in this thread anymore as we need stack space for this
833 : // So let's do the handling in another thread
834 : // On Wine, the exception handler is not called at all
835 : std::thread([&]() {
836 : DoHandleWindowsException(ExceptionInfo);
837 : }).join();
838 : } else {
839 : DoHandleWindowsException(ExceptionInfo);
840 : }
841 : return EXCEPTION_CONTINUE_SEARCH;
842 : }
843 : #endif
844 :
845 72 : void RegisterPrettySignalHandlers()
846 : {
847 : #if defined(WIN32)
848 : SetUnhandledExceptionFilter(HandleWindowsException);
849 : #else
850 72 : const std::vector<int> posix_signals = {
851 : // Signals for which the default action is "Core".
852 : SIGABRT, // Abort signal from abort(3)
853 : SIGBUS, // Bus error (bad memory access)
854 : SIGFPE, // Floating point exception
855 : SIGILL, // Illegal Instruction
856 : SIGIOT, // IOT trap. A synonym for SIGABRT
857 : SIGQUIT, // Quit from keyboard
858 : SIGSEGV, // Invalid memory reference
859 : SIGSYS, // Bad argument to routine (SVr4)
860 : SIGTRAP, // Trace/breakpoint trap
861 : SIGXCPU, // CPU time limit exceeded (4.2BSD)
862 : SIGXFSZ, // File size limit exceeded (4.2BSD)
863 : #if defined(__APPLE__)
864 : SIGEMT, // emulation instruction executed
865 : #endif
866 : };
867 :
868 936 : for (auto s : posix_signals) {
869 : struct sigaction sa_segv;
870 864 : sa_segv.sa_handler = HandlePosixSignal;
871 864 : sigemptyset(&sa_segv.sa_mask);
872 864 : sa_segv.sa_flags = 0;
873 864 : sigaction(s, &sa_segv, nullptr);
874 : }
875 : #endif
876 72 : }
|