Solaris™ 应用程序二进制接口 (Application Binary Interface, ABI) 定义了可供应用程序开发者使用的接口。如果应用程序符合 ABI,则可以增强二进制稳定性。本章介绍了 Solaris ABI 以及所提供的用于验证应用程序的 ABI 兼容性的工具,包括:
Solaris ABI 的定义和用途,在定义 Solaris ABI中介绍。
两个 ABI 工具 appcert 和 apptrace 的用法,在Solaris ABI 工具中介绍。
Solaris ABI 是一组受支持的运行时接口,可供应用程序在 Solaris 操作系统中使用。下面列出了最重要的 ABI 组件:
Solaris 系统库提供的接口,在手册页的第 3 节介绍
Solaris 内核系统调用提供的接口,在手册页的第 2 节介绍
各种系统文件和目录的位置及格式,在手册页的第 4 节介绍
Solaris 实用程序的输入和输出语法及语义,在手册页的第 1 节介绍
Solaris ABI 的主要组件是一组系统库接口。本章中的术语 ABI 专指此组件。ABI 仅包含 C 语言接口,因为 Solaris 操作系统仅为 C 语言提供接口。
写入 Solaris API(Application Programming Interface,应用编程接口)的 C 源代码将通过 C 编译器转换为四个 ABI 版本之一的二进制形式。这四个版本包括:
32 位 SPARC
64 位 SPARC
32 位 x86
64 位 x86 (Opteron)
尽管 ABI 与 API 非常相似,但是源代码编译过程还是有一些明显差异:
编译器指令(如 #define)可以更改或替换源代码级别的构造。生成的二进制内容可能缺少源代码中存在的符号,或者包含源代码中不存在的符号。
编译器可能会生成特定于处理器的符号(如算术指令),用于扩充或替换源代码的构造。
编译器的二进制布局可能特定于此编译器以及此编译器接受的源代码语言的版本。 这种情况下,使用不同编译器编译的相同代码可能会生成不兼容的二进制内容。
由于这些原因,源代码级别 (API) 兼容性无法在各 Solaris 发行版之间实现所要求的二进制兼容性。
Solaris ABI 由操作系统提供的支持接口构成。系统可用的接口中有一些旨在专供操作系统使用。这些专用接口不能被应用程序使用。在 SunOS 5.6 发行版之前,Solaris 库中的所有接口均可供应用程序开发者使用。利用 Solaris 链接编辑器中的库符号作用域技术,可以将不计划在库外部使用的接口的作用域缩小为完全在局部使用。有关详细信息,请参见《链接程序和库指南》。由于系统要求,并非所有专用接口都具有此类缩小了的作用域。这些接口会标记为专用,并且不包括在 Solaris ABI 中。
Solaris ABI 是在 Solaris 库中定义的。这些定义是通过链接编辑器和运行时链接程序中使用的库版本控制技术和策略完成的。
Solaris 链接编辑器和运行时链接程序使用两种库版本控制:文件版本控制和符号版本控制。在文件版本控制中,库名称后面会附带版本号,如 libc.so.1。对该库中的一个或多个公共接口进行不兼容的更改之后,版本号将递增(如递增到 libc.so.2)。在动态链接的应用程序中,生成时绑定的符号在运行时可能不会出现在库中。在符号版本控制中,Solaris 链接程序将符号集与名称关联。然后在运行时链接过程中,链接程序将检查库中是否存在此名称,以验证是否存在关联的符号。
库符号版本控制将符号集与符号版本名称关联,如果此名称具有编号方案,则还会通过映射文件将符号集与其编号关联。以下是假设的 Sun 库 libfoo.so.1 的映射文件示例。
SUNW_1.2 { global: symbolD; symbolE } SUNW_1.1; SUNW_1.1 { global: symbolA; symbolB; symbolC; }; SUNWprivate { global: __fooimpl; local: *; };
此映射文件表明 symbolA、symbolB 和 symbolC 与版本 SUNW_1.1 关联,symbolD 和 symbolE 与 SUNW_1.2 关联,并且 SUNW_1.2 继承了与 SUNW_1.1 关联的所有符号。 符号 __fooimpl 与不同名称的符号集关联,该符号集没有带编号的继承链。
在生成过程中,链接编辑器会检查应用程序使用的符号,并且记录应用程序中与这些符号相关的符号集名称。如果是一系列具有继承链关系的命名集,则链接编辑器将记录包含应用程序所使用的所有符号的最小命名集。如果应用程序仅使用 symbolA 和 symbolB,则链接编辑器将记录 SUNW_1.1 的某一依赖项。如果应用程序使用 symbolA、symbolB 和 symbolD,则链接编辑器将记录 SUNW_1.2 的某一依赖项,因为 SUNW_1.2 包括 SUNW_1.1。
在运行时,链接程序会验证在所链接的库中,是否存在应用程序中作为依赖项而记录的版本名称。通过此过程可以快速验证是否存在所需的符号。有关更多详细信息,请参见《链接程序和库指南》。
映射文件中的 local: * 指令表示,对于库中任何不与命名的符号集显式关联的符号,其作用域均为库局部作用域。此类局部作用域符号在库外部不可见。此约定可以确保符号仅与符号版本控制名称关联时才可见。
由于库中所有可见的符号都属于某个命名的符号集,因此可以使用命名方案来标记符号的 ABI 状态。这种标记操作可以通过将所有专用接口与以 SUNWprivate 开头的符号集名称关联来完成。公共接口以其他名称开头,具体如下:
SYSVABI,适用于根据 System V ABI 定义所定义的接口
SISCD,适用于根据 SPARC International SPARC Compliance Definition 定义的接口
SUNW,适用于由 Sun Microsystems 定义的接口
这些已命名的公共符号集按照 major.minor 编号方案进行编号。如果集合中包括新符号,则集合的次版本号将增加。如果现有符号的更改致使符号与其以前的行为不兼容,则包含此符号的集合的主版本号将增加。如果现有符号更改且与以前不兼容,则库文件名中的版本号也将增加。
因此,Solaris 库 ABI 的定义会包含在多个库中,并且包括与不以 SUNWprivate 开头的符号版本名称关联的符号集。可以使用 pvs 命令列出库中的符号。
Solaris 操作系统提供了两个工具,用于验证应用程序使用的 Solaris 接口是否符合 Solaris ABI。appcert 实用程序可针对专用接口用法实例,静态检查 ELF 二进制对象使用的 Solaris 库接口,并可针对其发现的任何潜在的二进制稳定性问题生成摘要和详细报告。apptrace 工具使用运行时链接程序的链接审计功能,在应用程序运行时可动态跟踪 Solaris 库例程调用。利用此功能,开发者可以检查应用程序使用 Solaris 系统接口的情况。
使用 ABI 工具,可以轻松快速地识别可能与给定的 Solaris 发行版存在二进制兼容性问题的二进制对象。要检查二进制稳定性,请执行以下步骤:
对于当前 Solaris 发行版使用 appcert 进行分级。此操作可以分别识别使用有问题的接口与使用没有问题的接口的二进制对象。
对于目标 Solaris 发行版使用 apptrace 进行验证。此操作通过在使用这些接口时对其进行动态观察,可以验证是否存在接口兼容性问题。
appcert 实用程序是一个 Perl 脚本,用于静态检查 ELF 二进制对象,并将所使用的库符号与给定的 Solaris 发行版中的公共接口和专用接口模型进行比较。此实用程序在 SPARC 或 x86 平台上运行。它可以检查 SPARC 和 x86 的 32 位接口以及 SPARC 平台上 64 位接口的使用情况。请注意,appcert 仅检查 C 语言接口。
随着新 Solaris 发行版的问世,某些库接口的行为可能会发生变化,或者完全消失。这些变化会影响依赖这些接口的应用程序的性能。Solaris ABI 定义了可供应用程序使用的安全稳定的运行时库接口。appcert 实用程序旨在帮助开发者验证应用程序与 Solaris ABI 的兼容性。
appcert 实用程序将针对以下内容对应用程序进行检查:
专用符号的使用情况
静态链接
非绑定符号
专用符号是 Solaris 库用来进行互相调用的函数或数据。专用符号的语义行为可能会发生变化,有时候还可能会删除符号。此类符号称为降级符号。专用符号可变的性质可能会导致依赖于专用符号的应用程序变得不稳定。
Solaris 库之间的专用符号调用的语义在不同的发行版之间可能会发生更改。因此,创建指向归档文件的静态链接会降低应用程序的二进制稳定性。使用指向归档文件对应的共享目标文件的动态链接可以避免此问题。
appcert 实用程序使用动态链接程序解析所检查的应用程序使用的库符号。动态链接程序无法解析的符号称为非绑定符号。非绑定符号可能是由于环境问题造成的,如 LD_LIBRARY_PATH 变量设置不正确。非绑定符号也可能是由于生成问题造成的,如在编译时省略了 -llib 或 -z 转换参数的定义。尽管这些示例中的问题都不很重要,但是 appcert 报告的非绑定符号可能指示更严重的问题,如与不再存在的专用符号存在相关性。
如果 appcert 检查的目标文件依赖于库,则必须在目标文件中记录这些依赖项。为此,编译代码时请确保使用编译器的 -l 转换参数。如果目标文件依赖于其他共享库,则运行 appcert 时必须能够通过 LD_LIBRARY_PATH 或 RPATH 访问这些库。
appcert 应用程序不能检查 64 位应用程序,除非计算机运行的是 64 位 Solaris 内核。由于 Solaris 未提供任何 64 位静态库,因此 appcert 不会对 64 位应用程序执行静态链接检查。
appcert 实用程序不能检查以下内容:
完全或部分静态链接的目标文件。完全静态链接的对象将被报告为不稳定。
没有执行权限集的可执行文件。appcert 实用程序会跳过此类可执行文件。没有执行权限集的共享对象按正常方式进行检查。
用户 ID 设置为 root 的目标文件。
非 ELF 的可执行文件,如 shell 脚本。
除 C 以外其他语言中的 Solaris 接口。代码无需为 C 语言,但是对 Solaris 库的调用必须使用 C 语言。
要使用 appcert 检查应用程序,请键入以下内容:
appcert object|directory |
将 object|directory 替换为以下任一项:
需要 appcert 检查的完整对象列表
包含此类对象的完整目录列表
运行 appcert 的环境可能不同于运行应用程序的环境。如果这两个环境不同,则 appcert 可能无法正确解析对 Solaris 库接口的引用。
appcert 实用程序使用 Solaris 运行时链接程序为每个可执行文件或共享目标文件构造一个接口依赖项的配置文件。此配置文件用于确定应用程序依赖的 Solaris 系统接口。此配置文件中概述的依赖项将与 Solaris ABI 进行比较,以验证是否与其兼容。不存在任何专用接口。
appcert 实用程序以递归方式搜索目录中的目标文件,从而忽略非 ELF 目标文件。appcert 检查完应用程序之后,会在标准输出(通常是屏幕)中列显汇总报告。此报告的副本将写入工作目录(通常是 /tmp/appcert.pid)中名为 Report 的文件中。在子目录名称中,pid 表示此特定 appcert 实例的 1 至 6 位数字的进程 ID。有关 appcert 写入输出文件的目录结构的更多信息,请参见appcert 结果。
以下选项用于修改 appcert 实用程序的行为。可以在命令行中 appcert 命令之后,object|directory 操作数之前键入其中任一选项。
以批处理模式运行 appcert。
在批处理模式下,appcert 生成的报告中包含的每一行都针对一个所检查的二进制对象。
以 PASS 开头的行表示该行中列出的二进制对象未触发任何 appcert 警告。
以 FAIL 开头的行表示在该库中发现问题。
以 INC 开头的行表示无法完全检查该行中指定的二进制对象。
文件 infile 应包含要检查的文件列表,每行包含一个文件名。这些文件将添加到命令行中已经指定的任何文件中。如果使用此转换参数,则无需在命令行中指定对象或目录。
列显 appcert 的使用情况信息。
缺省情况下,appcert 会对应用程序中的所有共享对象进行注释,并将共享对象所驻留的目录附加到 LD_LIBRARY_PATH。使用 -L 转换参数可以禁用这一行为。
缺省情况下,appcert 在搜索目录以查找要检查的二进制代码时会打开符号链接。使用 -n 转换参数可以禁用这一行为。
将 Solaris 库目录 /usr/openwin/lib 和 /usr/dt/lib 附加到 LD_LIBRARY_PATH。
指定要运行库组件的目录。另外,还将在此转换参数指定的目录中创建临时文件。如果未指定此转换参数,则 appcert 使用 /tmp 目录。
使用 appcert 实用程序,可以快速轻松地识别给定集合中存在潜在稳定性问题的应用程序。如果 appcert 未报告任何稳定性问题,则应用程序在后续 Solaris 发行版中可能不会遇到二进制稳定性问题。下表列出了一些常见的二进制稳定性问题。
表 11–1 常见的二进制稳定性问题
问题 |
采取的措施 |
---|---|
使用确定要更改的专用符号 |
立即停止使用此类符号。 |
使用尚未更改的专用符号 |
应用程序目前仍然可以运行,但是应根据实际情况尽快停止使用此类符号。 |
静态链接包含共享对象对应项的库 |
改用共享对象对应项。 |
静态链接不包含共享对象对应项的库 |
如果可能,使用命令 ld -z allextract 将 .a 文件转换为 .so 文件。否则,继续使用静态库,直到共享对象可用为止。 |
使用没有可用公共对应项的专用符号 |
与 Sun 联系,请求获取一个公共接口。 |
使用已过时的符号,或者使用计划要删除的符号 |
应用程序目前仍然可以运行,但是应根据实际情况尽快停止使用此类符号。 |
使用已更改的公共符号 |
重新编译。 |
给定的发行版中可能不会出现由于使用专用接口而导致的潜在稳定性问题。专用接口的行为在不同的发行版之间并不一定会发生变化。要验证专用接口的行为在目标发行版中是否已发生变化,请使用 apptrace 工具。apptrace 的用法在使用 apptrace 进行应用程序验证中介绍。
appcert 实用程序对应用程序目标文件的分析结果会写入多个文件,这些文件位于 appcert 实用程序的工作目录中,通常为 /tmp。工作目录下的主要子目录是 appcert.pid,其中,pid 是此 appcert 实例的进程 ID。appcert 实用程序的结果会写入以下文件:
包含所检查的二进制对象与特定于此二进制对象的 appcert 输出所在子目录之间的映射。
包含运行 appcert 时使用 stdout 所显示的汇总报告的副本。
包含要求 appcert 检查但强制跳过的二进制对象的列表以及跳过每个二进制对象的原因。以下列表中列出了这些原因:
文件不是二进制对象
用户无法读取文件
文件名包含元字符
文件未设置执行位
appcert 检查的每个对象的 objects 子目录下都有一个单独的子目录。其中每个子目录均包含以下文件:
包含 appcert 怀疑为降级的 Solaris 符号的列表。
包含与对象直接绑定的专用 Solaris 符号的列表。
包含与对象直接绑定的公共 Solaris 符号的列表。
包含运行 ldd -r 时不是由动态链接程序绑定的符号的列表。另外,还包括 ldd 返回的包含 “file not found” 的行。
包含 appcert 检查的对象中的打印机格式的动态绑定摘要,其中包括每个 Solaris 库中使用的公共符号和专用符号表。
返回以下四个退出值之一。
appcert 未找到二进制不稳定性的潜在原因。
appcert 实用程序未成功运行。
appcert 检查的某些对象存在潜在的二进制稳定性问题。
appcert 实用程序未找到任何要检查的二进制对象。
使用专用符号-依赖专用符号的应用程序在与其开发发行版不同的 Solaris 发行版中可能无法运行。发生这种情况是因为,出现在给定 Solaris 发行版中的专用符号在其他发行版中可能具有不同的行为或者不存在。如果 appcert 报告应用程序中使用了专用符号,请重新编写应用程序,以避免使用专用符号。
降级符号-降级符号是 Solaris 库中已删除的函数或数据变量,也可以是在以后的 Solaris 发行版中作用域限定为局部的函数或数据变量。如果某个发行版的库未导出此符号,则直接调用此类符号的应用程序将无法在此发行版中运行。
非绑定符号-非绑定符号是指在 appcert 调用时动态链接程序无法解析的应用程序所引用的库符号。尽管非绑定符号并不始终指示二进制稳定性欠佳,但是非绑定符号可能会指示更严重的问题,如存在降级符号的依赖项。
过时库-将来的发行版中可能会从 Solaris 操作系统中删除过时的库。appcert 实用程序可用于标志此类库的所有使用情况。依赖此类库的应用程序在不支持此类库的将来发行版中可能无法正常运行。要避免此问题,请勿使用过时库中的接口。
使用 sys_errlist 或 sys_nerr-使用 sys_errlist 和 sys_nerr 符号可能会降低二进制稳定性。可能会在 sys_errlist 数组结尾后面进行引用。要避免此风险,请改用 strerror。
使用强符号和弱符号-与弱符号关联的强符号会保留作为专用符号,因为其行为在将来的 Solaris 发行版中可能会发生变化。应用程序只应直接引用弱符号。_socket 即是一个强符号示例,它与弱符号 socket 关联。
apptrace 实用程序是一个 C 程序,用于在应用程序运行时动态跟踪对 Solaris 库例程的调用。apptrace 可以在 SPARC 或 x86 平台上运行。apptrace 既可以跟踪 SPARC 和 x86 的 32 位接口调用,也可以跟踪 SPARC 平台上的 64 位接口调用。与 appcert 一样,apptrace 仅检查 C 语言接口。
使用 appcert 确定应用程序面临二进制不稳定性风险之后,可以借助 apptrace 评估每种情况下的风险程度。要确定应用程序与给定发行版的二进制兼容性,请通过 apptrace 验证是否可以成功使用应用程序所用的每个接口。
apptrace 实用程序可以验证应用程序是否正确使用公共接口。例如,使用 open() 直接打开管理文件 /etc/passwd 的应用程序应改用相应的编程接口。这种检查 Solaris ABI 使用情况的能力使您能够轻松快速地识别潜在的接口问题。
apptrace 实用程序不要求对所跟踪的应用程序进行任何修改。要使用 apptrace,请键入 apptrace,后跟所需的任何选项以及用于运行有用的应用程序的命令行。apptrace 实用程序运行时可使用运行时链接程序的链接审计功能来拦截应用程序对 Solaris 库接口的调用。然后,apptrace 实用程序会通过列显调用参数的名称和值来跟踪调用并返回值。跟踪输出可以显示为一行,也可以分为多行以便于阅读。公共接口按人工可读的形式进行列显。专用接口以十六进制进行列显。
apptrace 实用程序允许有选择性跟踪调用,既可以跟踪各接口级调用,也可以跟踪库级调用。例如,apptrace 可以跟踪来自 libnsl 的 printf() 调用,也可以跟踪特定库内某个范围的调用。apptrace 实用程序还可以详细跟踪用户指定的调用。指示 apptrace 行为的规范会通过语法进行制约,此语法与 truss(1) 的用法一致。-f 选项用于指示 apptrace 遵循派生的子进程。-o 选项用于指定存储 apptrace 结果的输出文件。
apptrace 实用程序仅跟踪库级调用,并可装入运行的应用程序进程中,从而可以提高性能(比使用 truss 时的性能要高)。但使用 printf 则除外,在这种情况下 apptrace 无法跟踪对接受变量列表的函数的调用,也无法检查栈或其他调用方信息,例如 setcontext、getcontext、setjmp、longjmp 和 vfork。
以下示例包含跟踪简单的单二进制应用程序 ls 所产生的 apptrace 输出样例。
% apptrace ls /etc/passwd ls -> libc.so.1:atexit(func = 0xff3cb8f0) = 0x0 ls -> libc.so.1:atexit(func = 0x129a4) = 0x0 ls -> libc.so.1:getuid() = 0x32c3 ls -> libc.so.1:time(tloc = 0x23918) = 0x3b2fe4ef ls -> libc.so.1:isatty(fildes = 0x1) = 0x1 ls -> libc.so.1:ioctl(0x1, 0x540d, 0xffbff7ac) ls -> libc.so.1:ioctl(0x1, 0x5468, 0x23908) ls -> libc.so.1:setlocale(category = 0x6, locale = "") = "C" ls -> libc.so.1:calloc(nelem = 0x1, elsize = 0x40) = 0x23cd0 ls -> libc.so.1:lstat64(path = "/etc/passwd", buf = 0xffbff6b0) = 0x0 ls -> libc.so.1:acl(pathp = "/etc/passwd", cmd = 0x3, nentries = 0x0, aclbufp = 0x0) = 0x4 ls -> libc.so.1:qsort(base = 0x23cd0, nel = 0x1, width = 0x40, compar = 0x12038) ls -> libc.so.1:sprintf(buf = 0x233d0, format = 0x12af8, ...) = 0 ls -> libc.so.1:strlen(s = "") = 0x0 ls -> libc.so.1:strlen(s = "/etc/passwd") = 0xb ls -> libc.so.1:sprintf(buf = 0x233d0, format = 0x12af8, ...) = 0 ls -> libc.so.1:strlen(s = "") = 0x0 ls -> libc.so.1:printf(format = 0x12ab8, ...) = 11 ls -> libc.so.1:printf(/etc/passwd format = 0x12abc, ...) = 1 ls -> libc.so.1:exit(status = 0) |
以上示例显示了缺省跟踪行为,即跟踪 ls /etc/passwd 命令的每个库调用。apptrace 实用程序针对每个系统调用列显一行输出,指明以下信息:
调用的名称
调用所在的库
调用的参数和返回值
ls 的输出与 apptrace 输出混在一起。
% apptrace -t \*printf ls /etc/passwd ls -> libc.so.1:sprintf(buf = 0x233d0, format = 0x12af8, ...) = 0 ls -> libc.so.1:sprintf(buf = 0x233d0, format = 0x12af8, ...) = 0 ls -> libc.so.1:printf(format = 0x12ab8, ...) = 11 ls -> libc.so.1:printf(/etc/passwd format = 0x12abc, ...) = 1 |
以上示例说明了 apptrace 如何能够有选择性地跟踪使用正则表达式语法的调用。在此示例中,在 printf(包括 sprintf)中结束的接口调用与以前一样,都是在 ls 命令中进行跟踪。 因此,apptrace 仅跟踪 printf 和 sprintf 调用。
% apptrace -v sprintf ls /etc/passwd ls -> libc.so.1:sprintf(buf = 0x233d0, format = 0x12af8, ...) = 0 buf = (char *) 0x233d0 "" format = (char *) 0x12af8 "%s%s%s" ls -> libc.so.1:sprintf(buf = 0x233d0, format = 0x12af8, ...) = 0 buf = (char *) 0x233d0 "" format = (char *) 0x12af8 "%s%s%s" /etc/passwd |
以上示例显示了详细跟踪模式,在这种模式下,sprintf 的参数会列显在多行中以便于阅读。 最后,apptrace 会显示 ls 命令的输出。