标志寄存器非常重要,是需要记住的。

0x1标志寄存器

在od中,寄存器窗口里面有一个EFL寄存器,EFL寄存器就是标志寄存器,它是一个32位的寄存器。

标志寄存器

标志寄存器有32个二进制位组成,在看标志寄存器的时候要把数值转换成二进制来看。例如现在EFL储存的值是246,那么转换成二进制就是

1
2
3
4
5
2: 0010
4: 0100
6: 0110

连起来就是: 0010 0100 0110

EFL寄存器里面2前面全都是0,转换成二进制还是0,那么就省略了。标志寄存器的这8个十六制位可以拆分成32个二进制位。这32个二进制位里就包含了很多个标志位。其实OD已经自动把常用的标志位显示出来了,在寄存器窗口中,EIP和EFL之间的那一段就是OD自动拆分的标志位。虽然od很方便,但是我们也要把标志寄存器和标志位弄熟悉。这个很重要。

标志寄存器中各个标志位所在的位置:

标志位

0x2 CF位

进位标志CF位(carry flag): 进位标志是标志寄存器中右边第1位,如果运算结果的最高位产生了进位或者借位,CF寄存器值为1,否则为0;

现在的标志位如下图:

把0xFF放入一个8位的寄存器al里面,0xFF是一个8位寄存器能够储存的最大值,那这个寄存器的值再加1,那么最高位就必定进位。汇编代码:

1
2
mov al,0xFF
add al,1

执行这两行代码,再看标志位:

CF标志位就已经变成1了。现在EFL寄存器里面的值是257,转换成二进制:

1
2
3
4
5
2: 0010
5: 0101
7: 0111

连起来就是: 0010 0101 0111

右边第一位是1。

0x3 PF位

奇偶标志PF位(Parity Flag): 奇偶标志是标志寄存器中右边第3位,当运算结果的最后一个字节的二进制形式中,1的个数为偶数那么PF的值为1,否则为0。

为了便于观察,首先把标志位重置为0,并且把0x3储存到al寄存器:

al为3,所有标志位为0

然后把al的值加上0x3:

1
add al,0x3

0x3 + 0x3 = 0x6,6的二进制:

1
6: 0110

运算结果的二进制形式是0110,0110里面”1”的个数是2,2是偶数,所以PF位的值就变成了1:

al为6,PE位为1

再把al的值加上0x2:

1
add al,0x2

0x6 + 0x2 = 0x8,8的二进制:

1
8: 1000

运算结果的二进制形式是1000,1000里面”1”的个数是1,1是奇数,所以PF位就变成了0:

al为8,PF位为0

最后一个字节怎样理解呢?

上面的两个例子用的是一个8位寄存器al,al的宽度是字节,byte。

0x3 + 0x3 = 0x6:

1
2
3
4
    0000 0011
+ 0000 0011
-------------
0000 0110

0x6 + 0x2 = 0x8:

1
2
3
4
    0000 0110
+ 0000 0010
-------------
0000 1000

al寄存器在eax寄存器里就是最后一个字节了,所以0x3 + 0x3的结果中1的个数为偶数个,PF位变成1,0x6 + 0x2的结果中1的个数为奇数个,PF位变成0。

再看另外一个问题:0x803 + 0x1,PF位是0,而不是1:

0x803 + 0x1,PF位是0

0x804的二进制是100000000100,明明就有2个”1”。PF位为什么是0,不是1呢?把0x803 + 0x1转换成二进制来看:

1
2
3
4
5
6
7
    0000 0000     0000 0000     0000 1000     0000 0011
+ 0000 0000 0000 0000 0000 0000 0000 0001
-------------------------------------------------------
0000 0000 0000 0000 0000 1000 0000 0100
| AH | | AL |
| AX |
| EAX |

PF位看的不是你整个运算结果的二进制中”1”的个数,而是运算结果的最后一个字节的二进制中”1”的个数。那么0x804的最后一个字节就是0x04,0x04转换成二进制就是0000 0100。很明显”1”的个数是1个,1是奇数,所以PF位是0。

0x4 AF位

辅助进位标志AF位(Auxiliary carry Flag): 辅助进位标志是标志寄存器中右边第5位。

在32位的数据宽度中,一共有8个十六进制位,如果运算后右边第4个十六进制位产生了进位或者借位,AF位的值为1,否则为0;

在16位的数据宽度中,一共有4个十六进制位,如果运算后的右边第2个十六进制位产生了进位或者借位,AF位的值为1,否则为0;

怎样理解32位的宽度有8个十六进制位,16位的宽度有4个十六进制位这些概念,首先要看这张图,这是一个32位寄存器eax:

32位的宽度由32个0或者1组成。

每4个二进制位分成一组来表示1一个十六进制位,每个十六进制位占用4个二进制位;

每2个十六进制位分成一组来表示一个byte,每个byte占用8个二进制位;

每2个byte位分成一组来表示一个word,每个word占用16个二进制位;

每2个word分成一组来表示一个dword,每个dword占用32个二进制位;

先来看32位的情况,首先把一个立即数0xFFF存放到32位寄存器eax里面,并且把所有的标志位重置为0,便于观察:

1
mov eax,0xFFF

eax为0x00000FFF,AF位为0

现在eax里面是0x00000FFF,AF位是0。我标红的那个0就是第4个十六进制位,第4个十六进制位后面都是最大值F了,如果再加上1,那么必定会产生进位。给eax的值加上1:

1
add eax,0x1

执行汇编代码以后:

eax为0x00001000,AF位为1

F加1等于0,进一位。所以0x00000FFF + 1 = 0x00001000,第4位从之前的0变成了1,产生了进位,所以AF位的值就变成了1。

16位的情况下也是同理,先把一个立即数0xBBAF放到16位寄存器ax里面,并且把所有标志位重置为0,便于观察:

1
mov ax,0xBBAF

ax为0xBBAF,AF位为0

现在16位寄存器ax里面的值是0xBBAF,我标红那个A就是第2个十六进制位,如果运算结果中第2个十六进制位产生进位或借位,AF标志位就会变成1。给ax加1:

1
add ax,1

执行汇编代码以后:

ax位0xBBB0,AF位为1

F加1等于0,进一位。F加1进了一位,那么A加1等于B,所以0xBBAF + 0x1 = 0xBBB0。在运算中,第2个十六进制位的A出现了进位,所以AF位的值变成了1。

在8位的情况下也是同理。

0x5 ZF位

零标志位ZF(Zero Flag): 零标志位是标志寄存器中的第7,当运算结果为0的时候,ZF位的值为1,否则为0。

首先把0x11111111放进寄存器eax里面,然后把标志位重置为0,便于观察:

1
mov eax,0x11111111

eax为0x11111111,ZF位为0

然后计算eax和eax异或运算的结果:

1
xor eax,eax

1和1异或的结果为0,运算结果为0,所以ZF位的值变成1:

eax和eax进行异或运算,结果为0,ZF位为1

再来一个运算结果不为0的,现在eax里面的值是0x0,那么给它加上0x1,0x0 + 0x1 = 0x1,这个的运算结果就不为0,那么ZF位肯定会变成0。

1
add eax,0x1

计算结果为1,所以ZF为变成0

Ps: 当一个寄存器与它自身进行异或,结果必定是0。而异或又是一个运算,所以让一个寄存器和它自己进行异或可以同时做到既将寄存器清零,又修改ZF标志位。

0x6 SF位

符号标志位SF(Sign Flag): 符号标志位是标志寄存器中的第9位,在有符号运算时,如果运算结果为正数时,SF位为0,否则为1;在无符号运算时,SF位只能确定最高位为1,除此之外没有任何作用。

首先把0x1储存到寄存器eax里,并且将SF位设置为0:

1
mov eax,0x1

eax为0x1,SF位为0

然后让eax的值减2,1减2等于负一,运算结果为负数了,在看SF标志位变成1了:

运算结果为负数,SF位变成1

刚才eax里的值变成-1,SF位再给它加上3,-1 + 3等于2,结果是正数,再看运算结果为正数时SF位的值:

1
add eax,0x3

运算结果为正数,SF位变成0

0x7 OF位

溢出标志位OF(Overflow Flag): 溢出标志位是标志寄存器中的第13位,当计算结果发生溢出的时候,OF位为1,否则为0;

到底溢没溢出要分为两种情况,一种是有符号运算,一种是无符号运算。

这里用一个8位的寄存器al来举例吧,一个8位的寄存器就是拥有8个二进制位的寄存器,8个二进制位可以表示2个十六进制位。

在无符号的时候,一个8位的寄存器能表示的数值的范围就是0x00到0xFF。这些数全都是正数;

在有符号的时候,这个寄存器就要被从中间平均分成两半: 从0x00到0x7F的范围表示正数,从0x80到0xFF的范围表示为负数;

在8位有符号时,0x7F是正数中最大的数,0x7F再加1就变成了0x80,都侵占到了0x80,0x80本来是用来表示负数的空间,这种情况就称之为溢出。它表现出来的结果就是正数加正数结果变成了负数。0x7F + 0x1 = 0x80。转换成十进制就是127 + 1 = -1。比如这里先把0x7F放到8位寄存器al里面,并且把标志位清零便于观察:

al为0x7F,OF位为0

再给al加1:

al加1变成0x80以后,OF位从刚开始的0变成了1。

最终它到底有没有溢出取决于你把它当成是有符号运算还是无符号运算。比如0x7F + 0x1,如果你把它当成是有符号运算,那么它算是溢出了,如果你把它当成是无符号运算,那么它就不算溢出,只是进位。(是否溢出和OF位无关,和你怎么定义有关。反正只要溢出OF位就变成1,但是到底算不算是溢出由你来定义。)