资讯详情

Win32汇编 - 基本知识总结

汇编语言是所有程序设计语言中最古老的语言。它最接近计算机语言。它可以通过汇编语言直接访问计算机硬件,可以直接访问CPU对话,可以说汇编语言是所有编程语言中最自由的语法格式,但自由的成本是了解计算机系统结构和操作系统的大量细节,每个程序都需要考虑各种硬件的状态,因此,汇编程序的使用效率很低.

自1946年第一台计算机问世以来,在短短60多年的时间里,经历了电子管计算机(1946年)、晶体管计算机(1956年)、集成电路计算机(1958年)、超大型集成电路计算机(1972年)、这五代的变化。.

从当今的X86架构的CPU说起,X86指令集是Intel第一块16位CPU(80x86)专门开发,IBM世界上第一家公司于1981年推出PC机中的CPU—i8088(i8086简化版)也使用X同时,为了改进计算机中的86指令浮点数据处理能力增加X87芯片系列协处理器另外使用X为了提高处理器的性能,将能X86指令集和X87指令集统称为X86指令集.

虽然随着CPU技术的不断发展,Intel公司相继开发出更新型i80386、i80486、Pentium直到今天,为了确保计算机能够继续运行以前开发的各种应用程序,以保护和继承丰富的软件资源,Intel公司生产的一切CPU继续使用X86指令集,所以它CPU仍属于X86系列,由于X86系列及其兼容性CPU都使用X86指令集,所以今天形成了巨大的X86系列及兼容CPU阵容.

谈完处理器的基本开发过程,再来了解一下CPU分类指令集.

处理器分为两个架构阵营,即RISC(精简指令集计算机)CISC(复杂指令集计算机)是当前CPU的两种架构,它们的区别在于不同的CPU设计理念和方法,CPU该架构由制造商提供,属于同一系列CPU产品规范的主要目的是区分不同类型CPU的重要标示.

早期的CPU全部是CISC架构,其设计目的是用最少的机器语言指令完成所需的计算任务.比如乘法运算,在CISC架构的CPU你可能只需要一个指令就能得到相应的结果,这些幕后操作都取决于CPU该架构将完成中间设计的逻辑电路增加CPU结构的复杂性和对CPU制作工艺的要求对编译器的开发非常有利.

相比CISC架构系统,RISC架构要求软件指定每个操作步骤,如果需要上述乘法操作RISC在架构上实现,您需要指定其具体的实现步骤,并使用该架构进行生产CPU,它可以大大降低CPU允许在同一工艺水平下生产更强大的复杂性和功能CPU,但对编译器的设计有更高的要求.

总结:精简指令集出现后,大家都说复杂指令集已经过时,英特尔密切关注。.英特尔开发复杂指令集CPU和精简指令集CPU.精简指令处理器上市后,复杂指令集CPU依旧热销.精简指令集CPU因为之前的软件不能兼容,销量也不好.英特尔得出结论,复杂的指令集仍然强大,放弃精简指令集的开发.

80x86处理器的几种基本工作模式

IA-32处理器有三种基本工作模式:实地址模式,系统管理模式,保护模式,另一种模式被称为模式虚拟80x86模式,其实虚拟x86模式也是保护模式的一个特例。下面将简要描述这些系统模式:

在这种模式下,IA-32处理器使用20位地址线访问1048576(1MB)字节内存的地址范围是0-FFFFF,然而,8086处理器的寄存器是16个地址,不能存储20个地址。为了解决这个棘手的问题,提出了一个名字分段内存所有内存都分为多个64kb这些区域称为区域段(segment),我们使用段地址x16 偏移地址=绝对地址计算绝对地址.

在这种模式下,每个程序可以找到4GB内存,地址范围为0-FFFFFFFF,在这种模式下,编程不需要复杂的公式计算,只需要使用一个32位整数来存储任何指令和变量的地址。处理器将在后台计算和转换地址。这些工作对汇编程序员来说是透明的。在保护模式下有三个部分:CS:代码段,DS:数据段,SS:堆栈段,其他段操作系统负责维护.

在这种模式下,处理器实际上是在保护模式下创建的MB地址空间的虚拟机,虚拟机对运行于实地址模式下的80x86计算机模拟,在Windows NT系统下,打开一个控制台窗口,就创建了一个8086虚拟机,当然你也可同时打开多个控制台,他们之间是隔离的并不互相影响.

在这种模式下,所有段都映射到32位的物理地址空间中,一个程序至少需要两个段:代码段(CS,数据段(DS),每一段都段描述符定义,段描述符通常存储在一个段中全局描述符表(GDT)64位地址之一.

IA-32处理器支持称为分页(paging)允许一段被分割成称为页(page)对于4096字节的内存块,分页机制允许程序使用的总内存远远大于计算机的物理内存。操作系统映射的所有页面的集合称为虚拟内存,操作系统通常包含虚拟内存管理器的程序。分页机制会给人一种内存无限的错觉。然而,如果程序过于依赖分页,其运行效率将非常低.


CPU内部寄存器组,以及每个寄存器的作用

寄存器是CPU由于内部高速存储单元是固化的CPU内部组件的访问速度快于内存。在目前的处理器中,寄存器分为几种类型,其中8种是通用寄存器(EAX,EBX,ECX,EDX,EBP,ESP,ESI,EDI),6个段寄存器(CS,SS,DS,ES,FS,GS),处理器状态标志寄存器(EFLAGS),指针寄存器(EIP)寄存器.

CPU内部有八个通用寄存器主要用于计算操作和数据传输。这8个寄存器可以用作32位或两个16位的值,也可以用于8位寄存器。例如,通用寄存器可以分为高低寄存器来存储数据,例如:EAX寄存器,可被拆分为(AX)16位用寄存器(AX)16位寄存器也可以分为AH/AL(高低8位).

CPU内部有2个通用寄存器ESI和EDI,寄存器ESI、EDI称为变址寄存器(Index Register),它们主要用于存储存储单元在段内的偏移,可以实现各种存储操作数的搜索方式,方便以不同地址的形式访问存储单元.变址寄存器不可分割在字符串操作指令的执行过程中,8位寄存器对它们有特定的要求和特殊的功能DS数据段寄存器相关.

CPU内部有两个通用寄存器EBP和ESP,寄存器EBP、ESP称为指针寄存器(Pointer Register),主要用于存放堆栈存储单元的偏移量主要用于访问堆栈中的存储单元并规定,EBP基址指针寄存器,ESP堆栈指针寄存器,指针寄存器不可分割默认和8位寄存器SS堆栈段寄存器相关.

CPU指令寄存器内部有一个指令EIP,寄存器存储下一个要执行的指令地址。下一个要执行的指令通常被提取到指令队列中。除非发生转移,否则在理解其功能时,不考虑指令队列的存在EIP不能手动修改,一般由特殊指令修改CALL,RET,PUSH等间接修改.

段寄存器是根据内存段的管理模式设置的。内存单元的物理地址由段寄存器的值和偏移量组成,可以将两个较小的值组合成一个可访问较大物理空间的内存地址。常规段寄存器包括CS:代码段寄存器,DS:数据段寄存器,SS:堆栈段寄存器,ES:附加数据段寄存器这些寄存器通常由编译器或操作系统维护.

标志寄存器(EFLAGS),该寄存器用于控制CPU操作过程,或反应CPU独立的二进制位构成某些操作结果,常用的标志位包括CF(进位标志),ZF(零标志),PF(奇偶标志)等.


手动编译小程序

.386p
	.model flat,stdcall
	option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

MyDef equ 1024         ; 将数值指定名称

.data
	Main WORD 1024      ; 定义可赋值的变量
.data?                  ; 定义未知初始变量
	lyshark DWORD ?

.code
	main PROC
		xor eax,eax
		invoke ExitProcess,0
	main ENDP
END main

数据段的定义

MASM 定义了多种内部数据类型,每种数据类型都描述了该类型的变量和表达式的取值集合,汇编语言中数据类型的基本特征是以数据位数为度量单位:8,16,32,48,64,80位,而除此之外其他的特征如(符号,指针,浮点数)主要是为了方便我们记忆变量中存储的数据类型.

接下来看下表,表中是IEEE委员会发布的标准内部数据类型:

数据类型 作用(无符号) 数据类型 作用(有符号)
BYTE 8位无符号整数 SBYTE 8位有符号整数
WORD 16位无符号整数 SWORD 16位有符号整数
DWORD 32位无符号整数 SWORD 32位有符号整数
FWORD 48位整数(远指针) QWORD 64位整数定义
REAL4 32位(4字节)短实数 REAL8 64位(8字节)长实数

数据类型定义语句为变量在内存中保留存储空间,并且可以选择为变量指定一个名字,在汇编语言中所有的数据无非就是BYTE的集合,数据的定义语句格式如下:

[变量名] 数据定义伪指令 初始值[....]

在数据定义语句中使用BYTE(定义字节)SBYTE(定义有符号字节)伪指令,可以为每一个或多个有符号或无符号字节分配存储空间,每个初始值必须是8位整数表达式或字符常量,例如下面的定义:

.data
	var1 BYTE 'A'      ; 定义字符常量
	var2 BYTE ?        ; 定义未初始化变量
	var3 BYTE 0        ; 最小的无符号字节常量
	var4 BYTE 255      ; 最大的无符号字节常量
	var5 SBYTE -128    ; 最小的有符号字节常量
	var6 SBYTE +127    ; 最大的有符号字节常量

如果一条数据定义语句中有多个初始值,那么标号仅仅代表第一个初始值的偏移,如下我们首先定义一个BYTE数组,然后通过反汇编查看地址的偏移变化就能看到效果啦:

.data
	list BYTE 10,20,30,40,50

00E71000 | B8 0030E700        | mov eax,main.E73000                 | E73000=10
00E71005 | B8 0130E700        | mov eax,main.E73001                 | E73001=20
00E7100A | B8 0230E700        | mov eax,main.E73002                 | E73002=30
00E7100F | B8 0330E700        | mov eax,main.E73003                 | E73003=40
00E71014 | B8 0430E700        | mov eax,main.E73004                 | E73004=50

并非所有的数据定义都需要标号,如果想继续定义以list开始的字节数组,可以在随后的行上接着上面的定义:

.data
	list BYTE 10,20,30,40,50
	list BYTE 60,70,80,90,100

当然除了定义整数字符以外,还可以定义字符串,要想定义字符串应将一组字符用单引号或双引号括起来.最常见的字符串是以空格结尾0h,在C/C++,JAVA中定义字符串无需添加结尾0h,这是因为编译器会在编译的时候自动的在字符串后面填充了0h,在汇编语言中我们需要手动添加字符串结尾的标志:

.data
	string1 BYTE "hello lyshark",0h
	string2 BYTE "good night",0h

00F23000  68 65 6C 6C 6F 20 6C 79 73 68 61 72 6B 00 67 6F hello lyshark.go 
00F23010  6F 64 20 6E 69 67 68 74 00 00 00 00 00 00 00 00 od night........

字符串也可以占用多行,而无须为每行都提供一个编号,如下代码也是合法的:

.data
	string1 BYTE "welcom to the Demo program"
			BYTE "created by lyshark",0dh,0ah,
			BYTE "url:lyshark"
			BYTE "send me a copy",0dh,0ah,0

十六进制0dh,0ah也称为CR/LF(回车换行符),或者是行结束的字符,在向标准输出设备上写的时候,回车换行符可以将光标移动到下一行的开头位置,从而继续填充新的字符串.

有时我们需要初始化一些空值的内存空间,在为内存地址分配空间的时候,DUP伪指令就显得尤为重要,初始化和未初始化数据均可使用DUP指令定义,其定义语法如下:

.data
	string1 BYTE 20 DUP(0)       ; 分配20字节,全部填充0
		BYTE 20 DUP(?)       ; 分配20字节,且未初始化
		BYTE 50 DUP("stack") ; 分配50字节,"stackstack..."

.data
	smallArray DOWRD 10 DUP(0) ; 分配40字节
	bigArray DOWOR 5000 DUP(?) ; 分配20000字节

除了上面的例子以外,我们也可以直接定义常量,常量是不可以动态修改的数据类型,一般情况下一旦定义,那么在程序运行期间不可以被修改,常量的定义很简单,只需要将.data换成.const即可.

.const
	var1 BYTE  "hello world",0h   ; 初始化为BYTE的字符串
	var2 DWORD 10                 ; 初始化为10的DWORD类型
	var3 DWORD 100 dup(1,2)       ; 200个DWORD的缓冲区
	var4 BYTE  1024 dup(?)        ; 1024字节的缓冲区
	var5 BYTE "welcome",0dh,0ah,0 ; 0dh,0ah为换行符

有时我们需要计算数组的大小,但手动计算显得特别麻烦,此时我们可以使用MASM提供的$符号来进行数组大小的计算过程,如下.

.data
	list BYTE 10,20,30,40,50
	listsize = ($ - list)       ; 计算字节数据大小
.data
	list WORD 1000h,2000h,3000h,4000h
	listsize = ($ - list) /2    ; 计算字数据大小
.data
	list DWORD 100000h,200000h,300000h,400000h
	listsize = ($ - list) /4    ; 计算双字数据大小
Post_1 equ 1000
Post_2 equ 2000
Post_3 equ 3000

标准输入输出

使用masm32.inc提供的函数实现标准的输入与输出.

.386
	.model flat, stdcall
	
	include masm32.inc
	include kernel32.inc
	includelib masm32.lib
	includelib kernel32.lib

.data
	len equ 20
	OutText dw ?
	ShowText db "请输入一个数: ",0

.code
	main PROC
		invoke StdOut, addr ShowText    ; 输出提示信息
		invoke StdIn, addr OutText,len  ; 等待用户的输入
		invoke StdOut, addr OutText     ; 输出刚才输入的内容
		ret
	main ENDP
END main

通过调用系统的API函数,来实现具体的输出,其过程比较复杂不推荐使用.

.386
	.model flat, stdcall
	
	include windows.inc
	include kernel32.inc
	includelib kernel32.lib
.data
	szText db "hello lyshark!",0
.data?
	hOut dd ?     ; 保存句柄
	hLen dd ?     ; 保存字符长度
.code
	main PROC
		invoke GetStdHandle,STD_OUTPUT_HANDLE     ; 获取设备控制台句柄
		mov hOut,eax                              ; 把获取到的句柄给hOut
		invoke lstrlen,addr szText                ; 取出字符串的长度
		mov hLen,eax
		invoke WriteFile,hOut,addr szText,hLen,0,0 ;具体的输出
		ret
	main ENDP
END main

使用微软C标准库中的printf函数; msvscrt.inc 把它声明做 crt_printf

.386
	.model flat, stdcall
	
	include msvcrt.inc
	includelib msvcrt.lib
	
.data
	PrintText db "EAX=%d;EBX=%d;EDX=%d | InPut ->: ",0
	ScanFomat db "%s",0
	PrintTemp db ?
.code
	main PROC
		mov eax,10
		mov ebx,20
		mov ecx,30
		invoke crt_printf,addr PrintText,eax,ebx,ecx        ; 打印提示内容
		invoke crt_scanf, addr ScanFomat, addr PrintTemp    ; 输入内容并接收参数
		invoke crt_printf, addr PrintTemp                   ; 输出输入的内容
		ret
	main ENDP
END main

常用汇编指令

从源操作数向目标操作数之间复制数据.

00A41000 | B8 24100000        | mov eax,1024                        |
00A41005 | 8BD8               | mov ebx,eax                         |
00A41007 | 66:B9 0010         | mov cx,1000                         |

零扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值零扩展(zero-extend)至16位或者32位,该指令适用于无符号整数,其基本格式如下:

01301000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
01301004 | 0FB7C3             | movzx eax,bx                        | EAX = 0000A69B
01301007 | 0FB6D3             | movzx edx,bl                        | EDX = 0000009B
0130100A | 66:0FB6CB          | movzx cx,bl                         | CX = 009B

符号扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值符号扩展(sign-extend)至16位或者是32位,该指令只能用于有符号整数,其基本格式如下:

00FD1000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
00FD1004 | 0FBFC3             | movsx eax,bx                        | EAX = FFFFA69B
00FD1007 | 0FBED3             | movsx edx,bl                        | EDX = FFFFFF0B
00FD100A | 66:0FBECB          | movsx cx,bl                         | CX = FF9B

数据交换指令,该指令用于交换两个操作数中的内容,但该指令不接受立即数操作数.

00D71000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00D71005 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
00D7100A | 93                 | xchg ebx,eax                        | EAX = 2000h;EBX = 1000h

数据递增与递减,INC指令用于对寄存器或内存数据的递增,DEC指令用于对寄存器或内存数据递减.

00881000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00881005 | 40                 | inc eax                             | EAX = 1001h
00881006 | 40                 | inc eax                             | EAX = 1002h
00881007 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
0088100C | 4B                 | dec ebx                             | EBX = 1FFFF
0088100D | 4B                 | dec ebx                             | EBX = 1FFFE
0088100E | 4B                 | dec ebx                             | EBX = 1FFFD

操作数增加,该指令用于将源操作数和目的操作数相加,且不影响源操作数的值,而是改变目的操作数.

00BC1000 | B8 00100000        | mov eax,1000                        | EAX = 1000
00BC1005 | BB 00200000        | mov ebx,2000                        | EBX = 2000
00BC100A | 03D8               | add ebx,eax                         | EBX = EBX+EAX = 3000

操作数减少,该指令用于将源操作数和目的操作数相减,且不影响源操作数的值,而是改变目的操作数.

00811000 | B8 00200000        | mov eax,2000                        | EAX = 2000
00811005 | BB 00100000        | mov ebx,1000                        | EBX = 1000
0081100A | 2BC3               | sub eax,ebx                         | EAX = EAX-EBX = 1000

逻辑与/逻辑或/逻辑异或.

00DD100E | B8 01000000        | mov eax,1                              |
00DD1013 | BB 01000000        | mov ebx,1                              |
00DD1018 | B9 00000000        | mov ecx,0                              |
00DD101D | 21D8               | and eax,ebx                            |
00DD101F | 09CB               | or ebx,ecx                             |
00DD1021 | 31C0               | xor eax,eax                            |

返回数据标号的偏移地址,偏移地址代表标号距数据基址的距离,单位是字节.

.data
	var1 BYTE ?
	var2 WORD ?
	var3 DWORD ?
	var4 DWORD ?
.code
	main PROC
		mov esi,offset var1
		mov esi,offset var2
		mov esi,offset var3
		mov esi,offset var4
	main ENDP
END main

用来重载声明操作数的默认尺寸,这在试图以不同与变量声明时所使用的尺寸来访问变量时很有用.

.data
	temp DWORD 12345678h

.code
	main PROC
	mov eax,DWORD PTR [temp]  ; 将temp以双字取值并存储到eax
	mov ax,WORD PTR [temp]    ; 将temp以字为单位取值并存储到ax
	mov bx,WORD PTR [temp+2]  ; 在偏移基础上+2
	main ENDP
END main

00C11000 | A1 0030C100        | mov eax,dword ptr ds:[C13000]       | EAX = 12345678
00C11005 | 66:A1 0030C100     | mov ax,word ptr ds:[C13000]         | AX = 5678
00C1100B | 66:8B1D 0230C100   | mov bx,word ptr ds:[C13002]         | BX = 1234

计算数组元素的数目,元素由出现在的同一行的值定义.

.data
	ArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0h
	ArrayBT BYTE 1,2,3,4,5,6,7,8,9,0h
.code
	main PROC

		mov eax,lengthof ArrayDW
		mov eax,lengthof ArrayBT

		push 0
		call ExitProcess
	main ENDP
END main

返回按照字节计算的单个元素的大小.

.data
	var1 BYTE ?
	var2 WORD ?
	var3 DWORD ?
	var4 QWORD ?
.code
	main PROC
		mov eax,TYPE var1       ; 1
		mov ebx,TYPE var2       ; 2
		mov ecx,TYPE var3       ; 4
		mov edx,TYPE var4       ; 8

		push 0
		call ExitProcess
	main ENDP
END main

返回等于LENGTHOF(总元素数)和TYPE(每个元素占用字节)返回值的乘基.

.data
	var1 WORD 32 DUP(0)        ; 32*2
	var2 BYTE 10,20,30,40      ; 3
	var3 WORD 30 DUP(?),0,0    ; 30+2
	var4 DWORD 1,2,3,4         ; 4

.code
	main PROC
		mov eax,SIZEOF var1
		mov eax,SIZEOF var2
		mov eax,SIZEOF var3
		mov eax,SIZEOF var4
	main ENDP
END main

该指令检测ECX寄存器的变化,每次循环寄存器自动减1,当ECX=0循环结束,否则继续循环.

.code
	main PROC
		mov ecx,10      ; 计数循环寄存器初始化为10
	top:                    ; 循环标号,编译器会将其转换成一个地址
		xor eax,eax
		mov eax,ecx
		loop top        ; loop跳转到指定地址,此处为top

		push 0
		call ExitProcess
	main ENDP
END main

如果用光了所有的通用寄存器,但又必须要使用ECX的话,可以在循环开始将ECX保存.

.data
	count DWORD ?
.code
	main PROC

		mov ecx,10
	top:
		mov count,ecx       ; 将ecx寄存器放入count变量
		xor ecx,ecx
		mov ecx,1000        ; 重置ecx寄存器的数值
		add eax,ecx

		mov ecx,count       ; 处理完成后,恢复ECX寄存器
		loop top            ; 继续循环
		
		push 0
		call ExitProcess
	main ENDP
END main

在循环内部创建另一个循环的时候,必须考虑外层ECX中的外层循环计数该如何处理,把外层循环计数保存在内存中,是非常的理想的.

.data
	count DWORD ?
.code
	main PROC

		mov ecx,10        ; 设置外层循环计数
	L1:
		mov count,ecx     ; 保存外层循环计数
			mov ecx,20    ; 设置内层循环计数
		L2:
			xor eax,eax
			xor ebx,ebx
			xor edx,edx
			loop L2      ; 重复内层循环计数

		mov ecx,count    ; 恢复外层循环计数器
		loop L1          ; 执行外层循环跳转
		push 0
		call ExitProcess
	main ENDP
END main

.code
	main PROC
		mov eax,100
		mov ebx,200
		.IF (eax == ebx) && (ebx == ebx)
			xor eax,eax
			xor ebx,ebx
		.ELSEIF (eax >= 100) || (ebx == ebx)
			add eax,100
			add ebx,100
		.ENDIF
	main ENDP
END main

.data
	Count DWORD 10
	SumNum DWORD 0

.code
	main PROC
		xor eax,eax
		.WHILE (eax < Count)
			add SumNum,1
			inc eax
		.ENDW
	main ENDP
END main

以下代码利用循环伪指令,完成了1-10相加.

.data
	Count DWORD 10
	SumNum DWORD 0
.code
	main PROC
		xor eax,eax
		.REPEAT
			inc eax
			add SumNum,1
		.UNTIL (eax >= Count)
	main ENDP
END main

以下是个死循环,当eax寄存器的值等于5时,则执行.break结束程序的运行.

.code
	main PROC
		mov eax,10
		.while (1)
			dec eax
			.break .if(eax == 5)
		.endw
		ret
	main ENDP
END main

当EAX的值小于等于5时执行continue,否则执行inc ebx,总循环数为10.

.code
	main PROC
		mov eax,0
		mov ebx,0
		.repeat
			inc eax
			.continue .if(eax <= 5)
				inc ebx
		.until (eax >= 10)
		ret
	main ENDP
END main

该伪指令并不是循环,而是分别将指定的指令批量的替换到程序中.

.code
	main PROC
		for num,<1,2,3>
			xor eax,eax
			add eax,DWORD PTR [num]
		endm
		ret
	main ENDP
END main

该伪指令并不是循环,而是分别将指定的字串批量的替换到程序中.

.code
	main PROC
		forc code,<@#$%^&*()<>>
			BYTE "&code"
		endm
		ret
	main ENDP
END main

内存寻址方式

Windows系统默认运行于保护模式下,当处理器运行于保护模式下时,每个程序可以寻址4GB的内存范围,地址范围是从十六进制数的0-FFFFFFFF,微软汇编器的平坦模式,适用于保护模式编程,在平坦模式下其内存寻址的方式包括,直接寻址,间接寻址,基址变址寻址,比例因子寻址等,接下来将分别来演示.

◆直接寻址◆

在声明变量名称的后面加上一个偏移地址,可以创建直接偏移(direct-offset)操作数,可以通过它来访问没有显示标号的内存地址,接下来看一个实验例子:

.data
	ArrayB BYTE 10h,20h,30h,40h,50h
	ArrayW WORD 100h,200h,300h,400h
	ArrayDW DWORD 1h,2h,3h,4h

.code
	main PROC
	; 针对字节的寻址操作
		mov al,[ArrayB]           ; al=10
		mov al,[ArrayB+1]         ; al=20
		mov al,[ArrayB+2]         ; al=30
	; 针对内存单元字存储操作
		mov bx,[ArrayW]           ; bx=100
		mov bx,[ArrayW+2]         ; bx=200
		mov bx,[ArrayW+4]         ; bx=300
	; 针对内存单元双字存储操作
		mov eax,[ArrayDW]         ; eax=00000001
		mov eax,[ArrayDW+4]       ; eax=00000002
		mov eax,[ArrayDW+8]       ; eax=00000003
	main ENDP
END main

◆间接寻址◆

在处理数组操作时完全使用直接寻址是不切实际的,我们不大可能为数组的每个元素都提供一个不同的标号,也不太可能使用非常多的常量偏移地址去寻址数组的各个元素,处理数组唯一可行的方法是用寄存器作为指针并操作寄存器的值,这种方法称为间接寻址(indirect addressing),操作数使用间接寻址时,就称为间接操作数(indirect operand).

通过使用ESI寄存器,外加偏移地址(此处DWORD=4字节),实现寻址.

.data
	ArrayDW DWORD 10000h,20000h,300000h
.code
	main PROC
		mov esi,offset ArrayDW  ; 获取数据段的内存基址
		mov eax,[esi]           ; 取出[esi]地址中的数据,并赋值给eax
		add esi,4               ; 每次esi指针加4,因为数据格式为DWORD
		mov eax,[esi]
		add esi,4
		mov eax,[esi]
	main ENDP
END main

通过ESP堆栈寄存器,实现寻址.

.code
	main PROC
		mov eax,100                ; eax=1
		mov ebx,200                ; ebx=2
		mov ecx,300                ; ecx=3
		push eax                   ; push 1
		push ebx                   ; push 2
		push ecx                   ; push 3

		mov edx,[esp + 8]          ; EDX = [ESP+8]=1
		mov edx,[esp + 4]          ; EDX = [ESP+4]=2 
		mov edx,[esp]              ; EDX = [ESP]=3
	main ENDP
END main

◆变址寻址◆

变址寻址,变址操作数(indexed operand)把常量和寄存器相加以得到一个有效地址,任何32位通用寄存器都可以作为变址寄存器,MASM允许使用两种不同的变址操作数据格式.

通过变量名和寄存器结合,变量名代表变量偏移地址的常量,通过变更ESI寄存器的值进行数据寻址.

.data
	ArrayDW DWORD 10000h,20000h,300000h
.code
	main PROC
		mov esi,0
		mov eax,[ArrayDW + esi]     ; 通过变量名+esi寄存器寻址

		mov ebx,8                   ; 增加8字节
		mov eax,[ArrayDW + ebx]     ; 定位第三个DW数据内存
	main ENDP
END main

通过把变址寄存器内存偏移常量结合,用寄存器存放数组基址,用常量标识各个数组元素.

.data
	ArrayW WORD 1000h,2000h,3000h,4000h
.code
	main PROC
		mov esi,offset ArrayW    ; 获取基址
		mov ax,[esi]             ; 显示第一个数据
		mov ax,[esi + 2]         ; 显示第二个数据
		mov ax,[esi + 4]         ; 最后一个
	main ENDP
END main

通过计算公式,这里数组中每个元素占用4字节,所以需要乘以4,寄存器ECX为需要定位的元素偏移.

.data
	Array DWORD 1000h,2000h,3000h,4000h,0h
.code
	main PROC
		lea eax,Array
		mov ecx,2
		mov edx,DWORD PTR [eax + ecx * 4]      ;edx=3000h

		mov ecx,1
		mov edx,DWORD PTR [eax + ecx * 4]      ;edx=2000h
	main ENDP
END main

通过使用比例因子,以下例子每个DWORD=4字节,且总元素下标=0-3,得出比例因子3* type arrayDW.

.data
	ArrayDW DWORD 1000h,2000h,3000h,4000h
.code
	main PROC

	; 第1种比例因子寻址
		mov esi,3*type ArrayDW        ;总共3个下标x每个元素的类型
		mov eax,ArrayDW[esi]
	; 第2种比例因子寻址
		mov esi,3                      ; 变更ESI下标,可实现定位不同的数据
		mov eax,ArrayDW[esi*4]         ; 其中4代表每个数据类型4字节
	; 第3种比例因子寻址
		mov esi,3
		mov eax,ArrayDW[esi*type ArrayDW]
	main ENDP
END main

变量地址的变量称为指针变量(pointer variable),Intel处理器使用两种基本类型的指针,即near(近指针)far(远指针),保护模式下使用Near指针,所以它被存储在双字变量中.

.data
	ArrayB BYTE 10,20,30,40,50
	ArrayD DWORD 1,2,3,4,5

	ptrB DWORD OFFSET ArrayB    ; 指针ptrB --> ArrayB
	ptrD DWORD OFFSET ArrayD    ; 指针ptrD --> ArrayD
.code
	main PROC
	mov esi,ptrB      ; 指向数组ArrayB
	mov al,[esi]      ; 取出 10h
	mov esi,ptrD      ; 指向数组ArrayD
	mov eax,[esi]     ; 取出 1h
	main ENDP
END main

标志测试指令

在学习数据比较指令之前,需要先来了解一下标识寄存器这个东西,标志寄存器又称程序状态寄存器(Program Status Word,PSW),这是一个存放条件码标志,控制标志和系统标志的寄存器.

标志寄存器中存放的有条件标志,也有控制标志,它对于处理器的运行和整个过程的控制有着非常重要的作用.条件标志主要包括进位标志、奇偶标志、辅助进位标志、零标志、符号标志、溢出标志等,控制标志主要有跟踪标志,因为有标志寄存器的存在才能实现各种华丽的判断循环等,常用的标志有以下6个:

标志位 标志全称 标志序号 标志位说明
CF(Carry Flag) 进位标志位 0 当执行一个加法(或减法)运算,使最高位产生进位(或借位)时,CF为1;否则为0
PF(Parity Flag) 奇偶标志位 2 当运算结果中,所有bit位(例:1001010)中1的个数为偶数时,则PF=1;为基数PF=0
AF(Auxiliary Flag) 辅助进位标志 4 执行加法(减法)运算,结果的低4位向高4位有进位(借位)时,则AF=1;否则AF=0
ZF(Zero Flag) 零标志位 6 若当前的运算结果为零,则ZF=1;否则ZF=0
SF(Sign Flag) 符号标志位 7 若运算结果为负数,则SF=1;若为非负数则SF=0
TF(Trap Flag) 陷阱标志位 8 为方便程序调试而设计的,TF=1单步执行指令,TF=0则CPU正常执行程序
IF(Interrupt) 中断允许标志 9 当IF=1CPU可响应可屏蔽中断请求,当设置IF=0则CPU不响应可屏蔽中断请求
DF(Direction) 方向标志位 10 当DF=0时为正向传送数据(cld),否则为逆向传送数据(std)
OF(Overflow) 溢出标志位 11 记录是否产生了溢出,当补码运算有溢出时OF=1;否则OF=0

ZF标志相关指令执行后,结果为0则ZF=1;若结果不为0则ZF=0.

00C31000 | 90                   | nop                                              | ZF = 0
00C31001 | B8 01000000          | mov eax,1                                        | ZF = 0
00C31006 | 83E8 01              | sub eax,1                                        | ZF = 1

00C31000 | 90                   | nop                                              | ZF = 0
00C31001 | B8 02000000          | mov eax,2                                        | ZF = 0
00C31006 | 83E8 01              | sub eax,1                                        | ZF = 0

PF标志相关指令执行后,其结果所有bit位中的1若为偶数,则PF=1;若为奇数PF=0.

00C31000 | 90                   | nop                                              | PF = 0
00C31001 | B8 00000000          | mov eax,00000000                                 | PF = 0
00C31006 | 83C0 6F              | add eax,00000111                                 | PF = 1

00C31000 | 90                   | nop                                              | PF = 0
00C31001 | B8 00000000          | mov eax,00000000                                 | PF = 0
00C31006 | 83C0 6F              | add eax,00000011                                 | PF = 0

SF标志相关指令执行后,其结果是否为负,若为负则SF=1;若为非负SF=0.

00C3100B | 90                   | nop                                              | SF = 0
00C3100C | B8 E8030000          | mov eax,3E8                                      | SF = 0
00C31011 | 2D E9030000          | sub eax,3E9                                      | SF = 1

00C3100B | 90                   | nop                                              | SF = 0
00C3100C | B8 E8030000          | mov eax,3E8                                      | SF = 0
00C31011 | 2D E9030000          | sub eax,3E8                                      | SF = 0

CF标志相关指令执行后,在进行无符号运算时,如果表达式发生进位或借位则CF=1.

00C31016 | 90                   | nop                                              | CF = 0
00C31017 | 66:B8 FFFF           | mov ax,FFFF                                      | CF = 0
00C3101B | 66:83C0 01           | add ax,1                                         | CF = 1

00C31016 | 90                   | nop                                              | CF = 0
00C31017 | 66:B8 FFFF           | mov ax,FFFF                                      | CF = 0
00C3101B | 66:83C0 01           | sub ax,1                                         | CF = 0

OF标志相关指令执行后,超出机器所能表示的范围称为溢出若发生了溢出OF=1;否则OF=0.

00C3101B | 90                   | nop                                              | OF = 0
00C3101C | B0 40                | mov al,64                                        | OF = 0
00C3101E | 04 40                | add al,64                                        | OF = 1

00C31020 | 90                   | nop                                              | OF = 0
00C31021 | B0 3F                | mov al,63                                        | OF = 0
00C31023 | 04 40                | add al,64                                        | OF = 0

该操作与AND指令类似,唯一不同的是它不保存结果,常用来测试标志位状态.

00DD103B | B8 01000000        | mov eax,1                              | EAX = 1
00DD1040 | BB 00000000        | mov ebx,0                              | EBX = 0
00DD1045 | 85D8               | test eax,ebx                           | ZF = 1

00DD1051 | B8 01000000        | mov eax,1                              |
00DD1056 | A9 00000000        | test eax,0                             | ZF = 1
00DD105B | 83E0 00            | and eax,0                              | ZF = 1
00DD1062 | 83C8 01            | or eax,1                               | ZF = 0

在源操作数和目标操作数进行减法操作,只影响标志位.

00DD1001 | B8 00010000        | mov eax,100                            | EAX = 100
00DD1006 | BB 50000000        | mov ebx,50                             | EBX = 50
00DD100B | 39D8               | cmp eax,ebx                            | eax - ebx
00DD100D | 0F87 EDFF62FF      | ja 401000                              | jump

条件跳转指令

注记符 跳转条件 描述信息
JZ/JE ZF=1 为零则跳转,(leftOp - rightOp = 0)
JNZ/JNE ZF=0 不为零则跳转,(leftOp - rightOp != 0)
JC/JNC CF=1/0 设置进位标志则跳/未设置进位标志则跳
JO/JNO OF=1/0 设置溢出标志则跳/未设置溢出标志则跳
JS/JNS SF=1/0 设置符号标志则跳/未设置符号标志则跳
JP/JNP PF=1/0 设置奇偶标志则跳(偶)/未设置奇偶标志则跳(基)
无符号模式 有符号模式 跳转条件 描述信息
JA JG (left > right) 大于则跳转
JAE JGE (left >= right) 大于或等于则跳转
JB JL (left < right) 小于则跳转
JBE JLE (left <= right) 小于或等于则跳转

检测到ZF=1也就说明表达式返回了0,则程序跳转,否则不跳转.

01031001 | B8 00010000        | mov eax,64                      | eax=100
01031006 | BB 00010000        | mov ebx,64                      | ebx=100
0103100B | 39D8               | cmp eax,ebx                     | eax-ebx 
0103100D | 0F84 EDFF3CFF      | je 401000                       | jump
01031013 | 0F84 E7FF3CFF      | jz 401000                       | jump

检测到ZF=0也就说明表达式返回了1,则程序跳转,否则不跳转.

01031001 | B8 00010000        | mov eax,65                      | eax=101
01031006 | BB 00010000        | mov ebx,64                      | ebx=100
0103100B | 39D8               | cmp eax,ebx                     | eax-ebx 
0103100D | 0F84 EDFF3CFF      | jne 401000                      | not jump
01031013 | 0F84 E7FF3CFF      | jnz 401000                      | not jump

基于无符号数的跳转指令,JA大于则跳转JB小于则跳转.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB C8000000        | mov ebx,C8                      | ebx=200
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F87 EDFF3CFF      | ja 401000                       | ebx>eax jump

0103100F | B8 64000000        | mov eax,64                      | eax=100
01031014 | BB 32000000        | mov ebx,32                      | ebx=50
01031019 | 3BD8               | cmp ebx,eax                     | ebx-eax
0103101B | 0F82 DFFF3CFF      | jb 401000                       | ebx<eax jump

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BC3               | cmp eax,ebx                     | eax-ebx
0103100D | 0F87 EDFF3CFF      | ja 401000                       | eax=ebx not jump
01031013 | 0F82 E7FF3CFF      | jb 401000                       | eax=ebx not jump

基于无符号数的跳转指令,JAE大于等于则跳转JBE小于等于则跳转.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
01031010 | 3BC3               | cmp eax,ebx                     | eax-ebx
01031012 | 0F83 E8FF3CFF      | jae 401000                      | eax>=ebx jump

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB C8000000        | mov ebx,C8                      | ebx=200
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F83 EDFF3CFF      | jae 401000                      | ebx>=eax jump

01031001 | B8 C8000000        | mov eax,C8                      | eax=200
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F86 EDFF3CFF      | jbe 401000                      | ebx<=eax jump

基于有符号数的跳转指令,JG大于则跳转JL小于则跳转.

01031001 | B0 7F              | mov al,7F                       | al=0x7F(+127)
01031003 | B3 80              | mov bl,80                       | bl=0x80(-128)
01031005 | 3AC3               | cmp al,bl                       | (+128)-(-127)
01031007 | 0F87 F3FF3CFF      | ja 401000                       | 不跳转,因为7Fh不大于80h
0103100D | 0F8F EDFF3CFF      | jg 401000                       | 跳转,因为(+128)大于(-127)

01031001 | B0 9C              | mov al,9C                       | al=(-100)
01031003 | B3 32              | mov bl,32                       | bl=(50)
01031005 | 3AC3               | cmp al,bl                       | (-100)-(50)
01031007 | 0F82 F3FF3CFF      | jb 401000                       | 不跳转,因为9ch不小于32h
0103100D | 0F8C EDFF3CFF      | jl 401000                       | 跳转,因为(-100)小于(32)

基于有符号数的跳转指令,JGE大于等于则跳转JLE小于等于则跳转.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BC3               | cmp eax,ebx                     | eax-ebx
0103100D | 0F83 EDFF3CFF      | jae 401000                      | 跳转,无符号100=100
01031013 | 0F8D E7FF3CFF      | jge 401000                      | 跳转,有符号100=100

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 9CFFFFFF        | mov ebx,FFFFFF9C                | ebx=(-100)
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F8E EDFF3CFF      | jle 401000                      | 跳转,有符号数(-100)<(100)

检测ECX寄存器的值,如果等于零则执行跳转,否则跳过执行.

01031001 | B9 01000000        | mov ecx,1                       | ecx=1
01031006 | E3 F8              | jecxz <a.EntryPoint>            | not jump

0103100A | B9 00000000        | mov ecx,0                       | ecx=0
0103100F | E3 EF              | jecxz <a.EntryPoint>            | jump

移位相关指令

每种汇编语言都有进行操作数移位的指令,移位和循环移位指令在控制硬件设备,加密数据,以及实现高速图形运算时特别有用,移位指令也是汇编语言中最具特征的指令集,移位(Shifting)的含义是在操作数内向左或向右移动数据位,Intel处理器提供了多种移位指令,具体如下表所示:

指令集 含义 指令集 含义
SHL 逻辑左移(无符号数) SHR 逻辑右移(无符号数)
SAL 算数左移(有符号数) SAR 算数右移(有符号数)
ROL 循环左移(无符号数) ROR 循环右移(无符号数)
RCL 循环左移(带进位的) RCR 循环右移(带进位的)
SHLD 双精度左移(无符号) SHRD 双精度右移(无符号)

◆SHL/SHR 逻辑移位◆

对目标操作数执行逻辑左移(针对无符号数)操作,其左移后最低位0填充,而移动出去的最高位则会送入CF(进位标志)中,原来的进位标志位中的值将被覆盖.

Intel处理器中定义,执行移位的源操作数的范围必须在0-255之间,在任何处理器上都可以使用CL寄存器存放移位位数,例如在下面的指令中,AL寄存器被左移一位,最高位被复制到了进位标志中,最低位被清零:

01251006 | B3 8F                | mov al,10001111b                            | AL = 10001111b
01251008 | D0E3                 | shl al,1                                    | CF = 1,AL = 00011110b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 02              | shl al,2                                    | CF = 0,AL = 00000000b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 01              | shl al,1                                    | CF = 1,AL = 00000000b

01251006 | B0 01                | mov al,10100000b                            | AL = 10100000b
01251008 | C0E0 03              | shl al,2                                    | CF = 0,AL = 10000000b

另外使用SHL指令还可以进行2的次幂的高速乘法运算,任何操作数左移动N位,就相当于乘以2的N次方,如下例子:

01311002 | B0 05                | mov al,5                                    | AL 左移动1位
01311004 | D0E0                 | shl al,1                                    | al*2=10

01311007 | B0 05                | mov al,5                                    | AL左移2位
01311009 | C0E0 02              | shl al,2                                    | al*4=20

01311007 | B0 05                | mov al,5                                    | AL左移3位
01311009 | C0E0 03              | shl al,3                                    | al*8=40

对目标操作数执行逻辑右移(针对无符号数)操作,移出的数据位用0代替,最低位被复制到CF进位标志中,原来的进位标志位丢失.

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,1                                    | CF = 1,AL = 01000111b

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,2                                    | CF = 1,AL = 00100011b

另外任何无符号操作数逻辑右移N位,就相当于该操作数除以2的N次方,如下例子:

01311012 | B2 20                | mov dl,20                                   | DL 右移1位 
01311014 | D0EA                 | shr dl,1                                    | dl/2 = 10

01311012 | B2 20                | mov dl,20                                   | DL 右移2位 
01311014 | D0EA                 | shr dl,2                                    | dl/4 = 5

乘法/除法指令

MUL和IMUL指令分别进行有符号整数和无符号整数的乘法操作,MUL(无符号乘法)指令有三种格式.

计算AL寄存器BL寄存器相乘,积数默认放在AX寄存器中,进位标志CF清零,因为AH高位等于零.

00111002  | B0 05                    | mov al,5                                 | al = 5
00111004  | B3 10                    | mov bl,10                                | bl = 10
00111006  | F6E3                     | mul bl                                   | AX=50,CF=0

将16操作数2000h和100h相乘,乘积高位在DX中,低位在AX中.CF=1因为乘机高半部分DX=0

0008100F  | 66:B8 0020               | mov ax,2000                              | ax=2000
00081013  | 66:BB 0001               | mov bx,100                               | bx=100
00081017  | 66:F7E3                  | mul bx                                   | ax*bx

将32操作数12345h和1000h相乘,得到64位乘积,其高位在EDX中,低位在EAX中.

0008101B  | B8 45230100              | mov eax,12345                            |
00081020  | BB 00100000              | mov ebx,1000                             |
00081025  | F7E3                     | mul ebx                                  |

字串操作指令

移动串指令: MOVSB、MOVSW、MOVSD ;从 ESI -> EDI; 执行后, ESI 与 EDI 的地址移动相应的单位 比较串指令: CMPSB、CMPSW、CMPSD ;比较 ESI、EDI; 执行后, ESI 与 EDI 的地址移动相应的单位 扫描串指令: SCASB、SCASW、SCASD ;依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化 储存串指令: STOSB、STOSW、STOSD ;将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化 载入串指令: LODSB、LODSW、LODSD ;将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化 其中的 B、W、D 分别指 Byte、Word、DWord, 表示每次操作的数据的大小单位.

上述指令可以有重复前缀: REP ECX > 0 时 REPE (或 REPZ) ECX > 0 且 ZF=1 时 REPNE(或 REPNZ) ECX > 0 且 ZF=0 时 ;重复前缀可以自动按单位(1、2、4)递减 ECX

.data
	string1 db "hello lyshark",0      ; 原始字符串
	str_len equ $ - string1 -1        ; 计算出原始字符串长度
	string2 db str_len dup(?),0       ; 目标内存地址

.code
	main PROC
		cld                       ; 清除方向标志
		mov esi,offset string1    ; 取源字符串内存地址
		mov edi,offset string2    ; 取目标字符串内存地址
		mov ecx,str_len           ; 指定循环次数,为原字符串长度
		rep movsb                 ; 逐字节复制,直到ecx=0为止
		ret
	main ENDP
END main

不使用rep重复前缀的方式完成字串复制.

.data
	string1 db "hello lyshark",0      ; 原始字符串
	str_len equ $ - string1 -1        ; 计算出原始字符串长度
	string2 db str_len dup(?),0       ; 目标内存地址

.code
	main PROC
		lea esi,string1               ; 取string1的地址
		lea edi,string2               ; 取string2的地址
		mov ecx,str_len               ; 取字符串长度,用于循环
		cld                       ; 方向->正向
	@@:	movsb                     ; 每次复制一个字节BYTE
		dec ecx                   ; 每次ecx减一
		jnz @B                    ; 如果ecx不为0则循环
		ret
	main ENDP
END main

.data
	ddSource DWORD 10h,20h,30h               ; 定义三个四字节数据
	ddDest   DWORD lengthof ddSource dup(?)  ; 得到目标地址

.code
	main PROC
		lea esi,ddSource
		lea edi,ddDest
		mov ecx,lengthof ddSource
		cld
		rep movsd
		ret
	main ENDP
END main

.data
	Text1 db "hello lyshark",0
	Text2 db "hello lyshar1",0
.code
	main PROC
		lea esi,Text1
		lea edi,Text2
		mov ecx,lengthof Text1
		cld
		repe cmpsb
		je L1
		xor eax,eax            ; 字串不同则清空eax
		jmp L2
	L1:	xor ebx,ebx            ; 字串相同则清空ebx
	L2:	ret
	main ENDP
END main

比对两个双字数据是否相等.

.data
	var1 DWORD 1234h
	var2 DWORD 5678h
.code
	main PROC
		lea esi,var1
		lea edi,var2
		cmpsd
		je L1
		xor eax,eax      ; 两数如果相等则清空eax
		jmp L2
	L1:	xor ebx,ebx      ; 两数不相等则清空ebx
	L2:	ret
	main ENDP
END main

.data
	Array1 WORD 1,2,3,4,5      ; 必须全部相等才会清空ebx
	Array2 WORD 1,3,5,7,9
.code
	main PROC
		lea esi,Array1
		lea edi,Array2
		mov ecx,lengthof Array1
		
		cld
		repe cmpsw
		je L1
		xor eax,eax        ; 两数不相等则清空eax
		jmp L2
	L1:	xor ebx,ebx        ; 两数相等则清空ebx
	L2:	ret
	main ENDP
END main

依据 AL/AX/EAX 中的数据扫描 EDI 指向的数据, 执行后 EDI 自动变化

.data
	szText BYTE "ABCDEFGHIJK",0
.code
	main PROC
	
		lea edi,szText
		mov al,"F"
		mov ecx,lengthof szText -1
		cld
		repne scasb                 ; 如果不相等则重复
		je L1
		xor eax,eax                 ; 如果没找到F则清空eax
		jmp L2
	L1:	sub ecx,lengthof szText -1
		neg ecx           ; 如果找得到, 这里显示是第几个字符; 本例结果是 6
	L2:	ret
	main ENDP
END main

将 AL/AX/EAX 中的数据储存到 EDI 给出的地址, 执行后 EDI 自动变化

.data
	len = 10
	szText db len dup(0),0
.code
	main PROC
		lea edi,szText                   ; EDI指向字符串
		mov al,"W"                       ; 定义查找字母为W
		mov ecx,len                      ; 设置查找计数器
		cld                              ; 方向=正向
		rep stosb                        ; ecx>0则执行
		ret
	main ENDP
END main

将 ESI 指向的数据载入到 AL/AX/EAX, 执行后 ESI 自动变化,如下是累加求和

.data
	Array WORD 1,2,3,4,5,6,7,8,9,10
.code
	main PROC
		lea esi,Array
		mov ecx,lengthof Array
		xor edx,edx
		xor eax,eax
	@@:	lodsw
		add edx,eax
		loop @B
		
		mov eax,edx           ; 最后将相加结果放入eax

	main ENDP
END main

把String字符串中的,每一个字节均填充初始化为0FFh

.data
	Count = 100                 ; 申请空间为100
	String BYTE Count DUP(?)    ; 初始化为BYTE
.code
	main PROC
		mov al,0FFh             ; 指定要填充的数据为0FFh
		mov edi,offset String   ; EDI寄存器指向目标内存
		mov ecx,Count           ; 循环计数
		cld                     ; 初始化:方向=前方
		rep stosb               ; 以AL中的值进行填充
		ret
	main ENDP
END main

把双字数组中的每一个元素分别乘以一个常量,程序中使用了(LODSD加载),(STOSD保存).

标签: 105b热过载继电器bsr61晶体管

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台