lll*_*lll 2 x86 assembly flags intel cpu-registers
我正在分析一系列x86指令,并对以下代码感到困惑:
135328495: sbb edx, edx
135328497: neg edx
135328499: test edx, edx
135328503: jz 0x810f31c
Run Code Online (Sandbox Code Playgroud)
我了解这sbb相当于以des = des - (src + CF)某种方式-CF输入的第一条指令edx。然后它negtive -CF变成CF,test是否CF等于零?
但是请注意,不要jz检查标志!那么上述代码序列基本上想做什么?这是合法的指令序列,由version 产生。ZFCFx86g++4.6.3
该C++代码实际上来自botan项目。您可以在此处找到总体汇编代码(Botan RSA解密示例)。反汇编代码中有很多这样的指令序列。
Run Code Online (Sandbox Code Playgroud)sbb edx, edx
您对此指令的分析是正确的。SBB意思是“减去借方”。它以一种考虑进位标记(CF)的方式从目的地减去源。
因此,它等同于dst = dst - (src + CF),所以这是edx = edx - (edx + CF)或简单地edx = -CF。
不要让源操作数和目标操作数都在edx这里!SBB same, same在编译器生成的代码中是一个很常见的习惯用法,用于隔离进位标志(CF),尤其是当它们尝试生成无分支代码时。还有其他方法可以执行此操作,即SETC指令,该指令在大多数x86架构上可能会更快(请参阅注释以获取更详尽的剖析),但数量并不多。来自不同供应商(甚至可能有不同版本)的编译器往往倾向于使用另一种,而当您不进行特定于体系结构的调整时,它们会在各处使用。
Run Code Online (Sandbox Code Playgroud)neg edx
同样,您对此指令的分析是正确的。这很简单。NEG在其操作数上执行二进制补码求反。因此,这就是edx = -edx。
在这种情况下,我们知道edx最初包含-CF,这意味着它的初始值为0or或-1(因为CF始终为0或1,所以打开或关闭)。取反表示edx现在包含0或1。
也就是说,如果CF在最初设置,edx将现在包含1; 否则,它将包含0。这实际上是上述习语的完成;您需要NEG完全隔离的值CF。
Run Code Online (Sandbox Code Playgroud)test edx, edx
该TEST指令与该AND指令相同,不同之处在于它不影响目标操作数,它仅设置标志。
但这是另一种特殊情况。TEST same, same是优化代码中的标准惯用法,可以有效地确定寄存器中的值是否为0。您可以编写CMP edx, 0,这是人类程序员天真地做的,但是test速度更快。(为什么这样做呢?因为按位与的真值表。唯一的情况value & value == 0是when value是0。)
因此,这具有设置标志的作用。具体来说,它将零标志(ZF)设置edx为0,如果edx不为零,则将其清除。
因此,如果CF在最初设置,ZF现在将清晰; 否则,它将被设置。也许更简单的方法是将这三个指令设置ZF为与的原始值相反CF。
这是两个可能的数据流:
CF== 0→ edx= 0 edx = 0→→ZF = 1CF== 1→ edx= -1 edx = 1→→ZF = 0Run Code Online (Sandbox Code Playgroud)jz 0x810f31c
最后,这是基于的值的条件跳转ZF。如果ZF设置为,则跳至0x810f31c;否则,它将进入下一条指令。
然后,将所有内容放在一起,此代码CF通过涉及零标志(ZF)的间接路由来测试进位标志()的补码。如果进位标志最初被清除,则分支,如果进位标志最初被置位,则分支通过。
就是这样。也就是说,我无法解释为什么编译器选择以这种方式生成代码。在许多级别上,它似乎都不理想。最明显的是,编译器可能只是发出了一条JNC指令(如果没有进位则跳转)。尽管我和彼得·科德斯(Peter Cordes)在评论中做出了各种其他观察和推测,但除非可以提供有关此代码起源的更多信息,否则我认为将所有这些都纳入答案是没有道理的。