https://bugs.winehq.org/show_bug.cgi?id=53682
--- Comment #18 from Martin Storsjö martin@martin.st --- (In reply to Kevin Puetz from comment #16)
I wonder where I'm missing an initialization of it
I think you're missing it in _wine_syscall_dispatcher itself, when entered in the "normal" case (i.e. output_syscalls in tools/winebuild/import.c, when calling a function which the .spec file declared as `-syscall`). In that case the id is in x8, and the syscall_frame is built up on the stack (via all the code prior to the 3: label that __wine_syscall_dispatcher_return is jumping back to).
Or to put it another way, there should be an `str sp, [x10, #0x120]` somewhere in the vicinity of https://gitlab.winehq.org/wine/wine/-/blob/master/dlls/ntdll/unix/ signal_arm64.c#L1304-1319 to initialize your new syscall_stack (no longer just alignment padding) along with everything else as it builds up a new syscall_frame. Or something like that (haven't actually tested this)...
No, that's not it - on entry to __wine_syscall_dispatcher, we have an already allocated syscall_frame somewhere on the kernel stack, where we just fill in the register values from the userspace registers - the point being here that we already need to have syscall_frame set up and syscall_stack set. Whenever we allocate a new syscall_frame on the kernel stack, we'd need to set syscall_stack in it to point to the intended stack boundary.
But I found it; while signal_init_process sets up the syscall stack,
void *kernel_stack = (char *)ntdll_get_thread_data()->kernel_stack + kernel_stack_size; arm64_thread_data()->syscall_frame = (struct syscall_frame *)kernel_stack - 1;
call_init_thunk then later wipes the frame and sets it up:
memset( frame, 0, sizeof(*frame) ); NtSetContextThread( GetCurrentThread(), ctx );
frame->sp = (ULONG64)ctx; frame->pc = (ULONG64)pLdrInitializeThunk; frame->x[0] = (ULONG64)ctx; frame->x[18] = (ULONG64)teb; frame->prev_frame = NULL; frame->restore_flags |= CONTEXT_INTEGER; frame->syscall_table = KeServiceDescriptorTable;
pthread_sigmask( SIG_UNBLOCK, &server_block_set, NULL ); __wine_syscall_dispatcher_return( frame, 0 );
If I set up syscall_stack here, it works as I had expected.
(In reply to Kevin Puetz from comment #17)
there's a slight risk of clobbering the stack below sp at that point I believe
Yeah, I see no indications that wine uses sigaltstack outside of wineserver, so there's at least the chance of an ill-timed signal coming in and smashing things up if sp is *ever* pointed at syscall_frame rather than the actual top-of-stack. If signals use the normal stack, you can't just use sp as scratch register even in asm that will eventually put it back.
Ok, good that we agree on that :-)
I'll attach both of those patches too, for reference.
With that, we've got three different ways of fixing this issue: 1. Separate noinline function that sets up the syscall_frame 2. Add a gap on the stack to compensate for the difference between bottom of stack and syscall_frame 3. Keep track of the bottom of the syscall stack and the syscall frame separately
Two of them are watertight - 2. isn't, but it is dead simple (but needs overestimating how much extra stuff the compiler might add in the stack frame, below syscall_stack).
In the end, I would maybe perhaps still lean towards your patch, i.e. 1. here - it's simple and straightforward and doesn't need extra logic in assembly. The fact that part of the compiler generated stack gets clobbered during later syscalls is the only thing that feels odd with it, but it shouldn't make any practical difference.