1. 假设:
- 假设有函数main, f1, f2和g。其中,mainf1和f2,并且f1调用g,f2和g不再调用任何函数。
- 栈空间从高地址往低地址增长(linux是这样的)。
- 假设编译时没有开启eliminate frame pointer优化。如果开启该优化的话,对于参数固定的函数,不需要使用EBP寄存器来存储当前frame pointer。
- 如下的栈布局只是一般的情况,对于一些特殊编译器优化,还会有其它的元素。
- 本文也不讨论关于栈帧对齐以及栈内数据对齐的问题。
2. 用户程序的栈空间
--------------- g函数栈结束
local variables of g
---------------
callee-save registers
---------------
EBP
---------------
return address
++++++++++++++++++++++++ g函数栈开始
++++++++++++++++++++++++ f1函数栈结束
parameters of g
---------------
local variables of f1
---------------
callee-save registers
---------------
EBP
---------------
return address
+++++++++++++++++++++++++ f1及f2函数栈开始
+++++++++++++++++++++++++ main函数栈结束:L3
parameters of f1 and f2
---------------
local variables of main
---------------
callee-save regsiters
---------------EBP
--------------- :L2
return address
+++++++++++++++++++++++++ main函数栈开始(大小依赖于实际的main函数) :L1
+++++++++++++++++++++++++ __libc_start_main函数结束
parameters of main, init, fini,exit, .etc
----------------
local variables of __libc_start_main
----------------
callee-save registers
----------------
EBP
---------------
return address
+++++++++++++++++++++++++ __libc_start_main函数栈开始(大小为0x70字节):L0
+++++++++++++++++++++++++ __start函数结束
parameters of __libc_start_main
+++++++++++++++++++++++++ _start函数栈开始 (大小为0x20字节)
(4)__libc_start_main函数调用main函数之前,栈顶指针已经调整到图中L1所示。__libc_start_main将main函数的参数值传递到最接近栈顶指针的位置(假设main函数有两个参数,那么分别是esp和esp+0x4位置)。然后call指令自动将返回地址入栈,栈顶指针指向L2。
然后,像__libc_start_main函数一样,main函数保存ebp,修改ebp为当前栈顶指针,调整esp到图中L3所示位置。这样main函数通过ebp+0x8,ebp+0x12等可以访问__libc_start_main传递给自己的参数。因为main函数调用f1和f2,所以传递给f1和f2的参数也都在最接近L3的位置存储(假设f1有2个参数,f2有3个参数,那么f1的2个参数分别在esp和esp+0x4, f2的3个参数在esp、esp+0x4和esp+0x8)。