当前位置: 首页 > wzjs >正文

合肥网站建设博客虎门今日头条新闻

合肥网站建设博客,虎门今日头条新闻,怎样php网站建设,太原网站的优化前情提要:栈 要理解栈回溯,就要首先理解栈是如何运行的 下面看一段非常简单的代码 uint32_t funB(uint32_t b) {return b-1; }void funA(uint32_t a) {uint32_t b funB(a); }int main(void) {board_init();funA(3);while (1) {}return 0; }这段代码的逻…

前情提要:栈

        要理解栈回溯,就要首先理解栈是如何运行的

        下面看一段非常简单的代码

uint32_t funB(uint32_t b)
{return b-1;
}void funA(uint32_t a)
{uint32_t b = funB(a);
}int main(void)
{board_init();funA(3);while (1) {}return 0;
}

         这段代码的逻辑非常简单,调用链也很清晰,在main函数中调用funA,funA中调用funB,main函数的汇编代码如下所示

int main(void)
{
8000648c:	1141                	add	sp,sp,-16
8000648e:	c606                	sw	ra,12(sp)
80006490:	c422                	sw	s0,8(sp)
80006492:	0800                	add	s0,sp,16board_init();
80006494:	b16fe0ef          	jal	800047aa <board_init>funA(3);
80006498:	450d                	li	a0,3
8000649a:	b7dfd0ef          	jal	80004016 <funA>

        可以看到,当main函数执行时,首先向上开辟了一块栈空间,然后将ra,s0压栈,随后才是用户所写的代码逻辑,对这里用到的两个寄存器进行说明:

        -ra:保存了返回地址,当执行jal时,会将下一条指令的地址保存至ra

        -s0:又叫fp,frame point,栈帧指针,是回溯需要用到的重要寄存器

        这里需要注意一个细节,在调用main函数之前,把fp压栈了,随后将fp指向了当前函数栈底;这样意味着,当运行某个函数时,risc-v的fp寄存器,始终指向该函数的栈底,而该函数的栈中,保留了运行上一个函数时的fp寄存器的值

        这么说或许有些抽象,用一个图进行演示

        这么一看就清晰多了,也就是在FunB中,可以看到自己的caller FunA的栈底是0x2200110,而caller的栈顶,也就是callee的栈底,也就是0x2200120;

        这么一来,对于被调用者来说,可以知道调用者的栈地址,这也是通过FP进行栈回溯的基础

        以上基础是理解栈回溯的基础,务必搞清楚后才能理解栈回溯的本质

栈回溯实现

思路

        有了以上的铺垫之后,我们可以想想如果是要自己写一个栈回溯的函数traceback,该怎么做

  1. 首先traceback一定是在某处被调用,我们假设FunB调用了traceback
  2. 在funB运行时,我们可以从寄存器SP得到FunB的栈顶,从寄存器FP得到栈底
  3. 我们可以把栈底和栈顶传递给traceback,这样trackback就能得到栈中的所有内容
  4. 栈内有什么呢,有FunB的返回地址ra,还有之前保存的fp(这里要注意,栈中的fp和当前寄存器FP内的值是两回事,栈中的fp是调用FunB的函数FunA的栈底)
  5. 接着,我们可以根据栈中的fp跳转至FunA的栈,里面又可以得到FunA的返回地址,以及FunA栈中的fp,也就是main函数的栈底
  6. 这样一层一层套娃,也就完成了函数的调用链回溯
  7. 最后需要注意退出条件,fp指向整个栈底的时候意味着调用链结束,应当退出

演示代码


void backtrace() {uintptr_t *fp;// 内联汇编获取当前FP(s0寄存器)asm volatile ("mv %0, s0" : "=r"(fp));printf("Backtrace:\n");//如果FP>=栈底,则意味着函数调用链已经结束while (uintptr_t)FP < (uintptr_t)estack) {// 获取返回地址(RA = fp - 8)uintptr_t ra = *(fp - 1);if (ra == 0) break;// 获取前一个FP(prev_fp = fp - 16)uintptr_t *prev_fp = (uintptr_t *)*(fp - 2);// 打印RA(实际应用中可解析为函数名)printf("#%d: [FP=0x%lx] RA = 0x%lx\n", i, (uintptr_t)fp, ra);// 检查prev_fp有效性(如对齐、地址范围)if ((uintptr_t)prev_fp <= (uintptr_t)fp || prev_fp == NULL) break;fp = prev_fp;}
}

细节剖析

        uintptr_t ra = *(fp - 1);// 获取前一个FP(prev_fp = fp - 16)uintptr_t *prev_fp = (uintptr_t *)*(fp - 2);// 检查prev_fp有效性(如对齐、地址范围)if ((uintptr_t)prev_fp <= (uintptr_t)fp || prev_fp == NULL) break;fp = prev_fp;

1.为什么ra 和prev_fp是 *(fp -1) 和*(fp -2)

对于普通的函数调用这点很重要,因为ISR,Excpetion可能是另外的情况,下文会展开)来说,程序运行时首先开辟一块栈,将sp指向栈顶,然后将返回地址ra入栈,fp入栈;最后将fp指向栈底,栈底的第一个元素就是ra,第二个元素则是fp,至于为什么不是*fp和*(fp -1),那是因为这是一个自减栈,高地址是栈底,低地址则是栈顶。

2.检查prev_fp有效性

代码的演示中只是简单判断了prev_fp是否非NULL,实际使用中还可以根据栈的地址对齐进行额外校验,例如你的栈如果是16字节对齐,则可以判断prev_fp是否整除16

   .stack (NOLOAD) : {. = ALIGN(16);__stack_base__ = .;. += STACK_SIZE;. = ALIGN(16);PROVIDE (_stack = .);PROVIDE (_stack_safe = .);} > DLM

Trap的特殊处理

概述

        RISC-V的trap发生时,会进入trap_handler,然后根据发生trap的原因(中断还是异常),进入不同的处理函数,笔者所使用的RISC-V芯片开启了中断向量,因此中断并不会进入trap_handler,而会直接进入中断处理函数,因此对两种情况分开阐述。

中断

        当开启中断向量且中断发生后,芯片根据中断id号跳转至中断处理函数。由于中断的退出是通过mret跳转至mepc所指向的地址继续运行,因此在进入中断时并不会保存ra的地址。我们看一段中断的汇编代码如下

        可以看到,由于中断是随时发生的,系统并不会保存ra,只保存了s0(也就是fp),对ra的压栈发生在中断对上下文进行保存的过程中,是我们手动实现的,而绿色方框外的代码则是自动生成的。比起常规的函数调用,自动生成的代码中少了对ra的压栈,因此prev_fp是 *(fp -1) 和而非*(fp -2)

异常

        和中断类似,异常发生时会进入trap_handler,汇编代码如下

        同样的,s0和常规函数调用的压栈顺序发生的变化,prev_fp是 *(fp -5) 和而非*(fp -2)

演示代码修改

        在上文我们提供了backtrac的演示代码,他能很好地处理函数调用的栈回溯,但知道了trap需要的特殊处理后,我们需要对该代码进行修改,根据不同情况对prev_fp取值进行修改,代码如下所示:

void backtrace() {uintptr_t *fp;// 内联汇编获取当前FP(s0寄存器)asm volatile ("mv %0, s0" : "=r"(fp));printf("Backtrace:\n");//如果FP>=栈底,则意味着函数调用链已经结束while (uintptr_t)FP < (uintptr_t)estack) {uint32_index = 0;long *rs0;uint32_t pos[3] = {1,2,5};//根据不同情况找到prev_fp所在的位置for (uint32_t i = 0; i < sizeof(pos) / sizeof(pos[0]); i++) {rs0 = (long *) *(b - pos[i]);if (rs0 > (long *) *estack)continue;if (rs0 <= b)continue;if (((char *)rs0 - (char *)b) % 16 == 0) {index = pos[i];}}//isr并没有对ra自动压栈,在保存上下文时保存了raif (index == 1) {ra = (unsigned long) *SP;} else {ra = (unsigned long) *(FP - 1);}if (ra == 0) break;// 获取前一个FP(prev_fp = fp - index)uintptr_t *prev_fp = (uintptr_t *)*(fp - index);// 打印RA(实际应用中可解析为函数名)printf("#%d: [FP=0x%lx] RA = 0x%lx\n", i, (uintptr_t)fp, ra);// 检查prev_fp有效性(如对齐、地址范围)if ((uintptr_t)prev_fp <= (uintptr_t)fp || prev_fp == NULL) break;fp = prev_fp;}
}

http://www.dtcms.com/wzjs/293908.html

相关文章:

  • 武昌网站建设公司企业网络营销案例分析
  • 怎么查看网站空间大小厦门seo起梦网络科技
  • 免费邯郸网站建设精准客户软件
  • 网站制作测试范围怎么制作公司网页
  • wordpress文件储存seo推广需要多少钱
  • 网站单页在线制作国家最新新闻
  • 营销型网站建设案例杭州seo外包服务
  • 做微信首图的网站域名注册信息查询whois
  • 天津小型企业网站设计b站视频推广的方法有哪些
  • 网站建设公司哪有给网站做seo的价格
  • 彩票龙虎网站开发博客网站
  • 网站开发培训哪家好企业短视频推广
  • 怎么做网站底部版权信息长沙seo袁飞
  • 网页源代码翻译器搜索引擎优化是什么工作
  • 网页设计一般多少钱seo长沙
  • 台州首页关键词优化seo权重优化软件
  • 淳安千岛湖建设集团网站优秀软文范例800字
  • html网站建设源码视频优化软件
  • 牛商网做网站百度推广排名怎么做的
  • wordpress价格表单东莞关键词优化平台
  • 合肥做网站的软件公司上海高端seo公司
  • ppt模板做的好的网站有线下推广方法有哪些
  • web站点百度推广代理查询
  • 锦州网站建设网站页面的优化
  • 网站首页做后台链接百度视频推广怎么收费
  • 松江叶榭网站建设赣州seo培训
  • 新疆生产建设兵团33团网站自助建站申请
  • 金华建站价格沈阳线上教学
  • 学网站开发培训电商运营多少钱一个月
  • 北京建设监理网站谷歌广告优化