基于CSAPP对链接和库的理解

文章目录

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.alibmystdc.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 = 30ret 普通局部变量 未初始化(num显式赋值 30,运行时初始化),无static修饰
printf() 标准库函数 外部引用(仅调用,未在当前文件定义)

逐一对应ELF Section重点部分

  • .text:代码段,存放所有函数编译后的可执行机器指令

    对应代码中的:add() main()

    函数中的returnforprintf()调用等逻辑,会被编译器编译为 机器指令(二进制可执行代码)**,这些指令具有「可执行」属性,因此被分配到.text

  • .data:数据段,存放已初始化的全局变量、静态局部变量

    对应代码中的:g_val1 = 10 static 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

分析:

  1. foo3.cx是强符号

    遵循强符号唯一,强符号覆盖弱符号规则,保留foo3.cint x4字节内存,bar3.c中的double x不占内存

  2. bar3.c中的f()double类型修改int x的内存,导致x=0x0

    类型不匹配的关键问题

    • foo3.cxint类型(4 字节,内存布局为低地址到高地址存储 15213 的二进制);
    • bar3.cf()函数被编译时,仍按double类型(8 字节)操作 "x的内存地址"------ 但这个地址实际是int x的地址(仅 4 字节)

    -0.0double存储对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文件加载和进程地址空间加载过程

未完待续~

相关推荐
小杰帅气2 小时前
神秘的环境变量和进程地址空间
linux·运维·服务器
胖咕噜的稞达鸭2 小时前
进程间的通信(1)(理解管道特性,匿名命名管道,进程池,systeam V共享内存是什么及优势)重点理解代码!
linux·运维·服务器·数据库
Coder个人博客2 小时前
Linux6.19-ARM64 boot Makefile子模块深入分析
linux·车载系统·系统架构·系统安全·鸿蒙系统
可爱又迷人的反派角色“yang”2 小时前
k8s(五)
linux·运维·docker·云原生·容器·kubernetes
爱吃生蚝的于勒2 小时前
【Linux】进程间通信之匿名管道
linux·运维·服务器·c语言·数据结构·c++·vim
好奇心害死薛猫2 小时前
飞牛OS开机自动挂载SMB
linux
PascalMing3 小时前
ubuntu 24.04安装dotnet 10日志
linux·运维·ubuntu·dotnet10
optimistic_chen3 小时前
【Docker入门】容器技术
linux·运维·服务器·docker·容器
Lueeee.3 小时前
2.智梯云枢・全维管控广告系统——解决串口卡顿 + 优化稳定性
linux·运维·服务器