如何提交 LLVM 错误报告

简介 - 发现 Bug 了?

如果您在使用 LLVM 时遇到 Bug,我们非常希望了解情况。本文档介绍了您可以采取哪些措施来提高快速修复 Bug 的几率。

🔒 如果您认为该 Bug 与安全相关,请遵循 如何报告安全问题?。🔒

基本上,您至少需要做两件事。首先,确定该 Bug 是 导致编译器崩溃 还是编译器 编译错误(即,编译器成功生成可执行文件,但运行不正确)。根据 Bug 的类型,请按照链接部分中的说明缩小 Bug 范围,以便修复人员能够更容易地找到问题。

一旦您得到了一个简化的测试用例,请访问 LLVM Bug 追踪系统 并填写必要的详细信息(请注意,您不需要选择标签,如果不确定,请跳过)。Bug 描述应包含以下信息

  • 重现问题所需的所有信息。

  • 触发 Bug 的简化测试用例。

  • 您获取 LLVM 的位置(如果不是从我们的 Git 代码库获取)。

感谢您帮助我们改进 LLVM!

崩溃 Bug

通常情况下,编译器中的 Bug 会导致其崩溃,通常是由于某种断言失败。最关键的一步是确定崩溃发生在 Clang 前端还是 LLVM 库(例如优化器或代码生成器)中。

为了确定哪个组件发生了崩溃(前端、中间端优化器或后端代码生成器),请在崩溃发生时使用以下额外的命令行选项运行 clang 命令

  • -emit-llvm -Xclang -disable-llvm-passes:如果使用这些选项(禁用优化器和代码生成器)后 clang 仍然崩溃,则崩溃发生在前端。跳至 前端 Bug

  • -emit-llvm:如果使用此选项(禁用代码生成器)后 clang 崩溃,则您发现了中间端优化器 Bug。跳至 中间端 Bug

  • 否则,您遇到了后端代码生成器崩溃。跳至 代码生成器 Bug

前端 Bug

clang 崩溃时,编译器会转储一个预处理文件和一个重现 clang 命令的脚本。例如,您应该会看到类似以下内容

PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT:
Preprocessed source(s) and associated run script(s) are located at:
clang: note: diagnostic msg: /tmp/foo-xxxxxx.c
clang: note: diagnostic msg: /tmp/foo-xxxxxx.sh

The creduce 工具有助于将预处理文件缩减到仍然可以复制问题的最小代码量。鼓励您使用 creduce 来缩减代码,从而简化开发人员的工作。The clang/utils/creduce-clang-crash.py 脚本可用于 clang 转储的文件,以帮助自动创建用于检查编译器崩溃的测试。

cvisecreduce 的替代方案。

中间端优化 Bug

如果您发现某个 Bug 导致优化器崩溃,请通过传递“-emit-llvm -O1 -Xclang -disable-llvm-passes -c -o foo.bc”将您的测试用例编译为 .bc 文件。The -O1 非常重要,因为 -O0 会将 optnone 函数属性添加到所有函数,并且许多 Pass 不会在 optnone 函数上运行。然后运行

opt -O3 foo.bc -disable-output

如果这没有导致崩溃,请按照 前端 Bug 的说明进行操作。

如果这确实导致了崩溃,那么您应该可以使用以下 bugpoint 命令进行调试

bugpoint foo.bc -O3

运行此命令,然后提交一个 Bug,其中包含 bugpoint 生成的说明和简化的 .bc 文件。

如果 bugpoint 没有重现崩溃,llvm-reduce 是一种简化 LLVM IR 的替代方法。创建一个重现崩溃的脚本并运行

llvm-reduce --test=path/to/script foo.bc

这应该会生成简化的 IR 来重现崩溃。需要注意的是,llvm-reduce 仍然相当不成熟,可能会崩溃。

如果以上方法都不起作用,您可以通过使用 --print-before-all --print-module-scope 标志运行 opt 命令来获取崩溃前的 IR,以在每个 Pass 之前转储 IR。需要注意的是,这会产生非常详细的输出。

后端代码生成器 Bug

如果您发现某个 Bug 导致 clang 在代码生成器中崩溃,请通过将“-emit-llvm -c -o foo.bc”传递给 clang(以及您已经传递的选项)来将源文件编译为 .bc 文件。一旦您拥有 foo.bc,以下命令之一应该会失败

  1. llc foo.bc

  2. llc foo.bc -relocation-model=pic

  3. llc foo.bc -relocation-model=static

如果这些命令都没有导致崩溃,请按照 前端 Bug 的说明进行操作。如果其中一个命令导致了崩溃,您应该可以使用以下 bugpoint 命令行之一进行简化(使用上面导致失败的命令对应的命令行)

  1. bugpoint -run-llc foo.bc

  2. bugpoint -run-llc foo.bc --tool-args -relocation-model=pic

  3. bugpoint -run-llc foo.bc --tool-args -relocation-model=static

请运行此命令,然后提交一个 Bug,其中包含 bugpoint 生成的说明和简化的 .bc 文件。如果 bugpoint 出现问题,请提交“foo.bc”文件和 llc 崩溃的选项。

LTO Bug

如果您遇到在使用 -flto 选项时导致 LLVM LTO 阶段崩溃的 Bug,请按照以下步骤诊断和报告问题

使用以下选项(以及您现有的编译选项)将源文件编译为 .bc(位码)文件

export CFLAGS="-flto -fuse-ld=lld" CXXFLAGS="-flto -fuse-ld=lld" LDFLAGS="-Wl,-plugin-opt=save-temps"

这些选项启用 LTO 并保存编译期间生成的临时文件,以便以后分析。

在 Windows 上,您应该使用 lld-link 作为链接器。请调整您的编译标志如下:* 将 /lldsavetemps 添加到链接器标志中。* 当从编译器驱动程序链接时,添加 /link /lldsavetemps 以便将该标志转发到链接器。

使用指定的标志将生成四个中间字节码文件

  1. a.out.0.0.preopt.bc(在应用任何链接时优化(LTO)之前)

  2. a.out.0.2.internalize.bc(在应用初始优化之后)

  3. a.out.0.4.opt.bc(在进行大量优化之后)

  4. a.out.0.5.precodegen.bc(在 LTO 之后但在转换为机器代码之前)

执行以下命令之一以识别问题根源

  1. opt "-passes=lto<O3>" a.out.0.2.internalize.bc

  2. llc a.out.0.5.precodegen.bc

如果其中一个命令导致了崩溃,您应该可以使用 llvm-reduce 命令行进行简化(使用上面导致失败的命令对应的 bc 文件)

llvm-reduce --test reduce.sh a.out.0.2.internalize.bc

reduce.sh 脚本示例

$ cat reduce.sh
#!/bin/bash -e

path/to/not --crash path/to/opt "-passes=lto<O3>" $1 -o temp.bc  2> err.log
grep -q "It->second == &Insn" err.log

这里我们已经 grep 了失败的断言消息。

请运行此命令,然后提交一个 Bug,其中包含 llvm-reduce 生成的说明和简化的 .bc 文件。

编译错误

如果 clang 成功生成可执行文件,但该可执行文件运行不正确,则可能是代码中的 Bug 或编译器中的 Bug。首先要检查的是确保它没有使用未定义的行为(例如在定义变量之前读取变量)。特别是,检查程序在各种 sanitizers(例如 clang -fsanitize=undefined,address)和 valgrind 下是否干净。我们追查到的许多“LLVM Bug”最终都是被编译程序中的 Bug,而不是 LLVM 本身。

一旦确定程序本身没有 Bug,您应该选择希望使用哪个代码生成器来编译程序(例如 LLC 或 JIT),并可以选择运行一系列 LLVM Pass。例如

bugpoint -run-llc [... optzn passes ...] file-to-test.bc --args -- [program arguments]

bugpoint 会尝试将您的 Pass 列表缩小到导致错误的那个 Pass,并尽可能地简化 bitcode 文件以帮助您。它会打印一条消息,告诉您如何重现结果错误。

The OptBisect 页面显示了一种查找错误优化 Pass 的替代方法。

错误的代码生成

类似于通过行为异常的 Pass 调试错误的编译,您可以使用 bugpoint 调试 LLC 或 JIT 生成的错误代码。在这种情况下,bugpoint 的流程是尝试将代码缩小到一个由其中一种方法错误编译的函数,但由于正确性需要运行整个程序,因此 bugpoint 会使用 C 后端编译它认为不受影响的代码,然后链接它生成的共享对象。

调试 JIT

bugpoint -run-jit -output=[correct output file] [bitcode file]  \
         --tool-args -- [arguments to pass to lli]              \
         --args -- [program arguments]

类似地,要调试 LLC,可以运行

bugpoint -run-llc -output=[correct output file] [bitcode file]  \
         --tool-args -- [arguments to pass to llc]              \
         --args -- [program arguments]

特别说明:如果您正在调试 llvm/test 层次结构中已存在的 MultiSource 或 SPEC 测试,则有一种更简单的调试 JIT、LLC 和 CBE 的方法,即使用预先编写的 Makefile 目标,这些目标将传递 Makefile 中指定的程序选项。

cd llvm/test/../../program
make bugpoint-jit

bugpoint 运行成功结束时,您将获得两个 bitcode 文件:一个 *安全* 文件,可以使用 C 后端编译;以及一个 *测试* 文件,该文件由 LLC 或 JIT 错误代码生成,从而导致错误。

要重现 bugpoint 找到的错误,只需执行以下操作即可

  1. 从安全 bitcode 文件重新生成共享对象。

    llc -march=c safe.bc -o safe.c
    gcc -shared safe.c -o safe.so
    
  2. 如果调试 LLC,则将测试 bitcode 编译为原生代码,并与共享对象链接。

    llc test.bc -o test.s
    gcc test.s safe.so -o test.llc
    ./test.llc [program options]
    
  3. 如果调试 JIT,则加载共享对象并提供测试 bitcode。

    lli -load=safe.so test.bc [program options]