mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-31 01:43:03 +08:00
b1d7833600
While not strictly asan-related, this bug was found while running tests under asan. Evidently, strings are pooled differently in that build configuration. TEST=crashpad_util_test ExceptionPorts.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/1291573004 .
617 lines
22 KiB
C++
617 lines
22 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 "util/mach/exception_ports.h"
|
||
|
||
#include <mach/mach.h>
|
||
#include <pthread.h>
|
||
#include <signal.h>
|
||
#include <unistd.h>
|
||
|
||
#include "base/basictypes.h"
|
||
#include "base/logging.h"
|
||
#include "base/mac/mach_logging.h"
|
||
#include "base/mac/scoped_mach_port.h"
|
||
#include "base/strings/stringprintf.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 gtest EXPECT_* and ASSERT_* macros to perform its
|
||
// validation.
|
||
void TestGetExceptionPorts(const ExceptionPorts& exception_ports,
|
||
mach_port_t expect_port,
|
||
exception_behavior_t expect_behavior) {
|
||
const exception_mask_t kExceptionMask = EXC_MASK_CRASH;
|
||
|
||
thread_state_flavor_t expect_flavor = (expect_behavior == EXCEPTION_DEFAULT)
|
||
? THREAD_STATE_NONE
|
||
: MACHINE_THREAD_STATE;
|
||
|
||
std::vector<ExceptionPorts::ExceptionHandler> crash_handler;
|
||
ASSERT_TRUE(
|
||
exception_ports.GetExceptionPorts(kExceptionMask, &crash_handler));
|
||
|
||
if (expect_port != MACH_PORT_NULL) {
|
||
ASSERT_EQ(1u, crash_handler.size());
|
||
base::mac::ScopedMachSendRight port_owner(crash_handler[0].port);
|
||
|
||
EXPECT_EQ(kExceptionMask, crash_handler[0].mask);
|
||
EXPECT_EQ(expect_port, crash_handler[0].port);
|
||
EXPECT_EQ(expect_behavior, crash_handler[0].behavior);
|
||
EXPECT_EQ(expect_flavor, crash_handler[0].flavor);
|
||
} else {
|
||
EXPECT_TRUE(crash_handler.empty());
|
||
}
|
||
|
||
std::vector<ExceptionPorts::ExceptionHandler> handlers;
|
||
ASSERT_TRUE(exception_ports.GetExceptionPorts(
|
||
ExcMaskAll() | EXC_MASK_CRASH, &handlers));
|
||
|
||
EXPECT_GE(handlers.size(), crash_handler.size());
|
||
bool found = false;
|
||
for (const ExceptionPorts::ExceptionHandler& handler : handlers) {
|
||
if ((handler.mask & kExceptionMask) != 0) {
|
||
base::mac::ScopedMachSendRight port_owner(handler.port);
|
||
|
||
EXPECT_FALSE(found);
|
||
found = true;
|
||
EXPECT_EQ(expect_port, handler.port);
|
||
EXPECT_EQ(expect_behavior, handler.behavior);
|
||
EXPECT_EQ(expect_flavor, handler.flavor);
|
||
}
|
||
}
|
||
|
||
if (expect_port != MACH_PORT_NULL) {
|
||
EXPECT_TRUE(found);
|
||
} else {
|
||
EXPECT_FALSE(found);
|
||
}
|
||
}
|
||
|
||
class TestExceptionPorts : public MachMultiprocess,
|
||
public UniversalMachExcServer::Interface {
|
||
public:
|
||
// 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(SetOn set_on, SetType set_type, WhoCrashes who_crashes)
|
||
: MachMultiprocess(),
|
||
UniversalMachExcServer::Interface(),
|
||
set_on_(set_on),
|
||
set_type_(set_type),
|
||
who_crashes_(who_crashes),
|
||
handled_(false) {}
|
||
|
||
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_behavior = 0;
|
||
}
|
||
|
||
EXPECT_EQ(expect_behavior, behavior);
|
||
|
||
EXPECT_EQ(LocalPort(), exception_port);
|
||
|
||
EXPECT_EQ(EXC_CRASH, exception);
|
||
EXPECT_EQ(2u, code_count);
|
||
|
||
// 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);
|
||
|
||
// The child crashed with __builtin_trap(), which shows up as SIGILL.
|
||
EXPECT_EQ(SIGILL, signal);
|
||
|
||
SetExpectedChildTermination(kTerminationSignal, signal);
|
||
}
|
||
|
||
EXPECT_EQ(0, AuditPIDFromMachMessageTrailer(trailer));
|
||
|
||
return ExcServerSuccessfulReturnValue(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() {}
|
||
|
||
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) {
|
||
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));
|
||
}
|
||
}
|
||
|
||
int rv_int = pthread_create(&thread_, nullptr, ThreadMainThunk, this);
|
||
ASSERT_EQ(0, rv_int);
|
||
|
||
// 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.
|
||
CheckedReadFile(test_exception_ports_->ReadPipeHandle(), &c, 1);
|
||
EXPECT_EQ('\0', c);
|
||
|
||
// 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(0, rv_int);
|
||
}
|
||
|
||
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) {
|
||
CHECK(self_thread_ports.SetExceptionPort(EXC_MASK_CRASH,
|
||
remote_port,
|
||
EXCEPTION_STATE_IDENTITY,
|
||
MACHINE_THREAD_STATE));
|
||
}
|
||
|
||
// 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_;
|
||
|
||
DISALLOW_COPY_AND_ASSIGN(Child);
|
||
};
|
||
|
||
// 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;
|
||
CheckedReadFile(ReadPipeHandle(), &c, 1);
|
||
EXPECT_EQ('\0', c);
|
||
|
||
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(KERN_SUCCESS, kr) << MachErrorMessage(kr, "task_threads");
|
||
|
||
ScopedForbidReturn threads_need_owners;
|
||
ASSERT_EQ(2u, thread_count);
|
||
base::mac::ScopedMachSendRight main_thread(threads[0]);
|
||
base::mac::ScopedMachSendRight other_thread(threads[1]);
|
||
threads_need_owners.Disarm();
|
||
|
||
ExceptionPorts main_thread_ports(ExceptionPorts::kTargetTypeThread,
|
||
main_thread);
|
||
ExceptionPorts other_thread_ports(ExceptionPorts::kTargetTypeThread,
|
||
other_thread);
|
||
EXPECT_STREQ("thread", main_thread_ports.TargetTypeName());
|
||
EXPECT_STREQ("thread", other_thread_ports.TargetTypeName());
|
||
|
||
if (set_type_ == kSetOutOfProcess) {
|
||
// Test ExceptionPorts::SetExceptionPorts() 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(KERN_SUCCESS, kr)
|
||
<< MachErrorMessage(kr, "mach_port_insert_right");
|
||
base::mac::ScopedMachSendRight send_owner(local_port);
|
||
|
||
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));
|
||
}
|
||
}
|
||
|
||
// 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);
|
||
|
||
const mach_msg_timeout_t kTimeoutMs = 50;
|
||
kern_return_t kr =
|
||
MachMessageServer::Run(&universal_mach_exc_server,
|
||
local_port,
|
||
kMachMessageReceiveAuditTrailer,
|
||
MachMessageServer::kOneShot,
|
||
MachMessageServer::kReceiveLargeError,
|
||
kTimeoutMs);
|
||
EXPECT_EQ(KERN_SUCCESS, kr)
|
||
<< 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();
|
||
}
|
||
|
||
SetOn set_on_;
|
||
SetType set_type_;
|
||
WhoCrashes who_crashes_;
|
||
|
||
// true if an exception message was handled.
|
||
bool handled_;
|
||
|
||
DISALLOW_COPY_AND_ASSIGN(TestExceptionPorts);
|
||
};
|
||
|
||
TEST(ExceptionPorts, TaskExceptionPorts_SetInProcess_NoCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskOnly,
|
||
TestExceptionPorts::kSetInProcess,
|
||
TestExceptionPorts::kNobodyCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskExceptionPorts_SetInProcess_MainThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskOnly,
|
||
TestExceptionPorts::kSetInProcess,
|
||
TestExceptionPorts::kMainThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskExceptionPorts_SetInProcess_OtherThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskOnly,
|
||
TestExceptionPorts::kSetInProcess,
|
||
TestExceptionPorts::kOtherThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SetInProcess_NoCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskAndThreads,
|
||
TestExceptionPorts::kSetInProcess,
|
||
TestExceptionPorts::kNobodyCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SetInProcess_MainThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskAndThreads,
|
||
TestExceptionPorts::kSetInProcess,
|
||
TestExceptionPorts::kMainThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts,
|
||
TaskAndThreadExceptionPorts_SetInProcess_OtherThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskAndThreads,
|
||
TestExceptionPorts::kSetInProcess,
|
||
TestExceptionPorts::kOtherThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskExceptionPorts_SetOutOfProcess_NoCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskOnly,
|
||
TestExceptionPorts::kSetOutOfProcess,
|
||
TestExceptionPorts::kNobodyCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskExceptionPorts_SetOutOfProcess_MainThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskOnly,
|
||
TestExceptionPorts::kSetOutOfProcess,
|
||
TestExceptionPorts::kMainThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskExceptionPorts_SetOutOfProcess_OtherThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskOnly,
|
||
TestExceptionPorts::kSetOutOfProcess,
|
||
TestExceptionPorts::kOtherThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts, TaskAndThreadExceptionPorts_SetOutOfProcess_NoCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskAndThreads,
|
||
TestExceptionPorts::kSetOutOfProcess,
|
||
TestExceptionPorts::kNobodyCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts,
|
||
TaskAndThreadExceptionPorts_SetOutOfProcess_MainThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
TestExceptionPorts::kSetOnTaskAndThreads,
|
||
TestExceptionPorts::kSetOutOfProcess,
|
||
TestExceptionPorts::kMainThreadCrashes);
|
||
test_exception_ports.Run();
|
||
}
|
||
|
||
TEST(ExceptionPorts,
|
||
TaskAndThreadExceptionPorts_SetOutOfProcess_OtherThreadCrash) {
|
||
TestExceptionPorts test_exception_ports(
|
||
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.
|
||
|
||
base::mac::ScopedMachSendRight host(mach_host_self());
|
||
ExceptionPorts explicit_host_ports(ExceptionPorts::kTargetTypeHost, host);
|
||
EXPECT_STREQ("host", explicit_host_ports.TargetTypeName());
|
||
|
||
std::vector<ExceptionPorts::ExceptionHandler> handlers;
|
||
bool rv = explicit_host_ports.GetExceptionPorts(
|
||
ExcMaskAll() | EXC_MASK_CRASH, &handlers);
|
||
if (geteuid() == 0) {
|
||
EXPECT_TRUE(rv);
|
||
} else {
|
||
EXPECT_FALSE(rv);
|
||
}
|
||
|
||
ExceptionPorts implicit_host_ports(ExceptionPorts::kTargetTypeHost,
|
||
HOST_NULL);
|
||
EXPECT_STREQ("host", implicit_host_ports.TargetTypeName());
|
||
|
||
rv = implicit_host_ports.GetExceptionPorts(
|
||
ExcMaskAll() | EXC_MASK_CRASH, &handlers);
|
||
if (geteuid() == 0) {
|
||
EXPECT_TRUE(rv);
|
||
} else {
|
||
EXPECT_FALSE(rv);
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
} // namespace test
|
||
} // namespace crashpad
|