本文环境:Xcode 7.x

从苹果开源网站上,我们可以找到很多有意思的开源项目。objc4是其中一个,也即我们通常说的Runtime源码,我们遇到的libobjc.A.dylib就是用它编译出来的。
而这篇文章的目的,就是教你从苹果提供的Runtime源码,编译出自己的libobjc.A.dylib。进而你可以调试它,深入了解它。

如何浏览苹果开源网站

先偏离一下题目,浏览苹果的开源网站有必要说一下,因为objc4项目里缺失的头文件需要在苹果开源网站找回来。
从苹果源码首页下看到OS X 及iOS分类(Developer Tools 和 OS X Server忽略),iOS开源的东西比较少,基本上我们看OS X下的开源项目就够了。
打开版本OS X 10.11源码目录,⌘+Fobjc4项目点击进去。可得objc4OS X 10.11下的源码:

http://opensource.apple.com/source/objc4/objc4-680/

去掉具体版本路径(最后一个Path),可得到所有版本目录:

http://opensource.apple.com/source/objc4

去掉具体项目路径(最后一个Path),可得到所有项目目录:

http://opensource.apple.com/source/
(跟点击OS X版本链接的一样,但内容更多,比如launchd项目必须在这里才能找到)

上面所有链接把source改成tarballs,可得到打包的版本,i.e.:

http://opensource.apple.com/tarballs/objc4/

可看出,版本号的数值越大,版本越新。

编译Runtime源码

好了,回到正题吧,这里步骤比较详细。时间紧的同学,可以跳去看下一节的Shell Script,或直接去我的Github: objc4-680下载现成的。

1.下载源码

进入苹果源码首页,进入对应你系统版本的OS X源码目录,我的系统版本是10.11.6,那么点击进入macOS 10.11(进macOS 10.11.6的也行,大版本一样就得)。
分别⌘+F搜索objc4Libcdyldlibautolibclosurelibdispatchlibpthreadxnu。分别点击它们右边的下载图标(向下的箭头)下载。
但是,还差一个launchd项目,在此目录搜不到。于是,直接去Source目录或Tarballs目录⌘+F可搜索到。那就在这里下载个最新版本吧,没有我们要的头文件时再下个旧版就行了。

2.解压

于是我们得到了8个

  • objc4-680.tar.gz (Runtime源码objc工程)
  • Libc-1081.1.3.tar.gz (太新了,下面会替换个旧的版本)
  • dyld-360.14.tar.gz
  • libauto-186.tar.gz
  • libclosure-65.tar.gz
  • libdispatch-500.1.5.tar.gz
  • libpthread-137.1.1.tar.gz
  • xnu-3247.1.106.tar.gz
  • launchd-842.92.1.tar.gz

但是,对于Libc,从版本8XX版本9XX以后,对比发现,里面pthreads目录不见了,也即Libcpthreads相关实现不见了,应该被移到别的项目去了或者苹果私有化了它,总之是找不到了。因为解决下面的编译错误时,是需要用到Libcpthreads相关实现(我也是笨笨的逐个下旧版本发现的),所以我们下载的版本1081.1.3比较高是少了pthread相关的头文件的,删掉Libc-1081.1.3.tar.gz,下载Libc-825.40.1.tar.gz来替换它。

然后,把除了objc4-680外的7个都放到一个新建的文件夹(这里命名作AppleSources)下,方便搜索objc4-680里缺少的头文件,然后解压所有。

3.编译objc项目

进入解压的objc4-680/,打开项目工程文件objc.xcodeproj⌘+B(Build)编译一下看看,第一个错误出现了:mach-o/dyld_priv.h file not found

首先
objc4-680/下建立一个文件夹include,选择工程配置文件objc->TARGETS->objc->Build Settings->Search Paths->Header Search Paths,加入(Debug & Release都修改,下同)

$(SRCROOT)/include

我们一个一个来解决。下面的步骤,每解决掉一个Error后,大家就⌘+B(Build)一次来看下一个Error。

1. objc-os.h
# include <mach-o/dyld_priv.h>mach-o/dyld_priv.h file not found

去到AppleSources文件夹下搜索dyld_priv.h文件,用Finder右上角的搜索框,或在Terminal用命令:

cd AppleSources/
find . -name "dyld_priv.h"

我这里就用Terminal搜索了,在dyld下我们找到了dyld_priv.h
然后根据#indclude的路径,把它复制到objc4-680/include/下的mach-o/文件夹下(mach-o文件夹不存在,需创建)。(以后下面再有不存在的文件夹自己创建就好)
因为路径已经加入了Header Search Paths,我们直接⌘+B(Build),这个错误消失,下一个错误出现。

2. objc-os.h
# include <os/lock_private.h>os/lock_private.h file not found

关于lock_private.h搜了及搜遍整个互联网也搜不到。把它注释掉,然后定义三个锁函数(见下),利用已经引入的<libkern/OSAtomic.h>里的Lock & Unlock函数来代替它提供的功能。

//# include <os/lock_private.h>

以下三个锁函数请加到objc-os.h文件Line 118(第118行)(只要加在后面代码调用它们之前及#define ALWAYS_INLINE之后就行)

typedef OSSpinLock os_lock_handoff_s;
#define OS_LOCK_HANDOFF_INIT OS_SPINLOCK_INITALWAYS_INLINE void os_lock_lock(volatile os_lock_handoff_s *lock) {return OSSpinLockLock(lock);
}ALWAYS_INLINE void os_lock_unlock(volatile os_lock_handoff_s *lock) {return OSSpinLockUnlock(lock);
}ALWAYS_INLINE bool os_lock_trylock(volatile os_lock_handoff_s *lock) {return OSSpinLockTry(lock);
}

⌘+B,下一个错误

3. objc-os.h
# include <System/pthread_machdep.h>System/pthread_machdep.h file not found

AppleSources/文件夹下搜索pthread_machdep.h文件

find . -name "pthread_machdep.h"

Libc下我们找到了它(如果用Libc版本9XX或版本10XX是找不到它)。
然后根据#indclude的路径,把它复制到objc4-680/include/下的System/文件夹下(System文件夹不存在,需创建)。

⌘+B,下一个错误

4. pthread_machdep.h
#include <System/machine/cpu_capabilities.h>System/machine/cpu_capabilities.h file not found

AppleSources/文件夹下搜索cpu_capabilities.h

find . -name "cpu_capabilities.h"

结果发现在xnu下找到了两个:

./xnu-3247.1.106/osfmk/i386/cpu_capabilities.h
./xnu-3247.1.106/osfmk/machine/cpu_capabilities.h

注意看路径,选择第二个,因为它的路径跟我们#include中的一样,包含有machine/
然后根据#indclude的路径,把它复制到objc4-680/include/System/machine/下。(System/machine文件夹不存在,需创建)

5. objc-os.h
# include <CrashReporterClient.h>CrashReporterClient.h file not found

find . -name "CrashReporterClient.h"

Libc找到了,直接复制到objc4-680/include/下。

6. CrashReporterClient.h
#include_next <CrashReporterClient.h>CrashReporterClient.h file not found

这个错误发生在#include_next,事实上我们并没CrashReporterClient这个库。所以需要改一下工程配置文件:
Build Settings->Preprocessor Macros(Debug & Release)加入:

LIBC_NO_LIBCRASHREPORTERCLIENT

关于CrashReporterClient.h,后面你还会遇到跟它相关的两个Errors。你可以继续一下步,后面遇到了再回头看这里:

6.1 Use of undeclared identifier 'CRSetCrashLogMessage2'
只需在CrashReporterClient.h文件Line 36插入代码:

#define CRSetCrashLogMessage2(x) /* nothing */

6.2 ld: library not found for -lCrashReporterClient
只需去Build Settings -> Linking -> Other Linker Flags里删掉"-lCrashReporterClient"

7. objc-os.h
Line 788 处 Use of undeclared identifier '_PTHREAD_TSD_SLOT_MACH_THREAD_SELF'

我们利用grep命令,AppleSources/文件夹下,找找哪个文件定义(#define)了这个东西:

grep -rne "#define.*_PTHREAD_TSD_SLOT_MACH_THREAD_SELF" .

发现在libpthread项目下的tsd_private.h里。于是我们把这个tsd_private.h引入,在objc-os.hLine 106 那堆#include下插入代码:

# include <pthread/tsd_private.h>

然后搜索到并复制tsd_private.hobjc4-680/include/pthread/下(pthread文件夹不存在,需创建)。

8. tsd_private.h
#include <os/tsd.h>os/tsd.h file not found
#include <pthread/spinlock_private.h>pthread/spinlock_private.h file not found

上个步骤加入tsd_private.h后,它里面的出现了上面两错误。套路一样:

find . -name "tsd.h" && find . -name "spinlock_private.h"

得到结果:

./libdispatch-500.1.5/src/shims/tsd.h
./xnu-3247.1.106/libsyscall/os/tsd.h                // 就是你
./libpthread-137.1.1/private/spinlock_private.h     // 还有你

xnulibpthread下分别找到了。(tsd.h你会搜索到两个,上面也遇到过,用路径带有os/那个就好)
根据#indclude的路径,分别复制到objc4-680/include/os/objc4-680/include/pthread/下(os文件夹不存在,需创建)。

9. spinlock_private.h
Line 59 typedef volatile OSSpinLock pthread_lock_tTypedef redefinition with different types

重复定义了pthread_lock_t,切换到Terminal,⌘+T打开新的一个Tab,cdobjc4-680/include/grep一下就好了:

cd ../objc4-680/include/
grep -rne "typedef.*pthread_lock_t" .

结果:

./pthread/spinlock_private.h:59:typedef volatile OSSpinLock pthread_lock_t;
./System/pthread_machdep.h:214:typedef int pthread_lock_t;

从结果发现我们之前引入的pthread_machdep.h已经typedef过了。注释掉其中一个就好。但我这里选择注释掉pthread_machdep.h的,在Line 214:

//typedef int pthread_lock_t;

为什么是它呢?因为它是我们从Libc拷过来的,而Libc我们下载了较旧的版本。

10. tsd_private.h
又是tsd_private.h:)。这里有三处Redefinition of _pthread_xxx函数重定义错误:

  • _pthread_has_direct_tsd(void)
  • _pthread_getspecific_direct(unsigned long slot)
  • _pthread_setspecific_direct(unsigned long slot, void * val)

套路一样,你可以像上步一样grep出来,也可把include文件夹引入Xcode搜出来。会发现又是在pthread_machdep.h也有这三个函数的定义。
像第9步一样,再次修改pthread_machdep.h,分别注释掉pthread_machdep.h下面行号(代码太长,我就粘行号了):

  • _pthread_has_direct_tsd(void) 在 Line 216 - 232
  • _pthread_getspecific_direct(unsigned long slot) 在 Line 243 - 264
  • _pthread_setspecific_direct(unsigned long slot, void * val) 在 Line 267 - 294

其实经过上面这两次我们的修改,我们可以大概猜测到版本9XX后的Libc项目下的pthread相关代码,被苹果移到了libpthread项目了。(pthread_machdep.h来自Libc项目,tsd_private.hspinlock_private.h来自libpthread项目)

11. objc-os.h
Line 793 处 Unknown type name 'pthread_priority_t'
Line 797 处 Use of underclared identifier '_PTHREAD_PRIORITY_FLAGS_MASK'

来,回到Ternimal里在AppleSources/的那个Tab窗口,grep大法:

grep -re 'typedef.*pthread_priority_t' .

得到结果1:

./libdispatch-500.1.5/src/shims.h:typedef unsigned long pthread_priority_t;
./libdispatch-500.1.5/src/shims.h:typedef unsigned long pthread_priority_t;
./libpthread-137.1.1/private/qos_private.h:typedef unsigned long pthread_priority_t;

做到这里,大家也发现,其实就是耐心与细心。好了,少好为人师,继续grep:

grep -re 'def.*_PTHREAD_PRIORITY_FLAGS_MASK' .

得到结果2:

./libdispatch-500.1.5/src/inline_internal.h:    defaultpri &= ~_PTHREAD_PRIORITY_FLAGS_MASK;
./libpthread-137.1.1/private/qos_private.h:#define _PTHREAD_PRIORITY_FLAGS_MASK     (~0xffffff)

对比两个结果,你应该也发现了,就是她了:libpthread下的qos_private.h!于是一并解决了它们:

在这两个Errors之前,引入qos_private.h就完事了,即objc-os.h的Line 792下插入一行,插入代码:

#include <pthread/qos_private.h>

同时别忘了把./libpthread-137.1.1/private/qos_private.h复制到objc4-680/include/pthread/下。

12. qos_private.h
#include <sys/qos_private.h>'sys/qos_private.h' file not found

很简单:

find . -name "qos_private.h"

得到结果:

./libpthread-137.1.1/private/qos_private.h  // 这个我们已经上一步引入了
./libpthread-137.1.1/sys/qos_private.h  // 路径含有sys/,就是这个它了

根据#indclude的路径,把第二个qos_private.h复制到objc4-680/include/sys/下(sys文件夹不存在,需创建)。

13. objc-os.h
#include <pthread/workqueue_private.h>'pthread/workqueue_private.h' file not found

AppleSources/下搜索workqueue_private.h

find . -name "workqueue_private.h"
./libpthread-137.1.1/private/workqueue_private.h

workqueue_private.h复制到objc4-680/include/pthread/下。

13. objc-private.h
有两处file not found:
#include <objc-shared-cache.h>
include <auto_zone.h>

搜索:

find . -name "objc-shared-cache.h" -o -name "auto_zone.h"
./dyld-360.14/include/objc-shared-cache.h
./libauto-186/auto_zone.h

objc-shared-cache.hauto_zone.h,复制到objc4-680/include/下。

14. objc-errors.mm
也有两处file not found:
#include <vproc_priv.h>
#include <_simple.h>

搜索:

find . -name "vproc_priv.h" -or -name "_simple.h"
./launchd-842.92.1/liblaunch/vproc_priv.h
./Libc-825.26/gen/_simple.h

vproc_priv.h_simple.h,复制到objc4-680/include/下。

15. objc-auto.mm
也是两处file not found:
#include <Block_private.h>
#include <dispatch/private.h>

find . -name "Block_private.h" && find . -name "private.h"
./libclosure-65/Block_private.h             // 你
./Libc-825.26/stdtime/FreeBSD/private.h
./libdispatch-500.1.5/private/private.h     // 还有你。因为#include要的是'dispatch/'的。
./libpthread-137.1.1/private/private.h

Block_private.h复制到objc4-680/include/,把private.h复制到objc4-680/include/dispatch/下(dispatch文件夹不存在,需创建)。

16. private.h
7个fiel not found

#include <dispatch/benchmark.h>
#include <dispatch/queue_private.h>
#include <dispatch/source_private.h>
#include <dispatch/mach_private.h>
#include <dispatch/data_private.h>
#include <dispatch/io_private.h>
#include <dispatch/layout_private.h>

你可以按上面步骤一个个找出来放到objc4-680/include/dispatch/下。也可以一条命令解决:

find . -name "benchmark.h" -or -name "*_private.h" | grep -E \'dispatch.*(benchmark|queue_|source_|mach_|data_|io_|layout_)' | xargs -I{} cp {} ../objc4-680/include/dispatch/

(注意,我的objc4-680/文件夹跟AppleSources/文件夹是同级的)

好了,胜利就在眼前了!

17. objc-auto.mm
Use of undeclared identifier 'CRSetCrashLogMessage2'
可看回上面步骤6的6.1。在CrashReporterClient.h加上代码:

/* Fake the CrashReporterClient API */
#define CRGetCrashLogMessage() 0
#define CRSetCrashLogMessage(x) /* nothing */
#define CRSetCrashLogMessage2(x) /* nothing */      <------ 插入这一句代码

18
ld: can't open order file: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
MacOSX10.11.sdk/AppleInternal/OrderFiles/libobjc.order

libobjc.order 就在 $SRCROOT下,也即objc4-680/目录下。改一下工程配置:
Build Settings->Linking->Order File,把Debug和Release下的

$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order

改成

$(SRCROOT)/libobjc.order

或者

libobjc.order

都行

19
ld: library not found for -lCrashReporterClient
clang: error: linker command failed with exit code 1 (use -v to see invocation)

可看回上面步骤6的6.2。在Other Linker Flags(Debug & Release)里删掉"-lCrashReporterClient"

20 再按一次⌘+B编译,你成功了!

一步编译到位的Shell Script

为没时间看那么多步骤的同学而准备的,下载objc4-680.sh然后执行,或者只要在Terminal执行下面两条命令:

curl -O https://raw.githubusercontent.com/isaacselement/ShellScripts/master/objc4-680/objc4-680.sh
sh objc4-680.sh 

等待,等Terminal打开objc4-680Xcode工程后,⌘+B编译就行了。

最后,再提一下,可编译的版本objc源码,在我的 Github: objc-680。

调试你自己的libobjc.A.dylib

编译成功后,新建多一个Target,选择OS X下的Command Line Tool,命名作debug-objc。然后Build Phases->Target Dependencies,把Target objc加进来。然后,在main.m加入你想加的代码。

OK,放肆下断点,放肆改源码,签名好你自己的libobjc.A.dylib,放到虚拟机或越狱机上细心窥探吧。

多讲几句

1.至于是怎么知道那些缺失的头文件来自哪个苹果开源项目呢,从而下载这些项目?拿CrashReporterClient.h举例,在谷歌搜索带上stie:搜索它:CrashReporterClient.h site:opensource.apple.com,看搜索结果的链接,很容易可知道它来自Libc项目。其它缺失头文件同理(lock_private.h除外)。

2.当我们用otool查看我们的debug-objc这个Mach-o可执行文件时,并没有发现我们上面的Libclibpthread等动态库,它们去哪了?

otool -L debug-objc

但是结果里有个libSystem这个动态库,那么,你再查一下:

otool -L /usr/lib/libSystem.B.dylib

恩,它们都包含在了libSystem里了。

objc4-706

最近把Xcode升级到了Xcode 8,发现上面明明可以编译的objc4-680,现在编译不了:

跟踪这个DISPATCH_API_VERSION进去发现,果真是不一致了:

为什么?因为Xcode 8自带的是macOS 10.12的SDK。赶紧去苹果开源目录进入macOS 10.12看一下,已经是objc4-706了,OS X 10.11objc4-680已经过时了。

如何编译它objc4-706?大同小异,我另开一篇吧。

恩,问你们一句,你愿不愿意请我吃根卫龙辣条?

objc - 编译Runtime源码objc4-680相关推荐

  1. Runtime源码剖析-对象

    Runtime源码剖析-对象 预备知识 Clang 什么是Clang 终端编译命令 对象 对象结构 struct objc_object isa_t ISA_BITFIELD 总结 Isa_t初始流程 ...

  2. 通过Runtime源码了解关联对象的实现

    原文链接 在iOS开发中,Category是经常使用到的一个特性,合理的使用Category能够减少繁琐代码,提高开发效率.在使用Category时,有经验的开发者应该都知道,在Category中是无 ...

  3. 《深入解析Android 虚拟机》——第1章,第1.3节编译Android源码

    本节书摘来自异步社区<深入解析Android 虚拟机>一书中的第1章,第1.3节编译Android源码,作者 钟世礼,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...

  4. Windows IEDA 编译Hbase源码报错 - 无法执行shell脚本

    windows 下编译 hbase源码,报错 [ERROR] Command execution failed. java.io.IOException: Cannot run program &qu ...

  5. Win10+VS2019编译Jpeg源码时缺少win32.mak文件的内容

    在Win10中,使用VS2019的nmake编译Jpeg源码时,缺少win32.mak文件. win32.mak文件的内容如下: # Win32.Mak - Win32 application mas ...

  6. Java虚拟机(JVM源码):编译OpenJDK源码

    为什么要自己编译JDK源码 作为一个搞技术的同学,如果想在技术这条路上走下去,还是多研究些本质性的东西,修炼下内功.尤其是现在JDK都出到10了,如果你没有研究过,还是停留在之前的时代,那么确实说不过 ...

  7. 在Linux环境下怎么编译Android源码?

    http://www.linuxidc.com/Linux/2011-10/44236.htm JDK的安装: Linux Mint 13 中自带有OpenJDK,但这个不是我想要的,我们要的是sun ...

  8. Java扩展Nginx之二:编译nginx-clojure源码

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 为什么要编译nginx-clojure源码 作为< ...

  9. 都2039年了,还不会编译JDK源码?这篇文章该看看了~~

    文章目录 一.概述 二.环境准备 2.1 boot JDK 2.2 编译机器及依赖 三.JDK源码下载 四.编译过程 4.1 上传并解压源码 4.2 configure 4.3.make all 五. ...

最新文章

  1. 玉山银行的一名新员工“玉山小i随身金融顾问”
  2. Katana-CookieAuthenticationMiddleware-源码浅析
  3. centos6.5 安装mysql8,centos6上安装mysql8.0版本
  4. 使用Java让android手机自动执行重复重启
  5. 同一html页面中不同链接的不同样式
  6. 【转】2.2【MySQL】运行原理(二):InnoDB 内存结构、磁盘结构及update sql执行过程分析
  7. leetcode题库1277-- 统计全为 1 的正方形子矩阵
  8. 设计模式(一)----简单工厂、工厂方法和抽象工厂区别
  9. linux下安装软件后的环境变量设置
  10. 必要的系统组件未能正常运行,请修复Adobe Flash Player
  11. 捋一捋这些我记不清的python概念
  12. 家庭数据中心-私有云服务器定义和选择
  13. 最近整理的一些常见的面试题,面试大全,黑马程序员面试宝典题库---框架--篇
  14. 要闻君说:阿里云联合8家芯片模组商推出“全平台通信模组”;北汽与小桔车服联合“京桔”;IBM要帮助印度公司打造5G战略!...
  15. 为什么要用MQ,MQ是什么?(消息队列)
  16. Github每日精选(第61期):虚拟 DOM 走向未来 million
  17. MYSQL的简单查询
  18. 在axure中实现商品数量加减效果,原型库网站讲师-金乌 解答同学问
  19. PB级大规模Elasticsearch集群运维与调优实践
  20. asp+excel通用成绩查询系统 v6.8 工资查询物业费水电费查询通用哦

热门文章

  1. 关于keil5的ULINK烧录器设置步骤详解
  2. Invoke-Obfuscation笔记
  3. 工艺流程图绘制流程?试试这样绘制
  4. 基于KNN算法——手写海伦约会(学习)
  5. 天机巨门专业计算机科学与技术,欲戴王冠,必承其重——2020届计算机科学与技术马文杰...
  6. WORD BYTE 相互转换
  7. html5怎么给字加颜色代码,字体颜色怎么设置?
  8. 【象棋人生】01 人生如棋
  9. PLC学习笔记1:与PC下载与通讯
  10. rewrite功能介绍