美文网首页移动端面试文章合集
iOS高级强化--006:Mach-O体积优化

iOS高级强化--006:Mach-O体积优化

作者: 帅驼驼 | 来源:发表于2021-02-25 18:02 被阅读0次
    Bitcode

    什么是Bitcode

    Bitcode是被编译程序的一种中间形式的代码。包含Bitcode并上传到App Store ConnectApp,会在App Store上编译和链接。包含Bitcode可以在不提交新版本App的情况下,允许Apple在将来的时候再次优化你的App二进制文件。

    Xcode中,默认开启Bitcode。如果你的App支持BitcodeApp使用到的其他二进制形式也要支持Bitcode,否则就会报错。

    错误信息如下:linker command failed with exit code 1 (use -v to see invocation)

    解决错误的两种方案:

    • 方案一:将不支持BitcodeSDK移除掉,等待第三方更新。
    • 方案二:将使用Bitcode的选项设置为NO

    Bitcode是编译后生成汇编之前的中间表现:

    Bitcode底层编译流程:

    包含Bitcode并上传到App Store ConnectApp,会在App Store上编译和链接:

    链接时间优化(LTO)

    Link Time Optimization(LTO)链接时间优化是指:

    • 链接阶段执行模块间优化。

    通过整个程序分析和跨模块优化来获得更好的运行时性能的方法。

    在编译阶段,clang将发出LLVM bitcode而不是目标文件。

    链接器识别这些Bitcode文件,并在链接期间调用LLVM以生成将构成可执行文件的最终对象。

    接下来会加载所有输入的Bitcode文件,并将它们合并在一起以生成一个模块。

    通俗来讲,链接器将所有目标文件拉到一起,并将它们组合到一个程序中。链接器可以查看整个程序,因此可以进行整个程序的分析和优化。通常,链接器只有在将程序翻译成机器代码后才能看到该程序。

    LLVMLTO机制是通过把LLVM IR传递给链接器,从而可以在链接期间执行整个程序分析和优化。所以,LTO的工作方式是编译器输出的目标文件不是常规目标文件:它们是LLVM IR文件,仅通过目标文件文件扩展名伪装为目标文件。

    LTO有两种模式:

    • Full LTO是将每个单独的目标文件中的所有LLVM IR代码组合到一个大的module中,然后对其进行优化并像往常一样生成机器代码。
    • Thin LTO是将模块分开,但是根据需要可以从其他模块导入相关功能,并行进行优化和机器代码生成。

    进行LTO而不是一次全部编译的优点是(部分)编译与LTO并行进行。对于完整的LTO(-flto=full),仅并行执行语义分析,而优化和机器代码生成则在单个线程中完成。对于ThinLTO(-flto=thin),除全局分析步骤外,所有步骤均并行执行。因此,ThinLTOFullLTO或一次编译快得多。

    clang命令使用LTO的编译链接参数:

    • -flto=<value>:设置LTO的模式:full或者thin,默认full
    • -lto_library <path>:指定执行LTO方式的库所在位置。当执行链接时间优化(LTO)时,链接器将自动去链接libLTO.dylib,或者从指定路径链接。

    Xcode Build Setting中的设置:

    设置Link-Time Optimization

    通过案例来分析一下:

    打开a.h文件,写入以下代码

    #ifndef a_h
    #define a_h
    
    #include <stdio.h>
    
    extern int foo1(void);
    extern void foo2(void);
    extern void foo4(void);
    
    #endif /* a_h */
    

    打开a.c文件,写入以下代码

    #include "a.h"
    
    static signed int i = 0;
    
    void foo2(void) {
       i = -1;
    }
    
    static int foo3() {
       foo4();
       return 10;
    }
    
    int foo1(void) {
       int data = 0;
    
       if (i < 0)
           data = foo3();
    
       data = data + 42;
       return data;
    }
    

    打开main.c文件,写入以下代码

    #include <stdio.h>
    #include "a.h"
    
    void foo4(void) {
       printf("Hi\n");
    }
    
    int main() {
       return foo1();
    }
    

    进入终端运行:

    a.c编译生成Bitcode格式文件

    clang -flto -c a.c -o a.o
    

    main.c正常编译成目标文件

    clang -c main.c -o main.o
    

    a.cmain.c通过LTO方式链接到一起

    clang -flto a.o main.o -o main
    

    按照LTO优化方式:

    • 链接器首先按照顺序读取所有目标文件(此时,是Bitcode文件,仅伪装成目标文件)并收集符号信息
    • 接下来,链接器使用全局符号表解析符号。找到未定义的符号,替换weak符号等等
    • 按照解析的结果,告诉执行LTO的库文件(默认是libLTO.dylib)哪些符号是需要的。紧接着,链接器调用优化器和代码生成器,返回通过合并Bitcode文件并应用各种优化过程而创建的目标文件。然后更新内部全局符号表
    • 链接器继续运行,直到生成可执行文件

    上述案例中,LTO整个的优化顺序为:

    • 首先读取a.oBitcode文件)收集符号信息。链接器将foo1()foo2()foo4()识别为全局符号
    • 读取main.o(真正的目标文件),找到目标文件中使用的符号信息。此时,main.o使用了foo1(),定义了foo4()
    • 链接器完成了符号解析过程后,发现foo2()未在任何地方使用它将其传递给LTOfoo2()一旦可以删除,意味着发现foo1()里面调用foo3()的判断始终为假,也就是foo3()也没有使用,也可以删除
    • 符号处理完毕后,将处理结果传递给优化器和代码生成器,同时将a.o合并到main.o
    • 修改main.o的符号表信息。继续链接,生成可执行文件

    使用objdump --macho --syms main命令,查看最后生成的可执行文件main的符号表信息:

    SYMBOL TABLE:
    0000000100008008 l     O __DATA,__data __dyld_private
    0000000100000000 g     F __TEXT,__text __mh_execute_header
    0000000100003f70 g     F __TEXT,__text _foo1
    0000000100003f30 g     F __TEXT,__text _foo4
    0000000100003f50 g     F __TEXT,__text _main
    0000000000000000         *UND* _printf
    0000000000000000         *UND* dyld_stub_binder
    

    可以看到,链接完成之后,我们自己声明的函数只剩下:mainfoo1foo4

    这个地方有个问题,foo4函数并没有在任何地方使用,为什么没有把它干掉?

    因为LTO优化以入口文件需要的符号为准,来向外进行解析优化。所以,要优化掉foo4,那么就需要使用一个新的功能Dead Code Stripping

    Dead Code Stripping

    链接器的-dead_strip参数的作用:

    简单来讲,就是移除入口函数或者没有被导出符号使用到的函数或者代码。上述案例中的foo4正是符合这种情况,所以,通过-dead_strip可以删除掉无用代码

    创建动态库时,可以使用-mark_dead_strippable_dylib

    指明,如果并没有使用到该动态库的符号信息,那么链接器将会自动优化该动态库。不会因为路径问题崩溃

    同时,你也可以在App中使用-dead_strip_dylibs获得相同的功能

    Xcode Build Setting中的设置:

    设置Dead Code Stripping,它会在链接过程中进行优化

    • YES:删除掉无用代码
    • NO:不开启此项优化
    Code Generation Options

    Xcode Build Setting中的设置:

    设置Optimization Level(编译器的优化程度),它会在编译时生成目标文件时进行优化

    • None [-O0]:不优化。
      在这种设置下, 编译器的目标是降低编译消耗,保证调试时输出期望的结果。程序的语句之间是独立的:如果在程序的停在某一行的断点出,我们可以给任何变量赋新值抑或是将程序计数器指向方法中的任何一个语句,并且能得到一个和源码完全一致的运行结果。

    • Fast [-O1]:大函数所需的编译时间和内存消耗都会稍微增加。
      在这种设置下,编译器会尝试减小代码文件的大小,减少执行时间,但并不执行需要大量编译时间的优化。在苹果的编译器中,在优化过程中,严格别名,块重排和块间的调度都会被默认禁止掉。此优化级别提供了良好的调试体验,堆栈使用率也提高,并且代码质量优于None [-O0]

    • Faster [-O2]:编译器执行所有不涉及时间空间交换的所有的支持的优化选项。
      是更高的性能优化Fast [-O1]。在这种设置下,编译器不会进行循环展开、函数内联或寄存器重命名。和Fast [-O1]项相比,此设置会增加编译时间,降低调试体验,并可能导致代码大小增加,但是会提高生成代码的性能。

    • Fastest [-O3]:在开启Fast [-O1]项支持的所有优化项的同时,开启函数内联和寄存器重命名选项。
      是更高的性能优化Faster [-O2],指示编译器优化所生成代码的性能,而忽略所生成代码的大小,有可能会导致二进制文件变大。还会降低调试体验。

    • Fastest, Smallest [-Os]:在不显着增加代码大小的情况下尽量提供高性能。
      这个设置开启了Fast [-O1]项中的所有不增加代码大小的优化选项,并会进一步的执行可以减小代码大小的优化。增加的代码大小小于Fastest [-O3]。与Fast [-O1]相比,它还会降低调试体验。

    • Fastest, Aggressive Optimizations [-Ofast]:与Fastest, Smallest [-Os]相比该级别还执行其他更激进的优化。
      这个设置开启了Fastest [-O3]中的所有优化选项,同时也开启了可能会打破严格编译标准的积极优化,但并不会影响运行良好的代码。该级别会降低调试体验,并可能导致代码大小增加。

    • Smallest, Aggressive Size Optimizations [-Oz]:不使用LTO的情况下减小代码大小。
      -Os相似,指示编译器仅针对代码大小进行优化,而忽略性能优化,这可能会导致代码变慢。

    strip

    strip:移除指定符号。在Xcode中默认strip是在Archive的时候才会生效,移除对应符号

    strip命令的使用:

    • strip -x:除了全局符号都可以移除 (动态库使用)
    • strip -S:移除调试符号(静态库使用)
    • strip:除了间接符号表中使用的符号,其他符号都移除(上架App使用)

    Xcode Build Setting中的设置:

    设置Deployment Postprocessing

    • 打开后在编译阶段就会运行strip

    设置Strip Debug Symbols During Copy

    • 当应用在编译阶段copy了某些二进制文件时,打开该选项会脱掉该二进制的调试符号。但是不会脱去链接的最终产物(可执行文件\动态库)的符号信息。要脱去链接的产物(App的可执行文件)的符号信息

    设置Strip Linked Product

    • 如果没有打开Deployment Postprocessing,则会在Archive处理链接的最终产物(可执行文件)的符号信息。否则,在链接完成之后就会处理符号信息

    设置Strip Style(符号剥离级别),它会在生成可执行文件后进行优化,相当于对Mach-O文件进行修改

    • All Symbols:除了间接符号表中使用的符号,其他符号都移除(上架App使用)
    • Non-Global Symbols:除了全局符号都可以移除 (动态库使用)
    • Debugging Symbols:移除调试符号(静态库使用)

    Strip Style原理

    • App:可以剥离除间接符号表以外的所有符号
    • 动态库:可以剥离除全局符号以外的所有符号
    • 静态库:静态库是.o文件的合集,符号都存储在重定位符号表中。静态库只能剥离调试符号

    Debugging Symbols:剥离.o/静态库的调试符号

    Debugging Symbols:剥离动态库/可执行文件的调试符号

    All Symbols:剥离除间接符号表以外的所有符号

    Non-Global Symbols:剥离除全局符号以外的所有符号

    在LLVM项目中调试strip命令
    添加llvm-strip

    打开LLVM项目,打开Edit Scheme...弹窗

    选择Manage Schemes...

    点击+添加

    Target选择llvm-strip,填写Name后点击OK

    此时llvm-strip添加成功,点击Close关闭弹窗

    创建替身

    TAEGETS中搜索strip关键字,点击llvm-strip,选择Build Phases

    • llvm-strip是一个脚本,无法调试
    • 脚本作用:判断CONFIGURATION如果是Debug,将llvm-objcopy可执行文件,链接成llvm-strip可执行文件,相当于生成快捷方式

    选择llvm-strip,先运行一次脚本

    TAEGETS中搜索objcopy关键字,右键llvm-objcopy,菜单选择Duplicate

    TAEGETS中多出一个llvm-objcopy-copy

    将其重命名为strip

    打开链接后的目录/Volumes/study/Source/llvm/llvm-project/build/Debug/bin,找到llvm-strip可执行文件

    将其复制,重命名为strip

    回到项目,在Products目录中找到strip,右键strip,菜单选择Show in Finder,此时它自动跟Debug/bin目录中的strip可执行文件关联到一起

    添加strip

    打开LLVM项目,打开Edit Scheme...弹窗

    选择Manage Schemes...

    点击+添加

    Target选择strip,填写Name后点击OK

    此时strip添加成功,点击Close关闭弹窗

    添加默认参数

    打开Edit Scheme...弹窗,左上角选择strip,左侧选择Run,右侧选择Arguments

    Arguments Passed On Launch项中,点击+添加参数Mach-O路径

    • ⽆参数: 代表全部符号
    • -x: non_global
    • -S: 调试符号

    参数添加成功,点击Close关闭弹窗

    设置断点

    TAEGETS中搜索strip关键字,点击strip,选择Build Phases

    展开Compile Sources项,右键llvm-nm.cpp文件,菜单选择Reveal In Project Navigator,将文件显示在左侧

    打开llvm-nm.cpp代码,找到main函数并设置好断点

    运⾏程序

    选择strip,但不要直接运行,因为编译会非常耗时

    使用Run Without Building运⾏程序;第⼀次运⾏时,需要进⾏编译,以重新⽣成调试符号。再次运⾏,当代码没有改变,则不需要重新编译,直接运⾏现有可执⾏⽂件

    顺利进入断点,通过下标访问argv,之前设置的默认参数已传入main函数

    BreakPoint
    原文:

    Command

    • breakpoint -- Commands for operating on breakpoints (see 'help b' for shorthand.)

    Action

    Commands for operating on breakpoints (see 'help b' for shorthand.)

    Syntax: breakpoint

    The following subcommands are supported:

    • clear -- Delete or disable breakpoints matching the specified source file and line.
    • command -- Commands for adding, removing and listing LLDB commands executed when a breakpoint is hit.
    • delete -- Delete the specified breakpoint(s). If no breakpoints are specified, delete them all.
    • disable -- Disable the specified breakpoint(s) without deleting them. If none are specified, disable all breakpoints.
    • enable -- Enable the specified disabled breakpoint(s). If no breakpoints
      are specified, enable all of them.
    • list -- List some or all breakpoints at configurable levels of detail.
    • modify -- Modify the options on a breakpoint or set of breakpoints in the executable. If no breakpoint is specified, acts on the last created breakpoint. With the exception of -e, -d and -i, passing an empty argument clears the modification.
    • name -- Commands to manage name tags for breakpoints
    • read -- Read and set the breakpoints previously saved to a file with "breakpoint write".
    • set -- Sets a breakpoint or set of breakpoints in the executable.
    • write -- Write the breakpoints listed to a file that can be read in with "breakpoint read". If given no arguments, writes all breakpoints.

    For more help on any particular subcommand, type 'help <command> <subcommand>'.

    option - set

    Sets a breakpoint or set of breakpoints in the executable.
    
    Syntax: breakpoint set <cmd-options>
    
    Command Options Usage:
      breakpoint set [-DHd] -l <linenum> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>] [-m <boolean>]
    
      breakpoint set [-DHd] -a <address-expression> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-N <breakpoint-name>]
    
      breakpoint set [-DHd] -n <function-name> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-L <source-language>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>]
    
      breakpoint set [-DHd] -F <fullname> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-L <source-language>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>]
    
      breakpoint set [-DHd] -S <selector> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-L <source-language>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>]
    
      breakpoint set [-DHd] -M <method> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-L <source-language>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>]
    
      breakpoint set [-DHd] -r <regular-expression> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-L <source-language>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>]
    
      breakpoint set [-DHd] -b <function-name> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-L <source-language>] [-K <boolean>] [-N <breakpoint-name>] [-R <address>]
    
      breakpoint set [-ADHd] -p <regular-expression> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-s <shlib-name>] [-f <filename>] [-X <function-name>] [-N <breakpoint-name>] [-m <boolean>]
    
      breakpoint set [-DHd] -E <source-language> [-i <count>] [-o <boolean>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-c <expr>] [-G <boolean>] [-C <command>] [-w <boolean>] [-h <boolean>] [-O <type-name>] [-N <breakpoint-name>]
    
           -A ( --all-files )
                All files are searched for source pattern matches.
    
           -C <command> ( --command <command> )
                A command to run when the breakpoint is hit, can be provided more
                than once, the commands will get run in order left to right.
    
           -D ( --dummy-breakpoints )
                Act on Dummy breakpoints - i.e. breakpoints set before a file is
                provided, which prime new targets.
    
           -E <source-language> ( --language-exception <source-language> )
                Set the breakpoint on exceptions thrown by the specified language
                (without options, on throw but not catch.)
    
           -F <fullname> ( --fullname <fullname> )
                Set the breakpoint by fully qualified function names. For C++ this
                means namespaces and all arguments, and for Objective C this means
                a full function prototype with class and selector.  Can be repeated
                multiple times to make one breakpoint for multiple names.
    
           -G <boolean> ( --auto-continue <boolean> )
                The breakpoint will auto-continue after running its commands.
    
           -H ( --hardware )
                Require the breakpoint to use hardware breakpoints.
    
           -K <boolean> ( --skip-prologue <boolean> )
                sKip the prologue if the breakpoint is at the beginning of a
                function.  If not set the target.skip-prologue setting is used.
    
           -L <source-language> ( --language <source-language> )
                Specifies the Language to use when interpreting the breakpoint's
                expression (note: currently only implemented for setting
                breakpoints on identifiers).  If not set the target.language
                setting is used.
    
           -M <method> ( --method <method> )
                Set the breakpoint by C++ method names.  Can be repeated multiple
                times to make one breakpoint for multiple methods.
    
           -N <breakpoint-name> ( --breakpoint-name <breakpoint-name> )
                Adds this to the list of names for this breakpoint.
    
           -O <type-name> ( --exception-typename <type-name> )
                The breakpoint will only stop if an exception Object of this type
                is thrown.  Can be repeated multiple times to stop for multiple
                object types.  If you just specify the type's base name it will
                match against that type in all modules, or you can specify the full
                type name including modules.  Other submatches are not supported at
                present.Only supported for Swift at present.
    
           -R <address> ( --address-slide <address> )
                Add the specified offset to whatever address(es) the breakpoint
                resolves to.  At present this applies the offset directly as given,
                and doesn't try to align it to instruction boundaries.
    
           -S <selector> ( --selector <selector> )
                Set the breakpoint by ObjC selector name. Can be repeated multiple
                times to make one breakpoint for multiple Selectors.
    
           -T <thread-name> ( --thread-name <thread-name> )
                The breakpoint stops only for the thread whose thread name matches
                this argument.
    
           -X <function-name> ( --source-regexp-function <function-name> )
                When used with '-p' limits the source regex to source contained in
                the named functions.  Can be repeated multiple times.
    
           -a <address-expression> ( --address <address-expression> )
                Set the breakpoint at the specified address.  If the address maps
                uniquely to a particular binary, then the address will be converted
                to a "file" address, so that the breakpoint will track that
                binary+offset no matter where the binary eventually loads. 
                Alternately, if you also specify the module - with the -s option -
                then the address will be treated as a file address in that module,
                and resolved accordingly.  Again, this will allow lldb to track
                that offset on subsequent reloads.  The module need not have been
                loaded at the time you specify this breakpoint, and will get
                resolved when the module is loaded.
    
           -b <function-name> ( --basename <function-name> )
                Set the breakpoint by function basename (C++ namespaces and
                arguments will be ignored).  Can be repeated multiple times to make
                one breakpoint for multiple symbols.
    
           -c <expr> ( --condition <expr> )
                The breakpoint stops only if this condition expression evaluates to
                true.
    
           -d ( --disable )
                Disable the breakpoint.
    
           -f <filename> ( --file <filename> )
                Specifies the source file in which to set this breakpoint.  Note,
                by default lldb only looks for files that are #included if they use
                the standard include file extensions.  To set breakpoints on
                .c/.cpp/.m/.mm files that are #included, set
                target.inline-breakpoint-strategy to "always".
    
           -h <boolean> ( --on-catch <boolean> )
                Set the breakpoint on exception catcH.
    
           -i <count> ( --ignore-count <count> )
                Set the number of times this breakpoint is skipped before stopping.
    
           -l <linenum> ( --line <linenum> )
                Specifies the line number on which to set this breakpoint.
    
           -m <boolean> ( --move-to-nearest-code <boolean> )
                Move breakpoints to nearest code. If not set the
                target.move-to-nearest-code setting is used.
    
           -n <function-name> ( --name <function-name> )
                Set the breakpoint by function name.  Can be repeated multiple
                times to make one breakpoint for multiple names
    
           -o <boolean> ( --one-shot <boolean> )
                The breakpoint is deleted the first time it stop causes a stop.
    
           -p <regular-expression> ( --source-pattern-regexp <regular-expression> )
                Set the breakpoint by specifying a regular expression which is
                matched against the source text in a source file or files specified
                with the -f option.  The -f option can be specified more than once.
                If no source files are specified, uses the current "default source
                file".  If you want to match against all source files, pass the
                "--all-files" option.
    
           -q <queue-name> ( --queue-name <queue-name> )
                The breakpoint stops only for threads in the queue whose name is
                given by this argument.
    
           -r <regular-expression> ( --func-regex <regular-expression> )
                Set the breakpoint by function name, evaluating a
                regular-expression to find the function name(s).
    
           -s <shlib-name> ( --shlib <shlib-name> )
                Set the breakpoint only in this shared library.  Can repeat this
                option multiple times to specify multiple shared libraries.
    
           -t <thread-id> ( --thread-id <thread-id> )
                The breakpoint stops only for the thread whose TID matches this
                argument.
    
           -w <boolean> ( --on-throw <boolean> )
                Set the breakpoint on exception throW.
    
           -x <thread-index> ( --thread-index <thread-index> )
                The breakpoint stops only for the thread whose index matches this
                argument.
    
    基础介绍

    从文件中导入断点

    br read -f 【文件路径】
    

    将断点导出到文件

    br write -f 【文件路径】
    

    查看组内的断点列表

    br list 【组名称】
    

    启用组中的断点

    br enable 【组名称】
    

    禁用组中的断点

    br disable 【组名称】
    

    删除组中的断点

    br delete 【组名称】
    

    通过文件和行号设置断点

    br set -f 【文件名】 -l 【行号】
    

    通过函数名称设置断点,也能为classselector设置断点

    br set -n 【函数名称】
    br set -n -[NSString stringWithFormat:]
    

    C++函数设置断点

    br set -M 【函数名称】
    

    OCselector设置断点

    br set -S 【selector】
    

    为指定文件里的selector设置断点

    br set -f 【文件名】 -S 【selector】
    

    为某个image设置断点

    br set -s 【image名称】 -n 【函数名称】
    

    为项目中包含关键字的方法设置断点

    br set -r 【关键字】
    
    从文件中导入断点

    打开strip_lldb.m文件,断点如下:

    [
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["removeSections"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["MachOObjcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["handleArgs"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["MachOObjcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["executeObjcopyOnBinary"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["MachOObjcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["markSymbols"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["MachOObjcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["main"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["llvm-objcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["getDriverConfig"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["llvm-objcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["executeObjcopy"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["llvm-objcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["parseStripOptions"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["llvm-objcopy.cpp"]},"Type":"ModulesAndCU"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":false,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["MachOWriter::write"]},"Type":"SymbolName"},"Hardware":false,"Names":["strip"],"SearchFilter":{"Options":{"CUList":["MachOWriter.cpp"]},"Type":"ModulesAndCU"}}}
    ]
    
    • 这里记录了探索strip命令的关键点

    使用br read -f /Users/zang/Zang/Spark/strip_lldb.m命令,将strip_lldb.m文件中的断点导入项目

    • 导入的断点默认是没有被启用的

    使用br enable strip命令,将strip组中的断点全部启用

    此时断点全部启用;需要注意的是:br命令只对本次运行有效,当项目重新运行后,需要再次导入并启用断点

    将断点导出到文件

    main函数的断点为例

    直接使用Xcode设置断点并导出,文件中FileName会记录断点所在文件的绝对路径,这会造成导出的断点文件不具通用性

    使用br命令设置断点,可以解决通用性问题

    使用br set -f llvm-objcopy.cpp -l 346 -N cat命令,对llvm-objcopy.cpp文件的第346行设置断点,加入到cat分组

    使用br set -n removeSections -f MachOObjcopy.cpp -N cat命令,对llvm-objcopy.cpp文件的removeSections函数设置断点,加入到cat分组

    使用br list cat命令,查看cat分组下的断点

    使用br write -f /Users/zang/Zang/Spark/cat.m命令,将断点导出到cat.m文件

    [
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":true,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"Column":0,"Exact":false,"FileName":"llvm-objcopy.cpp","Inlines":true,"LineNumber":346,"Offset":0,"SkipPrologue":true},"Type":"FileAndLine"},"Hardware":false,"Names":["cat"],"SearchFilter":{"Options":{},"Type":"Unconstrained"}}},
       {"Breakpoint":{"BKPTOptions":{"AutoContinue":false,"ConditionText":"","EnabledState":true,"IgnoreCount":0,"OneShotState":false},"BKPTResolver":{"Options":{"NameMask":[56],"Offset":0,"SkipPrologue":true,"SymbolNames":["removeSections"]},"Type":"SymbolName"},"Hardware":false,"Names":["cat"],"SearchFilter":{"Options":{"CUList":["MachOObjcopy.cpp"]},"Type":"ModulesAndCU"}}}
    ]
    
    查看App Size报告

    方式一:

    通过App Store Connect提供准确的App大小

    方式二:

    通过Xcode内置报告工具,创建App尺寸报告

    • Archive App
    • 通过Ad HocDevelopment或者Enterprise等分发方式导出Archive App
    • 在设置开发分发选项的列表中,选择All compatible device variants以进行应用程序精简,然后启用Rebuild from Bitcode
    • 签名并且导出

    此过程将创建一个包含App的文件夹,里面有:

    • Universal IPA,包含多个平台的资源文件和二进制程序
    • Thinned IPA,指定平台的资源文件和二进制程序

    同时还包含一个App Thinning Size Report.txt,里面详细记录了App的体积占用情况:

    App Thinning Size Report for All Variants of ExampleApp
    
    Variant: ExampleApp.ipa
    Supported variant descriptors: [device: iPhone11,4, os-version: 12.0], [device: iPhone9,4, os-version: 12.0], [device: iPhone10,3, os-version: 12.0], [device: iPhone11,6, os-version: 12.0], [device: iPhone10,6, os-version: 12.0], [device: iPhone9,2, os-version: 12.0], [device: iPhone10,5, os-version: 12.0], [device: iPhone11,2, os-version: 12.0], and [device: iPhone10,2, os-version: 12.0]
    App + On Demand Resources size: 6.7 MB compressed, 18.6 MB uncompressed
    App size: 6.7 MB compressed, 18.6 MB uncompressed
    On Demand Resources size: Zero KB compressed, Zero KB uncompressed
    
    // Other Variants of Your App.
    

    方式三:

    通过脚本的方式指定输出App Size报告:

    xcodebuild -exportArchive -archivePath iOSApp.xcarchive -exportPath Release/MyApp -exportOptionsPlist OptionsPlist.plist
    
    总结:

    相关文章

      网友评论

        本文标题:iOS高级强化--006:Mach-O体积优化

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