feat(ulp): Add LP core debugging support

This commit is contained in:
Alexey Gerenkov
2024-06-28 13:24:21 +03:00
parent 6bfa408c7b
commit 625c437412
17 changed files with 580 additions and 5 deletions
@@ -0,0 +1,7 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(lp_debugging_example)
@@ -0,0 +1,77 @@
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 |
| ----------------- | -------- | -------- | -------- |
# LP Core Debugging Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview
This example demonstrates how to debug application running on the LP core.
## How to use example
### Hardware Required
To run this example, you should have an ESP32-C6 based development board.
### Build and Flash
Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Debugging Session
1) Run OpenOCD `openocd -f board/esp32c6-lpcore-builtin.cfg`.
2) Run GDB `riscv32-esp-elf-gdb -x gdbinit build/lp_debugging_example.elf`
3) `gdbinit` file will tell GDB to load debug info and symbols and set a number of breakpoints.
4) Type `c` upon hitting every breakpoint.
5) Finally LP core application should stop in `abort()`.
### LP Core Debugging Specifics
1) Add `-O0` compile option for ULP app in its CMakeLists.txt.
```
target_compile_options(${ULP_APP_NAME} PRIVATE -O0)
```
2) LP core supports limited set of HW exceptions, so, for example, writing at address `0x0` will not cause a panic as it would be for the code running on HP core. This can be overcome to some extent by enabling undefined behavior sanitizer for LP core application, so `ubsan` can help to catch some errors. But note that it will increase code size significantly and it can happen that application won't fit into RTC RAM. To enable `ubsan` for ULP app add `-fsanitize=undefined -fno-sanitize=shift-base` compile option to its CMakeLists.txt.
```
target_compile_options(${ULP_APP_NAME} PRIVATE -fsanitize=undefined -fno-sanitize=shift-base)
```
3) To be able to debug program running on LP core debug info and symbols need to be loaded to GDB. So there is special GDB command in `gdbinit`:
```
add-symbol build/esp-idf/main/ulp_debug_example/ulp_debug_example.elf
```
4) Upon startup LP core application is loaded into RAM, so all SW breakpoints set before that moment will get overwritten. The best moment to set breakpoints for LP core application is to do this when LP core program reaches `main` function. So `gdbinit` file used in this example sets temporary HW breakpoint on `main` and then set a bunch of other breakpoints when it hit.
```
thb main
commands
b main
b do_crash
b do_things
b ulp_lp_core_delay_us
c
end
```
## Limitations
1) Currently debugging is not supported when either HP or LP core enters any sleep mode. So this limits debugging scenarios.
2) FreeRTOS support in OpenOCD is disabled when debugging LP core, so you won't be able to see tasks running in the system. Instead there will be two threads representing HP ('esp32c6.cpu0') and LP ('esp32c6.cpu1') cores:
```
(gdb) info thread
Id Target Id Frame
1 Thread 1 "esp32c6.cpu0" (Name: esp32c6.cpu0, state: debug-request) 0x40803772 in esp_cpu_wait_for_intr ()
at /home/user/projects/esp/esp-idf/components/esp_hw_support/cpu.c:64
* 2 Thread 2 "esp32c6.cpu1" (Name: esp32c6.cpu1, state: breakpoint) do_things (max=1000000000)
at /home/user/projects/esp/esp-idf/examples/system/ulp/lp_core/debugging/main/lp_core/main.c:21
```
3) When setting HW breakpoint in GDB it is set on both cores, so the number of available HW breakpoints is limited to the number of them supported by LP core (2 for ESP32-C6).
4) OpenOCD flash support is disabled. It does not matter for LP core application because it is run completely from RAM and GDB can use SW breakpoints for it. But if you want to set a breakpoint on function from flash used by the code running on HP core (e.g. `app_main`) you should request to set HW breakpoint explicitly via `hb`/`thb` GDB commands.
## Troubleshooting
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
@@ -0,0 +1,16 @@
set pagination off
target extended-remote :3333
mon reset halt
maintenance flush register-cache
add-symbol build/esp-idf/main/ulp_debug_example/ulp_debug_example.elf
thb main
commands
b do_crash
b do_things
b ulp_lp_core_delay_us
c
end
c
@@ -0,0 +1,8 @@
# Set usual component variables
set(app_sources "lp_debug_main.c")
idf_component_register(SRCS ${app_sources}
REQUIRES ulp
WHOLE_ARCHIVE)
ulp_add_project("ulp_debug_example" "${CMAKE_SOURCE_DIR}/main/ulp/")
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_sleep.h"
#include "esp_err.h"
#include "ulp_lp_core.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
extern const uint8_t ulp_build_debug_bin_start[] asm("_binary_ulp_debug_example_bin_start");
extern const uint8_t ulp_build_debug_bin_end[] asm("_binary_ulp_debug_example_bin_end");
static void lp_core_init(void)
{
/* Set LP core wakeup source as the HP CPU */
ulp_lp_core_cfg_t cfg = {
.wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU,
};
/* Load LP core firmware */
ESP_ERROR_CHECK(ulp_lp_core_load_binary(ulp_build_debug_bin_start, (ulp_build_debug_bin_end - ulp_build_debug_bin_start)));
/* Run LP core */
ESP_ERROR_CHECK(ulp_lp_core_run(&cfg));
printf("LP core loaded with firmware and running successfully\n");
}
void app_main(void)
{
printf("Initializing LP core...\n");
/* Load LP Core binary and start the coprocessor */
lp_core_init();
printf("Do some work on HP core...\n");
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
@@ -0,0 +1,31 @@
# This CMakelists.txt is included in the ULP project
# so we have access to the ULP target: ULP_APP_NAME
cmake_minimum_required(VERSION 3.16)
# Project/target name is passed from the main project to allow IDF to have a dependency on this target
# as well as embed the binary into the main app
project(${ULP_APP_NAME})
add_executable(${ULP_APP_NAME} main.c)
# Import the ULP project helper functions
include(IDFULPProject)
# Apply default compile options
ulp_apply_default_options(${ULP_APP_NAME})
# Apply default sources provided by the IDF ULP component
ulp_apply_default_sources(${ULP_APP_NAME})
# Add targets for building the binary, as well as the linkerscript which exports ULP shared variables to the main app
ulp_add_build_binary_targets(${ULP_APP_NAME})
# Set custom compile flags
# By default ULP sources are compiled with -Os which is set in toolchain file in IDF build system.
# These options will appear on command line after default ones effectively overriding them.
# Therefore '-Os' will be overridden with '-O0' for this example for convenient debugging.
target_compile_options(${ULP_APP_NAME} PRIVATE -O0 -fsanitize=undefined -fno-sanitize=shift-base)
@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_lp_core_utils.h"
void do_crash(void)
{
volatile int *p = (int *)0x0;
// if ubsan is enabled (-fsanitize=undefined) line below will cause ubsan check failure
// and finally app will be stopped in abort()
*p = 32;
// if ubsan is disabled app will be stopped in abort() call below
abort();
}
void do_things(int max)
{
while (1) {
for (int i = 0; i < max; i++) {
ulp_lp_core_delay_us(100000);
if (i > 0)
do_crash();
}
}
}
int main (void)
{
do_things(1000000000);
return 0;
}
@@ -0,0 +1,4 @@
# Enable LP Core
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_TYPE_LP_CORE=y
CONFIG_ULP_COPROC_RESERVE_MEM=14000