引入
示例代码
//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这些库文件,运行之后,还会生成
可执行文件。
❓中间经历了什么呢
程序的加载过程
App启动到main函数发生了什么
我们在+ (void)load{}中加一个断点,用bt
来查看整个的堆栈流程。
App启动流程
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
以上为这个堆栈信息的推导过程。
总结
补充
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.静态库和动态库
- 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
- 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载次,多个程序可以共用,节省内存。