目录

为什么是编译器

LLVM和LLD

LLVM 的编译​​​​​​​的几个主要过程

链接器做了什么

符号链接

动态库链接

实操演示动态库链接​​​​​​​


        链接器最主要的作用,就是将符号绑定到地址上。

为什么是编译器

看看两种执行代码的方式

编译执行

编写的代码是先使用编译器把代码编译成机器码,然后直接在 CPU 上执行机器码的。

解释执行

在运行时解释执行代码,获取一段代码后就会将其翻译成目标代码(就是字节码(Bytecode)),然后一句一句地执行目标代码。

  • 采用编译器生成机器码执行的好处是效率高,缺点是调试周期长。
  • 解释器执行的好处是编写调试方便,缺点是执行效率低。

所以,苹果公司希望 iPhone 的执行效率更高、运行速度能达到最快,使用的是编译执行方式。

LLVM和LLD

苹果公司使用的编译器是 LLVM,相比于 Xcode 5 版本前使用的 GCC,编译速度提高了 3 倍。同时,苹果公司也反过来主导了 LLVM 的发展,让 LLVM 可以针对苹果公司的硬件进行更多的优化。

LLVM 是编译器工具链技术的一个集合。而其中的 lld 项目,就是内置链接器。编译器会对每个文件进行编译,生成 Mach-O(可执行文件);链接器会将项目中的多个 Mach-O 文件合并成一个。​​​​​​​lld就是我们本文的主角,从这里我们可以了解到它在整个编译过程中的充当的角色。

LLVM 的编译​​​​​​​的几个主要过程

LLVM 的编译过程非常复杂。如果你有兴趣的话,可以通过官方手册查看完整的编译过程。

  • 首先,你写好代码后,LLVM 会预处理你的代码,比如把宏嵌入到对应的位置。
  • 预处理完后,LLVM 会对代码进行词法分析和语法分析,生成 AST 。AST 是抽象语法树,结构上比代码更精简,遍历起来更快,所以使用 AST 能够更快速地进行静态检查,同时还能更快地生成 IR(中间表示)。
  • 最后 AST 会生成 IR,IR 是一种更接近机器码的语言,区别在于和平台无关,通过 IR 可以生成多份适合不同平台的机器码。对于 iOS 系统,IR 生成的可执行文件就是 Mach-O。

链接器做了什么

假设不用链接器会怎样?

        如果地址和符号不做绑定的话,要让机器知道你在操作什么内存地址,你就需要在写代码时给每个指令设好内存地址。写这样的代码的过程,就像你直接在和不同平台的机器沟通,连编译生成 AST 和 IR 的步骤都省掉了,甚至优化平台相关的代码都需要你自己编写。

这件事儿看起来挺酷,但可读性和可维护性都会很差,比如修改代码后对地址的维护就会让你崩溃。而这种“崩溃”的罪魁祸首就是代码和内存地址绑定得太早。

另外,绑定得太早除了可读性和可维护性差之外,还会有更多的重复工作。因为,你需要针对不同的平台写多份代码,而这些代码本可以通过高级语言一次编译成多份。

既然这样,那我们应该怎么办呢?我们首先想到的就是,用汇编语言来让这种绑定滞后。随着编程语言的进化,我们很快就发现,采用任何一种高级编程语言,都可以解决代码和内存绑定过早产生的问题,同时还能扫掉使用汇编写程序的烦恼。

符号链接

链接器在功能层面做了哪些

1. 完成符号(变量名、函数名)和地址绑定

Mach-O 文件里面的内容,主要就是代码和数据:代码是函数的定义;数据是全局变量的定义,包括全局变量的初始值。不管是代码还是数据,它们的实例都需要由符号将其关联起来。

而链接器的作用,就是完成变量、函数符号和其地址绑定这样的任务。而这里我们所说的符号,就可以理解为变量名和函数名。

​​​​​​​2. 把项目中的多个 Mach-O 文件合并成一个​​​​​​​
        项目中文件之间的变量和接口函数都是相互依赖的,所以这时我们就需要通过链接器将项目中生成的多个 Mach-O 文件的符号和地址绑定起来。

没有这个绑定过程的话,单个文件生成的 Mach-O 文件是无法正常运行起来的。因为,如果运行时碰到调用在其他文件中实现的函数的情况时,就会找不到这个调用函数的地址,从而无法继续执行。

链接器在链接多个目标文件的过程中,会创建一个符号表,用于记录所有已定义的和所有未定义的符号。链接时如果出现相同符号的情况,就会出现“ld: dumplicate symbols”的错误信息;如果在其他目标文件里没有找到符号,就会提示“Undefined symbols”的错误信息。

3. 理清有哪些函数是没被调用的,并自动去除掉

链接器在整理函数的调用关系时,会以 main 函数为源头,跟随每个引用,并将其标记为 live。跟随完成后,那些未被标记 live 的函数,就是无用函数。然后,链接器可以通过打开 Dead code stripping 开关,来开启自动去除无用代码的功能。并且,这个开关是默认开启的。

链接器在代码层面主要做了哪些(有点没太理解?)

  • 去项目文件里查找目标代码文件里没有定义的变量。
  • 扫描项目中的不同文件,将所有符号定义和引用地址收集起来,并放到全局符号表中。
  • 计算合并后长度及位置,生成同类型的段进行合并,建立绑定。
  • 对项目中不同文件里的变量进行地址重定位。

动态库链接

        链接的共用库分为静态库和动态库:静态库是编译时链接的库,需要链接进你的 Mach-O 文件里,如果需要更新就要重新编译一次,无法动态加载和更新;而动态库是运行时链接的库,使用 dyld 就可以实现动态加载。

动态库链接过程

        Mach-O 文件是编译后的产物,而动态库在运行时才会被链接,并没参与 Mach-O 文件的编译和链接,所以 Mach-O 文件中并没有包含动态库里的符号定义。也就是说,这些符号会显示为“未定义”,但它们的名字和对应的库的路径会被记录下来。运行时通过 dlopen 和 dlsym 导入动态库时,先根据记录的库路径找到对应的库,再通过记录的名字符号找到绑定的地址。

dlopen 会把共享库载入运行进程的地址空间,载入的共享库也会有未定义的符号,这样会触发更多的共享库被载入。dlopen 也可以选择是立刻解析所有引用还是滞后去做。dlopen 打开动态库后返回的是引用的指针,dlsym 的作用就是通过 dlopen 返回的动态库指针和函数符号,得到函数的地址然后使用。

使用 dyld 加载动态库,有两种方式

        1. 程序启动加载时绑定

2. 符号第一次被用到时绑定(可以减少启动时间)

加载过程开始会修正地址偏移,iOS 会用 ASLR 来做地址偏移避免攻击,确定 Non-Lazy Pointer 地址进行符号地址绑定,加载所有类,最后执行 load 方法和 Clang Attribute 的 constructor 修饰函数。每个函数、全局变量和类都是通过符号的形式定义和使用的,当把目标文件链接成一个 Mach-O 文件时,链接器在目标文件和动态库之间对符号做解析处理。​​​​​​​(不太理解)

【补充说明如下】

  • 一种是,在程序开始运行时通过 dyld 动态加载。通过 dyld 加载的动态库需要在编译时进行链接,链接时会做标记,绑定的地址在加载后再决定。
  • 第二种是,显式运行时链接(Explicit Runtime Linking),即在运行时通过动态链接器提供的 API dlopen 和 dlsym 来加载。这种方式,在编译时是不需要参与链接的。不过,通过这种运行时加载远程动态库的 App,苹果公司是不允许上线 App Store 的,所以只能用于线下调试环节。关于这种方式的适用场景,在文章“App 如何通过注入动态库的方式实现极速编译调试?​​​​​​​”中和你举例说明过。

实操演示动态库链接​​​​​​​      

        dyld 做了这么几件事儿:

  • 先执行 Mach-O 文件,根据 Mach-O 文件里 undefined 的符号加载对应的动态库,系统会设置一个共享缓存来解决加载的递归依赖问题;
  • 加载后,将 undefined 的符号绑定到动态库里对应的地址上;
  • 最后再处理 +load 方法,main 函数返回后运行 static terminator。

​​​​​​​调用 +load 方法是通过 runtime 库处理的。你可以通过一个可编译的开源 runtime 库来了解 runtime,从源码层面去看程序启动时 runtime 做了哪些事情。

最后dyld 是开源的,地址是:https://github.com/opensource-apple/dyld​​​​​​​​​​​​​​

​​​​​​​        《程序员的自我修养》里有非常详细的关于链接的内容,对于想学习这块知识的同学是很好的资料。

链接器:绑定符号到地址上相关推荐

  1. 链接器、链接过程及相关概念解析

    文章目录 1. 编译器驱动程序 2. 目标文件 2.1 可重定位目标文件(.o) 2.2 可执行目标文件(无后缀) 2.3 共享目标文件(.dll和.so) 3. 链接器的任务 3.1 符号解析(sy ...

  2. C++链接器工具错误:LNK2001, LNK2019

    这是归属于链接器工具错误 这一类. 无法解析的外部符号"symbol" 代码引用了链接器无法在库和对象文件中找到的内容(如函数.变量或标签). 可能的原因 代码请求的内容不存在(例 ...

  3. iOS高级进阶系列之项目开发基础(上)多环境配置,Mach-O与链接器。

    前言 最近对项目进行优化,就顺便写一些日常开发中会用到的中高级开发技巧.这篇文章聊一下下面三个内容:多环境配置,Mach-O与链接器,Symbol. 多环境配置 聊到多环境配置,我们先说几个概念 上图 ...

  4. 彻底理解链接器:二,符号决议

    符号决议 在这个过程当中,链接器需要做的工作就是确保所有目标文件中的符号引用都有唯一的定义.要想理解这句话我们首先来看看一个典型的c文件里都有些什么. c源文件中都有什么 如图所示是一个典型的c源文件 ...

  5. 链接器怎样使用静态库来解决符号引用

    链接器在根据命令行中输入的可重定位目标文件和静态库的顺序从左到右的扫描这些文件.在这个扫描中,链接器会维护一个集合E,该集合包含了将来要被合并生产可执行文件的所有可重定位目标文件:维护了一个集合U,包 ...

  6. 世界上能做一个链接器的人坐不满一屋!做个编译器和链接器应该看的书籍

    <链接器和加载器> 作者: John R.Levine 出版社: 北京航空航天大学出版社 原作名: Linkers and Loaders 译者: 李勇 <编译原理> 作者: ...

  7. 链接器(linker)的作用——CSAPP第7章读书笔记

    首先说说我为什么要去读这一章.这个学期开OS的课,在Morden Operating System上读到和Process有关的内容时看到这样一句话:"Process is fundament ...

  8. 深入探究VC —— 链接器link.exe(4)【转】http://blog.csdn.net/wangningyu/article/details/4849452...

    在程序编译完成后,生成的文件是以.obj为扩展名的对象文件,link.exe是将这些对象文件与库链接起来以创建可执行文件或动态链接库文件的工具. link.exe的输入文件包括obj文件.lib文件. ...

  9. C编译器、链接器、加载器详解

    一.概述 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接是把目标 ...

最新文章

  1. 基于GAN的自动驾驶汽车语义分割
  2. 《LeetCode力扣练习》第6题 C语言版 (做出来就行,别问我效率。。。。)
  3. JWT: 基于Token的验证
  4. c语言学生信息系统 完整版,C语言 学生信息管理系统(完整版).docx
  5. python排序链表_合并K个排序链表
  6. 一个浪漫又悲情的爱情故事...
  7. OJ1057: 素数判定(C语言经典列题,判断变量的应用)
  8. 使用OmniDB数据库管理工具,管理Oracle/MariaDB/PostgreSQL等关系型数据库
  9. ffmpeg获取设备支持的分辨率_Qt音视频开发6-ffmpeg解码处理
  10. Android实战开发通用流行框架大全
  11. session超时问题
  12. 低压电力线载波通信原理
  13. 一步一步教你写股票走势图——分时图四(高亮联动)
  14. 酒店管理系统数据库SQl设计思路
  15. Python 123精品卷一
  16. 全网通用Python点赞器(俗称刷分机器),想知道原理吗?看完本文你自己也能写个
  17. 舌尖上的创业者:吃货小分队CEO Amy Duan|伯斯人物志
  18. 假币问题 (java)居然有假币!!!
  19. 2020 年 HackerEarth 调查:Go 语言成为最受欢迎的语言(内含 Go 语言图谱下载)
  20. Qt入门教程【STL篇】QStack栈

热门文章

  1. python 串口接收字符_python串口接收数据
  2. 简介三种垃圾回收机制:分代复制垃圾回收,标记垃圾回收,增量垃圾回收
  3. 新2022年合肥市工业设计中心奖励申报条件及认定管理办法
  4. linux调节字体大小加粗,Linux下修改终端字体颜色和大小
  5. 趣图:初级程序员 VS 高级程序员
  6. 解决“System clock has been set back“、“FlexNet Licensing error:-88309“报错
  7. 【Java 责任链模式实例】
  8. 已从事软件测试10年,入行迷茫的你是否具备这个行业的硬+软实力!
  9. Java黑皮书课后题第6章:**6.30(游戏:双骰子)掷双骰子游戏是某场景中非常流行的骰子游戏。编写程序,玩这个游戏的变种
  10. windows10 + vs2017 + CERTI3.5.1安装说明