Skip to content

汇编语言程序设计笔记

1 汇编语言基础知识

本章是概念性章节,内容较少,直接列一个概念列表:

  • 由冯 · 诺伊曼设计思想的计算机由五大部件组成:运算器、控制器、存储器、输入设备、输出设备。
  • 数制:二进制、八进制、十进制、十六进制
  • 数制转换:低转高,乘权法;高转低,除基取余法。
  • 数值的编码(计组的噩梦之一):定点整数、原码、反码、补码、定点小数、浮点数。
  • 数值编码:BCD码、ASCII码、Unicode编码。
  • 寄存器:通用寄存器、段寄存器、指针寄存器、变址寄存器、控制寄存器、状态寄存器。
  • 通用寄存器:数据寄存器(AX、BX、CX、DX)、指针寄存器(SP、BP)、变址寄存器(SI、DI)。
  • 标志寄存器:状态标志(CF、PF、AF、ZF、SF)、控制标志(TF -> 单步标志、IF、DF、OF)、系统标志(NT、IOPL、VM、RF)
  • 存储格式:
    • 数据的存储格式:小端方式(8086 采用的方式),低对低、高对高;大端方式:低对高、高对低。
    • 同一个地址既可以看作字节单元的地址,也可以看作字单元的地址,还可以看作双字单元的地址。
  • 存储器的分段管理:8086 把存储器分成若干段,每段的大小最大为 64KB,每个存储单元可表示为“段基地址 :偏移地址”。段基地址是段的起始地址除以 16 后的值,即必须形如“XXXX0H”,偏移地址是段内偏移量(也称有效地址,EA),可以用 16 位表示。
  • 段寄存器的作用:

    • CS:代码段寄存器,存放代码段的段基地址。指令寄存器 IP 存放指令的偏移地址,处理器利用 “CS:IP” 访问下一条指令。
    • SS:堆栈段寄存器,存放堆栈段的段基地址。堆栈指针 SP 存放栈顶的偏移地址,处理器利用 “SS:SP” 访问堆栈段。
    • DS:数据段寄存器,存放数据段的段基地址。
    • ES:附加段寄存器,附加的数据段,也用于数据的保存。
  • (重要)寻址方式

    • 立即数寻址(立即寻址):指令中直接给出操作数。
    • 寄存器寻址:指令中直接给出寄存器名。
    • 储存器寻址:直接寻址(指令中直接包含了有效地址,如:mov ax, [1000H]);寄存器间接寻址(指令中给出寄存器名,寄存器中存放有效地址,如:mov ax, [bx]);寄存器相对寻址(指令中给出寄存器名和偏移量,寄存器中存放基地址,如:mov ax, [bx+10H]);基址变址寻址(指令中给出基址寄存器、变址寄存器和偏移量,如:mov ax, [bx+si]);相对基址变址寻址(指令中给出基址寄存器、变址寄存器、偏移量和段寄存器,如:mov ax, [bx+si+10H])。
  • 隐含寻址方式
  • 比例因子是386及其后继机型新增加的寻址方式中的一个术语,其值可为1,2,4或8。比例因子(1、2、4、8)只能与变址寄存器同时使用。

  • 外设中,每个接口包括一组寄存器: 数据寄存器 状态寄存器 命令寄存器

  • 输入端口的 8 位数据
    1
    2
    mov  dx,1234h
    in   al,dx
    

2 8086 的指令系统

各种指令

  • 数据传送指令:
    • mov(reg/mem, imm; reg/mem/seg, reg; reg/seg, mem; reg/mem, seg)
    • xchg(reg ↔ reg/mem 或 reg/mem ↔ reg)、xlat(al ← ds:[bx+al]);
  • 堆栈操作指令:push、pop(注意 push 的时候地址减少,pop 的时候地址增加);
  • 标志传送指令:lahf、sahf、pushf、popf;
  • 地址传送指令:lea、lds(Load Pointer Using DS)、les(Load Pointer Using ES);
  • 算术运算指令:add、adc、inc、sub、sbb、dec、cmp
    • neg:用 0 减去操作数(会导致 CF=1),结果相当于按位取反加 1
    • mul r8(16)/m8(16),
      • ax ← al(x) * r8(16)/m8(16)
    • imul r8(16)/m8(16)
      • ax ← al(x) * r8(16)/m8(16)
    • div r8(16)/m8(16)
      • al(ax) ← ax(dx.ax) 除 r8(16)/m8(16) 的商
      • ah(dx) ← ax(dx.ax) 除 r8(16)/m8(16) 的余数
    • idiv
  • 符号拓展指令:cbw(拓展到 AX)、cwd(拓展到 DX);
  • 逻辑运算指令
    • 影响标志位:and、or、xor、test、neg
    • 不影响标志位:not
    • 除了 NOT 不影响标志位外,其他所有逻辑运算指令执行完后,OF 和 CF 一定是 0
  • 移位指令:shl、shr、sal、sar、rol、ror、rcl、rcr;
  • 控制转移指令:jmp,太多了,可查 55 页表 2-3。
  • 循环指令:loop、loopz、loopnz;
  • 子程序指令:call(调用子程序)、ret(从子程序返回主程序)
  • 中断指令:int(内部中断)、iret(外部中断);

状态标志

  • CF:进位标志,无符号数运算时,是否溢出,即最高位是否有进位或借位。检测最高有效位(MSB,Most Significant Bit)是否产生了进位或借位
  • OF:溢出标志,有符号数运算时,是否溢出,运算结果是否超出范围。检测最高有效位(MSB,符号位) 和 次高位的进位 是否不一致
  • CF 和 OF 的区别:CF 表示无符号整数运算结果是否超出范围,超出范围后加上进位或借位运算结果依然正确;OF 表示有符号整数运算结果是否超出范围,超出范围后加上进位或借位运算结果不正确
  • SF:符号标志,结果的最高位。
  • ZF:零标志,结果是否为 0。
  • PF:奇偶标志,运算结果低八位中 1 的个数为 0 或者偶数时,PF=1;否则 PF=0。
  • 判断是否影响标志位原则:是否发生了算数、测试、比较、移位,发生了往往是影响标志位的
  • 特殊指令 INCDEC 不会影响 CF
  • stx、clx 类是置位,cmc 是对 CF 的取反

算术指令

  • 加法指令 ADD、ADC、INC
  • 减法指令 SUB、SBB、DEC、NEG、CMP
  • 乘法指令 MUL、IMUL
  • 除法指令 DIV、IDIV
  • add bx,es 是错误的

符号拓展

对于无符号数,高位置 0 即可拓展;对于有符号数,分别用 CBW 或 CWD 指令进行符号拓展。

十进制调整指令

为了实现在十六进制下进行十进制运算而进行的,比如 68h + 28h = 90h,执行 daa 后就会变成 96h,即十进制调整。

类似还有减法调整指令,das。还分压缩 BCD 码和非压缩 BCD 码,区别是前者用半个字节表示一个十进制位,而后者用一个字节表示一个十进制位。

位操作指令

  • 逻辑运算指令 AND、OR、XOR、NOT、TEST(AND)
  • 移位指令 SHL、SHR、SAL、SAR、ROL、ROR、RCL、RCR

转移类指令

  • 无条件转移指令 JMP
  • 利用 ZF:JZ、JE、JNZ、JNE
  • 利用 SF:JS、JNS
  • 利用 OF:JO、JNO
  • 利用 PF:JP、JPE、JNP、JPO
  • 利用 CF:JC、JB、JNAE、JNC、JNB、JAE
  • 比较无符号数时用 Above 和 Blow,而有符号数时用 Greater 和 Less。
  • 循环指令:LOOP(CX!=0)、LOOPZ/LOOPE(CX!=0 && ZF=1)、LOOPNZ/LOOPNE(CX!=0 && ZF=0)、JCXZ(CX!=0)

子程序指令

CALL、RET

中断指令

  • 中断类型

    • 外部中断
      • 可屏蔽中断
      • 非屏蔽中断
    • 内部中断(亦称“异常”)
    • 除法错中断
    • 指令中断
    • 溢出中断
    • 单步中断
  • 中断过程

    • 中断向量表
      • 每个中断的起始地址是 4 的倍数
      • 每个中断共两个字,低字存放中断服务程序的偏移地址 IP,高字存放段地址 CS
    • 内存的地址区域从 00000H 开始为中断向量表

3 汇编语言程序格式

汇编语言程序的开发

  • 标号规定

    • 最长 31 个字符
    • 第一个字符不能是数字
    • ? $ _ @可出现在标号的任意位置,但? $不能单独使用
    • . 只能出现在起始位置
    • 一个程序中,每个标识符的定义是唯一的,且不能与任何保留字相同
    • 标号有三种属性:段、偏移量和类型
  • 简化段定义

1
2
3
4
5
6
7
8
9
.model small
.stack
.data
    ; 数据定义
.code   
    .startup
    ; 代码定义
    .exit 0
end
  • 完整段定义
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
stack segment stack
    ; 堆栈段定义
stack ends
data segment
    ; 数据定义
data ends
code segment
    assume cs:code, ds:data, ss:stack
start: ; 程序入口
    ; 程序代码    
    mov ax, 4c00h
    int 21h
code ends
    end start

可执行文件的开发过程

  • 主程序目录

    • ml.exe,汇编程序
    • ml.err,汇编错误信息文件
    • link.exe,连接程序
    • lib.exe,子程序库管理文件
  • DOS 系统功能调用表

子功能号 功能 入口参数 出口参数
AH=01H 从标准输入设备输入一个字符 AL=输入字符的ASCII码
AH=02H 向标准输出设备输出一个字符 DL=字符的ASCII码
AH=09H 向标准输出设备输出一个字符串 DS:DX=字符串地址
AH=0AH 从标准输入设备输入一个字符串 DS:DX=缓冲区地址
AH=0BH 判断键盘是否有键按下 AL=0,无;AL=FFH,有
AH=4CH 程序执行终止 AL=返回代码

参数、变量、标号

  • 运算符
运算符类型 运算符号及说明
算术运算符 + (加), - (减), * (乘), / (除), MOD (取余)
逻辑运算符 AND (与), OR (或), XOR (异或), NOT (非)
移位运算符 SHL (逻辑左移), SHR (逻辑右移)
关系运算符 EQ (相等), NE (不相等), GT (大于), LT (小于), GE (大于等于), LE (小于等于)
高低分离符 HIGH (高字节), LOW (低字节), HIGHWORD (高字), LOWWORD (低字)
  • 变量定义伪指令

    • DB、DW、DD、DF、DQ、DT:字节、字、双字、三字、四字、十字
    • ORG:使它后面的数据或指令从参数指定的地址开始
    • EVEN:使它后面的数据或指令从偶地址开始
    • ALIGN \(n\):使它后面的数据或指令从 \(n\) 的整数倍地址开始
  • 伪操作:汇编程序对源程序进行汇编时处理的操作,完成处理器选择、存储模式定义、数据定义、存储器分配、指示程序开始结束等功能

伪操作名 格式 功能
EQU 名字 EQU 表达式 给名字赋值
= 名字 = 表达式 同上,但允许重复赋值
LABEL 名字 LABEL 表达式 定义变量或标号的类型
  • LABEL 的使用示例:
1
2
AGAINF LABEL FAR
AGAIN: PUSH BX
  • sizelength
    • length 对于变量定义使用 DUP 的情况,返回分配给其的元素数,其他情况返回 1,因此注意对于数组,length 并不等于数组的长度
    • size 满足 \(size=length\times type\),其实代表的就是字节数量

4 基本汇编语言程序设计

循环程序设计

  • 循环程序的三部分:
    • 循环初始部分
    • 循环体部分
    • 循环控制部分

串操作类指令

  • 源操作数用寄存器 SI 间接寻址,默认在数据段 DS 中,即 DS:[SI],允许段超越
  • 目的操作数用寄存器 DI 间接寻址,默认在数据段 ES 中,即 ES:[DI],不允许段超越
  • 每执行一次串操作,源指针 SI 和目的指针 DI 都自动递增或递减

  • 串传送

    • MOVSB es:[di] ← ds:[si], si ← si+1, di ← di+1
    • MOVSW es:[di] ← ds:[si], si ← si+2, di ← di+2
    • 常用重复前缀:REP
  • 串存储
    • STOSB es:[di] ← al, di ← di+1
    • STOSW es:[di] ← ax, di ← di+2
    • 常用重复前缀:REP
  • 串读取
    • LODSB al, ds:[si], si ← si+1
    • LODSW ax, ds:[si], si ← si+2
    • 常用重复前缀:REP
  • 串比较(CMPS)
    • CMPSB ds:[si]-es:[di], si+1, di+1
    • CMPSW ds:[si]-es:[di], si+2, di+2
  • 串扫描(SCAS,用 ALAX 的内容减去目的数据串,以比较两者的关系)
    • SCASB ; al-es:[di], di ← di\(\pm\)1
    • SCASW ; ax-es:[di], di ← di\(\pm\)2
  • CMPS 和 SCAS 都可以配合 REPE/REPZ/REPNE/REPNZ 前缀使用,通过 ZF 标志判断两数是否相等

子程序的参数传递

寄存器传递、变量传递、地址表、栈传递

子程序的嵌套、递归和重入

  • 嵌套 子程序内包含有子程序的调用
  • 递归 子程序调用自身
  • 重入 子程序被中断后,又被终端服务程序所调用。能够重入的子程序称为可重入子程序
  • 子程序近调用时,入栈 2B,出栈 2B,而子程序远调用时,入栈 4B,出栈 4B。

5 高级汇编语言程序设计

宏结构程序设计

  • 定义
1
2
3
macro  macro_name arg1, arg2, ...
    ; 宏代码
endm
  • 调用
1
macro_name arg1, arg2, ...
  • 宏定义允许嵌套、递归
  • 宏参数
    • 数量 \(\in[0, +\infty)\),实际上肯定不是 \(+\infty\),主要是我也不知道是多少
    • 类型 可以是常数、变量、存储单元、指令、表达式
    • 传参 使用 & 做变量替换。传字符串时有空格则要使用一对 <> 包裹。如果字符串中本身就有 <>,则使用 ! 进行转义。传表达式时用 %,如 %(1+2)
    • 宏中的标号要加上 local 关键字,在顶部声明
  • 重复汇编 repear 型:
    1
    2
    3
    repeat count
        ; 重复执行的代码
    endm
    
    for 型:
    1
    2
    3
    for 形参, <实参表>
        ; 重复执行的代码
    endm
    
    forc 型:
    1
    2
    3
    forc 形参, 字符串
        ; 重复执行的代码
    endm
    
  • 条件汇编
    1
    2
    3
    4
    5
    ifxx expression
        ; 条件成立时执行的代码
    [else
        ; 条件不成立时执行的代码]
    endif
    

DOS 功能调用

  • 步骤:
    • 将调用参数装入指定寄存器
    • 如需功能号,将它装入 AH
    • 如需子功能号,将它装入 AL
    • 按中断类型号调用 DOSBIOS 中断(DOS 功能调用 INT 21H
    • 检查返回参数是否正确

6 32 位指令及其编程

  • 实方式
    • 寻址 1MB 物理存储空间
    • 分段最大是 64KB
    • 可以使用 32 位寄存器和 32 位操作数
    • 可以采用 32 位寻址方式
  • 保护方式
    • 具有段式存储管理功能
    • 具有页式存储管理功能
    • 寻址 4GB 物理存储空间

做试卷遇到的

  1. 可屏蔽中断就是它的请求是否被 CPU 响应要受 IF 的控制
  2. 引起中断的紧急事务称为中断源
  3. 键盘 I/O 、显示 I/O 和打印 I/O 分别对应 16H、10H、17H 号中断
  4. 字符显示模式缓冲区中的一个字对应屏幕上的一个字符,字的低字节存放 ASCII 码,高字节存放属性(如颜色)
  5. 在段定义时,如果定位类型用户未选择,就表示是隐含类型,即 PARA
  6. 通常主程序和子程序间参数传送的方法有三种:用寄存器传递、用存储单元传递、用堆栈传递
  7. 当程序顺序执行时,每取一条指令语句,IP 指针增加的值是由指令长度决定的
  8. MOV 指令不能直接在两个段寄存器之间传递数据、不允许两个内存地址之间直接传递数据、MOV 不可以向 CX 中传递地址(除非是两地址之差)
  9. AF(辅助进位标志,Auxiliary Flag)检测低 4 位的运算中是否发生了进位或借位
  10. NEG 必然会导致 CF 为 1
  11. mov [bx+d/si], xx 可以,但是 mov [dx+d/si], xx 不可以,这是因为 dx 寄存器不能作为基址寄存器
  12. 注意 字符串存储数字存储方式 的区别,字符串是左低右高,数字左高右低。字符串存储时是按照字节从左向右依次存储其 ASCII 码的
  13. 段只能起始于 16 倍数的地址
  14. 段内调用 CALLPUSH 指令会压入偏移地址(IP,一个字),SP 会减 2
  15. 段间调用 CALLPUSH 指令会压入先压入段地址(CS),再压入偏移地址(IP),SP 会减 4
  16. 标号定义(如 EQU=)的数据,不占用主存,类似于宏
  17. 段寄存器都不能直接赋立即数,必须用寄存器做中介;CS 是唯一不可以修改的段寄存器
  18. 还能他妈的用别的变量来定义新变量
  19. SBB 的作用 结果 = 操作数1 - 操作数2 - CF
  20. 在寻址时可以提供偏移地址的寄存器:BXBPSIDI
  21. 8086/8088 CPU 的寄存器中,16 位的寄存器共有 14 个,通用寄存器(AXBXCXDX)共 4 个,段寄存器 4 个,指针和变址寄存器(SPBPSIDI)共 4 个,指令指针和标志寄存器(IPFLAG)共 2 个
  22. 段长度 \(\in[16B, 64KB]\)
  23. 字相除时要拓展为双字
  24. 寄存器有 16 位,编号是从 0~15 编号的
  25. mov ax, [si+di] 是错的,不能只使用两个索引寄存器。
  26. NOT 不影响 CF 和 OF 的值
  27. 指令限制常用圈套(最 SB 的题型,行不行我上机看看不就知道了?天天 nm 考标号里不能出现什么符号还有这种,每个语言都要记一遍):
    1. 长度不同型
    2. 篡改 cs 型
    3. 段地址参与运算型
    4. 双地址做操作数型
    5. cx 做地址寄存器型
    6. div / mul 后面跟的不是寄存器型
    7. 段地址赋立即数型
  28. 立即寻址:mov ax, offset array
  29. 读入 DX 指定的端口的数据:IN AL, DX;输出数据到 DX 指定的端口:OUT DX, AL
  30. 显示字符串时,定义的字符串最后要有 '$'。
  31. INC/DEC 指令之后不可以使用加法调整指令
  32. INC/DEC 除了不影响 CF,剩下的都影响
  33. 堆栈不能压入立即数
  34. loop 在判断 cx 前,cx 会减 1