一、堆、栈、寄存器
win32下PE文件结构(Portable Execute),EXE,DLL,OCX,SYS文件都是用此结构:
主体结构部分常有段:
- 执行代码段: .text (Microsoft,已编译程序的机器代码)或 CODE(Borland)
- 数据段: .data(已初始化的全局变量和静态变量) 、.rdata 或 .bss(Microsoft,未初始化的全局变量和静态变量)、DATA(Borland)
- 资源段: .rsrc
- 导出表: .edata
- 导入表: .idata
- 调试信息段: .debug
进程的虚拟内存空间:
其中堆区和栈区程序运行时的内存分配区域。“共享内存映射区”用来给共享库(.so,.dll)分配地址的,它的地址增长方式同堆一样,从低到高。
调用函数时,主调函数所拥有的局部变量等信息需要存储在特定区域,称为栈内存区。而利用new和malloc进行分配的内存区域称为堆内存。而程序在启动时,栈内存就被同一分配,不可再扩大。由于栈内存的限制,函数的递归深度也有上限。
堆(HEAP):
地址从低到高。按照顺序分配。
存储程序员自行分配的内存。malloc分配的内存只能用free来释放,而new分配的地址只能用delete来释放,如果new分配的是数组,则需要delete[ ]来释放。
堆中内存由程序员分配和释放,若程序员不释放,程序结束时由系统收回。
栈(STACK):
地址从高到低。(这样堆栈指针分别从最高处和最低处向中间移动。如果是同一方向的话,二者的起始位置就不好定。)由于空闲内存不一定连续,离散分配。
函数中声明的任何局部变量(非静态)都是在栈中分配的。
栈中内存由程序自动的分配和释放。
X86寄存器:
X86有8个32位寄存器,以E开头。其中ESP是指向上图的栈指针,EBP是基址指针(栈底指针)。64位系统寄存器以R开头。
8086/8088内部的寄存器组分成8个通用寄存器,4个段寄存器,1个标志寄存器和1个指令指针寄存器(一共14个寄存器,分为专用与通用),它们均为16位。 分别如下:
通用寄存器有:
数据寄存器(AX,BX,CX,DX);
指针寄存器:
堆栈指针寄存器SP;
基址指针寄存器BP;
变址寄存器:
源变址(source index)寄存器SI;
目标变址(destination index)寄存器DI;
SI通常指向源数组, DI通常指向目的数组. 通常用于成块地移动数据, 比如移动数组或结构体. SI和DI通常和DS和ES一起使用; DS:SI和ES:DI配对时通常用来执行一些字符串操作。
专用寄存器有:
控制寄存器:
指令指针IP(指示当前运行程序的当前指针);
标志寄存器FLAGS;
段(segment)寄存器:
代码段(code segment)寄存器CS;
堆栈段(stack segment)寄存器SS;
数据段(data segment)寄存器DS;
附加段寄存器ES;
寻址方式:
程序获取数据的方式无非三种:直接赋值、从寄存器取值、从内存中取值。寻址方式总共7种,后四种无非是对前三种进行了一些变化。
1、立即寻址:第二操作数直接是数值而非地址。 mov eax,80H(80H直接数)
2、寄存器寻址: add vard EBX(寄存器)
3、直接寻址: mov AX, DS[12 34H](默认段超越前缀为DS,可指定其他。通过算法计算出地址)
4、寄存器间接寻址: mov BX,[DI]
5、寄存器相对寻址: mov BX,[SI+1000H]
6、基址+变址: mov BX,[BX+SI]
7、相对基址+变址: mov BX,[BX+100H+SI]
二、汇编指令简介
声明:
其中DWORD PTR,BYTE PTR,WORD PTR作为一种标记表明直接数的字节数
指令 | 解释 | 实例 |
---|---|---|
dd(DWORD PTR) | 双字节(4字节) | dd 100 DUP(?) 声明双字节未初始化数组[100] |
db(BYTE PTR) | 单字节 | mov WORD PTR [ebx], 2 |
dw(WORD PTR) | 双字节 |
指令:
指令 | 解释 | 实例 |
---|---|---|
move | 将第二个操作数复制到第一个操作数。不能用于直接从内存复制到内存 | mov byte ptr [var], 2 |
push | 将操作数压入内存的栈中 | push eax |
pop | 将ESP指示的地址中的内容出栈,然后将ESP值加4 | |
add/sub | 将两个操作数相加,且将相加后的结果保存到第一个操作数中/相减 | add eax, 1 |
inc/dec | 自加/自减 | inc DWORD PTR [var] |
imul | 整数相乘 | imul eax, [var] — eax→ eax * [var] imul esi, edi, 25 — ESI → EDI * 25 |
idiv | idiv只有一个操作数,此操作数为除数,而被除数则为EDX:EAX中的内容(一个64位的整数),操作的结果有两部分:商和余数,其中商放在eax寄存器中,而余数则放在edx寄存器中 | idiv ebx |
and/or/xor | 与/或/异或 | and eax, 0fH |
not | 将操作数中的每一位取反 | not BYTE PTR [var] |
neg | 取负 | neg eax — EAX → - EAX |
shl/shr | 位移,第一个操作数表示被操作数,第二个操作数指示位移的数量 | shl eax, 1 |
je/jne/jz/jg/jge/jl/jle | =/不等于/=0/>/>=/ | cmp eax, ebx jle done |
cmp | 比较两个操作数的值,并根据比较结果设置机器状态字中的条件码 | |
call | 子程序调用 | |
retn | 子程序返回 | |
lea | 将第二个操作数表示的地址载入到第一个操作数(寄存器)中 | lea eax, [var] — var指示的地址载入eax中 |
关于cmp:
比较二数后设置flag的CF,ZF,OF,AF,PF位。
执行指令后
ZF=1 这个简单,则说明两个数相等,因为zero为1说明结果为0
当无符号时:
若
CF=1 则说明了有进位或借位,cmp是进行的减操作,故可以看出为借位,所以,此时oprd1<oprd2
CF=0 则说明了无借位,但此时要注意ZF是否为0,若为0,则说明结果不为0,故此时oprd1>oprd2
当有符号时:
若
SF=0,OF=0 则说明了此时的值为正数,没有溢出,可以直观的看出,oprd1>oprd2
SF=1,OF=0 则说明了此时的值为负数,没有溢出,则为oprd1<oprd2
SF=0,OF=1 则说明了此时的值为正数,有溢出,可以看出oprd1<oprd2
SF=1,OF=1则说明了此时的值为负数,有溢出,可以看出oprd1>oprd2
三、函数调用
#include <stdio.h>
int func(int param1 ,int param2)
{
int var1 = param1;
int var2 = param2;
printf("var1=%d,var2=%d",var1,var2);
return var1;
}
int main(int argc, char* argv[])
{
int result = func(1,2);
return 0;
}
栈过程:
1、从右到左压入main函数参数
2、压入返回地址
3、将函数func参数从右到左压入栈
4、压入返回地址
5、压入EBP地址
6、EBP赋值为ESP
7、执行赋值,压入var1,var2
8、调用函数printf
9、将func返回值保存到eax寄存器
10、局部变量出栈
11、EBP恢复原值
12、返回地址出栈,返回原来执行地址
13、参数param1,param2依次出栈