mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-28 15:50:26 +08:00
94a5a72efa
Crashpad has many tests that crash intentionally. Some of these are gtest death tests, and others arrange for intentional crashes to test Crashpad’s own crash-catching logic. On macOS, all of the gtest death tests and some of the other intentional crashes were being logged by ReportCrash, the system’s crash reporter. Since these reports corresponded to intentional crashes, they were never useful, and served only to clutter ~/Library/Logs/DiagnosticReports. Since Crashpad is adept at handling exceptions on its own, this introduces the “exception swallowing server”, crashpad_exception_swallower, which is a Mach exception server that implements a no-op exception handler routine for all exceptions received. The exception swallowing server is established as the task handler for EXC_CRASH and EXC_CORPSE_NOTIFY exceptions during gtest death tests invoked by {ASSERT,EXPECT}_DEATH_{CHECK,CRASH}, and for all child processes invoked by the Multiprocess test infrastructure. The exception swallowing server is not in effect at other times, so unexpected crashes in test code can still be handled by ReportCrash or another crash reporter. With this change in place, no new reports are generated in the user-level ~/Library/Logs/DiagnosticReports or the system’s /Library/Logs/DiagnosticReports during a run of Crashpad’s full test suite on macOS. Bug: crashpad:33 Change-Id: I13891853a7e25accc30da21fa7ea8bd7d1f3bd2f Reviewed-on: https://chromium-review.googlesource.com/777859 Commit-Queue: Mark Mentovai <mark@chromium.org> Reviewed-by: Robert Sesek <rsesek@chromium.org>
230 lines
7.3 KiB
C++
230 lines
7.3 KiB
C++
// Copyright 2017 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 <getopt.h>
|
||
#include <libgen.h>
|
||
#include <stdlib.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
|
||
#include <string>
|
||
#include <utility>
|
||
|
||
#include "base/logging.h"
|
||
#include "base/numerics/safe_conversions.h"
|
||
#include "base/strings/stringprintf.h"
|
||
#include "handler/mac/exception_handler_server.h"
|
||
#include "tools/tool_support.h"
|
||
#include "util/file/file_io.h"
|
||
#include "util/mach/exc_server_variants.h"
|
||
#include "util/mach/mach_extensions.h"
|
||
#include "util/misc/random_string.h"
|
||
#include "util/posix/close_stdio.h"
|
||
#include "util/stdlib/string_number_conversion.h"
|
||
#include "util/thread/thread.h"
|
||
|
||
namespace crashpad {
|
||
namespace {
|
||
|
||
// A Mach exception handler that accepts all exceptions but doesn’t do anything
|
||
// with any of them.
|
||
class SwallowingExceptionHandler : public UniversalMachExcServer::Interface {
|
||
public:
|
||
SwallowingExceptionHandler() {}
|
||
~SwallowingExceptionHandler() {}
|
||
|
||
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;
|
||
|
||
// Swallow.
|
||
|
||
ExcServerCopyState(
|
||
behavior, old_state, old_state_count, new_state, new_state_count);
|
||
return ExcServerSuccessfulReturnValue(exception, behavior, false);
|
||
}
|
||
|
||
private:
|
||
DISALLOW_COPY_AND_ASSIGN(SwallowingExceptionHandler);
|
||
};
|
||
|
||
// Watches a file descriptor, and when it reaches end-of-file, asks the
|
||
// ExceptionHandlerServer to stop. Because the file descriptor is one end of a
|
||
// socketpair(), and the other end is kept open in the client process,
|
||
// end-of-file will be reached when the client process terminates.
|
||
class EOFWatcherThread : public Thread {
|
||
public:
|
||
EOFWatcherThread(int fd, ExceptionHandlerServer* exception_handler_server)
|
||
: Thread(),
|
||
exception_handler_server_(exception_handler_server),
|
||
fd_(fd) {}
|
||
|
||
private:
|
||
void ThreadMain() override {
|
||
char c;
|
||
ssize_t rv = ReadFile(fd_.get(), &c, 1);
|
||
PCHECK(rv >= 0) << internal::kNativeReadFunctionName;
|
||
CHECK(rv == 0);
|
||
|
||
exception_handler_server_->Stop();
|
||
}
|
||
|
||
ExceptionHandlerServer* exception_handler_server_; // weak
|
||
base::ScopedFD fd_;
|
||
|
||
DISALLOW_COPY_AND_ASSIGN(EOFWatcherThread);
|
||
};
|
||
|
||
void Usage(const std::string& me) {
|
||
fprintf(stderr,
|
||
"Usage: %s [OPTION]...\n"
|
||
"Crashpad's exception swallower.\n"
|
||
"\n"
|
||
" --socket-fd=FD synchronize with the client over FD\n"
|
||
" --help display this help and exit\n"
|
||
" --version output version information and exit\n",
|
||
me.c_str());
|
||
ToolSupport::UsageTail(me);
|
||
}
|
||
|
||
int ExceptionSwallowerMain(int argc, char* argv[]) {
|
||
const std::string me(basename(argv[0]));
|
||
|
||
enum OptionFlags {
|
||
// Long options without short equivalents.
|
||
kOptionLastChar = 255,
|
||
kOptionSocketFD,
|
||
|
||
// Standard options.
|
||
kOptionHelp = -2,
|
||
kOptionVersion = -3,
|
||
};
|
||
|
||
struct {
|
||
int socket_fd;
|
||
} options = {};
|
||
options.socket_fd = -1;
|
||
|
||
const option long_options[] = {
|
||
{"socket-fd", required_argument, nullptr, kOptionSocketFD},
|
||
{"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 kOptionSocketFD:
|
||
if (!StringToNumber(optarg, &options.socket_fd) ||
|
||
options.socket_fd <= STDERR_FILENO) {
|
||
ToolSupport::UsageHint(me, "--socket-fd requires a file descriptor");
|
||
return EXIT_FAILURE;
|
||
}
|
||
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 (options.socket_fd < 0) {
|
||
ToolSupport::UsageHint(me, "--socket-fd is required");
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
if (argc) {
|
||
ToolSupport::UsageHint(me, nullptr);
|
||
return EXIT_FAILURE;
|
||
}
|
||
|
||
CloseStdinAndStdout();
|
||
|
||
// Build a service name. Include the PID of the client at the other end of the
|
||
// socket, so that the service name has a meaningful relation back to the
|
||
// client that started this server process. A simple getppid() won’t do
|
||
// because the client started this process with a double-fork().
|
||
pid_t peer_pid;
|
||
socklen_t peer_pid_size = base::checked_cast<socklen_t>(sizeof(peer_pid));
|
||
PCHECK(getsockopt(options.socket_fd,
|
||
SOL_LOCAL,
|
||
LOCAL_PEERPID,
|
||
&peer_pid,
|
||
&peer_pid_size) == 0)
|
||
<< "getsockopt";
|
||
CHECK_EQ(peer_pid_size, sizeof(peer_pid));
|
||
|
||
std::string service_name =
|
||
base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s",
|
||
peer_pid,
|
||
RandomString().c_str());
|
||
|
||
base::mac::ScopedMachReceiveRight receive_right(
|
||
BootstrapCheckIn(service_name));
|
||
CHECK(receive_right.is_valid());
|
||
|
||
// Tell the client that the service has been checked in, providing the
|
||
// service name.
|
||
uint8_t service_name_size = base::checked_cast<uint8_t>(service_name.size());
|
||
CheckedWriteFile(
|
||
options.socket_fd, &service_name_size, sizeof(service_name_size));
|
||
CheckedWriteFile(
|
||
options.socket_fd, service_name.c_str(), service_name.size());
|
||
|
||
ExceptionHandlerServer exception_handler_server(std::move(receive_right),
|
||
true);
|
||
|
||
EOFWatcherThread eof_watcher_thread(options.socket_fd,
|
||
&exception_handler_server);
|
||
eof_watcher_thread.Start();
|
||
|
||
// This runs until stopped by eof_watcher_thread.
|
||
SwallowingExceptionHandler swallowing_exception_handler;
|
||
exception_handler_server.Run(&swallowing_exception_handler);
|
||
|
||
eof_watcher_thread.Join();
|
||
|
||
return EXIT_SUCCESS;
|
||
}
|
||
|
||
} // namespace
|
||
} // namespace crashpad
|
||
|
||
int main(int argc, char* argv[]) {
|
||
return crashpad::ExceptionSwallowerMain(argc, argv);
|
||
}
|