blob: 508420686031e808b497acff7a2931c34e68f7e3 [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 <lib/zx/eventpair.h>
#include <string>
#include <vector>
#include "test_suite_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; }
const char kBeacon[] = "Counter: Thread running.\n";
// 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);
FX_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);
FX_DCHECK(opt_excp.has_value());
exception = std::move(*opt_excp);
FX_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);
FX_DCHECK(byte);
*byte += 1;
// We signal that we finished this write.
CHECK_OK(thread_setup->event.signal(kHarnessToThread, kThreadToHarness));
}
return 0;
}
// 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);
FX_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) {
FX_DCHECK(in_range) << "i: " << i << ", j: " << j << ". Got unexpected hit.";
} else {
FX_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));
}
// Aligned Watchpoint ------------------------------------------------------------------------------
//
// This test runs a thread that within a loop prints a group of ints, increments them (via var++)
// and then prints it again (the function is AlignedWatchpointThreadFunction).
// On the control thread, it sets a read/write watchpoint on one of the globals and verifies that
// the following accesses are hit:
//
// 1. Read on the first printf.
// 2. Read on the var++.
// 3. Write on the var++.
// 4. Read on the second printf.
//
// NOTE: In order to do this correctly, this tests does the same thing that zxdb does when it
// encounters a breakpoint: It deactivates the breakpoint, single steps the thread and then
// installs the breakpoint again. The watchpoint here is installed/uninstalled for every hit
// and the thread is single stepped.
int SomeInt = 10;
int SomeInt2 = 20;
int SomeInt3 = 30;
int SomeInt4 = 40;
struct AlignedWatchpointUserData {
// How many times to run the test.
int times = 10;
};
int AlignedWatchpointThreadFunction(void* user) {
auto* thread_setup = reinterpret_cast<ThreadSetup*>(user);
auto* user_data = reinterpret_cast<AlignedWatchpointUserData*>(thread_setup->user);
// We signal the test harness that we are here.
thread_setup->event.signal(kHarnessToThread, kThreadToHarness);
CHECK_OK(thread_setup->event.wait_one(kHarnessToThread, zx::time::infinite(), nullptr));
printf("User data times: %d.\n", user_data->times);
for (int i = 0; i < user_data->times; i++) {
printf("Before: %d, %d, %d, %d\n", SomeInt, SomeInt2, SomeInt3, SomeInt4);
SomeInt++;
SomeInt2++;
SomeInt3++;
SomeInt4++;
printf("After: %d, %d, %d, %d\n", SomeInt, SomeInt2, SomeInt3, SomeInt4);
printf("-----------------------------\n");
}
fflush(stdout);
// We signal that we finished this write.
CHECK_OK(thread_setup->event.signal(kHarnessToThread, kThreadToHarness));
return 0;
}
void WatchpointStepOver(uint64_t wp_address, const zx::thread& thread, const zx::port& port,
const zx::channel& exception_channel, std::optional<Exception> exception) {
RemoveWatchpoint(thread);
exception = SingleStep(thread, port, exception_channel, std::move(exception));
FX_DCHECK(exception);
// Now that we have single stepped, we can reinstall the watchpoint.
InstallWatchpoint(thread, wp_address, 4, WatchpointType::kReadWrite);
WaitAsyncOnExceptionChannel(port, exception_channel);
ResumeException(thread, std::move(*exception));
}
#define GET_DEADLINE(timeout) zx::deadline_after(zx::msec((timeout)))
void AlignedWatchpointTestCase() {
PRINT("Running aligned watchpoint test case.");
PRINT("SomeInt: 0x%p", &SomeInt);
PRINT("SomeInt2: 0x%p", &SomeInt2);
PRINT("SomeInt3: 0x%p", &SomeInt3);
PRINT("SomeInt4: 0x%p", &SomeInt4);
// Create test setup.
AlignedWatchpointUserData user_data = {};
user_data.times = 1;
auto thread_setup = CreateTestSetup(AlignedWatchpointThreadFunction, &user_data);
const zx::thread& thread = thread_setup->thread;
auto [port, exception_channel] = CreateExceptionChannel(thread);
WaitAsyncOnExceptionChannel(port, exception_channel);
// We install a watchpoint.
uint64_t wp_address = reinterpret_cast<uint64_t>(&SomeInt);
InstallWatchpoint(thread, wp_address, 4, WatchpointType::kReadWrite);
// Tell the test to run.
CHECK_OK(thread_setup->event.signal(kThreadToHarness, kHarnessToThread));
for (int i = 0; i < user_data.times; i++) {
uint64_t pc = 0;
PRINT("ITERATION %d ---------------------------------------------------------", i);
// Wait until the exception is hit.
auto exception = WaitForException(port, exception_channel, GET_DEADLINE(kExceptionWaitTimeout));
FX_DCHECK(exception.has_value());
FX_DCHECK(exception->pc > pc);
FX_DCHECK(DecodeHWException(thread, exception.value()) == HWExceptionType::kWatchpoint);
pc = exception->pc;
PRINT("Exception on 0x%p: Hit first printf read!", (void*)exception->pc);
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
exception = WaitForException(port, exception_channel, GET_DEADLINE(kExceptionWaitTimeout));
FX_DCHECK(exception.has_value());
FX_DCHECK(exception->pc > pc);
FX_DCHECK(DecodeHWException(thread, exception.value()) == HWExceptionType::kWatchpoint);
pc = exception->pc;
PRINT("Exception on 0x%p: Hit ++ read!", (void*)exception->pc);
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
exception = WaitForException(port, exception_channel, GET_DEADLINE(kExceptionWaitTimeout));
FX_DCHECK(exception.has_value());
FX_DCHECK(exception->pc > pc);
FX_DCHECK(DecodeHWException(thread, exception.value()) == HWExceptionType::kWatchpoint);
pc = exception->pc;
PRINT("Exception on 0x%p: Hit ++ write!", (void*)exception->pc);
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
exception = WaitForException(port, exception_channel, GET_DEADLINE(kExceptionWaitTimeout));
FX_DCHECK(exception.has_value());
FX_DCHECK(exception->pc > pc);
FX_DCHECK(DecodeHWException(thread, exception.value()) == HWExceptionType::kWatchpoint);
pc = exception->pc;
PRINT("Exception on 0x%p: Hit second printf read!", (void*)exception->pc);
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
}
// Wait until the thread is done.
CHECK_OK(thread_setup->event.wait_one(kThreadToHarness, zx::time::infinite(), nullptr));
}
// 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;
}
// Watchpoint Server/Client ------------------------------------------------------------------------
//
// The test spawns a new process with this binary, but passing the |watchpoint_client| option.
// It coordinates through a channel and an event. The idea is that the server listen on the debugger
// exception port of the client and setup a read-write watchpoint on a thread of the client.
// The client runs the same thread as AlignedWatchpointTestCase (AlignedWatchpointThreadFunction),
// which the server will set a brekapoint to. Basically it's a multi-process |aligned_watchpoint|
// test.
//
// The setup is as follows:
// 1. Client sends the addresses of the ints (SomeInt, SomeInt2, etc.). It also passes the memory
// associated with AlignedWatchpointThreadFunction, so that the server can verify which address
// actually triggered the exception.
// 2. The server listens on the exception port of the client and sets up a R/W Breakpoint.
// 3. The client starts another thread with AlignedWatchpointThreadFunction.
// 4. The server verifies that all the expected watchpoint exceptions are hit.
#define DEADLINE(d) zx::deadline_after(zx::sec((d)))
constexpr uint32_t kIterations = 1000;
constexpr uint32_t kInstructionBufferSize = 4096;
// These are the instructions that the server expects the client to have in the PC when it triggers
// each of the exceptions. Thhe client will send |kInstructionBufferSize| instructions starting
// with the first of |AlignedWatchpointThreadFunction| and the base address of it. That way the
// server can see the offset and see which instruction triggered the exception.
constexpr uint32_t kPrintLoad1 = 0xb9400101; // ldr w1, [x8]
constexpr uint32_t kPlusRead = 0xb940010c; // ldr w12, [x8]
constexpr uint32_t kPlusWrite = 0xb900010c; // str w12, [x8]
constexpr uint32_t kPrintLoad2 = 0xb9400101; // ldr w1, [x8]
// Verifies the exception that was triggered by the client.
// |instructions| is an array with the instructions of |AlignedWatchpointThreadFunction.
// |base_address| is the address where |AlignedWatchpointThreadFunction| starts.
// |expected_instruction| is what we expect the pc points to.
uint64_t CheckWatchpointException(const std::optional<Exception>& exception,
const zx::thread& thread, const zx::port& port,
const zx::channel& exception_channel, uint32_t* instructions,
uint32_t expected_instruction, uint64_t base_address, uint64_t pc,
const char* msg) {
// Wait until the exception is hit.
FX_DCHECK(exception.has_value());
FX_DCHECK(exception->pc > pc);
FX_DCHECK(DecodeHWException(thread, exception.value()) == HWExceptionType::kWatchpoint);
pc = exception->pc;
FX_DCHECK(pc < base_address + kInstructionBufferSize * sizeof(uint64_t));
uint32_t instruction = instructions[(pc - base_address) >> 2];
FX_DCHECK(instruction == expected_instruction);
PRINT("SERVER: Exception on %p (0x%x): %s.", reinterpret_cast<void*>(pc), instruction, msg);
return pc;
}
void WatchpointServer() {
PRINT("Running Watchpoint Server");
zx::job default_job(zx_job_default());
zx::job child_job;
CHECK_OK(zx::job::create(default_job, 0, &child_job));
// Spawn a process the FDIO way.
Process process;
std::vector<std::string> args = {"/pkg/bin/hw_breakpointer", "watchpoint_client"};
CHECK_OK(LaunchProcess(child_job, "test-process", args, &process));
zx::eventpair event, theirs;
CHECK_OK(zx::eventpair::create(0, &event, &theirs));
// Send an event down the channel.
zx_handle_t theirs_handle = theirs.release();
CHECK_OK(process.comm_channel.write(0, &kIterations, sizeof(kIterations), &theirs_handle, 1));
// Wait on the event.
CHECK_OK(WaitForClient(event, DEADLINE(1)));
PRINT("SERVER: Client got the event.");
// We set up the exception channel.
zx::port port;
CHECK_OK(zx::port::create(0, &port));
zx::channel exception_channel;
CHECK_OK(process.handle.create_exception_channel(0, &exception_channel));
WaitAsyncOnExceptionChannel(port, exception_channel);
// Wait until the client sends us where the addresses are.
CHECK_OK(WaitOnChannelReadable(process.comm_channel, DEADLINE(1)));
uint64_t kAddresses[4];
CHECK_OK(
process.comm_channel.read(0, kAddresses, nullptr, sizeof(kAddresses), 0, nullptr, nullptr));
PRINT("SERVER: SomeInt: 0x%p", reinterpret_cast<void*>(kAddresses[0]));
PRINT("SERVER: SomeInt2: 0x%p", reinterpret_cast<void*>(kAddresses[1]));
PRINT("SERVER: SomeInt3: 0x%p", reinterpret_cast<void*>(kAddresses[2]));
PRINT("SERVER: SomeInt4: 0x%p", reinterpret_cast<void*>(kAddresses[3]));
// Read the instructions.
CHECK_OK(WaitOnChannelReadable(process.comm_channel, DEADLINE(1)));
uint32_t kInstructions[kInstructionBufferSize] = {};
CHECK_OK(process.comm_channel.read(0, kInstructions, nullptr, sizeof(kInstructions), 0, nullptr,
nullptr));
// Read the base address.
CHECK_OK(WaitOnChannelReadable(process.comm_channel, DEADLINE(1)));
uint64_t kBaseAddress = 0;
CHECK_OK(process.comm_channel.read(0, &kBaseAddress, nullptr, sizeof(kBaseAddress), 0, nullptr,
nullptr));
FX_DCHECK(kBaseAddress > 0);
PRINT("SERVER: Got Base address %p.", reinterpret_cast<void*>(kBaseAddress));
// Ping the client we got it and wait for it to spawn up a thread and send the handle over.
CHECK_OK(SignalClient(event));
CHECK_OK(WaitForClient(event, DEADLINE(1)));
CHECK_OK(WaitOnChannelReadable(process.comm_channel, DEADLINE(1)));
zx::thread thread;
CHECK_OK(process.comm_channel.read(0, nullptr, thread.reset_and_get_address(), 0, 1, nullptr,
nullptr));
PRINT("SERVER: Received the thread handle.");
// Setup a watchpoint.
uint64_t wp_address = kAddresses[0];
InstallWatchpoint(thread, wp_address, 4, WatchpointType::kReadWrite);
CHECK_OK(SignalClient(event));
for (uint32_t i = 0; i < kIterations; i++) {
uint64_t pc = 0;
PRINT("SERVER: ITERATION %d ---------------------------------------------------------", i);
// printf 1.
auto exception = WaitForException(port, exception_channel, DEADLINE(1));
pc = CheckWatchpointException(exception, thread, port, exception_channel, kInstructions,
kPrintLoad1, kBaseAddress, pc, "First printf read");
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
// ++ Read.
exception = WaitForException(port, exception_channel, DEADLINE(1));
pc = CheckWatchpointException(exception, thread, port, exception_channel, kInstructions,
kPlusRead, kBaseAddress, pc, "++ read");
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
// ++ Write.
exception = WaitForException(port, exception_channel, DEADLINE(1));
pc = CheckWatchpointException(exception, thread, port, exception_channel, kInstructions,
kPlusWrite, kBaseAddress, pc, "++ write");
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
// printf 2.
exception = WaitForException(port, exception_channel, DEADLINE(1));
pc = CheckWatchpointException(exception, thread, port, exception_channel, kInstructions,
kPrintLoad2, kBaseAddress, pc, "Second printf read");
WatchpointStepOver(wp_address, thread, port, exception_channel, std::move(exception));
}
// Wait for the client to be done.
CHECK_OK(WaitForClient(event, DEADLINE(1)));
}
void WatchpointClient() {
zx::channel channel;
CHECK_OK(InitSubProcess(&channel));
CHECK_OK(WaitOnChannelReadable(channel, DEADLINE(1)));
uint32_t kTimes = 0;
zx::eventpair event;
CHECK_OK(
channel.read(0, &kTimes, event.reset_and_get_address(), sizeof(kTimes), 1, nullptr, nullptr));
FX_DCHECK(kTimes > 0);
PRINT("CLIENT: Read event. Times: %u.", kTimes);
CHECK_OK(SignalServer(event));
// Send over the addresses of the watchpoints.
uint64_t kAddresses[4] = {(uint64_t)&SomeInt, (uint64_t)&SomeInt2, (uint64_t)&SomeInt3,
(uint64_t)&SomeInt3};
CHECK_OK(channel.write(0, kAddresses, sizeof(kAddresses), nullptr, 0));
PRINT("CLIENT: SomeInt: 0x%p", reinterpret_cast<void*>(kAddresses[0]));
PRINT("CLIENT: SomeInt2: 0x%p", reinterpret_cast<void*>(kAddresses[1]));
PRINT("CLIENT: SomeInt3: 0x%p", reinterpret_cast<void*>(kAddresses[2]));
PRINT("CLIENT: SomeInt4: 0x%p", reinterpret_cast<void*>(kAddresses[3]));
PRINT("CLIENT: Wrote addresses.");
// Send over the instructions of the function.
uint32_t kInstructions[kInstructionBufferSize] = {};
memcpy(kInstructions, reinterpret_cast<void*>(AlignedWatchpointThreadFunction),
sizeof(kInstructions));
CHECK_OK(channel.write(0, kInstructions, sizeof(kInstructions), nullptr, 0));
// Send over the base address.
uint64_t kBaseAddress = (uint64_t)AlignedWatchpointThreadFunction;
CHECK_OK(channel.write(0, &kBaseAddress, sizeof(kBaseAddress), nullptr, 0));
PRINT("CLIENT: Sent base address %p.", reinterpret_cast<void*>(kBaseAddress));
// Wait for ack from the server.
CHECK_OK(WaitForServer(event, DEADLINE(1)));
// Start the thread.
AlignedWatchpointUserData user_data = {};
user_data.times = kTimes;
auto thread_setup = CreateTestSetup(AlignedWatchpointThreadFunction, &user_data);
// Write the thread handle over.
zx::thread thread_to_send;
CHECK_OK(thread_setup->thread.duplicate(ZX_RIGHT_SAME_RIGHTS, &thread_to_send));
CHECK_OK(SignalServer(event));
zx_handle_t handle = thread_to_send.release();
CHECK_OK(channel.write(0, nullptr, 0, &handle, 1));
PRINT("CLIENT: Created and sent the thread handle over.");
// Tell the client we wrote.
CHECK_OK(WaitForServer(event, DEADLINE(1)));
PRINT("CLIENT: Starting test thread.");
// Tell the test to run.
CHECK_OK(thread_setup->event.signal(kThreadToHarness, kHarnessToThread));
// Wait until the thread is done.
CHECK_OK(thread_setup->event.wait_one(kThreadToHarness, zx::time::infinite(), nullptr));
// Signal the server we're done.
CHECK_OK(SignalServer(event));
}
} // 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},
{"watchpoints", "Call multiple watchpoints.", WatchpointTestCase},
{"aligned_watchpoints", "Call aligned R/W watchpoint", AlignedWatchpointTestCase},
{"channel_calls",
"Send multiple messages over a channel call and read from it after it is closed.",
ChannelMessagingTestCase},
{"watchpoint_server", "Will start a client process and sets up a R/W watchpoint on it.",
WatchpointServer},
{"watchpoint_client", "Started by |watchpoint_server|. Not meant to be run manually.",
WatchpointClient},
};
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;
}