Explorar el Código

core: avoid signal-unsafe calls in signal handler

Use backtrace_symbols_fd() and demangle symbols in a separate
process to avoid malloc()s in signal handler.

Fixes: #255
Daniele Bartolini hace 1 año
padre
commit
341e8b3ed8

+ 6 - 0
src/core/error/callstack.h

@@ -12,6 +12,12 @@ namespace crown
 {
 namespace error
 {
+	/// Initializes the callstack subsystem.
+	s32 callstack_init();
+
+	/// Shutdowns the callstack subsystem.
+	void callstack_shutdown();
+
 	/// Logs the current call stack.
 	void callstack(log_internal::System system, LogSeverity::Enum severity = LogSeverity::LOG_INFO);
 

+ 10 - 0
src/core/error/callstack_emscripten.cpp

@@ -15,6 +15,16 @@ namespace crown
 {
 namespace error
 {
+	s32 callstack_init()
+	{
+		return 0;
+	}
+
+	void callstack_shutdown()
+	{
+		CE_NOOP();
+	}
+
 	void callstack(log_internal::System system, LogSeverity::Enum severity)
 	{
 		int size = emscripten_get_callstack(EM_LOG_C_STACK | EM_LOG_JS_STACK, NULL, 0);

+ 148 - 43
src/core/error/callstack_linux.cpp

@@ -9,17 +9,24 @@
 #include "core/error/callstack.h"
 #include "core/process.h"
 #include "core/strings/string.inl"
-#include <cxxabi.h>
-#include <execinfo.h>
+#include <cxxabi.h>   // __cxa_demangle
+#include <execinfo.h> // backtrace, backtrace_symbols_fd
+#include <stdio.h>    // fdopen, fgets
+#include <stdlib.h>   // free
+#include <string.h>   // strchr
+#include <sys/wait.h> // waitpid
+#include <unistd.h>   // getpid
 #include <stb_sprintf.h>
-#include <stdlib.h> // free
-#include <string.h> // strchr
-#include <unistd.h> // getpid
 
 namespace crown
 {
 namespace error
 {
+	static int symbol_fds[2];
+	static int demangled_fds[2];
+	static int exit_fds[2];
+	static pid_t demangler;
+
 	static const char *addr2line(char *line, int len, const char *addr)
 	{
 		char process_exe[256];
@@ -46,50 +53,148 @@ namespace error
 		return "<addr2line missing>";
 	}
 
+	static void demangler_main()
+	{
+		FILE *fp = fdopen(symbol_fds[0], "r");
+		if (fp == NULL)
+			return;
+
+		while (true) {
+			fd_set fdset;
+
+			FD_ZERO(&fdset);
+			FD_SET(symbol_fds[0], &fdset);
+			FD_SET(exit_fds[0], &fdset);
+			int maxfd = max(symbol_fds[0], exit_fds[0]);
+
+			if (select(maxfd + 1, &fdset, NULL, NULL, NULL) <= 0)
+				continue;
+
+			if (FD_ISSET(exit_fds[0], &fdset)) {
+				break;
+			} else if (FD_ISSET(symbol_fds[0], &fdset)) {
+				char msg[512];
+				char buf[512];
+
+				while (fgets(msg, sizeof(msg), fp) != NULL) {
+					u32 len = strlen32(msg);
+					if (len <= 1)
+						break;
+					msg[len - 1] = '\0';
+
+					char *mangled_name = strchr(msg, '(');
+					char *offset_begin = strchr(msg, '+');
+					char *offset_end   = strchr(msg, ')');
+					char *addr_begin   = strchr(msg, '[');
+					char *addr_end     = strchr(msg, ']');
+
+					// Attempt to demangle the symbol.
+					if (mangled_name && offset_begin && offset_end && mangled_name < offset_begin) {
+						*mangled_name++ = '\0';
+						*offset_begin++ = '\0';
+						*offset_end++   = '\0';
+						*addr_begin++   = '\0';
+						*addr_end++     = '\0';
+
+						int demangle_ok;
+						char *real_name = abi::__cxa_demangle(mangled_name, 0, 0, &demangle_ok);
+						char line[256];
+						memset(line, 0, sizeof(line));
+
+						stbsp_snprintf(buf
+							, sizeof(buf)
+							, "%s(%s+%s) in %s"
+							, msg
+							, (demangle_ok == 0 ? real_name : mangled_name)
+							, offset_begin
+							, addr2line(line, sizeof(line), addr_begin)
+							);
+
+						free(real_name);
+					} else {
+						stbsp_snprintf(buf, sizeof(buf), "%s", msg);
+					}
+
+					write(demangled_fds[1], buf, sizeof(buf));
+				}
+			}
+		}
+
+		fclose(fp);
+	}
+
+	void callstack_shutdown()
+	{
+		int wstatus;
+
+		if (demangler <= 0)
+			return;
+
+		write(exit_fds[1], "q", 1);
+		waitpid(demangler, &wstatus, 0);
+		demangler = 0;
+	}
+
+	s32 callstack_init()
+	{
+		s32 ret = -1;
+
+		if (pipe(symbol_fds) < 0)
+			goto close_0;
+		if (pipe(demangled_fds) < 0)
+			goto close_1;
+		if (pipe(exit_fds) < 0)
+			goto close_2;
+
+		demangler = fork();
+		if (demangler == -1) {
+			close(symbol_fds[0]);
+			close(symbol_fds[1]);
+			close(demangled_fds[0]);
+			close(demangled_fds[1]);
+			close(exit_fds[0]);
+			close(exit_fds[1]);
+			return -1;
+		} else if (demangler == 0) {
+			close(symbol_fds[1]);
+			close(demangled_fds[0]);
+			close(exit_fds[1]);
+			demangler_main();
+		} else {
+			ret = 0;
+		}
+
+		close(exit_fds[0]);
+	close_2:
+		close(demangled_fds[1]);
+	close_1:
+		close(symbol_fds[0]);
+	close_0:
+		return ret;
+	}
+
 	void callstack(log_internal::System system, LogSeverity::Enum severity)
 	{
+		char msg[512] = {};
 		void *array[64];
+
+		if (demangler <= 0) {
+			log_internal::logx(severity, system, "Callstack unavailable.");
+			return;
+		}
+
+		// Get symbols and write them to demangler process.
 		int size = backtrace(array, countof(array));
-		char **messages = backtrace_symbols(array, size);
-
-		// skip first stack frame (points here)
-		for (int i = 1; i < size && messages != NULL; ++i) {
-			char *msg = messages[i];
-			char *mangled_name = strchr(msg, '(');
-			char *offset_begin = strchr(msg, '+');
-			char *offset_end   = strchr(msg, ')');
-			char *addr_begin   = strchr(msg, '[');
-			char *addr_end     = strchr(msg, ']');
-
-			// Attempt to demangle the symbol
-			if (mangled_name && offset_begin && offset_end && mangled_name < offset_begin) {
-				*mangled_name++ = '\0';
-				*offset_begin++ = '\0';
-				*offset_end++   = '\0';
-				*addr_begin++   = '\0';
-				*addr_end++     = '\0';
-
-				int demangle_ok;
-				char *real_name = abi::__cxa_demangle(mangled_name, 0, 0, &demangle_ok);
-				char line[256];
-				memset(line, 0, sizeof(line));
-
-				log_internal::logx(severity
-					, system
-					, "[%2d] %s: (%s)+%s in %s"
-					, i
-					, msg
-					, (demangle_ok == 0 ? real_name : mangled_name)
-					, offset_begin
-					, addr2line(line, sizeof(line), addr_begin)
-					);
-
-				free(real_name);
-			} else {
+		backtrace_symbols_fd(array, size, symbol_fds[1]);
+		write(symbol_fds[1], "\n", 1);
+
+		// Log demangled symbols.
+		for (int i = 0; i < size; ++i) {
+			if (read(demangled_fds[0], msg, sizeof(msg)) <= 0)
+				break;
+			if (i >= 1) // Skip this very function.
 				log_internal::logx(severity, system, "[%2d] %s", i, msg);
-			}
 		}
-		free(messages);
 	}
 
 } // namespace error

+ 12 - 2
src/core/error/callstack_noop.cpp

@@ -12,13 +12,23 @@ namespace crown
 {
 namespace error
 {
+	s32 callstack_init()
+	{
+		return 0;
+	}
+
+	void callstack_shutdown()
+	{
+		CE_NOOP();
+	}
+
 	void callstack(log_internal::System system, LogSeverity::Enum severity)
 	{
-		log_internal::logx(severity, system, "Not supported");
+		log_internal::logx(severity, system, "Callstack not supported on this platform.");
 	}
 
 } // namespace error
 
 } // namespace crown
 
-#endif
+#endif // if CROWN_PLATFORM_ANDROID

+ 10 - 0
src/core/error/callstack_windows.cpp

@@ -21,6 +21,16 @@ namespace crown
 {
 namespace error
 {
+	s32 callstack_init()
+	{
+		return 0;
+	}
+
+	void callstack_shutdown()
+	{
+		CE_NOOP();
+	}
+
 	void callstack(log_internal::System system, LogSeverity::Enum severity)
 	{
 		SymInitialize(GetCurrentProcess(), NULL, TRUE);

+ 13 - 2
src/device/main_linux.cpp

@@ -8,6 +8,7 @@
 #if CROWN_PLATFORM_LINUX
 #include "core/command_line.h"
 #include "core/containers/array.inl"
+#include "core/error/callstack.h"
 #include "core/guid.h"
 #include "core/memory/globals.h"
 #include "core/memory/memory.inl"
@@ -1031,12 +1032,24 @@ struct InitGlobals
 	}
 };
 
+void at_exit()
+{
+	error::callstack_shutdown();
+}
+
 } // namespace crown
 
 int main(int argc, char **argv)
 {
 	using namespace crown;
 
+	if (error::callstack_init() != 0)
+		return EXIT_FAILURE;
+	if (atexit(at_exit) != 0) {
+		error::callstack_shutdown();
+		return EXIT_FAILURE;
+	}
+
 	struct sigaction act;
 	// code-format off
 	act.sa_handler = [](int signum) {
@@ -1055,12 +1068,10 @@ int main(int argc, char **argv)
 			case SIGPIPE:
 			case SIGSEGV:
 			case SIGSYS:
-				// FIXME: only use signal-safe functions.
 				error::abort("Signal %d", signum);
 				break;
 
 			default:
-				// FIXME: only use signal-safe functions.
 				error::abort("Unhandled signal %d", signum);
 				break;
 			}