汇编指令是程序执行的基本单位,所有的汇编指令组成了汇编指令集,即所说的汇编语言。汇编指令可以按照功能分为如下三类:
1,汇编指令:机器码助记符,有对应的机器码,比如mov指令等;
2,伪指令:无对应的机器码,编译器执行,比如assume语句;
3,其他符号:+,-,*,/,由编译器识别,无对应机器码。
不同平台采用的汇编指令集往往不一样,比如常见的汇编有X86汇编,ARM汇编等。这里我们主要介绍X86汇编,学会一种汇编,再学其它格式的汇编,也很容易触类旁通。X86汇编指令按照格式还分为intel汇编指令和AT&T汇编指令。下面以intel汇编指令为例子,了解一些常用的汇编指令及其功能。要掌握程序调试技术,熟练掌握汇编语言是必须的。因此,鼓励大家花一定的时间去理解阅读和调试汇编代码。
计算机的汇编指令可分为:RISC(reduced instruction set computer)即精简指令集计算机和CISC(complex instruction set computer)即复杂指令集计算机。其中X86是CISC代表,ARM是RISC代表。
CISC存在指令系统庞大,指令功能复杂,指令格式寻址方式多,执行速度慢,难以优化编译,编译程序复杂,以及80%的指令在20%的运行时间使用,无法并行,无法兼容等问题。
一般来说,CISC指令集与RISC指令集存在如下一些区别:
RISC指令集只提供很有限的操作,基本上单周期执行每条指令,其指令长度也是固定的。CISC指令复杂丰富,有些功能直接使用一个指令,功耗大(所以很少用在对能耗要求高的移动设备上),指令是变长的。
在RISC 中,CPU并不会对内存中的数据进行操作,所有的计算都要求在寄存器中完成。而寄存器和内存的通信则由单独的指令来完成。而在CSIC中,CPU是可以直接对内存进行操作的。
和CISC 相比,基于RISC的处理器有更多的通用寄存器可以使用,且每个寄存器都可以进行数据存储或者寻址。
现在以X86 CISC指令集为例子,给大家介绍一些常用的汇编指令。只要学会了一种汇编指令,那么很容易举一反三,学习其它汇编指令。在这里,我们把汇编指令归纳为:
1. 传送指令
2. 算术指令
3. 逻辑指令
4. 串操作指令
5. 控制转移指令等
传送指令主要用于将数据进行拷贝赋值等操作。它包括mov,lea,push,pop等指令。现在逐一对它们进行介绍:
MOV指令
指令格式:MOV DST,SRC
指令形式:MOV Reg/Mem, Reg/Mem/Imm
其中:Reg即Register(寄存器),Mem即Memory(存储器),Imm即Immediate(立即数) ,表示指令在第一个操作数可以是寄存器、内存,第二个操作数可以是寄存器、内存和立即数。
Mov指令例子:
Mov eax,5
Mov ds,eax
Mov ds, 5;
上面的代码是错误的,不能直接对段寄存器使用mov 立即数或者内存指令。需要先放入通用寄存器中。所以上面的代码应该改为:
Mov eax,5
Mov ds,eax
MOV EAX,DWORD PTR SS:[EBP
上面的指令将把ebp
类似于C中下面2句代码:
DWORD *p=EBP
eax<=*p
[ ]在汇编中用于获取一个内存地址所指向的内存,以便取得其中的值或者向其赋值。
MOV DWORD PTR DS:[ESI+
上面的代码将把eax中的值赋值到ESI+
DWORD *p=ESI+
*p<=eax
观察下面的代码:
mov ax, word ptr value
mov eax, dword ptr value
mov al,byte ptr value
这里的word ptr/dword ptr/byte ptr实际上是指定地址value的宽度(分别对应2字节,4字节和1个字节,以便按照宽度来取值。
PUSH/POP指令:
指令格式:push reg/mem/seg/imm;
该指令将会把reg/mem/seg/imm中的值入栈,放入栈顶ss:[sp](sp为栈顶指针寄存器)。然后将sp减去步长(入栈数据的长度)上抬。
ss:[sp]<=reg/mem/seg/imm;sp<=sp-len(2个字节或4个字节)
指令格式:pop reg/seg/mem
该指令将会把栈顶的数据放入reg/mem/seg里,然后将sp加上步长。reg/seg/mem<=ss:[sp],sp<=sp+len(2个字节或4个字节)
指令例子:
Push eax;
Push 5;
pop eax
PUSH [2000H]
PUSH CS
POP [2000H]
POP SS
LEA:load effective address
LEA指令是取偏移地址,MOV指令是取数据。这是两个容易混淆的指令,现举例说明。参见下面2条指令:
mov eax,[400000H]//将内存单元400000H中的数据放入eax中,[ ]类似与C语言中的*运算符
lea eax,[400000H]//直接将偏移地址400000H放入eax中,等价于:mov eax,40000H
又比如:
Mov ebx,[eax]//eax中存放的是内存地址,将该内存地址指向的内存数据放入ebx中
Lea ebx,[eax]//将eax直接放入ebx中,等价于mov ebx,eax
Lea ebx,TABLE//将TABLE的偏移地址放入ebx中,等价于mov ebx, offset table
LEA EAX,[EBX+ECX*2+1]//将ebx+ecx*2+1的值放入eax寄存器中
但不能写成:
Mov eax, ebx+ecx*2 +1// 不能这样写,mov指令不支持这种格式
lea eax,[ebx+edx+1]//将ebx+edx+1值放入eax
Mov eax, ebx+edx+1//不能这样写,mov指令不支持这种格式,如果要使用mov来实现,则需要使用3条指令来实现:
mov eax,ebx
add eax,edx
add eax,1
上面3条指令明显不如lea一条指令简洁。
LAHF(Load AH with flags):flags寄存器低8位送AH
用于将标志寄存器的低八位送入AH,即将标志寄存器FLAGS中的SF、ZF、AF、PF、CF五个标志位分别传送到累加器AH的对应位(八位中有三位是无效的)
SAHF(store AH into flags) AH送标志寄存器
PUSHF(push the flags) 标志寄存器进栈 ,等同于:push eflags
POPF(pop the flags) 标志寄存器出栈,等同于:pop eflags
ADD(add)加法
ADC(add with carry)带进位加法
INC(increment)加1
DEC(Decrement)减1
SUB(subtract)减法
SBB(subtract with borrow)带借位减法(DST)←(DST)-(SRC)-CF,其中CF为进位的值
NEG(Negate)求补
CMP(Compare)比较
MUL(Unsigned Multiple)无符号数乘法
IMUL(Signed Multiple)带符号数乘法
1.
IMUL al,r/m8 : AX <=AL * r/m
2.
IMUL r32,r/m32,imm32 : r32<=r/m*imm32
3.
IMUL eax,r/m32 : EDX:EAX <= EAX * r/m
DIV(Unsigned divide)无符号数除法
IDIV(Signed divide)带符号数除法
CBW(Convert byte to word)字节转换为字
CWD(Contert word to double word)字转换为双字
int add_asm(int x,int y)
{
__asm{
mov eax,x;
add eax,y;
}
}
int mul_asm(int x, int y)
{
__asm {
mov eax,x;
imul eax,y;
}
}
int idiv_asm(int x, int y,int *m)
{
__asm{
;mov edx,0;
xor edx,edx;把被除数放在edx:eax中,edx设为0
mov eax, x;被除数x放入eax中
idiv y;edx:eax/y,商放入eax中,余数放入edx中
mov ebx,m
mov [ebx],edx;int *p = ebx; *p = edx(*m=edx)
}
}
AND(and) 与
OR(or) 或
NOT(not) 非
XOR(exclusive or) 异或
TEST(test) 测试:(DST)&(SRC)
test两个操作数相与的结果不保存,只根据其特征置条件码(影响EFLAGS寄存器ZF位,配合jz/jnz等条件跳转指令)
串处理:rep movs/stos/lods cmps/scas
格式: REP string primitive
其中String Primitive可为MOVS, LODS或STOS指令
执行的操作:
1)如(CX)=0则退出REP ,否则往下执行。
2)(CX) <= (CX)-1
3)执行其中的串操作
4)重复1)~3)
CLD(Clear direction flag)该指令使DF=0,在执行串操作指令时可使地址自动增量;
STD(Set direction flag)该指令使DF=1,在执行串操作指令时可使地址自动减量。
REP MOVS BYTE PTR[DI],BYTE PTR[SI]
上面指令将[si]中的值拷贝到[di]位置,然后di和si同方向增加或者减少一个字节(由DF值决定),直到ecx中的值为0。
MOVS 串传送指令
格式:
MOVS DST,SRC
MOVSB(字节)
MOVSW(字)
其中第二、三种格式明确地注明是传送字节或字,第一种格式则应在操作数中表明是字还是字节操作,例如:MOVS ES:BYTE PTR[DI],DS:[SI]
执行的操作:
1)((DI)) <= ((SI))
2)字节操作:
(SI) <= (SI)+(或-)1,(DI) <=(DI)+(或-)1
当方向标志DF=0时用+,当方向标志DF=1时用-
3)字操作:
(SI) <= (SI)+(或-)2,(DI) <= (DI)+(或-)2
当方向标志DF=0时用+,当方向标志DF=1时用-
该指令不影响条件码。
下面是通过汇编实现的字符串n个字节拷贝函数
void strncpy_asm(char *dst, char *src,size_t len)
{
_asm{
;mov edi,dst;
;mov esi,src;
;mov ecx,len;
mov edi, [ebp+8]//目标地址
mov esi, [ebp+0xc]//源地址
mov ecx,[ebp+0x10];//ecx里rep的次数
cld;//设置DF=0,ESI,EDI ++
rep movs byte ptr [edi],byte ptr[esi]
}
}
STOS 存入串指令
格式:
STOS DST
STOSB(字节)
STOSW(字)
执行的操作:
字节操作: ((DI)) <= (AL),(DI) <= (DI)+-1
字操作: ((DI)) <= (AX),(DI) <= (DI)+-2
该指令把AL或AX的内容存入由(DI)指定的附加段的某单元中,并根据DF的值及数据类型修改DI的内容,当它与REP 联用时,可把AL或AX的内容存入一个长度为(CX)的缓冲区中。
LODS 从串取指令
格式:
LODS SRC
LODSB
LODSW
执行的操作:
字节操作:(AL) <= ((SI)),(SI) <= (SI)+-1
字操作: (AX) <= ((SI)),(SI) <= (SI)+-2
该指令把由(SI)指定的数据段中某单元的内容送到AL或AX中,并根据方向标志及数据类型修改SI的内容。指令允许使用段跨越前缀来指定非数据段的存储区。该指令也不影响条件码。一般说来,该指令不和REP 联用。有时缓冲区中的一串字符需要逐次取出来测试时,可使用本指令。
lea
edi,[ebp
mov
ecx,30h
mov
eax,0CCCCCCCCh
rep stos dword ptr es:[edi];
上面这段代码是C中函数调用的时候,将栈上局部变量空间,统一设置为CC的汇编代码。CC就是INT 3指令的机器码,所以,一旦有程序出现异常,执行到栈上位置,就会被中断下来,以便发现问题。
与REPE/REPZ和REPNZ/REPNE联合工作的CMPS和SCAS指令
REPE/REPZ 当相等/为零时重复串操作
格式: REPE(或REPZ) String Primitive
其中String Primitive可为CMPS或SCAS指令。
执行的操作:
1)如(CX)=0或ZF=0(即某次比较的结果两个操作数不等)时退出,否则往下执行
2)(CX)ß(CX)-1
3)执行其后的串指令
4)重复1)~3)
REPNE/REPNZ 当不相等/不为零时重复串操作
格式: REPNE(或REPNZ) String Primitive
其中String Primitive可为CMPS或SCAS指令
执行的操作: 除退出条件(CX==0)或ZF==1外,其他操作与REPE完全相同。
CMPS 串比较指令
格式:
CMP SRC,DST
CMPSB
CMPSW
执行的操作:
1)((SI))-((DI))
2)字节操作:(SI) <= (SI)+-1,(DI) <= (DI)+-1
字操作: (SI) <= (SI)+-2,(DI) <= (DI)+-2
指令把由(SI)指向的数据段中的一个字(或字节)与由(DI)指向的附加段中的一个字(或字节)相减,但不保存结果,只根据结果设置条件码,指令的其他特性和MOVS 指令的规定相同。
SCAS 串扫描指令
格式:
SCAS DST
SCASB
SCASW
执行的操作:
字节操作:(AL)-((DI)),(DI) <= (DI)+-1
字操作: (AL)-((DI)),(DI) <= (DI)+-2
该指令把AL(或AX)的内容与由(DI)指定的在附加段中的一个字节(或字)进行比较,并不保存结果,只根据结果置条件码。指令的其他特性和MOVS 的规定相同。
int strcmp_asm(char *s1, char *s2)
{
__asm{
mov
esi,s1;
mov
edi,s2;
L1:
lodsb;lodsb:[esi]<=al,esi=esi+1
scasb;scasb:al-[edi],edi = edi+1
jne L2;
test
al,al;判断esi中是否到达字符串末尾
jne L1;
xor
eax,eax
jmp L3;
L2:
sbb eax,eax;
or al,1;
L3:
}
}
跳转指令分为3大类指令:
一、无条件跳转: JMP;
二、根据 CX、ECX 寄存器的值跳转: JCXZ(CX 为 0 则跳转)、JECXZ(ECX 为 0 则跳转);
三、根据 EFLAGS 寄存器的标志位跳转
1.无条件转移指令
JMP跳转指令
1) 短转移
格式:JMP SHORT value ;
跳转范围:-128--127
2) 近转移
格式:JMP NEAR PTR value
跳转范围:同一个段内-32768至32767
3)段间(远)转移
格式:JMP FAR PTR value
2.条件转移
JE ;等于则跳转
JNE ;不等于则跳转
JZ ;为 0 则跳转
JNZ ;不为 0 则跳转
JS ;为负则跳转
JNS ;不为负则跳转
JC ;进位则跳转
JNC ;不进位则跳转
JO ;溢出则跳转
JNO ;不溢出则跳转
JA ;无符号大于则跳转,A:above, 无符号
JNA ;无符号不大于则跳转
JAE ;无符号大于等于则跳转
JNAE ;无符号不大于等于则跳转
JG ;有符号大于则跳转,G:great,有符号
JNG ;有符号不大于则跳转
JGE ;有符号大于等于则跳转
JNGE ;有符号不大于等于则跳转
JB ;无符号小于则跳转,B:below,无符号小于
JNB ;无符号不小于则跳转
JBE ;无符号小于等于则跳转
JNBE ;无符号不小于等于则跳转
JL ;有符号小于则跳转,L:less,有符号小于
JNL ;有符号不小于则跳转
JLE ;有符号小于等于则跳转
JNLE ;有符号不小于等于则跳转
JP ;奇偶位置位则跳转
JNP ;奇偶位清除则跳转
JPE ;奇偶位相等则跳转
JPO ;奇偶位不等则跳转
LOOP 循环指令
格式:
测试条件:(CX)!=0
LOOPZ/LOOPE 当为零或相等时循环指令
格式: LOOPZ(或LOOPE) OPR
测试条件:(CX)==0且ZF==1
LOOPNZ/LOOPNE 当不为零或不相等时循环指令
格式: LOOPNZ(或LOOPNE) OPR
测试条件:(CX)!=0且ZF==0
//计算2^n需要n-1条重复的指令add ax, ax
//2^index
unsigned int power_2(unsigned int index)
{
__asm{
mov eax,2
mov edx,index
sub edx,1
mov ecx,edx;
s:
add eax,eax
loop s
}
}
子程序调用指令:
CALL调用指令(段内,段间,涉及到CS和IP入栈)
Call eax
RET返回指令
RETN/RETF在汇编代码中的形式如下:
RETN
RETN N
RETF
RETF N
其中retn中的n意即near,retf中的f意即far。
RETN等价于一条指令:
POP eip
RETF等价于两条指令:
POP eip
POP CS
而带有操作数N的RETN/RETF指令则是在POP之后,执行ESP=ESP+N。比如:
RETF 8等价于:
pop eip
pop cs
add esp, 8
RET 既有可能是retn,也有可能是retf。
中断指令:
INT指令
格式: INT TYPE
比如:
int 3//断点指令
int 21h等
或 INT
iret
1.标志处理指令
CLC 进位位置0指令(Clear carry)CF<=0
CMC 进位位求反指令(Complement carry)CF<=CF
STC 进位位置1指令(Set carry)CF<=1
CLD 方向标志置0指令(Clear direction)DF<=0
STD 方向标志置1指令(Set direction)DF<=1
CLI 中断标志置0指令(Clear interrupt)IF<=0
STI 中断标志置1指令(Set interrupt)IF<=1
2.其他处理机控制指令
NOP(No Opreation) 无操作
HLT(Halt)
停机
WAIT(Wait)
等待
汇编语句由操作码加操作数(数据或者地址)组成,操作码(operation code,简写为 opcode,也被称为机器码),是指这条指令命令的编码。
比如有一条汇编指令:jmp 0x004fe351
那么它在内存中占5个字节,形式如下:
ea 51 e3
其中ea是jmp命令的操作码,51 e3
short jump:eb
near jump:e9
far jump:ea
je/jz:74
jne/jnz:75
nop:90
int 3:cc
ret:c3
call eax:d0ff
jmp esp:e4ff
记住一些常见的操作码,对于我们分析,逆向和调试程序非常有好处。当然,任何汇编指令的机器码,都可以通过查相应的汇编手册查出。
AT&T与Intel汇编都是X86汇编。Windows一般使用的是Intel汇编,而Linux系统一般使用的是AT&T汇编语法。只要掌握其中一种,通过差异比较,很容易上手另一种汇编语法。如下图所示,一些常见的intel与AT&T汇编语法格式的对比图:
1,intel语法操作的操作数长度体现在寄存器名称上。以ax寄存器为例,8位用al或者ah,1个字节用ax,4个字节用eax,地址宽度用byte ptr/word ptr/dword ptr。而AT&T汇编还会在指令后面加后缀:
movb/movw/movl
2,Intel中寄存器的名字写法:eax,而AT&T中寄存器的写法%eax
3,Intel汇编数据移动方向是从右往左,而AT&T是从左往右,比如把bl寄存器中的值移动到al中,那么分别写法:
Intel: mov al,bl
AT&T: movb %bl,%al
4,Intel语法中立即数为:0ffeh,AT&T中立即数写法:$0xffe。
5,base+index*scale+disp寻址写法,比如:
intel中:
sub eax,[ebx+ecx*4h-20h]
AT&T中: subl -0x20(%ebx,%ecx,0x4),%eax
intel中:
sub eax,[ebx+ecx*4h]
AT&T中: subl (%ebx,%ecx,0x4),%eax
intel中:
sub eax,[ebx -20h]
AT&T中: subl -0x20(%ebx),%eax
intel中:
sub eax,[ebx+ecx*4h]
AT&T中: subl (%ebx,%ecx,0x4),%eax
Copyright 2011-2020 © MallocFree. All rights reserved.