美文网首页
OC包体结构与优化

OC包体结构与优化

作者: zerryzarax | 来源:发表于2019-08-25 20:55 被阅读0次

    Objc包结构

    1、背景

            iOS系统对应用程序的二进制包大小有严格的限制,ios7、ios8系统TEXT字段只支持60M(60 *1000字节)大小的应用程序,目前XX音乐app已多次超出这个限制。为解决包大小问题,需要深入了解整个包的结构,并找出解决办法。

    2、编译过程

            要了解包结构,先从objc包的编译过程进行讲解,如果大家有了解过,应该都知道目前新版的xcode都是用llvm进行源码编译的,llvm编译的好处有很多文章介绍过,今天主要要讲解的是llvm的编译过程及其附属产物,以及怎么验证。

    (1)llvm的编译架构设计主要分为三层:

    前端——代码优化器——后端

    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"


    可以看到这里执行了两大程序,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段数据

    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修改为你想要创建文件的位置。如下图所示:

    link map配置

    (2)文件结构如下图所示:

    object file section symbols

    可以看出主要分为三大块内容:object file、section、symbols。

    object file中可以看出,编译过程总共处理了哪些文件;section基本与二进制文件中描述的分段保持一致;symbols上就列举了代码中的方法信息、字符串信息等。

    今天研究二进制包主要是为了解决包大小问题,从背景中提到TEXT区域超出限制大小,而这种问题的主要解决办法是需要删减无用的代码。在认真观察link map内容后,我们发现link map虽然提供了一些基本信息,但是不足以满足我们删代码的要求(无法得出完整的类结构),所以我们需要另辟蹊径。

    ps:虽然link map不能满足删减代码的工作,但是可以看出,编译过程中的最后一步是需要利用这个文件把所有的.o文件合并,并重新生成二进制包每个段的偏移地址的。

    5、otool命令

    通过上面的包结构分析,明显发现,要解决TEXT段超出上限的这个问题需要从二进制包作为突破口。幸运的是xcode为我们提供了个便利的工具:otool

    (1)otool -oV

    这个神奇的命令帮我们处理了大量的工作,命令里面包含主要内容:__DATA __objc_classlist、__DATA __objc_classrefs、__DATA __objc_superrefs、__DATA __objc_protolist、__DATA __objc_imageinfo,备注:这些信息大部分已经翻译好,省去了大量的工作,且满足剔除无用类的判断

    (2)算法查找过程

    查找无用类的过程

    (3)otool -v -s

    直接返回二进制包对应区域、段落的内容出来,并且如果可以成功翻译的情况下会进行字符串翻译。利用这个命令otool -v -s __DATA __objc_selrefs(返回所有会被执行的方法引用),并结合第一个命令,我们可以查找无用方法。过程如下:

    查找无用方法

    (4)查找动态生成的类

    利用otool -l缓存__TEXT __stubs的起始地址和结束地址,objdump -lazy-bind缓存NSClassFromString的调用地址,otool -v -s __TEXT __stubs获取所有动态库命令地址,从中找出执行命令地址和NSClassFromString的映射关系;otool -v -s __TEXT __text遍历命令,查找出NSClassFromString命令号,并反查出入参字符串,最终确定动态调用的类

    需要18分钟,2000w行代码,有误判情况,有特殊情况无法查找。(不建议使用)

    (5)无用方法排除协议误判

    利用otool -v -s __TEXT __objc_classname缓存类名,otool -v -s __DATA __objc_const缓存协议寻址结构,otool -v -s __TEXT __objc_methname缓存方法名,通过otool -v -s __DATA __data类协议和协议方法名的关系,最终汇总所有方法名和协议。然后从所有类方法里面,排除掉类引用了协议的方法。

    6、目前包大小处理缺陷

    目前包大小处理缺陷有以下几点:

    1、无法完全处理动态生成的类,虽然目前提供的方法可行,但是效率太低,没有实际用在生产环境上

    2、代码写得不规范容易出现误删情况,无法自动化

    3、系统基类方法协议无法追踪(目前已经有相关解决思路,可能后面会补充)

    总结:充分了解包体结构,对进一步分析解决包大小问题有至关重要的作用。

    相关文章

      网友评论

          本文标题:OC包体结构与优化

          本文链接:https://www.haomeiwen.com/subject/vfdqectx.html