汇编入门(长文多图,流量慎入!!!)

8086汇编


本笔记是笔者观看小甲鱼老师(鱼C论坛)《零基础入门学习汇编语言》系列视频的笔记,是王爽所著的《汇编语言》的简单版,感谢ttps://my.csdn.net/baidu_36313748的,在建议此感谢小甲鱼和像他一样共享资源、帮助他人的筒子们==本文比较长,由于笔者个人能力有限,错漏在所难免,欢迎读者们批评指正。

图文无关

一、基础知识

引言

  • 基本了解硬件系统的结构;
  • 利用硬件系统的编程结构和指令集,有效灵活地控制系统进行工作。

1.1 机器语言

  • 机器语言是机器指令的集合。电子计算机的机器指令是一系列二进制数字。计算机将之转换为一系列高低电平脉冲信号来驱动硬件工作的。

1.2 汇编语言的产生

  • 由于机器语言指令都是由01组成,难以编写,记忆和维护程序.所以汇编语言为了解决这一问题产生。汇编语言的主体是汇编指令,汇编指令是机器指令的助记符。
  • 寄存器: CPU中存储数据的器件,一个CPU中有多个寄存器。

1.3 汇编语言的组成

  • 1、汇编指令(机器码的助记符,有对应的机器码);
  • 2、伪指令(由编译器执行)和其他符号(由编译器识别)。

1.4 存储器

  • CPU工作需要指令和数据,指令和数据存储在存储器中。

1.5 指令和数据

  • 在内存或者磁盘中存储的都是为二进制信息,指令和数据由我们设定(走的总线)

1.6 存储单元

  • 存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号。
  • B、KB、MB、GB、TB等单位。

1.7 CPU对存储器的读写

  • CPU要对数据进行读写,必须和外部器件进行以下三类信息的交互:

    • 1、存储单元的地址(地址信息);
    • 2、器件的选择、读或写命令(控制信息);
    • 3、读或写的数据(数据信息) 。
  • 总线是连接CPU和其他芯片的导线,逻辑上分为地址总线数据总线控制总线

    逻辑上总线的分类

  • CPU从内存单元中读写数据的过程:

    • 1、CPU通过地址线将地址信息发出;
    • 2、CPU通过控制线发出内存读命令,选中存储器芯片,并通知它将要从中读或写数据;
    • 3、存储器将相应的地址单元中的数据通过数据线送入CPU或CPU通过数据线将数据送入相应的内存单元。

1.8 地址总线

  • CPU是通过地址总线指定存储单元,地址总线传送的能力决定了CPU对存储单元的寻址能力。(一般32位CPU,寻址能力为2^32=4G)

1.9 数据总线

  • CPU通过数据总线来与内存等器件进行数据传送,数据总线的宽度决定了CPU和外界的数据传送速度。

1.10 控制总线

  • 控制总线是一些不同控制的集合,CPU通过控制总线对外部器件的控制。控制总线的宽度决定了CPU对外部器件的控制能力。

小结

  • 1、汇编指令时机器指令的助记符,与机器指令一一对应。
  • 2、每一种CPU都有自己的汇编指令集。
  • 3、CPU可以直接使用的信息在存储器中存放。
  • 4、在存储器中指令和数据都是二进制信息。
  • 5、存储单元从0开始顺序编号。
  • 6、一个存储单元可以存储8个bit。
  • 7、B、KB、MB、GB等单位之间的转换。
  • 8、CPU管脚和总线相连。总线的宽度表示CPU不同方面的性能:
    • 地址总线的宽度决定了CPU的寻址能力;
    • 数据总线的宽度决定了CPU与其他器件进行一次数据传送的量;
    • 控制总线宽度决定了CPU对系统中其他器件的控制。

检测点 1.1

检测点1.1

1.11 内存地址空间(概述)

  • CPU可寻的内存单元构成这个CPU的内存地址空间。例如一个CPU的地址总线宽度为10,那么可以寻址的1024个内存单元构成了这个CPU的内存空间。

1.12 主板

  • 主板主板,主要的电路板 :laughing:

1.13 接口卡

  • CPU通过接口卡间接控制外部设备。

1.14 各类存储器

  • 随机存储器RAM(主板上的RAM、拓展插槽上的RAM和接口卡上的RAM)和只读存储器器ROM(装有BIOS的ROM)。
    PC集中各类存储器的逻辑连接

1.15 内存地址空间

  • 各类存储器在物理上是独立的,但是:

    • 1、都和CPU的总线相连;
    • 2、 CPU对他们进行读或写的时候都通过控制线发出的内存读写命令。

    将各类存储器看作一个逻辑存储器

  • 不同的计算机系统的内存地址空间分配情况是不同的。

二、寄存器(CPU的工作原理)

引言

  • CPU由运算器、控制器、寄存器 等器件组成,靠内部总线相连。
  • 内部总线实现CPU内部各器件之间的联系;外部总线实现CPU和主板上其他器件的联系。
  • 在CPU中:
    • 运算器进行信息处理;
    • 寄存器进行信息存储;
    • 控制器控制各种器件进行工作;
    • 内部总线连接各种器件在它们之间进行数据的传送。

2.1 通用寄存器

  • 8086有14个寄存器:
    • AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、CS、ES、PSW
  • AX、BX、CX、DX通常用来存放一般性数据,被称为通用寄存器。
  • 16位寄存器所能存储的数据最大值为2^16^-1 。
  • 为保证兼容性,8086 CPU的通用寄存器可以分为两个独立的8位寄存器使用。例: AX可分为AH和AL。

2.2 字在寄存器中的存储

  • 8086 CPU所有的寄存器是16位,可以存放2个字节(一个字)。

  • 一字节由8 bit 组成,可以存在8位寄存器中。

  • 字(word)是两字节,16位。

    一个字由两个字节组成

2.3 几条汇编指令

  • 汇编指令对大小写不敏感

    汇编指令举例

汇编指令 控制CPU完成的操作 用高级语言的语法描述
mov ax,18 将8送入AX AX=18
mov ah,78 将78送入AH AH=78
add ax,8 将寄存器AX中的数值加上8结果存入AX中 AX=AX+8
mov ax,bx 将寄存器BX中的数据送入寄存器AX AX=BX
add ax,bx 将AX,BX中的内容相加结果存入AX中 AX=AX+BX

检测点 2.1

检测点2.1

2.4 物理地址

  • 所有的内存单元构成一个一维的线性存储空间。
  • CPU访问内存单元时要给出内存单元的唯一地址就是物理地址。

2.5 16位结构的CPU

  • 1、运算器一次最多可以处理16位数据。
  • 2、 寄存器的最大宽度为16位。
  • 3、寄存器和运算器之间的通路是16位。

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

  • 8086有20位的地址总线,可以传送20位地址,寻址能力为1M;但8086内部为16位结构,只能传送16位的地址。
  • 8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。

8086CPU相关部件的逻辑结构

  • 8086CPU读写内存的步骤:
    • 1、CPU中的相关部件提供段子和偏移地址这两个16位的地址;
    • 2、段地址和偏移地址通过内部总线送入到一个称为地址加法器的部件;
    • 3、地址加法器将两个16位地址合并成一个20位的地址;
    • 4、地址加法器通过内部总线将20位物理地址送送入输入输出地址;
    • 5、输入输出控制电路将20位物理地址送上地址总线;
    • 6、20位物理地址被地址总线传送到存储器。
  • 地址加法器工作原理:物理地址=段地址*16+偏移地址。

地址加法器的过程
- 段地址*16就是数据左移4位(二进制)

移位位数 二进制 十六进制 十进制
0 10B 2H 2
1 100B 4H 4
2 1000B 8H 8
3 10000B 10H 16
4 100000B 20H 32
  • 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方。一个数据X进制形式左移N位,相当乘以NX。

2.7 段地址*16+偏移地址=物理地址

  • CPU可以通过不同的段地址和偏移地址形成一个相同的物理地址。

CPU可以通过不同的段地址和偏移地址形成相同的物理地址

段地址*16是移位

2.8 段的概念

  • 人为定义的,将若干地址连续的内存单元看作一个段。用段地址*16定位段的起始地址(基址),用偏移地址定位段中的内存单元。
    段的概念

  • 一个段的起始地址是16的倍数。偏移地址为16位,寻址能力为64K,所以段的最大长度也是64K。

检测点 2.2

检测点2.2

2.9 段寄存器

  • 8086 CPU有4个段寄存器:CS(代码段)、DS(数据段)、SS(堆栈段)、ES(附加段),这4个段提供给8086CPU内存单元的段地址。

2.10 CS和IP

  • CS(代码段寄存器)IP(指令指针寄存器) 是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。在任意时刻CPU将CS:IP指向的内容当作指令执行。
  • 8086CPU工作过程的简要概述:

    • 1、从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;

      8086PC机刚开始启动时,CPU从内存FFFF0h单元中读取指令执行,FFFF0h单元中的指令时8086PC机开机后执行的第一条指令。

    • 2、 IP=IP+所读取指令的长度,从而正确的指向下一条指令;

    • 3、执行指令。转到步骤1,周而复始。

2.11 修改CS、IP的指令

  • mov指令(传送指令) 可以改变8086CPU大部分寄存器的值,但不能用于设置CS、IP的值。
  • jmp指令(转移指令) 可以用来同时修改CS和IP的值,格式为
    jmp 段地址:偏移地址;同时修改CS和IP
    jmp 某一合法寄存器;则是仅修改IP

2.12 代码段

  • 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段。
  • 利用CS:IP来指向内存单元从而让CPU执行其中的内容。

检测点 2.3

检测点2.3

使用Debug

windows xp系统自带debug,请使用xp以上系统的读者执行自行下载debug.exe和dosbox,使用方法笔者不再赘述,在dosbox中可以使用debug。
debug in dosbox

  • 可以使用汇编金手指查阅指令。
  • R命令查看、改变CPU寄存器的内容;
  • D命令查看内存中的内容;
  • E命令改写内存中的内容;
  • U命令将内存中的机器指令翻译成汇编指令;
  • T命令执行一条机器指令;
  • G命令跳转到偏移地址;
  • P命令结束循环或者是int 21H时是退出程序;
  • A命令是以汇编指令的格式在内存中写入一条机器指令。

三、寄存器(内存访问)

3.1 内存中字的存储

  • 字是两个字节,要用两个地址连续的内存来存放,字的低位字节存在低地址中,高位字节存放在高地址单元中。

3.2 DS和[address]

  • DS通常存放要访问的数据的段地址。
  • 8086 CPU由于硬件的设计不支持将数据直接送入段寄存器的操作。

    数据 -> 通用寄存器 -> 段寄存器

  • [ ]里边的数据代表偏移地址值

  • mov指令:
    • 将数据直接送入寄存器;
    • 将一个寄存器或内存单元中的内容送入另一个寄存器;
  • mov指令格式:
mov 寄存器名,内存单元

3.3 字型的传送

  • 高地址单元和高8位寄存器,低地址单元和低8位寄存器相对应。

3.4 mov、add、sub指令

  • 有两个操作对象,jmp只有一个操作对象。
  • 使用汇编金手指查阅指令
  • mov指令的几种形式
mov 寄存器,数据;mov ax,8
mov 寄存器,寄存器;mov ax,bx
mov 寄存器,内存单元;mov ax,[0]
mov 内存单元,寄存器;mov [0],ax
mov 段寄存器,寄存器;mov ds,ax
mov 寄存器,段寄存器;mov ax,ds
     ……
  • add指令的几种形式
add 通用寄存器,数据
add 通用寄存器,通用寄存器
add 通用寄存器,内存单元
add 内存单元,寄存器
  • sub指令的几种形式
sub 通用寄存器,数据
sub 通用寄存器,通用寄存器
sub 通用寄存器,内存单元
sub 内存单元,通用寄存器  

3.5 数据段

  • 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放数据的,从而定义了一个数据段。
  • 可以通过在DS中存放数据段的段地址,用相关的指令访问数据段中的具体单元来访问数据段中的数据。

检测点 3.1

检测点3.1

3.6 栈

  • 具有特殊的访问方式的存储空间,也是内存空间的一部分,数据先进后出。
  • 有两个基本操作:
    • 入栈:将一个新的元素放到栈顶;
    • 出栈:从栈顶取出一个元素。
  • 栈顶元素最后入栈最先出栈。

3.7 8086 CPU提供的栈机制

  • 现今的CPU都有栈的设计,基于8086CPU编程可以将一段内存当作栈来使用。
  • 8086CPU的入栈(PUSH)POP(出栈),以字为单位。
    • push ax 将寄存器ax中的数据送入栈
    • pop ax 从栈顶取出数据送入ax
  • 段寄存器SS存放栈顶的段地址,寄存器SP存放栈顶的偏移地址。任意时刻SS:SP指向栈顶元素。push时SP先自减法后写内存,pop先读内存sp后自加。
  • pop之后数据还是存在内存中,push时覆盖。
    CS和IP存放当前指令的段地址和偏移地址。

3.8 栈顶越界的问题

  • 栈是空的,则SP指向栈底+1的内存。
  • 8086 CPU只纪录栈顶,栈空间由自己控制。栈顶越界问题导致溢出漏洞。
  • 8086CPU只考虑当前的情况:
    • 当前栈顶在何处;
    • 当前要执行的指令时哪一条。

3.9 push、pop指令

  • 可以直接对段寄存器使用。
;pushpop格式
push 寄存器
pop 寄存器
push 段寄存器
pop 段寄存器
push 内存单元
pop 内存单元
  • 通用寄存器命名是x结尾的,段寄存器是以s结尾。
  • CPU在执行指令时,数据的段地址是从DS中获得,代码是在CS中获得,栈地址是从SS获得。

3.10 栈段

  • 对于8086PC机,在编程时可以将长度为N(N小于等于64KB)的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是当作栈来用,从而定义了一个栈段。
  • 寄存器清零可用sub ax,ax或者直接赋值0,常见的也有使用xor
  • 当栈空间定义为最大时,栈为空时SP=0。

检测点 3.2

检测点3.2


四、第一个程序

引言

编写完成的汇编语言程序,用编译器编译成可执行文件并在操作系统中运行。

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

  • 编写
    • 用编辑器(Sublime Text、Nodepad++、UltraEdit)编写,文件后缀为.asm。
  • 编译链接

    • 使用MASM.EXE编译生产obj(目标文件)。masm也请读者自行搜索下载。
    • LINKE.EXE对目标文件进行连接生产可在操作系统中直接运行的可执行文件。

    可执行文件包含程序(机器码)、数据(源程序中定义的数据)和相关的描述信息。

  • 执行

    • 操作系统中依照可执行文件中的描述信息将可执行文件中的机器码和数据加载入内存并进行相关的初始化,然后CPU执行。
  • 4.2 源程序

    • 汇编指令:有对应的机器码的指令,编译为机器码被CPU执行
    • 伪指令:没有对应的机器码,不被CPU所执行,由编译器执行来进行相关的编译工作。
      • segment和ends是用来定义一个段的,是成对使用的伪指令,再写可被编译器编译的汇编程序是必须要用的。
    assume cs:codesg ;假设代码段的名称为codesg
    codesg segment ;定义一个codesg段
    mov ax,0123H
    mov bx,0456H
    add ax,bx
    add ax,ax
    mov ax,4c00h
    int 21h
    codesg ends ;codesg段结束
    end ;是个伪指令,程序的结束标记

    assume用来加上某一段寄存器和程序中的某一用segment……ends定义的段相关联。通过assume说明这种关联,在需要的情况下编译程序可以将段寄存器和某一个具体的段相联系。
    - 一个汇编程序是由多个段组成。一个有意义的汇编程序中至少要用一个段来存放代码。
    - 程序与源程序
    程序与源程序

    • 标号:指代地址
    • 程序的结构

    • 小练习:

    ;编程运算2^3
    assume cs:abc ;段与寄存器关联
    
    abc segment ;定义一个段,名称为abc
    mov ax,2;写入汇编指令
    add ax,ax
    add ax,ax
    
    abd ends      
    end ;程序结束处
    • 程序的返回:一个程序结束后将CPU的控制权交还给使它得以运行的程序的过程。应该在程序的末尾添加返回的程序段。
      codesg:放在segment前面,作为一个段的名称,这个段的名称最终将被编译、连接程序,称为一个段的段地址 。
    mov ax,4c00H
    int 21H ;第21号中断
    ;这两条指令说实现的功能就是程序返回。
    • 语法错误和逻辑错误

    4.3 编辑源程序

    • 使用编辑器编辑,扩展名为.asm
        assume cs:ABC
        ABC segment
            mov ax,2
            add ax,ax
            add ax,ax
            mov ax,4c00H
            int 21h
        ABC ends
        end

    4.4 编译

    • masn和 1.asm在同一目录中,dos下使用masm 1.asm命令即可生产1.obj文件。
      源程序文件和masm文件在同一目录下

      编译源程序

    4.5 连接

    • link 1.obj,生成exe文件,摁enter忽略编译程序提示输入的信息。

      连接程序

      连接生成exe

    • 当源程序很大时,可以将它分成多个源程序文件编译,每个源程序编译成目标文件后再用连接程序将他们连接到一起,生成一个可执行文件。或者程序中调用了某个库文件中的子程序,需要将这个库文件和该目标文件连接到一起,生成一个可执行文件。或者一个源程序编译后得到存有机器码的目标文件,目标文件中的有些内容还不能直接生成可执行文件,连接程序将此内容处理为最终的可执行文件信息。

    4.6 简化编译和连接

    • 使用ml命令,ml 1.asm

    4.7 exe的执行

    • 为兼容16位的程序,使用dosbox运行。

    4.8 可执行文件中的程序转入内存并运行的原理

    • 在dos中可执行文件中的程序p1若要运行吗必须有一个正在运行的程序p2将p1从可执行文件中加载如内存,将CPU的控制权交给它,p1才能得以运行;当p1运行完毕后,应该将CPU的控制权交还给使它de’yi 运行的程序p2。
    • 汇编程序从写出到执行的过程:编程 -> 编译 -> 连接 -> 加载 -> 内存中的程序 -> 运行
    • 在dos系统中.exe文件中的加载过程

      exe文件中程序的加载过程

      psp的内容

    4.9 程序执行过程的跟踪

    • 使用debug(xp以上的系统在dosbox中使用)来跟踪一个程序的运行过程。

    使用debug来跟踪程序运行


    五、[BX]和loop指令

    引言

    • 约定符号()来表示一个寄存器或者一个内存单元中的内容。例如(ax)=0010H表示ax中的内容为0010H;(21000H)=0010H,表示2000:1000处的内容为0010H。
    • 约定符号idata表示常量。

    5.1 [BX]

    • inc指令是自增1的意思
    • 和[0]有些类似,[0]表示内存单元,它的偏移地址是0。[bx]也是表示一个内存单元,它的内存偏移地址在bx中。
    mov bx,0
    mov ax,[bx]
    mov al,[bx]
    • 用以下两种信息描述一个内存单元:
      • 1、内存单元的地址;
      • 2、内训单元的长度(类型)。
        我们用[0]表示一个内训单元时,0表示单元的偏移地址,段地址默认在DS中,单元的长度(类型)可以由具体指令中的其他的操作对象(比如说寄存器)指出。
    mov ax,[0];0对应的字单元,主要单位要看操作对象(寄存器)
    mov al,[0];字节

    5.2 loop指令

    • 指令的格式是loop 标号。CUP执行loop指令时要进两步操作:
      • CX中存放循环的次数,执行时CX中的内容自减1。相当于C的do while
      • 判断CX中的值,不为0则转至标号处执行程序,为0则向下执行。
    • 通常loop指令来实现循坏功能CX中存放循环的次数。
    assume cs:code
           code segment
           mov ax,2
           add ax,ax
    
           mov ax,4c00H
           int 21H
    code ends
    end
    ;计算2^3
    assume cs:code
    code segment
         mov ax,2
         add ax,ax
         add,ax,ax
    
         mov ax,4c00H
         int 21h
    code ends
    end
    ;计算2^12
    assume cs:code
    code segment
    start:  mov ax,2
            mov cx,11
          p:add,ax,ax       
            loop p;p是标号
    
           mov ax,4c00H;masm默认数字是十进制
           int 21H
    code ends
    end start
    ;编程计算123*236,结果放在ax中
    assume cs:code
    code segment
    start:mov ax,0
          mov cx,236
       an:add ax,123
         loop an
          mov ax,4c00H
          int 21H
    code ends
    end start
    assume cs:code
    code segment
    start:mov ax,0
          mov cx,123
      pa:add ax,236
          loop pa
    
          mov ax,4c00H
          int 21H
    
    code ends
    end start

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

    • 注意:在汇编源程序中数据不能以字母开头,有字母的在前面加0处理。
    • t命令单步执行、G命令和P命令。
    • 使用汇编金手指查阅指令。

    5.4 Debug和汇编编译器Masm对指令的不同处理

    • Degug中mov ax,[0],表示将ds:0处的数据存入al中。ah=0,因为一个内存单元是8位的,ax是16位的,同位存储。而编译器[0]会被当作0处理
    • 将内存2000:0、2000:1、2000:2、2000:3单元中的数据(字节)送入阿al、bl、cl、dl中。

      • debug中:

      debug 1

      debug 3

      • 在MASM中:

      1masmtest

      masmtest2

      • 要在编译器中实现用偏移地址[]中的内容传送先bx来代替,mov 偏移地址,bx 再 mov al,[bx]。如要直接使用[ ]则要加上段地址ds:[偏移地址]
    • 在MASM中:
    mov al,[0] ;将al赋值0
    mov al,ds[0] ;将al赋值段地址为ds,偏移地址为0的内存单元中的内容
    mov al,[bx] ;默认段地址为ds,将al赋值偏移地址为bx
    mov al,ds:[bx] ;将al赋值段地址为ds,偏移地址为bx

    5.5 loop和[BX]的联合应用

    • 可以用循环来解决处理地址连续的内存单元中的数据的问题,用变量来给出内存单元的地址。

    5.6 段前缀

    • 出现在访问内存单元的指令中用显式地指明内存单元的段地址的ds、cs、ss、es称为段前缀。没有显式地给出内存单元的段地址则默认在ds中。

    5.7 一段安全的空间

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

    assume cs:code
    code segment
        mov ax,0
        mov ds,ax
        mov ds:[26H],ax
        mov ax,4c00H
        int 21H
    code ends
    end
    • 但笔者在练习的时候出现dosbox下debug卡死

      dangeroustest

    • dos下0:200H~0:2FFH的256个字节的空间是安全的,dos和其他合法程序一般都不会使用这段空间。内存0000:0000~0000:03FF大小为1kb的空间是系统存放中断处理程序入口地址的中断向量表。一般情况下0:200H~0:2FFH的256个字节的空间所对应的中断向量表都是空的,操作系统和其他应用程序都不占用。
      dos安全空间

    5.8 段前缀的使用

    • 将内存ffff:0~ffff:b段单元中的数据拷贝到0:200 ~ 0:20b单元中
    assume cs:code
    code segment
              mov bx,0 ;(bx)=0,偏移地址从0开始
              mov cx,12 ;(cx)=12,循环12次
          s:  mov ax,offffh
              mov ds,ax ;(ds)=0ffffh
              mov dl,[bx] ;(ds)=((ds)*16+(bx)),将ffff:bx中的数据送入dl
              mov ax,0020h
              mov ds,ax ;(ds)=0020h
              mov [bx],dl ;((ds)*16+(bx))=dl,将数据送入0020:bx
              inc bx ;(bx)=(bx)+1
              loop s
    
              mov ax,4c00h
              int 21h
    code ends
    end
    • 两个内存单元相差64KB则不再同一个段里,需要设置ds的值两次,效率不高。
    • 使用 es(附加段)
    ;优化后的代码,优化了两次设置ds
    assume cs:code
    code segment
               mov ax,offffh
               mov ds,ax ;(ds)=0ffffh
               mov ax,0020h
               mov es,ax ;(es)=0020H
               mov bx,0 ;(bx)=0,此时ds:bx指向ffff:0,es:bx指向0020:0
               mov cx,12 ;(cx)=12,循环12次
           s:  mov dl,[bx] ;(ds)=((ds)*16+(bx)),将ffff:bx中的数据送入dl
               mov es:[bx],dl ;((es)*16+(bx))=dl,将数据送入0020:bx
               inc bx ;(bx)=(bx)+1
               loop s
    
               mov ax,4c00h
               int 21h
    code ends
    end

    六、包含多个段的程序

    6.1在代码段中使用数据

    • 编程计算0123H、0456H,0abxH、0defH、0fesH、0cbaH、0987H这8个数据的和,结果存放在ax中:
    assume cs:codesg
    codesg segment
            dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
            ;dw,define word,定义字型数据,db定义字节型数据
            ;由于数据在代码段中,所以段地址是CS
            ;dw定义的数据在最开始的地方,所以偏移地址是0开始
        start:mov bx,0 ;第一条指令
            mov ax,0
            mov cx,8
        s:      add ax,cs:[bx]
            add bx,2
            loop s
            mov ax,4c00H
            int 21H
    codesg ends
    end start ;入口找end

    定义字型数据

    • end的作用除了通知编译器结束之外还有告诉编译器程序的入口在什么地方。
    • 可执行文件中的程序执行过程

    6.2 在代码段中使用栈

    • 利用栈编程将定义的数据逆序(联想栈的特性)存放:dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
    assume cs:codesg
    codesg segment
            dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H;地址0~15
            dw 0,0,0,0,0,0,0,0;定义8个字型空数据,后面当作栈来使用,地址是16~31
    
        start:  
                mov ax,cs
                mov ss,ax
                mov sp,32;设置栈底ss:sp指向cs:32,十进制的32
                mov bx,0
                mov cx,8
               s:push cs:[bx]
                add bx,2
                loop s; 以上代码段0~15个单元中的8个字型数据一次入栈
    
                mov bx,0
                mov cx,8
              s0:pop cs:[bx]
                add bx,2
                loop s0;依次出栈8个执行数据到代码段0~15单元中
    
                mov ax,4c00h
                int 21h
    codesg ends
    end start;指明程序入口在start处
    • 如果对此程序的栈有疑惑,跳转到 3.6 栈和3.10 栈段

    6.3 将数据、代码、栈放入不同的段

    • 在8086CPU中数据、栈和代码存储空间不能大于64KB。可以用像定义代码段一样的方法来定义多个段并在其中定义需要的数据,或者通过定义数据来取得栈空间。
    assume cs:codesg,ds:data,ss:stack;在源程序中为三个段进行有意义的名称
    data segment
            dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
    data ends
    
    stack segment
            dw 0,0,0,0,0,0,0,0;定义8个字型空数据,后面当作栈来使用
    stack ends
    
    code segment
        start:      
                mov ax,stack
                mov ss,ax
                mov sp,16;设置栈底ss:sp指向stack:16,
                mov ax,data
                mov ds,ax;ds指向data段
                mov bx,0;ds:bx指向data段中的第一个单元
               s:push cs:[bx]
                add bx,2
                loop s; 以上代码段0~16个单元中的8个字型数据一次入栈
    
                mov bx,0
                mov cx,8
              s0:pop cs:[bx]
                add bx,2
                loop s0;依次出栈8个执行数据到代码段0~16单元中
    
                mov ax,4c00h
                int 21h
    codesg ends
    end start;指明程序入口在start处
    • 程序中指令决定了断中的内容是作为数据处理还是作为指令执行还是作为栈空间使用。

    检测点 6.1

    检测点6.1

    实验五

    assume cs:codesg,ds:data,ss:stack
    data segment
            dw 0123H,0564H,0789H,0abcH,0defH,0fedH,0cbaH,0987H
    data ends
    
    stack segment
            dw 0,0,0,0,0,0,0,0
    stack ends
    
    codesg segment
        start:  mov ax,stack
                mov ss,ax
                mov sp,16
                mov ax,data
                mov ds,ax
                push ds:[0]
                push ds:[2]
                pop ds:[2]
                pop ds:[0]
    
                mov ax,4c00h
                int 21h
    codesg ends
    end start

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

    7.1 and和or指令

    • and指令:逻辑与指令,按位进行与运算。
      and两个同时为真的结果才为真。
    mov al,01100011B
    and al,00111011B
    ;执行后 al=00100011B
    • 可用and指令将操作对象的相应位设为0,其他位不变
    and al,10111111B;将al第六位设为0
    and al,01111111B;将al第七位设为0
    and al,11111110B;将al第0位设为0
    • or指令:逻辑或指令,按位进行或运算。
    mov al,01100011B
    and al,00111011B
    ;执行后 al=01111011B
    • 可用or指令将操作对象的相应位设为1,其他位不变
    and al,01000000B;将al第六位设为1
    and al,10000000B;将al第七位设为1
    and al,00000001B;将al第0位设为1

    7.2 关于ASCII码

    ASCII码表

    • 将字符的ascii码写入显存屏幕就显示出相关的字符。

    7.3 以字符形式给出数据

    • 用‘’的方式指明数据是以字符的形式给出的。例如’A’
    assume cs:code,ds:data
    
    data segment
            db 'unIx'
            db 'foRK'
    data ends
    
        code segment
        start:  mov al,'a'
                mov bx,'b'
    
                mov ax,4c00h
                int 21h
    code ends
    end start

评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值