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,从而触发非法指令异常,代码如下:

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

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

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

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

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

异常处理问题修改

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

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

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

        _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地址:

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

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

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

     

        接着修改上一篇文章中增加的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位置继续执行。

FreeRTOS\Source\timers.c

        编译运行,

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

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

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: RISC-V架构手册是一本介绍RISC-V指令集体系结构的重要参考资料。RISC-V是一种开放的指令集架构,具有可扩展性、灵活性和高效性的特点。 首先,RISC-V架构手册详细介绍了RISC-V指令集中的所有指令,包括常用的算术指令、逻辑指令、存取指令、分支指令等。它不仅列出了每个指令的编码格式和操作码,还详细说明了每个指令的功能和使用方法。这样的信息对于开发RISC-V处理器的工程师来说至关重要。 其次,RISC-V架构手册还介绍了RISC-V处理器的基本结构和中断处理机制。它详细解释了数据通路、控制单元和存储器等组件的连接方式和工作原理。同时,手册还讨论了RISC-V处理器的中断处理流程,包括中断的触发条件、中断处理程序的执行和中断返回等。这对于编写操作系统或者驱动程序的开发者来说非常有帮助。 此外,RISC-V架构手册还包含了关于扩展特性和可选模块的说明。它介绍了如何在RISC-V架构上扩展自定义指令或者功能,以及如何添加可选的模块,如浮点单元、虚拟内存管理等。这样的灵活性使得RISC-V架构非常适合于各种应用领域,从嵌入式系统到超级计算机。 总的来说,RISC-V架构手册是开发人员和研究人员掌握RISC-V指令集和架构设计的重要参考资料。它全面介绍了RISC-V指令集的各个方面,并提供了实现RISC-V处理器的指导。通过学习和理解这本手册,人们可以更好地应用和推广RISC-V架构,推动开源指令集的发展。 ### 回答2: RISC-V(精简指令集计算机-五)架构手册是一份完整的指南,用于描述和解释RISC-V计算机架构的细节和规范。这个手册为开发者和研究人员提供了一个详细的参考,以了解和标准化他们在RISC-V处理器设计和实现方面的工作。 RISC-V架构手册包含多个章节和附录,涵盖了RISC-V指令集的不同方面。它首先介绍了RISC-V的设计原理和目标,比如简洁性、可扩展性和定制化能力。然后,手册详细说明了RISC-V指令集的不同指令格式和编码规则,包括指令解码过程和操作码的定义。 这个手册还涵盖了RISC-V的寄存器和寄存器文件,描述了它们的使用方法和特殊规则。此外,手册还提供了关于异常处理机制和中断处理机制的解释,以及RISC-V中的特权级别和特权模式的详细信息。 RISC-V架构手册还包含了有关内存管理单元(MMU)和虚拟内存系统的信息,说明了RISC-V支持的不同内存访问方式和存储体系结构的细节。此外,手册还提供了有关浮点运算和向量指令集的详细说明以及其使用方法。 除了这些主要内容外,RISC-V架构手册还提供了一些附录,包括指令集的变种和扩展,以及示例代码和编程实例。这些附录为开发者提供了实际应用和开发RISC-V处理器的指导支持。 总之,RISC-V架构手册是一个重要的参考资料,用于理解和使用RISC-V计算机架构。它的详细说明和规范为开发者提供了标准化和统一化的参考,以便设计、实现和优化RISC-V处理器。 ### 回答3: RISC-V架构手册是一本详细介绍RISC-V指令集架构的重要参考资料。RISC-V是一种新兴的开源指令集架构,其优势在于简洁、可扩展和高度灵活。RISC-V架构手册系统地介绍了RISC-V指令集的各种特性和用法。 首先,RISC-V架构手册提供了RISC-V指令集的全面介绍。它详细解释了RISC-V的指令编码方式、寄存器组织、内存管理机制等基本概念。通过学习手册,人们可以了解到RISC-V指令的格式和操作方式,从而能够编写符合RISC-V架构的程序。 其次,RISC-V架构手册系统介绍了RISC-V的扩展指令集。RISC-V提供了一种模块化的设计理念,允许用户根据不同的应用需求选择使用不同的指令集扩展。手册详细介绍了RISC-V的各种扩展,如乘法/除法扩展、向量扩展等,并提供了使用这些扩展的示例和指导。 另外,RISC-V架构手册还介绍了RISC-V的异常处理和中断机制。这些机制对于系统安全和稳定运行非常重要。手册详细解释了异常和中断的分类、处理流程以及相关的指令和寄存器。通过学习手册,人们可以了解到如何在RISC-V架构中设计有效的异常处理和中断控制机制。 最后,RISC-V架构手册还介绍了RISC-V的特殊指令和特殊寄存器。这些特殊指令和寄存器常用于系统级编程和性能优化。手册提供了这些指令和寄存器的详细说明和使用方法,帮助人们充分发挥RISC-V架构的优势。 综上所述,RISC-V架构手册是学习和理解RISC-V指令集架构的重要工具。通过研读手册,人们可以掌握RISC-V的基本概念、指令格式和操作方式,进而能够灵活应用RISC-V的各种扩展和特殊功能,为不同的应用场景设计高效的RISC-V架构系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值