blob: cff5c07e4676e924556a00bf61aa022762304595 [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/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;
}