llvm学习(九):再启程,llvm-8.0.0+ndkr19的环境搭建

ndkr19默认用的是llvm-8.0.2,而今天才发布的llvm-8.0.0,之前是用7.0.0将就的,今天终于不用将就了,重新搭建了一下环境,对 ndk 使用 llvm 的理解更加深刻。本文介绍一下开发环境的搭建。

一、编译llvm-8.0.0及其附加组件

下载一下,我使用了这些组件,并且放到正确的位置:

先把llvm主工程解压出来:

工作目录是 llvm-8.0.0.src

然后把对应的文件全都放到对应的位置就行,见上面的文本,至于为什么要这样做,可以看对应父目录下的CMakeLists.txt,有个编译选项是:“当存在该目录时,编译该目录(大概这个意思)”

然后喝杯茶,lldb 编译爆炸了,有两个方案,一是舍弃lldb,二是看第二步。

舍弃lldb的配置是

install到我想要的地方

二、Mac上为lldb配置codesign

其实说明文件里是有的,就下面这段,按照描述操作一遍就可以编译通过了,非常稳。

不会的话,网上搜吧,懒得写了,就是自己创建一个证书并且信任它。

ndkllvm的处理

先说一下ndkr19和ndkr18的区别,关于 standard-toolchains 我认为这个是个很大的区别。

https://github.com/android-ndk/ndk/wiki/Changelog-r19

r19直接把这个功能砍了,说使用了更加友好的功能,经过体验,确实非常非常非常非常友好!强烈推荐这个大版本!

从原理上将,是将各个 Android 版本分别包装了一层,例如这个

总之,平时使用时候更加友好,替换工程也更加方便了!

然后我们对比一下 llvm-8.0.0和 ndk-llvm之间的区别。

第一步编译出来的llvm-8.0.0,进行一下 install,目录如下

而ndkr19里llvm的目录是

去除掉无关的东西,主要关注这三个路径 bin、lib、lib64,因为其他基本都不影响。

列一个表格,展示一下

 llvm-8.0.0ndkr19-llvm
binllvm 的 binary除了 llvm 的 binary,还有自己封装的一些脚本,本体在 clang 和 clang++
lib存放llvm的library,有.a和.dylib存放交叉编译相关的一些文件,与llvm无关
lib64不拥有存放llvm的library,只有.dylib

再对比一下二者存放 llvm 库的目录,llvm-8.0.0里既有静态链接库,也有动态链接库,而ndk-llvm里只有动态链接库。而且版本号也不一样,会引起头文件寻找路径不一样,因为寻找路径是硬编码在clang里面的,一个是  lib/clang/8.0.0/,另一个是 lib64/clang/8.0.2/ 。

还有一个细节,ndk-llvm 里是整合在一起的、叫libLLVM.dylib和libclang.dylib,这个与编译配置有关,所以 google 应该是主动设置过 llvm 的编译选项的,于是我准备去寻找google 对 llvm 到底做了什么。

根据clang -v的结果,我们看下仓库的 master 分支

打开 https://android.googlesource.com/toolchain/llvm ,里面没有主动编译它,似乎也没有配置文件,逛了一圈,不小心找到一个这个东西,如图

点进去一看,我去,踏破铁鞋无觅处,也就是这个仓库 https://android.googlesource.com/toolchain/llvm_androd/

里面写着 Android Clang/LLVM Toolchain 和 编译流程,直接一个 python 文件就搞定了

所以,所有的谜题都在 build.py 里解开了,我们仔细阅读一下,就知道ndk对llvm做了什么。

这里简单介绍一下,以 darwin 为例,主要执行逻辑如下:

 

在build_stage1里,编译整个llvm的框架,不包括子项目(似乎包括 clang),里面有各种各样的配置;

在build_stage2里,编译lld、lldb等子项目,里面有各种各样的配置;

在package_toolchain里,将前两步编译出来的东西打包一下,删去无关的东西。

删去无用的二进制文件,并且strip 掉来缩小体积。

之后组织头文件、写 License、设置一些细枝末节的东西。

那么回到最开始的问题,是哪个编译选项控制llvm 的输出是 lib64呢?这里搜两个字符串,分别是 lib64 和 64。前者一般是硬编码拼接字符串的,没有看到主动配置,后者却是是主动的,代码是:

正是因为这个选项,才导致编译出来的 lib 被命名为了 lib64。所有的配置都在这个 python 文件里写着,所以要想复现一个一模一样配置的环境出来,也是很简单的事情。

结论是:llvm-8.0.0 和 ndk-llvm 主要的不一致在于编译选项的不一致,次要的不一致在于可能在于某些细节上 google 做了 patch。

四、让 ndk-llvm 加载本地的 Pass

既然原理都说清楚了,之前暴力替换掉 ndk-llvm 整个工程的方法也可以不用了,当然,这里两条路都可以走,都介绍一下吧。

方案1:使用llvm-8.0.0替换掉ndk-llvm(之前一直在用的老方案)

直接用软连接,连到我们编译好的 llvm-8.0.0 上面即可。

第一步:删掉 android-ndk-r19c/toolchains/llvm/prebuilt/darwin-x86_64/lib64 ,删掉 android-ndk-r19c/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang 和 android-ndk-r19c/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++

第二步:修复lib64、clang、clang++(其实还有一些别的文件例如lld之类的需要替换,但跟我们无关)

主要代码如下:

将 llvm 完全替换掉,自己的 clang 加载自己的 pass,没毛病,相信这种办法大家已经都会了,下面介绍另一种办法,不修改NDK、让 NDK 加载我们的 Pass。

方案2:用相同配置编译一份 llvm 的环境,使用它编译 Pass。(虽然失败了,但发现了比较有意思的内容)

先试试看,既然都是llvm8直接加载,发现有个符号找不到,

这个错误很眼熟,之前用llvm7也是这个报错,于是我开始怀疑并不是版本不一致的问题,开始探索。

有个命令很好用,叫 nm -gU ,用来获取导出函数,这里放个结论:

结论是:我们自己编译的clang和libLLVM.dylib可以找到符号,ndk的clang找不到符号,ndk的libLLVM.dylib可以找到符号。

也就是说,ndk的clang是坏掉的!我这里对比了一下二者的大小,发现差的也太多了吧

这里我做了另一个正确的决定,直接把 clang 文件给换了,看看会发生什么事。经过测试,一次性成功!

那么问题就来了,估计又是哪个编译选项我没有注意到,开始下面的探索,为什么这个 ndk-clang 是坏的。

主要就阅读这个 python 文件,并且编译 llvm 进行测试,第一步就是下载相关的环境,经过多次测试,下面这个是完成build_stage1的集合,其实已经够我们用了,下载一份 master 代码回来。

这些工程放好就行,因为在 llvm 里,google 已经放好了软连接,将各个子项目连起来了。

这个列表主要是根据这段代码抠出来的:

放好之后,python 直接就可以直接跑了,编译过程会比较慢,大概一个小时,不要急。

编译完成之后,我看了一眼输出目录,居然少很多文件:

我们最最最关心的,opt 文件是不存在的!还有一些别的文件也不存在!

对llvm-8.0.0的代码 和 ndk-llvm 的源文件的配置项进行 diff,tools下和tools/opt下都没有什么变化,这时候有两种猜测,一个是在 stage2里再生成opt,一个是配置项里主动关闭了 opt。

经过阅读,暂时可以排除掉stage2里的编译内容,opt 是 llvm 本身的东西,不大会放在第二步执行。于是可以继续锁定到编译选项里,直到我找到了下面这句话

在llvm-8.0.0里,默认是开着的;而在 ndk-llvm里,默认是关着的。从名字上来看,像是针对 tools 下的文件是否进行编译,所以这个参数可以测试一下。

修改 python 里的文件,改为 ON,这时候果然出现了opt,于是我用这个clang 去加载我们的 Pass:

果然,是 LLVM_BUILD_TOOLS  引起的!

这时候遇到了老问题,是 __ZN4llvm23EnableABIBreakingChecksE 还是 __ZN4llvm24DisableABIBreakingChecksE 的问题,之前认为是 Release 版和 Debug 版的区别(详见第四篇文章)。这里我们编译的都是 Release 版,为什么也会出现符号不一致的情况呢?

这里让我想起另一个配置项,我编译 Release 版的时候,是有 -DLLVM_ENABLE_ASSERTIONS=ON,而ndk里这个是关掉的!

另一个细节是,Debug 版默认是 -DLLVM_ENABLE_ASSERTIONS=ON,而 Release 版默认是 OFF 的!

之后搜索代码,发现这个标记由 LLVM_ENABLE_ABI_BREAKING_CHECKS 直接决定。

LLVM_ENABLE_ABI_BREAKING_CHECKS 很可能是与 LLVM_ENABLE_ASSERTIONS 有关的,所以这里再搜一下,找到了下面的内容

破案了,这时候没有任何遗留的问题了。

回归主题!

ndk无法加载Pass的原因是:LLVM_BUILD_TOOLS  默认是 false,在这种情况下,本来就没有 opt 和编译时优化,是永远无法加载 Pass 的!

__ZN4llvm23EnableABIBreakingChecksE 和 __ZN4llvm24DisableABIBreakingChecksE 是由 LLVM_ENABLE_ASSERTIONS 决定的,需要保持一致。

 

五、总结

整个过程编译了十来遍 llvm,花了不少时间操作和思考,加深了对 llvm 整个体系的了解,加深了 ndk 里对 llvm 的配置。可以说学到非常多的东西了。


=============================================================
随着访客的增多,LeadroyaL在本站流量的开支越来越多了,曾经1元能用1个月,现在1元只能用3天。如果觉得本文帮到了你,希望能够为服务器的流量稍微打赏一点,谢谢!

《llvm学习(九):再启程,llvm-8.0.0+ndkr19的环境搭建》有3个想法

  1. 然后大小不一样的话。我记得strip会strip掉10m左右,其他的话确定一下是不是一样的build mode,release跟minsizerelease会差很多。然后如果一个启用了动态编译一个静态链接的话也有可能

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code