前言
我们平常在开发的App中,有很多我们自己编写的代码,也依赖了很多的动静态库(libdispatch、libobjc、UIKit)等,我们要向让应用程序用到这些内容,需要将我们的代码装载进内存才可以使用
复制代码
静态库 & 动态库
相同点:
静态库和动态库是一种可执行的二进制文件(build生成的exec文件),能够被操作系统加载到内存中来,那么他们的主要区别在于链接
区别:
静态库(.a .lib...):在链接阶段,会将汇编生成的目标文件与引用的库一起链接打包到可执行文件当中
动态库(.so .dll .framework...):程序编译时并不会链接到目标代码中,而是在程序运行时才被载入
动态库优势:
1、减少打包后的App大小
2、共享内容、节约资源
3、通过更新动态库,达到更新程序的目的(苹果已禁止)
复制代码
编译过程

App加载过程
应用代码开发之后,库文件和代码加载进内存中才能去执行,过程中链接器dyld起了非常重要的作用,dyld的加载过程大体是什么样的?

加载库的时候并不是真的将库文件加到内存,而是映射了一份镜像文件,即inages,在程序中打一个断点,转到lldb调试台,输入image list,就能看到应用程序所应用的库
Runtime注册的回调函数 _dyld_objc_notify_register(&map_images, load_images, unmap_image);注册了三个回调函数,当objc image被映射,取消映射,以及初始化是分别调用
dyld流程
引入
新建xcode工程,正常运行,分别再main 和 viewcontroller +load方法处打断点,发现先来的+load方法,然后在走main函数,调用堆栈如下

dyld先从_dyld_start开始,我们去官网下载一份dyld代码 opensource.apple.com/tarballs/dy…
源码分析
打开dyld源码,全局搜索_dyld_start,发现并没有这个函数,但是能看到_dyld_start的汇编代码,根据不同架构,找到arm架构部分代码,根据注释可以知道是跳转到dyldbootstrap::start()函数,接下来搜索c++命名空间dyldbootstrap,然后在命名空间中找到 start函数

dyld::start流程
作用:dyld的入口点,内核加载dyld并跳转到__dyld_start,设置一些寄存器并调用dyld::main函数

start方法的主要作用就是:先读取Mach-O文件的头部信息,设置虚拟地址偏移,这里的偏移主要用于重定向。接下来就是初始化Mach-O文件,用于后续加载库文件和DATA数据,再运行C++的初始化器,最后进入dyly的主函数。
dyld::main流程
在记录一下流程之前,了解一些关于MachO的相关知识:
// 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 ( 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;
}
复制代码
实际上是从Mach-O文件中依次读取所有的segment,判断segment->segname是否包含__RESTRICT字符串来判断是否受限。
getHostInfo:获取当前设备的CPU架构信息,设置环境变量完成且获取了CPU信息后,dyld就开始准备初始化主程序了
2、初始化主程序
a:将dyld添加到UUIDlist中
b:加载可执行文件 实例化ImageLoader对象
addDyldImageToUUIDList:加载DYLD到UUID list中,方法的实现如下:
// 将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中并更新个数。
instantiateFromLoadedImage:实例化一个ImageLoader
// 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";
}
复制代码
3 加载共享缓存
何为共享缓存,比如我们都知道iOS开发中会依赖系统的UIKit以及Foundation库,那么iOS系统中安装很多应用每个应用都要有自己独立加载UIKit吗?当然不是,所有的App会共用一份UIKit库,而这份UIKit库就存放在共享缓存中。
这一步我们重点关注:checkSharedRegionDisable,mapSharedCache
checkSharedRegionDisable: iOS如果没有共享库将无法运行
static void checkSharedRegionDisable() {
// iPhoneOS cannot run without shared region
}
复制代码
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架构匹配的缓存文件, 接着读取缓存文件的前8192字节, 解析缓存头dyld_cache_header的信息, 将解析好的缓存信息存入mappings变量, 最后调用_shared_region_map_and_slide_np()完成真正的映射工作。
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路径的过程:

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);
}
复制代码
6、链接插入的动态库
在链接主程序后链接插入的动态库,因此所有插入的动态库都会在系统使用的动态库后面。
与链接主程序相同,拆入的动态库也是通过调用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、执行初始化方法
// 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]);
}
复制代码
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:该方法遍历了Load Commands 找到LC_MAIN命令的入口点地址返回,这个地址就是main函数的地址
// 查找主程序的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;
}
复制代码
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:主要是注册了map_images,load_images方法
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
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();
}
复制代码
























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)