应用程序的加载

引入

示例代码

//ViewController中
@implementation ViewController
+ (void)load{
    NSLog(@"%s",__func__);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}
@end

//main()中
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    
    NSLog(@"--%s--", __func__);
    
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
__attribute__((constructor)) void cFunc(){
    printf("来了 : %s \n",__func__);
}
复制代码

运行结果

2021-07-11 16:58:xxx 002-应用程加载分析[2663:3630744] +[ViewController load]
来了 : cFunc 
2021-07-11 16:58 002-应用程加载分析[2663:3630744] --main--
复制代码

我们都知道程序的入口是main()函数,当我们在viewController里面写一个+load(),在main()函数中一个gcc扩展__attribute__((constructor))补充1,发现他们在main()函数之前调用,main函数之前都做了什么呢。

编译过程

OC中我们都是写一些.h、.m、有时候还会碰到一些.cpp, 有时候程序还会加载一些.a、.framework这些库文件,运行之后,还会生成
截屏2021-07-11 下午5.16.09.png可执行文件。
❓中间经历了什么呢

截屏2021-07-11 下午5.17.49.png

程序的加载过程

App启动到main函数发生了什么
我们在+ (void)load{}中加一个断点,用bt来查看整个的堆栈流程。

截屏2021-07-11 下午5.34.03.png

App启动流程

截屏2021-07-11 下午5.28.29.png

dyld流程
__dyld_start:

我们下载最新的dyld代码:搜索__dyld_start,它在dyldStartUp.s文件中。.s是汇编代码。我们看到又如下有用信息

__dyld_start:
	popl	%edx		# edx = mh of app
	pushl	$0		# push a zero for debugger end of frames marker
	movl	%esp,%ebp	# pointer to base of kernel frame
	andl    $-16,%esp       # force SSE alignment
	subl	$32,%esp	# room for locals and outgoing parameters

	call    L__dyld_start_picbase
L__dyld_start_picbase:
	popl	%ebx		# set %ebx to runtime value of picbase

	# call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
   	subl	$L__dyld_start_picbase-__dyld_start, %ebx # ebx = &__dyld_start

复制代码

# call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)从备注中看到,调用了dyldbootstrap::start

dyldbootstrap::start

dyldbootstrap是命名空间,start为方法名, 我们先搜索dyldbootstrap
在里面找到start方法。

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
				const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
        ...
	return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
复制代码
dyld::_main

里面调用了dyld::_main函数,点到dyld::_main函数中,发现里面有1k多行代码,为了快速定位我们要找到的,我们看到最后面有一个return result;我们看下result是在哪里赋值的。


uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
		int argc, const char* argv[], const char* envp[], const char* apple[], 
		uintptr_t* startGlue)
{

// link main executable
		gLinkContext.linkingMainExecutable = true;
...
// Bind and notify for the inserted images now interposing has been registered 
		if ( sInsertedDylibCount > 0 ) {
			for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
				ImageLoader* image = sAllImages[i+1];
				image->recursiveBind(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true, nullptr);
			}
		}
		
		// <rdar://problem/12186933> do weak binding only after all inserted images linked
		sMainExecutable->weakBind(gLinkContext);
		gLinkContext.linkingMainExecutable = false;
...
	#if SUPPORT_OLD_CRT_INITIALIZATION
		// Old way is to run initializers via a callback from crt1.o
		if ( ! gRunInitializersOldWay ) 
			initializeMainExecutable(); 
	#else
		// run all initializers
		initializeMainExecutable(); 
	#endif

		// notify any montoring proccesses that this process is about to enter main()
		notifyMonitoringDyldMain();
		if (dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE)) {
			dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 2);
		}
		ARIADNEDBG_CODE(220, 1);
}


复制代码

在里面调用了initializeMainExecutable();

initializeMainExecutable
void initializeMainExecutable()
{
	// record that we've reached this step
	gLinkContext.startedInitializingMainExecutable = true;

	// run initialzers for any inserted dylibs
	ImageLoader::InitializerTimingList initializerTimes[allImagesCount()];
	initializerTimes[0].count = 0;
	const size_t rootCount = sImageRoots.size();
	if ( rootCount > 1 ) {
		for(size_t i=1; i < rootCount; ++i) {
			sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
		}
	}
	
	// run initializers for main executable and everything it brings up 
	sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
	
	// register cxa_atexit() handler to run static terminators in all loaded images when this process exits
	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]);
}
复制代码
runInitializers

我们看一下sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);

void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
	...
	processInitializers(context, thisThread, timingInfo, up);
	context.notifyBatch(dyld_image_state_initialized, false);
	...
}
复制代码
processInitializers

看下processInitializers

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.
	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);
}
复制代码

processInitializers这是个递归调用,由此判断recursiveInitialization是重点,

recursiveInitialization
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
										  InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
...
// 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);
			
if ( hasInitializers ) {
	uint64_t t2 = mach_absolute_time();
	timingInfo.addTime(this->getShortName(), t2-t1);
}
....
}
复制代码

context.notifySingle(dyld_image_state_initialized, this, NULL);
我们看下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());

static _dyld_objc_notify_init		sNotifyObjCInit;
复制代码

搜索

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;
        ...
}
复制代码

我们看一下在什么地方调用的registerObjCNotifiers

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在这个函数中调起的。

doInitialization(context);
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
	CRSetCrashLogMessage2(this->getPath());

	// mach-o has -init and static initializers
	doImageInit(context);
	doModInitFunctions(context);
	
	CRSetCrashLogMessage2(NULL);
	
	return (fHasDashInit || fHasInitializers);
}
复制代码
doModInitFunctions
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
...
for (size_t j=0; j < count; ++j) {
						
                Initializer func = inits[j];
							// <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());
		}
		if ( ! dyld::gProcessInfo->libSystemInitialized ) {
								// <rdar://problem/17973316> libSystem initializer must run first
		const char* installPath = getInstallPath();
		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());
		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);
		if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) {
		// now safe to use malloc() and other calls in libSystem.dylib
								dyld::gProcessInfo->libSystemInitialized = true;
		}
	}
                                                ...


}
复制代码

从// rdar://problem/17973316 libSystem initializer must run first
知道首先是调用libSystem这个库func(context.argc, context.argv, context.envp, context.apple, &context.programVars);就是对Initializer的调用
我们打开libSystem的源码

libSystem_initializer
static void
libSystem_initializer(int argc,
		      const char* argv[],
		      const char* envp[],
		      const char* apple[],
		      const struct ProgramVars* vars)
{
...
libdispatch_init();
...
}

extern void libdispatch_init(void);		// from libdispatch.dylib
复制代码
libdispatch_init

我们打开libdispatch的源码

void
libdispatch_init(void)
{
...
_os_object_init();
...
}
复制代码

_os_object_init();

void
_os_object_init(void)
{
	_objc_init();
	...
}

#include <objc/objc-internal.h>
extern void _objc_init(void);
复制代码

_objc_init是objc的代码,回到objc源码中

void _objc_init(void)
{
   ...
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
   ...
}
复制代码

我们在_objc_init_dyld_objc_notify_register加一个断点,打印堆栈信息bt

截屏2021-07-11 下午10.33.31.png
以上为这个堆栈信息的推导过程。

总结

未命名.png

补充

1.gcc扩展__attribute__((constructor))

gcc对c语言做了很多扩展,使得c语言的表现力得到了很大的增强,本文主要介绍一下constructor扩展,这个扩展和C++的构造函数很像,它会在main函数之前由程序加载器自动调用,与之相对的是destructor,它会在main函数执行结束或者exit的时候自动调用,由于两个扩展是一对,destructor这里就不介绍了。ANSI C标准还引入了atexit函数,这个是在进程结束的时候自动调用。和destructor也比较相似。
constructor扩展的用法如下

void func() __attribute__((constructor));                              
复制代码

有如下规则

  • 构造函数先于main函数而执行
  • 不同构造函数如果在同一个文件中,则先出现的函数后执行
  • 对于不同文件中的构造函数,编译命令中后出现的.c文件的构造函数先执行

利用constructor属性,我们可以定义一些宏来实现模块的自动注册机制。也就是说我们用宏自动构造注册函数,然后把注册函数赋予constructor属性,这样我们在添加新的模块的时候就不需要显示的调用注册函数来,只需要在模块文件内加上一个宏调用即可。

2.静态库和动态库
  • 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
  • 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载次,多个程序可以共用,节省内存。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享