summaryrefslogtreecommitdiffstats
path: root/arch/x86/um/ptrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/x86/um/ptrace.c')
-rw-r--r--arch/x86/um/ptrace.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/arch/x86/um/ptrace.c b/arch/x86/um/ptrace.c
new file mode 100644
index 000000000000..57c504fd5626
--- /dev/null
+++ b/arch/x86/um/ptrace.c
@@ -0,0 +1,267 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/sched.h>
+#include <linux/elf.h>
+#include <linux/regset.h>
+#include <asm/user32.h>
+#include <asm/sigcontext.h>
+
+#ifdef CONFIG_X86_32
+/*
+ * FPU tag word conversions.
+ */
+
+static inline unsigned short twd_i387_to_fxsr(unsigned short twd)
+{
+ unsigned int tmp; /* to avoid 16 bit prefixes in the code */
+
+ /* Transform each pair of bits into 01 (valid) or 00 (empty) */
+ tmp = ~twd;
+ tmp = (tmp | (tmp>>1)) & 0x5555; /* 0V0V0V0V0V0V0V0V */
+ /* and move the valid bits to the lower byte. */
+ tmp = (tmp | (tmp >> 1)) & 0x3333; /* 00VV00VV00VV00VV */
+ tmp = (tmp | (tmp >> 2)) & 0x0f0f; /* 0000VVVV0000VVVV */
+ tmp = (tmp | (tmp >> 4)) & 0x00ff; /* 00000000VVVVVVVV */
+ return tmp;
+}
+
+static inline unsigned long twd_fxsr_to_i387(struct user_fxsr_struct *fxsave)
+{
+ struct _fpxreg *st = NULL;
+ unsigned long twd = (unsigned long) fxsave->twd;
+ unsigned long tag;
+ unsigned long ret = 0xffff0000;
+ int i;
+
+#define FPREG_ADDR(f, n) ((char *)&(f)->st_space + (n) * 16)
+
+ for (i = 0; i < 8; i++) {
+ if (twd & 0x1) {
+ st = (struct _fpxreg *) FPREG_ADDR(fxsave, i);
+
+ switch (st->exponent & 0x7fff) {
+ case 0x7fff:
+ tag = 2; /* Special */
+ break;
+ case 0x0000:
+ if (!st->significand[0] &&
+ !st->significand[1] &&
+ !st->significand[2] &&
+ !st->significand[3]) {
+ tag = 1; /* Zero */
+ } else {
+ tag = 2; /* Special */
+ }
+ break;
+ default:
+ if (st->significand[3] & 0x8000)
+ tag = 0; /* Valid */
+ else
+ tag = 2; /* Special */
+ break;
+ }
+ } else {
+ tag = 3; /* Empty */
+ }
+ ret |= (tag << (2 * i));
+ twd = twd >> 1;
+ }
+ return ret;
+}
+
+/* Get/set the old 32bit i387 registers (pre-FPX) */
+static int fpregs_legacy_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ struct user_fxsr_struct *fxsave = (void *)target->thread.regs.regs.fp;
+ int i;
+
+ membuf_store(&to, (unsigned long)fxsave->cwd | 0xffff0000ul);
+ membuf_store(&to, (unsigned long)fxsave->swd | 0xffff0000ul);
+ membuf_store(&to, twd_fxsr_to_i387(fxsave));
+ membuf_store(&to, fxsave->fip);
+ membuf_store(&to, fxsave->fcs | ((unsigned long)fxsave->fop << 16));
+ membuf_store(&to, fxsave->foo);
+ membuf_store(&to, fxsave->fos);
+
+ for (i = 0; i < 8; i++)
+ membuf_write(&to, (void *)fxsave->st_space + i * 16, 10);
+
+ return 0;
+}
+
+static int fpregs_legacy_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf, const void __user *ubuf)
+{
+ struct user_fxsr_struct *fxsave = (void *)target->thread.regs.regs.fp;
+ const struct user_i387_struct *from;
+ struct user_i387_struct buf;
+ int i;
+
+ if (ubuf) {
+ if (copy_from_user(&buf, ubuf, sizeof(buf)))
+ return -EFAULT;
+ from = &buf;
+ } else {
+ from = kbuf;
+ }
+
+ fxsave->cwd = (unsigned short)(from->cwd & 0xffff);
+ fxsave->swd = (unsigned short)(from->swd & 0xffff);
+ fxsave->twd = twd_i387_to_fxsr((unsigned short)(from->twd & 0xffff));
+ fxsave->fip = from->fip;
+ fxsave->fop = (unsigned short)((from->fcs & 0xffff0000ul) >> 16);
+ fxsave->fcs = (from->fcs & 0xffff);
+ fxsave->foo = from->foo;
+ fxsave->fos = from->fos;
+
+ for (i = 0; i < 8; i++) {
+ memcpy((void *)fxsave->st_space + i * 16,
+ (void *)from->st_space + i * 10, 10);
+ }
+
+ return 0;
+}
+#endif
+
+static int genregs_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ int reg;
+
+ for (reg = 0; to.left; reg++)
+ membuf_store(&to, getreg(target, reg * sizeof(unsigned long)));
+ return 0;
+}
+
+static int genregs_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf, const void __user *ubuf)
+{
+ int ret = 0;
+
+ if (kbuf) {
+ const unsigned long *k = kbuf;
+
+ while (count >= sizeof(*k) && !ret) {
+ ret = putreg(target, pos, *k++);
+ count -= sizeof(*k);
+ pos += sizeof(*k);
+ }
+ } else {
+ const unsigned long __user *u = ubuf;
+
+ while (count >= sizeof(*u) && !ret) {
+ unsigned long word;
+
+ ret = __get_user(word, u++);
+ if (ret)
+ break;
+ ret = putreg(target, pos, word);
+ count -= sizeof(*u);
+ pos += sizeof(*u);
+ }
+ }
+ return ret;
+}
+
+static int generic_fpregs_active(struct task_struct *target, const struct user_regset *regset)
+{
+ return regset->n;
+}
+
+static int generic_fpregs_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ void *fpregs = task_pt_regs(target)->regs.fp;
+
+ membuf_write(&to, fpregs, regset->size * regset->n);
+ return 0;
+}
+
+static int generic_fpregs_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf, const void __user *ubuf)
+{
+ void *fpregs = task_pt_regs(target)->regs.fp;
+
+ return user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+ fpregs, 0, regset->size * regset->n);
+}
+
+static struct user_regset uml_regsets[] __ro_after_init = {
+ [REGSET_GENERAL] = {
+ .core_note_type = NT_PRSTATUS,
+ .n = sizeof(struct user_regs_struct) / sizeof(long),
+ .size = sizeof(long),
+ .align = sizeof(long),
+ .regset_get = genregs_get,
+ .set = genregs_set
+ },
+#ifdef CONFIG_X86_32
+ /* Old FP registers, they are needed in signal frames */
+ [REGSET_FP_LEGACY] = {
+ .core_note_type = NT_PRFPREG,
+ .n = sizeof(struct user_i387_ia32_struct) / sizeof(long),
+ .size = sizeof(long),
+ .align = sizeof(long),
+ .active = generic_fpregs_active,
+ .regset_get = fpregs_legacy_get,
+ .set = fpregs_legacy_set,
+ },
+#endif
+ [REGSET_FP] = {
+#ifdef CONFIG_X86_32
+ .core_note_type = NT_PRXFPREG,
+ .n = sizeof(struct user32_fxsr_struct) / sizeof(long),
+#else
+ .core_note_type = NT_PRFPREG,
+ .n = sizeof(struct user_i387_struct) / sizeof(long),
+#endif
+ .size = sizeof(long),
+ .align = sizeof(long),
+ .active = generic_fpregs_active,
+ .regset_get = generic_fpregs_get,
+ .set = generic_fpregs_set,
+ },
+ [REGSET_XSTATE] = {
+ .core_note_type = NT_X86_XSTATE,
+ .size = sizeof(long),
+ .align = sizeof(long),
+ .active = generic_fpregs_active,
+ .regset_get = generic_fpregs_get,
+ .set = generic_fpregs_set,
+ },
+ /* TODO: Add TLS regset for 32bit */
+};
+
+static const struct user_regset_view user_uml_view = {
+#ifdef CONFIG_X86_32
+ .name = "i386", .e_machine = EM_386,
+#else
+ .name = "x86_64", .e_machine = EM_X86_64,
+#endif
+ .regsets = uml_regsets, .n = ARRAY_SIZE(uml_regsets)
+};
+
+const struct user_regset_view *
+task_user_regset_view(struct task_struct *tsk)
+{
+ return &user_uml_view;
+}
+
+static int __init init_regset_xstate_info(void)
+{
+ uml_regsets[REGSET_XSTATE].n =
+ host_fp_size / uml_regsets[REGSET_XSTATE].size;
+
+ return 0;
+}
+arch_initcall(init_regset_xstate_info);