操作系统原理:链接与ELF文件

2024-03-31 21:08
文章标签 原理 操作系统 链接 elf

本文主要是介绍操作系统原理:链接与ELF文件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ELF文件

本文主要针对Linux系统。在x86架构下,Linux使用的是ELF(Executable and Linkable Format)目标文件格式。目标文件的三种格式:

  1. Relocatable object file. 可重定位目标文件包含二进制代码和数据,编译时可与其他可重定位目标文件合并组成可执行目标文件,如 .o,.a文件。
  2. Executable object file. 可执行目标文件包含二进制代码和数据,可以直接加载到内存并执行, 如 .out文件。
  3. Shared object file. 共享目标文件是一种特殊类型的可重定位目标文件,可以在加载或运行时动态的加载到内存并链接。如 .so文件。

Relocatable object file

编译器和汇编器生成可重定向(或共享)目标文件,链接器生成可执行目标文件。ELF header 以一个16字节的序列开始的,描述了生成改文件的系统的字的大小(word size)和字节序(byte ordering),剩下的ELF header则包含了一些让链接器去parse和interpret目标文件的信息,如ELF头的大小,目标文件类型(Relocatable,executalbe, or shared), 及其类型(eg., IA32), section header table的文件偏移,section header table中条目大小和数目。不同节的位置和大小是由节头部表描述的, 且每个节都有个固定大小的条目。一个典型的Relocatable object file如下图:
relocatable elf

其中各部分解释如下:

  • .text: 编译好的机器代码。
  • .rodata: read-only数据,比如常量字符串。
  • .data: 初始化过的全局变量。
  • .bss: (block storage start)未初始化的全局比那里, better save sapce :),仅仅是占位符,未初始化不需要占据实际磁盘空间。
  • .symtab: symbol table, 函数与全局变量。
  • .rel.text: 一个 .text 节中位置的列表,链接器把其和其他可重定位文件链接时,会修改这些位置。通常调用外部函数或引用外部全局变量需要修改,本地则不需要。Executable目标文件则不需要这些信息,一般不显示制定就缺省。
  • .rel.data: 被模块引用或定义的全局变量的重定位信息,比如初始化的全局变量初始值是外部的一个全局变量或函数的地址。
  • .debug: 调试符号表,包含了局部变量和类型信息,引用或定位的全局变量,还有原始的C文件,编译加 -g 选项才此表。
  • .line: 源代码行号和.text中机器码的映射关系,编译时加 -g 才有此信息。
  • .strtab: string table, 包含了 .symtab 和 .debug中的符号表,还有节头部的节名字,是以null结尾的字符串序列。

c语言是分离式编译,每个.c可以编程一个.o,是一个编译单元。在链接器上下文中,static符号不对外部模块可见。

符号表由汇编器生成,是一个数组,里面的条目由下面结构体所描述。name是字符串表中自己偏移。value是符号的地址,对于 executable 文件就是运行时绝对地址。type是函数或数据。binding表示是本地还是全局。链接器解析全局符号时,只允许有一个强符号(已经初始化的全局变量是强符号,反之弱符号)。

typedef struct {int name;       /* string table offset */int value;      /* section offset, or VM address */int size;       /* object size in bytes */char type:4,    /* data, func, section, or src file name (4 bits) */binding:4; /* local or global (4 bits) */char reserved;  /* unused */char section;   /* section header index, ABS, UNDEF, *//* or COMMON */
} Elf_Symbol;

链接器链接静态库.a时有一些注意点。符号解析阶段,链接器按照从左到右的顺序来扫描命令行上输入的.o.a文件。这些文件在命令行上的顺序要注意,库.a一般放到结尾。如果引用的库还不是相互独立的,那么要求有序放置,要有定义在引用之后,也即a依赖b,那么b放到a后面。还有一种方法是把相互依赖的链接库合并。链接器完成了符号解析后,最后要做的是重定位,合并输入模块,并且为每个符号分配运行时地址。重定位有两部:重定位节和符号定义;重定位节中的符号引用。

Executable Object Files

可执行目标程序结构类似于可重定位目标程序,没有了重定位信息.rel节。ELF header还包括了程序的entry point,即程序运行时执行的第一条指令地址。.init节定义了一个小函数_init,程序的初始化代码会调用它。在命令行启动一个可执行程序,通过调用execve内核系统调用调到操作系统loader代码,把代码和数据读到内存,跳转到第一条指令entry point来运行它,拷贝的过程叫加载(loading),最终会调用程序的main函数。一个ELF的结构图如下:
executable elf

程序被加载运行,必然是在一个进程上下文中,有自己的虚拟地址空间。父进程fork一个子进程,然后通过execve系统调用加载器代码删除子进程已存的虚拟存储段,加载新的代码数据与堆栈,新的堆栈段会被初始化为0。32位系统的进程虚拟地址空间如下图:
32bit_runtime_process

Dynamic Linking with Shared Libraries

共享库so在运行期加载到内存,并和一个程序链接起来,即dynamic linking, 是由dynamic linker动态链接器来执行的。编译动态链接库so,需要用到编译器gcc的-shared -fPIC参数。

加载时动态链接

创建可执行文件时静态执行一些链接,最终程序加载时动态完成链接过程。举例:
编译动态链接库:gcc -shared -fPIC -o libxxx.so a.c b.c
编译可执行文件:gcc -o a.out main.c ./libxxx.so
其中的-fPIC创建(Position-Independent Code, PIC), 位置无关代码不需要链接器修改就可以在任何地址加载和执行这些代码。在进程虚拟地址空间中,数据段总是在代码段之后,代码段中的指令地址和数据段中全局变量地址之间距离是一个运行时常量。正好PIC数据引用可以利用此点,编译器在数据段开始的地方创建了一个全局偏移量表(Global offset Table,GOT), 每个被目标模块引用的全局变量都有一个条目,编译时还为每个条目生成一个重定位记录。加载时,Dynamic Linker就会重定位每个条目,使其包含正确的运行时绝对地址。

运行时动态链接

程序在运行时完成动态链接,动态加载so,可以使用下面的API. 这种方法使用的更加广泛写。API可以在include目录下搜索root@ubuntu:/usr/include# grep dlopen -r *, 很容易找到。

/* Open the shared object FILE and map it in; return a handle that can bepassed to `dlsym' to get symbol values from it.  */
extern void *dlopen (const char *__file, int __mode) __THROWNL;/* Unmap and close a shared object opened by `dlopen'.The handle cannot be used again after calling `dlclose'.  */
extern int dlclose (void *__handle) __THROWNL __nonnull ((1));/* Find the run-time address in the shared object HANDLE refers toof the symbol called NAME.  */
extern void *dlsym (void *__restrict __handle,const char *__restrict __name) __THROW __nonnull ((2));

ELF常用处理命令

  • nm,列出符号表
  • readelf, 读ELF文件完整结构
  • objdump, 显示ELF所有信息,常用来反汇编.text中指令
  • ldd,列出executable object file运行时需要的动态库
  • strings,列出所有可打印的字符串
  • size, 列出目标文件中节的名字和大小
  • ar, 创建静态库,插入、删除、列出和提取成员

对于ELF这种二进制文件,同样可以用grep来搜里面的字符串,如grep -ao a.out可以搜里面的常量字符串和符号表,有时候也是很方便的。

参考

Computer Systems: A Programmer’s Perspective (3rd Edition)

这篇关于操作系统原理:链接与ELF文件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/865342

相关文章

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重

Spring @Scheduled注解及工作原理

《Spring@Scheduled注解及工作原理》Spring的@Scheduled注解用于标记定时任务,无需额外库,需配置@EnableScheduling,设置fixedRate、fixedDe... 目录1.@Scheduled注解定义2.配置 @Scheduled2.1 开启定时任务支持2.2 创建

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事