Merge branch 'feature/riscv-gdbstub-runtime' into 'master'

esp_gdbstub: implement runtime gdbstub for riscv

Closes IDF-5621

See merge request espressif/esp-idf!23110
This commit is contained in:
Alexey Lapshin 2023-05-22 17:20:17 +08:00
commit a77f723962
31 changed files with 1051 additions and 130 deletions

View File

@ -92,6 +92,12 @@ test_certificate_bundle_on_host:
- cd components/mbedtls/esp_crt_bundle/test_gen_crt_bundle/
- ./test_gen_crt_bundle.py
test_gdbstub_on_host:
extends: .host_test_template
script:
- cd components/esp_gdbstub/test_gdbstub_host
- make test
test_idf_py:
extends: .host_test_template

View File

@ -11,7 +11,8 @@ if(CONFIG_IDF_TARGET_ARCH_XTENSA)
"src/port/xtensa/xt_debugexception.S")
list(APPEND priv_includes "src/port/xtensa/include")
elseif(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND srcs "src/port/riscv/gdbstub_riscv.c")
list(APPEND srcs "src/port/riscv/gdbstub_riscv.c"
"src/port/riscv/rv_decode.c")
list(APPEND priv_includes "src/port/riscv/include")
endif()

View File

@ -74,6 +74,16 @@ int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame);
*/
void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst);
/**
* Signal handler for debugging interrupts of the application.
*/
void esp_gdbstub_int(void *frame);
/**
* Signal handler for transport protocol interrupts.
*/
void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame);
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
/**
* Write registers from the saved frame of a given task to the GDB register file
@ -104,11 +114,13 @@ void esp_gdbstub_putchar(int c);
*/
void esp_gdbstub_flush(void);
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/**
* 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);
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/**** GDB packet related functions ****/
@ -144,7 +156,7 @@ void esp_gdbstub_stall_other_cpus_start(void);
void esp_gdbstub_stall_other_cpus_end(void);
void esp_gdbstub_clear_step(void);
void esp_gdbstub_do_step(void);
void esp_gdbstub_do_step(esp_gdbstub_frame_t *regs_frame);
void esp_gdbstub_trigger_cpu(void);
/**

View File

@ -11,6 +11,7 @@
#include "sdkconfig.h"
#include <sys/param.h>
#include "soc/soc_caps.h"
#include "soc/uart_reg.h"
#include "soc/periph_defs.h"
#include "esp_attr.h"
@ -187,6 +188,25 @@ static inline void enable_all_wdts(void)
}
}
int getActiveTaskNum(void);
int __swrite(struct _reent *, void *, const char *, int);
int gdbstub__swrite(struct _reent *data1, void *data2, const char *buff, int len);
volatile esp_gdbstub_frame_t *temp_regs_frame;
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
static int bp_count = 0;
static int wp_count = 0;
static uint32_t bp_list[SOC_CPU_BREAKPOINTS_NUM] = {0};
static uint32_t wp_list[SOC_CPU_WATCHPOINTS_NUM] = {0};
static uint32_t wp_size[SOC_CPU_WATCHPOINTS_NUM] = {0};
static esp_cpu_watchpoint_trigger_t wp_access[SOC_CPU_WATCHPOINTS_NUM] = {0};
static volatile bool step_in_progress = false;
static bool not_send_reason = false;
static bool process_gdb_kill = false;
static bool gdb_debug_int = false;
/**
* @breef Handle UART interrupt
*
@ -196,24 +216,7 @@ static inline void enable_all_wdts(void)
*
* @param curr_regs - actual registers frame
*
*/
static int bp_count = 0;
static int wp_count = 0;
static uint32_t bp_list[GDB_BP_SIZE] = {0};
static uint32_t wp_list[GDB_WP_SIZE] = {0};
static uint32_t wp_size[GDB_WP_SIZE] = {0};
static esp_cpu_watchpoint_trigger_t wp_access[GDB_WP_SIZE] = {0};
static volatile bool step_in_progress = false;
static bool not_send_reason = false;
static bool process_gdb_kill = false;
static bool gdb_debug_int = false;
int getActiveTaskNum(void);
int __swrite(struct _reent *, void *, const char *, int);
int gdbstub__swrite(struct _reent *data1, void *data2, const char *buff, int len);
volatile esp_gdbstub_frame_t *temp_regs_frame;
*/
void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame)
{
temp_regs_frame = regs_frame;
@ -264,11 +267,9 @@ void gdbstub_handle_uart_int(esp_gdbstub_frame_t *regs_frame)
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 */
}
{
/* Resume other core */
@ -356,16 +357,12 @@ void gdbstub_handle_debug_int(esp_gdbstub_frame_t *regs_frame)
gdb_debug_int = false;
}
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_);
esp_intr_alloc(ETS_UART0_INTR_SOURCE, 0, esp_gdbstub_int, NULL, NULL);
esp_gdbstub_init_dports();
}
#endif /* CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME */
@ -474,45 +471,71 @@ static void handle_M_command(const unsigned char *cmd, int len)
esp_gdbstub_send_end();
}
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
void update_breakpoints(void)
{
for (size_t i = 0; i < GDB_BP_SIZE; i++) {
#if CONFIG_IDF_TARGET_ARCH_XTENSA
for (size_t i = 0; i < SOC_CPU_BREAKPOINTS_NUM; i++) {
if (bp_list[i] != 0) {
esp_cpu_set_breakpoint(i, (const void *)bp_list[i]);
} else {
esp_cpu_clear_breakpoint(i);
}
}
for (size_t i = 0; i < GDB_WP_SIZE; i++) {
for (size_t i = 0; i < SOC_CPU_WATCHPOINTS_NUM; i++) {
if (wp_list[i] != 0) {
esp_cpu_set_watchpoint(i, (void *)wp_list[i], wp_size[i], wp_access[i]);
} else {
esp_cpu_clear_watchpoint(i);
}
}
#else // CONFIG_IDF_TARGET_ARCH_XTENSA
#if (SOC_CPU_BREAKPOINTS_NUM != SOC_CPU_WATCHPOINTS_NUM)
#error "riscv have a common number of BP and WP"
#endif
/*
* On riscv we have no separated registers for setting BP and WP as we have for xtensa.
* Instead we have common registers which could be configured as BP or WP.
*/
size_t i = 0;
for (size_t b = 0; b < SOC_CPU_BREAKPOINTS_NUM; b++) {
if (bp_list[b] != 0) {
esp_cpu_set_breakpoint(i, (const void *)bp_list[b]);
i++;
}
}
for (size_t w = 0; w < SOC_CPU_WATCHPOINTS_NUM && i < SOC_CPU_WATCHPOINTS_NUM; w++) {
if (wp_list[w] != 0) {
esp_cpu_set_watchpoint(i, (void *)wp_list[w], wp_size[w], wp_access[w]);
i++;
}
}
for (; i < SOC_CPU_BREAKPOINTS_NUM; i++) {
esp_cpu_clear_breakpoint(i);
}
#endif // CONFIG_IDF_TARGET_ARCH_XTENSA
}
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/** Write breakpoint */
static void handle_Z0_command(const unsigned char *cmd, int len)
{
cmd++; /* skip 'Z' */
cmd++; /* skip '0' */
uint32_t addr = esp_gdbstub_gethex(&cmd, -1);
if (bp_count >= GDB_BP_SIZE) {
if (bp_count >= SOC_CPU_BREAKPOINTS_NUM) {
esp_gdbstub_send_str_packet("E02");
return;
}
bool add_bp = true;
/* Check if bp already exist */
for (size_t i = 0; i < GDB_BP_SIZE; i++) {
for (size_t i = 0; i < SOC_CPU_BREAKPOINTS_NUM; i++) {
if (bp_list[i] == addr) {
add_bp = false;
break;
}
}
if (true == add_bp) {
for (size_t i = 0; i < GDB_BP_SIZE; i++) {
for (size_t i = 0; i < SOC_CPU_BREAKPOINTS_NUM; i++) {
if (bp_list[i] == 0) {
bp_list[i] = (uint32_t)addr;
bp_count++;
@ -531,7 +554,7 @@ static void handle_z0_command(const unsigned char *cmd, int len)
cmd++; /* skip 'z' */
cmd++; /* skip '0' */
uint32_t addr = esp_gdbstub_gethex(&cmd, -1);
for (size_t i = 0; i < GDB_BP_SIZE; i++) {
for (size_t i = 0; i < SOC_CPU_BREAKPOINTS_NUM; i++) {
if (bp_list[i] == addr) {
bp_list[i] = 0;
bp_count--;
@ -550,7 +573,7 @@ static void handle_Z2_command(const unsigned char *cmd, int len)
cmd++;
uint32_t size = esp_gdbstub_gethex(&cmd, -1);
if (wp_count >= GDB_WP_SIZE) {
if (wp_count >= SOC_CPU_WATCHPOINTS_NUM) {
esp_gdbstub_send_str_packet("E02");
return;
}
@ -569,7 +592,7 @@ static void handle_Z3_command(const unsigned char *cmd, int len)
uint32_t addr = esp_gdbstub_gethex(&cmd, -1);
cmd++;
uint32_t size = esp_gdbstub_gethex(&cmd, -1);
if (wp_count >= GDB_WP_SIZE) {
if (wp_count >= SOC_CPU_WATCHPOINTS_NUM) {
esp_gdbstub_send_str_packet("E02");
return;
}
@ -588,7 +611,7 @@ static void handle_Z4_command(const unsigned char *cmd, int len)
uint32_t addr = esp_gdbstub_gethex(&cmd, -1);
cmd++;
uint32_t size = esp_gdbstub_gethex(&cmd, -1);
if (wp_count >= GDB_WP_SIZE) {
if (wp_count >= SOC_CPU_WATCHPOINTS_NUM) {
esp_gdbstub_send_str_packet("E02");
return;
}
@ -605,7 +628,7 @@ static void handle_zx_command(const unsigned char *cmd, int len)
cmd++; /* skip 'z' */
cmd++; /* skip 'x' */
uint32_t addr = esp_gdbstub_gethex(&cmd, -1);
for (size_t i = 0; i < GDB_WP_SIZE; i++) {
for (size_t i = 0; i < SOC_CPU_WATCHPOINTS_NUM; i++) {
if (wp_list[i] == addr) {
wp_access[i] = 0;
wp_list[i] = 0;
@ -626,7 +649,7 @@ static void handle_S_command(const unsigned char *cmd, int len)
static void handle_s_command(const unsigned char *cmd, int len)
{
step_in_progress = true;
esp_gdbstub_do_step();
esp_gdbstub_do_step((esp_gdbstub_frame_t *)temp_regs_frame);
}
/** Step ... */
@ -888,9 +911,11 @@ static eTaskState get_task_state(size_t index)
eTaskState result = eReady;
TaskHandle_t handle = NULL;
get_task_handle(index, &handle);
#if CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
if (gdb_debug_int == false) {
result = eTaskGetState(handle);
}
#endif
return result;
}

View File

@ -15,7 +15,7 @@
#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
int esp_gdbstub_getchar()
int esp_gdbstub_getchar(void)
{
uint8_t c;
// retry the read until we succeed
@ -39,6 +39,12 @@ void esp_gdbstub_flush(void)
usb_serial_jtag_ll_txfifo_flush();
}
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
int esp_gdbstub_getfifo(void)
{
return 0; // TODO: IDF-7264
}
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
#else // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
@ -98,6 +104,7 @@ void esp_gdbstub_flush(void)
}
}
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
int esp_gdbstub_getfifo(void)
{
esp_gdbstub_uart_init();
@ -115,5 +122,5 @@ int esp_gdbstub_getfifo(void)
uart_ll_clr_intsts_mask(gdb_uart, UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT);
return doDebug;
}
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
#endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG

View File

@ -7,8 +7,12 @@
#include <string.h>
#include "esp_gdbstub.h"
#include "esp_gdbstub_common.h"
#include "esp_cpu.h"
#include "rv_decode.h"
#include "sdkconfig.h"
extern volatile esp_gdbstub_frame_t *temp_regs_frame;
static inline void init_regfile(esp_gdbstub_gdb_regfile_t *dst)
{
memset(dst, 0, sizeof(*dst));
@ -24,7 +28,7 @@ void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_
memcpy(&(dst->x[1]), &frame->ra, sizeof(uint32_t) * 31);
}
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS || CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/* Represents FreeRTOS TCB structure */
typedef struct {
@ -32,6 +36,7 @@ typedef struct {
/* Other members aren't needed */
} dummy_tcb_t;
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst)
{
@ -42,40 +47,117 @@ void esp_gdbstub_tcb_to_regfile(TaskHandle_t tcb, esp_gdbstub_gdb_regfile_t *dst
}
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS || CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
int esp_gdbstub_get_signal(const esp_gdbstub_frame_t *frame)
{
return 5; // SIGTRAP, see IDF-2490
switch (frame->mcause) {
case 0: /* Instruction address misaligned */
case 1: /* Instruction access fault */
case 2: /* Illegal instruction */
return 4; /* SIGILL */
case 3: /* Breakpoint */
return 5; /* SIGTRAP */
case 4: /* Load address misaligned */
case 5: /* Load access fault */
case 6: /* Store/AMO address misaligned */
case 7: /* Store/AMO access fault */
return 11; /* SIGSEGV */
case 8: /* Environment call from U-mode */
case 9: /* Environment call from S-mode */
// case 10: /* Reserved */
case 11: /* Environment call from M-mode */
return 5; /* SIGTRAP */
case 12: /* Instruction page fault */
case 13: /* Load page fault */
// case 14: /* Reserved */
case 15: /* Store/AMO page fault */
return 11; /* SIGSEGV */
};
return 5; /* SIGTRAP */
}
void _xt_gdbstub_int(void *frame)
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
void esp_gdbstub_int(__attribute__((unused)) void *frame)
{
/* Pointer to saved frame is in pxCurrentTCB
* See rtos_int_enter function
*/
extern void *pxCurrentTCB;
dummy_tcb_t *tcb = pxCurrentTCB;
gdbstub_handle_uart_int((esp_gdbstub_frame_t *)tcb->top_of_stack);
}
void esp_gdbstub_init_dports(void)
{
}
void esp_gdbstub_init_dports()
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
#if (!CONFIG_FREERTOS_UNICORE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
static bool stall_started = false;
#endif
void esp_gdbstub_stall_other_cpus_start(void)
{
#if (!CONFIG_FREERTOS_UNICORE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
if (stall_started == false) {
esp_ipc_isr_stall_other_cpu();
stall_started = true;
}
#endif
}
void esp_gdbstub_stall_other_cpus_start()
void esp_gdbstub_stall_other_cpus_end(void)
{
#if (!CONFIG_FREERTOS_UNICORE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
if (stall_started == true) {
esp_ipc_isr_release_other_cpu();
stall_started = false;
}
#endif
}
void esp_gdbstub_stall_other_cpus_end()
void esp_gdbstub_clear_step(void)
{
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
/* Setup triggers again because we removed them in esp_gdbstub_do_step() */
update_breakpoints();
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
}
void esp_gdbstub_clear_step()
{
}
void esp_gdbstub_do_step()
void esp_gdbstub_do_step(esp_gdbstub_frame_t *frame)
{
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
uint32_t pc = (uint32_t) frame->mepc;
uint32_t next_pc = rv_compute_next_pc(frame, pc);
esp_cpu_set_breakpoint(0, (void *) next_pc);
for (size_t i = 1; i < SOC_CPU_BREAKPOINTS_NUM; i++) {
esp_cpu_clear_breakpoint(i);
}
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
}
void esp_gdbstub_trigger_cpu(void)
{
#if !CONFIG_FREERTOS_UNICORE
if (0 == esp_cpu_get_core_id()) {
esp_crosscore_int_send_gdb_call(1);
} else {
esp_crosscore_int_send_gdb_call(0);
}
#endif
}
void esp_gdbstub_set_register(esp_gdbstub_frame_t *frame, uint32_t reg_index, uint32_t value)
{
/* RISC-V base ISA has registers x0-x31 */
if (reg_index == 0) { /* skip zero-wired register */
return;
} else if (reg_index < 32) {
(&frame->mepc)[reg_index] = value;
} else if (reg_index == 32) { /* register 32 is PC */
frame->mepc = value;
}
}

View File

@ -20,17 +20,6 @@ typedef struct {
uint32_t pc;
} esp_gdbstub_gdb_regfile_t;
// Amount of HW breakpoints used in GDB
#ifndef GDB_BP_SIZE
#define GDB_BP_SIZE 2
#endif // GDB_BP_SIZE
// Amount of HW watchpoints used in GDB
#ifndef GDB_WP_SIZE
#define GDB_WP_SIZE 2
#endif // GDB_WP_SIZE
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_gdbstub_common.h"
uintptr_t rv_compute_next_pc(esp_gdbstub_frame_t *frame, uintptr_t inst_addr);

View File

@ -0,0 +1,158 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "rv_decode.h"
static inline uint32_t rv_inst_len(uint32_t inst)
{
#ifdef __riscv_c
if ((inst & 0x3) != 0x3)
return 2; /* 16-bit instructions. */
#endif /* __riscv_c */
return 4; /* 32-bit instructions. */
}
static uint32_t rv_get_register_value(esp_gdbstub_frame_t *frame, uint32_t r_num)
{
r_num &= 0x1F;
if (r_num == 0) { /* zero-wired */
return 0;
}
return (&frame->mepc)[r_num];
}
static uint32_t rv_get_rs1_value(esp_gdbstub_frame_t *frame, uint32_t inst)
{
return rv_get_register_value(frame, inst >> 15);
}
static uint32_t rv_get_rs2_value(esp_gdbstub_frame_t *frame, uint32_t inst)
{
return rv_get_register_value(frame, inst >> 20);
}
static int32_t rv_get_branch_next_inst_offset(esp_gdbstub_frame_t *frame, uint32_t inst)
{
uint32_t funct = (inst >> 12) & 0x7;
uint32_t rs1 = rv_get_rs1_value(frame, inst);
uint32_t rs2 = rv_get_rs2_value(frame, inst);
if ((funct == 0 && rs1 == rs2) || /* beq true */
(funct == 1 && rs1 != rs2) || /* bne true */
(funct == 4 && (int32_t) rs1 < (int32_t) rs2) || /* blt true */
(funct == 5 && (int32_t) rs1 >= (int32_t) rs2) || /* bge true */
(funct == 6 && rs1 < rs2) || /* bltu true */
(funct == 7 && rs1 >= rs2)) { /* bgeu true */
return ((inst >> 8 ) & 0xF ) << 1 | /* imm[4:1] */
((inst >> 25) & 0x3F) << 5 | /* imm[10:5] */
((inst >> 7 ) & 0x1 ) << 11 | /* imm[11] */
((inst >> 31) ? 0xFFFFF000 : 0); /* imm[12] is sign part */;
}
return rv_inst_len(inst); /* branch will not jump. Next instruction will be executed */
}
static int32_t rv_get_jal_next_inst_offset(uint32_t inst)
{
return ((inst >> 21) & 0x3FF) << 1 | /* imm[10:1] */
((inst >> 20) & 0x1 ) << 11 | /* imm[11] */
((inst >> 12) & 0xFF ) << 12 | /* imm[19:12] */
((inst >> 31) ? 0xFFF00000 : 0); /* imm[20] is sign bit */
}
static uint32_t rv_get_jalr_next_inst(esp_gdbstub_frame_t *frame, uint32_t inst)
{
uint32_t rs1 = rv_get_rs1_value(frame, inst);
int32_t imm = ((inst >> 20) & 0xFFF); /* imm[11:0] */
imm |= (imm >> 11) ? 0xFFFFF000 : 0; /* imm[11] is sign bit */
return rs1 + imm;
}
#ifdef __riscv_c /* compressed riscv instruction set */
static uint32_t rv_get_c_rs1_value(esp_gdbstub_frame_t *frame, uint32_t inst)
{
return rv_get_register_value(frame, inst >> 7);
}
static uint32_t rv_get_c_rs2_num(uint32_t inst)
{
return (inst >> 2) & 0x1F;
}
static uint32_t rv_get_c_rd_num(uint32_t inst)
{
return (inst >> 7) & 0x1F;
}
static int32_t rv_get_c_jal_next_inst_offset(uint32_t inst)
{
return ((inst >> 3 ) & 0x7 ) << 1 | /* imm[3:1] */
((inst >> 11) & 0x1 ) << 4 | /* imm[4] */
((inst >> 2 ) & 0x1 ) << 5 | /* imm[5] */
((inst >> 7 ) & 0x1 ) << 6 | /* imm[6] */
((inst >> 6 ) & 0x1 ) << 7 | /* imm[7] */
((inst >> 9 ) & 0x3 ) << 8 | /* imm[9:8] */
((inst >> 8 ) & 0x1 ) << 10 | /* imm[10] */
((inst >> 12) & 0x1 ) << 11 | /* imm[11] */
((inst >> 12) & 0x1 ? 0xFFFFF000 : 0); /* imm[11] is sign part */;
}
static int32_t rv_get_c_branch_next_inst_offset(esp_gdbstub_frame_t *frame,uint32_t inst)
{
const int32_t rs1_value = (&frame->s0)[(inst >> 7) & 7];
const bool is_bnez = (inst >> 13) & 1;
if ((rs1_value == 0 && !is_bnez) ||
(rs1_value != 0 && is_bnez)) {
return ((inst >> 3 ) & 0x3 ) << 1 | /* imm[2:1] */
((inst >> 10) & 0x3 ) << 3 | /* imm[4:3] */
((inst >> 2 ) & 0x1 ) << 5 | /* imm[5] */
((inst >> 5 ) & 0x3 ) << 6 | /* imm[7:6] */
((inst >> 12) & 0x1 ) << 8 | /* imm[8] */
((inst >> 12) & 0x1 ? 0xFFFFFF00 : 0); /* imm[8] is sign part */;
}
return 2;
}
#endif /* __riscv_c */
uintptr_t rv_compute_next_pc(esp_gdbstub_frame_t *frame, uintptr_t inst_addr)
{
const uint32_t inst = *((uint32_t *) inst_addr);
const uint32_t inst_len = rv_inst_len(inst);
if (inst_len == 4) { /* this is 32-bit instruction */
switch (inst & 0x7f) {
case 0x63: /* branch */
return inst_addr + rv_get_branch_next_inst_offset(frame, inst);
case 0x6F: /* jal */
return inst_addr + rv_get_jal_next_inst_offset(inst);
case 0x67: /* jalr */
return rv_get_jalr_next_inst(frame, inst);
}
}
#ifdef __riscv_c /* compressed riscv instruction set */
const uint32_t funct3 = (inst & 0xFFFF) >> 13;
if ((inst & 3) == 1) {
switch (funct3) {
case 1: /* c.jal */
case 5: /* c.j */
return inst_addr + rv_get_c_jal_next_inst_offset(inst);
case 6: /* c.beqz */
case 7: /* c.bnez */
return inst_addr + rv_get_c_branch_next_inst_offset(frame, inst);
}
} else if ((inst & 3) == 2) {
uint32_t rs2 = rv_get_c_rs2_num(inst);
uint32_t rd = rv_get_c_rd_num(inst);
/* c.jr and c.jalr:
*
* They must have funct3 == 0b100, rd!=0 and rs2==0
* See Table 1.6: Instruction listing for RVC, Quadrant 2
* in The RISC-V Compressed Instruction Set Manual
*/
if (funct3 == 4 && rd != 0 && rs2 == 0) {
return rv_get_c_rs1_value(frame, inst);
}
}
#endif /* __riscv_c */
return inst_addr + inst_len;
}

View File

@ -9,10 +9,11 @@
.section .iram1, "ax"
.global gdbstub_handle_uart_int
.global _xt_gdbstub_int
.global esp_gdbstub_int
.type esp_gdbstub_int, @function
.align 4
_xt_gdbstub_int:
esp_gdbstub_int:
/* Allocate exception frame and save minimal context. */
mov a0, sp

View File

@ -139,7 +139,7 @@ static bool stall_started = false;
/** @brief GDB stall other CPU
* GDB stall other CPU
* */
void esp_gdbstub_stall_other_cpus_start()
void esp_gdbstub_stall_other_cpus_start(void)
{
#if CONFIG_IDF_TARGET_ARCH_XTENSA && (!CONFIG_FREERTOS_UNICORE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
if (stall_started == false) {
@ -152,7 +152,7 @@ void esp_gdbstub_stall_other_cpus_start()
/** @brief GDB end stall other CPU
* GDB end stall other CPU
* */
void esp_gdbstub_stall_other_cpus_end()
void esp_gdbstub_stall_other_cpus_end(void)
{
#if CONFIG_IDF_TARGET_ARCH_XTENSA && (!CONFIG_FREERTOS_UNICORE) && CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
if (stall_started == true) {
@ -174,7 +174,7 @@ void esp_gdbstub_clear_step(void)
/** @brief GDB do step
* GDB do one step
* */
void esp_gdbstub_do_step(void)
void esp_gdbstub_do_step( esp_gdbstub_frame_t *frame)
{
// We have gdbstub uart interrupt, and if we will call step, with ICOUNTLEVEL=2 or higher, from uart interrupt, the
// application will hang because it will try to step uart interrupt. That's why we have to set ICOUNTLEVEL=1

View File

@ -85,17 +85,6 @@ typedef struct {
} esp_gdbstub_gdb_regfile_t;
// Amount of HW breakpoints used in GDB
#ifndef GDB_BP_SIZE
#define GDB_BP_SIZE 2
#endif // GDB_BP_SIZE
// Amount of HW watchpoints used in GDB
#ifndef GDB_WP_SIZE
#define GDB_WP_SIZE 2
#endif // GDB_WP_SIZE
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,9 @@
TOPTARGETS := all clean test coverage_report
SUBDIRS := $(wildcard */.)
$(TOPTARGETS): $(SUBDIRS)
$(SUBDIRS):
$(MAKE) -C $@ $(MAKECMDGOALS)
.PHONY: $(TOPTARGETS) $(SUBDIRS)

View File

@ -0,0 +1,66 @@
TEST_PROGRAM=test_gdbstub_rv
GDBSTUB_SRC_DIR=../..
all: $(TEST_PROGRAM)
SOURCE_FILES = \
$(addprefix $(GDBSTUB_SRC_DIR)/src/, \
port/riscv/rv_decode.c \
) \
test_rv_decode.cpp \
main.cpp
INCLUDE_FLAGS = -I./include \
-I$(GDBSTUB_SRC_DIR)/private_include \
-I$(GDBSTUB_SRC_DIR)/include \
-I$(GDBSTUB_SRC_DIR)/src/port/riscv/include \
-I$(GDBSTUB_SRC_DIR)/../../tools/catch \
-I$(GDBSTUB_SRC_DIR)/../esp_hw_support/include \
-I$(GDBSTUB_SRC_DIR)/../soc/esp32c3/include \
-I$(GDBSTUB_SRC_DIR)/../esp_common/include \
-I$(GDBSTUB_SRC_DIR)/../riscv/include
CPPFLAGS += $(INCLUDE_FLAGS) -D__riscv_c -Wall -Werror -g --coverage
CFLAGS += $(INCLUDE_FLAGS) -D__riscv_c -Wall -Werror -g --coverage
LDFLAGS += -lstdc++ --coverage
ifeq ($(CC),clang)
CFLAGS += -fsanitize=address
CXXFLAGS += -fsanitize=address
LDFLAGS += -fsanitize=address
endif
OBJ_FILES = $(filter %.o, $(SOURCE_FILES:.cpp=.o) $(SOURCE_FILES:.c=.o))
COVERAGE_FILES = $(OBJ_FILES:.o=.gc*)
$(TEST_PROGRAM): $(OBJ_FILES)
$(CC) -o $@ $^ $(LDFLAGS)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
test: $(TEST_PROGRAM)
./$(TEST_PROGRAM) -d yes exclude:[long]
long-test: $(TEST_PROGRAM)
./$(TEST_PROGRAM) -d yes
$(COVERAGE_FILES): $(TEST_PROGRAM) long-test
coverage.info: $(COVERAGE_FILES)
find $(GDBSTUB_SRC_DIR)/src/ -name "*.gcno" -exec gcov -r -pb {} +
lcov --capture --directory $(GDBSTUB_SRC_DIR)/src --output-file coverage.info
coverage_report: coverage.info
genhtml coverage.info --output-directory coverage_report
@echo "Coverage report is in coverage_report/index.html"
clean-coverage:
rm -f $(COVERAGE_FILES) *.gcov
rm -rf coverage_report/
rm -f coverage.info
clean: clean-coverage
rm -f $(OBJ_FILES) $(TEST_PROGRAM)
.PHONY: clean clean-coverage all test long-test

View File

@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME

View File

@ -0,0 +1,7 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

View File

@ -0,0 +1,338 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "catch.hpp"
extern "C" {
#include "esp_gdbstub_common.h"
#include "rv_decode.h"
esp_gdbstub_frame_t regs;
esp_gdbstub_frame_t *temp_regs_frame = &regs;
} // extern "C"
#if 0
#define DEBUG_PRINTF(...) printf(__VA_ARGS__)
#else
#define DEBUG_PRINTF(...)
#endif
struct inst_list_s {
uint32_t inst;
const char *name;
};
struct inst_list_s rv32i_nojump[] = {
{0x31011d37, "lui"},
{0x00000917, "auipc"},
{0x0b1c0e03, "lb"},
{0xc8aecc03, "lbu"},
{0x07271103, "lh"},
{0x02525403, "lhu"},
{0x00392903, "lw"},
{0x000000a3, "sb"},
{0x00009da3, "sh"},
{0x0000a423, "sw"},
{0xd3060d13, "addi"},
{0x000cad13, "slti"},
{0x00003d13, "sltiu"},
{0x0001ce13, "xori"},
{0x00006013, "ori"},
{0x00007c13, "andi"},
{0x00099913, "slli"},
{0x00005e13, "srli"},
{0x4107d313, "srai"},
{0x0000fe33, "and"},
{0x401e0933, "sub"},
{0x000012b3, "sll"},
{0x0036a033, "slt"},
{0x00853433, "sltu"},
{0x00004033, "xor"},
{0x00005133, "srl"},
{0x40e7d7b3, "sra"},
{0x000062b3, "or"},
{0x000071b3, "and"},
{0x0000000f, "fence"},
{0x0000100f, "fence.i"},
{0x00002ef3, "csrr"},
{0x010312f3, "csrrw"},
{0x00005473, "csrrwi"},
{0x0001b073, "csrc"},
{0x01007073, "csrci"},
{0x000078f3, "csrrci"},
{0x0002a073, "csrs"},
{0x0000e073, "csrsi"},
{0x000225f3, "csrrs"}};
struct inst_list_s rv32m_nojump[] = {
{0x025a0133, "mul"},
{0x02031233, "mulh"},
{0x02f437b3, "mulhu"},
{0x03012e33, "mulhsu"},
{0x02a6c6b3, "div"},
{0x02f5d533, "divu"},
{0x02a47433, "remu"},
{0x0324e4b3, "rem"}};
struct inst_list_s rv32a_nojump[] = {
{0x100523af, "lr.w"},
{0x19d2ae2f, "sc.w"},
{0x0c07202f, "amoswap.w"},
{0x0000222f, "amoadd.w"},
{0x2072a2af, "amoxor.w"},
{0x6072a2af, "amoand.w"},
{0x4072a2af, "amoor.w"},
{0x801c262f, "amomin.w"}};
struct inst_list_s rv32c_nojump[] = {
{0x8522, "c.mv"},
{0x4200, "c.lw"},
{0x4501, "c.li"},
{0x6605, "c.lui"},
{0x8e65, "c.and"},
{0x985d, "c.andi"},
{0x8c9d, "c.sub"},
{0x8f41, "c.or"},
{0x8da5, "c.xor"},
{0x100a, "c.slli"},
{0x0002, "c.slli64"},
{0x8011, "c.srli"},
{0x8081, "c.srli64"},
{0x9459, "c.srai"},
{0x8481, "c.srai64"}};
TEST_CASE("decode rv32i instructions")
{
uintptr_t pc;
uint32_t inst;
uintptr_t inst_addr = (uintptr_t)&inst;
for (size_t i = 0; i < sizeof(rv32i_nojump)/sizeof(rv32i_nojump[0]); i++) {
inst = rv32i_nojump[i].inst;
DEBUG_PRINTF("testing instruction %s\n", rv32i_nojump[i].name);
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
}
/* beq positive offset */
/* 420147bc: 0af50b63 beq a0,a5,42014872 */
regs.a0 = regs.a5 = 0x777;
inst = 0x0af50b63;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x42014872 - 0x420147bc));
regs.a0 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
/* beq negative offset */
/* 40383b48: fae789e3 beq a5,a4,40383afa */
regs.a4 = regs.a5 = 0x777;
inst = 0xfae789e3;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x40383afa - 0x40383b48));
regs.a4 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
/* bne positive offset */
/* 42014798: 10f51163 bne a0,a5,4201489a */
regs.a0 = regs.a5 = 0x777;
inst = 0x10f51163;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a0 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4201489a - 0x42014798));
/* bne negative offset */
/* 4200b7e0: fed711e3 bne a4,a3,4200b7c2 */
regs.a3 = regs.a4 = 0x777;
inst = 0xfed711e3;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a3 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200b7c2 - 0x4200b7e0));
/* blt positive offset */
/* 420117a0: 04f54763 blt a0,a5,420117ee */
regs.a0 = regs.a5 = 0x777;
inst = 0x04f54763;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a0++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a0 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x420117ee - 0x420117a0));
/* blt negative offset */
/* 42014448: fcd5c0e3 blt a1,a3,42014408 */
regs.a1 = regs.a3 = 0x777;
inst = 0xfcd5c0e3;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a1++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a1 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x42014408 - 0x42014448));
/* bltu positive offset */
/* 42010dfa: 00f56a63 bltu a0,a5,42010e0e */
regs.a0 = regs.a5 = 0x777;
inst = 0x00f56a63;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a0++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.a0 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x42010e0e - 0x42010dfa));
/* bltu negative offset */
/* 4200137c: f79be5e3 bltu s7,s9,420012e6 */
regs.s7 = regs.s9 = 0x777;
inst = 0xf79be5e3;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.s7++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
regs.s7 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x420012e6 - 0x4200137c));
/* bge positive offset */
/* 4200cf88: 02f55763 bge a0,a5,4200cfb6 */
regs.a0 = regs.a5 = 0x777;
inst = 0x02f55763;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200cfb6 - 0x4200cf88));
regs.a0++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200cfb6 - 0x4200cf88));
regs.a0 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
/* bge negative offset */
/* 420001bc: fe87d6e3 bge a5,s0,420001a8 */
regs.a5 = regs.s0 = 0x777;
inst = 0xfe87d6e3;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x420001a8 - 0x420001bc));
regs.a5++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x420001a8 - 0x420001bc));
regs.a5 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
/* bgeu positive offset */
/* 40383552: 02f57d63 bgeu a0,a5,4038358c */
regs.a0 = regs.a5 = 0x777;
inst = 0x02f57d63;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4038358c - 0x40383552));
regs.a0++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4038358c - 0x40383552));
regs.a0 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
/* bgeu negative offset */
/* 42001178: fae7fbe3 bgeu a5,a4,4200112e */
regs.a5 = regs.a4 = 0x777;
inst = 0xfae7fbe3;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200112e - 0x42001178));
regs.a5++;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200112e - 0x42001178));
regs.a5 = 0x111;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
/* jal positive offset */
/* 4200f872: 016010ef jal ra,42010888 */
inst = 0x016010ef;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x42010888 - 0x4200f872));
/* jal negative offset */
/* 42015c94: e31ff0ef jal ra,42015ac4 */
inst = 0xe31ff0ef;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x42015ac4 - 0x42015c94));
/* jalr positive offset */
/* 4200fee0: 47c080e7 jalr 1148(ra) # 40000358 */
regs.ra = 0x40000358 - 1148;
inst = 0x47c080e7;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == 0x40000358);
/* jalr negative offset */
/* 4038779e: 8a6080e7 jalr -1882(ra) # 40000040 */
regs.ra = 0x40000040 - (-1882);
inst = 0x8a6080e7;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == 0x40000040);
}
TEST_CASE("decode rv32c instructions")
{
uintptr_t pc;
uint32_t inst;
uintptr_t inst_addr = (uintptr_t)&inst;
for (size_t i = 0; i < sizeof(rv32c_nojump)/sizeof(rv32c_nojump[0]); i++) {
inst = rv32c_nojump[i].inst;
DEBUG_PRINTF("testing instruction %s\n", rv32i_nojump[i].name);
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 2);
}
/* beqz positive offset */
/* 4200db50: c119 beqz a0,4200db56 */
regs.a0 = 0;
inst = 0xc119;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200db56 - 0x4200db50));
regs.a0 = 0xff;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 2);
/* beqz negative offset */
/* 42014886: d96d beqz a0,42014878 */
regs.a0 = 0;
inst = 0xd96d;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x42014878 - 0x42014886));
regs.a0 = 0xff;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 2);
/* bnez positive offset */
/* 4200ee80: e165 bnez a0,4200ef60 */
regs.a0 = 0xff;
inst = 0xe165;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200ef60 - 0x4200ee80));
regs.a0 = 0;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 2);
/* bnez negative offset */
/* 4200f472: ff29 bnez a4,4200f3cc */
regs.a4 = 0xff;
inst = 0xff29;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200f3cc - 0x4200f472));
regs.a4 = 0;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 2);
/* jal positive offset */
/* 4200ffb8: 2d5d jal 4201066e */
inst = 0x2d5d;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4201066e - 0x4200ffb8));
/* jal negative offset */
/* 42005588: 24cd jal 4200586a */
inst = 0x24cd;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + (0x4200586a - 0x42005588));
/* 42010366: 9782 jalr a5 */
regs.a5 = 0x88888888;
inst = 0x9782;
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == 0x88888888);
}
TEST_CASE("decode rv32a instructions")
{
uintptr_t pc;
uint32_t inst;
uintptr_t inst_addr = (uintptr_t)&inst;
for (size_t i = 0; i < sizeof(rv32a_nojump)/sizeof(rv32a_nojump[0]); i++) {
inst = rv32i_nojump[i].inst;
DEBUG_PRINTF("testing instruction %s\n", rv32i_nojump[i].name);
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
}
}
TEST_CASE("decode rv32m instructions")
{
uintptr_t pc;
uint32_t inst;
uintptr_t inst_addr = (uintptr_t)&inst;
for (size_t i = 0; i < sizeof(rv32m_nojump)/sizeof(rv32m_nojump[0]); i++) {
inst = rv32i_nojump[i].inst;
DEBUG_PRINTF("testing instruction %s\n", rv32i_nojump[i].name);
CHECK(rv_compute_next_pc(temp_regs_frame, inst_addr) == inst_addr + 4);
}
}

View File

@ -137,10 +137,6 @@ extern "C" {
Trigger Module register fields (Debug specification)
********************************************************/
/* tcontrol CSRs not recognized by toolchain currently */
#define CSR_TCONTROL 0x7a5
#define CSR_TDATA1 0x7a1
#define TCONTROL_MTE (1<<3) /*R/W, Current M mode trigger enable bit*/
#define TCONTROL_MPTE (1<<7) /*R/W, Previous M mode trigger enable bit*/
@ -152,6 +148,7 @@ extern "C" {
#define TDATA1_MATCH (1<<7)
#define TDATA1_MATCH_V (0xF) /*R/W,Address match type :0 : Exact byte match 1 : NAPOT range match */
#define TDATA1_MATCH_S (7)
#define TDATA1_HIT_S (20)
/* RISC-V CSR macros

View File

@ -132,49 +132,66 @@ FORCE_INLINE_ATTR void rv_utils_set_breakpoint(int bp_num, uint32_t bp_addr)
/* The code bellow sets breakpoint which will trigger `Breakpoint` exception
* instead transfering control to debugger. */
RV_WRITE_CSR(tselect, bp_num);
RV_SET_CSR(CSR_TCONTROL, TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER | TDATA1_MACHINE | TDATA1_EXECUTE);
RV_WRITE_CSR(tcontrol, TCONTROL_MPTE | TCONTROL_MTE);
RV_WRITE_CSR(tdata1, TDATA1_USER | TDATA1_MACHINE | TDATA1_EXECUTE);
RV_WRITE_CSR(tdata2, bp_addr);
}
FORCE_INLINE_ATTR void rv_utils_set_watchpoint(int wp_num,
uint32_t wp_addr,
size_t size,
bool on_read,
bool on_write)
{
RV_WRITE_CSR(tselect, wp_num);
RV_WRITE_CSR(tcontrol, TCONTROL_MPTE | TCONTROL_MTE);
RV_WRITE_CSR(tdata1, TDATA1_USER |
TDATA1_MACHINE |
TDATA1_MATCH |
(on_read ? TDATA1_LOAD : 0) |
(on_write ? TDATA1_STORE : 0));
/* From RISC-V Debug Specification:
* NAPOT (Naturally Aligned Power-Of-Two):
* Matches when the top M bits of any compare value match the top M bits of tdata2.
* M is XLEN 1 minus the index of the least-significant bit containing 0 in tdata2.
*
* Note: Expectng that size is number power of 2
*
* Examples for understanding how to calculate NAPOT:
*
* nnnn...nnnn0 2-byte NAPOT range
* nnnn...nnn01 4-byte NAPOT range
* nnnn...nn011 8-byte NAPOT range
* nnnn...n0111 16-byte NAPOT range
* nnnn...01111 32-byte NAPOT range
* * where n are bits from original address
*/
const uint32_t half_size = size >> 1;
uint32_t napot = wp_addr;
napot &= ~half_size; /* set the least-significant bit with zero */
napot |= half_size - 1; /* fill all bits with ones after least-significant bit */
RV_WRITE_CSR(tdata2, napot);
}
FORCE_INLINE_ATTR void rv_utils_clear_breakpoint(int bp_num)
{
RV_WRITE_CSR(tselect, bp_num);
RV_CLEAR_CSR(CSR_TCONTROL, TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER | TDATA1_MACHINE | TDATA1_EXECUTE);
}
FORCE_INLINE_ATTR void rv_utils_set_watchpoint(int wp_num,
uint32_t wp_addr,
size_t size,
bool on_read,
bool on_write)
{
RV_WRITE_CSR(tselect, wp_num);
RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER | TDATA1_MACHINE);
RV_SET_CSR_FIELD(CSR_TDATA1, (long unsigned int) TDATA1_MATCH, 1);
// add 0 in napot encoding
uint32_t addr_napot;
addr_napot = ((uint32_t) wp_addr) | ((size >> 1) - 1);
if (on_read) {
RV_SET_CSR(CSR_TDATA1, TDATA1_LOAD);
}
if (on_write) {
RV_SET_CSR(CSR_TDATA1, TDATA1_STORE);
}
RV_WRITE_CSR(tdata2, addr_napot);
/* tdata1 is a WARL(write any read legal) register
* We can just write 0 to it
*/
RV_WRITE_CSR(tdata1, 0);
}
FORCE_INLINE_ATTR void rv_utils_clear_watchpoint(int wp_num)
{
RV_WRITE_CSR(tselect, wp_num);
RV_CLEAR_CSR(CSR_TCONTROL, TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER | TDATA1_MACHINE);
RV_CLEAR_CSR_FIELD(CSR_TDATA1, (long unsigned int) TDATA1_MATCH);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_MACHINE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_LOAD | TDATA1_STORE | TDATA1_EXECUTE);
/* riscv have the same registers for breakpoints and watchpoints */
rv_utils_clear_breakpoint(wp_num);
}
FORCE_INLINE_ATTR bool rv_utils_is_trigger_fired(int id)
{
RV_WRITE_CSR(tselect, id);
return (RV_READ_CSR(tdata1) >> TDATA1_HIT_S) & 1;
}
// ---------------------- Debugger -------------------------

View File

@ -102,6 +102,9 @@
.global rtos_int_enter
.global rtos_int_exit
.global _global_interrupt_handler
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
.global gdbstub_handle_debug_int
#endif
.section .exception_vectors.text
/* This is the vector table. MTVEC points here.
@ -170,16 +173,27 @@ _panic_handler:
* have an pseudo excause */
mv a0, sp
csrr a1, mcause
/* Branches instructions don't accept immediates values, so use t1 to
* store our comparator */
li t0, 0x80000000
bgeu a1, t0, _call_panic_handler
sw a1, RV_STK_MCAUSE(sp)
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
li t0, 3
beq a1, t0, _call_gdbstub_handler
#endif
/* exception_from_panic never returns */
jal panic_from_exception
/* We arrive here if the exception handler has returned. */
j _return_from_exception
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
_call_gdbstub_handler:
call gdbstub_handle_debug_int
j _return_from_exception
#endif
_call_panic_handler:
/* Remove highest bit from mcause (a1) register and save it in the
* structure */

View File

@ -60,12 +60,6 @@ examples/system/gcov:
temporary: true
reason: lack of runners
examples/system/gdbstub:
disable:
- if: IDF_TARGET == "esp32c2" or IDF_TARGET == "esp32h2"
temporary: true
reason: target esp32c2, esp32h2 is not supported yet
examples/system/heap_task_tracking:
disable:
- if: IDF_TARGET == "esp32c2" or IDF_TARGET == "esp32h2"

View File

@ -1,5 +1,5 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# GDBstub example
@ -14,7 +14,9 @@ Upon exit from GDB, the application will continue to work in IDF Monitor as befo
The 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.
GDBStub is supported for all ESP chips.
NOTE: On chips with an integrated USB Serial/JTAG Controller, it is reasonable to use OpenOCD + GDB for debugging.
### Configure the project

View File

@ -130,6 +130,12 @@ tools/test_apps/system/gdb_loadable_elf:
temporary: true
reason: target esp32c6, esp32h2 is not supported yet
tools/test_apps/system/gdbstub_runtime:
disable_test:
- if: IDF_TARGET in ["esp32c2", "esp32h2"]
temporary: true
reason: resolve IDF-7264
tools/test_apps/system/longjmp_test:
enable:
- if: IDF_TARGET in ["esp32", "esp32s2", "esp32s3"]

View File

@ -0,0 +1,8 @@
# 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.16)
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(test_gdbstub_runtime)

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |

View File

@ -0,0 +1,26 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name
import os
import sys
import pytest
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
sys.path.append(os.path.expandvars(os.path.join('$IDF_PATH', 'tools', 'test_apps', 'system', 'panic')))
from test_panic_util import PanicTestDut # noqa: E402
@pytest.fixture(scope='module')
def monkeypatch_module(request: FixtureRequest) -> MonkeyPatch:
mp = MonkeyPatch()
request.addfinalizer(mp.undo)
return mp
@pytest.fixture(scope='module', autouse=True)
def replace_dut_class(monkeypatch_module: MonkeyPatch) -> None:
monkeypatch_module.setattr('pytest_embedded_idf.IdfDut', PanicTestDut)

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "test_app_main.c"
INCLUDE_DIRS ""
REQUIRES esp_gdbstub)

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
int var_1;
int var_2;
void foo(void)
{
var_2++;
}
void app_main(void)
{
printf("tested app is runnig.\n");
while(1) {
var_1++;
if (var_1 % 10 == 0) {
foo();
}
}
}

View File

@ -0,0 +1,114 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os
import sys
import pytest
sys.path.append(os.path.expandvars(os.path.join('$IDF_PATH', 'tools', 'test_apps', 'system', 'panic')))
from test_panic_util import PanicTestDut # noqa: E402
@pytest.mark.supported_targets
@pytest.mark.temp_skip_ci(targets=['esp32c2', 'esp32h2'], reason='resolve IDF-7264')
@pytest.mark.generic
def test_gdbstub_runtime(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 --line 23'
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
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['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):
# does not stoped 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['stopped-threads'] == 'all'
# Test finish command
cmd = '-exec-finish'
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
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'
assert payload['frame']['func'] == 'app_main'
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):
# does not stoped 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']['func'] == 'app_main'
assert payload['stopped-threads'] == 'all'
# test delete breakpoint
cmd = '-break-delete 1'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) is not None
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 ctrl-c
responses = dut.gdbmi.send_signal_to_gdb(2)
# assert dut.find_gdb_response('stopped', 'notify', responses) is not None
# ?? No response? check we stopped
dut.gdb_backtrace()
# test watchpoint
cmd = '-break-watch var_2'
responses = dut.gdb_write(cmd)
assert dut.find_gdb_response('done', 'result', responses) 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
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['frame']['func'] == 'foo'
assert payload['stopped-threads'] == 'all'
# Uncomment this when implement send reason to gdb: GCC-313
#
# assert payload['reason'] == 'watchpoint-trigger'
# assert int(payload['value']['new']) == int(payload['value']['old']) + 1
# assert payload['frame']['line'] == '14'

View File

@ -0,0 +1,2 @@
CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME=y
CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y

View File

@ -160,20 +160,21 @@ class PanicTestDut(IdfDut):
Runs GDB and connects it to the "serial" port of the DUT.
After this, the DUT expect methods can no longer be used to capture output.
"""
gdb_args = ['--nx', '--quiet', '--interpreter=mi2']
if self.is_xtensa:
gdb_path = f'xtensa-{self.target}-elf-gdb'
gdb_path = 'xtensa-esp-elf-gdb-no-python' # TODO: GCC-311
gdb_args = [f'--mcpu={self.target}'] + gdb_args
else:
gdb_path = 'riscv32-esp-elf-gdb'
gdb_path = 'riscv32-esp-elf-gdb-no-python' # TODO: GCC-311
try:
from pygdbmi.constants import GdbTimeoutError
default_gdb_args = ['--nx', '--quiet', '--interpreter=mi2']
gdb_command = [gdb_path] + default_gdb_args
gdb_command = [gdb_path] + gdb_args
self.gdbmi = GdbController(command=gdb_command)
pygdbmi_logger = attach_logger()
except ImportError:
# fallback for pygdbmi<0.10.0.0.
from pygdbmi.gdbcontroller import GdbTimeoutError
self.gdbmi = GdbController(gdb_path=gdb_path)
self.gdbmi = GdbController(gdb_path=gdb_path, gdb_args=gdb_args)
pygdbmi_logger = self.gdbmi.logger
# pygdbmi logs to console by default, make it log to a file instead