Win32汇编:汇编基本知识总结
汇编语言是所有程序设计语言中最古老的,它与计算机机器语言最为接近,通过汇编语言可以直接访问计算机的硬件,能够直接与CPU对话,可以说汇编语言是所有编程语言中语法格式最自由的,但自由的代价就是需要了解计算机体系结构和操作系统的大量细节,每编写一段程序都需要考虑各种硬件的状态,从而导致使用汇编写程序效率非常低.
本篇文章,文字描述部分参考自《Intel 汇编语言程序设计》这本书,学习后总结的重要笔记,如果时间充足可以去阅读此书。
自1946年第一台计算机问世以来,在短短的60多年中,已经历了由电子管计算机(1946年),晶体管计算机(1956年),集成电路计算机(1958年),超大规模集成电路计算机(1972年),这五代的更替,而且还在不断地向巨型化,微型化,网络化,智能化这四个方向不断发展.
从当今的X86架构的CPU说起,X86指令集是Intel为其第一块16位CPU(80x86)专门开发的,IBM公司1981年推出的世界第一台PC机中的CPU—i8088(i8086简化版)使用的也是X86指令,同时电脑中为提高浮点数据处理
能力而增加的X87芯片系列协处理器则另外使用X87指令,为了提高处理器性能,就将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:堆栈段
,其他的段操作系统负责维护.
虚拟x86模式: 在该模式下,实际上是处理器在保护模式下创建的一个具有1MB地址空间的虚拟机,虚拟机对运行于实地址模式下的80x86计算机
进行了模拟,在Windows NT
系统下,打开一个控制台窗口,就创建了一个8086虚拟机,当然你也可同时打开多个控制台,他们之间是隔离的并不互相影响.
平坦分段模式: 在该模式下,所有段都被映射到32位的物理地址空间中,一个程序至少需要2个段:代码段(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个通用寄存器
主要用于算数运算和数据的传送,这8个寄存器都可以作为一个32位的值或两个16位的值来寻址使用,还可以按照8位寄存器来使用,比如通用寄存器都可以被拆分为高低寄存器来存储数据,例如:EAX寄存器
,可被拆分为(AX)16位
寄存器来使用,而(AX)16位
寄存器还可拆分为AH/AL(高低8位)
.
变址寄存器: CPU内部有2个通用寄存器ESI和EDI
,寄存器ESI、EDI称为变址寄存器(Index Register)
,它们主要用于存放存储单元在段内的偏移量,用它可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便.变址寄存器不可分割
成8位寄存器,在字符串操作指令的执行过程中,对它们有特定的要求,而且还具有特殊的功能,该寄存器默认和DS数据段
寄存器相关联.
堆栈指针寄存器: CPU内部有2个通用寄存器EBP和ESP
,寄存器EBP、ESP称为指针寄存器(Pointer Register)
,主要用于存放堆栈
内存储单元的偏移量,它们主要用于访问堆栈内的存储单元并且规定,EBP为基址指针寄存器
,ESP为堆栈指针寄存器
,指针寄存器不可分割
成8位寄存器,该寄存器默认和SS堆栈段
寄存器相关联.
指令指针寄存器: CPU内部有1个指令指针寄存器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
标准输入输出
StdIn/StdOut: 使用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
WriteFile: 通过调用系统的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
crt_printf: 使用微软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
常用汇编指令
MOV指令: 从源操作数向目标操作数之间复制数据.
00A41000 | B8 24100000 | mov eax,1024 |
00A41005 | 8BD8 | mov ebx,eax |
00A41007 | 66:B9 0010 | mov cx,1000 |
MOVZX指令: 零扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值零扩展(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
MOVSX指令: 符号扩展传送,该指令将源操作数的内容复制到目标操作数中,并将该值符号扩展(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
XCHG指令: 数据交换指令,该指令用于交换两个操作数中的内容,但该指令不接受立即数操作数.
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指令: 数据递增与递减,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
ADD指令: 操作数增加,该指令用于将源操作数和目的操作数相加
,且不影响源操作数的值,而是改变目的操作数.
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
SUB指令: 操作数减少,该指令用于将源操作数和目的操作数相减
,且不影响源操作数的值,而是改变目的操作数.
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
AND/OR/XOR指令: 逻辑与/逻辑或/逻辑异或.
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 |
OFFSET操作符: 返回数据标号的偏移地址,偏移地址代表标号距数据基址的距离,单位是字节.
.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
PTR操作符: 用来重载声明操作数的默认尺寸,这在试图以不同与变量声明时所使用的尺寸来访问变量时很有用.
.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
LENGTHOF操作符: 计算数组元素的数目,元素由出现在的同一行的值定义.
.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
TYPE操作符: 返回按照字节计算的单个元素的大小.
.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
SIZEOF操作符: 返回等于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
LOOP循环(普通循环): 该指令检测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
LOOP循环(循环中使用ECX): 如果用光了所有的通用寄存器,但又必须要使用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
LOOP循环(嵌套循环): 在循环内部创建另一个循环的时候,必须考虑外层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
IF-ENDIF(伪指令):
.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
WHILE-ENDW(伪指令):
.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
REPEAT-UNTIL(伪指令): 以下代码利用循环伪指令,完成了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
BREAK(伪指令): 以下是个死循环,当eax寄存器的值等于5时,则执行.break结束程序的运行.
.code
main PROC
mov eax,10
.while (1)
dec eax
.break .if(eax == 5)
.endw
ret
main ENDP
END main
CONTINUE(伪指令): 当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
FOR 字符替换(伪指令): 该伪指令并不是循环,而是分别将指定的指令批量的替换到程序中.
.code
main PROC
for num,<1,2,3>
xor eax,eax
add eax,DWORD PTR [num]
endm
ret
main ENDP
END main
FORC字串替换(伪指令): 该伪指令并不是循环,而是分别将指定的字串批量的替换到程序中.
.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内存寻址: 通过使用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堆栈寻址: 通过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零标志位: 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奇偶标志位: 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标志相关指令执行后,其结果是否为负,若为负则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标志相关指令执行后,在进行无符号运算时,如果表达式发生进位或借位则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标志相关指令执行后,超出机器所能表示的范围称为溢出若发生了溢出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
TEST指令: 该操作与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
CMP指令: 在源操作数和目标操作数进行减法操作,只影响标志位.
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) | 小于或等于则跳转 |
JZ/JE通用跳转: 检测到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
JNZ/JNE通用跳转: 检测到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无符号跳转: 基于无符号
数的跳转
指令,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无符号跳转: 基于无符号
数的跳转
指令,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有符号跳转: 基于有符号
数的跳转
指令,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有符号跳转: 基于有符号
数的跳转
指令,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)
JCXZ/JECXZ跳转指令: 检测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 逻辑移位◆
SHL指令: 对目标操作数执行逻辑左移(针对无符号数)
操作,其左移后最低位
以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
SHR指令: 对目标操作数执行逻辑右移(针对无符号数)
操作,移出的数据位用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(无符号乘法)
指令有三种格式.
8位乘法: 计算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位乘法: 将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位乘法: 将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
字符串复制(movsb):
.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
另一种字串复制(movsb): 不使用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
(movsd)四字节复制字串:
.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
CMPSB:
.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
CMPSD: 比对两个双字数据是否相等.
.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
CMPSW:
.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
SCASB 扫描字串: 依据 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
STOSB 存储字串: 将 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
LODSW 载入指令: 将 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保存)
.
.data
Array DWORD 1,2,3,4,5
Multi DWORD 10
.code
main PROC
mov esi,offset Array ; 源指针
mov edi,esi ; 目的指针
cld ; 方向=向前
mov ecx,lengthof Array ; 循环计数器
L1: lodsd ; 加载[esi]至EAX
mul Multi ; 将EAX乘以10
stosd ; 将结果从EAX存储至[EDI]
loop L1
ret
main ENDP
END main
计算字符串长度: 以下代码用于计算字符串的长度,并将结果保存在EAX寄存器中.
.data
String BYTE "hello world",0 ; 带计算字符串
.code
main PROC
mov edi,offset String ; 取出字符串的基地址
xor eax,eax ; 清空eax用作计数器
L1: cmp byte ptr [edi],0 ; 分别那[edi]的值和0作比较
je L2 ; 上一步为零则跳转得到ret
inc edi ; 否则继续执行
inc eax
jmp L1
L2: ret
main ENDP
END main
小写字串转大写: 将MyString变量中的小写字符串,依次转换为大写字符串.
.data
MyString db "hello lyshark",0 ; 定义MyString字符串
.code
main PROC
mov esi,offset MyString ; 取出字符串的偏移地址
L1: cmp byte ptr [esi],0 ; 分别拿出每一个字节,与0比较
je L2 ; 如果相等则跳转到L2
and byte ptr [esi],11011111b ; 执行按位与操作
inc esi ; 每次esi指针递增1
jmp L1 ; 重复循环
L2: ret
main ENDP
END main
定义二维数组: 定义一个二维数组Table,并取出第一行中的第2偏移地址的元素.
.data
Table WORD 10h,20h,30h,40h,50h ; 定义一个数组
Row = ($ - Table) ; 取出数组每行的字节数
WORD 60h,70h,80h,90h,0Ah ; 继续定义数组
.code
main PROC
row_index = 1 ; 表的偏移地址
column_index = 2 ; 行的偏移地址
mov ebx,offset Table ; 取偏移地址给ebx
add ebx,Row*row_index ; 每行元素*偏移
mov esi,column_index ; 将行偏移赋值给esi
mov ax,[ebx+esi*TYPE Table] ; Table比例因子只能是2,4,8
main ENDP
END main
位操作指令
符号扩展(CBW/CWDE):
.code
main PROC
mov al,7fh
cbw ; 将 AL 扩展为 AX
PrintHex ax ;007F
mov al,80h
cbw
PrintHex ax ;FF80
mov ax,7fffh
cwde ; 将 AX 扩展为 EAX
PrintHex eax
main ENDP
END main
符号扩展(CDQ/CWD):
.code
main PROC
mov eax,7FFFFFFFh
cdq ; 将 EAX 扩展为 64 位数 EDX:EAX
PrintHex edx ;00000000
PrintHex eax ;7FFFFFFF
mov ax, 7FFFh
cwd ; 将 AX 扩展为 DX:AX
PrintHex dx ;0000
PrintHex ax ;7FFF
main ENDP
END main
BT、BTS、BTR、BTC: 位测试指令
;BT(Bit Test): 位测试
;BTS(Bit Test and Set): 位测试并置位
;BTR(Bit Test and Reset): 位测试并复位
;BTC(Bit Test and Complement): 位测试并取反
;它们的结果影响 CF
;它们的指令格式相同:
BT r16/r32/m16/m32, r16/r32/m16/m32
BT r16/r32/m16/m32, i8
.code
main proc
;BT 把 10000001b 的第七位复制到 CF, 得知是 1
mov dx, 10000001b
bt dx, 7
lahf
PrintHex ah ;47 - 01000111b (CF=1)
;BT 把 10000001b 的第六位复制到 CF, 得知是 0
bt dx, 6
lahf
PrintHex ah ;86 - 10000110b (CF=0)
;BTS 在执行 BT 命令的同时, 把操作数的指定位置为 1
mov dx, 10000001b
bts dx, 6
PrintHex dl ;C1 - 11000001b
;BTR 在执行 BT 命令的同时, 把操作数的指定位置为 0
mov dx, 10000001b
btr dx, 7
PrintHex dl ;01 - 00000001b
;BTC 在执行 BT 命令的同时, 把操作数的指定位取反
mov dx, 10000001b
btc dx, 0
PrintHex dl ;80 - 10000000b
btc dx, 0
PrintHex dl ;81 - 10000001b
ret
main endp
end main
BSF、BSR: 位扫描指令
;BSF(Bit Scan Forward): 位扫描, 低 -> 高
;BSR(Bit Scan Reverse): 位扫描, 高 -> 低
;它们的结果影响 ZF
;扫描的是参数二, 找到是 1 的位后, 把位置数给参数一并置 ZF=0
;找不到(也就是参数二是 0)时, 置 ZF=1
;它们的指令格式相同:
BSF r16/r32, r16/r32/m16/m32
.code
main proc
;扫描到时
mov dx, 0000111100001100b
bsf cx, dx
PrintDec cx ;2 - 也就是左数第 3 位
bsr cx, dx
PrintDec cx ;11 - 也就是左数第 12 位
;扫描不到时
mov cx, 0FFFFh
mov dx, 0
bsf cx, dx
lahf
PrintHex ah ;C6 - 11000110 (ZF=1)
PrintHex cx ;FFFF - 找不到时不会影响到目的值
ret
main endp
end main
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!