1.概述

32位寄存器的堆栈和64位的寄存器的函数堆栈,还是有较大的差别的,这里准备花两篇文章好好学习温习一下,防止在不做技术的路上越忘越远,因此尽量写的详细简单,本文主要是64位寄存器的堆栈图,调试系统为ubuntu 16.04(64位)
具体堆栈图的画法,我是偶尔在网易云课堂学习的一节课(堆栈图),使用excel表格画图,也挺形象的

2.源代码及其汇编

源代码

#include<stdio.h>
int sum(int a,int b) {
    return a+b;
}
int main(){
    int i = 1;
    int j = 2;
    int k = 0;
    k = sum(i,j);
    printf("%d",k);
    return 0;
}

汇编代码

    0x000000000040053a <+0>:     push   rbp
    0x000000000040053b <+1>:     mov    rbp,rsp
    0x000000000040053e <+4>:     sub    rsp,0x10
    0x0000000000400542 <+8>:     mov    DWORD PTR [rbp-0xc],0x1
    0x0000000000400549 <+15>:    mov    DWORD PTR [rbp-0x8],0x2
    0x0000000000400550 <+22>:    mov    DWORD PTR [rbp-0x4],0x0
    0x0000000000400557 <+29>:    mov    edx,DWORD PTR [rbp-0x8]
    0x000000000040055a <+32>:    mov    eax,DWORD PTR [rbp-0xc]
    0x000000000040055d <+35>:    mov    esi,edx
    0x000000000040055f <+37>:    mov    edi,eax
    0x0000000000400561 <+39>:    call   0x400526 <sum>// 将sum函数拆解
        0x0000000000400526 <+0>:     push   rbp
        0x0000000000400527 <+1>:     mov    rbp,rsp
        0x000000000040052a <+4>:     mov    DWORD PTR [rbp-0x4],edi
        0x000000000040052d <+7>:     mov    DWORD PTR [rbp-0x8],esi
        0x0000000000400530 <+10>:    mov    edx,DWORD PTR [rbp-0x4]
        0x0000000000400533 <+13>:    mov    eax,DWORD PTR [rbp-0x8]
        0x0000000000400536 <+16>:    add    eax,edx
        0x0000000000400538 <+18>:    pop    rbp
        0x0000000000400539 <+19>:    ret
    0x0000000000400566 <+44>:    mov    DWORD PTR [rbp-0x4],eax
    0x0000000000400569 <+47>:    mov    eax,DWORD PTR [rbp-0x4]
    0x000000000040056c <+50>:    mov    esi,eax
    0x000000000040056e <+52>:    mov    edi,0x400614
    0x0000000000400573 <+57>:    mov    eax,0x0
    0x0000000000400578 <+62>:    call   0x400400 <[email protected]>
    0x000000000040057d <+67>:    mov    eax,0x0
    0x0000000000400582 <+72>:    leave
    0x0000000000400583 <+73>:    ret

3.堆栈图(64位)

3.1开始执行main函数

gdb调试结果【逆向学习记录】x86-64栈帧及跳转

堆栈图

其中:0x400590 (<__libc_csu_init>: push r15)这个值执行main函数之前的rp

【逆向学习记录】x86-64栈帧及跳转

3.2函数跳转之前的栈分布

参数传递之前,将参数保存在寄存器:RDI/RCI

gdb调试结果

【逆向学习记录】x86-64栈帧及跳转

堆栈图

【逆向学习记录】x86-64栈帧及跳转

3.3执行call指令之后

gdb调试结果

【逆向学习记录】x86-64栈帧及跳转

堆栈图

通过这个堆栈图可以看到,执行call指令的时候,将RIP压栈(main的下一条指令),同时保存RBP
【逆向学习记录】x86-64栈帧及跳转

3.4执行SUM函数的过程

gdb调试结果

【逆向学习记录】x86-64栈帧及跳转

堆栈图

【逆向学习记录】x86-64栈帧及跳转
通过查看堆栈情况,这里有个比较奇怪的地方,上面两个内存没有申请,竟然在使用:
【逆向学习记录】x86-64栈帧及跳转

3.5 执行完成子函数,跳转回main函数

gdb调试结果

【逆向学习记录】x86-64栈帧及跳转

堆栈图

【逆向学习记录】x86-64栈帧及跳转

4总结

1 注意点:

push操作会导致ESP-1(8个字节)
pop操作会导致ESP+1(8个字节)
64位下的指针是8个字节,但是int是4个字节,因此赋值采用的是edx,eax
有很多文章说说ebp不再作为栈帧指针,是存在一定的偏差的,实际linux-64中依然是使用rbp作为栈帧指针,待进一步研究调查

2 call指令:

push rip
函数跳转之后会导致
push rbp
mov rbp,rsp

3 Ret指令:

pop rip

4 函数传参和32位不同:

32位是通过堆栈传参
64位是通过寄存器+堆栈传参,顺序依次是:rdi、rsi、rdx、rcx、r8、r9(edi、esi、edx、ecx、r8、r9)

5奇怪的点:

跳转到新的函数之后,并没有分配新的内存,但是程序却临时借用了就近的几个字节

5参考

64位和32位的寄存器和汇编的比较
上面这篇文章实际操作过程中,会有不相符的情况,比如rbp栈帧指针
x86-64传参规则
x64函数调用过程分析

相关文章:

  • 2021-12-01
  • 2021-09-27
  • 2021-08-21
  • 2021-05-31
  • 2021-06-02
  • 2021-05-12
  • 2021-10-11
  • 2021-12-19
猜你喜欢
  • 2021-08-31
  • 2021-11-07
  • 2022-12-23
  • 2021-07-25
  • 2022-02-10
  • 2021-11-23
  • 2022-12-23
相关资源
相似解决方案