text: // Copyright 2016 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <assert.h>
  #include <inttypes.h>
  #include <lib/test-exceptions/exception-catcher.h>
  #include <lib/zx/channel.h>
  #include <lib/zx/clock.h>
  #include <lib/zx/exception.h>
  #include <lib/zx/job.h>
  #include <lib/zx/process.h>
  #include <lib/zx/task.h>
  #include <lib/zx/thread.h>
  #include <lib/zx/time.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <zircon/compiler.h>
  #include <zircon/process.h>
  #include <zircon/processargs.h>
  #include <zircon/syscalls.h>
  #include <zircon/syscalls/debug.h>
  #include <zircon/syscalls/exception.h>
  #include <zircon/threads.h>
  
  #include <optional>
  #include <string>
  #include <type_traits>
  
  #include <fbl/algorithm.h>
  #include <test-utils/test-utils.h>
  #include <zxtest/zxtest.h>
  
  namespace {
  
  static int thread_func(void* arg);
  
  // argv[0]
  static char* program_path;
  
  static const char test_child_name[] = "test-child";
  static const char exit_closing_excp_handle_child_name[] = "exit-closing-excp-handle";
  
  enum message {
    // Make the type of this enum signed so that we don't get a compile failure
    // later with things like EXPECT_EQ(msg, MSG_PONG) [unsigned vs signed
    // comparison mismatch]
    MSG_ENSURE_SIGNED = -1,
    MSG_DONE,
    MSG_CRASH,
    MSG_PING,
    MSG_PONG,
    MSG_CREATE_AUX_THREAD,
    MSG_AUX_THREAD_HANDLE,
    MSG_CRASH_AUX_THREAD,
    MSG_SHUTDOWN_AUX_THREAD
  };
  
  static void crash_me() {
    volatile int* p = 0;
    *p = 42;
  }
  
  static void send_msg_new_thread_handle(zx_handle_t handle, zx_handle_t thread) {
    // Note: The handle is transferred to the receiver.
    uint64_t data = MSG_AUX_THREAD_HANDLE;
    tu_channel_write(handle, 0, &data, sizeof(data), &thread, 1);
  }
  
  static void send_msg(zx_handle_t handle, message msg) {
    uint64_t data = msg;
    tu_channel_write(handle, 0, &data, sizeof(data), NULL, 0);
  }
  
  static bool recv_msg(zx_handle_t handle, message* msg) {
    uint64_t data;
    uint32_t num_bytes = sizeof(data);
  
    if (!tu_channel_wait_readable(handle)) {
      return false;
    }
  
    tu_channel_read(handle, 0, &data, &num_bytes, NULL, 0);
    if (num_bytes != sizeof(data)) {
      return false;
    }
  
    *msg = static_cast<message>(data);
    return true;
  }
  
  static void recv_msg_new_thread_handle(zx_handle_t handle, zx_handle_t* thread) {
    uint64_t data;
    uint32_t num_bytes = sizeof(data);
  
    ASSERT_TRUE(tu_channel_wait_readable(handle), "peer closed while trying to read message");
  
    uint32_t num_handles = 1;
    tu_channel_read(handle, 0, &data, &num_bytes, thread, &num_handles);
    ASSERT_EQ(num_bytes, sizeof(data));
    ASSERT_EQ(num_handles, 1u);
  
    ASSERT_EQ(static_cast<message>(data), MSG_AUX_THREAD_HANDLE);
  }
  
  static bool ensure_child_running(zx_handle_t channel) {
    // Note: This function is called from external threads and thus does
    // not use EXPECT_*/ASSERT_*.
    message msg;
    send_msg(channel, MSG_PING);
    if (!recv_msg(channel, &msg)) {
      return false;
    }
    if (msg != MSG_PONG) {
      return false;
    }
    return true;
  }
  
  static void msg_loop(zx_handle_t channel) {
    bool my_done_tests = false;
    zx_handle_t channel_to_thread = ZX_HANDLE_INVALID;
  
    while (!my_done_tests) {
      message msg;
      if (!recv_msg(channel, &msg)) {
        return;
      }
      switch (msg) {
        case MSG_DONE:
          my_done_tests = true;
          break;
        case MSG_CRASH:
          crash_me();
          break;
        case MSG_PING:
          send_msg(channel, MSG_PONG);
          break;
        case MSG_CREATE_AUX_THREAD:
          // Spin up a thread that we can talk to.
          {
            if (channel_to_thread != ZX_HANDLE_INVALID) {
              printf("previous thread connection not shutdown\n");
              return;
            }
            zx_handle_t channel_from_thread;
            tu_channel_create(&channel_to_thread, &channel_from_thread);
            thrd_t thread;
            tu_thread_create_c11(&thread, thread_func, (void*)(uintptr_t)channel_from_thread,
                                 "msg-loop-subthread");
            // Make sure the new thread is up and running before sending
            // its handle back: this removes potential problems like
            // needing to handle ZX_EXCP_THREAD_STARTING exceptions if the
            // debugger exception channel is bound later.
            if (ensure_child_running(channel_to_thread)) {
              zx_handle_t thread_handle = thrd_get_zx_handle(thread);
              zx_handle_t copy = tu_handle_duplicate(thread_handle);
              send_msg_new_thread_handle(channel, copy);
            } else {
              // We could terminate the thread or some such, but the
              // process will be killed by our "caller".
              send_msg_new_thread_handle(channel, ZX_HANDLE_INVALID);
              zx_handle_close(channel_to_thread);
              channel_to_thread = ZX_HANDLE_INVALID;
            }
          }
          break;
        case MSG_CRASH_AUX_THREAD:
          send_msg(channel_to_thread, MSG_CRASH);
          break;
        case MSG_SHUTDOWN_AUX_THREAD:
          send_msg(channel_to_thread, MSG_DONE);
          zx_handle_close(channel_to_thread);
          channel_to_thread = ZX_HANDLE_INVALID;
          break;
        default:
          printf("unknown message received: %d\n", msg);
          break;
      }
    }
  }
  
  static int thread_func(void* arg) {
    zx_handle_t msg_channel = (zx_handle_t)(uintptr_t)arg;
    msg_loop(msg_channel);
    tu_handle_close(msg_channel);
    return 0;
  }
  
  static void __NO_RETURN test_child() {
    zx_handle_t channel = zx_take_startup_handle(PA_USER0);
    if (channel == ZX_HANDLE_INVALID)
      tu_fatal("zx_take_startup_handle", ZX_ERR_BAD_HANDLE - 1000);
    msg_loop(channel);
    exit(0);
  }
  
  static springboard_t* setup_test_child(zx_handle_t job, const char* arg, zx_handle_t* out_channel) {
    zx_handle_t our_channel, their_channel;
    tu_channel_create(&our_channel, &their_channel);
    const char* test_child_path = program_path;
    const char* const argv[] = {
        test_child_path,
        arg,
    };
    int argc = fbl::count_of(argv);
    zx_handle_t handles[1] = {their_channel};
    uint32_t handle_ids[1] = {PA_USER0};
    *out_channel = our_channel;
    springboard_t* sb =
        tu_launch_init(job, test_child_name, argc, argv, 0, NULL, 1, handles, handle_ids);
    return sb;
  }
  
  static void start_test_child_with_exception_channel(const zx::job& job, const std::string& arg,
                                                      zx::process* out_child,
                                                      zx::channel* out_exception_channel,
                                                      zx::channel* out_channel) {
    springboard_t* sb =
        setup_test_child(job.get(), arg.c_str(), out_channel->reset_and_get_address());
    out_exception_channel->reset(tu_create_exception_channel(springboard_get_process_handle(sb),
                                                             ZX_EXCEPTION_CHANNEL_DEBUGGER));
    out_child->reset(tu_launch_fini(sb));
  }
  
  struct proc_handles {
    zx_handle_t proc;
    zx_handle_t vmar;
  };
  
  // Waits for and reads an exception.
  //
  // If |type| is valid, checks that the received exception matches.
  // If |info| is non-null, fills it in with the received struct.
  //
  // Returns an invalid exception and marks test failure on error or if |type|
  // doesn't match.
  zx::exception ReadException(const zx::channel& channel,
                              std::optional<zx_excp_type_t> type = std::nullopt,
                              zx_exception_info_t* info_out = nullptr) {
    zx::exception exception;
    zx_exception_info_t info;
    uint32_t num_handles = 1;
    uint32_t num_bytes = sizeof(info);
  
    zx_status_t status = channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr);
    if (status != ZX_OK) {
      EXPECT_OK(status);
      return zx::exception();
    }
  
    tu_channel_read(channel.get(), 0, &info, &num_bytes, exception.reset_and_get_address(),
                    &num_handles);
    if (!exception.is_valid()) {
      EXPECT_TRUE(exception.is_valid());
      return zx::exception();
    }
  
    if (info_out != nullptr) {
      *info_out = info;
    }
  
    if (type && type != info.type) {
      EXPECT_EQ(type, info.type);
      return zx::exception();
    }
    return exception;
  }
  
  static void __NO_RETURN trigger_unsupported() {
    // An unsupported exception is not a failure.
    // Generally it just means that support for the exception doesn't
    // exist yet on this particular architecture.
    exit(0);
  }
  
  static void __NO_RETURN trigger_general() {
  #if defined(__x86_64__)
  #elif defined(__aarch64__)
  #endif
    trigger_unsupported();
  }
  
  static void __NO_RETURN trigger_fatal_page_fault() {
    *(volatile int*)0 = 42;
    trigger_unsupported();
  }
  
  static void __NO_RETURN trigger_undefined_insn() {
  #if defined(__x86_64__)
    __asm__("ud2");
  #elif defined(__aarch64__)
    // An instruction not supported at this privilege level will do.
    // ARM calls these "unallocated instructions". Geez, "unallocated"?
    __asm__("mrs x0, elr_el1");
  #endif
    trigger_unsupported();
  }
  
  static void __NO_RETURN trigger_sw_bkpt() {
  #if defined(__x86_64__)
    __asm__("int3");
  #elif defined(__aarch64__)
    __asm__("brk 0");
  #endif
    trigger_unsupported();
  }
  
  static void __NO_RETURN trigger_hw_bkpt() {
  #if defined(__x86_64__)
    // We can't set the debug regs from user space, support for setting the
    // debug regs via the debugger interface is work-in-progress, and we can't
    // use "int $1" here. So testing this will have to wait.
  #elif defined(__aarch64__)
  #endif
    trigger_unsupported();
  }
  
  // ARM does not trap on integer divide-by-zero.
  #if defined(__x86_64__)
  static void __NO_RETURN trigger_integer_divide_by_zero() {
    // Use an x86 division instruction (rather than doing division from C)
    // to ensure that the compiler does not convert the division into
    // something else.
    uint32_t result;
    __asm__ volatile("idivb %1" : "=a"(result) : "r"((uint8_t)0), "a"((uint16_t)1));
    trigger_unsupported();
  }
  
  static void __NO_RETURN trigger_sse_divide_by_zero() {
    // Unmask all exceptions for SSE operations.
    uint32_t mxcsr = 0;
    __asm__ volatile("ldmxcsr %0" : : "m"(mxcsr));
  
    double a = 1;
    double b = 0;
    __asm__ volatile("divsd %1, %0" : "+x"(a) : "x"(b));
  
    // QEMU's software emulation of x86 appears to have a bug where it does
    // not correctly emulate generating division-by-zero exceptions from
    // SSE instructions.  See https://bugs.launchpad.net/qemu/+bug/1668041.
    // So we will reach this point on non-KVM QEMU.  In this case, make the
    // test pass by generating a fault by other means.
    //
    // That means this test isn't requiring that "divsd" generates a fault.
    // It is only requiring that the fault is handled properly
    // (e.g. doesn't cause a kernel panic) if the instruction does fault
    // (as on real hardware).
    printf(
        "trigger_sse_divide_by_zero: divsd did not fault; "
        "assume we are running under a buggy non-KVM QEMU\n");
    trigger_integer_divide_by_zero();
  }
  
  static void __NO_RETURN trigger_x87_divide_by_zero() {
    // Unmask all exceptions for x87 operations.
    uint16_t control_word = 0;
    __asm__ volatile("fldcw %0" : : "m"(control_word));
  
    double a = 1;
    double b = 0;
    __asm__ volatile(
        "fldl %0\n"
        "fdivl %1\n"
        // Check for the pending exception.
        "fwait\n"
        :
        : "m"(a), "m"(b));
    trigger_unsupported();
  }
  #endif
  
  static const struct {
    zx_excp_type_t type;
    const char* name;
    bool crashes;
    void __NO_RETURN (*trigger_function)();
  } exceptions[] = {
      {ZX_EXCP_GENERAL, "general", false, trigger_general},
      {ZX_EXCP_FATAL_PAGE_FAULT, "page-fault", true, trigger_fatal_page_fault},
      {ZX_EXCP_UNDEFINED_INSTRUCTION, "undefined-insn", true, trigger_undefined_insn},
      {ZX_EXCP_SW_BREAKPOINT, "sw-bkpt", true, trigger_sw_bkpt},
      {ZX_EXCP_HW_BREAKPOINT, "hw-bkpt", false, trigger_hw_bkpt},
  #if defined(__x86_64__)
      {ZX_EXCP_GENERAL, "integer-divide-by-zero", true, trigger_integer_divide_by_zero},
      {ZX_EXCP_GENERAL, "sse-divide-by-zero", true, trigger_sse_divide_by_zero},
      {ZX_EXCP_GENERAL, "x87-divide-by-zero", true, trigger_x87_divide_by_zero},
  #endif
  };
  
  static void __NO_RETURN trigger_exception(const char* excp_name) {
    for (size_t i = 0; i < fbl::count_of(exceptions); ++i) {
      if (strcmp(excp_name, exceptions[i].name) == 0) {
        exceptions[i].trigger_function();
      }
    }
    fprintf(stderr, "unknown exception: %s\n", excp_name);
    exit(1);
  }
  
  static void __NO_RETURN test_child_trigger(const char* excp_name) {
    trigger_exception(excp_name);
    /* NOTREACHED */
  }
  
  TEST(ExceptionTest, Trigger) {
    for (size_t i = 0; i < fbl::count_of(exceptions); ++i) {
      zx_excp_type_t excp_type = exceptions[i].type;
      const char* excp_name = exceptions[i].name;
      zx::process child;
      zx::channel exception_channel, our_channel;
      std::string arg = std::string("trigger=") + excp_name;
      start_test_child_with_exception_channel(*zx::job::default_job(), arg, &child,
                                              &exception_channel, &our_channel);
  
      test_exceptions::ExceptionCatcher catcher(*zx::job::default_job());
  
      // First read the THREAD_STARTING exception. We can just discard it
      // immediately since THREAD_STARTING doesn't care whether it's resumed or
      // not.
      zx_exception_info_t info;
      ReadException(exception_channel, ZX_EXCP_THREAD_STARTING, &info);
      const zx_koid_t tid = info.tid;
  
      // This can be |excp_type| or THREAD_EXITING if |excp_type| is unsupported.
      zx::exception exception = ReadException(exception_channel, std::nullopt, &info);
      ASSERT_EQ(tid, info.tid);
  
      if (info.type != ZX_EXCP_THREAD_EXITING) {
        ASSERT_EQ(excp_type, info.type);
        exception.reset();
  
        if (exceptions[i].crashes) {
          ASSERT_OK(catcher.ExpectException(child));
          ASSERT_OK(child.kill());
        }
  
        exception = ReadException(exception_channel, ZX_EXCP_THREAD_EXITING, &info);
        ASSERT_EQ(tid, info.tid);
      }
  
      // We've already seen tid's thread-exit report, so just skip that
      // test here.
      exception.reset();
      EXPECT_OK(child.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr));
    }
  }
  
  static void __NO_RETURN test_child_exit_closing_excp_handle() {
    // Test ZX-1544. Process termination closing the last handle of the exception
    // channel should not cause a panic.
    tu_create_exception_channel(zx::process::self()->get(), 0);
    exit(0);
  
    /* NOTREACHED */
  }
  
  TEST(ExceptionTest, ExitClosingExcpHandle) {
    const char* test_child_path = program_path;
    const char* const argv[] = {
        test_child_path,
        exit_closing_excp_handle_child_name,
    };
    int argc = fbl::count_of(argv);
  
    springboard_t* sb = tu_launch_init(zx_job_default(), exit_closing_excp_handle_child_name, argc,
                                       argv, 0, NULL, 0, NULL, NULL);
    zx_handle_t child = tu_launch_fini(sb);
  
    zx_signals_t signals = ZX_PROCESS_TERMINATED;
    zx_signals_t pending;
    EXPECT_OK(tu_wait(1, &child, &signals, &pending));
    EXPECT_TRUE(pending & ZX_PROCESS_TERMINATED);
  
    EXPECT_EQ(tu_process_get_return_code(child), 0);
  }
  
  // Same as send_msg() but also allows ZX_ERR_PEER_CLOSED.
  // Useful for generic test cleanup to handle both live and killed tasks.
  static void SendMessageOrPeerClosed(const zx::channel& channel, message msg) {
    uint64_t data = msg;
    zx_status_t status = channel.write(0, &data, sizeof(data), nullptr, 0);
    if (status != ZX_OK && status != ZX_ERR_PEER_CLOSED) {
      tu_fatal(__func__, status);
    }
  }
  
  // C++ wrapper for our testing message loop to remove common boilerplate.
  //
  // Creates this test loop task structure under the current job:
  //   - parent job
  //     - job
  //       - process
  //         - thread
  //         - aux thread
  class TestLoop {
   public:
    enum class Control { kAutomatic, kManual };
  
    // TestLoop can operate in two different modes:
    //
    // Automatic control will take care of all the setup/teardown so that when
    // this constructor returns the test threads will be running, and when
    // the destructor is called they will be stopped and closed down.
    //
    // Manual control requires the caller to make the following calls in order:
    //   - Step1CreateProcess()
    //   - Step2StartThreads()
    //   - Step3FinishSetup()
    //   - Step4ShutdownAuxThread()
    //   - Step5ShutdownMainThread()
    // This is necessary to give the caller a chance to install exception
    // handlers in between each step, e.g. in order to catch THREAD_STARTING
    // synthetic exceptions.
    TestLoop(Control control = Control::kAutomatic) {
      EXPECT_OK(zx::job::create(*zx::job::default_job(), 0, &parent_job_));
      EXPECT_OK(zx::job::create(parent_job_, 0, &job_));
  
      if (control == Control::kAutomatic) {
        Step1CreateProcess();
        Step2StartThreads();
        Step3ReadAuxThreadHandle();
      }
    }
  
    void Step1CreateProcess() {
      springboard_ =
          setup_test_child(job_.get(), test_child_name, process_channel_.reset_and_get_address());
      ASSERT_NOT_NULL(springboard_);
      process_.reset(springboard_get_process_handle(springboard_));
    }
  
    void Step2StartThreads() {
      // The initial process handle we got is invalidated by this call
      // and we're given the new one to use instead.
      zx_handle_t process = tu_launch_fini(springboard_);
      if (process != process_.get()) {
        process_.reset(process);
      }
      ASSERT_TRUE(process_.is_valid());
      send_msg(process_channel_.get(), MSG_CREATE_AUX_THREAD);
    }
  
    // If there are any debugger handlers attached, the task start exceptions
    // must be handled before calling this or it will block forever.
    void Step3ReadAuxThreadHandle() {
      recv_msg_new_thread_handle(process_channel_.get(), aux_thread_.reset_and_get_address());
    }
  
    void Step4ShutdownAuxThread() {
      // Don't use use zx_task_kill() here, it stops exception processing
      // immediately so we may miss expected exceptions.
      SendMessageOrPeerClosed(process_channel_, MSG_SHUTDOWN_AUX_THREAD);
    }
  
    void Step5ShutdownMainThread() { SendMessageOrPeerClosed(process_channel_, MSG_DONE); }
  
    // Closes the test tasks and blocks until everything has cleaned up.
    //
    // If there is an active debug handler, the process must be closed first
    // via zx_task_kill() or Shutdown(), or else this can block forever waiting
    // for the thread exit exceptions to be handled.
    ~TestLoop() {
      // It's OK to call these multiple times so we can just unconditionally
      // call them in both automatic or manual control mode.
      Step4ShutdownAuxThread();
      Step5ShutdownMainThread();
  
      EXPECT_OK(process_.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), nullptr));
    }
  
    const zx::job& parent_job() const { return parent_job_; }
    const zx::job& job() const { return job_; }
    const zx::process& process() const { return process_; }
    const zx::thread& aux_thread() const { return aux_thread_; }
  
    // Sends a message to the aux thread to crash itself.
    //
    // If this is used, before exiting the test either kill the aux thread or
    // pass the exception to the unittest crash handler and block until it
    // kills the thread.
    //
    // The blocking is important because otherwise there's a race where the loop
    // process main thread can exit and kill the aux thread before the crash
    // handler gets a chance to see the exception. If this happens, the crash
    // handler will notice there was a registered exception that never occurred
    // and will fail the test.
    void CrashAuxThread() { send_msg(process_channel_.get(), MSG_CRASH_AUX_THREAD); }
  
   private:
    springboard* springboard_ = nullptr;
    zx::job parent_job_;
    zx::job job_;
    zx::process process_;
    zx::channel process_channel_;
    zx::thread aux_thread_;
  };
  
  // Returns true if the exception has a thread handle. If |koid| is given,
  // also checks that the thread's koid matches it.
  bool ExceptionHasThread(const zx::exception& exception, zx_koid_t koid = ZX_KOID_INVALID) {
    zx::thread thread;
    if (exception.get_thread(&thread) != ZX_OK) {
      return false;
    }
    return koid == ZX_KOID_INVALID || koid == tu_get_koid(thread.get());
  }
  
  // Returns true if the exception has a process handle. If |koid| is given,
  // also checks that the process' koid matches it.
  bool ExceptionHasProcess(const zx::exception& exception, zx_koid_t koid = ZX_KOID_INVALID) {
    zx::process process;
    if (exception.get_process(&process) != ZX_OK) {
      return false;
    }
    return koid == ZX_KOID_INVALID || koid == tu_get_koid(process.get());
  }
  
  uint32_t GetExceptionStateProperty(const zx::exception& exception) {
    uint32_t state = ~0;
    EXPECT_OK(exception.get_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)));
    return state;
  }
  
  void SetExceptionStateProperty(const zx::exception& exception, uint32_t state) {
    ASSERT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)));
  }
  
  // A finite timeout to use when you want to make sure something isn't happening
  // e.g. a certain signal isn't going to be asserted.
  auto constexpr kTestTimeout = zx::msec(50);
  
  TEST(ExceptionTest, CreateExceptionChannel) {
    TestLoop loop;
  
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
    EXPECT_TRUE(exception_channel.is_valid());
  }
  
  TEST(ExceptionTest, CreateExceptionChannelRights) {
    TestLoop loop;
  
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0, &exception_channel));
  
    zx_info_handle_basic_t info;
    ASSERT_OK(
        exception_channel.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr));
  
    // If this set of rights ever changes make sure to adjust the
    // task_create_exception_channel() documentation as well.
    EXPECT_EQ(info.rights, ZX_RIGHT_TRANSFER | ZX_RIGHT_WAIT | ZX_RIGHT_READ);
  }
  
  TEST(ExceptionTest, CreateExceptionChannelInvalidArgs) {
    TestLoop loop;
  
    zx::channel exception_channel;
    EXPECT_EQ(
        loop.aux_thread().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &exception_channel),
        ZX_ERR_INVALID_ARGS);
  }
  
  TEST(ExceptionTest, ProcessDebuggerAttached) {
    TestLoop loop;
  
    zx_info_process_t info;
    ASSERT_OK(loop.process().get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr));
    EXPECT_FALSE(info.debugger_attached);
  
    {
      zx::channel exception_channel;
      ASSERT_OK(
          loop.process().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &exception_channel));
  
      ASSERT_OK(loop.process().get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr));
      EXPECT_TRUE(info.debugger_attached);
    }
  
    ASSERT_OK(loop.process().get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr));
    EXPECT_FALSE(info.debugger_attached);
  }
  
  // Removes a right from a task and ensures that channel creation now fails.
  //
  // |task_func|: TestLoop member function to get the task.
  // |right|: ZX_RIGHT_* value to remove.
  template <auto task_func>
  void TaskRequiresRight(zx_rights_t right) {
    TestLoop loop;
    const auto& task = (loop.*task_func)();
  
    zx_info_handle_basic_t info;
    tu_handle_get_basic_info(task.get(), &info);
  
    auto reduced_task = typename std::remove_reference<decltype(task)>::type();
    ASSERT_OK(task.duplicate(info.rights & ~right, &reduced_task));
  
    zx::channel exception_channel;
    EXPECT_EQ(reduced_task.create_exception_channel(0, &exception_channel), ZX_ERR_ACCESS_DENIED);
  }
  
  TEST(ExceptionTest, ThreadRequiresRights) {
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::aux_thread>(ZX_RIGHT_INSPECT));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::aux_thread>(ZX_RIGHT_DUPLICATE));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::aux_thread>(ZX_RIGHT_TRANSFER));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::aux_thread>(ZX_RIGHT_MANAGE_THREAD));
  }
  
  TEST(ExceptionTest, ProcessRequiresRights) {
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::process>(ZX_RIGHT_INSPECT));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::process>(ZX_RIGHT_DUPLICATE));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::process>(ZX_RIGHT_TRANSFER));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::process>(ZX_RIGHT_MANAGE_THREAD));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::process>(ZX_RIGHT_ENUMERATE));
  }
  
  TEST(ExceptionTest, JobRequiresRights) {
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::job>(ZX_RIGHT_INSPECT));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::job>(ZX_RIGHT_DUPLICATE));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::job>(ZX_RIGHT_TRANSFER));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::job>(ZX_RIGHT_MANAGE_THREAD));
    ASSERT_NO_FAILURES(TaskRequiresRight<&TestLoop::job>(ZX_RIGHT_ENUMERATE));
  }
  
  TEST(ExceptionTest, CreateSecondExceptionChannel) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    // Trying to register a second channel should fail.
    zx::channel exception_channel2;
    EXPECT_EQ(loop.aux_thread().create_exception_channel(0u, &exception_channel2),
              ZX_ERR_ALREADY_BOUND);
    EXPECT_FALSE(exception_channel2.is_valid());
  }
  
  TEST(ExceptionTest, OverwriteClosedExceptionChannel) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    // If we close the existing channel, registering a new one should succeed.
    exception_channel.reset();
    zx::channel exception_channel2;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel2));
    EXPECT_TRUE(exception_channel2.is_valid());
  }
  
  // This is the basic test to receive an exception, parameterized so we can
  // easily run it against all the different exception handler types.
  //
  // |task_func|: TestLoop member function to get the task.
  // |create_flags|: flags to pass to zx_task_create_exception_channel().
  // |expected_type|: expected exception type reported in zx_info_thread_t.
  // |has_process|: true if the exception should have a process handle.
  template <auto task_func>
  void receive_test(uint32_t create_flags, uint32_t expected_type, bool has_process) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK((loop.*task_func)().create_exception_channel(create_flags, &exception_channel));
  
    loop.CrashAuxThread();
    zx_exception_info_t exception_info;
    zx::exception exception =
        ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT, &exception_info);
  
    // Make sure exception info is correct.
    EXPECT_EQ(exception_info.tid, tu_get_koid(loop.aux_thread().get()));
    EXPECT_TRUE(ExceptionHasThread(exception, exception_info.tid));
  
    EXPECT_EQ(exception_info.pid, tu_get_koid(loop.process().get()));
    if (has_process) {
      EXPECT_TRUE(ExceptionHasProcess(exception, exception_info.pid));
    } else {
      EXPECT_FALSE(ExceptionHasProcess(exception));
    }
  
    // Make sure the thread state is correct.
    zx_info_thread_t thread_info = tu_thread_get_info(loop.aux_thread().get());
    EXPECT_EQ(thread_info.state, ZX_THREAD_STATE_BLOCKED_EXCEPTION);
    EXPECT_EQ(thread_info.wait_exception_channel_type, expected_type);
  
    test_exceptions::ExceptionCatcher catcher(*zx::job::default_job());
    exception.reset();
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, ThreadReceive) {
    receive_test<&TestLoop::aux_thread>(0, ZX_EXCEPTION_CHANNEL_TYPE_THREAD, false);
  }
  
  TEST(ExceptionTest, ProcessReceive) {
    receive_test<&TestLoop::process>(0, ZX_EXCEPTION_CHANNEL_TYPE_PROCESS, true);
  }
  
  TEST(ExceptionTest, ProcessDebuggerReceive) {
    receive_test<&TestLoop::process>(ZX_EXCEPTION_CHANNEL_DEBUGGER,
                                     ZX_EXCEPTION_CHANNEL_TYPE_DEBUGGER, true);
  }
  
  TEST(ExceptionTest, JobReceive) {
    receive_test<&TestLoop::job>(0, ZX_EXCEPTION_CHANNEL_TYPE_JOB, true);
  }
  
  TEST(ExceptionTest, JobDebuggerReceive) {
    receive_test<&TestLoop::parent_job>(0, ZX_EXCEPTION_CHANNEL_TYPE_JOB, true);
  }
  
  TEST(ExceptionTest, ExceptionResume) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    loop.CrashAuxThread();
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    // If we tell this exception to resume the thread, it should fault
    // again and return another exception back to us rather than
    // bubbling up the chain.
    SetExceptionStateProperty(exception, ZX_EXCEPTION_STATE_HANDLED);
    exception.reset();
    exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    // Close the new exception without marking it handled so it bubbles up.
    test_exceptions::ExceptionCatcher catcher(loop.process());
    exception.reset();
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, ExceptionStateProperty) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    loop.CrashAuxThread();
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    // By default exceptions should be unhandled.
    EXPECT_EQ(GetExceptionStateProperty(exception), ZX_EXCEPTION_STATE_TRY_NEXT);
  
    SetExceptionStateProperty(exception, ZX_EXCEPTION_STATE_HANDLED);
    EXPECT_EQ(GetExceptionStateProperty(exception), ZX_EXCEPTION_STATE_HANDLED);
  
    SetExceptionStateProperty(exception, ZX_EXCEPTION_STATE_TRY_NEXT);
    EXPECT_EQ(GetExceptionStateProperty(exception), ZX_EXCEPTION_STATE_TRY_NEXT);
  
    test_exceptions::ExceptionCatcher catcher(loop.process());
    exception.reset();
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, ExceptionStatePropertyBadArgs) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    loop.CrashAuxThread();
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    // Wrong handle type.
    uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
    EXPECT_EQ(loop.aux_thread().set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)),
              ZX_ERR_WRONG_TYPE);
    EXPECT_EQ(loop.aux_thread().get_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)),
              ZX_ERR_WRONG_TYPE);
  
    // Illegal state value.
    state = ~0;
    EXPECT_EQ(exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)),
              ZX_ERR_INVALID_ARGS);
  
    // Buffer too short.
    state = ZX_EXCEPTION_STATE_HANDLED;
    EXPECT_EQ(exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state) - 1),
              ZX_ERR_BUFFER_TOO_SMALL);
    EXPECT_EQ(exception.get_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state) - 1),
              ZX_ERR_BUFFER_TOO_SMALL);
  
    test_exceptions::ExceptionCatcher catcher(loop.process());
    exception.reset();
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, CloseChannelWithException) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    loop.CrashAuxThread();
    ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr));
  
    // Closing the channel while it still contains the exception should pass
    // control to the next handler.
    test_exceptions::ExceptionCatcher catcher(loop.process());
    exception_channel.reset();
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, CloseChannelWithoutException) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    loop.CrashAuxThread();
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    // Closing the channel after the exception object has been read out has no
    // effect since the exception object now controls the exception lifecycle.
    exception_channel.reset();
  
    // Wait a little bit to make sure the thread really is still blocked on our
    // exception object. If it wasn't, the exception would filter up now and
    // ExpectException() will deadlock when it fails to find the exception.
    zx::nanosleep(zx::deadline_after(kTestTimeout));
  
    test_exceptions::ExceptionCatcher catcher(loop.process());
    exception.reset();
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  // Make sure a closed exception channel has no effect on other handlers.
  TEST(ExceptionTest, SkipClosedExceptionChannel) {
    TestLoop loop;
    zx::channel job_channel, process_channel;
    ASSERT_OK(loop.job().create_exception_channel(0, &job_channel));
    ASSERT_OK(loop.process().create_exception_channel(0, &process_channel));
  
    {
      zx::channel thread_channel;
      ASSERT_OK(loop.aux_thread().create_exception_channel(0, &thread_channel));
    }
  
    loop.CrashAuxThread();
  
    // We should receive the exception on the process handler and it should
    // wait for our response as normal.
    {
      zx::exception exception = ReadException(process_channel, ZX_EXCP_FATAL_PAGE_FAULT);
      ASSERT_EQ(job_channel.wait_one(ZX_CHANNEL_READABLE, zx::deadline_after(kTestTimeout), nullptr),
                ZX_ERR_TIMED_OUT);
    }
  
    // The exception should continue up to the job handler as normal.
    zx::exception exception = ReadException(job_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    ASSERT_OK(loop.aux_thread().kill());
  }
  
  // Killing the task should mark its exception channels with PEER_CLOSED.
  // Parameterized to more easily run it against the different handler types.
  template <auto task_func>
  void TaskDeathClosesExceptionChannel(uint32_t create_flags) {
    TestLoop loop;
    const auto& task = (loop.*task_func)();
    zx::channel exception_channel;
    ASSERT_OK(task.create_exception_channel(create_flags, &exception_channel));
  
    ASSERT_OK(task.kill());
    EXPECT_OK(exception_channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, TaskDeathClosesThreadExceptionChannel) {
    TaskDeathClosesExceptionChannel<&TestLoop::aux_thread>(0);
  }
  
  TEST(ExceptionTest, TaskDeathClosesProcessExceptionChannel) {
    TaskDeathClosesExceptionChannel<&TestLoop::process>(0);
  }
  
  TEST(ExceptionTest, TaskDeathClosesProcessDebugExceptionChannel) {
    TaskDeathClosesExceptionChannel<&TestLoop::process>(ZX_EXCEPTION_CHANNEL_DEBUGGER);
  }
  
  TEST(ExceptionTest, TaskDeathClosesJobExceptionChannel) {
    TaskDeathClosesExceptionChannel<&TestLoop::job>(0);
  }
  
  TEST(ExceptionTest, TaskDeathClosesJobDebugExceptionChannel) {
    TaskDeathClosesExceptionChannel<&TestLoop::job>(ZX_EXCEPTION_CHANNEL_DEBUGGER);
  }
  
  TEST(ExceptionTest, ThreadDeathWithExceptionInChannel) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    // Crash the thread and wait for the exception to be in the channel.
    loop.CrashAuxThread();
    ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), nullptr));
  
    // Killing the thread doesn't remove the exception from the channel, but
    // it does signal PEER_CLOSED.
    zx_signals_t observed;
    ASSERT_OK(loop.aux_thread().kill());
    EXPECT_OK(exception_channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &observed));
    EXPECT_TRUE(observed & ZX_CHANNEL_READABLE);
  
    // Receiving and closing the exception has no effect. Operations on the
    // exception should still succeed.
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
    uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
    EXPECT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)));
    EXPECT_OK(exception.get_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)));
  }
  
  // Similar to the above test, but pull the exception out of the channel before
  // killing the thread and make sure behavior is consistent.
  TEST(ExceptionTest, ThreadDeathWithExceptionReceived) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channel));
  
    loop.CrashAuxThread();
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    zx_signals_t observed;
    ASSERT_OK(loop.aux_thread().kill());
    ASSERT_OK(exception_channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &observed));
    EXPECT_FALSE(observed & ZX_CHANNEL_READABLE);
  
    uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
    EXPECT_OK(exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)));
    EXPECT_OK(exception.get_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state)));
  }
  
  TEST(ExceptionTest, ExceptionChannelOrder) {
    TestLoop loop;
  
    // Set the exception channels up in the expected order.
    zx::channel exception_channels[5];
    ASSERT_OK(loop.process().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER,
                                                      &exception_channels[0]));
    ASSERT_OK(loop.aux_thread().create_exception_channel(0u, &exception_channels[1]));
    ASSERT_OK(loop.process().create_exception_channel(0u, &exception_channels[2]));
    ASSERT_OK(loop.job().create_exception_channel(0u, &exception_channels[3]));
    ASSERT_OK(loop.parent_job().create_exception_channel(0u, &exception_channels[4]));
  
    loop.CrashAuxThread();
    test_exceptions::ExceptionCatcher catcher(*zx::job::default_job());
  
    for (const zx::channel& channel : exception_channels) {
      ReadException(channel, ZX_EXCP_FATAL_PAGE_FAULT);
    }
  
    ASSERT_OK(catcher.ExpectException(loop.aux_thread()));
    EXPECT_OK(loop.aux_thread().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
  }
  
  TEST(ExceptionTest, ThreadLifecycleChannelExceptions) {
    TestLoop loop(TestLoop::Control::kManual);
  
    loop.Step1CreateProcess();
    zx::channel exception_channel;
    ASSERT_OK(
        loop.process().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &exception_channel));
  
    // We should get both primary and aux thread exceptions.
    loop.Step2StartThreads();
  
    zx_exception_info_t primary_start_info;
    {
      zx::exception exception =
          ReadException(exception_channel, ZX_EXCP_THREAD_STARTING, &primary_start_info);
      EXPECT_EQ(primary_start_info.pid, tu_get_koid(loop.process().get()));
      EXPECT_TRUE(ExceptionHasThread(exception, primary_start_info.tid));
      EXPECT_TRUE(ExceptionHasProcess(exception, primary_start_info.pid));
    }
  
    zx_exception_info_t aux_start_info;
    {
      zx::exception exception =
          ReadException(exception_channel, ZX_EXCP_THREAD_STARTING, &aux_start_info);
      EXPECT_EQ(aux_start_info.pid, tu_get_koid(loop.process().get()));
      EXPECT_TRUE(ExceptionHasThread(exception, aux_start_info.tid));
      EXPECT_TRUE(ExceptionHasProcess(exception, aux_start_info.pid));
    }
  
    // We don't have access to the primary thread handle so just check the aux
    // thread TID to make sure it's correct.
    loop.Step3ReadAuxThreadHandle();
    EXPECT_EQ(aux_start_info.tid, tu_get_koid(loop.aux_thread().get()));
  
    loop.Step4ShutdownAuxThread();
    zx_exception_info_t aux_exit_info;
    {
      zx::exception exception =
          ReadException(exception_channel, ZX_EXCP_THREAD_EXITING, &aux_exit_info);
      EXPECT_TRUE(ExceptionHasThread(exception, aux_exit_info.tid));
      EXPECT_TRUE(ExceptionHasProcess(exception, aux_exit_info.pid));
      EXPECT_EQ(aux_exit_info.tid, aux_start_info.tid);
      EXPECT_EQ(aux_exit_info.pid, aux_start_info.pid);
    }
  
    loop.Step5ShutdownMainThread();
    zx_exception_info_t primary_exit_info;
    {
      zx::exception exception =
          ReadException(exception_channel, ZX_EXCP_THREAD_EXITING, &primary_exit_info);
      EXPECT_TRUE(ExceptionHasThread(exception, primary_exit_info.tid));
      EXPECT_TRUE(ExceptionHasProcess(exception, primary_exit_info.pid));
      EXPECT_EQ(primary_exit_info.tid, primary_start_info.tid);
      EXPECT_EQ(primary_exit_info.pid, primary_start_info.pid);
    }
  }
  
  // Parameterized to run against either the TestLoop job or parent job.
  template <auto task_func>
  void VerifyProcessLifecycle() {
    zx::channel exception_channel;
    {
      TestLoop loop(TestLoop::Control::kManual);
  
      ASSERT_OK((loop.*task_func)().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER,
                                                             &exception_channel));
  
      // ZX_EXCP_PROCESS_STARTING shouldn't be sent until step 2 when we
      // actually start the first thread on the process.
      loop.Step1CreateProcess();
      EXPECT_EQ(
          exception_channel.wait_one(ZX_CHANNEL_READABLE, zx::deadline_after(kTestTimeout), nullptr),
          ZX_ERR_TIMED_OUT);
  
      loop.Step2StartThreads();
      zx_exception_info_t info;
      {
        zx::exception exception = ReadException(exception_channel, ZX_EXCP_PROCESS_STARTING, &info);
        EXPECT_EQ(info.pid, tu_get_koid(loop.process().get()));
        EXPECT_TRUE(ExceptionHasThread(exception, info.tid));
        EXPECT_TRUE(ExceptionHasProcess(exception, info.pid));
      }
  
      loop.Step3ReadAuxThreadHandle();
      loop.Step4ShutdownAuxThread();
      loop.Step5ShutdownMainThread();
    }
  
    // There is no PROCESS_EXITING exception, make sure the kernel finishes
    // closing the channel without putting anything else in it.
    //
    // Unlike processes, jobs don't automatically die with their last child,
    // so the TestLoop handles must be fully closed at this point to get the
    // PEER_CLOSED signal.
    zx_signals_t signals;
    EXPECT_OK(exception_channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &signals));
    EXPECT_FALSE(signals & ZX_CHANNEL_READABLE);
  }
  
  TEST(ExceptionTest, ProcessLifecycleJobChannel) { VerifyProcessLifecycle<&TestLoop::job>(); }
  
  TEST(ExceptionTest, ProcessLifecycleParentJobChannel) {
    VerifyProcessLifecycle<&TestLoop::parent_job>();
  }
  
  TEST(ExceptionTest, ProcessStartExceptionDoesNotBubbleUp) {
    zx::channel parent_exception_channel;
    zx::channel exception_channel;
    {
      TestLoop loop(TestLoop::Control::kManual);
  
      ASSERT_OK(loop.parent_job().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER,
                                                           &parent_exception_channel));
      ASSERT_OK(
          loop.job().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &exception_channel));
  
      loop.Step1CreateProcess();
      loop.Step2StartThreads();
      ReadException(exception_channel, ZX_EXCP_PROCESS_STARTING);
  
      loop.Step3ReadAuxThreadHandle();
      loop.Step4ShutdownAuxThread();
      loop.Step5ShutdownMainThread();
    }
  
    // The parent job channel should never have seen anything since synthetic
    // PROCESS_STARTING exceptions do not bubble up the job chain.
    zx_signals_t signals;
    EXPECT_OK(
        parent_exception_channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &signals));
    EXPECT_FALSE(signals & ZX_CHANNEL_READABLE);
  }
  
  // Lifecycle exceptions should not be seen by normal (non-debug) handlers.
  TEST(ExceptionTest, LifecycleExceptionsToDebugHandlersOnly) {
    zx::channel exception_channels[4];
    {
      TestLoop loop(TestLoop::Control::kManual);
      ASSERT_OK(loop.parent_job().create_exception_channel(0, &exception_channels[0]));
      ASSERT_OK(loop.job().create_exception_channel(0, &exception_channels[1]));
  
      loop.Step1CreateProcess();
      ASSERT_OK(loop.process().create_exception_channel(0, &exception_channels[2]));
  
      loop.Step2StartThreads();
      loop.Step3ReadAuxThreadHandle();
      ASSERT_OK(loop.aux_thread().create_exception_channel(0, &exception_channels[3]));
  
      loop.Step4ShutdownAuxThread();
      loop.Step5ShutdownMainThread();
    }
  
    // None of the normal handlers should have seen any exceptions.
    for (const zx::channel& channel : exception_channels) {
      zx_signals_t signals;
      EXPECT_OK(channel.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &signals));
      EXPECT_FALSE(signals & ZX_CHANNEL_READABLE);
    }
  }
  
  // Returns the state of the thread underlying the given exception or
  // an invalid state on failure.
  zx_thread_state_t GetExceptionThreadState(const zx::exception& exception) {
    zx::thread thread;
    if (exception.get_thread(&thread) != ZX_OK) {
      return ~0;
    }
    return tu_thread_get_info(thread.get()).state;
  }
  
  // A lifecycle exception blocks due to:
  //   * process/thread start
  //   * thread killing itself via zx_thread_exit()
  //
  // It does not block due to:
  //   * zx_task_kill() on the thread or any of its parents
  //
  // In the non-blocking case, the exception is still sent, but the thread
  // doesn't wait for a response.
  TEST(ExceptionTest, LifecycleBlocking) {
    TestLoop loop(TestLoop::Control::kManual);
    loop.Step1CreateProcess();
  
    zx::channel job_channel;
    ASSERT_OK(loop.job().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &job_channel));
    zx::channel process_channel;
    ASSERT_OK(
        loop.process().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &process_channel));
  
    // Process/thread start: exception handler should block the task.
    loop.Step2StartThreads();
    {
      zx::exception exception = ReadException(job_channel, ZX_EXCP_PROCESS_STARTING);
      zx::nanosleep(zx::deadline_after(kTestTimeout));
      EXPECT_EQ(GetExceptionThreadState(exception), ZX_THREAD_STATE_BLOCKED_EXCEPTION);
    }
    for (int i = 0; i < 2; ++i) {
      zx::exception exception = ReadException(process_channel, ZX_EXCP_THREAD_STARTING);
      zx::nanosleep(zx::deadline_after(kTestTimeout));
      EXPECT_EQ(GetExceptionThreadState(exception), ZX_THREAD_STATE_BLOCKED_EXCEPTION);
    }
  
    // The aux thread exits gracefully via zx_thread_exit() so should block.
    loop.Step3ReadAuxThreadHandle();
    loop.Step4ShutdownAuxThread();
    {
      zx::exception exception = ReadException(process_channel, ZX_EXCP_THREAD_EXITING);
      zx::nanosleep(zx::deadline_after(kTestTimeout));
      // The thread reports DYING because it takes precedence over BLOCKED,
      // but if it wasn't actually blocking it would report DEAD by now.
      EXPECT_EQ(GetExceptionThreadState(exception), ZX_THREAD_STATE_DYING);
    }
  
    // The main thread shuts down the whole process via zx_task_kill() so
    // should not block.
    loop.Step5ShutdownMainThread();
    {
      zx::exception exception = ReadException(process_channel, ZX_EXCP_THREAD_EXITING);
      zx::thread thread;
      EXPECT_OK(zx_exception_get_thread(exception.get(), thread.reset_and_get_address()));
      EXPECT_OK(thread.wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
      EXPECT_EQ(GetExceptionThreadState(exception), ZX_THREAD_STATE_DEAD);
    }
  }
  
  // Test read/write register state during (non-synthetic) exceptions.
  //
  // |task_func|: TestLoop member function to get the task.
  // |create_flags|: flags to pass to zx_task_create_exception_channel().
  template <auto task_func>
  void ReadWriteThreadState(uint32_t create_flags) {
    TestLoop loop;
    zx::channel exception_channel;
    ASSERT_OK((loop.*task_func)().create_exception_channel(create_flags, &exception_channel));
  
    loop.CrashAuxThread();
    zx::exception exception = ReadException(exception_channel, ZX_EXCP_FATAL_PAGE_FAULT);
  
    zx_thread_state_general_regs_t regs;
    EXPECT_OK(loop.aux_thread().read_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs)));
    EXPECT_OK(loop.aux_thread().write_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs)));
  
    EXPECT_OK(loop.aux_thread().kill());
  }
  
  TEST(ExceptionTest, ReadWriteThreadStateFromThreadChannel) {
    ReadWriteThreadState<&TestLoop::aux_thread>(0);
  }
  
  TEST(ExceptionTest, ReadWriteThreadStateFromProcessChannel) {
    ReadWriteThreadState<&TestLoop::process>(0);
  }
  
  TEST(ExceptionTest, ReadWriteThreadStateFromProcessDebugChannel) {
    ReadWriteThreadState<&TestLoop::process>(ZX_EXCEPTION_CHANNEL_DEBUGGER);
  }
  
  TEST(ExceptionTest, ReadWriteThreadStateFromJobChannel) { ReadWriteThreadState<&TestLoop::job>(0); }
  
  TEST(ExceptionTest, ReadWriteThreadStateFromParentJobChannel) {
    ReadWriteThreadState<&TestLoop::parent_job>(0);
  }
  
  // Processes an exception and returns the result of trying to read/write
  // the thread general registers.
  //
  // If read/write return different status, marks a test failure and returns
  // ZX_ERR_INTERNAL.
  zx_status_t ExceptionRegAccess(const zx::channel& channel, zx_excp_type_t type) {
    zx_exception_info_t info;
    zx::exception exception = ReadException(channel, type, &info);
  
    zx::thread thread;
    EXPECT_OK(exception.get_thread(&thread));
    if (!thread.is_valid()) {
      return ZX_ERR_INTERNAL;
    }
  
    zx_thread_state_general_regs_t regs;
    zx_status_t read_status = thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
    zx_status_t write_status = thread.write_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
  
    EXPECT_EQ(read_status, write_status);
    if (read_status != write_status) {
      return ZX_ERR_INTERNAL;
    }
    return read_status;
  }
  
  // Read/write register state is supported during STARTING exceptions, but not
  // during EXITING.
  TEST(ExceptionTest, SyntheticExceptionReadWriteRegs) {
    zx::channel job_channel;
    zx::channel process_channel;
  
    TestLoop loop(TestLoop::Control::kManual);
    ASSERT_OK(loop.job().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &job_channel));
  
    loop.Step1CreateProcess();
    ASSERT_OK(
        loop.process().create_exception_channel(ZX_EXCEPTION_CHANNEL_DEBUGGER, &process_channel));
  
    loop.Step2StartThreads();
    EXPECT_OK(ExceptionRegAccess(job_channel, ZX_EXCP_PROCESS_STARTING));
    EXPECT_OK(ExceptionRegAccess(process_channel, ZX_EXCP_THREAD_STARTING));
    EXPECT_OK(ExceptionRegAccess(process_channel, ZX_EXCP_THREAD_STARTING));
  
    loop.Step3ReadAuxThreadHandle();
    loop.Step4ShutdownAuxThread();
    EXPECT_EQ(ExceptionRegAccess(process_channel, ZX_EXCP_THREAD_EXITING), ZX_ERR_NOT_SUPPORTED);
  
    // When the main thread is shut down it kills the whole process, which
    // causes it to stop waiting for responses from exception handlers. We'll
    // still receive the exception, but by the time we process it here it's
    // likely that the thread is already dead so we can't check reg access.
    loop.Step5ShutdownMainThread();
    ReadException(process_channel, ZX_EXCP_THREAD_EXITING);
  }
  
  void CrashThreadFunc(uintptr_t, uintptr_t) {
    crash_me();
    zx_thread_exit();
  }
  
  // Test killing a thread then immediately closing the exception never
  // propagates the exception (ZX-4105).
  //
  // This isn't possible to test deterministically so we just try to run it
  // for a little bit, if this looks like it's becoming flaky it probably
  // indicates a real underlying bug. Failures would manifest as the unittest
  // crash handler seeing an unregistered crash.
  constexpr zx::duration kRaceTestDuration = zx::sec(1);
  
  TEST(ExceptionTest, KillThreadAndCloseExceptionRace) {
    zx::time end_time = zx::deadline_after(kRaceTestDuration);
    while (zx::clock::get_monotonic() < end_time) {
      zx::thread thread;
      ASSERT_OK(zx::thread::create(*zx::process::self(), "crasher", strlen("crasher"), 0u, &thread));
  
      zx::channel channel;
      ASSERT_OK(thread.create_exception_channel(0, &channel));
  
      alignas(16) static uint8_t thread_stack[128];
      thread.start(&CrashThreadFunc, thread_stack + sizeof(thread_stack), 0, 0);
  
      zx::exception exception = ReadException(channel, ZX_EXCP_FATAL_PAGE_FAULT);
      ASSERT_TRUE(exception.is_valid());
  
      ASSERT_OK(thread.kill());
      exception.reset();
      ASSERT_OK(thread.wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
    }
  }
  
  static const char* check_trigger(int argc, char** argv) {
    static const char trigger[] = "trigger=";
    for (int i = 1; i < argc; ++i) {
      if (strncmp(argv[i], trigger, sizeof(trigger) - 1) == 0) {
        return argv[i] + sizeof(trigger) - 1;
      }
    }
    return NULL;
  }
  
  }  // namespace
  
  int main(int argc, char** argv) {
    program_path = argv[0];
  
    // We use this same binary for both the main test runner and a test process
    // running msg_loop(), but this can interfere with any common zxtest
    // arguments that get passed. If this becomes a problem, consider using
    // mini-process as the test process instead.
    if (argc >= 2) {
      const char* excp_name = check_trigger(argc, argv);
      if (excp_name) {
        test_child_trigger(excp_name);
        return 0;
      }
      if (strcmp(argv[1], test_child_name) == 0) {
        test_child();
        return 0;
      }
      if (strcmp(argv[1], exit_closing_excp_handle_child_name) == 0) {
        test_child_exit_closing_excp_handle();
        /* NOTREACHED */
      }
    }
  
    return RUN_ALL_TESTS(argc, argv);
  }
  