AK: Always use our assertion failure method, and add backtrace to it

On platforms that support it, enable using ``<execinfo.h>`` to get
backtrace(3) to dump a backtrace on assertion failure. This should make
debugging things like WebContent crashes in Lagom much easier.
This commit is contained in:
Andrew Kaster 2023-08-30 16:54:33 +02:00 committed by Andrew Kaster
parent 3056ff6b11
commit 4641af7873
3 changed files with 65 additions and 10 deletions

View file

@ -6,13 +6,68 @@
#include <AK/Assertions.h>
#include <AK/Format.h>
#include <AK/Platform.h>
#include <AK/StringView.h>
#if defined(AK_OS_LINUX) || defined(AK_OS_BSD_GENERIC) || defined(AK_OS_SOLARIS)
# define EXECINFO_BACKTRACE
#endif
#if defined(EXECINFO_BACKTRACE)
# include <cxxabi.h>
# include <execinfo.h>
#endif
#if !defined(KERNEL)
# if defined(EXECINFO_BACKTRACE)
namespace {
ALWAYS_INLINE void dump_backtrace()
{
// Grab symbols and dso name for up to 256 frames
void* trace[256] = {};
int const num_frames = backtrace(trace, sizeof(trace));
char** syms = backtrace_symbols(trace, num_frames);
for (auto i = 0; i < num_frames; ++i) {
// If there is a C++ symbol name in the line of the backtrace, demangle it
StringView sym(syms[i], strlen(syms[i]));
if (auto idx = sym.find("_Z"sv); idx.has_value()) {
// Play C games with the original string so we can print before and after the mangled symbol with a C API
// We don't want to call dbgln() here on substring StringView because we might VERIFY() within AK::Format
syms[i][idx.value() - 1] = '\0';
(void)fprintf(stderr, "%s ", syms[i]);
auto end_of_sym = sym.find(' ', idx.value()).value_or(sym.length() - 1);
syms[i][end_of_sym] = '\0';
size_t buf_size = 128u;
char* buf = static_cast<char*>(malloc(buf_size));
auto* raw_str = &syms[i][idx.value()];
buf = abi::__cxa_demangle(raw_str, buf, &buf_size, nullptr);
(void)fputs(buf ? buf : raw_str, stderr);
free(buf);
(void)fprintf(stderr, " %s", &syms[i][end_of_sym + 1]);
} else {
(void)fputs(sym.characters_without_null_termination(), stderr);
}
(void)fputs("\n", stderr);
}
free(syms);
}
}
# endif
extern "C" {
void ak_verification_failed(char const* message)
{
dbgln("VERIFICATION FAILED: {}", message);
# if defined(EXECINFO_BACKTRACE)
dump_backtrace();
# endif
__builtin_trap();
}
}

View file

@ -11,16 +11,12 @@
#else
# include <assert.h>
extern "C" __attribute__((noreturn)) void ak_verification_failed(char const*);
# if !defined(NDEBUG) && !defined(WIN32)
# define VERIFY assert
# else
# define __stringify_helper(x) #x
# define __stringify(x) __stringify_helper(x)
# define VERIFY(expr) \
(__builtin_expect(!(expr), 0) \
? ak_verification_failed(#expr "\n" __FILE__ ":" __stringify(__LINE__)) \
: (void)0)
# endif
# define __stringify_helper(x) #x
# define __stringify(x) __stringify_helper(x)
# define VERIFY(expr) \
(__builtin_expect(!(expr), 0) \
? ak_verification_failed(#expr " at " __FILE__ ":" __stringify(__LINE__)) \
: (void)0)
# define VERIFY_NOT_REACHED() VERIFY(false) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */
static constexpr bool TODO = false;
# define TODO() VERIFY(TODO) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */

View file

@ -331,6 +331,10 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
# Solaris has socket and networking related functions in two extra libraries
target_link_libraries(LibCore PRIVATE nsl socket)
endif()
if (${CMAKE_SYSTEM_NAME} MATCHES "BSD$")
# BSD Platforms have backtrace(3) in a separate library
target_link_libraries(LibCore PRIVATE execinfo)
endif()
target_sources(LibCore PRIVATE ${AK_SOURCES})
# LibMain