
Гибридное ядро, объединяющее Mach, FreeBSD и IOKit для macOS и iOS. Обеспечивает основные системные службы, каркас драйверов и принудительное применение политик безопасности на x86_64 и ARM64.
Ядро XNU является частью операционной системы Darwin и используется в macOS и iOS. XNU — это аббревиатура от X is Not Unix. XNU — это гибридное ядро, объединяющее ядро Mach, разработанное в Университете Карнеги — Меллона, с компонентами из FreeBSD и написанным на C++ API для драйверов, называемым IOKit. XNU работает на архитектурах x86_64 и ARM64 как для однопроцессорных, так и для многопроцессорных конфигураций.
config — конфигурации для экспортируемых API на поддерживаемых архитектурах и платформах.SETUP — базовый набор инструментов для настройки ядра, управления версиями и управления символами kext.EXTERNAL_HEADERS — заголовки, взятые из других проектов, чтобы избежать циклов зависимости при сборке. Эти заголовки должны регулярно синхронизироваться при обновлении исходного кода.libkern — код библиотеки C++ IOKit для управления драйверами и kext.libsa — код начальной загрузки ядра для запуска.libsyscall — интерфейс системных вызовов для пользовательских программ.libkdd — исходный код пользовательской библиотеки для разбора данных ядра, например, структурированных данных ядра.makedefs — правила и определения верхнего уровня для сборки ядра.osfmk — подсистемы на основе ядра Mach.pexpert — платформозависимый код, такой как обработка прерываний, атомарные операции и т.д.security — интерфейсы политик обязательного контроля доступа и связанная реализация.bsd — код подсистем BSD.tools — набор утилит для тестирования, отладки и профилирования ядра.DEVELOPMENTСистема сборки xnu может собирать ядро на основе переменных 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` на соответствующие сборку и платформу.
> Дополнительные флаги: Вы можете передавать дополнительные флаги компилятору C в командной строке с помощью параметра сборки `EXTRA_CFLAGS`. Эти флаги добавляются к базовым `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 путем чтения CFBundleVersion из файла System/Library/Extensions/System.kext/Info.plist. Это можно настроить, установив переменную RC_DARWIN_KERNEL_VERSION в окружении или в командной строке make.
Подробнее см. в doc/building/xnu_version.md.
По умолчанию во время фазы установки создается репозиторий отладочной информации DWARF; это «пакет» с именем kernel.development.<variant>.dSYM. Чтобы выбрать более старый формат отладочной информации STABS (где отладочная информация встроена в образ kernel.development.unstripped), установите переменную окружения BUILD_STABS.```sh export BUILD_STABS=1 make
## Сборка KernelCaches
Для тестирования ядра xnu необходимо собрать kernelcache, который связывает kext'ы и
ядро в единый загрузочный образ.
Для сборки kernelcache можно использовать следующие механизмы:
* Использование автоматической генерации kernelcache с помощью `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` для сборки нового kernelcache.
```text
kextcache -q -z -a x86_64 -l -n -c /var/tmp/kernelcache.test -K /var/tmp/kernel.test /System/Library/Extensions
```
## Загрузка KernelCache на целевой машине
Разработочное ядро и iBoot поддерживают настройку аргументов загрузки, чтобы можно было безопасно загрузиться в тестовое ядро и, в случае проблем, безопасно вернуться к ранее используемому kernelcache.
Для настройки такого режима выполните следующие шаги:
1. Создайте кеш ядра с помощью команды kextcache как `/kernelcache.test`
2. Скопируйте существующие конфигурации загрузки в альтернативный файл
```sh
cp /Library/Preferences/SystemConfiguration/com.apple.Boot.plist /next_boot.plist
```
3. Обновите kernelcache и boot-args для вашей конфигурации
```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` только для одной загрузки.
В случае паники ядра вы сможете легко перезагрузиться и вернуться к исходному ядру.
## Создание тегов и cscope
Настройте среду сборки и из корневой директории выполните:
make tags # это создаст ctags и etags на case-sensitive томе, только ctags на case-insensitive
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, который
создаёт список файлов для установки в разные расположения.
Если вы добавляете первый заголовочный файл в директорию, вам потребуется
создать Makefile, аналогичный `xnu/bsd/sys/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`, но для внутреннего использования Apple на пользовательском уровне
для встраиваемых ОС как `EXTRA_PRIVATE_DATAFILES` -
`$(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
SDK ExclaveKit -
`$(DSTROOT)/System/ExclaveKit/usr/include`
k. `EXCLAVECORE_DATAFILES`: Для установки заголовочного файла для внутреннего использования Apple
SDK ExclaveCore -
`$(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`: Устанавливает заголовочный файл в расположение,
доступное для внутреннего использования Apple для клиентов IOKit пользовательского пространства.
Расположение -
$(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`: Устанавливает заголовочный файл в расположение,
доступное для внутреннего использования Apple для ExclaveKit.
Расположение -
$(DSTROOT)/System/ExclaveKit/usr/include
Определение -
INSTALL_EXCLAVEKIT_MI_LIST = ${EXCLAVEKIT_DATAFILES}
l. `INSTALL_EXCLAVECORE_MI_LIST`: Устанавливает заголовочный файл в расположение,
доступное для внутреннего использования Apple для ExclaveCore.
Расположение -
$(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 и доступны в пользовательских/ядерных заголовках, установленных в разделах "PrivateHeaders" фреймворков System и Kernel в составе AppleInternal.
2. `KERNEL_PRIVATE` : Если определён, заключённый код доступен всему ядру xnu и внутренним расширениям ядра Apple, но исключён из пользовательских заголовков.
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`: Если определён, заключённый код виден исключительно в заголовках SDK DriverKit, используемых драйверами пространства пользователя.
8. `EXCLAVEKIT`: Если определён, заключённый код виден исключительно в заголовках SDK ExclaveKit.
9. `EXCLAVECORE`: Если определён, заключённый код виден исключительно в заголовках SDK ExclaveCore.
10. `MODULES_SUPPORTED` Если определён, заключённый код виден исключительно в местах, поддерживающих модули/Swift (т.е. не в фреймворках System или Kernel).
## Соглашение об именовании заголовочных файлов VM
Заголовки VM следуют следующим соглашениям об именовании:
* Заголовки `*_internal.h` содержат компоненты подсистемы VM, предназначенные только для использования кодом VM.
* Заголовки `*_xnu.h` содержат компоненты подсистемы VM, предназначенные только для использования другим кодом xnu.
* Заголовки `*.h` содержат компоненты подсистемы VM, экспортируемые в kext'ы.
* Заголовок `vm_iokit.h` содержит компоненты подсистемы VM, экспортируемые в подсистему iokit.
* Заголовок `vm_ubc.h` содержит компоненты подсистемы VM, экспортируемые в подсистему ubc.
## Соглашение об именовании файлов модульных карт
В простом случае подкаталог `usr/include` или `usr/local/include` может быть представлен отдельным модулем. В таком случае установите `INSTALL_MODULEMAP_MI_DIR` в `INSTALL_MI_DIR` и поместите туда файл `module.modulemap`. `module.modulemap` используется даже для приватных модулей в `usr/local/include`; `module.private.modulemap` не используется. Предостережение: чтобы оставаться в простом случае, имя модуля должно точно совпадать с именем каталога. Если это невозможно, потребуется применить следующий метод.
`xnu` вносит вклад в модули, определённые в CoreOSModuleMaps, устанавливая файлы карт модулей, которые берутся из `usr/include/module.modulemap` и `usr/local/include/module.modulemap`. Соглашение об именовании файлов карт модулей `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. *Характеристики ЦП* Если код, который вы защищаете, имеет конкретные характеристики, которые будут различаться только в зависимости от целевой архитектуры ЦП, используйте этот вариант. Предпочитайте проверять особенности архитектуры (например, `__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
По умолчанию ядро перезагружается при панике. Это поведение можно переопределить с помощью boot-arg `debug` — `debug=0x14e` приведёт к тому, что при панике ядро будет ожидать подключения отладчика.
Чтобы загрузить ядро для отладки с подключённой машины, переопределите boot-arg `kdp_match_name`, указав соответствующий интерфейс `ifconfig`.
Поддерживаются отладка через Ethernet, 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