汇编语言

文章目录

  • 文中知识点主要来自王爽 著<汇编语言 第3版>
  • 汇编语言
    • 操作
    • 机器指令
    • 汇编指令
    • 寄存器
    • 指令和数据
    • 存储单元
    • CPU对存储器的读写
      • 总线
        • 地址总线
        • 控制总线
        • 数据总线
        • 检测点
    • 内存空间地址
  • 寄存器
    • 通用寄存器
        • 字在寄存器中的存储
        • 几条汇编指令
        • 检测点
      • 物理地址
        • 16位结构的CPU
        • 8086CPU给出物理地址的方法
        • “段地址x16+偏移地址=物理地址”的本质含义
        • 段的概念
        • 检测点
      • 段寄存器
        • CS 和 IP
      • 修改CS、IP的指令
      • 代码段
        • 检测点2.3
  • 寄存器(内存访问)
    • 内存中字的存储
    • DS 和[address]
    • 字的传送
    • mov,add,sub指令
    • 数据段
    • 检测点3.1
    • CPU提供的栈机制
      • 栈顶超界的问题
        • push , pop 指令
      • 栈段
        • 检测点3.2
  • 第一个程序
    • 一个源程序从写出到执行的过程
  • [BX]和loop指令
    • 1.[bx]和内存单元的描述
    • 2.loop
    • 3.定义的描述性的符号:"()”
    • 4.约定符号idata表示常量
    • 5.1[BX]
    • 5.2 Loop 指令
    • loop和[bx]的联合应用
    • 段前缀
    • 一段安全的空间
  • 更灵活的定位内存地址的方法
    • and 和 or 指令
    • 关于ASCII码

文中知识点主要来自王爽 著<汇编语言 第3版>

汇编语言

  • 汇编语言的主体是汇编指令

操作

  • 寄存器bx的内容送到ax中

机器指令

  • 1000100111011000

汇编指令

  • 汇编语言的核心是汇编指令,它决定了汇编语言的特性.
  • 形式:mov ax,bx
  • 指令组成:

1.汇编指令:机器码的助记符,有一一对应的机器码
2.伪指令:没有对应的机器码,有编译器执行,计算机不执行
3.其他符号:如+,-,*,/等,由编译器识别,没有的对应的机器码

寄存器

  • 寄存器:CPU中可以存储数据的器件,一个CPU中有多个寄存器.
  • ax,bx为寄存器的代号
    汇编语言_第1张图片

指令和数据

  • 在内存或者磁盘中,指令和数据没有任何区别,都是二进制信息

如 内存中的二进制信息 1000100111011000 计算机可以看作大小为89D8H的数据,也可以看作指令mov ax,bx
1000100111011000 -> 89D8H
1000100111011000 -> mov ax,bx

存储单元

  • 存储器被划分成若干个存储单元

如一个存储器有128个存储单元
编号:0~127
汇编语言_第2张图片

  • 计算机的存储器容量以字节为最小单位
  • 一个存储单元 ->一个字节(Byte) ->8个二进制位(bit)
  • 对于拥有128个存储单元的存储器,容量为128个字节
  • 大容量的存储器一般用以下单位来计量容量

1KB = 1024B, 1MB = 1024KB, 1GB = 1024MB, 1TB = 1024GB

CPU对存储器的读写

  • 读数据->指定存储单元的地址

    在读写数据时指明哪种操作,读还是写

  • 读写时与外部器件的信息交互:

    存储单元的地址(地址信息);
    器件的选择,读会写的命令(控制信息);
    读或写的数据(数据信息);

总线

汇编语言_第3张图片

地址总线

  • CPU是通过地址总线来指定存储器单元的.
  • 寻址能力:

一根导线可以传送两种:高电平,低电平,二进制表示为1,0
如:10根地址总线
则为 210 = 1024 最小为0 最大为1023
一个CPU有N个地址总线->地址总线的宽度为N->寻址能力则为2N
汇编语言_第4张图片

控制总线

  • CPU对外部器件的控制是通过控制总线来进行的.
  • 有多少根控制总线,意味着CPU提供了对外部器件的多少种控制

数据总线

  • CPU与内存或其他器件之间的数据传送是通过数据总线进行的.

  • 数据总线的宽度决定了CPU和外界数据传递的速度.

  • 8根数据总线一次可以传送8个bit(一个字节)
    汇编语言_第5张图片

  • 16根数据总线一次可以传递16个bit(两个字节)
    汇编语言_第6张图片

  • 读数据:

    CPU通过地址总线将信息3发出.
    CPU通过控制总线发出读取内存命令.
    存储器将3号单元的数据8通过数据线送入CPU.

  • 写数据

    CPU通过地址总线将地址信息3发出.
    CPU通过控制总线发出写入内存命令.
    CPU通过数据总线将26送入内存的3号单元中.

  • 命令计算机读写数据的汇编指令:

    mov ax,[3]

检测点

  • (1) 1个CPU的寻址能力为8KB,那么它的地址总线的宽度为多少?

    N根的寻址能力为2N 一根地址总线寻址为21=2 10根地址总线就是210=1024byte=1KB 13根就是213=8KB 所以寻址能力为8KB的CPU,那么它的地址总线宽度为13根。
    公式: 8 * 1024 = 2N = 23 * 210 = 2(3+10) => N = 13

  • (2) 1KB的存储器有多少个存储单元.存储单元的编号从多少到多少?

    1024个存储单元,从0开始 0 ~ 1024-1 => 0 ~ 1023

  • (3) 1KB的存储器可以存储多少个bit.多少个Byte.

    1KB = 1024多少个Byte * 1KB * 8bit = 8192bit = 1024Byte

  • (4) 1GB、1MB、1KB分别是多少Byte.

    1KB=1024Byte
    1MB=1024 * 1024Byte
    1GB=1024 * 1024 * 1024Byte

  • (5) 8080、8088、 80286、 80386 的地址总线宽度分别为16根、20根、24根、32根,则它们的寻址能力分别为多少(KB)多少(MB)、多少 (MB)、多少 (GB)。

    一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。(一个内存单元=1Byte)。
    16根 216 = 64K
    20根 220 = 1MB
    24根 224 = 16MB
    32根 232 = 4GB

  • (6) 8080、 8088、8086、 80286、 80386的数据总线宽度分别为8根、8根、16根、16
    根、32根。则它们一次可以传送的数据为:多少(B)、多少(B)、多少 (B)、多少(B)、多少 (B)?

    8根数据总线一次可以传送8位二进制数据(即一个字节)。
    8080 8bit = 1B
    8088 8bit = 1B
    8086 16bit = 2B
    80286 16bit = 2B
    80386 32bit = 4B

  • (7) 从内存中读取1024字节的数据,8086至少要读 多少次,80386至少要读多少次?

    8086的数据总线宽度为16根 一次最多可传送16个bit数据, 1024字节有1024 * 8个bit数据,所以需要读 1024 * 8/16 = 512次
    80386的数据总线宽度为32根 一次最多可传送32个bit数据, 1024字节有1024 * 8个bit数据,所以需要读 1024 * 8/32 = 256次

  • (8) 在存储器中,数据和程序以什么形式存放?

    在存储器中指令和数据没有任何区别,都是二进制信息。

内存空间地址

汇编语言_第7张图片

  • 所有的物理存储器被看做一个由若干存储器组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,叫一段地址空间.CPU在这段地址空间中读写数据,实际上就是在对应的物理存储器中读写数据.
  • 内存地址空间大小受CPU地址总线宽度的限制.8086CPU的地址总线宽度为20.可以传送 2020 个不同的地址信息(大小从0 ~ 2020 - 1)
  • 不同的计算机的内存地址空间的分配情况是不同的.
  • 对CPU来说,系统中的所有存储器的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力的限制,这个逻辑存储器即是内存地址空间.

寄存器

  • 在CPU上

    运算器进行信息处理;
    寄存器进行信息存储;
    控制器控制各种器件进行工作;
    内部总线连接各种器件,在它们之前进行数据传送;

  • 不同的CPU,寄存器的个数,结构是不同的,每个寄存器都有一个名称,这些寄存器是:AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW.

通用寄存器

  • 8086CPU的所有寄存器都是16位的.可以存放两个字节.AX,BX,CX,DX这4个寄存器通常来存放一般性数据,成为通用寄存器.
    汇编语言_第8张图片
  • 8086CPU的上一代CPU中的寄存器为8位,为了向上兼容,8086的4个通用寄存器可以拆分成两个独立使用的8位寄存器

    AX => AH AL
    BX => BH BL
    CX => CH CL
    DX => DH DL
    汇编语言_第9张图片
    汇编语言_第10张图片

字在寄存器中的存储

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

    字节:Byte 一个字节由8个bit组成,可以存放8位寄存器中.
    字:word 一个字由两个字节组成,分别称为高位字节,低位字节.
    汇编语言_第11张图片

几条汇编指令

汇编语言_第12张图片
汇编语言_第13张图片
汇编语言_第14张图片

  • 问题2.1

    指令执行后AX中的数据是多少?
    因为AX为16位寄存器,只能存放4位十六进制的数据,所以最高的位的1不能放在AX中保存,ax中的数据为:044CH

汇编语言_第15张图片

  • 问题2.2

    指令执行后AX中的数据为多少?
    程序段中的最后一条指令 add al,93H ,执行前 al中的数据为C5H,相加所得的值为:158H,但是al为8位寄存器,只能存放两位十六进制的数据,所以最高为的1 丢失,ax中的数据为:0058H
    注意:此时的al是作为一个独立的8位寄存器来使用,和ah没有关系,CPU在执行这条命令时认为ah和al是两个不相关的寄存器
    如果执行add ax,93H,低8位的进位会存储在ah中,CPU在执行这条指令认为只有一个16位寄存器ax,进行的是16位运算.
    在进行数据传送或者运算时,要注意指令的两个操作对象的位数应该是一致的 如

    mov ax,bx  
    mov bx,cx
    mov ax,18H
    mov al,18H
    add ax,bx
    add ax,20000
    

    错误的指令为

    mov ax,bl    	;8位寄存器和16位寄存器中传送数据
    mov bh,ax		;16位寄存器和8位寄存器中传送数据
    mov al,20000	;8位寄存器最大可放的值为255的数据
    add al.100H		;将一个高于8位的数据加到一个8位的寄存器中
    

检测点

  • (1) 写出每条汇编指令执行后相关寄存器中的值。
    mov ax,62627   AX=?			;转换为十六进制:F4A3H   
    mov ah,31H     AX=?			;将高8位值覆盖:31A3H  
    mov al,23H     AX=?			;将低8位值覆盖:3123H   
    add ax,ax      AX=?			;值相加:6246H  
    mov bx,826CH   BX=?			;赋值:826CH   
    mov cx,ax      CX=?			;ax赋值给cx:6246H  
    mov ax,bx      AX=?			826CH 
    add ax,bx      AX=?			04D8H 
    mov al,bh      AX=?			0482H 
    mov ah,bl      AX=?			6C82H 
    add ah,ah      AX=?			D882H 
    add al,6       AX=?			D888H 
    add al,al      AX=?			D810H 
    mov ax,cx      AX=?			6246H
    
  • (2)只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方。
    mov ax,2H  AX=2 
    add ax,ax  AX=4 
    add ax,ax  AX=8 
    add ax,ax  AX=16 
    

物理地址

  • CPU访问内存单元时,会给出内存单元的地址,所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,成为物理地址;
  • CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址.在CPU向地址总线发出物理地址之前,必须在内部形成这个物理地址;

16位结构的CPU

  • 运算器最多一次可以处理16的数据
  • 寄存器的最大宽度为16位
  • 寄存器和运算器之间的通路为16位
  • 也就是能够一次性处理,传输,暂存的信息最大长度为16位
  • 内存单元的地址在送上地址总线之前,必须在CPU中处理,传输,暂时存放,对于16位CPU,能一次性处理,传输,暂时存储16位的地址

8086CPU给出物理地址的方法

  • 8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。
  • 8086CPU又 是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。
  • 8086CPU釆用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
  • 8086CPU相关部件的逻辑结构如图2.6所示
  • 汇编语言_第16张图片

    (1)CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;
    (2)段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
    (3)地址加法器将两个16位地址合成为一个20位的物理地址;
    (4)地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
    (5)输入输出控制电路将20位物理地址送上地址总线;
    (6)20位物理地址被地址总线传送到存储器。

  • 地址加法器采用物理地址=段地址X16+偏移地址的方法用段地址和偏移地址合成物理地址.
  • 例如,8086CPU要访问地址为123C8H的内存单元,此时,地址加法器的工作过程
  • 如图2.7所示(图中数据皆为十六进制表示)。
  • 汇编语言_第17张图片
  • 汇编语言_第18张图片

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

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

  • 更一般地说,8086CPU的这种寻址功能是“基础地址+偏移地址=物理地址”寻址模式的一种具体实现方案。8086CPU中,段地址X16可看作是基础地址。

  • 下面,我们用两个与CPU无关的例子做进一步的比喻说明。

  • 第一个比喻说明“基础地址+偏移地址=物理地址”的思想。

  • 比如说,学校、体育馆、图书馆同在一条笔直的单行路上(参考图2.8),学校位于路 的起点(从路的起点到学校距离是0米)。

  • 汇编语言_第19张图片

  • 你要去图书馆,问我那里的地址,我可以用两种方式告诉你图书馆的地址:

    (1) 从学校走2826m到图书馆。这2826m可以认为是图书馆的物理地址
    (2) 从学校走2000m到体育馆,从体育馆再走826m到图书馆。第一个距离2000m, 是相对于起点的基础地址,第二个距离826m是相对于基础地址的偏移地址(以基础地址为 起点的地址)。

  • 第一种方式是直接给出物理地址2826m,而第二种方式是用基础地址和偏移地址相加 来得到物理地址的。

  • 第二个比喻进一步说明“段地址X16+偏移地址=物理地址”的思想。

  • 我们为上面的例子加一些限制条件,比如,只能通过纸条来互相通信,你问我图书馆 的地址我只能将它写在纸上告诉你。显然,我必须有一张可以容纳4位数据的纸条,才能 写下2826这个数据。

  • 在这里插入图片描述

  • 可不巧的是,我没有能容纳4位数据的纸条,仅有两张可以容纳3位数据的纸条。这 样我只能以这种方式告诉你2826这个数据。

  • 汇编语言_第20张图片

  • 在第一张纸上写上200(段地址),在第二张纸上写上826(偏移地址)。假设我们事前对 这种情况又有过相关的约定:你得到这两张纸后,做这样的运算:200(段地址)*10+826(偏 移地址)=2826(物理地址)。

  • 8086CPU就是这样一个只能提供两张3位数据纸条的CPU。

段的概念

  • 内存并没有分段,段的划分来自于CPU,由于8086CPU用“基础地址(段地 址*16)+偏移地址=物理地址”的方式给出内存单元的物理地址

    如图2.9所示,我们可以认为:地址10000H〜100FFH的内存单元组成一 个段,该段的起始地址(基础地址)为10000H,段地址为1000H,大小为100H;我们也可 以认为地址10000H〜1007FH、10080H〜100FFH的内存单元组成两个段,它们的起始地址 (基础地址)为:10000H和10080H,段地址为:1000H和1008H,大小都为80H。
    汇编语言_第21张图片

  • 在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地 址X16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。有两点需要注 意:段地址X16必然是16的倍数,所以一个段的起始地址也一定是16的倍数;偏移地址 为16位,16位地址的寻址能力为64KB,所以一个段的长度最大为64KB。
  • CPU访问内存单元时, 必须向内存提供内存单元的物理地址。8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。
  • 思考下面的两个问题。
  • (1)观察下面的地址, 你有什么发现?
  • 汇编语言_第22张图片

    结论:CPU可以用不同的段地址和偏移地址形成同一个物理地址。

  • (2)如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可定位多少个内存单元?

    结论:偏移地址16位,变化范围为O-FFFFH,仅用偏移地址来寻址最多可寻64KB个内存单元。

在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址。
“数据在21F60H内存单元中。”这句话对于8086PC机一般不这样讲,取而代之的是两种类似的说 法:①数据存在内存2000:1F60单元中;②数据存在内存的2000H段中的1F60H单元中。这两种描述都 表示“数据在内存21F60H单元中”。

检测点

  • (1)给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为?到?

    物理地址=SA16+EA
    EA的变化范围为0H~FFFFH
    物理地址范围为(SA
    16+0H)~(SA16+FFFFH)
    =(0001H
    16+0H)~(0001H*16+FFFFH)
    =0010H ~ FFFFH

  • (2)有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻 到此单元。则SA应满足的条件是:最小为 ?,最大为 ?。

    物理地址=SA16+EA
    20000h=SA
    16+EA
    SA=(20000h-EA)/16=2000h-EA/16
    EA取最大值时,SA=2000h-ffffh/16=1001h,SA为最小值
    EA取最小值时,SA=2000h-0h/16=2000h,SA为最大值

段寄存器

  • 8086CPU有4个段寄存器:CS、DS、SS、ES。当 8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。

CS 和 IP

  • CS和IP是8086CPU中两个最关键的寄存器,它们指示了 CPU当前要读取指令的地 址。CS为代码段寄存器,IP为指令指针寄存器,从名称上我们可以看出它们和指令的关系。
  • 在8086PC机中,任意时刻,设CS中的内容为M, IP中的内容为N, 8086CPU将从 内存MX16+N单元开始,读取一条指令并执行。
  • 也可以这样表述:8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
  • 图2.10展示了 8086CPU读取、执行指令的工作原理(图中只包括了和所要说明的问题密切相关的部件,图中数字都为十六进制)。
  • 汇编语言_第23张图片
  • 图2.10说明如下。
  • (1) 8086CPU当前状态:CS中的内容为2000H, IP中的内容为0000H;
  • (2) 内存20000H〜20009H单元存放着可执行的机器码;
  • (3) 内存20000H〜20009H单元中存放的机器码对应的汇编指令如下。

    地址:20000H-20002H,内容:B8 23 01,长度:3Byte,对应汇编指令:mov ax,0123H
    地址: 20003H〜20005H,内容:BB03 00,长度:3Byte,对应汇编指令:movbx,0003H
    地址:20006H〜20007H,内容:89 D8,长度:2Byte,对应汇编指令:mov ax,bx
    地址:20008H〜20009H,内容:01 D8,长度:2Byte,对应汇编指令:add ax,bx

  • 下面的一组图(图2.11〜图2.19),以图2.10描述的情况为初始状态,展示了 8086CPU 读取、执行一条指令的过程。注意每幅图中发生的变化(下面对8086CPU的描述,是在逻 辑结构、宏观过程的层面上进行的,目的是使读者对CPU工作原理有一个清晰、直观的 认识,为汇编语言的学习打下基础。其中隐蔽了 CPU的物理结构以及具体的工作细节)。
  • 汇编语言_第24张图片
  • 汇编语言_第25张图片
  • 汇编语言_第26张图片
  • 汇编语言_第27张图片
  • 汇编语言_第28张图片
  • 汇编语言_第29张图片
  • 汇编语言_第30张图片
  • 汇编语言_第31张图片
  • 汇编语言_第32张图片
  • 汇编语言_第33张图片
  • 汇编语言_第34张图片
  • 汇编语言_第35张图片
  • 汇编语言_第36张图片
  • 汇编语言_第37张图片
  • 汇编语言_第38张图片
  • 汇编语言_第39张图片
  • 通过上面的过程展示,8086CPU的工作过程可以简要描述如下。

    (1)从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
    (2)IP=IP+所读取指令的长度,从而指向下一条指令;
    (3)执行指令。转到步骤(1),重复这个过程。

  • 在8086CPU加电启动或复位后(CPU刚开始工作时)CS和IP被设置为 CS=FFFFH, IP=OOOOH,即在8086PC机刚启动时,CPU从内存FFFFOH单元中读取指令 执行,FFFFOH单元中的指令是8O86PC机开机后执行的第一条指令。

修改CS、IP的指令

  • 能够改变CS、IP的 内容的指令被统称为转移指令
  • 若想同时修改CS、IP的内容,可用形如“jmp段地址:偏移地址”的指令完成,如
  • jmp 2AE3:3,执行后:CS=2AE3H, IP=OOO3H, CPU 将从 2AE33H 处读取指令。 jmp3:0B16,执行后:CS=OOO3H, IP=0B16H, CPU 将从 00B46H 处读取指令。
  • “jmp段地址:偏移地址”指令的功能为:用指令中给出的段地址修改CS,偏移地 址修改IPo
  • 若想仅修改IP的内容,可用形如“jmp某一合法寄存器”的指令完成,如
  • jmp ax,指令执行前:ax=1000H, CS=2000H, IP=0003H
  • 指令执行后:ax=1000H, CS=2000H, IP=1000H
  • jmp bx,指令执行前:bx=0B16H, CS=2000H, IP=0003H
  • 指令执行后:bx=0B16H, CS=2000H, IP=0B16H
  • “jmp某一合法寄存器”指令的功能为:用寄存器中的值修改IP。
  • jmp ax,在含义上好似:mov IP,ax。
  • 问题2.3
  • 内存中存放的机器码和对应的汇编指令情况如图2.27所示,设CPU初始状态:CS=2000H,IP=0000H,请写出指令执行序列.思考后看分析
  • 汇编语言_第40张图片
  • 分析:

    CPU对图2.27中的指令的执行过程如下。
    (1)当前 CS=2000H, IP=OOOOH,贝lj CPU 从内存 2000HX 16+0=20000H 处读取指 令,读入的指令是:B8 22 66(mov ax,6622H),读入后 IP=IP+3=OOO3H;
    (2)指令执行后,CS=2000H , IP=OOO3H,贝 U CPU 从内存 2000H X 16+0003H=20003H处读取指令,读入的指令是:EA 03 00 00 10(jmp 1000:0003),读入后 IP=IP+5=0008H;
    (3)指令执行后,CS=1000H , IP=0003H ,贝!I CPU 从内存 1000H X 16+0003H =10003H处读取指令,读入的指令是:B8 00 00(mov ax,0000),读入后IP=IP+3=0006H;
    (4)指令执行后,CS=1000H, IP=0006H ,贝 U CPU 从内存 1000H X 16+0006H
    =10006H处读取指令,读入的指令是:8B D8(mov bx,ax),读入后IP=IP+2=0008H;
    (5)指令执行后,CS=1000H, IP=0008H,贝 lj CPU 从内存 1000H X 16+0008H
    =10008H处读取指令,读入的指令是:FF E3(jmp bx),读入后IP=IP+2=000AH;
    (6)指令执行后,CS=1000H, IP=0000H, CPU从内存10000H处读取指令
    经分析后,可知指令执行序列为:
    (1)mov ax,6622H
    (2)jmp 1000:3
    (3)mov ax,0000
    (4)mov bx,ax
    (5)jmp bx
    (6)mov ax,0123H
    (7)转到第3步执行

代码段

对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个 段。

mov ax,0000		(B8 00 00)
add ax,0123H	(05 32 01)
mov bx,ax		(8B D8)
jmp bx			(FF E3)
  • 这段长度为10个字节的指令,存放在123B0H ~ 123B9H的一组内存单元中,可以认为,123B0H ~ 123B9H这段内存是用来存放代码的

  • 如何使得代码段中的指令被执行呢?

  • CPU只认被CS:IP指向的内存单元中的内容为指令。

  • 所以,要让CPU执行我们 放在代码段中的指令,必须要将CS:IP指向所定义的代码段中的第一条指令的首地址。

  • 对 于上面的例子,我们将一段代码存放在123B0H〜123B9H内存单元中,将其定义为代码 段,如果要让这段代码得到执行,可设CS=123BH、IP=0000H

  • 小 结

(1)段地址在8086CPU的段毒存器中存放。当8086CPU要访问内存时,由段寄存器提供内存单元的 段地址。8086CPU有4个段寄存器,其中CS用来存放指令的段地址。
(2)CS存放指令的段地址,IP存放指令的偏移地址。
8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
(3)8086CPU的工作过程:
①从CS: IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
②IP指向下一条指令;
③执行指令。(转到步骤①,重复这个过程。)
(4)8086CPU提供转移指令修改CS、IP的内容。

检测点2.3

  • 下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx
sub ax,ax
jmp ax

一共修改四次
第一次:读取mov ax,bx之后
第二次:读取sub ax,ax之后
第三次:读取jmp ax之后
第四次:执行jmp ax修改IP
最后IP的值为0000H,因为最后ax中的值为0000H,所以IP中的值也为0000H

寄存器(内存访问)

内存中字的存储

  • CPU中,用16位寄存器来存储一个字。高8位存放高位字 节,低8位存放低位字节。
  • 在内存中存储时,由于内存单元是 字节单元(一个单元存放一个字节),则一个字要用两个地址连续 的内存单元来存放,这个字的低位字节存放在低地址单元中, 高位字节存放在高地址单元中。比如我们从0地址开始存放 20000,这种情况如图3.1所示。
  • 汇编语言_第41张图片
  • 在图3.1中,我们用0、1两个内存单元存放数据 20000(4E20H). 0、1两个内存单元用来存储一个字,这两个单 元可以看作一个起始地址为0的字单元(存放一个字的内存单 元,由0、1两个字节单元组成)。对于这个字单元来说,0号单元是低地址单元,1号单 元是高地址单元,则字型数据4E20H的低位字节存放在0号单元中,高位字节存放在1 号单元中。同理,将2、3号单元看作一个字单元,它的起始地址为2.在这个字单元中 存放数据18(0012H),则在2号单元中存放低位字节12H,在3号单元中存放高位字节
    OOH
    字单元,即存放一个字型数据(16位)的内存单元,由两个地 址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中 存放字型数据的低位字节。
    问题
    对于图3.1:
    (1)0地址单元中存放的字节型数据是多少?

0地址单元中存放的字节型数据:20H;

(2)0地址字单元中存放的字型数据是多少?

0地址字单元中存放的字型数据:4E20H;

(3)2地址单元中存放的字节型数据是多少?

2地址单元中存放的字节型数据:12H;

(4)2地址字单元中存放的字型数据是多少?

2地址字单元中存放的字型数据:0012H;

(5)1地址字单元中存放的字型数据是多少?

1地址字单元,即起始地址为1的字单元,它由1号单元和2号单元组成,用这 两个单元存储一个字型数据,高位放在2号单元中,即:12H,低位放在1号单元中, 即:4EH,它们组成字型数据是124EH,大小为:4686。

从上面的问题中我们看到,任何两个地址连续的内存单元,N号单元和N+1号单 元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元 和低位字节单元。

DS 和[address]

CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中, 内存地址由段地址和偏移地址组成。8086CPU中有一个DS寄存器,通常用来存放要访问 数据的段地址。比如我们要读取10000H单元的内容,可以用如下的程序段进行。

mov bx,1000H
mov ds,bx
mov al,[0]

上面的3条指令将10000H(1000:0)中的数据读到al中。
下面详细说明指令的含义。

mov al,[0]

前面我们使用mov指令,可完成两种传送:①将数据直接送入寄存器;②将一个寄 存器中的内容送入另一个寄存器。
也可以使用mov指令将一个内存单元中的内容送入一个寄存器中。从哪一个内存单 元送到哪一个寄存器中呢?在指令中必须指明。寄存器用寄存器名来指明,内存单元则需 用内存单元的地址来指明。显然,此时mov指令的格式应该是:mov寄存器名,内存单 元地址。

  • “[…]”表示一个内存单元,”[…]”中的0表示内存单元的偏移地址。我们知道, 只有偏移地址是不能定位一个内存单元的,那么内存单元的段地址是多少呢?指令执行 时,8086CPU自动取ds中的数据为内存单元的段地址。
  • 如何用mov指令从10000H中读取数据。10000H用段地址和偏移地址 表示为1000:0,我们先将段地址1000H放入ds,然后用mov al,[0]完成传送。mov指令中 的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地 址默认放在ds中,指令执行时,8086CPU会自动从ds中取出。
mov bx,1000H
mov ds,bx

若要用mov al,[0]完成数据从1000:0单元到al的传送,这条指令执行时,ds中的内容 应为段地址1000H,所以在这条指令之前应该将1000H送入ds
如何把一个数据送入寄存器中呢?
要有一个寄存器进行转存,将1000H放入一个一般的寄存器中,如bx,在将bx的内容送入ds
问题
写几条指令,将al中的数据送入内存单元10000H中,思考后看分析.
分析
怎么将数据从寄存器送入内存单元?
从内存单元到寄存器的格式

mov 寄存器名,内存单元地址

从寄存器到内存单元的格式

mov 内存单元地址,寄存器名

10000H可表示为1000H:0.用ds存放段地址1000H,偏移地址是0,则mov[0],al可完成从al到10000H的数据传送.

mov bx,1000H
mov ds,bx
mov [0],al

字的传送

8086CPU是16位结构,一次性可以传送16位数据,一次性可以传递一个字

mov bx,1000H
mov ds,bx
mov ax,[0]    ;1000:0处的字型数据送入ax
mov [0],cx		;cx中的16位数据送到1000:0

问题
内存中的情况如图3.2所示,写出下面的指令执行后寄存器中ax,bx,cx中的值
汇编语言_第42张图片
汇编语言_第43张图片
问题
内存中的情况如图3.3所示,写出下面的指令执行后内存中的值,思考后看分析。

mov ax,1000H
mov ds,ax
mov ax,11316
mov [0],ax
mov bx,[0]	
sub bx,[2]	
mov [2],bx

汇编语言_第44张图片
汇编语言_第45张图片

mov,add,sub指令

mov、add、sub指令,它们都带有两个操作对象。

mov	寄存器,数据		比如:mov ax,8
mov	寄存器,寄存器	比如:mov ax,bx
mov	寄存器,内存单元	比如:mov ax,[0]
mov	内存单元,寄存器	比如:mov [0],ax
mov	段寄存器,寄存器	比如:mov ds,ax

(1)既然有“mov段寄存器,寄存器”,从寄存器向段寄存器传送数据,那么也应 该有“mov寄存器,段寄存器”,从段寄存器向寄存器传送数据。
(2)既然有“mov内存单元,寄存器”,从寄存器向内存单元传送数据,那么也应 该有“mov内存单元,段寄存器”,从段寄存器向内存单元传送数据。
(3) “mov段寄存器,内存单元”也应该可行,比如我们可以用10000H处存放的字 型数据设置ds(即将10000H处存放的字型数据送入ds)

mov ax,1000H
mov ds,ax
mov ds,[0]

add和sub指令同mov 一样

add寄存器,数据	比如:add ax,8
add寄存器,寄存器	比如:add ax,bx
add寄存器,内存单元	比如:add ax,[0]
add内存单元,寄存器	比如:add [0],ax
sub寄存器,数据	比如:sub ax,9
sub寄存器,寄存器	比如:subax,bx
sub寄存器,内存单元	比如:sub ax5[0]
sub内存单元,寄存器	比如:sub [0],ax

数据段

在编程时,可以根据需要,将一组内存单元 定义为一个段。我们可以将一组长度为N(NW64KB)、地址连续、起始地址为16的倍数 的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。比如用123B0H〜 123B9H这段内存空间来存放数据,我们就可以认为,123B0H〜123B9H这段内存是一个 数据段,它的段地址为123BH,长度为10个字节。
如何访问数据段中的数据呢?将一段内存当作数据段,是我们在编程时的一种安排,可以在具体操作的时候,用ds存放数据段的段地址,再根据需要,用相关指令访问数据 段中的具体单元。
比如,将123B0H〜123B9H的内存单元定义为数据段。现在要累加这个数据段中的前 3个单元中的数据,代码如下。

mov ax,123BH
mov ds,ax	;123BH送入ds中,作为数据段的段地址
mov al,0	;用al存放累加结果
add al,[0]	;将数据段第一个单元(偏移地址为0)中的数值加到al中
add al,[1]	;将数据段第二个单元(偏移地址为1)中的数值加到al中
add al,[2]	;将数据段第三个单元(偏移地址为2)中的数值加到al中

问题
写几条指令,累加数据段中的前3个字型数据,思考后看分析。
分析:
代码如下。

mov ax,123BH
mov ds,ax	;123BH送入ds中,作为数据段的段地址
mov ax,0	;用ax存放累加结果
add ax,[0]	;将数据段第一个字(偏移地址为0)加到ax中
add ax,[2]	;将数据段第二个字(偏移地址为2)加到ax中
add ax,[4]	;将数据段第三个字(偏移地址为4)加到ax中

注意,一个字型数据占两个单元,所以偏移地址是0、2、4

小 结
1 字在内存中存储时,要用两个连续的内存单元存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中
2 用mov指令访问内存单元,可以在mov指令中只给单元的偏移地址,此时,段地址默认在DS寄存器中
3 [address]表示一个偏移地址为address的内存单元
4 在内存和寄存器之间传送字型数据时,高地址单元和高位8位寄存器,低地址单元和低8位寄存器相对应
5 mov,add,sub是具有两个操作对象的指令,jmp是具有一个操作对象的指令

检测点3.1

(1)内存中的情况如图3.6所示。
各寄存器的初始值:CS=2000H, IP=0, DS=1000H, AX=0, BX=0;
①写出CPU执行的指令序列(用汇编指令写出)。
②写出CPU执行每条指令后,CS、IP和相关寄存器中的数值。

mov ax,6622H   		CS=2000H IP=3H DS=0 AX=6622H BX=0     
mov jmp 0ff0:0100 	CS=0ff0H IP=100H DS=0 AX=6622H BX=0
mov ax,2000H 		CS=0ff0H IP=103H DS=0 AX=2000H BX=0
mov ds,ax			CS=0ff0H IP=105H DS=2000 AX=2000H BX=0
mov ax,[0008]		CS=0ff0H IP=108H DS=2000 AX=C389H BX=0
mov ax,[0002]		CS=0ff0H IP=10BH DS=0 AX=EA66H BX=0

③再次体会:数据和程序有区别吗?如何确定内存中的信息哪些是数据,哪些是程序?

汇编语言_第46张图片

栈是一种具有特殊的访问方式的存储空 间。它的特殊性就在于,最后进入这个空间的数据,最先岀去。(后进先出)
栈有两个基本的操作:入栈和出 栈。入栈就是将一个新的元素放到栈顶,岀栈就是从栈顶取出一个元素。栈顶的元素总是 最后入栈,需要出栈时,又最先被从栈中取出。栈的这种操作规则被称为:LIFO(Last In First Out,后进先出)。

CPU提供的栈机制

在基于8086CPU编程的时候,可以将一段内存当作栈 来使用。
8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)和POP(出栈)。比 如,push ax表示将寄存器ax中的数据送入栈中,pop ax表示从栈顶取出数据送入ax。 8086CPU的入栈和出栈操作都是以字为单位进行的。
下面举例说明,我们可以将10000H〜1000FH这段内存当作栈来使用
图3.9描述了下面一段指令的执行过程。
汇编语言_第47张图片

mov ax,0123H 
push ax
mov bx,2266H 
push bx
mov ex,1122H
push ex
pop ax
pop bx
pop ex

注意,字型数据用两个单元存放,高地址单元存放高8位,低地址单元存放低8位。
看到图3.9所描述的push和pop指令的执行过程,是否有一些疑惑?总结一下, 大概是这两个问题。
其一,我们将10000H〜1000FH这段内存当作栈来使用,CPU执行push和pop指令 时,将对这段空间按照栈的后进先出的规则进行访问。但是,一个重要的问题是,CPU 如何知道10000H〜1000FH这段空间被当作栈来使用?
其二,push ax等入栈指令执行时,要将寄存器中的内容放入当前栈顶单元的上方, 成为新的栈顶元素;pop ax等指令执行时,要从栈顶单元中取出数据,送入寄存器中。显 然,push、pop在执行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?
行的时候,必须知道哪个单元是栈顶单元,可是,如何知道呢?
这不禁让我们想起另外一个问题,就是,CPU如何知道当前要执行的指令 所在的位置?我们现在知道答案,那就是CS、IP中存放着当前指令的段地址和偏移地 址。现在的问题是:CPU如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈 顶的地址,8086CPU中,有两个寄存器,段寄存器SS和寄存器SP,栈顶的段地址存放 在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素。push指令和pop指 令执行时,CPU从SS和SP中得到栈顶的地址。
现在,可以完整地描述push和pop指令的功能了,例如push ax
push ax的执行,由以下两步完成。
(1)SP=SP-2, SS:SP指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
(2)将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
图3.10描述了 8086CPU对push指令的执行过程。
汇编语言_第48张图片
从图中可以看出,8086CPU中,入栈时,栈顶从高地址向低地址方向增长。
问题
如果将10000H〜1000FH这段空间当作栈,初始状态栈是空的, SP=?思考后看分析。
汇编语言_第49张图片
将10000H〜1000FH这段空间当作栈段,SS=1000H,栈空间大小为16字节,栈最底 部的字单元地址为1000:000E.任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时 候,SS=1000H, SP=000EH。栈为空,就相当于栈中唯一的元素出栈,出栈后, SP=SP+2, SP原来为000EH,加2后SP=10H,所以,当栈为空的时候,SS=1000H, SP=10H.
换一个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素, 也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地 址为栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000E,所以栈空 时,SP=0010H。
我们描述pop指令的功能,例如pop ax
pop ax的执行过程和push ax刚好相反,由以下两步完成。
(1)将SS:SP指向的内存单元处的数据送入ax中;
(2)SP=SP+2, SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的 栈顶。

图3.12描述了8086CPU对pop指令的执行过程
汇编语言_第50张图片
注意,图3.12中,出栈后,SS:SP指向新的栈顶1000EH, pop操作前的栈顶元素, 1000CH处的2266H依然存在,但是,它已不在栈中。当再次执行push等入栈指令后, SS:SP移至1000CH,并在里面写入新的数据,它将被覆盖。

栈顶超界的问题

8086CPU用SS和SP指示栈顶的地址,并提供push和pop指令实现 入栈和出栈。
是,有一个问题需要讨论,就是SS和SP只是记录了栈顶的地址,依靠SS和 SP可以保证在入栈和出栈时找到栈顶。可是,如何能够保证在入栈、出栈时,栈顶不会 超出栈空间?
图3.13描述了在执行push指令后,栈顶超出栈空间的情况。
图3.13中,将10010H〜1001FH当作栈空间,该栈空间容量为16字节(8字),初始状 态为空,SS=1000H、SP=0020H, SS:SP 指向 10020H;
在执行8次push ax后,向栈中压入8个字,栈满,SS:SP指向10010H;
再次执行push ax: sp=sp-2, SS:SP指向1000EH,栈顶超出了栈空间,ax中的数据 送入1000EH单元处,将栈空间外的数据覆盖。
汇编语言_第51张图片
图3.14描述了在执行pop指令后,栈顶超出栈空间的情况。
图3.14中,将10010H〜1001FH当作栈空间,该栈空间容量为16字节(8字),当前状 态为满,SS=1000H、SP=0010H, SS:SP 指向 10010H;
在执行8次pop ax后,从栈中弹出8个字,栈空,SS:SP指向10020H;
再次执行pop ax: sp=sp+2, SS:SP指向10022H,栈顶超出了栈空间。此后,如果再 执行push指令,10020H、10021H中的数据将被覆盖。
汇编语言_第52张图片

  • 上面描述了执行push、pop指令时,发生的栈顶超界问题。可以看到,当栈满的时候 再使用push指令入栈,或栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。
  • 栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很 可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可 能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于 我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。

push , pop 指令

  • 前面我们一直在使用push ax和pop ax,显然push和pop指令是可以在寄存器和内存 (栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存 空间。)之间传送数据的。
    push和pop指令的格式可以是如下形式:
push 寄存器	;将一个寄存器中的数据入栈
pop 寄存器	;出栈,用一个寄存器接收出栈的数据

当然也可以是如下形式:

push 段寄存器	;将一个段寄存器中的数据入栈
pop 段寄存器	;出栈,用一个段寄存器接收出栈的数据

push和pop也可以在内存单元和内存单元之间传送数据,我们可以:

push 内存单元	;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)
pop 内存单元	;出栈,用一个内存字单元接收岀栈的数据

比如:

mov ax,1000H  
mov ds,ax	    	;1000:0处的字压入栈中
push [0]  			;内存单元的段地址要放在ds中
pop [2]				;出栈,出栈的数据送入1000:2

指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中只给出内存单元 的偏移地址,段地址在指令执行时,CPU从ds中取得。
问题3.7
编程,将10000H〜1000FH这段空间当作栈,初始状态栈是空的,将AX、BX、DS 中的数据入栈。

mov ax,1000H
mov	ss, ax		;设置栈的段地址,SS=1000H,不能直接向段寄存器SS中送入 ;数据,所以用ax中转。
mov	sp,0010H	;设置栈顶的偏移地址,因栈为空,所以sp=0010H
				;上面的3条指令设置栈顶地址。编程中要自己注意栈的大小。
push	ax	
push	bx	
push	ds	

问题3.8

编程:
(1)将10000H〜1000FH这段空间当作栈,初始状态栈是空的;
(2)设置 AX=001AH, BX=001BH;
(3)将AX、BX中的数据入栈;
(4)然后将AX、BX清零;
(5)从栈中恢复AX、BX原来的内容。

mov ax,1000H
mov ss,ax
mov sp,0010H ;初始化栈顶
mov ax,001AH 
mov bx,001BH
push ax
push bx
sub ax,ax
sub bx,bx
pop bx  ;从栈中恢复ax、bx原来的数据,当前栈顶的内容是bx
pop ax  ;中原来的内容:001BH, ax中原来的内容001AH在栈顶

汇编语言_第53张图片
从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序 要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先 出栈。
问题3.9
编程:
(1)将10000H〜1000FH这段空间当作栈,初始状态栈是空的;
(2)设置 AX=001AH, BX=001BH;
(3)利用栈,交换AX和BX中的数据。

mov ax,1000H
mov ss,ax
mov sp,0010H ;初始化栈顶
mov ax,001AH 
mov bx,001BH
pop ax  ;当前栈顶的数据是bx中原来的数据:001BH;
pop bx  ;执行pop ax后,栈顶的数据为ax原来的数据;

汇编语言_第54张图片

问题3.10
如果要在10000H处写入字型数据2266H,可以用以下的代码完成:

mov ax,1000H
mov ds,ax
mov ax,2266H
mov [0],ax


补全下面的代码,使它能够完成同样的功能:在10000H处写入字型数据2266H 要求:不能使用“mov内存单元,寄存器”这类指令。
?______
?______
?______
mov ax,2266H
push ax

我们来看需补全代码的最后两条指令,将ax中的2266H压入栈中,也就是说,最终 应由push ax将2266H写入10000H处。问题的关键就在于:如何使push ax访问的内存单 元是 lOOOOHo
push ax是入栈指令,它将在栈顶之上压入新的数据。一定要注意:它的执行过程 是,先将记录栈顶偏移地址的SP寄存器中的内容减2,使得SS:SP指向新的栈顶单元,• 然后再将寄存器中的数据送入SS:SP指向的新的栈顶单元。
所以,要在执行push ax之前,将SS:SP指向10002H(可以设SS=1OOOH, SP=0002H),这样,在执行push ax的时候,CPU先将SP=SP-2,使得SS:SP指向 10000H,再将ax中的数据送入SS:SP指向的内存单元处,即10000H处。
完成的程序如下。

mov ax,1000H
mov ss,ax
mov sp, 2
mov ax,2266H
push ax
  • 从问题3.10的分析中可以看出,push, pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。同时,push和pop指令还要改变SP中的内容
  • 我们要十分清楚的是,push和pop指令同mov指令不同,CPU执行mov指令只需一 步操作,就是传送,而执行push、pop指令却需要两步操作。.执行push时,CPU的两步 操作是:先改变SP,后向SS:SP处传送。执行pop时,CPU的两步操作是:先读取 SS:SP处的数据,后改变SP

注意,push, pop等栈操作指令,修改的只是SP.也就是说,栈顶的变化范围最大为:0〜FFFFH

  • 提供:SS、SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令.这就是8086CPU提供的栈操作机制。
  • 综述

1、8086CPU提供了栈操作机制,方案如下.
在SS、SP中存放栈顶的段地址和偏移地址;
提供入栈和出栈指令,他们根据SS:SP指示的地址,按照栈的方式访问内存单元;
2、push指令的执行步骤:①SP=SP-2;②向SS:SP指向的字单元传送数据
3、pop指令的执行步骤:①从SS:SP指向的字单元读取数据;②SP=SP+2
4、任何时刻SS:SP指向栈顶元素
5、8086CPU只记录栈顶,栈的空间大小要我们自己管理
6、用栈来暂存以后需要回复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反
7、push、pop实质上是一种内存传送指令,注意他们的灵活运用。
栈是一种非常重要的机制

栈段

  • 对于8086PC机,在编程时,可以根据需要,将一组内存单元 定义为一个段。我们可以将长度为N(NW64KB)的一组地址连续、起始地址为16的倍数 的内存单元,当作栈空间来用,从而定义了一个栈段。比如,我们将10010H〜1001FH这 段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。这段空间就可以称为一 个栈段,段地址为1001H,大小为16字节。
  • 问题3.11
  • 如果将10000H〜1FFFFH这段空间当作栈段,初始状态栈是空的,此时, SS=1000H, SP=?
  • 分析:
  • 如果将10000H-1FFFFH这段空间当作栈段,SS=1000H,栈空间为64KB,栈最底部 的字单元地址为1000:FFFE。任意时刻,SS:SP指向栈顶单元,当栈中只有一个元素的时候,SS=1000H , SP=FFFEH。栈为空,就相当于栈中唯一的元素出栈,出栈后, SP=SP+2。
    SP原来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H, SP=0。
    换一个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素, 也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的地址为 栈最底部的字单元的地址+2。栈最底部字单元的地址为1000:FFFE,所以栈空时,SP=0000H
  • 问题3.12
  • 一个栈段最大可以设为多少?为什么?
  • 这个问题显而易见,提出来只是为了提示我们将相关的知识融会起来。首先从栈操作 指令所完成的功能的角度上来看,push、pop等指令在执行的时候只修改SP,所以栈顶的 变化范围是0-FFFFH,从栈空时候的SP=0, 一直压栈,直到栈满时SP=0;如果再次压 栈,栈顶将环绕,覆盖了原来栈中的内容。所以一个栈段的容量最大为64KB。
  • 段的综述

我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是 我们自己的安排。
我们可以用一个段存放数据,将它定义为“数据段”;
我们可以用一个段存放代码,将它定义为“代码段”;
我们可以用一个段当作栈,将它定义为“栈段”。
我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:
对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我 们定义的数据段中的内容当作数据来访问;
对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执 行我们定义的代码段中的指令;
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操 作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。
可见,不管我们如何安排,CPU将内存中的某段内容当作代码,是因CS:IP指向了那里;CPU将某 段内存当作栈,是因为SS:SP指向了那里。我们一定要清楚,什么是我们的安排,以及如何让CPU按我 们的安排行事。要非常清楚CPU的工作机理,才能在控制CPU按照我们的安排运行的时候做到游刃 有余。
比如我们将10000H〜1001FH安排为代码段,并在里面存储如下代码:

	mov ax,1000H
	mov ss,ax
	mov sp, 0020H	;初始化栈顶
	mov ax,cs
	mov ds,ax	;设置数据段段地址
	mov ax,[0]
	add ax,[2] 
	mov bx,[4] 
	add bx,[6] 
	push ax 
	push bx
	pop ax 
	pop bx

设置CS=1000H , IP=0,这段代码将得到执行。可以看到,在这段代码中,我们又将 10000H〜1001FH安排为栈段和数据段。10000H〜1001FH这段内存,既是代码段,又是栈段和数据段。
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,也可以什么也不 是。关键在于CPU中寄存器的设置,即CS、IP, SS、SP, DS的指向。

检测点3.2

  • (1)补全下面的程序,使其可以将10000H〜1000FH中的8个字,逆序复制到 20000H〜2000FH中。逆序复制的含义如图3.17所示(图中内存里的数据均为假设)。
  • 汇编语言_第55张图片
mov ax,1000H
mov ds,ax
________
________
________
push [0]
push [2]
push [4]
push [6]
push [8]
push [a]
push [c]
push [e]

(2)补全下面的程序,使其可以将10000H〜1000FH中的8个字,逆序复制到 20000H〜2000FH 中。

mov ax,2000H
mov ds,ax
________
________
________
push [e]
push [c]
push [a]
push [8]
push [6]
push [4]
push [2]
push [0]

第一个程序

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

汇编语言_第56张图片

  • 图4.1描述了一个汇编语言程序从写出到最终执行的简要过程。具体说明如下。
  • 第一步:编写汇编源程序。
  • 使用文本编辑器(如Edit、记事本等),用汇编语言编写汇编源程序。
  • 这一步工作的结果是产生了一个存储源程序的文本文件。
  • 第二步:对源程序进行编译连接。
  • 使用汇编语言编译程序对源程序文件中 的源程序进行编译,产生目标文件;再用连 接程序对目标文件进行连接,生成可在操作 系统中直接运行的可执行文件。
  • 可执行文件包含两部分内容。
  • 程序(从源程序中的汇编指令翻译过 来的机器码)和数据(源程序中定义的 数据)
  • 相关的描述信息(比如,程序有多 大、要占用多少内存空间等)
  • 这一步工作的结果:产生了一个可在操作系统中运行的可执行文件。
  • 第三步:执行可执行文件中的程序。
  • 在操作系统中,执行可执行文件中的程序。
  • 操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内 存,并进行相关的初始化(比如设置CS:IP指向第一条要执行的指令),然后由CPU执行 程序。

[BX]和loop指令

1.[bx]和内存单元的描述

[bx]和[0]有些类似,[0]表示内存单元,它的偏移地址是0。比如在下面的指令中:

mov ax,[0]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个 字,偏移地址为0,段地址在ds中。

mov al,[0]

将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个 字节,偏移地址为0,段地址在ds中。
要完整地描述一个内存单元,需要两种信息:①内存单元的地址;②内存单元的长度 (类型)。
用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在ds中,单元的长 度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出。
[bx]同样也表示一个内存单元,它的偏移地址在bx中,比如下面的指令:

mov ax,[bx]

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个 字,偏移地址在bx中,段地址在ds中。

mov al,[bx]

将一个内存单元的内容送入al,这个内存单元的长度为1字节(字节单元),存放一个 字节,偏移地址在bx中,段地址在ds中。

2.loop

英文单词“loop”有循环的含义,显然这个指令和循环有关。
我们在这一章,讲解[bx]和loop指令的应用、意义和相关的内容。

3.定义的描述性的符号:"()”

为了描述上的简洁,在以后的课程中,我们将使用一个描述性的符号“()”来表示一 个寄存器或一个内存单元中的内容。比如:
(ax)表示ax中的内容、(al)表示al中的内容;
(20000H)表示内存20000H单元的内容(()中的内存单元的地址为物理地址);
((ds)* 16+(bx))表示:
ds中的内容为ADRI, bx中的内容为ADR2,内存ADRI X 16+ADR2单元的内容。
也可以理解为:ds中的ADR1作为段地址,bx中的ADR2作为偏移地址,内存 ADR1:ADR2单元的内容。
注意,“(尸中的元素可以有3种类型:①寄存器名;②段寄存器名;③内存单元的 物理地址(一个20位数据)。比如:
(ax)、(ds)、(al)、(ex)、(20000H)、((ds)*16+(bx))等是正确的用法;
(2000:0)、((ds): 1000H)等是不正确的用法。
我们看一下(X)的应用,比如
(1)ax中的内容为0010H,可以这样来描述:(ax)=0010H;
(2)2000:1000处的内容为0010H,可以这样来描述:(21000H)=0010H;
(3)对于mov ax,[2]的功能,可以这样来描述:(ax)=((ds)*16+2);
(4)对于mov(2),ax的功能,可以这样来描述:((ds)*16+2)=(ax);
.(5)对于add ax,2的功能,可以这样来描述:(ax)=(ax)+2;
(6)对于add ax,bx的功能,可以这样来描述:(ax)=(ax)+(bx);
(7)对于push ax的功能,可以这样来描述:
(sp)=(sp)-2
((ss)164-(sp))=(ax)
(8)对于pop ax的功能,可以这样来描述:
(ax)=((ss)
16+(sp))
(sp)=(sp)+2
"(X)”所表示的数据有两种类型:①字节;②字。是哪种类型由寄存器名或具体的 运算决定,比如:
(al)、(bl)、(cl)等得到的数据为字节型;(ds)、(ax)、(bx)等得到的数据为字型。
(al)=(20000H),贝iJ(20000H)得到的数据为字节型;(ax)=(20000H),则(20000H)得到的 数据为字型。

4.约定符号idata表示常量

用idata表示常 量。比如:
mov ax,[idata]就代表 movax,[l]、mov ax,[2]> mov ax,[3]等。
mov bx,idata 就代表 mov bx,l > mov bx,2> mov bx,3 等。
mov ds,idata就代表movds,l、mov ds,2等,它们都是非法指令。

5.1[BX]

看一看下面指令的功能。

mov ax,[bx]

功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将SA:EA 处的数据送入ax中。即:(ax)=((ds)*16+(bx))。

mov [bx],ax

功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的 数据送入内存SA:EA处。即:((ds)*16+(bx))=(ax)。
程序和内存中的情况如图5.1所示,写出程序执行后,21000H〜21007H单元中的内存中的情况
汇编语言_第57张图片

(1)先看一下程序的前3条指令:

mov ax,2000H
mov ds,ax
mov bx,1000H

这3条指令执行后,ds=2000H, bx=1000H
(2)接下来,第4条指令:

mov ax,[bx]

指令执行前:ds=2000H, bx=1000H,则mov ax,[bx]将把内存2000:1000处的字型数 据送入ax中。该指令执行后,ax=00beHo
(3)接下来,第5、6条指令:

inc bx
inc bx

这两条指令执行前bx=1000H,执行后bx=1002H
(4)接下来,第7条指令:
mov [bx],ax
指令执行前:ds=2000H, bx=1002H,贝U mov [bx],ax将把ax中的数据送入内存 2000:1002处。指令执行后,2000:1002单元的内容为BE, 2000:1003单元的内容为00。
(5)接下来,第8、9条指令:

inc bx
inc bx

这两条指令执行前bx=1002H,执行后bx=1004Ho
(6)接下来,第10条指令:

mov [bx],ax

指令执行前:ds=2000H, bx=1004H,则mov [bx],ax将把ax中的数据送入内存 2000:1004处。指令执行后,2000:1004单元的内容为BE, 2000:1005单元的内容为00。
(7)接下来,第11条指令:

inc bx

这条指令执行前bx=1004H,执行后bx=1005Ho
(8)接下来,第12条指令:

mov [bx],al

指令执行前:ds=2000H, bx=1005H,贝U mov [bx],al将把al中的数据送入内存 2000:1005处。指令执行后,2000:1005单元的内容为BE。
(9)接下来,第13条指令:

inc bx	

这条指令执行前bx=1005H,执行后bx=1006H
(10)接下来,第14条指令:

mov [bx],al	

指令执行前:ds=2000H, bx=1006H,则 mov [bx],al 将把 al 中的数据送入内存2000:1006处。指令执行后,2000:1006单元的内容为BE。
程序执行后,内存中的情况如图5.2所示。
汇编语言_第58张图片

5.2 Loop 指令

loop指令的格式是:loop标号,CPU执行loop指令的时候,要进行两步操作, ①(cx)=(cx)-1;②判断CX中的值,不为零则转至标号处执行程序,如果为零则向下 执行。
CX中的值影响着loop指令的执行结果。通常(注意,我 们说的是通常)我们用loop指令来实现循环功能,CX中存放循环次数。
计算22,结果存在ax中。
设(ax)=2,可计算(ax)=(ax) * 2,最后(ax为22的值。N*2可用N+N实现, 程序如下。

assume cs:code
code segment
mov ax,2
add ax,ax
mov ax,4c00h
int 21h
code ends
end

计算211

assume cs:code
code segment
mov ax,2
mov cx,11
s : add ax,ax
loop s
mov ax,4c00h
int 21h
code ends
end

(1)标号
在汇编语言中,标号代表一个地址,程序5.1中有一个标号S.它实际上标识了一个 地址,这个地址处有一条指令:add ax,ax
(2)loop s
CPU执行loop s的时候,要进行两步操作:
①(cx)=(cx)-1;
②判断cx中的值,不为0则转至标号s所标识的地址处执行(这里的指令是add ax,ax),如果为零则执行下一•条指令(下一条指令是mov ax,4c00h).
(3)以下3条指令

mov cx,11
s : add ax,ax
loop s

执行loop s时,首先要将(cx)减1,然后若(cx)不为0,则向前转至s处执行add ax,ax。所 以,可以利用cx来控制add ax,ax的执行次数。
下面我们详细分析一下这段程序的执行过程,从中体会如何用cx和loop s相配合实 现循环功能。

(1)执行 mov cx,11,设置(cx)=11 ;
(2)执行 add ax,ax(第 1 次);
(3)执行loop s将(cx)减1, (cx)=10, (cx)不为0,所以转至s处;
(4)执行 add ax,ax(第 2 次);
(5)执行 loop s 将(cx)减 1, (cx)=9, (cx)不为0,所以转至s处;

(22)执行 add ax,ax(第 11 次);
(23)执行 loops 将(cx)减 1, (cx)=0,(cx)为0,所以向下执行。(结束循环)
从上面的过程中,我们可以总结出用CX和loop指令相配合实现循环功能的3个 要点:
(1)在CX中存放循环次数;
(2)loop指令中的标号所标识地址要在前面;
(3)要循环执行的程序段,要写在标号和loop指令的中间。
用CX和loop指令相配合实现循环功能的程序框架如下。

mov cx,循环次数
循环执行的程序段
loop s

loop和[bx]的联合应用

考虑这样一个问题,计算ffff:0〜ffff:b单元中的数据的和,结果存储在dx中。
(1)运算后的结果是否会超出dx所能存储的范围?
ffff:0〜ffff:b内存单元中的数据是字节型数据,范围在0〜255之间,12个这样的数据 相加,结果不会大于65535,可以在dx中存放下。
(2)我们能否将ffff:0〜ffff:b中的数据直接累加到dx中?
不行,因为ffff:0〜ffff:b中的数据是8位的,不能直接加到16位寄存器dx中。
(3)我们能否将ffff:0〜ffff:b中的数据累加到dl中,并设置(dh)=0,从而实现累加到 dx中?
不行,因为dl是8位寄存器,能容纳的数据的范围在0〜255之间,ffff:O~ffff:b 中的数据也都是8位,如果仅向dl中累加12个8位数据,很有可能造成进位丢失。
(4)我们到底怎样将ffff:O〜ffff:b中的8位数据,累加到16位寄存器dx中?
从上面的分析中,可以看到,这里面有两个问题:类型的匹配和结果的不超界。具体 的说,就是在做加法的时候,我们有两种方法:
①(dx)=(dx)+内存中的8位数据;
②(dl)=(dl)+内存中的8位数据。
第一种方法中的问题是两个运算对象的类型不匹配,第二种方法中的问题是结果有可 能超界。
方法
用一个16位寄存器来做中介。将内存单元中的8位数据赋值到一个16位寄存器ax 中,再将ax中的数据加到dx,从而使两个运算对象的类型匹配并且结果不会超界。
想清楚以上的问题之后,编写程序如下。
程序5.5

assume cs:code
code segment
mov ax,Offffh
mov ds,ax		;设置(ds) =ffffh
mov dx,0		;初始化累加寄存器,(dx)=0
mov al,ds:[0]
mov ah,0		;(ax)=((ds)*16+0)=(ffff0h)
add dx,ax		;向dx中加上ffff: 0单元的数值
mov al,ds:[1]	
mov ah,0		;(ax)=((ds)*16+l)=(fffflh)
add dx,ax		;向dx中加上ffff: 1单元的数值
mov al,ds:[2]
mov ah,0		;(ax)=((ds)*16+2)=(ffff2h)
add dx,ax		;向dx中加上ffff: 2单元的数值			
mov al,ds:[3] 
mov ah,0 		;(ax)=((ds)*16+3)=(ffff3h)
add dx,ax		;向dx中加上ffff: 3单元的数值
mov al,ds:[4] 
mov ah,0		;(ax)=((ds)*16+4)=(ffff4h)
add dx,ax		;向dx中加上ffff: 4单元的数值
mov al,ds:[5]
mov ah,0		;(ax)=((ds)*16+5)=(ffff5h)
add dx,ax		;向dx中加上ffff :5单元的数值
mov al,ds:[6]	
mov ah,0		;(ax)=((ds)*16+6)=(ffff6h)
add dx,ax		;向dx中加上ffff :6单元的数值
mov al,ds:[7]	
mov ah,0		;(ax)=((ds)*16+7)=(ffff7h)
add dx,ax		;向dx中加上ffff: 7单元的数值
mov al,ds:[8]	
mov ah,0		;(ax)=((ds)*16+8)=(ffff8h)
add dx,ax		;向dx中加上ffff :8单元的数值
mov al,ds:[9]	
mov ah,0		;(ax)=((ds)*16+9)=(ffff9h)
add dx,ax		;向dx中加上ffff: 9单元的数值
mov al,ds:[Oah]	
mov ah,0		;(ax) = ( (ds) *16+0ah) = (ffffah)
add dx,ax		;向dx中加上ffff :a单元的数值
mov al,ds:[Obh]	
mov ah,0		;(ax)=((ds)*16+0bh)=(ffffbh)
add dx,ax		;向dx中加上ffff:b单元的数值
mov ax,4c00h	;程序返回
int 21h	

code ends
end

应用loop指令,改进程序5.5

mov al,ds:[X]  	;ds :X指向f f f f: X单元
mov ah,0		;(ax)=((ds)*16+(X))=(ffffXh)
add dx,ax		;向dx中加上ffff :X单元的数值

可以看到,12个相似的程序段中,只有moval,ds:[X]指令中的内存单元的偏移地 址是不同的,其他都一样。而这些不同的偏移地址是在0<=X<=bH的范围内递增变化的。

assume cs:code code segment
	mov ax,0ffffH
	mov ds,ax
	mov bx,0
	mov dx,0
	mov cx,12
	s: 
	mov al,[bx]
	mov ah,0
	add dx,ax
	inc bx
	loop s
	mov ax,4c00h
	int 21h
code ends
end

段前缀

指令“mov ax,[bx]”中,内存单元的偏移地址由bx给出,而段地址默认在ds中。我 们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。比如:
(1)mov ax,ds:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为 2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
(2)mov ax,cs:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为 2字节(字单元),存放一个字,偏移地址在bx中,段地址在cs中。
(3)mov ax,ss:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为 2字节(字单元),存放一个字,偏移地址在bx中,段地址在ss中。
(4)mov ax,es:[bx]
将一个内存单元的内容送入ax,这个内存单元的长度为 2字节(字单元),存放一个字,偏移地址在bx中,段地址在es中。
(5)mov ax,ss:[0]
将一个内存单元的内容送入ax,这个内存单元的长度为 2字节(字单元),存放一个字,偏移地址为0,段地址在ss中。
(6)mov ax,cs:[0]
将一个内存单元的内容送入ax,这个内存单元的长度为 2字节(字单元),存放一个字,偏移地址为0,段地址在cs中。
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的 “ds:” “cs:” “ss:” “es:” ,在汇编语言中称为段前缀

一段安全的空间

在8086模式中,随意向一段内存空间写入内容是很危险的,因为这段空间中可能存 放着重要的系统数据或代码。比如下面的指令:

mov ax,1000h
mov ds,ax
mov al,0
mov ds:[0],al

这种做法是不合理 的,因为之前我们并没有论证过1000:0中是否存放着重要的系统数据或代码。如果 1000:0中存放着重要的系统数据或代码,“mov ds:[0],al”将其改写,将引发错误。
(1)我们需要直接向一段内存中写入内容;
(2)这段内存空间不应存放系统或其他程序的数据或代码,否则写入操作很可能引发 错误;
(3)DOS方式下,一般情况,0:200〜0:2ff空间中没有系统或其他程序的数据或 代码;
(4)以后,我们需要直接向一段内存中写入内容时,就使用0:200〜0:2ff这段空间。

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

and 和 or 指令

首先,介绍两条指令and和or,因为我们下面的例程中要用到它们。
(1)and指令:逻辑与指令,按位进行与运算。
例如指令:

mov al,01100011B
and al,00111011B

执行后:al = 00100011B
通过该指令可将操作对象的相应位设为0,其他位不变。
例如:
将al的第6位设为0的指令是:and al,10111111B
将al的第7位设为0的指令是:and al,01111111B
将al的第0位设为0的指令是:and al,11111110B

(2)or指令:逻辑或指令,按位进行或运算。
例如指令:

mov al,01100011B
or  al,00111011B

执行后:al = 01111011B
通过该指令可将操作对象的相应位设为1,其他位不变。
例如:
将al的第6位设为1的指令是:or al,01000000B
将al的第7位设为1的指令是:or al,10000000B
将al的第0位设为1的指令是:or al,00000001B

关于ASCII码

你可能感兴趣的:(iOS逆向,汇编)