使用Rust进行Windows驱动开发(HelloWorld)
依赖
往常进行Windows驱动开发一般使用Visual Studio和WDK,使用Rust开发Windows驱动的话就不用VS了,可惜宇宙第一IDE不支持Rust,要是支持的话使用VS写Rust也未尝不可,嗯。
虽然说不用VS,但是使用Rust开发Windows驱动也需要依赖一些工具。
winget
在安装其它工具之前建议大家安装winget,可以方便后续的操作。
下载安装可以直接去
https://github.com/microsoft/winget-cli/releases
下载Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
双击运行安装即可。
「安装软件需要Windows版本在Windows 10 1809 (或更高版本)」,如果系统版本太低,那么后续的操作也无法进行了。
修改source
默认winget下载包是从github的https://github.com/microsoft/winget-pkgs这个仓库下载的
如果你的网络访问Github慢的话可以考虑修改源(不过我测试效果不怎么好)。
winget source remove winget
winget source add winget https://mirrors.ustc.edu.cn/winget-source
安装好winget即可快速安装以下依赖。
Rust和Rust-MSVC-Toolchain
Rust
winget安装
Rust的安装可以直接通过winget安装
winget install Rustlang.Rustup
官网安装(推荐)
上面会直接安装rustup,然后执行rustup-init去下载rust相关的工具链,但是由于rustup源在境外,也会很慢。
去到Rust官网https://www.rust-lang.org/learn/get-started下载Rust-init.exe,
执行后选择1、Quick install via the Visual Studio Community installer,将会引导你安装VS、MSVC编译器和WDK
选择2则自定义安装,选择3则会默认安装msvc工具链。
如果选择1的话则会安装VS,选择2则自定义安装,选择3则标准按照,这里推荐选择3默认按照。
如果下载慢,可以在终端执行前设置源
set RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
./rust-init.exe
Rust-MSVC—Toolchain
Rust安装后一般都会安装一个toolchain,如果是安装的Mingw的GNU-Toolchain,则需要安装MSVC-Toolchain,通过命令查看当前Rust的toolchain以及安装切换其它的toolchain。不过上面如果选择3标准安装的话,默认应该装的是msvc toolchain,下面就不用操作了。
# 查看当前所有的toolchain
rustup toolchain list
# 安装msvc toolchain
rustup toolchain install stable-x86_64-pc-windows-msvc
# 切换toolchain
rustup default stable-x86_64-pc-windows-msvc
LLVM
LLVM主要使用用于生成Windows驱动的绑定,安装也可以直接通过winget即可
winget install llvm
下载慢可以手动下载安装:
https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.5/LLVM-18.1.5-win64.exe
SDK
安装了msvc toolchain还需要下载它的链接器,可以下载C++生成工具安装,不用安装VS。
https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/
这个工具和VS安装差不多,但是可以不安装VS,我们只需要安装以下几个:
1、C++生成工具核心功能
2、Windows SDK 和WDK版本选择一致(最低22621)
3、MSVC生成工具(随意版本)
安装这三个之后,需要的相关工具就都有了。
WDK
WDK有几个版本,可以选择安装
# 搜索WDK版本列表
winget search wdk
# 搜索WDK得到ID,安装WDK
winget install Microsoft.WindowsWDK.10.0.22621
也可以去微软官网下载安装。
「SDK和WDK版本选择22621,因为这个版本的安装路径才符合wdk-build的搜索规则,不然执行cargo make会报找不到文件的错误」
Cargo-Make(可选)
如果需要执行cargo make则需要安装
cargo install cargo-make
工程
1、新建工程
cargo new rust_driver --lib
2、添加依赖
cd rust_driver
cargo add --build wdk-build
cargo add wdk wdk-sys wdk-alloc wdk-panic
3、工程文件
cargo.toml修改
[package]
name = "rust_driver"
version = "0.1.0"
edition = "2021"
[dependencies]
wdk = "0.2.0"
wdk-alloc = "0.2.0"
wdk-panic = "0.2.0"
wdk-sys = "0.2.0"
[build-dependencies]
wdk-build = "0.2.0"
[lib]
crate-type = ["cdylib"]
[package.metadata.wdk]
[profile.dev]
panic = "abort"
lto = true # optional setting to enable Link Time Optimizations
[profile.release]
panic = "abort"
lto = true # optional setting to enable Link Time Optimizations
build.rs
同cargo.toml同一级
// Copyright (c) Microsoft Corporation
// License: MIT OR Apache-2.0
use std::fs::{OpenOptions};
use std::io::Write;
fn p(s: String){
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open("output.txt")
.expect("Unable to open file");
writeln!(file, "{}", s).expect("Unable to write to file");
}
fn main() -> Result<(), wdk_build::ConfigError> {
p(format!("This is a debug message from build.rs"));
let wdk_sys_crate_dep_key =
format!("DEP_WDK_{}", "wdk_config".to_ascii_uppercase());
let wdk_crate_dep_key = format!(
"DEP_WDK-SYS_{}",
"wdk_config".to_ascii_uppercase()
);
let wdk_sys_crate_config_serialized = std::env::var(&wdk_sys_crate_dep_key);
let wdk_crate_config_serialized = std::env::var(&wdk_crate_dep_key);
match wdk_sys_crate_config_serialized {
Ok(s) =>{
p(format!("{}",s.replace("\"KMDF\":{\"kmdf_version_major\":1,\"kmdf_version_minor\":33}","\"WDM\":[]")));
std::env::set_var(&wdk_sys_crate_dep_key,s.replace("\"KMDF\":{\"kmdf_version_major\":1,\"kmdf_version_minor\":33}","\"WDM\":[]"));
}
Err(_) =>{}
}
match wdk_crate_config_serialized {
Ok(s) =>{
p(format!("{}",s.replace("\"KMDF\":{\"kmdf_version_major\":1,\"kmdf_version_minor\":33}","\"WDM\":[]")));
std::env::set_var(&wdk_crate_dep_key,s.replace("\"KMDF\":{\"kmdf_version_major\":1,\"kmdf_version_minor\":33}","\"WDM\":[]"));
}
Err(_) =>{}
}
wdk_build::Config::from_env_auto()?.configure_binary_build();
Ok(())
}
build.rs这里修改了驱动类型,默认是KMDF类型的驱动,改成WDM。
微软的wdk项目目前0.2.0版本是无法指定驱动类型的,maybe。
lib.rs
驱动程序的入口
// 不使用标准库
#![no_std]
// panic处理
#[cfg(not(test))]
extern crate wdk_panic;
// 内存分配
use core::alloc::{GlobalAlloc, Layout};
use wdk_sys::{
ntddk::{ExAllocatePool, ExFreePool},
SIZE_T,
_POOL_TYPE::NonPagedPool,
};
pub struct WDKAllocator;
unsafe impl GlobalAlloc for WDKAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let ptr =
unsafe {
ExAllocatePool(NonPagedPool, layout.size() as SIZE_T)
};
if ptr.is_null() {
return core::ptr::null_mut();
}
ptr.cast()
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
unsafe {
ExFreePool(ptr.cast());
}
}
}
#[cfg(not(test))]
#[global_allocator]
static GLOBAL_ALLOCATOR: WDKAllocator = WDKAllocator;
use wdk::println;
use wdk_sys::{
DRIVER_OBJECT,
NTSTATUS,
PCUNICODE_STRING,
};
pub unsafe extern "C" fn driver_unload(_driver: *mut DRIVER_OBJECT) {
println!("driver_unload");
}
// 入口
#[export_name = "DriverEntry"] // WDF expects a symbol with the name DriverEntry
pub unsafe extern "system" fn driver_entry(
driver: &mut DRIVER_OBJECT,
_registry_path: PCUNICODE_STRING,
) -> NTSTATUS {
println!("Hello World");
driver.DriverUnload = Some(driver_unload);
0
}
wdk crate默认内存分配器用的是微软的wdk_alloc中的WDKAllocator,它使用了ExAllocatePool2申请非分页内存,如果你的操作系统版本较低,则这里会有问题,因为低版本的内核没有ExAllocatePool2这个导出函数,加载驱动时会报0xc0000263错误。
解决办法是自己实现WDKAllocator,不使用ExAllocatePool2。至少目前0.2.0的版本不支持,后续可能会有更新_NT_TARGET_VERSION,就可以针对指定windows版本开发驱动了(maybe)。最好是通过条件编译针对不同的windows版本选择不同的API。
MakeFile.toml(可选)
同cargo.toml同一级,主要是执行cargo make时重命名dll文件为sys文件,移动pdb符号文件以及添加测试签名等操作。
extend = "target/rust-driver-makefile.toml"
[env]
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
[config]
load_script = '''
#!@rust
//! ```cargo
//! [dependencies]
//! wdk-build = "0.2.0"
//! ```
#![allow(unused_doc_comments)]
wdk_build::cargo_make::load_rust_driver_makefile()?
'''
inx文件(可选)
rust_driver.inx,与cargo.toml同一级
;===================================================================
; rust_driver
; Copyright (c) Microsoft Corporation
;===================================================================
[Version]
Signature = "$WINDOWS NT$"
; 使用你的驱动名称
Class = rust_driver
; 使用VS生成GUID
ClassGuid = {5E47E5F6-8CEE-4EC0-B56A-18D92CD6E4D8}
Provider = %ProviderString%
PnpLockDown = 1
[DestinationDirs]
DefaultDestDir = 13
[SourceDisksNames]
1 = %DiskId1%,,,""
; 你的驱动名称
[SourceDisksFiles]
rust_driver.sys = 1,,
; ================= Class section =====================
[ClassInstall32]
Addreg=SampleClassReg
[SampleClassReg]
HKR,,,0,%ClassName%
HKR,,Icon,,-5
; ================= Install section =================
[Manufacturer]
%StdMfg%=Standard,NT$ARCH$.10.0...16299
[Standard.NT$ARCH$.10.0...16299]
%DeviceDesc%=SampleKMDFDevice, root\SAMPLE_KMDF_HW_ID
[SampleKMDFDevice.NT$ARCH$]
CopyFiles=Drivers_Dir
; 你的驱动名称
[Drivers_Dir]
rust_driver.sys
; ================= Service installation =================
[SampleKMDFDevice.NT$ARCH$.Services]
AddService = SampleKMDFService, %SPSVCINST_ASSOCSERVICE%, Sample_KMDF_Service_Install
[Sample_KMDF_Service_Install]
DisplayName = %ServiceDesc%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
; 你的驱动名称
ServiceBinary = %13%\rust_driver.sys
; ================= Strings =================
[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
; 可改可不改
ProviderString = "TODO-Set-Provider"
StdMfg = "(Standard system devices)"
DiskId1 = "Sample KMDF Installation Disk #1"
DeviceDesc = "Sample KMDF Rust Driver"
ServiceDesc = "Sample KMDF Rust Service"
ClassName = "Sample Device"
4、编译
# 不用makefile.toml和inx文件,直接编译出dll后缀无签名的驱动PE文件
cargo build --profile dev
cargo build --profile release
# 将会执行build,然后重命名dll文件为sys,移动符号文件,并添加签名,需要makefile.toml和inx文件
cargo make default --profile dev
cargo make default --profile release
# 清理
cargo clean
总结
至此,就可以加载使用Rust编写的HelloWorld驱动了。
还有一些可以说下:
-
rust首次编译很慢,增量速度很快。 -
「WinDebug是支持Rust源码调试的」,这点对于使用rust编写驱动还是很有意义的。 -
微软提供的wdk crate提供了println宏可以方便的打印,因为rust默认字符串用的utf8编码,所以字符操作可能不是很方便。 -
默认的内存分配器是使用的ExAllocatePool2申请的非分页内存,可能最终非分页内存不足导致错误。如果需要申请分页内存的话,还是需要自己操作管理内存。并且ExAllocatePool2在低版本windows内核没有,导入表有这个函数,低版本windows加载驱动时会报错0xc0000263。 -
目前的wdk crate版本0.2.0还是有很多不足,比如无法指定驱动类型wdm、kmdf等,默认是kmdf,不过可以修改buil.rs支持。还有无法编译指定操作系统版本的驱动,比如ExAllocatePool2,在VS指定目标系统版本后,编译时就会报错。