XNU 内核是 Darwin 操作系统的一部分,用于 macOS 和 iOS 操作系统。XNU 是 X is Not Unix 的缩写。 XNU 是一个混合内核,结合了卡内基梅隆大学开发的 Mach 内核与 FreeBSD 的组件,以及一个用于编写驱动程序的 C++ API,称为 IOKit。 XNU 运行在 x86_64 和 ARM64 上,支持单处理器和多处理器配置。
config - 配置用于导出API的体系结构和平台SETUP - 用于配置内核、版本控制和 kextsymbol 管理的基本工具集。EXTERNAL_HEADERS - 从其他项目获取的头文件,以避免构建时的依赖循环。这些头文件应在源代码更新时定期同步。libkern - C++ IOKit 库代码,用于处理驱动程序和 kext。libsa - 用于启动的内核引导代码libsyscall - 用于用户空间程序的系统调用库接口libkdd - 用于解析内核数据(如内核分块数据)的用户库源代码。makedefs - 内核构建的顶级规则和定义。osfmk - 基于 Mach 内核的子系統pexpert - 平台特定代码,如中断处理、原子操作等。security - 强制访问检查策略接口及相关实现。bsd - BSD 子系统代码tools - 用于测试、调试和分析内核的一组实用程序。DEVELOPMENT 内核xnu make 系统可以根据 KERNEL_CONFIGS 和 ARCH_CONFIGS 变量作为参数构建内核。
以下是语法:```text
make SDKROOT= ARCH_CONFIGS= KERNEL_CONFIGS=
其中:
* `<sdkroot>`: 磁盘上 macOS SDK 的路径。(默认为 `/`)
* `<variant>`: 可以是 `debug`、`development`、`release`、`profile`,并配置内核代码中的编译标志和断言。
* `<arch>`: 可以是有效的构建架构。(例如 `X86_64`)
要为运行的操作系统的相同架构构建内核,只需输入```text
make SDKROOT=macosx.internal
另外,也支持通过 ARCH_CONFIGS 配置架构,以及使用 KERNEL_CONFIGS 配置内核。```text
make SDKROOT=macosx.internal ARCH_CONFIGS=X86_64 KERNEL_CONFIGS=DEVELOPMENT
make SDKROOT=macosx.internal ARCH_CONFIGS=X86_64 KERNEL_CONFIGS="RELEASE DEVELOPMENT DEBUG"
> 注意:默认情况下,架构设置为构建机器的架构,默认内核配置设置为用于 `DEVELOPMENT` 进行构建。
这还将创建一个可启动镜像、kernel.[config] 以及一个包含符号的内核二进制文件 kernel.[config].unstripped。
要将内核安装到 DSTROOT,请使用 `install_kernels` 目标:```text
make install_kernels DSTROOT=/tmp/xnu-dst
为了获得更令人满意的内核调试体验,能够访问所有局部变量和参数, 但又无需 DEBUG 内核中所有额外的检查,可以在你的 make 命令中添加类似以下内容:```text CFLAGS_DEVELOPMENTARM64="-O0 -g -DKERNEL_STACK_MULTIPLIER=2" CXXFLAGS_DEVELOPMENTARM64="-O0 -g -DKERNEL_STACK_MULTIPLIER=2"
请记得将 `DEVELOPMENT` 和 `ARM64` 替换为合适的构建和平台。
> 额外标志:您可以在命令行中使用 `EXTRA_CFLAGS` 构建设置向 C 编译器传递额外标志。这些标志会被追加到基础 `CFLAGS` 中,该设置的默认值为空字符串。
>
> 此设置允许您例如有选择地打开由预处理宏保护的调试代码。示例用法...
>
> ```text
> make SDKROOT=macosx.internal PRODUCT_CONFIGS=j314s
> EXTRA_CFLAGS='-DKERNEL_STACK_MULTIPLIER=2'
> ```
* 使用 RELEASE 内核配置构建
```text
make KERNEL_CONFIGS=RELEASE SDKROOT=/path/to/SDK
```
### 构建 FAT 内核二进制文件
在您的环境或运行 make 命令时定义架构。```text
make ARCH_CONFIGS="X86_64" exporthdrs all
XNU 构建系统可以选择性地输出颜色格式的构建输出。要启用此功能,您可以将 XNU_LOGCOLORS 环境变量设置为 y,或者将 LOGCOLORS=y 传递给 make 命令。
XNU 版本通过读取 SDK 或 KDK 中的 System/Library/Extensions/System.kext/Info.plist 文件的 CFBundleVersion 来获得。可以通过在环境中或在 make 命令行中设置 RC_DARWIN_KERNEL_VERSION 变量来自定义。
详见 doc/building/xnu_version.md。
默认情况下,在安装阶段会创建一个 DWARF 调试信息仓库;这是一个名为 kernel.development.<variant>.dSYM 的 "bundle"。 要选择较旧的 STABS 调试信息格式(其中调试信息嵌入在 kernel.development.unstripped 映像中),请设置 BUILD_STABS 环境变量。```sh export BUILD_STABS=1 make
## 构建内核缓存
要测试 xnu 内核,你需要构建一个将 kext 和内核链接在一起的内核缓存,形成一个可启动的镜像。
要构建内核缓存,你可以使用以下机制:
* 使用 `kextd` 自动生成内核缓存。
kextd 守护进程持续监视 `/System/Library/Extensions` 目录的变化。
因此,你可以按照以下步骤设置新内核:
```text
cp BUILD/obj/DEVELOPMENT/X86_64/kernel.development /System/Library/Kernels/
touch /System/Library/Extensions
ps -e | grep kextd
```
* 手动调用 `kextcache` 构建新的内核缓存。
```text
kextcache -q -z -a x86_64 -l -n -c /var/tmp/kernelcache.test -K /var/tmp/kernel.test /System/Library/Extensions
```
## 在目标机器上引导内核缓存
开发内核和 iBoot 支持配置启动参数,以便我们可以安全地引导到测试内核,并且在出现问题的情况下,安全地回退到之前使用的内核缓存。
以下是实现此类设置的步骤:
1. 使用 kextcache 命令创建内核缓存,路径为 `/kernelcache.test`
2. 将现有启动配置复制到备用文件
```sh
cp /Library/Preferences/SystemConfiguration/com.apple.Boot.plist /next_boot.plist
```
3. 更新内核缓存和启动参数以适配你的设置
```sh
plutil -insert "Kernel Cache" -string "kernelcache.test" /next_boot.plist
plutil -replace "Kernel Flags" -string "debug=0x144 -v kernelsuffix=test " /next_boot.plist
```
4. 将新配置复制到 `/Library/Preferences/SystemConfiguration/`
```sh
cp /next_boot.plist /Library/Preferences/SystemConfiguration/boot.plist
```
5. 使用新配置对卷进行 bless 操作。
```text
sudo -n bless --mount / --setBoot --nextonly --options "config=boot"
```
`--nextonly` 标志指定仅使用 `boot.plist` 配置一次启动。
因此,如果内核 panic,你可以轻松地强制重启并恢复到原始内核。
## 创建标签和 cscope
设置好构建环境后,从顶层目录运行:
make tags # 这将构建 ctags 和 etags(在区分大小写的卷上),在大小写不敏感的卷上仅构建 ctags
make TAGS # 这将构建 etags
make cscope # 这将构建 cscope 数据库
## 从 XNU 安装新头文件
XNU 将头文件安装在以下位置 -
a. $(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers
b. $(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders
c. $(DSTROOT)/usr/include/
d. $(DSTROOT)/usr/local/include/
e. $(DSTROOT)/System/DriverKit/usr/include/
f. $(DSTROOT)/System/Library/Frameworks/IOKit.framework/Headers
g. $(DSTROOT)/System/Library/Frameworks/IOKit.framework/PrivateHeaders
h. $(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders
`Kernel.framework` 供内核扩展使用。\
`System.framework`、`/usr/include` 和 `/usr/local/include` 供用户级应用程序使用。\
`IOKit.framework` 供 IOKit 用户空间客户端使用。\
`/System/DriverKit/usr/include` 供用户空间驱动程序使用。\
框架的 `PrivateHeaders` 中的头文件仅适用于 **Apple 内部开发**。
包含头文件的目录应有一个 Makefile,
用于创建应安装在不同位置的文件列表。
如果你是在某个目录中添加第一个头文件,则需要
创建类似于 `xnu/bsd/sys/Makefile` 的 Makefile。
根据你想要安装的位置,将你的头文件添加到正确的文件列表中。
每个文件列表的头文件默认安装位置如下 -
a. `DATAFILES` : 在用户级别提供头文件 -
`$(DSTROOT)/usr/include`
`$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders`
b. `DRIVERKIT_DATAFILES` : 为 DriverKit 用户空间驱动程序提供头文件 -
`$(DSTROOT)/System/DriverKit/usr/include`
c. `PRIVATE_DATAFILES` : 在用户级别为 Apple 内部提供头文件 -
`$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders`
d. `EMBEDDED_PRIVATE_DATAFILES` : 在 macOS 上作为 `EXTRA_DATAFILES` 在用户级别提供头文件,
但在嵌入式 OS 上作为 `EXTRA_PRIVATE_DATAFILES` 在用户级别供 Apple 内部使用 -
`$(DSTROOT)/usr/include` (`EXTRA_DATAFILES`)
`$(DSTROOT)/usr/local/include` (`EXTRA_PRIVATE_DATAFILES`)
e. `KERNELFILES` : 在内核级别提供头文件 -
`$(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers`
`$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders`
f. `PRIVATE_KERNELFILES` : 在内核扩展层面为 Apple 内部提供头文件 -
`$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders`
g. `MODULEMAPFILES` : 在用户级别提供模块映射文件 -
`$(DSTROOT)/usr/include`
h. `PRIVATE_MODULEMAPFILES` : 在用户级别为 Apple 内部提供模块映射文件 -
`$(DSTROOT)/usr/local/include`
i. `LIBCXX_DATAFILES` : 为内核中的 libcxx 客户端提供头文件:
`$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders/kernel_sdkroot`
j. `EXCLAVEKIT_DATAFILES` : 为 Apple 内部的 ExclaveKit SDK 提供头文件 -
`$(DSTROOT)/System/ExclaveKit/usr/include`
k. `EXCLAVECORE_DATAFILES` : 为 Apple 内部的 ExclaveCore SDK 提供头文件 -
`$(DSTROOT)/System/ExclaveCore/usr/include`
Makefile 将上述文件列表组合成不同的
安装列表,构建系统使用这些列表来安装头文件。有两种
类型的安装列表:机器相关的和机器无关的。
这些列表分别由构建设置中的 `MD` 和 `MI` 表示。如果你的头文件是特定于架构的,则应使用
机器相关的安装列表(例如 `INSTALL_MD_LIST`)。如果你的头文件
应针对所有架构安装,则应使用
机器无关的安装列表(例如 `INSTALL_MI_LIST`)。
如果你需要的安装列表不存在,请通过添加相应的文件列表来创建它。
默认的安装列表、其成员文件列表以及它们的默认位置如下所述 -
a. `INSTALL_MI_LIST`, `INSTALL_MODULEMAP_MI_LIST` : 将头文件和模块映射
文件安装到用户级别所有人都可访问的位置。
位置 -
$(DSTROOT)/usr/include
定义 -
INSTALL_MI_LIST = ${DATAFILES}
INSTALL_MODULEMAP_MI_LIST = ${MODULEMAPFILES}
b. `INSTALL_DRIVERKIT_MI_LIST` : 将头文件安装到
DriverKit 用户空间驱动程序可访问的位置。
位置 -
$(DSTROOT)/System/DriverKit/usr/include
定义 -
INSTALL_DRIVERKIT_MI_LIST = ${DRIVERKIT_DATAFILES}
c. `INSTALL_MI_LCL_LIST`, `INSTALL_MODULEMAP_MI_LCL_LIST` : 将头文件和
模块映射文件安装到用户级别为 Apple 内部提供的可访问位置。
位置 -
$(DSTROOT)/usr/local/include
定义 -
INSTALL_MI_LCL_LIST =
INSTALL_MODULEMAP_MI_LCL_LIST = ${PRIVATE_MODULEMAPFILES}
d. `INSTALL_IF_MI_LIST` : 将头文件安装到
所有 IOKit 用户空间客户端都可访问的位置。
位置 -
$(DSTROOT)/System/Library/Frameworks/IOKit.framework/Headers
定义 -
INSTALL_IF_MI_LIST = ${DATAFILES}
e. `INSTALL_IF_MI_LCL_LIST` : 将头文件安装到
IOKit 用户空间客户端中 Apple 内部可访问的位置。
位置 -
$(DSTROOT)/System/Library/Frameworks/IOKit.framework/PrivateHeaders
定义 -
INSTALL_IF_MI_LCL_LIST = ${DATAFILES} ${PRIVATE_DATAFILES}
f. `INSTALL_SF_MI_LCL_LIST` : 将头文件安装到用户级别中
Apple 内部可访问的位置。
位置 -
$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders
定义 -
INSTALL_SF_MI_LCL_LIST = ${DATAFILES} ${PRIVATE_DATAFILES}
g. `INSTALL_KF_MI_LIST` : 将头文件安装到
所有内核扩展都可访问的位置。
位置 -
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers
定义 -
INSTALL_KF_MI_LIST = ${KERNELFILES}
h. `INSTALL_KF_MI_LCL_LIST` : 将头文件安装到
内核扩展中 Apple 内部可访问的位置。
位置 -
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders
定义 -
INSTALL_KF_MI_LCL_LIST = ${KERNELFILES} ${PRIVATE_KERNELFILES}
i. `EXPORT_MI_LIST` : 将头文件导出到 xnu 的所有部分(bsd/、osfmk/ 等)
仅用于编译。不安装到 SDK 中。
定义 -
EXPORT_MI_LIST = ${KERNELFILES} ${PRIVATE_KERNELFILES}
j. `INSTALL_KF_LIBCXX_MI_LIST` : 安装用于内核内 libc++ 支持的头文件。
位置 -
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders/kernel_sdkroot
定义 -
INSTALL_KF_LIBCXX_MI_LIST = ${LIBCXX_DATAFILES}
k. `INSTALL_EXCLAVEKIT_MI_LIST` : 将头文件安装到
ExclaveKit 中 Apple 内部可访问的位置。
位置 -
$(DSTROOT)/System/ExclaveKit/usr/include
定义 -
INSTALL_EXCLAVEKIT_MI_LIST = ${EXCLAVEKIT_DATAFILES}
l. `INSTALL_EXCLAVECORE_MI_LIST` : 将头文件安装到
ExclaveCore 中 Apple 内部可访问的位置。
位置 -
$(DSTROOT)/System/ExclaveCore/usr/include
定义 -
INSTALL_EXCLAVECORE_MI_LIST = ${EXCLAVECORE_DATAFILES}
如果要将头文件安装到 (1) 中所述路径的子目录中,请使用两个变量
`INSTALL_MI_DIR` 和 `EXPORT_MI_DIR` 指定目录名称,如下所示 -```text
INSTALL_MI_DIR = dirname
EXPORT_MI_DIR = dirname
如果你想要将模块映射文件安装到子目录中,指定
目录名称使用变量 INSTALL_MODULEMAP_MI_DIR,如下所示 -```text
INSTALL_MODULEMAP_MI_DIR = dirname
使用上述步骤,单个头文件可以存在于不同的位置。但可能不希望让头文件中的所有代码在所有位置都可用。例如,你希望仅在内核级别导出某个函数,而不在用户级别导出。
你可以使用C语言的预处理器指令(#ifdef、#endif、#ifndef)来控制头文件安装前生成的文本。只有在条件宏为TRUE时,内核才会包含该代码,并从头文件中剥离条件为FALSE的代码。
一些预定义的宏及其描述如下:
1. `PRIVATE` : 如果定义,包围的代码被视为系统私有接口。这些接口在xnu内部可见,并暴露在安装在AppleInternal框架的"PrivateHeaders"部分的用户/内核头文件中,该框架属于System和Kernel框架。
2. `KERNEL_PRIVATE` : 如果定义,包围的代码对xnu内核和苹果内部内核扩展均可用,并从用户头文件中省略。
3. `BSD_KERNEL_PRIVATE` : 如果定义,包围的代码仅在xnu/bsd模块内可见。
4. `MACH_KERNEL_PRIVATE`: 如果定义,包围的代码仅在xnu/osfmk模块内可见。
5. `XNU_KERNEL_PRIVATE`: 如果定义,包围的代码仅在xnu内可见。
6. `KERNEL` : 如果定义,包围的代码在xnu和内核扩展中可用,并且在用户级头文件中不可见。只有安装在以下路径中的头文件才会包含该代码:
```text
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders
```
7. `DRIVERKIT`: 如果定义,包围的代码仅在用户空间驱动程序使用的DriverKit SDK头文件中可见。
8. `EXCLAVEKIT`: 如果定义,包围的代码仅在ExclaveKit SDK头文件中可见。
9. `EXCLAVECORE`: 如果定义,包围的代码仅在ExclaveCore SDK头文件中可见。
10. `MODULES_SUPPORTED` 如果定义,包围的代码仅在支持模块/Swift的位置可见(即非System或Kernel框架)。
## VM头文件命名约定
VM头文件遵循以下命名约定:
* `*_internal.h` 头文件包含VM子系统的组件,仅供VM代码使用。
* `*_xnu.h` 头文件包含VM子系统的组件,仅供其他xnu代码使用。
* `*.h` 头文件包含导出给kext的VM子系统组件。
* `vm_iokit.h` 头文件包含导出给iokit子系统的VM子系统组件。
* `vm_ubc.h` 头文件包含导出给ubc子系统的VM子系统组件。
## 模块映射文件命名约定
在简单情况下,`usr/include` 或 `usr/local/include` 的子目录可以用一个独立的模块来表示。在这种情况下,将 `INSTALL_MODULEMAP_MI_DIR` 设置为 `INSTALL_MI_DIR`,并在该处安装 `module.modulemap` 文件。即使在 `usr/local/include` 中的私有模块,也使用 `module.modulemap`;不使用 `module.private.modulemap`。注意:为了保持简单情况,模块名称需要与目录名称完全一致。如果不可能,则需要应用以下方法。
`xnu` 通过安装来自 `usr/include/module.modulemap` 和 `usr/local/include/module.modulemap` 的模块映射文件,为CoreOSModuleMaps中定义的模块做出贡献。`xnu` 模块映射文件的命名约定如下。
a. 理想情况下,模块映射文件覆盖整个目录。覆盖 `usr/include/a/b/c` 的模块映射文件应命名为 `a_b_c.modulemap`。覆盖 `usr/local/include/a/b/c` 的应命名为 `a_b_c_private.modulemap`。
b. 某些头文件是特殊的,需要自己的模块。在这种情况下,模块映射文件将以其定义的模块名称命名。定义模块 `One.Two.Three` 的模块映射文件应命名为 `one_two_three.modulemap`。
## 条件编译
`xnu` 提供以下机制用于条件编译代码:
1. *CPU特性* 如果你保护的代码具有仅因目标CPU架构而异的特定特性,请使用此选项。建议检查架构的特性(例如 `__LP64__`、`__LITTLE_ENDIAN__` 等)。
2. *新特性* 如果你保护的代码整体实现了一个特性,你应该在 `config/MASTER` 中定义一个新特性,并使用生成的 `CONFIG` 预处理器标记(例如,对于名为 `config_virtual_memory` 的特性,检查 `#if CONFIG_VIRTUAL_MEMORY`)。这种做法确保现有特性可以通过简单地切换特性开关而迁移到其他平台。
3. *现有特性* 如果你的代码与现有特性紧密相关,你可以使用它们(例如,如果你的代码实现了仅与可信内核相关的新功能,并更新了可信内核的定义/理解,则使用 `SECURE_KERNEL`)。
建议避免基于目标平台进行编译。`xnu` 不定义 `TargetConditionals.h` 中的平台宏(`TARGET_OS_OSX`、`TARGET_OS_IOS` 等)。
## 调试XNU
默认情况下,内核在发生panic时会重启。此行为可以通过 `debug` 启动参数覆盖——`debug=0x14e` 将导致panic等待调试器附加。要让内核能够被附加的机器调试,请将 `kdp_match_name` 启动参数覆盖为相应的 `ifconfig` 接口。支持以太网、Thunderbolt和串行调试,具体取决于硬件。
使用LLDB调试内核:```text
xcrun -sdk macosx lldb <path-to-unstripped-kernel>
(lldb) gdb-remote [<host-ip>:]<port>
内核的调试信息(dSYM)附带了一组宏来支持内核调试。要实现在附加到内核时自动加载这些宏,请将以下内容添加到 ~/.lldbinit:```text
settings set target.load-script-from-symbol-file true
`tools/lldbmacros` 目录包含这些命令的源代码。
请参阅该目录中的 README 了解用法,或使用内置的 LLDB 帮助:```text
(lldb) help showcurrentstacks