Pass by value and pass by reference in Assembly

为君一笑 提交于 2019-12-12 20:13:46

问题


I'm trying to solve this problem:

Create a PROC procedure that takes one parameter as Pass by Value and prints the number of 'X' according to the number passed as a parameter. Before printing, make sure parameter is a positive number, at end of program you need to change the registers that have been used back to their first values.

If the input to the procedure is 5 then the output on the console should be:

XXXXX

This is my code:

var db 5 ;In the dataseg  
push [var] ;in the codeseg  

proc example  
    pop cx  
    cmp cx,0  
    ja print  
print:  
    mov dl, 'X'  
    mov ah, 2h  
    int 21h  
    loop print  
    ret  
endp example  

Will this code work as expected, and if not, why? How can it be fixed?


回答1:


Nope, your code is seriously broken.

Functions access their args from the stack with stuff like mov ecx, [esp+4]. Or in 16bit code, [sp+2] isn't an encodable addressing mode, so you should make a stack frame:
push bp / mov bp, sp / mov cx, [bp+4]. (And undo this at the end of the function). Google "stack frame" for more about this, and how x86 functions access their args on the stack. (Or see the x86 tag wiki).

This is assuming you need to pass args on the stack. Some calling conventions pass the first couple args in registers, which saves instructions esp. in small functions.


Your function ends with ret, which means you're assuming you were called with a call. The return address will be at [sp] (or [esp] in 32bit code) on function entry. A pop at that point will load the return address. If an interrupt comes in while the return address is below sp, it will be overwritten, so it's not safe to pop and then adjust sp back down again before ret.

Pushing an arg and then falling through into a function is not good practice.


Your branch is also wrong:

    cmp cx, 0
    ja print  
print:         ; execution ends up here whether the branch is taken or not

Using ja means you're treating your arg as unsigned. That's fine, since it's still possible for it to be non-positive by being zero. However, I think the assignment was intending you to treat the arg as signed. Try something like jle nonpositive, and put nonpositive: somewhere useful.


The x86 tag wiki has some links to tutorials and reference material. With that + a debugger + google, you should be able to answer your own questions.




回答2:


As an addendum to Peter's answer you can use MASM/TASM to generate the prologue and epilogue code to set up BP for you, and allow you to access procedure/function arguments by label. A reasonably good tutorial about PROC subroutines used by MASM and TASM can be found here

I've also changed var to be a WORD instead of a BYTE. The resulting code looks like:

.MODEL SMALL
.STACK 100H

.DATA
    var dw 5           ; Change to 16-bit WORD

.CODE

example proc C         ; C Calling convention - parameters on stack right to left
    ARG num:WORD       ; We take one argument called `num` that is a word

    mov cx, num        ; Move the 16-bit value in `num` to CX counter
                       ;     same as: mov cx, [bp+4]
                       ;     [bp+0] is saved copy of BP put on stack by MASM's prologue
                       ;     [bp+2] return address placed on stack by CALL
    cmp cx, 0
    jle negative       ; If we are less than or equal to 0, exit procedure
    mov dl, 'X'
    mov ah, 2h         ; ah and dl not destroyed by int 21h/ah=2 so set them once
                       ;     before loop
print:
    int 21h            ; Print an 'X'
    loop print         ; Continue until loop is 0
negative:
    ret
endp example

main proc
     mov ax, @data     ; initialize DS
     mov ds, ax

     push [var]        ; Push 2-byte value at `var` (pushing by value)
     call example
     add sp, 2         ; Remove parameter from stack
                       ;     Not necessary since we use int 21h to exit right after
     mov ah, 4ch       ; return control to DOS
     int 21h
main endp

end main               ; Entry point = label main

The code above would have generated these instructions for the procedure example:

example proc
    push bp            ; Save BP on stack  \
    mov bp, sp         ; Set BP to SP      / Function prologue
                       ;     [bp+0] is saved copy of BP put on stack by prologue
                       ;     [bp+2] return address placed on stack by CALL
                       ;     [bp+4] first parameter (NUM)

    mov cx, [bp+4]     ; Move the value at BP+4 (NUM) to CX counter

    cmp cx, 0
    jle negative       ; If we are less than or equal to 0, exit procedure
    mov dl, 'X'
    mov ah, 2h         ; ah and dl not destroyed by int 21h/ah=2 so set them once
                       ;     before loop
print:
    int 21h            ; Print an 'X'
    loop print         ; Continue until loop is 0
negative:
    mov sp, bp         ; Restore stack pointer \
    pop bp             ; Restore BP register   / Function epilogue
    ret
endp example

I leave it as an exercise to the reader to determine all the registers the example PROC changes, and saves/restores them as requested in the homework assignment. Hint: PUSH them after the ARG directive and POP them in reverse order just prior to the RET



来源:https://stackoverflow.com/questions/36293714/pass-by-value-and-pass-by-reference-in-assembly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!