windows: rework DebugInfo to use less file operations and fix some memory management issues

This commit is contained in:
kcbanner 2022-09-24 21:50:15 -04:00
parent fcee1bf993
commit 461fb499f3
4 changed files with 204 additions and 156 deletions

View File

@ -1061,65 +1061,55 @@ pub const CoffError = error{
// Official documentation of the format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
pub const Coff = struct {
allocator: mem.Allocator,
data: []const u8 = undefined,
is_image: bool = false,
coff_header_offset: usize = 0,
data: []const u8,
is_image: bool,
coff_header_offset: usize,
guid: [16]u8 = undefined,
age: u32 = undefined,
pub fn deinit(self: *Coff) void {
self.allocator.free(self.data);
}
/// Takes ownership of `data`.
pub fn parse(self: *Coff, data: []const u8) !void {
self.data = data;
// The lifetime of `data` must be longer than the lifetime of the returned Coff
pub fn init(data: []const u8) !Coff {
const pe_pointer_offset = 0x3C;
const pe_magic = "PE\x00\x00";
var stream = std.io.fixedBufferStream(self.data);
var stream = std.io.fixedBufferStream(data);
const reader = stream.reader();
try stream.seekTo(pe_pointer_offset);
const coff_header_offset = try reader.readIntLittle(u32);
var coff_header_offset = try reader.readIntLittle(u32);
try stream.seekTo(coff_header_offset);
var buf: [4]u8 = undefined;
try reader.readNoEof(&buf);
self.is_image = mem.eql(u8, pe_magic, &buf);
const is_image = mem.eql(u8, pe_magic, &buf);
var coff = @This(){
.data = data,
.is_image = is_image,
.coff_header_offset = coff_header_offset,
};
// Do some basic validation upfront
if (self.is_image) {
self.coff_header_offset = coff_header_offset + 4;
const coff_header = self.getCoffHeader();
if (is_image) {
coff.coff_header_offset = coff.coff_header_offset + 4;
const coff_header = coff.getCoffHeader();
if (coff_header.size_of_optional_header == 0) return error.MissingPEHeader;
}
// JK: we used to check for architecture here and throw an error if not x86 or derivative.
// However I am willing to take a leap of faith and let aarch64 have a shot also.
return coff;
}
pub fn getPdbPath(self: *Coff, buffer: []u8) !usize {
assert(self.is_image);
const header = blk: {
if (self.getSectionByName(".buildid")) |hdr| {
break :blk hdr;
} else if (self.getSectionByName(".rdata")) |hdr| {
break :blk hdr;
} else {
return error.MissingCoffSection;
}
};
const data_dirs = self.getDataDirectories();
const debug_dir = data_dirs[@enumToInt(DirectoryEntry.DEBUG)];
const file_offset = debug_dir.virtual_address - header.virtual_address + header.pointer_to_raw_data;
var stream = std.io.fixedBufferStream(self.data);
const reader = stream.reader();
try stream.seekTo(file_offset);
try stream.seekTo(debug_dir.virtual_address);
// Find the correct DebugDirectoryEntry, and where its data is stored.
// It can be in any section.
@ -1128,16 +1118,8 @@ pub const Coff = struct {
blk: while (i < debug_dir_entry_count) : (i += 1) {
const debug_dir_entry = try reader.readStruct(DebugDirectoryEntry);
if (debug_dir_entry.type == .CODEVIEW) {
for (self.getSectionHeaders()) |*section| {
const section_start = section.virtual_address;
const section_size = section.virtual_size;
const rva = debug_dir_entry.address_of_raw_data;
const offset = rva - section_start;
if (section_start <= rva and offset < section_size and debug_dir_entry.size_of_data <= section_size - offset) {
try stream.seekTo(section.pointer_to_raw_data + offset);
break :blk;
}
}
try stream.seekTo(debug_dir_entry.address_of_raw_data);
break :blk;
}
}
@ -1238,6 +1220,16 @@ pub const Coff = struct {
return @ptrCast([*]align(1) const SectionHeader, self.data.ptr + offset)[0..coff_header.number_of_sections];
}
pub fn getSectionHeadersAlloc(self: *const Coff, allocator: mem.Allocator) ![]SectionHeader {
const section_headers = self.getSectionHeaders();
const out_buff = try allocator.alloc(SectionHeader, section_headers.len);
for (out_buff) |*section_header, i| {
section_header.* = section_headers[i];
}
return out_buff;
}
pub fn getSectionName(self: *const Coff, sect_hdr: *align(1) const SectionHeader) []const u8 {
const name = sect_hdr.getName() orelse blk: {
const strtab = self.getStrtab().?;
@ -1256,12 +1248,15 @@ pub const Coff = struct {
return null;
}
pub fn getSectionData(self: *const Coff, comptime name: []const u8) ![]const u8 {
const sec = self.getSectionByName(name) orelse return error.MissingCoffSection;
return self.data[sec.pointer_to_raw_data..][0..sec.virtual_size];
}
// Return an owned slice full of the section data
pub fn getSectionDataAlloc(self: *const Coff, comptime name: []const u8, allocator: mem.Allocator) ![]u8 {
const sec = self.getSectionByName(name) orelse return error.MissingCoffSection;
const out_buff = try allocator.alloc(u8, sec.virtual_size);
mem.copy(u8, out_buff, self.data[sec.pointer_to_raw_data..][0..sec.virtual_size]);
return out_buff;
const section_data = try self.getSectionData(name);
return allocator.dupe(u8, section_data);
}
};

View File

@ -811,7 +811,7 @@ fn printLineInfo(
pub const OpenSelfDebugInfoError = error{
MissingDebugInfo,
UnsupportedOperatingSystem,
};
} || @typeInfo(@typeInfo(@TypeOf(DebugInfo.init)).Fn.return_type.?).ErrorUnion.error_set;
pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugInfo {
nosuspend {
@ -827,60 +827,56 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI
.dragonfly,
.openbsd,
.macos,
.windows,
.solaris,
=> return DebugInfo.init(allocator),
.windows,
=> return try DebugInfo.init(allocator),
else => return error.UnsupportedOperatingSystem,
}
}
}
/// This takes ownership of coff_file: users of this function should not close
/// it themselves, even on error.
/// TODO it's weird to take ownership even on error, rework this code.
fn readCoffDebugInfo(allocator: mem.Allocator, coff_file: File) !ModuleDebugInfo {
fn readCoffDebugInfo(allocator: mem.Allocator, coff_bytes: []const u8) !ModuleDebugInfo {
nosuspend {
defer coff_file.close();
const coff_obj = try allocator.create(coff.Coff);
errdefer allocator.destroy(coff_obj);
coff_obj.* = .{ .allocator = allocator };
coff_obj.* = try coff.Coff.init(coff_bytes);
var di = ModuleDebugInfo{
.base_address = undefined,
.coff = coff_obj,
.coff_image_base = coff_obj.getImageBase(),
.coff_section_headers = undefined,
.debug_data = undefined,
};
// TODO convert to Windows' memory-mapped file API
const file_len = math.cast(usize, try coff_file.getEndPos()) orelse math.maxInt(usize);
const data = try coff_file.readToEndAlloc(allocator, file_len);
try di.coff.parse(data);
if (di.coff.getSectionByName(".debug_info")) |sec| {
if (coff_obj.getSectionByName(".debug_info")) |sec| {
// This coff file has embedded DWARF debug info
_ = sec;
// TODO: free the section data slices
const debug_info = di.coff.getSectionDataAlloc(".debug_info", allocator) catch null;
const debug_abbrev = di.coff.getSectionDataAlloc(".debug_abbrev", allocator) catch null;
const debug_str = di.coff.getSectionDataAlloc(".debug_str", allocator) catch null;
const debug_str_offsets = di.coff.getSectionDataAlloc(".debug_str_offsets", allocator) catch null;
const debug_line = di.coff.getSectionDataAlloc(".debug_line", allocator) catch null;
const debug_line_str = di.coff.getSectionDataAlloc(".debug_line_str", allocator) catch null;
const debug_ranges = di.coff.getSectionDataAlloc(".debug_ranges", allocator) catch null;
const debug_loclists = di.coff.getSectionDataAlloc(".debug_loclists", allocator) catch null;
const debug_rnglists = di.coff.getSectionDataAlloc(".debug_rnglists", allocator) catch null;
const debug_addr = di.coff.getSectionDataAlloc(".debug_addr", allocator) catch null;
const debug_names = di.coff.getSectionDataAlloc(".debug_names", allocator) catch null;
const debug_frame = di.coff.getSectionDataAlloc(".debug_frame", allocator) catch null;
const debug_info = coff_obj.getSectionDataAlloc(".debug_info", allocator) catch return error.MissingDebugInfo;
errdefer allocator.free(debug_info);
const debug_abbrev = coff_obj.getSectionDataAlloc(".debug_abbrev", allocator) catch return error.MissingDebugInfo;
errdefer allocator.free(debug_abbrev);
const debug_str = coff_obj.getSectionDataAlloc(".debug_str", allocator) catch return error.MissingDebugInfo;
errdefer allocator.free(debug_str);
const debug_line = coff_obj.getSectionDataAlloc(".debug_line", allocator) catch return error.MissingDebugInfo;
errdefer allocator.free(debug_line);
const debug_str_offsets = coff_obj.getSectionDataAlloc(".debug_str_offsets", allocator) catch null;
const debug_line_str = coff_obj.getSectionDataAlloc(".debug_line_str", allocator) catch null;
const debug_ranges = coff_obj.getSectionDataAlloc(".debug_ranges", allocator) catch null;
const debug_loclists = coff_obj.getSectionDataAlloc(".debug_loclists", allocator) catch null;
const debug_rnglists = coff_obj.getSectionDataAlloc(".debug_rnglists", allocator) catch null;
const debug_addr = coff_obj.getSectionDataAlloc(".debug_addr", allocator) catch null;
const debug_names = coff_obj.getSectionDataAlloc(".debug_names", allocator) catch null;
const debug_frame = coff_obj.getSectionDataAlloc(".debug_frame", allocator) catch null;
var dwarf = DW.DwarfInfo{
.endian = native_endian,
.debug_info = debug_info orelse return error.MissingDebugInfo,
.debug_abbrev = debug_abbrev orelse return error.MissingDebugInfo,
.debug_str = debug_str orelse return error.MissingDebugInfo,
.debug_info = debug_info,
.debug_abbrev = debug_abbrev,
.debug_str = debug_str,
.debug_str_offsets = debug_str_offsets,
.debug_line = debug_line orelse return error.MissingDebugInfo,
.debug_line = debug_line,
.debug_line_str = debug_line_str,
.debug_ranges = debug_ranges,
.debug_loclists = debug_loclists,
@ -889,13 +885,28 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_file: File) !ModuleDebugInfo
.debug_names = debug_names,
.debug_frame = debug_frame,
};
try DW.openDwarfDebugInfo(&dwarf, allocator);
DW.openDwarfDebugInfo(&dwarf, allocator) catch |err| {
if (debug_str_offsets) |d| allocator.free(d);
if (debug_line_str) |d| allocator.free(d);
if (debug_ranges) |d| allocator.free(d);
if (debug_loclists) |d| allocator.free(d);
if (debug_rnglists) |d| allocator.free(d);
if (debug_addr) |d| allocator.free(d);
if (debug_names) |d| allocator.free(d);
if (debug_frame) |d| allocator.free(d);
return err;
};
di.debug_data = PdbOrDwarf{ .dwarf = dwarf };
return di;
}
// Only used by pdb path
di.coff_section_headers = try coff_obj.getSectionHeadersAlloc(allocator);
var path_buf: [windows.MAX_PATH]u8 = undefined;
const len = try di.coff.getPdbPath(path_buf[0..]);
const len = try coff_obj.getPdbPath(path_buf[0..]);
const raw_path = path_buf[0..len];
const path = try fs.path.resolve(allocator, &[_][]const u8{raw_path});
@ -909,7 +920,7 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_file: File) !ModuleDebugInfo
try di.debug_data.pdb.parseInfoStream();
try di.debug_data.pdb.parseDbiStream();
if (!mem.eql(u8, &di.coff.guid, &di.debug_data.pdb.guid) or di.coff.age != di.debug_data.pdb.age)
if (!mem.eql(u8, &coff_obj.guid, &di.debug_data.pdb.guid) or coff_obj.age != di.debug_data.pdb.age)
return error.InvalidDebugInfo;
return di;
@ -1225,15 +1236,49 @@ fn mapWholeFile(file: File) ![]align(mem.page_size) const u8 {
}
}
pub const ModuleInfo = struct {
base_address: usize,
size: u32,
};
pub const DebugInfo = struct {
allocator: mem.Allocator,
address_map: std.AutoHashMap(usize, *ModuleDebugInfo),
modules: if (native_os == .windows) std.ArrayListUnmanaged(ModuleInfo) else void,
pub fn init(allocator: mem.Allocator) DebugInfo {
return DebugInfo{
pub fn init(allocator: mem.Allocator) !DebugInfo {
var debug_info = DebugInfo{
.allocator = allocator,
.address_map = std.AutoHashMap(usize, *ModuleDebugInfo).init(allocator),
.modules = if (native_os == .windows) .{} else {},
};
if (native_os == .windows) {
const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0);
if (handle == windows.INVALID_HANDLE_VALUE) {
switch (windows.kernel32.GetLastError()) {
else => |err| return windows.unexpectedError(err),
}
}
defer windows.CloseHandle(handle);
var module_entry: windows.MODULEENTRY32 = undefined;
module_entry.dwSize = @sizeOf(windows.MODULEENTRY32);
if (windows.kernel32.Module32First(handle, &module_entry) == 0) {
return error.MissingDebugInfo;
}
var module_valid = true;
while (module_valid) {
const module_info = try debug_info.modules.addOne(allocator);
module_info.base_address = @ptrToInt(module_entry.modBaseAddr);
module_info.size = module_entry.modBaseSize;
module_valid = windows.kernel32.Module32Next(handle, &module_entry) == 1;
}
}
return debug_info;
}
pub fn deinit(self: *DebugInfo) void {
@ -1322,79 +1367,20 @@ pub const DebugInfo = struct {
}
fn lookupModuleWin32(self: *DebugInfo, address: usize) !*ModuleDebugInfo {
const process_handle = windows.kernel32.GetCurrentProcess();
// Find how many modules are actually loaded
var dummy: windows.HMODULE = undefined;
var bytes_needed: windows.DWORD = undefined;
if (windows.kernel32.K32EnumProcessModules(
process_handle,
@ptrCast([*]windows.HMODULE, &dummy),
0,
&bytes_needed,
) == 0)
return error.MissingDebugInfo;
const needed_modules = bytes_needed / @sizeOf(windows.HMODULE);
// Fetch the complete module list
var modules = try self.allocator.alloc(windows.HMODULE, needed_modules);
defer self.allocator.free(modules);
if (windows.kernel32.K32EnumProcessModules(
process_handle,
modules.ptr,
math.cast(windows.DWORD, modules.len * @sizeOf(windows.HMODULE)) orelse return error.Overflow,
&bytes_needed,
) == 0)
return error.MissingDebugInfo;
// There's an unavoidable TOCTOU problem here, the module list may have
// changed between the two EnumProcessModules call.
// Pick the smallest amount of elements to avoid processing garbage.
const needed_modules_after = bytes_needed / @sizeOf(windows.HMODULE);
const loaded_modules = math.min(needed_modules, needed_modules_after);
for (modules[0..loaded_modules]) |module| {
var info: windows.MODULEINFO = undefined;
if (windows.kernel32.K32GetModuleInformation(
process_handle,
module,
&info,
@sizeOf(@TypeOf(info)),
) == 0)
return error.MissingDebugInfo;
const seg_start = @ptrToInt(info.lpBaseOfDll);
const seg_end = seg_start + info.SizeOfImage;
if (address >= seg_start and address < seg_end) {
if (self.address_map.get(seg_start)) |obj_di| {
for (self.modules.items) |module| {
if (address >= module.base_address and address < module.base_address + module.size) {
if (self.address_map.get(module.base_address)) |obj_di| {
return obj_di;
}
var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined;
// openFileAbsoluteW requires the prefix to be present
mem.copy(u16, name_buffer[0..4], &[_]u16{ '\\', '?', '?', '\\' });
const len = windows.kernel32.K32GetModuleFileNameExW(
process_handle,
module,
@ptrCast(windows.LPWSTR, &name_buffer[4]),
windows.PATH_MAX_WIDE,
);
assert(len > 0);
const mapped_module = @intToPtr([*]const u8, module.base_address)[0..module.size];
const obj_di = try self.allocator.create(ModuleDebugInfo);
errdefer self.allocator.destroy(obj_di);
const coff_file = fs.openFileAbsoluteW(name_buffer[0 .. len + 4 :0], .{}) catch |err| switch (err) {
error.FileNotFound => return error.MissingDebugInfo,
else => return err,
};
obj_di.* = try readCoffDebugInfo(self.allocator, coff_file);
obj_di.base_address = seg_start;
try self.address_map.putNoClobber(seg_start, obj_di);
obj_di.* = try readCoffDebugInfo(self.allocator, mapped_module);
obj_di.base_address = module.base_address;
try self.address_map.putNoClobber(module.base_address, obj_di);
return obj_di;
}
}
@ -1727,12 +1713,31 @@ pub const ModuleDebugInfo = switch (native_os) {
.uefi, .windows => struct {
base_address: usize,
debug_data: PdbOrDwarf,
coff: *coff.Coff,
coff_image_base: u64,
coff_section_headers: []coff.SectionHeader,
fn deinit(self: *@This(), allocator: mem.Allocator) void {
switch (self.debug_data) {
.dwarf => |*dwarf| {
allocator.free(dwarf.debug_info);
allocator.free(dwarf.debug_abbrev);
allocator.free(dwarf.debug_str);
allocator.free(dwarf.debug_line);
if (dwarf.debug_str_offsets) |d| allocator.free(d);
if (dwarf.debug_line_str) |d| allocator.free(d);
if (dwarf.debug_ranges) |d| allocator.free(d);
if (dwarf.debug_loclists) |d| allocator.free(d);
if (dwarf.debug_rnglists) |d| allocator.free(d);
if (dwarf.debug_addr) |d| allocator.free(d);
if (dwarf.debug_names) |d| allocator.free(d);
if (dwarf.debug_frame) |d| allocator.free(d);
},
.pdb => {
allocator.free(self.coff_section_headers);
},
}
self.debug_data.deinit(allocator);
self.coff.deinit();
allocator.destroy(self.coff);
}
pub fn getSymbolAtAddress(self: *@This(), allocator: mem.Allocator, address: usize) !SymbolInfo {
@ -1741,7 +1746,7 @@ pub const ModuleDebugInfo = switch (native_os) {
switch (self.debug_data) {
.dwarf => |*dwarf| {
const dwarf_address = relocated_address + self.coff.getImageBase();
const dwarf_address = relocated_address + self.coff_image_base;
return getSymbolFromDwarf(allocator, dwarf_address, dwarf);
},
.pdb => {
@ -1751,10 +1756,9 @@ pub const ModuleDebugInfo = switch (native_os) {
var coff_section: *align(1) const coff.SectionHeader = undefined;
const mod_index = for (self.debug_data.pdb.sect_contribs) |sect_contrib| {
const sections = self.coff.getSectionHeaders();
if (sect_contrib.Section > sections.len) continue;
if (sect_contrib.Section > self.coff_section_headers.len) continue;
// Remember that SectionContribEntry.Section is 1-based.
coff_section = &sections[sect_contrib.Section - 1];
coff_section = &self.coff_section_headers[sect_contrib.Section - 1];
const vaddr_start = coff_section.virtual_address + sect_contrib.Offset;
const vaddr_end = vaddr_start + sect_contrib.Size;

View File

@ -3801,6 +3801,26 @@ pub const PEB_LDR_DATA = extern struct {
ShutdownThreadId: HANDLE,
};
/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including:
/// - https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data
/// - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm
pub const LDR_DATA_TABLE_ENTRY = extern struct {
Reserved1: [2]PVOID,
InMemoryOrderLinks: LIST_ENTRY,
Reserved2: [2]PVOID,
DllBase: PVOID,
EntryPoint: PVOID,
SizeOfImage: ULONG,
FullDllName: UNICODE_STRING,
Reserved4: [8]BYTE,
Reserved5: [3]PVOID,
DUMMYUNIONNAME: extern union {
CheckSum: ULONG,
Reserved6: PVOID,
},
TimeDateStamp: ULONG,
};
pub const RTL_USER_PROCESS_PARAMETERS = extern struct {
AllocationSize: ULONG,
Size: ULONG,
@ -4349,3 +4369,25 @@ pub fn IsProcessorFeaturePresent(feature: PF) bool {
if (@enumToInt(feature) >= PROCESSOR_FEATURE_MAX) return false;
return SharedUserData.ProcessorFeatures[@enumToInt(feature)] == 1;
}
pub const TH32CS_SNAPHEAPLIST = 0x00000001;
pub const TH32CS_SNAPPROCESS = 0x00000002;
pub const TH32CS_SNAPTHREAD = 0x00000004;
pub const TH32CS_SNAPMODULE = 0x00000008;
pub const TH32CS_SNAPMODULE32 = 0x00000010;
pub const TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE;
pub const TH32CS_INHERIT = 0x80000000;
pub const MAX_MODULE_NAME32 = 255;
pub const MODULEENTRY32 = extern struct {
dwSize: DWORD,
th32ModuleID: DWORD,
th32ProcessID: DWORD,
GlblcntUsage: DWORD,
ProccntUsage: DWORD,
modBaseAddr: *BYTE,
modBaseSize: DWORD,
hModule: HMODULE,
szModule: [MAX_MODULE_NAME32 + 1]CHAR,
szExePath: [MAX_PATH]CHAR,
};

View File

@ -66,6 +66,7 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE;
const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
const MODULEENTRY32 = windows.MODULEENTRY32;
pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;
pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
@ -132,6 +133,8 @@ pub extern "kernel32" fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingComp
pub extern "kernel32" fn CreateThread(lpThreadAttributes: ?*SECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?*DWORD) callconv(WINAPI) ?HANDLE;
pub extern "kernel32" fn CreateToolhelp32Snapshot(dwFlags: DWORD, th32ProcessID: DWORD) callconv(WINAPI) HANDLE;
pub extern "kernel32" fn DeviceIoControl(
h: HANDLE,
dwIoControlCode: DWORD,
@ -265,6 +268,10 @@ pub extern "kernel32" fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASI
pub extern "kernel32" fn LocalFree(hMem: HLOCAL) callconv(WINAPI) ?HLOCAL;
pub extern "kernel32" fn Module32First(hSnapshot: HANDLE, lpme: *MODULEENTRY32) callconv(WINAPI) BOOL;
pub extern "kernel32" fn Module32Next(hSnapshot: HANDLE, lpme: *MODULEENTRY32) callconv(WINAPI) BOOL;
pub extern "kernel32" fn MoveFileExW(
lpExistingFileName: [*:0]const u16,
lpNewFileName: [*:0]const u16,