【Linux】动/静态库

火影  金牌会员 | 2025-3-13 19:44:45 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 994|帖子 994|积分 2982

目录
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 库的后缀区分



  • 对于Linux系统

    • 静态库 .a
    • 动态库 .so

  • 对于Windows系统

    • 静态库 .lib
    • 动态库 .dll

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文件也必要拷贝过去
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ cd ../niuma/
  2. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
  3. total 20
  4. -rw-rw-r-- 1 ouyang ouyang  517 Mar  3 16:11 main.c
  5. -rw-rw-r-- 1 ouyang ouyang  602 Mar  3 16:14 MyFile.h
  6. -rw-rw-r-- 1 ouyang ouyang 3400 Mar  3 16:14 MyFile.o
  7. -rw-rw-r-- 1 ouyang ouyang   49 Mar  3 16:14 MyStrlen.h
  8. -rw-rw-r-- 1 ouyang ouyang 1272 Mar  3 16:14 MyStrlen.o
复制代码


  • niuma同门生成自己的main.o文件举行毗连生成可实行步伐.exe
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc *.o
  2. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
  3. total 44
  4. -rwxrwxr-x 1 ouyang ouyang 13352 Mar  3 16:30 a.out
  5. -rw-rw-r-- 1 ouyang ouyang   514 Mar  3 16:22 main.c
  6. -rw-rw-r-- 1 ouyang ouyang   514 Mar  3 16:22 main_cp.c
  7. -rw-rw-r-- 1 ouyang ouyang  2248 Mar  3 16:30 main.o
  8. -rw-rw-r-- 1 ouyang ouyang   602 Mar  3 16:14 MyFile.h
  9. -rw-rw-r-- 1 ouyang ouyang  3400 Mar  3 16:14 MyFile.o
  10. -rw-rw-r-- 1 ouyang ouyang    49 Mar  3 16:14 MyStrlen.h
  11. -rw-rw-r-- 1 ouyang ouyang  1272 Mar  3 16:14 MyStrlen.o
  12. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./a.out
  13. len = 14
  14. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ cat log.txt
  15. Hello MyFile!
  16. Hello MyFile!
  17. Hello MyFile!
  18. Hello MyFile!
  19. Hello MyFile!
复制代码
3.1.2 生成一个静态库 

  1. ar -rc mylibc.a *.o
  2. # rc --> replace and create 归档所有.o文件,如果存在就覆盖,不存在就创建
  3. # mylibc.a 静态库的名称 使用时需要去掉lib和.a
复制代码

  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
  2. total 12
  3. -rw-rw-r-- 1 ouyang ouyang  514 Mar  3 16:22 main.c
  4. -rw-rw-r-- 1 ouyang ouyang 2009 Mar  3 16:45 stdc.tgz
复制代码
3.1.3 利用一个静态库 

  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -L stdc/lib/ -lmylibc
  2. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./main
  3. len = 14
  4. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls
  5. log.txt  main  main.c  makefile  stdc
  6. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ cat log.txt
  7. Hello MyFile!
  8. Hello MyFile!
  9. Hello MyFile!
  10. Hello MyFile!
  11. Hello MyFile!
复制代码
我们会发现我们在利用gcc编译时,带上了两个选项和路径,这是为什么呢?


  • -I,-L -lmylibc
我们试一试不带这两个选项会发生什么?
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c
  2. main.c:4:20: fatal error: MyFile.h: No such file or directory
  3. #include "MyFile.h"
  4.                     ^
  5. compilation terminated.
复制代码
我们发现main.c文件找不到对应的头文件,这是由于头文件和main.c不在同一起径中!而我们系统默认去自己的头文件库中找不存在就会报错,因此-I是告诉gcc(默认在系统和当前目录下找寻)去哪个路径下找寻头文件。 
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/
  2. /tmp/ccCjcIhR.o: In function `main':
  3. main.c:(.text+0x13): undefined reference to `Myfopen'
  4. main.c:(.text+0x69): undefined reference to `Myfwrite'
  5. main.c:(.text+0x75): undefined reference to `Myflush'
  6. main.c:(.text+0x8e): undefined reference to `Myfclose'
  7. main.c:(.text+0x9a): undefined reference to `MyStrlen'
  8. collect2: error: ld returned 1 exit status
复制代码
我们现在的问题是找不到头文件对应实现的方法了,我们同样的必要告诉gcc我们要利用哪个库举行毗连。 
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -lmylibc
  2. /usr/bin/ld: cannot find -lmylibc
  3. collect2: error: ld returned 1 exit status
复制代码
我们告诉了gcc找寻mylibc库举行毗连,但是系统默认是在/user/bin/ld探求,同样的我们的库并没实现在其中我们必要告诉我们库对应的位置,利用-L+路径。 
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -L stdc/lib/ -lmylibc
  2. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
  3.         linux-vdso.so.1 =>  (0x00007fff5d559000)
  4.         libc.so.6 => /lib64/libc.so.6 (0x00007fa970340000)
  5.         /lib64/ld-linux-x86-64.so.2 (0x00007fa97070e000)
  6. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls
  7. main  main.c  makefile  stdc
复制代码
我们通过以上过程可以发现,库必要安装到系统中才方便利用,而库安装-->本质就是拷贝对应的系统之中。
我们也发现ldd我们的可实行步伐并没有我们的静态库的链接,这是由于我们的静态库已经拷贝至可实行步伐中了,这也是为什么静态链接的可实行步伐空间大的缘故原由,包括纵然我们删除了之前的静态库,步伐仍旧可以实行的缘故原由!
3.2 动态库

3.2.1 生成一个动态库


  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ make
  2. gcc -fPIC -c MyFile.c
  3. gcc -fPIC -c MyStrlen.c
  4. gcc -o libmylibc.so MyFile.o MyStrlen.o -shared
  5. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ ll
  6. total 48
  7. -rwxrwxr-x 1 ouyang ouyang 12864 Mar  3 19:33 libmylibc.so
  8. -rw-rw-r-- 1 ouyang ouyang   259 Mar  3 19:33 makefile
  9. -rw-rw-r-- 1 ouyang ouyang   245 Mar  3 17:22 makefile_static
  10. -rw-rw-r-- 1 ouyang ouyang  2084 Mar  3 15:56 MyFile.c
  11. -rw-rw-r-- 1 ouyang ouyang   602 Mar  3 15:56 MyFile.h
  12. -rw-rw-r-- 1 ouyang ouyang  3456 Mar  3 19:33 MyFile.o
  13. -rw-rw-r-- 1 ouyang ouyang   129 Mar  3 16:00 MyStrlen.c
  14. -rw-rw-r-- 1 ouyang ouyang    49 Mar  3 16:00 MyStrlen.h
  15. -rw-rw-r-- 1 ouyang ouyang  1272 Mar  3 19:33 MyStrlen.o
复制代码
3.2.2 利用一个动态库

我们按照利用静态库的方式生成一个可实行步伐
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ gcc -o main main.c -I stdc/include/ -L stdc/lib/ -lmylibc
  2. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
  3. total 24
  4. -rwxrwxr-x 1 ouyang ouyang 8800 Mar  3 19:40 main
  5. -rw-rw-r-- 1 ouyang ouyang  514 Mar  3 16:22 main.c
  6. -rw-rw-r-- 1 ouyang ouyang   94 Mar  3 19:39 makefile
  7. drwxrwxr-x 4 ouyang ouyang 4096 Mar  3 19:34 stdc
  8. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./main
  9. ./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 (没有可以自己创建一个)

  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ echo $LD_LIBRARY_PATH
  2. :/home/ouyang/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
复制代码
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ pwd
  2. /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib
  3. [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
  4. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ echo $LD_LIBRARY_PATH
  5. :/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
  6. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ lib]$ cd ..
  7. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ stdc]$ cd ..
  8. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ll
  9. total 24
  10. -rwxrwxr-x 1 ouyang ouyang 8800 Mar  3 19:40 main
  11. -rw-rw-r-- 1 ouyang ouyang  514 Mar  3 16:22 main.c
  12. -rw-rw-r-- 1 ouyang ouyang   94 Mar  3 19:39 makefile
  13. drwxrwxr-x 4 ouyang ouyang 4096 Mar  3 19:34 stdc
  14. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
  15.         linux-vdso.so.1 =>  (0x00007fff30141000)
  16.         libmylibc.so => /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib/libmylibc.so (0x00007fa667a86000)
  17.         libc.so.6 => /lib64/libc.so.6 (0x00007fa6676b8000)
  18.         /lib64/ld-linux-x86-64.so.2 (0x00007fa667c89000)
复制代码
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ./main
  2. len = 14
  3. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls
  4. log.txt  main  main.c  makefile  stdc
复制代码
当然我们重启之后这个临时配置的环境变量就会消散,要永久保存必要系统配置。
4. ldconfig方案:配置/etc/ld.so.conf.d/ , ldconfig更新生效。
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls /etc/ld.so.conf.d/
  2. kernel-3.10.0-957.21.3.el7.x86_64.conf  kernel-3.10.0-957.el7.x86_64.conf  mysql-x86_64.conf
  3. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ sudo touch /etc/ld.so.conf.d/mylibso.conf
  4. [sudo] password for ouyang:
  5. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ls /etc/ld.so.conf.d/
  6. 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
复制代码
 这个操作必要管路员才气操作!
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ cat /etc/ld.so.conf.d/mylibso.conf
  2. /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib
  3. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
  4.         linux-vdso.so.1 =>  (0x00007fff6c2e8000)
  5.         libmylibc.so => /home/ouyang/Linux_Git/linux_-git_-warehouse/dir_2025_3_3_lib/niuma/stdc/lib/libmylibc.so (0x00007f3bb5efe000)
  6.         libc.so.6 => /lib64/libc.so.6 (0x00007f3bb5b30000)
  7.         /lib64/ld-linux-x86-64.so.2 (0x00007f3bb6101000)
复制代码
总结:


  • gcc/g++默认会去利用动态库(动静态库同时存在时)
  • 如果动静态库同时存在非要静态链接必要带上 -static选项
  • 如只存在静态库,无论加不加-static,都利用静态链接
4. ELF文件

4.1 什么是ELF文件

  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_3_5]$ file main.exe
  2. 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
  3. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_3_5]$ file code.o
  4. code.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
  5. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_3_5]$ file main.o
  6. main.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
  7. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ ouyang]$ file libmylibc.so
  8. 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之中
我们拿两段简单代码来观察合并之前的准备,其中我们必要用到以下命令和代码:


  • 代码:
    1. /* main.c */
    2. #include <stdio.h>
    3. extern void Run();
    4. int main()
    5. {
    6.     Run();
    7.     printf("I am main.c\n");
    8.     return 0;
    9. }
    复制代码
    1. /* code.c */
    2. #include <stdio.h>
    3. void Run()
    4. {
    5.     printf("Runing......!\n");
    6. }
    复制代码
  • 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的作用?
在相识作用之前我们必要知道利用什么命令去查看这个表头:
  1. 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文件怎样链接

研究链接之前我们必要得到得到链接前文件中的内容有什么:
  1. # 将目标文件进行反汇编
  2. objdump -d XXX.o > XXX.s
复制代码
 

以上我们发现.o文件中反汇编后callq去调用的函数都没有地址?地址为什么都是0?这是由于在编译时,编译器并不知道对应的函数地址在哪,只能先填充0来体现,等到后面链接时在举行地址重定位!这就是为什么我们称.o文件是可重定位目的文件。 
  1. readelf -s XXX.o # 简略读取
复制代码

  1. ouyang@iZ2ze0j6dd76e0o9qypo2rZ:~/linux_-git_-warehouse/dir_2025_3_6$ readelf -s main.exe
  2. Symbol table '.dynsym' contains 7 entries:
  3.    Num:    Value          Size Type    Bind   Vis      Ndx Name
  4.      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
  5.      1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
  6.      2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
  7.      3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
  8.      4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
  9.      5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
  10.      6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
  11. Symbol table '.symtab' contains 67 entries:
  12.    Num:    Value          Size Type    Bind   Vis      Ndx Name
  13.      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
  14.      1: 0000000000000318     0 SECTION LOCAL  DEFAULT    1
  15.      2: 0000000000000338     0 SECTION LOCAL  DEFAULT    2
  16.      3: 0000000000000358     0 SECTION LOCAL  DEFAULT    3
  17.      4: 000000000000037c     0 SECTION LOCAL  DEFAULT    4
  18.      5: 00000000000003a0     0 SECTION LOCAL  DEFAULT    5
  19.      6: 00000000000003c8     0 SECTION LOCAL  DEFAULT    6
  20.      7: 0000000000000470     0 SECTION LOCAL  DEFAULT    7
  21.      8: 00000000000004f2     0 SECTION LOCAL  DEFAULT    8
  22.      9: 0000000000000500     0 SECTION LOCAL  DEFAULT    9
  23.     10: 0000000000000520     0 SECTION LOCAL  DEFAULT   10
  24.     11: 00000000000005e0     0 SECTION LOCAL  DEFAULT   11
  25.     12: 0000000000001000     0 SECTION LOCAL  DEFAULT   12
  26.     13: 0000000000001020     0 SECTION LOCAL  DEFAULT   13
  27.     14: 0000000000001040     0 SECTION LOCAL  DEFAULT   14
  28.     15: 0000000000001050     0 SECTION LOCAL  DEFAULT   15
  29.     16: 0000000000001060     0 SECTION LOCAL  DEFAULT   16
  30.     17: 0000000000001208     0 SECTION LOCAL  DEFAULT   17
  31.     18: 0000000000002000     0 SECTION LOCAL  DEFAULT   18
  32.     19: 0000000000002020     0 SECTION LOCAL  DEFAULT   19
  33.     20: 0000000000002070     0 SECTION LOCAL  DEFAULT   20
  34.     21: 0000000000003db8     0 SECTION LOCAL  DEFAULT   21
  35.     22: 0000000000003dc0     0 SECTION LOCAL  DEFAULT   22
  36.     23: 0000000000003dc8     0 SECTION LOCAL  DEFAULT   23
  37.     24: 0000000000003fb8     0 SECTION LOCAL  DEFAULT   24
  38.     25: 0000000000004000     0 SECTION LOCAL  DEFAULT   25
  39.     26: 0000000000004010     0 SECTION LOCAL  DEFAULT   26
  40.     27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27
  41.     28: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
  42.     29: 0000000000001090     0 FUNC    LOCAL  DEFAULT   16 deregister_tm_clones
  43.     30: 00000000000010c0     0 FUNC    LOCAL  DEFAULT   16 register_tm_clones
  44.     31: 0000000000001100     0 FUNC    LOCAL  DEFAULT   16 __do_global_dtors_aux
  45.     32: 0000000000004010     1 OBJECT  LOCAL  DEFAULT   26 completed.8061
  46.     33: 0000000000003dc0     0 OBJECT  LOCAL  DEFAULT   22 __do_global_dtors_aux_fin
  47.     34: 0000000000001140     0 FUNC    LOCAL  DEFAULT   16 frame_dummy
  48.     35: 0000000000003db8     0 OBJECT  LOCAL  DEFAULT   21 __frame_dummy_init_array_
  49.     36: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
  50.     37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS code.c
  51.     38: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
  52.     39: 0000000000002194     0 OBJECT  LOCAL  DEFAULT   20 __FRAME_END__
  53.     40: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
  54.     41: 0000000000003dc0     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_end
  55.     42: 0000000000003dc8     0 OBJECT  LOCAL  DEFAULT   23 _DYNAMIC
  56.     43: 0000000000003db8     0 NOTYPE  LOCAL  DEFAULT   21 __init_array_start
  57.     44: 0000000000002020     0 NOTYPE  LOCAL  DEFAULT   19 __GNU_EH_FRAME_HDR
  58.     45: 0000000000003fb8     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_
  59.     46: 0000000000001000     0 FUNC    LOCAL  DEFAULT   12 _init
  60.     47: 0000000000001200     5 FUNC    GLOBAL DEFAULT   16 __libc_csu_fini
  61.     48: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
  62.     49: 0000000000004000     0 NOTYPE  WEAK   DEFAULT   25 data_start
  63.     50: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@@GLIBC_2.2.5
  64.     51: 000000000000116e    23 FUNC    GLOBAL DEFAULT   16 Run
  65.     52: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   25 _edata
  66.     53: 0000000000001208     0 FUNC    GLOBAL HIDDEN    17 _fini
  67.     54: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
  68.     55: 0000000000004000     0 NOTYPE  GLOBAL DEFAULT   25 __data_start
  69.     56: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
  70.     57: 0000000000004008     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle
  71.     58: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   18 _IO_stdin_used
  72.     59: 0000000000001190   101 FUNC    GLOBAL DEFAULT   16 __libc_csu_init
  73.     60: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   26 _end
  74.     61: 0000000000001060    47 FUNC    GLOBAL DEFAULT   16 _start
  75.     62: 0000000000004010     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start
  76.     63: 0000000000001149    37 FUNC    GLOBAL DEFAULT   16 main
  77.     64: 0000000000004010     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__
  78.     65: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
  79.     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对自己的代码和数据举行统⼀编址,从反汇编的文件中我们可以看出这一点:
  1. main.exe:     file format elf64-x86-64
  2. Disassembly of section .init:
  3. 0000000000001000 <_init>:
  4.     1000:        f3 0f 1e fa                  endbr64
  5.     1004:        48 83 ec 08                  sub    $0x8,%rsp
  6.     1008:        48 8b 05 d9 2f 00 00         mov    0x2fd9(%rip),%rax        # 3fe8 <__gmon_start__>
  7.     100f:        48 85 c0                     test   %rax,%rax
  8.     1012:        74 02                        je     1016 <_init+0x16>
  9.     1014:        ff d0                        callq  *%rax
  10.     1016:        48 83 c4 08                  add    $0x8,%rsp
  11.     101a:        c3                           retq   
  12. Disassembly of section .plt:
  13. 0000000000001020 <.plt>:
  14.     1020:        ff 35 9a 2f 00 00            pushq  0x2f9a(%rip)        # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
  15.     1026:        f2 ff 25 9b 2f 00 00         bnd jmpq *0x2f9b(%rip)        # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
  16.     102d:        0f 1f 00                     nopl   (%rax)
  17.     1030:        f3 0f 1e fa                  endbr64
  18.     1034:        68 00 00 00 00               pushq  $0x0
  19.     1039:        f2 e9 e1 ff ff ff            bnd jmpq 1020 <.plt>
  20.     103f:        90                           nop
  21. .............................................
复制代码
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)。动态链接器会处理惩罚所有的符号解析和重定位,确保步伐中的函数调用和变量访问能够准确地映射到动态库中的现实地址。
动态链接器:
  1. [ouyang@iZ2ze0j6dd76e0o9qypo2rZ niuma]$ ldd main
  2.         linux-vdso.so.1 =>  (0x00007fff5d559000)
  3.         libc.so.6 => /lib64/libc.so.6 (0x00007fa970340000)
  4.         /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企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

火影

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表