资讯动态

Linux 学习笔记(二十五):深入 ELF 格式与链接加载底层原理

发布时间:2026/10/26 10:18:54 来源:尧图企业网站定制
一、 程序的“标准集装箱”ELF在 Linux 下目标文件.o、可执行程序、动态库.so统一采用ELFExecutable and Linkable Format格式。ELF 内部被划分为多个“节Section”.text存放机器指令代码。.data存放已初始化的全局变量。.rodata存放只读数据如字符串常量。二、 静态链接的本质地址重定位当我们只编译不链接时gcc -c生成.o文件。此时如果代码调用了外部函数编译器是不知道它在哪里的。用readelf -s查看符号表会发现该外部函数的标记为UND未定义。用objdump -d反汇编会发现call跳转的地址暂时填成了0。链接器Linker的作用就是将多个.o文件及库拼装在一起并根据符号表找到函数的真实地址将.o里的0修正为真实地址这个过程叫做重定位。三、 动态链接的核心GOT 与 PLT动态库的加载地址每次运行都会改变地址随机化。为了解决这个问题引入了位置无关代码PIC与全局偏移表GOT。GOT 表在程序的.data节可写预留一个区域专门存放外部函数的真实地址。代码调用时先去 GOT 表查地址再跳转。PLT延迟绑定为了加快启动速度程序刚启动时并不马上查找所有函数的真实地址。第一次调用某函数时会跳转到 PLT 桩代码此时才去查找真实地址并更新到 GOT 表中。后续再调用该函数直接通过 GOT 表跳转极大提高了效率。总结从源码到可执行程序经历了预处理、编译、汇编和链接四大步。静态链接通过“填坑”完成重定位而动态链接通过 GOT 表实现了代码的共享与延迟绑定。理解了这些排查诸如“符号未定义”或“动态库找不到”等问题将易如反掌。自我实战操作cd ~/linux_study/lib_test实战 1解剖目标文件.o的未定义符号# 1. 准备两个源文件 vim hello.c#include stdio.h void run(); // 声明外部函数 int main() { printf(hello world!\n); run(); return 0; }vim code.c#include stdio.h void run() { printf(running...\n); }# 2. 只编译不链接 gcc -c hello.c -o hello.o gcc -c code.c -o code.o # 3. 用 readelf 查看 hello.o 的符号表 readelf -s hello.o高能现象你在输出里找一个叫run的符号你会发现它的Ndx列写着UND未定义并且它的Value地址是0。这就证明了在编译阶段它找不到run在哪。实战 2查看反汇编中的“填坑”# 反汇编 hello.o objdump -d hello.o高能现象你会看到call指令后面跟着的地址全是00000000或者类似e8 00 00 00 00。这就是编译器留下的“坑”等着链接器去填。实战 3链接之后坑被填上了# 1. 把它俩链接起来 gcc hello.o code.o -o main.exe # 2. 再次反汇编可执行文件 objdump -d main.exe | grep -A 5 main:高能现象这一次你会发现call后面不再是0了而是变成了call 1149 run。填坑完毕实战 4看穿动态链接的 GOT 表# 查看 ELF 头找入口地址 readelf -h main.exe | grep Entry # 查看 main.exe 依赖的动态库复习前面的知识 ldd main.exe

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑