iOS高级强化--006:Mach-O体积优化
Bitcode
什么是
Bitcode
?
Bitcode
是被编译程序的一种中间形式的代码。包含Bitcode
并上传到App Store Connect
的App
,会在App Store
上编译和链接。包含Bitcode
可以在不提交新版本App
的情况下,允许Apple
在将来的时候再次优化你的App
二进制文件。在
Xcode
中,默认开启Bitcode
。如果你的App
支持Bitcode
,App
使用到的其他二进制形式也要支持Bitcode
,否则就会报错。错误信息如下:
linker command failed with exit code 1 (use -v to see invocation)
解决错误的两种方案:
- 方案一:将不支持
Bitcode
的SDK
移除掉,等待第三方更新。- 方案二:将使用
Bitcode
的选项设置为NO
:
Bitcode
是编译后生成汇编之前的中间表现:
Bitcode
底层编译流程:
包含
Bitcode
并上传到App Store Connect
的App
,会在App Store
上编译和链接:
链接时间优化(LTO)
Link Time Optimization(LTO)
链接时间优化是指:
- 链接阶段执行模块间优化。
通过整个程序分析和跨模块优化来获得更好的运行时性能的方法。
在编译阶段,
clang
将发出LLVM bitcode
而不是目标文件。链接器识别这些
Bitcode
文件,并在链接期间调用LLVM
以生成将构成可执行文件的最终对象。接下来会加载所有输入的
Bitcode
文件,并将它们合并在一起以生成一个模块。通俗来讲,链接器将所有目标文件拉到一起,并将它们组合到一个程序中。链接器可以查看整个程序,因此可以进行整个程序的分析和优化。通常,链接器只有在将程序翻译成机器代码后才能看到该程序。
LLVM
的LTO
机制是通过把LLVM IR
传递给链接器,从而可以在链接期间执行整个程序分析和优化。所以,LTO
的工作方式是编译器输出的目标文件不是常规目标文件:它们是LLVM IR
文件,仅通过目标文件文件扩展名伪装为目标文件。
LTO
有两种模式:
Full LTO
是将每个单独的目标文件中的所有LLVM IR
代码组合到一个大的module
中,然后对其进行优化并像往常一样生成机器代码。Thin LTO
是将模块分开,但是根据需要可以从其他模块导入相关功能,并行进行优化和机器代码生成。进行
LTO
而不是一次全部编译的优点是(部分)编译与LTO
并行进行。对于完整的LTO(-flto=full)
,仅并行执行语义分析,而优化和机器代码生成则在单个线程中完成。对于ThinLTO(-flto=thin)
,除全局分析步骤外,所有步骤均并行执行。因此,ThinLTO
比FullLTO
或一次编译快得多。
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.c
和main.c
通过LTO
方式链接到一起clang -flto a.o main.o -o main
按照
LTO
优化方式:
- 链接器首先按照顺序读取所有目标文件(此时,是
Bitcode
文件,仅伪装成目标文件)并收集符号信息- 接下来,链接器使用全局符号表解析符号。找到未定义的符号,替换
weak
符号等等- 按照解析的结果,告诉执行
LTO
的库文件(默认是libLTO.dylib
)哪些符号是需要的。紧接着,链接器调用优化器和代码生成器,返回通过合并Bitcode
文件并应用各种优化过程而创建的目标文件。然后更新内部全局符号表- 链接器继续运行,直到生成可执行文件
上述案例中,
LTO
整个的优化顺序为:
- 首先读取
a.o
(Bitcode
文件)收集符号信息。链接器将foo1()
、foo2()
、foo4()
识别为全局符号- 读取
main.o
(真正的目标文件),找到目标文件中使用的符号信息。此时,main.o
使用了foo1()
,定义了foo4()
- 链接器完成了符号解析过程后,发现
foo2()
未在任何地方使用它将其传递给LTO
。foo2()
一旦可以删除,意味着发现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
可以看到,链接完成之后,我们自己声明的函数只剩下:
main
、foo1
和foo4
这个地方有个问题,
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 【行号】
通过函数名称设置断点,也能为
class
的selector
设置断点br set -n 【函数名称】 br set -n -[NSString stringWithFormat:]
为
C++
函数设置断点br set -M 【函数名称】
为
OC
的selector
设置断点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 Hoc
、Development
或者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