| // 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 <assert.h> |
| #include <inttypes.h> |
| #include <stdatomic.h> |
| #include <stdbool.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/syscalls/port.h> |
| #include <zircon/threads.h> |
| #include <test-utils/test-utils.h> |
| #include <unittest/unittest.h> |
| |
| 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" |
| |
| // When sending user packets use this key so that read_packet() knows they're |
| // legit. |
| static const uint64_t USER_PACKET_KEY = 0xee75736572ee; // eeuseree |
| |
| 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, zx_handle_t eport, |
| 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_from_exception(thread, eport, flags); |
| if (status < 0) |
| tu_fatal("resume_thread_from_exception", status); |
| zx_handle_close(thread); |
| } |
| |
| // Wait for and receive a user packet, 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 if (ZX_PKT_IS_USER(packet->type)) { |
| ASSERT_EQ(packet->key, USER_PACKET_KEY, ""); |
| } 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, ""); |
| } else if (ZX_PKT_IS_USER(packet.type)) { |
| continue; |
| } |
| 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_from_exception(thread, eport, 0); |
| 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, eport, 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, eport, 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, eport_process, |
| 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, eport, 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, eport, 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, eport, 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_from_exception(thread, eport, 0), 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, eport, 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, eport, 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, eport, |
| 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 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; |
| } |
| |
| static bool full_queue_sending_exception_packet_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_koid_t initial_tid; |
| ASSERT_TRUE(read_and_verify_exception(eport, child, ZX_EXCP_THREAD_STARTING, &initial_tid), ""); |
| resume_thread_from_exception(child, initial_tid, |
| ZX_EXCEPTION_PORT_TYPE_DEBUGGER, eport, 0); |
| |
| // Tell the subprocess to create a second thread. |
| // We need to observe a thread without the process exiting (process exits |
| // can make thread exceptions vanish). |
| 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, ""); |
| resume_thread_from_exception(child, tid, |
| ZX_EXCEPTION_PORT_TYPE_DEBUGGER, eport, 0); |
| |
| // Fill the port with packets thus preventing us from receiving the |
| // segfault exception. |
| const zx_port_packet_t pkt = { |
| .key = USER_PACKET_KEY, |
| }; |
| zx_status_t status; |
| size_t packet_count = 0; |
| while ((status = zx_port_queue(eport, &pkt)) == ZX_OK) { |
| ++packet_count; |
| } |
| unittest_printf("Queued %zu packets\n", packet_count); |
| |
| // Grab a handle to the thread before we cause it to exit. |
| // The kernel will kill the process when it tries to send the exception, |
| // after which we won't be able to get a handle. |
| zx_handle_t thread = tu_process_get_thread(child, tid); |
| ASSERT_NE(thread, ZX_HANDLE_INVALID, ""); |
| |
| // Alert the user about the kernel message that will get printed. |
| unittest_printf_critical( |
| "\nNote: We're intentionally crashing a thread in a way that cannot be handled.\n" |
| "The kernel will detect this and may print a helpful message,\n" |
| "which can be ignored.\n"); |
| |
| // Tell the second thread to crash, causing an exception to try to be sent, |
| // which should fail. |
| send_msg(our_channel, MSG_CRASH_AUX_THREAD); |
| |
| // Wait for the second thread to be in the exception. Before we start |
| // reading the user packets we stuffed into the port we want to make sure |
| // the kernel tried to send the exception packet. |
| uint32_t state; |
| do { |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(1))); |
| state = tu_thread_get_state(thread); |
| } while (state != ZX_THREAD_STATE_BLOCKED_EXCEPTION && |
| state != ZX_THREAD_STATE_DEAD); |
| |
| zx_port_packet_t out_pkt; |
| for (size_t i = 0; i < packet_count; ++i) { |
| ASSERT_TRUE(read_packet(eport, &out_pkt), ""); |
| ASSERT_TRUE(ZX_PKT_IS_USER(out_pkt.type), ""); |
| } |
| |
| // The process should now be dead, or will be shortly. |
| // TODO(ZX-2853): Or should it? |
| ASSERT_EQ(zx_object_wait_one(child, ZX_PROCESS_TERMINATED, |
| ZX_TIME_INFINITE, NULL), ZX_OK, ""); |
| |
| tu_handle_close(child); |
| tu_handle_close(eport); |
| tu_handle_close(our_channel); |
| |
| 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(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); |
| RUN_TEST(full_queue_sending_exception_packet_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; |
| } |