| Debugging | |
| ========= | |
| CHERIoT does not currently have support for interactive debugging. | |
| For now, the debugging options are logging, assertions and tracing. | |
| Debug logging | |
| ------------- | |
| The SDK supports logging in C++ using [debug.hh](../sdk/include/debug.hh). | |
| To use this you should include something like the following in your code: | |
| ```C++ | |
| #include <debug.hh> | |
| constexpr bool DebugFoo = DEBUG_FOO; | |
| using Debug = ConditionalDebug<DebugFoo, "Foo">; | |
| ``` | |
| 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](../sdk/xmake.lua) 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: | |
| ```C++ | |
| uint32_t anInt = 42; | |
| const char *aString = "CHERIoT"; | |
| Debug::log("The value of anInt is {}. aString is {}.", anInt, aString); | |
| ``` | |
| will output: | |
| ``` | |
| Foo: The value of anInt is 0x2a. aString is CHERIoT. | |
| ``` | |
| 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](../sdk/include/magic_enum/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: | |
| ``` | |
| 0x800058e0 (v:1 0x800058e0-0x800058ec l:0xc o:0xb p: G RWcgm- -- ---) | |
| address tag base top length otype permissions | |
| ``` | |
| 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: | |
| - **G**lobal (GL) | |
| - **R**eadable (LD) | |
| - **W**riteable (SD) | |
| - Load / store **c**apability (MC) | |
| - Load **g**lobal (LG) | |
| - Load **m**utable (LM) | |
| - Store **l**ocal (SL) | |
| - E**X**ecutable (EX) | |
| - Execute with **a**ccess system registers (SR) | |
| - **S**eal (SE) | |
| - **U**nseal (US) | |
| - 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. | |
| ### Assertions / invariants | |
| The `ConditionalDebug` class also supports assertions and invariants, for example: | |
| ```C++ | |
| Debug::Assert(anInt == 42, "anInt was not 42, was {}.", anInt); | |
| ``` | |
| 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: | |
| ``` | |
| answer.cc:13 Assertion failure in check_answer | |
| anInt was not 42, was 0x36. | |
| ``` | |
| 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](ErrorHandling.md) 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. | |
| Tracing | |
| ------- | |
| 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: | |
| ``` | |
| mem[X,0x80000000] -> 0x4081 | |
| [0] [M]: 0x80000000 (0x4081) c.li ra, 0 | |
| x1 <- t:0 s:0 perms:0b0000000000000 type:0x00000000 offset:0x00000000 base:0x00000000 length:0x000000000 | |
| mem[X,0x80000002] -> 0x4101 | |
| [1] [M]: 0x80000002 (0x4101) c.li sp, 0 | |
| x2 <- t:0 s:0 perms:0b0000000000000 type:0x00000000 offset:0x00000000 base:0x00000000 length:0x000000000 | |
| ``` | |
| 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: | |
| ``` | |
| [0] [M]: 0x80000000 (0x4081) c.li ra, 0 | |
| [1] [M]: 0x80000002 (0x4101) c.li sp, 0 | |
| [2] [M]: 0x80000004 (0x4181) c.li gp, 0 | |
| [3] [M]: 0x80000006 (0x4501) c.li a0, 0 | |
| [4] [M]: 0x80000008 (0x2231) c.jal 268 | |
| [5] [M]: 0x80000114 (0x4201) c.li tp, 0 | |
| [6] [M]: 0x80000116 (0x4281) c.li t0, 0 | |
| ``` | |
| 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: | |
| ``` | |
| cheriot_sim --trace=instr --trace=reg -t terminal.txt <path to elf> >trace.txt | |
| ``` | |
| will run the given ELF file, putting the console output in `terminal.txt` and a trace with instructions and register writes in `trace.txt`. |