【汇编语言与计算机系统结构笔记09】程序栈,(x86-32)过程调用,栈帧,寄存器使用惯例
本次笔记内容:
10.栈与过程调用的机器表示-1
11.栈与过程调用的机器表示-2
12.实验
文章目录
- 前言
- x86-32的程序栈
- 压栈操作
- 出栈操作
- 过程调用
- 基于栈的编程语言
- 栈帧
- x86-32/Linux下的栈帧
- 以swap过程为例
- 寄存器使用惯例
- 为什么设置“使用惯例”
- x86-32/Linux下的使用惯例
- 递归调用例子
- 带指针的“阶乘”过程
- x86-32过程调用小结
- x86-64通用寄存器与过程调用
- x86-64寄存器
- 例:x86-64下的swap过程 - 1
- 例:x86-64下的swap过程 - 2
- 例:x86-64下的swap过程 - 3
- 例:x86-64下的swap过程 - 4
- x86-64的栈帧使用实例
- 实验作业
前言
首先复习了上节课内容。
消除部分数据相关是有必要的,为了提高效率,可以使用 Partial Register Stall 等技术。
条件跳转指令可能对流水线效率造成伤害。
x86-32的程序栈
- 符合“栈(stack)”工作原理的一块内存区域,从高地址向低地址“增长”。
- %esp存储栈顶位置(尽量使esp指向当前栈的栈顶)。
栈底 |
---|
↑ Increasing Addresses |
↓ Stack Grows Down |
栈顶指针 %esp 栈顶 |
压栈操作
pushl Src
- 从Src取得操作数
- %esp = %esp - 4
- 写入栈顶地址
栈底 |
---|
↑ Increasing Addresses |
↓ Stack Grows Down |
%esp 本来指向这里,压栈后-4,指向下面 |
栈顶指针 %esp 栈顶 |
出栈操作
popl Dest
- 读取栈顶数据(%esp)
- %esp = %esp + 4
- 写入Dest
栈底 |
---|
↑ Increasing Addresses |
↓ Stack Grows Down |
栈顶指针 %esp 栈顶 |
%esp 本来指向这里,出栈后+4,指向上面 |
过程调用
- 利用栈支持过程调用与返回
过程调用指令:call label,将返回地址压入栈,跳转至label。
返回地址:call指令的下一条地址。汇编实例如下。
804854e: e8 3d 06 00 00 call 8048b90 <main>
8048553: 50 pushl %eax
Return address = 0x8048553
过程返回指令:ret,跳转至栈顶的返回地址。
我理解,其作用为,执行 call 后面的函数,执行结束后,在回到本线程来。call即,我在执行前,先把当前线程执行到哪里了,做个标记,压栈。
基于栈的编程语言
支持递归:
- e.g. C, Pascal, Java
- 代码时可重入的(Reentrant),同时有同一个过程的多个实例在运行;
- 因此需要有一块区域来存储每个过程实例的数据,包括参数、局部变量、返回地址。
栈的工作规律:
- 每个过程实例的运行时间是有限的,即栈的有效时间有限:From when called to when return;
- 被调用者先于调用者返回(一般情况下,如果遇到异常处理情况,则不是这个样子)。
每个过程实例在栈中维护一个栈帧(stack frame)。
栈帧
栈帧(stack frame)存储内容:
- 局部变量;
- 返回地址;
- 临时空间
栈帧的分配与释放:
- 进入过程后先“分配”栈帧空间,“Set-up” code;
- 过程返回时“释放”,“Finish” code。
- 寄存器%esp指向当前栈帧的起始地址。
过程调用时栈的变化:
x86-32/Linux下的栈帧
当前栈真的内容(自“顶”向下)
- 子过程参数:“Argument build”;
- 局部变量,因为通用寄存器个数有限;
- 被保存的寄存器值;
- 父过程的栈帧起始地址(old %ebp)
父过程的栈帧中与当前过程相关的内容:
- 返回地址,由call指令存入
- 当前过程的输入参数;
- etc.
Caller Frame | … |
---|---|
Caller Frame | Arguments |
Caller Frame | Return Addr |
栈帧指针(%esp) | Old %ebp |
Saved Registers + Local Variables | |
栈顶指针(%esp) | Argument Build |
以swap过程为例
如上图,当前%ebp还是父过程%ebp,因此Setup现将其存储,留着以后恢复。
之后将当前(新的)%ebp指向旧的%ebp,即设好之后工作的基址。
之后push %ebx,因为尽管父过程可能用%ebx,为了安全,要保存一下。
当然,也不是所有的实例的寄存器都要存。以后讲。
抽象的堆栈和实际的栈的对应关系如上图。
Finish在调用结束后,将父过程恢复。
寄存器使用惯例
为什么设置“使用惯例”
过程yoo调用who:
- yoo:caller
- who:callee
做一个软件层面的约定:哪些寄存器由调用者保存,哪些由被调用者保存。
如何使用寄存器作为程序的临时存储?
yoo:...movl $15213, %edxcall whoaddl %edx, %eax...retwho:...movl 8(%ebp), %edxaddl $91125, %edx...ret
如上例,%edx可能被yoo和who同时重复保存恢复,因此作出约定:
使用惯例:
- 调用者负责保存:caller在调用子过程之前将这些寄存器内容存储在它的栈帧内;
- 被调用者负责保存:callee在使用这些寄存器之前将其原有内容存储在它的栈帧内。
x86-32/Linux下的使用惯例
8个Registers:
- 两个特殊寄存器%ebp,%esp
- 三个由调用者负责保存:%ebx,%esi,%edi
- 三个由被调用者负责保存:%eax,%edx,%ecx
- %eax用于保存过程返回值
递归调用例子
int rfact(int x) {int rval;if (x <= 1)return 1;rval = rfact(x - 1);return reval * x;
}
寄存器使用情况:
- %eax直接使用;
- %ebx使用前保存旧值,退出前恢复。
.globl rfact.type
rfact, @function
rfact:pushl %ebpmovl %esp, %ebppushl %ebx # Set upmovl 8(%ebp), %ebxcmpl $1, %ebxjle .L78leal -1(%ebx), %eaxpushl %eaxcall rfactimull %ebxjmp .L79.align 4
.L78:movl $1, %eax
.L79:movl -4(%ebp), %ebxmovl %ebp, %esppopl %ebpret
带指针的“阶乘”过程
// Recursive Procedure
void s_helper(int x, int *accum) {if (x <= 1)return;else {int z = *accum * x;*accum = z;s_helper(x - 1, accum);}
}
// Top-Level Call
int sfact(int x) {int val = 1;s_helper(x, &val);return val;
}
首先,创建指针,如下图。
如上图,可以认识到,在编程中不能把临时变量的地址return。
之所以将%esp增加16 bytes,是因为很多机器(x86-32)中要求栈16 bytes对齐。
接下来,传递指针。
因此,如上图,在使用指针时,就如上图:
- %ecx存储变量x;
- %edx存储变量accum。
x86-32过程调用小结
程序栈:
- 各个过程运行实例的私有空间:不同实例间避免相互干扰,过程本地变量与参数存于栈内(采用相对于栈基址%ebp的寻址)
- 符合栈的基本工作规律:过程返回顺序与过程调用的顺序相反
相关指令与寄存器使用惯例:
- Call / Ret指令
- 寄存器使用惯例:调用者/被调用者保存,%ebp/%esp两个特殊奇存器
- 栈帧的存储内容
x86-64通用寄存器与过程调用
寄存器 | 惯例 | 寄存器 | 惯例 |
---|---|---|---|
%rax | Return Value | %r8 | Argument #5 |
%rbx | Callee Saved | %r9 | Argument #6 |
%rcx | Argument #4 | %r10 | Callee Saved |
%rdx | Argument #3 | %r11 | Used for linking |
%rsi | Argument #2 | %r12 | C: Callee Saved |
%rdi | Argument #1 | %r13 | Callee Saved |
%rsp | Stack Pointer | %r14 | Callee Saved |
%rbp | Callee Saved | %r15 | Callee Saved |
x86-64寄存器
过程参数(不超过6个)通过寄存器传递:
- 大于6个的仍使用栈传递;
- 这些传递参数的寄存器可以看成是“调用者保存”寄存器。
所有对于栈帧内容的访问都是基于%esp完成的:
- %ebp完全用作通用寄存器。
例:x86-64下的swap过程 - 1
void swap(long *xp, long *yp) {long t0 = *xp;long t1 = *yp;*xp = t1;*yp = t0;
}
swap:movq (%rdi), %rdxmovq (%rsi), %raxmovq %rax, (%rdi)movq %rdx, (%rsi)ret
参数由寄存器传递:
- First (xp) in %rdi, second (yp) in %rsi
- 64位指针
无需任何栈操作:
- 局部变量也存储于寄存器中。
例:x86-64下的swap过程 - 2
/* Swap, using local array */
void swap_a(long *xp, long *yp) {volatile long loc[2];loc[0] = *xp;loc[1] = *yp;*xp = loc[1];*yp = loc[0];
}
其中,使用 volatile关键字 强制使用栈空间,但在实际使用中没有修改栈顶寄存器(%rsp)。
swap_a:movq (%rdi), %raxmovq %rax, -24(%rsp)movq (%rsi), %raxmovq %rax, -16(%rsp)movq -16(%rsp), %raxmovq %rax, (%rdi)movq -24(%rsp), %raxmovq %rax, (%rsi)ret
例:x86-64下的swap过程 - 3
long scount = 0;
/* Swap a[i] & a[i+1] */
void swap_ele_se(long a[], int i) {swap(&a[i], &a[i+1]);scount++;
}
swap_ele_se:movslq %esi, %rsi # Sign extend ileaq (%rdi, %rsi, 8), %rdi # &a[i]leaq 8(%rdi), %rsi # &a[i+1]call swap # swap()incq scount(%rip) # scount++;ret
incq scont(%rip) 是把变量加1。
在x86下引入新寻址方式:
- 相对于当前指令(%rip)的寻址;
- 因为程序可能有动态链接库dll,而在dll中我们无法确定绝对位置,但是知道相对位置。
为什么swap_ele_se没有分配栈帧?
因为(除返回值外)没有私有数据来保留,用不着。
例:x86-64下的swap过程 - 4
long scount = 0;
/* Swap a[i] & a[i+1] */
void swap_ele(long a[], int i) {swap(&a[i], &a[i+1]);
}
swap_ele:movslq %esi, %rsi # Sign extend ileaq (%rdi, %rsi, 8), %rdi # &a[i]leaq 8(%rdi), %rsi # &a[i+1]jmp swap # swap
使用jmp指令调用过程,可以是因为对栈没有什么变化。
x86-64的栈帧使用实例
long sum = 0;
/* Swap a[i] & a[i+1] */
void swap_ele_su(long a[], int i) {swap(&a[i], &a[i+1];sum += a[i];
}
swap_ele_su:movq %rbx, -16(%rsp)movslq %esi, %rbxmovq %r12, -8(%rsp)movq %rdi, %r12leaq (%rdi, %rbx, 8), %rdisubq $16, %rspleaq 8(%rdi), %rsicall swapmovq (%r12, %rbx, 8), %raxaddq %rax, sum(%rip)movq (%rsp), %rbxmovq 8(%rsp), %r12addq $16, %rspret
- 变量a与i的值存于“被调用者保存”的寄存器中;
- 因此必须分配栈帧来保存这些寄存器。
实验作业
两个,BombLab与BufLab。
【汇编语言与计算机系统结构笔记09】程序栈,(x86-32)过程调用,栈帧,寄存器使用惯例相关推荐
- 【汇编语言与计算机系统结构笔记13】简单的上机过程示例
本次笔记内容: 16.上机过程-1(第16分钟开始) 17.上级过程-2 注:我找到了对应内容的课件,请见我于GitHub的CS笔记仓库.因此,为了节省时间,我只记录老师上课强调的内容与对应ppt页码 ...
- 【汇编语言与计算机系统结构笔记01】x86/MIPS/ARM指令集概述与特性,一篇HPCA引发的思考(商业生态的决定性作用)
资源Bilibili AV46914471 + AV57921488 汇编语言与计算机系统结构 清华大学 张悠慧 本次笔记内容: 01.汇编语言与计算机系统结构 02.汇编基础知识--指令集综述 文章 ...
- 【汇编语言与计算机系统结构笔记17】MIPS 汇编初步
本次笔记内容: 25.MIPS汇编初步-1 26.MIPS汇编初步-2 27.MIPS指令集与汇编程序设计 注:我找到了对应内容的课件,请见我于GitHub的CS笔记仓库.因此,为了节省时间,我只记录 ...
- 【汇编语言与计算机系统结构笔记11】程序格式与伪操作:段定义、堆栈 #简洁笔记形式
本次笔记内容: 14.程序格式与伪操作-1 注:本节课更换为一名女老师.我找到了对应内容的课件,请见我于GitHub的CS笔记仓库.因此,为了节省时间,我只记录老师上课强调的内容与对应ppt页码. 注 ...
- 【汇编语言与计算机系统结构笔记20】补充内容:可定制处理器指令集
本次笔记内容: 31.补充内容--可定制处理器指令集-1 32.补充内容--可定制处理器指令集-2 注:我找到了对应内容的课件,请见我于GitHub的CS笔记仓库. 本节课对应幻灯片:汇编语言程序设计 ...
- 【汇编语言与计算机系统结构笔记06】地址计算指令,lea / leal,x86-32与x86-64下的swap对比,汇编的格式对比(Intel/Microsoft Differs from GAS)
本次笔记内容: 07.寻址模式与数据传输指令等-2 文章目录 变址寻址 寻址模式实例 总结mov指令 地址计算指令 lea 整数计算指令 将leal指令用于计算 实例1 实例2 x86-32与x86- ...
- 【汇编语言与计算机系统结构笔记18】MIPS指令集与汇编程序设计 异常处理
本次笔记内容: 28.MIPS指令集与汇编程序设计-2 补充:MIPS32异常处理 注:我找到了对应内容的课件,请见我于GitHub的CS笔记仓库. 本节课对应幻灯片:汇编语言程式设计-MIPS.pd ...
- 【汇编语言与计算机系统结构笔记12】序格式与伪操作:简化段的定义、操作符等
本次笔记内容: 15.程序格式与伪操作-2 16.上机过程-1(前15分钟) 注:我找到了对应内容的课件,请见我于GitHub的CS笔记仓库.因此,为了节省时间,我只记录老师上课强调的内容与对应ppt ...
- 【汇编语言与计算机系统结构笔记05】汇编的系统结构,从C代码生产汇编代码,一个具体的、经典的数据传送指令(mov)实例与分析
本次笔记内容: 06.寻址模式与数据传输指令等 文章目录 汇编程序员眼中的系统结构 如何从C代码生产汇编代码 如何装gcc? 汇编语言数据格式 第一条汇编指令实例 数据传送指令(mov) 语法与操作数 ...
最新文章
- 自动调度GPU的卷积层
- Asp.net Mvc 多级控制器 路由重写 及 多级Views目录 的寻找视图的规则 (多级路由) 如:Admin/Test/Index...
- linux 内核参数 杨,Linux 内核参数
- 计算机驱动空间不够,Win8.1系统如何释放驱动器空间解决可用空间不足问题
- 前端学习(2794):实现拨打电话功能
- 工作62:显示省略号
- x+=y与x=x+y有什么区别?
- c语言标准库内存分配监控,C语言的本质(25)——C标准库之内存管理
- 【大数据部落】R语言电商网站爬虫
- 南京大学信号与系统851考研上岸经验分享
- 女生被渣,或许自己才是最大的黑手
- python手机app开发_H5 手机 App 开发入门:技术篇
- 案例分析:回归-克里金方法生成气温表面图(1)
- 你所不知的角落,有人在做没有深度学习的AI
- VC实现复制粘贴字符串
- 「需求广场」需求词更新明细(十六)
- Unity的声音(音频)管理器
- 傅里叶变换及低通滤波再反变换(C++opencv)
- 在matlab中配置vlfeat
- python 手眼标定OpenCV手眼标定(calibrateHandeye())二
热门文章
- java listen_java web-- listen
- 【Hive】Hive的三种交互方式
- 【Linux】修改Linux操作系统字符集与Oracle数据库一致
- 【Oracle】数据库热备
- 在IIS上部署.net core的webapi项目 以及502.5错误的两种解决方法
- Spring MVC中@ControllerAdvice注解实现全局异常拦截
- Python错误和异常小结
- python中引入包的时候报错AttributeError: module ‘sys‘ has no attribute ‘setdefaultencoding‘解决方法?
- jQuery-$(document).ready和$(window).load有什么区别?
- JavaScript警告框中的新行