文章目录
- [0. 引言](#0. 引言)
- [1. 动态库和静态库](#1. 动态库和静态库)
- [2. ELF文件](#2. ELF文件)
- [3. 符号表和符号解析](#3. 符号表和符号解析)
- 4.静态库链接
- [5. ELF文件加载和进程地址空间加载过程](#5. ELF文件加载和进程地址空间加载过程)
0. 引言
链接(linking)是将各种代码和数据片段收集并整合成为一个单一文件的过程->这个文件可以被加载到内存中并执行。早期计算机系统中,链接是手动进行的;现代系统中,链接由链接器(linker)的程序自动执行。核心价值是实现分离编译------ 大型项目中,修改单个文件后仅需重新编译该文件,不需要全量重构,极大降低维护成本。
库是链接的核心载体,本质是 "预编译的可复用代码二进制形式",分为静态库(.a/.lib)和动态库(.so/.dll),二者核心区别在于链接时机:静态库编译时链接,动态库运行时链接。而这一切的底层支撑,是 ELF(Executable and Linkable Format)文件格式 ------ 可重定位文件(.o)、静态库、动态库、可执行文件均遵循 ELF 标准,是理解库与链接的关键。
1. 动态库和静态库
什么是库?
库是写好的现成的可以复用的代码
本质是一种可执行代码的二进制形式,可以被OS载入内存中执行
- 静态库:
.a Linux.lib Winodws- 动态库:
.so Linux.dll Windows- 核心区别在于 链接时机------ 静态库编译时链接,动态库运行时链接。
静态库:编译时合并复制,运行时独立无依赖
静态库的核心逻辑:编译链接阶段,从静态库中复制用户代码用到的.o文件,与用户.o 文件合并,生成独立可执行文件,运行时无需依赖原库。
生成静态库
我们提前准备好4个文件

ar -rc 静态库名(libxxx.a) 要打包的.o文件
ar -rc libmystdc.a *.o
rc表示的是replace and create
bash
[vect@VM-0-11-centos warehouse]$ ar -tv libmystdc.a
rw-rw-r-- 1002/1002 2880 Jan 10 11:46 2026 mystdio.o
rw-rw-r-- 1002/1002 1272 Jan 10 11:46 2026 mystlen.o
tv:列出静态库中详细信息
静态库的使用
安装到系统目录
把静态库和.h文件拷贝到系统目录/lib64和/usr/include/目录下:
bash
sudo cp mystrlen.h mystdio.h /usr/include/
sudo cp libmystdc.a /lib64/
然后运行:
bash
[vect@VM-0-11-centos warehouse]$ gcc main.c
/tmp/ccCk12We.o: In function `main':
main.c:(.text+0x18): undefined reference to `mystrlen'
main.c:(.text+0x3f): undefined reference to `mfopen'
main.c:(.text+0x5d): undefined reference to `mystrlen'
main.c:(.text+0x71): undefined reference to `mfwrite'
main.c:(.text+0x7d): undefined reference to `mfclose'
collect2: error: ld returned 1 exit status
为什么会报找不到的错误呢?这是链接错误
gcc默认只识别C标准库->需要指定第三方库
gcc -l库名
注意:
-
库名是去掉前缀和后缀的
-
使用
-l选项时要放到源文件后面
bash
[vect@VM-0-11-centos warehouse]$ gcc main.c -lmystdc
[vect@VM-0-11-centos warehouse]$ ls
a.out libmystdc.a main.c Makefile mystdio.c mystdio.h mystdio.o mystlen.c mystlen.o mystrlen.h
[vect@VM-0-11-centos warehouse]$ ./a.out
abc:3
头文件和库文件在同一路径下
-Lpath:指定库文件路径
bash
[vect@VM-0-11-centos warehouse]$ gcc main.c -L. -lmystdc
[vect@VM-0-11-centos warehouse]$ ls
a.out libmystdc.a log.txt main.c Makefile mystdio.c mystdio.h mystdio.o mystlen.c mystlen.o mystrlen.h
[vect@VM-0-11-centos warehouse]$ ./a.out
abc:3
头文件和库文件有自己的独立路径
-L库文件路径-I头文件路径
bash
[vect@VM-0-11-centos warehouse]$ tree other
other
|-- include
| |-- mystdio.h
| `-- mystrlen.h
`-- lib
`-- libmystdc.a
2 directories, 3 files
[vect@VM-0-11-centos warehouse]$ gcc -o main main.c -I other/include/ -L other/lib/ -lmystdc
[vect@VM-0-11-centos warehouse]$ ls
main main.c Makefile mystdio.c mystlen.c other
[vect@VM-0-11-centos warehouse]$ ./main
abc:3
总结:
gcc 目标文件 [-I头文件路径] [-L库文件路径] -l指定第三方库名- 静态库链接之后,可以执行文件独立运行,撒谎才能胡静态库不影响程序。缺点文件体积大,库更新需要重新编译
动态库:运行时共享引用,延迟绑定节省资源
动态库(.so):编译时仅记录库路径和符号引用,不复制代码;运行时由动态链接器加载库到内存,完成符号解析和重定位,多进程可共享同一库内存。
生成动态库
gcc -fPIC -c 依赖.c文件:-fPIC生成与位置无关码,确保动态库加载到任意内存地址都能运行(采用相对寻址方式)
gcc -shared -o目标.so文件 依赖.o文件:生成共享的.so文件
bash
[vect@VM-0-11-centos warehouse]$ gcc -fPIC -c mystdio.c mystlen.c
[vect@VM-0-11-centos warehouse]$ ls
main.c Makefile mystdio.c mystdio.o mystlen.c mystlen.o other
[vect@VM-0-11-centos warehouse]$ gcc -shared -o libmystdc.so *.o
[vect@VM-0-11-centos warehouse]$ ls
libmystdc.so main.c Makefile mystdio.c mystdio.o mystlen.c mystlen.o other
动态库的使用
安装到系统目录
bash
[vect@VM-0-11-centos warehouse]$ sudo cp *.h /usr/include
[sudo] password for vect:
[vect@VM-0-11-centos warehouse]$ sudo cp libmystdc.so /lib64/
[vect@VM-0-11-centos warehouse]$ gcc main.c -lmystdc
[vect@VM-0-11-centos warehouse]$ ls
a.out libmystdc.so main.c Makefile mystdio.c mystdio.h mystdio.o mystlen.c mystlen.o mystrlen.h other
[vect@VM-0-11-centos warehouse]$ ./a.out
abc:3
[vect@VM-0-11-centos warehouse]$ ldd a.out
linux-vdso.so.1 => (0x00007fff107a1000)
libmystdc.so (0x00007f28f3e01000)
libc.so.6 => /lib64/libc.so.6 (0x00007f28f3a33000)
/lib64/ld-linux-x86-64.so.2 (0x00007f28f4003000)
可以看到现在我们a.out程序依赖的是我们自己实现的库
头文件和库文件在源文件同一路径下
gcc main.c -L. -lmystd
头文件和库文件有自己独立的路径
bash
[vect@VM-0-11-centos warehouse]$ tree other/
other/
|-- include
| |-- mystdio.h
| `-- mystrlen.h
`-- lib
|-- libmystdc.a
`-- libmystdc.so
2 directories, 4 files
[vect@VM-0-11-centos warehouse]$ gcc main.c -o main -L other/lib/ -I other/include/ -lmystdc
[vect@VM-0-11-centos warehouse]$ ls
main main.c Makefile mystdio.c mystlen.c other
[vect@VM-0-11-centos warehouse]$ ./main
abc:3
[vect@VM-0-11-centos warehouse]$ ldd main
linux-vdso.so.1 => (0x00007ffe948c9000)
libmystdc.so => /lib64/libmystdc.so (0x00007fc422abb000)
libc.so.6 => /lib64/libc.so.6 (0x00007fc4226ed000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc422cbd000)
关键特性:动静态优先级
当同路径下同时存在libmystdc.a和libmystdc.so时,gcc 默认优先使用动态库;强制使用静态库需加-static参数(需确保静态库存在):
bash
[vect@VM-0-11-centos warehouse]$ tree other/
other/
|-- include
| |-- mystdio.h
| `-- mystrlen.h
`-- lib
|-- libmystdc.a
`-- libmystdc.so
2 directories, 4 files
[vect@VM-0-11-centos warehouse]$ gcc main.c -o main -L other/lib/ -I other/include/ -lmystdc -static
[vect@VM-0-11-centos warehouse]$ ls
main main.c Makefile mystdio.c mystlen.c other
[vect@VM-0-11-centos warehouse]$ ldd main
not a dynamic executable
如果只给静态库,但是链接方式是动态库呢?
只针对.a局部静态链接
动态库路径解析问题
bash
[vect@VM-0-11-centos warehouse]$ ls
main main.c main_dy Makefile mystdio.c mystlen.c other
[vect@VM-0-11-centos warehouse]$ ldd main_dy
linux-vdso.so.1 => (0x00007ffd91ec4000)
libmystdc.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f944651c000)
/lib64/ld-linux-x86-64.so.2 (0x00007f94468ea000)
解决方案:
拷贝
.so文件到系统共享库路径下:一般是/sur/lib /usr/local/lib /lib64等路径建立软连接到系统路径
bash[vect@VM-0-11-centos warehouse]$ sudo ln -s /lib64/libmystdc.so libmystd.so [vect@VM-0-11-centos warehouse]$ ll libmystd.so lrwxrwxrwx 1 root root 19 Jan 10 13:13 libmystd.so -> /lib64/libmystdc.so [vect@VM-0-11-centos warehouse]$ ldd main_dy linux-vdso.so.1 => (0x00007ffccd1f6000) libmystdc.so => /lib64/libmystdc.so (0x00007fd921ecf000) libc.so.6 => /lib64/libc.so.6 (0x00007fd921b01000) /lib64/ld-linux-x86-64.so.2 (0x00007fd9220d1000)添加环境变量
LD_LIBRARY_PATH(当前终端临时有效)配置系统库搜索路径文件
动态库和静态库对比
| 对比维度 | 静态库(.a) | 动态库(.so) |
|---|---|---|
| 链接时机 | 编译阶段 | 运行阶段 |
| 可执行文件体积 | 大(合并库代码) | 小(仅存函数入口地址) |
| 内存占用 | 高(多个进程各存一份库代码) | 低(多进程共享库代码) |
| 库更新维护 | 需重新编译可执行文件 | 直接替换动态库,无需重新编译 |
| 运行依赖 | 无(独立运行) | 依赖动态库存在(需在搜索路径中) |
| 链接参数 | 无需 -fPIC/-shared |
必须 -fPIC -shared |
| 核心优势 | 部署简单、运行稳定 | 节省磁盘 / 内存 |
2. ELF文件
ELF文件四种类型
可通过readlf -h命令查看文件类型
| 类型 | 标识 | 作用 |
|---|---|---|
| 可重定位文件 | .o |
编译后未链接,需要和其他.o合并成可执行文件 |
| 可执行文件 | 无 | 链接完成后的可执行程序 |
| 共享目标文件 | .so |
动态库,运行时加载,多程序共享 |
| 内核转储文件 | core | 存放当前进程的执行上下文,用于调试 |
ELF文件结构详解
看段代码
c
#include <stdio.h>
int g_val1 = 10;
int g_val2;
int add(int a, int b){
return a + b;
}
int main(){
static int s_val = 20;
int num = 30;
int ret = add(s_val,num);
printf("%d\n",ret);
return 0;
}
gcc -c形成.o可重定位目标文件
用wc -c指令查看main.o的大小:
bash
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/add_file$ wc -c main.o
928 main.o
用readelf -h指令查看ELF Header:
bash
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: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 288 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 10
Section header string table index: 9

详细解释一下Magic魔数:

OS加载可执行文件会确认魔数的正确性,若不正确则拒绝加载
根据ELF Header,可以确定ELF文件基本格式:

ELF Header起始0x00,64B大小,所以Section起始位置就是0x40(64的十六进制)
Section Header Table中共有10个Section Header,每个大小为64B,共计640B,而起始地址是288B,二者相加刚好是总ELF文件大小
再用readelf -S查看Section Header Table的信息,映射到Section
bash
There are 10 section headers, starting at offset 0x120:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000000 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000040
0000000000000000 0000000000000000 WA 0 0 1
[ 3] .bss NOBITS 0000000000000000 00000040
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .comment PROGBITS 0000000000000000 00000040
000000000000002c 0000000000000001 MS 0 0 1
[ 5] .note.GNU-stack PROGBITS 0000000000000000 0000006c
0000000000000000 0000000000000000 0 0 1
[ 6] .note.gnu.pr[...] NOTE 0000000000000000 00000070
0000000000000020 0000000000000000 A 0 0 8
[ 7] .symtab SYMTAB 0000000000000000 00000090
0000000000000030 0000000000000018 8 2 8
[ 8] .strtab STRTAB 0000000000000000 000000c0
0000000000000008 0000000000000000 0 0 1
[ 9] .shstrtab STRTAB 0000000000000000 000000c8
0000000000000058 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
我们发现此时.data、.bss、.comment的起始位置都是0x40
重新梳理一下代码中的变量:
| 代码元素 | 类型 | 关键特征 |
|---|---|---|
g_val1 = 10 |
全局变量 | 已初始化(值为 10) |
g_val2 |
全局变量 | 未初始化 |
add() |
函数 | 自定义函数,有执行逻辑 |
main() |
函数 | 程序入口,有执行逻辑 |
s_val = 20 |
静态局部变量 | 已初始化(值为 20),static修饰 |
num = 30、ret |
普通局部变量 | 未初始化(num显式赋值 30,运行时初始化),无static修饰 |
printf() |
标准库函数 | 外部引用(仅调用,未在当前文件定义) |
逐一对应ELF Section重点部分
-
.text:代码段,存放所有函数编译后的可执行机器指令对应代码中的:
add()main()函数中的
return、for、printf()调用等逻辑,会被编译器编译为 机器指令(二进制可执行代码)**,这些指令具有「可执行」属性,因此被分配到.text -
.data:数据段,存放已初始化的全局变量、静态局部变量对应代码中的:
g_val1 = 10static int s_val = 20这两个变量都是已初始化有明确赋值且生命周期全局 的变量,编译器会将它们的初始值存到
.data中,占用磁盘空间 -
.bss:未初始化数据段,存放未初始化的全局变量,仅占位不占空间对应代码中的:
int g_val2未初始化的全局变量默认为0 ,编译器不需要将全0数据存在磁盘而浪费空间,只需要记录在
.bss中记录"该变量需要分配多少内存空间",运行时OS加载程序时,才会在内存中分配空间并初始化为0 -
.rodata:只读数据段,代码中未展现
为什么.text、.data、.bss的起始地址都是0x40?
部分Section不占用磁盘空间,无需独立分配偏移,直接复用0x40:
.bss:没有实际字节内容,磁盘上不占空间,仅在节头表中记录,运行时才分配需要的大小.text、.data:没有实际的字节内容(编译优化了),磁盘也不占空间,复用0x40

由此,可形成ELF文件的基本结构
ELF文件的两种视图
-
链接视图--对应节头表
- 文件结构更细致,将文件按功能模块的差异进行划分,静态链接分析时一般关注链接视图,理解ELF文件中的各部分信息
- 为了空间效率,在链接目标文件时,链接器会把很多节合并,规整成可执行的段、可读写的段、只读段等。很小的节会浪费很多物理内存页(物理内存页都是整数倍一块分配)

按照相同属性合并
-
执行试图--对应程序头表(我并未表示出来,一般这个表可忽略)
- 告诉OS,如何加载可执行文件,文成进程内存初始化
总结:Program Header Table在运行加载时起作用,Section Header Table在链接时起作用
3. 符号表和符号解析
符号表
每个可重定位目标文件都有符号表
把main.c函数稍微修改
cpp
#include <stdio.h>
int g_val1 = 10;
int g_val2;
int add(int a, int b){
return a + b;
}
int main(){
static int s_val = 20;
static int ss_val = 40;
int num = 30;
int ret = add(s_val,num);
printf("ret is: %d\n",ret);
return 0;
}
用readelf -s查看符号表
bash
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 .data
4: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata
5: 0000000000000004 4 OBJECT LOCAL DEFAULT 3 s_val.1
6: 0000000000000008 4 OBJECT LOCAL DEFAULT 3 ss_val.0
7: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 g_val1
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 g_val2
9: 0000000000000000 24 FUNC GLOBAL DEFAULT 1 add
10: 0000000000000018 72 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
关键字段解析:
| 字段 | 意义 |
|---|---|
| Type | 变量类型:FUNC函数、OBJECT变量、FILE文件 |
| Bind | 作用域:GLOBAL全局跨文件访问、LOCAL局部 |
| Ndx | UND未定义、ABS绝对地址、数字->对应节中的索引 |
| Value | 符号在节中的偏移地址 |
对于局部变量num,保存在栈帧中
在链接器的上下文中,有三中符号:
假设现在有个模块m和其他模块
- 全局符号:m定义的,m和其它都能引用的符号->对应非静态的C函数和全局变量
- 局部符号:m中可见可用,其它不可见不可用->对应带
static属性的C函数和全局变量- 外部符号:其他模块定义并被m模块引用的全局符号->对应使用其他文件的非静态C函数和全局变量
注意:在C中可用static修饰文件内的函数和变量,保证其他文件无法访问,类似于C++中的private
符号解析
未定义错误
看这段代码
cpp
void foo();
int main(){
foo();
return 0;
}
仅对文件进行编译汇编,不做链接,没有问题:

但是在链接时出错了:foo未定义
bash
M-0-11-ubuntu:~/LinuxRepo/csapp_link/add_file$ gcc -o linkerr linkerr.o
/usr/bin/ld: linkerr.o: in function `main':
linkerr.c:(.text+0x9): undefined reference to `foo'
collect2: error: ld returned 1 exit status
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/add_file$ readelf -s linkerr.o
Symbol table '.symtab' contains 5 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS linkerr.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 20 FUNC GLOBAL DEFAULT 1 main
4: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND foo
我们在符号表中可以查看foo是未定义的状态
这种情况比较好判断,现在再来几种情况
多重定义错误
先来看强弱符号定义:
- 强符号:函数和已初始化的全局变量
- 弱符号:未初始化的全局变量
多个强符号同时出现
cpp
// foo.c
int main(){return 0;}
// bar.c
int main(){return 0;}
链接时会报重定义的错误:
bash
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/prase_singal$ gcc foo.c bar.c
/usr/bin/ld: /tmp/ccOVYtJj.o: in function `main':
bar.c:(.text+0x0): multiple definition of `main'; /tmp/ccjS2Jzp.o:foo.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
一个强符号和多个同名弱符号
cpp
// foo2.c
#include <stdio.h>
void f(void);
int x = 11111;
int main(){
f();
printf("x=%d\n",x);
return 0;
}
// bar2.c
__attribute__((weak)) int x; // 明确声明为弱符号
void f(){x = 111;}
在foo2.c中,x是强符号,在bar2.c中,x是弱符号
链接时,会优先使用强符号,并不会出错:
bash
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/prase_singal$ gcc -o test foo2.c bar2.c
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/prase_singal$ ./test
x=111
不同编译器解读可能不同,这里代码强制声明为弱符号,因为不加前面的声明,我的编译器把bar2.c中的x视为强符号定义了
不同类型的定义
cpp
// foo3.c
// #include <stdio.h>
void f(void);
int x = 15212;
int y = 15213;
int main(){
f();
printf("x=0x%x y=0x%x\n",x,y);
return 0;
}
// bar3.c
__attribute__((weak))double x;
void f(){x = -0.0;}
输出结果:
bash
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/prase_singal$ gcc -o test3 foo3.c bar3.c
vect@VM-0-11-ubuntu:~/LinuxRepo/csapp_link/prase_singal$ ./test3
x=0x0 y=0x3b6c
分析:
foo3.c的x是强符号遵循强符号唯一,强符号覆盖弱符号规则,保留
foo3.c中int x4字节内存,bar3.c中的double x不占内存
bar3.c中的f()按double类型修改int x的内存,导致x=0x0类型不匹配的关键问题:
foo3.c的x是int类型(4 字节,内存布局为低地址到高地址存储 15213 的二进制);bar3.c的f()函数被编译时,仍按double类型(8 字节)操作 "x的内存地址"------ 但这个地址实际是int x的地址(仅 4 字节)
-0.0的double存储对int x的影响:
double类型的-0.0在内存中是 "符号位为 1,其余位全 0",其 8 字节二进制为0x80000000 0x00000000;f()函数向x的地址写入这 8 字节,但int x仅占用前 4 字节(低地址部分),即0x00000000,对应int值就是0x0------ 这就是输出x=0x0的原因
总结
为了避免上述错误,可以用-Werror选项,把所有警告变为错误,用-fno-common选项告诉链接器,遇到多重定义的全局符号时,触发错误
对于强弱符号原则:
- **强符号唯一原则:**多文件出现同名强符号,报重定义错误
- 强符号覆盖弱符号原则: 一个强符号和多个弱符号同名,优先选择强符号,忽略弱符号
- 多重弱符号取舍原则: 多个文件存在同名弱符号(无强符号),任选一个(通常是第一个出现的)
4.静态库链接
静态链接的本质是将用户.o 文件与静态库中的.o 文件 "无缝整合" 为独立可执行文件,核心依赖 "符号解析" 和 "重定位" 两大步骤,全程由链接器(ld)完成
收集输入文件(用户.o和静态库里的.o)
- 链接器接收用户的
.o文件和静态库.a,读取每个文件的节头表,识别核心节 - 遍历库中的
.o文件,仅提取用户代码中引用到的.o,没用到的不参与合并
符号解析(解决符号引用->符号定义的映射)
- 链接器汇总所有输入文件的符号表,按强弱符号规则处理同名符号:
- **强符号唯一原则:**多文件出现同名强符号,报重定义错误
- 强符号覆盖弱符号原则: 一个强符号和多个弱符号同名,优先选择强符号,忽略弱符号
- 多重弱符号取舍原则: 多个文件存在同名弱符号(无强符号),任选一个(通常是第一个出现的)
- 未定义符号处理:若引用的符号(如
printf)未在输入文件中找到定义,链接器就会查系统默认库(如lib.a),再找不到报未定义错误
合并节和地址分配
-
将所有输入文件的同名节合并

-
为合并后的每一个节分配统一的虚拟地址
重定位
- 依赖重定位表(
.rel.text/.rel.data):重定位表记录了需要修正的符号引用位置 - 修正逻辑:链接器根据符号表找到符号的最终地址,替换重定位表中标记的"无效地址"
5. ELF文件加载和进程地址空间加载过程
未完待续~