From 17a515d33dc56b9d6a3c982d2d2e81c61ff9ba41 Mon Sep 17 00:00:00 2001 From: Justin Cohen Date: Sat, 25 Apr 2020 17:16:10 -0400 Subject: [PATCH] [ios] Bring up first draft Mach exception server. Add Mach exception server and fill out exceptions snapshot. Note that: - The 'capture' portion of this CL will be moved out of the snapshot interface and into a separate in-process dump to disk location. - All of the pointer dereferences need to be wrapped in vm_read. - The read-fast-and-dump logic in exception_snapshot will end up in a different file completely, but until we pick a serialization/deserialization method, keep it as-is. Bug: crashpad:31 Change-Id: I44203aa44036a341d6b4517fde7ab0cb9d7e94d7 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2160122 Commit-Queue: Justin Cohen Reviewed-by: Mark Mentovai --- client/crashpad_client.h | 9 +- client/crashpad_client_ios.cc | 206 ++++++++++++++---- client/crashpad_client_ios_test.mm | 17 +- snapshot/ios/exception_snapshot_ios.cc | 170 +++++++++++++-- snapshot/ios/exception_snapshot_ios.h | 21 +- snapshot/ios/process_snapshot_ios.cc | 33 ++- snapshot/ios/process_snapshot_ios.h | 15 +- snapshot/ios/thread_snapshot_ios.cc | 7 +- snapshot/ios/thread_snapshot_ios.h | 2 +- snapshot/mac/cpu_context_mac.cc | 112 +++++++++- snapshot/mac/cpu_context_mac.h | 29 ++- snapshot/mac/exception_snapshot_mac.cc | 9 +- test/ios/host/cptest_crash_view_controller.mm | 2 - util/BUILD.gn | 2 +- util/misc/capture_context.h | 10 +- 15 files changed, 558 insertions(+), 86 deletions(-) diff --git a/client/crashpad_client.h b/client/crashpad_client.h index 7a6a18a9..f5e02c9c 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -431,18 +431,19 @@ class CrashpadClient { //! //! This method is only defined on iOS. //! - //! \return `true` on success, `false` on failure with a message logged. - //! //! TODO(justincohen): This method will need to take database, metrics_dir, //! url and annotations eventually. - bool StartCrashpadInProcessHandler(); + void StartCrashpadInProcessHandler(); // TODO(justincohen): This method is purely for bringing up iOS interfaces. //! \brief Requests that the handler capture a dump even though there hasn't //! been a crash. //! //! A handler must have already been installed before calling this method. - static void DumpWithoutCrash(); + //! + //! \param[in] context A NativeCPUContext, generally captured by + //! CaptureContext() or similar. + static void DumpWithoutCrash(NativeCPUContext* context); #endif #if defined(OS_MACOSX) || DOXYGEN diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc index 465867af..342e1090 100644 --- a/client/crashpad_client_ios.cc +++ b/client/crashpad_client_ios.cc @@ -16,65 +16,192 @@ #include +#include + #include "base/logging.h" -#include "base/strings/stringprintf.h" -#include "client/client_argv_handling.h" +#include "base/mac/mach_logging.h" +#include "base/mac/scoped_mach_port.h" +#include "base/stl_util.h" #include "snapshot/ios/process_snapshot_ios.h" #include "util/ios/exception_processor.h" #include "util/ios/ios_system_data_collector.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 crashpad { namespace { -// A base class for Crashpad signal handler implementations. -class SignalHandler { +// A base class for signal handler and Mach exception server. +class CrashHandler : public Thread, public UniversalMachExcServer::Interface { public: - // Returns the currently installed signal hander. - static SignalHandler* Get() { - static SignalHandler* instance = new SignalHandler(); + static CrashHandler* Get() { + static CrashHandler* instance = new CrashHandler(); return instance; } - bool Install(const std::set* unhandled_signals) { - return Signals::InstallCrashHandlers( - HandleSignal, 0, &old_actions_, unhandled_signals); + void Initialize() { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + InstallMachExceptionHandler(); + CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)); + INITIALIZATION_STATE_SET_VALID(initialized_); } - void HandleCrash(int signo, siginfo_t* siginfo, void* context) { - // TODO(justincohen): This is incomplete. - ProcessSnapshotIOS process_snapshot; - process_snapshot.Initialize(system_data); - process_snapshot.SetException(siginfo, - reinterpret_cast(context)); + void DumpWithoutCrash(NativeCPUContext* context) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + mach_exception_data_type_t code[2] = {}; + static constexpr int kSimulatedException = -1; + HandleMachException(MACH_EXCEPTION_CODES, + mach_thread_self(), + kSimulatedException, + code, + base::size(code), + MACHINE_THREAD_STATE, + reinterpret_cast(context), + MACHINE_THREAD_STATE_COUNT); } private: - SignalHandler() = default; + CrashHandler() = default; - // The base implementation for all signal handlers, suitable for calling - // directly to simulate signal delivery. - void HandleCrashAndReraiseSignal(int signo, - siginfo_t* siginfo, - void* context) { - HandleCrash(signo, siginfo, context); - // Always call system handler. - Signals::RestoreHandlerAndReraiseSignalOnReturn( - siginfo, old_actions_.ActionForSignal(signo)); + void InstallMachExceptionHandler() { + exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); + CHECK(exception_port_.is_valid()); + + kern_return_t kr = mach_port_insert_right(mach_task_self(), + exception_port_.get(), + exception_port_.get(), + MACH_MSG_TYPE_MAKE_SEND); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right"; + + // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT. + const exception_mask_t mask = + ExcMaskAll() & + ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | + EXC_MASK_RPC_ALERT | EXC_MASK_GUARD); + ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); + exception_ports.GetExceptionPorts(mask, &original_handlers_); + exception_ports.SetExceptionPort( + mask, + exception_port_.get(), + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + MACHINE_THREAD_STATE); + + Start(); + } + + // Thread: + + void ThreadMain() override { + UniversalMachExcServer universal_mach_exc_server(this); + while (true) { + 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(mr == MACH_SEND_INVALID_DEST, 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()) { + LOG(WARNING) << "task 0x" << std::hex << task << " != 0x" + << 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 as a crash. + 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) { + // TODO(justincohen): This is incomplete. + ProcessSnapshotIOS process_snapshot; + process_snapshot.Initialize(system_data_); + process_snapshot.SetExceptionFromMachException(behavior, + thread, + exception, + code, + code_count, + flavor, + old_state, + old_state_count); } // The signal handler installed at OS-level. - static void HandleSignal(int signo, siginfo_t* siginfo, void* context) { - Get()->HandleCrashAndReraiseSignal(signo, siginfo, context); + static void CatchSignal(int signo, siginfo_t* siginfo, void* context) { + Get()->HandleAndReraiseSignal( + signo, siginfo, reinterpret_cast(context)); } - Signals::OldActions old_actions_ = {}; + void HandleAndReraiseSignal(int signo, + siginfo_t* siginfo, + ucontext_t* context) { + // TODO(justincohen): This is incomplete. + ProcessSnapshotIOS process_snapshot; + process_snapshot.Initialize(system_data_); + process_snapshot.SetExceptionFromSignal(siginfo, context); - // Collect some system data before the signal handler is triggered. - IOSSystemDataCollector system_data; + // Always call system handler. + Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_); + } - DISALLOW_COPY_AND_ASSIGN(SignalHandler); + base::mac::ScopedMachReceiveRight exception_port_; + ExceptionPorts::ExceptionHandlerVector original_handlers_; + struct sigaction old_action_ = {}; + IOSSystemDataCollector system_data_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(CrashHandler); }; } // namespace @@ -83,16 +210,19 @@ CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} -bool CrashpadClient::StartCrashpadInProcessHandler() { +void CrashpadClient::StartCrashpadInProcessHandler() { InstallObjcExceptionPreprocessor(); - return SignalHandler::Get()->Install(nullptr); + + CrashHandler* crash_handler = CrashHandler::Get(); + DCHECK(crash_handler); + crash_handler->Initialize(); } // static -void CrashpadClient::DumpWithoutCrash() { - DCHECK(SignalHandler::Get()); - siginfo_t siginfo = {}; - SignalHandler::Get()->HandleCrash(siginfo.si_signo, &siginfo, nullptr); +void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { + CrashHandler* crash_handler = CrashHandler::Get(); + DCHECK(crash_handler); + crash_handler->DumpWithoutCrash(context); } } // namespace crashpad diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm index e07a1073..002fddbf 100644 --- a/client/crashpad_client_ios_test.mm +++ b/client/crashpad_client_ios_test.mm @@ -27,11 +27,24 @@ namespace { using CrashpadIOSClient = PlatformTest; -// TODO(justincohen): This is a placeholder. TEST_F(CrashpadIOSClient, DumpWithoutCrash) { CrashpadClient client; client.StartCrashpadInProcessHandler(); - client.DumpWithoutCrash(); + + NativeCPUContext context; +#if defined(ARCH_CPU_X86_64) + CaptureContext(&context); +#elif defined(ARCH_CPU_ARM64) + // TODO(justincohen): Implement CaptureContext for ARM64. + mach_msg_type_number_t thread_state_count = MACHINE_THREAD_STATE_COUNT; + kern_return_t kr = + thread_get_state(mach_thread_self(), + MACHINE_THREAD_STATE, + reinterpret_cast(&context), + &thread_state_count); + ASSERT_EQ(kr, KERN_SUCCESS); +#endif + client.DumpWithoutCrash(&context); } // This test is covered by a similar XCUITest, but for development purposes diff --git a/snapshot/ios/exception_snapshot_ios.cc b/snapshot/ios/exception_snapshot_ios.cc index 559b105e..db9e4891 100644 --- a/snapshot/ios/exception_snapshot_ios.cc +++ b/snapshot/ios/exception_snapshot_ios.cc @@ -22,6 +22,7 @@ #include "util/misc/from_pointer_cast.h" namespace crashpad { + namespace internal { ExceptionSnapshotIOS::ExceptionSnapshotIOS() @@ -30,24 +31,21 @@ ExceptionSnapshotIOS::ExceptionSnapshotIOS() codes_(), thread_id_(0), exception_address_(0), - signal_number_(0), - signal_code_(0), + exception_(0), + exception_info_(0), initialized_() {} ExceptionSnapshotIOS::~ExceptionSnapshotIOS() {} -bool ExceptionSnapshotIOS::Initialize(const siginfo_t* siginfo, - const ucontext_t* context) { +void ExceptionSnapshotIOS::InitializeFromSignal(const siginfo_t* siginfo, + const ucontext_t* context) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - if (!context) - return false; - mcontext_t mcontext = context->uc_mcontext; #if defined(ARCH_CPU_X86_64) context_.architecture = kCPUArchitectureX86_64; context_.x86_64 = &context_x86_64_; - x86_debug_state64_t empty_debug_state; + x86_debug_state64_t empty_debug_state = {}; InitializeCPUContextX86_64(&context_x86_64_, THREAD_STATE_NONE, nullptr, @@ -58,7 +56,12 @@ bool ExceptionSnapshotIOS::Initialize(const siginfo_t* siginfo, #elif defined(ARCH_CPU_ARM64) context_.architecture = kCPUArchitectureARM64; context_.arm64 = &context_arm64_; - InitializeCPUContextARM64(&context_arm64_, &mcontext->__ss, &mcontext->__ns); + InitializeCPUContextARM64(&context_arm64_, + THREAD_STATE_NONE, + nullptr, + 0, + &mcontext->__ss, + &mcontext->__ns); #endif // Thread ID. @@ -75,12 +78,151 @@ bool ExceptionSnapshotIOS::Initialize(const siginfo_t* siginfo, thread_id_ = identifier_info.thread_id; } - signal_number_ = siginfo->si_signo; - signal_code_ = siginfo->si_code; + exception_ = siginfo->si_signo; + exception_info_ = siginfo->si_code; + + // TODO(justincohen): Investigate recording more codes_. + exception_address_ = FromPointerCast(siginfo->si_addr); + // TODO(justincohen): Record the source of the exception (signal, mach, etc). + + INITIALIZATION_STATE_SET_VALID(initialized_); +} + +void ExceptionSnapshotIOS::InitializeFromMachException( + exception_behavior_t behavior, + thread_t exception_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 state, + mach_msg_type_number_t state_count) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + codes_.push_back(exception); + // TODO: rationalize with the macOS implementation. + for (mach_msg_type_number_t code_index = 0; code_index < code_count; + ++code_index) { + codes_.push_back(code[code_index]); + } + exception_ = exception; + exception_info_ = code[0]; + + // For serialization, float_state and, on x86, debug_state, will be identical + // between here and the thread_snapshot version for thread_id. That means + // this block getting float_state and debug_state can be skipped when doing + // proper serialization. +#if defined(ARCH_CPU_X86_64) + x86_thread_state64_t thread_state; + x86_float_state64_t float_state; + x86_debug_state64_t debug_state; + mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT; + mach_msg_type_number_t float_state_count = x86_FLOAT_STATE64_COUNT; + mach_msg_type_number_t debug_state_count = x86_DEBUG_STATE64_COUNT; + const thread_state_flavor_t kThreadStateFlavor = x86_THREAD_STATE64; + const thread_state_flavor_t kFloatStateFlavor = x86_FLOAT_STATE64; + const thread_state_flavor_t kDebugStateFlavor = x86_DEBUG_STATE64; +#elif defined(ARCH_CPU_ARM64) + arm_thread_state64_t thread_state; + arm_neon_state64_t float_state; + mach_msg_type_number_t float_state_count = ARM_NEON_STATE64_COUNT; + mach_msg_type_number_t thread_state_count = ARM_THREAD_STATE64_COUNT; + const thread_state_flavor_t kThreadStateFlavor = ARM_THREAD_STATE64; + const thread_state_flavor_t kFloatStateFlavor = ARM_NEON_STATE64; +#endif + + kern_return_t kr = + thread_get_state(exception_thread, + kThreadStateFlavor, + reinterpret_cast(&thread_state), + &thread_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kThreadStateFlavor << ")"; + } + + kr = thread_get_state(exception_thread, + kFloatStateFlavor, + reinterpret_cast(&float_state), + &float_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kFloatStateFlavor << ")"; + } + +#if defined(ARCH_CPU_X86_64) + kr = thread_get_state(exception_thread, + kDebugStateFlavor, + reinterpret_cast(&debug_state), + &debug_state_count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_get_state(" << kDebugStateFlavor << ")"; + } +#endif + +#if defined(ARCH_CPU_X86_64) + context_.architecture = kCPUArchitectureX86_64; + context_.x86_64 = &context_x86_64_; + InitializeCPUContextX86_64(&context_x86_64_, + flavor, + state, + state_count, + &thread_state, + &float_state, + &debug_state); +#elif defined(ARCH_CPU_ARM64) + context_.architecture = kCPUArchitectureARM64; + context_.arm64 = &context_arm64_; + InitializeCPUContextARM64( + &context_arm64_, flavor, state, state_count, &thread_state, &float_state); +#endif + + // Thread ID. + thread_identifier_info identifier_info; + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + kr = thread_info(mach_thread_self(), + THREAD_IDENTIFIER_INFO, + reinterpret_cast(&identifier_info), + &count); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "thread_identifier_info"; + } else { + thread_id_ = identifier_info.thread_id; + } + + // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present + // in code[1]. It may or may not be the instruction pointer address (usually + // it’s not). code[1] may carry the exception address for other exception + // types too, but it’s not guaranteed. But for all other exception types, the + // instruction pointer will be the exception address, and in fact will be + // equal to codes[1] when it’s carrying the exception address. In those cases, + // just use the instruction pointer directly. + bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS; + +#if defined(ARCH_CPU_X86_64) + // For x86 and x86_64 EXC_BAD_ACCESS exceptions, some code[0] values + // indicate that code[1] does not (or may not) carry the exception address: + // EXC_I386_GPFLT (10.9.5 xnu-2422.115.4/osfmk/i386/trap.c user_trap() for + // T_GENERAL_PROTECTION) and the oddball (VM_PROT_READ | VM_PROT_EXECUTE) + // which collides with EXC_I386_BOUNDFLT (10.9.5 + // xnu-2422.115.4/osfmk/i386/fpu.c fpextovrflt()). Other EXC_BAD_ACCESS + // exceptions come through 10.9.5 xnu-2422.115.4/osfmk/i386/trap.c + // user_page_fault_continue() and do contain the exception address in + // code[1]. + if (exception_ == EXC_BAD_ACCESS && + (exception_info_ == EXC_I386_GPFLT || + exception_info_ == (VM_PROT_READ | VM_PROT_EXECUTE))) { + code_1_is_exception_address = false; + } +#endif + + if (code_1_is_exception_address) { + exception_address_ = code[1]; + } else { + exception_address_ = context_.InstructionPointer(); + } + INITIALIZATION_STATE_SET_VALID(initialized_); - return true; } const CPUContext* ExceptionSnapshotIOS::Context() const { @@ -95,12 +237,12 @@ uint64_t ExceptionSnapshotIOS::ThreadID() const { uint32_t ExceptionSnapshotIOS::Exception() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return signal_number_; + return exception_; } uint32_t ExceptionSnapshotIOS::ExceptionInfo() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - return signal_code_; + return exception_info_; } uint64_t ExceptionSnapshotIOS::ExceptionAddress() const { diff --git a/snapshot/ios/exception_snapshot_ios.h b/snapshot/ios/exception_snapshot_ios.h index 3c19a074..5c1337db 100644 --- a/snapshot/ios/exception_snapshot_ios.h +++ b/snapshot/ios/exception_snapshot_ios.h @@ -38,12 +38,25 @@ class ExceptionSnapshotIOS final : public ExceptionSnapshot { ExceptionSnapshotIOS(); ~ExceptionSnapshotIOS() override; - //! \brief Initializes the object. + //! \brief Initializes the object from a signal. //! //! \return `true` if the snapshot could be created, `false` otherwise with //! an appropriate message logged. - bool Initialize(const siginfo_t* siginfo, const ucontext_t* context); + void InitializeFromSignal(const siginfo_t* siginfo, + const ucontext_t* context); + //! \brief Initialize the object from a Mach exception for the current task. + //! + //! \return `true` if the snapshot could be created, `false` otherwise with + //! an appropriate message logged. + void InitializeFromMachException(exception_behavior_t behavior, + thread_t exception_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 state, + mach_msg_type_number_t state_count); // ExceptionSnapshot: const CPUContext* Context() const override; @@ -66,8 +79,8 @@ class ExceptionSnapshotIOS final : public ExceptionSnapshot { std::vector codes_; uint64_t thread_id_; uintptr_t exception_address_; - int signal_number_; - int signal_code_; + uint32_t exception_; + uint32_t exception_info_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ExceptionSnapshotIOS); diff --git a/snapshot/ios/process_snapshot_ios.cc b/snapshot/ios/process_snapshot_ios.cc index 29701994..f45c6ee1 100644 --- a/snapshot/ios/process_snapshot_ios.cc +++ b/snapshot/ios/process_snapshot_ios.cc @@ -102,13 +102,36 @@ bool ProcessSnapshotIOS::Initialize(const IOSSystemDataCollector& system_data) { return true; } -void ProcessSnapshotIOS::SetException(const siginfo_t* siginfo, - const ucontext_t* context) { +void ProcessSnapshotIOS::SetExceptionFromSignal(const siginfo_t* siginfo, + const ucontext_t* context) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK(!exception_.get()); + exception_.reset(new internal::ExceptionSnapshotIOS()); - if (!exception_->Initialize(siginfo, context)) { - exception_.reset(); - } + exception_->InitializeFromSignal(siginfo, context); +} + +void ProcessSnapshotIOS::SetExceptionFromMachException( + exception_behavior_t behavior, + thread_t exception_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) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + DCHECK(!exception_.get()); + + exception_.reset(new internal::ExceptionSnapshotIOS()); + exception_->InitializeFromMachException(behavior, + exception_thread, + exception, + code, + code_count, + flavor, + old_state, + old_state_count); } pid_t ProcessSnapshotIOS::ProcessID() const { diff --git a/snapshot/ios/process_snapshot_ios.h b/snapshot/ios/process_snapshot_ios.h index 2332ff45..36b7ba13 100644 --- a/snapshot/ios/process_snapshot_ios.h +++ b/snapshot/ios/process_snapshot_ios.h @@ -44,7 +44,20 @@ class ProcessSnapshotIOS final : public ProcessSnapshot { //! an appropriate message logged. bool Initialize(const IOSSystemDataCollector& system_data); - void SetException(const siginfo_t* siginfo, const ucontext_t* context); + //! \brief Initialize exception information from a signal. + void SetExceptionFromSignal(const siginfo_t* siginfo, + const ucontext_t* context); + + //! \brief Initialize exception information from a Mach exception. + void SetExceptionFromMachException(exception_behavior_t behavior, + thread_t exception_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); + //! \brief Sets the value to be returned by ClientID(). //! //! On iOS, the client ID is under the control of the snapshot producer, diff --git a/snapshot/ios/thread_snapshot_ios.cc b/snapshot/ios/thread_snapshot_ios.cc index a5e96960..a52be51e 100644 --- a/snapshot/ios/thread_snapshot_ios.cc +++ b/snapshot/ios/thread_snapshot_ios.cc @@ -427,7 +427,12 @@ bool ThreadSnapshotIOS::Initialize(thread_t thread) { #elif defined(ARCH_CPU_ARM64) context_.architecture = kCPUArchitectureARM64; context_.arm64 = &context_arm64_; - InitializeCPUContextARM64(&context_arm64_, &thread_state, &float_state); + InitializeCPUContextARM64(&context_arm64_, + THREAD_STATE_NONE, + nullptr, + 0, + &thread_state, + &float_state); #endif INITIALIZATION_STATE_SET_VALID(initialized_); diff --git a/snapshot/ios/thread_snapshot_ios.h b/snapshot/ios/thread_snapshot_ios.h index c6dde7e6..978a8186 100644 --- a/snapshot/ios/thread_snapshot_ios.h +++ b/snapshot/ios/thread_snapshot_ios.h @@ -33,7 +33,7 @@ class ThreadSnapshotIOS final : public ThreadSnapshot { //! \brief Initializes the object. //! - //! \brief thread The mach thread used to initialize this object. + //! \brief thread The Mach thread used to initialize this object. bool Initialize(thread_t thread); //! \brief Returns an array of thread_t threads. diff --git a/snapshot/mac/cpu_context_mac.cc b/snapshot/mac/cpu_context_mac.cc index acec60ec..03893c40 100644 --- a/snapshot/mac/cpu_context_mac.cc +++ b/snapshot/mac/cpu_context_mac.cc @@ -438,11 +438,11 @@ void InitializeCPUContextX86_64(CPUContextX86_64* context, #elif defined(ARCH_CPU_ARM_FAMILY) -namespace internal { +namespace { -void InitializeCPUContextARM64(CPUContextARM64* context, - const arm_thread_state64_t* arm_thread_state64, - const arm_neon_state64_t* arm_neon_state64) { +void InitializeCPUContextARM64Thread( + CPUContextARM64* context, + const arm_thread_state64_t* arm_thread_state64) { // The structures of context->regs and arm_thread_state64->__x are laid out // identically for this copy, even though the members are organized // differently. Because of this difference, there can't be a static assert @@ -452,7 +452,10 @@ void InitializeCPUContextARM64(CPUContextARM64* context, context->pc = arm_thread_state64->__pc; context->spsr = static_castspsr)>(arm_thread_state64->__cpsr); +} +void InitializeCPUContextARM64Neon(CPUContextARM64* context, + const arm_neon_state64_t* arm_neon_state64) { static_assert(sizeof(context->fpsimd) == sizeof(arm_neon_state64->__v), "fpsimd context size mismatch"); memcpy(context->fpsimd, arm_neon_state64->__v, sizeof(arm_neon_state64->__v)); @@ -460,6 +463,107 @@ void InitializeCPUContextARM64(CPUContextARM64* context, context->fpcr = arm_neon_state64->__fpcr; } +thread_state_flavor_t InitializeCPUContextARM64Flavor( + CPUContextARM64* context, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count) { + mach_msg_type_number_t expected_state_count; + switch (flavor) { + case ARM_THREAD_STATE: + expected_state_count = ARM_THREAD_STATE_COUNT; + break; + case ARM_THREAD_STATE64: + expected_state_count = ARM_THREAD_STATE64_COUNT; + break; + case ARM_NEON_STATE64: + expected_state_count = ARM_NEON_STATE64_COUNT; + break; + case THREAD_STATE_NONE: { + // This may happen without error when called without exception-style + // flavor data, or even from an exception handler when the exception + // behavior is EXCEPTION_DEFAULT. + return flavor; + } + default: + LOG(WARNING) << "unhandled flavor " << flavor; + return THREAD_STATE_NONE; + } + + if (state_count < expected_state_count) { + LOG(WARNING) << "expected state_count " << expected_state_count + << " for flavor " << flavor << ", observed " << state_count; + return THREAD_STATE_NONE; + } + + switch (flavor) { + case ARM_THREAD_STATE: { + const arm_unified_thread_state_t* arm_thread_state = + reinterpret_cast(state); + if (arm_thread_state->ash.flavor != ARM_THREAD_STATE64) { + LOG(WARNING) << "expected flavor ARM_THREAD_STATE64, observed " + << arm_thread_state->ash.flavor; + return THREAD_STATE_NONE; + } + return InitializeCPUContextARM64Flavor( + context, + arm_thread_state->ash.flavor, + reinterpret_cast(&arm_thread_state->ts_64), + arm_thread_state->ash.count); + } + + case ARM_THREAD_STATE64: { + const arm_thread_state64_t* arm_thread_state = + reinterpret_cast(state); + InitializeCPUContextARM64Thread(context, arm_thread_state); + return ARM_THREAD_STATE64; + } + + case ARM_NEON_STATE64: { + const arm_neon_state64_t* arm_neon_state = + reinterpret_cast(state); + InitializeCPUContextARM64Neon(context, arm_neon_state); + return ARM_NEON_STATE64; + } + + case THREAD_STATE_NONE: { + // This may happen without error when called without exception-style + // flavor data, or even from an exception handler when the exception + // behavior is EXCEPTION_DEFAULT. + return flavor; + } + + default: { + NOTREACHED(); + return THREAD_STATE_NONE; + } + } +} + +} // namespace + +namespace internal { + +void InitializeCPUContextARM64(CPUContextARM64* context, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count, + const arm_thread_state64_t* arm_thread_state64, + const arm_neon_state64_t* arm_neon_state64) { + thread_state_flavor_t set_flavor = THREAD_STATE_NONE; + if (flavor != THREAD_STATE_NONE) { + set_flavor = + InitializeCPUContextARM64Flavor(context, flavor, state, state_count); + } + + if (set_flavor != ARM_THREAD_STATE64) { + InitializeCPUContextARM64Thread(context, arm_thread_state64); + } + if (set_flavor != ARM_NEON_STATE64) { + InitializeCPUContextARM64Neon(context, arm_neon_state64); + } +} + } // namespace internal #endif diff --git a/snapshot/mac/cpu_context_mac.h b/snapshot/mac/cpu_context_mac.h index 77aedb6b..05c035a1 100644 --- a/snapshot/mac/cpu_context_mac.h +++ b/snapshot/mac/cpu_context_mac.h @@ -110,13 +110,40 @@ void InitializeCPUContextX86_64(CPUContextX86_64* context, #elif defined(ARCH_CPU_ARM_FAMILY) || DOXYGEN //! \brief Initializes a CPUContextARM64 structure from native context -//! structures. +//! structures on iOS. +//! +//! \a flavor, \a state, and \a state_count may be supplied by exception +//! handlers in order for the \a context parameter to be initialized by the +//! thread state received by the exception handler to the extent possible. In +//! that case, whatever thread state specified by these three parameters will +//! supersede \a arm_thread_state64 or \a arm_neon_state64. If thread state in +//! this format is not available, \a flavor may be set to `THREAD_STATE_NONE`, +//! and all of \a arm_thread_state64 abd \a arm_neon_state64 will be honored. +//! +//! If \a flavor, \a state, and \a state_count are provided but do not contain +//! valid values, a message will be logged and their values will be ignored as +//! though \a flavor were specified as `THREAD_STATE_NONE`. //! //! \param[out] context The CPUContextARM64 structure to initialize. +//! \param[in] flavor The native thread state flavor of \a state. This may be +//! `ARM_THREAD_STATE64`, `ARM_THREAD_STATE` or `ARM_NEON_STATE64`. It may +//! also be `THREAD_STATE_NONE` if \a state is not supplied (and is +//! `nullptr`). +//! \param[in] state The native thread state, which may be a casted pointer to +//! `arm_thread_state64_t`, `arm_unified_thread_state` or +//! `arm_neon_state64_t`. This parameter may be `nullptr` to not supply this +//! data, in which case \a flavor must be `THREAD_STATE_NONE`. If a +//! “universal” structure is used, it must carry 64-bit state data of the +//! correct type. +//! \param[in] state_count The number of `int`-sized units in \a state. This +//! may be 0 if \a state is `nullptr`. //! \param[in] arm_thread_state64 The state of the thread’s integer registers. //! \param[in] arm_neon_state64 The state of the thread’s floating-point //! registers. void InitializeCPUContextARM64(CPUContextARM64* context, + thread_state_flavor_t flavor, + ConstThreadState state, + mach_msg_type_number_t state_count, const arm_thread_state64_t* arm_thread_state64, const arm_neon_state64_t* arm_neon_state64); #endif diff --git a/snapshot/mac/exception_snapshot_mac.cc b/snapshot/mac/exception_snapshot_mac.cc index 50d1a121..8eefac9f 100644 --- a/snapshot/mac/exception_snapshot_mac.cc +++ b/snapshot/mac/exception_snapshot_mac.cc @@ -142,8 +142,13 @@ bool ExceptionSnapshotMac::Initialize(ProcessReaderMac* process_reader, thread_id_ = thread->id; - // Normally, the exception address is present in code[1] for EXC_BAD_ACCESS - // exceptions, but not for other types of exceptions. + // Normally, for EXC_BAD_ACCESS exceptions, the exception address is present + // in code[1]. It may or may not be the instruction pointer address (usually + // it’s not). code[1] may carry the exception address for other exception + // types too, but it’s not guaranteed. But for all other exception types, the + // instruction pointer will be the exception address, and in fact will be + // equal to codes[1] when it’s carrying the exception address. In those cases, + // just use the instruction pointer directly. bool code_1_is_exception_address = exception_ == EXC_BAD_ACCESS; #if defined(ARCH_CPU_X86_FAMILY) diff --git a/test/ios/host/cptest_crash_view_controller.mm b/test/ios/host/cptest_crash_view_controller.mm index 8e49b027..c4677229 100644 --- a/test/ios/host/cptest_crash_view_controller.mm +++ b/test/ios/host/cptest_crash_view_controller.mm @@ -35,8 +35,6 @@ action:@selector(throwUIGestureEnvironmentException)]; [button addGestureRecognizer:tapGesture]; [button setTranslatesAutoresizingMaskIntoConstraints:NO]; - [button.widthAnchor constraintEqualToConstant:16.0].active = YES; - [button.heightAnchor constraintEqualToConstant:16.0].active = YES; [buttonStack addArrangedSubview:button]; diff --git a/util/BUILD.gn b/util/BUILD.gn index f291f5b7..d32863ad 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -305,6 +305,7 @@ static_library("util") { "mach/mach_message.h", "mach/mach_message_server.cc", "mach/mach_message_server.h", + "misc/capture_context_mac.S", "misc/clock_mac.cc", "misc/paths_mac.cc", "synchronization/semaphore_mac.cc", @@ -337,7 +338,6 @@ static_library("util") { "mach/symbolic_constants_mach.h", "mach/task_for_pid.cc", "mach/task_for_pid.h", - "misc/capture_context_mac.S", "net/http_transport_mac.mm", "posix/process_info_mac.cc", "process/process_memory_mac.cc", diff --git a/util/misc/capture_context.h b/util/misc/capture_context.h index a88a1033..3ff71184 100644 --- a/util/misc/capture_context.h +++ b/util/misc/capture_context.h @@ -17,9 +17,7 @@ #include "build/build_config.h" -#if defined(OS_IOS) -#include -#elif defined(OS_MACOSX) +#if defined(OS_MACOSX) #include #elif defined(OS_WIN) #include @@ -31,11 +29,11 @@ namespace crashpad { -#if defined(OS_IOS) -using NativeCPUContext = ucontext_t; -#elif defined(OS_MACOSX) +#if defined(OS_MACOSX) #if defined(ARCH_CPU_X86_FAMILY) using NativeCPUContext = x86_thread_state; +#elif defined(ARCH_CPU_ARM64) +using NativeCPUContext = arm_unified_thread_state; #endif #elif defined(OS_WIN) using NativeCPUContext = CONTEXT;