-
Notifications
You must be signed in to change notification settings - Fork 128
feat: add linker directive for VHF lib + move static linker directives from wdk-build to wdk-sys #685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add linker directive for VHF lib + move static linker directives from wdk-build to wdk-sys #685
Changes from 6 commits
88d3948
ed27cc2
0cce966
e8756f1
a3b51b8
453f9db
f8f26b7
20cced8
f0d20e6
f179fb7
9e6f1c8
2967d71
8348a3f
11453ac
082282c
0b5deed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
| /// 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]` | ||
|
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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. interesting tension here -- this couples
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or keep this
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")] | ||
|
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]) | ||
|
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. | ||
|
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 | ||
|
leon-xd marked this conversation as resolved.
Outdated
|
||
| // WindowsDriver.arm64.props. | ||
| if matches!( | ||
| self.driver_config, | ||
|
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> { | ||
|
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`]. | ||
| /// | ||
|
|
@@ -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 | ||
|
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 | ||
|
Alan632 marked this conversation as resolved.
|
||
| println!("cargo::rustc-cdylib-link-arg=/DRIVER"); | ||
| println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB"); | ||
|
|
@@ -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"); | ||
|
Alan632 marked this conversation as resolved.
|
||
| println!("cargo::rustc-cdylib-link-arg=/NODEFAULTLIB"); | ||
|
|
@@ -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"); | ||
|
|
@@ -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. | ||
|
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#" | ||
|
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 | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.