物奇5007crash问题dump、栈回溯的四种定位方法

概述

物奇属于RISC-V架构(chapter1_riscv.md · 华中科技大学操作系统团队/pke-doc - Gitee.com),在遇到crash问题时,系统内部会对内存进行相关的保护和检测,当发现异常时会主动调用 IOT_ASSERT,通常会产生相关的 log 文件, 如: ***.c:xxx Asserted! 出现异常时的现场 dump 记录,将通用的 32 个 CPU 寄存器 dump 输出。回栈信息记录。出现异常时的堆栈信息以及调用函数地址。

物奇5007crash问题dump、栈回溯的四种定位方法_第1张图片

 以空指针拷贝导致crash为例:

物奇5007crash问题dump、栈回溯的四种定位方法_第2张图片

第一种:addr2line内存调试工具回溯

addr2line将地址转换为文件名和行号。鉴于可执行文件中的地址或可重定位文件部分中的偏移量对象,它使用调试信息来确定哪个文件名以及行号与之相关联。

示例: addr2line -C -f -e xxx.elf 0x1234abcd

查看使用方法:man 1 addr2line

物奇5007crash问题dump、栈回溯的四种定位方法_第3张图片

我们可以使用脚本封装该模块成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

物奇5007crash问题dump、栈回溯的四种定位方法_第4张图片

可以得到大概信息为memcpy导致。

物奇5007crash问题dump、栈回溯的四种定位方法_第5张图片

截图来源WQ5007 SDK user guide_CN_V1.1.pdf

第二种:objdump反汇编栈回溯

反汇编,查看汇编代码,对于可执行文件elf,第一列为其虚拟内存地址。

用法示例:

物奇5007crash问题dump、栈回溯的四种定位方法_第6张图片

如果执行时候报错“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

物奇5007crash问题dump、栈回溯的四种定位方法_第7张图片

在dump文件中,我们查看前面异常寄存器中的ra寄存器的栈虚拟内存地址找到大概文件位置。

物奇5007crash问题dump、栈回溯的四种定位方法_第8张图片

再查看PC寄存器的虚拟内存地址,根据dump中信息找到大概位置为memcpy导致

物奇5007crash问题dump、栈回溯的四种定位方法_第9张图片

根据上面信息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

暂未实践,后续更新

物奇5007crash问题dump、栈回溯的四种定位方法_第10张图片

程序的函数调用栈结构(从main函数到bar函数)

arm、risc-v架构异常寄存器概念

FP:栈顶指针,指向一个栈帧的顶部,当函数发生跳转时,会记录当时的栈的起始位置。

SP:栈指针(也称为栈底指针),指向栈当前的位置,

LR:链接寄存器,保存函数返回的地址。(在risc-v架构中,ra寄存器就相当于lr的作用)

物奇5007crash问题dump、栈回溯的四种定位方法_第11张图片

 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博客

你可能感兴趣的:(risc-v,嵌入式硬件)