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)
[email protected]:
00A5111D  jmp         [email protected] (0A53BB0h)  、

_fun:                  ;注意了 fun在这里

00A51122  jmp         fun (0A513C0h)  ;可以看出fun还要跳转 这次跳到了0A513C0h

__unlock:
00A51127  jmp         __unlock (0A536F4h)
[email protected]:
00A5112C  jmp         [email protected] (0A53BB6h)
@[email protected]:
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的修改。

时间: 2024-10-25 04:25:49

c语言中函数调用的本质从汇编角度分析的相关文章

C语言中函数调用过程(如何管理栈空间)

ps:先做草稿,以后有时间再整理并贴图,:) 主要是利用栈底寄存器(ebp).栈顶寄存器(esp)跟eax寄存器(存储返回值)来实现. 假设P调用Q: P() { Q(1,2); } 1.调用前准备,将Q的参数放到栈中(非push) mov $1, (%esp) mov $2, 4(%esp) 2.调用call 0x12345678 (Q的地址) 首先将函数的返回地址(call语句后的那条指令的地址)进栈, 然后跳到0x12345678执行Q的代码. 3.将旧的ebp进栈(用于退出Q时还原) p

从汇编角度分析C语言的过程调用

?更多技术干货请戳:听云博客 基本术语定义 1.系统栈(system stack)是一个内存区,位于进程地址空间的末端. 2.在将数据压栈时,栈是自顶向下增长的,该内存区用于函数的局部变量提供内存.它也支持在调用函数时传递参数. 3.如果调用了嵌套的过程,栈会自上而下增长,并接受新的活动记录(activation record)来保存一个过程所需的所有数据. 4.当前执行过程的活动记录,由标记顶部位置的帧指针(frame point)和标记底部位置的栈指针(stack point)定义. 5.在

C++语言中数组指针和指针数组彻底分析

#################################                              ##       基本知识               ##                              ################################# 当然我们一切都是从最简单的内建类型开始,最后我会做一些推广.先看一下基本的形式,我们从这里起步! Cpp代码 --------------指针---------------- int a

深入理解C语言的函数调用过程 【转】

转自:http://blog.chinaunix.net/uid-25909619-id-4240084.html 原文地址:深入理解C语言的函数调用过程 作者:wjlkoorey258 本文主要从进程栈空间的层面复习一下C语言中函数调用的具体过程,以加深对一些基础知识的理解.    先看一个最简单的程序: 点击(此处)折叠或打开 /*test.c*/ #include <stdio.h> int foo1(int m,int n,int p) { int x = m + n + p; ret

从linux0.11中起动部分代码看汇编调用c语言函数

上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的.在LINUX 0.11中的head.s文件中会看到如下一段代码(linux0.11的启动分析部分会在另一部分中再分析,由于此文仅涉及c与汇编代码的问题,). after_page_tables: pushl $0 # These are the parameters to main :-) pushl $0 pushl $0 pushl $L6 # re

[汇编] C语言中嵌入汇编

>_<" 下面是在C语言中嵌入汇编的例子,下面是三点要注意的~ 1.内联式汇编 2._asm关键字 3.并不是所有中断都能被支持 1 #include<iostream> 2 #include<windows.h> 3 #include<tchar.h> 4 using namespace std; 5 6 int _tmain(int argc, _TCHAR * argv[]) 7 { 8 9 int data1=0x000000; 10 in

在C语言中破坏函数调用堆栈

1 // 这段代码显示,在C语言修改函数的返回地址 2 int test1() 3 { 4 return 0; 5 } 6 7 int test2(int a) 8 { 9 *(&a-1) = (int)test1; // 将返回地址修改为test1 10 return a; 11 } 12 13 int main() 14 { 15 test2(10); 16 return 0; 17 } 在C语言中破坏函数调用堆栈,布布扣,bubuko.com

在c语言中嵌入汇编语句,对于我来说相当难。

今天早上在csdn论坛上看到一个帖子http://topic.csdn.net/u/20120917/14/82f42e17-977a-4824-95bd-7b79db15d283.html:“C语言中嵌入汇编,究竟有何意义?” 其中看到一个例子是在c语言中插入一段汇编代码获取CPU的主频,制造商和型号的: //=====================================================================================/*      

c语言中数组名的本质

c语言中的数组名的本质是什么,数组名是指针吗? 1.数组名是数组元素的首地址#include <stdlib.h>#include <stdio.h>void main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8,9, 10}; printf("a:%d &a:%d :%d \n", a, &a); system("pause"); }上面的程序中,a &a大小一样,证明a就是数组元