diff --git a/dtool/src/parser-inc/DbgHelp.h b/dtool/src/parser-inc/DbgHelp.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dtool/src/prc/notify.cxx b/dtool/src/prc/notify.cxx index ce7df151c6..5c4010264b 100644 --- a/dtool/src/prc/notify.cxx +++ b/dtool/src/prc/notify.cxx @@ -35,6 +35,18 @@ #include "emscriptenLogStream.h" #endif +#ifndef NDEBUG +#ifdef PHAVE_EXECINFO_H +#include // for backtrace() +#include +#include +#endif + +#ifdef _WIN32 +#include +#endif +#endif // NDEBUG + using std::cerr; using std::cout; using std::ostream; @@ -348,15 +360,16 @@ assert_failure(const char *expression, int line, << expression << " at line " << line << " of " << source_file; string message = message_str.str(); - if (!_assert_failed) { + Notify *self = ptr(); + if (!self->_assert_failed) { // We only save the first assertion failure message, as this is usually // the most meaningful when several occur in a row. - _assert_failed = true; - _assert_error_message = message; + self->_assert_failed = true; + self->_assert_error_message = message; } - if (has_assert_handler()) { - return (*_assert_handler)(expression, line, source_file); + if (self->has_assert_handler()) { + return (*self->_assert_handler)(expression, line, source_file); } #ifdef ANDROID @@ -380,6 +393,33 @@ assert_failure(const char *expression, int line, // Make sure the error message has been flushed to the output. nout.flush(); + // Capture and list a stack trace. +#ifdef NDEBUG +#elif defined(PHAVE_EXECINFO_H) + void *trace[64]; + int size = backtrace(trace, 64); + if (size > 0) { + // Remove the frame(s) corresponding to the current function. + void **tracep = trace; + void *return_addr = __builtin_return_address(0); + for (int i = 0; i < size; ++i) { + if (trace[i] == return_addr) { + tracep = trace + (i + 1); + size -= i + 1; + break; + } + } + write_backtrace(tracep, size); + } +#elif defined(_WIN32) + const ULONG max_size = 62; + void *trace[max_size]; + int size = CaptureStackBackTrace(1, max_size, trace, nullptr); + if (size > 0) { + write_backtrace(trace, size); + } +#endif + #ifdef _MSC_VER // How to trigger an exception in VC++ that offers to take us into the // debugger? abort() doesn't do it. We used to be able to assert(false), @@ -411,6 +451,141 @@ assert_failure(const char *expression, int line, return true; } +/** + * + */ +void Notify:: +write_backtrace(void **trace, int size) { + std::ostream &out = nout; + +#ifdef NDEBUG +#elif defined(PHAVE_EXECINFO_H) + char namebuf[128]; + + for (int i = 0; i < size; ++i) { + void *addr = trace[i]; + Dl_info info; + if (dladdr((char *)addr - 1, &info) != 0) { + const char *name = nullptr; + + if (info.dli_sname != nullptr) { + int status = 0; + size_t size = 0; + char *demangled = nullptr; + if (info.dli_sname[0] == '_') { + size = sizeof(namebuf) - 1; + demangled = abi::__cxa_demangle(info.dli_sname, namebuf, &size, &status); + } + if (status == 0 && demangled != nullptr) { + namebuf[size] = 0; + name = demangled; + //if (strncmp(name, "Notify::assert_failure", 22) == 0) { + //continue; + //} + } else { + name = info.dli_sname; + } + } + + out << "[" << addr << "] "; + + if (info.dli_fname != nullptr) { + const char *slash = strrchr(info.dli_fname, '/'); + out << std::left << std::setw(30) << (slash ? slash + 1 : info.dli_fname) << " "; + } + + if (name != nullptr) { + out << name; + } else { + out << info.dli_saddr; + } + + int offset = reinterpret_cast(addr) - reinterpret_cast(info.dli_saddr); + if (offset > 0) { + out << " + " << offset; + } + out << "\n"; + } else { + out << "[" << addr << "]\n"; + } + } +#elif defined(_WIN32) + HMODULE handle = LoadLibraryA("dbghelp.dll"); + if (!handle) { + return; + } + + auto pSymInitialize = (BOOL (WINAPI *)(HANDLE, PCSTR, BOOL))GetProcAddress(handle, "SymInitialize"); + auto pSymCleanup = (BOOL (WINAPI *)(HANDLE))GetProcAddress(handle, "SymCleanup"); + auto pSymFromAddr = (BOOL (WINAPI *)(HANDLE, DWORD64, DWORD64 *, PSYMBOL_INFO))GetProcAddress(handle, "SymFromAddr"); + auto pSymGetModuleInfo64 = (BOOL (WINAPI *)(HANDLE, DWORD64, PIMAGEHLP_MODULE64))GetProcAddress(handle, "SymGetModuleInfo64"); + auto pSymGetLineFromAddr64 = (BOOL (WINAPI *)(HANDLE, DWORD64, PDWORD, PIMAGEHLP_LINE64))GetProcAddress(handle, "SymGetLineFromAddr64"); + if (!pSymInitialize || !pSymCleanup || !pSymFromAddr) { + FreeLibrary(handle); + return; + } + + HANDLE process = GetCurrentProcess(); + pSymInitialize(process, nullptr, TRUE); + + for (int i = 0; i < size; ++i) { + DWORD64 addr = (DWORD64)trace[i]; + + alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + 256] = {0}; + SYMBOL_INFO *symbol = (SYMBOL_INFO *)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = 255; + + out << "[" << (void *)addr << "]"; + + // Show the name of the library. + IMAGEHLP_MODULE64 module; + module.SizeOfStruct = sizeof(module); + const char *basename = "???"; + if (pSymGetModuleInfo64 && pSymGetModuleInfo64(process, addr, &module)) { + const char *slash = strrchr(module.ImageName, '\\'); + basename = (slash ? slash + 1 : module.ImageName); + } + + // Look up the symbol name. If the reported name comes from the export + // table and the address is way off, ignore it, it's probably not right. + if (pSymFromAddr(process, addr, nullptr, symbol) && + ((symbol->Flags & SYMFLAG_EXPORT) == 0 || addr - (DWORD64)symbol->Address < 0x4000)) { + + out << " " << std::left << std::setw(30) << basename << " " << symbol->Name; + + DWORD64 offset = addr - (DWORD64)(symbol->Address); + if (offset > 0) { + out << " + " << offset; + } + + // Look up filename + line number if available. + IMAGEHLP_LINE64 line; + DWORD displacement = 0; + line.SizeOfStruct = sizeof(line); + if (pSymGetLineFromAddr64(process, addr, &displacement, &line)) { + const char *slash = strrchr(line.FileName, '\\'); + const char *basename = (slash ? slash + 1 : line.FileName); + + out << " (" << basename << ":" << line.LineNumber; + //if (displacement > 0) { + // out << " + " << displacement; + //} + out << ")"; + } + } else { + out << " " << basename; + } + out << "\n"; + } + + pSymCleanup(process); + FreeLibrary(handle); +#endif + + out.flush(); +} + /** * Given a string, one of "debug", "info", "warning", etc., return the * corresponding Severity level, or NS_unspecified if none of the strings diff --git a/dtool/src/prc/pnotify.h b/dtool/src/prc/pnotify.h index a4c9a6aca2..76ab5be7fb 100644 --- a/dtool/src/prc/pnotify.h +++ b/dtool/src/prc/pnotify.h @@ -76,10 +76,12 @@ PUBLISHED: public: static ios_fmtflags get_literal_flag(); - bool assert_failure(const std::string &expression, int line, - const char *source_file); - bool assert_failure(const char *expression, int line, - const char *source_file); + static bool assert_failure(const std::string &expression, int line, + const char *source_file); + static bool assert_failure(const char *expression, int line, + const char *source_file); + + static void write_backtrace(void **trace, int size); static NotifySeverity string_severity(const std::string &string); @@ -204,7 +206,7 @@ private: #define nassertr(condition, return_value) \ { \ if (_nassert_check(condition)) { \ - if (Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) { \ + if (Notify::assert_failure(#condition, __LINE__, __FILE__)) { \ return return_value; \ } \ } \ @@ -213,7 +215,7 @@ private: #define nassertv(condition) \ { \ if (_nassert_check(condition)) { \ - if (Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) { \ + if (Notify::assert_failure(#condition, __LINE__, __FILE__)) { \ return; \ } \ } \ @@ -221,12 +223,12 @@ private: #define nassertd(condition) \ if (_nassert_check(condition) && \ - Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__)) + Notify::assert_failure(#condition, __LINE__, __FILE__)) #define nassertr_always(condition, return_value) nassertr(condition, return_value) #define nassertv_always(condition) nassertv(condition) -#define nassert_raise(message) Notify::ptr()->assert_failure(message, __LINE__, __FILE__) +#define nassert_raise(message) Notify::assert_failure(message, __LINE__, __FILE__) #endif // NDEBUG diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 89aed0d7ba..5441a5215c 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -2424,6 +2424,7 @@ DTOOL_CONFIG=[ ("PHAVE_DIRENT_H", 'UNDEF', '1'), ("PHAVE_UCONTEXT_H", 'UNDEF', '1'), ("PHAVE_STDINT_H", '1', '1'), + ("PHAVE_EXECINFO_H", 'UNDEF', '1'), ("HAVE_RTTI", '1', '1'), ("HAVE_X11", 'UNDEF', '1'), ("IS_LINUX", 'UNDEF', '1'), @@ -2560,6 +2561,7 @@ def WriteConfigSettings(): dtool_config["PHAVE_GLOB_H"] = 'UNDEF' dtool_config["PHAVE_LOCKF"] = 'UNDEF' dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF' + dtool_config["PHAVE_EXECINFO_H"] = 'UNDEF' if (GetTarget() == "emscripten"): # There are no threads in JavaScript, so don't bother using them. @@ -2572,6 +2574,7 @@ def WriteConfigSettings(): dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF' dtool_config["HAVE_X11"] = 'UNDEF' dtool_config["HAVE_GLX"] = 'UNDEF' + dtool_config["PHAVE_EXECINFO_H"] = 'UNDEF' # There are no environment vars either, or default prc files. prc_parameters["DEFAULT_PRC_DIR"] = 'UNDEF'