Sie sind auf Seite 1von 15

4.

SUBPROGRAMS

4.1 The x86 call and ret instructions


Besides jump and loop instructions, the x86 architecture provides two more flow control
instructions used to call and return from subprograms, namely call and ret (see Table 1).

Table 1. x86 call/ret instructions

Instruction Usage Description


Save the return address in the stack and jump
CALL Call CALL dest
(unconditionally) to the destination address
Pop the address stored in the top of the stack and
RET Return RET
jump (unconditionally) to that address

The call instruction pushes the address of the next instruction in the stack (see the push
instruction for details), and then performs an unconditional jump to the address indicated by
the operand. Unlike the simple jump instructions, the call instruction saves the return address
in the stack; this address will be used to return to the main program when the subprogram
completes.

The ret instruction implements a subprogram return mechanism. This instruction pops the
return address from the stack (see the pop instruction for details), and then it performs an
unconditional jump to that address.

Note that these two instructions use implicitly the stack and modify the stack pointer (SP).

Details and usage examples regarding the call/ret instructions are provided in Section 4.3.

4.2 The x86 call and ret instructions1


The call and ret instructions do not specify anything about input/output parameters. As Table 1
shows I/O parameters are not passed to/from the subprogram as instruction operands. There
are several programming conventions specifying how this data is transferred to/from
subprograms. In this laboratory we are going to use the rules explained below.

1 Parts of this section are inspired from http://www.cs.virginia.edu/~evans/cs216/guides/x86.html


4.2.1 Caller rules
To make a subprogram call, the caller should:

1. Before calling a subprogram, the caller should save the contents of certain registers that he
also needs after the subprogram call. For example, if the caller relies on the values of AX, BX
and DI after the subroutine returns, the caller must push the values in these registers in the
stack (so they can be restored after the subprogram returns).

2. To pass parameters to the subprogram, the caller should push them in the stack before the
call. The parameters should be pushed in inverted order (i.e. last parameter first). Since the
stack grows down, the first parameter will be stored at the lowest address (this inversion of
parameters was historically used to allow functions to be passed a variable number of
parameters).

3. To call the subprogram, use the call instruction. This instruction places the return address
on top of the parameters on the stack, and branches to the subprogram code. This invokes
the subprogram, which should follow the callee rules below.

After the subprogram returns (immediately following the call instruction), the caller can expect
to find the return value of the subroutine in the register AX. To restore the machine state, the
caller should:

1. Remove the parameters from stack. This restores the stack to its state before the call was
performed.

2. Restore the contents of caller-saved registers by popping them out of the stack.

4.2.2 Callee rules


When the subprogram begins:

1. The callee should load BP with the value of SP (mov BP, SP). This way, the callee will be able
to access the input parameters (in the stack) using the addresses BP+2 (the first parameter),
BP+4 (the second parameter), etc., even though SP might change during the subprogram
execution.

Before the subprogram ends, the callee should:

1. Pop anything that was pushed in the stack during the subprogram.

2. Store the output parameter in AX (and other registers if there is more than one output
parameter).

3. Return to the caller by executing a ret instruction. This instruction will find and remove the
appropriate return address from the stack (if step 1 above was done correctly).
4.2.3 Subprogram call and return example
Suppose the main program uses the register BX to store important data and it needs to call a
subprogram with two input parameters (1234h and the value in register DX) and retrieve one
result in AX. Consider the following block of instructions:

main: push AX ; save the value of AX in the stack


push BX ; save the value of BX in the stack
push DX ; push the second input parameter in the stack
push 1234h ; push the first input parameter in the stack
call subprogram ; call the subprogram
add SP, 4h ; pop the input parameters from the stack
mov SI, AX ; copy the result in SI, because AX will be restored
pop BX ; restore the previous value of BX (from the stack)
pop AX ; restore the previous value of AX (from the stack)


subprogram: mov BP, SP ; place BP to point to the same element as SP
; the subprogram body in which the input parameters can
;be accessed using the addresses BP+2, BP+4 and BP+6
; here the registers AX, BX and CX might be modified
mov AX, 98BBh ; store the result in AX
ret ; return to the main program

During the execution of this part of code the stack gets modified as shown in the following
figure. In this figure we used the following conventions:

all numbers are in hexadecimal

the values of some registers are displayed on the left part

the values in some memory locations are displayed on the right part; effective addresses
of the memory locations are in smaller fonts

purple cells are part of the stack and white cells are not part of the stack

xxxx means that the value stored in the register / memory location is unknown
program start after push AX after push BX

SP: FEE6 FEE6 xxxx SP: FEE4 FEE6 xxxx SP: FEE2 FEE6 xxxx
BP: xxxx FEE4 xxxx BP: xxxx FEE4 1122 BP: xxxx FEE4 1122
FEE2 xxxx FEE2 xxxx FEE2 3344
AX: 1122 FEE0 xxxx AX: 1122 FEE0 xxxx AX: 1122 FEE0 xxxx
BX: 3344 FEDE xxxx BX: 3344 FEDE xxxx BX: 3344 FEDE xxxx
DX: 5566 FEDC xxxx DX: 5566 FEDC xxxx DX: 5566 FEDC xxxx
FEDA xxxx FEDA xxxx FEDA xxxx

after push DX after push 1234h after call subprogram



SP: FEE0 FEE6 xxxx SP: FEDE FEE6 xxxx SP: FEDC FEE6 xxxx
BP: xxxx FEE4 1122 BP: xxxx FEE4 1122 BP: xxxx FEE4 1122
FEE2 3344 FEE2 3344 FEE2 3344
AX: 1122 FEE0 5566 AX: 1122 FEE0 5566 AX: 1122 FEE0 5566
BX: 3344 FEDE xxxx BX: 3344 FEDE 1234 BX: 3344 FEDE 1234
DX: 5566 FEDC xxxx DX: 5566 FEDC xxxx DX: 5566 FEDC 0500
FEDA xxxx IP: 0500 FEDA xxxx FEDA xxxx

after mov BP, SP after mov AX, 98BBh after ret



SP: FEDC FEE6 xxxx SP: FEDC FEE6 xxxx SP: FEDE FEE6 xxxx
BP: FEDC FEE4 1122 BP: FEDC FEE4 1122 BP: FEDC FEE4 1122
FEE2 3344 FEE2 3344 FEE2 3344
AX: 1122 FEE0 5566 AX: 98BB FEE0 5566 AX: 98BB FEE0 5566
BX: 3344 FEDE 1234 BX: xxxx FEDE 1234 BX: xxxx FEDE 1234
DX: 5566 FEDC 0500 DX: xxxx FEDC 0500 DX: xxxx FEDC 0500
FEDA xxxx FEDA xxxx IP: 0500 FEDA xxxx

after add SP, 4h after pop BX after pop AX



SP: FEE2 FEE6 xxxx SP: FEE4 FEE6 xxxx SP: FEE6 FEE6 xxxx
BP: FEDC FEE4 1122 BP: FEDC FEE4 1122 BP: FEDC FEE4 1122
FEE2 3344 FEE2 3344 FEE2 3344
AX: 98BB FEE0 5566 AX: 98BB FEE0 5566 AX: 1122 FEE0 5566
BX: xxxx FEDE 1234 BX: 3344 FEDE 1234 BX: 3344 FEDE 1234
DX: xxxx FEDC 0500 DX: xxxx FEDC 0500 DX: xxxx FEDC 0500
FEDA xxxx FEDA xxxx FEDA xxxx

Figure 1. Stack modifications while the CPU executes the sample source code
4.3 Exercises
4.3.1 Exercise 1
Objective. Understand how input/output parameters are sent to/received from subprograms
through the stack/registers.

Requirement. Write a subprogram that receives as input parameters three unsigned 16-bit
numbers (sent through the stack) and returns their average (in AX). Exemplify the usage of this
subprogram in a program which replaces each element in an array of 16-bit numbers with the
average of the element and its neighbors.

Solution.

Note: The solution to this exercise implies writing the subprogram and calling it for every
sequence of three numbers in the input array.

1. Start the emulator.

2. Load the program called lab4_prog1.asm using the Open button. The Source Code window
should display the following program:

org 100h
jmp main

dataset dw 12h, 18h, 1Ah, 16h, 08h, 08h, 12h


windowSize dw 3h

main: lea BX, dataset


mov SI, 2h
mov CX, (windowSize-dataset)/2-2

process: push [BX+SI-2]


push [BX+SI]
push [BX+SI+2]
call average
mov [BX+SI], AX
add SP, 6h
add SI, 2h
loop process
int 20h

average: mov BP, SP


mov AX, [BP+2]
mov DX, 0h
add AX, [BP+4]
adc DX, 0h
add AX, [BP+6]
adc DX, 0h
div word ptr windowSize
ret
3. Understand the program!

3.1. Note that the source code is segmented in three zones:

3.1.1. a zone that defines the array of numbers (called dataset) and the number of
elements which will be averaged at each step (windowSize),

3.1.2. a zone labeled main, which also includes the zone labeled process, representing the
main program,

3.1.3. a zone labeled average representing the subprogram.

3.2. The first instruction in this program (jmp main) jumps over the variable declaration
zone.

3.3. In this program BX is used to store the start address of the data array and it is initialized
from the beginning (lea BX, dataset). SI stores the index of the current element in the
array. Its value is doubled because each element of the array is stored in the memory
using two memory locations (therefore iterating through the dataset involves
incrementing SI by two). The first element to be processed is 18h, not 12h, because the
program has to replace each element with the average of him and its left-right
neighbors (and the first element does not have a left neighbor). This is why SI is
initialized with 2h. CX stores the number of elements to be processed. CX is initialized
with the number of elements minus two, because the first and the last elements in the
array will not be processed. Note that windowSize-dataset represents the number of
memory locations allocated for the array of numbers and this value has to be divided by
two to obtain the actual number of elements in the dataset (each element spawns over
two memory locations).

3.4. The zone labeled process starts by pushing in the stack the input parameters for the
subprogram. These input parameters are: the left neighbor of the current element, the
current element and the right neighbor of the current element. The three values are
found in the memory at the addresses BX+SI-2, BX+SI and BX+SI+2. After these values are
pushed in the stack, the next instruction (call average) calls the subprogram. Lets
ignore the instructions in the subprogram for now and focus on what happens after the
return from the subprogram. The next instruction after the call (mov [BX+SI], AX)
overwrites the current element in the array with the value returned, as a result, from
the subprogram. The input parameters are popped out of the stack simply by adding 6h
to SP (add SP, 6h): three parameters of two bytes each. Going further, the next
instruction (add SI, 2h) increments the index to point to the next element. Finally, the
loop instruction decrements CX and, if there are still more numbers to be processed
(CX>0), it jumps back to the process label. When CX reaches 0, the processor continues
by executing the next instruction (int 20h) which ends the current program.

3.5. The average subprogram performs the average of the three numbers received as input
parameters through the stack. The instructions are very similar to those of the first
program in the first laboratory (lab1_prog1.asm), which performed the average of three
constant numbers. The difference is that now the numbers are initially stored in the
stack. The register BP is loaded with the current value of SP (mov BP, SP) so that the
input parameters can be accessed using the addresses BP+2, BP+4 and BP+6 (refer to
Figure 1 to remember why). Due to the fact that the sum of three 16-bit numbers can
result in a 17-bit number the sum will be done using DX AX. Consequently, AX is
initialized with the first number (mov AX, [BP+2]), and DX is initialized with 0h.
Afterwards, the second and the third numbers are added to AX and the carry flag is
added to DX each time. Finally, the sum (the 32-bit number formed as DX AX) is divided
by 3h (16-bit value). The quotient is implicitly stored in AX and the remainder is
implicitly stored in DX. The last instruction of the subprogram (ret) returns to the caller
program using the address stored in the top of the stack.

4. Compile the program and view the symbol list

4.1. Click the Compile button to compile the program.

4.2. You will be prompted to save the executable file. Save it with the recommended name.

4.3. View the compilation status in the Assembler Status dialog. If the program was edited
correctly the message should be lab4_prog1.asm is assembled successfully into 75
bytes.

4.4. Click the View Button and then Symbol Table to view the symbol table associated to this
program. The information presented in this list should be interpreted as follows:

the symbols dataset and windowSize are word variables (size = 2) stored in the
memory at the addresses 0102h and 0110h. Note that even though dataset
defines an array, the symbol dataset represents only the start address of this
array. These symbols can be associated with C pointers.

the symbols main, process, and average are labels of some instructions in the
program and are associated with the addresses of these instructions (0112h,
011Bh, and 0132h).

4.5. The list of symbols will help you find the data you are working with (in the memory).

5. Load the executable program in the emulator.

5.1. Click the Run button to load the program in the emulator and execute it.

6. Execute the program step-by-step, watch the status change of the registers, memory
locations, flags, etc. and write down observations.

6.1. Click the Reload button to reload the executed program.

6.2. Inspect the Emulator window and note that:

6.2.1. The current instruction (jmp 0112h) is highlighted. This is the first instruction in
the program and was loaded at the logical address 0700:0100 (segment address :
effective address). The effective address was imposed by the org 100h assembly
directive. This instruction is equivalent to the instruction you wrote in the program
(jmp main), because the symbol main was replaced with the address 0112h.

6.2.2. The value in the register IP (the register that stores the effective address of the
current instruction) is 0100h.

6.3. Click on View Menu -> Memory to view the current status of the memory. In the Address
Text Box write the address of the dataset variable: leave the segment address
unchanged (0700) and replace the effective address (0100) with the effective address of
inputString (0102). Click on the Update Button and note that:

6.3.1. The start address is now 0700:0102 and the values stored in the memory are 12h,
00h, 18h, 00h, 1Ah, 00h, 16h, 00h, .... Recognize that these are the numbers in
dataset: each value (12h, 18h, 1Ah, 16h) occupies two memory locations with the
most significant byte stored at the higher address and the least significant byte
stored at the lower address (little endian convention).

6.4. Click the Single Step button to execute the first instruction (jmp 0112h) and note that
register IP was loaded with the address of 0122h: a jump over the data declaration zone
(and to the first instruction in the main program) was performed.

6.5. Execute the next three instructions and note that the registers BX, SI and CX were loaded
with the values 0102h, 0002h and 0005h.

6.6. Click on View -> Stack to view the Stack Window. Simultaneously inspect the Emulator
Window and note that:

6.6.1. The values stored in the registers SS and SP are 0700h and FFFEh. Consequently, the
element in the top of the stack is stored in the memory at the address 0700:FFFE.

6.6.2. The highlighted value in the Stack Window (the element in the top of the stack) is
stored at the address 0700:FFFE.

6.7. Execute the next instruction (push [BX+SI-2]) and note that:

6.7.1. The value in the register SP was decremented by two to make room for another
element in the stack. SP now stores the value FFFCh.

6.7.2. The 16-bit number stored in the memory at address BX+SI-2 = 102h (0012h) is now
part of the stack (in fact it is new top of the stack).

6.8. Execute the next instruction (push [BX+SI]) and note that:

6.8.1. The value in the register SP was decremented by two to make room for another
element in the stack. SP now stores the value FFFAh.

6.8.2. The 16-bit number stored in the memory at address BX+SI = 104h (0018h) is now
part of the stack (in fact it is new top of the stack).

6.9. Execute the next instruction (push [BX+SI+2]) and note that:

6.9.1. The value in the register SP was decremented by two to make room for another
element in the stack. SP now stores the value FFF8h.

6.9.2. The 16-bit number stored in the memory at address BX+SI = 106h (001Ah) is now
part of the stack (in fact it is new top of the stack).

6.10. Inspect the Emulator Window and the Stack Window and note that:
6.10.1. The current instruction is call 0132h. Remember that 0132h is the address
associated with the average subprogram.

6.10.2. The value stored in the register IP is 0123h. This means that the address of the
current instruction in the main program is 0123h.

6.10.3. The value of the register SP is FFF8h.

6.10.4. The element in the top of the stack is 001Ah.

6.11. Execute the next instruction (call 0132h) and note that:

6.11.1. The value in the register SP was decremented by two to make room for another
element in the stack. SP now stores the value FFF6h.

6.11.2. The new value inserted in the stack is 0126h, which is probably the address of
the instruction following call average.

6.11.3. The value in the register IP is 0132h (the address to which the call instruction
was pointing to).

6.12. Execute the next instruction (mov BP, SP) and note that the value in register SP is
copied in register BP.

6.13. Execute the following instructions step-by-step and note how the input
parameters in the stack (the values at addresses BP+2, BP+4 and BP+6) are added to AX.

6.14. When you reach the div instruction, compute the remainder and the quotient of
the division on a piece of paper. Then execute the instruction and note that register AX
is loaded with the correct quotient (0016h) and register DX is loaded with the correct
remainder (0002h).

6.15. Inspect the Emulator Window and the Stack Window and note that:

6.15.1. The stack has not changed throughout the execution of the subprogram (the
value in the top of the stack is still 0126h).

6.15.2. The value in the register IP is 0146h.

6.16. Execute the ret instruction and note that:

6.16.1. The return address was popped out of the stack (SP was incremented by 2h).

6.16.2. The return address was used to perform the jump back in the main program: the
register IP was loaded with the address popped out of the stack (0126h).

6.17. Execute the next instruction (mov [BX+SI], AX) and note that the value in the
memory at address BX+SI (the current element in the dataset) was changed from 0018h
into 0016h.
6.18. Execute the next instruction (add SP, 6h) and not that the three input parameters
were popped out of the stack (the highlighted value in the stack is now the value at
address FFFEh).

6.19. Execute the next instruction (add SI, 2h) and note that the value in SI was
incremented by 2h. This is equivalent to passing to the third element in the array.

6.20. The current instruction is loop 011Bh. Check the symbol list and remember that
the process label was associated with the 011Bh address. Note that the register CX
stored the value 0005h. Execute the instruction and note that;

6.20.1. The value in CX was decremented.

6.20.2. The processor took a jump to the instruction with the address 011Bh (the first
instruction in the process loop).

6.21. Continue to execute the instructions step by step noting the modifications in the
dataset, in the stack and in CX (which counts the number of unprocessed elements).
Stop when CX reaches 1. Execute the instruction (loop 011Bh) and note that;

6.21.1. The value in CX was decremented and now it stores the value 0h.

6.21.2. The processor does not jump back to the instruction with the address 011Bh (the
first instruction in the process loop), but continues with the following instruction
(int 20h).

6.22. The current instruction is int 20h. Click the Single Step button twice and note
that a Message dialog, saying that the program has returned control to the operating
system is displayed. Click Ok.

7. Write down conclusions regarding the effect of the various instructions on the registers,
flags and memory locations.

4.3.2 Exercise 2
Objective. Understand how input/output parameters are sent to/received from subprograms
through the stack/registers.

Requirement. Write a subprogram that receives as input parameters the start address and the
number of elements in an array of 16-bit signed numbers and finds the maximum in the array.
The subprogram should return the maximum value in register AX and the address of the
maximum in register DI. Exemplify the usage of this subprogram in a program which calls it one
time and then ends.

Solution.

Note: The solution to this exercise implies writing the subprogram and calling it once from a
main program.

1. Start the emulator.


2. Load the program called lab4_prog2.asm using the Open button. The Source Code window
should display the following program:

org 100h
jmp main

numbers dw 0172h, -218, 2B0h, 16h, -102

main: push offset numbers


push (main-numbers)/2
call getMax
int 20h

getMax: mov BP, SP


mov CX, [BP+2]
mov BX, [BP+4]
mov AX, [BX]
mov DI, BX

findLoop: cmp AX, [BX]


jl changeMax
nextElem: add BX, 2h
loop findLoop
ret

changeMax: mov AX, [BX]


mov DI, BX
jmp nextElem

3. Understand the program!

4. Compile the program.

5. Load the executable program in the emulator.

6. Execute the program step-by-step, watch the status change of the registers, memory
locations, flags, etc. and write down observations.

7. Write down conclusions regarding the effect of the various instructions on the registers,
flags and memory locations.

4.3.3 Exercise 3
Objective. Understand how input/output parameters are sent to/received from subprograms
through the stack/registers.

Requirement. Use the subprogram presented in the previous exercise to create a program that
sorts descending and array of 16-bit signed numbers.

Solution.

Note: The solution to this exercise implies writing a program that calls successively the
subprogram in the previous exercise, finding at each iteration the maximum in a shorter and
shorter array and exchanging this maximum with the first element of the array. In other words,
the subprogram will be called first using the start address of the array and the number of
elements = 5. After the return, the identified maximum will be exchanged with the first element
in the array. Next, the subprogram will be called again, using as start address the address of the
second element in the array and the number of elements = 5-1 = 4.

1. Start the emulator.

2. Load the program called lab4_prog3.asm using the Open button. The Source Code window
should display the following program:

org 100h
jmp main

numbers dw 0172h, -218h, 2B0h, 16h, -102h

main: lea BX, numbers


mov CX, (main-numbers)/2

sortLoop: push BX
push CX
call getMax
pop CX
pop BX
xchg [BX], AX
xchg AX, [DI]
add BX, 2h
loop sortLoop

int 20h

getMax: mov BP, SP


mov CX, [BP+2]
mov BX, [BP+4]
mov AX, [BX]
mov DI, BX

findLoop: cmp AX, [BX]


jl changeMax
nextElem: add BX, 2h
loop findLoop
ret

changeMax: mov AX, [BX]


mov DI, BX
jmp nextElem

3. Understand the program!

4. Compile the program.


5. Load the executable program in the emulator.

6. Execute the program step-by-step, watch the status change of the registers, memory
locations, flags, etc. and write down observations.

7. Write down conclusions regarding the effect of the various instructions on the registers,
flags and memory locations.
4.4 Appendix 1. Call and ret instruction examples
CALL Call Subprogram
Usage: CALL dest
Arguments:
dest (target) address of the first instruction in the called subprogram; can be an
immediate value, a general purpose register or a memory location;
Effects: The address of the next instruction is saved in the stack and the instruction pointer is
set to the target address (the CPU performs a jump to the subprogram): (SP) (SP) 2,
((SS):(SP)+1) (IPhigh), ((SS):(SP)) (IPlow), (IP) <- (dest)
Flags: none
Misc: Usually there is a RET instruction in the subprogram to return to the instruction after the
call.

Example
RET Return from Subprogram
Usage: RET
Arguments: none
Effects: The CPU pops the value in the top of the stack and uses it to jump back to the caller
program: (IPhigh) ((SS):(SP)+1), (IPlow) ((SS):(SP)), (SP) (SP) + 2.
Flags: none
Misc: Usually the address was placed in the stack by a call instruction and the return is made to
the address that follows the call instruction.

Example

Das könnte Ihnen auch gefallen