| // 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/syslog/cpp/macros.h> |
| #include <unistd.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/debug.h> |
| #include <zircon/threads.h> |
| |
| #include "src/developer/debug/shared/arch_arm64.h" |
| #include "src/developer/debug/shared/zx_status.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 watchpoint |
| // 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. |
| // |
| // THIS CODE IS MEANT TO CRASH WITH A HW EXCEPTION WHEN WORKING PROPERLY! |
| // |
| // The basic setup is: |
| // |
| // 1. Create a thread that will loop forever, continually calling a particular |
| // function. |
| // 2. Suspend that thread. |
| // 3. Install a HW watchpoint 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 variable we set the hw watchpoint on. |
| volatile int kVariableToChange = 0; |
| |
| static constexpr 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 ThreadFunction(void*) { |
| while (true) { |
| // We use write to avoid deadlocking with the outside libc calls. |
| write(1, kBeacon, sizeof(kBeacon)); |
| kVariableToChange++; |
| zx_nanosleep(zx_deadline_after(ZX_SEC(1))); |
| } |
| |
| return 0; |
| } |
| |
| #if defined(__x86_64__) |
| |
| zx_thread_state_debug_regs_t GetDebugRegs() { |
| zx_thread_state_debug_regs_t debug_regs = {}; |
| debug_regs.dr7 = 0b1 | // L0 = 1 (watchpoint is active). |
| 0b01 << 16 | // R/W0 = 01 (Only data write triggers). |
| 0b11 << 18; // LEN0 = 11 (4 byte watchpoint). |
| |
| // 4 byte aligned. |
| debug_regs.dr[0] = reinterpret_cast<uint64_t>(&kVariableToChange) & ~0b11; |
| return debug_regs; |
| } |
| |
| #elif defined(__aarch64__) |
| |
| zx_thread_state_debug_regs_t GetDebugRegs() { |
| zx_thread_state_debug_regs_t debug_regs = {}; |
| // For now the API is very simple, as zircon is not using further |
| // configuration beyond simply adding a write watchpoint. |
| debug_regs.hw_wps[0].dbgwcr = ARM64_FLAG_MASK(DBGWCR, E); |
| debug_regs.hw_wps[0].dbgwvr = reinterpret_cast<uint64_t>(&kVariableToChange); |
| return debug_regs; |
| } |
| |
| #else |
| #error Unsupported arch. |
| #endif |
| |
| int main() { |
| FX_LOGS(INFO) << "****** Creating thread."; |
| |
| thrd_t thread; |
| thrd_create(&thread, ThreadFunction, nullptr); |
| zx_handle_t thread_handle = 0; |
| thread_handle = thrd_get_zx_handle(thread); |
| |
| FX_LOGS(INFO) << "****** Suspending thread."; |
| |
| zx_status_t status; |
| zx_handle_t suspend_token; |
| status = zx_task_suspend(thread_handle, &suspend_token); |
| FX_DCHECK(status == ZX_OK) << "Could not suspend thread: " << debug_ipc::ZxStatusToString(status); |
| |
| zx_signals_t observed; |
| status = zx_object_wait_one(thread_handle, ZX_THREAD_SUSPENDED, zx_deadline_after(ZX_MSEC(500)), |
| &observed); |
| FX_DCHECK(status == ZX_OK) << "Could not get suspended signal: " |
| << debug_ipc::ZxStatusToString(status); |
| |
| FX_LOGS(INFO) << "****** Writing watchpoint."; |
| |
| auto debug_regs = GetDebugRegs(); |
| status = zx_thread_write_state(thread_handle, ZX_THREAD_STATE_DEBUG_REGS, &debug_regs, |
| sizeof(debug_regs)); |
| FX_DCHECK(status == ZX_OK) << "Could not write debug regs: " |
| << debug_ipc::ZxStatusToString(status); |
| |
| FX_LOGS(INFO) << "****** Resuming thread."; |
| |
| status = zx_handle_close(suspend_token); |
| FX_DCHECK(status == ZX_OK) << "Could not resume thread: " << debug_ipc::ZxStatusToString(status); |
| |
| FX_LOGS(INFO) << "****** Waiting for a bit to hit the watchpoint."; |
| |
| // The other thread won't ever stop, so there is no use waiting for a |
| // terminated signal. Instead we wait for a generous amount of time for the |
| // HW exception to happen. |
| // If it doesn't happen, it's an error. |
| zx_nanosleep(zx_deadline_after(ZX_SEC(10))); |
| |
| FX_LOGS(ERROR) << " THIS IS AN ERROR. THIS BINARY SHOULD'VE CRASHED!"; |
| return 1; |
| } |