动静态库链接生成和使用以及认识ELF文件
目录
- 1.什么是库?
- 2.静态库
- 2.1 静态库生成
- 2.2 静态库的使用
- 3.动态库
- 3.1 生成动态库
- 3.2 动态库使用
- 4.结论
- 5.ELF文件
- 6.ELF从形成到加载
- 6.1 ELF形成可执行
- 6.2 ELF可执行文件加载
1.什么是库?
- 库是写好的现有的,成熟的,可以复用的代码。
- 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行
- 库有两种:
静态库:.a [linux]、.lib [windows]
动态库:.so [linux]、.dll [windows]
2.静态库
-
静态库(.a) :程序在编译链接的时候把库的代码链接到可执行文件中,程序运行的时候将不再需要静态库
-
特点:
- 体积较大:因为静态库的代码被直接嵌入到可执行文件中
- 更新困难:如果静态库中的代码需要更新,必须重新编译所有使用该库的程序
-
一个可执行程序可能用到许多的库,这些库运行有的是静态库,有的是动态库,而我们的编译默认为动态链接库,只有在该库下找不到
动态.so
的时候才会采用同名静态库。我们也可以使用gcc
的-static
强转设置链接静态库 -
静态库,本质是一种归档文件,不需要使用者解包,而是
gcc/g++
直接进行链接
2.1 静态库生成
ar -rc libmyc.a *.o #将所有的.o文件一起打包成静态库
- 其中生成的静态库的名字是
myc
ar
:在linux 系统中,ar
是生成静态库的标准工具-rc参数
:是ar命令中常用的选项组合,用于创建和更新静态库文件r(replace)
:表示替换或添加目标文件到归档文件中。如果归档文件中已经存在同名的目标文件,则会被新文件替换c(create)
:表示创建一个新的归档文件。如果归档文件已经存在,c选项会覆盖它
$ ar -tv mylib/lib/libmyc.a
rw-r--r-- 0/0 1408 Jan 1 08:00 1970 my_strlen.o
rw-r--r-- 0/0 1560 Jan 1 08:00 1970 my_print.o
t
:列出静态库中的文件v
:详细信息
2.2 静态库的使用
# 场景1:头文件和库文件和我们自己的源文件在同一路径下
gcc main.c -L. -lmyc
#场景2:头文件和库文件有自己的独立路径
gcc main.c -I头文件路径 -L库文件路径 -lmyc
-L
:指定库路径-I
:指定头文件搜素路径-l
:指定库名
3.动态库
动态库(.so)
是一种包含可重用代码和数据的文件,它在程序运行时被加载到内存中,多个程序共享使用库- 动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。
- 操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用。
3.1 生成动态库
$ gcc -fPIC -c main.c -o main.o
$ gcc -shared -o libmyc.so main.o
-shared
:用于指定生成动态链接库(共享库)-fPIC
:用于生成与位置无关代码,确保动态库可以在运营时被加载到任意内存位置
3.2 动态库使用
#场景1:头文件和库文件和我们自己的源文件在同一路径下
$ gcc main.c -L. -lmyc
#场景2:头文件和库文件有自己独立路径
$ gcc main.c -I头文件路径 -L库文件路径 -lmyc
问题:
$ ldd a.out
linux-vdso.so.1 (0x00007ffd48fd6000)
libmyc.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbfd8b43000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbfd8d44000)
解决方案:
- 拷贝
.so文件
到系统共享库路径下,一般是/usr/bin
、/lib64
等
$ sudo cp libmyc.so /lib64
- 向系统共享库路径下建立同名软链接
$ sudo ln -fs /home/tc/113/linux/0221/zhangsan/mylib/lib/libmyc.so /lib64/libmyc.so
- 更改环境变量:
LD_LIBRARY_PATH
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:动态库路径
- 配置
/etc/ld.so.conf.d/
$ sudo touch /etc/ld.so.conf.d/XXX.conf
$ sudo nano /etc/ld.so.conf.d/XXX.conf
#向XXX.conf文件中添加动态库的路径
$ sudo ldconfig
4.结论
gcc/g++
默认使用动态库- 如果非得静态链接,只能
-static
一旦-static
,就必须存在对应的静态库 - 只存在静态库,可执行程序,对于该库,只能静态链接了
- 在
linux系统
下,默认安装的大部分库,默认都优先安装的是动态库! 库:应用程序=1:n
- 动静态库对比
特性 | 动态库 | 静态库 |
---|---|---|
链接时机 | 运行时链接 | 编译时来链接 |
可执行文件大小 | 较小(不包含代码) | 较大(包含代码) |
内存占用 | 多个程序共享内存 | 每个程序独立占用内存 |
更新和维护 | 更新库文件即可,不需要重新编译程序 | 需要重新编译所有的使用该库的程序 |
运行时依赖 | 需要动态库文件存在 | 无运行时依赖 |
适用场景 | 需要频繁更新、插件系统、节省内存 | 嵌入式、运行时独立、安全性要求高 |
目录
- 1.什么是库?
- 2.静态库
- 2.1 静态库生成
- 2.2 静态库的使用
- 3.动态库
- 3.1 生成动态库
- 3.2 动态库使用
- 4.结论
- 5.ELF文件
- 6.ELF从形成到加载
- 6.1 ELF形成可执行
- 6.2 ELF可执行文件加载
5.ELF文件
- 可重定位文件(xxx.o文件):包含适合于与其他目标文件链接来创建执行文件或者共享目标文件的代码和数据
- 可执行文件:即可执行程序
- 共享目标文件:即xxx.so文件
一个ELF文件由以下四部分组成:
- ELF头(ELF header):描述文件的主要特性。其位于文件的开始位置,它的主要目的是定位文件的其他部分。包含了文件的基本信息,如文件类型、架构、入口点地址、程序头表和节头表的位置等
- 程序头表(Program header table):列举了所有有效的段(segments)和它们的属性。
- 节头表(Section header table):包含对节的描述
- 节(Section):ELF文件中的基本组成单位,包含了特定类型的数据。ELF文件的各种信息和数据都存储在不同的节中
- 常见的节:
- 代码节(.text):用于保存机器指令,是程序的主要执行部分
- 数据节(.data):保存已初始化的全局变量和局部静态变量
6.ELF从形成到加载
6.1 ELF形成可执行
- 将多份
C/C++
源代码,翻译成为目标.o文件
- 将多份
.o文件section
进行合并
6.2 ELF可执行文件加载
- 一个ELF会有多种不同的
section
,在加载到内存的时候,也会进行section
合并,形成segment
- 合并原则:相同属性,比如:可读、可写等
#查看可执行程序的section
$ readelf -S a.out
There are 31 section headers, starting at offset 0x3978:
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.propert NOTE 0000000000000338 00000338
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.gnu.build-i NOTE 0000000000000358 00000358
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 0000037c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a0
0000000000000024 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 00000000000003c8 000003c8
00000000000000a8 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 0000000000000470 00000470
0000000000000082 0000000000000000 A 0 0 1
...
#查看section合并的segment
$ readelf -l a.out
Elf file type is DYN (Shared object file)
Entry point 0x1060
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
0x00000000000005f8 0x00000000000005f8 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x00000000000001f5 0x00000000000001f5 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000160 0x0000000000000160 R 0x1000
LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x0000000000000258 0x0000000000000260 RW 0x1000
DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000358 0x0000000000000358 0x0000000000000358
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
GNU_EH_FRAME 0x0000000000002014 0x0000000000002014 0x0000000000002014
0x0000000000000044 0x0000000000000044 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x0000000000000248 0x0000000000000248 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
为什么要将section
合并成为segment
??
- 减少页面碎片,提高内存使用效率。 比如,假设页面大小为4096字节,如果
.text部分
为4097字节,.init部分
为512字节,那么它们将占用3个页面,而合并后,它们只需2个页面
程序头表和节头表又有什么用呢??
- 程序头表:主要用于操作系统将程序从磁盘加载到内存
- 节头表:主要在链接时起作用
从链接视图来看:
- 命令
readelf -S code.c
可以帮助查看ELF文件的节头表 .text节
:是保存了程序代码指令的代码节.data节
:保存了初始化的全局变量和局部静态变量等数据.rodata节
:保存了只读的数据.bss节
:为未初始化的全局变量和局部静态变量预留位置.symtab节
:Symbol Table符号表,就是源码里面那些函数名、变量名和代码的对应关系.got.plt节
:.got节
保存了全局偏移量。.got节和.plt节
一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改
从执行视图来看:
- 告诉OS哪些模块可以被加载到内存
- 加载进内存之后哪些分段是可读可写,哪些分段是只读