news 2026/5/30 19:06:32

库制作与原理 [ ELF ]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
库制作与原理 [ ELF ]

目标文件

在 Windows 系统中,编译和链接这两个步骤被 IDE 高度封装,我们通常只需要一键构建就能完成,使用起来非常方便。

但一旦出现错误,尤其是链接阶段的错误,很多人就会变得束手无策。

在 Linux 环境下,我们之前已经学习过如何直接通过gcc编译器手动完成编译、链接等一系列完整操作。

.o/.obj:可重定位目标文件,简称:目标文件

接下来我们深入探讨编译与链接的完整过程,以便更好地理解动静态库的使用原理。

先来回顾一下:什么是编译?编译的过程,其实就是把我们写的源代码,翻译成 CPU 能够直接运行的机器代码。

但是这里说的“翻译成 CPU 能够直接运行的机器代码”,严格来说是“汇编”做的事。
而“编译”通常指:把高级语言(C/C++)翻译成汇编代码 或 另一种中间表示,然后再由汇编器、链接器完成后续步骤。

不过,在实际工程语境里,大家常把“编译”作为一个笼统的、包含预处理、编译、汇编、链接的完整过程来用。所以你的回顾并不是错,只是在严谨的定义上有模糊。

举个例子:在源文件hello.c中简单输出"hello world!",并且调用一个run函数,而这个函数定义在另一个源文件code.c中。这时我们就可以使用gcc -c分别编译这两个源文件。

// hello.c #include<stdio.h> void run(); int main() { printf("hello world!\n"); run(); return 0; }
// code.c #include<stdio.h> void run() { printf("running...\n"); }

编译两个源文件:

$ gcc -c hello.c $ gcc -c code.c $ ls code.c code.o hello.c hello.o

可以看到,在编译之后会生成两个扩展名为.o的文件,它们被称为目标文件。要注意的是,如果我们修改了一个源文件,那么只需要单独编译它,而不需要浪费时间重新编译整个工程。目标文件是一个二进制文件,文件的格式是 ELF是对二进制代码的一种封装。(我们可以使用 file 来观察)

注意:文件格式是ELF,它本质上就是对二进制代码、数据、符号信息的一种标准封装格式

  • .o目标文件是 ELF 格式
  • .so动态库是 ELF 格式
  • Linux 下的可执行程序也是 ELF 格式

它们内部结构类似,只是存放的内容和属性不一样。

.a静态库不是 ELF 文件,它是:.a= 把多个.o(ELF 可重定位目标文件)打包在一起的压缩包 / 归档文件

$ file hello.o hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

file命令用于辨识文件类型。

更具体来说就是:

.o 文件(目标文件)本质上是对机器码、符号表、重定位信息等的封装,且在类 Unix 系统(如 Linux)中,其格式通常就是 ELF(Executable and Linkable Format)


ELF 文件

代码和数据不能 “一股脑” 塞进二进制文件,而是要采用复杂的 ELF 格式。我们可以从下面几个方面来理解:

为什么不能简单地 "一股脑" 塞进去?

如果把代码和数据简单地 "一股脑" 塞进二进制文件,就像把一堆杂物随意扔进一个大箱子,而不进行任何分类和整理,那么在使用这些代码和数据时就会遇到很多问题:

  • 难以管理:没有结构化的组织方式,很难找到特定的代码或数据。

  • 无法重定位:代码和数据的位置固定,无法在不同环境中灵活使用。

  • 浪费空间:没有优化存储,可能导致大量空间浪费。

  • 难以共享:无法高效地共享代码和数据,每个程序都需要独立携带所有内容。

ELF 文件的类型

ELF 文件主要有三种类型,可以通过 ELF Header 中的e_type成员进行区分。

1. 可重定位文件(Relocatable File)ETL_REL。一般为.o文件。可以被链接成可执行文件或共享目标文件。注意:静态库(.a)内部装的就是这类文件

2. 可执行文件(Executable File)ET_EXEC。可以直接执行的程序。

3. 共享目标文件(Shared Object File)ET_DYN。一般为.so文件。有两种情况可以使用。

  • 链接器将其与其他可重定位文件、共享目标文件链接成新的目标文件;
  • 动态链接器将其与其他共享目标文件、结合一个可执行文件,创建进程映像。

ELF 文件的结构

一个 ELF 文件主要由以下几部分组成:

ELF 头(ELF Header):位于文件的开头,描述了文件的主要特性,如文件类型、目标机器架构、入口点地址等。它还包含了程序头表和节头表的偏移量,用于定位文件的其他部分

程序头表(Program Header Table):描述了文件的段(segments)信息,包括段的类型、偏移量、虚拟地址、文件大小等。段是文件在内存中的映射单元,一个段可以包含多个节。【可以看出:程序头表(Program Header)主要就是给【运行时、动态加载、动态库】服务的!

节头表(Section Header Table):描述了文件的节(sections)信息,包括节的名称、类型、偏移量、大小等。节是文件的逻辑单元,用于存储不同类型的数据。

也就是说:

ELF 头告诉文件这是 ELF,指明:程序头表在哪?节头表在哪

程序头表(Program Header)运行时加载用,和符号表无关。

节头表(Section Header)里面会管理两个关键节:

  • .symtab—— 完整符号表(链接用)
  • .dynsym—— 动态符号表(运行时用)

节(Section):是 ELF 文件的基本组成单位,包含特定类型的数据。常见的节包括:

lfz@HUAWEI:~/lesson/lesson22/win$ size a.out text data bss dec hex filename 3965 776 16 4757 1295 a.out
  • 代码节(.text):存储可执行代码。

  • 数据节(.data):存储已初始化的全局变量和静态变量。(初始化要记录数据)

  • 未初始化数据节(.bss):存储未初始化的全局变量和静态变量,程序加载时会自动初始化为零。(只有在内存展开的时候才开辟空间,然后内容清0,节省的是可执行程序所占据的磁盘空间,因为不用记录初始值 = better save space)

  • 只读数据节(.rodata):存储常量字符串等只读数据。

  • 字符串表(.strtab 和 .shstrtab):存储字符串信息,如节名、符号名等。

说到符号表:

在 Section 当中,我们来简单认识一下.symtab节:【完整符号表】

符号表(Symbol Table)是 ELF 文件格式中的一个重要组成部分,它维护着源码中的函数名、变量名、依赖库与对应地址、代码之间的映射关系

具体来说就是个 char类型的数组:

char label[] = "helloworld\0func\0libc\0a\0obj\0.....";

不过这不是符号表,这是字符串表(.strtab 或 .shstrtab)

它就是一长串连续的字符数组,所有符号名字都塞在这里,用\0分开。

比如:

  • "helloworld"
  • "func"
  • "libc"
  • "a"
  • "obj"

它们在内存里是连在一起的。


那符号表.symtab存什么?符号表里每条记录,有一个字段叫st_name

它不存字符串,只存一个数字:偏移量。

比如:

  • func在字符串表里从第12个字节开始
  • 那符号表中func对应的st_name = 12

链接器一看st_name=12,就去字符串表第 12 个位置读,读到func

st_name(偏移量) | 其他信息(地址、大小等) ------------------------------------------------ 1 | main 函数的信息 5 | printf 函数的信息 16 | func 函数的信息

符号表里只存数字(偏移量)

  • main→ 存数字1
  • printf→ 存数字5

想要知道这个符号叫什么名字:

  • 拿到偏移量 → 去字符串表里读字符串
┌─────────────────────────────────────────────────────────────┐ │ .strtab (字符串表) │ │ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │ │ │ \0 │ main \0 │printf \0 │func \0 │he...│ │ │ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │ │ 0 1 5 12 16 │ │ ↑ ↑ ↑ ↑ │ │ "main" "printf" "func" "hello" │ └─────────────────────────────────────────────────────────────┘ ↑ │ 通过偏移量关联 ↓ ┌─────────────────────────────────────────────────────────────┐ │ .symtab (符号表) │ │ ┌──────────────┬──────────────┬──────────────┬──────────┐ │ │ │ st_name = 1 │ st_name = 5 │ st_name = 12 │ st_name=16│ │ │ │ st_value=... │ st_value=... │ st_value=... │ st_value=...│ │ │ │ st_size=... │ st_size=... │ st_size=... │ st_size=...│ │ │ └──────────────┴──────────────┴──────────────┴──────────┘ │ └─────────────────────────────────────────────────────────────┘ 链接器要做的事:读取符号表第2个条目,知道它叫什么名字 步骤1: 看符号表 ┌─────────────────┐ │ st_name = 5 │ ← 拿到一个数字:5 └─────────────────┘ 步骤2: 拿着数字5,去字符串表 ┌─────────────────────────────────────────┐ │ 字符串表
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/29 10:04:00

Apache Flink实时计算框架原理与大数据实时业务实战

随着大数据业务的快速发展&#xff0c;传统离线批量计算模式无法满足实时数据统计、实时监控、实时预警的业务需求&#xff0c;Apache Flink作为一款高性能的分布式实时流计算框架&#xff0c;主打低延迟、高吞吐、 Exactly-Once精准数据一致性&#xff0c;成为目前大数据实时计…

作者头像 李华
网站建设 2026/5/29 10:01:27

ncmdumpGUI终极指南:如何轻松解锁网易云音乐NCM加密文件

ncmdumpGUI终极指南&#xff1a;如何轻松解锁网易云音乐NCM加密文件 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换&#xff0c;Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 还在为网易云音乐的NCM格式文件无法在其他…

作者头像 李华
网站建设 2026/5/29 10:01:23

7nm芯片后端实战:Innovus vs ICC2,我的踩坑记录与避坑指南

7nm芯片后端实战&#xff1a;Innovus与ICC2的深度避坑手册去年接手第一个7nm项目时&#xff0c;我对着屏幕上密密麻麻的DRC报错几乎崩溃。当工艺节点推进到7nm&#xff0c;传统28nm时代"能用就行"的粗暴玩法彻底失效——这里每平方微米都藏着物理规则与工具特性的双重…

作者头像 李华