使用大语言模型增强白盒编译器模糊测试

互联不一般哥 2024-05-03 10:16:38

White-box Compiler Fuzzing Empowered by Large Language Models

Chenyuan Yang, Yinlin Deng, Runyu Lu, Jiayi Yao, Jiawei Liu, Reyhaneh Jabbarvand, Lingming Zhang

引用

Yang C, Deng Y, Lu R, et al. White-box compiler fuzzing empowered by large language models[J]. arXiv preprint arXiv:2310.15991, 2023.

论文:https://arxiv.org/abs/2310.15991

仓库:https://github.com/ise-uiuc/WhiteFox

摘要

编译器是软件工程领域极其重要的一部分,它的正确性必须要得到保障,一旦出现误编译就会使程序行为虚假化,可能导致软件供应链出现严重问题。目前模糊测试是对编译器缺陷进行深入研究的主要方法。然而,编译器模糊测试仍然存在一定问题:现有的方法主要集中在黑盒和灰盒模糊测试上,这些方法生成测试程序时难以完全理解编译器内部的行为。因此它们往往无法构建测试程序来执行复杂优化。此外,传统的白盒技术如符号执行也无法应用于编译器系统的庞大代码库。最近研究者发现,大语言模型(LLMs)在代码生成/理解任务上表现出色,在黑盒模糊测试中表现出了优异的性能。然而,使用编译器源代码信息提示LLMs仍然是编译器测试中的一个缺失的研究领域。

为此,本文提出了WhiteFox,这是第一个使用LLMs和源代码信息进行白盒编译器模糊测试来优化编译器的工具。WhiteFox采用双模型框架:(i)分析LLM分析底层优化源代码,并生成对触发优化所需的高级测试程序的需求;(ii)生成LLM根据总结的要求生成测试程序。此外,把触发优化的测试用作反馈指标,以进一步实时增强测试生成提示。本文对来自各个领域的四个流行编译器进行评估,结果显示WhiteFox可以生成高质量的测试程序来执行需要复杂条件的深度优化,优化能力比现有最先进的模糊测试工具高出8倍。迄今为止,WhiteFox总共发现了96个编译器的错误,其中80个被确认为以前未知的,51个已经修复。除了编译器测试外,WhiteFox还可以用于一般复杂的实际软件系统的白盒模糊测试。

1 引言

现代编译器能将高级编程语言转换为高效的机器代码(例如X86指令)。然而,不正确的优化可能导致微小且难以检测的错误,甚至引发难以预见的程序行为。考虑到编译器在软件开发中的普遍应用,确保编译器优化的正确性至关重要。为了暴露潜在的编译器缺陷,研究者会使用模糊测试自动生成大量的测试输入。目前,有许多语言与编译器都有了模糊测试工具,并且这些工具也能在这些编译器中发现大量错误。

此前的研究中,研究人员提出了各种模糊测试技术,以在测试生成过程中能够观测到有关被测系统(SUT)的知识。它们主要根据模糊器可见SUT内部的程度分为三类:黑盒模糊测试、灰盒模糊测试和白盒模糊测试。黑盒模糊测试不关心SUT的内部工作原理,仅考虑系统输入/接口信息。因此,生成的输入不符合SUT的预期结构或行为。相比之下,白盒模糊测试技术通过检查SUT的源代码,并合成测试用例,以详尽地探索所有可能的代码路径。灰盒模糊测试介于黑盒和白盒模糊测试之间。通过利用SUT的部分程序信息(例如代码覆盖率),灰盒模糊测试试图有效地生成更有可能触发新程序行为的测试。虽然理论上可以将所有这些方法都应用于编译器模糊测试,但由于现代编译器系统的复杂性和庞大的规模,这些方法也很难直接得到应用。例如,广泛使用的C/C++编译器LLVM的代码量达到了1400万行,而流行的DL编译器TensorFlow有350万行代码。

黑盒模糊测试在不了解内部工作原理的情况下,很难触发优化所需的复杂条件。仅仅生成随机输入,没有任何指导,往往无法实现到达优化逻辑的深层次。灰盒模糊测试虽然比其黑盒模糊测试更加了解源代码内部,通常也具有更高的代码覆盖率,但仍然面临着类似于黑盒技术的问题。由于编译器优化通常需要满足精确引导和严格条件,它经常无法完全理解触发特定优化所需的条件。简单的覆盖率驱动策略可能无法有效地引导到这些特定状态。

能否将白盒模糊测试扩展到完全测试任何编译器的优化呢?作者基于这样一个思考来解决这个问题:大型语言模型(LLMs,Large Language Models)在广泛的编程语言内进行了预训练。这使它们擅长于理解和生成各种编程语言的代码。因此大语言模型可以尝试用来解决编译器优化的问题。

基于上述思考,本文提出了 WhiteFox,这是第一个利用大型语言模型(LLMs)进行白盒编译器模糊测试的方法,并能完全测试各种现代编译器中的核心优化模块。正如上文所述,现有的白盒测试方法无法扩展到模拟复杂编译器系统的行为信息。因此,WhiteFox的关键思想是利用LLMs根据源代码实现自动推断出能触发编译器中的优化的测试程序的要求。LLMs已经在自然语言和各种编程语言的广泛语料库上进行了预训练,具有理解和简洁总结优化源代码的能力。首先,作者使用一个分析LLM自动分析和总结触发优化所需的测试要求。随后使用生成式LLM根据生成的要求产生大量有意义的测试程序。为了生成可以直接触发相应优化的测试程序,WhiteFox进一步采用了一个反馈循环机制,使用优化触发测试作为少量示例来指导未来的测试生成。

本文做出了以下贡献:

本文引入了一种新的白盒编译器模糊测试维度,通过利用LLMs作为优化源代码分析器和测试输入生成器。这项工作是首个表明LLMs能够将低级优化实现信息转化为相应的高级测试程序的工作,使得将白盒模糊测试技术应用于测试各种领域的编译器变得可行。此外,WhiteFox还可以改编用于对其他复杂的、现实世界的软件系统进行白盒模糊测试。本文的方法是通用的,可以应用于各种编译器系统。作者使用WhiteFox实现了四个系统下的实验,包括三个广泛使用的深度学习(DL)编译器和一个流行的C/C++编译器LLVM。它由一个分析LLM(GPT4)来根据源代码总结要求,以及一个生成LLM(StarCoder)来根据要求生成大量测试输入组成。作者对WhiteFox与目标编译器上的最先进的模糊器进行了广泛的比较研究。结果表明,WhiteFox可以比基线多出2.5 倍的优化。截至目前,WhiteFox已检测到 96 个 bug,其中有 80 个已确认为之前未知的,其中 51 个已经修复。

2 技术介绍

图2展示了WhiteFox的总体概览,包括三个主要组成部分:需求总结、测试生成和反馈循环。WhiteFox从被测试的编译器中获取优化传递的源代码作为输入,并通过LLM生成高质量的测试程序。因此,在需求总结阶段需要使用分析LLM来总结测试程序的需求,通过检查其源代码实现来触发优化。接下来使用分析的需求来提示生成LLM,它综合测试程序来具体地实践相应的优化。在编译和执行后生成的测试程序后,WhiteFox观察它们是否确实通过检测激活了相应的优化。如果一个测试程序触发了优化,它将作为示例纳入WhiteFox的反馈循环中,以指导生成LLM在随后的迭代中针对性地生成优化。

图2:WhiteFox结构概览

WhiteFox采用了双模型框架:一个分析LLM,通过检查实现代码来推断触发优化的条件;一个生成LLM,用于生成大量有意义的测试程序。这种设计使得不同LLMs提供的成本和收益之间取得平衡,可以让分析LLM具有广泛的知识和推理能力(包括自然语言和代码),而将生成LLM专门用于高效的程序生成。

2.1 需求总结

尽管可以使用LLMs直接从优化源代码生成测试,但它的性能并不佳。这是因为优化源代码具有以下两个的特点:(i)优化的实现源代码通常很长。它通常包含了冗余信息,如详细的注释。此外,它可能包含与触发输入特征无关的代码,例如实现细节、触发优化后的IR转换和错误记录。这样的长源代码不仅浪费LLMs处理时间,而且增加了LLMs的理解难度,有些源代码甚至可能会超出LLMs可用的有限上下文窗口。(ii)实现代码的层次较低,涉及大量领域特定模块、IRs(中间表示)和辅助函数。它们在编译器内部使用,在高层不可见。为了解决上述问题,作者提出利用分析LLM提供清晰、简洁和易于理解的优化触发需求总结,这将有助于后续的测试生成。作者考虑了两种摘要格式:自然语言和伪代码。为了结合并最大化它们的优势,作者将自然语言和伪代码结合起来描述触发优化的需求,而不是仅仅依赖其中任何一种格式。这种混合格式为分析LLM提供了更大的灵活性,可以根据每个需求组件的需要使用自然语言或伪代码,从而产生更高质量的总结。作者的实验结果也支持,自然语言和伪代码混合使用效果最佳。WhiteFox首先利用分析LLM推断出可能触发优化的高级输入的需求,利用其在低级源代码中编写的实现。即,对于每个优化,作者使用少量示例上下文学习(few-shot in-context learning)来提示分析LLM生成其触发输入的要求,格式为自然语言和伪代码的混合格式。图3(a)展示了用于总结目标编译器中优化要求的通用少量提示模板。该提示模板以指令“请描述可以触发 [OPTIMIZATION NAME] 优化的 [TARGET INPUT] ...”开头,其中 [TARGET INPUT] 是特定于目标编译器的输入格式。然后,提供优化实现的源代码,并以描述输入应满足的要求结束。描述采用自然语言和伪代码的混合格式,包括 [PSEUDO CODE] 和 [NL DESCRIPTION]。目标优化具有与少量示例相同的结构,但其要求字段留空,等待LLM生成。以PyTorch Inductor中的需求汇总提示为例,它的简短提示如图3(b)所示。这些少量示例可以指导分析LLM生成所需的输出格式。此外,它们通过提供从低级优化实现到高级输入程序要求的示例映射,促进了分析LLM的学习过程。

图3:WhiteFox中的需求总结提示

2.2 测试生成

通过利用优化需求描述,WhiteFox利用LLM的能力生成可以有效触发相应优化以进行漏洞检测的测试输入。类似地,作者利用少量示例上下文学习来根据需求生成针对每个优化的特定测试输入。图4(a)展示了WhiteFox中用于测试生成的通用提示模板。在模板中,少量示例包含一条指示,如:“请生成一个符合指定要求的有效的[TARGET INPUT]示例,其中[INPUT SPECIFICATION]满足指定需求。”然后,它详细说明了激活优化所需的要需求,并以一个示例优化的示例输入结尾。目标优化类似于少量实例,而其测试输入由LLM生成。图4(b)展示了PyTorch Inductor中使用的测试生成提示。PyTorch Inductor的测试输入格式是一个PyTorch模型([TARGET INPUT])与公共PyTorch API([INPUT SPECIFICATION])。接下来,作者指定了测试输入满足激活permute_linear_fusion优化的要求。这与可以触发此优化的示例模型相辅相成。提供的少量示例有助于LLM以期望的格式生成测试。

图4:WhiteFox的测试生成提示词

2.3 反馈循环

利用优化实现来指导LLM生成测试的目的是生成能够有效执行优化的各种测试,最终发现优化中潜在的bug。虽然图4中的提示包含来自相同编译器的示例优化,但这些少量示例缺乏针对目标优化的具体指导。在WhiteFox中合并反馈循环是因为生成的测试本身是来自SUT的有价值的反馈,并且可以作为在未来迭代中模糊目标优化的指导。在每次迭代中,当为目标优化发现新生成的测试时,作者将它们收集为未来测试生成提示的少量示例候选项。通过将这些成功触发测试纳入提示,作者增强了对生成LLM的有针对性指导,使其能够生成更多触发目标优化的输入。

基于这一想法,WhiteFox将成功触发相应优化的测试合并为迭代测试生成过程中的补充示例。使用目标编译器来记录测试输入触发的优化。如果测试输入成功地执行了相应的优化,它将被纳入作为后续针对该特定优化的测试生成的候选示例。即在每次迭代中,如果存在当前优化的触发输入,WhiteFox将选择一些触发输入作为示例(默认3个)。这些示例触发输入将与目标优化的指令和需求摘要(与前一个提示相同的指令和摘要),并将用于生成下一批测试输入,如图5所示。这种反馈设计有助于LLM生成具有更高触发目标模式可能性的测试,作者也在后续实验中证明了这一点。在某些情况下,如果某个特定优化没有触发输入,WhiteFox将继续在后续迭代中使用初始提示,直到找到能够触发该优化的输入。

图5:用反馈提示生成测试

并非所有触发示例在指导LLM生成新的有价值触发测试方面都同样有效。评估其有效性的一个有效标准是将新生成的测试当作少量示例时的触发率。为了有效地选择能够不断发展的示例有效性知识的触发示例,必须在探索和开发之间找到平衡。为了解决这个问题,WhiteFox采用了Multi-Armed Bandit(MAB)算法,即Thompson Sampling(汤普森采样)作为触发示例的选择策略。在MAB框架中,每个触发示例被构想为一个臂。这里的主要假设每个触发示例都与一个概率相关联,表示触发率,该概率量化了使用给定示例时生成的能够触发优化的测试的比例。在模糊测试循环中,实验者的目标是估计与每个触发示例相关联的概率,以便使用最有效的示例生成更有价值的触发测试。

3 测试预言

WhiteFox使用以下表现作为编译器错误的特征。

结果不一致。在编译过程中,程序通过一系列优化过程迭代地转化为语义等效但更高效的代码。然而,由于优化过程实现中的逻辑缺陷,可能会发生编译错误,导致生成的机器代码中出现语义不一致。这种语义不一致可以通过差分测试来显现。即对于每个可编译且可执行的测试程序,在对程序施加相同的输入后交叉检查经过优化和未经优化(或最小优化)版本的程序产生的输出。崩溃。编译器和编译的可执行文件意外崩溃是编译器错误的明显标志。因此,WhiteFox积极捕获测试程序在编译和执行时的崩溃信号,包括进程中止(process aborts)、分段错误(segmention faults)和禁止的内部异常(forbidden internal exceptions,例如PyTorch中的INTERNAL_ASSERT_FAILED)。

4 实现

通过指定相关目录,作者收集了专门用于优化的编译器源代码。然后,作者通过简单的关键词模式匹配来识别执行优化的代码片段(例如函数),还收集了主要优化器调用的辅助函数,因为它们可能揭示激活优化器的基本条件。驱动WhiteFox需要管理和识别与优化相关的代码片段。为了收集反馈循环的触发信息,作者通过在每个收集的优化函数中插入一个日志记录语句来对其进行插装。之后在编译测试程序时,作者可以从日志中获得一系列激活的优化传递。

WhiteFox是建立在最先进的GPT4和StarCoder之上的。GPT4在代码理解方面表现出色,并且能够高效处理自然语言处理任务,但成本高昂,适合作为分析LLM,用于数量较少的分析。而StarCoder是一个具有155亿参数和8K上下文长度的专门用于代码的经济型LLM,因此适合于高效的连续测试生成。作者利用GPT4作为分析LLM,对于每个优化,作者让分析LLM通过OpenAI API生成一个温度为零的需求描述。同时选择StarCoder作为生成LLM。在每次迭代中,作者让StarCoder通过HuggingFace API生成一个温度为一的十个测试输入。

少量提示。针对每个目标编译器的需求摘要和初始测试生成少量提示(few-shot prompt),为了最小化构建提示的付出的人力劳动,并适应LLM的上下文大小,作者选择了一次性提示(one-shot prompt)。为此,作者从每个目标编译器中选择一个优化。随后,作者手动编写需求描述和一个能够触发优化的演示输入测试,将其作为需求摘要和测试生成的提示中的一次性提示。作者为每种类型分别设计两个提示符,并根据每种优化类型选择相应的提示符。对于反馈提示,需求由分析LLM生成,样本测试由生成LLM创建。总体上,为每个目标编译器组装提示所需的工作量仍然是可接受的。

5 实验评估

5.1 研究问题

作者在实验中调查以下研究问题:

RQ1:WhiteFox与现有的编译器模糊器相比如何?RQ2:如何确定WhiteFox的关键组成部分的有效性?RQ3:WhiteFox能否检测到现实世界中的错误?

5.2 实验配置

作者选用了四个待测系统进行评测,包括三个新兴的深度学习编译器PyTorch Inductor、TensorFlow Lite 和 TensorFlow-XLA和常用的 C/C++ 编译器 LLVM。这三个深度学习编译器分别属于 PyTorch 和 TensorFlow 这两个最广泛使用的深度学习框架。表 1 概述了被测试的编译器。

表1:待测系统的信息

基线。作者将WhiteFox与最先进的DL库模糊器进行比较,包括基于LLM的TitanFuzz和基于符号规则的NNSmith。对于LLVM,的基线是YARPGen和GrayC,YARPGen用于生成C/C++测试输入,并具有触发编译器中不同优化的策略。GrayC使用覆盖反馈生成C中测试程序的最先进的灰盒模糊器。每个工具都以其默认设置运行。

作者在消融研究中评估了WhiteFox的多个变体。考虑到PyTorch Inductor拥有最多的优化,消融研究专门针对PyTorch Inductor进行。需求生成有四种变体:WF-Mix(WhiteFox的默认设置)、WF-NL、WF-Code和WF-Impl。对于每个变体,作者利用生成LLM生成100个测试输入,引导不同格式的需求,作者默认设置WF-Mix的需求格式是由分析LLM生成的自然语言和伪代码混合格式。WF-NL(WF-Code)中使用的需求是从混合格式中提取的自然语言(伪代码)描述。此外,作者还评估了直接向生成LLM提供实现源代码的性能,即WF-Impl变体。关于反馈循环,除默认配置外,该配置使用了Thompson Sampling的反馈,还考虑了两种替代变体:一种没有任何反馈(WF-No-Feedback),另一种是加入了反馈但采用随机选择的变体(WF-Rand)。作者进一步探讨了使用GPT4作为分析LLM的决定,引入了另一个额外的变体WF-Starcoder。这个变体使用StarCoder作为分析LLM,从实现源代码中生成需求描述。

实验默认为每个优化生成1000个测试,进行100次迭代。在每次迭代中,WhiteFox根据上一次迭代的触发反馈生成测试,默认批处理大小为10。如果优化在先前迭代中具有触发输入,WhiteFox会采用Thompson Sampling随机选择三个触发输入作为下一次迭代中提示的示例。否则,WhiteFox将在本次迭代中重新使用默认的少量提示来为此优化生成测试。

5.3 实验结果

表2呈现了WhiteFox在四个目标编译器上与基线测试结果的区别。为了与执行时间较短的 NNSmith 进行公平比较,作者还提供了 WhiteFox-Mini 的结果。该变体为每个优化生成100个测试。

表2:WhiteFox与基线进行比较

在优化触发方面,在 PyTorch Inductor、TensorFlow Lite 和 LLVM 中,WhiteFox 的表现明显优于基准测试。总体而言,在测试的编译器中,WhiteFox 在触发优化数量上比现有的测试工具高8.2倍。例如,在 PyTorch Inductor 中的61项优化中,WhiteFox 能够触发41项优化,而基准方法最多只能触发5项优化,这是 WhiteFox 能够触发的优化的一个子集。就时间成本而言,WhiteFox 的时间消耗比除了 NNSmith 外的所有其他技术都要少。与此同时,考虑到 NNSmith 的性能较差,WhiteFox-Mini 仍然能够在较短的时间内触发更多的优化比 NNSmith。

综上,除了 TensorFlow-XLA,WhiteFox 在所有编译器上的表现均优于基准测试。。

5.3.1 需求生成与测试生成

作者首先研究需求描述的有效性以及不同格式选择的效果,如表3所示。需求生成的目标是帮助生成LLM生成更多可以触发编译器中额外优化的测试。因此主要比较点是触发的优化数量和可以触发该优化的测试数量。

与直接将实现源代码与生成LLM进行连接的 WF-Impl 相比,所有使用需求的三个变体(WF-Mix、WF-NL 和 WF-Code)在生成触发测试方面都表现出优异的性能。值得注意的是,默认设置 WF-Mix 能够生成比直接使用实现代码多1.74倍的触发测试。此外,WF-Mix 还可以比 WF-Impl 触发7个以上的优化,突显了使用需求描述的重要性。

如表3所示,WF-Mix 在触发优化和触发测试数量方面表现最佳,强调了将自然语言和伪代码结合起来进行需求描述的有效性。同时,虽然 WF-NL 比 WF-Code 触发了更多的优化,但导致触发测试更少。这是因为自然语言通常包含比伪代码更多的信息,确保在从实现源代码转换时不会错过重要的触发要求。相反,对于生成LLM来说,将伪代码格式化的需求与相应的测试程序相关联更加直观,从而导致更多的触发测试。当使用 StarCoder 生成的需求描述时,WF-Starcoder 不仅导致触发的优化数量较少,触发的测试数量也减少了。

表3:需求描述格式对PyTorch Inductor的影响

5.3.2 反馈循环

作者检查了反馈循环和汤普森抽样算法的有效性。反馈循环的主要目的是增加生成激活已触发优化的额外测试输入的可能性。因此,在这个消融研究中,研究重点是触发测试的数量。图6详细说明了在消融研究中探索的各种变体范围内每个触发的优化的触发测试数量。WhiteFox 和 WF-Rand,结合了反馈循环的变体,产生的触发测试数量显著多于没有反馈循环的变体(WF-No-Feedback),分别增加了 2.6 倍和 2.1 倍。这种提升的性能不仅强调了反馈循环的有效性,还表明它的指导更加适合触发目标优化,而不是依赖于其他不同优化的少样本示例。在默认配置中,WhiteFox 利用汤普森抽样算法来选择触发示例,并且比依赖于随机测试选择的 WF-Rand 实现了显著的 1.3 倍触发测试。如图6所示,WhiteFox 在触发的 41 个优化中的 32 个优化中,在触发测试数量方面表现优于其他方法。总的来说,实验结果显示了基于MAB的触发示例选择的有效性。

图6:反馈循环和汤普森采样对PyTorch Inductor的影响。

5.3.3 bug寻找能力

表4:WhiteFox检测到的bug数量

表4展示了WhiteFox在被测试的编译器(即PyTorch、TensorFlow Lite、TensorFlow-XLA和LLVM)上的Bug发现结果。截至目前,WhiteFox已经检测到96个Bug。其中,80个Bug被确认为先前未知的Bug。在65个PyTorch Inductor的Bug中,有5个被标记为高优先级。作者对 PyTorch Inductor 的 Bug 进行了全面分析,因为它是 WhiteFox 找到的 Bug 的主要来源(65/96,占比 67.7%),并且其中大多数已经被修复(51/65,占比 78.5%)。在这 65 个 Bug 中,只有 11 个(16.9%)可以被最先进的 DL 编译器模糊器覆盖,其中有 10 个可以被 TitanFuzz 检测到,而 3 个可以被 NNSmith 检测到。

转述:李顺

0 阅读:0

互联不一般哥

简介:感谢大家的关注