• 函数调用过程分析


    函数调用过程分析

    1. 静态变量和初始化

    int static_variable = 5;
    --------------------------------------------
            .data ;进入程序的数据区
            .even ;确保变量开始于内存的偶数地址
            .global _static_variable ;声明变量为全局类型
     _static_variable:
            .long 5 ;创建空间,初始化
    

    2. 堆栈帧

    一个函数分为:函数序、函数体、函数跋

    函数序:执行启动工作,如:为局部变量保存堆栈中的内存

    函数跋:在函数即将返回之前清理堆栈。

    函数体:执行工作的地方

    void f()
    {
        register int i1, i2, i3, i4, i5,
                     i6, i7, i8, i9, i10;
        register char *c1, *c2, *c3, *c4, *c5,
                      *c6, *c7, *c8, *c9, *c10;
        extern int a_very_long_name_to_see_...
        double dbl;
        int func_ret_int();
        double func_ret_double();
        char *func_ret_char_ptr();
    -------------------------------------------------------
            .text    ;进入程序代码段
            .global _f    ;函数的全局声明
    _f:     link a6, #-88     ;创建堆栈帧,堆栈帧是堆栈中的一个区域,存储变量和其他值
            moveml 0x3cfc, sp@    ;选定寄存器中的值复制到堆栈中
    

    0x3cfc表示寄存器d2至d7、a2到a5中的值需要被保存

    局部变量声明和函数原型不会产生任何汇编代码。但局部变量声明时进行了初始化,也会出现指令用于赋值操作

    3. 寄存器变量

    i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5;
    i6 = 6; i7 = 7; i8 = 8; i9 = 9; i10 = 10;
    c1 = (char *)110; c2 = (char *)120;
    c3 = (char *)130; c4 = (char *)140;
    c5 = (char *)150; c6 = (char *)160;
    c7 = (char *)170; c8 = (char *)180;
    c9 = (char *)190; c10 = (char *)200; 
    -------------------------------------------
    moveq #1,d7
    moveq #2,d6
    moveq #3,d5
    moveq #4,d4
    moveq #5,d3
    moveq #6,d2
    movl #7,a6@(-4)
    movl #8,a6@(-8)
    movl #9,a6@(-12)
    movl #10,a6@(-16)
    movl #110,a5
    movl #120,a4
    movl #130,a3
    movl #140,a2
    movl #150,a6@(-20)
    movl #160,a6@(-24)
    movl #170,a6@(-28)
    movl #180,a6@(-32)
    movl #190,a6@(-36)
    movl #200,a6@(-40)
    

    值1至6被放在数据寄存器,7至10放在其他地方

    指针变量前4个存放在寄存器

    其他变量,机器执行间接寻址和索引操作。a6称为帧指针,指向堆栈帧内部的一个引用位置

    这台机器(motorola 68000),a6用作帧指针,a7是堆栈指针sp,d0和d1用于存函数返回值

    4. 堆栈帧的布局

    运行时堆栈保存了每个函数运行时所需要的数据,包括它的自动变量和返回地址。

    4.1 传递函数参数

    i2 = func_ret_int(10, i1, i10);
    ---------------------------------
    movl a6@(-16),sp@-
    movl d7,sp@-
    pea  10
    jbsr _func_ret_int
    

    前3条指令把函数的参数压入堆栈中。以参数列表相反的次序逐个压入栈。

    接下来跳转子程序,把返回地址压入堆栈中,并跳转到_func_ret_int的起始位置,当被调用函数结束任务返回调用位置时,就需要用到压入堆栈的返回地址。

    5d3178bbba95520405

    4.2 函数序

    int func_ret_int(int a, int b, register int c)
    {
        int d;
    ---------------------------------------------------
            .global  _func_ret_int
    _func_ret_int:
            link    a6,#-8
            moveml  #0x80,sp@
            movl    a6@(16),d7
    

    link指令分成几个步骤:

    • a6的内容被压入堆栈中

    • 堆栈指针的当前值被复制到a6

    • link指令从堆栈指针中减去8

    • 这将创建空间用于保存局部变量和被保存的寄存器值*

      5d317bbfca55072652

    下一条指令把单一寄存器保存到堆栈帧,操作数0x80指定寄存器d7。寄存器存储在堆栈的顶部,剩余部分必然是局部变量存储的地方。如上图右边。

    函数序最后才能够堆栈复制一个值到d7,函数把第三个参数声明为寄存器变量。

    4.3 堆栈中的参数次序

    被调用函数使用帧指针加一个偏移量来访问参数,当参数以反序压入到堆栈时,参数列表的第一个参数便位于堆栈中这堆参数的顶部,它距离帧指针的偏移量是一个常数。

    4.4 最终的堆栈帧布局

    5d317e8fc517c97106

        d = b - 6;
        return a + b + c;
    --------------------------------
    movl    a6@(12),d0
    subl    #6,d0
    movl    d0,a6@(-4)
    movl    a6@(8),d0
    movl    a6@(12),d0
    addl    d7,d0
    moveml  a6@(-8),#0x80
    unlk    a6
    rts    
    

    4.5 函数跋

    • mveml恢复以前被保存的寄存器值

    • unkl(unlink)把a6的值复制给堆栈指针并把从堆栈中弹出的a6旧值装入a6中。清除堆栈帧中返回地址以上的那部分内容

    • rts指令通过把返回地址从堆栈中弹出到程序计数器,从而从该函数返回。

    现在,执行流从调用程序的地点继续。堆栈尚未完全清理

    i2 = func_ret_int(10, i1, i10);
    ------------------------------------
    lea    sp@(12),sp
    movl    d0,d6
    

    第1条指令把参数从堆栈中弹出,此时堆栈的状态就和调用前状态完全一样了。

    4.6 返回值

    函数跋没有使用d0,因此依然保存函数的返回值。第二条指令把d0复制给d6,后者存放变量i2的存放位置。

    dbl = func_ret_double();
    c1 = func_ret_char_ptr();
    -------------------------------------
    jbsr    _func_ret_double
    movl    d0,a6@(-48)
    movl    d1,a6@(-44)
    
    pea    a5@
    jbsr    _func_ret_char_ptr
    addqw   #4,sp
    movl    d0,a5
    

    第一个函数double是8字节,无法放入一个寄存器中,因此返回值需要d0和d1两个寄存器。

    最后那个函数调用说明了指针变量时如何从函数中返回的:也是通过d0进行传递的。

    参考:C和指针-第18章 运行时环境

  • 相关阅读:
    delphi point数据类型
    Sql Server 2008 R2链接服务器Oracle数据库
    ORA-28000 账号被锁定的解决办法
    [Oracle] sqlplus / as sysdba ora-01031 insufficient privileges
    Oracle的操作系统认证(/ as sydba 登录方式)
    Delphi使用线程TThread查询数据库
    oracle
    统计字符串中字符出现的次数-Python
    Jmeter保存下载的文件
    如何在Microsoft Store上免费获得 HEIF、HEVC 编码支持
  • 原文地址:https://www.cnblogs.com/luoxiao23/p/11214289.html
Copyright © 2020-2023  润新知