• c语言中函数调用的本质从汇编角度分析


    今天下午写篇博客吧,分析分析c语言中函数调用的本质,首先我们知道c语言中函数的本质就是一段代码,但是给这段代码起了一个名字,这个名字就是他的的这段代码的开始地址

    这也是函数名的本质,其实也就是汇编中的标号。下面我们会接触到一些东西 比如 eip 就是我们常常说的程序计数器,还有ebp和esp (这里是俩个指针,记得我们以前学8086也就一个sp堆栈指针)分别为EBP是指向栈底的指针,在过程调用中不变,又称为帧指针。ESP指向栈顶,程序执行时移动,ESP减小分配空间,ESP增大释放空间,ESP又称为栈指针。当然现在不理解没关系(在堆栈中变量分布是从高地址到低地址分布)。

    好了我们开始正式话题吧:

    1.先看图,下面我先贴出一个调用代码。

    # include <stdio.h>
    
    int fun(int a, int b)
    {
    	int c = 0;
    	c= a + b;
    	return c;
    
    }
    int main(void)
    {
    	int a = 1;
    	int b = 3;
    	fun(a,b);
    
    
    	return 0;
    }
    

      反汇编后的代码

       
    --- 汇编代码-----------------------------------------------------------------------
    
    __CxxUnhandledExceptionFilter:
    00A51113  jmp         __CxxUnhandledExceptionFilter (0A525E0h)  
    ___CxxSetUnhandledExceptionFilter:
    00A51118  jmp         __CxxSetUnhandledExceptionFilter (0A52660h)  
    _QueryPerformanceCounter@4:
    00A5111D  jmp         _QueryPerformanceCounter@4 (0A53BB0h)  、
    
    _fun:                  ;注意了 fun在这里
    
    
    00A51122  jmp         fun (0A513C0h)  ;可以看出fun还要跳转 这次跳到了0A513C0h
    
    
    __unlock:
    00A51127  jmp         __unlock (0A536F4h)  
    _GetCurrentProcessId@0:
    00A5112C  jmp         _GetCurrentProcessId@0 (0A53BB6h)  
    @_RTC_CheckStackVars2@12:
    00A51131  jmp         _RTC_CheckStackVars2 (0A51490h)  
    ___set_app_type:
    00A51136  jmp         ___set_app_type (0A5269Eh)  
    
    
    
    --- 被调函数的真正地址 -----------------------------------------
    
    
         1: # include <stdio.h>
         2: 
         3: int fun(int a, int b)
         4: {
    00A513C0  push        ebp         ;压栈 ebp 保护ebp
    00A513C1  mov         ebp,esp     ;将现在的esp地址给ebp换句话说ebp现在指向了这里
                                      ;其实也就是栈帧的最下面
    00A513C3  sub         esp,0CCh  
    00A513C9  push        ebx  
    00A513CA  push        esi  
    00A513CB  push        edi  
    00A513CC  lea         edi,[ebp-0CCh]  
    00A513D2  mov         ecx,33h  
    00A513D7  mov         eax,0CCCCCCCCh  
    00A513DC  rep stos    dword ptr es:[edi]  
         5:     int c = 0;
    00A513DE  mov         dword ptr [c],0  
         6:     c= a + b;
    00A513E5  mov         eax,dword ptr [a]  
    00A513E8  add         eax,dword ptr [b]  
    00A513EB  mov         dword ptr [c],eax  
         7:     return c;
    00A513EE  mov         eax,dword ptr [c]  
         8: 
         9: }
    00A513F1  pop         edi  
    00A513F2  pop         esi  
    00A513F3  pop         ebx  
    00A513F4  mov         esp,ebp  
    00A513F6  pop         ebp  
    00A513F7  ret  
    
    
    --- 主调函数 -----------------------------------------------------------------------
    
    
        10: int main(void)
        11: {
    00A51A11  mov         ebp,esp  
    00A51A13  sub         esp,0D8h  
    00A51A19  push        ebx  
    00A51A1A  push        esi  
    00A51A1B  push        edi  
    00A51A1C  lea         edi,[ebp-0D8h]  
    00A51A22  mov         ecx,36h  
    00A51A27  mov         eax,0CCCCCCCCh  
    00A51A2C  rep stos    dword ptr es:[edi]  
        12:     int a = 1;
    00A51A2E  mov         dword ptr [a],1  ;定义变量a
        13:     int b = 3;
    00A51A35  mov         dword ptr [b],3  ;定义变量b
        14:     fun(a,b);
    00A51A3C  mov         eax,dword ptr [b]  ;把变量b给eax
    00A51A3F  push        eax                ;eax压栈 也就b压栈
    00A51A40  mov         ecx,dword ptr [a]  ;同上
    00A51A43  push        ecx  
    00A51A44  call        _fun (0A51122h)    ;汇编开始调用,在汇编中函数名前面加下划线当标号处理
                                             ;地址是0A51122h,现在我们去哪里
    00A51A49  add         esp,8  
        15: 
        16: 
        17:     return 0;
    00A51A4C  xor         eax,eax  
        18: }
    00A51A4E  pop         edi  
    00A51A4F  pop         esi  
    00A51A50  pop         ebx  
    00A51A51  add         esp,0D8h  
    00A51A57  cmp         ebp,esp  
    00A51A59  call        __RTC_CheckEsp (0A5113Bh)  
    00A51A5E  mov         esp,ebp  
    00A51A60  pop         ebp  
    00A51A61  ret  
    --- 无源文件 -----------------------------------------------------------------------
    

      2.是不是看上面的已经懵逼了,没关系了,我来介绍一下

    上面我是在vs中进行了反汇编,原本准备gcc下搞,后来懒得折腾了。先讲一下函数调用的过程,函数调用的时候其实也就是汇编中的地址的跳转,汇编中的跳转源于标号地址。其实这个也好理解,不知道地址,你让我如何找你。但是在找的开始,我们需要先记录一下回家地址,当前的一些寄存器状态(这是因为调用到里面也可能用到这些寄存器)注意还要压入一些函数调用参数。来张图我们看看

    我们从上面的图可以看到,函数调用的时候依次压栈从右到左。压栈完毕调用call。call的作用有俩个,就是压栈返回值,然后修改程序计数器eip,实现程序跳转到被调函数。接着压栈ebp里面的内容(是什么我们先不讲)然后将esp赋值给ebp。也就是ebp里面的内容被改变,变为现在的esp内容,esp不就是栈顶,也就是说现在都指向了栈顶,然后压栈结束了或者可能换有一些其他的参数,比如我们递归调用,那下面就是下一个函数的参数,返回地址等等等。现在我们讨论的是ebp的作用是什么:那就是ebp指向了一个堆栈中一个栈帧的底部。而esp指向了顶部。我们可以利用ebp的偏移实现,局部变量和参数的访问。下面我们要讨论的就是如何返回。其实就是参数依次出栈,最后老ebp弹出到现在ebp。ebp指后到上一次的栈帧底部。但我们问一下参数是如何出栈的,难道是弹出,吗?弹出还有什么用,因为局部变量用完后就没用了呀,也没必要弹出给寄存器,其实是ebp将值赋给esp,esp由以前的栈顶指向栈底也就是ebp的地方。然后老ebp弹出到ebp。ebp归为到以前的ebp。esp再减4。esp回到返回地址处,然后在修改eip返回。然后esp再减4,回到新的栈顶。而返回的指令源于ret。

    其实这个过程也不是很难,就是繁琐。需要对着栈图分析。需要理解局部变量的抛弃源于ebp对esp的修改。

  • 相关阅读:
    2018 校招在线编程 20题-01
    ubuntu 配置muduo库
    plsql远程访问配置
    web开发转发和重定向大比拼
    Eclipse中svn同步忽略设置
    静态方法、实例方法和域
    接口中的域
    屏蔽所有异常的方法
    使用axis2时在temp文件产生大量缓存
    spring boot redis分布式锁 (转)
  • 原文地址:https://www.cnblogs.com/zhangfeionline/p/5903714.html
Copyright © 2020-2023  润新知