Procedures and Functions in Assembly Language

Procedures and Functions allows our code to break into small manageable, reusable blocks.

What are Procedures and Functions?

  • Procedures are blocks of code that perform a specific task and can be called from different parts of a program.
  • Functions are similar to procedures but typically return a value to the caller.

What is a Function?

In assembly language, a function is a sequence of instructions grouped together to perform a particular operation. By using functions, you can avoid writing the same code multiple times, thus reducing redundancy. Instead, you write the code once as a function and call it whenever needed.

Calling a Function in x86 NASM Assembly

In x86 assembly language, specifically using the Netwide Assembler (NASM), functions are called using the call instruction. The function itself is defined with a label that serves as its identifier. Once the function's task is complete, control returns to the calling code using the ret (return) instruction.

Here's a step-by-step breakdown of how functions work in NASM assembly:

1 Defining a Function:

  • A function is defined with a label that names the function. This label is followed by the code that constitutes the body of the function.
  • The function ends with the ret instruction, which returns control to the point immediately following the original call instruction.

2 Calling a Function:

  • The call instruction is used to invoke the function. When a function is called, the address of the next instruction (the one following the call) is pushed onto the stack. This is the return address.
  • The CPU then jumps to the address of the function label, executing the function's code.

3 Returning from a Function:

  • The ret instruction is used at the end of a function to return control to the caller. It pops the return address from the stack and jumps back to that address, resuming execution immediately after the call instruction.

Example: Defining and Calling a Function

Let's consider a simple example where we define a function called print_hello that prints the string "Hello, World!" to the screen.

section .data
hello_msg db 'Hello, World!', 0 ; Define the string with a null terminator

section .text
global _start

_start:
    ; Call the function
    call print_hello
    
    ; Exit the program
    mov eax, 1        ; sys_exit
    xor ebx, ebx      ; Exit code 0
    int 0x80          ; Interrupt to exit

; Define the function
print_hello:
    ; Print the string
    mov eax, 4        ; sys_write
    mov ebx, 1        ; File descriptor (stdout)
    mov ecx, hello_msg ; Pointer to the string
    mov edx, 13       ; Length of the string
    int 0x80          ; Interrupt to invoke the system call

    ; Return to the caller
    ret

In this example:

  • Data Section: We define the string hello_msg that contains "Hello, World!" followed by a null terminator.
  • Text Section:
    • We define the _start label as the entry point of the program.
    • We call the print_hello function using the call instruction.
    • After the function call, we exit the program using the sys_exit system call.
  • Function Definition:
    • The print_hello function starts with the label print_hello.
    • It uses the sys_write system call to print the string to the standard output (stdout).
    • Finally, it uses the ret instruction to return to the caller.
Step 2: Calling the Function
  • When the program starts, it begins execution at the _start label.
  • The call print_hello instruction pushes the address of the next instruction (the mov eax, 1 line) onto the stack and jumps to the print_hello label.
  • The code within the print_hello function is executed.
  • The ret instruction at the end of the print_hello function pops the return address off the stack and jumps back to that address, continuing execution from the mov eax, 1 instruction.

Parameter Passing

Functions often need to operate on data provided by the caller. In assembly language, parameters can be passed to functions using registers, the stack, or a combination of both.

Passing Parameters Using Registers

Passing parameters using registers is a common technique in assembly language, especially for performance-critical code, as it avoids the overhead of manipulating the stack. This method is often used in the fastcall calling convention, where the first few arguments are passed in specific registers.

In this explanation, we will use the x86 architecture with NASM syntax to demonstrate how to define functions and pass parameters using registers.

Overview of Registers Used for Parameter Passing

In the x86 architecture, the fastcall convention typically uses the following registers for the first few parameters:

  • ECX: The first parameter.
  • EDX: The second parameter.

Additional parameters are usually passed on the stack if there are more than two.

Example: Passing Two Parameters Using Registers

Let's create an example where we define a function multiply that takes two integers, multiplies them, and returns the result in the EAX register.

Step 1: Define the Function

First, we'll define the multiply function that expects two parameters to be passed in ECX and EDX.

section .text
global _start

_start:
    ; Initialize the parameters in registers
    mov ecx, 5        ; First parameter (multiplier)
    mov edx, 7        ; Second parameter (multiplicand)
    
    ; Call the multiply function
    call multiply

    ; At this point, EAX contains the result of the multiplication

    ; Exit the program
    mov eax, 1        ; sys_exit
    xor ebx, ebx      ; Exit code 0
    int 0x80          ; Interrupt to exit

; Define the multiply function
multiply:
    ; ECX = first parameter
    ; EDX = second parameter
    imul eax, ecx, edx ; EAX = ECX * EDX

    ; Return to the caller
    ret

In this example:

  • Initialization:
    • The _start section initializes two parameters in the ECX and EDX registers.
    • It then calls the multiply function.
  • Function Definition:
    • The multiply function uses the imul instruction to multiply the values in ECX and EDX, storing the result in EAX.
    • The ret instruction returns control to the caller.
Step 2: Calling the Function

When the program starts:

  1. The _start section sets up the parameters by moving the values 5 and 7 into the ECX and EDX registers, respectively.
  2. It calls the multiply function using the call instruction.
  3. The multiply function performs the multiplication (ECX * EDX) and stores the result in EAX.
  4. The ret instruction returns control to the _start section.
  5. The program then exits using the sys_exit system call.