第三章:汇编语言程序设计(一)

简介

  1. 汇编语言的特点:
    1. 汇编语言是一种用符号表示的,面向CPU指令系统的程序设计语言。
    2. 汇编语言对机器的依赖性很大,每种机器都有他专用的汇编语言。
    3. 用户必须对机器的硬件及软件资源有足够的了解才能设计汇编语言的程序。
  2. 汇编语言的编译过程
    1. 源程序: 用汇编语言编写的程序称为源程序,文件的扩展名为“.asm”.
      1. 可以用各种文件编辑软件编写ASM文件,该文件为ASCII码文件。
    2. 汇编: 用汇编语言编写的源程序在交付计算机执行之前,需要翻译成机器可直接识别和运行的二进制代码形式,这个翻译过程称为汇编,完成汇编任务的程序称为汇编程序
      1. 常用的汇编程序为MASM.EXE,此文件的作用是将ASCII码的源程序转换成用二进制代码表示的目标程序,也称为OBJ文件
      2. 在这个转换过程中,如果源程序中有语法错误,汇编程序将指出源程序中错误的位置及种类,提示用户修改源程序,知道源程序中没有语法错误,才能生成OBJ文件。
    3. 可执行程序(.EXE)
      1. CPU仍然不能运行由汇编程序汇编后生成的目标程序(OBJ)文件,必须经过连接程序(LINK.EXE)连接后,才能形成可执行程序(.EXE)
      2. 因为目标程序文件中的有些指令的地址还未完全确定,另外可能需要将多个目标程序组合成一个功能更强的程序,或要和某些高级语言的目标程序进行连接,这些都需要由连接程序连接后才能形成可执行程序。
    4. 整个过程如下:

      图1

  3. 汇编语言语句分类
    1. 指令语句: 就是前面学习的指令系统
    2. 伪指令语句:
      1. 伪指令语句不产生机器语言目标代码,而是指示、引到汇编程序在汇编过程中做一些操作,例如界定段的起始位置,为数据分配合适的存储单元
      2. 伪指令是在汇编程序运行时执行,在汇编阶段已经全部完成。
      3. 在目标程序中,不存在伪指令语句。
    3. 宏指令语句
      1. 宏指令是用户按照一定的规则自己设计的指令,是若干指令的集合,也就是用一条宏指令语句代替若干条指令语句,使用宏指令语句的目的主要是为了简化源程序
      2. 在汇编程序汇编时,还是将宏指令语句还原会指令语句的形式,转换成一条条机器目标代码的形式。
      3. 支持宏指令语句的汇编程序称为“宏汇编程序”,MASM.EXE就是宏汇编程序。

汇编语言程序的结构

汇编语言程序生成的可执行文件的结构

  1. 汇编语言源程序(.ASM文件)经过汇编和连接后,通常生成EXE结构的可执行程序(扩展名为.EXE的文件),改文件由操作系统装入内存后才能运行。
  2. EXE文件可以包含多个段,如代码段、数据段、堆栈段等,还可以有多个逻辑代码段和数据段。
  3. 其在内存的具体结构如图所示:
    图1
    1. 用户程序区占用内存的一部分,各个逻辑段的基地址一般是由系统指定的,可重装定位
    2. EXE程序由文件头和程序本身的二进制代码两部分组成。
    3. 文件头也称为程序段前缀(PSP),占用256个字节,其中的信息时DOS装载可执行文件时自动生成的。
    4. DOS通过PSP向用户程序传递参数,为用户程序提供正常结束和异常结束时返回DOS的路径。。。。,总之,DOS是通过PSP管理用户程序的。
    5. 当程序结束返回DOS后,用户程序和他的程序段前缀所占用的空间均被释放,交换DOS另行分配。
    6. DOS自动给DS,ES,SS和CS赋值,使得DS = ES = 存放PSP的基地址,CS:IP = 用户程序的启动地址,SS:SP指向堆栈的栈顶,在这以后,DOS才把控制权交给用户程序。

汇编源程序的结构

8086/8088汇编语言源程序采用分段结构,一个完整的源程序可以包含多个逻辑段,如数据段、堆栈段和代码段等。 常用的伪指令如下:

  1. 段定义伪指令
    1. 汇编语言程序是按段来组织程序和使用存储器的。
    2. 段定义伪指令的作用就是在汇编语言程序中定义逻辑段,指明段的名称、范围、属性等相关信息
    3. 说明逻辑段的起始和结束
    4. 说明不同程序模块中同类逻辑段之间的联系形态
    5. 格式:

       段名 SEGMENT <定位类型> <组合类型> <类别>
           ···
           段体
           ···
       段名 ENDS
      
      1. SEGMENT:表示逻辑段的开始,ENDS则表示逻辑段的结束,两者必须成对出现,切段名一致
      2. 段名:
        1. 表示这个逻辑段的段基地址
        2. 命名规则符合汇编语言规则的合法标识符(具体规则,查课本)
      3. 定位类型(详情请看课本)
        1. 用来表示逻辑段的起点
        2. 默认情况下都是以*节边界起始的,所谓的节:一节是16个单元,也就是说每一个逻辑段的起始地址都要被16整除,也就是为什么段首地址的后四位是0。
      4. 组合类型:
        1. 当装入内存时,不同的逻辑段在内存中的组合方式,默认情况下是不组合的
        2. 即用来指示段和段之间是怎样连接和定位的。
      5. 类别
        1. 是用单引号括起来的字符串,连接程序把类别相同的段放在连续的存储区中(可以不同名)
        2. 类别名是用户选定的,但一般为“CODE”,”STACK”,”DATA”等,表明该段的类型。
  2. 段寻址伪指令 ASSUME(重点:详细的说明了段寄存器、段基址之间的关系!!!!!!!
    1. 定义了多个逻辑段(多个数据段、多个代码段等),但是CPU的段寄存器只有4个(各一类),所以,段寄存器中应该放哪个逻辑段的段基址呢?
    2. 该指令作用是:
      1. 即将哪个逻辑段的段起始地址(段基址)装入段寄存器,即将哪个逻辑段设置为当前段。
      2. 格式:

         ASSUME 段寄存器名 : 段名 <,······,段寄存器名:段名>
        
        1. 段寄存器名
          1. 段寄存器CS/DS/ES/SS中的一个,段名是用SEGMENT/ENDS伪指令语句定义的段名;
          2. 注意: ASSUME伪指令只能使汇编程序知道程序中段名和段寄存器的对应关系,指定了某逻辑段应通过哪一个段寄存器去寻址,但是ASSUME伪指令并不会给段寄存器赋值
          3. 说白了,也就是说,ASSUME只是告诉汇编程序:CPU的段寄存器(假设数据段DS)对应的段基址,是内存中(此时内存中有多个数据段)哪一个逻辑段的段基址。
        2. 欲将段基址装入相应的段寄存器,必须通过指令手动设置。
          1. 注意了,这里跟之前讲的段基址的初始化只能是操作系统,程序员不能控制的观点不冲突。源程序是由多个逻辑段组成的,当程序运行时,操作系统将程序加载到内存,根据程序中的各个逻辑段分别分配到内存的相应区域的段中存储,而这些逻辑段在内存中的段基地址都是由操作系统给定的,程序员无法控制。
          2. 在4个段寄存器中,CS是特殊的,不需要用户专门处理,是由系统或在执行转移指令时自动完成对CS的赋值;
          3. SS有2种赋值方式
            1. 一种是系统自动赋值,即在堆栈段定义语句中选择组合类型的参数为“STACK”,则汇编时该段的段基址自动装入SS中,同时堆栈指针SP也自动赋值
            2. 另一种是用户用MOV语句编程赋值,同时也要对堆栈指针SP编程赋值。(知识整合里面的例子中用了这种方法)
          4. DS和ES只能由用户用MOV语句编程赋值
            1. 注意前面讲了DOS系统会再加载程序后给DS/ES初始化一个值,但是那只是初始化,实际对应的值还是需要程序员自己设置。
  3. 过程定义伪指令 PROC/ENDP
    1. 在汇编语言程序设计中,常常把具有一定功能的程序段设计成一个过程,也称为子程序。
    2. 可以将一些常用的操作或计算定义为过程,供主程序(主过程)调用,这样,可以避免重复编程,同时使程序结构清晰,便于阅读、修改。
    3. 格式;

       过程名 PROC <类型>
           ···
           过程体语句
           ···
           RET
       过程名 ENDP
      
      1. 过程名是用户为过程所起的名字,必须是合法的标识符,且过程的开始(PROC)和结束(ENDP)应使用同一个过程名,同时,过程名也是过程入口的符号地址。
      2. 过程的类型有两种:
        1. NEAR:的过程名仅供段内调用,是过程类型的默认值
        2. FAR:的过程可供段间调用。
      3. 在一个过程中,至少要有一条过程返回指令RET,RET指令可以在过程的任何位置,但一个过程执行的最后一条指令一定是RET指令
  4. 汇编结束伪指令 END
    1. 任何一个汇编源程序都必须用结束伪指令END作为源程序的最后一条语句。
    2. 格式:

       END 地址表达式
      
      1. 地址表达式通常是一个已定义的语句标号,表示程序执行时的启动地址。
  5. 返回DOS系统的方式(要理解!!!
    1. 汇编语言源程序经汇编和连接后成为可执行程序,即可在DOS环境下运行
    2. 当前连接程序目标程序进行连接和定位时,操作系统为每一个用户程序建立一个程序段前缀(PSP),在程序段前缀的开始处(偏移地址0000H)安排了一条INT 20H的中断指令,其中中断服务程序的功能就是将计算机的控制权交还给DOS
    3. 当用户要运行程序时,DOS建立了一个程序段前缀PSP后,将当前要执行的程序从外存调入内存,在定位程序时,将代码段置于PSP的下方,其后放置数据段,最后放置堆栈段。
    4. 内存分配好后,将DS和ES的值指向PSP的开始处(如上面的内存图),即INT 20H存放的地址,同时将CS设置为PSP后面代码段的段基址,IP设置为代码段执行的第1条指令处的偏移地址,将SS设置为堆栈段的段基地址,SP指向堆栈段的栈底
    5. 然后系统开始执行用户程序。
    6. 用户程序执行完返回DOS的方法有两种
      1. 标准程序段前缀
        1. 首先将主程序定义成一个FAR过程,最后一条指令为RET。然后在主过程一开始执行时,首先执行以下命令:

           PUSH DS
           XOR AX,AX
           PUSH AX
          
          1. 将PSP开始出的INT 20H指令的段基址(DS)以及偏移地址0000H压入堆栈。
          2. 这样,当程序运行完成,执行到RET指令时,就将程序最早存放在堆栈中的PSP的段基址和偏移地址弹出赋值给CS 和IP,实际上将程序转移到PSP的开始处,此时计算机执行INT 20H指令,这样就将计算机的控制权交还给DOS
      2. 用户DOS系统功能调用4CH
        1. 在用户程序结束后插入一下两条语句:

           MOV AH,4CH
           INT 21H
          
        2. 这是最常用的一种方法,尤其是主程序不用过程的形式编写时,通常采用这种方法

  6. 汇编语言源程序结构示例

     DAT SEGMENT                             ; 数据段开始
       x DB  6                               ; 定义数据变量X 和Y
       Y DB  8
     DAT ENDS                                ; 数据段结束
        
     STA SEGMENT STACK `STACK`               ; 堆栈段开始,其中组合类型STACK一定要有
       DB  100 DUP(?)                        ; 定义堆栈段的大小为100个字节
     STA ENDS                                ; 堆栈段结束
        
     CODE1 SEGMENT                           ; 代码段开始   
       ASSUME CS: CODE1, DS:DAT, SS:STACK    ; 段寻址
     BEGIN PROC FAR                          ; 主过程开始
           PUSH DS                           ; 将PSP首址压栈,以便于程序执行完毕后返回DOS
           XOR AX, AX
           PUSH AX
           MOV AX, DAT                       ;将数据段的首地址赋给数据段寄存器DS
           MOV DS, AX
           ···                               ;具体程序
           RET                               ;过程结束前的最后一条指令,程序运行INT 20H 返回DOS
     BEGIN ENDP                              ;过程结束
     CODE1 ENDS                              ;代码段结束
     END BEGIN                               ;源程序结束,BEGIN是程序第一条指令的地址。
    
Table of Contents