call_stack
IA-32
(Intel 32位) 寄存器
Note: 32 位说明栈中指针偏移量以 32位(4字节)为一个基元。EAX
~EDX
: 通用寄存器 (general purpose registers),- 通用寄存器是通用的,但是有一部分指令对寄存器有特殊要求,这时它们分别有如下用途:
EAX
: 累加器 (“A” means Accumulator)EBX
: 数据基地址暂存器 (“B” means Base)ECX
: 循环计数器 (“C” means Counter)EDX
: 操作数或操作结果暂存器 (“D” means Data)
- 为了方便存储较短的数据,通用寄存器都可以只使用低16位
- 通用寄存器是通用的,但是有一部分指令对寄存器有特殊要求,这时它们分别有如下用途:
ESI
和EDI
: 这两个寄存器优势也被包括在通用寄存器内,但是不能拆分,一般用作字符串操作的源指针和目的指针。ESP
和EBP
:ESP
存储的是栈顶地址,即栈指针 (Stack Pointer),而EBP
存储的是位于栈顶的栈帧的栈底地址,即基指针 (Base Pointer)
控制权移交
- 控制权是指
CPU
执行指令的顺序 - 当一个函数调用另一个函数时,
CPU
需要暂停当前函数的执行,转而去执行被调用函数。这个过程称为控制权移交。
- 控制权是指
栈帧(Stack Frame)
- 栈帧是堆栈的逻辑片段,当调用函数时逻辑栈帧被压入堆栈,当函数返回时逻辑栈帧被从堆栈中弹出。
- 栈帧的边界:
EBP
(Extended Base Pointer 扩展基指针) 指向当前栈帧底部(高地址),在当前栈帧内位置固定;ESP
(Extended Stack Pointer 扩展栈指针) 指向当前栈帧顶部(低地址),当程序执行时ESP
随着数据的入栈和出栈而移动。
函数调用栈 (Call Stack)
- 函数调用栈的典型内存布局图:
这是 caller function 和 callee function 的栈帧布局,m(%ebp)
表示以EBP
为基地址、偏移量为m
字节的内存空间(中的内容)。 - 函数序 (Prologue):被调函数设置 EBP,并保存其希望不变的寄存器值;被调函数为局部变量分配栈帧空间,并存储。
- 函数跋 (Epilogue):释放为局部变量分配的栈空间;恢复函数序中希望保持不变的寄存器值,包括主调函数的 EBP;被调函数将控制权交还主调函数(使用
ret
指令),这一步可能也会将先前的参数从栈上清除。
- 函数调用栈的典型内存布局图:
堆栈操作
push
: ESP 减小 4 个字节;将寄存器数据压入堆栈,从高到底按字节讲数据存入 ESP-1、ESP-2、ESP-3 指向的地址单元pop
:从 ESP 指向的空间取回数据存入寄存器,ESP 增加 4 个字节。call
:将当前的指令指针(EIP)压入栈,以备返回时恢复执行下一条指令;然后将 EIP 指向被调函数的代码开始处,跳转到被调函数的入口处执行。leave
:恢复主调函数的栈帧。等价于:1
2movl %ebp, %esp
popl %ebpret
:在执行leave
之后,从栈顶弹出返回地址(由call
指令压入栈中的)到 EIP,转到返回地址继续执行
函数调用约定:
函数参数的传递顺序和方式:是通过栈传递还是通过寄存器与栈的组合传递
栈的维护方式:被调函数返回时压栈的参数是由被调函数清除还是由主调函数清除
名称修饰 (Name-mangling) 策略:编译器在链接时为区分不同函数,对函数名作不同修饰
x86
函数参数传递方法:
x86 处理器 ABI 规范规定,所有传递给被调函数的参数都通过堆栈完成,压栈顺序为以函数参数从右到左的顺序。这种方式使得被调函数通过m(%ebp)
读取参数时反而是随m
增大而顺序读取,支持不定数量参数的函数。- 整型和指针参数的传递相同,因为都是
32bit
,直接从右向左压栈。 - 浮点参数的传递:浮点参数的传递于整型的类似,只是浮点数据占用
64bit
。 - 结构体和联合体参数的传递:与整型的类似,但是注意数据对齐,占用字节数为 4 的倍数。
- 整型和指针参数的传递相同,因为都是
x86
函数返回值传递方法:- 若返回值不超过4字节,通常将其保存在
EAX
中,调用方通过EAX
读取返回值。 - 若返回值大于4字节而小于8字节,则通过
EAX+EDX
联合返回,EAX
保存低字节,EDX
保存高字节。 - 若返回值为浮点类型,通过专用的协处理器浮点数寄存器的栈顶返回。
- 若返回值为结构体或联合体,传递返回值的方法根据编译器的不同,平台的不同,调用约定的不同,甚至编译参数的不同而不同,一种常见的方式是:
- 在将被调函数的参数依次压栈之后再向栈中压入一个隐藏参数,它是用于保存返回结构体或联合体的一块栈空间的地址。
- 若未定义用于接收返回值的变量,在栈上额外开辟一块空间作为接收返回值的临时变量
- 被调函数将返回值拷贝到隐藏参数指向的内存空间,再将该地址存入
EAX
。
- 若返回值不超过4字节,通常将其保存在
x86_64
的寄存器传参:
前六个参数分别使用rdi, rsi, rdx, rcx, r8, r9
传递,多余的参数均存储在栈上。x86_64
的函数返回值传递:
使用rax
传递第一个返回值。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 HaoIne!