Linux ELF 详解:从定义到底层原理

库制作与原理 [ ELF ] 其实我们不需要害怕,我们用到的ELF格式就是一个大文件,比如说这个文件一共是10兆,这个可执行程序10兆的话,所谓的section1,section2,...,sectionN,他本质上只需要告诉我,我这个sectionM相比较于整个文件的开头,我的偏移量大概在什么位置,再加上我的高度,那么此时,我们就能够在文件部分用起始的偏移量+sectionM的长度,就能够将一个section表示出来。是操作系统加载程序时的概念,代表了执行视角下程序在内存中的不同区域,如可执行代码段、可读写数据段、只读数据段等。 阅读详情

ELF(Executable and Linkable Format)是 Linux/Unix-like 系统的标准二进制文件格式,统一了可执行文件、共享库、目标文件、核心转储文件等多种类型的二进制文件规范。它替代了早期的 a.out 格式,具有跨架构兼容性、灵活的链接机制、支持动态加载等核心优势,是理解 Linux 程序运行和系统底层的关键基础。

一、ELF 的核心定位与作用

1. 核心目标

  • 统一二进制文件标准:解决不同类型二进制文件(目标文件、可执行文件、共享库等)的格式碎片化问题,让链接器(ld)、加载器(execve 系统调用)、调试器(gdb)等工具能统一处理。
  • 支持多场景需求:同时满足「编译链接阶段」和「运行加载阶段」的需求:
    • 链接时:作为目标文件(.o),支持符号解析、重定位、节(Section)合并。
    • 运行时:作为可执行文件(.out/.bin)或共享库(.so),支持内存加载、段(Segment)映射、动态链接。

2. 与其他格式的对比

格式适用场景优势劣势
ELFLinux/Unix-like 主流跨架构、支持动态链接、结构灵活复杂度高于 a.out
a.out早期 Unix/Linux结构简单、解析快速不支持动态链接、扩展性差
PE/COFFWindows 系统适配 Windows 内核与 Linux 不兼容

二、ELF 的文件结构(核心重点)

ELF 文件的结构遵循「头部 + 表 + 数据区域」的分层设计,所有结构均以 字节对齐 方式存储(避免内存访问异常)。核心结构可分为 4 个部分,用一张图直观展示:

+----------------------------+
| ELF Header (文件头部)       |  # 全局元信息,工具入口
+----------------------------+
| Program Header Table       |  # 段表:指导加载器映射内存(仅可执行/共享库有)
+----------------------------+
| 数据区域(Segments/Sections) |  # 实际内容:代码、数据、符号表等
|  - .text(代码段)          |
|  - .data(已初始化数据)     |
|  - .bss(未初始化数据)      |
|  - .symtab(符号表)        |
|  - ...(其他节/段)          |
+----------------------------+
| Section Header Table       |  # 节表:指导链接器合并/解析(仅目标文件/可执行文件有)
+----------------------------+

1. 核心结构详解

(1)ELF Header(文件头部)

ELF 文件的「总入口」,存储文件的全局元信息,工具(如 readelf、加载器)首先读取此部分获取文件的基本属性。

关键字段(32 位 / 64 位结构类似,仅字段长度不同):

字段名含义示例值
e_ident标识信息:魔数、字节序、架构、ELF 版本魔数 0x7f454c46("ELF")
e_type文件类型1 = 可重定位文件(.o)、2 = 可执行文件、3 = 共享库(.so)
e_machine目标架构3=IA-32、62=x86-64、40=ARM
e_entry程序入口地址(运行时 PC 起始值)0x400430(x86-64 可执行文件)
e_phoff程序头表在文件中的偏移量0x40(64 字节)
e_shoff节头表在文件中的偏移量0x1000(4096 字节)
e_phnum程序头表项数量9(x86-64 可执行文件常见)
e_shnum节头表项数量30+(根据文件复杂度变化)
e_shstrndx节名表(.shstrtab)在节头表中的索引29(指向存储节名的节)

作用:加载器 / 链接器通过 e_ident 验证文件合法性,通过 e_type 确定文件用途,通过 e_phoff/e_shoff 定位程序头表 / 节头表。

(2)Program Header Table(程序头表,段表)

仅存在于 可执行文件(.out) 和 共享库(.so) 中,描述「如何将文件加载到内存」。每个表项对应一个「段(Segment)」,段是内存加载的最小单位(由多个功能相关的「节(Section)」合并而成)。

关键字段(Elf64_Phdr 结构):

字段名含义示例值
p_type段类型PT_LOAD(可加载段)、PT_DYNAMIC(动态链接信息)
p_offset段在文件中的偏移量0x0(第一个 PT_LOAD 段)
p_vaddr段加载到内存的虚拟地址0x400000(x86-64 代码段地址)
p_paddr物理地址(嵌入式系统用,Linux 忽略)0x0
p_filesz段在文件中的大小(压缩 / 实际存储大小)0x1000(4096 字节)
p_memsz段在内存中的大小(可能包含未初始化数据)0x2000(8192 字节)
p_flags段权限PF_R(读)、PF_X(执行)、PF_W(写)
p_align内存对齐要求(必须是页大小的整数倍)0x1000(4KB,Linux 页大小)

核心段类型:

  • PT_LOAD:可加载段(最核心),分为两类:
    • 代码段(权限 R-X):包含 .text(机器指令)、.rodata(只读数据,如字符串常量)。
    • 数据段(权限 R-W):包含 .data(已初始化全局变量 / 静态变量)、.bss(未初始化全局变量 / 静态变量,文件中不占空间,内存中分配零初始化)。
  • PT_DYNAMIC:动态链接信息段,存储共享库依赖、符号查找等信息(.so 和动态链接的可执行文件有)。
  • PT_INTERP:解释器路径段,存储动态链接器路径(如 /lib64/ld-linux-x86-64.so.2),动态链接的可执行文件必须有。

作用:加载器(execve 系统调用触发)根据 PT_LOAD 表项,将文件中的段映射到内存对应虚拟地址,设置权限后,跳转到 e_entry 入口地址执行程序。

(3)Section Header Table(节头表)

存在于 目标文件(.o)、可执行文件、共享库 中,描述「文件的节结构」,是链接器(ld)的核心依赖。每个表项对应一个「节(Section)」,节是链接时的最小单位(按功能划分的逻辑单元)。

关键字段(Elf64_Shdr 结构):

字段名含义示例值
sh_name节名(索引到 .shstrtab 节)11(对应 ".text")
sh_type节类型SHT_PROGBITS(程序数据)、SHT_SYMTAB(符号表)
sh_flags节属性SHF_EXECINSTR(可执行)、SHF_WRITE(可写)
sh_addr节在内存中的虚拟地址(仅可执行文件有)0x400430(.text 节地址)
sh_offset节在文件中的偏移量0x430(1072 字节)
sh_size节的大小0x120(288 字节)
sh_link关联节的索引(如符号表关联字符串表)28(.strtab 节索引)
sh_info附加信息(如重定位表关联的节索引)1(.text 节索引)
sh_addralign节的对齐要求(如 8 字节对齐)16(.text 节常见)

核心节类型(按功能分类):

节名类型作用
.textSHT_PROGBITS存储机器指令(代码),权限 R-X
.rodataSHT_PROGBITS只读数据(字符串常量、const 变量)
.dataSHT_PROGBITS已初始化全局变量 / 静态变量(非 const)
.bssSHT_NOBITS未初始化全局变量 / 静态变量(文件中不占空间,内存中零初始化)
.symtabSHT_SYMTAB符号表(函数名、变量名、地址映射)
.strtabSHT_STRTAB字符串表(存储符号名、节名的原始字符串)
.shstrtabSHT_STRTAB节名表(专门存储节名的字符串表)
.rel.textSHT_REL代码段重定位表(记录需要修正的符号地址)
.rel.dataSHT_REL数据段重定位表
.dynamicSHT_DYNAMIC动态链接信息(共享库依赖、符号查找表)
.pltSHT_PROGBITS过程链接表(动态链接时延迟绑定符号)
.gotSHT_PROGBITS全局偏移表(存储符号的实际内存地址)

作用:链接器通过节头表找到各个节,合并多个目标文件的 .text/.data 节,通过 .symtab 解析符号引用(如函数调用、变量访问),通过 .rel.text/.rel.data 完成重定位(修正符号的实际地址)。

(4)数据区域(Segments/Sections 内容)

ELF 文件的实际存储区域,包含代码、数据、符号表等具体内容:

  • 节(Section):链接时的逻辑划分(如 .text 是代码,.data 是数据)。
  • 段(Segment):加载时的物理划分(由多个节合并,如 .text + .rodata → 一个 R-X 权限的 PT_LOAD 段)。
  • 你的程序能跑起来,本质是:链接器把 .o 文件的节合并成段,内核把段映射到内存,CPU 执行段里的代码。

关键区别:

维度节(Section)段(Segment)
核心用途编译 / 链接 / 调试(逻辑组织)内存加载 / 运行(物理映射)
面向对象编译器(gcc)、链接器(ld)、调试器(gdb)内核加载器(execve)、动态链接器(ld-linux.so)
划分依据功能(代码、数据、符号表…)内存权限 + 加载方式(R-X、R-W…)
存在场景目标文件(.o)、可执行文件(.out)、共享库(.so)仅可执行文件(.out)、共享库(.so)
数量多(几十甚至上百个)少(几个到十几个)
关系多个节 → 合并成一个段一个段 ← 包含多个节

2. 不同类型 ELF 文件的结构差异

ELF 支持 4 种核心文件类型,结构上各有侧重:

文件类型后缀核心结构特点用途
可重定位文件.o有节头表,无程序头表(或 PT_LOAD 表项不完整)编译后的中间文件,用于链接生成可执行文件 / 共享库
可执行文件无后缀 /.out有程序头表(含 PT_LOAD)和节头表直接运行(./a.out)
共享目标文件.so有程序头表(含 PT_LOAD/PT_DYNAMIC)和节头表动态链接库(如 libc.so.6),被多个程序共享
核心转储文件.core包含程序崩溃时的内存镜像、寄存器状态调试崩溃原因(gdb ./a.out core)

三、ELF 的核心流程:链接与加载

1. 链接过程(生成可执行文件 / 共享库)

链接器(ld)将多个 .o 目标文件 + 静态库(.a)/ 共享库(.so)合并为可执行文件 / 共享库,核心步骤:

  1. 合并节:将所有输入文件的 .text 节合并为一个 .text 节,.data 节合并为一个 .data 节,以此类推。
  2. 符号解析:通过 .symtab 符号表,解析跨文件的符号引用(如 main 函数调用 printf,printf 是 libc.so 中的符号)。
  3. 重定位:修正符号的实际地址(目标文件中符号地址是相对偏移,链接后替换为内存中的绝对地址):
    • 示例:目标文件中 call printf 的地址是 0x00000000(占位),链接后替换为 printf 在 libc.so 中的实际地址(或 .plt 表项地址,动态链接时)。
  4. 生成输出文件:
    • 若生成可执行文件:添加程序头表(PT_LOAD/PT_INTERP 等),设置 e_entry 入口地址。
    • 若生成共享库:添加 PT_DYNAMIC 段,支持动态链接(延迟绑定、符号重定位)。

2. 加载过程(运行可执行文件 / 共享库)

加载器(Linux 内核的 execve 系统调用 + 动态链接器 ld-linux.so)将 ELF 文件加载到内存并运行,核心步骤:

(1)静态链接可执行文件的加载

  1. 内核解析 ELF Header,验证文件合法性(魔数、架构匹配)。
  2. 读取程序头表,找到所有 PT_LOAD 段,将其映射到内存的 p_vaddr 虚拟地址(遵循页对齐)。
  3. 初始化 .bss 段(分配内存并清零,因为文件中不存储 .bss 内容)。
  4. 设置程序计数器(PC)为 e_entry 入口地址,跳转到用户态执行代码。

(2)动态链接可执行文件的加载

  1. 内核加载可执行文件的 PT_LOAD 段到内存。
  2. 读取 PT_INTERP 段,找到动态链接器路径(如 /lib64/ld-linux-x86-64.so.2),加载动态链接器到内存。
  3. 动态链接器解析可执行文件的 .dynamic 段,找到依赖的共享库(如 libc.so.6),加载共享库到内存。
  4. 动态链接器通过 .plt(过程链接表)和 .got(全局偏移表)完成延迟绑定:
    • 首次调用共享库函数(如 printf)时,.plt 表项触发动态链接器查找 printf 的实际地址,存入 .got。
    • 后续调用直接从 .got 读取地址,避免重复查找(提升性能)。
  5. 动态链接器修正所有符号的地址后,跳转到 e_entry 入口地址执行程序。

四、ELF 工具实战:查看与分析 ELF 文件

Linux 提供多个工具用于分析 ELF 文件,核心工具如下:

1. readelf:全面解析 ELF 结构

最强大的 ELF 分析工具,支持查看所有头部、表项、节 / 段信息。 

常用命令:

# 1. 查看 ELF Header(文件头部)
readelf -h a.out

# 2. 查看程序头表(段表)
readelf -l a.out  # -l = --program-headers

# 3. 查看节头表(节表)
readelf -S a.out  # -S = --section-headers

# 4. 查看符号表
readelf -s a.out  # -s = --symbols(.symtab 节)
readelf -s --dyn-syms a.out  # 查看动态符号表(.dynsym 节,共享库相关)

# 5. 查看动态链接信息(.dynamic 节)
readelf -d a.out  # -d = --dynamic

# 6. 查看重定位表
readelf -r a.out  # -r = --relocs(.rel.text/.rel.data 节)

示例输出(ELF Header):

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400430  # 程序入口地址
  Start of program headers:          64 (bytes into file)
  Start of section headers:          4096 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 29

2. objdump:反汇编与详细分析

侧重反汇编代码、查看节内容,适合调试和理解程序执行逻辑:

# 1. 反汇编 .text 节(查看机器指令)
objdump -d a.out  # -d = --disassemble

# 2. 查看所有节的内容(十六进制 + ASCII)
objdump -s a.out  # -s = --full-contents

# 3. 查看动态链接信息(类似 readelf -d)
objdump -p a.out  # -p = --private-headers

# 4. 查看重定位信息(类似 readelf -r)
objdump -r a.out  # -r = --reloc

3. nm:查看符号表

快速查看 ELF 文件的符号(函数名、变量名)及其类型:

nm a.out  # 查看所有符号
nm -D a.out  # 查看动态符号(共享库相关)
nm -u a.out  # 查看未定义符号(需链接其他文件的符号)

符号类型含义:

  • T:全局函数(.text 节)。
  • D:已初始化全局变量(.data 节)。
  • B:未初始化全局变量(.bss 节)。
  • U:未定义符号(如 printf,需要从共享库中查找)。
  • R:只读数据(.rodata 节)。

五、ELF 的核心优势

  1. 跨架构兼容性:支持 x86、ARM、RISC-V 等多种架构,通过 e_machine 字段标识目标架构,工具可自动适配。
  2. 灵活的链接机制:同时支持静态链接(合并静态库到可执行文件)和动态链接(运行时加载共享库,节省内存)。
  3. 延迟绑定(PLT/GOT):动态链接时,符号的地址绑定延迟到首次调用时,减少启动时间(仅加载必要的共享库符号)。
  4. 支持核心转储:程序崩溃时生成 .core 文件,包含完整的内存镜像和寄存器状态,便于调试。
  5. 扩展性强:可通过自定义节(如 .note 节存储版本信息)扩展功能,不破坏原有结构。

六、总结

ELF 是 Linux 系统底层的「二进制文件标准」,其核心价值在于统一链接和加载的接口:

  • 对开发者:编译(gcc)生成 .o 目标文件,链接(ld)生成可执行文件 / 共享库,无需关注底层格式细节。
  • 对系统:加载器(内核)和动态链接器(ld-linux.so)通过 ELF 头部和表结构,标准化地完成程序加载和运行。

理解 ELF 的结构和流程,是深入掌握 Linux 程序运行原理、动态链接、调试崩溃问题的关键基础。建议结合 readelf/objdump 工具实际分析一个简单的 C 程序(如 hello.c 编译后的 a.out),直观感受 ELF 的结构和字段含义。

七.  补充

程序执行状态:

程序状态虚拟地址情况物理地址情况内核核心操作
未执行(磁盘态)1. 仅存在「虚拟地址约定」(ELF 文件中的 p_vaddr、e_entry 等字段);2. 这些值是链接器写死的 “加载规则”,不是内存中的有效地址;3. 无进程虚拟地址空间(mm_struct 未创建),虚拟地址未 “生效”。1. 无任何物理地址分配;2. 无页表(虚拟→物理的映射表);3. 程序以二进制文件存储在磁盘,与物理内存无关联。无(内核未为该程序分配任何内存资源)。
执行中(内存态)1. 内核创建 mm_struct(进程虚拟地址空间),ELF 的 p_vaddr 映射为进程内的「合法虚拟地址」(如 0x400000 代码段);2. 虚拟地址是进程私有,每个进程独立(即使同程序,虚拟地址相同但隔离);3. 虚拟地址范围固定(x86-64 用户态 0~0x7FFFFFFFFFFF)。1. 「按需分配」:仅当进程访问虚拟地址时,内核才分配物理页并建立映射;2. 物理地址是全局唯一的硬件内存地址(如 0x12345678);3. 只读段(.text)可被多个进程共享物理内存(如动态库),可写段(.data)默认进程私有(COW 机制)。1. 创建 mm_struct 和 vm_area_struct(虚拟地址区域描述);2. 解析 ELF 段表,建立虚拟地址范围;3. 缺页异常触发物理页分配 + 页表更新;4. 动态链接时映射共享库虚拟地址。

动态库的物理内存副本 “不属于任何单个进程”,而是由内核全局管理;每个使用该动态库的进程,只是在自己的虚拟地址空间「共享区」建立了指向这份物理内存的映射

MMU和mm_struct:

构建虚拟地址到物理地址的映射关系,是内核(软件)+ MMU(硬件) 协同完成的核心工作,核心载体是「页表(Page Table)」,且遵循「先搭框架、按需填充」的原则(不会一次性构建所有映射)。

维度MMU(内存管理单元)mm_struct(进程地址空间描述符)
本质CPU 内置硬件组件内核软件数据结构
核心使命硬件层面完成虚拟→物理地址转换、权限校验软件层面管理进程虚拟地址空间、页表、内存资源
依赖关系依赖 mm_struct 关联的页表完成转换为 MMU 提供地址映射的 “规则”(页表)
触发交互缺页异常时通知内核(软件)处理内核通过它处理缺页异常、更新页表
生命周期随 CPU 硬件存在(开机即有)随进程创建而创建,进程退出而销毁
  • 每个进程(或线程)的task_struct中都有一个指针(mm)指向其内存描述符mm_struct。对于普通进程,每个进程都有自己独立的mm_struct。

  • 线程组(同一进程中的线程)共享同一个地址空间,因此它们共享同一个mm_struct。在task_struct中,线程的mm指针指向同一个mm_struct。

页表:

页表(Page Table)是 虚拟地址到物理地址映射关系的核心存储载体,本质是内核维护的「虚拟页号(VPN)→ 物理页号(PPN)」的映射表,配合 MMU 完成地址转换。它是虚拟内存机制的核心,解决了 “如何高效存储海量映射关系” 的问题 —— 不按单个字节映射,而是按「页(4KB 默认)」为最小单位映射,大幅降低映射表的内存开销。
 

动态库链接:

在Linux中,动态库(共享库)在内存中是以共享内存段的形式存在的,  所有依赖其的进程的共享区存放着指向其的引用.当一个动态库被加载到内存中时,它会被映射到进程的虚拟地址空间中的共享区域。多个进程可以共享同一个动态库在物理内存中的同一份代码段,每个进程的虚拟地址空间中都会有一个映射指向这个物理内存区域。

动态链接与静态链接不同,静态链接是在编译链接阶段就将所有库的代码和数据合并到可执行文件中,而动态链接则将链接过程推迟到程序加载时(或运行时)。

具体过程如下:

  1. 程序加载:当我们运行一个程序时,操作系统(实际上是动态链接器)会先将程序本身的代码和数据加载到内存中。然后,它会检查程序所依赖的动态库(共享库)。

  2. 动态库加载:操作系统会将程序依赖的动态库也加载到内存中。但是,这些动态库在内存中的加载地址并不是固定的(这与静态链接时确定的地址不同)。操作系统会根据当前地址空间的空闲情况,为每个动态库分配一段虚拟内存空间。

  3. 地址分配:由于动态库的加载地址在每次运行时可能都不相同(即使同一个库,在不同的运行过程中也可能被加载到不同的地址),因此需要在加载时确定库的具体地址。

  4. 修正引用:动态库中包含了需要跳转的函数地址(例如,库内部函数调用、库函数被外部调用等)。在加载动态库时,其加载地址确定后,动态链接器会修正这些跳转地址,使其指向正确的内存位置。这个过程称为重定位(Relocation)。

  5. 符号解析:动态链接器还会解析程序与库之间、库与库之间的符号引用,确保所有符号都能找到对应的地址。

  6. 延迟绑定:为了优化性能,动态链接通常采用延迟绑定(Lazy Binding)技术,即函数地址的解析推迟到第一次调用该函数时。这样,程序启动时不需要解析所有函数,加快启动速度。

GOT是什么?

GOT(全局偏移表)是一个存储在数据段中的表,其中每一项都是一个指针,指向一个全局符号(函数或变量)的地址。由于数据段在加载时可以被重定位,所以动态链接器可以通过修改GOT中的条目来更新这些符号的实际地址。

在程序开始运行前,动态链接器会解析出这些符号的绝对地址,然后填充GOT。这样,代码中通过GOT间接访问这些符号就可以得到正确的地址。

GOT如何工作?

我们以调用一个共享库中的函数为例:

在编译时,编译器并不知道这个函数的实际地址,所以它会在代码中生成一个对GOT条目的引用。这个GOT条目在编译时被预留,并在动态链接时由动态链接器填充。

例如,在x86_64架构上,使用位置无关代码,调用一个外部函数foo的代码可能看起来像这样:

call [rip + offset_to_GOT_entry_for_foo]

这里,offset_to_GOT_entry_for_foo是相对于当前指令指针(rip)的偏移,用于找到GOT中对应foo函数的条目。注意,GOT的地址在编译时是已知的(因为它是数据段的一部分,而数据段相对于代码段的偏移是固定的),但是GOT中条目的内容(即函数的实际地址)在编译时未知。

在程序加载时,动态链接器会计算foo函数的实际地址,并将其写入到GOT中对应的条目。因此,当上述指令执行时,它实际上是从GOT中取出foo的地址然后进行调用。

位置无关代码(PIC)详解

1. 什么是位置无关代码?

位置无关代码(Position-Independent Code, PIC) 是一种编译和链接技术,使得代码无论被加载到内存的哪个地址,都能够正确执行,而不需要修改代码段中的地址。PIC 是现代操作系统实现动态链接和地址空间布局随机化(ASLR)的基础。

2. 为什么需要 PIC?

在早期静态链接的程序中,代码和数据地址在编译链接时就已经确定。当程序加载到内存时,操作系统必须将其加载到固定的地址(即链接时指定的地址),否则程序无法运行。这带来了两个问题:

  • 内存浪费:每个进程都需要自己的一份代码副本,即使代码相同。

  • 安全问题:攻击者可以预测代码和数据的位置,从而容易进行攻击。

动态链接库(共享库)被设计为可以被多个进程共享,因此必须能够被加载到不同进程的地址空间的不同位置。此外,为了提高安全性,现代操作系统使用 ASLR 随机化进程地址空间,所以程序代码必须能够适应这种随机化。

3. PIC 的核心思想

PIC 的核心思想是:代码中不包含绝对地址,而是使用相对地址或通过间接寻址来访问数据和函数。这样,无论代码段被加载到哪里,只要相对偏移不变,代码就能正确运行。

PLT是什么?

PLT(Procedure Linkage Table,过程链接表)是动态链接中用于实现延迟绑定(lazy binding)的关键数据结构。它位于代码段,包含一小段代码,用于间接调用动态库中的函数。

当程序调用动态库中的函数时,编译器会生成一个对PLT条目的调用。每个PLT条目对应一个动态库函数,它包含跳转到GOT(Global Offset Table)中对应条目的代码。初始时,GOT中的条目指向PLT中的一段特殊代码,该代码会调用动态链接器来解析函数的真实地址,并将其写回GOT。之后,再次调用该函数时,PLT条目就可以直接跳转到GOT中存储的真实地址。

PLT的工作流程可以分为以下步骤:

  1. 程序调用一个动态库函数,例如printf,实际是调用了printf@plt。

  2. printf@plt的第一条指令跳转到GOT中对应的条目。初始时,这个GOT条目指向PLT中的下一条指令(即push指令)。

  3. 接着,程序将函数的编号(或标识符)压栈,然后跳转到PLT[0](这是一个特殊的PLT条目,位于所有PLT条目的开头)。

  4. PLT[0]会调用动态链接器(通过GOT[1]和GOT[2]的帮助)来解析函数的真实地址。

  5. 动态链接器找到函数的真实地址后,将其写回GOT中对应的条目,然后跳转到该地址执行函数。

  6. 以后再次调用该函数时,printf@plt的第一条指令就会直接跳转到GOT中存储的真实地址,无需再次解析。

这种机制被称为延迟绑定,因为函数地址的解析被推迟到第一次调用时,从而提高了程序的启动速度。

用python提取PDF中各类文本内容的方法 阅读详情

相关推荐

5种主流触觉传感器对比:GelSight、DIGIT、Taxim、GelSlim和TACTO如何选?

本文深度解析了GelSight、DIGIT、Taxim、GelSlim和TACTO五款主流触觉传感器的技术原理与核心特性。通过对比视觉传感与电学传感两大技术路径,为机器人灵巧抓取、精密检测等应用场景提供清晰的选型决策指南,帮助开发者根据项目需求在分辨率、响应速度、集成度与成本之间找到最佳平衡。

Black的专栏 930

linux elf 格式详解

linux elf格式与程序的链接和加载过程

shenhuxi_yu的专栏 2470

STK传感器配置全攻略:从光学到雷达的7种视场类型详解(附避坑指南)

本文详细解析了STK软件中传感器的7种视场类型,从基础的Simple Conic、Complex Conic到高级的SAR与EOIR,提供了完整的配置步骤与实战避坑指南。重点阐述了雷达、光学等不同物理设备的建模选择逻辑,并针对常见的“No Access”等报错问题给出了系统性的排查方案,帮助工程师高效完成卫星系统设计与任务分析。

weixin_29232507的博客 228

Linux(类Unix)系统可执行程序ELF文件格式详解

类Linux系统下,ELF文件结构大致可分为ELF header、Seciotn header table、Program header table,即将数据划分为多个Section存入ELF文件,然后通过Section header table进行索引,在ELF header中记录Section header table的位置和大小

ctbinzi的专栏 2049

Linux CentOS7文件系统(/dev/var)的容量扩充

原因是之前安装docker后,再想安装Nessus,系统说home、var文件系统的内存都不够。

JHoanna的博客 3226

用Keil5给CH552G编程:手把手配置8051开发环境+LED闪烁实战

本文详细介绍了如何使用Keil5为CH552G单片机配置8051开发环境,并完成LED闪烁实战。从获取必要软件、安装WCH器件库到创建工程、编写程序及烧录,手把手指导开发者避开常见陷阱,特别适合从STC转向CH552G的开发者。

weixin_30733003的博客 539

Linux elf文件分析

elf文件是储存linux下可重定位文件(.o,.ko),可执行文件,共享目标文件(.so)的一种文件,elf文件格式分析。

qq_40227064的博客 3673

Kindle电子书DRM破解全攻略:从下载到转换的完整流程(含最新DeDRM插件配置)

本文详细介绍了如何合法移除个人购买的Kindle电子书的DRM保护,实现跨设备阅读与备份。核心内容包括使用最新DeDRM插件(如noDRM分支)配合Calibre进行配置,并针对亚马逊最新的KFX加密格式提供了密钥提取与实战操作指南。文章强调该方法仅适用于个人合理使用,旨在帮助读者真正掌控自己的数字资产。

delta的博客 576

Linux_ELF文件

是Unix/Linux系统下的标准可执行文件、目标文件和共享库格式。是一种文件格式的名称。

w8848w的博客 1412

基于IC动态加权的机器学习多因子选股策略:从模型融合到实战回测

在量化投资领域,多因子选股模型通过挖掘市场数据中的有效信号来预测股票收益。其核心原理在于利用统计学与机器学习算法,从海量因子中识别出与未来收益具有稳定相关性的特征。这一技术的核心价值在于将主观投资决策转化为系统化、可重复的客观过程,从而在控制风险的前提下追求超额收益。其典型应用场景包括股票阿尔法策略构建、Smart Beta产品设计以及资产配置中的股票筛选环节。本文聚焦于如何通过信息系数(IC)这一关键指标,动态融合Ridge回归、MLP神经网络和随机森林等多个预测模型,构建一个能够自适应市场风格轮动的选股

weixin_34216614的博客 451

C2000 ePWM与SDFM同步配置:从寄存器到Driverlib实战解析

在嵌入式实时控制系统中,脉宽调制(PWM)是实现电机驱动、数字电源等应用的核心技术,其信号质量和时序精度直接决定了系统性能。为实现精确控制,PWM信号常需与模数转换器(ADC)或Sigma-Delta滤波器(SDFM)的采样时刻严格同步,以消除开关噪声并获取准确的反馈信号。其技术原理在于通过硬件同步机制,将PWM的特定时间点(如周期中心)作为触发源,控制数据采集的启动时刻。这种硬件级同步能有效提升闭环控制的稳定性和动态响应速度,广泛应用于伺服驱动、变频器等高性能场景。本文聚焦于TI C2000系列微控制器,

weixin_33692284的博客 443

【Linux】—elf文件的加载

但是内存信息的排布,并不是唯一的,其内容地址可变。Section Header Table 描述了所有节信息表,而 Program Header Table 其实描述的是所有的 段信息表。此时,在ELF文件头中就会有一项PT_INTERP,记录了程序的解释器路径。load_elf_binary,主要负责对可执行文件中elf文件读取,并提前做好相关的内存布局。找到段信息中为栈类型的,根据其标志,来调整相关标志变量。对进行的段、堆、代码段、数据段地址进行设置。根据loc中信息,确定解释器的类型。

王磊明的博客 1580

rk3568 Android12 增加 USB HOST 模式开关(二)

将原有 USB-C 改为 USB-A,并在设置中增加USB工作模式开关可以让用户选择USB端口的功能模式,例如充电、文件传输、或MIDI设备。用户能够灵活管理设备的连接方式,优化数据传输和设备兼容性。

weixin_35723192的博客 734

从 ELF 格式到动静态链接的底层原理

本文深入解析Linux中ELF文件格式与链接加载机制。首先介绍ELF文件结构,包括ELF头、程序头表、节头表和各类节区,阐述编译链接过程中目标文件的合并与重定位原理。静态链接通过地址修正将多个.o文件合并为可执行文件,而动态链接则利用GOT/PLT机制实现运行时加载和延迟绑定。文章还详细分析了进程地址空间的构建过程,说明虚拟地址到物理地址的映射原理。通过剖析静态/动态链接的核心差异,揭示了Linux程序从编译到运行的全过程技术实现。

2401_82551617的博客 1526

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【Linux之旅】Linux 动静态库与 ELF 加载全解析:从制作到底层原理

在 Linux 开发中,“库” 是绕不开的核心概念 —— 它是封装好的可复用代码,让开发者无需从零实现基础功能,大幅提升开发效率。但你是否好奇:静态库(.a)和动态库(.so)有何区别?动态库为什么会出现 “找不到” 的报错?可执行程序是如何加载库并运行的?ELF 文件又藏着怎样的底层秘密?

2302_80243065的博客 2084

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Linux系统:ELF文件的定义与加载以及动静态链接

ELF(Executable and Linkable Format)是类Unix系统中的标准二进制文件格式,用于存储可执行程序、目标文件和共享库。其结构包括ELF头、程序头表、节头表和数据区域,分别描述文件属性、内存布局、逻辑划分和实际内容。ELF文件的形成经历编译、汇编和链接阶段,链接器负责合并节、分配虚拟地址并修正符号引用。程序加载时,内核解析程序头表,通过mmap将段映射到进程虚拟地址空间,并利用缺页异常机制动态分配物理内存。动态链接通过地址无关代码(PIC)和全局偏移表(GOT)实现库的灵活加

yue_2899799318的博客 1873

【Linux】ELF 是什么?一文拆解 Linux 程序 “从零件到运行” 全流程

摘要: 本文详解Linux的ELF文件格式,从静态库编译到内存加载的全过程。静态库经链接器合并为ELF文件,其核心结构包括Section(编译视图)和Segment(执行视图)。Section按属性合并为Segment以优化内存管理,减少碎片。BSS段通过“零填充”节省空间。ELF头统领全局,Program Header Table指导加载。通过readelf命令可查看文件结构,理解操作系统如何将程序映射到内存执行。

欢迎来到我的博客,私信我可以一起交流技术 3007

ELF文件详解

ELF文件详解ELF

平凡者的专栏 3338

ELF 格式详解

ELF 全称 “Executable and Linkable Format”,即可执行可链接文件格式,目前常见的Linux、 Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件(吐核)均为此格式。文件布局 常见的ELF文件大致结构如下: 来自:http://chuquan.me/2018/05/21/elf-introduce/ 前文结尾说到编译器编...

随便写些什么 4514

linux可执行程序工作原理(ELF目标文件)

o.alda.outexec.solibc.soELF的作用:ELF文件参与程序的链接(构建一个可执行程序)和程序的执行(加载可执行程序),所以可以从不同的角度来看待ELF文件。如果用于编译和链接,则编译器和链接器将把ELF文件看作节的集合,所有节由节头表描述,程序头表可选。如果用于加载、执行可执行程序,则加载器将把ELF文件看作程序头表描述的段的集合,一个段可以包含多个可选的节和节头表。如果是动态链接库文件,则两者都有。

weixin_48175016的博客 1718

Linux程序构建核心:ELF文件编译、链接与加载机制详解

本文深入解析了ELF文件从编译到加载的全过程。首先介绍了编译阶段将源代码转换为.o目标文件,然后通过链接器合并目标文件生成可执行文件。重点探讨了ELF文件的双重视图结构:链接视图以节(Section)组织供链接器使用,执行视图以段(Segment)组织供加载器使用。通过readelf工具可以查看文件头、节头和程序头表信息。文章详细分析了为何需要将节合并为段,主要是为了优化内存利用和权限管理。最后比较了目标文件和可执行文件的关键差异,包括文件类型、入口地址和头部信息等,揭示了ELF格式设计的精妙之处。

祯的博客 1305

【Linux学习笔记】深入理解ELF和动静态库加载原理

本文深入解析了ELF文件的结构和加载原理。ELF文件可分为四种类型:可重定位文件、可执行文件、共享目标文件和内核转储文件。ELF由四部分组成:ELF头、程序头表、节头表和节。重点介绍了ELF从编译链接到加载执行的完整过程,包括.section合并为Segment的原则,以及如何通过程序头表确定加载方式。文章还提供了查看ELF文件结构的实用命令(readelf -S/-l),帮助开发者理解可执行程序的内存布局。通过对ELF文件加载机制的剖析,读者可以更深入地掌握Linux程序运行的底层原理。

大白的编程日记. 1332

【Linux】库制作与原理 从生成使用到 ELF 文件与链接原理解析

本文详细讲解 Linux 环境下动静态库的核心概念(静态库.a/.lib、动态库.so/.dll)、生成步骤(静态库用 ar 打包、动态库用 gcc -fPIC -shared)与使用方法(解决库路径依赖的 4 种方案),并深入剖析 ELF 文件结构(ELF Header、节头表、程序头表)及动静态链接原理(静态链接的地址重定位、动态链接的 GOT/PLT 延迟绑定),帮助开发者理解库的底层逻辑与实操要点。

2501_90265152的博客 1595

linux中ELF加载过程分析

 sys_execve   | - do_execve|| - search_binary_handler                     |- linux_binfmt= elf_format       |- elf_format-> load_elf_binary| - elf_entry = load_elf_interp()          |-

博青港湾-技术空间 6465

深入了解 ELF 文件格式:Linux 的可执行文件标准

ELF(Executable and Linkable Format)是一种灵活且标准化的文件格式,广泛用于 Unix 系统及其类系统中,如 Linux。它的设计既支持静态链接和动态链接,又能方便地扩展和维护,因此被用作可执行文件、共享库和目标文件的存储格式。可执行文件目标文件共享库无论是哪种类型,ELF 文件都包括代码、数据、符号表、重定位信息等。操作系统加载和执行 ELF 文件时,会通过特定的结构和信息来正确解析和映射文件。

u014358031的博客 3033

Linux库的原理与ELF文件

4.调⽤ __libc_start_main :⼀旦动态链接完成, _start 函数会调⽤ __libc_start_main (这是glibc提供的⼀个函数)。其它步骤和静态文件加载相同,而动态链接器打开动态库文件后将其映射到进程的共享区,共享区对应的vm_area_struct中有动态库文件的file指针,由此建立连接。5.调⽤ main 函数:最后, __libc_start_main 函数会调⽤程序的 main 函数,此时程序的执⾏控制权才正式交给⽤⼾编写的代码。• 库也会调⽤其他库!

2401_89044302的博客 606

Linux系统篇 21:文件(五)——动静态库、ELF 底层原理全解

库(Library)是写好的、现有的、成熟的、可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说,库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。编译链接时,链接器会把静态库中被调用的目标代码完整拷贝进可执行文件。程序编译完成后,删除.a文件程序仍能独立运行。一个可执行程序可能会用到许多库,这些库运行时有的是静态库,有的是动态库。我们的编译默认认为动态链接库,只有在该库下找不到动态.so的时候才会采用同名静态库。

2502_94387000的博客 841
上一篇: C++ STL库map,unordered_map,multimap,unordered_multimap详解
下一篇: 页表(Page Table)深度解析:虚拟地址映射的核心字典
Howrun777
博客等级 码龄1年 133粉丝 100原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值