返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C语言目标文件的详细讲解
  • 692
分享到

C语言目标文件的详细讲解

c语言目标文件是什么c语言目标文件 2023-01-17 15:01:32 692人浏览 薄情痞子
摘要

目录前言目标文件分类可重定位目标文件分段的优点符号和符号表符号解析重定位可执行目标文件总结 前言 一个 C 语言程序经编译器和汇编器生成可重定位目标文件,再经链接器生成可执

前言

一个 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

总结 

到此这篇关于C语言目标文件的文章就介绍到这了,更多相关C语言目标文件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C语言目标文件的详细讲解

本文链接: https://lsjlt.com/news/178016.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C语言目标文件的详细讲解
    目录前言目标文件分类可重定位目标文件分段的优点符号和符号表符号解析重定位可执行目标文件总结 前言 一个 C 语言程序经编译器和汇编器生成可重定位目标文件,再经链接器生成可执...
    99+
    2023-01-17
    c语言目标文件是什么 c语言目标文件
  • c语言压缩文件详细讲解
    目录c语言压缩文件一、单文件压缩二、多文件压缩三、多文件异步压缩四、压缩文件夹c语言压缩文件 话说当今压缩市场三足鼎立,能叫上名号的有zip、rar、7z。其中zip是压缩界的鼻祖,...
    99+
    2024-04-02
  • C语言超详细讲解文件的操作
    目录一、为什么使用文件二、什么是文件1.程序文件2.数据文件3.文件名三、文件指针四、文件的打开和关闭五、文件的顺序读写六、文件的随机读写fseekftellrewind七、文件结束...
    99+
    2024-04-02
  • C语言详细分析讲解多文件的程序设计
    目录一、多文件与编译器链接二、多文件之间的相互访问三、关于#include四、头文件使用的一些原则五、再论全局变量六、注意事项七、实验程序八、小结一、多文件与编译器链接 如下图所示,...
    99+
    2024-04-02
  • C语言详细讲解const的用法
    目录一、int const a / const int a二、const int(*p)/int const(*p)三、int*const p四、const用于函数的地址传递参数一、...
    99+
    2024-04-02
  • C语言全面细致讲解文件操作
    目录什么是文件程序文件数据文件文件名文件指针文件的打开和关闭文件的顺序读写字符输入输出函数字符串输入输出函数(fgets,fputs)格式化输入输出函数(fscanf,fprintf...
    99+
    2024-04-02
  • C语言详细讲解while语句的用法
    目录while语句格式例题1例题2例题3while语句格式 格式: while(表达式){    语句块} 1、先执行while(表达式),如条件为真执行语句块;...
    99+
    2024-04-02
  • C语言结构体(struct)的详细讲解
    目录引言1. 动态内存管理2. 结构体2.1 定义语法2.2 定义示例2.3 初始化2.4 结构体赋值2.5 结构体数组2.6 结构体指针赋值3. 学生管理系统附:结构体变量的存储原...
    99+
    2024-04-02
  • C语言超详细讲解库函数
    目录1 返回整数的getchar函数2 更新顺序文件3 缓冲输出与内存分配4 库函数练习1 返回整数的getchar函数 代码: #include<stdio.h> ...
    99+
    2024-04-02
  • C语言 超详细讲解链接器
    目录1 什么是链接器2 声明与定义3 命名冲突3.1 命名冲突3.2 static修饰符4 形参、实参、返回值5 检查外部类型6 头文件1 什么是链接器 典型的链接器把由编译器或汇编...
    99+
    2024-04-02
  • C语言结构体超详细讲解
    目录前言1、结构体的声明1.1 结构的基础知识1.2 结构的声明1.3 结构成员的类型1.4 结构体变量的定义和初始化2、结构体成员的访问2.1 点操作符访问2.2 ->操作符...
    99+
    2024-04-02
  • C语言数组超详细讲解上
    目录前言1、一维数组的创建和初始化1.1 一维数组的创建1.2 一维数组的初始化1.3 一维数组的使用1.4 一维数组在内存中的存储2、二维数组的创建和初始化2.1 二维数组的创建2...
    99+
    2024-04-02
  • C语言数组全面详细讲解
    目录1.基础知识2.数组的分类2.1按元素类型分类2.2按维数分类3.数组定义和初始化3.1 一维数组3.2 二维数组4.数组元素的引用方法5.字符数组的定义1.基础知识 C语言中使...
    99+
    2024-04-02
  • C语言超详细讲解线性表
    目录1. 顺序表1.1 管理结点1.2 顺序表的插入1.3 顺序表的删除1.4 顺序表的扩容2. 链表2.1 定义2.2 头部插入2.3 尾部插入2.4 任意位置插入2.5 任意位置...
    99+
    2024-04-02
  • C语言详细讲解循环语句的妙用
    目录一、循环语句分析二、do ... while 语句的循环方式三、while 语句的循环方式四、for 语句的循环方式五、break和 continue 的区别六、do 和 bre...
    99+
    2024-04-02
  • C语言详细讲解#pragma的使用方法
    目录一、#pragma 简介二、#pragma message三、#pragma once四、#pragma pack五、小结一、#pragma 简介 #pragma 用于指示编译器...
    99+
    2024-04-02
  • C语言详细讲解qsort函数的使用
    目录qsort1.int型2.float型3.struct型qsort 功能:Performs a quick sort.(快速排序)参数:void qsort( void *bas...
    99+
    2024-04-02
  • C语言超详细讲解指针的使用
    目录指针概述自身类型指向类型代码例子数值型指针字符型指针单字符字符数组字符串型指针字符数组总结指针概述 C语言中指针也可以认为是一种类型,不同于数值型和字符型的类型。推演过去指针变量...
    99+
    2024-04-02
  • C语言 详细讲解#pragma的使用方法
    #pragma是C语言中的预处理指令,用于告诉编译器在编译过程中采取特定的行为。以下是#pragma的使用方法的详细讲解:1. #p...
    99+
    2023-08-08
    C语言
  • C语言超详细讲解轮转数组
    目录题目描述实例解题思路1. 先整体逆转2.逆转子数组[0, k - 1]3.逆转子数组[k, numsSize - 1]易错点代码题目描述 给你一个数组,将数组中的元素向右轮转 k...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作