Page 1 of 1

Assembler x64 : Writing a 64-bit Frame Window Implementing a 64-bit frame window

#1 Martyn.Rae  Icon User is offline

  • The programming dinosaur
  • member icon

Reputation: 540
  • View blog
  • Posts: 1,406
  • Joined: 22-August 09

Posted 08 April 2010 - 11:20 AM

Assembler x64 : Writing a 64-bit Frame Window

Introduction

This tutorial is aimed at providing the reader with some insight as to the effort it takes to write a frame window that can be resized. The code has been based upon the code I wrote for the tutorial Assembler : Our First Windows Application, and took me about 10 minutes to convert.

Prerequisites

I need not remind you that in order to compile and run this code, you need to be running either a 64-bit edition of Microsoft Windows. Without that, you will not be able to compile and run the code provided below.

64-bit assembler programming

Many years ago, I had the pleasure of writing assembler applications on the Motorola 68000 processor which had 8 data registers and 8 index registers. When I moved over to the Intel 8086 processor, I thought I had died and gone to hell as there was only the al, bl, cl, dl, si and di registers (I never counted sp and bp). Since then, I have got used to the fewer registers and was overjoyed when I learnt that the x64 processor was to have the number of registers increased by r8 to r15. The first assembler application I wrote for the x64 processor was a disaster, all because of the extra registers! My advice to you out there is don't overuse them - just be sensible.

Before we take a look at the code, we need to look at how Microsoft have implemented their 64-bit Windows API, as it is rather unusual. The first parameter is passed in the rcx register, the second parameter in the rdx register, the third parameter in the r8 register and the fourth parameter in the r9 register. API calls that require more parameters need to have those passed on the stack, but you need to reserve 4 x 64 bits (or 4x8 bytes) in case the API you are calling needs to save the rcx, rdx,r8 and r9 registers into the parameter locations where they would have been presented had you not used the registers. This gets rather messy, as adjustments need to be made to the stack location manually in order to gain the benefit (if any) of passing the first four parameters in registers. The Windows API call does not make any adjustment to the stack pointer upon return from the call like they do in 32-bit standard calls. Confused? I was when I wrote my first 64-bit application! I will try and explain how we do this as we discuss the code in detail below.

The code

                    format              MS64 COFF
                    include             'c:\fasm\include\win64ax.inc'

                    ;==================================================
                    ; This is a simple window application that draws a
                    ; frame window whose client area is painted white.
                    ; The frame window can be resized and closed.
                    ;==================================================

                    public              WinMainCRTStartup
                    ;==================================================
                    ; A list of external definitiona that we need
                    ;==================================================
                    extrn               DefWindowProcA:dword
                    extrn               BeginPaint:dword
                    extrn               CreateSolidBrush:dword
                    extrn               GetClientRect:dword
                    extrn               FillRect:dword
                    extrn               DeleteObject:dword
                    extrn               EndPaint:dword
                    extrn               InvalidateRect:dword
                    extrn               PostQuitMessage:dword
                    extrn               GetModuleHandleA:dword
                    extrn               RegisterClassA:dword
                    extrn               CreateWindowExA:dword
                    extrn               ShowWindow:dword
                    extrn               GetMessageA:dword
                    extrn               TranslateMessage:dword
                    extrn               DispatchMessageA:dword
                    extrn               ExitProcess:dword

                    ;==================================================
                    ; Application data
                    ;==================================================
                    .data
app_instance        dq                  0
window_handle       dq                  0
class_name          db                  'main window class', 0
window_title        db                  'Assembler Frame Window', 0 

message             MSG                 <>
main_class          WNDCLASS            CS_OWNDC, main_window_proc, \
                                        0, 0, NULL, NULL, \
                                        NULL, NULL,NULL, class_name

                    ;==================================================
                    ; Start of the code section
                    ;==================================================
                    section             '.text' readable executable
                    ;==================================================
                    ; The main window message processing procedure
                    ;==================================================
proc                main_window_proc    hwnd:QWORD, wmsg:QWORD, \
                                        wparam:QWORD, lparam:QWORD
                    local               hDC:QWORD, hBrush:QWORD, 
                    local               paint_struct:PAINTSTRUCT
                    local               client_rect:RECT

                    sub                 rsp, 32
                    mov                 [hwnd], rcx
                    mov                 [wmsg], rdx
                    mov                 [wparam], r8
                    mov                 [lparam], r9

                    ;==================================================
                    ; Process the windows message
                    ;==================================================
                    cmp                 rdx, WM_CLOSE
                    je                  .close_window
                    cmp                 rdx, WM_PAINT
                    je                  .paint_window
                    cmp                 rdx, WM_SIZE
                    je                  .size_window
                    ;==================================================
                    ; Any message not processed above falls through 
                    ; to the default window message handler
                    ;==================================================
.defwndproc:        call                DefWindowProcA
                    jmp                 .finish
                    ;==================================================
                    ; Paint the window
                    ;==================================================
.paint_window:      lea                 rdx, [paint_struct]
                    mov                 rcx, [hwnd]
                    call                BeginPaint
                    mov                 [hDC], rax
                    mov                 rcx, 0FFFFFFH
                    call                CreateSolidBrush
                    mov                 [hBrush], rax
                    lea                 rdx, [client_rect]
                    mov                 rcx, [hwnd]
                    call                GetClientRect
                    mov                 r8, [hBrush]
                    lea                 rdx, [client_rect]
                    mov                 rcx, [hDC]
                    call                FillRect
                    mov                 rcx, [hBrush]
                    call                DeleteObject
                    lea                 rdx, [paint_struct]
                    mov                 rcx, [hwnd]
                    call                EndPaint
                    xor                 rax, rax
                    jmp                 .finish
                    ;==================================================
                    ; Ensure that when the window is resized, 
                    ; it is repainted
                    ;==================================================
.size_window:       mov                 r8, TRUE
                    xor                 rdx, rdx
                    mov                 rcx, [hwnd]
                    call                InvalidateRect
                    xor                 rax, rax
                    jmp                 .finish
                    ;==================================================
                    ; Post the quit message when the window is
                    ; closed
                    ;==================================================
.close_window:      xor                 rcx, rcx
                    call                PostQuitMessage
                    xor                 rax, rax
.finish:            add                 rsp, 32
                    ret
                    endp

                    ;==================================================
                    ; Windows main entry point
                    ;==================================================
WinMainCRTStartup:  sub                 rsp, 96
                    xor                 rcx, rcx
                    call                GetModuleHandleA
                    mov                 [app_instance], rax
                    ;==================================================
                    ; Set up the WNDClASS structure
                    ;==================================================
                    mov                 [main_class.hInstance], rax
                    ;==================================================
                    ; Register the window class
                    ;==================================================
                    mov                 rcx, main_class
                    call                RegisterClassA
                    ;==================================================
                    ; Create the frame window
                    ;==================================================
                    xor                 rax, rax
                    mov                 [rsp+88], rax
                    mov                 rbx, [app_instance]
                    mov                 [rsp+80], rbx
                    mov                 [rsp+72], rax
                    mov                 [rsp+64], rax
                    mov                 rax, 600
                    mov                 [rsp+56], rax
                    mov                 rax, 800
                    mov                 [rsp+48], rax
                    mov                 rax, 100
                    mov                 [rsp+40], rax
                    mov                 [rsp+32], rax
                    mov                 r9, WS_OVERLAPPEDWINDOW
                    lea                 r8, [window_title]
                    lea                 rdx, [class_name]
                    mov                 rcx, WS_EX_WINDOWEDGE or \
                                             WS_EX_CLIENTEDGE
                    call                CreateWindowExA
                    mov                 [window_handle], rax
                    ;==================================================
                    ; Make the window visible to the user
                    ;==================================================
                    mov                 rdx, SW_SHOW
                    mov                 rcx, rax
                    call                ShowWindow
                    ;==================================================
                    ; Windows message loop
                    ;==================================================
message_loop:       xor                 r9, r9
                    xor                 r8, r8
                    xor                 rdx, rdx
                    mov                 rcx, message
                    call                GetMessageA
                    ;==================================================
                    ; Check to see if we are closing
                    ;==================================================
                    or                  rax, rax
                    je                  message_loop_ended
                    ;==================================================
                    ; Translate the message
                    ;==================================================
                    mov                 rcx, message
                    call                TranslateMessage
                    ;==================================================
                    ; Dispatch the message
                    ;==================================================
                    mov                 rcx, message
                    call                DispatchMessageA
                    jmp                 message_loop
                    ;==================================================
                    ; Exit the process and return control back 
                    ; to the system
                    ;==================================================
message_loop_ended: add                 rsp, 96
                    xor                 rcx, rcx
                    call                ExitProcess



The first line that I would like to discuss, is the first line and last two lines of the main_window_proc routine.

                    sub                 rsp, 32
...
.finish:            add                 rsp, 32
                    ret




This is subtracting 32 bytes from the stack pointer which is the area required for any Windows API call that uses four or less registers. If you scan through the API calls covered in that routine, there is no API call that requires more than that. Those API calls that want to save the registers into their respective locations on the stack are not going to affect any stack frames below. Now, if you scan through that routine (and indeed the entire code provided above you will not see any push or pop instructions. They effectively become quite dangerous in 64-bit assembler as it requires an unbelievable overview of what is happening on the stack not just in your routine, but in the callers stack frame and the callers callers stack frame and is a devil of a job to debug when it goes wrong. Again, I speak from experience on that point as well!

Now let's turn our attention to the first line and last three lines of the WinMainCRTStartup function.

WinMainCRTStartup:  sub                 rsp, 96
...
message_loop_ended: add                 rsp, 96
                    xor                 rcx, rcx
                    call                ExitProcess



Here, we are making space on the stack for 96 bytes, or in other words 12x64-bit parameters. The reason is the CreateWindowEx routine which requires 12 parameters.

                    xor                 rax, rax
                    mov                 [rsp+88], rax
                    mov                 rbx, [app_instance]
                    mov                 [rsp+80], rbx
                    mov                 [rsp+72], rax
                    mov                 [rsp+64], rax
                    mov                 rax, 600
                    mov                 [rsp+56], rax
                    mov                 rax, 800
                    mov                 [rsp+48], rax
                    mov                 rax, 100
                    mov                 [rsp+40], rax
                    mov                 [rsp+32], rax
                    mov                 r9, WS_OVERLAPPEDWINDOW
                    lea                 r8, [window_title]
                    lea                 rdx, [class_name]
                    mov                 rcx, WS_EX_WINDOWEDGE or \
                                             WS_EX_CLIENTEDGE
                    call                CreateWindowExA



Notice that the last parameter set is the x-position of the window which is set at location [rsp+32]. That leaves 4x64-bit slots for the rcx, rdx, r8 and r9 registers.

It does take a little bit of time to get your head round, but hopefully you have begun to see what 64-bit assembler programming is all about.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1