其他
x86_64架构下的函数调用及栈帧原理
%rax :通常存储函数调用的返回结果,也被用在idiv (除法)和imul(乘法)命令中。 %rsp :堆栈指针寄存器,指向栈顶位置。pop操作通过增大rsp的值实现出栈,push操作通过减小rsp的值实现入栈。 %rbp :栈帧指针,标识当前栈帧的起始位置。 %rdi, %rsi, %rdx, %rcx,%r8, %r9 :六个寄存器,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9;当参数为7个以上时,前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。
; 参数>=7时
Func(a, b, c, d, e, f, g, h);
;参数的具体存放方式
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
h->8(%esp)
g->(%esp)
call Func
函数执行前后必须保持原始的寄存器有3个:是rbx、rbp、rsp。rx寄存器中,最后4个必须保持原值:r12、r13、r14、r15。保持原值的意义是为了让当前函数有可信任的寄存器,减小在函数调用过程中的保存&恢复操作。除了rbp、rsp用于特定用途外,其余5个寄存器可随意使用。 Caller Save 和 Callee Save寄存器 : 寄存器的值是由”调用者保存“ 还是由 ”被调用者保存“。当进行函数调用时,子函数通常也会使用通用寄存器,但这些寄存器中可能保存着父函数(调用者)的值。如果是Caller Save 寄存器,在进行子函数调用之前,需要由调用者提前保存寄存器中的值(入栈),然后在子函数中可以向这些寄存器中写入任何数据;在完成调用后,恢复寄存器原来的值(出栈)。如果是Callee Save寄存器,父函数在进行子函数调用前不会保存寄存器中的值,在调用子函数后,子函数会首先保存寄存器中的值(入栈);子函数完成功能后,恢复寄存器中的值,然后再返回到父函数,结束调用。
>>>> 1. 函数调用
1. 函数调用
调用者栈帧中,保存了被调用函数的参数以及调用者的返回地址,其流程大致如下: 父函数将调用参数从右到左依次压栈->返回地址入栈->跳转到子函数起始地址->子函数将父函数栈帧起始地址(%rbp)压栈->将%rbp 的值设置为当前 %rsp 的值,开辟栈帧空间 函数调用时的汇编指令如下:
... ;参数压栈
call Func ;将返回地址压栈,并跳转到子函数处执行
... ;函数调用的返回位置
Func: ;子函数入口
pushq %rbp ;保存父函数的帧指针,创建新栈帧
movq %rsp, %rbp ;让 %rbp 指向新栈帧的起始位置
subq $N, %rsp ;开辟栈帧空间供子程序使用
>>>> 2. 函数返回
2. 函数返回
movq %rbp, %rsp ; 使 %rsp 和 %rbp 指向同一位置,即子栈帧的起始处, 收回子栈帧空间
popq %rbp ; 将栈中保存的父栈帧的 %rbp 的值赋值给 %rbp,并且 %rsp 上移一个位置指向父栈帧的结尾处
int add(int a, int b, int c, int d, int e, int f, int g, int h) { // 8 个参数
int sum = a + b + c + d + e + f + g + h; //相加求和
return sum;
}
int main(void) {
int i = 10;
int j = 20;
int k = i + j;
int sum = add(11, 22,33, 44, 55, 66, 77, 88);
int m = k; // 为了观察 %rax Caller Save 寄存器的恢复
return 0;
}
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
main:
.LFB3:
pushq %rbp
.LCFI2:
movq %rsp, %rbp
.LCFI3:
subq $48, %rsp
.LCFI4:
movl $10, -20(%rbp)
movl $20, -16(%rbp)
movl -16(%rbp), %eax
addl -20(%rbp), %eax
movl %eax, -12(%rbp)
movl $88, 8(%rsp)
movl $77, (%rsp)
movl $66, %r9d
movl $55, %r8d
movl $44, %ecx
movl $33, %edx
movl $22, %esi
movl $11, %edi
call add
movl %eax, -8(%rbp)
movl -12(%rbp), %eax
movl %eax, -4(%rbp)
movl $0, %eax
leave
ret
.LFB3:
pushq %rbp
.LCFI2:
movq %rsp, %rbp
.LCFI3:
subq $48, %rsp
movl $10, -20(%rbp)
movl $20, -16(%rbp)
movl -16(%rbp), %eax
addl -20(%rbp), %eax
movl %eax, -12(%rbp)
movl $88, 8(%rsp)
movl $77, (%rsp)
movl $66, %r9d
movl $55, %r8d
movl $44, %ecx
movl $33, %edx
movl $22, %esi
movl $11, %edi
call add
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
movl %eax, -8(%rbp) ; 保存 add 函数返回值到栈中,对应 C 语句 int sum = add(...)
movl -12(%rbp), %eax ; 恢复寄存器 %eax 的值,与调用add前保存 %eax 相对应
movl %eax, -4(%rbp) ; 对应 C 语句 m = k,%eax 中的值就是 k。
movl $0, %eax ; main 函数返回值
leave ; main 函数返回
ret
看雪ID:有毒
https://bbs.pediy.com/user-779730.htm
推荐文章++++
* 实战栈溢出漏洞