The shadow space must be provided directly previous to the call. Imagine the shadow space as a relic from the old stdcall/cdecl convention: For WriteFile
you needed five pushes. The shadow space stands for the last four pushes (the first four arguments). Now you need four registers, the shadow space (just the space, contents don’t matter) and one value on the stack after the shadow space (which is in fact the first push). Currently the return address to the caller (start
) is in the space that WriteFile
will use as shadow space -> crash.
You can create a new shadow space for the WinAPI functions (GetStdHandle
and WriteConsoleA
) inside the function write
:
write:
push rbp
mov rbp, rsp
sub rsp, (16 + 32) ; 5th argument of WriteConsoleA (8) + Shadow space (32)
; plus another 8 to make it a multiple of 16 (to keep stack aligned after one push aligned it after function entry)
mov [rbp+16],rcx ; <-- use our Shadow space, provided by `start`
mov [rbp+24],rdx ; <-- and again, to save our incoming args
mov rcx, -11 ; Get handle to StdOut
call GetStdHandle
mov rcx,rax ; hConsoleOutput
mov rdx, [rbp+16] ; lpBuffer ; reloaded saved copy of register arg
mov r8, [rbp+24] ; nNumberOfCharsToWrite
mov r9,empty ; lpNumberOfCharsWritten
mov qword [rsp+32],0 ; lpReserved - 5th argument directly behind the shadow space
call WriteConsoleA
leave
ret