TIL about the cpuid instruction
So I’ve been trying to learn Rust more deeply lately, and one thing I found that I really enjoyed was Philipp Oppermann’s blog series, “Writing an OS in Rust”. I enjoyed it so much that I sponsored his work on GitHub and so I get the monthly newsletter, This Month in Rust OSDev. This issue linked to a repo where someone is writing a hobby OS based off of the blog series. I thought this was super cool, so I took a look at the code.
The first thing I checked out was the bootloader code. This is the first
non-firmware code that gets run and is responsible for initializing the stack,
transitioning the processor, loading the kernel, and setting up the page table.
This code is written in x86 assembly and looked reasonably familiar, with mov
,
jmp
, push
, pull
instructions. Reading through a bit more, I came across
an instruction named cpuid
. This stuck out to me because it wasn’t a “simple”
operation like moving data into a register or jumping to a memory location.
Here’s what it looked like in context:
From the comments, it looks like it gets used to see if the processor can transition into “long mode”, which is essentially 64-bit mode. This got me curious if there was other information that we could get from the CPU itself. I checked out the Wikipedia page for it and became even more fascinated with all of the information that you can get out of your processor. There was even some example assembly code:
I copied this into cpuid.s
and found this Stack Overflow article about compiling
assembly code on MacOS (using gcc -o cpuid cpuid.s
). Unfortunately, there were
errors. The first error was an invalid alignment value
on the .align 32
instruction.
I figured that it wasn’t necessary, so I removed it and moved on to the next error.
That got me to 32-bit absolute addressing is not supported in 64-bit mode
on the
movq $s0,%rdi
instruction. After a fair bit of searching, I managed to come across
the lea
instruction, which loads the effective address of a static value using
relative addressing. Changing the movq
to leaq s0(%rip), %rdi
got it to compile
and work! Here’s the output from my laptop:
$ gcc -o cpuid cpuid.s
$ ./cpuid
CPUID: 0x0306d4
I added an extra call to get the “highest function number,” which is what the original bootloader code is using to see if the CPU can be transitioned from “real mode” (16-bit mode) into “long mode” (64-bit mode). Check out the full code and try it for yourself!