条件跳转如何工作,基于x86程序集中的标志值?

时间:2017-07-23 01:24:23

标签: assembly x86 conditional branch

我正在阅读Jeff Duntemann的汇编语言循序渐进,我对一些条件跳转的工作原理感到困惑。我理解CMP用于使用减法比较两个值,然后抛弃结果以设置标志。

有没有办法弄清楚需要设置/取消设置哪些标志?我理解JEJNE它会查看ZF是否已设置,但我不确定其他分支操作。

这是我坚持的部分:

ClearLine:
    pushad                  ; Save all caller’s GP registers
    mov edx,15              ; We’re going to go 16 pokes, counting from 0
    .poke:  mov eax,1   
    sub edx,1
    jae .poke               ; Loop back if EDX >= 0
    popad                   
    ret                     `

如果EDX> = 0,为什么JAE会回来?如果EDX> = 1,它不会循环回来吗?毕竟,SUB操作就像CMP操作一样,但需要额外的步骤来保存结果。所以基本上说CMP edx,1我们不会说#34;如果第一个操作数(EDX)大于或等于第二个操作数(1)"则跳转?但是当我在调试器中测试时,它会显示它循环16次,而不是15次。我不明白为什么会这样。

1 个答案:

答案 0 :(得分:2)

根据问题中的措辞,似乎至少部分困惑源于没有正确地将比较指令与条件跳转指令分开。 CMP首先设置标志,然后条件跳转根据标志的状态进行分支。有许多不同的指令设置标志(几乎所有的算术和按位指令设置标志;有关详细信息,请参阅每条指令的文档),并且 none 中的任何分支都可以。为了根据标志进行分支,您需要Jcc指令(其中cc是条件代码,指示它将检查的标志,例如ae,这意味着&# 34;上述或相等到"。)

我指出这一点的原因是因为你说的是​​:

  

所以基本上说CMP edx,我们说'#34;如果第一个操作数(EDX)大于或等于第二个操作数(1)"则跳转?

可能只是描述实际发生的快捷方式,但仍然是一个不正确的心理模型,不可避免地会导致混乱。 CMP指令永远不会跳跃。它只是设置标志。您确定它将标记设置为与减法(SUB)完全相同,但标志不会执行任何操作,直到您执行Jcc为止相应地读取和分支的指令。

虽然您已经了解它们,但我们会从JE / JZJNE / JNZ开始,因为它们是最简单的条件了解。这些只是查看零标志(ZF),并根据其状态进行分支。 JE恰好等同于JZ。只有两种不同的助记符允许程序员根据他们认为会使代码更清晰,更易于阅读的内容进行选择。例如,当您执行CMP时,跟随JE通常是有意义的,因为逻辑,如果两个值相等,您就会跳跃。 技术上,如果减法的结果是0,你实际上是跳跃的,因为CMP设置了像SUB这样的标志,所以这就是为什么它是100相当于写JZ的%,你只是看不到程序员经常这样做。相反,当您执行TEST reg, reg之类的操作时,您经常会看到JZ后面的内容,因为如果最后一次操作的结果将其视为跳跃,则会更加语义化是零。添加"不是"对病情有明显的影响。

您可以找到条件分支指令here的非常有用的表格。我仍然发现自己经常咨询这张桌子或类似的东西。作为初学者,最有用的是助记符的文本描述。作为一个更高级的程序员,最有用的东西将成为助记符映射到被检查的实际标志。 (实际的代码字节有时也非常方便。)

正如您所看到的,JAE表示"如果超过或等于"则跳跃,这取决于进位标志(CF)的状态。如果未设置进位,则将采用分支。如果设置了进位,则执行将失败。正如该表也告诉您的那样,这对于 unsigned 比较非常方便。为什么?因为那是进位标志的用途。 I just wrote a lengthy answer explaining the carry and overflow flags here。它比您需要的更详细,但仍包含相关位,如这些标志的定义。

您还会在该图表中看到JAE有多个助记符,就像我们在JEJZ中看到的那样。替代助记符为JNBJNC。第一个JNB非常明显 - 这只是JAE的反面。如果某个值高于或等于另一个值,那么它也不低于该值。 JNC只是对跳转所基于的标志的更直接的描述:进位标志。同样,它在技术上并不重要,但如果您仔细选择,它通常会使您的代码在语义上更正确和可读。

通过这种概念性理解,让我们更详细地看一下您的代码:

    mov edx, 15
.poke:
    mov eax, 1
    sub edx, 1
    jae .poke

(我不喜欢你的格式化,所以我稍微重写了一遍。: - p)显然,这会将EDX设置为15,然后进入循环。在循环内部,它从EDX中减去1并设置标志。然后,当且仅当进位标志({{1} })未设置。

另一种思考方式是,当且仅当JAE中的值高于或等于1时,循环才会继续。符号上,这只是:.poke。当然,除了这个符号表达并不能正确表示我们正在进行无符号比较。正如我在上面链接的其他答案中提到的,CPU不知道或不关心值是有符号还是无符号。这是程序员要解释的东西。您使用相同的CF(或EDX)指令来执行有符号和无符号减法(比较)。之后您会看到哪些标志会发生什么变化。进位标志(EDX >= 1)用于无符号减法/比较;溢出标志(SUB)用于签名比较/减法。

让我们来看看CMP的几个示例值,以确保我们理解逻辑。

第一次循环,当CF为15时,OF指令从15减去1.结果当然是14.因此,零标志({{1} })设置为0(因为结果为非零)。进位标志(EDX)设置为0,因为没有进位(无无符号溢出)。溢出标志(EDX)设置为0,因为没有签名溢出。符号标志(SUB)设置为0,因为结果是无符号的(其符号标志,这是最重要的位,未设置,表示该值为正)。根据{{​​1}}的状态,ZF将分支回CF并继续循环。从逻辑上讲,您将保持循环,因为OF(15)中的值高于或等于1.

同样的事情持续了一段时间。我们让循环旋转,然后在SF为1时中断它。现在,CF指令从1中减去1.结果为0.因此,JAE是1(结果为零),.poke为0(未发生符号溢出),EDX为0(无进位,无符号溢出),EDX为0(结果为无符号) )。那么,分支会被采取吗?是的,SUB是0.逻辑上,1高于或等于1(当然等于)。

下次,ZF为0,因此将从0中减去1.结果为-1。 OF为0(结果为非零),CF为0(未发生签名溢出),SF为1(进位发生 - 即无符号溢出),{{1是1(结果已签名)。这次分支,因为CF是1.逻辑上,这是有道理的,因为0 高于或等于1(记住,这是无符号比较。)

这就是为什么它总共循环16次。它在EDX为15时循环,并且继续循环向上通过 ZF为0.这是因为您的条件测试位于 bottom 循环。也就是说,用C表示法:

OF
相关问题