Skip to content

jprx/osdev-cheatsheet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

OSDev-Cheatsheet

A quick reference for the X86_64, AARCH64, and RISCV64 ISAs.

Registers

RISCV64 AARCH64 X86_64
General Purpose X1-X31 X0-X30 RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8-R15
Program Counter PC PC RIP
Return Address RA (X1) LR (X30) On Stack
Stack Pointer SP (X2) SP RSP
Frame Pointer FP (X8/ S0) FP (X29) RBP
Zero Register ZERO (X0) XZR / WZR --
Segmentation -- -- CS, SS, DS, ES, FS, GS

Calling Conventions

RISCV64 AARCH64 X86_64
Arguments A0-A7 (X10-X17) X0-X7 RDI, RSI, RDX, RCX, R8, R9
Return Value A0 (X10) X0 RAX
Callee-Saved S0-S11, SP (X2) X19-X30, SP RBX, RBP, R12-R15, RSP
Caller-Saved RA (X1), A0-A7, T0-T6 X0-X18 RAX, RCX, RDX, RSI, RDI, R8-R11

Processor Modes

RISCV64 AARCH64 X86_64
User Mode U-Mode EL0 Ring 3
Kernel Mode S-Mode EL1 Ring 0
Hypervisor Mode -- EL2 --
Machine / Firmware Mode M-Mode EL3 --

Interrupt Mode

RISCV64 AARCH64 X86_64
Interrupts Enabled? sstatus.sie, sie DAIF (I,F) RFLAGS.IF
Disable Interrupts csrw sie, zero msr daifset, 0xf cli
Restore Interrupts restore sie restore daif sti, if IF was set previously

Virtual Memory

RISCV64 AARCH64 X86_64
Page Table Pointer satp ttbr0_el1, ttbr1_el1 cr3
Paging Enabled satp.mode sctlr_el1.mmu cr0.pg

System Instructions

RISCV64 AARCH64 X86_64
System Call ecall svc #X syscall
Trap Return sret eret iret, sysret
Flush TLB sfence.vma zero, zero tlbi vmalle1 write to CR3

Exceptions / Interrupts / Syscalls (AKA "traps")

RISCV64 AARCH64 X86_64 (Exceptions / Interrupts) X86_64 (Syscalls)
Exception Handler Base stvec vbar_el1 Interrupt Descriptor Table (IDT) LSTAR
Saved PC sepc elr_el1 On stack (RIP) RCX
Saved Privilege Level sstatus.spp spsr_el1.M On stack (CS) STAR (assumed ring 3)
Saved Interrupt Mode sstatus.spie spsr_el1.{I,F} On stack (RFLAGS.IF) R11 (saved RFLAGS.IF)
Exception Cause scause esr_el1 On stack (sometimes) --
Saved SP -- -- On stack (SS:RSP) --
New SP -- -- tss.rsp0 (only on cpl 3 -> 0) --
Scratch Register sscratch -- -- gs

Function Calls

In general, calling a function (call):

  1. Saves the address of the instruction after the call somewhere (either a register or the stack). This is called the "return address".
  2. Sets the program counter register to the first address of whatever function we're calling.

And returning from a function (ret):

  1. Sets the program counter register to whatever return address we saved during the previous call. This undoes the call instruction, bringing us to the instruction right after the call.

X86_64

call:

  1. push next rip to stack
  2. rip <- function to call

ret:

  1. rip <- pop from stack

AARCH64

call (aka bl, "branch with link"):

  1. X30 / LR <- pc + 4
  2. pc <- function to call

ret:

  1. pc <- X30 / LR

RISCV64

call (aka jal, "jump and link"):

  1. X1 / RA <- pc + 4
  2. pc <- function to call

ret:

  1. pc <- X1 / RA

Privilege Transitions ("traps")

In general, when taking a trap into the kernel (via an exception, interrupt, or system call), the CPU will save:

  1. The previous privilege level, as we're switching into kernel mode and will need to remember what mode we used to be in.
  2. Whether interrupts were enabled, as we're likely going to disable them when entering the trap handler.
  3. What instruction we were running before the trap.

And when returning from a trap (or when starting a user process by running the "return from trap" instruction) the CPU will load:

  1. The new privilege level.
  2. Whether interrupts should be enabled.
  3. The address to jump to.

Each ISA puts these fields in different places but they all ultimately do the same thing.

X86_64

On syscall:

  1. rcx <- old rip
  2. r11 <- old rflags
  3. rip <- lstar
  4. rflags <- rflags & ~sfmask
  5. cs <- star.syscall_cs
  6. ss <- star.syscall_cs + 8

On sysret (64-bit):

  1. rip <- rcx
  2. rflags <- r11
  3. cs <- star.sysret_cs + 16
  4. ss <- star.sysret_cs + 8

On interrupt/ exception:

  1. rsp <- tss.rsp0, only if we were in ring 3 before (otherwise rsp unchanged)
  2. push ss
  3. push rsp
  4. push rflags
  5. push cs
  6. push rip
  7. (for some exceptions) push an error code
  8. rip <- idt[idx].offset
  9. cs <- idt[idx].selector

On iret (64-bit):

  1. pop rip
  2. pop cs
  3. pop rflags
  4. pop rsp
  5. pop ss

AARCH64

On system call (svc) / interrupt/ exception:

  1. elr_el1 <- old pc
  2. esr_el1 <- trap reason
  3. spsr_el1 <- old pstate
  4. pc <- vbar_el1 + offset, where offset is dependent on what kind of trap this is

On eret:

  1. pc <- elr_el1
  2. pstate <- spsr_el1 (sets privilege level (EL0 / EL1), interrupt mode (DAIF), among other things)

RISCV64

On system call (ecall) / interrupt/ exception:

  1. sepc <- old pc
  2. sstatus.spp <- old privilege mode
  3. sstatus.spie <- old sstatus.sie (interrupt enable for supervisor mode)
  4. sstatus.sie <- 0
  5. scause <- trap reason
  6. stval <- (for some exceptions) extra information about the trap
  7. privilege mode <- supervisor (0b01)
  8. pc <- stvec

On sret:

  1. pc <- sepc
  2. privilege mode <- sstatus.spp
  3. sstatus.sie <- sstatus.spie

Note that sstatus.sie is ignored in U mode and is treated as always 1.

Notes

  • The 32 bit forms of registers refer to the lower 32 bits of their 64 bit counterparts. For example, EAX refers to the lower 32 bits of RAX.
  • AARCH64 has multiple stack pointers, one for each exception level. Which one is being used is controlled by SPSel.
  • All RISCV64 registers have multiple names. All registers have a number (eg. X0-X31), but can also be referred to by a human-readable name (eg. A0 means argument 0, and is the same as X10).
  • We are using the UNIX-style System-V ABI for X86_64, not the Microsoft x64 convention.
  • sstatus.spie is the saved value of sstatus.sie, which only controls interrupt delivery in S mode. To control interrupt delivery in U mode, use the sie csr, which is not saved / restored during traps.
  • If your ISA saves the return address in a register (ARM and RISCV), when you are writing some assembly function, if you call any other functions you must save and restore the return address register first! As performing a function call will change the return address and you won't be able to return to your caller when your function is done.

References

About

A reference guide comparing register names and calling conventions across ISAs

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published