什么是XNU?
XNU 内核是用于 OS X 和 iOS 操作系统的 Darwin 操作系统的一部分。 XNU 是 XNU 不是 Unix 的缩写。 XNU 是一个混合内核,将卡内基梅隆大学开发的 Mach 内核与 FreeBSD 和 C++ API 的组件结合起来,用于编写称为 IOKit 的驱动程序。 XNU 可在 I386、X86_64 上运行,适用于单处理器和多处理器配置。
XNU源码树
- config - 支持架构和平台的导出apis配置
- SETUP - 用于配置内核,版本控制和kextsymbol管理的基本工具集。
- EXTERNAL_HEADERS - 来自其他项目的标题,以避免构建时的依赖性循环。更新源时,应定期同步这些标头。
- libkern - 用于处理驱动程序和kexts的C ++ IOKit库代码。
- libsa - 用于启动的内核引导代码
- libsyscall - 用户空间程序的系统调用库接口
- libkdd - 解析内核数据(如内核分块数据)的用户库源码。
- makedefs - 内核构建的顶级规则和定义。
- osfmk - 基于Mach内核的子系统
- pexpert - 特定于平台的代码,如中断处理,原子等。
- security - 强制访问检查策略接口和相关实现。
- bsd - BSD子系统代码
- tools - 一组用于测试,调试和分析内核的实用程序。
如何构建XNU
构建开发内核
xnu make 系统可以基于 KERNEL_CONFIGS 和 ARCH_CONFIGS 变量作为参数。 这里是语法:
make SDKROOT=<sdkroot> ARCH_CONFIGS=<arch> KERNEL_CONFIGS=<variant>
其中:
-
:磁盘上macos sdk的路径。(默认为 /) -
:可以是 debug,development,release,profile 并配置编译标志和声明贯穿整个内核代码。 -
:可以是有效的构建版本。 (例如 i386 或 X86_64 )
要构建与运行OS相同的体系结构的内核,只需键入
$ make $ make SDKROOT=macosx.internal
此外,还支持通过 ARCH_CONFIGS 和 KERNEL_CONFIGS 配置内核配置。
$ make SDKROOT=macosx.internal ARCH_CONFIGS=X86_64 KERNEL_CONFIGS=DEVELOPMENT $ make SDKROOT=macosx.internal ARCH_CONFIGS=X86_64 KERNEL_CONFIGS="RELEASE DEVELOPMENT DEBUG"
注意:
- 默认情况下,体系结构设置为构建机器体系结构和默认内核 config已设置为开发。
这也将创建一个可启动映像,内核。[config]和一个内核二进制文件 带符号,内核。[config].unstripped。
- 使用 RELEASE 内核配置进行构建
make KERNEL_CONFIGS=RELEASE SDKROOT=/path/to/SDK
建立FAT内核二进制文件
在您的环境中或运行make命令时定义体系结构。
$ make ARCH_CONFIGS="X86_64" exporthdrs all
其他makefile选项
- $ make MAKEJOBS=-j8 #这将在构建期间使用8个进程。默认值是活动CPUS数量的两倍。
- $ make -j8 #标准命令行选项也被接受
- $ make -w #trace递归调用。与VERBOSE = YES 结合使用
- $ make BUILD_LTO=0 #无LLVM链接时间优化版本
- $ make REMOTEBUILD=user@remotehost #在远程主机上执行构建
- $ make BUILD_JSON_COMPILATION_DATABASE=1 #构建Clang JSON编译数据库
XNU构建系统可以选择输出颜色格式的构建输出。要启用它,你也可以 将 XNU_LOGCOLORS 环境变量设置为 y ,或者将 LOGCOLORS=y 传递给make命令。
调试信息格式
默认情况下,在安装阶段会创建一个DWARF调试信息库;这是一个名为kernel.development 的“bundle”。
$ export BUILD_STABS=1 $ make
构建KernelCaches
为了测试xnu内核,你需要建立一个连接kext和kernel的kernelcache 内核一起成为一个可启动的映像。 构建一个kernelcache哟你可以使用以下机制:
- 使用 kextd 自动生成kernelcache。
kextd守护进程一直在观察/System/Library/Extensions 目录中的更改。
所以你可以将新内核设置为
$ cp BUILD/obj/DEVELOPMENT/X86_64/kernel.development /System/Library/Kernels/ $ touch /System/Library/Extensions $ ps -e | grep kextd
- 手动调用 kextcache 来构建新的kernelcache。
$ kextcache -q -z -a x86_64 -l -n -c /var/tmp/kernelcache.test -K /var/tmp/kernel.test /System/Library/Extensions
在目标机器上运行KernelCache
开发内核和iBoot支持配置引导参数,以便我们可以安全地引导到测试内核,并且如果出现问题,安全地回退到先前使用的kernelcache。 以下是获取此类设置的步骤:
- 使用kextcache命令创建内核缓存为 /kernelcache.test
- 将退出的启动配置复制到备用文件
$ cp /Library/Preferences/SystemConfiguration/com.apple.Boot.plist /next_boot.plist
- 更新您的设置的kernelcache和boot-args
$ plutil -insert "Kernel Cache" -string "kernelcache.test" /next_boot.plist $ plutil -replace "Kernel Flags" -string "debug=0x144 -v kernelsuffix=test " /next_boot.plist
- 将新配置复制到/Library/Preferences/SystemConfiguration/
$ cp /next_boot.plist /Library/Preferences/SystemConfiguration/boot.plist
- 用新的配置保佑这个卷。
$ sudo -n bless --mount / --setBoot --nextonly --options "config=boot"
- nextonly 标志指定仅为一次引导使用 boot.plist 配置。 因此,如果内核出现恐慌,您可以轻松启动重新启动并恢复到原始内核。
创建标签和cscope
设置您的构建环境,并从顶部目录运行:
$ make tags #这将在区分大小写的卷上构建ctags和etags,只对大小写不敏感的ctags $ make TAGS #这将构建etags $ make cscope #这将构建cscope数据库
编码样式(重新加载文件)
源文件可以用.clang格式的clang格式设置进行重新格式化。 XNU遵循用于源代码格式的WebKit风格的变体。 请参阅 WebKit网站上的格式样式。 有关样式选项的更多选项,请参阅 clang文档
注意:clang格式的二进制文件可能不是基本安装的一部分。它可以从llvm clang源码进行编译,并且可以在$ PATH中访问。
从顶层目录运行:
$ make reindent #使用clang格式重新生成所有源文件。
如何从XNU安装新的头文件
要安装IOKit标头,请参阅 iokit/IOKit/Makefile 中的其他注释。
XNU在以下位置安装头文件 -
a. $(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers b. $(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders c. $(DSTROOT)/usr/include/ d. $(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders
内核扩展使用 Kernel.framework。
用户级应用程序使用 System.framework 和/usr/include 。 \ Framework的 PrivateHeaders 中的头文件仅适用于 Apple内部开发。
包含头文件的目录应该有一个Makefile 创建应该安装在不同位置的文件列表。 如果您要在目录中添加第一个头文件,您将需要 创建类似于xnu/bsd/sys/Makefile的Makefile。
根据你想要的位置将你的头文件添加到正确的文件列表中 安装它。头文件安装的默认位置 从每个文件列表 -
a. `DATAFILES` : 使用户级别的头文件可用 - `$(DSTROOT)/usr/include` b. `PRIVATE_DATAFILES` : 使用户级别的头文件可供Apple内部使用 - `$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders` c. `KERNELFILES` : 使内核级别的头文件可用 - `$(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers` `$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders` d. `PRIVATE_KERNELFILES` : 使头文件可供Apple内部使用于内核扩展 - `$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders`
Makefile结合了上面提到的文件列表进入不同的 安装构建系统用来安装头文件的列表。
如果您感兴趣的安装列表不存在,请创建它 通过添加适当的文件列表。默认安装列表,它的 成员文件列表及其默认位置如下所述 -
a. `INSTALL_MI_LIST` : Installs header file to a location that is available to everyone in user level. Locations - $(DSTROOT)/usr/include Definition - INSTALL_MI_LIST = ${DATAFILES} b. `INSTALL_MI_LCL_LIST` : Installs header file to a location that is available for Apple internal in user level. Locations - $(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders Definition - INSTALL_MI_LCL_LIST = ${PRIVATE_DATAFILES} c. `INSTALL_KF_MI_LIST` : Installs header file to location that is available to everyone for kernel extensions. Locations - $(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers Definition - INSTALL_KF_MI_LIST = ${KERNELFILES} d. `INSTALL_KF_MI_LCL_LIST` : Installs header file to location that is available for Apple internal for kernel extensions. Locations - $(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders Definition - INSTALL_KF_MI_LCL_LIST = ${KERNELFILES} ${PRIVATE_KERNELFILES} e. `EXPORT_MI_LIST` : Exports header file to all of xnu (bsd/, osfmk/, etc.) for compilation only. Does not install anything into the SDK. Definition - EXPORT_MI_LIST = ${KERNELFILES} ${PRIVATE_KERNELFILES}
使用这些步骤,单个头文件可以存在于不同的位置 上文提到的。但是,制作所有代码可能并不理想 在所有位置可用的头文件中。例如,你 希望将函数仅导出到内核级别,而不是用户级别。
您可以使用C语言的预处理器指令(#ifdef,#endif,#ifndef) 控制安装头文件之前生成的文本。内核 如果条件宏是TRUE并且去掉,只包含代码 代码为头文件中的FALSE条件。
一些预定义的宏和它们的描述是 -
a. `PRIVATE` : If defined, enclosed definitions are considered System Private Interfaces. These are visible within xnu and exposed in user/kernel headers installed within the AppleInternal "PrivateHeaders" sections of the System and Kernel frameworks. b. `KERNEL_PRIVATE` : If defined, enclosed code is available to all of xnu kernel and Apple internal kernel extensions and omitted from user headers. c. `BSD_KERNEL_PRIVATE` : If defined, enclosed code is visible exclusively within the xnu/bsd module. d. `MACH_KERNEL_PRIVATE`: If defined, enclosed code is visible exclusively within the xnu/osfmk module. e. `XNU_KERNEL_PRIVATE`: If defined, enclosed code is visible exclusively within xnu. f. `KERNEL` : If defined, enclosed code is available within xnu and kernel extensions and is not visible in user level header files. Only the header files installed in following paths will have the code - $(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers $(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders
如何添加新的系统调用
测试内核
XNU内核有多种测试机制。
- 断言 - 开发和DEBUG内核配置在启用断言的情况下编译。这使开发人员可以轻松 测试不变量和条件。
-
XNU开机自检( XNUPOST ):XNUPOST配置允许使用基本的测试功能组建内核
这是在第一个用户空间进程启动之前运行的。由于XNU是MACH和BSD的混合体,我们有两个位置可以添加测试。
xnu/osfmk/tests /#用于测试基于mach的内核结构和apis。 bsd/tests / #用于测试BSD接口。
请按照 osfmk/tests/README.md 的文档进行操作。 - 用户级别测试: tools/tests/目录包含验证xnu内核的系统调用和其他功能的所有测试。
make目标 xnu_tests 可用于构建所有支持的测试。
$ make RC_ProjectName = xnu_tests SDKROOT =/path/to/SDK
这些测试是个别程序,可以从终端运行,并通过std posix退出代码(0 ->sucess)和/或 stdout报告测试状态。 请阅读 tools/tests/unit_tests/README.md
内核数据描述符
XNU使用不同的数据格式在其api中传递数据。最标准的方法是使用系统调用参数。但对于复杂的数据,它通常依靠发送由C结构保存的内存。这种打包的数据传输机制很脆弱,并导致接口损坏 在用户空间程序和内核apis之间。 libkdd 目录保存用户空间库,该库可以解析由提供的自定义数据 相同版本的内核。内核分块数据格式在 libkdd/README.md中有详细描述。
调试内核
xnu内核支持使用远程内核调试协议(kdp)进行调试。请参阅技术说明中的文档。 默认情况下,内核被设置为在恐慌时重新启动。为了调试活动的内核,kdp服务器设置为通过以太网侦听UDP连接。对于没有以太网端口的机器,这种行为可以通过使用内核引导参数来改变。以下是一些常见选项。
- debug=0x144 - 设置调试变量以在恐慌时启动kdp debugserver
- -v - 在屏幕上打印内核日志。默认情况下,XNU只显示启动画面的灰色屏幕。
- kdp_match_name=en1 - 覆盖kdp的默认端口选择。支持以太网,Thunderbolt和串口调试。
要调试一个崩溃的内核,请使用llvm调试器(lldb)以及未提取的符号丰富的内核二进制文件。
sh $lldb kernel.development.unstripped
然后,您可以使用 kdp_remote [ip addr] 或 gdb_remote [hostip:port] 命令连接到崩溃的计算机。 每个内核都与内核特定的调试脚本一起打包,作为构建过程的一部分。出于安全原因,这些特殊命令 当lldb连接到机器时脚本不会自动加载。请将以下设置添加到〜/.lldbinit 中,如果你想永远加载这些宏。
settings set target.load-script-from-symbol-file true
tools/lldbmacros 目录包含每个命令的源代码。请按照 README.md 进行操作 有关命令及其用法的详细说明。