Trap Entry Internals
Syscall Entry · Page-Fault Entry · sscratch
ECE 391 Discussion
RISC-V
GDB
QEMU
Today's Agenda
01
Syscall Entry
ecall → trap frame → dispatch
02
sscratch Convention
U-mode vs S-mode fast detection
03
Page-Fault Entry
Lazy allocation with real GDB traces
04
Live Commands
Run these yourself in GDB
01
Syscall Entry
From user ecall to kernel handler
SYSCALL ENTRY
What is ecall?
Deliberate exception
ecall raises a CPU exception on purpose — it's how user code requests kernel services.
Arguments in registers
Syscall number → a7
Arguments → a0 through a6
# release/usr/syscall.S�_open:� li a7, SYSCALL_OPEN� ecall� ret��_write:� li a7, SYSCALL_WRITE� ecall� ret
SYSCALL ENTRY
Boot: Pointing stvec at trap entry
# release/sys/start.s�la t0, _smode_trap_entry�csrw stvec, t0��# S-mode: sscratch must be 0�csrw sscratch, zero
user ecall
scause = 8, sepc = faulting PC
_smode_trap_entry
save trap frame
handle_umode_exception()
scause values
scause | Meaning |
8 | User ecall |
9 | S-mode ecall |
12 | Instruction page fault |
13 | Load page fault |
15 | Store page fault |
SYSCALL ENTRY
Saving the trap frame
// release/sys/trap.h�struct trap_frame {� long a0, a1, a2, a3, a4, a5, a6, a7;� long t0..t6;� long s1..s11;� void *ra, *sp, *gp, *tp;� long sstatus;� void *sepc;�};
# trap.s — saving user state�sd a7, A7(sp)�csrr t6, sepc�sd t6, SEPC(sp)
Key fields after save:
a0–a6
Syscall arguments
a7
Syscall number
sepc
User PC at trap time
sp
User stack pointer
sstatus
Privilege state flags
GDB TRACE
Real trace: handle_umode_exception (cause=8)
(gdb) b handle_umode_exception�(gdb) c��Breakpoint, handle_umode_exception� (cause=8, tfr=0x80077f00)��(gdb) p cause�$1 = 8 # user ecall��(gdb) p/x tfr->a7�$4 = 0xf # SYSCALL_OPEN (15)��(gdb) p/x tfr->a0�$2 = 0x2 # fd argument��(gdb) p/x tfr->sepc�$5 = 0xc0002f56 # ecall PC in user
cause == 8
User ecall confirmed
tfr->a7 == 0xf
SYSCALL_OPEN from scnum.h
tfr->sepc
Advance by +4 before sret!
02
sscratch
Distinguishing U-mode from S-mode traps in one instruction
sscratch
The two-state convention
sscratch = 0
S-mode running
Kernel is executing. No user re-entry context needed.
sscratch ≠ 0
U-mode running
Points to kernel trap-frame at base of kernel stack. Ready for re-entry.
_smode_trap_entry:� # Swap sp and sscratch� csrrw sp, sscratch, sp� beqz sp, smode_trap_entry_from_smode
GDB TRACE
sscratch swap — before and after
S-mode trap (sscratch = 0)
# BEFORE csrrw�sp = 0x80077fc0�sscratch = 0x0��# AFTER csrrw�sp = 0x0 → beqz taken!�sscratch = 0x80077fc0
U-mode trap (sscratch ≠ 0)
# BEFORE csrrw�sp = 0xfffffed0 # user sp�sscratch = 0x80077f00 # kernel��# AFTER csrrw�sp = 0x80077f00 # kernel!�sscratch = 0xfffffed0
One instruction does it all: csrrw sp, sscratch, sp
If sp becomes 0 → S-mode trap. If sp gets a valid kernel address → U-mode trap.
03
Page-Fault Entry
Lazy allocation — deliberate, not accidental
PAGE FAULT
Same trap machinery — different scause
Key CSRs for page faults:
scause
12 / 13 / 15
Instr / Load / Store page fault
stval
faulting address
Virtual address that caused the fault
sepc
faulting instruction
User PC — kernel can retry by NOT advancing
// release/sys/memory.h�// Returns 1 → fault handled, retry instruction�// Returns 0 → fatal, terminate process�extern int handle_umode_page_fault(� struct trap_frame *tfr, uintptr_t vma);
PAGE FAULT
The lazyheap test program
#define PAGE_SIZE 4096UL�#define LAZYHEAP_PAGES 4UL��void main(void) {� volatile char *buf =� malloc(LAZYHEAP_PAGES * PAGE_SIZE);�� for (i = 0; i < LAZYHEAP_PAGES; i++)� buf[i * PAGE_SIZE] = (char)i;� // ↑ triggers store page fault� _exit();
}
malloc() is lazy
Only bumps a heap pointer. No pages mapped yet.
First store triggers fault
Each page's first write → scause=15, stval=page addr
4 faults → 4 pages
Addresses 0xe0000000, 0xe0001000, 0xe0002000, 0xe0003000
Same sepc every time
The fault instruction is retried after each mapping.
GDB TRACE
Four consecutive page faults
(gdb) b handle_umode_page_fault�(gdb) c��# Fault 1�vma = 0xe0000000 sepc = 0xc0003064��# Fault 2�vma = 0xe0001000 sepc = 0xc0003064��# Fault 3�vma = 0xe0002000 sepc = 0xc0003064��# Fault 4�vma = 0xe0003000 sepc = 0xc0003064��# → Program exits normally ✓
What the trace tells us:
sepc is the same every time — same instruction retried
vma advances by exactly 0x1000 (one page) per fault
scause = 15 (store page fault) every time
Program recovers fully — lazy allocation working!
SUMMARY
The unified trap dispatch path
ecall OR page fault
_smode_trap_entry
save trap frame
inspect scause
scause == 8
handle_syscall()
scause == 12/13/15
handle_umode_page_fault()
syscall
page fault
LIVE COMMANDS
GDB command cheatsheet
Trap entry & sscratch
b _smode_trap_entry
c
info reg sp sscratch scause sepc
si
info reg sp sscratch
Syscall arrival
b handle_umode_exception
c
p cause
p/x tfr->a7
p/x tfr->sepc
Page fault / lazy alloc
b handle_umode_page_fault
c
bt
info reg scause sepc stval
p/x vma
Key Takeaways
Syscall entry
sscratch
Page-fault entry