对于X64只支持fastcall调用规则,实测win7下的x64编程,尽量保证16字节对齐,即在进入某个函数入口前是16字节对齐的,这样保证了对栈中数据成员的16字节访问是对齐的,如SSE系列需要这个限制.
win32/win64统一编程已初见成效,在此探索过程中深有体会,机会编程要不得,特别是关于rsp栈指针的处理,到底应该减多少加多少合适?
由于ml64.exe默认情况下编译出来的有prolog 和epilog 代码. 为了深入rsp的处理细节,在每个函数的开头与结尾加上了
(关闭开场码与结束码)
OPTION PROLOGUE:None
OPTION EPILOGUE:None
....
函数体
....
(恢复默认的开场码与结束码)
OPTION PROLOGUE:PROLOGUEDEF
OPTION EPILOGUE:EPILOGUEDEF
这样函数内部也要加上push rbp mov rbp,rsp .....leave ret
这样的代码就不能统一用于win32和win64了,因为一个是stdcall ,一个是x64形式下的fastcall (x86与x64的fastcall也不同) 所以下面的程序仅仅分析能使X64编译成功的代码. 请注意每个细节,包括函数与子函数参数个数和局部变量个数对rsp的影响. 更多细节在代码注释中了.
如果要统一,可以将上面多出来的部分删掉.@INVOKE 为个人所写,用于stdcall和fastcall中. @IF宏取自masm64包.
以下我个人所写的包含资源文件的窗体消息代码的片段分析:
;--------------------------------------------------------------- ;--------------------------------------------------------------- ;--- Win32/Win64 simple GUI application. By G-Spider 2011 ;--- Note: requires ml.exe /ml64.exe ,link.exe ;--------------------------------------------------------------- ;--- X86 ;--- ml /c /coff file.ASM ;--- link /out:win32.exe /subsystem:windows /entry:main file.obj ;--------------------------------------------------------------- ;--- X64 ;--- ml64 /c /Zp8 /D _WIN64=1 file.ASM ;--- link /out:win64.exe /subsystem:windows /entry:main file.obj ;--------------------------------------------------------------- ;--------------------------------------------------------------- ;说明:ml.exe 和ml64.exe中有个命令选项: ;/D sysmbol=value 定义给定名字的文本宏。 ;所以可以通过这个命令来自动选择源代码中的x86或x64部分. ;在x86中,默认不用设置/D sysmbol=value或设置/D _WIN64=0 ;若选择ml64.exe编译,可以加上/D _WIN64=1即可 ;/Zp[n] 对结构指定的字节边界对齐 ;注意区别预处理if endif 与.if .endif的不同 ;--------------------------------------------------------------- ifndef _WIN64 _WIN64 equ <0> endif if _WIN64 eq 0 ;no -_WIN64 switch? .386 .model flat, stdcall rax equ <eax> rbx equ <ebx> rcx equ <ecx> rdx equ <edx> rsp equ <esp> rbp equ <ebp> rsi equ <esi> rdi equ <edi> endif option casemap:none ;/////////////////// include windows1.inc ;/////////////////// include IF_ELSEIF.inc include INVOKE.inc ;/////////////////// include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib ;================================================ ;以下为X64影子空间赋值宏 ;================================================ MYPROLOGUE macro mov [rbp + 16], rcx mov [rbp + 24], rdx mov [rbp + 32], r8 mov [rbp + 40], r9 endm ;--- 资源IDs IDR_MENU1 equ 101 IDM_EXIT equ 1001 .data szClass db "WndClassName",0 szWnd db "Sample",0 szHello db "Hello, world",0 .code ;*** handle WM_COMMAND OPTION PROLOGUE:None OPTION EPILOGUE:None OnCommand proc hWnd:HWND,wParam:WPARAM,lParam:LPARAM ;======================================== ;xxxxxxx0 .. rest of stack .. ;xxxxxxx8 return OnCommand address <- RSP 未对于16字节 ;======================================== push rbp ;======================================== ;xxxxxxx0 .. rbp .. <- RSP 对于16字节 ;xxxxxxx8 return OnCommand address ;======================================== mov rbp,rsp IF _WIN64 ne 0 ;MYPROLOGUE ;///////////X64影子空间(自定义宏) mov [rbp + 16], rcx ;//因为下面的函数只使用了hWnd和wParam mov [rbp + 24], rdx sub rsp,4*8 ;//偶数个8对齐16字节,PostMessage有4个参数 ;//如果取3*8将覆盖掉rbp的原值(变成0) ;//如果取2*8则覆盖掉rbp的原值及OnCommand函数返回地址(变成0) ENDIF mov rax,wParam ;.if eax == IDM_EXIT @IF <<cmp eax,IDM_EXIT>>,EQUAL? @INVOKE PostMessage, hWnd, WM_CLOSE, 0, 0 ;.endif @ENDIF leave ret OnCommand endp OPTION PROLOGUE:PROLOGUEDEF OPTION EPILOGUE:EPILOGUEDEF ;*** window procedure OPTION PROLOGUE:None OPTION EPILOGUE:None WndProc proc hWnd:HWND,message:UINT,wParam:WPARAM,lParam:LPARAM local ps:PAINTSTRUCT ;//注意包含了局部变量 ;;RECT struct ;;left dd ? ;;top dd ? ;;right dd ? ;;bottom dd ? ;;RECT ends ;;PAINTSTRUCT struct 8 ;;hdc LPSTR ? ;+8 ;;fErase dword ? ;+12 ;;rcPaint RECT <?> ;+44 ;;fRestore dword ? ;+48 ;;fIncUpdate dword ? ;+52 ;;rgbReserved byte 32 dup(?) ;+56 ;;PAINTSTRUCT ends ;======================================== ;xxxxxxx0 .. rest of stack .. ;xxxxxxx8 return WndProc address <- RSP 未对于16字节 ;======================================== push rbp ;======================================== ;xxxxxxx0 .. rbp .. <- RSP 对于16字节 ;xxxxxxx8 return WndProc address ;======================================== mov rbp,rsp IF _WIN64 ne 0 MYPROLOGUE ;///////////X64影子空间 sub rsp,56+5*8 ;//偶数个8对齐16字节 ;//结构体局部变量占56字节,加上DrawText子函数的5个参数 ENDIF mov eax,message @IF <<cmp eax,WM_COMMAND>>,EQUAL? ;eax==WM_COMMAND ? @INVOKE OnCommand,hWnd,wParam,lParam @ELSEIF <<cmp eax,WM_SIZE>>,EQUAL? ;eax==WM_SIZE ? @INVOKE InvalidateRect, hWnd, 0, 1 ;send WM_PAINT if size changes @ELSEIF <<cmp eax,WM_PAINT>>,EQUAL? ;eax==WM_PAINT ? @INVOKE BeginPaint, hWnd, addr ps @INVOKE GetClientRect, hWnd, addr ps.rcPaint @INVOKE DrawText, ps.hdc, offset szHello, 12, addr ps.rcPaint, DT_CENTER or DT_VCENTER or DT_SINGLELINE @INVOKE EndPaint, hWnd, addr ps xor rax,rax @ELSEIF <<cmp eax,WM_DESTROY>>,EQUAL? ;eax==WM_DESTROY ? @INVOKE PostQuitMessage, 0 xor rax,rax @ELSE xor rax,rax mov eax,message @INVOKE DefWindowProc, hWnd, rax, wParam, lParam @ENDIF leave ret WndProc endp OPTION PROLOGUE:PROLOGUEDEF OPTION EPILOGUE:EPILOGUEDEF ;*** InitApplication ;*** registers window class and creates main window OPTION PROLOGUE:None OPTION EPILOGUE:None InitApplication proc hInstance:HINSTANCE local wc:WNDCLASS ;======================================== ;xxxxxxx0 .. rest of stack .. ;xxxxxxx8 return InitApplication address <- RSP 未对于16字节 ;======================================== push rbp ;======================================== ;xxxxxxx0 .. rbp .. <- RSP 对于16字节 ;xxxxxxx8 return InitApplication address ;======================================== mov rbp,rsp IF _WIN64 ne 0 MYPROLOGUE ;///////////X64影子空间 sub rsp,(12)*8 ;//偶数个8,最小分配 ,wc结构使用后被CreateWindowExA函数的12个参数覆盖 ENDIF @INVOKE RtlZeroMemory,addr wc,sizeof wc mov wc.style,0 lea rax, WndProc mov wc.lpfnWndProc,rax mov rax,hInstance mov wc.hInstance,rax mov wc.hIcon,0 @INVOKE LoadCursor, NULL, IDC_ARROW mov wc.hCursor,rax mov wc.hbrBackground,COLOR_WINDOW + 1 mov wc.lpszMenuName,IDR_MENU1 lea rax, szClass mov wc.lpszClassName, rax @INVOKE RegisterClass, addr wc test rax,rax jz exit @INVOKE CreateWindowExA,NULL,offset szClass,offset szWnd,WS_OVERLAPPEDWINDOW,/ CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,0,0,hInstance,0 @IF <<cmp rax,0>>,NOEQUAL? ;eax!=0 ? push rax @INVOKE ShowWindow, rax, SW_SHOWNORMAL pop rax @ELSE @INVOKE MessageBoxA,0,0,0,0 @ENDIF exit: leave ret InitApplication endp OPTION PROLOGUE:PROLOGUEDEF OPTION EPILOGUE:EPILOGUEDEF ;*** WINMAIN *** OPTION PROLOGUE:None OPTION EPILOGUE:None WinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpszCmdline:LPSTR, cmdshow:UINT local @msg:MSG ;;POINT STRUCT ;; x DWORD ? ;; y DWORD ? ;;POINT ENDS ;;MSG STRUCT ;; hwnd LPSTR ? +8 ;; message DWORD ? +16 ;; wParam WPARAM ? +24 ;; lParam LPARAM ? +32 ;; time DWORD ? +36 ;; pt POINT <?> +48 ;;MSG ENDS ;=============================== ;xxxxxxx0 .. rest of stack .. ;xxxxxxx8 return WinMain address <- RSP 未对于16字节 ;=============================== push rbp ;=============================== ;xxxxxxx0 .. rbp .. <- RSP 对于16字节 ;xxxxxxx8 return WinMain address ;=============================== mov rbp,rsp IF _WIN64 ne 0 MYPROLOGUE ;///////////X64影子空间 sub rsp,48+4*8 ;//偶数个8对齐16字节 ;//最小分配 @msg局部变量的每个成员均要使用,不能被覆盖掉! ENDIF @INVOKE InitApplication, hInstance test rax,rax jz exit next: @INVOKE GetMessage, addr @msg, 0, 0, 0 test rax,rax jz exit @INVOKE DispatchMessage, addr @msg jmp next exit: xor rax, rax leave ret WinMain endp OPTION PROLOGUE:PROLOGUEDEF OPTION EPILOGUE:EPILOGUEDEF main proc ;=============================== ;xxxxxxx0 .. rest of stack .. ;xxxxxxx8 return main address <- RSP 未对于16字节 ;=============================== sub rsp,28h ;=============================== ;xxxxxxx8 (arg4 spill)r9 ;xxxxxxx0 (arg3 spill)r8 ;xxxxxxx8 (arg2 spill)rdx ;xxxxxxx0 (arg1 spill)rcx <-RSP 16字节对齐 ;xxxxxxx8 return GetModuleHandle/WinMain address ;================================ @INVOKE GetModuleHandle, NULL @INVOKE WinMain, rax, 0, 0, 0 ;@INVOKE ExitProcess, rax add rsp,28h ;//另一种结束方法 ret main endp end