学习
实践
活动
专区
工具
TVP
写文章
专栏首页嵌入式iotarm上backtrace的分析与实现原理

arm上backtrace的分析与实现原理

前言

我们往往在进行嵌入式开发的过程中,需要借助一些调试手段进行相关调试,比如在调试stm32的时候,可以在keil中利用jtag或者stlink进行硬件上的仿真与调试,一些高频的arm芯片也会使用jtag之类的硬件调试工具,还有trace32等等,但是这些往往需要借助一些硬件工具进行分析。当然,我们可以进行软件层面的分析。定位问题的方式通常有以下三点:

1.通过串口打印信息进行业务逻辑的梳理,结合代码设计进行分析

2.在程序死机的时候,输出的函数调用栈关系进行分析,结合符号文件进行跟踪定位

3.在程序死机时输出内存镜像,利用gdb还原死机现场

一般来讲,这三种方法都有一定的优缺点。

第一种靠串口输出信息一般比较有限,而且对于有些情况,串口输出没办法进行准确的定位,但是比较方便,实现起来比较容易。

第二种可以查看到函数调用的关系,根据这些调用关系,就可以非常方便的跟踪到出问题的地方,然后进行一定的跟踪。但是需要理解寄存和汇编之类的知识。

第三种的信息最全,调用关系和参数信息都有,但是对工具链和系统都提出了一些要求。往往在嵌入式开发过程中,涉及到业务逻辑非常复杂的时候可以进行分析。但是一般的情况不会用到coredump。

第一种可以不用讲,现在主要讲一下backtrace。

01

backtrace简介

backtrace就是回溯堆栈,简单的说就是可以列出当前函数调用关系。在理解backtrace之前我们需要理解一下函数执行过程的中的压栈过程。

1.1 寄存器与汇编指令

ARM微处理器共有37个寄存器,其中31个为通用寄存器,6个为状态寄存器。但是往往这些寄存器都不能同时被访问,需要在特定的模式下访问特定的指令。

但在任何时候,通用寄存器R0~R15、一个或两个状态寄存器都是可访问的。有三个特殊的通用寄存器:R13:在ARM指令中常用作堆栈指针SPR14:也称作子程序连接寄存器(Subroutine Link Register)即连接寄存器LRR15:也称作程序计数器PC

还有一个寄存器

R11:栈基址FP

THUMB2下为R7。

1.2 函数的压栈与入栈操作

当函数main调用func1的时候其栈的过程如上图所示,每个函数都有自己的栈空间,这一部分我们称为栈帧,在函数被调用的时候创建,在函数返回后销毁。

其中我们看到这其中涉及到四个比较关键的寄存器:PC、LR、SP、FP。需要注意的是,每个栈帧中的PC、LR、SP、FP都是寄存器的历史值,而不是当前值。

PC寄存器和LR寄存器均指向代码段,PC表示当前的代码指向到何处,LR表示当前函数返回后要到哪里去继续执行。

SP和FP用于维护函数的栈空间,其中SP指向栈顶,FP指向上一个函数栈帧的栈顶。

如上图所示

依次为当前函数指针PC、返回指针LR、栈指针SP、栈基址FP、传入参数个数及指针、本地变量和临时变量。如果函数准备调用另一个函数,跳转之前临时变量区先要保存另一个函数的参数。

1.3 栈回溯过程原理

在栈回溯的过程中,我们主要是利用的是这个FP寄存器进行回溯,因为根据FP寄存器就可以找到下一个FP寄存器的栈底,获得PC指针,然后固定偏移,又可以回溯到上个PC指针,这样回溯下去,然后就可以完全的跟踪到函数的运行过程了。然后利用addr2line工具,就可以详细跟踪到函数的执行过程了。

02

backtrace的过程详解

当程序出现异常或者死机的时候,我们可以读取当前寄存器的状态,找到当前pc指针的情况,但是这些往往还不能说明问题,我们有时需要跟踪函数的执行过程。

栈的回溯又分为两种:APCS(ARM Procedure Call Standard)与unwind。

栈回溯的实现依赖编译器的特性,与特定的平台相关。以linux内核实现arm栈回溯为例, 通过向gcc传递选项-mapcs或-funwind-tables,可选择APCS或unwind的任一方 式实现栈回溯。

gcc的有些编译优化命令,会让FP寄存器优化掉,比如-fomit-frame-pointer这个优化会让fp寄存器节省下来给其他的地方使用。所以要充分考虑这些问题。

2.1 APCS

ARM过程调用标准规范了arm寄存器的使用、过程调用时 出栈和入栈的约定。如下图示意。

栈回溯中输出的寄存器的值是入栈时保存起来的寄存器值。它通过解析指令码得到哪个 寄存器压栈了,在栈中的位置。

如果编译器遵循APCS,形成结构化的函数调用栈,就可以解析当前栈(callee)结构,从 而得到调用栈(caller)的结构,这样就输出了整个回溯栈。

2.2 unwind

对于APCS来说,优点是分析起来比较简单,跟踪起来也可以很容易。缺点就是指令过多,栈消耗大,占用的寄存器也过多,比如每次调用 都必须将r11,r12,lr,pc入栈。为了解决这个问题,提出了第二种方案:

使用unwind就能避免这些问题,生产指令的效率要有用的多。unwind是最新的编译器(>gcc-4.5)为arm支持的新特性。它的原理是记录每个函数的入栈指令(一般比APCS的入栈要少的多)到特殊的段.ARM.unwind_idx .ARM.unwind_tab。

所以如果我们要使用unwind,就必须在链接文件中定义这个段

.ARM.exidx : {

__exidx_start = .;

*(.ARM.exidx* .gnu.linkonce.armexidx.*)

__exidx_end = .;

}

我们也可以通过arm-none-eabi-readelf -u xxxxx.elf查看其内容。

以上面两个为例,set_date的函数的地址是0xc007c4a0,而set_time的函数的地址是0xc00a0fb0。

而r11也就是fp地址在unwind_tab段中,也就是位于0xc00a0fa4地址处。

回溯时根据pc值到段中得到对应的编码,解析这些编码计算出lr在栈中的位置,进而计算得到调用者的执行地址。

一般来说,我们使用unwind优势比使用apcs更好,因为采用apcs时,会产生更多的代码指令,对性能有影响,但是使用unwind方式只会产生一个额外的段空间,并不会影响性能,所以大多数情况下,使用unwind更加有利。

unwind回溯的过程可以总结为三部分:

1.根据pc找到函数unwind的段内存地址

2.根据unwind段中信息找到指令相关的编码数据

3.根据入栈地址,分析函数上一级的栈底保存的sp和lr。

03

函数符号表

栈回溯的过程中,往往需要符号表来进行操作,此时需要开启-mpoke-function-name这个编译选项。

使用这个选项编译出的二进制程序中可以包含 C 语言函数名称的信息,以方便函数调用链回溯时记录信息的可读性。

比如在Linux中,系统死机后,可以打印出栈的地址和函数的名称,根据这个进行回溯操作就可以进行使用了。

基本原理就是加上-mpoke-function-name后,在每段代码段后面,都会附加一个函数的符号,我们需要使用的时候,就根据函数的pc指针,然后找到相关的偏移量,之后将这个代码段的符号获取到了。

04

总结

对于arm32体系架构的backtrace基本原理可以参考如上的描述,其中最核心的部分是每个函数的栈中寄存器地址指向的是上个函数的地址,所以利用这个特性,就可以一级一级的跟踪下去,从而实现栈的回溯功能。这样我们在分析和定位问题的时候,就会更加的高效。

文章分享自微信公众号:
嵌入式IoT

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:bigmagic
原始发表时间:2020-03-14
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • STM32上的backtrace原理与分析

    一般来说,1,2,3板子都是在开发者手上,一旦遇到bug,只要可以复现,基本上都可以排查出来,然后修复或者规避。但一旦进入到4,5阶段,产品已经成型之后,再想排...

    bigmagic
  • ARM SMMU的原理与IOMMU

    如上图所示,smmu 的作用和mmu 类似,mmu作用是替cpu翻译页表将进程的虚拟地址转换成cpu可以识别的物理地址。同理,smmu的作用就是替设备将dma请...

    Linux阅码场
  • Redis Sentinel原理与实现 (上)

    Sentinel(哨兵)是Redis 2.8版本发布的一个功能,使用Sentinel可以实现高可用的Redis集群服务。Sentinel的作用是实时监控Redi...

    用户7686797
  • Lombok 原理分析与功能实现

    这两天没什么重要的事情做,但是想着还要春招总觉得得学点什么才行,正巧想起来前几次面试的时候面试官总喜欢问一些框架的底层实现,但是我学东西比较倾向于用到啥学啥,因...

    周三不加班
  • memory compaction原理、实现与分析

    赵金生,linux内核爱好者,就职于杭州某大型安防公司,担任Linux BSP软件工程师。对进程调度,内存管理有所了解。希望能通过对linux的学习,提升产品软...

    用户7244416
  • Lombok原理分析与功能实现

    这两天没什么重要的事情做,但是想着还要春招总觉得得学点什么才行,正巧想起来前几次面试的时候面试官总喜欢问一些框架的底层实现,但是我学东西比较倾向于用到啥学啥,因...

    mythsman
  • Tinker-使用教程与原理分析(上)

    前面我们讲解了AndFix的使用,这篇我们来讲解下微信的Tinker热修复,相比AndFix,Tinker的功能更加全面,更主要的是他支持gradle。他不仅做...

    g小志
  • DevTools 实现原理与性能分析实战

    从 2008 年 Google 释放出第一版的 Chrome 后,整个 Web 开发领域仿佛被注入了一股新鲜血液,渐渐打破了 IE 一家独大的时代。Chrome...

    2020labs小助手
  • Nodejs Stream pipe 的使用与实现原理分析

    通过流我们可以将一大块数据拆分为一小部分一点一点的流动起来,而无需一次性全部读入,在 Linux 下我们可以通过 | 符号实现,类似的在 Nodejs 的 St...

    五月君
  • 特征工程系列:特征筛选的原理与实现(上)

    数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功...

    Datawhale
  • 特征工程系列:特征筛选的原理与实现(上)

    数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功...

    石晓文
  • 特征工程系列:特征筛选的原理与实现(上)

    数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功...

    木东居士
  • 特征工程系列:特征筛选的原理与实现(上)

    本篇是来自木东居士的超赞文章,是关于特征工程的一些常用的方法理论以及python实现,大家在做特征工程的时候,可以有所借鉴。

    Sam Gor
  • UDP的FPGA实现(上) | 操作基础与理论分析

    “本地连接”只出现于Win7和XP中,在Win8和win10系统,被更名为“以太网”。(.....大声重复.....)

    根究FPGA
  • Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析

    前面我们已经分析完OpenFeign与Ribbon的源码,包括两者的整合使用,以及Ribbon的重试机制,从最顶层调用接口开始到负载均衡的实现。今天我们分析更底...

    Java艺术
  • 【经验分享】基于Linux命令行编程环境的栈追溯和反汇编技术实践分享

    在嵌入式开发中,往往受限于系统(芯片)本身的资源环境,没法像基于PC主机开发那样方便地进行程序debug,尤其当程序出现一些异常的时候,找到有效的方法进行deb...

    架构师李肯
  • Spring Cloud动态配置实现原理与源码分析

    实际项目开发中少不了各种配置,如连接数据库的配置、连接Redis集群的配置等,通常我们也会为一个项目部署到每个环境准备不同的配置文件,例如测试环境配置连接测试的...

    Java艺术
  • pprof 的原理与实现

    go 内置的 pprof API 在 runtime/pprof 包内, 它提供给了用户与 runtime 交互的能力, 让我们能够在应用运行的过程中分析当前应...

    梦醒人间

扫码关注腾讯云开发者

领取腾讯云代金券