ios: Add iOS in-process handler for managing minidump generation.
Manage the intermediate minidump generation, and own the crash report
upload thread and database.
Change-Id: Ie2d6fe504c0fe4fdcd9326e6b46569e14da3ba19
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3087721
Commit-Queue: Justin Cohen <justincohen@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
GitOrigin-RevId: 204abe16d225fc85779623063a8c4c85b707d577
diff --git a/client/BUILD.gn b/client/BUILD.gn
index 5c18c6b..ad2d71c 100644
--- a/client/BUILD.gn
+++ b/client/BUILD.gn
@@ -35,6 +35,8 @@
"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 @@
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 c8016d2..468c836 100644
--- a/client/crashpad_client.h
+++ b/client/crashpad_client.h
@@ -465,7 +465,8 @@
//! \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<std::string, std::string>& annotations);
@@ -503,7 +504,7 @@
const std::map<std::string, std::string>& 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 b32678d..9a911df 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 @@
return instance;
}
- void Initialize() {
+ bool Initialize(const base::FilePath& database,
+ const std::string& url,
+ const std::map<std::string, std::string>& 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<std::string, std::string>& annotations = {}) {}
+ 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 = {}) {}
+ const std::map<std::string, std::string>& 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 @@
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 @@
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<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.
@@ -187,35 +284,16 @@
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() {}
// static
-void CrashpadClient::StartCrashpadInProcessHandler(
+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);
- crash_handler->Initialize();
+ return crash_handler->Initialize(database, url, annotations);
}
// static
@@ -257,17 +334,16 @@
// 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 @@
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 @@
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 30fabae..3c350e9 100644
--- a/client/crashpad_client_ios_test.mm
+++ b/client/crashpad_client_ios_test.mm
@@ -18,6 +18,9 @@
#include <vector>
+#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 @@
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<CrashReportDatabase> database =
+ CrashReportDatabase::Initialize(database_dir.path());
+ std::vector<CrashReportDatabase::Report> 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, 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_ThrowException) {
CrashpadClient client;
ScopedTempDir database_dir;
- client.StartCrashpadInProcessHandler(
- base::FilePath(database_dir.path()), "", {});
+ ASSERT_TRUE(client.StartCrashpadInProcessHandler(
+ base::FilePath(database_dir.path()), "", {}));
std::vector<int> 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 0000000..85fd422
--- /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 <stdio.h>
+#include <sys/stat.h>
+
+#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<std::string, std::string>& 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<std::string, std::string>& extra_annotations) {
+ INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+ std::map<std::string, std::string> 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<std::string, std::string>& extra_annotations) {
+ INITIALIZATION_STATE_DCHECK_VALID(initialized_);
+
+ std::map<std::string, std::string> 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<std::string, std::string>& 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<CrashReportDatabase::NewReport> 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<base::FilePath> InProcessHandler::PendingFiles() {
+ DirectoryReader reader;
+ std::vector<base::FilePath> 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<IOSIntermediateDumpWriter>();
+ 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<IOSIntermediateDumpWriter>();
+ 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 0000000..877ae19
--- /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 <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#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<std::string, std::string>& 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<std::string, std::string>& 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<std::string, std::string>& 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<IOSIntermediateDumpWriter> 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<IOSIntermediateDumpWriter> GetWriter() {
+ return std::move(writer_);
+ }
+
+ void SetWriter(std::unique_ptr<IOSIntermediateDumpWriter> 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<std::string, std::string>& annotations);
+ void SaveSnapshot(ProcessSnapshotIOSIntermediateDump& process_snapshot);
+ std::vector<base::FilePath> PendingFiles();
+ bool OpenNewFile();
+ void PostReportCleanup();
+
+ bool upload_thread_started_ = false;
+ bool open_new_file_after_report_ = true;
+ std::map<std::string, std::string> annotations_;
+ base::FilePath base_dir_;
+ base::FilePath current_file_;
+ std::unique_ptr<IOSIntermediateDumpWriter> writer_;
+ std::unique_ptr<IOSIntermediateDumpWriter> alternate_mach_writer_;
+ std::unique_ptr<CrashReportUploadThread> upload_thread_;
+ std::unique_ptr<CrashReportDatabase> database_;
+ InitializationStateDcheck initialized_;
+};
+
+} // namespace internal
+} // namespace crashpad
diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc
index b7e445f..60f172b 100644
--- a/handler/crash_report_upload_thread.cc
+++ b/handler/crash_report_upload_thread.cc
@@ -68,7 +68,8 @@
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 cd60451..5ad0207 100644
--- a/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
+++ b/snapshot/ios/exception_snapshot_ios_intermediate_dump.cc
@@ -200,8 +200,7 @@
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 affa4e9..3e16b37 100644
--- a/util/BUILD.gn
+++ b/util/BUILD.gn
@@ -397,6 +397,7 @@
"ios/raw_logging.h",
"ios/scoped_vm_read.cc",
"ios/scoped_vm_read.h",
+ "net/http_transport_mac.mm",
]
}
@@ -659,8 +660,8 @@
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 @@
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 9247ce9..ebf36d3 100644
--- a/util/mach/mach_extensions.h
+++ b/util/mach/mach_extensions.h
@@ -47,6 +47,9 @@
//! \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.