Arch64: Clear VFP state on execve()

I noticed that many of the math-related tests were failing on AArch64.
After a lot of debugging, I noticed that the floating point exception flags
were not being reset when starting a new process. This change resets the
VFP inside exec_setregs() to ensure no VFP register state is leaked from
parent processes to children.

This commit also moves the clearing of fpcr that was added in 65618fdda0
from fork() to execve() since that makes more sense: fork() can retain
current register values, but execve() should result in a well-defined
clean state.

Reviewed By:	andrew
MFC after:	1 week
Differential Revision: https://reviews.freebsd.org/D29060
This commit is contained in:
Alex Richardson 2021-03-09 19:11:40 +00:00
parent dfb33cb0ef
commit 953a7d7c61
6 changed files with 44 additions and 1 deletions

View File

@ -51,6 +51,9 @@ __FBSDID("$FreeBSD$");
#include <sys/vnode.h>
#include <machine/elf.h>
#ifdef VFP
#include <machine/vfp.h>
#endif
#include <compat/freebsd32/freebsd32_util.h>
@ -251,6 +254,10 @@ freebsd32_setregs(struct thread *td, struct image_params *imgp,
tf->tf_x[14] = imgp->entry_addr;
tf->tf_elr = imgp->entry_addr;
tf->tf_spsr = PSR_M_32;
#ifdef VFP
vfp_reset_state(td, td->td_pcb);
#endif
}
void

View File

@ -552,6 +552,7 @@ void
exec_setregs(struct thread *td, struct image_params *imgp, uintptr_t stack)
{
struct trapframe *tf = td->td_frame;
struct pcb *pcb = td->td_pcb;
memset(tf, 0, sizeof(struct trapframe));
@ -559,6 +560,12 @@ exec_setregs(struct thread *td, struct image_params *imgp, uintptr_t stack)
tf->tf_sp = STACKALIGN(stack);
tf->tf_lr = imgp->entry_addr;
tf->tf_elr = imgp->entry_addr;
#ifdef VFP
vfp_reset_state(td, pcb);
#endif
/* TODO: Shouldn't we also reset pcb_dbg_regs? */
}
/* Sanity check these are the same size, they will be memcpy'd to and fro */

View File

@ -33,6 +33,7 @@ __FBSDID("$FreeBSD$");
#ifdef VFP
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/limits.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/pcpu.h>
@ -199,6 +200,26 @@ vfp_save_state(struct thread *td, struct pcb *pcb)
critical_exit();
}
/*
* Reset the FP state to avoid leaking state from the parent process across
* execve() (and to ensure that we get a consistent floating point environment
* in every new process).
*/
void
vfp_reset_state(struct thread *td, struct pcb *pcb)
{
critical_enter();
bzero(&pcb->pcb_fpustate.vfp_regs, sizeof(pcb->pcb_fpustate.vfp_regs));
KASSERT(pcb->pcb_fpusaved == &pcb->pcb_fpustate,
("pcb_fpusaved should point to pcb_fpustate."));
pcb->pcb_fpustate.vfp_fpcr = initial_fpcr;
pcb->pcb_fpustate.vfp_fpsr = 0;
pcb->pcb_vfpcpu = UINT_MAX;
pcb->pcb_fpflags = 0;
vfp_discard(td);
critical_exit();
}
void
vfp_restore_state(void)
{

View File

@ -108,7 +108,6 @@ cpu_fork(struct thread *td1, struct proc *p2, struct thread *td2, int flags)
td2->td_pcb->pcb_sp = (uintptr_t)td2->td_frame;
td2->td_pcb->pcb_fpusaved = &td2->td_pcb->pcb_fpustate;
td2->td_pcb->pcb_vfpcpu = UINT_MAX;
td2->td_pcb->pcb_fpusaved->vfp_fpcr = initial_fpcr;
/* Setup to release spin count in fork_exit(). */
td2->td_md.md_spinlock_count = 1;

View File

@ -68,6 +68,7 @@ struct thread;
void vfp_init(void);
void vfp_discard(struct thread *);
void vfp_reset_state(struct thread *, struct pcb *);
void vfp_restore_state(void);
void vfp_save_state(struct thread *, struct pcb *);

View File

@ -57,6 +57,10 @@ __FBSDID("$FreeBSD$");
#include <compat/linux/linux_util.h>
#include <compat/linux/linux_vdso.h>
#ifdef VFP
#include <machine/vfp.h>
#endif
MODULE_VERSION(linux64elf, 1);
const char *linux_kplatform;
@ -360,6 +364,10 @@ linux_exec_setregs(struct thread *td, struct image_params *imgp,
regs->tf_lr = 0xffffffffffffffff;
#endif
regs->tf_elr = imgp->entry_addr;
#ifdef VFP
vfp_reset_state(td, td->td_pcb);
#endif
}
int