【谷歌机翻】A Study of Modern Linux API Usage and Compatibility: What to Support When You’re Supporting

概述
本文研究了 Ubuntu Linux 15.04 发行版中所有应用程序和库的 Linux API 使用情况。 我们提出了用于推理各种系统 API 重要性的指标,包括系统调用、伪文件和 libc 函数。 我们的指标旨在评估原型系统或兼容性层的相对成熟度,本文重点关注与 Linux 应用程序的兼容性。 本研究结合使用静态分析来了解 API 使用情况和调查数据,以衡量应用程序对最终用户的相对重要性。
本文为开发人员和研究人员提供了一些见解,对于评估 Linux API 的复杂性和安全性非常有用。 例如,每个 Ubuntu 安装需要 224 个系统调用、208 个 ioctl、fcntl 和 prctl 代码以及数百个伪文件。 对于每种 API 类型,有大量 API 很少被使用(如果有的话)。 此外,一些与安全相关的 API 更改(例如用 faccessat 替换访问)的采用速度缓慢。 最后,数百个 libc 接口实际上未被使用,从而为通过重组 libc 来提高安全性和效率提供了机会。
一、简介
系统工程师和研究人员通常根据他们认为的系统常见和不常见行为来做出设计选择。 例如,最近的一个项目优化了 stat 和 open 系统调用,但牺牲了 rename 和 chmod [52]。 对于通用操作系统,准确确定常见情况可能具有挑战性。 因此,开发人员对哪些 API 重要的看法可能会严重偏向于该开发人员首选的工作负载。

同样,开发人员很难评估影响向后兼容性的更改的影响,这主要是因为缺乏指标。 弃用 API 通常是一个漫长的过程,用户会被反复警告,最终某些应用程序可能仍会被破坏。 例如,信号系统调用的不明确行为会引起一系列安全问题[1, 55]。 尽管 15 年来一直有人警告人们转向更安全的 sigaction 调用,但 signal 尚未从 32 位 x86 Linux 中删除,因为许多遗留应用程序都使用 signal。 消除或替换不必要的、有问题的 API 可能有利于操作系统的安全性、效率和可维护性,但实际上,如果没有分析 API 使用情况的工具,操作系统开发人员很难做到这一点。
许多实验操作系统添加了一个粗略的 Unix 或 Linux 兼容层,以增加支持的应用程序数量 [13,15,27,56]。 此类系统通常支持一小部分 Linux 系统调用,通常仅足以运行一些目标工作负载。 新功能的兼容性或完整性的一个指标是受支持的系统 API 的数量 [16,19,42,51]。 系统调用计数不能准确估计可以合理使用该系统的应用程序或用户的比例。 操作系统研究人员将受益于将一组受支持的系统调用转换为无需重新编译即可直接支持的应用程序部分的能力。 同样,了解哪些附加 API 可以支持最大范围的附加应用程序在系统上运行也很有用。 为了表明一般有用性,良好的兼容性度量应该考虑到系统完全支持其选择的应用程序的用户比例。
这些问题的根源在于缺乏对系统 API 在实践中如何使用的数据集和分析。 系统 API 并不是同样重要:一些 API 被流行的库使用,因此基本上被每个应用程序使用。 其他 API 只能由很少安装的应用程序使用。 评估兼容性从根本上来说是一个测量问题。
本文弥合了系统构建者易于测量的数据与他们所需的指标之间的差距,提供了一种方法论和对 API 使用的全面研究在 x86-64 Ubuntu/Debian Linux 中。 我们的研究静态分析了 Ubuntu/Debian Linux 存储库中所有 30,976 个软件包中的所有可执行文件和共享库二进制文件,以便识别每个二进制文件的系统 API“足迹”。 本文将足迹数据与每个软件包安装频率的数据相结合,这些数据是根据 Ubuntu 和 Debian “受欢迎度竞赛”调查数据测量的 [2, 7]。 通过结合这些数据源,本文提供了一些指标,通过实际安装中的估计使用情况来衡量每个系统 API 的使用情况。
本文提供了一个数据集和分析工具,可以回答系统研究人员的几个实际问题。 例如:在给定的原型中,哪些缺失的 API 会增加支持的应用程序的范围? 或者,如果给定的系统 API 得到优化,哪些广泛使用的应用程序可能会受益? 我们预计将评估工作负载与修改或支持的系统 API 相匹配的能力将特别有用。 同样,这些数据和工具集可以帮助操作系统维护人员评估API更改对应用程序的影响,并可以帮助用户评估原型系统是否适合他们的需求。
这项工作的贡献如下:
• 一种测量平台兼容性的方法,适用于评估原型系统的相对完整性。 我们不将兼容性视为二元属性(“某些东西会损坏吗?”),而是使用分数度量(“有多少程序不会损坏?”),即
更适合衡量原型的进度。
• Ubuntu 中当前 API 使用情况的综合数据集
Linux 15.04。
• 对当前 API 使用情况的分析和一系列见解模式。 例如,我们确定了实现新 Linux 兼容层的有效途径,最大限度地增加每个系统调用的附加应用程序。 我们还发现,许多 API 的使用情况分布相似:一些 API 被广泛使用,而很少或从未使用过的 API 则急剧下降,且具有很长的尾部。 例如,在典型安装中,不到 1% 的应用程序使用近 40% 的 libc API。
2. 某些 API 比其他 API 更平等
我们从研究的角度开始这项研究,寻找更好的方法来评估具有 Unix 兼容层的系统原型的完整性。 一般来说,兼容性被视为二元属性(例如,bug-for-bug 兼容性),在评估几乎肯定不完整的原型时,它会丢失重要信息。 论文经常吸引噪音指标,表明原型可能涵盖所有重要的用例,例如支持的系统或库调用的总数,以及支持的应用程序的种类。
这些指标很容易量化,但也存在问题。 简而言之,并非所有 API 都同样重要:有些是不可或缺的(例如读和写),而另一些则是非常重要的。
很少使用(例如 preadv 和删除模块)。 系统调用的简单计数很容易被主题变体的系统调用(例如 setuid、seteuid 和 setresuid)所扭曲。 此外,一些系统调用(例如 ioctl)导出广泛不同的操作 - 一些由所有应用程序使用,而许多基本上从未使用过(第 3.3 节)。 因此,“部分支持”ioctl 的系统很可能支持所有随 Ubuntu 分发的 Linux 应用程序,也可能不支持。
本文广泛考虑系统 API(“API”):这包括系统调用,以及请求操作系统内核功能的任何其他方式,例如伪文件系统 (/proc)。 本文还考虑了像 libc 这样的库,它们通常负责导出 API(如 POSIX),以及应用程序开发人员与操作系统内核交互的主要方式。
了解给定界面重要性的方法之一是衡量其对最终用户的影响。 换句话说,如果不支持给定的接口,有多少用户会注意到它的缺失? 或者,如果原型添加了给定的界面,那么还有多少用户能够使用该系统? 要回答这些问题,我们必须考虑应用程序之间API使用的差异,以及应用程序在最终用户中的流行程度。 我们通过分析应用程序二进制文件来衡量前者,并根据 Debian 和 Ubuntu 收集的安装统计信息确定后者 [2, 7]。 安装是单系统安装,可以是物理机、虚拟机、多引导系统中的分区或由 debootstrap 创建的 chroot 环境。 我们的数据来自超过 290 万个安装(2,745,304 个 Ubuntu 和 187,795 个 Debian)。
我们引入了两项新指标:一项针对每个 API,一项针对整个系统。 对于每个 API,我们都会衡量其缺失对应用程序和最终用户造成的破坏程度,我们将这一指标称为 API 重要性。 对于系统,我们计算一个加权百分比,称为加权完整性。 为简单起见,我们将系统定义为一组已实现或已翻译的 API,并假设如果应用程序的 API 足迹在系统上实现,则应用程序将在目标系统上运行。 这些指标可以应用于所有系统 API 或 API 的子集,例如系统调用或标准库函数。
本文重点关注 Ubuntu/Debian Linux,因为它是一个管理良好的 Linux 发行版,具有广泛的支持软件,并且还收集软件包安装统计信息。 Ubuntu/Debian Linux 上的默认软件包安装程序是 APT。 包是最小的安装粒度,通常与公共库或应用程序匹配。 一个包可能包含多个可执行文件、库和配置文件。 包还跟踪依赖关系,例如包含依赖于 Python 解释器的 Python 脚本的包。 Ubuntu/Debian Linux 安装统计信息以包粒度收集并收集几种类型的统计数据。 这项研究基于有多少 Ubuntu 或 Debian 安装安装了给定目标包的数据。
对于包中的每个二进制文件(无论是作为独立的可执行文件还是共享库),我们使用静态分析来识别该二进制文件可以调用的所有可能的 API 或 API 占用空间。 API 可以直接从二进制文件调用,也可以通过调用其他共享库导出的函数间接调用。 包的 API 足迹是其每个独立可执行文件的 API 足迹的联合。 我们根据每个包的安装频率对 API 足迹进行加权,以估算每个 API 的整体重要性。 尽管我们最初的重点是评估研究,但我们得出的指标和数据分析为更大的社区提供了见解,例如 API 使用趋势。
2.1 API 重要性:单个 API 的衡量标准
系统开发人员可以从 API 的重要性指标中受益,这反过来又可以指导优化工作、弃用决策和移植工作。 为了反映用户安装和使用不同软件包的事实,我们将 API 重要性定义为 API 对于随机选择的安装中的至少一个应用程序来说是必不可少的概率。 我们希望有一种指标能够随着识别和删除已弃用 API 的实例而降低,并且对于不可或缺的 API 而言,即使只有一个普遍存在的应用程序使用该 API,该指标仍将保持较高水平。
直观上,如果一个 API 没有被任何软件包或安装使用,那么该 API 的重要性将为零,即使删除也不会造成任何负面影响。 我们假设操作系统安装中安装的所有软件包都是不可或缺的。 只要某个 API 至少被一个包使用,该 API 就被认为对于安装很重要。 附录 A.1 包含 API 重要性的正式定义。
2.2 加权完整性:系统范围的指标
我们还以操作系统的粒度来衡量兼容性,我们称之为加权完整性。 加权完整性是指可能运行的应用程序的比例,按这些应用程序安装在系统上的可能性进行加权。
加权完整性的目标是衡量新操作系统原型或转换层与基准操作系统的兼容程度。 在本研究中,基准操作系统是 Ubuntu/Debian Linux。

衡量目标系统 API 子集加权完整性的方法总结如下:
1. 从目标系统支持的 API 列表开始,这些 API 可以从系统源中识别,也可以由系统开发人员提供。
2. 根据包的 API 足迹,框架生成支持和不支持的包的列表。
3. 然后框架会考虑包的依赖性。 如果受支持的包依赖于不受支持的包,则这两个包都将标记为不受支持。
4. 最后,框架根据软件包安装统计数据权衡支持的软件包列表。 与 API 重要性一样,我们测量安装最多的受影响软件包; 相反,加权完整性会计算在目标系统上运行的典型安装中包的预期比例。
我们注意到,这种典型安装模型有助于将指标减少到单个数字,但也无法捕获安装的分布。 此限制是可用软件包安装统计信息的结果,其中不包括已安装软件包之间的相关性。 此限制要求我们假设软件包安装是独立的,除非 APT 识别出依赖关系。 例如,如果软件包 foo 和 bar 都被报告为安装了一次,我们就无法判断它们是否在同一个安装中,或者是否是两个不同的安装。 如果 foo 和 bar 都使用模糊的系统 API,我们假设如果删除模糊的 API,两个安装都会受到影响。 如果 foo 依赖于 bar,我们假设安装重叠。 附录 A.2 正式定义了加权完整性。
2.3 通过静态分析收集数据
我们使用静态二进制分析来识别二进制文件的系统调用足迹。 这种方法的优点是不需要源代码或测试用例。 使用 strace 等工具记录动态系统调用更简单,但可能会错过依赖于输入的行为。 静态分析的一个限制是我们必须假设反汇编的二进制文件与运行时的预期指令流相匹配。 换句话说,我们假设二进制文件没有故意混淆自己,例如跳转到指令的中间(从反汇编器的角度来看)。 为了缓解这种情况,我们抽查静态分析是否作为 strace 结果的超集返回。
我们注意到,根据我们的经验,从二进制文件中识别诸如系统调用号甚至操作代码之类的东西是相当简单的。 这些往往是二进制文件中的固定标量,而其他参数(例如写入缓冲区的内容)是在运行时输入的。 我们假设二进制文件可以直接使用内联系统调用指令发出系统调用,或者可以通过库(例如 libc)调用系统调用。 我们的静态分析识别系统调用指令并构建整个程序调用图。

图 1. Ubuntu/Debian Linux 存储库中所有可执行文件中用解释语言编写的 ELF 文件和应用程序的百分比(按解释器分类)。 ELF 二进制文件包括静态二进制文件、共享库和动态链接的可执行文件。 解释器是通过文件的 shebang 来检测的。 越高越重要。
我们的研究主要集中在 ELF 二进制文件上,它占 Linux 应用程序的最大部分(图 1)。 对于解释性语言,例如Python或shell脚本,我们假设解释器和主要支持库的系统调用足迹过度近似应用程序的预期系统调用足迹。 我们的研究中不考虑动态加载的库,例如应用程序模块或语言本机接口(例如 JNI、Perl XS)。
2.4 限制
人气竞赛数据集。 本文的分析受到 Ubuntu/Debian Linux 的软件包安装程序 APT 及其软件包安装统计信息的限制。 由于 Ubuntu/Debian Linux 中的大多数软件包都是开源的,因此我们对 Linux API 使用的观察可能会偏向于开源开发模式。 通过其他方式购买和分发的商业应用程序不包括在本次调查数据中,但原则上,如果有其他数据可用,其他来源的数据也可以纳入分析中。
我们假设 Ubuntu/Debian Linux 提供的软件包安装统计数据具有代表性。 人气竞赛数据集相当大(336,195 个系统),但报告是可选的。
数据并未显示这些软件包的实际使用频率,仅显示它们的安装频率。 最后,该数据集不包含足够的历史数据来比较 API 使用情况随时间的变化。
静态分析。 由于我们的研究仅分析预编译的二进制文件,因此可能会错过一些编译时自定义。 已经使用 #ifdef LINUX 等宏移植的应用程序将被视为依赖于特定于 Linux 的 API,即使该应用程序可以针对其他系统重新编译。

我们的静态分析工具仅识别 API 是否可能被使用,而不是识别 API 在执行过程中使用的频率。 因此,仅仅对性能进行推断是不够的。
我们假设,一旦给定的 API(例如,写入)得到支持并且适用于合理的应用程序样本,处理遗漏的边缘情况应该是简单的工程,不太可能使项目的实验结果无效。 也就是说,在输入可能产生显着不同行为的情况下,例如给定打开的路径,我们会衡量这些参数的 API 重要性。 验证错误之间的兼容性通常需要与本研究中使用的技术很大程度上正交的技术,因此这超出了本工作的范围。
我们不进行过程间数据流分析。 因此,我们无法识别存储库中所有二进制文件中 2,454 个调用站点(相关调用站点的 4%)的系统调用号。 因此,某些系统调用使用值可能会被低估,并且可能会随着更复杂的静态分析而上升。
指标。 所提出的指标旨在是简单的数字,以便于比较。 但这种粗糙度失去了一些细微差别。 例如,我们的指标无法区分对一小部分人至关重要的 API(例如那些提供其他方式无法提供的功能的 API)和因软件不重要而很少使用的 API。 同样,仅凭这些指标无法区分尚未广泛采用的新 API 和使用率下降的旧 API。
3. 现代 Linux API 使用研究
本节介绍 API 使用情况的测量,以及 API 使用方式的几种趋势。 特别值得注意的是,基本上所有应用程序所需的操作系统接口远远大于大约 300 个 Linux 系统调用——所需的接口还包括几个向量系统调用操作,例如 ioctl,以及特殊的文件系统接口,例如 /sys 和 / 程序。 我们还注意到,许多系统调用和其他 API 很少使用,因此可以轻松地弃用它们。
本节首先考察 Linux 应用程序中系统调用的使用。 3.2 节分析了向原型添加系统调用的最有效路径,概述了从“hello world”的最小占用空间到要求最高的应用程序(qemu)的路径,并在每个步骤中最大化支持的应用程序数量。 3.3节分析了向量系统调用下操作的重要性,例如ioctl。 3.4 节评估伪文件的 API 重要性,例如 /proc 下的伪文件。 最后,第 3.5 节检查了 libc 当前的使用模式。 在整个部分中,我们确定了可以有效地限制、删除或重构 API 的几个点,以及可以识别意外 API 的点。

图2. API 重要性的趋势是具有 Linux 内核 3.19 的 Ubuntu Linux 15.04 的总共 320 个系统调用中最重要的系统调用。 越高越重要; 100% 表示所有安装都包含进行系统调用的软件。
对性能或功能至关重要。 我们在方框中突出显示了关键的见解和建议。
3.1 发现最有价值的系统调用
我们首先查看每个系统调用的 API 重要性,以便回答以下问题:
• 实施新系统时哪些系统调用最需要支持,或者如果需要的话更换成本很高?
• 哪些系统调用很少使用且应弃用?
• 哪些系统调用不受操作系统支持,但应用程序仍会尝试?
x86-64 Linux 3.19 中定义了 320 个系统调用(如 unistd.h 中所列)。 图 2 显示了系统调用按重要性的分布。 该图按最重要(100%)到最不重要(0% 左右)排序,类似于倒置 CDF。 该图突出显示了这条线上的几个兴趣点。
Linux 上超过三分之二(320 中的 224)系统调用是不可或缺的:每次安装时至少有一个应用程序需要它。 其中,33 个系统调用对于超过 10% 的安装非常重要。 44 系统调用的 API 重要性非常低:不到 10% 的安装至少包含一个使用这些系统调用的应用程序。
我们的研究还显示了 API 重要性的贡献者。 例如,表 1 列出了仅由一两个特定库(例如 libc)调用的系统调用。 这些系统调用由库 API 包装,因此应用程序依赖它们只是因为库这样做。 为了消除这些系统调用的使用,开发人员只需付出最小的努力来在库中重新实现包装器。
在 API 重要性高于零但低于 10% 的 44 个系统调用中,有些情况下可以使用更流行的替代方案。 例如,Linux 支持 POSIX 和 System V 消息队列。 POSIX 消息队列的五个 API 的 API 重要性低于 System V 消息队列。 我们相信这一点系统调用
时钟设置时间、iopl、ioperm、signalfd4 mbind
添加键
按键控制
请求密钥
preadv, pwritev
小鬼。 套餐
100% 库
36.0% libnuma、libopenblasp 27.2% libkeyutils
27.2% pam keyutil,
libkeyutils 14.4% libkeyutils
11.7% 库
表 1. 仅由特定库直接使用的系统调用及其 API 重要性(“Imp.”)。 仅显示 API 重要性大于 10% 的系统调用。 这些系统调用由库 API 包装,因此很容易通过修改库来弃用它们。
系统调用
seccomp、sched setattr、sched getattr
kexec加载
时钟调整时间重命名2
mq timedsend、mq getsetattr io getevent
获取CPU
小鬼。 套餐
1% 合作计算工具
1% kexec-tools 4% systemd 4% systemd,
合作计算工具 1% qemu-user
1% ioping、zfs-fuse 4% valgrind、rt-tests
表 2. 由特定包主导使用的系统调用及其 API 重要性(“Imp.”)。 此表不包括正式停用的系统调用。
这归因于 System V 消息队列更容易移植到其他 UNIX 系统。 同样,我们观察到 epoll wait (100%) 比 epoll pwait (3%) 具有更高的 API 重要性,尽管 epoll pwait 通常被认为对于相同目的(等待文件描述符事件)更强大。 表 2 列出了仅由一两个包使用的系统调用——通常是专用实用程序,例如 kexec load,它由 kexec-tools 使用。
在某些情况下,系统调用被有效地卸载到 /proc 或 /sys 中的文件。 例如,以前通过查询模块获得的一些信息可以从 /proc/modules、/proc/kallsyms 以及目录 /sys/module 下的文件获取。 同样,可以从 sysfs 系统调用获取的信息现在位于 /proc/filesystems 中。
我们还发现了五个系统调用 uselib、nfsservctl、afs syscall、vserver 和安全系统调用,这些系统调用已正式停用,但 API 重要性仍然较低但非零。 例如,nfsservctl 已从 Linux 内核 3.1 中删除,但 API 重要性仍然为 7%,因为它已被 NFS 实用程序(例如 exportfs)尝试过。 这些实用程序仍然尝试旧的要求,以向后兼容旧内核。

未使用的系统调用
设置线程区域、tuxcall、创建模块等 6 个操作。
系统文件系统
rt tgsigqueueinfo,获取稳健列表
重新映射文件页面
消息队列通知
查找 dcookie 重新启动系统调用移动页面
废弃原因
正式退役。 取而代之
/proc/文件系统。 未被应用程序使用。
没有非顺序的有序映射; 首选重复调用 mmap。
未使用:异步消息传递。
未使用:用于分析。 对应用程序透明。 未使用:用于 NUMA 使用。
图 3. 在操作系统中实现 N 个排名靠前的系统调用时的累积加权完整性。 越高则越顺从。
Ubuntu 应用程序。 该图突出显示了该曲线中的几个有趣点。
从本质上讲,如果没有至少 40 个系统调用,就无法运行即使是最简单的程序。 此后,通过添加另一个系统调用可以支持的其他应用程序的数量稳步增加,直到达到 125 个系统调用的拐点,或者支持文件的扩展属性,此时加权完整性跃升至 25%。 为了支持大约一半的 Ubuntu/Debian Linux 应用程序,必须有 145 个系统调用,并且曲线稳定在 202 个系统调用左右。 在最极端的情况下,qemu 的 MIPS 模拟器(在 x86-64 主机上)需要 270 个系统调用 [18]。 100% 的加权完整性意味着原型支持所有曾经使用过的 Linux 应用程序。
表 4 按所需系统调用的粗略类别细分了建议的开发阶段。 为了简洁起见,我们在此不提供完整的有序列表,但该列表可作为我们数据集的一部分提供,网址为:http://oscar.cs.stonybrook.edu/api-compat-study。
加权完整性的目标是帮助指导开发新系统原型的过程。 3.1 节显示 Ubuntu/Debian Linux 上的 320 个系统调用中有 224 个具有 100% API 重要性。 换句话说,如果这 224 个调用之一丢失,典型系统上至少有一个应用程序将无法运行。 然而,加权完整性更加宽容,因为它试图捕获典型安装中可以工作的部分。 只需要 40 个系统调用即可使加权完整性超过 1%。
为简单起见,表 4 仅包含系统调用,但可以构建包含其他 API 的类似路径,例如向量系统调用、伪文件和库 API。 例如,开发者不需要实现每一个操作
表 3. 未使用的系统调用以及未使用的说明。
总共,Linux 3.19 中的 320 个系统调用中有 18 个未被 Ubuntu/Debian Linux 存储库中的任何应用程序使用。 我们在表 3 中列出了这些系统调用。除了上面讨论的问题之外,其中 10 个系统调用没有入口点,但仍然在 Linux 头文件中定义。 五个未使用的系统调用(例如 rt tgsigqueueinfo、获取鲁棒列表、重新映射文件页面、mq 通知、查找 dcookie)提供了应用程序未使用的接口。 这些系统调用可能是弃用的潜在候选者。 然而,即使任何应用程序都不使用重新启动系统调用,它也会被内核内部使用。
3.2 从“Hello World”到Qemu
图 3 显示了向原型系统添加系统调用的最佳路径,使用简单、贪婪的策略来实现 N 个最重要的 API,从而最大化加权完整性。 换句话说,图上最左边的点是最重要的 API,但只有当支持足够多的系统调用以便可以执行简单程序(例如“hello world”)时,y 坐标才会增加。 与 CDF 类似,这条线一直持续到 100%

图 3. 在操作系统中实现 N 个排名靠前的系统调用时的累积加权完整性。 越高则越顺从。
Ubuntu 应用程序。 该图突出显示了该曲线中的几个有趣点。
从本质上讲,如果没有至少 40 个系统调用,就无法运行即使是最简单的程序。 此后,通过添加另一个系统调用可以支持的其他应用程序的数量稳步增加,直到达到 125 个系统调用的拐点,或者支持文件的扩展属性,此时加权完整性跃升至 25%。 为了支持大约一半的 Ubuntu/Debian Linux 应用程序,必须有 145 个系统调用,并且曲线稳定在 202 个系统调用左右。 在最极端的情况下,qemu 的 MIPS 模拟器(在 x86-64 主机上)需要 270 个系统调用 [18]。 100% 的加权完整性意味着原型支持所有曾经使用过的 Linux 应用程序。
表 4 按所需系统调用的粗略类别细分了建议的开发阶段。 为了简洁起见,我们在此不提供完整的有序列表,但该列表可作为我们数据集的一部分提供,网址为:http://oscar.cs.stonybrook.edu/api-compat-study。
加权完整性的目标是帮助指导开发新系统原型的过程。 3.1 节显示 Ubuntu/Debian Linux 上的 320 个系统调用中有 224 个具有 100% API 重要性。 换句话说,如果这 224 个调用之一丢失,典型系统上至少有一个应用程序将无法运行。 然而,加权完整性更加宽容,因为它试图捕获典型安装中可以工作的部分。 只需要 40 个系统调用即可使加权完整性超过 1%。
为简单起见,表 4 仅包含系统调用,但可以构建包含其他 API 的类似路径,例如向量系统调用、伪文件和库 API。 例如,开发者不需要实现每一个操作

表 4. 根据 API 重要性排名实现系统调用的五个阶段。 对于每个阶段,都会列出一组系统调用,以及需要完成的工作(系统调用的数量)以及可以达到的加权完整性。
在开发系统原型的早期阶段,使用了 ioctl、fcntl 和 prctl。
3.3 向量系统调用操作码
一些系统调用,例如 ioctl、fcntl 和 prctl,本质上导出辅助系统调用表,使用第一个参数作为操作代码。 这些向量系统调用显着扩展了系统 API,极大地增加了实现完全 API 兼容性的工作量。 在这些接口上实施稳健的安全策略也很困难,因为每个操作的参数变化很大。
主要扩展来自ioctl。 Linux定义了635个操作码,Linux内核模块和驱动程序可以定义额外的操作。 就 ioctl 而言,我们观察到有 52 个操作具有 100% API 重要性(图 4),其中每个操作与 226 个最重要的系统调用一样重要。 在这 52 个操作中,47 个是 TTY 控制台的常用操作(例如 TCGETS)或 IO 设备上的通用操作(例如 FIONREAD)。
从狭义上讲,在 Linux 内核 3.19 中,fcntl 和 prctl 分别有 18 和 44 个操作。 与 ioctl 不同,fcntl 和 prctl 不可通过模块或驱动程序扩展,并且它们的操作往往具有更高的 API 重要性(图 5)。 对于 fcntl,Linux 3.19 中 18 个 fcntl 操作中有 11 个的 API 重要性约为 100%。 对于 prctl,44 个操作中只有 9 个。 图 5. fcntl 和 prctl 代码之间 API 重要性的排名。 越高越重要; 100% 表示所有安装都包含请求操作的软件。
API 重要性约为 100%,只有 18 个 API 重要性大于 20%。
因此,新系统原型的开发人员应该支持这 47 个最重要的 ioctl 操作,大约一半的 fcntl 操作码,以及仅 9-20 个 prcntl 操作。
与系统调用相比,ioctl 的不常用操作尾部要长得多。 在 Linux 内核 3.19 中托管的模块或驱动程序定义的 635 个 ioctl 操作代码中,只有 188 个的 API 重要性超过 1%,并且只有 280 个我们可以在至少一个应用程序二进制文件中找到操作的用法。 这些未使用的操作是弃用的良好目标,以减少系统攻击面。
3.4 伪文件和设备
除了主系统调用表之外,Linux还通过伪文件系统导出许多附加的API,例如/proc、/dev和/sys。 这些称为伪文件系统,因为它们不受磁盘支持,而是导出将内核数据结构的内容提供给应用程序或管理员,就好像它们存储在文件中一样。 这些伪文件系统是导出调整参数、统计数据和其他特定于子系统或特定于设备的 API 的便捷位置。 尽管许多伪文件由管理员在命令行或脚本中使用,但也有少数是由应用程序例行使用的。 为了充分理解 Linux 内核的使用模式,还必须考虑伪文件。
我们应用静态分析来查找二进制文件被硬编码为使用伪文件的情况。 我们的分析无法捕获这些文件系统之一的路径作为应用程序的输入传递的情况,例如 dd if=/dev/zero。 然而,当伪文件被广泛用作系统调用的替代品时,这些路径往往会在二进制文件中硬编码为字符串或字符串模式。 我们观察到的一个常见模式是 sprintf(‘‘/proc/%d/cmdline’’, pid); 我们的分析也捕捉到了这些模式。 在本研究中,我们也不区分访问类型,例如分离伪文件的读与写; 相反,我们只考虑文件是否被访问。 因此,我们的分析仅限于存储在二进制文件中的字符串,但我们相信这捕获了一个重要的使用模式。
图 6 显示了 /dev 和 /proc 下常见伪文件的 API 重要性。 这些文件按照 API 重要性最高的顺序排列; 管理员很少使用或直接使用的文件的长尾被省略。
有些文件是必不可少的,例如/dev/null 和/proc-/cpuinfo。 这些文件广泛用于二进制文件和脚本中。 在使用硬编码路径的 12,039 个二进制文件中,有 3,324 个访问 /dev/null,439 个访问 /proc/cpuinfo。 然而,以更简单的方式提供相同的功能是合理的。 例如,/proc/cpuinfo 提供
cpuinfo 指令的格式化包装器,可以使用虚拟化硬件将其直接导出到用户空间,类似于 Dune [17]。 同样,/dev/zero 或 /dev/null 也方便在命令行上使用,
但令人惊讶的是,大量应用程序发出读或写系统调用,而不是简单地将缓冲区清零或跳过写入(例如 grub-install)。 因此,在实现 Linux 兼容层时,少量的伪文件是必不可少的,并且也许可以通过适度的应用程序更改来消除其他伪文件。
作为伪文件或伪设备的 API 也有大量不常用或未使用的 API。 其中许多旨在支持一种特定的应用程序或用户。 例如,/dev/kvm 仅用于 qemu 与 KVM 管理程序的内核部分进行通信。 类似地,/proc/kallsyms 主要用于向内核开发人员导出调试信息。
由于 /proc 中的许多文件都是从命令行或仅由单个应用程序访问的,因此很难得出任何应该被弃用的结论。 尽管如此,这些文件代表了庞大而复杂的 API,它们创建了一个需要防御的重要攻击面。 正如其他研究中指出的那样,/proc 上的权限往往设置得足够宽松,足以泄露大量信息 [34]。 对于单个应用程序使用的文件,像细粒度功能 [48] 这样的抽象可能会更好地满足这种需求。 对于主要由管理员使用的文件,仔细设置目录权限就足够了。
对于 /dev 文件系统,最常用的文件是伪设备,例如访问虚拟终端(/dev/tty、/dev/console 和 /dev/pts),或其他功能,例如 随机数生成器(/dev/urandom)。 即使在伪设备中,诸如通过 /dev/ 接口访问标准输入和输出或进程的 TTY 之类的功能也没有被大量使用。
直观上,人们不会期望许多设备路径都是硬编码的,并且与设备的最直接交互将使用管理工具来完成。 例如,我们看到一些应用程序对 /dev/hda 之类的路径进行硬编码(通常用于 IDE 硬盘驱动器),但越来越多的系统具有使用 SATA 的根硬盘驱动器,因此将其命名为 /dev/ sda。 因此,尽管应用程序可能使用 /dev/hda 等路径作为默认设备路径,但现代系统的变化很大,通常需要在运行时搜索这些路径。
3.5 重新组织系统库API
除了研究内核接口之外,我们还分析了核心系统库(例如libc)中定义的API的重要性。 大多数程序员并不直接使用内核导出的 API,而是编写 libc 和其他库中更用户友好的 API。 例如,GNUlibc [5] 导出用于使用锁和条件变量的 API,其内部使用微妙的 futex 系统调用 [28]。
图 7 显示了 libc 导出的全局函数符号的 API 重要性——总共 1,274 个。 在这些 API 中,42.8% 的 API 重要性为 100%,50.6% 的 API 重要性低于 50%,39.7% 的 API 重要性低于 1%,其中包括一些根本不使用的 API。 换句话说,libc 中大约 40% 的 API 要么没有被使用,要么只被少数应用程序使用。
这个结果意味着大多数进程正在将大量不必要的代码加载到其地址空间中。 通过根据 API 重要性和常见链接模式将 libc 拆分为多个子库,系统可以实现显着的空间节省。
避免将额外代码加载到应用程序中的原因有多种。 首先,存在代码重用攻击,例如面向返回的编程(ROP)[47],它们依赖于查找称为小工具的特定代码片段的能力。 在进程中乱扔额外的小工具会给攻击者提供不必要的帮助。 同样,当重要和不重要的 API 位于同一页面时,内存就会被浪费。 最后,未使用的大型跳转表的空间开销非常大。 在GNU libc 2.21中,libc-2.21.so本质上有1274个重定位条目,占用30,576字节的虚拟内存。 通过根据 API 使用情况对重定位表进行排序,大多数 libc 实例只能加载重定位表的前几页,并将剩余的重定位条目留给延迟加载。
我们分析了 GNU libc 2.21 的空间节省,该版本删除了 API 重要性低于 90% 的所有 API。 libc 总共将保留 889 个 API,大小将减少到原始大小的 63%。 应用程序需要缺失函数并从另一个库加载它的概率小于 9.3%(相当于剥离的 libc 的 90.7% 加权完整性)。 还可以进一步分解,例如将同一应用程序经常访问的API放入同一子库中。
系统调用
访问、arch prctl、mprotect 克隆、execve、getuid、gettid、kill、getrlimit、setresuid 关闭、退出、退出组、getcwd、getdents、getpid、lseek、lstat、mmap、munmap、madvise、mprotect、mremap、newfsstat、读取
rt sigreturn,设置鲁棒列表,设置tid地址
rt sigprocmask
富泰克斯
库 ld.so libc
libc、ld.so
库线程
库书
libc、ld.so、libpthread
表 5. 由 libc 系列初始化或终止引起的普遍存在的系统调用使用。
标准库对 API 重要性的影响。 Libc 和动态链接器 (ld.so) 也会影响每个动态链接的可执行文件的系统调用足迹。 这对某些系统调用的 API 重要性有显着影响。 表5列出了用于初始化程序的API。然而,在某些情况下,例如设置tid地址,libc或libpthread可能是唯一直接使用这些接口的二进制文件,这表明对一些重要系统接口的更改 只需要更改一两个低级库。
4. Linux 系统和仿真层
本节使用加权完整性来评估具有部分 Linux 兼容性的系统或仿真层。 我们还根据 GNU libc 2.21 导出的 API 评估了几个 libc 变体的完整性程度。
4.1 Linux系统的加权完整性
要评估Linux系统或仿真层的加权完整性,先决条件是确定目标系统支持的API。 由于Linux API和系统实现的复杂性,识别过程很难实现自动化。 然而,操作系统开发人员大多能够根据内部知识维护这样一个列表。
我们评估了四个 Linux 兼容系统或仿真层的加权完整性:用户模式 Linux [25]、L4Linux [32]、FreeBSD 仿真层 [26] 和 Graphene 库操作系统 [51]。 对于每个系统,我们探索技术
根据系统的构建方式帮助识别支持的系统调用。 例如,User-Mode-Linux和L4Linux是通过修改Linux源代码构建的,
或者向 Linux 添加新的架构。 这些系统将定义特定于体系结构的系统调用表,并重新实现 Linux 源代码中最初的 sys * 函数。

表 7. libc 变体的加权完整性。 对于每个变体,我们根据直接从二进制文件检索的符号以及反向变体特定替换后的符号(例如,printf 变为 printf chk)来计算加权完整性。
与 GNU libc 链接——如果没有采取其他方法来提高其兼容性。 低权重完整性的原因是 Dietlibc 没有实现许多普遍使用的 GNU libc API,例如 memalign(由 8887 软件包使用)和 cxa Finalize(由 7443 软件包使用)。
5. 未加权的 API 重要性
API 重要性根据使用该 API 的应用程序的安装数量进行加权。 因此,一个无处不在的应用程序可能会导致其使用的 API 的 API 重要性接近 100%。 本节使用附加的未加权 API 重要性指标来观察具有多个变体的 API 的趋势。 我们取消了安装频率的权重,以关注开发人员行为的趋势。
一旦某个 API 被识别为存在安全风险,并且开发了更安全的变体,人们可能希望知道有多少易受攻击的软件包仍然存在,以及有多少已转移到不易被利用的 API。 同样,人们可能想知道有多少应用程序尚未从已弃用的 API 中迁移出来,即使这些应用程序并未得到广泛使用。
我们首先查看每个系统调用的未加权 API 重要性。 图 8 显示了系统调用在包之间的分布。 回想一下,根据 API 的重要性,每次安装时,Linux 上至少有一个应用程序需要超过三分之二的系统调用。 使用未加权的 API 重要性,图 8 表明所有包仅使用 40 个系统调用,而至少 10% 的包使用 130 个系统调用。 超过一半的 Linux 系统调用被不到 10% 的软件包使用。
表 6. 多个 Linux 系统或仿真层的加权完整性。 对于每个系统,我们手动识别支持的系统调用数量(“#”),并计算加权完整性(“W.Comp.”)。 根据 API 重要性,我们建议添加最重要的 API。 (*:系统调用系列。¶:添加两个以上系统调用后的石墨烯。)
最后是 sys ni syscall(返回 -ENOSYS 的函数)的别名。 其他系统,如 FreeBSD 和 Graphene,是从头开始构建的,并且通常维护自己的系统调用表结构,其中不支持的系统调用被重定向到虚拟回调。
表 6 显示了仅考虑系统调用的加权完整性。 结果还确定了开发人员应考虑添加的最重要的系统调用。 用户模式Linux和L4Linux的加权完整性都超过90%,实现了超过280个系统调用。 FreeBSD 的加权完整性为 62.3%,因为它缺少一些不太重要的系统调用,例如 inotify init 和 timefd create。 石墨烯的加权完整性仅为0.42%。 我们观察到罪魁祸首是调度控制; 通过添加两个调度系统调用,Graphene 的加权完整性将为 21.1%。
4.2 Libc的加权完整性
这项研究还使用加权完整性来评估几个 libc 变体(eglibc [4]、uClibc [8]、musl [6] 和 Dietlibc [3])与 GNU libc 的兼容性,如表 7 所列。我们观察到,如果简单地 匹配导出的 API 符号,只有 elibc 直接兼容 GNU libc。 uClibc 和 musl 的权重完整性都较低,因为 GNU libc 的头文件在编译时使用宏用更安全的变体替换了许多 API。 例如,GNU libc 将 printf 替换为 printf chk,后者对堆栈溢出执行额外的检查。 在对这个编译时 API 替换进行规范化之后,uClibc 和 musl 的加权完整性都超过 40%。 相比之下,dietlibc 仍然与大多数二进制文件不兼容表 7. libc 变体的加权完整性。 对于每个变体,我们根据直接从二进制文件检索的符号以及反向变体特定替换后的符号(例如,printf 变为 printf chk)来计算加权完整性。
与 GNU libc 链接——如果没有采取其他方法来提高其兼容性。 低权重完整性的原因是 Dietlibc 没有实现许多普遍使用的 GNU libc API,例如 memalign(由 8887 软件包使用)和 cxa Finalize(由 7443 软件包使用)。
5. 未加权的 API 重要性
API 重要性根据使用该 API 的应用程序的安装数量进行加权。 因此,一个无处不在的应用程序可能会导致其使用的 API 的 API 重要性接近 100%。 本节使用附加的未加权 API 重要性指标来观察具有多个变体的 API 的趋势。 我们取消了安装频率的权重,以关注开发人员行为的趋势。
一旦某个 API 被识别为存在安全风险,并且开发了更安全的变体,人们可能希望知道有多少易受攻击的软件包仍然存在,以及有多少已转移到不易被利用的 API。 同样,人们可能想知道有多少应用程序尚未从已弃用的 API 中迁移出来,即使这些应用程序并未得到广泛使用。
我们首先查看每个系统调用的未加权 API 重要性。 图 8 显示了系统调用在包之间的分布。 回想一下,根据 API 的重要性,每次安装时,Linux 上至少有一个应用程序需要超过三分之二的系统调用。 使用未加权的 API 重要性,图 8 表明所有包仅使用 40 个系统调用,而至少 10% 的包使用 130 个系统调用。 超过一半的 Linux 系统调用被不到 10% 的软件包使用。

set*id API 系列是容易出现安全问题的 API 系列之一。 许多 set*id API 在不同的 Unix 变体中具有细微的语义差异。 陈等人。 [22] 得出的结论是 setresuid 在所有 Unix 风格中具有最清晰的语义。 表 8 显示了 set*id 和 get*id 系统调用的未加权 API 重要性。 大多数软件包都采用了更清晰、更安全的界面。 系统调用 setuid、setreuid 和 setresuid 的未加权 API 重要性分别为 15.67%、1.88% 和 99.68%。 然而,对于 get*id 系统调用,未加权的 API 重要性表明 getres*id 系统调用仅被大约 36% 的包使用。
目录操作长期以来一直存在可利用的竞争条件 [20,21,54] 或检查时间到使用时间 (TOCTTOU) 漏洞。 在特权应用程序中,一个系统调用(例如访问)检查用户的权限,第二个调用对文件进行操作。 有一些对策可以有效地遍历用户空间中的目录层次结构[50]。 这种方法用 faccessat 和类似的变体替换了诸如 access 之类的调用。 表 8 显示了 *at 系统调用变体及其旧版本的当前未加权 API 重要性。 我们观察到,有竞争倾向的访问的未加权 API 重要性仍然很高 (74.24%),而 faccessat 只有 0.63%。 这表明大约 75% 的软件包使用更容易受到攻击的访问系统调用,而不是更安全的访问系统调用。
除了与安全相关的提示之外,未加权的 API 重要性还表明过时的 API 是否已被更新的变体所取代。 例如,wait4 系统调用被认为已过时 [9],并且首选替代 waitid,因为它更精确地指定要等待哪个子状态更改。 然而,wait4 和 waitid 的未加权 API 重要性分别为 60.56% 和 0.24%。 这表明 60% 的软件包仍在使用较旧的 wait4 系统调用。 表 9 显示了其他一些系统调用的类似趋势。 我们的数据集为系统开发人员提供了更多与应用程序开发人员积极沟通的机会,以加快有问题的 API 退役的过程某些 API 特定于特定操作系统(例如 Linux),并且通常具有更可移植的变体。 表 10 显示了特定于 Linux 的 API 与其通用变体之间的比较。 结果显示,大多数开发人员更喜欢可移植或通用 API,而不是 Linux 特定的 API。 除了 pipeline2 之外,大多数特定于 Linux 的 API 变体的未加权 API 重要性均低于 10%。
最后,我们考虑具有多种变体的系统调用,其中一个版本增加了功能。 表 11 显示了这些系统调用之间的差异。 有趣的是,更多的开发人员选择了功能较弱的变体,例如使用 select 而不是 pselect6,或者使用 dup2 而不是 dup3。 这表明,开发人员通常会选择简单性,除非任务需要更强大的 API 变体的功能。

6. 对系统开发人员的影响
第 3 节中的统计数据可以为应用程序开发人员、库开发人员和内核开发人员的决策提供信息。 同样,轻松生成 API 足迹的全面数据集的能力也有多种实际用途。
这项研究的一个实际好处是能够自动识别随 Ubuntu/Debian Linux 分发的每个应用程序的系统调用配置文件。 事实上,我们观察到总共 31,433 个应用程序具有 11,680 个不同的系统调用足迹,其中 9,133 个应用程序具有独特的系统调用足迹。 我们注意到这些数字可能会因动态分析而变化,但有趣的是,所有 Debian/Ubuntu 应用程序中有三分之一具有独特的系统调用足迹。
系统调用足迹之前已被探索用于识别恶意软件或软件危害[36]。 Linux 最近添加了 seccomp,一个基于 Berkeley Packet Filter 的系统调用过滤框架 [46]; 使用我们的框架可以轻松自动生成 seccomp 策略,从而在应用程序受到攻击时减少系统的攻击面。
这些工具还可以帮助操作系统开发人员评估何时可以安全地删除已弃用的接口,或者何时可以安全地删除已弃用的接口。

似乎与大多数用户无关(例如,重新映射文件页面)。 在不相关的接口的情况下,这可能表明某些东西是废弃的候选者(例如,lookup dcookie),或者表明有用或重要的功能(例如,faccessat)没有获得足够的关注。 Linux 开发人员目前等待长达六年的时间才能淘汰一个接口,从而为应用程序和库开发人员提供了充足的时间进行更改。 我们的数据集和方法可以实现更主动的推广和更快速的系统发展。
libc 函数调用流行度同样可以帮助库开发人员删除未使用的函数调用(222 个函数)。 此外,函数调用重要性分布还可以通过按重要性组织内存布局来帮助减少库的内存占用。
七、实施细节
本节提供了我们的分析框架的其他实现细节。
我们的分析基于使用标准 objdump 工具反汇编每个应用程序包内的二进制文件。 这种方法消除了对源代码或重新编译的需要,并且可以处理闭源二进制文件。 我们实现了一个简单的调用图分析来检测可从二进制入口点(ELF 标头中的入口)到达的系统调用。 我们搜索所有二进制文件,包括库,以查找系统调用指令(int $0x80、syscall 或 sysenter)或调用 libc 的 syscall API。 我们发现大多数二进制文件(无论是共享库还是可执行文件)并不直接选择系统调用,而是使用 GNU C 库 API。 在 66,275 个研究的二进制文件中,只有 7,259 个可执行文件和 2,752 个共享库发出系统调用。
我们的调用图分析允许我们只选择应用程序实际使用的系统调用,而不是 libc 中出现的所有系统调用。 我们的分析采取以下步骤:
• 对于目标可执行文件或库,生成内部函数使用的调用图。
• 对于可执行文件所依赖的每个库函数,标识库中可从可执行文件调用的每个入口点访问的代码。
• 对于调用另一个库调用的每个库函数,递归地跟踪调用图并聚合结果。
从静态分析中精确确定所有可能的调用图具有挑战性。 与其他基于调用图构建的工具(例如控制流完整性(CFI))不同,我们的框架可以容忍因过度近似分析结果而导致的错误。 例如,程序有时会根据函数调用者作为参数传递的函数指针进行函数调用。 由于调用目标是动态的,因此在调用现场很难确定。 相反,我们跟踪函数指针分配给寄存器的位置,例如使用 lea 指令以及相对于当前程序计数器的地址。 这是一个过近似,因为我们假设将调用分配给局部变量的函数指针,而不是跟踪数据流。 如果包含数据流组件,该分析可能会更加精确。
我们还对一些常见和有问题的模式进行硬编码。 例如,我们通常假设将系统调用号传递给系统调用或将操作码传递给向量系统调用的寄存器不是同一函数中算术的结果。 我们抽查了这个假设,但没有进行数据流分析来检测这种情况。
最后,分析的最后一英里是递归聚合足迹数据。 我们将所有原始数据插入 Postgresql 数据库,并使用递归 SQL 查询来生成结果。 扫描存储库中的所有 30,976 个包、收集数据并生成结果大约需要三天时间。
表 12 总结了我们的实现。我们用 Python 编写了 3,105 行代码,用 SQL (Postgresql) 编写了 2,423 行代码。 该数据库包含 48 个表,超过 4.28 亿条条目。
八、相关工作
在我们工作的同时,Atlidakis 等人。 [14]对 POSIX 进行了类似的研究。 POSIX 研究的一个特别重点是测量不同 POSIX 兼容操作系统(Android、OS X 和 Ubuntu)之间的碎片,以及识别高层框架导致这种碎片的点,例如缺乏普遍存在的 图形的抽象。 这两项研究都发现了操作系统 API 中未使用或很少使用的功能的长尾。 POSIX 研究因素包括动态跟踪,这可以产生性能见解; 我们的研究使用安装指标,可以深入了解不兼容性对最终用户的影响。 我们的论文提供了一些补充见解,例如模拟层完整性的度量和增量路径,以及对不常分析的 API(例如 /proc 下的伪文件)重要性的分析。
先前的许多研究已经调查了操作系统的其他部分如何相互作用,通常是大规模的。 Kadav 和 Swift [35] 研究了 Linux 内核导出到设备驱动程序的有效 API,以及设备驱动程序与 Linux 的交互,这补充了我们对应用程序如何与内核或核心库交互的研究。 帕利克斯等人。 研究 Linux 内核所有子系统中的故障并识别最容易出现故障的子系统 [38]。 他们发现特定于体系结构的子系统的故障率最高,其次是文件系统。 哈特等人。 [31] 研究了一组 Mac OS X 应用程序与文件系统 API 的交互,识别了许多令人惊讶的 I/O 模式。 我们的方法是对这些研究的补充,重点关注整个 Linux 发行版的整体 API 使用情况。
之前的许多研究都使用 Debian 和 Ubuntu 软件包元数据和流行度竞赛统计数据来推断用户和开发人员的行为。 对 Debian 软件包进行了分析,以研究软件本身的演变 [29,37,45],衡量应用程序编程语言的流行度 [10],分析软件包之间的依赖关系 [23],识别趋势 包大小 [12]、参与开发和维护包 [44] 的开发人员数量,以及估计开发成本 [11]。 贾恩等人。 使用人气竞赛调查数据来优先考虑新系统安全策略的实施工作[33]。 这项研究的独特之处在于,根据应用程序安装的频率,使用此信息来推断系统 API 对最终用户的相对重要性。
许多先前的项目开发了识别软件不兼容性的技术或工具,目的是避免软件组件集成过程中的细微错误。 Linux Standard Base (LSB) [24] 根据应用程序从系统库导入的符号来预测应用程序是否可以在给定发行版上运行。 其他研究人员研究了同一库不同版本之间的应用程序兼容性,为库开发人员创建了维护跨版本兼容性的规则[40]。 以前的项目还开发了工具来验证库的向后兼容性,基于检查库变量类型定义和函数签名的任何更改[41]。 兼容性的另一种变化着眼于集成大型软件项目的独立开发组件; 解决方案检查组件源代码的各种属性,例如递归函数和不同类的强耦合[49]。 在这些研究中,兼容性是二元属性,反映了对正确性的关注。 此外,这些研究重点关注应用程序与图书馆或分发生态系统之间的接口。 相比之下,本文提出了一种衡量原型系统相对完整性的指标。
出于多种原因,识别应用程序的系统调用足迹非常有用; 我们的工作通过研究大量应用软件中系统 API 使用趋势来提供数据。 应用程序的系统调用足迹可以通过静态或动态分析来提取。 权衡是动态分析更容易快速实施,但结果取决于输入。 正如本文所使用的,二进制静态分析可能会被混淆的二进制文件所阻碍,这可能会混淆反汇编程序[57]。 静态二进制分析已用于自动生成特定于应用程序的沙箱策略[36]。 动态分析已用于比较系统调用序列两个应用程序作为潜在知识产权盗窃的指标[53],识别批处理系统调用的机会[43],对移动设备上的功耗进行建模[39],以及重新打包应用程序以在不同系统上运行 [30]。 这些项目回答的问题与我们的问题截然不同,但原则上可以从生成的数据集中受益。
9. 结论
基于这项研究,我们可以得出关于 Linux API 本质的几个结论。 首先,对于我们数据集中的任何操作系统安装,一旦考虑到 /proc 下的 ioctl 操作码和文件,所需的 API 大小比 Linux 中的 320 个系统调用大几倍。 坚实的三分之二的系统调用是不可或缺的。 我们表明,大量的系统调用和其他 API 很少甚至从未使用过。 该论文还绘制了向 Linux 模拟层或研究原型添加系统调用的粗略指南。
我们希望该数据集将供研究人员和开发人员进行进一步分析,我们的方法和工具可以轻松应用于未来的版本和其他发行版。 我们的数据集、工具和其他信息可从 http://oscar.cs.stonybrook.edu/api-compat-study 获取。
致谢
我们感谢匿名审稿人 William Jannen 和我们的牧羊人 Bianca Schroeder 对本书早期草稿的富有洞察力的评论。 这项研究得到了 NSF 拨款 CNS-1149229、CNS-1161541、CNS-1228839、CNS-1405641、CNS-1408695、CNS-1526707 和 VMware 的部分支持。
附录 A 正式定义 A.1 API 重要性
系统安装 (inst) 是一组已安装的软件包 ({pkg1, pkg2, ..., pkgk ∈ Pkgall})。 对于安装程序可以安装的每个包 (pkg),我们分析包中包含的每个可执行文件 (pkg = {exe1,exe2,...,exej}),并生成包的 API 占用空间 作为:
任何安装至少包含一个使用 API 的包的能力; 即,该 API 属于至少一个包的占用空间。 使用 Ubuntu/Debian Linux 的软件包安装-拉丁裔统计数据,可以计算安装特定软件包的概率:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值