Consider this procedure from Pascal
procedure Swap (var First, Second : integer); var Temp: integer; begin Temp := First; First := Second; Second := Temp; end;
or the similar procedure from C
void function Swap (int* First, int* Second); int Temp; begin Temp := *First; *First := *Second; *Second := Temp; end;
in both cases, the parameters are passed by address. This means that the address of the variable from the main program is passed to the procedure instead of the value of the variable. This is not a liability, since if you have the address of the variable, then its value is obtained easily. The advantage of having the address is that the procedure is allowed to change the value of the variable in the main program.
Consider another example from Pascal,
procedure PrintResult (Total, Count: integer; var Avg: real); begin println( The total is ,Total); println( There were ', Count, numbers entered'); Avg := Total / Count; println( The average is ,Avg); end;
and from C,
void function PrintResult (int Total, int Count, float* Avg); begin printf( The total is %d\n ,Total); printf( There were %d numbers entered\n', Count); Avg = (float) Total / (float) Count; printf( The average is %f\n ,Avg); end;
In these examples, the first two variables are passed by value. Since the procedure doesn't change either of these variables, then only the values need to be passed. The last parameter is passed by address, since its value is changing.
It is possible to call a procedure from one file when the actual procedure is in another file. For example, consider the procedures println and printf from the above procedures. So, where can the parameters be placed so that the caller can place them, and the procedure can retrieve them?
There are three possibilities
Global variables are not feasible, since it would require that every time you call a procedure you would have to call it with the same global variable. This would be a nuisance. Registers are not feasible, since there aren't enough of them. Sooooooooo, we are left with the stack. Each executable file has one stack and every module in the program has access to the stack. It is also very big.
It is important to remember how the stack works. It is possible to push a value onto the stack, and to pop a value off the stack. This is the mechanism used for placing information on the stack and removing it when the data is no longer needed
It is also important to understand that a stack is just an array, so if the index of an element in the stack is known, then it is possible to retrieve information from the stack without actually popping the information off the stack. In other words, besides pushing and popping, we are also allowed to peek into the stack. To take this a step further, it is possible to change the contents of an element of the stack if you know the element's index. Perhaps we can call this posting. The only register that we will use for peeking and posting will be the BP register. Any memory reference that uses BP automatically references the stack segment instead of the data segment.
Action of pushing
Action of popping
Action of peeking
Action of posting
It is important to remember how an array is referenced in assembler. First the address of the array is loaded into a register, then the [ ] are used to reference the array. Suppose that BX has the starting address of an array and that the array contains bytes, then the following references into the array are possible:
[BX] ;first element of the array [BX+1] ;second element of the array [BX+2] ;third element of the array
Suppose the array contained words, then the references would be
[BX] ;first element of the array [BX+2] ;second element of the array [BX+4] ;third element of the array
A variable is passed in one of two ways
The C calling convention states that the parameters are pushed onto the stack in reverse order. That means that the last parameter is pushed first and the first parametersis pushed last. This means that in the procedure, the first parameter will be the parameter closest to the top of the stack.
It is also necessary to remove the parameters from the stack after the procedure returns. Do this by popping all the values that were placed onto the stack before the call.
Calling the Swap procedure
;Calling Swap (Num1, Num2); LEA AX,NUM2 PUSH AX LEA AX,NUM1 PUSH AX CALL SWAP POP AX POP AX
Calling the PrintResult procedure
;Calling PrintResult (Balance,30, AvgBalance); LEA AX,AvgBalance PUSH AX MOV AX,30 PUSH AX PUSH Balance CALL PrintResult POP AX POP AX POP AX
The C calling convention uses the BP register to remember where the parameters are placed on the stack. You can be sure of the following
BP+4 Location of parameter 1 BP+6 Location of parameter 2 BP+8 Location of parameter 3 etc
Also, if the procedure uses local variables, then they will be located at
BP-2 Location of first local variable BP-4 Location of second local variable BP-6 Location of third local variable etc
However, this assurance doesn't come for free. The programmer writing the procedure must be sure that the BP is set up correctly. The first thing to realize, is that every procedure uses the BP in the same way, so if the BP is to be changed, it must be restored to its original value before the procedure returns, so that the calling procedure will be able to find its parameters and local variables.
Consider the following Data Segment and partial Code Segment for passing parameters on the stack
.DATA Num DW 12h Value DW 6h NumArr DB 34h,12h,8h,9h,44h,23h .CODE ... ... mov ax,offset NumArr push ax mov ax,offset Num push ax mov ax,Value push ax call Test ... ... Test proc near push bp mov bp,sp ... ...
The Stack and Data segments would appear as follows. Note that the addresses are arbitrary, I chose these numbers just as examples of how the memory might be allocated.
|
|
After the call, [BP+4] holds the value of the parameter that was passed by value.
In order to add this value to the AX register, the following command would be used inside the Test procedure
ADD AX,[BP+4]
In terms of the memory, this is saying
ADD AX,SS:[E90E]
Treating the stack as an array, this says to add the value from the array, 0006h, to AX. Notice that the memory that is being referenced is the SS, since any memory reference using BP addresses the SS.
Accessing the value for this parameter requires two steps
MOV BX,[BP+6] ADD AX,[BX]
In terms of the memory, this says
MOV BX,SS:[E910] ADD AX,DS:[0000]
Step one treats the stack as an array, and moves the value 0000h into BX. This value is the address of Num in the data segment. Step two treats the data segment as an array and adds the value 0012h to AX. Again, notice that any memory reference using BP addresses the stack, whereas the memory reference using BX references the data segment.
As a third example, by loading the address from BP+8 into BX, and loading the value at BP+4 into CX, it is possible to access all the values in the array, NumArr:
MOV BX,[BP+8] MOV CX,[BP+4] TOP: ADD AX,[BX] INC BX LOOP TOP
Here is what happens if I unroll the loop and see what each reference looks like:
MOV BX,SS:[E912] ;address of NumArr MOV CX,SS:[E90E] ;size of NumArr Add AX,DS:[0004] ;add 34h to AX INC BX ;BX becomes 0005 Add AX,DS:[0005] ;add 12h to AX INC BX ;BX becomes 0006 Add AX,DS:[0006] ;add 8h to AX INC BX ;BX becomes 0007 Add AX,DS:[0007] ;add 9h to AX INC BX ;BX becomes 0008 Add AX,DS:[0008] ;add 44h to AX INC BX ;BX becomes 0009 Add AX,DS:[0009] ;add 23h to AX INC BX
So, by passing just two parameters on the stack, it is possible to access an entire array in the data segment.
Using the same situations as above, the following steps would change the value of a parameter
After the call, [BP+4] holds the value of the parameter that was passed by value.
In order to add the AX register to this value, the following command would be used inside the Test procedure
ADD [bp+4],AX
In terms of the memory, this is saying
ADD SS:[E90E],AX
Treating the stack as an array, this says to add the AX register to the value from the array. Notice that the memory that is being referenced is the SS, since any memory reference using BP addresses the SS, so that the value in the data segment for Value doesn't change, only the value on the stack changes.
Accessing the value for this parameter requires two steps
MOV BX,[BP+6] ADD [BX],AX
In terms of the memory, this says
MOV BX,SS:[E910] ADD DS:[0000],AX
Step one treats the stack as an array, and moves the value 0000h into BX. This value is the address of Num in the data segment. Step two treats the data segment as an array and adds AX to the value stored there. So, by using pass-by-address it is possible for a procedure to change a variable in the data segment without referencing the name of of that variable. Again, notice that any memory reference using BP addresses the stack, whereas the memory reference using BX references the data segment.
As a third example, by loading the address from BP+8 into BX, and loading the value at BP+4 into CX, it is possible to access all the values in the array, NumArr:
MOV BX,[BP+8] MOV CX,[BP+4] TOP: ADD [BX],AX INC BX LOOP TOP
Here is what happens if I unroll the loop and see what each reference looks like:
MOV BX,SS:[E912] ;address of NumArr MOV CX,SS:[E90E] ;size of NumArr Add DS:[0004],AX ;add AX to 34h INC BX ;BX becomes 0005 Add DS:[0005],AX ;add AX to 12h INC BX ;BX becomes 0006 Add DS:[0006],AX ;add AX to 8h INC BX ;BX becomes 0007 Add DS:[0007],AX ;add AX to 9h INC BX ;BX becomes 0008 Add DS:[0008],AX ;add AX to 44h INC BX ;BX becomes 0009 Add DS:[0009],AX ;add AX to 23h INC BX
So, by passing just two parameters on the stack, it is possible to access an entire array in the data segment.
Local parameters are also allocated on the stack. They are also referenced with regard to the BP.
BP-2 Address for the first local parameter BP-4 Address for the second local parameter BP-6 Address for the third local parameter [BP-2] Value of the first local parameter [BP-4] Value of the second local parameter [BP-6] Value of the third local parameter
To allocate space for locals, just subtract the number of required bytes from the stack pointer: SUB SP,6
PUSH BP ;save caller's BP MOV BP,SP ;set up current BP SUB SP,6 ;allocate three local variables, each is a word ... ... ... MOV SP,BP ;restore SP and remove locals from the stack POP BP ;restore caller's BP RET
Two parameters passed by address, NUM1 and NUM2. One local variable TEMP. Please not that using a local variable, TEMP, is not necessary and that the code would be much simpler without it. However, it does illustrate how to allocate and reference a local variable.
SWAP PROC NEAR PUSH BP MOV BP,SP SUB SP,2 ;allocate a word on the stack for TEMP PUSH AX ;save caller's AX PUSH SI ;save caller's SI, since it will be used PUSH DI ;same for DI MOV SI,[BP+4] ;get address of NUM1 MOV AX,[SI] ;get value of NUM1 MOV [BP-2],AX ;store value of NUM1 into TEMP MOV DI,[BP+6] ;address of NUM2 MOV AX,[DI] ;value of NUM2 MOV [SI],AX ;store value of NUM2 into NUM1 MOV AX,[BP-2] ;value of TEMP MOV [DI],AX ;store value of TEMP into NUM2 POP DI ;restore caller's registers POP SI POP AX MOV SP,BP ;dealocate the local variable TEMP POP BP ;restore the caller's BP RET SWAP ENDP