blob: dac9f648fe90008cf750763166ad7f715a9bd859 [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 <err.h>
#include <string.h>
#include <sys/types.h>
#include <kernel/thread.h>
#include <arch/x86.h>
#include <arch/x86/feature.h>
#include <arch/debugger.h>
#include <magenta/syscalls/debug.h>
uint arch_num_regsets(void)
{
// TODO(dje): for now. general regs
return 1;
}
#define SYSCALL_OFFSETS_EQUAL(reg) \
(__offsetof(mx_x86_64_general_regs_t, reg) == \
__offsetof(x86_syscall_general_regs_t, reg))
static_assert(SYSCALL_OFFSETS_EQUAL(rax), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rbx), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rcx), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rdx), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rsi), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rdi), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rbp), "");
static_assert(SYSCALL_OFFSETS_EQUAL(rsp), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r8), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r9), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r10), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r11), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r12), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r13), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r14), "");
static_assert(SYSCALL_OFFSETS_EQUAL(r15), "");
static_assert(sizeof(mx_x86_64_general_regs_t) == sizeof(x86_syscall_general_regs_t), "");
static void x86_fill_in_gregs_from_syscall(mx_x86_64_general_regs_t *out,
const x86_syscall_general_regs_t *in)
{
memcpy(out, in, sizeof(*in));
}
static void x86_fill_in_syscall_from_gregs(x86_syscall_general_regs_t *out,
const mx_x86_64_general_regs_t *in)
{
// Don't allow overriding privileged fields of rflags, and ignore writes
// to reserved fields.
const uint64_t orig_rflags = out->rflags;
memcpy(out, in, sizeof(*in));
out->rflags = orig_rflags & ~X86_FLAGS_USER;
out->rflags |= in->rflags & X86_FLAGS_USER;
}
#define COPY_REG(out, in, reg) (out)->reg = (in)->reg
#define COPY_COMMON_IFRAME_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)
static void x86_fill_in_gregs_from_iframe(mx_x86_64_general_regs_t *out,
const x86_iframe_t *in)
{
COPY_COMMON_IFRAME_REGS(out, in);
out->rsp = in->user_sp;
out->rip = in->ip;
out->rflags = in->flags;
}
static void x86_fill_in_iframe_from_gregs(x86_iframe_t *out,
const mx_x86_64_general_regs_t *in)
{
COPY_COMMON_IFRAME_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;
}
static status_t arch_get_general_regs(struct thread *thread, void *grp, uint32_t *buf_size)
{
mx_x86_64_general_regs_t *out = (mx_x86_64_general_regs_t *)grp;
uint32_t provided_buf_size = *buf_size;
*buf_size = sizeof(*out);
if (provided_buf_size < sizeof(*out))
return MX_ERR_BUFFER_TOO_SMALL;
if (thread_stopped_in_exception(thread)) {
// TODO(dje): We could get called while processing a synthetic
// exception where there is no frame.
if (thread->exception_context->frame == nullptr)
return MX_ERR_NOT_SUPPORTED;
} else {
// TODO(dje): Punt if, for example, suspended in channel call.
// Can be removed when MG-747 done.
if (thread->arch.suspended_general_regs.gregs == nullptr)
return MX_ERR_NOT_SUPPORTED;
}
DEBUG_ASSERT(thread->arch.suspended_general_regs.gregs);
switch (thread->arch.general_regs_source) {
case X86_GENERAL_REGS_SYSCALL:
x86_fill_in_gregs_from_syscall(out, thread->arch.suspended_general_regs.syscall);
break;
case X86_GENERAL_REGS_IFRAME:
x86_fill_in_gregs_from_iframe(out, thread->arch.suspended_general_regs.iframe);
break;
default:
DEBUG_ASSERT(false);
return MX_ERR_BAD_STATE;
}
return MX_OK;
}
static status_t arch_set_general_regs(struct thread *thread, const void *grp, uint32_t buf_size)
{
const mx_x86_64_general_regs_t *in = (const mx_x86_64_general_regs_t *)grp;
if (buf_size != sizeof(*in))
return MX_ERR_INVALID_ARGS;
if (thread_stopped_in_exception(thread)) {
// TODO(dje): We could get called while processing a synthetic
// exception where there is no frame.
if (thread->exception_context->frame == nullptr)
return MX_ERR_NOT_SUPPORTED;
} else {
// TODO(dje): Punt if, for example, suspended in channel call.
// Can be removed when MG-747 done.
if (thread->arch.suspended_general_regs.gregs == nullptr)
return MX_ERR_NOT_SUPPORTED;
}
DEBUG_ASSERT(thread->arch.suspended_general_regs.gregs);
switch (thread->arch.general_regs_source) {
case X86_GENERAL_REGS_SYSCALL: {
// Disallow setting RIP to a non-canonical address, to prevent
// returning to such addresses using the SYSRET instruction.
// See docs/sysret_problem.md. Note that this check also
// disallows canonical top-bit-set addresses, but allowing such
// addresses is not useful and it is simpler to disallow them.
uint8_t addr_width = x86_linear_address_width();
uint64_t noncanonical_addr = ((uint64_t) 1) << (addr_width - 1);
if (in->rip >= noncanonical_addr)
return MX_ERR_INVALID_ARGS;
x86_fill_in_syscall_from_gregs(thread->arch.suspended_general_regs.syscall, in);
break;
}
case X86_GENERAL_REGS_IFRAME:
x86_fill_in_iframe_from_gregs(thread->arch.suspended_general_regs.iframe, in);
break;
default:
DEBUG_ASSERT(false);
return MX_ERR_BAD_STATE;
}
return MX_OK;
}
// The caller is responsible for making sure the thread is in an exception
// or is suspended, and stays so.
status_t arch_get_regset(struct thread *thread, uint regset, void *regs, uint32_t *buf_size)
{
switch (regset)
{
case 0:
return arch_get_general_regs(thread, regs, buf_size);
default:
return MX_ERR_INVALID_ARGS;
}
}
// The caller is responsible for making sure the thread is in an exception
// or is suspended, and stays so.
status_t arch_set_regset(struct thread *thread, uint regset, const void *regs, uint32_t buf_size)
{
switch (regset)
{
case 0:
return arch_set_general_regs(thread, regs, buf_size);
default:
return MX_ERR_INVALID_ARGS;
}
}