静态库 (.a) 和共享库 (.so) 之间的文件格式差异?

File format differences between a static library (.a) and a shared library (.so)?

我知道有很多关于共享库和静态库的用例的问题,这个问题不是关于那个的。我问的是存储在磁盘上的文件格式的差异。

为什么问题是,两者之间有什么区别?还是完全一样,只是用法不同?

我相信它们是不一样的,因为共享库上的 运行 'nm' 需要 -D 标志。显然,它需要做一些不同的事情。为什么?

它们都是ELF文件吗?

唯一的区别是共享库可以包含一些依赖路径吗?

静态库只不过是 relocatable 对象的集合(它甚至不是 ELF,而是 ELF 的虚拟存档)。

共享库是一个独立的功能块,具有定义的接口(即符号 table)和依赖项(它是一个 ELF 文件)。

来源

我在示例中使用的源代码如下:

class T {
public:
    T(int _x) : x(_x) { }
    T& operator=(const T& rhs) { x = rhs.x; return *this; }
    int getX() const { return x; }

private:
    int x = 0;
};

正在创建共享库

$ g++ -shared -fPIC -c test.cpp -o test.out && ld -o libtest.so test.out 
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078

正在创建静态库

$ g++ -fPIC -c test.cpp -o test.out && ar rcs libtest.a test.out

都是ELF文件吗?

有点...这是共享库的 readelf -h 的输出:

$ readelf -h libtest.so 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400078
  Start of program headers:          64 (bytes into file)
  Start of section headers:          408 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         1
  Size of section headers:           64 (bytes)
  Number of section headers:         5
  Section header string table index: 2

静态库输出很相似,但不完全相同:

$ readelf -h libtest.a

File: libtest.a(test.out)
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          360 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         9
  Section header string table index: 6

首先跳出的是静态库中的File入口。它不是 ELF 对象,而是 包含 ELF 对象。确认这一点的另一种方法是查看带有 hexdump -C(截断)的文件。一、共享库:

$ hexdump -C libtest.so
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  78 00 40 00 00 00 00 00  |..>.....x.@.....|
00000020  40 00 00 00 00 00 00 00  98 01 00 00 00 00 00 00  |@...............|
00000030  00 00 00 00 40 00 38 00  01 00 40 00 05 00 02 00  |....@.8...@.....|
00000040  51 e5 74 64 06 00 00 00  00 00 00 00 00 00 00 00  |Q.td............|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000070  10 00 00 00 00 00 00 00  47 43 43 3a 20 28 47 4e  |........GCC: (GN|

我们可以在这里非常清楚地看到字符序列 ELF,就在文件的开头。这是静态库输出:

$ hexdump -C libtest.a
00000000  21 3c 61 72 63 68 3e 0a  2f 20 20 20 20 20 20 20  |!<arch>./       |
00000010  20 20 20 20 20 20 20 20  31 34 38 35 34 36 31 31  |        14854611|
00000020  36 36 20 20 30 20 20 20  20 20 30 20 20 20 20 20  |66  0     0     |
00000030  30 20 20 20 20 20 20 20  34 20 20 20 20 20 20 20  |0       4       |
00000040  20 20 60 0a 00 00 00 00  74 65 73 74 2e 6f 75 74  |  `.....test.out|
00000050  2f 20 20 20 20 20 20 20  31 34 38 35 34 36 31 31  |/       14854611|
00000060  36 36 20 20 31 30 30 30  20 20 31 30 30 30 20 20  |66  1000  1000  |
00000070  31 30 30 36 36 34 20 20  39 33 36 20 20 20 20 20  |100664  936     |
00000080  20 20 60 0a 7f 45 4c 46  02 01 01 00 00 00 00 00  |  `..ELF........|
00000090  00 00 00 00 01 00 3e 00  01 00 00 00 00 00 00 00  |......>.........|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 68 01 00 00  |............h...|
000000b0  00 00 00 00 00 00 00 00  40 00 00 00 00 00 40 00  |........@.....@.|
000000c0  09 00 06 00 00 47 43 43  3a 20 28 47 4e 55 29 20  |.....GCC: (GNU) 

我们可以在 ELF 头开始之前看到一堆额外的东西,证实了我们的假设,即静态库与共享库的存储方式不同。

另一个区别是 Type 条目;共享库被标记为可执行,而静态库则不是。事实上,共享库和可执行文件之间根本没有太大区别:https://askubuntu.com/questions/690631/executables-vs-shared-objects

静态库,例如libfoo.a 不是任何类型的 executable。 它只是 unix ar format 中的索引存档 恰好是 ELF 的其他文件 目标文件。

像任何存档一样创建静态库:

ar crs libfoo.a objfile0.o objfile1.0...objfileN.o

输出新存档(c)libfoo.a,插入目标文件(r) 并添加了索引 (s).

您会在程序中听到 linking libfoo.a。这并不意味着 libfoo.a 本身 被 link 编入程序或与程序一起编入。这意味着 libfoo.a 作为存档传递给 linker,它可以从中提取并 link 该程序只是程序需要的存档中的那些目标文件。 所以静态库的格式(ar格式)只是一个object-file linker 输入的捆绑格式:它同样可以是其他一些捆绑 格式对 linker 的任务没有任何影响,即消化一组 目标文件和共享库并生成程序或共享库, 从他们。 ar 格式是历史的选择。

另一方面,共享库,例如libfoo.so一个ELF文件 而不是任何类型的存档。

不要怀疑静态库是一种 ELF 文件 事实上,所有 well-known ELF-parsers - objdumpreadelfnm - 将解析一个静态库。这些工具都知道静态库是 个 ELF 对象文件的存档,因此它们只解析所有对象文件 在库中,就像您在命令行中列出它们一样。

-D 选项与 nm 一起使用只是指示工具 select 只有动态交易品种 table(s) 中的交易品种,如果有的话, 它解析的 ELF 文件的数量 - 运行时间 linker 可见的符号 - 无论它们是否从存档中解析。它是 与 objdump -Treadelf --dyn-syms 相同。 不是 必须使用这些选项来解析共享库中的符号。如果 如果您不这样做,那么默认情况下您只会看到 完整 符号 table。 如果您 运行 nm -D 在静态库上,您将被告知 no symbols,因为 存档中的每个目标文件 - 同样,如果您 运行 nm -D 每个 这些目标文件单独。原因是目标文件 没有动态符号 table:只有共享库或程序有一个。

目标文件共享库程序都是ELF格式的变体。 如果您对 ELF 变体感兴趣,这些是您感兴趣的变体。

ELF 格式本身是一个漫长而棘手的技术阅读,是必需的 精确区分变体的背景。简介:一个ELF文件 包含一个 ELF header 结构,其中一个字段包含 type-identifier 文件作为目标文件、共享库或程序。当文件是 程序或共享库,它还包含一个可选的 程序头 table structure 其字段提供 运行time linker/loader 参数 它需要在一个过程中加载文件。在ELF结构方面, 程序和共享库之间的区别很小:它是 对他们的行为产生影响的详细内容 从装载机中引出。

对于冗长而棘手的技术阅读,请尝试 Excutable and Linkable Format (ELF)