1 of 17

Trap Entry Internals

Syscall Entry · Page-Fault Entry · sscratch

ECE 391 Discussion

RISC-V

GDB

QEMU

2 of 17

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

3 of 17

01

Syscall Entry

From user ecall to kernel handler

4 of 17

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

5 of 17

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

6 of 17

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

7 of 17

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!

8 of 17

02

sscratch

Distinguishing U-mode from S-mode traps in one instruction

9 of 17

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

10 of 17

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.

11 of 17

03

Page-Fault Entry

Lazy allocation — deliberate, not accidental

12 of 17

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);

13 of 17

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.

14 of 17

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!

15 of 17

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

16 of 17

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

17 of 17

Key Takeaways

Syscall entry

  • ecall is a deliberate exception — syscall number in a7
  • trap entry saves all user registers into struct trap_frame
  • kernel identifies syscall via scause == 8
  • advance sepc by +4 before returning to avoid infinite loop

sscratch

  • sscratch = 0 → trap from S-mode
  • sscratch ≠ 0 → trap from U-mode (holds kernel re-entry ptr)
  • one csrrw distinguishes the two cases instantly

Page-fault entry

  • same trap path — scause tells kernel 12 / 13 / 15
  • stval gives the faulting virtual address
  • return 1 to retry; return 0 to kill process
  • sepc stays the same when retrying the faulting instruction