Linux---ELF与库加载

一、外部库的使用及应用

我们使用的库,是我们自己写的,那我们能不能真正用一下别人的库?

答:当然可以。我们也可以使用外部库。

安装一个ncurses库

进行编译时,它会报错,不认识里面的代码,

此时我们需要说明一下:

会形成如下的画面:

二、目标文件

目标文件,可重定位目标文件,.o文件,是源代码编译后,链接前的中间产物,它是二进制格式。

ELF的引入

可执行程序是一个二进制文件,但不是随意放在一起的,它有自己的结构和格式,他的格式就叫做ELF格式。

三、ELF是什么

1、ELF定义

ELF = Executable and Linkable Format ,中文叫 可执行与可链接格式

Linux / Unix 系统里,可执行程序、库文件、目标文件的 "标准文件格式"

Windows 里的可执行文件是 .exe, Linux 里的可执行文件就是 ELF

它不是命令,不是代码,而是文件类型

在 Linux 里,下面这些全都是 ELF 格式

  1. 可执行程序(你直接运行的命令、工具)
  2. 动态库 .so(shared object)
  3. 静态库 .a
  4. 目标文件 .o(编译后未链接的文件)
  5. 内核镜像、驱动 也常用 ELF

为什么.o 可以形成.so库文件,或者可执行程序?

因为.o ,.so,可执行程序都是遵循ELF格式的,结构高度一致,所以可以把格式中相同的部分进行合并,形成更大的ELF。

2、ELF结构

  • **ELF 头部(ELF Header)**位于文件起始位置,描述整个 ELF 文件的基本属性,包括文件类型、目标架构、版本信息、程序入口地址、程序头部表偏移、节头部表偏移等,是解析 ELF 文件的基础。

  • **程序头部表(Program Header Table)**用于程序加载执行阶段,描述各段(Segment)在文件与内存中的布局、大小、权限和对齐方式,操作系统依靠它将程序加载到内存并运行。

  • **节头部表(Section Header Table)**用于编译、链接与调试,描述文件中所有节(Section)的元数据,包括节的名称、类型、位置、大小、标志等,链接器与调试器通过它定位各个节。

  • **节(Sections)**ELF 文件的实际数据存储区域,包含代码、数据、符号、字符串等信息。常见节包括:.text(代码节)、.data(数据节)、.rodata(只读数据节)、.symtab(符号表节)、.strtab(字符串表节)。

查看ELF Header

查看ELF Section Header Table

输入以下指令,查看

知道 ELF Header → 能找到 Section Header Table → 能知道每个 section 在文件里的偏移和大小 → 就能定位每个节的地址。

  1. ELF Header 里关键的 3 个字段
  • e_shoff:直接告诉你 Section Header Table 从文件第几个字节开始。
  • e_shentsize:每个节头大小(固定)。
  • e_shnum:有多少个节。
  1. Section Header 里关键的 2 个字段
  • sh_offset:这个 section 本身在文件里的地址(偏移)。
  • sh_size:占多大。

完整链路

  1. 读文件开头 → 得到 ELF Header
  2. e_shoff 找到 Section Header Table
  3. 遍历每个 section header
  4. 从每个 sh_offset + sh_size直接定位该 section 在文件中的位置

3、节和段

ELF没有一个一个的段,ELF是存在一个一个的节的,不同的节,大小不一,权限可以有相同的,ELF是文件,内容4kb,OS读取磁盘文件的内容,4kb为单位导入到内存中;把多个具有相同权限的节,加载的时候,由加载器帮我们进行节合并为segment!!!

加载运行可执行程序时,要先观察Program Header Table,作用是告诉操作系统如何把文件内容映射到内存

4、可执行程序的加载

(1)创建一个进程,先创建内核进程相关的数据结构,再加载ELF格式的二进制文件

(2)程序没有被加载到内存,程序自己有没有地址?

答:程序也是有地址的。这些地址是虚拟地址 ,在编译链接阶段就已经被链接器分配并写入ELF文件的相关结构中,属于程序自身的属性,和是否被加载到内存中无关。

平坦模式对我们的可执行程序中的"每一行代码"都进行编址,原则上从0号地址开始;在32位系统下,0000...0000(32位) ----->1111...1111(32位)

虚拟地址空间:标准:

OS也遵守,编译器也遵守。

偏移量是:相对地址

段地址+偏移量=逻辑地址

程序从磁盘加载到内存中,是有物理地址的,没有加载,就没有物理地址,但是还是有虚拟地址的。

程序加载后,虚拟地址会被映射到物理地址,但程序本身"看到的"依然是虚拟地址,物理地址对进程是透明的。

虚拟地址空间:不仅要让OS参与,编译器也要参与这个过程。

可执行程序加载与地址转换流程

1、程序加载阶段(磁盘到内存)

虚拟地址早已存在:ELF文件中就包含了代码数据的虚拟地址,这是编译链接时就确定的

物理地址由操作系统分配:加载时,内核为进程创建task_struct(进程描述符),和mm_struct(内存描述符),并通过页表将虚拟地址映射到物理内存地址

程序头表(Program Headers):加载器依据它将ELF段映射到虚拟地址空间,确定各段的权限和加载位置。

2、cpu执行阶段(虚拟地址->物理地址)

cpu视角: CPU只看到虚拟地址(比如EIP寄存器里的0x100a),完全不知道物理地址**。**

地址转换核心:

1、CR3寄存器指向当前进程的页表基地址。

2、MMU(内存管理单元)通过页表,将虚拟地址翻译成物理地址。

3、最终访问物理内存中的指令/数据。

  • CR3 = 页表的 "入口地址"

  • MMU = 拿着 CR3 去查表的硬件

  • CR3 是地图的第一页在哪

  • MMU 是拿着地图找路的硬件

可以理解为:

先记住两个身份

  1. CR3:只是一个 "地址" 它里面存的是:页表放在内存的哪个位置 → 你可以理解成:页表的门牌号

  2. MMU:是硬件干活的小弟不会写 CR3,它只做一件事:

    拿着虚拟地址,去查 CR3 指向的页表,算出物理地址。

相关推荐
Shaidou_Data2 小时前
数据要素自动化实践:沙淘金数据清洗与治理技术方案详解
运维·自动化
一行12 小时前
旧电脑重生:老i5装Ubuntu 保姆级步骤
服务器·windows·学习
pupudawang2 小时前
SQL-触发器(trigger)的详解以及代码演示
服务器·数据库·sql
秃头摸鱼侠2 小时前
OpenClaw + MCP 实战:从 0 搭建可复用自动化工作流
运维·自动化
IT研究所2 小时前
从工单到智能分析:AIGC运维助手应用价值
大数据·运维·数据库·人工智能·科技·低代码·自动化
西安小哥2 小时前
Linux操作系统运维命令大全
linux·运维·服务器
叁金Coder3 小时前
【Centos8 环境下 X86 版本 docker-29.1.3 的安装配置】
运维·docker·容器
秃头摸鱼侠3 小时前
OpenClaw 入门到实战:安装、配置、使用、升级与卸载(Windows/macOS/Linux)
linux·windows·macos