Page 90 - 《软件学报》2025年第7期
P. 90

贾昂 等: 面向函数内联场景的二进制到源代码函数相似性检测方法                                                 3011


                    在函数定义中, O2NMatcher 提取了“Inline”和“Static”两个重要关键字的使用次数作为特征. 关键字“Inline”是
                 对编译器的建议, 表明当其他函数调用此函数时, 应将其内联. 由                  Static 修饰的函数仅可由同一编译单元中的其他
                 函数访问. 这些关键字直接或间接地影响编译器的内联决策.
                    同样在图    2  中, 函数  dtls1_get_record  没有被定义为“Static”函数, 而函数  dtls1_process_buffered_records, 函数
                 dtls1_get_bitmap, 函数  dtls1_record_replay_check  都被定义为“Static”函数, 相比之下, Static 函数更容易被内联到其
                 他函数中.
                    在函数调用中, O2NMatcher 提取了调用函数与被调用函数的调用次数和被调用次数作为特征. 直观上, 如果
                 一个被调用函数被众多调用者函数调用, 将其内联到所有调用者中的成本会随着调用者数量的增加而增加. 相反
                 地, 当一个函数仅被调用一次时, 内联只会带来优化的好处而不会增加代码大小, 这是进行内联的理想情况.

                 4.1.2    调用指令
                    对于调用指令, 调用指令的位置和函数调用的参数信息是影响函数内联实施的两个重要因素. 例如, 如果一个
                 函数调用位于由“for”或“while”定义的循环中, 内联该函数调用将显著减少函数调用的时间. 此外, 如果一个函数
                 调用包含常量参数, 并且这个参数能帮助确定被调用函数中的某些分支, 那么内联该函数只需要内联对应的分支
                 内容, 这也会使内联这些函数的成本有所降低.

                 4.2   内联函数调用预测
                    内联函数调用预测旨在预测开源软件中的内联函数调用, 主要包括两个部分: 模型训练和模型测试. 从流程上
                 来讲, 本节首先设计一个分类器并在训练数据集上对其进行训练, 然后使用这个分类器来预测测试数据集中的函
                 数调用的标签.

                 4.2.1    模型训练
                    不同的编译器家族和不同的优化级别会导致不同的内联函数调用. 考虑到在每种编译设置下, 每个函数调用都
                 将有一个相应的内联标签, 本文将内联函数调用预测问题视为一个多标签分类                        (multi-label classification, MLC) 问题.
                    在多标签分类中, 预测的是多个标签的存在与否, 并且可以同时为一个样本分配多个标签. 例如, 由于在本章
                 的数据集中, 每个函数调用在         8  种编译器×4  种优化级别=32    种编译配置下被编译, 每种编译情况下都有对应的内
                 联情况, 因此它将有      32  个标签, 每个对应一种编译配置.
                    如第  3.2  节所发现的, 随着优化级别的增加, 内联函数调用呈递增叠加态势, 且同一编译器家族的编译器做出
                 类似的内联决策. 考虑到内联决策间的关联性, 本文设计了一个名为                     ECOCCJ48  的多标签分类器, 它使用二元关
                 联  (binary relevance) 为不同编译器家族预测标签, 并使用分类器链        (classifier chains) 为不同优化级别预测标签.
                    如图  6(a) 所示, 编译器级别的分类器为        GCC  和  Clang  编译器家族分别设计了对应的优化级别分类器, 这两个
                 分类器分别独立训练        (二元关联). 由于同一编译器家族的编译器做出类似的内联决策, ECOCCJ48                   整合了同一编
                 译器家族下的函数调用, 标签数量将从            32  减少到  8 (2  种编译器家族×4  种优化级别).

                                                                           O0       O0
                                                                         分类器        标签
                                       函数调用
                                         特征
                                                                           O1       O1
                                                                         分类器        标签
                                                             函数调用
                                                               特征
                                 GCC             Clang
                                 分类器            分类器                        O2       O2
                                                                         分类器        标签

                                 GCC             Clang                     O3       O3
                                 标签              标签                      分类器        标签
                                      (a) 编译器级别                       (b) 优化选项级别
                                                   图 6 ECOCCJ48  架构图
   85   86   87   88   89   90   91   92   93   94   95