Hello World背后的逻辑

一门语言的开发入门,总是抬手就能整出一个「Hello World Demo」。比如下面这样:

image.png

显然,熟悉 iOS 开发的同学都知道,上面这个来自 Objective-C。

今天,我们就从这熟悉的代码入手,来一起研究研究「Hello World」出世的整个过程。

main 函数

众说周知,main 函数是我们程序的入口,我们不妨从此入手,开始我们的表演。

入手的姿势已经确定,甩手一个断点,拿到下图:

image.png 显然,在main函数执行之前,先是调用了start方法,那这个所谓的start方法又是什么呢?哪里来的呢?又是怎么调起来的呢?

从上图我们并不看的很真切,因+ (void)load {}方法的调用是在加载阶段(后面验证),而加载完之后才触发main函数的调用,所以我们在load方法时候再加上一个断点(这里可以随便弄个类,重写load方法),看看究竟。

+load方法

再次运行代码之后很容易先确认前面提到的「load 方法调用在 main 调用前」,并拿到下面的调用堆栈(bt命令可打印更详细的堆栈信息):

image.png 从上图我们可以清楚的看到一切的开始源于一堆dyld的东西,那么,dyld是个啥?

dyld(全名 the dynamic link editor)是苹果的动态链接器,用来链接所有的库和可执行文件,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由 dyld 负责余下的工作。它的代码也是开源的,正常可以从这里下载

!!注意:网上有很多关于dyld执行流程的介绍,但都是基于稍老的一些版本,可以看到上面的堆栈信息与老版本的也有些许差异,但是总体流程上基本一致,这里介绍基于最新的dyld4版本,源码可从这里下载

我们接着说,在dyld做完加载库、可执行文件等一系列准备工作之后,通过dyld4::RuntimeState::notifyObjCInit 触发libobjc.A.dylib中的load_images函数,再到我们自定义的[Person load]方法的调用,最终到在之后的main函数。

此话怎么讲呢?iOS开发者对notify这样的字眼是不是有些熟悉?对的,通知!还是通知的触发,上面的过程很显然就是dyld触发通知到注册通知的接收方,哪里呢?libobjc.A.dylib中的load_images函数,这个库其实就是我们常念叨的RuntimeRuntime的代码也是在官方的开源库中,所以我们接下来可以直接验证一下我们的猜想。Runtime源码这里下载

这里额外说明一下,Runtime源码是可以运行起来的,当然需要一堆配置,这里提供一个可以直接跑起来的源码,在这里,下载下来之后,Target 选择KCObjcBuild就可以,源码中直接Debug,不要太爽。

load_images函数

哦了,接着说,我们怎么验证呢?直接在Runtime源码中直接搜索load_images,很容易定位到下面这里:

image.png 显然是load_images定义的地方,直接甩一断点验证看看,(源码直接编译优势凸显):

image.png 这里调用的堆栈信息,很显然符合我们的猜想。

继续,是通知就该有注册的地方,不然怎么就能调用到上面的load_images函数呢?前面搜索load_images的时候其实就有这么个地儿:

image.png

上图中我们看到了什么呢?是的,_dyld_objc_notify_register,盲猜一下,应该就是通知的注册地了吧。

既然如此,我们知道要想实现上面的调用过程,那么,通知的注册应该要在调用触发之前,不然可说不过去,来来来,接着验证,甩手一个断点(就问源码直接debug爽还是爽?):

image.png 果然如此吧,甚至上面的堆栈信息直接能看出来_objc_init的调用过程,算是意外之喜了。

_objc_init函数

_objc_init的调用过程,从上面的堆栈信息可以看到:

-> dyld`start 
-> dyld`dyld4::prepare() 
-> dyld`dyld4::APIs::runAllInitializersForMain() 
-> dyld`dyld4::Loader::findAndRunAllInitializers() 
-> dyld`dyld3::MachOAnalyzer::forEachInitializer() 
-> dyld`dyld3::MachOFile::forEachSection() const 
-> dyld`dyld3::MachOFile::forEachLoadCommand() 
-> dyld3::MachOFile::forEachSection() 
-> dyld3::MachOAnalyzer::forEachInitializer() 
-> dyld4::Loader::findAndRunAllInitializers() 
-> libSystem.B.dylib`libSystem_initializer 
-> libdispatch.dylib`libdispatch_init 
-> libdispatch.dylib`_os_object_init 
-> libobjc.A.dylib`_objc_init  

dyld相关的函数在dyld开源库中,前面已经提到了,这里。

libSystem.B.dylib的相关代码在官方的开源库中能直接拿到,这里。

libdispatch.dylib相关的代码也在官方的开源库中能查询到,这里。

libobjc.A.dylib就是上面提到的Runtime代码了,这里

运行轨迹

截止目前,我们大致了解了main函数调用的基本逻辑:

dyldstart开始 -> _objc_init函数加载(注册了load_images) -> 触发load_images函数 -> 触发+load方法 -> 在最后才调用main函数 -> 最终输出Hello World

可见,在main函数调用之前,确实还是有一大堆我们并没有意识到的操作,大部分是dyld在处理,我们姑且称之为–加载过程

但是main函数究竟是怎么被唤起的呢?目前看着仍旧不是很明朗,还是要继续撸源码,那块儿的源码呢?从前面的堆栈来看,还是要从dyldstartprepare入手。

dyld唤起main函数

我们可以在dyld源码中全局搜索prepare(,最终找到能这个地方:

image.png 而这里正好是start函数的内部调用,符合我们前面的堆栈信息,地方应该可以确认了,没跑了。

其实从这里我们就可以在此大胆假设,然后去小心求证了:

// load all dependents of program and bind them together
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);

从上面的命名及注释,很容易看到:prepare()主要就是处理main函数调用前的准备工作,比如加载所有的dependents,最终返回的就是main函数的入口(MainFunc类型),而后的appMain()实际上就是对main函数的调用了,我们可以看到其参数就跟我们main的参数神似了。

上面的这些,我们如果深入prepare()就会进一步得到验证,截取部分代码如下:

...
    // run all initializers
    state.runAllInitializersForMain();

    // notify we are about to call main
    notifyMonitoringDyldMain();
    if ( dyld3::kdebug_trace_dyld_enabled(DBG_DYLD_TIMING_LAUNCH_EXECUTABLE) ) {
        dyld3::kdebug_trace_dyld_duration_end(launchTraceID, DBG_DYLD_TIMING_LAUNCH_EXECUTABLE, 0, 0, 4);
    }
    ARIADNEDBG_CODE(220, 1);

    MainFunc result;
    if ( state.config.security.skipMain ) {
        return &fake_main;
    }
    else if ( state.config.process.platform == dyld3::Platform::driverKit ) {
        result = state.mainFunc();
        if ( result == 0 )
            halt("DriverKit main entry point not set");
#if __has_feature(ptrauth_calls)
        // HACK: DriverKit signs the pointer with a diversity different than dyld expects when calling the pointer.
        result = (MainFunc)__builtin_ptrauth_strip((void*)result, ptrauth_key_function_pointer);
        result = (MainFunc)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
    }
    else {
        // find entry point for main executable
        uint64_t entryOffset;
        bool     usesCRT;
        if ( !state.config.process.mainExecutable->getEntry(entryOffset, usesCRT) )
            halt("main executable has no entry point");
        result = (MainFunc)((uintptr_t)state.config.process.mainExecutable + entryOffset);
        if ( usesCRT ) {
            // main executable uses LC_UNIXTHREAD, dyld needs to cut back kernel arg stack and jump to "start"
#if SUPPPORT_PRE_LC_MAIN
            // backsolve for KernelArgs (original stack entry point in _dyld_start)
            const KernelArgs* kernArgs = (KernelArgs*)(&state.config.process.argv[-2]);
            gotoAppStart((uintptr_t)result, kernArgs);
#else
            halt("main executable is missing LC_MAIN");
#endif
        }
#if __has_feature(ptrauth_calls)
        result = (MainFunc)__builtin_ptrauth_sign_unauthenticated((void*)result, 0, 0);
#endif
    }

runAllInitializersForMain()也能在前面的堆栈中得以体现,函数名也很直观,为main函数的调用,做所有的初始化工作。

而下面的result赋值的过程,实际上就是main函数入口查找的过程。

总结

综上所述,main函数调用前的过程我们更清晰了,一切的开始是从dyldstart函数开始,其通过prepare()函数做好了main函数调用前的所有准备工作,如初始化、链接所有的动态库及可执行文件等,查找main函数的入口并返回到start函数后,实现main函数的调用触发。

当然,这中间实际上还有一些模糊的地方,比如_objc_init里面具体干了啥,load_images有什么用,以及上面的appMain就是我们最开始截图里面的main方法吗?(显然不是,参数数量对不上,哈哈,中间还有一些过程)。

有兴趣的小伙伴可以继续研究研究,后续我们再继续讨论。

Python 的迅速崛起对整个行业来说都是极其有利的 ,但“人红是非多”,导致它平添了许许多多的批评,不过依旧挡不住它火爆的发展势头。

如果你对Python感兴趣,想要学习python,这里给大家分享一份Python全套学习资料,都是我自己学习时整理的,希望可以帮到你,一起加油!

😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
Python全套学习资料

在这里插入图片描述

1️⃣零基础入门

① 学习路线

对于从来没有接触过Python的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
在这里插入图片描述

② 路线对应学习视频

还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~
在这里插入图片描述

③练习题

每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
在这里插入图片描述

2️⃣国内外Python书籍、文档

① 文档和书籍资料

在这里插入图片描述

3️⃣Python工具包+项目源码合集

①Python工具包

学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
在这里插入图片描述

②Python实战案例

光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
在这里插入图片描述

③Python小游戏源码

如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
在这里插入图片描述

4️⃣Python面试题

我们学会了Python之后,有了技能就可以出去找工作啦!下面这些面试题是都来自阿里、腾讯、字节等一线互联网大厂,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
在这里插入图片描述
在这里插入图片描述

5️⃣Python兼职渠道

而且学会Python以后,还可以在各大兼职平台接单赚钱,各种兼职渠道+兼职注意事项+如何和客户沟通,我都整理成文档了。
在这里插入图片描述

上述所有资料 ⚡️ ,朋友们如果有需要的,可以扫描下方👇👇👇二维码免费领取🆓
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

verdi如何打开时可以加载配置比如字体

打开tcl使能 找到配置字体的命令 其实其他有需要的文件配置都可以在这里找到对应的指令 存储文件 新建verdi001.tcl文件 输入想要调整的字体以及大小 verdiSetFont -font "Bitstream Vera Sans" -size "18" verdiSetFont -monoFont "Courier&q…

Kafka JNDI 注入分析(CVE-2023-25194)

Apache Kafka Clients Jndi Injection 漏洞描述 Apache Kafka 是一个分布式数据流处理平台,可以实时发布、订阅、存储和处理数据流。Kafka Connect 是一种用于在 kafka 和其他系统之间可扩展、可靠的流式传输数据的工具。攻击者可以利用基于 SASL JAAS 配置和 SAS…

做哪些副业可以日赚一百?对程序员来说简直不要太容易!

日赚一百?对程序员来说简直不要太容易!下面给程序员们推荐一些日赚100的副业: ①外包接单 程序员简单粗暴赚钱的副业之一。 外包接单的类型包括但不限于:软件开发、硬件开发、小程序功能开发、web开发……大到一个系统的开发、…

pip 安装任意软件包报错

现象 使用 pip 命令时提示 查看源码 可以看到是从 pip 包中导入 main失败,点击查看目录 main 文件不见了,判断是文件缺失,重装 pip 即可 # python3 下载 pip curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # python2 下载…

输入网址到网页显示,期间发生了什么?(收藏篇)

解析url 首先浏览器做的第一步工作就是要对 URL 进行解析,从而生成发送给 Web 服务器的请求信息。对 URL 进行解析之后,浏览器确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息了。 DNS解析 通过浏览器解析 URL 并…

uniapp使用vur-cli新建项目并打包

新建项目 npm install -g vue/cli vue create -p dcloudio/uni-preset-vue my-project选择默认模板npm run dev:h5 运行 安装sass和uview &#xff08;npm安装失败&#xff09; bug&#xff1a;使用uni.scss中的变量或样式&#xff0c;<style lang"scss"> 必…

亚马逊鲲鹏系统六大优势

亚马逊鲲鹏系统六大优势凭借其独特的能力&#xff0c;完全模拟真实的人类行为。只需几个简单的步骤 就可以自由安排任务&#xff0c;让所有账户随时发挥最大的作用。 1、全自动化操作 可以全自动批量注册买家号、AI智能养号、全自动批量测评&#xff0c;模拟人类的操作行为例…

vue-element-admin 集成框架设置中文语言

首先拉取中文版分支代码 https://github.com/PanJiaChen/vue-element-admin/tree/i18n &#xff08;下载卡的话&#xff0c;下载小羊的压缩包&#xff0c;已上传资源&#xff09; \src\lang\index.js 改完dangdangdang可以啦

CSDN中调整图片和文本样式

1.调整图片比例 插入图片后&#xff0c;觉得图片比例不协调&#xff0c;想改小点。只需要在文件后缀加个参数即可&#xff1a;?pic_center 60x。 NOTE&#xff1a;等号左边一定要加个空格&#xff0c;否则格式不生效 2.修改字体颜色 如上 NOTE&#xff1a;等号左边一定要…

Leo赠书活动-07期 【嵌入式虚拟化技术与应用】文末送书

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

UML与PlantUML简介

UML与PlantUML 1、UML与PlantUML概述2、PlantUML使用 1、UML与PlantUML概述 UML&#xff08;Unified Modeling Language&#xff09;是一种统一建模语言&#xff0c;为面向对象开发系统的产品进行说明、可视化、和编制文档的一种标准语言&#xff0c;独立于任何具体程序设计语言…

什么是伺服电机?Parker派克伺服电机盘点

一、什么是伺服电机&#xff1f; 要准确地定义伺服电机&#xff0c;我们首先需理解其核心特性&#xff1a;反馈与闭环控制。伺服电机凭借这些特性&#xff0c;能精确控制扭矩、速度或位置&#xff0c;即使在零速度下&#xff0c;也能保持足够的扭矩以锁定负载。 伺服电机与其…

RAW图像处理软件Capture One 23 Enterprise mac中文版功能特点

Capture One 23 Enterprise mac是一款专业的图像处理软件&#xff0c;旨在为企业用户提供高效、快速和灵活的工作流程。 Capture One 23 Enterprise mac软件的特点和功能 强大的图像编辑工具&#xff1a;Capture One 23 Enterprise提供了一系列强大的图像编辑工具&#xff0c;…

【Linux语音控制 安卓设备刷短视频 orangePi zero2 H616 (已开源) 】.md uptada:23/11/07

文章目录 H616_实现Ubuntu语音控制安卓设备刷短视频小美效果展示H616 ubuntu系统 安装adb智能公元 SU-03T 离线语音模组 固件制作配合串口实现 小美_控制安卓刷抖音 H616_实现Ubuntu语音控制安卓设备刷短视频 注意&#xff1a;orangePi zero2 H616 安装系统为ubuntu 小美效果…

Linux Centos配置邮件发送

Linux Centos配置邮件发送 这里使用的是外部发送邮件方式&#xff0c;也就是使用自己的账号发送 第一步 首先要开启STMP授权码&#xff0c;以QQ邮箱为例 配置文件 vim /etc/mail.rc找到之后在最下面添加如下 #邮箱set from3324855376qq.com #默认smtp发送&#xff0c;stmp…

自动驾驶系统激光雷达传感器反射率标定板

自动驾驶技术正在全球范围内快速发展和推广。在中国&#xff0c;自动驾驶技术也得到了高度重视和大力支持。中国政府已经出台了一系列政策&#xff0c;推动自动驾驶技术的发展和应用。例如&#xff0c;上海、北京等地已经开放了自动驾驶测试道路&#xff0c;并开展了自动驾驶公…

剑指JUC原理-17.CompletableFuture

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&…

Spring boot 整合grpc 运用

文章目录 GRPC基础概念&#xff1a;Protocol Buffers&#xff1a;proto 基础语法&#xff1a;调用类型&#xff1a; Spring boot 整合 grpc项目结构&#xff1a;整合代码&#xff1a;父 pomproto 模块服务端&#xff1a;客户端&#xff1a;实际调用&#xff1a; 原生集成 GRPC基…

【uniapp/uview】Collapse 折叠面板更改右侧小箭头图标

最终效果是这样的&#xff1a; 官方没有给出相关配置项&#xff0c;后来发现小箭头不是 uview 的图标&#xff0c;而是 unicode 编码&#xff0c;具体代码&#xff1a; // 箭头图标 ::v-deep .uicon-arrow-down[data-v-6e20bb40]:before {content: \1f783; }附一个查询其他 u…

机器学习 - 决策树:技术全解与案例实战

目录 一、引言二、决策树基础决策树模型概述构建决策树的关键概念特征选择决策树的生成 决策树的剪枝 三、算法研究进阶提升树和随机森林提升树&#xff08;Boosted Trees&#xff09;随机森林&#xff08;Random Forests&#xff09; 进化算法与决策树决策树结构的进化 多目标…
最新文章