`testl` eax对抗eax?


118

我试图了解一些汇编。

汇编如下,我对这testl一行感兴趣:

000319df  8b4508        movl   0x08(%ebp), %eax  
000319e2  8b4004        movl   0x04(%eax), %eax  
000319e5  85c0          testl  %eax, %eax  
000319e7  7407          je     0x000319f0  

我想了解testl介于%eax和之间的那一点%eax。我认为这段代码的具体内容并不重要,我只是想了解自身的测试-价值不总是真实的吗?

Answers:


91

它测试是否eax为0或更高或更低。在这种情况下,如果eax为0 ,则进行跳转。


2
我进行了编辑,以将这个受欢迎的答案变成一个更好的规范答案,以暗示“这是TEST的全部内容,以及它与CMP有何不同”。有关同义的JE和JZ的语义含义的注释,请参阅我自己的答案。请检查我的修改内容,因为它相当大,仍然是您的答案。
彼得·科德斯

@PeterCordes我很高兴这样做,但是我将还原您的编辑。1.您的“声音”与我的有很大不同,现在,它的读音比我的更像您的答案。2.更大的问题是大胆的断言,即标志test和之间的标记完全相同cmp。是的,我了解您的信念是基于您对科迪的评论。但是,将其放在我的帖子中是另一回事。这不是我愿意坚持的主张,仅仅是因为我不知道在所有情况下它是否相同。
克里斯·杰斯特·杨

1
@PeterCordes如果我有空余时间,我想补充一下这个答案,以便更加规范。我会像写它一样写它,而且我对写东西的方式也很特别。:-)比如,我会写jejzcmp,和test,而不是JE,JZ,CMP或TEST。我很挑剔
克里斯·杰斯特·杨

1
我并不是想提高自己的答案。我实际上忘记了自己在进行编辑时回答了这个问题,后来才注意到。我只是在有人碰到它之后才看这个东西,然后从小的编辑开始滚雪球般地滚了过来。您没有想要回滚的冒犯;这只是一个建议,肯定听起来像我的作品,而不是您的作品。我将把自己写的内容放到自己的答案中。
彼得·科德斯

2
哇,在编辑完对这个问题的答案以包括我添加到您的答案之后,我意识到我几乎完全复制了我6月份写的大部分内容。糟糕!我用更多的理由更新了它,以支持我的主张test a,acmp $0,a设置相同的标志。感谢您指出这是不平凡的主张。re:TEST vs . test:最近,我开始使用像Intel手册这样的全大写字母。但是,当我谈论AT&T助记符与Intel助记符时,我testb对AT&T 使用样式。IDK是否有助于提高可读性。
彼得·科德斯

90

的意思test是将这些参数与在一起,并检查结果是否为零。因此,此代码测试EAX是否为零。je如果为零将跳。

顺便说一句,这会生成一条比cmp eax, 0其小的指令,这是编译器通常会以这种方式执行的原因。


34

测试指令在操作数之间进行逻辑“与”运算,但不会将结果写回到寄存器中。仅标志被更新。

在您的示例测试eax中,如果eax为零,则eax将设置零标志,如果设置了最高位,则将设置符号标志,以及其他一些标志。

如果设置了零标志,则跳转相等指令(je)。

您可以将代码转换为更具可读性的代码,如下所示:

cmp eax, 0
je  somewhere

它具有相同的功能,但需要更多字节的代码空间。这就是为什么编译器发出测试而不是比较的原因。


3
实际上,cmp可能在那里不起作用。也就是说,它适用于所介绍的特定情况,但是cmp对标志的影响与test不同,因为cmp是内部子变量而不是and。要记住的事情。
科迪·布罗西

4
对于零测试是完全有效的。
尼尔斯·派宾布林克

3
但是您不知道以后还会看到什么。对标志的影响是非常不同的,因此这可能是一个问题,并且经常出现。
科迪·布罗维奇

2
不,由不同的/ method /设置的唯一标志是进位和溢出,两者都设置为0。其他标志的/ values /会有所不同,因为cmp使用sub和test的使用和。
科迪·布罗维奇

2
@CodyBrocious:test eax, eaxcmp eax, 0都设置所有标志,并将它们设置为相同的值。两条指令均“根据结果”设置所有标志。减法0永远不会产生进位或溢出。你的论点比0任何其他即时正确的,但不能为0
彼得·科德斯

22

test类似于and,只是它只写FLAGS,而两个输入都保持不变。对于两个不同的输入,这对于测试某些位是否全部为零或是否设置了至少一个很有用。(例如,test al, 3如果EAX为4的倍数,则设置ZF(因此将其低2位都清零)。


test eax,eax将所有的标志正是以同样的方式cmp eax, 0

  • CF和OF清除(AND / TEST总是这样做;减零永远不会产生进位)
  • ZF,SF和PF根据EAX中的值。(a = a&a = a-0)。
    (通常仅根据低8位设置 PF )

除了过时的AF(辅助进位标志,由ASCII / BCD指令使用)。 TEST使它保持未定义状态,但是CMP会“根据结果”对其进行设置。由于减零不能从第4位到第5位产生进位,因此CMP应始终清除AF。


TEST较小(不立即),有时更快(可以比CMP在更多情况下将其宏融合到更多CPU上的“比较分支” uop中)。 这就是test比较寄存器与零的首选惯用法。这是一个窥视孔优化,cmp reg,0无论语义如何,都可以使用。

使用立即数CMP的唯一常见原因是要与内存操作数进行比较。例如,cmpb $0, (%esi)在隐式长度的C样式字符串的末尾检查终止的零字节。


AVX512F加法kortestw k1, k2和AVX512DQ / BW(Skylake-X但不支持KNL)加法ktestb/w/d/q k1, k2,它们对AVX512掩码寄存器(k0..k7)进行操作,但仍像设置test整数ORAND指令一样设置常规FLAGS 。(类似于SSE4 ptest或SSE的排序ucomiss:在SIMD域中输入,并产生整数FLAGS。)

kortestw k1,k1是根据AVX512比较结果来分支/ cmovcc / setcc的惯用方式,取代了SSE / AVX2 (v)pmovmskb/ps/pd+ testcmp


jzvs.的使用je可能会造成混淆。

jz并且je实际上是相同的指令,即机器代码中的相同操作码。 它们做同样的事情,但是对人类有不同的语义。反汇编程序(通常是编译器的asm输出)只会使用一个,因此会失去语义上的区别。

cmpsub设置ZF当其两个输入是相等的(即,相减结果为0)。 je(如果相等则跳转)是语义相关的同义词。

test %eax,%eax/ and %eax,%eax在结果为零时再次设置ZF,但是没有“平等”测试。测试后的ZF不会告诉您两个操作数是否相等。因此jz(如果为零则跳转)是语义上相关的同义词。


我会考虑添加有关test按位and操作的基本信息,这对于刚刚学习汇编的人来说可能并不明显(并且懒惰/不知道每60秒检查一次指令参考指南;):))。
Ped7g

1
@ Ped7g:足够公平,我想将所有内容放在此答案中而不是将其留给其他答案并没有什么害处。添加了AVX512 kortest*ktest*而我当时还在。
彼得·科德斯

顺便说一句,这基本上与我对相同问题的另一个版本的回答相同,但是我在那儿说了更多有关性能的内容,例如,可以通过用相同的值重写寄存器来避免像Nehalem这样的旧P6系列CPU上的寄存器读取停顿。
彼得·科德斯

@PeterCordes这应该是公认的答案:详尽而技术性的。与已接受的职位不同,这消除了人们对知识的好奇和渴望。保持下去,先生。
程序员

应当注意,PF设置为低8位的奇偶校验,在这种情况下为AL。
ecm

5

此代码段来自一个子例程,该子例程被赋予了指向某些内容(可能是某些结构或对象)的指针。第二行取消对该指针的引用,从该东西中获取一个值-可能本身就是一个指针,或者可能只是一个int,存储为第二个成员(偏移+4)。第三行和第四行将该值测试为零(如果是指针,则为NULL),如果该值为零,则跳过以下几个操作(未显示)。

零测试有时被编码为与立即数立即值零的比较,但是编写此代码的编译器(或人类?)可能认为testl op运行得更快-考虑到所有现代CPU的东西,例如流水线和寄存器重命名。它来自同一堆花样,其中包含用XOR EAX,EAX(我在科罗拉多州某人的车牌上看到的!)清除寄存器的想法,而不是明显但较慢的MOV EAX#0(我使用的是较旧的表示法) )。

在asm中,就像perl一样,TMTOWTDI。



0

在某些程序中,它们可用于检查缓冲区溢出。在分配的空间的最顶部放置一个0。将数据输入堆栈后,它将在分配的空间的开头查找0,以确保分配的空间不会溢出。

在exploits-exercises的stack0练习中使用它来检查它是否溢出,如果没有,并且那里为零,它将显示“再试一次”。

0x080483f4 <main+0>:    push   ebp
0x080483f5 <main+1>:    mov    ebp,esp
0x080483f7 <main+3>:    and    esp,0xfffffff0
0x080483fa <main+6>:    sub    esp,0x60                     
0x080483fd <main+9>:    mov    DWORD PTR [esp+0x5c],0x0 ;puts a zero on stack
0x08048405 <main+17>:   lea    eax,[esp+0x1c]
0x08048409 <main+21>:   mov    DWORD PTR [esp],eax
0x0804840c <main+24>:   call   0x804830c <gets@plt>
0x08048411 <main+29>:   mov    eax,DWORD PTR [esp+0x5c] 
0x08048415 <main+33>:   test   eax,eax                  ; checks if its zero
0x08048417 <main+35>:   je     0x8048427 <main+51>
0x08048419 <main+37>:   mov    DWORD PTR [esp],0x8048500 
0x08048420 <main+44>:   call   0x804832c <puts@plt>
0x08048425 <main+49>:   jmp    0x8048433 <main+63>
0x08048427 <main+51>:   mov    DWORD PTR [esp],0x8048529
0x0804842e <main+58>:   call   0x804832c <puts@plt>
0x08048433 <main+63>:   leave
0x08048434 <main+64>:   ret

我看不到这种检查寄存器是否为非零的特殊情况会给此问答带来什么。特别是cmp DWORD PTR [esp+0x5c], 0/ jz 0x8048427 <main+51>会比单独的MOV加载和TEST更有效。这几乎不是检查零的常见用例。
彼得·科德斯

-4

我们可以看到jgjle 如果testl %edx,%edx. jle .L3容易找到jle是合适的(SF^OF)|ZF,如果%edx为零,ZF = 1,但是如果%edx不为零且为-1,则在testl之后,OF = 0,并且SF = 1,所以标志= true,表示实现跳转。对不起,我的英语不好

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.