feat(ulp): add pytest for debugging example

This commit is contained in:
Samuel Obuch
2026-05-12 21:02:41 +02:00
parent 4cb453dd70
commit 6b91bec609
4 changed files with 73 additions and 4 deletions
@@ -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.lp.cpu" (Name: esp32c6.lp.cpu, state: breakpoint) do_things (max=1000000000)
at /home/user/projects/esp/esp-idf/examples/system/ulp/lp_core/debugging/main/ulp/main.c:22
```
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
@@ -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
@@ -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' can be overridden here with '-O0' for this example for convenient debugging.
target_compile_options(${ULP_APP_NAME} PRIVATE -fsanitize=undefined -fno-sanitize=shift-base)
add_custom_command(
TARGET ${ULP_APP_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:${ULP_APP_NAME}>
$<TARGET_FILE_DIR:${ULP_APP_NAME}>/../../../gdbinit/$<TARGET_FILE_NAME:${ULP_APP_NAME}>
)
@@ -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')