blob: 99efb74fe6dc5b33b2458c90d9841d90701f4356 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/zircon-internal/macros.h>
#include <string.h>
#include <zircon/errors.h>
#include <zircon/syscalls/debug.h>
#include <zircon/types.h>
#include <arch/debugger.h>
#include <arch/regs.h>
#include <arch/x86.h>
#include <arch/x86/feature.h>
#include <arch/x86/mmu.h>
#include <arch/x86/registers.h>
#include <kernel/lockdep.h>
#include <kernel/thread.h>
#include <kernel/thread_lock.h>
#include <vm/vm.h>
// Note on locking: The below functions need to read and write the register state and make sure that
// nothing happens with respect to scheduling that thread while this is happening. As a result they
// use ThreadLock. In most cases this will not be necessary but there are relatively few
// guarantees so we lock the scheduler. Since these functions are used mostly for debugging, this
// shouldn't be too significant a performance penalty.
namespace {
#define COPY_REG(out, in, reg) (out)->reg = (in)->reg
#define COPY_COMMON_REGS(out, in) \
do { \
COPY_REG(out, in, rax); \
COPY_REG(out, in, rbx); \
COPY_REG(out, in, rcx); \
COPY_REG(out, in, rdx); \
COPY_REG(out, in, rsi); \
COPY_REG(out, in, rdi); \
COPY_REG(out, in, rbp); \
COPY_REG(out, in, r8); \
COPY_REG(out, in, r9); \
COPY_REG(out, in, r10); \
COPY_REG(out, in, r11); \
COPY_REG(out, in, r12); \
COPY_REG(out, in, r13); \
COPY_REG(out, in, r14); \
COPY_REG(out, in, r15); \
} while (0)
void x86_fill_in_gregs_from_syscall(zx_thread_state_general_regs_t* out, const syscall_regs_t* in) {
COPY_COMMON_REGS(out, in);
out->rip = in->rip;
out->rsp = in->rsp;
out->rflags = in->rflags;
}
void x86_fill_in_syscall_from_gregs(syscall_regs_t* out, const zx_thread_state_general_regs_t* in) {
COPY_COMMON_REGS(out, in);
out->rip = in->rip;
out->rsp = in->rsp;
// Don't allow overriding privileged fields of rflags, and ignore writes
// to reserved fields.
out->rflags &= ~X86_FLAGS_USER;
out->rflags |= in->rflags & X86_FLAGS_USER;
}
void x86_fill_in_gregs_from_iframe(zx_thread_state_general_regs_t* out, const iframe_t* in) {
COPY_COMMON_REGS(out, in);
out->rsp = in->user_sp;
out->rip = in->ip;
out->rflags = in->flags;
}
void x86_fill_in_iframe_from_gregs(iframe_t* out, const zx_thread_state_general_regs_t* in) {
COPY_COMMON_REGS(out, in);
out->user_sp = in->rsp;
out->ip = in->rip;
// Don't allow overriding privileged fields of rflags, and ignore writes
// to reserved fields.
out->flags &= ~X86_FLAGS_USER;
out->flags |= in->rflags & X86_FLAGS_USER;
}
// Whether an operation gets thread state or sets it.
enum class RegAccess { kGet, kSet };
// Checks whether the mxcsr register has unsupported bits.
// The processor specifies which flags of the mxcsr are supported via the
// mxcsr_mask obtained with the fxsave instruction.
//
// The manuals mention that it is possible for the mask to be 0, and specify
// 0x000ffbf as the default value.
//
// For details see:
// Intel 64 and IA-32 Architectures Software Developer’s Manual
// Volume 1: Basic Architecture
// Section: 11.6.6 Guidelines for Writing to the MXCSR Register
// AMD64 Architecture Programmer’s Manual
// Volume 2: System Programming
// Section: 11.5.9 MXCSR State Management
static inline bool mxcsr_is_valid(uint32_t mxcsr, uint32_t mxcsr_mask) {
if (mxcsr_mask == 0x0) {
mxcsr_mask = 0x0000ffbf;
}
return (mxcsr & ~mxcsr_mask) == 0;
}
// Backend for arch_get_vector_regs and arch_set_vector_regs. This does a read or write of the
// thread to or from the regs structure.
zx_status_t x86_get_set_vector_regs(Thread* thread, zx_thread_state_vector_regs_t* regs,
RegAccess access) {
// Function to copy memory in the correct direction. Write the code using this function as if it
// was "memcpy" in "get" mode, and it will be reversed in "set" mode.
auto get_set_memcpy =
(access == RegAccess::kGet)
? [](void* regs, void* thread, size_t size) { memcpy(regs, thread, size); }
: // Get mode.
[](void* regs, void* thread, size_t size) { memcpy(thread, regs, size); }; // Set mode.
if (access == RegAccess::kGet) {
// Not all parts will be filled in in all cases so zero out first.
memset(regs, 0, sizeof(zx_thread_state_vector_regs_t));
}
// Whether to force the components to be marked present in the xsave area.
bool mark_present = access == RegAccess::kSet;
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
constexpr int kNumSSERegs = 16;
// The low 128 bits of registers 0-15 come from the legacy area and are always present.
constexpr int kXmmRegSize = 16; // Each XMM register is 128 bits / 16 bytes.
uint32_t comp_size = 0;
x86_xsave_legacy_area* save =
static_cast<x86_xsave_legacy_area*>(x86_get_extended_register_state_component(
thread->arch().extended_register_buffer, X86_XSAVE_STATE_INDEX_SSE, mark_present,
&comp_size));
DEBUG_ASSERT(save); // Legacy getter should always succeed.
// https://fxbug.dev/42127734: Overwriting the reserved bits of the mxcsr register
// causes a #GP Fault. We need to check against the mxcsr_mask to see if the
// proposed mxcsr is valid.
if (access == RegAccess::kSet && !mxcsr_is_valid(regs->mxcsr, save->mxcsr_mask)) {
return ZX_ERR_INVALID_ARGS;
}
for (int i = 0; i < kNumSSERegs; i++) {
get_set_memcpy(&regs->zmm[i].v[0], &save->xmm[i], kXmmRegSize);
}
// MXCSR (always present): 32-bit status word.
get_set_memcpy(&regs->mxcsr, &save->mxcsr, 4);
// AVX grows the registers to 256 bits each. Optional.
constexpr int kYmmHighSize = 16; // Additional bytes in each register.
uint8_t* ymm_highbits = static_cast<uint8_t*>(x86_get_extended_register_state_component(
thread->arch().extended_register_buffer, X86_XSAVE_STATE_INDEX_AVX, mark_present,
&comp_size));
if (ymm_highbits) {
DEBUG_ASSERT(comp_size == kYmmHighSize * kNumSSERegs);
for (int i = 0; i < kNumSSERegs; i++) {
get_set_memcpy(&regs->zmm[i].v[2], &ymm_highbits[i * kYmmHighSize], kYmmHighSize);
}
}
return ZX_OK;
}
} // namespace
zx_status_t arch_get_general_regs(Thread* thread, zx_thread_state_general_regs_t* out) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
// Punt if registers aren't available. E.g.,
// TODO(https://fxbug.dev/42105394): Registers aren't available in synthetic exceptions.
if (thread->arch().suspended_general_regs.gregs == nullptr)
return ZX_ERR_NOT_SUPPORTED;
DEBUG_ASSERT(thread->arch().suspended_general_regs.gregs);
switch (GeneralRegsSource(thread->arch().general_regs_source)) {
case GeneralRegsSource::Iframe:
x86_fill_in_gregs_from_iframe(out, thread->arch().suspended_general_regs.iframe);
break;
case GeneralRegsSource::Syscall:
x86_fill_in_gregs_from_syscall(out, thread->arch().suspended_general_regs.syscall);
break;
default:
ASSERT(false);
}
out->fs_base = thread->arch().fs_base;
out->gs_base = thread->arch().gs_base;
return ZX_OK;
}
zx_status_t arch_set_general_regs(Thread* thread, const zx_thread_state_general_regs_t* in) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
// Punt if registers aren't available. E.g.,
// TODO(https://fxbug.dev/42105394): Registers aren't available in synthetic exceptions.
if (thread->arch().suspended_general_regs.gregs == nullptr)
return ZX_ERR_NOT_SUPPORTED;
// If these addresses are not canonical, the kernel will GPF when it tries
// to set them as the current values.
if (!x86_is_vaddr_canonical(in->fs_base))
return ZX_ERR_INVALID_ARGS;
if (!x86_is_vaddr_canonical(in->gs_base))
return ZX_ERR_INVALID_ARGS;
// https://fxbug.dev/42127735: Disallow setting RIP to a non-canonical address, to
// prevent returning to such addresses using the SYSRET or IRETQ
// instructions. See docs/concepts/kernel/sysret_problem.md.
//
// The code also restricts the RIP to userspace addresses. There is no use
// case for setting the RIP to a kernel address.
if (!x86_is_vaddr_canonical(in->rip) || is_kernel_address(in->rip))
return ZX_ERR_INVALID_ARGS;
DEBUG_ASSERT(thread->arch().suspended_general_regs.gregs);
switch (GeneralRegsSource(thread->arch().general_regs_source)) {
case GeneralRegsSource::Iframe:
x86_fill_in_iframe_from_gregs(thread->arch().suspended_general_regs.iframe, in);
break;
case GeneralRegsSource::Syscall: {
x86_fill_in_syscall_from_gregs(thread->arch().suspended_general_regs.syscall, in);
break;
}
default:
ASSERT(false);
}
thread->arch().fs_base = in->fs_base;
thread->arch().gs_base = in->gs_base;
return ZX_OK;
}
zx_status_t arch_get_single_step(Thread* thread, zx_thread_state_single_step_t* out) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
// Punt if registers aren't available. E.g.,
// TODO(https://fxbug.dev/42105394): Registers aren't available in synthetic exceptions.
if (thread->arch().suspended_general_regs.gregs == nullptr)
return ZX_ERR_NOT_SUPPORTED;
uint64_t* flags = nullptr;
switch (GeneralRegsSource(thread->arch().general_regs_source)) {
case GeneralRegsSource::Iframe:
flags = &thread->arch().suspended_general_regs.iframe->flags;
break;
case GeneralRegsSource::Syscall:
flags = &thread->arch().suspended_general_regs.syscall->rflags;
break;
default:
ASSERT(false);
}
*out = !!(*flags & X86_FLAGS_TF);
return ZX_OK;
}
zx_status_t arch_set_single_step(Thread* thread, const zx_thread_state_single_step_t* in) {
if (*in != 0 && *in != 1) {
return ZX_ERR_INVALID_ARGS;
}
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
// Punt if registers aren't available. E.g.,
// TODO(https://fxbug.dev/42105394): Registers aren't available in synthetic exceptions.
if (thread->arch().suspended_general_regs.gregs == nullptr)
return ZX_ERR_NOT_SUPPORTED;
uint64_t* flags = nullptr;
switch (GeneralRegsSource(thread->arch().general_regs_source)) {
case GeneralRegsSource::Iframe:
flags = &thread->arch().suspended_general_regs.iframe->flags;
break;
case GeneralRegsSource::Syscall:
flags = &thread->arch().suspended_general_regs.syscall->rflags;
break;
default:
ASSERT(false);
}
if (*in) {
*flags |= X86_FLAGS_TF;
} else {
*flags &= ~X86_FLAGS_TF;
}
return ZX_OK;
}
zx_status_t arch_get_fp_regs(Thread* thread, zx_thread_state_fp_regs_t* out) {
// Don't leak any reserved fields.
memset(out, 0, sizeof(zx_thread_state_fp_regs_t));
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
uint32_t comp_size = 0;
const x86_xsave_legacy_area* save =
static_cast<const x86_xsave_legacy_area*>(x86_get_extended_register_state_component(
thread->arch().extended_register_buffer, X86_XSAVE_STATE_INDEX_X87, false, &comp_size));
DEBUG_ASSERT(save); // Legacy getter should always succeed.
out->fcw = save->fcw;
out->fsw = save->fsw;
out->ftw = save->ftw;
out->fop = save->fop;
out->fip = save->fip;
out->fdp = save->fdp;
memcpy(out->st, save->st, sizeof(out->st));
return ZX_OK;
}
zx_status_t arch_set_fp_regs(Thread* thread, const zx_thread_state_fp_regs_t* in) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
uint32_t comp_size = 0;
x86_xsave_legacy_area* save =
static_cast<x86_xsave_legacy_area*>(x86_get_extended_register_state_component(
thread->arch().extended_register_buffer, X86_XSAVE_STATE_INDEX_X87, true, &comp_size));
DEBUG_ASSERT(save); // Legacy getter should always succeed.
save->fcw = in->fcw;
save->fsw = in->fsw;
save->ftw = in->ftw;
save->fop = in->fop;
save->fip = in->fip;
save->fdp = in->fdp;
memcpy(save->st, in->st, sizeof(in->st));
return ZX_OK;
}
zx_status_t arch_get_vector_regs(Thread* thread, zx_thread_state_vector_regs_t* out) {
// The get_set function promises to not modify the thread state in get mode.
return x86_get_set_vector_regs(thread, out, RegAccess::kGet);
}
zx_status_t arch_set_vector_regs(Thread* thread, const zx_thread_state_vector_regs_t* in) {
// The get_set function won't write in "kSet" mode so the const_cast is safe.
return x86_get_set_vector_regs(thread, const_cast<zx_thread_state_vector_regs_t*>(in),
RegAccess::kSet);
}
zx_status_t arch_get_debug_regs(Thread* thread, zx_thread_state_debug_regs_t* out) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
// The kernel updates this per-thread data everytime a hw debug event occurs, meaning that
// these values will be always up to date. If the thread is not using hw debug capabilities,
// these will have the default zero values.
out->dr[0] = thread->arch().debug_state.dr[0];
out->dr[1] = thread->arch().debug_state.dr[1];
out->dr[2] = thread->arch().debug_state.dr[2];
out->dr[3] = thread->arch().debug_state.dr[3];
out->dr6 = thread->arch().debug_state.dr6;
out->dr7 = thread->arch().debug_state.dr7;
return ZX_OK;
}
zx_status_t arch_set_debug_regs(Thread* thread, const zx_thread_state_debug_regs_t* in) {
Guard<MonitoredSpinLock, IrqSave> thread_lock_guard{ThreadLock::Get(), SOURCE_TAG};
DEBUG_ASSERT(thread->IsUserStateSavedLocked());
// Replace the state of the thread with the given one. We now need to keep track of the debug
// state of this register across context switches.
x86_debug_state_t new_debug_state;
new_debug_state.dr[0] = in->dr[0];
new_debug_state.dr[1] = in->dr[1];
new_debug_state.dr[2] = in->dr[2];
new_debug_state.dr[3] = in->dr[3];
new_debug_state.dr6 = in->dr6;
new_debug_state.dr7 = in->dr7;
// Validate the new input. This will mask reserved bits to their stated values.
if (!x86_validate_debug_state(&new_debug_state))
return ZX_ERR_INVALID_ARGS;
// NOTE: This currently does a write-read round-trip to the CPU in order to ensure that
// |thread->arch().debug_state| tracks the exact value as it is stored in the registers.
// TODO(https://fxbug.dev/42108006): Ideally, we could do some querying at boot time about the
// format that the CPU is storing reserved bits and we can create a mask we can apply to the input
// values and avoid changing the state.
// Save the current debug state temporarily.
x86_debug_state_t current_debug_state;
x86_read_hw_debug_regs(&current_debug_state);
// Write and then read from the CPU to have real values tracked by the thread data.
// Mark the thread as now tracking the debug state.
x86_write_hw_debug_regs(&new_debug_state);
x86_read_hw_debug_regs(&thread->arch().debug_state);
thread->arch().track_debug_state = true;
// Restore the original debug state. Should always work as the input was already validated.
x86_write_hw_debug_regs(&current_debug_state);
return ZX_OK;
}
// NOTE: While x86 supports up to 4 hw breakpoints/watchpoints, there is a catch:
// They are shared, so (breakpoints + watchpoints) <= HW_DEBUG_REGISTERS_COUNT.
uint8_t arch_get_hw_breakpoint_count() { return HW_DEBUG_REGISTERS_COUNT; }
uint8_t arch_get_hw_watchpoint_count() { return HW_DEBUG_REGISTERS_COUNT; }