前面一篇 《iOS APP 启动优化(三):dyld(the dynamic link editor)动态链接器和 dyld 加载流程.md》分析到 dyld::_main
函数的整体流程,还没有分析到每一个细节,那么就由本篇继续分析。
接上篇。
下面我们对 initializeMainExecutable
进行分析。
initializeMainExecutable
上面我们分析了 dyld::_main
函数的总体流程,其中 initializeMainExecutable
函数进行了所有的 initializers
,下面我们看一下它的执行过程。
void initializeMainExecutable()
{
// record that we've reached this step
// 在 gLinkContext 全局变量中标记现在 main executable 开始执行 Initializers
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs(为任何插入的 dylibs 运行 initialzers)
// 创建一个 struct InitializerTimingList 的数组,用来记录 Initializer 所花费的时间
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
// sImageRoots 是一个静态全局变量:static std::vector<ImageLoader*> sImageRoots;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
// ⬇️ 调用 ImageLoader 的 runInitializers 函数
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
// ⬇️ 为 main executable 及其带来的一切运行 initializers
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
// 注册 cxa_atexit() 处理程序以在此进程退出时在所有加载的 image 中运行静态终止符
if ( gLibSystemHelpers != NULL )
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// dump info if requested
// 根据环境变量判断是否需要进行这些信息打印
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoader::printStatistics((unsigned int)allImagesCount(), initializerTimes[0]);
if ( sEnv.DYLD_PRINT_STATISTICS_DETAILS )
ImageLoaderMachO::printStatisticsDetails((unsigned int)allImagesCount(), initializerTimes[0]);
}
复制代码
gLinkContext
是一个 ImageLoader::LinkContext gLinkContext;
类型的全局变量,LinkContext 是在 class ImageLoader
中定义的一个结构体,其中定义了很多函数指针和成员变量,来记录和处理 Link 的上下文。其中 bool startedInitializingMainExecutable;
则是用来记录标记 Main Executable 开始进行 Initializing
了,这里是直接把它的值置为 true
。
InitializerTimingList
也是在 class ImageLoader
中定义的一个挺简单的结构体。用来记录 Initializer
所花费的时间。
struct InitializerTimingList
{
uintptr_t count;
struct {
const char* shortName;
uint64_t initTime;
} images[1];
void addTime(const char* name, uint64_t time);
};
// 给指定的 image 追加时间
void ImageLoader::InitializerTimingList::addTime(const char* name, uint64_t time)
{
for (int i=0; i < count; ++i) {
if ( strcmp(images[i].shortName, name) == 0 ) {
images[i].initTime += time;
return;
}
}
images[count].initTime = time;
images[count].shortName = name;
++count;
}
复制代码
看到 addTime
函数是为当前记录到的 image 追加时间。
runInitializers
下面看一下 sImageRoots[i]
和 sMainExecutable
都要调用的 runInitializers
函数,runInitializers
函数定义在 ImageLoader
类中。
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
// 计时开始
uint64_t t1 = mach_absolute_time();
// 记录当前所处的线程
mach_port_t thisThread = mach_thread_self();
// UninitedUpwards 是在 ImageLoader 类内部定义的结构体,
// 它的 imagesAndPaths 成员变量用来记录 image 和 image 的 path
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
// 核心 ⬇️⬇️⬇️
processInitializers(context, thisThread, timingInfo, up);
// 通知已经处理完
context.notifyBatch(dyld_image_state_initialized, false);
// deallocate 任务
mach_port_deallocate(mach_task_self(), thisThread);
// 执行结束时的计时
uint64_t t2 = mach_absolute_time();
// 统计时长
fgTotalInitTime += (t2 - t1);
}
复制代码
在 runInitializers
函数中我们看到了两个老面孔,在学习 GCD 源码时见过的 mach_absolute_time
和 mach_thread_self
一个用来统计初始化时间,一个用来记录当前线程。
UninitedUpwards
是 ImageLoader
类内部定义的一个超简单的结构体,其中的成员变量 std::pair<ImageLoader*, const char*> imagesAndPaths[1];
一个值记录 ImageLoader
的地址,另一个值记录该 ImageLoader
的路径。
struct UninitedUpwards
{
uintptr_t count;
std::pair<ImageLoader*, const char*> imagesAndPaths[1];
};
复制代码
可看到 processInitializers(context, thisThread, timingInfo, up);
是其中最重要的函数,下面来一起看看。
processInitializers
处理初始化过程,看到 processInitializers
中 recursiveInitialization
函数的递归调用,之所以递归调用,是因为我们的动态库或者静态库会引入其它类库,而且表是个多表结构,所以需要递归实例化。
// <rdar://problem/14412057> upward dylib initializers can be run too soon
// To handle dangling dylibs which are upward linked but not downward, all upward linked dylibs
// have their initialization postponed until after the recursion through downward dylibs
// has completed.
// 为了处理向上链接但不向下链接的悬空控件,所有向上链接的控件都将其初始化推迟到完成通过向下控件的递归完成以后
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards& ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of uninitialized upward dependencies.
// 在 image 列表中所有 image 调用递归实例化,以建立未初始化的向上依赖关系新列表
for (uintptr_t i=0; i < images.count; ++i) {
images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
// 如果还有任何向上的依赖关系,将其初始化
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
复制代码
images.imagesAndPaths[i].first
是 ImageLoader
指针(ImageLoader *
),即调用 class ImageLoader
的 recursiveInitialization
函数,下面我们看一下 recursiveInitialization
函数的定义。
recursiveInitialization
recursiveInitialization
函数定义较长,其中比较重要的是 // initialize lower level libraries first 下的 for 循环,进行循环判断是否都加载过,没有的话就再执行 dependentImage->recursiveInitialization
因为我们前面说的动态库可能会依赖其它库。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
// 递归锁结构体,会持有当前所在的线程
// struct recursive_lock {
// recursive_lock(mach_port_t t) : thread(t), count(0) {}
// mach_port_t thread;
// int count;
// };
recursive_lock lock_info(this_thread);
// 递归加锁
recursiveSpinLock(lock_info);
// dyld_image_state_dependents_initialized = 45, // Only single notification for this
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
// 首先初始化较低级别的库
// unsigned int libraryCount() const { return fLibraryCount; }
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
// 依赖库的递归初始化
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
// 记录终止命令
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
// 让 objc 知道我们将要初始化这个 image 了
uint64_t t1 = mach_absolute_time(); // ⬅️ 起点计时
fState = dyld_image_state_dependents_initialized;
oldState = fState;
// 核心 ⬇️⬇️⬇️
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// ⬆️⬆️⬆️
// initialize this image
// 初始化 image
// 这里便是最终的执行 initialize,那它内部的内容是什么呢?就是下面两个函数!
// mach-o has -init and static initializers
// doImageInit(context);
// doModInitFunctions(context); // __mod_init_func 区的 Initializer 执行
// 核心 ⬇️⬇️⬇️
bool hasInitializers = this->doInitialization(context);
// ⬆️⬆️⬆️
// let anyone know we finished initializing this image
// 让任何人知道我们完成了这个 image 的初始化
fState = dyld_image_state_initialized;
oldState = fState;
// void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*);
// 核心 ⬇️⬇️⬇️
context.notifySingle(dyld_image_state_initialized, this, NULL);
// ⬆️⬆️⬆️
// 进行计时
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time(); // ⬅️ 终点计时
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
// 如果初始化失败,则解锁抛错
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
// 递归解锁
recursiveSpinUnLock();
}
复制代码
recursiveInitialization
函数内会有动态库依赖的递归调用初始化,主要研究的代码是 notifySingle
和 doInitialization
。
看到这里的时候,我们可以先稍微停顿一下,回忆文章开头处,在 +load
函数打断点,然后 bt
指令打印函数调用栈,现在正是到达了其中的 recursiveInitialization
和 notifySingle
。
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: Test_ipa_Simple`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:17:5
frame #1: libobjc.A.dylib`load_images + 944
frame #2: dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 464
frame #3: dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 512
frame #4: dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 184
frame #5: dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 92
frame #6: dyld`dyld::initializeMainExecutable() + 216
frame #7: dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 5216
frame #8: dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 396
frame #9: dyld`_dyld_start + 56
(lldb)
复制代码
可看到从 _dyld_start
-> dyldbootstrap::start
-> dyld::_main
-> dyld::initializeMainExecutable
-> ImageLoader::runInitializers
-> ImageLoader::processInitializers
-> ImageLoader::recursiveInitialization
-> dyld::notifySingle
-> libobjc.A.dylib load_images
-> +[ViewController load]
的一路调用流程,而我们目前则到了其中的 notifySingle
。
下面我们接着分析 recursiveInitialization
函数。
在 recursiveInitialization
函数内部的 // let objc know we are about to initialize this image
注释往下走,它们才是 recursiveInitialization
函数最重要的部分,我们首先看一下:
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
复制代码
函数的调用,首先这里我们一直往上追溯的话可发现 context
参数即在 initializeMainExecutable
函数中传入的 ImageLoader::LinkContext gLinkContext;
这个全局变量。
// notifySingle 是这样的一个函数指针
void (*notifySingle)(dyld_image_states, const ImageLoader* image, InitializerTimingList*);
复制代码
然后在 dyld/src/dyld2.cpp 文件中的:
static void setContext(const macho_header* mainExecutableMH, int argc, const char* argv[], const char* envp[], const char* apple[]) { ... }
复制代码
这个静态全局函数中,gLinkContext.notifySingle
被赋值为 ¬ifySingle;
,而这个 notifySingle
函数是在 dyld2.cpp 中定义的一个静态全局函数。看到这里,我们即可确定 recursiveInitialization
函数中调用的 context.notifySingle
即 gLinkContext.notifySingle
,即 dyld/src/dyld2.cpp 中的 notifySingle
这个静态全局函数。
notifySingle
然后我们直接在 dyld2.cpp 中搜索 notifySingle
函数, 它是一个静态全局函数,我们现在看一下它的实现:
首先我们看一组在 ImageLoader.h
中定义的枚举,它们每个值都表示 dyld 过程中 image 的状态。
enum dyld_image_states
{
dyld_image_state_mapped = 10, // No batch notification for this
dyld_image_state_dependents_mapped = 20, // Only batch notification for this
dyld_image_state_rebased = 30,
dyld_image_state_bound = 40,
dyld_image_state_dependents_initialized = 45, // Only single notification for this
dyld_image_state_initialized = 50,
dyld_image_state_terminated = 60 // Only single notification for this
};
复制代码
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
//dyld::log("notifySingle(state=%d, image=%s)\n", state, image->getPath());
// 下面第一个 if 的内容是回调 image 的状态变化(dyld_image_state_change_handler)
std::vector<dyld_image_state_change_handler>* handlers = stateToHandlers(state, sSingleHandlers);
if ( handlers != NULL ) {
dyld_image_info info;
info.imageLoadAddress = image->machHeader();
info.imageFilePath = image->getRealPath();
info.imageFileModDate = image->lastModified();
for (std::vector<dyld_image_state_change_handler>::iterator it = handlers->begin(); it != handlers->end(); ++it) {
const char* result = (*it)(state, 1, &info);
if ( (result != NULL) && (state == dyld_image_state_mapped) ) {
//fprintf(stderr, " image rejected by handler=%p\n", *it);
// make copy of thrown string so that later catch clauses can free it
const char* str = strdup(result);
throw str;
}
}
}
// 如果 state 是 dyld_image_state_mapped 时对 Shared Cache 的一些处理
// 已知在 recursiveInitialization 函数,两次调用 context.notifySingle 函数,
// state 参数分别传入的:dyld_image_state_dependents_initialized 和 dyld_image_state_initialized,
// 所以这里我们暂时忽略 state 是 dyld_image_state_mapped 的情况
if ( state == dyld_image_state_mapped ) {
// <rdar://problem/7008875> Save load addr + UUID for images from outside the shared cache
// <rdar://problem/50432671> Include UUIDs for shared cache dylibs in all image info when using private mapped shared caches
if (!image->inSharedCache()
|| (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion)) {
dyld_uuid_info info;
if ( image->getUUID(info.imageUUID) ) {
info.imageLoadAddress = image->machHeader();
addNonSharedCacheImageUUID(info);
}
}
}
// ⬇️⬇️⬇️ 这里是我们要重点看的
// sNotifyObjCInit 是一个静态全局变量:static _dyld_objc_notify_init sNotifyObjCInit;
// 其实是一个函数指针:typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
// 然后 image->notifyObjC() 默认为 1
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 函数,那么这个全局变量的函数指针在什么时候赋值的呢?
(*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) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
// 当 state 是 dyld_image_state_terminated 时的处理操作,我们可忽略
// mach message csdlc about dynamically unloaded images
if ( image->addFuncNotified() && (state == dyld_image_state_terminated) ) {
notifyKernel(*image, false);
const struct mach_header* loadAddress[] = { image->machHeader() };
const char* loadPath[] = { image->getPath() };
notifyMonitoringDyld(true, 1, loadAddress, loadPath);
}
}
复制代码
可看到 notifySingle
函数的核心便是这个 (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
函数的执行!
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
static _dyld_objc_notify_init sNotifyObjCInit;
复制代码
sNotifyObjCInit
是一个静态全局的名为 _dyld_objc_notify_init
的函数指针。然后在 dyld2.cpp 文件中搜索,可看到在 registerObjCNotifiers
函数中,有对 sNotifyObjCInit
这个全局变量进行赋值操作。
registerObjCNotifiers
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
// 记录要调用的函数
sNotifyObjCMapped = mapped;
// ⬇️⬇️⬇️
sNotifyObjCInit = init;
// ⬆️⬆️⬆️
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
// ⬇️⬇️⬇️
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
复制代码
其中进行赋值的三个全局变量定义在一起,即为三个类型不同的函数指针。
typedef void (*_dyld_objc_notify_mapped)(unsigned count, const char* const paths[], const struct mach_header* const mh[]);
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
typedef void (*_dyld_objc_notify_unmapped)(const char* path, const struct mach_header* mh);
static _dyld_objc_notify_mapped sNotifyObjCMapped;
static _dyld_objc_notify_init sNotifyObjCInit;
static _dyld_objc_notify_unmapped sNotifyObjCUnmapped;
复制代码
我们看到 registerObjCNotifiers
函数的 _dyld_objc_notify_init init
参数会直接赋值给 sNotifyObjCInit
,并在下面的 for 循环中进行调用。
_dyld_objc_notify_register
那么什么时候调用 registerObjCNotifiers
函数呢?_dyld_objc_notify_init init
的实参又是什么呢?我们全局搜索 registerObjCNotifiers
函数。(其实看到这里,看到 registerObjCNotifiers
函数的形参我们可能会有一点印象了,之前看 objc 的源码时的 _objc_init
函数中涉及到 image 部分。)
我们全局搜索 registerObjCNotifiers
函数,在 dyld/src/dyldAPIs.cpp 中的 _dyld_objc_notify_register
函数内部调用了 registerObjCNotifiers
函数(属于 namespace dyld)。
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);
}
复制代码
这下就连上了,_dyld_objc_notify_register
函数在 objc 源码中也有调用过,并且就在 _objc_init
函数中。下面我们先看一下 _dyld_objc_notify_register
函数的声明。
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to call dlopen() on them to keep them from being unloaded.
// During the call to _dyld_objc_notify_register(), dyld will call the "mapped" function with already loaded objc images.
// During any later dlopen() call, dyld will also call the "mapped" function.
// Dyld will call the "init" function when dyld would be called initializers in that image.
// This is when objc calls any +load methods in that image.
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
复制代码
_dyld_objc_notify_register
函数仅供 objc runtime 使用。注册在 mapped、unmapped 和 initialized objc images 时要调用的处理程序。Dyld 将使用包含 objc-image-info section 的 images 数组回调 mapped 函数。那些 dylib 的 images 将自动增加引用计数,因此 objc 将不再需要对它们调用 dlopen() 以防止它们被卸载。在调用 _dyld_objc_notify_register()
期间,dyld 将使用已加载的 objc images 调用 mapped 函数。在以后的任何 dlopen() 调用中,dyld 还将调用 mapped 函数。当 dyld 在该 image 中调用 initializers 时,Dyld 将调用 init 函数。这是当 objc 调用 image 中的任何 +load 方法时。
Note: only for use by objc runtime
提示我们 _dyld_objc_notify_register
函数仅提供给 objc runtime 使用,那么我们就去 objc4 源码中寻找 _dyld_objc_notify_register
函数的调用。
_objc_init
下面我们在 objc4-781 中搜一下 _dyld_objc_notify_register
函数,在 _objc_init
中我们看到了它的身影。
void _objc_init(void)
{
// initialized 作为一个局部静态变量,只能初始化一次,保证 _objc_init 全局只执行一次
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
// ⬇️⬇️⬇️
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
复制代码
那么追了这么久,上面的拿着 init
形参对 sNotifyObjCInit
赋值的实参便是 load_images
,另外两个则是 &map_images
赋值给 sNotifyObjCMapped
和 unmap_image
赋值给 sNotifyObjCUnmapped
。
看到这里我们就能连上了,load_images
会调用 image 中所有父类、子类、分类中的 +load
函数,前面的文章中我们有详细分析过,这里就不展开 +load
函数的执行过程了。
那么到这里我们就能直接从源码层面连上了:recursiveInitialization
-> context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo)
-> (*sNotifyObjCInit)(image->getRealPath(), image->machHeader())
,而这个 sNotifyObjCInit
便是 _objc_init
函数中调用 _dyld_objc_notify_register
注册进来的 load_images
函数,即在 objc 这层注册了回调函数,然后在 dyld 调用这些回调函数。(当前看的两个苹果开源的库:objc4-781 和 dyld-832.7.3)
这样我们最开始的 bt 指令的截图中出现的函数就都浏览一遍了:_dyld_start
-> dyldbootstrap::start
-> dyld::_main
-> dyld::initializeMainExecutable
-> ImageLoader::runInitializers
-> ImageLoader::processInitializers
-> ImageLoader::recursiveInitialization
-> dyld::notifySingle
-> libobjc.A.dylib load_images
。
在 _objc_init
中注册回调函数,在 dyld 中调用这些回调函数。
在 _objc_init
中注册回调函数,在 dyld 中调用这些回调函数。
在 _objc_init
中注册回调函数,在 dyld 中调用这些回调函数。
doInitialization
那么看到这里,我们心中不免有一个疑问,既然在 _objc_init
函数内部调用 _dyld_objc_notify_register
函数注册了 dyld 的回调函数,那什么时候调用 _objc_init
呢?_objc_init
是 libobjc
这个 image 的初始化函数,那么 libobjc
什么时候进行初始化呢?
我们依然顺着上面的 recursiveInitialization
函数往下,在 context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
调用的下面便是:
// initialize this image
// 初始化 image
bool hasInitializers = this->doInitialization(context);
复制代码
那么下面我们一起来看这个 doInitialization
函数。
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// ⬇️⬇️⬇️
// mach-o has -init and static initializers
// mach-o 包含 -init 和 static initializers(静态初始化方法)
// for 循环调用 image 的初始化方法(libSystem 库需要第一个初始化)
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
复制代码
其中的核心是 doImageInit(context);
和 doModInitFunctions(context);
两个函数调用。
在 doImageInit(context);
中,核心就是 for 循环调用 image 的初始化函数,但是需要注意的是 libSystem 库需要第一个初始化。
doImageInit
下面我们看一下 doImageInit
函数的实现:
// rdar://problem/17973316 libSystem initializer must run first
void ImageLoaderMachO::doImageInit(const LinkContext& context)
{
// fHasDashInit 是 class ImageLoaderMachO 其中一个位域:fHasDashInit : 1,
if ( fHasDashInit ) {
// load command 的数量
// fMachOData 是 class ImageLoaderMachO 的一个成员变量:const uint8_t* fMachOData;
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
// 从 mach header 直接寻址到 load_command 的位置
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
// 下面是对 load_command 进行遍历
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
// 看到这里只处理 #define LC_ROUTINES_COMMAND LC_ROUTINES_64 类型的 load_command
//(大概是这种类型的 load_command 就是用来放 Initializer 的吗?)
case LC_ROUTINES_COMMAND:
// __LP64__ 下 macho_routines_command 继承自 routines_command_64,
// struct macho_routines_command : public routines_command_64 {};
// 在 darwin-xnu/EXTERNAL_HEADERS/mach-o/loader.h 下搜 routines_command_64 可看到如下定义
/*
* The 64-bit routines command. Same use as above.
*/
// struct routines_command_64 { /* for 64-bit architectures */
// uint32_t cmd; /* LC_ROUTINES_64 */
// uint32_t cmdsize; /* total size of this command */
// uint64_t init_address; /* address of initialization routine 初始化程序地址 */
// uint64_t init_module; /* index into the module table that the init routine is defined in */
// uint64_t reserved1;
// uint64_t reserved2;
// uint64_t reserved3;
// uint64_t reserved4;
// uint64_t reserved5;
// uint64_t reserved6;
// };
// 这里是找到当前这一条 load_command 的 Initializer
Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
#if __has_feature(ptrauth_calls)
func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
// libSystem 的 initializer 必须首先运行
// extern struct dyld_all_image_infos* gProcessInfo; 声明在 dyld 这个命名空间中
// 这里如果 libSystem.dylib 没有初始化(及 link)则抛错
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
// 执行 initializer
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
break;
}
// cmd 指向下一条 load_command
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
复制代码
在 doImageInit
函数内部看到其中就是遍历当前 image 的 load command,找到其中 LC_ROUTINES_COMMAND
类型的 load command 然后通过内存地址偏移找到 Initializer
函数的位置并执行。(Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
) 其中的 if ( ! dyld::gProcessInfo->libSystemInitialized )
是判断 libSystem 必须先初始化,否则就直接抛错。
看了 doImageInit
的函数实现,我们肯定对其中的那行 if ( ! dyld::gProcessInfo->libSystemInitialized )
记忆犹新,那么为什么 libSystem.dylib
要第一个初始化,是因为 libobjc
库的初始化是在 libDispatch
库执行的,而 libDispatch
库是在 libSystem
库初始化后执行。那么我们怎么验证这个呢?
我们在 objc4-781 源码的 _objc_init
处打一个断点并运行,可看到如下的堆栈信息。
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001860964a0 libobjc.A.dylib`_objc_init // ⬅️ 这里
frame #1: 0x00000001002f5014 libdispatch.dylib`_os_object_init + 24 // ⬅️ 这里
frame #2: 0x0000000100308728 libdispatch.dylib`libdispatch_init + 476 // ⬅️ 这里
frame #3: 0x000000018f8777e8 libSystem.B.dylib`libSystem_initializer + 220 // ⬅️ 这里
frame #4: 0x000000010003390c dyld`ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 868
frame #5: 0x0000000100033b94 dyld`ImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 56
frame #6: 0x000000010002d84c dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 620
frame #7: 0x000000010002d794 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 436
frame #8: 0x000000010002b300 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 192
frame #9: 0x000000010002b3cc dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 96
frame #10: 0x00000001000167fc dyld`dyld::initializeMainExecutable() + 140
frame #11: 0x000000010001cb98 dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 7388
frame #12: 0x0000000100015258 dyld`dyldbootstrap::start(dyld3::MachOLoaded const*, int, char const**, dyld3::MachOLoaded const*, unsigned long*) + 476
frame #13: 0x0000000100015038 dyld`_dyld_start + 56
(lldb)
复制代码
自 _dyld_start
到 ImageLoaderMachO::doModInitFunctions
的函数调用我们都已经比较熟悉了,这里我们主要看的是 libSystem_initializer
到 _objc_init
的调用过程。
libobjc.A.dylib`_objc_init
⬆️⬆️⬆️
libdispatch.dylib`_os_object_init
⬆️⬆️⬆️
libdispatch.dylib`libdispatch_init
⬆️⬆️⬆️
libSystem.B.dylib`libSystem_initializer
复制代码
恰好这些函数所处的库都是开源的,下面我们下载源码一探究竟。
libSystem_initializer
我们看到了 libSystem_initializer
函数的调用,我们去下载源码:Libsystem,打开源码,全局搜索 libSystem_initializer
,可在 Libsystem/init.c 中看到 libSystem_initializer
函数的定义如下(只摘录一部分):
// libsyscall_initializer() initializes all of libSystem.dylib
// <rdar://problem/4892197>
__attribute__((constructor))
static void
libSystem_initializer(int argc,
const char* argv[],
const char* envp[],
const char* apple[],
const struct ProgramVars* vars)
{
static const struct _libkernel_functions libkernel_funcs = {
.version = 4,
// V1 functions
#if !TARGET_OS_DRIVERKIT
.dlsym = dlsym,
#endif
.malloc = malloc,
.free = free,
.realloc = realloc,
._pthread_exit_if_canceled = _pthread_exit_if_canceled,
...
复制代码
下面我们摘录 libSystem_initializer
函数中比较重要的内容:
// 对内核的初始化
__libkernel_init(&libkernel_funcs, envp, apple, vars);
// 对平台的初始化
__libplatform_init(NULL, envp, apple, vars);
// 对线程的初始化(初始化后我们的 GCD 才能使用)
__pthread_init(&libpthread_funcs, envp, apple, vars);
// 对 libc 的初始化
_libc_initializer(&libc_funcs, envp, apple, vars);
// 对 malloc 初始化
// TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
// Note that __malloc_init() will also initialize ASAN when it is present
__malloc_init(apple);
// 对 dyld 进行初始化(dyld_start 时 dyld 并没有初始化,dyld 也是一个库)
_dyld_initializer();
// 对 libdispatch 进行初始化,上面的堆栈信息中我们也看到了 libdispatch 的初始化
libdispatch_init();
_libxpc_initializer();
复制代码
在 Libsystem/init.c 文件中我们能看到一组外联函数的声明:
// system library initialisers
extern void mach_init(void); // from libsystem_kernel.dylib
extern void __libplatform_init(void *future_use, const char *envp[], const char *apple[], const struct ProgramVars *vars);
extern void __pthread_init(const struct _libpthread_functions *libpthread_funcs, const char *envp[], const char *apple[], const struct ProgramVars *vars); // from libsystem_pthread.dylib
extern void __malloc_init(const char *apple[]); // from libsystem_malloc.dylib
extern void __keymgr_initializer(void); // from libkeymgr.dylib
extern void _dyld_initializer(void); // from libdyld.dylib
extern void libdispatch_init(void); // from libdispatch.dylib
extern void _libxpc_initializer(void); // from libxpc.dylib
extern void _libsecinit_initializer(void); // from libsecinit.dylib
extern void _libtrace_init(void); // from libsystem_trace.dylib
extern void _container_init(const char *apple[]); // from libsystem_containermanager.dylib
extern void __libdarwin_init(void); // from libsystem_darwin.dylib
复制代码
看到 libSystem_initializer
函数的内部,会调用其他库的初始化函数,例如 _dyld_initializer();
这个是 dyld
库的初始化,因为 dyld
也是一个动态库。
在启动一个可执行文件的时候,系统内核做完环境的初始化,就把控制权交给
dyld
去执行加载和链接。
libdispatch_init
看到 libSystem_initializer
函数内部调用 libdispatch_init();
,同时可看到 libdispatch_init
是在 libdispatch.dylib
中,我们去下载源码:libdispatch,打开源码,全局搜索 libdispatch_init
,可在 libdispatch/Dispatch Source/queue.c 中看到 libdispatch_init
函数的定义如下(只摘录一部分):
DISPATCH_EXPORT DISPATCH_NOTHROW
void
libdispatch_init(void)
{
dispatch_assert(sizeof(struct dispatch_apply_s) <=
DISPATCH_CONTINUATION_SIZE);
if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
_dispatch_mode |= DISPATCH_MODE_STRICT;
}
...
...
复制代码
我们一直沿着 libdispatch_init
的定义往下看,临近定义结束处,会有 _dispatch_thread
的一些 creat 操作(GCD 相关)。
...
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
// ⬇️⬇️⬇️
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
复制代码
_os_object_init
看到其中 _os_object_init
的调用(本身它也属于 libdispatch.dylib
),我们全局搜一下 _os_object_init
,可在 libdispatch/Dispatch Source/object.m 中看到其定义。
void
_os_object_init(void)
{
// ⬇️⬇️⬇️
_objc_init();
Block_callbacks_RR callbacks = {
sizeof(Block_callbacks_RR),
(void (*)(const void *))&objc_retain,
(void (*)(const void *))&objc_release,
(void (*)(const void *))&_os_objc_destructInstance
};
_Block_use_RR2(&callbacks);
#if DISPATCH_COCOA_COMPAT
const char *v = getenv("OBJC_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
v = getenv("DISPATCH_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
v = getenv("LIBDISPATCH_DEBUG_MISSING_POOLS");
if (v) _os_object_debug_missing_pools = _dispatch_parse_bool(v);
#endif
}
复制代码
我们看到 _os_object_init
函数定义第一行就是 _objc_init
调用,也就是从 _os_object_init
跳入到 _objc_init
进入 runtime 的初始化,上面我们讲了 _objc_init
会调用 _dyld_objc_notify_register
,对 sNotifyObjCInit
进行赋值。
所以到这里我们可以总结一下 _objc_init
的调用流程:
_dyld_start
->dyldbootstrap::start
->dyld::_main
->dyld::initializeMainExecutable
->ImageLoader::runInitializers
->ImageLoader::processInitializers
->ImageLoader::recursiveInitialization
->doInitialization
->doModInitFunctions
->libSystem_initializer 属于 libSystem.B.dylib
->libdispatch_init 属于 libdispatch.dylib
->_os_object_init 属于 libdispatch.dylib
->_objc_init 属于 libobjc.A.dylib
- 当
dyld
加载到开始链接mainExecutable
的时候,递归调用recursiveInitialization
函数。 - 这个函数第一次运行,会进行
libSystem
的初始化,会走到doInitialization -> doModInitFunctions -> libSystem_initializer
。 libSystem
的初始化,会调用libdispatch_init
,libdispatch_init
会调用_os_object_init
,_os_object_init
会调用_objc_init
。- 在
_objc_init
中调用dyld
的_dyld_objc_notify_register
函数注册保存了map_images
、load_images
、unmap_images
的函数地址。 - 注册完回到
dyld
的recursiveInitialization
递归下一次调用,例如libObjc
,当libObjc
来到recursiveInitialization
调用时,会触发保存的load_images
回调,就调用了load_images
函数。
看到这里时我们还有一个函数没有看,上面我们分析了 void ImageLoaderMachO::doImageInit(const LinkContext& context)
函数的内容,然后在 ImageLoaderMachO::doInitialization
函数定义内部进行 doImageInit(context);
调用,然后下面还有一行 doModInitFunctions(context);
的调用,正是在 doModInitFunctions
的调用过程中,调用了 libSystem
的初始化函数 libSystem_initializer
。
doModInitFunctions
下面我们看一下 void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
函数的定义。
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
// 在 ImageLoaderMachO 定的一个位域:fHasInitializers : 1, 标记是否进行过初始化
if ( fHasInitializers ) {
// 找到当前 image 的 load command 的位置
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
// 遍历 load command
for (uint32_t i = 0; i < cmd_count; ++i) {
// 仅处理类型是 LC_SEGMENT_64 的 load command(#define LC_SEGMENT_COMMAND LC_SEGMENT_64)
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
// struct macho_segment_command : public segment_command_64 {};
// 转化为 segment_command_64 并移动指针,找到 macho_section 的位置
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
// 对当前的 segment 的 section 进行遍历
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
// 取出当前 section 的类型
const uint8_t type = sect->flags & SECTION_TYPE;
// 如果当前是 S_MOD_INIT_FUNC_POINTERS 类型(即 __mod_init_funcs 区)
if ( type == S_MOD_INIT_FUNC_POINTERS ) {
Initializer* inits = (Initializer*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(uintptr_t);
// <rdar://problem/23929217> Ensure __mod_init_func section is within segment
// 确认 __mod_init_func 区在当前段内
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath());
// 遍历当前区的所有的 Initializer
for (size_t j=0; j < count; ++j) {
// 取出每一个 Initializer
Initializer func = inits[j];
// <rdar://problem/8543820&9228031> verify initializers are in image
// 验证 initializers 是否在 image 中
if ( ! this->containsAddress(stripPointer((void*)func)) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
// 确保 libSystem.dylib 首先进行初始化
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
const char* installPath = getInstallPath();
// 即如果当前 libSystem.dylib 没有初始化,并且当前 image 的路径为 NULL 或者 当前 image 不是 libSystem.dylib,则进行抛错
// 即如果当前 libSystem.dylib 没有进行初始化,并且当前 image 是 libSystem.dylib 之前的 image 则直接抛错,
// 即必须保证 libSystem.dylib 第一个进行初始化
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
// 打印开始调用初始化
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
// 调用初始化
// const struct LibSystemHelpers* gLibSystemHelpers = NULL; 是一个全局变量,用来协助 LibSystem
// struct LibSystemHelpers 是装满函数指针的结构体
// 如果当前是 libSystem.dylib 则 haveLibSystemHelpersBefore 的值是 NO
bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
{
// 计时
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
// 执行初始化函数
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
// 如果当前是 libSystem.dylib 则执行到这里时 haveLibSystemHelpersBefore 的值是 YES
bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
// 如果前置两个条件一个是 NO 一个是 YES,则表示刚刚是进行的是 libSystem.dylib 的初始化,则把 libSystemInitialized 标记为 true
if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
// now safe to use malloc() and other calls in libSystem.dylib
// libSystem 初始化完成,现在可以安全地在 libSystem.dylib 中使用 malloc() 和其他调用了
dyld::gProcessInfo->libSystemInitialized = true;
}
}
}
// 如果当前是 S_MOD_INIT_FUNC_POINTERS 类型(即 __init_offsets 区)
else if ( type == S_INIT_FUNC_OFFSETS ) {
// 读出 inits
const uint32_t* inits = (uint32_t*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(uint32_t);
// Ensure section is within segment
// 确保当前 section 在当前 segment 内
if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) )
dyld::throwf("__init_offsets section has malformed address range for %s\n", this->getPath());
// 确认当前段是只读的
if ( seg->initprot & VM_PROT_WRITE )
dyld::throwf("__init_offsets section is not in read-only segment %s\n", this->getPath());
// 遍历当前区的所有的 inits
for (size_t j=0; j < count; ++j) {
uint32_t funcOffset = inits[j];
// verify initializers are in image
// 验证 initializers 是否在 image 中
if ( ! this->containsAddress((uint8_t*)this->machHeader() + funcOffset) ) {
dyld::throwf("initializer function offset 0x%08X not in mapped image for %s\n", funcOffset, this->getPath());
}
// 确保 libSystem.dylib 首先进行初始化
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// <rdar://problem/17973316> libSystem initializer must run first
// libSystemPath(context) 函数返回的路径,context 参数仅是用来判断是否是 driverKit 环境
// #define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib"
// 看到 libSystem 这个系统动态库在本地的位置是固定的
const char* installPath = getInstallPath();
// 即如果当前 libSystem.dylib 没有初始化,并且当前 image 的路径为 NULL 或者 当前 image 不是 libSystem.dylib,则进行抛错
// 即如果当前 libSystem.dylib 没有进行初始化,并且当前 image 是 libSystem.dylib 之前的 image 则直接抛错,
// 即必须保证 libSystem.dylib 第一个进行初始化
if ( (installPath == NULL) || (strcmp(installPath, libSystemPath(context)) != 0) )
dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
// 转换为 Initializer 函数指针
Initializer func = (Initializer)((uint8_t*)this->machHeader() + funcOffset);
// 打印开始调用初始化
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
#if __has_feature(ptrauth_calls)
func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0);
#endif
// 调用初始化
// haveLibSystemHelpersBefore 和 haveLibSystemHelpersAfter 两个变量的使用同上
bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
{
// 计时
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
// 执行初始化函数
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
// 如果前置两个条件一个是 NO 一个是 YES,则表示刚刚是进行的是 libSystem.dylib 的初始化,则把 libSystemInitialized 标记为 true
if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
// now safe to use malloc() and other calls in libSystem.dylib
// libSystem 初始化完成,现在可以安全地在 libSystem.dylib 中使用 malloc() 和其他调用了
dyld::gProcessInfo->libSystemInitialized = true;
}
}
}
}
}
// 继续遍历下一条 load command
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
复制代码
doModInitFunctions
函数内部是对 __mod_init_func
区和 __init_offsets
两个分区的 Initializer 进行调用。而在 libSystem.dylib
库的 __mod_init_func
区中存放的正是 libSystem.dylib
的初始化函数 libSystem_initializer
。(__mod_init_func
区仅用来存放初始化函数。)
文章开头处我们看到 load 方法是最先执行的,在之前的文章中我们有详细分析过 +load
的执行,如果还有印象的话一定记得它的入口 load_imags
函数。这正和我们上面的分析联系起来了,在 objc-781 源码中,它最先走的是 objc_init
,它最后会调用 _dyld_objc_notify_register
传入 load_images
,而 load_images
内部的 prepare_load_methods
和 call_load_methods
完成了整个项目中父类、子类、分类中的所有 +load 函数的调用。
doModInitFunctions
函数首先遍历找到类型是 LC_SEGMENT_COMMAND
的 Load command,然后遍历该段中类型是 S_MOD_INIT_FUNC_POINTERS
和 S_INIT_FUNC_OFFSETS
的区,然后便利其中的 Initializer
并执行。
typedef void (*Initializer)(int argc, const char* argv[], const char* envp[], const char* apple[], const ProgramVars* vars);
复制代码
我们在之前写的 main_front
函数中打一个断点,运行代码,使用 bt 查看其函数调用堆栈。
可看到 main_front
正是在 ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&)
下执行的,也正说明了 C++ 的静态方法就是在执行 doModInitFunctions
下执行的。
下面我们用 MachOView 看一下 libSystem.dylib
的结构。(这里没找到 libSystem.dylib
就不看了)
__attribute__((constructor))
__attribute__((constructor))
被attribute((constructor))
标记的函数,会在main
函数之前或动态库加载时执行。在 mach-o 中,被attribute((constructor))
标记的函数会在_DATA
段的__mod_init_func
区中。当多个被标记attribute((constructor))
的方法想要有顺序的执行,怎么办?attribute((constructor))
是支持优先级的:_attribute((constructor(1)))
。
前面我们学习 __attribute__((constructor))
时,我们知道被 attribute((constructor))
标记的函数会在 _DATA
段的 __mod_init_func
区中,而在 Libsystem-1292.100.5 中我们搜索 libSystem_initializer
函数时,我们能看到它前面有 attribute((constructor))
标记,即 libSystem_initializer
位于 libSystem.dylib 的 __mod_init_func
区中,上面 void ImageLoaderMachO::doModInitFunctions
函数调用过程中,查找的正是 __mod_init_func
区,既而当 libSystem.dylib
调用 doModInitFunctions
函数时,正会执行 libSystem_initializer
函数。
当 libSystem_initializer
被调用时,dyld
会对 gProcessInfo->libSystemInitialized
进行标记,表示 libSystem
已经被初始化。
_dyld_initializer
这里我们再看一个点,dyld 是怎么知道 libSystem 已经被初始化了,这里用到了 _dyld_initializer
函数:
// called by libSystem_initializer only
extern void _dyld_initializer(void);
复制代码
_dyld_initializer
函数仅由 libSystem_initializer
调用。
//
// during initialization of libSystem this routine will run and call dyld,
// registering the helper functions.
//
extern "C" void tlv_initializer();
void _dyld_initializer()
{
void (*p)(dyld::LibSystemHelpers*);
// Get the optimized objc pointer now that the cache is loaded
// 现在缓存已加载,获取优化的 objc 指针
const dyld_all_image_infos* allInfo = _dyld_get_all_image_infos();
if ( allInfo != nullptr ) {
const DyldSharedCache* cache = (const DyldSharedCache*)(allInfo->sharedCacheBaseAddress);
if ( cache != nullptr )
// 仅为了 gObjCOpt 赋值
gObjCOpt = cache->objcOpt();
}
if ( gUseDyld3 ) {
// 如果开始使用 dyld3 了,则执行如下,对 gAllImages 中所有 Image 执行初始化
dyld3::gAllImages.applyInitialImages();
#if TARGET_OS_IOS && !TARGET_OS_SIMULATOR
// For binaries built before 13.0, set the lookup function if they need it
// 对于 13.0 之前构建的二进制文件,如果需要,请设置查找功能
if (dyld_get_program_sdk_version() < DYLD_PACKED_VERSION(13,0,0))
setLookupFunc((void*)&dyld3::compatFuncLookup);
#endif
}
else {
_dyld_func_lookup("__dyld_register_thread_helpers", (void**)&p);
if(p != NULL)
// sHelpers 是一个静态全局结构体变量:static dyld::LibSystemHelpers = {....}
p(&sHelpers);
}
// 这里调用了 tlv_initializer 函数
tlv_initializer();
}
复制代码
在 libSystem
初始化期间,此例程将运行并调用 dyld,注册辅助函数(LibSystemHelpers
)。这里也对应了 doModInitFunctions
函数内部, bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL);
和 bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL);
两个变量加一起可用来表示 dyld::gProcessInfo->libSystemInitialized = true;
,指示 libSystem
初始化完成,现在可以安全地在 libSystem.dylib
中使用 malloc()
和其他调用了。
libSystem
的初始化是一个内部行为,dyld
是如何知道它被初始化的呢?libSystem
是一个特殊的dylib
,默认情况下会被所有可执行文件所依赖,dyld
为它单独提供了一个 API:_dyld_initializer
,当libSystem
被初始化时,会调用该函数,进而dyld
内部就知道了libSystem
被初始化了。
在 _dyld_start 中调用 main() 函数
看到这里我们的 dyld
的 _dyld_start
函数就执行过程中会啊调用 main()
函数,那它是怎么调用到我们的 main.m 的 main
函数呢?
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001045d1ad8 Test_ipa_Simple`main(argc=1, argv=0x000000016b82dc88) at main.mm:89:5
frame #1: 0x0000000180223cbc libdyld.dylib`start + 4
(lldb)
复制代码
此时我们要回忆我们的 dyld
的 dyldbootstrap::start
函数,如果我们对前面的函数调用还有印象的话,dyldbootstrap::start
函数的最后是返回 dyld::_main
函数的执行结果:return dyld::_main((macho_header*)mainExecutableMH, appsSlide, argc, argv, envp, apple, startGlue);
,而 dyld::_main
函数的返回值就是 main()
函数的地址,dyld::_main
函数的注释也说明了这一点:
Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which sets up some registers and call this function.
Returns address of main() in target program which __dyld_start jumps to.
dyld
的入口点。内核加载 dyld
并跳转到 __dyld_start
,它设置一些寄存器并调用此函数。
返回 __dyld_start
跳转到的目标程序中 main()
的地址。
下面我们深入 dyld::_main
函数的实现看一下最后的返回结果:
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
...
return result;
复制代码
下面我们看一下 getEntryFromLC_MAIN
函数实现:
void* ImageLoaderMachO::getEntryFromLC_MAIN() const
{
// load command 的数量
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
// 跳过 macho_header 寻址到 load command 的位置
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
// 遍历 load command
for (uint32_t i = 0; i < cmd_count; ++i) {
// 找到 LC_MAIN 类型的 load_command
if ( cmd->cmd == LC_MAIN ) {
// 返回 entry
entry_point_command* mainCmd = (entry_point_command*)cmd;
void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
// <rdar://problem/8543820&9228031> verify entry point is in image
if ( this->containsAddress(entry) ) {
#if __has_feature(ptrauth_calls)
// start() calls the result pointer as a function pointer so we need to sign it.
return __builtin_ptrauth_sign_unauthenticated(entry, 0, 0);
#endif
return entry;
}
else
throw "LC_MAIN entryoff is out of range";
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return NULL;
}
复制代码
即返回 LC_MAIN 的 Entry Point,而它正是当前可执行程序的 main()
地址。
至此我们可以接着看 dyld
的 __dyld_start
的汇编实现,这里我们摘录 __arm64
下的汇编实现:
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
// ⬆️ 上面便是 dyldbootstrap::start 调用,执行完成后返回 main() 入口地址,并保存在 x16 中
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, clean up stack and jump to result
#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
// ⬇️ 跳转到程序的入口,即 main() 函数
#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, set up stack for call to main() 为调用 main() 设置堆栈
Lnew: mov lr, x1 // simulate return address into _start in libdyld.dylib 将返回地址模拟到 libdyld.dylib 中的 _start
// ⬇️ 下面是我们熟悉的 main 函数的 argc 和 argv 参数
#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
复制代码
看到这里我们就把 main()
函数之前的流程都看完了,在执行完 dyldbootstrap::start
后,会调用程序的 main()
函数,并且我们也看到了 main()
函数的地址其实是从 LC_MAIN
类型的 load command
读出来的,这也表明了 main()
函数是底层写定函数,
main
函数是被编译到可执行文件中的,而且是固定写死的,编译器找到 main
函数会加载到内存中,如果我们修改 main
函数的名字则会报如下错误: ld: entry point (_main) undefined. for architecture x86_64
,告诉我们找不到 main
函数。
至此 main()
函数之前的流程我们就全部看完了。完结撒花 ???
参考链接
参考链接:?
- dyld-832.7.3
- OC底层原理之-App启动过程(dyld加载流程)
- iOS中的dyld缓存是什么?
- iOS进阶之底层原理-应用程序加载(dyld加载流程、类与分类的加载)
- iOS应用程序在进入main函数前做了什么?
- dyld加载应用启动原理详解
- iOS里的动态库和静态库
- Xcode 中的链接路径问题
- iOS 利用 Framework 进行动态更新
- 命名空间namespace ,以及重复定义的问题解析
- C++ 命名空间namespace
- 一文了解 Xcode 生成「静态库」和「动态库」 的流程
- Hook static initializers
- iOS逆向 dyld流程
- OC 底层探索 13、类的加载1 – dyld和objc的关联
下面是一些新增的参考链接?: