2011-09-04 66 views
3

我正在學習x86程序集。我想知道你是如何有條件地調用子程序的。 據我所知,跳轉到標籤不起作用,因爲返回地址沒有存儲,因此它不知道在哪裏返回。有條件地調用子程序在程序集中

cmp bx, 0 
jz zero ; how do I do this correctly ? 
; do something else and exit 

zero: 
; do something 
ret 
+2

我還沒寫過程序集,但我想我記得在堆棧或寄存器中推送地址,並在子程序結束時跳到該值。 –

回答

6

那麼它的工作原理,如果你不需要返回到該地址。通常情況下,你可以構建你的代碼,這樣的話。

否則,您將不得不使用Jxx指令來跳轉呼叫站點或以其他方式構造圍繞此限制的代碼。在這種情況下,反向測試應該工作:

cmp bx, 0 
    jnz not_zero 
    call zero 
    ; fall through here, return or do what you want 
not_zero: 
    ; do something else and exit 
    ; ... 
    ret 

zero: 
    ; do something 
    ret 

編輯2016年4月25日:由於@Peter科德斯在評論中提到,下面的代碼可能會執行得要命。見例如this article爲解釋爲什麼。

@Manny Ds在評論中的建議激勵我寫下如下內容。它可能不是清潔劑或更好,但它是另一種方式來構建它:

push back_from_zero ; Push where we want to return after possibly branching to 'zero' 
    cmp bx, 0 
    jz zero ; branch if bx==0 
    add esp, 4 ; adjust stack in case we didn't branch 
back_from_zero: ; returning from the zero branch here or continuing from above 
    ; do something else and exit 

zero: 
    ; do something 
    ret 

它明確地推堆棧上的返回地址,因此zero函數可以返回,或從棧中彈出值(add esp, 4)如果我們不要調用函數(重新調整爲堆棧)。請注意,如果您希望它在16位或64位模式下工作,您需要稍做調整。

+0

不幸的是,我有'if-else'的情況,所以我不認爲我可以像那樣重組。我可能在一個單獨的子例程中爲那個特定的情況提供了那麼一小段代碼,但是這看起來有點冒失。我希望有一個乾淨的方式來做到這一點。 – Jon

+3

如果性能很重要,切勿使用'push back_from_zero'技巧。現代CPU保留了一個返回地址預測堆棧,而一個錯誤匹配的'call' /'ret'打破了這個堆棧,導致每個'ret'的分支錯誤預測,可能是接下來的15個級別的回報。通過手動返回被調用的函數(不帶'ret')來避免這種情況:例如彈出返回地址和'jmp edx'。或者在帶有紅色區域的64位ABI中,也可以調整堆棧,然後執行內存間接的'jmp [rsp - 8]'。該間接jmp可能不會有BTB條目,但不會破壞其他代碼的性能。 –

+0

@PeterCordes:好點,我添加了一個警告。 – user786653

2

我相信正確的方法是使用call指令。這相當於使用更高級的編程語言的函數調用。 PC存儲在堆棧中,因此zero:子程序結束時的ret可以完成它應有的功能。

+0

我知道'call'指令,但我不知道基於條件調用它的語法。 – Jon

+1

「PC」在x86上被稱爲「eip」;) – BlackBear

3

乾淨的方式做到這一點很簡單:

cmp bx,0 
    jnz notzero 
    ; handle case for zero here 
    jmp after_notzero 
notzero: 
    ; handle case for not zero here 
after_notzero: 
    ; continue with rest of processing 

我知道了如果其他情況沒有更好的辦法。好吧,如果這兩個機構必須直接返回之後,你可以這樣做:

cp bx,0 
    jnz notzero: 
    ; handle case for zero here 
    ret 

notzero: 
    ; handle case for not zero here 
    ret 

如果一些處理必須發生在RET之前(如雨後春筍般冒出值以前壓),你應該使用第一種方法。

+0

您可以在asm中執行一個if/else,而其中一個路徑上沒有采用分支。條件跳轉到函數中'ret'後的一個塊,所以快速路徑不需要跳過它。所以快速路徑有一個未被採用的分支,而慢路徑有兩個被採用的分支。 (它會跳回到你想要的任何地方。)gcc一直都在做這樣的代碼:一小塊代碼帶有一個或多個insn,然後一個'jmp'備份到函數的主要部分。好的一點是有條件的尾巴呼叫是可能的。 [gcc沒有,但可以](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=42497) –