gcc - static 库和动态库之间的差异忽略了链接器/加载程序如何使用它们

  显示原文与译文双语对照的内容
61 4

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

  • 然而为什么没有一个类型的库文件附带一个编译器标志,指示库应该如何链接( static vs 动态) 。
  • 简单地说,我们拥有 static 和动态库,我假设这些文件具有特定的内容,可以分别启用 static 和动态链接。有人可以对 static 和共享库文件的内容之间的差异throw一些 light?
时间:原作者:0个回答

61 2

遗憾的是,在 static 库和动态库都是 form,因为它一直leads程序员认为essentially本质上相同的事物。这几乎就像一个假设,一个羽毛球院和一个高法院本质上是同样的东西。事实上,它更有误导性,因为没有人认为一个羽毛球院和一个大法院本质上是同样的。

有人可以对 static 和共享库文件的内容之间的差异throw一些 light?

让我们用例子来推回羽毛球法院/最高法院雾,我将使用更精确的技术术语。我将说 ar 动态共享对象,而不是动态库,我将说动态共享对象,或者 short 。

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

目标文件不需要使用 -fPIC 编译到 ar 归档文件中。我只想用这种方式编译。

然后我将创建一个名为 libsundry.aar 归档文件,其中包含对象文件 foo.obar.o,以及 limerick.txt:

$ ar rcs libsundry.a foo.o bar.o limerick.txt

当然,ar 归档是用 ar 插件( GNU通用归档程序) 创建的。因此,它不是由链接器创建的。没有链接发生 ar 如何报告归档文件的内容:

$ ar -t libsundry.a 
foo.o
bar.o
limerick.txt

下面是存档中的五行打油诗:

$ 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.

将两个对象文件和一个ASCII打油诗放入同一个 ar 归档中有什么意义?

来表示我可以。要显示一个 ar 存档,只需要一个文件包就行了。

让我们来看看 file 对有什么好处

$ 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

这是 hunky in 。链接器显然没有在 libsundry.a 中存在一个ASCII首选项。

这样做的原因是链接器甚至没有尝试将 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

我们已经创建了许多默认库和对象文件,但是我们创建的唯一对象文件 linker链接器是:

fooprog.o
(./libsundry.a)foo.o

链接器与 ./libsundry.a 所做的全部工作是:从包中取出,然后将它的链接到程序 。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 归档提供给链接器一系列对象从其中挑选需要继续链接的文件。它将把那些对象文件从包中取出并将它们链接到输出文件。这个包绝对没有其他用途。这个包根本不给连接起作用。

这个包只是让你的生活更简单,因为你需要知道你需要什么对象文件。你只需要知道:是的,他们在那个包里面。

DSO DSO

让我们做一个。

foobar.c

extern void foo(void);
extern void bar(void);
void foobar(void)
{
 foo();
 bar();
}

我们将编译这个新的源代码 file:

$ gcc -c -Wall -fPIC foobar.c

然后使用 foobar.o 使用 libsundry.a 制作一个 DSO

$ 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

这使得这个 libfoobar.so 。"。"。注意:是由链接器管理器制作的。它链接起来就像是一个程序。libfoopar.so的链接看起来非常像 foobarprog的链接,但是添加选项 -shared 指示链接器生成一个DSO而不是程序。这里我们看到链接使用的目标文件是:

foobar.o
(./libsundry.a)foo.o
(./libsundry.a)bar.o

ar 根本不理解示波器:

$ 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

现在,如果使用 libfoobar.so 而不是 libsundry.a 重新链接 foobarprog:

$ 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的本身被链接在一起。不是某些对象文件"里面"。它里面没有任何对象文件。以及如何在程序的动态依赖项中看到这一点如何为链接提供帮助:

$ 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 上出现了运行时依赖。这就是连接示波器的原因。我们可以看到,这个运行时依赖。这样程序就会运行:

$./foobarprog
foo
bar

跟以前一样。

事实表明,一个数字示波器和一个程序不同于一个 ar 档案都是链接器的产品,它的本质是DSO的变种,基本上是相同类型的事情。file的输出表明。linux和程序都是ELF二进制文件,操作系统加载程序可以将它们映射到进程地址空间。不只是一包文件。ar 归档不是任何类型的ELF二进制文件。

程序类型ELF文件和 non-program-type ELF之间的差别在于链接器写入ELF头结构和程序头结构的不同值。这些差异指导操作系统加载程序在加载程序类型ELF文件时启动一个新进程。因此,非程序DSO被映射到它的父程序的进程中。程序启动新进程的事实要求程序应有单个默认入口点,操作系统将传递控制:这个入口点是C 或者 C++ 程序中必需的main 函数。另一方面,非程序DSO不需要单一的强制入口点。它可以通过来自父程序的函数调用导出的任何全局函数来输入。

但是从文件结构和内容的角度来看,一个DSO和一个程序是非常相似的东西。它们是可以成为流程组件的文件。程序必须是初始组件。DSO可以是辅助组件。

对于进一步的区别仍然是很常见的:DSO必须完全由可重定位代码( 因为linktime根本不知道装载器可能需要将它放在进程地址空间中) 组成,而程序由绝对代码组成,总是以相同的地址加载。但实际上,链接可重定位的程序是很有可能的:

$ 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 文件格式的详细信息,这里是 ar 格式的细节,下面是ELF格式的细节。

为什么没有一种类型的库文件附带编译器标志,指示库应如何链接( static vs 动态)?

在动态和动态链接之间的选择已经完全由命令行链接选择,所以不需要放弃存档或者 dso,从而实现这一功能。如果链接器不能使用 ar 存档方式,那么这将是一个相当大的麻烦。当然如果连接器不能连接我们就会回到操作系统的石头年。

原作者:
93 0

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

原作者:
133 5
  • 在运行时加载的动态库的格式由操作系统编写器确定。static 库的格式由工具链编写器设置。通常程序员类之间存在一些重叠,但是他们倾向于维护关注点的分离。
  • 运行时加载程序需要知道加载图像的大小,可能是一些堆栈和数据段大小,以及DLL中函数的名称和入口点。链接器需要对 static 库中的每个对象的( 函数/数据) 都有更多的了解。功能签名,数据类型,内容大小,初始化,访问范围。

每个操作系统和工具链都有自己特定的需求和修订历史,因此在这里对它们的文件布局进行准确的描述是不现实的。有关详细信息,请参阅操作系统和工具链文档。

原作者:
...