mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-09 14:06:33 +00:00
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:
parent
07a6b70755
commit
204abe16d2
@ -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",
|
||||
|
@ -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<std::string, std::string>& annotations);
|
||||
@ -503,7 +504,7 @@ class CrashpadClient {
|
||||
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.
|
||||
|
@ -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<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 @@ 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<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 @@ 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<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 @@ 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
|
||||
|
@ -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 @@ 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<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, 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<int> empty_vector;
|
||||
empty_vector.at(42);
|
||||
}
|
||||
|
298
client/ios_handler/in_process_handler.cc
Normal file
298
client/ios_handler/in_process_handler.cc
Normal 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
|
199
client/ios_handler/in_process_handler.h
Normal file
199
client/ios_handler/in_process_handler.h
Normal 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
|
@ -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() {
|
||||
|
@ -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.";
|
||||
|
@ -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",
|
||||
]
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user