| // Copyright 2020 The Crashpad Authors. All rights reserved. |
| // |
| // 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 "client/crashpad_client.h" |
| |
| #include <unistd.h> |
| |
| #include <ios> |
| |
| #include "base/cxx17_backports.h" |
| #include "base/logging.h" |
| #include "base/mac/mach_logging.h" |
| #include "base/mac/scoped_mach_port.h" |
| #include "client/ios_handler/exception_processor.h" |
| #include "client/ios_handler/in_process_handler.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 { |
| |
| 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, base::size(mib), &kern_proc_info, &len, nullptr, 0) == 0) |
| return kern_proc_info.kp_proc.p_flag & P_TRACED; |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace crashpad { |
| |
| namespace { |
| |
| // 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() { |
| static CrashHandler* instance = new CrashHandler(); |
| return instance; |
| } |
| |
| bool Initialize(const base::FilePath& database, |
| const std::string& url, |
| const std::map<std::string, std::string>& annotations) { |
| INITIALIZATION_STATE_SET_INITIALIZING(initialized_); |
| if (!in_process_handler_.Initialize(database, url, annotations) || |
| !InstallMachExceptionHandler() || |
| !Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)) { |
| LOG(ERROR) << "Unable to initialize Crashpad."; |
| return false; |
| } |
| INITIALIZATION_STATE_SET_VALID(initialized_); |
| return true; |
| } |
| |
| void ProcessIntermediateDumps( |
| const std::map<std::string, std::string>& annotations) { |
| in_process_handler_.ProcessIntermediateDumps(annotations); |
| } |
| |
| void ProcessIntermediateDump( |
| const base::FilePath& file, |
| const std::map<std::string, std::string>& annotations) { |
| in_process_handler_.ProcessIntermediateDump(file, annotations); |
| } |
| |
| void DumpWithContext(NativeCPUContext* context) { |
| const 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<ConstThreadState>(context), |
| MACHINE_THREAD_STATE_COUNT); |
| } |
| |
| void DumpWithoutCrash(NativeCPUContext* context, bool process_dump) { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| internal::InProcessHandler::ScopedAlternateWriter scoper( |
| &in_process_handler_); |
| if (scoper.Open()) { |
| DumpWithContext(context); |
| if (process_dump) { |
| in_process_handler_.ProcessIntermediateDump(scoper.path()); |
| } |
| } |
| } |
| |
| void DumpWithoutCrashAtPath(NativeCPUContext* context, |
| const base::FilePath& path) { |
| internal::InProcessHandler::ScopedAlternateWriter scoper( |
| &in_process_handler_); |
| if (scoper.OpenAtPath(path)) |
| DumpWithContext(context); |
| } |
| |
| void StartProcessingPendingReports() { |
| INITIALIZATION_STATE_DCHECK_VALID(initialized_); |
| in_process_handler_.StartProcessingPendingReports(); |
| } |
| |
| private: |
| CrashHandler() = default; |
| |
| bool InstallMachExceptionHandler() { |
| exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); |
| if (!exception_port_.is_valid()) { |
| return false; |
| } |
| |
| kern_return_t kr = mach_port_insert_right(mach_task_self(), |
| exception_port_.get(), |
| exception_port_.get(), |
| 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_.get(), |
| EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, |
| MACHINE_THREAD_STATE)) { |
| return false; |
| } |
| |
| Start(); |
| return true; |
| } |
| |
| // 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) { |
| in_process_handler_.DumpExceptionFromMachException(system_data_, |
| 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_.DumpExceptionFromNSExceptionFrames( |
| system_data_, 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 { |
| const mach_exception_data_type_t code[2] = {0, 0}; |
| in_process_handler_.DumpExceptionFromMachException( |
| system_data_, |
| MACH_EXCEPTION_CODES, |
| mach_thread_self(), |
| kMachExceptionFromNSException, |
| code, |
| base::size(code), |
| MACHINE_THREAD_STATE, |
| reinterpret_cast<ConstThreadState>(context), |
| MACHINE_THREAD_STATE_COUNT); |
| |
| // 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)); |
| } |
| |
| // The signal handler installed at OS-level. |
| static void CatchSignal(int signo, siginfo_t* siginfo, void* context) { |
| Get()->HandleAndReraiseSignal( |
| signo, siginfo, reinterpret_cast<ucontext_t*>(context)); |
| } |
| |
| void HandleAndReraiseSignal(int signo, |
| siginfo_t* siginfo, |
| ucontext_t* context) { |
| in_process_handler_.DumpExceptionFromSignal(system_data_, siginfo, context); |
| |
| // Always call system handler. |
| Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_); |
| } |
| |
| base::mac::ScopedMachReceiveRight exception_port_; |
| ExceptionPorts::ExceptionHandlerVector original_handlers_; |
| struct sigaction old_action_ = {}; |
| internal::InProcessHandler in_process_handler_; |
| internal::IOSSystemDataCollector system_data_; |
| InitializationStateDcheck initialized_; |
| }; |
| |
| } // namespace |
| |
| CrashpadClient::CrashpadClient() {} |
| |
| CrashpadClient::~CrashpadClient() {} |
| |
| // static |
| bool CrashpadClient::StartCrashpadInProcessHandler( |
| const base::FilePath& database, |
| const std::string& url, |
| const std::map<std::string, std::string>& annotations) { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| InstallObjcExceptionPreprocessor(crash_handler); |
| return crash_handler->Initialize(database, url, annotations); |
| } |
| |
| // static |
| void CrashpadClient::ProcessIntermediateDumps( |
| const std::map<std::string, std::string>& annotations) { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| crash_handler->ProcessIntermediateDumps(annotations); |
| } |
| |
| // static |
| void CrashpadClient::ProcessIntermediateDump( |
| const base::FilePath& file, |
| const std::map<std::string, std::string>& annotations) { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| crash_handler->ProcessIntermediateDump(file, annotations); |
| } |
| |
| // static |
| void CrashpadClient::StartProcessingPendingReports() { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| crash_handler->StartProcessingPendingReports(); |
| } |
| |
| // static |
| void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| crash_handler->DumpWithoutCrash(context, /*process_dump=*/true); |
| } |
| |
| // static |
| void CrashpadClient::DumpWithoutCrashAndDeferProcessing( |
| NativeCPUContext* context) { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| crash_handler->DumpWithoutCrash(context, /*process_dump=*/false); |
| } |
| |
| // static |
| void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath( |
| NativeCPUContext* context, |
| const base::FilePath path) { |
| CrashHandler* crash_handler = CrashHandler::Get(); |
| DCHECK(crash_handler); |
| crash_handler->DumpWithoutCrashAtPath(context, path); |
| } |
| |
| } // namespace crashpad |