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?

+3


source to share


3 answers


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.

+3


source


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

      

+2


source


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

      

0


source







All Articles