调用在main()之前
一般开发场景中,我们都是把main()函数作为程序的入口,但是这里探究一下man()函数开始之前发生了什么。
程序运行依赖很多的库和文件,有动态库(.so .framwork)还有静态库(.a .lib)以及很多的.h .m文件 他们如何加载 到程序中的呢
静态库:链接时会被完整的复制到可执行文件中,所以如果两个程序都用了某个静态库,那么每个二进制可执行文件里面其实都含有这份静态库的代码。
动态库: 链接时不复制,在程序启动后用动态加载,然后再决议符号,所以理论上动态库只用存在一份,好多个程序都可以动态链接到这个动态库上面,达到了节省内存,还有另外一个好处,由于动态库并不绑定到可执行程序上,所以我们想升级这个动态库就很容易,windows和linux上面一般插件和模块机制都是这样实现的。
动态链接库包括:
- iOS 中用到的所有系统 framework
- 加载OC runtime方法的libobjc,
- 系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)
这些放在内存中的共享库文件会在app启动后交给dyld
动态连接器来进行链接管理。
动静态链接库以及对于App本身的可执行文件而言,都称为image。
dyld则是以image为单位将这些可执行文件加载到app中。
开始在main()之前
首先做一份断点信息,iOS中-load()
方法会在main()
函数开始之前就调用,在这里设置断点可以很好的追踪主函数开始之前的调用信息。同样的,使用__attribute__ ((constructor))
修饰的C++属性函数也会在main()
之前被加载调用.
这里通过函数名称大致也能推测出一些比较重要的函数名称
_dyld_start
dyldbootstrap::start
dyld::main
dyld::initializeMainExecutable
imageLoader::****
dyld:: notifySingle
-
load_images
等
dyld的加载流程
很明显_dyld_start
是作为整个流程的起始位置。
打开一份新鲜的objc756源码,开始查找_dyld_start
。
__dyld_start:
mov x28, sp
and sp, x28, #~15 // force 16-byte alignment of stack
//...
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
//...
_dyld_start
的源码是用汇编写的,但是只需要看注释就能明白多个版本的下的_dyld_start
都会来到dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
这个函数。
来看看dyldbootstrap::start
内部实现:
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue){
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
rebaseDyld(dyldsMachHeader);
const char** envp = &argv[argc+1];
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
__guard_setup(apple);
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
出了一些参数的处理 最重要的一步就是_main()
这个函数了。
_main()函数
_main()
函数代码量很大,分了很多功能,主要完成了上下文的建立,主程序初始化成ImageLoader
对象,加载共享的系统动态库,加载依赖的动态库,链接动态库,初始化主程序,返回主程序main()函数地址。
加载共享缓存
mapSharedCache()
负责将系统中的共享动态库加载进内存空间,比如UIKit就是动态共享库,这也是不同的App之间能够实现动态库共享的机制。不同App间访问的共享库最终都映射到了同一块物理内存,从而实现了共享动态库。
内部会调用loadDyldCache()
mapSharedCache()
的基本逻辑就是:
- 先判断共享动态库是否已经映射到内存中了。
- 如果已经存在,则直接返回。
- 否则打开缓存文件,并将共享动态库映射到内存中。
load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache();
#else
mapSharedCache();
#endif
}
生成ImageLoader对象
动静态库以及程序自己的.o文件都是以image
的形式加载的。
ImageLoader
本身是一个抽象类,用于帮助加载特定格式的可执行文件。例如ImageLoaderMachO
继承自ImageLoader
.
instantiateFromLoadedImage()
会实例化一个ImageLoader
对象,然后调用instantiateMainExecutable()
加载文件生成image
并进行链接。
APP启动过程中,相关的库和主程序都被加载成ImageLoader
对象
最后返回一个ImageLoaderMachO
对象用于访问所有的images
对象
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";
}
加载所有插入的库 loadInsertedDylib
遍历sEnv.DYLD_INSERT_LIBRARIES
这个环境变量中的库,调用loadInsertedDylib
来加载库。
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
链接主程序
这一步最终调用的还是ImageLoader::link
,内部会调用recursiveLoadLibraries
递归加载动态库。
注意,先链接主程序,然后链接所有加载的库文件。
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
if ( gLinkContext.allowInterposing ) {
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing(gLinkContext);
}
}
}
执行初始化方法 initializeMainExecutable
initializeMainExecutable()
里面先对动态库进行runInitializers()
,然后才对主程序进行runInitializers()
。
runInitializers()
内部调用了Imageloader::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 ) {
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
}
}
我们需要注意这几行代码:
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
context.notifySingle(dyld_image_state_initialized, this, NULL);
doInitialization()
doInitialization(context);
这里开始做images
初始化工作。
注意,在这个函数里面有一个判断:
if ( ! dyld::gProcessInfo->libSystemInitialized ) {
// libSystem initializer must run first
dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath());
}
那就是在所有的images
的初始化中,libSystem
必须放在第一位。
既然libSystem
这么重要,那么就看看libSystem
的初始化方法:
static void
libSystem_initializer(int argc,const char* argv[],const char* envp[],const char* apple[],const struct ProgramVars* vars){
//内核初始化
__libkernel_init(&libkernel_funcs, envp, apple, vars);
//平台初始化
__libplatform_init(NULL, envp, apple, vars);
//线程相关初始化
__pthread_init(&libpthread_funcs, envp, apple, vars);
_libc_initializer(&libc_funcs, envp, apple, vars);
__malloc_init(apple);
//dyld的初始化,上面dyld_start之后并不代表就开始初始化。
_dyld_initializer();
//这里是重点
libdispatch_init();
_libxpc_initializer();
}
_objc_init()
libdispatch_init()
这个库的初始化应该最熟悉了,内部会对GCD底层进行一些初始化工作,以及会调用一个对我们当前探索流程很重要的一个初始化函数_os_object_init()
,而_os_object_init
内部则会直接调用_objc_init()
这个函数,来完成_dyld_objc_notify_register(&map_images, load_images, unmap_image);
函数注册。
void
libdispatch_init(void)
{
//...
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
从_os_object_init()内部调用来到这里
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
notifySingle()
doInitialization()
完成前后,都会有一个通知信号notifySingle()
。
找到它的源码位置
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
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) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
只发现了(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
这个函数的调用。
先看看它的定义:
typedef void (*_dyld_objc_notify_init)(const char* path, const struct mach_header* mh);
全局搜索它的赋值,只有一处地方能找到:
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;
//...
}
那么说明一定是在dyld的某个地方,调用了registerObjCNotifiers
,然后给了sNotifyObjCInit
赋值。
同样,全局找到了唯一的调用位置:
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);
}
那么看到这里就能明白,这里之所以能调用*sNotifyObjCInit ()
,就是因为在doInitialization()
中完成了libSystem_initializer()
的调用,而libSystem
初始化的时候内部调用了objc_init()
函数,完成了_dyld_objc_notify_register(&map_images, load_images, unmap_image);
函数的注册。
因此,在doInitialization()
执行完毕之后,发送一条完成的通知notifySingle()
,并执行回调函数。
dyld总体流程总结:
_dyld_start
开始
- 环境变量的配置
- 共享缓存
- 主程序初始化
- 加入动态库
- link主程序
- link动态库
- main()