一些简单的汇编指令
如下一个程序
void test()
{
std::cout << "test" << std::endl;
}
int main()
{
test();
system("pause");
}
程序反汇编
在反汇编情况下
test()
00A4E8A8 call 00A3A87A
system("pause");
00A4E8AD push 0B5286C
call ret push pop
程序进行至调用该函数时,该函数汇编指令为 call 00A3A87A
即call+函数地址
进入00A3A87A中,有一个 jmp 00A4E250
jmp 跳转 下一步跳转到00A4E250地址去,进入一个内存空间,空间中有很多指令
在最后有一个ret指令,当执行完ret指令后,返回到最开始位置,刚进入反汇编的空间中
所以,在执行call指令时分两步
call 123156 函数地址
第一步 push 压栈 stack ,压栈下一行地址,存到栈里
第二步 jmp 跳转到123156,进到函数头的位置
在尾部有ret,在ret时通过pop将存储在栈中下一行地址弹出到eip寄存器(指令指针寄存器)中
在jmp到eip
栈中有两个寄存器一个esp一个eep
esp 栈顶指针寄存器,随参数压入压出浮动的,压入压出是通过push指令和pop指令执行
push指令把任意东西压入栈中
堆和栈
栈中的单位大小是由操作系统决定
16位 2字节
32位 4字节
64位 8字节
栈相对大小是小的,一般放函数参数,返回地址,临时变量
堆起始由每一个进程开始时创建出来的,叫做默认堆,有默认大小
堆的空间是由new malloc在默认堆开辟出来的,并随此不断增大空间
堆一般存储大块的内存
栈 堆都是属于虚拟内存中的
eep 栈底指针寄存器,在当前函数范围内是不动的
创建一个堆内存
char* szBuffer = new char[256] szBuffer相当于该内存首地址
如果这个代码是在一个函数内部或者它是一个参数,则在栈里容纳他的地址szBuffer,指向它内存所在的堆内存,所以栈中的每一个空间都可以指向一个堆内存
虚拟内存
虚拟内存独属于进程自己的
其大小在x86下时4GB内存,叫做可寻址空间,需要32根地址线进行寻址,2^32 = 4GB。但它只能用2GB,剩下给内核
在x64下,有256TB内存,需要64根地址线进行寻址
x64CPU 只提供48根地址线可用
Windows只有44位地址线,有8TB内存
函数调用约定
函数调用约定是对函数调用的一个约束和规定(规范),描述了函数参数是怎么传递和由谁清除堆栈的。有以下三种约定
1、参数传递的方式
参数的传递方式,最常见的是通过栈传递。函数的调用方将参数压入栈中,函数自己再从栈中将参数取出。
对于有多个参数的函数,调用惯例要规定函数调用方将参数压栈的顺序,是从左往右还是从右往左。有些调用惯例还允许使用寄存器传递参数
2、函数调用结束后的栈指针由谁恢复
栈的维护方式:在函数将参数压栈之后,函数体 会被调用,此后需要将被压入的参数全部弹出,以使得栈在函数调用前后保持一致。 这个弹出工作可以由函数的调用方来完成,也可以由函数本身完成.
3、函数编译后的名称
名称修饰策略,为了链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰。不同的调用惯例有不同的名字修饰策略
在x86下
C/C++基本调用约定有如下几种
1._cdecl C++默认函数调用约定
如以下一个代码
void _edecl test(int nNumberA, int nNumberB)
_cdecl是一个关键字,参数从右向左入栈,主调函数(main)负责栈平衡。
2._stdcall win32
需要包含window.h 头文件
_stdcall被调函数负责栈平衡
3._fastcall
当有多个参数时,前两个参数放到寄存器中,edx和ecx,剩下的放栈
4._thiscall 类函数传参使用
class Myclass
{
public:
int a;
int b;
x64下
_fastcall
栈每次调用函数返回时,进去时和回来时是一样的,叫做堆栈平衡
寄存器
寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成。
按照功能的不同,可将寄存器分为基本寄存器和移位寄存器两大类。基本寄存器只能并行送入数据,也只能并行输出。移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移,数据既可以并行输入、并行输出,也可以串行输入、串行输出,还可以并行输入、串行输出,或串行输入、并行输出,十分灵活,用途也很广。