iOS:走近Mach-O文件之Objc代码存储

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

  • 可执行文件;

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

  • 动态库,如dylib,framework文件;

Mach-O文件的格式一般包括一个Mach-O头,一系列的载入命令,一个或多个Section,如下图:

Mach-O文件格式

  • Header:记录平台属性,CPU架构、版本,文件类型,加载指令数量、大小,文件状态等信息;

  • Load Commands:Section定义内存空间,获取动态链接器路径,获取应用程序入口,加载动态库等指令;

Section

Section段存放的内容通过MachOView工具查看主要有两大类:__Text__DATA

而不管是__Text还是__DATA下面还都包含若干子项,为了弄清楚每个子项中存放的内容,我们编写一个测试工程,里面尽量包含Objc语音的特性:类,继承,分类,协议,实例方法,类方法等

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协议:

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

最后实现Person类的子类Student,同时实现StudentProtocol协议:

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 的优化,我们在main函数里面显示调用下上面的类/方法:

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;
}

同时,为了保持工程的简洁,我们创建的是Command Line Tool 工程。将生成的可执行文件用MachOView打开:

可执行文件

下面我们看下每个子项存储的内容:

  • __TEXT,__text__TEXT,__stubs
    __TEXT,__text

图中所示,__TEXT,__text存放的为具体的方法实现,如Person类的sleep和walk方法,MachOView 已经帮我们翻译成汇编代码。引用的外部符号如NSLog记录在偏移地址0x100001C98的位置,而0x100001C98__TEXT,__stubs中,作为App启动时动态链接的辅助,动态链接的内容我们以后介绍:

__TEXT,__stubs

  • __TEXT,__cstring__Text,__cfstring
    __cstring

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

__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中保存的是类名、协议名、Category名。

  • __TEXT,__objc_methname

    __objc_methname

    __TEXT,__objc_methname中保存了方法名,包括系统库中的方法名。

  • __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类型,协议的保存在__DATA,__objc_protolist__DATA,__data
    __data

协议的信息保存的很详细,协议没有ISA指针,同时对实例方法,类方法,是不是optional的都进行了记录。

  • __DATA,__objc_const
    __objc_const

__DATA,__objc_const记录了方法、协议、属性列表和类概要信息。

  • __DATA,__objc_selrefs和- __DATA,__objc_classrefs
    __objc_selrefs

记录了被使用的类和方法,因为Objc是动态语言,这里只记录了被显式使用的类和方法。

  • __DATA,__objc_catlist
    为什么__DATA,__objc_catlist在最后写呢?因为上面我们构建的工程的__DATA,__objc_catlist段是空的,我们明明给Person类加了Person+other分类,为什么在分类列表中没有相关的信息呢?
    __objc_catlist

    再想下分类的作用,我们使用分类更多的是给非自定义类添加方法,如系统类或者第三方库中的类,既然自定义的类都以源码形式,Xcode会不会将分类的方法当成普通方法处理了呢?为了证实猜想,给系统类NSString添加分类NSString+Trim:
    __objc_catlist

    果然__objc_catlist中出现了数据,同时可以在__DATA,__objc_const看到Person分类中的ill方法和主类中的sleepwalk方法在内存中是连续的。以上猜想得到了论证:
    __objc_const

    那这个优化是发生在编译阶段还是链接阶段呢?
    我们改下工程Build Setting中把Mach-O TypeExecutable改成Static Library,即将可执行文件改成静态库工程,然后再查看下目标文件:
    目标文件

    从上图可以看到,编译/汇编阶段主类和分类都生成了.o目标文件,优化的过程发生在链接阶段。

以上是Objc代码在可执行文件中的存储方式,了解代码的存储方式可以让我们更深入的理解Objc的运行时,同时给我们优化可执行文件大小以及解决Appstore对__TEXT段大小限制提供思路:

  • 如通过链接参数进行修改数据存放位置:
-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扫描无语类和方法等。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335