espressif / openocd-esp32

OpenOCD branch with ESP32 JTAG support
Other
362 stars 132 forks source link

How can I debug the LP Core of ESP32-C6? (OCD-949) #329

Closed andylinpersonal closed 3 months ago

andylinpersonal commented 5 months ago

How can I debug ESP32-C6's LP Core with openocd and gdb? Current TRM doesn't mention anything other than the basic debug and trigger CSRs. Where can I start to work with it? Thanks.

erhankur commented 5 months ago

@andylinpersonal We plan to add debugging support for the LP core in the next release, likely in July.

erhankur commented 3 months ago

@andylinpersonal You can give it a try with the latest OpenOCD release

Please follow the instructions below.

Debugging ULP LP-Core Applications with GDB and OpenOCD
-------------------------------------------------------

Run OpenOCD with special config file for LP core debugging support. And then run GDB with special ``gdbinit`` file.
openocd -f board/esp32c6-lpcore-builtin.cfg
riscv32-esp-elf-gdb -x gdbinit <path to main program ELF

`gdbinit` file contents with inline comments is below.
# connect to target
target extended-remote :3333
# reset chip
mon reset halt
maintenance flush register-cache
# add symbols and debugging info for ULP program
add-symbol <path to ULP program ELF>
# temporary HW breakpoint to setup breakpoints 
# if you need more than HW supports
thb main
commands
# set breakpoints here
# At this moment ULP program is loaded into RAM and when there are 
# no free HW breakpoints slots available GDB will set SW ones
b func1
b func2
b func3
# resume execution
c
end
# start main program after reset
c
LP Core Debugging Specifics
^^^^^^^^^^^^^^^^^^^^^^^^^^^

#. For convenient debugging you may need to add `-O0` compile option for ULP app in its CMakeLists.txt. See :example:`system/ulp/lp_core/build_system/` how to do this.
#. 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. See :example:`system/ulp/lp_core/build_system/` how to do this.
#. To be able to debug program running on LP core debug info and symbols need to be loaded to GDB. It can be done via GDB command line or in ``gdbinit`` file. See section above.
#. 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.
#. When using IDEs it can be that it does not support breakpoint actions/commands configuration shown in ``gdbinit`` above, so in this case you have to preset all breakpoints before debug session start and disable all of them except for ``main``. When program is stopped at ``main`` manually enable remaining breakpoints and resume execution.

Limitations
^^^^^^^^^^^

#. Currently debugging is not supported when either HP or LP core enters any sleep mode. So it limits available debugging scenarios.
#. 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 several 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
#. 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).
#. 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.
#. Since main and ULP programs are linked as separate binaries ir is possible for them to have global symbols (functions, variables) with the same name. When you set breakpoint for such a functions using its name GDB will set breakpoints for all of them. It could lead to the problems when one of the function is located in the flash because currently flash support is disabled in OpenOCD when debugging LP core. In that case you can use source line or address based breakpoints.
andylinpersonal commented 3 months ago

Thanks, the above instructions helped me a lot! It even works with cortex-debug and vscode :-)

Hybrid stack straddling between LP and HP SRAM :)

image

andylinpersonal commented 3 months ago

Semihosting logging from LP Core is also possible :)

launch.json for cortex-debug

    {
      "name": "ESP32-C6 LP: Cortex-Debug Attach",
      "type": "cortex-debug",
      "request": "attach",
      "servertype": "openocd",
      "serverpath": "${env:HOME}/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240726/openocd-esp32/bin/openocd",
      "openOCDLaunchCommands": [
        "init",
        "halt",
        "log_output openocd.log",
        "esp32c6.lp.cpu arm semihosting enable",
        "esp32c6.lp.cpu arm semihosting_resexit enable",
      ],
      "toolchainPrefix": "riscv32-esp-elf",
      "armToolchainPath": "${env:HOME}/.espressif/tools/riscv32-esp-elf/esp-13.2.0_20230928/riscv32-esp-elf/bin",
      "configFiles": [
        "board/esp32c6-lpcore-builtin.cfg"
      ],
      "symbolFiles": [
        "/PATH/TO/LP_CORE.elf",
        "${workspaceFolder}/build/${command:espIdf.getProjectName}.elf",
      ],
      "breakAfterReset": true,
      "svdPath": "/PATH/TO/esp32c6-lp.svd",
      "objdumpPath": "${env:HOME}/.espressif/tools/riscv32-esp-elf/esp-13.2.0_20240530/riscv32-esp-elf/bin/riscv32-esp-elf-objdump",
      "gdbPath": "${env:HOME}/.espressif/tools/riscv32-esp-elf-gdb/14.2_20240403/riscv32-esp-elf-gdb/bin/riscv32-esp-elf-gdb",
      "cwd": "${workspaceFolder}",
      "executable": "/PATH/TO/LP_CORE.elf",
      "runToEntryPoint": "main",
      "numberOfProcessors": 2
    },

We'll need a copy of ${IDF_PATH}/components/vfs/openocd_semihosting.h. Dunno why the toolchain cannot find out the path to <sys/fnctl.h>, we'll also need a copy of it.

Finally, open_mode parameter of semihosting_open should be replaced with one of the following values:

// From https://github.com/espressif/newlib-esp32/blob/esp-4.3.0/libgloss/riscv/semihost-sys_open.c#L14

#define SEMIHOST_MODE_R     0
#define SEMIHOST_MODE_RPLUS 2
#define SEMIHOST_MODE_W     4
#define SEMIHOST_MODE_WPLUS 6
#define SEMIHOST_MODE_A     8
#define SEMIHOST_MODE_APLUS 10

Have fun!