1.什么是库
C/C++的标准库
使用ldd命令可以查看我们的可执行程序都使用了哪些库。


库名字必须以lib为前缀,实际库的名字就是去掉去前缀lib,在去掉后缀

C/C++代码使用gcc或g++编译链接的时候,默认使用动态链接。
想要静态链接,就需要 -static 选项
如果编译失败,说明当前系统中没有安装静态库。
bash
# 更新软件源(可选,但建议执行)
sudo apt update
# 安装编译工具链(gcc/g++、make等)和基础静态库
sudo apt install build-essential

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:
静态库.a[Linux]、.lib[windows]
动态库.so[Linux]、.dll[windows]
编译链接过程:


在进行多文件编译时,推荐先把.c文件编译成.o文件,再把.o文件链接形成可执行文件。
库文件就是".o"文件的集合。
2.静态库
2.1静态库的生成
静态库生成命令:ar -rc [静态库名] [静态库包含的.o文件]

库文件就是把多个.o文件进行打包。
2.2静态库的使用
- 使用静态库时,必须要有头文件,头文件提供声明,库文件提供所有方法的实现。
- 操作系统在查找库时默认到lib64目录下寻找。
- 在使用静态库形成可执行程序时,链接所有.o文件,要将主函数.c文件先编译形成.o文件再链接静态库。
- 库文件中不能包含main函数
bash
libmy.a:mystdio.o mystr.o
ar rcs $@ $^
%.o:%.c
gcc -c $<
.PHONY:clean
clean:
rm -f *.o *.a
2.2.1方法一:使用当前目录的库
gcc编译时选项:
- -l:表示当前.o文件链接形成可执行程序所需要的静态库的**[文件名]**,该文件要去掉前缀lib和后缀.a。
- -L: 静态库的路径

- 我们在使用c标准库进程编译时,gcc认识c标准库,也知道c标准库在哪个路径下。
- 所以只用到c标准库时,不需要带任何选项。
- 当我们以一个库的制作者去发布一个库时,提供给别人的是一个这样的目录:

2.2.2方法二:将下载的库/自己做的库拷贝到系统目录下



- 虽然我们把下载的库安装到系统路径下,但是在链接时还是需要告诉gcc要链接哪个库。

2.2.3方法三:当库和头文件不在当前路径下时,指定gcc的寻找路径


- -I 告诉系统除了当前和系统目录,-I指定的路径下也需要搜索头文件
- I(大写i)用来寻找头文件
2.2.4方法四:在系统目录下建立自己的库的软链接

3.动态库
3.1动态库的打包

bash
libmy.so:mystdio.o mystr.o
gcc -shared -o $@ $^
%.o:%.c
gcc -fPIC -c $<
.PHONY:clean
clean:
rm -f *.o *.so mylib
。PHONY:output
output:
mkdir -p mylib
mkdir -p mylib/include
mkdir -p mylib/lib
cp *.h mylib/include
cp *.so mylib/lib

- 细节:形成动态库,不再使用ar,而是使用gcc/g++,说明:动态库是最常见的场景,一款合格的编译器本身,就能形成动态库。
- 静态库则是使用ar命令,说明静态库是shall帮我们完成的。
3.2动态库的使用


- 从上图我们可以看到,main可执行程序找不到我们的动态库
- 我们使用gcc带上库和头文件的路径形成的main,相当于我们告诉了gcc编译器我们要用到的库在哪
- 当我们要运行时,是操作系统来执行这个程序,操作系统并不知道我们的动态库在哪。
- c标准库是在系统默认路径下的,所以操作系统可以找到
- 使用静态库,静态库的方法拷贝到程序内部,程序运行就不需要库了。
动态库加载(运行)时的查找问题
1.直接把库拷贝到系统的lib64下



- 动态库要在编译时能被编译器找到
- 运行时要能被操作系统找到
2.在系统目录下建立软链接


3.通过更改环境变量
环境变量LD_LIBRARY_PATH能帮助操作系统找到动态库。
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ echo $LD_LIBRARY_PATH
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ldd main
linux-vdso.so.1 (0x00007ffcc0194000)
libmy.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000073d7eba00000)
/lib64/ld-linux-x86-64.so.2 (0x000073d7ebd0e000)
wx@VM-8-14-ubuntu:~/linux-learning/friend$ pwd
/home/ubuntu/linux-learning/friend
wx@VM-8-14-ubuntu:~/linux-learning/friend$ tree ./
./
├── main
├── main.c
└── mylib
├── include
│ ├── mystdio.h
│ └── mystr.h
└── lib
└── libmy.so
4 directories, 5 files
wx@VM-8-14-ubuntu:~/linux-learning/friend$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ubuntu/linux-learning/friend/mylib/lib
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ldd main
linux-vdso.so.1 (0x00007fffbf38b000)
libmy.so => /home/ubuntu/linux-learning/friend/mylib/lib/libmy.so (0x000072d7c7e6d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000072d7c7c00000)
/lib64/ld-linux-x86-64.so.2 (0x000072d7c7e79000)
4.更改配置文件
配置文件位置:/etc/ld.so.conf.d/

将动态库的绝对路径写入配置文件中,更改配置文件需要root用户
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ls /etc/ld.so.conf.d/
libc.conf x86_64-linux-gnu.conf
wx@VM-8-14-ubuntu:~/linux-learning/friend$ pwd
/home/ubuntu/linux-learning/friend
wx@VM-8-14-ubuntu:~/linux-learning/friend$ tree ./
./
├── main
├── main.c
└── mylib
├── include
│ ├── mystdio.h
│ └── mystr.h
└── lib
└── libmy.so
4 directories, 5 files
wx@VM-8-14-ubuntu:~/linux-learning/friend$ sudo touch /etc/ld.so.conf.d/l13.conf
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ls -l /etc/ld.so.conf.d/
total 8
-rw-r--r-- 1 root root 0 Jan 9 14:15 l13.conf
-rw-r--r-- 1 root root 44 Aug 2 2022 libc.conf
-rw-r--r-- 1 root root 100 Mar 30 2024 x86_64-linux-gnu.conf
root用户:
root@VM-8-14-ubuntu:~# echo "/home/ubuntu/linux-learning/friend/mylib/lib" > /etc/ld.so.conf.d/l13.conf
root@VM-8-14-ubuntu:/etc/ld.so.conf.d# ldconfig
root@VM-8-14-ubuntu:/home/ubuntu/linux-learning/friend# ldd main
linux-vdso.so.1 (0x00007ffd5f3fa000)
libmy.so => /home/ubuntu/linux-learning/friend/mylib/lib/libmy.so (0x00007b85d54db000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b85d5200000)
/lib64/ld-linux-x86-64.so.2 (0x00007b85d54ee000)
__vsc_prompt_cmd_original: command not found
当动静态库同时存在时,编译器如何选择?
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ tree .
.
├── main
├── main.c
└── mylib
├── include
│ ├── mystdio.h
│ └── mystr.h
└── lib
├── libmy.a
└── libmy.so
4 directories, 6 files
wx@VM-8-14-ubuntu:~/linux-learning/friend$ gcc main.c -o main -I ./mylib/include/ -L ./mylib/lib/ -l my
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ldd main
linux-vdso.so.1 (0x00007ffd7c472000)
libmy.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000070eaa7000000)
/lib64/ld-linux-x86-64.so.2 (0x000070eaa7267000)
- 同时存在两种库时,编译器默认链接动态库。
- 如果强制链接静态库,在编译时要额外添加-static
- 只有动态库时,如果添加-static选项,会报错。
只有静态库时,不带-static选项,编译器如何选择?
cpp
wx@VM-8-14-ubuntu:~/linux-learning/friend$ gcc main.c -o main -I ./mylib/include/ -L ./mylib/lib/ -l my
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ls -l
total 24
-rwxr-xr-x 1 wx ubuntu 15984 Jan 9 15:31 main
-rw-r--r-- 1 wx ubuntu 95 Jan 9 10:37 main.c
drwxr-xr-x 4 wx ubuntu 4096 Jan 9 11:57 mylib
wx@VM-8-14-ubuntu:~/linux-learning/friend$ tree .
.
├── main
├── main.c
└── mylib
├── include
│ ├── mystdio.h
│ └── mystr.h
└── lib
├── libmy.a
└── libmy.so
4 directories, 6 files
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ tree .
.
├── main
├── main.c
└── mylib
├── include
│ ├── mystdio.h
│ └── mystr.h
└── lib
├── libm.so
└── libmy.a
4 directories, 6 files
wx@VM-8-14-ubuntu:~/linux-learning/friend$ gcc main.c -o main -I ./mylib/include/ -L ./mylib/lib/ -l my
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ls -al
total 32
drwxr-xr-x 3 wx ubuntu 4096 Jan 9 15:23 .
drwxr-xr-x 14 wx ubuntu 4096 Jan 9 10:17 ..
-rwxr-xr-x 1 wx ubuntu 16088 Jan 9 15:23 main
-rw-r--r-- 1 wx ubuntu 95 Jan 9 10:37 main.c
drwxr-xr-x 4 wx ubuntu 4096 Jan 9 11:57 mylib
wx@VM-8-14-ubuntu:~/linux-learning/friend$ gcc main.c -o main -I ./mylib/include/ -L ./mylib/lib/ -l my -static
wx@VM-8-14-ubuntu:~/linux-learning/friend$ ls -l
total 776
-rwxr-xr-x 1 wx ubuntu 785488 Jan 9 15:23 main
-rw-r--r-- 1 wx ubuntu 95 Jan 9 10:37 main.c
drwxr-xr-x 4 wx ubuntu 4096 Jan 9 11:57 mylib
- 如果只提供静态库,即便采用默认动态链接,对于该库来说,编译器也只能静态链接。
- 但是c语言标准库有动态库,所以这种情况动态链接其他库,只静态链接我们提供的静态库
- -static作用:强制要求所以库都静态链接
.o文件被称为目标文件
4.ELF文件
可执行文件,是一个二进制文件,这些二进制位有自己的结构和格式---ELF格式
目标.o文件,动静态库都是ELF格式的文件。
4.1elf文件的组成部分
elf文件核心由4部分构成:
**1.ELF头(ELFheader):**描述文件的主要特性。其位于文件的开始位置,它的主要目的是定位文件的其他部分(比如从哪个位置开始是"程序头表",从哪个位置开始是"节头表")。
**2.程序头表(Programheadertable)**列举了所有有效的段(segments)和他们的属性。表里记着每个段的开始的位置和位移(offset)、长度,毕竟这些段,都是紧密的放在二进制文件中,需要段表的描述信息,才能把他们每个段分割开。
可执行程序天然有各种各种各样的数据节,与虚拟地址空间中各个区域的划分有关

**3.节头表(Sectionheadertable):**包含对节(sections)的描述(各个节的其实位置和内容说明)。
4.节(Section): ELF文件中的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中,如代码节存储了可执行代码,数据节存储了全局变量和静态数据等**。**
反汇编查看节中的内容:objdump -S [文件名]
最常见的节:
代码节(text):用于保存机器指令,是程序的主要执行部分(程序在形成进程后,加载到虚拟地址空间的代码区)。
数据节(data):保存已初始化的全局变量和局部静态变量(虚拟地址空间的各种数据区)。

节和段关系
elf中没有段信息
elf中会有一个个的数据节,不同的节大小不一,权限可以有相同的。
- elf也是文件,其内容也都是以4kb为单位进行存储,OS读取磁盘内容以4kb为单位导入到内存中。
- 一个节要占用一个4kb的data block,如果不进行加工,一个节可能只有128字节,但是还要占用4kb的内存,会很浪费空间
- 为了更好的实现内存加载和不浪费空间,多个相同权限的节,在加载的时候,由加载器帮我们进行节合并,形成段。
- 这些由相同权限的节结合成的段,加载到内存后就是虚拟地址空间中的代码段,未初始化数据段,已初始化数据段,共享区等。
program header
该区域保存了,有多少段需要加载,以及每个段相关的节有哪些。
elf中的节:
4.2ELF文件形成可执行文件

- .o文件,库文件,可执行程序都是ELF格式
- .o文件和库文件(.o文件的集合)形成可执行文件的过程,就是这些文件相同部位的内容合并在一起。
- 例如:不同目标文件的elf header区域的二进制内容,结合到一起,形成可执行文件的elf header区域的内容。
4.3可执行程序加载
- 创建一个进程,先创建内核数据结构,然后加载elf格式的二进制文件。
- 程序在编译好之后,还没加载到内存,程序中的每一行代码都有自己的"地址",反汇编中可以看出来。

- 我们当前的编译器,编译可执行程序,它会对我们的每一行代码采用"平坦模式",对我们可执行程序中的每一行代码都进行编址,原则上从零号地址开始,在32位系统下,到32位全1结束,这些地址都是虚拟地址(逻辑地址)。
可执行程序的加载

- 每一行代码加载到内存中后,既要有自己的虚拟地址也要有物理地址
- 虚拟地址从二进制文件中来,加载到内存后,形成对应的物理地址。
cpp
unsigned long total_vm, locked_vm, shared_vm, exec_vm;
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;
- 创建进程需要先创建内核数据结构,那么就要创建虚拟地址空间,那么其中的数据怎么初始化
- 虚拟地址空间(mm_struct)中各种区域的划分就是从磁盘中可执行程序中读取。

- program header中virtaddr memsiz部分就已经说明了,虚拟地址空间各个区域的划分由哪开始到哪结束
- 进程数据结构中的地址都是虚拟地址,cpu在执行进程时,进入cpu的地址都是虚拟地址,经过mmu转化成物理地址。
- 得到物理地址,就可以直接访问内存中的数据了。
5.动静态库的链接与加载
在linux中程序运行的第一个入口是CRTStartup函数,整个函数会调用main函数

库也是elf格式的文件。
动态库加载到内存,是要被映射到共享区。

- 动态库会被多个程序所需要,动态库也只需要在内存中存在一份即可。
静态链接过程
本质是把库中的相关代码拷贝到程序中。
cpp
code.c
#include<stdio.h>
void run();
int main()
{
printf("hello world!\n");
run();
return 0;
}
hello.c
#include<stdio.h>
void run()
{
printf("running...\n");
}
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ gcc -c *.c
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ ls
code.c code.o hello.c hello.o
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ gcc *.o -o myexe
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ ls
code.c code.o hello.c hello.o myexe
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ ./myexe
hello world!
running...
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ file code.o
code.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ objdump -d code.o
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # f <main+0xf>
f: 48 89 c7 mov %rax,%rdi
12: e8 00 00 00 00 call 17 <main+0x17>
17: b8 00 00 00 00 mov $0x0,%eax
1c: e8 00 00 00 00 call 21 <main+0x21>
21: b8 00 00 00 00 mov $0x0,%eax
26: 5d pop %rbp
27: c3 ret
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ objdump -d hello.o
hello.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <run>:
0: f3 0f 1e fa endbr64
4: 55 push %rbp
5: 48 89 e5 mov %rsp,%rbp
8: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # f <run+0xf>
f: 48 89 c7 mov %rax,%rdi
12: e8 00 00 00 00 call 17 <run+0x17>
17: 90 nop
18: 5d pop %rbp
19: c3 ret

- 第三列是反汇编,第二列是反汇编对应的cpu能看懂的指令集,e8就是call命令。
- 但是e8后面跟的是全0,说明并不知道需要调用哪个方法,因为我们只是看的是.o文件,还没有链接,所以调用函数不确定。
- code.c代码中调用了printf和run函数,所以有两个call命令,由于没有链接,所以e8后面是全0(调用的目标函数地址不知道)
- 没有找到调用函数的地址,但是编译.c文件到.o不报错,是暂时忽略了,目标调用函数的地址
查看.o文件的符号表:
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ readelf -s code.o
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata
4: 0000000000000000 40 FUNC GLOBAL DEFAULT 1 main
5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND run
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ readelf -s hello.o
Symbol table '.symtab' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 .text
3: 0000000000000000 0 SECTION LOCAL DEFAULT 5 .rodata
4: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 run
5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND puts
- 因为还没链接,所以代码中的符号的地址也都是未知的。
链接所有的.o文件
cpp
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (3)
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
6: 0000000000000000 0 FUNC WEAK DEFAULT UND [...]@GLIBC_2.2.5 (3)
Symbol table '.symtab' contains 38 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS Scrt1.o
2: 000000000000038c 32 OBJECT LOCAL DEFAULT 4 __abi_tag
3: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
4: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
5: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
6: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
7: 0000000000004010 1 OBJECT LOCAL DEFAULT 26 completed.0
8: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtor[...]
9: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy
10: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_in[...]
11: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.c
12: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
13: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
14: 0000000000002120 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
15: 0000000000000000 0 FILE LOCAL DEFAULT ABS
16: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
17: 000000000000201c 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
18: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
19: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]
20: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
21: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
22: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5
23: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata
24: 0000000000001171 26 FUNC GLOBAL DEFAULT 16 run
25: 000000000000118c 0 FUNC GLOBAL HIDDEN 17 _fini
26: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
27: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
28: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
29: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
30: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end
31: 0000000000001060 38 FUNC GLOBAL DEFAULT 16 _start
32: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
33: 0000000000001149 40 FUNC GLOBAL DEFAULT 16 main
34: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
35: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...]
36: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@G[...]
37: 0000000000001000 0 FUNC GLOBAL HIDDEN 12 _init
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ objdump -d myexe
myexe: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 call *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 ret
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 9a 2f 00 00 push 0x2f9a(%rip) # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: ff 25 9c 2f 00 00 jmp *0x2f9c(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
102c: 0f 1f 40 00 nopl 0x0(%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 push $0x0
1039: e9 e2 ff ff ff jmp 1020 <_init+0x20>
103e: 66 90 xchg %ax,%ax
Disassembly of section .plt.got:
0000000000001040 <__cxa_finalize@plt>:
1040: f3 0f 1e fa endbr64
1044: ff 25 ae 2f 00 00 jmp *0x2fae(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
104a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Disassembly of section .plt.sec:
0000000000001050 <puts@plt>:
1050: f3 0f 1e fa endbr64
1054: ff 25 76 2f 00 00 jmp *0x2f76(%rip) # 3fd0 <puts@GLIBC_2.2.5>
105a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Disassembly of section .text:
0000000000001060 <_start>:
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp
1073: 45 31 c0 xor %r8d,%r8d
1076: 31 c9 xor %ecx,%ecx
1078: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 1149 <main>
107f: ff 15 53 2f 00 00 call *0x2f53(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>
1085: f4 hlt
1086: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
108d: 00 00 00
0000000000001090 <deregister_tm_clones>:
1090: 48 8d 3d 79 2f 00 00 lea 0x2f79(%rip),%rdi # 4010 <__TMC_END__>
1097: 48 8d 05 72 2f 00 00 lea 0x2f72(%rip),%rax # 4010 <__TMC_END__>
109e: 48 39 f8 cmp %rdi,%rax
10a1: 74 15 je 10b8 <deregister_tm_clones+0x28>
10a3: 48 8b 05 36 2f 00 00 mov 0x2f36(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>
10aa: 48 85 c0 test %rax,%rax
10ad: 74 09 je 10b8 <deregister_tm_clones+0x28>
10af: ff e0 jmp *%rax
10b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
10b8: c3 ret
10b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010c0 <register_tm_clones>:
10c0: 48 8d 3d 49 2f 00 00 lea 0x2f49(%rip),%rdi # 4010 <__TMC_END__>
10c7: 48 8d 35 42 2f 00 00 lea 0x2f42(%rip),%rsi # 4010 <__TMC_END__>
10ce: 48 29 fe sub %rdi,%rsi
10d1: 48 89 f0 mov %rsi,%rax
10d4: 48 c1 ee 3f shr $0x3f,%rsi
10d8: 48 c1 f8 03 sar $0x3,%rax
10dc: 48 01 c6 add %rax,%rsi
10df: 48 d1 fe sar $1,%rsi
10e2: 74 14 je 10f8 <register_tm_clones+0x38>
10e4: 48 8b 05 05 2f 00 00 mov 0x2f05(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>
10eb: 48 85 c0 test %rax,%rax
10ee: 74 08 je 10f8 <register_tm_clones+0x38>
10f0: ff e0 jmp *%rax
10f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10f8: c3 ret
10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001100 <__do_global_dtors_aux>:
1100: f3 0f 1e fa endbr64
1104: 80 3d 05 2f 00 00 00 cmpb $0x0,0x2f05(%rip) # 4010 <__TMC_END__>
110b: 75 2b jne 1138 <__do_global_dtors_aux+0x38>
110d: 55 push %rbp
110e: 48 83 3d e2 2e 00 00 cmpq $0x0,0x2ee2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1115: 00
1116: 48 89 e5 mov %rsp,%rbp
1119: 74 0c je 1127 <__do_global_dtors_aux+0x27>
111b: 48 8b 3d e6 2e 00 00 mov 0x2ee6(%rip),%rdi # 4008 <__dso_handle>
1122: e8 19 ff ff ff call 1040 <__cxa_finalize@plt>
1127: e8 64 ff ff ff call 1090 <deregister_tm_clones>
112c: c6 05 dd 2e 00 00 01 movb $0x1,0x2edd(%rip) # 4010 <__TMC_END__>
1133: 5d pop %rbp
1134: c3 ret
1135: 0f 1f 00 nopl (%rax)
1138: c3 ret
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 <frame_dummy>:
1140: f3 0f 1e fa endbr64
1144: e9 77 ff ff ff jmp 10c0 <register_tm_clones>
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 8d 05 ac 0e 00 00 lea 0xeac(%rip),%rax # 2004 <_IO_stdin_used+0x4>
1158: 48 89 c7 mov %rax,%rdi
115b: e8 f0 fe ff ff call 1050 <puts@plt>
1160: b8 00 00 00 00 mov $0x0,%eax
1165: e8 07 00 00 00 call 1171 <run>
116a: b8 00 00 00 00 mov $0x0,%eax
116f: 5d pop %rbp
1170: c3 ret
0000000000001171 <run>:
1171: f3 0f 1e fa endbr64
1175: 55 push %rbp
1176: 48 89 e5 mov %rsp,%rbp
1179: 48 8d 05 91 0e 00 00 lea 0xe91(%rip),%rax # 2011 <_IO_stdin_used+0x11>
1180: 48 89 c7 mov %rax,%rdi
1183: e8 c8 fe ff ff call 1050 <puts@plt>
1188: 90 nop
1189: 5d pop %rbp
118a: c3 ret
Disassembly of section .fini:
000000000000118c <_fini>:
118c: f3 0f 1e fa endbr64
1190: 48 83 ec 08 sub $0x8,%rsp
1194: 48 83 c4 08 add $0x8,%rsp
1198: c3 ret


- run方法和main方法等都有了自己的地址
- 其他方法或指令还没有自己的地址是因为c标准库是动态链接的。
- UND表示没找到
静态链接就是把库中的.o进行合并,和上述过程一样
所以链接其实就是将编译之后的所有目标文件连同用到的一些静态库运行时库组合,拼装成一个独立的可执行文件。其中就包括我们之前提到的地址修正,当所有模块组合在一起之后,链接器会根据我们的.o文件或者静态库中的重定位表找到那些需要被重定位的函数全局变量,从而修正它们的地址。这其实就是静态链接的过程

动态链接与动态库加载


- 动态链接的可执行程序,是知道自己要链接哪些库的。
- 虽然形成了可执行程序,但是其中的一些动态链接的方法和符号表是UND的
- 所以这个程序在运行之前,库方法不确定,动态链接的可执行程序在加载到内存之前,要先把该程序链接的动态库加载到内存中。
- 库加载到内存后,然后在内存中让这些UND方法变成确定的动态库中的地址。
- 要加载对应的库,就要先找到对应的库,而一个个的库本质就是磁盘文件,所以找库就是找文件的过程,所以就需要库的路径。
- 库本质就是.o文件的集合,加载到内存后库中的函数都有自己的物理地址。
- 需要在进程的页表中建立虚拟地址与库函数物理地址的映射
- 动态库的物理地址会被页表映射到进程虚拟地址空间中的共享区
- 库函数的调用本质是在进程的虚拟地址空间内进行函数跳转

- 如果OS中又有一个新的进程,也需要使用到与上一个进程相同的动态库,这个库以经被上一个进程加载到内存中了,同时上一个进程没有结束。
- 所以新的进程就不需要再次加载这个动态库。
- 只需要把新进程需要用到的代码区的库函数的地址改成实际库函数物理地址经过页表映射后的共享区的虚拟地址,以及修改符号表中的GND即可
- 所以同一个库可能会被多个进程共享使用,这种动态库也叫做共享库。
- 库本身也是代码和数据,多个进程需要用到的资源,只需要在内存中形成一份。
- 库被使用的本质是把库映射到了进程的地址空间中
既然第二个进程需要的库如果已经被加载到内存中,就不需要再加载一次,那么OS是怎么知道哪些库已经被加载了?
内存中有很多不同的库,OS就要对这些库进行管理(先描述,再组织),所以在操作系统内部就会有一种数据结构来描述这些库。
动态链接
动态链接其实远比静态链接要常用得多。比如我们查看下hel1o这个可执行程序依赖的动态库,会发现它就用到了一个c动态链接库:
cpp
ldd myexe
linux-vdso.so.1 (0x00007ffc63b34000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007243ad600000)
/lib64/ld-linux-x86-64.so.2 (0x00007243ad970000)
- 这里的libc.so是C语言的运行时库,里面提供了常用的标准输入输出文件字符串处理等等这些功能。
- 那为什么编译器默认不使用静态链接呢?静态链接会将编译产生的所有目标文件,连同用到的各种库,合并形成一个独立的可执行文件,它不需要额外的依赖就可以运行。照理来说应该更加方便才对
- 静态链接最大的问题在于生成的文件体积大,并且相当耗费内存资源。随着软件复杂度的提升,我们的操作系统也越来越臃肿,不同的软件就有可能都包含了相同的功能和代码,显然会浪费大量的硬盘空间。
- 这个时候,动态链接的优势就体现出来了,我们可以将需要共享的代码单独提取出来,保存成一个独立的动态链接库,等到程序运行的时候再将它们加载到内存,这样不但可以节省空间,因为同一个模块在内存中只需要保留一份副本,可以被不同的进程所共享。
- 动态链接到底是如何工作的??
- 首先要交代一个结论,**动态链接实际上将链接的整个过程推迟到了程序加载的时候。**比如我们去运行一个程序,操作系统会首先将程序的数据代码连同它用到的一系列动态库先加载到内存,其中每个动态库的加载地址都是不固定的,操作系统会根据当前地址空间的使用情况为它们动态分配一段内存。
- 当动态库被加载到内存以后,一旦它的内存地址被确定,我们就可以去修正动态库中的那些函数跳转地址了。
动态库自身是没有main函数的。动态库内部是包含了大量的方法的,每一个方法都要有自己的地址,这些方法的编址也是平坦编址,全0到全F的,我们把这个地址被叫做逻辑偏移地址。
编译器对可执行程序的操作
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ ldd myexe
linux-vdso.so.1 (0x00007ffc63b34000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007243ad600000)
/lib64/ld-linux-x86-64.so.2 (0x00007243ad970000)
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ ldd /usr/bin/ls
linux-vdso.so.1 (0x00007ffe4298a000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x0000711eddb5b000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000711edd800000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x0000711eddac1000)
/lib64/ld-linux-x86-64.so.2 (0x0000711eddbb6000)
在C/C++程序中,当程序开始执行时,它首先并不会直接跳转到main函数。实际上,程序的入口点
是_start,这是一个由C运行时库(通常是glibc)或链接器(如ld)提供的特殊函数。
在_start函数中,会执行一系列初始化操作,这些操作包括:
1.设置堆栈:为程序创建一个初始的堆栈环境。
2.初始化数据段:将程序的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位
置,并清零未初始化的数据段。
3.动态链接:这是关键的一步,start函数会调用动态链接器的代码来解析和加载程序所依赖的
动态库(sharedlibraries)。动态链接器会处理所有的符号解析和重定位,确保程序中的函数调
用和变量访问能够正确地映射到动态库中的实际地址
动态链接器:
动态链接器(如ld-linux.so)负责在程序运行时加载动态库。
- 当程序启动时,动态链接器会解析程序中的动态库依赖,并加载这些库到内存中。环境变量和配置文件
- Linux系统通过环境变量(如LD_LIBRARY_PATH)和配置文件(如/etc/ld.sO.conf及其子配置文件)来指定动态库的搜索路径。
- 这些路径会被动态链接器在加载动态库时搜索。
缓存文件:
- 为了提高动态库的加载效率,Linux系统会维护一个名为/etc/ld.so.cache的缓存文件。
- 该文件包含了系统中所有已知动态库的路径和相关信息,动态链接器在加载动态库时会首先搜索这个缓存文件
4.调用__libc_start_main:一旦动态链接完成,_start函数会调用__libc_start_main(这是glibc提供的一个函数)。__libc_start_main函数负责执行一些额外的初始化工作,比如设置信号处理函数、初始化线程库人如果使用了线程)等。
5.调用main函数:最后,__libc_start_main函数会调用程序的main函数,此时程序的执行控制权才正式交给用户编写的代码。
6.处理main函数的返回值:当main函数返回时,libc_start_main会负责处理这个返回值,并最终调用_exit函数来终止程序。
上述过程描述了C/C++程序在main函数之前执行的一系列操作,但这些操作对于大多数程序员来说是透明的。程序员通常只需要关注main函数中的代码,而不需要关心底层的初始化过程。然而,了解这些底层细节有助于更好地理解程序的执行流程和调试问题
动态库中的相对地址
动态库为了随时进行加载,为了支持并映射到任意进程的任意位置,对动态库中的方法,统一编址,采用相对编址的方案进行编制的(其实可执行程序也一样,都要遵守平坦模式,只不过exe是直接加载的)。
bash
# ubuntu下查看任意⼀个库的反汇编
objdump -S /lib/x86_64-linux-gnu/libc-2.31.so | less
# Cetnos下查看任意⼀个库的反汇编
$ objdump -S /lib64/libc-2.17.so | less
可执行程序和库的映射
- 动态库也是一个文件,要访问也是要被先加载,要加载也是要被打开的
- 让我们的进程找到动态库的本质:也是文件操作,不过我们访问库函数,通过虚拟地址进行跳转访问的,所以需要把动态库映射到进程的地址空间中

程序进行库函数调用的过程
- 库已经被我们映射到了当前进程的地址空间中
- 库的虚拟起始地址我们也已经知道了
- 库中每一个方法的偏移量地址我们也知道
- 所有:访问库中任意方法,只需要知道库的起始虚拟地址+方法偏移量即可定位库中的方法
- 而且:整个调用过程,是从代码区跳转到共享区,调用完毕在返回到代码区,整个过程完全在进程地址空间中进行的。

全局偏移量表GOT
- 我们的程序运行之前,先把所有库加载并映射,所有库的起始虚拟地址都应该提前知道
- 然后对我们加载到内存中的程序的库函数调用进行地址修改,在内存中二次完成地址设置(这个叫做加载地址重定位)
所以:动态链接采用的做法是在.data(可执行程序或者库自己)中专门预留一片区域用来存放函数的跳转地址,它也被叫做全局偏移表GOT,表中每一项都是本运行模块要引用的一个全局变量或函数的地址。
因为.data区域是可读写的,所以可以⽀持动态进⾏修改

1.由于代码段只读,我们不能直接修改代码段。但有了GOT表,代码便可以被所有进程共享。但在不同进程的地址空间中,各动态库的绝对地址、相对位置都不同。反映到GOT表上,就是每个进程的每个动态库都有独立的GOT表,所以进程间不能共享GOT表。
2.在单个.so下,由于GOT表与text的相对位置是固定的,我们完全可以利用CPU的相对寻址来找
到GOT表。
3.在调用函数的时候会首先查表,然后根据表中的地址来进行跳转,这些地址在动态库加载的时候会被修改为真正的地址。
4.这种方式实现的动态链接就被叫做PIC地址无关代码 。换句话说,我们的动态库不需要做任何修
改,被加载到任意内存地址都能够正常运行,并且能够被所有进程共享,这也是为什么之前我们给
编译器指定-fPIC参数的原因,PIC=相对编址+GOT。
bash
wx@VM-8-14-ubuntu:~/linux-learning/leasson14$ objdump -S myexe
myexe: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 call *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 ret
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 9a 2f 00 00 push 0x2f9a(%rip) # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: ff 25 9c 2f 00 00 jmp *0x2f9c(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
102c: 0f 1f 40 00 nopl 0x0(%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 push $0x0
1039: e9 e2 ff ff ff jmp 1020 <_init+0x20>
103e: 66 90 xchg %ax,%ax
Disassembly of section .plt.got:
0000000000001040 <__cxa_finalize@plt>:
1040: f3 0f 1e fa endbr64
1044: ff 25 ae 2f 00 00 jmp *0x2fae(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
104a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Disassembly of section .plt.sec:
0000000000001050 <puts@plt>:
1050: f3 0f 1e fa endbr64
1054: ff 25 76 2f 00 00 jmp *0x2f76(%rip) # 3fd0 <puts@GLIBC_2.2.5>
105a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Disassembly of section .text:
0000000000001060 <_start>:
1060: f3 0f 1e fa endbr64
1064: 31 ed xor %ebp,%ebp
1066: 49 89 d1 mov %rdx,%r9
1069: 5e pop %rsi
106a: 48 89 e2 mov %rsp,%rdx
106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1071: 50 push %rax
1072: 54 push %rsp
1073: 45 31 c0 xor %r8d,%r8d
1076: 31 c9 xor %ecx,%ecx
1078: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 1149 <main>
107f: ff 15 53 2f 00 00 call *0x2f53(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>
1085: f4 hlt
1086: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
108d: 00 00 00
0000000000001090 <deregister_tm_clones>:
1090: 48 8d 3d 79 2f 00 00 lea 0x2f79(%rip),%rdi # 4010 <__TMC_END__>
1097: 48 8d 05 72 2f 00 00 lea 0x2f72(%rip),%rax # 4010 <__TMC_END__>
109e: 48 39 f8 cmp %rdi,%rax
10a1: 74 15 je 10b8 <deregister_tm_clones+0x28>
10a3: 48 8b 05 36 2f 00 00 mov 0x2f36(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>
10aa: 48 85 c0 test %rax,%rax
10ad: 74 09 je 10b8 <deregister_tm_clones+0x28>
10af: ff e0 jmp *%rax
10b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
10b8: c3 ret
10b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010c0 <register_tm_clones>:
10c0: 48 8d 3d 49 2f 00 00 lea 0x2f49(%rip),%rdi # 4010 <__TMC_END__>
10c7: 48 8d 35 42 2f 00 00 lea 0x2f42(%rip),%rsi # 4010 <__TMC_END__>
10ce: 48 29 fe sub %rdi,%rsi
10d1: 48 89 f0 mov %rsi,%rax
10d4: 48 c1 ee 3f shr $0x3f,%rsi
10d8: 48 c1 f8 03 sar $0x3,%rax
10dc: 48 01 c6 add %rax,%rsi
10df: 48 d1 fe sar $1,%rsi
10e2: 74 14 je 10f8 <register_tm_clones+0x38>
10e4: 48 8b 05 05 2f 00 00 mov 0x2f05(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>
10eb: 48 85 c0 test %rax,%rax
10ee: 74 08 je 10f8 <register_tm_clones+0x38>
10f0: ff e0 jmp *%rax
10f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10f8: c3 ret
10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001100 <__do_global_dtors_aux>:
1100: f3 0f 1e fa endbr64
1104: 80 3d 05 2f 00 00 00 cmpb $0x0,0x2f05(%rip) # 4010 <__TMC_END__>
110b: 75 2b jne 1138 <__do_global_dtors_aux+0x38>
110d: 55 push %rbp
110e: 48 83 3d e2 2e 00 00 cmpq $0x0,0x2ee2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1115: 00
1116: 48 89 e5 mov %rsp,%rbp
1119: 74 0c je 1127 <__do_global_dtors_aux+0x27>
111b: 48 8b 3d e6 2e 00 00 mov 0x2ee6(%rip),%rdi # 4008 <__dso_handle>
1122: e8 19 ff ff ff call 1040 <__cxa_finalize@plt>
1127: e8 64 ff ff ff call 1090 <deregister_tm_clones>
112c: c6 05 dd 2e 00 00 01 movb $0x1,0x2edd(%rip) # 4010 <__TMC_END__>
1133: 5d pop %rbp
1134: c3 ret
1135: 0f 1f 00 nopl (%rax)
1138: c3 ret
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001140 <frame_dummy>:
1140: f3 0f 1e fa endbr64
1144: e9 77 ff ff ff jmp 10c0 <register_tm_clones>
0000000000001149 <main>:
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 8d 05 ac 0e 00 00 lea 0xeac(%rip),%rax # 2004 <_IO_stdin_used+0x4>
1158: 48 89 c7 mov %rax,%rdi
115b: e8 f0 fe ff ff call 1050 <puts@plt>
1160: b8 00 00 00 00 mov $0x0,%eax
1165: e8 07 00 00 00 call 1171 <run>
116a: b8 00 00 00 00 mov $0x0,%eax
116f: 5d pop %rbp
1170: c3 ret
0000000000001171 <run>:
1171: f3 0f 1e fa endbr64
1175: 55 push %rbp
1176: 48 89 e5 mov %rsp,%rbp
1179: 48 8d 05 91 0e 00 00 lea 0xe91(%rip),%rax # 2011 <_IO_stdin_used+0x11>
1180: 48 89 c7 mov %rax,%rdi
1183: e8 c8 fe ff ff call 1050 <puts@plt>
1188: 90 nop
1189: 5d pop %rbp
118a: c3 ret
Disassembly of section .fini:
000000000000118c <_fini>:
118c: f3 0f 1e fa endbr64
1190: 48 83 ec 08 sub $0x8,%rsp
1194: 48 83 c4 08 add $0x8,%rsp
1198: c3 ret
库间依赖
- 不仅仅有可执行程序调用库
- 库也会调用其他库!!库之间是有依赖的,如何做到库和库之间互相调用也是与地址无关的呢??
- 库中也有.GOT,和可执行一样!这也就是为什么大家为什么都是ELF的格式!

由于动态链接在程序加载的时候需要对大量函数进行重定位,这一步显然是非常耗时的。为了进一
步降低开销,我们的操作系统还做了一些其他的优化,比如延迟绑定,或者也叫PLT(过程连接表
(ProcedureLinkageTable))。与其在程序一开始就对所有函数进行重定位,不如将这个过程
推迟到函数第一次被调用的时候,因为绝大多数动态库中的函数可能在程序运行期间一次都不会被
使用到。
思路是:GOT中的跳转地址默认会指向一段辅助代码,它也被叫做桩代码/stup。在我们第一次
调用函数的时候,这段代码会负责查询真正函数的跳转地址,并且去更新GOT表。于是我们再次
调用函数的时候,就会直接跳转到动态库中真正的函数实现

总而言之,动态链接实际上将链接的整个过程,比如符号查询、地址的重定位从编译时推迟到了程序的运行时,它虽然牺牲了一定的性能和程序加载时间,但绝对是物有所值的。因为动态链接能够更有效的利用磁盘空间和内存资源,以极大方便了代码的更新和维护,更关键的是,它实现了二进制级别的代码复用。
解析依赖关系的时候,就是加载并完善互相之间的GOT表的过程.
6.查看elf格式文件中各个部分内容的操作

6.1查看ELF header
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ readelf -h main
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: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1080
Start of program headers: 64 (bytes into file)
Start of section headers: 14000 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
cpp
// 内核中关于ELF Header相关的数据结构
// 没错,操作系统⾃⼰必须能够识别特定格式的可执⾏程序:/linux/include/elf.h
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry; /* Entry point */
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
typedef struct elf64_hdr {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
6.2ELF Program Header Table
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ readelf -l main
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x1080
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000668 0x0000000000000668 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000199 0x0000000000000199 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x00000000000000e4 0x00000000000000e4 R 0x1000
LOAD 0x0000000000002da0 0x0000000000003da0 0x0000000000003da0
0x0000000000000270 0x0000000000000278 RW 0x1000
DYNAMIC 0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
0x0000000000000200 0x0000000000000200 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000030 0x0000000000000030 R 0x8
NOTE 0x0000000000000368 0x0000000000000368 0x0000000000000368
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000030 0x0000000000000030 R 0x8
GNU_EH_FRAME 0x0000000000002004 0x0000000000002004 0x0000000000002004
0x0000000000000034 0x0000000000000034 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002da0 0x0000000000003da0 0x0000000000003da0
0x0000000000000260 0x0000000000000260 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .dynamic .got
cpp
// 内核中关于ELF Program Header相关的数据结构
typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment, file & memory */
} Elf64_Phdr;
6.3ELF Section Header Table
cpp
wx@VM-8-14-ubuntu:~/linux-learning/friend$ readelf -S main
There are 31 section headers, starting at offset 0x36b0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.pr[...] NOTE 0000000000000338 00000338
0000000000000030 0000000000000000 A 0 0 8
[ 3] .note.gnu.bu[...] NOTE 0000000000000368 00000368
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000038c 0000038c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003d8 000003d8
00000000000000c0 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000498 00000498
000000000000009f 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 0000000000000538 00000538
0000000000000010 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000000548 00000548
0000000000000030 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000000578 00000578
00000000000000c0 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000000638 00000638
0000000000000030 0000000000000018 AI 6 24 8
[12] .init PROGBITS 0000000000001000 00001000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000001020 00001020
0000000000000030 0000000000000010 AX 0 0 16
[14] .plt.got PROGBITS 0000000000001050 00001050
0000000000000010 0000000000000010 AX 0 0 16
[15] .plt.sec PROGBITS 0000000000001060 00001060
0000000000000020 0000000000000010 AX 0 0 16
[16] .text PROGBITS 0000000000001080 00001080
000000000000010c 0000000000000000 AX 0 0 16
[17] .fini PROGBITS 000000000000118c 0000118c
000000000000000d 0000000000000000 AX 0 0 4
[18] .rodata PROGBITS 0000000000002000 00002000
0000000000000004 0000000000000004 AM 0 0 4
[19] .eh_frame_hdr PROGBITS 0000000000002004 00002004
0000000000000034 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 0000000000002038 00002038
00000000000000ac 0000000000000000 A 0 0 8
[21] .init_array INIT_ARRAY 0000000000003da0 00002da0
0000000000000008 0000000000000008 WA 0 0 8
[22] .fini_array FINI_ARRAY 0000000000003da8 00002da8
0000000000000008 0000000000000008 WA 0 0 8
[23] .dynamic DYNAMIC 0000000000003db0 00002db0
0000000000000200 0000000000000010 WA 7 0 8
[24] .got PROGBITS 0000000000003fb0 00002fb0
0000000000000050 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000004000 00003000
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000004010 00003010
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00003010
000000000000002b 0000000000000001 MS 0 0 1
[28] .symtab SYMTAB 0000000000000000 00003040
0000000000000378 0000000000000018 29 18 8
[29] .strtab STRTAB 0000000000000000 000033b8
00000000000001d7 0000000000000000 0 0 1
[30] .shstrtab STRTAB 0000000000000000 0000358f
000000000000011a 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)
cpp
// 内核中关于ELF Section Header相关的数据结构
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
typedef struct elf64_shdr {
Elf64_Word sh_name; /* Section name, index in string tbl */
Elf64_Word sh_type; /* Type of section */
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Size of section in bytes */
Elf64_Word sh_link; /* Index of another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
6.4查看具体的sections信息
bash
wx@VM-8-14-ubuntu:~/linux-learning/friend$ objdump -S main
main: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 call *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 ret
Disassembly of section .plt:
0000000000001020 <.plt>:
1020: ff 35 92 2f 00 00 push 0x2f92(%rip) # 3fb8 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: ff 25 94 2f 00 00 jmp *0x2f94(%rip) # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x10>
102c: 0f 1f 40 00 nopl 0x0(%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 push $0x0
1039: e9 e2 ff ff ff jmp 1020 <_init+0x20>
103e: 66 90 xchg %ax,%ax
1040: f3 0f 1e fa endbr64
1044: 68 01 00 00 00 push $0x1
1049: e9 d2 ff ff ff jmp 1020 <_init+0x20>
104e: 66 90 xchg %ax,%ax
Disassembly of section .plt.got:
0000000000001050 <__cxa_finalize@plt>:
1050: f3 0f 1e fa endbr64
1054: ff 25 9e 2f 00 00 jmp *0x2f9e(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
105a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Disassembly of section .plt.sec:
0000000000001060 <mystr@plt>:
1060: f3 0f 1e fa endbr64
1064: ff 25 5e 2f 00 00 jmp *0x2f5e(%rip) # 3fc8 <mystr@Base>
106a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
0000000000001070 <mystdio@plt>:
1070: f3 0f 1e fa endbr64
1074: ff 25 56 2f 00 00 jmp *0x2f56(%rip) # 3fd0 <mystdio@Base>
107a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
Disassembly of section .text:
0000000000001080 <_start>:
1080: f3 0f 1e fa endbr64
1084: 31 ed xor %ebp,%ebp
1086: 49 89 d1 mov %rdx,%r9
1089: 5e pop %rsi
108a: 48 89 e2 mov %rsp,%rdx
108d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1091: 50 push %rax
1092: 54 push %rsp
1093: 45 31 c0 xor %r8d,%r8d
1096: 31 c9 xor %ecx,%ecx
1098: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 1169 <main>
109f: ff 15 33 2f 00 00 call *0x2f33(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34>
10a5: f4 hlt
10a6: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
10ad: 00 00 00
00000000000010b0 <deregister_tm_clones>:
10b0: 48 8d 3d 59 2f 00 00 lea 0x2f59(%rip),%rdi # 4010 <__TMC_END__>
10b7: 48 8d 05 52 2f 00 00 lea 0x2f52(%rip),%rax # 4010 <__TMC_END__>
10be: 48 39 f8 cmp %rdi,%rax
10c1: 74 15 je 10d8 <deregister_tm_clones+0x28>
10c3: 48 8b 05 16 2f 00 00 mov 0x2f16(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base>
10ca: 48 85 c0 test %rax,%rax
10cd: 74 09 je 10d8 <deregister_tm_clones+0x28>
10cf: ff e0 jmp *%rax
10d1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
10d8: c3 ret
10d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010e0 <register_tm_clones>:
10e0: 48 8d 3d 29 2f 00 00 lea 0x2f29(%rip),%rdi # 4010 <__TMC_END__>
10e7: 48 8d 35 22 2f 00 00 lea 0x2f22(%rip),%rsi # 4010 <__TMC_END__>
10ee: 48 29 fe sub %rdi,%rsi
10f1: 48 89 f0 mov %rsi,%rax
10f4: 48 c1 ee 3f shr $0x3f,%rsi
10f8: 48 c1 f8 03 sar $0x3,%rax
10fc: 48 01 c6 add %rax,%rsi
10ff: 48 d1 fe sar $1,%rsi
1102: 74 14 je 1118 <register_tm_clones+0x38>
1104: 48 8b 05 e5 2e 00 00 mov 0x2ee5(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base>
110b: 48 85 c0 test %rax,%rax
110e: 74 08 je 1118 <register_tm_clones+0x38>
1110: ff e0 jmp *%rax
1112: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1118: c3 ret
1119: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001120 <__do_global_dtors_aux>:
1120: f3 0f 1e fa endbr64
1124: 80 3d e5 2e 00 00 00 cmpb $0x0,0x2ee5(%rip) # 4010 <__TMC_END__>
112b: 75 2b jne 1158 <__do_global_dtors_aux+0x38>
112d: 55 push %rbp
112e: 48 83 3d c2 2e 00 00 cmpq $0x0,0x2ec2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1135: 00
1136: 48 89 e5 mov %rsp,%rbp
1139: 74 0c je 1147 <__do_global_dtors_aux+0x27>
113b: 48 8b 3d c6 2e 00 00 mov 0x2ec6(%rip),%rdi # 4008 <__dso_handle>
1142: e8 09 ff ff ff call 1050 <__cxa_finalize@plt>
1147: e8 64 ff ff ff call 10b0 <deregister_tm_clones>
114c: c6 05 bd 2e 00 00 01 movb $0x1,0x2ebd(%rip) # 4010 <__TMC_END__>
1153: 5d pop %rbp
1154: c3 ret
1155: 0f 1f 00 nopl (%rax)
1158: c3 ret
1159: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001160 <frame_dummy>:
1160: f3 0f 1e fa endbr64
1164: e9 77 ff ff ff jmp 10e0 <register_tm_clones>
0000000000001169 <main>:
1169: f3 0f 1e fa endbr64
116d: 55 push %rbp
116e: 48 89 e5 mov %rsp,%rbp
1171: b8 00 00 00 00 mov $0x0,%eax
1176: e8 f5 fe ff ff call 1070 <mystdio@plt>
117b: b8 00 00 00 00 mov $0x0,%eax
1180: e8 db fe ff ff call 1060 <mystr@plt>
1185: b8 00 00 00 00 mov $0x0,%eax
118a: 5d pop %rbp
118b: c3 ret
Disassembly of section .fini:
000000000000118c <_fini>:
118c: f3 0f 1e fa endbr64
1190: 48 83 ec 08 sub $0x8,%rsp
1194: 48 83 c4 08 add $0x8,%rsp
1198: c3 ret