Ruby 与 clang

2021-01-04 10:10:27 浏览数 (1)

笔者在使用 `rbenv`[1] 安装 ruby 时,遇到一个头文件缺失导致无法编译失败的问题。

本文会记录笔者对该问题产生的原因分析,并通过分析 clang 源码的方式提供一个通用的解决方案。

通过该方案,可以解决所有类似的头文件缺失问题。

rbenv 安装 ruby 失败

笔者是执行 rbenv install 2.7.2 命令时遇到了问题。

控制台输出如下:

image-20201214210046129

根据添加 --verbose 参数,我们可以得到更加详细的错误信息:

代码语言:javascript复制
clang  -I. -Iinclude -fPIC -arch x86_64 -O3 -Wall -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DOPENSSLDIR=""/Users/kukudeaidian/.rbenv/versions/2.7.2/openssl/ssl"" -DENGINESDIR=""/Users/kukudeaidian/.rbenv/versions/2.7.2/openssl/lib/engines-1.1"" -D_REENTRANT -DZLIB -DZLIB_SHARED -DNDEBUG -I/Users/kukudeaidian/.rbenv/versions/2.7.2/include  -MMD -MF apps/app_rand.d.tmp -MT apps/app_rand.o -c -o apps/app_rand.o apps/app_rand.c
In file included from apps/app_rand.c:10:
In file included from apps/apps.h:13:
In file included from ./e_os.h:16:
In file included from include/openssl/e_os2.h:243:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/include/inttypes.h:21:15: fatal error: 'inttypes.h' file not found
#include_next <inttypes.h>
              ^~~~~~~~~~~~
1 error generated.
make[1]: *** [apps/app_rand.o] Error 1
make: *** [all] Error 2

从上面的日志中,我们可以发现三个关键点:

  • rbenv 最终调用了 clang 执行编译任务
  • clang 执行编译任务时,无法找到系统库头文件 <inttypes.h>
  • clang 命令缺失 -isysroot 参数

系统库文件查找路径

通常情况下,我们可以添加参数 -isysroot <dir> 的方式解决上面的报错:

代码语言:javascript复制
clang -c -o apps/app_rand.o apps/app_rand.c -I. -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

但是,本次是通过 rbenv 命令执行 ruby 的安装任务,所以上面的解决方案行不通。

Clang driver

为了查找变通方案,我们需要先了解一下 `Clang driver`[2]

在 llvm 编译器高级用法:第三方库插桩中,我们曾经提到过 clang 会按照以下顺序执行。

image-20201215013642049

而负责组装每个任务的就是 clang Driver

clang Driver 的处理逻辑分为以下几步:

  • Parse: Option Parsing:解析传入的参数
  • Pipeline: Compilation Action Construction:根据每个输入的文件和类型,组建 action(比如 PreprocessJobAction
    • 通过 -ccc-print-phases 可以查看 action
代码语言:javascript复制
  clang -ccc-print-phases -c t0.c t1.c
               - 0: input, "t0.c", c
            - 1: preprocessor, {0}, cpp-output
         - 2: compiler, {1}, ir
      - 3: backend, {2}, assembler
   - 4: assembler, {3}, object
  5: bind-arch, "x86_64", {4}, object
               - 6: input, "t1.c", c
            - 7: preprocessor, {6}, cpp-output
         - 8: compiler, {7}, ir
      - 9: backend, {8}, assembler
   - 10: assembler, {9}, object
  11: bind-arch, "x86_64", {10}, object

Bind: Tool & Filename Selection:根据 action 选择对应的工具文件名信息

  • 通过 -ccc-print-bindings 可以查看对应的工具文件名信息 clang -ccc-print-bindings -c t0.c t1.c -arch arm64 -arch armv7 # "aarch64-apple-darwin19.6.0" - "clang", inputs: ["t0.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-9a2aac.o" # "arm-apple-darwin19.6.0" - "clang", inputs: ["t0.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-7a4059.o" # "arm-apple-darwin19.6.0" - "darwin::Lipo", inputs: ["/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-9a2aac.o", "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t0-7a4059.o"], output: "t0.o" # "aarch64-apple-darwin19.6.0" - "clang", inputs: ["t1.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-950abb.o" # "arm-apple-darwin19.6.0" - "clang", inputs: ["t1.c"], output: "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-044386.o" # "arm-apple-darwin19.6.0" - "darwin::Lipo", inputs: ["/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-950abb.o", "/var/folders/4j/jqzrrjzn0nvgm4pyxrqddxnmm530jm/T/t1-044386.o"], output: "t1.o"

Translate: Tool Specific Argument Translation:根据输入的参数转为不同tool 的参数

原始参数:

代码语言:javascript复制
 xcrun --sdk iphoneos clang -target arm64-apple-ios8.0  main.m -v

各个 tool 的参数:

代码语言:javascript复制
clang -cc1 -triple arm64-apple-ios8.0.0 -o main-a28fc8.o -x objective-c main.m
ld -arch arm64 -platform_version ios 8.0.0  -o a.out main-a28fc8.o
  • 比如,插桩参数-fsanitize-coverage=trace-pc-guard 会变为 -fsanitize-coverage-type=3 -fsanitize-coverage-trace-pc-guard 参数

Execute:真正的执行不同的工具

为了方便理解,我们可以将下面的图片和上面的流程对应:

DriverArchitecture

第一版方案:通过环境变量控制头文件搜索路径

因为 macDarwin tool chain 对应,所以我们需要重点关注 Darwin tool chain 相关的逻辑。

通过查看 clang::driver::toolchains::Darwin 相关的代码,我们会发现下面的执行逻辑:

Compilation 调用 clang::driver::toolchains::DarwinTranslateArgs函数时,会在命令行的 -isysroot 参数缺失的情况下,通过环境变量 SDKROOT 获取。

image-20201214222425629

image-20201215013626760

如下图,通过 export 命令设置环境变量后,clang 命令可以正常执行

代码语言:javascript复制
export SDKROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

image-20201215010258221

xcrun

考虑到不同的系统会对应不同的 SYSROOT,每次都通过 export 方式设置环境变量比较繁琐,我们需要换一种更简单的配置方式。

根据 xcrun 的帮助文件,我们可以发现,通过 xcrun 命令可以方便的调整不同系统对应的 SDKROOT 路径。

代码语言:javascript复制

       SDKROOT
          Specifies the default SDK to be used when looking up tools (some tools may have SDK specific versions).

          This environment variable is also set by xcrun to be the absolute path to the user provided SDK (either via  SDKROOT  or  the
          --sdk  option),  when  it is used to invoke a normal developer tool (build tools like xcodebuild or make are exempt from this
          behavior).

          For example, if xcrun is used to invoke clang via:
              xcrun --sdk macosx clang test.c

          then xcrun will provide the full path to the macosx SDK in the environment variable SDKROOT. That in turn  will  be  used  by
          clang(1) to automatically select that SDK when compiling the test.c file.

比如,我们可以通过以下代码完成编译:

代码语言:javascript复制
// 编译 iPhone 项目
xcrun -l --sdk iphoneos clang -target arm64-apple-ios8.0  main.m
// 编译 macosx 项目
xcrun -l clang main.m

优化方案:xcrun

根据上面的信息,我们可以尝试使用 xcrun 调用安装命令:

image-20201215012636981

通过截图,我们可以发现 xcrun rbenv install 2.7.2 命令组合可以安装 ruby

总结

通过本文,我们可以得到以下经验:当因为标准库头文件缺失导致编译失败时,可以通过搭配 xcrun 完成编译任务

参考资料

[1]

rbenv: https://github.com/rbenv/rbenv

[2]

Clang driver: https://clang.llvm.org/docs/DriverInternals.html

0 人点赞