文章目录

  • 背景
  • 如何编译并调试 objc 运行库
  • 1、编译 objc 运行库
  • 2、理解 dyld 的动态链接过程
  • otool
  • 3、复制并调整默认的 objc 路径
  • install_name_tool
  • mac 特殊问题
  • 总结


背景

本文写作背景是有位网友求助“为什么自己编译的 objc 运行时,在 mac 设备上无法进行调试?”

考虑到更多的同学是 iOS 开发工程师,本文将尝试更加广度和深度的角度讲解 iOS 的相关技巧,相信看完本篇本章后,对 mac 设备的调试也不在话下。

效果图:

iOS objc 存储数据到iCloud_iOS


OK,下面开始进入正题。

如何编译并调试 objc 运行库

1、编译 objc 运行库

首先,挡在开发者面前的第一步是编译 objc 运行库。这份工作和普通的 iOS 日常开发遇到的类似(通常需要解决一些头文件缺失导致的编译失败等问题)。考虑到网络上的文章都会重点讲解如何解决头文件问题。本文不再做详细解释。读者可以根据自己的系统版本选择合适 objc 版本编译。

2、理解 dyld 的动态链接过程

在 iOS 的世界中,动态库都是通过 dyld 完成动态链接工作的,如下所示,默认情况下,libobjc 是作为系统库的一部分被 map 并在程序运行时动态进行 load 操作。

dyld: Using shared cached for /usr/lib/libobjc.A.dylib

dyld: loaded: <20AC082F-2DB7-3974-A2D4-8C5E01787584> /usr/lib/libobjc.A.dylib

从上面的日志,我们很容易发现,dyld 是通过 shared cache 加载 libobjc 库,并且 libobjc 的原始路径是 /usr/lib/libobjc.A.dylib

shared cache 是一种提高 APP 的启动速度的优化手段。通过将所有的系统库打包为单一的共享库文件,可以将系统库间的依赖关系进行缓存。

如果读者对 mach-o 文件格式熟悉的话,很容易发现这个路径是写在 mach-o 的 load commands 部分。

otool

otool 是一款常见的调试工具,在 Mac 最新的命令行工具中,它实际上是由 llvm-objdump 伪装后的工具。

通过 otool -l file-path,可以将合法的 mach-o 文件的 load command 打印到控制台。

我们使用 otool 工具验证一下:

otool -l ~/Library/Developer/Xcode/DerivedData/testLock-fqojwzmsigqvkigfdphosmtuudwt/Build/Products/Debug-iphoneos/testLock.app/testLock | grep libobjc -C 3
Load command 15
          cmd LC_LOAD_DYLIB
      cmdsize 56
         name /usr/lib/libobjc.A.dylib (offset 24)
   time stamp 2 Thu Jan  1 08:00:02 1970
      current version 228.0.0
compatibility version 1.0.0

上面的信息表明,第 15 个 load commandLC_LOAD_DYLIB(load a dynamically linked shared library) ,文件位置是 /usr/lib/libobjc.A.dylib

3、复制并调整默认的 objc 路径

为了让 dyld 加载自行编译的 libobjc,我们首先需要将 libobjc 复制到安装包中。

iOS objc 存储数据到iCloud_加载_02

然后,让 load command 的路径指向这个二进制文件。

install_name_tool

install_name_tool 同样是一款常用的调试工具。它可以实现 mach-o文件的动态库的增删改操作。

我们使用 install_name_tool 搭配 otool 工具验证一下。

install_name_tool -change /usr/lib/libobjc.A.dylib @rpath/libobjc.A.dylib ~/Library/Developer/Xcode/DerivedData/testBlock-hcugstfyttogsqgdxjjypicnuusw/Build/Products/Release-iphoneos/testBlock.app/testBlock

iOS objc 存储数据到iCloud_加载_03

很明显, libobjc 的链接位置发生了改变 @rpath/libobjc.A.dylib

ps.为了方便开发,我们可以通过以下方法实现 Xcode 编译完成后自动替换指向路径。

install_name_tool -change /usr/lib/libobjc.A.dylib @rpath/libobjc.A.dylib $TARGET_BUILD_DIR/$EXECUTABLE_PATH

iOS objc 存储数据到iCloud_运行库_04

mac 特殊问题

iOS 的原理讲解完毕了,我们切换一下思路,回到网友遇到的 Mac 无法调试的问题。

默认情况下,iOS 的应用都会通过对二进制签名保证安全,但是,Mac 却提供了关闭关操作的开关:Disable Library Validation Entitlement官方链接

如下图所示,只有当图中的 Disable Library Validation 被勾选时,我们才可以让自己的 Mac 程序加载任意的二进制文件。否则,就只能加载同一个开发者账号签名的文件。

iOS objc 存储数据到iCloud_运行库_05

总结

本文讲解了调试 libobjc 的原理,并简单介绍了 install_name_tool 调整库路径 和 otool 工具进行验证。