《深入理解计算机系统》学习笔记 - 第七课 - 机器级别的程序三

Lecture 07 Machine Level Programming III Procedures 机器级别的程序三

文章目录

  • Lecture 07 Machine Level Programming III Procedures 机器级别的程序三
    • 概述
      • 程序机制
    • 栈结构
      • 栈说明
      • 栈定义
      • 推入数据
      • 弹出数据
    • 调用控制
      • 代码示例
        • 程序控制流程
          • %rip
      • 传递数据
        • ABI 标准
        • 示例
    • 管理局部数据
      • 基于堆栈的语言
      • 栈帧
      • 函数递归调用示例
      • linux 栈帧
        • 示例
    • 寄存器保存数据惯例
      • linux寄存器的使用
    • 递归说明
      • 对于递归的观察
    • 《深入理解计算机系统》书籍学习笔记

概述

本章所说的程序/过程(procedure),它即可以是函数过程,也可以是面向对象编程中的方法,这些大致相同的东西,我们统一使用术语程序(procedure)来称呼。

ABI: Application Binary Interface, 应用程序二进制接口。
它要求所有Linux程序,所有的编译器,所有的操作系统,系统的所有不同部分,都需要对如何管理机器上的资源有一些共同的理解,并共同遵守这套规则。
因此,它超越了硬件实际提供的软件标准。
它是机器程序级别的接口。

程序机制

  • 传递控制
    • 程序(函数)代码的开始
    • 返回点
  • 传递数据
    • 程序参数
    • 返回值
  • 内存管理
    • 程序运行时分配内存空间
    • 返回时解除分配
      在这里插入图片描述

传递控制:我们需要将控制权转移给一个函数并确保它能返回正确的位置。
当P调用Q时,程序必须以某种方式跳转到Q中,并开始执行Q的代码。然后当Q执行到它的退出点,程序需要以某种方式回到P。
不是返回到P的任何地方都可以,必须恰好在P调用Q后的位置。
因此为了返回正确的位置,我们需要记录返回位置的信息。

传递参数:我们如何传递参数?
Q是一个函数,它接受单个参数i并能在函数内部使用这个参数,在P调用它的地方,P给Q传递了一个称为x的值,所以x的值必须以某种形式记录下来,使的在Q内,程序有权访问其他信息。
类似的,当Q想要返回有一个值时,P也将用相同的方式利用该值。

函数中的局部数据:需要分配一些空间
那么在哪里分配这些空间?如何确保正确分配?如何确保空间被正确释放?

将程序分解成更小的函数,尽可能减少过程调用的开销。
在好的编程风格中,你写的函数往往专注于很小的功能。

设计者的原则之一是:只做绝对必要的事情
如果数据不需要本地存储空间则不要分配和释放空间了。
如果你没有必要传递任何值,那就不要传递它们。

栈结构

栈说明

前景问题:如何将控制传递给一个函数? 栈
栈并不是什么特殊的内存,栈并不是特殊的内存,它只不过是普通内存的一个区域。
对于汇编层面的程序员而言,内存只是一个巨大的字节数组。在那一堆自己中的某个地方,我们将其称为栈。

程序用栈来管理过程与返回的状态。
在栈中,程序传递潜在信息,控制信息和数据,并分配本地数据。栈可以用于管理这些信息的原因在于栈这种数据结构,符合过程调用和返回的整个想法的实质。
当你调用时,可能需要一些信息,但是当你从调用中返回时,所有这些信息可以被丢弃,因此它利用了栈的那种后进先出的原则,这与调用与返回的思想十分吻合。

栈定义

  • 用栈规则管理内存区域。
  • 向较低地址增长
  • 寄存器%rsp 包含最低栈地址。也就是最上面的元素(最先出栈的元素)
    在这里插入图片描述

每次在栈上分配更多空间时,都会通过递减该指针来实现。

推入数据

pushq Src

  • 从Src获取操作数
  • 寄存器%rsp 减8
  • 将数据写入寄存器%rsp 给的地址。

弹出数据

popq Dest

  • 从寄存器%rsp中读取数据
  • 寄存器%rsp地址 加8
  • 将值存到Dest(必须是寄存器)

拓展:
弹出数据之后,寄存器%rsp地址加8,增加栈指针来释放空间,并不意味着我神奇的抹去了什么,仅仅移动了栈指针而已,原来的栈顶元素热然保存在内存中,只是不再时栈中的一部分了。

调用控制

指令call与ret使用了栈push与pop相同的思想。

代码示例

  • c 语言代码
long mult2(long a, long b)
{
    long s = a * b;
    return s;
}

void multstore(long x,long y,long *dest)
{
    long t = mult2(x, y);
    *dest = t;
}
  • 汇编码
0000000000400540 <multstore>:
  400540: push   %rbx		# Save %rbx
  400541: mov    %rdx,%rbx		# Save dest
  400544: callq  400550 <mult2>	# mult2(x,y)
  400549: mov    %rax,(%rbx)	# Save at dest
  40054c: pop    %rbx		# Restore %rbx
  40054d: retq			# Return

0000000000400550 <mult2>:
  400550:  mov    %rdi,%rax	# a 
  400553:  imul   %rsi,%rax	# a * b
  400557:  retq			# Return
程序控制流程
  • 使用栈来支持程序的调用和返回。
  • 程序调用:调用标签
    • push推送数据,返回栈地址
    • 跳到函数调用标签
  • 返回地址
    • 调用函数之后,下一个指令的地址
  • 程序返回:ret
    • pop弹出地址,从栈中
    • 跳到地址

如图:
在这里插入图片描述

调用call:此时栈指针(%rsp)指向0x120, 指令指针(%rip)指向call指令。
在这里插入图片描述

运行mult2: 栈指针(%rsp)-8,指向0x118。指令指针(%rip)指向mult2中的程序。
在这里插入图片描述

mult2返回:指令指针(%rip)指向mult2中的ret。
在这里插入图片描述

调用函数之后,程序恢复执行:栈指针(%rsp)+8,指向0x120。指令指针(%rip)指向multstore中的程序。

拓展:
0x120 - 8 = 288 - 8 = 280 = 0x118

%rip

%rip: register instruction pointer, 指令指针寄存器。
它存储了当前正在执行的指令的内存地址。

当处理器执行程序时,它会按顺序读取内存中的指令并执行它们。%rip 寄存器指示了下一条要执行的指令的内存地址。当处理器执行完一条指令后,它会自动更新 %rip 寄存器,使其指向下一条要执行的指令的地址。

%rip 寄存器在程序执行期间是自动更新的,程序员无法直接修改它的值。

传递数据

前面六个参数存储在寄存器中:

  • %rdi
  • %rsi
  • %rdx
  • %rcx
  • %r8
  • %9
    当超过6个参数, 超过的参数存储到栈中:
...
Arg n
...
Arg 8
Arg 7

返回值:
%rax

ABI 标准

基本上代码能运行基于这样的假设:无论什么参数都按列出的顺序被传递给这一系列寄存器。
并且代码显然是依赖于这些假设。类似的,返回值的处理方式也是传递给指定的寄存器%rax。

示例

以上面的例子为例:

# x in %rdi,  y in %rsi, dest in %rdx
# t in %rax
void multstore(long x,long y,long *dest)
{
    long t = mult2(x, y);
    *dest = t;
}

管理局部数据

基于堆栈的语言

  • 支持递归
  • 代码课重入
    单个程序可以多个实例同时实例化
  • 需要一个地方来存储实例状态
    • 参数
    • 局部变量
    • 返回指针
  • 堆栈的原则
    • 特定的程序在有限时间内的状态
      从被调用到返回
    • 被调用方在调用方之前返回。
  • 按帧分配的堆栈
    单个程序实例化的状态。

调用和返回的功能之一是你可以对函数进行嵌套调用。
执行特定程序时,它只需要引用该函数内部的数据或已传递给它的值。
我们可以再这个栈上分配这个当前函数需要任意多的空间,我们不需要保留与该函数相关的任何信息,返回时,之前被调用的函数就可以永远消失了。这就是为什么我们要用栈的思想。
在栈上分配空间,它们返回的时候退出栈,并释放空间。这些栈的规则完全适用,它与过程调用和返回思想完美匹配。
因此,我们把栈上用于特定call(特定函数)的每个内存块称为栈帧。

栈帧

  • 内容
    • 返回信息
    • 局部存储
    • 临时存储
  • 管理
    • 当进入程序分配空间
      • 开始代码
      • call调用指令,推送数据(参数)
    • 返回时释放空间
      • 完成代码
      • ret指令,弹出数据(返回数据)

在这里插入图片描述

通常一个栈帧由两个指针分隔:栈指针, 基指针。
基指针式一个可选的指针,所以这个寄存器实际上并不会在你的过程中以帧指针的形式出现,所以通常情况西啊,你知道栈帧的唯一事情栈指针。因此,你甚至无法弄清楚栈帧的确切范围。你只知道栈的顶部式对应于最顶层函数的顶部栈帧。

每次开始调用一个函数时,栈上就会为它的栈帧分配一些空间。然后该栈帧的位置由一个指针或两个指针指示。

栈指针寄存器 %rsp : register stack pointer. 用于指向当前栈顶的位置。
栈是一种用于存储临时数据和函数调用信息的数据结构。通过修改 %rsp 的值,可以在栈上分配和释放内存空间。

基址指针寄存器 %rbp :用于指向当前函数的基址。基址指针通常用于访问函数的局部变量和参数。通过 %rbp,可以在栈帧中定位和访问这些变量和参数。

函数递归调用示例

在这里插入图片描述

每层调用都会由自己管理局部状态,这样栈的原则再次保证了它能正确的工作。

linux 栈帧

在这里插入图片描述

  • 当前栈帧(从顶部到底部)
    • 参数
    • 局部变量
    • 保存的寄存器上下文
    • 旧的栈帧指针(可选)
  • 调用者栈帧
    • 返回地址
      call指令推送数据。
    • 调用该函数的参数
示例
  • c 代码
long incr(long *p, long val)
{
    long x = *p;
    long y = x + val;
    *p = y;
    return x;
}

long call_incr()
{
    long v1 = 15213;
    long v2 = incr(&v1, 3000);
    return v1 + v2;
}
  • 汇编代码
incr:
	movq	(%rdi), %rax
	addq	%rax, %rsi
	movq	%rsi, (%rdi)
	ret
call_incr:
	subq	$16, %rsp
	movq	$15213, 8(%rsp)
	movl	$3000, %esi
	leaq	8(%rsp), %rdi
	call	incr
	addq	8(%rsp), %rax
	addq	$16, %rsp
	ret

movl $3000, %esi: 因为3000足够小,所以适用movl指令就行了。%esi寄存器,高32位会设置为0。编译器喜欢这么干的原因是movl比movq少一个字节。

寄存器保存数据惯例

  • 当程序yoo 调用 who
    • yoo 是调用者(caller)
    • who 是被调用者(callee)
  • 调用者保存
    调用者在调用前保存临时值到它自己的栈帧
  • 被调用者保存
    被调用者在使用前保存临时变量的值到它自己的栈帧。
    被调用者在返回给调用者时再次存储。

linux寄存器的使用

在这里插入图片描述

  • %rax
    返回值
    调用者保存
    可以被程序修改。

  • %rdi,…,%r9
    参数
    调用者保存
    可以被程序修改

  • %r1o,%r11
    调用者保存
    可以被程序修改。

这些寄存器也经常被作为临时寄存器。
在这里插入图片描述

  • %rbx,%r12,%r13,%r14
    被调用者保存
    被调用者必须保存和恢复

  • %rbp
    被调用者保存
    被调用者必须保存和恢复
    可能被用作栈帧指针
    可以混搭

  • %rsp
    特殊形式被调用者保存
    退出程序后恢复为原始值

递归说明

  • c 代码
long pcount_r(unsigned long x)
{
    if (x == 0)
        return 0;
    else
        return (x & 1) + pcount_r(x >> 1);
}
  • 汇编代码
pcount_r:
	testq	%rdi, %rdi
	jne	.L8
	movl	$0, %eax
	ret
.L8:
	pushq	%rbx
	movq	%rdi, %rbx
	andl	$1, %ebx
	shrq	%rdi
	call	pcount_r
	addq	%rbx, %rax
	popq	%rbx
	ret

每个程序都会以这种方式处理%rbx, 即修改之前将它先暂存在栈上。

对于递归的观察

  • 栈帧意味着每个方法调用有着自己的私有存储空间
    存储寄存器和局部变量
    存储返回指针
  • 寄存器保存数据惯例可以放置一个函数调用破坏其他数据。
    除非C代码显式地这样做(例如,第9讲中的缓冲区溢出)
  • 栈满足调用返回匹配成对
    如果P调用Q,则Q在P之前返回。
    后进先出。

《深入理解计算机系统》书籍学习笔记

《深入理解计算机系统》学习笔记 - 第一课 - 课程简介
《深入理解计算机系统》学习笔记 - 第二课 - 位,字节和整型
《深入理解计算机系统》学习笔记 - 第三课 - 位,字节和整型
《深入理解计算机系统》学习笔记 - 第四课 - 浮点数
《深入理解计算机系统》学习笔记 - 第五课 - 机器级别的程序
《深入理解计算机系统》学习笔记 - 第六课 - 机器级别的程序二
《深入理解计算机系统》学习笔记 - 第七课 - 机器级别的程序三

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/275968.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF Button使用漂亮 控件模板ControlTemplate 按钮使用控制模板实例及源代码 设计一个具有圆角边框、鼠标悬停时颜色变化的按钮模板

续前两篇模板文章 模板介绍1 模板介绍2 WPF中的Button控件默认样式简洁&#xff0c;但可以通过设置模板来实现更丰富的视觉效果和交互体验。按钮模板主要包括背景、边框、内容&#xff08;通常为文本或图像&#xff09;等元素。通过自定义模板&#xff0c;我们可以改…

JVM篇:JVM的简介

JVM简介 JVM全称为Java Virtual Machine&#xff0c;翻译过来就是java虚拟机&#xff0c;Java程序&#xff08;Java二进制字节码&#xff09;的运行环境 JVM的优点&#xff1a; Java最大的一个优点是&#xff0c;一次编写&#xff0c;到处运行。之所以能够实现这个功能就是依…

Docker自建私人云盘系统

Docker自建私人云盘系统。 有个人云盘需求的人&#xff0c;主要需求有这几类&#xff1a; 文件同步、分享需要。 照片、视频同步需要&#xff0c;尤其是全家人都是用的同步。 影视观看需要&#xff08;分为家庭内部、家庭外部&#xff09; 搭建个人网站/博客 云端OFFICE需…

TiDB在WMS物流系统的应用与实践 (转)

业务背景 北京科捷物流有限公司于2003年在北京正式成立,是ISO质量管理体系认证企业、国家AAAAA级物流企业、海关AEO高级认证企业,注册资金1亿元,是中国领先的大数据科技公司——神州控股的全资子公司。科捷物流融合B2B和B2C的客户需求,基于遍布全国的物流网络与自主知识产…

电话号码的字母组合[中等]

一、题目 给定一个仅包含数字2-9的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意1不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&am…

【MySQL学习笔记008】多表查询及案例实战

1、多表关系 概述&#xff1a;项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系&#xff0c;基本上可分为三种&a…

什么是 NLP (自然语言处理)

NLP&#xff08;自然语言处理&#xff09;到底是做什么&#xff1f; NLP 的全称是 Natural Language Processing&#xff0c;翻译成中文称作&#xff1a;自然语言处理。它是计算机和人工智能的一个重要领域。顾名思义&#xff0c;该领域研究如何处理自然语言。 自然语言就是我…

小狐狸ChatGPT付费创作系统小程序端开发工具提示打开显示无法打开页面解决办法

最新版2.6.7版下载&#xff1a;https://download.csdn.net/download/mo3408/88656497 很多会员在上传小程序前端时经常出现首页无法打开的情况&#xff0c;错误提示无法打开该页面&#xff0c;不支持打开&#xff0c;这种问题其实就是权限问题&#xff0c;页面是通过调用web-v…

实习知识整理13:在购物车界面点击提交订单进入订单信息界面

在这块主要就是对前端传到后端的数据的处理&#xff0c;然后由后端再返还到新的前端界面 首先点击下单按钮后&#xff0c; 提交购物车中所选中的信息 因为前端是将name定义为 cartList[0].cartId &#xff0c;cartList[1].cartId 形式的 所以后端需要重新定义一个类来进行封装…

C语言中宏定义的一种妙用

1.前言 最近分析了一个宏定义的妙用方法&#xff0c;利用宏定义来构建一个枚举类型&#xff0c;通过自己代码测试验证&#xff0c;方法可行&#xff0c;分享给大家。 2.源码 实验源码如下所示&#xff1a; head1.h DEF_TEST(name1) DEF_TEST(name2) DEF_TEST(name3) #unde…

Redis哨兵sentinel

是什么&#xff1f; 哨兵巡查监控后台master主机是否故障&#xff0c;如果故障根据投票数自动将某一个slave库变为master&#xff0c;就行对外服务&#xff0c;称为无人值守运维 能干嘛&#xff1f; 主从监控&#xff1a;监控主从redis库是否正常工作 消息通知&#xff1a;…

带大家做一个,易上手的家常红烧茄子

我们先准备茄子 我这里用的一个大茄子 建议大茄子两个 一个做出来 还是看着有点少 茄子切成滚刀块 茄子倒入 小半勺盐 然后用手抓拌均匀 腌制十分钟 准备一根半小葱 三瓣蒜 蒜切成 蒜末 葱切碎 调一个料汁 两勺生抽 半勺老抽 半勺白砂糖 半勺盐 倒一点蚝油 半勺淀粉 小半…

在用Vite开发时静态图片放哪里,才能保证显示,不出现找不到资源

在用Vite开发时静态图片放哪里 在用Vite开发时静态图片&#xff08;资源&#xff09;放哪里呢 &#xff1f; 如果你想直接全部显示的那么请你把静态资源放到public目录下面&#xff0c;这样你一打包所有的静态资源都会放到打包根目录下。但是此时你在项目中引用的地址一定要是…

github登录需要双因素认证(Two-factor authentication)

前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 github登录需要双因素认证&#xff08;Two-factor authentication&#xff09; 今天登录github发现需要绑定双因素才能够登录 我们…

【开源】基于Vue+SpringBoot的实验室耗材管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 耗材档案模块2.2 耗材入库模块2.3 耗材出库模块2.4 耗材申请模块2.5 耗材审核模块 三、系统展示四、核心代码4.1 查询耗材品类4.2 查询资产出库清单4.3 资产出库4.4 查询入库单4.5 资产入库 五、免责说明 一、摘要 1.1…

单文件超过4GB就无法拷贝到U盘?这个你一定要知道

前言 随着现在科技发展&#xff0c;小伙伴们所使用的数据也越变越大。还记得WindowsXP流行的时候&#xff0c;XP的镜像文件仅为几百MB大小。 但是现在随便一个系统就有可能超过4GB。 如果单个文件超过4GB就有可能没办法拷贝进U盘&#xff0c;在这里就需要给小伙伴们普及一下U…

不再悲观,投行发表2024年股市展望

KlipC报道&#xff1a;2023已经步入尾声&#xff0c;尽管地缘政治冲突下&#xff0c;高利率和高通胀仍扰动着各国经济前景&#xff0c;但对于2024年&#xff0c;投行们已不似展望2023年时那样悲观。 那么展望2024年&#xff0c;世界经济会是什么样的&#xff1f; 摩根大通在接受…

k8s-cni网络 10

Flannel vxlan模式跨主机通信原理 在同一个节点上的pod 流量通过cni网桥可以直接进行转发&#xff1b; 在需要跨主机访问时&#xff0c;数据包通过flannel(隧道) 知道另一边的mac地址&#xff0c;就可以拿到另一边的ip地址&#xff0c;然后构建常规的以太网数据包&#xff0c;…

Java的maven

一.概念&#xff1a; 是一款用于管理和构建java项目的工具 作用: 方便项目的依赖管理 统一项目的结构,方便程序员开发及维护 提供了一套标准的项目构建流程,方便编译和构建 二.仓库类型: 本地仓库>自己计算机上的一个目录 中央仓库>由Maven团队维护的全球唯一的。…

每日一题--------求数字的每⼀位之和

大家好今天的每日一题又来了&#xff0c;有啥不对的请在评论区留言哦 文章目录 目录 文章目录 求数字的每⼀位之和 题⽬描述&#xff1a; 输⼊⼀个整数m&#xff0c;求这个整数m的每⼀位之和&#xff0c;并打印。 一、解题思路 我们可以通过不断获取该整数的个位数&#xff0c…