本篇文章将探究app
运用程序是怎么加载的?
咱们平时都认为main
是程序的进口,可是实际上是这姿态的吗?不是的话,运用在冷发动后main
之前做了什么呢,咱们去探究下流程。
一、发动流程开端探究
新建iOS
工程,在ViewController.m
中添加一个load
办法并打断点,程序main.m
中添加一个C++
函数kcFunc
。
-
ViewControler
代码:
@interface ViewController ()
@end
@implementation ViewController
+ (void)load{
NSLog(@"%s", __func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"viewDidLoad --- ");
}
@end
-
main.m
代码:
// 在`main`函数之前,履行一些咱们的设置,比方状况打印
__attribute__((constructor)) void kcFunc(){
printf("C... %s \n",__func__);
}
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"main ...");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- llvm打印:
2023-02-02 14:21:51.278198+0800 DyldTest[17170:4322854] +[ViewController load]
C... kcFunc
2023-02-02 14:21:51.280613+0800 DyldTest[17170:4322854] main ...
2023-02-02 14:21:52.835821+0800 DyldTest[17170:4322854] viewDidLoad ---
- 定论:
-
__attribute__((constructor))
是在main
函数之前履行一个函数,便于咱们做一些准备工作。能够查阅了GNU的文档印证了我的主意。 -
运转程序,依据终端打印成果,可发现程序首要运转了
load
办法,然后调用了C++
函数,终究才进入到main
办法中。
在剖析发动流程之前,先学习一些概念。
二、运用程序编译进程
静态库
在链接阶段,会将汇编生成的方针程序与引证的库一同链接打包到可履行文件傍边。此刻的静态库就不会再改变了,由于它是编译时被直接复制一份,复制到方针程序里的。例如:.a
、.lib
- 长处:编译完结后,库文件实际上就没有作用了,方针程序没有外部依靠,直接就能够运转
- 缺陷:由于静态库或许会有两份,所以会导致方针程序的体积增大,对内存、功能、速度消耗很大
动态库
程序编译时并不会链接到方针程序中,方针程序只会存储指向动态库的引证,在程序运转时才被载入。例如:.so
、.framwork
、.dll
-
长处:削减打包之后
app
的巨细,共享内存,节省资源,更新动态库,到达更新程序 - 缺陷:动态载入会带来一部分功能损失,运用动态库也会使得程序依靠于外部环境,假如环境缺少了动态库,或许库的版别不正确,就会导致程序无法运转。
编译进程
程序的编译进程:预编译->编译->汇编->链接->可履行文件
-
源文件:载入
.h、.m、.cpp
等文件 -
预处理:替换宏,删去注释,翻开头文件,产生
.i
文件 -
编译:经过词法剖析、语法剖析、语义剖析后,将
.i
文件转换为汇编语言,产生.s
文件 -
汇编:将汇编文件转换为机器码文件,产生
.o
文件 -
链接:对
.o
文件中引证其他库的当地进行引证,生成Mach-O
格局的可履行文件 经过上述步骤,咱们开发的代码会生成一个Mach-O
格局的可履行文件。概况可阅读这儿。
动态链接器dyld
dyld(the dynamic link editor)
是苹果的动态链接器,是苹果操作体系的重要组成部分,在app
被编译打包成可履行文件格局的Mach-O
文件后,交由dyld
担任连接,并加载程序。
-
dyld
的作用:加载各个库,也便是image
镜像文件,由dyld
从内存中读到表中,加载主程序,link
链接各个动静态库,进行主程序初始化。
Mach-O
Mach-O
: Mach Object
文件格局的缩写,它是一种用于可履行文件,方针文件.o
,动态库,内核转储的文件格局。作为a.out
格局的代替,Mach-O
供给了更强的扩展性,并提升了符号表中信息的拜访速度。
Mach-O
格局的文件:
-
Executable
(产品为ipa包) -
Dynamic Library
(产品为动态库) -
Bundle
(产品为bundle文件) -
Static Library
(产品为静态库) -
Relocatable Object File
(重定向文件)
- 翻开
ipa
内容文件:
- 经过
MachOView
可查看可履行文件信息:
-
Mach-O
由四部分组成:Mach-O Header
、Load Commands
、Section
、Other Data
。
典型的Mach-O文件
包含三个区域:
-
Header
:保存Mach-O
的一些基本信息,包括渠道、文件类型、指令数、指令总巨细,dyld
符号Flags
等等。 -
Load Commands
:紧跟Header
,加载Mach-O文件
时会运用这部分数据确认内存散布,对体系内核加载器和动态连接器起指导作用。 -
Data
:每个segment
的具体数据保存在这儿,包含具体的代码、数据等等。概况可阅读这儿,了解这个部分是iOS
逆向工程的根底。
App发动的进程:
当咱们点击iPhone上的运用图标后,整个iOS
体系发动App阅历了:
- 体系调用
exec()
分配内存、开辟进程 -
app
对应的可履行文件
加载到内存 -
dyld
动态链接器加载到内存:load dyld
-
dyld
动态链接器进行动态链接:rebase
->bing
->Objc
->initalizers
- 调起
main()
函数
发动时间的划分能够把main()
函数作为要害点分割成两块
三、dyld
进口探究
在Viewcontroller
的load
办法添加断点,运转程序。在控制台输入指令bt
,查看运转的函数调用仓库信息。
- 图:
- llvm打印:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x00000001047cde38 DyldTest`+[ViewController load](self=ViewController, _cmd="load") at ViewController.m:17:5
frame #1: 0x00000001bed30a80 libobjc.A.dylib`load_images + 824
frame #2: 0x00000001e40d83fc dyld`dyld4::RuntimeState::notifyObjCInit(dyld4::Loader const*) + 164
frame #3: 0x00000001e40dbcc0 dyld`dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&) const + 204
frame #4: 0x00000001e40e133c dyld`dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const + 328
frame #5: 0x00000001e4115244 dyld`dyld4::APIs::runAllInitializersForMain() + 360
frame #6: 0x00000001e40ea66c dyld`dyld4::prepare(dyld4::APIs&, dyld3::MachOAnalyzer const*) + 3388
frame #7: 0x00000001e40e88d4 dyld`start + 2388
(lldb)
- 定论:
-
从左侧的仓库信息能够找到了程序的进口是
start
,这个部分是iOS16
后的发动情况。现在最新的iOS
体系基本上都是dyld4
流程,与网上大部分材料仍是iOS15
曾经版别的dyld3
流程是有所不同,可是这儿不进行详细翻开。 -
调用流程:
start
->dyld4::prepare
->dyld4::APIs::runAllInitializersForMain
->dyld4::Loader::runInitializersBottomUpPlusUpwardLinks
->dyld4::Loader::runInitializersBottomUp
->dyld4::RuntimeState::notifyObjCInit
->load_images
->[ViewController load]
。
四、探究dyld源码
截止现在(2023年2月)dyld
现已更新至dyld4(1042.1)了,iOS15
以上体系都是运用的dyld4
。
下载dyld
当时最新版别是dyld-1042.1 ,这部分源码是不能编译的,能够结合objc4
源码剖析。
- 翻开
dyld
源码,虽然控制台是从start
开端打印的,可是dyld4
跟dyld3
一样都是从__dyld_start
。先在工程里全局查找__dyld_start
,开端探究程序加载流程。终究在dyldStartup.s
文件中查找到了伪汇编流程。
-
见下图:
-
定论:
①. 此汇编代码仅仅对齐仓库并跳转到
C++
代码dyld:: start
- 针对不同的环境供给了不同的完结方式,比方,咱们现在看到的便是针对
arm64
真机环境的完结。看注释发现,不论是何种环境,终究都会走到start
流程中。这也和咱们前面bt
看到的函数调用仓库持续!下面全局查找start
。
- 见下图:
start
查看dyld-1042.1
工程里找到的start
函数。获取Mach-O
解析器,解析Mach-O
文件,还有获取一个随机数(ASLR
)。
namespace dyld4 {
...
...
// dyld的进口点。内核加载dyld并跳转到__dyld_start,它设置一些寄存器并调用这个函数。
// 留意:这个函数永久不会回来,它调用exit()。
// 因而,仓库维护程序是无用的,由于永久不会履行epilog。
// 符号函数no-return禁用仓库维护。
// 仓库维护器也会导致armv7k代码生成的问题,由于它经过prolog中的GOT槽拜访随机值,但dyld还没有rebased。
void start(const KernelArgs* kernArgs, void* prevDyldMH) __attribute__ ((noreturn)) __asm("start");
void start(const KernelArgs* kernArgs, void* prevDyldMH)
{
// 宣布kdebug盯梢点来指示dyld引导程序现已发动 <rdar://46878536>
// 留意:这是在dyld rebased之前调用的,所以kdebug_trace_dyld_marker()不能运用任何全局变量
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
// 走一切的fixups chains 和 rebase dyld
// 留意:withChainStarts()和fixupAllChainedFixups()不能运用任何静态数据指针,由于它们还没有rebased
const MachOAnalyzer* dyldMA = getDyldMH(); //获取Mach-O解析器,用于解析Mach-O格局文件内容
uintptr_t slide = dyldMA->getSlide();// 会生成一个随机数(ASLR)
if ( !dyldMA->inDyldCache() ) {
assert(dyldMA->hasChainedFixups());
__block Diagnostics diag;
dyldMA->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
dyldMA->fixupAllChainedFixups(diag, starts, slide, dyld3::Array<const void*>(), nullptr);
});
diag.assertNoError();
// make __DATA_CONST read-only (kernel maps it r/w)
dyldMA->forEachSegment(^(const MachOAnalyzer::SegmentInfo& segInfo, bool& stop) {
if ( segInfo.readOnlyData ) {
const uint8_t* start = (uint8_t*)(segInfo.vmAddr + slide);
size_t size = (size_t)segInfo.vmSize;
sSyscallDelegate.mprotect((void*)start, size, PROT_READ);
}
});
}
// 现在,咱们能够调用运用DATA的函数
mach_init();// mach音讯初始化
// 为stack canary设置随机值
__guard_setup(kernArgs->findApple()); //栈溢出维护
// 设置以便open_with_subsystem()工作
_subsystem_init(kernArgs->findApple());
// 在__DATA_CONST中将ProcessConfig目标变为只读之前,运用placement new来结构它
handleDyldInCache(dyldMA, kernArgs, (MachOFile*)prevDyldMH);
bool useHWTPro = false;
// 在自己的分配池中创立一个分配器Allocator
Allocator& allocator = Allocator::persistentAllocator(useHWTPro);
// 运用placement new在Allocator池中结构ProcessConfig目标
// 构建进程装备
ProcessConfig& config = *new (allocator.aligned_alloc(alignof(ProcessConfig), sizeof(ProcessConfig))) ProcessConfig(kernArgs, sSyscallDelegate, allocator);//构建进程装备
#if !SUPPPORT_PRE_LC_MAIN
// 仓库分配RuntimeLocks。它们不能在通常为只读的Allocator池中
RuntimeLocks sLocks;
#endif
// 在分配器中创立API(也称为RuntimeState)目标
APIs& state = *new (allocator.aligned_alloc(alignof(APIs), sizeof(APIs))) APIs(config, allocator, sLocks);
#if !TARGET_OS_SIMULATOR
// FIXME:咱们应该更早地移动它,但现在咱们需求在设置紧缩信息之前将运转时状况初始化,
// 直到咱们这样做之前,紧缩信息或许会错过某些前期的dyld崩溃
auto processSnapshot = state.getCurrentProcessSnapshot();
processSnapshot->setPlatform((uint64_t)state.config.process.platform);
processSnapshot->setDyldState(dyld_process_state_dyld_initialized);
FileRecord cacheFileRecord = state.fileManager.fileRecordForVolumeDevIDAndObjID(gProcessInfo->sharedCacheFSID, gProcessInfo->sharedCacheFSObjID);
if (cacheFileRecord.exists()) {
auto sharedCache = SharedCache(state.ephemeralAllocator, std::move(cacheFileRecord), processSnapshot->identityMapper(), (uint64_t)config.dyldCache.addr, gProcessInfo->processDetachedFromSharedRegion);
processSnapshot->addSharedCache(std::move(sharedCache));
}
// 将dyld添加到紧缩信息
if ( dyldMA->inDyldCache() && processSnapshot->sharedCache() ) { } else { }
// 将主可履行文件添加到紧缩信息
FileRecord mainExecutableFile;
if (state.config.process.mainExecutableFSID && state.config.process.mainExecutableObjID) { } else { }
auto mainExecutableImage = Image(state.ephemeralAllocator, std::move(mainExecutableFile), processSnapshot->identityMapper(), (const mach_header*)state.config.process.mainExecutable);
processSnapshot->addImage(std::move(mainExecutableImage));
processSnapshot->setInitialImageCount(state.initialImageCount());
state.commitProcessSnapshot();
#endif
// -------------要点进口-------------------
// 加载一切的库与程序
MainFunc appMain = prepare(state, dyldMA);
// 现在将一切dyld分配的数据结构设置为只读
state.decWritable();
// 调用main(),假如它回来,调用exit()并回来成果
// 留意:这是经过安排的,以便在程序的主线程中回溯时只在“main”下面显现“start”。
int result = appMain(state.config.process.argc, state.config.process.argv, state.config.process.envp, state.config.process.apple);
// 假如咱们到达这儿,main()回来(与程序调用exit()相反)
#if TARGET_OS_OSX
// <rdar://74518676>libSystemHelpers没有为模拟器设置,因而直接调用_exit()
if ( MachOFile::isSimulatorPlatform(state.config.process.platform) )
_exit(result);
#endif
state.libSystemHelpers->exit(result);
}
...
...
}// namespace
-
定论:
①.
dyld4
跟dyld3
不同便是,main
没有了回来值,是经过exit
结束!无法经过epilog
退出代码,它释放仓库空间并回来到调用者。②.
start
中读取mach-o
文件中的符号地址都是虚拟地址,在程序发动的时候,体系会生成一个随机数(ASLR
),运用虚拟地址加上ASLR
才是物理地址,也便是程序真正调用的地址。咱们把从虚拟地址换算成物理地址的进程称之为rebase
。
prepare
//
// 加载任何相关的dylib并将它们绑定在一同。
// 回来main()在target中的地址。
//
__attribute__ ((noinline)) static MainFunc prepare(APIs& state, const MachOAnalyzer* dyldMH)
{
// `gProcessInfo`是存储dyld一切镜像信息的结构体:,保存着mach_header,dyld_uuid_info,dyldVersion等等信息。
// 装备dyld信息中中止标识符
gProcessInfo->terminationFlags = 0; // 默许情况下,在崩溃日志中显现回溯
// 装备dyld信息中渠道信息
gProcessInfo->platform = (uint32_t)state.config.process.platform;
// 装备dyld信息中途径信息
gProcessInfo->dyldPath = state.config.process.dyldPath;
uint64_t launchTraceID = 0;
if ( dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE) ) {
launchTraceID = dyld3::kdebug_trace_dyld_duration_start(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, (uint64_t)state.config.process.mainExecutable, 0, 0);
}
#if TARGET_OS_OSX //模拟环境,涉及Simulator和Sim(略过)
const bool isSimulatorProgram = MachOFile::isSimulatorPlatform(state.config.process.platform);
if ( const char* simPrefixPath = state.config.pathOverrides.simRootPath() ) {
#if __arm64e__
if ( strcmp(state.config.process.mainExecutable->archName(), "arm64e") == 0 )
halt("arm64e not supported for simulator programs");
#endif
if ( isSimulatorProgram ) {
char simDyldPath[PATH_MAX];
strlcpy(simDyldPath, simPrefixPath, PATH_MAX);
strlcat(simDyldPath, "/usr/lib/dyld_sim", PATH_MAX);
return prepareSim(state, simDyldPath);
}
halt("DYLD_ROOT_PATH only allowed with simulator programs");
}
else if ( isSimulatorProgram ) {
halt("DYLD_ROOT_PATH not set for simulator program");
}
#endif
#if 0
// 查看主可履行文件是否有效
Diagnostics diag;
bool validMainExec = state.config.process.mainExecutable->isValidMainExecutable(diag, state.config.process.mainExecutablePath, -1, *(state.config.process.archs), state.config.process.platform);
if ( !validMainExec && state.config.process.mainExecutable->enforceFormat(dyld3::MachOAnalyzer::Malformed::sdkOnOrAfter2021)) {
state.log("%s in %s", diag.errorMessage(), state.config.process.mainExecutablePath);
halt(diag.errorMessage());
}
#endif
// 假如问询,log打印环境env变量
if ( state.config.log.env ) {
for (const char* const* p=state.config.process.envp; *p != nullptr; ++p) {
state.log("%s\n", *p);
}
}
// 查看预构建pre-built Loader
state.initializeClosureMode();
const PrebuiltLoaderSet* mainSet = state.processPrebuiltLoaderSet();
Loader* mainLoader = nullptr;
if ( mainSet != nullptr ) {
mainLoader = (Loader*)mainSet->atIndex(0);
state.loaded.reserve(state.initialImageCount()); // 协助避免Vector的从头定位
}
if ( mainLoader == nullptr ) {
// 假如没有预构建的Loader,制作一个just-in-time
// just-in-time是dyld4的新特性:`pre-build + just-in-time`预构建+实时解析的双解析形式
state.loaded.reserve(512); // 猜测Vector巨细的起点
Diagnostics buildDiag;
mainLoader = JustInTimeLoader::makeLaunchLoader(buildDiag, state, state.config.process.mainExecutable,
state.config.process.mainExecutablePath, nullptr);
if ( buildDiag.hasError() ) {
state.log("%s in %s\n", buildDiag.errorMessage(), state.config.process.mainExecutablePath);
halt(buildDiag.errorMessage());
}
}
state.setMainLoader(mainLoader);
// 首要将主可履行文件添加到mainLoader预构建的镜像列表中
state.notifyDebuggerLoad(mainLoader);
const bool needToWritePrebuiltLoaderSet = !mainLoader->isPrebuilt && (state.saveAppClosureFile() || state.failIfCouldBuildAppClosureFile());
// <rdar://problem/10583252> Add dyld to uuidArray to enable symbolication of stackshots (unless dyld is in the cache)
// 装载uuid相关信息
if ( !dyldMH->inDyldCache() ) {
dyld_uuid_info dyldInfo;
dyldInfo.imageLoadAddress = dyldMH;
dyldMH->getUuid(dyldInfo.imageUUID);
addNonSharedCacheImageUUID(state.persistentAllocator, dyldInfo);
}
// 加载所需求刺进的动态库(dylibs)
STACK_ALLOC_OVERFLOW_SAFE_ARRAY(Loader*, topLevelLoaders, 16);
topLevelLoaders.push_back(mainLoader);
Loader::LoadChain loadChainMain { nullptr, mainLoader };
Loader::LoadOptions options;
options.staticLinkage = true;
options.launching = true;
options.insertedDylib = true;
options.canBeDylib = true;
options.rpathStack = &loadChainMain;
state.config.pathOverrides.forEachInsertedDylib(^(const char* dylibPath, bool& stop) {
Diagnostics insertDiag;
if ( Loader* insertedDylib = (Loader*)Loader::getLoader(insertDiag, state, dylibPath, options) ) {
topLevelLoaders.push_back(insertedDylib);
state.notifyDebuggerLoad(insertedDylib);
if ( insertedDylib->isPrebuilt )
state.loaded.push_back(insertedDylib);
}
else if ( insertDiag.hasError() && !state.config.security.allowInsertFailures ) {
state.log("terminating because inserted dylib '%s' could not be loaded: %s\n", dylibPath, insertDiag.errorMessageCStr());
halt(insertDiag.errorMessage());
}
});
// 将刺进的库移到处于状况state.loaded中的主可履行文件之前,以获得正确的平面命名空间查找
if ( topLevelLoaders.count() != 1 ) {
state.loaded.erase(state.loaded.begin());
state.loaded.push_back(mainLoader);
}
// 用于录制一定丢掉的文件
__block MissingPaths missingPaths;
auto missingLogger = ^(const char* mustBeMissingPath) {
missingPaths.addPath(mustBeMissingPath);
};
// 递归加载 主可履行文件 和 刺进的dylib所需的一切内容
// 记载刺进信息, 遍历一切dylibs, 一些记载查看操作持续往下走
Diagnostics depsDiag;
options.insertedDylib = false;
if ( needToWritePrebuiltLoaderSet )
options.pathNotFoundHandler = missingLogger;
for ( Loader* ldr : topLevelLoaders ) {
ldr->loadDependents(depsDiag, state, options);
if ( depsDiag.hasError() ) {
//state.log("%s loading dependents of %s\n", depsDiag.errorMessage(), ldr->path());
// 让debugger/crashreporter了解咱们能够加载的dylib
uintptr_t topCount = topLevelLoaders.count();
STACK_ALLOC_VECTOR(const Loader*, newLoaders, state.loaded.size() - topCount);
for (size_t i = topCount; i != state.loaded.size(); ++i)
newLoaders.push_back(state.loaded[i]);
state.notifyDebuggerLoad(newLoaders);
gProcessInfo->terminationFlags = 1; // 不要显现痕迹,由于没有什么风趣的
halt(depsDiag.errorMessage());
}
}
uintptr_t topCount = topLevelLoaders.count();
{
STACK_ALLOC_VECTOR(const Loader*, newLoaders, state.loaded.size());
for (const Loader* ldr : state.loaded)
newLoaders.push_back(ldr);
// 在主可履行文件之后告诉调试器debuger一切加载的映像
std::span<const Loader*> unnotifiedNewLoaders(&newLoaders[topCount], newLoaders.size() - topCount);
state.notifyDebuggerLoad(unnotifiedNewLoaders);
// 告诉kernel内核任何dtrace静态用户勘探
state.notifyDtrace(newLoaders);
}
// <rdar://problem/7739489> 记载images的初始计数
// 因而CrashReporter能够记载哪些images是动态加载的
gProcessInfo->initialImageCount = state.loaded.size();
// 添加到永久范围
STACK_ALLOC_ARRAY(const Loader*, nonCacheNeverUnloadLoaders, state.loaded.size());
for (const Loader* ldr : state.loaded) {
if ( !ldr->dylibInDyldCache )
nonCacheNeverUnloadLoaders.push_back(ldr);
}
state.addPermanentRanges(nonCacheNeverUnloadLoaders);
// proactive weakDefMap means we build the weakDefMap before doing any binding
// 主动weakDefMap意味着咱们在进行任何绑定之前构建weakDefMap
if ( state.config.process.proactivelyUseWeakDefMap ) {
state.weakDefMap = new (state.persistentAllocator.malloc(sizeof(WeakDefMap))) WeakDefMap();
STACK_ALLOC_VECTOR(const Loader*, allLoaders, state.loaded.size());
for (const Loader* ldr : state.loaded)
allLoaders.push_back(ldr);
Loader::addWeakDefsToMap(state, allLoaders);
}
// 在履行修正之前查看是否刺进元组
state.buildInterposingTables();
// do fixups做修补工作,这儿也是去处理了`rebase`
{
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_APPLY_FIXUPS, 0, 0, 0);
// 以防万一咱们需求修补这个案件
DyldCacheDataConstLazyScopedWriter cacheDataConst(state);
// C++标准表示,首要可履行文件能够界说非弱函数,这些函数掩盖dylib中的weak-defs
// 关于在发动时绑定的任何目标,这都会主动产生,但dyld缓存是预绑定pre-bound的,因而咱们需求修补主可履行文件中被该非弱目标掩盖的任何绑定。
// 留意,在macOS上,咱们还答应dylib具有weak-defs的non-weak掩盖
if ( !mainLoader->isPrebuilt )
JustInTimeLoader::handleStrongWeakDefOverrides(state, cacheDataConst);
for ( const Loader* ldr : state.loaded ) {
Diagnostics fixupDiag;
ldr->applyFixups(fixupDiag, state, cacheDataConst, true);
if ( fixupDiag.hasError() ) {
halt(fixupDiag.errorMessage());
}
// 根目录需求修补缓存中未找到的GOT
ldr->applyCachePatches(state, cacheDataConst);
}
// 假如咱们有单例修补
state.doSingletonPatching();
}
// 假如存在刺进,则将刺进元组运用于dyld缓存
if ( !state.interposingTuplesAll.empty() ) {
Loader::applyInterposingToDyldCache(state);
}
// 假如mainLoader是pre-build,则或许会掩盖dyld缓存中的weak-defs
if ( mainLoader->isPrebuilt ) {
DyldCacheDataConstLazyScopedWriter dataConstWriter(state);
DyldCacheDataConstLazyScopedWriter* dataConstWriterPtr = &dataConstWriter; // 在cacheWeakDefFixup中进行变通以使其可拜访
state.processPrebuiltLoaderSet()->forEachCachePatch(^(const PrebuiltLoaderSet::CachePatch& patch) {
uintptr_t newImpl = (uintptr_t)patch.patchTo.value(state);
state.config.dyldCache.addr->forEachPatchableUseOfExport(patch.cacheDylibIndex, patch.cacheDylibVMOffset,
^(uint64_t cacheVMOffset,
dyld3::MachOLoaded::PointerMetaData pmd, uint64_t addend) {
uintptr_t* loc = (uintptr_t*)(((uint8_t*)state.config.dyldCache.addr) + cacheVMOffset);
uintptr_t newValue = newImpl + (uintptr_t)addend;
#if __has_feature(ptrauth_calls)
if ( pmd.authenticated )
newValue = MachOLoaded::ChainedFixupPointerOnDisk::Arm64e::signPointer(newValue, loc, pmd.usesAddrDiversity, pmd.diversity, pmd.key);
#endif
// 忽略重复的修补程序条目
if ( *loc != newValue ) {
dataConstWriterPtr->makeWriteable();
if ( state.config.log.fixups )
state.log("cache patch: %p = 0x%0lX\n", loc, newValue);
*loc = newValue;
}
});
});
}
// call kdebug trace for each image
if ( kdebug_is_enabled(KDBG_CODE(DBG_DYLD, DBG_DYLD_UUID, DBG_DYLD_UUID_MAP_A)) ) {
// dyld in the cache event was sent earlier when we unmapped the on-disk dyld
// 缓存事件中的dyld是在咱们取消映射磁盘上的dyld时发送的
if ( !dyldMH->inDyldCache() ) {
// 为dyld本身添加盯梢
uuid_t dyldUuid;
dyldMH->getUuid(dyldUuid);
struct stat stat_buf;
fsid_t dyldFsid = { { 0, 0 } };
fsobj_id_t dyldFfsobjid = { 0, 0 };
if ( dyld3::stat(state.config.process.dyldPath, &stat_buf) == 0 ) {
dyldFfsobjid = *(fsobj_id_t*)&stat_buf.st_ino;
dyldFsid = { { stat_buf.st_dev, 0 } };
}
kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, state.config.process.dyldPath, &dyldUuid, dyldFfsobjid, dyldFsid, dyldMH);
}
// 为每个image的加载进程添加盯梢
for ( const Loader* ldr : state.loaded ) {
const MachOLoaded* ml = ldr->loadAddress(state);
fsid_t fsid = { { 0, 0 } };
fsobj_id_t fsobjid = { 0, 0 };
struct stat stat_buf;
if ( !ldr->dylibInDyldCache && (dyld3::stat(ldr->path(), &stat_buf) == 0) ) { //FIXME Loader knows inode
fsobjid = *(fsobj_id_t*)&stat_buf.st_ino;
fsid = { { stat_buf.st_dev, 0 } };
}
uuid_t uuid;
ml->getUuid(uuid);
kdebug_trace_dyld_image(DBG_DYLD_UUID_MAP_A, ldr->path(), &uuid, fsobjid, fsid, ml);
}
}
// 告诉任何其他查看此进程的进程
// 告诉盯梢此进程中负载的任何进程
STACK_ALLOC_ARRAY(const char*, pathsBuffer, state.loaded.size());
STACK_ALLOC_ARRAY(const mach_header*, mhBuffer, state.loaded.size());
for ( const Loader* ldr : state.loaded ) {
pathsBuffer.push_back(ldr->path());
mhBuffer.push_back(ldr->loadAddress(state));
}
notifyMonitoringDyld(false, (unsigned int)state.loaded.size(), &mhBuffer[0], &pathsBuffer[0]);
// 将libdyld.dylib连接到dyld
LibdyldDyld4Section* libdyld4Section = nullptr;
if ( state.libdyldLoader != nullptr ) {
const MachOLoaded* libdyldML = state.libdyldLoader->loadAddress(state);
uint64_t sectSize;
libdyld4Section = (LibdyldDyld4Section*)libdyldML->findSectionContent("__DATA", "__dyld4", sectSize, true);
#if __has_feature(ptrauth_calls)
if ( libdyld4Section == nullptr )
libdyld4Section = (LibdyldDyld4Section*)libdyldML->findSectionContent("__AUTH", "__dyld4", sectSize, true);
#endif
if ( libdyld4Section != nullptr ) {
// 设置指向全局API目标的指针
libdyld4Section->apis = &state;
// 将指针设置为dyld_all_image_infosos
libdyld4Section->allImageInfos = gProcessInfo;
// 程序变量(例如environ)通常在libdyld.dylib中界说(但或许在旧macOS二进制文件的主可替换文件中界说)
// 记住progams变量的方位,以便libc能够同步它们
state.vars = &libdyld4Section->defaultVars;
state.vars->mh = state.config.process.mainExecutable;
*state.vars->NXArgcPtr = state.config.process.argc;
*state.vars->NXArgvPtr = (const char**)state.config.process.argv;
*state.vars->environPtr = (const char**)state.config.process.envp;
*state.vars->__prognamePtr = state.config.process.progname;
}
else {
halt("compatible libdyld.dylib not found");
}
}
else {
halt("libdyld.dylib not found");
}
if ( state.libSystemLoader == nullptr)
halt("program does not link with libSystem.B.dylib");
#if !TARGET_OS_SIMULATOR
// 假如运用JustInTimeLoader发动,或许需求将其序列化
if ( needToWritePrebuiltLoaderSet ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_BUILD_CLOSURE, 0, 0, 0);
if ( state.config.log.loaders )
state.log("building PrebuiltLoaderSet for main executable\n");
Diagnostics prebuiltDiag;
const PrebuiltLoaderSet* prebuiltAppSet = PrebuiltLoaderSet::makeLaunchSet(prebuiltDiag, state, missingPaths);
if ( (prebuiltAppSet != nullptr) && prebuiltDiag.noError() ) {
if ( state.failIfCouldBuildAppClosureFile() )
halt("dyld: PrebuiltLoaderSet expected but not found");
// 将PrebuiltLoaderSet保存到磁盘以便下次发动时运用,持续运用JustInTimeLoaders运转
if ( state.saveAppPrebuiltLoaderSet(prebuiltAppSet) )
state.setSavedPrebuiltLoaderSet();
prebuiltAppSet->deallocate();
timer.setData4(dyld3::DyldTimingBuildClosure::LaunchClosure_Built);
}
else if ( state.config.log.loaders ) {
state.log("could not build PrebuiltLoaderSet: %s\n", prebuiltDiag.errorMessage());
}
}
// 假如运用程序发动pre-warm,请提前退出
if ( state.config.process.environ("DYLD_JUST_BUILD_CLOSURE") != nullptr ) {
return &fake_main;
}
#endif
#if SUPPPORT_PRE_LC_MAIN
uint32_t progVarsOffset;
dyld3::DyldLookFunc* dyldLookupFuncAddr = nullptr;
bool crtRunsInitializers = false;
if ( state.config.process.mainExecutable->hasProgramVars(progVarsOffset, crtRunsInitializers, dyldLookupFuncAddr) ) {
// 这是一个旧的macOS运用程序,它有自己的NXArgv等全局变量。咱们需求运用它们。
ProgramVars* varsInApp = (ProgramVars*)(((uint8_t*)state.config.process.mainExecutable) + progVarsOffset);
varsInApp->mh = state.config.process.mainExecutable;
*varsInApp->NXArgcPtr = state.config.process.argc;
*varsInApp->NXArgvPtr = (const char**)state.config.process.argv;
*varsInApp->environPtr = (const char**)state.config.process.envp;
*varsInApp->__prognamePtr = state.config.process.progname;
state.vars = varsInApp;
}
if ( dyldLookupFuncAddr ) {
if ( libdyld4Section != nullptr ) {
*dyldLookupFuncAddr = (dyld3::DyldLookFunc)libdyld4Section->dyldLookupFuncAddr;
} else {
halt("compatible libdyld.dylib not found");
}
}
if ( !crtRunsInitializers )
state.runAllInitializersForMain();
#else
// 运转一切的初始化
dyld4::notifyMonitoringDyldBeforeInitializers();
state.runAllInitializersForMain();
...
...
return result;
}
-
定论:
①. 回来
main
函数地址,一切装备完后_dyld_register_driverkit_main
便是咱们自界说工程的main
地址。②.
gProcessInfo
是存储dyld
一切images
镜像信息的结构体,保存着mach_header
、dyld_uuid_info
、dyldVersion
等等信息。③. 创立
just-in-time
,是dyld4
一个新特性。dyld4
在保留了dyld3
的mach-o
解析器根底上,同时也引入了just-in-time
的加载器来优化。-
dyld3
出于对发动速度的优化的目的, 增加了预构建(闭包)。App
第一次发动或许App产生改变时会将部分发动数据创立为闭包存到本地,那么App下次发动将不再从头解析数据,而是直接读取闭包内容。当然前提是运用程序和体系应很少产生改变,但假如这两者常常改变等, 就会导闭包丢掉或失效。 -
dyld4
采用了pre-build
+just-in-time
的双解析形式,预构建pre-build
对应的便是dyld3
中的闭包,just-in-time
能够理解为实时解析。当然just-in-time
也是能够利用pre-build
的缓存的,所以功能可控。有了just-in-time
, 现在运用首次发动、体系版别更新、一般发动,dyld4
则能够依据缓存是否有效去选择合适的形式进行解析。
-
runAllInitializersForMain
// 这是dyldMain.cpp中提取出来的一部分,以支持运用crt1.o的旧macOS运用程序
void APIs::runAllInitializersForMain()
{
// 禁用page-in链接,不用于dlopen()加载images
if ( !config.security.internalInstall || (config.process.pageInLinkingMode != 3) )
config.syscall.disablePageInLinking();
// 首要答应libSystem的初始化器
const_cast<Loader*>(this->libSystemLoader)->beginInitializers(*this);
this->libSystemLoader->runInitializers(*this);
gProcessInfo->libSystemInitialized = true;
// 运转libSystem的初始化程序后,告诉objc在libSystem子dylib上运转任何+load办法
this->notifyObjCInit(this->libSystemLoader);
// <rdar://problem/32209809> 调用一切现已images的'init' 函数 (below libSystem)
// 运用下标索引进行迭代,以便在+load dloopen时数组不会在咱们下面增加
for ( uint32_t i = 0; i != this->loaded.size(); ++i ) {
const Loader* ldr = this->loaded[i];
if ( (ldr->dylibInDyldCache || ldr->analyzer(*this)->isDylib()) && (strncmp(ldr->analyzer(*this)->installName(), "/usr/lib/system/lib", 19) == 0) ) {
// 查看装置名称而不是途径,以处理libsystem子dylibs的DYLD_LIBRARY_PATH掩盖
const_cast<Loader*>(ldr)->beginInitializers(*this);
this->notifyObjCInit(ldr);
const_cast<Loader*>(ldr)->runInitializers(*this);
}
}
#if TARGET_OS_OSX
// If we're PID 1, scan for roots
if ( (this->config.process.pid == 1) && (this->libSystemHelpers->version() >= 5) ) {
this->libSystemHelpers->run_async(&ProcessConfig::scanForRoots, (void*)&this->config);
}
#endif // TARGET_OS_OSX
//自下而上运转一切其他初始化器,首要运转刺进的dylib初始化器
//运用下标索引进行迭代,以便假如初始dloopen时数组不会向下增加
for ( uint32_t i = 0; i != this->loaded.size(); ++i ) {
const Loader* ldr = this->loaded[i];
ldr->runInitializersBottomUpPlusUpwardLinks(*this);
// stop as soon as we did main executable
// normally this is first image, but if there are inserted N dylibs, it is Nth in the list
if ( ldr->analyzer(*this)->isMainExecutable() )
break;
}
}
-
定论:
①. 在libSystem的初始化时,用
notifyObjCInit
告诉libSystem
依靠的子dylibs
运转一切的+load
办法,这儿不是咱们自界说工程里的+load
②. 处理完
libSystem
的初始化后,再加载后自下而上运转一切其他镜像的初始化器。
runInitializersBottomUpPlusUpwardLinks
void Loader::runInitializersBottomUpPlusUpwardLinks(RuntimeState& state) const
{
//state.log("runInitializersBottomUpPlusUpwardLinks() %s\n", this->path());
state.incWritable();
// 递归运转一切初始化程序 initializers
STACK_ALLOC_ARRAY(const Loader*, danglingUpwards, state.loaded.size());
this->runInitializersBottomUp(state, danglingUpwards);
//state.log("runInitializersBottomUpPlusUpwardLinks(%s), found %d dangling upwards\n", this->path(), danglingUpwards.count());
// 回来到一切向上链接的images,并从头查看它们是否已初始化(或许是挂起的)
STACK_ALLOC_ARRAY(const Loader*, extraDanglingUpwards, state.loaded.size());
for ( const Loader* ldr : danglingUpwards ) {
//state.log("running initializers for dangling upward link %s\n", ldr->path());
ldr->runInitializersBottomUp(state, extraDanglingUpwards);
}
if ( !extraDanglingUpwards.empty() ) {
// 假如有两个向上悬挂的images,请再次查看初始化器
danglingUpwards.resize(0);
for ( const Loader* ldr : extraDanglingUpwards ) {
//state.log("running initializers for dangling upward link %s\n", ldr->path());
ldr->runInitializersBottomUp(state, danglingUpwards);
}
}
state.decWritable();
}
-
定论:
①.递归运转一切初始化程序
initializers
runInitializersBottomUp
void Loader::runInitializersBottomUp(RuntimeState& state, Array<const Loader*>& danglingUpwards) const
{
// 假如已运转初始化程序,则不履行任何操作
if ( (const_cast<Loader*>(this))->beginInitializers(state) )
return;
//state.log("runInitializersBottomUp(%s)\n", this->path());
// 在运转初始化程序之前,请保证此image下面的一切内容都已初始化
const uint32_t depCount = this->dependentCount();
for ( uint32_t i = 0; i < depCount; ++i ) {
DependentKind childKind;
if ( Loader* child = this->dependent(state, i, &childKind) ) {
if ( childKind == DependentKind::upward ) {
// 向上添加到列表以稍后处理
if ( !danglingUpwards.contains(child) )
danglingUpwards.push_back(child);
}
else {
child->runInitializersBottomUp(state, danglingUpwards);
}
}
}
// 告诉objc在此image映像中运转任何+load办法(在C++初始化器之前完结)
state.notifyObjCInit(this);
// 为此image映像运转初始化程序
this->runInitializers(state);
}
-
定论:
①. 终究经过
notifyObjCInit
告诉objc
在此image
映像中运转任何+load
办法(在C++
初始化器之前完结)。
notifyObjCInit
void RuntimeState::notifyObjCInit(const Loader* ldr)
{
//this->log("objc-init-notifier checking mh=%p, path=%s, +load=%d, objcInit=%p\n", ldr->loadAddress(), ldr->path(), ldr->mayHavePlusLoad, _notifyObjCInit);
if ( (_notifyObjCInit != nullptr) && ldr->mayHavePlusLoad ) {
const MachOLoaded* ml = ldr->loadAddress(*this);
const char* pth = ldr->path();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)ml, 0, 0);
if ( this->config.log.notifications )
this->log("objc-init-notifier called with mh=%p, path=%s\n", ml, pth);
_notifyObjCInit(pth, ml);
}
}
-
定论:
②. 首要是遍历各个库文件,经过
_notifyObjCInit
来加载+load
办法。
探究_notifyObjCInit
来历
-
_notifyObjCInit
的界说是在RuntimeState
类的声明里:
-
_dyld_objc_notify_init
在setObjCNotifiers
被赋值的:
#if BUILDING_DYLD || BUILDING_UNIT_TESTS
void RuntimeState::setObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped, _dyld_objc_notify_patch_class patchClass,
_dyld_objc_notify_mapped2 mapped2)
{
_notifyObjCMapped = mapped;
_notifyObjCInit = init;
_notifyObjCUnmapped = unmapped;
_notifyObjCPatchClass = patchClass;
_notifyObjCMapped2 = mapped2;
withLoadersReadLock(^{
if ( (_notifyObjCPatchClass != nullptr) && !this->objcReplacementClasses.empty() ) {
// 告诉Symbolication象征咱们正在修补类
this->setDyldPatchedObjCClasses();
for ( const ObjCClassReplacement& classReplacement : this->objcReplacementClasses )
(*_notifyObjCPatchClass)(classReplacement.cacheMH, (void*)classReplacement.cacheImpl,
classReplacement.rootMH, (const void*)classReplacement.rootImpl);
if ( this->config.log.notifications ) {
this->log("objc-patch-class-notifier called with %ld patches:\n", this->objcReplacementClasses.size());
}
// 清除替换类。假如产生dlopen,咱们不想再次告诉它们
this->objcReplacementClasses.clear();
}
// 回调关于已加载图像images
size_t maxCount = this->loaded.size();
STACK_ALLOC_ARRAY(const mach_header*, mhs, maxCount);
STACK_ALLOC_ARRAY(const char*, paths, maxCount);
STACK_ALLOC_ARRAY(_dyld_objc_notify_mapped_info, infos, maxCount);
for ( const Loader* ldr : loaded ) {
// 这儿不需求_mutex互斥锁,由于这是在进程仍然是单线程时调用的
const MachOLoaded* ml = ldr->loadAddress(*this);
if ( ldr->hasObjC ) {
paths.push_back(ldr->path());
mhs.push_back(ml);
infos.push_back({ml, ldr->path(), ldr->dyldDoesObjCFixups(), 0});
// 在map_images运转时内存read-write可读写
if ( ldr->hasConstantSegmentsToProtect() && ldr->hasReadOnlyObjC )
ldr->makeSegmentsReadWrite(*this);
}
}
if ( !mhs.empty() ) {
if ( _notifyObjCMapped != nullptr )
// 经过指针_notifyObjCMapped指向运转map_images
(*_notifyObjCMapped)((uint32_t)mhs.count(), &paths[0], &mhs[0]);
if ( _notifyObjCMapped2 != nullptr )
(*_notifyObjCMapped2)((uint32_t)mhs.count(), &infos[0]);
if ( this->config.log.notifications ) {
this->log("objc-mapped-notifier called with %ld images:\n", mhs.count());
for ( uintptr_t i = 0; i < mhs.count(); ++i ) {
this->log(" objc-mapped: %p %s\n", mhs[i], paths[i]);
}
}
// 运转map_images后使内存为只读read-only
for ( const Loader* ldr : loaded ) {
if ( ldr->hasObjC && ldr->hasConstantSegmentsToProtect() && ldr->hasReadOnlyObjC )
ldr->makeSegmentsReadOnly(*this);
}
}
});
}
- 那
setObjCNotifiers
是在_dyld_objc_notify_register
被调用的:
// FIXME: 一旦libobjc移动到_dyld_objc_register_callbacks(),请删去此项
// 这个是从前的注册办法
void APIs::_dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
if ( config.log.apis )
log("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped);
setObjCNotifiers(mapped, init, unmapped, nullptr, nullptr);
// If we have prebuilt loaders, then the objc optimisations may hide duplicate classes from libobjc.
// We need to print the same warnings libobjc would have.
if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() )
mainSet->logDuplicateObjCClasses(*this);
}
// 最新的代码objc4的注册办法
void APIs::_dyld_objc_register_callbacks(const _dyld_objc_callbacks* callbacks)
{
if ( config.log.apis ) {
void** p = (void**)callbacks;
log("_dyld_objc_register_callbacks(%lu, %p, %p, %p, %p)\n", callbacks->version, p[1], p[2], p[31], p[4]);
}
if ( callbacks->version == 1 ) {
const _dyld_objc_callbacks_v1* v1 = (const _dyld_objc_callbacks_v1*)callbacks;
setObjCNotifiers(v1->mapped, v1->init, v1->unmapped, v1->patches, nullptr);
}
else if ( callbacks->version == 2 ) {
const _dyld_objc_callbacks_v2* v2 = (const _dyld_objc_callbacks_v2*)callbacks;
setObjCNotifiers(nullptr, v2->init, v2->unmapped, v2->patches, v2->mapped);
}
else {
}
// 假如咱们有预构建的加载器,那么objc优化或许会从libobjc中躲藏重复的类。
// 咱们需求打印libobjc将具有的相同警告。
if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() )
mainSet->logDuplicateObjCClasses(*this);
}
-
定论:
①. 从前
dyld3
的注册办法_dyld_objc_notify_register
,最新dyld4
是_dyld_objc_register_callbacks
。
- 而
_dyld_objc_register_callbacks
是在objc4-866.9源码里的objc_init()
里被调用的
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**************************************************************************/
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();
static_init();
runtime_init();
exception_init();
cache_t::init();
_imp_implementationWithBlock_init();
_dyld_objc_callbacks_v1 callbacks = {
1, // version
&map_images,
load_images,
unmap_image,
_objc_patch_root_of_class
};
_dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);
didCallDyldNotifyRegister = true;
}
-
定论:
①. 这儿解释了从前dyld进口探究,调用完
notifyObjCInit
后便是load_images
。②.
map_images
映射镜像、load_images
加载镜像、unmap_image
免除镜像是objc4
源码初始化装备的要点,为了进一步了解dyld4
关于images
镜像们的办理,我要先去了解objc4
的objc_init。
五、总结
-
剖析了
map_images
与load_images
的注册和load_images
的调用流程,可是未具体剖析objc4
的_objc_init
、map_images
、load_images
的源码与流程。 -
只剖析了
dyld4
的部分流程,而未剖析dyld3
的流程。下面是两者对比:
-
dyld3
: -
dyld4
: