四川平昌县建设局网站,怎么把网站维护,百度app小程序,企业门户首页Lecture 07 Machine Level Programming III Procedures 机器级别的程序三 文章目录 Lecture 07 Machine Level Programming III Procedures 机器级别的程序三概述程序机制 栈结构栈说明栈定义推入数据弹出数据 调用控制代码示例程序控制流程%rip 传递数据ABI 标准示例 管理局部…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 %rbx400541: mov    %rdx,%rbx		# Save dest400544: callq  400550 mult2	# mult2(x,y)400549: mov    %rax,(%rbx)	# Save at dest40054c: pop    %rbx		# Restore %rbx40054d: retq			# Return0000000000400550 mult2:400550:  mov    %rdi,%rax	# a 400553:  imul   %rsi,%rax	# a * b400557:  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), %raxaddq	%rax, %rsimovq	%rsi, (%rdi)ret
call_incr:subq	$16, %rspmovq	$15213, 8(%rsp)movl	$3000, %esileaq	8(%rsp), %rdicall	incraddq	8(%rsp), %raxaddq	$16, %rspretmovl $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;elsereturn (x  1)  pcount_r(x  1);
}汇编代码 
pcount_r:testq	%rdi, %rdijne	.L8movl	$0, %eaxret
.L8:pushq	%rbxmovq	%rdi, %rbxandl	$1, %ebxshrq	%rdicall	pcount_raddq	%rbx, %raxpopq	%rbxret每个程序都会以这种方式处理%rbx, 即修改之前将它先暂存在栈上。 
对于递归的观察 
栈帧意味着每个方法调用有着自己的私有存储空间 存储寄存器和局部变量 存储返回指针寄存器保存数据惯例可以放置一个函数调用破坏其他数据。 除非C代码显式地这样做(例如第9讲中的缓冲区溢出)栈满足调用返回匹配成对 如果P调用Q则Q在P之前返回。 后进先出。 
《深入理解计算机系统》书籍学习笔记 
《深入理解计算机系统》学习笔记 - 第一课 - 课程简介 《深入理解计算机系统》学习笔记 - 第二课 - 位字节和整型 《深入理解计算机系统》学习笔记 - 第三课 - 位字节和整型 《深入理解计算机系统》学习笔记 - 第四课 - 浮点数 《深入理解计算机系统》学习笔记 - 第五课 - 机器级别的程序 《深入理解计算机系统》学习笔记 - 第六课 - 机器级别的程序二 《深入理解计算机系统》学习笔记 - 第七课 - 机器级别的程序三