iOS高級強化--004:Symbol

  • Symbol Table:?來保存符號。
  • String Table:?來保存符號的名稱。
  • Indirect Symbol Table:間接符號表。保存使?的外部符號。更準確?點就是使?的外部動態庫的符號。是Symbol Table的子集。

符號表也是通過讀取Load Command找到符號表的具體位置


通過兩個Load Commands,描述Symbol Table的大小和位置,以及其他元數據

  • LC_SYMTAB:當前Mach-O中的符號表信息
  • LC_DYSYMTAB:描述動態鏈接器使用其他的Symbol Table信息
LC_SYMTAB

用來描述該文件的符號表。不論是靜態鏈接器還是動態鏈接器在鏈接此文件時,都要使用該Load Command。調試器也可以使用該Load Command找到調試信息

symtab_command

定義LC_SYMTAB加載命令具體屬性。在/usr/include/mach-o/loader.h中定義:

struct symtab_command {
    // 共有屬性。指明當前描述的加載命令,當前被設置為LC_SYMTAB
    uint32_t cmd ;
    // 共有屬性。指明加載命令的大小,當前被設置為sizeof(symtab_command)
    uint32_t cmdsize;
    // 表示從文件開始到symbol table所在位置的偏移量。symbol table用[nlist]來表示
    uint32_t symoff;
    // 符號表內符號的數量
    uint32_t nsyms;
    // 表示從文件開始到string table所在位置的偏移量。
    uint32_t stroff;
    // 表示string table大小(以byteカ單位)
    uint32_t strsize;
};
搭建測試項目

符號可以使用終端命令進行查看,但每次手動操作過于繁瑣。這里介紹一個更高效的方案:使用Sellp腳本,在項目編譯后,自動將符號展示到終端

創建xcode_run_cmd.sh文件,寫入以下代碼,將文件放到項目根目錄

該腳本的作用:執行命令并將結果展示到終端

#!/bin/sh

RunCommand() {

  if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then

      if [[ -n "$TTY" ]]; then
          echo "? $@" 1>$TTY
      else
          echo "? $*"
      fi
      echo "------------------------------------------------------------------------------" 1>$TTY
  fi

  if [[ -n "$TTY" ]]; then
        eval "$@" &>$TTY
  else
      "$@"
  fi

  return $?
}

EchoError() {

    if [[ -n "$TTY" ]]; then
        echo "$@" 1>&2>$TTY
    else
        echo "$@" 1>&2
    fi
    
}

RunCMDToTTY() {
    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

腳本內定義了兩個變量:

  • CMD:命令+參數
  • TTY:終端的標識

打開終端,使用tty命令,可獲取終端標識

打開項目,創建xcconfig文件,定義腳本中需要的兩個變量

MACH_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/${PRODUCT_NAME}
CMD=nm -pa $MACHO_PATH
TTY=/dev/ttys001
  • MACHO_PATH:定義變量,存儲Mach-O文件的路徑
  • ${BUILD_DIR}:當前編譯路徑
  • $(CONFIGURATION):構建產品目錄
  • $(EFFECTIVE_PLATFORM_NAME)Mach-O所在目錄
  • ${PRODUCT_NAME}:項目名稱,也是Mach-O文件的名稱
  • nm:是names的簡稱,通過該命令可以列舉文件中的符號
  • -p:符號不進行排序
  • -a:顯示所有符號,包含調試符號

點擊Target,選擇Build Phases,在Run Script中輸入:/bin/sh "$SRCROOT/xcode_run_cmd.sh"

  • 通過/bin/bash命令運行xcode_run_cmd.sh腳本
  • $SRCROOT代表的是項目根目錄下

項目編譯后,自動將Mach-O中的符號展示到終端,無需手動操作

查看項目在Build時,腳本的執行時機

  • 在編譯鏈接之后,簽名之前
C語言符號

打開main.m文件,寫入以下代碼:

#import <Foundation/Foundation.h>

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, char *argv[]) {
    static_uninit_value = 10;
    NSLog(@"%d", static_init_value);
    return 0;
}

代碼里包含了全局變量和static關鍵字修飾的靜態變量,二者最大的差異在于作用域:

  • 全局變量:對整個項目可見
  • 靜態變量:僅對當前文件可見
案例1:

使用nm -pa ${MACH_PATH}命令查看符號表

0000000100008008 d __dyld_private
0000000100008014 d _static_init_value
0000000100008018 b _static_uninit_value
0000000100008028 s _default_x
0000000000000000 - 00 0000    SO /Users/zang/Zang/Spark/MachOAndSymbol/
0000000000000000 - 00 0000    SO main.m
0000000060335e1d - 03 0001   OSO /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003f50 - 01 0000 BNSYM
0000000100003f50 - 01 0000   FUN _main
000000000000003f - 00 0000   FUN
000000000000003f - 01 0000 ENSYM
0000000000000000 - 00 0000  GSYM _global_init_value
0000000100008014 - 0a 0000 STSYM _static_init_value
0000000100008018 - 0b 0000 STSYM _static_uninit_value
0000000000000000 - 00 0000  GSYM _global_uninit_value
0000000000000000 - 00 0000  GSYM _default_x
0000000000000000 - 01 0000    SO
0000000100000000 T __mh_execute_header
0000000100008010 D _global_init_value
0000000100008020 S _global_uninit_value
0000000100003f50 T _main
                 U _NSLog
                 U ___CFConstantStringClassReference
                 U dyld_stub_binder

第二列:按照符號種類劃分的標識,參見以下列表

按照符號種類劃分:
Type 說明
U undefined(未定義)
A absolute(絕對符號)
T text section symbol__TEXT.__text
D data section symbol__DATA.__data
B bss section symbol__DATA.__bss
C common symbol(只能出現在MH_OBJECT類型的Mach-O?件中)
- debugger symbol table
S 除了上?所述的,存放在其他section的內容,例如未初始化的全局變量存放在(__DATA,__common)中
I indirect symbol(符號信息相同,代表同?符號)
u 動態共享庫中的?寫u表示?個未定義引?對同?庫中另?個模塊中私有外部符號

注:標記Type,?寫代表本地符號(local symbol

案例2:

使用objdump --macho --syms ${MACH_PATH}命令查看符號表

SYMBOL TABLE:
0000000100008008 l     O __DATA,__data __dyld_private
0000000100008014 l     O __DATA,__data _static_init_value
0000000100008018 l     O __DATA,__bss _static_uninit_value
0000000100008028 l     O __DATA,__common _default_x
0000000000000000 l    d  *UND* /Users/zang/Zang/Spark/study/iOS高級強化/20210118-iOS強化第二節課:符號與鏈接(下)/上課代碼/MachOAndSymbol/
0000000000000000 l    d  *UND* main.m
0000000060335b53 l    d  *UND* /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
0000000100003f50 l    d  *UND*
0000000100003f50 l    d  *UND* _main
000000000000003f l    d  *UND*
000000000000003f l    d  *UND*
0000000000000000 l    d  *UND* _global_init_value
0000000100008014 l    d  *UND* _static_init_value
0000000100008018 l    d  *UND* _static_uninit_value
0000000000000000 l    d  *UND* _global_uninit_value
0000000000000000 l    d  *UND* _default_x
0000000000000000 l    d  *UND*
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100008010 g     O __DATA,__data _global_init_value
0000000100008020 g     O __DATA,__common _global_uninit_value
0000000100003f50 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* dyld_stub_binder
  • 第二列:l代表本地符號(local),g代表全局符號(global
  • 第三列:按照功能劃分的標識,參見以下列表
按照功能劃分:
Type 說明
f File
F Function
O Data
d Debug
ABS Absolute
COM Common
UND 未定義
剝離調試符號

調試符號:當文件編譯成.o文件時,它會生成DWARF格式的調試信息,放在__DWARF段。鏈接時,將__DWARF段變成符號放到符號表中。

上面的打印信息中,包含大量調試符號。為了避免干擾,下面介紹如何剝離調試符號。

方式一:

使用Build SettingsStrip配置項

  • Deployment PostprocessingDebugRelease下均默認為NO,它相當于是Deployment的總開關。將其改為YES
  • Strip StyleDebugRelease下均默認All Symbols,剝離除間接符號表以外的全部符號。將其改為Debugging Symbols,剝離調試符號
  • Deployment PostprocessingNOStrip Style的配置是無法生效的

編譯項目,Strip操作已經執行。但它是在腳本執行后才觸發,故此該方式在使用Sellp腳本自動化的場景下不可行

方式二:

xcconfig中使用鏈接器配置

鏈接器的作用就是將多個目標文件合并到一起,此時可以對符號進行指定處理

使用man ld命令查看鏈接器參數:

使用/-S向下查找-S關鍵字

  • -S:不要將調試信息放置到輸出文件中,本質上和StripDebugging Symbols配置項是一樣的效果

打開xcconfig文件,添加OTHER_LDFLAGS配置項

OTHER_LDFLAGS = -Xlinker -S
  • Xcode使用Target編譯時,執行的是clang命令。但此時-S需要傳遞給ld鏈接器,所以前面要加上-Xlinker關鍵字

Build Settings中的Other Linker Flags配置已生效

來到終端,先command+k清空內容,再編譯項目。此時打印出除調試符號以外的所有符號

SYMBOL TABLE:
0000000100008008 l     O __DATA,__data __dyld_private
0000000100008014 l     O __DATA,__data _static_init_value
0000000100008018 l     O __DATA,__bss _static_uninit_value
0000000100008028 l     O __DATA,__common _default_x
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100008010 g     O __DATA,__data _global_init_value
0000000100008020 g     O __DATA,__common _global_uninit_value
0000000100003f50 g     F __TEXT,__text _main
0000000000000000         *UND* _NSLog
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* dyld_stub_binder
  • global_uninit_valueglobal_init_value為全局符號
  • static_init_valuestatic_uninit_value為本地符號
  • default_x并沒有使用static關鍵字修飾,本應是全局符號,但使用了__attribute__((visibility("hidden"))),變為本地符號

__attribute__()的作用:將編譯器支持的參數傳遞給編譯器。例如:對default_x全局變量進行visibility("hidden")設置,就是隱藏其可見性

符號可見性

全局符號和本地符號,它們本質上的區別就是可見性

一個符號的可見性有兩種:

  • default:默認值,定義的符號類型是全局即為全局,是本地即為本地
  • hidden:將全局符號隱藏,變為本地符號

故此隱藏全局符號的方式有兩種:

  • 使用static關鍵字修飾
  • 使用__attribute__((visibility("hidden")))
案例1:

打開項目,里面包含兩個Project

來到LGOneFramework,打開LGOneObject.m文件,實現global_object函數

LGOneObject.h文件中,并沒有暴露global_object函數

來到LGApp(另一個Project),打開ViewController.m文件,定義global_object函數,但并不實現。在viewDidLoad方法中調用global_object函數

運行項目,LGOneFramework中實現的global_object函數被正常調用

案例2:

如果將LGOneFramework中的global_object函數,使用static關鍵字修飾

LGApp中,global_object函數的調用代碼不變

此時global_object函數變成本地符號,僅對當前文件可見。所以在LGApp中調用該函數,編譯報錯,并提示未定義符號

通過上述兩個案例,理解符號的可見性:

  • 全局符號對整個項目可見
  • 本地符號僅對當前文件可見
案例3:

LGOneFramework中,實現global_object函數,不使用static關鍵字修飾

LGAppViewController.m中,也實現global_object函數

運行項目,調用的是LGApp中的global_object函數

案例3中,為什么不會發生沖突?
LGAppLGOneFramework兩個Project中,都定義了global_object函數,對于global_object函數來說,它們其實存儲在兩個Mach-O中。由于編譯器有?級命名空間的概念,所以兩個global_object函數的符號其實是不一樣的

two_levelnamespace & flat_namespace
?級命名空間與?級命名空間。鏈接器默認采??級命名空間,也就是除了會記錄符號名稱,還會記錄符號屬于哪個Mach-O的,?如會記錄下來_NSLog來?Foundation

案例4:

LGAppViewController.m中,實現global_object函數

同樣在LGApp中,打開AppDelegate.m文件,也實現global_object函數

同一個Project中,實現兩個global_object函數。此時編譯報錯,提示出現重復符號

  • 這說明在同一作用域中,不能出現相同的全局符號
導入符號和導出符號

export symbol:導出符號意味著,告訴別的模塊,我有?個這樣的符號,你可以將其導?(Import)。

NSLog為例:

NSLog(@"%d", static_init_value);

NSLog存儲在Foundation庫中

  • 對于Foundation庫來說,NSLog屬于供外部使用的導出符號
  • 對于當前程序來說,NSLog屬于從Foundation庫中導入的符號

導出符號就是全局符號

項目中定義的全局變量生成為全局符號,默認就會被導出,這些符號可以被外界查看并使用

使用objdump --macho --exports-trie ${MACH_PATH}命令查看導出符號

Exports trie:
0x100000000  __mh_execute_header
0x100003F50  _main
0x100008010  _global_init_value
0x100008020  _global_uninit_value
  • 只有上述四個導出符號,對應符號表中的四個全局符號

動態庫在運行時才會加載,在編譯鏈接階段只提供符號即可。Mach-O中使用的動態庫符號保存在間接符號表里

使用objdump --macho --indirect-symbols ${MACH_PATH}命令查看間接符號表

Indirect symbols for (__TEXT,__stubs) 1 entries
address            index name
0x0000000100003f90     8 _NSLog
Indirect symbols for (__DATA_CONST,__got) 1 entries
address            index name
0x0000000100004000    10 dyld_stub_binder
Indirect symbols for (__DATA,__la_symbol_ptr) 1 entries
address            index name
0x0000000100008000     8 _NSLog
  • 符號在Mach-O中占有一定體積,剝離符號時,間接符號表是不能被刪除的
  • Mach-O所使用的動態庫的全局符號都不能被刪除
  • 動態庫剝離符號,只能剝離非全局符號的所有符號

查看OC中的符號

打開LGOneObject.m文件,寫入以下代碼:

#import "LGOneObject.h"

@interface LGOneObject : NSObject

- (void)testOneObject;

@end

@implementation LGOneObject

- (void)testOneObject {
    NSLog(@"testOneObject");
}

@end

使用objdump --macho --exports-trie ${MACH_PATH}命令查看導出符號

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

OC默認都是全局符號,同時也是導出符號。它們可以被外界查看并使用,會增加Mach-O的體積

開發OC動態庫時,想要減小Mach-O的體積,就要將外部無需使用的符號剝離。此時可以借助鏈接器,將不想暴露的符號聲明為不導出符號

打開xcconfig文件,添加OTHER_LDFLAGS配置項

OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_CLASS_$_LGOneObject
OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbol -Xlinker _OBJC_METACLASS_$_LGOneObject
  • _OBJC_CLASS_$_LGOneObject聲明為不導出符號
  • _OBJC_METACLASS_$_LGOneObject聲明為不導出符號

編譯項目,此時OC的兩個導出符號已經被隱藏

Exports trie:
0x100000000  __mh_execute_header
0x100003F20  _main
0x100008110  _global_init_value
0x100008120  _global_uninit_value
  • 隱藏OC不想暴露的符號,需要借助鏈接器,將符號聲明為不導出符號
  • 由于OC是運行時語言,不能直接使用visibility("hidden")
  • 不導出符號,將全局符號變為本地符號,這些符號可以被剝離,從而減小Mach-O的體積
  • 隱藏不需要暴露的符號,從而避免被外界查看并使用,解決安全隱患

鏈接器提供的另一種方式:指定一個文件,將文件內的符號全部聲明為不導出符號

創建symbol.txt文件,放到工程目錄中,里面定義不想暴露的符號

_OBJC_CLASS_$_LGOneObject
_OBJC_METACLASS_$_LGOneObject
_global_init_value
_global_uninit_value

打開xcconfig文件,添加OTHER_LDFLAGS配置項

OTHER_LDFLAGS=$(inherited) -Xlinker -unexported_symbols_list >$(PROJECT_DIR)/symbol.txt

編譯項目,此時symbol.txt文件中定義的四個符號已經被隱藏

Exports trie:
0x100000000  __mh_execute_header
0x100003F20  _main
Weak Symbol
弱定義符號

Weak Defintion Symbol:表示此符號為弱定義符號。如果靜態鏈接器或動態鏈接器為此符號找到另?個(?弱)定義,則弱定義將被忽略。只能將合并部分中的符號標記為弱定義

打開WeakSymbol.m文件,寫入以下代碼:

#import "WeakSymbol.h"
#import <Foundation/Foundation.h>

void weak_function(void) {
   NSLog(@"weak_function");
}
  • 此時weak_function是一個全局符號,同樣也是導出符號

打開WeakSymbol.h文件,寫入以下代碼:

void weak_function(void)  __attribute__((weak));
  • 使用__attribute__((weak))weak_function聲明為弱定義符號

使用objdump --macho --exports-trie ${MACH_PATH}命令查看導出符號

Exports trie:
0x100000000  __mh_execute_header
0x100003EF0  _weak_function [weak_def]
0x100003F30  _main
0x100008010  _global_init_value
0x100008020  _global_uninit_value
  • weak_function還是導出符號,這也證明它依然是全局符號,其后增加了[weak_def]的標記

弱定義符號的作用

WeakSymbol.mmain.m中,都實現一個weak_function函數

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

同一個Project中,出現兩個相同的全局符號,此時編譯報錯,提示出現重復符號

將其中一個weak_function函數聲明為弱定義符號,此時編譯成功

void weak_function(void)  __attribute__((weak));
  • 弱定義符號的作用:可以解決同名符號的沖突;鏈接器按照符號上下順序,找到一處符號的實現后,其他地方的同名符號將被忽略

如果同時使用weakvisibility("hidden"),符號會變成一個弱定義的本地符號

打開WeakSymbol.m文件,寫入以下代碼:

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

打開WeakSymbol.h文件,將weak_hidden_function函數同時使用weakvisibility("hidden")修飾

void weak_hidden_function(void) __attribute__((weak, visibility("hidden")));

使用objdump --macho --syms ${MACH_PATH}命令查看符號表

SYMBOL TABLE:
0000000100003f10 lw    F __TEXT,__text _weak_hidden_function
0000000100008008 l     O __DATA,__data __dyld_private
0000000100008014 l     O __DATA,__data _static_init_value
0000000100008018 l     O __DATA,__bss _static_uninit_value
0000000100008028 l     O __DATA,__common _default_x
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100008010 g     O __DATA,__data _global_init_value
0000000100008020 g     O __DATA,__common _global_uninit_value
0000000100003f30 g     F __TEXT,__text _main
0000000100003ef0 gw    F __TEXT,__text _weak_function
0000000000000000         *UND* _NSLog
0000000000000000         *UND* ___CFConstantStringClassReference
0000000000000000         *UND* dyld_stub_binder
  • 此時_weak_hidden_function被標記為lw,變為弱定義本地符號
弱引用符號

Weak Reference Symbol:表示此未定義符號是弱引?。如果動態鏈接器找不到該符號的定義,則將其設置為0。鏈接器會將此符號設置弱鏈接標志

打開WeakImportSymbol.h文件,寫入以下代碼:

void weak_import_function(void) __attribute__((weak_import));
  • 使用__attribute__((weak_import))weak_import_function聲明為若引用符號
  • 此時項目中沒有weak_import_function函數的實現

打開main.m文件,寫入以下代碼:

#import <Foundation/Foundation.h>
#import "WeakImportSymbol.h"

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

由于weak_import_function函數沒有實現,但在main.m中被使用,此時編譯報錯,提示未定義符號

  • 當導入.h頭文件并使用符號時,類似于API的使用,只要找到符號的聲明即可。即使函數沒有被實現,也可以生成目標文件。但鏈接生成可執行文件時,需要知道符號的具體位置,如果函數沒有被實現,會出現錯誤提示:未定義符號

解決弱引用符號的使用問題,可以通過鏈接器,將符號聲明為動態鏈接

使用man ld命令查看鏈接器參數:

  • -U:指明該符號未定義,需要運行時動態查找

打開xcconfig文件,添加OTHER_LDFLAGS配置項

OTHER_LDFLAGS=$(inherited) -Xlinker -U -Xlinker _weak_import_function
  • 此時項目可以正常編譯成功
  • 通過-U參數,告訴鏈接器此符號是動態鏈接的,所以在鏈接階段,即使它是未定義符號,忽略,不用管它。因為在運行時,動態鏈接器會自動找到它

運行項目,雖然weak_import_function函數沒有被實現,但運行并不會報錯

  • 因為main函數中調用weak_import_function函數之前有if (weak_import_function)的判斷
  • 當動態鏈接器找不到該符號的定義,則將其設置為0。所以weak_import_function函數并不會被調用

弱引用符號的作用

  • 將一個符號聲明為弱引用符號,可以避免編譯鏈接時報錯。在調用之前增加條件判斷,運行時也不會報錯
  • 使用動態庫的時候,可以將整個動態庫聲明為弱引用,此時動態庫即使沒有被導入,也不會出現未找到動態庫的錯誤
Common Symbol

在定義時,未初始化的全局符號

例如:main.m文件中,未初始化的global_uninit_value全局變量,它就屬于Common Symbol

int global_uninit_value;

打開main.m文件,定義兩個同名的全局變量,一個初始化,另一個不進行初始化,這種操作并不會報錯

int global_init_value = 10;
int global_init_value;
  • Common Symbol的作用
  • 在編譯和鏈接的過程中,如果找到定義的符號,會自動將未定義符號刪掉
  • 在鏈接過程中,鏈接器默認會把未定義符號變成強制定義的符號

鏈接器設置:

  • -d:強制定義Common Symbol
  • -commons:指定對待Common Symbol如何響應
重新導出符號

NSLog為例:

  • 對于當前程序來說,NSLog屬于存儲在間接符號表中的未定義符號

NSLog可以在當前程序使用,如果想讓使用此程序的其他程序也能使用,就要將此符號重新導出。重新導出之后的符號會放在導出符號表中,此時才能被外界查看并使用

使用man ld命令查看鏈接器參數:

  • -alias:只能給間接符號表中的符號創建別名,別名符號具有全局可見性

打開xcconfig文件,添加OTHER_LDFLAGS配置項

OTHER_LDFLAGS=$(inherited) -Xlinker -alias -Xlinker _NSLog -Xlinker Cat_NSLog
  • _NSLog符號創建Cat_NSLog別名

使用nm -m ${MACH_PATH} | grep "Cat_NSLog"命令查看符號表,指定"Cat_NSLog"關鍵字

(indirect) external Cat_NSLog (for _NSLog)
  • 此時Cat_NSLog是一個間接外部符號,是_NSLog符號的別名

使用objdump --macho --exports-trie ${MACH_PATH}命令查看導出符號

Exports trie:
0x100000000  __mh_execute_header
0x100003F20  _main
0x100008018  _global_init_value
0x100008028  _global_uninit_value
[re-export] Cat_NSLog (_NSLog from Foundation)
  • Cat_NSLog為導出符號,并且標記為[re-export],代表重新導出符號

重新導出符號的作用

  • 將一個間接符號表中的符號聲明為重新導出符號,可以讓使用此程序的其他程序也能使用
  • 當程序鏈接A動態庫,而A動態庫又鏈接B動態庫時,B動態庫對于程序來說是不可見的。此時可以使用重新導出的方式,讓B動態庫對程序可見
圖解
查看項目使用的三方庫和符號等信息

通過鏈接器,可以查看當前項目中使用的三方庫和符號等信息

使用man ld命令查看鏈接器參數:

  • -map:將所有符號詳細信息導出到指定文件

打開xcconfig文件,添加OTHER_LDFLAGS配置項

OTHER_LDFLAGS=$(inherited) -Xlinker -map -Xlinker $(PROJECT_DIR)/export.txt

編譯項目,此時項目目錄下多出export.txt文件

打開export.txt文件

# Path: /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Products/Debug/MachOAndSymbol
# Arch: x86_64
# Object files:
[  0] linker synthesized
[  1] /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/LGOneObject.o
[  2] /Users/zang/Library/Developer/Xcode/DerivedData/MachOAndSymbol-bdzlylfoorwnhggerxdmwocpeyad/Build/Intermediates.noindex/MachOAndSymbol.build/Debug/MachOAndSymbol.build/Objects-normal/x86_64/main.o
[  3] /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk/System/Library/Frameworks//Foundation.framework/Foundation.tbd
# Sections:
# Address   Size        Segment Section
0x100003EF0 0x0000006F  __TEXT  __text
0x100003F60 0x00000006  __TEXT  __stubs
0x100003F68 0x0000001A  __TEXT  __stub_helper
0x100003F82 0x00000011  __TEXT  __cstring
0x100003F93 0x0000000C  __TEXT  __objc_classname
0x100003F9F 0x0000000E  __TEXT  __objc_methname
0x100003FAD 0x00000008  __TEXT  __objc_methtype
0x100003FB8 0x00000048  __TEXT  __unwind_info
0x100004000 0x00000008  __DATA_CONST    __got
0x100004008 0x00000040  __DATA_CONST    __cfstring
0x100004048 0x00000008  __DATA_CONST    __objc_classlist
0x100004050 0x00000008  __DATA_CONST    __objc_imageinfo
0x100008000 0x00000008  __DATA  __la_symbol_ptr
0x100008008 0x000000B0  __DATA  __objc_const
0x1000080B8 0x00000050  __DATA  __objc_data
0x100008108 0x00000010  __DATA  __data
0x100008118 0x00000004  __DATA  __bss
0x100008120 0x00000010  __DATA  __common
# Symbols:
# Address   Size        File  Name
0x100003EF0 0x00000027  [  1] -[LGOneObject testOneObject]
0x100003F20 0x0000003F  [  2] _main
0x100003F60 0x00000006  [  3] _NSLog
0x100003F68 0x00000010  [  0] helper helper
0x100003F78 0x0000000A  [  3] _NSLog
0x100003F82 0x0000000E  [  1] literal string: testOneObject
0x100003F90 0x00000003  [  2] literal string: %d
0x100003F93 0x0000000C  [  1] literal string: LGOneObject
0x100003F9F 0x0000000E  [  1] literal string: testOneObject
0x100003FAD 0x00000008  [  1] literal string: v16@0:8
0x100003FB8 0x00000048  [  0] compact unwind info
0x100004000 0x00000008  [  0] non-lazy-pointer-to-local: dyld_stub_binder
0x100004008 0x00000020  [  1] CFString
0x100004028 0x00000020  [  2] CFString
0x100004048 0x00000008  [  1] objc-cat-list
0x100004050 0x00000008  [  0] objc image info
0x100008000 0x00000008  [  3] _NSLog
0x100008008 0x00000048  [  1] __OBJC_METACLASS_RO_$_LGOneObject
0x100008050 0x00000020  [  1] __OBJC_$_INSTANCE_METHODS_LGOneObject
0x100008070 0x00000048  [  1] __OBJC_CLASS_RO_$_LGOneObject
0x1000080B8 0x00000028  [  1] _OBJC_METACLASS_$_LGOneObject
0x1000080E0 0x00000028  [  1] _OBJC_CLASS_$_LGOneObject
0x100008108 0x00000008  [  0] __dyld_private
0x100008110 0x00000004  [  2] _global_init_value
0x100008114 0x00000004  [  2] _static_init_value
0x100008118 0x00000004  [  2] _static_uninit_value
0x100008120 0x00000008  [  2] _global_uninit_value
0x100008128 0x00000008  [  2] _default_x
  • 文件內包含了編譯鏈接時生成的目標文件,項目中使用的三方庫,還包含項目中的SectionsSymbols等信息
Section的名稱與作用
名稱 作用
TEXT.text 可執行的機器碼
TEXT.cstring 去重后的C字符串
TEXT.const 初始化過的常量
TEXT.stubs 符號樁。lazybinding的表對 應項指針指向的地址的代碼
TEXT.stub_ helper 輔助函數。當在lazybinding的表中沒有找到對應項的指針表示的真正的符號地址的時候,指向這
TEXT.unwind_info 存儲處理異常情況信息
TEXT.eh_frame 調試輔助信息
DATA.data 初始化過的可變的數據
DATA.nI_symbol_ptr lazy-binding的指針表,每個表中的指針指向一個在裝載過程中,被動態鏈接器搜索完成的符號
DATA.Ia_symbol_ptr lazy-binding的指針表,每個表中的指針一開始指向stub_helper
DATA.const 沒有初始化過的常量
DATA.mod_init_func 初始化函數,在main之前調用
DATA.mod_term_func 終止函數,在main返回之后調用
DATA.bss 沒有初始化的靜態變量
DATA.common 沒有初始化過的符號聲明(for example, int I;
Swift符號表

打開SwiftSymbol.swift文件,寫入以下代碼:

public class LGSwiftClassSymbol {
   func testSwiftSymbol() {
   }
}

使用objdump --macho --syms ${MACH_PATH} | grep "Swift"命令查看符號表,指定Swift關鍵字

0000000100003f8a lw    O __TEXT,__swift5_typeref _symbolic _____ 14MachOAndSymbol012LGSwiftClassC0C
0000000100003f90 l     O __TEXT,__swift5_fieldmd _$s14MachOAndSymbol012LGSwiftClassC0CMF
0000000100008020 l     O __DATA,__objc_const __METACLASS_DATA__TtC14MachOAndSymbol18LGSwiftClassSymbol
0000000100008068 l     O __DATA,__objc_const __DATA__TtC14MachOAndSymbol18LGSwiftClassSymbol
00000001000080e8 l     O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CMf
0000000100003d90 g     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0C09testSwiftC0yyF
0000000100003f78 g     O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0C09testSwiftC0yyFTq
0000000100003e10 g     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CACycfC
0000000100003f80 g     O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0CACycfCTq
0000000100003e40 g     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CACycfc
0000000100003e60 g     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CMa
00000001000080c0 g     O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CMm
0000000100003f44 g     O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC0CMn
00000001000080f8 g     O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC0CN
0000000100003dd0 g     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0CfD
0000000100003db0 g     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC0Cfd
0000000000000000         *UND* _OBJC_CLASS_$__TtCs12_SwiftObject
0000000000000000         *UND* _OBJC_METACLASS_$__TtCs12_SwiftObject
  • 查找出所有包含Swift關鍵字的符號,其中有很多標記為g的全局符號

打開SwiftSymbol.swift文件,修改LGSwiftClassSymbol類的訪問控制,改為private修飾

private class LGSwiftClassSymbol {
   func testSwiftSymbol() {
   }
}

使用objdump --macho --syms ${MACH_PATH} | grep "Swift"命令查看符號表,指定Swift關鍵字

0000000100003d10 l     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC09testSwiftC0yyF
0000000100003d30 l     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCfd
0000000100003d50 l     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCfD
0000000100003d90 l     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMa
0000000100003db0 l     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfC
0000000100003de0 l     F __TEXT,__text _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfc
0000000100003f1c lw    O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMXX
0000000100003f44 l     O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMn
0000000100003f78 l     O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC09testSwiftC0yyFTq
0000000100003f80 l     O __TEXT,__const _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCADycfCTq
0000000100003f8a lw    O __TEXT,__swift5_typeref _symbolic _____ 14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLC
0000000100003f90 l     O __TEXT,__swift5_fieldmd _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMF
0000000100008020 l     O __DATA,__objc_const __METACLASS_DATA__TtC14MachOAndSymbolP33_66093EBE10D00815F1A5CBD65FFF466118LGSwiftClassSymbol
0000000100008068 l     O __DATA,__objc_const __DATA__TtC14MachOAndSymbolP33_66093EBE10D00815F1A5CBD65FFF466118LGSwiftClassSymbol
00000001000080c0 l     O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMm
00000001000080e8 l     O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCMf
00000001000080f8 l     O __DATA,__data _$s14MachOAndSymbol012LGSwiftClassC033_66093EBE10D00815F1A5CBD65FFF4661LLCN
0000000000000000         *UND* _OBJC_CLASS_$__TtCs12_SwiftObject
0000000000000000         *UND* _OBJC_METACLASS_$__TtCs12_SwiftObject
  • 之前那些標記為g的全局符號,全部變為本地符號
$(SRCROOT)$(PROJECT_DIR)的區別
  • $(SRCROOT)代表的是項目根目錄下
  • $(PROJECT_DIR)代表的是整個項目

往項目添加文件時,例如.a文件,要先Show in Finder,復制到工程目錄中,然后再拖到xcode項目中

而有時,.a不在工程目錄中。例如在工程的父目錄,可以寫成:$(SRCROOT)/../Spark/libSDK。其中/../就是指向父目錄

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容