如何在 Linux 中处理动态和静态库

了解 Linux 如何使用库,包括静态链接和动态链接之间的区别,可以帮助您解决依赖性问题。
276 位读者喜欢这篇文章。
Why the operating system matters even more in 2017

Internet Archive Book Images。由 Opensource.com 修改。CC BY-SA 4.0

在某种程度上,Linux 是一系列相互依赖的静态和动态库。对于基于 Linux 的系统的新用户来说,库的整个处理过程可能是一个谜。但随着经验的积累,内置于操作系统中的大量共享代码在编写新应用程序时可能成为一种优势。

为了帮助您了解这个主题,我准备了一个小应用程序示例,它展示了在常见的 Linux 发行版上有效的最常用方法(这些方法尚未在其他系统上进行测试)。要按照本实践教程使用示例应用程序,请打开命令提示符并输入

$ git clone https://github.com/hANSIc99/library_sample
$ cd library_sample/
$ make
cc -c main.c -Wall -Werror 
cc -c libmy_static_a.c -o libmy_static_a.o -Wall -Werror 
cc -c libmy_static_b.c -o libmy_static_b.o -Wall -Werror 
ar -rsv libmy_static.a libmy_static_a.o libmy_static_b.o
ar: creating libmy_static.a
a - libmy_static_a.o
a - libmy_static_b.o
cc -c -fPIC libmy_shared.c -o libmy_shared.o
cc -shared -o libmy_shared.so libmy_shared.o
$ make clean
rm *.o

执行这些命令后,这些文件应添加到目录中(运行 ls 查看它们)

my_app
libmy_static.a
libmy_shared.so

关于静态链接

当您的应用程序链接到静态库时,库的代码将成为生成的可执行文件的一部分。 此操作仅在链接时执行一次,这些静态库通常以 .a 扩展名结尾。

静态库是对象文件的存档 (ar)。 对象文件通常为 ELF 格式。 ELF 是 Executable and Linkable Format(可执行和可链接格式)的缩写,它与许多操作系统兼容。

file 命令的输出告诉您静态库 libmy_static.aar 存档类型

$ file libmy_static.a
libmy_static.a: current ar archive

使用 ar -t,您可以查看此存档;它显示了两个对象文件

$ ar -t libmy_static.a 
libmy_static_a.o
libmy_static_b.o

您可以使用 ar -x <archive-file> 提取存档的文件。 提取的文件是 ELF 格式的对象文件

$ ar -x libmy_static.a
$ file libmy_static_a.o 
libmy_static_a.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

关于动态链接

动态链接意味着使用共享库。 共享库通常以 .so 结尾(是“shared object”的缩写)。

共享库是管理 Linux 系统上的依赖项的最常见方式。 这些共享资源在应用程序启动之前被加载到内存中,当多个进程需要同一个库时,它只会在系统上加载一次。 此功能节省了应用程序的内存使用量。

另一点需要注意的是,当共享库中修复了一个错误时,每个引用此库的应用程序都会从中受益。 这也意味着如果该错误仍然未被发现,则每个引用应用程序都会受到其影响(如果应用程序使用了受影响的部分)。

当应用程序需要特定版本的库,但链接器只知道不兼容版本的位置时,对于初学者来说可能非常困难。 在这种情况下,您必须帮助链接器找到正确版本的路径。

尽管这不是每天都会发生的问题,但理解动态链接肯定会帮助您解决此类问题。

幸运的是,这方面的机制非常简单。

要检测启动应用程序需要哪些库,您可以使用 ldd,它将打印出给定文件使用的共享库

$ ldd my_app 
	linux-vdso.so.1 (0x00007ffd1299c000)
	libmy_shared.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00007f56b869b000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f56b8881000)

请注意,库 libmy_shared.so 是存储库的一部分,但未找到。 这是因为动态链接器负责在执行应用程序之前将所有依赖项加载到内存中,但它无法在它搜索的标准位置找到此库。

对于新用户来说,与链接器找到不兼容版本的常用库(例如 bzip2)相关的错误可能会令人困惑。 解决此问题的一种方法是将存储库文件夹添加到环境变量 LD_LIBRARY_PATH,以告诉链接器在哪里查找正确的版本。 在这种情况下,正确的版本在此文件夹中,因此您可以导出它

$ LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH
$ export LD_LIBRARY_PATH

现在,动态链接器知道在哪里找到该库,并且可以执行该应用程序。 您可以重新运行 ldd 以调用动态链接器,它会检查应用程序的依赖项并将其加载到内存中。 对象路径后显示内存地址

$ ldd my_app 
	linux-vdso.so.1 (0x00007ffd385f7000)
	libmy_shared.so => /home/stephan/library_sample/libmy_shared.so (0x00007f3fad401000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f3fad21d000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f3fad408000)

要找出调用了哪个链接器,您可以使用 file

$ file my_app 
my_app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=26c677b771122b4c99f0fd9ee001e6c743550fa6, for GNU/Linux 3.2.0, not stripped

链接器 /lib64/ld-linux-x86–64.so.2 是指向 ld-2.30.so 的符号链接,它是我的 Linux 发行版的默认链接器

$ file /lib64/ld-linux-x86-64.so.2 
/lib64/ld-linux-x86-64.so.2: symbolic link to ld-2.31.so

回顾 ldd 的输出,您还可以看到(在 libmy_shared.so 旁边)每个依赖项都以一个数字结尾(例如,/lib64/libc.so.6)。 共享对象的常用命名方案是

**lib** XYZ.so **.<MAJOR>** . **<MINOR>**

在我的系统上,libc.so.6 也是指向同一文件夹中共享对象 libc-2.30.so 的符号链接

$ file /lib64/libc.so.6 
/lib64/libc.so.6: symbolic link to libc-2.31.so

如果您遇到应用程序由于加载的库版本错误而无法启动的问题,则很可能可以通过检查和重新排列符号链接或指定正确的搜索路径来解决此问题(请参阅下面的“动态加载器:ld.so”)。

有关更多信息,请查看 ldd 手册页

动态加载

动态加载意味着在程序运行时加载库(例如,.so 文件)。 这是使用某种编程方案完成的。

当应用程序使用可以在运行时修改的插件时,将应用动态加载。

有关更多信息,请参阅 dlopen 手册页

动态加载器:ld.so

在 Linux 上,您主要处理共享对象,因此必须有一种机制来检测应用程序的依赖项并将其加载到内存中。

ld.so 按以下顺序在以下位置查找共享对象

  1. 应用程序中的相对或绝对路径(使用 GCC 上的 -rpath 编译器选项硬编码)
  2. 在环境变量 LD_LIBRARY_PATH
  3. 在文件 /etc/ld.so.cache

请记住,将库添加到系统库存档 /usr/lib64 需要管理员权限。 您可以将 libmy_shared.so 手动复制到库存档,并使应用程序在不设置 LD_LIBRARY_PATH 的情况下工作

unset LD_LIBRARY_PATH
sudo cp libmy_shared.so /usr/lib64/

当您运行 ldd 时,您现在可以看到库存档的路径显示出来

$ ldd my_app 
	linux-vdso.so.1 (0x00007ffe82fab000)
	libmy_shared.so => /lib64/libmy_shared.so (0x00007f0a963e0000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f0a96216000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0a96401000)

在编译时自定义共享库

如果您希望您的应用程序使用您的共享库,您可以在编译时指定绝对或相对路径。

修改 makefile(第 10 行)并通过调用 make -B 重新编译程序。 然后,ldd 的输出显示 libmy_shared.so 以其绝对路径列出。

更改此

CFLAGS =-Wall -Werror -Wl,-rpath,$(shell pwd) 

为此(请务必编辑用户名)

CFLAGS =/home/stephan/library_sample/libmy_shared.so 

然后重新编译

$ make

确认它正在使用您设置的绝对路径,您可以在输出的第 2 行看到

$ ldd my_app
    linux-vdso.so.1 (0x00007ffe143ed000)
	libmy_shared.so => /lib64/libmy_shared.so (0x00007fe50926d000)
	/home/stephan/library_sample/libmy_shared.so (0x00007fe509268000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fe50909e000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fe50928e000)

这是一个很好的例子,但是如果您正在制作一个供他人使用的库,这将如何工作? 可以通过将新的库位置写入 /etc/ld.so.conf 或创建一个包含 /etc/ld.so.conf.d/ 下的位置的 <library-name>.conf 文件来注册它们。 之后,必须执行 ldconfig 以重写 ld.so.cache 文件。 在安装一个带有某些特殊共享库的程序之后,有时需要执行此步骤。

有关更多信息,请参阅 ld.so 手册页

如何处理多种架构

通常,32 位和 64 位版本的应用程序有不同的库。 以下列表显示了它们在不同 Linux 发行版中的标准位置

Red Hat 系列

  • 32 位:/usr/lib
  • 64 位:/usr/lib64

Debian 系列

  • 32 位:/usr/lib/i386-linux-gnu
  • 64 位:/usr/lib/x86_64-linux-gnu

Arch Linux 系列

  • 32 位:/usr/lib32
  • 64 位:/usr/lib64

FreeBSD (技术上不是 Linux 发行版)

  • 32位: /usr/lib32
  • 64位: /usr/lib

了解在何处查找这些关键库可以使损坏的库链接成为过去的问题。

虽然起初可能会令人困惑,但了解 Linux 库中的依赖项管理是掌控操作系统的途径。 使用其他应用程序运行这些步骤,以熟悉常用库,并继续学习如何修复可能遇到的任何库挑战。

接下来阅读什么
标签
User profile image.
Stephan 是一位技术爱好者,他欣赏开源,因为它能深入了解事物的工作方式。 Stephan 是一名全职支持工程师,主要从事工业自动化软件领域的工作。 如果可能,他会处理他基于 Python 的开源项目、撰写文章或驾驶摩托车。

评论已关闭。

知识共享许可协议本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.