summaryrefslogtreecommitdiffstats
path: root/wsutil/cpu_info.c
blob: 8b06b0705447a0f2a1bc221478031e18f0090d5b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
/* 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 <wsutil/cpu_info.h>

#include <string.h>

#include <wsutil/ws_cpuid.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 int
compare_model_names(gconstpointer a, gconstpointer b, void * 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(void * key, void * value _U_,
                         void * 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 = g_new(wchar_t, max_subkey_len);
    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.
     */
    uint32_t CPUInfo[4];
    char CPUBrandString[0x40];
    char *model_name;
    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

    int 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:
 */