您的位置:首页 > 其它

一步步学习汇编(12)之标识指令(破解软件的必修课三)

2009-03-29 16:27 417 查看
n 8086CPU的flag寄存器的结构:



n flag的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11位都具有特殊的含义



1.ZF标志

n flag的第6位是ZF,零标志位。

它记录相关指令执行后,

n 结果为0 ,ZF = 1

n 结果不为0,ZF = 0

n 例子:比如:

mov ax,1

sub ax,1

指令执行后,结果为0,则ZF = 1。

mov ax,2

sub ax,1

指令执行后,结果为1,则ZF = 0。

n 对于ZF的值,我们可以这样来看,ZF标记相关指令的计算结果是否为0,如果为0,则在ZF要记录下“是0”这样的肯定信息。

n 在计算机中1 表示逻辑真,表示肯定,所以当结果为0的时候 ZF=1,表示“结果是0 ”。如果结果不为0,则ZF要记录下“不是0”这样的否定信息。

n 在计算机中0表示逻辑假,表示否定,所以当结果不为0 的时候ZF=0,表示“结果不是0”。

2.PF标志

n flag的第2位是PF,奇偶标志位。

它记录指令执行后,结果的所有二进制位中1的个数:

n 为偶数,PF = 1;

n 为奇数,PF = 0。

n 示例

n 指令:mov al,1

add al,10

执行后,结果为00001011B,其中有3(奇数)个1,则PF=0;

n 指令:mov al,1

or al,10

执行后,结果为00000011B,其中有2(偶数)个1,则PF=1;

3.SF标志

n flag的第7位是SF,符号标志位。

它记录指令执行后,

n 结果为负,SF = 1;

n 结果为正,SF = 0。

n 有符号数与补码

n 示例

mov al,10000001B

add al,1

结果: (al)=10000010B

解释:

n 我们知道计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。

n 比如:

n 00000001B ,可以看作为无符号数 1 ,或有符号数+1;

n 10000001B ,可以看作为无符号数129,也可以看作有符号数-127。

为什么有符号的数就是-`127呢?

解释:

1). 将该数的所有位取反(invert),也就是将所有的零改为一,所有的一改为零。

2). 将取反后的数加一(忽略溢出)。

例如,以下就是计算十进制数-5的8位表示的步骤:

%0000_0101

五 (二进制)

%1111_1010

所有位取反

%1111_1011

加一,得到−5 (二进制补码表示)

现在对-5再取负,结果就是5(%0000_0101),正如我们所预期的一样:

%1111_1011

-5的二进制补码表示

%0000_0100

所有位取反

%0000_0101

加一,得到5 (二进制)

-59的补码表示为:

结合上面的例子:

10000001B 如果先声明了为有符号数,那么第一位就是代表符号,此处为1,表示负数,然后将此此数取反+1,01111111B=64+32+16+8+4+2+1=127,也就是-127。

3). 理解有符号数和无符号数

我们所讲的数都是正数。同样是年纪和工资,前者不需要有负值,但后者可能需要——至少所有的老板都这样认为。

那么,负数在计算机中如何表示呢?

这一点,你可能听过两种不同的回答。

一种是教科书,它会告诉你:计算机用“补码”表示负数。可是有关“补码”的概念一说就得一节课,这一些我们需要在第6章中用一章的篇幅讲2进制的一切。再者,用“补码”表示负数,其实一种公式,公式的作用在于告诉你,想得问题的答案,应该如何计算。却并没有告诉你为什么用这个公式就可以和答案?

另一种是一些程序员告诉你的:用二进制数的最高位表示符号,最高位是0,表示正数,最高位是1,表示负数。这种说法本身没错,可是如果没有下文,那么它就是错的。至少它不能解释,为什么字符类型的-1用二进制表示是“1111 1111”(16进制为FF);而不是我们更能理解的“1000 0001”。(为什么说后者更好理解呢?因为既然说最高位是1时表示负数,那1000 0001不是正好是-1吗?)。

让我们从头说起。

1、你自已决定是否需要有正负。

就像我们必须决定某个量使用整数还是实数,使用多大的范围数一样,我们必须自已决定某个量是否需要正负。如果这个量不会有负值,那么我们可以定它为带正负的类型。

在计算机中,可以区分正负的类型,称为有符类型,无正负的类型(只有正值),称为无符类型。

数值类型分为整型或实型,其中整型又分为无符类型或有符类型,而实型则只有符类型。

字符类型也分为有符和无符类型。

比如有两个量,年龄和库存,我们可以定前者为无符的字符类型,后者定为有符的整数类型。

2、使用二制数中的最高位表示正负。

首先得知道最高位是哪一位?1个字节的类型,如字符类型,最高位是第7位,2个字节的数,最高位是第15位,4个字节的数,最高位是第31位。不同长度的数值类型,其最高位也就不同,但总是最左边的那位(如下示意)。字符类型固定是1个字节,所以最高位总是第7位。

(红色为最高位)

单字节数: 1111 1111

双字节数: 1111 1111 1111 1111

四字节数: 1111 1111 1111 1111 1111 1111 1111 1111

 

当我们指定一个数量是无符号类型时,那么其最高位的1或0,和其它位一样,用来表示该数的大小。

当我们指定一个数量是无符号类型时,此时,最高数称为“符号位”。为1时,表示该数为负值,为0时表示为正值。

 

3、无符号数和有符号数的范围区别。

无符号数中,所有的位都用于直接表示该值的大小。有符号数中最高位用于表示正负,所以,当为正值时,该数的最大值就会变小。我们举一个字节的数值对比:

无符号数: 1111 1111 值:255 1* 27 + 1* 26 + 1* 25 + 1* 24 + 1* 23 + 1* 22 + 1* 21 + 1* 20

有符号数: 0111 1111 值:127 1* 26 + 1* 25 + 1* 24 + 1* 23 + 1* 22 + 1* 21 + 1* 20

 

同样是一个字节,无符号数的最大值是255,而有符号数的最大值是127。原因是有符号数中的最高位被挪去表示符号了。并且,我们知道,最高位的权值也是最高的(对于1字节数来说是2的7次方=128),所以仅仅少于一位,最大值一下子减半。

不过,有符号数的长处是它可以表示负数。因此,虽然它的在最大值缩水了,却在负值的方向出现了伸展。我们仍一个字节的数值对比:

无符号数: 0 ----------------- 255

有符号数: -128 --------- 0 ---------- 127

 

同样是一个字节,无符号的最小值是 0 ,而有符号数的最小值是-128。所以二者能表达的不同的数值的个数都一样是256个。只不过前者表达的是0到255这256个数,后者表达的是-128到+127这256个数。

一个有符号的数据类型的最小值是如何计算出来的呢?

有符号的数据类型的最大值的计算方法完全和无符号一样,只不过它少了一个最高位(见第3点)。但在负值范围内,数值的计算方法不能直接使用1* 26 + 1* 25 的公式进行转换。在计算机中,负数除为最高位为1以外,还采用补码形式进行表达。所以在计算其值前,需要对补码进行还原。这些内容我们将在第六章中的二进制知识中统一学习。

这里,先直观地看一眼补码的形式:

以我们原有的数学经验,在10进制中:1 表示正1,而加上负号:-1 表示和1相对的负值。

那么,我们会很容易认为在2进制中(1个字节): 0000 0001 表示正1,则高位为1后:1000 0001应该表示-1。

然而,事实上计算机中的规定有些相反,请看下表:

二进制值(1字节)

十进制值

1000 0000

-128

1000 0001

-127

1000 0010

-126

1000 0011

-125

...

...

1111 1110

-2

1111 1111

-1

 

首先我们看到,从-1到-128,其二进制的最高位都是1(表中标为红色),正如我们前面的学。

然后我们有些奇怪地发现,1000 0000 并没有拿来表示 -0;而1000 0001也不是拿来直观地表示-1。事实上,-1 用1111 1111来表示。

怎么理解这个问题呢?先得问一句是-1大还是-128大?

当然是 -1 大。-1是最大的负整数。以此对应,计算机中无论是字符类型,或者是整数类型,也无论这个整数是几个字节。它都用全1来表示 -1。比如一个字节的数值中:1111 1111表示-1,那么,1111 1111 - 1 是什么呢?和现实中的计算结果完全一致。1111 1111 - 1 = 1111 1110,而1111 1110就是-2。这样一直减下去,当减到只剩最高位用于表示符号的1以外,其它低位全为0时,就是最小的负值了,在一字节中,最小的负值是1000 0000,也就是-128。

4).[/b]汇编中的确良有符号数和无符号数探讨[/b] [/b]
这个问题,要是简单的理解,是很容易的,不过要是考虑的深了,还真有些东西呢。
下面我就把这个东西尽量的扩展一点,深入一点和大家说说。

只有一个标准!

在汇编语言层面,声明变量的时候,没有 signed 和 unsignde 之分,汇编器统统,将你输入的整数字面量当作有符号数处理成补码存入到计算机中,只有这一个标准!汇编器不会区分有符号还是无符号然后用两个标准来处理,它统统当作有符号的!并且统统汇编成补码!也就是说,db -20 汇编后为:EC ,而 db 236 汇编后也为 EC 。这里有一个小问题,思考深入的朋友会发现,db 是分配一个字节,那么一个字节能表示的有符号整数范围是:-128 ~ +127 ,那么 db 236 超过了这一范围,怎么可以?是的,+236 的补码的确超出了一个字节的表示范围,那么拿两个字节(当然更多的字节更好了)是可以装下的,应为:00 EC,也就是说 +236的补码应该是00 EC,一个字节装不下,但是,别忘了“截断”这个概念,就是说最后汇编的结果被截断了,00 EC 是两个字节,被截断成 EC ,所以,这是个“美丽的错误”,为什么这么说?因为,当你把 236 当作无符号数时,它汇编后的结果正好也是 EC ,这下皆大欢喜了,虽然汇编器只用一个标准来处理,但是借用了“截断”这个美丽的错误后,得到的结果是符合两个标准的!也就是说,给你一个字节,你想输入有符号的数,比如 -20 那么汇编后的结果是符合有符号数的;如果你输入 236 那么你肯定当作无符号数来处理了(因为236不在一个字节能表示的有符号数的范围内啊),得到的结果是符合无符号数的。于是给大家一个错觉:汇编器有两套标准,会区分有符号和无符号,然后分别汇编。其实,你们被骗了。:-)

存在两套指令!

第一点说明汇编器只用一个方法把整数字面量汇编成真正的机器数。但并不是说计算机不区分有符号数和无符号数,相反,计算机对有符号和无符号数区分的十分清晰,因为计算机进行某些同样功能的处理时有两套指令作为后备,这就是分别为有符号和无符号数准备的。但是,这里要强调一点,一个数到底是有符号数还是无符号数,计算机并不知道,这是由你来决定的,当你认为你要处理的数是有符号的,那么你就用那一套处理有符号数的指令,当你认为你要处理的数是无符号的,那就用处理无符号数的那一套指令。加减法只有一套指令,因为这一套指令同时适用于有符号和无符号。下面这些指令:mul div movzx … 是处理无符号数的,而这些:imul idiv movsx … 是处理有符号的。
举例来说:
内存里有 一个字节x 为:0x EC ,一个字节 y 为:0x 02 。当把x,y当作有符号数来看时,x = -20 ,y = +2 。当作无符号数看时,x = 236 ,y = 2 。下面进行加运算,用 add 指令,得到的结果为:0x EE ,那么这个 0x EE 当作有符号数就是:-18 ,无符号数就是 238 。所以,add 一个指令可以适用有符号和无符号两种情况。(呵呵,其实为什么要补码啊,就是为了这个呗,:-))
乘法运算就不行了,必须用两套指令,有符号的情况下用imul 得到的结果是:0x FF D8 就是 -40 。无符号的情况下用 mul ,得到:0x 01 D8 就是 472 。(

n 如果在进行有符号数运算时发生溢出,那么运算的结果将不正确。

n 就上面的两个例子来说:
mov al,98
add al,99

n 197 1100 0101=128+64+4+1=197

n 32+16+8+2+1=

n 7 6 5 4 3 2 1 0

n 0 0 1 1 1 0 1 1

n 1 1 0 0 0 1 0 1

n

n

n add指令运算的结果是(al)=0C5H ,因为进行的是有符号数运算,所以 al中存储的是有符号数,而0C5H是有符号数-59的补码

1. Je的用法:

MOV AX,DATAS
MOV DS,AX
mov ax,1
mov bx,1
cmp ax,bx
je print

2. 为举例方便说一下JNZ和JZ
测试条件
JZ ZF=1
JNZ ZF=0
即JZ=JUMP IF ZERO (结果为0则设置ZF零标志为1,跳转)
JNZ=JUMP IF NOT ZERO

3. TEST属于逻辑运算指令

功能: 执行BIT与BIT之间的逻辑运算
测试(两操作数作与运算,仅修改标志位,不回送结果).
TEST对两个参数(目标,源)执行AND逻辑操作,并根据结果设置标志寄存器,结果本身不会保存。TEST AX,BX 与 AND AX,BX 命令有相同效果

语法: TEST R/M,R/M/DATA
影响标志: C,O,P,Z,S(其中C与O两个标志会被设为0)

运用举例:
1.TEST用来测试一个位,例如寄存器:

TEST EAX, 100B; B后缀意为二进制
JNZ ******; 如果EAX右数第三个位为1,JNZ将会跳转

是这样想的,JNZ跳转的条件是ZF=0,ZF=0意味着ZF(零标志)没被置位,即逻辑与结果

2.TEST的一个非常普遍的用法是用来测试一方寄存器是否为空

TEST ECX, ECX
JZ SOMEWHERE

如果ECX为零,设置ZF零标志为1,JZ跳转

4.neg求补码

0000 0001

1111 11110+1=1111 1111

5.sbb

n sbb是带错位减法指令,它利用了CF位上记录的借位值。

n 格式:sbb 操作对象1,操作对象2

n 功能:

操作对象1=操作对象1–操作对象2–CF

n 比如:sbb ax,bx

实现功能: (ax) = (ax) – (bx) – CF

6.adc带进位加法指令

n adc是带进位加法指令 ,它利用了CF位上记录的进位值。

n 格式: adc 操作对象1,操作对象2

n 功能:

操作对象1=操作对象1+操作对象2+CF

n 比如:adc ax,bx 实现的功能是:

(ax)=(ax)+(bx)+CF

n adc指令示例(一)

n mov ax,2
mov bx,1
sub bx,ax
adc ax,l

执行后,(ax)=4。

adc执行时,相当于计算: (ax)+1+CF=2+1+1=4。

n adc指令示例(三)

n mov al,98H
add al,al
adc al,3

执行后,(ax)=34H。

adc执行时,相当于计算: (ax)+3+CF=30H+3+1=34H。

n 可以看出,adc指令比add指令多加了一个CF位的值。

为什么要加上CF的值呢?

CPU为什么要提供这样一条指令呢?

n 在执行 adc 指令的时候加上的 CF 的值的含义,由 adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。

n 显然,如果CF 的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。

7.cmp指令

n cmp 是比较指令,功能相当于减法指令,只是不保存结果。

n cmp 指令执行后,将对标志寄存器产生影响。

其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果

n cmp指令

n 格式:cmp 操作对象1,操作对象2

n 功能:计算操作对象1–操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。

n 比如:cmp ax,ax

做(ax)–(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。

指令执行后:

ZF=1,

PF=1,

SF=0,

CF=0,

OF=0。

n 下面的指令:

mov ax,8

mov bx,3

cmp ax,bx

执行后: (ax)=8,

ZF=0,

PF=1,

SF=0,

CF=0,

OF=0。

n 其实,我们通过cmp 指令执行后,相关标志位的值就可以看出比较的结果。

n cmp ax,bx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: