mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-29 08:39:44 +08:00
330adfb029
This is a temporary measure to try to account for lower than expected upload volume from Chrome in the wild. So this doesn't fix bug 23, but is related. The ability to delimit the upload rate is useful when testing locally too. R=mark@chromium.org BUG=crashpad:23 Review URL: https://codereview.chromium.org/1563683002 .
419 lines
13 KiB
C++
419 lines
13 KiB
C++
// 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 <getopt.h>
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
|
||
#include <map>
|
||
#include <string>
|
||
#include <utility>
|
||
|
||
#include "base/auto_reset.h"
|
||
#include "base/files/file_path.h"
|
||
#include "base/files/scoped_file.h"
|
||
#include "base/logging.h"
|
||
#include "base/memory/scoped_ptr.h"
|
||
#include "base/scoped_generic.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/prune_crash_reports.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/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_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 "util/mach/child_port_handshake.h"
|
||
#include "util/mach/mach_extensions.h"
|
||
#include "util/posix/close_stdio.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"
|
||
#endif // OS_MACOSX
|
||
|
||
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"
|
||
" --mach-service=SERVICE register SERVICE with the bootstrap server\n"
|
||
#elif defined(OS_WIN)
|
||
" --handshake-handle=HANDLE\n"
|
||
" create a new pipe and send its name via HANDLE\n"
|
||
#endif // OS_MACOSX
|
||
" --no-rate-limit don't rate limit crash uploads\n"
|
||
#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"
|
||
" --help display this help and exit\n"
|
||
" --version output version information and exit\n",
|
||
me.value().c_str());
|
||
ToolSupport::UsageTail(me);
|
||
}
|
||
|
||
#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) {
|
||
DCHECK(g_exception_handler_server);
|
||
g_exception_handler_server->Stop();
|
||
}
|
||
|
||
#endif // OS_MACOSX
|
||
|
||
} // namespace
|
||
|
||
int HandlerMain(int argc, char* argv[]) {
|
||
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,
|
||
kOptionMachService,
|
||
#elif defined(OS_WIN)
|
||
kOptionHandshakeHandle,
|
||
#endif // OS_MACOSX
|
||
kOptionNoRateLimit,
|
||
#if defined(OS_MACOSX)
|
||
kOptionResetOwnCrashExceptionPortToSystemDefault,
|
||
#elif defined(OS_WIN)
|
||
kOptionPipeName,
|
||
#endif // OS_MACOSX
|
||
kOptionURL,
|
||
|
||
// Standard options.
|
||
kOptionHelp = -2,
|
||
kOptionVersion = -3,
|
||
};
|
||
|
||
struct {
|
||
std::map<std::string, std::string> annotations;
|
||
std::string url;
|
||
const char* database;
|
||
#if defined(OS_MACOSX)
|
||
int handshake_fd;
|
||
std::string mach_service;
|
||
bool reset_own_crash_exception_port_to_system_default;
|
||
#elif defined(OS_WIN)
|
||
HANDLE handshake_handle;
|
||
std::string pipe_name;
|
||
#endif // OS_MACOSX
|
||
bool rate_limit;
|
||
} options = {};
|
||
#if defined(OS_MACOSX)
|
||
options.handshake_fd = -1;
|
||
#elif defined(OS_WIN)
|
||
options.handshake_handle = INVALID_HANDLE_VALUE;
|
||
#endif
|
||
options.rate_limit = true;
|
||
|
||
const option long_options[] = {
|
||
{"annotation", required_argument, nullptr, kOptionAnnotation},
|
||
{"database", required_argument, nullptr, kOptionDatabase},
|
||
#if defined(OS_MACOSX)
|
||
{"handshake-fd", required_argument, nullptr, kOptionHandshakeFD},
|
||
{"mach-service", required_argument, nullptr, kOptionMachService},
|
||
#elif defined(OS_WIN)
|
||
{"handshake-handle", required_argument, nullptr, kOptionHandshakeHandle},
|
||
#endif // OS_MACOSX
|
||
{"no-rate-limit", no_argument, nullptr, kOptionNoRateLimit},
|
||
#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},
|
||
{"version", no_argument, nullptr, kOptionVersion},
|
||
{nullptr, 0, nullptr, 0},
|
||
};
|
||
|
||
int opt;
|
||
while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) {
|
||
switch (opt) {
|
||
case kOptionAnnotation: {
|
||
std::string key;
|
||
std::string value;
|
||
if (!SplitString(optarg, '=', &key, &value)) {
|
||
ToolSupport::UsageHint(me, "--annotation requires KEY=VALUE");
|
||
return EXIT_FAILURE;
|
||
}
|
||
std::string old_value;
|
||
if (!MapInsertOrReplace(&options.annotations, key, value, &old_value)) {
|
||
LOG(WARNING) << "duplicate key " << key << ", discarding value "
|
||
<< old_value;
|
||
}
|
||
break;
|
||
}
|
||
case kOptionDatabase: {
|
||
options.database = 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 EXIT_FAILURE;
|
||
}
|
||
break;
|
||
}
|
||
case kOptionMachService: {
|
||
options.mach_service = optarg;
|
||
break;
|
||
}
|
||
#elif defined(OS_WIN)
|
||
case kOptionHandshakeHandle: {
|
||
// Use unsigned int, because the handle was presented by the client in
|
||
// 0x%x format.
|
||
unsigned int handle_uint;
|
||
if (!StringToNumber(optarg, &handle_uint) ||
|
||
(options.handshake_handle = IntToHandle(handle_uint)) ==
|
||
INVALID_HANDLE_VALUE) {
|
||
ToolSupport::UsageHint(me, "--handshake-handle requires a HANDLE");
|
||
return EXIT_FAILURE;
|
||
}
|
||
break;
|
||
}
|
||
#endif // OS_MACOSX
|
||
case kOptionNoRateLimit: {
|
||
options.rate_limit = false;
|
||
break;
|
||
}
|
||
#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;
|
||
break;
|
||
}
|
||
case kOptionHelp: {
|
||
Usage(me);
|
||
return EXIT_SUCCESS;
|
||
}
|
||
case kOptionVersion: {
|
||
ToolSupport::Version(me);
|
||
return EXIT_SUCCESS;
|
||
}
|
||
default: {
|
||
ToolSupport::UsageHint(me, nullptr);
|
||
return EXIT_FAILURE;
|
||
}
|
||
}
|
||
}
|
||
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 EXIT_FAILURE;
|
||
}
|
||
if (options.handshake_fd >= 0 && !options.mach_service.empty()) {
|
||
ToolSupport::UsageHint(
|
||
me, "--handshake-fd and --mach-service are incompatible");
|
||
return EXIT_FAILURE;
|
||
}
|
||
#elif defined(OS_WIN)
|
||
if (options.handshake_handle == INVALID_HANDLE_VALUE &&
|
||
options.pipe_name.empty()) {
|
||
ToolSupport::UsageHint(me, "--handshake-handle or --pipe-name is required");
|
||
return EXIT_FAILURE;
|
||
}
|
||
if (options.handshake_handle != INVALID_HANDLE_VALUE &&
|
||
!options.pipe_name.empty()) {
|
||
ToolSupport::UsageHint(
|
||
me, "--handshake-handle and --pipe-name are incompatible");
|
||
return EXIT_FAILURE;
|
||
}
|
||
#endif // OS_MACOSX
|
||
|
||
if (!options.database) {
|
||
ToolSupport::UsageHint(me, "--database is required");
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
if (argc) {
|
||
ToolSupport::UsageHint(me, nullptr);
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
#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) {
|
||
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 EXIT_FAILURE;
|
||
}
|
||
|
||
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_sa;
|
||
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().
|
||
struct sigaction sa = {};
|
||
sigemptyset(&sa.sa_mask);
|
||
sa.sa_flags = SA_SIGINFO;
|
||
sa.sa_sigaction = HandleSIGTERM;
|
||
int rv = sigaction(SIGTERM, &sa, &old_sa);
|
||
PCHECK(rv == 0) << "sigaction";
|
||
reset_sigterm.reset(&old_sa);
|
||
}
|
||
#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));
|
||
} else if (options.handshake_handle != INVALID_HANDLE_VALUE) {
|
||
std::wstring pipe_name = exception_handler_server.CreatePipe();
|
||
|
||
uint32_t pipe_name_length = static_cast<uint32_t>(pipe_name.size());
|
||
if (!LoggingWriteFile(options.handshake_handle,
|
||
&pipe_name_length,
|
||
sizeof(pipe_name_length))) {
|
||
return EXIT_FAILURE;
|
||
}
|
||
if (!LoggingWriteFile(options.handshake_handle,
|
||
pipe_name.c_str(),
|
||
pipe_name.size() * sizeof(pipe_name[0]))) {
|
||
return EXIT_FAILURE;
|
||
}
|
||
}
|
||
#endif // OS_MACOSX
|
||
|
||
scoped_ptr<CrashReportDatabase> database(CrashReportDatabase::Initialize(
|
||
base::FilePath(ToolSupport::CommandLineArgumentToFilePathStringType(
|
||
options.database))));
|
||
if (!database) {
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
// 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 upload_thread(
|
||
database.get(), options.url, options.rate_limit);
|
||
upload_thread.Start();
|
||
|
||
PruneCrashReportThread prune_thread(database.get(),
|
||
PruneCondition::GetDefault());
|
||
prune_thread.Start();
|
||
|
||
CrashReportExceptionHandler exception_handler(
|
||
database.get(), &upload_thread, &options.annotations);
|
||
|
||
exception_handler_server.Run(&exception_handler);
|
||
|
||
upload_thread.Stop();
|
||
prune_thread.Stop();
|
||
|
||
return EXIT_SUCCESS;
|
||
}
|
||
|
||
} // namespace crashpad
|