☰
XNU 内核完全指南:从 Darwin 混合内核架构到构建、调试与扩展开发
2026/10/7 5:59:22 网站建设 项目流程
  • 操作系统
  • 驱动开发

【免费下载链接】darwin-xnu

Legacy mirror of Darwin Kernel. Replaced by https://github.com/apple-oss-distributions/xnu

项目地址:https://gitcode.com/gh_mirrors/da/darwin-xnu
点击查看免费下载

XNU(X is Not Unix)是 Darwin 操作系统的核心,也是 macOS 与 iOS 的基础内核。它融合了 CMU 的 Mach 微内核、FreeBSD 子系统与 C++ 编写的 IOKit 驱动框架,形成独特的混合内核。本文以仓库根目录的 README.md 为骨架,结合源码与配套文档,系统讲解 XNU 的目录结构、内核变体(debug / development / release / profile)构建方法、kernelcache 生成与安全回退、kdp 远程调试、条件编译规范、头文件安装机制、内核自检(XNUPOST)与用户态测试,并深入libkdd内核分块数据格式,帮助你从源码层面理解并驾驭这一核心系统。

XNU 是什么:一个“X 不是 Unix”的混合内核

XNU 全称X is Not Unix,是 Darwin 操作系统(macOS 与 iOS 的基础系统)的内核。它并非单一架构的内核,而是三种技术路线的组合(见 README.md):

  • Mach 内核:源自卡内基梅隆大学(CMU)的微内核研究成果,负责任务/线程调度、虚拟内存、IPC 端口等底层抽象,对应仓库中的osfmk/目录;
  • FreeBSD 子系统:提供 POSIX 风格的系统调用、网络协议栈、文件系统与安全模型,对应bsd/目录;
  • IOKit:一套 C++ API 驱动框架,用于编写设备驱动与内核扩展(kext),对应libkern/与iokit/目录。

README 明确说明 XNU 支持x86_64 架构的单处理器与多处理器配置。这一“混合”定位决定了 XNU 的目录组织、构建系统与测试策略都围绕三大部分展开。

XNU 源码树:理解仓库顶层目录

README 给出了源码树的顶层目录清单,理解它们是对后续构建与开发的基础:

目录职责
config/受支持架构与平台的导出 API 配置(如MASTER、各.exports文件)
SETUP/内核配置、版本管理(newvers)与 kextsymbol 管理的基础工具集
EXTERNAL_HEADERS/从其他项目同步的头文件,避免构建时产生依赖环,需随上游源码更新而同步
libkern/C++ IOKit 库代码,负责驱动与 kext 的处理
libsa/内核启动引导代码
libsyscall/面向用户态程序的 syscall 库接口
libkdd/解析内核分块数据(kernel chunked data)的用户态库
makedefs/内核构建的顶层规则与宏定义(MakeInc.*)
osfmk/基于 Mach 内核的子系统(进程、内存、IPC 等)
pexpert/平台相关代码,如中断处理、原子操作等
security/强制访问控制(MAC)策略接口及实现
bsd/BSD 子系统代码
tools/测试、调试与内核性能剖析工具集

例如config/MASTER是机器无关的内核主配置文件,由SETUP/config下的doconf脚本(SETUP/config/doconf)结合机器相关主文件生成具体配置,其中以<foo,bar>属性选择器控制行的取舍,无属性注释的行对所有配置生效。

构建 XNU 内核:KERNEL_CONFIGS 与 ARCH_CONFIGS

构建语法与关键参数

XNU 的 make 系统通过KERNEL_CONFIGS与ARCH_CONFIGS两个变量驱动构建(README.md):

make SDKROOT=<sdkroot> ARCH_CONFIGS=<arch> KERNEL_CONFIGS=<variant>

各参数含义:

  • <sdkroot>:磁盘上 macOS SDK 的路径,默认值为/;
  • <variant>:可选debug、development、release、profile,决定编译标志与内核代码中的断言(assert)是否启用;
  • <arch>:目标架构,如X86_64。

常用构建命令

构建与当前运行系统相同架构的内核,直接执行:

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"

注意:默认架构取构建机器的架构,默认内核配置为DEVELOPMENT。

构建产物包括可引导镜像kernel.[config]以及带符号的二进制kernel.[config].unstripped(例如kernel.development与kernel.development.unstripped)。

安装内核到 DSTROOT

使用install_kernels目标把内核安装到指定 DSTROOT:

make install_kernels DSTROOT=/tmp/xnu-dst

构建 FAT 内核二进制

通过环境变量或 make 参数定义多个架构即可生成 FAT 内核:

make ARCH_CONFIGS="X86_64" exporthdrs all

其他有用的 make 选项

README 列出的常用选项(均已在当前仓库 make 系统中支持):

  • make MAKEJOBS=-j8:使用 8 个进程并行构建,默认值为活跃 CPU 数的 2 倍;
  • make -j8:标准的命令行并行选项同样可用;
  • make -w:追踪递归 make 调用,与VERBOSE=YES配合使用;
  • make BUILD_LTO=0:禁用 LLVM 链接时优化(LTO);
  • make REMOTEBUILD=user@remotehost:在远程主机上执行构建;
  • make BUILD_JSON_COMPILATION_DATABASE=1:生成 Clang JSON Compilation Database(实现见 SETUP/json_compilation_db/);
  • 彩色构建输出:设置环境变量XNU_LOGCOLORS=y,或向 make 传递LOGCOLORS=y。

调试体验优化提示

如果你希望获得更满意的内核调试体验——能访问所有局部变量与参数,又不想承担 DEBUG 内核的额外检查开销——README 建议在 make 命令中追加:

CFLAGS_DEVELOPMENTARM64="-O0 -g -DKERNEL_STACK_MULTIPLIER=2" \ CXXFLAGS_DEVELOPMENTARM64="-O0 -g -DKERNEL_STACK_MULTIPLIER=2"

其中DEVELOPMENT与ARM64应替换为实际构建的配置与平台。以-O0 -g关闭优化、保留完整调试信息,并借KERNEL_STACK_MULTIPLIER放大内核栈,为内核态调试留出更多空间。

调试信息格式:DWARF 与 STABS

默认情况下,XNU 在 install 阶段会生成 DWARF 调试信息仓库(bundle),即名为kernel.development.<variant>.dSYM的包。若想改用更老的STABS格式(调试信息直接嵌入kernel.development.unstripped镜像),设置环境变量后重新构建:

export BUILD_STABS=1 make

构建 KernelCaches:kextd 自动生成与 kextcache 手动构建

单独的内核镜像并不能直接引导测试——必须把内核与 kext 链接成单一可引导镜像(kernelcache)。README 提供了两种机制:

方式一:kextd 自动生成 kernelcache

kextd守护进程会持续监视/System/Library/Extensions目录的变化,因此可以通过以下步骤让系统自动重建 kernelcache:

cp BUILD/obj/DEVELOPMENT/X86_64/kernel.development /System/Library/Kernels/ touch /System/Library/Extensions ps -e | grep kextd

touch触发目录时间戳变化,kextd 检测后即开始重建。

方式二:手动调用 kextcache

kextcache -q -z -a x86_64 -l -n -c /var/tmp/kernelcache.test -K /var/tmp/kernel.test /System/Library/Extensions

其中-K指定内核路径、-c指定输出 kernelcache 路径、-a指定架构。

在目标机器上运行 KernelCache:boot.plist 与安全回退

开发内核与 iBoot 支持通过引导参数(boot-args)配置,使机器能安全地引导进测试内核,若出问题还能回退到之前使用的 kernelcache。README 给出了完整的五步设置:

  1. 创建内核缓存,使用 kextcache 命令生成/kernelcache.test;

  2. 备份现有引导配置到备用文件:

    cp /Library/Preferences/SystemConfiguration/com.apple.Boot.plist /next_boot.plist
  3. 更新 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
  4. 复制新配置到/Library/Preferences/SystemConfiguration/:

    cp /next_boot.plist /Library/Preferences/SystemConfiguration/boot.plist
  5. Bless 卷宗,使新配置在下次引导生效:

    sudo -n bless --mount / --setBoot --nextonly --options "config=boot"

其中--nextonly标志指定boot.plist配置仅用于下一次引导。因此一旦内核 panic,只需重新上电即可轻松恢复到原有内核——这是测试内核时最关键的兜底机制。

生成 tags 与 cscope 数据库

在顶部目录设置好构建环境后,可生成代码导航数据库:

make tags # 在大小写敏感卷上同时生成 ctags 与 etags,大小写不敏感卷仅生成 ctags make TAGS # 仅生成 etags make cscope # 生成 cscope 数据库

条件编译规范:CPU 特性、新功能与既有功能

XNU 提供三类条件编译机制(README.md),选择顺序有明确建议:

  • CPU 特性:若代码仅随目标 CPU 架构而变,优先检查架构特性宏,如__LP64__、__LITTLE_ENDIAN__等,而不是平台宏;
  • 新功能:若代码整体实现了一个新功能,应在config/MASTER中定义新特性,并使用生成的CONFIG预处理标记,例如名为config_virtual_memory的功能用#if CONFIG_VIRTUAL_MEMORY检查。这一实践保证了既有功能只需切换特性开关即可迁移到其他平台;
  • 既有功能:若代码与已有功能强相关,可直接使用既有特性,例如实现与可信内核(trusted kernel)相关的新功能时使用SECURE_KERNEL。

重要约束:XNU 不定义TargetConditionals.h中的平台宏(TARGET_OS_OSX、TARGET_OS_IOS等),应避免基于目标平台编译。已废弃的TARGET_OS_EMBEDDED宏因其定义过宽也应避免使用。从源码结构看,config/MASTER中大量options行(如INET、HW_AST、MACH)正是通过这套机制生成各平台的CONFIG_*开关。

安装新的内核头文件:文件清单与安装列表

安装位置

XNU 将头文件安装到以下位置(README.md):

位置用途
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers内核扩展(kext)使用
$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders仅供 Apple 内部开发的内核扩展使用
$(DSTROOT)/usr/include/用户级应用程序使用
$(DSTROOT)/System/DriverKit/usr/include/用户态驱动(DriverKit)使用
$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeadersApple 内部用户级程序使用

文件清单(file list)

头文件所在目录应有一个 Makefile 负责生成各安装位置的安装清单。如果是在某目录新增第一个头文件,需要参照 bsd/sys/Makefile 创建 Makefile,并把头文件加入正确的文件清单:

  • DATAFILES:用户级可用,安装到$(DSTROOT)/usr/include;
  • DRIVERKIT_DATAFILES:DriverKit 用户态驱动可用,安装到$(DSTROOT)/System/DriverKit/usr/include;
  • PRIVATE_DATAFILES:Apple 内部用户级可用,安装到$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders;
  • KERNELFILES:内核级可用,安装到Kernel.framework的Headers与PrivateHeaders;
  • PRIVATE_KERNELFILES:Apple 内部内核扩展可用,安装到Kernel.framework的PrivateHeaders。

以 bsd/sys/Makefile 为例,可确认这些清单在仓库中的真实形态:DATAFILES、DRIVERKIT_DATAFILES、PRIVATE_DATAFILES、KERNELFILES、PRIVATE_KERNELFILES均以多行变量列出具体头文件。

安装列表(install list)与 MD/MI 划分

Makefile 将上述文件清单组合成安装列表,供构建系统使用。安装列表分为**机器相关(MD,machine-dependent)与机器无关(MI,machine-independent)**两类:头文件若与架构相关,使用机器相关列表(如INSTALL_MD_LIST);若对所有架构都安装,则使用机器无关列表(如INSTALL_MI_LIST)。

README 给出的默认安装列表、成员清单与默认位置如下:

安装列表成员文件清单默认位置
INSTALL_MI_LIST${DATAFILES}$(DSTROOT)/usr/include
INSTALL_DRIVERKIT_MI_LIST${DRIVERKIT_DATAFILES}$(DSTROOT)/System/DriverKit/usr/include
INSTALL_MI_LCL_LIST${PRIVATE_DATAFILES}$(DSTROOT)/System/Library/Frameworks/System.framework/PrivateHeaders
INSTALL_KF_MI_LIST${KERNELFILES}$(DSTROOT)/System/Library/Frameworks/Kernel.framework/Headers
INSTALL_KF_MI_LCL_LIST${KERNELFILES} ${PRIVATE_KERNELFILES}$(DSTROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders
EXPORT_MI_LIST${KERNELFILES} ${PRIVATE_KERNELFILES}仅导出到 xnu 内部(bsd/、osfmk/ 等)供编译,不装入 SDK
INSTALL_MODULEMAP_INCDIR_MI_LIST${MODULEMAP_INCDIR_FILES}$(DSTROOT)/usr/include(INCDIR 根)

若希望把头文件安装到上述路径的子目录,用INSTALL_MI_DIR与EXPORT_MI_DIR指定目录名:

INSTALL_MI_DIR = dirname EXPORT_MI_DIR = dirname

用预处理宏控制头文件内容

单个头文件可能被安装到多个位置,但你未必希望所有内容在所有位置都可见(例如只想导出函数到内核层而非用户层)。解决办法是利用 C 预处理指令(#ifdef、#ifndef、#endif)控制安装前生成的文本:内核只保留条件宏为 TRUE 的代码,并剥离 FALSE 分支。

预定义宏及其可见范围(README.md):

宏可见范围
PRIVATESystem Private Interfaces:xnu 内部可见,并暴露于 System/Kernel framework 的 AppleInternal "PrivateHeaders" 部分
KERNEL_PRIVATE全部 xnu 内核与 Apple 内部内核扩展可见,从用户头文件中省略
BSD_KERNEL_PRIVATE仅 xnu/bsd 模块内可见
MACH_KERNEL_PRIVATE仅 xnu/osfmk 模块内可见
XNU_KERNEL_PRIVATE仅 xnu 内可见
KERNELxnu 与内核扩展内可见,用户级头文件不可见;仅Kernel.framework/Headers与PrivateHeaders安装的头文件包含该代码
DRIVERKIT仅 DriverKit SDK 头文件(用户态驱动使用)可见

添加新的系统调用与测试内核

添加新 syscall

README 预留了 “How to add a new syscall” 章节(未给出详细步骤),但内核系统调用的实际入口分散在osfmk/(Mach trap)与bsd/(BSD syscall)两侧。若要在当前仓库实践,可参照 libsyscall/mach/ 下各.defs文件与bsd/sys/中现有 syscall 的实现来建立调用链。

测试机制总览

XNU 提供多种测试机制(README.md):

  • 断言(Assertions):DEVELOPMENT 与 DEBUG 内核配置默认启用断言,便于开发者验证不变量与条件;

  • 内核上电自检(XNUPOST):XNUPOST配置允许构建带基础测试函数集的内核,在首个用户空间进程启动前运行。由于 XNU 是 Mach 与 BSD 的混合体,测试分两处添加:

    • osfmk/tests/:测试 Mach 内核结构与 API;
    • bsd/tests/:测试 BSD 接口;
    • 详细文档见 osfmk/tests/README.md。
  • 用户级测试:tools/tests/目录存放验证 syscall 及其他特性的测试。使用xnu_testsmake 目标构建全部测试:

    make RC_ProjectName=xnu_tests SDKROOT=/path/to/SDK

    这些测试是独立程序,可在终端运行,通过标准 POSIX 退出码(0 表示成功)和/或 stdout 报告测试状态。可在 tools/tests/Makefile 中确认RC_ProjectName对测试构建的控制逻辑。

深入 XNUPOST:内核上电自检框架

osfmk/tests/README.md 详细描述了 XNUPOST 的特性与用法,这是 README 测试章节的直接延伸:

特性

  • 编译时从 RELEASE 内核中剔除;
  • 通过 boot-argkernPOST启用:0x1用于桌面测试,0x3用于 BATs(自动化测试)环境;
  • 自动跳过专为 panic 设计的内核测试(桌面测试跳过,BATs 环境运行);
  • 无需完整安装到设备,仅 kernelcache 即可运行;
  • 可同时检查断言与 panic 路径。

运行方法

启动 usbterm 并将目标设备置于 iBoot 后:

设置 boot-args 包含 "kernPOST=0x1" # 开机启用内核测试 usb get /path/to/kc # 加载 kernelcache bootx # 引导镜像

在 nanokdp 串口输出中观察带[KTEST] <test> logs标签的日志。

只运行指定测试

通过 boot-argkernPOST_config配置,支持单测与范围:

kernPOST_config=8 # 只运行测试 #8 kernPOST_config=1_3,5_9999 # 跳过 #4,运行 1、2、3 与 5 及之后 kernPOST_config=1_3,4_9999 # 不跳过任何测试,上下界均含

添加新测试

  1. 按测试领域在 osfmk/tests/ 或 bsd/tests/ 添加新.c文件,#include <xnupost.h>获取测试所需的函数与宏,并在osfmk/conf/files或bsd/conf/files中登记(例如osfmk/tests/my_tests.c optional config_xnupost;仓库 bsd/conf/files 中可见bsd/tests/bsd_tests.c optional config_xnupost等真实登记行);
  2. 声明测试函数:kern_return_t my_sample_tests(void);
  3. 加入测试数组,如 osfmk/tests/kernel_tests.c 中的struct xnupost_test kernel_post_tests[]:
struct xnupost_test kernel_post_tests[] = { XNUPOST_TEST_CONFIG_BASIC(my_sample_tests), // 简单测试 XNUPOST_TEST_CONFIG_TEST_PANIC(panic_test) // 预期 panic 的测试 };

仓库中kernel_post_tests[]的实例确认了XNUPOST_TEST_CONFIG_BASIC与XNUPOST_TEST_CONFIG_TEST_PANIC的实际用法(如zalloc_test、test_thread_call等)。

示例测试函数(使用T_*宏族):

kern_return_t my_sample_tests(void) { uint64_t test_begin_timestamp = 0; uint64_t cur_timestamp = 0, tmp; T_SETUPBEGIN; test_begin_timestamp = mach_absolute_time(); T_ASSERT_NOTNULL(test_begin_timestamp, "mach_absolute_time returned 0."); T_SETUPEND; T_LOG("Testing mach_absolute_time for 100 iterations"); for (int i = 0; i < 100; i++) { tmp = mach_absolute_time(); T_EXPECT_TRUE((cur_timestamp <= tmp), "Time went backwards"); cur_timestamp = tmp; } T_LOG("Completed mach_absolute_time tests."); return KERN_SUCCESS; }

以KERN_SUCCESS报告成功,其他错误码报告失败。注意:测试必须做好状态清理,内核预期在测试后继续引导;若无法清理而需要重启,则使用XNUPOST_TEST_CONFIG_TEST_PANIC类型并在函数末尾 panic,让测试控制器在自动化中重启并运行下一个测试。

T_EXPECT 与 T_ASSERT 的区别

  • T_ASSERT宏检查条件,失败时以KERN_FAILURE返回,确保后续测试代码不再执行;
  • T_EXPECT仅报告该测试用例失败,但继续执行后续测试代码。

测试 panic 与断言:panic widget

xnupost 子系统提供注册panic widget的机制,用于检查特定条件并报告测试 SUCCESS/FAILURE。在 debug/development 内核上,panic()代码被改造为调用xnupost_process_panic(),该回调判断测试是否启用、是否注册了检查 panic 的 widget,并据返回值决定动作。widget 可返回:

  • XT_PANIC_UNRELATED:与本测试无关,继续 panic;
  • XT_RET_W_FAIL:报告 FAILURE 并从 panic 返回;
  • XT_RET_W_SUCCESS:报告 SUCCESS 并从 panic 返回;
  • XT_PANIC_W_FAIL:报告 FAILURE 并继续 panic;
  • XT_PANIC_W_SUCCESS:报告 SUCCESS 并继续 panic。

panic widget 数据保存在内部数组struct xnupost_panic_widget(含上下文指针、输出指针、widget 名称与回调函数)中。断言检查示例(来自 osfmk/tests/README.md):

kern_return_t test_foo_arg_assertion(void) { void * assert_retval = NULL; kern_return_t kr = T_REGISTER_ASSERT_CHECK("arg > 0", &assert_retval); T_ASSERT(kr == KERN_SUCCESS, "register assertion handler"); foo(-1); /* this will cause assert to fire */ T_ASSERT(assert_retval == (void *)XT_RET_W_SUCCESS, "verify assertion was hit"); }

内核数据描述符:libkdd 与 Kernel Chunked Data

为什么需要 libkdd

XNU 在不同 API 间传递数据有多种格式,最标准的是 syscall 参数;但对复杂数据,内核往往依赖以 C 结构体保存在内存中的数据。这种打包式传输机制很脆弱,会导致用户态程序与内核 API 之间的接口破坏。libkdd/ 目录存放用户态库,用于解析与内核同版本提供的自定义数据。内核分块数据(kernel chunked data)格式在 libkdd/README.md 中有完整描述,库 API 定义在 libkdd/kdd.h。

KCDATA 格式布局

数据按通用格式组织为一段连续的块(chunk):

| 8 - bytes | |---------------------------| ------ offset = 00 | type = MAGIC | LENGTH | # BEGIN Header | 0 | |---------------------------| ------ offset = 16 | type | size | # chunk header | flags | |---------------------------| ------ offset = 32 | data | # arbitrary data (len=16) |___________data____________| |---------------------------| ------ offset = 48 | type | size | # chunk header | flags | |---------------------------| ------ offset = 64 | data | # arbitrary data (len=32) | data | |___________data____________| |---------------------------| ------ offset = 96 | type = END | size=0 | # chunk header | 0 |

type字段描述数据类型,例如TASK_CRASHINFO_UUID表示其后为 uuid;这些类型需在task_corpses.h中定义,便于用户态检查工具消费。部分类型范围预留给 int、long 等特殊类型。

这种可扩展格式的最大价值在于:内核可以按需加入更多信息,而无需用户态工具重编译即可保持兼容——例如引入新的rusage结构版本不会破坏现有工具。

带描述的自描述数据

进一步地,数据可以携带文本描述。内核侧示例:

kcdata_add_uint64_with_description(cdatainfo, 0x700, "NUM MACH PORTS");

用户态工具即使事先未编译进对该字段的了解,也能根据描述打印数据。README 给出了真实的缓冲区分块样例(PID、PARENT PID等带描述块)。

复合数据的容器标记

对于需要包含多个可选字段的复杂内核数据类型,使用容器标记(container markers)打包。例如 stackshot 收集某任务在众多子系统中的状态(io 统计、vm 计数、进程名/标志、syscall 计数):

kcdata_add_container_marker(kcdata_p, KCDATA_TYPE_CONTAINER_BEGIN, STACKSHOT_KCCONTAINER_TASK, task_uniqueid); // add multiple data, or add_<type>_with_description()s here kcdata_add_container_marker(kcdata_p, KCDATA_TYPE_CONTAINER_END, STACKSHOT_KCCONTAINER_TASK, task_uniqueid);

按需描述自定义格式

得益于格式的自描述特性,内核提供方可描述某个数据类型(用唯一数字标识)并在缓冲区中使用,消费方解析类型信息即可理解数据。示例:描述结构体sample_disk_io_stats的字段布局:

struct sample_disk_io_stats { uint64_t disk_reads_count; uint64_t disk_reads_size; uint64_t io_priority_count[4]; uint64_t io_priority_size; } __attribute__ ((packed)); struct kcdata_subtype_descriptor disk_io_stats_def[] = { {KCS_SUBTYPE_FLAGS_NONE, KC_ST_UINT64, 0 * sizeof(uint64_t), sizeof(uint64_t), "disk_reads_count"}, {KCS_SUBTYPE_FLAGS_NONE, KC_ST_UINT64, 1 * sizeof(uint64_t), sizeof(uint64_t), "disk_reads_size"}, {KCS_SUBTYPE_FLAGS_ARRAY, KC_ST_UINT64, 2 * sizeof(uint64_t), KCS_SUBTYPE_PACK_SIZE(4, sizeof(uint64_t)), "io_priority_count"}, {KCS_SUBTYPE_FLAGS_ARRAY, KC_ST_UINT64, (2 + 4) * sizeof(uint64_t), sizeof(uint64_t), "io_priority_size"}, };

随后把类型定义写入缓冲区:

kcdata_add_type_definition(kcdata_p, KCTYPE_SAMPLE_DISK_IO_STATS, "sample_disk_io_stats", &disk_io_stats_def[0], sizeof(disk_io_stats_def)/sizeof(struct kcdata_subtype_descriptor));

调试内核:kdp 远程调试协议与 lldb

基本调试流程

XNU 支持通过远程内核调试协议kdp进行调试(README.md)。默认内核在 panic 时配置为重启;为调试活内核,kdp 服务器会监听以太网上的 UDP 连接。常见引导参数:

  • debug=0x144:设置调试变量,在 panic 时启动 kdp debugserver;
  • -v:在内核启动日志打印到屏幕(默认 XNU 只显示带引导图案的灰屏);
  • kdp_match_name=en1:覆盖 kdp 默认端口选择,支持以太网、雷电(thunderbolt)与串口调试。

调试 panic 的内核时,配合带符号的未剥离内核二进制使用 LLVM 调试器 lldb:

lldb kernel.development.unstripped

连接 panic 机器使用kdp_remote [ip addr]或gdb_remote [hostip : port]命令。

加载内核专用调试脚本

每个内核在构建时都会打包内核专用的调试脚本。出于安全考虑,这些特殊命令与脚本在 lldb 连接机器时不会自动加载。若希望始终加载这些宏,在~/.lldbinit中添加:

settings set target.load-script-from-symbol-file true

tools/lldbmacros目录保存所有这些命令的源码,详细用法见 tools/lldbmacros/README.md。

lldbmacros:内核调试命令与类型摘要

从 tools/lldbmacros/README.md 可以看到,这套框架建立在 lldb 的 Python 脚本桥接之上,命令与摘要通过@lldb_command、@lldb_type_summary()等装饰器注册(入口脚本为 tools/lldbmacros/xnu.py)。核心用法:

  • kgmhelp列出xnu.py提供的全部命令;
  • 启用内核类型摘要:(lldb) showlldbtypesummaries,之后print (thread_t)0x...即可直接看到线程摘要(thread_id、processor、优先级、状态、等待队列等);
  • 通用选项(--之后):-h帮助、-s <regexp>只打印匹配行、-o <file>输出到文件、-v增加详细度、-p <plugin>交给插件处理;
  • 长输出命令可用-- -i获得即时终端输出;
  • 修改命令代码后用(lldb) xnudebug reload memory热重载,不必退出调试会话;
  • 显示原始数据而不触发摘要用showraw命令;
  • 开发者可通过环境变量DEBUG_XNU_LLDBMACROS=1禁用从符号自动加载宏,从而加载本地修改版xnu.py。

该框架还被随构建打包进每个内核的 dSYM,使 lldb 命令与内核源码变更保持版本锁定(rev-locking)。

总结

从本文可以看到,XNU 是一个层次清晰的混合内核工程:README.md 给出了从源码树、构建、kernelcache、条件编译、头文件安装到测试与调试的完整开发链路;config/MASTER、bsd/sys/Makefile 等配置文件印证了构建与安装机制;osfmk/tests/README.md 与 osfmk/tests/kernel_tests.c 展示了内核自检框架的实际形态;libkdd/README.md 阐明了内核与用户态之间的自描述数据协议;tools/lldbmacros/README.md 则提供了从崩溃现场提取信息的实用工具链。掌握这些环节,你就具备了在 XNU 源码层面完成从构建、测试到调试的完整开发闭环能力。

  • 操作系统
  • 驱动开发

【免费下载链接】darwin-xnu

Legacy mirror of Darwin Kernel. Replaced by https://github.com/apple-oss-distributions/xnu

项目地址:https://gitcode.com/gh_mirrors/da/darwin-xnu
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询