深究Xcode的bitcode設置

深究Xcode的bitcode設置

轉發至:http://www.jianshu.com/p/f42a33f5eb61前端

 

前言

作iOS開發的朋友們都知道,目前最新的Xcode7,新建項目默認就打開了bitcode設置.並且大部分開發者都被這個突如其來的bitcode功能給坑過致使項目編譯失敗,而這些由於bitcode而編譯失敗的的項目都有一個共同點,就是連接了第三方二進制的庫或者框架,而這些框架或者庫剛好沒有包含bitcode的東西(暫且稱爲東西),從而致使項目編譯不成功.因此每當遇到這個狀況時候大部分人都是直接設置Xcode關閉bitcode功能,所有不生成bitcode.也不去深究這一開關背後隱藏的原理.中槍的請點個贊.ios

LLVM是目前蘋果採用的編譯器工具鏈,Bitcode是LLVM編譯器的中間代碼的一種編碼,LLVM的前端能夠理解爲C/C++/OC/Swift等編程語言,LLVM的後端能夠理解爲各個芯片平臺上的彙編指令或者可執行機器指令數據,那麼,BitCode就是位於這二者直接的中間碼. LLVM的編譯工做原理是前端負責把項目程序源代碼翻譯成Bitcode中間碼,而後再根據不一樣目標機器芯片平臺轉換爲相應的彙編指令以及翻譯爲機器碼.這樣設計就可讓LLVM成爲了一個編譯器架構,能夠垂手可得的在LLVM架構之上發明新的語言(前端),以及在LLVM架構下面支持新的CPU(後端)指令輸出,雖然Bitcode僅僅只是一箇中間碼不能在任何平臺上運行,可是它能夠轉化爲任何被支持的CPU架構,包括如今還沒被髮明的CPU架構,也就是說如今打開Bitcode功能提交一個App到應用商店,之後若是蘋果新出了一款手機並CPU也是全新設計的,在蘋果後臺服務器同樣能夠從這個App的Bitcode開始編譯轉化爲新CPU上的可執行程序,可供新手機用戶下載運行這個App.git

歷史回顧

在iPhone出來以前,蘋果主要的編譯器技術是用通過稍微改進的GCC工具鏈來把Objective-C語言編寫的代碼編譯出所指定的機器處理器上原生的可執行程序.編譯器產生的可執行程序叫作"Fat Binaries"--相似於Windows下PE格式的exe和Linux下的ELF格式的二進制,不一樣的是,一個"Fat Binary"能夠包含同一個程序的不少版本,因此同一個可執行文件能夠在不一樣的處理器上運行.主要就是這個技術讓蘋果的硬件很容易的從PowerPC遷移到PowerPC64的處理器,以及後來再遷移到Intel和Intel64處理器.這個方案帶來的負面影響就是同一個文件中存了多份可執行代碼,除了當前機器可執行的那一份以外其餘都是無用的,白佔空間. 這個在市場上被稱爲"Universal Binary",在蘋果從PowerPC遷移到Intel處理器的事情開始存在的(一個二進制文件既包含一份PowerPC版本和一份Intel版本).慢慢的後來又支持同時包含Intel 32bit和Intel 64bit. 在一個Fat binary中,又操做系統運行時根據處理器類型動態選擇正確的二進制版原本運行,可是應用程序要支持不一樣平臺的處理器的話,應用程序自己要多佔用一些空間.固然也有一些瘦身的工具,好比lipo,能夠用來移除fat binary中那些當前機器中不被支持的或者多餘的可執行代碼達到瘦身目的,lipo不會改變程序執行邏輯,僅僅只是文件的大小瘦身.程序員

編譯器現狀

隨着移動設備移動互聯網的深刻發展,如今移動設備中的程序大小變得愈來愈重要了,主要是由於移動設備中不會有電腦上那麼大的一個硬盤驅動器.還有就是蘋果早就從原始的ARM處理器遷移到自家設計的A4,A5,A5X,A6,A7,A8,A8X,A9,A9X以及後續的A10處理器,他們的指令集已經發生了改變和原始ARM設計的有所區別,全部的這些變化都被iOS操做系統底層以及Xcode/LLVM編譯工具向上層程序員必定程度的透明瞭,編譯出來的程序會包含不少執行代碼版本.當面對這個問題後,蘋果投入大量成本遷移到LLVM編譯器架構並使用bitcode的必要性愈來愈大.從最開始的把OPENGL編譯爲特定的GPU指令到把Clang編譯器(LLCM的C/OC編譯前端)支持Objective-C的改進並做爲Xcode的默認編譯器.github

LLVM提供了一個虛擬指令集機制,它能夠翻譯出指定的所支持的處理器架構的執行代碼(機器碼).這個就使得爲iOS應用程序的編譯開發一個徹底基於LLVM架構的工具鏈成爲可能.而LLVM的這個虛擬的通用的指令集能夠用不少種表示格式:編程

  • 叫作IR的文本表示的彙編格式(像彙編語言);
  • 轉換爲二進制數據表示的格式(像目標代碼),這個二進制格式就是咱們所說的bitcode.

Bitcode和傳統的可執行指令集不一樣,他維護的是函數功能的類型和簽名,好比,傳統可執行指令集中,一系列(<=8)的布爾值能夠壓縮存儲到單個字節中,可是在bitcode中他們是各自獨自表示的.此外,邏輯運算操做(好比寄存器清零操做)也由他們對應的邏輯表示方法($R=0);當這些BitCode要轉換爲特定機器平臺的指令集時,他能夠用通過針對特定機器平臺優化過的彙編指令來代替:xor eax, eax.(這個彙編指令一樣是寄存器<eax>清零操做).swift

然而bitcode他也不是徹底獨立於處理器平臺和調用約定的.寄存器的大小在指令集中是一個至關重要的特性,衆所周知,64bit寄存器能夠比32bit寄存器存儲更多的數據,生成64bit平臺的bitcode和32bit平臺的bitcode是明顯不一樣的,還有,調用約定能夠根據函數定義或者函數調用來定義,這些能夠肯定函數的參數傳遞是傳寄存器值呢仍是壓棧. 一些編程語言還有一些像sizeof(long)這樣的預處理指令,這些將在bitcode生成以前前被翻譯.通常狀況下,對於支持fastcc(fast calling convention)調用的64bit平臺會生成與其一致的bitcode代碼.後端

蘋果的要求

到此,讓咱們思考一下,爲何蘋果默認要求watchOS和tvOS的App要上傳bitcode? 由於把bitcode上傳到他本身的中心服務器後,他能夠爲目標安裝App的設備進行優化二進制,減少安裝包的下載大小,固然iOS開發者也能夠上傳多個版本而不是打包到單個包裏,可是這樣會佔用更多的存儲空間. 最重要的是容許蘋果能夠在後臺服務器對應用程序進行簽名,而不用導出任何密鑰到終端開發者那.bash

上傳到服務器的bitcode給蘋果帶來更好處是: 之後新設計了新指令集的新CPU,能夠繼續從這份bitcode開始編譯出新CPU上執行的可執行文件,以供用戶下載安裝.
可是bitcode給開發者帶來的不便之處就是: 沒用bitcode以前,當應用程序奔潰後,開發者能夠根據獲取的的奔潰日誌再配上上傳到蘋果服務器的二進制文件的調試符號表信息能夠還原程序運行過程到奔潰時後調用棧信息,對問題進行定位排查.可是用了bitcode以後,用戶安裝的二進制不是開發者這邊生成的,而是蘋果服務器通過優化後生成的,其對應的調試符號信息丟失了,也就沒法進行前面說的還原奔潰現場找緣由了.服務器

目前,watchOS和tvOS應用發佈必須上傳帶bitcode版本的包.iOS應用發佈對bitcode的要求是可選的,用戶能夠在Xcode的項目設置中關閉. 至關於在編譯的時候加一個標記:embed-bitcode-marker(調試構建) embed-bitcode(打包/真機構建).這個在clang編譯器的參數是-fembed-bitcode,swift編譯器的參數是-embed-bitcode.

實踐出真知

咱們仍是應該實際弄兩個測試代碼進行實踐和檢驗一下比較好.作兩次測試,第一次準備兩個C語言源代碼繼續測試;第二次把其中一個轉變爲彙編語言源代碼後再一個C代碼和一個彙編代碼一塊兒重複以前的測試步驟進行對比校驗差別.

  • 1 . 以下兩個所有是Objective-C代碼:

test.m :

#import <Foundation/Foundation.h> void greeting(void) { NSLog(@"hello world!"); }

demo.m :

#import <Foundation/Foundation.h> void demo(void) { NSLog(@"demo func"); }

用Clang編譯成 ARM64 格式且帶bitcode的目標文件test.o demo.o:

wuqiong:~ apple$ xcrun -sdk iphoneos clang -arch arm64 -fembed-bitcode -c test.m demo.m

而後把兩個目標文件打包爲一個靜態庫文件:

wuqiong:~ apple$ xcrun -sdk iphoneos ar  -r libTest.a test.o demo.o
ar: creating archive libTest.a

用Shell命令otool查看目標文件中是否包含bitcode段:

wuqiong:~ apple$ otool -l test.o |grep bitcode sectname __bitcode sectname __bitcode

若是看到輸出了2行sectname __bitcode,就是說明這靜態庫中的兩個目標文件包含了bitcode.

  • 2.下面把其中一個demo.m換成彙編語言再參與編譯:

    用下面的命令把demo.m的C代碼轉換爲ARM64彙編語言格式demo.s:

wuqiong:~ apple$ xcrun -sdk iphoneos clang -arch arm64 -S demo.m
wuqiong:~ apple$ cat demo.s
    .section    __TEXT,__text,regular,pure_instructions
    .ios_version_min 9, 2 .globl _demo .align 2 _demo: ; @demo .cfi_startproc ; BB#0: stp x29, x30, [sp, #-16]! mov x29, sp Ltmp0: .cfi_def_cfa w29, 16 Ltmp1: .cfi_offset w30, -8 Ltmp2: .cfi_offset w29, -16 adrp x0, L__unnamed_cfstring_@PAGE add x0, x0, L__unnamed_cfstring_@PAGEOFF bl _NSLog ldp x29, x30, [sp], #16 ret .cfi_endproc .section __TEXT,__cstring,cstring_literals L_.str: ; @.str .asciz "demo func" .section __DATA,__cfstring .align 4 ; @_unnamed_cfstring_ L__unnamed_cfstring_: .quad ___CFConstantStringClassReference .long 1992 ; 0x7c8 .space 4 .quad L_.str .quad 9 ; 0x9 .section __DATA,__objc_imageinfo,regular,no_dead_strip L_OBJC_IMAGE_INFO: .long 0 .long 0 .subsections_via_symbol

而後刪除demo.m這個C源代碼,僅留下test.mdemo.s:

wuqiong:~ apple$ rm demo.m

如今,咱們來把test.m這個C源代碼和dmeo.s這個彙編源代碼來一塊兒帶着-fembed-bitcode參數來生成目標代碼並打包爲一個靜態庫:

wuqiong:~ apple$ xcrun -sdk iphoneos clang -arch arm64 -fembed-bitcode -c test.m demo.s
wuqiong:~ apple$ xcrun -sdk iphoneos ar -r libTest.a test.o demo.o

而後咱們再運行otool工具來檢查這個新的靜態庫中包含的2個目標文件是否都帶有bitcode段:

wuqiong:~ apple$ ar -t libTest.a
__.SYMDEF SORTED
test.o
demo.o
wuqiong:~ apple$ otool -l libTest.a | grep bitcode sectname __bitcode

很意外,這一次,只有一行sectname __bitcode輸出,這就說明這兩個目標文件,有一個不帶有bitcode段,哪怕咱們在編譯的時候指定了參數-fembed-bitcode也沒有用.至於具體是哪個不帶bitcode段,咱們確定知道就是那個從ARM64彙編語言編譯過來的目標文件不帶.

那麼就得出一個結論,bitcode的生成,是由彙編語言以上的上層語言編譯而來,和最前面所說的那樣,他是上層語言與彙編語言(機器語言)之間的一箇中間碼.

目前咱們平常的iOS應用開發中,通常不會須要用到彙編層面去優化的代碼.因此咱們主要關注第三方(開源)C代碼,尤爲是音視頻編碼解碼這些計算密集型項目代碼,關鍵計算的代碼針對特定平臺都有對應平臺的彙編版本實現,固然也有C的實現,可是默認編譯通常都是用的彙編版本,這樣就會致使咱們在編譯這個開源代碼的時候哪怕你帶了-fembed-bitcode參數也僅僅只是讓項目中的部分C代碼的目標文件帶了bitcode段,而那小數的彙編代碼的目標文件同樣不帶bitcode段,這樣編譯出這個庫交給上層開發者使用的時候,就會出如今打包上傳或者真機調試的時候由於Xcode默認開了bitcode功能而連接失敗,致使不能真機調試或者不能上傳應用到AppStore.

此文之初衷

最近在輔導我戴維營戰友們作手機音視頻直播的App,調試的時候手機採集音視頻,視頻用h264編碼,音頻採用aac編碼,經過RTMP協議往鬥魚直播頻道發佈媒體流,項目須要用FFMPEGlibx264兩個開源項目,在編譯爲iOS框架庫提供給學生用的時候,他們遇到了bitcode的問題,雖然能夠採起直接關閉bitcode來避免錯誤,可是戰友的求知慾必須知足,格物致知,必須讓其知其究竟.

libx264是VideoLan基金會管理的一個視頻編解碼的開源項目,其大量使用了各個平臺的多媒體彙編指令進行了優化,在編譯爲不帶bitcode的庫的時候,徹底按官方autotools編譯方法是沒有任何問題的;編譯全帶bitcode的庫的時候咱們不得不關閉彙編優化,在執行./configure階段能夠加上--disable-asm參數來禁用匯編.可是,這個選項在configure腳本中的實現機制有問題.致使其仍然調用了彙編的函數,可是彙編的代碼卻沒有編譯進去,從而會致使項目爲真機構建和打包的連接階段會爆出找不到符號的錯誤,這樣就不能作到一箭雙鵰.出於輕微程度的強迫症影響,故把以前的FFMPEGlibx264項目的編譯腳本進行了改進和打補丁.目前已經能夠作到一鍵編譯出帶所有bitcode的FFMPEG和libx264的框架了.

FFmpeg須要依賴libx264.

自動編譯腳本項目位置放在github:
https://github.com/Diveinedu-CN/FFmpeg-iOS-build-script.git

相關文章
相關標籤/搜索