继上篇报告了虚拟存储器和历程今后,措施终于乖乖“开始”运行了。此篇就来聊聊措施如何“一连”运行,继承来看看what’s beyond the scene。
栈
作为一名有政治觉悟的、僵持政治思想正确的、胸口飘扬鲜红的工卡的措施员,我们要服膺这句话:
没有党就没有新中国,没有栈就没有进程挪用。
是的,栈就是进程挪用的基本。为担保顺利的进程挪用,栈主要做了以下四点:
生存函数参数这是必需的,不多说了,虽然有大概是寄存器生存,详细细节下面会聊到。生存返回地点,生存call指令的下一条指令的地点,ret指令会返回这一地点。生存寄存器这一步,主要是为了防备被挪用者包围挪用者的寄存器。而帧指针和栈指针之间的空间就是本次栈帧的空间,调解这两个指针以会见空间。
常见的栈布局如下:
—— —— —— —— —— —— —— ------ high |..... | | |—— —— —— —— —— —— —— | | |参数n | | |—— —— —— —— —— —— —— | | |..... | |------>挪用者栈 |—— —— —— —— —— —— —— | | |参数1 | | |—— —— —— —— —— —— —— | | |返回地点 | | ebp--->|—— —— —— —— —— —— —— |------- |被生存的ebp | | |—— —— —— —— —— —— —— | | |被生存的寄存器和 | | |局部变量 | |------->被挪用者栈帧 |—— —— —— —— —— —— —— | | |其他数据 | | esp--->|—— —— —— —— —— —— —— |------- low
进程挪用可以分为三个部门,初始化栈帧、主体、销毁栈帧。初始化栈帧和销毁栈帧部门涉及到的就是栈主要做的四点。主体部门涉及到的就是利用栈帧的部门。
常见的进程挪用汇编如下:
call xxx 生存返回地点,并跳转到进程起始地点 push ebp 生存帧指针 mov ebp esp 调解帧指针 push 被生存的寄存器 生存寄存器 sub esp xxx 调解栈指针,分派帧栈。 ...... 主体 mov eax xxx 返回值放入eax pop 被生存的寄存器 取出被生存的寄存器 mov esp ebp 调解栈指针 pop ebp 取出帧指针 ret 跳回到返回地点
可以看到,进程挪用的初始化和销毁部门是完全相反的。而且,栈在进程挪用前后现场必需是一致的。
Calling-Convention(挪用老例)
就像平时写代码时会用到的协议模式,两边之间需要划定一些法则才气协力将一件事做好。进程挪用也一样,需要挪用者和被挪用者遵守一组“协议”,以顺利地配合完成进程挪用。Calling Convention就可以看做是这样的一组协议。Calling Convention主要包罗以下三个方面:
先聊第一点,函数参数的通报顺序和方法。方法指的是参数是放在寄存器上照旧栈上照旧殽杂?ia32划定参数全部放在栈上,而x86-64则划定先放寄存器上,假如高出6个再放到栈上。这是因为ia32有8个通用寄存器,而x86-64有16个通用寄存器。函数的通报顺序指的是,从左至右存储照旧从右至左存储。较量常见的Calling-Convention好比C语言默认的cdecl划定就是从右至左存储。
对付第二点,栈规复现场的方法。要做到栈在进程挪用前后现场一致这一点,就要将参数都从栈上pop掉。实际上,只要调解esp就可以。这件事可以由挪用者做,也可以由被挪用者做,大部门Calling Convention划定由挪用者做。Calling Convention就要划定这一点。除了参数,被生存的寄存器也有本身的Calling Convention,被生存的寄存器可以被分为挪用者生存可能被挪用者生存,栈规复现场的时候,被生存的寄存器也要按Calling Convention从栈上pop掉。这第二点是必需要做的,Calling Convention就是划定由挪用者做照旧被挪用者做。
第三点,函数返回值的通报方法。险些所有的Calling Convention都划定小于4字节的返回值,放到eax中。小于8字节、大于4字节的返回值,放到eax和edx中。若是更大的返回值,怎么办?总不能都放在寄存器中吧。那就不直接通报值,通报地点。挪用者先在栈上分派足够大的空间,然后将空间的地点以隐式参数(implicit argument )的方法通报给被挪用者,被挪用者将返回值先复制到作为隐式参数的地点中,再将地点放到eax中。挪用者将eax中地点的内容再复制到返回值中,这样通过一其中介空间和其地点就可以处理惩罚更大的返回值。