本文环境:Xcode 8.x。
其实编译Runtime源码objc4-706,跟objc - 编译Runtime源码objc4-680是大同小异的。这里也简单写一下吧,如果大家编译过objc4-680,相信下面的步骤是很熟悉的。想直接跳过这些步骤直接拿到可编译版本的同学,可以到我的Github objc4-706下载,然后自己加个Target来调戏(直接看最后调试的那节)。
1.下载源码
进入苹果开源网站,进入macOS 10.12,⌘+F
分别搜索和下载下面的项目:
objc4
,Libc
,dyld
,libauto
,libclosure
,libdispatch
,libpthread
,xnu
然后,再去开源项目Tarballs目录⌘+F
搜索下载launchd
项目,下载个最新的版本吧。
2.解压
于是我们得到了8个
- objc4-706.tar.gz (Runtime源码objc4工程)
Libc-1158.1.2.tar.gz(太新了,有些头文件找不到,替换个旧点的吧 Libc-825.40.1.tar.gz)- dyld-421.1.tar.gz
- libauto-187.tar.gz
- libclosure-67.tar.gz
- libdispatch-703.1.4.tar.gz
- libpthread-218.1.3.tar.gz
- xnu-3789.1.32.tar.gz
- launchd-842.92.1.tar.gz
然后,新建一个objc4/
文件夹,再在里面建一个AppleSources/
文件夹,把objc4-706.tar.gz
放到objc4/
下,其余的都放到objc4/AppleSources/
目录下,这样做是为了方便搜索objc4-706
项目里缺少的头文件,好了,解压这里所有的.tar
吧,我们开始吧。
3.编译
1.
在刚刚解压出来的objc4-706/
工程目录下建立一个文件夹include
:
mkdir objc4/objc4-706/include
选择工程配置文件objc->TARGETS->objc->Build Settings
->Search Paths->Header Search Paths
,加入:
$(SRCROOT)/include
2.
# include <sys/reason.h>
处 'sys/reason.h' file not found
,找不到'sys/reason.h'
文件。解决方法就是我们去AppleSources/
目录搜索到这个文件,然后放到刚刚新建的include/
目录下就好了:
cd objc4/AppleSources/
find . -name "reason.h"
看输出结果:
./xnu-3789.1.32/bsd/sys/reason.h
找到了!OK,因为它说的是'sys/reason.h'
文件找不到,所以我们需要在include/
下建立个sys/
目录,然后把reason.h
放到include/sys/
目录下:(发觉我挺啰嗦的,生怕遗露了哪点没说,希望你们细心点)
mkdir ../objc4-706/include/sys/
find . -name "reason.h" | xargs -I{} cp {} ../objc4-706/include/sys/
上面命令其实就是在objc4-706/include/
下建立个sys/
目录,然后把reason.h
复制到里面去。你们在Finder
用鼠标拖过去也一样的,我习惯了命令行。
好了,回到Xcode,⌘+B(Build)
编译一下,下一个错误。下面也是,每个步骤后⌘+B
一下。
3.
# include <mach-o/dyld_priv.h>
,解决方法跟上面一样,我们就一步到位吧:
find . -name "dyld_priv.h" ##确认有唯一结果打印输出后,继续执行下面两条命令吧
mkdir ../objc4-706/include/mach-o/
find . -name "dyld_priv.h" | xargs -I{} cp {} ../objc4-706/include/mach-o/
继续⌘+B(Build)
编译,看下一个错误。
4.
# include <os/lock_private.h>
,这个的解决有点特别,因为我们搜索不到它,搜了整个互联网也搜不到(你若找到了告诉我一声)。好吧,注释掉它:
//# include <os/lock_private.h>
到了下面遇到lock锁相关的错误时我们会用#include <os/lock.h>
来代替它的提供的功能,这里就先注释掉它,往下看。
⌘+B
,下一个错误
5.
# include <System/pthread_machdep.h>
,同理,一步到位吧:
find . -name "pthread_machdep.h" ##在`Libc`找到了,如果下载了较新的`Libc`版本是找不到的
mkdir ../objc4-706/include/System/
find . -name "pthread_machdep.h" | xargs -I{} cp {} ../objc4-706/include/System/
⌘+B
,下一个错误
6.
#include <System/machine/cpu_capabilities.h>
,这个错就发生在我们上一步加进去的文件pthread_machdep.h
里了,没事,同理的:
find . -name "cpu_capabilities.h"
发现结果输出有两个:
./xnu-3789.1.32/osfmk/i386/cpu_capabilities.h
./xnu-3789.1.32/osfmk/machine/cpu_capabilities.h ##选你了,谁叫你带有个'machine'
我们要第二个结果:
mkdir ../objc4-706/include/System/machine/
find . -name "cpu_capabilities.h" | grep machine | xargs -I{} cp {} ../objc4-706/include/System/machine/
7.
# include <CrashReporterClient.h>
,闭着眼睛解决了它:
find . -name "CrashReporterClient.h"
find . -name "CrashReporterClient.h" | xargs -I{} cp {} ../objc4-706/include/
8.
#include_next <CrashReporterClient.h>
,又是发生在我们上一步加进去的文件。看看错误处周围的注释及简单理解一下代码,#ifdef LIBC_NO_LIBCRASHREPORTERCLIENT
为true
时,不是就不会有个这编译错误了?那我们就去预编译那里做点手脚,改一下工程配置文件就好,
Build Settings
->Preprocessor Macros
,加入:
LIBC_NO_LIBCRASHREPORTERCLIENT
⌘+B
后,在objc-os.h
文件里的错误是不是多得吓到你了?别慌。
9.
Use of undeclared identifier '_PTHREAD_TSD_SLOT_MACH_THREAD_SELF'
,既然它说了我们用了没声明的标识符,那么我们就找找它在哪里声明了:
grep -rne "#define.*_PTHREAD_TSD_SLOT_MACH_THREAD_SELF" .
得到结果
./libpthread-218.1.3/private/tsd_private.h:73:#define _PTHREAD_TSD_SLOT_MACH_THREAD_SELF __TSD_MACH_THREAD_SELF
好了,在objc-os.h
文件里引入头文件tsd_private.h
就好。于是,我们在objc-os.h
前面那堆#include
下插入代码:
# include <pthread/tsd_private.h>
引入了还没完事,还要把文件复制过去:
mkdir ../objc4-706/include/pthread
find . -name "tsd_private.h" | xargs -I{} cp {} ../objc4-706/include/pthread
10.
#include <os/tsd.h>
,错误出现在上个步骤加入tsd_private.h
里,同理:
find . -name "tsd.h
输出
./libdispatch-703.1.4/src/shims/tsd.h
./xnu-3789.1.32/libsyscall/os/tsd.h ## 恩,是你了
于是复制到include/os/
下:
mkdir ../objc4-706/include/os
find . -name "tsd.h" | grep os | xargs -I{} cp {} ../objc4-706/include/os
11
#include <pthread/spinlock_private.h>
,套路一样,一步到位:
find . -name "spinlock_private.h" | xargs -I{} cp {} ../objc4-706/include/pthread
⌘+B
哎呀,出现了些重复定义宏的警告,虽然我也很恨警告,但我们还是先解决错误,回头再来整理它们吧。
12
spinlock_private.h
Line 59处,typedef volatile OSSpinLock pthread_lock_t __deprecated_msg("Use <os/lock.h> instead");
处 Typedef redefinition with different types
说我们重复定义了pthread_lock_t
,切换到Terminal,⌘+T
打开新的一个Tab,cd
到objc4-706/include/
那grep
一下就好了:
cd ../objc4-706/include/
grep -rne "typedef.*pthread_lock_t" .
结果:
./pthread/spinlock_private.h:59:typedef volatile OSSpinLock pthread_lock_t __deprecated_msg("Use <os/lock.h> instead");
./System/pthread_machdep.h:214:typedef int pthread_lock_t;
从结果可以发现我们之前引入的pthread_machdep.h
已经typedef
过它了。我这里选择注释掉pthread_machdep.h
里的,在Xcode里⌘+⇧+O
输入pthread_machdep.h
打开去到Line 214行,注释掉:
//typedef int pthread_lock_t;
13
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)
像上步骤一样在objc4-706/include/
的那个Terminal Tab里grep
出来,也可以把include
文件夹引入Xcode的objc4-706
项目⌘+⇧+F
搜出来:
grep -re '_pthread_has_direct_tsd(void)' .
grep -re '_pthread_getspecific_direct(unsigned long slot)' .
grep -re '_pthread_setspecific_direct(unsigned long slot, void \* val)' .
会发现又是在pthread_machdep.h
也有这三个函数的定义实现,于是在pthread_machdep.h
里大胆地注释掉它们就好。
14
objc-os.h
里的
Line 794 处 Unknown type name 'pthread_priority_t'
Line 798 处 Use of underclared identifier '_PTHREAD_PRIORITY_FLAGS_MASK'
来,回到Ternimal里,⌘+⇧+{
切换回到AppleSources/
的那个Tab窗口,grep
大法:
grep -re 'typedef.*pthread_priority_t' .
grep -re 'def.*_PTHREAD_PRIORITY_FLAGS_MASK' .
发现了啥?有个文件在输出结果里出现了两次:
./libpthread-218.1.3/private/qos_private.h
好了,那我们在objc-os.h
前面引入它:
#include <pthread/qos_private.h>
别忘了复制它到include/pthread/
下:
cp ./libpthread-218.1.3/private/qos_private.h ../objc4-706/include/pthread
15
#include <sys/qos_private.h>
,同理:
find . -name "qos_private.h" | grep sys | xargs -I{} cp {} ../objc4-706/include/sys
16
#include <objc-shared-cache.h>
,同理:
find . -name "objc-shared-cache.h" | xargs -I{} cp {} ../objc4-706/include/
17
#include <_simple.h>
,同理:
find . -name "_simple.h" | xargs -I{} cp {} ../objc4-706/include/
18
objc-os.h
文件里的两个锁相关错误,估计就是我们第4个步骤注释掉的#include <os/lock_private.h>
文件提供的:
1.os_unfair_lock
处的 Unknown type nam 'os_unfair_lock'
2.OS_UNFAIR_LOCK_INIT
处的 Use of undeclared identifier'OS_UNFAIR_LOCK_INIT'
解决过了上面的步骤9及步骤12-14,其实你应该知道怎么解决了,用grep
嘛:
grep -rne "os_unfair_lock" .
grep -rne "OS_UNFAIR_LOCK_INIT" .
两个都在./libpthread-218.1.3/src/internal.h
里能找到了,可是,这并不是真正的答案。因为,当你把internal.h
引入并复制到include/
而后编译,你会发现它里面的没声明没定义的标识符错误跟objc-os.h
里一些错误是一样的,让你感觉解决掉了这两个错误,却又多了些意外的解决不掉的错误。
那么,我们需要另辟蹊径。
其实,除了第一个步骤我们显式配置的include/
文件来会被clang
编译工具用来搜索头文件外,还有一个include
文件夹会被它用来搜索头文件,那就是/usr/include
,或许你在Terminal里cd
到根目录/
,会发现自己的系统里没有/usr/include
,因为你没安装好macOS的Command Line Tools,执行xcode-select --install
就可安装。就算你不安装,Xcode自己用的那份在/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
。装了的话,你可以对比一下看看是不是一样的。(StackOverflow Reference: Where is my “stdio.h” in Mac?)
好了,切换到Terminal,⌘+T
再打开新的一个Tab,cd
到Xcode的usr/include/
下:
grep -rne "typedef.*os_unfair_lock" .
grep -rne "OS_UNFAIR_LOCK_INIT" .
愉快地发现./os/lock.h
里有哦,那么我们只需要在objc-os.h
文件里的前面愉快地引入就行了:
# include <os/lock.h>
其实上面的第12个步骤也提示我们"Use <os/lock.h> instead"
,甚至,你还可以在Xcode里⌘+⇧+O
输入os_unfair_lock
发现,所以,机灵如你。
19
objc-os.h
文件里在void lock()
和 void unlock()
的实现里有两个锁相关错误:
OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION
处的Use of undeclared 'OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION'
os_unfair_lock_unlock_inline
处的Use of undeclared 'os_unfair_lock_unlock_inline'
既然上个步骤已经引入了# include <os/lock.h>
,那么就看看里有什么函数可以代替提供这些功能的。好了,这样改:
void lock()
里:
// os_unfair_lock_lock_with_options_inline (&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION); ## 注释掉有错误的一行
os_unfair_lock_lock(&mLock); ## 加入这一行
void unlock()
里:
// os_unfair_lock_unlock_inline(&mLock); ## 注释掉有错误的一行
os_unfair_lock_unlock(&mLock); ## 加入这一行
20
objc-lockdebug.mm
文件里的两个assert
函数体错误,有两个解决方法:
1 . 在两函数上面加入代码(即Line 170行):
extern "C" void os_unfair_lock_assert_owner(os_unfair_lock *);
extern "C" void os_unfair_lock_assert_not_owner(os_unfair_lock *);
看名字是系统提供的,所以就extern
了。但还是需要确定一下在别的文件里有或存在这两个函数声明及实现,于是在IDA反编译里看了看系统的/usr/lib/libobjc.A.dylib
,并没有找到这两个Symbol
,又或许会在别的.dylib
里动态加载进来,不管怎么样,我们还是偏向下面的解决方法好了。
2 . 直接注释掉错误的行:
void
lockdebug_mutex_assert_locked(mutex_t *lock)
{
// os_unfair_lock_assert_owner((os_unfair_lock *)lock);
}
void
lockdebug_mutex_assert_unlocked(mutex_t *lock)
{
// os_unfair_lock_assert_not_owner((os_unfair_lock *)lock);
}
大概看它是debug的又是assert的,再搜搜调用的地方,好像也没什么用处,就注释掉好了。
又或者,你也可以把 #if !TARGET_OS_SIMULATOR ... #else ...
中的#else
里面的相同函数及实现,复制上来覆盖了也可以达到目的。
21
#include <Block_private.h>
,回到Terminal,⌘+⇧+{
切换回到AppleSources/
的Tab,搜索复制:
find . -name "Block_private.h" | xargs -I{} cp {} ../objc4-706/include/
22
ld: can't open order file: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/
MacOSX10.12.sdk/AppleInternal/OrderFiles/libobjc.order
libobjc.order
就在 $SRCROOT
下,也即objc4-706/
目录下。改一下工程配置:
Build Settings
->Linking
->Order File
,把Debug和Release下的
$(SDKROOT)/AppleInternal/OrderFiles/libobjc.order
改成
$(SRCROOT)/libobjc.order
或者
libobjc.order
都行
23
ld: library not found for -lCrashReporterClient
只需要去Build Settings
-> Linking
-> Other Linker Flags
里删掉"-lCrashReporterClient"
(Debug 和 Release 都有的哦,需要删两个哦)
24
没有24了,如无意外,你编译成功了。还有些重复定义的警告,注释掉它们就好了。
其实,通篇下来,你会发现(可以diff看看),除了include/
文件夹下的,我们只改动了原项目里的两个文件:objc-os.h
和objc-lockdebug.mm
。
# 解压原始的一份到桌面下,对比
diff -Nrq objc4-706 ~/Desktop/objc4-706 | grep objc4-706/runtime/
调试
编译成功后,可以调试你自己编译出来的libobjc.A.dylib,新建多一个Target,选择macOS下的Command Line Tool,命名作debug-objc
:
然后Build Phases
->Target Dependencies
,把Target objc加进来。然后,在debug-objc/main.m
加入你想加的代码:
好了,这下你可以不断下断点来深入了解它了,或改改源码然后签名好编译出来的libobjc.A.dylib
,放到虚拟机或越狱机上做些研究吧。
最后,我弄了一个可编译版本在Github objc4-706,懒的弄得同学就直接下载吧。
新年快乐
文章拖了挺久的,因为一直在忙,也快过年了,提前几天祝大家:新春快乐,招财进宝,合家团圆,幸福美满,祝君开怀笑,祝新年快乐!
——2017.01.24