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语法:
我想我现在明白了。这就是我的想法(设置了借款标志)
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之后