Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
280 changes: 246 additions & 34 deletions crates/wdk-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,130 @@ impl Config {
.collect())
}

/// Returns the formatted `#[link]` raw lines for the given [`ApiSubset`],
/// ready to be passed to bindgen's `raw_line`.
///
/// This is the link-directive analogue of
/// [`Config::bindgen_header_contents`]: it maps the [`ApiSubset`] (and the
/// active [`DriverConfig`] and [`CpuArchitecture`]) to the native libraries
Comment thread
leon-xd marked this conversation as resolved.
Outdated
/// that must be linked, rendering them as `#[link(...)]` attributes emitted
/// into the generated bindings.
///
/// Unlike header selection, link subsets are selected *non-cumulatively*:
/// each generated bindings file requests only its own subset's libraries
/// (e.g. `hid.rs` requests only [`ApiSubset::Hid`], not
/// [`ApiSubset::Base`]), since the base libraries are already emitted
/// into the base bindings file. This avoids duplicate `#[link]`
Comment thread
leon-xd marked this conversation as resolved.
Outdated
/// directives across generated files, and is why this takes a single
/// [`ApiSubset`] rather than a set like
/// [`Config::bindgen_header_contents`].
///
/// Each emitted directive is gated behind `#[cfg(not(feature =
/// "test-stubs"))]`, evaluated in the crate that compiles the generated
/// bindings (`wdk-sys`), so the directives are suppressed when `wdk-sys` is
/// built with its `test-stubs` feature.
#[must_use]
pub fn bindgen_library_link_raw_lines(&self, api_subset: ApiSubset) -> String {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting tension here -- this couples wdk-sys and wdk-build given that we have a hard dependency on the test-stubs feature to exist. but this is a public API. more of a philosophy question for @wmmc88 but how do we resolve the fact that this needs to be public for wdk-sys to use this but is also so specific to wdk-sys that it is likely not useful to be used for other functions? do we genericize this and have wdk-sys determine the specific shape of the line?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can shrug our shoulders and say wdk-build is meant to be for wdk-sys. or maybe this is a sign that all of this is meant to go into wdk-sys?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or keep this #[doc(hidden)] -- that's what i did for the syn parsing pr that is out. but iirc melvin left a comment asking why i did that so maybe that's not right

Comment thread
leon-xd marked this conversation as resolved.
Outdated
self.libraries(api_subset)
.iter()
.map(LinkDirective::render)
.collect()
}

/// Returns the native libraries that must be linked for a given
/// [`ApiSubset`], based off this [`Config`]. Subsets that contribute no
/// extra libraries return an empty [`Vec`].
///
/// This is the link-directive analogue of [`Config::headers`].
fn libraries(&self, api_subset: ApiSubset) -> Vec<LinkDirective> {
match api_subset {
ApiSubset::Base => self.base_libraries(),
ApiSubset::Hid => self.hid_libraries(),
ApiSubset::Wdf
| ApiSubset::Gpio
| ApiSubset::ParallelPorts
| ApiSubset::Spb
| ApiSubset::Storage
| ApiSubset::Usb => Vec::new(),
}
}

/// Builds the static library link directives for the base bindings.
///
/// TODO: Once [link-arg-attribute](https://doc.rust-lang.org/unstable-book/language-features/link-arg-attribute.html)
/// stabilizes, the `cargo::rustc-cdylib-link-arg=*` directives emitted by
/// [`wdk_build::Config::configure_binary_build`] will be moved here too.
#[tracing::instrument(level = "trace")]
Comment thread
leon-xd marked this conversation as resolved.
Outdated
fn base_libraries(&self) -> Vec<LinkDirective> {
const fn static_lib(name: &'static str) -> LinkDirective {
// `LinkModifier::NoBundle` (rustc `-bundle`): don't pack the static
// lib into wdk-sys's rlib; defer its linking to the final driver
// binary, which is where the WDK `rustc-link-search` paths are
// emitted (by `Config::configure_binary_build`). Without it, building
// wdk-sys's rlib would fail since those search paths aren't available
// then.
LinkDirective::new(name, LinkKind::Static).with_modifiers(&[LinkModifier::NoBundle])
Comment thread
leon-xd marked this conversation as resolved.
Outdated
}

let mut directives = Vec::new();

// Base libraries derived from WindowsDriver.KernelMode.props (WDM/KMDF)
// and WindowsDriver.UserMode.props (UMDF) in the Ni(22H2) WDK.
Comment thread
leon-xd marked this conversation as resolved.
Outdated
match &self.driver_config {
DriverConfig::Wdm => {
directives.extend([
static_lib("BufferOverflowFastFailK"),
static_lib("ntoskrnl"),
static_lib("hal"),
static_lib("wmilib"),
]);
}
DriverConfig::Kmdf(_) => {
directives.extend([
static_lib("BufferOverflowFastFailK"),
static_lib("ntoskrnl"),
static_lib("hal"),
static_lib("wmilib"),
static_lib("WdfLdr"),
static_lib("WdfDriverEntry"),
]);
}
DriverConfig::Umdf(umdf_config) => {
if umdf_config.umdf_version_major >= 2 {
directives.extend([static_lib("WdfDriverStubUm"), static_lib("ntdll")]);
}
directives.push(static_lib("OneCoreUAP"));
}
}

// ARM64-specific libraries to link to derived from
Comment thread
leon-xd marked this conversation as resolved.
Outdated
// WindowsDriver.arm64.props.
if matches!(
self.driver_config,
Comment thread
leon-xd marked this conversation as resolved.
Outdated
DriverConfig::Wdm | DriverConfig::Kmdf(_)
) && self.cpu_architecture == CpuArchitecture::Arm64
{
directives.push(static_lib("arm64rt"));
}

directives
}

#[tracing::instrument(level = "trace")]
fn hid_libraries(&self) -> Vec<LinkDirective> {
Comment thread
leon-xd marked this conversation as resolved.
match self.driver_config {
DriverConfig::Wdm | DriverConfig::Kmdf(_) => {
// `LinkModifier::NoBundle` for the same reason as the base
// static libs; see `static_lib` in `base_libraries`.
vec![
LinkDirective::new("VhfKm", LinkKind::Static)
.with_modifiers(&[LinkModifier::NoBundle]),
]
}
DriverConfig::Umdf(_) => vec![LinkDirective::new("VhfUm", LinkKind::Dylib)],
}
}

/// Configure a Cargo build of a library that depends on the WDK. This
/// emits specially formatted prints to Cargo based on this [`Config`].
///
Expand Down Expand Up @@ -1125,20 +1249,12 @@ impl Config {
println!("cargo::rustc-link-search={}", path.display());
}

// TODO: Once [link-arg-attribute](https://doc.rust-lang.org/unstable-book/language-features/link-arg-attribute.html)
// stabilizes, the `cargo::rustc-cdylib-link-arg=*` directives will be moved to
// the wdk-sys build script
Comment thread
Alan632 marked this conversation as resolved.
match &self.driver_config {
DriverConfig::Wdm => {
// Emit WDM-specific libraries to link to
println!("cargo::rustc-link-lib=static=BufferOverflowFastFailK");
println!("cargo::rustc-link-lib=static=ntoskrnl");
println!("cargo::rustc-link-lib=static=hal");
println!("cargo::rustc-link-lib=static=wmilib");

// Emit ARM64-specific libraries to link to derived from
// WindowsDriver.arm64.props
if self.cpu_architecture == CpuArchitecture::Arm64 {
println!("cargo::rustc-link-lib=static=arm64rt");
}

// Emit WDM-specific linker args
// Linker arguments derived from WindowsDriver.KernelMode.props in Ni(22H2) WDK
Comment thread
Alan632 marked this conversation as resolved.
println!("cargo::rustc-cdylib-link-arg=/DRIVER");
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB");
Expand All @@ -1159,20 +1275,7 @@ impl Config {
println!("cargo::rustc-cdylib-link-arg=/IGNORE:4216");
}
DriverConfig::Kmdf(_) => {
// Emit KMDF-specific libraries to link to
println!("cargo::rustc-link-lib=static=BufferOverflowFastFailK");
println!("cargo::rustc-link-lib=static=ntoskrnl");
println!("cargo::rustc-link-lib=static=hal");
println!("cargo::rustc-link-lib=static=wmilib");
println!("cargo::rustc-link-lib=static=WdfLdr");
println!("cargo::rustc-link-lib=static=WdfDriverEntry");

// Emit ARM64-specific libraries to link to derived from
// WindowsDriver.arm64.props
if self.cpu_architecture == CpuArchitecture::Arm64 {
println!("cargo::rustc-link-lib=static=arm64rt");
}

// Emit KMDF-specific linker args
// Linker arguments derived from WindowsDriver.KernelMode.props in Ni(22H2) WDK
println!("cargo::rustc-cdylib-link-arg=/DRIVER");
Comment thread
Alan632 marked this conversation as resolved.
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB");
Expand All @@ -1187,16 +1290,9 @@ impl Config {
// might not run` since `rustc` has no support for `/KERNEL`
println!("cargo::rustc-cdylib-link-arg=/IGNORE:4257");
}
DriverConfig::Umdf(umdf_config) => {
// Emit UMDF-specific libraries to link to
if umdf_config.umdf_version_major >= 2 {
println!("cargo::rustc-link-lib=static=WdfDriverStubUm");
println!("cargo::rustc-link-lib=static=ntdll");
}

DriverConfig::Umdf(_) => {
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB:kernel32.lib");
println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB:user32.lib");
println!("cargo::rustc-link-lib=static=OneCoreUAP");

// Linker arguments derived from WindowsDriver.UserMode.props in Ni(22H2) WDK
println!("cargo::rustc-cdylib-link-arg=/SUBSYSTEM:WINDOWS");
Expand Down Expand Up @@ -1495,6 +1591,122 @@ pub fn configure_wdk_binary_build() -> Result<(), ConfigError> {
Config::from_env_auto()?.configure_binary_build()
}

/// `cfg` predicate that gates the WDK link directives so that they are only
/// emitted when the `wdk-sys` `test-stubs` feature is *not* enabled.
///
/// Downstream test executables that depend on `wdk-sys` enable its `test-stubs`
/// feature (which provides stubbed symbols *and*, via this gate, suppresses
/// real WDK library linkage; see `wdk_sys::test_stubs`). Such executables are
/// not driver binaries, so they never receive the WDK `rustc-link-search` paths
/// that `Config::configure_binary_build` emits, and must therefore not attempt
/// to link the real WDK libraries.
const NOT_TEST_STUBS_CFG: &str = r#"not(feature = "test-stubs")"#;

/// The `kind` of a `#[link]` attribute (i.e. how the library is linked).
///
/// Only the kinds actually emitted by [`Config`] are modelled; see the
/// [rustc reference][1] for the full set.
///
/// [1]: https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute
#[derive(Debug, Clone, Copy)]
enum LinkKind {
/// `kind = "static"`
Static,
/// `kind = "dylib"`
Dylib,
}

impl LinkKind {
/// Returns the string used in the `kind = "..."` field of a `#[link]`
/// attribute.
const fn as_str(self) -> &'static str {
match self {
Self::Static => "static",
Self::Dylib => "dylib",
}
}
}

/// A linking modifier for a `#[link]` attribute (the `modifiers = "..."`
/// field). Only the modifiers actually emitted by [`Config`] are modelled.
#[derive(Debug, Clone, Copy)]
enum LinkModifier {
/// `-bundle`
NoBundle,
}

impl LinkModifier {
/// Returns the string used in the `modifiers = "..."` field of a `#[link]`
/// attribute, including its `+`/`-` prefix.
Comment thread
leon-xd marked this conversation as resolved.
Outdated
const fn as_str(self) -> &'static str {
match self {
Self::NoBundle => "-bundle",
}
}
}

/// A native library to link into the generated bindings.
///
/// Rendered by [`LinkDirective::render`] as a `#[link(...)]` attribute on an
/// (empty) `extern` block and emitted into the bindgen output via bindgen's
/// `raw_line`. Every directive is gated behind `NOT_TEST_STUBS_CFG` so that
/// non-driver test consumers don't link the real WDK libraries.
#[derive(Debug, Clone)]
struct LinkDirective {
/// `name = "..."` — the library to link.
name: &'static str,
/// `kind = "..."` — how the library is linked.
kind: LinkKind,
/// `modifiers = "..."` values (e.g. [`LinkModifier::NoBundle`]). Joined
/// with commas; an empty slice omits the `modifiers` key entirely.
modifiers: &'static [LinkModifier],
}

impl LinkDirective {
/// Creates a directive with no modifiers.
const fn new(name: &'static str, kind: LinkKind) -> Self {
Self {
name,
kind,
modifiers: &[],
}
}

/// Sets the linking modifiers (e.g. `[LinkModifier::NoBundle]`).
const fn with_modifiers(mut self, modifiers: &'static [LinkModifier]) -> Self {
self.modifiers = modifiers;
self
}

/// Renders this directive into a self-contained block of Rust source.
///
/// Emitted as a single block so the attributes always stay attached to the
/// following `extern` block, regardless of where bindgen places raw lines.
fn render(&self) -> String {
let modifiers = if self.modifiers.is_empty() {
String::new()
} else {
let modifiers = self
.modifiers
.iter()
.map(|modifier| modifier.as_str())
.collect::<Vec<_>>()
.join(",");
format!(r#", modifiers = "{modifiers}""#)
};

format!(
r#"
Comment thread
leon-xd marked this conversation as resolved.
Outdated
#[cfg({cfg})]
#[link(name = "{name}", kind = "{kind}"{modifiers})]
unsafe extern "C" {{}}"#,
cfg = NOT_TEST_STUBS_CFG,
name = self.name,
kind = self.kind.as_str(),
)
}
}

/// This currently only exports the driver type, but may export more metadata in
/// the future. `EXPORTED_CFG_SETTINGS` is a mapping of cfg key to allowed cfg
/// values
Expand Down
6 changes: 4 additions & 2 deletions crates/wdk-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ fn generate_base(out_path: &Path, config: &Config) -> Result<(), ConfigError> {

let bindgen_builder = bindgen::Builder::wdk_default(config)?
.with_codegen_config((CodegenConfig::TYPES | CodegenConfig::VARS).complement())
.header_contents(&format!("{outfile_name}-input.h"), &header_contents);
.header_contents(&format!("{outfile_name}-input.h"), &header_contents)
.raw_line(config.bindgen_library_link_raw_lines(ApiSubset::Base));
trace!(bindgen_builder = ?bindgen_builder);

let output_file_path = out_path.join(format!("{outfile_name}.rs"));
Expand Down Expand Up @@ -360,7 +361,8 @@ fn generate_hid(out_path: &Path, config: &Config) -> Result<(), ConfigError> {
for header_file in config.headers(ApiSubset::Hid)? {
builder = builder.allowlist_file(format!("(?i).*{header_file}.*"));
}
builder

builder.raw_line(config.bindgen_library_link_raw_lines(ApiSubset::Hid))
Comment thread
leon-xd marked this conversation as resolved.
Outdated
};
trace!(bindgen_builder = ?bindgen_builder);

Expand Down
2 changes: 1 addition & 1 deletion tests/wdk-sys-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ driver-type = "WDM"
[lib]

[dependencies]
wdk-sys = { path = "../../crates/wdk-sys" }
wdk-sys = { features = ["test-stubs"], path = "../../crates/wdk-sys" }
Comment thread
leon-xd marked this conversation as resolved.
Comment thread
leon-xd marked this conversation as resolved.
Loading