2
\$\begingroup\$

This outputs the manufacturer string associated with your CPU using the CPUID instruction. I wrote this up for a homework assignment, then found out that I didn't need to.

When run on my (virtual) machine, I get the following output:

EAX: 16
EBX: 756e6547
ECX: 6c65746e
EDX: 49656e69

GenuineIntel

What I'd like commented on:

  • I'm not super-proficient yet in bit manipulation. Originally, print_register had a for loop that iterated the mask directly, like

    for (int32_t mask = 0xFF;; mask <<= 8) {
    

    This worked until I realized that I needed to right-shift the result of the bitwise AND, and couldn't figure out how to get the bits shifted from the mask. I ended up going for a less efficient solution of iterating bit-counts and creating the mask each iteration. This feels like a naïve approach though.

  • Anything else notable. I'm starting to venture into increasingly unknown territory here, so anything would help.

Note that although inline asm seems to be considered bad practice to use in many cases, using it was the point of the exercise here. I know wrapper macros exist that would clean this up a bit.

#include <stdio.h>
#include <stdint.h>


void print_register(int32_t reg_value) {
    for (int shifted = 0; shifted < 0x20; shifted += 8) {
        int32_t mask = 0xFF << shifted;
        int32_t matched = (reg_value & mask) >> shifted;
        printf("%c", matched);
    }
}


int main() {
    int32_t out_eax = -1;
    int32_t out_ebx = -1;
    int32_t out_ecx = -1;
    int32_t out_edx = -1;

    int32_t leaf = 0;

    asm volatile ("cpuid"
                  :  "=a"(out_eax),  // 0
                     "=b"(out_ebx),
                     "=c"(out_ecx),
                     "=d"(out_edx)
                  :  "0"(leaf));

    printf("EAX: %x\nEBX: %x\nECX: %x\nEDX: %x\n\n",
           out_eax, out_ebx, out_ecx, out_edx);

    print_register(out_ebx);
    print_register(out_edx);
    print_register(out_ecx);

    printf("\n");
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Use uint32_t

You have to be careful with shift operations on signed integers. In particular, right shifts on signed integers have implementation-defined behaviour, so it is hard to write portable code for it. It might work out in this case, but it is better to treat the values as unsigned integers here.

Bit shifting

While you have written a correct function to print the individual bytes of a 32-bit integer, it is a bit overkill. You can write it like so:

void print_register(uint32_t value) {
    printf("%c%c%c%c",
           (int)value,
           (int)(value >> 8),
           (int)(value >> 16),
           (int)(value >> 24));
}

This uses the fact that when printing a character, the value will already by truncated to 8 bits as if you would have written value & 0xFF, (value >> 8) & 0xFF, and so on. The cast to int might be necessary because the %c conversion expects an int, but int32_t might have a different size than an int on some platforms (for example, on an Arduino with an AVR CPU, int will be 16 bits).

\$\endgroup\$
4
  • \$\begingroup\$ Thanks. Both points make sense. \$\endgroup\$ Commented Jun 1, 2020 at 18:51
  • \$\begingroup\$ Corner; printf("%c%c%c%c", value, value >> 8, value >> 16, value >> 24); relies on int/unsigned is 32+ bits. As 16-bit, code is UB. \$\endgroup\$
    – chux
    Commented Jun 2, 2020 at 0:49
  • \$\begingroup\$ @chux-ReinstateMonica: you mean the arguments should be (int)value, (int)(value >> 8), ...? \$\endgroup\$
    – G. Sliepen
    Commented Jun 2, 2020 at 6:35
  • \$\begingroup\$ ... or (int8_t) value, .... as those will re-promote to int and is closer to the concept of what code does here. \$\endgroup\$
    – chux
    Commented Jun 2, 2020 at 13:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.