| Index: util/mach/exception_ports_test.cc
 | 
| diff --git a/util/mach/exception_ports_test.cc b/util/mach/exception_ports_test.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..d549651c53d4474bc40adabb7059c4148a59f5dd
 | 
| --- /dev/null
 | 
| +++ b/util/mach/exception_ports_test.cc
 | 
| @@ -0,0 +1,601 @@
 | 
| +// 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 <dispatch/dispatch.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 "util/file/fd_io.h"
 | 
| +#include "util/mach/exc_server_variants.h"
 | 
| +#include "util/mach/mach_extensions.h"
 | 
| +#include "util/misc/scoped_forbid_return.h"
 | 
| +#include "util/test/errors.h"
 | 
| +#include "util/test/mac/mach_errors.h"
 | 
| +#include "util/test/mac/mach_multiprocess.h"
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +using namespace crashpad;
 | 
| +using namespace crashpad::test;
 | 
| +
 | 
| +// 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 UniversalMachExcServer,
 | 
| +                           public MachMultiprocess {
 | 
| + public:
 | 
| +  // 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 entities to set exception ports for.
 | 
| +  enum SetOn {
 | 
| +    kSetOnTaskOnly = 0,
 | 
| +    kSetOnTaskAndThreads,
 | 
| +  };
 | 
| +
 | 
| +  // Which thread in the child process is expected to crash.
 | 
| +  enum WhoCrashes {
 | 
| +    kNobodyCrashes = 0,
 | 
| +    kMainThreadCrashes,
 | 
| +    kOtherThreadCrashes,
 | 
| +  };
 | 
| +
 | 
| +  TestExceptionPorts(SetType set_type, SetOn set_on, WhoCrashes who_crashes)
 | 
| +      : UniversalMachExcServer(),
 | 
| +        MachMultiprocess(),
 | 
| +        set_type_(set_type),
 | 
| +        set_on_(set_on),
 | 
| +        who_crashes_(who_crashes),
 | 
| +        handled_(false) {}
 | 
| +
 | 
| +  SetType set_type() const { return set_type_; }
 | 
| +  SetOn set_on() const { return set_on_; }
 | 
| +  WhoCrashes who_crashes() const { return who_crashes_; }
 | 
| +
 | 
| +  // UniversalMachExcServer:
 | 
| +
 | 
| +  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,
 | 
| +      const natural_t* old_state,
 | 
| +      mach_msg_type_number_t old_state_count,
 | 
| +      thread_state_t new_state,
 | 
| +      mach_msg_type_number_t* new_state_count,
 | 
| +      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 code_count check 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 (code_count >= 1) {
 | 
| +      // The signal that terminated the process is stored in code[0] along with
 | 
| +      // some other data. See 10.9.4 xnu-2422.110.17/bsd/kern/kern_exit.c
 | 
| +      // proc_prepareexit().
 | 
| +      int sig = (code[0] >> 24) & 0xff;
 | 
| +
 | 
| +      // The child crashed with a division by zero, which shows up as SIGFPE.
 | 
| +      // This was chosen because it’s unlikely to be generated by testing or
 | 
| +      // assertion failures.
 | 
| +      EXPECT_EQ(SIGFPE, sig);
 | 
| +
 | 
| +      SetExpectedChildTermination(kTerminationSignal, sig);
 | 
| +    }
 | 
| +
 | 
| +    // Even for an EXC_CRASH handler, returning KERN_SUCCESS with a
 | 
| +    // state-carrying reply will cause the kernel to try to set a new thread
 | 
| +    // state, leading to a perceptible waste of time. Returning
 | 
| +    // MACH_RCV_PORT_DIED is the only way to suppress this behavior while also
 | 
| +    // preventing the kernel from looking for another (host-level) EXC_CRASH
 | 
| +    // handler. See 10.9.4 xnu-2422.110.17/osfmk/kern/exception.c
 | 
| +    // exception_triage().
 | 
| +    exception_behavior_t basic_behavior = behavior & ~MACH_EXCEPTION_CODES;
 | 
| +    bool has_state = basic_behavior == EXCEPTION_STATE ||
 | 
| +                     basic_behavior == EXCEPTION_STATE_IDENTITY;
 | 
| +    return has_state ? MACH_RCV_PORT_DIED : KERN_SUCCESS;
 | 
| +  }
 | 
| +
 | 
| + private:
 | 
| +  class Child {
 | 
| +   public:
 | 
| +    explicit Child(TestExceptionPorts* test_exception_ports)
 | 
| +        : test_exception_ports_(test_exception_ports),
 | 
| +          thread_(),
 | 
| +          init_semaphore_(dispatch_semaphore_create(0)),
 | 
| +          crash_semaphore_(dispatch_semaphore_create(0)) {}
 | 
| +
 | 
| +    ~Child() {
 | 
| +      dispatch_release(crash_semaphore_);
 | 
| +      dispatch_release(init_semaphore_);
 | 
| +    }
 | 
| +
 | 
| +    void Run() {
 | 
| +      ExceptionPorts self_task_ports(ExceptionPorts::kTargetTypeTask,
 | 
| +                                     MACH_PORT_NULL);
 | 
| +      ExceptionPorts self_thread_ports(ExceptionPorts::kTargetTypeThread,
 | 
| +                                       MACH_PORT_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_, NULL, ThreadMainThunk, this);
 | 
| +      ASSERT_EQ(0, rv_int);
 | 
| +
 | 
| +      // Wait for the new thread to be ready.
 | 
| +      long rv_long =
 | 
| +          dispatch_semaphore_wait(init_semaphore_, DISPATCH_TIME_FOREVER);
 | 
| +      ASSERT_EQ(0, rv_long);
 | 
| +
 | 
| +      // Tell the parent process that everything is set up.
 | 
| +      char c = '\0';
 | 
| +      ssize_t rv_ssize = WriteFD(test_exception_ports_->WritePipeFD(), &c, 1);
 | 
| +      ASSERT_EQ(1, rv_ssize) << ErrnoMessage("write");
 | 
| +
 | 
| +      // Wait for the parent process to say that its end is set up.
 | 
| +      rv_ssize = ReadFD(test_exception_ports_->ReadPipeFD(), &c, 1);
 | 
| +      ASSERT_EQ(1, rv_ssize) << ErrnoMessage("read");
 | 
| +      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.
 | 
| +      dispatch_semaphore_signal(crash_semaphore_);
 | 
| +
 | 
| +      // 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_, NULL);
 | 
| +      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,
 | 
| +                                       MACH_PORT_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.
 | 
| +      dispatch_semaphore_signal(init_semaphore_);
 | 
| +
 | 
| +      // Wait for the main thread to signal that it’s safe to proceed.
 | 
| +      long rv =
 | 
| +          dispatch_semaphore_wait(crash_semaphore_, DISPATCH_TIME_FOREVER);
 | 
| +      CHECK_EQ(0, rv) << "dispatch_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 NULL;
 | 
| +    }
 | 
| +
 | 
| +    // Crashes by performing a division by zero. The assignment is present to
 | 
| +    // avoid optimizing zero_ out entirely by making it appear that its value
 | 
| +    // might change.
 | 
| +    static void Crash() { zero_ = 1 / zero_; }
 | 
| +
 | 
| +    // 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.
 | 
| +    dispatch_semaphore_t 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.
 | 
| +    dispatch_semaphore_t crash_semaphore_;
 | 
| +
 | 
| +    // Always zero. Crash() divides by this in order to trigger a crash. This is
 | 
| +    // structured as a static volatile int to ward off aggressive compiler
 | 
| +    // optimizations.
 | 
| +    static volatile int zero_;
 | 
| +
 | 
| +    DISALLOW_COPY_AND_ASSIGN(Child);
 | 
| +  };
 | 
| +
 | 
| +  // MachMultiprocess:
 | 
| +
 | 
| +  virtual 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;
 | 
| +    ssize_t rv = ReadFD(ReadPipeFD(), &c, 1);
 | 
| +    ASSERT_EQ(1, rv) << ErrnoMessage("read");
 | 
| +    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_EQ("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_EQ("thread", main_thread_ports.TargetTypeName());
 | 
| +    EXPECT_EQ("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';
 | 
| +    rv = WriteFD(WritePipeFD(), &c, 1);
 | 
| +    ASSERT_EQ(1, rv) << ErrnoMessage("write");
 | 
| +
 | 
| +    if (who_crashes_ != kNobodyCrashes) {
 | 
| +      kern_return_t kr = MachMessageServer::Run(this,
 | 
| +                                                local_port,
 | 
| +                                                MACH_MSG_OPTION_NONE,
 | 
| +                                                MachMessageServer::kOneShot,
 | 
| +                                                MachMessageServer::kBlocking,
 | 
| +                                                0);
 | 
| +      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().
 | 
| +    rv = ReadFD(ReadPipeFD(), &c, 1);
 | 
| +    ASSERT_EQ(0, rv);
 | 
| +  }
 | 
| +
 | 
| +  virtual void MachMultiprocessChild() override {
 | 
| +    Child child(this);
 | 
| +    child.Run();
 | 
| +  }
 | 
| +
 | 
| +  SetType set_type_;
 | 
| +  SetOn set_on_;
 | 
| +  WhoCrashes who_crashes_;
 | 
| +
 | 
| +  // true if an exception message was handled.
 | 
| +  bool handled_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(TestExceptionPorts);
 | 
| +};
 | 
| +
 | 
| +volatile int TestExceptionPorts::Child::zero_ = 0;
 | 
| +
 | 
| +TEST(ExceptionPorts, TaskAndThreadExceptionPorts) {
 | 
| +  struct Testcase {
 | 
| +    TestExceptionPorts::SetType set_type;
 | 
| +    TestExceptionPorts::SetOn set_on;
 | 
| +    TestExceptionPorts::WhoCrashes who_crashes;
 | 
| +  };
 | 
| +  const Testcase kTestcases[] = {
 | 
| +      {TestExceptionPorts::kSetInProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskOnly,
 | 
| +       TestExceptionPorts::kNobodyCrashes},
 | 
| +      {TestExceptionPorts::kSetInProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskOnly,
 | 
| +       TestExceptionPorts::kMainThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetInProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskOnly,
 | 
| +       TestExceptionPorts::kOtherThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetInProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskAndThreads,
 | 
| +       TestExceptionPorts::kNobodyCrashes},
 | 
| +      {TestExceptionPorts::kSetInProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskAndThreads,
 | 
| +       TestExceptionPorts::kMainThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetInProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskAndThreads,
 | 
| +       TestExceptionPorts::kOtherThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetOutOfProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskOnly,
 | 
| +       TestExceptionPorts::kNobodyCrashes},
 | 
| +      {TestExceptionPorts::kSetOutOfProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskOnly,
 | 
| +       TestExceptionPorts::kMainThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetOutOfProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskOnly,
 | 
| +       TestExceptionPorts::kOtherThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetOutOfProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskAndThreads,
 | 
| +       TestExceptionPorts::kNobodyCrashes},
 | 
| +      {TestExceptionPorts::kSetOutOfProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskAndThreads,
 | 
| +       TestExceptionPorts::kMainThreadCrashes},
 | 
| +      {TestExceptionPorts::kSetOutOfProcess,
 | 
| +       TestExceptionPorts::kSetOnTaskAndThreads,
 | 
| +       TestExceptionPorts::kOtherThreadCrashes},
 | 
| +  };
 | 
| +
 | 
| +  for (size_t index = 0; index < arraysize(kTestcases); ++index) {
 | 
| +    const Testcase& testcase = kTestcases[index];
 | 
| +    SCOPED_TRACE(
 | 
| +        base::StringPrintf("index %zu, set_type %d, set_on %d, who_crashes %d",
 | 
| +                           index,
 | 
| +                           testcase.set_type,
 | 
| +                           testcase.set_on,
 | 
| +                           testcase.who_crashes));
 | 
| +
 | 
| +    TestExceptionPorts test_exception_ports(
 | 
| +        testcase.set_type, testcase.set_on, testcase.who_crashes);
 | 
| +    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_EQ("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,
 | 
| +                                     MACH_PORT_NULL);
 | 
| +  EXPECT_EQ("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
 | 
| 
 |