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