iOS高級強(qiáng)化--006:Mach-O體積優(yōu)化

Bitcode

什么是Bitcode

Bitcode是被編譯程序的一種中間形式的代碼。包含Bitcode并上傳到App Store ConnectApp,會在App Store上編譯和鏈接。包含Bitcode可以在不提交新版本App的情況下,允許Apple在將來的時候再次優(yōu)化你的App二進(jìn)制文件。

Xcode中,默認(rèn)開啟Bitcode。如果你的App支持Bitcode,App使用到的其他二進(jìn)制形式也要支持Bitcode,否則就會報錯。

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

解決錯誤的兩種方案:

  • 方案一:將不支持BitcodeSDK移除掉,等待第三方更新。
  • 方案二:將使用Bitcode的選項(xiàng)設(shè)置為NO

Bitcode是編譯后生成匯編之前的中間表現(xiàn):

Bitcode底層編譯流程:

包含Bitcode并上傳到App Store ConnectApp,會在App Store上編譯和鏈接:

鏈接時間優(yōu)化(LTO)

Link Time Optimization(LTO)鏈接時間優(yōu)化是指:

  • 鏈接階段執(zhí)行模塊間優(yōu)化。

通過整個程序分析和跨模塊優(yōu)化來獲得更好的運(yùn)行時性能的方法。

在編譯階段,clang將發(fā)出LLVM bitcode而不是目標(biāo)文件。

鏈接器識別這些Bitcode文件,并在鏈接期間調(diào)用LLVM以生成將構(gòu)成可執(zhí)行文件的最終對象。

接下來會加載所有輸入的Bitcode文件,并將它們合并在一起以生成一個模塊。

通俗來講,鏈接器將所有目標(biāo)文件拉到一起,并將它們組合到一個程序中。鏈接器可以查看整個程序,因此可以進(jìn)行整個程序的分析和優(yōu)化。通常,鏈接器只有在將程序翻譯成機(jī)器代碼后才能看到該程序。

LLVMLTO機(jī)制是通過把LLVM IR傳遞給鏈接器,從而可以在鏈接期間執(zhí)行整個程序分析和優(yōu)化。所以,LTO的工作方式是編譯器輸出的目標(biāo)文件不是常規(guī)目標(biāo)文件:它們是LLVM IR文件,僅通過目標(biāo)文件文件擴(kuò)展名偽裝為目標(biāo)文件。

LTO有兩種模式:

  • Full LTO是將每個單獨(dú)的目標(biāo)文件中的所有LLVM IR代碼組合到一個大的module中,然后對其進(jìn)行優(yōu)化并像往常一樣生成機(jī)器代碼。
  • Thin LTO是將模塊分開,但是根據(jù)需要可以從其他模塊導(dǎo)入相關(guān)功能,并行進(jìn)行優(yōu)化和機(jī)器代碼生成。

進(jìn)行LTO而不是一次全部編譯的優(yōu)點(diǎn)是(部分)編譯與LTO并行進(jìn)行。對于完整的LTO(-flto=full),僅并行執(zhí)行語義分析,而優(yōu)化和機(jī)器代碼生成則在單個線程中完成。對于ThinLTO(-flto=thin),除全局分析步驟外,所有步驟均并行執(zhí)行。因此,ThinLTOFullLTO或一次編譯快得多。

clang命令使用LTO的編譯鏈接參數(shù):

  • -flto=<value>:設(shè)置LTO的模式:full或者thin,默認(rèn)full。
  • -lto_library <path>:指定執(zhí)行LTO方式的庫所在位置。當(dāng)執(zhí)行鏈接時間優(yōu)化(LTO)時,鏈接器將自動去鏈接libLTO.dylib,或者從指定路徑鏈接。

Xcode Build Setting中的設(shè)置:

設(shè)置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();
}

進(jìn)入終端運(yùn)行:

a.c編譯生成Bitcode格式文件

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

main.c正常編譯成目標(biāo)文件

clang -c main.c -o main.o

a.cmain.c通過LTO方式鏈接到一起

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

按照LTO優(yōu)化方式:

  • 鏈接器首先按照順序讀取所有目標(biāo)文件(此時,是Bitcode文件,僅偽裝成目標(biāo)文件)并收集符號信息
  • 接下來,鏈接器使用全局符號表解析符號。找到未定義的符號,替換weak符號等等
  • 按照解析的結(jié)果,告訴執(zhí)行LTO的庫文件(默認(rèn)是libLTO.dylib)哪些符號是需要的。緊接著,鏈接器調(diào)用優(yōu)化器和代碼生成器,返回通過合并Bitcode文件并應(yīng)用各種優(yōu)化過程而創(chuàng)建的目標(biāo)文件。然后更新內(nèi)部全局符號表
  • 鏈接器繼續(xù)運(yùn)行,直到生成可執(zhí)行文件

上述案例中,LTO整個的優(yōu)化順序?yàn)椋?/p>

  • 首先讀取a.oBitcode文件)收集符號信息。鏈接器將foo1()、foo2()、foo4()識別為全局符號
  • 讀取main.o(真正的目標(biāo)文件),找到目標(biāo)文件中使用的符號信息。此時,main.o使用了foo1(),定義了foo4()
  • 鏈接器完成了符號解析過程后,發(fā)現(xiàn)foo2()未在任何地方使用它將其傳遞給LTO。foo2()一旦可以刪除,意味著發(fā)現(xiàn)foo1()里面調(diào)用foo3()的判斷始終為假,也就是foo3()也沒有使用,也可以刪除
  • 符號處理完畢后,將處理結(jié)果傳遞給優(yōu)化器和代碼生成器,同時將a.o合并到main.o
  • 修改main.o的符號表信息。繼續(xù)鏈接,生成可執(zhí)行文件

使用objdump --macho --syms main命令,查看最后生成的可執(zhí)行文件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

可以看到,鏈接完成之后,我們自己聲明的函數(shù)只剩下:main、foo1foo4

這個地方有個問題,foo4函數(shù)并沒有在任何地方使用,為什么沒有把它干掉?

因?yàn)?code>LTO優(yōu)化以入口文件需要的符號為準(zhǔn),來向外進(jìn)行解析優(yōu)化。所以,要優(yōu)化掉foo4,那么就需要使用一個新的功能Dead Code Stripping

Dead Code Stripping

鏈接器的-dead_strip參數(shù)的作用:

簡單來講,就是移除入口函數(shù)或者沒有被導(dǎo)出符號使用到的函數(shù)或者代碼。上述案例中的foo4正是符合這種情況,所以,通過-dead_strip可以刪除掉無用代碼

創(chuàng)建動態(tài)庫時,可以使用-mark_dead_strippable_dylib

指明,如果并沒有使用到該動態(tài)庫的符號信息,那么鏈接器將會自動優(yōu)化該動態(tài)庫。不會因?yàn)槁窂絾栴}崩潰

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

Xcode Build Setting中的設(shè)置:

設(shè)置Dead Code Stripping,它會在鏈接過程中進(jìn)行優(yōu)化

  • YES:刪除掉無用代碼
  • NO:不開啟此項(xiàng)優(yōu)化
Code Generation Options

Xcode Build Setting中的設(shè)置:

設(shè)置Optimization Level(編譯器的優(yōu)化程度),它會在編譯時生成目標(biāo)文件時進(jìn)行優(yōu)化

  • None [-O0]:不優(yōu)化。
    在這種設(shè)置下, 編譯器的目標(biāo)是降低編譯消耗,保證調(diào)試時輸出期望的結(jié)果。程序的語句之間是獨(dú)立的:如果在程序的停在某一行的斷點(diǎn)出,我們可以給任何變量賦新值抑或是將程序計(jì)數(shù)器指向方法中的任何一個語句,并且能得到一個和源碼完全一致的運(yùn)行結(jié)果。

  • Fast [-O1]:大函數(shù)所需的編譯時間和內(nèi)存消耗都會稍微增加。
    在這種設(shè)置下,編譯器會嘗試減小代碼文件的大小,減少執(zhí)行時間,但并不執(zhí)行需要大量編譯時間的優(yōu)化。在蘋果的編譯器中,在優(yōu)化過程中,嚴(yán)格別名,塊重排和塊間的調(diào)度都會被默認(rèn)禁止掉。此優(yōu)化級別提供了良好的調(diào)試體驗(yàn),堆棧使用率也提高,并且代碼質(zhì)量優(yōu)于None [-O0]。

  • Faster [-O2]:編譯器執(zhí)行所有不涉及時間空間交換的所有的支持的優(yōu)化選項(xiàng)。
    是更高的性能優(yōu)化Fast [-O1]。在這種設(shè)置下,編譯器不會進(jìn)行循環(huán)展開、函數(shù)內(nèi)聯(lián)或寄存器重命名。和Fast [-O1]項(xiàng)相比,此設(shè)置會增加編譯時間,降低調(diào)試體驗(yàn),并可能導(dǎo)致代碼大小增加,但是會提高生成代碼的性能。

  • Fastest [-O3]:在開啟Fast [-O1]項(xiàng)支持的所有優(yōu)化項(xiàng)的同時,開啟函數(shù)內(nèi)聯(lián)和寄存器重命名選項(xiàng)。
    是更高的性能優(yōu)化Faster [-O2],指示編譯器優(yōu)化所生成代碼的性能,而忽略所生成代碼的大小,有可能會導(dǎo)致二進(jìn)制文件變大。還會降低調(diào)試體驗(yàn)。

  • Fastest, Smallest [-Os]:在不顯著增加代碼大小的情況下盡量提供高性能。
    這個設(shè)置開啟了Fast [-O1]項(xiàng)中的所有不增加代碼大小的優(yōu)化選項(xiàng),并會進(jìn)一步的執(zhí)行可以減小代碼大小的優(yōu)化。增加的代碼大小小于Fastest [-O3]。與Fast [-O1]相比,它還會降低調(diào)試體驗(yàn)。

  • Fastest, Aggressive Optimizations [-Ofast]:與Fastest, Smallest [-Os]相比該級別還執(zhí)行其他更激進(jìn)的優(yōu)化。
    這個設(shè)置開啟了Fastest [-O3]中的所有優(yōu)化選項(xiàng),同時也開啟了可能會打破嚴(yán)格編譯標(biāo)準(zhǔn)的積極優(yōu)化,但并不會影響運(yùn)行良好的代碼。該級別會降低調(diào)試體驗(yàn),并可能導(dǎo)致代碼大小增加。

  • Smallest, Aggressive Size Optimizations [-Oz]:不使用LTO的情況下減小代碼大小。
    -Os相似,指示編譯器僅針對代碼大小進(jìn)行優(yōu)化,而忽略性能優(yōu)化,這可能會導(dǎo)致代碼變慢。

strip

strip:移除指定符號。在Xcode中默認(rèn)strip是在Archive的時候才會生效,移除對應(yīng)符號

strip命令的使用:

  • strip -x:除了全局符號都可以移除 (動態(tài)庫使用)
  • strip -S:移除調(diào)試符號(靜態(tài)庫使用)
  • strip:除了間接符號表中使用的符號,其他符號都移除(上架App使用)

Xcode Build Setting中的設(shè)置:

設(shè)置Deployment Postprocessing

  • 打開后在編譯階段就會運(yùn)行strip

設(shè)置Strip Debug Symbols During Copy

  • 當(dāng)應(yīng)用在編譯階段copy了某些二進(jìn)制文件時,打開該選項(xiàng)會脫掉該二進(jìn)制的調(diào)試符號。但是不會脫去鏈接的最終產(chǎn)物(可執(zhí)行文件\動態(tài)庫)的符號信息。要脫去鏈接的產(chǎn)物(App的可執(zhí)行文件)的符號信息

設(shè)置Strip Linked Product

  • 如果沒有打開Deployment Postprocessing,則會在Archive處理鏈接的最終產(chǎn)物(可執(zhí)行文件)的符號信息。否則,在鏈接完成之后就會處理符號信息

設(shè)置Strip Style(符號剝離級別),它會在生成可執(zhí)行文件后進(jìn)行優(yōu)化,相當(dāng)于對Mach-O文件進(jìn)行修改

  • All Symbols:除了間接符號表中使用的符號,其他符號都移除(上架App使用)
  • Non-Global Symbols:除了全局符號都可以移除 (動態(tài)庫使用)
  • Debugging Symbols:移除調(diào)試符號(靜態(tài)庫使用)

Strip Style原理

  • App:可以剝離除間接符號表以外的所有符號
  • 動態(tài)庫:可以剝離除全局符號以外的所有符號
  • 靜態(tài)庫:靜態(tài)庫是.o文件的合集,符號都存儲在重定位符號表中。靜態(tài)庫只能剝離調(diào)試符號

Debugging Symbols:剝離.o/靜態(tài)庫的調(diào)試符號

Debugging Symbols:剝離動態(tài)庫/可執(zhí)行文件的調(diào)試符號

All Symbols:剝離除間接符號表以外的所有符號

Non-Global Symbols:剝離除全局符號以外的所有符號

在LLVM項(xiàng)目中調(diào)試strip命令
添加llvm-strip

打開LLVM項(xiàng)目,打開Edit Scheme...彈窗

選擇Manage Schemes...

點(diǎn)擊+添加

Target選擇llvm-strip,填寫Name后點(diǎn)擊OK

此時llvm-strip添加成功,點(diǎn)擊Close關(guān)閉彈窗

創(chuàng)建替身

TAEGETS中搜索strip關(guān)鍵字,點(diǎn)擊llvm-strip,選擇Build Phases

  • llvm-strip是一個腳本,無法調(diào)試
  • 腳本作用:判斷CONFIGURATION如果是Debug,將llvm-objcopy可執(zhí)行文件,鏈接成llvm-strip可執(zhí)行文件,相當(dāng)于生成快捷方式

選擇llvm-strip,先運(yùn)行一次腳本

TAEGETS中搜索objcopy關(guān)鍵字,右鍵llvm-objcopy,菜單選擇Duplicate

TAEGETS中多出一個llvm-objcopy-copy

將其重命名為strip

打開鏈接后的目錄/Volumes/study/Source/llvm/llvm-project/build/Debug/bin,找到llvm-strip可執(zhí)行文件

將其復(fù)制,重命名為strip

回到項(xiàng)目,在Products目錄中找到strip,右鍵strip,菜單選擇Show in Finder,此時它自動跟Debug/bin目錄中的strip可執(zhí)行文件關(guān)聯(lián)到一起

添加strip

打開LLVM項(xiàng)目,打開Edit Scheme...彈窗

選擇Manage Schemes...

點(diǎn)擊+添加

Target選擇strip,填寫Name后點(diǎn)擊OK

此時strip添加成功,點(diǎn)擊Close關(guān)閉彈窗

添加默認(rèn)參數(shù)

打開Edit Scheme...彈窗,左上角選擇strip,左側(cè)選擇Run,右側(cè)選擇Arguments

Arguments Passed On Launch項(xiàng)中,點(diǎn)擊+添加參數(shù)Mach-O路徑

  • ?參數(shù): 代表全部符號
  • -x: non_global
  • -S: 調(diào)試符號

參數(shù)添加成功,點(diǎn)擊Close關(guān)閉彈窗

設(shè)置斷點(diǎn)

TAEGETS中搜索strip關(guān)鍵字,點(diǎn)擊strip,選擇Build Phases

展開Compile Sources項(xiàng),右鍵llvm-nm.cpp文件,菜單選擇Reveal In Project Navigator,將文件顯示在左側(cè)

打開llvm-nm.cpp代碼,找到main函數(shù)并設(shè)置好斷點(diǎn)

運(yùn)?程序

選擇strip,但不要直接運(yùn)行,因?yàn)榫幾g會非常耗時

使用Run Without Building運(yùn)?程序;第?次運(yùn)?時,需要進(jìn)?編譯,以重新?成調(diào)試符號。再次運(yùn)?,當(dāng)代碼沒有改變,則不需要重新編譯,直接運(yùn)?現(xiàn)有可執(zhí)??件

順利進(jìn)入斷點(diǎn),通過下標(biāo)訪問argv,之前設(shè)置的默認(rèn)參數(shù)已傳入main函數(shù)

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.
基礎(chǔ)介紹

從文件中導(dǎo)入斷點(diǎn)

br read -f 【文件路徑】

將斷點(diǎn)導(dǎo)出到文件

br write -f 【文件路徑】

查看組內(nèi)的斷點(diǎn)列表

br list 【組名稱】

啟用組中的斷點(diǎn)

br enable 【組名稱】

禁用組中的斷點(diǎn)

br disable 【組名稱】

刪除組中的斷點(diǎn)

br delete 【組名稱】

通過文件和行號設(shè)置斷點(diǎn)

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

通過函數(shù)名稱設(shè)置斷點(diǎn),也能為classselector設(shè)置斷點(diǎn)

br set -n 【函數(shù)名稱】
br set -n -[NSString stringWithFormat:]

C++函數(shù)設(shè)置斷點(diǎn)

br set -M 【函數(shù)名稱】

OCselector設(shè)置斷點(diǎn)

br set -S 【selector】

為指定文件里的selector設(shè)置斷點(diǎn)

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

為某個image設(shè)置斷點(diǎn)

br set -s 【image名稱】 -n 【函數(shù)名稱】

為項(xiàng)目中包含關(guān)鍵字的方法設(shè)置斷點(diǎn)

br set -r 【關(guān)鍵字】
從文件中導(dǎo)入斷點(diǎn)

打開strip_lldb.m文件,斷點(diǎn)如下:

[
   {"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命令的關(guān)鍵點(diǎn)

使用br read -f /Users/zang/Zang/Spark/strip_lldb.m命令,將strip_lldb.m文件中的斷點(diǎn)導(dǎo)入項(xiàng)目

  • 導(dǎo)入的斷點(diǎn)默認(rèn)是沒有被啟用的

使用br enable strip命令,將strip組中的斷點(diǎn)全部啟用

此時斷點(diǎn)全部啟用;需要注意的是:br命令只對本次運(yùn)行有效,當(dāng)項(xiàng)目重新運(yùn)行后,需要再次導(dǎo)入并啟用斷點(diǎn)

將斷點(diǎn)導(dǎo)出到文件

main函數(shù)的斷點(diǎn)為例

直接使用Xcode設(shè)置斷點(diǎn)并導(dǎo)出,文件中FileName會記錄斷點(diǎn)所在文件的絕對路徑,這會造成導(dǎo)出的斷點(diǎn)文件不具通用性

使用br命令設(shè)置斷點(diǎn),可以解決通用性問題

使用br set -f llvm-objcopy.cpp -l 346 -N cat命令,對llvm-objcopy.cpp文件的第346行設(shè)置斷點(diǎn),加入到cat分組

使用br set -n removeSections -f MachOObjcopy.cpp -N cat命令,對llvm-objcopy.cpp文件的removeSections函數(shù)設(shè)置斷點(diǎn),加入到cat分組

使用br list cat命令,查看cat分組下的斷點(diǎn)

使用br write -f /Users/zang/Zang/Spark/cat.m命令,將斷點(diǎn)導(dǎo)出到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提供準(zhǔn)確的App大小

方式二:

通過Xcode內(nèi)置報告工具,創(chuàng)建App尺寸報告

  • Archive App
  • 通過Ad Hoc、Development或者Enterprise等分發(fā)方式導(dǎo)出Archive App
  • 在設(shè)置開發(fā)分發(fā)選項(xiàng)的列表中,選擇All compatible device variants以進(jìn)行應(yīng)用程序精簡,然后啟用Rebuild from Bitcode
  • 簽名并且導(dǎo)出

此過程將創(chuàng)建一個包含App的文件夾,里面有:

  • Universal IPA,包含多個平臺的資源文件和二進(jìn)制程序
  • Thinned IPA,指定平臺的資源文件和二進(jìn)制程序

同時還包含一個App Thinning Size Report.txt,里面詳細(xì)記錄了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
總結(jié):
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容