• 【反汇编玩耍1】通过反汇编真正理解函数参数传递过程


    过去我一直以为,函数参数就只是通过栈传递的,最近看一些反汇编代码,发现好多时候都是通过寄存器。做了个实验,最终明白了,函数的参数怎样传递,其实是和参数个数有关。

    在linux下的objdump反汇编的结果:如果参数很多,有一些参数会通过栈传递,否则通过寄存器。

    在windows下的VS反汇编的结果:都通过栈。

    C代码:

    #include <stdio.h>
    
    void func1(int a, int b)
    {
        printf("%d", a+b);
    }
    
    void func2(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k)
    {
        printf("%d",a+b+c+d+e+f+g+h+i+j+k);
    }
    
    void main ()
    {
        int a = 1;
        int b = 1;
        int c = 1;
        int d = 1;
        int e = 1;
        int f = 1;
        int g = 1;
        int h = 1;
        int i = 1;
        int j = 1;
        int k = 1;
        func1(a, b);
        func2(a, b, c, d, e, f, g, h, i, j, k);
    }

    linux下通过objdump反汇编:

    [root@VM_120_194_centos C]# gcc -E 20170704.c -o 20170704.i
    [root@VM_120_194_centos C]# gcc -S 20170704.i -o 20170704.s
    [root@VM_120_194_centos C]# gcc -c 20170704.s -o 20170704.o
    [root@VM_120_194_centos C]# objdump -S 20170704.o
    
    20170704.o:     文件格式 elf64-x86-64
    
    
    Disassembly of section .text:
    
    0000000000000000 <func1>:
       0:    55                       push   %rbp
       1:    48 89 e5                 mov    %rsp,%rbp
       4:    48 83 ec 10              sub    $0x10,%rsp
       8:    89 7d fc                 mov    %edi,-0x4(%rbp)
       b:    89 75 f8                 mov    %esi,-0x8(%rbp)
       e:    8b 45 f8                 mov    -0x8(%rbp),%eax
      11:    8b 55 fc                 mov    -0x4(%rbp),%edx
      14:    01 d0                    add    %edx,%eax
      16:    89 c6                    mov    %eax,%esi
      18:    bf 00 00 00 00           mov    $0x0,%edi
      1d:    b8 00 00 00 00           mov    $0x0,%eax
      22:    e8 00 00 00 00           callq  27 <func1+0x27>
      27:    c9                       leaveq 
      28:    c3                       retq   
    
    0000000000000029 <func2>:
      29:    55                       push   %rbp
      2a:    48 89 e5                 mov    %rsp,%rbp
      2d:    48 83 ec 20              sub    $0x20,%rsp
      31:    89 7d fc                 mov    %edi,-0x4(%rbp)
      34:    89 75 f8                 mov    %esi,-0x8(%rbp)
      37:    89 55 f4                 mov    %edx,-0xc(%rbp)
      3a:    89 4d f0                 mov    %ecx,-0x10(%rbp)
      3d:    44 89 45 ec              mov    %r8d,-0x14(%rbp)
      41:    44 89 4d e8              mov    %r9d,-0x18(%rbp)
      45:    8b 45 f8                 mov    -0x8(%rbp),%eax
      48:    8b 55 fc                 mov    -0x4(%rbp),%edx
      4b:    01 c2                    add    %eax,%edx
      4d:    8b 45 f4                 mov    -0xc(%rbp),%eax
      50:    01 c2                    add    %eax,%edx
      52:    8b 45 f0                 mov    -0x10(%rbp),%eax
      55:    01 c2                    add    %eax,%edx
      57:    8b 45 ec                 mov    -0x14(%rbp),%eax
      5a:    01 c2                    add    %eax,%edx
      5c:    8b 45 e8                 mov    -0x18(%rbp),%eax
      5f:    01 c2                    add    %eax,%edx
      61:    8b 45 10                 mov    0x10(%rbp),%eax
      64:    01 c2                    add    %eax,%edx
      66:    8b 45 18                 mov    0x18(%rbp),%eax
      69:    01 d0                    add    %edx,%eax
      6b:    89 c6                    mov    %eax,%esi
      6d:    bf 00 00 00 00           mov    $0x0,%edi
      72:    b8 00 00 00 00           mov    $0x0,%eax
      77:    e8 00 00 00 00           callq  7c <func2+0x53>
      7c:    c9                       leaveq 
      7d:    c3                       retq   
    
    000000000000007e <main>:
      7e:    55                       push   %rbp
      7f:    48 89 e5                 mov    %rsp,%rbp
      82:    48 83 ec 60              sub    $0x60,%rsp
      86:    c7 45 fc 01 00 00 00     movl   $0x1,-0x4(%rbp)
      8d:    c7 45 f8 01 00 00 00     movl   $0x1,-0x8(%rbp)
      94:    c7 45 f4 01 00 00 00     movl   $0x1,-0xc(%rbp)
      9b:    c7 45 f0 01 00 00 00     movl   $0x1,-0x10(%rbp)
      a2:    c7 45 ec 01 00 00 00     movl   $0x1,-0x14(%rbp)
      a9:    c7 45 e8 01 00 00 00     movl   $0x1,-0x18(%rbp)
      b0:    c7 45 e4 01 00 00 00     movl   $0x1,-0x1c(%rbp)
      b7:    c7 45 e0 01 00 00 00     movl   $0x1,-0x20(%rbp)
      be:    c7 45 dc 01 00 00 00     movl   $0x1,-0x24(%rbp)
      c5:    c7 45 d8 01 00 00 00     movl   $0x1,-0x28(%rbp)
      cc:    c7 45 d4 01 00 00 00     movl   $0x1,-0x2c(%rbp)
      d3:    8b 55 f8                 mov    -0x8(%rbp),%edx
      d6:    8b 45 fc                 mov    -0x4(%rbp),%eax
      d9:    89 d6                    mov    %edx,%esi
      db:    89 c7                    mov    %eax,%edi
      dd:    e8 00 00 00 00           callq  e2 <main+0x64>
      e2:    44 8b 4d e8              mov    -0x18(%rbp),%r9d
      e6:    44 8b 45 ec              mov    -0x14(%rbp),%r8d
      ea:    8b 4d f0                 mov    -0x10(%rbp),%ecx
      ed:    8b 55 f4                 mov    -0xc(%rbp),%edx
      f0:    8b 75 f8                 mov    -0x8(%rbp),%esi
      f3:    8b 45 fc                 mov    -0x4(%rbp),%eax
      f6:    8b 7d d4                 mov    -0x2c(%rbp),%edi
      f9:    89 7c 24 20              mov    %edi,0x20(%rsp)
      fd:    8b 7d d8                 mov    -0x28(%rbp),%edi
     100:    89 7c 24 18              mov    %edi,0x18(%rsp)
     104:    8b 7d dc                 mov    -0x24(%rbp),%edi
     107:    89 7c 24 10              mov    %edi,0x10(%rsp)
     10b:    8b 7d e0                 mov    -0x20(%rbp),%edi
     10e:    89 7c 24 08              mov    %edi,0x8(%rsp)
     112:    8b 7d e4                 mov    -0x1c(%rbp),%edi
     115:    89 3c 24                 mov    %edi,(%rsp)
     118:    89 c7                    mov    %eax,%edi
     11a:    e8 00 00 00 00           callq  11f <main+0xa1>
     11f:    c9                       leaveq 
     120:    c3                       retq   

    func1和func2两个函数的反汇编代码不用管,我们看main函数里的。可以清晰看到,86 - cc是给变量(栈空间内存)赋值的过程,也就是

    int a = 1;int b = 1;int c = 1;int d = 1;int e = 1;int f = 1;int g = 1;int h = 1;int i = 1;int j = 1;int k = 1;

    d3 - dd是func1调用的过程。需要两个参数,赋值给edi和esi寄存器。也就是说,这里函数参数是存在寄存器里的。

    func1里面,则是先将edi和esi的内容copy到自己的栈空间。这就是C语言书里说的所谓“值传递”吧?? 也不知道谁发明的这种词。以汇编视角看,就是内存的copy而已。

    func1的调用是,main函数的e2 - 11a,这个很有趣,前一部分变量是存在寄存器里(和上面讲的一个道理),而寄存器不够的时候(参数太多了),就通过rsp栈指针寄存器,直接存到下一个要调用的函数的栈空间里面。好有趣。

    windows下VS反汇编代码:

    void main ()
    {
    00BF1540  push        ebp  
    00BF1541  mov         ebp,esp  
    00BF1543  sub         esp,144h  
    00BF1549  push        ebx  
    00BF154A  push        esi  
    00BF154B  push        edi  
    00BF154C  lea         edi,[ebp-144h]  
    00BF1552  mov         ecx,51h  
    00BF1557  mov         eax,0CCCCCCCCh  
    00BF155C  rep stos    dword ptr es:[edi]  
        int a = 1;
    00BF155E  mov         dword ptr [a],1  
        int b = 1;
    00BF1565  mov         dword ptr [b],1  
        int c = 1;
    00BF156C  mov         dword ptr [c],1  
        int d = 1;
    00BF1573  mov         dword ptr [d],1  
        int e = 1;
    00BF157A  mov         dword ptr [e],1  
        int f = 1;
    00BF1581  mov         dword ptr [f],1  
        int g = 1;
    00BF1588  mov         dword ptr [g],1  
        int h = 1;
    00BF158F  mov         dword ptr [h],1  
        int i = 1;
    00BF1596  mov         dword ptr [i],1  
        int j = 1;
    00BF159D  mov         dword ptr [j],1  
        int k = 1;
    00BF15A4  mov         dword ptr [k],1  
        func1(a, b);
    00BF15AB  mov         eax,dword ptr [b]  
    00BF15AE  push        eax  
    00BF15AF  mov         ecx,dword ptr [a]  
    00BF15B2  push        ecx  
    00BF15B3  call        func1 (0BF11F4h)  
    00BF15B8  add         esp,8  
        func2(a, b, c, d, e, f, g, h, i, j, k);
    00BF15BB  mov         eax,dword ptr [k]  
    00BF15BE  push        eax  
    00BF15BF  mov         ecx,dword ptr [j]  
    00BF15C2  push        ecx  
    00BF15C3  mov         edx,dword ptr [i]  
    00BF15C6  push        edx  
    00BF15C7  mov         eax,dword ptr [h]  
    00BF15CA  push        eax  
    00BF15CB  mov         ecx,dword ptr [g]  
    00BF15CE  push        ecx  
    00BF15CF  mov         edx,dword ptr [f]  
    00BF15D2  push        edx  
    00BF15D3  mov         eax,dword ptr [e]  
    00BF15D6  push        eax  
    00BF15D7  mov         ecx,dword ptr [d]  
    00BF15DA  push        ecx  
    00BF15DB  mov         edx,dword ptr [c]  
    00BF15DE  push        edx  
    00BF15DF  mov         eax,dword ptr [b]  
    00BF15E2  push        eax  
    00BF15E3  mov         ecx,dword ptr [a]  
    00BF15E6  push        ecx  
    00BF15E7  call        func2 (0BF11FEh)  
    00BF15EC  add         esp,2Ch  
    }
    00BF15EF  xor         eax,eax  
    00BF15F1  pop         edi  
    00BF15F2  pop         esi  
    00BF15F3  pop         ebx  
    00BF15F4  add         esp,144h  
    00BF15FA  cmp         ebp,esp  
    00BF15FC  call        __RTC_CheckEsp (0BF1140h)  
    00BF1601  mov         esp,ebp  
    00BF1603  pop         ebp  
    00BF1604  ret  

    dword ptr [x]是指取标号为x,地址内的值,大小是双字double word。

    这里代码清晰的多,看到push指令就明白了。

    可以看出,这里反汇编代码,不管参数多少,都是通过栈来传参的。

    至于【在linux下的objdump反汇编的结果】与【在windows下的VS反汇编的结果】为何有这种差别?我就不清楚了。

    具体区别也有:

                             (1)为何VS下反汇编函数调用时,固定要将ebx、esi、edi寄存器入栈(函数运行结束再pop),不管有没有必要。而linux objdump反汇编就没有这个过程,可能这个程序这样做没有意义就把这过程优化了吧。

                             (2)为何VS下反汇编是通过push指令入栈,而linux objdump是通过操作rsp栈指针入栈。虽然作用都是一样的。

    其实二者都是大同小异了。不管是通过寄存器传递,还是通过栈传递,最后被调用函数内部都是只用栈内存来存参数的。

    附一张之前画的函数调用栈内存开辟、回收过程示意图:

    这张图有一个没描绘到的部分,就是ip的入栈,是通过call指令达到的。

  • 相关阅读:
    雅虎公司C#笔试题
    DES
    从Socket看Visual C#.Net网络程序开发
    进一步接触C#委托与事件
    学习C#消息:循序渐进
    解惑答疑:C#委托和事件
    利用Visual C#实现Windows管道技术1
    使用命名管道通过网络在进程之间进行通信
    C#体验编程技术 类与对象
    学习C#实现HTTP协议:多线程文件传输
  • 原文地址:https://www.cnblogs.com/rixiang/p/7116589.html
Copyright © 2020-2023  润新知