2016-05-29 134 views
6

我试图在程序集中制作一个子程序,它将在屏幕上绘制一个正方形。我不认为我可以像在C++中那样将参数传递给子程序,所以我认为我可以使用堆栈来存储和访问参数(我不能使用公共数据寄存器,因为变量太多通过)。x86程序集:通过堆栈将参数传递给函数

问题是(我记得在某处读到),当我使用call命令将当前“程序”的地址保存在堆栈中时,这样当它使用“ret”命令时,它会知道在哪里回来。但是,如果我在堆栈中存储了一些东西,然后调用该函数,我将不得不在地址的某个地方(即堆栈顶部)保存,然后安全地弹出这些参数。然后在代码完成之后,在调用“ret”之前,我将不得不推回该地址。

我对不对?如果是的话,我可以在哪里存储地址(我不认为该地址只有1个字节长,以便它适合AX或BX或任何其他数据寄存器)。我可以使用IP来做到这一点(虽然我知道这是用于其他)?

这是我想象:

[BITS 16] 
.... 
main: 
    mov ax,100b 
    push ax 
    call rectangle ;??--pushes on the stack the current address? 

jml $ 

rectangle: 
    pop ax ;??--this is the addres of main right(where the call was made)? 
    pop bx ;??--this is the real 100b, right? 
    .... 
    push ax 
ret ;-uses the address saved in stack 

回答

8

通常,您使用的基指针(bp 16位,ebp 32位)来引用参数和当地人。

其基本思想是每次进入一个函数时,都会将堆栈指针保存在基指针中,以便在该函数执行过程中函数被调用为“固定参考点”时将堆栈指针指向。在这个模式中,[ebp-something]通常是本地的,[ebp+something]是一个参数。

移调典型的32位,被调用者清理调用约定,你可以这样做:

来电者:

push param1 
push param2 
call subroutine 

子程序:

push bp  ; save old base pointer 
mov bp,sp  ; use the current stack pointer as new base pointer 
; now the situation of the stack is 
; bp+0 => old base pointer 
; bp+2 => return address 
; bp+4 => param2 
; bp+6 => param1 
mov ax,[bp+4] ; that's param2 
mov bx,[bp+6] ; that's param1 
; ... do your stuff, use the stack all you want, 
; just make sure that by when we get here push/pop have balanced out 
pop bp  ; restore old base pointer 
ret 4   ; return, popping the extra 4 bytes of the arguments in the process 
+0

是不是BP已经在子程序中使用了,因为我不知道什么地址? '16位BP寄存器主要帮助引用传递给子程序的参数变量。 SS寄存器中的地址与BP中的偏移量组合以获取参数的位置。 BP还可以与DI和SI结合作为特殊寻址的基址寄存器。' (来自http://www.tutorialspoint.com/assembly_programming/assembly_registers.htm) –

+0

为什么[BP + 6]?我知道[]在引用地址时使用。如果BP是子例程的地址(我猜),那么[BP + 6]将指向子例程右边的命令? (我有点新,所以我可能是错的..)。为什么6? (我知道+1意味着例如下一个可以指向var或其他地址的地址) –

+0

因此..2实际上意味着2个字节是正确的?......跳过2个字节不是整个地址(I到+1意味着跳过一个完整的地址,无论它的长度是多少...)。 –

3

这会工作,但从来电者的角度来看,你的功能修改了sp。在32位大多数调用约定中,只允许修改eax/ecx/edx,并且如果他们想要使用它们则必须保存/恢复其他regs。我假设16bit是相似的。 (虽然在asm中当然可以用你喜欢的任何自定义调用约定来编写函数)。

一些调用约定期望被调用者弹出由调用者推送的参数,所以在这种情况下实际上可以工作。 Matteo的回答中的ret 4就是这样。 (见信息的标签维基上调用约定,以及其他良好的联系吨)。


这是超级怪异,而不是做的事情最好的办法,这就是为什么它不正常使用。 最大的问题是它只能让你访问参数,而不是随机访问。你只能访问前6个参数,因为你用尽了寄存器来弹出它们。

它还绑定了一个寄存器,其中包含返回地址。 x86(在x86-64之前)有很少的寄存器,所以这是非常糟糕的。在将其他函数参数弹出到寄存器中后,您可以推送返回地址,我想可以将其释放以供使用。

jmp ax在技术上工作,而不是push/ret,但这违背了返回地址预测,减缓未来ret指令。


但无论如何,使得与push bp/mov bp, sp堆栈帧中的16位代码被普遍使用,因为它的价格便宜,给你随机访问堆栈。 ([sp +/- constant]不是16位有效的寻址模式(但它是在32位和64位)([bp +/- constant]有效)然后你可以随时从它们重新加载它们

在32位和64位代码中,编译器通常使用[esp + 8]之类的寻址模式或其他任何方法,而不是浪费指令并绑定ebp(是默认值),这意味着您必须跟踪对esp的更改以找出相同数据的正确偏移量在不同的指令中,所以它在手写asm中并不流行,尤其是在教程/教材中。在真正的代码中,你显然会做任何最有效的工作,因为如果你愿意牺牲效率,那么你只需要使用C编译器。