前言: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
前言
接着上一篇的内容我们从 _dyld_objc_notify_register(&map_images, load_images, unmap_image)
开始:
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
- 这个方法中的 map_images是在什么时候调用的呢?
- load_images方法执行的时候其内部的一个流程是什么样的?
- 最后,我们现在所在的 dyld 阶段是如何进入到 main 函数主程序的呢?
带着这些疑问开始我们今天的内容。
承接上节课的内容 在 doModInitFunctions
之后 我们加载 libSystem.B.dylib libSystem_initializer
、libsidpatch.dylib libdispatch_init
、libsidpatch.dylib _os_object_init
接着执行到 _objc_init
。 在 _objc_init
中,我们调用 了 _dyld_objc_notify_register
。
_dyld_objc_notify_register
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
registerObjCNotifiers
// _dyld_objc_notify_init
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// 记录要调用的函数,完成赋值
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// 调用“映射”函数与所有镜像映射到目前为止
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
...
}
notifyBatchPartial
此函数中 通过 (*sNotifyObjCMapped)(objcImageCount, paths, mhs);
完成了对 map_images
的调用。
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
...
// 告诉objc关于新的镜像
if ( (onlyHandler == NULL) && ((state == dyld_image_state_bound) || (orLater && (dyld_image_state_bound > state))) && (sNotifyObjCMapped != NULL) ) {
const char* paths[imageCount];
const mach_header* mhs[imageCount];
unsigned objcImageCount = 0;
for (int i=0; i < imageCount; ++i) { ... }
if ( objcImageCount != 0 ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_MAP, 0, 0, 0);
uint64_t t0 = mach_absolute_time();
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
uint64_t t1 = mach_absolute_time(); ImageLoader::fgTotalObjCSetupTime += (t1-t0);
}
}
}
allImagesUnlock();
if ( dontLoadReason != NULL )
throw dontLoadReason;
if ( !preflightOnly && (state == dyld_image_state_dependents_mapped) ) {
const struct mach_header* loadAddresses[imageCount];
const char* loadPaths[imageCount];
for(uint32_t i = 0; i<imageCount; ++i) {
loadAddresses[i] = infos[i].imageLoadAddress;
loadPaths[i] = infos[i].imageFilePath;
}
notifyMonitoringDyld(false, imageCount, loadAddresses, loadPaths);
}
}
}
也就是 _dyld_objc_notify_register
方法进来之后,系统记录下要调用的函数后 就 try
map_images
完成调用, 下面看下 load_images
在上一篇的 recursiveInitialization
环节,
ImageLoader::recursiveInitialization
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {... }
// 记录终止订单
if ( this->needsTermination() ) context.terminationRecorder(this);
// 让objc知道我们将要初始化这个镜像
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// 初始化镜像
// 初始化所有images load_images 赋值
// map load - map() --> cxx = objc
// notifySingle : load()
// cxx = SMobjcBuild
bool hasInitializers = this->doInitialization(context);
// 让任何人知道我们已经初始化了这个镜像
fState = dyld_image_state_initialized;
oldState = fState;
//objc - SMObjc
context.notifySingle(dyld_image_state_initialized, this, NULL);
if ( hasInitializers ) { ... }
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}
就会分析进入到 notifySingle
:
notifySingle
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
if ( handlers != NULL ) { ... }
if ( state == dyld_image_state_mapped ) { ... }
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL)) { ... }
}
}
也就是在 notifySingle 中调用了load_images
。
接下来我们看看,应用程序加载过程中,是如何从 dyld
进入到 main()
的。
dyld_start 汇编源码
通过分析 dyld_start 源码,发现在dyld的最后,会调起系统的 main()
函数。
#if __arm64__ && !TARGET_OS_SIMULATOR
.text
.align 2
.globl __dyld_start
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
mov x0, #0
mov x1, #0
stp x1, x0, [sp, #-16]! // make aligned terminating frame
mov fp, sp // set up fp to point to terminating frame
sub sp, sp, #16 // make room for local variables
#if __LP64__
ldr x0, [x28] // get app's mh into x0
ldr x1, [x28, #8] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
add x2, x28, #16 // get argv into x2
#else
ldr w0, [x28] // get app's mh into x0
ldr w1, [x28, #4] // get argc into x1 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment)
add w2, w28, #8 // get argv into x2
#endif
adrp x3,___dso_handle@page
add x3,x3,___dso_handle@pageoff // get dyld's mh in to x4
mov x4,sp // x5 has &startGlue
// 调用 dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
mov x16,x0 // save entry point address in x16
#if __LP64__
ldr x1, [sp]
#else
ldr w1, [sp]
#endif
cmp x1, #0
b.ne Lnew
// LC_UNIXTHREAD way, 清理堆栈然后跳转到结果
#if __LP64__
add sp, x28, #8 // restore unaligned stack pointer without app mh
#else
add sp, x28, #4 // restore unaligned stack pointer without app mh
#endif
#if __arm64e__
braaz x16 // jump to the program's entry point
#else
br x16 // jump to the program's entry point
#endif
// LC_MAIN case, 设置调用main()的堆栈
Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib
#if __LP64__
ldr x0, [x28, #8] // main param1 = argc
add x1, x28, #16 // main param2 = argv
add x2, x1, x0, lsl #3
add x2, x2, #8 // main param3 = &env[0]
mov x3, x2
Lapple: ldr x4, [x3]
add x3, x3, #8
#else
ldr w0, [x28, #4] // main param1 = argc
add x1, x28, #8 // main param2 = argv
add x2, x1, x0, lsl #2
add x2, x2, #4 // main param3 = &env[0]
mov x3, x2
Lapple: ldr w4, [x3]
add x3, x3, #4
#endif
cmp x4, #0
b.ne Lapple // main param4 = apple
#if __arm64e__
braaz x16
#else
br x16
#endif
#endif // __arm64__ && !TARGET_OS_SIMULATOR
接下来真机验证:
验证
我们知道 我们准备的项目 先是 [viewcontroller load]
调用 然后是 cxx
方法 ,最后进入到 main()
函数,那么,在项目中,我们在cxx方法处打一个断点,开始调试看看:
断点进来之后,debug显示汇编代码,然后逐步执行,看到在 dyld_start
流程的最后,会主动的去调起 main 函数
,
到这里,我们就解释了上篇文章抛出的三个问题。
所以,补充一下昨天的 dyld流程图:
dyld流程图
补充
首先要探索的流程就是 map_images
map_images
/***********************************************************************
* map_images
* 处理dyld映射到的给定镜像
* 在使用特定于abi的锁后调用与abi无关的代码
*
* 锁定:写锁runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
之后就从 map_images
来到了 map_images_nolock
map_images_nolock
/***********************************************************************
* map_images_nolock
* 处理dyld映射到的给定镜像
* 所有的类注册和修正都被执行(或延迟等待)
* 发现丢失的超类等,并调用+load方法
*
* Info[]是自下而上的顺序,即libobjc将在早些时候
* 数组比任何链接到libobjc的库
*
* 锁定:loadMethodLock(旧的)或runtimeLock(新的)由map_images获取
**********************************************************************/
#if __OBJC2__
#include "objc-file.h"
#else
#include "objc-file-old.h"
#endif
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// 如果需要,执行首次初始化
// 该函数在普通库初始化器之前调用
// 修复延迟初始化直到找到一个使用objc的镜像?
if (firstTime) { ... }
if (PrintImages) { ... }
// 找到所有带有Objective-C元数据的镜像
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) { ... }
}
// 执行必须延迟到的一次性运行时初始化
// 可执行文件本身被找到。这需要提前完成
// 进一步的初始化
// 可执行文件可能不在这个infoList中,如果
// 可执行程序不包含Objective-C代码,而是Objective-C
// 稍后动态加载
if (firstTime) { ... }
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
// 在一切设置完成后调用镜像加载函数
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
找到所有带有Objective-C元数据的镜像后,之后就开始对镜像内容进行初始处理。
总结
用户将我们开发的app安装到手机后,一直存储在手机的磁盘中,一旦用户点击了我们的app,那么,系统就会进入到dyld流程中,为我们的app中所使用的各种动静态库做链接,加载各种镜像文件,接着就是加载我们项目中的各种类文件,最终,进入到 main()
函数,之后是 AppDelegate
或 Secen
来处理我们为用户开发的各种功能。
本篇内容算是,上述流程中 dyld 流程探索的一个总结, 下一篇,我们开始类的加载流程探索
。 大家,加油!!!