// Copyright 2014 The Crashpad Authors
//
// 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 "util/mach/exception_ports.h"
#include <mach/mach.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include "base/apple/mach_logging.h"
#include "base/apple/scoped_mach_port.h"
#include "base/check.h"
#include "base/notreached.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/mac/mach_errors.h"
#include "test/mac/mach_multiprocess.h"
#include "util/file/file_io.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_types.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"
#include "util/misc/scoped_forbid_return.h"
#include "util/synchronization/semaphore.h"
namespace crashpad {
namespace test {
namespace {
// Calls GetExceptionPorts() on its |exception_ports| argument to look up the
// EXC_MASK_CRASH handler. If |expect_port| is not MACH_PORT_NULL, it expects to
// find a handler for this mask whose port matches |expect_port| and whose
// behavior matches |expect_behavior| exactly. In this case, if
// |expect_behavior| is a state-carrying behavior, the looked-up thread state
// flavor is expected to be MACHINE_THREAD_STATE, otherwise, it is expected to
// be THREAD_STATE_NONE. If |expect_port| is MACH_PORT_NULL, no handler for
// EXC_MASK_CRASH is expected to be found.
//
// A second GetExceptionPorts() lookup is also performed on a wider exception
// mask, EXC_MASK_ALL | EXC_MASK_CRASH. The EXC_MASK_CRASH handler’s existence
// and properties from this second lookup are validated in the same way.
//
// This function uses Google Test EXPECT_* and ASSERT_* macros to perform its
// validation.
void TestGetExceptionPorts(const ExceptionPorts& exception_ports,
mach_port_t expect_port,
exception_behavior_t expect_behavior) {
constexpr exception_mask_t kExceptionMask = EXC_MASK_CRASH;
thread_state_flavor_t expect_flavor = (expect_behavior == EXCEPTION_DEFAULT)
? THREAD_STATE_NONE
: MACHINE_THREAD_STATE;
ExceptionPorts::ExceptionHandlerVector crash_handler;
ASSERT_TRUE(
exception_ports.GetExceptionPorts(kExceptionMask, &crash_handler));
if (expect_port != MACH_PORT_NULL) {
ASSERT_EQ(crash_handler.size(), 1u);
EXPECT_EQ(crash_handler[0].mask, kExceptionMask);
EXPECT_EQ(crash_handler[0].port, expect_port);
EXPECT_EQ(crash_handler[0].behavior, expect_behavior);
EXPECT_EQ(crash_handler[0].flavor, expect_flavor);
} else {
EXPECT_TRUE(crash_handler.empty());
}
ExceptionPorts::ExceptionHandlerVector handlers;
ASSERT_TRUE(exception_ports.GetExceptionPorts(ExcMaskValid(), &handlers));
EXPECT_GE(handlers.size(), crash_handler.size());
bool found = false;
for (const ExceptionPorts::ExceptionHandler& handler : handlers) {
if ((handler.mask & kExceptionMask) != 0) {
EXPECT_FALSE(found);
found = true;
EXPECT_EQ(handler.port, expect_port);
EXPECT_EQ(handler.behavior, expect_behavior);
EXPECT_EQ(handler.flavor, expect_flavor);
}
}
if (expect_port != MACH_PORT_NULL) {
EXPECT_TRUE(found);
} else {
EXPECT_FALSE(found);
}
}
class TestExceptionPorts : public MachMultiprocess,
public UniversalMachExcServer::Interface {
public:
// Whether to call SetExceptionPort or SwapExceptionPorts.
enum SetOrSwap {
kSetExceptionPort = 0,
kSwapExceptionPorts,
};
// Which entities to set exception ports for.
enum SetOn {
kSetOnTaskOnly = 0,
kSetOnTaskAndThreads,
};
// Where to call ExceptionPorts::SetExceptionPort() from.
enum SetType {
// Call it from the child process on itself.
kSetInProcess = 0,
// Call it from the parent process on the child.
kSetOutOfProcess,
};
// Which thread in the child process is expected to crash.
enum WhoCrashes {
kNobodyCrashes = 0,
kMainThreadCrashes,
kOtherThreadCrashes,
};
TestExceptionPorts(SetOrSwap set_or_swap,
SetOn set_on,
SetType set_type,
WhoCrashes who_crashes)
: MachMultiprocess(),
UniversalMachExcServer::Interface(),
set_or_swap_(set_or_swap),
set_on_(set_on),
set_type_(set_type),
who_crashes_(who_crashes),
handled_(false) {
if (who_crashes_ != kNobodyCrashes) {
SetExpectedChildTerminationBuiltinTrap();
}
}
TestExceptionPorts(const TestExceptionPorts&) = delete;
TestExceptionPorts& operator=(const TestExceptionPorts&) = delete;
SetOrSwap set_or_swap() const { return set_or_swap_; }
SetOn set_on() const { return set_on_; }
SetType set_type() const { return set_type_; }
WhoCrashes who_crashes() const { return who_crashes_; }
// UniversalMachExcServer::Interface:
virtual 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;
EXPECT_FALSE(handled_);
handled_ = true;
// To be able to distinguish between which handler was actually triggered,
// the different handlers are registered with different behavior values.
exception_behavior_t expect_behavior;
if (set_on_ == kSetOnTaskOnly) {
expect_behavior = EXCEPTION_DEFAULT;
} else if (who_crashes_ == kMainThreadCrashes) {
expect_behavior = EXCEPTION_STATE;
} else if (who_crashes_ == kOtherThreadCrashes) {
expect_behavior = EXCEPTION_STATE_IDENTITY;
} else {
NOTREACHED();
}
EXPECT_EQ(behavior, expect_behavior);
EXPECT_EQ(exception_port, LocalPort());
EXPECT_EQ(exception, EXC_CRASH);
EXPECT_EQ(code_count, 2u);
// The exception and code_count checks above would ideally use ASSERT_EQ so
// that the next conditional would not be necessary, but ASSERT_* requires a
// function returning type void, and the interface dictates otherwise here.
if (exception == EXC_CRASH && code_count >= 1) {
int signal;
ExcCrashRecoverOriginalException(code[0], nullptr, &signal);
#if defined(ARCH_CPU_X86_FAMILY)
constexpr int kBuiltinTrapSignal = SIGILL;
#elif defined(ARCH_CPU_ARM64)
constexpr int kBuiltinTrapSignal = SIGTRAP;
#else
#error Port
#endif
EXPECT_EQ(signal, kBuiltinTrapSignal);
}
EXPECT_EQ(AuditPIDFromMachMessageTrailer(trailer), 0);
ExcServerCopyState(
behavior, old_state, old_state_count, new_state, new_state_count);
return ExcServerSuccessfulReturnValue(exception, behavior, false);
}
private:
class Child {
public:
explicit Child(TestExceptionPorts* test_exception_ports)
: test_exception_ports_(test_exception_ports),
thread_(),
init_semaphore_(0),
crash_semaphore_(0) {}
Child(const Child&) = delete;
Child& operator=(const Child&) = delete;
~Child() {}
void Run() {
ExceptionPorts self_task_ports(ExceptionPorts::kTargetTypeTask,
TASK_NULL);
ExceptionPorts self_thread_ports(ExceptionPorts::kTargetTypeThread,
THREAD_NULL);
mach_port_t remote_port = test_exception_ports_->RemotePort();
// Set the task’s and this thread’s exception ports, if appropriate.
if (test_exception_ports_->set_type() == kSetInProcess) {
switch (test_exception_ports_->set_or_swap()) {
case kSetExceptionPort: {
ASSERT_TRUE(self_task_ports.SetExceptionPort(EXC_MASK_CRASH,
remote_port,
EXCEPTION_DEFAULT,
THREAD_STATE_NONE));
if (test_exception_ports_->set_on() == kSetOnTaskAndThreads) {
ASSERT_TRUE(
self_thread_ports.SetExceptionPort(EXC_MASK_CRASH,
remote_port,
EXCEPTION_STATE,
MACHINE_THREAD_STATE));
}
break;
}
case kSwapExceptionPorts: {
ExceptionPorts::ExceptionHandlerVector old_handlers;
ASSERT_TRUE(self_task_ports.SwapExceptionPorts(EXC_MASK_CRASH,
remote_port,
EXCEPTION_DEFAULT,
THREAD_STATE_NONE,
&old_handlers));
if (test_exception_ports_->set_on() == kSetOnTaskAndThreads) {
ASSERT_TRUE(
self_thread_ports.SwapExceptionPorts(EXC_MASK_CRASH,
remote_port,
EXCEPTION_STATE,
MACHINE_THREAD_STATE,
&old_handlers));
}
break;
}
default: {
NOTREACHED();
}
}
}
int rv_int = pthread_create(&thread_, nullptr, ThreadMainThunk, this);
ASSERT_EQ(rv_int, 0);
// Wait for the new thread to be ready.
init_semaphore_.Wait();
// Tell the parent process that everything is set up.
char c = '\0';
CheckedWriteFile(test_exception_ports_->WritePipeHandle(), &c, 1);
// Wait for the parent process to say that its end is set up.
CheckedReadFileExactly(test_exception_ports_->ReadPipeHandle(), &c, 1);
EXPECT_EQ(c, '\0');
// Regardless of where ExceptionPorts::SetExceptionPort() ran,
// ExceptionPorts::GetExceptionPorts() can always be tested in-process.
{
SCOPED_TRACE("task");
TestGetExceptionPorts(self_task_ports, remote_port, EXCEPTION_DEFAULT);
}
{
SCOPED_TRACE("main_thread");
mach_port_t thread_handler =
(test_exception_ports_->set_on() == kSetOnTaskAndThreads)
? remote_port
: MACH_PORT_NULL;
TestGetExceptionPorts(
self_thread_ports, thread_handler, EXCEPTION_STATE);
}
// Let the other thread know it’s safe to proceed.
crash_semaphore_.Signal();
// If this thread is the one that crashes, do it.
if (test_exception_ports_->who_crashes() == kMainThreadCrashes) {
Crash();
}
// Reap the other thread.
rv_int = pthread_join(thread_, nullptr);
ASSERT_EQ(rv_int, 0);
}
private:
// Calls ThreadMain().
static void* ThreadMainThunk(void* argument) {
Child* self = reinterpret_cast<Child*>(argument);
return self->ThreadMain();
}
// Runs the “other” thread.
void* ThreadMain() {
ExceptionPorts self_thread_ports(ExceptionPorts::kTargetTypeThread,
THREAD_NULL);
mach_port_t remote_port = test_exception_ports_->RemotePort();
// Set this thread’s exception handler, if appropriate.
if (test_exception_ports_->set_type() == kSetInProcess &&
test_exception_ports_->set_on() == kSetOnTaskAndThreads) {
switch (test_exception_ports_->set_or_swap()) {
case kSetExceptionPort: {
CHECK(self_thread_ports.SetExceptionPort(EXC_MASK_CRASH,
remote_port,
EXCEPTION_STATE_IDENTITY,
MACHINE_THREAD_STATE));
break;
}
case kSwapExceptionPorts: {
ExceptionPorts::ExceptionHandlerVector old_handlers;
CHECK(self_thread_ports.SwapExceptionPorts(EXC_MASK_CRASH,
remote_port,
EXCEPTION_STATE_IDENTITY,
MACHINE_THREAD_STATE,
&old_handlers));
break;
}
default: {
NOTREACHED();
}
}
}
// Let the main thread know that this thread is ready.
init_semaphore_.Signal();
// Wait for the main thread to signal that it’s safe to proceed.
crash_semaphore_.Wait();
// Regardless of where ExceptionPorts::SetExceptionPort() ran,
// ExceptionPorts::GetExceptionPorts() can always be tested in-process.
{
SCOPED_TRACE("other_thread");
mach_port_t thread_handler =
(test_exception_ports_->set_on() == kSetOnTaskAndThreads)
? remote_port
: MACH_PORT_NULL;
TestGetExceptionPorts(
self_thread_ports, thread_handler, EXCEPTION_STATE_IDENTITY);
}
// If this thread is the one that crashes, do it.
if (test_exception_ports_->who_crashes() == kOtherThreadCrashes) {
Crash();
}
return nullptr;
}
static void Crash() {
__builtin_trap();
}
// The parent object.
TestExceptionPorts* test_exception_ports_; // weak
// The “other” thread.
pthread_t thread_;
// The main thread waits on this for the other thread to start up and
// perform its own initialization.
Semaphore init_semaphore_;
// The child thread waits on this for the parent thread to indicate that the
// child can test its exception ports and possibly crash, as appropriate.
Semaphore crash_semaphore_;
};
// MachMultiprocess:
void MachMultiprocessParent() override {
// Wait for the child process to be ready. It needs to have all of its
// threads set up before proceeding if in kSetOutOfProcess mode.
char c;
CheckedReadFileExactly(ReadPipeHandle(), &c, 1);
EXPECT_EQ(c, '\0');
mach_port_t local_port = LocalPort();
// Get an ExceptionPorts object for the task and each of its threads.
ExceptionPorts task_ports(ExceptionPorts::kTargetTypeTask, ChildTask());
EXPECT_STREQ("task", task_ports.TargetTypeName());
// Hopefully the threads returned by task_threads() are in order, with the
// main thread first and the other thread second. This is currently always
// the case, although nothing guarantees that it will remain so.
thread_act_array_t threads;
mach_msg_type_number_t thread_count = 0;
kern_return_t kr = task_threads(ChildTask(), &threads, &thread_count);
ASSERT_EQ(kr, KERN_SUCCESS) << MachErrorMessage(kr, "task_threads");
ScopedForbidReturn threads_need_owners;
ASSERT_EQ(thread_count, 2u);
base::apple::ScopedMachSendRight main_thread(threads[0]);
base::apple::ScopedMachSendRight other_thread(threads[1]);
threads_need_owners.Disarm();
ExceptionPorts main_thread_ports(ExceptionPorts::kTargetTypeThread,
main_thread.get());
ExceptionPorts other_thread_ports(ExceptionPorts::kTargetTypeThread,
other_thread.get());
EXPECT_STREQ("thread", main_thread_ports.TargetTypeName());
EXPECT_STREQ("thread", other_thread_ports.TargetTypeName());
if (set_type_ == kSetOutOfProcess) {
// Test ExceptionPorts::SetExceptionPort() being called from
// out-of-process.
//
// local_port is only a receive right, but a send right is needed for
// ExceptionPorts::SetExceptionPort(). Make a send right, which can be
// deallocated once the calls to ExceptionPorts::SetExceptionPort() are
// done.
kr = mach_port_insert_right(
mach_task_self(), local_port, local_port, MACH_MSG_TYPE_MAKE_SEND);
ASSERT_EQ(kr, KERN_SUCCESS)
<< MachErrorMessage(kr, "mach_port_insert_right");
base::apple::ScopedMachSendRight send_owner(local_port);
switch (set_or_swap_) {
case kSetExceptionPort: {
ASSERT_TRUE(task_ports.SetExceptionPort(EXC_MASK_CRASH,
local_port,
EXCEPTION_DEFAULT,
THREAD_STATE_NONE));
if (set_on_ == kSetOnTaskAndThreads) {
ASSERT_TRUE(
main_thread_ports.SetExceptionPort(EXC_MASK_CRASH,
local_port,
EXCEPTION_STATE,
MACHINE_THREAD_STATE));
ASSERT_TRUE(
other_thread_ports.SetExceptionPort(EXC_MASK_CRASH,
local_port,
EXCEPTION_STATE_IDENTITY,
MACHINE_THREAD_STATE));
}
break;
}
case kSwapExceptionPorts: {
ExceptionPorts::ExceptionHandlerVector old_handlers;
ASSERT_TRUE(task_ports.SwapExceptionPorts(EXC_MASK_CRASH,
local_port,
EXCEPTION_DEFAULT,
THREAD_STATE_NONE,
&old_handlers));
if (set_on_ == kSetOnTaskAndThreads) {
ASSERT_TRUE(
main_thread_ports.SwapExceptionPorts(EXC_MASK_CRASH,
local_port,
EXCEPTION_STATE,
MACHINE_THREAD_STATE,
&old_handlers));
ASSERT_TRUE(
other_thread_ports.SwapExceptionPorts(EXC_MASK_CRASH,
local_port,
EXCEPTION_STATE_IDENTITY,
MACHINE_THREAD_STATE,
&old_handlers));
}
break;
}
default: {
NOTREACHED();
}
}
}
// Regardless of where ExceptionPorts::SetExceptionPort() ran,
// ExceptionPorts::GetExceptionPorts() can always be tested out-of-process.
{
SCOPED_TRACE("task");
TestGetExceptionPorts(task_ports, local_port, EXCEPTION_DEFAULT);
}
mach_port_t thread_handler =
(set_on_ == kSetOnTaskAndThreads) ? local_port : MACH_PORT_NULL;
{
SCOPED_TRACE("main_thread");
TestGetExceptionPorts(main_thread_ports, thread_handler, EXCEPTION_STATE);
}
{
SCOPED_TRACE("other_thread");
TestGetExceptionPorts(
other_thread_ports, thread_handler, EXCEPTION_STATE_IDENTITY);
}
// Let the child process know that everything in the parent process is set
// up.
c = '\0';
CheckedWriteFile(WritePipeHandle(), &c, 1);
if (who_crashes_ != kNobodyCrashes) {
UniversalMachExcServer universal_mach_exc_server(this);
constexpr mach_msg_timeout_t kTimeoutMs = 50;
kr = MachMessageServer::Run(&universal_mach_exc_server,
local_port,
kMachMessageReceiveAuditTrailer,
MachMessageServer::kOneShot,
MachMessageServer::kReceiveLargeError,
kTimeoutMs);
EXPECT_EQ(kr, KERN_SUCCESS)
<< MachErrorMessage(kr, "MachMessageServer::Run");
EXPECT_TRUE(handled_);
}
// Wait for the child process to exit or terminate, as indicated by it
// closing its pipe. This keeps LocalPort() alive in the child as
// RemotePort(), for the child’s use in its TestGetExceptionPorts().
CheckedReadFileAtEOF(ReadPipeHandle());
}
void MachMultiprocessChild() override {
Child child(this);
child.Run();
}
SetOrSwap set_or_swap_;
SetOn set_on_;
SetType set_type_;
WhoCrashes who_crashes_;
// true if an exception message was handled.
bool handled_;
};
TEST(ExceptionPorts, TaskExceptionPorts_SetInProcess_NoCrash) {
TestExceptionPorts test_exception_ports(TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SetInProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SetInProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SetInProcess_NoCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SetInProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SetInProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SetOutOfProcess_NoCrash) {
TestExceptionPorts test_exception_ports(TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SetOutOfProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SetOutOfProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SetOutOfProcess_NoCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SetOutOfProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SetOutOfProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSetExceptionPort,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SwapInProcess_NoCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SwapInProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SwapInProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SwapInProcess_NoCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SwapInProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SwapInProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetInProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SwapOutOfProcess_NoCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SwapOutOfProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskExceptionPorts_SwapOutOfProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskOnly,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SwapOutOfProcess_NoCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kNobodyCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SwapOutOfProcess_MainThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kMainThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts,
TaskAndThreadExceptionPorts_SwapOutOfProcess_OtherThreadCrash) {
TestExceptionPorts test_exception_ports(
TestExceptionPorts::kSwapExceptionPorts,
TestExceptionPorts::kSetOnTaskAndThreads,
TestExceptionPorts::kSetOutOfProcess,
TestExceptionPorts::kOtherThreadCrashes);
test_exception_ports.Run();
}
TEST(ExceptionPorts, HostExceptionPorts) {
// ExceptionPorts isn’t expected to work as non-root. Just do a quick test to
// make sure that TargetTypeName() returns the right string, and that the
// underlying host_get_exception_ports() function appears to be called by
// looking for a KERN_INVALID_ARGUMENT return value. Or, on the off chance
// that the test is being run as root, just look for KERN_SUCCESS.
// host_set_exception_ports() is not tested, because if the test were running
// as root and the call succeeded, it would have global effects.
const bool expect_success = geteuid() == 0;
base::apple::ScopedMachSendRight host(mach_host_self());
ExceptionPorts explicit_host_ports(ExceptionPorts::kTargetTypeHost,
host.get());
EXPECT_STREQ("host", explicit_host_ports.TargetTypeName());
ExceptionPorts::ExceptionHandlerVector explicit_handlers;
bool rv =
explicit_host_ports.GetExceptionPorts(ExcMaskValid(), &explicit_handlers);
EXPECT_EQ(rv, expect_success);
ExceptionPorts implicit_host_ports(ExceptionPorts::kTargetTypeHost,
HOST_NULL);
EXPECT_STREQ("host", implicit_host_ports.TargetTypeName());
ExceptionPorts::ExceptionHandlerVector implicit_handlers;
rv =
implicit_host_ports.GetExceptionPorts(ExcMaskValid(), &implicit_handlers);
EXPECT_EQ(rv, expect_success);
EXPECT_EQ(implicit_handlers.size(), explicit_handlers.size());
}
} // namespace
} // namespace test
} // namespace crashpad