Runtime Errors

Overview

Run-time errors are primarily handled by module RuntimeErrors.

There are two types of run-time problems that are caught and reported:

  • Errors: checks inserted by the Astrobe compiler, using the SVC system exception to signal a problem;
  • Faults: checks done by the MCU hardware, using the dedicated system exceptions to signal a problem.

Errors are always caught synchronously to the execution of the program, Faults can be synchronous or asynchronous. Errors include array index out of bounds, division by zero, Faults include BusFaults or UsageFaults.

The Astrobe compiler uses the SVC system exception to signal a run-time Error. The hardware uses the different corresponding Fault system exceptions available in the MCU. Module RuntimeErrors installs corresponding handlers at initialisation.

Together with the SVC instruction, the Astrobe compiler also inserts the corresponding line number of the source code, so the error messages can include a direct reference to the faulty line in the source text, together with the absolute code address in memory. For Fault exceptions, we only have the code address available to track down the exact location of the problem.

Separation of Error and Fault Handling and Output

Since a control program must be able to run unattended, that is, without any human operator present, possibly even without a terminal attached, RuntimeErrors does not do any error message printing. Rather, the Error and Fault handlers collect the information in a data structure, which they will pass on to a user-installable handler.

That handler can then log the error and its data, print it (or both), or even attempt to recover from the error, eg. by resetting the offending processor, or the MCU. Module RuntimeErrorsOut provides a compatible handler that prints out the error information for development and debugging purposes.

Also, printing error messages on a serial terminal takes a lot of time. If autonomous problem recovery is the primary goal, in order to keep the control program running, no time should be wasted with output.

Exception Data

The data collected by the Error and Fault handlers include:

  • the Error or Fault code;
  • the absolute code address in memory;
  • the stack address where the code address was collected from;
  • the source code line number, if available;
  • the data in the registers as stacked by the exception entry sequence;
  • the contents of some of the current registers; and
  • a stack trace, if so configured.

The registers’ data is always collected, while building the stack trace can be disabled; it’s enabled by default. If the register data is not relevant, the handler can ignore it.

The current registers are mostly relevant for programmers who want to change (or fix) the Error and Fault handling. This feature may be removed in the future.

We’re only collecting stacked register contents of the most basic eight 32-bit words stack frame.

With the RP2350, when the FPU is enabled and used, the stack frame contains an additional 18 word stack context for the lower half of the FPU registers. And when using both Secure and Non-secure code, there can be additional contexts. In total there are four different possible stack frame layouts. We neglect the Non-secure realm for now, so we only have to deal with two possible stack frame schemes.

Terminology

  • Each exception creates a stack frame.
  • Each stack frame contains one or more contexts:
    • state context: the standard 8x 32-bit words
    • additional state context: 10x 32 bit words, used for Non-secure exceptions
    • FP context: 18x 32-bit words, used for FPU registers
    • additional FP context: 16x 32-bit words, used for Non-secure exceptions

Core Separation

Since a run-time problem can occur in either core at the same time, RuntimeErrors maintains exception data structures for each core, and the program code is re-entrant (thread safe).