对于8个参数,可以看出最后两个参数从后到前被压入堆栈,并且前6个参数都保存到相应的参数寄存器中,这与在开始时的描述一致本文.
输入添加后的操作如下:
add:
.LFB2:
pushq %rbp # 保存父栈帧指针
.LCFI0:
movq %rsp, %rbp # 创建新栈帧
.LCFI1:
movl %edi, -20(%rbp) # 在寄存器中的参数压栈
movl %esi, -24(%rbp)
movl %edx, -28(%rbp)
movl %ecx, -32(%rbp)
movl %r8d, -36(%rbp)
movl %r9d, -40(%rbp)
movl -24(%rbp), %eax
addl -20(%rbp), %eax
addl -28(%rbp), %eax
addl -32(%rbp), %eax
addl -36(%rbp), %eax
addl -40(%rbp), %eax
addl 16(%rbp), %eax
addl 24(%rbp), %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
leave
ret
add的前两个指令实现了新堆栈框架的创建. 然后,寄存器中的函数调用参数被压入堆栈. 如本文前面所述,由于参数的内存地址可以在子例程中使用,因此无法在寄存器中访问这些参数. 在这里,参数被压入堆栈,这恰好证实了我们先前的猜想.

将参数压入堆栈时,我们发现未使用诸如push之类的指令,并且未调整%esp指针的值. 而是使用-N(%rbp)之类的指令来使用新的堆栈空间. 这种使用“基地址+偏移量”来使用堆栈的方式实际上与直接使用%esp指向堆栈的顶部相同.
有两个与编译器的具体实现有关的问题: 首先,在上述程序中,未使用-8(%rbp)和-12(%rbp)地址,并且这两个地址之前的地址-使用4(%rbp)和更高的-16(%rsp),这可能是由于编译器的特定实现. 另一个是以下两个指令:
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
首先将%eax的值分配给-4(%rbp),然后将该值反转一次,以为编译器可能会出于一般性考虑而这样做. 以上两个问题有待进一步研究.
当add函数返回时,返回的结果将存储在%eax中,%rbp和%rsp将被调整为指向main的堆栈帧,然后将执行main函数中的以下指令:
movl %eax, -8(%rbp) # 保存 add 函数返回值到栈中,对应 C 语句 int sum = add(...)
movl -12(%rbp), %eax # 恢复 call save 寄存器 %eax 的值,与调用add前保存 %eax 相对应
movl %eax, -4(%rbp) # 对应 C 语句 m = k,%eax 中的值就是 k。
movl $0, %eax # main 函数返回值
leave # main 函数返回
ret
可以看出,当add函数返回时,返回值保存在%eax中. 使用返回值后,将恢复呼叫者保存寄存器%eax的值. 此时,主堆栈框架与调用add之前的框架完全相同.
应注意,在调用add之前,在main中执行诸如subq 48,%rsp之类的指令. 原因是在调用add之后,main中没有调用其他函数,而是执行了两个赋值语句. 直接从main返回之后. 两条指令在main的结尾处离开和ret直接覆盖%rsp的值并返回main的父堆栈帧. 如果先调整主堆栈帧的%rsp值,然后离开然后覆盖%rsp值,则意味着该调整是多余的. 因此,更合理的做法是,在main中添加收益后,省略对rsp的调整,并使用休假直接覆盖rsp.
本文介绍了从汇编级别调用X86-64体系结构中的函数时堆栈帧切换的原理. 理解这些底层细节对于理解程序的运行非常有帮助. 并且在许多当前程序中,为了实现程序的有效运行,使用了汇编语言. 在了解了函数堆栈框架切换的原理之后,对于理解这些程序集也很有帮助.
在下一篇文章中,我们将详细介绍libco库中以汇编语言实现的协程的上下文切换. 本文可以作为理解协程上下文切换的基础.
结局.
我是我,一只企鹅疾驰.
我是我,不同的烟花.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-164560-2.html
我们的小王子
不过这样的军事表演秀是不会起到任何作用的
吨位也大了
来就来呗
人若犯我