• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "host-common/opengl/NativeGpuInfo.h"
16 #include "host-common/opengl/emugl_config.h"
17 
18 #include "aemu/base/StringFormat.h"
19 #include "aemu/base/containers/SmallVector.h"
20 #include "aemu/base/StringFormat.h"
21 #include "aemu/base/files/PathUtils.h"
22 #include "aemu/base/system/System.h"
23 #include "aemu/base/system/Win32UnicodeString.h"
24 
25 #include <windows.h>
26 #include <d3d9.h>
27 
28 #include <ctype.h>
29 
30 #include <algorithm>
31 #include <string>
32 #include <tuple>
33 
34 using android::base::PathUtils;
35 using android::base::SmallFixedVector;
36 using android::base::StringFormat;
37 using android::base::Win32UnicodeString;
38 
toLower(std::string & s)39 static std::string& toLower(std::string& s) {
40     std::transform(s.begin(), s.end(), s.begin(), ::tolower);
41     return s;
42 }
43 
parse_windows_gpu_ids(const std::string & val,GpuInfoList * gpulist)44 static void parse_windows_gpu_ids(const std::string& val,
45                                   GpuInfoList* gpulist) {
46     std::string result;
47     size_t key_start = 0;
48     size_t key_end = 0;
49 
50     key_start = val.find("VEN_", key_start);
51     if (key_start == std::string::npos) {
52         return;
53     }
54     key_end = val.find("&", key_start);
55     if (key_end == std::string::npos) {
56         return;
57     }
58     result = val.substr(key_start + 4, key_end - key_start - 4);
59     gpulist->currGpu().make = std::move(toLower(result));
60 
61     key_start = val.find("DEV_", key_start);
62     if (key_start == std::string::npos) {
63         return;
64     }
65     key_end = val.find("&", key_start);
66     if (key_end == std::string::npos) {
67         return;
68     }
69     result = val.substr(key_start + 4, key_end - key_start - 4);
70     gpulist->currGpu().device_id = std::move(toLower(result));
71 }
72 
startsWith(const std::string & string,const std::string & prefix)73 static bool startsWith(const std::string& string, const std::string& prefix) {
74     return string.size() >= prefix.size() &&
75             memcmp(string.data(), prefix.data(), prefix.size()) == 0;
76 }
77 
add_predefined_gpu_dlls(GpuInfo * gpu)78 static void add_predefined_gpu_dlls(GpuInfo* gpu) {
79     const std::string& currMake = gpu->make;
80     if (currMake == "NVIDIA" || startsWith(gpu->model, "NVIDIA")) {
81         gpu->addDll("nvoglv32.dll");
82         gpu->addDll("nvoglv64.dll");
83     } else if (currMake == "Advanced Micro Devices, Inc." ||
84                startsWith(gpu->model, "Advanced Micro Devices, Inc.")) {
85         gpu->addDll("atioglxx.dll");
86         gpu->addDll("atig6txx.dll");
87     }
88 }
89 
parse_windows_gpu_dlls(int line_loc,int val_pos,const std::string & contents,GpuInfoList * gpulist)90 static void parse_windows_gpu_dlls(int line_loc,
91                                    int val_pos,
92                                    const std::string& contents,
93                                    GpuInfoList* gpulist) {
94     if (line_loc - val_pos != 0) {
95         const std::string& dll_str =
96                 contents.substr(val_pos, line_loc - val_pos);
97 
98         size_t vp = 0;
99         size_t dll_sep_loc = dll_str.find(",", vp);
100         size_t dll_end = (dll_sep_loc != std::string::npos)
101                                  ? dll_sep_loc
102                                  : dll_str.size() - vp;
103         gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp));
104 
105         while (dll_sep_loc != std::string::npos) {
106             vp = dll_sep_loc + 1;
107             dll_sep_loc = dll_str.find(",", vp);
108             dll_end = (dll_sep_loc != std::string::npos) ? dll_sep_loc
109                                                          : dll_str.size() - vp;
110             gpulist->currGpu().addDll(dll_str.substr(vp, dll_end - vp));
111         }
112     }
113 
114     add_predefined_gpu_dlls(&gpulist->currGpu());
115 }
116 
load_gpu_registry_info(const wchar_t * keyName,GpuInfo * gpu)117 static void load_gpu_registry_info(const wchar_t* keyName, GpuInfo* gpu) {
118     HKEY hkey;
119     if (::RegOpenKeyW(HKEY_LOCAL_MACHINE, keyName, &hkey) != ERROR_SUCCESS) {
120         return;
121     }
122 
123     SmallFixedVector<wchar_t, 256> name;
124     SmallFixedVector<BYTE, 1024> value;
125     for (int i = 0;; ++i) {
126         name.resize_noinit(name.capacity());
127         value.resize_noinit(value.capacity());
128         DWORD nameLen = name.size();
129         DWORD valueLen = value.size();
130         DWORD type;
131         auto res = RegEnumValueW(hkey, i, name.data(), &nameLen, nullptr, &type,
132                                  value.data(), &valueLen);
133         if (res == ERROR_NO_MORE_ITEMS) {
134             break;
135         } else if (res == ERROR_MORE_DATA) {
136             if (type != REG_SZ && type != REG_MULTI_SZ) {
137                 // we don't care about other types for now, so let's not even
138                 // try
139                 continue;
140             }
141             name.resize_noinit(nameLen + 1);
142             value.resize_noinit(valueLen + 1);
143             nameLen = name.size();
144             valueLen = value.size();
145             res = ::RegEnumValueW(hkey, i, name.data(), &nameLen, nullptr,
146                                   &type, value.data(), &valueLen);
147             if (res != ERROR_SUCCESS) {
148                 break;
149             }
150         }
151         if (res != ERROR_SUCCESS) {
152             break;  // well, what can we do here?
153         }
154 
155         name[nameLen] = L'\0';
156 
157         if (type == REG_SZ && wcscmp(name.data(), L"DriverVersion") == 0) {
158             const auto strVal = (wchar_t*)value.data();
159             const auto strLen = valueLen / sizeof(wchar_t);
160             strVal[strLen] = L'\0';
161             gpu->version = Win32UnicodeString::convertToUtf8(strVal, strLen);
162         } else if (type == REG_MULTI_SZ &&
163                    (wcscmp(name.data(), L"UserModeDriverName") == 0 ||
164                     wcscmp(name.data(), L"UserModeDriverNameWoW") == 0)) {
165             const auto strVal = (wchar_t*)value.data();
166             const auto strLen = valueLen / sizeof(wchar_t);
167             strVal[strLen] = L'\0';
168             // Iterate over the '0'-delimited list of strings,
169             // stopping at double '0' (AKA empty string after the
170             // delimiter).
171             for (const wchar_t* ptr = strVal;;) {
172                 auto len = wcslen(ptr);
173                 if (!len) {
174                     break;
175                 }
176                 gpu->dlls.emplace_back(
177                         Win32UnicodeString::convertToUtf8(ptr, len));
178                 ptr += len + 1;
179             }
180         }
181     }
182 
183     ::RegCloseKey(hkey);
184 }
185 
186 // static const int kGPUInfoQueryTimeoutMs = 5000;
187 // static std::string load_gpu_info_wmic() {
188 //     auto guid = Uuid::generateFast().toString();
189 //     // WMIC doesn't allow one to have any unquoted '-' characters in file name,
190 //     // so let's get rid of them.
191 //     guid.erase(std::remove(guid.begin(), guid.end(), '-'), guid.end());
192 //     auto tempName = PathUtils::join(System::get()->getTempDir(),
193 //                                     StringFormat("gpuinfo_%s.txt", guid));
194 //
195 //     auto deleteTempFile = makeCustomScopedPtr(
196 //             &tempName,
197 //             [](const std::string* name) { path_delete_file(name->c_str()); });
198 //     if (!System::get()->runCommand(
199 //                 {"wmic", StringFormat("/OUTPUT:%s", tempName), "path",
200 //                  "Win32_VideoController", "get", "/value"},
201 //                 RunOptions::WaitForCompletion | RunOptions::TerminateOnTimeout,
202 //                 kGPUInfoQueryTimeoutMs)) {
203 //         return {};
204 //     }
205 //     auto res = android::readFileIntoString(tempName);
206 //     return res ? Win32UnicodeString::convertToUtf8(
207 //                          (const wchar_t*)res->c_str(),
208 //                          res->size() / sizeof(wchar_t))
209 //                : std::string{};
210 // }
211 
parse_gpu_info_list_windows(const std::string & contents,GpuInfoList * gpulist)212 void parse_gpu_info_list_windows(const std::string& contents,
213                                  GpuInfoList* gpulist) {
214     size_t line_loc = contents.find("\r\n");
215     if (line_loc == std::string::npos) {
216         line_loc = contents.size();
217     }
218     size_t p = 0;
219     size_t equals_pos = 0;
220     size_t val_pos = 0;
221     std::string key;
222     std::string val;
223 
224     // Windows: We use `wmic path Win32_VideoController get /value`
225     // to get a reasonably detailed list of '<key>=<val>'
226     // pairs. From these, we can get the make/model
227     // of the GPU, the driver version, and all DLLs involved.
228     while (line_loc != std::string::npos) {
229         equals_pos = contents.find("=", p);
230         if ((equals_pos != std::string::npos) && (equals_pos < line_loc)) {
231             key = contents.substr(p, equals_pos - p);
232             val_pos = equals_pos + 1;
233             val = contents.substr(val_pos, line_loc - val_pos);
234 
235             if (key.find("AdapterCompatibility") != std::string::npos) {
236                 gpulist->addGpu();
237                 gpulist->currGpu().os = "W";
238                 // 'make' will be overwritten in parsing 'PNPDeviceID'
239                 // later. Set it here because we need it in paring
240                 // 'InstalledDisplayDrivers' which comes before
241                 // 'PNPDeviceID'.
242                 gpulist->currGpu().make = val;
243             } else if (key.find("Caption") != std::string::npos) {
244                 gpulist->currGpu().model = val;
245             } else if (key.find("PNPDeviceID") != std::string::npos) {
246                 parse_windows_gpu_ids(val, gpulist);
247             } else if (key.find("DriverVersion") != std::string::npos) {
248                 gpulist->currGpu().version = val;
249             } else if (key.find("InstalledDisplayDrivers") !=
250                        std::string::npos) {
251                 parse_windows_gpu_dlls(line_loc, val_pos, contents, gpulist);
252             }
253         }
254         if (line_loc == contents.size()) {
255             break;
256         }
257         p = line_loc + 2;
258         line_loc = contents.find("\r\n", p);
259         if (line_loc == std::string::npos) {
260             line_loc = contents.size();
261         }
262     }
263 }
264 
queryGpuInfoD3D(GpuInfoList * gpus)265 static bool queryGpuInfoD3D(GpuInfoList* gpus) {
266     LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);
267     UINT numAdapters = pD3D->GetAdapterCount();
268 
269     char vendoridBuf[16] = {};
270     char deviceidBuf[16] = {};
271 
272     // MAX_DEVICE_IDENTIFIER_STRING can be pretty big,
273     // don't allocate on stack.
274     std::vector<char> descriptionBuf(MAX_DEVICE_IDENTIFIER_STRING + 1, '\0');
275 
276     if (numAdapters == 0) return false;
277 
278     // The adapter that is equal to D3DADAPTER_DEFAULT is the primary display adapter.
279     // D3DADAPTER_DEFAULT is currently defined to be 0, btw---but this is more future proof
280     for (UINT i = 0; i < numAdapters; i++) {
281         if (i == D3DADAPTER_DEFAULT) {
282             gpus->addGpu();
283             GpuInfo& gpu = gpus->currGpu();
284             gpu.os = "W";
285 
286             D3DADAPTER_IDENTIFIER9 id;
287             pD3D->GetAdapterIdentifier(0, 0, &id);
288             snprintf(vendoridBuf, sizeof(vendoridBuf), "%04x", (unsigned int)id.VendorId);
289             snprintf(deviceidBuf, sizeof(deviceidBuf), "%04x", (unsigned int)id.DeviceId);
290             snprintf(&descriptionBuf[0], MAX_DEVICE_IDENTIFIER_STRING, "%s", id.Description);
291             gpu.make = vendoridBuf;
292             gpu.device_id = deviceidBuf;
293             gpu.model = &descriptionBuf[0];
294             // crashhandler_append_message_format(
295             //     "gpu found. vendor id %04x device id 0x%04x\n",
296             //     (unsigned int)(id.VendorId),
297             //     (unsigned int)(id.DeviceId));
298             return true;
299         }
300     }
301 
302     return false;
303 }
304 
getGpuInfoListNative(GpuInfoList * gpus)305 void getGpuInfoListNative(GpuInfoList* gpus) {
306     if (queryGpuInfoD3D(gpus)) return;
307 
308     // crashhandler_append_message_format("d3d gpu query failed.\n");
309 
310     DISPLAY_DEVICEW device = { sizeof(device) };
311 
312     for (int i = 0; EnumDisplayDevicesW(nullptr, i, &device, 0); ++i) {
313         gpus->addGpu();
314         GpuInfo& gpu = gpus->currGpu();
315         gpu.os = "W";
316         gpu.current_gpu =
317                 (device.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) != 0;
318         gpu.model = Win32UnicodeString::convertToUtf8(device.DeviceString);
319         parse_windows_gpu_ids(
320                 Win32UnicodeString::convertToUtf8(device.DeviceID), gpus);
321 
322         // Now try inspecting the registry directly; |device|.DeviceKey can be a
323         // path to the GPU information key.
324         static const std::string prefix = "\\Registry\\Machine\\";
325         if (startsWith(Win32UnicodeString::convertToUtf8(device.DeviceKey),
326                        prefix)) {
327             load_gpu_registry_info(device.DeviceKey + prefix.size(), &gpu);
328         }
329         add_predefined_gpu_dlls(&gpu);
330     }
331 
332     if (gpus->infos.empty()) {
333         // Everything failed; bail.
334         // Everything failed - fall back to the good^Wbad old WMIC command.
335         // auto gpuInfoWmic = load_gpu_info_wmic();
336         // parse_gpu_info_list_windows(gpuInfoWmic, gpus);
337     }
338 }
339 
340 // windows: blacklist depending on amdvlk and certain versions of vulkan-1.dll
341 // Based on chromium/src/gpu/config/gpu_info_collector_win.cc
badAmdVulkanDriverVersion()342 bool badAmdVulkanDriverVersion() {
343     int major, minor, build_1, build_2;
344 
345     // crashhandler_append_message_format(
346     //     "checking for bad AMD Vulkan driver version...\n");
347 
348     if (!android::base::queryFileVersionInfo("amdvlk64.dll", &major, &minor, &build_1, &build_2)) {
349         // crashhandler_append_message_format(
350         //     "amdvlk64.dll not found. Checking for amdvlk32...\n");
351         if (!android::base::queryFileVersionInfo("amdvlk32.dll", &major, &minor, &build_1, &build_2)) {
352             // crashhandler_append_message_format(
353             //     "amdvlk32.dll not found. No bad AMD Vulkan driver versions found.\n");
354             // Information about amdvlk64 not availble; not blacklisted
355             return false;
356         }
357     }
358 
359     // crashhandler_append_message_format(
360     //     "AMD driver info found. Version: %d.%d.%d.%d\n",
361     //     major, minor, build_1, build_2);
362 
363     bool isBad = (major == 1 && minor == 0 && build_1 <= 54);
364 
365     if (isBad) {
366         // crashhandler_append_message_format(
367         //     "Is bad AMD driver version; blacklisting.\n");
368     } else {
369         // crashhandler_append_message_format(
370         //     "Not known bad AMD driver version; passing.\n");
371     }
372 
373     return isBad;
374 }
375 
376 using WindowsDllVersion = std::tuple<int, int, int, int>;
377 
badVulkanDllVersion()378 bool badVulkanDllVersion() {
379     int major, minor, build_1, build_2;
380 
381     // crashhandler_append_message_format(
382     //     "checking for bad vulkan-1.dll version...\n");
383 
384     const char* vulkanDllPath = emuglConfig_get_vulkan_runtime_full_path();
385     if (!android::base::queryFileVersionInfo(vulkanDllPath, &major, &minor, &build_1, &build_2)) {
386         // crashhandler_append_message_format(
387         //     "info on vulkan-1.dll cannot be found, continue.\n");
388         // Information about vulkan-1.dll not available; not blacklisted
389         return false;
390     }
391 
392     // crashhandler_append_message_format(
393     //     "vulkan-1.dll version: %d.%d.%d.%d\n",
394     //     major, minor, build_1, build_2);
395 
396     // Ban all Windows Vulkan drivers < 1.1;
397     // they sometimes advertise vkEnumerateInstanceVersion
398     // and then running that function pointer causes a segfault.
399     // In any case, properly updated GPU drivers for Windows
400     // should all be 1.1 now for the major manufacturers
401     // (even Intel; see https://www.intel.com/content/www/us/en/support/articles/000005524/graphics-drivers.html)
402     bool isBad =
403         major == 1 && minor == 0;
404 
405     if (isBad) {
406         // crashhandler_append_message_format(
407         //     "Is bad vulkan-1.dll version; blacklisting.\n");
408     } else {
409         // crashhandler_append_message_format(
410         //     "Not known bad vulkan-1.dll version; continue.\n");
411     }
412 
413     return isBad;
414 }
415 
isVulkanSafeToUseNative()416 bool isVulkanSafeToUseNative() {
417     return !badAmdVulkanDriverVersion() && !badVulkanDllVersion();
418 }
419