bitcode

苹果在WWDC 2015大会上引入了bitcode,随后在Xcode7中添加了在二进制中嵌入bitcode(Enable Bitcode)的功能,并且默认设置为开启状态。

什么是bitcode

研究bitcode之前需要先了解一下LLVM,因为bitcode是由LLVM引入的一种中间代码(Intermediate Representation,简称IR),它是源代码被编译为二进制机器码过程中的中间表示形态,它既不是源代码,也不是机器码。从代码组织结构上看它比较接近机器码,但是在函数和指令层面使用了很多高级语言的特性。

LLVM是一套优秀的编译器框架,目前NDK/Xcode均采用LLVM作为默认的编译器。LLVM的编译过程可以简单分为3个部分:

  1. 前端(Frontend),负责把各种类型的源代码编译为中间表示,也就是bitcode,在LLVM体系内,不同的语言有不同的编译器前端,最常见的如clang负责c/c++/oc的编译,flang负责fortran的编译,swiftc负责swift的编译等等
  2. 优化(Optimizer),负责对bitcode进行各种类型的优化,将bitcode代码进行一些逻辑等价的转换,使得代码的执行效率更高,体积更小,比如DeadStrip/SimplifyCFG
  3. 后端(Backend),也叫CodeGenerator,负责把优化后的bitcode编译为指定目标架构的机器码,比如X86Backend负责把bitcode编译为x86指令集的机器码

LLVM的前端可以理解为C/C++/OC/Swift等编程语言,LLVM的后端可以理解为各个芯片平台上的汇编指令或者可执行机器指令数据,那么,BitCode就是位于这两者直接的中间码. LLVM的编译工作原理是前端负责把项目程序源代码翻译成Bitcode中间码,然后再根据不同目标机器芯片平台转换为相应的汇编指令以及翻译为机器码.这样设计就可以让LLVM成为了一个编译器架构,可以轻而易举的在LLVM架构之上发明新的语言(前端),以及在LLVM架构下面支持新的CPU(后端)指令输出,虽然Bitcode仅仅只是一个中间码不能在任何平台上运行,但是它可以转化为任何被支持的CPU架构,包括现在还没被发明的CPU架构,也就是说现在打开Bitcode功能提交一个App到应用商店,以后如果苹果新出了一款手机并CPU也是全新设计的,在苹果后台服务器一样可以从这个App的Bitcode开始编译转化为新CPU上的可执行程序,可供新手机用户下载运行这个App.

苹果的要求

上传到服务器的bitcode给苹果带来更好处是: 以后新设计了新指令集的新CPU,可以继续从这份bitcode开始编译出新CPU上执行的可执行文件,以供用户下载安装.
但是bitcode给开发者带来的不便之处就是: 没用bitcode之前,当应用程序崩溃后,开发者可以根据获取的的崩溃日志再配上上传到苹果服务器的二进制文件的调试符号表信息可以还原程序运行过程到崩溃溃时后调用栈信息,对问题进行定位排查.但是用了bitcode之后,用户安装的二进制不是开发者这边生成的,而是苹果服务器经过优化后生成的,其对应的调试符号信息丢失了,也就无法进行前面说的还原崩溃现场找原因了.

环境及平台处理

  • 我们知道生成的静态库可能用于Debug模式,也可能用于Release模式;可能是在模拟器上面运行,也可能是在真机上运行,既然有这么多可变因素,那么我们就需要一一解决。
  • 我们首先将不同的开发模式和不同的运行环境下生成对应的库文件(共4种):
    • 模拟器 + Debug
    • 模拟器 + release:
    • 真机 + Debug
    • 真机 + release

有一个注意点,需要将构建建构设置为NO,否则只支持选中的设备架构:

使用lipo -info命令查看静态库支持的CPU架构,如

  • 以看出使用真机编译生成的静态库是支持armv7 arm64架构的,使用模拟器编译生成的静态库是支持i386 x86_64架构的(armv7是兼容armv7s的)。
  • 关于设备的CPU架构(指令集)
    • 模拟器:
      • i386 : 32位架构 4S ~ 5
      • x86_64 : 64位架构 5S ~ 现在的机型
    • 真机(iOS设备):
      • armv7 : 32位架构 3GS ~ 4S
      • armv7s: 特殊的架构 5 ~ 5C (此架构有问题, 有的程序变得更快, 有的程序变得更慢)
      • amr64 : 64位架构 5S ~ 现在的机型

静态库合并

  • 将静态库合并也比较简单,只需要需要支持的架构的静态库合并即可,一般我们主要合并模拟器Release下的静态库和真机Release下的静态库,保证在模拟器和真机下都可以调试并且可以发布应用。合成指令如下:
1
lipo -create 静态库1.a 静态库2.a -output 新静态库.a
  • 合成完成会生成一个新的.a静态库,将该.a静态库和头文件一起提供给调用者即可。

编译打包项目

通常,在build时,compile的参数是-fembed-bitcode-marker,它的意思只是标记二进制文件中有bitcode,但是实际上没有内容,一般用于测试。

在jenkins上打包时,先archive,然后再导出ipa包,而archive的编译参数是-fembed-bitcode,所以与本地调试不太一样。

那么,这两个参数有什么区别呢?

StackOverFlow上的回答说的比较清楚。

• -fembed-bitcode-marker simply marks where the bitcode would be in the binary after an archive build.
• -fembed-bitcode actually does the full bitcode generation and embedding, so this is what you need to use for building static libraries.
• Xcode itself builds with -fembed-bitcode-marker for regular builds (like deploy to simulator)
• Xcode only builds with -fembed-bitcode for archive builds / production builds (as this is only needed for Apple).

-fembed-bitcode-marker会生成一个最小bitcode的section,但是没有任何内容,size=1。

-fembed-bitcode则会生成bitcode的相关内容。

You should be aware that a normal build with the -fembed-bitcode-marker option will produce minimal size embedded bitcode sections without any real content. This is done as a way of testing the bitcode-related aspects of your build without slowing down the build process. The actual bitcode content is included when you do an Archive build.

所以我们在打包lib的时候,需要设置参数-fembed-bitcode,这样才会包含bitcode的内容。

检查生成的静态库文件是否支持Bitcode,请使用如下指令:

1
2
3
otool -arch armv7 -l libBLECrypto.a 
或者
otool -arch arm64 -l libBLECrypto.a

搜索bitcode字段,如果存在sectname为bitcode的字段且对应的size不是0x0000000000000001,说明静态库支持Bitcode

因此,解决方案有两种:

  • 禁用bitcode
  • 设置参数-fembed-bitcode

如果项目中导入了.a静态库且上架需要开启Bitcode选项,那么静态库就需要支持Bitcode才可以,否则打包上架会遇到如下问题:

1
ld: bitcode bundle could not be generated because 'xxx.a(netcore.o)' was built without full bitcode. All object files and libraries for bitcode must be generated from Xcode Archive or Install build for architecture armv7

Cocoapod默认生成的库是打开bitcode的,如果自己封装的库中,含有 .a等静态库文件,如果静态库文件不支持 bitcode则会提示上面错误,所以解决方法需要在项目 podfile中关闭pod库的 bitcode

1
2
3
4
5
6
7
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end

另外可以试下以下方法标识.a来打开bitcode,没试过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#bitcode enable
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'YES'

if config.name == 'Release'
config.build_settings['BITCODE_GENERATION_MODE'] = 'bitcode'
else
config.build_settings['BITCODE_GENERATION_MODE'] = 'marker'
end

cflags = config.build_settings['OTHER_CFLAGS'] || ['$(inherited)']

if config.name == 'Release'
cflags << '-fembed-bitcode'
else
cflags << '-fembed-bitcode-marker'
end

config.build_settings['OTHER_CFLAGS'] = cflags
end
end
end

Reference

1.使cocoapods打开bitcode

2.iOS中动/静态库支持bitcode的问题

3.iOS开发之关于静态库制作及注意点

Author

Sylar

Posted on

2020-02-16

Updated on

2020-09-21

Licensed under

Comments