我理解链接器/加载器如何使用静态/动态库。
答案 0 :(得分:7)
很遗憾静态库和动态库这些术语是 两种形式的 ADJECTIVE库,因为它永远引导程序员 认为它们表示基本上相同类型的变体。 这几乎和羽毛球场和最高法院的想法一样具有误导性 本质上是同一种东西。事实上,它更具误导性, 因为没有人真的会想到羽毛球场和至高无上的地位 法庭本质上是同一种事。
有人可以了解静态和共享库文件内容之间的差异吗?
让我们使用示例。要反击羽毛球场/最高法院的迷雾
我将使用更准确的技术术语。我不是说静态库,而是说ar
存档,而不是动态库我会说
动态共享对象或简称 DSO 。
ar
存档是
我将从这三个文件开始制作ar
存档:
<强> foo.c的强>
#include <stdio.h>
void foo(void)
{
puts("foo");
}
<强> bar.c 强>
#include <stdio.h>
void bar(void)
{
puts("bar");
}
<强> 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.
我将这两个C源编译成位置无关目标文件:
$ gcc -c -Wall -fPIC foo.c
$ gcc -c -Wall -fPIC bar.c
不需要编译目的地为ar
存档的目标文件
-fPIC
。我只是想用这种方式编译这些。
然后,我将创建一个名为ar
的{{1}}存档,其中包含目标文件libsundry.a
和foo.o
,
加bar.o
:
limerick.txt
当然,使用ar
创建了$ ar rcs libsundry.a foo.o bar.o limerick.txt
存档,
GNU通用归档器。所以它不是由链接器创建的。没有联系
发生。以下是ar
报告存档内容的方式:
ar
以下是档案中的打油诗:
$ ar -t libsundry.a
foo.o
bar.o
limerick.txt
问。 将两个目标文件和ASCII limerick放入同一个$ 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.
存档的重点是什么?
A。表明我可以。要显示ar
存档只是一包文件。
让我们看看ar
对libsundry.a.
$ file libsundry.a
libsundry.a: current ar archive
现在,我将编写一些在其链接中使用libsundry.a
的程序。
<强> fooprog.c 强>
extern void foo(void);
int main(void)
{
foo();
return 0;
}
编译,链接并运行那个:
$ gcc -c -Wall fooprog.c
$ gcc -o fooprog fooprog.o -L. -lsundry
$ ./fooprog
foo
那个笨拙的海鲂。连接器显然没有受到存在的困扰
libsundry.a
中的ASCII limerick。
原因是链接器甚至尝试链接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
那里有很多默认库和目标文件,但唯一的对象 文件我们创建了所使用的链接器:
fooprog.o
(./libsundry.a)foo.o
链接器对./libsundry.a
所做的一切都是取出 foo.o
包并将其链接到程序中。将fooprog.o
链接到程序后
它需要找到foo
的定义。
它放在包里。它在foo.o
中找到了定义,因此从foo.o
开始
袋子并将其链接在程序中。在链接fooprog
,
gcc -o fooprog fooprog.o -L. -lsundry
与以下链接完全相同:
$ gcc -o fooprog fooprog.o foo.o
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
这是我的第二个项目:
<强> foobarprog.c 强>
extern void foo(void);
extern void bar(void);
int main(void)
{
foo();
bar();
return 0;
}
编译,链接和运行:
$ gcc -c -Wall foobarprog.c
$ gcc -o foobarprog foobarprog.o -L. -lsundry
$ ./foobarprog
foo
bar
再次与-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
所以这一次,链接器使用的目标文件是:
foobarprog.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
将foobarprog.o
关联到该计划后,需要找到foo
和bar
的定义。
它放在包里。它分别在foo.o
和bar.o
中找到了定义,因此它取自了它们
包和他们在程序中链接。在链接foobarprog
,
gcc -o foobarprog foobarprog.o -L. -lsundry
与以下链接完全相同:
$ gcc -o foobarprog foobarprog.o foo.o bar.o
总结一切。 ar
存档只是一包文件。您可以使用
一个ar
存档,为链接器提供一堆 object 文件
选择继续连接所需的那些。它将采用那些目标文件
从包中取出并将它们链接到输出文件中。绝对没有别的
用于包。这个包对连接没什么贡献。
这个包只是让你需要知道,让你的生活变得更简单 确切地说,特定链接需要哪些目标文件。你只需要 要知道:嗯,他们就在那个包里。
什么是DSO
让我们做一个。
<强> foobar.c但是强>
extern void foo(void);
extern void bar(void);
void foobar(void)
{
foo();
bar();
}
我们将编译这个新的源文件:
$ gcc -c -Wall -fPIC foobar.c
然后使用foobar.o
制作DSO并重新使用libsundry.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
这使DSO成为libfoobar.so
。注意: DSO由链接器生成。它
链接就像程序链接一样。 libfoopar.so
的联系看起来非常多
像foobarprog
的链接,但添加选项
-shared
指示链接器生成DSO而不是程序。在这里我们看到了我们的对象
链接消耗的文件是:
foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o
ar
根本不了解DSO:
$ ar -t libfoobar.so
ar: libfoobar.so: File format not recognised
但file
确实:
$ file libfoobar.so
libfoobar.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), \
dynamically linked, BuildID[sha1]=16747713db620e5ef14753334fea52e71fb3c5c8, \
not stripped
现在,如果我们使用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
我们看到了
foobarprog.o
-lfoobar (./libfoobar.so)
已./libfoobar.so
本身已关联。不是一些目标文件&#34;在它内部&#34;。那里
不在其中的任何目标文件。这是怎么回事
在程序的动态依赖性中可以看到对链接的贡献:
$ 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)
该程序已经出现了libfoobar.so
的运行时依赖性。这就是连接DSO的作用。
我们可以看到满足此运行时依赖性。所以该程序将运行:
$ ./foobarprog
foo
bar
和以前一样。
与ar
存档不同,DSO和程序都是产品
链接器表明DSO和程序是基本上相同类型的变体。
file
输出也表明了这一点。 DSO和程序都是ELF二进制文件
OS加载程序可以映射到进程地址空间。不只是一包文件。
ar
存档不是任何类型的ELF二进制文件。
程序类型ELF文件和非程序类型ELF之间的区别在于不同的值
链接器写入ELF标头结构和程序标头
ELF文件格式的结构。这些差异指示OS加载程序
在加载程序类型的ELF文件时启动新进程并进行扩充
加载非程序ELF文件时正在构建的进程。从而
非程序DSO映射到其父程序的进程中。一个程序的事实
启动新进程要求程序具有单个默认入口点
操作系统将通过控制:该入口点是强制main
功能
在C或C ++程序中。另一方面,非程序DSO不需要单个强制入口点。它可以通过函数调用从它导出的任何全局函数输入
父母计划。
但是从文件结构和内容的角度来看,一个DSO和一个程序 是非常相似的事情。它们是可以成为进程组件的文件。 程序必须是初始组件。 DSO可以是辅助组件。
进一步区分仍然很常见:DSO必须完全包含在内 可重定位代码(因为在加载器可能需要的链接时间不知道) 将它放在进程地址空间中),而程序由绝对代码组成, 总是加载在同一个地址。但事实上它很可能链接一个可重定位的 程序:
$ gcc -pie -o foobarprog foobarprog.o -L. -lfoobar -Wl,--rpath=$(pwd)
-pie
(位置独立可执行文件)在这里做了什么。然后:
$ file foobarprog
foobarprog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), ....
file
会说foobarprog
是DSO ,它是,但它是
还仍然是一个程序:
$ ./foobarprog
foo
bar
PIE可执行文件正在流行。在Debian 9和衍生发行版(Ubuntu 17.04 ......)GCC工具链中 默认情况下构建PIE程序 。
如果您渴望了解ar
和ELF
文件的详细信息
格式,这里是file
这里是details of the ar
format。
为什么没有单一类型的库文件,伴随着编译器标志 指出库应该如何链接(静态与动态)?
动态和静态链接之间的选择已经完全可以控制
命令行链接选项,因此不需要放弃ar
档案或DSO或发明另一种
图书馆实现这一目标。如果链接器无法以{1}}的方式使用ar
存档,
这将是一个相当大的不便。当然,如果链接器无法链接
DSO我们回到了操作系统的石器时代。
答案 1 :(得分:1)
因为它们是完全不同的东西。静态库只是编译器生成的目标代码的集合。动态库是链接的。
答案 2 :(得分:0)
每个操作系统和工具链都有自己的特定要求和修订历史记录,因此在此处描述所有操作系统的确切文件布局是不切实际的。有关详细信息,请参阅操作系统和工具链文档。
答案 3 :(得分:0)
可共享库倾向于编译为与位置无关的代码。这意味着,例如,控制转移使用PC相对寻址。这是因为它们可能被映射到不同客户端程序的地址空间中的不同位置。
可共享库应该在只读代码+数据和读/写数据之间有明确的区别。这样,只需要将只读部分的单个副本加载到内存中以供所有客户端进程使用。
如果客户端程序A引用库B和C,而库B也引用C,那么您必须确保所有内容都是使用一致的可共享性设置构建的。例如,如果C构建在可共享和不可共享的版本中,并且B构建为可共享的C版本可共享,则还必须针对B和C的可共享版本构建A。如果它尝试使用不可共享的版本C的版本,这可能会导致构建问题,因为与B使用的C的可共享版本冲突。
Ulrich Drepper(GNU libc库的前维护者)的This document比你想知道的可共享库更详细。