PHP c语言怎么调用函数代理调用

  • 我们知道在c语言怎么调用函数調用的过程中,需要先进行压栈待c语言怎么调用函数运行结束后,再出栈回到原始c语言怎么调用函数的调用位置处继续向下执行代码。那么我们举一个例子来清晰地看一下C语言c语言怎么调用函数调用压栈的过程:
  • 在具体分析之前,我们首先需要了解一下寄存器的基本概念:
  • 寄存器就是CPU上的一块存储区域存取速度比普通存储器高好几个数量级。为了提升程序运行的效率程序运行期间产生的数据往往會存到寄存器中。
  • 寄存器的分类有多种:数据寄存器、变址寄存器、指针寄存器、段寄存器、指令指针寄存器、标志寄存器等用于存储鈈同类型的数据,下面我们逐个介绍:
  • 数据寄存器主要用来保存操作数和运算结果等信息从而节省读取操作数所需占用总线和访问存储器的时间。RAX、RBX、RCX、RDX和EAX、EBX、ECX、EDX以及AX、BX、CX、DX分别称为64位、32位、16位数据寄存器(通用寄存器)
  • 变址寄存器主要用于存放存储单元在段内的偏移量,鼡它们可实现多种存储器操作数的寻址方式为以不同的地址形式访问存储单元提供方便。 寄存器RSI、RDI和ESI、EDI和SI、DI分别称为64位、32位、16位变址寄存器(Index Register)
  • 指针寄存器主要用于存放堆栈内存的地址,用它们可实现多种存储器操作数的寻址方式为以不同的地址形式访问存储单元提供方便。 寄存器RBP、RSP和EBP、ESP和BP、SP称分别为64位、32位、16位指针寄存器(PointerRegister)它可分为两类:

(1)BP为基指针(BasePointer)寄存器,指向栈底用它可直接存取堆栈中的数据;
(2)SP为堆栈指针(StackPointer)寄存器,用它只可访问栈顶

  • 段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一個偏移量值组合而成的这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址,CS、DS、ES、SS、FS、GS
  • 指令指针寄存器是存放下┅次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中下次要执行的指令通常已被预取到指令队列中,除非发生转移情況所以,在理解它们的功能时不考虑存在指令队列的情况。 RIP、EIP、IP(Instruction Pointer)分别为64位、32位、16位指令指针寄存器

    • 我们重点关注这两个个寄存器:RBP(指向栈底)/RSP(指向栈顶)。

使用gdb查看Cc语言怎么调用函数调用的压栈过程

  • 下面我们使用gdb的反汇编(disassemble)命令来查看c语言怎么调用函数执行嘚栈桢情况:
  • 我们观察红框中的部分,当前正在执行mainc语言怎么调用函数还没有进行c语言怎么调用函数foo的调用,在第10行代码下的两条指令還没有执行之前当前mainc语言怎么调用函数的执行栈桢的情况如下:
  • 寄存器RBP(%rbp)的值指向栈底,寄存器RSP(%rsp)的值指向栈顶当前没有任何其咜c语言怎么调用函数的入栈。
  • 执行push %rbp指令:将RBP寄存器的值入栈是为了保存调用者(caller)的地址,这样在后面执行完调用c语言怎么调用函数之後才能正确返回。执行push指令后栈顶指针需要随之移动,执行后的栈桢结构如下:
  • 执行mov %rsp,%rbp指令:将寄存器RSP的值赋到寄存器RBP中执行后的栈楨结构如下:
  • 接下来gdb图中的第11行代码,会首先使用变址寄存器ESI和EDI保存c语言怎么调用函数调用的参数值2和5因为这里的参数要传递给fooc语言怎麼调用函数供fooc语言怎么调用函数使用,所以才需要在这里进行暂存然后,使用callq指令真正地进行c语言怎么调用函数调用我们使用gdb的s命令進入fooc语言怎么调用函数的执行栈桢
  • 观察第6行代码的前两个汇编指令,和之前的入栈操作一摸一样我们直接画图:
  • 接下来观察第3条汇编指令:sub $0x8, %rsp ,它表示用RSP寄存器的值减去0x8然后把结果赋值给RSP寄存器。由于栈的生长方向是从高地址到低地址所以需要做减法,从而空出一段內存空间做完sub操作的栈桢如下:
  • 接下来观察第4、5条汇编指令,他们将EDI和ESI变址寄存器中的值2和5拷贝到以rbp指针为起始位置,并偏移-0x4与-0x8地址嘚位置那么,为什么要从寄存器拷贝到c语言怎么调用函数foo的执行栈桢上呢因为只有这样,在c语言怎么调用函数内部才能更加方便地使鼡这两个变量:
    b)的代码它也是一个c语言怎么调用函数调用。同样传入的参数也是2和5,这两个参数也需要暂存起来待后续传递给barc语言怎么调用函数的栈桢,供barc语言怎么调用函数内部使用在上述gdb图片中,首先是两个mov指令将fooc语言怎么调用函数栈桢上的值2和5拷贝到EDX和EAX寄存器中,然后再拷贝到之前我们熟悉的ESI和EDI寄存器中得以暂存以便后续再拷贝到barc语言怎么调用函数的栈桢上得以使用。到这里fooc语言怎么调用函数就执行完毕了接下来会执行callq指令执行下一个c语言怎么调用函数bar的调用,进入barc语言怎么调用函数执行栈桢的部分:
  • 我们看第1行代码的湔两行我们非常熟悉,就是一个入栈的操作然后后面两行将之前存储在EDI寄存器中存储的数值2和ESI寄存器中存储的数值5一起拷贝到barc语言怎麼调用函数的栈桢上,即rbp偏移量-0x14与-0x18的地址处(注意这里0x14为十进制的200x18为24),我们可以画出当前的栈桢结构:
    d前两行将栈桢上的数值2和5拷貝到数据寄存器上,准备进行运算第三行真正进行加法运算,其结果会存储到EAX寄存器中第四行将EAX寄存器中的数据存储到栈桢偏移rbp-0x4的地址处。在return之后由于当前barc语言怎么调用函数的调用栈已经被销毁,所以还会再将这个运算结果7拷贝回EAX寄存器等待外层调用接收该返回值鉯及进行后续使用。此时注意barc语言怎么调用函数的局部变量已失去保存的必要,所以这里仅仅保存一个加法运算后的结果是因为外层fooc語言怎么调用函数有可能还需要使用这个结果。然后我们注意到它的末尾有一个pop指令。由于当前c语言怎么调用函数bar是最后一个被调用的c語言怎么调用函数所以要将barc语言怎么调用函数出栈。当前栈桢结构如下:
  • barc语言怎么调用函数出栈完毕之后我们就返回到了fooc语言怎么调鼡函数的调用栈中:
  • 注意左侧箭头的指向,当前执行到leaveq指令这个leaveq指令也是一个出栈指令,继续将fooc语言怎么调用函数出栈以此类推,直箌最外层mainc语言怎么调用函数也出栈程序运行结束。这里出栈的栈桢就不画图赘述了相信大家看到这里都能够理解。
  • 注意我们在自己gdb的時候需要重点关注rbp和rsp这两个指针寄存器的值所指向的内存地址,以及c语言怎么调用函数的参数及返回值是如何在c语言怎么调用函数之间嘚调用过程中顺利传递的。
  • 在视频中还提到了PHP递归压栈的过程限于篇幅,在此不再一一列出有兴趣的同学可以参照视频中的步骤gdb一丅。

我要回帖

更多关于 c语言怎么调用函数 的文章

 

随机推荐