Introduction
In this tip, we will learn how to create a simple Window using Assembly language. We will also gather knowledge about various assembly language instructions. As assembler, we will use the NASM assembler.
Background
I will assume that you are familiar with Intel x86 assembly syntax.
Why Assembly Language?
Studying and learning assembly language will give you a better idea about how a program actually executes in a computer. And also, you can learn assembly language to build up your efficiency on programming.
Why NASM Assembler?
NASM is an Open-Source 80x86 assembler designed for portability. That means it supports many platforms. Its syntax is very simple and easy to understand. The syntax is similar to Intel's but less complex. And that's why I have chosen this assembler.
For compiling the source-code of the project, you will need the NASM assembler. I used the NASM v2.11.05 for this project. So make sure you have it. If you don't have it, then you can easily download it from their website.
For creating an executable file from the object file produced by NASM, you will need a linker. There are several linkers available on the internet such as GoLink, ALink, JWlink, etc. I've used GoLink for this project.
Using NASM to Create the Window
First of all, we will define our program entry point and then we will create our window.
Entry Point
Entry point is where the code execution begins. In NASM, the entry point is top-level global function of .text
section, if you don't manually specify the entry point symbol name to your Linker.
We can write the entry point in .text
section in this way:
section .text
global START
START:
ret
Window
Inside the entry point function ( START
), we will create our window. In Windows, we use the CreateWindowExA
function for creating a window. As class name of the window, we will use 'MyClass
'. But before creating the window, we need to register the class name. We will use the RegisterClassExA
function for that purpose.
In the following codes, first we initialize a 48 bytes structure called OurWindowClass
declared in .bss
section. Then, we pass it to the RegisterClassExA
function parameter. And finally, we create our main window through calling the CreateWindowExA
function.
mov dword[OurWindowclass], 48
mov dword[OurWindowclass+4], CS_OWNDC|CS_HREDRAW|CS_VREDRAW
mov dword[OurWindowclass+8], WindowProc
mov dword[OurWindowclass+12], 0
mov dword[OurWindowclass+16], 0
mov eax, dword[Instance]
mov dword[OurWindowclass+20], eax
At offset 32
, we set the background brush for our window:
mov dword[OurWindowclass+32], 5
Here we assign the window class name called 'MyClass
' at offset 40
of OurWindowClass
structure:
mov dword[OurWindowclass+40], Windowclassname
Here, we will register our class. We will use the LEA
instruction to get pointer of OurWindowclass
into EAX
register and, push EAX
into system stack and then call the RegisterClassExA
function using CALL
instruction to register our window class:
lea eax, [OurWindowclass]
push eax
call RegisterClassExA
Here, we call the CreateWindowExA
function to create the main window:
push 0
push dword[Instance]
push 0
push 0
push 382
push 362
push 140
push 360
push WS_VISIBLE+WS_OVERLAPPEDWINDOW
push Windowname
push Windowclassname
push 0
call CreateWindowExA
In assembly language, to call a function, we push all the parameters in backwards order.
We know that CreateWindowExA
returns created window handle. In assembly language, return value generally exists on EAX
register. So, it's better we save the value before the EAX
register get modified by another operation:
mov dword[Windowhandle], eax
Windowhandle
is a variable declared in the .data
section of the program.
Window Message Handler
You may have noticed that we have assigned address of a function called WindowProc
at offset 8
of OurWindowClass
structure. This function will handle all the common messages such as WM_PAINT
, WM_SIZE
, WM_CLOSE
for our main window.
Here is our WindowProc
function:
global WindowProc
WindowProc:
push ebp
mov ebp, esp
In the above codes, we use the PUSH
instruction to save EBP
register value. Then we save ESP
register by putting it into EBP
.
The System will call this WindowProc
function to let us handle massages like WM_PAINT
, WM_CLOSE
, etc. for our window. But before calling the function, the system will push 4 parameters on stack. So that means WindowProc
function has 4 parameters.
The 1st parameter of WindowProc
is handle of sender window. The 2nd parameter is the message. The 3rd and 4th parameters contain additional message information. Each parameter is 4 bytes in size. So we can make a list of all the parameters in this way:
You might be wondering why parameter starts at 8 bytes ahead of EBP
pointer?
Well, when any function is called, the return instruction pointer is pushed to the stack, that is 4
bytes. And then, when we set up the stack frame, the EBP
register is pushed to the stack, that is more 4
bytes. So we have to skip over 8
bytes, to reach the first parameter.
Now, we will handle the WM_CLOSE
message to make sure that our window will close properly:
cmp dword[ebp+12], WM_CLOSE
jne Ldefault
push 0
call PostQuitMessage
The CMP
instruction compares the Message
parameter value with WM_CLOSE
constant and instead of storing the result into a register or something, it will affect the CPU FLAGS
. The JNE
is a conditional jump instruction. Here it will check the CPU FLAGS
whether the Message parameter is equal to WM_CLOSE
or not. If not equal, then it will jump to a label called Ldefault
. And that's how we do conditional operation in assembly language.
In the Ldefault
label, we call a function named DefWindowProcA
. DefWindowProcA
is a window procedure like our WindowProc
function. This function processes window messages that our application does not process. This function makes sure that every message is processed for our window.
Ldefault:
push dword[ebp+20]
push dword[ebp+16]
push dword[ebp+12]
push dword[ebp+8]
call DefWindowProcA
Finally, we restore the ESP
register again from EBP
, recover the previous EBP
and end the function with RET
instruction.
mov esp, ebp
pop ebp
ret
That's it!
Conclusion
The main purpose behind writing this tip is to give you information on GUI programming with assembly language. Although, assembly isn't an easy programming language at all. In fact, it is the hardest programming language to learn and it's very tough to remember the instructions set of assembly language. But practicing more and more will help you to learn this low-level programming language.