1、背景介绍
本文着重从问题定位的角度来介绍如何定位嵌入式软件系统中的问题,并结合AliOS Things提供的部分维测手段来介绍。
AliOS Things目前已经提供了丰富的Debug维测功能、离线的脚本解析,以及可视化的IDE工具来让用户快速上手使用我们的维测功能;相关的维测功能和工具也在实际开发项目中不断完善。
完善的维测工具一直是AliOS Things的主要发展的方向;但是从另一个方面来说,工具本身的作用也是有限的,嵌入式系统出现的问题可以说是千变万化;不同的开发者,对于工具提供的信息理解也不尽相同。我们在熟悉使用基本的维测工具的同时,还需要了解底层问题定位的基础知识,基本的定位手段和思路。解Bug的技术思路和维测工具的完善应该是相辅相成,相互促进的。
本文介绍了部分常用的嵌入式问题定位的基础知识和基本的手段。其中涉及到的AliOS Things内核一些已有的工具,以及一些内容的拓展和深入,读者可以自行了解和学习。
结合AliOS Things谈嵌入式系统通用问题定位方法(2):内核相关基础
结合AliOS Things谈嵌入式系统通用问题定位方法(3):问题定位思路
1.1、对象
嵌入式内核和驱动开发测试人员
协议栈、中间件开发测试人员
上层APP组件开发测试人员
1.2、目的
如何从底层软件源头上开始解bug
如何快速确认分流大量bug
从问题本身理解规范性代码如何编写
1.3、内容
结合AliOS Things内核来介绍:
解bug的必要知识点
解bug的通用方法、步骤、关键点
AliOS Things提供定位问题的手段、工具
本文介绍的内容主要以ARMv7架构为例,编译器为gcc。
2、CPU相关基础点
(以ARMv7a为例)
2.1、基础架构
!
Monitor:安全非安全
Hypervisor: 虚拟化
PL1:特权模式
PL0:用户模式
内核的内核态对应CPU的特权模式;
内核的用户态对应CPU的用户模式。
对于PL1,有7种子模式,我们主要关注:
SVC:内核正常运行模式
IRQ:中断进入模式
UND: 指令异常
ABT:数据异常
CPU当前状态可通过CPSR尾部的Mode状态寄存器来获取:
-
.equ CPSR_Mode_USR, 0x10
-
.equ CPSR_Mode_FIQ, 0x11
-
.equ CPSR_Mode_IRQ, 0x12
-
.equ CPSR_Mode_SVC, 0x13
-
.equ CPSR_Mode_ABT, 0x17
-
.equ CPSR_Mode_UND, 0x1B
-
.equ CPSR_Mode_SYS, 0x1F
2.2、基础寄存器
32bit ARM汇编有16个32位寄存器:
- r0-r3 主要用来传递函数调用第1到第4个参数(a0-a3),更多的参数须通过栈来传递。
- r0-r1也作为结果寄存器,保存函数返回结果;被调用的子程序在返回前无须恢复这些寄存器的内容。
- r4-r9 为被调保存(callee-save)寄存器,一般保存内部局部变量(local variables)。
- r7 大部分情况用来保存系统调用号(syscall number)。
- r9 某些变体可能当作特殊寄存器。
- r10(SL)被调保存寄存器,Stack Limit。
- r11(FP)被调保存寄存器, 帧指针(Flame Pointer)。
- r12(IP)特殊寄存器,栈寄存器(Intra Procedure)。
- r13(SP)特殊寄存器,栈指针,类似x86_64中的RSP。
- r14(LR)特殊寄存器。Link Register.
- r15(PC)特殊寄存器。Program Counter (like RIP in x86_64 & EIP in x86)
其中注意几点:每个模式下SP(R13)独立,PC只有一个,LR独立,SPSR自动保存着进入该模式前的CPSR。Fiq比较特殊,R8~R14都是独立的。
2.3、协处理器
ARM体系架构支持协处理器,用于扩展ARM处理器功能。协处理器指令用于访问协处理器。协处理器支持16个协处理器,编号0-15,使用CP0-CP15描述。
- CP15:提供系统控制功能。包括架构和特性ID,以及控制,状态信息和配置支持。
- CP14:提供硬件Debug功能。
- CP10,CP11:共同支持浮点运算和向量操作。控制和配置浮点和高级SIMD扩展架构。
- CP8,9,12,13:为ARM架构保留协处理器。
- CP0-7:由厂家定义协处理功能
2.4、基础指令
ARM提供两种指令编码格式:Thumb(2)、ARM指令集
ARM指令集每条指令固定为4 字节大小;
Thumb(2)指令集每条指令为2或者4字节大小;
具体细节不在此赘述。
2.5、压栈出栈相关
压栈出栈指令可以用下面表格来概括:
!
其中LDM的后缀对应关系:
FA: full + add 满增
FD:full + decrease 满减
EA:empty + add 空增
ED:empty + decrease 空减
IA: increment after 先取值,SP后增加
IB: increment before SP先增加,后取值
DA: decrement after 先取值,SP后减小
DB: decrement before SP先减小,后取值
目前一般使用的是:
LDMIA :弹栈时,先取SP内数据,后SP地址增大
STMDB :压栈时,SP地址先减小,后开始存放数据
2.6、异常
异常寄存器:
主要关注四个寄存器,来判断访问哪个地址出现了何种异常:
DFSR:数据异常状态寄存器
DFAR:导致数据异常的地址
IFSR:指令异常状态寄存器
IFAR:导致指令异常的地址
2.7、MMU/MPU相关
对于MPU和MMU的细节,用户可不需要关注。但是需要了解基本概念。
MPU针对ARM-M系列,可以设定特殊内存段的属性是特权还是非特权访问,没有虚拟和物理的映射;
MMU针对ARM-A系列,包含虚拟和物理的映射,以及各种权限的控制。
M系列的用户态有特权和非特权之分;
A系列的用户态即对应最小用户权限模式。
对于访问地址的权限问题,有时需要查看MMU页表,去核对属性。
容易遇到的问题:
- 访问权限问题,如代码段数据段的权限问题
- 页表覆盖导致内存空间冲突问题
2.8、函数/数据断点
函数和数据断点是CPU提供的基本调式Debug功能。
对应gdb的对函数打断点,watch监控某一块内存的修改访问,以及进一步的单步调试功能。
2.9、编译汇编链接相关
2.9.1、需关注编译选项
Cpu相关:
-mcpu=cortex-a7 :指定cpu架构
-mfpu=neon-vfpv4 :浮点数、neno指令
-mfloat-abi=hard :软硬浮点
emu相关:
-fshort-enums:
sizeof(enum1)不增加这个-fshort-enums选项的时候为4,增加后为该enum实际可以存放的最小字节数据类型,如1字节。
默认不设定,一旦设置就选用节省内存的enum长度
编译告警相关:
-Wall : 编译显示所有告警
-Werror: 所有的警告当成错误进行处理
-Wfatal-errors:出现错误停止编译
-w : 关闭告警
需要使用gdb查看详细的数据结构信息:
加入-g选项
2.9.2、汇编注意点
函数跳转指令(B、BL、BLX):
BL 和 BLX 指令可将下一个指令的地址复制到 LR(r14,链接寄存器)中。
BX 和 BLX 指令可将处理器的状态从 ARM 更改为 Thumb,或从 Thumb 更改为 ARM。
BLX label 无论何种情况,始终会更改处理器的状态。
BX Rm 和 BLX Rm 可从 Rm 的位 [0] 推算出目标状态:
如果 Rm 的位 [0] 为 0,则处理器的状态会更改为(或保持在)ARM 状态
如果 Rm 的位 [0] 为 1,则处理器的状态会更改为(或保持在)Thumb 状态。
反汇编别名:
在查看汇编时,通用寄存器会有特殊的别名,常用对应如下:
R10 sl 栈限制
R11 fp 桢指针
R12 ip
R13 sp 栈指针
R14 lr 连接寄存器
R15 pc 程序计数器
函数入参传递:
对于ARM-32位处理器,R0、R1、R2、R3寄存器分别作为函数的第0个至第3个入参,超出入参使用栈空间;
32位函数出参统一使用R0。
调用者报错和被调用者保存的寄存器:
被调用函数只会保存被自己上下文修改的R4开始的寄存器,R0~R3由调用栈按需保存。
指令编码:
每条指令都有固定的编码格式,否则就是非法指令。动态加载/调用栈解析等会有对指令修改的操作,一般对定位问题有两个小用途:
- 看开栈大小:主要有没有定义过大的数组,通过sub sp的指令来搜索立即数的大小;
e24dd014 sub sp, sp, #20
- 强制修改指令
在代码段可修改的情况下,强制修改指令,达到某种如进入死循环,强制跳转的目的
2.9.3、ELF结构相关
ELF内是比较复杂的数据结构,其中分可重定位文件(.o)、共享目标文件(.so)、以及可执行文件(elf)。二进制bin文件不属于elf格式,而是没有elf的头部和相关符号等信息,纯碎装载的可执行段的二进制文件。内部结构比较复杂,只介绍基本定位问题使用的。
Elf中的section:一般在ld中可见定义的描述,如下面即是一个section
-
.data : {
-
__data_start = .;
-
*(.data)
-
*(.data.*)
-
__data_end = .;
-
} > DRAM_ADDR
Elf中的段:在最后链接时,链接器会将各个section组合成最终的几个段,当然可以在ld中显示地关联段。
Section一般存在于尚未链接的可重定位文件,最终生成可执行的elf中都是组合的段信息
!
基本查看ELF信息的编译器工具:
- 查看ELF头部信息、段组成
结果示例:
- Elf转汇编
- 查找符号
-
localhost: arm-none-eabi-nm ./helloworld@developerkit.elf | grep aos_init
-
080021f8 T aos_init
相关信息也可通过map文件,其同样也是通过elf生成。
- addr2line
-
localhost: arm-none-eabi-addr2line -e ./helloworld@developerkit.elf 0x80021f8
-
/workspace/aos_gerrit_new/core/osal/aos/rhino.c:815
通过指令地址,导出对应C的文件名和行数
对于C++一般使用 –pfiCe选项
- C++函数名
汇编中C++的函数名非常复杂,可通过下面命令输入正常的函数名:
-
localhost: arm-none-eabi-c++filt smartbox@mt8153a-mk.app.elf _ZN17GwMeshControlInfoD1Ev smartbox@mt8153a-mk.app.elf
-
GwMeshControlInfo::~GwMeshControlInfo()
开发者技术支持
如需更多技术支持,可加入钉钉开发者群,或者关注微信公众号
更多技术与解决方案介绍,请访问阿里云AIoT首页https://iot.aliyun.com/