我正在分析一系列x86
指令,并与以下代码混淆:
135328495: sbb edx, edx
135328497: neg edx
135328499: test edx, edx
135328503: jz 0x810f31c
我理解sbb
等于des = des - (src + CF)
,换句话说,第一条指令以某种方式将-CF
放入edx
。然后negtive
-CF
进入CF
,test
CF
是否等于零?
但请注意,jz
会检查标记ZF
,而不是CF
!那么基本上上面的代码序列是什么尝试呢?这是一个合法的x86
指令序列,由g++
版本4.6.3
生成。
C++
代码实际上来自botan项目。您可以在here找到整体汇编代码(Botan RSA解密示例)。在反汇编代码中有很多这样的指令序列。
答案 0 :(得分:5)
sbb edx, edx
您对此说明的分析是正确的。 SBB
表示“借用减法”。它以一种将进位标志(CF
)考虑在内的方式从目的地中减去来源。
因此,它相当于dst = dst - (src + CF)
,因此这是edx = edx - (edx + CF)
,或只是edx = -CF
。
不要让你欺骗源头和目标操作数都在这里edx
! SBB same, same
是编译器生成的代码中非常常见的习惯用法,用于隔离进位标志(CF
),尤其是当它们试图生成无分支代码时。还有其他方法可以做到这一点,即SETC
指令,在大多数x86架构中可能更快(请参阅注释以获得更彻底的解剖),但不是很多。来自不同供应商(甚至可能是不同版本)的编译器往往倾向于选择其中一个,并且在您不进行特定于体系结构的调优时随处使用。
neg edx
同样,您对此说明的分析是正确的。这是一个非常简单的。 NEG
对其操作数执行二进制补码。因此,这只是edx = -edx
。
在这种情况下,我们知道edx
最初包含-CF
,这意味着其初始值为0
或-1
(因为CF
是始终为0或1,开或关)。否定它意味着edx
现在包含0
或1
。
也就是说,如果CF
最初设置,edx
现在将包含1
;否则,它将包含0
。这实际上是上面讨论的习语的完成;您需要NEG
来完全隔离CF
的值。
test edx, edx
TEST
指令与AND
指令相同,只是它不影响目标操作数 - 它只设置标志。
但这是另一个特例。 TEST same, same
是a standard idiom in optimized code来有效地确定寄存器中的值是否为0.您可以编写CMP edx, 0
,这是人类程序员天真的做法,但test
更快。 (为什么这样做?由于按位AND的真值表。value & value == 0
在value
为0时的唯一情况。)
所以这具有设置标志的效果。具体来说,如果ZF
为0,则设置零标记(edx
),如果edx
非零,则将其清除。
因此,如果CF
最初设置,ZF
现在将会清除;否则,它将被设置。也许更简单的方法是将这三条指令设置为ZF
与CF
的原始值相反。
以下是两种可能的数据流:
CF
== 0→edx
= 0→edx = 0
→ZF = 1
CF
== 1→edx
= -1→edx = 1
→ZF = 0
jz 0x810f31c
最后,这是一个基于ZF
值的条件跳转。如果设置了ZF
,则跳转到0x810f31c
;否则,它会进入下一条指令。
将所有内容放在一起,然后,此代码通过涉及零标志(CF
)的间接路由测试进位标志(ZF
)的补码。如果进位标志最初是清除的,它会分支,如果最初设置了进位标志,它就会失效。
这就是它的工作原理。也就是说,我无法解释为什么编译器选择以这种方式生成代码。它似乎在许多级别上都是次优的。最明显的是,编译器可以简单地发出JNC
指令(如果不携带则跳转)。虽然Peter Cordes和我在评论中做了各种其他观察和推测,但我认为将所有这些都纳入答案是没有意义的,除非可以提供有关此代码来源的更多信息。
答案 1 :(得分:2)
我知道sbb等于des = des - (src + CF),换句话说,第一条指令以某种方式将-CF放入edx。
是的,edx = edx - (edx + CF) = -CF
。因此,sbb edx,edx
会在CF = 0时将edx
设置为0
,并在CF = 1时设置为-1
(0xFFFFFFFF
)。此外,减法本身会产生新的CF值,如果我没有太多困惑,它等于旧值。
然后它将-CF否定为CF,并测试CF是否等于零??
差不多但是没有。它否定edx
,而不是CF.为了否定CF,有单独的指令CMC
(来自stc/clc/cmc
进位标志修改指令系列)。
因此,从0 / -1开始,edx将被修改为0/1,CF将再次设置为0/1(哇,我不知道neg将CF设置为~ZF)。此外neg
已经设置了ZF,因此以下test edx,edx
是多余的。
test edx,edx
不测试CF,而是测试edx(此时为0
或1
),它将产生CF = 0和ZF = 1/0的0 / 1值。
所以你坚持认为edx
中的数值来自CF,你一直在想CF,但实际上从第一个sbb
开始,你就忘记了旧CF ,每个下一条指令(包括sbb
)都是算术运算,因此它会以自己的方式修改CF.但是那些neg/test
指令edx
集中在注册号码上,CF只是他们计算的副产品。
但请注意,jz会检查标志ZF,而不是CF!
确实,因为CF在0
之后确实包含test
,与sbb
之前的初始CF值完全无关。另一方面,ZF与原始CF值直接相关,如果代码以CF = 1开始,那么最后jz
将不被采用(ZF = 0),如果代码在CF = 0下开始,最后jz
将被采用(ZF = 1)。