Files
ladybird/Services/WebContent/main.cpp
Zaggy1024 e4a8fc4b7b WebContent: Exit after the crash signal handler dumps a backtrace
I'm unsure exactly why this is possible, but using a freed GC pointer
on macOS was causing a segmentation violation to reach our signal
handler, but instead of exiting, the process would get stuck.

To solve this, when terminal signals are received, just exit() instead
of trying to let it get to the default handler. This allows test-web to
get unstuck in cases like this, and instead of timing out and leaving a
zombie, count the test as a crash.

In particular, the issue was caused by calling top_level_traversable()
on a null Navigable. Since GC::Ptr's null checks are debug-only, it was
trying to access garbage m_parent fields. With or without the signal
handlers, this would result in an (unsurprising) EXC_BAD_ACCESS, as
observed by attaching lldb. Regardless of debuggers being attached, or
signal handlers being enabled, after the signal, the process would get
stuck and refuse to exit, even after a SIGKILL. Sampling the stuck
processes didn't seem to indicate that the program counter was moving
in this state, so I'm unsure what causes it to get stuck.
2026-03-15 11:10:05 +01:00

321 lines
13 KiB
C++

/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibCore/Process.h>
#include <LibCore/Resource.h>
#include <LibCore/System.h>
#include <LibCore/SystemServerTakeover.h>
#include <LibCrypto/OpenSSLForward.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibIPC/TransportHandle.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMain/Main.h>
#include <LibRequests/RequestClient.h>
#include <LibUnicode/TimeZone.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/HTML/UniversalGlobalScope.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Internals/Internals.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Painting/BackingStoreManager.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Platform/FontPlugin.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWebView/Plugins/ImageCodecPlugin.h>
#include <LibWebView/SiteIsolation.h>
#include <LibWebView/Utilities.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageClient.h>
#include <WebContent/WebDriverConnection.h>
#include <openssl/thread.h>
#if defined(AK_OS_MACOS)
# include <LibCore/Platform/ProcessStatisticsMach.h>
#endif
#if defined(AK_OS_WINDOWS)
# include <objbase.h>
#endif
#include <SDL3/SDL_init.h>
#if !defined(AK_OS_WINDOWS)
# include <signal.h>
static void crash_signal_handler(int signo)
{
char const* name;
switch (signo) {
case SIGSEGV:
name = "SIGSEGV";
break;
case SIGBUS:
name = "SIGBUS";
break;
case SIGFPE:
name = "SIGFPE";
break;
case SIGABRT:
name = "SIGABRT";
break;
case SIGILL:
name = "SIGILL";
break;
default:
name = "unknown";
break;
}
warnln("\n\033[31;1mCRASH\033[0m: Received signal {} ({})", name, signo);
dump_backtrace(2, 100);
exit(128 + signo);
}
static void install_crash_signal_handlers()
{
struct sigaction sa;
sa.sa_handler = crash_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGSEGV, &sa, nullptr);
sigaction(SIGBUS, &sa, nullptr);
sigaction(SIGFPE, &sa, nullptr);
sigaction(SIGABRT, &sa, nullptr);
sigaction(SIGILL, &sa, nullptr);
}
#endif
static ErrorOr<void> load_content_filters(StringView config_path);
static ErrorOr<void> connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle);
static ErrorOr<void> connect_to_image_decoder(IPC::TransportHandle const& handle);
ErrorOr<int> ladybird_main(Main::Arguments arguments)
{
AK::set_rich_debug_enabled(true);
#if !defined(AK_OS_WINDOWS)
install_crash_signal_handlers();
#endif
#if defined(AK_OS_WINDOWS)
// NOTE: We need this here otherwise SDL inits COM in the APARTMENTTHREADED model which we don't want as we need to
// make calls across threads which would otherwise have a high overhead. It is safe for all the objects we use.
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
VERIFY(SUCCEEDED(hr));
ScopeGuard uninitialize_com = []() { CoUninitialize(); };
#endif
// SDL is used for the Gamepad API.
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
dbgln("Failed to initialize SDL3: {}", SDL_GetError());
return -1;
}
Core::EventLoop event_loop;
WebView::platform_init();
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPlugin);
StringView command_line {};
StringView executable_path {};
auto config_path = ByteString::formatted("{}/ladybird/default-config", WebView::s_ladybird_resource_root);
StringView mach_server_name {};
Vector<ByteString> certificates;
bool enable_test_mode = false;
bool expose_experimental_interfaces = false;
bool expose_internals_object = false;
bool wait_for_debugger = false;
bool log_all_js_exceptions = false;
bool disable_site_isolation = false;
bool enable_idl_tracing = false;
bool enable_http_memory_cache = false;
bool force_cpu_painting = false;
bool force_fontconfig = false;
bool collect_garbage_on_every_allocation = false;
bool is_headless = false;
bool disable_scrollbar_painting = false;
StringView echo_server_port_string_view {};
StringView default_time_zone {};
bool file_origins_are_tuple_origins = false;
Core::ArgsParser args_parser;
args_parser.add_option(command_line, "Browser process command line", "command-line", 0, "command_line");
args_parser.add_option(executable_path, "Browser process executable path", "executable-path", 0, "executable_path");
args_parser.add_option(config_path, "Ladybird configuration path", "config-path", 0, "config_path");
args_parser.add_option(enable_test_mode, "Enable test mode", "test-mode");
args_parser.add_option(expose_experimental_interfaces, "Expose experimental IDL interfaces", "expose-experimental-interfaces");
args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions");
args_parser.add_option(disable_site_isolation, "Disable site isolation", "disable-site-isolation");
args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing");
args_parser.add_option(enable_http_memory_cache, "Enable HTTP cache", "enable-http-memory-cache");
args_parser.add_option(force_cpu_painting, "Force CPU painting", "force-cpu-painting");
args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig");
args_parser.add_option(collect_garbage_on_every_allocation, "Collect garbage after every JS heap allocation", "collect-garbage-on-every-allocation");
args_parser.add_option(disable_scrollbar_painting, "Don't paint horizontal or vertical viewport scrollbars", "disable-scrollbar-painting");
args_parser.add_option(echo_server_port_string_view, "Echo server port used in test internals", "echo-server-port", 0, "echo_server_port");
args_parser.add_option(is_headless, "Report that the browser is running in headless mode", "headless");
args_parser.add_option(default_time_zone, "Default time zone", "default-time-zone", 0, "time-zone-id");
args_parser.add_option(file_origins_are_tuple_origins, "Treat file:// URLs as having tuple origins", "tuple-file-origins");
args_parser.parse(arguments);
if (wait_for_debugger) {
Core::Process::wait_for_debugger_and_break();
}
if (!default_time_zone.is_empty()) {
if (auto result = Unicode::set_current_time_zone(default_time_zone); result.is_error())
dbgln("Failed to set default time zone: {}", result.error());
}
if (file_origins_are_tuple_origins)
URL::set_file_scheme_urls_have_tuple_origins();
auto& font_provider = static_cast<Gfx::PathFontProvider&>(Gfx::FontDatabase::the().install_system_font_provider(make<Gfx::PathFontProvider>()));
if (force_fontconfig) {
font_provider.set_name_but_fixme_should_create_custom_system_font_provider("FontConfig"_string);
}
font_provider.load_all_fonts_from_uri("resource://fonts"sv);
Web::set_browser_process_command_line(command_line);
Web::set_browser_process_executable_path(executable_path);
// Always use the CPU backend for tests, as the GPU backend is not deterministic
WebContent::PageClient::set_use_skia_painter(force_cpu_painting ? WebContent::PageClient::UseSkiaPainter::CPUBackend : WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
WebContent::PageClient::set_is_headless(is_headless);
if (disable_site_isolation)
WebView::disable_site_isolation();
if (enable_http_memory_cache)
Web::Fetch::Fetching::set_http_memory_cache_enabled(true);
Web::Painting::set_paint_viewport_scrollbars(!disable_scrollbar_painting);
if (!echo_server_port_string_view.is_empty()) {
if (auto maybe_echo_server_port = echo_server_port_string_view.to_number<u16>(); maybe_echo_server_port.has_value())
Web::Internals::Internals::set_echo_server_port(maybe_echo_server_port.value());
else
VERIFY_NOT_REACHED();
}
#if defined(AK_OS_MACOS)
if (!mach_server_name.is_empty()) {
auto server_port = Core::Platform::register_with_mach_server(mach_server_name);
Web::Painting::BackingStoreManager::set_browser_mach_port(move(server_port));
}
#endif
OPENSSL_TRY(OSSL_set_max_threads(nullptr, Core::System::hardware_concurrency()));
Web::HTML::Window::set_enable_test_mode(enable_test_mode);
Web::HTML::Window::set_internals_object_exposed(expose_internals_object);
Web::HTML::UniversalGlobalScopeMixin::set_experimental_interfaces_exposed(expose_experimental_interfaces);
Web::Platform::FontPlugin::install(*new Web::Platform::FontPlugin(enable_test_mode, &font_provider));
Web::Bindings::initialize_main_thread_vm(Web::Bindings::AgentType::SimilarOriginWindow);
if (collect_garbage_on_every_allocation)
Web::Bindings::main_thread_vm().heap().set_should_collect_on_every_allocation(true);
if (log_all_js_exceptions) {
JS::set_log_all_js_exceptions(true);
}
if (enable_idl_tracing) {
Web::WebIDL::set_enable_idl_tracing(true);
}
auto maybe_content_filter_error = load_content_filters(config_path);
if (maybe_content_filter_error.is_error())
dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());
auto webcontent_socket = TRY(Core::take_over_socket_from_system_server("WebContent"sv));
auto webcontent_client = WebContent::ConnectionFromClient::construct(make<IPC::Transport>(move(webcontent_socket)));
auto& heap = Web::Bindings::main_thread_vm().heap();
webcontent_client->on_request_server_connection = [&heap](auto const& handle) {
if (auto result = connect_to_resource_loader(heap, handle); result.is_error())
dbgln("Failed to connect to resource loader: {}", result.error());
};
webcontent_client->on_image_decoder_connection = [](auto const& handle) {
if (auto result = connect_to_image_decoder(handle); result.is_error())
dbgln("Failed to connect to image decoder: {}", result.error());
};
return event_loop.exec();
}
static ErrorOr<void> load_content_filters(StringView config_path)
{
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
auto file = TRY(Core::File::open(ByteString::formatted("{}/BrowserContentFilters.txt", config_path), Core::File::OpenMode::Read));
auto ad_filter_list = TRY(Core::InputBufferedFile::create(move(file)));
Vector<String> patterns;
while (TRY(ad_filter_list->can_read_line())) {
auto line = TRY(ad_filter_list->read_line(buffer));
if (line.is_empty())
continue;
auto pattern = TRY(String::from_utf8(line));
TRY(patterns.try_append(move(pattern)));
}
auto& content_filter = Web::ContentFilter::the();
TRY(content_filter.set_patterns(patterns));
return {};
}
ErrorOr<void> connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle)
{
auto transport = TRY(handle.create_transport());
auto request_client = TRY(try_make_ref_counted<Requests::RequestClient>(move(transport)));
#ifdef AK_OS_WINDOWS
auto response = request_client->send_sync<Messages::RequestServer::InitTransport>(Core::System::getpid());
request_client->transport().set_peer_pid(response->peer_pid());
#endif
if (Web::ResourceLoader::is_initialized())
Web::ResourceLoader::the().set_client(move(request_client));
else
Web::ResourceLoader::initialize(heap, move(request_client));
return {};
}
ErrorOr<void> connect_to_image_decoder(IPC::TransportHandle const& handle)
{
auto transport = TRY(handle.create_transport());
auto new_client = TRY(try_make_ref_counted<ImageDecoderClient::Client>(move(transport)));
#ifdef AK_OS_WINDOWS
auto response = new_client->send_sync<Messages::ImageDecoderServer::InitTransport>(Core::System::getpid());
new_client->transport().set_peer_pid(response->peer_pid());
#endif
if (Web::Platform::ImageCodecPlugin::is_initialized())
static_cast<WebView::ImageCodecPlugin&>(Web::Platform::ImageCodecPlugin::the()).set_client(move(new_client));
else
Web::Platform::ImageCodecPlugin::install(*new WebView::ImageCodecPlugin(move(new_client)));
return {};
}