1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/arch/x86/um/ptrace.c
Benjamin Berg 3f17fed214 um: switch to regset API and depend on XSTATE
The PTRACE_GETREGSET API has now existed since Linux 2.6.33. The XSAVE
CPU feature should also be sufficiently common to be able to rely on it.

With this, define our internal FP state to be the hosts XSAVE data. Add
discovery for the hosts XSAVE size and place the FP registers at the end
of task_struct so that we can adjust the size at runtime.

Next we can implement the regset API on top and update the signal
handling as well as ptrace APIs to use them. Also switch coredump
creation to use the regset API and finally set HAVE_ARCH_TRACEHOOK.

This considerably improves the signal frames. Previously they might not
have contained all the registers (i386) and also did not have the
sizes and magic values set to the correct values to permit userspace to
decode the frame.

As a side effect, this will permit UML to run on hosts with newer CPU
extensions (such as AMX) that need even more register state.

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Link: https://patch.msgid.link/20241023094120.4083426-1-benjamin@sipsolutions.net
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2024-10-23 12:13:16 +02:00

267 lines
6.6 KiB
C

// 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 */
};
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);