C编译过程 以及 ELF文件(学习笔记)

news2024/12/29 8:32:32

C编译过程

一个用C语言编写的高级语言程序是从编写到打包、再到编译执行的基本过程,我们知道在CPU上执行的是低级别的机器语言,从高级语言到低级别的机器语言肯定是要经过翻译过程,这个过程大体的过程如下图所示:

img

在Unix系统中,从源文件到可执行目标文件是由编译驱动程序完成的,如大名鼎鼎的 gcc,翻译过程包括图中的是个阶段;

  1. 预处理阶段

    1. 预处理器(cpp)根据以字符#开头的命令修给原始的C程序,结果得到另一个C程序,通常以.i作为文件扩展名。主要是进行 文本替换、宏展开、删除注释 这类简单工作。
    2. 对应的命令:linux> gcc -E hello.c hello.i
  2. 编译阶段

    1. 编译器将文本文件 hello.i 翻译成 hello.s ,包含相应的汇编语言程序
    2. 对应的命令:linux> gcc -S hello.c hello.s
  3. 汇编阶段

    1. 将.s文件翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件.o中(把汇编语言翻译成机器语言的过程)。

    2. 把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行 词法分析 和 语法分析 ,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。

      • 词法分析阶段是编译过程的第一个阶段。这个阶段的任务是从左到右一个字符一个字符地读入源程序,即 对构成源程序的字符流进行扫描然后根据构词规则识别单词 (也称单词符号或符号)。词法分析程序实现这个任务。词法分析程序可以使用lex等工具自动生成。

      • 语法分析是编译过程的一个逻辑阶段。语法分析的任务是 在词法分析的基础上将单词序列组合成各类语法短语 ,如“程序”,“语句”,“表达式”等等.语法分析程序判断源程序在结构上是否正确.源程序的结构由上下文无关文法描述.

      • 语义分析是编译过程的一个逻辑阶段. 语义分析的任务是 对结构上正确的源程序进行上下文有关性质的审查 , 进行类型审查.例如一个C程序片断:

        int arr[2], b;

        b = arr * 10;

        源程序的结构是正确的.

        语义分析将审查类型并报告错误:不能在表达式中使用一个数组变量,赋值语句的右端和左端的类型不匹配.

      • 在进行了语法分析和语义分析阶段的工作之后,有的编译程序将源程序变成一种内部表示形式,这种内部表示形式叫做中间语言或中间表示或中间代码。所谓“中间代码”是一种结构简单、含义明确的记号系统,这种记号系统复杂性介于源程序语言和机器语言之间,容易将它翻译成目标代码。另外,还可以在中间代码一级进行与机器无关的优化。

      • 想详细了解编译过程,请看:编译原理(douban.com)

    3. 对应的命令:linux> gcc -c hello.c hello.o

  4. 链接阶段

    1. 此时hello程序调用了printf函数。 printf函数存在于一个名为printf.o的单独的预编译目标文件中。 链接器(ld)就负责处理把这个文件并入到hello.o程序中,结果得到hello文件,一个可执行文件。
    2. 最后可执行文件加载到储存器后由系统负责执行, 函数库一般分为静态库和动态库两种。
      1. 静态库是指 编译链接时,把库文件的代码全部加入到可执行文件中 ,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为.a。
      2. 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是 在程序执行时由运行时链接文件加载库 ,这样可以节省系统的开销。动态库一般后缀名为.so,gcc在编译时默认使用动态库。
    3. 想详细了解链接脚本,请看:链接脚本(Linker Scripts)语法和规则解析(自官方手册) - BSP-路人甲 - 博客园 (cnblogs.com)

ELF文件

由上面的过程,我们可以看出在经过汇编器和连接器作用后都会输出一个目标文件,那这两个目标文件有什么样的区别呢?说到这里我们先引入目标文件的形式。

  • ELF(Executable Linkble Format)是一种Unix-like系统上的二进制文件格式标准。

  • ELF标准中定义的采用 ELF 格式的文件分为4类:

    ELF文件类型说明实例
    可重定位文件(Relocatable File)内容包含了代码和数据,可以被链接成可执行文件或共享目标文件。Linux上的 .o 文件
    可执行文件(Executable File)可以直接执行的程序Linux上的 a.out
    共享目标文件(Shared Object File)内容包含了代码和数据,可以作为链接器的输入,在链接阶段和其他的 Relocatable File 或者 Shared Object File 一起链接成新的 Object File;或者在运行阶段,作为动态连接器的输入,和Executable File 结合,作为进程的一部分来运行。Linux上的 .so
    核心转储文件(Core Dump File)进程意外终止时,系统可以将该进程的部分内容和终止时的状态信息保存到该文件中以供调试分析。Linux 上的 core 文件

    ELF格式文件

文件格式

ELF文件格式提供了两种不同的视角,在汇编器和链接器看来,ELF文件是由Section Header Table描述的一系列Section的集合,而执行一个ELF文件时,在加载器(Loader)看来它是由Program Header Table描述的一系列Segment的集合

ELF文件格式

左边是从汇编器和链接器的视角来看这个文件,开头的 ELF Header 描述了体系结构和操作系统等基本信息,并指出 Section Header Table 和 Program Header Table 在文件中的什么位置,Program Header Table在汇编和链接过程中没有用到,所以是可有可无的,Section Header Table 中保存了所有 Section 的描述信息。

右边是从加载器的视角来看这个文件,开头是 ELF Header,Program Header Table 中保存了所有 Segment 的描述信息,Section Header Table 在加载过程中没有用到,所以是可有可无的。注意 Section Header Table 和 Program Header Table 并不是一定要位于文件开头和结尾的,其位置由 ELF Header 指出,上图这么画只是为了清晰。



我们在汇编程序中用 .section 声明的 Section 会成为目标文件中的Section,此外汇编器还会自动添加一些 Section(比如符号表)。Segment 是指在程序运行时加载到内存的具有相同属性的区域,由一个或多个 Section 组成,比如有两个 Section 都要求加载到内存后可读可写,就属于同一个 Segment。有些 Section 只对汇编器和链接器有意义,在运行时用不到,也不需要加载到内存,那么就不属于任何Segment。

目标文件需要链接器做进一步处理,所以一定有 Section Header Table;可执行文件需要加载运行,所以一定有 Program Header Table;而共享库既要加载运行,又要在加载时做动态链接,所以既有Section Header Table 又有 Program Header Table。

ELF可重定位文件

下面用 readelf 工具读出目标文件 max.o 的 ELF Header 和 Section Header Table,然后我们逐段分析。

img

接下来我们来看 Section Header Table 格式

img

从 Section Header 中读出各 Section 的描述信息,其中 .text 和 .data 是我们在汇编程序中声明的 Section,而其它 Section 是汇编器自动添加的。Addr是这些段加载到内存中的地址(我们讲过程序中的地址都是虚拟地址),加载地址要在链接时填写,现在空缺,所以是全0。Off和Size两列指出了各Section的文件地址,比如.data从文件地址0x60开始,一共0x38个字节,回去翻一下程序,.data中定义了14个4字节的整数,一共是56个字节,也就是0x38个。根据以上信息可以描绘出整个目标文件的布局。

起始文件地址Section或Header
0ELF Header
0x34.text
0x60.data
0x98.bss(此段为空)
0x98.shstrtab
0xc8Section Header Table
0x208.symtab
0x288.strtab
0x2b0.rel.text

这个文件不大,我们直接用hexdump或者使用010 Editor工具把目标文件的字节全部打印出来看。

img

.shstrtab和.strtab

.shstrtab 和 .strtab 这两个 Section 中存放的都是 ASCII 码:

img

可见 .shstrtab中保存着各Section的名字.strtab中保存着程序中用到的符号的名字 。每个名字都是以’\0’结尾的字符串。

我们知道,C语言的全局变量如果在代码中没有初始化,就会在程序加载时用0初始化。这种数据属于.bss段,在加载时它和.data段一样都是可读可写的数据,但是在ELF文件中 .data 段需要占用一部分空间保存初始值,而 .bss 段则不需要。也就是说,.bss 段在文件中只占一个 Section Header 而没有对应的 Section,程序加载时 .bss 段占多大内存空间在 Section Header 中描述。在我们这个例子中没有用到 .bss 段,以后我们会看到这样的例子。

.rel.text和.symtab

我们继续分析 readelf 输出的最后一部分,是从 .rel.text 和 .symtab 这两个 Section 中读出的信息。

img

.rel.text 告诉链接器指令中的哪些地方需要重定位,我们在下一节讨论。

.symtab 是符号表。Ndx 列是每个符号所在的 Section 编号,例如 data_items 在第 3 个 Section 里(也就是 .data ),各 Section 的编号见 Section Header Table 。Value 列是每个符号所代表的地址,在目标文件中,符号地址都是相对于该符号所在Section的相对地址,比如 data_items 位于 .data 段的开头,所以地址是0,_start 位于 .text 段的开头,所以地址也是0,但是 start_loop 和 loop_exit 相对于 .text 段的地址就不是0了。从 Bind 这一列可以看出 _start 这个符号是 GLOBAL 的,而其它符号是 LOCAL 的,GLOBAL 符号是在汇编程序中用 .globl 指示声明过的符号。

.text节

通过使用 objdump 工具可以把程序中的机器指令进行反汇编(Disassemble),得到其汇编代码

img

ELF可执行文件

先看可执行文件header的变化

img

在看section header的变化

img

.text和.data的加载地址分别改成了 0x08048074 和 0x080490a0 。.bss段没有用到,所以被删掉了。.rel.text段就是用于链接过程的,链接完了就没用了,所以也删掉了。

在看多出来的两个program header

img

多出来的Program Header Table描述了两个Segment的信息。.text段和前面的 ELFHeader、Program Header Table 一起组成一个 Segment(FileSiz指出总长度是0x9e),.data段组成另一个Segment(总长度是0x38)。VirtAddr列指出第一个Segment加载到虚拟地址0x0804 8000(注意在x86平台上后面的PhysAddr列是没有意义的),第二个Segment加载到地址0x0804 90a0。Flg列指出第一个Segment的访问权限是可读可执行,第二个Segment的访问权限是可读可写。最后一列Align的值0x1000(4K)是x86平台的内存页面大小。在加载时要求文件中的一页对应内存中的一页,对应关系如下图所示。

img

这个可执行文件很小,总共也不超过一页大小,但是两个Segment 必须加载到内存中两个不同的页面,因为 MMU 的权限保护机制是以页为单位的,一个页面只能设置一种权限。此外还规定每个Segment在文件页面内偏移多少加载到内存页面仍然偏移多少,比如第二个Segment在文件中的偏移是0xa0,在内存页面0x0804 9000中的偏移仍然是0xa0,所以是从0x0804 90a0开始,这样规定是为了简化链接器和加载器的实现。从上图也可以看出.text段的加载地址应该是0x0804 8074,也正是_start符号的地址和程序的入口地址。

原来目标文件符号表中的Value都是相对地址,现在都改成绝对地址了。此外还多了三个符号 __bss_start、 _edata 和 _end,这些是在链接过程中添进去的,加载器可以利用这些信息把 .bss段初始化为0。

再看一下反汇编的结果:

img

参考

ELF文件详解—初步认识_code&poetry的博客-CSDN博客

词法分析、语法分析、语义分析 - JackYang - 博客园 (cnblogs.com)

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

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

相关文章

基于simulink的四节串联锂电池的主动均衡算法(PID控制)

电动汽车往往搭载上百节单体电池作为能量来源,如此多的电池聚在一起其性能差异往往造成电量的不一致。不均衡性会随着电池的充放电循环而不断加剧,致使单体容量快速衰减,最终个别单体的失效就会影响整个电池组乃至用电系统的工作。常见的均衡方式可分为主动均衡和被…

亚马逊云科技“专库专用”模式,可有效提高数据库的性能和效率

近日,全球数据库市场发生了一件令人瞩目的事件,根据Gartner的数据,我们发现亚马逊云科技作为一个纯云厂商,夺得了2022年全球数据库领导者的桂冠,占据全球市场的25.3%份额。 云原生数据库的发展方向:与数据分…

深度剖析PostgreSQL慢SQL:原因与优化方案大揭秘

​ PostgreSQL 是一种成熟稳定的关系型数据库管理系统,它支持高级的数据类型、索引以及查询语言。但是,尽管 PostgreSQL 在性能和可靠性方面表现出色,但偶尔也会出现慢 SQL 的情况。本文将探讨 PostgreSQL 慢 SQL 的原因和优化方案&#xff0…

Qt安卓AMD64-v8a配置OpenCV4.5.2

1.OpenCV官方网下载OpenCV的安卓平台SDK并解压 在工程的配置pro文件中添加opencv包含路径INCLUDEPATH与库LIBS unix {ANDROID_OPENCV = C:\Users\dev2\Desktop\OpenCV-android-sdk\sdk\nativeINCLUDEPATH += $$ANDROID_OPENCV/jni/include/opencv2 \$$ANDROID_OPENCV/jni/in…

进行网站建设,开启数字化时代的新篇章

在当今数字化时代,拥有一个个人或企业网站已经成为了非常普遍的需求。网站可以帮助你展示自己或品牌的形象、提供各种服务和信息,甚至是进行在线销售等,这些都是传统媒体所无法比拟的优势。 网站建设是什么? 网站建设是指在互联…

LaravelPHP笔记-转json后中文变成\u数据

用 PHP 的 json_encode 来处理中文的时候,中文都会被编码,变成不可读的,类似”\u***” 的格式,如果想汉字不进行转码,可用如下方法: 在json_encode第二个参数添加JSON_UNESCAPED_UNICODE。 如下代码&…

dsl语句查询elasticsearch集群节点分布和资源使用情况

查询语句如下(本文是直接在kibana里面执行的哦) GET _cat/nodes?v执行结果 这样就可以很直观的看到,es部署在了哪些节点上,以及各节点资源分布使用

【Ant Design of Vue】自定义SVG图标的使用

一、需求 由于Ant Design Vue提供的 Icon 图标满足不了项目的需求(需求图标未提供),所以我们使用了自定义的 SVG 图标 二、技术栈 前端框架:vue2 Vue Cli前端UI框架:Ant Design of Vue(v1.7.8&#xff…

Python自动化测试,Excel数据驱动读取 xlrd实战(超详细)

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 xlrd模块安装 pi…

JS逆向系列之猿人学爬虫第16题-window蜜罐

文章目录 目标网站参数定位与加密逻辑分析补全后的jspython调用测试往期逆向文章推荐目标网站 https://match.yuanrenxue.cn/match/16参数定位与加密逻辑分析 t就是时间戳,m是我们主要逆向的参数,跟栈进入window

mysql之mha高可用

目录 一、MHA的相关知识 1)什么是 MHA 2)MHA 的组成 (1)MHA Node(数据节点) (2)MHA Manager(管理节点) 3)MHA 的特点 二、MHA的一主两从部…

Java中创建对象的方式有几种?

使用new关键字 这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。 Student s new Student();2.使用Class类的newInstance方法(反射) 我们也可以使用Class类的newInstance方法创建对象…

必要条件与充分条件

关于对充分条件、必要条件、充要条件的最简单扼要的理解: 充分条件:有A就一定有B,则A是B的充分条件; 必要条件:无A就一定无B,则A是B的必要条件; 充要条件:有A就一定有B&#xff0…

黑客是怎样练成的

网学黑客技术的人越来越多了,不少人都不知道该怎么学,今天就来详细的说一说黑客是如何炼成的。 首先,什么是黑客? 黑客 :泛指擅长IT技术的电脑高手 黑客一词,源自英文Hacker,早期其实就是一群…

6月27日亚马逊云科技中国峰会议程抢先看

大会亮点预览 ● 汇聚百余位重磅嘉宾共同探路云端 ● 技术分享与发布 赋能数字化转型创新 ● 共同探索行业转型之道 驱动创新价值 ● 聚焦前沿科技 云计算年度热点话题盘点 ● 热点主题展示 打造数字科技创新型展区 ● 开发者专属版块 学玩一体 高效进阶 ● Amazon De…

海外网红营销潜藏的风险:如何规避失败的可能性?

在数字化时代,海外网红营销已成为品牌推广的重要策略。然而,不少企业在海外网红营销中遭遇失败,导致推广效果不佳甚至适得其反。本文Nox聚星将和大家探讨海外网红营销失败的原因,并详细分析其中的关键问题。 1、文化差异 海外网红…

string类学习

本篇将深入学习string类,通过各个测试函数玩遍cstring类,学到就是赚到!!! 文章目录 1.头文件和源文件1.1源文件1.2头文件 2.构造函数3.赋值重载函数4.元素访问运算符5.迭代器5.1正向迭代器5.2反向迭代器 6.添加字符串…

github本地修改后不想提交

本地做了修改后,不想提交,想恢复如初,如果直接git reset --hard 会提示你本地还有没暂存的文件。 所以可以先暂存,然后再回退

Linux ~ NFS 文件共享

Ubuntu 下载nfs服务软件包 sudo apt-get install nfs-kernel-server配置nfs vim /etc/exports表头表头/mnt/*指示要共享的目录*代表允许所有的网络段访问rw指示具有可读写的权限sync指示资料同步写入内存和硬盘no_root_squash客户端分享目录使用者的权限 启动rpcbind服务 …

Databend 开源周报 第 99 期

Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展,遇到更贴近你心意的 Databend 。 Flink CDC Apa…