diff --git a/examples/system/ulp/lp_core/debugging/README.md b/examples/system/ulp/lp_core/debugging/README.md index 585d4233e81..451c26ced61 100644 --- a/examples/system/ulp/lp_core/debugging/README.md +++ b/examples/system/ulp/lp_core/debugging/README.md @@ -13,7 +13,7 @@ This example demonstrates how to debug application running on the LP core. ### Hardware Required -To run this example, you should have an ESP32-C6 based development board. +To run this example, you should have an ESP based development board that integrates an LP core. ### Build and Flash @@ -49,10 +49,15 @@ add-symbol build/esp-idf/main/ulp_debug_example/ulp_debug_example.elf ``` thb main commands -b main b do_crash b do_things +commands +c +end b ulp_lp_core_delay_us +commands +c +end c end ``` @@ -69,8 +74,7 @@ end * 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. +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, ESP32-C5 or ESP32-P4). ## Troubleshooting diff --git a/examples/system/ulp/lp_core/debugging/gdbinit b/examples/system/ulp/lp_core/debugging/gdbinit index 72dc4035e7d..e01c6af9109 100644 --- a/examples/system/ulp/lp_core/debugging/gdbinit +++ b/examples/system/ulp/lp_core/debugging/gdbinit @@ -10,7 +10,13 @@ thb main commands b do_crash b do_things +commands +c +end b ulp_lp_core_delay_us +commands +c +end c end c diff --git a/examples/system/ulp/lp_core/debugging/main/ulp/CMakeLists.txt b/examples/system/ulp/lp_core/debugging/main/ulp/CMakeLists.txt index 5bfa37d6bd3..1d5cd60163f 100644 --- a/examples/system/ulp/lp_core/debugging/main/ulp/CMakeLists.txt +++ b/examples/system/ulp/lp_core/debugging/main/ulp/CMakeLists.txt @@ -29,3 +29,9 @@ ulp_add_build_binary_targets(${ULP_APP_NAME}) # 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) + +add_custom_command( + TARGET ${ULP_APP_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ + $/../../../gdbinit/$ +) diff --git a/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py b/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py new file mode 100644 index 00000000000..f61d7b961f4 --- /dev/null +++ b/examples/system/ulp/lp_core/debugging/pytest_lp_core_debugging.py @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import os.path +import time +import typing + +import pexpect.fdpexpect +import pytest +from pytest_embedded_idf import IdfDut +from pytest_embedded_idf.utils import idf_parametrize + +if typing.TYPE_CHECKING: + from conftest import OpenOCD + + +@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4'], indirect=['target']) +@pytest.mark.usb_serial_jtag +def test_lp_core_debugging(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + dut.expect('Do some work on HP core...') + + # Prepare gdbinit file + gdb_logfile = os.path.join(dut.logdir, 'gdb.txt') + gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit') + gdbinit = os.path.join(dut.logdir, 'gdbinit') + with open(gdbinit_orig) as f_r, open(gdbinit, 'w') as f_w: + for line in f_r: + if line.startswith('add-symbol'): + f_w.write(f'add-symbol {dut.app.binary_path}/gdbinit/ulp_debug_example.elf\n') + else: + f_w.write(line) + + # Launch with gdbinit and check abort function is called + with openocd_dut.run() as oocd, open(gdb_logfile, 'w') as gdb_log, pexpect.spawn( + f'idf.py -B {dut.app.binary_path} gdb --batch -x {gdbinit}', + timeout=60, + logfile=gdb_log, + encoding='utf-8', + codec_errors='ignore', + ) as p: + p.expect('hit Breakpoint 2, do_crash') + # dont intercept ebreak in abort + oocd.write(f'{dut.target}.lp.cpu configure -ebreak exception') + # let the panic handler finish + oocd.write('resume') + time.sleep(2) + + # Validate GDB logs + with open(gdb_logfile, encoding='utf-8') as fr: # pylint: disable=protected-access + gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno()) + gdb_pexpect_proc.expect('hit Temporary breakpoint 1, main') + gdb_pexpect_proc.expect('hit Breakpoint 3, do_things') + gdb_pexpect_proc.expect('hit Breakpoint 4, ulp_lp_core_delay_us') + gdb_pexpect_proc.expect('hit Breakpoint 2, do_crash')