objc - 编译Runtime源码objc4-680

###本文环境: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_INIT

ALWAYS_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 ?大同小异,我另开一篇吧。

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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值