objc - 编译Runtime源码objc4-706

本文环境:Xcode 8.x

其实编译Runtime源码objc4-706,跟objc - 编译Runtime源码objc4-680是大同小异的。这里也简单写一下吧,如果大家编译过objc4-680,相信下面的步骤是很熟悉的。想直接跳过这些步骤直接拿到可编译版本的同学,可以到我的Github objc4-706下载,然后自己加个Target来调戏(直接看最后调试的那节)。

1.下载源码

进入苹果开源网站,进入macOS 10.12⌘+F分别搜索和下载下面的项目:
objc4Libcdyldlibautolibclosurelibdispatchlibpthreadxnu
然后,再去开源项目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_LIBCRASHREPORTERCLIENTtrue时,不是就不会有个这编译错误了?那我们就去预编译那里做点手脚,改一下工程配置文件就好,
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.hLine 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,cdobjc4-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.hobjc-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

恩,如果觉得文章帮到了您,请我吃根卫龙辣条吧 :)

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值