diff --git a/handler/crashpad_handler.md b/handler/crashpad_handler.md index 30dacfe..9544860 100644 --- a/handler/crashpad_handler.md +++ b/handler/crashpad_handler.md @@ -138,6 +138,38 @@ establish the Crashpad client environment before running a program. service declared in a job’s `MachServices` dictionary (see launchd.plist(5)). The service name may also be completely unknown to the system. + * **--metrics-dir**=_DIR_ + + Metrics information will be written to _DIR_. This option only has an effect + when built as part of Chromium. In non-Chromium builds, and in the absence of + this option, metrics information will not be written. + + * **--monitor-self** + + Causes a second instance of the Crashpad handler program to be started, + monitoring the original instance for exceptions. The original instance will + become a client of the second one. The second instance will be started with + the same **--annotation**, **--database**, **--no-rate-limit**, + **--no-upload-gzip**, and **--url** arguments as the original one. The second + instance will not be started with a **--metrics-dir** argument even if the + original instance was. + + Where supported by the underlying operating system, the second instance will + be restarted should it exit before the first instance. The second instance + will not be eligible to be started asynchronously. + + * **--monitor-self-argument**=_ARGUMENT_ + + When directed by **--monitor-self** to start a second instance of the + Crashpad handler program, the second instance will be started with _ARGUMENT_ + as one of its arguments. This option may appear zero, one, or more times. + This option has no effect in the absence of **--monitor-self**. + + This supports embedding the Crashpad handler into a multi-purpose executable + that dispatches to the desired entry point based on a command-line argument. + To prevent excessive accumulation of handler processes, _ARGUMENT_ must not + be `--monitor-self`. + * **--no-rate-limit** Do not rate limit the upload of crash reports. By default uploads are diff --git a/handler/handler_main.cc b/handler/handler_main.cc index 16eb114..661a19e 100644 --- a/handler/handler_main.cc +++ b/handler/handler_main.cc @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include "tools/tool_support.h" #include "util/file/file_io.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" @@ -83,8 +85,8 @@ void Usage(const base::FilePath& me) { " --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" -" --mach-service=SERVICE register SERVICE with the bootstrap server\n" -#elif defined(OS_WIN) +#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" @@ -94,15 +96,22 @@ void Usage(const base::FilePath& me) { " 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_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-argument=ARGUMENT\n" +" provide additional arguments to the second handler\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_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" -#elif defined(OS_WIN) -" --pipe-name=PIPE communicate with the client over PIPE\n" #endif // OS_MACOSX " --url=URL send crash reports to this Breakpad server URL,\n" " only if uploads are enabled for the database\n" @@ -112,6 +121,25 @@ void Usage(const base::FilePath& me) { ToolSupport::UsageTail(me); } +struct Options { + std::map annotations; + std::string url; + base::FilePath database; + base::FilePath metrics_dir; + std::vector 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_WIN) + std::string pipe_name; + InitialClientData initial_client_data; +#endif // OS_MACOSX + bool monitor_self; + bool rate_limit; + bool upload_gzip; +}; + // 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 @@ -188,6 +216,13 @@ void HandleTerminateSignal(int sig, siginfo_t* siginfo, void* context) { 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. +} + void InstallCrashHandler() { Signals::InstallCrashHandlers(HandleCrashSignal, 0, nullptr); @@ -254,9 +289,17 @@ class TerminateHandler final : public SessionEndWatcher { DISALLOW_COPY_AND_ASSIGN(TerminateHandler); }; -void InstallCrashHandler() { +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 reintsall 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 @@ -268,6 +311,46 @@ void InstallCrashHandler() { #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 extra_arguments(options.monitor_self_arguments); + if (!options.rate_limit) { + extra_arguments.push_back("--no-rate-limit"); + } + if (!options.upload_gzip) { + extra_arguments.push_back("--no-upload-gzip"); + } + + // 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 (!crashpad_client.StartHandler(executable_path, + options.database, + base::FilePath(), + options.url, + options.annotations, + extra_arguments, + true, + false)) { + return; + } + + // Make sure that appropriate metrics will be recorded on crash before this + // process is terminated. + ReinstallCrashHandler(); +} + } // namespace int HandlerMain(int argc, char* argv[]) { @@ -293,12 +376,15 @@ int HandlerMain(int argc, char* argv[]) { kOptionMachService, #endif // OS_MACOSX kOptionMetrics, + kOptionMonitorSelf, + kOptionMonitorSelfArgument, kOptionNoRateLimit, kOptionNoUploadGzip, +#if defined(OS_WIN) + kOptionPipeName, +#endif // OS_WIN #if defined(OS_MACOSX) kOptionResetOwnCrashExceptionPortToSystemDefault, -#elif defined(OS_WIN) - kOptionPipeName, #endif // OS_MACOSX kOptionURL, @@ -307,28 +393,6 @@ int HandlerMain(int argc, char* argv[]) { kOptionVersion = -3, }; - struct { - std::map annotations; - std::string url; - const char* database; - const char* metrics; -#if defined(OS_MACOSX) - int handshake_fd; - std::string mach_service; - bool reset_own_crash_exception_port_to_system_default; -#elif defined(OS_WIN) - std::string pipe_name; - InitialClientData initial_client_data; -#endif // OS_MACOSX - bool rate_limit; - bool upload_gzip; - } options = {}; -#if defined(OS_MACOSX) - options.handshake_fd = -1; -#endif - options.rate_limit = true; - options.upload_gzip = true; - const option long_options[] = { {"annotation", required_argument, nullptr, kOptionAnnotation}, {"database", required_argument, nullptr, kOptionDatabase}, @@ -345,15 +409,21 @@ int HandlerMain(int argc, char* argv[]) { {"mach-service", required_argument, nullptr, kOptionMachService}, #endif // OS_MACOSX {"metrics-dir", required_argument, nullptr, kOptionMetrics}, + {"monitor-self", no_argument, nullptr, kOptionMonitorSelf}, + {"monitor-self-argument", + required_argument, + nullptr, + kOptionMonitorSelfArgument}, {"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit}, {"no-upload-gzip", no_argument, nullptr, kOptionNoUploadGzip}, +#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}, -#elif defined(OS_WIN) - {"pipe-name", required_argument, nullptr, kOptionPipeName}, #endif // OS_MACOSX {"url", required_argument, nullptr, kOptionURL}, {"help", no_argument, nullptr, kOptionHelp}, @@ -361,6 +431,13 @@ int HandlerMain(int argc, char* argv[]) { {nullptr, 0, nullptr, 0}, }; + Options options = {}; +#if defined(OS_MACOSX) + options.handshake_fd = -1; +#endif + options.rate_limit = true; + options.upload_gzip = true; + int opt; while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { switch (opt) { @@ -379,7 +456,8 @@ int HandlerMain(int argc, char* argv[]) { break; } case kOptionDatabase: { - options.database = optarg; + options.database = base::FilePath( + ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); break; } #if defined(OS_MACOSX) @@ -396,7 +474,8 @@ int HandlerMain(int argc, char* argv[]) { options.mach_service = optarg; break; } -#elif defined(OS_WIN) +#endif // OS_MACOSX +#if defined(OS_WIN) case kOptionInitialClientData: { if (!options.initial_client_data.InitializeFromString(optarg)) { ToolSupport::UsageHint( @@ -405,9 +484,18 @@ int HandlerMain(int argc, char* argv[]) { } break; } -#endif // OS_MACOSX +#endif // OS_WIN case kOptionMetrics: { - options.metrics = optarg; + options.metrics_dir = base::FilePath( + ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); + break; + } + case kOptionMonitorSelf: { + options.monitor_self = true; + break; + } + case kOptionMonitorSelfArgument: { + options.monitor_self_arguments.push_back(optarg); break; } case kOptionNoRateLimit: { @@ -418,16 +506,17 @@ int HandlerMain(int argc, char* argv[]) { options.upload_gzip = false; break; } +#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; } -#elif defined(OS_WIN) - case kOptionPipeName: { - options.pipe_name = optarg; - break; - } #endif // OS_MACOSX case kOptionURL: { options.url = optarg; @@ -475,7 +564,7 @@ int HandlerMain(int argc, char* argv[]) { } #endif // OS_MACOSX - if (!options.database) { + if (options.database.empty()) { ToolSupport::UsageHint(me, "--database is required"); return ExitFailure(); } @@ -485,16 +574,22 @@ int HandlerMain(int argc, char* argv[]) { 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 defined(OS_MACOSX) if (options.mach_service.empty()) { // Don’t do this when being run by launchd. See launchd.plist(5). CloseStdinAndStdout(); } - if (options.reset_own_crash_exception_port_to_system_default) { - CrashpadClient::UseSystemDefaultHandler(); - } - base::mac::ScopedMachReceiveRight receive_right; if (options.handshake_fd >= 0) { @@ -543,13 +638,11 @@ int HandlerMain(int argc, char* argv[]) { #endif // OS_MACOSX base::GlobalHistogramAllocator* histogram_allocator = nullptr; - if (options.metrics) { - const base::FilePath metrics_dir( - ToolSupport::CommandLineArgumentToFilePathStringType(options.metrics)); + if (!options.metrics_dir.empty()) { static const char kMetricsName[] = "CrashpadMetrics"; const size_t kMetricsFileSize = 1 << 20; if (base::GlobalHistogramAllocator::CreateWithActiveFileInDir( - metrics_dir, kMetricsFileSize, 0, kMetricsName)) { + options.metrics_dir, kMetricsFileSize, 0, kMetricsName)) { histogram_allocator = base::GlobalHistogramAllocator::Get(); histogram_allocator->CreateTrackingHistograms(kMetricsName); } @@ -557,9 +650,8 @@ int HandlerMain(int argc, char* argv[]) { Metrics::HandlerLifetimeMilestone(Metrics::LifetimeMilestone::kStarted); - std::unique_ptr database(CrashReportDatabase::Initialize( - base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType( - options.database)))); + std::unique_ptr database( + CrashReportDatabase::Initialize(options.database)); if (!database) { return ExitFailure(); } diff --git a/third_party/getopt/README.crashpad b/third_party/getopt/README.crashpad index a7166e1..b7ff954 100644 --- a/third_party/getopt/README.crashpad +++ b/third_party/getopt/README.crashpad @@ -9,9 +9,12 @@ Description: A public domain implementation of getopt. Local Modifications: -- Minor compilation fixes applied for Windows. -- Add copy of copyright (Public domain) to the top of both files for Chromium's - checklicenses step. -- Compiled as .cc, and wrapped in namespace crashpad. -- memcmp() -> strncmp() in getopt.cc to make ASan happier about some string - manipulation. + - Minor compilation fixes applied for Windows. + - NO_ARG, REQUIRED_ARG, and OPTIONAL_ARG were renamed to the more traditional + no_argument, required_argument, and optional_argument for source + compatibility with BSD and glibc getopt_long(). + - Add copy of copyright (Public domain) to the top of both files for Chromium's + checklicenses step. + - Compiled as .cc, and wrapped in namespace crashpad. + - memcmp() -> strncmp() in getopt.cc to make ASan happier about some string + manipulation. diff --git a/third_party/getopt/getopt.cc b/third_party/getopt/getopt.cc index 137218b..7dfd888 100644 --- a/third_party/getopt/getopt.cc +++ b/third_party/getopt/getopt.cc @@ -310,16 +310,17 @@ getopt_internal (int argc, char **argv, char *shortopts, } return (optopt = '?'); } - has_arg = ((cp[1] == ':') - ? ((cp[2] == ':') ? OPTIONAL_ARG : required_argument) : no_argument); - possible_arg = argv[optind] + optwhere + 1; - optopt = *cp; + has_arg = ((cp[1] == ':') ? ((cp[2] == ':') ? optional_argument + : required_argument) + : no_argument); + possible_arg = argv[optind] + optwhere + 1; + optopt = *cp; } /* get argument and reset optwhere */ arg_next = 0; switch (has_arg) { - case OPTIONAL_ARG: + case optional_argument: if (*possible_arg == '=') possible_arg++; if (*possible_arg != '\0') diff --git a/third_party/getopt/getopt.h b/third_party/getopt/getopt.h index 282a489..27ab0dd 100644 --- a/third_party/getopt/getopt.h +++ b/third_party/getopt/getopt.h @@ -14,7 +14,7 @@ using it. /* macros defined by this include file */ #define no_argument 0 #define required_argument 1 -#define OPTIONAL_ARG 2 +#define optional_argument 2 /* types defined by this include file */