client: Add a tvOS version of CrashHandler, add tvOS support to tests
tvOS as a platform is very similar to iOS, but one of significant
difference is that Mach message passing (including mach_msg()) is not
allowed. This means that it is completely impossible to handle Mach
exceptions on tvOS.
Consequently, we need a CrashHandler implementation that is entirely
based on the POSIX signals API. Breakpad has had such an implementation
for years [1], so some of the code was based on Breakpad's.
[1] https://source.chromium.org/chromium/chromium/src/+/main:third_party/breakpad/breakpad/src/client/ios/exception_handler_no_mach.cc
In general terms:
- GN: Most of the code in util/mach/ is not built on tvOS, and the mig
calls are skipped altogether. The only allowed files at the moment are
util/mach/mach_extensions.{cc,h}, which contain some constants and
functions that are useful and allowed outside the Mach exception
context.
- CrashHandler: the code was split into multiple files for clarity. The
user-visible API (i.e. the CrashpadClient calls) remains in
crashpad_client_ios.cc, while the internal CrashHandler class was
split into multiple files and classes.
Namely, the common code handling NSExceptions and communication with
InProcessHandler is part of CrashHandlerBase and the iOS and
tvOS-specific implementations are in crash_handler_{ios,tvos}.{cc,h}.
The tvOS CrashHandler code is basically a simplified version of the
iOS one: we watch a lot of signals in Initialize() and call
DumpExceptionFromSignal() when we get one (after which we call
Signals::RestoreHandlerAndReraiseSignalOnReturn() and exit).
- Tests:
- Tests that checked for specific Mach exceptions needed a new
tvOS-specific code path. We need to check for specific signal values
where possible instead.
- testRecursion() had to be skipped altogether. We cannot monitor Mach
exceptions, and sigaltstack() is also forbidden on tvOS. This means
there is no reliable way of monitoring a stack overflow as explained
in the comment in code.
Bug: 405140658, 351887716
Change-Id: I2f13a5788d8e5ac085b49716c43bb375d017ad35
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/6421038
Reviewed-by: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Raphael Kubo da Costa <kubo@igalia.com>
diff --git a/client/BUILD.gn b/client/BUILD.gn
index 78c3ded..a3ccc71 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -32,6 +32,8 @@
if (crashpad_is_ios) {
sources += [
+ "crash_handler_base_ios.cc",
+ "crash_handler_base_ios.h",
"crashpad_client_ios.cc",
"ios_handler/exception_processor.h",
"ios_handler/exception_processor.mm",
@@ -44,6 +46,18 @@
"simulate_crash_ios.h",
"upload_behavior_ios.h",
]
+
+ if (!crashpad_is_tvos) {
+ sources += [
+ "crash_handler_ios.cc",
+ "crash_handler_ios.h",
+ ]
+ } else {
+ sources += [
+ "crash_handler_tvos.cc",
+ "crash_handler_tvos.h",
+ ]
+ }
}
if (crashpad_is_linux || crashpad_is_android) {
diff --git a/client/crash_handler_base_ios.cc b/client/crash_handler_base_ios.cc
new file mode 100644
index 0000000..6b349ca
--- /dev/null
+++ b/client/crash_handler_base_ios.cc
@@ -0,0 +1,127 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "client/crash_handler_base_ios.h"
+
+#include "base/logging.h"
+#include "util/posix/signals.h"
+
+namespace crashpad {
+
+CrashHandlerBase::CrashHandlerBase() = default;
+CrashHandlerBase::~CrashHandlerBase() = default;
+
+bool CrashHandlerBase::Initialize(
+ const base::FilePath& database,
+ const std::string& url,
+ const std::map<std::string, std::string>& annotations,
+ internal::InProcessHandler::ProcessPendingReportsObservationCallback
+ callback) {
+ INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
+ if (!in_process_handler().Initialize(database, url, annotations, callback)) {
+ LOG(ERROR) << "Unable to initialize Crashpad.";
+ return false;
+ }
+ if (!DoInitialize()) {
+ return false;
+ }
+ INITIALIZATION_STATE_SET_VALID(initialized_);
+ return true;
+}
+
+void CrashHandlerBase::ProcessIntermediateDumps(
+ const std::map<std::string, std::string>& annotations,
+ const UserStreamDataSources* user_stream_sources) {
+ in_process_handler_.ProcessIntermediateDumps(annotations,
+ user_stream_sources);
+}
+
+void CrashHandlerBase::ProcessIntermediateDump(
+ const base::FilePath& file,
+ const std::map<std::string, std::string>& annotations) {
+ in_process_handler_.ProcessIntermediateDump(file, annotations);
+}
+
+void CrashHandlerBase::DumpWithoutCrash(NativeCPUContext* context,
+ bool process_dump) {
+ INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+ base::FilePath path;
+ if (!in_process_handler_.DumpExceptionFromSimulatedMachException(
+ context, kMachExceptionSimulated, &path)) {
+ return;
+ }
+
+ if (process_dump) {
+ in_process_handler_.ProcessIntermediateDump(path);
+ }
+}
+
+void CrashHandlerBase::DumpWithoutCrashAtPath(NativeCPUContext* context,
+ const base::FilePath& path) {
+ in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath(
+ context, kMachExceptionSimulated, path);
+}
+
+void CrashHandlerBase::StartProcessingPendingReports(
+ UploadBehavior upload_behavior) {
+ INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+ in_process_handler_.StartProcessingPendingReports(upload_behavior);
+}
+
+void CrashHandlerBase::SetExceptionCallbackForTesting(void (*callback)()) {
+ in_process_handler_.SetExceptionCallbackForTesting(callback);
+}
+
+void CrashHandlerBase::HandleAndReraiseSignal(int signo,
+ siginfo_t* siginfo,
+ ucontext_t* context,
+ struct sigaction* old_action) {
+ in_process_handler_.DumpExceptionFromSignal(siginfo, context);
+
+ // Always call system handler.
+ Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action);
+}
+
+void CrashHandlerBase::HandleUncaughtNSException(const uint64_t* frames,
+ const size_t num_frames) {
+ in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames,
+ num_frames);
+ // After uncaught exceptions are reported, the system immediately triggers a
+ // call to std::terminate()/abort(). Remove the abort handler so a second
+ // dump isn't generated.
+ CHECK(Signals::InstallDefaultHandler(SIGABRT));
+}
+
+void CrashHandlerBase::HandleUncaughtNSExceptionWithContext(
+ NativeCPUContext* context) {
+ base::FilePath path;
+ in_process_handler_.DumpExceptionFromSimulatedMachException(
+ context, kMachExceptionFromNSException, &path);
+
+ // After uncaught exceptions are reported, the system immediately triggers a
+ // call to std::terminate()/abort(). Remove the abort handler so a second
+ // dump isn't generated.
+ CHECK(Signals::InstallDefaultHandler(SIGABRT));
+}
+
+void CrashHandlerBase::HandleUncaughtNSExceptionWithContextAtPath(
+ NativeCPUContext* context,
+ const base::FilePath& path) {
+ in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath(
+ context, kMachExceptionFromNSException, path);
+}
+
+bool CrashHandlerBase::MoveIntermediateDumpAtPathToPending(
+ const base::FilePath& path) {
+ if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) {
+ // After uncaught exceptions are reported, the system immediately triggers
+ // a call to std::terminate()/abort(). Remove the abort handler so a
+ // second dump isn't generated.
+ CHECK(Signals::InstallDefaultHandler(SIGABRT));
+ return true;
+ }
+ return false;
+}
+
+} // namespace crashpad
diff --git a/client/crash_handler_base_ios.h b/client/crash_handler_base_ios.h
new file mode 100644
index 0000000..0aea192
--- /dev/null
+++ b/client/crash_handler_base_ios.h
@@ -0,0 +1,89 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_
+#define CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_
+
+#include <stdint.h>
+#include <sys/signal.h>
+
+#include <map>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "client/ios_handler/exception_processor.h"
+#include "client/ios_handler/in_process_handler.h"
+#include "client/upload_behavior_ios.h"
+#include "util/misc/capture_context.h"
+#include "util/misc/initialization_state_dcheck.h"
+
+namespace crashpad {
+
+// Base class shared by the iOS and tvOS CrashHandler implementations.
+class CrashHandlerBase : public ObjcExceptionDelegate {
+ public:
+ CrashHandlerBase(const CrashHandlerBase&) = delete;
+ CrashHandlerBase& operator=(const CrashHandlerBase&) = delete;
+
+ bool Initialize(
+ const base::FilePath& database,
+ const std::string& url,
+ const std::map<std::string, std::string>& annotations,
+ internal::InProcessHandler::ProcessPendingReportsObservationCallback
+ callback);
+
+ void ProcessIntermediateDumps(
+ const std::map<std::string, std::string>& annotations,
+ const UserStreamDataSources* user_stream_sources);
+
+ void ProcessIntermediateDump(
+ const base::FilePath& file,
+ const std::map<std::string, std::string>& annotations);
+
+ void DumpWithoutCrash(NativeCPUContext* context, bool process_dump);
+
+ void DumpWithoutCrashAtPath(NativeCPUContext* context,
+ const base::FilePath& path);
+
+ void StartProcessingPendingReports(UploadBehavior upload_behavior);
+
+ void SetExceptionCallbackForTesting(void (*callback)());
+
+ protected:
+ CrashHandlerBase();
+ virtual ~CrashHandlerBase();
+
+ // Subclasses are expected to install signal handlers and set up Mach ports in
+ // this function.
+ virtual bool DoInitialize() = 0;
+
+ void HandleAndReraiseSignal(int signo,
+ siginfo_t* siginfo,
+ ucontext_t* context,
+ struct sigaction* old_action);
+
+ internal::InProcessHandler& in_process_handler() {
+ return in_process_handler_;
+ }
+
+ private:
+ // ObjcExceptionDelegate overrides:
+ void HandleUncaughtNSException(const uint64_t* frames,
+ const size_t num_frames) override;
+
+ void HandleUncaughtNSExceptionWithContext(NativeCPUContext* context) override;
+
+ void HandleUncaughtNSExceptionWithContextAtPath(
+ NativeCPUContext* context,
+ const base::FilePath& path) override;
+
+ bool MoveIntermediateDumpAtPathToPending(const base::FilePath& path) override;
+
+ internal::InProcessHandler in_process_handler_;
+ InitializationStateDcheck initialized_;
+};
+
+} // namespace crashpad
+
+#endif // CRASHPAD_CLIENT_CRASH_HANDLER_BASE_IOS_H_
diff --git a/client/crash_handler_ios.cc b/client/crash_handler_ios.cc
new file mode 100644
index 0000000..f149ae7
--- /dev/null
+++ b/client/crash_handler_ios.cc
@@ -0,0 +1,307 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "client/crash_handler_ios.h"
+
+#include <sys/sysctl.h>
+#include <unistd.h>
+
+#include <iterator>
+
+#include "base/apple/mach_logging.h"
+#include "util/ios/raw_logging.h"
+#include "util/mach/mach_extensions.h"
+#include "util/mach/mach_message.h"
+#include "util/mach/mach_message_server.h"
+#include "util/posix/signals.h"
+
+namespace {
+
+bool IsBeingDebugged() {
+ int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
+ size_t len = sizeof(kinfo_proc);
+ kinfo_proc kern_proc_info;
+ if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) {
+ return kern_proc_info.kp_proc.p_flag & P_TRACED;
+ }
+ return false;
+}
+
+} // namespace
+
+namespace crashpad {
+
+CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::
+ ThreadSafeScopedMachPortWithReceiveRight()
+ : port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {}
+
+CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::
+ ~ThreadSafeScopedMachPortWithReceiveRight() {
+ reset();
+}
+
+mach_port_t CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::get() {
+ return port_.load();
+}
+void CrashHandler::ThreadSafeScopedMachPortWithReceiveRight::reset() {
+ mach_port_t old_port = port_.exchange(MACH_PORT_NULL);
+ if (old_port == MACH_PORT_NULL) {
+ // Already reset, nothing to do.
+ return;
+ }
+ kern_return_t kr = mach_port_mod_refs(
+ mach_task_self(), old_port, MACH_PORT_RIGHT_RECEIVE, -1);
+ MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
+ << "ThreadSafeScopedMachPortWithReceiveRight mach_port_mod_refs";
+ kr = mach_port_deallocate(mach_task_self(), old_port);
+ MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
+ << "ThreadSafeScopedMachPortWithReceiveRight mach_port_deallocate";
+}
+
+CrashHandler* CrashHandler::instance_ = nullptr;
+
+CrashHandler::CrashHandler() = default;
+
+CrashHandler::~CrashHandler() {
+ UninstallObjcExceptionPreprocessor();
+ // Reset the SIGPIPE handler only if the current handler is the
+ // one installed by DoInitialize(). In other words, if an
+ // application has set its own SIGPIPE handler after initializing
+ // Crashpad, there is no need to change the signal handler here.
+ struct sigaction sa;
+ if (sigaction(SIGPIPE, nullptr, &sa) == 0 &&
+ sa.sa_sigaction == CatchAndReraiseSignalDefaultAction) {
+ Signals::InstallDefaultHandler(SIGPIPE);
+ }
+ Signals::InstallDefaultHandler(SIGABRT);
+ UninstallMachExceptionHandler();
+}
+
+// static
+CrashHandler* CrashHandler::Get() {
+ if (!instance_)
+ instance_ = new CrashHandler();
+ return instance_;
+}
+
+// static
+void CrashHandler::ResetForTesting() {
+ delete instance_;
+ instance_ = nullptr;
+}
+
+bool CrashHandler::DoInitialize() {
+ if (!InstallMachExceptionHandler() ||
+ // xnu turns hardware faults into Mach exceptions, so the only signal
+ // left to register is SIGABRT, which never starts off as a hardware
+ // fault. Installing a handler for other signals would lead to
+ // recording exceptions twice. As a consequence, Crashpad will not
+ // generate intermediate dumps for anything manually calling
+ // raise(SIG*). In practice, this doesn’t actually happen for crash
+ // signals that originate as hardware faults.
+ !Signals::InstallHandler(
+ SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) {
+ return false;
+ }
+
+ // For applications that haven't ignored or set a handler for SIGPIPE:
+ // It’s OK for an application to set its own SIGPIPE handler (including
+ // SIG_IGN) before initializing Crashpad, because Crashpad will discover the
+ // existing handler and not install its own.
+ // It’s OK for Crashpad to install its own SIGPIPE handler and for the
+ // application to subsequently install its own (including SIG_IGN)
+ // afterwards, because its handler will replace Crashpad’s.
+ // This is useful to cover the default situation where nobody installs a
+ // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a
+ // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without
+ // Crashpad, SIGPIPE results in a silent and unreported kill (and not even
+ // ReportCrash will record it), but developers probably want to be alerted to
+ // the condition.
+ struct sigaction sa;
+ if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) {
+ Signals::InstallHandler(
+ SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr);
+ }
+
+ InstallObjcExceptionPreprocessor(this);
+ return true;
+}
+
+bool CrashHandler::InstallMachExceptionHandler() {
+ mach_port_t exception_port = exception_port_.get();
+ if (exception_port == MACH_PORT_NULL) {
+ return false;
+ }
+
+ kern_return_t kr = mach_port_insert_right(mach_task_self(),
+ exception_port,
+ exception_port,
+ MACH_MSG_TYPE_MAKE_SEND);
+ if (kr != KERN_SUCCESS) {
+ MACH_LOG(ERROR, kr) << "mach_port_insert_right";
+ return false;
+ }
+
+ // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
+ // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger.
+ exception_mask_t mask =
+ ExcMaskAll() &
+ ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT |
+ EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0));
+
+ ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
+ if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) ||
+ !exception_ports.SetExceptionPort(
+ mask,
+ exception_port,
+ EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
+ MACHINE_THREAD_STATE)) {
+ return false;
+ }
+
+ mach_handler_running_ = true;
+ Start();
+ return true;
+}
+
+void CrashHandler::UninstallMachExceptionHandler() {
+ mach_handler_running_ = false;
+ exception_port_.reset();
+ Join();
+}
+
+// Thread:
+
+void CrashHandler::ThreadMain() {
+ UniversalMachExcServer universal_mach_exc_server(this);
+ while (mach_handler_running_) {
+ mach_msg_return_t mr =
+ MachMessageServer::Run(&universal_mach_exc_server,
+ exception_port_.get(),
+ MACH_MSG_OPTION_NONE,
+ MachMessageServer::kPersistent,
+ MachMessageServer::kReceiveLargeIgnore,
+ kMachMessageTimeoutWaitIndefinitely);
+ MACH_CHECK(
+ mach_handler_running_
+ ? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for
+ // exception messages that come
+ // from the kernel itself, but if
+ // something else in-process sends
+ // exception messages and breaks,
+ // handle that case.
+ : (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the
+ // thread was listening.
+ mr == MACH_RCV_INVALID_NAME), // Port was closed before the
+ // thread started listening.
+ mr)
+ << "MachMessageServer::Run";
+ }
+}
+
+// UniversalMachExcServer::Interface:
+
+kern_return_t CrashHandler::CatchMachException(
+ exception_behavior_t behavior,
+ exception_handler_t exception_port,
+ thread_t thread,
+ task_t task,
+ exception_type_t exception,
+ const mach_exception_data_type_t* code,
+ mach_msg_type_number_t code_count,
+ thread_state_flavor_t* flavor,
+ ConstThreadState old_state,
+ mach_msg_type_number_t old_state_count,
+ thread_state_t new_state,
+ mach_msg_type_number_t* new_state_count,
+ const mach_msg_trailer_t* trailer,
+ bool* destroy_complex_request) {
+ *destroy_complex_request = true;
+
+ // TODO(justincohen): Forward exceptions to original_handlers_ with
+ // UniversalExceptionRaise.
+
+ // iOS shouldn't have any child processes, but just in case, those will
+ // inherit the task exception ports, and this process isn’t prepared to
+ // handle them
+ if (task != mach_task_self()) {
+ CRASHPAD_RAW_LOG("MachException task != mach_task_self()");
+ return KERN_FAILURE;
+ }
+
+ HandleMachException(behavior,
+ thread,
+ exception,
+ code,
+ code_count,
+ *flavor,
+ old_state,
+ old_state_count);
+
+ // Respond with KERN_FAILURE so the system will continue to handle this
+ // exception. xnu will turn this Mach exception into a signal and take the
+ // default action to terminate the process. However, if sigprocmask is
+ // called before this Mach exception returns (such as by another thread
+ // calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach
+ // exception will be converted into a signal but delivery will be blocked.
+ // Since concurrent exceptions lead to the losing thread sleeping
+ // indefinitely, if the abort thread never returns, the thread that
+ // triggered this Mach exception will repeatedly trap and the process will
+ // never terminate. If the abort thread didn’t have a user-space signal
+ // handler that slept forever, the abort would terminate the process even if
+ // all other signals had been blocked. Instead, unblock all signals
+ // corresponding to all Mach exceptions Crashpad is registered for before
+ // returning KERN_FAILURE. There is still racy behavior possible with this
+ // call to sigprocmask, but the repeated calls to CatchMachException here
+ // will eventually lead to termination.
+ sigset_t unblock_set;
+ sigemptyset(&unblock_set);
+ sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION
+ sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT
+ sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC
+ sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS
+ sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS
+ if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) {
+ CRASHPAD_RAW_LOG("sigprocmask");
+ }
+ return KERN_FAILURE;
+}
+
+void CrashHandler::HandleMachException(exception_behavior_t behavior,
+ thread_t thread,
+ exception_type_t exception,
+ const mach_exception_data_type_t* code,
+ mach_msg_type_number_t code_count,
+ thread_state_flavor_t flavor,
+ ConstThreadState old_state,
+ mach_msg_type_number_t old_state_count) {
+ in_process_handler().DumpExceptionFromMachException(behavior,
+ thread,
+ exception,
+ code,
+ code_count,
+ flavor,
+ old_state,
+ old_state_count);
+}
+
+// static
+void CrashHandler::CatchAndReraiseSignal(int signo,
+ siginfo_t* siginfo,
+ void* context) {
+ Get()->HandleAndReraiseSignal(signo,
+ siginfo,
+ reinterpret_cast<ucontext_t*>(context),
+ &(Get()->old_action_));
+}
+
+// static
+void CrashHandler::CatchAndReraiseSignalDefaultAction(int signo,
+ siginfo_t* siginfo,
+ void* context) {
+ Get()->HandleAndReraiseSignal(
+ signo, siginfo, reinterpret_cast<ucontext_t*>(context), nullptr);
+}
+
+} // namespace crashpad
diff --git a/client/crash_handler_ios.h b/client/crash_handler_ios.h
new file mode 100644
index 0000000..35e1753
--- /dev/null
+++ b/client/crash_handler_ios.h
@@ -0,0 +1,109 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_
+#define CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_
+
+#include <atomic>
+
+#include "client/crash_handler_base_ios.h"
+#include "util/mach/exc_server_variants.h"
+#include "util/mach/exception_ports.h"
+#include "util/thread/thread.h"
+
+namespace crashpad {
+
+// A base class for signal handler and Mach exception server.
+class CrashHandler : public Thread,
+ public UniversalMachExcServer::Interface,
+ public CrashHandlerBase {
+ public:
+ CrashHandler(const CrashHandler&) = delete;
+ CrashHandler& operator=(const CrashHandler&) = delete;
+
+ static CrashHandler* Get();
+
+ static void ResetForTesting();
+
+ private:
+ // Thread-safe version of `base::apple::ScopedMachReceiveRight` which
+ // allocates the Mach port upon construction and deallocates it upon
+ // destruction.
+ class ThreadSafeScopedMachPortWithReceiveRight {
+ public:
+ ThreadSafeScopedMachPortWithReceiveRight();
+
+ ThreadSafeScopedMachPortWithReceiveRight(
+ const ThreadSafeScopedMachPortWithReceiveRight&) = delete;
+ ThreadSafeScopedMachPortWithReceiveRight& operator=(
+ const ThreadSafeScopedMachPortWithReceiveRight&) = delete;
+
+ ~ThreadSafeScopedMachPortWithReceiveRight();
+
+ mach_port_t get();
+ void reset();
+
+ private:
+ std::atomic<mach_port_t> port_;
+ };
+
+ CrashHandler();
+
+ ~CrashHandler() override;
+
+ bool DoInitialize() override;
+
+ bool InstallMachExceptionHandler();
+
+ void UninstallMachExceptionHandler();
+
+ // Thread:
+
+ void ThreadMain() override;
+
+ // UniversalMachExcServer::Interface:
+
+ kern_return_t CatchMachException(exception_behavior_t behavior,
+ exception_handler_t exception_port,
+ thread_t thread,
+ task_t task,
+ exception_type_t exception,
+ const mach_exception_data_type_t* code,
+ mach_msg_type_number_t code_count,
+ thread_state_flavor_t* flavor,
+ ConstThreadState old_state,
+ mach_msg_type_number_t old_state_count,
+ thread_state_t new_state,
+ mach_msg_type_number_t* new_state_count,
+ const mach_msg_trailer_t* trailer,
+ bool* destroy_complex_request) override;
+
+ void HandleMachException(exception_behavior_t behavior,
+ thread_t thread,
+ exception_type_t exception,
+ const mach_exception_data_type_t* code,
+ mach_msg_type_number_t code_count,
+ thread_state_flavor_t flavor,
+ ConstThreadState old_state,
+ mach_msg_type_number_t old_state_count);
+
+ // The signal handler installed at OS-level.
+ static void CatchAndReraiseSignal(int signo,
+ siginfo_t* siginfo,
+ void* context);
+
+ static void CatchAndReraiseSignalDefaultAction(int signo,
+ siginfo_t* siginfo,
+ void* context);
+
+ ThreadSafeScopedMachPortWithReceiveRight exception_port_;
+ ExceptionPorts::ExceptionHandlerVector original_handlers_;
+ struct sigaction old_action_ = {};
+ static CrashHandler* instance_;
+ std::atomic<bool> mach_handler_running_ = false;
+};
+
+} // namespace crashpad
+
+#endif // CRASHPAD_CLIENT_CRASH_HANDLER_IOS_H_
diff --git a/client/crash_handler_tvos.cc b/client/crash_handler_tvos.cc
new file mode 100644
index 0000000..3b92fab
--- /dev/null
+++ b/client/crash_handler_tvos.cc
@@ -0,0 +1,97 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "client/crash_handler_tvos.h"
+
+#include <sys/signal.h>
+
+#include "util/posix/signals.h"
+#include "util/thread/thread.h"
+
+namespace crashpad {
+
+CrashHandler* CrashHandler::instance_ = nullptr;
+
+CrashHandler::CrashHandler() = default;
+
+CrashHandler::~CrashHandler() {
+ UninstallObjcExceptionPreprocessor();
+ for (int signo = 1; signo < NSIG; ++signo) {
+ if (!Signals::IsCrashSignal(signo)) {
+ Signals::RestoreOrResetHandler(signo,
+ old_actions_.ActionForSignal(signo));
+ } else if (signo == SIGPIPE) {
+ // Reset the SIGPIPE handler only if the current handler is the one
+ // installed by DoInitialize(). In other words, if an application has set
+ // its own SIGPIPE handler after initializing Crashpad, there is no need
+ // to change the signal handler here.
+ struct sigaction sa;
+ if (sigaction(SIGPIPE, nullptr, &sa) == 0 &&
+ sa.sa_sigaction == CatchAndReraiseSignal) {
+ Signals::InstallDefaultHandler(SIGPIPE);
+ }
+ }
+ }
+}
+
+// static
+CrashHandler* CrashHandler::Get() {
+ if (!instance_) {
+ instance_ = new CrashHandler;
+ }
+ return instance_;
+}
+
+// static
+void CrashHandler::ResetForTesting() {
+ delete instance_;
+ instance_ = nullptr;
+}
+
+uint64_t CrashHandler::GetThreadIdForTesting() {
+ return Thread::GetThreadIdForTesting();
+}
+
+bool CrashHandler::DoInitialize() {
+ if (!Signals::InstallCrashHandlers(CatchAndReraiseSignal,
+ /*flags=*/0,
+ &old_actions_)) {
+ LOG(ERROR) << "Unable to install crash handlers";
+ return false;
+ }
+
+ // For applications that haven't ignored or set a handler for SIGPIPE:
+ // It’s OK for an application to set its own SIGPIPE handler (including
+ // SIG_IGN) before initializing Crashpad, because Crashpad will discover the
+ // existing handler and not install its own.
+ // It’s OK for Crashpad to install its own SIGPIPE handler and for the
+ // application to subsequently install its own (including SIG_IGN)
+ // afterwards, because its handler will replace Crashpad’s.
+ // This is useful to cover the default situation where nobody installs a
+ // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a
+ // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without
+ // Crashpad, SIGPIPE results in a silent and unreported kill (and not even
+ // ReportCrash will record it), but developers probably want to be alerted to
+ // the condition.
+ struct sigaction sa;
+ if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) {
+ Signals::InstallHandler(SIGPIPE, CatchAndReraiseSignal, 0, nullptr);
+ }
+
+ InstallObjcExceptionPreprocessor(this);
+ return true;
+}
+
+// static
+void CrashHandler::CatchAndReraiseSignal(int signo,
+ siginfo_t* siginfo,
+ void* context) {
+ CrashHandler* self = Get();
+ self->HandleAndReraiseSignal(signo,
+ siginfo,
+ reinterpret_cast<ucontext_t*>(context),
+ self->old_actions_.ActionForSignal(signo));
+}
+
+} // namespace crashpad
diff --git a/client/crash_handler_tvos.h b/client/crash_handler_tvos.h
new file mode 100644
index 0000000..61e2718
--- /dev/null
+++ b/client/crash_handler_tvos.h
@@ -0,0 +1,44 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_
+#define CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_
+
+#include "client/crash_handler_base_ios.h"
+#include "util/posix/signals.h"
+
+namespace crashpad {
+
+// A crash handler based on POSIX signals.
+// The APIs to handle Mach exceptions are not available to third-party
+// applications on tvOS.
+class CrashHandler final : public CrashHandlerBase {
+ public:
+ CrashHandler(const CrashHandler&) = delete;
+ CrashHandler& operator=(const CrashHandler&) = delete;
+
+ static CrashHandler* Get();
+
+ static void ResetForTesting();
+
+ uint64_t GetThreadIdForTesting();
+
+ private:
+ CrashHandler();
+ ~CrashHandler() override;
+
+ bool DoInitialize() override;
+
+ // The signal handler installed at OS-level.
+ static void CatchAndReraiseSignal(int signo,
+ siginfo_t* siginfo,
+ void* context);
+
+ Signals::OldActions old_actions_ = {};
+ static CrashHandler* instance_;
+};
+
+} // namespace crashpad
+
+#endif // CRASHPAD_CLIENT_CRASH_HANDLER_TVOS_H_
diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc
index f110ec6..fbff1a3 100644
--- a/client/crashpad_client_ios.cc
+++ b/client/crashpad_client_ios.cc
@@ -14,441 +14,17 @@
#include "client/crashpad_client.h"
-#include <signal.h>
-#include <unistd.h>
-
-#include <atomic>
-#include <iterator>
-
-#include "base/apple/mach_logging.h"
-#include "base/logging.h"
-#include "client/ios_handler/exception_processor.h"
+#include "build/buildflag.h"
#include "client/ios_handler/in_process_handler.h"
-#include "util/ios/raw_logging.h"
-#include "util/mach/exc_server_variants.h"
-#include "util/mach/exception_ports.h"
-#include "util/mach/mach_extensions.h"
-#include "util/mach/mach_message.h"
-#include "util/mach/mach_message_server.h"
-#include "util/misc/initialization_state_dcheck.h"
-#include "util/posix/signals.h"
-#include "util/thread/thread.h"
-namespace {
-
-bool IsBeingDebugged() {
- kinfo_proc kern_proc_info;
- int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
- size_t len = sizeof(kern_proc_info);
- if (sysctl(mib, std::size(mib), &kern_proc_info, &len, nullptr, 0) == 0)
- return kern_proc_info.kp_proc.p_flag & P_TRACED;
- return false;
-}
-
-} // namespace
+#if !BUILDFLAG(IS_IOS_TVOS)
+#include "client/crash_handler_ios.h"
+#else
+#include "client/crash_handler_tvos.h"
+#endif
namespace crashpad {
-namespace {
-
-// Thread-safe version of `base::apple::ScopedMachReceiveRight` which allocates
-// the Mach port upon construction and deallocates it upon destruction.
-class ThreadSafeScopedMachPortWithReceiveRight {
- public:
- ThreadSafeScopedMachPortWithReceiveRight()
- : port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) {}
-
- ThreadSafeScopedMachPortWithReceiveRight(
- const ThreadSafeScopedMachPortWithReceiveRight&) = delete;
- ThreadSafeScopedMachPortWithReceiveRight& operator=(
- const ThreadSafeScopedMachPortWithReceiveRight&) = delete;
-
- ~ThreadSafeScopedMachPortWithReceiveRight() { reset(); }
-
- mach_port_t get() { return port_.load(); }
- void reset() {
- mach_port_t old_port = port_.exchange(MACH_PORT_NULL);
- if (old_port == MACH_PORT_NULL) {
- // Already reset, nothing to do.
- return;
- }
- kern_return_t kr = mach_port_mod_refs(
- mach_task_self(), old_port, MACH_PORT_RIGHT_RECEIVE, -1);
- MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
- << "ThreadSafeScopedMachPortWithReceiveRight mach_port_mod_refs";
- kr = mach_port_deallocate(mach_task_self(), old_port);
- MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr)
- << "ThreadSafeScopedMachPortWithReceiveRight mach_port_deallocate";
- }
-
- private:
- std::atomic<mach_port_t> port_;
-};
-
-// A base class for signal handler and Mach exception server.
-class CrashHandler : public Thread,
- public UniversalMachExcServer::Interface,
- public ObjcExceptionDelegate {
- public:
- CrashHandler(const CrashHandler&) = delete;
- CrashHandler& operator=(const CrashHandler&) = delete;
-
- static CrashHandler* Get() {
- if (!instance_)
- instance_ = new CrashHandler();
- return instance_;
- }
-
- static void ResetForTesting() {
- delete instance_;
- instance_ = nullptr;
- }
-
- bool Initialize(
- const base::FilePath& database,
- const std::string& url,
- const std::map<std::string, std::string>& annotations,
- internal::InProcessHandler::ProcessPendingReportsObservationCallback
- callback) {
- INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
- if (!in_process_handler_.Initialize(database, url, annotations, callback) ||
- !InstallMachExceptionHandler() ||
- // xnu turns hardware faults into Mach exceptions, so the only signal
- // left to register is SIGABRT, which never starts off as a hardware
- // fault. Installing a handler for other signals would lead to
- // recording exceptions twice. As a consequence, Crashpad will not
- // generate intermediate dumps for anything manually calling
- // raise(SIG*). In practice, this doesn’t actually happen for crash
- // signals that originate as hardware faults.
- !Signals::InstallHandler(
- SIGABRT, CatchAndReraiseSignal, 0, &old_action_)) {
- LOG(ERROR) << "Unable to initialize Crashpad.";
- return false;
- }
-
- // For applications that haven't ignored or set a handler for SIGPIPE:
- // It’s OK for an application to set its own SIGPIPE handler (including
- // SIG_IGN) before initializing Crashpad, because Crashpad will discover the
- // existing handler and not install its own.
- // It’s OK for Crashpad to install its own SIGPIPE handler and for the
- // application to subsequently install its own (including SIG_IGN)
- // afterwards, because its handler will replace Crashpad’s.
- // This is useful to cover the default situation where nobody installs a
- // SIGPIPE handler and the disposition is at SIG_DFL, because SIGPIPE is a
- // “kill” signal (bsd/sys/signalvar.h sigprop). In that case, without
- // Crashpad, SIGPIPE results in a silent and unreported kill (and not even
- // ReportCrash will record it), but developers probably want to be alerted
- // to the conditon.
- struct sigaction sa;
- if (sigaction(SIGPIPE, nullptr, &sa) == 0 && sa.sa_handler == SIG_DFL) {
- Signals::InstallHandler(
- SIGPIPE, CatchAndReraiseSignalDefaultAction, 0, nullptr);
- }
-
- InstallObjcExceptionPreprocessor(this);
- INITIALIZATION_STATE_SET_VALID(initialized_);
- return true;
- }
-
- void ProcessIntermediateDumps(
- const std::map<std::string, std::string>& annotations,
- const UserStreamDataSources* user_stream_sources) {
- in_process_handler_.ProcessIntermediateDumps(annotations,
- user_stream_sources);
- }
-
- void ProcessIntermediateDump(
- const base::FilePath& file,
- const std::map<std::string, std::string>& annotations) {
- in_process_handler_.ProcessIntermediateDump(file, annotations);
- }
-
- void DumpWithoutCrash(NativeCPUContext* context, bool process_dump) {
- INITIALIZATION_STATE_DCHECK_VALID(initialized_);
- base::FilePath path;
- if (!in_process_handler_.DumpExceptionFromSimulatedMachException(
- context, kMachExceptionSimulated, &path)) {
- return;
- }
-
- if (process_dump) {
- in_process_handler_.ProcessIntermediateDump(path);
- }
- }
-
- void DumpWithoutCrashAtPath(NativeCPUContext* context,
- const base::FilePath& path) {
- in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath(
- context, kMachExceptionSimulated, path);
- }
-
- void StartProcessingPendingReports(UploadBehavior upload_behavior) {
- INITIALIZATION_STATE_DCHECK_VALID(initialized_);
- in_process_handler_.StartProcessingPendingReports(upload_behavior);
- }
-
- void SetExceptionCallbackForTesting(void (*callback)()) {
- in_process_handler_.SetExceptionCallbackForTesting(callback);
- }
-
- uint64_t GetThreadIdForTesting() { return Thread::GetThreadIdForTesting(); }
-
- private:
- CrashHandler() = default;
-
- ~CrashHandler() {
- UninstallObjcExceptionPreprocessor();
- // Reset the SIGPIPE handler only if the current handler is the one
- // installed by Initialize(). In other words, if an application has set its
- // own SIGPIPE handler after initializing Crashpad, there is no need to
- // change the signal handler here.
- struct sigaction sa;
- if (sigaction(SIGPIPE, nullptr, &sa) == 0 &&
- sa.sa_sigaction == CatchAndReraiseSignalDefaultAction) {
- Signals::InstallDefaultHandler(SIGPIPE);
- }
- Signals::InstallDefaultHandler(SIGABRT);
- UninstallMachExceptionHandler();
- }
-
- bool InstallMachExceptionHandler() {
- mach_port_t exception_port = exception_port_.get();
- if (exception_port == MACH_PORT_NULL) {
- return false;
- }
-
- kern_return_t kr = mach_port_insert_right(mach_task_self(),
- exception_port,
- exception_port,
- MACH_MSG_TYPE_MAKE_SEND);
- if (kr != KERN_SUCCESS) {
- MACH_LOG(ERROR, kr) << "mach_port_insert_right";
- return false;
- }
-
- // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
- // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger.
- exception_mask_t mask =
- ExcMaskAll() &
- ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT |
- EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0));
-
- ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
- if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) ||
- !exception_ports.SetExceptionPort(
- mask,
- exception_port,
- EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
- MACHINE_THREAD_STATE)) {
- return false;
- }
-
- mach_handler_running_ = true;
- Start();
- return true;
- }
-
- void UninstallMachExceptionHandler() {
- mach_handler_running_ = false;
- exception_port_.reset();
- Join();
- }
-
- // Thread:
-
- void ThreadMain() override {
- UniversalMachExcServer universal_mach_exc_server(this);
- while (mach_handler_running_) {
- mach_msg_return_t mr =
- MachMessageServer::Run(&universal_mach_exc_server,
- exception_port_.get(),
- MACH_MSG_OPTION_NONE,
- MachMessageServer::kPersistent,
- MachMessageServer::kReceiveLargeIgnore,
- kMachMessageTimeoutWaitIndefinitely);
- MACH_CHECK(
- mach_handler_running_
- ? mr == MACH_SEND_INVALID_DEST // This shouldn't happen for
- // exception messages that come
- // from the kernel itself, but if
- // something else in-process sends
- // exception messages and breaks,
- // handle that case.
- : (mr == MACH_RCV_PORT_CHANGED || // Port was closed while the
- // thread was listening.
- mr == MACH_RCV_INVALID_NAME), // Port was closed before the
- // thread started listening.
- mr)
- << "MachMessageServer::Run";
- }
- }
-
- // UniversalMachExcServer::Interface:
-
- kern_return_t CatchMachException(exception_behavior_t behavior,
- exception_handler_t exception_port,
- thread_t thread,
- task_t task,
- exception_type_t exception,
- const mach_exception_data_type_t* code,
- mach_msg_type_number_t code_count,
- thread_state_flavor_t* flavor,
- ConstThreadState old_state,
- mach_msg_type_number_t old_state_count,
- thread_state_t new_state,
- mach_msg_type_number_t* new_state_count,
- const mach_msg_trailer_t* trailer,
- bool* destroy_complex_request) override {
- *destroy_complex_request = true;
-
- // TODO(justincohen): Forward exceptions to original_handlers_ with
- // UniversalExceptionRaise.
-
- // iOS shouldn't have any child processes, but just in case, those will
- // inherit the task exception ports, and this process isn’t prepared to
- // handle them
- if (task != mach_task_self()) {
- CRASHPAD_RAW_LOG("MachException task != mach_task_self()");
- return KERN_FAILURE;
- }
-
- HandleMachException(behavior,
- thread,
- exception,
- code,
- code_count,
- *flavor,
- old_state,
- old_state_count);
-
- // Respond with KERN_FAILURE so the system will continue to handle this
- // exception. xnu will turn this Mach exception into a signal and take the
- // default action to terminate the process. However, if sigprocmask is
- // called before this Mach exception returns (such as by another thread
- // calling abort, see: Libc-1506.40.4/stdlib/FreeBSD/abort.c), the Mach
- // exception will be converted into a signal but delivery will be blocked.
- // Since concurrent exceptions lead to the losing thread sleeping
- // indefinitely, if the abort thread never returns, the thread that
- // triggered this Mach exception will repeatedly trap and the process will
- // never terminate. If the abort thread didn’t have a user-space signal
- // handler that slept forever, the abort would terminate the process even if
- // all other signals had been blocked. Instead, unblock all signals
- // corresponding to all Mach exceptions Crashpad is registered for before
- // returning KERN_FAILURE. There is still racy behavior possible with this
- // call to sigprocmask, but the repeated calls to CatchMachException here
- // will eventually lead to termination.
- sigset_t unblock_set;
- sigemptyset(&unblock_set);
- sigaddset(&unblock_set, SIGILL); // EXC_BAD_INSTRUCTION
- sigaddset(&unblock_set, SIGTRAP); // EXC_BREAKPOINT
- sigaddset(&unblock_set, SIGFPE); // EXC_ARITHMETIC
- sigaddset(&unblock_set, SIGBUS); // EXC_BAD_ACCESS
- sigaddset(&unblock_set, SIGSEGV); // EXC_BAD_ACCESS
- if (sigprocmask(SIG_UNBLOCK, &unblock_set, nullptr) != 0) {
- CRASHPAD_RAW_LOG("sigprocmask");
- }
- return KERN_FAILURE;
- }
-
- void HandleMachException(exception_behavior_t behavior,
- thread_t thread,
- exception_type_t exception,
- const mach_exception_data_type_t* code,
- mach_msg_type_number_t code_count,
- thread_state_flavor_t flavor,
- ConstThreadState old_state,
- mach_msg_type_number_t old_state_count) {
- in_process_handler_.DumpExceptionFromMachException(behavior,
- thread,
- exception,
- code,
- code_count,
- flavor,
- old_state,
- old_state_count);
- }
-
- void HandleUncaughtNSException(const uint64_t* frames,
- const size_t num_frames) override {
- in_process_handler_.DumpExceptionFromNSExceptionWithFrames(frames,
- num_frames);
- // After uncaught exceptions are reported, the system immediately triggers a
- // call to std::terminate()/abort(). Remove the abort handler so a second
- // dump isn't generated.
- CHECK(Signals::InstallDefaultHandler(SIGABRT));
- }
-
- void HandleUncaughtNSExceptionWithContext(
- NativeCPUContext* context) override {
- base::FilePath path;
- in_process_handler_.DumpExceptionFromSimulatedMachException(
- context, kMachExceptionFromNSException, &path);
-
- // After uncaught exceptions are reported, the system immediately triggers a
- // call to std::terminate()/abort(). Remove the abort handler so a second
- // dump isn't generated.
- CHECK(Signals::InstallDefaultHandler(SIGABRT));
- }
-
- void HandleUncaughtNSExceptionWithContextAtPath(
- NativeCPUContext* context,
- const base::FilePath& path) override {
- in_process_handler_.DumpExceptionFromSimulatedMachExceptionAtPath(
- context, kMachExceptionFromNSException, path);
- }
-
- bool MoveIntermediateDumpAtPathToPending(
- const base::FilePath& path) override {
- if (in_process_handler_.MoveIntermediateDumpAtPathToPending(path)) {
- // After uncaught exceptions are reported, the system immediately triggers
- // a call to std::terminate()/abort(). Remove the abort handler so a
- // second dump isn't generated.
- CHECK(Signals::InstallDefaultHandler(SIGABRT));
- return true;
- }
- return false;
- }
-
- // The signal handler installed at OS-level.
- static void CatchAndReraiseSignal(int signo,
- siginfo_t* siginfo,
- void* context) {
- Get()->HandleAndReraiseSignal(signo,
- siginfo,
- reinterpret_cast<ucontext_t*>(context),
- &(Get()->old_action_));
- }
-
- static void CatchAndReraiseSignalDefaultAction(int signo,
- siginfo_t* siginfo,
- void* context) {
- Get()->HandleAndReraiseSignal(
- signo, siginfo, reinterpret_cast<ucontext_t*>(context), nullptr);
- }
-
- void HandleAndReraiseSignal(int signo,
- siginfo_t* siginfo,
- ucontext_t* context,
- struct sigaction* old_action) {
- in_process_handler_.DumpExceptionFromSignal(siginfo, context);
-
- // Always call system handler.
- Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, old_action);
- }
-
- ThreadSafeScopedMachPortWithReceiveRight exception_port_;
- ExceptionPorts::ExceptionHandlerVector original_handlers_;
- struct sigaction old_action_ = {};
- internal::InProcessHandler in_process_handler_;
- static CrashHandler* instance_;
- std::atomic<bool> mach_handler_running_ = false;
- InitializationStateDcheck initialized_;
-};
-
-CrashHandler* CrashHandler::instance_ = nullptr;
-
-} // namespace
-
CrashpadClient::CrashpadClient() {}
CrashpadClient::~CrashpadClient() {}
diff --git a/test/ios/crash_type_xctest.mm b/test/ios/crash_type_xctest.mm
index 1c92fb6..c81e8c2 100644
--- a/test/ios/crash_type_xctest.mm
+++ b/test/ios/crash_type_xctest.mm
@@ -159,6 +159,7 @@
- (void)testTrap {
[rootObject_ crashTrap];
+#if !BUILDFLAG(IS_IOS_TVOS)
#if defined(ARCH_CPU_X86_64)
[self verifyCrashReportException:EXC_BAD_INSTRUCTION];
#elif defined(ARCH_CPU_ARM64)
@@ -166,6 +167,12 @@
#else
#error Port to your CPU architecture
#endif
+#else // !BUILDFLAG(IS_IOS_TVOS)
+ [self verifyCrashReportException:EXC_SOFT_SIGNAL];
+ NSNumber* report_exception;
+ XCTAssertTrue([rootObject_ pendingReportExceptionInfo:&report_exception]);
+ XCTAssertEqual(report_exception.intValue, SIGTRAP);
+#endif
}
- (void)testAbort {
@@ -178,7 +185,14 @@
- (void)testBadAccess {
[rootObject_ crashBadAccess];
+#if !BUILDFLAG(IS_IOS_TVOS)
[self verifyCrashReportException:EXC_BAD_ACCESS];
+#else
+ [self verifyCrashReportException:EXC_SOFT_SIGNAL];
+ NSNumber* report_exception;
+ XCTAssertTrue([rootObject_ pendingReportExceptionInfo:&report_exception]);
+ XCTAssertEqual(report_exception.intValue, SIGSEGV);
+#endif
}
- (void)testException {
@@ -250,7 +264,14 @@
- (void)testCatchUIGestureEnvironmentNSException {
// Tap the button with the string UIGestureEnvironmentException.
+#if !BUILDFLAG(IS_IOS_TVOS)
[app_.buttons[@"UIGestureEnvironmentException"] tap];
+#else
+ // tvOS does not have [XCUIElement tap]. This version assumes there is just
+ // one big button with "UIGestureEnvironmentException" as title, so we can
+ // just press Select on the remote to activate it.
+ [XCUIRemote.sharedRemote pressButton:XCUIRemoteButtonSelect];
+#endif
[self verifyCrashReportException:crashpad::kMachExceptionFromNSException];
NSDictionary* dict = [rootObject_ getAnnotations];
XCTAssertTrue([[dict[@"objects"][0] valueForKeyPath:@"exceptionReason"]
@@ -280,10 +301,21 @@
isEqualToString:@"NSGenericException"]);
}
+// This test cannot run correctly on tvOS: it is impossible to catch this stack
+// overflow as a Mach exception (like we do on iOS), and sigaltstack() is also
+// forbidden so we cannot detect it with POSIX signals either.
+// Per xnu-11215.81.4/bsd/uxkern/ux_exception.c's handle_ux_exception(), when a
+// stack overflow is detected but no alternate stack is specified, the kernel
+// will reset SIGSEGV to SIG_DFL before delivering the signal, so we are never
+// able to capture this crash. Even if we do pass SA_ONSTACK to sigaction(), we
+// will just crash a bit later, as we are still using the same stack that has
+// already overflown.
+#if !BUILDFLAG(IS_IOS_TVOS)
- (void)testRecursion {
[rootObject_ crashRecursion];
[self verifyCrashReportException:EXC_BAD_ACCESS];
}
+#endif
- (void)testClientAnnotations {
[rootObject_ crashKillAbort];
diff --git a/util/BUILD.gn b/util/BUILD.gn
index 7f7e767..b174ddf 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -19,7 +19,7 @@
import("//build/config/sanitizers/sanitizers.gni")
}
-if (crashpad_is_apple) {
+if (crashpad_is_apple && !crashpad_is_tvos) {
if (crashpad_is_in_chromium || crashpad_is_in_fuchsia) {
import("//build/config/sysroot.gni")
} else {
@@ -322,29 +322,37 @@
"mac/sysctl.h",
"mac/xattr.cc",
"mac/xattr.h",
- "mach/composite_mach_message_server.cc",
- "mach/composite_mach_message_server.h",
- "mach/exc_client_variants.cc",
- "mach/exc_client_variants.h",
- "mach/exc_server_variants.cc",
- "mach/exc_server_variants.h",
- "mach/exception_behaviors.cc",
- "mach/exception_behaviors.h",
- "mach/exception_ports.cc",
- "mach/exception_ports.h",
"mach/mach_extensions.cc",
"mach/mach_extensions.h",
- "mach/mach_message.cc",
- "mach/mach_message.h",
- "mach/mach_message_server.cc",
- "mach/mach_message_server.h",
- "mach/symbolic_constants_mach.cc",
- "mach/symbolic_constants_mach.h",
"misc/capture_context_mac.S",
"misc/clock_mac.cc",
"misc/paths_mac.cc",
"synchronization/semaphore_mac.cc",
]
+
+ # Exclude files related to Mach exceptions when building for tvOS. Mach
+ # messaging APIs are not available to third-party applications on tvOS so
+ # crashes are handled via POSIX signals.
+ if (!crashpad_is_tvos) {
+ sources += [
+ "mach/composite_mach_message_server.cc",
+ "mach/composite_mach_message_server.h",
+ "mach/exc_client_variants.cc",
+ "mach/exc_client_variants.h",
+ "mach/exc_server_variants.cc",
+ "mach/exc_server_variants.h",
+ "mach/exception_behaviors.cc",
+ "mach/exception_behaviors.h",
+ "mach/exception_ports.cc",
+ "mach/exception_ports.h",
+ "mach/mach_message.cc",
+ "mach/mach_message.h",
+ "mach/mach_message_server.cc",
+ "mach/mach_message_server.h",
+ "mach/symbolic_constants_mach.cc",
+ "mach/symbolic_constants_mach.h",
+ ]
+ }
}
if (crashpad_is_mac && !crashpad_is_in_fuchsia) {
@@ -583,10 +591,11 @@
if (crashpad_is_apple) {
include_dirs += [ "$root_gen_dir" ]
- deps += [
- ":mig_output",
- "../build:apple_enable_arc",
- ]
+ deps += [ "../build:apple_enable_arc" ]
+
+ if (!crashpad_is_tvos) {
+ deps += [ ":mig_output" ]
+ }
}
if (crashpad_is_mac && !crashpad_is_in_fuchsia) {
@@ -813,14 +822,19 @@
if (crashpad_is_apple) {
sources += [
"mac/xattr_test.cc",
- "mach/composite_mach_message_server_test.cc",
- "mach/exc_server_variants_test.cc",
- "mach/exception_behaviors_test.cc",
- "mach/mach_extensions_test.cc",
- "mach/mach_message_test.cc",
- "mach/symbolic_constants_mach_test.cc",
"misc/capture_context_test_util_mac.cc",
]
+
+ if (!crashpad_is_tvos) {
+ sources += [
+ "mach/composite_mach_message_server_test.cc",
+ "mach/exc_server_variants_test.cc",
+ "mach/exception_behaviors_test.cc",
+ "mach/mach_extensions_test.cc",
+ "mach/mach_message_test.cc",
+ "mach/symbolic_constants_mach_test.cc",
+ ]
+ }
}
if (crashpad_is_mac) {
diff --git a/util/posix/signals.cc b/util/posix/signals.cc
index e24bb60..58f957c 100644
--- a/util/posix/signals.cc
+++ b/util/posix/signals.cc
@@ -278,14 +278,8 @@
}
// static
-void Signals::RestoreHandlerAndReraiseSignalOnReturn(
- const siginfo_t* siginfo,
- const struct sigaction* old_action) {
- // Failures in this function should _exit(kFailureExitCode). This is a quick
- // and quiet failure. This function runs in signal handler context, and it’s
- // difficult to safely be loud from a signal handler.
- constexpr int kFailureExitCode = 191;
-
+bool Signals::RestoreOrResetHandler(int sig,
+ const struct sigaction* old_action) {
struct sigaction default_action;
sigemptyset(&default_action.sa_mask);
default_action.sa_flags = 0;
@@ -297,9 +291,25 @@
// Try to restore restore_action. If that fails and restore_action was
// old_action, the problem may have been that old_action was bogus, so try to
// set the default action.
- const int sig = siginfo->si_signo;
if (sigaction(sig, restore_action, nullptr) != 0 && old_action &&
sigaction(sig, &default_action, nullptr) != 0) {
+ return false;
+ }
+ return true;
+}
+
+// static
+void Signals::RestoreHandlerAndReraiseSignalOnReturn(
+ const siginfo_t* siginfo,
+ const struct sigaction* old_action) {
+ // Failures in this function should _exit(kFailureExitCode). This is a quick
+ // and quiet failure. This function runs in signal handler context, and it’s
+ // difficult to safely be loud from a signal handler.
+ constexpr int kFailureExitCode = 191;
+
+ const int sig = siginfo->si_signo;
+
+ if (!RestoreOrResetHandler(sig, old_action)) {
_exit(kFailureExitCode);
}
diff --git a/util/posix/signals.h b/util/posix/signals.h
index e12ad72..934b312 100644
--- a/util/posix/signals.h
+++ b/util/posix/signals.h
@@ -196,6 +196,24 @@
//! \note This function is safe to call from a signal handler.
static bool WillSignalReraiseAutonomously(const siginfo_t* siginfo);
+ //! \brief Restores a previous signal action or reinstalls the default signal
+ //! handler for a given signal.
+ //!
+ //! Attempts to reinstate the action given by \a old_action and, in case of
+ //! failure or if \a old_actiono is `nullptr`, resets the handler for \a sig
+ //! to the default action.
+ //!
+ //! \param[in] sig The signal to manage.
+ //! \param[in] old_action The previous action for the signal, which will be
+ //! re-established as the signal’s action. May be `nullptr`, which directs
+ //! the default action for the signal to be used.
+ //!
+ //! \return `true` on success, `false` if `sigaction()` fails.
+ //!
+ //! \note This function is safe to call from a signal handler.
+ static bool RestoreOrResetHandler(int sig,
+ const struct sigaction* old_action);
+
//! \brief Restores a previous signal action and arranges to re-raise a signal
//! on return from a signal handler.
//!