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
其中,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)
汇编程序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
变量名一经定义便具有两类属性:
在汇编语言程序设计中,经常会用到变量名的属性,汇编程序提供有关的操作符,以方便获取这些属性值,如表
属性 | 操作符 | 作用 |
---|---|---|
地址 | [ ] |
将括起的表达式作为存储器地址指针 |
地址 | $ |
返回当前偏移地址 |
地址 | 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的段寄存器 | 偏移地址 |
---|---|---|---|
读取指令 | 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位有效地址=基址寄存器+(变址寄存器×比例)+位移量
其中的4个组成部分是:
有效地址只有位移量部分,且直接包含在指令代码中,就是存储器的直接寻址方式。直接寻址常用于存取变量。
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位数据的字节个数,从而方便以数组元素为单位寻址相应数据。
传送指令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
交换指令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
获取有效地址指令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位通用寄存器。
数据表是常见的数据结构,编程中经常需要获得数据表中的某个特定数据项,处理器为此专门设计了换码指令。
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更简单更常用
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(寄存器或存储单元),隐含使用目的操作数
零位扩展对应无符号数,符号扩展对应有符号数,它们使数据位数加长,但数据大小并没有改变。另外,还可以使用符号扩展指令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
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