Assembly 在程序集中有条件地调用子例程

Assembly 在程序集中有条件地调用子例程,assembly,x86,Assembly,X86,我正在学习x86汇编。我想知道如何有条件地调用子例程。 据我所知,跳转到标签不起作用,因为返回地址没有存储,因此它不知道返回到哪里 cmp bx, 0 jz zero ; how do I do this correctly ? ; do something else and exit zero: ; do something ret 如果你不需要回到那个地址,它就行了。通常情况下,您可以对代码进行结构化,使其成为现实 否则,您将不得不使用Jxx指令进行分支,这些指令在调用站点上

我正在学习x86汇编。我想知道如何有条件地调用子例程。 据我所知,跳转到标签不起作用,因为返回地址没有存储,因此它不知道返回到哪里

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

 zero:
 ; do something
 ret

如果你不需要回到那个地址,它就行了。通常情况下,您可以对代码进行结构化,使其成为现实

否则,您将不得不使用
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-04-25:正如@Peter Cordes在评论中提到的,下面的代码可能会执行得非常糟糕。有关原因的解释,请参见

@评论中的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位模式下工作,则需要进行一些轻微的调整。

我认为正确的方法是使用
调用
指令。这相当于高级编程语言中的函数调用。PC存储在堆栈上,因此
zero:
子例程末尾的
ret
执行它应该执行的操作。

干净的方法是:

    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
我知道没有更好的方法来应对“如果不是”的情况。好的,如果两个分支都必须随后直接返回,则可以执行以下操作:

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

notzero:
    ; handle case for not zero here
    ret

如果必须在ret之前进行某些处理(例如,先前推送的弹出值),你应该使用第一种方法。

我已经有一段时间没有编写汇编了,但我记得我在堆栈或寄存器中推送地址,然后跳到子程序末尾的那个值。我知道
call
指令,但我不知道根据条件调用它的语法。“PC”称为“eip”在x86上;)不幸的是,我有一个
if-else
的情况,所以我认为我不能像那样重组。我可能在一个单独的子例程中为那个特殊的情况编写了一段代码,但它似乎有点粗糙。我希望有一个干净的方法来做到这一点。如果性能很重要的话,永远不要使用
从零向后推的技巧。现代CPU保留一个返回地址预测堆栈,一个不匹配的
调用
/
ret
会破坏该堆栈,导致每个
ret
上的分支预测失误,可能会导致接下来15个级别的返回。通过让被调用的函数手动返回(不带
ret
)来避免这种情况:例如,弹出返回地址和
jmp-edx
。或者在带有红色区域的64位ABI中,您也可以调整堆栈,然后执行内存间接
jmp[rsp-8]
。该间接jmp可能不会有BTB条目,但不会破坏其他代码的性能。如果
zero
pop-edx
/
jmp-edx
或其他什么结尾,实际上是可以的。但是,只有在调用函数时没有
调用
insn时,这才有效。如果只有一个调用站点,Rudy关于如何正确构造分支以有条件地运行某些东西的回答是一个更好的方法。您可以在asm中执行If/else,其中一条路径上没有分支。条件跳转到函数中
ret
之后的一个块,因此快速路径不需要跳过它。因此,快速路径有一个未执行的分支,而慢速路径有两个执行的分支。(它跳回到任何你想要的地方。)gcc一直都是这样编写代码的:带有一个或多个INSN的小代码块,然后一个
jmp
返回到函数的主要部分。很好,有条件的尾部调用是可能的。