Page 1 of 1

Assembler : Working With Parameters Parameters explained

#1 Martyn.Rae  Icon User is offline

  • The programming dinosaur
  • member icon

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

Posted 06 April 2010 - 12:57 PM

Assembler : Working With Parameters

Introduction

This tutorial looks at parameters passed to a routine that we have written ourselves.

New Instructions

We are introducing two new instruction in this tutorial

  • add - add memory to a register, register to memory ir register to register
  • ret - return from a subroutine


Processor stacks

It is important to know what happens on the stack when we call a routine such as the Windows API WriteFile. So let's look at calling this routine in more detail, and more specifically, what happens with the stack. So, here is a code snippet from the previous tutorial.

                    push        0                       ; We do not want overlapped I/O
                    push        dword bytes_written     ; The address of the number of bytes written
                    push        24                      ; The length of the text we are writing
                    push        prompt                  ; The address of the text we are writing
                    push        dword [output_handle]   ; The handle returned from GetStdHandle call
                    call        _WriteFile@20           ; Write the text to the standard output handle



Let's arbitrarily say that at the instruction immediately prior to the first line of the snippet, our stack pointer is pointing to an address x. The first instruction to be executed is a push a 32-bit value onto the stack of zero. So, the processor in executing this instruction will subtract 4 from the stack pointer and set the 32-bits that it is now point to to zero. Our stack pointer is now pointing to x-4.

Next, we push the address of the bytes_written location which is anther 32-bits, so the processor subtracts 4 from the stack pointer and sets the 32-bits that it is now pointing at, to the address of bytes_written. Our stack pointer is now pointing to x-8.

The next instruction to be executed is a push a 32-bit value onto the stack of 24. So, the processor in executing this instruction will subtract 4 from the stack pointer and set the 32-bits that it is now point to to zero. Our stack pointer is now pointing to x-12.

Next, we push another 32-bit value onto the stack which is the address of the buffer to output. Our stack pointer is now pointing to x-16.

Finally, we push the 32-bit handle onto the stack, which further subtracts 4 from the stack pointer giving us an address of x-20.

The next instruction is the call instruction. This causes the processor to subtract another 4 bytes of the stack pointer and set the address that the stack pointer is pointing at to the address of the instruction immediately after the call. It then sets the instruction pointer to the address of the routine we are calling which is the WriteFile windows API routine. So the stack pointer is set to x-24, when control reaches the first instruction in the WriteFile routine.

Now, seen from the WriteFile routines perspective, the 32-bit address pointed to by the stack pointer is the return address of the function. At esp+4, we have the output file handle, at esp+8 we have the address of the buffer that needs to be written, at esp+12, we have the length of the buffer to be written, at esp+16 we have the address of the 32-bit value in which to place the number of bytes written and finally, at esp+20 we have the address of the overlapped I/O structure that is set to zero.

The last instruction that the WriteFile routine will execute is a ret instruction. This instruction causes the processor to remove the 32-bit value from the stack. That would make the stack pointer now be at x-20 which would not be correct as once the routine has completed, you would expect the stack pointer to be pointing to the original location x.

Stack parameter cleanup

The parameters that have been placed onto a stack need to be removed, and there are two ways of doing this. Either the caller cleans up the stack, or the called routine cleans up the stack. This is a problem that has been resolved in C++ with the use of the __stdcall and __cdecl keywords. A function or procedure declared as __stdcall means that the called routine cleans up the stack before it returns and functions and routines declared as __cdecl means that the caller cleans up the stack upon return.

The advantage of the __cdecl declared routine is that the caller of a routine knows how many parameters have been placed onto the stack permitting a variable number of arguments (as in the printf type of routine), whereas the __stdcall routine must have and only have the number of arguments it expects.

In assembler, we can always assume that if the C++ call to a routine is __stdcall, then the caller will clean up the stack before control is returned to the caller, and if the C++ call to a routine is __cdecl, then the caller has to clean up the stack.

Referring back to the WriteFile function, it will actually execute a ret 20 instruction which the processor executes as remove the return address from the stack then add 20 to the stack pointer. Then set the instruction pointer to the return address and continue execution.

To round things off, let's say we were using the printf routine to write our data, the code snippet for this would be

                    push        prompt                  ; Stack the address of the message
                    call        _printf                 ; Call the printf routine
                    add         esp, 4                  ; Clean up the stack



We have stacked one parameter the address of the prompt message, and as this does not contain any formatting tokens, it is the only parameter we need. The printf function is called, then upon return we add 4 to the stack to get rid of the parameter we pushed onto the stack prior to the call.

The code

The code below is a modified version taken from the previous tutorial. Here, I have created a routine called FileWrite that takes two parameters; the address of the buffer to write, and the length of the buffer to write. The function simply supplies these to the WriteFile function in the appropriate places. Let's look at this part more closely.

FileWrite:          mov         ebp, esp                ; Set ebp to the stack address
                    add         ebp, 4                  ; Point to first parameter
                    push        0                       ; We do not want overlapped I/O
                    push        dword bytes_written     ; The address of the number of bytes written
                    push        dword [ebp+4]           ; Stack the length of the string
                    push        dword [ebp]             ; Stack the string address
                    push        dword [output_handle]   ; The handle returned from GetStdHandle call
                    call        _WriteFile@20           ; Write the text to the standard output handle
                    mov         eax, [bytes_written]    ; Load the number of bytes written
                    ret         8                       ; Return to the caller



The first line takes the stack pointer on entry and transfers it to the ebp register. Wer then add 4 to the ebp register so we are now pointing to the address of the first parameter on the stack. (It is necessary to do this transfer because the next set on instructions start changing the stack pointer address). We push the overlapped I/O structure address which is zero, we push the address of the bytes_written location, then we push the contents of the location pointed to by ebp+4 bytes which is the position on the stack where the length of the buffer to write is located. Then we push the contents of the location pointed to by ebp which is the position on the stack where the address of the buffer to write is located. We then stack the output handle and call the WriteFile function. Finally, as we are responsible for cleaning up the stack, we issue a ret 8 instruction which removes 8 bytes from the stack and returns to the caller.

Below is the code that represents the application.

                    ; This is a console application that reads some text from the standard input
                    ; device and prints the string back to the output device, but in reverse

                    global      _mainCRTStartup         ; This is the main program entry point
                    extern      _ExitProcess@4          ; Windows API call to exit the process
                    extern      _GetStdHandle@4         ; Windows API call to get the standard output handle
                    extern      _ReadFile@20            ; Windows API call to read from a handle
                    extern      _WriteFile@20           ; Windows API call to write to a handle
                    
                    section     .data                   ; Start of the data segment

prompt              db          'Please enter some text', 10, 10
terminator          db          0                       ; The terminating character
input_string        times       256 db 0                ; Space for the input message
bytes_read          dd          0                       ; Return 32-bit word from ReadFile
bytes_written       dd          0                       ; Return 32-bit word from WriteFile
input_handle        dd          0                       ; The standard input handle
output_handle       dd          0                       ; The standard output handle
new_lines           db          10, 10                  ; The new line characters

                    section     .code                   ; Start of the code segment

FileWrite:          mov         ebp, esp                ; Load the stack address
                    add         ebp, 4                  ; Point to first parameter
                    push        0                       ; We do not want overlapped I/O
                    push        dword bytes_written     ; The address of the number of bytes written
                    push        dword [ebp+4]           ; Stack the length of the string
                    push        dword [ebp]             ; Stack the string address
                    push        dword [output_handle]   ; The handle returned from GetStdHandle call
                    call        _WriteFile@20           ; Write the text to the standard output handle
                    mov         eax, [bytes_written]    ; Load the number of bytes written
                    ret         8                       ; Return to the caller
                    
                    ; We need to get hold of the standard input and output handle so we can write
                    ; our text to it. This is provided by the GetStdHandle windows API call
                    
_mainCRTStartup:    push        -10                     ; We want the standard input handle
                    call        _GetStdHandle@4         ; Call the Windows API GetStdHandle to retrieve it
                    mov         [input_handle], eax     ; Save the handle for later use

                    push        -11                     ; We want the standard output handle
                    call        _GetStdHandle@4         ; Call the Windows API GetStdHandle to retrieve it
                    mov         [output_handle], eax    ; Save the handle for later use
                    
                    ; Output our request for some user text to the standard output stream
                    
                    push        24                      ; The length of the text we are writing
                    push        prompt                  ; The address of the text we are writing
                    call        FileWrite               ; Write the text to the standard output handle
                    
                    ; Read in the text from the user into our input_string area
                    
                    push        0                       ; We do not want overlapped I/O
                    push        bytes_read              ; The address of the number of bytes read
                    push        256                     ; The length of the text we are reading
                    mov         esi, input_string       ; Load the address of the input string
                    push        esi                     ; The address of the text we are reading
                    push        dword [input_handle]    ; The handle returned from GetStdHandle call
                    call        _ReadFile@20            ; Read the text to the standard input handle
                    mov         edi, [bytes_read]       ; Load the number of bytes read into input_string
           
                    ; The start of our loop, and the position we return to if we want to
                    ; print out the next character
                   
start_loop:         push        1                       ; The length of the text we are writing
                    lea         eax, [esi+edi]          ; Load the address of the byte to output
                    push        eax                     ; Stack the address of the char we are writing
                    call        FileWrite               ; Write the text to the standard output handle
                    dec         edi                     ; Decrement the index field
                    mov         al, [esi+edi]           ; Load this previous byte 
                    or          al, al                  ; Set the condition code flags
                    jne         start_loop              ; Jump back to the start of the loop if not end
                    
                    push        2                       ; The length of the text we are writing
                    push        new_lines               ; The address of the text we are writing
                    call        FileWrite               ; Write the text to the standard output handle

                    ; The text has been written, so all we need to do now is exit the
                    ; process and return control back to the console
                    
                    push        0                       ; Stack the exit code
                    call        _ExitProcess@4          ; Exit the process


This post has been edited by Martyn.Rae: 06 April 2010 - 10:02 PM


Is This A Good Question/Topic? 3
  • +

Page 1 of 1