欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

动态库静态库的链接过程

程序员文章站 2022-06-13 14:01:24
...

1、预备知识

1.1、地址概念

连接地址<>运行地址
存储地址<
>加载地址

加载时地址就是程序放置的地址
运行地址就是程序定位的绝对地址,也即在编译连接时定位的地址。
first 0x30000000 : AT(0){main.o},表示运行地址为0x30000000,加载地址为0

如果程序是在flash里运行,则运行地址和加载地址是相同的。
如果程序是在ram里运行,但程序是存储在flash里,则运行地址指向ram,而加载地址是指向flash

1.2、位置代码

位置无关码:指令只使用相对地址
位置有关码:指令使用过一次或多次绝对地址

在程序运行初期,只有小部分内存初始化完成,因此只会加载程序的一部分执行,之一部分代码一定需要为
位置无关码,因为加入指使用较大的绝对地址,会越界到不可访问内存。

2、库

2.1、静态库

1、静态库是由编译中间产物.O文件,通过AR命令打包而成的。
1、gcc st_lib.c -o st_lib.o -c
2、ar -rc st_lib.a st_lib.o

     arm-linux-ar –t st_lib.a 该命令可以看看libgcc.a中有多少个.o文件
     arm-linux-nm –a st_lib.a该命令可以看看libgcc.a中定义的符号情况
2、使用
   	1、静态连接静态库
			gcc main.c  -o main -lst_lib -L./st_lib -static
   2、动态链接静态库(实际静态库已复制到main中,即静态库无法动态链接)
			gcc main.c  -o main -lst_lib -L./st_lib

3、重定位(静态链接)
		使用静态库的代码实际是把整个静态库复制到自己的代码段,因此需要对静态库内容做重定位,同时说明静态库是reallocatable的

动态库静态库的链接过程

objdump -d st_lib.a会发现里面定义的函数的起始地址是0,说明静态库内的函数没有实际地址(就连调用printf的call也是用的相对地址)
再看看main可执行文件objdump -d main:
动态库静态库的链接过程
此时的main函数以及test函数都有了明确的运行地址(实际是链接器根据链接脚本的链接地址指定的)
可以验证下是否真的是运行地址
gdb main b test
可以看到地址相同
也可以在函数中打印出__executable_start的地址来确认,之所以运行地址在400000附近,是由于默认链接脚本这样指定的
ld -verbose来查看链接脚本中指定的运行地址:
动态库静态库的链接过程

2.2、动态库

1、制作
		1、gcc dt_lib.c -o dt_lib.o –fPIC
			实际也是生成可重定位文件,但是加上fPIC后加了fPIC实现真正意义上的多个进程共享so文件。因为fPIC可以生成位置无关码,即so内的所有跳转全部是基于GOT+偏移实现,没有绝对地址的参与。这样多个进程引用同一个 PIC 动态库时,可以共用内存。这一个库在不同进程中的虚拟地址不同,但操作系统显然会把它们映射到同一块物理内存上。

对于不加-fPIC的,不加fPIC,则加载so文件时,需要对代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置。
可见,这种方式更消耗内存。但是不加fPIC编译的 so文件的优点是加载速度比较快,但是实测,不加fPIC生成的o文件根本无法生成so
2、gcc -shared -o libtest.so dt_lib.o

2、使用
gcc main.c -L lib -l test
动态库只能默认动态链接,无法静态链接

3、链接

3.1、静态链接

1、段合并
   		各个o文件、a文件相同段合并
2、重定位
   		.a文件中的函数地址都是0,说明此时并未确定地址,等到与其他c文件、so文件等链接成可执行文件后便有了地址,那么链接器如何知道哪些符号需要重定位,又是怎么重定位的呢?
		首先只编译不链接可执行文件,得到中间文件并查看其符号表

动态库静态库的链接过程
其中有一个函数func1为定义,而这个函数就是a文件中定义的,因此编译器就是将符号表中未定义的函数进行重定位
再看重定位过程,首先依次读取链接与未链接的可执行文件
objdump -d main.o
objdump -d main.out
得到如下结果:
动态库静态库的链接过程动态库静态库的链接过程
在main.o中,main函数没有地址,而func1与其偏移为0x18
链接完成的main.out中,由于main函数由链接脚本指定了运行基地址,因此自己的运行的地址也可确定为0x400526,而此时func1的函数运行地址为0x40053b,相差为0x15。

3.2、动态链接

既然动态链接去链接动态库并不是拷贝,那么各个进程使用动态库的进程是如何确定动态库函数的运行地址呢?这就是动态链接要做的事

使用上面编译出的main可执行文件
objdump -d main
动态库静态库的链接过程
代码段并没有libtest.so中的test函数,但是main函数调用了aaa@qq.com,去这个函数的地址看一下:
动态库静态库的链接过程
先跳转到0x601030,然后跳到0x40050,使用objdump –h main看一下地址分布:
动态库静态库的链接过程
动态库静态库的链接过程
这个貌似都是符号表存放地址
使用gdb调试查看
gdb main

disass main
动态库静态库的链接过程
在跳转到aaa@qq.com函数时打断点

b 0x400580
run
si
p/x $pc //每执行一步,打印一次pc,可以发现最后跑到了
0x400540
si
动态库静态库的链接过程

因此可推断,在plt表内存储了dl可执行文件的加载地址,程序运行到这才真正调用ld可执行文件去查找、重定位具体的so.所以动态链接在编译时,并没有确定so的运行地址,而是通过其他方法在运行时动态重定位的,因此才称起为动态链接。
另外,动态库的数据段是可读写的,多个进程同时使用是不行的,因此对于数据段,是采取COW(copy on write)方式来进行隔离。代码段是只读的,但是对于同一函数同时被不同进程调用,同样会出现重入问题,此时可考虑使用进程共享的互斥锁进行锁定。