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

news2025/1/24 17:55:33

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之前返回。
    后进先出。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YB75XXH系列是采用CMOS工艺制造,低功耗的高压稳压器

YB75xxH 高耐压线性稳压器 ■产品简介&#xff1a; YB75XXH系列是采用CMOS工艺制造&#xff0c;低功耗的高压稳压器&#xff0c;最高输入电压可达25V,输出电压范围为1.5V一12.0V。它具有高精度的输出电压、极低的供电电流、极低的跌落电压等特点。 ■产品特点&#xff1a; …

使用Guava轻松创建和管理不可变集合

第1章&#xff1a;引言 大家好&#xff0c;我是小黑。今天&#xff0c;我们来聊聊一个在Java编程里超有用的话题&#xff1a;使用Guava创建和管理不可变集合。首先&#xff0c;咱们得明白&#xff0c;什么是不可变集合。简单来说&#xff0c;不可变集合就是一旦创建就不能被修…

深入了解 Android 中的应用程序签名

深入了解 Android 中的应用程序签名 一、应用程序签名介绍1.1 应用程序签名1.2 应用程序签名的意义1.3 应用程序签名的流程1.4 应用程序签名的方案1.5 签名的重要性和应用场景 二、AOSP 的应用签名2.1 AOSP的应用签名文件路径2.2 应用程序指定签名文件 三、Android Studio 的应…

【SpringBoot篇】基于Redis实现生成全局唯一ID的方法

文章目录 &#x1f354;生成全局唯一ID&#x1f339;为什么要生成全局唯一id&#x1f33a;生成全局id的方法✨代码实现 &#x1f354;生成全局唯一ID 是一种在分布式系统下用来生成全局唯一id的工具 在项目中生成全局唯一ID有很多好处&#xff0c;其中包括&#xff1a; 数据…

在Next.js和React中搭建Cesium项目

在Next.js和React中搭建Cesium项目&#xff0c;需要确保Cesium能够与服务端渲染(SSR)兼容&#xff0c;因为Next.js默认是SSR的。Cesium是一个基于WebGL的地理信息可视化库&#xff0c;通常用于在网页中展示三维地球或地图。下面是一个基本的步骤&#xff0c;用于在Next.js项目中…

信号与线性系统翻转课堂笔记8——周期信号的频谱

信号与线性系统翻转课堂笔记8——周期性信号的频谱 The Flipped Classroom8 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff0c;重点&#…

【github】github设置项目为私有

点击setting change to private 无脑下一步

【Proteus/8086】swjtu西南交大微机与接口技术实验:计时器

实验内容: 计时器基本功能: 1)CPU必须用8086 2)计时器最小计时单位为秒。 3)以00:00格式显示计时,前2位表示分钟,后2位表示秒。 4)计时器是正计时方式 5)有暂停、继续计时功能 6&#xff09;有复位计时功能 7&#xff09;每次按下暂停键&#xff0c;能显示计时间隔时间 参考…

uniapp中使用封装步骤条组件

针对步骤条封装完终于清清楚楚啦 先看效果&#xff1a; 附上代码&#xff1a;使用可直接复用&#xff1a;数据是写在了当前组件中&#xff0c;如有必须&#xff0c;可以使用其中的props传值stepInfos传递相应的数据&#xff0c;根据steps步数就可以控制走到哪一步啦 <temp…

双向数据绑定是什么

一、什么是双向绑定 我们先从单向绑定切入单向绑定非常简单&#xff0c;就是把Model绑定到View&#xff0c;当我们用JavaScript代码更新Model时&#xff0c;View就会自动更新双向绑定就很容易联想到了&#xff0c;在单向绑定的基础上&#xff0c;用户更新了View&#xff0c;Mo…

在 Windows 中关闭指定端口的方法

方法一&#xff1a;使用命令行&#xff08;Command Prompt&#xff09; 查找端口占用情况 打开命令提示符&#xff08;Command Prompt&#xff09;并输入以下命令来查找占用指定端口的进程&#xff1a; netstat -aon|findstr "<port_number>" 这里的 <p…

【笔记】Spring的循环依赖

Spring的循环依赖 ObjectFactory:函数式接口&#xff0c;可以将lambda表达式作为参数放在方法的实参种&#xff0c;在方法执行的时候&#xff0c;并不会实际的调用当前lambda表达式&#xff0c;只有在调用getObject方法的时候才回去调用lambda表达式 为什么spring要用三级缓存…

一周工作问题总结(2023.12.18-2023.12.22)

一周工作问题总结 1. 接口调用频率2. 汉字在数据库中占用字节问题3. Map在循环中修改自己的key与value4. Group BY5.递归同步数据6.代码移动Idea飘红 1. 接口调用频率 供应商给的接口可以每秒调用5-10次&#xff0c;那么我为了保险每秒调用5次&#xff0c;为了防止过度调用接口…

SpringBoot+SSM项目实战 苍穹外卖(7)(Spring Cache)

继续上一节的内容&#xff0c;本节实现缓存菜品、缓存套餐、添加购物车、查看购物车和清空购物车功能。 目录 缓存菜品缓存套餐(基于Spring Cache)EnableCaching、Cacheable、CachePut和CacheEvictSpring Cache实现缓存套餐 添加购物车查看购物车清空购物车 缓存菜品 用户端小…

nodejs+vue+ElementUi医院预约挂号系统3e3g0

本医院预约挂号系统有管理员&#xff0c;医生和用户。该系统将采用B/S结构模式&#xff0c;使用Vue和ElementUI框架搭建前端页面&#xff0c;后端使用Nodejs来搭建服务器&#xff0c;并使用MySQL&#xff0c;通过axios完成前后端的交互 管理员功能有个人中心&#xff0c;用户管…

使用 Elasticsearch 检测抄袭 (一)

作者&#xff1a;Priscilla Parodi 抄袭可以是直接的&#xff0c;涉及复制部分或全部内容&#xff0c;也可以是释义的&#xff0c;即通过更改一些单词或短语来重新表述作者的作品。 灵感和释义之间是有区别的。 即使你得出类似的结论&#xff0c;也可以阅读内容&#xff0c;获得…

java练习题之多态练习

1&#xff1a;关于多态描述错误的是(D) A. 父类型的引用指向不同的子类对象 B. 用引用调用方法&#xff0c;只能调用引用中声明的方法 C. 如果子类覆盖了父类中方法&#xff0c;则调用子类覆盖后的方法 D. 子类对象类型会随着引用类型的改变而改变 2&#xff1a;class Supe…

【ONE·MySQL || 基础介绍】

总言 主要内容&#xff1a;MySQL在Centos 7下的安装&#xff08;主要学习相关指令语句&#xff0c;理解安装操作是在做什么&#xff09;、对MySQL数据库有一个基础认识。 文章目录 总言0、MySQL的安装与卸载&#xff08;Centos 7&#xff09;0.1、MySQL的卸载0.1.1、卸载不必要…

C练习题13答案

单项选择题(本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中,选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) 1.结构化程序由三种基本结构组成、三种基本结构组成的算法是(A) A.可以完成任何复杂的任务 B. 只能完成部分复杂的任务 C. 只能完…

没通知就降公积金的脉脉,面试考什么?

写在前面 刚过去的周末&#xff0c;又一家神奇公司映入眼帘&#xff1a; 怎么说呢&#xff1f;自家的瓜出现在自家&#xff0c;至少说明脉脉没有跟微博似的限流。 自家做职场社交&#xff0c;就用自家帖子做全员通知&#xff0c;脉脉你就这么“降本”的吗&#xff1f;&#x1f…