-
Notifications
You must be signed in to change notification settings - Fork 95
Implement aligned WDK allocator #353
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?
Conversation
Now the WDK allocator can allocate aligned memory according to the layout argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @Zero-Tang!
How did you go about validating these changes? I think some concrete examples of the non-aligned case should be added the example drivers (ie. sample wdm & sample kmdf)
let q = ((p as usize & mask) + layout.align()) as *mut *mut u8; | ||
// Store the original pointer right before the pointer to return, | ||
// so that ExFreePoolWithTag can receive the correct pointer. | ||
// This is also how `_aligned_malloc` is implemented in msvcrt.dll |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this documented?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not documented, but _aligned_malloc
is actually somewhat open-source. Check the %ProgramFiles%\Windows Kits\10\Source\<Your SDK version>\ucrt\heap\align.cpp
file, and you can find its implementation.
Thanks for the feedback! |
Also address Melvin's reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zero-Tang The overall logic looks good to me. Thanks. Added comments for some small improvements.
{ | ||
// Example of using WDK allocator. | ||
// Allocations will be properly aligned on their boundaries! | ||
// Allocate a single instance. | ||
let ah=Box::new(AlignedHigher(1234)); | ||
println!("ah is allocated at {:p}, value={ah:?}",&raw const *ah); | ||
// Verify its alignment. | ||
assert_eq!((&raw const *ah) as usize & (align_of::<AlignedHigher>() - 1), 0); | ||
// Allocate a vector that occupies more than a page. | ||
let vh=vec![AlignedHigher(1234), AlignedHigher(5678), AlignedHigher(9012), AlignedHigher(3456), AlignedHigher(7890)]; | ||
println!("vh is allocated at {:p}, value={vh:?}",vh.as_ptr()); | ||
// Verify their alignments. | ||
for x in &vh { | ||
assert_eq!((core::ptr::from_ref::<AlignedHigher>(x) as usize) & (align_of::<AlignedHigher>() - 1), 0); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wmmc88 Is there a more preferred way to write tests?
How about we write unit tests in the crates/wdk-alloc/src/lib.rs
file itself. We can mock APIs like ExAllocatePool2
by putting them in global statics and then then in the test replacing them with mock implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can mock ExAllocatePool2
by calling VirtualAlloc
on Windows or mmap
on Linux, then add the returned pointer by 16 or 8. In this case, we can guarantee no coincidental big alignments returned by CRT's malloc
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may be easier for the mock ExAllocatePool2
to use a global array as its source of allocations instead of VirtualAlloc
or malloc
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be preferable if there are unit-tests with mocked wdk api calls in the wdk-alloc
.
Ideally, integration tests for the allocator would be facilitated in wdk-alloc/examples
folder, but i don't think either our cargo-make
scripts, nor the new cargo-wdk
tool support packaging drivers from examples
right now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be preferable if there are unit-tests with mocked wdk api calls in the wdk-alloc.
In that case @Zero-Tang can you please see if you can write some key unit tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Emmmmmm, I'm not so sure how to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An approach like this can work:
- Declare a static pointing either to the real
ExAllocatePool2
or a mock depending on whether we're running tests or not:
type ExAllocatePool2Fn = unsafe extern "system" fn(pool_flags: u32, number_of_bytes: usize, tag: u32) -> *mut c_void;
#[cfg(not(test))]
pub static mut EX_ALLOCATE_POOL2_FN: ExAllocatePool2Fn = ExAllocatePool2;
#[cfg(test)]
pub static mut EX_ALLOCATE_POOL2_FN: ExAllocatePool2Fn = mock_ex_allocate_pool2;
- Declare a static buffer that
mock_ex_allocate_pool2
will "allocate" from:
static ALLOC_BUFFER: [u8; 5120] = [0; 5120]; // 5 Pages worth of buffer to allocate from
static mut FREE_INDEX: usize = 0; // Index to keep track of available memory in buffer
-
Implement
mock_ex_allocate_pool2
using this buffer as the source of allocations. -
In
WdkAllcator
callEX_ALLOCATE_POOL2_FN
instead of callingExAllocatePoo2
directly.
Now you should be ready to write your tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda give up on this in that I couldn't make the test link to wdk_alloc::kernel_mode
module due to the #[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))]
attribute.
I tried cargo test --config driver_model__driver_type=\"WDM\"
but it doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wmmc88 Can you please help with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda give up on this in that I couldn't make the test link to wdk_alloc::kernel_mode module due to the #[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] attribute.
I tried cargo test --config driver_model__driver_type="WDM" but it doesn't work.
I don't think this will work as currently structured. I am working on some tooling to enable better unit testing of code that relies on the metadata section. unfortunately, since this is such a high impact change, I think we'll wait for that tooling to finish before merging this (with tests).
Address Gurinder's review comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the requested changes @Zero-Tang. Here are a few more suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments on comments.
Further clarify how the original pointer is stored and why it's safe.
Just FYI that I edited the first comment of this PR thread so that this PR will close #303 on merge. |
Now the WDK allocator can allocate aligned memory according to the layout argument.
Note that this allocator won't waste additional space for alignment if the alignment is normal (e.g.: less than or equal to 16-byte boundary on 64-bit platform). In average scenario, this is better than
_aligned_malloc
and_aligned_free
in msvcrt, in that I guess we rarely need big alignments in drivers for some stuff in the pool. Still, some performance-critical codes might require cache-aligned (e.g.: 64-byte in x86) objects, which is usually bigger thanExAllocatePool2
's alignment.Close #303.