RISC-V架构异常处理与栈回溯分析(二)

        之前的文章RISC-V FreeRTOS异常处理及任务切换分析(基于qemu+gdb跟踪调试)_Dingjun798077632的博客-CSDN博客中,有提到FreeRTOS\Source\portable\GCC\RISC-V\portASM.S文件中通过宏portcontextSAVE_CONTEXT_INTERNAL保存上下文代码有点漏洞:portcontextSAVE_CONTEXT_INTERNAL最后将sp保存到pxCurrentTCB->pxTopOfStack中,在创建任务和调用vTaskStartScheduler()之前pxCurrentTCB必然是个空指针,此时触发异常处理,跳转到异常函数处理入口freertos_risc_v_trap_handler处执行,通过宏portcontextSAVE_CONTEXT_INTERNAL保存上下文又会触发异常,如此反复。这样上一篇文章RISC-V架构栈帧分析与栈回溯实现_Dingjun798077632的博客-CSDN博客中增加的栈回溯等相关代码在启动过程前半部分不起效,但实际开项目中,启动过程需要做大量初始化动作,启动过程是相对比较复杂,比较容易出错的,为方便以后问题调试,最好是能够解决该问题,下面就该问题分析。

异常处理问题验证

        首先,我们先验证上面提出的问题是否存在,还是基于之前的文件FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\main_blinky.c,在main_blinky函数中xTaskCreate之前调用test_fun_c,从而触发非法指令异常,代码如下:

RISC-V架构异常处理与栈回溯分析(二)_第1张图片

还是用asm volatile(".word 0x1234567");触发异常

RISC-V架构异常处理与栈回溯分析(二)_第2张图片

        编译后启用gdb调试,在freertos_risc_v_trap_handler和synchronous_exception函数入口处设置断点,运行结果如下:

RISC-V架构异常处理与栈回溯分析(二)_第3张图片

        对照反汇编代码可以看到,第一次触发异常时mepc为0x80000660刚好为test_fun_b中的非法指令:

RISC-V架构异常处理与栈回溯分析(二)_第4张图片

        继续运行到portcontextSAVE_CONTEXT_INTERNAL保存上下文时又触发异常,触发异常时的mepc为0x8000398c(实际上mepc并不一定指向对应的sw指令位置,Load和Store一般属于非精确异常,上一篇文章中已解释),对照反汇编和代码,刚好是想pxCurrentTCB指针保存数据时:

RISC-V架构异常处理与栈回溯分析(二)_第5张图片

RISC-V架构异常处理与栈回溯分析(二)_第6张图片

        如上所示,在异常处理保存上下文过程中,又触发异常,导致再次跳回到异常入口处,又保存上下文,如此不断循环,无法正在运行到接下来的synchronous_exception异常处理函数处。整个现象和之前的推测完全一直。

异常处理问题修改

        portcontextSAVE_CONTEXT_INTERNAL保存上下文数据基本都是放入触发异常时的栈中,只有最好需要把sp指针,保存到另一个位置,之后切换到中断栈(前面的文章中已经分析过了),这样也就是只要在启动过程中准备一个位置存放sp指针就可以。

之前文章中分析启动过程中,有提到FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\start.S中设置的初始化sp指针为_stack_top,_stack_top在fake_rom.lds链接脚本中定义的。

RISC-V架构异常处理与栈回溯分析(二)_第7张图片

链接脚本FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\fake_rom.lds

RISC-V架构异常处理与栈回溯分析(二)_第8张图片

        _stack_top为栈的高地址,我们增加一行_stack_end = .;用于标记栈的低地址,之后用该地址做为上文的启动过程的异常处理portcontextSAVE_CONTEXT_INTERNAL保存sp指针的地址(栈向下增长,也就是从_stack_top位置递减,_stack_end位置不会被用的,除非设置的栈太小,栈要溢出了)。通过tp寄存器(之前分析过,riscv64-unknown-elf-gcc编译器中没使用tp寄存器),刚启动初始化把tp初始化为_stack_end的地址,在运行到xPortStartFirstTask(启动第一个任务,也就是开启任务调度)时把tp设置为pxCurrentTCB。portcontextSAVE_CONTEXT_INTERNAL保存上下文时,时直接将sp值保存到tp指针指向的内存。同时在函数vTaskSwitchContext()切换pxCurrentTCB指针时,更新tp指针。

代码修改后代码如下,宏RISC_V_START_TRAP_HANDLER中为新增的代码

1. 修改FreeRTOS\Demo\RISC-V-Qemu-virt_GCC\start.S文件,将pxCurrentTCB指向_stack_end地址:

RISC-V架构异常处理与栈回溯分析(二)_第9张图片

2. FreeRTOS\Source\portable\GCC\RISC-V\portContext.h修改,用tp代替pxCurrentTCB:

RISC-V架构异常处理与栈回溯分析(二)_第10张图片

RISC-V架构异常处理与栈回溯分析(二)_第11张图片

3. FreeRTOS\Source\portable\GCC\RISC-V\portASM.S修改,启动调度时,设置tp=pxCurrentTCB,增加一个汇编函数processed_switchcontext,vTaskSwitchContext中调用该函数更新tp指针

RISC-V架构异常处理与栈回溯分析(二)_第12张图片

RISC-V架构异常处理与栈回溯分析(二)_第13张图片

4. FreeRTOS\Source\tasks.c中,vTaskSwitchContext调用processed_switchcontext更新tp指针:

RISC-V架构异常处理与栈回溯分析(二)_第14张图片     

        接着修改上一篇文章中增加的freertos_risc_v_application_exception_handler函数,在if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED )的else中增加如下代码: 

void freertos_risc_v_application_exception_handler(UBaseType_t mcause, UBaseType_t mepc, 
	UBaseType_t mtval, UBaseType_t mstatus, StackType_t * pxTopOfStack)
{

   ... ...
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED ) 
    {
    ... ...
    } 
    else 
    {
				xprintf("The start bactrace:\n");

	extern UBaseType_t _stack_end[];
	extern UBaseType_t _stack_top[];

	    xprintf("Ther startup stack_end:%p, StackTop:%p\n",  _stack_end, _stack_top);
#if 0
        while ((fp > _stack_end) && fp < _stack_top) {
            xprintf("fun%d: FramePointer:0x%lx\tReturnAddr:0x%lx\n", i++, fp, *(fp-1));
			fp = (UBaseType_t*)*(fp-2);
        } 	
#endif
		xprintf("Ther startup Stack:\n");
		i = 0;
		for (pStack = pxTopOfStack + portCONTEXT_COUNT; pStack < _stack_top; pStack++)
		{
			reg = *(UBaseType_t*)pStack;
			if (i++ % 8 == 0) {
				xprintf("\n%p:  0x%lx  ", pStack, reg);
			} else{
				xprintf("0x%lx  ", reg);
			}
		}

		xprintf("\n");
    }
loop:
	xprintf("Freertos risc-v application exception handler end\n");
	//while(1);
}

修改后测试

        修测试改代码,在函数main_blinky()和,xTimerCreateTimerTask主动触发两个异常。上面freertos_risc_v_application_exception_handler() 函数最后的while(1)去掉了,也就是退出异常时,回到触发异常时的pc+4位置继续执行。

RISC-V架构异常处理与栈回溯分析(二)_第15张图片

FreeRTOS\Source\timers.c

RISC-V架构异常处理与栈回溯分析(二)_第16张图片

        编译运行,

RISC-V架构异常处理与栈回溯分析(二)_第17张图片

        可以看到第一次触发异常在main_blinky中,推出异常处理后打印了“Hello main_blinky”,接着第二次触发异常,

RISC-V架构异常处理与栈回溯分析(二)_第18张图片

RISC-V架构异常处理与栈回溯分析(二)_第19张图片

RISC-V架构异常处理与栈回溯分析(二)_第20张图片

        测试结果符合预期,可以根据栈数据对照汇编找出函数调用关系。退出异常处理后,创建的两个任务正常运行。

        修改后的代码发生中断和异常时,portcontextSAVE_CONTEXT_INTERNAL处理,保存sp指针时,相对于先原来的代码少了一条load指令,也就是保存上下文和恢复上下文处理更快了。

你可能感兴趣的:(RISC-V,FreeRTOS,risc-v)