Skip to content

Commit

Permalink
Arm unix_fault_handler(): add emulation of MRS instruction
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
francescolavra committed Dec 22, 2024
1 parent 2d7b130 commit 92df866
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/aarch64/crt0.S
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions src/aarch64/kernel_machine.c
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
16 changes: 16 additions & 0 deletions src/aarch64/kernel_machine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ***/
Expand Down Expand Up @@ -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?
Expand Down
2 changes: 2 additions & 0 deletions src/riscv64/kernel_machine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
3 changes: 2 additions & 1 deletion src/unix/unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions src/x86_64/kernel_machine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 92df866

Please sign in to comment.