09. Exceptions and traps
What is «system event»?
- Occurs while program running
Has a cause
Interrupt: not mentioned by the program, caused by external source (device I/O, hardware malfunction etc.)
Exception: the program supposes certain kind of event at certain code position (like zero division or overflow)
Trap: the program initiates event itself (simulates event)
Requires processing: switching from normal execution flow to handler and back
(way to implement) Store $pc, then change in to predefined value (handler), and restore after processing
Can be asynchronous
- ⇒ non-atomic corruption
- ⇒ races
- …
Event type |
Code position |
Sure |
Source |
Name |
Unexpected situation during execution: zero division, illegal instruction, illegal address etc. |
Fixed |
No |
Internal |
exception |
Simulation of unexpected situation: «nothing happens, but we want to call a handler» |
Fixed |
Yes |
Internal |
trap |
Expected situation handled by operating system (calling a certain system subroutine outside program code) |
Fixed |
Yes |
Internal |
syscall |
Device request (like status change, I/O operation result, timer etc.) |
Random |
No |
External |
interrupt |
Fatal device malfunction (bad media, system failure etc.) |
Sometimes predictable |
No |
External |
interrupt |
Implementation:
Interrupt vectors (not in MIPS). When interrupt № K occurs, CPU picks actual handler address from jump table (jump table is a common technology for tasks like this)
Address
Content
Meaning
0x80000100
0x80000180
Interrupt № 0 handler address
0x80000104
0x800007ac
Interrupt № 1 handler address
0x80000108
0x800015b0
Interrupt № 2 handler address
…
…
…
0x80000120
0x80000e54
Interrupt № 8 handler address
Interrupt vectors doubles memory access and requires additional addiu
- In theory, requires double indirect addressing
Single handler (MIPS: kernel subroutine at 0x80000180)
- Needs more direct calculations to distinguish types of events
Hardware requirements:
Inhibit or restrict recurrent events (injecting event while already in handler)
- Set type of event in dedicated register
- Store return address (esp. when handling interrupt)
- (not in MIPS) upon entering handler, keep all registers (incl. flag register and other status ones), restore them before return
- Fix microcode / pipeline / and all that
MIPS: exceptions
Exceptions, events and traps are handled by control CPU CPU0.
MARS: only partial support:
Name |
Number |
Purpose |
BadVAddr |
8 |
Address caused exception (if exception is related to incorrect memory addressing) |
Status |
12 |
Flags bit scale (interrupt mask, permissions etc.) |
Cause |
13 |
Event type and delayed event info |
EPC |
14 |
Address of instruction caused the exception or being executed when interrupt occurred |
Instructions:
mfc0 Rdest, C0src — move from C0 register C0src to common register Rdest
mtc0 Rsrc C0des — move to C0
eret — return from exception
Exception handling:
- Set up bit 1 of C0 $12 Status register (EXception Level, EXL).
- Set up bits 2-6 of C0 $13 Cause to exception type
- Store current instruction address to C0 $14 EPC
- If invalid addressing took place, set C0 $8 BadVAddr to accused memory address
Jump to 0x8000180 (this address is fixed for MIPS32). The .ktext section (kernel code) is started from 0x8000000, so on real MIPS some instructions can be executed only from that part of memory.
Handler must call eret after processing. This jumps to address stored at $14 (EPC) and cleans EXL status bit in $12.
When handling exception it's good idea to add 4 to EPC, so exception won't occur again
Real MIPS hardware and accurate simulators always have something in kernel space, but MARS has nothing and handles all exceptions/syscalls by executing Java code.
The Status C0 register (slightly MARS-specific):
bits |
31-16 |
15-8 |
7-5 |
4 |
3-2 |
1 |
0 |
target |
unused |
Int. mask |
unused |
K/U |
unused |
Exception level |
Int enable |
- Interrupt mask — bit scale of enabled/disabled (1/0) interrupts. Disabled interrupts are ignored
- Kernel Mode / User Mode — indicating whether CPU in kernel or user mode (MARS: always kernel)
- Exception level — is set to 1 while handling exception, disables recurrence
- Interrupt enable — global interrupt handling enable/disable (1/0)
The Cause C0 register
bits |
31 |
30-16 |
15-8 |
7 |
6-2 |
1-0 |
target |
Br |
unused |
Pending interrupts |
unused |
Exception code |
unused |
- Br: 1 if
Pending interrupts: bit scale of interrupts just occurred. Being asynchronous, interrupts can occur simultaneously
- Exception code is set to the exception type
Exception handler
Exception types (partially in MARS):
- ADDRESS_EXCEPTION_LOAD (4)
- ADDRESS_EXCEPTION_STORE (5)
SYSCALL_EXCEPTION (8) (MARS : exception while handling syscall)),
BREAKPOINT_EXCEPTION (9) (MARS — simulate DIVIDE_BY_ZERO_EXCEPTION),
- RESERVED_INSTRUCTION_EXCEPTION (10),
- ARITHMETIC_OVERFLOW_EXCEPTION (12),
- TRAP_EXCEPTION ( 13),
- DIVIDE_BY_ZERO_EXCEPTION (15) (not in MARS),
- FLOATING_POINT_OVERFLOW (16),
- FLOATING_POINT_UNDERFLOW (17).
Registers: handler can use $k0 and $k1 registers only, and must keep all other registers intact. Nothing should be changed after eret.
It's common to use separate kernel stack, but there's no hardware support for it. There's no urgent need for stack, because exception handlers are non-recurrent
Simple example (debug it step by step):
1 .text
2 nop
3 lw $t0, ($zero) # illegal read from 0x00000000
4 li $v0 10
5 syscall
6
7 .ktext 0x80000180
8 mfc0 $k0 $14 # EPC keeps address of accused instruction
9 # See also BadVAddr
10 addi $k0 $k0,4 # Next instruction is at EPC+4
11 mtc0 $k0 $14 # Store that to EPС
12 eret # Continue normal execution
Our more thorough handler must keep all registers intact, except for $k0 and $k1.
1 .text
2 lui $t0 0x7fff
3 addi $t0 $t0 0xffff
4 addi $t0 $t0 0xffff # integer overflow
5 sw $t0 0x400 # bad addressing
6 divu $t0 $t0 $zero # zero division
7 teq $zero $zero # trap (exception simulation)
8 li $v0 10
9 syscall
10 .kdata
11 msg: .asciiz "Exception "
12 .ktext 0x80000180
13 move $k0 $v0 # keep $v0
14 move $k1 $a0 # keep $a0
15 la $a0 msg # print a message
16 li $v0 4
17 syscall
18 mfc0 $a0 $13 # take Cause
19 srl $a0 $a0 2 # shift to cause number
20 andi $a0 $a0 0x1f # separate it from other bits
21 li $v0 1 # print cause
22 syscall
23 li $a0 10
24 li $v0 11 # print '\n'
25 syscall
26
27 move $v0 $k0 # restore $v0
28 move $a0 $k1 # restore $a0
29
30 li $k0 0
31 mtc0 $k0 $13 # clean Cause
32 mfc0 $k0 $14 # take current instruction address from EPC
33 addi $k0 $k0,4 # calculate next instruction address
34 mtc0 $k0 $14 # store back to EPС
35 eret # let the program continue
Q: what common register we've corrupted anyway?
Note:
MARS break command and the corresponded exception are nothing to do with breakpoints. Breakpoints is operated «by hardware» — in MARS case by executing corresponded Java code.
IRL breakpoint exception is used by debuggers. The debuggers remembers the instruction to be breakpointed a writes break to it's place. And when break exception occures, deals it with the situation.
MARS has no «division by zero» exception and emulates it by break ☺
MIPS: traps
MIPS system event handling is fast an furious, so programmer may want to take advantage of it, developing new exceptions manually.
Trap is exception 13 and handles like all exceptions.
teq $t1,$t2 |
Trap if equal |
Trap if $t1 is equal to $t2 |
teqi $t1,-100 |
Trap if equal to immediate |
Trap if $t1 is equal to sign-extended 16 bit immediate |
tge $t1,$t2 |
Trap if greater or equal |
Trap if $t1 is greater than or equal to $t2 |
tgei $t1,-100 |
Trap if greater than or equal to immediate |
Trap if $t1 greater than or equal to sign-extended 16 bit immediate |
tgeiu $t1,-100 |
Trap if greater or equal to immediate unsigned |
Trap if $t1 greater than or equal to sign-extended 16 bit immediate, unsigned comparison |
tgeu $t1,$t2 |
Trap if greater or equal unsigned |
Trap if $t1 is greater than or equal to $t2 using unsigned comparision |
tlt $t1,$t2 |
Trap if less than |
Trap if $t1 less than $t2 |
tlti $t1,-100 |
Trap if less than immediate |
Trap if $t1 less than sign-extended 16-bit immediate |
tltiu $t1,-100 |
Trap if less than immediate unsigned |
Trap if $t1 less than sign-extended 16-bit immediate, unsigned comparison |
tltu $t1,$t2 |
Trap if less than unsigned |
Trap if $t1 less than $t2, unsigned comparison |
tne $t1,$t2 |
Trap if not equal |
Trap if $t1 is not equal to $t2 |
tnei $t1,-100 |
Trap if not equal to immediate |
Trap if $t1 is not equal to sign-extended 16 bit immediate |
Note these are atomic operations!
When trap is emitted, exception handler detects type 13 (trap) and can perform different actions based on EPC value (we know every trap address for sure).
R-type trap instruction can bear additional information at third register parameter field (dst). Handler can extract this data by reading and parsing word with the instruction, taken from address stored in EPC. But MARS assembler has no such feature.
Where to use:
- Assertion. If some unwanted/improbable situation evolves, do not deal with it, but pass to the higher level. To stop calculation is sometimes better than to continue it wit wrong data. This example illustrates infinite loop prevention by checking if increment is non-zero:
- Inject software exception in case there's no hardware one. This example traps on array bound violation:
H/W
EJudge: NoError 'No errors'
Write a program that inputs 10 integers, not taking in account failed inputs. When all 10 integers are read, the program outputs them.
zz 20 fwewefqwe .654 71 -124 0.1 82 6. 334423 -94 VII 7535 6 . - 17 8968
20 71 -124 82 334423 -94 7535 6 17 8968
EJudge: NotOval 'Not Oval'
Write a program that inputs two integers (paper color and ink color) and draws an oval on Mars «Bitmap Display» with following settings (note 4X4 pixel size:
To draw an oval you should scale a circle to fit screen rectangle. Look at the example code. After drawing an oval, the program prints all videomemory out in hexadecimal.
3377169 16763989
0x00338811 0x00338811 0x00338811 ... 0x00ffcc55 0x00ffcc55 0x00ffcc55 ... 0x00338811 0x00338811 0x00338811