// 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