###本文环境: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源码目录,⌘+F
搜objc4
项目点击进去。可得objc4
在OS 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
搜索objc4
,Libc
,dyld
,libauto
,libclosure
,libdispatch
,libpthread
,xnu
。分别点击它们右边的下载图标(向下的箭头)下载。
但是,还差一个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
目录不见了,也即Libc
下pthreads
相关实现不见了,应该被移到别的项目去了或者苹果私有化了它,总之是找不到了。因为解决下面的编译错误时,是需要用到Libc
的pthreads
相关实现(我也是笨笨的逐个下旧版本发现的),所以我们下载的版本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.h
Line 106 那堆#include
下插入代码:
# include <pthread/tsd_private.h>
然后搜索到并复制tsd_private.h
到objc4-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 // 还有你
在xnu
和libpthread
下分别找到了。(tsd.h
你会搜索到两个,上面也遇到过,用路径带有os/
那个就好)
根据#indclude
的路径,分别复制到objc4-680/include/os/
和objc4-680/include/pthread/
下(os
文件夹不存在,需创建)。
9. spinlock_private.h
Line 59 typedef volatile OSSpinLock pthread_lock_t
处 Typedef redefinition with different types
重复定义了pthread_lock_t
,切换到Terminal,⌘+T
打开新的一个Tab,cd
到objc4-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.h
和spinlock_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.h
和auto_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-680
Xcode工程后,⌘+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可执行文件时,并没有发现我们上面的Libc
、libpthread
等动态库,它们去哪了?
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.11
的objc4-680
已经过时了。
如何编译它objc4-706
?大同小异,我另开一篇吧。