x86 Assembler using MASM32 Tutorial 9 - User Functions



Watch the video or follow the tutorial.



In this tutorial, number 9, we will create two user defined functions, one with parameters and one without. User functions have three components, the function prototype which tells the application about the form of the function (how many parameters it takes and the function name), the function definition which describes the inner workings of the function, what it will actually do and then thirdly, the function call, the part of the code that makes the function run. So, first of all open Visual MASM, if you haven't installed Visual MASM yet you can learn how to do it here. Once installed open it and learn create the default code template needed for all my tutorials here. This default code will be the starting point for most of our tutorials including this one. So, once all this is done you should be looking at the code window as seen below:


Scroll down until you see the "MASM32 proto types for Win32 functions and structures" section and under the line:

include c:\masm32\include\windows.inc

Add the following code:

; Function prototypes one with parameters and one without
function_Main proto
; 4 bytes, 2 bytes, 1 byte, 8 bytes
function_Other proto :DWORD , :WORD , :BYTE , :DWORD ,

Then scroll down to the .data section and add the following variables:

strTitle DB "Assembler Functions", 0
strMessage1 DB "Before Functions", 0
strMessage2 DB "Within Function_Main", 0
strMessage3 DB "Within Function_Other", 0
strMessage4 DB "After Functions", 0
int1 DW 30000

Then scroll down to the uninitialised data section which starts with .data? and add the following code:

; HINSTANCE is a 4 byte handle to an instance of something, if you have
; five windows in a program then each would have their own separate instance
; handle. In this way you can distinguish between the different windows
hInstance HINSTANCE ?
var1 DW ?

In the .code section after start: the following code should be added

; Signal start of app before function
Push MB_OK
Push Offset strTitle
Push Offset strMessage1
Push 0
Call MessageBox

; Run Function_Main that has no paramters
Call function_Main

; Push the four parameters for Funciotn_Other onto stack and call Function
Push SW_SHOWNORMAL
Push var1
Push int1
Push hInstance
Call function_Other

Push MB_OK
Push Offset strTitle
Push Offset strMessage4
Push 0
Call MessageBox

Invoke ExitProcess, 0

function_Main proc

Push MB_OK
Push Offset strTitle
Push Offset strMessage2
Push 0
Call MessageBox

; If Eax = Ebx then Xor Eax, Ebx puts zero in Eax
; If Eax != Ebx then Xor Eax, Ebx puts 1 in Eax
; Since Eax will always equal Eax Xor Eax, Eax always puts 0 in Eax
; Equal to Mov Eax, 0 but slower apparently
Xor Eax, Eax
Ret

function_Main endp

function_Other proc DW1:DWORD, W1:WORD, BYT1:BYTE, DW2:DWORD

LOCAL str1:LPSTR
LOCAL msg:MSG
LOCAL hwnd:HWND

Push MB_OK
Push Offset strTitle
Push Offset strMessage3
Push 0
Call MessageBox

Xor Eax, Eax
Ret

function_Other endp

Initally we set up the function prototypes or declarations at the top of the page. The first function Function_Main has no parameters and so we only need the function name and the word proto. The second function prototype again gives the function name but also lists the parameters that will be passed to the function at the time the function is called to be used within the function. Function_Other takes four parameters, a DWORD (4-bytes), a WORD (2-bytes), a BYTE (1-byte) and another DWORD (4-bytes). These variables that are passed into the function will be used internally by the inner workings of the function itself.

Next we have five variables in our data section. String variables are defined as byte arrays with each character given a single byte. that is to say the variable strTitle DB "Assembler Functions", 0 could be defined as

strTitle + 0 DB "A"
strTitle + 1 DB "s"
strTitle + 2 DB "s"
strTitle + 3 DB "e"
strTitle + 4 DB "m"
strTitle + 5 DB "b"
strTitle + 6 DB "l"
strTitle + 7 DB "e"
strTitle + 8 DB "r"
strTitle + 9 DB " "
strTitle + 10 DB "F"
strTitle + 11 DB "u"
strTitle + 12 DB "n"
strTitle + 13 DB "c"
strTitle + 14 DB "t"
strTitle + 15 DB "i"
strTitle + 16 DB "o"
strTitle + 17 DB "n"
strTitle + 18 DB "s"
strTitle + 19 DB "0"

Where the number after the variable name represents the number of bytes in memory after the first memory address assigned to the strTitle variable. We then define the rest of the variables with int1 being using the DW command to define a WORD object that has two bytes in memory. Two bytes will allow a value of up to 65,536 so 30000 fits nicely.

We then move onto our unitialised section where we define two unitialised variables. HINSTANCE is a 4 byte handle to an instance of something that is used to distinguish between different instances of the same type of object, like three separate windows. Each window would have a different HINSTANCE object with a different variable name attached to it. var1 is 2 two bytes in size and is also unitialised, meaning it is not given a value before the program is run. These variables are not important and are chosen at random. They are used because the have the specific size necessary to fit the parameters of the function Funciton_Other. Any variable or object can be used as long as it is the correct size. Please note though if you use the wrong sized variable no error will occur you will just not get the correct value being passed into your function. You need to check this yourself before you use the app.

Then we move onto the actual code section, which is broken down into four separate sections each with a MessageBox that will inform us that the program is at a certain point. Firstly the program displays a MessageBox with the message "Before Functions". This lets us know that the program is running and working but has not reached the section with the functions yet. Next Function_Main is called and since it has no parameters nothing is pushed onto the stack before the call. When the function is called the program locates the function definition, runs whatever code is inside it then returns to the original code location and continues. When Function_Main runs a MessageBox is displayed stating that we are "Inside Function_Main". Next we have the function call for Function_Other before which are four Push commands that push the variables that are to be passed into Function_Other as parameters onto the stack. Notice that the parameters are Pushed in reverse order, this is because the stack works on a last on first off approach so when the parameters are retrieved by the function they are found in the correct order because the last one will be seen first. When Function_Other runs a MessageBox is displayed stating that we are "Within Funciton_Other".

After both functions have returned another MessageBox tells us that we are "After Functions" and the ExitProcess API is called and the program ends. I generally like to use the Call command when calling functions pushing the parameters onto the stack beforehand just like it would appear in disassembly but just to show you there is another way. The Invoke command can be used to call a function and if you do decide to use this then notice that the parameters are not reversed.

In the last section of the code we have the two function definitions that show us exactly what happens inside them as they run. This is where we actually write the code for the function and I have included a couple of "local" variables which can only be used inside the function and cannot be accessed from any other place in the code. So hit "Save All" and then F9 and run the application and you should see the four MessageBoxes that show the different stages of the program.

The entire code looks like this:

; *************************************************************************
; 32-bit Windows Program
; *************************************************************************

.686                                      ; Enable 80686+ instruction set
.model flat, stdcall                ; Flat, 32-bit memory model (not used in 64-bit)
option casemap: none         ; Case sensitive syntax

; *************************************************************************
; MASM32 proto types for Win32 functions and structures
; *************************************************************************
include c:\masm32\include\windows.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\masm32rt.inc     ; for using ustr$() and such like

; Function prototypes one with parameters and one without
function_Main proto
; 4 bytes, 2 bytes, 1 byte, 8 bytes
function_Other proto :DWORD , :WORD , :BYTE , :DWORD ,

; *************************************************************************
; MASM32 object libraries
; *************************************************************************
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib

; *************************************************************************
; Our data section.
; *************************************************************************
.data

strTitle DB "Assembler Functions", 0
strMessage1 DB "Before Functions", 0
strMessage2 DB "Within Function_Main", 0
strMessage3 DB "Within Function_Other", 0
strMessage4 DB "After Functions", 0
int1 DW 30000

; *************************************************************************
; Our unintialised data section.
; *************************************************************************
.data?

; HINSTANCE is a 4 byte handle to an instance of something, if you have
; five windows in a program then each would have their own separate instance
; handle. In this way you can distinguish between the different windows
hInstance HINSTANCE ?
var1 DW ?

; *************************************************************************
; Our constant section.
; *************************************************************************
.const



; *************************************************************************
; Macros
; *************************************************************************



; *************************************************************************
; Our executable assembly code starts here in the .code section
; *************************************************************************
.code

start:

     ; Signal start of app before function
     Push MB_OK
     Push Offset strTitle
     Push Offset strMessage1
     Push 0
     Call MessageBox

     ; Run Function_Main that has no paramters
     Call function_Main

     ; Push the four parameters for Funciotn_Other onto stack and call Function
     Push SW_SHOWNORMAL
     Push var1
     Push int1
     Push hInstance
     Call function_Other

     Push MB_OK
     Push Offset strTitle
     Push Offset strMessage4
     Push 0
     Call MessageBox

     Invoke ExitProcess, 0

function_Main proc

     Push MB_OK
     Push Offset strTitle
     Push Offset strMessage2
     Push 0
     Call MessageBox

     ; If Eax = Ebx then Xor Eax, Ebx puts zero in Eax
     ; If Eax != Ebx then Xor Eax, Ebx puts 1 in Eax
     ; Since Eax will always equal Eax Xor Eax, Eax always puts 0 in Eax
     ; Equal to Mov Eax, 0 but slower apparently
     Xor Eax, Eax
     Ret

function_Main endp

function_Other proc DW1:DWORD, W1:WORD, BYT1:BYTE, DW2:DWORD

     LOCAL str1:LPSTR
     LOCAL msg:MSG
     LOCAL hwnd:HWND

     Push MB_OK
     Push Offset strTitle
     Push Offset strMessage3
     Push 0
     Call MessageBox

     Xor Eax, Eax
     Ret

function_Other endp

end start


Thank you for following this tutorial, I hope you found it useful. In the next tutorial we will have a look at comparing two strings and determining whether they match or not, so until then, enjoy.

Link to a text file with complete source code and more comments - here.