How CPUs Process Instructions

DAMIAN ROBINSONDAMIAN ROBINSON
6 min read

I’m diving into the realm of Assembly Programming in my spare time, because I’ve had an interest in truly low-level code for a while, now. I’d like to develop a deeper understand of how all this tech around me operates at the base level, and I hear it really helps with cybersecurity, too.

Here’s a simple example of assembly code for the x86 architecture to demonstrate how a CPU processes instructions. I'll include comments to explain each step in detail. This example will show a few basic instructions, including loading data into registers, performing arithmetic, and storing the result.

Assembly Code (x86, NASM syntax)

section .data                  ; Data section: where variables are stored
    num1 db 5                  ; Define a byte variable 'num1' with value 5
    num2 db 10                 ; Define a byte variable 'num2' with value 10

section .bss                   ; Uninitialized data section (not needed for this example)

section .text                  ; Code section: where executable code is written
    global _start              ; Entry point for the program (needed for Linux systems)

_start:
    ; Step 1: Load values into registers
    mov al, [num1]             ; Load the value of 'num1' (5) into the AL register (8-bit register)
    mov bl, [num2]             ; Load the value of 'num2' (10) into the BL register (8-bit register)

    ; Step 2: Perform an addition (CPU processes the instruction)
    add al, bl                 ; Add the value in BL to the value in AL. AL = AL + BL (5 + 10)
                                ; After this instruction, AL = 15 (in decimal)

    ; Step 3: Store the result (this is not necessary in real programs but shows how to work with memory)
    mov [num1], al             ; Store the result (15) back into 'num1'

    ; Step 4: Exit the program (Linux system call to exit)
    mov eax, 1                 ; The syscall number for exit is 1 in Linux
    xor ebx, ebx               ; Set exit status to 0 (no errors)
    int 0x80                   ; Interrupt to call kernel (executes the system call)

Explanation

  1. Loading values into registers:

    • The mov instructions load the values from memory (variables num1 and num2) into the CPU’s registers (AL and BL). Registers are very fast storage locations inside the CPU, allowing it to quickly manipulate values.
  2. Executing an addition:

    • The add instruction tells the CPU to perform arithmetic. It adds the value in the bl register (10) to the value in the al register (5). After this operation, the result (15) is stored in the al register. The CPU handles this by fetching the values from the registers, performing the addition, and updating the result.
  3. Storing the result:

    • The result of the addition (15) is moved back into the num1 variable in memory. This demonstrates how data flows from registers to memory.
  4. Exiting the program:

    • The program ends with an exit system call, which is a process managed by the CPU's instruction execution. The mov instructions set up the system call, and the int 0x80 instruction triggers it, signaling the CPU to switch from user-mode execution to kernel-mode to exit the program.

How the CPU Processes Instructions

  • Fetch: The CPU fetches the instruction from memory.

  • Decode: The CPU decodes the instruction to understand what operation to perform (e.g., mov, add).

  • Execute: The CPU performs the operation (like loading values into registers, adding values, or storing the result).

  • Write-back: The CPU writes the result to the specified location (register or memory).

Certainly! Here’s the equivalent assembly code for ARM architecture. The logic will be the same, but the syntax and instructions differ since ARM and x86 have different instruction sets.

ARM Assembly Code

    .data                        // Data section: where variables are stored
num1:   .byte 5                  // Define a byte variable 'num1' with value 5
num2:   .byte 10                 // Define a byte variable 'num2' with value 10

    .text                        // Code section: where executable code is written
    .global _start               // Entry point for the program

_start:
    // Step 1: Load values into registers
    ldrb r0, =num1               // Load the address of 'num1' into register r0
    ldrb r1, [r0]                // Load the value at the address in r0 (value of 'num1', which is 5) into register r1

    ldrb r0, =num2               // Load the address of 'num2' into register r0
    ldrb r2, [r0]                // Load the value at the address in r0 (value of 'num2', which is 10) into register r2

    // Step 2: Perform an addition (CPU processes the instruction)
    add r1, r1, r2               // Add the value in r2 (10) to the value in r1 (5). r1 = r1 + r2 (5 + 10)
                                 // After this instruction, r1 = 15

    // Step 3: Store the result (this is not necessary in real programs but shows how to work with memory)
    ldrb r0, =num1               // Load the address of 'num1' again into r0
    strb r1, [r0]                // Store the result (15) from r1 back into 'num1'

    // Step 4: Exit the program (using a system call)
    mov r7, #1                   // Load the syscall number for exit (1) into register r7
    mov r0, #0                   // Load the exit status (0) into register r0 (indicates no errors)
    swi 0                        // Software interrupt to invoke the syscall (exit the program)

Explanation

  1. Loading values into registers:

    • ldrb r0, =num1 loads the address of num1 into the register r0.

    • ldrb r1, [r0] loads the value at the address in r0 (the value of num1) into the r1 register. The value 5 from num1 is now in r1.

  2. Executing an addition:

    • The instruction add r1, r1, r2 tells the CPU to add the value in r2 (which is 10, from num2) to the value in r1 (which is 5, from num1). After the addition, r1 will hold the value 15.
  3. Storing the result:

    • strb r1, [r0] stores the result (15) from r1 back into the num1 memory location.
  4. Exiting the program:

    • The program ends with a software interrupt (swi 0), which invokes the system call to exit the program. Registers r7 and r0 are used to specify the system call number (1 for exit) and the exit status (0).

How ARM CPU Processes Instructions

  • Fetch: The ARM CPU fetches the instructions from memory, one by one.

  • Decode: The CPU decodes the instructions (like ldrb, add, strb, swi) to understand what operations to perform.

  • Execute: The CPU executes the operations. It loads values into registers, performs arithmetic, and stores results back to memory.

  • Write-back: The result is stored back into memory, and the program ends with a system call.

Conclusion

As shown, we have x86 and ARM CPU architectures, and ARM assembly code demonstrates similar operations as the x86 example, but in the ARM architecture's instruction set. The main idea is the same: understanding how the CPU processes instructions involves loading data, performing operations on it, and storing the result.

That’s it for this post. I think this granular approach will help me greatly in my later programming endeavors. If you find it helpful, feel free to drop a like or comment. I hope to post more in the coming weeks and months as I start learning Assembly. 🤓

0
Subscribe to my newsletter

Read articles from DAMIAN ROBINSON directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

DAMIAN ROBINSON
DAMIAN ROBINSON