• C程序是如何跑起来的01 —— 普通可执行文件的构成


    学习目的

    • 程序烧到什么地方?
    • 程序加载到内存什么地方?
    • 程序如何执行?

    一、编译环境搭建

    ubuntu 20.04使用arm-linux-gnueabihf-gcc 7.5.0。

    二、程序源码

    main.c:

    #include 
    #include "calc.h"
    
    int main(int argc, char *argv[])
    {
        int a, b;
        static int local_val = 2;
        static int uninit_local_val;
    
        a = add(2, 3);
        b = sub(5, 4);
    
        printf("a = %d\n", a);
        printf("b = %d\n", b);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    calc.h:

    #ifndef _CALC_H_
    #define _CALC_H_
    
    int add(int a, int b);
    int sub(int a, int b);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    calc.c:

    #include "calc.h"
    
    int add(int a, int b)
    {
        return a + b;
    }
    
    int sub(int a, int b)
    {
        return a - b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    编译:

    arm-linux-gnueabihf-gcc main.c calc.c
    
    • 1

    交叉编译生成 a.out 可执行文件,文件类型是32位ARM平台可执行文件。

    三、readelf工具

    readelf工具由编译器提供,用来列出关于可执行文件的内容的相关信息

    使用格式如下:

    Usage: readelf <option(s)> elf-file(s)
    
    • 1

    (1)查看可执行文件的头部 信息

    • -h:用于列出ELF文件的头部信息,包括可执行文件运行的平台、软件版本、程序入口地址,以及program headers、section header等信息;


    (2)查看section header

    • -S:用于列出程序中section的头部信息

    四、可执行文件的组成结构

    一个可执行文件由一系列section构成,section称为段,包括:代码段text、只读数据段rodata、数据段data、bss段等。

    每个section用一个section header描述,包括段名、段的类型、段的起始地址、段的偏移、段的大小等。

    将可执行文件的所有section header集合到一起就是section header table,使用readelf 的 -S 参数查看的就是该表。

    在程序编译的时候,对C语言代码中定义的函数、变量、未初始化的全局变量进行编译分类,放置在不同的段中:

    • 普通代码翻译成二进制放到代码段(text)中
    • 常量放在只读数据段(rodata)中
    • 初始化的全局变量和静态局部变量放在数据段(data)中

    BSS段比较特殊,未初始化的全局变量和静态变量都会放置到bss段中,但因为这些变量的值都是0,没有必要再开辟空间存储,所以在可执行文件中bss段是不占用空间的。

    但是BSS段的大小、起始地址、各个变量的地址信息都会分别保存在section header table和符号表symtab中,当程序运行的时候,加载器会根据这些信息在内存中紧挨着数据段之后的空间,为BSS段开辟一片存储空间,为各个变量分配存储空间。

    总而言之:BSS段在可执行文件中不占用空间,在程序运行的时候才分配对应的空间

    如果在编译时开启了调试选项,则可执行文件中还会有 .debug section,用来保存可执行文件中每一条二进制指令对应的源码位置信息,根据这些信息,GDB调试器就可以支持源码级的单步调试。

    在最后环节,编译器还会在可执行文件中添加一些其它的section,比如 .init section,这些代码来自C语言运行库的一些汇编代码,用来初始化C程序所依赖的环境。

    参考资料

  • 相关阅读:
    操作系统实训题目
    qmt量化交易策略小白学习笔记第43期【qmt编程之期货数据--如何获取历史主力合约--原生python】
    【数据分享】北京市出租车GPS数据~
    AnsibleFATE部署过程
    Python and和or的优先级实例比较
    大二暑假 + 大三上
    源码解析之——ReentrantLock
    python,满分,砝码称重【第十二届】【省赛】【研究生组】
    【英语:基础高阶_经典外刊阅读】L1.阅读理解—读题定位法
    [附源码]java毕业设计基于的城镇住房公积金管理系统
  • 原文地址:https://blog.csdn.net/Mculover666/article/details/126068050