196 lines
7.1 KiB
C++
196 lines
7.1 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 "test/mac/exception_swallower.h"
|
|||
|
|
|||
|
#include <errno.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <unistd.h>
|
|||
|
|
|||
|
#include <string>
|
|||
|
|
|||
|
#include "base/check_op.h"
|
|||
|
#include "base/mac/scoped_mach_port.h"
|
|||
|
#include "base/strings/stringprintf.h"
|
|||
|
#include "handler/mac/exception_handler_server.h"
|
|||
|
#include "util/mach/bootstrap.h"
|
|||
|
#include "util/mach/exc_server_variants.h"
|
|||
|
#include "util/mach/exception_ports.h"
|
|||
|
#include "util/mach/mach_extensions.h"
|
|||
|
#include "util/misc/random_string.h"
|
|||
|
#include "util/thread/thread.h"
|
|||
|
|
|||
|
namespace crashpad {
|
|||
|
namespace test {
|
|||
|
|
|||
|
namespace {
|
|||
|
|
|||
|
constexpr char kServiceEnvironmentVariable[] =
|
|||
|
"CRASHPAD_EXCEPTION_SWALLOWER_SERVICE";
|
|||
|
|
|||
|
ExceptionSwallower* g_exception_swallower;
|
|||
|
|
|||
|
// Like getenv(), but fails a CHECK() if the underlying function fails. It’s not
|
|||
|
// considered a failure for |name| to be unset in the environment. In that case,
|
|||
|
// nullptr is returned.
|
|||
|
const char* CheckedGetenv(const char* name) {
|
|||
|
errno = 0;
|
|||
|
const char* value;
|
|||
|
PCHECK((value = getenv(name)) || errno == 0) << "getenv";
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
} // namespace
|
|||
|
|
|||
|
class ExceptionSwallower::ExceptionSwallowerThread
|
|||
|
: public Thread,
|
|||
|
public UniversalMachExcServer::Interface {
|
|||
|
public:
|
|||
|
explicit ExceptionSwallowerThread(
|
|||
|
base::mac::ScopedMachReceiveRight receive_right)
|
|||
|
: Thread(),
|
|||
|
UniversalMachExcServer::Interface(),
|
|||
|
exception_handler_server_(std::move(receive_right), true),
|
|||
|
pid_(getpid()) {
|
|||
|
Start();
|
|||
|
}
|
|||
|
|
|||
|
ExceptionSwallowerThread(const ExceptionSwallowerThread&) = delete;
|
|||
|
ExceptionSwallowerThread& operator=(const ExceptionSwallowerThread&) = delete;
|
|||
|
|
|||
|
~ExceptionSwallowerThread() override {}
|
|||
|
|
|||
|
void Stop() { exception_handler_server_.Stop(); }
|
|||
|
|
|||
|
// Returns the process ID that the thread is running in. This is used to
|
|||
|
// detect misuses that place the exception swallower server thread and code
|
|||
|
// that wants its exceptions swallowed in the same process.
|
|||
|
pid_t ProcessID() const { return pid_; }
|
|||
|
|
|||
|
private:
|
|||
|
// Thread:
|
|||
|
|
|||
|
void ThreadMain() override { exception_handler_server_.Run(this); }
|
|||
|
|
|||
|
// UniversalMachExcServer::Interface:
|
|||
|
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
ExceptionHandlerServer exception_handler_server_;
|
|||
|
pid_t pid_;
|
|||
|
};
|
|||
|
|
|||
|
ExceptionSwallower::ExceptionSwallower() : exception_swallower_thread_() {
|
|||
|
CHECK(!g_exception_swallower);
|
|||
|
g_exception_swallower = this;
|
|||
|
|
|||
|
if (CheckedGetenv(kServiceEnvironmentVariable)) {
|
|||
|
// The environment variable is already set, so just proceed with the
|
|||
|
// existing service. This normally happens when the Google Test “threadsafe”
|
|||
|
// death test style is chosen, because the test child process will
|
|||
|
// re-execute code already run in the test parent process. See
|
|||
|
// https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#death-test-styles.
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
std::string service_name =
|
|||
|
base::StringPrintf("org.chromium.crashpad.test.exception_swallower.%d.%s",
|
|||
|
getpid(),
|
|||
|
RandomString().c_str());
|
|||
|
base::mac::ScopedMachReceiveRight receive_right(
|
|||
|
BootstrapCheckIn(service_name));
|
|||
|
CHECK(receive_right.is_valid());
|
|||
|
|
|||
|
exception_swallower_thread_.reset(
|
|||
|
new ExceptionSwallowerThread(std::move(receive_right)));
|
|||
|
|
|||
|
PCHECK(setenv(kServiceEnvironmentVariable, service_name.c_str(), 1) == 0)
|
|||
|
<< "setenv";
|
|||
|
}
|
|||
|
|
|||
|
ExceptionSwallower::~ExceptionSwallower() {
|
|||
|
PCHECK(unsetenv(kServiceEnvironmentVariable) == 0) << "unsetenv";
|
|||
|
|
|||
|
exception_swallower_thread_->Stop();
|
|||
|
exception_swallower_thread_->Join();
|
|||
|
|
|||
|
CHECK_EQ(g_exception_swallower, this);
|
|||
|
g_exception_swallower = nullptr;
|
|||
|
}
|
|||
|
|
|||
|
// static
|
|||
|
void ExceptionSwallower::SwallowExceptions() {
|
|||
|
// The exception swallower thread can’t be in this process, because the
|
|||
|
// EXC_CRASH or EXC_CORPSE_NOTIFY exceptions that it needs to swallow will be
|
|||
|
// delivered after a crash has occurred and none of its threads will be
|
|||
|
// scheduled to run.
|
|||
|
CHECK(!g_exception_swallower ||
|
|||
|
!g_exception_swallower->exception_swallower_thread_ ||
|
|||
|
g_exception_swallower->exception_swallower_thread_->ProcessID() !=
|
|||
|
getpid());
|
|||
|
|
|||
|
const char* service_name = CheckedGetenv(kServiceEnvironmentVariable);
|
|||
|
CHECK(service_name);
|
|||
|
|
|||
|
base::mac::ScopedMachSendRight exception_swallower_port(
|
|||
|
BootstrapLookUp(service_name));
|
|||
|
CHECK(exception_swallower_port.is_valid());
|
|||
|
|
|||
|
ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask,
|
|||
|
TASK_NULL);
|
|||
|
|
|||
|
// The mask is similar to the one used by CrashpadClient::UseHandler(), but
|
|||
|
// EXC_CORPSE_NOTIFY is added. This is done for the benefit of tests that
|
|||
|
// crash intentionally with their own custom exception port set for EXC_CRASH.
|
|||
|
// In that case, depending on the actions taken by the EXC_CRASH handler, the
|
|||
|
// exception may be transformed by the kernel into an EXC_CORPSE_NOTIFY, which
|
|||
|
// would be sent to an EXC_CORPSE_NOTIFY handler, normally the system’s crash
|
|||
|
// reporter at the task or host level. See 10.13.0
|
|||
|
// xnu-4570.1.46/bsd/kern/kern_exit.c proc_prepareexit(). Swallowing
|
|||
|
// EXC_CORPSE_NOTIFY at the task level prevents such exceptions from reaching
|
|||
|
// the system’s crash reporter.
|
|||
|
CHECK(task_exception_ports.SetExceptionPort(
|
|||
|
(EXC_MASK_CRASH |
|
|||
|
EXC_MASK_RESOURCE |
|
|||
|
EXC_MASK_GUARD |
|
|||
|
EXC_MASK_CORPSE_NOTIFY) & ExcMaskValid(),
|
|||
|
exception_swallower_port.get(),
|
|||
|
EXCEPTION_DEFAULT,
|
|||
|
THREAD_STATE_NONE));
|
|||
|
}
|
|||
|
|
|||
|
} // namespace test
|
|||
|
} // namespace crashpad
|