https://bugs.winehq.org/show_bug.cgi?id=46132
Bug ID: 46132 Summary: Multiple Windows 10 ARM64 apps crash with illegal instruction fault due to access of ARMv8 PMU cycle counter via 'PMCCNTR_EL0' register in EL0 (Linux kernel disallows access by default) Product: Wine Version: 3.20 Hardware: aarch64 OS: Linux Status: NEW Severity: normal Priority: P2 Component: -unknown Assignee: wine-bugs@winehq.org Reporter: focht@gmx.net Distribution: ---
Hello folks,
just for documentation ...
--- snip --- $ WINEDEBUG=+seh,+loaddll,+process,+relay,+msvcrt wine64 ./gatherosstate.exe
log.txt 2>&1
... 0009:trace:msvcrt:DllMain finished process init 0009:Ret PE DLL (proc=0x7fae7e3b04,module=0x7fae730000 L"msvcrt.dll",reason=PROCESS_ATTACH,res=0x22fc48) retval=1 0009:Call PE DLL (proc=0x7fae5fd5d8,module=0x7fae580000 L"advapi32.dll",reason=PROCESS_ATTACH,res=0x22fc48) 0009:Ret PE DLL (proc=0x7fae5fd5d8,module=0x7fae580000 L"advapi32.dll",reason=PROCESS_ATTACH,res=0x22fc48) retval=1 0009:Starting process L"Z:\home\focht\Downloads\ARM64-win10-apps\gatherosstate.exe" (entryproc=0x14002fd80) 0009:trace:process:NtQueryInformationProcess (0xffffffffffffffff,0x00000007,0x22f988,0x00000008,(nil)) 0009:trace:seh:call_stack_handlers calling handler at 0x7b4d6330 code=c000001d flags=0 wine: Unhandled illegal instruction at address 0x14002fc0c (thread 0009), starting debugger... 0009:trace:seh:start_debugger Starting debugger "winedbg --auto 8 32" --- snip ---
strace:
--- snip --- 2752 [000000014002fc0c] --- SIGILL {si_signo=SIGILL, si_code=ILL_ILLOPC, si_addr=0x14002fc0c} --- --- snip ---
Disassembly around faulting instruction:
--- snip --- start: 000000014002FD80 F3 53 BE A9 STP X19, X20, [SP,#-0x20+var_s0]! 000000014002FD84 F5 0B 00 F9 STR X21, [SP,#var_s10] 000000014002FD88 FD 7B BF A9 STP X29, X30, [SP,#var_10]! 000000014002FD8C FD 03 00 91 MOV X29, SP 000000014002FD90 13 00 80 52 MOV W19, #0 000000014002FD94 9B FF FF 97 BL sub_14002FC00 000000014002FD98 F5 03 00 2A MOV W21, W0 ... sub_14002FC00: 000000014002FC00 F3 0F 1F F8 STR X19, [SP,#-0x10+var_s0]! 000000014002FC04 FD 7B BF A9 STP X29, X30, [SP,#var_10]! 000000014002FC08 FD 03 00 91 MOV X29, SP 000000014002FC0C 08 9D 3B D5 MRS X8, #3, c9, c13, #0 ; *boom* 000000014002FC10 0A 03 00 B0 ADRP X10, #qword_140090670@PAGE 000000014002FC14 53 C1 19 91 ADD X19, X10, #qword_140090670@PAGEOFF 000000014002FC18 08 79 40 92 AND X8, X8, #0x7FFFFFFF --- snip ---
Decoding the sysreg using ARM's ARMv8 Processor Technical Reference Manual:
MRS <Xt>, (<systemreg>|S<op0>_<op1>_<Cn>_<Cm>_<op2>)
08 9D 3B D5 -> D5 3B 9D 08
| D5 | 3B | 9D | 08 | |11010101|00 1 11 011|1001 1101|000 01000| | OPCode |L|o0|op1|CRn |CRm |op2| Rt |
op0 = 3 op1 = 3 op2 = 0 CRn = 9 CRm = 13 Rt = 8
0x08,0x9d,0x3b,0xd5 = 'MRS X8, PMCCNTR_EL0'
Microsoft docs:
https://docs.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?vie...
--- quote --- Cycle counter
All ARMv8 CPUs are required to support a cycle counter register. This is a 64-bit register that Windows configures to be readable at any exception level (including user mode). It can be accessed via the special PMCCNTR_EL0 register, using the MSR opcode in assembly code, or the _ReadStatusReg intrinsic in C/C++ code.
Note that the cycle counter here is a true cycle counter, not a wall clock, and thus the counting frequency will vary with the processor frequency. If you feel you must know the frequency of the cycle counter, you should not be using the cycle counter. Instead, you want to measure wall clock time, for which you should use QueryPerformanceCounter. --- quote ---
Defined as 'ReadTimeStampCounter()' in Windows 10 SDK header 'winnt.h'.
Current QEMU for ARM64 supports it (if not run under real hardware):
https://github.com/qemu/qemu/blob/master/target/arm/helper.c#L1410
--- snip --- #ifndef CONFIG_USER_ONLY ... { .name = "PMCCNTR_EL0", .state = ARM_CP_STATE_AA64, .opc0 = 3, .opc1 = 3, .crn = 9, .crm = 13, .opc2 = 0, .access = PL0_RW, .accessfn = pmreg_access_ccntr, .type = ARM_CP_IO, .readfn = pmccntr_read, .writefn = pmccntr_write, }, #endif --- snip ---
The relevant piece of Linux kernel code:
https://github.com/torvalds/linux/commit/60792ad349f3c6dc5735aafefe5dc9121c7...
--- quote --- arm64: kernel: enforce pmuserenr_el0 initialization and restore
The pmuserenr_el0 register value is architecturally UNKNOWN on reset. Current kernel code resets that register value iff the core pmu device is correctly probed in the kernel. On platforms with missing DT pmu nodes (or disabled perf events in the kernel), the pmu is not probed, therefore the pmuserenr_el0 register is not reset in the kernel, which means that its value retains the reset value that is architecturally UNKNOWN (system may run with eg pmuserenr_el0 == 0x1, which means that PMU counters access is available at EL0, which must be disallowed).
This patch adds code that resets pmuserenr_el0 on cold boot and restores it on core resume from shutdown, so that the pmuserenr_el0 setup is always enforced in the kernel. --- quote ---
Since the Linux kernel maintainers disallow PMU counter access in EL0 by default, one has to patch the Linux kernel or use a helper kernel module. Windows 10 ARM64 likely has PMU counter access in EL0 enabled by default.
There were some attempts in the past, but they never made it in.
https://patchwork.kernel.org/patch/5217341/
https://github.com/zhiyisun/enable_arm_pmu/tree/dev
Anyway, nothing to fix in Wine here. Upstream (if at all).
Regards