目录
1. 团体学习思维导图
2. 库是什么
2.1 库的概念
2.2 库的后缀区分
3. 库的制作
3.1 静态库
3.1.1 利用.o文件毗连生成.exe文件
3.1.2 生成一个静态库
3.1.3 利用一个静态库
3.2 动态库
3.2.1 生成一个动态库
3.2.2 利用一个动态库
总结:
4. ELF文件
4.1 什么是ELF文件
4.1.1 以下文件格式为ELF:
4.1.1 什么是ELF文件:
4.2 静态链接,研究.o文件怎样链接
4.3 ELF文件的加载,ELF -> 进程
4.3.1 我们知道只有当一个可实行步伐实行时才会加载到内存,如果加载到内存他是否存在地址?
4.3.2 进程与ELF的联系
4.4 动态库怎样与我们进程关联
4.5 动态库怎样加载
4.5.1 全局偏移量表GOT(globaloffsettable)
4.5.2 库与库之间的调用
4.5.3 PLT
1. 团体学习思维导图
2. 库是什么
2.1 库的概念
库是一些函数实现的二进制代码,他们是可以直接实行,库的内容是一些我们经常必要利用的一些方法如:printf/scanf这类函数,我们要加速开发速率不大概再去实现一遍,我们一样平常都是利用c库封装好的,包上头文件直接利用!
2.2 库的后缀区分
3. 库的制作
总体概念(无论动静态库)
- 动/静态库不要实现main函数
- 头文件.h是对源文件实现方法的说明书
- 所有的库(动/静)都是源文件的,以.o后缀结尾
3.1 静态库
- 静态库的本质就是对.o文件举行一个打包,静态库.a->归档文件->利用时不必要举行解包->gcc/g++直接利用即可!
3.1.1 利用.o文件毗连生成.exe文件
场景一:ouyang同学实现了MyFile/MyStrlen,niuma同学不想实现想要直接利用,ouyang同学给他传来.o文件,他毗连自己实现的main函数调用即可!
- ouyang同门生成.o文件拷贝给niuma同学,利用说明书.h文件也必要拷贝过去
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ cd ../niuma/
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
- total 20
- -rw-rw-r-- 1 ouyang ouyang 517 Mar 3 16:11 main.c
- -rw-rw-r-- 1 ouyang ouyang 602 Mar 3 16:14 MyFile.h
- -rw-rw-r-- 1 ouyang ouyang 3400 Mar 3 16:14 MyFile.o
- -rw-rw-r-- 1 ouyang ouyang 49 Mar 3 16:14 MyStrlen.h
- -rw-rw-r-- 1 ouyang ouyang 1272 Mar 3 16:14 MyStrlen.o
复制代码
- niuma同门生成自己的main.o文件举行毗连生成可实行步伐.exe
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc *.o
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
- total 44
- -rwxrwxr-x 1 ouyang ouyang 13352 Mar 3 16:30 a.out
- -rw-rw-r-- 1 ouyang ouyang 514 Mar 3 16:22 main.c
- -rw-rw-r-- 1 ouyang ouyang 514 Mar 3 16:22 main_cp.c
- -rw-rw-r-- 1 ouyang ouyang 2248 Mar 3 16:30 main.o
- -rw-rw-r-- 1 ouyang ouyang 602 Mar 3 16:14 MyFile.h
- -rw-rw-r-- 1 ouyang ouyang 3400 Mar 3 16:14 MyFile.o
- -rw-rw-r-- 1 ouyang ouyang 49 Mar 3 16:14 MyStrlen.h
- -rw-rw-r-- 1 ouyang ouyang 1272 Mar 3 16:14 MyStrlen.o
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./a.out
- len = 14
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ cat log.txt
- Hello MyFile!
- Hello MyFile!
- Hello MyFile!
- Hello MyFile!
- Hello MyFile!
复制代码 3.1.2 生成一个静态库
- ar -rc mylibc.a *.o
- # rc --> replace and create 归档所有.o文件,如果存在就覆盖,不存在就创建
- # mylibc.a 静态库的名称 使用时需要去掉lib和.a
复制代码
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
- total 12
- -rw-rw-r-- 1 ouyang ouyang 514 Mar 3 16:22 main.c
- -rw-rw-r-- 1 ouyang ouyang 2009 Mar 3 16:45 stdc.tgz
复制代码 3.1.3 利用一个静态库
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -L stdc/lib/ -lmylibc
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./main
- len = 14
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls
- log.txt main main.c makefile stdc
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ cat log.txt
- Hello MyFile!
- Hello MyFile!
- Hello MyFile!
- Hello MyFile!
- Hello MyFile!
复制代码 我们会发现我们在利用gcc编译时,带上了两个选项和路径,这是为什么呢?
我们试一试不带这两个选项会发生什么?
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c
- main.c:4:20: fatal error: MyFile.h: No such file or directory
- #include "MyFile.h"
- ^
- compilation terminated.
复制代码 我们发现main.c文件找不到对应的头文件,这是由于头文件和main.c不在同一起径中!而我们系统默认去自己的头文件库中找不存在就会报错,因此-I是告诉gcc(默认在系统和当前目录下找寻)去哪个路径下找寻头文件。
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/
- /tmp/ccCjcIhR.o: In function `main':
- main.c:(.text+0x13): undefined reference to `Myfopen'
- main.c:(.text+0x69): undefined reference to `Myfwrite'
- main.c:(.text+0x75): undefined reference to `Myflush'
- main.c:(.text+0x8e): undefined reference to `Myfclose'
- main.c:(.text+0x9a): undefined reference to `MyStrlen'
- collect2: error: ld returned 1 exit status
复制代码 我们现在的问题是找不到头文件对应实现的方法了,我们同样的必要告诉gcc我们要利用哪个库举行毗连。
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -lmylibc
- /usr/bin/ld: cannot find -lmylibc
- collect2: error: ld returned 1 exit status
复制代码 我们告诉了gcc找寻mylibc库举行毗连,但是系统默认是在/user/bin/ld探求,同样的我们的库并没实现在其中我们必要告诉我们库对应的位置,利用-L+路径。
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -L stdc/lib/ -lmylibc
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
- linux-vdso.so.1 => (0x00007fff5d559000)
- libc.so.6 => /lib64/libc.so.6 (0x00007fa970340000)
- /lib64/ld-linux-x86-64.so.2 (0x00007fa97070e000)
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls
- main main.c makefile stdc
复制代码 我们通过以上过程可以发现,库必要安装到系统中才方便利用,而库安装-->本质就是拷贝对应的系统之中。
我们也发现ldd我们的可实行步伐并没有我们的静态库的链接,这是由于我们的静态库已经拷贝至可实行步伐中了,这也是为什么静态链接的可实行步伐空间大的缘故原由,包括纵然我们删除了之前的静态库,步伐仍旧可以实行的缘故原由!
3.2 动态库
3.2.1 生成一个动态库
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ make
- gcc -fPIC -c MyFile.c
- gcc -fPIC -c MyStrlen.c
- gcc -o libmylibc.so MyFile.o MyStrlen.o -shared
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ ll
- total 48
- -rwxrwxr-x 1 ouyang ouyang 12864 Mar 3 19:33 libmylibc.so
- -rw-rw-r-- 1 ouyang ouyang 259 Mar 3 19:33 makefile
- -rw-rw-r-- 1 ouyang ouyang 245 Mar 3 17:22 makefile_static
- -rw-rw-r-- 1 ouyang ouyang 2084 Mar 3 15:56 MyFile.c
- -rw-rw-r-- 1 ouyang ouyang 602 Mar 3 15:56 MyFile.h
- -rw-rw-r-- 1 ouyang ouyang 3456 Mar 3 19:33 MyFile.o
- -rw-rw-r-- 1 ouyang ouyang 129 Mar 3 16:00 MyStrlen.c
- -rw-rw-r-- 1 ouyang ouyang 49 Mar 3 16:00 MyStrlen.h
- -rw-rw-r-- 1 ouyang ouyang 1272 Mar 3 19:33 MyStrlen.o
复制代码 3.2.2 利用一个动态库
我们按照利用静态库的方式生成一个可实行步伐
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -L stdc/lib/ -lmylibc
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
- total 24
- -rwxrwxr-x 1 ouyang ouyang 8800 Mar 3 19:40 main
- -rw-rw-r-- 1 ouyang ouyang 514 Mar 3 16:22 main.c
- -rw-rw-r-- 1 ouyang ouyang 94 Mar 3 19:39 makefile
- drwxrwxr-x 4 ouyang ouyang 4096 Mar 3 19:34 stdc
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./main
- ./main: error while loading shared libraries: libmylibc.so: cannot open shared object file: No such file or directory
复制代码
我们按照我们之前的方式成功生成可实行步伐,但是却不可以实行,查看main发现没有找到我们自己封装的动态库,这是为什么呢?由于我们前面的指令都是告诉gcc我们的库在什么位置,我们现在的命令是实行可实行步伐,我们并没有告诉我们的可实行步伐我们的动态库位置,因此导致了动态库找不到的问题!
- 拷贝至系统:我们发现我们并没有告诉main可实行步伐我们c尺度库的位置,它依然找到,说明默认会去系统查找,我们只必要拷贝至系统就可以链接了
- 拷贝 .so 文件到系统共享库路径下,⼀般指 /usr/lib、/usr/local/lib、/lib64
- 建立软毗连:向系统共享库路径下建立同名软毗连,毗连指向我们实现的动态库
- 更改环境变量: LD_LIBRARY_PATH (没有可以自己创建一个)
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ echo $LD_LIBRARY_PATH
- :/home/ouyang/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
复制代码- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ pwd
- /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ echo $LD_LIBRARY_PATH
- :/home/ouyang/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ cd ..
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ stdc]$ cd ..
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
- total 24
- -rwxrwxr-x 1 ouyang ouyang 8800 Mar 3 19:40 main
- -rw-rw-r-- 1 ouyang ouyang 514 Mar 3 16:22 main.c
- -rw-rw-r-- 1 ouyang ouyang 94 Mar 3 19:39 makefile
- drwxrwxr-x 4 ouyang ouyang 4096 Mar 3 19:34 stdc
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
- linux-vdso.so.1 => (0x00007fff30141000)
- libmylibc.so => /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib/libmylibc.so (0x00007fa667a86000)
- libc.so.6 => /lib64/libc.so.6 (0x00007fa6676b8000)
- /lib64/ld-linux-x86-64.so.2 (0x00007fa667c89000)
复制代码- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./main
- len = 14
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls
- log.txt main main.c makefile stdc
复制代码 当然我们重启之后这个临时配置的环境变量就会消散,要永久保存必要系统配置。
4. ldconfig方案:配置/etc/ld.so.conf.d/ , ldconfig更新生效。
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls /etc/ld.so.conf.d/
- kernel-3.10.0-957.21.3.el7.x86_64.conf kernel-3.10.0-957.el7.x86_64.conf mysql-x86_64.conf
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ sudo touch /etc/ld.so.conf.d/mylibso.conf
- [sudo] password for ouyang:
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls /etc/ld.so.conf.d/
- kernel-3.10.0-957.21.3.el7.x86_64.conf kernel-3.10.0-957.el7.x86_64.conf mylibso.conf mysql-x86_64.conf
复制代码 这个操作必要管路员才气操作!
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ cat /etc/ld.so.conf.d/mylibso.conf
- /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
- linux-vdso.so.1 => (0x00007fff6c2e8000)
- libmylibc.so => /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib/libmylibc.so (0x00007f3bb5efe000)
- libc.so.6 => /lib64/libc.so.6 (0x00007f3bb5b30000)
- /lib64/ld-linux-x86-64.so.2 (0x00007f3bb6101000)
复制代码 总结:
- gcc/g++默认会去利用动态库(动静态库同时存在时)
- 如果动静态库同时存在非要静态链接必要带上 -static选项
- 如只存在静态库,无论加不加-static,都利用静态链接
4. ELF文件
4.1 什么是ELF文件
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_3_5]$ file main.exe
- main.exe: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=f509ef97646a4c0b361e15ac85eda3db9048a110, not stripped
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_3_5]$ file code.o
- code.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_3_5]$ file main.o
- main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ file libmylibc.so
- libmylibc.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=6c459975831b4db7227b252daf3b14bdb33d70e2, not stripped
复制代码 通过以上代码我们会发现一个问题,可实行文件.exe和重定位目的文件.o文件类型都是ELF类型的文件,那么哪些文件是ELF文件,什么是ELF文件?
4.1.1 以下文件格式为ELF:
- 可重定向目的文件:以.o为结尾的文件
- 可实行步伐文件:可以实行的文件
- 共享目的文件:以.so结尾的文件,即动态库文件
4.1.1 什么是ELF文件:
我们先相识ELF文件的构成部门:
我们之前所认知的可实行文件.exe我们简单的说是由代码+数据构成!实在可实行文件作为ELF格式文件,是由多个.o文件链接形成的,由于文件类型的相同其中少不了把多个文件合并成一个文件的过程!
- 多个section(节)会合并成segment(段)
- 那么将节合并成段必要一个合并规则,规范合并哪些,怎么合并必要有一个准则尺度,这个合并的原则存在于Program Header Table之中
我们拿两段简单代码来观察合并之前的准备,其中我们必要用到以下命令和代码:
- 代码:
- /* main.c */
- #include <stdio.h>
- extern void Run();
- int main()
- {
- Run();
- printf("I am main.c\n");
- return 0;
- }
复制代码- /* code.c */
- #include <stdio.h>
- void Run()
- {
- printf("Runing......!\n");
- }
复制代码 - readelf -S _ELF文件_ -- 读取section Header Table(这是一个数组,其中包含对section的形貌)
- readelf -l _ELF文件_ -- 读取 Program Headers
section Header Table中形貌了各个节的信息。
红框中的形貌是权限R/W/E。
从图中我们可以得知那些数据节(section)必要合并在一起成为一个段(segment)。
3. 为什么要将section合并成一个segment?
这个问题我们必要先引进文件的加载,我们知道在Ext文件系统之中我们为了服从会一次去访问八个扇区也就是一个块(4KB巨细), 而内存的每一次申请空间为了能够方便交互也是4KB巨细,也就是说我们存储一个数据会出现碎片化,存储不足4KB的情况,这个空间我们叫做页面,合并可以减少页面的碎片化,如.text部门4097字节这就必要2个页面存储,此时.init部门的巨细为256字节必要一个页面,一旦合并只必要2个页面即可,提高了工具利用率。
4. section Header Table(这是一个数组,其中包含对section的形貌)有什么用?
这个节表头看起来像节的管理者,他可以清楚的告诉我们节的信息。
- 链接视角:我们在链接.o文件必要合并section为一个segment,那么那些必要合并在一起,那些不合并在一起,section Header Table就是告诉静态链接节的信息位置,让链接去区分合并!
- 加载视角:我们观察第二张图可以发现有形貌权限的内容,这一点很关键,我们知道我们平常操作文件时必要对应的权限,这个权限是加载到内存的,但是系统怎么知道那些内容可读还是可写?这就section Header Table的作用了,他会告诉操作系统哪些该加载到什么地方,完成文件的初始化。
5. Program Header Table的作用?
在相识作用之前我们必要知道利用什么命令去查看这个表头:
- readelf -h _ELF文件_ # 查看 Program Header Table
复制代码
- Entry point address:形貌步伐的入口地址,这个有什么用呢?
- Start of program headers:program headers的起始位置
- Start of section headers:section headers的起始位置
我们发现program headers Table保存的是整个ELF文件各个部门的分布信息位置,它的重要目的是定位文件的其他部门。
4.2 静态链接,研究.o文件怎样链接
研究链接之前我们必要得到得到链接前文件中的内容有什么:
- # 将目标文件进行反汇编
- objdump -d XXX.o > XXX.s
复制代码 
以上我们发现.o文件中反汇编后callq去调用的函数都没有地址?地址为什么都是0?这是由于在编译时,编译器并不知道对应的函数地址在哪,只能先填充0来体现,等到后面链接时在举行地址重定位!这就是为什么我们称.o文件是可重定位目的文件。

- ouyang@iZ2ze0j6dd76e0o9qypo2rZ:~/linux_-git_-warehouse/dir_2025_3_6$ readelf -s main.exe
- Symbol table '.dynsym' contains 7 entries:
- Num: Value Size Type Bind Vis Ndx Name
- 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
- 1: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
- 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.2.5 (2)
- 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2)
- 4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
- 5: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
- 6: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
- Symbol table '.symtab' contains 67 entries:
- Num: Value Size Type Bind Vis Ndx Name
- 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
- 1: 0000000000000318 0 SECTION LOCAL DEFAULT 1
- 2: 0000000000000338 0 SECTION LOCAL DEFAULT 2
- 3: 0000000000000358 0 SECTION LOCAL DEFAULT 3
- 4: 000000000000037c 0 SECTION LOCAL DEFAULT 4
- 5: 00000000000003a0 0 SECTION LOCAL DEFAULT 5
- 6: 00000000000003c8 0 SECTION LOCAL DEFAULT 6
- 7: 0000000000000470 0 SECTION LOCAL DEFAULT 7
- 8: 00000000000004f2 0 SECTION LOCAL DEFAULT 8
- 9: 0000000000000500 0 SECTION LOCAL DEFAULT 9
- 10: 0000000000000520 0 SECTION LOCAL DEFAULT 10
- 11: 00000000000005e0 0 SECTION LOCAL DEFAULT 11
- 12: 0000000000001000 0 SECTION LOCAL DEFAULT 12
- 13: 0000000000001020 0 SECTION LOCAL DEFAULT 13
- 14: 0000000000001040 0 SECTION LOCAL DEFAULT 14
- 15: 0000000000001050 0 SECTION LOCAL DEFAULT 15
- 16: 0000000000001060 0 SECTION LOCAL DEFAULT 16
- 17: 0000000000001208 0 SECTION LOCAL DEFAULT 17
- 18: 0000000000002000 0 SECTION LOCAL DEFAULT 18
- 19: 0000000000002020 0 SECTION LOCAL DEFAULT 19
- 20: 0000000000002070 0 SECTION LOCAL DEFAULT 20
- 21: 0000000000003db8 0 SECTION LOCAL DEFAULT 21
- 22: 0000000000003dc0 0 SECTION LOCAL DEFAULT 22
- 23: 0000000000003dc8 0 SECTION LOCAL DEFAULT 23
- 24: 0000000000003fb8 0 SECTION LOCAL DEFAULT 24
- 25: 0000000000004000 0 SECTION LOCAL DEFAULT 25
- 26: 0000000000004010 0 SECTION LOCAL DEFAULT 26
- 27: 0000000000000000 0 SECTION LOCAL DEFAULT 27
- 28: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
- 29: 0000000000001090 0 FUNC LOCAL DEFAULT 16 deregister_tm_clones
- 30: 00000000000010c0 0 FUNC LOCAL DEFAULT 16 register_tm_clones
- 31: 0000000000001100 0 FUNC LOCAL DEFAULT 16 __do_global_dtors_aux
- 32: 0000000000004010 1 OBJECT LOCAL DEFAULT 26 completed.8061
- 33: 0000000000003dc0 0 OBJECT LOCAL DEFAULT 22 __do_global_dtors_aux_fin
- 34: 0000000000001140 0 FUNC LOCAL DEFAULT 16 frame_dummy
- 35: 0000000000003db8 0 OBJECT LOCAL DEFAULT 21 __frame_dummy_init_array_
- 36: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
- 37: 0000000000000000 0 FILE LOCAL DEFAULT ABS code.c
- 38: 0000000000000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
- 39: 0000000000002194 0 OBJECT LOCAL DEFAULT 20 __FRAME_END__
- 40: 0000000000000000 0 FILE LOCAL DEFAULT ABS
- 41: 0000000000003dc0 0 NOTYPE LOCAL DEFAULT 21 __init_array_end
- 42: 0000000000003dc8 0 OBJECT LOCAL DEFAULT 23 _DYNAMIC
- 43: 0000000000003db8 0 NOTYPE LOCAL DEFAULT 21 __init_array_start
- 44: 0000000000002020 0 NOTYPE LOCAL DEFAULT 19 __GNU_EH_FRAME_HDR
- 45: 0000000000003fb8 0 OBJECT LOCAL DEFAULT 24 _GLOBAL_OFFSET_TABLE_
- 46: 0000000000001000 0 FUNC LOCAL DEFAULT 12 _init
- 47: 0000000000001200 5 FUNC GLOBAL DEFAULT 16 __libc_csu_fini
- 48: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
- 49: 0000000000004000 0 NOTYPE WEAK DEFAULT 25 data_start
- 50: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@@GLIBC_2.2.5
- 51: 000000000000116e 23 FUNC GLOBAL DEFAULT 16 Run
- 52: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 25 _edata
- 53: 0000000000001208 0 FUNC GLOBAL HIDDEN 17 _fini
- 54: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
- 55: 0000000000004000 0 NOTYPE GLOBAL DEFAULT 25 __data_start
- 56: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
- 57: 0000000000004008 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
- 58: 0000000000002000 4 OBJECT GLOBAL DEFAULT 18 _IO_stdin_used
- 59: 0000000000001190 101 FUNC GLOBAL DEFAULT 16 __libc_csu_init
- 60: 0000000000004018 0 NOTYPE GLOBAL DEFAULT 26 _end
- 61: 0000000000001060 47 FUNC GLOBAL DEFAULT 16 _start
- 62: 0000000000004010 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
- 63: 0000000000001149 37 FUNC GLOBAL DEFAULT 16 main
- 64: 0000000000004010 0 OBJECT GLOBAL HIDDEN 25 __TMC_END__
- 65: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
- 66: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@@GLIBC_2.2
复制代码 其中UND(体现未界说),我们看图会发现两个.o对于对于调用的函数都是未界说,但是我们查看main.exe会发现对于缺失的函数地址已经举行重定位了,静态链接->.o合并,地址修正!

4.3 ELF文件的加载,ELF -> 进程
4.3.1 我们知道只有当一个可实行步伐实行时才会加载到内存,如果加载到内存他是否存在地址?
过去我们对于一个可实行步伐编址偏向于逻辑地址,访问时必要起始地址加偏移量,当代计算机工作的时候,都采用"平展模式"举行工作。以是也要求ELF对自己的代码和数据举行统⼀编址,从反汇编的文件中我们可以看出这一点:
- main.exe: 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__>
- 100f: 48 85 c0 test %rax,%rax
- 1012: 74 02 je 1016 <_init+0x16>
- 1014: ff d0 callq *%rax
- 1016: 48 83 c4 08 add $0x8,%rsp
- 101a: c3 retq
- Disassembly of section .plt:
- 0000000000001020 <.plt>:
- 1020: ff 35 9a 2f 00 00 pushq 0x2f9a(%rip) # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
- 1026: f2 ff 25 9b 2f 00 00 bnd jmpq *0x2f9b(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
- 102d: 0f 1f 00 nopl (%rax)
- 1030: f3 0f 1e fa endbr64
- 1034: 68 00 00 00 00 pushq $0x0
- 1039: f2 e9 e1 ff ff ff bnd jmpq 1020 <.plt>
- 103f: 90 nop
- .............................................
复制代码 4.3.2 进程与ELF的联系
- 进程mm_struct、vm_area_struct在进程刚刚创建的时候,初始化数据从那里来的?从ELF各个segment来,每个segment有自己的起始地址和自己的长度,用来初始化内核结构中的[start,end]等范围数据,另外在用详细地址,填充页表
- CPU会通过Entry point address字段获取步伐入口,进入CPU的地址是虚拟地址,EIP获取入口后得到虚拟地址,将虚拟地址交给CR3,通过查看页表的映射关系得到物理地址举行访问!
以是:虚拟地址机制,不光光OS要支持,编译器也要支持!
4.4 动态库怎样与我们进程关联
- 动态库必要被进程看见:动态库必要映射到进程的地址空间上!
- 被进程调用:在进程的地址空间调转!
整个过程:动态库加载-->物理内存-->页表映射(不会重复加载,多个进程也可以利用)-->共享库
4.5 动态库怎样加载
- 动态链接差别于静态链接,他将链接的整个过程推迟到了步伐加载时候举行!
我们的可实行步伐编译时:调用动态库中的函数方法时会先填充一个0地址,期待动态库加载到内存中,⼀旦它的内存地址被确定,我们就可以去修正动态库中的那些函数跳转地址了。
- 在我们的步伐开始实行时我们第一个入口并不是main函数,步伐的入口点是_start,这是⼀个由C运行时库(通常是glibc)或链接器(如ld)提供的特殊函数。
_start的作用:
- 设置堆栈:创建一个初始的堆栈环境
- 初始化数据段:将步伐的数据段(如全局变量和静态变量)从初始化数据段复制到相应的内存位置,并清零未初始化的数据段
- 动态链接:这是关键的⼀步, _start 函数会调用动态链接器的代码来解析和加载步伐所依靠的动态库(sharedlibraries)。动态链接器会处理惩罚所有的符号解析和重定位,确保步伐中的函数调用和变量访问能够准确地映射到动态库中的现实地址。
动态链接器:
- [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
- linux-vdso.so.1 => (0x00007fff5d559000)
- libc.so.6 => /lib64/libc.so.6 (0x00007fa970340000)
- /lib64/ld-linux-x86-64.so.2 (0x00007fa97070e000)
复制代码 这个动态链接器会帮我们在步伐运行时加载动态库,查找动态库会去对应的环境变量(LD_LIBRARY_PATH)大概配置文件(/etc/ld.so.conf.d/)中查找,但是每次查找都会消耗一定的资源时间,以是其内部还存在一个缓存文件用于保存已知的动态库相干信息(名称+路径等等),动态链接器会优先搜寻这个缓存文件!
动态库也是一个ELF文件,加载到内存时也是平展模式,然后通过页表映射关系映射到对应进程的共享区部门,但是我们知道此时进程的函数代码都保存在代码区,而代码区只是可读权限,那么怎么去修正我们调用函数的地址呢?
4.5.1 全局偏移量表GOT(globaloffsettable)
为相识决上面代码区不能修改的问题,我们在代码区预留了一个用来存放函数的跳转地址,它也被叫做全局偏移表GOT。
- .got: 加载重定向表,GOT表对于每个进程的映射部门都是不一样的,因此每个进程都拥有一份独立的GOT表,进程之间的GOT表不可以共享!
- 每次调用函数时,都会先举行查表跳转到对应的函数地址!
- 这种方式实现的动态链接就被叫做 PIC 地址无关代码 。换句话说,我们的动态库不必要做任何修改,被加载到任意内存地址都能够正常运行,并且能够被所有进程共享,这也是为什么之前我们给编译器指定-fPIC参数的缘故原由,PIC = 相对编址 + GOT。
4.5.2 库与库之间的调用
我们平常不止有进程对库举行调用,偶尔候还会出现库和库之间的调用,那么怎么明白库与库之间的关联呢?实在库中也存在.got,和可实行步伐一样!这也是为什么可实行和库文件都是ELF文件!
4.5.3 PLT
- 由于动态链接在步伐加载(每次加载动态库都会加载到内存的差别地址位置)的时候必要对大量函数举行重定位,这⼀步显然是非常耗时的。为了进⼀步低落开销,我们的操作系统还做了⼀些其他的优化,好比延迟绑定,大概也叫PLT(Procedure Linkage Table)->过程链接表。与其在步伐⼀开始就对所有函数举行重定位,不如将这个过程推迟到函数第⼀次被调用的时候,由于绝大多数动态库中的函数大概在步伐运行期间⼀次都不会被利用到。
- 思路是:GOT中的跳转地址默认会指向⼀段辅助代码,它也被叫做桩代码/stup。在我们第⼀次调用函数的时候,这段代码会负责查询真正函数的跳转地址,并且去更新GOT表。于是我们再次调用函数的时候,就会直接跳转到动态库中真正的函数实现。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |