目录
一、栈
1.栈的简介
栈主要是用于存储程序运行过程中的局部信息,大小不一,动态增长。栈的内存一般根据函数栈来进行划分(不用函数的程序很少见),不同的函数栈之间是互相隔离的,从而能实现函数的有效切换。函数栈上存储的信息一般包括:临时变量(包括栈保护哨carry)、函数栈的返回栈基址(bp)、函数的返回地址(ip)。
程序栈
2.函数栈的调用机制
程序运行时,为了实现函数之间的互相隔离,需要在进入新函数之前保存当前函数的状态,而这些状态信息全在栈上,为了实现状态的隔离,由此引出函数栈的概念,当前函数栈的边界就是栈顶指针(sp)和栈底指针(bp)所知的区域所指的区域。sp主要指的是esp(x86)和rsp(x64),bp主要指ebp(x86)和rbp(x64)
在函数调用(即进入子函数时)时,首先将参数入栈,然后压入返回地址和栈底指针寄存器bp(也有不压bp的情况),其中压入返回地址是通过call实现的。
在函数结束时,将sp重新指向bp的位置,并弹出bp(与前面是否压入bp保持一致)和返回地址ip,通常,弹出bp是通过leave或者pop bp(pop rbp或者pop ebp)来实现。
x86程序参数传递实例
x64程序参数传递实例
修改bp寄存器,然后执行ret,函数状态将恢复成进入子函数时的状态,实现了函数栈的切换。
在函数栈中,bp中存储上个函数栈的基址,而ip存储的是调用处的下一条指令位置,返回当前函数,会从栈上弹出这两个值,从而恢复上一个函数的信息。
3.函数参数传递
由于函数的传参规则受函数调用协议的影响,首先介绍函数调用的协议,_stdcall、_cdecl、_fastcall是三种函数调用,函数调用协议会影响函数参数的入栈方式、栈平衡的修复方式、编译器函数名的修饰规则。
(1)调用协议的常用场合
_stcall:windows API默认的函数调用协议
_cdecl:C/C++默认的函数调用协议
_fastcall:适用于对性能要求较高的场景
(2)函数参数的入栈方式
_stcall:函数参数从左向右入栈
_cdecl:函数参数从右向左入栈
_fastcall:从左开始将小于4字节的参数放入CPU的ecx和edx寄存器,其余参数从右往左入栈
(3)栈平衡修复方式
_stcall:函数调用结束后由被调用函数来平衡栈
_cdecl:函数调用结束后函数调用者来平衡栈
_fastcall:函数调用结束后由被调用函数来平衡栈
对于linux来说,通常采用_cdecl的调用方式
(4)对于x86程序
普通函数传参
参数基本压在栈上(有寄存器传参的情况)
syscall传参
eax对应系统调用号,ebx,ecx,edx,esi,edi,ebp分别对应前6个参数。多余的参数压在栈上。
(5)对于x64程序
普通函数传参
先使用rdi,rsi,rdx,rex,r8,r9寄存器作为函数参数的前六个参数,多余的参数会依次压在站上。
syscall传参
rax对应系统调用号,传参规则与普通函数传参一致。
(6)对于ARM程序
R0,R1,R2,R3,依次对应前四个参数,多余的参数会依次压在栈上。
普通函数传参如下: