• 部分gcc预定义宏和函数栈帧的内存分布


    本文简单基于树莓派8,linux4.4.50版本,32位arm cpu
    尝试了解函数调用栈的内存分布的形态。使用gcc内置的宏 __builtin_frame_address 来打印栈帧内存上的信息,以及了解一下常用的gcc 内置的宏的输出。
    针对 __builtin_frame_address 在gcc官网上可以看到更多的说明:
    https://gcc.gnu.org/onlinedocs/gcc/extensions-to-the-c-language-family/getting-the-return-or-frame-address-of-a-function.html

    1 预定义宏的输出
    #include 
    
    int call_func1(){
        printf("\n %s \n",__func__);
        printf("__func__ %s\n",__func__);
        printf("__LINE__ %d\n",__LINE__);
        printf("__FILE__ %s\n",__FILE__);
        printf("__DATE__ %s\n",__DATE__);
        printf("__time__ %s\n",__TIME__);
        printf("__func__ %s __builtin_frame_address %p\n",__func__,__builtin_frame_address(0));
        printf("__func__ %s,__builtin_return_address %p\n",__func__,__builtin_return_address(0));
    }
    
    int main(){
        call_func1();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    上述源文件的输出如下:

    
     call_func1
    __func__ call_func1 //函数名
    __LINE__ 51         //行号
    __FILE__ main.c     //源文件名
    __DATE__ Nov 27 2022    //编译日期
    __time__ 17:58:17           //编译时间
    __func__ call_func1 __builtin_frame_address 0x7ec541a4
    __func__ call_func1,__builtin_return_address 0x10794
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    __builtin_frame_address 表示函数的栈帧地址,__builtin_return_address表示函数的返回地址。

    2 栈帧的内存打印

    以如下形式的程序调用为例

    #include 
    
    int call_func2(int parm, int parm2){
        int i = 10;
        int j = 0;
        void * offset = NULL;
    
        printf("\n %s \n",__func__);
    
        /*打印从函数的栈帧开始 10个cpu地址(32位)的地址和值*/
        for (j = 0 ; j < i; j++){
            offset = __builtin_frame_address(0)- j*sizeof(int*);
            printf("__builtin_frame_address %p value [%x]\n",offset,*(int*)offset);
        }
    
        /*打印函数的返回地址 ,3个变量的地址,两个入参的地址*/
        printf("__builtin_return_address %p\n",__builtin_return_address(0));
        printf("first variable i  %p\n",&i);
        printf("second variable j  %p\n",&j);
        printf("third variable j  %p\n",&offset);
        printf("first parm parm   %p\n",&parm);
        printf("second parm parm   %p\n",&parm2);
        return 0;
    }
    
    int call_func1(){
        printf("__func__ %s __builtin_frame_address %p\n",__func__,__builtin_frame_address(0));
        printf("__func__ %s,__builtin_return_address %p\n",__func__,__builtin_return_address(0));
        call_func2();
        return 0;
    }
    
    int main(){
        call_func1();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    上述的程序的调用流程是 main->call_func1->call_func2 。其中call_func1打印了函数的栈帧地址和返回地址,call_func2先后打印了其以帧地址为开始之后10个4字节内存的地址和内容,然后打印了函数的返回地址 ,3个变量的内存地址,两个入参的内存地址
    其输出结果如下:

    __func__ call_func1 __builtin_frame_address 0x7e8041a4
    __func__ call_func1,__builtin_return_address 0x10810
    
     call_func2
    __builtin_frame_address 0x7e985194 value [0x107cc]
    __builtin_frame_address 0x7e985190 value [0x7e9851a4]
    __builtin_frame_address 0x7e98518c value [0x10810]
    __builtin_frame_address 0x7e985188 value [0x10a4c]
    __builtin_frame_address 0x7e985184 value [0xa]
    __builtin_frame_address 0x7e985180 value [0x5]
    __builtin_frame_address 0x7e98517c value [0x7e98517c]
    __builtin_frame_address 0x7e985178 value [0x76ffa000]
    __builtin_frame_address 0x7e985174 value [0x4]
    __builtin_frame_address 0x7e985170 value [0x1]
    __builtin_return_address 0x107cc
    first variable i  0x7e804184
    second variable j  0x7e804180
    third variable j  0x7e80417c
    first parm parm   0x7e804174
    second parm parm   0x7e804170
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    以内存地址的形式呈现,可以得到如下图:

    在这里插入图片描述

    栈帧的开始位置,其内容是是 0x107cc ,是函数的返回地址。即__builtin_return_address(0),下一个32位的内容是0x7e8041a4 ,根据其返回函数的打印,是返回后函数的栈帧开始位置。可以印证 栈是向下增长的,返回地址的栈在更高的地址。再下一个32位的内容是,0x10810,是其返回函数的返回地址。

    再接下来一个32位的内容为空,或是任意值,该值可能为预留,使用gdb也未寻找到符号位置。

    接下来三个32位均为栈上的变量的地址,i j 和offset。

    再下一个值的内容为随机,其含义也不明。该32位可能也为预留值。

    接下来两个地址为函数调用的局部变量的地址,parm和parm2

    如果有更多的入参调用,会发现预留给参数变量的栈帧大小大约是 4*32 ,如果超过该范围,就会预先先进行调用参数的内存分配,再执行函数的栈上内存分配。即地址会在0x7e985194 栈帧地址之前。
    在这种情况下,如果形参进行了越界的修改,就会导致栈帧上的返回地址被破坏,导致出现崩溃。该点可后续继续实验。

  • 相关阅读:
    cpp中的函数重载
    算法-打家劫舍
    Elastic Agent 的安装及使用
    GIS开发:gdal在nodejs中使用
    vue中常用的两种路由模式
    鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    找不到模块“./App.vue”或其相应的类型声明。ts(2307)
    现网工作中经常遇到却说不出来的技术名词,看看你都知道吗?-思科,华为,网络工程师
    寒假作业2月6号
    PT_正态总体的抽样分布
  • 原文地址:https://blog.csdn.net/u010050543/article/details/128067895