Intel80x86汇编语言

文章目录

  • 汇编语言基础
    • 寄存器
      • 通用寄存器
      • 标志寄存器
      • 段寄存器
    • 存储器组织
    • 指令代码格式
    • 语句格式
      • 标号与名字
      • 助记符
      • 操作数和参数
      • 注释和分隔符
    • 源程序框架
    • 开发过程
  • 数据表示和寻址
    • 常量表达
    • 变量应用
      • 变量定义
        • 变量定义伪指令
        • 字节量数据
        • 字量数据
        • 双字量数据
        • 变量定位
      • 变量属性
        • 地址操作符
        • 类型操作符
    • 数据寻址方式
      • 立即数寻址方式
      • 寄存器寻址方式
      • 存储器寻址方式
        • 段寄存器的默认和超越(Override)
        • 偏移地址的组成
        • 直接寻址
        • 寄存器间接寻址
        • 寄存器相对寻址
        • 变址寻址
  • 通用数据处理指令
    • 数据传送类指令
      • 通用数据传送指令
      • 堆栈操作指令
      • 其他传送指令
    • 算术运算类指令
      • 状态标志
      • 加法指令
      • 减法指令
      • 乘法和除法指令
      • 其他运算指令
    • 位操作类指令
      • 逻辑运算指令
      • 移位指令
  • 程序结构
    • 顺序程序结构
    • 分支程序结构
      • 无条件转移指令
      • 条件转移指令
      • 单分支程序结构
      • 双分支程序结构
      • 多分支程序结构
    • 循环程序结构
      • 循环指令
      • 计数控制循环
      • 条件控制循环
      • 多重循环
  • 模块化程序设计
    • 子程序结构
    • 参数传递
      • 寄存器传递参数
      • 共享变量传递参数
      • 堆栈传递参数
    • 多模块程序结构
      • 源文件包含
      • 模块连接
      • 子程序库
    • 宏结构
      • 宏汇编
      • 重复汇编
      • 条件汇编

汇编语言基础

寄存器

IA-32常用寄存器:

通用寄存器:EAX EBX ECX EDX ESI EDI EBP ESP

标志寄存器指令指针:EFLAGS EIP

段寄存器:CS SS DS ES FS GS

通用寄存器

通用寄存器参考

寄存器名称 含义 作用
EAX 累加器(Accumulator) 使用频度最高,用于算术运算、逻辑运算以及与外设传送信息等
EBX 基址寄存器(Base Address Register) 常用来存放存储器地址,以方便指向变量或数组中的元素
ECX 计数器(Counter) 常作为循环操作等指令中的计数器
EDX 数据寄存器(Data Register) 可用来存放数据,其中低16位DX常用来存放外设端口地址
ESI 源变址寄存器(Source Index Register) 用于指向字符串或数组的源操作数
EDI 目的变址寄存器(Destination Index Register) 用于指向字符串或数组的目的操作数
EBP 基址指针寄存器(Base Pointer Register) 默认情况下指向程序堆栈区域的数据,主要用于在子程序中访问通过堆栈传递的参数和局部变量
ESP 堆栈指针寄存器(Stack Pointer Register) 专用于指向程序堆栈区域顶部的数据,在涉及堆栈操作的指令中会自动增加或减少

标志寄存器

(1)状态标志
状态标志有6个,处理器主要使用其中5个。从低位到高位依次是:进位标志CF(Carry Flag)、奇偶标志PF(Parity Flag)、调整标志AF(Adjust Flag)、零标志ZF (Zero Flag)、符号标志SF(Sign Flag)、溢出标志OF(Overflow Flag)
(2)控制标志
IA-32处理器只有一个控制标志:方向标志 DF(Direction Flag),该标志仅用于串操作指令中,控制地址的变化方向。
(3)系统标志
如中断允许标志IF(Interrupt-enable Flag),陷阱标志TF(Trap Flag)

段寄存器

16位80x86处理器设计有4个16位段寄存器:代码段寄存器CS、堆栈段寄存器SS、数据段寄存器DS和附加段寄存器ES。

IA-32处理器又增加了两个同样是16位的段寄存器:FS和GS,它们都属于数据段性质的段寄存器。

存储器组织

(1)平展存储模型
存储器是一个连续的地址空间,称为线性地址空间。程序需要的代码、数据和堆栈都包含在这个地址空间中。线性地址空间也以字节为基本存储单位,即每个存储单元保存一个字节且具有一个地址,这个地址称为线性地址(LinearAddress)。IA-32处理器支持的线性地址空间是0~232-1(4GB容量)。
(2)段式存储模型
存储器由一组独立的地址空间组成,这个地址空间称为段(Segment)。通常,代码、数据和堆栈位于分开的段中。程序利用逻辑地址( Logical Address)寻址段中的每个字节单元,每个段都可以达到4GB。
(3)实地址存储模型
实地址存储模型(Real- address Mode Memory Model)是8086处理器的存储模型。IA-32处理器之所以支持这种存储模型,是为了兼容原来为8086处理器编写的程序。

指令代码格式

IA-32处理器代码一般包含:可选的指令前缀(0 ~ 4字节)、主要操作码(1~3字节)、可选的寻址方式域(包括ModR/M和SIB字段,各为0或1字节)、可选的位移量(0、1、2或4字节)和可选的立即数(0、1、2或4字节)。
指令前缀和主要操作码字段对应指令的操作码部分,其他字段对应指令的操作数部分。

mov dest,src	;src为源操作数,dest为目的操作数
mov eax,ebx	;将寄存器EBX的数据传送到EAX寄存器
mov eax,[ebx]	;将由EBX指明偏移地址的存储器内的数据传送到EAX
mov eax,es:[ebx]	;数据不在默认的DS数据段,使用段超越前缀显式说明(此数据在ES段)
mov eax,[ebx+esi*4+80h]	;数据来自主存数据段,偏移地址由ESI内容乘4加EBX再加80H组成

语句格式

执行性语句:标号:处理器指令助记符 操作数,操作数 ;注释

说明性语句:名字 伪指令助记符 参数,参数,... ;注释

标号与名字

在执行性语句中,冒号前的标号表示处理器指令在主存中的逻辑地址,主要用于指示分支循环等程序的目的地址,可有可无。说明性语句中的名字可以是变量名、段名、子程序名等,反映变量、段和子程序等的逻辑地址。

标号采用冒号分隔处理器指令,名字采用空格或制表符分隔伪指令。

助记符

字节变量定义伪指令 助记符:BYTE(或DB)

msg byte 'Hello,Assembly!' ,13,10,0 
;用BYTE伪指令定义一个字符串,并使用变量名MSG表达其在主存中的逻辑地址,字符串最后的0表示字符串结束

;操作符OFFSET获得偏移地址
mov eax,offset msg  ;EAX获得MSG的偏移地址

操作数和参数

mov eax,offset msg ;EAX是寄存器形式的目的操作数,OFFSET MSG是常量形式的源操作数,经汇编后转换为一个具体的偏移地址。

伪指令的参数可以是常量、变量名、表达式等,可以有多个,参数之间用逗号分隔。例如,在'Hello,Assembly!',13,10,0 示例中,参数包括用单引号表达的字符串“Hello,Assembly!”、常量13和10(这两个常量在ASCII码表中分别表示回车和换行控制字符,其作用相当于C语言的 \n)、一个数值0(作为字符串结尾)。

注释和分隔符

MASM支持续行符\,表示本行内容与上一行内容属于同一个语句。注释可以使用英文书写,在支持汉字的编辑环境当然也可以使用汉字进行程序注释,但注意分隔符都必须使用英文标点,否则无法通过汇编。

源程序框架

一个程序模板:

; eg0000.asm in Windows Console
	include io32.inc	; 包含32位输出输出文件
	.data				; 定义数据段
	......				; 数据定义(数据待填)
	.code				; 定义代码段
start:					; 程序执行起始位置
	......				; 主程序(指令待填)
	exit 0				; 程序正常执行结束
	......				; 子程序(指令待填)
	end start			; 汇编结束

[例1-1] 信息显示程序

; eg0101.asm in Windows Console
	include io32.inc
	.data
msg	byte 'Hello, Assembly!',13,10,0	;字符串
	.code
start:
	mov eax,offset msg	;指定字符串的偏移地址
	call dispmsg ;调用IO子程序显示信息 DISPMSG需要在调用前设置EAX等于字符串在主存的偏移地址。

	exit 0
	end start

IO32.LIB子程序库文件和IO32.INC包含文件中常用I/O子程序:

C语言格式符 子程序名 参数及功能说明
printf("%s",a) DISPMSG 入口参数:EAX=字符串地址 功能说明:显示字符串(以0结尾)
printf("%c",a) DISPC 入口参数:AL=字符的ASCII码 功能说明:显示一个字符
printf("\n") DISPCRLF 功能说明:光标回车换行,到下一行首位置
DISPRD 功能说明:显示8个32位通用寄存器内容(十六进制)
DISPRF 功能说明:显示6个状态标志的状态
printf("%lX",a) DISPHD 入口参数:EAX =32位数据 功能说明:以十六进制形式显示8位数据
printf("%lu",a) DISPUID 入口参数:EAX =32位数据 功能说明:显示无符号十进制整数
printf("%ld",a) DISPSID 入口参数:EAX =32位数据 功能说明:显示有符号十进制整数
scanf("%s",a) READMSG 入口参数:EAX=缓冲区地址 功能说明:输入一个字符串(回车结束) 出口参数:EAX=实际输入的字符个数(不含结尾字符0),字符串以0结尾
scanf("%c",&a) READC 出口参数:AL=字符的ASCHII码功能说明:输入一个字符(回显)
scanf("%lX",&a) READHD 出口参数:EAX=32位数据 功能说明:输入8位十六进制数据
scanf("%lu",&a) READUID 出口参数:EAX=32位数据 功能说明:输入无符号十进制整数(≤232-1)
scanf("%ld",&a) READSID 出口参数:EAX=32位数据 功能说明:输人有符号十进制整数(-231~231-1)

调用这些子程序的格式

mov eax,入口参数
call 子程序名

开发过程

编辑、编译(汇编)、连接

首先,用一个文本编辑器形成一个以 ASM为扩展名的源程序文件;
然后,用汇编程序翻译源程序,将ASM文件转换为OBJ目标模块文件;
最后,用连接程序将一个或多个目标文件(含. LIB库文件)连接成一个.EXE可执行文件。

make32 eg0101.asm
eg0101

Intel80x86汇编语言_第1张图片
其中,ML表示运行ML.EXE程序(保存在BIN子目录,如果已经建立搜索路径,则可以省略“BIN\”),参数/c表示仅利用ML实现源程序的汇编,参数coff(小写字母)表示生成COFF (Common Object File Format)格式的目标模块文件。

COFF是32位Windows和UNIX操作系统使用的目标文件格式。上述两个参数必须有,注意参数之间一定要用空格分隔。参数也可以使用短线引导,例如:
BIN\ML -c -coff eg0101.asm
如果源程序中没有语法错误,MASM将自动生成一个目标模块文件(ECO101.OBJ),否则MASM将给出相应的错误信息。这时应根据错误信息,重新编辑修改源程序文件后,再进行汇编。

连接程序能把一个或多个目标文件和库文件合成一个可执行文件。在MASM目录下有了EGO101.OBJ文件,键入如下命令实现目标文件的连接:
BIN\LINK32 /subsystem:console eg0101.obj
其中,参数/subsystem:console必须有,表示生成Windows 控制台(Console)环境的可执行文件。如果生成图形窗口的可执行文件,则应该使用“/ subsystem: windows”参数。
如果连接过程没有错误,将自动生成一个可执行文件(ECO101.EXE)

Intel80x86汇编语言_第2张图片

汇编程序ML和连接程序LINK支持很多参数,以便控制汇编和连接过程,用/?参数就可以看到帮助信息。例如,ML可以用空格分隔多个ASM源程序文件,以便一次性汇编多个源文件。LINK也可以将多个模块文件连接起来(用加号“+”分隔),形成一个可执行文件;还可以带LIB库文件进行连接。

再如,ML的参数/Fl表示生成列表文件;要在调试程序中直接使用程序定义的各种标识符,可在ML命令中增加参数/Zi,LINK命令中增加参数/debug,表示生成调试用的符号信息。

atest.lst

Microsoft (R) Macro Assembler Version 6.15.8803		    01/26/21 21:46:39
atest.asm						     Page 1 - 1


				; eg0101.asm in Windows Console
					include io32.inc
			      C .nolist
			      C .list
			      C 
 00000000				.data
 00000000 48 65 6C 6C 6F	msg    byte 'Hello, Assembly!',13,10,0	;字符串
	   2C 20 41 73 73
	   65 6D 62 6C 79
	   21 0D 0A 00
 00000000				.code
 00000000			start:
 00000000  B8 00000000 R		mov eax,offset msg		;显示
 00000005  E8 00000000 E		call dispmsg

					exit 0
					end start
Microsoft (R) Macro Assembler Version 6.15.8803		    01/26/21 21:46:39
atest.asm						     Symbols 2 - 1




Macros:

                N a m e                 Type

exit . . . . . . . . . . . . . .	Proc


Segments and Groups:

                N a m e                 Size     Length   Align   Combine Class

FLAT . . . . . . . . . . . . . .	GROUP
_DATA  . . . . . . . . . . . . .	32 Bit	 00000013 Para	  Public  'DATA'	
_TEXT  . . . . . . . . . . . . .	32 Bit	 00000011 Para	  Public  'CODE'	


Procedures,  parameters and locals:

                N a m e                 Type     Value    Attr

ExitProcess  . . . . . . . . . .	P Near	 00000000 FLAT	Length= 00000000 External STDCALL


Symbols:

                N a m e                 Type     Value    Attr

@CodeSize  . . . . . . . . . . .	Number	 00000000h   
@DataSize  . . . . . . . . . . .	Number	 00000000h   
@Interface . . . . . . . . . . .	Number	 00000003h   
@Model . . . . . . . . . . . . .	Number	 00000007h   
@code  . . . . . . . . . . . . .	Text   	 _TEXT
@data  . . . . . . . . . . . . .	Text   	 FLAT
@fardata?  . . . . . . . . . . .	Text   	 FLAT
@fardata . . . . . . . . . . . .	Text   	 FLAT
@stack . . . . . . . . . . . . .	Text   	 FLAT
dispbb . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispbd . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispbw . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispcrlf . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispc  . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disphb . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disphd . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disphw . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispmsg  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disprb . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disprd . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disprf . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
disprw . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispsib  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispsid  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispsiw  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispuib  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispuid  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
dispuiw  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
msg  . . . . . . . . . . . . . .	Byte	 00000000 _DATA	
readbb . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readbd . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readbw . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readc  . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readhb . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readhd . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readhw . . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readmsg  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readsib  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readsid  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readsiw  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readuib  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readuid  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
readuiw  . . . . . . . . . . . .	L Near	 00000000 FLAT	External STDCALL
start  . . . . . . . . . . . . .	L Near	 00000000 _TEXT	Public STDCALL

	   0 Warnings
	   0 Errors

为了让调试程序方便进行源程序级调试,汇编时需要增加参数/Zi,连接命令中增加参数/debug。这时,连接过程还将生成增量状态文件(.ILK,微软连接程序的数据库,用于增量连接。重新连接时会提示警告信息,不必理会)和程序数据库文件(.PDB,保存调试和项目状态信息,使用这些信息可以对程序的调试配置进行增量连接)。

数据表示和寻址

常量表达

定义字节变量: byte伪指令

符号定义伪指令EQU = EQU用于数值等价时不能重复定义符号名,但=允许有重复赋值。

[例2-1]数据表达程序

;eg0201.asm
	include io32.inc
	.data
const1	byte 100,100d,01100100b,64h, 'd' ; 用不同的进制和形式表达了100
const2	byte 1,+127,128,-128,255,-1
const3	byte 105,-105,32,-32,32h,-32h  ; 一些典型数据
const4	byte '0123456789', 'abcxyz', 'ABCXYZ' ; 定义字符串
crlf	byte 0dh,0ah,0  ; 0DH和0AH分别是ASCII码表中的回车符和换行符,前导0不能省略,数字0表示字符串结尾,调用显示功能时需要它

minint	= 10 
maxint	equ 0ffh
; 符号常量MININT数值为10,MAXINT数值为255,它们只是一个符号,不占主存空间,应用时可直接将其代表的内容替代

const5	byte minint,minint+5,maxint-5,maxint-minint
const6	byte 4*4,34h+34,67h-52h,52h-67h
	.code
start:
	mov eax,offset const4 ; 代码段从CONST4开始显示,遇到0结束
	call dispmsg

	exit 0
	end start
	
; Output
; 0123456789abcxyzABCXYZ 

变量应用

变量定义

变量定义伪指令

变量名 变量定义伪指令 初值表

变量名即汇编语句名字部分,是用户自定义的标识符,可以省略
初值表是用逗号分隔的参数,由各种形式的常量以及特殊的符号“?”和 DUP组成。“?”表示初值不确定,即未赋初值。如果多个存储单元初值相同,可以用复制操作符DUP进行说明。DUP的格式为:
重复次数 DUP(重复参数)
变量定义伪指令有BYTE、WORD、DWORD、FWORD、QWORD 和TBYTE(早期版本依次是DB、DW、DD、DF、DQ、DT,它们在新版本中也可以使用)

助记符 变量类型 变量定义功能
BYTE 字节 分配一个或多个字节单元;每个数据是字节量,也可以是字符串常量 字节量表示8位无符号数或有符号数、字符的 ASCII 码值
WORD 分配一个或多个字单元;每个数据是字量、16位数据 字量表示16位无符号数或有符号数、16位段选择器、16位偏移地址
DWORD 双字 分配一个或多个双字单元;每个数据是双字量、32位数据 双字量表示32位无符号数或有符号数、32位段基地址、32位偏移地址
FWORD 3个字 分配一个或多个6字节单元 6字节量常表示含16位段选择器和32位偏移地址的48位指针地址
QWORD 4个字 分配一个或多个8字节单元 8字节量表示64位数据
TBYTE 10个字节 分配一个或多个10字节单元,表示 BCD码、10字节数据(用于浮点运算)

汇编语言还支持复杂的数据变量,如结构(Structure)、记录(Record )、联合(Union)等。

字节量数据

BYTE定义的变量是8位字节量(Byte-sized)数据(对应C、C++语言中的char类型)。它可以表示无符号整数0~255、补码表示的有符号整数-128 ~ +127、一个字符(ASCII码值),还可以表达压缩BCD码0 ~ 99、非压缩BCD码0 ~ 9等。

[例2-2]字节变量程序

;eg0202.asm
	include io32.inc
	.data
minint	= 10
bvar1	byte 0,128,255,-128,0,+127
bvar2	byte 1,-1,38,-38,38h,-38h
bvar3	byte ?
bvar4	byte 5 dup ('$')
bvar5	byte minint dup(0),minint dup(minint,?)
		byte 2 dup(2,3,2 dup(4)) ; 无变量名的变量初值依次是:02 03 04 04 02 03 04 04
	.code
start:
	exit 0
	end start

; Output
; 
字量数据

用WORD定义的变量是16位字量(Word-sized)数据(对应C、C++语言中的short类型)。字量数据包含高低两个字节,可以表示更大的数据。实地址方式下的段地址和偏移地址都是16位的,可以用16位变量保存。

[例2-3]字变量程序

;eg0203.asm
	include io32.inc
	.data
minint	= 10
wvar1	word 0,32768,65535,-32768,0,+32767
wvar2	word 1,-1,38,-38,38h,-38h
wvar3	word ?
wvar4	word 2010h,1020h
		word 5 dup(minint,?)
wvar6	word 3139h,3832h
bvar6	byte 39h,31h,32h,38h
		byte 0
	.code
start:
	mov eax,offset wvar6
	call dispmsg
	exit 0
	end start

; Output
; 91289128
双字量数据

用DWORD定义的变量是32位双字量(Doubleword-sized)数据(对应C、C++语言中的long类型),占用4个连续的字节空间,采用小端方式存放。在32位平展存储模型中,32位变量可用于保存32位偏移地址、线性地址或段基地址。

[例2-4]双字变量程序

;eg0204.asm
	include io32.inc
	.data
minint	= 10
dvar1	dword 0,80000000h,0ffffffffh,-80000000h,0,7fffffffh
dvar2	dword 1,-1,38,-38,38h,-38h
dvar3	dword ?
	dword 2010h,1020h
dvar5	dword minint dup(minint,?)
dvar6	dword 38323139h
bvar6	byte 39h,31h,32h,38h
	byte 0
	.code
start:
	mov eax,offset dvar6
	call dispmsg
	exit 0
	end start

; Output
; 91289128
变量定位

(1) ORG伪指令
ORG伪指令将参数表达的偏移地址作为当前偏移地址,格式是:
ORG 参数
例如,从偏移地址100H处安排数据或程序,可以使用语句:
org 100h

(2)ALIGN伪指令
对于以字节为存储单位的主存储器来说,多字节数据不仅存在按小端或大端方式存放的问题,还有是否对齐地址边界的问题。
对N(N=2,4,8,16,…)个字节的数据,如果起始于能够被N整除的存储器地址位置(也称为模N地址)存放,则对齐地址边界。例如,16位2字节数据起始于偶地址(模2地址,地址最低1位为0)、32位4字节数据起始于模4地址(地址最低2位为00)就是对齐地址边界。
难道不允许N字节数据起始于非模N地址吗?是,也不是。
有很多处理器要求数据的存放必须对齐地址边界,否则会发生非法操作。而IA-32处理器比较灵活,允许不对齐边界存放数据。不过,访问未对齐地址边界的数据,处理器需要更多的读写操作,其性能不如访问对齐地址边界的数据,尤其是有大量频繁的存储器数据操作时。
所以,为了获得更好的性能,常要进行地址边界对齐。ALIGN伪指令便是用于此目的,其格式如下:
ALIGN N
其中,N是对齐的地址边界值,取2的乘方(2,4,8,16,…)。另外,EVEN伪指令用于实现对齐偶地址,与“ALIGN 2”语句的功能一样。

[例2-5] 变量定位程序

;eg0205.asm
	include io32.inc
	.data
		org 100h
bvar1	byte 100
		align 2
wvar2	word 100
		align 4
dvar3	dword ?
		align 4
dvar4	dword ?
	.code
start:

	exit 0
	end start

变量属性

变量名一经定义便具有两类属性:

  • 地址属性:指首个变量所在存储单元的逻辑地址,含有段基地址和偏移地址。
  • 类型属性:指变量定义的数据单位,有字节量、字量、双字量、3字量、4字量和10字节量

在汇编语言程序设计中,经常会用到变量名的属性,汇编程序提供有关的操作符,以方便获取这些属性值,如表

属性 操作符 作用
地址 [ ] 将括起的表达式作为存储器地址指针
地址 $ 返回当前偏移地址
地址 OFFSET 变量名 返回变量名所在段的偏移地址
地址 SEG 变量名 返回段基地址(实地址存储模型)
类型 类型名 PTR 变量名 将变量名按照指定的类型使用
类型 TYPE 变量名 返回一个字量数值,表明变量名的类型
类型 LENGTHOF 变量名 返回整个变量的数据项数
类型 SIZEOF 变量名 返回整个变量占用的字节数
地址操作符

[例2-6]变量地址属性程序

;eg0206.asm
	include io32.inc
	.data
bvar	byte 12h,34h
		org $+10 
array	word 1,2,3,4,5,6,7,8,9,10
wvar	word 5678h
arr_size= $-array
arr_len	= arr_size/2
dvar	dword 9abcdef0h
	.code
start:
	mov al,bvar
	mov ah,bvar+1
	mov bx,wvar[2]
	mov ecx,arr_len
	mov edx,$
	mov esi,offset dvar
	mov edi,[esi]
	mov ebp,dvar
	call disprd ; 显示8个32位通用寄存器内容(十六进制)

	exit 0
	end start

; Output
; EAX=00143412, EBX=0025DEF0, ECX=0000000B, EDX=00401017                                    ; ESI=00405022, EDI=9ABCDEF0, EBP=9ABCDEF0, ESP=0014FF74 

按住ctrl并点击 跳转至通用寄存器参考

类型操作符

[例2-7]变量类型属性程序

;eg0207.asm
	include io32.inc
	.data
bvar	byte 12h,34h
		org $+10 
array	word 1,2,3,4,5,6,7,8,9,10
wvar	word 5678h
arr_size= $-array
arr_len	= arr_size/2
dvar	dword 9abcdef0h
	.code
start:
	mov eax,dword ptr array
	mov ebx,type bvar
	mov ecx,type wvar
	mov edx,type dvar
	mov esi,lengthof array
	mov edi,sizeof array
	mov ebp,arr_size
	call disprd
	
	exit 0
	end start

; Output
; EAX=00020001, EBX=00000001, ECX=00000002, EDX=00000004                                    ; ESI=0000000A, EDI=00000014, EBP=00000016, ESP=0014FF74 

对于32位指令集结构的IA-32处理器,编程中的一般规则是:尽量使用32位操作数和寄存器,除非需要单独对8位或16位数据进行处理。

数据寻址方式

除外设数据外的数据寻址方式有以下3类:

  • 用常量表达的具体数值(立即数寻址)
  • 用寄存器名表示的其中内容(寄存器寻址)
  • 用存储器地址代表的保存的数据(存储器寻址)

立即数寻址方式

注意,立即数(常量)没有类型,它的类型取决于另一个操作数的类型。

[例2-8]立即数寻址程序

; eg0208.asm in Windows Console
	include io32.inc
	.data
const	= 64
bvar	byte 87h,49h
dvar	dword 12345678h,12
	.code
start:
		mov al,12H
		mov ah,'d'
labl:	
		mov bx,-1
		mov ecx,const
		mov edx,const*4/type dvar
		mov esi,offset bvar
		mov edi,labl
		mov bvar,01001100b
		mov dvar+4,12h
		call disprd
	exit 0
	end start
	
; Output
; EAX=00146412, EBX=002AFFFF, ECX=00000040, EDX=00000040                                    ; ESI=00405000, EDI=00401004, EBP=0014FF80, ESP=0014FF74

寄存器寻址方式

[例2-9]寄存器寻址程序

; eg0209.asm in Windows Console
	include io32.inc
	.data
	.code
start:
	mov al,ah
	mov bx,ax
	mov ebx,eax
	mov dx,ds
	mov es,dx
	mov edi,si
	exit 0
	end start
	
; error A2022: instruction operands must be the same size

注意,指令通常要求操作数类型一致,所以最后一条指令MOV EDI,SI有错,列表文件将其标明出来,记录下语句行号、错误.编号和错误原因(有时不甚准确,尤其是多种错误同时出现时)。该指令错误信息的含义是:指令操作数必须类型一致(同样长度)。汇编程序MASM提示的错误信息保存在ML ERR文件中

存储器寻址方式

段寄存器的默认和超越(Override)
访问存储器方式 默认的段寄存器 可Override的段寄存器 偏移地址
读取指令 CS EIP
堆栈操作 SS ESP
一般的数据访问(下列除外) DS CS ES SS FS GS 有效地址EA
EBP或ESP为基地址的数据访问 SS CS ES SS FS GS 有效地址EA
串指令的源操作数 DS CS ES SS FS GS ESI
串指令的目的操作数 ES EDI

如果不使用默认的段选择器,则需要书写段超越指令前缀显式说明。段超越指令前缀是一种只能跟随在具有存储器操作数的指令之前的指令,其助记符是段寄存器名后跟英文冒号,即CS: , ss: 、ES:、FS:或GS:

偏移地址的组成

因为段基地址由默认的或指定的段寄存器指明,所以指令中只有偏移地址即可。存储器操作数寻址使用的偏移地址常称为有效地址(Effective Address,EA)。
为了方便各种数据结构的存取,IA-32处理器设计了多种主存寻址方式,但可以统一表达如下:

32位有效地址=基址寄存器+(变址寄存器×比例)+位移量

Intel80x86汇编语言_第3张图片

其中的4个组成部分是:

  • 基址寄存器——任何8个32位通用寄存器之一
  • 变址寄存器——除ESP之外的任何32位通用寄存器之一
  • 比例——可以是1、2、4或8(因为操作数的长度可以是1、2、4或8字节)
  • 位移量——可以是8或32位有符号值
直接寻址

有效地址只有位移量部分,且直接包含在指令代码中,就是存储器的直接寻址方式。直接寻址常用于存取变量。

mov ecx,count ;也可以表达为 mov ecx,[count]

[例2-10]存储器直接寻址程序

; eg0210.asm in Windows Console
	include io32.inc
	.data
bvar	byte 87h,49h
dvar	dword 12345678h,12
	.code
start:
	mov cl,bvar
	mov edx,dvar
	mov bvar+1,dh
	mov word ptr dvar+2,dx
	mov dvar,87654321h
	mov dvar+4,dvar
	exit 0
	end start
	
; error A2070: invalid instruction operands

大多数指令不支持两个操作数都是存储单元,所以最后一条指令提示有错误,含义是:无效的指令操作数。

寄存器间接寻址

有效地址存放在寄存器中,就是采用寄存器间接寻址存储器操作数。MASM汇编程序使用英文中括号括起寄存器表示寄存器间接寻址。IA-32处理器的8个32位通用寄存器都可以作为间接寻址的寄存器,但建议主要使用EBX、ESI、EDI,访问堆栈数据时使用EBP。
例如,下面的前两条指令的源操作数、后两条指令的目的操作数都是寄存器间接寻址方式:

mov edx,[ebx]	;双字传送,EBX间接寻址主存数据段
mov cx,[esi]	;字传送,ESI间接寻址主存数据段
mov [esi],al	;字节传送,EDI间接寻址主存数据段
mov [ebp],edx	;双字传送,EBP间接寻址主存堆栈段

在寄存器间接寻址中,寄存器的内容是偏移地址,相当于一个地址指针。

[例2-11]寄存器间接寻址程序

; eg0211.asm in Windows Console
	include io32.inc
	.data
srcmsg	byte 'Try your best, why not.'
dstmsg	byte sizeof srcmsg dup(?)
	.code
start:
	mov ecx,lengthof srcmsg	;ECX=字符串字符个数
	mov esi,offset srcmsg	;ESI=源字符串首地址
	mov edi,offset dstmsg	;EDI=目的字符串首地址
again:	
	mov al,[esi]	;取源串一个字符送AL
	mov [edi],al	;将AL传送给目的串
	add esi,1	;源串指针加1,指向下一个字符
	add edi,1	;目的串指针加1,指向下一个字符
	loop again	;字符个数ECX减1,若不为0,则转到AGAIN标号处执行
	
	mov eax,offset dstmsg	;显示目的字符串内容
	call dispmsg
	exit 0
	end start
	
; Output
; Try your best, why not.
寄存器相对寻址

寄存器相对寻址的有效地址是寄存器内容与位移量之和。例如:mov esi,[ebx + 4]
源操作数也可以表达为:[4][ebx],或者4[ebx]
也可以使用变量所在的地址作为偏移量。

mov edi,[ebp-08h] ;源操作数也可以表达为:[-08h][ebx],但不能是:-08h[ebx]
在该指令中,源操作数的有效地址等于EBP-8,与之配合的默认段寄存器为SS。

利用寄存器相对寻址也可以方便地对数组的元素或字符串的字符进行操作。方法是:用数组或字符串首地址作为位移量,赋值寄存器等于数组元素或字符所在的位置量。

[例2-12]寄存器相对寻址程序

; eg0212.asm in Windows Console
	include io32.inc
	.data
srcmsg	byte 'Try your best, why not.'
dstmsg	byte sizeof srcmsg dup(?)
	.code
start:
	mov ecx,lengthof srcmsg	;ECX=字符串字符个数
	mov ebx,0	;EBX指向首个字符
again:	
	mov al,srcmsg[ebx]	;取源串一个字符送AL
	mov dstmsg[ebx],al	;将AL传送给目的串
	add ebx,1	;加1,指向下一个字符
	loop again	;字符个数ECX减1,若不为0,则转到AGAIN标号处执行
	
	mov eax,offset dstmsg	;显示目的字符串内容
	call dispmsg
	exit 0
	end start
变址寻址

使用变址寄存器寻址操作数称为变址寻址。在变址寄存器不带比例(或者认为比例为1)的情况下,需要配合使用一个基址寄存器(称为基址变址寻址方式),还可以再包含一个位移量(称为相对基址变址寻址方式)

存储器操作数的有效地址由一个基址寄存器的内容加上变址寄存器的内容或再加上位移量构成。这种寻址方式适用于二维数组等数据结构

mov edi,[ebx+esi]		; 基址变址寻址,功能 EDI=DS:[EBX+ESI]
mov eax,[ebx+edx+80h]	; 相对基址变址寻址,功能 EAX=DS:[EBX+EDX+80H]

MASM 允许两个寄存器都用中括号,但位移量要书写在中括号前,例如:

mov edi,[ebx][esi]		; 基址变址寻址,功能 EDI=DS:[EBX+ESI]
mov eax,80h[ebx+edx]	; 相对基址变址寻址,功能 EAX=DS:[EBX+EDX+80H]
mov eax,80h[ebx][edx]	; 相对基址变址寻址,功能 EAX=DS:[EBX+EDX+80H]

对应使用变址寄存器的存储器寻址,IA-32处理器支持变址寄存器内容乘以比例1(可以省略)、2、4或8的带比例存储器寻址方式。例如:

mov eax,[ebx*4]		;带比例的变址寻址
mov eax,[esi*2+80h]	;带比例的相对变址寻址
mov eax,[ebx+esi*4]	;带比例的基址变址寻址
mov eax,[ebx+esi*8-80h];带比例的相对基址变址寻址

主存以字节为可寻址单位,所以地址的加减是以字节单元为单位,比例1、2、4和8分别对应8、16、32和64位数据的字节个数,从而方便以数组元素为单位寻址相应数据。

通用数据处理指令

数据传送类指令

通用数据传送指令

  1. 传送指令MOV

传送指令MOV(Move)把一个字节、字或双字的操作数从源位置传送至目的位置,可以实现立即数到通用寄存器或主存的传送,通用寄存器与通用寄存器、主存或段寄存器之间的传送,主存与段寄存器之间的传送。类似于高级语言中的赋值语句
MOV指令的各种组合可以使用下列各式表达(斜线“/”表示多种组合形式,注释是说明或功能解释,下同)

mov reg/mem,imm		;立即数传送
mov reg/mem/seg,reg	;寄存器传送
mov reg/seg,mem		;存储器传送
mov reg/mem,seg		;段寄存器传送

常见错误

  • IA-32指令系统可以对8位、16位和32位整数类型进行处理,但是双操作数指令(除特别说明)的目的操作数与源操作数必须类型一致。例如:

    MOV ESI,DL	;错误:类型不一致。ESI为32位寄存器,DL为8位寄存器
    mov esi,edx	;正确:两个32位寄存器传送
    MOV AL,050AH;错误:类型不一致。050AH超出了寄存器AL的范围
    mov eax,050ah;正确:双字量数据传送
    
  • 寄存器名表达了其类型,变量一经定义也具有类型属性,但立即数和寄存器间接寻址的存储单元等却无明确的类型。IA-32指令系统要求类型一致的两个操作数之一必须有明确的类型,否则要用PTR指明。例如:

    MOV [EBX],255;错误:无明确类型
    mov byte ptr[ebx],255;正确:BYTE PTR 说明是字节操作
    mov word ptr[ebx],255;正确:WORD PTR说明是字操作
    mov dword ptr[ebx],255;正确:DWORD PTR 说明是双字操作
    
  • 为了减小指令编码长度,IA-32指令系统没有设计两个存储器操作数的指令(除串操作指令,见8.2节),也就是不允许两个操作数都是存储单元。例如:

    ;假设DBUF1和 DBUF2是两个双字变量
    Mov DBUF2,DBUF1;错误:两个操作数都是存储单元
    mov eax,dbuf1;正确:EAX = DBUF1(将DBUF1内容送EAX)
    mov dbuf2,eax;正确:DBUE2 = EAX(将EAX内容送DBUF2)
    
  • 能对专用寄存器进行操作的指令有限、功能不强,使用时要注意。例如:

    Mov DS,@DATA 	;错误:立即数不能直接传送段寄存器(@DATA是数据段地址)
    
    mov ax,@data
    mov ds,ax 		;正确:通过AX间接传送给DS
    
  1. 交换指令XCHG

交换指令XCHG(Exchange)用来交换源操作数的和目的操作数的内容,可以在通用寄存器与通用寄存器或存储器之间对换数据。类似于高级语言的交换函数。使用操作数符号的合法格式如下:

XCHG reg,reg/mem
XCHG reg/mem,reg 

交换指令的两个操作数实现位置互换,实际上既是源操作数也是目的操作数,所以它们哪个在前哪个在后就无所谓了,但不能是立即数,也不支持存储器与存储器之间的数据对换。IA-32处理器采用小端方式存储多字节数据,但有些处理器却采用大端方式。当数据在不同处理器之间交换时,有时需要进行小端、大端的互换。
例如,双字变量DVAR进行小端、大端的互换可以使用交换指令:

mov al,byte ptr dvar;取第1个字节
xchg al,byte ptr dvar+3;与第4个字节交换
xchg byte ptr dvar,al;实现低1,4个字节互换(也可以用MOV指令)
mov al,byte ptr dvar+1;同上,AL=第2个字节
xchg al,byte ptr dvar+2 ;与第3个字节交换,AL=第3个字节
xchg al,byte ptr dvar+1 ;实现第2、3个字节互换

指令系统中有一条空操作(No Operation)指令:NOP。在IA-32处理器中,NOP指令与指令“XCHG EAX,EAX”具有同样的指令代码(90H),实际上就是同一条指令。空操作指令看似毫无作用,但处理器执行该指令需要花费时间,且放置在主存中也要占用一个字节空间。编程中,有时利用NOP指令实现短时间延时,还可以临时占用代码空间以便以后填人需要的指令代码。

堆栈操作指令

IA-32处理器的堆栈建立在主存区域中,使用SS段寄存器指向段基地址。堆栈段的范围由堆栈指针寄存器ESP的初值确定,这个位置就是堆栈底部(不再变化)。堆栈只有一个数据出入口,即当前栈顶(不断变化),由堆栈指针寄存器ESP的当前值指定栈顶的偏移地址随着数据进入堆栈,ESP逐渐减小(栈顶指针从高地址向低地址移动)

IA-32处理器的堆栈只能以字或双字为单位操作。字量数据进栈时,ESP向低地址移动2个字节单元(即减2)指向当前栈顶。双字量数据进栈时,ESP减4,即准备4个字节单元。然后,数据以“低对低、高对高”的小端方式存放到堆栈顶部,POP类推

PUSH r16/m16/i16/seg	;ESP=ESP-2 , SS:[ESP]=r16/m16/i16/seg
PUSH r32/m32/i32		;ESP=ESP-4 , SS:[ESP]=r32/m32/i32

POP r16/m16/seg	;r16/m16/seg=SS:[ESP] , ESP=ESP+2
POP r32/m32		;r32/m32=SS:[ESP] , ESP=ESP+4

[例3-1]堆栈操作程序

; eg0301.asm in Windows Console
	include io32.inc
	.data
ten	=10
dvar dword 67762000h,12345678h
	.code
start:
	mov eax,dvar+4	;EAX=12345678H
	push eax	;将EAX内容压入堆栈
	push dword ptr ten	;将立即数以双字量压入堆栈
	push dvar	;将变量DVAR第一个数据压入堆栈
	pop eax	;栈顶数据弹出到EAX
	pop dvar+4	;栈顶数据弹出到DVAR+4位置
	mov ebx,dvar+4	;EBX=000000AH
	pop ecx	;栈顶数据弹出到ECX
	call disprd
	exit 0
	end start
	
; Output
; EAX=67762000, EBX=0000000A, ECX=12345678, EDX=00401000                                     ; ESI=00401000, EDI=00401000, EBP=0014FF80, ESP=0014FF74

其他传送指令

  1. 地址传送指令

获取有效地址指令LEA(Load Effective Address)

LEA r16/r32,mem ; r16/r32=mem的有效地址EA (不需要类型一致)

LEA指令将存储器操作数的有效地址(段内偏移地址)传送至16位或32位通用寄存器中。它的作用等同于汇编程序MASM的地址操作符OFFSET。但是,LEA指令是在指令执行时计算出偏移地址,而OFFSET操作符是在汇编阶段取得变量的偏移地址后者执行速度更快。不过,对于在汇编阶段无法确定的偏移地址,就只能利用LEA指令获取了
[例3-2]地址传送程序

; eg0302.asm in Windows Console
	include io32.inc
	.data
dvar	dword 41424344h
	.code
start:
	mov eax,dvar	;直接寻址获得变量值:EAX=41424344H
	lea esi,dvar	;执行时获得变量地址:ESI指向DVAR
	mov ebx,[esi]	;通过地址获得变量值:EBX=41424344H
	mov edi,offset dvar	;汇编时获得变量地址:EDI指向DVAR
	mov ecx,[edi] 	;通过地址获得变量值:ECX=41424344H
	lea edx,[esi+edi*4+100h] 	;EDX=ESI+EDI×4+100H
	call disprd
	exit 0
	end start

; Output
; EAX=41424344, EBX=41424344, ECX=41424344, EDX=01419100                            
; ESI=00405000, EDI=00405000, EBP=0014FF80, ESP=0014FF74

IA-32处理器指令系统还有指针传送指令LDS、LES、LFS、LCS 和ISS,它们能将主存连续4个或6个字节内容的前两个依次传送给DS、ES、FS、GS和SS,后续字节作为偏移地址传送给指令的16位或32位通用寄存器。

  1. 换码指令

数据表是常见的数据结构,编程中经常需要获得数据表中的某个特定数据项,处理器为此专门设计了换码指令。

XLAT ;AL←[EBX+AL]

使用XLAT指令前,需要将EBX指向主存缓冲区(即数据表首地址)并给AL赋值距离缓冲区开始的位移量(即表中数据项的位置),执行的功能是将缓冲区该位移量位置的数据取出赋给AL,默认该缓冲区在DS数据段;如果设置的缓冲区在其他段,则需要写明缓冲区的变量名,汇编程序就会加上必要的段超越前缀,用户也可以在变量名前加上段超越前缀。

[例3-3] 换码显示程序

; eg0303.asm in Windows Console
	include io32.inc
	.data
num	byte 6,7,7,8,3,0,0,0	;要被转换的数字
tab	byte '0123456789'	;代码表
	.code
start:
	mov ecx,lengthof num
	mov esi,offset num
	mov ebx,offset tab	;EBX指向代码表
again:	
	mov al,[esi]	;AL=要转换的数字
	xlat	;换码
	call dispc	;将AL中的ASCII字符显示在当前光标处
	add esi,1	;指向下一个数字
	loop again	;循环

	exit 0
	end start

; Output
; 67783000

如果不存在XLAT,可用下面的程序完成相同的功能

    mov ecx,lengthof num
    mov esi,offset num
    mov ebx,offsettab	;ebx指向码表
again:
	mov eax,0
	mov al,[esi]
	add eax,ebx
	mov al,[eax]
	call dispc
	add esi,1
	loop again

利用寄存器相对寻址具有的计算能力,可以删除对EBX传送表格首地址和ADD加法指令,用MOV AL,TAB[EAX]就可以实现换码:

	mov ecx,lengthof num
	mov esi,offset num
again:
	mov eax,0
	mov al,tab[eax]
	call dispc
	add esi,1
	loop again

这种方法比XLAT更简单更常用

  1. 标志传送指令

IA-32处理器有可以直接改变CF、DF、IF标志状态的标志位操作指令,还有针对标志寄存器低8位、低16位和全部32位传送的指令,如表所示

指令 功能
CLC 复位进位标志:CF=0
STC 置位进位标志:CF=1
CMC 求反进位标志
CLD 复位方向标志:DF=0,串操作后地址增大
STD 置位方向标志:DF=1,串操作后地址减小
CLI 复位中断标志:IF=0,禁止可屏蔽中断
STI 置位中断标志:IF=1,允许可屏蔽中断
LAHF 标志寄存器低字节内容传送到AH寄存器
SAHF AH寄存器内容传送到标志寄存器低字节
PUSHF 标志寄存器低16位压入堆栈
POPF 堆栈顶不一个字量数据弹出到寄存器低16位
PUSHFD 32位标志寄存器全部内容压入堆栈
POPFD 当前堆栈顶部一个双字数据弹出到标志寄存器

算术运算类指令

状态标志

1.进位标志CF(Carry Flag)

进位标志是针对无符号整数运算设计的,用于反映无符号数据加减运算结果是否超出范围是否需要利用进(借)位反映正确结果。N位二进制数表达无符号整数的范围是0~2N-1。如果相应位数的加减运算结果超出了其能够表达的范围,就是产生了进位或借位。

2.溢出标志 OF(Overflow Flag)

溢出标志是针对有符号整数运算设计的,用于反映有符号数据加减运算结果是否超出范围。处理器默认采用补码形式表示有符号整数,N位补码表达的范围是 -2N-1 ~ +2N-1-1。如果相应位数的有符号整数运算结果超出了这个范围,就是产生了溢出。

注意,溢出标志OF和进位标志CF是两个意义不同的标志。进位标志表示无符号整数运算结果是否超出范围,超出范围后加上进位或借位运算结果仍然正确;而溢出标志表示有符号整数运算结果是否超出范围,超出范围运算结果不正确。处理器对两个操作数进行运算时,按照无符号整数求得结果,并相应设置进位标志CF;同时,根据是否超出有符号整数的范围设置溢出标志OF。应该利用哪个标志,则由程序员来决定。也就是说,如果将参加运算的操作数认为是无符号数,就应该关心进位;而如果将参加运算的操作数认为是有符号数,则要注意是否溢出。

一个简单规则:只有当两个相同符号数相加(含两个不同符号数相减),而运算结果的符号与原数据符号相反时,才产生溢出。

3.其他状态标志

零标志ZF( Zero Flag)反映运算结果是否为0。

符号标志SF ( Sign Flag)反映运算结果是正数还是负数。运算结果的最高位为1,则SF =1;否则SF =0。

奇偶标志PF(Parity Flag)反映运算结果最低字节中“1”的个数是偶数还是奇数,最低字节中“1”的个数为零或偶数时,PF=1;最低字节中“1”的个数为奇数时,PF =0。

调整标志AF( Adjust Flag)反映加减运算时最低半字节有无进位或借位。最低半字节有进位或借位时,AF=1;否则AF=0。

加法指令

加法运算主要包含ADD、ADC和INC三条指令,除INC不影响进位标志CF外,其他指令按照定义影响全部状态标志位
1.加法指令ADD
加法指令ADD使目的操作数加上源操作数,和的结果送到目的操作数。格式如下:

ADD reg,imm/reg/mem	;加法:reg = reg+ imm/ reg / mem
ADD mem,imm/reg		;加法:mem = mem + imm/ reg

它支持寄存器与立即数、寄存器、存储单元,以及存储单元与立即数、寄存器间的加法运算,按照定义影响6个状态标志位。例如:

mov eax,0aaff7348h	;EAX= AAFF7348H,不影响标志
add al,27h			;AL= AL+27H=48H+27H=6FH,所以EAX=AAFE736FH
					;状态标志:OF =0,SF.= 0 ,ZF = 0, PF =1,CF =0
add ax,3fffh		;AX= AX+ 3FFFH =736FH +3FFFH = B36EH,所以EAX = AAFFB36EH
					;状态标志:OF =1,SF = 1,ZF=0,PF =o,CF =0
add eax,88000000h	;EAX =EAX+88000000H = AAFFB36EH +88000000H=[1]32FFB36EH
					;状态标志:OF=1,SF =0,ZF=0,PF =0,CF =1

2.带进位加法指令ADC
带进位加法指令ADC (Add with Carry)除完成ADD加法运算外,还要加上进位CF,结果送到目的操作数,按照定义影响6个状态标志位。格式如下:

ADC reg,imm/reg/mem	;加法:reg = reg+ imm/ reg / mem+CF
ADC mem,imm/reg		;加法:mem = mem + imm/ reg+CF

ADC指令用于与ADD指令相结合实现多精度数的加法。IA-32处理器可以实现32位加法。但是,多于32位的数据相加就需要先将两个操作数的低32位相加(用ADD指令),然后再加高位部分,并将进位加到高位(需要用ADC指令)。

[例3-4]64位数据相加程序

; eg0304.asm in Windows Console
	include io32.inc
	.data
qvar1	qword 6778300082347856h	;64位数据1
qvar2	qword 6776200012348998h	;64位数据2

	.code
start:
	mov eax,dword ptr qvar1	;取低32位
	add eax,dword ptr qvar2	;加低32位,设置CF
	mov edx,dword ptr qvar1+4	;取高32位
	adc edx,dword ptr qvar2+4	;加高32位,同时也加上CF
	call disprd

	exit 0
	end start
	
; Output
; EAX=946901EE, EBX=00254000, ECX=00401000, EDX=CEEE5000    
; ESI=00401000, EDI=00401000, EBP=0014FF80, ESP=0014FF74 

3.增量指令INC
增量指令INC (Increment)只有一个操作数,对操作数加1(增量)再将结果返回原处。操作数是寄存器或存储单元。格式如下:

INC reg/mem

设计增量指令的目的,主要是对计数器和地址指针进行调整,所以它不影响进位CF标志,但影响其他状态标志位。例如:

减法指令

减法运算主要包括SUB、SBB、DEC、NEG和CMP指令,除DEC不影响CF标志外,其他按照定义影响全部状态标志位。
1.减法指令SUB
2.带借位减法指令SBB
3.减量指令DEC

[例3-5]大小写字母转换程序

; eg0305.asm in Windows Console
	include io32.inc
	.data
msg	byte 'welcome',0
	.code
start:
	mov ecx,(lengthof msg)-1	;ECX等于字符串长度
	mov ebx,0	;EBX=0指向头一个字母
again:	
	sub msg[ebx],'a'-'A'	;小写字母减20H转换为大写
	inc ebx	;指向下一个字母
	loop again	;循环
	
	mov eax,offset msg
	call dispmsg

	exit 0
	end start

4.求补指令NEG
求补指令NEG (Negative)也是一个单操作数指令,它对操作数执行求补运算,即用零减去操作数,然后结果返回操作数。

NEG reg/mem		;用0作减法:reg/mem=0-reg/mem

5.比较指令CMP
比较指令CMP( Compare)使目的操作数减去源操作数,差值不回送到目的操作数,但按照减法结果影响状态标志。格式如下:

CMP reg,imm/reg/mem	;减法:reg-imm/reg/mem
CMP mem,imm/reg		;减法:mem-imm/reg

乘法和除法指令

IA-32处理器的乘法和除法指令需要区别无符号数和有符号数,并隐含使用了EAX(和EDX)寄存器
1.乘法指令MUL/IMUL
基本的乘法指令指出源操作数reg/mem(寄存器或存储单元),隐含使用目的操作数。若源操作数是8位数r8/m8,AL与其相乘得到16位积,存入AX中;若源操作数是16位数rl6/m16,AX与其相乘得到32位积,高16位存入DX、低16位存入AX中;若源操作数是32位数 r32/m32,EAX与其相乘得到64位积,高32位存入EDX、低32位存入EAX中

乘法指令分成无符号数乘法指令MUL和有符号数乘法指令IMUL。同一个二进制编码表示无符号数和有符号数时,真值可能不同。

注意﹑加减指令只进行无符号数运算,程序员利用CF和OF区别结果。

2.除法指令 DIV/IDIV
除法指令给出源操作数reg/ mem(寄存器或存储单元),隐含使用目的操作数

其他运算指令

  1. 零位扩展和符号扩展指令 MOVZX

零位扩展对应无符号数,符号扩展对应有符号数,它们使数据位数加长,但数据大小并没有改变。另外,还可以使用符号扩展指令CBW、CWD、CWDE和CDQ,它们的功能分别是将AL符号扩展为AX、AX符号扩展为DX和AX、AX符号扩展为EAX、EAX符号扩展为EDX和EAX。Intel 8086只支持CBW和CWD指令,不支持包括MOVZX和MOVSX在内的其他扩展指令。

[例3-6]温度转换程序

; eg0306.asm in Windows Console
	include io32.inc
	.data
tempc	word 26	;假设一个摄氏温度C
tempf	word ?	;保存华氏温度F
	.code
start:
	movsx eax,tempc	;16位有符号数符号扩展成32位:EAX=C
	imul eax,9	;EAX=C×9
	cdq	;EAX符号扩展为EDX和EAX,作为被除数
	mov ebx,5
	idiv ebx	;EAX=C×9/5(没有考虑余数)
	add eax,32	;EAX=F=C×9/5+32
	mov tempf,ax	;取16位结果(高16位是符号位,没有数值意义)

	exit 0
	end start
  1. 十进制调整指令

位操作类指令

逻辑运算指令

AND

AND reg,imm/reg/mem	;逻辑与 reg=reg∧imm/reg/mem
AND mem,imm/reg		;逻辑与 mem=mem∧imm/reg

OR

OR reg,imm/reg/mem	;逻辑或 reg=reg ∨ imm/reg/mem
OR mem,imm/reg		;逻辑或 mem=mem ∨ imm/reg

NOT

NOT reg/mem			;逻辑非 reg/mem=~reg/mem

XOR

XOR reg,imm/reg/mem	;逻辑或 reg=reg⊕imm/reg/mem
XOR mem,imm/reg		;逻辑或 mem=mem⊕imm/reg

[例3-7]逻辑运算程序

; eg0307.asm in Windows Console
	include io32.inc
	.data
varA	dword 11001010000111100101010101001101b
varB	dword 00110111010110100011010111100001b
varT1	dword ?
varT2	dword ?

	.code
start:
	mov eax,varA	;EAX=11001010000111100101010101001101B
	not eax	;EAX=00110101111000011010101010110010B
	and eax,varB	;EAX=00110101010000000010000010100000B
	mov ebx,varB	;EBX=00110111010110100011010111100001B
	not ebx	;EBX=11001000101001011100101000011110B
	and ebx,varA	;EBX=11001000000001000100000000001100B
	or eax,ebx	;EAX=11111101010001000110000010101100B
	mov varT1,eax
	;
	mov eax,varA
	xor eax,varB	;EAX=11111101010001000110000010101100B
	mov varT2,eax
	;
	mov eax,varT1	;二进制形式显示VART1
	call dispbd
	call dispcrlf	;换行显示
	mov eax,varT2	;二进制形式显示VART2
	call dispbd

	exit 0
	end start
	
; Output
; 11111101010001000110000010101100                                                 
; 11111101010001000110000010101100 

测试指令TEST
测试指令TEST 将两个操作数按位进行逻辑与运算。格式如下:

TEST reg,imm/reg/mem
TEST mem,imm/reg

TEST指令不返回逻辑与结果,只根据结果像AND指令一样来设置状态标志。TEST指令通常用于检测一些条件是否满足,但又不希望改变原操作数的情况。TEST指令和CMP指令类似,一般后跟条件转移指令,目的是利用测试条件转向不同的分支。

移位指令

1.移位指令
移位(Shift)指令分逻辑(Logical)移位和算术(Arithmetic)移位,分别具有左移(Left )或右移( Right)操作

SHL reg/mem,i8/CL;逻辑左移:reg/mem左移i8/CL位,最低位补0,最高位进入CF
SHR reg/mem,i8/CL;逻辑右移:reg/mem右移i8/CL位,最高位补0,最低位进入CF
SAL reg/mem,i8/CL;算术左移,与SHL是同一条指令
SAR reg/mem,i8/CL;算术右移:reg/mem右移i8/CL位,最高位不变,最低位进入CF

[例3-8]移位指令实现乘法程序

; eg0308.asm in Windows Console
	include io32.inc
	.data
wvar	word 34000

	.code
start:
	xor eax,eax	;EAX=0
	mov ax,wvar	;AX=要乘以10的无符号数
	shl eax,1	;左移一位等于乘2
	mov ebx,eax	;EBX=EAX×2
	shl eax,2	;再左移2位,EAX=EAX×8
	add eax,ebx	;EAX=EAX×10
	call dispuid	;以无符号形式显示EAX内容
	call dispcrlf	;换行
	imul eax,10	;EAX=EAX×10
	call dispuid	;显示乘积

	exit 0
	end start
	
; Output
; 340000                                                                             
; 3400000 

DISPUID子程序来自输人输出子程序库,实现以无符号十进制形式显示EAX内容。

2.循环移位指令
循环(Rotate)移位指令类似于移位指令,但要将从一端移出的位返回到另一端形成循环。它分成不带进位循环移位和带进位循环移位,分别具有左移或右移操作

ROL reg/mem,i8/CL;不带进位循环左移:reg/mem左移i8/CL位,最高位进入CF和最低位
ROR reg/mem,i8/CL;不带进位循环右移:reg/mem右移i8/CL位,最低位进入CF和最高位
RCL reg/mem,i8/CL;带进位循环左移:reg/mem左移i8/CL位,最高位进人CF,CF进入最低位
RCR reg/mem,i8/CL;带进位循环右移:reg/mem右移i8/CL位,最低位进入CF,CF进入最高位

[例3-9]循环移位程序

; eg0309.asm in Windows Console
	include io32.inc
	.data
qvar	qword 1234567887654321h
ascii	byte '38'
bcd		byte ?

	.code
start:
	mov ecx,4 
again:	
	shr dword ptr qvar+4,1	;先移动高32位
	rcr dword ptr qvar,1	;后移动低32位
	loop again
	;
	mov al,ascii
	and al,0fh	;处理低4位对应的字符
	mov ah,ascii+1
	shl ah,4	;处理高4位对应的字符
	or al,ah	;组合形成压缩BCD码
	mov bcd,al

	exit 0
	end start

IA-32处理器可以直接对8、16和32位数据进行各种移位操作,但是对多于32位的数据就需要组合移位指令来实现。本示例程序将QVAR指定的64位数据逻辑右移4位。首先可以将高32位逻辑右移一位(用SHR指令),最高位被移入0,移出的位进入了标志CF﹔接着带进位右移一位(用RCR指令),这样CF的内容(即高32位移出的位)进入低32位,同时最低位进入CF。这样就实现了64位数据右移一位,需要多少位移动,就设置ECX等于多少,用循环指令LOOP实现多少次循环就可以了。

程序结构

顺序程序结构

[例4-1]自然数求和程序(公式法)

; eg0401.asm in Windows Console
	include io32.inc
	.data
num	dword 3456	;假设一个N值(小于2^32-1)
sum	qword ?

	.code
start:
	mov eax,num	;EAX=N
	add eax,1	;EAX=N+1
	imul num	;EDX.EAX=(1+N)×N
	shr edx,1	
	rcr eax,1	;64位逻辑右移一位,相当于除以2,EDX.EAX= EDX.EAX÷2
	mov dword ptr sum,eax
	mov dword ptr sum+4,edx	;按小端方式保存

	exit 0
	end start

[例4-2] 处理器识别程序

; eg0402.asm in Windows Console
	include io32.inc
	.data
buffer	byte 'The processor vendor ID is ',12 dup(0),0
bufsize	= sizeof buffer

	.code
start:
	mov eax,0
	cpuid	;执行处理器识别指令
	mov dword ptr buffer+bufsize-13,ebx
	mov dword ptr buffer+bufsize-9,edx
	mov dword ptr buffer+bufsize-5,ecx
	mov eax,offset buffer	;显示信息
	call dispmsg

	exit 0
	end start

; Output
; The processor vendor ID is AuthenticAMD

[例4-3] 不同格式显示程序

; eg0403.asm in Windows Console
	include io32.inc
	.data
var	byte 01100100b
	.code
start:
	mov al,var
	call dispbb	;二进制形式显示:01100100
	call dispcrlf	;回车换行(用于分隔)
	mov al,var
	call disphb	;十六进制形式显示:64
	call dispcrlf	;回车换行(用于分隔)
	mov al,var
	call dispuib	;十进制形式显示:100
	call dispcrlf	;回车换行(用于分隔)
	mov al,var
	call dispc	;字符显示:d
	exit 0
	end start
	
; Output
; 01100100                                                                 
; 64                                                                        
; 100                                                                       
; d    

分支程序结构

汇编语言需要首先利用比较CMP、测试TEST、加减运算、逻辑运算等影响状态标志的指令形成条件,然后利用条件转移指令判断由标志表达的条件,并根据标志状态控制程序转移到不同的程序段。

无条件转移指令

JMP 无条件转移指令,相当于goto

JMP指令根据目标地址的转移范围和寻址方式,可以分成以下4种类型:

JMP label	;段内转移,相对寻址

JMP r32/r16	
JMP m32/m16	;段内转移,间接寻址

JMP label	;段间转移,直接寻址
JMP m48/m32	;段间转移,间接寻址

[例4-4]无条件转移程序

; eg0404.asm in Windows Console
	include io32.inc
	.data
nvar	dword ?

	.code
start:
		jmp labl1	;相对寻址
		nop
labl1:	jmp near ptr labl2	;相对近转移
		nop
labl2:	mov eax,offset labl3 
		jmp eax	;寄存器间接寻址
		nop
labl3:	mov eax,offset labl4
		mov nvar,eax
		jmp nvar	;存储器间接寻址
		nop
labl4:

	exit 0
	end start

条件转移指令

Jcc条件转移指令

条件转移指令Jcc中的cc表示利用标志判断的条件,共有16种,如表所示。表中斜线分隔了同一条指令的多个助记符形式,目的是方便记

如jz,je设计两种功能完全一致甚至连机器码都一样,一般在cmp指令后用je,test指令后用jz(类比if(条件)和if(1))

助记符 标志位 英文含义 中文说明
JZ/JE ZF=1 Jump if Zero/Equal 等于0/相等
JNZ/JNE ZF=0 Jump if Not Zero/ Not Equal 不等于0/不相等
JS SF=1 Jump if Sign 符号为负
JNS SF=0 Jump if Not Sign 符号为正
JP/JPE PF=1 Jump if Parity/Parity Even 1的个数为偶
JNP/JPO PF=0 Jump if Not Parity/Parity Odd 1的个数为奇
JO OF=1 Jump if Overflow 溢出
JNO OF=0 Jump if Not Overflow 无溢出
JC/JB/JNAE CF=1 Jump if Cany / Below / Not Above or Equal 进位/低于/不高于等于
JNC/JNB/JAE CF=0 Jump if Not Carry / Not Below / Above or Equal 无进位/不低于/高于等于
JBE/JNA CF=1或ZF=1 Jump if Below or Equal / Not Above 低于等于/不高于
JNBE/JA CF=0或ZF=0 Jump if Not Below or Equal / Above 不低于等于/高于
JL/JNGE SF≠OF Jump if Less / Not Greater or Equal 小于/不大于等于
JNL/JGE SF=OF Jump if Not Less / Greater or Equal 不小于/大于等于
JLE/JNG ZF≠OF或ZF=1 Jump if Less or Equal / Not Greater 小于等于/不大于
JNLE/JG SF=OF且ZF=0 Jump if Not Less or Equal / Greater 不小于等于/大于

[例4-5]个数折半程序

; eg0405.asm in Windows Console
	include io32.inc
	.data

	.code
start:
	mov eax,885	;假设一个数据
	shr eax,1	;数据右移进行折半
	jnc goeven	;余数为0,即CF=0条件成立,不需要处理,转移
	add eax,1	;否则余数为1,即CF=1,进行加1操作
goeven:	
	call dispuid	;显示结果

	exit 0
	end start

; Output
; 443

[例4-6]位测试程序

; eg0406.asm in Windows Console
	include io32.inc
	.data
no_msg	byte 'Not Ready!',0
yes_msg	byte 'Ready to Go!',0

	.code
start:
	mov eax,56h	;假设一个数据
	test eax,02h	;测试D1位(使用D1=1,其他位为0的数据)
	jz nom	;D1=0条件成立,转移
	mov eax,offset yes_msg	;D1=1,显示准备好
	jmp done	;跳转过另一个分支体!
nom:	
	mov eax,offset no_msg	;显示没有准备好
done:	
	call dispmsg

	exit 0
	end start

; Output
; Ready to Go! 

[例4-7]奇校验程序

; eg0407.asm in Windows Console
	include io32.inc
	.data

	.code
start:
	call readc	;键盘输入,返回值在AL寄存器
	call dispcrlf	;回车换行(用于分隔)
	call dispbb	;以二进制形式显示数据
	call dispcrlf	;回车换行(用于分隔)
	and al,7fh	;最高位置“0”、其他位不变,同时标志PF反映“1”的个数
	jnp next	;个数为奇数,不需处理,转移
	or al,80h	;个数为偶数,最高位置“1”、其他位不变 
next:	
	call dispbb	;显示含校验位的数据

	exit 0
	end start

[例4-8]-数据比较程序

; eg0408.asm in Windows Console
	include io32.inc
	.data
in_msg1	byte 'Enter a number: ',0
in_msg2	byte 'Enter another number: ',0
out_msg1	byte 'Two numbers are equal: ',0
out_msg2	byte 'The less number is: ',0
out_msg3	byte 13,10,'The greater number is: ',0

	.code
start:
	mov eax,offset in_msg1	;提示输入第一个数据
	call dispmsg
	call readsid	;输入第一个数据
	mov ebx,eax	;保存到EBX
	mov eax,offset in_msg2	;提示输入第二个数据
	call dispmsg
	call readsid	;输入第二个数据
	mov ecx,eax	;保存到ECX
	cmp ebx,ecx	;二个数据进行比较
	jne nequal	;两数不相等,转移
	mov eax,offset out_msg1	;两数相等
	call dispmsg
	mov eax,ebx
	call dispsid	;显示相等的数据
	jmp done	;转移到结束
nequal:	
	jl first	;EBX较小,不需要交换,转移
	xchg ebx,ecx	;EBX保存较小数,ECX保存较大数
first:	
	mov eax,offset out_msg2	;显示较小数
	call dispmsg
	mov eax,ebx	;较小数在EBX中
	call dispsid
	mov eax,offset out_msg3	;显示较大数
	call dispmsg
	mov eax,ecx	;较大数在ECX中
	call dispsid
done:

	exit 0
	end start

单分支程序结构

[例4-9]求绝对值程序

; eg0409.asm in Windows Console
	include io32.inc
	.data

	.code
start:
	call readsid	;输入一个有符号数,从EAX返回值
	cmp eax,0	;比较EAX与0
	jge nonneg	;条件满足:EAX≥0,转移
	neg eax	;条件不满足:EAX<0,为负数,需求补得正值
nonneg:	
	call dispuid	;分支结束,显示结果

	exit 0
	end start

[例4-10]字母判断程序

; eg0410.asm in Windows Console
	include io32.inc
	.data

	.code
start:
	call readc	;输入一个字符,从AL返回值
	cmp al,'A'	;与大写字母A比较
	jb done	;比大写字母A小,不是大写字母,转移
	cmp al,'Z'	;与大写字母Z比较
	ja done	;比大写字母Z大,不是大写字母,转移
	or al,20h	;转换为小写
	call dispcrlf	;回车换行(用于分隔)
	call dispc	;显示小写字母
done:

	exit 0
	end start

双分支程序结构

[例4-11]显示数据最高位程序

; eg0411.asm in Windows Console
	include io32.inc
	.data
dvar	dword 0bd630422h	;假设一个数据
	.code
start:
	mov ebx,dvar
	shl ebx,1	;EBX最高位移入CF标志
	jc one	;CF=1,即最高位为1,转移
	mov al,'0'	;CF=0,即最高位为0:AL='0'
	jmp two	;一定要跳过另一个分支体
one:	mov al,'1'	;AL='1'
two:	call dispc	;显示

	exit 0
	end start

[例4-12] 有符号数运算溢出程序

; eg0412.asm in Windows Console
	include io32.inc
	.data
dvar1	dword 1234567890	;假设两个数据
dvar2	dword -999999999
dvar3	dword ?
okmsg	byte 'Correct!',0	;正确信息
errmsg	byte 'ERROR ! Overflow!',0	;错误信息

	.code
start:
	mov eax,dvar1 
	sub eax,dvar2	;求差
	jo error	;有溢出,转移
	mov dvar3,eax	;无溢出,保存差值
	mov eax,offset okmsg	;显示正确
	jmp disp
error:	mov eax,offset errmsg	;显示错误
disp:	call dispmsg

	exit 0
	end start

多分支程序结构

[例4-13]地址表程序
假设有10个信息(字符串),编程显示指定的信息。功能:
1)提示输入数字,并输入数字。
2)判断数字是否在规定的范围内,不在范围内、重新输入。
3)显示数字对应的信息,退出。

; eg0413.asm in Windows Console
	include io32.inc
	.data
msg1	byte 'Chapter 1: Fundamentals',0dh,0ah,0
msg2	byte 'Chapter 2: Data Representation',0dh,0ah,0
msg3	byte 'Chapter 3: Basic Instructions',0dh,0ah,0
msg4	byte 'Chapter 4: Program Structure',0dh,0ah,0
msg5	byte 'Chapter 5: Procedure Progamming',0dh,0ah,0
msg6	byte 'Chapter 6: Windows Programming',0dh,0ah,0
msg7	byte 'Chapter 7: Mixed Programming',0dh,0ah,0
msg8	byte 'Chapter 8: I/O Programming',0dh,0ah,0
msg9	byte 'Chapter 9: FP/SIMD/64-bit Instructions',0dh,0ah,0
msg10	byte 'Chapter 10: Other Topics',0dh,0ah,0	;10个信息
msg     byte 'Input number(1~10):',0dh,0ah,0	;提示输入字符串
table	dword disp1,disp2,disp3,disp4,disp5,disp6,disp7,disp8,disp9,disp10	;地址表

	.code
start:
again:
	mov eax,offset msg
	call dispmsg	;提示输入
	call readuid	;接收输入:EAX=数字
	cmp eax,1	;判断范围
	jb again
	cmp eax,10
	ja again	;不在范围内,重新输入
	dec eax	;EAX=EAX-1
	shl eax,2	;EAX=EAX×4
	jmp table[eax]	;多分支跳转
disp1:	mov eax,offset msg1
	jmp disp
disp2:	mov eax,offset msg2
	jmp disp
disp3:	mov eax,offset msg3
	jmp disp
disp4:	mov eax,offset msg4
	jmp disp
disp5:	mov eax,offset msg5
	jmp disp
disp6:	mov eax,offset msg6
	jmp disp
disp7:	mov eax,offset msg7
	jmp disp
disp8:	mov eax,offset msg8
	jmp disp
disp9:	mov eax,offset msg9
	jmp disp
disp10:	mov eax,offset msg10
disp:	call dispmsg

	exit 0
	end start

循环程序结构

循环指令

[例4-14]数组求和程序

; eg0414.asm in Windows Console
	include io32.inc
	.data
array	dword 136,-138,133,130,-161	;数组
sum	dword ?	;结果变量

	.code
start:
	mov ecx,lengthof array	;ECX=数组元素个数
	xor eax,eax	;求和初值为0
	mov ebx,eax	;数组指针为0
again:	
	add eax,array[ebx*(type array)]	;求和 
	inc ebx	;指向下一个数组元素 INC 指令功能 目标操作数+1
	loop again
	mov sum,eax	;保存结果
	call dispsid	;显示结果

	exit 0
	end start

LOOP指令先进行ECX 减1操作,然后判断。如果ECX等于0时执行LOOP指令,则将循环232次。所以,如果数组元素的个数为0,本程序将出错。为此,可以使用另一条循环指令JECXZ(实地址存储模型是JCXZ指令)排除ECX等于0的情况

计数控制循环

[例4-15]求最大值程序

;eg0415.asm in Windows Console
	include io32.inc
	.data
array	dword -3,0,20,900,587,-632,777,234,-34,-56		;假设一个数组
count	= lengthof array	;数组的元素个数
max	dword ?	;存放最大值

	.code
start:
	mov ecx,count-1	;元素个数减1是循环次数
	mov esi,offset array
	mov eax,[esi]	;取出第一个元素给EAX,用于暂存最大值
again:	
	add esi,4
	cmp eax,[esi]	;与下一个数据比较
	jge next	;已经是较大值,继续下一个循环比较
	mov eax,[esi]	;EAX取得更大的数据
next:	
	loop again	;计数循环
	
	mov max,eax	;保存最大值

	exit 0
	end start

[例4-16]简单加密解密程序

; eg0416.asm in Windows Console
	include io32.inc
	.data
key	byte 234	;假设的一个密钥
bufnum	= 255
buffer	byte bufnum+1 dup(0)	;定义键盘输入需要的缓冲区
msg1	byte 'Enter messge: ',0
msg2	byte 'Encrypted message: ',0
msg3	byte 13,10,'Original messge: ',0

	.code
start:
	mov eax,offset msg1	;提示输入字符串
	call dispmsg
	mov eax,offset buffer	;设置入口参数EAX
	call readmsg	;调用输入字符串子程序
	push eax	;字符个数保存进入堆栈
	mov ecx,eax	;ECX=实际输入的字符个数,作为循环的次数
	xor ebx,ebx	;EBX指向输入字符 自身异或就是清0
	mov al,key	;AL=密钥
encrypt:	
	xor buffer[ebx],al	;异或加密
	inc ebx
	dec ecx	;等同于指令:loop encrypt
	jnz encrypt	;处理下一个字符
	mov eax,offset msg2
	call dispmsg
	mov eax,offset buffer	;显示加密后的密文
	call dispmsg
	;
	pop ecx	;从堆栈弹出字符个数,作为循环的次数
	xor ebx,ebx	;EBX指向输入字符
	mov al,key	;AL=密钥
decrypt:	
	xor buffer[ebx],al	;异或解密
	inc ebx
	dec ecx
	jnz decrypt	;处理下一个字符
	mov eax,offset msg3
	call dispmsg
	mov eax,offset buffer	;显示解密后的明文
	call dispmsg

	exit 0
	end start

条件控制循环

[例4-17]字符个数统计程序

; eg0417.asm in Windows Console
	include io32.inc
	.data
string	byte 'Do you have fun with Assembly?',0	;以0结尾的字符串

	.code
start:
	xor ebx,ebx	;EBX用于记录字符个数,同时也用于指向字符的指针
again:	
	mov al,string[ebx]
	cmp al,0	;用指令“test al,al”更好
	jz done
	inc ebx	;个数加1
	jmp again	;继续循环
done:	
	mov eax,ebx	;显示个数
	call dispuid 

	exit 0
	end start

[例4-18]斐波那契数列程序

; eg0418.asm in Windows Console
	include io32.inc
	.data

	.code
start:
	mov eax,1	;EAX=F(1)=1
	call dispuid	;显示第1个数
	call dispcrlf	;回车换行
	call dispuid	;显示第2个数
	call dispcrlf	;回车换行
	mov ebx,eax	;EBX=F(2)=1
again:	
	add eax,ebx	;EAX=F(N)=F(N-1)+F(N-2)
	jc done
	call dispuid	;显示一个数
	call dispcrlf	;回车换行
	xchg eax,ebx	;EAX=F(N-1),EBX=F(N-2)
	jmp again
done:

	exit 0
	end start

多重循环

[例4-19]冒泡排序

; eg0419.asm in Windows Console
	include io32.inc
	.data
array	dword 587,-632,777,234,-34		;假设一个数组
count	= lengthof array	;数组的元素个数

	.code
start:
	mov ecx,count	;ECX←数组元素个数
	dec ecx	;元素个数减1为外循环次数
outlp:	
	mov edx,ecx	;EDX←内循环次数
	mov ebx,offset array
inlp:	
	mov eax,[ebx]	;取前一个元素
	cmp eax,[ebx+1]	;与后一个元素比较
	jng next
	;前一个不大于后一个元素,则不进行交换
	xchg eax,[ebx+1]	;否则,进行交换
	mov [ebx],eax
next:
	inc ebx	;下一对元素
	dec edx
	jnz inlp	;内循环尾
	loop outlp	;外循环尾

	exit 0
	end start

[例4-20]字符剔除程序

; eg0420.asm in Windows Console
	include io32.inc
	.data
string	byte 'Let us have a try !',0dh,0ah,0	; 以0结尾的字符串

	.code
start:
	mov eax,offset string	; 显示处理前的字符串
	call dispmsg
	mov esi,offset string
outlp:	
	cmp byte ptr [esi],0	; 外循环,先判断后循环
	jz done	; 为0结束
again:	
	cmp byte ptr [esi],' '	; 检测是否是空格
	jnz next	; 不是空格继续循环
	mov edi,esi	; 是空格,进入剔除空格分支
inlp:
	inc edi	; 该分支是循环程序
	mov al,[edi]	; 前移一个位置
	mov [edi-1],al
	cmp byte ptr [edi],0	; 内循环,先循环后判断
	jnz inlp	; 内循环结束处
	jmp again	; 再次判断是否为空格(处理连续空格情况)
next:
	inc esi	; 继续对后续字符进行判断处理
	jmp outlp	; 外循环结束处
done:
	mov eax,offset string	; 显示处理后的字符串
	call dispmsg

	exit 0
	end start

模块化程序设计

子程序结构

子程序调用指令CALL

子程序返回指令RET

过程定义伪指令

过程名 PROC
...  ;过程体
过程名 ENDP

[例5-1]子程序调用程序

; eg0501.asm in Windows Console
	include io32.inc
	.data

	.code
start:
        mov eax,1
		mov ebp,5
		call subp
retp1:	mov ecx,3
retp2:	mov edx,4
		call disprd

	exit 0
subp	proc	;过程定义,过程名为subp
		push ebp
		mov ebp,esp
		mov esi,[ebp+4]
			;ESI=CALL下一条指令(标号RETP1)的偏移地址
		mov edi,offset retp2	;EDI=标号RETP2的偏移地址
		mov ebx,2
		pop ebp	;弹出堆栈,保持堆栈平衡
		ret	;子程序返回
subp	endp	;过程结束

	end start

参数传递

寄存器传递参数

共享变量传递参数

堆栈传递参数

多模块程序结构

源文件包含

模块连接

子程序库

宏结构

宏汇编

重复汇编

条件汇编

你可能感兴趣的:(汇编,单片机,嵌入式硬件)