CIS-77 Home http://www.c-jump.com/CIS77/CIS77syllabus.htm
Two instructions control the use of assembly-language procedures:
CALL pushes the return address onto the stack and transfers control to a procedure.
RET pops the return address off the stack and returns control to that location.
Sample program: call_ret.asm
The Assembler supports formal construct for defining procedures.
Assembly-language procedures are similar to C functions.
The PROC and ENDP directives mark the beginning and the ending of a procedure.
Additionally, PROC can automatically:
Preserve register values that should not change but that the procedure might otherwise alter.
Set up a local stack pointer, so that you can access parameters and local variables placed on the stack.
Adjust the stack when the procedure ends.
At a minimum procedures require:
label at the start of the procedure
RET instruction at the end.
The RET instruction is normally placed immediately before the ENDP directive:
label PROC . . . RET constant label ENDP
The syntax for PROC is:
The parts of the PROC directive include:
label - the name of the procedure.
attributes - call distance, calling convention, and visibility of the procedure.
reglist - a list of registers (separated by spaces) following the USES keyword
The registers will be saved on the stack upon procedure entry.
Registers in the list must be separated by blanks or tabs, not by commas.
The assembler generates prologue code to push registers onto the stack upon entry.
Upon exit assembler generates epilogue code to pop the saved registers off the stack.
parameter - the list of input parameters to be passed to the procedure on the stack.
Data, passed to a procedure, are called input arguments.
Most common method is the stack (not global data or registers.)
But first, a new concept: calling conventions.
Calling conventions describe the interface of called code:
The order in which parameters are allocated.
Where parameters are placed (pushed on the stack or placed in registers.)
Which registers may be used by the procedure.
Whether the caller or the callee is responsible for unwinding the stack upon return.
For example,
add3nums PROC NEAR32 C
Here, add3nums is the first line of procedure declaration.
Calls to add3nums should be made similar to a C function call, that is,
caller pushes parameters into the stack in the reverse order (right-to-left)
calling the function
popping the stack to clean up those pushed arguments.
; example of __cdecl
push arg3
push arg2
push arg1
call function
add sp,12 ; effectively pop; pop; pop;
Function return values are returned in the EAX register.
PASCAL-style parameters are pushed on the stack in left-to-right order.
The STDCALL calling convention is a variation of the PASCAL calling convention in which are pushed on the stack right-to-left.
Note: parameters to Windows API functions are passed using the STDCALL calling method.
Pascal-style procedure call is made with the
caller pushing parameters into the stack in left-to-right order (opposite of __cdecl)
calling the function
stack is cleaned up by the callee
; example of pascal-style call. ; NOTE: __stdcall would push the arguments in reverse order. push arg1 push arg2 push arg3 call function ; no stack cleanup upon return: callee did it
PASCAL and STDCALL call
pros:
the code is slightly smaller, though the size impact is only visible in large programs.
cons:
C functions like printf() have variable arguments (aka variadic functions)
Variadic functions are almost impossible to get right with PASCAL and STDCALL methods, because only the caller really knows how many arguments were passed in order to clean them up.
See also: Using
The C_style_procedure_call.asm sample demonstrates standard entry into procedure used in high-level programming language C.
Stack grows in the direction of the lower addresses.
Local variables exist on the stack while the procedure is excuting.
The address of the first parameter is defined as [ EBP + 8 ] .
Address of the first local variable, if it is reserved, is defined as [ EBP - 4 ], with the DWORD variable in mind.
C approach to passing parameters does not require procedure to restore stack volume occupied by its parameters.
Therefore, the caller is responsible for making this adjustment:
add esp, 12 ; destroy the pushed arguments, C-style calling convention
Calling Convention | Argument Passing | Stack Maintenance | Name Decoration | Notes |
---|---|---|---|---|
__cdecl | Right to left. | Caller removes arguments from the stack. This calling convention is the only one that allows variable argument lists. | Underscore prefixed to function names, as in _Foo. | C convention. The default for C and C++ functions. |
__stdcall | Right to left. | Callee removes its own arguments from the stack. | Underscore prefixed to function name, and @ appended followed by the decimal number of bytes in the argument list, as in _Foo@12. | Standard call. Used by almost all system functions; the default for Visual Basic internal functions. |
pascal | Left to right. | Callee removes its own arguments from the stack. | Undecorated symbol name in uppercase letters, as in FOO. | The convention adopted in Pascal. |
__fastcall | First three DWORD parameters are passed in EAX, EDX, and ECX. If these registers are not sufficient for passing all parameters, then the remaining parameters are passed on the stack, right to left. | Callee removes its own arguments from the stack. | An @ is prefixed to the name, and @ is appended followed by the number of decimal bytes in the argument list, as in @Foo@12. | Aka register, or fast calling convention. Applies only to Intel CPUs. This calling convention is the default for Borland Delphi compilers. |
this | Right to left. The this parameter is passed in the ECX register. | Caller removes arguments from the stack. | None. | Used automatically by C++ class methods unless you specify standard call. COM methods are declared as standard call. |
naked | Right to left. | Caller removes arguments from the stack. | None. | Used when you need custom prolog and epilog. |
Procedure begins with a standard sequence of commands,
push ebp mov ebp, esp sub esp, N ; grow stack volume by N bytes
where N is number of bytes required for housing of local variables.
At the end of the procedure, the following commands are found:
mov esp, ebp pop ebp ret M
Here, M is the stack volume taken for passing parameters.
Pascal approach to passing parameters requires procedure to restore stack volume occupied by its parameters:
ret M ; Pascal and __stdcall-style calling convention stack cleanup
The following sample illustrates the code generated in the calling function and in the called function to support __cdecl, the C calling convention:
int __cdecl CFunc( int a, int b ); calling function called function ----------------- ------------------- push b _CFunc PROC NEAR push a . call _CFunc . add esp,8 . . RET . _CFunc ENDP . int __cdecl CVarFunc( int a, ...); calling function called function ----------------- ------------------- push ... _CVarFunc PROC NEAR push a . call _CVarFunc . add esp,4+... . . RET . _CVarFunc ENDP .
The following sample illustrates the code generated in the calling function and in the called function to support __stdcall, the standard calling convention:
int __stdcall StdFunc( int a, int b ); calling function called function ----------------- --------------------- push b _StdFunc@8 PROC NEAR push a . call _StdFunc@8 . . . . RET 8 . _StdFunc@8 ENDP
The following sample illustrates the code generated in the calling function and in the called function to support __fastcall, the fastcall calling convention:
int __fastcall FastFunc( int a, int b ); calling function called function ------------------------------------------- mov edx, b @FastFunc@8 PROC NEAR mov ecx, a . call @FastFunc@8 . . . . RET 8 . @FastFunc@8 ENDP
PROC directive may include parameter declarations: label PROC attributes USES reglist, parameter:tag, parameter:tag, ...
Parameter declarations allow assembler to
calculate necessary offsets for you
let you refer to parameters by name.
For example,
add2nums PROC NEAR32 C, arg1:DWORD, arg2:DWORD mov eax, arg1 add eax, arg2 ret add2nums ENDP
|
|
There are two formats for the RET instruction.
The most common form has no operand and is simply coded
ret
An alternative version has a single operand and is coded
ret count
where count operand is added to the contents of ESP after restoring EIP and, for a FAR procedure, CS.
PROC directive can specify
registers to be saved
input parameters, parameter types, and assign their symbolic names.
If arguments for a procedure are pointers, the assembler does not pass the actual values, it passes the pointers.
Program must explicitly treat the argument as a pointer to get to the data.
Sample code: pointer_args.asm
In high-level languages like C local variables are visible only within a procedure.
Local variables are usually constructed on the stack.
Assembly-language procedure can define and use its own local variables in a similar manner.
To use local variables, procedure makes indirect access to stack via base pointer register EBP.
The address of the first local variable is defined as
[ EBP - varsize ]
where varsize is the variable size.
Sample program: local_vars.asm
LOCAL directive automates the process for creating local variables on the stack.
LOCAL frees the programmer from having to count stack bytes.
LOCAL makes code easier to write and maintain.
The assembler calculates how much space required on the stack by locals and generates instructions to
properly decrement ESP upon entry
reset ESP upon return from the procedure by RET N; instruction.
LOCAL variables make possible to refer to the variables by name rather than EBP offset pointing to the stack.
Sample program: local_automated.asm
The LOCAL directive must be on the line immediately following the PROC statement with the following syntax:
LOCAL vardef, vardef, vardef...
Each vardef defines a local variable.
Local variable definition formats:
LOCAL label:qualifiedtype, label[ count ]:qualifiedtype
where
label is the name of the local variable.
count is the number of array elements, making local variable a simple array on the stack with count.
qualifiedtype is a valid MASM data type.
For example,
ARRAY_SIZE EQU 20 myproc PROC NEAR32 C USES edi, arg:DWORD LOCAL myarray[ ARRAY_SIZE ] : BYTE LOCAL var2 : WORD . ret myproc ENDP
The assembler does not initialize local variables - program must perform any necessary initializations.
For example, the following code fragment sets up a local array of bytes and initializes array to zero: local_array.asm
Note: assembler translates variables names into EBP offsets, so the local variables are not visible outside the procedure.
MASM provides the INVOKE directive to simplify coding of procedure calls.
Big advantage is that pushing parameters according to the correct calling convention is done by INVOKE automatically.
This tecnique is best used with procedure prototypes, declared by PROTO before the INVOKE.
PROTO statements inform the assembler of types and arguments of the procedure independently of the procedure definition.
This works because assembler does not require any procedure implementation details to generate proper CALL instruction.
The assembler encounters the prototype before the actual procedure definition.
Declaring procedure prototypes is a good programming practice.
The easiest way to create prototypes with PROTO is to
write your procedure
copy the first line
change PROC to PROTO and remove the USES reglist, the prologuearg field, and the visibility field.
For example,
; Procedure prototypes: load_data PROTO NEAR32 C, arg1:WORD, arg2:WORD, arg3:WORD save_data PROTO NEAR32 C, arg1:DWORD, arg2:BYTE . ; Procedure calls: INVOKE load_data, ax, x, y INVOKE save_data, ebx, 10 . ; Procedure declarations: load_data PROC NEAR32 C, arg1:WORD, arg2:WORD, arg3:WORD . save_data PROC NEAR32 C PUBLIC USES di si, arg1:DWORD, arg2:BYTE .
A procedure prototype includes
procedure name
types of all parameters
(optionally) the names of parameters.
Prototypes usually are placed
at the beginning of an assembly program
or in a separate include file.
Prototypes do not include
the list of registers
prologuearg list
the scope of the procedure.
INVOKE generates a sequence of instructions that push arguments and calls a procedure as follows:
converts arguments to the expected types.
pushes arguments on the stack in the correct order.
cleans the stack when the procedure returns.
Sample program: invoke_proc.asm
The ADDR operator allows to pass the address of memory where an object resides.
Sample program: invoke_addr.asm