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




MultiQuote


|