summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar yzct123452021-08-13 18:58:35 +0000
committerGravatar GitHub2021-08-13 18:58:35 +0000
commit81ed54d13ecea920583c521373d2a6079fc67040 (patch)
tree47c9bbab0c757e24a488497e771cc00993cdc7ed /src
parentlogging: Simplify and make thread-safe (diff)
downloadyuzu-81ed54d13ecea920583c521373d2a6079fc67040.tar.gz
yuzu-81ed54d13ecea920583c521373d2a6079fc67040.tar.xz
yuzu-81ed54d13ecea920583c521373d2a6079fc67040.zip
logging: Display backtrace on crash
This implements backtraces so we don't have to tell users how to use gdb anymore. This prints a backtrace after abort or segfault is detected. It also fixes the log getting cut off with the last line containing only a bracket. This change lets us know what caused a crash not just what happened the few seconds before it. I only know how to add support for Linux with GCC. Also this doesn't work outside of C/C++ such as in dynarmic or certain parts of graphics drivers. The good thing is that it'll try and just crash again but the stack frames are still there so the core dump will work just like before.
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt3
-rw-r--r--src/common/logging/backend.cpp112
2 files changed, 114 insertions, 1 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 57922b51c..316c4dedc 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -176,3 +176,6 @@ if (MSVC)
176else() 176else()
177 target_link_libraries(common PRIVATE zstd) 177 target_link_libraries(common PRIVATE zstd)
178endif() 178endif()
179if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID STREQUAL GNU)
180 target_link_libraries(common PRIVATE backtrace)
181endif()
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 13edda9c9..4e7cfdc99 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -12,6 +12,14 @@
12#include <windows.h> // For OutputDebugStringW 12#include <windows.h> // For OutputDebugStringW
13#endif 13#endif
14 14
15#if defined(__linux__) && defined(__GNUG__) && !defined(__clang__)
16#define BOOST_STACKTRACE_USE_BACKTRACE
17#include <boost/stacktrace.hpp>
18#undef BOOST_STACKTRACE_USE_BACKTRACE
19#include <signal.h>
20#define YUZU_LINUX_GCC_BACKTRACE
21#endif
22
15#include "common/fs/file.h" 23#include "common/fs/file.h"
16#include "common/fs/fs.h" 24#include "common/fs/fs.h"
17#include "common/fs/fs_paths.h" 25#include "common/fs/fs_paths.h"
@@ -154,6 +162,14 @@ public:
154 162
155bool initialization_in_progress_suppress_logging = false; 163bool initialization_in_progress_suppress_logging = false;
156 164
165#ifdef YUZU_LINUX_GCC_BACKTRACE
166[[noreturn]] void SleepForever() {
167 while (true) {
168 pause();
169 }
170}
171#endif
172
157/** 173/**
158 * Static state as a singleton. 174 * Static state as a singleton.
159 */ 175 */
@@ -225,9 +241,66 @@ private:
225 while (max_logs_to_write-- && message_queue.Pop(entry)) { 241 while (max_logs_to_write-- && message_queue.Pop(entry)) {
226 write_logs(); 242 write_logs();
227 } 243 }
228 })} {} 244 })} {
245#ifdef YUZU_LINUX_GCC_BACKTRACE
246 int waker_pipefd[2];
247 int done_printing_pipefd[2];
248 if (pipe2(waker_pipefd, O_CLOEXEC) || pipe2(done_printing_pipefd, O_CLOEXEC)) {
249 abort();
250 }
251 backtrace_thread_waker_fd = waker_pipefd[1];
252 backtrace_done_printing_fd = done_printing_pipefd[0];
253 std::thread([this, wait_fd = waker_pipefd[0], done_fd = done_printing_pipefd[1]] {
254 Common::SetCurrentThreadName("yuzu:Crash");
255 for (u8 ignore = 0; read(wait_fd, &ignore, 1) != 1;)
256 ;
257 const int sig = received_signal;
258 if (sig <= 0) {
259 abort();
260 }
261 StopBackendThread();
262 const auto signal_entry =
263 CreateEntry(Class::Log, Level::Critical, "?", 0, "?",
264 fmt::vformat("Received signal {}", fmt::make_format_args(sig)));
265 ForEachBackend([&signal_entry](Backend& backend) {
266 backend.EnableForStacktrace();
267 backend.Write(signal_entry);
268 });
269 const auto backtrace =
270 boost::stacktrace::stacktrace::from_dump(backtrace_storage.data(), 4096);
271 for (const auto& frame : backtrace.as_vector()) {
272 auto line = boost::stacktrace::detail::to_string(&frame, 1);
273 if (line.empty()) {
274 abort();
275 }
276 line.pop_back(); // Remove newline
277 const auto frame_entry =
278 CreateEntry(Class::Log, Level::Critical, "?", 0, "?", line);
279 ForEachBackend([&frame_entry](Backend& backend) { backend.Write(frame_entry); });
280 }
281 using namespace std::literals;
282 const auto rip_entry = CreateEntry(Class::Log, Level::Critical, "?", 0, "?", "RIP"s);
283 ForEachBackend([&rip_entry](Backend& backend) {
284 backend.Write(rip_entry);
285 backend.Flush();
286 });
287 for (const u8 anything = 0; write(done_fd, &anything, 1) != 1;)
288 ;
289 // Abort on original thread to help debugging
290 SleepForever();
291 }).detach();
292 signal(SIGSEGV, &HandleSignal);
293 signal(SIGABRT, &HandleSignal);
294#endif
295 }
229 296
230 ~Impl() { 297 ~Impl() {
298#ifdef YUZU_LINUX_GCC_BACKTRACE
299 if (int zero_or_ignore = 0;
300 !received_signal.compare_exchange_strong(zero_or_ignore, SIGKILL)) {
301 SleepForever();
302 }
303#endif
231 StopBackendThread(); 304 StopBackendThread();
232 } 305 }
233 306
@@ -266,6 +339,36 @@ private:
266 delete ptr; 339 delete ptr;
267 } 340 }
268 341
342#ifdef YUZU_LINUX_GCC_BACKTRACE
343 [[noreturn]] static void HandleSignal(int sig) {
344 signal(SIGABRT, SIG_DFL);
345 signal(SIGSEGV, SIG_DFL);
346 if (sig <= 0) {
347 abort();
348 }
349 instance->InstanceHandleSignal(sig);
350 }
351
352 [[noreturn]] void InstanceHandleSignal(int sig) {
353 if (int zero_or_ignore = 0; !received_signal.compare_exchange_strong(zero_or_ignore, sig)) {
354 if (received_signal == SIGKILL) {
355 abort();
356 }
357 SleepForever();
358 }
359 // Don't restart like boost suggests. We want to append to the log file and not lose dynamic
360 // symbols. This may segfault if it unwinds outside C/C++ code but we'll just have to fall
361 // back to core dumps.
362 boost::stacktrace::safe_dump_to(backtrace_storage.data(), 4096);
363 std::atomic_thread_fence(std::memory_order_seq_cst);
364 for (const int anything = 0; write(backtrace_thread_waker_fd, &anything, 1) != 1;)
365 ;
366 for (u8 ignore = 0; read(backtrace_done_printing_fd, &ignore, 1) != 1;)
367 ;
368 abort();
369 }
370#endif
371
269 static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter}; 372 static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
270 373
271 Filter filter; 374 Filter filter;
@@ -276,6 +379,13 @@ private:
276 std::thread backend_thread; 379 std::thread backend_thread;
277 MPSCQueue<Entry> message_queue{}; 380 MPSCQueue<Entry> message_queue{};
278 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; 381 std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
382
383#ifdef YUZU_LINUX_GCC_BACKTRACE
384 std::atomic_int received_signal{0};
385 std::array<u8, 4096> backtrace_storage{};
386 int backtrace_thread_waker_fd;
387 int backtrace_done_printing_fd;
388#endif
279}; 389};
280} // namespace 390} // namespace
281 391