diff --git a/core/error/error_macros.cpp b/core/error/error_macros.cpp index 8376c0aaf84c..1b7160b83b28 100644 --- a/core/error/error_macros.cpp +++ b/core/error/error_macros.cpp @@ -31,6 +31,7 @@ #include "error_macros.h" #include "core/io/logger.h" +#include "core/object/script_language.h" #include "core/os/os.h" #include "core/string/ustring.h" @@ -125,6 +126,67 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li _err_print_index_error(p_function, p_file, p_line, p_index, p_size, p_index_str, p_size_str, p_message.utf8().get_data(), p_editor_notify, p_fatal); } +void _err_print_callstack(const String &p_error, bool p_editor_notify, ErrorHandlerType p_type) { + // Print detailed call stack information from everywhere available. It is recommended to only + // use this for debugging, as it has a fairly high overhead. + String callstack; + + // Print script stack frames, if available. + Vector si; + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + si = ScriptServer::get_language(i)->debug_get_current_stack_info(); + if (si.size()) { + callstack += "Callstack from " + ScriptServer::get_language(i)->get_name() + ":\n"; + for (int j = 0; j < si.size(); ++j) { + callstack += si[i].file + ':' + itos(si[i].line) + " @ " + si[i].func + '\n'; + } + callstack += '\n'; + } + } + + // Print C++ call stack. + Vector cpp_stack = OS::get_singleton()->get_cpp_stack_info(); + callstack += "C++ call stack:\n"; + for (int i = 0; i < cpp_stack.size(); ++i) { + String descriptor = OS::get_singleton()->get_debug_descriptor(cpp_stack[i]); + callstack += descriptor + " (" + cpp_stack[i].file + ":0x" + String::num_uint64(cpp_stack[i].offset, 16) + " @ " + cpp_stack[i].function + ")\n"; + } + + _err_print_error(__FUNCTION__, __FILE__, __LINE__, p_error + '\n' + callstack, p_editor_notify, p_type); +} + +void _err_print_error_backtrace(const char *p_filter, const String &p_error, bool p_editor_notify, ErrorHandlerType p_type) { + // Print script stack frame, if available. + Vector si; + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + si = ScriptServer::get_language(i)->debug_get_current_stack_info(); + if (si.size()) { + _err_print_error(si[0].func.utf8(), si[0].file.utf8(), si[0].line, p_error, p_editor_notify, p_type); + return; + } + } + + // If there is not a script stack frame, use the C++ stack frame. + Vector cpp_stack = OS::get_singleton()->get_cpp_stack_info(); + + for (int i = 1; i < cpp_stack.size(); ++i) { + if (!cpp_stack[i].function.contains(p_filter)) { + String descriptor = OS::get_singleton()->get_debug_descriptor(cpp_stack[i]); + if (descriptor.is_empty()) { + // If we can't get debug info, just print binary file name and address. + _err_print_error(cpp_stack[i].function.utf8(), cpp_stack[i].file.utf8(), cpp_stack[i].offset, p_error, p_editor_notify, p_type); + } else { + // Expect debug descriptor to replace file and line info. + _err_print_error(cpp_stack[i].function.utf8(), cpp_stack[i].file.utf8(), cpp_stack[i].offset, "", descriptor + ": " + p_error, p_editor_notify, p_type); + } + return; + } + } + + // If there is no usable stack frame (this should basically never happen), fall back to using the current stack frame. + _err_print_error(__FUNCTION__, __FILE__, __LINE__, p_error, p_editor_notify, p_type); +} + void _err_flush_stdout() { fflush(stdout); } diff --git a/core/error/error_macros.h b/core/error/error_macros.h index ab7dbcbd44b7..ec9740af0097 100644 --- a/core/error/error_macros.h +++ b/core/error/error_macros.h @@ -69,6 +69,8 @@ void _err_print_error(const char *p_function, const char *p_file, int p_line, co void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool p_editor_notify = false, bool fatal = false); void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false); +void _err_print_callstack(const String &p_error, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); +void _err_print_error_backtrace(const char *p_filter, const String &p_error, bool p_editor_notify = false, ErrorHandlerType p_type = ERR_HANDLER_ERROR); void _err_flush_stdout(); #ifdef __GNUC__ diff --git a/core/os/os.h b/core/os/os.h index 63cc6ed50e63..798ecffe73db 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -339,6 +339,16 @@ class OS { virtual PreferredTextureFormat get_preferred_texture_format() const; + struct StackInfo { + String function; + String file; + uint64_t offset; + const void *load_address; + const void *symbol_address; + }; + virtual Vector get_cpp_stack_info() const { return Vector(); } + virtual String get_debug_descriptor(const StackInfo &p_info) { return String(); } + // Load GDExtensions specific to this platform. // This is invoked by the GDExtensionManager after loading GDExtensions specified by the project. virtual void load_platform_gdextensions() const {} diff --git a/core/templates/list.h b/core/templates/list.h index 6663f06c3096..c8cdc574209e 100644 --- a/core/templates/list.h +++ b/core/templates/list.h @@ -35,6 +35,8 @@ #include "core/os/memory.h" #include "core/templates/sort_array.h" +#include + /** * Generic Templatized Linked List Implementation. * The implementation differs from the STL one because @@ -761,6 +763,12 @@ class List { } } + List(std::initializer_list p_init) { + for (const T &i : p_init) { + push_back(i); + } + } + List() {} ~List() { diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 7534a154a1c3..fd5004f2b389 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -1089,7 +1089,7 @@ void VariantUtilityFunctions::push_error(const Variant **p_args, int p_arg_count } } - ERR_PRINT(s); + _err_print_error_backtrace(FUNCTION_STR, s); r_error.error = Callable::CallError::CALL_OK; } @@ -1109,7 +1109,7 @@ void VariantUtilityFunctions::push_warning(const Variant **p_args, int p_arg_cou } } - WARN_PRINT(s); + _err_print_error_backtrace(FUNCTION_STR, s, false, ERR_HANDLER_WARNING); r_error.error = Callable::CallError::CALL_OK; } diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index ce2553456d91..2c49e116e310 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -68,6 +68,11 @@ #include #endif +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) +#include +#endif + +#include #include #include #include @@ -1006,6 +1011,49 @@ void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, i } } +OS::StackInfo OS_Unix::describe_function(const char *dli_fname, const void *dli_fbase, const char *dli_sname, const void *dli_saddr, const void *address) const { + StackInfo result; + + // Demangle C++ symbols + int status; + char *demangled = abi::__cxa_demangle(dli_sname, nullptr, nullptr, &status); + + if (status == 0) { + result.function = demangled; + } else { + result.function = dli_sname; + } + free(demangled); + + // Get file info + result.file = dli_fname; + result.offset = static_cast(address) - static_cast(dli_fbase); + result.load_address = dli_fbase; + result.symbol_address = address; + + return result; +} + +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) +Vector OS_Unix::get_cpp_stack_info() const { + constexpr int kMaxBacktraceDepth = 25; + void *backtrace_addrs[kMaxBacktraceDepth]; + int trace_size = backtrace(backtrace_addrs, kMaxBacktraceDepth); + + Vector result; + result.resize(trace_size - 1); + + // Skip the current stack frame and return the stack frame of the calling function + for (int i = 1; i < trace_size; ++i) { + Dl_info info; + dladdr(backtrace_addrs[i], &info); + result.write[i - 1] = describe_function(info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr, backtrace_addrs[i]); + } + + return result; +} +#endif + UnixTerminalLogger::~UnixTerminalLogger() {} OS_Unix::OS_Unix() { diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index df269a59d32a..a695a64f5c63 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -53,6 +53,8 @@ class OS_Unix : public OS { virtual void finalize_core() override; + virtual StackInfo describe_function(const char *dli_fname, const void *dli_fbase, const char *dli_sname, const void *dli_saddr, const void *address) const; + public: OS_Unix(); @@ -101,6 +103,10 @@ class OS_Unix : public OS { virtual String get_executable_path() const override; virtual String get_user_data_dir() const override; + +#if !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) + virtual Vector get_cpp_stack_info() const override; +#endif }; class UnixTerminalLogger : public StdLogger { diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 6a016c217a86..2745f0ca04d3 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -49,7 +49,7 @@ void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_f if (p_errorexp && p_errorexp[0]) { err_str = String::utf8(p_errorexp); } else { - err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error); + err_str = String::utf8(p_func) + " (" + String::utf8(p_file) + ": " + itos(p_line) + ") - " + String::utf8(p_error); } if (p_editor_notify) { diff --git a/platform/macos/os_macos.h b/platform/macos/os_macos.h index 912a682a6bbc..4d1cd1325e9b 100644 --- a/platform/macos/os_macos.h +++ b/platform/macos/os_macos.h @@ -75,6 +75,10 @@ class OS_MacOS : public OS_Unix { virtual void set_main_loop(MainLoop *p_main_loop) override; virtual void delete_main_loop() override; +#ifdef DEBUG_ENABLED + virtual String get_debug_descriptor(const StackInfo &p_info) override; +#endif + public: virtual void set_cmdline_platform_args(const List &p_args); virtual List get_cmdline_platform_args() const override; diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 9f0bea5951c3..8631bd85f3ac 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -757,6 +757,19 @@ return PREFERRED_TEXTURE_FORMAT_S3TC_BPTC; } +#ifdef DEBUG_ENABLED +String OS_MacOS::get_debug_descriptor(const StackInfo &p_info) { + String pipe; + Error error = execute("atos", { "-o", p_info.file, "-l", String::num_uint64(reinterpret_cast(p_info.load_address), 16), String::num_uint64(reinterpret_cast(p_info.symbol_address), 16) }, &pipe); + + if (error == OK) { + return pipe.strip_edges(); + } else { + return String(); + } +} +#endif + void OS_MacOS::run() { if (!main_loop) { return;