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)化器——后端
(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)建文件的位置。如下圖所示:
(2)文件結構如下圖所示:
可以看出主要分為三大塊內容: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)有相關解決思路,可能后面會補充)