notify: Print out C++ backtrace on assert-abort
This commit is contained in:
parent
2540648ac9
commit
6ac2164c25
|
|
@ -35,6 +35,18 @@
|
|||
#include "emscriptenLogStream.h"
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#ifdef PHAVE_EXECINFO_H
|
||||
#include <execinfo.h> // for backtrace()
|
||||
#include <dlfcn.h>
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <dbghelp.h>
|
||||
#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<uintptr_t>(addr) - reinterpret_cast<uintptr_t>(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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue