[OC学习笔记]启动流程

news2024/11/16 15:32:36

我们的app是如何从桌面图标被启动的嘞?这个问题值得探究。

冷启动与热启动

这两个启动的区别其实很简单,就看启动之前手机后台是否有app存活。

名称区别
冷启动启动时,App的进程不在系统里,需要开启新进程。
热启动启动时,App的进程还在系统里,不需要开启新进程。

Mach-O

mach-o是iOS/macOS二进制文件的格式,mach-o又分为几种不同的类型。本文介绍了常见的mach-o文件类型以及它们的不同之处。
Xcode->Build Setting ->Mach-O Type中,我们可以选择下面几种类型:

  • Executable(产物为ipa包)
  • Dynamic Library(产物为动态库)
  • Bundle(产物为bundle文件)
  • Static Library(产物为静态库)
  • Relocatable Object File(重定向文件)

请添加图片描述

这个exec文件就是mach-o格式的可执行文件。
请添加图片描述

可执行文件生成流程

  • 源文件:载入.h、.m、.cpp等文件
  • 预编译:替换宏,删除注释,展开头文件,产生.i文件
  • 编译:将.i文件转换为汇编语言,产生.s文件
  • 汇编:将汇编文件转换为机器码文件,产生.o文件
  • 链接:对.o文件中引用其他库的地方进行引用,生成最后的可执行文件
    请添加图片描述

APP启动流程

dyld的概念

点击图标后,系统调用exec()函数,系统将对应的Mach-O文件加载进内存,同时再将dyld加载进内存。dyld就会进行动态链接。其中dyld的主要工作有一下几点:

  • 递归加载可执行文件所依赖所有动态库
  • 进行rebasebingding操作
  • 调用main函数

dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责连接,加载程序。关于dyld,苹果已经开源dyld源码,我们可以通过阅读源码来了解dyldpre-main阶段的流程。截止目前dyld已经更新至dyld4了,iOS15以上系统都是使用的dyld4

app启动起始点

ViewControllerload方法打上断点,使用bt查看堆栈:
请添加图片描述
可以发现一开始的堆栈是 start然而通过网上查找资料可以发现,在调用start前还调用了_dyld_start。从网上找到这么一张图:
请添加图片描述
流程和我们查看的堆栈基本一致。

dyld源码分析

在源码中全局搜索_dyld_start,发现源码是由汇编编写完成的,通过下面的注释:

//
// This assembly code just needs to align the stack and jump into the C code for:
//      dyld::start(dyld4::KernelArgs*)
//

我们可以得知,此汇编代码只需要对齐堆栈并跳转到dyld::start(dyld4::KernelArgs*)。通过搜索namespace dyld4,可以找到start函数:

//
// Entry point for dyld.  The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Note: this function never returns, it calls exit().  Therefore stack protectors
// are useless, since the epilog is never executed.  Marking the fucntion no-return
// disable the stack protector.  The stack protector was also causing problems
// with armv7k codegen since it access the random value through a GOT slot in the
// prolog, but dyld is not rebased yet.
//
/*
dyld 的入口点。 内核加载 d 并跳转到 __dyld_start 这会设置一些寄存器并调用此函数。
注意:这个函数从不返回,它调用exit()。 
因此,堆栈保护器是无用的,因为永远不会执行epilog。 标记不返回功能禁用堆栈保护器。 
堆栈保护器也导致了armv7k codegen的问题,因为它通过prolog中的GOT插槽访问随机值,但dyld尚未重新定位。
*/
void start(const KernelArgs* kernArgs) __attribute__((noreturn)) __asm("start");
void start(const KernelArgs* kernArgs)
{
    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    // Note: this is called before dyld is rebased, so kdebug_trace_dyld_marker() cannot use any global variables
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // walk all fixups chains and rebase dyld
    // Note: withChainStarts() and fixupAllChainedFixups() cannot use any static DATA pointers as they are not rebased yet
    const MachOAnalyzer* dyldMA = getDyldMH();
    assert(dyldMA->hasChainedFixups());
    uintptr_t           slide = (long)dyldMA; // all fixup chain based images have a base address of zero, so slide == load address
    __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();

    // Now, we can call functions that use DATA
    mach_init();

    // set up random value for stack canary
    // 栈溢出保护
    __guard_setup(kernArgs->findApple());

    // setup so that open_with_subsystem() works
    _subsystem_init(kernArgs->findApple());

    // use placement new to construct ProcessConfig object in __DATA_CONST, before it is made read-only
    ProcessConfig& config = *new ((ProcessConfig*)sConfigBuffer) ProcessConfig(kernArgs, sSyscallDelegate);

    // 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);
        }
    });

#if !SUPPPORT_PRE_LC_MAIN
    // stack allocate RuntimeLocks. They cannot be in the Allocator pool which is usually read-only
    RuntimeLocks  sLocks;
#endif

    // create Allocator and APIs/RuntimeState object in that allocator
    // 在该分配器中创建分配器和 API/运行时状态对象
    APIs& state = APIs::bootstrap(config, sLocks);

    // load all dependents of program and bind them together
    // 加载程序的所有依赖项并将它们绑定在一起
    // 注意:⚠️调用流程图里提到的prepare()函数
    MainFunc appMain = prepare(state, dyldMA);

    // now make all dyld Allocated data structures read-only
    state.decWritable();

    // call main() and if it returns, call exit() with the result
    // Note: this is organized so that a backtrace in a program's main thread shows just "start" below "main"
    int result = appMain(state.config.process.argc, state.config.process.argv, state.config.process.envp, state.config.process.apple);

    // if we got here, main() returned (as opposed to program calling exit())
#if TARGET_OS_OSX
    // <rdar://74518676> libSystemHelpers is not set up for simulators, so directly call _exit()
    if ( MachOFile::isSimulatorPlatform(state.config.process.platform) )
        _exit(result);
#endif
    state.libSystemHelpers->exit(result);
}

我们可以继续看prepare()函数,立面调用了runAllInitializersForMain()
在这里插入图片描述

加载动态库

找到runAllInitializersForMain()源码:
请添加图片描述
在objc4源码中的_objc_init 方法处断点,并且使用bt命令查看调用栈:
请添加图片描述
_objc_init里面调用_dyld_objc_notify_register来注册3个方法:

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();
    runtime_init();
    exception_init();
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
	// 调用_dyld_objc_notify_register来注册3个方法 
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

map_images的调用

先看map_images源码:

map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

dyld源码中搜索_dyld_objc_notify_register方法,可以看到mapped(第一个参数)就是map_images

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);

    // 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);
}

继续寻找setObjCNotifiers方法并定位:

void RuntimeState::setObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// 保存三个方法
    _notifyObjCMapped   = mapped;
    _notifyObjCInit     = init;
    _notifyObjCUnmapped = unmapped;
    
	// 传入块
    withLoadersReadLock(^{
        // callback about already loaded images
        size_t maxCount = this->loaded.size();
        STACK_ALLOC_ARRAY(const mach_header*, mhs, maxCount);
        STACK_ALLOC_ARRAY(const char*, paths, maxCount);
        for ( const Loader* ldr : loaded ) {
            // don't need _mutex here because this is called when process is still single threaded
            const MachOLoaded* ml = ldr->loadAddress(*this);
            if ( ldr->hasObjC ) {
                paths.push_back(ldr->path());
                mhs.push_back(ml);
            }
        }
        if ( !mhs.empty() ) {
            (*_notifyObjCMapped)((uint32_t)mhs.count(), &paths[0], &mhs[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]);
                }
            }
        }
    });
}

可以看到里面对全局变量赋值其实就是保存3个方法,之后调用了withLoadersReadLock并且传入了blockblock内部执行了_notifyObjCMapped。那么block的执行就会调用map_images方法。

void RuntimeState::withLoadersReadLock(void (^work)())
{
#if BUILDING_DYLD
    if ( this->libSystemHelpers != nullptr ) {
        this->libSystemHelpers->os_unfair_recursive_lock_lock_with_options(&(_locks.loadersLock), OS_UNFAIR_LOCK_NONE);
        work();
        this->libSystemHelpers->os_unfair_recursive_lock_unlock(&_locks.loadersLock);
    }
    else
#endif
    {
        work();//⚠️
    }
}

可以看到在withLoadersReadLock是直接调用块的。也就是说,map_images是在objc_init的时候就会调用。

load_images的调用

先看我们前面传入的三个参数:

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

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);

void RuntimeState::setObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
	// 保存三个方法
    _notifyObjCMapped   = mapped;
    _notifyObjCInit     = init;
    _notifyObjCUnmapped = unmapped;

还是在runAllInitializersForMain中调用:
请添加图片描述
可以看到不论加载libSystem还是其他类都用到了这个方法,点进去看实现:

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);//⚠️
    }
}

其中除了打log外,主要的目的就是调用_notifyObjCInit,这个值是objc通过_dyld_objc_notify_register传给dyld的,然后在setObjCNotifiers方法中存储的。
可以看到dyld在加载应用程序的时候也是需要objc的。dyld像是一个首脑派发加载任务给objcobjc把动态库加载进内存。

rebase和bind

mach-o文件中的符号地址都是虚拟地址,在程序启动的时候,系统会生成一个随机数(alsr),使用虚拟地址加上alsr才是物理地址,也就是程序正真调用的地址。我们把从虚拟地址换算成物理地址的过程称之为rebase请添加图片描述
可以看到start中生成了slide这个相当于alsr。注释:所有基于修正链的image的基址均为零,因此slide等于加载地址。在后面调用prepare方法时传入了这个值:

// load all dependents of program and bind them together
MainFunc appMain = prepare(state, dyldMA);

请添加图片描述
这里的do fixups也是去处理了rebase

调用main函数

start()函数里面的后面,调用prepare方法获取main函数,之后直接调用,程序就进入我们的源代码中了。
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/134220.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端学习笔记006:React.js

&#xff08;前面学了那么多&#xff0c;终于到 React 了~&#xff09; React.js 大家应该听说过&#xff0c;是用于构建前端网页的主流框架之一。本文会把 React.js 开发中会用到的东西&#xff08;包括 React 核心&#xff0c;React 脚手架&#xff0c;React AJAX&#xff0…

nodejs接收时get请求参数

在http协议中&#xff0c;一个完整的url路径如下图 通过下图我们可以得知&#xff0c;get请求的参数是直接在url路径中显示。 get的请求参数在path资源路径的后面添加&#xff0c;以?表示参数的开始&#xff0c;以keyvalue表示参数的键值对&#xff0c;多个参数以&符号分…

Typescript中函数类型

不给参数定义类型&#xff0c;会报错&#xff0c;如下&#xff1a; 常见写法 function add1(x: number, y: number) {return x y; }function add1_1(x: number, y: number): number {return x y; }const add2 function (x: number, y: number): number {return x y; };con…

第一天作业

第一天 配置ansible学习环境实现以下要求1.控制主机和受控主机通过root用户通过免密验证方式远程控住受控主机实施对应&#xff08;普通命令&#xff0c;特权命令&#xff09;任务2.控制主机连接受控主机通过普通用户以免密验证远程控住受控主机实施指定&#xff08;普通命令&…

【Wayland】QtWayland启动流程分析

QtWayland启动流程分析 QtWayland版本&#xff1a;6.4.0 QtWayland的服务端(CompositorServer)入口点是QWaylandCompositor这个类&#xff0c;可以参考官网提供的example&#xff0c;其路径为&#xff1a;examples\wayland\minimal-cpp 下面基于minimal-cpp这个例子&#xff…

详解 Redis 中的 AOF 日志

AOF&#xff08;Append Only File&#xff09;追加写&#xff0c; AOF 日志它是写后日志&#xff0c;“写后”的意思是 Redis 是先执行命令&#xff0c;把数据写入内存&#xff0c;然后才记录日志&#xff0c;如下图所示&#xff1a; 后写日志有什么好处呢&#xff1f; Redis …

FPGA知识汇集-串行 RapidIO: 高性能嵌入式互连技术

本文摘自&#xff1a;德州仪器网站 串行RapidIO: 高性能嵌入式互连技术 | 德州仪器 (http://ti.com.cn) 串行RapidIO针对高性能嵌入式系统芯片间和板间互连而设计&#xff0c;它将是未来十几年中嵌入式系统互连的最佳选择。 本文比较RapidIO和传统互连技术的优点&#xff1b…

Windows内核--GUI显示原理(6.1)

传统的Windows图形处理 在Vista之前&#xff0c;图形子系统内核部分win32k.sys 通过DDI接口操作显示驱动&#xff0c; 显示驱动通过ENG接口调用win32k.sys. From: Windows 2000 显示体系结构 显示驱动程序的作用 不同显示驱动程序负责对于不同显示设备的优化。GDI仅负责标准位图…

使用python的parser.add_argument()在卷积神经网络中如何预定义参数?

在训练卷积神经网络时&#xff0c;需要预定义很多参数&#xff0c;例如batchsizebatch_sizebatchs​ize,backbone,dataset,datasetrootbackbone,dataset,dataset_rootbackbone,dataset,datasetr​oot等等&#xff0c;这些参数多而且特别零散&#xff0c;如果我们最初不把这些参…

我国制造行业 工业控制系统安全控制措施建设思考总结

声明 本文是学习GB-T 32919-2016 信息安全技术 工业控制系统安全控制应用指南. 下载地址 http://github5.com/view/585而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 工业控制系统安全控制概述 从概念上来说&#xff0c;工业控制系统的安全与其它领域…

【网络】网络发展,网络协议,网络传输流程,地址管理

目录 1.计算机网络背景 1.1网络发展 局域网和广域网 1.2 协议 2.网络协议初识 2.1协议分层 2.2OSI七层模型 2.3 TCP/IP 五层&#xff08;或四层&#xff09;模型 网络和操作系统之间的关系 2.4重谈协议 -- 计算机的视角&#xff0c;如何看待协议&#xff1f; 2.5 网…

python带你采集淘/苏/唯/考四大电商平台商品数据

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 又到了学Python时刻~ 环境介绍: python 3.8 越稳定越好 pycharm 2021专业版 一、苏宁 模块使用: selenium >>> pip install selenium 3.141.0 Python当中的模块 操作 浏览器的驱动 Chrome浏览器 Chromedr…

2023北京国际残疾人用品展览会|福祉博览会

全 称&#xff1a;2023中国国际福祉博览会暨中国国际康复博览会 英 文&#xff1a;Care & Rehabilitation Expo China 2023 时 间&#xff1a;2023年5月21-23日 地 点&#xff1a;北京首钢会展中心&#xff08;1-3号馆&#xff09; 主 办&#xff1a;中国残疾人联合会 承 办…

visdrone数据集转化为MOT数据集(用作MOTR模型训练)

文章目录visdrone数据集转化为MOT数据集MOT17 数据集格式traindet.txtgt.txtseqinfo.initestdet.txtvisdrone——Task 4_ Multi-Object Tracking配置seqinfo.ini文件代码如下Linuxvisdrone数据集转化为MOT数据集 MOT17 数据集格式 ├── MOT17 │ ├── images │ ├─…

YOLO学习记录之模型修改

我们在做实验时&#xff0c;不免需要对模型结构进行修改来检测自己的改进性能&#xff0c;对于一般模型而言&#xff0c;我们只需要简单的在代码中添加网络层即可&#xff0c;但对于一些预训练好的模型&#xff0c;我们则需要进行较为复杂的修改。以我们的YOLOV7模型为例&#…

[Linux]----守护进程

文章目录前言一、什么是守护进程?二、会话和进程组会话进程组三、守护进程的编程流程总结前言 这节课我来给大家讲解在Linux下如何让进程守护化,运行在后台,处理我们的任务. 正文开始! 一、什么是守护进程? 守护进程也称为精灵进程(Daemon),是运行在后台的一种特殊进程.它…

Mybatis-Plus快速使用相关知识点1

Mybatis-Plus的mapper、service 基本CURD BaseMapper BaseMapper是MyBatis-Plus提供的模板mapper&#xff0c;其中包含了基本的CRUD方法&#xff0c;泛型为操作的实体类型&#xff0c;Mapper 继承该接口后&#xff0c;无需编写 mapper.xml 文件&#xff0c;即可获得CRUD功能…

JavaScript刷LeetCode拿offer-链表篇

一、链表 链表&#xff08;Linked List&#xff09;是一种常见的基础数据结构&#xff0c;也是线性表的一种。 一个线性表是 n 个具有相同特性的数据元素的有限序列&#xff0c;线性表的存储结构分为两类&#xff1a;顺序表&#xff08;数组&#xff09;和链表。 链表相比较顺…

站得高,望得远

1、站得高&#xff0c;望的远 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。 这句话几乎概括了计算机系统软件体系结构的设计要点 &#xff0c;整个体系结构从上到下都是按照严格的层次结构设计的。不仅是计算机系统软件整个体系是这样的&#xff0c;体系里…

884. 两句话中的不常见单词 map与stringstream

目录 力扣884. 两句话中的不常见单词 【解法一】&#xff1a;最后写出了一坨屎&#xff0c;虽然它是一坨屎&#xff0c;但是它能动&#xff0c;虽然它是一坨屎&#xff0c;但起码这是我自己拉的 【大佬解法】 stringstream的使用 以及 map的使用 884. 两句话中的不常见单词 句…