在上一篇文章中我们介绍了应用启动在objc_init方法执行前的调用堆栈,根据这个堆栈我们可以看出在main函数之前实际上系统内核以及dyld还做了很多的操作,那么这篇文章我们来详细的看一下在这个过程中到底做了哪些事情。
我们在来看下这这张图:
从上图中我们看一看到应用启动的入口实际是_dyld_start函数,我们从XNU源码dyldStartup.s
中找到了这个方法:
__dyld_start
__dyld_start:
//..........省略掉汇编代码
// call dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
bl __ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
//..........省略掉汇编代码
__dyld_start
是一个汇编方法(看不懂😢),不过我们也可以看出这个方法里实际上是调用了dyldbootstrap::start
方法,恰好也验证了我们截图中的调用堆栈。
dyldbootstrap::start
dyldbootstrap::start(...)
, 首先bootstrapping dyld, 然后调用dyld::_main核心方法
// appsMachHeader 即mach-o文件的header字段
// argc 即 argument count 即程序运行的参数个数
// argv[] 即 argument value 是一个字符串数组 用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数
// slide 偏移量
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[],
intptr_t slide, const struct macho_header* dyldsMachHeader,
uintptr_t* startGlue)
{
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
// 如果slide dyld, 我们必须 fixeup dyly中的内容
if ( slide != 0 ) {
// 重新设定dyld
rebaseDyld(dyldsMachHeader, slide);
}
// allow dyld to use mach messaging
// 允许dyld使用mach消息传递
mach_init();
// kernel sets up env pointer to be just past end of agv array
// 内核设置的env pointers, 也就是环境参数
// envp = environment pointer
// 取出argv的第argc条数据 但是实际上argv 只有argc个参数
// 因此 envp 默认是紧挨着argv存储的
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
// kernel将apple指针设置为刚好超出envp数组的末尾
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
// 栈溢出保护
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
// 在dyld中运行所有C初始化程序
runDyldInitializers(dyldsMachHeader, slide, argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
// 调用dyld的main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
这里附上start
方法的重要参数macho_header
的结构体:
struct mach_header_64 {
uint32_t magic; /* 区分系统架构版本 */
cpu_type_t cputype; /*CPU类型 */
cpu_subtype_t cpusubtype; /* CPU具体类型 */
uint32_t filetype; /* 文件类型 */
uint32_t ncmds; /* loadcommands 条数,即依赖库数量*/
uint32_t sizeofcmds; /* 依赖库大小 */
uint32_t flags; /* 标志位 */
uint32_t reserved; /* 保留字段,暂没有用到*/
};
start
方法的主要作用就是:先读取Mach-O文件的头部信息,设置虚拟地址偏移,这里的偏移主要用于重定向。接下来就是初始化Mach-O文件,用于后续加载库文件和DATA数据,再运行C++的初始化器,最后进入dyly的主函数。
dyld::_main
// dyld的main函数 dyld的入口方法kernel加载dyld并设置设置一些寄存器并调用此函数,之后跳转到__dyld_start
// mainExecutableSlide 主程序的slider,用于做重定向 会在main方法中被赋值
// mainExecutableMH 主程序MachO的header
// argc 表示main函数参数个数
// argv 表示main函数的参数值 argv[argc] 可以获取到参数值
// envp[] 表示以设置好的环境变量
// apple 是从envp开始获取到第一个值为NULL的指针地址
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
uintptr_t result = 0;
// 1 设置运行环境,处理环境变量 mainExecutableMH为macho_header类型
// 表示的是当前主程序的Mach-O头部信息, 有了头部信息, 加载器就可以从头开始, 遍历整个Mach-O文件的信息
sMainExecutableMachHeader = mainExecutableMH;
CRSetCrashLogMessage("dyld: launch started");
// 设置上下文 包括一些回调函数, 参数与标志设置信息
setContext(mainExecutableMH, argc, argv, envp, apple);
// Pickup the pointer to the exec path.
// 获取指向exec路径的指针 执行exec相关指令 apple是一个数组 所以apple表示数组首元素的地址
// _simple_getenv 方法可以理解为从apple中获取"executable_path"对应的值
sExecPath = _simple_getenv(apple, "executable_path");
// <rdar://problem/13868260> Remove interim apple[0] transition code from dyld
if (!sExecPath) sExecPath = apple[0];
// 将可执行文件的路径由相对路径转化成绝对路径
bool ignoreEnvironmentVariables = false;
// 判断是否是相对路径的条件
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
// 获取可执行文件去除前面的路径, 获取它的name
// strrchr:在参数 sExecPath 所指向的字符串中搜索最后一次出现字符 '/'的位置
sExecShortName = ::strrchr(sExecPath, '/');
// 如果获取到了文件名的位置
if ( sExecShortName != NULL )
// 文件名真正的起始位置
++sExecShortName;
else
// 文件名起始位置就是绝对路径
sExecShortName = sExecPath;
// 配置进程是否受到限制
sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);
// 如果进程受限
if ( sProcessIsRestricted ) {
#if SUPPORT_LC_DYLD_ENVIRONMENT
// 检查加载命令环境变量
// 遍历Mach-O中所有的LC_DYLD_ENVIRONMENT加载命令, 然后调用processDyldEnvironmentVariable()对不同的环境变量做相应的处理
checkLoadCommandEnvironmentVariables();
#endif
// 删除进程的LD_LIBRARY_PATH与所有以DYLD_开头的环境变量, 这样以后创建的子进程就不包含这些环境变量了
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
// 重新设置链接上下文。这一步执行的主要目的是由于环境变量发生变化了, 需要更新进程的envp与apple参数
setContext(mainExecutableMH, argc, argv, envp, apple);
}
else {
if ( !ignoreEnvironmentVariables )
// 检查环境变量
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
}
// 打印信息 不需要关注
if ( sEnv.DYLD_PRINT_OPTS )
printOptions(argv);
if ( sEnv.DYLD_PRINT_ENV )
printEnvironmentVariables(envp);
// 获取当前设备的CPU架构信息
getHostInfo(mainExecutableMH, mainExecutableSlide);
// install gdb notifier
// 注册gdb的监听者, 用于调试
stateToHandlers(dyld_image_state_dependents_mapped, sBatchHandlers)->push_back(notifyGDB);
stateToHandlers(dyld_image_state_mapped, sSingleHandlers)->push_back(updateAllImages);
// make initial allocations large enough that it is unlikely to need to be re-alloced
sAllImages.reserve(INITIAL_IMAGE_COUNT);
sImageRoots.reserve(16);
sAddImageCallbacks.reserve(4);
sRemoveImageCallbacks.reserve(4);
sImageFilesNeedingTermination.reserve(16);
sImageFilesNeedingDOFUnregistration.reserve(8);
#ifdef WAIT_FOR_SYSTEM_ORDER_HANDSHAKE
// <rdar://problem/6849505> Add gating mechanism to dyld support system order file generation process
WAIT_FOR_SYSTEM_ORDER_HANDSHAKE(dyld::gProcessInfo->systemOrderFlag);
#endif
//2 初始化主程序
try {
// add dyld itself to UUID list
// 将dyld添加到UUIDlist中
addDyldImageToUUIDList();
CRSetCrashLogMessage(sLoadingCrashMessage);
// instantiate ImageLoader for main executable
// 加载sExecPath路径下的可执行文件, 实例化一个ImageLoader对象
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// 设置上下文, 将MainExecutable 这个 ImageLoader设置给链接上下文, 配置链接上下文其他变量
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.processIsRestricted = sProcessIsRestricted;
gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
// load shared cache
// 3 加载共享缓存
checkSharedRegionDisable();
#if DYLD_SHARED_CACHE_SUPPORT
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion )
// 映射共享缓存
mapSharedCache();
#endif
// Now that shared cache is loaded, setup an versioned dylib overrides
#if SUPPORT_VERSIONED_PATHS
checkVersionedPaths();
#endif
// load any inserted libraries
// 4 加载插入的动态库
// 变量 `DYLD_INSERT_LIBRARIES` 环境变量, 调用`loadInsertedDylib`方法加载所有要插入的库,
// 这些库都被加入到`sAllImages`数组中
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.
// 记录插入的库的数量,以便进行统一搜索插入的库,然后是main,然后是其他
sInsertedDylibCount = sAllImages.size()-1;
// link main executable
// 5 链接主程序
// 开始链接主程序, 此时主程序已经被加载到gLinkContext.mainExecutable中,
// 调用 link 链接主程序。内核调用的是ImageLoader::link 函数。
gLinkContext.linkingMainExecutable = true;
// link方法
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
// 设置永不递归卸载
sMainExecutable->setNeverUnloadRecursive();
// mach-o header中的MH_FORCE_FLAT
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// link any inserted libraries
// 6 链接插入的动态库
// 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
// 对 sAllimages (除了主程序的Image外)中的库调用link进行链接,
// 然后调用 registerInterposing 注册符号插入, 例如是libSystem就是此时加入的
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
// link
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
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];
// 注册符号插入,Interposition, 是通过编写与函数库同名的函数来取代函数库的行为.
image->registerInterposing();
}
}
// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
for (int i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image->inSharedCache() )
continue;
image->registerInterposing();
}
// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(); ++i) {
sImageRoots[i]->applyInterposing(gLinkContext);
}
gLinkContext.linkingMainExecutable = false;
// <rdar://problem/12186933> do weak binding only after all inserted images linked
// 7 执行弱符号绑定
sMainExecutable->weakBind(gLinkContext);
CRSetCrashLogMessage("dyld: launch, running initializers");
#if SUPPORT_OLD_CRT_INITIALIZATION
// Old way is to run initializers via a callback from crt1.o
if ( ! gRunInitializersOldWay )
initializeMainExecutable();
#else
// run all initializers
// 8 执行初始化方法
// 执行初始化方法, 其中`+load` 和constructor方法就是在这里执行,
// `initializeMainExecutable`方法先是内部调用动态库的初始化方法, 然后调用主程序的初始化方法
initializeMainExecutable();
#endif
// find entry point for main executable
// 9 查找APP入口点并返回
result = (uintptr_t)sMainExecutable->getThreadPC();
if ( result != 0 ) {
// main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
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->getMain();
*startGlue = 0;
}
}
catch(const char* message) {
syncAllImages();
halt(message);
}
catch(...) {
dyld::log("dyld: launch failed\n");
}
CRSetCrashLogMessage(NULL);
return result;
}
下面我们对main
函数进行拆分讲解
1 设置运行环境,处理环境变量
这一步我们主要关注sExecPath
,processRestricted
,getHostInfo
这几个方法。
sExecPath
sExecPath = _simple_getenv(apple, "executable_path");
我们在介绍参数的时候介绍到 apple 实际上存储这应用的环境变量的数组,executable_path
就表示执行路径,而_simple_getenv方法就是从apple中获取executable_path
对应的值。不过这里获取到的可能是一个相对路径,而dyld判断是否为相对路径的条件:
if ( sExecPath[0] != '/' ) {
// 相对路径
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;
}
}
这样我们就可以获取到执行文件的绝对路径。在获取到绝对路径后,我们可以根据绝对路径获取到执行文件的文件名:
sExecShortName = ::strrchr(sExecPath, '/');
strrchr方法的功能为:在参数 sExecPath 所指向的字符串中搜索最后一次出现字符 '/'的位置
。
processRestricted
进程是否受限,这里我们主要关注下Mach-O相关的一个判断:
// 进程受限
static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation)
{
// <rdar://problem/13158444&13245742> Respect __RESTRICT,__restrict section for root processes
// 段名受限。当Mach-O包含一个__RESTRICT/__restrict段时,进程会被设置成受限
if ( hasRestrictedSegment(mainExecutableMH) ) {
// existence of __RESTRICT/__restrict section make process restricted
sRestrictedReason = restrictedBySegment;
return true;
}
return false;
}
hasRestrictedSegment方法的实现如下:
//dyld::log("seg name: %s\n", seg->segname);
if (strcmp(seg->segname, "__RESTRICT") == 0) {
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if (strcmp(sect->sectname, "__restrict") == 0)
return true;
}
}
实际上是从Mach-O文件中依次读取所有的segment,判断segment->segname是否包含__RESTRICT字符串来判断是否受限。
getHostInfo
getHostInfo是用来获取当前设备的CPU架构信息。
我们来简单看下这个方法的实现:
static void getHostInfo(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide)
{
#if CPU_SUBTYPES_SUPPORTED
#if __ARM_ARCH_7K__
sHostCPU = CPU_TYPE_ARM;
sHostCPUsubtype = CPU_SUBTYPE_ARM_V7K;
#elif __ARM_ARCH_7A__
sHostCPU = CPU_TYPE_ARM;
sHostCPUsubtype = CPU_SUBTYPE_ARM_V7;
#elif __ARM_ARCH_6K__
sHostCPU = CPU_TYPE_ARM;
sHostCPUsubtype = CPU_SUBTYPE_ARM_V6;
#elif __ARM_ARCH_7F__
sHostCPU = CPU_TYPE_ARM;
sHostCPUsubtype = CPU_SUBTYPE_ARM_V7F;
#elif __ARM_ARCH_7S__
sHostCPU = CPU_TYPE_ARM;
sHostCPUsubtype = CPU_SUBTYPE_ARM_V7S;
#else
struct host_basic_info info;
mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
mach_port_t hostPort = mach_host_self();
kern_return_t result = host_info(hostPort, HOST_BASIC_INFO, (host_info_t)&info, &count);
if ( result != KERN_SUCCESS )
throw "host_info() failed";
sHostCPU = info.cpu_type;
sHostCPUsubtype = info.cpu_subtype;
mach_port_deallocate(mach_task_self(), hostPort);
#if __x86_64__
#if TARGET_IPHONE_SIMULATOR
sHaswell = false;
#else
sHaswell = (sHostCPUsubtype == CPU_SUBTYPE_X86_64_H);
// <rdar://problem/18528074> x86_64h: Fall back to the x86_64 slice if an app requires GC.
if ( sHaswell ) {
if ( isGCProgram(mainExecutableMH, mainExecutableSlide) ) {
// When running a GC program on a haswell machine, don't use and 'h slices
sHostCPUsubtype = CPU_SUBTYPE_X86_64_ALL;
sHaswell = false;
gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
}
}
#endif
#endif
#endif
#endif
}
设置环境变量完成且获取了CPU信息后,dyld就开始准备初始化主程序了,下面我们看下main函数的下一步初始化主程序。
2 初始化主程序
初始化主程序主要做了两件事:
- 将dyld添加到UUIDlist中
- 加载可执行文件 实例化ImageLoader对象
下面我们来详细看下这两步分别都做了什么
addDyldImageToUUIDList
addDyldImageToUUIDList方法是加载DYLD到UUID list中,我们来看下这个方法的实现:
// <rdar://problem/10583252> Add dyld to uuidArray to enable symbolication of stackshots
// 将dyld添加到uuidArray以启用符号堆叠
static void addDyldImageToUUIDList()
{
const struct macho_header* mh = (macho_header*)&__dso_handle;
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)((char*)mh + sizeof(macho_header));
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_UUID: {
uuid_command* uc = (uuid_command*)cmd;
// 新建一个dyld_uuid_info
dyld_uuid_info info;
// 给新建的info imageLoadAddress 字段赋值
info.imageLoadAddress = (mach_header*)mh;
// 复制uc->uuid的16个字节给info.imageUUID
memcpy(info.imageUUID, uc->uuid, 16);
// 利用组装好的info给dyld的gProcessInfo的uuidArray和uuidArrayCount赋值
addNonSharedCacheImageUUID(info);
return;
}
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
从代码中我们可以看出,这个方法是遍历了Mach-O
中的load_command
并将cmd->cmd
值为LC_UUID
的添加到dyld::gProcessInfo->uuidArray
中并更新个数。
我们可以通过addNonSharedCacheImageUUID的实现进一步确认:
// 将info中的uuidArray添加到dyld::gProcessInfo中
void addNonSharedCacheImageUUID(const dyld_uuid_info& info)
{
// set uuidArray to NULL to denote it is in-use
// 将uuidArray设置为NULL 表示这个字段正在使用中
dyld::gProcessInfo->uuidArray = NULL;
// append all new images
// 追加外部传入的info到sImageUUIDs中
sImageUUIDs.push_back(info);
// 重新设置追加后uuidArrayCount
dyld::gProcessInfo->uuidArrayCount = sImageUUIDs.size();
// set uuidArray back to base address of vector (other process can now read)
// 更新追加后的uuidArray
dyld::gProcessInfo->uuidArray = &sImageUUIDs[0];
}
instantiateFromLoadedImage
从方法名中我们就可以看到这个方法是实例化一个ImageLoader,下面我们来详细了解下这个方法:
// mh 即 Mach-O文件的header
// slide 表示偏移量
// path 表示可执行文件地址
static ImageLoader* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
// 检查mach-o的subtype是否是当前cpu可以支持
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
// 根据传入的参数实例化一个ImageLoaderMachO类型的ImageLoader
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
// 将主程序添加到全局主列表sAllImages中,
// 最后调用addMappedRange()申请内存, 更新主程序映像映射的内存区
addImage(image);
return image;
}
throw "main executable not a known format";
}
实例化一个ImageLoaderMachO后,我们将第一步获取到的一些变量设置给我们刚创建的ImageLoader:
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.processIsRestricted = sProcessIsRestricted;
gLinkContext.processRequiresLibraryValidation = sProcessRequiresLibraryValidation;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
3 加载共享缓存
何为共享缓存,比如我们都知道iOS开发中会依赖系统的UIKit以及Foundation库,那么iOS系统中安装很多应用每个应用都要有自己独立加载UIKit吗?当然不是,所有的App会共用一份UIKit库,而这份UIKit库就存放在共享缓存中。
这一步我们重点关注:checkSharedRegionDisable
,mapSharedCache
,checkVersionedPaths
这几个方法:
checkSharedRegionDisable
static void checkSharedRegionDisable()
{
// iPhoneOS cannot run without shared region
}
这个方法中包含了一些Mac OS的判断不过在方法的最后,系统的注释: iOS如果没有共享库将无法运行。所以这个方法我们也不需要多做解读
mapSharedCache
static void mapSharedCache() {
// 快速检查缓存是否已经被加载到共享缓存中了 如果没有返回-1
if ( _shared_region_check_np(&cacheBaseAddress) == 0 ) {
if ( (header->mappingOffset >= 0x48) && (header->slideInfoSize != 0) ) {
// solve for slide by comparing loaded address to address of first region
// 通过比较加载的地址和第一个区域的地址来解决偏移问题
const uint8_t* loadedAddress = (uint8_t*)sSharedCache;
const dyld_cache_mapping_info* const mappings = (dyld_cache_mapping_info*)(loadedAddress+header->mappingOffset);
const uint8_t* preferedLoadAddress = (uint8_t*)(long)(mappings[0].address);
//加载的地址 - 第一个区域的地址
// 更新偏移量
sSharedCacheSlide = loadedAddress - preferedLoadAddress;
dyld::gProcessInfo->sharedCacheSlide = sSharedCacheSlide;
}
// if cache has a uuid, copy it
// 更新UUID
if ( header->mappingOffset >= 0x68 ) {
memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16);
}
} else {
if ( (sysctlbyname("kern.safeboot", &safeBootValue, &safeBootValueSize, NULL, 0) == 0) && (safeBootValue != 0) ) {
// 安全模式下
::unlink(MACOSX_DYLD_SHARED_CACHE_DIR DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME);
// 设置sharedRegionMode = kDontUseSharedRegion
gLinkContext.sharedRegionMode = ImageLoader::kDontUseSharedRegion;
return;
} else {
// map in shared cache to shared region
int fd = openSharedCacheFile();
if ( fd != -1 ) {
uint8_t firstPages[8192];
if ( ::read(fd, firstPages, 8192) == 8192 ) {
dyld_cache_header* header = (dyld_cache_header*)firstPages;
for (const dyld_cache_mapping_info* p = fileMappingsStart; p < fileMappingsEnd; ++p, ++i) {
mappings[i].sfm_address = p->address;
mappings[i].sfm_size = p->size;
mappings[i].sfm_file_offset = p->fileOffset;
mappings[i].sfm_max_prot = p->maxProt;
mappings[i].sfm_init_prot = p->initProt;
// rdar://problem/5694507 old update_dyld_shared_cache tool could make a cache file
// that is not page aligned, but otherwise ok.
if ( p->fileOffset+p->size > (uint64_t)(stat_buf.st_size+4095 & (-4096)) ) {
dyld::log("dyld: shared cached file is corrupt: %s" DYLD_SHARED_CACHE_BASE_NAME ARCH_NAME "\n", sSharedCacheDir);
goodCache = false;
}
if ( (mappings[i].sfm_init_prot & (VM_PROT_READ|VM_PROT_WRITE)) == (VM_PROT_READ|VM_PROT_WRITE) ) {
readWriteMappingIndex = i;
}
if ( mappings[i].sfm_init_prot == VM_PROT_READ ) {
readOnlyMappingIndex = i;
}
if ( gLinkContext.verboseMapping ) {
dyld::log("dyld: calling _shared_region_map_and_slide_np() with regions:\n");
for (int i=0; i < mappingCount; ++i) {
dyld::log(" address=0x%08llX, size=0x%08llX, fileOffset=0x%08llX\n", mappings[i].sfm_address, mappings[i].sfm_size, mappings[i].sfm_file_offset);
}
}
if (_shared_region_map_and_slide_np(fd, mappingCount, mappings, codeSignatureMappingIndex, cacheSlide, slideInfo, slideInfoSize) == 0) {
// successfully mapped cache into shared region
sSharedCache = (dyld_cache_header*)mappings[0].sfm_address;
sSharedCacheSlide = cacheSlide;
dyld::gProcessInfo->sharedCacheSlide = cacheSlide;
//dyld::log("sSharedCache=%p sSharedCacheSlide=0x%08lX\n", sSharedCache, sSharedCacheSlide);
// if cache has a uuid, copy it
if ( header->mappingOffset >= 0x68 ) {
memcpy(dyld::gProcessInfo->sharedCacheUUID, header->uuid, 16);
}
}
}
}
}
}
}
}
这一步先通过mapSharedCache()
方法来映射共享缓存, 该函数先通过_shared_region_check_np()来检查缓存是否已经映射到了共享区域了, 如果已经映射了, 就更新缓存的slide与UUID, 然后返回;
如果有没有映射 判断系统是否处于安全启动模式(safe-boot mode)下,如果是就删除缓存文件并返回, 如果非安全启动模式, 接下来调用openSharedCacheFile()打开缓存文件, 该函数在sSharedCacheDir路径下, 打开与系统当前cpu架构匹配的缓存文件,也就是/var/db/dyld/dyld_shared_cache_x86_64h, 接着读取缓存文件的前8192字节, 解析缓存头dyld_cache_header的信息, 将解析好的缓存信息存入mappings变量, 最后调用_shared_region_map_and_slide_np()完成真正的映射工作。
checkVersionedPaths
static void checkVersionedPaths()
{
// search DYLD_VERSIONED_LIBRARY_PATH directories for dylibs and check if they are newer
// 读取DYLD_VERSIONED_LIBRARY_PATH环境变量
if ( sEnv.DYLD_VERSIONED_LIBRARY_PATH != NULL ) {
for(const char* const* lp = sEnv.DYLD_VERSIONED_LIBRARY_PATH; *lp != NULL; ++lp) {
// 判断是否需要覆盖当前目录下的库
checkDylibOverridesInDir(*lp);
}
}
// 读取DYLD_VERSIONED_FRAMEWORK_PATH环境变量
// search DYLD_VERSIONED_FRAMEWORK_PATH directories for dylibs and check if they are newer
if ( sEnv.DYLD_VERSIONED_FRAMEWORK_PATH != NULL ) {
for(const char* const* fp = sEnv.DYLD_VERSIONED_FRAMEWORK_PATH; *fp != NULL; ++fp) {
// 判断是否需要覆盖当前目录下的库
checkFrameworkOverridesInDir(*fp);
}
}
}
4、加载插入的动态库
// 遍历 `DYLD_INSERT_LIBRARIES` 环境变量, 调用`loadInsertedDylib`方法加载所有要插入的库,
// 这些库都被加入到`sAllImages`数组中
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
// 这里传入的是每个lib的path
loadInsertedDylib(*lib);
}
上面的这段代码主要是:遍历sEnv.DYLD_INSERT_LIBRARIES所有要拆入的库(地址连续所以使用++获取地址),然后调用了loadInsertedDylib
方法进行加载插入的库。
下面我们来详细看下这个方法:
static void loadInsertedDylib(const char* path)
{
// 创建一个imageloader
ImageLoader* image = NULL;
try {
LoadContext context;
context.useSearchPaths = false;
context.useFallbackPaths = false;
context.useLdLibraryPath = false;
context.implicitRPath = false;
context.matchByInstallName = false;
context.dontLoad = false;
context.mustBeBundle = false;
context.mustBeDylib = true;
context.canBePIE = false;
context.origin = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
context.rpath = NULL;
// 根据外部传入的path和新建的context构造一个ImageLoader
image = load(path, context);
}
}
load
方法会先调用loadPhase0
方法方式从文件加载,而loadPhase0
又会调用loadPhase1
或loadPhase2
去加载,实际上调用层次没加一层都是在对应load
方法的path参数后拼接了一层,是不断的完善path路径的过程:
加载拆入的库后,还需要更新sInsertedDylibCount
:
sInsertedDylibCount = sAllImages.size()-1;
这里的-1操作实际上是排除主程序之外
5、链接主程序
// 开始链接主程序, 此时主程序已经被加载到gLinkContext.mainExecutable中,
// 调用 link 链接主程序。内核调用的是ImageLoader::link 函数。
gLinkContext.linkingMainExecutable = true;
// link方法
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
// 设置永不递归卸载
sMainExecutable->setNeverUnloadRecursive();
// mach-o header中的MH_FORCE_FLAT
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
这一步就是将加载进来的二进制变为可用状态的过程:rebase
=> binding
rebase
就是针对 “mach-o在加载到内存中不是固定的首地址” 这一现象做数据修正的过程。
binding
就是将这个二进制调用的外部符号进行绑定的过程。
lazyBinding
就是在加载动态库的时候不会立即binding, 当时当第一次调用这个方法的时候再实施binding。
例如我们objc代码中需要使用到NSObject, 即符号_OBJC_CLASS_$_NSObject,但是这个符号又不在我们的二进制中,在系统库 Foundation.framework中,因此就需要binding这个操作将对应关系绑定到一起。
Link
这一步我们主要是看link
方法(简化版):
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths)
{
// 递归加载库
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths);
context.notifyBatch(dyld_image_state_dependents_mapped);
// 递归rebase
this->recursiveRebase(context);
context.notifyBatch(dyld_image_state_rebased);
// 递归bind
this->recursiveBind(context, forceLazysBound, neverUnload);
if ( !context.linkingMainExecutable )
// weakBind
this->weakBind(context);
context.notifyBatch(dyld_image_state_bound);
std::vector<DOFInfo> dofs;
// 递归获取DOFSection
this->recursiveGetDOFSections(context, dofs);
context.registerDOFs(dofs);
}
经过link操作后主程序达到了一个可用的状态。
6、链接插入的动态库
在链接主程序后链接插入的动态库,因此所有插入的动态库都会在系统使用的动态库后面。
与链接主程序相同,拆入的动态库也是通过调用link方法进行链接:
link
// sInsertedDylibCount 插入动态库的个数
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));
//
image->setNeverUnloadRecursive();
}
sInsertedDylibCount表示前期通过调用addImage方法插入到sAllImages的动态库的个数,遍历每一个拆入的动态库注意
:这里sAllImages的下标是从1开始的,因为第0个位置存放的是主程序。
registerInterposing
void ImageLoaderMachO::registerInterposing()
{
// mach-o files advertise interposing by having a __DATA __interpose section
// 这个方法是要操作 Mach-O文件的__DATA__区
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
// 找到load_commands中的LC_SEGMENT_COMMAND
case LC_SEGMENT_COMMAND:
{
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
// 查找__DATA段的__interpose节区
if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) {
for (size_t i=0; i < count; ++i) {
// 找到需要应用插入操作(也可以叫作符号地址替换)的数据
if ( this->containsAddress((void*)tuple.replacement) ) {
// 将要替换的符号与被替换的符号信息存入fgInterposingTuples列表中, 供以后具体符号替换时查询
ImageLoader::fgInterposingTuples.push_back(tuple);
}
}
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
registerInterposing()查找__DATA段的__interpose节区, 找到需要应用插入操作(也可以叫作符号地址替换)的数据, 然后做一些检查后, 将要替换的符号与被替换的符号信息存入fgInterposingTuples列表中, 供以后具体符号替换时查询(applyInterposing中会用到)。
applyInterposing
applyInterposing()
-> recursiveApplyInterposing()
-> doInterpose()
-> eachBind()
-> interposeAt()
下面看下interposeAt
方法:
uintptr_t ImageLoaderMachOCompressed::interposeAt(const LinkContext& context, uintptr_t addr, uint8_t type, const char*,
uint8_t, intptr_t, long, const char*, LastLookup*, bool runResolver)
{
if ( type == BIND_TYPE_POINTER ) {
uintptr_t* fixupLocation = (uintptr_t*)addr;
uintptr_t curValue = *fixupLocation;
uintptr_t newValue = interposedAddress(context, curValue, this);
if ( newValue != curValue)
*fixupLocation = newValue;
}
return 0;
}
这个方法的实现很简单就是对比了新值和旧值 如果不同就将对应地址的值改为新值。
7 执行弱符号绑定
void ImageLoader::weakBind(const LinkContext& context)
{
ImageLoader* imagesNeedingCoalescing[fgImagesRequiringCoalescing];
// 将sAllImages中所有含有弱符号的映像合并成一个列表
int count = context.getCoalescedImages(imagesNeedingCoalescing);
// don't need to do any coalescing if only one image has overrides, or all have already been done
// 如果进行weakbind的镜像个数>0
if ( (countOfImagesWithWeakDefinitionsNotInSharedCache > 0) && (countNotYetWeakBound > 0) ) {
// make symbol iterators for each
ImageLoader::CoalIterator iterators[count];
ImageLoader::CoalIterator* sortedIts[count];
for(int i=0; i < count; ++i) {
// 对镜像进行排序
imagesNeedingCoalescing[i]->initializeCoalIterator(iterators[i], i);
sortedIts[i] = &iterators[i];
}
int doneCount = 0;
while ( doneCount != count ) {
// 收集需要进行绑定的弱符号
// 该函数读取映像动态链接信息的weak_bind_off与weak_bind_size来确定弱符号的数据偏移与大小,然后挨个计算它们的地址信息
if ( sortedIts[0]->image->incrementCoalIterator(*sortedIts[0]) )
++doneCount;
// process all matching symbols just before incrementing the lowest one that matches
if ( sortedIts[0]->symbolMatches && !sortedIts[0]->done ) {
ImageLoader* targetImage = NULL;
for(int i=0; i < count; ++i) {
if ( strcmp(iterators[i].symbolName, nameToCoalesce) == 0 ) {
if ( iterators[i].weakSymbol ) {
if ( targetAddr == 0 ) {
// 按照映像的加载顺序在导出表中查找符号的地址
targetAddr = iterators[i].image->getAddressCoalIterator(iterators[i], context);
if ( targetAddr != 0 )
targetImage = iterators[i].image;
}
}
else {
targetAddr = iterators[i].image->getAddressCoalIterator(iterators[i], context);
if ( targetAddr != 0 ) {
targetImage = iterators[i].image;
// strong implementation found, stop searching
break;
}
}
}
}
// tell each to bind to this symbol (unless already bound)
if ( targetAddr != 0 ) {
for(int i=0; i < count; ++i) {
if ( strcmp(iterators[i].symbolName, nameToCoalesce) == 0 ) {
// 绑定操作
// 内部执行绑定的是bindLocation()
iterators[i].image->updateUsesCoalIterator(iterators[i], targetAddr, targetImage, context);
}
}
}
}
}
8、执行初始化方法
执行初始化方法, 其中+load
和constructor方法就是在这里执行。
// initializeMainExecutable 执行初始化方法,其中 +load 和 constructor 方法就是在这里执行。
// initializeMainExecutable 内部先调用了动态库的初始化方法,后调用主程序的初始化方法。
// 初始化主程序
void initializeMainExecutable()
{
// record that we've reached this step
gLinkContext.startedInitializingMainExecutable = true;
// run initialzers for any inserted dylibs
// 给被插入的所有的 dylibs 进行初始化 -- 调用 initialzers
ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
// 这里是下标1开始 排除掉了主程序的初始化
for(size_t i=1; i < rootCount; ++i) {
// 执行镜像的初始化方法
// 从 sImageRoots 中的第一个变量是 MainExcutable image,
// 因此这里初始化的时候需要跳过第一个数据, 对其他后面插入的dylib进行调用
// ImageLoader::runInitializers进行初始化
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// run initializers for main executable and everything it brings up
// 调用主程序的初始化方法
// 单独对 main executable调用ImageLoader::runInitializers进行初始化
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 )
ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}
这个方法主要是执行了ImageLoader的runInitializers方法,下面我看下这个方法的实现:
runInitializers
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
// 初始化当前 imageLoader 中的 image镜像的实际调用方法 ImageLoader::processInitializers
processInitializers(context, thisThread, timingInfo, up);
context.notifyBatch(dyld_image_state_initialized);
}
processInitializers
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
// 处理当前image依赖 dylib动态库, 调用 recursiveInitialization 方法!!!
for (uintptr_t i=0; i < images.count; ++i) {
images.images[i]->recursiveInitialization(context, thisThread, timingInfo, ups);
}
// If any upward dependencies remain, init them.
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
recursiveInitialization
// 递归调用 image 进行初始化, 先调用image依赖的image进行初始化. 直到自己
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
// 当前ImageLoader依赖的Image还没有初始化完, 进入if中, 如果执行完成, 直接返回
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
// break cycles -> 这是设置当前imageLoader的state接近依赖初始化.
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
// 首先初始化image底层的依赖库
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.images[uninitUps.count] = dependentImage;
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
// 递归调用
dependentImage->recursiveInitialization(context, this_thread, timingInfo, uninitUps);
}
}
}
// 到这里image底层的依赖库都递归调用, 初始化完成.
// let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
// 通知 runtime, 当前状态发生变化 -- image的依赖已经完全加载.
// 注意这里可能在runtime中注册了状态监听, 注册了callback函数, 当状态发送变化时,
// 会触发回调函数.
context.notifySingle(dyld_image_state_dependents_initialized, this);
// initialize this image
// 初始化当前image, `ImageLoaderMachO::doInitialization`方法内部会调用image
// 的"Initializer", 这是一个函数指针, 实际是image的初始化方法. 例如
// `libSystem.dylib`, 它的初始化方法就比较特殊, 我们可以参考libSystem的init.c源
// 码, 内部的`libsystem_initializer`函数就是初始化真正调用的函数
// _init_objc方法!!!!
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
//通知runtime, 档期那状态发送变化 -- image自己已经完成初始化!!!!
context.notifySingle(dyld_image_state_initialized, this);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.images[timingInfo.count].image = this;
timingInfo.images[timingInfo.count].initTime = (t2-t1);
timingInfo.count++;
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
}
doInitialization
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
// 调用Mach-O的 init 和 static initializers方法
doImageInit(context);
doModInitFunctions(context);
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
doImageInit
获取mach-o的init方法的地址并调用
void ImageLoaderMachO::doImageInit(const LinkContext& context)
{
if ( fHasDashInit ) {
// mach-o文件中指令的个数
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
// 遍历指令
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_ROUTINES_COMMAND:
// 获取macho_routines_command的init_address
Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide);
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress((void*)func) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath());
// 执行-init方法
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
break;
}
// 计算下一个指令((char*)cmd)+cmd->cmdsize
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
doModInitFunctions
获取mach-o的static initializer的地址并调用
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
if ( fHasInitializers ) {
// mach-o文件中指令的个数
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
// 遍历所有的指令
for (uint32_t i = 0; i < cmd_count; ++i) {
// 如果指令是Mach-o中的LC_SEGMENT_COMMAND
if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
// 从sectionsStart到sectionsEnd遍历所有的macho_section
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
const uint8_t type = sect->flags & SECTION_TYPE;
//
if ( type == S_MOD_INIT_FUNC_POINTERS ) {
Initializer* inits = (Initializer*)(sect->addr + fSlide);
const size_t count = sect->size / sizeof(uintptr_t);
for (size_t i=0; i < count; ++i) {
// 获取到Initializer方法
Initializer func = inits[i];
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress((void*)func) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
// 执行initializer方法
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
}
}
}
// 根据指令的地址+指令大小获取到下一个指令
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
}
}
9、查找APP入口点并返回
这一步也是最后一步主要功能为:
查找到main函数的地址,并返回。
// 9 查找APP入口点并返回
result = (uintptr_t)sMainExecutable->getThreadPC();
if ( result != 0 ) {
// main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
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->getMain();
*startGlue = 0;
}
getThreadPC
// 查找主程序的LC_MAIN加载命令获取程序的入口点,
void* ImageLoaderMachO::getThreadPC() const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
if ( cmd->cmd == LC_MAIN ) {
entry_point_command* mainCmd = (entry_point_command*)cmd;
void* entry = (void*)(mainCmd->entryoff + (char*)fMachOData);
// <rdar://problem/8543820&9228031> verify entry point is in image
if ( this->containsAddress(entry) )
return entry;
else
throw "LC_MAIN entryoff is out of range";
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return NULL;
}
该方法遍历了Load Commands 找到LC_MAIN命令的入口点地址返回,这个地址就是main函数的地址
getMain
如果getThreadPC没有找到LC_MAIN的入口地址
// 在LC_UNIXTHREAD加载命令中去找, 找到后就跳到入口点指定的地址
void* ImageLoaderMachO::getMain() const
{
const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_UNIXTHREAD:
{
#if __i386__
const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16);
void* entry = (void*)(registers->eip + fSlide);
#elif __x86_64__
const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16);
void* entry = (void*)(registers->rip + fSlide);
#elif __arm__
const arm_thread_state_t* registers = (arm_thread_state_t*)(((char*)cmd) + 16);
void* entry = (void*)(registers->__pc + fSlide);
#elif __arm64__
const arm_thread_state64_t* registers = (arm_thread_state64_t*)(((char*)cmd) + 16);
void* entry = (void*)(registers->__pc + fSlide);
#else
#warning need processor specific code
#endif
// <rdar://problem/8543820&9228031> verify entry point is in image
if ( this->containsAddress(entry) ) {
return entry;
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
throw "no valid entry point";
}
objc_init
从上面的步骤描述中我们知道实际上objc_init是在ImageLoaderMachO::doModInitFunctions
时就被调用了,我们先来看代码
doModInitFunctions
// doModInitFunctions方法部分代码
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
for (size_t i=0; i < count; ++i) {
// 获取到Initializer方法
Initializer func = inits[i];
// <rdar://problem/8543820&9228031> verify initializers are in image
if ( ! this->containsAddress((void*)func) ) {
dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath());
}
if ( context.verboseInit )
dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
// 执行initializer方法
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
}
}
doModInitFunctions
方法实际上调用了Initializer
方法,对对一个动态库进行初始化是通过_libdispatch_init
方法进行的 我们来看下这个方法:
_libdispatch_init
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
方法,然后我们在看下这个方法的实现:
_os_object_init
void
_os_object_init(void)
{
// 省略....
_objc_init();
// 省略....
}
从上面的代码中我们看到,在_os_object_init
方法中调用了我们熟知的_objc_init
方法,至此运行时就开始。
_objc_init
void _objc_init(void)
{
// 省略代码...
/*
仅供objc运行时使用,注册在映射、取消映射和初始化objc映像调用的处理程序。dyld将使用包含objc-image-info回调给`mapped`.
这些dylibs将自动引用计数,因此objc将不再需要调用dlopen()防止未加载。
在调用_dyld_objc_notify_register()期间,dyld将调用 `mapped` 在已经加载好 images,稍后dlopen()。
在调动init的时候也会调用`mapped`,在dyld调用的时候,也会调用init函数
在调用任何images +load方法时候
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
这个方法中,主要是注册了map_images
,load_images
方法.
map_images
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
load_images
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
//加载 class+load 和category+load方法
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
//执行 class+load 和category+load方法
call_load_methods();
}
总结
总结上述的整个过程便是在主工程的main函数开始执行之前系统做的所有操作,其中有一些步骤和环境我也不是太清楚,因此需要进一步的完善和梳理。