The code in Cortex/FaultCortex.fth provides debugging facilities for when a fault occurs that triggers an exception.
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.
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.
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.
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.
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.
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.