Bug 250043

Summary: ptrace() GETFPREGS/SETFPREGS uses 32-bit version of *XSAVE*/*XRSTOR* truncating FIP/FDP
Product: Base System Reporter: Michał Górny <mgorny>
Component: kernAssignee: Konstantin Belousov <kib>
Status: Closed FIXED    
Severity: Affects Only Me CC: emaste, jhb, kib, markj
Priority: ---    
Version: Unspecified   
Hardware: amd64   
OS: Any   

Description Michał Górny 2020-10-02 06:44:35 UTC
This is a problem discovered while working on new FreeBSD plugin for LLDB, as contracted to Moritz Systems by FreeBSD Foundation.

TL;DR: We'd need to replace 'plain' *xsave* and *xrstor* calls used to populate GETFPREGS/SETFPREGS on amd64 (or possibly all of them) with *xsave*64 and *xrstor*64.  This changes the FIP/FDP representation inside the struct not to be truncated to 32 bits (without changing anything else).


Long version:

The fxsave, xsave... and fxrstor, xrstor... instructions have two variants on amd64.  The variant without prefix uses a 32-bit compatible x87 register dump structure while the variant prefixed by rex.w=1 uses a 64-bit x87 register dump.  The latter can be done in gas by appending '64' suffix to the command, e.g. fxsave64.

The only difference in these two variants is how FIP/FDP registers are written.  In the 32-bit compatible variant, they are written as a 16-bit segment register (FCS/FDS) and an actual pointer truncated to 32 bits (FIP[31:0], FDP[31:0]), plus 16 bits of padding.  In the 64-bit version, they are written as full 64-bit register instead.

FreeBSD currently calls the 32-bit compatible variant of instructions.  This is problematic because it means that pointers exceeding 2^32 are truncated.  Switching to the 64-bit variant would solve that.

This is a potentially breaking change but we don't think it is likely to break anything.  The FIP/FDP registers are used to locate instruction and its memory operand (if any) when software handling of x87 exceptions is used.  We don't think modern debuggers are used often in that context and even if they are, the current state is broken as they get a truncated pointer.

We'd lose access to respective segment registers but unless I'm mistaken they aren't really used much these days.

The problem also affects current versions of gdb.  Linux gdb and lldb are using the 64-bit variant.  Although it is displayed split as fiseg/fioff, foseg/fooff, the *seg register does not contain the 16-bit segment but instead FIP[63:32], FDP[63:32].
Comment 1 Konstantin Belousov freebsd_committer freebsd_triage 2020-10-02 14:33:40 UTC
https://reviews.freebsd.org/D26643
Comment 2 commit-hook freebsd_committer freebsd_triage 2020-10-03 23:17:34 UTC
A commit references this bug:

Author: kib
Date: Sat Oct  3 23:17:30 UTC 2020
New revision: 366417
URL: https://svnweb.freebsd.org/changeset/base/366417

Log:
  amd64: Store full 64bit of FIP/FDP for 64bit processes when using XSAVE.

  If current process is 64bit, use rex-prefixed version of XSAVE
  (XSAVE64).  If current process is 32bit and CPU supports saving
  segment registers cs/ds in the FPU save area, use non-prefixed variant
  of XSAVE.

  Reported and tested by:	Micha? G?rny <mgorny@mgorny@moritz.systems>
  PR:	250043
  Reviewed by:	emaste, markj
  Sponsored by:	The FreeBSD Foundation
  MFC after:	1 week
  Differential revision:	https://reviews.freebsd.org/D26643

Changes:
  head/sys/amd64/amd64/cpu_switch.S
  head/sys/amd64/amd64/fpu.c
  head/sys/amd64/include/md_var.h
Comment 3 commit-hook freebsd_committer freebsd_triage 2020-10-10 13:47:28 UTC
A commit references this bug:

Author: kib
Date: Sat Oct 10 13:46:48 UTC 2020
New revision: 366614
URL: https://svnweb.freebsd.org/changeset/base/366614

Log:
  MFC r366417:
  amd64: Store full 64bit of FIP/FDP for 64bit processes when using XSAVE.

  PR:	250043

Changes:
_U  stable/12/
  stable/12/sys/amd64/amd64/cpu_switch.S
  stable/12/sys/amd64/amd64/fpu.c
  stable/12/sys/amd64/include/md_var.h