| // 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; |
| } |