OpenOCD高级调试技巧-修改源码在TARGET为RISCV时支持FreeRTOS调试
一. 前言
前文《GDB高级调试技巧-FreeRTOS内核信息显示》以手动的方式进行FreeRTOS的内核信息的打印。实际上OpenOCD是支持RTOS调试的,target支持-rtos选项,支持FreeRTOS等RTOS,但是不是所有的Target都支持,比如对于FreeRTOS就只支持cortex_m和hla_target的target,不支持RISCV。我们就来修改OpenOCD的源码,使其在target为RISCV时支持FreeRTOS。其中OpenOCD开发环境搭建参考《STEP BY STEP设计一个RISC-V仿真器之一:OpenOCD开发环境搭建》
二. OpenOCD对RTOS相关的配置与原理
2.1 配置
1) OpenOCD的配置
OpenOCD支持RTOS, 默认是未使能的,需要在target配置-rtos参数,即-rtos rtos_type。
rtos_type 可以为auto, none, eCos, ThreadX, FreeRTOS, linux, ChibiOS, embKernel, mqx, uCOS-III, nuttx, RIOT, Zephyr, rtkernel。
还有一个特殊的hwthread 这个实际不是RTOS. 在SMP系统中(硬件线程),OpenOCD 将其视为伪 RTOS 。GDB就可以将CPU cores (“hardware threads”) 作为一个线程对待,可以使用info threads 查看状态,thread命令可用于切换core,step 和stepi命令可针对某个core。
配置实例
$_TARGETNAME configure -rtos FreeRTOS或者$_TARGETNAME configure -rtos auto
其中_TARGETNAME是前面创建的target名字,推荐使用auto自动检测即可。
2) 目标代码中的适配
OpenOCD识别对应的RTOS的原理是检测特定系统的特定符号。
这些符号实际就是对应系统的内核全局变量,可以获取内核的相关信息。
所以实际也可以手动去实现这些功能的,即我们前文分享的《GDB高级调试技巧-FreeRTOS内核信息显示》。
以下是不同系统对应的符号
上述很多符号对应的RTOS是默认有的,但是有些可能需要一些配置或者人工添加
lZephyr 需要使用DEBUG_THREAD_INFO选项编译
lFreeRTOS需要编译链接contrib/rtos-helpers/FreeRTOS-openocd.c
luC/OS-III需要编译链接contrib/rtos-helpers/uCOS-III-openocd.c
3)FreeRTOS的实例
从OpenOCD的源码复制
contrib\rtos-helpers\FreeRTOS-openocd.c到自己的工程编译进去。
其中#define USED __attribute__((used)),USED是避免编译时优化。
const int USED uxTopUsedPriority = configMAX_PRIORITIES – 1;
如果使用了–gc-sections则还需要增加以下链接参数,避免链接时优化
-Wl,–undefined=uxTopUsedPriority
配置脚本中增加
target create $_TARGETNAME0 riscv -chain-position $_CHIPNAME.cpu -coreid 0
$_TARGETNAME0 configure -rtos FreeRTOS
或者
$_TARGETNAME0 configure -rtos auto
连接上GDB即可。 连接GDB时OpenOCD成功检测到RTOS则会打印相关信息,例如
Info : Auto-detected RTOS: FreeRTOS
4)GDB相关的多线程命令
参考https://sourceware.org/gdb/current/onlinedocs/gdb.html/Threads.html
后面第三章实测部分会演示实例。
5)问题
我们这里平台是riscv, 所以会看到OpenOCD打印
Error: Could not find target in FreeRTOS compatibility list
搜索打印信息位于如下函数位置
staticintfreertos_create(struct target *target){for (unsigned int i = 0; i < ARRAY_SIZE(freertos_params_list); i++)if (strcmp(freertos_params_list[i].target_name, target_type_name(target)) == 0) {target->rtos->rtos_specific_params = (void *)&freertos_params_list[i];return ERROR_OK;}LOG_ERROR("Could not find target in FreeRTOS compatibility list");return ERROR_FAIL;}
我们看到freertos_params_list下只有cortex_m和hla_target的支持,即riscv平台不支持FreeRTOS。
我们就来修改OpenOCD源码增加这个支持。
2.2 关键代码
1) 配置命令执行过程
相关代码位于src\rtos\下,比如FreeRTOS就是src\rtos\freertos.c
src\target\target.c中执行target配置命令
static COMMAND_HELPER(target_configure, struct target *target, unsigned int index, bool is_configure)
n = nvp_name2value(nvp_config_opts, CMD_ARGV[index]);
搜寻nvp_config_opts, 匹配选项-rtos, 找到n为
{ .name = “-rtos”, .value = TCFG_RTOS },
switch (n->value) {对应
case TCFG_RTOS:if (is_configure) {if (index == CMD_ARGC) {command_print(CMD, "missing argument to %s", CMD_ARGV[index - 1]);return ERROR_COMMAND_ARGUMENT_INVALID;}retval = rtos_create(CMD, target, CMD_ARGV[index]);if (retval != ERROR_OK)return retval;index++;} else {if (index != CMD_ARGC)return ERROR_COMMAND_SYNTAX_ERROR;if (target->rtos)command_print(CMD, "%s", target->rtos->type->name);}/* loop for more */break;
调用
retval = rtos_create(CMD, target, CMD_ARGV[index]);
其中CMD_ARGV[index]为配置-rtos选项的参数比如auto,FreeRTOS
在rtos_create中根据指定的os名字,创建对应的对象
对于auto暂时只先os_alloc,分配target->rtos,赋值为rtos_types[0]
对于其他的则os_alloc_create即先os_alloc,分配target->rtos,赋值为rtos_types[x],对于freertos就是&freertos_rtos,然后调用对应create接口
target->rtos->type->create对于freertos就是
调用&freertos_rtos的create成员即
.create = freertos_create,
过程如下

2) freertos_create
遍历freertos_params_list,查询target的name是否在freertos_params_list的target_name中,在就记录freertos_params_list
target->rtos->rtos_specific_params = (void *)&freertos_params_list[i]
staticintfreertos_create(struct target *target){for (unsigned int i = 0; i < ARRAY_SIZE(freertos_params_list); i++)if (strcmp(freertos_params_list[i].target_name, target_type_name(target)) == 0) {target->rtos->rtos_specific_params = (void *)&freertos_params_list[i];return ERROR_OK;}LOG_ERROR("Could not find target in FreeRTOS compatibility list");return ERROR_FAIL;}
我们看到目前的实现freertos_params_list中只有target_name为cortex_m和hla_target的
所以我们需要增加一个riscv的实现,
增加一个
{"riscv", /* target_name */4, /* thread_count_width; */4, /* pointer_width; */12, /* list_next_offset; */20, /* list_width; */4, /* list_elem_next_offset; */12, /* list_elem_content_offset */0, /* thread_stack_offset; */52, /* thread_name_offset; */0, /* stacking_info */0,0,&rtos_standard_cortex_riscv_fpu_stacking,},
其中只有参数struct rtos_register_stacking和平台相关,其他参数对于FreeRTOS都是一样的。
3) os检测原理
通过struct rtos_type的成员.detect_rtos,对于freertos是
freertos_detect_rtos来实现。
rtos_thread_packet->rtos_qsymbol中调用os->type->detect_rtos(target)
staticboolfreertos_detect_rtos(struct target *target){if ((target->rtos->symbols) &&(target->rtos->symbols[FREERTOS_VAL_PX_READY_TASKS_LISTS].address != 0)) {/* looks like FreeRTOS */return true;}return false;}
即来判断
target->rtos->symbols[FREERTOS_VAL_PX_READY_TASKS_LISTS].address不为0则表示为FreeRTOS
即freertos_symbol_list数组中的
{ “pxDelayedTaskList”, false },
freertos_symbol_list记录了需要的符号。
target->rtos->symbols是通过以下函数赋值的
.get_symbol_list_to_lookup = freertos_get_symbol_list_to_lookup,
在rtos_qsymbol->next_symbol中调用os->type->get_symbol_list_to_lookup(&os->symbols);
staticintfreertos_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[]){unsigned int i;*symbol_list = calloc(ARRAY_SIZE(freertos_symbol_list), sizeof(struct symbol_table_elem));for (i = 0; i < ARRAY_SIZE(freertos_symbol_list); i++) {(*symbol_list)[i].symbol_name = freertos_symbol_list[i].name;(*symbol_list)[i].optional = freertos_symbol_list[i].optional;}return 0;}
所以这里只要存在pxReadyTasksLists这个符号,即这个全局变量即可,即GDB中能够p pxReadyTasksLists看到这个变量,只要检测到有这个变量则认为存在FreeRTOS。
4) 适配不同的target
freertos_rtos中的实现都是通用的
const struct rtos_type freertos_rtos = {.name = "FreeRTOS",.detect_rtos = freertos_detect_rtos,.create = freertos_create,.update_threads = freertos_update_threads,.get_thread_reg_list = freertos_get_thread_reg_list,.get_symbol_list_to_lookup = freertos_get_symbol_list_to_lookup,};
适配target需要freertos_params_list中增加一个target的参数,所以需要重点了解这些参数的含义,然后按照对应的target添加即可。
参数结构体如下
struct freertos_params {const char *target_name;const unsigned char thread_count_width;const unsigned char pointer_width;const unsigned char list_next_offset; /* offsetof(List_t, xListEnd.pxNext) */const unsigned char list_width; /* sizeof(List_t) */const unsigned char list_elem_next_offset; /* offsetof(ListItem_t, pxNext) */const unsigned char list_elem_content_offset; /* offsetof(ListItem_t, pvOwner) */const unsigned char thread_stack_offset; /* offsetof(TCB_t, pxTopOfStack) */const unsigned char thread_name_offset; /* offsetof(TCB_t, pcTaskName) */const struct rtos_register_stacking *stacking_info_cm3;const struct rtos_register_stacking *stacking_info_cm4f;const struct rtos_register_stacking *stacking_info_cm4f_fpu;};
1.target_nametarget的名字,即-rtos选项后的参数
2.thread_count_width
3.pointer_width指针的宽度32位系统 4字节
4.list_next_offset
xListEnd.pxNext在List_t中的偏移
这里需要注意configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES是否使能,未使能是3个WORD,12字节。
5.list_widthList_t结构体体的宽度
这里需要注意configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES是否使能,未使能是5个WORD,20字节。
struct xMINI_LIST_ITEM{listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */configLIST_VOLATILE TickType_t xItemValue;struct xLIST_ITEM * configLIST_VOLATILE pxNext;struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;};typedef struct xMINI_LIST_ITEM MiniListItem_t;typedef struct xLIST{listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */volatile UBaseType_t uxNumberOfItems;ListItem_t * configLIST_VOLATILE pxIndex; /*< Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */MiniListItem_t xListEnd; /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */} List_t;
6.list_elem_next_offset
pxNext在ListItem_t中的偏移
这里需要注意configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES是否使能,未使能是1个WORD,4字节。
struct xLIST;struct xLIST_ITEM{listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */configLIST_VOLATILE TickType_t xItemValue; /*< The value being listed. In most cases this is used to sort the list in descending order. */struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< Pointer to the next ListItem_t in the list. */struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */void * pvOwner; /*< Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */struct xLIST * configLIST_VOLATILE pxContainer; /*< Pointer to the list in which this list item is placed (if any). */listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */};typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
7.list_elem_content_offset
pvOwner在ListItem_t中的偏移
这里需要注意configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES是否使能,未使能是3个WORD,12字节。
8.thread_stack_offset
pxTopOfStack在TCB_t中的偏移
为0
typedef struct tskTaskControlBlock { /* The old naming convention is used to prevent breaking kernel aware debuggers. */volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
9.thread_name_offset
pcTaskName在TCB_t中的偏移,13个WORD,52字节
未使能portUSING_MPU_WRAPPERS
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES则
typedef struct tskTaskControlBlock { /* The old naming convention is used to prevent breaking kernel aware debuggers. */volatile StackType_t *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */#if ( portUSING_MPU_WRAPPERS == 1 )xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */#endifListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ListItem_t xEventListItem; /*< Used to reference a task from an event list. */UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */StackType_t *pxStack; /*< Points to the start of the stack. */char pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
10.Target相关的寄存器
const struct rtos_register_stacking *stacking_info_cm3;const struct rtos_register_stacking *stacking_info_cm4f;const struct rtos_register_stacking *stacking_info_cm4f_fpu;
这里需要增加一个riscv的参数
增加一个
const struct rtos_register_stacking *stacking_info_riscv_fpu;
这里感觉不应该不同target的rtos_register_stacking都放出来,应该是一个target对应一个rtos_register_stacking即可。
5) freertos_get_thread_reg_list
该函数比较关键,用于获取寄存器列表,对于不同的任务控制块,有不同的寄存器列表值(即上下文环境)
先进行了参数检查
param = (const struct freertos_params *) rtos->rtos_specific_params;
即freertos_create时设置的
target->rtos->rtos_specific_params = (void *)&freertos_params_list[i];
然后读取栈指针
/* Read the stack pointer */uint32_t pointer_casts_are_bad;retval = target_read_u32(rtos->target,thread_id + param->thread_stack_offset,&pointer_casts_are_bad);if (retval != ERROR_OK) {LOG_ERROR("Error reading stack frame from FreeRTOS thread");return retval;}stack_ptr = pointer_casts_are_bad;LOG_DEBUG("FreeRTOS: Read stack pointer at 0x%" PRIx64 ", value 0x%" PRIx64,thread_id + param->thread_stack_offset,stack_ptr);
偏移thread_stack_offset是前面参数指定的
其实就是从栈帧处读内容,所以关键是要知道栈帧的位置,这个其实是记录在TCB中的第一个成员的,而TCB是可以通过上一篇文章的各种链表获取的。所以GDB是知道TCB的地址,知道栈帧的基地址的。
freertos_get_thread_reg_list函数是通过thread_id传递这个基地址的。
.get_thread_reg_list = freertos_get_thread_reg_list,
搜索get_thread_reg_list看在哪里调用
位于rtos_get_gdb_reg_list
看到来自于target->rtos->current_threadid

我们继续看target->rtos->current_threadid怎么来的
搜索current_threadid
看到来自于GDB的TCP的包
rtos_thread_packet中
即从H包中解析出来的,即GDB告诉OpenOCD的,正如我们前面分析的

继续来看freertos_get_thread_reg_list的实现后面是target相关的处理
先判断是不是armv7m看是cm3还是cm4,是cm4然后判断是不是使能fpu,
所以我们在此再加一层,判断是不是riscv
通过以下接口判断是不是riscv
staticinlineboolis_riscv(conststruct riscv_info *riscv_info){return riscv_info->common_magic == RISCV_COMMON_MAGIC;}
详细代码如下
struct riscv_info *riscv = riscv_info(rtos->target);if (!is_riscv(riscv)) {// ....原来的逻辑} else {return rtos_generic_stack_read(rtos->target, param->stacking_info_riscv_fpu, stack_ptr, reg_list, num_regs);}
6) FreeRTOS的栈帧–与GDB寄存器列表获取
现在来到最关键的一环,获取寄存器列表。因为核心是要获取任务的信息,即任务的上下文信息,即保存在栈中的CONTEX,即一系列的寄存器。
以CM3为例先来看其他平台怎么写的,然后再依葫芦画瓢添加riscv的。
栈帧参考
https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/main/portable/GCC/ARM_CM3/port.c

对应如下
高地址 偏移xPSR 0x3CPC 0x38LR 0x34R12 0x30R3 0x2CR2 0x28R1 0x24R0 0x20R11 0x1CR10 0x18R9 0x14R8 0x10R7 0x0CR6 0x08R5 0x04R4 0x00低地址 偏移从低地址开始算 即递减栈 栈是往下生长,指针指向低地址
而GDB需要获取的寄存器列表参见
src\target\armv7m.h中的枚举
/* offsets into armv7m core register cache */enum {/* for convenience, the first set of indices match* the Cortex-M DCRSR.REGSEL selectors*/ARMV7M_R0 = ARMV7M_REGSEL_R0,ARMV7M_R1 = ARMV7M_REGSEL_R1,ARMV7M_R2 = ARMV7M_REGSEL_R2,ARMV7M_R3 = ARMV7M_REGSEL_R3,ARMV7M_R4 = ARMV7M_REGSEL_R4,ARMV7M_R5 = ARMV7M_REGSEL_R5,ARMV7M_R6 = ARMV7M_REGSEL_R6,ARMV7M_R7 = ARMV7M_REGSEL_R7,ARMV7M_R8 = ARMV7M_REGSEL_R8,ARMV7M_R9 = ARMV7M_REGSEL_R9,ARMV7M_R10 = ARMV7M_REGSEL_R10,ARMV7M_R11 = ARMV7M_REGSEL_R11,ARMV7M_R12 = ARMV7M_REGSEL_R12,ARMV7M_R13 = ARMV7M_REGSEL_R13,ARMV7M_R14 = ARMV7M_REGSEL_R14,ARMV7M_PC = ARMV7M_REGSEL_PC,ARMV7M_XPSR = ARMV7M_REGSEL_XPSR,
static const struct stack_register_offset rtos_standard_cortex_m3_stack_offsets[ARMV7M_NUM_CORE_REGS] = {{ ARMV7M_R0, 0x20, 32 }, /* r0 */{ ARMV7M_R1, 0x24, 32 }, /* r1 */{ ARMV7M_R2, 0x28, 32 }, /* r2 */{ ARMV7M_R3, 0x2c, 32 }, /* r3 */{ ARMV7M_R4, 0x00, 32 }, /* r4 */{ ARMV7M_R5, 0x04, 32 }, /* r5 */{ ARMV7M_R6, 0x08, 32 }, /* r6 */{ ARMV7M_R7, 0x0c, 32 }, /* r7 */{ ARMV7M_R8, 0x10, 32 }, /* r8 */{ ARMV7M_R9, 0x14, 32 }, /* r9 */{ ARMV7M_R10, 0x18, 32 }, /* r10 */{ ARMV7M_R11, 0x1c, 32 }, /* r11 */{ ARMV7M_R12, 0x30, 32 }, /* r12 */{ ARMV7M_R13, -2, 32 }, /* sp */{ ARMV7M_R14, 0x34, 32 }, /* lr */{ ARMV7M_PC, 0x38, 32 }, /* pc */{ ARMV7M_XPSR, 0x3c, 32 }, /* xPSR */};
所以参数
const struct rtos_register_stacking rtos_standard_cortex_m3_stacking = {.stack_registers_size = 0x40,.stack_growth_direction = -1,.num_output_registers = ARMV7M_NUM_CORE_REGS,.calculate_process_stack = rtos_standard_cortex_m3_stack_align,.register_offsets = rtos_standard_cortex_m3_stack_offsets};
那么我们就来增阿吉riscv的参数,src\rtos\rtos_standard_stackings.c中增加
static const struct stack_register_offset rtos_standard_cortex_riscv_fpu_stack_offsets[] = {}
const struct rtos_register_stacking rtos_standard_cortex_riscv_fpu_stacking = {.stack_registers_size = 0xF8, /* 栈帧的大小 62个WORD */.stack_growth_direction = -1,.num_output_registers = 33, /* rtos_standard_cortex_riscv_fpu_stack_offsets中寄存器个数 即从栈帧stack_registers_size范围内选寄存器到num_output_registers个寄存器 */.calculate_process_stack = 0,.register_offsets = rtos_standard_cortex_riscv_fpu_stack_offsets};
参考pxPortInitialiseStack的实现来确认栈帧的寄存器列表
/*
* mstatus
* x31
* x30
* x29
* x28
* x27
* x26
* x25
* x24
* x23
* x22
* x21
* x20
* x19
* x18
* x17
* x16
* x15
* x14
* x13
* x12
* x11
* pvParameters
* x9
* x8
* x7
* x6
* x5
* portTASK_RETURN_ADDRESS
* [chip specific registers go here]
* pxCode
*/
30+32 62个寄存器
其中chip specific registers为32个
栈帧为
//以下是FreeRTOS栈帧信息 高地址{ 0, 0xF4, 32 }, /* mstatus */{ 1, 0xF0, 32 }, /* x31 t6 */{ 2, 0xEC, 32 }, /* x30 t5 */{ 3, 0xE8, 32 }, /* x29 t4 */{ 4, 0xE4, 32 }, /* x28 t3 */{ 5, 0xE0, 32 }, /* x27 s11 */{ 6, 0xDC, 32 }, /* x26 s10*/{ 7, 0xD8, 32 }, /* x25 s9 */{ 8, 0xD4, 32 }, /* x24 s8 */{ 9, 0xD0, 32 }, /* x23 s7 */{ 10, 0xCC, 32 }, /* x22 s6 */{ 11, 0xC8, 32 }, /* x21 s5 */{ 12, 0xC4, 32 }, /* x20 s4 */{ 13, 0xC0, 32 }, /* x19 s3 */{ 14, 0xBC, 32 }, /* x18 s2 */{ 15, 0xB8, 32 }, /* x17 a7 */{ 16, 0xB4, 32 }, /* x16 a6 */{ 17, 0xB0, 32 }, /* x15 a5 */{ 18, 0xAC, 32 }, /* x14 a4 */{ 19, 0xA8, 32 }, /* x13 a3 */{ 20, 0xA4, 32 }, /* x12 a2 */{ 21, 0xA0, 32 }, /* x11 a1 */{ 22, 0x9C, 32 }, /* x10 a0 pvParameters */{ 23, 0x98, 32 }, /* x9 s1 */{ 24, 0x94, 32 }, /* x8 s0/fp */{ 25, 0x90, 32 }, /* x7 t2 */{ 26, 0x8C, 32 }, /* x6 t1 */{ 27, 0x88, 32 }, /* x5 t0 */{ 28, 0x84, 32 }, /* x1 ra portTASK_RETURN_ADDRESS *//* 前面29个WORD *//* 后面32+1个WORD */{ 29, 0x80, 32 }, /* ft11 */{ 30, 0x7C 32 }, /* ft10 */{ 31, 0x78, 32 }, /* ft9 */{ 32, 0x74, 32 }, /* ft8 */{ 33, 0x70, 32 }, /* fs11 */{ 34, 0x6C, 32 }, /* fs10 */{ 35, 0x68, 32 }, /* fs9 */{ 36, 0x64, 32 }, /* fs8 */{ 37, 0x60, 32 }, /* fs7 */{ 38, 0x5C, 32 }, /* fs6 */{ 39, 0x58, 32 }, /* fs5 */{ 40, 0x54, 32 }, /* fs4 */{ 41, 0x50, 32 }, /* fs3 */{ 42, 0x4C, 32 }, /* fs2 */{ 43, 0x48, 32 }, /* fa7 */{ 44, 0x44, 32 }, /* fa6 */{ 45, 0x40, 32 }, /* fa5 */{ 46, 0x3C, 32 }, /* fa4 */{ 47, 0x38, 32 }, /* fa3 */{ 48, 0x34, 32 }, /* fa2 */{ 49, 0x30, 32 }, /* fa1 */{ 50, 0x2C, 32 }, /* fa0 */{ 51, 0x28, 32 }, /* fs1 */{ 52, 0x24, 32 }, /* fs0 */{ 53, 0x20, 32 }, /* ft7 */{ 54, 0x1C, 32 }, /* ft6 */{ 55, 0x18, 32 }, /* ft5 */{ 56, 0x14, 32 }, /* ft4 */{ 57, 0x10, 32 }, /* ft3 */{ 58, 0x0C, 32 }, /* ft2 */{ 59, 0x08, 32 }, /* ft1 */{ 60, 0x04, 32 }, /* ft0 */{ 61, 0x00, 32 }, /* x1 ra pxCode*///低地址 偏移从低地址开始算 即递减栈 栈是往下生长,指针指向低地址
对应要发给GDB的寄存器列表
static const struct stack_register_offset rtos_standard_cortex_riscv_fpu_stack_offsets[] = {{ 0, -1, 32 }, /* x0 */{ 1, 0x84, 32 }, /* x1 ra */{ 2, -2, 32 }, /* x2 sp */{ 3, -1, 32 }, /* x3 gp */{ 4, -1, 32 }, /* x4 tp */{ 5, 0x88, 32 }, /* x5 t0 */{ 6, 0x8C, 32 }, /* x6 t1 */{ 7, 0x90, 32 }, /* x7 t2 */{ 8, 0x94, 32 }, /* x8 s0/fp */{ 9, 0x98, 32 }, /* x9 s1 */{ 10, 0x9C, 32 }, /* x10 a0 */{ 11, 0xA0, 32 }, /* x11 a1 */{ 12, 0xA4, 32 }, /* x12 a2 */{ 13, 0xA8, 32 }, /* x13 a3 */{ 14, 0xAC, 32 }, /* x14 a4 */{ 15, 0xB0, 32 }, /* x15 a5 */{ 16, 0xB4, 32 }, /* x16 a6 */{ 17, 0xB8, 32 }, /* x17 a7 */{ 18, 0xBC, 32 }, /* x18 s2 */{ 19, 0xC0, 32 }, /* x19 s3 */{ 20, 0xC4, 32 }, /* x20 s4 */{ 21, 0xC8, 32 }, /* x21 s5 */{ 22, 0xCC, 32 }, /* x22 s6 */{ 23, 0xD0, 32 }, /* x23 s7 */{ 24, 0xD4, 32 }, /* x24 s8 */{ 25, 0xD8, 32 }, /* x25 s9 */{ 26, 0xDC, 32 }, /* x26 s10 */{ 27, 0xE0, 32 }, /* x27 s11 */{ 28, 0xE4, 32 }, /* x28 t3 */{ 29, 0xE8, 32 }, /* x29 t4 */{ 30, 0xEC, 32 }, /* x30 t5 */{ 31, 0xF0, 32 }, /* x31 t6 */{ 32, 0x00, 32 }, /* pc pxCode */}
2.3 调试
动态打开调试信息
telnet中
log_level 3打印更多信息,然后操作,然后log_level 2
减少打印信息,这样可以看到操作更多的打印信息。
例如几个关键路径加打印确认是否执行
如果提示错误Error: No symbols for FreeRTOS
说明symbols没有赋值
if (!rtos->symbols) {LOG_ERROR("No symbols for FreeRTOS");return -3;}
赋值是在freertos_get_symbol_list_to_lookup中所以可以在此加打印看是否执行

其他路径的调试类似。比如要看栈帧基地址,获取的寄存器列表内容等,都可以类似的添加打印。
三. 实测
针对常见的命令进行测试
3.1自动检测到RTOS
Openocd配置脚本中配置
$_TARGETNAME0 configure -rtos auto
GDB连接上时
target extended-remote 172.16.11.110:3333
自动检测到FreeRTOS,OpenOCD打印
Info : Auto-detected RTOS: FreeRTOS
3.2自动通知创建新线程
GDB运行到指定断点,中间有创建任务,会打印如下New Thread信息
(gdb) b app.c:90Breakpoint 1 at 0x20004f70: file acore/app.c, line 90.(gdb) cContinuing.[New Thread 672723960][New Thread 672725640][New Thread 672732152][New Thread 672736416][New Thread 672727888][Switching to Thread 672723960]Thread 2 "app_entry" hit Breakpoint 1, app_entry (arg=<optimized out>) at acore/app.c:90/home/lhj//examples/usb_demo/acore/app.c:90:2095:beg:0x20004f70
3.3查看线程
语法
info threads [-gid] [-stopped] [-running] [thread-id-list]
thread-id-list的形式可以是类似以下形式
1 2-3 4.5 6.7-9 7.*
info threads(gdb) info threadsId Target Id Frame* 3 Thread 672714336 "app_entry" (Name: app_entry, State: Running) app_entry (arg=<optimized out>) at acore/app.c:914 Thread 672716016 "IDLE" (Name: IDLE) prvIdleTask (pvParameters=0x0)at /home/lhj//os/freertos_10_2_1/freertos/tasks.c:30685 Thread 672718264 "Tmr Svc" (Name: Tmr Svc) syscall_yield ()at /home/lhj/os/freertos_10_2_1/portable/riscv/syscallASM.S:96 Thread 672722528 "uart_tx_task" (Name: uart_tx_task) wrapper_task_func (arg=0x2818de40)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:427 Thread 672726792 "shell_task" (Name: shell_task) wrapper_task_func (arg=0x2818eee8)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:42(gdb)

Id是线程的ID, 型号表示当前运行线程
-gid可以查看全局ID
(gdb) info threads -gidId GId Target Id Frame* 3 3 Thread 672714336 "app_entry" (Name: app_entry, State: Running) app_entry (arg=<optimized out>) at acore/app.c:914 4 Thread 672716016 "IDLE" (Name: IDLE) prvIdleTask (pvParameters=0x0)at /home/lhj/os/freertos_10_2_1/freertos/tasks.c:30685 5 Thread 672718264 "Tmr Svc" (Name: Tmr Svc) syscall_yield ()at /home/lhj/os/freertos_10_2_1/portable/riscv/syscallASM.S:96 6 Thread 672722528 "uart_tx_task" (Name: uart_tx_task) wrapper_task_func (arg=0x2818de40)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:427 7 Thread 672726792 "shell_task" (Name: shell_task) wrapper_task_func (arg=0x2818eee8)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:42(gdb)
-stopped查看stopped的线程
-running查看运行的线程
符号$_thread表示线程ID,进程内唯一,不同进程可能一样
符号 $_gthread表示全局线程ID,全局唯一,info threads -gid查看
符号$_inferior_thread_count表示live线程的个数
3.4切换线程
thread thread-id
3切换到4
(gdb) info threads -gidId GId Target Id Frame* 3 3 Thread 672714336 "app_entry" (Name: app_entry, State: Running) app_entry (arg=<optimized out>) at acore/app.c:914 4 Thread 672716016 "IDLE" (Name: IDLE) prvIdleTask (pvParameters=0x0)at /home/lhj/os/freertos_10_2_1/freertos/tasks.c:30685 5 Thread 672718264 "Tmr Svc" (Name: Tmr Svc) syscall_yield ()at /home/lhj/os/freertos_10_2_1/portable/riscv/syscallASM.S:96 6 Thread 672722528 "uart_tx_task" (Name: uart_tx_task) wrapper_task_func (arg=0x2818de40)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:427 7 Thread 672726792 "shell_task" (Name: shell_task) wrapper_task_func (arg=0x2818eee8)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:42(gdb) thread 4[Switching to thread 4 (Thread 672716016)]#0 prvIdleTask (pvParameters=0x0) at /home/lhj//os/freertos_10_2_1/freertos/tasks.c:3068/home/lhj//os/freertos_10_2_1/freertos/tasks.c:3068:128142:beg:0x2000b45e(gdb) info threadsId Target Id Frame3 Thread 672714336 "app_entry" (Name: app_entry, State: Running) app_entry (arg=<optimized out>) at acore/app.c:91* 4 Thread 672716016 "IDLE" (Name: IDLE) prvIdleTask (pvParameters=0x0)at /home/lhj/os/freertos_10_2_1/freertos/tasks.c:30685 Thread 672718264 "Tmr Svc" (Name: Tmr Svc) syscall_yield ()at /home/lhj/os/freertos_10_2_1/portable/riscv/syscallASM.S:96 Thread 672722528 "uart_tx_task" (Name: uart_tx_task) wrapper_task_func (arg=0x2818de40)at /home/lhj/os_shim/src/freertos_10_2_1/os_task.c:427 Thread 672726792 "shell_task" (Name: shell_task) wrapper_task_func (arg=0x2818eee8)at /home/lhj/os/os_shim/src/freertos_10_2_1/os_task.c:42(gdb)
3.5 对多个线程执行命令
thread apply [thread-id-list | all] args
3.6 线程start和exit消息
set print thread-events on使能
set print thread-events off关闭
show print thread-events查看使能状态
3.7 查找线程
thread find[regexp]
例如
(gdb) thread find 3Thread 3 has target id 'Thread 672714336'(gdb) thread find IDLEThread 4 has target name 'IDLE'Thread 4 has extra info 'Name: IDLE'(gdb)
四. 问题
Remote ‘g’ packet reply is too long (expected 132 bytes, got 248 bytes):
发送给GDB的寄存器链表寄存器的个数不一样,需要按照前文介绍进行确认。
五. 总结
以上修改OpenOCD的源码,是的RISCV对应的target支持FreeRTOS的调试,这样可以方便的切换线程,查看线程状态等,方便调试,这里是基于OpenOCD的原生支持,当然前文介绍的手动添加脚本去查看信息也是可以的,殊途同归。
夜雨聆风
