C语言函数与栈、寄存器的关系

最近想学PWN,虽然这些是之前学过的,但是在做题的时候发现细节的重要性,就决定回顾一遍,这里写的主要也就是给自己记录一下,加深映像,同时加了一些自己的理解和补充

主要参考的文献:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/stack_intro/

32位

上来肯定事先了解一下寄存器啦,如下图

这里就先说一下ESP和EBP好了,可以理解为前者是栈顶指针,后者是栈底指针,那么根据两个的偏移量就可以指定栈当中的内容了,栈刚开始理解为开辟的一个存放空间就好,但是该控件可以在用完后销毁,经常用来放函数中的局部变量,如下图所示

那么说到了局部变量,就正好把函数的实参形参与栈的分布结构,如下

实参N~1→主调函数返回地址→主调函数帧基指针EBP→被调函数局部变量1~N

也就是说C语言中函数的调用,实参是从右往左压入栈,而局部变量是由创建的顺序一次压入栈的

有一点要注意静态局部变量是在编译时就准备好的,和全局变量是一样的,为什么会展示出局部性,是因为给了他调用的次数(有时候是时间),它自身并不是在栈中新创建的,是提前就有的

EBP指针在当前函数运行过程中(未调用其他函数时)保持不变。在函数调用前,ESP指针指向栈顶地址,也是栈底地址。在函数完成现场保护之类的初始化工作后,ESP会始终指向当前函数栈帧的栈顶,此时,若当前函数又调用另一个函数,则会将此时的EBP视为旧EBP压栈,而与新调用函数有关的内容会从当前ESP所指向位置开始压栈。

然后是寄存器ebx、esi和edi为被调函数保存寄存器(callee-saved registers),即被调函数在覆盖这些寄存器的值时,必须先将寄存器原值压入栈中保存起来,并在函数返回前从栈中恢复其原值,因为主调函数可能也在使用这些寄存器,这里说了一堆反正就是调用函数时要小心就是了

我觉得极其关键的一个寄存器是eip,里面存放的是下一条要执行的命令的代码的地址(改变了它,就可以修改程序执行的流程了)

堆栈操作:

这里我主要看重C函数调用以及调用结束后的栈操作

具体解释:

调用函数是,先保存环境以便日后函数调用结束后,恢复环境,所以将当前比如主函数中的栈底指针压入栈,为什么不用保存栈顶指针ESP呢,显然程序执行的栈帧永远是最顶上的,那么栈顶指针永远在顶部,所以不用太在意啦;

起初ESP与EBP是相等的,但是函数难免要调用一些局部变量以及一些函数如printf等等,那么肯定要开辟一个空间,所以就把栈顶指针移位形成一个新栈帧;

销毁函数其实就是个你过程,将栈帧清除,返回原地址的过程

但其实C函数调用的压栈顺序根据不同方式是有差别的,具体如下表,一般性主要就是用前3个

最重要的区别就是栈回收时的操作,采取stdcall的函数栈的销毁工作是自己处理的;

而使用cdecl的函数栈的销毁是交给主函数来运作的,他的特性就是传参个数不确定,比如printf、scanf

具体如下图,请关注sub操作是在什么时候执行的

64位

列举一些重要的区别吧,我觉得最重要的一点就是对函数传参的顺序

64位函数的参数传递顺序和32位不同,传参顺序:rdi,rsi,rdx,rcx,r8,r9,栈

猜你喜欢

转载自blog.csdn.net/qq_42192672/article/details/82842725