main.s
g:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
addl $3, %eax
popq %rbp
ret
f:
pushq %rbp
movq %rsp, %rbp
subq $8, %rsp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edi
call g
leave
ret
main:
pushq %rbp
movq %rsp, %rbp
movl $8, %edi
call f
addl $1, %eax
popq %rbp
ret
enter 指令和 leave 指令可以理解为宏指令。其中 leave 指令用来撤销函数堆栈。
等价于下面两条指令
movl %rbp, %rsp
popl %rbp
enter
指令就是再建立起一个空栈。
通常寄存器的名称为分为 16 位和 32 位置以及 64 位置,E 开头的就是 32 位寄存器,R 开头为 64 位寄存器,都是向下兼容的。
EIP 寄存器是指向代码段中的一条条指令,即 main.s 中的汇编指令,从 main:开始,它会自动加一,调用 call 指令时它会修改 EIP 寄存器。EBP 寄存器和 ESP 寄存器也特别重要,这两个寄存器总是指向一个堆栈,EBP 指向栈底,而 ESP 指向栈顶。
每个函数都有自己的函数堆栈和基地址。另外,EAX 寄存器用于暂存一些数值,函数的返回值默认使用 EAX 寄存器存储并返回给上一级调用函数。
首先假定堆栈为空栈的情况下 EBP 和 ESP 寄存器都指向栈底,X86 体系结构栈地址是向下生长的(地址减小)。
首先说几个概念,对于 call
指令,它在执行的时候的实际动作是先将 EIP 压栈,然后再把 call 指令所要调用的函数地址给 EIP,对于 EIP 而言,由于每次执行的时候自动加 1,也就是指向下一条指令,所以当执行 call 指令的时候,EIP 已经指向了 call 指令的下一条指令了,所以压栈的数据也即是 call 指令的下一条指令地址。
为什么要这样?因为在高级语言中,我们每次调用一个函数的时候,调用的函数执行完,都要回到调用之前的函数中,代码也就是接着该函数的下一调指令继续执行。这里也是一样的道理。
与 call 指令对应的就是 ret 指令了,ret 指令就是函数的返回,该指令的实际动作也就是将我们 call 指令执行时候压入的地址
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于