AK: Clean up backtraces

This commit replaces the default backtrace logic with cpptrace, for
nicer, colored backtraces. Cpptrace runs on all of our supported
platforms excpet android. As such backtrace.h is left in place.

All the backtrace functions are made noinline to have a consistent
number of frames. A maximum depth parameter is added to dump_backtrace
with a default of 100. This should be enough, and can be easily
changed, and allows for limiting the maximum depth.

Setting the LADYBIRD_BACKTRACE_SNIPPETS environment variable enables
surrouding code snippets in the backtrace. Specifically 2 lines above
and below. This number can be changed by calling snippet_context on the
formatter. For the whole list of options of what can be done with
formatting see the cpptrace repository.

On Windows we skipped frames when verification fails and when
dump_backtrace was added the logic was wrong and would have skipped
frames we care about.

This commit also implements skipping frames on Linux.
The only time where this does not skip all frames is when the call to
backtrace gets intercepted. Then we will end up skipping one frame less
than needed.

To keep delayload on Windows a patch and overlay port is used. When
upstream accepts these changes and vcpkg bumps the version the patch
could be removed to have just the cmake define.
This commit is contained in:
R-Goc 2025-06-26 18:29:30 +02:00 committed by Jelle Raaijmakers
parent 1e6ac54b75
commit 3c7bad32cd
Notes: github-actions[bot] 2025-10-08 05:09:20 +00:00
11 changed files with 139 additions and 100 deletions

View file

@ -22,9 +22,11 @@
# define PRINT_ERROR(s) (void)::fputs((s), stderr)
#endif
#if defined(AK_HAS_STD_STACKTRACE)
# include <stacktrace>
# include <string>
#if defined(AK_HAS_CPPTRACE)
# include <cpptrace/cpptrace.hpp>
# include <cpptrace/formatting.hpp>
# include <cstdlib>
# include <iostream>
#elif defined(AK_HAS_BACKTRACE_HEADER)
# include <AK/StringBuilder.h>
# include <AK/StringView.h>
@ -39,22 +41,25 @@
extern "C" {
#if defined(AK_HAS_STD_STACKTRACE)
void dump_backtrace()
#if defined(AK_HAS_CPPTRACE)
void dump_backtrace(unsigned frames_to_skip, unsigned max_depth)
{
// We assume the stacktrace implementation demangles symbols, as does microsoft/STL
PRINT_ERROR(std::to_string(std::stacktrace::current(2)).c_str());
PRINT_ERROR("\n");
// We should be using cpptrace for everything but android.
auto stacktrace = cpptrace::generate_trace(frames_to_skip, max_depth);
auto* var = getenv("LADYBIRD_BACKTRACE_SNIPPETS");
bool print_snippets = var && strnlen(var, 1) > 0;
static auto formatter = cpptrace::formatter {}.snippets(print_snippets);
formatter.print(std::cerr, stacktrace);
}
#elif defined(AK_HAS_BACKTRACE_HEADER)
void dump_backtrace()
void dump_backtrace(int frames_to_skip)
{
// Grab symbols and dso name for up to 256 frames
void* trace[256] = {};
int const num_frames = backtrace(trace, array_size(trace));
char** syms = backtrace_symbols(trace, num_frames);
for (auto i = 0; i < num_frames; ++i) {
for (auto i = frames_to_skip; 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]));
StringBuilder error_builder;
@ -93,7 +98,7 @@ void dump_backtrace()
free(syms);
}
#else
void dump_backtrace()
void dump_backtrace([[maybe_unused]] int frames_to_skip)
{
PRINT_ERROR("dump_backtrace() is not supported with the current compilation options.\n");
}
@ -104,16 +109,27 @@ bool ak_colorize_output(void)
#if defined(AK_OS_SERENITY) || defined(AK_OS_ANDROID)
return true;
#elif defined(AK_OS_WINDOWS)
return false;
HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);
if (hStdErr == INVALID_HANDLE_VALUE) {
return false;
}
DWORD dwMode = 0;
if (!GetConsoleMode(hStdErr, &dwMode)) {
return false;
}
DWORD mask = ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
return (dwMode & mask) == mask;
#else
return isatty(STDERR_FILENO) == 1;
#endif
}
void ak_trap(void)
NEVER_INLINE void ak_trap(void)
{
#if defined(AK_HAS_BACKTRACE_HEADER) || defined(AK_HAS_STD_STACKTRACE)
dump_backtrace();
#if defined(AK_HAS_BACKTRACE_HEADER) || defined(AK_HAS_CPPTRACE)
// Skip 3 frames to get to caller. That is dump_backtrace, ak_trap, and ak_verification_failed.
dump_backtrace(3, 100);
#endif
__builtin_trap();
}

View file

@ -6,11 +6,12 @@
#pragma once
extern "C" void dump_backtrace();
// All the functions for stack traces are never inline as we want a consistent number of frames
extern "C" __attribute__((noinline)) void dump_backtrace(unsigned frames_to_skip = 1, unsigned max_depth = 100);
extern "C" bool ak_colorize_output(void);
extern "C" __attribute__((noreturn)) void ak_trap(void);
extern "C" __attribute__((noreturn, noinline)) void ak_trap(void);
extern "C" __attribute__((noreturn)) void ak_verification_failed(char const*);
extern "C" __attribute__((noreturn, noinline)) void ak_verification_failed(char const*);
#define __stringify_helper(x) #x
#define __stringify(x) __stringify_helper(x)
#define VERIFY(...) \
@ -25,7 +26,7 @@ static constexpr bool TODO = false;
#define TODO_PPC64() VERIFY(TODO) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */
#define TODO_PPC() VERIFY(TODO) /* NOLINT(cert-dcl03-c,misc-static-assert) No, this can't be static_assert, it's a runtime check */
extern "C" __attribute__((noreturn)) void ak_assertion_failed(char const*);
extern "C" __attribute__((noreturn, noinline)) void ak_assertion_failed(char const*);
#ifndef NDEBUG
# define ASSERT(...) \
(__builtin_expect(/* NOLINT(readability-simplify-boolean-expr) */ !(__VA_ARGS__), 0) \

View file

@ -49,9 +49,23 @@ endif()
ladybird_lib(AK ak)
include(stacktrace)
find_package(cpptrace CONFIG)
find_package(Backtrace)
if(cpptrace_FOUND AND LADYBIRD_ENABLE_CPPTRACE)
target_link_libraries(AK PRIVATE cpptrace::cpptrace)
target_compile_definitions(AK PRIVATE AK_HAS_CPPTRACE=1)
elseif(Backtrace_FOUND)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.30)
target_link_libraries(${target} PRIVATE Backtrace::Backtrace)
else()
target_include_directories(${target} PRIVATE ${Backtrace_INCLUDE_DIRS})
target_link_libraries(${target} PRIVATE ${Backtrace_LIBRARIES})
endif()
else()
message(WARNING "Cpptrace and Backtrace.h not found. Stack traces will not be available.")
endif()
configure_file(Backtrace.h.in Backtrace.h @ONLY)
link_stacktrace_library(AK STD_DEFINITION AK_HAS_STD_STACKTRACE)
find_package(simdutf REQUIRED)
swizzle_target_properties_for_swift(simdutf::simdutf)

View file

@ -1321,6 +1321,7 @@ static int initialize_console_settings()
// Enable Virtual Terminal Processing to allow ANSI escape codes
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
mode |= ENABLE_PROCESSED_OUTPUT;
if (!SetConsoleMode(console_handle, mode)) {
dbgln("Unable to set console mode");
return 0;

View file

@ -5,8 +5,8 @@ endif()
# Enable better flags for configuring swift compilation mode
if (POLICY CMP0157)
cmake_policy(SET CMP0157 NEW)
set(CMAKE_Swift_COMPILATION_MODE "$<IF:$<CONFIG:Release>,wholemodule,incremental>")
cmake_policy(SET CMP0157 NEW)
set(CMAKE_Swift_COMPILATION_MODE "$<IF:$<CONFIG:Release>,wholemodule,incremental>")
endif()
# Check arguments to return()
@ -42,7 +42,7 @@ ladybird_option(ENABLE_GUI_TARGETS ON CACHE BOOL "Enable building GUI targets")
ladybird_option(ENABLE_INSTALL_HEADERS ON CACHE BOOL "Enable installing headers")
ladybird_option(ENABLE_INSTALL_FREEDESKTOP_FILES ${freedesktop_files_default} CACHE BOOL "Enable installing .desktop and .service files")
ladybird_option(ENABLE_SWIFT OFF CACHE BOOL "Enable building Swift files")
ladybird_option(ENABLE_STD_STACKTRACE OFF CACHE BOOL "Force use of std::stacktrace instead of libbacktrace. If it is not supported the build will fail")
ladybird_option(LADYBIRD_ENABLE_CPPTRACE ON CACHE BOOL "Enable use of cpptrace as the default library for stacktraces. If not available falls back to backtrace.h")
ladybird_option(ENABLE_WINDOWS_CI OFF CACHE BOOL "Enable building targets supported on Windows for CI")
ladybird_option(ENABLE_CI_BASELINE_CPU OFF CACHE BOOL "Use a baseline CPU target for improved ccache sharing")

View file

@ -1,76 +0,0 @@
#
# Provides definitions for stack trace support via libbacktrace or std::stacktrace
#
include_guard()
find_package(Backtrace)
include(CheckCXXSourceCompiles)
function(check_std_stacktrace link_lib library_target out_var)
set(CMAKE_REQUIRED_LIBRARIES ${link_lib})
set(check_var HAVE_STD_STACKTRACE_CHECK)
if (link_lib)
set(check_var "HAVE_STD_STACKTRACE_WITH_${link_lib}")
endif()
check_cxx_source_compiles("
#include <version>
#include <stacktrace>
#include <iostream>
#if !defined(__cpp_lib_stacktrace) || (__cpp_lib_stacktrace < 202011L)
# error \"No std::stacktrace available\"
#endif
int main() {
std::cout << std::stacktrace::current() << std::endl;
return 0;
}"
${check_var}
)
set(${out_var} ${${check_var}})
if (${out_var})
target_link_libraries(${library_target} PRIVATE "${link_lib}")
endif()
return(PROPAGATE ${out_var})
endfunction()
function(link_stacktrace_library target)
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "STD_DEFINITION" "")
if (Backtrace_FOUND AND NOT ENABLE_STD_STACKTRACE)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.30)
target_link_libraries(${target} PRIVATE Backtrace::Backtrace)
else()
target_include_directories(${target} PRIVATE ${Backtrace_INCLUDE_DIRS})
target_link_libraries(${target} PRIVATE ${Backtrace_LIBRARIES})
endif()
else()
check_std_stacktrace("" ${target} HAVE_STD_STACKTRACE)
if(NOT HAVE_STD_STACKTRACE AND CMAKE_CXX_COMPILER_ID STREQUAL GNU)
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14.1)
check_std_stacktrace("stdc++exp" ${target} HAVE_STD_STACKTRACE)
else()
check_std_stacktrace("stdc++_libbacktrace" ${target} HAVE_STD_STACKTRACE)
endif()
endif()
if (NOT HAVE_STD_STACKTRACE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang$")
foreach(lib IN ITEMS "stdc++exp" "stdc++_libbacktrace" "c++experimental" )
check_std_stacktrace("${lib}" ${target} HAVE_STD_STACKTRACE)
if(HAVE_STD_STACKTRACE)
break()
endif()
endforeach()
endif()
if(HAVE_STD_STACKTRACE)
target_compile_definitions(${target} PRIVATE ${ARG_STD_DEFINITION})
else()
set(msg_level WARNING)
if (ENABLE_STD_STACKTRACE)
set(msg_level FATAL_ERROR)
endif()
message(${msg_level} "Backtrace and <stacktrace> not found, stack traces will be unavailable")
endif()
endif()
endfunction()

View file

@ -0,0 +1,28 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 78e0af04..b4aa973d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -276,6 +276,11 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
SET(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
endif()
+if(CPPTRACE_DELAYLOAD_DBGHELP AND BUILD_SHARED_LIBS AND MSVC)
+ target_link_libraries(${target_name} PRIVATE delayimp.lib)
+ target_link_options(${target_name} PRIVATE /DELAYLOAD:dbghelp.dll)
+endif()
+
# =================================================== Back-end setup ===================================================
if(HAS_CXX_EXCEPTION_TYPE)
diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake
index 417982bd..e726db11 100644
--- a/cmake/OptionVariables.cmake
+++ b/cmake/OptionVariables.cmake
@@ -187,6 +187,7 @@ set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
option(CPPTRACE_PROVIDE_EXPORT_SET "" ON)
option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" OFF)
option(CPPTRACE_DISABLE_CXX_20_MODULES "" OFF)
+option(CPPTRACE_DELAYLOAD_DBGHELP "" OFF)
mark_as_advanced(
CPPTRACE_BACKTRACE_PATH

View file

@ -0,0 +1,25 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO jeremy-rifkin/cpptrace
REF "v${VERSION}"
SHA512 4ae394fb3c21149bf2441a754eebe639e6a5534927426b6507806c7bee0b1c982e047c972904d472f1c660adb5be3881e7e3a6eddd18e4e9d376ae3855d50a7c
HEAD_REF main
PATCHES
delay.patch
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
OPTIONS -DCPPTRACE_USE_EXTERNAL_LIBDWARF=ON -DCPPTRACE_USE_EXTERNAL_ZSTD=ON -DCPPTRACE_VCPKG=ON -DCPPTRACE_DELAYLOAD_DBGHELP=ON
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(
PACKAGE_NAME "cpptrace"
CONFIG_PATH "lib/cmake/cpptrace"
)
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

View file

@ -0,0 +1,4 @@
cpptrace provides CMake targets:
find_package(cpptrace CONFIG REQUIRED)
target_link_libraries(main PRIVATE cpptrace::cpptrace)

View file

@ -0,0 +1,22 @@
{
"name": "cpptrace",
"version": "1.0.2",
"description": "Simple, portable, and self-contained stacktrace library for C++11 and newer",
"homepage": "https://github.com/jeremy-rifkin/cpptrace",
"license": "MIT",
"supports": "!(uwp | android)",
"dependencies": [
{
"name": "libdwarf",
"platform": "!windows | mingw"
},
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
}
]
}

View file

@ -12,6 +12,10 @@
"name": "angle",
"platform": "linux | windows | android | freebsd"
},
{
"name": "cpptrace",
"platform": "linux | windows | freebsd | osx"
},
{
"name": "curl",
"default-features": false,