rah*_*hul 3 c compiler-construction gcc dynamic-linking static-linking
我理解链接器/加载器如何使用静态/动态库.
遗憾的是,术语静态库和动态库都是ADJECTIVE库的形式,因为它永远地导致程序员认为它们表示基本上相同类型的变体.这几乎和羽毛球场和最高法院基本上是同一种事物的想法一样具有误导性.事实上,它更具误导性,因为没有人真的认为羽毛球场和最高法院本质上是同一类事.
有人可以对静态和共享库文件的内容之间的差异有所了解吗?
我们来看一些例子.为了反击羽毛球场/最高法院的雾,我将使用更准确的技术术语.而不是静态库,我会说ar
归档,而不是动态库,我会说
动态共享对象,或简称DSO.
什么是ar
档案
我将从ar
这三个文件开始存档:
foo.c的
#include <stdio.h>
void foo(void)
{
puts("foo");
}
Run Code Online (Sandbox Code Playgroud)
bar.c
#include <stdio.h>
void bar(void)
{
puts("bar");
}
Run Code Online (Sandbox Code Playgroud)
limerick.txt
There once was a young lady named bright
Whose speed was much faster than light
She set out one day
In a relative way
And returned on the previous night.
Run Code Online (Sandbox Code Playgroud)
我将这两个C源编译成Position Independent目标文件:
$ gcc -c -Wall -fPIC foo.c
$ gcc -c -Wall -fPIC bar.c
Run Code Online (Sandbox Code Playgroud)
不需要ar
编译发往存档的
目标文件-fPIC
.我只是希望这些编译方式.
然后,我将创建一个ar
名为存档libsundry.a
包含目标文件foo.o
和bar.o
,加limerick.txt
:
$ ar rcs libsundry.a foo.o bar.o limerick.txt
Run Code Online (Sandbox Code Playgroud)
一个ar
归档被创建,当然,用ar
的GNU通用归档.所以它不是由链接器创建的.没有联系发生.以下是ar
报告存档内容的方式:
$ ar -t libsundry.a
foo.o
bar.o
limerick.txt
Run Code Online (Sandbox Code Playgroud)
以下是档案中的打油诗:
$ rm limerick.txt
$ ar x libsundry.a limerick.txt; cat limerick.txt
There once was a young lady named bright
Whose speed was much faster than light
She set out one day
In a relative way
And returned on the previous night.
Run Code Online (Sandbox Code Playgroud)
问 :将两个目标文件和ASCII limerick放入同一个ar
存档中有什么意义?
A.表明我可以.显示ar
存档只是一包文件.
让我们看看是什么file
造成的libsundry.a.
$ file libsundry.a
libsundry.a: current ar archive
Run Code Online (Sandbox Code Playgroud)
现在我将编写一些libsundry.a
在其链接中使用的程序.
fooprog.c
extern void foo(void);
int main(void)
{
foo();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译,链接并运行那个:
$ gcc -c -Wall fooprog.c
$ gcc -o fooprog fooprog.o -L. -lsundry
$ ./fooprog
foo
Run Code Online (Sandbox Code Playgroud)
那是笨拙的海鲂.链接器显然没有受到ASCII limerick存在的困扰libsundry.a
.
原因是链接器甚至没有尝试链接limerick.txt
到程序中.让我们再次进行链接,这次使用诊断选项,它将准确显示输入文件的链接:
$ gcc -o fooprog fooprog.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
fooprog.o
(./libsundry.a)foo.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
Run Code Online (Sandbox Code Playgroud)
那里有很多默认库和目标文件,但我们创建的链接器所使用的唯一目标文件是:
fooprog.o
(./libsundry.a)foo.o
Run Code Online (Sandbox Code Playgroud)
所有链接器做了与./libsundry.a
被取 foo.o
出来的袋子,在程序链接它.链接fooprog.o
到程序后,需要找到一个定义foo
.它放在包里.它找到了定义foo.o
,所以foo.o
从包中取出并将其链接到程序中.在链接中fooprog
,
gcc -o fooprog fooprog.o -L. -lsundry
Run Code Online (Sandbox Code Playgroud)
与以下链接完全相同:
$ gcc -o fooprog fooprog.o foo.o
Run Code Online (Sandbox Code Playgroud)
怎么file
说fooprog
?
$ file fooprog
fooprog: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), \
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, \
for GNU/Linux 2.6.32, BuildID[sha1]=32525dce7adf18604b2eb5af7065091c9111c16e,
not stripped
Run Code Online (Sandbox Code Playgroud)
这是我的第二个项目:
foobarprog.c
extern void foo(void);
extern void bar(void);
int main(void)
{
foo();
bar();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
编译,链接和运行:
$ gcc -c -Wall foobarprog.c
$ gcc -o foobarprog foobarprog.o -L. -lsundry
$ ./foobarprog
foo
bar
Run Code Online (Sandbox Code Playgroud)
这里再次联系-trace
:
$ gcc -o foobarprog foobarprog.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
Run Code Online (Sandbox Code Playgroud)
所以这一次,链接器使用的目标文件是:
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
Run Code Online (Sandbox Code Playgroud)
在链接foobarprog.o
到程序后,它需要找到foo
和的定义bar
.它放在包里.它分别在foo.o
和中 找到了定义bar.o
,因此它从包中取出并将它们链接到程序中.在链接中foobarprog
,
gcc -o foobarprog foobarprog.o -L. -lsundry
Run Code Online (Sandbox Code Playgroud)
与以下链接完全相同:
$ gcc -o foobarprog foobarprog.o foo.o bar.o
Run Code Online (Sandbox Code Playgroud)
总结一切.一个ar
档案是刚刚的文件袋.您可以使用ar
存档向链接器提供一堆目标文件,从中可以选择继续链接所需的文件.它会将这些目标文件从包中取出并将它们链接到输出文件中.它绝对没有其他用途.袋子对连杆没有任何贡献.
通过让您需要确切知道特定链接所需的目标文件,这个包只会让您的生活变得更简单.你只需要知道:嗯,他们就在那个包里.
什么是DSO
我们来做一个.
foobar.c但是
extern void foo(void);
extern void bar(void);
void foobar(void)
{
foo();
bar();
}
Run Code Online (Sandbox Code Playgroud)
我们将编译这个新的源文件:
$ gcc -c -Wall -fPIC foobar.c
Run Code Online (Sandbox Code Playgroud)
然后使用foobar.o
并重新使用DSOlibsundry.a
$ gcc -shared -o libfoobar.so foobar.o -L. -lsundry -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbeginS.o
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
Run Code Online (Sandbox Code Playgroud)
这就是DSO libfoobar.so
.注意:DSO由链接器生成.它就像程序链接一样链接.libfoopar.so
外观的链接非常类似于链接foobarprog
,但是选项的添加
-shared
指示链接器生成DSO而不是程序.在这里,我们看到链接消耗的目标文件是:
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
Run Code Online (Sandbox Code Playgroud)
ar
根本不了解DSO:
$ ar -t libfoobar.so
ar: libfoobar.so: File format not recognised
Run Code Online (Sandbox Code Playgroud)
但是file
:
$ file libfoobar.so
libfoobar.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), \
dynamically linked, BuildID[sha1]=16747713db620e5ef14753334fea52e71fb3c5c8, \
not stripped
Run Code Online (Sandbox Code Playgroud)
现在如果我们重新foobarprog
使用libfoobar.so
而不是libsundry.a
:
$ gcc -o foobarprog foobarprog.o -L. -lfoobar -Wl,-trace,--rpath=$(pwd)
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
foobarprog.o
-lfoobar (./libfoobar.so)
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
-lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
Run Code Online (Sandbox Code Playgroud)
我们看
foobarprog.o
-lfoobar (./libfoobar.so)
Run Code Online (Sandbox Code Playgroud)
这./libfoobar.so
本身就是联系在一起 没有一些目标文件"在它里面".里面没有任何目标文件.在程序的动态依赖关系中可以看出这对链接的贡献如何:
$ ldd foobarprog
linux-vdso.so.1 => (0x00007ffca47fb000)
libfoobar.so => /home/imk/develop/so/scrap/libfoobar.so (0x00007fb050eeb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb050afd000)
/lib64/ld-linux-x86-64.so.2 (0x000055d8119f0000)
Run Code Online (Sandbox Code Playgroud)
该程序已经出现了运行时依赖性libfoobar.so
.这就是连接DSO的作用.我们可以看到满足此运行时依赖性.所以该程序将运行:
$ ./foobarprog
foo
bar
Run Code Online (Sandbox Code Playgroud)
和以前一样.
事实上,DSO和程序 - 与ar
存档不同- 都是链接器的产品,这表明DSO和程序是基本相同类型的变体.该file
输出表明这一点.DSO和程序都是ELF二进制文件,OS加载程序可以映射到进程地址空间.不只是一包文件.一个ar
存档不为任何类型的ELF二进制文件.
程序类型ELF文件和非程序类型ELF之间的区别在于链接器写入ELF标头结构和ELF文件格式的程序标头结构的不同值.这些差异指示OS加载程序在加载程序类型的ELF文件时启动新进程,并在加载非程序ELF文件时扩充它正在构建的进程.因此,非程序DSO被映射到其父程序的进程中.程序启动新进程的事实要求程序具有OS将通过控制的单个默认入口点:该入口点是main
C或C++程序中的强制函数.另一方面,非程序DSO不需要单个强制入口点.它可以通过父程序的函数调用导出的任何全局函数输入.
但是从文件结构和内容的角度来看,DSO和程序是非常相似的.它们是可以成为进程组件的文件.程序必须是初始组件.DSO可以是次要组件.
它仍然是常见的,以作出进一步的区分:一个DSO必须由完全重定位代码(因为没有知道在链接时间在那里装载程序可能需要将其放置在一个进程的地址空间),而一个程序由绝对代码,总是加载在同一个地址.但实际上它很可能链接一个可重定位的 程序:
$ gcc -pie -o foobarprog foobarprog.o -L. -lfoobar -Wl,--rpath=$(pwd)
Run Code Online (Sandbox Code Playgroud)
这就是-pie
(Position Independent Executable)在这里做的事情.然后:
$ file foobarprog
foobarprog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), ....
Run Code Online (Sandbox Code Playgroud)
file
会说,foobarprog
是一种DSO,它是,虽然它
也仍然是一个程序:
$ ./foobarprog
foo
bar
Run Code Online (Sandbox Code Playgroud)
PIE可执行文件正在流行.在Debian 9和衍生发行版(Ubuntu 17.04 ...)中,GCC工具链默认构建PIE程序.
如果你留恋的的详细知识ar
和ELF
文件格式,这里的详细信息的ar
格式
,这里是ELF格式的细节.
为什么没有一个类型的库文件伴随着编译器标志,指示库应该如何链接(静态与动态)?
动态链接选项和静态链接之间的选择已完全由命令行链接选项控制,因此无需放弃ar
存档或DSO,也不需要发明另一种库来实现此目的.如果链接器不能ar
像它那样使用存档,那将是一个相当大的不便.当然,如果链接器无法链接DSO,我们将回到操作系统石器时代.