iOS高级进阶系列之-库(中)动态库探索

系列文章:OC底层原理系列OC基础知识系列Swift底层探索系列iOS高级进阶系列shell

补充

以前的文章iOS高级进阶系列之-库(上)静态库探索在后面讲到了Dead_Strip,也说道了-all_load-ObjC以及-force_load,这篇文章让你们以为-noall_load-all_load-ObjC以及-force_load能够控制Dead_Strip,其实不是这样的markdown

  • Dead_Strip在Xcode中是Build Settings 查找Dead

image.png

经过上面能够看出来这个Dead_Strip-noall_load-all_load-ObjC以及-force_load没有太大关系-noall_load、-all_load、-ObjC以及-force_load只是在链接静态库的时候起做用,而Dead_Strip只是给开发者提供了一种优化代码的方式架构

代码验证

image.png image.png

上图是test.m的代码,下图是文件,经过脚本将test.m编译成可执行文件。前面的文章介绍过:先将test.m编译成静态库test.o文件,再将TestExample.m编译成静态库TestExample.o,最后经过将test.o链接TestExample.o生成可执行文件。咱们直接执行脚本函数

当生成可执行文件后,查看macho信息 image.png 咱们能够发现这个里面没有任何关于TestExample内容,下面咱们对脚本进行改进 image.pngpost

脚本默认的为-noall_load,它不会将没有用过的代码放入到静态库优化

改完脚本再次执行脚本 image.pngui

发现多了不少东西,并且未使用的TestExample也导入到项目中spa

咱们看下dead_strip的定义 image.png3d

大意就是删除没有被入口点或者导出符号使用过的代码。咱们再改一下脚本code

image.png 运行下脚本,看下变化 image.png

这里看的是符号表,咱们看下以前代码

image.png

咱们发现global_function全局导出符号,可是未被使用,可是在导出的符号表中并没有global_function,这就是-dead_strip做用

下面咱们调用下global_function方法 image.png 在执行脚本,读取符号表 image.png

发现global_function在符号表

总结Dead_Strip

  • 1.没有被入口点使用就会被干掉
  • 2.没有被导出符号使用的也会被干掉

-all_load

下面咱们将TestExample代码放开,在执行脚本 image.png 再来看下符号表 image.png

发现都在符号表里

下面该下脚本和代码 image.png image.png 问题:此时符号表仍是否有TestExample内容 image.png

发现仍是存在的,缘由是OC代码动态运行的代码,因此不敢干掉权限不够

扩展

shell命令有个能够查看某个代码为何存在的指令:-why_live image.png

查看global_function为何会存在

image.png

解释为何global_function符号存在,它被从test.o来的,它被main函数使用,main函数又从test.o来

上面讲完后对Dead_Strip和-noall_load、-all_load、-ObjC以及-force_load没有太大关系有了更好的理解了吧

动态库

首先咱们来把test.m和AFNetworking动态库进行链接 image.png

下图为test.m代码,咱们引入了AFN,并初始化AFHTTPSessionManager image.png

  • 1.先将test.m编译成目标文件

image.png

静态库中介绍过命令的用法,这里再也不说明

image.png

  • 2.链接AFN动态库

image.png image.png

链接动态库链接静态库使用的指令都是同样

  • 3.运行test可执行文件

image.png

当咱们运行,当即发生了错误

那究竟为何会出错了?咱们下面探究一下

动态库原理

准备以前的代码 image.png image.png

  • 1.为了节省时间,写一个脚本去编译可执行文件

image.png

红框的-dynamiclib就是告诉编译器我下面要执行的是编译动态库

  • 2.执行脚本
    • 执行脚本报下面错误 image.png
    • 执行chmod +x ./build.sh权限

再次运行脚本 image.png image.png

  • 3.运行可执行文件

image.png 咱们发现仍是报和上面相同的错误,为何呢,是否是脚本命令是否是有问题?

分析原理

静态库的文章已经说过静态库.o的合集(文章直接演示将.o文件直接改为静态库能够运行),可是动态库是一个连接编译最终产物。这也就意味着静态库能够经过连接变成动态库

静态库连接编译为动态库

  • 1.修改脚本,将修改部分贴出来

image.png

  • 2.跑脚本

image.png

报错了,就是在连接的过程当中找不到_TestExample导出符号

  • 3.咱们查找一下动态库的符号表

image.png

发现导出符号表空的,什么都没有

  • 4.为何是空的呢?咱们看下脚本

image.png

前面介绍编译期链接的时候默认执行的是-noall_load,由于动态库没有使用.a内容,因此不加载。那么是否是这个缘由形成的呢?咱们改下脚本

  • 5.添加-all_load

image.png

  • 6.再次运行脚本

image.png

发现符号表有内容

  • 7.再次跑可执行文件

image.png

仍是熟悉的味道,是否是有点无语,问题究竟出在哪里呢?下面就来介绍动态库的另外一个特性

总结

  • 1.静态库是.o文件的合集
  • 2.动态库是.o文件连接事后的产物
  • 3.静态库能够经过连接生成动态库
  • 4.动态库也就是最终产物,这也是动态库没法合并缘由

动态库解析

下面咱们连连接下动态库,咱们下面来说解LoginApp怎么去连接动态库SYCSSColor动态库 image.png 咱们再看看SYCSSColor里面的内容 image.png

tbd格式

image.png

这个是连接路径

  • SYCSSColor.tbd文件直接拖到项目中,不在xcconfig中写编译连接,看看会发生什么
    image.png image.png image.png

经过上面咱们看到了,咱们引入了我SYCSSColor头文件,使用了代码而且编译成功

咱们只是将SYCSSColor.tbd文件放入项目中,那么什么是tbd格式呢?

  • 1.tbd全称是text-based stub libraries,本质上就是一个YAML描述的文本文件
  • 2.他的做用是用于记录动态库的一些信息,包括导出的符号动态库的架构信息动态库的依赖信息
  • 3.用于避免真机开发过程当中直接使用传统的dylib
  • 4.对于真机来讲,因为动态库都是在设备上,在Xcode上使用基于tbd格式伪framework能够大大减小Xcode的大小

分析tbd格式

咱们查看下SYCSSColor.tbd的内容, image.png

  • 1.第9行exports导出的意思
  • 2.第11行symbols是符号的意思,也就意味着11-14行都是导出符号
  • 3.objc-classes是objc类的集合

经过上面咱们能够知道咱们用脚本去连接库的是用到-L,-l连接是符号,也就是咱们只须要知道符号所在的位置不须要知道源码位置。上面编译经过了,下面就运行看看 image.png 上面看到直接崩溃,为何呢?这是由于动态库是动态连接的,在运行的时候,由dyld动态加载动态库,在加载的时候,它回去找这个符号真实地址的时候,找不到了,而静态库编译的时候就已将符号归放在一块儿,不会动态加载

tbd怎么生成

Xcode的Build Settings搜索text image.png

原理:就是经过拼上一些参数,来扫描Headers里面的头文件,而后把这个符号写到文件里去

查找Framework实际位置

image.png image.png

  • 1.下面咱们使用脚本编译,先看framework文件中的脚本

image.png

  • 2.运行脚本

image.png

  • 3.咱们看到动态库是.dyld的形式,其实是没有什么标识的,下面咱们吧生成的删除,重新生成一份叫TestExample

image.png image.png

  • 4.此时咱们的Frameworks准备好了,下面就是将test.m和咱们的Frameworks进行连接

image.png

  • 5.运行脚本

image.png

这就说明咱们前面制做的.framework成功

  • 6.运行验证可执行文件

image.png 仍是相同错误,为何还会有这样的错误,这就要从dyld加载动态库提及。以下图: image.png

  • 1.这里的Mach-o至关于咱们的可执行文件Test
  • 2.Test的Mach-o有个专门的LC_LOAD_DYLIB,它里面有咱们须要用到的动态库路径`
  • 3.当它找不到这个路径的时候就会报错

下面咱们看下路径究竟是什么东西 image.png 咱们发现咱们Test中一共使用了5个动态库,那么使用哪5个呢? image.png

-A 5 意思就是找到动态库后多现实5行

咱们看到咱们使用的动态库中name字段就是该动态库路径,可是咱们导入的动态库TestExample路径只有一个名字,别的什么都没有,这样确定找不到TestExample。 image.png 下面咱们就来解决这个问题

动态库路径

动态库有一个专门的地方保存本身的路径,也就是说动态库的路径是保存在本身的Mach-o中的,下面咱们就来查看下这个路径 image.png

-A是向下展现向上展现时-B

咱们发现这个地方的路径就没有给对,我要给对正确的路径,外部有个命令来更改路径名称:install_name_tool image.png image.png

咱们看到install_name_tool给的解释就是改变更态库install name

下面咱们就使用一下,来改变路径 image.png

发现报错,这是由于咱们并没有告诉编译期往哪里加这个路径

image.png

发现此时的路径已经更改了,那咱们再来执行一次build.sh脚本,从新生成test文件,在查看下引入的动态库 image.png

咱们发现此时的路径已经改变了,那么此时咱们再运行可执行文件

image.png

此时并无报错,成功运行了起来。也说明咱们此时将test.m成功的链接了TestExample动态库

优化

脚本优化

上面咱们讲了是生成动态库后去更改路径的,那么能够在生成前进行更改呢?答案是固然能够,咱们在脚本中更改,咱们使用命令-install_name,先看下install_name解释 image.png

就是添加路径

image.png 【问题】上面咱们看到给到的是一个绝对路径,绝对路径有一个很大的问题,当咱们作好SDK后给别人使用,那么这个路径就会改变,别人就须要更改路径,走着没法调用成功

路径优化

上面的问题怎么解决,这就须要双方约定好规则,什么规则,看下图: image.png

A使用B,B提供一个TestExample路径(Frameworks/TestExample.framework/TestExample),而A给B提供一个使用者路径(也就是test的路径)而后B给拼接起来,此时路径不久完整了嘛!

@rpath

这就牵扯到@rpath@rpath:Runpath search Paths! dyld搜索路径。运行时@rpath指示dyld按顺序搜索路径列表,以找到动态库@rpath保存一个或多个路径的变量! 也就是谁链接我谁给我提供@rpath,下面咱们直接修改路径 image.png

此时咱们看到路径已经修改完了

下面就是须要test给TestExample提供@rpath了,怎么提供,可执行文件test的mach-o中存一个@rpath,在链接的时候,将其提供给TestExample。 image.png

此时咱们在test可执行文件中搜索rpath,发现不存在,说明此时若是咱们去连接TestExample是找不到位置的,此时须要咱们手动添加

咱们查找一下命令 image.png

上面是替换rpath,下面的是添加rpath到指定的Mach-o

image.png

这时候就完成了,此时咱们从新运行可执行文件

image.png

成功了

路径再优化

咱们上面看到咱们打印的path,依然是一个绝对路径,那么此时有么有什么办法,改成相对路径呢?这里系统给咱们提供了两个方法:

  • 1.@executable_path:表示可执行程序所在的目录,解析为可执行文件的绝对路径。
  • 2.@loader_path:表示被加载的Mach-O所在的目录,每次加载时,均可能被设置为不一样的路径,由上层指定。

@executable_path

也就是说若是用了@executable_path,无论在谁的电脑上它都指向执行程序所在的路径 下面咱们来执行如下,由于咱们已经设置过了rpath,因此须要替换,上面已经锁了替换方法,来进行操做 image.png 此时咱们再看下test的rpath路径 image.png 已经将绝对路径修改为@executable_path,运行一下test文件,验证一下 image.png 咱们看到运行成功,说明咱们的修改是正确的

@loader_path

咱们看下这种状况,test中使用的framework中包含TestExample.framework,而TestExample.framework的framework中包含TestExampleLog.framework image.png 下面咱们编译一遍,从后往前编译

  • 1.先编译TestExampleLog.framework

image.png

  • 2.编译TestExample.framework

image.png image.png

咱们发现连接的TestExampleLog.framework的name是不正确的,并且自身的name一样不正确

  • 3.编译test

image.png 此时编译完成,但此时运行时报错的,由于路径不对,下面咱们来改一下

  • 4.改动TestExampleLog.framework中的脚本

image.png

加入name,地址使用rpath,再运行脚本

image.png 此时的路径就对了

  • 5.改动TestExample.framework的脚本

image.png 运行脚本 image.png

  • 6.改动test的脚本

test是提供rpath路径image.png 运行脚本 image.png

  • 7.运行可执行文件

image.png

咱们发现报错了,由于找不到TestExampleLog的路径,也就是咱们在TestExample.framework的脚本中配了本身的路径,可是并无配TestExampleLog的路径

  • 8.修改TestExample.framework的脚本(截取部分)

image.png 运行脚本 image.png 下面咱们手动看一下 image.png

咱们看到这个路径比较长,下面咱们再来生成下test,若是上面写的都是对的,那就能运行起来

image.png

咱们看到这样运行时运行起来了,因此说咱们以前配置的路径是对的

可是有个问题,咱们上面的路径太长了,下面就用到了@loader_path,上面讲了那么多,一方面为了给你们属性下脚本,第二是让你们更好的理解。

  • 9.再次改脚本

image.png 在运行脚本,执行可执行文件 image.png

查看cocoaPod

为了加深@executable_path印象,咱们能够看看cocoaPods的xcconfig文件 image.png

此时的name是一个名称

当咱们编译代码,看下可执行文件的包内容,咱们发现动态库都会存放在framework文件中,也就是上面设置的文件里 image.png

总结

到此最难的连接动态库的一种方式已经说完了,连接动态库因为动态加载,因此必定要指定好路径,否咋就会报错。

  • 1.咱们介绍了经过install_name_tool更改路径
  • 2.为了优化路径,咱们引入@rpath
  • 3.-rpath lod new 是替换路径-add_rpath是将路径添加到指定位置
  • 4.为了再优化,咱们又引入了@executable_path@loader_path

上面讲了能够替换路径,那么就想到了破解,由于咱们能够改变路径,将咱们本身写的动态库做为目标软件的依赖达到破解的目的。咱们这么作事改变了Mach-o,以前讲过Mach-o只有签名后才会被苹果系统认可,因此再破解软件是都会调用一句命令:code sign --force --deep --sign - 这句命令也就是强制打一个签名

实际应用

当咱们作动态库时,能够在Xcode中设置咱们上面说的命令 image.png 咱们在搜下rpath image.png 咱们上面的路径设置均可以在Xcode去处理

扩展

咱们上面说的test是能够使用TestExample的方法,那么他test能够使用TestExampleLog的代码吗? 咱们看下导出符号 image.png 咱们看到这里面没有TestExampleLog的导出符号,因此不能使用,那么怎么才可以使用呢?在以前在讲符号的时候,说过从新导出符号,下面说下命令:reexport image.png

从新导出framework

image.png

从新导出TestExampleLog,运行脚本

image.png

多了一个从新导出符号TestExampleLog,此时就可使用TestExampleLog代码

  • 验证,咱们将test.m代码作以下改动,若运行打印没有问题,那就说明咱们以前的是对的

image.png

  • 运行脚本,运行可执行文件

image.png 咱们更改的代码以及打印都能正常运行,验证了咱们上面改的东西

写到最后

动态库写的内容比较多,写了一周多时间!写了将近6000字,写的比较细,都是把本身探索过程记录下来,但愿你们可以按着文章去实操一遍,这部分也比较无聊,可是这是高阶必走的路,在过程当中学会shell语言,会写脚本,这部分都是基础,后面会介绍项目高阶应用:静态库合并,动态库优化等内容。有什么疑问能够在下面留言,也但愿你们多多交流,点赞!

相关文章
相关标签/搜索