dyld链接image后如何加载到内存中?
- dyld -> images -> 内存 -> Person (方法、协议…)
- images(macho) -> 地址 -> 表 -> 类 -> 初始化(rw - ro)
objc_init分析
_objc_init 中注册回调_dyld_objc_notify_register之前,还有一些列初始化操作。
- environ_init() 读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助
- tls_init() 关于线程key的绑定,比如:线程数据的析构函数
- static_init() 运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc 会调用 _objc_init()
- runtime_init() runtime运行时环境初始化
- exception_init() libobjc异常处理系统初始化
- cache_t::init() 缓存条件初始化
- _imp_implementationWithBlock_init() 启动回调机制。通常不会做什么,因为所有的初始化都是惰性的
- _dyld_objc_notify_register(&map_images, load_images, unmap_image)
- 通过x/4gx 查看isa的内存地址
- 通过p/t 查看二进制地址,其末位是1,代表nonpointerIsa ```cpp CCPerson *p = [CCPerson alloc];
x/4gx p 0x108db8e90: 0x011d8001000083d1 0x0000000000000000 0x108db8ea0: 0x0000000108db8f70 0x0000000108db91b0
p/t 0x011d8001000083d1 (long) $1 = 0b0000000100011101100000000000000100000000000000001000001111010001
- **环境变量的全部配置指令,在终端输入**`**export OBJC_HELP=1**`
- 接着在`Scheme`中设置环境变量并勾选
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21860440/1626137861272-14bcb912-c473-483a-9212-95b54522b3b7.png#clientId=ua9d5af68-836e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=1008&id=u91d2ead5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1008&originWidth=1866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=179781&status=done&style=none&taskId=u3d6c3dcf-6942-4e5f-93eb-d2604b16a70&title=&width=1866)
- 重新运行指令,x/4gx 和 p/t, 观察到其末位变为0,为非nonpointerIsa
```cpp
x/4gx p
0x108c74670: 0x00000001000083d0 0x0000000000000000
0x108c74680: 0x65766153534e5b2d 0x6573206c656e6150
p/t 0x00000001000083d0
(long) $1 = 0b0000000000000000000000000000000100000000000000001000001111010000
- 同理,在环境变量中勾选打印load_methods,可以将全部load方法打印出来
tls_init()
本地线程线程池的初始化和析构
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
static_init()
全局静态C++函数调用
- libc calls _objc_init() before dyld would call our static constructors
_objc_init() 调用先于dyld中的调用
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
runtime_init()
runtime初始化两张表
allocatedClasses,已经被开辟过class的一张表
unattachedCategories,分类未链接表
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init();
}
void init(Ts &&... Args) {
new (_storage) Type(std::forward<Ts>(Args)...);
}
exception_init()
异常捕获,程序底层有runloop循环执行,在发生异常时会捕获exceptions
- 如果上层对uncaught_handler进行了赋值,下了句柄,相对于底层对上层有了回调,因此可以在上层处理此异常,防止了崩溃
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
_dyld_objc_notify_register
- &map_images,引用类型,指针传递,保证内外部变化同步,映射整个镜像文件,非常重要,一处发送错误,整个都会发生错乱
- loadImages
map_images——重点
- 除了lookUpImpOrForward,第二个重点函数
- 将mach-o文件映射到内存中,map_images -> map_images_nolock -> _read_images ```cpp _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
**_read_images主要做了哪些事情**
1. **条件控制进行一次的加载**
1. **修复预编译阶段的**[@selector(selector)](/selector)**混乱问题**
1. **错误混乱的类处理**
1. **修复重映射一些没有被镜像文件加载进来的类**
1. **修复一些消息**
1. **当我们的类里面有协议的时候readProtocol**
1. **修复没有被加载的协议**
1. **分类处理**
1. **类的加载处理**
1. **没有被处理的类,优化那些被侵犯的类**
<a name="NjdfJ"></a>
#### 条件控制进行一次的加载
- initializeTaggedPointerObfuscator小对象数据类型,进行混淆
- cache的imp & mask 出现过类似的混淆
```cpp
static void initializeTaggedPointerObfuscator(void)
{
if (!DisableTaggedPointerObfuscation) {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
#if OBJC_SPLIT_TAGGED_POINTERS
// The obfuscator doesn't apply to any of the extended tag mask or the no-obfuscation bit.
objc_debug_taggedpointer_obfuscator &= ~(_OBJC_TAG_EXT_MASK | _OBJC_TAG_NO_OBFUSCATION_MASK);
// Shuffle the first seven entries of the tag permutator.
int max = 7;
for (int i = max - 1; i >= 0; i--) {
int target = arc4random_uniform(i + 1);
swap(objc_debug_tag60_permutations[i],
objc_debug_tag60_permutations[target]);
}
#endif
} else {
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
objc_debug_taggedpointer_obfuscator = 0;
}
}
表的创建 — 容量表
- namedClassesSize总容量 = totalClasses 4 / 3 , 例如 totalClasses = 8时,开辟空间 x = (totalClasses 4 / 3) * 3/4时进行扩容
NXCreateMapTable根据容量创建表-总表
相对位置修复,mach-o文件和dyld中的sel名字相同,其地址可能不同,所以进行局部修复
- 在llvm就加载了sel, sel = 名字 + 地址
-
错误混乱的类处理
resolvedFutureClasses处理未被加载的类,部分类被删除时混乱,还存在内存中
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
核心重点readClass
readClass传入cls,所以这里我们可以打印观察读取的类
- readClass对类做了处理,在mach-o中,class都是符号,得出结果则是NSStackBlock这种处理过的类 ```cpp Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
printf(“%s -CC: 要研究的: - %s\n”,func,cls->nonlazyMangledName()); ```
- 这里是先加载系统类,再加载我们创建的类
将类加载到hashMap和表中,这里还未涉及class_rw、class_ro操作
- addNamedClass(Class cls, constchar *name, Class replacing = nil) -> NXMapInsert(gdb_objc_realized_classes, name, cls),name和cls(地址)以key、value形式存储在hash表中
- ro、rw相关处理都与realizeClassWithoutSwift(cls, ni)相关