静态库和动态库之间的差异忽略了链接器/加载器

时间:2017-11-04 23:04:06

标签: c gcc compiler-construction static-linking dynamic-linking

我理解链接器/加载器如何使用静态/动态库。

  1. 但是为什么没有一个类型的库文件伴随着编译器标志,指示库应该如何链接(静态与动态)?
  2. 通过我们确实拥有静态和动态库的简单事实,我假设这些文件具有分别启用静态和动态链接的特定内容。有人可以对静态和共享库文件的内容之间的差异有所了解吗?

4 个答案:

答案 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.afoo.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存档只是一包文件

让我们看看arlibsundry.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

filefooprog的评价是什么?

$ 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关联到该计划后,需要找到foobar的定义。 它放在包里。它分别在foo.obar.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程序

如果您渴望了解arELF文件的详细信息 格式,这里是file 这里是details of the ar format

  

为什么没有单一类型的库文件,伴随着编译器标志   指出库应该如何链接(静态与动态)?

动态和静态链接之间的选择已经完全可以控制 命令行链接选项,因此不需要放弃ar档案或DSO或发明另一种 图书馆实现这一目标。如果链接器无法以{1}}的方式使用ar存档, 这将是一个相当大的不便。当然,如果链接器无法链接 DSO我们回到了操作系统的石器时代。

答案 1 :(得分:1)

因为它们是完全不同的东西。静态库只是编译器生成的目标代码的集合。动态库是链接的。

答案 2 :(得分:0)

  1. 在运行时加载的动态库的格式由操作系统编写者决定。静态库的格式由工具链编写者设置。通常这些程序员类别之间存在一些重叠,但他们倾向于保持关注点的分离。
  2. 运行时加载器需要知道要加载的图像的大小,可能是一些堆栈和数据段大小以及DLL中函数的名称和入口点。链接器需要更多地了解静态库中存档的每个对象(函数/数据)。功能签名,数据类型,事物大小,初始化,访问范围等。
  3. 每个操作系统和工具链都有自己的特定要求和修订历史记录,因此在此处描述所有操作系统的确切文件布局是不切实际的。有关详细信息,请参阅操作系统和工具链文档。

答案 3 :(得分:0)

可共享库倾向于编译为与位置无关的代码。这意味着,例如,控制转移使用PC相对寻址。这是因为它们可能被映射到不同客户端程序的地址空间中的不同位置。

可共享库应该在只读代码+数据和读/写数据之间有明确的区别。这样,只需要将只读部分的单个副本加载到内存中以供所有客户端进程使用。

如果客户端程序A引用库B和C,而库B也引用C,那么您必须确保所有内容都是使用一致的可共享性设置构建的。例如,如果C构建在可共享和不可共享的版本中,并且B构建为可共享的C版本可共享,则还必须针对B和C的可共享版本构建A。如果它尝试使用不可共享的版本C的版本,这可能会导致构建问题,因为与B使用的C的可共享版本冲突。

Ulrich Drepper(GNU libc库的前维护者)的

This document比你想知道的可共享库更详细。