// 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. // N.B. We can't fully test the system exception handler here as that would // interfere with the global crash logger. // TODO(dbort): A good place to test the system exception handler would be in // the "core" tests. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int thread_func(void* arg); // argv[0] static char* program_path; // This is the key that is assigned to the port when bound. // This value appears in |packet.key| in all exception messages. static const uint64_t EXCEPTION_PORT_KEY = 0x6b6579; // "key" 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(void) { unittest_printf("Attempting to crash."); 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; unittest_printf("sending new thread %d message on handle %u\n", thread, handle); tu_channel_write(handle, 0, &data, sizeof(data), &thread, 1); } static void send_msg(zx_handle_t handle, enum message msg) { uint64_t data = msg; unittest_printf("sending message %d on handle %u\n", msg, handle); tu_channel_write(handle, 0, &data, sizeof(data), NULL, 0); } static bool recv_msg(zx_handle_t handle, enum message* msg) { uint64_t data; uint32_t num_bytes = sizeof(data); unittest_printf("waiting for message on handle %u\n", handle); if (!tu_channel_wait_readable(handle)) { unittest_printf("peer closed while trying to read message\n"); return false; } tu_channel_read(handle, 0, &data, &num_bytes, NULL, 0); if (num_bytes != sizeof(data)) { unittest_printf("recv_msg: unexpected message size, %u != %zu\n", num_bytes, sizeof(data)); return false; } *msg = data; unittest_printf("received message %d\n", *msg); return true; } // This returns "bool" because it uses ASSERT_*. static bool recv_msg_new_thread_handle(zx_handle_t handle, zx_handle_t* thread) { uint64_t data; uint32_t num_bytes = sizeof(data); unittest_printf("waiting for message on handle %u\n", handle); 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), "unexpected message size"); ASSERT_EQ(num_handles, 1u, "expected one returned handle"); enum message msg = data; ASSERT_EQ(msg, MSG_AUX_THREAD_HANDLE, "expected MSG_AUX_THREAD_HANDLE"); unittest_printf("received thread handle %d\n", *thread); return true; } // "resume" here means "tell the kernel we're done" // This test assumes no presence of the "debugger API" and therefore we can't // resume from a segfault. Such a test is for the debugger API anyway. static void resume_thread_from_exception(zx_handle_t process, zx_koid_t tid, uint32_t excp_port_type, uint32_t flags) { zx_handle_t thread; zx_status_t status = zx_object_get_child(process, tid, ZX_RIGHT_SAME_RIGHTS, &thread); if (status < 0) tu_fatal("zx_object_get_child", status); zx_info_thread_t info = tu_thread_get_info(thread); EXPECT_EQ(info.state, ZX_THREAD_STATE_BLOCKED_EXCEPTION, ""); if (excp_port_type != ZX_EXCEPTION_PORT_TYPE_NONE) { EXPECT_EQ(info.wait_exception_port_type, excp_port_type, ""); } status = zx_task_resume(thread, ZX_RESUME_EXCEPTION | flags); if (status < 0) tu_fatal("resume_thread_from_exception", status); zx_handle_close(thread); } // Wait for and receive an exception or signal on |eport|. static bool read_packet(zx_handle_t eport, zx_port_packet_t* packet) { ASSERT_EQ(zx_port_wait(eport, ZX_TIME_INFINITE, packet), ZX_OK, "zx_port_wait failed"); if (ZX_PKT_IS_SIGNAL_REP(packet->type)) { unittest_printf("signal received: key %" PRIu64 ", observed 0x%x\n", packet->key, packet->signal.observed); } else { ASSERT_TRUE(ZX_PKT_IS_EXCEPTION(packet->type), ""); ASSERT_EQ(packet->key, EXCEPTION_PORT_KEY, "bad report key"); ASSERT_EQ(packet->status, ZX_OK, ""); unittest_printf("exception received: pid %" PRIu64 ", tid %" PRIu64 ", type %d\n", packet->exception.pid, packet->exception.tid, packet->type); } return true; } // The bool result is because we use the unittest EXPECT/ASSERT macros. static bool verify_exception(const zx_port_packet_t* packet, zx_handle_t process, zx_excp_type_t expected_type) { ASSERT_EQ(packet->type, expected_type, ""); EXPECT_EQ(packet->key, EXCEPTION_PORT_KEY, ""); // Verify the exception was from |process|. if (process != ZX_HANDLE_INVALID) { zx_koid_t pid = tu_get_koid(process); EXPECT_EQ(pid, packet->exception.pid, ""); } return true; } // The bool result is because we use the unittest EXPECT/ASSERT macros. static bool verify_signal(const zx_port_packet_t* packet, uint64_t key, zx_signals_t expected_signals) { ASSERT_TRUE(ZX_PKT_IS_SIGNAL_ONE(packet->type) || ZX_PKT_IS_SIGNAL_REP(packet->type), ""); if (key != 0u) EXPECT_EQ(packet->key, key, ""); EXPECT_TRUE(packet->signal.observed & expected_signals, ""); return true; } static bool read_and_verify_exception(zx_handle_t eport, zx_handle_t process, zx_excp_type_t expected_type, zx_koid_t* tid) { zx_port_packet_t packet; if (!read_packet(eport, &packet)) return false; *tid = packet.exception.tid; return verify_exception(&packet, process, expected_type); } // Wait for a process to exit, and while it's exiting verify we get the // expected exception reports. // The caller must have attached an async-wait for |process| to |eport|. // See start_test_child_with_eport(). // We may receive thread-exit reports while the process is terminating but // any other kind of exception is an error. // This may be used when attached to the process or debugger exception port. // The bool result is because we use the unittest EXPECT/ASSERT macros. static bool wait_process_exit(zx_handle_t eport, zx_handle_t process) { zx_port_packet_t packet; zx_koid_t pid = tu_get_koid(process); for (;;) { unittest_printf("%s: calling read_packet\n", __func__); if (!read_packet(eport, &packet)) return false; unittest_printf("%s: read_packet done\n", __func__); // If we get a process exit signal then all threads have exited. // Any other signal packet is an error. if (ZX_PKT_IS_SIGNAL_ONE(packet.type) || ZX_PKT_IS_SIGNAL_REP(packet.type)) { if (packet.key == pid && (packet.signal.observed & ZX_PROCESS_TERMINATED)) break; ASSERT_TRUE(false, ""); } if (!verify_exception(&packet, process, ZX_EXCP_THREAD_EXITING)) return false; // ZX_EXCP_THREAD_EXITING reports must normally be responded to. // However, when the process exits it kills all threads which will // kick them out of the ExceptionHandlerExchange. Thus there's no // need to resume them here. } // This isn't necessary, but it tests being able to wait on the process // handle directly, after having waited on it via |eport|. tu_process_wait_signaled(process); return true; } // Wait for a process to exit, and while it's exiting verify we get the // expected exception reports. // The caller must have attached an async-wait for |process| to |eport|. // See start_test_child_with_eport(). // N.B. This is only for use when attached to the debugger exception port: // only it gets thread-exit reports. // A thread-exit report for |tid| is expected to be seen. // We may get other thread-exit reports, that's ok, we don't assume the child // is single-threaded. But it is an error to get any other kind of exception // report from a thread. // The bool result is because we use the unittest EXPECT/ASSERT macros. static bool wait_process_exit_from_debugger(zx_handle_t eport, zx_handle_t process, zx_koid_t tid) { bool tid_seen = false; zx_port_packet_t packet; zx_koid_t pid = tu_get_koid(process); ASSERT_NE(tid, ZX_KOID_INVALID, "invalid koid"); for (;;) { unittest_printf("%s: calling read_packet\n", __func__); if (!read_packet(eport, &packet)) return false; unittest_printf("%s: read_packet done\n", __func__); // If we get a process exit signal then all threads have exited. // Any other signal packet is an error. if (ZX_PKT_IS_SIGNAL_ONE(packet.type) || ZX_PKT_IS_SIGNAL_REP(packet.type)) { if (packet.key == pid && (packet.signal.observed & ZX_PROCESS_TERMINATED)) break; ASSERT_TRUE(false, ""); } if (!verify_exception(&packet, process, ZX_EXCP_THREAD_EXITING)) return false; if (packet.exception.tid == tid) tid_seen = true; // ZX_EXCP_THREAD_EXITING reports must normally be responded to. // However, when the process exits it kills all threads which will // kick them out of the ExceptionHandlerExchange. So send this thread // on its way, but it's ok if the thread is gone. zx_handle_t thread; zx_status_t status = zx_object_get_child(process, tid, ZX_RIGHT_SAME_RIGHTS, &thread); if (status == ZX_OK) { status = zx_task_resume(thread, ZX_RESUME_EXCEPTION); if (status < 0) { // If the resume failed the thread must be dying or dead. EXPECT_EQ(status, ZX_ERR_BAD_STATE, ""); EXPECT_TRUE(tu_thread_is_dying_or_dead(thread), ""); } zx_handle_close(thread); } } EXPECT_TRUE(tid_seen, "missing ZX_EXCP_THREAD_EXITING report"); // This isn't necessary, but it tests being able to wait on the process // handle directly, after having waited on it via |eport|. tu_process_wait_signaled(process); return true; } static bool ensure_child_running(zx_handle_t channel) { // Note: This function is called from external threads and thus does // not use EXPECT_*/ASSERT_*. enum message msg; send_msg(channel, MSG_PING); if (!recv_msg(channel, &msg)) { unittest_printf("ensure_child_running: Error while receiving msg\n"); return false; } if (msg != MSG_PONG) { unittest_printf("ensure_child_running: expecting PONG, got %d instead\n", msg); 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) { enum message msg; if (!recv_msg(channel, &msg)) { unittest_printf("Error while receiving msg\n"); 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) { unittest_printf("previous thread connection not shutdown"); 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 port 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: unittest_printf("unknown message received: %d\n", msg); break; } } } static int thread_func(void* arg) { unittest_printf("test thread starting\n"); zx_handle_t msg_channel = (zx_handle_t) (uintptr_t) arg; msg_loop(msg_channel); unittest_printf("test thread exiting\n"); tu_handle_close(msg_channel); return 0; } static void __NO_RETURN test_child(void) { unittest_printf("Test child starting.\n"); 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); unittest_printf("Test child exiting.\n"); exit(0); } static launchpad_t* setup_test_child(zx_handle_t job, const char* arg, zx_handle_t* out_channel) { unittest_printf("Starting test child %s.\n", arg); zx_handle_t our_channel, their_channel; tu_channel_create(&our_channel, &their_channel); const char* test_child_path = program_path; const char verbosity_string[] = { 'v', '=', utest_verbosity_level + '0', '\0' }; const char* const argv[] = { test_child_path, arg, verbosity_string, }; int argc = countof(argv); zx_handle_t handles[1] = { their_channel }; uint32_t handle_ids[1] = { PA_USER0 }; *out_channel = our_channel; launchpad_t* lp = tu_launch_fdio_init(job, test_child_name, argc, argv, NULL, 1, handles, handle_ids); unittest_printf("Test child setup.\n"); return lp; } static void start_test_child(zx_handle_t job, const char* arg, zx_handle_t* out_child, zx_handle_t* out_channel) { launchpad_t* lp = setup_test_child(job, arg, out_channel); *out_child = tu_launch_fdio_fini(lp); unittest_printf("Test child started.\n"); } static void start_test_child_with_eport(zx_handle_t job, const char* arg, zx_handle_t* out_child, zx_handle_t* out_eport, zx_handle_t* out_channel) { launchpad_t* lp = setup_test_child(zx_job_default(), arg, out_channel); zx_handle_t eport = tu_io_port_create(); // Note: child is a borrowed handle, launchpad still owns it at this point. zx_handle_t child = launchpad_get_process_handle(lp); tu_set_exception_port(child, eport, EXCEPTION_PORT_KEY, ZX_EXCEPTION_PORT_DEBUGGER); child = tu_launch_fdio_fini(lp); // Now we own the child handle, and lp is destroyed. // Note: This is a different handle, the previous child handle is gone at // this point (transfered to the child process). unittest_printf("child 0x%x, eport 0x%x\n", child, eport); tu_object_wait_async(child, eport, ZX_PROCESS_TERMINATED); *out_child = child; *out_eport = eport; } // Tests binding and unbinding behavior. // |object| must be a valid job, process, or thread handle. // |debugger| must only be set if |object| is a process handle. If set, // tests the behavior of binding the debugger eport; otherwise, binds // the non-debugger exception port. // This returns "bool" because it uses ASSERT_*. static bool test_set_close_set(zx_handle_t object, bool debugger) { ASSERT_NE(object, ZX_HANDLE_INVALID, "invalid handle"); uint32_t options = debugger ? ZX_EXCEPTION_PORT_DEBUGGER : 0; // Bind an exception port to the object. zx_handle_t eport = tu_io_port_create(); zx_status_t status; status = zx_task_bind_exception_port(object, eport, 0, options); ASSERT_EQ(status, ZX_OK, "error setting exception port"); // Try binding another exception port to the same object, which should fail. zx_handle_t eport2 = tu_io_port_create(); status = zx_task_bind_exception_port(object, eport, 0, options); ASSERT_EQ(status, ZX_ERR_ALREADY_BOUND, "wrong result from setting already bound exception port"); // Close the ports. tu_handle_close(eport2); tu_handle_close(eport); // Verify the close removed the previous handler by successfully // adding a new one. eport = tu_io_port_create(); status = zx_task_bind_exception_port(object, eport, 0, options); ASSERT_EQ(status, ZX_OK, "error setting exception port (#2)"); tu_handle_close(eport); // Try unbinding from an object without a bound port, which should fail. status = zx_task_bind_exception_port(object, ZX_HANDLE_INVALID, 0, options); ASSERT_NE(status, ZX_OK, "resetting unbound exception port errantly succeeded"); return true; } static bool job_set_close_set_test(void) { BEGIN_TEST; zx_handle_t job = tu_job_create(zx_job_default()); test_set_close_set(job, /* debugger */ false); tu_handle_close(job); END_TEST; } static bool process_set_close_set_test(void) { BEGIN_TEST; test_set_close_set(zx_process_self(), /* debugger */ false); END_TEST; } static bool process_debugger_set_close_set_test(void) { BEGIN_TEST; test_set_close_set(zx_process_self(), /* debugger */ true); END_TEST; } static bool thread_set_close_set_test(void) { BEGIN_TEST; zx_handle_t our_channel, their_channel; tu_channel_create(&our_channel, &their_channel); thrd_t thread; tu_thread_create_c11(&thread, thread_func, (void*)(uintptr_t)their_channel, "thread-set-close-set"); zx_handle_t thread_handle = thrd_get_zx_handle(thread); test_set_close_set(thread_handle, /* debugger */ false); send_msg(our_channel, MSG_DONE); // thrd_join doesn't provide a timeout, but we have the watchdog for that. thrd_join(thread, NULL); END_TEST; } typedef struct { zx_handle_t proc; zx_handle_t vmar; } proc_handles; // Creates but does not start a process, returning its handles in |*ph|. // Returns false if an assertion fails. static bool create_non_running_process(const char* name, proc_handles* ph) { memset(ph, 0, sizeof(*ph)); zx_status_t status = zx_process_create( zx_job_default(), name, strlen(name), 0, &ph->proc, &ph->vmar); ASSERT_EQ(status, ZX_OK, "zx_process_create"); ASSERT_NE(ph->proc, ZX_HANDLE_INVALID, "proc handle"); return true; } // Closes any valid handles in |ph|. static void close_proc_handles(proc_handles *ph) { if (ph->proc > 0) { tu_handle_close(ph->proc); ph->proc = ZX_HANDLE_INVALID; } if (ph->vmar > 0) { tu_handle_close(ph->vmar); ph->vmar = ZX_HANDLE_INVALID; } } static bool non_running_process_set_close_set_test(void) { BEGIN_TEST; // Create but do not start a process. proc_handles ph; ASSERT_TRUE(create_non_running_process(__func__, &ph), ""); // Make sure binding and unbinding behaves. test_set_close_set(ph.proc, /* debugger */ false); close_proc_handles(&ph); END_TEST; } static bool non_running_process_debugger_set_close_set_test(void) { BEGIN_TEST; // Create but do not start a process. proc_handles ph; ASSERT_TRUE(create_non_running_process(__func__, &ph), ""); // Make sure binding and unbinding behaves. test_set_close_set(ph.proc, /* debugger */ true); close_proc_handles(&ph); END_TEST; } static bool non_running_thread_set_close_set_test(void) { BEGIN_TEST; // Create but do not start a process. proc_handles ph; ASSERT_TRUE(create_non_running_process(__func__, &ph), ""); // Create but do not start a thread in that process. zx_handle_t thread = ZX_HANDLE_INVALID; zx_status_t status = zx_thread_create(ph.proc, __func__, sizeof(__func__)-1, 0, &thread); ASSERT_EQ(status, ZX_OK, "zx_thread_create"); ASSERT_NE(thread, ZX_HANDLE_INVALID, "thread handle"); // Make sure binding and unbinding behaves. test_set_close_set(thread, /* debugger */ false); tu_handle_close(thread); close_proc_handles(&ph); END_TEST; } // Creates a process, possibly binds an eport to it (if |bind_while_alive| is set), // then tries to unbind the eport, checking for the expected status. static bool dead_process_unbind_helper(bool debugger, bool bind_while_alive) { const uint32_t options = debugger ? ZX_EXCEPTION_PORT_DEBUGGER : 0; // Start a new process. zx_handle_t child, our_channel; start_test_child(zx_job_default(), test_child_name, &child, &our_channel); // Possibly bind an eport to it. zx_handle_t eport = ZX_HANDLE_INVALID; if (bind_while_alive) { // If we're binding to the debugger exception port make sure the // child is running first so that we don't have to process // ZX_EXCP_THREAD_STARTING. if (debugger) { ASSERT_TRUE(ensure_child_running(our_channel), ""); } eport = tu_io_port_create(); tu_set_exception_port(child, eport, EXCEPTION_PORT_KEY, options); tu_object_wait_async(child, eport, ZX_PROCESS_TERMINATED); } // Tell the process to exit and wait for it. send_msg(our_channel, MSG_DONE); if (debugger && bind_while_alive) { // If we bound a debugger port, the process won't die until we // consume the exception reports. ASSERT_TRUE(wait_process_exit(eport, child), ""); } else { ASSERT_EQ(tu_process_wait_exit(child), 0, "non-zero exit code"); } // Try unbinding. zx_status_t status = zx_task_bind_exception_port(child, ZX_HANDLE_INVALID, 0, options); if (bind_while_alive) { EXPECT_EQ(status, ZX_OK, "matched unbind should have succeeded"); } else { EXPECT_NE(status, ZX_OK, "unmatched unbind should have failed"); } // Clean up. tu_handle_close(child); if (eport != ZX_HANDLE_INVALID) { tu_handle_close(eport); } tu_handle_close(our_channel); return true; } static bool dead_process_matched_unbind_succeeds_test(void) { BEGIN_TEST; // If an eport is bound while a process is alive, it should be // valid to unbind it after the process is dead. ASSERT_TRUE(dead_process_unbind_helper( /* debugger */ false, /* bind_while_alive */ true), ""); END_TEST; } static bool dead_process_mismatched_unbind_fails_test(void) { BEGIN_TEST; // If an eport was not bound while a process was alive, it should be // invalid to unbind it after the process is dead. ASSERT_TRUE(dead_process_unbind_helper( /* debugger */ false, /* bind_while_alive */ false), ""); END_TEST; } static bool dead_process_debugger_matched_unbind_succeeds_test(void) { BEGIN_TEST; // If a debugger port is bound while a process is alive, it should be // valid to unbind it after the process is dead. ASSERT_TRUE(dead_process_unbind_helper( /* debugger */ true, /* bind_while_alive */ true), ""); END_TEST; } static bool dead_process_debugger_mismatched_unbind_fails_test(void) { BEGIN_TEST; // If an eport was not bound while a process was alive, it should be // invalid to unbind it after the process is dead. ASSERT_TRUE(dead_process_unbind_helper( /* debugger */ true, /* bind_while_alive */ false), ""); END_TEST; } // Creates a thread, possibly binds an eport to it (if |bind_while_alive| is set), // then tries to unbind the eport, checking for the expected status. static bool dead_thread_unbind_helper(bool bind_while_alive) { // Start a new thread. zx_handle_t our_channel, their_channel; tu_channel_create(&our_channel, &their_channel); thrd_t cthread; tu_thread_create_c11(&cthread, thread_func, (void*)(uintptr_t)their_channel, "thread-set-close-set"); zx_handle_t thread = thrd_get_zx_handle(cthread); ASSERT_NE(thread, ZX_HANDLE_INVALID, "failed to get thread handle"); // Duplicate the thread's handle. thrd_join() will close the |thread| // handle, but we need to be able to refer to the thread after that. zx_handle_t thread_copy = tu_handle_duplicate(thread); // Possibly bind an eport to it. zx_handle_t eport = ZX_HANDLE_INVALID; if (bind_while_alive) { eport = tu_io_port_create(); tu_set_exception_port(thread, eport, EXCEPTION_PORT_KEY, 0); } // Tell the thread to exit and wait for it. send_msg(our_channel, MSG_DONE); // thrd_join doesn't provide a timeout, but we have the watchdog for that. thrd_join(cthread, NULL); // Try unbinding. zx_status_t status = zx_task_bind_exception_port(thread_copy, ZX_HANDLE_INVALID, 0, 0); if (bind_while_alive) { EXPECT_EQ(status, ZX_OK, "matched unbind should have succeeded"); } else { EXPECT_NE(status, ZX_OK, "unmatched unbind should have failed"); } // Clean up. The |thread| and |their_channel| handles died // along with the thread. tu_handle_close(thread_copy); if (eport != ZX_HANDLE_INVALID) { tu_handle_close(eport); } tu_handle_close(our_channel); return true; } static bool dead_thread_matched_unbind_succeeds_test(void) { BEGIN_TEST; // If an eport is bound while a thread is alive, it should be // valid to unbind it after the thread is dead. ASSERT_TRUE(dead_thread_unbind_helper(/* bind_while_alive */ true), ""); END_TEST; } static bool dead_thread_mismatched_unbind_fails_test(void) { BEGIN_TEST; // If an eport was not bound while a thread was alive, it should be // invalid to unbind it after the thread is dead. ASSERT_TRUE(dead_thread_unbind_helper(/* bind_while_alive */ false), ""); END_TEST; } static void finish_basic_test(zx_handle_t child, zx_handle_t eport, zx_handle_t our_channel, enum message crash_msg, uint32_t excp_port_type) { send_msg(our_channel, crash_msg); zx_koid_t tid; if (read_and_verify_exception(eport, child, ZX_EXCP_FATAL_PAGE_FAULT, &tid)) { resume_thread_from_exception(child, tid, excp_port_type, ZX_RESUME_TRY_NEXT); tu_process_wait_signaled(child); } tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); } static bool job_handler_test(void) { BEGIN_TEST; zx_handle_t job = tu_job_create(zx_job_default()); zx_handle_t child, our_channel; start_test_child(job, test_child_name, &child, &our_channel); zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(job, eport, EXCEPTION_PORT_KEY, 0); REGISTER_CRASH(child); finish_basic_test(child, eport, our_channel, MSG_CRASH, ZX_EXCEPTION_PORT_TYPE_JOB); tu_handle_close(job); END_TEST; } bool job_debug_handler_test_helper(zx_handle_t job, zx_handle_t eport_job_handle) { zx_handle_t child, our_channel; zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(eport_job_handle, eport, EXCEPTION_PORT_KEY, ZX_EXCEPTION_PORT_DEBUGGER); start_test_child(job, test_child_name, &child, &our_channel); zx_info_handle_basic_t child_info; tu_handle_get_basic_info(child, &child_info); zx_port_packet_t start_packet; ASSERT_TRUE(read_packet(eport, &start_packet), "error reading start exception"); ASSERT_TRUE(verify_exception(&start_packet, child, ZX_EXCP_PROCESS_STARTING), "unexpected exception"); zx_koid_t packet_pid = start_packet.exception.pid; zx_koid_t packet_tid = start_packet.exception.tid; EXPECT_EQ(child_info.koid, packet_pid, "packet pid mismatch"); // set exception on process zx_handle_t eport_process = tu_io_port_create(); tu_set_exception_port(child, eport_process, EXCEPTION_PORT_KEY, ZX_EXCEPTION_PORT_DEBUGGER); tu_object_wait_async(child, eport_process, ZX_PROCESS_TERMINATED); // resume thread from job debugger resume_thread_from_exception(child, packet_tid, ZX_EXCEPTION_PORT_TYPE_JOB_DEBUGGER, 0); zx_port_packet_t start_packet_process; ASSERT_TRUE(read_packet(eport_process, &start_packet_process), "error reading start exception"); ASSERT_TRUE(verify_exception(&start_packet_process, child, ZX_EXCP_THREAD_STARTING), "unexpected exception"); packet_pid = start_packet.exception.pid; packet_tid = start_packet.exception.tid; EXPECT_EQ(child_info.koid, packet_pid, "packet pid mismatch"); send_msg(our_channel, MSG_DONE); resume_thread_from_exception(child, packet_tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); wait_process_exit_from_debugger(eport_process, child, packet_tid); tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); return true; } static bool nested_job_debug_handler_test(void) { BEGIN_TEST; zx_handle_t job = tu_job_create(zx_job_default()); zx_handle_t nested_job = tu_job_create(job); job_debug_handler_test_helper(nested_job, job); tu_handle_close(nested_job); tu_handle_close(job); END_TEST; } static bool job_debug_handler_test(void) { BEGIN_TEST; zx_handle_t job = tu_job_create(zx_job_default()); job_debug_handler_test_helper(job, job); tu_handle_close(job); END_TEST; } static bool grandparent_job_handler_test(void) { BEGIN_TEST; zx_handle_t grandparent_job = tu_job_create(zx_job_default()); zx_handle_t parent_job = tu_job_create(grandparent_job); zx_handle_t job = tu_job_create(parent_job); zx_handle_t child, our_channel; start_test_child(job, test_child_name, &child, &our_channel); zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(grandparent_job, eport, EXCEPTION_PORT_KEY, 0); REGISTER_CRASH(child); finish_basic_test(child, eport, our_channel, MSG_CRASH, ZX_EXCEPTION_PORT_TYPE_JOB); tu_handle_close(job); tu_handle_close(parent_job); tu_handle_close(grandparent_job); END_TEST; } static bool process_handler_test(void) { BEGIN_TEST; unittest_printf("process exception handler basic test\n"); zx_handle_t child, our_channel; start_test_child(zx_job_default(), test_child_name, &child, &our_channel); zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(child, eport, EXCEPTION_PORT_KEY, 0); REGISTER_CRASH(child); finish_basic_test(child, eport, our_channel, MSG_CRASH, ZX_EXCEPTION_PORT_TYPE_PROCESS); END_TEST; } static bool thread_handler_test(void) { BEGIN_TEST; unittest_printf("thread exception handler basic test\n"); zx_handle_t child, our_channel; start_test_child(zx_job_default(), test_child_name, &child, &our_channel); zx_handle_t eport = tu_io_port_create(); send_msg(our_channel, MSG_CREATE_AUX_THREAD); zx_handle_t thread; recv_msg_new_thread_handle(our_channel, &thread); if (thread != ZX_HANDLE_INVALID) { tu_set_exception_port(thread, eport, EXCEPTION_PORT_KEY, 0); REGISTER_CRASH(child); finish_basic_test(child, eport, our_channel, MSG_CRASH_AUX_THREAD, ZX_EXCEPTION_PORT_TYPE_THREAD); tu_handle_close(thread); } else { zx_task_kill(child); ASSERT_NE(thread, ZX_HANDLE_INVALID, ""); } END_TEST; } static bool debugger_handler_test(void) { BEGIN_TEST; unittest_printf("debugger exception handler basic test\n"); zx_handle_t child, our_channel; start_test_child(zx_job_default(), test_child_name, &child, &our_channel); // We're binding to the debugger exception port so make sure the // child is running first so that we don't have to process // ZX_EXCP_THREAD_STARTING. ASSERT_TRUE(ensure_child_running(our_channel), ""); zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(child, eport, EXCEPTION_PORT_KEY, ZX_EXCEPTION_PORT_DEBUGGER); finish_basic_test(child, eport, our_channel, MSG_CRASH, ZX_EXCEPTION_PORT_TYPE_DEBUGGER); END_TEST; } static bool packet_pid_test(void) { BEGIN_TEST; zx_handle_t child, eport, our_channel; start_test_child_with_eport(zx_job_default(), test_child_name, &child, &eport, &our_channel); zx_info_handle_basic_t child_info; tu_handle_get_basic_info(child, &child_info); zx_port_packet_t start_packet; ASSERT_TRUE(read_packet(eport, &start_packet), "error reading start exception"); ASSERT_TRUE(verify_exception(&start_packet, child, ZX_EXCP_THREAD_STARTING), "unexpected exception"); zx_koid_t packet_pid = start_packet.exception.pid; zx_koid_t packet_tid = start_packet.exception.tid; EXPECT_EQ(child_info.koid, packet_pid, "packet pid mismatch"); send_msg(our_channel, MSG_DONE); resume_thread_from_exception(child, packet_tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); wait_process_exit_from_debugger(eport, child, packet_tid); tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } // Check that zx_thread_read_state() and zx_thread_write_state() both // return ZX_ERR_NOT_SUPPORTED. This is used for testing the cases where a // thread is paused in the ZX_EXCP_THREAD_STARTING or or // ZX_EXCP_THREAD_EXITING states. static bool check_read_or_write_regs_is_rejected(zx_handle_t process, zx_handle_t tid) { zx_handle_t thread; ASSERT_EQ(zx_object_get_child(process, tid, ZX_RIGHT_SAME_RIGHTS, &thread), ZX_OK, ""); zx_thread_state_general_regs_t regs; EXPECT_EQ(zx_thread_read_state(thread, ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)), ZX_ERR_NOT_SUPPORTED, ""); EXPECT_EQ(zx_thread_write_state(thread, ZX_THREAD_STATE_GENERAL_REGS, ®s, sizeof(regs)), ZX_ERR_NOT_SUPPORTED, ""); ASSERT_EQ(zx_handle_close(thread), ZX_OK, ""); return true; } // Test the behavior of zx_thread_read_state() and zx_thread_write_state() // when a thread is paused in the ZX_EXCP_THREAD_STARTING or // ZX_EXCP_THREAD_EXITING states. // // For ZX_EXCP_THREAD_EXITING, this tests the case where a thread is // exiting without the whole process also exiting. static bool thread_state_when_starting_or_exiting_test(void) { BEGIN_TEST; zx_handle_t child, eport, our_channel; start_test_child_with_eport(zx_job_default(), test_child_name, &child, &eport, &our_channel); // Wait for the ZX_EXCP_THREAD_STARTING message for the subprocess's // initial thread. zx_koid_t initial_tid; ASSERT_TRUE(read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &initial_tid), ""); EXPECT_TRUE(check_read_or_write_regs_is_rejected(child, initial_tid), ""); resume_thread_from_exception(child, initial_tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); // Tell the subprocess to create a second thread. send_msg(our_channel, MSG_CREATE_AUX_THREAD); // Wait for the ZX_EXCP_THREAD_STARTING message about that thread. zx_koid_t tid; ASSERT_TRUE(read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &tid), ""); EXPECT_NE(tid, initial_tid, ""); EXPECT_TRUE(check_read_or_write_regs_is_rejected(child, tid), ""); resume_thread_from_exception(child, tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); // Tell the second thread to exit. send_msg(our_channel, MSG_SHUTDOWN_AUX_THREAD); // Wait for the ZX_EXCP_THREAD_EXITING message about that thread. zx_koid_t tid2; ASSERT_TRUE(read_and_verify_exception(eport, child, ZX_EXCP_THREAD_EXITING, &tid2), ""); EXPECT_EQ(tid2, tid, ""); EXPECT_TRUE(check_read_or_write_regs_is_rejected(child, tid), ""); // Clean up: Resume the thread so that the process can exit. zx_handle_t thread; ASSERT_EQ(zx_object_get_child(child, tid, ZX_RIGHT_SAME_RIGHTS, &thread), ZX_OK, ""); ASSERT_EQ(zx_task_resume(thread, ZX_RESUME_EXCEPTION), ZX_OK, ""); tu_handle_close(thread); // Clean up: Tell the process to exit and wait for it to exit. send_msg(our_channel, MSG_DONE); tu_process_wait_signaled(child); tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } static bool process_start_test(void) { BEGIN_TEST; unittest_printf("process start test\n"); zx_handle_t child, eport, our_channel; start_test_child_with_eport(zx_job_default(), test_child_name, &child, &eport, &our_channel); zx_koid_t tid; if (read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &tid)) { send_msg(our_channel, MSG_DONE); resume_thread_from_exception(child, tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); wait_process_exit_from_debugger(eport, child, tid); } tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } // Verify ZX_PROCESS_TERMINATED comes through bound exception port // via async wait. static bool process_exit_notification_test(void) { BEGIN_TEST; unittest_printf("process exit notification test\n"); zx_handle_t child, our_channel; start_test_child(zx_job_default(), test_child_name, &child, &our_channel); zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(child, eport, EXCEPTION_PORT_KEY, 0); tu_object_wait_async(child, eport, ZX_PROCESS_TERMINATED); send_msg(our_channel, MSG_DONE); wait_process_exit(eport, child); tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } // Verify ZX_THREAD_TERMINATED comes through bound exception port // via async wait. static bool thread_exit_notification_test(void) { BEGIN_TEST; unittest_printf("thread exit notification test\n"); zx_handle_t our_channel, their_channel; tu_channel_create(&our_channel, &their_channel); zx_handle_t eport = tu_io_port_create(); thrd_t thread; tu_thread_create_c11(&thread, thread_func, (void*) (uintptr_t) their_channel, "thread-gone-test-thread"); zx_handle_t thread_handle = thrd_get_zx_handle(thread); // |thread_handle| isn't usable to us, the thread exits before we're done // with the handle. So make a copy. zx_handle_t thread_handle_copy = tu_handle_duplicate(thread_handle); // Attach to the thread exception report as we're testing for ZX_THREAD_TERMINATED // reports from the thread here. tu_set_exception_port(thread_handle_copy, eport, EXCEPTION_PORT_KEY, 0); tu_object_wait_async(thread_handle_copy, eport, ZX_THREAD_TERMINATED); send_msg(our_channel, MSG_DONE); zx_port_packet_t packet; ASSERT_TRUE(read_packet(eport, &packet), ""); zx_koid_t tid = tu_get_koid(thread_handle_copy); ASSERT_TRUE(verify_signal(&packet, tid, ZX_THREAD_TERMINATED), ""); // thrd_join doesn't provide a timeout, but we have the watchdog for that. thrd_join(thread, NULL); tu_handle_close(thread_handle_copy); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } static void __NO_RETURN trigger_unsupported(void) { unittest_printf("unsupported exception\n"); // 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(void) { #if defined(__x86_64__) #elif defined(__aarch64__) #endif trigger_unsupported(); } static void __NO_RETURN trigger_fatal_page_fault(void) { *(volatile int*) 0 = 42; trigger_unsupported(); } static void __NO_RETURN trigger_undefined_insn(void) { #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(void) { #if defined(__x86_64__) __asm__("int3"); #elif defined(__aarch64__) __asm__("brk 0"); #endif trigger_unsupported(); } static void __NO_RETURN trigger_hw_bkpt(void) { #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(void) { // 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(void) { // 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(void) { // 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) (void); } 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 < countof(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) { unittest_printf("Exception trigger test child (%s) starting.\n", excp_name); trigger_exception(excp_name); /* NOTREACHED */ } static bool trigger_test(void) { BEGIN_TEST; unittest_printf("exception trigger tests\n"); for (size_t i = 0; i < countof(exceptions); ++i) { zx_excp_type_t excp_type = exceptions[i].type; const char *excp_name = exceptions[i].name; zx_handle_t child, eport, our_channel; char* arg = tu_asprintf("trigger=%s", excp_name); start_test_child_with_eport(zx_job_default(), arg, &child, &eport, &our_channel); free(arg); if (exceptions[i].crashes) { REGISTER_CRASH(child); } zx_koid_t tid = ZX_KOID_INVALID; (void) read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &tid); resume_thread_from_exception(child, tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); zx_port_packet_t packet; if (read_packet(eport, &packet)) { // ZX_EXCP_THREAD_EXITING reports must normally be responded to. // However, when the process exits it kills all threads which will // kick them out of the ExceptionHandlerExchange. Thus there's no // need to resume them here. ASSERT_TRUE(ZX_PKT_IS_EXCEPTION(packet.type), ""); if (packet.type != ZX_EXCP_THREAD_EXITING) { tid = packet.exception.tid; verify_exception(&packet, child, excp_type); resume_thread_from_exception(child, tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, ZX_RESUME_TRY_NEXT); zx_koid_t tid2; if (read_and_verify_exception(eport, child, ZX_EXCP_THREAD_EXITING, &tid2)) { ASSERT_EQ(tid2, tid, "exiting tid mismatch"); } } else { EXPECT_EQ(packet.exception.tid, tid, ""); // Either the process exited cleanly because the exception // is unsupported, or it exited because exception processing // finished and the kernel killed the process. Either way // the process is dead thus there's no need to resume the // thread. } // We've already seen tid's thread-exit report, so just skip that // test here. wait_process_exit(eport, child); } tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); } END_TEST; } typedef struct { // The walkthrough stops at the grandparent job as we don't want // crashlogger to see the exception: causes excessive noise in test output. // It doesn't stop at the parent job as we want to exercise finding threads // of processes of child jobs. zx_handle_t grandparent_job; zx_handle_t parent_job; zx_handle_t job; // the test process zx_handle_t child; // the test thread and its koid zx_handle_t thread; zx_koid_t tid; zx_handle_t grandparent_job_eport; zx_handle_t parent_job_eport; zx_handle_t job_eport; zx_handle_t child_eport; zx_handle_t thread_eport; zx_handle_t debugger_eport; // the communication channel to the test process zx_handle_t our_channel; } walkthrough_state_t; static bool walkthrough_setup(walkthrough_state_t* state) { memset(state, 0, sizeof(*state)); state->grandparent_job = tu_job_create(zx_job_default()); state->parent_job = tu_job_create(state->grandparent_job); state->job = tu_job_create(state->parent_job); state->grandparent_job_eport = tu_io_port_create(); state->parent_job_eport = tu_io_port_create(); state->job_eport = tu_io_port_create(); state->child_eport = tu_io_port_create(); state->thread_eport = tu_io_port_create(); state->debugger_eport = tu_io_port_create(); start_test_child(state->job, test_child_name, &state->child, &state->our_channel); send_msg(state->our_channel, MSG_CREATE_AUX_THREAD); recv_msg_new_thread_handle(state->our_channel, &state->thread); ASSERT_NE(state->thread, ZX_HANDLE_INVALID, ""); state->tid = tu_get_koid(state->thread); tu_set_exception_port(state->grandparent_job, state->grandparent_job_eport, EXCEPTION_PORT_KEY, 0); tu_set_exception_port(state->parent_job, state->parent_job_eport, EXCEPTION_PORT_KEY, 0); tu_set_exception_port(state->job, state->job_eport, EXCEPTION_PORT_KEY, 0); tu_set_exception_port(state->child, state->child_eport, EXCEPTION_PORT_KEY, 0); tu_set_exception_port(state->thread, state->thread_eport, EXCEPTION_PORT_KEY, 0); tu_set_exception_port(state->child, state->debugger_eport, EXCEPTION_PORT_KEY, ZX_EXCEPTION_PORT_DEBUGGER); // Non-debugger exception ports don't get synthetic exceptions like // ZX_EXCP_THREAD_STARTING. We have to trigger an architectural exception. send_msg(state->our_channel, MSG_CRASH_AUX_THREAD); return true; } static void walkthrough_close(zx_handle_t* handle) { if (*handle != ZX_HANDLE_INVALID) { tu_handle_close(*handle); *handle = ZX_HANDLE_INVALID; } } static void walkthrough_teardown(walkthrough_state_t* state) { zx_task_kill(state->child); tu_process_wait_signaled(state->child); walkthrough_close(&state->thread); walkthrough_close(&state->child); walkthrough_close(&state->our_channel); walkthrough_close(&state->job); walkthrough_close(&state->parent_job); walkthrough_close(&state->grandparent_job); walkthrough_close(&state->debugger_eport); walkthrough_close(&state->thread_eport); walkthrough_close(&state->child_eport); walkthrough_close(&state->job_eport); walkthrough_close(&state->parent_job_eport); walkthrough_close(&state->grandparent_job_eport); } static void walkthrough_read_and_verify_exception(const walkthrough_state_t* state, zx_handle_t eport) { zx_koid_t exception_tid; if (read_and_verify_exception(eport, state->child, ZX_EXCP_FATAL_PAGE_FAULT, &exception_tid)) { EXPECT_EQ(exception_tid, state->tid, ""); } } // Set up every kind of handler (except the system, we can't touch it), and // verify unbinding an exception port walks through each handler in the search // list (except the system exception handler which we can't touch). static bool unbind_walkthrough_by_reset_test(void) { BEGIN_TEST; walkthrough_state_t state; if (!walkthrough_setup(&state)) goto Fail; walkthrough_read_and_verify_exception(&state, state.debugger_eport); tu_set_exception_port(state.child, ZX_HANDLE_INVALID, 0, ZX_EXCEPTION_PORT_DEBUGGER); walkthrough_read_and_verify_exception(&state, state.thread_eport); tu_set_exception_port(state.thread, ZX_HANDLE_INVALID, 0, 0); walkthrough_read_and_verify_exception(&state, state.child_eport); tu_set_exception_port(state.child, ZX_HANDLE_INVALID, 0, 0); walkthrough_read_and_verify_exception(&state, state.job_eport); tu_set_exception_port(state.job, ZX_HANDLE_INVALID, 0, 0); walkthrough_read_and_verify_exception(&state, state.parent_job_eport); tu_set_exception_port(state.parent_job, ZX_HANDLE_INVALID, 0, 0); walkthrough_read_and_verify_exception(&state, state.grandparent_job_eport); Fail: walkthrough_teardown(&state); END_TEST; } // Set up every kind of handler (except the system, we can't touch it), and // verify closing an exception port walks through each handler in the search // list (except the system exception handler which we can't touch). static bool unbind_walkthrough_by_close_test(void) { BEGIN_TEST; walkthrough_state_t state; if (!walkthrough_setup(&state)) goto Fail; walkthrough_read_and_verify_exception(&state, state.debugger_eport); walkthrough_close(&state.debugger_eport); walkthrough_read_and_verify_exception(&state, state.thread_eport); walkthrough_close(&state.thread_eport); walkthrough_read_and_verify_exception(&state, state.child_eport); walkthrough_close(&state.child_eport); walkthrough_read_and_verify_exception(&state, state.job_eport); walkthrough_close(&state.job_eport); walkthrough_read_and_verify_exception(&state, state.parent_job_eport); walkthrough_close(&state.parent_job_eport); walkthrough_read_and_verify_exception(&state, state.grandparent_job_eport); Fail: walkthrough_teardown(&state); END_TEST; } // This test is different than the walkthrough tests in that it tests // successful resumption of the child after the debugger port closes. static bool unbind_while_stopped_test(void) { BEGIN_TEST; unittest_printf("unbind_while_stopped tests\n"); zx_handle_t child, eport, our_channel; start_test_child_with_eport(zx_job_default(), test_child_name, &child, &eport, &our_channel); { zx_koid_t tid; (void) read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &tid); } // Now unbind the exception port and wait for the child to cleanly exit. // If this doesn't work the thread will stay blocked, we'll timeout, and // the watchdog will trigger. tu_set_exception_port(child, ZX_HANDLE_INVALID, 0, ZX_EXCEPTION_PORT_DEBUGGER); send_msg(our_channel, MSG_DONE); tu_process_wait_signaled(child); tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } static bool unbind_rebind_while_stopped_test(void) { BEGIN_TEST; unittest_printf("unbind_rebind_while_stopped tests\n"); zx_handle_t child, eport, our_channel; start_test_child_with_eport(zx_job_default(), test_child_name, &child, &eport, &our_channel); zx_port_packet_t start_packet; // Assert reading the start packet succeeds because otherwise the rest // of the test is moot. ASSERT_TRUE(read_packet(eport, &start_packet), "error reading start exception"); ASSERT_TRUE(verify_exception(&start_packet, child, ZX_EXCP_THREAD_STARTING), "unexpected exception"); zx_koid_t tid = start_packet.exception.tid; zx_handle_t thread; zx_status_t status = zx_object_get_child(child, tid, ZX_RIGHT_SAME_RIGHTS, &thread); if (status < 0) tu_fatal("zx_object_get_child", status); // The thread may still be running: There's a window between sending the // exception report and the thread going to sleep that is exposed to us. // We want to verify the thread is still waiting for an exception after we // unbind, so wait for the thread to go to sleep before we unbind. // Note that there's no worry of this hanging due to our watchdog. zx_info_thread_t info; do { zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); info = tu_thread_get_info(thread); } while (info.state != ZX_THREAD_STATE_BLOCKED_EXCEPTION); // Unbind the exception port quietly, meaning to leave the thread // waiting for an exception response. tu_set_exception_port(child, ZX_HANDLE_INVALID, 0, ZX_EXCEPTION_PORT_DEBUGGER | ZX_EXCEPTION_PORT_UNBIND_QUIETLY); // Rebind and fetch the exception report, it should match the one // we just got. tu_set_exception_port(child, eport, EXCEPTION_PORT_KEY, ZX_EXCEPTION_PORT_DEBUGGER); // Verify exception report matches current exception. zx_exception_report_t report; status = zx_object_get_info(thread, ZX_INFO_THREAD_EXCEPTION_REPORT, &report, sizeof(report), NULL, NULL); if (status < 0) tu_fatal("zx_object_get_info(ZX_INFO_THREAD_EXCEPTION_REPORT)", status); EXPECT_EQ(report.header.type, start_packet.type, "type mismatch"); // The "thread-start" report is a synthetic exception and doesn't contain // any arch info yet, so we can't test report.context.arch. // Done verifying we got the same exception, send the child on its way // and tell it we're done. resume_thread_from_exception(child, tid, ZX_EXCEPTION_PORT_TYPE_DEBUGGER, 0); send_msg(our_channel, MSG_DONE); wait_process_exit_from_debugger(eport, child, tid); // We should still be able to get info on the thread. info = tu_thread_get_info(thread); EXPECT_EQ(info.state, ZX_THREAD_STATE_DEAD, "unexpected thread state"); EXPECT_EQ(info.wait_exception_port_type, ZX_EXCEPTION_PORT_TYPE_NONE, "wrong exception port type at thread exit"); tu_handle_close(thread); tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } static bool kill_while_stopped_at_start_test(void) { BEGIN_TEST; unittest_printf("kill_while_stopped_at_start tests\n"); zx_handle_t child, eport, our_channel; start_test_child_with_eport(zx_job_default(), test_child_name, &child, &eport, &our_channel); zx_koid_t tid; if (read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &tid)) { // Now kill the thread and wait for the child to exit. // This assumes the inferior only has the one thread. // If this doesn't work the thread will stay blocked, we'll timeout, and // the watchdog will trigger. zx_handle_t thread; zx_status_t status = zx_object_get_child(child, tid, ZX_RIGHT_SAME_RIGHTS, &thread); if (status < 0) tu_fatal("zx_object_get_child", status); zx_task_kill(thread); tu_process_wait_signaled(child); // Keep the thread handle open until after we know the process has exited // to ensure the thread's handle lifetime doesn't affect process lifetime. tu_handle_close(thread); } tu_handle_close(child); tu_handle_close(eport); tu_handle_close(our_channel); END_TEST; } static void write_to_addr(void* addr) { *(int*) addr = 42; } static bool death_test(void) { BEGIN_TEST; int* addr = 0; ASSERT_DEATH(write_to_addr, addr, "registered death: write to address 0x0"); END_TEST; } static bool self_death_test(void) { BEGIN_TEST; REGISTER_CRASH(zx_thread_self()); crash_me(); END_TEST; } typedef struct thread_info { zx_handle_t our_channel, their_channel; zx_handle_t thread_handle; } thread_info_t; static bool multiple_threads_registered_death_test(void) { BEGIN_TEST; const unsigned int num_threads = 5; thread_info_t thread_info[num_threads]; // Create some threads and register them as expected to crash. // This tests the crash list can handle multiple registered // handles. for (unsigned int i = 0; i < num_threads; i++) { tu_channel_create(&thread_info[i].our_channel, &thread_info[i].their_channel); thrd_t thread; tu_thread_create_c11(&thread, thread_func, (void*)(uintptr_t)thread_info[i].their_channel, "registered-death-thread"); // Note: We're assuming the thread won't exit before we're done with // the result of thrd_get_zx_handle. thread_info[i].thread_handle = thrd_get_zx_handle(thread); REGISTER_CRASH(thread_info[i].thread_handle); } // Make each thread crash. As they are registered, they will be // silently handled by the crash handler and the test should complete // without error. for (unsigned int i = 0; i < num_threads; i++) { send_msg(thread_info[i].our_channel, MSG_CRASH); ASSERT_EQ(zx_object_wait_one(thread_info[i].thread_handle, ZX_THREAD_TERMINATED, zx_deadline_after(ZX_MSEC(500)), NULL), ZX_OK, "failed to wait for thread termination"); tu_handle_close(thread_info[i].thread_handle); tu_handle_close(thread_info[i].our_channel); tu_handle_close(thread_info[i].their_channel); } END_TEST; } static void __NO_RETURN test_child_exit_closing_excp_handle(void) { unittest_printf("Exit closing excp handle starting.\n"); // Test ZX-1544. Process termination closing the last handle of the eport // should not cause a panic. zx_handle_t eport = tu_io_port_create(); tu_set_exception_port(zx_process_self(), eport, EXCEPTION_PORT_KEY, 0); exit(0); /* NOTREACHED */ } static bool exit_closing_excp_handle_test(void) { BEGIN_TEST; unittest_printf("Starting test child.\n"); const char* test_child_path = program_path; const char verbosity_string[] = { 'v', '=', utest_verbosity_level + '0', '\0' }; const char* const argv[] = { test_child_path, exit_closing_excp_handle_child_name, verbosity_string, }; int argc = countof(argv); launchpad_t* lp = tu_launch_fdio_init(zx_job_default(), exit_closing_excp_handle_child_name, argc, argv, NULL, 0, NULL, NULL); zx_handle_t child = tu_launch_fdio_fini(lp); zx_signals_t signals = ZX_PROCESS_TERMINATED; zx_signals_t pending; zx_status_t result = tu_wait(1, &child, &signals, &pending); EXPECT_EQ(result, ZX_OK, ""); EXPECT_TRUE(pending & ZX_PROCESS_TERMINATED, ""); EXPECT_EQ(tu_process_get_return_code(child), 0, ""); END_TEST; } BEGIN_TEST_CASE(exceptions_tests) RUN_TEST(job_set_close_set_test); RUN_TEST(process_set_close_set_test); RUN_TEST(process_debugger_set_close_set_test); RUN_TEST(thread_set_close_set_test); RUN_TEST(non_running_process_set_close_set_test); RUN_TEST(non_running_process_debugger_set_close_set_test); RUN_TEST(non_running_thread_set_close_set_test); RUN_TEST(dead_process_matched_unbind_succeeds_test); RUN_TEST(dead_process_mismatched_unbind_fails_test); RUN_TEST(dead_process_debugger_matched_unbind_succeeds_test); RUN_TEST(dead_process_debugger_mismatched_unbind_fails_test); RUN_TEST(dead_thread_matched_unbind_succeeds_test); RUN_TEST(dead_thread_mismatched_unbind_fails_test); RUN_TEST_ENABLE_CRASH_HANDLER(job_handler_test); RUN_TEST_ENABLE_CRASH_HANDLER(grandparent_job_handler_test); RUN_TEST_ENABLE_CRASH_HANDLER(process_handler_test); RUN_TEST_ENABLE_CRASH_HANDLER(thread_handler_test); RUN_TEST(packet_pid_test); RUN_TEST(job_debug_handler_test); RUN_TEST(nested_job_debug_handler_test); RUN_TEST(thread_state_when_starting_or_exiting_test); RUN_TEST(process_start_test); RUN_TEST(process_exit_notification_test); RUN_TEST(thread_exit_notification_test); RUN_TEST_ENABLE_CRASH_HANDLER(trigger_test); RUN_TEST(unbind_walkthrough_by_reset_test); RUN_TEST(unbind_walkthrough_by_close_test); RUN_TEST(unbind_while_stopped_test); RUN_TEST(unbind_rebind_while_stopped_test); RUN_TEST(kill_while_stopped_at_start_test); RUN_TEST(death_test); RUN_TEST_ENABLE_CRASH_HANDLER(self_death_test); RUN_TEST_ENABLE_CRASH_HANDLER(multiple_threads_registered_death_test); RUN_TEST(exit_closing_excp_handle_test); END_TEST_CASE(exceptions_tests) static void scan_argv(int argc, char** argv) { for (int i = 1; i < argc; ++i) { if (strncmp(argv[i], "v=", 2) == 0) { int verbosity = atoi(argv[i] + 2); unittest_set_verbosity_level(verbosity); } } } 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; } int main(int argc, char **argv) { program_path = argv[0]; scan_argv(argc, argv); 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 */ } } bool success = unittest_run_all_tests(argc, argv); return success ? 0 : -1; }