iOS:走近Mach-O文件之Objc代碼存儲

在iOS中Mach-O文件主要有以下三種:

  • 可執(zhí)行文件;

  • 目標文件,如.o文件;

  • 動態(tài)庫,如dylib,framework文件;

Mach-O文件的格式一般包括一個Mach-O頭,一系列的載入命令,一個或多個Section,如下圖:

Mach-O文件格式

  • Header:記錄平臺屬性,CPU架構(gòu)、版本,文件類型,加載指令數(shù)量、大小,文件狀態(tài)等信息;

  • Load Commands:Section定義內(nèi)存空間,獲取動態(tài)鏈接器路徑,獲取應(yīng)用程序入口,加載動態(tài)庫等指令;

Section

Section段存放的內(nèi)容通過MachOView工具查看主要有兩大類:__Text__DATA

而不管是__Text還是__DATA下面還都包含若干子項,為了弄清楚每個子項中存放的內(nèi)容,我們編寫一個測試工程,里面盡量包含Objc語音的特性:類,繼承,分類,協(xié)議,實例方法,類方法等

Person.h

@interface Person : NSObject

- (void)sleep;
- (void)walk;

@end


Person.m

NSString *string1 = @"it is string1";
NSString *string2;

@implementation Person

- (void)sleep {
    NSLog(@"i am sleeping");
}

- (void)walk {
    printf("i am walking");
}

+ (void)grow {
    static NSString *string3 = @"it is string3";
    static NSString *string4;
    
    NSLog(@"%@_%@",string3,string4);
}
@end

Person類增加分類:

Person+other.h

@interface Person (other)

- (void)ill;

@end

Person+other.m

@implementation Person (other)
- (void)ill {
    NSLog(@"i am fall ill");
}
@end

添加StudentProtocol協(xié)議:

@protocol StudentProtocol <NSObject>
- (void)study;
+ (void)playgame;
@end

最后實現(xiàn)Person類的子類Student,同時實現(xiàn)StudentProtocol協(xié)議:

Student.h

@interface Student : Person <StudentProtocol>

@end

Student.m

@implementation Student
- (void)study {
    NSLog(@"i am studying");
}

+ (void)playgame {
    NSLog(@"i am playing game");
}

@end

為了防止Xcode的dead code 的優(yōu)化,我們在main函數(shù)里面顯示調(diào)用下上面的類/方法:

int main(int argc, const char * argv[]) {
    
    Person *p = [[Person alloc] init];
    [p sleep];
    [p walk];
    [p ill];
    
    Student *student = [[Student alloc] init];
    [student study];
    [Student playgame];
    return 0;
}

同時,為了保持工程的簡潔,我們創(chuàng)建的是Command Line Tool 工程。將生成的可執(zhí)行文件用MachOView打開:

可執(zhí)行文件

下面我們看下每個子項存儲的內(nèi)容:

  • __TEXT,__text__TEXT,__stubs
    __TEXT,__text

圖中所示,__TEXT,__text存放的為具體的方法實現(xiàn),如Person類的sleep和walk方法,MachOView 已經(jīng)幫我們翻譯成匯編代碼。引用的外部符號如NSLog記錄在偏移地址0x100001C98的位置,而0x100001C98__TEXT,__stubs中,作為App啟動時動態(tài)鏈接的輔助,動態(tài)鏈接的內(nèi)容我們以后介紹:

__TEXT,__stubs

  • __TEXT,__cstring__Text,__cfstring
    __cstring

很好理解,__cstring中保存的是c字符串,比如sleep方法中引用的 "i am sleeping"。那么,__TEXT,__cfstring保存的是什么內(nèi)容呢?

__cfstring

從上圖可知,實際上__TEXT,__cfstring中保存的是Objc字符串,包括類型,值,大小,如從偏移地址00002058開始記錄了Objc字符串@"i am sleeping"的信息,類型是__CFConstantStringClassReference,大小為13,保存的值為0x100001CF6,從__cstring中可查到0x100001CF6指向的正是"i am sleeping"。從上面的分析看一個Objc字符串占用的空間比c字符串多個塊__TEXT,__cfstring的存儲。

  • __TEXT,__objc_classname

    __objc_classname

    __objc_classname中保存的是類名、協(xié)議名、Category名。

  • __TEXT,__objc_methname

    __objc_methname

    __TEXT,__objc_methname中保存了方法名,包括系統(tǒng)庫中的方法名。

  • __TEXT,__objc_methtype

    __objc_methtype

    方法類型的描述。

  • __DATA,__objc_classlist__DATA,__objc_data

    __objc_classlist

__DATA,__objc_classlist中保存了自定義的類,而類的具體信息在__DATA,__objc_data中:

image.png

0x100002718的地址保存了Person類的信息:ISA、父類、緩存、虛表等。

  • __DATA,__objc_protolist__DATA,__data
    __objc_protolist

    __DATA,__objc_classlist類型,協(xié)議的保存在__DATA,__objc_protolist__DATA,__data
    __data

協(xié)議的信息保存的很詳細,協(xié)議沒有ISA指針,同時對實例方法,類方法,是不是optional的都進行了記錄。

  • __DATA,__objc_const
    __objc_const

__DATA,__objc_const記錄了方法、協(xié)議、屬性列表和類概要信息。

  • __DATA,__objc_selrefs和- __DATA,__objc_classrefs
    __objc_selrefs

記錄了被使用的類和方法,因為Objc是動態(tài)語言,這里只記錄了被顯式使用的類和方法。

  • __DATA,__objc_catlist
    為什么__DATA,__objc_catlist在最后寫呢?因為上面我們構(gòu)建的工程的__DATA,__objc_catlist段是空的,我們明明給Person類加了Person+other分類,為什么在分類列表中沒有相關(guān)的信息呢?
    __objc_catlist

    再想下分類的作用,我們使用分類更多的是給非自定義類添加方法,如系統(tǒng)類或者第三方庫中的類,既然自定義的類都以源碼形式,Xcode會不會將分類的方法當成普通方法處理了呢?為了證實猜想,給系統(tǒng)類NSString添加分類NSString+Trim:
    __objc_catlist

    果然__objc_catlist中出現(xiàn)了數(shù)據(jù),同時可以在__DATA,__objc_const看到Person分類中的ill方法和主類中的sleepwalk方法在內(nèi)存中是連續(xù)的。以上猜想得到了論證:
    __objc_const

    那這個優(yōu)化是發(fā)生在編譯階段還是鏈接階段呢?
    我們改下工程Build Setting中把Mach-O TypeExecutable改成Static Library,即將可執(zhí)行文件改成靜態(tài)庫工程,然后再查看下目標文件:
    目標文件

    從上圖可以看到,編譯/匯編階段主類和分類都生成了.o目標文件,優(yōu)化的過程發(fā)生在鏈接階段。

以上是Objc代碼在可執(zhí)行文件中的存儲方式,了解代碼的存儲方式可以讓我們更深入的理解Objc的運行時,同時給我們優(yōu)化可執(zhí)行文件大小以及解決Appstore對__TEXT段大小限制提供思路:

  • 如通過鏈接參數(shù)進行修改數(shù)據(jù)存放位置:
-Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname
-Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype
-Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname
-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring
-Wl,-rename_section,__TEXT,__const,__RODATA,__const
  • 通過分析__DATA,__objc_selrefs和- __DATA,__objc_classrefs掃描無語類和方法等。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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