0%

静态库与动态库

Object File

object file是个有结构的位元块。这些位元块包含程序代码】准备给Linker和Loader使用的相关信息。

查看object file:

1
objdump -macho -section-headers /bin/ls

object file的形式:

  • Relocatable:包含可以在编译时被其他Relocatable链接的代码和数据,以生成Executable。多个Relocatable可被封装成.a(archive)静态库(static library、static archive)。
  • Executable:可以载入内存的执行的指令集合。链接器会把静态库中的代码给定一个固定的load地址,并包含(copies and relocates)进Executable中。且每个Executable使用静态库都要拷贝一份静态库。
  • Shared:一种特殊形式的Relocatable,类似动态库。不并入任何Executable,可在多个Executable之间共享。
  • Bundle:在macOS中长作为插件使用。

macOS支持的可执行格式

可执行格式 magic 用途
脚本 \x7FELF 主要用于 shell 脚本,但是也常用语其他解释器,如 Perl, AWK 等。也就是我们常见的脚本文件中在 #! 标记后的字符串,即为执行命令的指令方式,以文件的 stdin 来传递命令
通用二进制格式 0xcafebabe 0xbebafeca 包含多种架构支持的二进制格式,只在 macOS 上支持
Mach-O 0xfeedface(32 位) 0xfeedfacf(64 位) macOS 的原生二进制格式

通用二进制格式

通用二进制格式(Universal Binary、Fat Binary)。Apple提出这个是为了解决一些历史问题。macOS,更确切地说是OS X,最早是基于PPC架构的,后来才移植到Intel架构(OSX Tiger 10.4.7开始),通用二进制格式可以在PPC和x86两种处理器上执行。即,对多架构二进制文件的打包集合文件。

macOS的多架构二进制文件就是适配不同架构的Mach-O文件。

Mach-O

Mach-O(Mach Object File Format)是苹果平台OS上的可执行文件格式,类似于Linux和大部分UNIX的原声格式ELF(Extensible Firmware Interface)。

文件格式

mach-o

Mach-O格式主要由以下3部分组成:

  • Mach-O头(Mach-O Header):描述了Mach-O的CPU架构、文件类型、加载命令等信息。
  • 加载命令(Load Command):描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示。
  • 数据(Data):存储每个段(Segment)的数据。段拥有一个或多个Section,存储数据和代码,与ELF文件中的段类似。

参考

静态库

静态库(Static Libraries),多个目标文件(object file)的打包集合。

特点:

  • 静态库会直接嵌入到App的Mach-O中。
  • 编译时已经链接,启动时不需要二次查找。因此App启动更快。
  • 每个Executable使用都要拷贝一份静态库。
图1 使用静态库的应用

构建设置

  • Linking-Math-O Type: Static Library
  • Dead Code Stripping: No

动态库

动态库(动态链接库、Dynamic Libraries、Shared Library、Shared Object),同样也是目标文件的集合,与静态库区别的是嵌入App的方式和在App加载的方式。

特点:

  • 以独立文件嵌入App包中。
  • App的Mach-O中只包含其引用信息,使用的时候才进行动态链接和加载。可在两个时机载入,并动态分配一段地址:
    • App载入时(load time):启动时加载,称为动态链接库。
    • App运行时(run time):启动后加载,称为动态加载库。
  • 多个Executable使用都不会进行拷贝。可独立更新。

只有系统库或在macOS上的动态库才有以上自由选择载入时机的特性,在iOS中,只能通过Embedding Frameworks的方式使用动态库,并在启动时载入与链接动态库。而其链接动态库也是造成启动时间长的原因。

在iOS的多个Executable可以是App和Extension,他们可以共用包中的framework。

列出所有动态链接的库:

1
otool -L <PathToArchive>/Products/Applications/<AppName>.app/<AppBinary>
图2 使用动态库的应用程序

区分

使用file命令输出对应的Mach-O信息:

静态库:current ar archive random library

动态库:dynamically linked shared library

性能差异

待定。

framework

framework时一个有着特定结构的文件夹,里面包含各种共享的资源。如:静态库/动态库、头文件、模块信息和资源(例如storyboard、xib、图像文件和本地化字符串)。

其中framework里面的object file类型决定了其可用的资源:

静态库:只能使用其中的头文件、模块信息。

动态库:可全部使用,即除了头文件、模块信息,还能嵌入资源。

集成方式

集成到App时有以下两个选项:

  • Linked:仅链接。启动时链接则勾选,否则要运行时才链接则不勾选。
  • Embedded:拷贝到App包中的framework目录。

对于静态库和动态库framework有不同的选择:

静态库:Linked。因为静态库已经拷贝到App的Executable Mach-O文件中,Embed是没意义的,虽然Xcode允许这样做。

动态库:Linked(iOS可选,macOS必选) + Embedded

动态更新动态库

这里的动态更新是针对于已编译的包的动态更新,不是运行时的动态更新。

首先对于iOS,上App Store的App是不允许动态更新动态库的,因为在iOS中使用动态库只能通过framework形式,而上App Store会进行签名,其中就包含对framework的哈希,即上架后,就不允许改变其framework。而动态更新framework的方式可以在in house和develop模式下使用。

iOS 利用 Framework 进行动态更新.md

CocoaPods中的使用

Podfile:

  • use_frameworks!:当前范围使用framework。可以指定动态库、静态库。
    • use_frameworks! :linkage => :dynamic
    • use_frameworks! :linkage => :static

Podspec:

  • spec.static_framework = true:当使用use_frameworks!标记时,使用静态库framework。
  • 引用系统库:
    • spec.frameworks = 'QuartzCore', 'CoreData'
    • spec.libraries = 'xml2', 'z'
  • 引用外部库:
    • spec.vendored_frameworks = 'MyFramework.framework', 'TheirFramework.framework'
    • spec.vendored_libraries = 'libProj4.a', 'libJavaScriptCore.a'

动态库巧用

减少静态库的依赖拷贝

通过前面我们知道可执文件(主程序或者动态库)在构建的链接阶段,遇到静态库,吸附进来;遇到动态库,打标记,彼此保持独立。

正因为动态库是保持独立的,那么可以自定义一个动态库把依赖的静态库吸附进来。对外整体呈现的是动态库特性。其他的组件依赖我们自定义的动态库,由于隔离性的存在,不会出现问题。

这个思路在处理项目组件化的时候非常有用,尤其是在使用Swift的项目中。

处理静态库之间的符号冲突

背景:需要知道,在打包IPA的时候,最终静态库会被连接到最终的那个可执行文件中。所以如果多个静态库拥有了相同的符号必定会产生符号冲突。

静态库的符号和动态库库符号可以隔离,进而避免了链接时产生的符号冲突。

这一点在处理一些由于底层三方库源码不能手动修改(比如boringssl与openssl)的时候,非常有用。

参考

欢迎关注我的其它发布渠道