diff --git a/core/.changelog.d/2380.added b/core/.changelog.d/2380.added new file mode 100644 index 000000000..e646a026f --- /dev/null +++ b/core/.changelog.d/2380.added @@ -0,0 +1 @@ +Documentation for embedded C+Rust debugging diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index f7e1318d5..1f6cdd4b5 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -26,16 +26,19 @@ crate-type = ["staticlib"] [profile.dev] panic = "abort" -split-debuginfo = "unpacked" +split-debuginfo = "off" +debug = 2 [profile.release] panic = "abort" opt-level = "z" lto = true codegen-units = 1 +split-debuginfo = "off" +debug = 2 [profile.test] -split-debuginfo = "unpacked" +split-debuginfo = "off" # Runtime dependencies diff --git a/docs/core/embed_debug/CLion_Rust_embedded_Trezor_01.png b/docs/core/embed_debug/CLion_Rust_embedded_Trezor_01.png new file mode 100644 index 000000000..8500a1931 Binary files /dev/null and b/docs/core/embed_debug/CLion_Rust_embedded_Trezor_01.png differ diff --git a/docs/core/embed_debug/CLion_Rust_embedded_Trezor_02.png b/docs/core/embed_debug/CLion_Rust_embedded_Trezor_02.png new file mode 100644 index 000000000..c6a531343 Binary files /dev/null and b/docs/core/embed_debug/CLion_Rust_embedded_Trezor_02.png differ diff --git a/docs/core/embed_debug/index.md b/docs/core/embed_debug/index.md new file mode 100644 index 000000000..b3674763b --- /dev/null +++ b/docs/core/embed_debug/index.md @@ -0,0 +1,211 @@ +# Embedded debug of firmware (C and Rust) + +Notes on how to get both C and Rust debugging working "nicely". + +## Building properly + +The #1 hassle in embedded debug is proper build because it is very easy to run out of flash space. +Size optimizations on the other hand go against comfort or usability of debug. + +Therefore it's usually hard to make a single profile or setting, but best way is to start with +is probably these build options: + + make PYOPT=0 BITCOIN_ONLY=1 V=1 VERBOSE=1 OPTIMIZE=-Og build_firmware + +Options mean: + + * `PYOPT=0` - enable debuglink and test + * `V=1 VERBOSE=1` - just more of a check to see it's building with options you want + * `BITCOIN_ONLY=1` - most of the time for C/Rust parts you don't need other coins and it saves + space on flash to be usable for other than `-Os` optimization + * `OPTIMIZE=-Og` - optimization of C better suited for debug, but it will be larger than default `-Os` + +Micropython has its own optimization setting, so if you need to step through its code as well, +set it separately in its build. + +Another way to save space in case build overflows flash is changing `-fstack-protector-all` to +`-fstack-protector-strong` or `-fstack-protector-explicit` temporarily for debugging in +`SConscript.firmware`. + +Debug info is enabled for C and Rust in the flags and profiles (stripped when generating the .bin +final image). + +## Putting it into debugger + +Once you have built and flashed the FW, configure debugger for remote debug. +General background into remote debug and instructions +for basic `arm-none-eabi-gdb` and VSCode are [listed here](https://docs.rust-embedded.org/debugonomicon/). + +Below are instructions for CLion with [Rust plugin](https://plugins.jetbrains.com/plugin/8182-rust/docs). + +So far CLion seems the most complete implementation for ARM embedded debug, but +these evolve quickly now. + +Though all debuggers will have some historic limitations (especially some watch expressions +and return values). + + +### Start OpenOCD/JLink GDB server in a terminal + +Depending on your SWD adapter, either (change speed up to 50000 depending on adapter) + + JLinkGDBServerCLExe -select USB -device STM32F427VI -endian little -if SWD -speed 4000 -LocalhostOnly + +or with openocd (best to use latest from git) + + openocd -f interface/stlink.cfg -f target/stm32f4x.cfg + +### Set up a debug configuration as remote debug + +Default port for "target remote" JLink GDB server is :2331, for openocd :3333 + +![Remote debug settings](CLion_Rust_embedded_Trezor_02.png) + +It should be also possible to use "Remote GDB Server" setting and let CLion execute +openocd or JLink GDB server. + +### Now you can see variables from both Rust and C, set breakpoints + +![Remote debug settings](CLion_Rust_embedded_Trezor_01.png) + +For pointers you can use memory view from variable's context menu. + +### Known limitations + +Rust support is still in progress, so expect bugs sometimes. + +Only way so far to get return value of function is to switch to GDB console and +use `finish` GDB command - unless you assign it to variable. GDB may not always show +it due to optimizations. + +Not all trait info is output into debug info, so you will have issue with watching +some expressions like [this issue](https://github.com/rust-lang/rust/issues/66482) or +[this one](https://github.com/rust-lang/rust/issues/33014). + +Try not to put breakpoints on macro calls, since they may internally expand to +too many addresses depending on inlining. This manifests when GDB will complain +suddenly you have too many HW breakpoints or when JLink starts using flash +breakpoints instead of just HW breakpoints. + +## Other ideas not thoroughly tested + +You can define custom optimization level by choosing the `-fxx` options for C compiler and +similar ones for Rust with `llvm-args` [that target LLVM passes](https://llvm.org/docs/Passes.html). +Note that these change with compiler versions, LLVM 13 has +[new pass manager](https://llvm.org/docs/NewPassManager.html#invoking-opt). + +The point would be to make a optimization level producing somewhat slower code, less inlining, +but better debug experience. + +Rust does not have equivalent of `-Og` level, this would be only way to make something similar. + +The idea is generally to take an existing optimization level and change/remove some options +that affect code size or optimize variables away, force them to stay in memory instead of +registers. To look at what is used in passes you can print them out with: + + llvm-as < /dev/null | opt -Oz -disable-output -debug-pass=Arguments + +The `-O0` level often generates too big code to fit in flash which is why this experiment +in customizing optimization level exists. + +## Additional notes on making CLion understand and parse code correctly + +**Note**: Creating a project in CLion doesn't seem necessary for running debug like described above. + +CLion remote debugger bindings will gather most information from debug info after +connecting to external debugger (JLink or openocd GDB server), but it may be handy +for general edit/completion/following definitions and so on. + +Since we don't keep a `CMakeLists.txt` for `core` because everyone is using different +editor/IDE, here is a trick for creating it so that CLion will parse code without having +to run the debugger with debug info. + +First, clone the repo and build both emulator and embedded code: + + make build_unix + make build_embed + +Now rename `Makefile` under `core` to something else, like `Makefile.orig`. Open the +`core` directory as new project in CLion. + +Open any .c file, e.g. `embed/firmware/main.c`. +At this point since CLion does not see `Makefile` or `CMakeLists.txt`, it will +[suggest creating CMakeLists for you based on existing files](https://www.jetbrains.com/help/clion/creating-new-project-from-scratch.html#example). + +Let it autogenerate one, then add following defines that are taken from build +(there are more that should be added, but this suffices for most code including micropython +stm32lib): + + add_definitions( + -DFF_FS_READONLY=0 + -DFF_FS_MINIMIZE=0 + -DFF_USE_STRFUNC=0 + -DFF_USE_FIND=0 + -DFF_USE_FASTSEEK=0 + -DFF_USE_EXPAND=0 + -DFF_USE_CHMOD=0 + -DFF_USE_LABEL=0 + -DFF_USE_FORWARD=0 + -DFF_USE_REPAIR=0 + -DFF_CODE_PAGE=437 + -DFF_USE_LFN=1 + -DFF_LFN_UNICODE=2 + -DFF_STRF_ENCODE=3 + -DFF_FS_RPATH=0 + -DFF_VOLUMES=1 + -DFF_STR_VOLUME_ID=0 + -DFF_MULTI_PARTITION=0 + -DFF_USE_TRIM=0 + -DFF_FS_NOFSINFO=0 + -DFF_FS_TINY=0 + -DFF_FS_EXFAT=0 + -DFF_FS_NORTC=1 + -DFF_FS_LOCK=0 + -DFF_FS_REENTRANT=0 + -DFF_USE_MKFS=1 + + -DSTM32_HAL_H= + + -DTREZOR_MODEL=T + -DTREZOR_MODEL_T=1 + -DSTM32F427xx + -DUSE_HAL_DRIVER + -DSTM32_HAL_H="" + -DAES_128 -DAES_192 + -DRAND_PLATFORM_INDEPENDENT + -DUSE_KECCAK=1 + -DUSE_ETHEREUM=1 + -DUSE_MONERO=1 + -DUSE_CARDANO=1 + -DUSE_NEM=1 + -DUSE_EOS=1 + -DSECP256K1_BUILD + -DUSE_ASM_ARM + -DUSE_NUM_NONE + -DUSE_FIELD_INV_BUILTIN + -DUSE_SCALAR_INV_BUILTIN + -DUSE_EXTERNAL_ASM + -DUSE_FIELD_10X26 + -DUSE_SCALAR_8X32 + -DUSE_ECMULT_STATIC_PRECOMPUTATION + -DUSE_EXTERNAL_DEFAULT_CALLBACKS + -DECMULT_WINDOW_SIZE=8 + -DENABLE_MODULE_GENERATOR + -DENABLE_MODULE_RANGEPROOF + -DENABLE_MODULE_RECOVERY + -DENABLE_MODULE_ECDH + -DTREZOR_FONT_BOLD_ENABLE + -DTREZOR_FONT_NORMAL_ENABLE + -DTREZOR_FONT_MONO_ENABLE + -DTREZOR_FONT_MONO_BOLD_ENABLE + ) + + include_directories(vendor/micropython) + include_directories(build/firmware/genhdr/) + include_directories(vendor/micropython/lib/stm32lib/STM32L4xx_HAL_Driver/Inc) + +Rename the `Makefile.orig` back to `Makefile`. This is clumsy, but AFAIK there is no +explicit option to autogenerate `CMakeLists.txt` otherwise. + +To make Rust code part of the project, right click `embed/rust/Cargo.toml` and +choose "Attach Cargo Project"