OC包體結構與優(yōu)化

Objc包結構

1、背景

????????iOS系統(tǒng)對應用程序的二進制包大小有嚴格的限制,ios7、ios8系統(tǒng)TEXT字段只支持60M(60 *1000字節(jié))大小的應用程序,目前XX音樂app已多次超出這個限制。為解決包大小問題,需要深入了解整個包的結構,并找出解決辦法。

2、編譯過程

????????要了解包結構,先從objc包的編譯過程進行講解,如果大家有了解過,應該都知道目前新版的xcode都是用llvm進行源碼編譯的,llvm編譯的好處有很多文章介紹過,今天主要要講解的是llvm的編譯過程及其附屬產(chǎn)物,以及怎么驗證。

(1)llvm的編譯架構設計主要分為三層:

前端——代碼優(yōu)化器——后端


llvm結構

(2)clang編譯過程

通過命令了解整個編譯過程:

clang -ccc-print-phases sample.m,查看完整的編譯過程

0: input, "sample.m", objective-c

1: preprocessor, {0}, objective-c-cpp-output

2: compiler, {1}, ir

3: backend, {2}, assembler

4: assembler, {3}, object

5: linker, {4}, image

6: bind-arch, "x86_64", {5}, image

clang -### sample.m,顯示編譯過程用到的命令

clang -### sample.mApple LLVM version 9.0.0 (clang-900.0.39.2)Target: x86_64-apple-darwin16.7.0Thread model: posixInstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin?"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang" "-cc1" "-triple" "x86_64-apple-macosx10.12.0" "-Wdeprecated-objc-isa-usage" "-Werror=deprecated-objc-isa-usage" "-emit-obj" "-mrelax-all" "-disable-free" "-disable-llvm-verifier" "-discard-value-names" "-main-file-name" "sample.m" "-mrelocation-model" "pic" "-pic-level" "2" "-mthread-model" "posix" "-mdisable-fp-elim" "-fno-strict-return" "-masm-verbose" "-munwind-tables" "-target-cpu" "penryn" "-target-linker-version" "305" "-dwarf-column-info" "-debugger-tuning=lldb" "-resource-dir" "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0" "-fdebug-compilation-dir" "/Users/zerryzarax/Desktop/llvm例子" "-ferror-limit" "19" "-fmessage-length" "80" "-stack-protector" "1" "-fblocks" "-fobjc-runtime=macosx-10.12.0" "-fencode-extended-block-signature" "-fobjc-exceptions" "-fexceptions" "-fmax-type-align=16" "-fdiagnostics-show-option" "-fcolor-diagnostics" "-o" "/var/folders/0j/wwr16xvd6k73wsgplg230p4c0000gn/T/sample-f35851.o" "-x" "objective-c" "sample.m"?"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld" "-demangle" "-lto_library" "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libLTO.dylib" "-no_deduplicate" "-dynamic" "-arch" "x86_64" "-macosx_version_min" "10.12.0" "-o" "a.out" "/var/folders/0j/wwr16xvd6k73wsgplg230p4c0000gn/T/sample-f35851.o" "-lSystem" "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/lib/darwin/libclang_rt.osx.a"


可以看到這里執(zhí)行了兩大程序,clang和ld,所以一般我們直接調用clang命令是包含完整的編譯過程的

clang --analyze sample.m,就是平時xcode所用的analyze功能,最終會生成plist文件,把所有的分析結果都匯總在里面

clang -E sample.m -o samplepre,預編譯文件,會把引用到的代碼都會加到同一個文件上(對應過程1)

clang -emit-llvm -S sample.m -o sample(ir).ll,只進行編譯,并輸出ir代碼。(對應過程2)

clang -S sample.m -o sample(x86).ll,編譯成x86匯編代碼(對應過程3)

xcrun -sdk iphoneos clang -arch arm64 -S sample.m -o sample(arm64).ll(對應過程3)

clang -c sample.m,編譯成.o文件,在pc上運行默認編譯成x86的命令(對應過程4)

clang -fembed-bitcode -c sample.m,會編譯成.o文件,但是不一樣的是,這次會多了以下llvm段數(shù)據(jù)

Section?

sectname __bitcode? ?

segname __LLVM? ? ?

addr 0x0000000000000090? ? ?

size 0x0000000000000be0? ?

offset 1088?? ?

align 2^4 (16)? ?

reloff 0? ?

nreloc 0?? ?

flags 0x00000000?

reserved1 0?

reserved2 0

Section?

sectname __cmdline??

segname __LLVM? ? ?

addr 0x0000000000000c70? ? ?

size 0x0000000000000042? ?

offset 4128?? ?

align 2^4 (16)? ?

reloff 0? ?

nreloc 0?? ?

flags 0x00000000?

reserved1 0?

reserved2 0

/usr/bin/ld 對應過程5、6

(3)額外,ast樹查看

通過clang -Xclang -ast-dump -fsyntax-only sample.m命令可以查看語法樹生成的情況

3、objc包結構總覽

直接命令:clang -framework Foundation sample.m

(1)把生成的a.out文件拖入到hopper,可以觀察到包的總體結構分為以下幾部分:

二進制包結構

4、linkmap對比

(1)打開xcode的link map file功能:build setting->write link map file設置為YES,path to link map file修改為你想要創(chuàng)建文件的位置。如下圖所示:

link map配置

(2)文件結構如下圖所示:


object file
section
symbols

可以看出主要分為三大塊內容:object file、section、symbols。

object file中可以看出,編譯過程總共處理了哪些文件;section基本與二進制文件中描述的分段保持一致;symbols上就列舉了代碼中的方法信息、字符串信息等。

今天研究二進制包主要是為了解決包大小問題,從背景中提到TEXT區(qū)域超出限制大小,而這種問題的主要解決辦法是需要刪減無用的代碼。在認真觀察link map內容后,我們發(fā)現(xiàn)link map雖然提供了一些基本信息,但是不足以滿足我們刪代碼的要求(無法得出完整的類結構),所以我們需要另辟蹊徑。

ps:雖然link map不能滿足刪減代碼的工作,但是可以看出,編譯過程中的最后一步是需要利用這個文件把所有的.o文件合并,并重新生成二進制包每個段的偏移地址的。

5、otool命令

通過上面的包結構分析,明顯發(fā)現(xiàn),要解決TEXT段超出上限的這個問題需要從二進制包作為突破口。幸運的是xcode為我們提供了個便利的工具:otool

(1)otool -oV

這個神奇的命令幫我們處理了大量的工作,命令里面包含主要內容:__DATA __objc_classlist、__DATA __objc_classrefs、__DATA __objc_superrefs、__DATA?__objc_protolist、__DATA?__objc_imageinfo,備注:這些信息大部分已經(jīng)翻譯好,省去了大量的工作,且滿足剔除無用類的判斷

(2)算法查找過程


查找無用類的過程

(3)otool -v -s

直接返回二進制包對應區(qū)域、段落的內容出來,并且如果可以成功翻譯的情況下會進行字符串翻譯。利用這個命令otool -v -s?__DATA __objc_selrefs(返回所有會被執(zhí)行的方法引用),并結合第一個命令,我們可以查找無用方法。過程如下:


查找無用方法

(4)查找動態(tài)生成的類

利用otool -l緩存__TEXT __stubs的起始地址和結束地址,objdump -lazy-bind緩存NSClassFromString的調用地址,otool -v -s __TEXT __stubs獲取所有動態(tài)庫命令地址,從中找出執(zhí)行命令地址和NSClassFromString的映射關系;otool -v -s __TEXT __text遍歷命令,查找出NSClassFromString命令號,并反查出入?yún)⒆址罱K確定動態(tài)調用的類

需要18分鐘,2000w行代碼,有誤判情況,有特殊情況無法查找。(不建議使用)

(5)無用方法排除協(xié)議誤判

利用otool -v -s __TEXT __objc_classname緩存類名,otool -v -s __DATA __objc_const緩存協(xié)議尋址結構,otool -v -s __TEXT __objc_methname緩存方法名,通過otool -v -s __DATA __data類協(xié)議和協(xié)議方法名的關系,最終匯總所有方法名和協(xié)議。然后從所有類方法里面,排除掉類引用了協(xié)議的方法。

6、目前包大小處理缺陷

目前包大小處理缺陷有以下幾點:

1、無法完全處理動態(tài)生成的類,雖然目前提供的方法可行,但是效率太低,沒有實際用在生產(chǎn)環(huán)境上

2、代碼寫得不規(guī)范容易出現(xiàn)誤刪情況,無法自動化

3、系統(tǒng)基類方法協(xié)議無法追蹤(目前已經(jīng)有相關解決思路,可能后面會補充)

總結:充分了解包體結構,對進一步分析解決包大小問題有至關重要的作用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容