iOS ------ 编译链接

编译流程分析

编译可以分为四步:

  • 预处理(Prepressing)
  • 编译(Compilation)
  • 汇编 (Assembly)
  • 链接(Linking)

在这里插入图片描述

预编译(Prepressing)

过程是源文件main.c和相关头文件被(stdio.h)被预编译器cpp预编译成一个.i文件

使用命令:clang -E main.m 或在Xcode的Product->Perform Action->Preprocess得到预编译结果。

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...NSLog(@"Hello, World!");Person* person = [[Person alloc] init];}return 0;
}

预编译后

# 187 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Foundation.framework/Headers/Foundation.h" 2 3
# 9 "main.m" 2
# 1 "./Person.h" 1
# 10 "./Person.h"
#pragma clang assume_nonnull begin@interface Person : NSObject
@property NSString* name;
@end
# 14 "./Person.h"
#pragma clang assume_nonnull end
# 10 "main.m" 2
int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"Hello, World!");Person* person = [[Person alloc] init];person.name = @"111";}return 0;
}

预编译主要是处理源代码中以“#”开头的预编译指令:

  • "define"删除并展开对应宏定义
  • 处理所有的条件预编译指令,如#if/#ifdef/#else/#endif
  • "#include/#import"包含的文件递归插入此处
  • 删除所有的注释"//或/**/"
  • 添加行号或文件名标识。如“#1"main.m"”,编译调试会用到

编译(compliation)

编译过程就是把预处理完的文件进行一系列的:词法分析语法分析语义分析及优化后生产相应的汇编代码文件,此过程是整个程序构建的核心部分,也是最复杂的部分之一。
其编译过程相当于如下命令:

clang -S main.i -o main.s
  • 词法分析:这一步把源文件中的代码转化为特殊的标记流,源码被分割成一个一个 token(关键字、标识符、字面量、特殊符号),在行尾Loc中都标记出了源码所在的对应源文件和具体行数,方便在报错时定位问题。

使用命令clang -Xclang -dump-tokens main.m

star '*'		Loc=<main.m:14:15>
identifier 'person'	 [LeadingSpace]	Loc=<main.m:14:17>
equal '='	 [LeadingSpace]	Loc=<main.m:14:25>
l_square '['	 [LeadingSpace]	Loc=<main.m:14:27>
l_square '['		Loc=<main.m:14:28>
identifier 'Person'		Loc=<main.m:14:29>
identifier 'alloc'	 [LeadingSpace]	Loc=<main.m:14:36>
r_square ']'		Loc=<main.m:14:41>
identifier 'init'	 [LeadingSpace]	Loc=<main.m:14:43>
r_square ']'		Loc=<main.m:14:47>
semi ';'		Loc=<main.m:14:48>
  • 语法分析:把语法分析生成的标记流,解析成一个抽象语法树(AST),每个节点也标记了其在源码的位置

执行 clang 命令 clang -Xclang -ast-dump -fsyntax-only main.m

抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。

 |   |     `-ObjCMessageExpr 0x130078978 <col:28, col:41> 'Person *' selector=alloc class='Person'|   `-PseudoObjectExpr 0x130078b50 <line:15:9, col:24> 'NSString *'|     |-BinaryOperator 0x130078af8 <col:9, col:24> 'NSString *' '='|     | |-ObjCPropertyRefExpr 0x130078ab0 <col:9, col:16> '<pseudo-object type>' lvalue objcproperty Kind=PropertyRef Property="name" Messaging=Setter|     | | `-OpaqueValueExpr 0x130078a98 <col:9> 'Person *'|     | |   `-ImplicitCastExpr 0x130078a10 <col:9> 'Person *' <LValueToRValue>|     | |     `-DeclRefExpr 0x1300789f0 <col:9> 'Person *' lvalue Var 0x130078900 'person' 'Person *'|     | `-OpaqueValueExpr 0x130078ae0 <col:23, col:24> 'NSString *'|     |   `-ObjCStringLiteral 0x130078a78 <col:23, col:24> 'NSString *'|     |     `-StringLiteral 0x130078a58 <col:24> 'char[4]' lvalue "111"
  • 静态分析:分析类型声明和匹配问题。比如整型和字符串相加,肯定会报错。
    中间语言生成:CodeGen根据AST自顶向下遍历逐步翻译成 LLVM IR,并且在编译期就可以确定的表达式进行优化,比如代码里t1=2+6,可以优化t1=8。(假如开启了bitcode,)

使用命令clang -O3 -S -emit-llvm main.m -o main.ll

@__CFConstantStringClassReference = external global [0 x i32]
@.str = private unnamed_addr constant [14 x i8] c"Hello, World!\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_ = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i32 0, i32 0), i64 13 }, section "__DATA,__cfstring", align 8 #0
@"OBJC_CLASS_$_Person" = external global %struct._class_t
@"OBJC_CLASSLIST_REFERENCES_$_" = internal global %struct._class_t* @"OBJC_CLASS_$_Person", section "__DATA,__objc_classrefs,regular,no_dead_strip", align 8
@.str.1 = private unnamed_addr constant [4 x i8] c"111\00", section "__TEXT,__cstring,cstring_literals", align 1
@_unnamed_cfstring_.2 = private global %struct.__NSConstantString_tag { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str.1, i32 0, i32 0), i64 3 }, section "__DATA,__cfstring", align 8 #0
@llvm.compiler.used = appending global [1 x i8*] [i8* bitcast (%struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_" to i8*)], section "llvm.metadata"
  • 目标代码生成与优化:根据中间语言生成依赖具体机器的汇编语言。并优化汇编语言。这个过程中,假如有变量且定义在同一个编译单元里,那给这个变量分配空间,确定变量的地址。假如变量或者函数不定义在这个编译单元,得链接时候,才能确定地址。

使用命令xcrun clang -S -o - main.m | open -f

.section	__TEXT,__text,regular,pure_instructions.build_version macos, 13, 0	sdk_version 13, 1.globl	_main                           ; -- Begin function main.p2align	2
_main:                                  ; @main.cfi_startproc
; %bb.0:sub	sp, sp, #64stp	x29, x30, [sp, #48]             ; 16-byte Folded Spilladd	x29, sp, #48.cfi_def_cfa w29, 16.cfi_offset w30, -8.cfi_offset w29, -16; implicit-def: $x8mov	w8, #0str	w8, [sp, #20]                   ; 4-byte Folded Spillstur	wzr, [x29, #-4]stur	w0, [x29, #-8]stur	x1, [x29, #-16]

汇编(Assembly)

汇编就是把上面得到的.s文件里的汇编指令一一翻译为机器语言。汇编器的汇编过程相较于编译器来讲比较简单,只是根据汇编指令和机器指令的对照表一一翻译就可以了。

汇编指令:

clang -c main.s -o main.o

链接(Linking)

链接指令:

clang main.o -o main

现在程序为了便于维护都是分模块组成,比如一个App,对应有多个源代码文件。每个源代码文件汇编成目标文件,根据上面流程A目标文件访问B目标文件的函数或者变量,是不知道地址的,链接就是要解决这个问题。链接过程主要包括地址和空间分配、符号决议和重定位。

链接就是把目标文件(一个或多个)和需要的库(静态库/动态库)链接成可执行文件。后面会分别讲静态链接和动态链接。

静态库和动态库

什么是库?

  • 库就是程序代码的集合,将N个文件组织起来,是共享程序代码的一种方式
  • 本质上是一种可执行代码的二进制文件,可以被载入程序中运行

静态库和动态库

  • 什么是静态库和动态库
    • 静态和动态是相对于编译期和运行期而言的,静态库会在程序编译时会被链接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时不会被链接到目标代码中,只在程序运行时才被载入
      静态库在程序编译链接的时候,如下图所示
      在这里插入图片描述

动态库在程序编译链接的时候,如下图所示:

在这里插入图片描述

  • 静态库和动态库都是程序编译好的二进制文件,苹果官方的解释:

    • 动态库:可以在运行或启动的时候加载到内存中,加载到一块独立于App的内存地址
    • 静态库:当程序启动的时候时,会将App的代码(包括静态库的代码)一同加载到App所处的内存地址上。相比于静态库的方案,使用动态库会花费更多的启动时间和内存消耗,还会增加可执行文件的大小
  • 存在形式:

    • 静态库:以 “.a” 或者 “.framework” 为文件后缀名;.a 是一个纯二进制文件,.framework 中除了有二进制文件之外还有资源文件。.a 要有 .h 文件以及资源文件配合,.framework 文件可以直接使用。总的来说,.a + .h + sourceFile = .framework,因此创建静态库最好还是用 .framework 的形式。
    • 动态库:以 “.dylib” 或者 “.framework” 为文件后缀名(Xcode7 之后 .tbd 替代 .dylib)。
  • 使用区别

    • 静态库链接时会被完整的复制到可执行文件中,被多次使用就会被多份拷贝
    • 在这里插入图片描述

因为整个数据库的代码都被整合到目标代码中,则编译成的文件比较大。编译后的执行程序不再需要外部的数据库支持。如果静态数据库改变了,那么程序必须重新编译。

  • 动态库链接时不复制,程序运行时有系统动态加载到内存中,供程序调用,而且系统只加载一次,多个程序调用节省内存
    在这里插入图片描述

  • 动态库在编译时,并没有编译进目标代码,所以产生的可执行文件比较小。当程序运行时执行到相关函数才才动态申请并调用函数库的相应函数,所以程序的运行环境必须提供相应的库。且动态函数库的升级并不影响程序。

  • 各种优点:

静态库:

  • 模块化,分工合作,提高了代码的复用及核心技术的保密程度;
  • 避免少量改动经常导致大量的重复编译连接;
  • 也可以重用,注意不是共享使用。
    动态库:
  • 可以将最终可执行文件体积缩小,将整个应用程序分模块,团队合作,进行分工,影响比较小;
  • 多个应用程序共享内存中得同一份库文件,节省资源;
  • 可以不重新编译连接可执行程序的前提下,更新动态库文件达到更新应用程序的目的;

dyld动态链接器

动态库在程序中是怎么加载到内存呢?系统是怎么链接的?这就要用到dyld(the dymanic link editor)动态链接器

dyld(the dymanic link editor)动态链接器是苹果操作系统的一个重要组成部分,在系统内核XNU完成Mach- O文件的加载,做好程序准备工作后,加载或链接动态共享库或可执行文件确保程序能正确执行。

加载流程:

1,环境变量控制:根据环境的状态配置和环境变量下的相应的条件判断状态值及获取当前架构
2,共享缓存解析处理:检查是否开启了共享缓存及共享缓存是否映射到共享区域,例如UIKi t,CoreFoundation等
3,主程序初始化:调用instantiateFromLoadedImage函数实例化出一个ImageLoader对象
4,插入动态库:遍历环境变量DYLD_INSERT_LIBRARIES待插入动态库表容器组,调用loadInsertedDylib加载引入
5,链接主程序和动态库:进行符号和地址的绑定,加载所有类,最后执行load方法和clang attribute的constructor修饰函数

dyld2

dyld2是如何加载程序的?

  • Parse mach-0 header/Find dependencies:分析mach-o headers,通过分析得知需要加载哪些库,然后通过递归查找上述的库又需要那些其他库的支持,知道获得所有dylib完整的二进制文件。普通的iOS程序需要3-600个dylib,数据很庞大,需要大量的处理。
  • 第二Map mach-o files:映射所有的mach-o文件,将他们放入到地址空间内,即内存
  • 第三Perform symbol lookups:执行符号查找,假设程序内使用了printf函数,将会查找printf是否在系统库中,然后找到函数的地址,将它复制到你的程序汇中的函数指针
  • 第四Bind and rebase:进行符号的重绑定,复制这些指针,由于使用随机地址,所有指针必须使用基地址
  • 第五Run initializers:运行所有的初始化器
  • 第六:准备运行main函数
    在这里插入图片描述

dyld3

经过对dyld 2的优化,dyld 3的加载过程的不同之处

  • 将perform symbol lookups移到第二步,向磁盘写入闭包处理。
  • 将dyld分成了3部分,红色部分是一个进程外的mach-o分析器与编译器,也是一个进程内引擎,执行启动闭包处理,也是一个启用闭包的缓存服务,大多数的程序启动会使用缓存,但是始终不需要调用进程外mach-o分析器和编译器,启用闭包比mach-o更加的简单,它们是内存映射文件,不需要复杂的方法进行分析
  • 进程外编译器部分:首先解析所有的搜索路径,所有rpaths、所有环境变量、然后分析mach-o的二进制数据,执行所有的符号查找,利用这些结果来创建闭包处理
  • dyld3也是一个小型进程内引擎,这部分驻留在进程内,它所做的事情就是验证闭包是否正确,然后映射到dylib中,再跳转到main函数,与dyld2对比,dyld3不需要分析mach-o文件头或执行符号查找,不需要做这些事情就可以启动应用,因此极大的提升了程序的启动速度
  • 最后dyld3还会启动一个闭包缓存服务,这里指的是将系统程序闭包直接加入到共享缓存

在这里插入图片描述

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

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

相关文章

使用Redis的SETNX命令实现分布式锁

什么是分布式锁 分布式锁是一种用于在分布式系统中控制多个节点对共享资源进行访问的机制。在分布式系统中&#xff0c;由于多个节点可能同时访问和修改同一个资源&#xff0c;因此需要一种方法来确保在任意时刻只有一个节点能够对资源进行操作&#xff0c;以避免数据不一致或…

Kafka Producer之幂等性

文章目录 1. 启用幂等性2. 底层变化3. 数据不重复4. 数据有序 幂等性通过消耗时间和性能的方式&#xff0c;解决乱序和重复问题。 但是只能保证同一生产者在一个分区中的幂等性。 1. 启用幂等性 //创建producerHashMap<String, Object> config new HashMap<>();…

ELK kibana查询与过滤

ELK kibana查询与过滤 1、通过布尔操作符 AND 、 OR 和 NOT 来指定更多的搜索条件(注意&#xff1a;这AND、OR、NOT必须大写)。例如&#xff0c;搜索message包含服务层关键词并且日志级别为INFO的条目&#xff0c;您可以输入 message:“服务层” AND level:“INFO”。 2、要搜…

Spring Boot集成syslog快速入门Demo

1.什么syslog&#xff1f; Syslog-ng是由Balabit IT Security Ltd.维护的一套开源的Unix和类Unix系统的日志服务套件。它是一个灵活的、可伸缩的系统日志记录程序。对于服务器日志集中收集&#xff0c;使用它是一个不错的解决方案。syslog-ng (syslog-Next generation) 是sysl…

STM32全栈嵌入式人脸识别考勤系统:融合OpenCV、Qt和SQLite的解决方案

1. 项目概述 本项目旨在设计并实现一个基于STM32的全栈人脸识别考勤系统。该系统结合了嵌入式开发、计算机视觉和数据库技术&#xff0c;实现了自动人脸检测、识别和考勤记录功能。 主要特点: 使用STM32F4系列微控制器作为主控制器采用OpenCV进行人脸检测和识别Qt开发跨平台…

Pytorch学习笔记day3——用神经网络学习一组函数

好的&#xff0c;我们开始吧。首先第一个问题&#xff0c;神经网络的本质是什么&#xff1f;是古典主义的人类的神经元吗&#xff1f;绝对不是&#xff0c;他只是一个优化函数 y f θ ( x ) y f_{\theta}(x) yfθ​(x) 这和小学学到的线性函数拟合并无本质区别。只是其中参数…

汇编教程1

本教程主要教大家如何使用vscode插件编写汇编语言&#xff0c;这样更方便&#xff0c;不用在32位虚拟机中编写汇编语言&#xff0c;后续的汇编实验代码都是使用vscode编写&#xff0c;话不多说&#xff0c;开始教学 安装vscode 如果已经安装过vscode&#xff0c;可以跳过这一…

Django 请求和响应

1、请求 &#xff08;1&#xff09;get请求 用户直接在浏览器输入网址&#xff0c;参数直接在url中携带 http://127.0.0.1:8000/login/?a1&b%221243%22 &#xff08;2&#xff09;post请求 在html使用post,login.html <!DOCTYPE html> <html lang"en&…

操作系统发展简史(Unix/Linux 篇 + DOS/Windows 篇)+ Mac 与 Microsoft 之风云争霸

操作系统发展简史&#xff08;Unix/Linux 篇&#xff09; 说到操作系统&#xff0c;大家都不会陌生。我们天天都在接触操作系统 —— 用台式机或笔记本电脑&#xff0c;使用的是 windows 和 macOS 系统&#xff1b;用手机、平板电脑&#xff0c;则是 android&#xff08;安卓&…

el-select选择器修改背景颜色

<!--* FilePath: topSearch.vue* Author: 是十九呐* Date: 2024-07-18 09:46:03* LastEditTime: 2024-07-18 10:42:03 --> <template><div class"topSearch-container"><div class"search-item"><div class"item-name&quo…

Hadoop-36 HBase 3节点云服务器集群 HBase Shell 增删改查 全程多图详细 列族 row key value filter

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; HadoopHDFSMapReduceHiveFlumeSqoopZookeeperHBase 正在 章节内容 上一节我们完成了&#xff1a; 集群的…

内部类+图书管理系统

内部类图书管理系统 1. 实例内部类1.1 实例内部类的结构1.2 实例内部类的一些问题1.2.1 如何在main中创建实例内部类对象&#xff1f;1.2.2 内部类成员变量被static修饰问题&#xff1f;1.2.3 内部类和外部类变量重名的调用问题&#xff1f;1.2.4 外部类访问内部类变量的问题 2…

浅谈C嘎嘎入门基础

看到这篇文章的童鞋或许会有疑惑&#xff0c;这不是之前 已经出过了吗&#xff0c;是的但是之前那篇文章可能不太好理解&#xff0c;因此我再写一篇便于大家理解的文章 那么上一篇文章已经帮大家过渡到C嘎嘎了&#xff0c;那么这篇文章我们继续讲解C嘎嘎的知识点。 C嘎嘎中的引…

【面试题】数据结构:堆排序的排序思想?

堆排序的排序思想&#xff1f; 堆排序是一种高效的排序算法&#xff0c;其基本思想是利用堆这种数据结构来实现排序。堆是一种特殊的完全二叉树&#xff0c;通常用数组来表示。堆排序的基本步骤如下&#xff1a; 1. 构建初始堆&#xff1a; 将待排序的数组转换成一个最大堆&a…

在RK3568上如何烧录MAC?

这里我们用RKDevInfoWriteTool 1.1.4版本 下载地址&#xff1a;https://pan.baidu.com/s/1Y5uNhkyn7D_CjdT98GrlWA?pwdhm30 提 取 码&#xff1a;hm30 烧录过程&#xff1a; 1. 解压RKDevInfoWriteTool_Setup_V1.4_210527.7z 进入解压目录&#xff0c;双击运行RKDevInfo…

Java案例斗地主游戏

目录 一案例要求&#xff1a; 二具体代码&#xff1a; 一案例要求&#xff1a; &#xff08;由于暂时没有学到通信知识&#xff0c;所以只会发牌&#xff0c;不会设计打牌游戏&#xff09; 二具体代码&#xff1a; Ⅰ&#xff1a;主函数 package three;public class test {…

【BUG】已解决:zipfile.BadZipFile: File is not a zip file

已解决&#xff1a;zipfile.BadZipFile: File is not a zip file 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市开发…

如何在项目中使用线程池自定义拒绝策略

首先呢&#xff0c;我设计了一个图表在我的项目里面&#xff0c;为了方便展示&#xff0c;我只修改一个字段&#xff0c;线程池设置参数 (2,4,30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),new RJ()); 然后通过循环持续的进行增加任务&#xff0c;目的修改数据库的…

机器人开源调度系统OpenTCS-6最新版本地源码运行

OpenTCS 项目使用 Gradle 而不是 Maven&#xff0c;那么需要使用 Gradle 来导入和构建项目。在 IntelliJ IDEA 中导入和运行使用 Gradle 的项目&#xff0c;可以按照以下步骤进行操作&#xff1a; 克隆 OpenTCS 源码 首先&#xff0c;克隆 OpenTCS 的源码到本地。您可以使用以…

Jenkins-zookeeper-docker-xxljob-rancher

文章目录 Jenkins实战1 新建任务需要的配置pipeline Zookeeper基础 Docker基础实操windows11 docker mysql DockerhouseDockerhubxxl-Job基础实战 Rancher基础思考 实战1 Rancher的某个namespace的scale为0 Jenkins 实战 1 新建任务需要的配置pipeline 该代码是Jenkinsfile&…