cmp 汇编指令如何设置标志(X86_64 GNU Linux)
How cmp assembly instruction sets flags (X86_64 GNU Linux)
这是一个简单的 C 程序:
void main()
{
unsigned char number1 = 4;
unsigned char number2 = 5;
if (number1 < number2)
{
number1 = 0;
}
}
所以我们在这里比较两个数字。在汇编中,它将使用 cmp 完成。
cmp 的工作原理是从一个操作数中减去另一个操作数。
现在cmp如何减去操作数?它是从第二个操作数中减去第一个操作数还是相反?无论如何,这应该是这样的:
案例#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 后命中断点:
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 [=13=]x4,-0x2(%rbp)
0x00000000004004fe <+8>: movb [=13=]x5,-0x1(%rbp)
0x0000000000400502 <+12>: movzbl -0x2(%rbp),%eax
0x0000000000400506 <+16>: cmp -0x1(%rbp),%al
=> 0x0000000000400509 <+19>: jae 0x40050f <main+25>
0x000000000040050b <+21>: movb [=13=]x0,-0x2(%rbp)
0x000000000040050f <+25>: pop %rbp
0x0000000000400510 <+26>: retq
End of assembler dump.
执行完 cmp 后的寄存器转储:
(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 执行减法但不存储结果。
因此,对标志的影响在以下之间完全相同:
cmp eax,ecx
sub eax,ecx
根据 documentation:
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.
旗帜效果
因此,以下标志会受到影响:
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 变体。
在这种情况下,您需要考虑
CMP %EAX, %ECX => result for the flags = ECX - EAX
CMP OP2, OP1 = flags = OP1 - OP2
而英特尔语法是
CMP ECX, EAX => result for the flags = ECX - EAX
CMP OP1, OP2 => flags = OP1 - OP2
您可以使用以下命令指示 gdb 向您显示 Intel 语法:set disassembly-flavor intel
我想我现在明白了。这就是我的想法(借用标志已设置)
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
7654 3210 <-- Bit number
1
1
0000 0000
- 0000 0101
------------
所以在上面的步骤中,我们将第一个操作数的位 4 写为两个 2(两个 1 在第一个操作数的位 2 之上。)
我们又知道,一个 2 可以写成两个 1。
所以我们从第一个操作数的第 1 位借一个 1,然后在第一个操作数的第 0 位写两个 1。
7654 3210 <-- Bit number
1
11
0000 0000
- 0000 0101
------------
现在我们准备对位 0 和位 1 执行减法。
7654 3210 <-- Bit number
1
11
0000 0000
- 0000 0101
------------
11
所以在解决了 bit 0 和 bit 1 之后,让我们看看 bit 2。
我们又遇到了同样的问题。
第一个操作数的位 2 = 0
第二个操作数的位 2 = 1
为此,让我们从第一个操作数的第 2 位左侧借一个 1。
8 7654 3210 <-- Bit number
1
11
1 0000 0000
- 0000 0101
------------
11
现在你看,第一个操作数的第8位是1,我们借用了这个1。
在这个阶段,将设置进位标志。所以CF=1.
现在,如果第 8 位为 1,则表示 256。
256 = 128 + 128
如果第7位为1,则表示128,我们可以改写为
8 7654 3210 <-- Bit number
1 1
1 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
11 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
111 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
1111 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
1111 1 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
1111 1111
0000 0000
- 0000 0101
------------
11
终于可以解决这个问题了
从上面的所有内容中减去第二个操作数将得到
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
这是一个简单的 C 程序:
void main()
{
unsigned char number1 = 4;
unsigned char number2 = 5;
if (number1 < number2)
{
number1 = 0;
}
}
所以我们在这里比较两个数字。在汇编中,它将使用 cmp 完成。 cmp 的工作原理是从一个操作数中减去另一个操作数。
现在cmp如何减去操作数?它是从第二个操作数中减去第一个操作数还是相反?无论如何,这应该是这样的:
案例#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 后命中断点:
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 [=13=]x4,-0x2(%rbp)
0x00000000004004fe <+8>: movb [=13=]x5,-0x1(%rbp)
0x0000000000400502 <+12>: movzbl -0x2(%rbp),%eax
0x0000000000400506 <+16>: cmp -0x1(%rbp),%al
=> 0x0000000000400509 <+19>: jae 0x40050f <main+25>
0x000000000040050b <+21>: movb [=13=]x0,-0x2(%rbp)
0x000000000040050f <+25>: pop %rbp
0x0000000000400510 <+26>: retq
End of assembler dump.
执行完 cmp 后的寄存器转储:
(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 执行减法但不存储结果。
因此,对标志的影响在以下之间完全相同:
cmp eax,ecx
sub eax,ecx
根据 documentation:
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.
旗帜效果
因此,以下标志会受到影响:
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 变体。
在这种情况下,您需要考虑
CMP %EAX, %ECX => result for the flags = ECX - EAX
CMP OP2, OP1 = flags = OP1 - OP2
而英特尔语法是
CMP ECX, EAX => result for the flags = ECX - EAX
CMP OP1, OP2 => flags = OP1 - OP2
您可以使用以下命令指示 gdb 向您显示 Intel 语法:set disassembly-flavor intel
我想我现在明白了。这就是我的想法(借用标志已设置)
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
7654 3210 <-- Bit number
1
1
0000 0000
- 0000 0101
------------
所以在上面的步骤中,我们将第一个操作数的位 4 写为两个 2(两个 1 在第一个操作数的位 2 之上。)
我们又知道,一个 2 可以写成两个 1。 所以我们从第一个操作数的第 1 位借一个 1,然后在第一个操作数的第 0 位写两个 1。
7654 3210 <-- Bit number
1
11
0000 0000
- 0000 0101
------------
现在我们准备对位 0 和位 1 执行减法。
7654 3210 <-- Bit number
1
11
0000 0000
- 0000 0101
------------
11
所以在解决了 bit 0 和 bit 1 之后,让我们看看 bit 2。
我们又遇到了同样的问题。
第一个操作数的位 2 = 0
第二个操作数的位 2 = 1
为此,让我们从第一个操作数的第 2 位左侧借一个 1。
8 7654 3210 <-- Bit number
1
11
1 0000 0000
- 0000 0101
------------
11
现在你看,第一个操作数的第8位是1,我们借用了这个1。
在这个阶段,将设置进位标志。所以CF=1.
现在,如果第 8 位为 1,则表示 256。
256 = 128 + 128
如果第7位为1,则表示128,我们可以改写为
8 7654 3210 <-- Bit number
1 1
1 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
11 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
111 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
1111 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
1111 1 11
0000 0000
- 0000 0101
------------
11
如前所述,我们可以将其重写为:
8 7654 3210 <-- Bit number
1 1
1111 1111
0000 0000
- 0000 0101
------------
11
终于可以解决这个问题了
从上面的所有内容中减去第二个操作数将得到
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