第七章 汇编语言基础
7.1 汇编语言概述
7.1.1 汇编语言的基本概念
汇编语言(Assembly Language)是一种面向机器的低级程序设计语言,它使用助记符来表示机器指令,使用符号地址代替机器地址,是机器语言(二进制代码)的符号化表示。
汇编语言与机器语言之间存在一一对应的关系,汇编语言程序需要通过汇编程序(Assembler)翻译成机器语言程序才能在计算机上执行。这种翻译过程称为汇编。
汇编语言的主要特点:
优点: - 执行效率高:汇编语言程序经汇编后生成机器码,执行速度与机器语言相同 - 占用存储空间小:程序员可以直接控制存储空间的使用 - 可以直接访问硬件资源:便于编写设备驱动程序和嵌入式程序 - 精确控制程序执行:适合对时间和空间要求严格的场合
缺点: - 可移植性差:不同CPU架构的汇编语言不同 - 编程效率低:编写和调试困难,开发周期长 - 可读性差:程序难以理解维护 - 容易出错:需要程序员管理大量细节
7.1.2 汇编语言的应用领域
尽管高级语言已经成为主流,汇编语言在以下领域仍然不可替代:
系统软件开发: - 操作系统内核:启动代码、中断处理、进程调度 - 编译器:代码生成、运行时库 - 设备驱动程序:直接与硬件交互
嵌入式系统: - 对代码体积和执行速度要求严格的嵌入式应用 - 启动引导程序(Bootloader) - 实时控制系统
性能关键代码: - 加密算法核心 - 数字信号处理 - 视频编解码 - 高性能计算库
逆向工程与安全: - 病毒分析 - 漏洞挖掘 - 软件破解与保护
7.2 汇编语言程序结构
7.2.1 汇编语言源程序的组成
一个完整的汇编语言源程序通常由以下几个部分组成:
指令语句:是可执行语句,汇编后生成对应的机器码,在程序运行时执行。 格式:[标号:] 操作码 [操作数] [;注释]
伪指令语句:是不可执行语句,用于指示汇编程序如何进行汇编,不生成机器码。 格式:[名字] 伪操作 [参数] [;注释]
宏指令:是用户自定义的指令序列,汇编时会被展开成多条指令。
注释:以分号“;”开头,用于说明程序的功能,增强可读性。
7.2.2 汇编语言的基本语法元素
标识符:是程序员定义的符号名称,用于表示常量、变量、标号、段名等。 - 由字母、数字和下划线组成 - 不能以数字开头 - 不能使用汇编语言的保留字 - 不区分大小写(某些汇编器区分)
常量:是固定不变的数值,可以是: - 数值常量:二进制(后缀B)、八进制(后缀Q或O)、十进制(默认或后缀D)、十六进制(后缀H) - 字符常量:用单引号括起来的ASCII字符 - 字符串常量:用引号括起来的字符序列
变量:是存储器中数据的符号地址,具有类型属性(字节、字、双字等)。
标号:是指令的符号地址,代表该指令在存储器中的位置。标号后面通常跟冒号。
表达式:由常量、变量、标号和运算符组成的式子,汇编时计算其值。
7.2.3 汇编语言的语句格式
典型的汇编语句格式如下:
``` [标号] 操作码/伪操作 [操作数] [;注释] ```
标号字段:可选,用于标识该语句的地址。标号可以被其他指令引用。
操作码字段:必需,指明指令或伪操作的操作类型。
操作数字段:根据指令的要求,可以有零个、一个或两个操作数。多个操作数之间用逗号分隔。
注释字段:以分号开始,直到行尾。注释对程序执行没有影响,仅用于说明。
7.3 伪指令
7.3.1 数据定义伪指令
数据定义伪指令用于为数据分配存储空间并初始化。
DB(Define Byte):定义字节数据,每个数据占1字节。 ``` DATA1 DB 10H ;定义一个字节,值为10H DATA2 DB 'ABCD' ;定义4个字节的字符串 ```
DW(Define Word):定义字数据(2字节),低位字节在前。 ``` DATA3 DW 1234H ;定义一个字,值为1234H ```
DD(Define Doubleword):定义双字数据(4字节)。 ``` DATA4 DD 12345678H ;定义一个双字 ```
DQ(Define Quadword):定义四字数据(8字节)。
DT(Define Ten Bytes):定义十字节数据(10字节),常用于BCD码。
DUP操作符:用于重复定义相同的数据。 ``` BUFFER DB 100 DUP(0) ;定义100个字节的缓冲区,初始化为0 TABLE DW 10 DUP(?) ;定义10个字的表,不初始化 ```
7.3.2 符号定义伪指令
EQU(Equate):为符号定义一个常量值,该值在程序中不能改变。 ``` COUNT EQU 100 ;定义符号COUNT等于100 PORT EQU 3F8H ;定义符号PORT等于3F8H ```
=(等号):与EQU类似,但允许重新定义。 ``` VALUE = 10 VALUE = 20 ;允许重新定义 ```
LABEL:为当前位置定义一个具有指定类型的标号。 ``` BUFFER LABEL WORD ;BUFFER为字类型 BUFFER DB 100 DUP(0);实际分配100个字节 ```
7.3.3 段定义伪指令
段定义伪指令用于定义程序的逻辑段。
SEGMENT/ENDS:定义段的开始和结束。 ``` CODE SEGMENT ;代码段开始
...
CODE ENDS ;代码段结束 ```
ASSUME:告诉汇编程序各段寄存器与逻辑段的对应关系。 ``` ASSUME CS:CODE, DS:DATA, SS:STACK ```
注意:ASSUME只是说明性的,实际的段寄存器赋值需要用MOV指令完成。
GROUP:将多个段组合成一个段组,使它们共享同一个段地址。
7.3.4 过程定义伪指令
过程(子程序)的定义使用PROC和ENDP伪指令。
``` 过程名 PROC [NEAR/FAR]
;过程体 RET
过程名 ENDP ```
NEAR:近过程,与调用程序在同一代码段内,返回时只恢复IP。
FAR:远过程,与调用程序在不同代码段,返回时恢复CS和IP。
7.3.5 其他常用伪指令
ORG(Origin):指定后续代码或数据的起始偏移地址。 ``` ORG 100H ;从偏移100H开始存放数据或代码 ```
END:表示源程序结束。 ``` END [标号] ;可选标号指明程序入口点 ```
PUBLIC:说明本模块定义的符号可供其他模块使用。
EXTRN:说明本模块使用的符号在其他模块中定义。
INCLUDE:将指定的文件插入到当前位置。
7.4 汇编语言指令系统
7.4.1 数据传送指令详解
MOV指令:最基本的传送指令,格式为MOV 目的, 源。 规则: - 两个操作数不能同时为存储器操作数 - 两个操作数不能同时为段寄存器 - 目的操作数不能为立即数 - 不能向CS传送数据 - 立即数不能直接传送给段寄存器
PUSH/POP指令:堆栈操作指令。 - PUSH 源:将字或双字压入堆栈,SP=SP-2(或4) - POP 目的:从堆栈弹出字或双字,SP=SP+2(或4) 规则:操作数不能是立即数。
XCHG指令:交换指令,交换两个操作数的值。 规则:不能交换两个存储器操作数,操作数不能是立即数。
LEA指令:装入有效地址。 ``` LEA BX, BUFFER ;将BUFFER的偏移地址送入BX ```
LDS/LES指令:装入段地址和偏移地址。 ``` LDS SI, [BX] ;将BX所指的双字装入DS:SI ```
XLAT指令:查表转换指令。 ``` XLAT ;AL ← [BX+AL] ``` 执行前:BX=表首地址,AL=索引值 执行后:AL=查表所得值
7.4.2 算术运算指令详解
加法指令: - ADD 目的, 源:目的 = 目的 + 源 - ADC 目的, 源:目的 = 目的 + 源 + CF(带进位加) - INC 目的:目的 = 目的 + 1
减法指令: - SUB 目的, 源:目的 = 目的 - 源 - SBB 目的, 源:目的 = 目的 - 源 - CF(带借位减) - DEC 目的:目的 = 目的 - 1 - NEG 目的:目的 = 0 - 目的(取负) - CMP 目的, 源:目的 - 源,结果不保存,只影响标志位
乘法指令: - MUL 源(无符号乘法)
- 字节乘法:AX = AL × 源
- 字乘法:DX:AX = AX × 源
- IMUL 源(带符号乘法)
除法指令: - DIV 源(无符号除法)
- 字节除法:AL = AX ÷ 源的商,AH = 余数
- 字除法:AX = DX:AX ÷ 源的商,DX = 余数
- IDIV 源(带符号除法)
【例题】编写程序计算:(A + B) × C - D,其中A、B、C、D都是字节变量。
``` DATA SEGMENT
A DB 10 B DB 20 C DB 5 D DB 15 RESULT DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA MOV DS, AX MOV AL, A ;AL = A ADD AL, B ;AL = A + B MUL C ;AX = (A + B) × C SUB AX, D ;AX = (A + B) × C - D MOV RESULT, AX ;保存结果 MOV AH, 4CH INT 21H
CODE ENDS END START ```
7.4.3 逻辑运算与移位指令详解
逻辑运算指令: - AND 目的, 源:按位与,用于清零某些位 - OR 目的, 源:按位或,用于置位某些位 - XOR 目的, 源:按位异或,用于取反某些位或清零寄存器 - NOT 目的:按位取反 - TEST 目的, 源:测试,执行AND但不保存结果
移位指令: - SHL 目的, 计数值:逻辑左移,低位补0,高位进CF - SHR 目的, 计数值:逻辑右移,高位补0,低位进CF - SAL 目的, 计数值:算术左移(同SHL) - SAR 目的, 计数值:算术右移,高位补符号位
循环移位指令: - ROL 目的, 计数值:循环左移,高位到低位和CF - ROR 目的, 计数值:循环右移,低位到高位和CF - RCL 目的, 计数值:带进位循环左移 - RCR 目的, 计数值:带进位循环右移
【例题】将AL中的高4位与低4位互换。 ``` MOV CL, 4 ROL AL, CL ;循环左移4位 ```
【例题】将AX中的内容乘以10(不使用乘法指令)。 ``` SHL AX, 1 ;AX = AX × 2 MOV BX, AX ;BX = AX × 2 SHL AX, 1 ;AX = AX × 4 SHL AX, 1 ;AX = AX × 8 ADD AX, BX ;AX = AX × 8 + AX × 2 = AX × 10 ```
7.4.4 控制转移指令详解
无条件转移指令: - JMP 目标:无条件跳转到目标地址
- 短转移:JMP SHORT 标号(-128~+127字节)
- 近转移:JMP NEAR PTR 标号(同一代码段)
- 远转移:JMP FAR PTR 标号(不同代码段)
条件转移指令:所有条件转移都是短转移(在386及以上处理器中可以是近转移)。
根据单个标志位判断: - JC/JNC:CF=1/0时转移 - JE/JZ:ZF=1时转移 - JNE/JNZ:ZF=0时转移 - JS/JNS:SF=1/0时转移 - JO/JNO:OF=1/0时转移 - JP/JNP:PF=1/0时转移
根据两个标志位判断(无符号数比较): - JA/JNBE:高于转移(CF=0且ZF=0) - JAE/JNB:高于等于转移(CF=0) - JB/JNAE:低于转移(CF=1) - JBE/JNA:低于等于转移(CF=1或ZF=1)
根据两个标志位判断(有符号数比较): - JG/JNLE:大于转移(ZF=0且SF=OF) - JGE/JNL:大于等于转移(SF=OF) - JL/JNGE:小于转移(SF≠OF) - JLE/JNG:小于等于转移(ZF=1或SF≠OF)
循环控制指令: - LOOP 标号:CX=CX-1,若CX≠0则转移 - LOOPE/LOOPZ 标号:CX=CX-1,若CX≠0且ZF=1则转移 - LOOPNE/LOOPNZ 标号:CX=CX-1,若CX≠0且ZF=0则转移 - JCXZ 标号:若CX=0则转移
子程序调用与返回: - CALL 目标:调用子程序
- 近调用:将IP压栈
- 远调用:将CS和IP压栈
- RET [n]:从子程序返回
- 近返回:从栈弹出IP
- 远返回:从栈弹出IP和CS
- 可选的n表示返回后再将SP加上n(用于清除参数)
7.5 汇编语言程序设计方法
7.5.1 顺序结构程序设计
顺序结构是最基本的程序结构,指令按顺序逐条执行。
【例题】计算表达式:Z = (X + Y) × (X - Y) ``` DATA SEGMENT
X DW 50 Y DW 30 Z DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA MOV DS, AX MOV AX, X ;AX = X ADD AX, Y ;AX = X + Y MOV BX, AX ;BX = X + Y MOV AX, X ;AX = X SUB AX, Y ;AX = X - Y IMUL BX ;DX:AX = (X+Y) × (X-Y) MOV Z, AX ;假设结果不溢出,只保存低16位 MOV AH, 4CH INT 21H
CODE ENDS END START ```
7.5.2 分支结构程序设计
分支结构根据条件判断选择不同的执行路径。
【例题】求两个有符号数中的较大值。 ``` DATA SEGMENT
NUM1 DW -100 NUM2 DW 50 MAX DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA MOV DS, AX MOV AX, NUM1 CMP AX, NUM2 ;比较NUM1和NUM2 JGE GREATER ;若NUM1 ≥ NUM2,跳转到GREATER MOV AX, NUM2 ;否则,AX = NUM2
GREATER:
MOV MAX, AX ;保存较大值 MOV AH, 4CH INT 21H
CODE ENDS END START ```
【例题】根据AL中的值(0-9)转移到不同的处理程序。 ```
CMP AL, 0 JB ERROR CMP AL, 9 JA ERROR MOV BX, OFFSET JUMP_TABLE MOV AH, 0 SHL AX, 1 ;AX = AL × 2(字表索引) ADD BX, AX JMP [BX] ;跳转到对应的处理程序
JUMP_TABLE:
DW HANDLE_0 DW HANDLE_1 ; ... DW HANDLE_9
```
7.5.3 循环结构程序设计
循环结构用于重复执行某段代码。
【例题】计算1 + 2 + 3 + … + 100。 ``` DATA SEGMENT
SUM DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA MOV DS, AX MOV AX, 0 ;累加器清零 MOV CX, 100 ;循环计数器 MOV BX, 1 ;当前加数
LOOP_START:
ADD AX, BX ;累加 INC BX ;加数加1 LOOP LOOP_START ;CX减1,若不为0则继续循环 MOV SUM, AX MOV AH, 4CH INT 21H
CODE ENDS END START ```
【例题】在数组中查找最大值。 ``` DATA SEGMENT
ARRAY DW 23, 56, 12, 89, 34, 78, 45, 67 COUNT EQU ($ - ARRAY) / 2 MAX DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA MOV DS, AX LEA SI, ARRAY MOV AX, [SI] ;假设第一个元素最大 MOV CX, COUNT-1 ;比较次数 ADD SI, 2 ;指向下一个元素
FIND_MAX:
CMP AX, [SI] ;比较当前最大值与下一个元素 JGE NEXT MOV AX, [SI] ;更新最大值
NEXT:
ADD SI, 2 LOOP FIND_MAX MOV MAX, AX MOV AH, 4CH INT 21H
CODE ENDS END START ```
7.5.4 子程序设计
子程序(过程)是完成特定功能的独立代码段,可以被多次调用。
【例题】编写子程序计算N的阶乘(N!)。 ``` ;子程序:计算N的阶乘 ;入口:CX = N(N ≤ 8,确保结果在16位内) ;出口:AX = N! FACTORIAL PROC
PUSH BX PUSH CX MOV AX, 1 ;阶乘结果初始化为1
FACT_LOOP:
CMP CX, 1 JLE FACT_DONE MUL CX ;AX = AX × CX LOOP FACT_LOOP
FACT_DONE:
POP CX POP BX RET
FACTORIAL ENDP ```
【例题】主程序调用阶乘子程序计算5! + 6!。 ``` DATA SEGMENT
RESULT DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA
START:
MOV AX, DATA MOV DS, AX MOV CX, 5 CALL FACTORIAL ;计算5!,结果在AX MOV BX, AX ;保存5! MOV CX, 6 CALL FACTORIAL ;计算6!,结果在AX ADD AX, BX ;AX = 5! + 6! MOV RESULT, AX MOV AH, 4CH INT 21H
FACTORIAL PROC
;...(同上)
FACTORIAL ENDP
CODE ENDS END START ```
7.6 汇编语言与高级语言的接口
7.6.1 汇编语言与C语言的混合编程
在系统编程和性能关键代码中,经常需要在C语言程序中嵌入汇编代码,或将汇编语言编写的模块与C语言程序链接。
内联汇编(Inline Assembly):在C/C++代码中直接嵌入汇编指令。
GCC内联汇编格式: ```c asm [volatile] (“汇编指令”
: 输出操作数
: 输入操作数
: 被破坏的寄存器);
```
示例: ```c int a = 10, b = 20, result; asm volatile (
"addl %%ebx, %%eax" : "=a" (result) ;输出:result = EAX : "a" (a), "b" (b) ;输入:EAX = a, EBX = b
); ```
独立汇编模块:将汇编代码编译成目标文件,然后与C程序链接。
汇编模块示例(x86-64,System V AMD64 ABI): ```asm ; add.asm - 实现加法函数 section .text global add_numbers
add_numbers:
mov rax, rdi ;第一个参数在RDI add rax, rsi ;第二个参数在RSI ret ;结果在RAX返回
```
对应的C程序: ```c main.c extern int add_numbers(int a, int b); int main() { int result = add_numbers(10, 20); return 0; } ``` ==== 7.6.2 调用约定 ==== 调用约定规定了函数调用时参数的传递方式、寄存器的使用规则和栈的清理责任。 x86-32常用调用约定: - cdecl:参数从右向左压栈,调用者清理栈,返回值在EAX - stdcall:参数从右向左压栈,被调用者清理栈,Win32 API使用 - fastcall:前两个参数通过ECX和EDX传递,其余压栈 x86-64 System V AMD64 ABI(Linux/Unix): - 参数通过寄存器传递:RDI, RSI, RDX, RCX, R8, R9 - 额外的参数压栈 - 返回值在RAX(64位)或RAX:RDX(128位) - 被调用者保存的寄存器:RBX, RBP, R12-R15 x86-64 Windows x64调用约定: - 参数通过寄存器传递:RCX, RDX, R8, R9 - 调用者分配32字节的“阴影空间” - 返回值在RAX ===== 7.7 本章重点总结 ===== 核心概念: - 汇编语言是机器语言的符号化表示,与机器语言一一对应 - 汇编语言源程序由指令语句、伪指令语句和宏指令组成 - 汇编程序的功能是将汇编语言翻译成机器语言 关键语法: - 数据定义伪指令:DB、DW、DD、DUP - 符号定义伪指令:EQU、= - 段定义伪指令:SEGMENT、ENDS、ASSUME - 过程定义伪指令:PROC、ENDP 指令分类: - 数据传送:MOV、PUSH/POP、XCHG、LEA、XLAT - 算术运算:ADD、SUB、MUL、DIV、INC、DEC、CMP - 逻辑运算:AND、OR、XOR、NOT、TEST - 移位:SHL、SHR、SAR、ROL、ROR、RCL、RCR - 控制转移:JMP、Jcc、CALL、RET、LOOP 程序结构: - 顺序结构:按顺序执行 - 分支结构:根据条件选择路径 - 循环结构:重复执行代码块 - 子程序结构:模块化设计,代码复用 混合编程: - 内联汇编:在C/C++中嵌入汇编代码 - 独立模块:汇编模块与C程序链接 - 调用约定:规定参数传递和寄存器使用规则 学习建议: - 掌握寻址方式和指令系统 - 熟悉常用伪指令的用法 - 练习编写基本程序结构(顺序、分支、循环、子程序) - 理解汇编与高级语言的接口机制