基于x86程序集中标志的值,条件跳转如何工作? [英] How do conditional jumps work, based on the values of the flags in x86 assembly?

查看:75
本文介绍了基于x86程序集中标志的值,条件跳转如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在逐步阅读Jeff Duntemann的汇编语言,我对某些条件跳转的工作方式感到困惑.我知道 CMP 用于通过减法比较两个值,然后丢弃结果以仅设置标志.

I was reading Jeff Duntemann's Assembly Language Step-by-Step, and I am confused about how some of the conditional jumps work. I understand that CMP is used to compare two values using subtraction and then throws away the result to just set the flags.

有什么办法可以找出需要设置/取消设置的标志吗?我知道对于 JE JNE ,它查看是否设置了 ZF ,但是我不确定其他分支操作.

Is there any way to figuring out which flags need to be set/unset? I understand for JE and JNE it looks at whether the ZF is set, but I'm unsure about the other branching operations.

这是我坚持的部分:

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 不是说如果第一个操作数( EDX )大于或等于第二个操作数( 1 ,则跳转>)"?但是,当我在调试器中对其进行测试时,它显示它循环了16次,而不是15次.我不明白为什么会这样.

Why does JAE loop back if EDX >= 0? Wouldn't it loop back if EDX >= 1? After all, the SUB operation is just like the CMP operation but with the extra step of saving the result. So by basically saying CMP edx,1 aren't we saying "jump if first operand (EDX) is greater or equal to second operand (1)"? But when I test it in a debugger, it shows it looped 16 times, not 15. I don't understand why that is.

推荐答案

基于问题的措辞,您似乎至少有部分困惑是由于没有正确地将比较指令与条件跳转指令区分开来. CMP 首先设置标志,然后根据标志的状态进行条件跳转分支.设置标志有很多不同的指令(实际上所有算术和按位指令都设置了标志;有关详细信息,请参见每个指令的文档),这些指令的 none 均不做任何分支.为了基于标志进行分支,您需要一条 Jcc 指令(其中 cc 是条件代码,指示它将检查的标志,例如 ae,意思是等于或高于").

Based on the phrasing in the question, it seems that at least part of your confusion stems from not properly separating the compare instruction from the conditional-jump instruction. The CMP first sets the flags, and then the conditional-jump branches according to the state of the flags. There are many different instructions that set flags (virtually all of the arithmetic and bitwise instructions set flags; see the documentation for each instruction for details), and none of these do any branching. In order to branch based on the flags, you need a Jcc instruction (where cc is the condition code, indicating the flags that it will check, such as ae, which means "above-or-equal-to").

我指出这一点的原因是因为您说的是这样的话

The reason I point this out is because you're saying things like:

因此,基本上说CMP edx,1不是说如果第一个操作数(EDX)大于或等于第二个操作数(1),则跳转"吗?

So by basically saying CMP edx,1 aren't we saying "jump if first operand (EDX) is greater or equal to second operand (1)"?

可能是可能的描述,它只是描述实际发生的事情的捷径,但仍然如此-这是一种错误的思维模式,不可避免地会引起混乱. CMP 指令从不执行任何跳转.它所做的只是设置标志.您是正确的,它设置标志的方式与减法( SUB )完全一样,但是在执行 Jcc 之前,标志不会任何操作.读取它们并相应分支的code>指令.

which is probably intended to just be a shortcut to describing what actually happens, but still—it is an incorrect mental model and will inevitably lead to confusion. The CMP instruction never does any jumping. All it does is set flags. You're correct that it sets the flags exactly like a subtraction (SUB) would, but the flags don't do anything until you execute a Jcc instruction that reads them and branches accordingly.

尽管您已经了解它们,但我们将从 JE / JZ JNE / JNZ 开始,因为它们是最容易理解的条件.这些仅查看零标志( ZF ),并根据其状态进行分支. JE 正好等同于 JZ .程序员可以根据自己的想法选择两种不同的助记符,以使自己的代码更清晰,更易于阅读.例如,当您执行 CMP 时,通常跟随 JE 是有意义的,因为逻辑上,如果两个值相等,您将跳转是平等的.从技术上说,如果减法的结果为0,则实际上是在跳,因为 CMP 设置像 SUB 这样的标志,所以这就是为什么它是100%相当于编写 JZ ,您只是不会看到程序员经常这样做.相反,当您执行诸如 TEST reg,reg 之类的操作时,通常会看到其后跟 JZ ,因为如果将以下内容视为结果,则将其视为跳跃是更语义上的最后一个操作为零.在条件中添加"not"具有明显的效果.

Although you already understand them, we'll start with JE/JZ and JNE/JNZ, because they're the easiest conditions to understand. These just look at the zero flag (ZF), and branch according to its state. JE is precisely equivalent to JZ. There are just two different mnemonics that programmers are allowed to choose between, based on what they think will make their code clearer and easier to read. For example, when you do a CMP, it usually makes sense to follow with JE, because logically, you're jumping if the two values were equal. Technically, you're actually jumping if the result of the subtraction was 0, because CMP sets flags like SUB, so this is why it's 100% equivalent to write JZ, you just won't see programmers do this as often. Conversely, when you do something like TEST reg, reg, you'll often see that followed by JZ, because it's more semantic to think of it as jumping if the result of the last operation was zero. Adding in the "not" to the condition has the obvious effect.

您可以在此处找到非常有用的条件分支说明表.我仍然发现自己会定期查阅这张桌子或类似的东西.作为初学者,最有用的是助记符的文字描述.作为更高级的程序员,最有用的事情将变为助记符到已检查的实际标志的映射.(有时,实际的代码字节也很方便.)

You can find a very helpful table of the conditional branching instructions here. I still find myself consulting this table or something much like it on a regular basis. As a beginner, the most useful thing there will be the textual description of the mnemonics. As a more advanced programmer, the most useful thing will become the mapping of the mnemonics to the actual flags that are checked. (The actual code bytes are quite handy sometimes, too.)

如您所见, JAE 的意思是如果大于或等于则跳转",这由进位标志( CF )的状态确定.如果未设置进位,则将采用分支.如果设置了进位,则执行将失败.正如该表还告诉您的那样,这对于 unsigned 比较很方便.为什么?因为这就是进位标志的用途.我刚刚写了一个很长的答案在此处解释进位和溢出标志.它比您需要的要详细一些,但仍然包含相关的位,例如这些标志的定义.

As you can see, JAE means "jump if above or equal", and that is determined by the status of the carry flag (CF). If carry is not set, then the branch will be taken. If carry is set, then execution will fall through. As the table also tells you, this is handy for unsigned comparisons. Why? Because that's what the carry flag is used for. I just wrote a lengthy answer explaining the carry and overflow flags here. It's a bit more detail than you need, but still contains relevant bits, like the definition of these flags.

您还将在该图表中看到 JAE 有多种助记符,就像我们在 JE JZ 中看到的一样.备用助记符是 JNB JNC .第一个是 JNB ,非常明显-这只是 JAE 的反义词.如果一个值大于或等于另一个值,那么它也不小于该值. JNC 只是对跳转所基于的标志的更直接的描述:进位标志.同样,使用哪种代码并不重要,但是如果您仔细选择,通常可以使代码更正确,更易读.

You'll also see in that chart that there are multiple mnemonics for JAE, just like we saw with JE and JZ. The alternative mnemonics are JNB and JNC. The first one, JNB, is pretty obvious—that's just the converse of JAE. If a value is above or equal to another value, then it is also not below that value. JNC is just a more literal description of what flags the jump is based upon: the carry flag. Again, it doesn't technically matter which one you use, but it often makes your code semantically more correct and readable if you choose carefully.

有了这种概念上的理解,让我们更详细地看一下您的代码:

With that conceptual understanding, let's look at your code in more detail:

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

(我不喜欢您的格式,所以我稍微改了一下.:-p)显然,这将 EDX 设置为15,然后进入循环.在循环内部,它从 EDX 中减去1并设置标志.然后,以下 JAE 指令查看标志的状态,并分支回到 .poke (继续循环) if并且仅当进位标记( CF )未设置.

(I didn't like your formatting, so I rewrote it slightly. :-p) Obviously, this sets EDX to 15, and then enters the loop. Inside of the loop, it subtracts 1 from EDX and sets flags. Then, the following JAE instruction looks at the state of the flags and branches back to .poke (continuing the loop) if and only if the carry flag (CF) is not set.

另一种思考方式是,当且仅当 EDX 中的值大于或等于1时,循环才会继续.; = 1 .当然,除了这个符号表达式不能正确表示我们正在做 unsigned 比较.正如我在上面链接的另一个答案中提到的那样,CPU不知道或不在乎值是带符号的还是无符号的.这是程序员要解释的东西.您使用完全相同的 SUB (或 CMP )指令进行带符号减法和无符号减法(比较).发生的变化是您随后要查看的标志.进位标志( CF )用于无符号减法/比较;溢出标志( OF )用于带符号的比较/减法.

Another way of thinking about this is that the loop continues if and only if the value in EDX is above-or-equal-to 1. Symbolically, that is just: EDX >= 1. Except, of course, that this symbolic expression doesn't properly signify that we are doing an unsigned comparison. As I mentioned in the other answer I linked above, the CPU doesn't know or care if values are signed or unsigned. That's something for the programmer to interpret. You use the same exact SUB (or CMP) instruction to do both signed and unsigned subtraction (comparison). What changes is which flags you look at afterwards. The carry flag (CF) is used for unsigned subtraction/comparison; the overflow flag (OF) is used for signed comparison/subtraction.

让我们看一下 EDX 的几个示例值,以确保我们理解逻辑.

Let's walk through a couple of sample values of EDX to make sure we understand the logic.

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

The first time through the loop, when EDX is 15, the SUB instruction subtracts 1 from 15. The result is, of course, 14. Thus, the zero flag (ZF) is set to 0 (because the result is non-zero). The carry flag (CF) is set to 0 because there was no carry (no unsigned overflow). The overflow flag (OF) is set to 0 because there was no signed overflow. The sign flag (SF) is set to 0 because the result was unsigned (its sign flag, which is the most significant bit, was not set, meaning that the value is positive). Based on the status of CF, the JAE will branch back to .poke and continue the loop. Logically, you will keep looping because the value in EDX (15) was above-or-equal-to 1.

同一件事持续了一段时间.我们让循环旋转,然后在 EDX 为1时中断它.现在, SUB 指令从1中减去1.结果为0.因此, ZF 为1(结果为零), OF 为0(未发生有符号溢出), CF 为0(无进位-即无符号溢出),并且 SF 为0(结果是无符号的).那么,分支将被采用吗?是的, CF 为0.从逻辑上讲,1等于1或大于1(当然,等于).

The same thing continues for some time. We'll let the loop spin, and then interrupt it when EDX is 1. Now, the SUB instruction subtracts 1 from 1. The result is 0. Thus, ZF is 1 (result is zero), OF is 0 (no signed overflow occurred), CF is 0 (no carry—i.e., unsigned overflow), and SF is 0 (result is unsigned). So, will the branch be taken? Yes, CF is 0. Logically, 1 is above-or-equal-to 1 (it is equal to, of course).

下一次, EDX 为0,因此将从0中减去1.结果为&min ;; 1. ZF 为0(结果不为零), OF 为0(未发生签名溢出), CF 为1(发生进位,即,无符号溢出),并且 SF 为1(结果是带符号的).这次是 not 分支,因为 CF 为1.从逻辑上讲,这是有道理的,因为0等于或不等于 not 到1(请记住,这是一个 unsigned 比较).

Next time, EDX is 0, so 1 will be subtracted from 0. The result is −1. ZF is 0 (result is non-zero), OF is 0 (no signed overflow occurred), CF is 1 (carry occurred—i.e., unsigned overflow), and SF is 1 (result is signed). The branch is not taken this time, because CF is 1. Logically, this makes sense, because 0 is not above-or-equal-to 1 (remember, this is an unsigned comparison).

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

This is why it looped 16 times in all. It looped when EDX was 15, and it continued looping up through EDX being 0. This is because your condition test was at the bottom of the loop. That is, in C notation:

do
{
    ...
}
while (edx-- >= 1);

这篇关于基于x86程序集中标志的值,条件跳转如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆