/src/wireshark/wsutil/cpu_info.c
Line | Count | Source |
1 | | /* cpu_info.c |
2 | | * Routines to report CPU information |
3 | | * |
4 | | * Wireshark - Network traffic analyzer |
5 | | * By Gerald Combs <gerald@wireshark.org> |
6 | | * Copyright 1998 Gerald Combs |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | */ |
10 | | |
11 | | #include "config.h" |
12 | | #include <wsutil/cpu_info.h> |
13 | | |
14 | | #include <string.h> |
15 | | |
16 | | #include <wsutil/ws_cpuid.h> |
17 | | #include <wsutil/file_util.h> |
18 | | |
19 | | #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__) |
20 | | #define HAVE_SYSCTL |
21 | | #elif defined(sun) || defined(__sun) |
22 | | #define HAVE_SYSINFO |
23 | | #endif |
24 | | |
25 | | #if defined(_WIN32) |
26 | | #include <windows.h> |
27 | | #elif defined(HAVE_SYSCTL) |
28 | | #include <sys/types.h> |
29 | | #include <sys/sysctl.h> |
30 | | #elif defined(HAVE_SYSINFO) |
31 | | #include <sys/systeminfo.h> |
32 | | #endif |
33 | | |
34 | | /* |
35 | | * Functions used for the GTree we use to keep a list of *unique* |
36 | | * model strings. |
37 | | */ |
38 | | static int |
39 | | compare_model_names(const void *a, const void *b, void * user_data _U_) |
40 | 434 | { |
41 | 434 | return strcmp((const char *)a, (const char *)b); |
42 | 434 | } |
43 | | |
44 | | struct string_info { |
45 | | GString *str; |
46 | | const char *sep; |
47 | | }; |
48 | | |
49 | | static gboolean |
50 | | add_model_name_to_string(void * key, void * value _U_, |
51 | | void * data) |
52 | 14 | { |
53 | 14 | struct string_info *info = (struct string_info *)data; |
54 | | |
55 | | /* Separate this from the previous entry, if necessary. */ |
56 | 14 | if (info->sep != NULL) |
57 | 0 | g_string_append(info->str, info->sep); |
58 | | |
59 | | /* Now add the model name. */ |
60 | 14 | g_string_append(info->str, g_strstrip((char *)key)); |
61 | | |
62 | | /* |
63 | | * There will *definitely* need to be a separator for any subsequent |
64 | | * model string. |
65 | | */ |
66 | 14 | info->sep = ", "; |
67 | | |
68 | | /* Keep going. */ |
69 | 14 | return false; |
70 | 14 | } |
71 | | |
72 | | static const char * |
73 | | get_translator_name(void) |
74 | 14 | { |
75 | | #if defined(__APPLE__) /* Darwin */ |
76 | | /* |
77 | | * OK, are we running x86(-64) code on Arm(64) under Rosetta? |
78 | | * |
79 | | * See |
80 | | * |
81 | | * https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment#Determine-Whether-Your-App-Is-Running-as-a-Translated-Binary |
82 | | */ |
83 | | int proc_translated; |
84 | | size_t proc_translated_len = sizeof proc_translated; |
85 | | |
86 | | if (sysctlbyname("sysctl.proc_translated", &proc_translated, |
87 | | &proc_translated_len, NULL, 0) == -1) { |
88 | | /* |
89 | | * errno = ENOENT means there's no such name; presumably |
90 | | * that means there's no translator to check for, so we're |
91 | | * not running under translation. |
92 | | * |
93 | | * For other errors, we just punt. |
94 | | */ |
95 | | return NULL; |
96 | | } |
97 | | return proc_translated ? "Rosetta 2" : NULL; |
98 | | #else |
99 | | /* |
100 | | * XXX - handle Windows x86-64-to-ARM64 translator, possibly with |
101 | | * IsWow64Process2(); see |
102 | | * |
103 | | * https://learn.microsoft.com/en-us/windows/win32/api/wow64apiset/nf-wow64apiset-iswow64process2 |
104 | | * |
105 | | * Any others? |
106 | | */ |
107 | 14 | return NULL; |
108 | 14 | #endif |
109 | 14 | } |
110 | | |
111 | | /* |
112 | | * Get the CPU info, and append it to the GString |
113 | | * |
114 | | * On at least some OSes, there's a call that will return this information |
115 | | * for all CPU types for which the OS determines that information, not just |
116 | | * x86 processors with CPUID and the brand string. On those OSes, we use |
117 | | * that. |
118 | | * |
119 | | * On other OSes, we use ws_cpuid(), which will fail unconditionally on |
120 | | * non-x86 CPUs. |
121 | | */ |
122 | | void |
123 | | get_cpu_info(GString *str) |
124 | 14 | { |
125 | 14 | GTree *model_names = g_tree_new_full(compare_model_names, NULL, g_free, NULL); |
126 | 14 | const char *translator_name; |
127 | | |
128 | 14 | #if defined(__linux__) |
129 | | /* |
130 | | * We scan /proc/cpuinfo looking for lines that begins with |
131 | | * "model name\t: ", and extract what comes after that prefix. |
132 | | * |
133 | | * /proc/cpuinfo can report information about multiple "CPU"s. |
134 | | * A "CPU" appears to be a CPU core, so this treats a multi-core |
135 | | * chip as multiple CPUs (which is arguably should), but doesn't |
136 | | * appear to treat a multi-threaded core as multiple CPUs. |
137 | | * |
138 | | * So we accumulate a table of *multiple* CPU strings, saving |
139 | | * one copy of each unique string, and glue them together at |
140 | | * the end. We use a GTree for this. |
141 | | * |
142 | | * We test for Linux first, so that, even if you're on a Linux |
143 | | * that supports sysctl(), we don't use it, we scan /proc/cpuinfo, |
144 | | * as that's the right way to do this. |
145 | | */ |
146 | 14 | FILE *proc_cpuinfo; |
147 | | |
148 | 14 | proc_cpuinfo = ws_fopen("/proc/cpuinfo", "r"); |
149 | 14 | if (proc_cpuinfo == NULL) { |
150 | | /* Just give up. */ |
151 | 0 | g_tree_destroy(model_names); |
152 | 0 | return; |
153 | 0 | } |
154 | | |
155 | 14 | char *line = NULL; |
156 | 14 | size_t linecap = 0; |
157 | 14 | static const char prefix[] = "model name\t: "; |
158 | 12.5k | #define PREFIX_STRLEN (sizeof prefix - 1) |
159 | 14 | ssize_t linelen; |
160 | | |
161 | | /* |
162 | | * Read lines from /proc/cpuinfo; stop when we either hit an EOF |
163 | | * or get an error. |
164 | | */ |
165 | 12.1k | for (;;) { |
166 | 12.1k | linelen = getline(&line, &linecap, proc_cpuinfo); |
167 | 12.1k | if (linelen == -1) { |
168 | | /* EOF or error; just stop. */ |
169 | 14 | break; |
170 | 14 | } |
171 | | /* Remove trailing newline. */ |
172 | 12.0k | if (linelen != 0) |
173 | 12.0k | line[linelen - 1] = '\0'; |
174 | 12.0k | if (strncmp(line, prefix, PREFIX_STRLEN) == 0) { |
175 | | /* OK, we have a model name. */ |
176 | 448 | char *model_name; |
177 | | |
178 | | /* Get everything after the prefix. */ |
179 | 448 | model_name = g_strdup(line + PREFIX_STRLEN); |
180 | | |
181 | | /* |
182 | | * Add an entry to the tree with the model name as key and |
183 | | * a null value. There will only be one such entry in the |
184 | | * tree; if there's already such an entry, it will be left |
185 | | * alone, and model_name will be freed, otherwise a new |
186 | | * node will be created using model_name as the key. |
187 | | * |
188 | | * Thus, we don't free model_name; either it will be freed |
189 | | * for us, or it will be used in the tree and freed when we |
190 | | * free the tree. |
191 | | */ |
192 | 448 | g_tree_insert(model_names, model_name, NULL); |
193 | 448 | } |
194 | 12.0k | } |
195 | | |
196 | 14 | fclose(proc_cpuinfo); |
197 | 14 | #define xx_free free /* hack so checkAPIs doesn't complain */ |
198 | 14 | xx_free(line); /* yes, free(), as getline() mallocates it */ |
199 | | #elif defined(_WIN32) |
200 | | /* |
201 | | * They're in the Registry. (Isn't everything?) |
202 | | */ |
203 | | HKEY processors_key; |
204 | | |
205 | | if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, |
206 | | L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor", |
207 | | 0, KEY_READ, &processors_key) != ERROR_SUCCESS) { |
208 | | /* Just give up. */ |
209 | | g_tree_destroy(model_names); |
210 | | return; |
211 | | } |
212 | | |
213 | | /* |
214 | | * The processors appear under that key. Enumerate all the keys |
215 | | * under it. |
216 | | */ |
217 | | DWORD num_subkeys; |
218 | | DWORD max_subkey_len; |
219 | | wchar_t *subkey_buf; |
220 | | |
221 | | /* |
222 | | * How many subkeys are there, and what's the biggest subkey size? |
223 | | * |
224 | | * I assume that when the documentation says that some number is |
225 | | * in units of "Unicode characters" they mean "units of elements |
226 | | * of UTF-16 characters", i.e. "units of 2-octet items". |
227 | | */ |
228 | | if (RegQueryInfoKeyW(processors_key, NULL, NULL, NULL, &num_subkeys, |
229 | | &max_subkey_len, NULL, NULL, NULL, NULL, NULL, |
230 | | NULL) != ERROR_SUCCESS) { |
231 | | /* Just give up. */ |
232 | | g_tree_destroy(model_names); |
233 | | return; |
234 | | } |
235 | | |
236 | | /* |
237 | | * max_subkey_len does not count the trailing '\0'. Add it. |
238 | | */ |
239 | | max_subkey_len++; |
240 | | |
241 | | /* |
242 | | * Allocate a buffer for the subkey. |
243 | | */ |
244 | | subkey_buf = g_new(wchar_t, max_subkey_len); |
245 | | if (subkey_buf == NULL) { |
246 | | /* Just give up. */ |
247 | | g_tree_destroy(model_names); |
248 | | return; |
249 | | } |
250 | | |
251 | | for (DWORD processor_index = 0; processor_index < num_subkeys; |
252 | | processor_index++) { |
253 | | /* |
254 | | * The documentation says that this is "in characters"; I'm |
255 | | * assuming, for now, that they mean "Unicode characters", |
256 | | * meaning "2-octet items". |
257 | | */ |
258 | | DWORD subkey_bufsize = max_subkey_len; |
259 | | if (RegEnumKeyExW(processors_key, processor_index, subkey_buf, |
260 | | &subkey_bufsize, NULL, NULL, NULL, |
261 | | NULL) != ERROR_SUCCESS) { |
262 | | /* Just exit the loop. */ |
263 | | break; |
264 | | } |
265 | | |
266 | | /* |
267 | | * Get the length of processor name string for this processor. |
268 | | * |
269 | | * That's the "ProcessorNameString" value for the subkey of |
270 | | * processors_key with the name in subkey_buf. |
271 | | * |
272 | | * It's a string, so only allow REG_SZ values. |
273 | | */ |
274 | | DWORD model_name_bufsize; |
275 | | |
276 | | model_name_bufsize = 0; |
277 | | if (RegGetValueW(processors_key, subkey_buf, L"ProcessorNameString", |
278 | | RRF_RT_REG_SZ, NULL, NULL, |
279 | | &model_name_bufsize) != ERROR_SUCCESS) { |
280 | | /* Just exit the loop. */ |
281 | | break; |
282 | | } |
283 | | |
284 | | /* |
285 | | * Allocate a buffer for the string, as UTF-16. |
286 | | * The retrieved length includes the terminating '\0'. |
287 | | */ |
288 | | wchar_t *model_name_wchar = g_malloc(model_name_bufsize); |
289 | | if (RegGetValueW(processors_key, subkey_buf, L"ProcessorNameString", |
290 | | RRF_RT_REG_SZ, NULL, model_name_wchar, |
291 | | &model_name_bufsize) != ERROR_SUCCESS) { |
292 | | /* Just exit the loop. */ |
293 | | g_free(model_name_wchar); |
294 | | break; |
295 | | } |
296 | | |
297 | | /* Convert it to UTF-8. */ |
298 | | char *model_name = g_utf16_to_utf8(model_name_wchar, -1, NULL, NULL, NULL); |
299 | | g_free(model_name_wchar); |
300 | | |
301 | | /* |
302 | | * Add an entry to the tree with the model name as key and |
303 | | * a null value. There will only be one such entry in the |
304 | | * tree; if there's already such an entry, it will be left |
305 | | * alone, and model_name will be freed, otherwise a new |
306 | | * node will be created using model_name as the key. |
307 | | * |
308 | | * Thus, we don't free model_name; either it will be freed |
309 | | * for us, or it will be used in the tree and freed when we |
310 | | * free the tree. |
311 | | */ |
312 | | g_tree_insert(model_names, model_name, NULL); |
313 | | } |
314 | | |
315 | | g_free(subkey_buf); |
316 | | |
317 | | /* |
318 | | * Close the registry key. |
319 | | */ |
320 | | RegCloseKey(processors_key); |
321 | | #elif defined(HAVE_SYSCTL) |
322 | | /* |
323 | | * Fetch the string using the appropriate sysctl. |
324 | | */ |
325 | | size_t model_name_len; |
326 | | char *model_name; |
327 | | #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) |
328 | | /* |
329 | | * Thanks, OpenBSD guys, for not having APIs to map MIB names to |
330 | | * MIB values! Just consruct the MIB entry directly. |
331 | | * |
332 | | * We also do that for FreeBSD and DragonFly BSD, because we can. |
333 | | * |
334 | | * FreeBSD appears to support this for x86, PowerPC/Power ISA, and |
335 | | * Arm. OpenBSD appears to support this for a number of |
336 | | * architectures. DragonFly BSD appears to support it only for |
337 | | * x86, but I think they only run on x86-64 now, and may never |
338 | | * have run on anything non-x86. |
339 | | */ |
340 | | int mib[2] = { CTL_HW, HW_MODEL }; |
341 | | size_t miblen = 2; |
342 | | #else |
343 | | /* These require a lookup, as they don't have #defines. */ |
344 | | #if defined(__APPLE__) /* Darwin */ |
345 | | /* |
346 | | * The code seems to support this on both x86 and ARM. |
347 | | */ |
348 | | #define BRAND_STRING_SYSCTL "machdep.cpu.brand_string" |
349 | | #define MIB_DEPTH 3 |
350 | | #elif defined(__NetBSD__) |
351 | | /* |
352 | | * XXX - the "highly portable Unix-like Open Source operating |
353 | | * system" that "is available for a wide range of platforms" |
354 | | * doesn't seem to support this except on x86, and doesn't |
355 | | * seem to support any other MIB for, for example, ARM64. |
356 | | * |
357 | | * Maybe someday, so use it anyway. |
358 | | */ |
359 | | #define BRAND_STRING_SYSCTL "machdep.cpu_brand" |
360 | | #define MIB_DEPTH 2 |
361 | | #endif |
362 | | int mib[MIB_DEPTH]; |
363 | | size_t miblen = MIB_DEPTH; |
364 | | |
365 | | /* Look up the sysctl name and get the MIB. */ |
366 | | if (sysctlnametomib(BRAND_STRING_SYSCTL, mib, &miblen) == -1) { |
367 | | /* |
368 | | * Either there's no such string or something else went wrong. |
369 | | * Just give up. |
370 | | */ |
371 | | g_tree_destroy(model_names); |
372 | | return; |
373 | | } |
374 | | #endif |
375 | | if (sysctl(mib, (u_int)miblen, NULL, &model_name_len, NULL, 0) == -1) { |
376 | | /* |
377 | | * Either there's no such string or something else went wrong. |
378 | | * Just give up. |
379 | | */ |
380 | | g_tree_destroy(model_names); |
381 | | return; |
382 | | } |
383 | | model_name = g_malloc(model_name_len); |
384 | | if (sysctl(mib, (u_int)miblen, model_name, &model_name_len, NULL, 0) == -1) { |
385 | | /* |
386 | | * Either there's no such string or something else went wrong. |
387 | | * Just give up. |
388 | | */ |
389 | | g_free(model_name); |
390 | | g_tree_destroy(model_names); |
391 | | return; |
392 | | } |
393 | | g_tree_insert(model_names, model_name, NULL); |
394 | | #elif defined(HAVE_SYSINFO) && defined(SI_CPUBRAND) |
395 | | /* |
396 | | * Solaris. Use sysinfo() with SI_CPUBRAND; the documentation |
397 | | * indicates that it works on SPARC as well as x86. |
398 | | * |
399 | | * Unfortunately, SI_CPUBRAND seems to be a recent addition, so |
400 | | * older versions of Solaris - dating back to some versions of |
401 | | * 11.3 - don't have it. |
402 | | */ |
403 | | int model_name_len; |
404 | | char *model_name; |
405 | | |
406 | | /* How big is the model name? */ |
407 | | model_name_len = sysinfo(SI_CPUBRAND, NULL, 0); |
408 | | if (model_name_len == -1) { |
409 | | g_tree_destroy(model_names); |
410 | | return; |
411 | | } |
412 | | model_name = g_malloc(model_name_len); |
413 | | if (sysinfo(SI_CPUBRAND, model_name, model_name_len) == -1) { |
414 | | g_tree_destroy(model_names); |
415 | | return; |
416 | | } |
417 | | g_tree_insert(model_names, model_name, NULL); |
418 | | #else |
419 | | /* |
420 | | * OS for which we don't support the "get the CPU type" call; we |
421 | | * use ws_cpuid(), which uses CPUID on x86 and doesn't get any |
422 | | * information for other instruction sets. |
423 | | */ |
424 | | uint32_t CPUInfo[4]; |
425 | | char CPUBrandString[0x40]; |
426 | | char *model_name; |
427 | | unsigned nExIds; |
428 | | |
429 | | /* |
430 | | * Calling ws_cpuid with 0x80000000 as the selector argument, i.e. |
431 | | * executing a cpuid instruction with EAX equal to 0x80000000 and |
432 | | * ECX equal to 0, gets the number of valid extended IDs. |
433 | | */ |
434 | | if (!ws_cpuid(CPUInfo, 0x80000000)) { |
435 | | g_tree_destroy(model_names); |
436 | | return; |
437 | | } |
438 | | |
439 | | nExIds = CPUInfo[0]; |
440 | | |
441 | | if (nExIds<0x80000005) { |
442 | | g_tree_destroy(model_names); |
443 | | return; |
444 | | } |
445 | | |
446 | | memset(CPUBrandString, 0, sizeof(CPUBrandString)); |
447 | | |
448 | | /* Interpret CPU brand string */ |
449 | | ws_cpuid(CPUInfo, 0x80000002); |
450 | | memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); |
451 | | ws_cpuid(CPUInfo, 0x80000003); |
452 | | memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); |
453 | | ws_cpuid(CPUInfo, 0x80000004); |
454 | | memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); |
455 | | |
456 | | model_name = g_strdup(g_strstrip(CPUBrandString)); |
457 | | g_tree_insert(model_names, model_name, NULL); |
458 | | #endif |
459 | | |
460 | 14 | int num_model_names = g_tree_nnodes(model_names); |
461 | | |
462 | 14 | if (num_model_names > 0) { |
463 | | /* |
464 | | * We have at least one model name, so add the name(s) to |
465 | | * the string. |
466 | | */ |
467 | 14 | if (num_model_names > 1) { |
468 | | /* |
469 | | * There's more than one, so put the list inside curly |
470 | | * brackets. |
471 | | */ |
472 | 0 | g_string_append(str, "{ "); |
473 | 0 | } |
474 | | |
475 | | /* Iterate over the tree, adding model names to the string. */ |
476 | 14 | struct string_info info; |
477 | 14 | info.str = str; |
478 | 14 | info.sep = NULL; |
479 | 14 | g_tree_foreach(model_names, add_model_name_to_string, &info); |
480 | | |
481 | 14 | if (num_model_names > 1) { |
482 | | /* |
483 | | * There's more than one, so put the list inside curly |
484 | | * brackets. |
485 | | */ |
486 | 0 | g_string_append(str, " }"); |
487 | 0 | } |
488 | 14 | } |
489 | | |
490 | | /* We're done; get rid of the tree. */ |
491 | 14 | g_tree_destroy(model_names); |
492 | | |
493 | | /* |
494 | | * Are we running under a translator? |
495 | | */ |
496 | 14 | translator_name = get_translator_name(); |
497 | 14 | if (translator_name != NULL) { |
498 | | /* |
499 | | * Yes. |
500 | | */ |
501 | 0 | g_string_append(str, " running "); |
502 | 0 | g_string_append(str, translator_name); |
503 | 0 | } |
504 | | |
505 | | /* |
506 | | * We do this on all OSes and instruction sets, so that we don't |
507 | | * have to figure out how to dredge the "do we have SSE 4.2?" |
508 | | * information from whatever source provides it in the OS on |
509 | | * x86 processors. We already have ws_cpuid_sse42() (which we |
510 | | * use to determine whether to use SSE 4.2 code to scan buffers |
511 | | * for strings), so use that; it always returns "false" on non-x86 |
512 | | * processors. |
513 | | * |
514 | | * If you have multiple CPUs, some of which support it and some |
515 | | * of which don't, I'm not sure we can guarantee that buffer |
516 | | * scanning will work if, for example, the scanning code gets |
517 | | * preempted while running on an SSE-4.2-capable CPU and, when |
518 | | * it gets rescheduled, gets rescheduled on a non-SSE-4.2-capable |
519 | | * CPU and tries to continue the SSE 4.2-based scan. So we don't |
520 | | * worry about that case; constructing a CPU string is the *least* |
521 | | * of our worries in that case. |
522 | | */ |
523 | 14 | if (ws_cpuid_sse42()) |
524 | 14 | g_string_append(str, " (with SSE4.2)"); |
525 | 14 | } |
526 | | |
527 | | /* |
528 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
529 | | * |
530 | | * Local variables: |
531 | | * c-basic-offset: 4 |
532 | | * tab-width: 8 |
533 | | * indent-tabs-mode: nil |
534 | | * End: |
535 | | * |
536 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
537 | | * :indentSize=4:tabSize=8:noTabs=true: |
538 | | */ |