|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 兰陵月 于 2017-12-5 21:56 编辑
第6章 相同的功能,不同的代码
6.1 代码清单6-1
6.2 跳过非指令的数据区
用一个跳转指令跳过安排在程序前段的数据区。
jmp near star【star是一个标号】
6.3 在数据声明中使用字面值
为了显示一个字符串,便将每个字符的ASCII码包含在每条指令中,单个顺序去显示,这种方法很原始,也不利于修改显示的内容。因此,我们可以采取专门定义一个存放字符串的数据区,当要显示他们的时候,再用指令取出来,一个一个地传送到显示缓冲区。这样一来,负责在屏幕上显示的指令就和要显示的内容无关了。
“\”是续行符,当一行写不下时,可以在行尾使用这个符号,以表明下一行与当前行应该合并为一行。
6.4 段地址的初始化
汇编语言源程序的编译符合一种假设,即编译后的代码将从某个内存段中,偏移地址为0的地方开始加载。但是,如果程序加载时,不是从段内偏移地址为0的地方开始的,而是从0x7c00,那么,段内的某个要引用的标号处的实际偏移地址就是0c7c05。
在主引导程序中,访问内存的指令很多,如果都要加上0x7c00无疑是很麻烦的,其实,产生问题的根源,就是因为程序在加载时,没有段内偏移地址为0的地方开始。
其实逻辑地址和0x0000:0x7c00对应的物理地址是0x07c00,该地址又是段0x07c0的起始地址。因此,这个物理地址其实还对应着另一个逻辑地址0x07c0:0x0000。
尽管BIOS将主引导扇区加载到物理地址0x07c00处,但我们却可以认为它是从0x07c0:0x0000处开始加载的。
6.5 段之间的批量数据传送
批量传送:movsb或movsw
movsb和movsw通常用于把数据从内存中的一个地方批量地传送(复制)到另一个地方,处理器把它们看成是数据串。movsb的传送是以字节为单位,movsw的传送是以字为单位。
movsb和movsw指令执行时,原始数据串的段地址由DS指定,偏移地址由SI指定,简写为DS:SI;要传送到的目的地址由ES:DI指定;传送的字节数(movsb)或者字数(movsw)由CX指定。除此之外,还要指定是正向传送还是反向传送,正向传送是指传送操作的方向是从内存区域的低地址端到高地址端;反向传送则正好相反。正向传送时,没传送一个字节(movsb)或者一个字(movsw),SI和DI加1或者加2;反向传送时,每传送一个字节(movsb)或者一个字(movsw)时,SI和DI减去1或者减去2.不管是正向传送还是反向传送,也不管每次传送的是字节还是字,每传送一次,CX的内容自动减一。
6.6 使用循环分解位数
loop指令的功能是重复执行一段相同的代码,处理器在执行它的时候会顺序做两件事情:
⑴将寄存器CX的内容减一;⑵如果CX的内容不为零,转移到指定的位置处执行,否则顺序执行后面的指令。
loop 标号和jmp near 标号一样,也是具有迷惑性的指令,它的机器指令操作码是0xE2,后面跟着一个字节的操作数,而且也是相对于标号处的偏移量,是在编译阶段,编译器用标号所在位置处的汇编地址减去loop指令的汇编地址,再减去loop指令的长度(2)来得到的。
在8086处理器上,如果要用寄存器来提供偏移地址,只能使用BX、SI、DI、BP,不能使用其他寄存器。原因很简单,寄存器BX最初的功能之一就是用来提供数据访问的基地址,所以又叫基址寄存器(Base Address Register),之所以不能用SP、IP、AX、CX、DX,这是一种硬性规定,说不上什么特别的理由。而且,在设计8086处理器时,每个寄存器都有自己的特殊用途,比如AX是累加器(Accumulator),与它有关的指令还会做指令长度上的优化(较短);CX是计数器(Counter);DX是数据(Data)寄存器;除了作为通用寄存器外,还专门用于和外设之间进行数据传送;SI是源索引寄存器(Source Index);DI是目标索引寄存器(Destination Index),用于数据传送操作,我们已经在movsb和movsw指令的用法中领略过了。
注意,可以在任何带有内存操作数的指令中BX、SI或者DI提供偏移地址。
inc是加一指令,操作数可以是8位或者16位的寄存器,也可以是字节或者字内存单元。从功能上来将,它和add bx,1一样,但前者的机器码更短,速度更快。
dec是减一指令,格式和inc相同。
【很明显,在指令的地址部分使用寄存器,而不是数值或者标号(其实标号是数值的等价形式,在编译后也是数值)有一个明显的好处,那就是可以在循环体里方便地改变偏移地址,如果是使用数值就不能做到这一点。】
6.7 计算机中的负数
【6.7.1 无符号数和有符号数】
负数相当于0减去该数的绝对值,当然这种计算不是十进制的减法,而是二进制的减法。然后根据保存的寄存器或者地址的长度取右边(比如寄存器或者数据的长度是8位或16位,则相应地取右边的8位或者16位,然后变成源程序中的我们可见的十六进制数据)。
要永远记住,计算中的计算都是基于二进制的基础上的,我们平时看到的十进制、十六进制都是为了便于我们的观看和显示,而在计算机内部,都是二进制的,所以我们都要以二进制这个本质来看待计算机中的所有数据。
计算机中的数被分为两大类:无符号数和有符号数。
无符号数:
8位无符号数的范围是00000000~11111111,即十进制的0~255。
16位无符号数的范围是0000000000000000~1111111111111111,即十进制的0~65535。
同理,32位无符号数的范围是十进制的0~4,294,967,295。
有符号数:有符号数是分正、负的,而且规定,数的正负要通过它的最高位来辨别。如果最高位是0,它就是正数;如果是1,就是负数。
8位有符号数的范围是10000000~01111111,即十进制的-128~127。
16位有符号数的范围是十进制的-32768~32767。
32位有符号数的范围是十进制的-2,147,483,648~2,147,483,647。
正的有符号数和与它同值的无符号数相同。负数的有符号数相对应的正数就是0减去这个负数。
neg指令:neg指令带有一个操作数,可以是8位或者16位的寄存器,或者内存单元。功能是0减去指令中制定的操作数。
一个8位的有符号数,要想用16位的形式来表示,只需要将其最高位,也就是用来辨别符号的那一位(几乎所有的书上都称之为符号位,实际上这并不严谨),扩展到到高8位即可。为了方便,处理器专门设计了两条指令来做这件事:cbw(Convert Byte to Word)和cwd(Convert Word to Duble-Word)。
cbw没有操作数,操作码为98。功能是将寄存器AL中的有符号数扩展到整个AX。cwd也没有操作数,操作码为99。功能是将寄存器AX中的有符号数扩展到DX:AX。
尽管有符号数的最高位通常为符号位,但并不意味着它仅仅用来表示正负号。事实上,通过上面的讲述和实例可以看出,它既是数的一部分,和其他比特一起共同表示数的大小,同时又用来判断数的正负。
【6.7.2 处理器视角中的数据类型】
无符号数和有符号数的划分并没有从根本上打消我们的疑虑,比如一个最高位为1的数,它到底是一个负数还是一个无符号数呢?答案是,这是你自己的事情,取决于你怎么看待它。对于处理器的多数指令来说,执行的结果和操作数的类型没有关系。换句话说,无论你是从无符号数的角度来看,还是从有符号数的角度来看,指令的执行结果都是正确无误的。
sub减法指令,目的操作数可以是8位或者16位通用寄存器,也可以是8位或者16位的内存单元;源操作数可以是通用寄存器,也可以是内存单元或者立即数(不允许两个操作数同时为内存单元)。
几乎所有的处理器指令既能操作无符号数,又能操作有符号数。但是,有几条指令除外,比如除法指令和乘法指令。
div严格来说是无符号除法指令,因为这条这令只能工作于无符号数。换句话说,只有从无符号数的角度来解释它的执行结果才能说得通。
idiv指令,有符号除法指令,做8位除法如有溢出,则应采取32位除法,但是dx的内容进行扩展时,不应该扩展为全部是0,而应该用cwd指令将最高位1扩展到dx,这样结果才能保证正确。
6.8 数位的显示
索引寄存器或者变址寄存器:SI、DI。
INTEL8086处理器只允许以下几种基址寄存器和变址寄存器的组合:
[bx+si]、[bx+di]、[bp+si]、[bp+di]
上面的这些组合可以用于任何带有内存操作数的指令中。
intel处理器的标志寄存器里有符号位SF(Sign Flag),很多算术逻辑运算都会影响到该位,如果计算结果的最高位是比特“0”,处理器把SF位置“0”,否则SF位置“1”。
jns是条件转移指令,处理器在执行它的时候要参考标志寄存器的SF位。除了只是在符合条件的时候才转移外,它和jmp指令很相似,它也是相对转移指令,编译后的机器指令操作数也是一个相对偏移量,是用标号处的汇编地址减去当前指令的汇编地址,再减去当前指令的长度得到的。
6.9 其他标志位和条件转移指令
【6.9.1 奇偶标志位PF】
如果运算结果中,最低8位中,有偶数个为1的比特,则PF=1;否则PF=0。
【6.9.2 进位标志位CF】
当处理器进行算术操作时,如果最高位有向前进位或借位的情况发生,则CF=1;否则CF=0。
CF标志始终忠实地记录进位或者借位是否发生,但少数指令除外(如inc和dec)。
【6.9.3 溢出标志OF】
在所有的情况下,计算机并不知道你进行的是无符号数运算,还是有符号数运算。为此,它提供了这个标志。该标志的意思是,假定你进行的是有符号数运算,如果结果超出了目标操作数所能容纳的范围,OF=1;否则,OF=0。至于程序员如何处理这个结果,那是程序员的事情。
换句话说,在有符号数的运算当中,溢出就意味着一个错误的计算结果。
【6.9.4 现有指令对标志位的影响】
add OF、SF、ZF、AF、CF和PF的状态依计算结果而定。
cbw 不影响任何标志位。
cld DF=0,CF、OF、ZF、SF、AF和PF未定义。未定义的意思是到目前为止还不打算让该指令影响到这些标志,因此,不要在程序中依赖这些标志。
cwd 不影响任何标志位。
dec CF标志不受影响,因为该指令通常在程序中用于循环计数,而且在循环体内通常有依赖CF标志的指令,故不希望它打扰CF标志;对OF、SF、ZF、AF和PF的影响依计算结果而定。
div/idiv 对CF、OF、SF、ZF、AF和PF的影响未定义。
inc CF标志不受影响,对OF、SF、ZF、AF和PF的影响依计算结果而定。
mov/movs 这类指令不影响任何标志位
neg 如果操作数为0,则CF=0,否则CF=1;对OF、SF、ZF、AF和PF的影响依计算结果而定。
std DF=1,不影响其他标志位。
sub 对OF、SF、ZF、AF、PF和CF的影响依计算结果而定。
xor OF=0,CF=0;对SF、ZF和PF依计算结果而定;对AF的影响未定义。
【6.9.5 条件转移指令】
转移指令必须出现在影响标志的指令之后。
cmp比较指令,它需要两个操作数,目的操作数可以是8位或者16位通用寄存器,也可以是8位或者16位内存单元;源操作数可以是与目的操作数宽度一致的通用寄存器、内存单元或者立即数,但两个操作数同时为内存单元的情况除外。cmp指令功能上和sub指令相同,但是cmp指令仅根据计算结果设置相应的标志位,而不保留计算结果,因此也就不会改变两个操作数的原有内容。
cmp指令会影响到CF、OF、SF、ZF、AF和PF标志位。
各种比较结果和相应的条件转移指令:
je、jne、jg、jge、jng、jnge、jl、jle、jnl、jnle、ja、jae、jna、jnae、jb、jbe、jnb、jnbe、jpe、jpo。
JCXZ,当CX寄存器的内容为0时则转移。执行这条指令时,处理器先测试寄存器CX是否为零。
6.10 NASM编译器的$和$$标记
jmp near $
times 510-($-$$) db 0
db 0x55,0xaa
上面的语句实际上是一个无限循环。NASM编译器提供了一个标记“$”,该标记等同于标号,你可以把它看成是一个隐藏在当前行行首的标号。因此,该语句的意思是,转移到当前指令继续执行。
$$是NASM编译器提供的另一个标记,代表当前汇编节(段)的起始汇编地址。当前程序没有定义节或段,就默认地自成一个汇编段,而且起始的汇编地址是0(程序起始处)。
这样,用当前汇编地址减去程序开头的汇编地址(0),就是程序实体的大小。再用510减去程序实体的大小,就是需要填充的字节数。
|
|