Contents

共享库文件简介

1. 概述

在本教程中,我们将探索如何在 Linux 文件系统中组织so (共享库)文件。

2. 例子

为了更好地理解本文的上下文,我们需要一个示例。由于这可能因操作系统而异,让我们使用ldconfig 工具找到一些可使用的库:

$ ldconfig -p
....
libGLX.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLX.so.0
libGLU.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLU.so.1
libGLESv2.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGLESv2.so.2
libGL.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libGL.so.1
libFLAC.so.8 (libc6,x86-64) => /lib/x86_64-linux-gnu/libFLAC.so.8
libEGL_mesa.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libEGL_mesa.so.0
libEGL.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libEGL.so.1
libBrokenLocale.so.1 (libc6,x86-64, OS ABI: Linux 3.2.0) => /lib/x86_64-linux-gnu/libBrokenLocale.so.1
libBrokenLocale.so (libc6,x86-64, OS ABI: Linux 3.2.0) => /lib/x86_64-linux-gnu/libBrokenLocale.so
libBLTlite.2.5.so.8.6 (libc6,x86-64) => /lib/libBLTlite.2.5.so.8.6
libBLT.2.5.so.8.6 (libc6,x86-64) => /lib/libBLT.2.5.so.8.6
ld-linux-x86-64.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

使用ldconfig,我们可以看到该系统上安装的所有库的打印列表。

让我们去zip 库:

$ ldconfig -p | grep "libzip.so*"
libzip.so.5 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzip.so.5
libzip.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libzip.so

在这个系统上,zip 库位于/lib/x86_64-linux-gnu/libzip.so*。

请注意,如果 zip 库不存在,我们将不得不从列表中选择另一个libname.so文件。

$ ls -l /lib/x86_64-linux-gnu/libzip.so*
lrwxrwxrwx 1 root root 11 Nov 27 2018 /lib/x86_64-linux-gnu/libzip.so -> libzip.so.5
lrwxrwxrwx 1 root root 13 Nov 27 2018 /lib/x86_64-linux-gnu/libzip.so.5 -> libzip.so.5.0
-rw-r--r-- 1 root root 105672 Nov 27 2018 /lib/x86_64-linux-gnu/libzip.so.5.0

因此,以libzip 为例,让我们讨论三个不同的文件及其编号,例如*.so.5.so.5.0*。

3. Linux 上的 SO 文件组织

我们可以看到我们选择的示例中有三个文件:

/lib/x86_64-linux-gnu/libzip.so
/lib/x86_64-linux-gnu/libzip.so.5
/lib/x86_64-linux-gnu/libzip.so.5.0

这些文件中的每一个都有一个特殊的名称和用途:

libzip.so        # is called the linker-name used for linking.
libzip.so.5      # is called the soname used by the operating system loader.
libzip.so.5.0    # is called the real-name which is updated by the library maintainer.

其中“zip”是库的名称。

每个文件的末尾都有不同的编号。这些数字代表库的版本。它们非常重要,因为正是这些版本决定了每个文件的角色。

它们是两种流行的版本控制方案。一是语义版本控制。另一个变体是 Libtool 版本控制方案。

让我们从更详细地讨论这些版本方案开始,然后使用使用的命名约定。然后我们将详细讨论每个文件的作用。

3.1. Linux 版本控制

Linux 中的每个库都有其文件名中写入的版本信息。例如,lbizip.so.5.0具有 libzip.so.XYZ 的通用形式。这也称为语义版本控制 ,Linux 系统上的每个库都遵循的约定。

X 代表主要版本。当库开发人员进行非向后兼容更改时,他们将不得不增加主要版本。

Y 代表次要版本。向后兼容的更改会增加次要版本。例如,添加一个孤立的功能,如新功能。另请注意,如果 Z 编号不存在,那么这也可能代表错误修复。

Z 是补丁号。这是可选的。错误修复将增加补丁版本。

一个更重要的方面是发布版本。

让我们快速浏览一下已安装的llvm库:

$ ldconfig -p | grep LLVM
libLLVM-9.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-9.so.1
libLLVM-9.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-9.so
libLLVM-8.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-8.so.1
libLLVM-8.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libLLVM-8.so

**注意这里有两个版本,libLLVM-9libLLVM-8用于同一个库llvm。**版本 8 中的分歧意味着该库不再向后兼容。因此,库开发人员决定创建一个单独的版本libLLVM-9

3.2. Libtool 版本控制

重要的是不要将libtool版本方案 与语义版本混淆。libtool版本控制方案的目标是跨多个平台标准化版本。

使用*libtool * 的开发人员可以使用libtool版本方案;但是, libtool会尽可能地将其转换为Linux 系统的语义版本控制。

与语义版本控制相反。** libtool版本方案具有 XYZ 编号字段的当前、修订和年龄**。这不是主要、次要和补丁。libtool版本方案还附带了一组关于何时增加版本值的规则!

3.3. 命名约定

查看ldconfig列出的各种库,我们可以注意到一个约定。

ldconfig实用程序和其他工具期望so(共享对象)文件遵循 lib*.so* 或 ld-*.so* 的命名模式,其中后者仅为动态链接器保留。这个约定在许多 Linux 工具中是硬编码的。这就是为什么ldconfig列出的所有库都以lib开头的原因。

期望此约定的工具的一个示例是GCC 。当开发人员尝试使用 GCC 编译代码时,库名称与*-l*标志一起传递:

gcc a.c -l zip

链接器采用字符串“lib”并附加“zip”(库名称)。这会产生一个名为libzip.so的文件,链接器会尝试查找该文件。

结论是 Linux 环境中的工具遵循libname.so的命名约定。

4. SO 文件

让我们详细看看每个链接器文件。首先,让我们通过实名制

** 实名是磁盘上物理库的实际文件名。**重要的一点是,如果我们按如下方式扩充libzip库:

# Linker-name
/lib/x86_64-linux-gnu/libzip.so
# sonames
/lib/x86_64-linux-gnu/libzip.so.4
/lib/x86_64-linux-gnu/libzip.so.5
# real-names
/lib/x86_64-linux-gnu/libzip.so.4.1
/lib/x86_64-linux-gnu/libzip.so.4.2
/lib/x86_64-linux-gnu/libzip.so.5.0

我们可以在 Linux 系统上拥有多个物理库。他们都幸福地生活在同一个文件夹中。通过创建soname文件,我们可以使用适当的库来执行应用程序。开发人员甚至可以通过更改链接器名称来选择要链接的库。

让我们详细看看其余的概念。

5. linker名称

libzip.so是一个符号链接,没有与之关联的数字。它被称为linker名称。

由于链接器的搜索机制而得名。搜索机制接受一个作为库名称的键并返回一个具有libname.so模式的文件。 作为一个符号链接并且没有版本,libzip.so可用于指向任何libzip版本:

$ ls -lah /lib/x86_64-linux-gnu/libzip.so
lrwxrwxrwx 1 root root 15 Mar 9 12:45 /lib/x86_64-linux-gnu/libzip.so -> libzip.so.5

在此特定示例中,libzip.so指向最新的主要libzip版本libzip.so.5

默认情况下,符号链接由安装脚本设置以指向他们正在安装的库的版本。如果我们想更改要链接的库,我们可以更改符号链接。

** linker名称符号链接作为一个概念提供了链接器可以依赖的一致接口。**它还使我们可以自由地告诉链接器要搜索和链接哪个库文件。

6. soname

现在让我们更详细地看一下sonamelibzip.so.5是通过运行ldconfig自动生成的。

让我们看看这是如何工作的:

首先,让我们删除libzip.so.5,如下所示:

$ sudo rm /usr/lib/x86_64-linux-gnu/libzip.so.5
$ ls -l /usr/lib/x86_64-linux-gnu/libzip*
lrwxrwxrwx 1 root root     33 May 21 16:21 /usr/lib/x86_64-linux-gnu/libzip.so -> /lib/x86_64-linux-gnu/libzip.so.5
-rw-r--r-- 1 root root 105672 Nov 27  2018 /usr/lib/x86_64-linux-gnu/libzip.so.5.0

所以我们已经成功删除了libzip.so.5的符号链接。

现在,让我们运行ldconfig

$ sudo ldconfig
$ ls -l /usr/lib/x86_64-linux-gnu/libzip*
lrwxrwxrwx 1 root root     33 May 21 16:21 /usr/lib/x86_64-linux-gnu/libzip.so -> /lib/x86_64-linux-gnu/libzip.so.5
lrwxrwxrwx 1 root root     13 May 21 16:26 /usr/lib/x86_64-linux-gnu/libzip.so.5 -> libzip.so.5.0
-rw-r--r-- 1 root root 105672 Nov 27  2018 /usr/lib/x86_64-linux-gnu/libzip.so.5.0

啊哈!libzip.so.5又回来了。它指向实际的库libzip.so.5.0

这里重要的一点是,ldconfig如何知道要创建什么符号链接?

让我们更详细地看一下libzip.so.5.0文件:

$ readelf -d /usr/lib/x86_64-linux-gnu/libzip.so.5.0
Dynamic section at offset 0x18db0 contains 28 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libz.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libbz2.so.1.0]
 0x0000000000000001 (NEEDED)             Shared library: [libcrypto.so.1.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000e (SONAME)             Library soname: [libzip.so.5]
....

我们可以看到soname被写入了库的元数据中。开发人员将其设置为libzip.so.5。符号链接libzip.so.5是一个库组,代表libzip库的主要版本 5。

因此,符号链接反映了实际位于物理库libzip.so.5.0中的元数据。

为了更好地理解它是如何使用的,让我们看一下*ls *:

$ ldd `which ls`
linux-vdso.so.1 (0x00007ffd0319c000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f02b281d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f02b262b000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f02b259b000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f02b2595000)
/lib64/ld-linux-x86-64.so.2 (0x00007f02b2883000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f02b2572000)

如我们所见,ldd 列出了ls的运行时依赖项。第一列包含共享对象的sonames。第二列是共享对象的路径以及加载它的地址。

让我们以libpthread.so.0为例,试着想想这个条目是如何添加的:

$ ls -l /usr/lib/x86_64-linux-gnu/libpthread*
-rwxr-xr-x 1 root root  157224 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
-rw-r--r-- 1 root root 6587378 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread.a
lrwxrwxrwx 1 root root      37 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread.so -> /lib/x86_64-linux-gnu/libpthread.so.0
lrwxrwxrwx 1 root root      18 Apr 14 20:26 /usr/lib/x86_64-linux-gnu/libpthread.so.0 -> libpthread-2.31.so

编译ls时,链接器遵循libpthread.so链接器名称符号链接。这导致查看libpthread-2.31.so的元数据,其中链接器从真实库libpthread-2.31.so中提取了soname元数据。

$ readelf -d libpthread-2.31.so | grep soname
0x000000000000000e (SONAME)             Library soname: [libpthread.so.0]

此元数据包含已添加到运行时依赖项列表的soname libpthread.so.0

**这表明soname的主要目的是用于执行应用程序。**动态链接器使用soname将这些文件加载到内存中。这样,开发人员可以在后台更新库而不会破坏任何运行时依赖项。