Exception Fault handling

The code in Cortex/FaultCortex.fth provides debugging facilities for when a fault occurs that triggers an exception.

Fault handler framework

struct /AppFrame        \ -- len
Structure defining what is saved on the application's stack.

struct /ExData  \ -- len
A structure holding data saved on entry to the exception routine

/ExData buffer: ExData  \ -- addr
Holds additional data about the fault.

PROC FltEntry   \ --
This is the template code for Cortex-M3+ fault handlers. R0..R3 have already been saved by the hardware

PROC FltEntry   \ --
This is the template code for Cortex-M0/M1 fault handlers. R0..R3 have already been saved by the hardware

: FLT:          \ xt vec# -- ; -- isr
Creates a fault handler that runs the given Forth word. At run time the entry point of the ISR is returned. Use in the form:

  ' <action> <vec#> FLT: <actionISR>

The example below will define a handler for the HardFault exception that runs the Forth word HFhandler.

  ' HFhandler 3 FLT: HFentry

: .item         \ addr -- addr+4
Display a cell item at addr and increment addr.

: .items        \ addr n -- addr+4n
Display n items at addr and increment addr.

: AppItems      \ -- addr
The address of fault data saved on the application's stack.

: MainItems     \ -- addr
The address of fault data saved on SP_main.

: AppRSP        \ -- addr
The Forth return stack pointer at the fault.

: AppPSP        \ -- addr
The Forth data stack pointer at the fault for Cortex-M3+.

: AppPSP        \ -- addr
The Forth data stack pointer at the fault for Cortex-M3+.

: AppUP         \ -- addr
The Forth UP at the fault.

: AppTOS        \ -- x
The Forth TOS at the fault.

: AppPC \ -- x
The PC at the fault.

: .FltFrame     \ --
Display the CPU state pointed to by the data frame.

: name?         \ addr -- flag
Check to see if the supplied address is a valid NFA. This word is implementation dependent. A valid NFA for MPE embedded systems satisfies the following:

: ip>nfa        \ addr -- nfa
Attempt to move backwards from an address within a definition to the relevant NFA.

: check-aligned \ addr -- addr'
Check addr for cell alignment, report and correct if misaligned.

: ?Clip32       \ xsp xspTop -- xsp xspTop'
Clip the stack display to 32 items.

: .rsFault      \ --
Display the return stack of the fault, assuming the Forth RSP=R13 and RUP=R11.

: .psFault      \ --
Display the data stack indicated by the frame, assuming the Forth RSP=AppPSP and RUP=R11.

Default Fault handlers

The interrupt structure of the Cortex-M3 is such that the simplest way to display exception information without large code and RAM overheads is to use polled operation of the comms link without interrupts. By default, the fault handler only transmits, it does not use KEY. If your serial driver normally uses interrupt-driven transmit routines, you must provide the word +FaultConsole which switches it into polled operation. An example can be found in Cortex/Drivers/serLPC17xxqi.fth.

By default, there is no return from a fault handler, the system is reset using REBOOT. From experience, attempting to restart the system by executing the boot vector is not enough. Rebooting by triggering the watchdog always works.

: showFault     \ --
Generic fault handler.

Intepreting the crash dump

The example below comes from typing

 55 0 !

on the Forth console. The device is an NXP LPC2388 and address zero is Flash to which writes have not been permitted. There are only minor differences between the ARM example here and the fault display for Cortex-M devices.


55 0 !
DAbort exception at PC = 0000:1C64
=== Registers ===
CPSR = 6000:001F
R0  = 0000:0037 0000:1C60 0000:0021 0000:0021
R4  = 0000:0070 0005:FD8C 4000:0298 0000:000C
R8  = 0000:1C60 0000:0000 0000:0000 4000:FEE0
R12 = 4000:FED4 4000:FDCC 0000:4FAC 0000:1C6C

RSP = 4000:FDCC   R0 = 4000:FDE0
--- Return stack high ---
4000:FDDC 0000:51E0 QUIT
4000:FDD8 4000:FED0
4000:FDD4 0000:0000
4000:FDD0 4000:FD94
4000:FDCC 0000:3244 CATCH
--- Return stack low ---

PSP = 4000:FED4   S0 = 4000:FED4
--- Data stack high ---
--- Data stack low ---
rTOS/R10  0000:0000
Restarting system ...

The exception occurred in the instruction at $1C64. It was a data abort exception, which means that it was a data load or store at an invalid address.

Assuming that restart is to a Forth console, you can find out where the fault occurred if you have compiled the file Common\DebugTools.fth or Powernet\DebugTools.fth.

  $1C64 ip>nfa .name<Enter> !  ok

The return stack dump shows that CATCH was used, in turn called by QUIT, the text interpreter.

Further interpretation requires some knowledge of the use of the CPU registers.

Register usage

Cortex

For Cortex-M the following register usage is the default:


  r15         pc      program counter
  r14         link    link register; bit0=1=Thumb, usually set
  r13         rsp     return stack pointer
  r12         psp     data stack pointer
  r11         up      user area pointer
  r10         --
  r9          lp      locals pointer
  r8          --
  r7          tos     cached top of stack
  r0-r6       scratch

The VFX optimiser reserves R0 and R1 for internal operations. CODE definitions must use R10 as TOS with NOS pointed to by R12 as a full descending stack in ARM terminology. R0..R8 are free for use by CODE definitions and need not be preserved or restored. You should assume that any register can be affected by other words.

ARM32

On the ARM the following register usage is the default:


  r15         pc      program counter
  r14         link    link register
  r13         rsp     return stack pointer
  r12         psp     data stack pointer
  r11         up      user area pointer
  r10         tos     cached top of stack
  r9          lp      locals pointer
  r0-r8       scratch

The VFX optimiser reserves R0 and R1 for internal operations. CODE definitions must use R10 as TOS with NOS pointed to by R12 as a full descending stack in ARM terminology. R0..R8 are free for use by CODE definitions and need not be preserved or restored. You should assume that any register can be affected by other words.

Interpreting the registers

Using the ARM example above, we can learn more.


DAbort exception at PC = 0000:1C64
=== Registers ===
CPSR = 6000:001F
R0  = 0000:0037 0000:1C60 0000:0021 0000:0021
R4  = 0000:0070 0005:FD8C 4000:0298 0000:000C
R8  = 0000:1C60 0000:0000 0000:0000 4000:FEE0
R12 = 4000:FED4 4000:FDCC 0000:4FAC 0000:1C6C

The exception occurred in the instruction at $1C64. It was a data abort exception, which means that it was a data load or store at an invalid address.


TOS = R10 = 0000:0000
UP  = R11 = 4000:FEE0
PSP = R12 = 4000:FED4
RSP = R13 = 4000:FDCC

In general, UP > PSP > RSP. In this case that's good. TOS=0, which we would expect from the phrase:

  55 0 !

We now switch back to the cross compiler, which you did leave running, didn't you? Since we now know that $1C64 is in !, we can disassemble it.


dis !
!
( 0000:1C60 0100BCE8 ..<h )  ldmia r12 ! { r0 }
( 0000:1C64 00008AE5 ...e )  str r0, [ r10, # $00 ]
( 0000:1C68 0004BCE8 ..<h )  ldmia r12 ! { r10 }
( 0000:1C6C 0EF0A0E1 .p a )  mov PC, LR
16 bytes, 4 instructions.
 ok

From this, we can see that the offending instruction is

( 0000:1C64 00008AE5 ...e )  str r0, [ r10, # $00 ]

Since R10 is 0, we now know that it was attempting a write to 0, which is not permitted.

Provided that you keep words small, the register contents at the crash point, with the stack contents and the disassembly often provide enough information to reconstruct the state of stack on entry to the word.