link與Symbol

Mach-O

Mach-O(Mach Object)是macOS梨水、iOS旋奢、iPadOS存儲程序和庫的文件格式迄薄。對應(yīng)系統(tǒng)通過應(yīng)用二進(jìn)制接口(application binary interface谍肤,縮寫為 ABI)來運行該格式的文件啦租。
Mach-O格式用來替代BSD系統(tǒng)的a.out格式。Mach-O文件格式保存了在編譯過程和鏈接過程中產(chǎn)生的機器代碼和數(shù)據(jù)荒揣,從而為靜態(tài)鏈接和動態(tài)鏈接的代碼提供了單一文件格式篷角。

可執(zhí)行文件的調(diào)用過程:

  1. 調(diào)用fork函數(shù),創(chuàng)建一個process(進(jìn)程)系任;
  2. 調(diào)用execve或其衍生函數(shù)恳蹲,在該進(jìn)程上加載落追,執(zhí)行Mach-O文件预皇。
    當(dāng)調(diào)用execve(程序加載器) 時,內(nèi)核實際上在執(zhí)行以下操作:
    i.將文件加載到內(nèi)存金抡;
    ii.開始分析Mach-O中的mach_header举农,以確認(rèn)它是有效的Mach-O文件荆针。

Mach-o File Format

一個Mach-o文件有兩部分組成:headerdata

image.png

一個簡單的main函數(shù)例子:

int main(int argc, const char * argv[]) {
    return 0;
}

編譯好后查看下__TEXT 段:
objdump --macho -d ${MACHO_PATH}
main函數(shù)對應(yīng)的機器碼如下颁糟,后面的匯編是給我們閱讀的航背,機器并不需要,可以理解為注釋棱貌。

image.png

'int main() { }' compiled for x86_64-apple-macosx with clang
0x55,                         // offset 0 -- pushq %rbp
0x48, 0x89, 0xe5,  // offset 1 -- movq %rsp, %rbp
0x31, 0xc0,             // offset 4 -- xorl %eax, %eax
0x5d,                       // offset 6 -- popq %rbp
0xc3                        // offset 7 -- retq

這里的機器碼是固定的玖媚,也就是所謂的ABI穩(wěn)定。

Big endian or little endian

iOSmacOS開發(fā)都是小端模式婚脱。

image.png

當(dāng)讀取一個地址的時候

  • 小端:從右往左讀
  • 大端:從左往右讀

header

代表了文件的映射今魔,描述了文件的內(nèi)容以及文件所有內(nèi)容所在的位置勺像。header內(nèi)的section描述了對應(yīng)的二進(jìn)制信息。
header包含三種類型:

  • Mach header
  • segment
  • sections

Mach header

??:Mach header屬于header的一部分错森,它包含了整個文件的信息和segment信息吟宦。

image.png

直接新建一個工程看下Mach header

objdump --macho -private-header ${MACHO_PATH}
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64  X86_64        ALL  0x00     EXECUTE    19       1944   NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK PIE

也可以通過otool查看:

otool -h ${MACHO_PATH}
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x00           2    20       1960 0x00218085

查看mach header

  1. objdump --macho -private-header ${MACHO_PATH}
  2. otool -h ${MACHO_PATH}

相對來說objdump更容易閱讀,otool更接近原始數(shù)據(jù)涩维。

data

緊跟header之后殃姓,由多個二進(jìn)制組成,one by one瓦阐。

Load Commands

二進(jìn)制文件加載進(jìn)內(nèi)存要執(zhí)行的一些指令蜗侈。
這里的指令主要在負(fù)責(zé)我們 APP 對應(yīng)進(jìn)程的創(chuàng)建和基本設(shè)置(分配虛擬內(nèi)存,創(chuàng)建主線程睡蟋,處理代碼簽名/加密的工作)踏幻,然后對動態(tài)鏈接庫(.dylib 系統(tǒng)庫和我們自己創(chuàng)建的動態(tài)庫)進(jìn)行庫加載和符號解析的工作。

直接在.app目錄下dumpobjdump --macho --private-headers)一下可執(zhí)行文件:

?  TestMutableConfig.app objdump --macho --private-headers TestMutableConfig

可以看到load command信息如下:

image.png

都知道程序主入口是main函數(shù)薄湿,看下對應(yīng)的main信息:

objdump --macho --private-headers TestMutableConfig | grep 'LC_MAIN' -A 3

       cmd LC_MAIN
   cmdsize 24
  entryoff 7856
 stacksize 0

dyld通過LC_MAIN去找程序的主入口叫倍。當(dāng)然可以自己指定不為main
可以理解為Mach-O就是:文件配置+二進(jìn)制代碼

Mach-O 是可讀可寫的

上面通過objdump輸出的信息有點多豺瘤,如果只想看自己關(guān)注的信息吆倦,可以精簡下寫一個自定義讀取程序machoInfo
原理是根據(jù)Mach-O格式讀取數(shù)據(jù)
1:直接終端命令查看:

image.png

2:直接main函數(shù)傳參進(jìn)去:
image.png

image.png

machoInfo地址:machoInfo

目標(biāo)文件生成過程:


image.png
  • 鏈接器(llvm-ld)并沒有被執(zhí)行。
  • 目標(biāo)文件不會包含Unix程序在被裝載和執(zhí)行時所必須包含的信息坐求。
    修改main.m文件:
int global_uninit_value;

// 外部符號
int global_init_value = 10;

double default_x __attribute__((visibility("hidden"))) ;

static int static_init_value = 9;

static int static_uninit_value;

int main(int argc, const char * argv[]) {
    static_uninit_value = 10;
    NSLog(@"%d", static_init_value);

    return 0;
}

對應(yīng)的代碼段:

(__TEXT,__text) section
_main:
100003f50:  55  pushq   %rbp
100003f51:  48 89 e5    movq    %rsp, %rbp
100003f54:  48 83 ec 10 subq    $16, %rsp
100003f58:  48 8d 05 a9 00 00 00    leaq    169(%rip), %rax ## Objc cfstring ref: @"%d"
100003f5f:  c7 45 fc 00 00 00 00    movl    $0, -4(%rbp)
100003f66:  89 7d f8    movl    %edi, -8(%rbp)
100003f69:  48 89 75 f0 movq    %rsi, -16(%rbp)
100003f6d:  c7 05 a1 40 00 00 0a 00 00 00   movl    $10, _static_init_value(%rip)
100003f77:  8b 35 97 40 00 00   movl    _static_init_value(%rip), %esi
100003f7d:  48 89 c7    movq    %rax, %rdi
100003f80:  b0 00   movb    $0, %al
100003f82:  e8 09 00 00 00  callq   0x100003f90 ## symbol stub for: _NSLog
100003f87:  31 c0   xorl    %eax, %eax
100003f89:  48 83 c4 10 addq    $16, %rsp
100003f8d:  5d  popq    %rbp
100003f8e:  c3  retq

在生成.o的過程中:

  1. 能變成匯編的代碼盡量變成匯編蚕泽。
  2. 符號歸類 -> 重定位符號表(放的是.m/.o文件用到的API)。也就是保存了當(dāng)前文件用到的符號桥嗤。
  3. .o -> 鏈接 -> 合并到一張表 -> exec(可執(zhí)行文件)

查看重定位符號表
objdump --macho -reloc test.o(目標(biāo)文件)

image.png

鏈接

鏈接的本質(zhì)就是把多個目標(biāo)文件組合成一個文件
將多個.o文件合并成一個可執(zhí)行文件须妻,在鏈接的過程中可以進(jìn)行操作。
可以通過man ld查看都有哪些功能泛领,比如:why-live荒吏、why-load等。

Symbol

  • Symbol Table: 用來保存符號渊鞋。
  • String Table: 用來保存符號的名稱绰更。
  • Indirect Symbol Table: 間接符號表。保存使用的外部符號锡宋,更準(zhǔn)確一點就是使用外部動態(tài)庫的符號儡湾。是Symbol Table的子集。

符號的種類

為了方便操作执俩,直接在Xcode Run script中添加命令編譯后直接將結(jié)果輸出到終端:
Run Script

#輸出 "HotpotCat" 重定位到 終端 /dev/ttys003
echo "HotpotCat" > /dev/ttys003

其中/dev/ttys003為終端徐钠,可以在終端中輸入tty查看獲取。

image.png

Run Script也可以讀取XCConfig中的內(nèi)容役首〕⒇ぃ可以配合起來完成显拜。所以我們可以分為3步:配置讀取符號腳本,XCConfig傳遞參數(shù)摊崭,Run sctipt 執(zhí)行腳本

1.xcode_run_cmd.sh

定義一個xcode_run_cmd腳本讼油,將符號輸出到終端杰赛,這個腳本需要2個參數(shù)呢簸,參數(shù)從XCConfig配置。

#!/bin/sh

RunCommand() {
  #判斷全局字符串VERBOSE_SCRIPT_LOGGING是否為空乏屯。-n string判斷字符串是否非空
  #[[是 bash 程序語言的關(guān)鍵字根时。用于判斷
  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
    #作為一個字符串輸出所有參數(shù)。使用時加引號"$*" 會將所有的參數(shù)作為一個整體辰晕,以"$1 $2 … $n"的形式輸出所有參數(shù)
      if [[ -n "$TTY" ]]; then
          echo "? $@" 1>$TTY
      else
          echo "? $*"
      fi
      echo "------------------------------------------------------------------------------" 1>$TTY
  fi
  #與$*相同蛤迎。但是使用時加引號,并在引號中返回每個參數(shù)含友。"$@" 會將各個參數(shù)分開替裆,以"$1" "$2" … "$n" 的形式輸出所有參數(shù)
  if [[ -n "$TTY" ]]; then
        eval "$@" &>$TTY
  else
      "$@"
  fi
  #顯示最后命令的退出狀態(tài)。0表示沒有錯誤窘问,其他任何值表明有錯誤辆童。
  return $?
}

EchoError() {
    #在shell腳本中,默認(rèn)情況下惠赫,總是有三個文件處于打開狀態(tài)把鉴,標(biāo)準(zhǔn)輸入(鍵盤輸入)、標(biāo)準(zhǔn)輸出(輸出到屏幕)儿咱、標(biāo)準(zhǔn)錯誤(也是輸出到屏幕)庭砍,它們分別對應(yīng)的文件描述符是0,1混埠,2
    # >  默認(rèn)為標(biāo)準(zhǔn)輸出重定向怠缸,與 1> 相同
    # 2>&1  意思是把 標(biāo)準(zhǔn)錯誤輸出 重定向到 標(biāo)準(zhǔn)輸出.
    # &>file  意思是把標(biāo)準(zhǔn)輸出 和 標(biāo)準(zhǔn)錯誤輸出 都重定向到文件file中
    # 1>&2 將標(biāo)準(zhǔn)輸出重定向到標(biāo)準(zhǔn)錯誤輸出。實際上就是打印所有參數(shù)已標(biāo)準(zhǔn)錯誤格式
    if [[ -n "$TTY" ]]; then
        echo "$@" 1>&2>$TTY
    else
        echo "$@" 1>&2
    fi
    
}

RunCMDToTTY() {
    # CMD = 運行到命令
    # TTY = 終端
    if [[ ! -e "$TTY" ]]; then
        EchoError "=========================================="
        EchoError "ERROR: Not Config tty to output."
        exit -1
    fi
    if [[ -n "$CMD" ]]; then
        RunCommand $CMD
    else
        EchoError "=========================================="
        EchoError "ERROR:Failed to run CMD. THE CMD must not null"
    fi
}


RunCMDToTTY

2.XCConfig配置:

由于腳本中有3個參數(shù)钳宪,可以直接在XCConfig中配置揭北,腳本中就可以讀取到了。因為腳本是在Run strip執(zhí)行的使套,并且和工程在同一目錄罐呼。

// BUILD_DIR build路徑 *為可執(zhí)行文件名稱,這里為了適配寫為*通配符
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/*
//# CMD = 運行的命令
//# CMD_FLAG = 運行的命令參數(shù)
//# TTY = 終端
CMD = nm
// -p: 不排序
// -a: 顯示所有符號侦高,包含調(diào)試符號
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000

BUILD_DIR:

image.png

由于不需要調(diào)試符號嫉柴,可以脫去(strip)調(diào)試符號
我們知道build setting中有個strip設(shè)置:
Deployment PostprocessingStrip Style:
image.png

image.png

可以看到Strip是在Run Script之后執(zhí)行的。所以在build setting中設(shè)置行不通奉呛。
那么直接man ld查看下有沒有可用的命令:
image.png

可以看到有個S比較符合计螺。XCConfig修改完后配置如下:

// BUILD_DIR build路徑 *為可執(zhí)行文件名稱夯尽,這里為了適配寫為*通配符
MACHO_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/*
//# CMD = 運行的命令
//# CMD_FLAG = 運行的命令參數(shù)
//# TTY = 終端
CMD = nm
// -p: 不排序
// -a: 顯示所有符號,包含調(diào)試符號
CMD_FLAG = -pa ${MACHO_PATH}
TTY = /dev/ttys000
//-Xlinker 雖然執(zhí)行的ld登馒,但是命令傳給連接器
OTHER_LDFLAGS=-Xlinker -S

3.Run script執(zhí)行腳本

# SRCROOT 當(dāng)前代碼路徑
/bin/sh "$SRCROOT/xcode_run_cmd.sh"

至此匙握,環(huán)境已經(jīng)配置好了。

接下來配置一些符號看下:

//全局變量
int global_uninit_value;

// 外部符號
int global_init_value = 10;

double default_x __attribute__((visibility("hidden"))) ;
//靜態(tài)變量
static int static_init_value = 9;
static int static_uninit_value;

int main(int argc, const char * argv[]) {
    static_uninit_value = 10;
    NSLog(@"%d", static_init_value);

    return 0;
}

objdump --macho --syms ${MACHO_PATH}查看下:

image.png

可以看到有好多Debug符號陈轿,在生成文件的時候會生成DWARF調(diào)試文件圈纺,放在.o專門的DWARF段,當(dāng)連接的時候會放入符號表中麦射。也就是說調(diào)試符號在目標(biāo)文件中是放在DWARF段中的蛾娶,當(dāng)鏈接后放在符號表中。去掉這些干擾信息潜秋,有兩種方式:

  1. xcconfig中配置OTHER_LDFLAGS=$(inherited) -Xlinker -S
  2. xcode中配置蛔琅。
    image.png
  • 從上面的分析可以看出,不論初始化的還是未初始化的全局變量都是全局符號峻呛。
  • 靜態(tài)變量都變成了本地符號罗售。

全局和本地本質(zhì)上就是可見性。
全局變量和靜態(tài)變量區(qū)別
當(dāng)將一個全局變量定義為靜態(tài)變量后就變成了本地變量钩述,從全局可見變成了文件可見寨躁。 (靜態(tài)變量 -> 本地變量)。

按功能劃分:

Type 說明 備注
f File 文件
F Function 方法
O Data 數(shù)據(jù)
d Debug 調(diào)試符號
ABS Absolute
COM Common
UND ? 未定義符號

符號可見性

符號可見性在clang下有兩種hiddendefault(默認(rèn))切距。protectedclang下是沒有的朽缎。

  • default:用它定義的符號將被導(dǎo)出。
  • hidden:用它定義的符號將不被導(dǎo)出谜悟。

__attribute__可以把編譯器支持的參數(shù)傳遞給編譯器话肖。我們經(jīng)常遇到就是__attribute__((deprecated))
visibility控制文件導(dǎo)出符號葡幸,限制符號可見性最筒。

int hidden_y __attribute__((visibility("hidden"))) = 99;
double default_y __attribute__((visibility("default"))) = 100;

在上面例子中default_x通過hidden變成了l本地符號,放在__DATA__common未初始化區(qū)蔚叨。
所以可以通過:

  1. static
  2. __attribute__((visibility("hidden")))

控制符號可見性床蜘。

two_levelnamespace & flat_namespace

二級命名空間與一級命名空間。鏈接器默認(rèn)采用二級命名空間蔑水,也就是除了會記錄符號 名稱邢锯,還會記錄符號屬于哪個Mach-O的,比如會記錄下來_NSLog來自Foundation搀别。
所以如果我們在自己主工程中有一個函數(shù)丹擎,另外一個framework中也有同名函數(shù):

void global_object() {
    NSLog(@"global_object");
}

在調(diào)用過程中由于存在二級命名空間并不會產(chǎn)生沖突。如果修改為一級命名空間就會沖突。

導(dǎo)入導(dǎo)出符號

export symbol:導(dǎo)出符號意味著蒂培,告訴別的模塊再愈,我有一個這樣的符號,你可以將 其導(dǎo)入(Import)护戳。
導(dǎo)出導(dǎo)入是相對的翎冲。比如NSLog對于Foundation來說是導(dǎo)出符號,對于調(diào)用方而言是導(dǎo)入符號媳荒。導(dǎo)出符號一定是全局符號抗悍。
查看導(dǎo)出符號:
objdump --macho --exports-trie ${MACHO_PATH}

Exports trie:
0x100000000  __mh_execute_header
0x100003F50  _main
0x100008010  _global_init_value
0x100008020  _global_uninit_value

可以看到就是上面例子中的全局符號。全局變量默認(rèn)會被導(dǎo)出肺樟。
對于動態(tài)庫而言是在運行的過程中被加載檐春,在編譯連接階段只要提供符號就可以了。這里就涉及到間接符號表(保存著可執(zhí)行文件使用的其它動態(tài)庫的符號)了么伯。
查看間接符號表:
objdump --macho --indirect-symbols ${MACHO_PATH}

image.png

  • 全局符號可以變成導(dǎo)出符號。

由于符號影響macho的大小卡儒,而間接符號不能刪除田柔。也就意味著使用到的動態(tài)庫的全局符號不能刪除。所以在strip動態(tài)庫的時候只能脫不是全局符號的符號骨望。
間接符號->動態(tài)庫的導(dǎo)出符號->全局符號->strip只能拖不是全局符號的符號
定義一個OC類:

@interface OCObject ()
- (void)testOCObject;
@end

@implementation OCObject
- (void)testOCObject {
    NSLog(@"testOCObject");
}
@end

查看一下導(dǎo)出符號:

Exports trie:
0x100000000  __mh_execute_header
0x100003F20  _main
0x1000080B8  _OBJC_METACLASS_$_OCObject
0x1000080E0  _OBJC_CLASS_$_OCObject
0x100008110  _global_init_value
0x100008120  _global_uninit_value

可以看到OC的類硬爆,默認(rèn)就是導(dǎo)出符號。所以對于OC的動態(tài)庫要控制體積擎鸠,對于不想暴露的符號可以借助鏈接器設(shè)置-unexported_symbol 后面跟不想導(dǎo)出的符號:
OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_OCObject

image.png

可以看到已經(jīng)沒有了_OBJC_CLASS_$_OCObject導(dǎo)出符號了缀磕。這個時候就可以strip掉了。對于外界也不可見了劣光。

weak symbol

Weak Reference Symbol: 表示此未定義符號是弱引用袜蚕。如果動態(tài)鏈接器找不到該符號的定義,則將其設(shè)置為0绢涡。鏈接器會將此符號設(shè)置弱鏈接標(biāo)志牲剃。
Weak defintion Symbol: 表示此符號為弱定義符號。如果靜態(tài)鏈接器或動態(tài)鏈接器為此符號找到另一個(非弱)定義雄可,則弱定義將被忽略凿傅。只能將合并部分中的符號標(biāo)記為弱定義。

定義WeakSymbol.h:

// 弱定義
void weak_function(void)  __attribute__((weak));
//弱引用
void weak_import_function(void) __attribute__((weak_import));

實現(xiàn)WeakSymbol.m:

void weak_function(void) {
    NSLog(@"weak_function");
}

void weak_import_function(void) {
    NSLog(@"weak_import_function");
}

image.png

如果在main文件中再實現(xiàn)一個weak_function

void weak_function(void) {
    NSLog(@"main weak_function");
}

這個時候正常情況下應(yīng)該報符號沖突数苫,弱定義后并不會沖突聪舒。


image.png
  • 弱定義符號并不影響符號的全局和導(dǎo)出。
  • 聲明成弱定義的符號找到一個符號后虐急,其它的會被忽略箱残。

若把一個弱定義的全局符號隱藏掉,則會變成弱定義的本地符號戏仓。

void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
image.png

弱引用
如果沒有實現(xiàn)則不會報錯疚宇,調(diào)用的地方做判斷即可亡鼠。

    if (weak_import_function) {
        weak_import_function();
    }

在編譯的時候需要告訴編譯器不要管-U,這個符號是動態(tài)實現(xiàn)的敷待。

-U symbol_name
Specified that it is ok for symbol_name to have no definition. With -two_levelnamespace,the resulting symbol will be marked dynamic_lookup which means dyld will search all loaded images.

OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function
這個時候即使weak_import_function不實現(xiàn)间涵,編譯和運行也都不會報錯。
在這里如果我們將動態(tài)庫全部聲明為弱引用榜揖,則就不會報找不到符號的錯誤了勾哩。

重新導(dǎo)出符號

比如NSLog對于我們來說是導(dǎo)入符號,對于可執(zhí)行文件是一個未定義符號(存在間接符號表中)举哟。

image.png

這里的NSLog只能在當(dāng)前文件中使用思劳,如果想給其它文件使用就需要重新導(dǎo)出。重新導(dǎo)出后就放在導(dǎo)出符號表中妨猩,外部就可以使用了潜叛。可以通過給一個符號起別名(只能給間接符號表中的符號起別名壶硅,起別名后會自動把間接符號表中的符號變成導(dǎo)出符號)的方式重新導(dǎo)出符號威兜。

OTHER_LDFLAGS=$(inherited) -Xlinker -alias  -Xlinker _NSLog -Xlinker HOTPOT_NSLog

image.png

通過易于閱讀的nm方式輸出下:

nm -m ${MACHO_PATH} | grep 'HOTPOT_NSLog'

image.png

可以看到是間接的外部的_NSLog別名的符號。
接著看下導(dǎo)出符號表的情況

objdump --macho --exports-trie ${MACHO_PATH}

image.png

可以看到HOTPOT_NSLog是一個re-export的導(dǎo)出符號庐椒。所以我們已通過將自己庫依賴的動態(tài)庫重新導(dǎo)出讓動態(tài)庫可見椒舵。一般用在自己的庫依賴其它動態(tài)庫,讓其它動態(tài)庫可見(可以是庫也可以是符號)约谈。

-reexported_symbols_list file
The specified filename contains a list of symbol names that are implemented in a dependent dylib and should be re-exported through the dylib being created.
-unexported_symbol symbol
The specified symbol is added to the list of global symbols names that will not remain as global symbols in the output file. This option can be used multiple times. For short lists, this can be more convenient than creating a file and using -unexported_symbols_list.
reexported_symbols_list后面跟文件笔宿,可以將需要重新導(dǎo)出的符號寫入到文件中。也可以通過unexported_symbol隱藏起來棱诱。

swift symbol

internal enum SwiftEnumSymbol {
    case maxHeap
    case minHeap

    internal func testSwiftEnumSymbol<T: Comparable>(type: T.Type) -> (T, T) -> Bool {
        switch self {
        case .maxHeap:
            return (>)
        case .minHeap:
            return (<)
        }
    }
}

struct SwiftStructSymbol {
    func testSwiftStructSymbol(o: Int) {

    }
}

public protocol SwiftProtocolSymbol: class {
    func testSwiftProtocolSymbol()
}

public class SwiftClassSymbol {

    func testSwiftClassSymbol() {

    }
}

查看全部符號

objdump --macho --syms ${MACHO_PATH}

可以看到添加swift文件后多了很多符號:

image.png

只查看SwiftClassSymbol的符號:

objdump --macho --syms ${MACHO_PATH} | grep 'SwiftClassSymbol'
image.png

私有化SwiftClassSymbol

private class SwiftClassSymbol {

    func testSwiftClassSymbol() {

    }
}
image.png

可以看到都變成了local泼橘。所以swift是編譯型語言,編譯過程中就已經(jīng)確定了符號的類型军俊。

strip

  • 動態(tài)庫不能脫全局符號侥加。
  • App導(dǎo)出符號一般不提供給別人使用,對于間接符號(也就是導(dǎo)入符號)不能脫粪躬, 其它的都可以脫(本地+全局+弱定義都可以脫担败,只留下間接符號表中的符號)。
  • 靜態(tài)庫是.o文件的合集 + 重定位符號表镰官。所以重定位符號不能脫提前,唯一能脫的就是調(diào)試符號。

strip style

  • Debugging Symbols (.o 靜態(tài)庫 / 可執(zhí)行文件 動態(tài)庫)
  • All Symbols(App)
  • Non-Global Symbols(動態(tài)庫)

只符號表來說App使用靜態(tài)庫比使用動態(tài)庫文件大小要小泳唠。由于靜態(tài)庫最終會合并到macho狈网,對于App來說會脫去所有符號,只留下間接符號。單純只說靜態(tài)庫和動態(tài)庫拓哺,那么靜態(tài)庫相對較小勇垛。引入App后靜態(tài)庫小。(僅僅從符號的角度考慮)士鸥。
對于動態(tài)庫可以考慮從導(dǎo)出符號優(yōu)化體積闲孤,對于OC默認(rèn)就是導(dǎo)出符號。

strip命令:

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

image.png

dead code strip

在構(gòu)建完成之后如果是 C烤礁、C++ 等靜態(tài)的語言的代碼讼积、一些常量定義,如果發(fā)現(xiàn)沒有被使用到將會被標(biāo)記為 Dead code脚仔。開啟 DEAD_CODE_STRIP = YES 這些 Dead code 將不會被打包到安裝包中勤众。在 LinkMap 這些符號也會被標(biāo)記為 <<dead>>。

dead code strip(死代碼剝離)是一個連接器參數(shù)鲤脏,可以掃描哪些代碼沒有用到并且刪掉的不是導(dǎo)出符號(本地符號)们颜。

image.png

-dead_strip
Remove functions and data that are unreachable by the entry point or exported symbols.
可以通過終端man ld然后輸入/ + 關(guān)鍵字搜索命令,n往下查找,N往上查找凑兰。

打開code dead strip前:

image.png

打開code dead strip后可以查看下符號表:
image.png

可以看到?jīng)]有使用的weak_hidden_function方法被脫去了掌桩。

strip原理

.o/靜態(tài)庫

image.png

.o/靜態(tài)庫符號放在__DWARF段,在脫符號的時候:
1.遍歷LoadCommands找到Segname__DWARFLoadCommand然后移除下面所有的Section;
2.再移除符號表中的Symbol;
3.將重新改寫后的macho文件重新寫入;

動態(tài)庫/可執(zhí)行文件

調(diào)試符號

image.png

遍歷符號表判斷符號表中的符號n_type是否包含N_STAB(0xe0)姑食,是調(diào)試符號就刪除。

All Symbols

image.png

遍歷間接符號表中所有的符號茅坛,只要不是間接符號表中的符號都可以刪除音半。

Non-Global Symbols

image.png

遍歷符號表,通過n_type != N_EXT判斷贡蓖。

dead code stripstrip主要是操作符號曹鸠。

包大小優(yōu)化方向

  • -O1 -Oz 生成目標(biāo)文件
  • dead code strip 死代碼剝離 鏈接的時候
  • strip 剝離符號 修改mach-o

LLVM調(diào)試

對于想要調(diào)試的命令直接添加scheme,然后在Compile Sources中查看文件:

image.png

2.找到對應(yīng)文件在main函數(shù)中打斷點:

image.png

3.添加-pa并將要調(diào)試的可執(zhí)行程序拖進(jìn)去

image.png

4.run without building

image.png

strip調(diào)試

1.在編譯好的LLVM源碼工程中在TARGETS中搜索strip都時候斥铺,只有一個llvm-strip

image.png

可以看到是一個腳本:
image.png

這個腳本將llvm-objcopy可執(zhí)行文件鏈接成了llvm-strip可執(zhí)行文件彻桃。類似快捷方式。
2.添加llvm-strip腳本對應(yīng)的scheme編譯晾蜘。
3.編譯完成后在源碼對應(yīng)的llvm-project/build/Debug/bin目錄下可以看到llvm-strip對應(yīng)的快捷方式邻眷。
image.png

4.在targets中復(fù)制一個可執(zhí)行文件命名為strip
image.png

image.png

由于llvm-objcopy的代碼中作樂判斷剔交,如果名稱為llvm-objcopy句當(dāng)作llvm-objcopy運行肆饶,如果是strip就當(dāng)作strip運行。
strip
image.png

Show In Finder這個時候是找不到對應(yīng)的文件的岖常。
5.優(yōu)化
復(fù)制一份llvm-strip
image.png

改名為strip
image.png

這個時候再重復(fù)4中的Show In Finder驯镊,就自動關(guān)聯(lián)到strip了。
6.添加strip scheme并且在llvm-objcopy.cpp文件的main函數(shù)打上斷點,然后run without building就可以調(diào)試了
image.png

  • **br read -f 文件路徑**可以將一個文件的斷點信息導(dǎo)入板惑。導(dǎo)入后默認(rèn)沒有啟用橄镜。
  • br list strip將所有斷點放入strip組中。
  • br enable strip啟用組里面的所有斷點冯乘。

Xcode默認(rèn)斷點式根據(jù)絕對路徑來斷的洽胶,不通用。通過lldb終端下的斷點式真正的符號斷點往湿,可以通用妖异。
我們當(dāng)然可以將自己的斷點寫入到文件中:
br write -f 文件寫入文件。

7.拖入可執(zhí)行文件就可以開始調(diào)試了


image.png

demo地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末领追,一起剝皮案震驚了整個濱河市他膳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绒窑,老刑警劉巖棕孙,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異些膨,居然都是意外死亡蟀俊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門订雾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肢预,“玉大人,你說我怎么就攤上這事洼哎√逃常” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵噩峦,是天一觀的道長锭沟。 經(jīng)常有香客問我,道長识补,這世上最難降的妖魔是什么族淮? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮凭涂,結(jié)果婚禮上祝辣,老公的妹妹穿的比我還像新娘。我一直安慰自己导盅,他們只是感情好较幌,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著白翻,像睡著了一般乍炉。 火紅的嫁衣襯著肌膚如雪绢片。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天岛琼,我揣著相機與錄音底循,去河邊找鬼。 笑死槐瑞,一個胖子當(dāng)著我的面吹牛熙涤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播困檩,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祠挫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悼沿?” 一聲冷哼從身側(cè)響起等舔,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糟趾,沒想到半個月后慌植,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡义郑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年蝶柿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片非驮。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡交汤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劫笙,到底是詐尸還是另有隱情蜻展,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布邀摆,位于F島的核電站,受9級特大地震影響伍茄,放射性物質(zhì)發(fā)生泄漏栋盹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一敷矫、第九天 我趴在偏房一處隱蔽的房頂上張望例获。 院中可真熱鬧,春花似錦曹仗、人聲如沸榨汤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽收壕。三九已至妓灌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜜宪,已是汗流浹背虫埂。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留圃验,地道東北人掉伏。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像澳窑,于是被迫代替她去往敵國和親斧散。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容