本篇文章主要去研究一下main函数以及load的具体调用,我们知道load是在main函数之前调用,我们通过源码来了解一下具体的过程。
通过这篇文章我们将了解到:
1、load在源码中是如何被调用的,具体的加载过程是?
2、在调用main函数之前具体都做了什么?
load
我们还是回归到dyld源码,来具体的研究一下load是如何被调用的。上一篇我们了解到启动大致有9个步骤,总结来说就是先进行一些环境的读取然后设置的准备工作,然后进行共享缓存的初始化,然后是main的初始化准备工作,然后是一些需要的动态库的插入以及链接等等这样一个过程。
那么我们了解这个流程以后,要去研究main具体做了些什么,我们去看跟main相关的步骤,也就是main的初始化以及一些准备工作,因为我们了解到load是在main之前,所以我们去看main相关之前的有可能找到load相关的操作,于是我们就定位到代码:
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
为main的可执行初始化一些镜像加载器
instantiateFromLoadedImage
// The kernel maps in main executable before dyld gets control. We need to
// make an ImageLoader* for the already mapped in main executable.
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
// if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
addImage(image);
return (ImageLoaderMachO*)image;
// }
// throw "main executable not a known format";
}
了解下这个方法的注释:在dyld获得控制之前,内核映射到主可执行文件中。我们需要为已映射到主可执行文件中的文件创建ImageLoader*。
主要的方法是调用了一个instantiateMainExecutable这个,::(双冒号) 这是c++的语法,,前边一般是类名,后边是类的方法或者函数。于是我们搜一下ImageLoaderMachO:
找到这个文件ImageLoaderMachO,然后去文件里找到方法instantiateMainExecutable
image.png
然后我们深入到方法里进去看,发现里面的具体内容是创建images的过程,没有相关main还有load的内容,这一个步骤为了sMainExecutable根据路径去读取images的一个过程。下面截取部分代码。
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
//dyld::log("ImageLoader=%ld, ImageLoaderMachO=%ld, ImageLoaderMachOClassic=%ld, ImageLoaderMachOCompressed=%ld\n",
// sizeof(ImageLoader), sizeof(ImageLoaderMachO), sizeof(ImageLoaderMachOClassic), sizeof(ImageLoaderMachOCompressed));
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
else
#if SUPPORT_CLASSIC_MACHO
return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
#else
throw "missing LC_DYLD_INFO load command";
#endif
}
// create image for main executable
ImageLoaderMachOCompressed* ImageLoaderMachOCompressed::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path,
unsigned int segCount, unsigned int libCount, const LinkContext& context)
{
ImageLoaderMachOCompressed* image = ImageLoaderMachOCompressed::instantiateStart(mh, path, segCount, libCount);
// set slide for PIE programs
image->setSlide(slide);
// for PIE record end of program, to know where to start loading dylibs
if ( slide != 0 )
fgNextPIEDylibAddress = (uintptr_t)image->getEnd();
image->disableCoverageCheck();
image->instantiateFinish(context);
image->setMapped(context);
if ( context.verboseMapping ) {
dyld::log("dyld: Main executable mapped %s\n", path);
for(unsigned int i=0, e=image->segmentCount(); i < e; ++i) {
const char* name = image->segName(i);
if ( (strcmp(name, "__PAGEZERO") == 0) || (strcmp(name, "__UNIXSTACK") == 0) )
dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segPreferredLoadAddress(i), image->segPreferredLoadAddress(i)+image->segSize(i));
else
dyld::log("%18s at 0x%08lX->0x%08lX\n", name, image->segActualLoadAddress(i), image->segActualEndAddress(i));
}
}
return image;
}
所以哇,不是这个地方,这个地方只是加载了需要的镜像文件,应该还没有运行,接着往下看!之前有一个流程是运行所有的初始化,initializeMainExecutable这个方法,然后详细看下这个看看有没有我们需要的。
initializeMainExecutable
image.png这里是实际运行了初始化设置的默认项,翻译一下标记的注释:为主可执行文件及其带来的所有内容运行初始值设定项
。查看里面的具体实现发现:
void runInitializers(ImageLoader* image)
{
// do bottom up initialization
ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
initializerTimes[0].count = 0;
image->runInitializers(gLinkContext, initializerTimes[0]);
}
这是一个镜像运行的递归调用。
然后我们结合上下文去查找,上面是一个关于ImageLoader的初始化:ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
在文件ImageLoader中查看具体实现:
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.imagesAndPaths[0] = { this, this->getPath() };
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized, false);
mach_port_deallocate(mach_task_self(), thisThread);
uint64_t t2 = mach_absolute_time();
fgTotalInitTime += (t2 - t1);
}
然后我们顺着方法runInitializers继续往里看里层的方法调用processInitializers->recursiveInitialization->notifySingle->sNotifyObjCInit(_dyld_objc_notify_init),sNotifyObjCInit= init;的赋值又在registerObjCNotifiers方法中调用,点进去registerObjCNotifiers发现它是由方法_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里就没有关于_dyld_objc_notify_register的其他线索了,我们去objc的源码里通过符号断点的方式去照一下_dyld_objc_notify_register,发现_dyld_objc_notify_register通过_objc_init调用了_dyld_objc_notify_register(&map_images, load_images, unmap_image);
也刚好是三个参数,和dyld里面的_dyld_objc_notify_register正好可以对应上。
image.png由此可以进行推测dyld和objc就通过_dyld_objc_notify_register有了关联,然后我们再具体去看在objc里_dyld_objc_notify_register具体做了什么,我们点进去参数map_images,还有load_images,发现分别是方法的实现。
map_images
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_nolock,而通过查看map_images_nolock的实现发现里面的核心方法是_read_images**,这个我们下一篇文章进行深入的学习。
再结合dyld中的方法_dyld_objc_notify_register的第一个参数mapped,在方法registerObjCNotifiers中赋值
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
mapped赋值给sNotifyObjCMapped,经过查询sNotifyObjCMapped在方法notifyBatchPartial有调用,然后notifyBatchPartial 又被registerObjCNotifiers,也就是把mapped赋值以后,再去调用mapImages。
load_images
根据赋值可以了解到sNotifyObjCInit对应的就是load_images参数,load_images的具体实现如下:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
由源码可以学习到,load_images是通过call_load_methods调用所有的+load方法;
call_load_methods先去调用”call class +loads until there aren't any more“类的+load,然后调用”2. Call category +loads ONCE”分类的+load;
所以,到这里我们终于找到了load的调用。
load的调用是通过dyld中的runInitializers->processInitializers->recursiveInitialization->notifySingle->sNotifyObjCInit-> registerObjCNotifiers-> _dyld_objc_notify_register-> load_images-> call_load_methods去调用的所有的+loads方法!
而在调用main函数之前,细节也是如上runInitializers->processInitializers->recursiveInitialization->notifySingle->sNotifyObjCInit-> registerObjCNotifiers-> _dyld_objc_notify_register到这里objc层_objc_init调用了_dyld_objc_notify_register是第一个参数map_images,然后进行回调再赋值,再继续调用registerObjCNotifiers,然后是第二个参数load_images的调用。
网友评论