深入理解过程(函数)调用

来源:互联网 发布:ubuntu上可以干什么 编辑:程序博客网 时间:2024/05/17 04:00

通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的。

int g(int x){  return x + 3;}int f(int x){  return g(x);}int main(void){  return f(8) + 1;}

main函数调用f函数,f函数调用g函数(main—>f—>g)
1、传递控制。在进入过程f(8)的时候,程序计数器必须被设置为f的代码的起始地址,然后在返回时,要把程序计数器设置为main中调用f后面那条指令的地址。
2、传递数据。main必须能够向f提供一个或者多个参数,f必须能向main返回一个值。
3、分配和释放内存。开始时,f可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间。
f调用g,同理

一个C过程大致结构如下:
一、准备阶段
-形成帧底:pushl和movl指令
-生成栈帧(如果需要的话):subl指令
-保存现场(如果有被调用者保存寄存器):movl指令
二、过程体
-分配局部变量,并赋值
-具体处理逻辑,如果遇到函数调用时
准备参数:将实参送栈帧入口参数处(x86-64先送寄存器)
CALL指令:保存返回值并转被调函数
-在EAX中准备返回参数
三、结束阶段
-退栈:leave指令
-取返回地址返回:ret指令

如图运行时栈帧:
通用的栈帧结构(栈用来传递参数、存储返回信息、保存寄存器,以及局部变量存储)
通用的栈帧结构(栈用来传递参数、存储返回信息、保存寄存器,以及局部变量存储)

Linux内核使用的是AT&T汇编格式。与Intel格式略有不同,其中最重要的是在带有多个操作数指令情况下,列出的操作数顺序相反。

pushl %eax         subl $4,%esp                    movl %eax, (%esp)popl %eax          movl (%esp), %eax                    addl $4, %espcall 0x12345       pushl %eip                    movl &0x12345, (%eip)leave               movl %ebp, %esp                    popl %ebpret                 popl %eip

通过gcc –S –o main.s main.c -m32指令把main.c文件编译成32位机的main.s汇编源文件
c语言汇编后文件

x86-32main:    //建立被调用者函数的堆栈框架    pushl   %ebp    movl    %esp, %ebp    //被调用者函数体    subl    $4, %esp     //生成main栈帧    ……                  //分配局部变量(此处没有定义局部变量)    movl    $8, (%esp)   //准备入口参数(通过栈传递参数)    call    f            // 将eip中下一条指令的地址压入栈顶,                         //设置eip指向被调程序f开始出    addl    $1, %eax    //拆除被调用者函数的堆栈框架    leave    ret

通过gcc –S –o main.s main.c 指令把main.c文件编译成64位机的main.s汇编源文件

x86-64main:    pushq   %rbp    movq    %rsp, %rbp    movl    $8, %edi    //准备入口参数(通过寄存器传递参数)    call    f    addl    $1, %eax    popq    %rbp    ret

在x86-64中,大部分过程间的数据传送是通过寄存器实现的。但通过寄存器传送最多传递6个整型(如整数和指针)参数,寄存器是按照特殊顺序来使用的,而使用的名字是根据参数的大小来确定的(%edi->%esi->%edx->%ecx->%r8d->%r9d)。如果一个函数有大于6个整形参数,超出6个参数的函数,需要在自己的栈帧中为超出部分的参数分配空间,即参数构造区。

生长main栈帧:
这里写图片描述
这里写图片描述
call f指令把返回地址(紧跟在call指令后面的那条指令的地址)入栈,并将PC设置为f函数的起始地址。我们把这个返回地址当作main的栈帧的一部分。因为它存放的是与main相关的状态。

生长f栈帧:
这里写图片描述
main函数的基地址入站。保存现场,用于main函数退栈时恢复调用者的栈帧。
这里写图片描述
在原有main函数堆栈上建立了一个新的空的f函数调用堆栈
这里写图片描述
f的代码会扩展当前空的f栈帧的边界,分配它的栈帧所需的空间。在这个空间中,它可以保存寄存器的值,分配局部变量空间,为它调用的过程设置参数。大多数栈帧都是定长的,在过程开始就分配好了。上图,f函数并没有定义局部变量,也没有要保存的寄存器的值,所以在栈帧上只传递了它调用g函数的过程参数8。
这里写图片描述
在x86-64中,通过寄存器,过程main最多传递6个整数值(也就是指针和整数),但是如果f需要更多的参数,main可以在调用f之前在自己的栈帧里存储好这些参数。而在x86-32中,这些参数均是通过栈帧来存储传递的(如图中8(%ebp)中存储的参数8)。
这里写图片描述
同理于,call f指令。

生长g栈帧:
这里写图片描述

总结:计算机是如何工作的?(总结)——三个法宝

  • 1、存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;
  • 2、函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能;

enter
——pushl %ebp
——movl %esp,%ebp
leave
——movl %ebp,%esp
——popl %ebp
函数参数传递机制和局部变量存储

  • 3、中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。
原创粉丝点击