blob: d769eb9dec0a75979573391c28ae0e45f252ed26 [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 "debugger.h"
#include <assert.h>
#include <inttypes.h>
#include <lib/backtrace-request/backtrace-request.h>
#include <lib/zx/exception.h>
#include <lib/zx/thread.h>
#include <link.h>
#include <stdlib.h>
#include <string.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 <zircon/threads.h>
#include <atomic>
#include <pretty/hexdump.h>
#include <test-utils/test-utils.h>
#include <unittest/unittest.h>
#include "crash-and-recover.h"
#include "inferior-control.h"
#include "inferior.h"
#include "utils.h"
namespace {
// This returns a bool as it's a unittest "helper" routine.
// N.B. This runs on the wait-inferior thread.
bool handle_expected_page_fault(zx_handle_t inferior, const zx_exception_info_t* info,
zx::exception exception, std::atomic<int>* segv_count) {
BEGIN_HELPER;
unittest_printf("wait-inf: got page fault exception\n");
zx::thread thread;
ASSERT_EQ(exception.get_thread(&thread), ZX_OK);
dump_inferior_regs(thread.get());
// Verify that the fault is at the PC we expected.
if (!test_segv_pc(thread.get()))
return false;
// Do some tests that require a suspended inferior.
test_memory_ops(inferior, thread.get());
fix_inferior_segv(thread.get());
// Useful for debugging, otherwise a bit too verbose.
// dump_inferior_regs(thread);
// Increment this before resuming the inferior in case the inferior
// sends RESP_RECOVERED_FROM_CRASH and the testcase processes the message
// before we can increment it.
atomic_fetch_add(segv_count, 1);
thread.reset();
uint32_t exception_state = ZX_EXCEPTION_STATE_HANDLED;
EXPECT_EQ(
exception.set_property(ZX_PROP_EXCEPTION_STATE, &exception_state, sizeof(exception_state)),
ZX_OK);
END_HELPER;
}
// N.B. This runs on the wait-inferior thread.
bool debugger_test_exception_handler(inferior_data_t* data, 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.
std::atomic<int>* segv_count = static_cast<std::atomic<int>*>(handler_arg);
zx_info_handle_basic_t basic_info;
zx_status_t status = zx_object_get_info(data->inferior, ZX_INFO_HANDLE_BASIC, &basic_info,
sizeof(basic_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
zx_koid_t pid = basic_info.koid;
ASSERT_TRUE(ZX_PKT_IS_SIGNAL_ONE(packet->type));
status = zx_object_get_info(data->exception_channel, ZX_INFO_HANDLE_BASIC, &basic_info,
sizeof(basic_info), nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
if (packet->key != basic_info.koid) {
ASSERT_TRUE(packet->key != pid);
// Must be a signal on one of the threads.
// Here we're only expecting TERMINATED.
ASSERT_TRUE(packet->signal.observed & ZX_THREAD_TERMINATED);
} else {
zx::exception exception;
zx_exception_info_t info;
uint32_t num_bytes = sizeof(info);
uint32_t num_handles = 1;
zx_status_t status =
zx_channel_read(data->exception_channel, 0, &info, exception.reset_and_get_address(),
num_bytes, num_handles, nullptr, nullptr);
ASSERT_EQ(status, ZX_OK);
switch (info.type) {
case ZX_EXCP_THREAD_STARTING:
unittest_printf("wait-inf: inferior started\n");
break;
case ZX_EXCP_THREAD_EXITING:
// N.B. We could get thread exiting messages from previous
// tests.
EXPECT_TRUE(handle_thread_exiting(data->inferior, &info, std::move(exception)));
break;
case ZX_EXCP_FATAL_PAGE_FAULT:
ASSERT_NONNULL(segv_count);
ASSERT_TRUE(
handle_expected_page_fault(data->inferior, &info, std::move(exception), segv_count));
break;
default: {
char msg[128];
snprintf(msg, sizeof(msg), "unexpected exception type: 0x%x", info.type);
ASSERT_TRUE(false, msg);
__UNREACHABLE;
}
}
}
END_HELPER;
}
bool DebuggerTest() {
BEGIN_TEST;
springboard_t* sb;
zx_handle_t inferior, channel;
if (!setup_inferior(kTestInferiorChildName, &sb, &inferior, &channel))
return false;
std::atomic<int> segv_count;
expect_debugger_attached_eq(inferior, false, "debugger should not appear attached");
zx_handle_t port = ZX_HANDLE_INVALID;
EXPECT_EQ(zx_port_create(0, &port), ZX_OK);
size_t max_threads = 10;
inferior_data_t* inferior_data = attach_inferior(inferior, port, max_threads);
thrd_t wait_inf_thread =
start_wait_inf_thread(inferior_data, debugger_test_exception_handler, &segv_count);
EXPECT_NE(port, ZX_HANDLE_INVALID);
expect_debugger_attached_eq(inferior, true, "debugger should appear attached");
if (!start_inferior(sb))
return false;
if (!verify_inferior_running(channel))
return false;
segv_count.store(0);
send_simple_request(channel, RQST_CRASH_AND_RECOVER_TEST);
EXPECT_TRUE(recv_simple_response(channel, RESP_RECOVERED_FROM_CRASH), "");
EXPECT_EQ(segv_count.load(), kNumSegvTries, "segv tests terminated prematurely");
expect_debugger_attached_eq(inferior, true, "debugger should still appear attached");
if (!shutdown_inferior(channel, inferior))
return false;
// When a process terminates it closes its exception channels.
expect_debugger_attached_eq(inferior, false, "debugger should no longer appear attached");
// Stop the waiter thread before closing the port that it's waiting on.
join_wait_inf_thread(wait_inf_thread);
detach_inferior(inferior_data, true);
zx_handle_close(port);
zx_handle_close(channel);
zx_handle_close(inferior);
END_TEST;
}
bool DebuggerThreadListTest() {
BEGIN_TEST;
springboard_t* sb;
zx_handle_t inferior, channel;
if (!setup_inferior(kTestInferiorChildName, &sb, &inferior, &channel))
return false;
zx_handle_t port = ZX_HANDLE_INVALID;
EXPECT_EQ(zx_port_create(0, &port), ZX_OK);
size_t max_threads = 10;
inferior_data_t* inferior_data = attach_inferior(inferior, port, max_threads);
thrd_t wait_inf_thread =
start_wait_inf_thread(inferior_data, debugger_test_exception_handler, NULL);
EXPECT_NE(port, ZX_HANDLE_INVALID);
if (!start_inferior(sb))
return false;
if (!verify_inferior_running(channel))
return false;
send_simple_request(channel, RQST_START_LOOPING_THREADS);
EXPECT_TRUE(recv_simple_response(channel, RESP_THREADS_STARTED), "");
uint32_t buf_size = 100 * sizeof(zx_koid_t);
size_t num_threads;
zx_koid_t* threads = static_cast<zx_koid_t*>(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+kNumExtraThreads threads in the result.
ASSERT_GE(num_threads, 1 + kNumExtraThreads, "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 = ZX_HANDLE_INVALID;
status = zx_object_get_child(inferior, koid, ZX_RIGHT_SAME_RIGHTS, &thread);
EXPECT_EQ(status, ZX_OK, "zx_object_get_child failed");
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, ZX_OBJ_TYPE_THREAD, "not a thread");
}
free(threads);
if (!shutdown_inferior(channel, inferior))
return false;
// Stop the waiter thread before closing the port that it's waiting on.
join_wait_inf_thread(wait_inf_thread);
detach_inferior(inferior_data, true);
zx_handle_close(port);
zx_handle_close(channel);
zx_handle_close(inferior);
END_TEST;
}
bool PropertyProcessDebugAddrTest() {
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 BUILD.gn.
const char* libc_so = "libc.so";
bool found_libc = false;
const char* unittest_so = "libunittest.so";
bool found_unittest = false;
const r_debug* debug = (r_debug*)debug_addr;
const link_map* lmap = debug->r_map;
EXPECT_EQ(debug->r_state, r_debug::RT_CONSISTENT);
while (lmap != NULL) {
if (strcmp(lmap->l_name, libc_so) == 0)
found_libc = true;
else if (strcmp(lmap->l_name, unittest_so) == 0)
found_unittest = true;
lmap = lmap->l_next;
}
EXPECT_TRUE(found_libc);
EXPECT_TRUE(found_unittest);
END_TEST;
}
int write_text_segment_helper() __ALIGNED(8);
int write_text_segment_helper() {
/* 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;
}
bool WriteTextSegmentTest() {
BEGIN_TEST;
zx_handle_t self = zx_process_self();
// Exercise fxbug.dev/30693
// 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;
}
} // namespace
BEGIN_TEST_CASE(debugger_tests)
RUN_TEST(DebuggerTest)
RUN_TEST(DebuggerThreadListTest)
RUN_TEST(PropertyProcessDebugAddrTest)
RUN_TEST(WriteTextSegmentTest)
END_TEST_CASE(debugger_tests)