前言
在编写一个应用程序时候,我们看到的入口函数都是main.m
里面的 main
函数,曾以为这是程序的入口,其实不然,程序在执行main
函数之前已经执行了+load
和constructor
构造函数。下面让我们一起来分析
dyld简介
-
dyld
(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld
负责余下的工作 -
在iOS/Mac OSX
系统中,仅有很少量的进程只需要内核就能完成加载,基本上所有的进程都是动态链接的,所以Mach-O
镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容的填补,这个填补工作就是由 动态链接器dyld 来完成的,也就是符号绑定。 - 动态链接器
dyld
在系统中以一个用户态的可执行文件形式存在,一般应用程序会在Mach-O
文件部分指定一个LC_LOAD_DYLINKER
的加载命令,此加载命令指定了dyld
的路径,通常它的默认值是/usr/lib/dyld
- 系统内核在加载
Mach-O
文件时,都需要用 dyld(位于 /usr/lib/dyld )程序进行链接。
分析
我们知道+load
函数在main
函数之前调用 我随意实现 一个load
函数并断言在此。
可以看出程序是从_dyld_start
为入口 ,我们准备好dyld代码可以从苹果开源网站下载(https://opensource.apple.com/tarballs/dyld/,这里我下载的是773.6版本)。
全局搜索_dyld_start 发现如下汇编
在晦涩难懂的汇编我们一眼就可以看到
bl
跳转 虽说代码看不懂可以看注释呀// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
全局搜索dyldbootstrap
dyld 引导配置
根据断言及源代码搜索 _main 漫长的搜索 在 dyld2.cpp
里 这里有些长精简代码
// 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
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
... 省略
/// 6158 - 6160 行
uintptr_t result = 0;
/// 保存执行文件头部,后续可以根据头部访问其他信息
sMainExecutableMachHeader = mainExecutableMH;
sMainExecutableSlide = mainExecutableSlide;
...
/// 6233 -6226
/// 设置上下文信息
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
/// 获取可执行文件路径
sExecPath = _simple_getenv(apple, "executable_path");
...
///6242 - 6262
///将相对路径转换成绝对路径
if ( sExecPath[0] != '/' ) {
// have relative path, use cwd to make absolute
char cwdbuff[MAXPATHLEN];
if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) {
// maybe use static buffer to avoid calling malloc so early...
char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2];
strcpy(s, cwdbuff);
strcat(s, "/");
strcat(s, sExecPath);
sExecPath = s;
}
}
// Remember short name of process for later logging
///获取文件的名字
sExecShortName = ::strrchr(sExecPath, '/');
if ( sExecShortName != NULL )
++sExecShortName;
else
sExecShortName = sExecPath;
///配置进程是否受限
configureProcessRestrictions(mainExecutableMH, envp);
...
/// 6298 -6302
#endif
{
///检查设置环境变量
checkEnvironmentVariables(envp);
///如DYLD_FALLBACK为nil,将其设置为默认值
defaultUninitializedFallbackPaths(envp);
}
...
/// 6317 -6320
///如果设置了DYLD_PRINT_OPTS环境变量 则打印参数
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
///如果设置了DYLD_PRINT_ENV环境变量 则打印参数
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
...
///6347 - 6356
/// 获取当前运行架构的信息
getHostInfo(mainExecutableMH, mainExecutableSlide);
////检査共享缓存是否开启,在iOS中必须开启
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
#if TARGET_OS_SIMULATOR
if ( sSharedCacheOverrideDir)
mapSharedCache();
#else
//检査共享缓存是否映射到了共享区域
mapSharedCache();
...
/// 6517- 6520
///加载可执行文件并生成一个ImageLoader实例对象
// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
...
/// 6558 - 6561
// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
///检查库的版本是否有更新,如有则覆盖原有的
checkVersionedPaths();
#endif
...
/// 6582 - 6589
/// 加载所有的DYLD_INSERT_LIBRARIES指定的库
// load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// record count of inserted libraries so that a flat search will look at
// inserted libraries, then main, then others.
sInsertedDylibCount = sAllImages.size()-1;
....
/// 6592- 6605
///连接主程序
// link main executable
gLinkContext.linkingMainExecutable = true;
...
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;
}
/// 连接插入的动态库 6607 -6630
// 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();
}
// 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);///注册符号插入
}
}
// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image->inSharedCache() )
continue;
image->registerInterposing(gLinkContext);
}
...
///6664- 6689
// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(); ++i) {
sImageRoots[i]->applyInterposing(gLinkContext);
}
///应用插入到Dyld缓存
ImageLoader::applyInterposingToDyldCache(gLinkContext);
///现在已经注册了插入操作,为主可执行文件绑定和通知
// Bind and notify for the main executable now that interposing has been registered
uint64_t bindMainExecutableStartTime = mach_absolute_time();
sMainExecutable->recursiveBindWithAccounting(gLinkContext, sEnv.DYLD_BIND_AT_LAUNCH, true);
uint64_t bindMainExecutableEndTime = mach_absolute_time();
ImageLoaderMachO::fgTotalBindTime += bindMainExecutableEndTime - bindMainExecutableStartTime;
gLinkContext.notifyBatch(dyld_image_state_bound, false);
//对插入的 镜像文件 进行绑定和通知,现在插入已注册
// 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);
}
}
// <rdar://problem/12186933> do weak binding only after all inserted images linked
sMainExecutable->weakBind(gLinkContext);
gLinkContext.linkingMainExecutable = false;
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
···
/// 6696 - 6702
#else
///执行初始化方法
// run all initializers
initializeMainExecutable();
#endif
///通知即将进入 main()
// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain();
...
///查找主可执行文件的入口点
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
// main executable uses LC_MAIN, we need to use helper in libdyld to call into main()
if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
else
halt("libdyld.dylib support not present for LC_MAIN");
}
else {
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
*startGlue = 0;
}
通过代码可以知道,dyld的加载流程主要包括9个步骤
01:设置上下文信息,配置进程是否受限制
02:配置环境变量,获取当前运行架构
03:检查共享缓存是否映射到了共享区域
04:加载可执行文件,生成一个imageLoader实例对象
05:加载所有插入的库
06:连接主程序
07:连接所有插入的库,执行符号替换
08:执行初始化方法
09 : 寻找主程序入口
initializeMainExecutable(); 执行初始化方法分析第八步分析
-
眼睛聚焦到高亮位置
-
找到
runInitializers
,全局搜索runInitializers(cons
发现其核心实现为processInitializers
函数调用
-
进入
processInitializers
函数源码
-
找到
recursiveInitialization
, 全局搜索 -
全局搜索 notifySingle
这里我们定位到了 一个 sNotifyObjCInit 指针函数调用 可是它是什么?
-
全局搜索
sNotifyObjCInit
,发现在dyld源码中并未找到,只定位到了 registerObjCNotifiers函数内部赋值
这里我们定位到了 这个可能是一个外部传进来 回调函数 -
全局搜索 registerObjCNotifiers 函数
-
全局搜索 _dyld_objc_notify_register dyld源码并未找到只找到了相关注释
-
那么到底谁调用了_dyld_objc_notify_register()呢?静态分析已经无法得知,只能对_dyld_objc_notify_register()下个符号断点观察一下了,
点击Xcode的“Debug”菜单,然后点击“Breakpoints”,接着选择“Create Symbolic Breakpoint...”。在弹出窗如下图所示。
-
运行 成功断住 bt命令打印当前堆栈信息
-
可以看到当前是 libobjc 的 _objc_init 发起的调用 在objc源码里进行搜索 _objc_init
-
我们再来看一遍上面的sNoitfyObjcInit
1、由此我们可以得出结论 此处注册的init函数就是load_images,所以上面的sNotifyObjCInit调用的就是 objc中的 load_images, 所以notifySingle是一个回调函数
2、_dyld_objc_notify_register 这个方法的调用为 _objc_init函数
3、_objc_init 引导初始化。注册我们的镜像通知 与 dyld
4、在库初始化时间之前由libSystem调用
- 搜索
load_images
底层实现 实现所有的load方法
再次回到
根据方法名及源代码 我们知道此方法为递归的初始化
_dyld_objc_notify_register
符号断点 实现构造函数constructor
断点load
方法 断言调用栈对比图 流程
无论是 libSystem的初始化
, 还是 constructor
函数的调用 都
是由 doInitialization函数发起调用 当然看 调用栈和上面的源码我们也知道它的入口来自 recursiveInitialization
函数
查看doInitialization函数
doImageInit 函数 for循环初始化镜像 libSystem初始化优先级最高
doModInitFunctions 初始化所有 cxx文件 libSystem初始化优先级最高
流程图
下一篇 dyld与objc的关联