概述
物奇属于RISC-V架构(chapter1_riscv.md · 华中科技大学操作系统团队/pke-doc - Gitee.com),在遇到crash问题时,系统内部会对内存进行相关的保护和检测,当发现异常时会主动调用 IOT_ASSERT,通常会产生相关的 log 文件, 如: ***.c:xxx Asserted! 出现异常时的现场 dump 记录,将通用的 32 个 CPU 寄存器 dump 输出。回栈信息记录。出现异常时的堆栈信息以及调用函数地址。
以空指针拷贝导致crash为例:
第一种:addr2line内存调试工具回溯
addr2line将地址转换为文件名和行号。鉴于可执行文件中的地址或可重定位文件部分中的偏移量对象,它使用调试信息来确定哪个文件名以及行号与之相关联。
示例: addr2line -C -f -e xxx.elf 0x1234abcd
查看使用方法:man 1 addr2line
我们可以使用脚本封装该模块成backtrace.sh脚本,可以做到直接方便调用
#!/bin/bash
usage() {
echo -e "Usage :"
echo -e " $0 [log file] [out file]"
echo -e " log file: error dump log file"
echo -e " out file: .out execute file"
echo -e ""
echo -e "example:"
echo -e " $0 stack.txt ht_cco.out"
echo -e ""
}
if [ ! -n "$1" ]; then
usage
exit
fi
if [ ! -n "$2" ]; then
usage
exit
fi
STACK_FILE=$1
OUT_NAME=$2
tail -n 1000 $STACK_FILE | awk '{print $2}' | grep "0x[0-9A-Fa-f]*" | \
xargs riscv64-unknown-elf-addr2line -f -e $OUT_NAME
把异常模式下得到的log文件与ai.elf放入一起,执行脚本,如如: ./backtrac.sh crash5.txt ai.elf
可以得到大概信息为memcpy导致。
截图来源WQ5007 SDK user guide_CN_V1.1.pdf
第二种:objdump反汇编栈回溯
反汇编,查看汇编代码,对于可执行文件elf,第一列为其虚拟内存地址。
用法示例:
如果执行时候报错“objdump: can't disassemble for architecture UNKNOWN!”
使用objdump -i来查看支持的architecture, 找到合适的文件格式,执行例如:objdump -m i386 -d ai.elf ,没有合适的,那么可以换台机器。
鉴于dump文件过大,可以使用重定义到文件中再查看定位更加方便;
objdump -m i386 -d ai.elf > a.c
在dump文件中,我们查看前面异常寄存器中的ra寄存器的栈虚拟内存地址找到大概文件位置。
再查看PC寄存器的虚拟内存地址,根据dump中信息找到大概位置为memcpy导致
根据上面信息crash信息mcause: 0x03了解为零地址访问可以确定是在FaceRegister类中的memcpy函数的空指针导致crash,即可确认问题所在。
第三种:使用jtag
需要保留现场实时调试栈内存回溯确认位置,暂未实践,后续更新
第四种: readelf
读取elf的相关信息,如段信息。
如果想dump某些段内的信息,可以通过readelf -x 来hex-dump 或者通过readelf -p来string-dump,比如c/c++内的string literals
经过编译可能会存放在.rodata 只读数据段内,可以通过readelf -p .rodata xxx.elf > your outputfile 来dump
暂未实践,后续更新
程序的函数调用栈结构(从main函数到bar函数)
arm、risc-v架构异常寄存器概念
FP:栈顶指针,指向一个栈帧的顶部,当函数发生跳转时,会记录当时的栈的起始位置。
SP:栈指针(也称为栈底指针),指向栈当前的位置,
LR:链接寄存器,保存函数返回的地址。(在risc-v架构中,ra寄存器就相当于lr的作用)
RV64G的32个通用寄存器(寄存器的宽度都是64位)
FP的作用
关于APCS(ARM Procedure Call Standard,ARM 程序调用标准)的说法 ,
除非子程序没有修改链接寄存器,否则FP都需要记录有效的栈帧位置
其寄存器(r11或者x29)不能被用做一个通用型的寄存器
FP的主要作用就是用来栈回溯,找到子程序的调用关系,也成为backtrace,当然一级一级的子程序调用时,FP的记录也在变化,也会一级一级的保存到栈中,最后通过FP的值来反推出一级一级的调用关系。
R13/SP的作用
sp 为栈指针,通过push pop 实现对栈存储的访问,栈主要是用来存储局部变量 中间值 等数据,同样和全部变量等存储的区域一样,也是一块memory,没有任何区别,只是使用的方式不一样。
LR的作用
LR为程序跳转时需要用到的寄存器,用来保存返回地址(同时也包含异常返回地址)。
程序经常会存在调用关系,当程序执行完子程序之后,肯定会返回到主程序,这是返回到主程序的地址就是在LR保存。
在一些CorteM系列的处理,LR的第0位会置1 表示,表示Thumb状态。
当然没有LR这个寄存器也可以的,直接将返回地址保存到栈中,最后执行完之后弹出到PC也行,但是寄存器的访问速度可以远高于栈(存储器SRAM),所以LR的作用还是很明显的。
此外对应ARMv8系列,还有ELR寄存器,对应的是异常状态下的返回地址。
借鉴文章部分知识点链接:ARM学习(1) 寄存器的理解 ===》FP、SP、LR寄存器_张一西的博客-CSDN博客