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源码目录,⌘+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_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.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
?大同小异,我另开一篇吧。
恩,问你们一句,你愿不愿意请我吃根卫龙辣条?
objc - 编译Runtime源码objc4-680相关推荐
- Runtime源码剖析-对象
Runtime源码剖析-对象 预备知识 Clang 什么是Clang 终端编译命令 对象 对象结构 struct objc_object isa_t ISA_BITFIELD 总结 Isa_t初始流程 ...
- 通过Runtime源码了解关联对象的实现
原文链接 在iOS开发中,Category是经常使用到的一个特性,合理的使用Category能够减少繁琐代码,提高开发效率.在使用Category时,有经验的开发者应该都知道,在Category中是无 ...
- 《深入解析Android 虚拟机》——第1章,第1.3节编译Android源码
本节书摘来自异步社区<深入解析Android 虚拟机>一书中的第1章,第1.3节编译Android源码,作者 钟世礼,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...
- Windows IEDA 编译Hbase源码报错 - 无法执行shell脚本
windows 下编译 hbase源码,报错 [ERROR] Command execution failed. java.io.IOException: Cannot run program &qu ...
- Win10+VS2019编译Jpeg源码时缺少win32.mak文件的内容
在Win10中,使用VS2019的nmake编译Jpeg源码时,缺少win32.mak文件. win32.mak文件的内容如下: # Win32.Mak - Win32 application mas ...
- Java虚拟机(JVM源码):编译OpenJDK源码
为什么要自己编译JDK源码 作为一个搞技术的同学,如果想在技术这条路上走下去,还是多研究些本质性的东西,修炼下内功.尤其是现在JDK都出到10了,如果你没有研究过,还是停留在之前的时代,那么确实说不过 ...
- 在Linux环境下怎么编译Android源码?
http://www.linuxidc.com/Linux/2011-10/44236.htm JDK的安装: Linux Mint 13 中自带有OpenJDK,但这个不是我想要的,我们要的是sun ...
- Java扩展Nginx之二:编译nginx-clojure源码
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 为什么要编译nginx-clojure源码 作为< ...
- 都2039年了,还不会编译JDK源码?这篇文章该看看了~~
文章目录 一.概述 二.环境准备 2.1 boot JDK 2.2 编译机器及依赖 三.JDK源码下载 四.编译过程 4.1 上传并解压源码 4.2 configure 4.3.make all 五. ...
最新文章
- 玉山银行的一名新员工“玉山小i随身金融顾问”
- Katana-CookieAuthenticationMiddleware-源码浅析
- centos6.5 安装mysql8,centos6上安装mysql8.0版本
- 使用Java让android手机自动执行重复重启
- 同一html页面中不同链接的不同样式
- 【转】2.2【MySQL】运行原理(二):InnoDB 内存结构、磁盘结构及update sql执行过程分析
- leetcode题库1277-- 统计全为 1 的正方形子矩阵
- 设计模式(一)----简单工厂、工厂方法和抽象工厂区别
- linux下安装软件后的环境变量设置
- 必要的系统组件未能正常运行,请修复Adobe Flash Player
- 捋一捋这些我记不清的python概念
- 家庭数据中心-私有云服务器定义和选择
- 最近整理的一些常见的面试题,面试大全,黑马程序员面试宝典题库---框架--篇
- 要闻君说:阿里云联合8家芯片模组商推出“全平台通信模组”;北汽与小桔车服联合“京桔”;IBM要帮助印度公司打造5G战略!...
- 为什么要用MQ,MQ是什么?(消息队列)
- Github每日精选(第61期):虚拟 DOM 走向未来 million
- MYSQL的简单查询
- 在axure中实现商品数量加减效果,原型库网站讲师-金乌 解答同学问
- PB级大规模Elasticsearch集群运维与调优实践
- asp+excel通用成绩查询系统 v6.8 工资查询物业费水电费查询通用哦