转:https://www.jianshu.com/p/0299f56edab5
1. 栈区(stack)
高地址向低地址生长的一块连续的内存区域,所以栈顶地址和栈的最大容量都是系统预先规定好的;编译器自动管理;
方式类似数据结构中的栈,后入先出(LIFO);
每个进程在用户态对应一个调用栈结构;
存放函数参数和返回值,函数局部变量(不包括 static 声明的变量,它们存放在静态变量区);
高效快速,但大小限制,数据不灵活(支持数据类型有限,一般是整型,指针,浮点型等系统直接支持的数据类型);
2. 栈帧(stack frame)
函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息,每个未完成运行的函数占用一个独立连续区域(包含这个函数涉及的参数,局部变量,返回地址等相关信息),称为栈帧。当调用函数时,就要压入一个新的栈帧,发起调用函数的栈帧成为调用者栈帧,被调用函数的栈帧则称为当前栈帧(
rsp
和 rbp
之间的内存空间);被调用的函数运行结束后回收栈帧,回到调用者栈帧。这一过程都是自动的,由系统分配与销毁,无需手动调度。3. 寄存器 (register)
x86-64中,所有寄存器都是64位,相对32位的x86来说,标识符发生了变化,比如:从原来的%ebp
变成了%rbp
。为了向后兼容性,%ebp
依然可以使用,不过指向了%rbp
的低32位。
rip
指令地址寄存器,用来存储 CPU 即将要执行的指令地址。每次 CPU 执行完相应的汇编指令之后,rip
寄存器的值就会自行累加;rip
无法直接赋值,call, ret, jmp
等指令可以修改 rip
。rbp
栈基地址寄存器,保存当前帧的栈底地址。rsp
栈指针寄存器,保存当前栈顶。栈帧中,最重要的是帧指针
rbp
和栈指针 rsp
,有了这两个指针,我们就可以刻画一个完整的栈帧。3.1 寄存器保存惯例
调用者栈帧需要寄存器暂存数据,被调用者栈帧也需要寄存器暂存数据。如果调用者使用了
rbx
,那被调用者就需要在使用之前把 rbx
保存起来,然后在返回调用者栈帧之前,恢复 rbx
。遵循该使用规则的寄存器就是被调用者保存寄存器,对于调用者来说, rbx
就是非易失的。调用者使用
r10
存储局部变量,为了能在子函数调用后还能使用 r10
,调用者把 r10
先保存起来,然后在子函数返回之后,再恢复 r10
。遵循该使用规则的寄存器就是调用者保存寄存器,对于调用者来说, r10
就是易失的。4. 函数调用栈
4.1 参数入栈
参数从右向左依次入栈(支持可变参数)。x86-64 中,有 6 个寄存器来存储参数,多于 6 个参数,依然还是通过入栈实现。
4.2 返回地址入栈
实际代码中我们是看不到push rip
这句的;它是包含在
call
指令之中的 call function = push rip + jmp function
x86 Instruction Set Reference
4.3 代码区跳转
它是包含在call
指令之中的 call function = push rip + jmp function
4.4 栈帧调整
- 将调用帧的
push %rbp
入栈。 - 切换栈帧到当前栈帧
movq %rsp, %rbp
。 - 抬高栈顶,分配临时数据区
subq &xx, %rsp
。
[popexizhi: 此文下发值得深入阅读xcode的code分析,next mark]
没有评论:
发表评论