https://bugs.winehq.org/show_bug.cgi?id=46189
Bug ID: 46189 Summary: [Bug 46187] Windows PowerShell Core 6.2 Preview 2 for ARM32 crashes due to ntdll 'set_cpu_context' not restoring Thumb mode during return from exception handling Product: Wine Version: 3.21 Hardware: arm OS: Linux Status: NEW Severity: normal Priority: P2 Component: ntdll Assignee: wine-bugs@winehq.org Reporter: focht@gmx.net Distribution: ---
Hello folks,
the continuation of bug 46187("Windows PowerShell Core 6.2 Preview 2 for ARM32 crashes due to unhandled trap_no 0 (write watch access causes SIGSEGV)")
Even with trap code 0 properly translated it still crashes.
Debugger session (with fixup for bug #46187 applied).
--- snip --- $ gdb wine GNU gdb (GDB) 8.2 ... Reading symbols from wine...done. (gdb) run pwsh.exe Starting program: /home/focht/projects/wine/mainline-install-arm/bin/wine pwsh.exe ... Thread 1 "pwsh.exe" hit Breakpoint 1, virtual_handle_fault (addr=0xf3ce0000, err=1, on_signal_stack=0) at /home/focht/projects/wine/mainline-src/dlls/ntdll/virtual.c:2010 2010 NTSTATUS ret = STATUS_ACCESS_VIOLATION; (gdb) bt #0 virtual_handle_fault (addr=0xf3ce0000, err=1, on_signal_stack=0) at /home/focht/projects/wine/mainline-src/dlls/ntdll/virtual.c:2010 #1 0xf7c612ec in raise_segv_exception (rec=0xf73ce9f8, context=0xf73ce858) at /home/focht/projects/wine/mainline-src/dlls/ntdll/signal_arm.c:574 #2 0xf7c61152 in raise_func_trampoline_thumb () at /home/focht/projects/wine/mainline-src/dlls/ntdll/signal_arm.c:508 Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) info locals ret = 0xc0000005 page = 0xf3ce0000 sigset = {__val = {0x0, 0x0, 0xf7c61150, 0x600f0030, 0xf3ce0000, 0x0, 0x0, 0xf3ce0000, 0xf7ccdd34, 0xa0000, 0xffffffff, 0xf6d4a5e4, 0xf7ddf7a9, 0xf73ce82c, 0xf7c7f6c0, 0x0, 0x1, 0x11000, 0xf3ce0000, 0x43, 0x1, 0xf73ce834, 0xf7c7fd20, 0xffffffff, 0xa5e4, 0x11000, 0xf3ce0000, 0xb0000, 0x1, 0x1, 0xf3ce0000, 0x11}} vprot = 0x63 ... (gdb) n 2027 set_page_vprot_bits( page, page_size, 0, VPROT_WRITEWATCH ); (gdb) n 2028 mprotect_range( page, page_size, 0, 0 ); (gdb) n 2031 if (VIRTUAL_GetUnixProt( get_page_vprot( page )) & PROT_WRITE) (gdb) n 2033 if ((vprot & VPROT_WRITEWATCH) || is_write_watch_range( page, page_size )) (gdb) n 2034 ret = STATUS_SUCCESS;
... --- snip ---
The problem is the way Wine restores the context on ARM32 via 'set_cpu_context':
--- snip --- Dump of assembler code for function set_cpu_context: 0xf7c605c0 <+0>: ldr r1, [r0, #68] ; 0x44 => 0xf7c605c4 <+4>: msr CPSR_f, r1 0xf7c605c8 <+8>: ldr r1, [r0, #64] ; 0x40 0xf7c605cc <+12>: ldr lr, [r0, #60] ; 0x3c 0xf7c605d0 <+16>: ldr sp, [r0, #56] ; 0x38 0xf7c605d4 <+20>: push {r1} ; (str r1, [sp, #-4]!) 0xf7c605d8 <+24>: ldmib r0, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12} 0xf7c605dc <+28>: pop {pc} ; (ldr pc, [sp], #4)
(gdb) info reg r0 0xf73ce858 0xf73ce858 r1 0x602f0030 0x602f0030 r2 0x0 0x0 r3 0x0 0x0 r4 0xf74baee0 0xf74baee0 r5 0x90068 0x90068 r6 0x0 0x0 r7 0xf6d4a5e4 0xf6d4a5e4 r8 0xf3ce0000 0xf3ce0000 r9 0xf6e78500 0xf6e78500 r10 0xf6d51268 0xf6d51268 r11 0xf73ce854 0xf73ce854 r12 0xaf 0xaf sp 0xf73ce840 0xf73ce840 lr 0xf7c61350 0xf7c61350 pc 0xf7c605c4 0xf7c605c4 <set_cpu_context+4> cpsr 0x600f0010 0x600f0010 Unable to fetch SVE register header: Invalid argument. --- snip ---
CPSR = 0x600f0010 = Wine ARM32 mode R1 = old CPSR before fault = 0x602f0030 = app code in Thumb mode
Wine -> CPSR_f = only flag bits set (execution state/control bits can't be set explicitly in USR mode by design, no USR SPSR_xxx).
--- snip --- (gdb) disas Dump of assembler code for function set_cpu_context: 0xf7c605c0 <+0>: ldr r1, [r0, #68] ; 0x44 0xf7c605c4 <+4>: msr CPSR_f, r1 0xf7c605c8 <+8>: ldr r1, [r0, #64] ; 0x40 0xf7c605cc <+12>: ldr lr, [r0, #60] ; 0x3c 0xf7c605d0 <+16>: ldr sp, [r0, #56] ; 0x38 0xf7c605d4 <+20>: push {r1} ; (str r1, [sp, #-4]!) 0xf7c605d8 <+24>: ldmib r0, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12} => 0xf7c605dc <+28>: pop {pc} ; (ldr pc, [sp], #4) End of assembler dump.
(gdb) si 0xf6c5160c in ?? ()
(gdb) info reg r0 0xf3ce0020 0xf3ce0020 r1 0xf73ce944 0xf73ce944 r2 0x11000 0x11000 r3 0xf3d90000 0xf3d90000 r4 0xf74baee0 0xf74baee0 r5 0x90068 0x90068 r6 0x0 0x0 r7 0xf6d4a5e4 0xf6d4a5e4 r8 0xf3ce0000 0xf3ce0000 r9 0xf6e78500 0xf6e78500 r10 0xf6d51268 0xf6d51268 r11 0xf73ceaa0 0xf73ceaa0 r12 0xaf 0xaf sp 0xf73cea48 0xf73cea48 lr 0xf7ddf7a9 0xf7ddf7a9 pc 0xf6c5160c 0xf6c5160c cpsr 0x600f0010 0x600f0010 Unable to fetch SVE register header: Invalid argument.
(gdb) set arm fallback-mode thumb
(gdb) x/10i $pc => 0xf6c5160c: strd r6, r3, [r0, #-32] 0xf6c51610: ldr r3, [sp, #36] ; 0x24 0xf6c51612: ldr r4, [sp, #44] ; 0x2c 0xf6c51614: str.w r3, [r0, #-24] 0xf6c51618: ldr r3, [sp, #40] ; 0x28 0xf6c5161a: add.w r3, r0, r3, lsl #2 0xf6c5161e: str.w r3, [r0, #-20] 0xf6c51622: strd r5, r6, [r0, #-8] 0xf6c51626: ldr.w r3, [r0, #-20] 0xf6c5162a: add.w r3, r3, r4, lsl #1
(gdb) si 0xf6c51610 in ?? ()
(gdb) info reg r0 0xf3ce0020 4090363936 r1 0xf73ce944 4147964228 r2 0x11000 69632 r3 0xf3d90000 4091084800 r4 0xf74baee0 4148932320 r5 0x90068 589928 r6 0x0 0 r7 0xf6d4a5e4 4141131236 r8 0xf3ce0000 4090363904 r9 0xf6e78500 4142368000 r10 0xf6d51268 4141159016 r11 0xf73ceaa0 4147964576 r12 0xaf 175 sp 0xf73cea48 0xf73cea48 lr 0xf7ddf7a9 -136448087 pc 0xf6c51610 0xf6c51610 cpsr 0x600f0010 1611595792 Unable to fetch SVE register header: Invalid argument.
(gdb) si 0xf6c51614 in ?? () (gdb) si 0xf6c51618 in ?? () (gdb) si 0xf6c78248 in ?? () (gdb) si
Thread 1 "pwsh.exe" received signal SIGILL, Illegal instruction. 0xf6c78248 in ?? () --- snip ---
Wine source:
https://source.winehq.org/git/wine.git/blob/HEAD:/dlls/ntdll/signal_arm.c#l2...
--- snip --- 283 void DECLSPEC_HIDDEN set_cpu_context( const CONTEXT *context ); 284 __ASM_GLOBAL_FUNC( set_cpu_context, 285 ".arm\n\t" 286 "ldr r1, [r0, #0x44]\n\t" /* context->Cpsr */ 287 "msr CPSR_f, r1\n\t" 288 "ldr r1, [r0, #0x40]\n\t" /* context->Pc */ 289 "ldr lr, [r0, #0x3c]\n\t" /* context->Lr */ 290 "ldr sp, [r0, #0x38]\n\t" /* context->Sp */ 291 "push {r1}\n\t" 292 "ldmib r0, {r0-r12}\n\t" /* context->R0..R12 */ 293 "pop {pc}" ) --- snip ---
Well, that's an unfortunate combination of things here. The app binaries contain a mix of 16-bit Thumb and 32-bit Thumb(2) instructions. Thumb-2 instruction set is the default for Windows on ARM per convention.
https://msdn.microsoft.com/en-us/library/dn736986.aspx)
--- quote --- The instruction set for Windows on ARM is strictly limited to Thumb-2. All code executed on this platform is expected to start and remain in Thumb mode at all times. An attempt to switch into the legacy ARM instruction set may succeed, but if it does, any exceptions or interrupts that occur may lead to an application fault in user mode, or a bugcheck in kernel mode.
A side-effect of this requirement is that all code pointers must have the low bit set. This is so that when they are loaded and branched to via BLX or BX, the processor will remain in Thumb mode and not try to execute the target code as 32-bit ARM instructions. --- quote ---
The write-watch fault is caused by an instruction that has 32-bit encoding (Thumb2), located at 0xf6c5160c -> bits[1:0] of the address = 0b00. Due to the segfault handling, the mode is switched to ARM (there are also explicit '.arm' directives in Wine code) -> CPSR 'T' bit gone.
Wine's 'set_cpu_context' uses 'pop {pc}' to return to the faulting instruction for re-execution after write-watch has been reset. The instruction behaviour is defined by ARM:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0588b/Babefbc...
--- quote --- == POP, with reglist including the PC
This instruction causes a branch to the address popped off the stack into the PC. This is usually a return from a subroutine, where the LR was pushed onto the stack at the start of the subroutine.
In ARMv5T and above:
bits[1:0] must not be 0b10
if bit[0] is 1, execution continues in Thumb state
if bit[0] is 0, execution continues in ARM state.
In ARMv4, bits[1:0] of the address loaded must be 0b00.
== Thumb instructions
A subset of these instructions are available in the Thumb instruction set.
The following restrictions apply to the 16-bit instructions:
For PUSH, reglist can only include the Lo registers and the LR
For POP, reglist can only include the Lo registers and the PC.
The following restrictions apply to the 32-bit instructions:
reglist must not include the SP
For PUSH, reglist must not include the PC
For POP, reglist can include either the LR or the PC, but not both.
== Restrictions on reglist in ARM instructions
ARM PUSH instructions can have SP and PC in the reglist but these instructions that include SP or PC in the reglist are deprecated in ARMv6T2 and above.
ARM POP instructions cannot have SP but can have PC in the reglist. These instructions that include both PC and LR in the reglist are deprecated in ARMv6T2 and above. --- quote ---
Since I run the whole target under QEMU aarch64 system emulation I double checked that part too. Qemu behaves correctly, not switching from ARM to Thumb mode because bits[1:0] of the address = 0b00.
https://github.com/qemu/qemu/blob/master/target/arm/translate.c#L225
--- snip --- /* Set a CPU register. The source must be a temporary and will be marked as dead. */ static void store_reg(DisasContext *s, int reg, TCGv_i32 var) { if (reg == 15) { /* In Thumb mode, we must ignore bit 0. * In ARM mode, for ARMv4 and ARMv5, it is UNPREDICTABLE if bits [1:0] * are not 0b00, but for ARMv6 and above, we must ignore bits [1:0]. * We choose to ignore [1:0] in ARM mode for all architecture versions. */ tcg_gen_andi_i32(var, var, s->thumb ? ~1 : ~3); s->base.is_jmp = DISAS_JUMP; } tcg_gen_mov_i32(cpu_R[reg], var); tcg_temp_free_i32(var); } --- snip ---
Due to Wine's cpu context helper not restoring Thumb mode properly, execution is incorrectly resumed in ARM mode, leading to crash shortly after.
Any Wine ARM32-specific assembly code/wrappers that pass control directly to app code, bypassing the compiler's ARM-Thumb interworking, has to make sure that it restores thumb mode properly.
$ sha1sum PowerShell-6.2.0-preview.2-win-arm32.zip b77b87906514e802c03c84fcb72ce39f925c3b41 PowerShell-6.2.0-preview.2-win-arm32.zip
$ du -sh PowerShell-6.2.0-preview.2-win-arm32.zip 40M PowerShell-6.2.0-preview.2-win-arm32.zip
Regards