Merge branch 'bugfix/interrupted_thread_gdb_bt' into 'master'

riscv: Fixes GDB backtrace of interrupted threads

See merge request espressif/esp-idf!16939
This commit is contained in:
Ivan Grokhotkov 2022-02-24 20:21:17 +00:00
commit 4a553510b7
8 changed files with 242 additions and 26 deletions

View File

@ -27,8 +27,6 @@ typedef struct {
esp_apptrace_mem_block_t * mem_blocks;
} esp_apptrace_riscv_ctrl_block_t;
#define RISCV_APPTRACE_SYSNR 0x64
#define ESP_APPTRACE_RISCV_BLOCK_LEN_MSK 0x7FFFUL
#define ESP_APPTRACE_RISCV_BLOCK_LEN(_l_) ((_l_) & ESP_APPTRACE_RISCV_BLOCK_LEN_MSK)
#define ESP_APPTRACE_RISCV_BLOCK_LEN_GET(_v_) ((_v_) & ESP_APPTRACE_RISCV_BLOCK_LEN_MSK)
@ -104,7 +102,7 @@ __attribute__((weak)) int esp_apptrace_advertise_ctrl_block(void *ctrl_block_add
if (!esp_cpu_in_ocd_debug_mode()) {
return 0;
}
return (int) semihosting_call_noerrno(RISCV_APPTRACE_SYSNR, (long*)ctrl_block_addr);
return (int) semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_APPTRACE_INIT, (long*)ctrl_block_addr);
}
/* Returns up buffers config.

View File

@ -14,7 +14,6 @@
const static char *TAG = "esp_dbg_stubs";
#define RISCV_DBG_STUBS_SYSNR 0x65
/* Advertises apptrace control block address to host */
static int esp_dbg_stubs_advertise_table(void *stub_table_addr)
@ -22,7 +21,7 @@ static int esp_dbg_stubs_advertise_table(void *stub_table_addr)
if (!esp_cpu_in_ocd_debug_mode()) {
return 0;
}
return (int) semihosting_call_noerrno(RISCV_DBG_STUBS_SYSNR, (long*)stub_table_addr);
return (int) semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_DBG_STUBS_INIT, (long*)stub_table_addr);
}
void esp_dbg_stubs_ll_init(void *stub_table_addr)

View File

@ -115,7 +115,7 @@ void vPortEndScheduler(void)
// ------------------------ Stack --------------------------
static void prvTaskExitError(void)
__attribute__((noreturn)) static void _prvTaskExitError(void)
{
/* A function that implements a task must not exit or attempt to return to
its caller as there is nothing to return to. If a task wants to exit it
@ -128,6 +128,18 @@ static void prvTaskExitError(void)
abort();
}
__attribute__((naked)) static void prvTaskExitError(void)
{
asm volatile(".option push\n" \
".option norvc\n" \
"nop\n" \
".option pop");
/* Task entry's RA will point here. Shifting RA into prvTaskExitError is necessary
to make GDB backtrace ending inside that function.
Otherwise backtrace will end in the function laying just before prvTaskExitError in address space. */
_prvTaskExitError();
}
StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
{
extern uint32_t __global_pointer$;
@ -190,7 +202,9 @@ StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxC
sp -= RV_STK_FRMSZ;
RvExcFrame *frame = (RvExcFrame *)sp;
memset(frame, 0, sizeof(*frame));
frame->ra = (UBaseType_t)prvTaskExitError;
/* Shifting RA into prvTaskExitError is necessary to make GDB backtrace ending inside that function.
Otherwise backtrace will end in the function laying just before prvTaskExitError in address space. */
frame->ra = (UBaseType_t)prvTaskExitError + 4/*size of the nop insruction at the beginning of prvTaskExitError*/;
frame->mepc = (UBaseType_t)pxCode;
frame->a0 = (UBaseType_t)pvParameters;
frame->gp = (UBaseType_t)&__global_pointer$;

View File

@ -14,6 +14,7 @@
#include "soc/assist_debug_reg.h"
#include "esp_attr.h"
#include "riscv/csr.h"
#include "riscv/semihosting.h"
/*performance counter*/
#define CSR_PCER_MACHINE 0x7e0
@ -71,8 +72,29 @@ static inline void cpu_ll_init_hwloop(void)
// Nothing needed here for ESP32-C3
}
FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
{
return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
}
static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
{
if (cpu_ll_is_debugger_attached()) {
/* If we want to set breakpoint which when hit transfers control to debugger
* we need to set `action` in `mcontrol` to 1 (Enter Debug Mode).
* That `action` value is supported only when `dmode` of `tdata1` is set.
* But `dmode` can be modified by debugger only (from Debug Mode).
*
* So when debugger is connected we use special syscall to ask it to set breakpoint for us.
*/
long args[] = {true, id, (long)pc};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
if (ret == 0) {
return;
}
}
/* The code bellow sets breakpoint which will trigger `Breakpoint` exception
* instead transfering control to debugger. */
RV_WRITE_CSR(tselect,id);
RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@ -82,6 +104,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
static inline void cpu_ll_clear_breakpoint(int id)
{
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {false, id};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
if (ret == 0){
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@ -105,6 +135,17 @@ static inline void cpu_ll_set_watchpoint(int id,
bool on_write)
{
uint32_t addr_napot;
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {true, id, (long)addr, (long)size,
(long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
if (ret == 0) {
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@ -123,6 +164,14 @@ static inline void cpu_ll_set_watchpoint(int id,
static inline void cpu_ll_clear_watchpoint(int id)
{
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {false, id};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
if (ret == 0){
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@ -132,11 +181,6 @@ static inline void cpu_ll_clear_watchpoint(int id)
return;
}
FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
{
return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
}
static inline void cpu_ll_break(void)
{
asm volatile("ebreak\n");

View File

@ -14,6 +14,7 @@
#include "soc/assist_debug_reg.h"
#include "esp_attr.h"
#include "riscv/csr.h"
#include "riscv/semihosting.h"
/*performance counter*/
#define CSR_PCER_MACHINE 0x7e0
@ -71,8 +72,29 @@ static inline void cpu_ll_init_hwloop(void)
// Nothing needed here for ESP32-C3
}
FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
{
return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
}
static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
{
if (cpu_ll_is_debugger_attached()) {
/* If we want to set breakpoint which when hit transfers control to debugger
* we need to set `action` in `mcontrol` to 1 (Enter Debug Mode).
* That `action` value is supported only when `dmode` of `tdata1` is set.
* But `dmode` can be modified by debugger only (from Debug Mode).
*
* So when debugger is connected we use special syscall to ask it to set breakpoint for us.
*/
long args[] = {true, id, (long)pc};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
if (ret == 0) {
return;
}
}
/* The code bellow sets breakpoint which will trigger `Breakpoint` exception
* instead transfering control to debugger. */
RV_WRITE_CSR(tselect,id);
RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@ -82,6 +104,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
static inline void cpu_ll_clear_breakpoint(int id)
{
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {false, id};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
if (ret == 0){
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@ -105,6 +135,17 @@ static inline void cpu_ll_set_watchpoint(int id,
bool on_write)
{
uint32_t addr_napot;
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {true, id, (long)addr, (long)size,
(long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
if (ret == 0) {
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@ -123,6 +164,14 @@ static inline void cpu_ll_set_watchpoint(int id,
static inline void cpu_ll_clear_watchpoint(int id)
{
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {false, id};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
if (ret == 0){
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@ -132,11 +181,6 @@ static inline void cpu_ll_clear_watchpoint(int id)
return;
}
FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
{
return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
}
static inline void cpu_ll_break(void)
{
asm volatile("ebreak\n");

View File

@ -12,6 +12,7 @@
#include "soc/assist_debug_reg.h"
#include "esp_attr.h"
#include "riscv/csr.h"
#include "riscv/semihosting.h"
/*performance counter*/
#define CSR_PCER_MACHINE 0x7e0
@ -69,8 +70,29 @@ static inline void cpu_ll_init_hwloop(void)
// Nothing needed here for ESP32-H2
}
FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
{
return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
}
static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
{
if (cpu_ll_is_debugger_attached()) {
/* If we want to set breakpoint which when hit transfers control to debugger
* we need to set `action` in `mcontrol` to 1 (Enter Debug Mode).
* That `action` value is supported only when `dmode` of `tdata1` is set.
* But `dmode` can be modified by debugger only (from Debug Mode).
*
* So when debugger is connected we use special syscall to ask it to set breakpoint for us.
*/
long args[] = {true, id, (long)pc};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
if (ret == 0) {
return;
}
}
/* The code bellow sets breakpoint which will trigger `Breakpoint` exception
* instead transfering control to debugger. */
RV_WRITE_CSR(tselect,id);
RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@ -80,6 +102,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc)
static inline void cpu_ll_clear_breakpoint(int id)
{
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {false, id};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args);
if (ret == 0){
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE);
@ -103,6 +133,17 @@ static inline void cpu_ll_set_watchpoint(int id,
bool on_write)
{
uint32_t addr_napot;
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {true, id, (long)addr, (long)size,
(long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
if (ret == 0) {
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE);
RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@ -121,6 +162,14 @@ static inline void cpu_ll_set_watchpoint(int id,
static inline void cpu_ll_clear_watchpoint(int id)
{
if (cpu_ll_is_debugger_attached()) {
/* see description in cpu_ll_set_breakpoint() */
long args[] = {false, id};
int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args);
if (ret == 0){
return;
}
}
RV_WRITE_CSR(tselect,id);
RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE);
RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE);
@ -130,11 +179,6 @@ static inline void cpu_ll_clear_watchpoint(int id)
return;
}
FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void)
{
return REG_GET_BIT(ASSIST_DEBUG_CORE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE);
}
static inline void cpu_ll_break(void)
{
asm volatile("ebreak\n");

View File

@ -10,6 +10,53 @@
extern "C" {
#endif
/* ESP custom semihosting calls numbers */
/**
* @brief Initialize apptrace data at host side
*
* @param addr address of apptrace control data block
* @return return 0 on sucess or non-zero error code
*/
#define ESP_SEMIHOSTING_SYS_APPTRACE_INIT 0x64
/**
* @brief Initialize debug stubs table at host side
*
* @param addr address of debug stubs table
* @return return 0 on sucess or non-zero error code
*/
#define ESP_SEMIHOSTING_SYS_DBG_STUBS_INIT 0x65
/**
* @brief Set/clear breakpoint
*
* @param set if true set breakpoint, otherwise clear it
* @param id breakpoint ID
* @param addr address to set breakpoint at. Ignored if `set` is false.
* @return return 0 on sucess or non-zero error code
*/
#define ESP_SEMIHOSTING_SYS_BREAKPOINT_SET 0x66
/**
* @brief Set/clear watchpoint
*
* @param set if true set watchpoint, otherwise clear it
* @param id watchpoint ID
* @param addr address to set watchpoint at. Ignored if `set` is false.
* @param size size of watchpoint. Ignored if `set` is false.
* @param flags watchpoint flags, see description below. Ignored if `set` is false.
* @return return 0 on sucess or non-zero error code
*/
#define ESP_SEMIHOSTING_SYS_WATCHPOINT_SET 0x67
/* bit values for `flags` argument of ESP_SEMIHOSTING_SYS_WATCHPOINT_SET call. Can be ORed. */
/* watch for 'reads' at `addr` */
#define ESP_SEMIHOSTING_WP_FLG_RD (1UL << 0)
/* watch for 'writes' at `addr` */
#define ESP_SEMIHOSTING_WP_FLG_WR (1UL << 1)
/**
* @brief Perform semihosting call
*

View File

@ -176,16 +176,31 @@ _panic_handler:
bgeu a1, t0, _call_panic_handler
sw a1, RV_STK_MCAUSE(sp)
/* exception_from_panic never returns */
j panic_from_exception
jal panic_from_exception
/* We arrive here if the exception handler has returned. */
j _return_from_exception
_call_panic_handler:
/* Remove highest bit from mcause (a1) register and save it in the
* structure */
not t0, t0
and a1, a1, t0
sw a1, RV_STK_MCAUSE(sp)
/* exception_from_isr never returns */
j panic_from_isr
.size panic_from_isr, .-panic_from_isr
jal panic_from_isr
/* We arrive here if the exception handler has returned. This means that
* the exception was handled, and the execution flow should resume.
* Restore the registers and return from the exception.
*/
_return_from_exception:
restore_mepc
/* MTVEC and SP are assumed to be unmodified.
* MSTATUS, MHARTID, MTVAL are read-only and not restored.
*/
lw gp, RV_STK_GP(sp)
restore_general_regs RV_STK_FRMSZ
mret
.size _panic_handler, .-_panic_handler
/* This is the interrupt handler.
* It saves the registers on the stack,
@ -201,6 +216,17 @@ _interrupt_handler:
save_general_regs
save_mepc
/* Though it is not necessary we save GP and SP here.
* SP is necessary to help GDB to properly unwind
* the backtrace of threads preempted by interrupts (OS tick etc.).
* GP is saved just to have its proper value in GDB. */
/* As gp register is not saved by the macro, save it here */
sw gp, RV_STK_GP(sp)
/* Same goes for the SP value before trapping */
addi t0, sp, CONTEXT_SIZE /* restore sp with the value when interrupt happened */
/* Save SP */
sw t0, RV_STK_SP(sp)
/* Before doing anythig preserve the stack pointer */
/* It will be saved in current TCB, if needed */
mv a0, sp