文章来源:陈同学 | Procedure Call and Stack
最近查资料时,偶然在youtobe看到了华盛顿大学自然科学与工程一位老师 关于 Procedure & Stacks 的课程,深入讲解了基于Stack的过程调用,展示了应用级别和寄存器级别的处理过程,演示非常形象,受益良多。以下是课程重点及视频链接,可以自行翻墙观看。
1-Stacks2-Procedure Calls and Returns3-Stack-based languages4-Linux stack frame5-Registers and variables6-x86-64 Procedure Calling Convention文本作为学习笔记,仅先记录过程调用时Stack和寄存器的变化.
下图为Caller(调用方) 调用 Callee(被调用方)的示例.
Caller需要保存它在寄存器上的数据,因为Callee会覆盖;Caller需要设置参数,调用Callee,然后清理参数,将数据重新存储到寄存器,然后找到返回值。
Callee需要保存局部变量,存储返回值,将一些数据存储到寄存器,再返回到Caller
save regs 表示保存寄存器数据;args 表示参数;local vars 表示局部变量; return val 表示返回值
为了实现上述过程,需要解决以下问题。
Callee 需知道去哪儿找参数(机器没有传参之说,它只知道去哪儿读取数据,然后做何种计算)Callee 需知道去哪儿找 “return address”, 即Callee执行结束后如何返回到上图中Caller部分的call代码处,并继续执行Caller中的指令Caller 需要去哪儿找Callee返回的结果由于Caller 和 Callee 运行在同一个CPU上,它们共享寄存器,因此它们需要自行存储寄存器上的数据。Caller 和 Callee 之间需要一定的约定,例如:Callee约定将返回值存到某个寄存器,Caller去某个寄存器读取数据即可,这是一种通过约定共享信息的方式。这种约定成为 Procedure call linkage通过 Stack 来支持 procedure call 和 return.
先假设几个概念,方法main调用方法B,假设方法main的代码如下:
B(123); // call println("123"); // return address我们暂且称调用B()方法的指令为call指令,称call之后需要执行的指令(println("123"))的地址为 return address(返回地址)
那么调用时执行的指令可以用下图来表示:
call 8048b90: 表示调用方法B()的指令8048553: 表示返回地址,即执行call之后需要返回到Caller处继续执行(println("123"))的指令,需要把这条指令push到栈顶,这样B()执行完后可以返回回来,那么 return address 的值就是8048553当B() return时,将 return address 从stack 中pop出来,这样就拿到了下一条需要执行的指令。然后再读取return address上存储的指令并执行即可(这条指令做的事情就是println(result))说明:%eip、%esp、
