From 92df8663b9e112343de3f98e6444186eb79d2a6b Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Sun, 22 Dec 2024 16:54:19 +0100 Subject: [PATCH] Arm unix_fault_handler(): add emulation of MRS instruction Starting with Go version 1.23, the Golang runtime for Arm64 during initialization tries to retrieve the values of some system registers (via the MRS instruction) in order to evaluate the features implemented by the processor. This instruction causes a CPU exception, which the kernel handles by sending SIGILL to the user process; as a result, Go programs built with Go version 1.23 do to not work on Arm. Add emulation of the MRS instruction to retrieve the values of the system registers used by the Go runtime, so that Go 1.23 works properly on Arm. --- src/aarch64/crt0.S | 5 +++++ src/aarch64/kernel_machine.c | 27 +++++++++++++++++++++++++++ src/aarch64/kernel_machine.h | 16 ++++++++++++++++ src/riscv64/kernel_machine.h | 2 ++ src/unix/unix.c | 3 ++- src/x86_64/kernel_machine.h | 2 ++ 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/aarch64/crt0.S b/src/aarch64/crt0.S index 4b550a5db..70557c7a8 100644 --- a/src/aarch64/crt0.S +++ b/src/aarch64/crt0.S @@ -180,6 +180,11 @@ context_suspend: ldr x0, [x18] b context_suspend_finish +.globl sysreg_get_id_aa64zfr0 +sysreg_get_id_aa64zfr0: + .long (INSN_MRS(0) | SYSREG_ID_AA64ZFR0_EL1) + ret + .globl arm_hvc arm_hvc: // incomplete, just enough to issue power off diff --git a/src/aarch64/kernel_machine.c b/src/aarch64/kernel_machine.c index 21eb9b830..eced85b30 100644 --- a/src/aarch64/kernel_machine.c +++ b/src/aarch64/kernel_machine.c @@ -88,6 +88,33 @@ void clone_frame_pstate(context_frame dest, context_frame src) runtime_memcpy(dest, src, sizeof(u64) * FRAME_N_PSTATE); } +boolean insn_emulate(context_frame f) +{ +#define CASE_SYSREG(id) case SYSREG_##id: val = read_psr(id); break + + u32 *insn_ptr = (u32 *)frame_fault_pc(f); + u32 insn = *insn_ptr; + if ((insn & 0xfff80000) == 0xd5380000) { + /* read from non-debug system registers and special-purpose registers (op0 = 3) */ + u64 val; + switch (insn & 0x001fffe0) { + CASE_SYSREG(ID_AA64PFR0_EL1); + CASE_SYSREG(ID_AA64ISAR0_EL1); + CASE_SYSREG(ID_AA64ISAR1_EL1); + case SYSREG_ID_AA64ZFR0_EL1: + val = sysreg_get_id_aa64zfr0(); + break; + default: + return false; + } + u64 *dest = &f[FRAME_X0] + (insn & 0x0000001f); /* destination register */ + *dest = val; + frame_set_insn_ptr(f, u64_from_pointer(insn_ptr + 1)); /* go to next instruction */ + return true; + } + return false; +} + void interrupt_exit(void) { gic_eoi(gic_dispatch_int()); diff --git a/src/aarch64/kernel_machine.h b/src/aarch64/kernel_machine.h index a42a03a92..684dd5f86 100644 --- a/src/aarch64/kernel_machine.h +++ b/src/aarch64/kernel_machine.h @@ -21,6 +21,16 @@ #define VIRTUAL_ADDRESS_BITS 48 +#define INSN_MRS(Rt) (0xd5300000 | Rt) + +#define SYSREG(op0, op1, CRn, CRm, op2) \ + (((op0) << 19) | ((op1) << 16) | ((CRn) << 12) | ((CRm) << 8) | ((op2) << 5)) + +#define SYSREG_ID_AA64PFR0_EL1 SYSREG(3, 0, 0, 4, 0) +#define SYSREG_ID_AA64ZFR0_EL1 SYSREG(3, 0, 0, 4, 4) +#define SYSREG_ID_AA64ISAR0_EL1 SYSREG(3, 0, 0, 6, 0) +#define SYSREG_ID_AA64ISAR1_EL1 SYSREG(3, 0, 0, 6, 1) + #define CNTV_CTL_EL0_ISTATUS 4 #define CNTV_CTL_EL0_MASK 2 #define CNTV_CTL_EL0_ENABLE 1 @@ -240,6 +250,10 @@ MK_MMIO_WRITE(64, "", "x"); #define read_psr_s(rstr) ({ register u64 r; asm volatile("mrs %0, " rstr : "=r"(r)); r;}) #define write_psr_s(rstr, v) do { asm volatile("msr " rstr ", %0" : : "r"((u64)(v))); } while (0) +/* Manually encoded system register access instructions for registers that are not supported with + * the processor features enabled in the `-march` compiler flags. */ +u64 sysreg_get_id_aa64zfr0(void); + struct cpuinfo_machine { /*** Fields accessed by low-level entry points. ***/ /* Don't move these without updating x18-relative accesses in crt0.s ***/ @@ -273,6 +287,8 @@ static inline cpuinfo current_cpu(void) extern void clone_frame_pstate(context_frame dest, context_frame src); extern void init_extended_frame(context_frame f); +boolean insn_emulate(context_frame f); + static inline boolean is_pte_error(context_frame f) { // arm equivalent? diff --git a/src/riscv64/kernel_machine.h b/src/riscv64/kernel_machine.h index 989fee074..8a2ed9006 100644 --- a/src/riscv64/kernel_machine.h +++ b/src/riscv64/kernel_machine.h @@ -195,6 +195,8 @@ static inline cpuinfo current_cpu(void) extern void clone_frame_pstate(context_frame dest, context_frame src); +#define insn_emulate(f) false + static inline boolean is_pte_error(context_frame f) { // riscv equivalent? diff --git a/src/unix/unix.c b/src/unix/unix.c index 6b799d738..a4b1bacfb 100644 --- a/src/unix/unix.c +++ b/src/unix/unix.c @@ -269,7 +269,8 @@ closure_func_basic(fault_handler, context, unix_fault_handler, } else if (is_illegal_instruction(f)) { if (user) { pf_debug("invalid opcode fault in user mode, rip 0x%lx", fault_pc); - deliver_fault_signal(SIGILL, t, fault_pc, ILL_ILLOPC); + if (!insn_emulate(f)) + deliver_fault_signal(SIGILL, t, fault_pc, ILL_ILLOPC); schedule_thread(t); return 0; } else { diff --git a/src/x86_64/kernel_machine.h b/src/x86_64/kernel_machine.h index 9803fca47..4daea8d07 100644 --- a/src/x86_64/kernel_machine.h +++ b/src/x86_64/kernel_machine.h @@ -365,6 +365,8 @@ static inline void frame_disable_interrupts(context_frame f) extern void xsave(context_frame f); extern void clone_frame_pstate(context_frame dest, context_frame src); +#define insn_emulate(f) false + static inline boolean is_protection_fault(context_frame f) { return (f[FRAME_ERROR_CODE] & FRAME_ERROR_PF_P) != 0;