本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
背景
上篇文章给大家介绍了addHeader
方法的一部分逻辑:当opt中有header_info的缓存情况下直接取出,本文笔者将继续和大家讨论没有缓存的情况下设置mhdr的过程。
分析
对应的源代码为:
// Didn't find an hinfo in the dyld shared cache.
// Weed out duplicates
for (hi = FirstHeader; hi; hi = hi->getNext()) {
if (mhdr == hi->mhdr()) return NULL;
}
// Locate the __OBJC segment
size_t info_size = 0;
unsigned long seg_size;
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
if (!objc_segment && !image_info) return NULL;
// Allocate a header_info entry.
// Note we also allocate space for a single header_info_rw in the
// rw_data[] inside header_info.
hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
// Set up the new header_info entry.
hi->setmhdr(mhdr);
// Install a placeholder image_info if absent to simplify code elsewhere
static const objc_image_info emptyInfo = {0, 0};
hi->setinfo(image_info ?: &emptyInfo);
hi->setLoaded(true);
hi->setAllClassesRealized(NO);
调用栈仍然位于:
_objc_init
|-dyld_objc_notify_register
|-map_2_images
|-map_images_nolock
|-addHeader
下。
以上代码我们逐步分析:
for (hi = FirstHeader; hi; hi = hi->getNext()) {
if (mhdr == hi->mhdr()) return NULL;
}
如果是第一次进入该方法,FirstHeader
是没有值的。因此这个循环走不进去。
接着是下面三行代码:
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
if (!objc_segment && !image_info) return NULL;
我们点进去看就会发现,_getObjcImageInfo
获取的是__DATA,__objc_imageinfo
的数据;而第二行是获取的segment
为__OBJC
的数据。
那这两个区或者段的作用是什么呢,大家可以参考这篇文章:深入理解Macho文件(二)- 消失的__OBJC段与新生的__DATA段
其实不看这篇文章我们也大概能了解其含义,因为第一行代码返回的是数据结构objc_image_info
,其定义如下:
// Description of an Objective-C image.
// __DATA,__objc_imageinfo stores one of these.
typedef struct objc_image_info {
uint32_t version; // currently 0
uint32_t flags;
#if __cplusplus >= 201103L
private:
enum : uint32_t {
IsReplacement = 1<<0, // used for Fix&Continue, now ignored
SupportsGC = 1<<1, // image supports GC
RequiresGC = 1<<2, // image requires GC
OptimizedByDyld = 1<<3, // image is from an optimized shared cache
CorrectedSynthesize = 1<<4, // used for an old workaround, now ignored
IsSimulated = 1<<5, // image compiled for a simulator platform
HasCategoryClassProperties = 1<<6, // class properties in category_t
SwiftVersionMaskShift = 8,
SwiftVersionMask = 0xff << SwiftVersionMaskShift // Swift ABI version
};
public:
enum : uint32_t {
SwiftVersion1 = 1,
SwiftVersion1_2 = 2,
SwiftVersion2 = 3,
SwiftVersion3 = 4
};
public:
bool isReplacement() const { return flags & IsReplacement; }
bool supportsGC() const { return flags & SupportsGC; }
bool requiresGC() const { return flags & RequiresGC; }
bool optimizedByDyld() const { return flags & OptimizedByDyld; }
bool hasCategoryClassProperties() const { return flags & HasCategoryClassProperties; }
bool containsSwift() const { return (flags & SwiftVersionMask) != 0; }
uint32_t swiftVersion() const { return (flags & SwiftVersionMask) >> SwiftVersionMaskShift; }
#endif
} objc_image_info;
代码量有点多,但其实我们只需要关注#if __cplusplus >= 201103L
以上的部分即可。其中#if __cplusplus >= 201103L
的含义大概说一下,就是判断编译器环境是不是支持C++11。
因此:
version这个字段目前永远为0。flags是用于做表示需要支持的特性的,比如是否需要/支持 Garbage Collection。
后面的分配内存的代码很好理解了:
hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
分配的空间大小是header_info
大小与header_info_rw
大小之和,原因在注释中也说了,只是要预留给header_info_rw
这个结构体内的数组。等等,这个header_info_rw
又是什么呢?我们再次点击他,进入其定义:
// Split out the rw data from header info. For now put it in a huge array
// that more than exceeds the space needed. In future we'll just allocate
// this in the shared cache builder.
typedef struct header_info_rw {
bool getLoaded() const {
return isLoaded;
}
void setLoaded(bool v) {
isLoaded = v ? 1: 0;
}
bool getAllClassesRealized() const {
return allClassesRealized;
}
void setAllClassesRealized(bool v) {
allClassesRealized = v ? 1: 0;
}
header_info *getNext() const {
return (header_info *)(next << 2);
}
void setNext(header_info *v) {
next = ((uintptr_t)v) >> 2;
}
private:
uintptr_t isLoaded : 1;
uintptr_t allClassesRealized : 1;
uintptr_t next : 62;
} header_info_rw;
对,里面存放了可读写的数据。这里先不多说这个结构体了,如果后面碰到的话,我们再继续研究。
我们继续看代码,
hi->setmhdr(mhdr);
终于!封装mhdr的方法出现了。于是迫不及待的点进去看,却发现如下代码:
const headerType *mhdr() const {
return (const headerType *)(((intptr_t)&mhdr_offset) + mhdr_offset);
}
void setmhdr(const headerType *mhdr) {
mhdr_offset = (intptr_t)mhdr - (intptr_t)&mhdr_offset;
}
what?不是应该是将mhdr设置到header_info
里,并进行存储么。为什么是一串 offset和类型转换。笔者看到这段代码的一开始是懵逼的。直到看到了这篇文章:
C语言--通过结构体成员的地址获取结构体变量的地址大概意思是:
C 语言的结构体可以将不同类型的对象聚合到一个对象中,在内存中,编译器按照成员列表顺序分别为每个结构体变量成员分配内存,但由于 C 的内存对齐机制以及不同机器间的差异,各个成员之间可能会有间隙,所以不能简单的通过成员类型所占的字长来推断其它成员或结构体对象的地址。
如果要计算结构体中某成员相对于该结构体首地址的偏移量,一般第一个反应就是该成员的地址与结构体对象的首地址之间的字节数。
所以,这么设计其实是从效率和空间上都是非常明智的选择:我们只需要计算出mhdr的内存地址,从而获取该对象,而不要手动将其赋值给当前对象。省略了很多赋值的麻烦步骤。
最后的最后,我们看代码:
static const objc_image_info emptyInfo = {0, 0};
hi->setinfo(image_info ?: &emptyInfo);
hi->setLoaded(true);
hi->setAllClassesRealized(NO);
大概意思和我们刚刚的偏移的处理方式是一样的了。至此,header_info终于分析完成!
参考
广告
我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。