[ARM64_DYNAREC] FSIN/FCOS/FSCALE corrupt host FPCR when DYNAREC_FASTROUND=0
您好! I have just a done many rounds of intensive code review of the box64 source codes and found many divergences between the interpreters and LA64/ARM64 JIT backends.
## Environment
- Host: aarch64 Linux (128 cores), glibc
- box64: reproduced on v0.4.1 release and current main
- Guest: statically-linked test ELF (attached [box64-fpcr-repro.zip](https://github.com/user-attachments/files/28689879/box64-fpcr-repro.zip)), generated by a random instruction-stream differential fuzzer I run against the dynarec
## Summary
When `dynarec_fastround` is disabled, the arm64 dynarec implementations of `FSIN`, `FCOS` and `FSCALE` save the host FPCR in scratch register x4 via `x87_setround()`, then call the C helper through `CALL_D(...)` **without passing that register in the sav1 slot** (it passes -1). x4 is caller-saved in the AArch64 ABI, so libm clobbers it, and `x87_restoreround()` then writes whatever libm left in x4 back into FPCR.
From that point the process runs with a corrupted host FPCR. In my runs the garbage value had bit 8 (IOE, invalid-op trap enable) set, so a later NEON comparison involving a NaN (an `fcmgt` emitted for a guest `cmpps`) raised a trapped FP exception → fatal SIGFPE. Depending on the garbage it could instead silently change rounding mode or FZ/AH for the rest of the process. The corruption never self-heals: all other FPCR writes in box64 are read-modify-write of the rounding/FZ bits only.
The same file shows the intended pattern: `FPTAN`, `FPATAN` and `FSINCOS` pass `u8` through the save slot. `FSCALE`/`FSIN`/`FCOS` are the three sites that miss it:
- correct: `src/dynarec/arm64/dynarec_arm64_d9.c` cases 0xF2 (`CALL_D(..., u8, -1)`),
0xF3, 0xF4 (`CALL_(..., u8)`)
- broken: cases 0xFD/0xFE/0xFF (`CALL_D(..., -1, -1)`)
(Note: when fastround is on, `u8` is a stale local in these cases, so the fix also needs FPTAN's `else u8 = 0;`.)
## Reproduction
Attached: `seed-249.elf` (+ disassembly `seed-249.asm`). The program is a linear run of register-only instructions ending in exit(0); it runs clean under `BOX64_DYNAREC=0`.
```
# crashes (test mode forces fastround=0):
BOX64_DYNAREC_TEST=1 box64 seed-249.elf # SIGFPE, rc=136
# also crashes with the production setting alone:
BOX64_DYNAREC_FASTROUND=0 box64 seed-249.elf # SIGFPE, rc=136
```
gdb at the fault: pc is inside a dynablock on `fcmgt v7.4s, v16.4s, v7.4s` with `fpcr=0x100 [IOE]`, `fpsr=[IOC ...]`. Nothing in box64 ever sets IOE.
## Impact
Any configuration with `dynarec_fastround=0` and guest code using fsin/fcos/fscale:
- `BOX64_DYNAREC_TEST=1`
- `BOX64_PROFILE=safest`
- the bundled `system/box64.box64rc` profiles that set `BOX64_DYNAREC_FASTROUND=0` (steamwebhelper, factorio, OxygenNotIncluded, Soma, weixin, Cyberpunk2077.exe, DXMD.exe, ForzaHorizon4.exe, MassEffect.exe, ...)
Default settings (fastround=1) are not affected.
## Suggested fix
```diff
--- a/src/dynarec/arm64/dynarec_arm64_d9.c
+++ b/src/dynarec/arm64/dynarec_arm64_d9.c
@@ -456,7 +456,9 @@ uintptr_t dynarec64_D9(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin
v2 = x87_get_st(dyn, ninst, x1, x2, 1, NEON_CACHE_ST_D);
if(!BOX64ENV(dynarec_fastround))
u8 = x87_setround(dyn, ninst, x1, x2, x4);
- CALL_D(const_direct_fscale, v1, v1, v2, -1, -1);
+ else
+ u8 = 0;
+ CALL_D(const_direct_fscale, v1, v1, v2, u8, -1);
if(!BOX64ENV(dynarec_fastround))
x87_restoreround(dyn, ninst, u8);
break;
@@ -466,7 +468,9 @@ uintptr_t dynarec64_D9(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin
v1 = x87_get_st(dyn, ninst, x1, x2, 0, NEON_CACHE_ST_D);
if(!BOX64ENV(dynarec_fastround))
u8 = x87_setround(dyn, ninst, x1, x2, x4);
- CALL_D(const_direct_fsin, v1, v1, -1, -1, -1);
+ else
+ u8 = 0;
+ CALL_D(const_direct_fsin, v1, v1, -1, u8, -1);
if(!BOX64ENV(dynarec_fastround))
x87_restoreround(dyn, ninst, u8);
break;
@@ -476,7 +480,9 @@ uintptr_t dynarec64_D9(dynarec_arm_t* dyn, uintptr_t addr, uintptr_t ip, int nin
v1 = x87_get_st(dyn, ninst, x1, x2, 0, NEON_CACHE_ST_D);
if(!BOX64ENV(dynarec_fastround))
u8 = x87_setround(dyn, ninst, x1, x2, x4);
- CALL_D(const_direct_fcos, v1, v1, -1, -1, -1);
+ else
+ u8 = 0;
+ CALL_D(const_direct_fcos, v1, v1, -1, u8, -1);
if(!BOX64ENV(dynarec_fastround))
x87_restoreround(dyn, ninst, u8);
break;
```
关闭于 2026-06-08 1 条评论