I try to make a minimal 64-bit windows gui program in assembly (!). I used the MASM compiler (ml64.exe), and the MASM64 include files (https://masm32.com/board/index.php?topic=10052.0). I do this out of curiosity and to understand how windows / x86 cpu actually works.
Following after this is the whole listing. I compiled this program with: ml64.exe helloworld64.exe /link /subsystem:windows /entry:WinMain
option casemap:none
option prologue:none
option epilogue:none
include win64.inc ; MASM64 include file
include kernel32.inc ; Handles, modules, paths, etc
include user32.inc ; Windows, controls, etc
includelib kernel32.Lib
includelib user32.lib
.data
ClassName byte "MyWinClass", 0, 0, 0
AppName byte "Testprogramma by Tijs", 0, 0, 0
.data?
hInstance HINSTANCE ?
uExitCode dword ?
.code
WinMain proc
local wc:WNDCLASSEX ; Create these vars on the stack, hence LOCAL
local msg:MSG
local hwnd:HWND
local sui:STARTUPINFO
local nCmdShow:dword
; prologue:
push rbp
mov rbp, rsp
sub rsp, 170h
; GetStartupInfoA:
lea rcx, sui
call GetStartupInfoA ; Find out if wShowWindow should be used
test sui.dwFlags, STARTF_USESHOWWINDOW
jz @1
movzx eax, sui.wShowWindow ; If the show window flag bit was nonzero, we use wShowWindow
mov nCmdShow, eax
jmp @2
@1:
mov nCmdShow, SW_SHOWDEFAULT ; Use the default
@2:
; GetModuleHandle:
mov rcx, NULL ; Get the instance handle of our app (NULL means ourselves)
call GetModuleHandle ; GetModuleHandle will return instance handle in EAX
mov hInstance, rax
; Fill WNDCLASSEX:
mov wc.cbSize, sizeof WNDCLASSEX ; Fill in the values in the members of our windowclass
mov wc.style, CS_HREDRAW or CS_VREDRAW ; Redraw if resized in either dimension
lea rax, WndProc
mov wc.lpfnWndProc, rax ; Our callback function to handle window messages
mov wc.cbClsExtra, 0 ; No extra class data
mov wc.cbWndExtra, 0 ; No extra window data
mov rax, hInstance
mov wc.hInstance, rax ; Our instance handle
mov wc.hbrBackground, COLOR_3DSHADOW+1 ; Default brush colors are color plus one
mov wc.lpszMenuName, NULL ; No app menu
lea rax, ClassName
mov wc.lpszClassName, rax ; The window's class name
; loadicon
mov rcx, 0 ; hInstance
mov rdx, IDI_APPLICATION ; Use the default application icon (lpIconName)
call LoadIcon
mov wc.hIcon, rax
mov wc.hIconSm, rax
; LoadCursor
mov rcx, 0
mov rdx, IDC_ARROW ; Get the default "arrow" mouse cursor
call LoadCursor
mov wc.hCursor, rax
; RegisterClassExA
lea rcx, wc
call RegisterClassExA ; Register the window class
cmp ax, 0 ; Registering failed
je WinMainRet
; CreateWindowExA
mov rcx, 0 ; Extended style bits, if any
lea rdx, ClassName ; The window class name of what we're creating
lea r8, AppName ; The window title (our application name)
mov r9d, WS_OVERLAPPEDWINDOW + WS_VISIBLE ; Window stytle (normal and visible)
mov eax, CW_USEDEFAULT
mov dword ptr [rsp + 20], eax ; X
mov dword ptr [rsp + 28], eax ; Y
mov dword ptr [rsp + 30], 800 ; Our requested width
mov dword ptr [rsp + 38], 600 ; Our requested height
mov qword ptr [rsp + 40], NULL ; Parent window (if we were a child window)
mov qword ptr [rsp + 48], NULL ; Menu handle
mov rax, hInstance
mov [rsp + 50], rax ; Our app instance handle
mov qword ptr [rsp + 58], 0
call CreateWindowExA
cmp rax, NULL
je WinMainRet ; Fail and bail on NULL handle returned
mov hwnd, rax ; Window handle is the result, returned in eax
; ShowWindow(hWnd, nCmdShow)
mov rcx, rax
mov edx, nCmdShow
call ShowWindow
; UpdateWindow(hWnd)
mov rcx, hwnd ; Force a paint of our window
call UpdateWindow
MessageLoop:
lea rcx, msg
mov rdx, NULL
mov r8d, 0
mov r9d, 0
call GetMessage ; Get a message from the application's message queue
cmp eax, 0 ; When GetMessage returns 0, it's time to exit
je DoneMessages
lea rcx, msg ; Translate 'msg'
call TranslateMessage
lea rcx, msg ; Dispatch 'msg'
call DispatchMessage
jmp MessageLoop
DoneMessages:
mov rax, msg.wParam ; Return wParam of last message processed
WinMainRet:
mov uExitCode, eax
; MessageBoxA
;mov rcx, NULL
;lea rdx, ClassName
;lea r8, ClassName
;mov r9d, MB_OK
;call MessageBoxA
mov ecx, uExitCode
call ExitProcess
; epilogue:
mov rsp, rbp
pop rbp
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
push rbp
mov rbp, rsp
sub rsp, 20h
cmp edx, WM_DESTROY
jne NotWMDestroy
; WM_DESTROY:
mov ecx, 0 ; WM_DESTROY received, post our quit msg
call PostQuitMessage ; Quit our application
xor rax, rax ; Return 0 to indicate we handled it
mov rsp, rbp
pop rbp
ret
NotWMDestroy:
cmp edx, WM_PAINT
jne Default
; WM_PAINT:
xor rax, rax
mov rsp, rbp
pop rbp
ret
Default:
; DEFAULT:
call DefWindowProc ; Forward message on to default processing and
mov rsp, rbp
pop rbp
ret ; return whatever it does
WndProc endp
end
The program works, but the window is not initialized correctly. The window is the smallest possible size, with only the minimize, resize and close buttons visible. The window should be 800x600 pixels in size.
I want to make a program with minimum filesize. But I don't know how to debug this program.
EDIT: Jester pointed out that the numbers in mov dword ptr [rsp + 20], eax and the lines after that are in decimal, but they should be in hexadecimal (mov dword ptr [rsp + 20h], eax)