| // 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 <arch/debugger.h> |
| #include <arch/x86.h> |
| #include <arch/x86/feature.h> |
| #include <arch/x86/registers.h> |
| #include <err.h> |
| #include <kernel/thread.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <zircon/syscalls/debug.h> |
| #include <zircon/types.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 AutoThreadLock. 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 SYSCALL_OFFSETS_EQUAL(reg) \ |
| (__offsetof(zx_thread_state_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(zx_thread_state_general_regs_t) == sizeof(x86_syscall_general_regs_t), ""); |
| |
| void x86_fill_in_gregs_from_syscall(zx_thread_state_general_regs_t* out, |
| const x86_syscall_general_regs_t* in) { |
| memcpy(out, in, sizeof(*in)); |
| } |
| |
| void x86_fill_in_syscall_from_gregs(x86_syscall_general_regs_t* out, |
| const zx_thread_state_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) |
| |
| void x86_fill_in_gregs_from_iframe(zx_thread_state_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; |
| } |
| |
| void x86_fill_in_iframe_from_gregs(x86_iframe_t* out, |
| const zx_thread_state_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; |
| } |
| |
| // Whether an operation gets thread state or sets it. |
| enum class RegAccess { kGet, kSet }; |
| |
| // 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(struct thread* thread, zx_thread_state_vector_regs* 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)); |
| } |
| |
| // Whether to force the components to be marked present in the xsave area. |
| bool mark_present = access == RegAccess::kSet; |
| |
| AutoThreadLock lock; |
| if (thread->state == THREAD_RUNNING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| 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_state, |
| X86_XSAVE_STATE_INDEX_SSE, mark_present, |
| &comp_size)); |
| DEBUG_ASSERT(save); // Legacy getter should always succeed. |
| for (int i = 0; i < kNumSSERegs; i++) { |
| get_set_memcpy(®s->zmm[i].v[0], &save->xmm[i], kXmmRegSize); |
| } |
| |
| // MXCSR (always present): 32-bit status word. |
| get_set_memcpy(®s->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_state, |
| 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(®s->zmm[i].v[2], &ymm_highbits[i * kYmmHighSize], kYmmHighSize); |
| } |
| } |
| |
| // AVX-512 opmask registers (8 64-bit registers). Optional. |
| constexpr int kNumOpmaskRegs = 8; |
| uint64_t* opmask = static_cast<uint64_t*>( |
| x86_get_extended_register_state_component(thread->arch.extended_register_state, |
| X86_XSAVE_STATE_INDEX_AVX512_OPMASK, mark_present, |
| &comp_size)); |
| if (opmask) { |
| DEBUG_ASSERT(comp_size == kNumOpmaskRegs * sizeof(uint64_t)); |
| for (int i = 0; i < kNumOpmaskRegs; i++) { |
| get_set_memcpy(®s->opmask[i], &opmask[i], sizeof(uint64_t)); |
| } |
| } |
| |
| // AVX-512 high bits (256 bits extra each) for ZMM0-15. |
| constexpr int kZmmHighSize = 32; // Additional bytes in each register. |
| uint8_t* zmm_highbits = static_cast<uint8_t*>( |
| x86_get_extended_register_state_component(thread->arch.extended_register_state, |
| X86_XSAVE_STATE_INDEX_AVX512_LOWERZMM_HIGH, |
| mark_present, &comp_size)); |
| if (zmm_highbits) { |
| DEBUG_ASSERT(comp_size == kZmmHighSize * kNumSSERegs); |
| for (int i = 0; i < kNumSSERegs; i++) { |
| get_set_memcpy(®s->zmm[i].v[4], &zmm_highbits[i * kZmmHighSize], kZmmHighSize); |
| } |
| } |
| |
| // AVX-512 registers 16-31 (512 bits each) are in component 7. |
| constexpr int kNumZmmHighRegs = 16; // Extra registers added over xmm/ymm. |
| constexpr int kZmmRegSize = 64; // Total register size. |
| uint8_t* zmm_highregs = static_cast<uint8_t*>( |
| x86_get_extended_register_state_component(thread->arch.extended_register_state, |
| X86_XSAVE_STATE_INDEX_AVX512_HIGHERZMM, |
| mark_present, &comp_size)); |
| if (zmm_highregs) { |
| DEBUG_ASSERT(comp_size == kNumZmmHighRegs * kZmmRegSize); |
| for (int i = 0; i < kNumZmmHighRegs; i++) { |
| get_set_memcpy(®s->zmm[i + kNumSSERegs], &zmm_highregs[i * kZmmRegSize], |
| kZmmRegSize); |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| zx_status_t arch_get_general_regs(struct thread* thread, zx_thread_state_general_regs_t* out) { |
| AutoThreadLock lock; |
| |
| // Punt if registers aren't available. E.g., |
| // ZX-563 (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 (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 ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_set_general_regs(struct thread* thread, const zx_thread_state_general_regs_t* in) { |
| AutoThreadLock lock; |
| |
| // Punt if registers aren't available. E.g., |
| // ZX-563 (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 (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 ZX_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 ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_get_single_step(struct thread* thread, bool* single_step) { |
| AutoThreadLock lock; |
| |
| // Punt if registers aren't available. E.g., |
| // ZX-563 (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 (thread->arch.general_regs_source) { |
| case X86_GENERAL_REGS_SYSCALL: |
| flags = &thread->arch.suspended_general_regs.syscall->rflags; |
| break; |
| case X86_GENERAL_REGS_IFRAME: |
| flags = &thread->arch.suspended_general_regs.iframe->flags; |
| break; |
| default: |
| DEBUG_ASSERT(false); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| *single_step = !!(*flags & X86_FLAGS_TF); |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_set_single_step(struct thread* thread, bool single_step) { |
| AutoThreadLock lock; |
| |
| // Punt if registers aren't available. E.g., |
| // ZX-563 (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 (thread->arch.general_regs_source) { |
| case X86_GENERAL_REGS_SYSCALL: |
| flags = &thread->arch.suspended_general_regs.syscall->rflags; |
| break; |
| case X86_GENERAL_REGS_IFRAME: |
| flags = &thread->arch.suspended_general_regs.iframe->flags; |
| break; |
| default: |
| DEBUG_ASSERT(false); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (single_step) { |
| *flags |= X86_FLAGS_TF; |
| } else { |
| *flags &= ~X86_FLAGS_TF; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_get_fp_regs(struct thread* thread, zx_thread_state_fp_regs* out) { |
| // Don't leak any reserved fields. |
| memset(out, 0, sizeof(zx_thread_state_fp_regs)); |
| |
| AutoThreadLock lock; |
| if (thread->state == THREAD_RUNNING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| 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_state, |
| 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[0], &save->st[0], sizeof(out->st)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_set_fp_regs(struct thread* thread, const zx_thread_state_fp_regs* in) { |
| AutoThreadLock lock; |
| if (thread->state == THREAD_RUNNING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| 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_state, |
| 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[0], &in->st[0], sizeof(in->st)); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_get_vector_regs(struct thread* thread, zx_thread_state_vector_regs* out) { |
| return x86_get_set_vector_regs(thread, out, RegAccess::kGet); |
| } |
| |
| zx_status_t arch_set_vector_regs(struct thread* thread, const zx_thread_state_vector_regs* 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*>(in), |
| RegAccess::kSet); |
| } |
| |
| zx_status_t arch_get_extra_regs(struct thread* thread, zx_thread_state_extra_regs* out) { |
| AutoThreadLock lock; |
| if (thread->state == THREAD_RUNNING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| out->fs = thread->arch.fs_base; |
| out->gs = thread->arch.gs_base; |
| return ZX_OK; |
| } |
| |
| zx_status_t arch_set_extra_regs(struct thread* thread, const zx_thread_state_extra_regs* in) { |
| AutoThreadLock lock; |
| if (thread->state == THREAD_RUNNING) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| thread->arch.fs_base = in->fs; |
| thread->arch.gs_base = in->gs; |
| return ZX_OK; |
| } |
| |