aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/arch/x86/um/os-Linux
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/um/os-Linux')
-rw-r--r--arch/x86/um/os-Linux/Makefile2
-rw-r--r--arch/x86/um/os-Linux/mcontext.c234
-rw-r--r--arch/x86/um/os-Linux/registers.c152
-rw-r--r--arch/x86/um/os-Linux/task_size.c151
-rw-r--r--arch/x86/um/os-Linux/tls.c1
5 files changed, 279 insertions, 261 deletions
diff --git a/arch/x86/um/os-Linux/Makefile b/arch/x86/um/os-Linux/Makefile
index 5249bbc30dcd..77a308aaa5ec 100644
--- a/arch/x86/um/os-Linux/Makefile
+++ b/arch/x86/um/os-Linux/Makefile
@@ -3,7 +3,7 @@
# Licensed under the GPL
#
-obj-y = registers.o task_size.o mcontext.o
+obj-y = registers.o mcontext.o
obj-$(CONFIG_X86_32) += tls.o
diff --git a/arch/x86/um/os-Linux/mcontext.c b/arch/x86/um/os-Linux/mcontext.c
index 49c3744cac37..a21403df6663 100644
--- a/arch/x86/um/os-Linux/mcontext.c
+++ b/arch/x86/um/os-Linux/mcontext.c
@@ -1,8 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
-#include <sys/ucontext.h>
#define __FRAME_OFFSETS
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <sys/ucontext.h>
#include <asm/ptrace.h>
+#include <asm/sigcontext.h>
#include <sysdep/ptrace.h>
+#include <sysdep/mcontext.h>
+#include <arch.h>
void get_regs_from_mc(struct uml_pt_regs *regs, mcontext_t *mc)
{
@@ -16,6 +21,10 @@ void get_regs_from_mc(struct uml_pt_regs *regs, mcontext_t *mc)
COPY2(UESP, ESP); /* sic */
COPY(EBX); COPY(EDX); COPY(ECX); COPY(EAX);
COPY(EIP); COPY_SEG_CPL3(CS); COPY(EFL); COPY_SEG_CPL3(SS);
+#undef COPY2
+#undef COPY
+#undef COPY_SEG
+#undef COPY_SEG_CPL3
#else
#define COPY2(X,Y) regs->gp[X/sizeof(unsigned long)] = mc->gregs[REG_##Y]
#define COPY(X) regs->gp[X/sizeof(unsigned long)] = mc->gregs[REG_##X]
@@ -26,7 +35,226 @@ void get_regs_from_mc(struct uml_pt_regs *regs, mcontext_t *mc)
COPY(RIP);
COPY2(EFLAGS, EFL);
COPY2(CS, CSGSFS);
- regs->gp[CS / sizeof(unsigned long)] &= 0xffff;
- regs->gp[CS / sizeof(unsigned long)] |= 3;
+ regs->gp[SS / sizeof(unsigned long)] = mc->gregs[REG_CSGSFS] >> 48;
+#undef COPY2
+#undef COPY
+#endif
+}
+
+void mc_set_rip(void *_mc, void *target)
+{
+ mcontext_t *mc = _mc;
+
+#ifdef __i386__
+ mc->gregs[REG_EIP] = (unsigned long)target;
+#else
+ mc->gregs[REG_RIP] = (unsigned long)target;
#endif
}
+
+/* Same thing, but the copy macros are turned around. */
+void get_mc_from_regs(struct uml_pt_regs *regs, mcontext_t *mc, int single_stepping)
+{
+#ifdef __i386__
+#define COPY2(X,Y) mc->gregs[REG_##Y] = regs->gp[X]
+#define COPY(X) mc->gregs[REG_##X] = regs->gp[X]
+#define COPY_SEG(X) mc->gregs[REG_##X] = regs->gp[X] & 0xffff;
+#define COPY_SEG_CPL3(X) mc->gregs[REG_##X] = (regs->gp[X] & 0xffff) | 3;
+ COPY_SEG(GS); COPY_SEG(FS); COPY_SEG(ES); COPY_SEG(DS);
+ COPY(EDI); COPY(ESI); COPY(EBP);
+ COPY2(UESP, ESP); /* sic */
+ COPY(EBX); COPY(EDX); COPY(ECX); COPY(EAX);
+ COPY(EIP); COPY_SEG_CPL3(CS); COPY(EFL); COPY_SEG_CPL3(SS);
+#else
+#define COPY2(X,Y) mc->gregs[REG_##Y] = regs->gp[X/sizeof(unsigned long)]
+#define COPY(X) mc->gregs[REG_##X] = regs->gp[X/sizeof(unsigned long)]
+ COPY(R8); COPY(R9); COPY(R10); COPY(R11);
+ COPY(R12); COPY(R13); COPY(R14); COPY(R15);
+ COPY(RDI); COPY(RSI); COPY(RBP); COPY(RBX);
+ COPY(RDX); COPY(RAX); COPY(RCX); COPY(RSP);
+ COPY(RIP);
+ COPY2(EFLAGS, EFL);
+ mc->gregs[REG_CSGSFS] = mc->gregs[REG_CSGSFS] & 0xffffffffffffl;
+ mc->gregs[REG_CSGSFS] |= (regs->gp[SS / sizeof(unsigned long)] & 0xffff) << 48;
+#endif
+
+ if (single_stepping)
+ mc->gregs[REG_EFL] |= X86_EFLAGS_TF;
+ else
+ mc->gregs[REG_EFL] &= ~X86_EFLAGS_TF;
+}
+
+#ifdef CONFIG_X86_32
+struct _xstate_64 {
+ struct _fpstate_64 fpstate;
+ struct _header xstate_hdr;
+ struct _ymmh_state ymmh;
+ /* New processor state extensions go here: */
+};
+
+/* Not quite the right structures as these contain more information */
+int um_i387_from_fxsr(struct _fpstate_32 *i387,
+ const struct _fpstate_64 *fxsave);
+int um_fxsr_from_i387(struct _fpstate_64 *fxsave,
+ const struct _fpstate_32 *from);
+#else
+#define _xstate_64 _xstate
+#endif
+
+static struct _fpstate *get_fpstate(struct stub_data *data,
+ mcontext_t *mcontext,
+ int *fp_size)
+{
+ struct _fpstate *res;
+
+ /* Assume floating point registers are on the same page */
+ res = (void *)(((unsigned long)mcontext->fpregs &
+ (UM_KERN_PAGE_SIZE - 1)) +
+ (unsigned long)&data->sigstack[0]);
+
+ if ((void *)res + sizeof(struct _fpstate) >
+ (void *)data->sigstack + sizeof(data->sigstack))
+ return NULL;
+
+ if (res->sw_reserved.magic1 != FP_XSTATE_MAGIC1) {
+ *fp_size = sizeof(struct _fpstate);
+ } else {
+ char *magic2_addr;
+
+ magic2_addr = (void *)res;
+ magic2_addr += res->sw_reserved.extended_size;
+ magic2_addr -= FP_XSTATE_MAGIC2_SIZE;
+
+ /* We still need to be within our stack */
+ if ((void *)magic2_addr >
+ (void *)data->sigstack + sizeof(data->sigstack))
+ return NULL;
+
+ /* If we do not read MAGIC2, then we did something wrong */
+ if (*(__u32 *)magic2_addr != FP_XSTATE_MAGIC2)
+ return NULL;
+
+ /* Remove MAGIC2 from the size, we do not save/restore it */
+ *fp_size = res->sw_reserved.extended_size -
+ FP_XSTATE_MAGIC2_SIZE;
+ }
+
+ return res;
+}
+
+int get_stub_state(struct uml_pt_regs *regs, struct stub_data *data,
+ unsigned long *fp_size_out)
+{
+ mcontext_t *mcontext;
+ struct _fpstate *fpstate_stub;
+ struct _xstate_64 *xstate_stub;
+ int fp_size, xstate_size;
+
+ /* mctx_offset is verified by wait_stub_done_seccomp */
+ mcontext = (void *)&data->sigstack[data->mctx_offset];
+
+ get_regs_from_mc(regs, mcontext);
+
+ fpstate_stub = get_fpstate(data, mcontext, &fp_size);
+ if (!fpstate_stub)
+ return -EINVAL;
+
+#ifdef CONFIG_X86_32
+ xstate_stub = (void *)&fpstate_stub->_fxsr_env;
+ xstate_size = fp_size - offsetof(struct _fpstate_32, _fxsr_env);
+#else
+ xstate_stub = (void *)fpstate_stub;
+ xstate_size = fp_size;
+#endif
+
+ if (fp_size_out)
+ *fp_size_out = xstate_size;
+
+ if (xstate_size > host_fp_size)
+ return -ENOSPC;
+
+ memcpy(&regs->fp, xstate_stub, xstate_size);
+
+ /* We do not need to read the x86_64 FS_BASE/GS_BASE registers as
+ * we do not permit userspace to set them directly.
+ */
+
+#ifdef CONFIG_X86_32
+ /* Read the i387 legacy FP registers */
+ if (um_fxsr_from_i387((void *)&regs->fp, fpstate_stub))
+ return -EINVAL;
+#endif
+
+ return 0;
+}
+
+/* Copied because we cannot include regset.h here. */
+struct task_struct;
+struct user_regset;
+struct membuf {
+ void *p;
+ size_t left;
+};
+
+int fpregs_legacy_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to);
+
+int set_stub_state(struct uml_pt_regs *regs, struct stub_data *data,
+ int single_stepping)
+{
+ mcontext_t *mcontext;
+ struct _fpstate *fpstate_stub;
+ struct _xstate_64 *xstate_stub;
+ int fp_size, xstate_size;
+
+ /* mctx_offset is verified by wait_stub_done_seccomp */
+ mcontext = (void *)&data->sigstack[data->mctx_offset];
+
+ if ((unsigned long)mcontext < (unsigned long)data->sigstack ||
+ (unsigned long)mcontext >
+ (unsigned long) data->sigstack +
+ sizeof(data->sigstack) - sizeof(*mcontext))
+ return -EINVAL;
+
+ get_mc_from_regs(regs, mcontext, single_stepping);
+
+ fpstate_stub = get_fpstate(data, mcontext, &fp_size);
+ if (!fpstate_stub)
+ return -EINVAL;
+
+#ifdef CONFIG_X86_32
+ xstate_stub = (void *)&fpstate_stub->_fxsr_env;
+ xstate_size = fp_size - offsetof(struct _fpstate_32, _fxsr_env);
+#else
+ xstate_stub = (void *)fpstate_stub;
+ xstate_size = fp_size;
+#endif
+
+ memcpy(xstate_stub, &regs->fp, xstate_size);
+
+#ifdef __i386__
+ /*
+ * On x86, the GDT entries are updated by arch_set_tls.
+ */
+
+ /* Store the i387 legacy FP registers which the host will use */
+ if (um_i387_from_fxsr(fpstate_stub, (void *)&regs->fp))
+ return -EINVAL;
+#else
+ /*
+ * On x86_64, we need to sync the FS_BASE/GS_BASE registers using the
+ * arch specific data.
+ */
+ if (data->arch_data.fs_base != regs->gp[FS_BASE / sizeof(unsigned long)]) {
+ data->arch_data.fs_base = regs->gp[FS_BASE / sizeof(unsigned long)];
+ data->arch_data.sync |= STUB_SYNC_FS_BASE;
+ }
+ if (data->arch_data.gs_base != regs->gp[GS_BASE / sizeof(unsigned long)]) {
+ data->arch_data.gs_base = regs->gp[GS_BASE / sizeof(unsigned long)];
+ data->arch_data.sync |= STUB_SYNC_GS_BASE;
+ }
+#endif
+
+ return 0;
+}
diff --git a/arch/x86/um/os-Linux/registers.c b/arch/x86/um/os-Linux/registers.c
index df8f4b4bf98b..eb1cdadc8a61 100644
--- a/arch/x86/um/os-Linux/registers.c
+++ b/arch/x86/um/os-Linux/registers.c
@@ -16,132 +16,72 @@
#include <asm/sigcontext.h>
#include <linux/elf.h>
#include <registers.h>
+#include <sys/mman.h>
-int have_xstate_support;
+static unsigned long ptrace_regset;
+unsigned long host_fp_size;
-int save_i387_registers(int pid, unsigned long *fp_regs)
-{
- if (ptrace(PTRACE_GETFPREGS, pid, 0, fp_regs) < 0)
- return -errno;
- return 0;
-}
-
-int save_fp_registers(int pid, unsigned long *fp_regs)
+int get_fp_registers(int pid, unsigned long *regs)
{
-#ifdef PTRACE_GETREGSET
- struct iovec iov;
+ struct iovec iov = {
+ .iov_base = regs,
+ .iov_len = host_fp_size,
+ };
- if (have_xstate_support) {
- iov.iov_base = fp_regs;
- iov.iov_len = FP_SIZE * sizeof(unsigned long);
- if (ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov) < 0)
- return -errno;
- return 0;
- } else
-#endif
- return save_i387_registers(pid, fp_regs);
-}
-
-int restore_i387_registers(int pid, unsigned long *fp_regs)
-{
- if (ptrace(PTRACE_SETFPREGS, pid, 0, fp_regs) < 0)
+ if (ptrace(PTRACE_GETREGSET, pid, ptrace_regset, &iov) < 0)
return -errno;
return 0;
}
-int restore_fp_registers(int pid, unsigned long *fp_regs)
-{
-#ifdef PTRACE_SETREGSET
- struct iovec iov;
- if (have_xstate_support) {
- iov.iov_base = fp_regs;
- iov.iov_len = FP_SIZE * sizeof(unsigned long);
- if (ptrace(PTRACE_SETREGSET, pid, NT_X86_XSTATE, &iov) < 0)
- return -errno;
- return 0;
- } else
-#endif
- return restore_i387_registers(pid, fp_regs);
-}
-
-#ifdef __i386__
-int have_fpx_regs = 1;
-int save_fpx_registers(int pid, unsigned long *fp_regs)
+int put_fp_registers(int pid, unsigned long *regs)
{
- if (ptrace(PTRACE_GETFPXREGS, pid, 0, fp_regs) < 0)
- return -errno;
- return 0;
-}
+ struct iovec iov = {
+ .iov_base = regs,
+ .iov_len = host_fp_size,
+ };
-int restore_fpx_registers(int pid, unsigned long *fp_regs)
-{
- if (ptrace(PTRACE_SETFPXREGS, pid, 0, fp_regs) < 0)
+ if (ptrace(PTRACE_SETREGSET, pid, ptrace_regset, &iov) < 0)
return -errno;
return 0;
}
-int get_fp_registers(int pid, unsigned long *regs)
-{
- if (have_fpx_regs)
- return save_fpx_registers(pid, regs);
- else
- return save_fp_registers(pid, regs);
-}
-
-int put_fp_registers(int pid, unsigned long *regs)
-{
- if (have_fpx_regs)
- return restore_fpx_registers(pid, regs);
- else
- return restore_fp_registers(pid, regs);
-}
-
-void arch_init_registers(int pid)
+int arch_init_registers(int pid)
{
- struct user_fpxregs_struct fpx_regs;
- int err;
-
- err = ptrace(PTRACE_GETFPXREGS, pid, 0, &fpx_regs);
- if (!err)
- return;
-
- if (errno != EIO)
- panic("check_ptrace : PTRACE_GETFPXREGS failed, errno = %d",
- errno);
-
- have_fpx_regs = 0;
-}
+ struct iovec iov = {
+ /* Just use plenty of space, it does not cost us anything */
+ .iov_len = 2 * 1024 * 1024,
+ };
+ int ret;
+
+ iov.iov_base = mmap(NULL, iov.iov_len, PROT_WRITE | PROT_READ,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (iov.iov_base == MAP_FAILED)
+ return -ENOMEM;
+
+ /* GDB has x86_xsave_length, which uses x86_cpuid_count */
+ ptrace_regset = NT_X86_XSTATE;
+ ret = ptrace(PTRACE_GETREGSET, pid, ptrace_regset, &iov);
+ if (ret)
+ ret = -errno;
+
+ if (ret == -ENODEV) {
+#ifdef CONFIG_X86_32
+ ptrace_regset = NT_PRXFPREG;
#else
+ ptrace_regset = NT_PRFPREG;
+#endif
+ iov.iov_len = 2 * 1024 * 1024;
+ ret = ptrace(PTRACE_GETREGSET, pid, ptrace_regset, &iov);
+ if (ret)
+ ret = -errno;
+ }
-int get_fp_registers(int pid, unsigned long *regs)
-{
- return save_fp_registers(pid, regs);
-}
-
-int put_fp_registers(int pid, unsigned long *regs)
-{
- return restore_fp_registers(pid, regs);
-}
-
-void arch_init_registers(int pid)
-{
-#ifdef PTRACE_GETREGSET
- void * fp_regs;
- struct iovec iov;
-
- fp_regs = malloc(FP_SIZE * sizeof(unsigned long));
- if(fp_regs == NULL)
- return;
+ munmap(iov.iov_base, 2 * 1024 * 1024);
- iov.iov_base = fp_regs;
- iov.iov_len = FP_SIZE * sizeof(unsigned long);
- if (ptrace(PTRACE_GETREGSET, pid, NT_X86_XSTATE, &iov) == 0)
- have_xstate_support = 1;
+ host_fp_size = iov.iov_len;
- free(fp_regs);
-#endif
+ return ret;
}
-#endif
unsigned long get_thread_reg(int reg, jmp_buf *buf)
{
diff --git a/arch/x86/um/os-Linux/task_size.c b/arch/x86/um/os-Linux/task_size.c
deleted file mode 100644
index 1dc9adc20b1c..000000000000
--- a/arch/x86/um/os-Linux/task_size.c
+++ /dev/null
@@ -1,151 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-#include <stdio.h>
-#include <stdlib.h>
-#include <signal.h>
-#include <sys/mman.h>
-#include <longjmp.h>
-
-#ifdef __i386__
-
-static jmp_buf buf;
-
-static void segfault(int sig)
-{
- longjmp(buf, 1);
-}
-
-static int page_ok(unsigned long page)
-{
- unsigned long *address = (unsigned long *) (page << UM_KERN_PAGE_SHIFT);
- unsigned long n = ~0UL;
- void *mapped = NULL;
- int ok = 0;
-
- /*
- * First see if the page is readable. If it is, it may still
- * be a VDSO, so we go on to see if it's writable. If not
- * then try mapping memory there. If that fails, then we're
- * still in the kernel area. As a sanity check, we'll fail if
- * the mmap succeeds, but gives us an address different from
- * what we wanted.
- */
- if (setjmp(buf) == 0)
- n = *address;
- else {
- mapped = mmap(address, UM_KERN_PAGE_SIZE,
- PROT_READ | PROT_WRITE,
- MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- if (mapped == MAP_FAILED)
- return 0;
- if (mapped != address)
- goto out;
- }
-
- /*
- * Now, is it writeable? If so, then we're in user address
- * space. If not, then try mprotecting it and try the write
- * again.
- */
- if (setjmp(buf) == 0) {
- *address = n;
- ok = 1;
- goto out;
- } else if (mprotect(address, UM_KERN_PAGE_SIZE,
- PROT_READ | PROT_WRITE) != 0)
- goto out;
-
- if (setjmp(buf) == 0) {
- *address = n;
- ok = 1;
- }
-
- out:
- if (mapped != NULL)
- munmap(mapped, UM_KERN_PAGE_SIZE);
- return ok;
-}
-
-unsigned long os_get_top_address(void)
-{
- struct sigaction sa, old;
- unsigned long bottom = 0;
- /*
- * A 32-bit UML on a 64-bit host gets confused about the VDSO at
- * 0xffffe000. It is mapped, is readable, can be reprotected writeable
- * and written. However, exec discovers later that it can't be
- * unmapped. So, just set the highest address to be checked to just
- * below it. This might waste some address space on 4G/4G 32-bit
- * hosts, but shouldn't hurt otherwise.
- */
- unsigned long top = 0xffffd000 >> UM_KERN_PAGE_SHIFT;
- unsigned long test, original;
-
- printf("Locating the bottom of the address space ... ");
- fflush(stdout);
-
- /*
- * We're going to be longjmping out of the signal handler, so
- * SA_DEFER needs to be set.
- */
- sa.sa_handler = segfault;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_NODEFER;
- if (sigaction(SIGSEGV, &sa, &old)) {
- perror("os_get_top_address");
- exit(1);
- }
-
- /* Manually scan the address space, bottom-up, until we find
- * the first valid page (or run out of them).
- */
- for (bottom = 0; bottom < top; bottom++) {
- if (page_ok(bottom))
- break;
- }
-
- /* If we've got this far, we ran out of pages. */
- if (bottom == top) {
- fprintf(stderr, "Unable to determine bottom of address "
- "space.\n");
- exit(1);
- }
-
- printf("0x%lx\n", bottom << UM_KERN_PAGE_SHIFT);
- printf("Locating the top of the address space ... ");
- fflush(stdout);
-
- original = bottom;
-
- /* This could happen with a 4G/4G split */
- if (page_ok(top))
- goto out;
-
- do {
- test = bottom + (top - bottom) / 2;
- if (page_ok(test))
- bottom = test;
- else
- top = test;
- } while (top - bottom > 1);
-
-out:
- /* Restore the old SIGSEGV handling */
- if (sigaction(SIGSEGV, &old, NULL)) {
- perror("os_get_top_address");
- exit(1);
- }
- top <<= UM_KERN_PAGE_SHIFT;
- printf("0x%lx\n", top);
-
- return top;
-}
-
-#else
-
-unsigned long os_get_top_address(void)
-{
- /* The old value of CONFIG_TOP_ADDR */
- return 0x7fc0002000;
-}
-
-#endif
diff --git a/arch/x86/um/os-Linux/tls.c b/arch/x86/um/os-Linux/tls.c
index 3e1b1bf6acbc..eed9efe29ade 100644
--- a/arch/x86/um/os-Linux/tls.c
+++ b/arch/x86/um/os-Linux/tls.c
@@ -6,6 +6,7 @@
#include <sys/syscall.h>
#include <unistd.h>
+#include <os.h>
#include <sysdep/tls.h>
#ifndef PTRACE_GET_THREAD_AREA