8086 CPU内部的指令指针寄存器IP是计算机自动运行程序的关键。指令指针寄存器IP自动指向下一条将要执行的指令。
本节内容:使用指令指针寄存器读取和执行指令的工作原理和段寄存器的引用。
■指令指针: 8086CPU中的指令指针寄存器IP是16位寄存器。指令指针寄存器IP指向将要执行的指令在代码段中的偏移。只有使用控制指令才可以改变CS:IP的值,如JMP指令、JCC指令、CALL指令、RET指令、IRET指令等。
■读取、执行指令的工作原理:代码段内汇编指令的逻辑地址采用CS:IP表示,通过地址加法器将CS*16+IP转换为20位物理地址,然后在该物理内存地址处读取指令机器码。
■段寄存器的引用:正常引用段寄存器时,使用段超越前缀,比如“DS:[SI]”中的DS表示数据段超越前缀,表示引用数据段DS中的SI偏移地址处存储的值。默认缺省段超越前缀存在两种情形。一是数据段寄存器DS缺省,偏移地址可以使用[BX]、[SI]、[DI]或其组合形式表示。二是堆栈段寄存器SS缺省,偏移地址只可以使用[BP]表示,[BP]等价于SS:[BP]。
6.3.1 指令指针
当我们的操作系统将计算机可执行程序加载到计算机的内存中之后,IP指令指针寄存器自动指向该程序的入口地址(第一条指令的位置)。然后,按照程序中指令执行的流程和先后顺序,自动执行。
■指令编码规则
程序员在编写计算机的源程序时,按照计算机语言的指定编写规则编写源代码。编写程序时,通常按照数据和代码分开的原则编写。数据写在数据段,指令写在代码段。汇编源程序中编写的地址是逻辑地址。代码段的逻辑地址表示为CS:IP,由16位段值和16位偏移组成。当操作系统把编译后的可执行程序加载到计算机的内存中后,CPU读取的指令地址为源程序中编写的逻辑地址,CPU还需要将逻辑地址转换为物理地址后,才可以正确地读写物理内存中的指令或数据。
读取指令时,需要将指令的逻辑地址转换为物理地址:
8086物理地址=段值*16 +偏移。
代码段的逻辑地址: CS:IP。
代码段的物理地址 = CS*16 + IP。
下一条指令的地址=当前指令地址+当前指令机器码的字节数。
动手实验10:测试指令地址
如图6-12所示,在Debug调试器中输入命令a,输入下面三条语句,接着输入u命令,注意观察CS:IP的变化。
图6-12 指令地址
mov ax,1的硬编码为B80100,地址为073f:0100;
mov bx,2的硬编码为B80200,地址为073f:0103;
mov cx,3的硬编码为B90300,地址为073f:0106;
上述3条指令的硬编码都是3个字节,下一条指令的地址等于当前指令地址+3。
■指令指针
8086 CPU中的指令指针寄存器IP是16位寄存器。指令指针寄存器IP指向将要执行的指令在代码段中的偏移。实际上,除非发生转移,否则将要执行的指令会被预取到指令预取队列,然后再将一条指令分解为多条微指令,并将微指令提交CPU执行。关于指令队列和指令执行的具体流程,我们将在第三部分32位汇编的第二十八章80386处理器体系结构一章中详细讲解。
CS和IP是8086 CPU两个最关键的寄存器。任意时刻CPU都将CS:IP指向的内容当作指令执行。
6.3.2 读取、执行指令的工作原理
■读取执行指令
如图6-13所示:
●8086 CPU当前状态:CS中的内容为2000H,IP中的内容为0000H;
●内存20000H~20009H单元存放着可执行的机器码;
●内存20000H~20009H单元存放的机器码对应的汇编指令如下:
地址:20000H~20002H,内容:B8 21 03,长度:3byte,对应汇编指令:mov ax,0321h。
地址:20003H~20005H,内容:BB 06 00,长度:3byte,对应汇编指令:mov bx,0006h。
地址:20006H~20007H,内容:89 D8,长度:2byte,对应汇编指令:mov ax,bx。
地址:20008H~20009H,内容:01 DB,长度:2byte,对应汇编指令:add ax,bx。
假设当前CPU提取的代码段逻辑地址为2000H:0H,由CPU内的地址加法器将此逻辑地址转换为物理地址20000H。地址总线加载物理地址20000H,该物理地址存储汇编指令“mov ax,0321H”对应的机器语言(也称为硬编码)“B8 21 03”,这条机器语言就是将要执行的指令。
图6-13 读取、执行指令的工作原理
6.3.3 段寄存器的引用
在代码段的指令代码中,如何引用段寄存器呢?如图6-14所示,8086 CPU内有4个16位的段寄存器CS、DS、SS、ES,分别用于存储代码段、数据段、堆栈段和附加段的段值。这些段是由程序员在编写16位汇编源程序时人为定义的逻辑段。逻辑段的段值在源程序中并不是指定的,而是使用符号表示,称为该逻辑段的段地址标号。
图6-14 段寄存器的引用
堆栈段段值存储在SS段寄存器中,堆栈段使用SP栈顶指针寄存器指向堆栈段的栈顶,BP寄存器作为堆栈段内的基址寄存器。
代码段段值存储在CS段寄存器中,代码段使用IP指令指针寄存器指向将要执行的指令。
数据段段值存储在DS段寄存器中,数据段使用BX、SI、DI、BP四个指针寄存器指向段内偏移,或者使用直接段内地址偏移。在使用内存数据拷贝(字符串指令)时,固定使用DS:[SI]指向源地址。
附加段段值存储在ES段寄存器中,附加段通常用于数据拷贝,在字符串指令中固定使用ES:[DI]指向目的地址。
举例
假设在一个源程序中引用了4个逻辑段,需要在源程序的开始位置做如下声明:
assume cs:code,ds:data,ss:stack,es:edata
意思是假设“地址标号code”表示代码段,“地址标号data”表示数据段,“地址标号stack”表示堆栈段,“地址标号edata”表示附加段。
16位汇编语言源程序中段的定义如下:
assume cs:code,ds:data,ss:stack,es:edata
stack segment ;堆栈段定义的起始位置
定义堆栈空间的大小
stack ends ;堆栈段到此结束
data segment ;数据段定义的起始位置
数据段内的数据定义
data ends ;数据段到此结束
edata segment ;附加段定义的起始位置
附加段内的数据定义
data ends ;附加段到此结束
code segment ;代码段定义的起始位置
start: ;代码段内的地址标号
汇编指令代码…
code ends ;代码段到此结束
end start ;指定代码段的入口地址位于地址标号start处
在源程序中,根据实际需要定义一个或多个逻辑段,至少应包含一个代码段。
下面分别介绍4个段寄存器的引用方法。
■代码段
在汇编语言的源程序中,代码段内存储的是汇编指令代码。代码段的地址标号通常用“code”表示,也可以自定义。当我们编写16位汇编语言源程序时,并不需要真正给段寄存器赋值,只需要声明段地址标号就可以了。当操作系统将编译后的二进制可执行程序(.exe后缀的程序)加载到计算机内存中时,会根据当前计算机系统的配置,自动分配一个段值给段寄存器,替换源代码中的段地址标号。这样的好处是,不需要使用固定的段值,可以将同一个可执行程序加载到不同的计算机系统上运行。
当CPU通过CS:IP读取代码段内的指令时,会自动引用代码段寄存器CS,加上指令指针寄存器IP所给的16位代码段内偏移,得到指令的物理地址。
■堆栈段
堆栈段是一个动态分配的内存空间。段寄存器SS存储堆栈段的段值。当涉及堆栈操作时,引用堆栈段SS段寄存器,加上SP栈顶指针寄存器给出的16位堆栈段内偏移,得到堆栈操作的物理地址。
注意
1.编写16位汇编程序时,我们可以自定义堆栈段(小于64KB),也可以使用DOS系统默认分配的64KB堆栈空间。
2.SP栈顶指针寄存器永远指向堆栈段的栈顶。
3.堆栈内的数据存储按照后进先出的原则进行。
4.8086计算机的堆栈空间每次固定分配2个字节空间,即按照16位对齐,不足16位,前面补0。
5.入栈操作时SP寄存器减2,出栈操作时,SP寄存器加2。例如:
push ax ;将ax寄存器的值压入堆栈栈顶,SP-2
pop ax ;将堆栈栈顶2个字节的值pop 到ax寄存器中,SP+2
6.BP寄存器是栈内基址指针寄存器,用于堆栈内的寻址。如果是BP寄存器计算偏移时,缺省引用段寄存器,默认为SS段。例如,汇编语句mov ax,[bp+2]是mov ax,ss:[bp+2]的简写形式。
我们将在第七章8086寻址方式和第八章8086指令系统详细讲解堆栈内的寻址和堆栈操作指令。
■附加段
ES段寄存器存储附加段的段值。通常附加段用于数据拷贝。在16位汇编语言中,使用字符串指令时,ES:[DI]指向目的地址,目的数据字符串只可以使用ES段寄存器。我们将在第十七章字符串处理的章节中学习字符串指令。
注意
字符串指令LODS、STOS、MOVS、SCANS、CMPS用于字符串的装载、存储、拷贝、扫描和比对。在16位汇编语言中,字符串是以ASCII码字符的形式存储在计算机内存中。对内存中字符串的操作,就是对ASCII数值的操作。因而字符串指令本质上就是内存数据拷贝。所以,我们说ES段寄存器“用于数据拷贝”和“用于字符串指令”的说法是一回事。
■数据段
数据段的段值存储于DS段寄存器,用于存储程序所需的数据定义。16位汇编语言程序中,通常我们定义为data segment数据段。数据段作为全局段存在的,整个程序中的任一指令都可以直接引用。
源程序中的引用方法为DS:[SI],缺省DS段寄存器时,默认为DS段。如果一个程序不超过64KB,只需要在程序开始时分别给DS,SS赋值就可以了,程序的其他地方不需要再考虑这些段寄存器,如果程序超过64KB,就需要在两个或者多个段寄存器中存取数据,需要改变段寄存器的值。
段超越前缀:直接明确指定引用的段寄存器,可缺省。如DS:[SI]中,我们将“DS:”称为数据段超越前缀。
数据段和附加段的段内地址偏移可以使用SI、DI、BP、BX寄存器间接方式给出,或者直接给出段内偏移地址的数值,称为直接寻址方式。我们将在第七章8086寻址方式中详细讲解。
■段寄存器的引用规则
表6-8给出了16位汇编语言中段引用的规则。可以分为三种情形:
●正常使用段超越前缀
代码段必须使用CS段寄存器存储段值,源程序中使用CS:IP表示代码段内逻辑地址。
堆栈段必须使用SS段寄存器存储段值,源程序中使用SS:[BP] 或SS:[BP+立即数]表示堆栈段内逻辑地址。8086CPU指令集不支持SS:[SP]这样的指令。SP栈顶指针寄存器永远指向堆栈的栈顶。
附加段必须使用ES段寄存器存储段值,在字符串指令中,只能使用ES:[DI]表示附加段内的目的地址。
数据段使用DS段寄存器存储段值。在字符串指令中,只能使用DS:[SI]表示数据段内的目的地址。
●缺省段超越前缀只有两种情形
1.数据段寄存器DS缺省,偏移地址可以使用[BX]、[SI]、[DI]或其组合形式表示。
2.是堆栈段寄存器SS缺省,偏移地址只可以使用[BP]表示,[BP]等价于SS:[BP]。
●段值相同
DS数据段和SS堆栈段可以与CS、ES的段值相同,即都属于同一个段。
访问存储器涉及的方式 | 正常使用的段寄存器 | 可选用的段寄存器 | 偏移 |
取指令 | CS | 无 | IP |
堆栈操作 | SS | 无 | SP |
一般数据存取(下来情况除外) | DS | CS、ES、SS | 有效地址 |
源数据串 | DS | CS、ES、SS | SI |
目的数据串 | ES | 无 | DI |
BP作为指针寄存器使用 | SS | CS、DS、ES | 有效地址 |
表6-8 段寄存器的引用规定
练习
1、请举例说明何为段前缀超越。什么场合下要使用段前缀超越?
2、什么情况下,缺省的段寄存器是SS?为什么要这样安排?
3、如何实现数据段与代码段相同?
4、堆栈有哪些用途,请举例说明?
本文摘自编程达人系列教材《X86汇编语言基础教程》。