SRI-CSL/gllvm

GitHub: SRI-CSL/gllvm

一个用 Go 语言编写的全程序 LLVM bitcode 构建工具,能从未修改的 C/C++ 源码包中提取完整的 bitcode 文件。

Stars: 337 | Forks: 39

# Go 语言的 Whole Program LLVM [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Build Status](https://travis-ci.org/SRI-CSL/gllvm.svg?branch=master)](https://travis-ci.org/SRI-CSL/gllvm) [![Go Report Card](https://goreportcard.com/badge/github.com/SRI-CSL/gllvm)](https://goreportcard.com/report/github.com/SRI-CSL/gllvm) **TL; DR:** [wllvm](https://github.com/SRI-CSL/whole-program-llvm) 的直接替代品,可以并行构建 bitcode,速度更快。通过构建 [Linux kernel](https://github.com/SRI-CSL/gllvm/tree/master/examples/linux-kernel) 可以看出这两个工具之间的对比。 ## 快速入门对比表 | wllvm 命令/环境变量 | gllvm 命令/环境变量 | |-----------------------------|-----------------------------| | wllvm | gclang | | wllvm++ | gclang++ | | wfortran | gflang | | extract-bc | get-bc | | wllvm-sanity-checker | gsanity-check | | LLVM_COMPILER_PATH | LLVM_COMPILER_PATH | | LLVM_CC_NAME ... | LLVM_CC_NAME ... | | | LLVM_F_NAME | | WLLVM_CONFIGURE_ONLY | WLLVM_CONFIGURE_ONLY | | WLLVM_OUTPUT_LEVEL | WLLVM_OUTPUT_LEVEL | | WLLVM_OUTPUT_FILE | WLLVM_OUTPUT_FILE | | LLVM_COMPILER | *不支持* (仅限 clang) | | LLVM_GCC_PREFIX | *不支持* (仅限 clang) | | LLVM_DRAGONEGG_PLUGIN | *不支持* (仅限 clang) | | LLVM_LINK_FLAGS | LLVM_LINK_FLAGS | 本项目 `gllvm` 提供了用于从未经修改的 C 或 C++ 源码包构建全程序(或全库)LLVM bitcode 文件的工具。它目前运行在 `*nix` 平台上,例如 Linux、FreeBSD 和 Mac OS X。它是 [wllvm](https://github.com/SRI-CSL/whole-program-llvm) 的 Go 语言移植版。 `gllvm` 提供的编译器包装器分两个阶段工作。包装器首先像往常一样调用编译器。然后,对于每个目标文件,它们调用 bitcode 编译器生成 LLVM bitcode。随后,包装器将生成的 bitcode 文件的位置存储在目标文件的专用段中。当目标文件链接在一起时,专用段的内容会被连接起来(这样我们就不会丢失任何组成部分 bitcode 文件的位置)。构建完成后,可以使用 `gllvm` 工具读取专用段的内容,并将所有 bitcode 链接成一个单一的全程序 bitcode 文件。该工具适用于可执行文件和原生库。 更多详情请参阅 [wllvm](https://github.com/SRI-CSL/whole-program-llvm)。 ## 前置条件 安装 `gllvm` 需要Go 语言 [工具](https://golang.org/doc/install)。 使用 `gllvm` 需要 clang/clang++/flang 以及 llvm 工具 llvm-link 和 llvm-ar。 `gllvm` 与实际的 llvm 版本无关。`gllvm` 还依赖于标准的构建工具,如 `objcopy` 和 `ld`。 ## 安装 只需执行(务必包含那些 `...`) ``` go install github.com/SRI-CSL/gllvm/cmd/...@latest ``` 这应该会在 `$GOPATH/bin` 目录下安装六个二进制文件:`gclang`, `gclang++`, `gflang`, `get-bc`, `gparse`, 和 `gsanity-check` ## 使用说明 `gclang` 和 `gclang++` 是用于编译 C 和 C++ 的包装器。 `gflang` 是用于编译 Fortran 的包装器。 `get-bc` 用于从构建产物(可以是目标文件、可执行文件、库或归档文件)中提取 bitcode。`gsanity-check` 可用于检测配置错误。`gparse` 可用于检查 `gllvm` 如何解析编译器/链接器命令行。 这是一个简单的例子。假设 clang 在你的 `PATH` 中,你可以按如下方式为 `pkg-config` 构建 bitcode: ``` tar xf pkg-config-0.26.tar.gz cd pkg-config-0.26 CC=gclang ./configure make ``` 这应该会生成可执行文件 `pkg-config`。要提取 bitcode: ``` get-bc pkg-config ``` 这将生成 bitcode 模块 `pkg-config.bc`。关于此示例的更多信息,请参阅[这里](https://github.com/SRI-CSL/gllvm/tree/master/examples/pkg-config)。 ## 高级配置 如果 clang 和 llvm 工具不在你的 `PATH` 中,你需要设置一些环境变量。 * `LLVM_COMPILER_PATH` 可以设置为包含编译器和其他将要使用的 LLVM 工具的目录的绝对路径。 * `LLVM_CC_NAME` 可以在你的 clang 编译器不叫 `clang` 而是叫 `clang-3.7` 之类的时候设置。类似地,`LLVM_CXX_NAME` 和 `LLVM_F_NAME` 可分别用于描述 C++ 和 Fortran 编译器的名称。我们也会以类似的方式关注环境变量 `LLVM_LINK_NAME` 和 `LLVM_AR_NAME`。 另一个有用且有时必要的环境变量是 `WLLVM_CONFIGURE_ONLY`。 * `WLLVM_CONFIGURE_ONLY` 可以设置为任何值。如果设置了它,`gclang` 和 `gclang++` 的行为就像普通的 C 或 C++ 编译器。它们不会生成 bitcode。设置 `WLLVM_CONFIGURE_ONLY` 可以防止因意外生成隐藏 bitcode 文件而导致的配置错误。在配置构建时有时需要这样做。 例如: WLLVM_CONFIGURE_ONLY=1 CC=gclang ./configure make ## 提取 Bitcode `get-bc` 工具用于从构建产物(如可执行文件、目标文件、瘦归档、归档或库)中提取 bitcode。在最简单的用例中,如上所示,只需执行: ``` get-bc -o ``` 这将生成所需的 bitcode 文件。对于目标文件,情况类似。 对于归档或库,可以选择生成 bitcode 模块还是 bitcode 归档。此选择通过使用 `-b` 开关做出。 另一个有用的开关是 `-m` 开关,除了生成 bitcode 外,它还会生成构成最终产品的 bitcode 文件的清单。通常 ``` get-bc -h ``` 将列出所有命令行开关。因为我们使用 `golang` 的 `flag` 模块,所以开关必须位于构建产物路径之前。 ## 在存储中保留 bitcode 文件 有时,由于病态的构建系统,保留构建中生成的 bitcode 文件会很有用, either to prevent deletion or to retrieve it later. 如果环境变量 `WLLVM_BC_STORE` 设置为现有目录的绝对路径,那么 WLLVM 会将生成的 bitcode 文件复制到该目录中。 复制的 bitcode 文件的名称是原始 bitcode 文件路径的哈希值。为了方便,当同时使用 `get-bc` 的清单功能和存储时,清单将包含原始路径和存储路径。 ## 调试 gllvm 工具可以显示不同级别的输出以辅助调试。 要显示此输出,请将 `WLLVM_OUTPUT_LEVEL` 环境变量设置为以下级别之一: * `ERROR` * `WARNING` * `AUDIT` * `INFO` * `DEBUG` 例如: ``` export WLLVM_OUTPUT_LEVEL=DEBUG ``` 输出将定向到标准错误流,除非你通过 `WLLVM_OUTPUT_FILE` 环境变量指定日志文件的路径。 `AUDIT` 级别(2022 年新增)仅记录对编译器的调用,并指出每次调用是 *编译* 还是 *链接*、使用的编译器以及提供的参数。 例如: ``` export WLLVM_OUTPUT_FILE=/tmp/gllvm.log ``` ## 拒绝 Dragons `gllvm` 不支持 dragonegg 插件。 ## 健全性检查 环境变量太多?尝试做一个健全性检查: ``` gsanity-check ``` 它可能会指出问题所在。 ## 底层原理 `wllvm` 和 `gllvm` 工具集做的事情基本相同,但方式略有不同。`gllvm` 工具集的代码库是用 `golang` 编写的,主要源于 `wllvm` 的 python 代码库。 两者都使用编译器生成目标文件和 bitcode 文件。`wllvm` 可以使用 `gcc` 和 `dragonegg`,`gllvm` 只能使用 `clang`。`gllvm` 工具集并行执行这两项任务,而 `wllvm` 按顺序执行。这加上 python 的 `fork exec` 缓慢及其解释性质,解释了两个工具集之间巨大的效率差距。 两者都将 `.o` 文件的 bitcode 版本的路径注入到 `.o` 文件本身的专用段中。该段在工具集之间是相同的,因此可以通过任一工具集中的适当工具提取 bitcode。在 `*nix` 上,两个工具集都使用 `objcopy` 添加段,而在 OS X 上它们使用 `ld`。 当目标文件链接到生成的库或可执行文件中时,bitcode 路径段被附加,因此生成的二进制文件包含构成该二进制文件的所有 bitcode 文件的路径。为了提取这些段,`gllvm` 工具集使用 golang 包 `"debug/elf"` 和 `"debug/macho"`,而 `wllvm` 工具集在 `*nix` 上使用 `objdump`,在 OS X 上使用 `otool`。 然后,这两个工具都使用 `llvm-link` 或 `llvm-ar` 将 bitcode 文件组合成所需的形式。 ## 底层定制 你可以通过设置 `GLLVM_OBJCOPY` 和 `GLLVM_LD` 环境变量来指定 `gllvm` 用于操作构建产物的 `objcopy` 和 `ld` 的确切版本。关于 `gllvm` 底层更多的细节,请尝试 ``` gsanity-check -e ``` ## 自定义 BitCode 生成(例如 LTO) 在某些情况下,可能希望在生成 bitcode 的步骤中将某些标志传递给 `clang`。这可以通过将 `LLVM_BITCODE_GENERATION_FLAGS` 环境变量设置为所需的标志来实现,例如 `"-flto -fwhole-program-vtables"`。 在其他情况下,可能希望在将多个单独的 bitcode 文件合并在一起的步骤中(即在 `get-bc` 内部)将某些标志传递给 `llvm-link`。这可以通过将 `LLVM_LINK_FLAGS` 环境变量设置为所需的标志来实现,例如 `"-internalize -only-needed"`。 ## 当心链接时优化。 如果你正在构建的包恰好利用了最近的 `clang` 发展,例如*链接时优化*(通过编译器标志 `-flto` 的存在来指示),那么你的构建不太可能生成 `get-bc` 能处理的任何东西。这是预料之中的。在这些标志下工作时,编译器实际上生成的是 bitcode 形式的目标文件,你这里唯一的补救措施是尝试保存这些目标文件,并自己检索它们。 这可以通过将 `LTO_LINKING_FLAGS` 设置为类似 `"-g -Wl,-plugin-opt=save-temps"` 的内容来完成,这些内容将在链接时附加到标志中。 这至少会保留 bitcode 文件,即使 `get-bc` 无法为你检索它们。 ## 交叉编译说明 交叉编译项目时(即你将 `--target=` 或 `-target` 标志传递给编译器),你需要将 `GLLVM_OBJCOPY` 变量设置为 * `llvm-objcopy` 以使用 LLVM 的 objcopy,它自然支持 clang 支持的所有目标。 * `YOUR-TARGET-TRIPLE-objcopy` 以使用 GNU 的 objcopy,因为 `objcopy` 仅支持原生架构。 示例: ``` # 测试程序 echo 'int main() { return 0; }' > a.c clang --target=aarch64-linux-gnu a.c # works gclang --target=aarch64-linux-gnu a.c # breaks GLLVM_OBJCOPY=llvm-objcopy gclang --target=aarch64-linux-gnu a.c # works GLLVM_OBJCOPY=aarch64-linux-gnu-objcopy gclang --target=aarch64-linux-gnu a.c # works if you have GNU's arm64 toolchain ``` ## 开发者工具 调试通常归结为查看日志,也许添加一两个打印语句。 还有一个上面未提到的额外可执行文件,称为 `gparse`,它会随 `gclang`、`gclang++`、`gflang`、`get-bc` 和 `gsanity-check` 一起安装。`gparse` 接受编译器的命令行参数,并输出它是如何解析它们的。这有时会很有帮助。 ## 许可证 `gllvm` 在 BSD 许可证下发布。有关[详情](LICENSE)请参阅文件 `LICENSE`。 本材料基于国家科学基金会支持的[ACI-1440800](http://www.nsf.gov/awardsearch/showAward?AWD_ID=1440800)拨款工作。本材料中表达的任何观点、发现、结论或建议均为作者个人观点,不一定反映国家科学基金会的观点。
标签:clang, EVTX分析, gllvm, Go语言, Linux内核, LLVM, SOC Prime, TLS抓取, wllvm, 云安全监控, 云资产清单, 全程序分析, 可配置连接, 并行编译, 开发工具, 日志审计, 比特码提取, 程序分析, 程序破解, 编译器, 软件安全, 逆向工程, 静态分析