赞
踩
指令指针寄存器EIP:
CS:EIP
顺序执行指令的过程
CPU执行代码(程序)就是一条接一条地执行机器指令。可以把CPU执行指令的过程看做一条处理指令的流水线,通过以下两个步骤实现的指令的顺序执行。
这些工作是CPU自动完成的,只需要把我们编写的汇编程序存入代码段,就可以自动顺序执行了。
控制转移指令
顺序执行指令,相当于一个顺序结构,很多时候这并不能满足要求。就好像我们平时写的C语言程序只能从前往后一条条执行一样,如果我们想做一个分支或者循环,就必须要用关键词if、for、while。汇编程序中与其对应的就是控制转移指令,它通过直接改变EIP寄存器的内容,实现指令执行过程中的跳转
转移
:非自动顺序调整EIP内容控制转移指令
:专门用于改变EIP内容的指令条件转移
:某一条件满足时,发生转移,否则继续顺序执行。Jcc LABEL
jcc
代表各种条件转移指令的缩写(助记符),当条件满足时,转到标号LABEL
处执行;否则顺序执行段内转移
。在满足条件的情况下,只改变指令指针寄存器EIP。也就是说,条件转移的转移目的地仅限于同一个代码段内。这种不改变代码段寄存器,仅改变EIP的转移称为段内转移让我们先来看一看什么叫比较,一段自然语言描述 “如果a比b大,那么…”,实际上包含2个工作:
对应到编程中也是如此,如果比较结果没有对程序运行造成任何影响,那么比较没有意义。上述2个工作在汇编实现中,对应到:
cmp指令
)jcc指令
)名称 | CMP(比较指令) |
---|---|
格式 | CMP DEST,SRC |
动作 | 根据DEST-SRC的差 影响标志寄存器中各状态标志,但不结果作为结果的差值送目的寄存器 |
合法值 | SRC:通用寄存器、存储单元、立即数 |
DEST:通用寄存器、存储单元 | |
注意 | DEST 和 SRC 必须尺寸一致 |
除了不把差值结果送DEST外,其他和SUB指令完全一致 |
CMP EDX, -2 ;把EDX与-2比较
CMP ESI, EBX ;把ESI与EBX比较
CMP AL, [ESI] ;AL与由ESI所指的字节存储单元值作比较
CMP [EBX+EDI*4+5], DX ;由EBX+EDI*4+5所指字存储单元值与DX作比较
JN/JE
)
JNB/JAE/JC
)JL/JNGE
)//假设ECX和EDX存储两个数,现在要把较大的存在ECX中,较小的存在EDX中
//如果这两个数是有符号数
cmp ecx,edx
jge OK //有符号数比较转移:ecx>=edx转到OK
xchg ecx,edx //ecx<edx,交换
OK:
//如果这两个数是无符号数
cmp ecx,edx
jae OK //无符号数比较转移:ecx>=edx转到OK
xchg ecx,edx //ecx<edx,交换
OK:
//用汇编实现下面C函数转的功能: int myCmpare(int x,int y) { int z=1; if(x>=13 && y<=28) z=2; return z; } //转汇编,假设ecx传递x,edx传递y,eax作变量z: mov eax,1 cmp ecx,13 //x和13比较 jl SHORT lab1 //SHORT参数代表转移目的地就在附近 cmp edx,28 //y和28比较 jg SHORT lab1 mov eax,2 lab1: ret //函数结束,返回到调用者
名称 | CMP(比较指令) |
---|---|
格式 | JMP LABEL |
动作 | 指令控制无条件转移到LABEL处 |
注意 | 是段内转移,没有任何前提一定发生转移,类似C中的goto |
通常用在if-else分支用,if分支结束后跳过else分支 |
//原始C代码(_fastcall表示用寄存器传参数) int _fastcall cf215(int x, int y) { int z; if ( x > 10 ) //语句A z = 3*x+4*y+7; else z = 2*x+7*y-12; if ( y <= 20 ) //语句B z = 4*z+3; return z; //语句C } //反编译 cmp ecx, 10 //x与10比较 jle SHORT LN3cf215 //当小于等于10时转 lea eax, DWORD PTR [ecx+ecx*2] //计算表达式3*x+4*y+7 lea eax, DWORD PTR [eax+edx*4+7] jmp SHORT LN2cf215 //无条件转(if-else语句结束) LN3cf215: lea eax, DWORD PTR [edx*8] //计算表达式7*y+2*x-12 sub eax, edx lea eax, DWORD PTR [eax+ecx*2-12] LN2cf215: cmp edx, 20 //y与20比较 jg SHORT LN1cf215 //当大于20时转 lea eax, DWORD PTR [eax*4+3] //计算4*z+3LN1cf215: ret
程序的运行和堆栈有密切关系
堆栈
堆栈
:一段内存区域,对他的访问限于一端进行。存储于堆栈段,段寄存器为SS栈底
:堆栈中地址较大的一端栈顶
:堆栈中地址较小的一端堆栈的操作
进栈/压栈操作
:存入数据出栈/弹出操作
:取出数据堆栈相关寄存器
寄存器 | 存储内容 |
---|---|
SS(堆栈段寄存器) | 当前堆栈段号,指示堆栈所在内存区域的位置 |
ESP(堆栈指针寄存器) | 栈顶的偏移,SS:ESP永远指向栈顶,CPU自动控制 |
EBP(堆栈数据寄存器) | 栈内数据的偏移,SS:EBP指向栈中一个数据 (习惯指向函数帧栈底),手动控制 |
堆栈平衡
堆栈平衡
:在函数调用前后esp和ebp的值应当相同堆栈的用途
SS:ESP
; 堆栈数据地址为SS:EBP
mov ebp,esp
使ebp存储函数帧低部。这是为了方便在栈中取数据,比如[ebp+4]就是函数返回地址,[ebp+8]就是参数y。当然我们也可以用esp来寻址,但esp是不断变化的,操作困难,编译器也不会这样做。mov esp,ebp
,把栈顶放回函数帧底部,从而撤销所有局部变量。(这就是局部变量生命周期的本质)pop ebp
,恢复ebp调用前的值,平衡堆栈ret
自动返回到调用位置,函数返回地址出栈add esp,8
,这样就ok了名称 | PUSH(进栈指令) |
---|---|
格式 | PUSH SRC |
动作 | 把源操作数SRC压入堆栈,并调整esp指向栈顶 |
合法值 | SRC:32/16位通用Reg或段Reg;双字/字存储单元;立即数 |
注意 | 双字入栈:ESP-4,然后把双字送到ESP所指的数据单元 |
字入栈:ESP-2,然后把字送到ESP所指的数据单元 | |
至少进栈一个字 |
PUSH EAX //把EAX的内容压入堆栈
PUSH DWORD PTR [ECX] //把ECX指示的双字存储单元的内容压入堆栈
PUSH BX //把BX的内容压入堆栈
PUSH WORD PTR [EDX] //把EDX指示的字存储单元的内容压入堆栈
名称 | POP(出栈指令) |
---|---|
格式 | POP DEST |
动作 | 从栈顶弹出一个双字/字到DEST,并调整esp指向栈顶 |
合法值 | DEST:32/16位通用Reg或段Reg;双字/字存储单元。但是不能是立即数或代码段寄存器CS |
注意 | 双字出栈:先从ESP所指存储单元弹出一个双字数据送DEST,然后ESP+=4 |
字出栈:先从ESP所指存储单元弹出一个字数据送DEST,然后ESP+=2 | |
至少出栈一个字 | |
DEST不能是立即数或代码段寄存器CS |
POP ESI //从堆栈弹出一个双字到ESI
POP DWORD PTR [EBX+4] //从堆栈弹出一个双字到EBX+4所指示存储单元
POP DI //从堆栈弹出一个字到DI
POP WORD PTR [EDX+8] //从堆栈弹出一个字到EDX+8所指示的存储单元
#include <stdio.h> int main( ) { int varsp1, varsp2, varsp3, varsp4, varsp5; //用于存放ESP值 int varr1, varr2; //用于存放EBX值 _asm { MOV EAX, 12345678H //初值 MOV varsp1, ESP //保存演示之初的ESP(假设为0013FA74H) PUSH EAX //把EAX压入堆栈 MOV varsp2, ESP //保存当前ESP(0013FA70H) PUSH AX //把AX压入堆栈 MOV varsp3, ESP //保存当前ESP(0013FA6EH) POP EBX //从堆栈弹出双字到EBX MOV varsp4, ESP //保存当前ESP(0013FA72H) MOV varr1, EBX POP BX //从堆栈弹出字到BX MOV varsp5, ESP //保存当前ESP(0013FA74H) MOV varr2, EBX } printf("ESP1=%08XH\n",varsp1); //显示为ESP1=0013FA74H printf("ESP2=%08XH\n",varsp2); //显示为ESP2=0013FA70H printf("ESP3=%08XH\n",varsp3); //显示为ESP3=0013FA6EH printf("ESP4=%08XH\n",varsp4); //显示为ESP4=0013FA72H printf("ESP5=%08XH\n",varsp5); //显示为ESP5=0013FA74H printf("EBX1=%08XH\n",varr1); //显示为EBX1=56785678H printf("EBX2=%08XH\n",varr2); //显示为EBX2=56781234H return 0; }
(1)16位通用Reg
名称 | PUSHA(16位通用寄存器全进栈指令) |
---|---|
格式 | PUSHA |
动作 | 将8个16位通用寄存器的内容压入堆栈,压入顺序:AX、CX、DX、BX、SP、BP、SI、DI |
名称 | POPA(16位通用寄存器全出栈指令) |
---|---|
格式 | POPA |
动作 | 从堆栈弹出内容,以PUSHA相反的顺序送通用寄存器 |
(2)32位通用Reg
名称 | PUSHAD(32位通用寄存器全进栈指令) |
---|---|
格式 | PUSHAD |
动作 | 将8个16位通用寄存器的内容压入堆栈,压入顺序:EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI |
名称 | POPAD(32位通用寄存器全出栈指令) |
---|---|
格式 | POPAD |
动作 | 从堆栈弹出内容,以PUSHA相反的顺序送通用寄存器 |
//演示PUSHAD指令的执行效果,还演示另一种访问堆栈区域存储单元的方法 #include <stdio.h> int buff[8]; //全局数组,存放从堆栈中取出的各寄存器之值 int main( ) { _asm { PUSH EBP //先保存EBP!! ; MOV EAX, 0 //给各通用寄存器赋一个特定的值 MOV EBX, 1 MOV ECX, 2 MOV EDX, 3 ; //决不能随意改变ESP!! MOV EBP, 5 MOV ESI, 6 MOV EDI, 7 ; PUSHAD //把8个通用寄存器之值全部推到堆栈 ; MOV EBP, ESP //使得EBP也指向堆栈顶 LEA EBX, buff //把数组buff首元素的有效地址送到EBX MOV ECX, 0 //设置计数器(下标)初值 NEXT: MOV EAX, [EBP+ECX*4] //依次从堆栈中取 MOV [EBX+ECX*4], EAX //依次保存到数组buff INC ECX //计数器加1 CMP ECX, 8 //是否满8 JNZ NEXT //没有满8个,继续处理下一个 ; POPAD //恢复8个通用寄存器 POP EBP } //依次显示数组buff各元素之值,从中观察PUAHAD指令压栈的效果 int i; for (i=0; i<8; i++) printf("buff[%d]=%u\n", i, buff[i]); return 0; }
从堆栈到数组图示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。