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: I272d790a827cd13f6872e56f4675f366d13719c5
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>
This commit is contained in:
Justin Cohen 2021-10-14 10:55:03 -04:00 committed by Crashpad LUCI CQ
parent 07a6b70755
commit 204abe16d2
10 changed files with 692 additions and 67 deletions

View File

@ -35,6 +35,8 @@ crashpad_static_library("client") {
"crashpad_client_ios.cc", "crashpad_client_ios.cc",
"ios_handler/exception_processor.h", "ios_handler/exception_processor.h",
"ios_handler/exception_processor.mm", "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.cc",
"ios_handler/in_process_intermediate_dump_handler.h", "ios_handler/in_process_intermediate_dump_handler.h",
"simulate_crash_ios.h", "simulate_crash_ios.h",
@ -84,7 +86,6 @@ crashpad_static_library("client") {
cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union cflags = [ "/wd4201" ] # nonstandard extension used : nameless struct/union
} }
# TODO(justincohen): Temporary dependency to bring up the iOS client.
if (crashpad_is_ios) { if (crashpad_is_ios) {
deps += [ deps += [
"../handler:common", "../handler:common",

View File

@ -465,7 +465,8 @@ class CrashpadClient {
//! \param[in] database The path to a Crashpad database. //! \param[in] database The path to a Crashpad database.
//! \param[in] url The URL of an upload server. //! \param[in] url The URL of an upload server.
//! \param[in] annotations Process annotations to set in each crash report. //! \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 base::FilePath& database,
const std::string& url, const std::string& url,
const std::map<std::string, std::string>& annotations); const std::map<std::string, std::string>& annotations);
@ -503,7 +504,7 @@ class CrashpadClient {
const std::map<std::string, std::string>& annotations = {}); const std::map<std::string, std::string>& annotations = {});
//! \brief Requests that the handler begin in-process uploading of any //! \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 //! Once called the handler will start looking for pending reports to upload
//! on another thread. This method does not block. //! on another thread. This method does not block.

View File

@ -23,6 +23,7 @@
#include "base/mac/mach_logging.h" #include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h" #include "base/mac/scoped_mach_port.h"
#include "client/ios_handler/exception_processor.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/ios/ios_system_data_collector.h"
#include "util/mach/exc_server_variants.h" #include "util/mach/exc_server_variants.h"
#include "util/mach/exception_ports.h" #include "util/mach/exception_ports.h"
@ -33,6 +34,19 @@
#include "util/posix/signals.h" #include "util/posix/signals.h"
#include "util/thread/thread.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 crashpad {
namespace { namespace {
@ -50,23 +64,33 @@ class CrashHandler : public Thread,
return instance; 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_); INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
InstallMachExceptionHandler(); if (!in_process_handler_.Initialize(database, url, annotations) ||
CHECK(Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)); !InstallMachExceptionHandler() ||
!Signals::InstallHandler(SIGABRT, CatchSignal, 0, &old_action_)) {
LOG(ERROR) << "Unable to initialize Crashpad.";
return false;
}
INITIALIZATION_STATE_SET_VALID(initialized_); INITIALIZATION_STATE_SET_VALID(initialized_);
return true;
} }
void ProcessIntermediateDumps( 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( void ProcessIntermediateDump(
const base::FilePath& file, 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) { void DumpWithContext(NativeCPUContext* context) {
INITIALIZATION_STATE_DCHECK_VALID(initialized_); const mach_exception_data_type_t code[2] = {};
mach_exception_data_type_t code[2] = {};
static constexpr int kSimulatedException = -1; static constexpr int kSimulatedException = -1;
HandleMachException(MACH_EXCEPTION_CODES, HandleMachException(MACH_EXCEPTION_CODES,
mach_thread_self(), mach_thread_self(),
@ -78,33 +102,68 @@ class CrashHandler : public Thread,
MACHINE_THREAD_STATE_COUNT); 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: private:
CrashHandler() = default; CrashHandler() = default;
void InstallMachExceptionHandler() { bool InstallMachExceptionHandler() {
exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE)); 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(), kern_return_t kr = mach_port_insert_right(mach_task_self(),
exception_port_.get(), exception_port_.get(),
exception_port_.get(), exception_port_.get(),
MACH_MSG_TYPE_MAKE_SEND); 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. // 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() & ExcMaskAll() &
~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_BREAKPOINT | ~(EXC_MASK_EMULATION | EXC_MASK_SOFTWARE | EXC_MASK_RPC_ALERT |
EXC_MASK_RPC_ALERT | EXC_MASK_GUARD); EXC_MASK_GUARD | (IsBeingDebugged() ? EXC_MASK_BREAKPOINT : 0));
ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL); ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
exception_ports.GetExceptionPorts(mask, &original_handlers_); if (!exception_ports.GetExceptionPorts(mask, &original_handlers_) ||
exception_ports.SetExceptionPort( !exception_ports.SetExceptionPort(
mask, mask,
exception_port_.get(), exception_port_.get(),
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
MACHINE_THREAD_STATE); MACHINE_THREAD_STATE)) {
return false;
}
Start(); Start();
return true;
} }
// Thread: // Thread:
@ -175,7 +234,45 @@ class CrashHandler : public Thread,
thread_state_flavor_t flavor, thread_state_flavor_t flavor,
ConstThreadState old_state, ConstThreadState old_state,
mach_msg_type_number_t old_state_count) { 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. // The signal handler installed at OS-level.
@ -187,35 +284,16 @@ class CrashHandler : public Thread,
void HandleAndReraiseSignal(int signo, void HandleAndReraiseSignal(int signo,
siginfo_t* siginfo, siginfo_t* siginfo,
ucontext_t* context) { ucontext_t* context) {
// TODO(justincohen): This is incomplete. in_process_handler_.DumpExceptionFromSignal(system_data_, siginfo, context);
// Always call system handler. // Always call system handler.
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_); 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_; base::mac::ScopedMachReceiveRight exception_port_;
ExceptionPorts::ExceptionHandlerVector original_handlers_; ExceptionPorts::ExceptionHandlerVector original_handlers_;
struct sigaction old_action_ = {}; struct sigaction old_action_ = {};
internal::InProcessHandler in_process_handler_;
internal::IOSSystemDataCollector system_data_; internal::IOSSystemDataCollector system_data_;
InitializationStateDcheck initialized_; InitializationStateDcheck initialized_;
}; };
@ -227,15 +305,14 @@ CrashpadClient::CrashpadClient() {}
CrashpadClient::~CrashpadClient() {} CrashpadClient::~CrashpadClient() {}
// static // static
void CrashpadClient::StartCrashpadInProcessHandler( bool CrashpadClient::StartCrashpadInProcessHandler(
const base::FilePath& database, const base::FilePath& database,
const std::string& url, const std::string& url,
const std::map<std::string, std::string>& annotations) { const std::map<std::string, std::string>& annotations) {
CrashHandler* crash_handler = CrashHandler::Get(); CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler); DCHECK(crash_handler);
InstallObjcExceptionPreprocessor(crash_handler); InstallObjcExceptionPreprocessor(crash_handler);
crash_handler->Initialize(); return crash_handler->Initialize(database, url, annotations);
} }
// static // static
@ -257,17 +334,16 @@ void CrashpadClient::ProcessIntermediateDump(
// static // static
void CrashpadClient::StartProcessingPendingReports() { void CrashpadClient::StartProcessingPendingReports() {
// TODO(justincohen): Start the CrashReportUploadThread. CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler);
crash_handler->StartProcessingPendingReports();
} }
// static // static
void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) {
CrashHandler* crash_handler = CrashHandler::Get(); CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler); DCHECK(crash_handler);
crash_handler->DumpWithoutCrash(context); crash_handler->DumpWithoutCrash(context, /*process_dump=*/true);
// TODO(justincohen): Change this to only process the dump from above, not all
// intermediate dump files.
crash_handler->ProcessIntermediateDumps();
} }
// static // static
@ -275,7 +351,7 @@ void CrashpadClient::DumpWithoutCrashAndDeferProcessing(
NativeCPUContext* context) { NativeCPUContext* context) {
CrashHandler* crash_handler = CrashHandler::Get(); CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler); DCHECK(crash_handler);
crash_handler->DumpWithoutCrash(context); crash_handler->DumpWithoutCrash(context, /*process_dump=*/false);
} }
// static // static
@ -284,8 +360,7 @@ void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath(
const base::FilePath path) { const base::FilePath path) {
CrashHandler* crash_handler = CrashHandler::Get(); CrashHandler* crash_handler = CrashHandler::Get();
DCHECK(crash_handler); DCHECK(crash_handler);
// TODO(justincohen): Change to DumpWithoutCrashAtPath(context, path). crash_handler->DumpWithoutCrashAtPath(context, path);
crash_handler->DumpWithoutCrash(context);
} }
} // namespace crashpad } // namespace crashpad

View File

@ -18,6 +18,9 @@
#include <vector> #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 "client/simulate_crash.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "test/scoped_temp_dir.h" #include "test/scoped_temp_dir.h"
@ -32,9 +35,53 @@ using CrashpadIOSClient = PlatformTest;
TEST_F(CrashpadIOSClient, DumpWithoutCrash) { TEST_F(CrashpadIOSClient, DumpWithoutCrash) {
CrashpadClient client; CrashpadClient client;
ScopedTempDir database_dir; ScopedTempDir database_dir;
client.StartCrashpadInProcessHandler( ASSERT_TRUE(client.StartCrashpadInProcessHandler(
base::FilePath(database_dir.path()), "", {}); 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(); 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 // 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) { TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
CrashpadClient client; CrashpadClient client;
ScopedTempDir database_dir; ScopedTempDir database_dir;
client.StartCrashpadInProcessHandler( ASSERT_TRUE(client.StartCrashpadInProcessHandler(
base::FilePath(database_dir.path()), "", {}); base::FilePath(database_dir.path()), "", {}));
[NSException raise:@"GoogleTestNSException" format:@"ThrowException"]; [NSException raise:@"GoogleTestNSException" format:@"ThrowException"];
} }
@ -56,8 +103,8 @@ TEST_F(CrashpadIOSClient, DISABLED_ThrowNSException) {
TEST_F(CrashpadIOSClient, DISABLED_ThrowException) { TEST_F(CrashpadIOSClient, DISABLED_ThrowException) {
CrashpadClient client; CrashpadClient client;
ScopedTempDir database_dir; ScopedTempDir database_dir;
client.StartCrashpadInProcessHandler( ASSERT_TRUE(client.StartCrashpadInProcessHandler(
base::FilePath(database_dir.path()), "", {}); base::FilePath(database_dir.path()), "", {}));
std::vector<int> empty_vector; std::vector<int> empty_vector;
empty_vector.at(42); empty_vector.at(42);
} }

View File

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

View File

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

View File

@ -68,7 +68,8 @@ CrashReportUploadThread::~CrashReportUploadThread() {
void CrashReportUploadThread::ReportPending(const UUID& report_uuid) { void CrashReportUploadThread::ReportPending(const UUID& report_uuid) {
known_pending_report_uuids_.PushBack(report_uuid); known_pending_report_uuids_.PushBack(report_uuid);
thread_.DoWorkNow(); if (thread_.is_running())
thread_.DoWorkNow();
} }
void CrashReportUploadThread::Start() { void CrashReportUploadThread::Start() {

View File

@ -200,8 +200,7 @@ bool ExceptionSnapshotIOSIntermediateDump::InitializeFromNSException(
INITIALIZATION_STATE_SET_INITIALIZING(initialized_); INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
DCHECK(exception_data); DCHECK(exception_data);
exception_ = EXC_SOFTWARE; exception_ = kMachExceptionFromNSException;
exception_info_ = 0xDEADC0DE; /* uncaught NSException */
if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) { if (!GetDataValueFromMap(exception_data, Key::kThreadID, &thread_id_)) {
LOG(ERROR) << "Exceptions require a thread id."; LOG(ERROR) << "Exceptions require a thread id.";

View File

@ -397,6 +397,7 @@ crashpad_static_library("util") {
"ios/raw_logging.h", "ios/raw_logging.h",
"ios/scoped_vm_read.cc", "ios/scoped_vm_read.cc",
"ios/scoped_vm_read.h", "ios/scoped_vm_read.h",
"net/http_transport_mac.mm",
] ]
} }
@ -659,8 +660,8 @@ if (!crashpad_is_android && !crashpad_is_ios) {
deps = [ deps = [
":util", ":util",
"../third_party/cpp-httplib",
"$mini_chromium_source_parent:base", "$mini_chromium_source_parent:base",
"../third_party/cpp-httplib",
"../third_party/zlib", "../third_party/zlib",
"../tools:tool_support", "../tools:tool_support",
] ]
@ -867,12 +868,12 @@ source_set("util_test") {
deps = [ deps = [
":util", ":util",
"$mini_chromium_source_parent:base",
"../client", "../client",
"../compat", "../compat",
"../test", "../test",
"../third_party/googletest:googlemock", "../third_party/googletest:googlemock",
"../third_party/googletest:googletest", "../third_party/googletest:googletest",
"$mini_chromium_source_parent:base",
"../third_party/zlib", "../third_party/zlib",
] ]

View File

@ -47,6 +47,9 @@ constexpr exception_behavior_t kMachExceptionCodes = MACH_EXCEPTION_CODES;
//! \brief An exception type to use for simulated exceptions. //! \brief An exception type to use for simulated exceptions.
constexpr exception_type_t kMachExceptionSimulated = 'CPsx'; 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`. //! \brief A const version of `thread_state_t`.
//! //!
//! This is useful as the \a old_state parameter to exception handler functions. //! This is useful as the \a old_state parameter to exception handler functions.