Page 87 - 《软件学报》2025年第7期
P. 87
3008 软件学报 2025 年第 36 卷第 7 期
等的语义, 因此当添加该源代码函数集合至开源软件语料库后, 不再面临超出语料库问题. O2NMatcher 的整体设
计见第 4 节.
为了应对挑战 2, O2NMatcher 训练了模型 ECOCCJ48 用于自动化的函数内联预测. 模型 ECOCCJ48 将函数
内联的预测问题建模为一个多标签分类问题, 并构建一个内联数据集用于训练和测试. 第 3.1 节介绍数据集的构
建过程, ECOCCJ48 的训练和测试过程见第 4.1、4.2 节.
为了应对挑战 3, 本文对不同编译设置下函数内联之间的关联性展开了分析, 发现了在编译器家族和不同优
化选项下的内联规律, 并依照此规律将 ECOCCJ48 设计为一个基于分类器链和二元关联的多标签分类模型. 第
3.2 节是函数内联规律分析, ECOCCJ48 模型的具体设计见第 4.2 节.
3 内联规律分析
在介绍 O2NMatcher 的具体设计之前, 本文先对函数内联的规律展开一系列调研.
3.1 数据集构建与标注
本文使用了 GNU 组件中的 51 个项目, 并使用 8 个编译器 (GCC-4.9.4, GCC-5.5.0, GCC-6.4.0, GCC-7.3.0,
Clang-4.0, Clang-5.0, Clang-6.0, Clang-7.0), 4 个编译选项 (O0, O1, O2, O3) 编译到 X86-64 架构下, 共生成 7 520 个
二进制文件和 1 130 467 个二进制函数. 编译过程使用了 Binkit [22] 提供的编译工具进行构建.
对于源代码项目和编译后得到的二进制文件, 本文使用我们先前工作 [12] 中的内联函数标注方法, 以得到二进
制函数和源代码函数之间的映射关系. 具体而言, 首先从二进制文件的. debug_line 段抽取了二进制地址到源文件
代码行的行映射关系, 然后在源代码端抽取了源代码行和源函数的映射关系, 在二进制端抽取了二进制地址和二
进制函数的映射关系, 最后将二进制函数和源代码函数通过二进制地址到代码行的映射进行拓展, 即可获得二进
制函数和源代码函数的映射关系.
如果一个二进制函数映射到多个源代码函数上, 它将被识别为一个内联的二进制函数. 而为了进一步标注发
生内联的函数调用, 本文继续分析了这些调用的行映射关系. 简单来说, 当存在一个二进制函数调用, 其地址映射
到某个源代码调用位置时, 那么这个源代码调用位置是没有被内联的. 相反, 如果不存在二进制调用位置的地址能
映射到某个源代码调用位置上, 那么这个源代码调用位置就是被内联的.
经过标注后, 数据集中共识别出了 211 680 个发生内联的二进制函数. 其中在编译过程中, 有 909 597 个函数
调用未被内联, 173 453 个函数调用被内联.
3.2 内联规律分析
源代码函数集合需要对应发生内联的二进制函数, 而内联的函数调用决定了内联的二进制函数的生成. 因此,
哪些函数调用会被内联实际上决定源代码函数集合将如何构建. 基于上述逻辑, 本节将分析不同编译设置下的内
联函数调用之间的关联性, 以便于后续源代码函数集合构建方法的设计.
3.2.1 不同编译选项下的函数内联关系
图 3 展示了在 8 个编译器和 4 种优化选项下内联函数调用的分布情况. 对于每个编译器, 图中统计了高优化
级别下的内联函数调用与低优化级别下的内联函数调用的交集, 并在使用不同颜色进行表示. 例如, 在 GCC-4.9.4
中, O3 优化级别柱状图的红色部分代表了在使用 O3 和 O0 优化时均被内联的函数调用, 蓝色部分代表了在使用
O3 和 O1 优化时均被内联的函数调用, 黄色部分代表了在使用 O3 和 O2 优化时均被内联的函数调用, 而绿色部
分则代表了仅在使用 O3 优化时被内联的函数调用.
可以看出, 随着优化级别的提高, 内联函数调用的数量呈递增叠加趋势. 如图 3 所示, 在高优化级别下的内联
函数调用大量继承了低优化级别下的内联函数调用. 例如, 在 GCC-4.9.4 中, 使用 O1 优化时有 4 156 个函数调用
被内联, 其中 3 058 个函数调用 (占 75%) 在使用 O2 优化时也被内联, 2 956 个函数调用 (占 71%) 在使用 O3 优化
时被内联. 总体而言, 低优化级别下被内联的函数调用中有 75.7% 在使用高优化级别时也被内联 (低优化级别内
联的 105 553 个函数调用中有 79 862 个在高优化选项下也被内联).

