| // 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; |
| |
| ExceptionPorts::ExceptionHandlerVector crash_handler; |
| ASSERT_TRUE( |
| exception_ports.GetExceptionPorts(kExceptionMask, &crash_handler)); |
| |
| if (expect_port != MACH_PORT_NULL) { |
| ASSERT_EQ(1u, crash_handler.size()); |
| |
| 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()); |
| } |
| |
| 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(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)); |
| |
| 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() {} |
| |
| 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.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::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. |
| |
| const bool expect_success = geteuid() == 0; |
| |
| base::mac::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(expect_success, rv); |
| |
| 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(expect_success, rv); |
| |
| EXPECT_EQ(explicit_handlers.size(), implicit_handlers.size()); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |