动态链接器和加载器

记录了动态链接器的使用说明,以及 libc、glibc 和 glib 的关系

记录了动态链接器的使用说明,以及 libc、glibc 和 glib 的关系

动态链接器可以通过运行一些动态链接的程序或共享对象间接运行(在这种情况下,不能传递动态链接器的命令行选项,在ELF情况下,存储在程序的.interp部分的动态链接器会被执行),或者直接运行:

/lib/ld-linux.so.*  [OPTIONS] [PROGRAM [ARGUMENTS]]

说明

ld.sold-linux.so*程序找到并加载程序所需的共享对象(共享库),为程序的运行做准备,然后运行。

Linux 二进制文件需要动态链接 (运行时的链接), 除非在编译过程中给 ld 添加了 -static 选项。

ld.so 程序处理 a.out 二进制文件,这是很久以前使用的格式;ld-linux.so*处理 ELF(/lib/ld-linux.so.1 用于 libc5/lib/ld-linux.so.2 用于 glibc2),现在大家已经使用了很多年。 除此之外,两者都有相同的行为,并使用相同的支持文件和程序ldd(1)、ldconfig(8)和/etc/ld.so.conf。

在解析共享对象依赖关系时,动态链接器首先检查每个依赖关系字符串是否包含斜线(如果在链接时指定了包含斜线的共享对象路径名,则会出现这种情况)。 如果发现斜线,那么依赖字符串就会被解释为(相对或绝对)路径名,并使用该路径名加载共享对象。

如果一个共享对象的依赖关系不包含斜线,那么就会按照以下顺序进行搜索:

  1. (仅 ELF) 如果二进制文件的 DT_RPATH 动态部分属性存在且 DT_RUNPATH 属性不存在,则使用该属性指定的目录。 DT_RPATH 的使用已被废弃。
  2. 使用环境变量LD_LIBRARY_PATH(除非可执行文件是在安全执行模式下运行的),在这种情况下,它会被忽略
  3. (仅 ELF) 使用二进制文件 DT_RUNPATH 动态部分属性中指定的目录(如果存在)。
  4. 来自缓存文件/etc/ld.so.cache,它包含了之前在增强库路径中找到的候选共享对象的编译列表。 然而,如果二进制文件是用-z nodeflib链接器选项链接的,那么默认路径中的共享对象将被跳过。 安装在硬件能力目录中的共享对象(见下文)比其他共享对象更优先。
  5. 在默认路径中/lib,然后是/usr/lib。 (在某些64位架构上,64位共享对象的默认路径是/lib64,然后是/usr/lib64。) 如果二进制文件是用-z nodeflib链接器选项链接的,则跳过这一步。

选项

选项 说明
–list 列出所有的依赖关系,以及它们是如何解析(resolved)的
–verify 确认程序是动态链接的,而且这个动态链接器可以处理
–inhibit-cache 不要使用/etc/ld.so.cache
–library-path <路径> 使用<路径>代替`LD_LIBRARY_PATH`环境变量设置(见下文)
–inhibit-rpath <列表> 忽略<列表>指定的对象中的RPATH和RUNPATH信息。 在安全执行模式下运行时,这个选项会被忽略(见下文)
–audit <列表> 使用列表中命名的对象作为审核员

环境

各种环境变量影响着动态链接器的运行

安全执行模式

为了安全起见,如果动态链接器确定二进制文件应该在安全执行模式下运行,那么一些环境变量的效果就会失效或被修改。 这个决定是通过检查辅助向量中的AT_SECURE条目(见getauxval(3))是否有一个非零值来实现的。 这个条目可能因为各种原因而具有非零值,包括:

  1. 进程的实际和有效值。
  2. 进程的真实和有效用户ID不同,或者真实和有效组ID不同。 这通常是执行set-user-ID或set-group-ID程序的结果
  3. 一个非root用户ID的进程执行了一个赋予允许或有效能力的二进制程序。
  4. Linux安全模块可能设置了一个非零值

比较重要的环境变量

环境变量 说明
LD_ASSUME_KERNEL 每个共享对象都可以告知动态链接器它所需要的最小内核ABI版本。 (这个要求被编码在ELF注解部分,可以通过readelf -n查看标有NT_GNU_ABI_TAG的部分。) 在运行时,动态链接器确定运行中的内核的ABI版本,并将拒绝加载指定了超过该ABI版本的最小ABI版本的共享对象。
LD_ASSUME_KERNEL可以用来使动态链接器假定它是在一个具有不同的内核ABI版本的系统上运行。 例如,下面的命令行使动态链接器在加载myprog所需的共享对象时,假定它运行在Linux 2.2.5上。
$ LD_ASSUME_KERNEL=2.2.5 ./myprog
在提供共享对象的多个版本(在搜索路径的不同目录中)的系统上,这些共享对象有不同的最低内核ABI版本要求,LD_ASSUME_KERNEL可以用来选择使用的对象版本(取决于目录搜索顺序)。 从历史上看,LD_ASSUME_KERNEL功能最常见的用法是在同时提供LinuxThreads和NPTL的系统上手动选择旧的LinuxThreads POSIX线程实现(后者通常是这类系统的默认版本);参见pthreads(7)。
LD_LIBRARY_PATH 在执行时搜索ELF库的目录列表。 列表中的项目由冒号或分号分隔。 类似于PATH环境变量。 在安全执行模式下,这个变量被忽略
LD_PRELOAD 在所有其他对象之前加载的额外的、用户指定的ELF共享对象列表。 列表中的项目可以用空格或冒号分隔。 这可以用来选择性地覆盖其他共享对象的功能。 使用在DESCRIPTION下给出的规则搜索对象。 在安全执行模式下,包含斜线的预加载路径名会被忽略,只有当共享对象文件上的set-user-ID模式位被启用时,才会加载标准搜索目录中的共享对象。
LD_TRACE_LOADED_OBJECTS (仅 ELF) 如果设置 (为任何值),将导致程序列出其动态依赖关系,就像通过 ldd(1) 运行一样,而不是正常运行。

Rpath标签扩展

ld.so可以理解rpath规范中的某些字符串(DT_RPATHDT_RUNPATH);这些字符串被替换为以下内容:

  1. $ORIGIN(或等价的${ORIGIN})

    这将扩展到包含程序或共享对象的目录。 因此,一个位于somedir/app的应用程序可以用以下方式编译

    gcc -Wl,-rpath,'$ORIGIN/../lib'
    

    这样它能在somedir/lib中找到相关的共享对象,无论somedir在目录层次中的位置如何。 这有利于创建 “交钥匙 “应用程序(turn-key applications),这些应用程序不需要安装到特殊的目录中,而是可以解压到任何目录中,并且仍然可以找到自己的共享对象。

  2. $LIB (或者${LIB})

    根据不同的架构,它可以扩展为lib或lib64(例如,在x86-64上,它扩展为lib64,在x86-32上,它扩展为lib)。

  3. $PLATFORM (或者${PLATFORM})

    这将扩展为对应于主机系统处理器类型的字符串(例如,”x86_64”)。 在某些架构上,Linux内核并没有向动态链接器提供平台字符串。 这个字符串的值取自辅助向量中的AT_PLATFORM值(见getauxval(3))。

文件

  1. /lib/ld.so

    a.out 的动态链接器/加载器

  2. /lib/ld-linux.so.{1, 2}

    ELF 的动态链接器/加载器

  3. /etc/ld.so.cache

    文件中包含一个用于搜索共享对象的目录汇编列表,以及候选共享对象的有序列表。

  4. /etc/ld.so.preload

    包含一个以空格分隔的ELF共享对象列表的文件,要在程序之前加载。

  5. lib*.so*

    共享对象

注意

ld.so功能适用于使用libc 4.4.3或更高版本编译的可执行文件。 ELF功能从Linux 1.1.52和libc5开始提供。

libc、glibc和glib的关系

glibc 和 libc

glibc 和 libc 都是 Linux 下的 C 函数库。

libc 是 Linux 下的 ANSI C 函数库;glibc 是 Linux 下的 GUN C 函数库。

ANSI C 和 GNU C 的区别

ANSI C 函数库是基本的 C 语言函数库,包含了 C 语言最基本的库函数。这些库函数在其各种支持 C 语言的 IDE 中都是有的。

GNU C 函数库是一种类似于第三方插件的东西。由于 Linux 是用 C 语言写的,所以 Linux 的一些操作是用 C 语言实现的。因此,GUN 组织开发了一个 C 语言的库,以便让我们更好的利用 C 语言开发基于 Linux 操作系统的程序。不过现在的不同的 Linux 的发行版本对这两个函数库有不同的处理方法,有的可能已经集成在同一个库里了。

glibc是linux下面c标准库的实现,即GNU C Library。glibc本身是GNU旗下的C标准库,后来逐渐成为了Linux的标准c库,而Linux下原来的标准c库Linux libc逐渐不再被维护。Linux下面的标准c库不仅有这一个,如uclibc、klibc,以及上面被提到的Linux libc,但是glibc无疑是用得最多的。glibc在/lib目录下的.so文件为libc.so.6。

查看当前系统的 glibc 版本的两种方法:

  1. /lib/libc.so.6

    # /lib32/libc.so.6 
    GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11) stable release version 2.23, by Roland McGrath et al.
    Copyright (C) 2016 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.
    There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
    PARTICULAR PURPOSE.
    Compiled by GNU CC version 5.4.0 20160609.
    Available extensions:
            crypt add-on version 2.1 by Michael Glad and others
            GNU Libidn by Simon Josefsson
            Native POSIX Threads Library by Ulrich Drepper et al
            BIND-8.2.3-T5B
    libc ABIs: UNIQUE IFUNC
    For bug reporting instructions, please see:
    <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
    
  2. ldd

    # ldd --version            
    ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23
    Copyright (C) 2016 自由软件基金会。
    这是一个自由软件;请见源代码的授权条款。本软件不含任何没有担保;甚至不保证适销性
    或者适合某些特殊目的。
    由 Roland McGrath 和 Ulrich Drepper 编写。
    

glibc 和 glib

glib 和 glibc 基本上没有太大联系,可能唯一的共同点就是,其都是 C 编程需要调用的库而已。

glib 是 Gtk+ 库和 Gnome 的基础。glib 可以在多个平台下使用,比如 Linux、Unix、Windows 等。glib 为许多标准的、常用的 C 语言结构提供了相应的替代物。

glib是GTK+的基础库,它由基础类型、对核心应用的支持、实用功能、数据类型和对象系统五个部分组成,可以在gtk网站下载其源代码。是一个综合用途的实用的轻量级的C程序库,它提供C语言的常用的数据结构的定义、相关的处理函数,有趣而实用的宏,可移植的封装和一些运行时机能,如事件循环、线程、动态调用、对象系统等的API。GTK+是可移植的,当然glib也是可移植的,你可以在linux下,也可以在windows下使用它。使用gLib2.0(glib的2.0版本)编写的应用程序,在编译时应该在编译命令中加入pkg-config --cflags --libs glib-2.0,如:

gcc `pkg-config --cflags --libs glib-2.0` hello.c -o hello

使用glib最有名的就是GNOME了。

总结

libc, glibc在一个层次,都是C的标准实现库,是操作系统级别的基石之一。

glib是用C写的一些utilities,即C的工具库,和libc/glibc没有关系。