feature(gdbstub): Move runtime gdbstub out of panic config

Closes https://github.com/espressif/esp-idf/pull/11569
This commit is contained in:
Alexey Lapshin 2023-07-27 23:35:44 +04:00
parent cfa1896780
commit 47e400c296
11 changed files with 148 additions and 49 deletions

View File

@ -6,6 +6,17 @@ menu "GDB Stub"
bool
select FREERTOS_ENABLE_TASK_SNAPSHOT
config ESP_SYSTEM_GDBSTUB_RUNTIME
bool "GDBStub at runtime"
select ESP_GDBSTUB_ENABLED
help
Enable builtin GDBStub.
This allows to debug the target device using serial port:
- Run 'idf.py monitor'.
- Wait for the device to initialize.
- Press Ctrl+C to interrupt the execution and enter GDB attached to your device for debugging.
NOTE: all UART input will be handled by GDBStub.
config ESP_GDBSTUB_SUPPORT_TASKS
bool "Enable listing FreeRTOS tasks through GDB Stub"
depends on ESP_GDBSTUB_ENABLED

View File

@ -18,18 +18,21 @@ menu "ESP System Settings"
config ESP_SYSTEM_PANIC_PRINT_HALT
bool "Print registers and halt"
depends on !ESP_SYSTEM_GDBSTUB_RUNTIME
help
Outputs the relevant registers over the serial port and halt the
processor. Needs a manual reset to restart.
config ESP_SYSTEM_PANIC_PRINT_REBOOT
bool "Print registers and reboot"
depends on !ESP_SYSTEM_GDBSTUB_RUNTIME
help
Outputs the relevant registers over the serial port and immediately
reset the processor.
config ESP_SYSTEM_PANIC_SILENT_REBOOT
bool "Silent reboot"
depends on !ESP_SYSTEM_GDBSTUB_RUNTIME
help
Just resets the processor without outputting anything
@ -40,16 +43,6 @@ menu "ESP System Settings"
Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem
of the crash.
config ESP_SYSTEM_GDBSTUB_RUNTIME
bool "GDBStub at runtime"
select ESP_GDBSTUB_ENABLED
help
Enable GDBStub inclusion into firmware.
This allows to debug the target device using serial port:
- Run 'idf.py monitor'.
- Wait for the device to initialize.
- Press Ctrl+C to interrupt the execution and enter GDB attached to your device for debugging.
NOTE: all UART input will be handled by GDBStub.
endchoice
config ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS

View File

@ -65,10 +65,6 @@ Subsequent behavior of the panic handler can be set using :ref:`CONFIG_ESP_SYSTE
Start GDB server which can communicate with GDB over console UART port. This option will only provide read-only debugging or post-mortem debugging. See `GDB Stub`_ for more details.
- Invoke dynamic GDB Stub (``ESP_SYSTEM_GDBSTUB_RUNTIME``)
Start the GDB server which can communicate with GDB over the console UART port. This option allows the user to debug a program at run time and set breakpoints, alter the execution, etc. See `GDB Stub`_ for more details.
The behavior of the panic handler is affected by three other configuration options.
- If :ref:`CONFIG_ESP_DEBUG_OCDAWARE` is enabled (which is the default), the panic handler will detect whether a JTAG debugger is connected. If it is, execution will be halted and control will be passed to the debugger. In this case, registers and backtrace are not dumped to the console, and GDBStub / Core Dump functions are not used.

View File

@ -240,7 +240,7 @@ Launching GDB with GDBStub
GDBStub is a useful runtime debugging feature that runs on the target and connects to the host over the serial port to receive debugging commands. GDBStub supports commands such as reading memory and variables, examining call stack frames etc. Although GDBStub is less versatile than JTAG debugging, it does not require any special hardware (such as a JTAG to USB bridge) as communication is done entirely over the serial port.
A target can be configured to run GDBStub in the background by setting the :ref:`CONFIG_ESP_SYSTEM_PANIC` to ``GDBStub on runtime``. GDBStub will run in the background until a ``Ctrl+C`` message is sent over the serial port and causes the GDBStub to break (i.e., stop the execution of) the program, thus allowing GDBStub to handle debugging commands.
A target can be configured to run GDBStub in the background by setting the :ref:`CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME`. GDBStub will run in the background until a ``Ctrl+C`` message is sent over the serial port and causes the GDBStub to break (i.e., stop the execution of) the program, thus allowing GDBStub to handle debugging commands.
Furthermore, the panic handler can be configured to run GDBStub on a crash by setting the :ref:`CONFIG_ESP_SYSTEM_PANIC` to ``GDBStub on panic``. When a crash occurs, GDBStub will output a special string pattern over the serial port to indicate that it is running.

View File

@ -65,10 +65,6 @@
启动 GDB 服务器,通过控制台 UART 接口与 GDB 进行通信。该选项只提供只读调试或者事后调试,详细信息请参阅 `GDB Stub`_
- 调用动态 GDB Stub (``ESP_SYSTEM_GDBSTUB_RUNTIME``)
启动 GDB 服务器,通过控制台 UART 接口与 GDB 进行通信。该选项允许用户在程序运行时对其进行调试、设置断点和改变其执行方式等,详细信息请参阅 `GDB Stub`_
紧急处理程序的行为还受到另外两个配置项的影响:
- 如果使能了 :ref:`CONFIG_ESP_DEBUG_OCDAWARE` (默认),紧急处理程序会检测 {IDF_TARGET_NAME} 是否已经连接 JTAG 调试器。如果检测成功,程序会暂停运行,并将控制权交给调试器。在这种情况下,寄存器和回溯不会被打印到控制台,并且也不会使用 GDB Stub 和 Core Dump 的功能。

View File

@ -240,7 +240,7 @@ ROM ELF 文件会根据 ``IDF_PATH`` 和 ``ESP_ROM_ELF_DIR`` 环境变量的路
GDBStub 支持在运行时进行调试。GDBStub 在目标上运行并通过串口连接到主机从而接收调试命令。GDBStub 支持读取内存和变量、检查调用堆栈帧等命令。虽然没有 JTAG 调试通用,但由于 GDBStub 完全通过串行端口完成通信,故不需要使用特殊硬件(如 JTAG/USB 桥接器)。
通过:ref:`CONFIG_ESP_SYSTEM_PANIC` 设置为 ``GDBStub on runtime``,可以将目标配置为在后台运行 GDBStub。GDBStub 将保持在后台运行,直到通过串行端口发送 ``Ctrl+C`` 导致应用程序中断(即停止程序执行),从而让 GDBStub 处理调试命令。
通过设置 :ref:`CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME`,可以将目标配置为在后台运行 GDBStub。GDBStub 将保持在后台运行,直到通过串行端口发送 ``Ctrl+C`` 导致应用程序中断(即停止程序执行),从而让 GDBStub 处理调试命令。
此外,还可以通过设置 :ref:`CONFIG_ESP_SYSTEM_PANIC```GDBStub on panic`` 来配置 panic 处理程序,使其在发生 crash 事件时运行 GDBStub。当 crash 发生时GDBStub 将通过串口输出特殊的字符串模式,表示 GDBStub 正在运行。

View File

@ -26,11 +26,9 @@ idf.py menuconfig
```
Current example is pre-configured. The user can scroll through the system parameters and see the settings.
Most important one is:
-> Component Config -> ESP System Settings -> Panic handler behaviour -> GDBStub on runtime
-> Component Config -> GDB Stub -> GDBStub on runtime
This selection switches gdbstub to runtime mode.
Depending on the project, following settings could be used:
-> Component Config -> GDB Stub -> ...
The user can enable or disable task list handling and define a maximum amount of tasks.
Using another options in this menu, the user can also enable or disable task list handling and define a maximum amount of tasks.
### Build and Flash

View File

@ -1,18 +1,7 @@
#
# GDB Stub
#
CONFIG_ESP_GDBSTUB_ENABLED=y
CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME=y
CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y
CONFIG_ESP_GDBSTUB_MAX_TASKS=32
# end of GDB Stub
#
# ESP System Settings
#
# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set
# CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT is not set
# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set
CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME=y
# CONFIG_ESP_INT_WDT is not set
# CONFIG_ESP_TASK_WDT_EN is not set
# end of ESP System Settings

View File

@ -1,3 +1,5 @@
idf_component_register(SRCS "test_app_main.c"
INCLUDE_DIRS ""
REQUIRES esp_gdbstub)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-unused-label")

View File

@ -5,22 +5,37 @@
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
int var_1;
int var_2;
int do_panic;
void foo(void)
{
var_2++;
var_2+=2;
var_2--;
}
void app_main(void)
{
printf("tested app is runnig.\n");
vTaskDelay(5000 / portTICK_PERIOD_MS);
while(1) {
var_1++;
if (var_1 % 10 == 0) {
label_1:
foo();
}
label_3:
if (do_panic) {
char *t = NULL;
label_5:
*t = 'X';
}
}
}

View File

@ -1,15 +1,24 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os
import os.path as path
import sys
import pytest
sys.path.append(os.path.expandvars(os.path.join('$IDF_PATH', 'tools', 'test_apps', 'system', 'panic')))
sys.path.append(path.expandvars(path.join('$IDF_PATH', 'tools', 'test_apps', 'system', 'panic')))
from test_panic_util import PanicTestDut # noqa: E402
def get_line_number(lookup: str, offset: int = 0) -> int:
src_file = path.join(path.dirname(path.abspath(__file__)), 'main', 'test_app_main.c')
with open(src_file) as f:
for num, line in enumerate(f, 1):
if lookup in line:
return num + offset
return -1
@pytest.mark.supported_targets
@pytest.mark.generic
def test_gdbstub_runtime(dut: PanicTestDut) -> None:
@ -18,21 +27,21 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
dut.start_gdb()
# Test breakpoint
cmd = '-break-insert --source test_app_main.c --line 23'
cmd = '-break-insert --source test_app_main.c --function app_main --label label_1'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))
assert response is not None
cmd = '-exec-continue'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# does not stoped on breakpoint yet
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'breakpoint-hit'
assert payload['bkptno'] == '1'
assert payload['frame']['func'] == 'app_main'
assert payload['frame']['line'] == '23'
assert payload['frame']['line'] == str(get_line_number('label_1:', 1))
assert payload['stopped-threads'] == 'all'
# Test step command
@ -40,13 +49,13 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# does not stoped on breakpoint yet
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'end-stepping-range'
assert payload['frame']['func'] == 'foo'
assert payload['frame']['line'] == '14'
assert payload['frame']['line'] == str(get_line_number('var_2+=2;'))
assert payload['stopped-threads'] == 'all'
# Test finish command
@ -54,26 +63,36 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# does not stoped on breakpoint yet
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'function-finished'
assert payload['frame']['line'] == '23'
# On riscv we may have situation when returned from a function but stay on exactly the same line
# foo();
# 4200ae5c: f99ff0ef jal ra,4200adf4 <foo>
# 4200ae60: a011 j 4200ae64 <app_main+0x4e> <----------- here after return from foo()
# }
assert payload['frame']['line'] == str(get_line_number('label_3:', 1) if dut.is_xtensa else get_line_number('foo();', 0))
assert payload['frame']['func'] == 'app_main'
assert payload['stopped-threads'] == 'all'
cmd = '-exec-continue'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
assert dut.find_gdb_response('running', 'notify', responses) is not None
# Test next command
cmd = '-exec-next'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# does not stoped on breakpoint yet
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'end-stepping-range'
assert payload['frame']['line'] == '21'
assert payload['frame']['line'] == str(get_line_number('label_3:', 1))
assert payload['frame']['func'] == 'app_main'
assert payload['stopped-threads'] == 'all'
@ -100,7 +119,7 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# does not stoped on breakpoint yet
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'signal-received'
@ -111,3 +130,83 @@ def test_gdbstub_runtime(dut: PanicTestDut) -> None:
# assert payload['reason'] == 'watchpoint-trigger'
# assert int(payload['value']['new']) == int(payload['value']['old']) + 1
# assert payload['frame']['line'] == '14'
cmd = '-break-delete 2'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
# test set variable
cmd = '-gdb-set do_panic=1'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
# test panic handling
cmd = '-exec-continue'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'signal-received'
assert payload['signal-name'] == 'SIGSEGV'
assert payload['frame']['func'] == 'app_main'
assert payload['frame']['line'] == str(get_line_number('label_5', 1))
assert payload['stopped-threads'] == 'all'
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.generic
@pytest.mark.temp_skip_ci(targets=['esp32s3'], reason='fix IDF-7927')
def test_gdbstub_runtime_xtensa_stepping_bug(dut: PanicTestDut) -> None:
dut.expect_exact('tested app is runnig.')
dut.write(b'\x03') # send Ctrl-C
dut.start_gdb()
# Test breakpoint
cmd = '-break-insert --source test_app_main.c --function app_main --label label_1'
response = dut.find_gdb_response('done', 'result', dut.gdb_write(cmd))
assert response is not None
cmd = '-exec-continue'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'breakpoint-hit'
assert payload['bkptno'] == '1'
assert payload['frame']['func'] == 'app_main'
assert payload['frame']['line'] == str(get_line_number('label_1:', 1))
assert payload['stopped-threads'] == 'all'
# Test step command
cmd = '-exec-step'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'end-stepping-range'
assert payload['frame']['func'] == 'foo'
assert payload['frame']['line'] == str(get_line_number('var_2+=2;'))
assert payload['stopped-threads'] == 'all'
# Test next command
cmd = '-exec-next'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('running', 'result', responses) is not None
if not dut.find_gdb_response('stopped', 'notify', responses):
# have not stopped on breakpoint yet
responses = dut.gdbmi.get_gdb_response(timeout_sec=3)
assert dut.find_gdb_response('stopped', 'notify', responses) is not None
payload = dut.find_gdb_response('stopped', 'notify', responses)['payload']
assert payload['reason'] == 'end-stepping-range'
assert payload['frame']['line'] == str(get_line_number('var_2--;', 0))
assert payload['frame']['func'] == 'foo'
assert payload['stopped-threads'] == 'all'