学习笔记 汇编语言(1.9-1.15)

本文介绍了8086CPU的寄存器结构,包括通用寄存器、字存储和汇编指令,如MOV、ADD、SUB。详细阐述了物理地址的计算,16位结构的CPU特性,以及8086CPU如何通过段地址和偏移地址形成物理地址。此外,讨论了代码段、数据段和栈的概念,以及如何使用DEBUG工具进行程序调试。还涉及了数据处理、转移指令和CALL、RET指令的工作原理。
摘要由CSDN通过智能技术生成

16位汇编语言

使用的是王爽的《汇编语言》。
限于时间关系,没有完全学完,学到书中的第十章。

第一章基础知识,感觉主要是一些常识性的内容,没有太多需要总结的,所以跳过了,以书中第二章作为总结的第一章。

一、寄存器

1.1 通用寄存器

8086CPU的所有寄存器都是16位的,可以存放两个字节,AX、BX、CX、DX通常用来存放一般性的数据,被称为通用寄存器。为保证与上一代的兼容,这四个通用寄存器都可以拆作两个可独立使用的8位寄存器来使用。

例如:
AX可分为AH和AL,
BX可分为BH和BL,
CX可分为CH和CL,
DX可分为DH和DL。
其中,高8位构成_H寄存器,低8位构成_L寄存器。

1.2 字在寄存器中的存储

8086CPU可以一次性处理以下两种尺寸的数据:

字节:byte,一个字节由八个bit组成,可以存在八位寄存器中。
 字 :word,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节。

一个字的高位字节和低位字节存储在寄存器的高8位和低8位寄存器中。

为了区分不同的进制,
可以在十六进制数字后加“H”,
在二进制数字后加“B”,
而十进制什么也不加,
例如:
20000(十进制)
4E20H(十六进制)
0100111000100000B(二进制)

1.3 几条汇编指令 & 2.4 mov、add、sub指令

2.4作为1.3的延伸,就放在一起总结了。

这两章主要介绍了mov、add、sub1指令,这几个指令都拥有两个操作对象。

mov指令,可以理解为赋值,形如 mov ax,bx ,其意义是将bx中的值送入寄存器ax中,用高级语言形容就是 ax=bx 。
mov指令有以下几种形式:

mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器
mov 寄存器,段寄存器
mov 段寄存器,寄存器
mov 内存单元,段寄存器
mov 段寄存器,内存单元

经过试验,我发现,除了不能直接将数据送入段寄存器中,也不能进行内存单元与其他内存单元的计算,还不能进行段寄存器与其他段寄存器之间的运算,其余的操作应该都是可行的。

add指令,同样有两个操作对象,形如 add ax,bx ,其意义是将bx的值加到ax上,用高级语言形容就是ax+=bx。
add指令有以下几种形式:

add 寄存器,数据
add 寄存器,寄存器
add 寄存器,内存单元
add 内存单元,寄存器

add指令不能对段寄存器进行操作,如果产生的进位超过了寄存器的存储范围,该进位则会丢失。

sub指令意义与add基本相似,只不过换加为减,如果在该存储器的最高位借位,也不会对上一个存储器产生影响。
形式与add相同,不多赘述了。

有几个需要注意的地方,
首先,在写汇编指令或一个寄存器的名字的时候,不区分大小写;
另外,在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的,如果不同会报错。

1.4 物理地址

每个内存单元,都在内存空间中有一个唯一的地址,我们称其为物理地址。
CPU通过地址总线送入存储器的,必须是这样的一个内存单元的物理地址。而要发出物理地址,就必须先在内部形成物理地址。对于这件事。不同的CPU可以有不同的方式,本书目前讨论的是8086CPU是如何在内部形成内存单元的物理地址的。

1.5 16位结构的CPU

16位结构的CPU有如下特点:

运算器一次最多可以处理16位的数据;
寄存器的最大宽度位16位’;
寄存器和运算器之间的通路为16位;

内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放,对于16位的CPU,能一次性处理、传输、暂时存储16位的数据。

1.6 8086CPU给出物理地址的方法

在8086CPU中,地址总线有20位,而其本身只有16位,为了弥补这样的差距,8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
其具体过程是:

CPU中的相关部件会提供两个16位地址,一个称为段地址,一个称为偏移地址。
两个地址通过地址加法器这个部件合成为一个20位的物理地址。

其具体的计算方法是,物理地址=段地址*16+偏移地址,例如段地址为1200H,偏移地址为1234H,那么可以合成为13234H这个物理地址。

因为段地址一般用16位显示,所以段地址*16也意味着段地址的十六进制形式左移一位,换算到二进制形式则是左移四位。
通过这个特点,可以得出:一个X进制的数据左移1位,相当于乘以X。

1.7 “段地址*16+偏移地址=物理地址”的本质含义

“段地址16+偏移地址=物理地址”的本质含义是:CPU在访问内存时,用一个基础地址(段地址16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。

1.8 段的概念

我们可以将若干地址连续的内存单元看成一个段,用段地址16定位段的起始地址,用偏移地址定位段中的内存单元。
因为段地址
16必然是16的倍数,所以一个段的起始地址也一定是16的倍数。
偏移地址为16位,16位的寻址能力为64KB,所以一个段的长度最大为64KB。
实际上,可以由不同的段地址和偏移地址形成同一个物理地址。

1.9 段寄存器

段寄存器用来存放段地址,8086CPU有4个段寄存器:CS、DS、SS、ES。

1.10 CS和IP

CS是代码段寄存器,IP为指令指针寄存器。
在8086PC机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内存M*16+N单元开始,读取一条指令并执行。
也可以这样表述:8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。

在内存中,指令和数据没有区别,都只是二进制信息,只有被CS:IP指向的才是指令。

1.11 修改CS、IP的指令

CS、IP不能用mov指令设置,它们有另外的指令,能够改变CS、IP的指令被统称为转移指令,在这章,主要介绍了jmp指令。
jmp指令有两种形式:
使用 jmp 段地址:偏移地址 的指令可以同时修改CS和IP。
使用 jmp 某一合法寄存器 的指令可以用该寄存器中的值修改IP。

1.12 代码段

我们可以将代码存在一组地址连续、起始地址为16的倍数的内存单元中,从而定义一个代码段。
通过将CS:IP改为这个代码段的首地址,使这个代码段得到执行。

实验一 查看CPU和内存,用机器指令和汇编指令编程

这章主要讲解了Debug的使用。

Debug是DOS、Windows都提供的实模式(8086方式)程序的调试工具。
使用它,可以查看CPU各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。

我用来学习的电脑系统是64位系统,貌似不支持命令行模式下进入debug模式,不过在谷歌之后找到了解决方案
目前学到的Debug功能有六种,分别是R、D、E、U、T、A命令。

R命令:可以用R命令来查看、改变CPU寄存器中的内容。
r 可以查看CPU中各个寄存器中的内容。
r+寄存器名称可以更改寄存器中内容,如r cs、r ip,中间的空格可以省略。

D命令:可以查看内存中的内容。
d 可以查看Debug预设的地址处的内容。
在我的电脑上,这个地址跟cs:ip的初始地址一致,但即使我把所有段寄存器修改,d命令显示的地址依然是初始的地址,这个“预设的地址”看来跟段寄存器无关。
d 段地址:偏移地址 可以查看此位置处的内容。
d 段地址:起始偏移地址 结尾偏移地址 可以查看从起始地址到结束地址的内容。
可以用段寄存器代替段地址,但不能用其他寄存器代替段地址,也不能用ip、sp等寄存器代替偏移地址。

E命令可以改写内存中的内容。
e 起始地址 数据 数据 数据 数据 ,以这样的形式可以直接改写内存中的内容。
也可以输入 e 起始数据 后直接回车,会以提问的形式改写内存中的内容。
用 ‘字符’ 的方式可以写入字符。
用 “字符串”的方式写入字符串。

U命令可以查看内存中机器码的含义。
u 可以查看预设地址的内容,和d一样。
u 段地址:偏移地址
u 段地址:起始偏移地址 结尾偏移地址
这些功能与D命令相似,U命令同样可以用段寄存器代替段地址,但不能用其他寄存器代替段地址,也不能用ip、sp等寄存器代替偏移地址。

T命令可以执行CS:IP指向的代码。
每次执行后,IP会增加。

A命令可以以汇编语言的形式在内存中写入机器指令。
a 段地址:偏移地址 可以在指定位置写入。
a 可以在预设位置写入。

要注意的是,所有地址、数据都是以16位的形式输入的,并且结尾不加H。

二、寄存器(内存访问)

2.1 内存中字的存储

在CPU中,一个字要用两个地址连续的内存单元来存放,低位字节存放在低地址单元,高位字节存放在高地址单元。
字单元:存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。
N地址字单元:指起始地址为N的字单元。

2.2 DS和[address]

可以用 [偏移地址] 的方式来方便的表达一个内存单元的地址。
这个内存单元的段地址储存在DS段寄存器中,和所有段寄存器一样,不能直接将数据送入DS寄存器,需要一个寄存器进行中转。

2.3 字的传送

8086CPU可以一次性传送16位的数据,在mov指令中给出16位寄存器就可以进行字的传送。

2.4 mov、add、sub指令

已经在1.3总结过,不重复了。

2.5 数据段

和代码段一样,我们可以将数据存在一组地址连续、起始地址为16的倍数的内存单元中,从而定义一个数据段。
用DS来存储数据段的段地址,用相关指令来访问。

2.6 栈

栈是一种具有特殊的访问方式的存储空间,遵循LIFO(Last In First Out 后进先出)规则。

2.7 CPU提供的栈机制

这一章主要介绍了入栈push、出栈pop指令,与SS、SP两个寄存器用来指向栈顶元素。
push 寄存器:将一个寄存器中的数据入栈。
实际上,该指令的执行分为两步:

  1. SP=SP-2,SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶。
  2. 将寄存器中的内容送入SS:SP指向的内存单元处,SS:SP指向新栈顶。

pop 寄存器:出栈,用一个寄存器接收出栈的数据。
pop执行的过程与push相反:

  1. 将SS:SP指向的内存单元处的数据送入寄存器中。
  2. SP=SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

这里的寄存器也可以换成段寄存器或者是内存单元
要注意的是,在执行过pop指令后,被出栈的数据原本位置的数据并没有消失,等到下一次push等入栈指令后,才会被覆盖。

SS和SP:任意时刻SS:SP指向栈顶元素,栈顶的段地址放在SS中,偏移地址放在SP中。

2.8 栈顶超界的问题

实际上,8086CPU并不会保证栈顶不会出界,它只知道SS:SP(栈顶的位置)而不知道我们安排的栈空间有多大,所以在编程的时候要注意这一点。

2.9 push、pop指令

2.7中提过了,略过。

2.10 栈段

和代码段一样,我们可以将一组地址连续、起始地址为16的倍数的内存单元定义为栈段,从而以栈的形式访问。

实验2 用机器指令和汇编指令编程

段寄存器替代段地址的部分,在实验1中提到过了,不再重复。

Debug的T命令在执行修改寄存器SS的指令时,下一条指令也紧接着执行。

关于实验任务的(2),在我的电脑上没有得到相同的结论,我猜是64位系统的原因,先不管了。

三、第一个程序

3.1 一个源程序从写出到执行的过程

第一步,编写汇编源程序。
第二步,对源程序进行编译连接。

3.2 源程序

汇编指令与伪指令:
汇编指令有对应的机器码,可以被编译为机器指令,最终被CPU执行。
伪指令没有对应的机器码,由编译器来执行。
这一小节介绍了几个基本的伪指令,还有一些基本概念。

XXX segmentXXX ends:
这两个指令成对使用,可以定义一个段,segment说明一个段开始,ends说明一个段结束,一个段必须有一个名称来标识。
一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。

end:
他是汇编程序的结束标记,编译器在编译过程中碰到它就会结束对源程序的编译,在写完程序后必须加上这个指令,否则编译器不知道程序在何处结束。
要注意不要混淆end指令和ends指令。

assume:
这条指令假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联。

源程序文件中的所有内容可以被称为源程序,源程序中最终由计算机执行、处理的指令或数据称为程序。

标号:
一个标号指代了一个地址,例如segment和ends前面的XXX,作为一个段的名称,它最终将会被编译、连接程序处理位一个段的段地址。

程序返回:
一个程序P1在可执行文件中,必须有另一个正在运行的程序P2,将P1从可执行文件加载入内存后,将CPU的控制权交给P1,P1才得以运行,当P1运行完毕后,又需要将CPU的控制权交还给P2,这个交还的过程,我们称其为程序返回。
在这一小节,我们知道了,只要在程序末尾使用:

mov ax,4c00H
int 21H

这两条指令就可以实现程序返回,但书中暂时没有给出原因,留待以后学习。

程序在结束时不返回,不会被编译器认为是错误,所以要更加注意这一点。

3.3 编辑源程序

用任意的文本编辑器编辑源程序,最后需将其存储为纯文本文件。
在编译的时候要注意,源程序的名字不能超过八位,拓展名不能超过三位。另外,masm的默认拓展名是asm。

3.4-7 编译、连接以及其简化方式,还有执行

这部分内容感觉书里的很详细,没什么可精简的,感觉没什么必要做总结了(
只记一下简化方式。

编译: 直接输入 masm 文件名;,可以在当前路径下生成目标文件,并且忽略中间文件的生成。
连接: 直接输入 link 文件名;,可以在当前路径下生成目标文件,并且忽略中间文件的生成。

要注意结尾的分号。
另外如果不是相同路径下,要把路径写全,而不能只写文件名。

如果没有向显示屏上输出信息,程序就看不到结果,输出的方法在几章以后。

3.8 谁将可执行文件中的程序装载进入内存并使它运行?

答案是command.com捏,在DOS中,command处理各种输入:命令或要执行的程序的文件名。我们就是通过command来进行工作的。
它就承担了3.2中的程序P2的作用,
可以看一下它对于一个程序执行的过程:

  1. 正在运行的command将程序加载入内存。
  2. command设置CPU的CS:IP指向程序的第一条指令,从而使程序得到运行。
  3. 程序运行结束后,返回到command中,CPU继续运行command。

3.9 程序执行过程的跟踪

可以用Debug来跟踪一个程序的运行过程。
用command加载程序时,command会放弃CPU的控制权,
在使用Debug时,虽然也会把程序加载入内存运行,但并不会放弃CPU的控制权,所以可以用Debug的相关命令单步执行程序,查看每一条指令的执行结果。

在提示符后输入 debug 文件名 (要加后缀!),按enter后,程序加载入内存,进行相关初始化后设置CS:IP指向程序的入口。

DS储存了程序所用内存区的段地址,但由于在这个区域开头需要有一个名为PSP的数据区,DOS用它来和程序通信,它占据了256个字节,所以实际CS并不指向这段内存区的开头,和DS并不相同,而是(CS)=(DS)+10H。

可以用T命令单步执行程序中的每一条指令,但int 21这条指令要用P命令执行。
可以用Q命令退出Debug,退出Debug将会返回到command中,因为是command加载了Debug,然后Debug再加载了我们要执行的程序。

四、[BX]和loop指令

本章定义了一个符号 (),用来描述里面所写的寄存器或内存单元的内容,例如(AX)或(21000H)。
约定了一个符号idata表示常量。
这一章编写程序的时候,书里反复提到,要先考虑运算后的结果会不会超出存储器的范围,所以应该着重注意这一点。

4.1 [BX]

[bx] 和 [address] 类似,只不过address这个偏移地址储存在寄存器BX中,用来表示一个内存单元。

4.2 Loop指令

loop指令的格式是,
loop标号,CPU执行到loop指令的时候,先(1)执行(cx)=(cx)-1;(2)若(cx)不为0,则跳转到标号处(前面提到标号表示一个地址)。
cx中存放的是循环的执行次数。

4.3 在Debug中跟踪用loop指令实现的循环程序

一个要注意的点:在汇编源程序中,数据不能以字母开头

可以使用G命令,来跳过不需要调试的部分。
g 偏移地址 可以让程序直接执行到当前代码段(CS)的偏移地址处。

在遇到循环次数较大的loop指令时,可以使用P命令,来自动重复执行loop指令。
也可以用g命令跳转到loop指令后,实现同样的效果。

4.4 Debug和汇编编译器masm对指令的不同处理

虽然在Debug中,可以用[idata]来表示一个内存单元,但在编译器中,它会被当成idata这个数据来处理。
解决方法是,将idata送入bx中,使用[bx]即可。
也可以采用这样的形式:mov ax,ds:[0]

4.5 loop和[bx]的联合应用

如果需要对地址连续的内存单元中的数据进行操作,就可以联合应用loop和[bx]了。
这里还用到了inc指令,作用类似于++运算符。

具体方法是,在循环外先初始化bx,然后在每次循环中inc bx,以达到每次循环bx增加的效果。

4.6 段前缀

可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。

例如
mov ax,ds:[bx]
mov ax,ss:[0]
等。

这些出现在访问内存单元的指令中,用于显式地给出内存单元的段地址的 “ds:” “cs:” “ss:” “es:”,在汇编语言中被称为段前缀

4.7 一段安全的空间

随意往一段内存空间写入内容很危险,有可能更改重要的系统数据或代码。
书中提到,0:200~0:2ff这段空间中没有系统或其他程序的数据或代码,所以建议使用这段空间。

4.8 段前缀的使用

在需要访问相距大于64kb的内存单元的时候,段前缀就可以派上用场了。
访问内存单元的指令默认读取的段寄存器是ds,可以把要访问的另一个段地址放在其他寄存器中,再显式地访问。

实验四 [BX]和loop的使用

刚学完段前缀,把所有的逗号都当冒号写了,以后要多注意。
除了这个没什么别的问题。

五、包含多个段的程序

前面记太细了,后面少写点,毕竟是笔记不是抄书嘛。

  1. 受限于段的大小(64kb),对于稍大些的程序,我们不可能把程序的所有部分都写在代码段,所以需要分成多个段。
    定义其他段的方法和定义代码段一样,也使用segment和ends指令。
    在文件开头用assume指令将段和它们对应的段寄存器联系起来。

  2. 那么如何让CPU以需要的方式访问它们呢?
    实际上,所谓的“代码段” “数据段” “栈段”完全是我们的安排,虽然我们用assume指令将它们联系了起来,但assume是伪指令,CPU并不知道,所以我们需要在代码段中设定ss、sp、ds来规定栈段和数据段的位置。

  3. 我们又怎么知道我们规定的其他段被CPU分配的位置呢?
    前面有提到过,这些段的标号,实际上就是这些段的段地址,可以直接用标号作为数据传入寄存器,不过要注意,由于不能直接将数据传入段寄存器,所以需要中转。

六、更灵活的定位内存地址的方法

  1. and指令: 逻辑与,例如:

    mvv al,01100011B
    and al,00111011B
    执行后,al=00111011B。

  2. or指令: 逻辑或,例如:

    mov al,01100011B
    or al,00111011B
    执行后,al=01111011B

  3. 可以用 ‘…’ 的方式指明数据是以字符的形式给出的,编译器将把它们转化为相对应的ASCII码
    例如 mov al,‘a’

  4. 可以用 [bx+idata] 的方式表示一个内存单元。

  5. 除了bx,寄存器si、di也可以用来表示内存单元,只不过它们不能分成两个8位寄存器使用。

  6. 可以用 [bx+si] [bx+di] 的方式表示一个内存单元。

  7. 可以用 [bx+si+idata] [bx+di+idata] 的方式表示一个内存单元。

  8. 在程序的运行过程中,我们经常需要暂存某些数据,在之前的章节里,都是用寄存器来完成这项工作的,但寄存器数量有限,所以我们可以把这些数据存到内存中。一般来说,在需要暂存数据的时候,我们都应该使用栈。

  9. xor指令: 书里这部分还没说,但看到作业里有,就查了一下,其实就是按位异或,跟and和or指令差不多。

     总结:
     1. [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
     2. [bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
     3. [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
     4. [bx+si]用两个变量表示地址;
     5. [bx+si+idata]用两个变量和一个常量表示地址。
    

七、数据处理的两个问题

这一章主要起一个总结作用,有些重复的,前面写过的就不记了。

  1. bp寄存器: 和bx类似,在使用它的时候,默认的段地址储存在ss中。

  2. [bx+bp][si+di] 这样的指令是错误的。

  3. 指令在执行前,所要处理的数据可以在:CPU内部、内存、端口。

  4. word ptr 指令byte ptr 指令可以用来指明需要访问的内存单元的长度,例如:

     mov word  ptr ds:[0],1
     inc word ptr [bx]
     inc byte ptr ds:[0]
     add byte ptr [bx],2
    

    这些指令一般用在没有寄存器参与的内存单元访问指令中。
    push和pop指令默认访问字单元,所以不需要这些指令。

  5. div指令:
    div指令的格式如下:

     div reg	
     或
     div 内存单元
    

    对于不同位数的除数,被除数和结果的存储位置不同。

除数被除数余数
8位16位,在ax中存放al存储商,ah存储余数
16位32位,dx存放高16位,ax存放低16位ax存储商,dx存储余数
  1. 伪指令dd:
    和db,dw类似,dd可以用来定义双字型数据。

  2. dup:
    配合dw、db、dd等伪指令使用,可以实现数据的重复。
    例如:

     db 3 dup (0)
     定义了3个字节,它们的值都是0,相当于db 0,0,0。
     db 3 dup (0,1,2)
     定义了9个字节,它们是0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2。
    

八、转移指令的原理

  1. 转移行为的分类:
    可以按只 修改ip同时修改ip与cs 来分为 段内转移段间转移
    段内转移又可以按ip的修改范围:-128~127-32768~32767来分为 短转移近转移

  2. 操作符offset: 它可以取得标号的偏移地址,例如:mov ax,offset start。

  3. **jmp指令:**无条件转移指令,可以只修改ip,也可以同时修改cs和ip。
    jmp指令要给出转移的目的地址,和转移的距离(段间转移、段内短转移、段内近转移),所以有不同的格式。
    这里介绍的格式有:

     jmp short 标号
     它对ip的修改范围为-128~127(八位位移),实现的是段内短转移。
     
     jmp near ptr 标号
     它对ip的修改范围为-32768~32767(十六位位移),实现的是段内近转移。
    
     jmp far ptr 标号
     它可以同时修改cs和ip,实现的是段间转移。
    

    如果转移地址在寄存器中,那么格式为:

     jmp 16位reg
    

    如果转移地址在内存中,那么格式有两种,分别为:

     jmp word ptr 内存单元地址(段内转移)
     jmp dward ptr 内存单元地址(段间转移)
     第二种方式,(cs)=(内存单元地址+2),(ip)=(内存单元地址)
    
  4. **jcxz指令:**有条件转移指令,所有的有条件转移指令都是短转移。
    格式为:

     jcxz 标号
     如果(cx)=0,则转移到标号处执行。
    
  5. 如果转移位移超界,会报错,所以尽量选择合适的转移类型。

九、CALL和RET指令

  1. call指令: 一个转移指令,在执行时,进行以下两步操作:

     1. 将当前的 ip 或 cs和ip 压入栈中
     2. 转移
    

    格式为:

     call 标号
     此时将ip入栈,并实现段内转移。
     
     call far ptr 标号
     此时先后将cs、ip入栈,实现段间转移。
    

    如果转移地址在寄存器中,那么格式为:

     call 16位reg
    

    如果转移地址在内存中,那么格式为:

     call word ptr 内存单元地址
     call dword ptr 内存单元地址
    
  2. ret和retf指令: 转移指令,它们会利用栈中的内容进行转移。
    CPU执行以下两种指令对应的汇编语言:

     CPU执行ret指令时,相当于执行:
     pop IP
     
     CPU执行retf指令时,相当于执行:
     pop IP
     pop CS
    
  3. 组合使用call和ret就可以实现子程序(函数?)功能了,先用call将地址入栈并转移到子程序,执行完需要的程序段后,再用ret出栈转移回来。

  4. 因为子程序中使用的寄存器可能会和主程序冲突,所以在子程序开始时先把子程序需要用到的寄存器入栈,结束时再把它们出栈,这样就可以避免子程序与主程序互相影响了。
    书中给出了一套标准框架:

     子程序开始:子程序中使用的寄存器入栈
     		    子程序内容
     		    子程序中使用的寄存器出栈
     		    返回(ret、retf)
    

  1. yysy,翻了半天也没在前面的内容里看见sub指令,貌似没有讲,但2.4说讲了那就讲了吧。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值