|
| 1 | +#include "DFHackVersion.h" |
| 2 | +#include <csignal> |
| 3 | +#include <thread> |
| 4 | +#include <semaphore> |
| 5 | +#include <filesystem> |
| 6 | +#include <fstream> |
| 7 | + |
| 8 | +#include <execinfo.h> |
| 9 | + |
| 10 | +const int BT_ENTRY_MAX = 25; |
| 11 | +int bt_entries = 0; |
| 12 | +void* bt[BT_ENTRY_MAX]; |
| 13 | +int crash_signal = 0; |
| 14 | + |
| 15 | +std::binary_semaphore crashlog_ready{0}; |
| 16 | +std::binary_semaphore crashlog_complete{0}; |
| 17 | + |
| 18 | +std::thread crashlog_thread; |
| 19 | +volatile bool shutdown = false; |
| 20 | + |
| 21 | +extern "C" void dfhack_crashlog_handle_signal(int sig) { |
| 22 | + crash_signal = sig; |
| 23 | + bt_entries = backtrace(bt, BT_ENTRY_MAX); |
| 24 | + |
| 25 | + // Signal saving of crashlog and wait for completion |
| 26 | + crashlog_ready.release(); |
| 27 | + crashlog_complete.acquire(); |
| 28 | + std::quick_exit(1); |
| 29 | +} |
| 30 | + |
| 31 | +void dfhack_save_crashlog() { |
| 32 | + char** backtrace_strings = backtrace_symbols(bt, bt_entries); |
| 33 | + if (!backtrace_strings) { |
| 34 | + // Something has gone terribly wrong |
| 35 | + return; |
| 36 | + } |
| 37 | + std::filesystem::path crashlog_path = "./crash.txt"; |
| 38 | + std::ofstream crashlog(crashlog_path); |
| 39 | + |
| 40 | + crashlog << "Dwarf Fortress has crashed!" << "\n"; |
| 41 | + crashlog << "DwarfFortress Version " << DFHack::Version::df_version() << "\n"; |
| 42 | + crashlog << "DFHack Version " << DFHack::Version::dfhack_version() << "\n\n"; |
| 43 | + |
| 44 | + for (int i = 0; i < bt_entries; i++) { |
| 45 | + crashlog << i << "> " << backtrace_strings[i] << "\n"; |
| 46 | + } |
| 47 | + |
| 48 | + free(backtrace_strings); |
| 49 | +} |
| 50 | + |
| 51 | +void dfhack_crashlog_thread() { |
| 52 | + // Wait for crash or shutdown signal |
| 53 | + crashlog_ready.acquire(); |
| 54 | + if (shutdown) |
| 55 | + return; |
| 56 | + |
| 57 | + dfhack_save_crashlog(); |
| 58 | + crashlog_complete.release(); |
| 59 | + std::quick_exit(1); |
| 60 | +} |
| 61 | + |
| 62 | +const int desired_signals[3] = {SIGSEGV,SIGILL,SIGABRT}; |
| 63 | +namespace DFHack { |
| 64 | +void dfhack_crashlog_init() { |
| 65 | + for (int signal : desired_signals) { |
| 66 | + std::signal(signal, dfhack_crashlog_handle_signal); |
| 67 | + } |
| 68 | + |
| 69 | + // Ensure the library is initialized to avoid AsyncSignal-Unsafe init during crash |
| 70 | + int _ = backtrace(bt, 1); |
| 71 | + |
| 72 | + crashlog_thread = std::thread(dfhack_crashlog_thread); |
| 73 | +} |
| 74 | + |
| 75 | +void dfhack_crashlog_shutdown() { |
| 76 | + shutdown = true; |
| 77 | + crashlog_ready.release(); |
| 78 | + crashlog_thread.join(); |
| 79 | + return; |
| 80 | +} |
| 81 | +} |
0 commit comments