Chapter 8Advanced Procedures |
Modified: |
Call-by-value and call-by-reference parameters both have merits in given applications and are commonly supported by high level languages. The meaning here is that normally used in C++.Call-by-value passes a copy of the value for the actual parameter via the stack from the caller to the callee.
Call-by-reference differs in that the offset of the actual parameter is passed via the stack from the caller to the callee.
The implications for the programmer is relatively straightforward and is summarized by the following examples.
Indirection is necessary for implementing parameter passing via the stack. It is useful to first relate our usual notion of indirectionto assembly language and the machine's architecture. C++ supports indirection using pointer variables that 1) hold an address that references 2) a variable, hence the indirection. The Intel processor uses registers (Bx, Si, Di, and Bp for 16-bit or any 32-bit) as pointers, these registers can hold an address used for the indirection. The following three examples are identical with variables i, an integer, and bx, a pointer to an integer. In the diagram at right, BX holds the address of variable i.
In the following Assembler program, we can examine the offsets of the variables and determine precisely what is the contents of eBx register. The instruction
Mov eBx, Offset icopies the offset of i, which is 32-bit 00000000416 to eBx.
C++ version
#include <iostream.h>
void main ( void )
{
int i;
int *ebx;
i = 5;
ebx = &i; // Alias i
cout << *ebx; // Prints 5
}
|
Assembler version
.data
00000000 h dword 3
00000004 i dword 5
00000008 j dword 9
.code
Main Proc
Mov eBx, Offset i ;; Alias i, eBx=0000004
Mov eAx, [eBx] ;; Get i value indirectly
Call WriteDec ;; Prints 5
Mov eAx, [eBx+4] ;; Get j value indirectly
Call WriteDec ;; Prints 9
Exit
Main Endp
End Main
|
Indirection to Access Parameters
By enclosing in square brackets, [], registers such as eBx, eSi, eDi and other 32-bit registers act as pointers to reference memory.
Using 16-bit variables, Bx and Si can point to the data segment, Di a pointer to the extra segment, while Bp can be a pointer to the stack segment. In the above example Bx or Si could be used but not Bp since the variable i is in the data segment and Bp references the stack segment.
In the following assembly program examples, notice the use of eBp as a pointer to access the parameters on the stack. Suppose program fragments of:
Notice that after Mov eBp, eSP the eBp register can be used to access parameters by adding an appropriate displacement to eBp. For example, the instruction:
Mov eAx, [eBp+8]
adds 8 to the offset in eBp to indirectly access the stack location holding the A parameter. Because any calls made to this function would place the actual parameters at the same relative stack location, [eBp+8] will always correspond to the formal A parameter. This follows the protocol used by C++ in calling functions and passing value parameters.
General Rules for C++ Functions
Using functions requires following specific rules or protocol by both the caller and the callee. In general, the rules can be summarized by the following:
| Caller | Callee |
|
|
Assembly version
Stack after Mov eBp, eSp _________ | eDx | |_________| | Flags | |_________| B | 7 | <- eBp+12 |_________| A | 9 | <- eBp+8 |_________| | Ret Addr| <- eBp+4 |_________| | Old eBp | <- eBp+0 = eSp |_________| |
1. Question
Draw the stack after Mov eBp, eSp.
What are eAx, eBx, and eCx prior to Pop eBp?
main proc
Push 1
Push 2
Push 3
Call p
Add eSp, 12
Call WriteDec
exit
main endp
p proc
Push eBp
Mov eBp, eSp
Mov eAx, [eBp+8]
Mov eBx, [eBp+12]
Mov eCx, [eBp+16]
Pop eBp
Ret
p endp
|
Call-by-Value versus Call-by-Reference
The following figure illustrates the difference between value (left figure) and reference (right figure) passing in the program examples that follow later. In value passing the function does not have access to the original parameter, in reference passing the reference allows indirect access to the caller's parameters.
Call by Value
Call by Reference
![]() |
||
|
Requires the value of the actual parameter to be pushed onto the stack by the calling routine. The called function uses the value directly from the stack. The called function cannot alter parameters because it lacks a reference (i.e. the data segment offset of the actual parameter is unknown).
Caller - The caller's responsibility is to:
|
Callee - The callee's responsibility is to:
|
A more complete example of call-by-value implementation is given below using C++ calling methods.
2. Question
Translate the C++ function p to Assembler.
|
int p( int X, int Y) { return X*Y; } |
.data
A dword 5
B dword 7
.code
main proc
Push A
Push B
Call p
Add eSp, 8
Call WriteDec
exit
main endp
|
The protocol is similar to call-by-value but rather than pushing values onto the stack, references to the parameters (offsets) are pushed.The protocol requires the data segment offset of the actual parameter to be pushed onto the stack by the calling routine. The called function then accesses the location of the actual parameter using the data segment offset. Since the parameter's location in memory is now accessible by the called function, it can be directly referenced and altered. The responsibility of the caller and callee is nearly identical for value and reference parameter passing, except that offsets are used rather than values to access parameters.
Caller - The caller's responsibility is to:
|
Callee - The callee's responsibility is to:
|
A more complete example of call-by-reference implementation is given below using C++ calling methods.
3. Question
Draw the stack after Mov eBp, eSp.
What are eAx, eSi, and eDi prior to Pop eBp?
.data
00000144 A dword 5
00000148 B dword 7
.code
main proc
Push OFFSET A
Push OFFSET B
Call p
Add eSp, 8
Call WriteDec
exit
main endp
p proc
Push eBp
Mov eBp, eSp
Mov eSi, [eBp+8]
Mov eDi, [eBp+12]
Mov eAx, [eSi]
Add eAx, [eDi]
Pop eBp
Ret
p endp
|
4. Question
Translate the C++ function p to Assembler.
|
int p( int &X, int &Y) { return X*Y; } |
.data
A dword 5
B dword 7
.code
main proc
Push OFFSET A
Push OFFSET B
Call p
Add eSp, 8
Call WriteDec
exit
main endp
|
Dynamic (Local) Variables Discussion
In C++ procedures, dynamic variables are generally termed local variables, which are allocated each time the procedure is entered and deallocated on each exit. To allocate storage for a local or dynamic variable, the stack pointer is decremented, once for a byte variable, twice for a word variable, 200 times for an array of 100 words, etc. Deallocation is by restoring the stack pointer, by Mov eSp, eBp. References to the dynamic variable are still relative to eBp though the displacement is normally negative rather than positive. Generally, dynamic variables use requires all the same steps used when entering or exiting a procedure, summarized below:
Entering 1) Push eBp 2) Mov eBp, eSp 3) Sub eSp, # # is the number of bytes to allocate for dynamic variables 4) Push Registers Save any registers Exiting 4) Pop Registers Restore any registers 3) Mov eSp, eBp Restore eSp which deallocates dynamic variables 2) Pop eBp 1) Ret
Dynamic (Local) Variables C++ and Assembler
int a = 5, b = 6;
void main(void) {
cout << addup(a, b);
}
|
.data a dd 5 b dd 6 .code main proc push offset b push a call addup add eSp, 8 call WriteDec Exit main endp |
int addup(int x, int &y)
{ int sum;
sum = y;
sum = sum + x;
return sum;
}
|
addup PROC push eBp mov eBp, eSp sub eSp, 4 ; int sum mov eBx, [eBp+12] ; sum = y; mov eAx, [eBx] add eAx, [eBp+8] ; sum = sum + x mov [eBp-4], eAx mov eAx, [eBp-4] ; return sum mov eSp, eBp pop eBp ret addup ENDP |
5. Question
Draw the stack after Mov eSp, eBp.
What are eAx, eSi, and eDi prior to Pop eBp?
.data
00000144 A dword 5
00000148 B dword 7
.code
main proc
Push A
Push OFFSET B
Call p
Add eSp, 8
Call WriteDec
exit
main endp
p proc
Push eBp
Mov eBp, eSp
Sub eSp, 4
Mov eSi, [eBp+8]
Mov eDi, [eBp+12]
Mov eAx, [eSi]
Add eAx, eDi
Mov [eBp-4], eAx
Mov eSp, eBp
Pop eBp
Ret
p endp
|
6. Question
Translate the C++ function p to Assembler.
|
int p( int X, int Y) { int Z; Z = 3; |
.data
A dword 5
B dword 7
.code
main proc
Push A
Push B
Call p
Add eSp, 8
Call WriteDec
exit
main endp
|
Double Indirection
The swap function illustrates an appropriate use of reference parameters, that is when the parameter requires a side-effect. It also illustrates that call-by-reference parameters have an access cost of two indirections. To access the right-most variable B in the following example requires:
8.5 Declaring Procedure Parameters with the PROC Directive
Calling Procedures with the INVOKE DirectiveWith the PROC directive, you can specify registers to be saved, define parameters to the procedure, and assign symbol names to parameters (rather than as offsets from eBp). This section describes how to use the PROC directive to automate the parameter-accessing techniques described earlier.
Value
The following illustrates pass-by-value parameters in C as implemented by hand pushing parameters and access through eBp and using the combined PROC and INVOKE directive. Note that the INVOKE pushes parameters on the stack in right-to-left order.
int a = 3, b = 35; cout << addup(a, b); |
push b push a call addup add eSp, 8 call WriteDec |
invoke addup, a, b call WriteDec |
int addup(int x, int y)
{
return x + y;
}
|
addup PROC push eBp mov eBp, eSp mov eAx, [eBp+8] add eAx, [eBp+12] pop eBp ret addup ENDP |
addup PROC C, x:DWORD, y:DWORD mov eAx, x add eAx, y ret addup ENDP |
7. Question
Translate the C++ function p to Assembler using invoke/proc.
|
int p( int X, int Y, int Z) { return X+Y+Z; } |
int a = 3, b = 35, c=4; cout << addup(a, b, c); |
Reference
If the arguments for a procedure are pointers, the assembler does not generate any code to get the value or values that the pointers reference; your program must still explicitly treat the argument as a pointer.
In the following example, even though the procedure declares the parameters as pointers, you must code two MOV instructions to get the values of the parameters. The first MOV gets the address of the parameters, and the second MOV gets the parameter.
int a = 3, b = 35; cout << addup(a, b); |
push offset b push offset a call addup add eSp, 8 call WriteDec |
invoke addup, ADDR a, ADDR b call WriteDec |
int addup(int &x, int &y)
{
return x + y;
}
|
addup PROC push eBp mov eBp, eSp mov eBx, [eBp+8] mov eAx, [eBx] mov eBx, [eBp+12] add eAx, [eBx] pop eBp ret addup ENDP |
addup PROC C, x:PTR DWORD, y:PTR DWORD mov eBx, x mov eAx, [eBx] mov eBx, y add eAx, [eBx] ret addup ENDP |
8. Question
Translate the C++ function p to Assembler using invoke/proc.
|
int p( int &X, int &Y, int Z) { return X+Y+Z; } |
int a = 3, b = 35, c=4; cout << addup(a, b, c); |
Procedure Prototypes
Calling Procedures with INVOKEPrototypes perform the same function as prototypes in C and other high-level languages.
A procedure prototype includes the procedure name, the types, and (optionally) the names of all parameters the procedure expects. Prototypes usually are placed at the beginning of an assembly program or in a separate include file so the assembler encounters the prototype before the actual procedure. Prototypes enable the assembler to check for unmatched parameters and are especially useful for procedures called from other modules and other languages.
The following example illustrates how to define and then declare two typical procedures.
addup PROTO C, x : DWORD, y : DWORD
swap PROTO C, L : PTR DWORD, R : PTR DWORDWhen you call a procedure with INVOKE, the assembler checks the arguments given by INVOKE against the parameters expected by the procedure. If the data types of the arguments do not match, MASM reports an error or converts the type to the expected type.
Using Local VariablesINVOKE generates procedure calls automatically which:
Converts arguments to the expected types.
Pushes arguments on the stack in the correct order specified by the model. C and stdcall models parameters are pushed right-to-left order.
Cleans the stack of parameters when the procedure returns.
Procedures with these prototypes
int addup (int x, int y )
void swap ( int &L, int &R)or these corresponding procedure declarations
addup PROC C, x : DWORD, y : DWORD
swap PROC C, L : PTR DWORD, R : PTR DWORDcan be called with INVOKE statements as:
INVOKE addup, eAx, eBx
INVOKE swap, ADDR A, ADDR B
In high-level languages, local variables are visible only within a procedure and are usually stored on the stack. In assembly-language programs, you can also have local variables. The LOCAL directive automatically allocates local variable space on the stack at the start of the procedure, defines a reference to the variable by its position in the stack, at the end of the procedure the local variable is deallocated by restoring the stack pointer.
In the following, the local variable sum is allocated by Sub eSp, 4 and deallocated by restoring the stack pointer in Mov eSp, eBp. Note that the local statement can also be used without the invoke.
int a = 3, b = 35; cout << addup(a, b); |
push b push a call addup add eSp, 8 call WriteDec |
invoke addup, a, b call WriteDec |
int addup(int x, int y)
{ int sum = x + y;
return sum;
}
|
addup PROC push eBp mov eBp, eSp sub eSp, 4 mov eAx, [eBp+8] add eAx, [eBp+12] mov [eBp-4], eAx mov eAx, [eBp-4] mov eSp, eBp pop eBp ret addup ENDP |
addup PROC C, x:DWORD, y:DWORD local sum : DWORD mov eAx, x add eAx, y mov sum, eAx mov eAx, sum ret addup ENDP |
9. Question
Translate the C++ to Assembler.
int A = 10;
int B = 15;A = p( A, B);
int p( int X, int &Y) { int Z = 5;
return X+Y+Z;
}
Arrays
int A[] = {1, 2, 3, 4, 5,6};
cout << sum( A );
|
.data
A dword 1, 2, 3, 4, 5, 6
.code
invoke sum, addr A
call WriteDec
|
int sum( int &X )
{ eAx = 0;
for( eCx=0; eCx < 6; eCx++)
eAx = X[ eCx ] + eAx;
return eAx;
}
|
sum Proc C, X:PTR DWORD
Mov eBx, X
Mov eAx, 0
Mov eSi, 0
@for:
Cmp eSi, 6
Jl @do
Jmp @endfor
@do:
Add eAx, [eBx+eSi*4]
Inc eSi
Jmp @for
@endfor:
Ret
sum Endp
|
10. Question - Translate the C++ to Assembler.
int A[5];
p( A, 5);void p( int X[], int n) {
int i;
for( i=0; i<n; i++)
X[ i ] = 10;
}
A slightly more challenging example that illustrates the added clarity of use of PROC/INVOKE/LOCAL directives, swapping parameter values.
int a = 5, b = 6; swap(a, b); |
.data
A dd 5
B dd 6
.code
main Proc
push offset B
push offset A
call swap
Add eSp, 8
push 0
Call ExitProcess
main Endp
End main
|
.data
A dd 5
B dd 6
.code
main Proc
invoke swap, addr A, addr B
invoke ExitProcess, 0
main Endp
End main
|
void swap(int &L, int &R)
{ int T;
T=L;
L=R;
R=T;
}
|
swap proc push eBp mov eBp, eSp sub eSp, 4 Mov eBx, [eBp+8] ; T = L Mov eAx, [eBx] Mov [eBp-4], eAx Mov eBx, [eBp+8] ; L = R Mov eSi, [eBp+12] Mov eAx, [eSi] Mov [eBx], eAx Mov eAx, [eBp-4] ; R = T Mov eSi, [eBp+12] Mov [eSi], eAx mov eSp, eBp pop eBp ret addup ENDP |
swap Proc C,
L:PTR DWORD,
R:PTR DWORD
LOCAL T : DWORD
Mov eBx, L ; T = L
Mov eAx, [eBx]
Mov T, eAx
Mov eBx, L ; L = R
Mov eSi, R
Mov eAx, [eSi]
Mov [eBx], eAx
Mov eAx, T ; R = T
Mov eSi, R
Mov [eSi], eAx
Ret
swap Endp
|