Mach-O符號表
Symbol Table 符號表,符號名稱和地址
String Table 符號名稱
Indirect Symbol Table 間接符號表。保存使用的外部符號,也就是使用的外部動態庫的符號。Symbol Table的子集。
常用命令
查看符號:
objdump --macho -t (mach-o文件);
nm -m (mach-o文件);objdump --macho --syms (mach-o文件)。
恢復符號號:
./restore-symbol /Users/lenny/Desktop/CompileTest -o symbolDemo.symbol。
查看文件:
file。
查看mach-header:
objdump --macho -private-header (mach-o文件)。
查看 __TEXT:
objdump --macho -d (mach-o文件)。
查看導出符號:
objdump --macho --exports-trie (mach-o文件)。
查看間接符號表:
objdump --macho --indirect-symbols (mach-o文件)。
``
符號分類
Mach-O文件中的符號 = 全局(給別人用) + 本地(自用)+ 間接符號(別人的動態庫)。將全局符號導出給其他文件訪問的符號叫導出符號,使用其他Mach-O文件導出符號叫導入符號,比如間接符號。此外還可以分為弱符號和非弱符號。以下將一一講解。
全局符號(Global Symbol)
允許全局訪問的符號,默認情況下其他文件是可以訪問的。通過objdump --macho --syms 命令可以查看全局符號,比如("g"標記的表示全局(global)符號):
本地符號(Local Symbol)
只允許內部文件訪問的符號,比如下面的demo,用static 聲明的靜態變量,通過objdump --macho --syms 命令查看編譯后的Mach-O文件(本地符號的標記是"l"):
間接符號(Indirect Symbol)
本文件使用的外部符號,也就是使用的外部動態庫的符號。Mach-O文件的間接符號可以通過objdump --macho --indirect-symbols命令進行查看,示例如下:
導出符號
導出符號是Mach-O文件提供外部文件訪問的符號。默認情況下全局符號一般都是導出符號,但是有時候可以通過鏈接器來控制導出符號。通過命令objdump --macho --exports-trie可以查看導出符號,比如:
- 過濾掉不需要的導出符號
有一些符號我們不希望導出提供外部訪問,比如下面我們自定義的一個OC類:
#import "MyObject.h"
@interface MyObject : NSObject
- (void)doSomething;
@end
@implementation MyObject
- (void)doSomething {
NSLog(@"%s",__func__);
}
@end
查看導出符號:
假設我們不希望MyObject被導出,可以通過Other linker flags配置如下過濾MyObject的符號:
-Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_MyObject
-Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_MyObject
通過命令objdump --macho --exports-trie查看導出符號:
已經沒有MyObject的符號。
作用:優化包體積大小。
導入符號
導入的外部符號,也就是導入外部動態的符號。就比如間接符號表的符號,實際上就是當前Mach-O文件導入其他文件的符號。
弱符號(Weak Symbol)
弱符號(Weak Symbol)又分為弱引用符號(Weak Reference Symbol )和弱定義符號(Weak Defined Symbol)。
-
弱定義符號
表示此符號為弱定義符號。如果靜態鏈接器或動態鏈接器為此符號找到另一個(非弱)定義,則弱定義將被忽略。只能將合并部分中的符號標記為弱定義。
弱定義符號.jpg
如果靜態鏈接器或動態鏈接器為此符號找到另一個(非弱)定義,則弱定義將被忽略,比如下面demo中,在main.m中定義一個弱定義變量weak_def_variable,在MyObject.h中定義一個相同名稱非弱定義變量,在mian函數中打印這個變量:
// 非弱定義符號
int weak_def_variable = 20;
@interface MyObject : NSObject
- (void)doSomething;
@end
NS_ASSUM
// 弱定義符號
int weak_def_variable __attribute__((weak)) = 10;
int main(int argc, char *argv[]) {
NSLog(@"%d", weak_def_variable);
return 0;
}
打印結果是20。這里表示weak_def_variable使用的是MyObject.h里的定義。
弱定義符號也是可以隱藏的,比如:
// 隱藏弱定義符號
void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));
這樣它就不會出現在導出符號表中。
-
弱引用符號
表示此未定義符號是弱引用。如果動態鏈接器找不到該符號的定義,則將其置為0,鏈接器會將此符號設置為弱連接標志。
比如下面的弱引用方法weak_reference_function,只有聲明,沒有定義:
// 弱引用
void weak_reference_function(void) __attribute__((weak_import));
int main(int argc, char *argv[]) {
// NSLog(@"%d", weak_def_variable);
if(weak_reference_function){
weak_reference_function();
}
return 0;
}
在Other Linker Flags 加入:
-Xlinker -U -Xlinker _weak_reference_function
這個告訴鏈接器weak_reference_function為未定義符號,只在運行時使用。這時候的weak_reference_function被連接器置為0,所以訪問時沒有報錯。
弱引用讀好的可以單個指定,也可以將整個動態庫的導出符號全部制定為弱引用,這樣即使存在未定義的符號也不會報錯,有運行時處理(慎用,有風險)。
未定義符號(undefined)
未定義符號一般是在編譯時無法確定它的定義符號。當dyld加載動態庫時會根據undefined的符號加載對應的動態庫。加載后,將未定義的符號綁定到動態庫里對應的地址上。以下"UND"標記的是未定義符號:
Swift符號
Swift是靜態語言,其符號跟OC不一樣,當然它也有全局和本地符號之分,比如下面的demo,定義了一個public類和private類:
public class SwiftPublicClass{
func testFunc() {
}
}
private class SwiftPrivateClass {
func testFunc() {
}
}
通過objdump --macho --syms命令查看Swift符號:
可以看出Swift代碼的符號比OC多得多。
符號剝除(Strip)
動態庫:對于動態庫來說,全局符號不能剝除掉,因為它是給外部文件訪問的;
靜態庫:對于靜態庫,它是.o文件和重定位符號表的合集。重定位符號符號不能剝除;
app:對于app來說除了間接符號表(訪問其他動態庫的符號表)不能剝除,其他如全局符號和本地符號都可以剝除以優化app的體積。
dead_code_stripping:連接器去除無用代碼。剝除本地符號以及未使用的符號;
strip_style:
Debugging Symbols 剝除調試符號
Non-Global Symbols 保留全局符號(動態庫)
All Symbols 剝除所有符號(除了間接符號,app)
問題:就符號來說,使用靜態庫體積和動態體積誰會比較小?
答:靜態庫。因為靜態庫直接編譯連接到app中,除了間接符號都可剝除,而動態庫不行。
符號沖突
動態庫:動態庫使用的是二維命名空間,符號調用先找庫再找符號,所以沒有符號沖突。
靜態庫:
1、沒有調用的符號,靜態庫不會連接。
2、鏈接器在找到符號之后就不會再鏈接相同的符號了。
3、對OC來說,連接的最小單位是類。
4、ObjC告訴編譯器添加(鏈接)所有的分類,解決沖突使用(-force_load 庫路徑),force_load表示在庫重復時優先鏈接指定路徑的庫。
鏈接
Mach-O文件在編譯時會生成相應的符號,鏈接是處理目標文件(.o文件)的過程。多個目標文件合并到到一起,多張表合并到一張表中。鏈接是根據符號表鏈接;LLVM是編譯器工具鏈的一個集合:先對每個文件進行編譯生成Mach-O(可執行文件);連接器會將多個Mach-O文件合并成一個。
連接器做了什么?
(1)去項目文件里查找目標代碼文件里沒有定義的變量;
(2)掃描項目中的不同文件,將所有符號定義和引用地址收集起來;
(3)計算合并后長度和位置,生成同類型的段并進行合并,建立綁定;
(4)對項目中不同文件里的變量進行重定位。
動態鏈接庫和靜態鏈接庫
靜態鏈接庫是編譯時鏈接的庫,需要鏈接進你的Mach-O文件里,如果需要更新就要重新編譯一次,無法動態加載和更新。
動態鏈接庫是運行時進行鏈接的庫,使用dyld實現動態加載。使用dyld加載動態庫的兩種方式:程序加載時綁定和符號第一次被用到時綁定。dyld做了什么事:
(1)先執行Mach-O文件,根據Mach-O文件里的undefined的符號加載對應的動態庫,系統會設置一個共享緩存來解決加載的遞歸依賴問題。
(2)加載后,將undefined的符號綁定到動態庫里對應的地址上;
(3)最后再處理+load方法,main函數返回后運行static terminator。
注:運行時通過dlopen和dlsym導入動態庫時,先根據記錄的路徑找到對應的庫,再通過符號名稱在符號表找到綁定的地址。dlopen打開動態庫后返回的是引用的指針,dlsym的作用就是通過dlopen返回的動態庫指針和函數符號,得到函數的地址然后使用。