Skip to content

Commit 7d24647

Browse files
committed
windows: Add CreateSection and MapViewOfSection.
These functions are wrappers over NtDll's `NtCreateSection` and `NtMapViewOfSection`. They convert Zig-native ergonomics into calls to the wrapped functions.
1 parent 0f0f543 commit 7d24647

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

lib/std/os/windows.zig

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,146 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
154154
}
155155
}
156156

157+
pub const CreateFileMappingError = error{
158+
NameTooLong,
159+
} || UnexpectedError;
160+
161+
pub const CreateSectionOptions = struct {
162+
/// One or more of the `SECTION_*` flags.
163+
access: ACCESS_MASK,
164+
/// The size of the section in bytes.
165+
size: u64,
166+
/// One or more of the `PAGE_*` flags.
167+
page_attributes: ULONG,
168+
/// One or more of the `SEC_*` flags.
169+
section_attributes: ULONG = SEC_COMMIT,
170+
/// A backing file from which pages should be mapped.
171+
///
172+
/// If `null`, then the page file is used.
173+
file: ?HANDLE = null,
174+
/// The security attributes which should be applied to the kernel object.
175+
sa: ?*const SECURITY_ATTRIBUTES = null,
176+
/// The name of the kernel object.
177+
///
178+
/// See [CreateFileMappingEx](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw)
179+
/// for more information on what format this may take.
180+
name: ?[]const u16 = null,
181+
};
182+
183+
/// Allocate virtual memory for memory-mapping pages from a file into this process.
184+
///
185+
/// Asserts `options.size` is non-zero if `options.file == null`.
186+
pub fn CreateSection(options: CreateSectionOptions) CreateFileMappingError!HANDLE {
187+
std.debug.assert(options.file != null or options.size > 0);
188+
189+
var result: HANDLE = undefined;
190+
191+
// Create the string struct for the name. Since the name is optional, this is complicated
192+
// somewhat compared to `OpenFile`.
193+
var nt_name = if (options.name) |n| blk: {
194+
const name_len_bytes = math.cast(u16, n.len * 2) orelse return error.NameTooLong;
195+
break :blk UNICODE_STRING{
196+
.Length = name_len_bytes,
197+
.MaximumLength = name_len_bytes,
198+
.Buffer = @constCast(n.ptr),
199+
};
200+
} else UNICODE_STRING{
201+
.Length = 0,
202+
.MaximumLength = 0,
203+
.Buffer = null,
204+
};
205+
206+
// Create the object attributes for the section. This includes the name and a decomposition of
207+
// the `sattr` into ntdll terms.
208+
var attr = OBJECT_ATTRIBUTES{
209+
.Length = @sizeOf(OBJECT_ATTRIBUTES),
210+
.RootDirectory = null,
211+
.Attributes = if (options.sa) |ptr|
212+
if (ptr.bInheritHandle == TRUE) OBJ_INHERIT else 0
213+
else
214+
0,
215+
.ObjectName = &nt_name,
216+
.SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
217+
.SecurityQualityOfService = null,
218+
};
219+
220+
var max_size: LARGE_INTEGER = @intCast(options.size);
221+
222+
const status = ntdll.NtCreateSection(
223+
&result,
224+
options.access,
225+
&attr,
226+
&max_size,
227+
options.page_attributes,
228+
options.section_attributes,
229+
options.file,
230+
);
231+
return switch (status) {
232+
.SUCCESS => result,
233+
// TODO: Map out all the possible failure modes of this function. It isn't documented
234+
// anywhere immediately obvious, so it will have to be done as and when people find errors
235+
// from which they want to recover.
236+
else => unexpectedStatus(status),
237+
};
238+
}
239+
240+
pub const MapViewOfFileError = UnexpectedError;
241+
242+
pub const MapViewOfSectionOptions = struct {
243+
/// Whether the mapping should be inherited by subprocesses.
244+
inheritance: SECTION_INHERIT,
245+
/// One of the `PAGE_*` flags.
246+
protection: ULONG,
247+
/// The number of bytes starting at `offset` which should be mapped.
248+
length: usize,
249+
/// The byte offset within the file to start mapping.
250+
offset: u64 = 0,
251+
/// Optionally, a pointer which hints to the kernel where to place the map in virtual memory.
252+
hint: ?[*]align(std.mem.page_size) u8 = null,
253+
};
254+
255+
/// Memory-map a range within a section into virtual memory.
256+
pub fn MapViewOfSection(
257+
section_handle: HANDLE,
258+
options: MapViewOfSectionOptions,
259+
) MapViewOfFileError![]align(std.mem.page_size) volatile u8 {
260+
const process_handle = GetCurrentProcess();
261+
var result: usize = @intFromPtr(options.hint);
262+
var section_offset: LARGE_INTEGER = @intCast(options.offset);
263+
var actual_length: SIZE_T = @intCast(options.length);
264+
265+
const status = ntdll.NtMapViewOfSection(
266+
section_handle,
267+
process_handle,
268+
@ptrCast(&result),
269+
null, // zero bits
270+
0, // commit size
271+
&section_offset,
272+
&actual_length,
273+
options.inheritance,
274+
0, // allocation type
275+
options.protection,
276+
);
277+
switch (status) {
278+
.SUCCESS => {
279+
// Ensure the actual size meets the requested size.
280+
std.debug.assert(actual_length >= options.length);
281+
const ptr: [*]align(std.mem.page_size) volatile u8 = @ptrFromInt(result);
282+
return ptr[0..options.length];
283+
},
284+
// TODO: Map out all the possible failure modes of this function. It isn't documented
285+
// anywhere immediately obvious, so it will have to be done as and when people find errors
286+
// from which they want to recover.
287+
else => return unexpectedStatus(status),
288+
}
289+
}
290+
291+
pub fn UnmapViewOfSection(base: [*]align(std.mem.page_size) u8) void {
292+
const process_handle = GetCurrentProcess();
293+
const status = ntdll.NtUnmapViewOfSection(process_handle, @ptrCast(base));
294+
std.debug.assert(status == .SUCCESS); // Resource deallocation must succeed.
295+
}
296+
157297
pub fn GetCurrentProcess() HANDLE {
158298
const process_pseudo_handle: usize = @bitCast(@as(isize, -1));
159299
return @ptrFromInt(process_pseudo_handle);

0 commit comments

Comments
 (0)