mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-12 16:22:46 +00:00
Instead use a custom mechanism based on the filename. Rather than a filename of <uuid>, instead name the file <bundle-id>|<uuid>[.locked]. A locked file will have the optional .locked extension. Files can be unlocked after writing an intermediate dump, or during initialization by looking for matching bundle-ids. Clients that call ProcessIntermediateDumps() will clean up any leftover locked intermediate dumps. Clients that never call ProcessIntermediateDumps, such as extensions that leave this up to the main application, will be cleaned up in a followup change. Bug: crashpad:31 Change-Id: Icd4aaa3b79351870fbe9b8463cfbdf7cff7d5f87 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3229429 Commit-Queue: Justin Cohen <justincohen@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Rohit Rao <rohitrao@chromium.org>
368 lines
13 KiB
C++
368 lines
13 KiB
C++
// Copyright 2020 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/crashpad_client.h"
|
||
|
||
#include <unistd.h>
|
||
|
||
#include <ios>
|
||
|
||
#include "base/cxx17_backports.h"
|
||
#include "base/logging.h"
|
||
#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"
|
||
#include "util/mach/mach_extensions.h"
|
||
#include "util/mach/mach_message.h"
|
||
#include "util/mach/mach_message_server.h"
|
||
#include "util/misc/initialization_state_dcheck.h"
|
||
#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 {
|
||
|
||
// A base class for signal handler and Mach exception server.
|
||
class CrashHandler : public Thread,
|
||
public UniversalMachExcServer::Interface,
|
||
public ObjcExceptionDelegate {
|
||
public:
|
||
CrashHandler(const CrashHandler&) = delete;
|
||
CrashHandler& operator=(const CrashHandler&) = delete;
|
||
|
||
static CrashHandler* Get() {
|
||
static CrashHandler* instance = new CrashHandler();
|
||
return instance;
|
||
}
|
||
|
||
bool Initialize(const base::FilePath& database,
|
||
const std::string& url,
|
||
const std::map<std::string, std::string>& annotations) {
|
||
INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
|
||
if (!in_process_handler_.Initialize(
|
||
database, url, annotations, system_data_) ||
|
||
!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) {
|
||
in_process_handler_.ProcessIntermediateDumps(annotations);
|
||
}
|
||
|
||
void ProcessIntermediateDump(
|
||
const base::FilePath& file,
|
||
const std::map<std::string, std::string>& annotations) {
|
||
in_process_handler_.ProcessIntermediateDump(file, annotations);
|
||
}
|
||
|
||
void DumpWithContext(NativeCPUContext* context) {
|
||
const mach_exception_data_type_t code[2] = {};
|
||
static constexpr int kSimulatedException = -1;
|
||
HandleMachException(MACH_EXCEPTION_CODES,
|
||
mach_thread_self(),
|
||
kSimulatedException,
|
||
code,
|
||
base::size(code),
|
||
MACHINE_THREAD_STATE,
|
||
reinterpret_cast<ConstThreadState>(context),
|
||
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;
|
||
|
||
bool InstallMachExceptionHandler() {
|
||
exception_port_.reset(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
|
||
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);
|
||
if (kr != KERN_SUCCESS) {
|
||
MACH_LOG(ERROR, kr) << "mach_port_insert_right";
|
||
return false;
|
||
}
|
||
|
||
// TODO: Use SwapExceptionPort instead and put back EXC_MASK_BREAKPOINT.
|
||
// Until then, remove |EXC_MASK_BREAKPOINT| while attached to a debugger.
|
||
exception_mask_t mask =
|
||
ExcMaskAll() &
|
||
~(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);
|
||
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:
|
||
|
||
void ThreadMain() override {
|
||
UniversalMachExcServer universal_mach_exc_server(this);
|
||
while (true) {
|
||
mach_msg_return_t mr =
|
||
MachMessageServer::Run(&universal_mach_exc_server,
|
||
exception_port_.get(),
|
||
MACH_MSG_OPTION_NONE,
|
||
MachMessageServer::kPersistent,
|
||
MachMessageServer::kReceiveLargeIgnore,
|
||
kMachMessageTimeoutWaitIndefinitely);
|
||
MACH_CHECK(mr == MACH_SEND_INVALID_DEST, mr) << "MachMessageServer::Run";
|
||
}
|
||
}
|
||
|
||
// UniversalMachExcServer::Interface:
|
||
|
||
kern_return_t CatchMachException(exception_behavior_t behavior,
|
||
exception_handler_t exception_port,
|
||
thread_t thread,
|
||
task_t task,
|
||
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,
|
||
thread_state_t new_state,
|
||
mach_msg_type_number_t* new_state_count,
|
||
const mach_msg_trailer_t* trailer,
|
||
bool* destroy_complex_request) override {
|
||
*destroy_complex_request = true;
|
||
|
||
// TODO(justincohen): Forward exceptions to original_handlers_ with
|
||
// UniversalExceptionRaise.
|
||
|
||
// iOS shouldn't have any child processes, but just in case, those will
|
||
// inherit the task exception ports, and this process isn’t prepared to
|
||
// handle them
|
||
if (task != mach_task_self()) {
|
||
LOG(WARNING) << "task 0x" << std::hex << task << " != 0x"
|
||
<< mach_task_self();
|
||
return KERN_FAILURE;
|
||
}
|
||
|
||
HandleMachException(behavior,
|
||
thread,
|
||
exception,
|
||
code,
|
||
code_count,
|
||
*flavor,
|
||
old_state,
|
||
old_state_count);
|
||
|
||
// Respond with KERN_FAILURE so the system will continue to handle this
|
||
// exception as a crash.
|
||
return KERN_FAILURE;
|
||
}
|
||
|
||
void HandleMachException(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) {
|
||
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.
|
||
static void CatchSignal(int signo, siginfo_t* siginfo, void* context) {
|
||
Get()->HandleAndReraiseSignal(
|
||
signo, siginfo, reinterpret_cast<ucontext_t*>(context));
|
||
}
|
||
|
||
void HandleAndReraiseSignal(int signo,
|
||
siginfo_t* siginfo,
|
||
ucontext_t* context) {
|
||
in_process_handler_.DumpExceptionFromSignal(system_data_, siginfo, context);
|
||
|
||
// Always call system handler.
|
||
Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, &old_action_);
|
||
}
|
||
|
||
base::mac::ScopedMachReceiveRight exception_port_;
|
||
ExceptionPorts::ExceptionHandlerVector original_handlers_;
|
||
struct sigaction old_action_ = {};
|
||
internal::InProcessHandler in_process_handler_;
|
||
internal::IOSSystemDataCollector system_data_;
|
||
InitializationStateDcheck initialized_;
|
||
};
|
||
|
||
} // namespace
|
||
|
||
CrashpadClient::CrashpadClient() {}
|
||
|
||
CrashpadClient::~CrashpadClient() {}
|
||
|
||
// static
|
||
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);
|
||
return crash_handler->Initialize(database, url, annotations);
|
||
}
|
||
|
||
// static
|
||
void CrashpadClient::ProcessIntermediateDumps(
|
||
const std::map<std::string, std::string>& annotations) {
|
||
CrashHandler* crash_handler = CrashHandler::Get();
|
||
DCHECK(crash_handler);
|
||
crash_handler->ProcessIntermediateDumps(annotations);
|
||
}
|
||
|
||
// static
|
||
void CrashpadClient::ProcessIntermediateDump(
|
||
const base::FilePath& file,
|
||
const std::map<std::string, std::string>& annotations) {
|
||
CrashHandler* crash_handler = CrashHandler::Get();
|
||
DCHECK(crash_handler);
|
||
crash_handler->ProcessIntermediateDump(file, annotations);
|
||
}
|
||
|
||
// static
|
||
void CrashpadClient::StartProcessingPendingReports() {
|
||
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, /*process_dump=*/true);
|
||
}
|
||
|
||
// static
|
||
void CrashpadClient::DumpWithoutCrashAndDeferProcessing(
|
||
NativeCPUContext* context) {
|
||
CrashHandler* crash_handler = CrashHandler::Get();
|
||
DCHECK(crash_handler);
|
||
crash_handler->DumpWithoutCrash(context, /*process_dump=*/false);
|
||
}
|
||
|
||
// static
|
||
void CrashpadClient::DumpWithoutCrashAndDeferProcessingAtPath(
|
||
NativeCPUContext* context,
|
||
const base::FilePath path) {
|
||
CrashHandler* crash_handler = CrashHandler::Get();
|
||
DCHECK(crash_handler);
|
||
crash_handler->DumpWithoutCrashAtPath(context, path);
|
||
}
|
||
|
||
} // namespace crashpad
|