OpenTTD Source  20241121-master-g67a0fccfad
crashlog_osx.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "../../stdafx.h"
11 #include "../../crashlog.h"
12 #include "../../fileio_func.h"
13 #include "../../string_func.h"
14 #include "../../gamelog.h"
15 #include "../../saveload/saveload.h"
16 #include "../../video/video_driver.hpp"
17 #include "macos.h"
18 
19 #include <setjmp.h>
20 #include <signal.h>
21 #include <mach-o/arch.h>
22 #include <dlfcn.h>
23 #include <cxxabi.h>
24 #include <execinfo.h>
25 
26 #ifdef WITH_UNOFFICIAL_BREAKPAD
27 # include <client/mac/handler/exception_handler.h>
28 #endif
29 
30 #include "../../safeguards.h"
31 
32 
33 /* Macro testing a stack address for valid alignment. */
34 #if defined(__i386__)
35 #define IS_ALIGNED(addr) (((uintptr_t)(addr) & 0xf) == 8)
36 #else
37 #define IS_ALIGNED(addr) (((uintptr_t)(addr) & 0xf) == 0)
38 #endif
39 
40 #define MAX_STACK_FRAMES 64
41 
43 static constexpr int _signals_to_handle[] = { SIGSEGV, SIGABRT, SIGFPE, SIGBUS, SIGILL, SIGSYS, SIGQUIT };
44 
48 class CrashLogOSX : public CrashLog {
50  int signum;
51 
52  void SurveyCrash(nlohmann::json &survey) const override
53  {
54  survey["id"] = signum;
55  survey["reason"] = strsignal(signum);
56  }
57 
58  void SurveyStacktrace(nlohmann::json &survey) const override
59  {
60  void *trace[64];
61  int trace_size = backtrace(trace, lengthof(trace));
62 
63  survey = nlohmann::json::array();
64 
65  char **messages = backtrace_symbols(trace, trace_size);
66  for (int i = 0; i < trace_size; i++) {
67  survey.push_back(messages[i]);
68  }
69  free(messages);
70  }
71 
72 #ifdef WITH_UNOFFICIAL_BREAKPAD
73  static bool MinidumpCallback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded)
74  {
75  CrashLogOSX *crashlog = reinterpret_cast<CrashLogOSX *>(context);
76 
77  crashlog->crashdump_filename = crashlog->CreateFileName(".dmp");
78  std::rename(fmt::format("{}/{}.dmp", dump_dir, minidump_id).c_str(), crashlog->crashdump_filename.c_str());
79  return succeeded;
80  }
81 
82  bool WriteCrashDump() override
83  {
84  return google_breakpad::ExceptionHandler::WriteMinidump(_personal_dir, MinidumpCallback, this);
85  }
86 #endif
87 
88  /* virtual */ bool TryExecute(std::string_view section_name, std::function<bool()> &&func) override
89  {
90  this->try_execute_active = true;
91 
92  /* Setup a longjump in case a crash happens. */
93  if (setjmp(this->internal_fault_jmp_buf) != 0) {
94  fmt::print("Something went wrong when attempting to fill {} section of the crash log.\n", section_name);
95 
96  /* Reset the signals and continue on. The handler is responsible for dealing with the crash. */
97  sigset_t sigs;
98  sigemptyset(&sigs);
99  for (int signum : _signals_to_handle) {
100  sigaddset(&sigs, signum);
101  }
102  sigprocmask(SIG_UNBLOCK, &sigs, nullptr);
103 
104  this->try_execute_active = false;
105  return false;
106  }
107 
108  bool res = func();
109  this->try_execute_active = false;
110  return res;
111  }
112 
113 public:
119 
121  void DisplayCrashDialog() const
122  {
123  static const char crash_title[] =
124  "A serious fault condition occurred in the game. The game will shut down.";
125 
126  std::string message = fmt::format(
127  "Please send crash.json.log, crash.dmp, and crash.sav to the developers. "
128  "This will greatly help debugging.\n\n"
129  "https://github.com/OpenTTD/OpenTTD/issues.\n\n"
130  "{}\n{}\n{}\n{}",
131  this->crashlog_filename, this->crashdump_filename, this->savegame_filename, this->screenshot_filename);
132 
133  ShowMacDialog(crash_title, message.c_str(), "Quit");
134  }
135 
138 
140  bool try_execute_active = false;
141 
144 };
145 
146 /* static */ CrashLogOSX *CrashLogOSX::current = nullptr;
147 
154 static sigset_t SetSignals(void(*handler)(int))
155 {
156  sigset_t sigs;
157  sigemptyset(&sigs);
158  for (int signum : _signals_to_handle) {
159  sigaddset(&sigs, signum);
160  }
161 
162  struct sigaction sa;
163  memset(&sa, 0, sizeof(sa));
164  sa.sa_flags = SA_RESTART;
165 
166  sigemptyset(&sa.sa_mask);
167  sa.sa_handler = handler;
168  sa.sa_mask = sigs;
169 
170  for (int signum : _signals_to_handle) {
171  sigaction(signum, &sa, nullptr);
172  }
173 
174  return sigs;
175 }
176 
181 static void CDECL HandleInternalCrash(int)
182 {
183  if (CrashLogOSX::current == nullptr || !CrashLogOSX::current->try_execute_active) {
184  fmt::print("Something went seriously wrong when creating the crash log. Aborting.\n");
185  _exit(1);
186  }
187 
188  longjmp(CrashLogOSX::current->internal_fault_jmp_buf, 1);
189 }
190 
196 static void CDECL HandleCrash(int signum)
197 {
198  if (CrashLogOSX::current != nullptr) {
200  _exit(2);
201  }
202 
203  /* Capture crashing during the handling of a crash. */
204  sigset_t sigs = SetSignals(HandleInternalCrash);
205  sigset_t old_sigset;
206  sigprocmask(SIG_UNBLOCK, &sigs, &old_sigset);
207 
208  if (_gamelog.TestEmergency()) {
209  ShowMacDialog("A serious fault condition occurred in the game. The game will shut down.",
210  "As you loaded an emergency savegame no crash information will be generated.\n",
211  "Quit");
212  _exit(3);
213  }
214 
216  ShowMacDialog("A serious fault condition occurred in the game. The game will shut down.",
217  "As you loaded an savegame for which you do not have the required NewGRFs no crash information will be generated.\n",
218  "Quit");
219  _exit(3);
220  }
221 
222  CrashLogOSX *log = new CrashLogOSX(signum);
223  CrashLogOSX::current = log;
224  log->MakeCrashLog();
225  if (VideoDriver::GetInstance() == nullptr || VideoDriver::GetInstance()->HasGUI()) {
226  log->DisplayCrashDialog();
227  }
228 
230  _exit(2);
231 }
232 
233 /* static */ void CrashLog::InitialiseCrashLog()
234 {
236 }
237 
238 /* static */ void CrashLog::InitThread()
239 {
240 }
bool SaveloadCrashWithMissingNewGRFs()
Did loading the savegame cause a crash? If so, were NewGRFs missing?
Definition: afterload.cpp:352
OSX implementation for the crash logger.
void SurveyCrash(nlohmann::json &survey) const override
Convert system crash reason to JSON.
CrashLogOSX(int signum)
A crash log is always generated by signal.
jmp_buf internal_fault_jmp_buf
Buffer to track the long jump set setup.
bool TryExecute(std::string_view section_name, std::function< bool()> &&func) override
Execute the func() and return its value.
static CrashLogOSX * current
Points to the current crash log.
void SurveyStacktrace(nlohmann::json &survey) const override
Convert stacktrace to JSON.
bool try_execute_active
Whether we are in a TryExecute block.
int signum
Signal that has been thrown.
void DisplayCrashDialog() const
Show a dialog with the crash information.
Helper class for creating crash logs.
Definition: crashlog.h:18
void MakeCrashLog()
Makes the crash log, writes it to a file and then subsequently tries to make a crash dump and crash s...
Definition: crashlog.cpp:270
std::string CreateFileName(const char *ext, bool with_dir=true) const
Create a timestamped filename.
Definition: crashlog.cpp:74
static std::string message
Error message coming from #FatalError(format, ...).
Definition: crashlog.h:21
virtual bool WriteCrashDump()
Write the (crash) dump to a file.
Definition: crashlog.cpp:210
static void InitThread()
Prepare crash log handler for a newly started thread.
static void AfterCrashLogCleanup()
Try to close the sound/video stuff so it doesn't keep lingering around incorrect video states or so,...
Definition: crashlog.cpp:336
static void InitialiseCrashLog()
Initialiser for crash logs; do the appropriate things so crashes are handled by our crash handler ins...
bool TestEmergency()
Finds out if current game is a loaded emergency savegame.
Definition: gamelog.cpp:364
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
static constexpr int _signals_to_handle[]
The signals we want our crash handler to handle.
static void CDECL HandleInternalCrash(int)
Entry point for a crash that happened during the handling of a crash.
static void CDECL HandleCrash(int signum)
Entry point for the crash handler.
static sigset_t SetSignals(void(*handler)(int))
Set a signal handler for all signals we want to capture.
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition: fileio.cpp:869
Gamelog _gamelog
Gamelog instance.
Definition: gamelog.cpp:31
Functions related to MacOS support.
void ShowMacDialog(const char *title, const char *message, const char *button_label)
Helper function displaying a message the best possible way.
void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: stdafx.h:334
#define lengthof(array)
Return the length of an fixed size array.
Definition: stdafx.h:280