diff --git a/AK/Debug.h.in b/AK/Debug.h.in index e571002d47..7ae8039865 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -298,6 +298,10 @@ # cmakedefine01 LZW_DEBUG #endif +#ifndef MACH_PORT_DEBUG +# cmakedefine01 MACH_PORT_DEBUG +#endif + #ifndef MALLOC_DEBUG # cmakedefine01 MALLOC_DEBUG #endif diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 061e73db06..ae7a559e4e 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -173,6 +173,11 @@ else() add_library(ladybird STATIC ${SOURCES}) endif() +if (APPLE) + target_sources(ladybird PRIVATE MachPortServer.cpp) + target_link_libraries(ladybird PRIVATE LibThreading) +endif() + target_sources(ladybird PUBLIC FILE_SET ladybird TYPE HEADERS BASE_DIRS ${SERENITY_SOURCE_DIR} FILES ${LADYBIRD_HEADERS} diff --git a/Ladybird/MachPortServer.cpp b/Ladybird/MachPortServer.cpp new file mode 100644 index 0000000000..4a7868627b --- /dev/null +++ b/Ladybird/MachPortServer.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "MachPortServer.h" +#include +#include + +namespace Ladybird { + +MachPortServer::MachPortServer() + : m_thread(Threading::Thread::construct([this]() -> intptr_t { thread_loop(); return 0; }, "MachPortServer"sv)) + , m_server_port_name(ByteString::formatted("org.SerenityOS.Ladybird.helper.{}", getpid())) +{ + if (auto err = allocate_server_port(); err.is_error()) + dbgln("Failed to allocate server port: {}", err.error()); + else + start(); +} + +MachPortServer::~MachPortServer() +{ + stop(); +} + +void MachPortServer::start() +{ + m_thread->start(); +} + +void MachPortServer::stop() +{ + // FIXME: We should join instead (after storing should_stop = false) when we have a way to interrupt the thread's mach_msg call + m_thread->detach(); + m_should_stop.store(true, MemoryOrder::memory_order_release); +} + +bool MachPortServer::is_initialized() +{ + return MACH_PORT_VALID(m_server_port_recv_right.port()) && MACH_PORT_VALID(m_server_port_send_right.port()); +} + +ErrorOr MachPortServer::allocate_server_port() +{ + m_server_port_recv_right = TRY(Core::MachPort::create_with_right(Core::MachPort::PortRight::Receive)); + m_server_port_send_right = TRY(m_server_port_recv_right.insert_right(Core::MachPort::MessageRight::MakeSend)); + TRY(m_server_port_recv_right.register_with_bootstrap_server(m_server_port_name)); + + dbgln_if(MACH_PORT_DEBUG, "Success! we created and attached mach port {:x} to bootstrap server with name {}", m_server_port_recv_right.port(), m_server_port_name); + return {}; +} + +void MachPortServer::thread_loop() +{ + while (!m_should_stop.load(MemoryOrder::memory_order_acquire)) { + + WebView::ParentPortMessage message {}; + + // Get the pid of the child from the audit trailer so we can associate the port w/it + mach_msg_options_t const options = MACH_RCV_MSG | MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); + + // FIXME: How can we interrupt this call during application shutdown? + auto const ret = mach_msg(&message.header, options, 0, sizeof(message), m_server_port_recv_right.port(), MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != KERN_SUCCESS) { + dbgln("mach_msg failed: {}", mach_error_string(ret)); + break; + } + + if (message.header.msgh_id != WebView::SELF_TASK_PORT_MESSAGE_ID) { + dbgln("Received message with id {}, ignoring", message.header.msgh_id); + continue; + } + + if (MACH_MSGH_BITS_LOCAL(message.header.msgh_bits) != MACH_MSG_TYPE_MOVE_SEND) { + dbgln("Received message with invalid local port rights {}, ignoring", MACH_MSGH_BITS_LOCAL(message.header.msgh_bits)); + continue; + } + + auto pid = static_cast(message.trailer.msgh_audit.val[5]); + auto child_port = Core::MachPort::adopt_right(message.port_descriptor.name, Core::MachPort::PortRight::Send); + dbgln_if(MACH_PORT_DEBUG, "Received child port {:x} from pid {}", child_port.port(), pid); + + if (on_receive_child_mach_port) + on_receive_child_mach_port(pid, move(child_port)); + } +} + +} diff --git a/Ladybird/MachPortServer.h b/Ladybird/MachPortServer.h new file mode 100644 index 0000000000..71a7559d9b --- /dev/null +++ b/Ladybird/MachPortServer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#if !defined(AK_OS_MACH) +# error "This file is only for Mach kernel-based OS's" +#endif + +#include +#include +#include +#include + +namespace Ladybird { + +class MachPortServer { + +public: + MachPortServer(); + ~MachPortServer(); + + void start(); + void stop(); + + bool is_initialized(); + + Function on_receive_child_mach_port; + + ByteString const& server_port_name() const { return m_server_port_name; } + +private: + void thread_loop(); + ErrorOr allocate_server_port(); + + NonnullRefPtr m_thread; + ByteString const m_server_port_name; + Core::MachPort m_server_port_recv_right; + Core::MachPort m_server_port_send_right; + + Atomic m_should_stop { false }; +}; + +} diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index d8e249ee45..76592e6949 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -114,6 +114,7 @@ set(LOOKUPSERVER_DEBUG ON) set(LOOPBACK_DEBUG ON) set(LZMA_DEBUG ON) set(LZW_DEBUG ON) +set(MACH_PORT_DEBUG ON) set(MALLOC_DEBUG ON) set(MARKDOWN_DEBUG ON) set(MATROSKA_DEBUG ON) diff --git a/Meta/gn/secondary/AK/BUILD.gn b/Meta/gn/secondary/AK/BUILD.gn index 1c24d6168f..8ee89fc320 100644 --- a/Meta/gn/secondary/AK/BUILD.gn +++ b/Meta/gn/secondary/AK/BUILD.gn @@ -302,6 +302,7 @@ write_cmake_config("ak_debug_gen") { "LOOKUPSERVER_DEBUG=", "LZMA_DEBUG=", "LZW_DEBUG=", + "MACH_PORT_DEBUG=", "MALLOC_DEBUG=", "MARKDOWN_DEBUG=", "MATROSKA_DEBUG=", diff --git a/Meta/gn/secondary/Ladybird/BUILD.gn b/Meta/gn/secondary/Ladybird/BUILD.gn index d58cc4d6db..b03476ce3b 100644 --- a/Meta/gn/secondary/Ladybird/BUILD.gn +++ b/Meta/gn/secondary/Ladybird/BUILD.gn @@ -145,7 +145,10 @@ executable("ladybird_executable") { ] } - if (current_os != "mac") { + if (current_os == "mac") { + sources += [ "MachPortServer.cpp" ] + deps += [ "//Userland/Libraries/LibThreading" ] + } else { data_deps += [ ":ladybird_copy_config_resources", ":ladybird_copy_emoji", diff --git a/Meta/gn/secondary/Userland/Libraries/LibWebView/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibWebView/BUILD.gn index 3434a55868..a6abba1cfe 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibWebView/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibWebView/BUILD.gn @@ -154,6 +154,8 @@ shared_library("LibWebView") { ] } else if (current_os == "linux") { sources += [ "Platform/ProcessStatisticsLinux.cpp" ] + } else if (current_os == "mac") { + sources += [ "Platform/ProcessStatisticsMach.cpp" ] } else { sources += [ "Platform/ProcessStatisticsNoop.cpp" ] } diff --git a/Userland/Libraries/LibWebView/CMakeLists.txt b/Userland/Libraries/LibWebView/CMakeLists.txt index 5feecf07b1..7d2b6e2326 100644 --- a/Userland/Libraries/LibWebView/CMakeLists.txt +++ b/Userland/Libraries/LibWebView/CMakeLists.txt @@ -31,6 +31,10 @@ elseif(LINUX AND NOT ANDROID) list(APPEND SOURCES Platform/ProcessStatisticsLinux.cpp ) +elseif(APPLE) + list(APPEND SOURCES + Platform/ProcessStatisticsMach.cpp + ) else() list(APPEND SOURCES Platform/ProcessStatisticsNoop.cpp diff --git a/Userland/Libraries/LibWebView/Platform/ProcessInfo.h b/Userland/Libraries/LibWebView/Platform/ProcessInfo.h index 09a47f6874..3a7dd6781a 100644 --- a/Userland/Libraries/LibWebView/Platform/ProcessInfo.h +++ b/Userland/Libraries/LibWebView/Platform/ProcessInfo.h @@ -8,6 +8,10 @@ #include +#if defined(AK_OS_MACH) +# include +#endif + namespace WebView { enum class ProcessType { @@ -20,12 +24,28 @@ enum class ProcessType { }; struct ProcessInfo { - ProcessType type; + ProcessInfo(ProcessType type, pid_t pid) + : type(type) + , pid(pid) + { + } + + ProcessType type = ProcessType::WebContent; pid_t pid; u64 memory_usage_bytes = 0; float cpu_percent = 0.0f; u64 time_spent_in_process = 0; + +#if defined(AK_OS_MACH) + Core::MachPort child_task_port; + + ProcessInfo(pid_t pid, Core::MachPort&& port) + : pid(pid) + , child_task_port(move(port)) + { + } +#endif }; } diff --git a/Userland/Libraries/LibWebView/Platform/ProcessStatisticsMach.cpp b/Userland/Libraries/LibWebView/Platform/ProcessStatisticsMach.cpp new file mode 100644 index 0000000000..9d7ccab7be --- /dev/null +++ b/Userland/Libraries/LibWebView/Platform/ProcessStatisticsMach.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#if !defined(AK_OS_MACH) +# error "This file is only available on Mach platforms" +#endif + +#include +#include +#include + +namespace WebView { + +ErrorOr update_process_statistics(ProcessStatistics&) +{ + return {}; +} + +void register_with_mach_server(ByteString const& server_name) +{ + auto server_port_or_error = Core::MachPort::look_up_from_bootstrap_server(server_name); + if (server_port_or_error.is_error()) { + dbgln("Failed to lookup server port: {}", server_port_or_error.error()); + return; + } + auto server_port = server_port_or_error.release_value(); + + // Send our own task port to the server so they can query statistics about us + ChildPortMessage message {}; + message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSGH_BITS_ZERO) | MACH_MSGH_BITS_COMPLEX; + message.header.msgh_size = sizeof(message); + message.header.msgh_remote_port = server_port.port(); + message.header.msgh_local_port = MACH_PORT_NULL; + message.header.msgh_id = SELF_TASK_PORT_MESSAGE_ID; + message.body.msgh_descriptor_count = 1; + message.port_descriptor.name = mach_task_self(); + message.port_descriptor.disposition = MACH_MSG_TYPE_COPY_SEND; + message.port_descriptor.type = MACH_MSG_PORT_DESCRIPTOR; + + mach_msg_timeout_t const timeout = 100; // milliseconds + + auto const send_result = mach_msg(&message.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, message.header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL); + if (send_result != KERN_SUCCESS) { + dbgln("Failed to send message to server: {}", mach_error_string(send_result)); + return; + } +} + +} diff --git a/Userland/Libraries/LibWebView/Platform/ProcessStatisticsMach.h b/Userland/Libraries/LibWebView/Platform/ProcessStatisticsMach.h new file mode 100644 index 0000000000..fd64d4ec75 --- /dev/null +++ b/Userland/Libraries/LibWebView/Platform/ProcessStatisticsMach.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +#if !defined(AK_OS_MACH) +# error "This file is only available on Mach platforms" +#endif + +#include +#include + +namespace WebView { + +struct ChildPortMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t port_descriptor; +}; + +struct ParentPortMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t port_descriptor; + mach_msg_audit_trailer_t trailer; // for the child's pid +}; + +static constexpr mach_msg_id_t SELF_TASK_PORT_MESSAGE_ID = 0x1234CAFE; + +void register_with_mach_server(ByteString const& server_name); + +} diff --git a/Userland/Libraries/LibWebView/ProcessManager.cpp b/Userland/Libraries/LibWebView/ProcessManager.cpp index 40b71bdc11..c93a170445 100644 --- a/Userland/Libraries/LibWebView/ProcessManager.cpp +++ b/Userland/Libraries/LibWebView/ProcessManager.cpp @@ -84,12 +84,31 @@ void ProcessManager::initialize() void ProcessManager::add_process(ProcessType type, pid_t pid) { + Threading::MutexLocker locker { m_lock }; dbgln("ProcessManager::add_process({}, {})", process_name_from_type(type), pid); - m_statistics.processes.append({ type, pid, 0, 0 }); + if (auto existing_process = m_statistics.processes.find_if([&](auto& info) { return info.pid == pid; }); !existing_process.is_end()) { + existing_process->type = type; + return; + } + m_statistics.processes.append({ type, pid }); } +#if defined(AK_OS_MACH) +void ProcessManager::add_process(pid_t pid, Core::MachPort&& port) +{ + Threading::MutexLocker locker { m_lock }; + dbgln("ProcessManager::add_process({}, {:p})", pid, port.port()); + if (auto existing_process = m_statistics.processes.find_if([&](auto& info) { return info.pid == pid; }); !existing_process.is_end()) { + existing_process->child_task_port = move(port); + return; + } + m_statistics.processes.append({ pid, move(port) }); +} +#endif + void ProcessManager::remove_process(pid_t pid) { + Threading::MutexLocker locker { m_lock }; m_statistics.processes.remove_first_matching([&](auto& info) { if (info.pid == pid) { dbgln("ProcessManager: Remove process {} ({})", process_name_from_type(info.type), pid); @@ -113,13 +132,15 @@ void ProcessManager::update_all_processes() } } + Threading::MutexLocker locker { m_lock }; (void)update_process_statistics(m_statistics); } String ProcessManager::generate_html() { + Threading::MutexLocker locker { m_lock }; StringBuilder builder; - auto processes = m_statistics.processes; + auto const& processes = m_statistics.processes; builder.append(R"( @@ -170,7 +191,7 @@ String ProcessManager::generate_html() )"sv); - for (auto& process : processes) { + for (auto const& process : processes) { builder.append(""sv); builder.append(""sv); builder.append(WebView::process_name_from_type(process.type)); diff --git a/Userland/Libraries/LibWebView/ProcessManager.h b/Userland/Libraries/LibWebView/ProcessManager.h index 835aa9b303..8745a7a7fa 100644 --- a/Userland/Libraries/LibWebView/ProcessManager.h +++ b/Userland/Libraries/LibWebView/ProcessManager.h @@ -4,14 +4,15 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#pragma once + #include #include #include +#include #include #include -#pragma once - namespace WebView { ProcessType process_type_from_name(StringView); @@ -25,8 +26,12 @@ public: void add_process(WebView::ProcessType, pid_t); void remove_process(pid_t); +#if defined(AK_OS_MACH) + void add_process(pid_t, Core::MachPort&&); +#endif + void update_all_processes(); - Vector processes() const { return m_statistics.processes; } + Vector const& processes() const { return m_statistics.processes; } String generate_html(); @@ -35,6 +40,7 @@ private: ~ProcessManager(); ProcessStatistics m_statistics; + Threading::Mutex m_lock; }; }