Returning a value from a procedure via the stack
I am looking into assembly and I need to write a procedure (function) that gets a number and returns 1
if it is an even number and 0
if it is not.
I have to return the answer not via register or flags, but via the stack (for example, I cannot answer the question bx
or ax
and check their value in the main program). How can i do this?
source to share
The following program was made using Intel EMU8086 Intel syntax (just copy, paste and run), this is what it does: displays a message, fixes a number from the keyboard, converts a number from a string to a number, checks if the number is even or odd, saves " 1 "or" 0 "on the stack and displays a message depending on" 1 "or" 0 ". Here it is, many comments to help you understand:
.stack 100h
;------------------------------------------
.data
msj1 db 13,10,'Enter the number: $'
msj2 db 13,10,'The number is even$'
msj3 db 13,10,'The number is odd$'
str db 6 ;MAX NUMBER OF CHARACTERS ALLOWED (5).
db ? ;LENGTH (NUMBER OF CHARACTERS ENTERED BY USER).
db 6 dup (?) ;CHARACTERS ENTERED BY USER.
;------------------------------------------
.code
;INITIALIZE DATA SEGMENT.
mov ax, @data
mov ds, ax
;DISPLAY MESSAGE.
call clear_screen ;DECLARED AT THE END OF THIS CODE.
mov ah, 9
mov dx, offset msj1
int 21h
;CAPTURE NUMBER FROM KEYBOARD AS STRING.
mov ah, 0Ah
mov dx, offset str
int 21h
;CONVERT CAPTURED NUMBER FROM STRING TO NUMERIC.
mov si, offset str ;PARAMETER FOR STRING2NUMBER.
call string2number ;NUMBER RETURNS IN BX.
;CALL PROC TO FIND OUT IF NUMBER IS EVEN OR ODD. THE INSTRUCTION
;"CALL" WILL PUSH IN STACK THE ADDRESS OF THIS INSTRUCTION, THAT
;HOW IT KNOWS HOW TO COME BACK HERE TO CONTINUE EXECUTION.
call even_or_odd
;GET RESULT FROM STACK.
pop ax
;DISPLAY RESULT.
cmp al, '1'
je even_number
;IF NO JUMP, AL == '0'.
mov ah, 9
mov dx, offset msj3
int 21h
jmp wait_for_key ;SKIP "EVEN_NUMBER".
even_number:
mov ah, 9
mov dx, offset msj2
int 21h
;WAIT FOR USER TO PRESS ANY KEY.
wait_for_key:
mov ah,7
int 21h
;FINISH THE PROGRAM.
mov ax, 4c00h
int 21h
;------------------------------------------
;THIS PROCEDURE RETURNS '1' IN STACK IF THE NUMBER
;IS EVEN OR '0' IF IT ODD.
;ASSUME THE NUMBER COMES IN BX.
proc even_or_odd
;DIVIDE NUMBER BY 2.
mov ax, bx
mov bl, 2
div bl ;AX / BL (NUMBER / 2). RESULT : QUOTIENT=AL, REMAINDER=AH.
;IF REMAINDER IS 0 THEN NUMBER IS EVEN, ELSE IT ODD.
cmp ah, 0
je its_even
;IF NO JUMP, IT ODD.
mov ax, '0' ;VALUE TO STORE IN STACK.
jmp finish ;SKIP "ITS_EVEN".
its_even:
mov ax, '1' ;VALUE TO STORE IN STACK.
finish:
;STORE VALUE IN STACK. IMPORTANT: WHEN THIS PROCEDURE
;WAS CALLED, THE RETURN ADDRESS WAS PUSHED IN STACK. TO
;RETURN THE VALUE IN STACK IT NECESSARY TO RETRIEVE
;THE RETURN ADDRESS FIRST, PUSH THE VALUE ('0' OR '1')
;AND PUSH THE RETURN ADDRESS BACK.
pop bx ;RETRIEVE RETURN ADDRESS FROM THE CALL.
push ax ;VALUE TO RETURN ('0' OR '1').
push bx ;PUT RETURN ADDRESS BACK.
ret ;THIS "RET" POPS THE RETURN ADDRESS. THIS IS HOW
endp ;IT KNOWS HOW TO RETURN WHERE THE PROC WAS CALLED.
;------------------------------------------
;CONVERT STRING TO NUMBER IN BX.
;SI MUST ENTER POINTING TO THE STRING.
proc string2number
;MAKE SI TO POINT TO THE LEAST SIGNIFICANT DIGIT.
inc si ;POINTS TO THE NUMBER OF CHARACTERS ENTERED.
mov cl, [ si ] ;NUMBER OF CHARACTERS ENTERED.
mov ch, 0 ;CLEAR CH, NOW CX==CL.
add si, cx ;NOW SI POINTS TO LEAST SIGNIFICANT DIGIT.
;CONVERT STRING.
mov bx, 0
mov bp, 1 ;MULTIPLE OF 10 TO MULTIPLY EVERY DIGIT.
repeat:
;CONVERT CHARACTER.
mov al, [ si ] ;CHARACTER TO PROCESS.
sub al, 48 ;CONVERT ASCII CHARACTER TO DIGIT.
mov ah, 0 ;CLEAR AH, NOW AX==AL.
mul bp ;AX*BP = DX:AX.
add bx,ax ;ADD RESULT TO BX.
;INCREASE MULTIPLE OF 10 (1, 10, 100...).
mov ax, bp
mov bp, 10
mul bp ;AX*10 = DX:AX.
mov bp, ax ;NEW MULTIPLE OF 10.
;CHECK IF WE HAVE FINISHED.
dec si ;NEXT DIGIT TO PROCESS.
loop repeat ;COUNTER CX-1, IF NOT ZERO, REPEAT.
ret
endp
;------------------------------------------
proc clear_screen
mov ah,0
mov al,3
int 10H
ret
endp
Notice the "str" variable used to capture the number from the keyboard uses the 3-DB format: the first DB sets the maximum length (plus one for the end of chr (13)), the other DB for the length of the string entered by the user, and the third DB for the string itself.
The jester is another solution to the problem. There's even a third solution: by shifting the number to the right (SHR instruction), the flushed bit is stored in the carry flag, then we can check if the carry flag is 0 or 1 with JC or JNC instructions.
source to share
When a function returns a value on the stack, it is usually implemented
- deleting all values entered in the calling sequence (folded parameters, frame pointer and return address, saved registers), creating a clean stack with respect to the calling site,
- pushing a function value onto a clean stack,
- and then exit via the return address of the function.
You can implement these ideas directly, or you can do them in an optimized way.
Here's a typical, simple code to do this:
call_site: call get_number ; assumed to eax
push eax ; push argument onto the stack
call is_even_or_odd
pop eax ; get the function result back from the stack
test eax, eax
je even
odd: ...
is_even_or_odd:
push ebp ; save frame pointer
mov eax, 8[ebp] ; get argument (above saved EBP and return address)
and eax, 1 ; now eax == 0 if even, 1 if odd
pop ebp ; pop the push values from the stack
pop edx
leas 4[esp] ; pop the argument
push eax ; push the result
jmp edx ; go to the return address
The above procedure is coded generically. This particular procedure can be coded more compactly and has better performance:
is_even_or_odd:
; no need to save frame pointer; just leave EBP alone
pop edx ; get return address
pop eax ; pop the argument
and eax, 1 ; now eax == 0 if even, 1 if odd
push eax ; push the result
push edx ; instead of "jmp edx"
ret
A peculiar idiom at the end, hitting the return address and then executing "ret" allows the machine to maintain accurate tracking of return addresses in its shadow stack. This means that when it hits a ret instruction, it assumes that the original return address is the value (which is what it has in the shadow stack) and can immediately start receiving instructions at the return point. The "jmp edx" idiom works, but breaks down the branch address prediction, slowing down the time it takes to return from the subroutine.
Another variation uses space on the call stack for arguments to return a result. This works when the size of the arguments is equal to the size of the result, as in this example:
is_even_or_odd:
; no need to save frame pointer; just leave EBP alone
mov eax, 4[esp] ; get the argument
and eax, 1 ; now eax == 0 if even, 1 if odd
mov 4[esp], eax ; smash the argument with the result
ret
source to share
This is especially silly, but it is possible, of course. If you also get input on the stack, just replace it with the result. Assuming 16 bit code, something like this:
push bp
mov bp, sp
and word [bp+4], 1 ; keep lowest bit
xor byte [bp+4], 1 ; flip it to return 1 for even
pop bp
ret
source to share