From 409742cd40bcd163a40146bc69fe220d79f70ef3 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 4 Feb 2015 18:32:42 -0500 Subject: [PATCH] handler: Write crash reports to a crash report database. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/904493002 --- handler/handler.gyp | 5 + handler/mac/crash_report_exception_handler.cc | 138 ++++++++++++++++++ handler/mac/crash_report_exception_handler.h | 65 +++++++++ handler/mac/exception_handler_server.cc | 45 +++--- handler/mac/exception_handler_server.h | 21 ++- handler/mac/main.cc | 26 +++- 6 files changed, 268 insertions(+), 32 deletions(-) create mode 100644 handler/mac/crash_report_exception_handler.cc create mode 100644 handler/mac/crash_report_exception_handler.h diff --git a/handler/handler.gyp b/handler/handler.gyp index a6ff39ce..427c3b2e 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -20,7 +20,10 @@ 'target_name': 'crashpad_handler', 'type': 'executable', 'dependencies': [ + '../client/client.gyp:client', '../compat/compat.gyp:compat', + '../minidump/minidump.gyp:minidump', + '../snapshot/snapshot.gyp:snapshot', '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', '../tools/tools.gyp:tool_support', '../util/util.gyp:util', @@ -29,6 +32,8 @@ '..', ], 'sources': [ + 'mac/crash_report_exception_handler.cc', + 'mac/crash_report_exception_handler.h', 'mac/exception_handler_server.cc', 'mac/exception_handler_server.h', 'mac/main.cc', diff --git a/handler/mac/crash_report_exception_handler.cc b/handler/mac/crash_report_exception_handler.cc new file mode 100644 index 00000000..2f497c6b --- /dev/null +++ b/handler/mac/crash_report_exception_handler.cc @@ -0,0 +1,138 @@ +// Copyright 2015 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 "handler/mac/crash_report_exception_handler.h" + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "minidump/minidump_file_writer.h" +#include "snapshot/mac/process_snapshot_mac.h" +#include "util/file/file_writer.h" +#include "util/mach/exception_behaviors.h" +#include "util/mach/mach_extensions.h" +#include "util/mach/scoped_task_suspend.h" +#include "util/misc/uuid.h" + +namespace crashpad { + +namespace { + +// Calls CrashReportDatabase::ErrorWritingCrashReport() upon destruction unless +// disarmed by calling Disarm(). Armed upon construction. +class CallErrorWritingCrashReport { + public: + CallErrorWritingCrashReport(CrashReportDatabase* database, + CrashReportDatabase::NewReport* new_report) + : database_(database), + new_report_(new_report) { + } + + ~CallErrorWritingCrashReport() { + if (new_report_) { + database_->ErrorWritingCrashReport(new_report_); + } + } + + void Disarm() { + new_report_ = nullptr; + } + + private: + CrashReportDatabase* database_; // weak + CrashReportDatabase::NewReport* new_report_; // weak + + DISALLOW_COPY_AND_ASSIGN(CallErrorWritingCrashReport); +}; + +} // namespace + +CrashReportExceptionHandler::CrashReportExceptionHandler( + CrashReportDatabase* database) + : database_(database) { +} + +CrashReportExceptionHandler::~CrashReportExceptionHandler() { +} + +kern_return_t CrashReportExceptionHandler::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, + const natural_t* 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) { + *destroy_complex_request = true; + + // The expected behavior is EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + // but it’s possible to deal with any exception behavior as long as it + // carries identity information (valid thread and task ports). + if (!ExceptionBehaviorHasIdentity(behavior)) { + LOG(ERROR) << base::StringPrintf( + "unexpected exception behavior 0x%x, rejecting", behavior); + return KERN_FAILURE; + } else if (behavior != (EXCEPTION_STATE_IDENTITY | kMachExceptionCodes)) { + LOG(WARNING) << base::StringPrintf( + "unexpected exception behavior 0x%x, proceeding", behavior); + } + + if (task == mach_task_self()) { + LOG(ERROR) << "cannot suspend myself"; + return KERN_FAILURE; + } + + ScopedTaskSuspend suspend(task); + + ProcessSnapshotMac process_snapshot; + if (!process_snapshot.Initialize(task)) { + return KERN_FAILURE; + } + + CrashReportDatabase::NewReport* new_report; + CrashReportDatabase::OperationStatus database_status = + database_->PrepareNewCrashReport(&new_report); + if (database_status != CrashReportDatabase::kNoError) { + return KERN_FAILURE; + } + + CallErrorWritingCrashReport call_error_writing_crash_report(database_, + new_report); + + WeakFileHandleFileWriter file_writer(new_report->handle); + + MinidumpFileWriter minidump; + minidump.InitializeFromSnapshot(&process_snapshot); + if (!minidump.WriteEverything(&file_writer)) { + return KERN_FAILURE; + } + + call_error_writing_crash_report.Disarm(); + + UUID uuid; + database_status = database_->FinishedWritingCrashReport(new_report, &uuid); + if (database_status != CrashReportDatabase::kNoError) { + return KERN_FAILURE; + } + + return ExcServerSuccessfulReturnValue(behavior, false); +} + +} // namespace crashpad diff --git a/handler/mac/crash_report_exception_handler.h b/handler/mac/crash_report_exception_handler.h new file mode 100644 index 00000000..65cc4da2 --- /dev/null +++ b/handler/mac/crash_report_exception_handler.h @@ -0,0 +1,65 @@ +// Copyright 2015 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. + +#ifndef CRASHPAD_HANDLER_MAC_CRASH_REPORT_EXCEPTION_HANDLER_H_ +#define CRASHPAD_HANDLER_MAC_CRASH_REPORT_EXCEPTION_HANDLER_H_ + +#include + +#include "base/basictypes.h" +#include "client/crash_report_database.h" +#include "util/mach/exc_server_variants.h" + +namespace crashpad { + +//! \brief An exception handler that writes crash reports for exception messages +//! to a CrashReportDatabase. +class CrashReportExceptionHandler : public UniversalMachExcServer::Interface { + public: + //! \brief Creates a new object that will store crash reports in \a database. + //! + //! \param[in] database The database to store crash reports in. Weak. + explicit CrashReportExceptionHandler(CrashReportDatabase* database); + + ~CrashReportExceptionHandler(); + + // UniversalMachExcServer::Interface: + + //! \brief Processes an exception message by writing a crash report to this + //! object’s CrashReportDatabase. + 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, + const natural_t* 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; + + private: + CrashReportDatabase* database_; // weak + + DISALLOW_COPY_AND_ASSIGN(CrashReportExceptionHandler); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_MAC_CRASH_REPORT_EXCEPTION_HANDLER_H_ diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc index dc1b704a..803ebfdc 100644 --- a/handler/mac/exception_handler_server.cc +++ b/handler/mac/exception_handler_server.cc @@ -16,10 +16,7 @@ #include "base/logging.h" #include "base/mac/mach_logging.h" -#include "base/strings/stringprintf.h" #include "util/mach/composite_mach_message_server.h" -#include "util/mach/exc_server_variants.h" -#include "util/mach/exception_behaviors.h" #include "util/mach/mach_extensions.h" #include "util/mach/mach_message.h" #include "util/mach/mach_message_server.h" @@ -33,12 +30,15 @@ class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface, public NotifyServer::Interface { public: - explicit ExceptionHandlerServerRun(mach_port_t exception_port) + ExceptionHandlerServerRun( + mach_port_t exception_port, + UniversalMachExcServer::Interface* exception_interface) : UniversalMachExcServer::Interface(), NotifyServer::Interface(), mach_exc_server_(this), notify_server_(this), composite_mach_message_server_(), + exception_interface_(exception_interface), exception_port_(exception_port), notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)), running_(true) { @@ -124,28 +124,25 @@ class ExceptionHandlerServerRun mach_msg_type_number_t* new_state_count, const mach_msg_trailer_t* trailer, bool* destroy_complex_request) override { - *destroy_complex_request = true; - if (exception_port != exception_port_) { LOG(WARNING) << "exception port mismatch"; return MIG_BAD_ID; } - // The expected behavior is EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, - // but it’s possible to deal with any exception behavior as long as it - // carries identity information (valid thread and task ports). - if (!ExceptionBehaviorHasIdentity(behavior)) { - LOG(WARNING) << base::StringPrintf( - "unexpected exception behavior 0x%x, rejecting", behavior); - return KERN_FAILURE; - } else if (behavior != (EXCEPTION_STATE_IDENTITY | kMachExceptionCodes)) { - LOG(WARNING) << base::StringPrintf( - "unexpected exception behavior 0x%x, proceeding", behavior); - } - - // TODO(mark): Implement. - - return ExcServerSuccessfulReturnValue(behavior, false); + return exception_interface_->CatchMachException(behavior, + exception_port, + thread, + task, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + trailer, + destroy_complex_request); } // NotifyServer::Interface: @@ -213,6 +210,7 @@ class ExceptionHandlerServerRun UniversalMachExcServer mach_exc_server_; NotifyServer notify_server_; CompositeMachMessageServer composite_mach_message_server_; + UniversalMachExcServer::Interface* exception_interface_; // weak mach_port_t exception_port_; // weak base::mac::ScopedMachReceiveRight notify_port_; bool running_; @@ -230,8 +228,9 @@ ExceptionHandlerServer::ExceptionHandlerServer() ExceptionHandlerServer::~ExceptionHandlerServer() { } -void ExceptionHandlerServer::Run() { - ExceptionHandlerServerRun run(receive_port_); +void ExceptionHandlerServer::Run( + UniversalMachExcServer::Interface* exception_interface) { + ExceptionHandlerServerRun run(receive_port_, exception_interface); run.Run(); } diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h index a0a5764a..37c61a0f 100644 --- a/handler/mac/exception_handler_server.h +++ b/handler/mac/exception_handler_server.h @@ -20,6 +20,7 @@ #include #include "base/mac/scoped_mach_port.h" +#include "util/mach/exc_server_variants.h" namespace crashpad { @@ -32,20 +33,24 @@ class ExceptionHandlerServer { //! \brief Runs the exception-handling server. //! - //! This method monitors \a receive_port_ for exception messages and - //! no-senders notifications. It continues running until it has no more - //! clients, indicated by the receipt of a no-senders notification. It is - //! important to assure that a send right has been transferred to a client - //! (or queued by `mach_msg()` to be sent to a client) prior to calling this - //! method, or it will detect that it is sender-less and return immediately. + //! \param[in] exception_interface An object to send exception messages to. + //! + //! This method monitors receive_port() for exception messages and no-senders + //! notifications. It continues running until it has no more clients, + //! indicated by the receipt of a no-senders notification. It is important to + //! assure that a send right has been transferred to a client (or queued by + //! `mach_msg()` to be sent to a client) prior to calling this method, or it + //! will detect that it is sender-less and return immediately. + //! + //! All exception messages will be passed to \a exception_interface. //! //! This method must only be called once on an ExceptionHandlerServer object. //! //! If an unexpected condition that prevents this method from functioning is //! encountered, it will log a message and terminate execution. Receipt of an - //! invalid message on \a receive_port_ will cause a message to be logged, but + //! invalid message on receive_port() will cause a message to be logged, but //! this method will continue running normally. - void Run(); + void Run(UniversalMachExcServer::Interface* exception_interface); //! \brief Returns the receive right that will be monitored for exception //! messages. diff --git a/handler/mac/main.cc b/handler/mac/main.cc index 4dfecef2..c5d152f7 100644 --- a/handler/mac/main.cc +++ b/handler/mac/main.cc @@ -18,7 +18,11 @@ #include +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "client/crash_report_database.h" #include "tools/tool_support.h" +#include "handler/mac/crash_report_exception_handler.h" #include "handler/mac/exception_handler_server.h" #include "util/mach/child_port_handshake.h" #include "util/posix/close_stdio.h" @@ -32,6 +36,7 @@ void Usage(const std::string& me) { "Usage: %s [OPTION]...\n" "Crashpad's exception handler server.\n" "\n" +" --database=PATH store the crash report database at PATH\n" " --handshake-fd=FD establish communication with the client over FD\n" " --help display this help and exit\n" " --version output version information and exit\n", @@ -45,6 +50,7 @@ int HandlerMain(int argc, char* argv[]) { enum OptionFlags { // Long options without short equivalents. kOptionLastChar = 255, + kOptionDatabase, kOptionHandshakeFD, // Standard options. @@ -53,11 +59,13 @@ int HandlerMain(int argc, char* argv[]) { }; struct { + const char* database; int handshake_fd; } options = {}; options.handshake_fd = -1; const struct option long_options[] = { + {"database", required_argument, nullptr, kOptionDatabase}, {"handshake-fd", required_argument, nullptr, kOptionHandshakeFD}, {"help", no_argument, nullptr, kOptionHelp}, {"version", no_argument, nullptr, kOptionVersion}, @@ -67,6 +75,9 @@ int HandlerMain(int argc, char* argv[]) { int opt; while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { switch (opt) { + case kOptionDatabase: + options.database = optarg; + break; case kOptionHandshakeFD: if (!StringToNumber(optarg, &options.handshake_fd) || options.handshake_fd < 0) { @@ -94,6 +105,11 @@ int HandlerMain(int argc, char* argv[]) { return EXIT_FAILURE; } + if (!options.database) { + ToolSupport::UsageHint(me, "--database is required"); + return EXIT_FAILURE; + } + if (argc) { ToolSupport::UsageHint(me, nullptr); return EXIT_FAILURE; @@ -107,7 +123,15 @@ int HandlerMain(int argc, char* argv[]) { exception_handler_server.receive_port(), MACH_MSG_TYPE_MAKE_SEND); - exception_handler_server.Run(); + scoped_ptr database( + CrashReportDatabase::Initialize(base::FilePath(options.database))); + if (!database) { + return EXIT_FAILURE; + } + + CrashReportExceptionHandler exception_handler(database.get()); + + exception_handler_server.Run(&exception_handler); return EXIT_SUCCESS; }