|
CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
|
CHERIoT does not currently have support for interactive debugging. For now, the debugging options are logging, assertions and tracing.
The SDK supports logging in C++ using debug.hh. To use this you should include something like the following in your code:
Here DEBUG_FOO is a compile time macro that should be set to true or false to enable or disable debug output in the Foo module. "Foo" is a prefix that will be prepended to debug messages.
The SDK's xmake file provides support for defining debug macros of the form DEBUG_FOO. To use it call debugOption("foo") in your xmake.lua and add add_rules("cherimcu.component-debug") to your target definitions. You can then specify --debug-foo=true when running xmake config to build with debugging enabled.
Once you have defined an alias for ConditionalDebug as above you can then use Debug::log to output relevant debug messages, for example:
will output:
Note that {} in the template string is replaced by the formatted value of the variable passed in. The formatter supports some common types such as integers and strings. By default, it will attempt to use magic_enum.hpp to pretty-print enumerations. Pretty-printing enumerations can significantly increase firmware size and so can be disabled by defining CHERIOT_AVOID_CAPRELOCS, which will then fall back to printing their integer values. Pointers are displayed as capabilities using the following format:
The capability permissions are displayed as a string of the form G RWcgml Xa SU0, where a letter indicates that a permission is present and dash in the corresponding position indicates it is absent. The letters correspond to the permissions as follows:
User**0 (U0)
Note that lower case letters denote permissions that are 'dependent' on other permissions. For example, load / store **c**apabilities requires either **R**ead or **W**rite; load **g**lobal requires both **R**ead and load / store **c**apabilities.
The ConditionalDebug class also supports assertions and invariants, for example:
If debugging is enabled this will test whether anInt is equal to 42. If the condition is false a message will be printed with the line number of the assertion and the formatted message, for example:
After printing the message the assertion failure will trigger a trap using a reserved instruction. The effect of this trap will depend on the rest of the application, for example whether there is an error handler registered for the compartment. Take care not to write conditions that have side-effects (e.g. using the ++ operator to increment a variable) because these will execute differently depending on whether debugging is enabled.
Debug::Invariant is identical to Debug::Assert except that the condition is checked even if debugging is not enabled. It will cause a trap whenever the condition evaluates to false, but the message will be printed only if debugging is enabled.
For detailed debugging the Sail simulator supports instruction level tracing. This can be enabled using the -v option. By default it is extremely verbose, printing all memory accesses (including instruction fetch), instructions executed, and registers written, for example:
This slows down execution and makes it very difficult to see UART output, therefore tracing can be selectively enabled using --trace=instr|reg|mem|exception|platform|all. For example --trace=instr will output just the instruction count, PC, opcode and disassembly for each instruction:
It is possible to specify more than one --trace option to enable multiple kinds of trace output.
To see terminal output more easily when tracing you can redirect it to a file using the -t / --terminal-log option. For example, if using bash:
will run the given ELF file, putting the console output in terminal.txt and a trace with instructions and register writes in trace.txt.