Skip to content

Commit 093896d

Browse files
committed
Improve linux crashlog functionality and safety
1 parent 0871519 commit 093896d

1 file changed

Lines changed: 85 additions & 34 deletions

File tree

library/Crashlog.cpp

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,132 @@
11
#include "DFHackVersion.h"
22
#include <csignal>
33
#include <thread>
4-
#include <semaphore>
54
#include <filesystem>
65
#include <fstream>
76

87
#include <execinfo.h>
98

109
const int BT_ENTRY_MAX = 25;
11-
int bt_entries = 0;
12-
void* bt[BT_ENTRY_MAX];
13-
int crash_signal = 0;
10+
struct CrashInfo {
11+
int backtrace_entries = 0;
12+
void* backtrace[BT_ENTRY_MAX];
13+
int signal = 0;
14+
};
1415

15-
std::binary_semaphore crashlog_ready{0};
16-
std::binary_semaphore crashlog_complete{0};
16+
CrashInfo crash_info;
17+
18+
/*
19+
* As of c++17 the only safe stdc++ methods are plain lock-free atomic methods
20+
* This sadly means that using std::semaphore *could* cause issues according to the standard.
21+
*/
22+
std::atomic_bool crashed = false;
23+
std::atomic_bool crashlog_ready = false;
24+
std::atomic_bool crashlog_complete = false;
25+
26+
void flag_set(std::atomic_bool &atom) {
27+
atom.store(true);
28+
atom.notify_all();
29+
}
30+
void flag_wait(std::atomic_bool &atom) {
31+
atom.wait(false);
32+
}
1733

1834
std::thread crashlog_thread;
19-
volatile bool shutdown = false;
35+
bool shutdown = false;
2036

2137
extern "C" void dfhack_crashlog_handle_signal(int sig) {
22-
crash_signal = sig;
23-
bt_entries = backtrace(bt, BT_ENTRY_MAX);
38+
if (crashed.exchange(true)) {
39+
// Crashlog already produced, bail thread.
40+
std::quick_exit(1);
41+
}
42+
crash_info.signal = sig;
43+
crash_info.backtrace_entries = backtrace(crash_info.backtrace, BT_ENTRY_MAX);
2444

2545
// Signal saving of crashlog and wait for completion
26-
crashlog_ready.release();
27-
crashlog_complete.acquire();
46+
flag_set(crashlog_ready);
47+
flag_wait(crashlog_complete);
2848
std::quick_exit(1);
2949
}
3050

51+
void dfhack_crashlog_handle_terminate() {
52+
dfhack_crashlog_handle_signal(0);
53+
}
54+
55+
std::string signal_name(int sig) {
56+
switch (sig) {
57+
case SIGINT:
58+
return "SIGINT";
59+
case SIGILL:
60+
return "SIGILL";
61+
case SIGABRT:
62+
return "SIGABRT";
63+
case SIGFPE:
64+
return "SIGFPE";
65+
case SIGSEGV:
66+
return "SIGSEGV";
67+
case SIGTERM:
68+
return "SIGTERM";
69+
}
70+
return "";
71+
}
72+
3173
void dfhack_save_crashlog() {
32-
char** backtrace_strings = backtrace_symbols(bt, bt_entries);
74+
char** backtrace_strings = backtrace_symbols(crash_info.backtrace, crash_info.backtrace_entries);
3375
if (!backtrace_strings) {
34-
// Something has gone terribly wrong
76+
// Allocation failed, give up
3577
return;
3678
}
3779
std::filesystem::path crashlog_path = "./crash.txt";
3880
std::ofstream crashlog(crashlog_path);
3981

40-
crashlog << "Dwarf Fortress has crashed!" << "\n";
41-
crashlog << "DwarfFortress Version " << DFHack::Version::df_version() << "\n";
82+
crashlog << "Dwarf Fortress Linux has crashed!" << "\n";
83+
crashlog << "Dwarf Fortress Version " << DFHack::Version::df_version() << "\n";
4284
crashlog << "DFHack Version " << DFHack::Version::dfhack_version() << "\n\n";
4385

44-
for (int i = 0; i < bt_entries; i++) {
86+
std::string signal = signal_name(crash_info.signal);
87+
if (!signal.empty()) {
88+
crashlog << "Signal " << signal << "\n";
89+
}
90+
91+
for (int i = 0; i < crash_info.backtrace_entries; i++) {
4592
crashlog << i << "> " << backtrace_strings[i] << "\n";
4693
}
4794

4895
free(backtrace_strings);
4996
}
5097

5198
void dfhack_crashlog_thread() {
52-
// Wait for crash or shutdown signal
53-
crashlog_ready.acquire();
54-
if (shutdown)
99+
// Wait for activation signal
100+
flag_wait(crashlog_ready);
101+
if (shutdown) // Shutting down gracefully, end thread.
55102
return;
56103

57104
dfhack_save_crashlog();
58-
crashlog_complete.release();
105+
106+
flag_set(crashlog_complete);
59107
std::quick_exit(1);
60108
}
61109

62110
const int desired_signals[3] = {SIGSEGV,SIGILL,SIGABRT};
63111
namespace DFHack {
64-
void dfhack_crashlog_init() {
65-
for (int signal : desired_signals) {
66-
std::signal(signal, dfhack_crashlog_handle_signal);
67-
}
112+
void dfhack_crashlog_init() {
113+
for (int signal : desired_signals) {
114+
std::signal(signal, dfhack_crashlog_handle_signal);
115+
}
116+
std::set_terminate(dfhack_crashlog_handle_terminate);
68117

69-
// Ensure the library is initialized to avoid AsyncSignal-Unsafe init during crash
70-
int _ = backtrace(bt, 1);
118+
// https://sourceware.org/glibc/manual/latest/html_mono/libc.html#index-backtrace-1
119+
// backtrace is AsyncSignal-Unsafe due to dynamic loading of libgcc_s
120+
// Using it here ensures it is loaded before use in the signal handler.
121+
int _ = backtrace(crash_info.backtrace, 1);
71122

72-
crashlog_thread = std::thread(dfhack_crashlog_thread);
73-
}
123+
crashlog_thread = std::thread(dfhack_crashlog_thread);
124+
}
74125

75-
void dfhack_crashlog_shutdown() {
76-
shutdown = true;
77-
crashlog_ready.release();
78-
crashlog_thread.join();
79-
return;
80-
}
126+
void dfhack_crashlog_shutdown() {
127+
shutdown = true;
128+
flag_set(crashlog_ready);
129+
crashlog_thread.join();
130+
return;
131+
}
81132
}

0 commit comments

Comments
 (0)