C 语言目标文件

news2024/11/15 4:25:12

前言

一个 C 语言程序经编译器和汇编器生成可重定位目标文件,再经链接器生成可执行目标文件。那么目标文件中存放的是什么?我们的源代码在经编译以后又是怎么存储的?

文章为 《深入理解计算机系统》的读书笔记,更为详细的内容可以阅读原书。

目标文件分类

目标文件有三种形式:

  • 可重定位目标文件
    • 包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并,创建一个可执行目标文件
  • 可执行目标文件
    • 包含二进制代码和数据,其形式可以被直接复制到内存并执行
  • 共享目标文件
    • 一种特殊可重定位目标文件,可以在加载或运行时被动态地加载进内存并链接

可重定位目标文件

下图为一个典型的 ELF 可重定位目标文件的格式。

ELF object file

ELF 头以一个 16 字节的序列开始,这个序列包含了:生成该文件的系统的字的大小和字节顺序、目标文件的类型、机器类型、节头部表(也称段表)的文件偏移,以及节头部表中条目的大小和数量等。

节头部表是由描述文件中各个节的条目(entry)组成的数组。节头部表描述了文件中各个节在文件中的偏移位置及节的属性等,从节头部表里面可以得到每个节的所有信息。

  • .text:已编译程序的机器代码

  • .rodata:只读数据

    • 比如 printf 语句中的格式串(%d\n
  • .data已初始化的全局和静态 C 变量

    • 局部 C 变量在运行时被保存在栈中,既不在 .data 节中,也不在 .bss 节中
  • .bss未初始化的全局和静态 C 变量,以及所有被初始化为 0 的全局或静态变量

    • 目标文件格式区分已初始化和未初始化变量是为了空间效率:
      • 未初始化变量不需要占据任何实际的磁盘空间,因为没有初始化,值没有意义,也就不必表示每个值
      • .bss 段只是为未初始化全部变量和局部静态变量预留位置,它并没有内容,也不占据实际的空间,仅仅是个占位符
      • .bss 段的大小存放在节头部表中
        • 可以使用 readelf -S test 来查看 test 可执行程序节头部表
      • 运行时,在内存中分配这些变量,并初始化为 0
  • .symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息

    • 包含局部静态变量
    • 不包含局部非静态变量,这些符号在运行时在栈中被管理,链接器不关心这些
  • .rel.text:一个 .text 节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置

  • .rel.data:被模块引用或定义的所有全局变量的重定位信息

  • .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的 C 源文件

    • 只有在编译时加入 -g 选项才会得到这张表
  • .line:原始 C 源程序中的行号和 .text 节中机器指令之间的映射

    • 只有在编译时加入 -g 选项才会得到这张表
  • .strtab:一个字符串表,其中包含 .symtab 和 .debug 节中的符号表,以及节头部中的节名字

    • 字符串表就是以 NULL 结尾的字符串序列

分段的优点

为什么要这么麻烦,把程序的指令和数据分开存放?

  • 程序被装载进内存后,数据和指令分别被映射到两个虚拟区域
    • 由于数据是可读写的,指令是只读的,权限可以分别设置成可读写和只读
    • 可以防止程序指令被改写
  • 对现代 CPU 而言缓存是极其重要的
    • 数据区和指令区分离有利于提高程序的局部性,从而提高缓存命中率
  • 启动多个相同进程时
    • 可以共享一份指令,节省内存

符号和符号表

每个可重定位目标模块 m 都有一个符号表,它包含 m 定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:

  • 由模块 m 定义并能被其他模块引用的全局符号
    • 全局链接器符号对应于非静态的 C 函数和全局变量
  • 由其他模块定义并被模块 m 引用的全局符号
    • 这些符号称为外部符号,对应于在其他模块中定义的非静态 C 函数和全局变量
  • 只被模块 m 定义和引用的符号
    • 它们对应于带 static 属性的 C 函数和全局变量,这些符号在模块 m 中任何位置都可见,但是不能被其他模块引用

符号表是由汇编器用编译器输出到汇编语言 .s 文件中的符号构造的。.symtab 节中包含 ELF 符号表,这张符号表包含一个条目的数组。下面是 ELF 符号表条目格式:

typedef struct {
    int	  name;		// String table offset
    char  type:4,	// Function or data (4 bits)
    	  binding:4;// Local or global (4 bits)
    char  reserved;	// Unused
    short section;	// Section header index
    long  value;	// Section offset or absolute address
    long  size;		// Object size in bytes
} Elf64_Symbol;

name 是字符串表中的字节偏移,指向符号的字符串名字。value 是符号的位置。对于可重定位目标文件,value 是距定义目标的起始位置的偏移。对于可执行目标文件,该值是一个绝对运行时地址。size 是目标的大小(以字节为单位)。

每个符号都被分配到目标文件的某个节,由 section 字段表示,该字段是一个到节头部表的索引。有三个特殊的的伪节,它们在节头部表中是没有条目的:

  • ABS:代表不该被重定位的符号
  • UNDEF:代表未定义的符号,也就是在本目标模块中引用,但定义在其他地方的符号
  • COMMON:表示还未被分配位置的未初始化的数据目标
    • 对于该类型符号,value 字段给出对齐要求,size 给出最小的大小

只有可重定位目标文件中才有伪节,可执行目标文件中是没有的。

GCC 将可重定位目标文件中的符号分配到 COMMON 和 .bss 的规则:

  • COMMON:未初始化的全局变量
  • .bss:未初始化的静态变量,以及初始化为 0 的全局或静态变量

符号解析

对于局部符号的解析是非常简单的,因为编译器只允许每个模块中每个局部符号有一个定义。不过,对全局符号的引用解析就麻烦的多。当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。

如果多个模块定义同名的全局符号,会发生什么呢?下面是 Linux 编译系统采用的方法。

在编译时,编译器向汇编器输出每个全局符号,或者是强或者是弱,汇编器把这个信息编码在可重定位目标文件的符号表里。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。

根据强弱符号的定义,Linux 链接器使用如下规则来处理多重定义的符号名:

  • 不允许有多个同名强符号
  • 如果有一个强符号和多个弱符号同名,选择强符号
  • 如果有多个弱符号同名,从弱符号中任意选择一个

重定位

当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到对最终位置位置的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在 .rel.text 中,已初始化数据的重定位条目放在 .rel.data 中。

下图为 ELF 重定位条目的格式:

elf object

符号解析完成后,代码中的每个符号引用和正好一个符号定义关联起来。此时,就可以开始重定位了。在重定位中,将合并输入模块,并为每个符号分配运行时地址。重定位由两步组成:

  • 重定位节和符号定义
    • 链接器将所有相同类型的节合并为同一类型的新的聚合节
    • 链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号

merge secssion

  • 重定位节中的符号引用
    • 链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址

可执行目标文件

下图为一个典型的 ELF 可执行文件:

elf can

可执行文件加载到内存:

elf load

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据结构】双向链表

1.双向链表的结构2.双向链表的实现首先在VS里面的源文件建立test.c和List.c,在头文件里面建立List.hList.h:#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> typedef int LTDateType; typedef struct ListNode {LTDateType data;s…

LeetCode 329. 矩阵中的最长递增路径(C++)*

思路&#xff1a; 1.用动态规划&#xff0c;但是时间复杂度太高&#xff0c;效率太低 2.使用常规的DFS&#xff0c;时间复杂度高&#xff0c;包含了太多重复无效遍历&#xff0c;会超时 3.在DFS的基础上使用记忆化搜索&#xff0c;帮助消去重复的遍历&#xff0c;提高效率 原题…

解决: 您目前无法访问 因为此网站使用了 HSTS。网络错误和攻击通常是暂时的,因此,此网页稍后可能会恢复正常

目录 问题描述 报错信息 问题原因 如何解决 参考资料 问题描述 您目前无法访问 因为此网站使用了 HSTS。网络错误和攻击通常是暂时的&#xff0c;因此&#xff0c;此网页稍后可能会恢复正常。 报错信息 今天使用Edge浏览器在访问一个平时常用的emoji网站时&#xff0c;…

springboot整合spring-security

在web开发中&#xff0c;安全性问题比较重要&#xff0c;一般会使用过滤器或者拦截器的方式对权限等进行验证过滤。此博客根据b站up主&#xff0c;使用demo示例进行展示spring-security的一些功能作用。 目 录 1、创建项目 2、编写controller 3、添加spring-security依赖 …

Spring Cloud OpenFeign 配置

最少的配置&#xff08;使用默认配置&#xff09; 最少/默认配置示例如下&#xff08;使用Nacos作为服务的注册与发现中心&#xff09;&#xff1a; application.properties server.port8082 spring.application.namenacos-consumer spring.cloud.nacos.discovery.server-ad…

[拆轮子] PaddleDetection中__shared__、__inject__ 和 from_config 三者分别做了什么

在上一篇中&#xff0c;PaddleDetection Register装饰器到底做了什么 https://blog.csdn.net/HaoZiHuang/article/details/128668393 已经介绍了 __shared__ 和 __inject__ 的作用: __inject__ 表示引入全局字典中已经封装好的模块。如loss等。__shared__为了实现一些参数的配…

excel函数技巧:函数TEXT七助数据大变身

如果函数有职业&#xff0c;那各函数的职业会是什么呢&#xff1f;别的先不说&#xff0c;就拿TEXT而言&#xff0c;它可以让日期变数字、数字变日期、阿拉伯数字变大写中文数字、金额元变万元&#xff0c;连IF的条件判断它也可以变出来…这简直就是当之无愧的变装女皇啊&#…

从0到1完成一个Node后端(express)项目(三、写接口、发起请求)

往期 从0到1完成一个Node后端&#xff08;express&#xff09;项目&#xff08;一、初始化项目、安装nodemon&#xff09; 从0到1完成一个Node后端&#xff08;express&#xff09;项目&#xff08;二、下载数据库、navicat、express连接数据库&#xff09; 写接口 我们看ex…

关于Linux部署Tomcat的访问问题

文章目录1.问题2.排除问题2.1检查Tomcat是否启动2.2检查防火墙&端口3.其他可能的问题3.1java的配置问题3.2可能出现了端口占用问题1.问题 在CentOS7系统的主机中配置好了Tomcat后发现通过默认端口无法访问到&#xff08;http://xx:xx:xx:xx:8080&#xff09; 2.排除问题 …

C语言在杨氏矩阵中找一个数

这道题大家都会做&#xff0c;使用暴力算法遍历整个数组。但是题目要求时间复杂度小于O&#xff08;n&#xff09;&#xff0c;这样做显然不合题意&#xff0c;所以&#xff0c;通过分析杨氏矩阵的特点&#xff0c;我们发现矩阵右上角的那个数为一行中最大的&#xff0c;一列中…

SAP MM 新建移动类型(Movement Type)

一、概念 物料的移动类型&#xff08;Movement Type&#xff09;代表了货物的移动&#xff0c;当一个物料做某种移动时&#xff0c;便开始了如下一系列事件&#xff1a; 1、一个物料凭证会被创建&#xff0c;可以被用来作为移动的证明及作为其它任何相关应用的一个信息来源&am…

Jetson nano 入手系列之6—使用qt creator 开发c++ opencv+CSI摄像头人脸检测

Jetson nano 入手系列之6—使用qt creator 开发c opencvCSI摄像头人脸检测1.创建摄像头人脸检测项目1.1 创建并配置项目1.2 编辑文件1.2.1 main.cpp1.2.2 CMakeLists.txt2.构建及编译2.1 直接使用qt creator完成2.2 使用命令行参考文献本系列针对亚博科技jetson nano开发板。 …

一篇文章带你学会MySQL数据库的基本管理

目录 前言 一、数据库的介绍 二、mariadb的安装 三、数据库的开启及安全初始化 四、数据库的基本管理 五、数据库密码更改及破解 六、用户授权 七、数据库的备份 八、phpmyadmin的安装 总结 前言 什么是数据库&#xff1f; 每个人家里都会有衣柜&#xff0c;衣柜是…

前端效果积累 | 酷炫、实用3D地球路径飞行效果实现

&#x1f4cc;个人主页&#xff1a;个人主页 ​&#x1f9c0; 推荐专栏&#xff1a;前端开发成神之路 --【这是一个为想要入门和进阶前端开发专门开启的精品专栏&#xff01;从个人到商业的全套开发教程&#xff0c;实打实的干货分享&#xff0c;确定不来看看&#xff1f; &…

【C语言进阶】自定义类型之结构体

目录一&#xff1a;结构体1.1&#xff1a;结构的基础知识&#xff1a; 1.2&#xff1a;结构的声明&#xff1a; 1.3&#xff1a;特殊声明&#xff08;匿名结构体&#xff09;&#xff1a; 1.4&#xff1a;结构的自引用&#xff1a; 1.5&#xff1a;结构体变量的定义和初始化&am…

springboot 项目自定义log日志文件提示系统找不到指定的文件

自己尝试搭建了一个springboot项目&#xff0c;自定义了log日志文件&#xff0c;启动后报错 Logging system failed to initialize using configuration from logback-spring.xml java.io.FileNotFoundException: E:\code_demo\xxxx\logback-spring.xml (系统找不到指定的文件…

Elasticsearch(二)--Elasticsearch客户端讲解

一、前言 在上一章我们大致了解了下elasticsearch,虽说上次的内容全是八股文&#xff0c;但是很多东西还是非常有用的&#xff0c;这些哪怕往小说作为面试&#xff0c;往大说是可以帮你很快的理解es是个什么玩意儿&#xff0c;所以还是非常推荐大家去看一下上一章内容。 这一章…

【C++】map和set的使用

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;关联式容…

码二哥的技术专栏 总入口

已发表的技术专栏&#xff08;订阅即可观看所有专栏&#xff09; 0  grpc-go、protobuf、multus-cni 技术专栏 总入口 1  grpc-go 源码剖析与实战  文章目录 2  Protobuf介绍与实战 图文专栏  文章目录 3  multus-cni   文章目录(k8s多网络实现方案) 4  gr…

JVM整理笔记之测试工具JCStress的使用及其注解的应用

文章目录前言如何使用JCStress测试代码JCStress 注解说明前言 如果要研究高并发&#xff0c;一般会借助高并发工具来进行测试。JCStress&#xff08;Java Concurrency Stress&#xff09;它是OpenJDK中的一个高并发测试工具&#xff0c;它可以帮助我们研究在高并发场景下JVM&a…