-
概念
Mach-O
是一种文件格式,是mac
上可执行文件的格式。编写的C
、C++
、swift
、OC
,最终编译链接生成Mach-O
可执行文件。链接的共用库分为静态库和动态库:静态库是编译时链接的库,需要链接进你的
Mach-O
文件里,如果需要更新就要重新编译一次,无法动态加载和更新;而动态库是运行时链接的库,使用dyld
就可以实现动态加载。
dyld
(the dynamic link editor)是苹果的动态链接器,是苹果操作系统的一个重要组成部分,在应用被编译打包成可执行文件格式的Mach-O
文件之后,交由dyld
负责链接,加载程序 。
-
加载流程
为了辅助探索底层,我们可以用符号断点看一下堆栈信息:
- 首先调用的是
_dyld_start
,我们马上进入dyld源码开始探究:
- 然后从汇编跳转调用
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)
{
slide = slideOfMainExecutable(dyldsMachHeader);//通过一个随机值来实现地址空间配置随机加载, 防止被攻击
bool shouldRebase = slide != 0;
...
// allow dyld to use mach messaging
mach_init();//开放函数消息使用
...
// set up random value for stack canary
__guard_setup(apple);//设置堆栈保护
...
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = slideOfMainExecutable(appsMachHeader);
return dyld::_main(appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);//开始链接共享对象
}
- 接着进入
_main
,代码很多所以只看流程代码:
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
...
{
checkEnvironmentVariables(envp);//检查环境变量
defaultUninitializedFallbackPaths(envp);
}
...
// load shared cache
checkSharedRegionDisable((dyld3::MachOLoaded*)mainExecutableMH, mainExecutableSlide);//验证共享缓存路径
...
if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) {
mapSharedCache();//加载共享缓存
}
...
try {
// add dyld itself to UUID list
addDyldImageToUUIDList();//添加dyld到UUID列表
...
reloadAllImages:
...
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);//初始化主程序;内核会映射到可执行文件中
...
// load any inserted libraries
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();
}
...
}
...
ImageLoader::applyInterposingToDyldCache(gLinkContext);//插入任何动态加载的镜像文件
...
// run all initializers
initializeMainExecutable(); //运行所有初始化程序
...
notifyMonitoringDyldMain();//通知监听dyld的main
// find entry point for main executable
result = (uintptr_t)sMainExecutable->getEntryFromLC_MAIN();//找到真正 main 函数入口并返回
...
}
...
}
- 流程:配置环境变量 -> 加载共享缓存 -> 实例化主程序 -> 加载动态库 -> 链接动态库 -> 运行所有初始化程序 -> 通知监听
dyld
的main
-> 找到真正main
函数入口
- 然后就开始初始化:
static ImageLoaderMachO* instantiateFromLoadedImage(const macho_header* mh, uintptr_t slide, const char* path)
{
// try mach-o loader
if ( isCompatibleMachO((const uint8_t*)mh, path) ) {
ImageLoader* image = ImageLoaderMachO::instantiateMainExecutable(mh, slide, path, gLinkContext);//通过instantiateMainExecutable中的sniffLoadCommands加载主程序其实就是对MachO文件中LoadCommons段的一系列加载
addImage(image);//生成镜像文件后,添加到sAllImages全局镜像中,主程序永远是sAllImages的第一个对象
return (ImageLoaderMachO*)image;
}
throw "main executable not a known format";
}
- 接着下一步就是加载插入的动态库:
static void loadInsertedDylib(const char* path)
{
ImageLoader* image = NULL;
unsigned cacheIndex;
try {
image = load(path, context, cacheIndex);
}
...
}
最后就是链接动态库,插入任何动态加载的镜像文件。
然后就开始运行:
void initializeMainExecutable()
{
...
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);//先为所有插入并链接完成的动态库执行初始化操作
}
}
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);//为主要可执行文件及其带来的一切运行初始化程序
...
}
void ImageLoader::runInitializers(const LinkContext& context, InitializerTimingList& timingInfo)
{
...
processInitializers(context, thisThread, timingInfo, up);//初始化准备
...
}
void ImageLoader::processInitializers(const LinkContext& context, mach_port_t thisThread,
InitializerTimingList& timingInfo, ImageLoader::UninitedUpwards& images)
{
...
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.
for (uintptr_t i=0; i < images.count; ++i) {//遍历image.count
images.images[i]->recursiveInitialization(context, thisThread, images.images[i]->getPath(), timingInfo, ups);//递归初始化镜像
}
...
}
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
...
try {
...
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);//单个镜像通知;获取到镜像的回调
// initialize this image
bool hasInitializers = this->doInitialization(context);
...
}
...
}
bool ImageLoaderMachO::doInitialization(const LinkContext& context)
{
CRSetCrashLogMessage2(this->getPath());
// mach-o has -init and static initializers
doImageInit(context);
doModInitFunctions(context);//比如会调用c++全局构造函数-初始化
CRSetCrashLogMessage2(NULL);
return (fHasDashInit || fHasInitializers);
}
void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
...
if ( type == S_MOD_INIT_FUNC_POINTERS ) {
Initializer* inits = (Initializer*)(sect->addr + fSlide);
...
for (size_t j=0; j < count; ++j) {
Initializer func = inits[j];
...
{
dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0);
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);//就是调用libSystem_initializer
}
...
}
...
}
- 到这里就要跳转到Libsystem源码:
__attribute__((constructor))
static void
libSystem_initializer(int argc,
const char* argv[],
const char* envp[],
const char* apple[],
const struct ProgramVars* vars)
{
...
libdispatch_init();
...
}
- 接着又跳转到libdispatch源码:
void
libdispatch_init(void)
{
...
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
void
_os_object_init(void)
{
_objc_init();
...
}
- 最后的最后终于调用了
_objc_init
,接着又要跳转到objc4源码:
void _objc_init(void)
{
...
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
map_images
:dyld
将image
加载进内存时 , 会触发该函数.
load_images
:dyld
初始化image
会触发该方法. ( 我们所熟知的load
方法也是在此处调用 ) .
unmap_image
:dyld
将image
移除时 , 会触发该函数 .
- 很不幸,又要跳回到dyld源码中:
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 registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;//load_images函数
...
for (std::vector<ImageLoader*>::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());//获取镜像文件真实地址; 比如会调用[ViewController load]
}
}
}
这里可以看到调用的sNotifyObjCInit
就是load_images
函数。
(dyld
-> libSystem
-> libDispatch
-> libObjc
-> _objc_init
)
- 总结