一、外部库的使用及应用
我们使用的库,是我们自己写的,那我们能不能真正用一下别人的库?
答:当然可以。我们也可以使用外部库。
安装一个ncurses库

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

此时我们需要说明一下:

会形成如下的画面:

二、目标文件
目标文件,可重定位目标文件,.o文件,是源代码编译后,链接前的中间产物,它是二进制格式。
ELF的引入
可执行程序是一个二进制文件,但不是随意放在一起的,它有自己的结构和格式,他的格式就叫做ELF格式。
三、ELF是什么
1、ELF定义
ELF = Executable and Linkable Format ,中文叫 可执行与可链接格式。
Linux / Unix 系统里,可执行程序、库文件、目标文件的 "标准文件格式"。
Windows 里的可执行文件是 .exe, Linux 里的可执行文件就是 ELF
它不是命令,不是代码,而是文件类型。
在 Linux 里,下面这些全都是 ELF 格式:
- 可执行程序(你直接运行的命令、工具)
- 动态库
.so(shared object) - 静态库
.a - 目标文件
.o(编译后未链接的文件) - 内核镜像、驱动 也常用 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 在文件里的偏移和大小 → 就能定位每个节的地址。
- ELF Header 里关键的 3 个字段

e_shoff:直接告诉你 Section Header Table 从文件第几个字节开始。e_shentsize:每个节头大小(固定)。e_shnum:有多少个节。
- Section Header 里关键的 2 个字段
sh_offset:这个 section 本身在文件里的地址(偏移)。sh_size:占多大。
完整链路
- 读文件开头 → 得到 ELF Header
- 从
e_shoff找到 Section Header Table - 遍历每个 section header
- 从每个
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 是拿着地图找路的硬件
可以理解为:
先记住两个身份
-
CR3:只是一个 "地址" 它里面存的是:页表放在内存的哪个位置 → 你可以理解成:页表的门牌号
-
MMU:是硬件干活的小弟 它不会写 CR3,它只做一件事:
拿着虚拟地址,去查 CR3 指向的页表,算出物理地址。

