wireshark/wsutil/cpu_info.c

493 lines
15 KiB
C

/* cpu_info.c
* Routines to report CPU information
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include <string.h>
#include <glib.h>
#include <wsutil/ws_cpuid.h>
#include <wsutil/cpu_info.h>
#include <wsutil/file_util.h>
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__APPLE__)
#define HAVE_SYSCTL
#elif defined(sun) || defined(__sun)
#define HAVE_SYSINFO
#endif
#if defined(_WIN32)
#include <windows.h>
#elif defined(HAVE_SYSCTL)
#include <sys/types.h>
#include <sys/sysctl.h>
#elif defined(HAVE_SYSINFO)
#include <sys/systeminfo.h>
#endif
/*
* Functions used for the GTree we use to keep a list of *unique*
* model strings.
*/
static gint
compare_model_names(gconstpointer a, gconstpointer b, gpointer user_data _U_)
{
return strcmp((const char *)a, (const char *)b);
}
struct string_info {
GString *str;
const char *sep;
};
static gboolean
add_model_name_to_string(gpointer key, gpointer value _U_,
gpointer data)
{
struct string_info *info = (struct string_info *)data;
/* Separate this from the previous entry, if necessary. */
if (info->sep != NULL)
g_string_append(info->str, info->sep);
/* Now add the model name. */
g_string_append(info->str, g_strstrip((char *)key));
/*
* There will *definitely* need to be a separator for any subsequent
* model string.
*/
info->sep = ", ";
/* Keep going. */
return FALSE;
}
/*
* Get the CPU info, and append it to the GString
*
* On at least some OSes, there's a call that will return this information
* for all CPU types for which the OS determines that information, not just
* x86 processors with CPUID and the brand string. On those OSes, we use
* that.
*
* On other OSes, we use ws_cpuid(), which will fail unconditionally on
* non-x86 CPUs.
*/
void
get_cpu_info(GString *str)
{
GTree *model_names = g_tree_new_full(compare_model_names, NULL, g_free, NULL);
#if defined(__linux__)
/*
* We scan /proc/cpuinfo looking for lines that begins with
* "model name\t: ", and extract what comes after that prefix.
*
* /proc/cpuinfo can report information about multiple "CPU"s.
* A "CPU" appears to be a CPU core, so this treats a multi-core
* chip as multiple CPUs (which is arguably should), but doesn't
* appear to treat a multi-threaded core as multiple CPUs.
*
* So we accumulate a table of *multiple* CPU strings, saving
* one copy of each unique string, and glue them together at
* the end. We use a GTree for this.
*
* We test for Linux first, so that, even if you're on a Linux
* that supports sysctl(), we don't use it, we scan /proc/cpuinfo,
* as that's the right way to do this.
*/
FILE *proc_cpuinfo;
proc_cpuinfo = ws_fopen("/proc/cpuinfo", "r");
if (proc_cpuinfo == NULL) {
/* Just give up. */
g_tree_destroy(model_names);
return;
}
char *line = NULL;
size_t linecap = 0;
static const char prefix[] = "model name\t: ";
#define PREFIX_STRLEN (sizeof prefix - 1)
ssize_t linelen;
/*
* Read lines from /proc/cpuinfo; stop when we either hit an EOF
* or get an error.
*/
for (;;) {
linelen = getline(&line, &linecap, proc_cpuinfo);
if (linelen == -1) {
/* EOF or error; just stop. */
break;
}
/* Remove trailing newline. */
if (linelen != 0)
line[linelen - 1] = '\0';
if (strncmp(line, prefix, PREFIX_STRLEN) == 0) {
/* OK, we have a model name. */
char *model_name;
/* Get everything after the prefix. */
model_name = g_strdup(line + PREFIX_STRLEN);
/*
* Add an entry to the tree with the model name as key and
* a null value. There will only be one such entry in the
* tree; if there's already such an entry, it will be left
* alone, and model_name will be freed, otherwise a new
* node will be created using model_name as the key.
*
* Thus, we don't free model_name; either it will be freed
* for us, or it will be used in the tree and freed when we
* free the tree.
*/
g_tree_insert(model_names, model_name, NULL);
}
}
fclose(proc_cpuinfo);
#define xx_free free /* hack so checkAPIs doesn't complain */
xx_free(line); /* yes, free(), as getline() mallocates it */
#elif defined(_WIN32)
/*
* They're in the Registry. (Isn't everything?)
*/
HKEY processors_key;
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor",
0, KEY_READ, &processors_key) != ERROR_SUCCESS) {
/* Just give up. */
g_tree_destroy(model_names);
return;
}
/*
* The processors appear under that key. Enumerate all the keys
* under it.
*/
DWORD num_subkeys;
DWORD max_subkey_len;
wchar_t *subkey_buf;
/*
* How many subkeys are there, and what's the biggest subkey size?
*
* I assume that when the documentation says that some number is
* in units of "Unicode characters" they mean "units of elements
* of UTF-16 characters", i.e. "units of 2-octet items".
*/
if (RegQueryInfoKeyW(processors_key, NULL, NULL, NULL, &num_subkeys,
&max_subkey_len, NULL, NULL, NULL, NULL, NULL,
NULL) != ERROR_SUCCESS) {
/* Just give up. */
g_tree_destroy(model_names);
return;
}
/*
* max_subkey_len does not count the trailing '\0'. Add it.
*/
max_subkey_len++;
/*
* Allocate a buffer for the subkey.
*/
subkey_buf = (wchar_t *)g_malloc(max_subkey_len * sizeof (wchar_t));
if (subkey_buf == NULL) {
/* Just give up. */
g_tree_destroy(model_names);
return;
}
for (DWORD processor_index = 0; processor_index < num_subkeys;
processor_index++) {
/*
* The documentation says that this is "in characters"; I'm
* assuming, for now, that they mean "Unicode characters",
* meaning "2-octet items".
*/
DWORD subkey_bufsize = max_subkey_len;
if (RegEnumKeyExW(processors_key, processor_index, subkey_buf,
&subkey_bufsize, NULL, NULL, NULL,
NULL) != ERROR_SUCCESS) {
/* Just exit the loop. */
break;
}
/*
* Get the length of processor name string for this processor.
*
* That's the "ProcessorNameString" value for the subkey of
* processors_key with the name in subkey_buf.
*
* It's a string, so only allow REG_SZ values.
*/
DWORD model_name_bufsize;
model_name_bufsize = 0;
if (RegGetValueW(processors_key, subkey_buf, L"ProcessorNameString",
RRF_RT_REG_SZ, NULL, NULL,
&model_name_bufsize) != ERROR_SUCCESS) {
/* Just exit the loop. */
break;
}
/*
* Allocate a buffer for the string, as UTF-16.
* The retrieved length includes the terminating '\0'.
*/
wchar_t *model_name_wchar = g_malloc(model_name_bufsize);
if (RegGetValueW(processors_key, subkey_buf, L"ProcessorNameString",
RRF_RT_REG_SZ, NULL, model_name_wchar,
&model_name_bufsize) != ERROR_SUCCESS) {
/* Just exit the loop. */
g_free(model_name_wchar);
break;
}
/* Convert it to UTF-8. */
char *model_name = g_utf16_to_utf8(model_name_wchar, -1, NULL, NULL, NULL);
g_free(model_name_wchar);
/*
* Add an entry to the tree with the model name as key and
* a null value. There will only be one such entry in the
* tree; if there's already such an entry, it will be left
* alone, and model_name will be freed, otherwise a new
* node will be created using model_name as the key.
*
* Thus, we don't free model_name; either it will be freed
* for us, or it will be used in the tree and freed when we
* free the tree.
*/
g_tree_insert(model_names, model_name, NULL);
}
g_free(subkey_buf);
/*
* Close the registry key.
*/
RegCloseKey(processors_key);
#elif defined(HAVE_SYSCTL)
/*
* Fetch the string using the appropriate sysctl.
*/
size_t model_name_len;
char *model_name;
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
/*
* Thanks, OpenBSD guys, for not having APIs to map MIB names to
* MIB values! Just consruct the MIB entry directly.
*
* We also do that for FreeBSD and DragonFly BSD, because we can.
*
* FreeBSD appears to support this for x86, PowerPC/Power ISA, and
* Arm. OpenBSD appears to support this for a number of
* architectures. DragonFly BSD appears to support it only for
* x86, but I think they only run on x86-64 now, and may never
* have run on anything non-x86.
*/
int mib[2] = { CTL_HW, HW_MODEL };
size_t miblen = 2;
#else
/* These require a lookup, as they don't have #defines. */
#if defined(__APPLE__) /* Darwin */
/*
* The code seems to support this on both x86 and ARM.
*/
#define BRAND_STRING_SYSCTL "machdep.cpu.brand_string"
#define MIB_DEPTH 3
#elif defined(__NetBSD__)
/*
* XXX - the "highly portable Unix-like Open Source operating
* system" that "is available for a wide range of platforms"
* doesn't seem to support this except on x86, and doesn't
* seem to support any other MIB for, for example, ARM64.
*
* Maybe someday, so use it anyway.
*/
#define BRAND_STRING_SYSCTL "machdep.cpu_brand"
#define MIB_DEPTH 2
#endif
int mib[MIB_DEPTH];
size_t miblen = MIB_DEPTH;
/* Look up the sysctl name and get the MIB. */
if (sysctlnametomib(BRAND_STRING_SYSCTL, mib, &miblen) == -1) {
/*
* Either there's no such string or something else went wrong.
* Just give up.
*/
g_tree_destroy(model_names);
return;
}
#endif
if (sysctl(mib, (u_int)miblen, NULL, &model_name_len, NULL, 0) == -1) {
/*
* Either there's no such string or something else went wrong.
* Just give up.
*/
g_tree_destroy(model_names);
return;
}
model_name = g_malloc(model_name_len);
if (sysctl(mib, (u_int)miblen, model_name, &model_name_len, NULL, 0) == -1) {
/*
* Either there's no such string or something else went wrong.
* Just give up.
*/
g_free(model_name);
g_tree_destroy(model_names);
return;
}
g_tree_insert(model_names, model_name, NULL);
#elif defined(HAVE_SYSINFO) && defined(SI_CPUBRAND)
/*
* Solaris. Use sysinfo() with SI_CPUBRAND; the documentation
* indicates that it works on SPARC as well as x86.
*
* Unfortunately, SI_CPUBRAND seems to be a recent addition, so
* older versions of Solaris - dating back to some versions of
* 11.3 - don't have it.
*/
int model_name_len;
char *model_name;
/* How big is the model name? */
model_name_len = sysinfo(SI_CPUBRAND, NULL, 0);
if (model_name_len == -1) {
g_tree_destroy(model_names);
return;
}
model_name = g_malloc(model_name_len);
if (sysinfo(SI_CPUBRAND, model_name, model_name_len) == -1) {
g_tree_destroy(model_names);
return;
}
g_tree_insert(model_names, model_name, NULL);
#else
/*
* OS for which we don't support the "get the CPU type" call; we
* use ws_cpuid(), which uses CPUID on x86 and doesn't get any
* information for other instruction sets.
*/
guint32 CPUInfo[4];
char CPUBrandString[0x40];
unsigned nExIds;
/*
* Calling ws_cpuid with 0x80000000 as the selector argument, i.e.
* executing a cpuid instruction with EAX equal to 0x80000000 and
* ECX equal to 0, gets the number of valid extended IDs.
*/
if (!ws_cpuid(CPUInfo, 0x80000000)) {
g_tree_destroy(model_names);
return;
}
nExIds = CPUInfo[0];
if (nExIds<0x80000005) {
g_tree_destroy(model_names);
return;
}
memset(CPUBrandString, 0, sizeof(CPUBrandString));
/* Interpret CPU brand string */
ws_cpuid(CPUInfo, 0x80000002);
memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo));
ws_cpuid(CPUInfo, 0x80000003);
memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo));
ws_cpuid(CPUInfo, 0x80000004);
memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo));
model_name = g_strdup(g_strstrip(CPUBrandString));
g_tree_insert(model_names, model_name, NULL);
#endif
gint num_model_names = g_tree_nnodes(model_names);
if (num_model_names > 0) {
/*
* We have at least one model name, so add the name(s) to
* the string.
*
* If the string is not empty, separate the name(s) from
* what precedes it.
*/
if (str->len > 0)
g_string_append(str, ", with ");
if (num_model_names > 1) {
/*
* There's more than one, so put the list inside curly
* brackets.
*/
g_string_append(str, "{ ");
}
/* Iterate over the tree, adding model names to the string. */
struct string_info info;
info.str = str;
info.sep = NULL;
g_tree_foreach(model_names, add_model_name_to_string, &info);
if (num_model_names > 1) {
/*
* There's more than one, so put the list inside curly
* brackets.
*/
g_string_append(str, " }");
}
}
/* We're done; get rid of the tree. */
g_tree_destroy(model_names);
/*
* We do this on all OSes and instruction sets, so that we don't
* have to figure out how to dredge the "do we have SSE 4.2?"
* information from whatever source provides it in the OS on
* x86 processors. We already have ws_cpuid_sse42() (which we
* use to determine whether to use SSE 4.2 code to scan buffers
* for strings), so use that; it always returns "false" on non-x86
* processors.
*
* If you have multiple CPUs, some of which support it and some
* of which don't, I'm not sure we can guarantee that buffer
* scanning will work if, for example, the scanning code gets
* preempted while running on an SSE-4.2-capable CPU and, when
* it gets rescheduled, gets rescheduled on a non-SSE-4.2-capable
* CPU and tries to continue the SSE 4.2-based scan. So we don't
* worry about that case; constructing a CPU string is the *least*
* of our worries in that case.
*/
if (ws_cpuid_sse42())
g_string_append(str, " (with SSE4.2)");
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/