diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 82e8400d5..6f8351872 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -872,6 +872,14 @@ class CrashpadClient { void RemoveAttachment(const base::FilePath& attachment); #endif + //! \brief Requests that the handler retry pending report uploads. + //! + //! Wakes the handler's upload thread to immediately process pending reports + //! rather than waiting for its next periodic scan. Useful after state + //! changes (such as clearing UploadsPaused) that should take effect without + //! delay. + void RequestRetry(); + private: #if BUILDFLAG(IS_WIN) || DOXYGEN //! \brief Registers process handlers for the client. diff --git a/client/crashpad_client_linux.cc b/client/crashpad_client_linux.cc index df72a25cc..43844df03 100644 --- a/client/crashpad_client_linux.cc +++ b/client/crashpad_client_linux.cc @@ -445,6 +445,11 @@ class RequestCrashDumpHandler : public SignalHandler { client.RemoveAttachment(attachment); } + void RequestRetry() { + ExceptionHandlerClient client(sock_to_handler_.get(), true); + client.RequestRetry(); + } + private: RequestCrashDumpHandler() = default; @@ -832,4 +837,9 @@ void CrashpadClient::RemoveAttachment(const base::FilePath& attachment) { signal_handler->RemoveAttachment(attachment); } +void CrashpadClient::RequestRetry() { + auto signal_handler = RequestCrashDumpHandler::Get(); + signal_handler->RequestRetry(); +} + } // namespace crashpad diff --git a/client/crashpad_client_mac.cc b/client/crashpad_client_mac.cc index 8e339c606..f6cb8f87d 100644 --- a/client/crashpad_client_mac.cc +++ b/client/crashpad_client_mac.cc @@ -31,6 +31,7 @@ #include "util/mac/mac_util.h" #include "util/mach/bootstrap.h" #include "util/mach/child_port_handshake.h" +#include "util/mach/exception_handler_protocol.h" #include "util/mach/exception_ports.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" @@ -612,6 +613,11 @@ base::apple::ScopedMachSendRight CrashpadClient::GetHandlerMachPort() const { return base::apple::ScopedMachSendRight(exception_port_.get()); } +void CrashpadClient::RequestRetry() { + SendClientToServerMessage(exception_port_.get(), + ClientToServerMessage::kRequestRetry); +} + // static void CrashpadClient::UseSystemDefaultHandler() { base::apple::ScopedMachSendRight system_crash_reporter_handler( diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index b81f4256c..15cbe93dd 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -1231,4 +1231,11 @@ void CrashpadClient::RemoveAttachment(const base::FilePath& attachment) { &response); } +void CrashpadClient::RequestRetry() { + ClientToServerMessage message = {}; + message.type = ClientToServerMessage::kRequestRetry; + ServerToClientMessage response = {}; + SendToCrashHandlerServer(ipc_pipe_, message, &response); +} + } // namespace crashpad diff --git a/client/settings.cc b/client/settings.cc index 38da08b8b..dabed1f3e 100644 --- a/client/settings.cc +++ b/client/settings.cc @@ -126,6 +126,7 @@ struct SettingsReader::Data { enum Options : uint32_t { kUploadsEnabled = 1 << 0, + kUploadsPaused = 1 << 1, }; Data() : magic(kSettingsMagic), @@ -174,6 +175,17 @@ bool SettingsReader::GetUploadsEnabled(bool* enabled) { return true; } +bool SettingsReader::GetUploadsPaused(bool* paused) { + DCHECK(initialized().is_valid()); + + Data settings; + if (!OpenAndReadSettings(&settings)) + return false; + + *paused = (settings.options & Data::Options::kUploadsPaused) != 0; + return true; +} + bool SettingsReader::GetLastUploadAttemptTime(time_t* time) { DCHECK(initialized().is_valid()); @@ -268,6 +280,22 @@ bool Settings::SetUploadsEnabled(bool enabled) { return WriteSettings(handle.get(), settings); } +bool Settings::SetUploadsPaused(bool paused) { + DCHECK(initialized().is_valid()); + + Data settings; + ScopedLockedFileHandle handle = OpenForWritingAndReadSettings(&settings); + if (!handle.is_valid()) + return false; + + if (paused) + settings.options |= Data::Options::kUploadsPaused; + else + settings.options &= ~Data::Options::kUploadsPaused; + + return WriteSettings(handle.get(), settings); +} + bool Settings::SetLastUploadAttemptTime(time_t time) { DCHECK(initialized().is_valid()); diff --git a/client/settings.h b/client/settings.h index 7da179de2..9ca78d89c 100644 --- a/client/settings.h +++ b/client/settings.h @@ -100,6 +100,20 @@ class SettingsReader { //! error logged. bool GetUploadsEnabled(bool* enabled); + //! \brief Retrieves whether uploads are paused. + //! + //! When paused, pending reports are retained in the `pending` state rather + //! than being uploaded or skipped. Reports accumulate in `pending` until the + //! flag is cleared, at which point normal processing resumes. + //! + //! The default value is `false`. + //! + //! \param[out] paused Whether uploads are paused. + //! + //! \return On success, returns `true`, otherwise returns `false` with an + //! error logged. + bool GetUploadsPaused(bool* paused); + //! \brief Retrieves the last time at which a report was attempted to be //! uploaded. //! @@ -172,6 +186,16 @@ class Settings final : public SettingsReader { //! error logged. bool SetUploadsEnabled(bool enabled); + //! \brief Sets whether uploads are paused. + //! + //! See GetUploadsPaused() for the semantics of this flag. + //! + //! \param[in] paused Whether uploads should be paused. + //! + //! \return On success, returns `true`, otherwise returns `false` with an + //! error logged. + bool SetUploadsPaused(bool paused); + //! \brief Sets the last time at which a report was attempted to be uploaded. //! //! This is only meant to be used internally by the CrashReportDatabase. diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index c4eeaf46f..271ae542b 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -122,6 +122,11 @@ void CrashReportUploadThread::ReportPendingSync(const UUID& report_uuid) { DoWork(nullptr); } +void CrashReportUploadThread::RetryPending() { + if (thread_.is_running()) + thread_.DoWorkNow(); +} + void CrashReportUploadThread::Start() { thread_.Start( options_.watch_pending_reports ? 0.0 : WorkerThread::kIndefiniteWait); @@ -140,6 +145,14 @@ void CrashReportUploadThread::ProcessPendingReports() { // uploads complete (regardless of whether or not that succeeded). ScopedFunctionInvoker scoped_function_invoker(callback_); + bool uploads_paused; + if (database_->GetSettings()->GetUploadsPaused(&uploads_paused) && + uploads_paused) { + // Leave known pending report UUIDs in the queue so they are retried once + // the pause is lifted, and skip scanning for new pending reports. + return; + } + std::vector known_report_uuids = known_pending_report_uuids_.Drain(); for (const UUID& report_uuid : known_report_uuids) { CrashReportDatabase::Report report; diff --git a/handler/crash_report_upload_thread.h b/handler/crash_report_upload_thread.h index 239103105..a12bb8946 100644 --- a/handler/crash_report_upload_thread.h +++ b/handler/crash_report_upload_thread.h @@ -114,6 +114,15 @@ class CrashReportUploadThread : public WorkerThread::Delegate, //! This method will run on the pool thread executing the OnCrashDumpEvent void ReportPendingSync(const UUID& report_uuid); + //! \brief Wakes the upload thread to immediately process pending reports. + //! + //! Unlike ReportPending(), no specific report UUID is enqueued; the next + //! pass relies on the thread's pending-report scan (requires + //! \a watch_pending_reports). + //! + //! This method may be called from any thread. + void RetryPending(); + // Stoppable: //! \brief Starts a dedicated upload thread, which executes ThreadMain(). diff --git a/handler/linux/crash_report_exception_handler.cc b/handler/linux/crash_report_exception_handler.cc index 13bc234ea..11f0f4717 100644 --- a/handler/linux/crash_report_exception_handler.cc +++ b/handler/linux/crash_report_exception_handler.cc @@ -236,6 +236,12 @@ void CrashReportExceptionHandler::RemoveAttachment( attachments_.erase(it); } +void CrashReportExceptionHandler::RequestRetry() { + if (upload_thread_) { + upload_thread_->RetryPending(); + } +} + bool CrashReportExceptionHandler::WriteMinidumpToDatabase( ProcessSnapshotLinux* process_snapshot, ProcessSnapshotSanitized* sanitized_snapshot, diff --git a/handler/linux/crash_report_exception_handler.h b/handler/linux/crash_report_exception_handler.h index d38055dc3..269e00c4c 100644 --- a/handler/linux/crash_report_exception_handler.h +++ b/handler/linux/crash_report_exception_handler.h @@ -100,6 +100,7 @@ class CrashReportExceptionHandler : public ExceptionHandlerServer::Delegate { void AddAttachment(const base::FilePath& attachment) override; void RemoveAttachment(const base::FilePath& attachment) override; + void RequestRetry() override; private: bool HandleExceptionWithConnection( diff --git a/handler/linux/exception_handler_server.cc b/handler/linux/exception_handler_server.cc index 69c3c319a..05e74342a 100644 --- a/handler/linux/exception_handler_server.cc +++ b/handler/linux/exception_handler_server.cc @@ -439,6 +439,10 @@ bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) { case ExceptionHandlerProtocol::ClientToServerMessage::kTypeRemoveAttachment: delegate_->RemoveAttachment(base::FilePath(message.attachment_info.path)); return true; + + case ExceptionHandlerProtocol::ClientToServerMessage::kTypeRequestRetry: + delegate_->RequestRetry(); + return true; } DCHECK(false); diff --git a/handler/linux/exception_handler_server.h b/handler/linux/exception_handler_server.h index df9749ef3..677d8a4f9 100644 --- a/handler/linux/exception_handler_server.h +++ b/handler/linux/exception_handler_server.h @@ -117,6 +117,10 @@ class ExceptionHandlerServer { //! \brief Called to remove an attachment from the crash report. virtual void RemoveAttachment(const base::FilePath& attachment) = 0; + //! \brief Called when the server has received a request to retry pending + //! report uploads. + virtual void RequestRetry() = 0; + virtual ~Delegate() {} }; diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc index 9a31a2ba6..9612ca4d5 100644 --- a/handler/mac/crash_report_exception_handler.cc +++ b/handler/mac/crash_report_exception_handler.cc @@ -308,4 +308,10 @@ kern_return_t CrashReportExceptionHandler::CatchMachException( return KERN_SUCCESS; } +void CrashReportExceptionHandler::RequestRetry() { + if (upload_thread_) { + upload_thread_->RetryPending(); + } +} + } // namespace crashpad diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h index 326393535..5893a0679 100644 --- a/handler/mac/crash_report_exception_handler.h +++ b/handler/mac/crash_report_exception_handler.h @@ -22,6 +22,7 @@ #include "client/crash_report_database.h" #include "handler/crash_report_upload_thread.h" +#include "handler/mac/exception_handler_server.h" #include "handler/user_stream_data_source.h" #include "util/misc/uuid.h" #include "util/mach/exc_server_variants.h" @@ -31,7 +32,7 @@ namespace crashpad { //! \brief An exception handler that writes crash reports for exception messages //! to a CrashReportDatabase. class CrashReportExceptionHandler final - : public UniversalMachExcServer::Interface { + : public ExceptionHandlerServer::Delegate { public: //! \brief Creates a new object that will store crash reports in \a database. //! @@ -91,7 +92,11 @@ class CrashReportExceptionHandler final const mach_msg_trailer_t* trailer, bool* destroy_complex_request) override; + // ExceptionHandlerServer::Delegate: + void RequestRetry() override; + private: + CrashReportDatabase* database_; // weak CrashReportUploadThread* upload_thread_; // weak const std::map* process_annotations_; // weak diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc index 5c1814c8c..b538c5388 100644 --- a/handler/mac/exception_handler_server.cc +++ b/handler/mac/exception_handler_server.cc @@ -14,12 +14,16 @@ #include "handler/mac/exception_handler_server.h" +#include + +#include #include #include "base/apple/mach_logging.h" #include "base/check.h" #include "base/logging.h" #include "util/mach/composite_mach_message_server.h" +#include "util/mach/exception_handler_protocol.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" @@ -29,26 +33,72 @@ namespace crashpad { namespace { +// Dispatches ClientToServerMessage Mach messages to the delegate by message +// type. +class ClientToServerMessageServer : public MachMessageServer::Interface { + public: + explicit ClientToServerMessageServer(ExceptionHandlerServer::Delegate* delegate) + : delegate_(delegate) {} + + ClientToServerMessageServer(const ClientToServerMessageServer&) = delete; + ClientToServerMessageServer& operator=(const ClientToServerMessageServer&) = + delete; + + bool MachMessageServerFunction(const mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) override { + const ClientToServerMessage* message = + ReceiveClientToServerMessage(in_header, out_header); + if (!message) { + return false; + } + *destroy_complex_request = true; + switch (message->type) { + case ClientToServerMessage::kRequestRetry: + delegate_->RequestRetry(); + break; + } + return true; + } + + std::set MachMessageServerRequestIDs() override { + return {kClientToServerMessageID}; + } + + mach_msg_size_t MachMessageServerRequestSize() override { + return sizeof(ClientToServerMessage); + } + + mach_msg_size_t MachMessageServerReplySize() override { + return sizeof(mig_reply_error_t); + } + + private: + ExceptionHandlerServer::Delegate* delegate_; // weak +}; + class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, public NotifyServer::DefaultInterface { public: - ExceptionHandlerServerRun( - mach_port_t exception_port, - mach_port_t notify_port, - bool launchd, - UniversalMachExcServer::Interface* exception_interface) + ExceptionHandlerServerRun(mach_port_t exception_port, + mach_port_t notify_port, + bool launchd, + ExceptionHandlerServer::Delegate* delegate) : UniversalMachExcServer::Interface(), NotifyServer::DefaultInterface(), mach_exc_server_(this), notify_server_(this), + client_to_server_message_server_(delegate), composite_mach_message_server_(), - exception_interface_(exception_interface), + delegate_(delegate), exception_port_(exception_port), notify_port_(notify_port), running_(true), launchd_(launchd) { composite_mach_message_server_.AddHandler(&mach_exc_server_); composite_mach_message_server_.AddHandler(¬ify_server_); + composite_mach_message_server_.AddHandler( + &client_to_server_message_server_); } ExceptionHandlerServerRun(const ExceptionHandlerServerRun&) = delete; @@ -142,20 +192,20 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, return KERN_FAILURE; } - return exception_interface_->CatchMachException(behavior, - exception_port, - thread, - task, - exception, - code, - code_count, - flavor, - old_state, - old_state_count, - new_state, - new_state_count, - trailer, - destroy_complex_request); + return delegate_->CatchMachException(behavior, + exception_port, + thread, + task, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + trailer, + destroy_complex_request); } // NotifyServer::DefaultInterface: @@ -182,8 +232,9 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, private: UniversalMachExcServer mach_exc_server_; NotifyServer notify_server_; + ClientToServerMessageServer client_to_server_message_server_; CompositeMachMessageServer composite_mach_message_server_; - UniversalMachExcServer::Interface* exception_interface_; // weak + ExceptionHandlerServer::Delegate* delegate_; // weak mach_port_t exception_port_; // weak mach_port_t notify_port_; // weak bool running_; @@ -205,10 +256,9 @@ ExceptionHandlerServer::ExceptionHandlerServer( ExceptionHandlerServer::~ExceptionHandlerServer() { } -void ExceptionHandlerServer::Run( - UniversalMachExcServer::Interface* exception_interface) { +void ExceptionHandlerServer::Run(Delegate* delegate) { ExceptionHandlerServerRun run( - receive_port_.get(), notify_port_.get(), launchd_, exception_interface); + receive_port_.get(), notify_port_.get(), launchd_, delegate); run.Run(); } diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h index 6d63a290d..c83d98f27 100644 --- a/handler/mac/exception_handler_server.h +++ b/handler/mac/exception_handler_server.h @@ -26,6 +26,17 @@ namespace crashpad { //! process. class ExceptionHandlerServer { public: + //! \brief Interface for an object that receives exceptions and + //! client-to-server control messages dispatched by the server. + class Delegate : public UniversalMachExcServer::Interface { + public: + ~Delegate() = default; + + //! \brief Called when the server has received a request to retry pending + //! report uploads. + virtual void RequestRetry() = 0; + }; + //! \brief Constructs an ExceptionHandlerServer object. //! //! \param[in] receive_port The port that exception messages and no-senders @@ -44,7 +55,8 @@ class ExceptionHandlerServer { //! \brief Runs the exception-handling server. //! - //! \param[in] exception_interface An object to send exception messages to. + //! \param[in] delegate An object to send exception messages and + //! client-to-server control messages to. //! //! This method monitors the receive port for exception messages and, if //! not being run by launchd, no-senders notifications. It continues running @@ -54,15 +66,13 @@ class ExceptionHandlerServer { //! queued by `mach_msg()` to be sent to a client) prior to calling this //! method, or it will detect that it is sender-less and return immediately. //! - //! All exception messages will be passed to \a exception_interface. - //! //! This method must only be called once on an ExceptionHandlerServer object. //! //! If an unexpected condition that prevents this method from functioning is //! encountered, it will log a message and terminate execution. Receipt of an //! invalid message on the receive port will cause a message to be logged, but //! this method will continue running normally. - void Run(UniversalMachExcServer::Interface* exception_interface); + void Run(Delegate* delegate); //! \brief Stops a running exception-handling server. //! diff --git a/handler/win/crash_report_exception_handler.cc b/handler/win/crash_report_exception_handler.cc index 9761542cf..8e06556b0 100644 --- a/handler/win/crash_report_exception_handler.cc +++ b/handler/win/crash_report_exception_handler.cc @@ -217,4 +217,10 @@ void CrashReportExceptionHandler::ExceptionHandlerServerAttachmentRemoved( attachments_.erase(it); } +void CrashReportExceptionHandler::ExceptionHandlerServerRetryRequested() { + if (upload_thread_) { + upload_thread_->RetryPending(); + } +} + } // namespace crashpad diff --git a/handler/win/crash_report_exception_handler.h b/handler/win/crash_report_exception_handler.h index 57f50eff3..9fe36b00d 100644 --- a/handler/win/crash_report_exception_handler.h +++ b/handler/win/crash_report_exception_handler.h @@ -87,6 +87,7 @@ class CrashReportExceptionHandler final const base::FilePath& attachment) override; void ExceptionHandlerServerAttachmentRemoved( const base::FilePath& attachment) override; + void ExceptionHandlerServerRetryRequested() override; private: CrashReportDatabase* database_; // weak diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt index 3ab946aaa..7d6db7ce0 100644 --- a/util/CMakeLists.txt +++ b/util/CMakeLists.txt @@ -155,6 +155,8 @@ if(APPLE) mach/exc_server_variants.h mach/exception_behaviors.cc mach/exception_behaviors.h + mach/exception_handler_protocol.cc + mach/exception_handler_protocol.h mach/exception_ports.cc mach/exception_ports.h mach/mach_extensions.cc diff --git a/util/linux/exception_handler_client.cc b/util/linux/exception_handler_client.cc index 60d72257d..306a932e3 100644 --- a/util/linux/exception_handler_client.cc +++ b/util/linux/exception_handler_client.cc @@ -244,4 +244,11 @@ void ExceptionHandlerClient::RemoveAttachment( UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)); } +void ExceptionHandlerClient::RequestRetry() { + ExceptionHandlerProtocol::ClientToServerMessage message; + message.type = + ExceptionHandlerProtocol::ClientToServerMessage::kTypeRequestRetry; + UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)); +} + } // namespace crashpad diff --git a/util/linux/exception_handler_client.h b/util/linux/exception_handler_client.h index 30c706a0c..a7e4c66df 100644 --- a/util/linux/exception_handler_client.h +++ b/util/linux/exception_handler_client.h @@ -74,6 +74,9 @@ class ExceptionHandlerClient { //! \brief Removes an attachment from the crash report. void RemoveAttachment(const base::FilePath& attachment); + //! \brief Requests that the handler retry pending report uploads. + void RequestRetry(); + private: int SendCrashDumpRequest( const ExceptionHandlerProtocol::ClientInformation& info, diff --git a/util/linux/exception_handler_protocol.h b/util/linux/exception_handler_protocol.h index 07773a001..0b6a642f0 100644 --- a/util/linux/exception_handler_protocol.h +++ b/util/linux/exception_handler_protocol.h @@ -93,6 +93,9 @@ class ExceptionHandlerProtocol { //! \brief Request that the server remove an attachment. kTypeRemoveAttachment, + + //! \brief Request that the server retry pending report uploads. + kTypeRequestRetry, }; Type type; diff --git a/util/mach/exception_handler_protocol.cc b/util/mach/exception_handler_protocol.cc new file mode 100644 index 000000000..e9cc6c49d --- /dev/null +++ b/util/mach/exception_handler_protocol.cc @@ -0,0 +1,79 @@ +// Copyright 2025 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/mach/exception_handler_protocol.h" + +#include + +#include "base/apple/mach_logging.h" +#include "base/logging.h" +#include "util/mach/mach_message.h" + +namespace crashpad { + +bool SendClientToServerMessage(mach_port_t port, + ClientToServerMessage::Type type, + const std::string& payload) { + if (port == MACH_PORT_NULL) { + DLOG(ERROR) << "cannot send client-to-server message: invalid port"; + return false; + } + + ClientToServerMessage message = {}; + message.header.msgh_bits = + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX; + message.header.msgh_size = sizeof(message); + message.header.msgh_remote_port = port; + message.header.msgh_local_port = MACH_PORT_NULL; + message.header.msgh_id = kClientToServerMessageID; + + message.body.msgh_descriptor_count = 1; + + message.payload.address = const_cast(payload.c_str()); + message.payload.size = static_cast(payload.size() + 1); + message.payload.deallocate = FALSE; + message.payload.copy = MACH_MSG_VIRTUAL_COPY; + message.payload.type = MACH_MSG_OOL_DESCRIPTOR; + + message.ndr = NDR_record; + message.type = type; + + kern_return_t kr = mach_msg(&message.header, + MACH_SEND_MSG, + sizeof(message), + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_msg"; + return false; + } + + return true; +} + +const ClientToServerMessage* ReceiveClientToServerMessage( + const mach_msg_header_t* in_header, + mach_msg_header_t* out_header) { + if (in_header->msgh_id != kClientToServerMessageID) { + return nullptr; + } + + PrepareMIGReplyFromRequest(in_header, out_header); + SetMIGReplyError(out_header, MIG_NO_REPLY); + return reinterpret_cast(in_header); +} + +} // namespace crashpad diff --git a/util/mach/exception_handler_protocol.h b/util/mach/exception_handler_protocol.h new file mode 100644 index 000000000..145f1550a --- /dev/null +++ b/util/mach/exception_handler_protocol.h @@ -0,0 +1,80 @@ +// Copyright 2025 The Crashpad Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_MACH_EXCEPTION_HANDLER_PROTOCOL_H_ +#define CRASHPAD_UTIL_MACH_EXCEPTION_HANDLER_PROTOCOL_H_ + +#include +#include + +#include + +namespace crashpad { + +//! \brief Mach message ID used by client-to-server control messages sent to +//! the crashpad handler's exception port. +//! +//! FourCC `'CPad'`, chosen far from Apple's Mach MIG subsystem bases (which +//! cluster between 64 and ~6000) so it cannot collide with a kernel-originated +//! message delivered to the handler's exception port. +constexpr mach_msg_id_t kClientToServerMessageID = 0x43506164; // 'CPad' + +//! \brief A control message sent by a Crashpad client to the handler. +//! +//! The message carries a typed command and an optional out-of-line payload. +//! The payload is always transferred as an OOL descriptor even when empty, +//! so the struct layout is uniform for all message types. +struct ClientToServerMessage { + //! \brief Command types understood by the handler. + enum Type : uint32_t { + //! \brief Ask the handler to retry pending report uploads. Carries no + //! meaningful payload. + kRequestRetry = 1, + }; + + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_ool_descriptor_t payload; + NDR_record_t ndr; + Type type; +}; + +//! \brief Sends a ClientToServerMessage to the handler. +//! +//! \param[in] port The handler's exception port. +//! \param[in] type The command type. +//! \param[in] payload An optional string payload. The null terminator is +//! included in the transferred OOL region, so `payload.size` is always +//! at least 1. +//! +//! \return `true` on success, `false` otherwise (logs the error). +bool SendClientToServerMessage(mach_port_t port, + ClientToServerMessage::Type type, + const std::string& payload = ""); + +//! \brief Validates and unwraps a received ClientToServerMessage. +//! +//! \param[in] in_header The incoming Mach message header. +//! \param[out] out_header The reply header to populate. +//! +//! \return A pointer to the received ClientToServerMessage on success (aliases +//! \a in_header). `nullptr` if the message is not a ClientToServerMessage +//! or is malformed. +const ClientToServerMessage* ReceiveClientToServerMessage( + const mach_msg_header_t* in_header, + mach_msg_header_t* out_header); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_EXCEPTION_HANDLER_PROTOCOL_H_ diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc index 8b2e65acc..554cfd768 100644 --- a/util/win/exception_handler_server.cc +++ b/util/win/exception_handler_server.cc @@ -554,6 +554,14 @@ bool ExceptionHandlerServer::ServiceClientConnection( return false; } + case ClientToServerMessage::kRequestRetry: { + ServerToClientMessage response = {}; + service_context.delegate()->ExceptionHandlerServerRetryRequested(); + LoggingWriteFile( + service_context.pipe(), &response, sizeof(response)); + return false; + } + default: LOG(ERROR) << "unhandled message type: " << message.type; return false; diff --git a/util/win/exception_handler_server.h b/util/win/exception_handler_server.h index df1ce87d1..d693cc3c7 100644 --- a/util/win/exception_handler_server.h +++ b/util/win/exception_handler_server.h @@ -72,6 +72,10 @@ class ExceptionHandlerServer { virtual void ExceptionHandlerServerAttachmentRemoved( const base::FilePath& attachment) = 0; + //! \brief Called when the server has received a request to retry pending + //! report uploads. + virtual void ExceptionHandlerServerRetryRequested() = 0; + protected: ~Delegate(); }; diff --git a/util/win/registration_protocol_win_structs.h b/util/win/registration_protocol_win_structs.h index dd6a894fa..61f95f7b4 100644 --- a/util/win/registration_protocol_win_structs.h +++ b/util/win/registration_protocol_win_structs.h @@ -163,6 +163,10 @@ struct ClientToServerMessage { //! \brief For AttachmentRequestV2 (variable-length paths). kRemoveAttachmentV2, + + //! \brief Requests that the server retry pending report uploads. No + //! additional payload. + kRequestRetry, } type; union {