链接程序和库指南

附录 C 使用动态字符串标记建立依赖性

动态库可以显式建立依赖性,也可以通过过滤器建立依赖性。其中每一种机制都可以用运行路径来扩展,该路径指示运行时链接程序搜索并装入所需依赖性。用于记录过滤器、依赖项和运行路径信息的字符串名称可以用以下保留的动态字符串标记来扩展:

以下各节提供了如何使用这些标记的示例。

特定于硬件功能的共享库

动态标记 $HWCAP 可用于指定特定于硬件功能的共享库所在的目录。此标记可用于过滤器和依赖项。由于此标记可以扩展到多个目标文件,因此它与依赖项一起使用时应受到控制。通过 dlopen(3C) 获取的依赖项可以在 RTLD_FIRST 模式下使用此标记。使用此标记的显式依赖项将装入找到的第一个适当的依赖项。

路径名称指定必须包含以 $HWCAP 标记结束的全路径名。由 $HWCAP 标记指定的目录中的共享库将在运行时受到检查。这些目标文件应指明其硬件功能要求。 请参见标识硬件和软件功能。将根据可用于此进程的硬件功能验证每个目标文件。适用于进程的那些目标文件按其硬件功能值的降序进行排序。这些已排序的 filtee 用于解析过滤器内定义的符号。

硬件功能目录中的 filtee 在命名方面没有限制。以下示例说明了如何设计辅助过滤器 libfoo.so.1 以使其访问硬件功能 filtee。


$ LD_OPTIONS='-f /opt/ISV/lib/hwcap/$HWCAP' \

cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c

$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"

  [1]    SONAME    libfoo.so.1

  [2]    AUXILIARY /opt/ISV/lib/hwcap/$HWCAP

$ elfdump -H /opt/ISV/lib/hwcap/*



/opt/ISV/lib/hwcap/filtee.so.3:



Hardware/Software Capabilities Section:  .SUNW_cap

     index  tag               value

       [0]  CA_SUNW_HW_1     0x1000  [ SSE2 ]



/opt/ISV/lib/hwcap/filtee.so.1:



Hardware/Software Capabilities Section:  .SUNW_cap

     index  tag               value

       [0]  CA_SUNW_HW_1     0x40  [ MMX ]



/opt/ISV/lib/hwcap/filtee.so.2:



Hardware/Software Capabilities Section:  .SUNW_cap

     index  tag               value

       [0]  CA_SUNW_HW_1     0x800  [ SSE ]

如果在具有 MMXSSE 功能的平台上处理过滤器 libfoo.so.1,则出现以下 filtee 搜索顺序。


$ cc -o prog prog.c -R. -lfoo

$ LD_DEBUG=symbols prog

.....

debug: symbol=foo;  lookup in file=libfoo.so.1  [ ELF ]

debug: symbol=foo;  lookup in file=hwcap/filtee.so.2  [ ELF ]

debug: symbol=foo;  lookup in file=hwcap/filtee.so.1  [ ELF ]

.....

请注意,filtee.so.2 的功能值大于 filtee.so.1 的功能值。由于 SSE2 功能不可用,因此 filtee.so.3 不会包括在符号搜索中。

减少 filtee 搜索

通过在过滤器内使用 $HWCAP,可使一个或多个 filtee 实现过滤器内定义的接口。

指定的 $HWCAP 目录中的所有共享库都会被检查,以验证其可用性并对找到的那些适用于进程的目标文件进行排序。排序后,将装入所有目标文件以备使用。

可以使用链接编辑器的 -z endfiltee 选项生成 filtee,以指明它是最后一个可用的 filtee。使用此选项标识的 filtee 将终止此过滤器的已排序 filtee 列表。不会为过滤器装入任何排在此 filtee 之后的目标文件。在前面的示例中,如果使用 -z endfiltee 标记了 filter.so.2 filtee,则 filtee 搜索将如下所示:


$ LD_DEBUG=symbols prog

.....

debug: symbol=foo;  lookup in file=libfoo.so.1  [ ELF ]

debug: symbol=foo;  lookup in file=hwcap/filtee.so.2  [ ELF ]

.....

特定于指令集的共享库

将在运行时扩展动态标记 $ISALIST,以反映可在此平台上执行的本机指令集,如实用程序 isalist(1) 所示。此标记可用于过滤器、运行路径定义和依赖项。由于此标记可以扩展到多个目标文件,因此它与依赖项一起使用时应受到控制。通过 dlopen(3C) 获取的依赖项可以在 RTLD_FIRST 模式下使用此标记。使用此标记的显式依赖项将装入找到的第一个适当的依赖项。

引入 $ISALIST 标记的任何字符串名称将有效地复制到多个字符串中。并且会为每个字符串指定一个可用的指令集。

以下示例说明了如何设计辅助过滤器 libfoo.so.1 以使其访问特定于指令集的 filtee libbar.so.1


$ LD_OPTIONS='-f /opt/ISV/lib/$ISALIST/libbar.so.1' \

cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c

$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"

  [1]    SONAME    libfoo.so.1

  [2]    AUXILIARY /opt/ISV/lib/$ISALIST/libbar.so.1

或者,也可以使用运行路径。


$ LD_OPTIONS='-f libbar.so.1' \

cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R'/opt/ISV/lib/$ISALIST' foo.c

$ dump -Lv libfoo.so.1 | egrep "RUNPATH|AUXILIARY"

  [1]    RUNPATH   /opt/ISV/lib/$ISALIST

  [2]    AUXILIARY libbar.so.1

在这两种情况下,运行时链接程序均使用平台上可用的指令列表来构造多个搜索路径。例如,以下应用程序依赖于 libfoo.so.1,并且在 SUNW,Ultra-2 上执行:


$ ldd -ls prog

.....

  find object=libbar.so.1; required by ./libfoo.so.1

    search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)

      trying path=/opt/ISV/lib/sparcv9+vis/libbar.so.1

      trying path=/opt/ISV/lib/sparcv9/libbar.so.1

      trying path=/opt/ISV/lib/sparcv8plus+vis/libbar.so.1

      trying path=/opt/ISV/lib/sparcv8plus/libbar.so.1

      trying path=/opt/ISV/lib/sparcv8/libbar.so.1

      trying path=/opt/ISV/lib/sparcv8-fsmuld/libbar.so.1

      trying path=/opt/ISV/lib/sparcv7/libbar.so.1

      trying path=/opt/ISV/lib/sparc/libbar.so.1

或者,在配置了 MMXPentium Pro 上执行具有类似依赖项的应用程序:


$ ldd -ls prog

.....

  find object=libbar.so.1; required by ./libfoo.so.1

    search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)

      trying path=/opt/ISV/lib/pentium_pro+mmx/libbar.so.1

      trying path=/opt/ISV/lib/pentium_pro/libbar.so.1

      trying path=/opt/ISV/lib/pentium+mmx/libbar.so.1

      trying path=/opt/ISV/lib/pentium/libbar.so.1

      trying path=/opt/ISV/lib/i486/libbar.so.1

      trying path=/opt/ISV/lib/i386/libbar.so.1

      trying path=/opt/ISV/lib/i86/libbar.so.1

减少 filtee 搜索

通过在过滤器内使用 $ISALIST,可使一个或多个 filtee 实现过滤器内定义的接口。

过滤器内定义的任何接口都可能导致全面搜索所有可能的 filtee,以尝试找到所需接口。如果使用 filtee 以提供性能关键的功能,则这种全面的 filtee 搜索可能会对效率带来负面影响。

可以使用链接编辑器的 -z endfiltee 选项生成 filtee,以指明它是最后一个可用的 filtee。此选项将终止该过滤器的任何进一步 filtee 搜索。在前面的示例中,如果存在 SPARCV9 filtee,并且它使用了 -z endfiltee 标记,则 filtee 搜索将如下所示:


$ ldd -ls prog

.....

  find object=libbar.so.1; required by ./libfoo.so.1

    search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)

      trying path=/opt/ISV/lib/sparcv9+vis/libbar.so.1

      trying path=/opt/ISV/lib/sparcv9/libbar.so.1

特定于系统的共享库

将在运行时扩展动态标记 $OSNAME$OSREL$PLATFORM,以提供特定于系统的信息。这些标记可用于过滤器、运行路径或依赖项定义。

将扩展 $OSNAME 以反映操作系统的名称,如组合使用实用程序 uname(1)-s 选项时所示。将扩展 $OSREL 以反映操作系统的发行版级别,如 uname -r 所示。将扩展 $PLATFORM 以反映基础硬件实现,如 uname -i 所示。

以下示例说明了如何设计辅助过滤器 libfoo.so.1 以使其访问特定于平台的 filtee libbar.so.1


$ LD_OPTIONS='-f /platform/$PLATFORM/lib/libbar.so.1' \

cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c

$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"

  [1]    SONAME    libfoo.so.1

  [2]    AUXILIARY /platform/$PLATFORM/lib/libbar.so.1

此机制在 Solaris 操作环境中用于提供特定于平台的共享库 /lib/libc.so.1 的扩展。

查找关联的依赖项

通常,非绑定产品用于在唯一的位置上安装。此产品由二进制文件、共享库依赖项和关联的配置文件组成。例如,非绑定产品 ABC 可能具有下图所示的布局。

图 C–1 非绑定依赖项

非绑定依赖项示例。

假定此产品适用于安装在 /opt 下。通常,您会向 PATH 中增加 /opt/ABC/bin,以定位产品的二进制代码。每个二进制代码均使用硬编码的运行路径在二进制代码内查找其依赖项。对于应用程序 abc,此运行路径将如下所示:


% cc -o abc abc.c -R/opt/ABC/lib -L/opt/ABC/lib -lA

% dump -Lv abc

  [1]    NEEDED  libA.so.1

  [2]    RUNPATH /opt/ABC/lib

类似地,对于依赖项 libA.so.1,它将显示为:


% cc -o libA.so.1 -G -Kpic A.c -R/opt/ABC/lib -L/opt/ABC/lib -lB

% dump -Lv libA.so.1

  [1]    NEEDED  libB.so.1

  [2]    RUNPATH /opt/ABC/lib

此依赖项表示法将一直有效,直到将产品安装到除建议的缺省目录之外的某个目录中。

动态标记 $ORIGIN 扩展到目标文件的原始目录中。此标记可用于过滤器、运行路径或依赖项定义。可以使用此技术重新定义非绑定应用程序,根据 $ORIGIN 查找其依赖项:


% cc -o abc abc.c '-R$ORIGIN/../lib' -L/opt/ABC/lib -lA

% dump -Lv abc

  [1]    NEEDED  libA.so.1

  [2]    RUNPATH $ORIGIN/../lib

而依赖项 libA.so.1 也可以根据 $ORIGIN 进行定义:


% cc -o libA.so.1 -G -Kpic A.c '-R$ORIGIN' -L/opt/ABC/lib -lB

% dump -Lv libA.so.1

  [1]    NEEDED  libB.so.1

  [2]    RUNPATH $ORIGIN

如果此产品目前安装在 /usr/local/ABC 下,并在用户的 PATH 中增加 /usr/local/ABC/bin,则调用应用程序 abc 将产生如下所示的路径名查询,以查找其依赖项:


% ldd -s abc

.....

  find object=libA.so.1; required by abc

    search path=$ORIGIN/../lib  (RPATH from file abc) 

      trying path=/usr/local/ABC/lib/libA.so.1

        libA.so.1 =>     /usr/local/ABC/lib/libA.so.1

 

  find object=libB.so.1; required by /usr/local/ABC/lib/libA.so.1

    search path=$ORIGIN  (RPATH from file /usr/local/ABC/lib/libA.so.1)

      trying path=/usr/local/ABC/lib/libB.so.1

        libB.so.1 =>     /usr/local/ABC/lib/libB.so.1

非绑定产品之间的依赖性

另一个与依赖项位置相关的问题是如何建立非绑定产品能借以表达相互之间依赖性的模型。

例如,非绑定产品 XYZ 可能依赖于产品 ABC。可以通过主机软件包安装脚本来建立此依赖性。此脚本生成一个指向 ABC 产品安装点的符号链接,如下图所示:

图 C–2 非绑定共同依赖性

非绑定共同依赖性示例。

XYZ 产品的二进制代码和共享库可使用符号链接来表示其对于 ABC 产品的依赖性。此链接现在是一个稳定的参照点。对于应用程序 xyz,此运行路径将如下所示:


% cc -o xyz xyz.c '-R$ORIGIN/../lib:$ORIGIN/../ABC/lib' \

-L/opt/ABC/lib -lX -lA

% dump -Lv xyz

  [1]    NEEDED  libX.so.1

  [2]    NEEDED  libA.so.1

  [3]    RUNPATH $ORIGIN/../lib:$ORIGIN/../ABC/lib

类似地,对于依赖项 libX.so.1,此运行路径将如下所示:


% cc -o libX.so.1 -G -Kpic X.c '-R$ORIGIN:$ORIGIN/../ABC/lib' \

-L/opt/ABC/lib -lY -lC

% dump -Lv libX.so.1

  [1]    NEEDED  libY.so.1

  [2]    NEEDED  libC.so.1

  [3]    RUNPATH $ORIGIN:$ORIGIN/../ABC/lib

如果此产品目前安装在 /usr/local/XYZ 下,则需要使用其后安装脚本来建立以下内容的符号链接:


% ln -s ../ABC /usr/local/XYZ/ABC

如果在用户的 PATH 中增加 /usr/local/XYZ/bin,则调用应用程序 xyz 将产生如下所示的路径名查询,以查找其依赖项:


% ldd -s xyz

.....

  find object=libX.so.1; required by xyz

    search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RPATH from file xyz)

      trying path=/usr/local/XYZ/lib/libX.so.1

        libX.so.1 =>     /usr/local/XYZ/lib/libX.so.1

 

  find object=libA.so.1; required by xyz

    search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RPATH from file xyz)

      trying path=/usr/local/XYZ/lib/libA.so.1

      trying path=/usr/local/ABC/lib/libA.so.1

        libA.so.1 =>     /usr/local/ABC/lib/libA.so.1

 

  find object=libY.so.1; required by /usr/local/XYZ/lib/libX.so.1

     search path=$ORIGIN:$ORIGIN/../ABC/lib \

                (RPATH from file /usr/local/XYZ/lib/libX.so.1)

      trying path=/usr/local/XYZ/lib/libY.so.1

        libY.so.1 =>     /usr/local/XYZ/lib/libY.so.1

 

  find object=libC.so.1; required by /usr/local/XYZ/lib/libX.so.1

     search path=$ORIGIN:$ORIGIN/../ABC/lib \

                (RPATH from file /usr/local/XYZ/lib/libX.so.1)

      trying path=/usr/local/XYZ/lib/libC.so.1

      trying path=/usr/local/ABC/lib/libC.so.1

        libC.so.1 =>     /usr/local/ABC/lib/libC.so.1

 

  find object=libB.so.1; required by /usr/local/ABC/lib/libA.so.1

     search path=$ORIGIN  (RPATH from file /usr/local/ABC/lib/libA.so.1)

       trying path=/usr/local/ABC/lib/libB.so.1

        libB.so.1 =>     /usr/local/ABC/lib/libB.so.1

安全

在安全的进程中,只有将 $ORIGIN 字符串扩展到受信任的目录时才允许对其进行扩展。出现其他相对路径名将产生安全风险。

$ORIGIN/../lib 之类的路径明显指向由可执行文件的位置决定的固定位置。但是,此位置实际上并不固定。相同文件系统上的可写入目录可能会利用使用 $ORIGIN 的安全程序。

以下示例显示,如果在安全进程中随意扩展 $ORIGIN,可能会产生这种安全风险。


% cd /worldwritable/dir/in/same/fs

% mkdir bin lib

% ln $ORIGIN/bin/program bin/program

% cp ~/crooked-libc.so.1 lib/libc.so.1

% bin/program

..... using crooked-libc.so.1

可以使用实用程序 crle(1) 指定让安全应用程序能够使用 $ORIGIN 的可信目录。使用此技术的管理员应确保已对目标目录进行了适当的保护,以防受到恶意入侵。