blob: ccc0854cb9690f25413d43ff9ac875535fe59b1f [file] [log] [blame] [view]
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`.