Разработка драйверов для Windows традиционно ассоциируется с языком C/C++ и набором инструментов WDK (Windows Driver Kit). Однако с ростом популярности Rust появляется всё больше энтузиастов и профессионалов, желающих использовать безопасный и надёжный язык для столь «низкоуровневых» задач. В недавней статье «Writing a Simple Driver in Rust» автор делится опытом создания простого WDM-драйвера на Rust. Ниже — мой взгляд на основные моменты и технические детали, которые могут быть интересны как новичкам, так и матерым «драйверостроителям».
Почему именно Rust?
🦀 Безопасность памяти и потоков. Rust славится встроенной защитой от целого ряда ошибок, традиционно свойственных С/С++ (утечки памяти, гонки данных и прочих проблем). На уровне ядра такая защита особенно ценна: любой сбой может привести к синим экранам (BSOD) и другим неприятным последствиям.
🦾 Системный язык. В отличие от высокоуровневых языков, Rust предоставляет тонкий контроль над ресурсами и компоновкой данных. Этот контроль необходим для разработки драйверов, где каждая операция может работать напрямую с памятью и аппаратными регистрами.
⚙️ Экосистема. Система сборки (Cargo) и репозиторий пакетов (crates.io) позволяют инкапсулировать повторяющиеся задачи и быстрее подключать полезные библиотеки.
Подготовка окружения и ключевые нюансы
⚙️ Установка WDK
Для Windows-драйверов Rust не отменяет классического окружения: нужен установленный Windows Driver Kit, а также LLVM/Clang. Многие существующие руководства (включая Windows Drivers-rs) подсказывают, как правильно настроить проект.
📁 Структура проекта
Создаём типичную библиотеку Rust:
new --lib booster
Внутри папки booster создаём файл build.rs и корректируем Cargo.toml, чтобы настроить статическую линковку и зависимости от wdk-, wdk-sys-, wdk-alloc-пакетов. Главная задача — заставить Cargo корректно собрать драйвер как «cdylib» (по сути, аналог DLL), совместимую с ядром Windows.
🚫 no_std
Так как в пространстве ядра отсутствует стандартная библиотека C runtime, Rust-проекты для драйверов включают директиву #![no_std]. При этом «вырубается» часть стандартного функционала. На помощь приходят alloc-библиотеки, позволяющие использовать Vec, String и другие полезные типы без полного std.
Тонкости реализации и «фишки» Rust-драйвера
🔌 Глобальный аллокатор
Внутри ядра нельзя просто так использовать привычный malloc. Вместо этого Rust-пакеты для WDK предоставляют собственный глобальный аллокатор (WdkAllocator), который делегирует память вызовам вроде ExAllocatePool2. Это важно для корректного управления ресурсами внутри ядра Windows.
🔐 Обработка сбоев
В пользовательском пространстве сбой в Rust обычно вызывает «развёртывание стека» и завершается сообщением об ошибке. В драйверах такой сценарий недопустим — может быть критически поздно. Поэтому часто используется режим «немедленного завершения при сбое» («abort on panic»), при котором драйвер либо аварийно завершает выполнение, либо вызывается BSOD, предотвращая повреждение ядра.
🔧 Связывание с Windows API
Через wdk_sys подключается низкоуровневый API (например, IoCreateDevice, PsLookupThreadByThreadId и т.д.). Дополнительно в Rust мы имеем дело со структурами UNICODE_STRING, IRP-структурами и другими элементами WDK. Для удобства можно писать вспомогательные функции (например, конвертация UTF-16 ↔ Rust String).
Что делает примерный драйвер «Booster»?
Идея проста: драйвер позволяет изменять приоритет потока в системе. В классических уроках по драйверам часто приводят подобный пример на C/C++. А здесь показано, как выполнить аналогичную задачу на Rust:
📝 Создаём устройство: \Device\Booster, а также символьную ссылку \??\Booster, чтобы доступ к драйверу можно было получить обычным вызовом CreateFile из user-space.
📝 Обрабатываем IRP:
- 🏗️ IRP_MJ_CREATE и IRP_MJ_CLOSE просто отвечают «успешно», чтобы пользователь мог открывать и закрывать дескриптор устройства.
- 🏋️ IRP_MJ_WRITE принимает структуру с Thread ID и нужным приоритетом, затем вызывает PsLookupThreadByThreadId, а после — KeSetPriorityThread. Если всё хорошо, приоритет потока меняется.
Личный взгляд: плюсы и минусы подхода
🤔 Плюсы:
- Безопасность: Rust сводит к минимуму риск случайной порчи памяти и проблем с синхронизацией.
- Расширяемость: экосистема Rust активно развивается, появляются новые обёртки для системных вызовов, упрощающие код драйверов.
- Удобство в больших проектах: типичные шаблоны вроде RAII (Resource Acquisition Is Initialization - Инициализация через захват ресурса) становятся более предсказуемыми, чем в C++ без умных указателей.
⚠️ Минусы/Сложности:
- Ограниченные инструменты отладки: Rust-драйверы всё ещё «экзотика»; многие отладочные инструменты и расширения от Visual Studio ориентированы на C/C++.
- Ранний этап экосистемы: пакеты wdk и wdk-sys не идеальны; некоторые вещи приходится делать вручную, особенно структуры и функции, на которые ещё нет обёрток.
- Процесс подписывания и установка: драйвер остаётся драйвером. Потребуется ручная подпись (signtool) и загрузка тестового сертификата для отладки на Test Mode.
Как это собрать и установить?
🔨 Сборка:
- Настраиваем Cargo (см. пример Cargo.toml и build.rs).
- Запускаем cargo build --release или cargo build --features nightly при необходимости.
- Получаем booster.dll (который по сути является драйвером). Можно переименовать его в booster.sys или оставить как есть — Windows поддерживает и «.dll» (хотя это нестандартно).
🔏 Подпись и загрузка:
- Генерируем или берём тестовый сертификат (например, через Visual Studio) и подписываем signtool sign /n wdk /fd sha256 <путь_к_dll>.
- Устанавливаем драйвер при помощи sc create booster type= kernel binPath= ..., затем sc start booster.
Что дальше?
Проект «Booster» демонстрирует лишь базовый функционал: создание устройства, обмен данными через IRP и несколько системных вызовов. Однако это лишний раз подтверждает, что Rust подходит для разработки драйверов — пусть и с некоторыми компромиссами и нюансами в виде небезопасных (unsafe) блоков и ручной настройки сборки.
На мой взгляд, массовое принятие Rust для драйверов — лишь вопрос времени. Microsoft уже ведёт эксперименты по внедрению Rust в некоторых модулях Windows. Для нас, разработчиков, это шанс сочетать «низкоуровневую мощь» C/C++ с более безопасным и современным подходом к управлению памятью.
Ссылки на оригинальный материал и дополнительные ресурсы
Пусть ваш Rust-драйвер будет стабилен, надёжен и никогда не увидит «синий экран»!