ITADN

[ARM64_DYNAREC] FSIN/FCOS/FSCALE corrupt host FPCR when DYNAREC_FASTROUND=0

#3932Closedqilin-llvm-v2 创建于 2026-06-08
Q
qilin-llvm-v2commented
您好! 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 条评论