From cf0e5f1b8a968e8a65f2ca4597dccb84d981ab94 Mon Sep 17 00:00:00 2001 From: not-matthias Date: Mon, 2 Nov 2026 09:22:16 +0100 Subject: [PATCH 1/2] fix(vex): decode arm64 MRS of all EL0-visible ID registers Linux emulates EL0 reads of the ID registers in Op0=3, Op1=0, CRn=0, CRm=2..7 and returns 0 for registers it does not track, so these reads never trap natively. VEX decoded only some of them and raised SIGILL for the rest, e.g. ID_AA64FPFR0_EL1, which NumKong reads at import time. Return 0 for every encoding in that range that no specific case handles, reporting the features as absent. Also relabel the case for 0xD5380440, which decodes ID_AA64PFR2_EL1, not ID_AA64ZFR0_EL1. Closes #58 --- .gitignore | 1 + CODSPEED-CHANGELOG.md | 9 ++++++++ VEX/priv/guest_arm64_toIR.c | 25 ++++++++++++++++++--- none/tests/arm64/Makefile.am | 2 ++ none/tests/arm64/mrs_id_regs.c | 29 +++++++++++++++++++++++++ none/tests/arm64/mrs_id_regs.stderr.exp | 0 none/tests/arm64/mrs_id_regs.stdout.exp | 12 ++++++++++ none/tests/arm64/mrs_id_regs.vgtest | 2 ++ 8 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 none/tests/arm64/mrs_id_regs.c create mode 100644 none/tests/arm64/mrs_id_regs.stderr.exp create mode 100644 none/tests/arm64/mrs_id_regs.stdout.exp create mode 100644 none/tests/arm64/mrs_id_regs.vgtest diff --git a/.gitignore b/.gitignore index bd24963586..f58566ed7b 100644 --- a/.gitignore +++ b/.gitignore @@ -1890,6 +1890,7 @@ /none/tests/arm64/ldxp_stxp /none/tests/arm64/integer /none/tests/arm64/memory_test +/none/tests/arm64/mrs_id_regs /none/tests/arm64/simd_dotprod /none/tests/arm64/simd_v81 /none/tests/arm64/sha512_v82 diff --git a/CODSPEED-CHANGELOG.md b/CODSPEED-CHANGELOG.md index 6416e4bf74..22cfc2a02a 100644 --- a/CODSPEED-CHANGELOG.md +++ b/CODSPEED-CHANGELOG.md @@ -11,6 +11,15 @@ last-level cache when `--LL` (or its `--L2` alias) supplies the cache configurat The warning remains when LL is auto-detected. Cache geometry and simulation results are unchanged. +### arm64: decode reads of all EL0-visible ID registers + +`MRS` of an ID register in `S3_0_C0_C2_0` to `S3_0_C0_C7_7` that VEX did not +decode, such as `ID_AA64FPFR0_EL1`, `ID_AA64ZFR0_EL1` or `ID_AA64ISAR2_EL1`, +raised SIGILL under Valgrind. Linux emulates these reads, so they succeed +natively; NumKong reads `ID_AA64FPFR0_EL1` at import time. These reads now +return 0, which reports the features as absent. The case that was labelled +`ID_AA64ZFR0_EL1` decoded `ID_AA64PFR2_EL1`, and is now labelled as such. + ## Features ### Fixed Linux startup stack layout diff --git a/VEX/priv/guest_arm64_toIR.c b/VEX/priv/guest_arm64_toIR.c index ebab85b823..6b4ed2ee41 100644 --- a/VEX/priv/guest_arm64_toIR.c +++ b/VEX/priv/guest_arm64_toIR.c @@ -7635,15 +7635,15 @@ Bool dis_ARM64_branch_etc(/*MB_OUT*/DisResult* dres, UInt insn, DIP("mrs %s, id_aa64pfr1_el1 (FAKED)\n", nameIReg32orZR(tt)); return True; } - /* ---- Case for ID_AA64ZFR0_EL1 (RO) ---- + /* ---- Case for ID_AA64PFR2_EL1 (RO) ---- We just return 0x0 here, as we don't support the opcodes of new commands in the emulation environment. - 0xD53804 010 Rt MRS rT, id_aa64zfr0_el1 + 0xD53804 010 Rt MRS rT, id_aa64pfr2_el1 */ if ((INSN(31,0) & 0xFFFFFFE0) == 0xD5380440 /*MRS*/) { UInt tt = INSN(4,0); putIReg64orZR(tt, mkU64(0x0)); - DIP("mrs %s, id_aa64zfr0_el1 (FAKED)\n", nameIReg64orZR(tt)); + DIP("mrs %s, id_aa64pfr2_el1 (FAKED)\n", nameIReg64orZR(tt)); return True; } /* ---- Case for ID_AA64DFR0_EL1 (RO) ---- @@ -7790,6 +7790,25 @@ Bool dis_ARM64_branch_etc(/*MB_OUT*/DisResult* dres, UInt insn, DIP("mrs %s, id_aa64mmfr2_el1 (FAKED)\n", nameIReg64orZR(tt)); return True; } + /* ---- Case for the remaining ID registers (RO) ---- + Linux emulates EL0 reads of Op0=3, Op1=0, CRn=0, CRm=2..7 and + returns 0 for every register it does not track, so a read never + traps natively. This covers ID_AA64ZFR0_EL1, ID_AA64SMFR0_EL1, + ID_AA64FPFR0_EL1, ID_AA64ISAR2_EL1, ID_AA64MMFR3_EL1, the AArch32 + ID registers and unallocated encodings. Return 0 for all of them: + it reports the features as absent, which matches what the cases + above report for ID_AA64PFR0_EL1 (no SVE) and ID_AA64PFR1_EL1 + (no SME). + 0xD538 0 CRm op2 Rt MRS rT, s3_0_c0_c_ + */ + if ((INSN(31,0) & 0xFFFFF000) == 0xD5380000 /*MRS*/ + && INSN(11,8) >= 2 && INSN(11,8) <= 7) { + UInt tt = INSN(4,0); + putIReg64orZR(tt, mkU64(0x0)); + DIP("mrs %s, s3_0_c0_c%u_%u (FAKED)\n", + nameIReg64orZR(tt), INSN(11,8), INSN(7,5)); + return True; + } /* ---- Cases for TPIDR_EL0 ---- 0xD51BD0 010 Rt MSR tpidr_el0, rT 0xD53BD0 010 Rt MRS rT, tpidr_el0 diff --git a/none/tests/arm64/Makefile.am b/none/tests/arm64/Makefile.am index 4b0d580c41..ff80dc7a20 100644 --- a/none/tests/arm64/Makefile.am +++ b/none/tests/arm64/Makefile.am @@ -17,6 +17,7 @@ EXTRA_DIST = \ integer.stdout.exp integer.stderr.exp integer.vgtest \ memory_test.stdout.exp memory_test.stderr.exp memory_test.vgtest \ memory_test.stdout.exp-clang \ + mrs_id_regs.stdout.exp mrs_id_regs.stderr.exp mrs_id_regs.vgtest \ atomics_v81.stdout.exp atomics_v81.stderr.exp atomics_v81.vgtest \ simd_v81.stdout.exp simd_v81.stderr.exp simd_v81.vgtest \ simd_dotprod.stdout.exp simd_dotprod.stderr.exp simd_dotprod.vgtest \ @@ -39,6 +40,7 @@ check_PROGRAMS = \ fp_and_simd \ integer \ memory_test \ + mrs_id_regs \ fmadd_sub \ ldxp_stxp diff --git a/none/tests/arm64/mrs_id_regs.c b/none/tests/arm64/mrs_id_regs.c new file mode 100644 index 0000000000..a631e210dc --- /dev/null +++ b/none/tests/arm64/mrs_id_regs.c @@ -0,0 +1,29 @@ +/* Linux emulates EL0 reads of the ID registers in Op0=3, Op1=0, CRn=0, + CRm=2..7, so these reads must not raise SIGILL under Valgrind. Valgrind + reports the features as absent, so each read returns 0. */ + +#include + +#define READ_ID_REG(name, sysreg) \ + do { \ + unsigned long value; \ + __asm__ volatile("mrs %0, " sysreg : "=r"(value)); \ + printf("%-17s 0x%016lx\n", name, value); \ + } while (0) + +int main(void) +{ + READ_ID_REG("ID_ISAR0_EL1", "S3_0_C0_C2_0"); + READ_ID_REG("MVFR0_EL1", "S3_0_C0_C3_0"); + READ_ID_REG("ID_AA64PFR2_EL1", "S3_0_C0_C4_2"); + READ_ID_REG("ID_AA64ZFR0_EL1", "S3_0_C0_C4_4"); + READ_ID_REG("ID_AA64SMFR0_EL1", "S3_0_C0_C4_5"); + READ_ID_REG("ID_AA64FPFR0_EL1", "S3_0_C0_C4_7"); + READ_ID_REG("ID_AA64DFR2_EL1", "S3_0_C0_C5_2"); + READ_ID_REG("ID_AA64ISAR2_EL1", "S3_0_C0_C6_2"); + READ_ID_REG("ID_AA64ISAR3_EL1", "S3_0_C0_C6_3"); + READ_ID_REG("ID_AA64MMFR3_EL1", "S3_0_C0_C7_3"); + READ_ID_REG("ID_AA64MMFR4_EL1", "S3_0_C0_C7_4"); + READ_ID_REG("S3_0_C0_C7_7", "S3_0_C0_C7_7"); + return 0; +} diff --git a/none/tests/arm64/mrs_id_regs.stderr.exp b/none/tests/arm64/mrs_id_regs.stderr.exp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/none/tests/arm64/mrs_id_regs.stdout.exp b/none/tests/arm64/mrs_id_regs.stdout.exp new file mode 100644 index 0000000000..702d1f662a --- /dev/null +++ b/none/tests/arm64/mrs_id_regs.stdout.exp @@ -0,0 +1,12 @@ +ID_ISAR0_EL1 0x0000000000000000 +MVFR0_EL1 0x0000000000000000 +ID_AA64PFR2_EL1 0x0000000000000000 +ID_AA64ZFR0_EL1 0x0000000000000000 +ID_AA64SMFR0_EL1 0x0000000000000000 +ID_AA64FPFR0_EL1 0x0000000000000000 +ID_AA64DFR2_EL1 0x0000000000000000 +ID_AA64ISAR2_EL1 0x0000000000000000 +ID_AA64ISAR3_EL1 0x0000000000000000 +ID_AA64MMFR3_EL1 0x0000000000000000 +ID_AA64MMFR4_EL1 0x0000000000000000 +S3_0_C0_C7_7 0x0000000000000000 diff --git a/none/tests/arm64/mrs_id_regs.vgtest b/none/tests/arm64/mrs_id_regs.vgtest new file mode 100644 index 0000000000..9cd53f30fb --- /dev/null +++ b/none/tests/arm64/mrs_id_regs.vgtest @@ -0,0 +1,2 @@ +prog: mrs_id_regs +vgopts: -q From 8a6979102c56d010127550dd960b28727b16297d Mon Sep 17 00:00:00 2001 From: not-matthias Date: Mon, 2 Nov 2026 09:26:30 +0100 Subject: [PATCH 2/2] fix(vex): decode arm64 MRS of TPIDRRO_EL0 EL0 may read TPIDRRO_EL0, and Linux zeroes it for AArch64 tasks, so the read succeeds natively. VEX had no case for it and raised SIGILL. Return 0, matching the kernel. --- CODSPEED-CHANGELOG.md | 13 +++++++------ VEX/priv/guest_arm64_toIR.c | 12 ++++++++++++ none/tests/arm64/mrs_id_regs.c | 9 ++++++--- none/tests/arm64/mrs_id_regs.stdout.exp | 1 + 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/CODSPEED-CHANGELOG.md b/CODSPEED-CHANGELOG.md index 22cfc2a02a..61808930c4 100644 --- a/CODSPEED-CHANGELOG.md +++ b/CODSPEED-CHANGELOG.md @@ -13,12 +13,13 @@ results are unchanged. ### arm64: decode reads of all EL0-visible ID registers -`MRS` of an ID register in `S3_0_C0_C2_0` to `S3_0_C0_C7_7` that VEX did not -decode, such as `ID_AA64FPFR0_EL1`, `ID_AA64ZFR0_EL1` or `ID_AA64ISAR2_EL1`, -raised SIGILL under Valgrind. Linux emulates these reads, so they succeed -natively; NumKong reads `ID_AA64FPFR0_EL1` at import time. These reads now -return 0, which reports the features as absent. The case that was labelled -`ID_AA64ZFR0_EL1` decoded `ID_AA64PFR2_EL1`, and is now labelled as such. +Previously unsupported `MRS` reads in `S3_0_C0_C2_0` through `S3_0_C0_C7_7` +now return 0 instead of raising SIGILL, reporting the features as absent. +Linux emulates these reads natively. The decoder label for `ID_AA64PFR2_EL1` +was also corrected from `ID_AA64ZFR0_EL1`. + +`MRS` reads of `TPIDRRO_EL0` now return 0 instead of raising SIGILL, +matching Linux on AArch64. ## Features diff --git a/VEX/priv/guest_arm64_toIR.c b/VEX/priv/guest_arm64_toIR.c index 6b4ed2ee41..7c8566b45d 100644 --- a/VEX/priv/guest_arm64_toIR.c +++ b/VEX/priv/guest_arm64_toIR.c @@ -7826,6 +7826,18 @@ Bool dis_ARM64_branch_etc(/*MB_OUT*/DisResult* dres, UInt insn, } return True; } + /* ---- Case for TPIDRRO_EL0 (RO) ---- + EL0 can read but not write it. Linux zeroes it for AArch64 + tasks (it holds the TLS pointer only for AArch32 tasks), so + return 0. + 0xD53BD0 011 Rt MRS rT, tpidrro_el0 + */ + if ((INSN(31,0) & 0xFFFFFFE0) == 0xD53BD060 /*MRS*/) { + UInt tt = INSN(4,0); + putIReg64orZR(tt, mkU64(0x0)); + DIP("mrs %s, tpidrro_el0 (FAKED)\n", nameIReg64orZR(tt)); + return True; + } /* ---- Cases for FPCR ---- 0xD51B44 000 Rt MSR fpcr, rT 0xD53B44 000 Rt MSR rT, fpcr diff --git a/none/tests/arm64/mrs_id_regs.c b/none/tests/arm64/mrs_id_regs.c index a631e210dc..81e6a60a97 100644 --- a/none/tests/arm64/mrs_id_regs.c +++ b/none/tests/arm64/mrs_id_regs.c @@ -1,6 +1,8 @@ -/* Linux emulates EL0 reads of the ID registers in Op0=3, Op1=0, CRn=0, - CRm=2..7, so these reads must not raise SIGILL under Valgrind. Valgrind - reports the features as absent, so each read returns 0. */ +/* Linux lets EL0 read the ID registers in Op0=3, Op1=0, CRn=0, CRm=2..7 + (by emulating the read) and TPIDRRO_EL0, so these reads must not raise + SIGILL under Valgrind. Valgrind reports the ID register features as + absent, and Linux zeroes TPIDRRO_EL0 for AArch64 tasks, so each read + returns 0. */ #include @@ -25,5 +27,6 @@ int main(void) READ_ID_REG("ID_AA64MMFR3_EL1", "S3_0_C0_C7_3"); READ_ID_REG("ID_AA64MMFR4_EL1", "S3_0_C0_C7_4"); READ_ID_REG("S3_0_C0_C7_7", "S3_0_C0_C7_7"); + READ_ID_REG("TPIDRRO_EL0", "tpidrro_el0"); return 0; } diff --git a/none/tests/arm64/mrs_id_regs.stdout.exp b/none/tests/arm64/mrs_id_regs.stdout.exp index 702d1f662a..10127ddf4b 100644 --- a/none/tests/arm64/mrs_id_regs.stdout.exp +++ b/none/tests/arm64/mrs_id_regs.stdout.exp @@ -10,3 +10,4 @@ ID_AA64ISAR3_EL1 0x0000000000000000 ID_AA64MMFR3_EL1 0x0000000000000000 ID_AA64MMFR4_EL1 0x0000000000000000 S3_0_C0_C7_7 0x0000000000000000 +TPIDRRO_EL0 0x0000000000000000