mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/gdbstubs_rt' into 'master'
Extension of GDBStub component for handling GDB by serial port at runtime. See merge request espressif/esp-idf!10312
This commit is contained in:
commit
38d902f544
@ -1319,6 +1319,9 @@ esp_err_t uart_flush_input(uart_port_t uart_num)
|
||||
esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t *uart_queue, int intr_alloc_flags)
|
||||
{
|
||||
esp_err_t r;
|
||||
#ifdef CONFIG_ESP_GDBSTUB_ENABLED
|
||||
UART_CHECK((uart_num != CONFIG_ESP_CONSOLE_UART_NUM), "UART used by GDB-stubs! Please disable GDB in menuconfig.", ESP_FAIL);
|
||||
#endif // CONFIG_ESP_GDBSTUB_ENABLED
|
||||
UART_CHECK((uart_num < UART_NUM_MAX), "uart_num error", ESP_FAIL);
|
||||
UART_CHECK((rx_buffer_size > SOC_UART_FIFO_LEN), "uart rx buffer length error", ESP_FAIL);
|
||||
UART_CHECK((tx_buffer_size > SOC_UART_FIFO_LEN) || (tx_buffer_size == 0), "uart tx buffer length error", ESP_FAIL);
|
||||
|
@ -9,8 +9,12 @@ idf_component_register(SRCS "src/gdbstub.c" "src/packet.c"
|
||||
|
||||
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
|
||||
target_include_directories(${COMPONENT_LIB} PUBLIC "xtensa" "${target}")
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "xtensa/gdbstub_xtensa.c" "${target}/gdbstub_${target}.c")
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "xtensa/gdbstub_xtensa.c"
|
||||
"xtensa/gdbstub-entry.S"
|
||||
"esp_common/gdbstub_common.c")
|
||||
|
||||
elseif(CONFIG_IDF_TARGET_ARCH_RISCV)
|
||||
target_include_directories(${COMPONENT_LIB} PUBLIC "riscv" "${target}")
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "riscv/gdbstub_riscv.c" "${target}/gdbstub_${target}.c")
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "riscv/gdbstub_riscv.c"
|
||||
"${target}/gdbstub_${target}.c")
|
||||
endif()
|
||||
|
@ -1,4 +1,4 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_PRIV_INCLUDEDIRS := private_include esp32 xtensa
|
||||
COMPONENT_SRCDIRS := src esp32 xtensa
|
||||
COMPONENT_SRCDIRS := src esp32 xtensa esp_common
|
||||
COMPONENT_ADD_LDFRAGMENTS += linker.lf
|
||||
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define UART_NUM CONFIG_ESP_CONSOLE_UART_NUM
|
||||
|
||||
void esp_gdbstub_target_init(void)
|
||||
{
|
||||
}
|
||||
|
||||
int esp_gdbstub_getchar(void)
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
|
||||
;
|
||||
}
|
||||
return REG_READ(UART_FIFO_REG(UART_NUM));
|
||||
}
|
||||
|
||||
void esp_gdbstub_putchar(int c)
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
|
||||
;
|
||||
}
|
||||
REG_WRITE(UART_FIFO_REG(UART_NUM), c);
|
||||
}
|
||||
|
||||
void esp_gdbstub_flush()
|
||||
{
|
||||
//not needed for uart
|
||||
}
|
||||
|
||||
int esp_gdbstub_readmem(intptr_t addr)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see esp_cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
uint32_t val_aligned = *(uint32_t *)(addr & (~3));
|
||||
uint32_t shift = (addr & 3) * 8;
|
||||
return (val_aligned >> shift) & 0xff;
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define UART_NUM CONFIG_ESP_CONSOLE_UART_NUM
|
||||
|
||||
void esp_gdbstub_target_init()
|
||||
{
|
||||
}
|
||||
|
||||
int esp_gdbstub_getchar()
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
|
||||
;
|
||||
}
|
||||
return REG_READ(UART_FIFO_AHB_REG(UART_NUM));
|
||||
}
|
||||
|
||||
void esp_gdbstub_putchar(int c)
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
|
||||
;
|
||||
}
|
||||
REG_WRITE(UART_FIFO_AHB_REG(UART_NUM), c);
|
||||
}
|
||||
|
||||
void esp_gdbstub_flush()
|
||||
{
|
||||
//not needed for uart
|
||||
}
|
||||
|
||||
int esp_gdbstub_readmem(intptr_t addr)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see esp_cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
uint32_t val_aligned = *(uint32_t *)(addr & (~3));
|
||||
uint32_t shift = (addr & 3) * 8;
|
||||
return (val_aligned >> shift) & 0xff;
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
|
||||
#define UART_NUM CONFIG_ESP_CONSOLE_UART_NUM
|
||||
|
||||
void esp_gdbstub_target_init()
|
||||
{
|
||||
}
|
||||
|
||||
int esp_gdbstub_getchar()
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
|
||||
;
|
||||
}
|
||||
return REG_READ(UART_FIFO_AHB_REG(UART_NUM));
|
||||
}
|
||||
|
||||
void esp_gdbstub_putchar(int c)
|
||||
{
|
||||
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
|
||||
;
|
||||
}
|
||||
REG_WRITE(UART_FIFO_AHB_REG(UART_NUM), c);
|
||||
}
|
||||
|
||||
void esp_gdbstub_flush()
|
||||
{
|
||||
//not needed for uart
|
||||
}
|
||||
|
||||
int esp_gdbstub_readmem(intptr_t addr)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
uint32_t val_aligned = *(uint32_t *)(addr & (~3));
|
||||
uint32_t shift = (addr & 3) * 8;
|
||||
return (val_aligned >> shift) & 0xff;
|
||||
}
|
133
components/esp_gdbstub/esp_common/gdbstub_common.c
Normal file
133
components/esp_gdbstub/esp_common/gdbstub_common.c
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "soc/uart_periph.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "hal/uart_ll.h"
|
||||
|
||||
#define UART_NUM CONFIG_ESP_CONSOLE_UART_NUM
|
||||
|
||||
static uart_dev_t *gdb_uart = NULL;
|
||||
|
||||
|
||||
void esp_gdbstub_target_init(void)
|
||||
{
|
||||
switch (UART_NUM) {
|
||||
case 0:
|
||||
gdb_uart = &UART0;
|
||||
break;
|
||||
#if SOC_UART_NUM > 1
|
||||
case 1:
|
||||
gdb_uart = &UART1;
|
||||
break;
|
||||
#endif
|
||||
#if SOC_UART_NUM > 2
|
||||
case 2:
|
||||
gdb_uart = &UART2;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
gdb_uart = &UART0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int esp_gdbstub_getchar(void)
|
||||
{
|
||||
if (gdb_uart == NULL) {
|
||||
esp_gdbstub_target_init();
|
||||
}
|
||||
unsigned char data;
|
||||
while (uart_ll_get_rxfifo_len(gdb_uart) == 0) {
|
||||
|
||||
}
|
||||
uart_ll_read_rxfifo(gdb_uart, &data, 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
void esp_gdbstub_putchar(int c)
|
||||
{
|
||||
if (gdb_uart == NULL) {
|
||||
esp_gdbstub_target_init();
|
||||
}
|
||||
while (uart_ll_get_txfifo_len(gdb_uart) <= 126) {
|
||||
}
|
||||
uart_ll_write_txfifo(gdb_uart, (uint8_t *)&c, 1);
|
||||
}
|
||||
|
||||
void esp_gdbstub_flush()
|
||||
{
|
||||
//not needed for uart
|
||||
}
|
||||
|
||||
int esp_gdbstub_getfifo()
|
||||
{
|
||||
if (gdb_uart == NULL) {
|
||||
esp_gdbstub_target_init();
|
||||
}
|
||||
int doDebug = 0;
|
||||
|
||||
int fifolen = uart_ll_get_rxfifo_len(gdb_uart);
|
||||
while (fifolen != 0) {
|
||||
unsigned char data;
|
||||
uart_ll_read_rxfifo(gdb_uart, &data, 1);
|
||||
if (data == 0x3) {
|
||||
doDebug = 1; //Check if any of the chars is Ctrl+C. Throw away rest.
|
||||
}
|
||||
if (data == '+') {
|
||||
doDebug = 1; //Check if any of the chars is '+'. Throw away rest.
|
||||
}
|
||||
fifolen--;
|
||||
}
|
||||
uart_ll_clr_intsts_mask(gdb_uart, UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);
|
||||
return doDebug;
|
||||
}
|
||||
|
||||
int esp_gdbstub_readmem(intptr_t addr)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
uint32_t val_aligned = *(uint32_t *)(addr & (~3));
|
||||
uint32_t shift = (addr & 3) * 8;
|
||||
return (val_aligned >> shift) & 0xff;
|
||||
}
|
||||
|
||||
int esp_gdbstub_writemem(unsigned int addr, unsigned char data)
|
||||
{
|
||||
if (addr < 0x20000000 || addr >= 0x80000000) {
|
||||
/* see cpu_configure_region_protection */
|
||||
return -1;
|
||||
}
|
||||
|
||||
int *i = (int *)(addr & (~3));
|
||||
if ((addr & 3) == 0) {
|
||||
*i = (*i & 0xffffff00) | (data << 0);
|
||||
}
|
||||
if ((addr & 3) == 1) {
|
||||
*i = (*i & 0xffff00ff) | (data << 8);
|
||||
}
|
||||
if ((addr & 3) == 2) {
|
||||
*i = (*i & 0xff00ffff) | (data << 16);
|
||||
}
|
||||
if ((addr & 3) == 3) {
|
||||
*i = (*i & 0x00ffffff) | (data << 24);
|
||||
}
|
||||
asm volatile("ISYNC\nISYNC\n");
|
||||
|
||||
return 0;
|
||||
}
|
@ -14,13 +14,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_gdbstub_arch.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame) __attribute__((noreturn));
|
||||
void esp_gdbstub_init(void);
|
||||
void esp_gdbstub_panic_handler(void *frame);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -18,7 +18,8 @@
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "esp_gdbstub.h"
|
||||
#include "gdbstub_target_config.h"
|
||||
#include "esp_gdbstub_arch.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
@ -30,6 +31,7 @@
|
||||
#define GDBSTUB_ST_ENDPACKET -1
|
||||
#define GDBSTUB_ST_ERR -2
|
||||
#define GDBSTUB_ST_OK -3
|
||||
#define GDBSTUB_ST_CONT -4
|
||||
|
||||
/* Special task index values */
|
||||
#define GDBSTUB_CUR_TASK_INDEX_UNKNOWN -1
|
||||
@ -125,6 +127,20 @@ int esp_gdbstub_readmem(intptr_t addr);
|
||||
*/
|
||||
void esp_gdbstub_flush(void);
|
||||
|
||||
/**
|
||||
* Write a byte to target memory
|
||||
* @param addr address
|
||||
* @param data data byte
|
||||
* @return 0 in case of success, -1 in case of error
|
||||
*/
|
||||
int esp_gdbstub_writemem(unsigned int addr, unsigned char data);
|
||||
|
||||
/**
|
||||
* Read a data from fifo and detect start symbol
|
||||
* @return 1 if break symbol was detected, or 0 if not
|
||||
*/
|
||||
int esp_gdbstub_getfifo(void);
|
||||
|
||||
/**** GDB packet related functions ****/
|
||||
|
||||
/** Begin a packet */
|
||||
@ -143,7 +159,7 @@ void esp_gdbstub_send_hex(int val, int bits);
|
||||
void esp_gdbstub_send_end(void);
|
||||
|
||||
/** Send a packet with a string as content */
|
||||
void esp_gdbstub_send_str_packet(const char* str);
|
||||
void esp_gdbstub_send_str_packet(const char *str);
|
||||
|
||||
/** Get a hex value from the gdb packet */
|
||||
uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits);
|
||||
|
@ -58,3 +58,8 @@ int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame)
|
||||
{
|
||||
return 5; // SIGTRAP, see IDF-2490
|
||||
}
|
||||
|
||||
void _xt_gdbstub_int(void * frame)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -17,6 +17,14 @@
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "soc/uart_reg.h"
|
||||
#include "soc/periph_defs.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "hal/wdt_hal.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
static inline int gdb_tid_to_task_index(int tid);
|
||||
@ -25,16 +33,20 @@ static void init_task_info(void);
|
||||
static void find_paniced_task_index(void);
|
||||
static void set_active_task(size_t index);
|
||||
static int handle_task_commands(unsigned char *cmd, int len);
|
||||
static void esp_gdbstub_send_str_as_hex(const char *str);
|
||||
#endif
|
||||
|
||||
static void send_reason(void);
|
||||
|
||||
|
||||
static esp_gdbstub_scratch_t s_scratch;
|
||||
static esp_gdbstub_gdb_regfile_t *gdb_local_regfile = &s_scratch.regfile;
|
||||
|
||||
|
||||
void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame)
|
||||
/**
|
||||
* @brief panic handler
|
||||
*/
|
||||
void esp_gdbstub_panic_handler(void *in_frame)
|
||||
{
|
||||
esp_gdbstub_frame_t *frame = (esp_gdbstub_frame_t *)in_frame;
|
||||
#ifndef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
|
||||
#else
|
||||
@ -80,7 +92,9 @@ void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set interrupt reason to GDB
|
||||
*/
|
||||
static void send_reason(void)
|
||||
{
|
||||
esp_gdbstub_send_start();
|
||||
@ -89,13 +103,159 @@ static void send_reason(void)
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap bytes in word
|
||||
*/
|
||||
static uint32_t gdbstub_hton(uint32_t i)
|
||||
{
|
||||
return __builtin_bswap32(i);
|
||||
}
|
||||
|
||||
static wdt_hal_context_t rtc_wdt_ctx = {.inst = WDT_RWDT, .rwdt_dev = &RTCCNTL};
|
||||
static wdt_hal_context_t wdt0_context = {.inst = WDT_MWDT0, .mwdt_dev = &TIMERG0};
|
||||
static wdt_hal_context_t wdt1_context = {.inst = WDT_MWDT1, .mwdt_dev = &TIMERG1};
|
||||
|
||||
static bool wdt0_context_enabled = false;
|
||||
static bool wdt1_context_enabled = false;
|
||||
static bool rtc_wdt_ctx_enabled = false;
|
||||
/**
|
||||
* Disable all enabled WDTs
|
||||
*/
|
||||
static inline void disable_all_wdts(void)
|
||||
{
|
||||
wdt0_context_enabled = wdt_hal_is_enabled(&wdt0_context);
|
||||
wdt1_context_enabled = wdt_hal_is_enabled(&wdt1_context);
|
||||
rtc_wdt_ctx_enabled = wdt_hal_is_enabled(&rtc_wdt_ctx);
|
||||
|
||||
//Task WDT is the Main Watchdog Timer of Timer Group 0
|
||||
if (true == wdt0_context_enabled) {
|
||||
wdt_hal_write_protect_disable(&wdt0_context);
|
||||
wdt_hal_disable(&wdt0_context);
|
||||
wdt_hal_feed(&wdt0_context);
|
||||
wdt_hal_write_protect_enable(&wdt0_context);
|
||||
}
|
||||
|
||||
//Interupt WDT is the Main Watchdog Timer of Timer Group 1
|
||||
if (true == wdt1_context_enabled) {
|
||||
wdt_hal_write_protect_disable(&wdt1_context);
|
||||
wdt_hal_disable(&wdt1_context);
|
||||
wdt_hal_feed(&wdt1_context);
|
||||
wdt_hal_write_protect_enable(&wdt1_context);
|
||||
}
|
||||
if (true == rtc_wdt_ctx_enabled) {
|
||||
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
|
||||
wdt_hal_disable(&rtc_wdt_ctx);
|
||||
wdt_hal_feed(&rtc_wdt_ctx);
|
||||
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all enabled WDTs
|
||||
*/
|
||||
static inline void enable_all_wdts(void)
|
||||
{
|
||||
//Task WDT is the Main Watchdog Timer of Timer Group 0
|
||||
if (false == wdt0_context_enabled) {
|
||||
wdt_hal_write_protect_disable(&wdt0_context);
|
||||
wdt_hal_enable(&wdt0_context);
|
||||
wdt_hal_write_protect_enable(&wdt0_context);
|
||||
}
|
||||
// Interupt WDT is the Main Watchdog Timer of Timer Group 1
|
||||
if (false == wdt1_context_enabled) {
|
||||
wdt_hal_write_protect_disable(&wdt1_context);
|
||||
wdt_hal_enable(&wdt1_context);
|
||||
wdt_hal_write_protect_enable(&wdt1_context);
|
||||
}
|
||||
|
||||
if (false == rtc_wdt_ctx_enabled) {
|
||||
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
|
||||
wdt_hal_enable(&rtc_wdt_ctx);
|
||||
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @breef Handle UART interrupt
|
||||
*
|
||||
* Handle UART interrupt for gdbstub. The function disable WDT.
|
||||
* If Ctrl+C combination detected (0x03), then application will start to process incoming GDB messages.
|
||||
* The gdbstub will stay in this interrupt until continue command will be received ($c#63).
|
||||
*
|
||||
* @param curr_regs - actual registers frame
|
||||
*
|
||||
*/
|
||||
void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame)
|
||||
{
|
||||
// Disable all enabled WDT on enter
|
||||
disable_all_wdts();
|
||||
|
||||
int doDebug = esp_gdbstub_getfifo();
|
||||
s_scratch.signal = esp_gdbstub_get_signal(regs_frame);
|
||||
|
||||
if (doDebug) {
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
init_task_info();
|
||||
#endif// CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
esp_gdbstub_frame_to_regfile(regs_frame, gdb_local_regfile);
|
||||
send_reason();
|
||||
while (true) {
|
||||
unsigned char *cmd;
|
||||
size_t size;
|
||||
|
||||
int res = esp_gdbstub_read_command(&cmd, &size);
|
||||
if (res == '-') {
|
||||
send_reason();
|
||||
continue;
|
||||
}
|
||||
if (res > 0) {
|
||||
/* character received instead of a command */
|
||||
continue;
|
||||
}
|
||||
if (res == -2) {
|
||||
esp_gdbstub_send_str_packet("E01");
|
||||
continue;
|
||||
}
|
||||
res = esp_gdbstub_handle_command(cmd, size);
|
||||
if (res == -2) {
|
||||
esp_gdbstub_send_str_packet(NULL);
|
||||
}
|
||||
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
if (res == GDBSTUB_ST_CONT) {
|
||||
break;
|
||||
}
|
||||
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intr_handle_t intr_handle_;
|
||||
extern void _xt_gdbstub_int(void * );
|
||||
|
||||
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
/** @brief Init gdbstub
|
||||
* Init uart interrupt for gdbstub
|
||||
* */
|
||||
void esp_gdbstub_init(void)
|
||||
{
|
||||
esp_intr_alloc(ETS_UART0_INTR_SOURCE, 0, _xt_gdbstub_int, NULL, &intr_handle_);
|
||||
}
|
||||
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
|
||||
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
|
||||
/** Send string as a het to uart */
|
||||
static void esp_gdbstub_send_str_as_hex(const char *str)
|
||||
{
|
||||
while (*str) {
|
||||
esp_gdbstub_send_hex(*str, 8);
|
||||
str++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Send all registers to gdb */
|
||||
static void handle_g_command(const unsigned char* cmd, int len)
|
||||
static void handle_g_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
uint32_t *p = (uint32_t *) &s_scratch.regfile;
|
||||
esp_gdbstub_send_start();
|
||||
@ -106,7 +266,7 @@ static void handle_g_command(const unsigned char* cmd, int len)
|
||||
}
|
||||
|
||||
/** Receive register values from gdb */
|
||||
static void handle_G_command(const unsigned char* cmd, int len)
|
||||
static void handle_G_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
uint32_t *p = (uint32_t *) &s_scratch.regfile;
|
||||
for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
|
||||
@ -116,7 +276,7 @@ static void handle_G_command(const unsigned char* cmd, int len)
|
||||
}
|
||||
|
||||
/** Read memory to gdb */
|
||||
static void handle_m_command(const unsigned char* cmd, int len)
|
||||
static void handle_m_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
intptr_t addr = (intptr_t) esp_gdbstub_gethex(&cmd, -1);
|
||||
cmd++;
|
||||
@ -135,12 +295,32 @@ static void handle_m_command(const unsigned char* cmd, int len)
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/** Write memory from gdb */
|
||||
static void handle_M_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
intptr_t addr = (intptr_t) esp_gdbstub_gethex(&cmd, -1);
|
||||
cmd++; // skip '.'
|
||||
uint32_t size = esp_gdbstub_gethex(&cmd, -1);
|
||||
cmd++; // skip ':'
|
||||
|
||||
if (esp_gdbstub_readmem(addr) < 0 || esp_gdbstub_readmem(addr + size - 1) < 0) {
|
||||
esp_gdbstub_send_str_packet("E01");
|
||||
return;
|
||||
}
|
||||
for (int k = 0; k < size; k++) {
|
||||
esp_gdbstub_writemem(addr, esp_gdbstub_gethex(&cmd, 8));
|
||||
addr++;
|
||||
}
|
||||
esp_gdbstub_send_start();
|
||||
esp_gdbstub_send_str_packet("OK");
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
/** Handle a command received from gdb */
|
||||
int esp_gdbstub_handle_command(unsigned char *cmd, int len)
|
||||
{
|
||||
unsigned char *data = cmd + 1;
|
||||
if (cmd[0] == 'g')
|
||||
{
|
||||
if (cmd[0] == 'g') {
|
||||
handle_g_command(data, len - 1);
|
||||
} else if (cmd[0] == 'G') {
|
||||
/* receive content for all registers from gdb */
|
||||
@ -148,6 +328,9 @@ int esp_gdbstub_handle_command(unsigned char *cmd, int len)
|
||||
} else if (cmd[0] == 'm') {
|
||||
/* read memory to gdb */
|
||||
handle_m_command(data, len - 1);
|
||||
} else if (cmd[0] == 'M') {
|
||||
/* write to memory from GDB */
|
||||
handle_M_command(data, len - 1);
|
||||
} else if (cmd[0] == '?') {
|
||||
/* Reply with stop reason */
|
||||
send_reason();
|
||||
@ -155,6 +338,8 @@ int esp_gdbstub_handle_command(unsigned char *cmd, int len)
|
||||
} else if (s_scratch.state != GDBSTUB_TASK_SUPPORT_DISABLED) {
|
||||
return handle_task_commands(cmd, len);
|
||||
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
|
||||
} else if (strncmp((char *)cmd, "vCont;c", 7) == 0 || cmd[0] == 'c') { //continue execution
|
||||
return GDBSTUB_ST_CONT;
|
||||
} else {
|
||||
/* Unrecognized command */
|
||||
return GDBSTUB_ST_ERR;
|
||||
@ -212,7 +397,7 @@ static bool get_task_handle(size_t index, TaskHandle_t *handle)
|
||||
/** Get the index of the task running on the current CPU, and save the result */
|
||||
static void find_paniced_task_index(void)
|
||||
{
|
||||
TaskHandle_t cur_handle = xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
|
||||
TaskHandle_t cur_handle = (TaskHandle_t)xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
|
||||
TaskHandle_t handle;
|
||||
for (int i = 0; i < s_scratch.task_count; i++) {
|
||||
if (get_task_handle(i, &handle) && cur_handle == handle) {
|
||||
@ -244,7 +429,7 @@ static void set_active_task(size_t index)
|
||||
}
|
||||
|
||||
/** H command sets the "current task" for the purpose of further commands */
|
||||
static void handle_H_command(const unsigned char* cmd, int len)
|
||||
static void handle_H_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
const char *ret = "OK";
|
||||
if (cmd[1] == 'g') {
|
||||
@ -270,7 +455,7 @@ static void handle_H_command(const unsigned char* cmd, int len)
|
||||
}
|
||||
|
||||
/** qC returns the current thread ID */
|
||||
static void handle_qC_command(const unsigned char* cmd, int len)
|
||||
static void handle_qC_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
esp_gdbstub_send_start();
|
||||
esp_gdbstub_send_str("QC");
|
||||
@ -282,7 +467,7 @@ static void handle_qC_command(const unsigned char* cmd, int len)
|
||||
* Since GDB isn't going to ask about the tasks which haven't been listed by q*ThreadInfo,
|
||||
* and the state of tasks can not change (no stepping allowed), simply return "OK" here.
|
||||
*/
|
||||
static void handle_T_command(const unsigned char* cmd, int len)
|
||||
static void handle_T_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
esp_gdbstub_send_str_packet("OK");
|
||||
}
|
||||
@ -299,14 +484,14 @@ static void send_single_thread_info(int task_index)
|
||||
/** qfThreadInfo requests the start of the thread list, qsThreadInfo (below) is repeated to
|
||||
* get the subsequent threads.
|
||||
*/
|
||||
static void handle_qfThreadInfo_command(const unsigned char* cmd, int len)
|
||||
static void handle_qfThreadInfo_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
assert(s_scratch.task_count > 0); /* There should be at least one task */
|
||||
send_single_thread_info(0);
|
||||
s_scratch.thread_info_index = 1;
|
||||
}
|
||||
|
||||
static void handle_qsThreadInfo_command(const unsigned char* cmd, int len)
|
||||
static void handle_qsThreadInfo_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
int task_index = s_scratch.thread_info_index;
|
||||
if (task_index == s_scratch.task_count) {
|
||||
@ -319,7 +504,7 @@ static void handle_qsThreadInfo_command(const unsigned char* cmd, int len)
|
||||
}
|
||||
|
||||
/** qThreadExtraInfo requests the thread name */
|
||||
static void handle_qThreadExtraInfo_command(const unsigned char* cmd, int len)
|
||||
static void handle_qThreadExtraInfo_command(const unsigned char *cmd, int len)
|
||||
{
|
||||
cmd += sizeof("qThreadExtraInfo,") - 1;
|
||||
int task_index = gdb_tid_to_task_index(esp_gdbstub_gethex(&cmd, -1));
|
||||
@ -329,19 +514,20 @@ static void handle_qThreadExtraInfo_command(const unsigned char* cmd, int len)
|
||||
return;
|
||||
}
|
||||
esp_gdbstub_send_start();
|
||||
const char* task_name = pcTaskGetTaskName(handle);
|
||||
while (*task_name) {
|
||||
esp_gdbstub_send_hex(*task_name, 8);
|
||||
task_name++;
|
||||
}
|
||||
/** TODO: add "Running" or "Suspended" and "CPU0" or "CPU1" */
|
||||
esp_gdbstub_send_str_as_hex("Name: ");
|
||||
esp_gdbstub_send_str_as_hex((const char *)pcTaskGetTaskName(handle));
|
||||
esp_gdbstub_send_hex(' ', 8);
|
||||
|
||||
// Current version report only Suspended state
|
||||
esp_gdbstub_send_str_as_hex("State: Suspended");
|
||||
|
||||
esp_gdbstub_send_end();
|
||||
}
|
||||
|
||||
bool command_name_matches(const char* pattern, const unsigned char* ucmd, int len)
|
||||
bool command_name_matches(const char *pattern, const unsigned char *ucmd, int len)
|
||||
{
|
||||
const char* cmd = (const char*) ucmd;
|
||||
const char* end = cmd + len;
|
||||
const char *cmd = (const char *) ucmd;
|
||||
const char *end = cmd + len;
|
||||
for (; *pattern && cmd < end; ++cmd, ++pattern) {
|
||||
if (*pattern == '?') {
|
||||
continue;
|
||||
@ -375,6 +561,8 @@ static int handle_task_commands(unsigned char *cmd, int len)
|
||||
/* Unrecognized command */
|
||||
return GDBSTUB_ST_ERR;
|
||||
}
|
||||
} else if (strncmp((char *)cmd, "vCont;c", 7) == 0 || cmd[0] == 'c') { //continue execution
|
||||
return GDBSTUB_ST_CONT;
|
||||
} else {
|
||||
/* Unrecognized command */
|
||||
return GDBSTUB_ST_ERR;
|
||||
|
@ -53,7 +53,7 @@ void esp_gdbstub_send_str(const char *c)
|
||||
// 'bits'/4 dictates the number of hex chars sent.
|
||||
void esp_gdbstub_send_hex(int val, int bits)
|
||||
{
|
||||
const char* hex_chars = "0123456789abcdef";
|
||||
const char *hex_chars = "0123456789abcdef";
|
||||
for (int i = bits; i > 0; i -= 4) {
|
||||
esp_gdbstub_send_char(hex_chars[(val >> (i - 4)) & 0xf]);
|
||||
}
|
||||
@ -68,7 +68,7 @@ void esp_gdbstub_send_end(void)
|
||||
}
|
||||
|
||||
// Send a packet with a string as content
|
||||
void esp_gdbstub_send_str_packet(const char* str)
|
||||
void esp_gdbstub_send_str_packet(const char *str)
|
||||
{
|
||||
esp_gdbstub_send_start();
|
||||
if (str != NULL) {
|
||||
@ -164,7 +164,7 @@ int esp_gdbstub_read_command(unsigned char **out_cmd, size_t *out_size)
|
||||
// A # has been received. Get and check the received chsum.
|
||||
sentchs[0] = esp_gdbstub_getchar();
|
||||
sentchs[1] = esp_gdbstub_getchar();
|
||||
const unsigned char* c_ptr = &sentchs[0];
|
||||
const unsigned char *c_ptr = &sentchs[0];
|
||||
unsigned char rchsum = esp_gdbstub_gethex(&c_ptr, 8);
|
||||
if (rchsum != chsum) {
|
||||
esp_gdbstub_putchar('-');
|
||||
|
43
components/esp_gdbstub/xtensa/gdbstub-entry.S
Normal file
43
components/esp_gdbstub/xtensa/gdbstub-entry.S
Normal file
@ -0,0 +1,43 @@
|
||||
#include "freertos/xtensa_rtos.h"
|
||||
|
||||
// ------------------------------------------------
|
||||
.section .iram1,"ax"
|
||||
|
||||
.global gdbstub_handle_uart_int
|
||||
.global _xt_gdbstub_int
|
||||
.align 4
|
||||
|
||||
_xt_gdbstub_int:
|
||||
|
||||
/* Allocate exception frame and save minimal context. */
|
||||
mov a0, sp
|
||||
addi sp, sp, -XT_STK_FRMSZ
|
||||
s32i a0, sp, XT_STK_A1
|
||||
#if XCHAL_HAVE_WINDOWED
|
||||
s32e a0, sp, -12 /* for debug backtrace */
|
||||
#endif
|
||||
rsr a0, PS /* save interruptee's PS */
|
||||
s32i a0, sp, XT_STK_PS
|
||||
rsr a0, EPC_1 /* save interruptee's PC */
|
||||
s32i a0, sp, XT_STK_PC
|
||||
#if XCHAL_HAVE_WINDOWED
|
||||
s32e a0, sp, -16 /* for debug backtrace */
|
||||
#endif
|
||||
s32i a12, sp, XT_STK_A12 /* _xt_context_save requires A12- */
|
||||
s32i a13, sp, XT_STK_A13 /* A13 to have already been saved */
|
||||
|
||||
/* Save exc cause and vaddr into exception frame */
|
||||
rsr a0, EXCCAUSE
|
||||
s32i a0, sp, XT_STK_EXCCAUSE
|
||||
rsr a0, EXCVADDR
|
||||
s32i a0, sp, XT_STK_EXCVADDR
|
||||
|
||||
|
||||
/* _xt_context_save seems to save the current a0, but we need the interuptees a0. Fix this. */
|
||||
rsr a0, EXCSAVE_1 /* save interruptee's a0 */
|
||||
|
||||
s32i a0, sp, XT_STK_A0
|
||||
|
||||
mov a6,sp
|
||||
call0 gdbstub_handle_uart_int
|
||||
ret
|
@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <string.h>
|
||||
#include "esp_gdbstub.h"
|
||||
#include "esp_gdbstub_common.h"
|
||||
#include "soc/cpu.h"
|
||||
#include "soc/soc_memory_layout.h"
|
||||
|
@ -26,11 +26,19 @@ menu "ESP System Settings"
|
||||
Just resets the processor without outputting anything
|
||||
|
||||
config ESP_SYSTEM_PANIC_GDBSTUB
|
||||
bool "Invoke GDBStub"
|
||||
bool "GDBStub on panic"
|
||||
select ESP_GDBSTUB_ENABLED
|
||||
help
|
||||
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 FREERTOS_UNICORE
|
||||
select ESP_GDBSTUB_ENABLED
|
||||
help
|
||||
Invoke gdbstub on the serial port, allowing for gdb to attach to it and to do a debug on runtime.
|
||||
This feature will switch system to single core mode.
|
||||
endchoice
|
||||
|
||||
config ESP_SYSTEM_SINGLE_CORE_MODE
|
||||
|
@ -322,7 +322,7 @@ void esp_panic_handler(panic_info_t *info)
|
||||
wdt_hal_disable(&rtc_wdt_ctx);
|
||||
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
|
||||
panic_print_str("Entering gdb stub now.\r\n");
|
||||
esp_gdbstub_panic_handler((esp_gdbstub_frame_t *)info->frame);
|
||||
esp_gdbstub_panic_handler((void *)info->frame);
|
||||
#else
|
||||
#if CONFIG_ESP_COREDUMP_ENABLE
|
||||
static bool s_dumping_core;
|
||||
@ -373,7 +373,7 @@ void esp_panic_handler(panic_info_t *info)
|
||||
}
|
||||
|
||||
|
||||
void __attribute__((noreturn,no_sanitize_undefined)) panic_abort(const char *details)
|
||||
void __attribute__((noreturn, no_sanitize_undefined)) panic_abort(const char *details)
|
||||
{
|
||||
g_panic_abort = true;
|
||||
s_panic_abort_details = (char *) details;
|
||||
|
@ -61,6 +61,10 @@ volatile unsigned port_xSchedulerRunning[portNUM_PROCESSORS] = {0};
|
||||
|
||||
static void main_task(void* args);
|
||||
|
||||
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
void esp_gdbstub_init(void);
|
||||
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
|
||||
extern void app_main(void);
|
||||
|
||||
void esp_startup_start_app_common(void)
|
||||
@ -79,6 +83,10 @@ void esp_startup_start_app_common(void)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
esp_gdbstub_init();
|
||||
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
|
||||
|
||||
portBASE_TYPE res = xTaskCreatePinnedToCore(&main_task, "main",
|
||||
ESP_TASK_MAIN_STACK, NULL,
|
||||
ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE);
|
||||
|
@ -10,7 +10,6 @@ This tool can be launched from an IDF project by running ``idf.py monitor``.
|
||||
|
||||
For the legacy GNU Make system, run ``make monitor``.
|
||||
|
||||
|
||||
Keyboard Shortcuts
|
||||
==================
|
||||
|
||||
@ -59,7 +58,10 @@ For easy interaction with IDF Monitor, use the keyboard shortcuts given in the t
|
||||
* - * Ctrl+X (or X)
|
||||
- Exit the program
|
||||
-
|
||||
|
||||
* - Ctrl+C
|
||||
- Interrupt running application
|
||||
- Pauses IDF monitor and run GDB_ project debugger to debug the application at runtime. This requires :ref:CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME option to be enabled.
|
||||
|
||||
Any keys pressed, other than ``Ctrl-]`` and ``Ctrl-T``, will be sent through the serial port.
|
||||
|
||||
|
||||
@ -184,19 +186,23 @@ To decode each address, IDF Monitor runs the following command in the background
|
||||
Launching GDB with GDBStub
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
By default, if esp-idf crashes, the panic handler prints relevant registers and the stack dump (similar to the ones above) over the serial port. Then it resets the board.
|
||||
|
||||
Furthermore, the application can be configured to run GDBStub in the background and handle the Ctrl+C event from the monitor.
|
||||
|
||||
Optionally, the panic handler can be configured to run GDBStub, the tool which can communicate with GDB_ project debugger. GDBStub allows to read memory, examine call stack frames and variables, etc. It is not as versatile as JTAG debugging, but this method does not require any special hardware.
|
||||
|
||||
To enable GDBStub, open the project configuration menu (``idf.py menuconfig``) and set :ref:`CONFIG_ESP_SYSTEM_PANIC` to ``Invoke GDBStub``.
|
||||
To enable GDBStub on panic, open the project configuration menu (``idf.py menuconfig``) and set :ref:`CONFIG_ESP_SYSTEM_PANIC` to ``GDBStub on panic`` or set :ref:`CONFIG_ESP_SYSTEM_PANIC` to ``GDBStub on runtime``.
|
||||
|
||||
In this case, if the panic handler is triggered, as soon as IDF Monitor sees that GDBStub has loaded, it automatically pauses serial monitoring and runs GDB with necessary arguments. After GDB exits, the board is reset via the RTS serial line. If this line is not connected, please reset the board manually by pressing its Reset button.
|
||||
In this case, if the panic handler or Ctrl+C command is triggered, as soon as IDF Monitor sees that GDBStub has loaded, it automatically pauses serial monitoring and runs GDB with necessary arguments. After GDB exits, the board is reset via the RTS serial line. If this line is not connected, please reset the board manually by pressing its Reset button.
|
||||
|
||||
In the background, IDF Monitor runs the following command::
|
||||
|
||||
{IDF_TARGET_TOOLCHAIN_PREFIX}-gdb -ex "set serial baud BAUD" -ex "target remote PORT" -ex interrupt build/PROJECT.elf :idf_target:`Hello NAME chip`
|
||||
|
||||
|
||||
|
||||
Output Filtering
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
|
6
examples/system/gdbstub/CMakeLists.txt
Normal file
6
examples/system/gdbstub/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(gdbstub)
|
8
examples/system/gdbstub/Makefile
Normal file
8
examples/system/gdbstub/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := gdbstub
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
127
examples/system/gdbstub/README.md
Normal file
127
examples/system/gdbstub/README.md
Normal file
@ -0,0 +1,127 @@
|
||||
# GDBstub example
|
||||
|
||||
This example shows how to use gdbstub and it's functionality at runtime to debug an application with GDB.
|
||||
With the gdbstub component it is possible to run GDB from IDF Monitor by pressing Ctrl+C and debug
|
||||
the application using GDB. It is also possible to read/modify memory values, interrupt and continue the application.
|
||||
Upon exit from GDB, the application will continue to work in IDF Monitor as before.
|
||||
|
||||
## How to use example
|
||||
### Hardware Required
|
||||
|
||||
he example can run on any commonly available ESP32 development board.
|
||||
There are two possible ways to execute gdbstub with GDB: from IDF Monitor and as standalone application.
|
||||
gdbstub support ESP32, ESP32-S2 and ESP32-S3 chips.
|
||||
|
||||
### Configure the project
|
||||
|
||||
By default, the example is already pre-configured, but the user can change configuration options with the following command:
|
||||
```
|
||||
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
|
||||
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.
|
||||
Note that gdbstub can now only be used when FreeRTOS is run on the first core only.
|
||||
This setting is located here:
|
||||
-> Component Config -> FreeRTOS -> Run FreeRTOS only on first core.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run IDF Monitor to view the serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
Replace PORT with the name of the serial port to use, for example COM4 for Windows or /dev/ttyUSB0 for Linux.
|
||||
To exit the serial monitor, type ``Ctrl-]``.
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
In addition, it is also possible to run GDB and connect to the Esp32 directly, without IDF Monitor.
|
||||
```
|
||||
xtensa-esp32-elf-gdb ./build/gdbstub.elf -ex "set serial baud 115200" -ex "target remote \\.\COM10"
|
||||
```
|
||||
This will execute GDB and GDB will connect to your Esp32 by serial port COM10 with baudrate 115200.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example demonstrates how to switch to GDB, watch values, change values, continue to run, and exit from GDB to the application.
|
||||
To switch to GDB, the user presses Ctrl+C. This will stop the application and run the GDB.
|
||||
In GDB, the user can print values "print call_count" and "print update_log_level" and then
|
||||
change them "set call_count 100" and "set update_log_level = ESP_LOG_WARN".
|
||||
The user can continue running the application in GDB by entering "continue" and then interrupt the application by pressing Ctrl+C.
|
||||
The user can check again that the application has worked by checking variable "print call_count".
|
||||
The user can exit from GDB to continue seeing the trace from IDF Monitor by pressing "quit" and then "y".
|
||||
The user will see in IDF Monitor that call_count and logging level have changed.
|
||||
A typical console output for such a scenario is shown below:
|
||||
```
|
||||
I (300) cpu_start: Starting scheduler on PRO CPU.
|
||||
Hello GDB example!
|
||||
I (307) gdbstub_example: INFO mode enabled. Call - 0. To enter GDB please press "Ctrl+C"
|
||||
W (317) gdbstub_example: WARN mode enabled. Call - 0. To enter GDB please press "Ctrl+C"
|
||||
I (1317) gdbstub_example: INFO mode enabled. Call - 1. To enter GDB please press "Ctrl+C"
|
||||
W (1317) gdbstub_example: WARN mode enabled. Call - 1. To enter GDB please press "Ctrl+C"
|
||||
To exit from the idf.py please use "Ctrl+]"
|
||||
$T02#b6GNU gdb (crosstool-NG esp-2020r3) 8.1.0.20180627-git
|
||||
Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
||||
This is free software: you are free to change and redistribute it.
|
||||
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
|
||||
and "show warranty" for details.
|
||||
This GDB was configured as "--host=x86_64-host_w64-mingw32 --target=xtensa-esp32-elf".
|
||||
Type "show configuration" for configuration details.
|
||||
For bug reporting instructions, please see:
|
||||
<http://www.gnu.org/software/gdb/bugs/>.
|
||||
Find the GDB manual and other documentation resources online at:
|
||||
<http://www.gnu.org/software/gdb/documentation/>.
|
||||
For help, type "help".
|
||||
Type "apropos word" to search for commands related to "word"...
|
||||
Reading symbols from c:\esp-idf\examples\system\gdbstub\build\gdbstub.elf...done.
|
||||
Remote debugging using \\.\COM15
|
||||
0x400dff0a in esp_pm_impl_waiti () at C:/esp-idf/components/esp_pm/pm_impl.c:533
|
||||
533 asm("waiti 0");
|
||||
(gdb) print call_count
|
||||
$1 = 2
|
||||
(gdb) set call_count = 100
|
||||
(gdb) print call_count
|
||||
$2 = 100
|
||||
(gdb) print update_log_level
|
||||
$3 = ESP_LOG_DEBUG
|
||||
(gdb) set update_log_level = ESP_LOG_WARN
|
||||
(gdb) print update_log_level
|
||||
$4 = ESP_LOG_WARN
|
||||
(gdb) c
|
||||
Continuing.
|
||||
|
||||
Thread 1 received signal SIGINT, Interrupt.
|
||||
0x400dff0a in esp_pm_impl_waiti () at C:/esp-idf/components/esp_pm/pm_impl.c:533
|
||||
533 asm("waiti 0");
|
||||
(gdb) print call_count
|
||||
$6 = 108
|
||||
(gdb) quit
|
||||
A debugging session is active.
|
||||
|
||||
Inferior 1 [Remote target] will be killed.
|
||||
|
||||
Quit anyway? (y or n) y
|
||||
W (13977) gdbstub_example: WARN mode enabled. Call - 108. To enter GDB please press "Ctrl+C"
|
||||
W (14977) gdbstub_example: WARN mode enabled. Call - 109. To enter GDB please press "Ctrl+C"
|
||||
W (15977) gdbstub_example: WARN mode enabled. Call - 110. To enter GDB please press "Ctrl+C"
|
||||
W (16977) gdbstub_example: WARN mode enabled. Call - 111. To enter GDB please press "Ctrl+C"
|
||||
```
|
||||
|
||||
To reproduce this scenario run the application by: idf.py -P PORT flash monitor
|
||||
Then:
|
||||
1. Interrupt the application by pressing Ctrl+C
|
||||
2. In GDB, print the application values by typing in GDB command line "print call_count" or "print update_log_level"
|
||||
3. Modify the application values by typing in GDB command line "set call_count = 100" or "set update_log_level = ESP_LOG_WARN"
|
||||
4. Continue the application by typing in GDB command line "continue"
|
||||
5. Interrupt application by pressing Ctrl+C
|
||||
6. Check the value by typing in GDB command line "print call_count" or "print update_log_level"
|
||||
7. Exit GDB by typing "quit" and then "y"
|
||||
|
||||
To exit from monitor please use Ctrl+]
|
||||
|
2
examples/system/gdbstub/main/CMakeLists.txt
Normal file
2
examples/system/gdbstub/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "gdbstub_main.c"
|
||||
INCLUDE_DIRS "")
|
4
examples/system/gdbstub/main/component.mk
Normal file
4
examples/system/gdbstub/main/component.mk
Normal file
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
39
examples/system/gdbstub/main/gdbstub_main.c
Normal file
39
examples/system/gdbstub/main/gdbstub_main.c
Normal file
@ -0,0 +1,39 @@
|
||||
/* Hello World Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
||||
int call_count = 0;
|
||||
|
||||
static const char *TAG = "gdbstub_example";
|
||||
esp_log_level_t log_level = ESP_LOG_DEBUG;
|
||||
esp_log_level_t update_log_level = ESP_LOG_DEBUG;
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
printf("Hello GDB example!\n");
|
||||
|
||||
call_count = 0;
|
||||
|
||||
for (;;) {
|
||||
ESP_LOGD(TAG, "DEBUG mode enabled. Call - %i. To enter GDB please press \"Ctrl+C\"", call_count);
|
||||
ESP_LOGI(TAG, "INFO mode enabled. Call - %i. To enter GDB please press \"Ctrl+C\"", call_count);
|
||||
ESP_LOGW(TAG, "WARN mode enabled. Call - %i. To enter GDB please press \"Ctrl+C\"", call_count++);
|
||||
if (update_log_level != log_level) {
|
||||
log_level = update_log_level;
|
||||
esp_log_level_set(TAG, log_level);
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
22
examples/system/gdbstub/sdkconfig.defaults
Normal file
22
examples/system/gdbstub/sdkconfig.defaults
Normal file
@ -0,0 +1,22 @@
|
||||
#
|
||||
# GDB Stub
|
||||
#
|
||||
CONFIG_ESP_GDBSTUB_ENABLED=y
|
||||
CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y
|
||||
CONFIG_ESP_GDBSTUB_MAX_TASKS=32
|
||||
# end of GDB Stub
|
||||
|
||||
#
|
||||
# FreeRTOS
|
||||
#
|
||||
CONFIG_FREERTOS_UNICORE=y
|
||||
|
||||
#
|
||||
# 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_SYSTEM_SINGLE_CORE_MODE=y
|
||||
# end of ESP System Settings
|
10
tools/idf.py
10
tools/idf.py
@ -33,6 +33,7 @@ import json
|
||||
import locale
|
||||
import os
|
||||
import os.path
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter, OrderedDict
|
||||
@ -723,7 +724,16 @@ def init_cli(verbose_output=None):
|
||||
return CLI(help=cli_help, verbose_output=verbose_output, all_actions=all_actions)
|
||||
|
||||
|
||||
def signal_handler(_signal, _frame):
|
||||
# The Ctrl+C processed by other threads inside
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# Processing of Ctrl+C event for all threads made by main()
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
checks_output = check_environment()
|
||||
cli = init_cli(verbose_output=checks_output)
|
||||
# the argument `prog_name` must contain name of the file - not the absolute path to it!
|
||||
|
@ -140,6 +140,8 @@ class Monitor(object):
|
||||
self._decode_panic = decode_panic
|
||||
self._reading_panic = PANIC_IDLE
|
||||
self._panic_buffer = b''
|
||||
self.gdb_exit = False
|
||||
self.start_cmd_sent = False
|
||||
|
||||
def invoke_processing_last_line(self):
|
||||
# type: () -> None
|
||||
@ -149,43 +151,70 @@ class Monitor(object):
|
||||
# type: () -> None
|
||||
self.console_reader.start()
|
||||
self.serial_reader.start()
|
||||
self.gdb_exit = False
|
||||
self.start_cmd_sent = False
|
||||
try:
|
||||
while self.console_reader.alive and self.serial_reader.alive:
|
||||
try:
|
||||
item = self.cmd_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
try:
|
||||
item = self.event_queue.get(True, 0.03)
|
||||
except queue.Empty:
|
||||
continue
|
||||
if self.gdb_exit is True:
|
||||
self.gdb_exit = False
|
||||
|
||||
time.sleep(0.3)
|
||||
try:
|
||||
# Continue the program after exit from the GDB
|
||||
self.serial.write(codecs.encode('+$c#63'))
|
||||
self.start_cmd_sent = True
|
||||
except serial.SerialException:
|
||||
pass # this shouldn't happen, but sometimes port has closed in serial thread
|
||||
except UnicodeEncodeError:
|
||||
pass # this can happen if a non-ascii character was passed, ignoring
|
||||
|
||||
event_tag, data = item
|
||||
if event_tag == TAG_CMD:
|
||||
self.handle_commands(data, self.target)
|
||||
elif event_tag == TAG_KEY:
|
||||
try:
|
||||
self.serial.write(codecs.encode(data))
|
||||
item = self.cmd_queue.get_nowait()
|
||||
except queue.Empty:
|
||||
try:
|
||||
item = self.event_queue.get(True, 0.03)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
(event_tag, data) = item
|
||||
|
||||
if event_tag == TAG_CMD:
|
||||
self.handle_commands(data, self.target)
|
||||
elif event_tag == TAG_KEY:
|
||||
try:
|
||||
self.serial.write(codecs.encode(data))
|
||||
except serial.SerialException:
|
||||
pass # this shouldn't happen, but sometimes port has closed in serial thread
|
||||
except UnicodeEncodeError:
|
||||
pass # this can happen if a non-ascii character was passed, ignoring
|
||||
elif event_tag == TAG_SERIAL:
|
||||
self.handle_serial_input(data)
|
||||
if self._invoke_processing_last_line_timer is not None:
|
||||
self._invoke_processing_last_line_timer.cancel()
|
||||
self._invoke_processing_last_line_timer = threading.Timer(0.1, self.invoke_processing_last_line)
|
||||
self._invoke_processing_last_line_timer.start()
|
||||
# If no futher data is received in the next short period
|
||||
# of time then the _invoke_processing_last_line_timer
|
||||
# generates an event which will result in the finishing of
|
||||
# the last line. This is fix for handling lines sent
|
||||
# without EOL.
|
||||
elif event_tag == TAG_SERIAL_FLUSH:
|
||||
self.handle_serial_input(data, finalize_line=True)
|
||||
else:
|
||||
raise RuntimeError('Bad event data %r' % ((event_tag,data),))
|
||||
except KeyboardInterrupt:
|
||||
try:
|
||||
yellow_print('To exit from IDF monitor please use \"Ctrl+]\"')
|
||||
self.serial.write(codecs.encode('\x03'))
|
||||
except serial.SerialException:
|
||||
pass # this shouldn't happen, but sometimes port has closed in serial thread
|
||||
except UnicodeEncodeError:
|
||||
pass # this can happen if a non-ascii character was passed, ignoring
|
||||
elif event_tag == TAG_SERIAL:
|
||||
self.handle_serial_input(data)
|
||||
if self._invoke_processing_last_line_timer is not None:
|
||||
self._invoke_processing_last_line_timer.cancel()
|
||||
self._invoke_processing_last_line_timer = threading.Timer(0.1, self.invoke_processing_last_line)
|
||||
self._invoke_processing_last_line_timer.start()
|
||||
# If no further data is received in the next short period
|
||||
# of time then the _invoke_processing_last_line_timer
|
||||
# generates an event which will result in the finishing of
|
||||
# the last line. This is fix for handling lines sent
|
||||
# without EOL.
|
||||
elif event_tag == TAG_SERIAL_FLUSH:
|
||||
self.handle_serial_input(data, finalize_line=True)
|
||||
else:
|
||||
raise RuntimeError('Bad event data %r' % ((event_tag, data),))
|
||||
except SerialStopException:
|
||||
normal_print('Stopping condition has been received\n')
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
self.console_reader.stop()
|
||||
@ -200,6 +229,13 @@ class Monitor(object):
|
||||
|
||||
def handle_serial_input(self, data, finalize_line=False):
|
||||
# type: (bytes, bool) -> None
|
||||
# Remove "+" after Continue command
|
||||
if self.start_cmd_sent is True:
|
||||
self.start_cmd_sent = False
|
||||
pos = data.find(b'+')
|
||||
if pos != -1:
|
||||
data = data[(pos + 1):]
|
||||
|
||||
sp = data.split(b'\n')
|
||||
if self._last_line_part != b'':
|
||||
# add unprocessed part from previous "data" to the first line
|
||||
@ -260,6 +296,7 @@ class Monitor(object):
|
||||
def __exit__(self, *args, **kwargs): # type: ignore
|
||||
""" Use 'with self' to temporarily disable monitoring behaviour """
|
||||
self.console_reader.start()
|
||||
self.serial_reader.gdb_exit = self.gdb_exit # write gdb_exit flag
|
||||
self.serial_reader.start()
|
||||
|
||||
def prompt_next_action(self, reason): # type: (str) -> None
|
||||
@ -480,10 +517,24 @@ class Monitor(object):
|
||||
cmd = ['%sgdb' % self.toolchain_prefix,
|
||||
'-ex', 'set serial baud %d' % self.serial.baudrate,
|
||||
'-ex', 'target remote %s' % self.serial.port,
|
||||
'-ex', 'interrupt', # monitor has already parsed the first 'reason' command, need a second
|
||||
self.elf_file]
|
||||
process = subprocess.Popen(cmd, cwd='.')
|
||||
process.wait()
|
||||
|
||||
# Here we handling GDB as a process
|
||||
# Open GDB process
|
||||
try:
|
||||
process = subprocess.Popen(cmd, cwd='.')
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
# We ignore Ctrl+C interrupt form external process abd wait responce util GDB will be finished.
|
||||
while True:
|
||||
try:
|
||||
process.wait()
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
pass # We ignore the Ctrl+C
|
||||
self.gdb_exit = True
|
||||
|
||||
except OSError as e:
|
||||
red_print('%s: %s' % (' '.join(cmd), e))
|
||||
except KeyboardInterrupt:
|
||||
@ -499,7 +550,6 @@ class Monitor(object):
|
||||
subprocess.call(['stty', 'sane'])
|
||||
except Exception:
|
||||
pass # don't care if there's no stty, we tried...
|
||||
self.prompt_next_action('gdb exited')
|
||||
|
||||
def output_enable(self, enable): # type: (bool) -> None
|
||||
self._output_enabled = enable
|
||||
@ -733,6 +783,8 @@ def main(): # type: () -> None
|
||||
yellow_print('--- Print filter: {} ---'.format(args.print_filter))
|
||||
|
||||
monitor.main_loop()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
if ws:
|
||||
ws.close()
|
||||
|
@ -38,6 +38,7 @@ class SerialReader(StoppableThread):
|
||||
self.baud = serial_instance.baudrate
|
||||
self.serial = serial_instance
|
||||
self.event_queue = event_queue
|
||||
self.gdb_exit = False
|
||||
if not hasattr(self.serial, 'cancel_read'):
|
||||
# enable timeout for checking alive flag,
|
||||
# if cancel_read not available
|
||||
@ -47,10 +48,25 @@ class SerialReader(StoppableThread):
|
||||
# type: () -> None
|
||||
if not self.serial.is_open:
|
||||
self.serial.baudrate = self.baud
|
||||
self.serial.rts = True # Force an RTS reset on open
|
||||
# We can come to this thread at startup or from external application line GDB.
|
||||
# If we come from GDB we would like to continue to run without reset.
|
||||
if self.gdb_exit:
|
||||
self.serial.rts = False
|
||||
self.serial.dtr = True
|
||||
else: # if we exit from GDB, we don't need to reset the target
|
||||
# This sequence of DTR/RTS and open/close set the serial port to
|
||||
# condition when GDB not make reset of the target by switching DTR/RTS.
|
||||
self.serial.rts = True # IO0=LOW
|
||||
self.serial.dtr = self.serial.dtr # usbser.sys workaround
|
||||
self.serial.rts = False # IO0=HIGH
|
||||
self.serial.dtr = False
|
||||
|
||||
# Current state not reset the target!
|
||||
self.gdb_exit = False
|
||||
self.serial.open()
|
||||
self.serial.rts = False
|
||||
self.serial.dtr = self.serial.dtr # usbser.sys workaround
|
||||
time.sleep(0.005) # Add a delay to meet the requirements of minimal EN low time (2ms for ESP32-C3)
|
||||
self.serial.rts = False # Set rts/dtr to the working state
|
||||
self.serial.dtr = self.serial.dtr # usbser.sys workaround
|
||||
try:
|
||||
while self.alive:
|
||||
try:
|
||||
|
Loading…
x
Reference in New Issue
Block a user