2011-09-04 65 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) –