iOS app代码的编译过程
- 源文件:载入.h、.m、.cpp等文件
- 预处理:替换宏,删除注释,展开头文件,产生.i文件
- 编译:将.i文件转换为汇编语言,产生.s文件
-
汇编:将汇编文件转换为机器码文件,产生.o文件
链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件
静态库
- 静态库在链接阶段,会将可汇编生成的目标程序与引用的库一起链接打包到可执行文件当中。此时的静态库就不会在改变了,因为它是编译时被直接拷贝一份,复制到目标程序里的
- 好处:编译完成后,库文件实际上就没有作用了,目标程序没有外部依赖,直接就可以运行
- 缺点:由于静态库会有两份,所以会导致目标程序的体积增大,对内存、性能、速度消耗很大
动态库
动态库在程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入
优势:
1.减少打包之后app的大小:因为不需要拷贝至目标程序中,所以不会影响目标程序的体积,与静态库相比,减少了app的体积大小。
2.共享内存,节约资源:同一份库可以被多个程序使用通过更新动态库,达到更新程序的目的:由于运行时才载入的特性,可以随时对库进行替换,而不需要重新编译代码缺点:动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境,如果环境缺少了动态库,或者库的版本不正确,就会导致程序无法运行
dyld
-
dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的重要组成部分,在app被编译打包成可执行文件格式的Mach-O文件后,交由dyld负责连接,加载程序。
App的启动流程图如下
- 【app启动起点】:通过程序运行发现,是从
dyld
中的_dyld_start
开始的,所以需要去OpenSource下载一份dyld的源码来进行分析
1、__dyld_start:
2、dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue){
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
rebaseDyld(dyldsMachHeader);
const char** envp = &argv[argc+1];
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
runDyldInitializers(argc, argv, envp, apple);
#endif
_subsystem_init(apple);
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
3、dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
//从环境中获取主要可执行文件的cdHash
// Grab the cdHash of the main executable from the environment
uint8_t mainExecutableCDHashBuffer[20];
const uint8_t* mainExecutableCDHash = nullptr;
if ( const char* mainExeCdHashStr = _simple_getenv(apple, "executable_cdhash") ) {
unsigned bufferLenUsed;
if ( hexStringToBytes(mainExeCdHashStr, mainExecutableCDHashBuffer, sizeof(mainExecutableCDHashBuffer), bufferLenUsed) )
mainExecutableCDHash = mainExecutableCDHashBuffer;
}
getHostInfo(mainExecutableMH, mainExecutableSlide);//获取当前运行环境架构的信息
//【第一步:环境变量配置】
checkEnvironmentVariables(envp);//检查设置的环境变量
defaultUninitializedFallbackPaths(envp);//(DYLD_FALLBACK_LIBRARY_PATH 为nil,为其设置默认值)
//【第二步:共享缓存】
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);//检查共享缓存是否开启(在iOS中必须开启)
mapSharedCache(mainExecutableSlide);//检查共享缓存是否映射到了共享区域
//【第三步:主程序的初始化】instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);//加载可执行文件,并生成一个ImageLoader实例对象
gLinkContext.mainExecutable = sMainExecutable;
gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH);
//【第四步:插入动态库】load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
//加载所有DYLD_INSERT_LIBRARIES指定的库
//遍历DYLD_INSERT_LIBRARIES环境变量,调用loadInsertedDylib加载
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
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
// previous link() on main executable has already adjusted its internal pointers for ASLR
// work around that by rebasing by inverse amount
sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
//【第五步:link 主程序】链接主程序
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;
}
//【第六步:link 动态库】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 usesdylibs(例如libSystem)不会在程序使用的dylibs前面
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();
}
if ( gLinkContext.allowInterposing ) {
// 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);
}
}
}
//【第七步:弱符号绑定】
// apply interposing to initial set of images将插入应用于初始图像集
for(int i=0; i < sImageRoots.size(); ++i) {
sImageRoots[i]->applyInterposing(gLinkContext);
}
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, nullptr);
}
}
// <rdar://problem/12186933> do weak binding only after all inserted images linked只有在所有插入的图像链接后才进行弱绑定
//弱绑定
sMainExecutable->weakBind(gLinkContext);
gLinkContext.linkingMainExecutable = false;
sMainExecutable->recursiveMakeDataReadOnly(gLinkContext);
CRSetCrashLogMessage("dyld: launch, running initializers");
//【第八步:执行初始化方法】
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
notifyMonitoringDyldMain();
//【第九步:寻找主程序入口即main函数】
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();
if ( result != 0 ) {
//main可执行文件使用LC\u main,我们需要使用libdyld中的helper来调用main()
// 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 {
//主可执行文件使用LC\u UNIXTHREAD,dyld需要在为main()设置的程序中让“启动”
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getEntryFromLC_UNIXTHREAD();
*startGlue = 0;
}
}
return result;
}