Commit 7ea2c7fbcd

Jakub Konka <kubkon@jakubkonka.com>
2022-11-26 17:34:45
windows: use RtlQueryRegistryValues to query reg in a single syscall
1 parent 153afed
Changed files (4)
lib
lib/std/os/windows/kernel32.zig
@@ -10,6 +10,7 @@ const DWORD = windows.DWORD;
 const FILE_INFO_BY_HANDLE_CLASS = windows.FILE_INFO_BY_HANDLE_CLASS;
 const HANDLE = windows.HANDLE;
 const HMODULE = windows.HMODULE;
+const HKEY = windows.HKEY;
 const HRESULT = windows.HRESULT;
 const LARGE_INTEGER = windows.LARGE_INTEGER;
 const LPCWSTR = windows.LPCWSTR;
@@ -57,6 +58,8 @@ const UCHAR = windows.UCHAR;
 const FARPROC = windows.FARPROC;
 const INIT_ONCE_FN = windows.INIT_ONCE_FN;
 const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION;
+const REGSAM = windows.REGSAM;
+const LSTATUS = windows.LSTATUS;
 
 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;
@@ -412,3 +415,11 @@ pub extern "kernel32" fn SleepConditionVariableSRW(
 pub extern "kernel32" fn TryAcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) BOOLEAN;
 pub extern "kernel32" fn AcquireSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;
 pub extern "kernel32" fn ReleaseSRWLockExclusive(s: *SRWLOCK) callconv(WINAPI) void;
+
+pub extern "kernel32" fn RegOpenKeyExW(
+    hkey: HKEY,
+    lpSubKey: LPCWSTR,
+    ulOptions: DWORD,
+    samDesired: REGSAM,
+    phkResult: *HANDLE,
+) callconv(WINAPI) LSTATUS;
lib/std/os/windows/ntdll.zig
@@ -22,6 +22,8 @@ const RTL_OSVERSIONINFOW = windows.RTL_OSVERSIONINFOW;
 const FILE_BASIC_INFORMATION = windows.FILE_BASIC_INFORMATION;
 const SIZE_T = windows.SIZE_T;
 const CURDIR = windows.CURDIR;
+const PCWSTR = windows.PCWSTR;
+const RTL_QUERY_REGISTRY_TABLE = windows.RTL_QUERY_REGISTRY_TABLE;
 
 pub const THREADINFOCLASS = enum(c_int) {
     ThreadBasicInformation,
@@ -259,3 +261,11 @@ pub extern "ntdll" fn NtOpenKey(
     DesiredAccess: ACCESS_MASK,
     ObjectAttributes: OBJECT_ATTRIBUTES,
 ) callconv(WINAPI) NTSTATUS;
+
+pub extern "ntdll" fn RtlQueryRegistryValues(
+    RelativeTo: ULONG,
+    Path: PCWSTR,
+    QueryTable: [*]RTL_QUERY_REGISTRY_TABLE,
+    Context: ?*anyopaque,
+    Environment: ?*anyopaque,
+) callconv(WINAPI) NTSTATUS;
lib/std/os/windows.zig
@@ -2088,6 +2088,7 @@ pub const LPWSTR = [*:0]WCHAR;
 pub const LPCWSTR = [*:0]const WCHAR;
 pub const PVOID = *anyopaque;
 pub const PWSTR = [*:0]WCHAR;
+pub const PCWSTR = [*:0]const WCHAR;
 pub const SIZE_T = usize;
 pub const UINT = c_uint;
 pub const ULONG_PTR = usize;
@@ -2876,8 +2877,134 @@ pub const ACCESS_MASK = DWORD;
 pub const LSTATUS = LONG;
 
 pub const HKEY = HANDLE;
+
 pub const HKEY_LOCAL_MACHINE: HKEY = @intToPtr(HKEY, 0x80000002);
 
+/// Combines the STANDARD_RIGHTS_REQUIRED, KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_CREATE_SUB_KEY,
+/// KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, and KEY_CREATE_LINK access rights.
+pub const KEY_ALL_ACCESS = 0xF003F;
+/// Reserved for system use.
+pub const KEY_CREATE_LINK = 0x0020;
+/// Required to create a subkey of a registry key.
+pub const KEY_CREATE_SUB_KEY = 0x0004;
+/// Required to enumerate the subkeys of a registry key.
+pub const KEY_ENUMERATE_SUB_KEYS = 0x0008;
+/// Equivalent to KEY_READ.
+pub const KEY_EXECUTE = 0x20019;
+/// Required to request change notifications for a registry key or for subkeys of a registry key.
+pub const KEY_NOTIFY = 0x0010;
+/// Required to query the values of a registry key.
+pub const KEY_QUERY_VALUE = 0x0001;
+/// Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY values.
+pub const KEY_READ = 0x20019;
+/// Required to create, delete, or set a registry value.
+pub const KEY_SET_VALUE = 0x0002;
+/// Indicates that an application on 64-bit Windows should operate on the 32-bit registry view.
+/// This flag is ignored by 32-bit Windows.
+pub const KEY_WOW64_32KEY = 0x0200;
+/// Indicates that an application on 64-bit Windows should operate on the 64-bit registry view.
+/// This flag is ignored by 32-bit Windows.
+pub const KEY_WOW64_64KEY = 0x0100;
+/// Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, and KEY_CREATE_SUB_KEY access rights.
+pub const KEY_WRITE = 0x20006;
+
+/// Open symbolic link.
+pub const REG_OPTION_OPEN_LINK: DWORD = 0x8;
+
+pub const RTL_QUERY_REGISTRY_TABLE = extern struct {
+    QueryRoutine: RTL_QUERY_REGISTRY_ROUTINE,
+    Flags: ULONG,
+    Name: ?PWSTR,
+    EntryContext: ?*anyopaque,
+    DefaultType: ULONG,
+    DefaultData: ?*anyopaque,
+    DefaultLength: ULONG,
+};
+
+pub const RTL_QUERY_REGISTRY_ROUTINE = ?std.meta.FnPtr(fn (
+    PWSTR,
+    ULONG,
+    ?*anyopaque,
+    ULONG,
+    ?*anyopaque,
+    ?*anyopaque,
+) callconv(WINAPI) NTSTATUS);
+
+/// Path is a full path
+pub const RTL_REGISTRY_ABSOLUTE = 0;
+/// \Registry\Machine\System\CurrentControlSet\Services
+pub const RTL_REGISTRY_SERVICES = 1;
+/// \Registry\Machine\System\CurrentControlSet\Control
+pub const RTL_REGISTRY_CONTROL = 2;
+/// \Registry\Machine\Software\Microsoft\Windows NT\CurrentVersion
+pub const RTL_REGISTRY_WINDOWS_NT = 3;
+/// \Registry\Machine\Hardware\DeviceMap
+pub const RTL_REGISTRY_DEVICEMAP = 4;
+/// \Registry\User\CurrentUser
+pub const RTL_REGISTRY_USER = 5;
+pub const RTL_REGISTRY_MAXIMUM = 6;
+
+/// Low order bits are registry handle
+pub const RTL_REGISTRY_HANDLE = 0x40000000;
+/// Indicates the key node is optional
+pub const RTL_REGISTRY_OPTIONAL = 0x80000000;
+
+/// Name is a subkey and remainder of table or until next subkey are value
+/// names for that subkey to look at.
+pub const RTL_QUERY_REGISTRY_SUBKEY = 0x00000001;
+
+/// Reset current key to original key for this and all following table entries.
+pub const RTL_QUERY_REGISTRY_TOPKEY = 0x00000002;
+
+/// Fail if no match found for this table entry.
+pub const RTL_QUERY_REGISTRY_REQUIRED = 0x00000004;
+
+/// Used to mark a table entry that has no value name, just wants a call out, not
+/// an enumeration of all values.
+pub const RTL_QUERY_REGISTRY_NOVALUE = 0x00000008;
+
+/// Used to suppress the expansion of REG_MULTI_SZ into multiple callouts or
+/// to prevent the expansion of environment variable values in REG_EXPAND_SZ.
+pub const RTL_QUERY_REGISTRY_NOEXPAND = 0x00000010;
+
+/// QueryRoutine field ignored.  EntryContext field points to location to store value.
+/// For null terminated strings, EntryContext points to UNICODE_STRING structure that
+/// that describes maximum size of buffer. If .Buffer field is NULL then a buffer is
+/// allocated.
+pub const RTL_QUERY_REGISTRY_DIRECT = 0x00000020;
+
+/// Used to delete value keys after they are queried.
+pub const RTL_QUERY_REGISTRY_DELETE = 0x00000040;
+
+/// Use this flag with the RTL_QUERY_REGISTRY_DIRECT flag to verify that the REG_XXX type
+/// of the stored registry value matches the type expected by the caller.
+/// If the types do not match, the call fails.
+pub const RTL_QUERY_REGISTRY_TYPECHECK = 0x00000100;
+
+/// No value type
+pub const REG_NONE = 0;
+/// Unicode nul terminated string
+pub const REG_SZ = 1;
+/// Unicode nul terminated string (with environment variable references)
+pub const REG_EXPAND_SZ = 2;
+/// Free form binary
+pub const REG_BINARY = 3;
+/// 32-bit number
+pub const REG_DWORD = 4;
+/// 32-bit number (same as REG_DWORD)
+pub const REG_DWORD_LITTLE_ENDIAN = 4;
+/// 32-bit number
+pub const REG_DWORD_BIG_ENDIAN = 5;
+/// Symbolic Link (unicode)
+pub const REG_LINK = 6;
+/// Multiple Unicode strings
+pub const REG_MULTI_SZ = 7;
+/// Resource list in the resource map
+pub const REG_RESOURCE_LIST = 8;
+/// Resource list in the hardware description
+pub const REG_FULL_RESOURCE_DESCRIPTOR = 9;
+pub const REG_RESOURCE_REQUIREMENTS_LIST = 10;
+
 pub const FILE_NOTIFY_INFORMATION = extern struct {
     NextEntryOffset: DWORD,
     Action: DWORD,
@@ -4020,187 +4147,3 @@ pub fn IsProcessorFeaturePresent(feature: PF) bool {
     if (@enumToInt(feature) >= PROCESSOR_FEATURE_MAX) return false;
     return SharedUserData.ProcessorFeatures[@enumToInt(feature)] == 1;
 }
-
-pub const KEY_QUERY_VALUE = 0x0001;
-
-/// Open symbolic link.
-pub const REG_OPTION_OPEN_LINK: DWORD = 0x8;
-
-inline fn IsPredefKey(hkey: HKEY) bool {
-    return @ptrToInt(hkey) & 0xF0000000 == 0x80000000;
-}
-
-inline fn GetPredefKeyIndex(hkey: HKEY) usize {
-    return @ptrToInt(hkey) & 0x0FFFFFFF;
-}
-
-inline fn ClosePredefKey(hkey: HKEY) void {
-    if (@ptrToInt(hkey) & 0x1 != 0) {
-        assert(ntdll.NtClose(hkey) == .SUCCESS);
-    }
-}
-
-const MAX_DEFAULT_HANDLES = 6;
-pub const REG_MAX_NAME_SIZE = 256;
-
-pub const RegOpenKeyOpts = struct {
-    ulOptions: DWORD = 0,
-    samDesired: ACCESS_MASK = KEY_QUERY_VALUE,
-};
-
-/// Pulls existing key from the registry.
-pub fn RegOpenKey(hkey: HKEY, lpSubKey: []const u16, opts: RegOpenKeyOpts) !HKEY {
-    if (IsPredefKey(hkey) and lpSubKey.len == 0) {
-        return hkey;
-    }
-
-    const key_handle = try MapDefaultKey(hkey);
-    defer ClosePredefKey(key_handle);
-
-    var subkey_string: UNICODE_STRING = undefined;
-    if (lpSubKey.len == 0 or mem.eql(u16, &[_]u16{'\\'}, lpSubKey)) {
-        subkey_string = .{
-            .Length = 0,
-            .MaximumLength = 0,
-            .Buffer = @intToPtr([*]u16, @ptrToInt(&[0]u16{})),
-        };
-    } else {
-        const len_bytes = math.cast(u16, lpSubKey.len * 2) orelse return error.NameTooLong;
-        subkey_string = .{
-            .Length = len_bytes,
-            .MaximumLength = len_bytes,
-            .Buffer = @intToPtr([*]u16, @ptrToInt(lpSubKey.ptr)),
-        };
-    }
-
-    var attributes: ULONG = OBJ_CASE_INSENSITIVE;
-    if (opts.ulOptions & REG_OPTION_OPEN_LINK != 0) {
-        attributes |= OBJ_OPENLINK;
-    }
-
-    var attr = OBJECT_ATTRIBUTES{
-        .Length = @sizeOf(OBJECT_ATTRIBUTES),
-        .RootDirectory = key_handle,
-        .Attributes = attributes,
-        .ObjectName = &subkey_string,
-        .SecurityDescriptor = null,
-        .SecurityQualityOfService = null,
-    };
-
-    var result: HKEY = undefined;
-    const rc = ntdll.NtOpenKey(
-        &result,
-        opts.samDesired,
-        attr,
-    );
-    switch (rc) {
-        .SUCCESS => return result,
-        else => return unexpectedStatus(rc),
-    }
-}
-
-pub fn RegCloseKey(hkey: HKEY) void {
-    if (IsPredefKey(hkey)) return;
-    assert(ntdll.NtClose(hkey) == .SUCCESS);
-}
-
-extern var DefaultHandleHKUDisabled: BOOLEAN;
-extern var DefaultHandlesDisabled: BOOLEAN;
-extern var DefaultHandleTable: [MAX_DEFAULT_HANDLES]?HANDLE;
-
-fn MapDefaultKey(key: HKEY) !HANDLE {
-    if (!IsPredefKey(key)) return @intToPtr(HANDLE, @ptrToInt(key) & ~@as(usize, 0x1));
-
-    const index = GetPredefKeyIndex(key);
-    if (index >= MAX_DEFAULT_HANDLES) {
-        return error.InvalidParameter;
-    }
-
-    const def_disabled = if (key == HKEY_LOCAL_MACHINE) DefaultHandleHKUDisabled else DefaultHandlesDisabled;
-
-    var handle: HANDLE = undefined;
-    var do_open: bool = true;
-
-    if (def_disabled != 0) {
-        const tmp = DefaultHandleTable[index];
-        if (tmp) |h| {
-            do_open = false;
-            handle = h;
-        }
-    }
-
-    if (do_open) {
-        handle = try OpenPredefinedKey(index);
-    }
-
-    if (def_disabled == 0) {
-        handle = @intToPtr(HANDLE, @ptrToInt(handle) | 0x1);
-    }
-
-    return handle;
-}
-
-fn OpenPredefinedKey(index: usize) !HANDLE {
-    switch (index) {
-        0 => {
-            // HKEY_CLASSES_ROOT
-            return error.Unimplemented;
-        },
-        1 => {
-            // HKEY_CURRENT_USER
-            return error.Unimplemented;
-        },
-        2 => {
-            // HKEY_LOCAL_MACHINE
-            return OpenLocalMachineKey();
-        },
-        3 => {
-            // HKEY_USERS
-            return error.Unimplemented;
-        },
-        5 => {
-            // HKEY_CURRENT_CONFIG
-            return error.Unimplemented;
-        },
-        6 => {
-            // HKEY_DYN_DATA
-            return error.Unimplemented;
-        },
-        else => {
-            return error.InvalidParameter;
-        },
-    }
-}
-
-fn OpenLocalMachineKey() !HANDLE {
-    const path = "\\Registry\\Machine";
-    var path_u16: [REG_MAX_NAME_SIZE]u16 = undefined;
-    const path_len_u16 = try std.unicode.utf8ToUtf16Le(&path_u16, path);
-    const path_len_bytes = @intCast(u16, path_len_u16 * 2);
-
-    var key_name = UNICODE_STRING{
-        .Length = path_len_bytes,
-        .MaximumLength = path_len_bytes,
-        .Buffer = @intToPtr([*]u16, @ptrToInt(&path_u16)),
-    };
-
-    var attr = OBJECT_ATTRIBUTES{
-        .Length = @sizeOf(OBJECT_ATTRIBUTES),
-        .RootDirectory = null,
-        .Attributes = OBJ_CASE_INSENSITIVE,
-        .ObjectName = &key_name,
-        .SecurityDescriptor = null,
-        .SecurityQualityOfService = null,
-    };
-
-    var result: HKEY = undefined;
-    const rc = ntdll.NtOpenKey(
-        &result,
-        MAXIMUM_ALLOWED,
-        attr,
-    );
-    switch (rc) {
-        .SUCCESS => return result,
-        else => return unexpectedStatus(rc),
-    }
-}
lib/std/zig/system/windows.zig
@@ -43,13 +43,124 @@ pub fn detectRuntimeVersion() WindowsVersion {
     return @intToEnum(WindowsVersion, version);
 }
 
+fn detectCpuModelArm64() !*const Target.Cpu.Model {
+    // Pull the CPU identifier from the registry.
+    // Assume max number of cores to be at 128.
+    const max_cpu_count = 128;
+    const cpu_count = getCpuCount();
+
+    if (cpu_count > max_cpu_count) return error.TooManyCpus;
+
+    const table_size = max_cpu_count * 3 + 1;
+    const actual_table_size = cpu_count * 3 + 1;
+    var table: [table_size]std.os.windows.RTL_QUERY_REGISTRY_TABLE = undefined;
+
+    // Table sentinel
+    table[actual_table_size - 1] = .{
+        .QueryRoutine = null,
+        .Flags = 0,
+        .Name = null,
+        .EntryContext = null,
+        .DefaultType = 0,
+        .DefaultData = null,
+        .DefaultLength = 0,
+    };
+
+    // Technically, a registry value can be as long as 16k u16s. However, MS recommends storing
+    // values larger than 2048 in a file rather than directly in the registry, and since we
+    // are only accessing a system hive \Registry\Machine, we stick to MS guidelines.
+    // https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits
+    const max_sz_value = 2048;
+    const key_name = std.unicode.utf8ToUtf16LeStringLiteral("Identifier");
+
+    var i: usize = 0;
+    var index: usize = 0;
+    while (i < cpu_count) : (i += 1) {
+        var buf: [max_sz_value]u16 = undefined;
+        var buf_uni = std.os.windows.UNICODE_STRING{
+            .Length = buf.len * 2,
+            .MaximumLength = buf.len * 2,
+            .Buffer = &buf,
+        };
+
+        var next_cpu_buf: [std.math.log2(max_cpu_count)]u8 = undefined;
+        const next_cpu = try std.fmt.bufPrint(&next_cpu_buf, "{d}", .{i});
+
+        var subkey: [std.math.log2(max_cpu_count) / 2]u16 = undefined;
+        const subkey_len = try std.unicode.utf8ToUtf16Le(&subkey, next_cpu);
+        subkey[subkey_len] = 0;
+
+        table[index] = .{
+            .QueryRoutine = null,
+            .Flags = std.os.windows.RTL_QUERY_REGISTRY_SUBKEY | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
+            .Name = subkey[0..subkey_len :0],
+            .EntryContext = null,
+            .DefaultType = std.os.windows.REG_NONE,
+            .DefaultData = null,
+            .DefaultLength = 0,
+        };
+
+        table[index + 1] = .{
+            .QueryRoutine = null,
+            .Flags = std.os.windows.RTL_QUERY_REGISTRY_DIRECT | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED,
+            .Name = @intToPtr([*:0]u16, @ptrToInt(key_name)),
+            .EntryContext = &buf_uni,
+            .DefaultType = std.os.windows.REG_NONE,
+            .DefaultData = null,
+            .DefaultLength = 0,
+        };
+
+        table[index + 2] = .{
+            .QueryRoutine = null,
+            .Flags = std.os.windows.RTL_QUERY_REGISTRY_TOPKEY,
+            .Name = null,
+            .EntryContext = null,
+            .DefaultType = std.os.windows.REG_NONE,
+            .DefaultData = null,
+            .DefaultLength = 0,
+        };
+
+        index += 3;
+    }
+
+    const topkey = std.unicode.utf8ToUtf16LeStringLiteral("\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor");
+    const res = std.os.windows.ntdll.RtlQueryRegistryValues(
+        std.os.windows.RTL_REGISTRY_ABSOLUTE,
+        topkey,
+        &table,
+        null,
+        null,
+    );
+    switch (res) {
+        .SUCCESS => {},
+        else => return error.QueryRegistryFailed,
+    }
+
+    // Parse the models from strings
+    i = 0;
+    index = 0;
+    while (i < cpu_count) : (i += 1) {
+        const entry = @ptrCast(*align(1) const std.os.windows.UNICODE_STRING, table[index + 1].EntryContext);
+        index += 3;
+
+        var identifier_buf: [max_sz_value * 2]u8 = undefined;
+        const len = try std.unicode.utf16leToUtf8(&identifier_buf, entry.Buffer[0 .. entry.Length / 2]);
+        const identifier = identifier_buf[0..len];
+        _ = identifier;
+    }
+
+    return &Target.aarch64.cpu.microsoft_sq3;
+}
+
 fn detectNativeCpuAndFeaturesArm64() Target.Cpu {
     const Feature = Target.aarch64.Feature;
 
+    const model = detectCpuModelArm64() catch Target.Cpu.Model.generic(.aarch64);
+
     var cpu = Target.Cpu{
         .arch = .aarch64,
-        .model = Target.Cpu.Model.generic(.aarch64),
-        .features = Target.Cpu.Feature.Set.empty,
+        .model = model,
+        .features = model.features,
     };
 
     if (IsProcessorFeaturePresent(PF.ARM_NEON_INSTRUCTIONS_AVAILABLE)) {