Как пересобрать ядро для WSL2 и остаться в живых

fbe552777f4ba9ac056011627890c75f

Мы активно пользуемся WSL2 для того, чтобы открывать линуксовые коры в Visual Studio. Для обеспечения консистентности символов и коры мы монтируем squashfs образ, созданный в целевой системе. После очередного апдейта целевого дистрибутива, у нас всё сломалось — squash монтировался, но мог посередине файла выдать ошибку чтения, записав в dmesg что-то типа

[ 17.157892] SQUASHFS error: xz decompression failed, data probably corrupt
[ 17.158705] SQUASHFS error: Failed to read block 0×5c3cb5e: -5

Естественно, точно такой же сквош на целевой системе работал как часы и никаких ошибок не выдавал.

По мере исследования выяснилось, что ядро WSL не включает опции ядра, связанные с BCJ фильтрацией в XZ.

BCJ фильтр

Алгоритм XZ это на самом деле LZMA плюс интервальное кодирование как разновидность арифметического. В свою очередь, LZMA можно очень упрощенно описать как замену одинаковых участков сжимаемого текста на ссылки 'назад' — сжатие по словарю, а интервальное кодирование — как выбор самого короткого числа, описывающего сжимаемый текст с точки зрения вероятностей отдельных символов в месте их появления.

Поскольку используется словарное сжатие LZMA, важно иметь много одинакового текста. А в исполняемых файлах много вызовов библиотечных функций. Эти вызовы выглядят примерно так:

   0x000000000040112a <+4>:	    bf 10 20 40 00	mov    $0x402010,%edi
   0x000000000040112f <+9>:	    e8 fc fe ff ff	call   0x401030 
   0x0000000000401134 <+14>:	bf 1c 20 40 00	mov    $0x40201c,%edi
   0x0000000000401139 <+19>:	e8 f2 fe ff ff	call   0x401030 ё

Два вызова puts () в листинге выше используют инструкцию call relative to next instruction, но на самом деле указывают на одно и то же место. Если бы мы нашли все такие вызовы puts () и поменяли их с относительного адреса на абсолютный, то у нас было бы очень много одинаковых пятибайтных кусочков. То же самое справедливо для инструкций ветвления (Branch) и безусловных переходов (Jump) — отсюда и название BCJ фильтра — Branch/Call/Jump.

Естественно, такой способ подходит только для исполняемого кода и замедляет обработку — например, при тестировании сборки Fedora Linux за 30 мб cжатия от 1.7 Гб образа заплатили 18 минутами распаковки (примерно в два раза медленнее). Но кому-то и такое может зайти. Дефолт такой дефолт.

Впрочем, раз squashfs по дефолту собирается с BCJ фильтром, надо соответствующие опции включить в ядре. А для этого ядро надо пересобрать.

Сборка ядра

В качестве подопытного кролика будет выступать AlmaLinux 9 -, но не так сложно найти гайды по пересборке ядра в базовой для WSL Ubuntu. В общем и целом процесс отличается от «обычной» сборки ядра для линукса только файлом настроек.

Внимание!
В отличие от линукса, NTFS регистронезависим, и поэтому могут быть проблемы с доступом к исходникам во время компиляции! Все действия этого раздела проводятся внутри WSL2 на собственной файловой системе!

Поставим всё, что для сборки понадобится:

sudo dnf install --enablerepo=crb kernel-devel bc \
   git dwarves ncurses-devel binutils{,-gold} rsync kmod

kernel-devel это немножко перебор -, но зато ставит большую часть инструментария для сборки ядра.
ncurses-devel это для menuconfig — текстового интерфейса конфигурации ядра.

Скачаем исходники ядра с настройками «от Микрософт»

git clone https://github.com/microsoft/WSL2-Linux-Kernel.git --depth=1 -b linux-msft-wsl-5.15.y
cd WSL2-Linux-Kernel

На момент публикации основная ветка — linux-msft-wsl-5.15.y, следующий кандидат — linux-msft-wsl-6.1.y.
Если нужно конкретное ядро — есть таги вида linux-msft-wsl-... — например, linux-msft-wsl-6.1.21.2

Файл с настройками ядра можно найти в Microsoft/config-wsl.
Теперь нужно изменить эти настройки — руками в файле или через menuconfig

make menuconfig KCONFIG_CONFIG=Microsoft/config-wsl

Конкретно для работы BCJ было достаточно добавить

CONFIG_XZ_DEC_X86=y

но в ядре есть и опции для BCJ фильтрации кода других архитектур.

Для точного определения какое ядро используется можно поменять суффикс локальной версии (General setupLocal Version) и/или добавить git sha к суффиксу (General setupAutomatically append version information).

Дальше — сборка ядра и установка модулей.
Базовая настройка от Микрософта не имеет настроек компиляции в модули, но вдруг именно вам надо?
Заголовки ядра тоже вещь очень на любителя.

make -j $(nproc) KCONFIG_CONFIG=Microsoft/config-wsl
make modules_install headers_install

Предупреждение — мне ни модули ни хедеры не пригодились, поэтому привожу команду только для полноты картины. Возможно, потребуется что-то ещё чтобы модули загружались автоматически.

Установка и проверка нового ядра

Если make закончился успешно, то новое ядро можно найти в arch/x86/boot/bzImage. Для использования в WSL его надо вытащить наружу

mkdir -p /mnt/c/Users//.wsl-kernels/
cp arch/x86/boot/bzImage /mnt/c/Users//.wsl-kernels/

WSL2 использует конфигурационный файл .wslconfig для юзероспецифичных настроек WSL.
Для использования своего ядра надо туда записать

[wsl2] 
kernel=C:\\Users\\\\.wsl-kernels\\bzImage

потом перезапустить подсистему WSL

wsl --shutdown
wsl

и потом изнутри WSL проверить какое ядро запущено

uname -r

Дополнительные ссылки

Ядерная реализация XZ в линуксе взята из https://github.com/tukaani-project/xz-embedded

LZMA — https://neerc.ifmo.ru/wiki/index.php? title=Алгоритм_LZMA, https://www.7-zip.org/sdk.html

интервальное кодирование — https://ru.wikipedia.org/wiki/Интервальное_кодирование

Другие маны по сборке ядра для WSL2:
https://github.com/Zhoneym/WSL-Docs/blob/main/WSL/compile-wsl2-kernel.md
https://alexkaouris.medium.com/run-your-own-kernel-with-wsl2–21e3143e014e

© Habrahabr.ru