x86-64 TUTORIAL: CPUID


x86-64 TUTORIAL: CALLING CONVENTIONS  |  x86-64 TUTORIAL: HELLO WORLD!

The x86 and x86-64 instruction sets have an instruction called CPUID that tells the program who made the CPU and what features it may have. We try to get that info using x86-64 assembly in this tutorial.

When we set EAX to 0, and call the CPUID, instruction, the processor fills the ECX, EBX, EDX registers with parts of the string containing the name of the processor, e.g. AuthenticAMD or GenuineIntel. The PUSH instruction on the x86-64 instruction set pushes the 64-bit versions of the registers. We need to concatenate the strings in the 3 registers and print them on screen, using the write() system call.

So what we do is shift the lower 32-bit part of RDX (i.e. EDX) to the upper 32-bit part of RDX and move the lower 32-bit part of RBX (i.e. EBX) into the lower 32-bit part of RDX, thus placing the two of the parts of the string into the RDX register. The remaining part of the string is in the lower 32-bit part of RCX, which when gets pushed connects to the remaining string in the RDX register. Then we push both the registers on the stack, and then call the write() system call.

For doing a write() system call, we place the number of the system call as given in unistd.h into RAX and then place the arguments into RDI, RSI & RDX registers. RDI is the file descriptor where we want to write to. RSI holds the address of the string and RDX holds the length of the string. If the value of RDI is 0x1, the string obtained from the CPUID call will be printed on screen (stdout).

We then do an exit() system call to exit the program. This program does not use the C library and uses the kernel interface directly. Hence we do not have to define a main() function. We can directly use the _start symbol that the operating system uses to invoke every application. We have to declare it global so that it is noted as a symbol in the application’s final binary that has been created and can be called by the operating system.

section .text
    global _start

_start:
    xor eax,eax       ; place 0x0 in EAX for getting the name of the processor 
    cpuid
    shl  rdx,0x20     ; shifting lower 32-bits into upper 32-bit of RDX
    xor  rdx,rbx      ; moving EBX into EDX
    push rcx          ; push the string on the stack
    push rdx
    mov  rdx, 0x10    ; since we are pushing 2 registers, the length is not more than 16 bytes.
    mov  rsi, rsp     ; The address of the string is RSP because the string is on the stack
    push 0x1          ; The system call write() has the value 0x1 in the sytem call table
    pop  rax
    mov  rdi, rax     ; Since we are printing to stdout, the value of the file descriptor is also 0x1
    syscall           ; make the system call
    mov  rax, 0x3c    ; We now make the exit() system call here.
    xor  rdi, rdi     ; the argument is 0x0
    syscall           ; this exits the application and gives control back to the shell or the Operating system

Download cpuid.asm.

The command to compile the above code is as follows:

$ yasm -f elf64 cpuid.asm 
$ ld -o cpuid.out cpuid.o  

x86-64 TUTORIAL: CALLING CONVENTIONS  |  x86-64 TUTORIAL: HELLO WORLD!