mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
Ladybird+LibWebView: Add mechanism to get Mach task port for helpers
On macOS, it's not trivial to get a Mach task port for your children. This implementation registers the chrome process as a well-known service with launchd based on its pid, and lets each child process send over a reference to its mach_task_self() back to the chrome. We'll need this Mach task port right to get process statistics.
This commit is contained in:
parent
77f18cf062
commit
8c5e64e686
Notes:
sideshowbarker
2024-07-17 01:46:00 +09:00
Author: https://github.com/ADKaster Commit: https://github.com/SerenityOS/serenity/commit/8c5e64e686 Pull-request: https://github.com/SerenityOS/serenity/pull/23841 Reviewed-by: https://github.com/bugaevc Reviewed-by: https://github.com/nico ✅ Reviewed-by: https://github.com/trflynn89 ✅
14 changed files with 305 additions and 8 deletions
|
@ -298,6 +298,10 @@
|
|||
# cmakedefine01 LZW_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef MACH_PORT_DEBUG
|
||||
# cmakedefine01 MACH_PORT_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef MALLOC_DEBUG
|
||||
# cmakedefine01 MALLOC_DEBUG
|
||||
#endif
|
||||
|
|
|
@ -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}
|
||||
|
|
90
Ladybird/MachPortServer.cpp
Normal file
90
Ladybird/MachPortServer.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "MachPortServer.h"
|
||||
#include <AK/Debug.h>
|
||||
#include <LibWebView/Platform/ProcessStatisticsMach.h>
|
||||
|
||||
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<void> 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<pid_t>(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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
49
Ladybird/MachPortServer.h
Normal file
49
Ladybird/MachPortServer.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#if !defined(AK_OS_MACH)
|
||||
# error "This file is only for Mach kernel-based OS's"
|
||||
#endif
|
||||
|
||||
#include <AK/Atomic.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibThreading/Thread.h>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
class MachPortServer {
|
||||
|
||||
public:
|
||||
MachPortServer();
|
||||
~MachPortServer();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
bool is_initialized();
|
||||
|
||||
Function<void(pid_t, Core::MachPort)> on_receive_child_mach_port;
|
||||
|
||||
ByteString const& server_port_name() const { return m_server_port_name; }
|
||||
|
||||
private:
|
||||
void thread_loop();
|
||||
ErrorOr<void> allocate_server_port();
|
||||
|
||||
NonnullRefPtr<Threading::Thread> m_thread;
|
||||
ByteString const m_server_port_name;
|
||||
Core::MachPort m_server_port_recv_right;
|
||||
Core::MachPort m_server_port_send_right;
|
||||
|
||||
Atomic<bool> m_should_stop { false };
|
||||
};
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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=",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" ]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
#include <AK/Types.h>
|
||||
|
||||
#if defined(AK_OS_MACH)
|
||||
# include <LibCore/MachPort.h>
|
||||
#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
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#if !defined(AK_OS_MACH)
|
||||
# error "This file is only available on Mach platforms"
|
||||
#endif
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <LibCore/MachPort.h>
|
||||
#include <LibWebView/Platform/ProcessStatisticsMach.h>
|
||||
|
||||
namespace WebView {
|
||||
|
||||
ErrorOr<void> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#if !defined(AK_OS_MACH)
|
||||
# error "This file is only available on Mach platforms"
|
||||
#endif
|
||||
|
||||
#include <LibWebView/Platform/ProcessStatistics.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
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);
|
||||
|
||||
}
|
|
@ -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"(
|
||||
<html>
|
||||
|
@ -170,7 +191,7 @@ String ProcessManager::generate_html()
|
|||
<tbody>
|
||||
)"sv);
|
||||
|
||||
for (auto& process : processes) {
|
||||
for (auto const& process : processes) {
|
||||
builder.append("<tr>"sv);
|
||||
builder.append("<td>"sv);
|
||||
builder.append(WebView::process_name_from_type(process.type));
|
||||
|
|
|
@ -4,14 +4,15 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibCore/EventReceiver.h>
|
||||
#include <LibThreading/Mutex.h>
|
||||
#include <LibWebView/Forward.h>
|
||||
#include <LibWebView/Platform/ProcessStatistics.h>
|
||||
|
||||
#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<ProcessInfo> processes() const { return m_statistics.processes; }
|
||||
Vector<ProcessInfo> const& processes() const { return m_statistics.processes; }
|
||||
|
||||
String generate_html();
|
||||
|
||||
|
@ -35,6 +40,7 @@ private:
|
|||
~ProcessManager();
|
||||
|
||||
ProcessStatistics m_statistics;
|
||||
Threading::Mutex m_lock;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue