blob: bdedd196e773c331256ea3e4bd27cade7df84e2f [file] [log] [blame]
// Copyright 2018 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/fzl/memory-probe.h>
#include <lib/zx/channel.h>
#include <lib/zx/exception.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <limits.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
namespace {
// These are not really functions, but entry points for a thread that has a
// tiny stack and no other setup. They're not really entered with the C
// ABI as such. Rather, they're entered with the first argument register
// set to an address and with the SP at the very top of the allocated
// stack. They're defined in pure assembly so that there are no issues
// with compiler-generated code's assumptions about the proper ABI setup,
// instrumentation, etc.
//
// Since this calls into the vDSO, it must adhere to the vDSO's ABI, which is
// the "vanilla" C calling convention (no safe-stack or shadow-call-stack).
// As well as the register usage conventions, this mandates a stack of some
// reasonable minimum size, even on AArch64 where the calling convention
// doesn't per se involve the stack (but it is specified that the SP must be
// "valid" on function entry). Today's vDSO implementation might not actually
// make use of the stack in the zx_thread_exit call, but it always could. The
// x86 C calling convention mandates that the stack pointer have exactly the
// alignment it gets from the call instruction on an aligned stack (that is,
// SP % 16 == 8).
extern "C" void read_thread_func(uintptr_t address, uintptr_t);
extern "C" void write_thread_func(uintptr_t address, uintptr_t);
#define PROBE_FUNC(name, insn) \
__asm__(".pushsection .text." #name \
",\"ax\",%progbits\n" \
".balign 4\n" \
".type " #name \
",%function\n" \
".cfi_startproc\n" #name ":\n" insn "\n" CALL_INSN \
" zx_thread_exit\n" \
".cfi_endproc\n" \
".size " #name ", . - " #name \
"\n" \
".popsection");
#ifdef __aarch64__
constexpr auto kPcRegister = &zx_thread_state_general_regs_t::pc;
constexpr int kProbeInsnSize = 4;
#define CALL_INSN "bl"
#define READ_PROBE_INSN "ldrb w1, [x0]"
#define WRITE_PROBE_INSN "strb w1, [x0]"
#elif defined(__riscv)
constexpr auto kPcRegister = &zx_thread_state_general_regs_t::pc;
constexpr int kProbeInsnSize = 4;
#define CALL_INSN "call"
#define READ_PROBE_INSN "lb a1, (a0)"
#define WRITE_PROBE_INSN "sb a1, (a0)"
#elif defined(__x86_64__)
constexpr auto kPcRegister = &zx_thread_state_general_regs_t::rip;
constexpr int kProbeInsnSize = 2;
#define CALL_INSN "call"
#define READ_PROBE_INSN "movb (%rdi), %al"
#define WRITE_PROBE_INSN "movb %al, (%rdi)"
#else
#error "what machine?"
#endif
PROBE_FUNC(read_thread_func, READ_PROBE_INSN)
PROBE_FUNC(write_thread_func, WRITE_PROBE_INSN)
zx_status_t advance_program_counter(const zx::thread& thread) {
zx_thread_state_general_regs_t regs;
auto status = thread.read_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
if (status != ZX_OK) {
return status;
}
regs.*kPcRegister += kProbeInsnSize;
return thread.write_state(ZX_THREAD_STATE_GENERAL_REGS, &regs, sizeof(regs));
}
zx_status_t do_probe(void (*op)(uintptr_t address, uintptr_t), uintptr_t addr) {
// This function starts a new thread to perform the read/write test, and catches any exceptions
// in this thread to see if it failed or not.
zx::thread thread;
ZX_ASSERT(zx::thread::create(*zx::process::self(), "memory_probe", 12u, 0u, &thread) == ZX_OK);
alignas(16) static uint8_t thread_stack[128];
void* stack = thread_stack + sizeof(thread_stack);
zx::channel exception_channel;
ZX_ASSERT(thread.create_exception_channel(0, &exception_channel) == ZX_OK);
thread.start(op, stack, addr, 0);
// Wait for exception or thread completion.
zx_signals_t signals = 0;
ZX_ASSERT(exception_channel.wait_one(ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
zx::time::infinite(), &signals) == ZX_OK);
if (signals & ZX_CHANNEL_READABLE) {
zx_exception_info_t info;
zx::exception exception;
ZX_ASSERT(exception_channel.read(0, &info, exception.reset_and_get_address(), sizeof(info), 1,
nullptr, nullptr) == ZX_OK);
ZX_ASSERT(info.type == ZX_EXCP_FATAL_PAGE_FAULT);
ZX_ASSERT(advance_program_counter(thread) == ZX_OK);
zx_exception_report_t report;
ZX_ASSERT(thread.get_info(ZX_INFO_THREAD_EXCEPTION_REPORT, &report, sizeof(report), nullptr,
nullptr) == ZX_OK);
zx_status_t page_fault_error_code = static_cast<zx_status_t>(report.context.synth_code);
ZX_ASSERT_MSG(page_fault_error_code != ZX_OK, "The page fault must have generated an error");
uint32_t state = ZX_EXCEPTION_STATE_HANDLED;
exception.set_property(ZX_PROP_EXCEPTION_STATE, &state, sizeof(state));
return page_fault_error_code;
}
// Thread terminated normally so the memory is readable/writable.
return ZX_OK;
}
} // namespace
zx_status_t probe_for_read(const void* addr) {
return do_probe(read_thread_func, reinterpret_cast<uintptr_t>(addr));
}
zx_status_t probe_for_write(void* addr) {
return do_probe(write_thread_func, reinterpret_cast<uintptr_t>(addr));
}