汇编语言-学习笔记

汇编语言-学习笔记

第 1 章 预备知识

什么是汇编

好处:

  • 解密程序、逆向工程、病毒木马分析和防止的唯一
  • 理解 C 语言程序的最好途径
  • 了解操作系统运行细节的最佳方式
  • 特定场合下编写程序的必然选择
  • 了解计算机工作原理和后继课程学习的基础

目标:

  • 掌握语法:指令格式 (关键:寻址方式)
  • 掌握语义:指令功能 (关键:分类记忆)
  • 灵活应用:阅读、编程 (关键:实践)

伪指令:

  • 由汇编程序在汇编过程中执行的指令

指令:

  • 告诉 CPU 要执行的操作(一般还要指出操作数地址),在程序运行时执行

Intel 80X86 寄存器组

8 个 32 位寄存器 (通用寄存器)分为两组:数据寄存器组、指示器变址寄存器组

数组寄存器组

Register
EAX累加器Accumulator
EBX基址寄存器Base
ECX计数器Count
EDX数据寄存器Data

EAX 可以看成某个存储单元的地址
(EAX) 则表示 EAX 单元中的内容

寄存器划分:

  • 低 16 位组 —— AX, BX, CX, DX
  • 低 8 位组 —— AL, BL, CL, DL
  • 高 8 位组 —— AH, BH, CH, DH
  • (AX) = (AH, AL) , …
Register31-16位15-8位7-0位
EAXAHAL
EBXBHBL
ECXCHCL
EDXDHDL

将 EAX 的低 16 位置 0:MOV AX, 0

将 EAX 的低 8~15 位置 1:MOV AH, 0FFH

变址寄存器组

Register
ESI源变址寄存器Source Index
EDI基址寄寄存器Destination Index
ESP堆栈指示器Stack Pointer
EBP堆栈基址寄存器Base Pointer

寄存器划分:

  • ESI、EDI、ESP、EBP 都是 32 位寄存器
  • 低 16 位作 16 位寄存器用,SI、DI、SP、BP
  • 一般用作指示器或变址寄存器
    可作为数据寄存器用
  • ESP, SP 一般不作数据寄存器使用
Register31-16位15-0位
ESISI
EDIDI
ESPBP
EBPSP

指令预取部件和指令译码部件

指令预取部件:将要执行的指令从主存中取出,送入指令排队机构中排队

指令译码部件:从指令预取队列中读出指令并译码,再送入译码指令队列排队供执行部件使用

指令的提取、译码、执行重叠进行,形成了指令流水线。

指令指示器:

  • EIP/IP:保存下一条要被 CPU 执行的指令的偏移地址 EA。由微处理器硬件自动设置
  • EIP/IP 不能由指令直接访问,执行转移指令、子程序调用指令等可使其改变
Register31-16位15-0位
IPIP

分段部件和分页部件

$ 虚拟存储空间 \xrightarrow{分段部件} 一维线性地址 \xrightarrow{分页部件} 物理存储空间 $

  • 虚拟存储地址是一种概念性的逻辑地址,并非实际空间地址
  • 程序员编写程序时不用考虑物理存储器大小
  • 存储管理单元 MMU 进行虚地址到是地址的自动变换
  • 地址变换对应用程序是透明的

分段部件中 6 个 16 位的段寄存器:

Register
CS代码段寄存器Code Segment
DS数据段寄存器Data Segment
SS堆栈段寄存器Stack Segment
ES附加数据段寄存器
FS
GS

80x86 微处理器结构

1569237958957

80x86 的三种工作方式

  • 实方式 (实地址方式)

    • 相当于 8086
    • CPU 32 位、16 位数据总线、20 位地址总线
  • 保护方式 (虚地址)

    • 支持多任务环境的工作方式,建立保护机制存储区采用分段、分页的存储管理机制
    • 为每个任务提供一台虚拟处理器
  • 虚拟 8086 方式

    • 保护方式下所提供的同时模拟多个 8086 处理器
    • 例如 Windows 的 CMD

主存储器

内存:用来存放程序和数据的装置

字节 Byte:最小的寻址单位

字 Word:两个相邻的字节组成一个字

  • 低 8 位在低字节(低地址)
  • 高 8 位在相邻的高字节
  • 上述是小端模式,即低字节在低地址

双字 DoubleWord:四个连续的字节,地址是四个字节中最低的地址

实验-地址类型转换:

  • “1234567”,在内存中存为 31H、32H、…、37H,从上到下
1
2
3
4
5
6
7
8
9
10
11
int main()
{
char s[10];
strcpy(s, "1234567");
printf("%ld\n", *(long *)(s+2)); // 输出 909456435 =(36353433)_16
printf("%ld\n", *(short *)(s+2));// 输出 13363 = (3433)_16
printf("%d\n", *(char *)(s+2)); // 输出 51 = (33)_16
*(int *)(s+1)=16706;// 16706=(00004142)_16
printf("%s\n", s); // 输出 1BA
return 0;
}

堆栈

堆栈的建立:

1
2
3
4
; 建立一个 16 位段,地址形式是16位段地址, 16位偏移, 使用16位寄存器SP指向栈顶
MY_S_NAME SEGMENT USE16 STACK
DB 200 DUP(0)
MY_S_NAME ENDS

1569240163289

进栈指令:

  • PUSH OPS,OP 指操作,S 指源

    • 单字进栈: (SP)-2 → SP; 字数据 → [sp];
      记为 (OPS) → ↓ (sp)
    • 双字进栈:(SP)-4 -> SP; …
  • 功能:将立即数、寄存器、段寄存器、存储器中的一个字/ 双字数据压入堆栈

    1569240381187

出栈指令:

  • POP OPD,OP 指操作,D 指目的

    • ([SP]) → OPD; (SP+2) → SP
      记为 ↑ (SP) → OPD
  • 功能:将栈顶元素弹出送至某一寄存器、段寄存器 (CS除外)、存储器中

8 个 16 位寄存器内容顺序存入指令:

  • PUSHA
  • 功能:将 8 个 16 位寄存器按 AX, CX, DX, BX, SP, BP, SI, DI顺序入堆栈
  • 说明:入栈的 SP 是执行 PUSHA 之前的 SP 值
  • 依次送出 DI, SI, BP, SP, BX, DX, CX, AS 的 POPA 指令

8 个 32 位寄存器内容顺序存入指令:

  • PUSHAD
  • 功能:将 8 个 32 位寄存器按 EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI顺序入堆栈
  • 说明:入栈的 SP 是执行 PUSHAD 之前的 SP 值
  • 依次送出 EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAS 的 POPAD 指令

物理地址的形成

程序中单元 (如变量等) 的相对位置、逻辑地址已定。

两段程序的位置如下:

  • 确定了白线条的位置,其他线条的位置可它们之间的相对位置关系计算

1569241216644

  • 段的开始地址要能被 16 整除,16 = 10H = 10000B

  • 段址:段开始单元的物理地址(段首址) / 16

  • 偏移地址:距离段首址的距离

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

  • “段首地址 : 偏移地址”:这样为二维的逻辑地址

  • 一个段最大为 64 KB ( $2^{16}$ )

  • 1 M 内存至少有 16 个段

  • 一个段内可同时访问 4 个段寄存器 CS, DS, ES, SS

在代码段中取指令时

  • 指令的物理地址 PA = (CS) 左移4位 + (IP),注意不是 EIP

在数据段中读/写数据时

  • 数据的物理地址 PA = (DS或ES) 左移4位 + 16位偏址 (偏址由寻址方式确定)

在堆栈操作时

  • 栈顶的物理地址 PA = (SS) 左移四位 + (SP)

8086 CPU 在运行一个程序时,如何确定 CS, DS, ES, SS 中的值呢?

  • 在将一个程序装入内存,准备运行时,由操作系统确定程序中定义的各个段在什么位置。
  • 对于代码段,系统自动将代码段首址送到 CS 中 ,并设置 IP 为第一条要执行指令的偏移地址(C 语言的 MAIN )。
  • 对于堆栈段,系统将自动把堆栈段首址送到 SS中,并根据定义的堆栈段的大小,设置 SP 的值。
实方式物理地址的形成
  • 32 位 CPU 与 8086 一样只能寻址 1M 物理存储空间

  • 可以访问 6 个段 CS, DS, SS, ES, FS, GS,每个段至多 64K

  • 物理地址 = (段寄存器) 左移 4 位 + 偏移地址

  • C 语言变量和指令写在一起,没有分段的概念,而机器语言层次上是要分段的,为什么?

    • 变量也需要存储空间存放,而 cpu 执行的执行代码,全部为 01 程序,因此需要堆栈段、数据段、代码段分开,使 cpu 执行代码时可以区分变量和指令。
保护方式下物理地址的形成

80386 中寄存器 32 位,地址线 32 根。

  • 在多任务环境下,系统中有多个程序在运行,程序之间要隔离
  • 分段是存储管理的一种方式,为保护提供基础
  • 不同程序在不同段中,一个程序可包含多个段
  • 段用于封闭具有共同属性的存储区域
1
2
3
4
5
6
7
8
int main()
{
int c[10];
int y = 10;
c[-1] = 48;
printf("%d\n", y); // 输出 48
// c[-10000] = 48 会报错,这是分段保护(超出段范围)
}

描述符,8Byte —— 保护一个段所需的信息及其存放 :

  • 段的起始位置(段基地址)

  • 段的大小(段界限)

  • 段的特权级

  • 段的属性(是代码段,数据段,还是堆栈段?数据段是否可写?代码段是否可读出?)

  • 段的位置(在内存还是在磁盘?)

  • 段的类型(在系统段还是用户段?)

  • 段的使用(段被访问过,还是没有?)

  • 分段

1569298216883

描述符表 Local Description Table—— 描述符的集合:

  • 一个 LDT,是一个系统段,最大可为 64KB,最多可存放 8192 个描述符

1569298386648

  • 全局描述符表,只有一个 GDT,最大可为 64KB,存放 8192个描述符。
    包括:操作系统所使用的段的描述符、各个 LDT 段的描述

    1569298526903

  • 全局描述符表,在 GDTR 这个 48 位寄存器,存放其地址。还有一个 LDTR,表示全局描述符中 LDT 的偏址

    1569298802680

“xxxx : yyyyyyyy” 从虚拟地址到线性地址的映射:

1569298951858

  • xxxx 不是段开始的地址,而是指出栈相应段描述符的方式。称之为 段选择符
  • TI = 0
    • 从 GDTR 寄存器取 GDT 表的基址
    • 在 GDT 表中,以 xxxx 的高 13 位作为偏址,取出描述符 A
    • 描述符 A 中的段基址 + yyyyyyyy,即为要访问单元的线性地址
  • TI = 1
    • 从 GDTR 寄存器取 GDT 表的基址
    • 在 GDT 表中,以 LDTR 寄存器的高 13 位作偏址,取出描述符 A
    • 描述符 A 描述的段为一个 LDT 段 (如 LDT_A)
    • 用 xxxx 的高 13 位作偏址,在 LDT_A 段中找到描述符 P_A
    • P_A 描述段的基址 + yyyyyyyy 为线性地址

从线性地址到物理地址:

1569299732649

标识寄存器

  • 保存一条指令执行之后,CPU 所处状态的信息及运算结果的特征

  • 16 位 CPU 中的标志寄存器是 16 位,称 FLAGS

  • 32 位 CPU 中的标志寄存器是 32 位,称 EFLAGS

  • 32 位的 EFLAGS 包含了 16 位 FLAGS 的全部标识

标志位

1569300269809

  • 条件标志位
    Overflow Flag 溢出
    Sign Flag 符号
    Zero Flag 零
    Carry Flag 进位

  • 符号标志 SF,最高位为 1,则 SF=1

  • 零标志 ZF,运算结果为 0,则 ZF=1

  • 溢出标志 OF,若两个加数最高位相同,且结果最高位相反,则溢出 OF=1

  • 进位标志 CF,若运算从最高位向前产生进位 (或借位),则 CF=1

标志寄存器操作指令

LAHF (Load AH From Flags):

  • 功能:将标志寄存器的低 8 位送入 AH 中,对标志位无影响。

SAHF (Store AH into Flags):

  • 功能:将 AH 中的内容送入标志寄存器的低 8 位中,而高位保持不变。

标志寄存器堆栈指令:

  • 低 16 位:PUSHF、POPF
  • 32 位标志寄存器:PUSHFD、POPFD

汇编源程序举例

for 语句

1
2
3
4
5
6
7
8
9
; AX=0; for(BX=1;BX<100;BX+=2) AX+=BX;
MOV AX, 0
MOV BX, 1
MAINP: CMP BX, 100
JGE EXIT
ADD AX, BX
ADD BX, 2
JMP MAINP
EXIT:

dowhile语句

1
2
3
4
5
6
7
8
; AX=0; BX=1; do{AX+=BX;BX+=2;}while(BX<100);
MOV AX, 0
MOV BX, 1
MAINP: ADD AX, BX
ADD BX, 2
CMP BX, 100
JL MAINP
EXIT:

第 2 章 寻址方式

  • 内存中,何处找操作数

  • CPU 如何知道操作数地址

  • C 语言中地址的寻找

    1
    2
    3
    4
    5
    6
    int i, j;
    int A[10];
    int *p;
    int B[20][10];
    A[i] = 5;
    B[i][j] = 10;

一条指令,关注:

  • 操作码 —— 执行什么操作
  • 操作数在哪
    • CPU 寄存器
    • 主存,操作数在主存时,关注段址/段选择符、段内偏移
    • IO 设备端口
  • 操作数类型

双操作数的指令格式:

  • 操作符 OPD, OPS

  • ADD AX, BX,AX 是目的操作数地址,BX 是源操作数地址

  • (OPD) + (OPS) → OPD

寄存器寻址

格式:R

功能:寄存器 R 中的内容即操作数(除个别指令外,R 可为任意寄存器)

操作数在寄存器(操作数地址就是符号地址),操作数类型是字节

例如:

  • DEC BL

  • ADD AX, BX

  • MOV AX, BX

  • ADD EAX, EDX

  • MOV AX, BH,注意两个的字节数

寄存器间接寻址

格式:[R]

功能:操作数在内存中,操作数的偏址在寄存器 R 中,即 (R) 为操作数的偏址

1569307692100

R 可以是:

  • 8 个 32 位通用寄存器中任一:EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP
  • 4 个 16 位通用寄存器中任一:BX, SI, DI, BP (向下兼容的遗留问题)

操作数所在段是:

  • R 为 BP、EBP、ESP,系统默认操作数在堆栈中,等同于 SS:[R]
  • 其他情况,默认操作数在 DS 所指示的段中

操作数类型:未知

例:

  • MOV AX, [SI]
    执行前 (AX)=0005H, (SI)=(0020H), DS:(20H)=1234H,执行后 (AX)=1234H, (SI)=0020H。
    该指令中目的操作数是寄存器寻址,源操作数是寄存器间接寻址
    若指令是 MOV AL, [SI],则 (AL)=34H,目的操作数暗示操作数类型

  • MOV EAX, -1 MOV [ESP], EAX POP EBX ,结果 (EBX)=-1

  • MOV AX, [CX],不能用这个 16 位寄存器

  • MOV AX, BXMOV AX, [BX] 差别很大,前者是值存在 BX 中,后者存的是偏址,还要根据偏址找到值

C -> 汇编实例

1
2
3
4
5
6
7
8
9
10
11
12
; 寄存器间接寻址实现 strcpy(buf2, buf1, ...)
MOV SI, OFFSET BUF1
MOV DI, OFFSET BUF2
MOV CX, 5
MAINP:
MOV AL, [SI]
MOV [DI], AL
INC SI
INC DI
DEC CX
JNZ MAINP
EXIT:

变址寻址

格式:

  • [R×F + V]
  • [R×F] + V
  • V[R×F]

功能:操作数在内存。R 中的内容 × F + V 为操作数的偏址

1569307758547

R 可以是:

  • 8 个 32 位通用寄存器中任一:EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP
  • 4 个 16 位通用寄存器中任一:BX, SI, DI, BP (向下兼容的遗留问题)

F (比例因子) 可以是:1、2 (字)、4 (双字)、8

  • 当 R 为 16 位寄存器时,F = 1

V 与操作数所在:

  • 当 V 为数值常量,是二进制补码表示的有符号数。
    若 R 为 BP、EBP、ESP,则系统默认操作数在堆栈中,等同于 SS:[R];
    其他情况默认操作数在 DS 所指示的段中
  • 当 V 为变量时,取该变量对应单元的有效地址参与运算。即系统默认操作数在该变量或标号所在的段中(除非显式指明形如 CS:[BX])。

例:

  • MOV AL, [EBX*2]+5
    • 执行前 (AL)=18H, (EBX)=1100H, DS:(2205H)=55H
    • 执行后 (AL)=55H, 其他不变

C -> 汇编实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
; 变址寻址实现 for(i=0;i<5;i++) result+=buf[i];
.386
STACK SEGMENT USE16 STACK
DB 200 DUP(0)
STACK ENDS

SEG1 SEGMENT USE16
BUF DD 10,20,30,40,50
RES DD ?
SEG1 ENDS

CODE SEGMENT USE16 ASSUME CS:CODE, DS:SEG1,SS:STACK

START:
MOV AX, SEG1
MOV DS, AX
MOV EBX, 0
MOV EAX, 0
LP:
CMP EBX, 5
JGE EXIT
ADD EAX, BUF[EBX*4] ; DD 定义双字
INC EBX
JMP LP
EXIT:
MOV RES, EAX
MOV AX, 4C00H
INT 21H
CODE ENDS END START
1
2
3
4
5
6
7
8
9
10
; 变址寻址实现 strcpy(buf2, buf1, ...)
MOV EBX, 0
MAINP:
CMP EBX, 5
JGE EXIT
MOV AL, BUF1[EBX*4]
MOV BUF2[EBX*4], AL
INC EBX
JMP MAINP
EXIT:

基址加变址寻址

格式:

  • [BR + IR×F + V]
  • V[BR][IR×F]
  • V[IR×F][BR]
  • V[BR + IR×F]

功能:

  • 操作数的偏移 = 变址寄存器 IR 中的内容 × 比例因子F + 位移量V + 基址寄存器 BR 的内容
    EA = (IR)*F + V + (BR)

使用 16 位寄存器时:

  • BR ∈ {BX, BP}
    为 BX 默认操作数在 DS 所指示的段
    为 BP 默认操作数在 SS 所指示的段
  • IR ∈ {SI, DI}
  • F = 1

使用 32 位寄存器时:

  • BR 可是 8 个通用寄存器中任一
  • IR 可是除 ESP 外任一
  • 没有比例因子时,写在前面的寄存器是 BR
  • 当 BR 为 ESP、EBP,默认段是 SS

操作数的类型:

  • 当 V 为变量,则操作数类型为变量的类型
  • 当 V 为常量,类型位置

例:

  • MOV AX, 8[BX][SI]
    • 执行前 (AX)=45H, (BX)=30H, (SI)=20H, DS:(0058H)=99H
    • 执行后 (AX)=99H,其他不变

立即寻址

格式:n

  • 操作数直接放在指令中,在指令的操作码后
  • 操作数是指令的一部分,位于代码段中
  • 指令中的操作数是 8 位、 16 位或 32 位二进制数
  • 只能作源操作数
  • 立即数不能直接送段寄存器

直接寻址

格式:

  • 段寄存器名 : [n]
  • 变量
  • 变量+常量

功能:操作码的下一个字/双字单元的内容为操作数的偏址 EA

  • 操作数在内存中。操作数的偏址 EA 紧跟在指令操作码后

操作数所在的段:

  • 由段寄存器名指示,或是变量所在的段

操作数的类型:

  • 若有变量,则是定义变量的类型
  • 若无,未知

例:

  • MOV AX, DS:[2000H]
    • 执行前 (AX)=1, DS:(2000H)=976
    • 执行后 (AX)=976

有关问题

寻址方式 6 种,根据操作数的存放位置可归 3 类:

  • 寄存器方式
  • 立即方式
  • 存储器方式
    • 寄存器间接寻址
    • 变址寻址
    • 基址加变址寻址
    • 直接寻址

双操作数寻址方式的规定:

  • 一条指令的源操作数和目的操作数不能同时用存储器方式

操作数的类型:

  • 寄存器寻址方式中,操作数类型由寄存器定
  • 立即数没有类型
  • 含变量的寻址方式所表示的操作数类型为变量的类型
  • 不含变量的存储器方式所表示的操作数类型未知
    • MOV [BX], 0 两个操作数类型都不明确
    • 属性定义算符 PTR
      MOV BYTE PTR[BX], 0
      MOV WORD PTR[BX], 0
      MOV DWORD PTR[BX], 0

双操作数的类型规定:

  • 双操作数中至少应有一个的类型是明确的
  • 若两个操作数的类型都明确,则两个的类型应相同

第 3 章 宏汇编语言

目标:

  • 正确而熟练地使用地址表达式和数值表达式
  • 熟悉常用的机器指令的使用格式、功能
  • 区别机器指令语句和伪指令语句
  • 常用的伪指令功能、使用方法
  • 熟练掌握常用的 DOS 系统功能调用 (1, 2, 9, 10 号调用)

宏汇编语言表达式

常量:

  • 便于程序修改、阅读,在

  • MOV CX, 10 中 10 为数值常量

  • MOV CX, AA 中 AA 为符号常量。汇编时编译器会将其换为常量

数值表达式:

  • 由常量和运算符组成的有意义的式子

  • 一个常量是一个数值表达式;

    由数值表达式通过运算符和括号连接起来是数值表达式

  • 算术运算:+、-、&、/、MOD、SHR、SHL

  • 关系运算:EQ、NE、LT,GT、LE、GE

  • 逻辑运算:AND、OR、XOR、NOT

变量:

  • 一个数据存储单元的名字
  • 存储单元的属性:段属性、偏移地址、单元的类型、单元中的内容
  • 变量定义、变量定义伪指令、表达式[, …]
    DB 字节
    DW 字
    DD 双字
    DF 三字
    DQ 四字
    DT 十字
  • 表达式有五种
    • 数值表达式:X DB 10
    • 字符串 (如果串长超过 2 个字符定义伪指令只能用 DB):Z DW '12'Y DB '12'X DB 'abcd',前两者在内存中情况相反
    • 地址表达式(不能出现带寄存器符号,数据定义伪指令只能用 DW、DD,用 DD 的时候包含段首址)。
      有点像指针,面向对象汇编后的代码中大量使用。
      1569414743376
    • ? (就是一个问号),表示定义的变量无确定的初值
    • 重复字句 n DUP (表达式[,…])
      • X DB 3, DUP(2) 等价于 X DB 2,2,2
      • X DB 3, DUP(1,2) 等价于 X DB 1,2,1,2,1,2
1569415286144

标号:

  • 机器指令存放地址的符号表示
  • 三个属性
    • 段属性
    • 偏址
    • 类型:NEAR, FAR

地址表达式

  • 类型运算符 PTR
    BYTE WORD DWORD FWORD NEAR FAR
    • MOV BYTE PTR DS:[2000],2
    • BUF DB 1,2MOV AX WORD PTR BUF,结果是 0201H。
      因为 BUF 在内存中从上到下是 01H 02H,取字后就是 0201H
  • 跨段前缀
    段寄存器名:地址表达式
    段名:地址表达式
  • 属性分离算符
    段属性、偏移地址、类型的分离`
    • SEG 变量或标号,如 MOV AX, SEG BUF
    • OFFSET 变量或标号,如 MOV AX, OFFSET BUF
    • TYPE 变量或标号,如 MOV AX, TYPE BUF

常用机器指令语句

指令的共同要求:

  • 双操作数的操作数类型必须匹配
  • 目的操作数一定不能是立即操作数
  • 目的操作数和源操作数不能同时为存储器操作数。
    如果一个操作数在数据存储单元中 ,另一个一定要是立即数或寄存器操作数

数据传送指令

一般传送指令:

  • MOV 指令

    • MOV OPD, OPS

    • 立即数不能送段寄存器;

    • 不能用 MOV 指令改变 CS;

      1569416356167

  • 有符号数传送指令

    • MOVSX OPD, OPS,”S” 指 Signed (386指令)
    • 功能:将源操作数的符号向前拓展(符号位是 1 拓展全为 1,符号位是 0 拓展全为 0)成与目的操作数相同的数据类型
    • OPS 不能为立即数
    • OPD 必须为 16/32 位寄存器
  • 无符号数传送指令
    • MOVZX OPD, OPS,”Z” 指 Zero (386指令)
    • 功能:将源操作数的符号高位补零成与目的操作数相同的数据类型
    • OPS 不能为立即数
    • OPD 必须为 16/32 位寄存器

一般数据交换指令:

  • XCHG 指令

    • XCHG OPD, OPS,将源、目的地址指明的单元中内容互换
    • 不能使用段寄存器 XCHG DS, AX
  • 查表转换指令

    • XLAT。([BX+AL]) → AL,或 ([EBX+AL]) → AL

    • 功能:将 (BX) 或 (EBX) 为首址,(AL) 为偏移量的字节存储单元中的数据传送给 AL
      XLAT 可用来对文本数据进行编码和译码,从而实现简单的加密和解密。

    • 用 BX 还是 EBX 取决于 16 位段还是 32 位段

      1
      2
      3
      4
      5
      6
      7
      8
      ; 设有一个 16 进制数码( 0 ~ 9 , A~F) 在(AL) 中,现请将该数码转换为对应的 ASCII 
      ; 简单的方法是分类讨论 (AL) 是否小于等于9
      ; 现在用查表实现

      MYTAB DB '0123456789ABCDEF'

      MOV BX, OFFSET MYTAB
      XLAT

地址传送指令:

  • 传送偏移地址指令

    • LEA OPD, OPS,Load effective address
    • 功能:计算 OPS 的偏址,并将其送入 OPD 中
    • OPD 一定是一个 16/32 位的通用寄存器;
    • OPS 所提供的一定是一个存储器地址;
    • 如果偏址是 32 位,而 OPD 为 16 位,则取低 16 位
      如果偏址是 16 位,而 OPD 为 32 位,则高 16 位补 0
    • MOV SI, OFFSET NUMLEA SI, NUM 等效
    • 没有与 LEA DI, [SI+4] 等效的 MOV 语句
    • 没有与 MOV POIN, OFFSET BUF 等效的 LEA 语句
  • 传送偏址和数据段首址指令

    • LDS OPD, OPS,Load data segment
    • 功能:(OPS) → OPD,(OPS+2或4) → DS
    • OPD 一定是一个 16/32 位的通用寄存器;
    • OPS 所提供的一定是一个存储器地址,类型为 DWORD/FWORD;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.386
DATA1 SEGMENT USE16
T1 DW -50H
T2 DD F
T3 DB '1234567'
DATA1 ENDS

DATA2 SEGMENT USE16
BUF DB 'ABCDEF'
F DW 70H
DATA2 ENDS

CODE SEGMENT USE16
ASSUME CS:CODE, DS:DATA1

START:
MOV AX, DATA1
MOV DS, AX
MOV SI, 6
MOV AX, [SI] ; (AX)=3231H

MOV AX, T1 ; (AX)=0FFB0H
LDS SI, T2
MOV AX, [SI] ; (AX)=0070H

MOV AX, T1 ; (AX)=4241H, 因为此时段首址变成了DATA2

MOV AH, 4CH
INT 21H
CODE ENDS
END START

算术运算指令

一般对标志位都有影响

加法指令:

  • 加 1 指令

    • INC OPD
  • 加指令

  • ADD OPD, OPS

  • 带进位加指令

    • ADC OPD, OPS

    • 计算 1234 F00FH +1234 80F0H,只允许使用 16 位寄存器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      data segment use16
      dn1 dw 0f00fh, 1234h
      dn2 dw 80f0h, 1234h
      dsum dw 0, 0
      data ends

      code segment
      assume cs:code, ds:data

      start:
      mov ax, data
      mov ds, ax
      mov ax, dn1
      add ax, dn2
      mov dsum, ax ; 不影响标志位
      mov ax, dn1+2 ; 不影响标志位
      adc ax, dn2+2 ; 标志位中低位的影响加到当前加法
      mov dsum+2, ax
      mov ah, 4ch
      int 21h
      code ends
      end start

减法指令:

  • DEC 对 OF、SF、ZF、PF、AF 有影响,其他指令对 CF、OF、SF、ZF、PF、AF 有影响

  • 减 1 指令

    • DEC OPD
  • 求补指令

    • NEG OPD
    • 功能:求反加一
  • 减指令

    • SUB OPD, OPS
  • 带借位减指令

    • SBB OPD, OPS
  • 比较指令

    • CMP OPD, OPS
    • (OPD) - (OPS) 的标志位

乘法指令:

  • 有符号乘法 - 双操作数有符号乘

    • IMUL OPD, OPS
    • 功能:(OPD) * (OPS) → OPD,OPD 为 16/32 位寄存器,OPS 为同类型寄存器、存储器操作数或立即数
    • 例:
      IMUL AX, BX
      IMUL EAX, DWORD PTR[SI]
      IMUL AX, 3
  • 有符号乘法 - 三操作数有符号乘

    • IMUL OPD, OPS, n
    • 功能:(OPS) * n → OPD
  • 有符号乘法 - 单操作数有符号乘

    • IMUL OPS
    • 看 OPS 类型选:
      字节乘法:(AL) * (OPS) → AX
      字乘法:(AX) * (OPS) → DX, AX(为什么不是 EAX ?)
      双字乘法:(EAX) * (OPS) → EDX, EAX
    • OPS 不能是立即数
      若乘积的高位 不是 低位的符号拓展,而是包含有效位(溢出),则 CF=1, OF=1
  • 无符号乘法

    • MUL OPS
    • 看 OPS 类型选:字节乘法、字乘法、双字乘法

除法指令:

  • 有符号除法

    • IDIV OPS
    • 看 OPS 类型选:
      字节除法:(AX) / (OPS) → AH(余), AL(商)
      字除法:(DX, AX) / (OPS) → DX(余), AX(商)
      双字除法:(EDX, EAX) / (OPS) → EDX(余), EAX(商)
  • 无符号除法

    • DIV OPS
    • 同理,看 OPS 类型

符号拓展指令:

  • 将字节转换成字

    • CBW
    • 功能:将 AL 中的符号拓展到 AH 中
  • 将字节转换成字

    • CWD
    • 功能:将 AX 中的符号拓展到 DX 中
  • 将 AX 中的有符号数拓展为 32 位送 EAX

    • CWDE
  • 将 EAX 中的有符号数拓展为 64 位送 EDX, EAX

    • CDQ

位操作指令

逻辑运算指令:

  • NOT OPD:(OPD) 求反 → OPD

  • AND OPD, OPS:(OPD)&(OPS) → OPD

  • OR OPD, OPS:(OPD)|(OPS) → OPD

  • XOR OPD, OPS:(OPD)^(OPS) → OPD

  • 测试指令:TEST OPD, OPS

    • 功能:依据 (OPD)^(OPS) 设置标志位,(OPD)、(OPS) 不变
    • CF=0, OF=0, ZF、SF、PF 依结果而定。AND、OR、XOR 亦是如此

移位指令:

  • 算术左移 SAL Shift Arithmetic Left
    逻辑左移 SHL SHift Logical Left
    逻辑右移 SHR SHift Logical Right
    算术右移 SAR Shift Arithmetic Right
    循环左移 ROL Rotate Left
    循环右移 ROR Rotate Right
    带进位的循环左移 RCL Rotate Left through Carry
    • 格式:操作符 OPD, n 或 CL
    • 功能:将 (OPD) 中的所有位按操作符规定的方式移动,结果存在 OPD 对应的单元中
    • 特别说明:OPD 可是寄存器或地址表达式;在 8086 中 n 只能是 1,其他要用 CL

1569588107378

串操作指令

控制转移指令

第4章 分支程序设计中涉及

处理机控制指令

伪指令语句

处理器选择伪指令:

  • 告诉汇编程序选择何种 CPU 所支持的指令系统
伪指令功能伪指令功能
.8086接受8086指令(缺省方式).586接受 Pentium 指令(除特权指令)
.386接受80386指令(除特权指令).586P接受全部 Pentium 指令
.386P接受所有80386指令.686接受 Pentium Pro 指令(除特权指令)
.486接受80486指令(除特权指令).686P接受全部 Pentium Pro 指令
.486P接受所有80486指令.MMX接受 MMX 指令
  • .386

  • MASM 不同版本支持的指令系统不同

数据定义伪指令:

  • 格式:[变量名] 数据定义伪指令 表达式 [, ...]

  • 功能:定义一数据存储区,其类型由所使用的数据定位伪指令指定

  • 表达式 5 种:数值表达式、字符串、地址表达式、?、重复字句

  • 具体查看本章 “宏汇编语言表达式” 部分 -> 变量相关

符号定义伪指令:

  • 格式: 符号名 EQU 表达式
  • 功能:为常量、表达式及其他符号定义一个等价的符号名
  • 说明:符号名不可省。数据定义中变量名可省。变量占用存储单元,但符号名不占用存储单元。

段定义伪指令:

  • 段定义伪指令:

    • 格式:段名 SEGMENT [使用类型] [定位方式] [组合方式] ['类别'] ...... 段名 ENDS
    • “使用类型”:USE16,16 位段,段的最大长度为 64KB,地址的形式是 16 位段地址和 16 位偏移地址,寻址方式为 16 位寻址方式;USE32,.386 默认使用 32 位段。
    • 使用了伪指令 .386 (或以上),”使用类型” 才起作用
  • 假定伪指令:

    • 格式:ASSUME 段寄存器名:段名 [, ...]
    • 功能:用来设定段寄存器与段之间的对应关系
    • 目标程序运行时才能给段寄存器置值
    • CS 和 SS 的内容将由操作系统自动设置
    • DS 和 ES 的内容须由程序指令设置,且一定要做,这才能保证正确地产生数据存储单元的物理地址
    • 在程序启动时,DS 中的值是
      程序段前缀 Program Segment Prefix (PSP)
  • 置汇编地址计数器伪指令

    • 汇编地址计数器:$

    • 汇编程序在翻译程序时,每遇到一个新段,就将汇编地址计数器置 0 。

    • 在分配存储单元后(变量定义、机器指令),汇编地址计数器累加其分配的单元长度。

    • $用来记录正在被汇编程序翻译的语句的地址。

    • 标号和变量的偏移地址就是准备翻译该语句时当前汇编地址计数器$的值。

    • 汇编地址计数器符号$可出现在表达式中。

      1569760391036

    • ORG 数值表达式

    • 功能:将 $ 设置成数值表达式的值。数值表达式的值应为非负的整数,其值可在 0 ~ 65535 之间(16 位段)或 0 ~4G 之间( 32 位段)

      1569760578294

源程序结束伪指令:

  • END [表达式]
  • 功能:遇到该语句是,汇编工作停止
  • 如果有表达式,指出第一条被执行指令的地址。
  • 如果无表达式,则说明该程序不能单独运行,这时,它作为一个子模块供其他程序调用。
  • 不可将 END 语句错误地安排在程序中间。

常用 DOS 系统功能调用

调用操作系统提供的功能:设备管理、文件管理、目录管理等

一般过程:

  • 调用号放入 AH 中
  • 置好入口参数
  • INT 21H
  • 调用结束,分析出口参数

键盘输入 1 个字符 —— 1 号:

  • MOV AH, 1
    INT 21H
  • 功能:
    等待从键盘输入一个字符;
    将输入字符的ASCII 码 → AL;
    将该字符送显示器显示。

显示输出 1 个字符 —— 2 号:

  • MOV AH, 2
    MOV DL, 带显示字符的 ASCII
    INT 21H
  • 功能:将 DL 中的字符送显示器显示

显示输出字符串 —— 9 号:

  • LEA DX, 字符串首偏移地址
    MOV AH, 9
    INT 21H
  • 功能:从 DS:DX 所指向的单元开始,依次显示字符,直到遇到 ‘$‘ 为止。
  • 若字符串本身包含 ‘$‘ 就用 2 号调用循环输出吧

键盘输入字符串 —— 10 号:

  • LEA DX, 缓冲区首偏移地址
    MOV AH, 10
    INT 21H
  • 功能:从 DS:DX 所指的输入缓冲区输入字符串并送显示器显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS

DATA SEGMENT
BUF DB 11 ; 表示至多 10 个字符
DB ? ; 放实际输入字符个数
DB 11 DUP(0) ; 字符串最后一字符是 0DH
DATA ENDS

CODE SEGMENT
ASSUME CS:CODE, DS:DATA, SS:STACK

START:
MOV AX, DATA
MOV DS, AX

LEA DX, BUF
MOV AH, 10
INT 21H

; LEA DX, BUF
; MOV AH, 9
; INT 21H

MOV AH, 4CH
INT 21H
CODE ENDS
END START

第 4 章 程序设计方法

分支程序设计

1
2
3
4
5
6
7
8
9
10
11
;if(x==y) statements1
;else statements2
MOV AX, X
CMP AX, Y
JNE L1
statements1
JMP L2
L1:
statements2
L2:
...

转移指令:

  • 条件转移
    • 简单条件转移 10 条
    • 无符号数条件转移 4 条
    • 有符号数条件转移 4 条
  • 无条件转移 JMP
    • 段内直接、段间直接
    • 段内间接、段间间接

条件转移

简单条件转移:

指令条件
JZ / JEZF=1
JNZ / JNEZF=0
JSSF=1
JNSSF=0
JOOF=1
JNOOF=0
JCCF=1
JNCCF=0
JP / JPEPF=1
JNP / JPOPF=0

无符号数条件转移指令:

指令条件
JA / JNBECF=0 且 ZF=0
JAE / JNBCF=0 或 ZF=1
JB / JNAECF=1 且 ZF=0
JBE / JNACF=1 或 ZF=1
1
2
3
4
5
6
	CMP	AX, BX
JA L1
...
L1:
...
; 将 (AX), (BX) 当成无符号数,执行(AX)-(BX),若(AX)>(BX),则CF一定会为0,ZF=0,转移到L1处

有符号数条件转移指令:

指令条件
JG / JNLESF=OF 且 ZF=0
JGE / JNLSF=OF 或 ZF=1
JL / JNGESF != OF 且 ZF=0
JLE / JNGSF != OF 或 ZF=1

例子:根据输入的数字,显示对应的串。如 0 -> ‘zero’, 1 -> ‘first’, …,对于不同的输入,输出的串长度不同。

程序的关键:如何根据输入,将对应的待显示的串首址送 DX

打表的写法:

1
2
3
4
5
6
STR0 DB 'zero', '$'
STR1 DB 'one', '$'
...
TAB DW STR0, STR1, STR2, ...
; 使用如下
MOV DX, TAB[BX]

无条件转移

格式名称功能
JMP 标号段内直接(IP/EIP)+位移量 → IP/EIP
JMP OPD段内间接(OPD) → IP/EIP
JMP 标号段间直接标号的EA → IP/EIP
段首址 → CS
JMP OPD段间间接(OPD) → IP/EIP
(OPD+2或4) → CS

例子:根据不同输入,跳到不同程序段。1->LP1, 2->LP2, …

朴素的写法:

1
2
3
4
5
... 判1
JMP LP1
... 判2
JMP LP2
...

打表的写法:把指令地址列表构造好,直接 JMP TAB[BX]。(swtich-case 和 面向对象时大量采用)

条件控制流伪指令,知道有就行了,本课不推荐使用。我们深入底层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.386
data segment USE16
x db -5
bufp db 'positive > 0 $'
bufn db ' < 0 $'
zero db 'zero $'
data ends
code segment USE16
...
.if x==0
lea dx, zero
.elseif x>0
lea dx, bufp
.else
lea dx, bufn
.endif
mov ah, 9
int 21h

; 出错,理由如下
X DB -5 ; 可能会当成无符号数
X SBYTE -5 ; 定义位有符号数
; 类似的还有 SWORD SDWORD

循环程序设计

例:设以 BUF 为首址的一片单元中,存放了 N 个有符号字节数据,找出其中的最大数,存放到 AL中。

1
2
3
BUF DB 1,-10,20,-25, 25,50, …
N = $ - BUF ; 如果是DW就再除2
; ...

80x86 提供四种计数控制循环转移指令:

  • LOOP 标号

    • 功能:(CX / ECX) - 1 → CX / ECX
      若 (CX / ECX) 不为 0,则转标号处执行。
    • 基本等价于 DEC CX/ECX JNZ 标号,因为 LOOP 指令对标志位无影响
  • LOOPE / LOOPZ 标号

    • 功能:(CX / ECX) - 1 → CX / ECX
      若 (CX / ECX) 不为 0,且 ZF=1,则转标号处执行。

    • 等于 0 转移指令,本指令对标志位无影响

    • 例子:判断以 BUF 为首址的 10 个字节中是否有非 0 字节。有,则置 ZF 为 0, 否则 ZF 置为 1 。

      1
      2
      3
      4
      5
        MOV CX, 10
      MOV BX, OFFSET BUF -1
      L3:
      INC BX
      CMP BYTE PTR [BX], 0 LOOPE L3
  • LOOPNE / LOOPNZ 标号

    • 功能:(CX / ECX) - 1 → CX / ECX
      若 (CX / ECX) != 0,且 ZF=0,则转标号处执行。
    • 例子:判断以 BUF 为首址的 10 个字节中是否有空格字节。
  • JCXZ / JECXZ 标号

    • 若 (CX/ECX) 为 0,则转移

子程序设计

子程序格式:

1
2
3
4
5
子程序名 PROC [类型]
过程体
子程序名 ENDP
; 类型:FAR 和 NEAR (缺省)
; NEAR 为段内调用,即主子程序在同一个代码段内。FAR 类型为段间调用,被另外代码段调用的过程可定义为FAR

子程序的调用和返回(CALL 和 RET)

名称JMP 格式CALL 格式
段内直接JMP 标号CALL 标号
段间直接JMP 标号 (FAR)CALL 标号 (FAR)
  • 直接调用

    • 段内直接调用
      • 格式:CALL 子程序名
      • 功能:
        (IP/EIP) -> (SP/ESP)
        目的地址 EA -> IP/EIP
    • 段间直接调用
      • 格式:CALL FAR PTR 子程序名
      • 功能:
        a.(CS)→↓(SP/ESP)
        b.(IP/EIP)→↓(SP/ESP)
        c.目的地址的段首址→ CS
        d.目的地址的EA→IP/EIP
  • 间接调用

    • 段内间接调用
      • 格式:CALL WORD PTR OPD(16位段)
        CALL DWORD PTR OPD(32位段)
      • 功能:(IP/EIP)→↓(SP/ESP)
        (OPD)→IP/EIP
    • 段间间接调用
      • 格式:CALL DWORD PTR OPD(16位段)
        CALL FWORD PTR OPD(32 位段)
      • 功能:
        a.(CS)→↓(SP/ESP)
        b.(IP/EIP)→↓(SP/ESP)
        c.(OPD)→IP/EIP
        d.(OPD+2/4)→CS
      • 注:OPD寻址方式与JMP类似
        ①段内间接调用可用除立即方式以外的其它寻址方式;
        ②段间间接调用可用除立即方式和寄存器寻址方式以外的其它寻址方式;
        间接调用时,子程序的入口地址可由寻址方式得到。
      • 应用:多个子程序入口地址组成地址表时,可用寻址方式确定转入子程序的入口地址。
  • 返回指令 RET

    • 格式:RET / RET n
    • 功能:
      a. 段内返回: ↑(SP)→IP/EIP
      b. 段间返回: ↑(SP)→IP/EIP, ↑(SP)→CS

子程序调用现场的保护方法

调用现场的保护与恢复:

  • 保护现场:主要指调用子程序时,主程序中使用的寄存器的值不因子程序的调用而被破坏。
  • 可在主程序做,也能在子程序做

主程序与子程序的参数传递:

  • 参数传递 : 主程序为子程序提供入口参数,子程序返回结果给主程序

  • 寄存器法: 将所需参数放在寄存器中带入子程序。适合于参数少的情况。

    • 优点:传递信息快,编程简单方便,节省存贮单元,但参数不能太多,要避免出错。
    • 注意的问题:出口参数是子程序交给主程序的处理结果,没有必要将其列在需要保护的现场寄存器之中。而入口参数是否要保护,可依实际情况事先约定。
  • 堆栈法:堆栈法指将传递的参数放在堆栈中,进入子程序或返回主程序后,再将参数从堆栈中一一取出送入指定的寄存器。

    • 当参数个数较多时,一般用堆栈法传递参数,在使用堆栈时要特别注意栈顶的变化,要收回堆栈中传递参数的单元。
  • 约定单元法:将数据与运行好的结果放入事先规定好的存贮单元中。

例子:子程序RADIX,将EAX中的32位无符号二进制数转换为(EBX)所指定进制的ASCII码送入(SI)所指定的偏移地址为首地址的存储区中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
; 寄存器法
.386
RADIX PROC
PUSH CX
PUSH EDX
MOV CX, 0
LOP1:
MOV EDX, 0
DIV EBX
PUSH DX
INC CX
OR EAX, EAX
JNE LOP1
NEXT:
POP AX
CMP AX, 10
JB L2
ADD AL, 7
L2:
ADD AL, 30H ;
MOV [SI], AL
INC SI
LOOP NEXT
POP EDX
POP CX
RET
RADIX ENDP

; 约定单元法
RADIX PROC
PUSH CX
PUSH EDX
PUSH EBX
PUSH EAX
PUSH SI
MOVZX EBX,NUM
MOV EAX,NUM1
MOV SI , NUM+2
MOV CX, 0
L1:
MOV EDX, 0
DIV EBX
PUSH DX
INC CX
OR AX,AX
JNE L1
NEXT:
POP AX
CMP AX, 10
JB L2
ADD AL, 7
L2:
ADD AL, 30H
MOV [SI], AL
INC SI
LOOP NEXT
MOV NUM+4 , SI
POP SI
POP EAX
POP EBX
POP EDX
POP CX
RET
RADIX ENDP

; 堆栈法
RADIX PROC
PUSH CX
PUSH EDX
PUSH EAX
PUSH EBX
PUSH BP
MOV BP, SP
MOV SI, 18[BP]
MOV EAX,20[BP]
MOV EBX,24[BP]
MOV CX,0 ; -----(1)
L1:
MOV EDX,0
DIV EBX
PUSH DX
INC CX
OR AX, AX
JNE L1
NEXT:
POP AX ; ---(2)
CMP AX,10
JB L2
ADD AL,7
L2:
ADD AL, 30H
MOV [SI],AL
INC SI
LOOP NEXT
POP BP
POP EBX
POP EAX
POP EDX
POP CX
RET 10 ; ----(3) 把栈内10个字节的空间都释放
RADIX ENDP

第 5 章 程序设计的其他方法和技术

宏指令的定义与调用

模块化程序设计方法及连接技术


评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×