blob: bf68d90cb67adb0da68ad1e7ed14f4d18febc2c5 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <inttypes.h>
#include <link.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <launchpad/launchpad.h>
#include <launchpad/vmo.h>
#include <zircon/crashlogger.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/object.h>
#include <zircon/syscalls/port.h>
#include <test-utils/test-utils.h>
#include <unittest/unittest.h>
#include "utils.h"
typedef bool (wait_inferior_exception_handler_t)(zx_handle_t inferior,
const zx_port_packet_t* packet,
void* handler_arg);
// Sleep interval in the watchdog thread. Make this short so we don't need to
// wait too long when tearing down in the success case. This is especially
// helpful when running "while /boot/test/debugger-test; do true; done".
#define WATCHDOG_DURATION_TICK ((int64_t)ZX_MSEC(30)) // 0.03 seconds
// Number of sleep intervals until the watchdog fires.
#define WATCHDOG_DURATION_TICKS 100 // 3 seconds
#define TEST_MEMORY_SIZE 8
#define TEST_DATA_ADJUST 0x10
// Do the segv recovery test a number of times to stress test the API.
#define NUM_SEGV_TRIES 4
#define NUM_EXTRA_THREADS 4
// Produce a backtrace of sufficient size to be interesting but not excessive.
#define TEST_SEGFAULT_DEPTH 4
static const char test_inferior_child_name[] = "inferior";
// The segfault child is not used by the test.
// It exists for debugging purposes.
static const char test_segfault_child_name[] = "segfault";
// Used for testing the s/w breakpoint insn.
static const char test_swbreak_child_name[] = "swbreak";
// Setting to true when done turns off the watchdog timer. This
// must be an atomic so that the compiler does not assume anything
// about when it can be touched. Otherwise, since the compiler
// knows that vDSO calls don't make direct callbacks, it assumes
// that nothing can happen inside the watchdog loop that would touch
// this variable. In fact, it will be touched in parallel by
// another thread.
static volatile atomic_bool done_tests;
static atomic_int extra_thread_count = ATOMIC_VAR_INIT(0);
static void test_memory_ops(zx_handle_t inferior, zx_handle_t thread)
{
uint64_t test_data_addr = 0;
uint8_t test_data[TEST_MEMORY_SIZE];
#ifdef __x86_64__
test_data_addr = get_uint64_register(thread, offsetof(zx_x86_64_general_regs_t, r9));
#endif
#ifdef __aarch64__
test_data_addr = get_uint64_register(thread, offsetof(zx_arm64_general_regs_t, r[9]));
#endif
size_t size = read_inferior_memory(inferior, test_data_addr, test_data, sizeof(test_data));
EXPECT_EQ(size, sizeof(test_data), "read_inferior_memory: short read");
for (unsigned i = 0; i < sizeof(test_data); ++i) {
EXPECT_EQ(test_data[i], i, "test_memory_ops");
}
for (unsigned i = 0; i < sizeof(test_data); ++i)
test_data[i] += TEST_DATA_ADJUST;
size = write_inferior_memory(inferior, test_data_addr, test_data, sizeof(test_data));
EXPECT_EQ(size, sizeof(test_data), "write_inferior_memory: short write");
// Note: Verification of the write is done in the inferior.
}
static void fix_inferior_segv(zx_handle_t thread)
{
unittest_printf("Fixing inferior segv\n");
#ifdef __x86_64__
// The segv was because r8 == 0, change it to a usable value.
// See test_prep_and_segv.
uint64_t rsp = get_uint64_register(thread, offsetof(zx_x86_64_general_regs_t, rsp));
set_uint64_register(thread, offsetof(zx_x86_64_general_regs_t, r8), rsp);
#endif
#ifdef __aarch64__
// The segv was because r8 == 0, change it to a usable value.
// See test_prep_and_segv.
uint64_t sp = get_uint64_register(thread, offsetof(zx_arm64_general_regs_t, sp));
set_uint64_register(thread, offsetof(zx_arm64_general_regs_t, r[8]), sp);
#endif
}
static bool test_segv_pc(zx_handle_t thread)
{
#ifdef __x86_64__
uint64_t pc = get_uint64_register(
thread, offsetof(zx_x86_64_general_regs_t, rip));
uint64_t r10 = get_uint64_register(
thread, offsetof(zx_x86_64_general_regs_t, r10));
ASSERT_EQ(pc, r10, "fault PC does not match r10");
#endif
#ifdef __aarch64__
uint64_t pc = get_uint64_register(
thread, offsetof(zx_arm64_general_regs_t, pc));
uint64_t x10 = get_uint64_register(
thread, offsetof(zx_arm64_general_regs_t, r[10]));
ASSERT_EQ(pc, x10, "fault PC does not match x10");
#endif
return true;
}
// A simpler exception handler.
// All exceptions are passed on to |handler|.
// Returns false if a test fails.
// Otherwise waits for the inferior to exit and returns true.
static bool wait_inferior_thread_worker(zx_handle_t inferior,
zx_handle_t eport,
wait_inferior_exception_handler_t* handler,
void* handler_arg)
{
while (true) {
zx_port_packet_t packet;
if (!read_exception(eport, inferior, &packet))
return false;
// Is the inferior gone?
if (packet.type == ZX_EXCP_GONE && packet.exception.tid == 0) {
unittest_printf("wait-inf: inferior gone\n");
return true;
}
if (!handler(inferior, &packet, handler_arg))
return false;
}
}
typedef struct {
zx_handle_t inferior;
zx_handle_t eport;
wait_inferior_exception_handler_t* handler;
void* handler_arg;
} wait_inf_args_t;
static int wait_inferior_thread_func(void* arg)
{
wait_inf_args_t* args = arg;
zx_handle_t inferior = args->inferior;
zx_handle_t eport = args->eport;
wait_inferior_exception_handler_t* handler = args->handler;
void* handler_arg = args->handler_arg;
free(args);
bool pass = wait_inferior_thread_worker(inferior, eport, handler, handler_arg);
return pass ? 0 : -1;
}
static int watchdog_thread_func(void* arg)
{
for (int i = 0; i < WATCHDOG_DURATION_TICKS; ++i) {
zx_nanosleep(zx_deadline_after(WATCHDOG_DURATION_TICK));
if (atomic_load(&done_tests))
return 0;
}
unittest_printf_critical("\n\n*** WATCHDOG TIMER FIRED ***\n");
// This kills the entire process, not just this thread.
// TODO(dbort): Figure out why the shell sometimes reports a zero
// exit status when we expect to see '5'.
exit(5);
}
static thrd_t start_wait_inf_thread(zx_handle_t inferior,
zx_handle_t* out_eport,
wait_inferior_exception_handler_t* handler,
void* handler_arg)
{
zx_handle_t eport = attach_inferior(inferior);
wait_inf_args_t* args = calloc(1, sizeof(*args));
// Both handles are loaned to the thread. The caller of this function
// owns and must close them.
args->inferior = inferior;
args->eport = eport;
args->handler = handler;
args->handler_arg = handler_arg;
thrd_t wait_inferior_thread;
tu_thread_create_c11(&wait_inferior_thread, wait_inferior_thread_func,
(void*)args, "wait-inf thread");
*out_eport = eport;
return wait_inferior_thread;
}
static void join_wait_inf_thread(thrd_t wait_inf_thread)
{
unittest_printf("Waiting for wait-inf thread\n");
int thread_rc;
int ret = thrd_join(wait_inf_thread, &thread_rc);
EXPECT_EQ(ret, thrd_success, "thrd_join failed");
EXPECT_EQ(thread_rc, 0, "unexpected wait-inf return");
unittest_printf("wait-inf thread done\n");
}
static bool expect_debugger_attached_eq(
zx_handle_t inferior, bool expected, const char* msg)
{
zx_info_process_t info;
// ZX_ASSERT returns false if the check fails.
ASSERT_EQ(zx_object_get_info(
inferior, ZX_INFO_PROCESS, &info, sizeof(info), NULL, NULL), ZX_OK, "");
ASSERT_EQ(info.debugger_attached, expected, msg);
return true;
}
// This returns a bool as it's a unittest "helper" routine.
// N.B. This runs on the wait-inferior thread.
static bool handle_thread_exiting(zx_handle_t inferior,
const zx_port_packet_t* packet)
{
BEGIN_HELPER;
zx_koid_t tid = packet->exception.tid;
zx_handle_t thread;
zx_status_t status = zx_object_get_child(inferior, tid, ZX_RIGHT_SAME_RIGHTS, &thread);
// If the process has exited then the kernel may have reaped the
// thread already. Check.
if (status != ZX_ERR_NOT_FOUND) {
zx_info_thread_t info = tu_thread_get_info(thread);
// The thread could still transition to DEAD here (if the
// process exits), so check for either DYING or DEAD.
EXPECT_TRUE(info.state == ZX_THREAD_STATE_DYING ||
info.state == ZX_THREAD_STATE_DEAD, "");
// If the state is DYING it would be nice to check that the
// value of |info.wait_exception_port_type| is DEBUGGER. Alas
// if the process has exited then the thread will get
// THREAD_SIGNAL_KILL which will cause
// UserThread::ExceptionHandlerExchange to exit before we've
// told the thread to "resume" from ZX_EXCP_THREAD_EXITING.
// The thread is still in the DYING state but it is no longer
// in an exception. Thus |info.wait_exception_port_type| can
// either be DEBUGGER or NONE.
EXPECT_TRUE(info.wait_exception_port_type == ZX_EXCEPTION_PORT_TYPE_NONE ||
info.wait_exception_port_type == ZX_EXCEPTION_PORT_TYPE_DEBUGGER, "");
tu_handle_close(thread);
} else {
EXPECT_TRUE(tu_process_has_exited(inferior), "");
}
unittest_printf("wait-inf: thread %" PRId64 " exited\n", tid);
// A thread is gone, but we only care about the process.
if (!resume_inferior(inferior, tid))
return false;
END_HELPER;
}
// This returns a bool as it's a unittest "helper" routine.
// N.B. This runs on the wait-inferior thread.
static bool handle_expected_page_fault(zx_handle_t inferior,
const zx_port_packet_t* packet,
atomic_int* segv_count)
{
BEGIN_HELPER;
unittest_printf("wait-inf: got page fault exception\n");
zx_koid_t tid = packet->exception.tid;
zx_handle_t thread = tu_get_thread(inferior, tid);
dump_inferior_regs(thread);
// Verify that the fault is at the PC we expected.
if (!test_segv_pc(thread))
return false;
// Do some tests that require a suspended inferior.
test_memory_ops(inferior, thread);
fix_inferior_segv(thread);
// Useful for debugging, otherwise a bit too verbose.
//dump_inferior_regs(thread);
// Increment this before resuming the inferior in case the inferior
// sends MSG_RECOVERED_FROM_CRASH and the testcase processes the message
// before we can increment it.
atomic_fetch_add(segv_count, 1);
zx_status_t status = zx_task_resume(thread, ZX_RESUME_EXCEPTION);
tu_handle_close(thread);
ASSERT_EQ(status, ZX_OK, "");
END_HELPER;
}
// N.B. This runs on the wait-inferior thread.
static bool debugger_test_exception_handler(zx_handle_t inferior,
const zx_port_packet_t* packet,
void* handler_arg)
{
BEGIN_HELPER;
// Note: This may be NULL if the test is not expecting a page fault.
atomic_int* segv_count = handler_arg;
zx_koid_t tid = packet->exception.tid;
switch (packet->type) {
case ZX_EXCP_THREAD_STARTING:
unittest_printf("wait-inf: inferior started\n");
if (!resume_inferior(inferior, tid))
return false;
break;
case ZX_EXCP_THREAD_EXITING:
EXPECT_TRUE(handle_thread_exiting(inferior, packet), "");
break;
case ZX_EXCP_GONE:
// A thread is gone, but we only care about the process which is
// handled by the caller.
break;
case ZX_EXCP_FATAL_PAGE_FAULT:
ASSERT_NONNULL(segv_count, "");
ASSERT_TRUE(handle_expected_page_fault(inferior, packet, segv_count), "");
break;
default: {
char msg[128];
snprintf(msg, sizeof(msg), "unexpected packet type: 0x%x",
packet->type);
ASSERT_TRUE(false, msg);
__UNREACHABLE;
}
}
END_HELPER;
}
static bool debugger_test(void)
{
BEGIN_TEST;
launchpad_t* lp;
zx_handle_t inferior, channel;
if (!setup_inferior(test_inferior_child_name, &lp, &inferior, &channel))
return false;
atomic_int segv_count;
expect_debugger_attached_eq(inferior, false, "debugger should not appear attached");
zx_handle_t eport = ZX_HANDLE_INVALID;
thrd_t wait_inf_thread =
start_wait_inf_thread(inferior, &eport,
debugger_test_exception_handler, &segv_count);
EXPECT_NE(eport, ZX_HANDLE_INVALID, "");
expect_debugger_attached_eq(inferior, true, "debugger should appear attached");
if (!start_inferior(lp))
return false;
if (!verify_inferior_running(channel))
return false;
atomic_store(&segv_count, 0);
enum message msg;
send_msg(channel, MSG_CRASH_AND_RECOVER_TEST);
if (!recv_msg(channel, &msg))
return false;
EXPECT_EQ(msg, MSG_RECOVERED_FROM_CRASH, "unexpected response from crash");
EXPECT_EQ(atomic_load(&segv_count), NUM_SEGV_TRIES, "segv tests terminated prematurely");
if (!shutdown_inferior(channel, inferior))
return false;
// Stop the waiter thread before closing the eport that it's waiting on.
join_wait_inf_thread(wait_inf_thread);
expect_debugger_attached_eq(inferior, true, "debugger should still appear attached");
tu_handle_close(eport);
expect_debugger_attached_eq(inferior, false, "debugger should no longer appear attached");
tu_handle_close(channel);
tu_handle_close(inferior);
END_TEST;
}
static bool debugger_thread_list_test(void)
{
BEGIN_TEST;
launchpad_t* lp;
zx_handle_t inferior, channel;
if (!setup_inferior(test_inferior_child_name, &lp, &inferior, &channel))
return false;
zx_handle_t eport = ZX_HANDLE_INVALID;
thrd_t wait_inf_thread =
start_wait_inf_thread(inferior, &eport,
debugger_test_exception_handler, NULL);
EXPECT_NE(eport, ZX_HANDLE_INVALID, "");
if (!start_inferior(lp))
return false;
if (!verify_inferior_running(channel))
return false;
enum message msg;
send_msg(channel, MSG_START_EXTRA_THREADS);
if (!recv_msg(channel, &msg))
return false;
EXPECT_EQ(msg, MSG_EXTRA_THREADS_STARTED, "unexpected response when starting extra threads");
uint32_t buf_size = 100 * sizeof(zx_koid_t);
size_t num_threads;
zx_koid_t* threads = tu_malloc(buf_size);
zx_status_t status = zx_object_get_info(inferior, ZX_INFO_PROCESS_THREADS,
threads, buf_size, &num_threads, NULL);
ASSERT_EQ(status, ZX_OK, "");
// There should be at least 1+NUM_EXTRA_THREADS threads in the result.
ASSERT_GE(num_threads, (unsigned)(1 + NUM_EXTRA_THREADS), "zx_object_get_info failed");
// Verify each entry is valid.
for (uint32_t i = 0; i < num_threads; ++i) {
zx_koid_t koid = threads[i];
unittest_printf("Looking up thread %llu\n", (long long) koid);
zx_handle_t thread = tu_get_thread(inferior, koid);
zx_info_handle_basic_t info;
status = zx_object_get_info(thread, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), NULL, NULL);
EXPECT_EQ(status, ZX_OK, "zx_object_get_info failed");
EXPECT_EQ(info.type, (uint32_t) ZX_OBJ_TYPE_THREAD, "not a thread");
}
if (!shutdown_inferior(channel, inferior))
return false;
// Stop the waiter thread before closing the eport that it's waiting on.
join_wait_inf_thread(wait_inf_thread);
tu_handle_close(eport);
tu_handle_close(channel);
tu_handle_close(inferior);
END_TEST;
}
static bool property_process_debug_addr_test(void)
{
BEGIN_TEST;
zx_handle_t self = zx_process_self();
// We shouldn't be able to set it.
uintptr_t debug_addr = 42;
zx_status_t status = zx_object_set_property(self, ZX_PROP_PROCESS_DEBUG_ADDR,
&debug_addr, sizeof(debug_addr));
ASSERT_EQ(status, ZX_ERR_ACCESS_DENIED, "");
// Some minimal verification that the value is correct.
status = zx_object_get_property(self, ZX_PROP_PROCESS_DEBUG_ADDR,
&debug_addr, sizeof(debug_addr));
ASSERT_EQ(status, ZX_OK, "");
// These are all dsos we link with. See rules.mk.
const char* launchpad_so = "liblaunchpad.so";
bool found_launchpad = false;
const char* libc_so = "libc.so";
bool found_libc = false;
const char* test_utils_so = "libtest-utils.so";
bool found_test_utils = false;
const char* unittest_so = "libunittest.so";
bool found_unittest = false;
const struct r_debug* r_debug = (struct r_debug*) debug_addr;
const struct link_map* lmap = r_debug->r_map;
EXPECT_EQ((int) r_debug->r_state, RT_CONSISTENT, "");
while (lmap != NULL) {
if (strcmp(lmap->l_name, launchpad_so) == 0)
found_launchpad = true;
else if (strcmp(lmap->l_name, libc_so) == 0)
found_libc = true;
else if (strcmp(lmap->l_name, test_utils_so) == 0)
found_test_utils = true;
else if (strcmp(lmap->l_name, unittest_so) == 0)
found_unittest = true;
lmap = lmap->l_next;
}
EXPECT_TRUE(found_launchpad, "");
EXPECT_TRUE(found_libc, "");
EXPECT_TRUE(found_test_utils, "");
EXPECT_TRUE(found_unittest, "");
END_TEST;
}
static int write_text_segment_helper(void) __ALIGNED(8);
static int write_text_segment_helper(void)
{
/* This function needs to be at least two bytes in size as we set a
breakpoint, figuratively speaking, on write_text_segment_helper + 1
to ensure the address is not page aligned. Returning some random value
will ensure that. */
return 42;
}
static bool write_text_segment(void)
{
BEGIN_TEST;
zx_handle_t self = zx_process_self();
// Exercise MG-739
// Pretend we're writing a s/w breakpoint to the start of this function.
// write_text_segment_helper is suitably aligned, add 1 to ensure the
// byte we write is not page aligned.
uintptr_t addr = (uintptr_t) write_text_segment_helper + 1;
uint8_t previous_byte;
size_t size = read_inferior_memory(self, addr, &previous_byte, sizeof(previous_byte));
EXPECT_EQ(size, sizeof(previous_byte), "");
uint8_t byte_to_write = 0;
size = write_inferior_memory(self, addr, &byte_to_write, sizeof(byte_to_write));
EXPECT_EQ(size, sizeof(byte_to_write), "");
size = write_inferior_memory(self, addr, &previous_byte, sizeof(previous_byte));
EXPECT_EQ(size, sizeof(previous_byte), "");
END_TEST;
}
// This function is marked as no-inline to avoid duplicate label in case the
// function call is being inlined.
__NO_INLINE static bool test_prep_and_segv(void)
{
uint8_t test_data[TEST_MEMORY_SIZE];
for (unsigned i = 0; i < sizeof(test_data); ++i)
test_data[i] = i;
#ifdef __x86_64__
void* segv_pc;
// Note: Fuchsia is always PIC.
__asm__("leaq .Lsegv_here(%%rip),%0" : "=r" (segv_pc));
unittest_printf("About to segv, pc %p\n", segv_pc);
// Set r9 to point to test_data so we can easily access it
// from the parent process. Likewise set r10 to segv_pc
// so the parent process can verify it matches the fault PC.
__asm__("\
movq %[zero],%%r8\n\
movq %[test_data],%%r9\n\
movq %[pc],%%r10\n\
.Lsegv_here:\n\
movq (%%r8),%%rax\
" : :
[zero] "g" (0),
[test_data] "g" (&test_data[0]),
[pc] "g" (segv_pc) :
"rax", "r8", "r9", "r10");
#endif
#ifdef __aarch64__
void* segv_pc;
// Note: Fuchsia is always PIC.
__asm__("adrp %0, .Lsegv_here\n"
"add %0, %0, :lo12:.Lsegv_here" : "=r" (segv_pc));
unittest_printf("About to segv, pc %p\n", segv_pc);
// Set r9 to point to test_data so we can easily access it
// from the parent process. Likewise set r10 to segv_pc
// so the parent process can verify it matches the fault PC.
__asm__("\
mov x8,xzr\n\
mov x9,%[test_data]\n\
mov x10,%[pc]\n\
.Lsegv_here:\n\
ldr x0,[x8]\
" : :
[test_data] "r" (&test_data[0]),
[pc] "r" (segv_pc) :
"x0", "x8", "x9", "x10");
#endif
// On resumption test_data should have had TEST_DATA_ADJUST added to each element.
// Note: This is the inferior process, it's not running under the test harness.
for (unsigned i = 0; i < sizeof(test_data); ++i) {
if (test_data[i] != i + TEST_DATA_ADJUST) {
unittest_printf("test_prep_and_segv: bad data on resumption, test_data[%u] = 0x%x\n",
i, test_data[i]);
return false;
}
}
unittest_printf("Inferior successfully resumed!\n");
return true;
}
static int extra_thread_func(void* arg)
{
atomic_fetch_add(&extra_thread_count, 1);
unittest_printf("Extra thread started.\n");
while (true)
zx_nanosleep(zx_deadline_after(ZX_SEC(1)));
return 0;
}
// This returns a bool as it's a unittest "helper" routine.
static bool msg_loop(zx_handle_t channel)
{
BEGIN_HELPER; // Don't stomp on the main thread's current_test_info.
bool my_done_tests = false;
while (!atomic_load(&done_tests) && !my_done_tests)
{
enum message msg;
ASSERT_TRUE(recv_msg(channel, &msg), "Error while receiving msg");
switch (msg)
{
case MSG_DONE:
my_done_tests = true;
break;
case MSG_PING:
send_msg(channel, MSG_PONG);
break;
case MSG_CRASH_AND_RECOVER_TEST:
for (int i = 0; i < NUM_SEGV_TRIES; ++i) {
if (!test_prep_and_segv())
exit(21);
}
send_msg(channel, MSG_RECOVERED_FROM_CRASH);
break;
case MSG_START_EXTRA_THREADS:
for (int i = 0; i < NUM_EXTRA_THREADS; ++i) {
// For our purposes, we don't need to track the threads.
// They'll be terminated when the process exits.
thrd_t thread;
tu_thread_create_c11(&thread, extra_thread_func, NULL, "extra-thread");
}
// Wait for all threads to be started.
// Each will require an ZX_EXCP_STARTING exchange with the "debugger".
while (atomic_load(&extra_thread_count) < NUM_EXTRA_THREADS)
zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
send_msg(channel, MSG_EXTRA_THREADS_STARTED);
break;
default:
unittest_printf("unknown message received: %d\n", msg);
break;
}
}
END_HELPER;
}
void test_inferior(void)
{
zx_handle_t channel = zx_get_startup_handle(PA_USER0);
unittest_printf("test_inferior: got handle %d\n", channel);
if (!msg_loop(channel))
exit(20);
atomic_store(&done_tests, true);
unittest_printf("Inferior done\n");
exit(1234);
}
// Compilers are getting too smart.
// These maintain the semantics we want even under optimization.
volatile int* crashing_ptr = (int*) 42;
volatile int crash_depth;
// This is used to cause fp != sp when the crash happens on arm64.
int leaf_stack_size = 10;
static int __NO_INLINE test_segfault_doit2(int*);
static int __NO_INLINE test_segfault_leaf(int n, int* p)
{
volatile int x[n];
x[0] = *p;
*crashing_ptr = x[0];
return 0;
}
static int __NO_INLINE test_segfault_doit1(int* p)
{
if (crash_depth > 0)
{
int n = crash_depth;
int use_stack[n];
memset(use_stack, 0x99, n * sizeof(int));
--crash_depth;
return test_segfault_doit2(use_stack) + 99;
}
return test_segfault_leaf(leaf_stack_size, p) + 99;
}
static int __NO_INLINE test_segfault_doit2(int* p)
{
return test_segfault_doit1(p) + *p;
}
// Produce a crash with a moderately interesting backtrace.
static int __NO_INLINE test_segfault(void)
{
crash_depth = TEST_SEGFAULT_DEPTH;
int i = 0;
return test_segfault_doit1(&i);
}
// Invoke the s/w breakpoint insn using the crashlogger mechanism
// to request a backtrace but not terminate the process.
static int __NO_INLINE test_swbreak(void)
{
unittest_printf("Invoking s/w breakpoint instruction\n");
crashlogger_request_backtrace();
unittest_printf("Resumed after s/w breakpoint instruction\n");
return 0;
}
BEGIN_TEST_CASE(debugger_tests)
RUN_TEST(debugger_test)
RUN_TEST(debugger_thread_list_test)
RUN_TEST(property_process_debug_addr_test)
RUN_TEST(write_text_segment)
END_TEST_CASE(debugger_tests)
static void check_verbosity(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);
break;
}
}
}
int main(int argc, char **argv)
{
program_path = argv[0];
if (argc >= 2 && strcmp(argv[1], test_inferior_child_name) == 0) {
check_verbosity(argc, argv);
test_inferior();
return 0;
}
if (argc >= 2 && strcmp(argv[1], test_segfault_child_name) == 0) {
check_verbosity(argc, argv);
return test_segfault();
}
if (argc >= 2 && strcmp(argv[1], test_swbreak_child_name) == 0) {
check_verbosity(argc, argv);
return test_swbreak();
}
thrd_t watchdog_thread;
tu_thread_create_c11(&watchdog_thread, watchdog_thread_func, NULL, "watchdog-thread");
bool success = unittest_run_all_tests(argc, argv);
atomic_store(&done_tests, true);
thrd_join(watchdog_thread, NULL);
return success ? 0 : -1;
}