关于x86 64:cmp汇编指令如何设置标志(X86_64 GNU Linux)

How cmp assembly instruction sets flags (X86_64 GNU Linux)

这是一个简单的C程序:

1
2
3
4
5
6
7
8
9
10
void main()
{
       unsigned char number1 = 4;
       unsigned char number2 = 5;

       if (number1 < number2)
       {
               number1 = 0;
       }
}

因此,我们在这里比较两个数字。组装时将使用cmp完成。
cmp的工作原理是从另一个操作数中减去一个操作数。

现在,cmp如何减去操作数?是从2减去1还是从1减去1?无论如何,应该这样:

情况1:

4-5 =(0000 0100-0000 0101)=(0000 0100 + 1111 1010 +1)=(0000 0100 + 1111 1011)

= 1111 1111 = -1

因此,由于符号位= 1,所以SF应该为1。

无进位,因此CF应该= 0。

案例2:

5-4 =(0000 0101-0000 0100)=(0000 0101 + 1111 1011 +1)

=(0000 0101 + 1111 1100)= 1 0000 0001

所以在这里CF应该是= 1

由于结果为正,因此SF应该= 0

现在,我编译并运行程序(Linux x86_64,gcc,gdb),在cmp指令后放置一个断点以查看寄存器状态。

cmp之后命中断点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Breakpoint 2, 0x0000000000400509 in main ()
(gdb) disassemble
Dump of assembler code for function main:
   0x00000000004004f6 <+0>:     push   %rbp
   0x00000000004004f7 <+1>:     mov    %rsp,%rbp
   0x00000000004004fa <+4>:     movb   $0x4,-0x2(%rbp)
   0x00000000004004fe <+8>:     movb   $0x5,-0x1(%rbp)
   0x0000000000400502 <+12>:    movzbl -0x2(%rbp),%eax
   0x0000000000400506 <+16>:    cmp    -0x1(%rbp),%al
=> 0x0000000000400509 <+19>:    jae    0x40050f <main+25>
   0x000000000040050b <+21>:    movb   $0x0,-0x2(%rbp)
   0x000000000040050f <+25>:    pop    %rbp
   0x0000000000400510 <+26>:    retq
End of assembler dump.

在执行cmp之后注册转储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(gdb) info reg
rax            0x4  4
rbx            0x0  0
rcx            0x0  0
rdx            0x7fffffffe608   140737488348680
rsi            0x7fffffffe5f8   140737488348664
rdi            0x1  1
rbp            0x7fffffffe510   0x7fffffffe510
rsp            0x7fffffffe510   0x7fffffffe510
r8             0x7ffff7dd4dd0   140737351863760
r9             0x7ffff7de99d0   140737351948752
r10            0x833    2099
r11            0x7ffff7a2f950   140737348041040
r12            0x400400 4195328
r13            0x7fffffffe5f0   140737488348656
r14            0x0  0
r15            0x0  0
rip            0x400509 0x400509 <main+19>
eflags         0x297    [ CF PF AF SF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0  0
es             0x0  0
fs             0x0  0
gs             0x0  0
(gdb)

因此,我们可以看到在执行cmp之后,CF = 1,SF = 1。

因此实际的结果标志(CF = 1&SF = 1)不等于我们在

情况#1(CF = 0&SF = 1)或情况#2(CF = 1&SF = 0)

那怎么了cmp实际上是如何设置标志的?


CMP的操作
CMP执行减法,但不存储结果。
因此,对标志的影响在以下两个之间完全相同:

1
2
cmp eax,ecx
sub eax,ecx

根据文档:

Operation
temp ← SRC1 ? SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)
Flags Affected
The CF, OF, SF, ZF, AF, and PF flags are set according to the result.

对标志的影响
因此,以下标志将受到影响:

1
2
3
4
5
6
7
8
Assume result = op1 - op2

CF - 1 if unsigned op2 > unsigned op1
OF - 1 if sign bit of OP1 != sign bit of result
SF - 1 if MSB (aka sign bit) of result = 1
ZF - 1 if Result = 0 (i.e. op1=op2)
AF - 1 if Carry in the low nibble of result
PF - 1 if Parity of Least significant byte is even

我建议您在这里阅读OF和CF:http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt

操作数的顺序
我看到您喜欢疼痛,并且正在使用名为ATT语法的x86汇编的Braindead变体。
在这种情况下,您需要考虑到

1
2
CMP %EAX, %ECX  =>  result for the flags = ECX - EAX
CMP OP2, OP1    =   flags = OP1 - OP2

英特尔的语法是

1
2
CMP ECX, EAX    =>  result for the flags = ECX - EAX
CMP OP1, OP2    =>  flags = OP1 - OP2

您可以使用以下命令指示gdb向您显示Intel语法:set disassembly-flavor intel


我想我现在明白了。这就是我的想法(设置了借款标志)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
4 - 5

1st operand = 4 = 0000 0100
2nd operand = 5 = 0000 0101

So we have to perform

      1st operand
    - 2nd operand
    --------------


      7654 3210 <-- Bit number
      0000 0100
    - 0000 0101
    ------------

Lets start.

Bit 0 of 1st operand = 0
Bit 0 of 2nd operand = 1

so

  0
- 1
 ===
  ?

去做这个,

让我们从第一个操作数的位0的左侧借一个1。

所以我们看到第一个操作数的位2是1。

当位2 = 1时,表示4。

我们知道我们可以将4写成2 +2。所以我们可以将4写成两个2。

1
2
3
4
5
6
      7654 3210 <-- Bit number
             1
             1        
      0000 0000
    - 0000 0101
    ------------

因此,在上面的步骤中,我们将第一个操作数的位4写入了两个2(在第一个操作数的位2之上的两个1)。

现在再次众所周知,一个2可以写成两个1。
因此,我们从第一个操作数的位1借一个1,并在第一个操作数的位0上写两个1。

1
2
3
4
5
6
      7654 3210 <-- Bit number
              1
             11        
      0000 0000
    - 0000 0101
    ------------

现在我们准备对位0和位1进行减法。

1
2
3
4
5
6
7
      7654 3210 <-- Bit number
              1
             11        
      0000 0000
    - 0000 0101
    ------------
             11

因此,在解决了位0和位1之后,让我们看一下位2。

我们再次看到相同的问题。

第一个操作数的位2 = 0

第二个操作数的位2 = 1

为此,让我们从第一个操作数的第2位的左侧借一个1。

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
              1
             11        
    1 0000 0000
    - 0000 0101
    ------------
             11

现在您看到,第一个操作数的第8位为1。

在此阶段,将设置进位标志。因此CF = 1。

现在,如果位8为1,则表示256。

256 = 128 + 128

如果第7位是1,则表示128。我们可以重写为

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
      1       1
      1      11        
      0000 0000
    - 0000 0101
    ------------
             11

和以前一样,我们可以将其重写为:

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
       1      1
      11     11        
      0000 0000
    - 0000 0101
    ------------
             11

和以前一样,我们可以将其重写为:

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
        1     1
      111    11        
      0000 0000
    - 0000 0101
    ------------
             11

和以前一样,我们可以将其重写为:

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
         1    1
      1111   11        
      0000 0000
    - 0000 0101
    ------------
             11

和以前一样,我们可以将其重写为:

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
           1  1
      1111 1 11        
      0000 0000
    - 0000 0101
    ------------
             11

和以前一样,我们可以将其重写为:

1
2
3
4
5
6
7
    8 7654 3210 <-- Bit number
            1 1
      1111 1111        
      0000 0000
    - 0000 0101
    ------------
             11

最后我们可以解决这个问题。

从上面减去第二个操作数将得到

1
2
3
4
5
6
7
8
9
10
    8 7654 3210 <-- Bit number
            1 1
      1111 1111        
      0000 0000
    - 0000 0101
    ------------
      1111 1111


So result = 1111 1111

注意,结果中的符号位=位7 = 1

因此将设置标志标志。即SF = 1

因此SF = 1,CF = 1,4-5之后