如何在x86 Assembly中计算辅助标志状态

时间:2018-07-13 13:36:09

标签: assembly x86 nasm flags

在x86 Assembly中如何计算辅助标志?

我能找到的大多数 resources解释说,如果从第3位到第4位有进位,则辅助标志设置为'1'。

Wiki :

  

它指示在执行算术指令之后,何时从累加器寄存器的最低有效四位中产生进位或借位。

示例:

ID                          PRODS                        No. of Match
------------------------------------------------------------------------
1                           ,142,10,75,                  1
2                           ,142,87,63,                  2
3                           ,75,73,2,58,                 0
4                           ,142,2,                      1

* 中的括号显示了存储的二进制模式。

结果:mov al,-14 *(1111 0010) mov bl,-130 (0111 1110) sub al,bl (1111 0010 – 0111 1110) 将使用二进制补码计算为1111 0010 – 0111 1110,得出结果1111 0010 + 1000 0010

在给定的示例中,AF被设置为(= 1)。我不明白为什么会这样,因为我看不到从第3位到第4位有进位。0111 0100 + OF + 0010(最低有效位)的加等于{{1} },没有携带。累加器寄存器的最低有效四位已从0010更改为0100(从“ 2”更改为“ 4”),从低半字节到高半字节没有进位吗? >

请有人能解释我的想法哪里出问题了吗?

我怀疑大量的“负数”会在某个时候把我赶走,因为我在调试器中尝试了几个不同的示例,并且它们都按照我的期望行事,禁止这个示例。

1 个答案:

答案 0 :(得分:3)

自第一个芯片8086起,x86 CPU上的sub instruction是“真实”指令,即,它不是某种形式的汇编程序方便性,它转换为取反+加法,但是它具有自己的二进制操作码和CPU自己会意识到应该产生减法结果。

该指令具有Intel的定义,它如何影响标志,并且在这种情况下,标志被“好像”计算出实际减法一样被修改。当您专注于编程算法或检查某些代码的正确性时,这就是您所需要知道的。芯片本身是否实现,以及是否有一些额外的晶体管将标志转换为“减法”变体,都是“实现细节”,只要您只想知道结果,这并不重要

在调整特定代码的性能时,实现细节变得很重要,然后考虑芯片的内部体系结构和特定操作码的实现可能会为您提供一些思路,如何以更不直观/非人工的方式重写特定代码,通常甚至比“朴素”版本具有更多指令,但是由于对芯片内部实现的更好利用,性能会更好。

但是结果定义得很好,并且无法通过某些实现细节更改,这将是“ CPU漏洞”,就像第一批奔腾芯片确实为某些除法计算了错误结果一样。

也就是说,汇编指令的定义已经像其他语言一样泄漏了实现细节,因为在设计时,汇编指令位于“什么在硬件晶体管中创建简单”和“什么使某些编程意义”,而其他更高级别的编程语言则更偏向于“什么有意义”,但只是勉强地对硬件实现施加了一些麻烦的限制,例如变量类型的特定位大小的值范围。

因此好奇于实现以及为什么某些事物按其原样定义(例如为什么dec xxx不更新CF标志,否则只是sub xxx,1)通常会为您带来新的感觉深入了解如何在组装中更有效地编写某些任务,以及芯片如何演变以及哪些任务比其他任务更容易计算。

但首先要基础。 sub指令更新标志,就好像计算了减法一样,并且sub指令不知道它正在处理的值的任何上下文,它得到的只是值的二进制模式。情况:1111_0010 – 0111_1110是在带符号的8位数学运算中解释为“ -14-+126”(-130不适合8位,因此被截断为+126,好的汇编器将在其中发出警告/错误) ,或在未签名的8b数学“ 242-126”中解释时。如果是带符号数学运算,则结果应为-140,该值将被截断(发生溢出,OF = 1)为8b值+116;如果是带符号数学运算,结果为+116,而无符号溢出(进位/借位CF = 0)

减法本身是按位定义的,即

         1111_0010
       – 0111_1110
       ___________
result:  0111_0100
borrow:  0111_1100
              ^ this borrow goes to AF
         ^ the last borrow goes to CF
         ^ the last result bit goes to SF
  All zero result bits sets ZF=1
  PF is calculated from only low 8 bits of result (even with 32b registers!)
  where PF=1 means there was even number of set bits, like here 4.

您可以从右到左进行逐位减法,即0-0 = 0、1-1 = 0、0-1 = 1 + b,0-2 = 0 + b等。 (其中+ b表示需要“借用”,即第一个操作数被借用+2(下一位+1)以使结果有效位值+0或+1)

顺便说一句,在位级别上OF的精确设置有点棘手,因此在SO上有一些不错的Q + A,您可以搜索它,但是从数学的角度来看,如果结果以正负号被“截断”解释(如本例中所示),然后设置OF。这就是它的定义方式(并且实现方式与此一致)。

如您所见,所有标志都是按定义设置的,sub甚至都不知道第一个参数是-14还是+242,因为这不会改变在位级上的任何位,该指令将只从另一位中减去一个位模式,并按定义完成所有标志。位模式的实际含义以及标志结果的解释方式,取决于以下说明(代码逻辑),而与sub本身无关。

减法仍然有可能通过在CPU内部加法来实现(尽管这种情况不太可能,实现减法并不困难),并通过更多的标志处理来修复这些标志,但这取决于特定的芯片实现。

请记住,现代的x86是非常复杂的野兽,首先将经典的x86指令转换为微代码操作,并在可能的情况下对它们进行重新排序,以免出现停顿(如等待内存芯片的值),有时会在其中执行多个微操作。并行(一次IIRC最多进行3次操作),并使用一百多个物理寄存器,这些寄存器会动态重命名/映射为原始寄存器(如代码中的al, bl),即,如果您将这3行asm复制到其自身下两次,那么现代的x86 CPU实际上可能会与两个不同的物理“ al”寄存器完全并行地执行它,然后下一个要求结果“ al”的代码将从后面的一个中获取该值,第一个显然已被后者丢弃第二sub。但是,所有这些都经过定义和创建,以产生可观察到的结果,“就像经典的8086确实在真正的单个物理AL寄存器上依次运行每个指令”,至少在单核意义上(在多核/线程设置中有额外的指令,以允许程序员在代码的特定点序列化/完成结果,然后其他内核/线程可以检查它们以一致的方式查看它们。)

只要您只是学习x86程序集的基础知识,您甚至根本不需要知道现代x86 CPU内是否存在一些微体系结构,可以将您的机器代码转换为不同的代码(程序员无法直接使用,因此没有可以直接编写这些微操作的“现代x86微组件”,您只能生成常规的x86机器代码,并让CPU自己处理内部实现。