blob: 0fbb9c36092fd1ebbbd768cd446e1517af582faf [file] [log] [blame]
// Copyright 2019 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 "hw_breakpointer_helpers.h"
// This is a self contained binary that is meant to be run *manually*. This is the smallest code
// that can be used to reproduce a HW breakpoint exception.
// This is meant to be able to test the functionality of zircon without having to go throught the
// hassle of having the whole debugger context around.
namespace {
// Test Cases ======================================================================================
// BreakOnFunction ---------------------------------------------------------------------------------
//
// 1. Create a thread that will loop forever, continually calling a particular
// function.
// 2. Suspend that thread.
// 3. Install a HW breakpoint through zx_thread_write_state.
// 4. Resume the thread.
// 5. Wait for some time for the exception. If the exception never happened, it
// means that Zircon is not doing the right thing.
// This is the function that we will set up the breakpoint to.
using HWBreakpointTestCaseFunctionToBeCalled = int (*)(int);
int __NO_INLINE FunctionToBreakpointOn1(int c) { return c + c; }
int __NO_INLINE FunctionToBreakpointOn2(int c) { return c + c; }
int __NO_INLINE FunctionToBreakpointOn3(int c) { return c + c; }
int __NO_INLINE FunctionToBreakpointOn4(int c) { return c + c; }
int __NO_INLINE FunctionToBreakpointOn5(int c) { return c + c; }
// This is the code that the new thread will run.
// It's meant to be an eternal loop.
int BreakOnFunctionThreadFunction(void* user) {
auto* thread_setup = reinterpret_cast<ThreadSetup*>(user);
// We signal the test harness that we are here.
thread_setup->event.signal(kHarnessToThread, kThreadToHarness);
// We wait now for the harness to tell us we can continue.
CHECK_OK(thread_setup->event.wait_one(kHarnessToThread, zx::time::infinite(), nullptr));
PRINT("Got signaled by harness.");
int counter = 1;
while (thread_setup->test_running) {
auto* function_to_call =
reinterpret_cast<HWBreakpointTestCaseFunctionToBeCalled>(thread_setup->user);
FXL_DCHECK(function_to_call);
// We use write to avoid deadlocking with the outside libc calls.
write(1, kBeacon, sizeof(kBeacon));
counter = function_to_call(counter);
zx::nanosleep(zx::deadline_after(zx::sec(1)));
}
return 0;
}
void BreakOnFunctionTestCase() {
printf("Running HW breakpoint when calling a function test.\n");
// The functions to be called sequentially by the test.
// clang-format off
HWBreakpointTestCaseFunctionToBeCalled breakpoint_functions[] = {
FunctionToBreakpointOn1,
FunctionToBreakpointOn2,
FunctionToBreakpointOn3,
FunctionToBreakpointOn4,
FunctionToBreakpointOn5,
};
// clang-format on
auto thread_setup = CreateTestSetup(BreakOnFunctionThreadFunction);
auto [port, exception_channel] = CreateExceptionChannel(thread_setup->thread);
WaitAsyncOnExceptionChannel(port, exception_channel);
Exception exception = {};
for (size_t i = 0; i < std::size(breakpoint_functions); i++) {
// If this is the first iteration, we don't resume the exception.
if (i > 0u) {
WaitAsyncOnExceptionChannel(port, exception_channel);
ResumeException(thread_setup->thread, std::move(exception));
}
auto* breakpoint_function = breakpoint_functions[i];
// Pass in the function to call as extra data.
thread_setup->user = reinterpret_cast<void*>(breakpoint_function);
// Install the breakpoint.
uint64_t breakpoint_address = reinterpret_cast<uint64_t>(breakpoint_function);
InstallHWBreakpoint(thread_setup->thread, breakpoint_address);
// Tell the thread to continue.
thread_setup->event.signal(kThreadToHarness, kHarnessToThread);
// We wait until we receive an exception.
auto opt_excp = WaitForException(port, exception_channel);
FXL_DCHECK(opt_excp.has_value());
exception = std::move(*opt_excp);
FXL_DCHECK(exception.info.type == ZX_EXCP_HW_BREAKPOINT);
PRINT("Hit HW breakpoint %zu on 0x%zx", i, exception.pc);
// Remove the breakpoint.
RemoveHWBreakpoint(thread_setup->thread);
}
// Tell the thread to exit.
thread_setup->test_running = false;
ResumeException(thread_setup->thread, std::move(exception));
}
// Watchpoints -------------------------------------------------------------------------------------
//
// This test has an array of bytes that will be accessed one by one by another thread.
// The harness will set a watchpoint on each of those bytes and expects to receive an exception for
// of them.
uint8_t gDataToTouch[16] = {};
int WatchpointThreadFunction(void* user) {
auto* thread_setup = reinterpret_cast<ThreadSetup*>(user);
// We signal the test harness that we are here.
thread_setup->event.signal(kHarnessToThread, kThreadToHarness);
while (thread_setup->test_running) {
// We wait now for the harness to tell us we can continue.
CHECK_OK(thread_setup->event.wait_one(kHarnessToThread, zx::time::infinite(), nullptr));
uint8_t* byte = reinterpret_cast<uint8_t*>(thread_setup->user);
FXL_DCHECK(byte);
*byte += 1;
// We signal that we finished this write.
CHECK_OK(thread_setup->event.signal(kHarnessToThread, kThreadToHarness));
}
return 0;
}
constexpr int kExceptionWaitTimeout = 25;
// Returns whether the breakpoint was hit.
bool TestWatchpointRun(const zx::port& port, const zx::channel& exception_channel,
ThreadSetup* thread_setup, uint64_t wp_address, uint32_t length,
uint8_t* address_to_write) {
thread_setup->user = address_to_write;
// Install the watchpoint.
InstallWatchpoint(thread_setup->thread, wp_address, length);
// Tell the thread to continue.
CHECK_OK(thread_setup->event.signal(kThreadToHarness, kHarnessToThread));
// Wait until the exception is hit.
auto opt_excp = WaitForException(port, exception_channel,
zx::deadline_after(zx::msec(kExceptionWaitTimeout)));
// Remove the watchpoint.
RemoveWatchpoint(thread_setup->thread);
if (!opt_excp) {
PRINT_CLEAN("Writing into 0x%zx.", (uint64_t)address_to_write);
return false;
}
Exception exception = std::move(*opt_excp);
FXL_DCHECK(exception.info.type = ZX_EXCP_HW_BREAKPOINT);
PRINT_CLEAN("Writing into 0x%zx. Hit!", (uint64_t)address_to_write);
WaitAsyncOnExceptionChannel(port, exception_channel);
ResumeException(thread_setup->thread, std::move(exception));
// Wait until the thread tells us it's ready.
CHECK_OK(thread_setup->event.wait_one(kThreadToHarness, zx::time::infinite(), nullptr));
return true;
}
void WatchpointTestCase() {
PRINT("Running Watchpoint test case.");
auto thread_setup = CreateTestSetup(WatchpointThreadFunction);
auto [port, exception_channel] = CreateExceptionChannel(thread_setup->thread);
WaitAsyncOnExceptionChannel(port, exception_channel);
uint32_t kSizes[] = {1, 2, 4, 8};
for (uint32_t size : kSizes) {
PRINT_CLEAN("====================================================================");
PRINT_CLEAN("%u BYTE ALIGNED WATCHPOINTS", size);
for (size_t i = 0; i < std::size(gDataToTouch); i++) {
uint64_t brk = reinterpret_cast<uint64_t>(gDataToTouch) + i;
if (i > 0)
PRINT_CLEAN("----------------------------------------");
PRINT_CLEAN("* Setting %u byte watchpoint for 0x%zx\n", size, brk);
for (size_t j = 0; j < std::size(gDataToTouch); j++) {
// Pass in the byte to break on.
uint8_t* data_ptr = gDataToTouch + j;
bool hit =
TestWatchpointRun(port, exception_channel, thread_setup.get(), brk, size, data_ptr);
// We should only hit if it is the expected byte.
bool in_range = (j - i) < size;
if (hit) {
FXL_DCHECK(in_range) << "i: " << i << ", j: " << j << ". Got unexpected hit.";
} else {
FXL_DCHECK(!in_range) << "i: " << i << ", j: " << j << ". Didn't get expected hit.";
}
}
}
}
// Tell the thread to exit.
thread_setup->test_running = false;
CHECK_OK(thread_setup->event.signal(kThreadToHarness, kHarnessToThread));
}
// Channel messaging -------------------------------------------------------------------------------
//
// 1. Thread writes a set of messages into the channel then closes its endpoint.
// 2. The main thread will wait until the channel has been closed.
// 3. It will then read all the messages from it.
int ChannelMessagingThreadFunction(void* user) {
ThreadSetup* thread_setup = reinterpret_cast<ThreadSetup*>(user);
// We signal the test harness that we are here.
thread_setup->event.signal(kHarnessToThread, kThreadToHarness);
// We wait now for the harness to tell us we can continue.
CHECK_OK(thread_setup->event.wait_one(kHarnessToThread, zx::time::infinite(), nullptr));
zx::channel* channel = reinterpret_cast<zx::channel*>(thread_setup->user);
constexpr char kMsg[] = "Hello, World!";
for (int i = 0; i < 10; i++) {
CHECK_OK(channel->write(0, kMsg, sizeof(kMsg), nullptr, 0));
PRINT("Added message %d.", i);
}
channel->reset();
PRINT("Closed channel.");
return 0;
}
void ChannelMessagingTestCase() {
PRINT("Running channel messaging.");
zx::channel mine, theirs;
CHECK_OK(zx::channel::create(0, &mine, &theirs));
auto thread_setup = CreateTestSetup(ChannelMessagingThreadFunction, &theirs);
// Tell the thread to continue.
thread_setup->event.signal(kThreadToHarness, kHarnessToThread);
// Wait for peer closed.
CHECK_OK(mine.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), nullptr));
// Start reading from the channel.
int read_count = 0;
char buf[1024] = {};
while (true) {
zx_status_t status = mine.read_etc(0, buf, nullptr, sizeof(buf), 0, nullptr, nullptr);
if (status == ZX_OK) {
PRINT("Read message %d: %s", read_count++, buf);
} else {
PRINT("No more messages (status: %s).", zx_status_get_string(status));
break;
}
}
thread_setup->test_running = false;
}
} // namespace
// Main --------------------------------------------------------------------------------------------
namespace {
struct TestCase {
using TestFunction = void (*)();
std::string name = nullptr;
std::string description = nullptr;
TestFunction test_function = nullptr;
};
TestCase kTestCases[] = {
{"hw_breakpoints", "Call multiple HW breakpoints on different functions.",
BreakOnFunctionTestCase},
{"channel_calls",
"Send multiple messages over a channel call and read from it after it is closed.",
ChannelMessagingTestCase},
{"watchpoints", "Call multiple watchpoints.", WatchpointTestCase},
};
void PrintUsage() {
printf("Usage: hw_breakpointer <TEST CASE>\n");
printf("Test cases are:\n");
for (auto& test_case : kTestCases) {
printf("- %s: %s\n", test_case.name.c_str(), test_case.description.c_str());
}
fflush(stdout);
}
TestCase::TestFunction GetTestCase(std::string test_name) {
for (auto& test_case : kTestCases) {
if (test_name == test_case.name)
return test_case.test_function;
}
return nullptr;
}
} // namespace
int main(int argc, char* argv[]) {
if (argc != 2) {
PrintUsage();
return 1;
}
const char* test_name = argv[1];
auto* test_function = GetTestCase(test_name);
if (!test_function) {
printf("Unknown test case %s\n", test_name);
PrintUsage();
return 1;
}
test_function();
return 0;
}