Laborator 8 - Theory

Function calls

Function libraries

Even if the assembly language directly uses the hardware components of the system, there are frequently used pieces of code and writing them every single time would be impractical (for example, communicating with input/output devices, which often implies complex protocols). One of the roles of the operating system is to abstract the hardware machine for the programmer, providing multiple libraries of functions that can be called for certain frequent operations, such as printing the data in a certain format, finding a substring in a string or different mathematical functions.

Using a functions means transferring control to the procedure address, executing the code corresponding to the function, and returning to the instruction immediatley after the call of the function:

Using external functions. Call conventions

For functions calls we use the instruction CALL.

Syntax:

call <system_function_name>

Semantics:

CALL pushes the address of the instruction immediately after itself (return address) onto the stack and then transfers control to the procedure address. This allows a RET instruction to pop the return address and thus return control to the instruction immediately after the CALL instruction.

Calling a system function. Parameters

Often functions have parameters and a return value. There are many conventions for passing parameters, but we will use the cdecl (which stands for C declaration) calling convention.

The calling convention is not related to the syntax of the assembly language, but it is a ”contract” between the caller and the callee, specifying how parameters are passed and how the value is returned.

cdecl convention

  • Arguments are placed on the stack from right to left
  • Implicit result returned by the functions are stored in EAX
  • Registers EAX, ECX, EDX are used within the functions ( so they can be overwritten! )
  • Pay attention! If you need the values soterd in EAX, ECX, EDS you need to store them somewhere else before the function call (in memory variables, on the stack or in different registers)
  • The function doesn’t empty the stack, it is the responsibility of the programmer to pop the arguments out of the stack after the function call.
  • Example: printf, scanf

Standard msvcrt functions

Printing on the screen

For printing a text on the screen we use the printf function, which requires a certain format.

Syntax of the printf function in a high level programming language is:

int printf(const char * format, variable_1, constant_2, ...);

The printf function respects the cdecl convention.

The first argument of the function is the a string containing the printing format, followed by the same number of arguments (constant values or variable names) as specified in the format.

The character string representing the format can contain certain formatting markups, starting with the character ’%’, which will be replaced by the values given in the following arguments.

Code Type Example Value representation dimension
c Character a byte
d or i Signed decimal integer 392 dword
u Unsigned decimal integer 7235 dword
x Unsigned hexadecimal integer 7fa dword
s String (terminated with a 0) example string of bytes terminated with 0
 

When the printf() function gets executed, the doubleword from the top of the stack contains the Return Address and below this doubleword we have another doubleword which contains the offset of the formatting string (which is the first argument of the printf function). On the stack, below this second doubleword we have other doublewords corresponding to the other arguments of the printf() function. The format descriptors %d, %i, %c, %x, %u will always be applied to a doubleword from the stack (even if the format descriptor is %c, it also uses a doubleword from the stack, but it will take only the least significant byte of this doubleword). Consequently, the first format descriptor will be applied to the third doubleword from the stack (counting down from the top of the stack). The second (possible) format descriptor will be applied o the fourth doubleword from the stack (counting down from the top of the stack), the third (possible) format descriptor from the formatting string will pe applied to the fifth doubleword from the stack (counting down from the top of the stack) and so on.

Example:

Printing a message
  • In a high level programming language:
  • 
    	printf("I have to go to school today.");
    	
  • Equivalent in assembly language:
  • 
    	segment data use32 class=data
    		message  db "I have to go to school today.", 0  ; definining the message
    	segment code use32 class=code
    		; ...
    		 push dword message  ; pushing the parameter on the stack
    		 call [printf]       ; calling the printf function
    		 add esp, 4 * 1     ; cleaning the parameters from the stack
    		; ...
    
Printing a signed integer in base 10
  • In a high level programming language:
  • 
    	printf("%d", -17);
    	
  • Equivalent in assembly language:
  • 
    	segment data use32 class=data
    		format  db "%d", 0  ; definining the format
    	segment code use32 class=code
    		; ...
    		 push dword -17  ; pushing the parameters on the stack from right to left
    		 push dword format  
    		 call [printf]       ; calling the printf function
    		 add esp, 4 * 2     ; cleaning the parameters from the stack
    		; ...
    
Printing an integer in base 16
  • In a high level programming language:
  • 
    	printf("%x", 0xAB);
    	
  • Equivalent in assembly language:
  • 
    	segment data use32 class=data
    		format  db "%x", 0  ; definining the format
    	segment code use32 class=code
    		; ...
    		 push dword 0xAB  ; pushing the parameters on the stack from right to left
    		 push dword format  
    		 call [printf]       ; calling the printf function
    		 add esp, 4 * 2     ; cleaning the parameters from the stack
    		; ...
    
Printing a message that contains an integer in base 10, stored in a variable n
  • In a high level programming language:
  • 
    	printf("It's week %d of school", n);
    	
  • Equivalent in assembly language:
  • 
    	segment data use32 class=data
    		n dd 8
    		format  db "It's week %d of school", 0  ; definining the format
    	segment code use32 class=code
    		; ...
    		 push dword [n]  ; pushing the value of n on the stack
    		 push dword format  
    		 call [printf]       ; calling the printf function
    		 add esp, 4 * 2     ; cleaning the parameters from the stack
    		; ...
    
Priting a message that contains more integers in base 10.
  • In a high level programming language:
  • 
    	printf("It's semester %d, week %d of school.", 1, 8);
    	
  • Equivalent in assembly language:
  • 
    	segment data use32 class=data
    		format  db "It's semester %d, week %d of school.", 0  ; definining the format
    	segment code use32 class=code
    		; ...
    		 push dword 8  ; pushing parameters on the stack
    		 push dword 1 
    		 push dword format  
    		 call [printf]       ; calling the printf function
    		 add esp, 4 * 3     ; cleaning the parameters from the stack
    		; ...
    
 

Reading an input from keyboard

For reading input data from keyboard we use the scanf function.

Syntax of the scanf function in a high level programming language is:

int scanf(const char * format, variable_address_1, ...);

The syntax of the scanf function is similar to the syntax of the printf function. The main difference is that its argument do not have to be constants or values of variables, but only addresses of variables, where the values read will be stored.

Example:

Reading an integer and storing it in the variable n
  • In a high level programming language:
  • 
    	scanf("%d", &n);
    		
    	 &n represent the address of the variable n where the function scanf stores the value read from keyboard
    	
  • Equivalent in assembly language:
  • 
    	segment data use32 class=data
    		n dd  0       ; defining the variable n
    		format  db "%d", 0  ; definining the format
    	segment code use32 class=code
    		; ...
    		push dword n       ; pushing the parameters on the stack from right to left
    		push dword format
    		call [scanf]       ; calling the scanf function for reading
    		add esp, 4 * 2     ; cleaning the parameters from the stack
    		; ...
    
    
    

Text file operations

Text file operations

A file is a sequence of bytes. In order to read from a file, we need to follow several steps:
  1. Open file, which falls into one of the following two cases:
    • Open existing file
    • Create new file and open it
  2. Read from a file / Write into a file
  3. Close file

Opening a file

For opening an existing file or creating a new file, we use the fopen function.

Syntax of the function

fopen in a high level programming language:
FILE * fopen(const char* file_name, const char * access_mode)
The fopen function respects the cdecl convention and it can be found in the msvcrt.dll library.

Arguments of the fopen function:

The first argument of the function is the address of a character string containing the name of the function. The second argument is the address of a character string containing the access mode for opening the file.
Access mode Meaning Description
r read - Open file for reading. The file must exist.
w write - If the file does not exist, it creates a new file with the given name and opens it for writing.
- If a file with the given name exists, it opens it for writing. It overwrites the content of the file and starts writing from the beginning of the file.
a append - If the file does not exist, it creates a new file and opens it for writing.
- If a file with the given name exists, it opens it for writing. It does not overwrite the content, it continues writing at the end of the file.
r+ read and write from/into existing file - Open file for reading and writing. The file must exist.
w+ read and write - If the file does not exist, it creates a new file and opens it for reading and writing.
- If a file with the given name exists, it opens it for reading and writing. It overwrites the content of the file and starts writing at the beginning of the file.
a+ read and append - If the file does not exist, it creates a new file and opens it for reading and writing.
- If a file with the given name exists, it opens it for reading and writing. It does not overwrite the content, it continues writing at the end of the file.

Observations:

  • The name of the function must include the extension (ex: name.txt, example.asm).
  • File are created or opened in the current directory (in the same directory where the asm source is located). Important: in order to open a file using its name, the file must be placed in the same folder as the asm source file, otherwise the file will not be found.
  • Writing operations will fail if the file was opened only for reading (ex. access mode "r"). Reading operations will fail for files opened only for writing or appending (ex. access mode "w", "a").
  • Both arguments of the fopen function represent character strings that have to be terminated with a 0 (similar to the format for the printf function).

The value returned by the fopen function:

If the file is successfully opened, EAX will contain the file descriptor (an identifier) which can be used for working with the file (reading and writing). If an error occurs, fopen will set EAX to the value 0.

Other observations:

It is important to check the value returned by the function in EAX before continuing to work with the file, in order to know whether the file was correctly opened. If a program opens more files using the fopen function, each value returned by the function must be stored separately, since each file has a unique identifier. When we are done working with a file, it is important to close the file ( it can be done at the end of the program - before exit). For closing the file we use the fclose function.

Writing into a file

For writing text into a file we use the fprintf function.

Syntax of the function

fprintf in a high level programming language:
int fprintf(FILE * stream, const char * format, <variable_1>, <constant_2>, <...>)
The fprintf function respects the cdecl convention and it can be found in the msvcrt.dll library. Syntax of the fprintf function is similar to the syntax of the printf function (used for printing on the screen). The difference is that, in addition to the parameters of the printf function, the fprintf function has the file descriptor as the first argument.

Arguments of the fprintf function

First argument of the function represents the file descriptor (identifier) returned by the fopen function call. The next argument of the function is a character string containing the format for printing, followed by the same number of arguments (constant values or variable names) as specified in the format. Similar to the printf function, the character string representing the format can contain certain formatting markups, starting with the character ’%’, which will be replaced by the values given in the following arguments.
Code Type Example Value representation dimension
c Character a byte
d or i Signed decimal integer 392 dword
u Unsigned decimal integer 7235 dword
x Unsigned hexadecimal integer 7fa dword
s String (terminated with a 0) example string of bytes terminated with 0

Reading from a file

For reading from a file we use the fread function.

Syntax of the function

fread in a high level programming language:
int fread(void * str, int size, int count, FILE * stream)
The fread function respects the cdecl convention and it can be found in the msvcrt.dll library.

Arguments of the fread function

The first argument of the fread functions represents the address of a string where the data read from file is stored. The second argument represent the size of one element that will be read from the file. The third argument represents the maximum number of elements to be read. The last argument of the function represents the file descriptor (identifier) returned by the fopen function call. When reading from a text file, the first argument of the fread function is a byte string and the second argument is 1 (= dimension of one byte). The third argument is the size of the byte string (number of elements).

The value returned by the fread function::

The fread function stores in EAX the number of elements read. If this number is below the value of the count argument, it means that either there was an error, or that the function got to the end of the file.

Observations:

If text files have large sizes, one cannot read the whole content of the file with one function call. In that case multiple fread function calls are necessary, until the whole content of the file is read. In the „Examples” section we will present such an example. In order to check whether we got to the end of the file, we can check if the value returned by fread is 0.

Closing an opened file

When we finish working with an opened file, this must be closed. This step must not be forgotten in a program that opened files. For closing a file we use the fclose function.

Syntax of the function

fclose in a high level programming language:
int fclose(FILE * descriptor)
The fclose function respects the cdecl convention and it can be found in the msvcrt.dll library.

Argument of the fclose function

The argument of the fclose function is the file descriptor (identifier) returned by the fopen function call.