| // Copyright 2014 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/handler_main.h" |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/auto_reset.h" |
| #include "base/compiler_specific.h" |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_file.h" |
| #include "base/logging.h" |
| #include "base/metrics/persistent_histogram_allocator.h" |
| #include "base/scoped_generic.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "client/crash_report_database.h" |
| #include "client/crashpad_client.h" |
| #include "client/crashpad_info.h" |
| #include "client/prune_crash_reports.h" |
| #include "client/simple_string_dictionary.h" |
| #include "handler/crash_report_upload_thread.h" |
| #include "handler/prune_crash_reports_thread.h" |
| #include "tools/tool_support.h" |
| #include "util/file/file_io.h" |
| #include "util/misc/address_types.h" |
| #include "util/misc/metrics.h" |
| #include "util/misc/paths.h" |
| #include "util/numeric/in_range_cast.h" |
| #include "util/stdlib/map_insert.h" |
| #include "util/stdlib/string_number_conversion.h" |
| #include "util/string/split_string.h" |
| #include "util/synchronization/semaphore.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "handler/linux/cros_crash_report_exception_handler.h" |
| #endif |
| |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| #include <unistd.h> |
| |
| #include "handler/linux/crash_report_exception_handler.h" |
| #include "handler/linux/exception_handler_server.h" |
| #include "util/posix/signals.h" |
| #elif defined(OS_MACOSX) |
| #include <libgen.h> |
| #include <signal.h> |
| |
| #include "base/mac/scoped_mach_port.h" |
| #include "handler/mac/crash_report_exception_handler.h" |
| #include "handler/mac/exception_handler_server.h" |
| #include "handler/mac/file_limit_annotation.h" |
| #include "util/mach/child_port_handshake.h" |
| #include "util/mach/mach_extensions.h" |
| #include "util/posix/close_stdio.h" |
| #include "util/posix/signals.h" |
| #elif defined(OS_WIN) |
| #include <windows.h> |
| |
| #include "handler/win/crash_report_exception_handler.h" |
| #include "util/win/exception_handler_server.h" |
| #include "util/win/handle.h" |
| #include "util/win/initial_client_data.h" |
| #include "util/win/session_end_watcher.h" |
| #elif defined(OS_FUCHSIA) |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| |
| #include <lib/zx/channel.h> |
| #include <lib/zx/job.h> |
| |
| #include "handler/fuchsia/crash_report_exception_handler.h" |
| #include "handler/fuchsia/exception_handler_server.h" |
| #elif defined(OS_LINUX) |
| #include "handler/linux/crash_report_exception_handler.h" |
| #include "handler/linux/exception_handler_server.h" |
| #endif // OS_MACOSX |
| |
| #if defined(STARBOARD) |
| // Stub out required starboard implementation as this is built in parallel. |
| #include "starboard/event.h" |
| void SbEventHandle(const SbEvent* event) {} |
| #endif // defined(STARBOARD) |
| |
| namespace crashpad { |
| |
| namespace { |
| |
| void Usage(const base::FilePath& me) { |
| fprintf(stderr, |
| "Usage: %" PRFilePath " [OPTION]...\n" |
| "Crashpad's exception handler server.\n" |
| "\n" |
| " --annotation=KEY=VALUE set a process annotation in each crash report\n" |
| " --database=PATH store the crash report database at PATH\n" |
| #if defined(OS_MACOSX) |
| " --handshake-fd=FD establish communication with the client over FD\n" |
| #endif // OS_MACOSX |
| #if defined(OS_WIN) |
| " --initial-client-data=HANDLE_request_crash_dump,\n" |
| " HANDLE_request_non_crash_dump,\n" |
| " HANDLE_non_crash_dump_completed,\n" |
| " HANDLE_pipe,\n" |
| " HANDLE_client_process,\n" |
| " Address_crash_exception_information,\n" |
| " Address_non_crash_exception_information,\n" |
| " Address_debug_critical_section\n" |
| " use precreated data to register initial client\n" |
| #endif // OS_WIN |
| #if defined(OS_ANDROID) || defined(OS_LINUX) |
| " --initial-client-fd=FD a socket connected to a client.\n" |
| #endif // OS_ANDROID || OS_LINUX |
| #if defined(OS_MACOSX) |
| " --mach-service=SERVICE register SERVICE with the bootstrap server\n" |
| #endif // OS_MACOSX |
| " --metrics-dir=DIR store metrics files in DIR (only in Chromium)\n" |
| " --monitor-self run a second handler to catch crashes in the first\n" |
| " --monitor-self-annotation=KEY=VALUE\n" |
| " set a module annotation in the handler\n" |
| " --monitor-self-argument=ARGUMENT\n" |
| " provide additional arguments to the second handler\n" |
| " --no-identify-client-via-url\n" |
| " when uploading crash report, don't add\n" |
| " client-identifying arguments to URL\n" |
| " --no-periodic-tasks don't scan for new reports or prune the database\n" |
| " --no-rate-limit don't rate limit crash uploads\n" |
| " --no-upload-gzip don't use gzip compression when uploading\n" |
| #if defined(OS_ANDROID) |
| " --no-write-minidump-to-database\n" |
| " don't write minidump to database\n" |
| #endif // OS_ANDROID |
| #if defined(OS_WIN) |
| " --pipe-name=PIPE communicate with the client over PIPE\n" |
| #endif // OS_WIN |
| #if defined(OS_MACOSX) |
| " --reset-own-crash-exception-port-to-system-default\n" |
| " reset the server's exception handler to default\n" |
| #endif // OS_MACOSX |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| " --sanitization-information=SANITIZATION_INFORMATION_ADDRESS\n" |
| " the address of a SanitizationInformation struct.\n" |
| " --shared-client-connection the file descriptor provided by\n" |
| " --initial-client-fd is shared among multiple\n" |
| " clients\n" |
| " --trace-parent-with-exception=EXCEPTION_INFORMATION_ADDRESS\n" |
| " request a dump for the handler's parent process\n" |
| #endif // OS_LINUX || OS_ANDROID |
| #if defined(STARBOARD) |
| " --evergreen-information=EVERGREEN_INFORMATION_ADDRESS\n" |
| " the address of a EvegreenInfo struct.\n" |
| " --handler-started-at-crash\n" |
| " the handler was started at time of crash, as\n" |
| " opposed to time of launch\n" |
| #endif |
| " --url=URL send crash reports to this Breakpad server URL,\n" |
| " only if uploads are enabled for the database\n" |
| #if defined(OS_CHROMEOS) |
| " --use-cros-crash-reporter\n" |
| " pass crash reports to /sbin/crash_reporter\n" |
| " instead of storing them in the database\n" |
| " --minidump-dir-for-tests=TEST_MINIDUMP_DIR\n" |
| " causes /sbin/crash_reporter to leave dumps in\n" |
| " this directory instead of the normal location\n" |
| " --always-allow-feedback\n" |
| " pass the --always_allow_feedback flag to\n" |
| " crash_reporter, thus skipping metrics consent\n" |
| " checks\n" |
| #endif // OS_CHROMEOS |
| #if defined(OS_ANDROID) |
| " --write-minidump-to-log write minidump to log\n" |
| #endif // OS_ANDROID |
| " --help display this help and exit\n" |
| " --version output version information and exit\n", |
| me.value().c_str()); |
| ToolSupport::UsageTail(me); |
| } |
| |
| struct Options { |
| std::map<std::string, std::string> annotations; |
| std::map<std::string, std::string> monitor_self_annotations; |
| std::string url; |
| base::FilePath database; |
| base::FilePath metrics_dir; |
| std::vector<std::string> monitor_self_arguments; |
| #if defined(OS_MACOSX) |
| std::string mach_service; |
| int handshake_fd; |
| bool reset_own_crash_exception_port_to_system_default; |
| #elif defined(OS_LINUX) || defined(OS_ANDROID) |
| VMAddress exception_information_address; |
| VMAddress sanitization_information_address; |
| int initial_client_fd; |
| bool shared_client_connection; |
| #if defined(STARBOARD) |
| VMAddress evergreen_information_address; |
| bool handler_started_at_crash = false; |
| #endif // defined(STARBOARD) |
| #if defined(OS_ANDROID) |
| bool write_minidump_to_log; |
| bool write_minidump_to_database; |
| #endif // OS_ANDROID |
| #elif defined(OS_WIN) |
| std::string pipe_name; |
| InitialClientData initial_client_data; |
| #endif // OS_MACOSX |
| bool identify_client_via_url; |
| bool monitor_self; |
| bool periodic_tasks; |
| bool rate_limit; |
| bool upload_gzip; |
| #if defined(OS_CHROMEOS) |
| bool use_cros_crash_reporter = false; |
| base::FilePath minidump_dir_for_tests; |
| bool always_allow_feedback = false; |
| #endif // OS_CHROMEOS |
| }; |
| |
| // Splits |key_value| on '=' and inserts the resulting key and value into |map|. |
| // If |key_value| has the wrong format, logs an error and returns false. If the |
| // key is already in the map, logs a warning, replaces the existing value, and |
| // returns true. If the key and value were inserted into the map, returns true. |
| // |argument| is used to give context to logged messages. |
| bool AddKeyValueToMap(std::map<std::string, std::string>* map, |
| const std::string& key_value, |
| const char* argument) { |
| std::string key; |
| std::string value; |
| if (!SplitStringFirst(key_value, '=', &key, &value)) { |
| LOG(ERROR) << argument << " requires KEY=VALUE"; |
| return false; |
| } |
| |
| std::string old_value; |
| if (!MapInsertOrReplace(map, key, value, &old_value)) { |
| LOG(WARNING) << argument << " has duplicate key " << key |
| << ", discarding value " << old_value; |
| } |
| return true; |
| } |
| |
| // Calls Metrics::HandlerLifetimeMilestone, but only on the first call. This is |
| // to prevent multiple exit events from inadvertently being recorded, which |
| // might happen if a crash occurs during destruction in what would otherwise be |
| // a normal exit, or if a CallMetricsRecordNormalExit object is destroyed after |
| // something else logs an exit event. |
| void MetricsRecordExit(Metrics::LifetimeMilestone milestone) { |
| static bool once = [](Metrics::LifetimeMilestone milestone) { |
| Metrics::HandlerLifetimeMilestone(milestone); |
| return true; |
| }(milestone); |
| ALLOW_UNUSED_LOCAL(once); |
| } |
| |
| // Calls MetricsRecordExit() to record a failure, and returns EXIT_FAILURE for |
| // the convenience of callers in main() which can simply write “return |
| // ExitFailure();”. |
| int ExitFailure() { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kFailed); |
| return EXIT_FAILURE; |
| } |
| |
| class CallMetricsRecordNormalExit { |
| public: |
| CallMetricsRecordNormalExit() {} |
| ~CallMetricsRecordNormalExit() { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kExitedNormally); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CallMetricsRecordNormalExit); |
| }; |
| |
| #if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID) |
| |
| void HandleCrashSignal(int sig, siginfo_t* siginfo, void* context) { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed); |
| |
| // Is siginfo->si_code useful? The only interesting values on macOS are 0 (not |
| // useful, signals generated asynchronously such as by kill() or raise()) and |
| // small positive numbers (useful, signal generated via a hardware fault). The |
| // standard specifies these other constants, and while xnu never uses them, |
| // they are intended to denote signals generated asynchronously and are |
| // included here. Additionally, existing practice on other systems |
| // (acknowledged by the standard) is for negative numbers to indicate that a |
| // signal was generated asynchronously. Although xnu does not do this, allow |
| // for the possibility for completeness. |
| bool si_code_valid = !(siginfo->si_code <= 0 || |
| siginfo->si_code == SI_USER || |
| siginfo->si_code == SI_QUEUE || |
| siginfo->si_code == SI_TIMER || |
| siginfo->si_code == SI_ASYNCIO || |
| siginfo->si_code == SI_MESGQ); |
| |
| // 0x5343 = 'SC', signifying “signal and code”, disambiguates from the schema |
| // used by ExceptionCodeForMetrics(). That system primarily uses Mach |
| // exception types and codes, which are not available to a POSIX signal |
| // handler. It does provide a way to encode only signal numbers, but does so |
| // with the understanding that certain “raw” signals would not be encountered |
| // without a Mach exception. Furthermore, it does not allow siginfo->si_code |
| // to be encoded, because that’s not available to Mach exception handlers. It |
| // would be a shame to lose that information available to a POSIX signal |
| // handler. |
| int metrics_code = 0x53430000 | (InRangeCast<uint8_t>(sig, 0xff) << 8); |
| if (si_code_valid) { |
| metrics_code |= InRangeCast<uint8_t>(siginfo->si_code, 0xff); |
| } |
| Metrics::HandlerCrashed(metrics_code); |
| |
| Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); |
| } |
| |
| void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated); |
| Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr); |
| } |
| |
| void ReinstallCrashHandler() { |
| // This is used to re-enable the metrics-recording crash handler after |
| // MonitorSelf() sets up a Crashpad exception handler. On macOS, the |
| // metrics-recording handler uses signals and the Crashpad handler uses Mach |
| // exceptions, so there’s nothing to re-enable. |
| // On Linux, the signal handler installed by StartHandler() restores the |
| // previously installed signal handler by default. |
| } |
| |
| void InstallCrashHandler() { |
| Signals::InstallCrashHandlers(HandleCrashSignal, 0, nullptr); |
| |
| // Not a crash handler, but close enough. |
| Signals::InstallTerminateHandlers(HandleTerminateSignal, 0, nullptr); |
| } |
| |
| #if defined(OS_MACOSX) |
| |
| struct ResetSIGTERMTraits { |
| static struct sigaction* InvalidValue() { |
| return nullptr; |
| } |
| |
| static void Free(struct sigaction* sa) { |
| int rv = sigaction(SIGTERM, sa, nullptr); |
| PLOG_IF(ERROR, rv != 0) << "sigaction"; |
| } |
| }; |
| using ScopedResetSIGTERM = |
| base::ScopedGeneric<struct sigaction*, ResetSIGTERMTraits>; |
| |
| ExceptionHandlerServer* g_exception_handler_server; |
| |
| // This signal handler is only operative when being run from launchd. |
| void HandleSIGTERM(int sig, siginfo_t* siginfo, void* context) { |
| // Don’t call MetricsRecordExit(). This is part of the normal exit path when |
| // running from launchd. |
| |
| DCHECK(g_exception_handler_server); |
| g_exception_handler_server->Stop(); |
| } |
| |
| #endif // OS_MACOSX |
| |
| #elif defined(OS_WIN) |
| |
| LONG(WINAPI* g_original_exception_filter)(EXCEPTION_POINTERS*) = nullptr; |
| |
| LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kCrashed); |
| Metrics::HandlerCrashed(exception_pointers->ExceptionRecord->ExceptionCode); |
| |
| if (g_original_exception_filter) |
| return g_original_exception_filter(exception_pointers); |
| else |
| return EXCEPTION_CONTINUE_SEARCH; |
| } |
| |
| // Handles events like Control-C and Control-Break on a console. |
| BOOL WINAPI ConsoleHandler(DWORD console_event) { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated); |
| return false; |
| } |
| |
| // Handles a WM_ENDSESSION message sent when the user session is ending. |
| class TerminateHandler final : public SessionEndWatcher { |
| public: |
| TerminateHandler() : SessionEndWatcher() {} |
| ~TerminateHandler() override {} |
| |
| private: |
| // SessionEndWatcher: |
| void SessionEnding() override { |
| MetricsRecordExit(Metrics::LifetimeMilestone::kTerminated); |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(TerminateHandler); |
| }; |
| |
| void ReinstallCrashHandler() { |
| // This is used to re-enable the metrics-recording crash handler after |
| // MonitorSelf() sets up a Crashpad exception handler. The Crashpad handler |
| // takes over the UnhandledExceptionFilter, so reinstall the metrics-recording |
| // one. |
| g_original_exception_filter = |
| SetUnhandledExceptionFilter(&UnhandledExceptionHandler); |
| } |
| |
| void InstallCrashHandler() { |
| ReinstallCrashHandler(); |
| |
| // These are termination handlers, not crash handlers, but that’s close |
| // enough. Note that destroying the TerminateHandler would wait for its thread |
| // to exit, which isn’t necessary or desirable. |
| SetConsoleCtrlHandler(ConsoleHandler, true); |
| static TerminateHandler* terminate_handler = new TerminateHandler(); |
| ALLOW_UNUSED_LOCAL(terminate_handler); |
| } |
| |
| #elif defined(OS_FUCHSIA) |
| |
| void InstallCrashHandler() { |
| // There's nothing to do here. Crashes in this process will already be caught |
| // here because this handler process is in the same job that has had its |
| // exception port bound. |
| |
| // TODO(scottmg): This should collect metrics on handler crashes, at a |
| // minimum. https://crashpad.chromium.org/bug/230. |
| } |
| |
| void ReinstallCrashHandler() { |
| // TODO(scottmg): Fuchsia: https://crashpad.chromium.org/bug/196 |
| NOTREACHED(); |
| } |
| |
| #endif // OS_MACOSX |
| |
| void MonitorSelf(const Options& options) { |
| base::FilePath executable_path; |
| if (!Paths::Executable(&executable_path)) { |
| return; |
| } |
| |
| if (std::find(options.monitor_self_arguments.begin(), |
| options.monitor_self_arguments.end(), |
| "--monitor-self") != options.monitor_self_arguments.end()) { |
| LOG(WARNING) << "--monitor-self-argument=--monitor-self is not supported"; |
| return; |
| } |
| std::vector<std::string> extra_arguments(options.monitor_self_arguments); |
| if (!options.identify_client_via_url) { |
| extra_arguments.push_back("--no-identify-client-via-url"); |
| } |
| extra_arguments.push_back("--no-periodic-tasks"); |
| if (!options.rate_limit) { |
| extra_arguments.push_back("--no-rate-limit"); |
| } |
| if (!options.upload_gzip) { |
| extra_arguments.push_back("--no-upload-gzip"); |
| } |
| for (const auto& iterator : options.monitor_self_annotations) { |
| extra_arguments.push_back( |
| base::StringPrintf("--monitor-self-annotation=%s=%s", |
| iterator.first.c_str(), |
| iterator.second.c_str())); |
| } |
| |
| // Don’t use options.metrics_dir. The current implementation only allows one |
| // instance of crashpad_handler to be writing metrics at a time, and it should |
| // be the primary instance. |
| CrashpadClient crashpad_client; |
| #if defined(OS_ANDROID) |
| if (!crashpad_client.StartHandlerAtCrash(executable_path, |
| options.database, |
| base::FilePath(), |
| options.url, |
| options.annotations, |
| extra_arguments)) { |
| return; |
| } |
| #else |
| if (!crashpad_client.StartHandler(executable_path, |
| options.database, |
| base::FilePath(), |
| options.url, |
| options.annotations, |
| extra_arguments, |
| true, |
| false)) { |
| return; |
| } |
| #endif |
| |
| // Make sure that appropriate metrics will be recorded on crash before this |
| // process is terminated. |
| ReinstallCrashHandler(); |
| } |
| |
| class ScopedStoppable { |
| public: |
| ScopedStoppable() = default; |
| |
| ~ScopedStoppable() { |
| if (stoppable_) { |
| stoppable_->Stop(); |
| } |
| } |
| |
| void Reset(Stoppable* stoppable) { stoppable_.reset(stoppable); } |
| |
| Stoppable* Get() { return stoppable_.get(); } |
| |
| private: |
| std::unique_ptr<Stoppable> stoppable_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ScopedStoppable); |
| }; |
| |
| } // namespace |
| |
| int HandlerMain(int argc, |
| char* argv[], |
| const UserStreamDataSources* user_stream_sources) { |
| #if defined(OS_CHROMEOS) |
| if (freopen("/var/log/chrome/chrome", "a", stderr) == nullptr) { |
| PLOG(ERROR) << "Failed to redirect stderr to /var/log/chrome/chrome"; |
| } |
| #endif |
| |
| InstallCrashHandler(); |
| CallMetricsRecordNormalExit metrics_record_normal_exit; |
| |
| const base::FilePath argv0( |
| ToolSupport::CommandLineArgumentToFilePathStringType(argv[0])); |
| const base::FilePath me(argv0.BaseName()); |
| |
| enum OptionFlags { |
| // Long options without short equivalents. |
| kOptionLastChar = 255, |
| kOptionAnnotation, |
| kOptionDatabase, |
| #if defined(OS_MACOSX) |
| kOptionHandshakeFD, |
| #endif // OS_MACOSX |
| #if defined(OS_WIN) |
| kOptionInitialClientData, |
| #endif // OS_WIN |
| #if defined(OS_ANDROID) || defined(OS_LINUX) |
| kOptionInitialClientFD, |
| #endif // OS_ANDROID || OS_LINUX |
| #if defined(OS_MACOSX) |
| kOptionMachService, |
| #endif // OS_MACOSX |
| kOptionMetrics, |
| kOptionMonitorSelf, |
| kOptionMonitorSelfAnnotation, |
| kOptionMonitorSelfArgument, |
| kOptionNoIdentifyClientViaUrl, |
| kOptionNoPeriodicTasks, |
| kOptionNoRateLimit, |
| kOptionNoUploadGzip, |
| #if defined(OS_ANDROID) |
| kOptionNoWriteMinidumpToDatabase, |
| #endif // OS_ANDROID |
| #if defined(OS_WIN) |
| kOptionPipeName, |
| #endif // OS_WIN |
| #if defined(OS_MACOSX) |
| kOptionResetOwnCrashExceptionPortToSystemDefault, |
| #endif // OS_MACOSX |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| kOptionSanitizationInformation, |
| kOptionSharedClientConnection, |
| kOptionTraceParentWithException, |
| #if defined(STARBOARD) |
| kOptionEvergreenInformaton, |
| kOptionHandlerStartedAtCrash, |
| #endif // defined(STARBOARD) |
| #endif |
| kOptionURL, |
| #if defined(OS_CHROMEOS) |
| kOptionUseCrosCrashReporter, |
| kOptionMinidumpDirForTests, |
| kOptionAlwaysAllowFeedback, |
| #endif // OS_CHROMEOS |
| #if defined(OS_ANDROID) |
| kOptionWriteMinidumpToLog, |
| #endif // OS_ANDROID |
| |
| // Standard options. |
| kOptionHelp = -2, |
| kOptionVersion = -3, |
| }; |
| |
| static constexpr option long_options[] = { |
| {"annotation", required_argument, nullptr, kOptionAnnotation}, |
| {"database", required_argument, nullptr, kOptionDatabase}, |
| #if defined(OS_MACOSX) |
| {"handshake-fd", required_argument, nullptr, kOptionHandshakeFD}, |
| #endif // OS_MACOSX |
| #if defined(OS_WIN) |
| {"initial-client-data", |
| required_argument, |
| nullptr, |
| kOptionInitialClientData}, |
| #endif // OS_MACOSX |
| #if defined(OS_ANDROID) || defined(OS_LINUX) |
| {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD}, |
| #endif // OS_ANDROID || OS_LINUX |
| #if defined(OS_MACOSX) |
| {"mach-service", required_argument, nullptr, kOptionMachService}, |
| #endif // OS_MACOSX |
| {"metrics-dir", required_argument, nullptr, kOptionMetrics}, |
| {"monitor-self", no_argument, nullptr, kOptionMonitorSelf}, |
| {"monitor-self-annotation", |
| required_argument, |
| nullptr, |
| kOptionMonitorSelfAnnotation}, |
| {"monitor-self-argument", |
| required_argument, |
| nullptr, |
| kOptionMonitorSelfArgument}, |
| {"no-identify-client-via-url", |
| no_argument, |
| nullptr, |
| kOptionNoIdentifyClientViaUrl}, |
| {"no-periodic-tasks", no_argument, nullptr, kOptionNoPeriodicTasks}, |
| {"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit}, |
| {"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip}, |
| #if defined(OS_ANDROID) |
| {"no-write-minidump-to-database", |
| no_argument, |
| nullptr, |
| kOptionNoWriteMinidumpToDatabase}, |
| #endif // OS_ANDROID |
| #if defined(OS_WIN) |
| {"pipe-name", required_argument, nullptr, kOptionPipeName}, |
| #endif // OS_WIN |
| #if defined(OS_MACOSX) |
| {"reset-own-crash-exception-port-to-system-default", |
| no_argument, |
| nullptr, |
| kOptionResetOwnCrashExceptionPortToSystemDefault}, |
| #endif // OS_MACOSX |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| {"sanitization-information", |
| required_argument, |
| nullptr, |
| kOptionSanitizationInformation}, |
| {"shared-client-connection", |
| no_argument, |
| nullptr, |
| kOptionSharedClientConnection}, |
| {"trace-parent-with-exception", |
| required_argument, |
| nullptr, |
| kOptionTraceParentWithException}, |
| #endif // OS_LINUX || OS_ANDROID |
| #if defined(STARBOARD) |
| {"evergreen-information", |
| required_argument, |
| nullptr, |
| kOptionEvergreenInformaton}, |
| {"handler-started-at-crash", |
| no_argument, |
| nullptr, |
| kOptionHandlerStartedAtCrash}, |
| #endif |
| {"url", required_argument, nullptr, kOptionURL}, |
| #if defined(OS_CHROMEOS) |
| {"use-cros-crash-reporter", |
| no_argument, |
| nullptr, |
| kOptionUseCrosCrashReporter}, |
| {"minidump-dir-for-tests", |
| required_argument, |
| nullptr, |
| kOptionMinidumpDirForTests}, |
| {"always-allow-feedback", |
| no_argument, |
| nullptr, |
| kOptionAlwaysAllowFeedback}, |
| #endif // OS_CHROMEOS |
| #if defined(OS_ANDROID) |
| {"write-minidump-to-log", no_argument, nullptr, kOptionWriteMinidumpToLog}, |
| #endif // OS_ANDROID |
| {"help", no_argument, nullptr, kOptionHelp}, |
| {"version", no_argument, nullptr, kOptionVersion}, |
| {nullptr, 0, nullptr, 0}, |
| }; |
| |
| Options options = {}; |
| #if defined(OS_MACOSX) |
| options.handshake_fd = -1; |
| #endif |
| options.identify_client_via_url = true; |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| options.initial_client_fd = kInvalidFileHandle; |
| #endif |
| options.periodic_tasks = true; |
| options.rate_limit = true; |
| options.upload_gzip = true; |
| #if defined(OS_ANDROID) |
| options.write_minidump_to_database = true; |
| #endif |
| |
| int opt; |
| while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { |
| switch (opt) { |
| case kOptionAnnotation: { |
| if (!AddKeyValueToMap(&options.annotations, optarg, "--annotation")) { |
| return ExitFailure(); |
| } |
| break; |
| } |
| case kOptionDatabase: { |
| options.database = base::FilePath( |
| ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); |
| break; |
| } |
| #if defined(OS_MACOSX) |
| case kOptionHandshakeFD: { |
| if (!StringToNumber(optarg, &options.handshake_fd) || |
| options.handshake_fd < 0) { |
| ToolSupport::UsageHint(me, |
| "--handshake-fd requires a file descriptor"); |
| return ExitFailure(); |
| } |
| break; |
| } |
| case kOptionMachService: { |
| options.mach_service = optarg; |
| break; |
| } |
| #endif // OS_MACOSX |
| #if defined(OS_WIN) |
| case kOptionInitialClientData: { |
| if (!options.initial_client_data.InitializeFromString(optarg)) { |
| ToolSupport::UsageHint( |
| me, "failed to parse --initial-client-data"); |
| return ExitFailure(); |
| } |
| break; |
| } |
| #endif // OS_WIN |
| #if defined(OS_ANDROID) || defined(OS_LINUX) |
| case kOptionInitialClientFD: { |
| if (!base::StringToInt(optarg, &options.initial_client_fd)) { |
| ToolSupport::UsageHint(me, "failed to parse --initial-client-fd"); |
| return ExitFailure(); |
| } |
| break; |
| } |
| #endif // OS_ANDROID || OS_LINUX |
| case kOptionMetrics: { |
| options.metrics_dir = base::FilePath( |
| ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); |
| break; |
| } |
| case kOptionMonitorSelf: { |
| options.monitor_self = true; |
| break; |
| } |
| case kOptionMonitorSelfAnnotation: { |
| if (!AddKeyValueToMap(&options.monitor_self_annotations, |
| optarg, |
| "--monitor-self-annotation")) { |
| return ExitFailure(); |
| } |
| break; |
| } |
| case kOptionMonitorSelfArgument: { |
| options.monitor_self_arguments.push_back(optarg); |
| break; |
| } |
| case kOptionNoIdentifyClientViaUrl: { |
| options.identify_client_via_url = false; |
| break; |
| } |
| case kOptionNoPeriodicTasks: { |
| options.periodic_tasks = false; |
| break; |
| } |
| case kOptionNoRateLimit: { |
| options.rate_limit = false; |
| break; |
| } |
| case kOptionNoUploadGzip: { |
| options.upload_gzip = false; |
| break; |
| } |
| #if defined(OS_ANDROID) |
| case kOptionNoWriteMinidumpToDatabase: { |
| options.write_minidump_to_database = false; |
| break; |
| } |
| #endif // OS_ANDROID |
| #if defined(OS_WIN) |
| case kOptionPipeName: { |
| options.pipe_name = optarg; |
| break; |
| } |
| #endif // OS_WIN |
| #if defined(OS_MACOSX) |
| case kOptionResetOwnCrashExceptionPortToSystemDefault: { |
| options.reset_own_crash_exception_port_to_system_default = true; |
| break; |
| } |
| #endif // OS_MACOSX |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| case kOptionSanitizationInformation: { |
| if (!StringToNumber(optarg, |
| &options.sanitization_information_address)) { |
| ToolSupport::UsageHint(me, |
| "failed to parse --sanitization-information"); |
| return ExitFailure(); |
| } |
| break; |
| } |
| case kOptionSharedClientConnection: { |
| options.shared_client_connection = true; |
| break; |
| } |
| case kOptionTraceParentWithException: { |
| if (!StringToNumber(optarg, &options.exception_information_address)) { |
| ToolSupport::UsageHint( |
| me, "failed to parse --trace-parent-with-exception"); |
| return ExitFailure(); |
| } |
| break; |
| } |
| #if defined(STARBOARD) |
| case kOptionEvergreenInformaton: { |
| if (!StringToNumber(optarg, |
| &options.evergreen_information_address)) { |
| ToolSupport::UsageHint(me, |
| "failed to parse --evergreen-information"); |
| return ExitFailure(); |
| } |
| break; |
| } |
| case kOptionHandlerStartedAtCrash: { |
| options.handler_started_at_crash = true; |
| break; |
| } |
| #endif // defined(STARBOARD) |
| #endif // OS_LINUX || OS_ANDROID |
| case kOptionURL: { |
| options.url = optarg; |
| break; |
| } |
| #if defined(OS_CHROMEOS) |
| case kOptionUseCrosCrashReporter: { |
| options.use_cros_crash_reporter = true; |
| break; |
| } |
| case kOptionMinidumpDirForTests: { |
| options.minidump_dir_for_tests = base::FilePath( |
| ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); |
| break; |
| } |
| case kOptionAlwaysAllowFeedback: { |
| options.always_allow_feedback = true; |
| break; |
| } |
| #endif // OS_CHROMEOS |
| #if defined(OS_ANDROID) |
| case kOptionWriteMinidumpToLog: { |
| options.write_minidump_to_log = true; |
| break; |
| } |
| #endif // OS_ANDROID |
| case kOptionHelp: { |
| Usage(me); |
| MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly); |
| return EXIT_SUCCESS; |
| } |
| case kOptionVersion: { |
| ToolSupport::Version(me); |
| MetricsRecordExit(Metrics::LifetimeMilestone::kExitedEarly); |
| return EXIT_SUCCESS; |
| } |
| default: { |
| ToolSupport::UsageHint(me, nullptr); |
| return ExitFailure(); |
| } |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| #if defined(OS_MACOSX) |
| if (options.handshake_fd < 0 && options.mach_service.empty()) { |
| ToolSupport::UsageHint(me, "--handshake-fd or --mach-service is required"); |
| return ExitFailure(); |
| } |
| if (options.handshake_fd >= 0 && !options.mach_service.empty()) { |
| ToolSupport::UsageHint( |
| me, "--handshake-fd and --mach-service are incompatible"); |
| return ExitFailure(); |
| } |
| #elif defined(OS_WIN) |
| if (!options.initial_client_data.IsValid() && options.pipe_name.empty()) { |
| ToolSupport::UsageHint(me, |
| "--initial-client-data or --pipe-name is required"); |
| return ExitFailure(); |
| } |
| if (options.initial_client_data.IsValid() && !options.pipe_name.empty()) { |
| ToolSupport::UsageHint( |
| me, "--initial-client-data and --pipe-name are incompatible"); |
| return ExitFailure(); |
| } |
| #elif defined(OS_LINUX) || defined(OS_ANDROID) |
| if (!options.exception_information_address && |
| options.initial_client_fd == kInvalidFileHandle) { |
| ToolSupport::UsageHint( |
| me, "--trace-parent-with-exception or --initial-client-fd is required"); |
| return ExitFailure(); |
| } |
| if (options.sanitization_information_address && |
| !options.exception_information_address) { |
| ToolSupport::UsageHint( |
| me, |
| "--sanitization_information requires --trace-parent-with-exception"); |
| return ExitFailure(); |
| } |
| if (options.shared_client_connection && |
| options.initial_client_fd == kInvalidFileHandle) { |
| ToolSupport::UsageHint( |
| me, "--shared-client-connection requires --initial-client-fd"); |
| return ExitFailure(); |
| } |
| #if defined(OS_ANDROID) |
| if (!options.write_minidump_to_log && !options.write_minidump_to_database) { |
| ToolSupport::UsageHint(me, |
| "--no_write_minidump_to_database is required to use " |
| "with --write_minidump_to_log."); |
| ExitFailure(); |
| } |
| #endif // OS_ANDROID |
| #endif // OS_MACOSX |
| |
| if (options.database.empty()) { |
| ToolSupport::UsageHint(me, "--database is required"); |
| return ExitFailure(); |
| } |
| |
| if (argc) { |
| ToolSupport::UsageHint(me, nullptr); |
| return ExitFailure(); |
| } |
| |
| #if defined(OS_MACOSX) |
| if (options.reset_own_crash_exception_port_to_system_default) { |
| CrashpadClient::UseSystemDefaultHandler(); |
| } |
| #endif // OS_MACOSX |
| |
| if (options.monitor_self) { |
| MonitorSelf(options); |
| } |
| |
| if (!options.monitor_self_annotations.empty()) { |
| // Establish these annotations even if --monitor-self is not present, in |
| // case something such as generate_dump wants to try to access them later. |
| // |
| // If the handler is part of a multi-purpose executable, simple annotations |
| // may already be present for this module. If they are, use them. |
| CrashpadInfo* crashpad_info = CrashpadInfo::GetCrashpadInfo(); |
| SimpleStringDictionary* module_annotations = |
| crashpad_info->simple_annotations(); |
| if (!module_annotations) { |
| module_annotations = new SimpleStringDictionary(); |
| crashpad_info->set_simple_annotations(module_annotations); |
| } |
| |
| for (const auto& iterator : options.monitor_self_annotations) { |
| module_annotations->SetKeyValue(iterator.first.c_str(), |
| iterator.second.c_str()); |
| } |
| } |
| |
| std::unique_ptr<CrashReportDatabase> database( |
| CrashReportDatabase::Initialize(options.database)); |
| if (!database) { |
| return ExitFailure(); |
| } |
| |
| ScopedStoppable upload_thread; |
| if (!options.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.identify_client_via_url = |
| options.identify_client_via_url; |
| upload_thread_options.rate_limit = options.rate_limit; |
| upload_thread_options.upload_gzip = options.upload_gzip; |
| upload_thread_options.watch_pending_reports = options.periodic_tasks; |
| |
| upload_thread.Reset(new CrashReportUploadThread( |
| database.get(), options.url, upload_thread_options)); |
| upload_thread.Get()->Start(); |
| } |
| |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| std::unique_ptr<ExceptionHandlerServer::Delegate> exception_handler; |
| #else |
| std::unique_ptr<CrashReportExceptionHandler> exception_handler; |
| #endif |
| |
| #if defined(OS_CHROMEOS) |
| if (options.use_cros_crash_reporter) { |
| auto cros_handler = std::make_unique<CrosCrashReportExceptionHandler>( |
| database.get(), |
| &options.annotations, |
| user_stream_sources); |
| |
| if (!options.minidump_dir_for_tests.empty()) { |
| cros_handler->SetDumpDir(options.minidump_dir_for_tests); |
| } |
| |
| if (options.always_allow_feedback) { |
| cros_handler->SetAlwaysAllowFeedback(); |
| } |
| |
| exception_handler = std::move(cros_handler); |
| } else { |
| exception_handler = std::make_unique<CrashReportExceptionHandler>( |
| database.get(), |
| static_cast<CrashReportUploadThread*>(upload_thread.Get()), |
| &options.annotations, |
| true, |
| false, |
| user_stream_sources); |
| } |
| #else |
| exception_handler = std::make_unique<CrashReportExceptionHandler>( |
| database.get(), |
| static_cast<CrashReportUploadThread*>(upload_thread.Get()), |
| &options.annotations, |
| #if defined(OS_FUCHSIA) |
| // TODO(scottmg): Process level file attachments, and for all platforms. |
| nullptr, |
| #endif |
| #if defined(OS_ANDROID) |
| options.write_minidump_to_database, |
| options.write_minidump_to_log, |
| #endif // OS_ANDROID |
| #if defined(OS_LINUX) |
| true, |
| false, |
| #endif // OS_LINUX |
| user_stream_sources); |
| #endif // OS_CHROMEOS |
| |
| #if defined(OS_LINUX) || defined(OS_ANDROID) |
| if (options.exception_information_address) { |
| ExceptionHandlerProtocol::ClientInformation info; |
| info.exception_information_address = options.exception_information_address; |
| info.sanitization_information_address = |
| options.sanitization_information_address; |
| #if defined(STARBOARD) |
| info.evergreen_information_address = |
| options.evergreen_information_address; |
| if (options.handler_started_at_crash) { |
| info.handler_start_type = ExceptionHandlerProtocol::kStartAtCrash; |
| } |
| #endif // defined(STARBOARD) |
| return exception_handler->HandleException(getppid(), geteuid(), info) |
| ? EXIT_SUCCESS |
| : ExitFailure(); |
| } |
| #endif // OS_LINUX || OS_ANDROID |
| |
| ScopedStoppable prune_thread; |
| if (options.periodic_tasks) { |
| prune_thread.Reset(new PruneCrashReportThread( |
| database.get(), PruneCondition::GetDefault())); |
| prune_thread.Get()->Start(); |
| } |
| |
| #if defined(OS_MACOSX) |
| if (options.mach_service.empty()) { |
| // Don’t do this when being run by launchd. See launchd.plist(5). |
| CloseStdinAndStdout(); |
| } |
| |
| base::mac::ScopedMachReceiveRight receive_right; |
| |
| if (options.handshake_fd >= 0) { |
| receive_right.reset( |
| ChildPortHandshake::RunServerForFD( |
| base::ScopedFD(options.handshake_fd), |
| ChildPortHandshake::PortRightType::kReceiveRight)); |
| } else if (!options.mach_service.empty()) { |
| receive_right = BootstrapCheckIn(options.mach_service); |
| } |
| |
| if (!receive_right.is_valid()) { |
| return ExitFailure(); |
| } |
| |
| ExceptionHandlerServer exception_handler_server( |
| std::move(receive_right), !options.mach_service.empty()); |
| base::AutoReset<ExceptionHandlerServer*> reset_g_exception_handler_server( |
| &g_exception_handler_server, &exception_handler_server); |
| |
| struct sigaction old_sigterm_action; |
| ScopedResetSIGTERM reset_sigterm; |
| if (!options.mach_service.empty()) { |
| // When running from launchd, no no-senders notification could ever be |
| // triggered, because launchd maintains a send right to the service. When |
| // launchd wants the job to exit, it will send a SIGTERM. See |
| // launchd.plist(5). |
| // |
| // Set up a SIGTERM handler that will call exception_handler_server.Stop(). |
| // This replaces the HandleTerminateSignal handler for SIGTERM. |
| if (Signals::InstallHandler( |
| SIGTERM, HandleSIGTERM, 0, &old_sigterm_action)) { |
| reset_sigterm.reset(&old_sigterm_action); |
| } |
| } |
| |
| RecordFileLimitAnnotation(); |
| #elif defined(OS_WIN) |
| // Shut down as late as possible relative to programs we're watching. |
| if (!SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY)) |
| PLOG(ERROR) << "SetProcessShutdownParameters"; |
| |
| ExceptionHandlerServer exception_handler_server(!options.pipe_name.empty()); |
| |
| if (!options.pipe_name.empty()) { |
| exception_handler_server.SetPipeName(base::UTF8ToUTF16(options.pipe_name)); |
| } |
| #elif defined(OS_FUCHSIA) |
| // These handles are logically "moved" into these variables when retrieved by |
| // zx_take_startup_handle(). Both are given to ExceptionHandlerServer which |
| // owns them in this process. There is currently no "connect-later" mode on |
| // Fuchsia, all the binding must be done by the client before starting |
| // crashpad_handler. |
| zx::job root_job(zx_take_startup_handle(PA_HND(PA_USER0, 0))); |
| if (!root_job.is_valid()) { |
| LOG(ERROR) << "no job handle passed in startup handle 0"; |
| return EXIT_FAILURE; |
| } |
| |
| zx::channel exception_channel(zx_take_startup_handle(PA_HND(PA_USER0, 1))); |
| if (!exception_channel.is_valid()) { |
| LOG(ERROR) << "no exception channel handle passed in startup handle 1"; |
| return EXIT_FAILURE; |
| } |
| |
| ExceptionHandlerServer exception_handler_server(std::move(root_job), |
| std::move(exception_channel)); |
| #elif defined(OS_LINUX) || defined(OS_ANDROID) |
| ExceptionHandlerServer exception_handler_server; |
| #endif // OS_MACOSX |
| |
| base::GlobalHistogramAllocator* histogram_allocator = nullptr; |
| if (!options.metrics_dir.empty()) { |
| static constexpr char kMetricsName[] = "CrashpadMetrics"; |
| constexpr size_t kMetricsFileSize = 1 << 20; |
| if (base::GlobalHistogramAllocator::CreateWithActiveFileInDir( |
| options.metrics_dir, kMetricsFileSize, 0, kMetricsName)) { |
| histogram_allocator = base::GlobalHistogramAllocator::Get(); |
| histogram_allocator->CreateTrackingHistograms(kMetricsName); |
| } |
| } |
| |
| Metrics::HandlerLifetimeMilestone(Metrics::LifetimeMilestone::kStarted); |
| |
| #if defined(OS_WIN) |
| if (options.initial_client_data.IsValid()) { |
| exception_handler_server.InitializeWithInheritedDataForInitialClient( |
| options.initial_client_data, exception_handler.get()); |
| } |
| #elif defined(OS_LINUX) || defined(OS_ANDROID) |
| if (options.initial_client_fd == kInvalidFileHandle || |
| !exception_handler_server.InitializeWithClient( |
| ScopedFileHandle(options.initial_client_fd), |
| options.shared_client_connection)) { |
| return ExitFailure(); |
| } |
| #endif // OS_WIN |
| |
| exception_handler_server.Run(exception_handler.get()); |
| |
| return EXIT_SUCCESS; |
| } |
| |
| } // namespace crashpad |