前言:
动态库和静态库
我们都知道,一段程序的运行,都会依赖各种各样的库,那么什么是程序依赖的库呢?简而言之,库就是一个可执行的二进制文件,作为程序运行的支撑。通俗的说,当我们需要造一辆汽车的时候,库就是轮子。我们常见windows
系列的库就是.dll
文件,linux
系列库就是.so
文件。
库有动态库、静态库之分,那么iOS
使用的库有哪些呢?
- 静态库:
.a、.lib
- 动态库:
framework
动态库和静态库加载的区别:
- 静态库在链接阶段,会将汇编生成的目标与引用的库一起链接打包到可执行的文件中。
- 动态库并不会链接到目标代码中,在运行时才会被载入。
动态库的优势:
- 减少打包之后
App
的大小 - 共享内容
- 动态更新
常见的动态库:
UIKit
libdispatch
libobjc.dyld
程序的编译过程
程序的编译过程其实就是将源文件转化成可执行文件的过程:
App
加载分析
当我们运行一个iOS App
的时候,它的加载过程是什么呢?当我们点击桌面应用图标的时候,App
就会进入启动流程,系统先会加载libSystem
,runtime
向dyld
注册回调函数,然后加载新的镜像文件(image)
,执行map_images、load_images
,最后调用main
函数,这样App
就会启动起来。
我们创建一个新程序,在ViewController.m
文件中实现如下的方法:
+ (void)load{
NSLog(@"%s",__func__);
}
然后在main
函数的入口处打一个断点,当程序停在断点的时候,我们会发现控制台已经输出如下log
:
+[ViewController load]
说明,当程序进入main
函数之前,已经执行了类的load
方法。
查看旁边的调用顺序:
我们可以看到,在main函数之前,会调用一个start方法,该方法即
libdyld.dylib`start
那么,dyld
是什么呢?
dyld
dyld
简介
dyld: the dynamic link editor,苹果系统的动态链接器
。它的作用是在需要使用动态库的时候将其加载到内存中。那么dyld
是怎样将动态库加载到内存中的?dyld
的加载流程又是什么样的呢?
dyld
加载流程
在上面的代码中,我们可以看出,在main
函数之前,会先调用dyld_start
函数,通过查看dyld相关源码
,我们来逐步分析其加载流程:
汇编部分
首先进行全局搜索dyld_start
,进入dyldStartup.s
文件,找到arm64
相关部分,如图:
如图所示,在进行一系列汇编操作之后,程序会跳转到:
__ZN13dyldbootstrap5startEPK12macho_headeriPPKclS2_Pm
c++部分
也就是调用一个c++
方法:
dyldbootstrap::start(app_mh, argc, argv, slide, dyld_mh, &startGlue)
全局搜索“start(
”,即可找到该方法对应的实现部分:
uintptr_t start(const struct macho_header* appsMachHeader, int argc, const char* argv[],
intptr_t slide, const struct macho_header* dyldsMachHeader,
uintptr_t* startGlue)
{
// 忽略其它条件
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
该方法最终会调用dyld::_main
方法:
由于该方法比较长,我们就不一一贴出代码,择重要的展示:
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
// 设置上下文
setContext(mainExecutableMH, argc, argv, envp, apple);
// 检查环境变量的相关操作
checkEnvironmentVariables(envp);
defaultUninitializedFallbackPaths(envp);
// 加载共享缓存
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);
mapSharedCache();
// 将dyld添加到UUID列表
addDyldImageToUUIDList();
// 为主要可执行文件实例化ImageLoader
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
// 加载任何插入的库
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// 链接库
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);
}
}
// 运行所有的初始化
initializeMainExecutable();
// 通知任何监听的进程此进程即将进入main()函数
notifyMonitoringDyldMain();
}
具体
通过该方法的注释,我们可以知道dyld::_main
是dyld方法的入口
,内核设置了寄存器,并且跳转到__dyld_start
,然后调用了该方法。
准备工作:
- 设置上下文信息
- 检查环境变量的配置
// 此处只展示较为重要的代码
static void checkEnvironmentVariables(const char* envp[])
{
if ( !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsPrint )
return;
const char** p;
for(p = envp; *p != NULL; p++) {
......
processDyldEnvironmentVariable(key, value, NULL);
}
......
}
加载共享缓存
// 此处只展示较为重要的代码
static void mapSharedCache()
{
dyld3::SharedCacheOptions opts;
opts.cacheDirOverride = sSharedCacheOverrideDir;
opts.forcePrivate = (gLinkContext.sharedRegionMode == ImageLoader::kUsePrivateSharedRegion);
opts.verbose = gLinkContext.verboseMapping;
loadDyldCache(opts, &sSharedCacheLoadInfo);
....
}
bool loadDyldCache(const SharedCacheOptions& options, SharedCacheLoadInfo* results)
{
results->loadAddress = 0;
results->slide = 0;
results->errorMessage = nullptr;
if ( options.forcePrivate ) {
// 仅加载当前进程的缓存
return mapCachePrivate(options, results);
}
else {
// 缓存已经被加载到共享缓存文件了
bool hasError = false;
if ( reuseExistingCache(options, results) ) {
hasError = (results->errorMessage != nullptr);
} else {
// 第一次加载缓存
hasError = mapCacheSystemWide(options, results);
}
return hasError;
}
......
}
加载镜像文件:
- 为可执行文件实例化
imageLoader
- 为主可执行文件创建
image
- 加载任何插入动态库
- 链接动态库
为可执行文件实例化imageLoader
:
// 此处只展示较为重要的代码
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath)
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
// 为主可执行文件创建image
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);
// 添加镜像
addImage(image);
return (ImageLoaderMachO*)image;
}
}
为主可执行文件创建image:
// 此处只展示较为重要的代码
ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, uintptr_t slide, const char* path, const LinkContext& context)
{
bool compressed;
unsigned int segCount;
unsigned int libCount;
const linkedit_data_command* codeSigCmd;
const encryption_info_command* encryptCmd;
// 确定此mach-o文件是否具有经典或压缩的LINKEDIT以及其具有的段数
sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd);
// instantiate concrete class based on content of load commands
if ( compressed )
return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context);
}
加载任何插入动态库:
// 此处只展示较为重要的代码
static void loadInsertedDylib(const char* path)
{
ImageLoader* image = NULL;
unsigned cacheIndex;
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.enforceIOSMac = true;
context.origin = NULL; // can't use @loader_path with DYLD_INSERT_LIBRARIES
context.rpath = NULL;
image = load(path, context, cacheIndex);
}
......
}
链接程序:
// 此处只展示较为重要的代码
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();
}
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing(gLinkContext);
}
}
// 递归加载所有依赖库
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths, const char* imagePath)
{
(*context.setErrorStrings)(0, NULL, NULL, NULL);
uint64_t t0 = mach_absolute_time();
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths, imagePath);
......
}
void ImageLoaderMegaDylib::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths, const char* loadPath)
{
unsigned index = findImageIndex(context, loadPath);
recursiveMarkLoaded(context, index);
}
运行所有的初始化:
一步一步递归,初始化任何插入的库。
// 此处只展示较为重要的代码
void initializeMainExecutable()
{
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]);
}
}
......
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
uint64_t t1 = mach_absolute_time();
mach_port_t thisThread = mach_thread_self();
ImageLoader::UninitedUpwards up;
up.count = 1;
up.images[0] = this;
processInitializers(context, thisThread, timingInfo, up);
......
}
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;
for (uintptr_t i=0; i < images.count; ++i) {
images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);
}
if ( ups.count > 0 )
processInitializers(context, thisThread, timingInfo, ups);
}
// 在每一个image初始化完成之后,通知objc和anyone,当前image的初始化工作完成了
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
// let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// 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);
recursiveSpinUnLock();
}
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);
}
那么当image加载完成之后,它是怎们告诉大家它加载完成了呢?答案是指针回调。加载完成之后,会调用下面的方法:
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
而这两个方法的实现如下:
// 此处只展示较为重要的代码
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
......
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
......
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
......
}
......
}
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;
// call 'init' function on all images already init'ed (below libSystem)
for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
// 该函数的注释大概可以知道:
// _dyld_objc_notify_mapped是dyld已经加载完成image进行映射操作
// _dyld_objc_notify_init 是初始化image操作
// _dyld_objc_notify_unmapped 解除映射操作
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);
}
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
追踪该函数的调用,我们发现当调用到_objc_init
函数时,此处就注册了image
加载完成的通知。那么什么时候会调用_objc_init
方法呢?在此处打一个断点,运行程序,在lldb
查看使用bt
指令,就可以看到代码调用的顺序如下:
分析得到如下流程图:
总结:dyld加载顺序
-
__dyly_start(汇编)
-
uintptr_t start()
-
uintptr_t _main()
- 3.1 配置上下文
- 3.2 处理环境变量
- 3.3 加载共享缓存
- 3.4 将
dyld
加入UUID
列表 - 3.5 加载所有
image
- 3.5.1 为可执行文件实例化
imageLoader
- 3.5.2 为主可执行文件创建
image
- 3.5.3 加载任何插入动态库
- 3.5.4 链接动态库
- 3.5.1 为可执行文件实例化
- 3.6 初始化所有程序
- 3.6.1 遍历初始化
image
- 3.6.2 通知
image
初始化完成 - 3.6.3 初始化
libSystem
- 3.6.4 初始化
libdispatch
- 3.6.5 初始化
libobjc
- 3.6.1 遍历初始化
- 3.7 进入主程序的
main
函数
Tips: 共享缓存(dyld_shared_cache)
dyld
加载时,为了优化程序启动,使用了共享缓存(dyld_shared_cache)
。共享缓存会在进程启动时被dyld
映射到内存中,之后,当任何Mach-O
映像加载时,dyld
首先会检查该Mach-O
映像与所需的动态库是否在共享缓存中,如果存在,则直接将它在共享内存中的内存地址映射到进程的内存地址空间。在程序依赖的系统动态库很多的情况下,这种做法对程序启动性能是有明显提升的。
update_dyld_shared_cache
程序确保了dyld
的共享缓存是最新的,它会扫描 /var/db/dyld/shared_region_roots/
目录下paths
路径文件,这些paths
文件包含了需要加入到共享缓存的Mach-O
文件路径列表,update_dyld_shared_cache()
会挨个将这些Mach-O
文件及其依赖的dylib
都加共享缓存中去。
共享缓存是以文件形式存放在/var/db/dyld/
目录下的,生成共享缓存的update_dyld_shared_cache
程序位于是/usr/bin/
目录下。
Tips: 函数指针
定义:指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。
用途:调用函数和做函数的参数。
eg:
void (*funcP)(int num);
void printFunc(int num) {
printf("num:%d\n",num);
}
int main(int argc,char *argv[]) {
funcP = printFunc;
(*funcP)(100);
funcP(100);
return 0;
}
运行以上程序,控制台就会输出100;100。因为funcP
是一个函数指针,当执行funcP = printFunc
的时候,其实就是让funcP
指向printFunc
,所以调用(*funcP)(100)
实际上就是调用printFunc(100)
。而funcP(100)
的结果和(*funcP)(100)
是一样的,只是写法不同。
Tips: 回调函数
定义:通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
eg:
//函数功能:实现求和
int func_sum(int n,int b) {
return a + b;
}
//这个函数是回调函数,其中第二个参数为一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
int callback(int m, int n, int (*p)(int))
{
return p(m, n);
}
int main(void)
{
printf("the sum is %d\n", callback(1, 2, func_sum));
return 0;
}
此处输出控制台输出3,
Tips:指针函数
定义:返回值是指针的函数。
eg:
int * func_sum(int a, int, b)
{
sum = a + b;
int *p = ∑
return p;
}