函数栈2:gcc及llvm中x86机器的栈分配
来源:互联网 发布:java如何做app 编辑:程序博客网 时间:2024/05/17 03:59
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函数栈结束:L3parameters of f1 and f2--------------- local variables of main---------------callee-save regsiters---------------EBP--------------- :L2return 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字节)
3. 解释
(1)首先在shell中的命令行及参数,传递给内核代码。内核代码进行一些处理以后(包括设置用户栈的起始地址——从2^32+2^31 (3G)地址开始的第一个页(假设为4K大小)开始,但是页内地址随机),将控制权,以及命令、参数传递给用户代码。用户代码从_start函数开始,这可以从对应可执行文件的代码段的起始地址看出来。
(2)_start函数拷贝内核代码传给的命令行参数,将EBP清零,将栈顶指针按照16字节对齐,然后将得到的命令行参数,以及_init, _fini,和main函数的地址传递给__libc_start_main函数。传递参数给__libc_start_main的过程,通过将参数在当前栈顶上一一入栈实现。
(3)_start函数调用__libc_start_main函数之前,栈顶指针指向两个函数的栈帧的分界线(上图的L0所示)。call指令自动将返回地址存储在当前栈顶。__libc_start_main函数首先通过“push ebp; mov ebp esp",存储上个栈帧的指针,同时将ebp寄存器更新为当前esp。也就是说,栈帧指针(即当前ebp的值),比上图中的当前函数栈起点少两个地址值所占的空间大小(返回地址以及EBP地址,在32位机器上一共是8个字节)。
然后,__libc_start_main函数继续通过push指令,保存相关寄存器(callee-save registers)的值。
接着,__libc_start_main函数通过“sub esp, offe”指令,将栈顶指针调整到__libc_start_main函数栈的结束地址(图中L1所示)。这样,可以通过esp加上一个正数偏移量访问__libc_start_main函数的局部变量,以及__libc_start_main函数调用的其它函数(init, fini, main, exit)的形式参数。
(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)。
- 函数栈2:gcc及llvm中x86机器的栈分配
- LLVM及Clang、llvm-gcc
- LLVM及Clang、llvm-gcc
- LLVM/GCC中如何使用Intel格式的汇编
- LLVM/GCC中如何使用Intel格式的汇编
- GCC中x86架构下simd intrinsic函数的实现的分析
- X86和X86-64的函数栈帧结构
- Apple LLVM compiler 4.2 与LLVM GCC的区别
- X86函数内存栈
- [CSAPP-I] 过程(函数栈帧) C语句的机器级表示(gcc -S)
- MFC中常用的内存分配及释放函数
- DLL函数中内存分配及释放的问题
- 编译器二:LLVM和GCC的区别
- 升级GCC 6.2编译LLVM的问题
- x86-64 下函数调用及栈帧原理
- 关于函数中栈内存的分配问题追踪
- 程序运行在X86和X64机器上由字节分配不一样引发的问题
- centos/ubutu x86-64 编译gcc-4.6.2 的经历
- Android 面试题1
- 职业规划一家谈---何伟平
- Android 中的进程
- Java语言中的修饰符列表
- 硬件设备的监控处理之三——(USB设备的禁用和启用)
- 函数栈2:gcc及llvm中x86机器的栈分配
- Flyweight Design
- android 时间(TimePicker)日期(DatePicker)选择
- webfocus在移动终端上的应用DEMO
- Fedora / Redhat 软件包管理指南
- PKIX path building failed: unable to find valid certification path to requested target
- 硬件设备的监控处理之四——SD卡的禁用和启用
- Linux系统下临时文件TMP清理
- 遍历二叉树