diff --git a/client/BUILD.gn b/client/BUILD.gn index 5c18c6b0..ad2d71c7 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -35,6 +35,8 @@ crashpad_static_library("client") { "crashpad_client_ios.cc", "ios_handler/exception_processor.h", "ios_handler/exception_processor.mm", + "ios_handler/in_process_handler.cc", + "ios_handler/in_process_handler.h", "ios_handler/in_process_intermediate_dump_handler.cc", "ios_handler/in_process_intermediate_dump_handler.h", "simulate_crash_ios.h", @@ -84,7 +86,6 @@ crashpad_static_library("client") { cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union } - # TODO(justincohen): Temporary dependency to bring up the iOS client. if (crashpad_is_ios) { deps += [ "../handler:common", diff --git a/client/crashpad_client.h b/client/crashpad_client.h index c8016d25..468c8361 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -465,7 +465,8 @@ class CrashpadClient { //! \param[in] database The path to a Crashpad database. //! \param[in] url The URL of an upload server. //! \param[in] annotations Process annotations to set in each crash report. - static void StartCrashpadInProcessHandler( + //! \return `true` on success, `false` on failure with a message logged. + static bool StartCrashpadInProcessHandler( const base::FilePath& database, const std::string& url, const std::map& annotations); @@ -503,7 +504,7 @@ class CrashpadClient { const std::map& annotations = {}); //! \brief Requests that the handler begin in-process uploading of any - //! pending reports. + //! pending reports. //! //! Once called the handler will start looking for pending reports to upload //! on another thread. This method does not block. diff --git a/client/crashpad_client_ios.cc b/client/crashpad_client_ios.cc index b32678d6..9a911dfd 100644 --- a/client/crashpad_client_ios.cc +++ b/client/crashpad_client_ios.cc @@ -23,6 +23,7 @@ #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" @@ -33,6 +34,19 @@ #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 { @@ -50,23 +64,33 @@ class CrashHandler : public Thread, return instance; } - void Initialize() { + bool Initialize(const base::FilePath& database, + const std::string& url, + const std::map& annotations) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); - InstallMachExceptionHandler(); - CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)); + 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& annotations = {}) {} + const std::map& annotations) { + in_process_handler_.ProcessIntermediateDumps(annotations); + } void ProcessIntermediateDump( const base::FilePath& file, - const std::map& annotations = {}) {} + const std::map& annotations) { + in_process_handler_.ProcessIntermediateDump(file, annotations); + } - void DumpWithoutCrash(NativeCPUContext* context) { - INITIALIZATION_STATE_DCHECK_VALID(initialized_); - mach_exception_data_type_t code[2] = {}; + void DumpWithContext(NativeCPUContext* context) { + const mach_exception_data_type_t code[2] = {}; static constexpr int kSimulatedException = -1; HandleMachException(MACH_EXCEPTION_CODES, mach_thread_self(), @@ -78,33 +102,68 @@ class CrashHandler : public Thread, 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; - void InstallMachExceptionHandler() { + bool InstallMachExceptionHandler() { exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); - CHECK(exception_port_.is_valid()); + 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); - MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_right"; + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_port_insert_right"; + return false; + } // TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT. - const exception_mask_t mask = + // Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger. + exception_mask_t mask = ExcMaskAll() & - ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | - EXC_MASK_RPC_ALERT | EXC_MASK_GUARD); + ~(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); - exception_ports.GetExceptionPorts(mask, &original_handlers_); - exception_ports.SetExceptionPort( - mask, - exception_port_.get(), - EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, - MACHINE_THREAD_STATE); + 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: @@ -175,7 +234,45 @@ class CrashHandler : public Thread, thread_state_flavor_t flavor, ConstThreadState old_state, mach_msg_type_number_t old_state_count) { - // TODO(justincohen): This is incomplete. + 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(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. @@ -187,35 +284,16 @@ class CrashHandler : public Thread, void HandleAndReraiseSignal(int signo, siginfo_t* siginfo, ucontext_t* context) { - // TODO(justincohen): This is incomplete. + in_process_handler_.DumpExceptionFromSignal(system_data_, siginfo, context); // Always call system handler. Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_); } - void HandleUncaughtNSException(const uint64_t* frames, - const size_t num_frames) override { - // TODO(justincohen): Call into in_process_handler. - - // 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 { - // TODO(justincohen): Call into in_process_handler. - - // 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)); - } - base::mac::ScopedMachReceiveRight exception_port_; ExceptionPorts::ExceptionHandlerVector original_handlers_; struct sigaction old_action_ = {}; + internal::InProcessHandler in_process_handler_; internal::IOSSystemDataCollector system_data_; InitializationStateDcheck initialized_; }; @@ -227,15 +305,14 @@ CrashpadClient::CrashpadClient() {} CrashpadClient::~CrashpadClient() {} // static -void CrashpadClient::StartCrashpadInProcessHandler( +bool CrashpadClient::StartCrashpadInProcessHandler( const base::FilePath& database, const std::string& url, const std::map& annotations) { - CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); InstallObjcExceptionPreprocessor(crash_handler); - crash_handler->Initialize(); + return crash_handler->Initialize(database, url, annotations); } // static @@ -257,17 +334,16 @@ void CrashpadClient::ProcessIntermediateDump( // static void CrashpadClient::StartProcessingPendingReports() { - // TODO(justincohen): Start the CrashReportUploadThread. + 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); - // TODO(justincohen): Change this to only process the dump from above, not all - // intermediate dump files. - crash_handler->ProcessIntermediateDumps(); + crash_handler->DumpWithoutCrash(context, /*process_dump=*/true); } // static @@ -275,7 +351,7 @@ void CrashpadClient::DumpWithoutCrashAndDeferProcessing( NativeCPUContext* context) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); - crash_handler->DumpWithoutCrash(context); + crash_handler->DumpWithoutCrash(context, /*process_dump=*/false); } // static @@ -284,8 +360,7 @@ void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath( const base::FilePath path) { CrashHandler* crash_handler = CrashHandler::Get(); DCHECK(crash_handler); - // TODO(justincohen): Change to DumpWithoutCrashAtPath(context, path). - crash_handler->DumpWithoutCrash(context); + crash_handler->DumpWithoutCrashAtPath(context, path); } } // namespace crashpad diff --git a/client/crashpad_client_ios_test.mm b/client/crashpad_client_ios_test.mm index 30fabae0..3c350e9f 100644 --- a/client/crashpad_client_ios_test.mm +++ b/client/crashpad_client_ios_test.mm @@ -18,6 +18,9 @@ #include +#include "base/strings/sys_string_conversions.h" +#include "client/crash_report_database.h" +#include "client/ios_handler/exception_processor.h" #include "client/simulate_crash.h" #include "gtest/gtest.h" #include "test/scoped_temp_dir.h" @@ -32,9 +35,53 @@ using CrashpadIOSClient = PlatformTest; TEST_F(CrashpadIOSClient, DumpWithoutCrash) { CrashpadClient client; ScopedTempDir database_dir; - client.StartCrashpadInProcessHandler( - base::FilePath(database_dir.path()), "", {}); + ASSERT_TRUE(client.StartCrashpadInProcessHandler( + base::FilePath(database_dir.path()), "", {})); + std::unique_ptr database = + CrashReportDatabase::Initialize(database_dir.path()); + std::vector reports; + EXPECT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 0u); CRASHPAD_SIMULATE_CRASH(); + reports.clear(); + EXPECT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 1u); + + CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING(); + reports.clear(); + EXPECT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 1u); + client.ProcessIntermediateDumps(); + reports.clear(); + EXPECT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 2u); + + ScopedTempDir crash_dir; + UUID uuid; + uuid.InitializeWithNew(); + CRASHPAD_SIMULATE_CRASH_AND_DEFER_PROCESSING_AT_PATH( + crash_dir.path().Append(uuid.ToString())); + reports.clear(); + EXPECT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 2u); + + NSError* error = nil; + NSArray* paths = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:base::SysUTF8ToNSString( + crash_dir.path().value()) + error:&error]; + ASSERT_EQ([paths count], 1u); + client.ProcessIntermediateDump( + crash_dir.path().Append([paths[0] fileSystemRepresentation])); + reports.clear(); + EXPECT_EQ(database->GetPendingReports(&reports), + CrashReportDatabase::kNoError); + ASSERT_EQ(reports.size(), 3u); } // This test is covered by a similar XCUITest, but for development purposes it's @@ -44,8 +91,8 @@ TEST_F(CrashpadIOSClient, DumpWithoutCrash) { TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) { CrashpadClient client; ScopedTempDir database_dir; - client.StartCrashpadInProcessHandler( - base::FilePath(database_dir.path()), "", {}); + ASSERT_TRUE(client.StartCrashpadInProcessHandler( + base::FilePath(database_dir.path()), "", {})); [NSException raise:@"GoogleTestNSException" format:@"ThrowException"]; } @@ -56,8 +103,8 @@ TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) { TEST_F(CrashpadIOSClient, DISABLED_ThrowException) { CrashpadClient client; ScopedTempDir database_dir; - client.StartCrashpadInProcessHandler( - base::FilePath(database_dir.path()), "", {}); + ASSERT_TRUE(client.StartCrashpadInProcessHandler( + base::FilePath(database_dir.path()), "", {})); std::vector empty_vector; empty_vector.at(42); } diff --git a/client/ios_handler/in_process_handler.cc b/client/ios_handler/in_process_handler.cc new file mode 100644 index 00000000..85fd422e --- /dev/null +++ b/client/ios_handler/in_process_handler.cc @@ -0,0 +1,298 @@ +// Copyright 2021 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/ios_handler/in_process_handler.h" + +#include +#include + +#include "base/logging.h" +#include "client/ios_handler/in_process_intermediate_dump_handler.h" +#include "client/settings.h" +#include "minidump/minidump_file_writer.h" +#include "util/file/directory_reader.h" +#include "util/file/filesystem.h" + +namespace { + +// Creates directory at |path|. +void CreateDirectory(const base::FilePath& path) { + if (mkdir(path.value().c_str(), 0755) == 0) { + return; + } + if (errno != EEXIST) { + PLOG(ERROR) << "mkdir " << path.value(); + } +} + +} // namespace + +namespace crashpad { +namespace internal { + +InProcessHandler::InProcessHandler() = default; + +InProcessHandler::~InProcessHandler() { + upload_thread_->Stop(); +} + +bool InProcessHandler::Initialize( + const base::FilePath& database, + const std::string& url, + const std::map& annotations) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + annotations_ = annotations; + database_ = CrashReportDatabase::Initialize(database); + + if (!url.empty()) { + // TODO(scottmg): options.rate_limit should be removed when we have a + // configurable database setting to control upload limiting. + // See https://crashpad.chromium.org/bug/23. + CrashReportUploadThread::Options upload_thread_options; + upload_thread_options.rate_limit = false; + upload_thread_options.upload_gzip = true; + upload_thread_options.watch_pending_reports = true; + upload_thread_options.identify_client_via_url = true; + + upload_thread_.reset(new CrashReportUploadThread( + database_.get(), url, upload_thread_options)); + } + + CreateDirectory(database); + static constexpr char kPendingSerializediOSDump[] = + "pending-serialized-ios-dump"; + base_dir_ = database.Append(kPendingSerializediOSDump); + CreateDirectory(base_dir_); + + if (!OpenNewFile()) + return false; + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +void InProcessHandler::DumpExceptionFromSignal( + const IOSSystemDataCollector& system_data, + siginfo_t* siginfo, + ucontext_t* context) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + { + ScopedReport report(writer_.get(), system_data); + InProcessIntermediateDumpHandler::WriteExceptionFromSignal( + writer_.get(), system_data, siginfo, context); + } + PostReportCleanup(); +} + +void InProcessHandler::DumpExceptionFromMachException( + const IOSSystemDataCollector& system_data, + 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) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + { + ScopedReport report(writer_.get(), system_data); + InProcessIntermediateDumpHandler::WriteExceptionFromMachException( + writer_.get(), + behavior, + thread, + exception, + code, + code_count, + flavor, + old_state, + old_state_count); + } + PostReportCleanup(); +} + +void InProcessHandler::DumpExceptionFromNSExceptionFrames( + const IOSSystemDataCollector& system_data, + const uint64_t* frames, + const size_t num_frames) { + { + ScopedReport report(writer_.get(), system_data, frames, num_frames); + InProcessIntermediateDumpHandler::WriteExceptionFromNSException( + writer_.get()); + } + PostReportCleanup(); +} + +void InProcessHandler::ProcessIntermediateDumps( + const std::map& extra_annotations) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + std::map annotations(annotations_); + annotations.insert(extra_annotations.begin(), extra_annotations.end()); + + for (auto& file : PendingFiles()) + ProcessIntermediateDumpWithCompleteAnnotations(file, annotations); +} + +void InProcessHandler::ProcessIntermediateDump( + const base::FilePath& file, + const std::map& extra_annotations) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + std::map annotations(annotations_); + annotations.insert(extra_annotations.begin(), extra_annotations.end()); + ProcessIntermediateDumpWithCompleteAnnotations(file, annotations); +} + +void InProcessHandler::StartProcessingPendingReports() { + if (!upload_thread_started_ && upload_thread_) { + upload_thread_->Start(); + upload_thread_started_ = true; + } +} + +void InProcessHandler::ProcessIntermediateDumpWithCompleteAnnotations( + const base::FilePath& file, + const std::map& annotations) { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + + ProcessSnapshotIOSIntermediateDump process_snapshot; + if (process_snapshot.Initialize(file, annotations)) { + SaveSnapshot(process_snapshot); + } +} + +void InProcessHandler::SaveSnapshot( + ProcessSnapshotIOSIntermediateDump& process_snapshot) { + std::unique_ptr new_report; + CrashReportDatabase::OperationStatus database_status = + database_->PrepareNewCrashReport(&new_report); + if (database_status != CrashReportDatabase::kNoError) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kPrepareNewCrashReportFailed); + } + process_snapshot.SetReportID(new_report->ReportID()); + + MinidumpFileWriter minidump; + minidump.InitializeFromSnapshot(&process_snapshot); + if (!minidump.WriteEverything(new_report->Writer())) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kMinidumpWriteFailed); + } + UUID uuid; + database_status = + database_->FinishedWritingCrashReport(std::move(new_report), &uuid); + if (database_status != CrashReportDatabase::kNoError) { + Metrics::ExceptionCaptureResult( + Metrics::CaptureResult::kFinishedWritingCrashReportFailed); + } + + if (upload_thread_) { + upload_thread_->ReportPending(uuid); + } +} + +std::vector InProcessHandler::PendingFiles() { + DirectoryReader reader; + std::vector files; + if (!reader.Open(base_dir_)) { + return files; + } + base::FilePath file; + DirectoryReader::Result result; + while ((result = reader.NextFile(&file)) == + DirectoryReader::Result::kSuccess) { + file = base_dir_.Append(file); + if (file != current_file_) { + ScopedFileHandle fd(LoggingOpenFileForRead(file)); + if (LoggingLockFile(fd.get(), + FileLocking::kExclusive, + FileLockingBlocking::kNonBlocking) == + FileLockingResult::kSuccess) { + files.push_back(file); + } + } + } + return files; +} + +InProcessHandler::ScopedAlternateWriter::ScopedAlternateWriter( + InProcessHandler* handler) + : handler_(handler) {} + +bool InProcessHandler::ScopedAlternateWriter::Open() { + UUID uuid; + uuid.InitializeWithNew(); + const std::string uuid_string = uuid.ToString(); + return OpenAtPath(handler_->base_dir_.Append(uuid_string)); +} + +bool InProcessHandler::ScopedAlternateWriter::OpenAtPath( + const base::FilePath& path) { + path_ = path; + handler_->SetOpenNewFileAfterReport(false); + original_writer_ = handler_->GetWriter(); + auto writer = std::make_unique(); + if (!writer->Open(path_)) { + DLOG(ERROR) << "Unable to open alternate intermediate dump file: " + << path_.value(); + return false; + } + handler_->SetWriter(std::move(writer)); + return true; +} + +InProcessHandler::ScopedAlternateWriter::~ScopedAlternateWriter() { + handler_->SetWriter(std::move(original_writer_)); + handler_->SetOpenNewFileAfterReport(true); +} + +InProcessHandler::ScopedReport::ScopedReport( + IOSIntermediateDumpWriter* writer, + const IOSSystemDataCollector& system_data, + const uint64_t* frames, + const size_t num_frames) + : rootMap_(writer) { + InProcessIntermediateDumpHandler::WriteHeader(writer); + InProcessIntermediateDumpHandler::WriteProcessInfo(writer); + InProcessIntermediateDumpHandler::WriteSystemInfo(writer, system_data); + InProcessIntermediateDumpHandler::WriteThreadInfo(writer, frames, num_frames); + InProcessIntermediateDumpHandler::WriteModuleInfo(writer); +} + +bool InProcessHandler::OpenNewFile() { + UUID uuid; + uuid.InitializeWithNew(); + const std::string uuid_string = uuid.ToString(); + current_file_ = base_dir_.Append(uuid_string); + writer_ = std::make_unique(); + if (!writer_->Open(current_file_)) { + DLOG(ERROR) << "Unable to open intermediate dump file: " + << current_file_.value(); + return false; + } + return true; +} + +void InProcessHandler::PostReportCleanup() { + if (writer_) { + writer_->Close(); + writer_.reset(); + } + if (open_new_file_after_report_) + OpenNewFile(); +} + +} // namespace internal +} // namespace crashpad diff --git a/client/ios_handler/in_process_handler.h b/client/ios_handler/in_process_handler.h new file mode 100644 index 00000000..877ae19c --- /dev/null +++ b/client/ios_handler/in_process_handler.h @@ -0,0 +1,199 @@ +// Copyright 2021 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 + +#include +#include +#include + +#include "base/files/file_path.h" +#include "handler/mac/crash_report_exception_handler.h" +#include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" +#include "util/ios/ios_intermediate_dump_writer.h" +#include "util/ios/ios_system_data_collector.h" +#include "util/misc/initialization_state_dcheck.h" + +namespace crashpad { +namespace internal { + +//! \brief Manage intermediate minidump generation, and own the crash report +//! upload thread and database. +class InProcessHandler { + public: + InProcessHandler(); + ~InProcessHandler(); + InProcessHandler(const InProcessHandler&) = delete; + InProcessHandler& operator=(const InProcessHandler&) = delete; + + //! \brief Initializes the in-process handler. + //! + //! This method must be called only once, and must be successfully called + //! before any other method in this class may be called. + //! + //! \param[in] database The path to a Crashpad database. + //! \param[in] url The URL of an upload server. + //! \param[in] annotations Process annotations to set in each crash report. + //! \return `true` if a handler to a pending intermediate dump could be + //! opened. + bool Initialize(const base::FilePath& database, + const std::string& url, + const std::map& annotations); + + //! \brief Generate an intermediate dump from a signal handler exception. + //! + //! \param[in] system_data An object containing various system data points. + //! \param[in] siginfo A pointer to a `siginfo_t` object received by a signal + //! handler. + //! \param[in] context A pointer to a `ucontext_t` object received by a + //! signal. + void DumpExceptionFromSignal(const IOSSystemDataCollector& system_data, + siginfo_t* siginfo, + ucontext_t* context); + + //! \brief Generate an intermediate dump from a mach exception. + //! + //! \param[in] system_data An object containing various system data points. + //! \param[in] behavior + //! \param[in] thread + //! \param[in] exception + //! \param[in] code + //! \param[in] code_count + //! \param[in,out] flavor + //! \param[in] old_state + //! \param[in] old_state_count + void DumpExceptionFromMachException(const IOSSystemDataCollector& system_data, + 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); + + //! \brief Generate an intermediate dump from an uncaught NSException. + //! + //! When the ObjcExceptionPreprocessor does not detect an NSException as it is + //! thrown, the last-chance uncaught exception handler passes a list of call + //! stack frame addresses. Record them in the intermediate dump so a minidump + //! with a 'fake' call stack is generated. + //! + //! \param[in] system_data An object containing various system data points. + //! \param[in] frames An array of call stack frame addresses. + //! \param[in] num_frames The number of frames in |frames|. + void DumpExceptionFromNSExceptionFrames( + const IOSSystemDataCollector& system_data, + const uint64_t* frames, + const size_t num_frames); + + //! \brief Requests that the handler convert all intermediate dumps into + //! minidumps and trigger an upload if possible. + //! + //! \param[in] annotations Process annotations to set in each crash report. + void ProcessIntermediateDumps( + const std::map& annotations); + + //! \brief Requests that the handler convert a specific intermediate dump into + //! a minidump and trigger an upload if possible. + //! + //! \param[in] path Path to the specific intermediate dump. + //! \param[in] annotations Process annotations to set in each crash report. + void ProcessIntermediateDump( + const base::FilePath& path, + const std::map& annotations = {}); + + //! \brief Requests that the handler begin in-process uploading of any + //! pending reports. + void StartProcessingPendingReports(); + + //! \brief Helper that swaps out the InProcessHandler's |writer_| with an + //! alternate writer so DumpWithContext does not interfere with the + //! |writer_| created on startup. This is useful for -DumpWithoutCrash, + //! which may write to an alternate location. + class ScopedAlternateWriter { + public: + ScopedAlternateWriter(InProcessHandler* handler); + ~ScopedAlternateWriter(); + ScopedAlternateWriter(const ScopedAlternateWriter&) = delete; + ScopedAlternateWriter& operator=(const ScopedAlternateWriter&) = delete; + //! \brief Open's an alternate dump writer in the same directory as the + //! default InProcessHandler's dump writer, so the file will be + //! processed with -ProcessIntermediateDumps() + bool Open(); + + //! \brief Open's an alternate dump writer in the client provided |path|. + //! The file will only be processed by calling + //! ProcessIntermediateDump(path) + bool OpenAtPath(const base::FilePath& path); + + //! \brief The path of the alternate dump writer. + const base::FilePath& path() { return path_; } + + private: + InProcessHandler* handler_; + std::unique_ptr original_writer_; + base::FilePath path_; + }; + + private: + //! \brief Helper to start and end intermediate reports. + class ScopedReport { + public: + ScopedReport(IOSIntermediateDumpWriter* writer, + const IOSSystemDataCollector& system_data, + const uint64_t* frames = nullptr, + const size_t num_frames = 0); + ~ScopedReport() {} + ScopedReport(const ScopedReport&) = delete; + ScopedReport& operator=(const ScopedReport&) = delete; + + private: + IOSIntermediateDumpWriter::ScopedRootMap rootMap_; + }; + + std::unique_ptr GetWriter() { + return std::move(writer_); + } + + void SetWriter(std::unique_ptr writer) { + writer_ = std::move(writer); + } + + void SetOpenNewFileAfterReport(bool open_new_file_after_report) { + open_new_file_after_report_ = open_new_file_after_report; + } + + void ProcessIntermediateDumpWithCompleteAnnotations( + const base::FilePath& file, + const std::map& annotations); + void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot); + std::vector PendingFiles(); + bool OpenNewFile(); + void PostReportCleanup(); + + bool upload_thread_started_ = false; + bool open_new_file_after_report_ = true; + std::map annotations_; + base::FilePath base_dir_; + base::FilePath current_file_; + std::unique_ptr writer_; + std::unique_ptr alternate_mach_writer_; + std::unique_ptr upload_thread_; + std::unique_ptr database_; + InitializationStateDcheck initialized_; +}; + +} // namespace internal +} // namespace crashpad diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index b7e445fd..60f172b6 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -68,7 +68,8 @@ CrashReportUploadThread::~CrashReportUploadThread() { void CrashReportUploadThread::ReportPending(const UUID& report_uuid) { known_pending_report_uuids_.PushBack(report_uuid); - thread_.DoWorkNow(); + if (thread_.is_running()) + thread_.DoWorkNow(); } void CrashReportUploadThread::Start() { diff --git a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc index cd604510..5ad02076 100644 --- a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc +++ b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc @@ -200,8 +200,7 @@ bool ExceptionSnapshotIOSIntermediateDump::InitializeFromNSException( INITIALIZATION_STATE_SET_INITIALIZING(initialized_); DCHECK(exception_data); - exception_ = EXC_SOFTWARE; - exception_info_ = 0xDEADC0DE; /* uncaught NSException */ + exception_ = kMachExceptionFromNSException; if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) { LOG(ERROR) << "Exceptions require a thread id."; diff --git a/util/BUILD.gn b/util/BUILD.gn index affa4e9e..3e16b374 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -397,6 +397,7 @@ crashpad_static_library("util") { "ios/raw_logging.h", "ios/scoped_vm_read.cc", "ios/scoped_vm_read.h", + "net/http_transport_mac.mm", ] } @@ -659,8 +660,8 @@ if (!crashpad_is_android && !crashpad_is_ios) { deps = [ ":util", - "../third_party/cpp-httplib", "$mini_chromium_source_parent:base", + "../third_party/cpp-httplib", "../third_party/zlib", "../tools:tool_support", ] @@ -867,12 +868,12 @@ source_set("util_test") { deps = [ ":util", + "$mini_chromium_source_parent:base", "../client", "../compat", "../test", "../third_party/googletest:googlemock", "../third_party/googletest:googletest", - "$mini_chromium_source_parent:base", "../third_party/zlib", ] diff --git a/util/mach/mach_extensions.h b/util/mach/mach_extensions.h index 9247ce94..ebf36d3d 100644 --- a/util/mach/mach_extensions.h +++ b/util/mach/mach_extensions.h @@ -47,6 +47,9 @@ constexpr exception_behavior_t kMachExceptionCodes = MACH_EXCEPTION_CODES; //! \brief An exception type to use for simulated exceptions. constexpr exception_type_t kMachExceptionSimulated = 'CPsx'; +//! \brief An exception type to use for uncaught NSExceptions. +constexpr exception_type_t kMachExceptionFromNSException = 'CPnx'; + //! \brief A const version of `thread_state_t`. //! //! This is useful as the \a old_state parameter to exception handler functions.