Мне никогда не приходилось заниматься разработкой ядра, но вот в последнее время всё чаще возникает необходимость заглянуть в его исходники, чтобы уточнить для себя, как именно работает тот или иной системный вызов или файл в sysfs/proc. И каждый раз это было жутко неудобно, т. к. IDE сходу не могло адекватно проиндексировать код ядра, чтобы можно было более или менее сносно прыгать по функциям. Поэтому решил потратить какое-то время и разобраться, как можно улучшить эту ситуацию.
C/C++ extension
Если погуглить, то наиболее частая рекомендация сводится к использованию VS Code с расширениями C/C++ и Makefile Tools с примерно следующим .vscode/c_cpp_properties.json:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/arch/x86/include/generated",
"${workspaceFolder}/arch/x86/include",
"${workspaceFolder}/include",
"${workspaceFolder}/**"
],
"forcedInclude": [
"${workspaceFolder}/include/generated/autoconf.h"
],
"dotConfig": "${workspaceFolder}/.config",
"configurationProvider": "ms-vscode.makefile-tools",
"compileCommands": "${workspaceFolder}/compile_commands.json",
"compilerPath": "/usr/bin/gcc",
"intelliSenseMode": "linux-gcc-x64",
"cStandard": "gnu11",
"cppStandard": "gnu++11"
}
],
"version": 4
}
В итоге оно работает, но очень ограниченно: навигация по коду постоянно тормозит и либо вовсе не находит часть символов, либо для части функций находит только их объявление в заголовочном файле, но не реализацию – и нормально работать в таком режиме просто невозможно, т. к. постоянно приходится переключаться на обычный поиск по содержимому файлов.
clangd
Но, как оказалось, у вышеупомянутых расширений от Microsoft есть очень достойная альтернатива в виде расширения clangd, которое работает поверх полноценного language server’а clangd. И это очень хорошая альтернатива, особенно учитывая то, что с относительно недавних пор ядро Linux поддерживает сборку clang’ом.
И оно действительно работает. Да – первоначальное построение индекса занимает какое-то время, но вот зато потом IDE видит все символы и, что не менее важно, осуществляет очень быструю навигацию по ним. Проблемы возникают разве что с заголовочными файлами, для которых в силу понятных причин не может быть однозначной информации, с какими флагами компиляции они будут использоваться.
Приступаем к работе
Клонируем репозиторий:
git clone git@github.com:torvalds/linux.git
Чтобы самому не возиться с конфигурацией ядра, а также работать именно с той, которая будет использоваться в реальной жизни, берём конфигурацию из текущей системы:
cp "/boot/config-$(uname -r)" .config
Устанавливаем пакеты, которые нам понадобятся для сборки:
- Fedora:
sudo dnf install bc bison clang clangd elfutils-libelf-devel flex lld llvm make ncurses-devel openssl-devel xz zstd - Ubuntu:
sudo apt install bc bison clang clangd flex libelf-dev libncurses-dev libssl-dev lld llvm make xz-utils zstd
В .vscode/settings.json задаём общие настройки проекта:
{
"[c]": {
"editor.tabSize": 8,
"editor.insertSpaces": false,
"editor.detectIndentation": false,
"editor.rulers": [80, 100]
},
"files.associations": {
"*.h": "c"
},
"files.exclude": {
"**/modules.order": true,
"**/.*.*.cmd": true,
"**/*.a": true,
"**/*.o": true,
"**/*.ko": true,
"**/*.mod": true,
"**/*.mod.c": true,
"**/*.symvers": true
}
}
Далее устанавливаем расширение clangd – либо через интерфейс VS Code, либо с помощью команды code --install-extension llvm-vs-code-extensions.vscode-clangd.
И я очень рекомендую добавить следующий параметр в настройки VS Code, в котором после -j указать количество ядер в вашем процессоре, т. к. по какой-то причине clangd по умолчанию при индексации использует только часть из них, что на таком большом проекте, как ядро Linux, приводит к невероятно долгой первичной индексации:
{
"clangd.arguments": ["-j", "10"]
}
clangd ожидает, что в корне проекта у нас будет файл compile_commands.json, в котором будут указаны опции компиляции каждого *.c-файла проекта. Именно благодаря этому файлу он может правильно проиндексировать код. Поэтому запускаем следующую команду:
make -j "$(nproc)" LLVM=1 compile_commands.json
… и идём заниматься своими делами – работать оно будет довольно долго.
Как только make отработает, и у нас появится compile_commands.json, открываем в VS Code любой *.c-файл, чтобы стриггерить clangd – и он тут же начнёт индексировать наш проект (в status bar будет соответствующая информация о прогрессе индексации). Тут тоже придётся подождать какое-то время, но только в первый раз – при последующем переоткрытии проекта оно будет подхватываться гораздо быстрее.
Как только индексация закончится, должны заработать все стандартные средства навигации по коду.
Работа с кодом ядра из MacOS
В качестве рабочего инструмента у меня MacBook Pro M1 и, казалось бы, это не самая удобная конфигурация для того, чтобы копаться в исходниках ядра, но, к счастью, это не так.
Устанавливаем OrbStack и создаём себе виртуальную машину:
orbctl create ubuntu kernel
При этом, несмотря на то, что OrbStack поддерживает эмуляцию x86 через Rosetta, в ней нет необходимости, т. к. при сборке ядра мы можем использовать кросс-компиляцию.
Заходим на нашу виртуальную машину:
orb
и клонируем наш репозиторий.
Добавляем в самое начало ~/.vscode/ssh/config следующую строку:
Include ~/.orbstack/ssh/config
А затем с помощью расширения Remote - SSH подключаемся VS Code’ом к нашей виртуальной машине и выполняем все действия, которые были перечислены в предыдущем разделе, но за одним исключением – при вызове make необходимо указать ARCH=x86, чтобы наша ARM’овая виртуалка собрала ядро под целевую архитектуру (x86-64):
make -j "$(nproc)" LLVM=1 ARCH=x86 compile_commands.json