2016-05-29 160 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編譯器。

相關問題