这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【换取手持数字示波器】RISC-VGCC使用-fno-omit-frame-po

共1条 1/1 1 跳转至

【换取手持数字示波器】RISC-VGCC使用-fno-omit-frame-pointer编译选项实现栈回溯

工程师
2025-01-11 08:48:10     打赏

简介:

gcc 编译器可以使用 “-fno-omit-frame-pointer”(不忽略栈帧指针) 编译选项,编译器会把特定寄存器作为栈帧保存寄存器,gcc 手册中对该编译选项的描述如下:

image.png

开启该编译选项后,risc-v 架构的处理器是使用S0寄存器作为特定的栈帧寄存器,来保存每个函数的栈帧,使用该编译选项可以根据栈帧的情况实现函数调用的栈回溯,RISC-V 文档中对寄存器的使用规定中可以看出s0(x8)作为栈帧保存寄存器使用

 image.png

从上述描述还可以看出当函数是leaf 函数时不需要保存栈帧,从计算机的角度来看,用保存栈帧与仓库存储的类比,可以更理论化地解析如下:

  1. 普通函数调用:仓库存储多个任务的中间状态

    • 函数的局部变量(相当于任务使用的工具)。

    • 函数的返回地址(相当于任务完成后回到正确的下一步)。

    • 调用者的寄存器状态(相当于任务前的仓库初始状态)。

    • 每次一个普通函数调用另一个函数(比如函数A调用函数B),函数A需要暂时停止当前任务,并记录自己执行到哪一步、使用了哪些资源等信息。这些信息被保存在栈帧中,就像在仓库中分配一个专属空间,存储当前任务的中间状态。

    • 栈帧保存的信息包括:

    • 当子函数(B)完成后,仓库通过读取之前保存的状态,帮助主函数(A)恢复继续工作。

  2. 叶子函数调用:直接处理而不需要保存状态

    • 叶子函数是调用链的终点,不会再调用其他函数。因为它的逻辑简单、独立,不需要保存调用者的执行状态。执行叶子函数时,寄存器或其他资源可以直接被使用,完成任务后直接返回,不涉及栈帧的分配和回收。

    • 类比到仓库:叶子函数的任务就像一个独立的简化流程,执行时只需要直接取用货物并返回,整个过程无需额外占用储物空间(栈帧)存储中间状态。

  3. 效率和资源管理:减少不必要的开销

    • 栈帧的保存和恢复会引入额外的开销(比如保存栈、恢复栈帧),类似于在仓库中开辟和清理储物空间。而叶子函数由于不需要保存这些状态,可以直接运行完成,节省了内存和时间。

普通函数调用需要“仓库”来保存当前任务状态,以便任务切换后能够恢复。而叶子函数由于没有后续调用,任务独立、简单,直接完成返回,不需要保存状态,从而避免了额外的存储开销,执行更高效。

我们编写如下的测试代码验证栈帧调用的功能

void func_d(void)
{
    printf("i am funcd\r\n");
    vTaskDelay(pdMS_TO_TICKS(1000000));
}

void func_c(void)
{
    printf("i am funcc\r\n");
    func_d();
    printf("bye funcc\r\n");
}

void func_b(void)
{
    printf("i am funcb\r\n");
    func_c();
    printf("bye funcb\r\n");
}


void func_a(void)
{
    printf("i am funca\r\n");
    func_b();
    printf("bye funca\r\n");
}

-fno-omit-frame-pointer 编译选项后,编译器编译的代码会在函数掉用过程中加入sp,ra 的保存处理过程,对应过程如下。

image.png

编译器对每个函数入口都添加上述的栈保存处理,最终上述func_a->func_b->func_c->func_d函数调用的栈最终在栈中体现如下:

image.png

上述栈帧相当于链表结构,我们只要知道func_d 的s0 FP 地址信息就可以获取func_c 的信息从而完成函数栈的回溯。

image.png

添加如下代码即可实现函数调用的回溯

#if __riscv_xlen == 32
#define BACKTRACE_LEN 8
#endif

#if __riscv_xlen == 64
#define BACKTRACE_LEN 16
#endif

struct stackframe
{
    uint32_t s_fp; // frame pointer
    uint32_t s_ra; // return address
};

unsigned int backtrace(char argc,char ** argv)
{
    /* Thread list */
    rt_list_t * pos;
    tskTCBList * node;
    struct stackframe * fp;
    uint8_t num = 0;


    rt_list_for_each(pos,&tasklist)
    {
        num = 0;
        node = rt_list_entry(pos,tskTCBList,list);
        const register unsigned long current_sp __asm__("sp"); //   get current stack pointer

        if(current_sp<= (uint32_t)node->tcb->pxEndOfStack &&
           current_sp >= (uint32_t)node->tcb->pxStack)
        {
            fp = (struct stackframe *)(__builtin_frame_address(0) - BACKTRACE_LEN);
            printf("=%p\r\n",fp);
        }
        else
        {
            fp = (struct stackframe *)(node->tcb->pxTopOfStack[8] - BACKTRACE_LEN);
            printf("==%p\r\n",fp);
        }
        printf("task %s backtrace fp 0x%08p.\r\n",node->tcb->pcTaskName,fp);
        while(1)
        {
            if(fp->s_fp > (uint32_t)node->tcb->pxEndOfStack ||
               fp->s_fp < (uint32_t)node->tcb->pxStack)
            {
                break;
            }

            printf("[%d] stack fp = 0x%08x ra = 0x%08x.\r\n",num++,fp->s_fp,fp->s_ra);

            fp = (struct stackframe *)(fp->s_fp - BACKTRACE_LEN);
        }

    }

    return 1;
}
LTSH_FUNCTION_EXPORT(backtrace,"show task backtrace");



共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]