第三章:汇编指令


汇编指令是程序执行的基本单位,所有的汇编指令组成了汇编指令集,即所说的汇编语言。汇编指令可以按照功能分为如下三类:

 

1,汇编指令:机器码助记符,有对应的机器码,比如mov指令等;

2,伪指令:无对应的机器码,编译器执行,比如assume语句;

3,其他符号:+-*/,由编译器识别,无对应机器码。

 

不同平台采用的汇编指令集往往不一样,比如常见的汇编有X86汇编,ARM汇编等。这里我们主要介绍X86汇编,学会一种汇编,再学其它格式的汇编,也很容易触类旁通。X86汇编指令按照格式还分为intel汇编指令和AT&T汇编指令。下面以intel汇编指令为例子,了解一些常用的汇编指令及其功能。要掌握程序调试技术,熟练掌握汇编语言是必须的。因此,鼓励大家花一定的时间去理解阅读和调试汇编代码。

3.1CISC VS RISC

计算机的汇编指令可分为:RISC(reduced instruction set computer)即精简指令集计算机和CISC(complex instruction set computer)即复杂指令集计算机。其中X86CISC代表,ARMRISC代表。

CISC存在指令系统庞大,指令功能复杂,指令格式寻址方式多,执行速度慢,难以优化编译,编译程序复杂,以及80%的指令在20%的运行时间使用,无法并行,无法兼容等问题。

一般来说,CISC指令集与RISC指令集存在如下一些区别:

RISC指令集只提供很有限的操作,基本上单周期执行每条指令,其指令长度也是固定的。CISC指令复杂丰富,有些功能直接使用一个指令,功耗大(所以很少用在对能耗要求高的移动设备上),指令是变长的。

RISC 中,CPU并不会对内存中的数据进行操作,所有的计算都要求在寄存器中完成。而寄存器和内存的通信则由单独的指令来完成。而在CSIC中,CPU是可以直接对内存进行操作的。

CISC 相比,基于RISC的处理器有更多的通用寄存器可以使用,且每个寄存器都可以进行数据存储或者寻址。

现在以X86 CISC指令集为例子,给大家介绍一些常用的汇编指令。只要学会了一种汇编指令,那么很容易举一反三,学习其它汇编指令。在这里,我们把汇编指令归纳为:

1.         传送指令

2.         算术指令

3.         逻辑指令

4.         串操作指令

5.         控制转移指令等

3.2传送指令:Mov/lea/push/pop

传送指令主要用于将数据进行拷贝赋值等操作。它包括movleapushpop等指令。现在逐一对它们进行介绍:

MOV指令

指令格式:MOV   DSTSRC

指令形式:MOV Reg/Mem, Reg/Mem/Imm

其中:RegRegister(寄存器)MemMemory(存储器)ImmImmediate(立即数) ,表示指令在第一个操作数可以是寄存器、内存,第二个操作数可以是寄存器、内存和立即数。

Mov指令例子:

Mov eax,5

Mov ds,eax

Mov ds, 5;

上面的代码是错误的,不能直接对段寄存器使用mov 立即数或者内存指令。需要先放入通用寄存器中。所以上面的代码应该改为:

Mov eax,5

Mov ds,eax

MOV EAX,DWORD PTR SS:[EBP-1C];

上面的指令将把ebp-1C地址存放的值给eax

类似于C中下面2句代码:

DWORD *p=EBP-1C;

eax<=*p

[ ]在汇编中用于获取一个内存地址所指向的内存,以便取得其中的值或者向其赋值。

MOV DWORD PTR DS:[ESI+1C],EAX;

上面的代码将把eax中的值赋值到ESI+1C所指的内存中去,类似于下面的C代码:

DWORD *p=ESI+1C;

*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+len2个字节或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中的SFZFAFPFCF五个标志位分别传送到累加器AH的对应位(八位中有三位是无效的)

SAHF(store AH into flags) AH送标志寄存器

PUSHF(push the flags)  标志寄存器进栈 ,等同于:push eflags

POPF(pop the flags) 标志寄存器出栈,等同于:pop eflags

3.3算术

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)

 

       }

}

3.4逻辑指令

AND(and)                    

OR(or)                       

NOT(not)                     

XOR(exclusive or)            异或

TEST(test)                     测试:(DST)&(SRC)

test两个操作数相与的结果不保存,只根据其特征置条件码(影响EFLAGS寄存器ZF位,配合jz/jnz等条件跳转指令)

3.5串操作指令

串处理:rep movs/stos/lods    cmps/scas

格式: REP string primitive

其中String Primitive可为MOVS LODSSTOS指令

执行的操作:

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]位置,然后disi同方向增加或者减少一个字节(由DF值决定),直到ecx中的值为0

 

MOVS 串传送指令

格式:

MOVS  DSTSRC

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];//ecxrep的次数

 

     cld;//设置DF=0ESI,EDI ++

rep movs byte ptr [edi],byte ptr[esi]

     }

}

 

STOS 存入串指令

格式: 

STOS   DST

STOSB(字节)

STOSW()

执行的操作:

字节操作: ((DI)) <= (AL)(DI) <= (DI)+-1

字操作:   ((DI)) <= (AX)(DI) <= (DI)+-2

该指令把ALAX的内容存入由(DI)指定的附加段的某单元中,并根据DF的值及数据类型修改DI的内容,当它与REP 联用时,可把ALAX的内容存入一个长度为(CX)的缓冲区中。

 

LODS 从串取指令

格式: 

LODS   SRC

LODSB

LODSW

执行的操作:

字节操作:(AL) <= ((SI))(SI) <= (SI)+-1

字操作: (AX) <= ((SI))(SI) <= (SI)+-2

该指令把由(SI)指定的数据段中某单元的内容送到ALAX中,并根据方向标志及数据类型修改SI的内容。指令允许使用段跨越前缀来指定非数据段的存储区。该指令也不影响条件码。一般说来,该指令不和REP 联用。有时缓冲区中的一串字符需要逐次取出来测试时,可使用本指令。

 

lea           edi,[ebp-0C0h]

mov         ecx,30h

mov         eax,0CCCCCCCCh

rep stos   dword ptr es:[edi];

 

上面这段代码是C中函数调用的时候,将栈上局部变量空间,统一设置为CC的汇编代码。CC就是INT 3指令的机器码,所以,一旦有程序出现异常,执行到栈上位置,就会被中断下来,以便发现问题。

 

REPE/REPZREPNZ/REPNE联合工作的CMPSSCAS指令

REPE/REPZ 当相等/为零时重复串操作

格式:  REPE(REPZ) String Primitive

其中String Primitive可为CMPSSCAS指令。

执行的操作:

1)(CX)=0ZF=0(即某次比较的结果两个操作数不等)时退出,否则往下执行

2)(CX)ß(CX)-1

3)执行其后的串指令

4)重复1)~3)

REPNE/REPNZ   当不相等/不为零时重复串操作

格式:  REPNE(REPNZ)    String Primitive

其中String Primitive可为CMPSSCAS指令

执行的操作: 除退出条件(CX==0)ZF==1外,其他操作与REPE完全相同。

 

CMPS  串比较指令

格式:

CMP SRCDST

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.6控制转移指令

跳转指令分为3大类指令:

一、无条件跳转: JMP;

二、根据 CXECX 寄存器的值跳转: JCXZ(CX 0 则跳转)JECXZ(ECX 0 则跳转);

三、根据 EFLAGS 寄存器的标志位跳转

 

1.无条件转移指令

JMP跳转指令

1) 短转移

格式:JMP SHORT value ;

跳转范围:-128--127

2) 近转移

格式:JMP NEAR PTR value

跳转范围:同一个段内-3276832767

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   ;有符号大于则跳转,Ggreat,有符号

JNG  ;有符号不大于则跳转

JGE  ;有符号大于等于则跳转

JNGE ;有符号不大于等于则跳转

JB   ;无符号小于则跳转,Bbelow,无符号小于

JNB  ;无符号不小于则跳转

JBE  ;无符号小于等于则跳转

JNBE ;无符号不小于等于则跳转

JL   ;有符号小于则跳转,Lless,有符号小于

JNL  ;有符号不小于则跳转

JLE  ;有符号小于等于则跳转

JNLE ;有符号不小于等于则跳转

JP   ;奇偶位置位则跳转

JNP  ;奇偶位清除则跳转

JPE  ;奇偶位相等则跳转

JPO  ;奇偶位不等则跳转

 

LOOP 循环指令

格式: LOOP OPR

测试条件:(CX)!=0

LOOPZ/LOOPE 当为零或相等时循环指令

格式: LOOPZ(LOOPE) OPR

测试条件:(CX)==0ZF==1

LOOPNZ/LOOPNE 当不为零或不相等时循环指令

格式: LOOPNZ(LOOPNE)   OPR

测试条件:(CX)!=0ZF==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调用指令(段内,段间,涉及到CSIP入栈)

Call eax

 

RET返回指令

RETN/RETF在汇编代码中的形式如下:
  RETN
  RETN   N
  RETF
  RETF   N

其中retn中的n意即nearretf中的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

3.7处理机控制指令

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)        等待

3.8指令操作码

汇编语句由操作码加操作数(数据或者地址)组成,操作码(operation code,简写为 opcode,也被称为机器码),是指这条指令命令的编码。

比如有一条汇编指令:jmp 0x004fe351

那么它在内存中占5个字节,形式如下:

ea 51 e3 4f 00

其中eajmp命令的操作码,51 e3 4f 00是待跳转地址的低位优先存放格式。每个处理器平台使用的汇编操作码与汇编指令都有对照表。一些常见的汇编指令对应的操作码如下:

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

记住一些常见的操作码,对于我们分析,逆向和调试程序非常有好处。当然,任何汇编指令的机器码,都可以通过查相应的汇编手册查出。

3.9 AT&T汇编语法与Intel语法的比较

AT&TIntel汇编都是X86汇编。Windows一般使用的是Intel汇编,而Linux系统一般使用的是AT&T汇编语法。只要掌握其中一种,通过差异比较,很容易上手另一种汇编语法。如下图所示,一些常见的intelAT&T汇编语法格式的对比图:

1intel语法操作的操作数长度体现在寄存器名称上。以ax寄存器为例,8位用al或者ah1个字节用ax4个字节用eax,地址宽度用byte ptr/word ptr/dword ptr。而AT&T汇编还会在指令后面加后缀:

movb/movw/movl

2Intel中寄存器的名字写法:eax,而AT&T中寄存器的写法%eax

3Intel汇编数据移动方向是从右往左,而AT&T是从左往右,比如把bl寄存器中的值移动到al中,那么分别写法:

Intel:          mov al,bl

AT&T:        movb %bl,%al

4Intel语法中立即数为:0ffehAT&T中立即数写法:$0xffe

5base+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



看文字不过瘾?点击我,进入周哥教IT视频教学
麦洛科菲长期致力于IT安全技术的推广与普及,我们更专业!我们的学员已经广泛就职于BAT360等各大IT互联网公司。详情请参考我们的 业界反馈 《周哥教IT.C语言深学活用》视频

我们的微信公众号,敬请关注