【简介】
我们之前介绍过Cortex M异常解析神器(RA8 Freertos 使用 CmBacktrace 调试死机问题)的使用。CmBacktrace 的一大特色功能是可以根据任务栈中的内容回溯出触发异常时的函数调用栈,我们本次的主题时解析下这部分功能的实现原理。Cmbacktrace 解析函数调用栈的基本原理是检查任务栈中的数据元素是是否在代码段区间,如果在代码段对应的地址的上一条指令如果为BL/BLX 指令的话改该地址就认为是函数调用栈的一个返回地址。上述逻辑的核心一是解析栈里面的代码段的地址的上一条指令是否是BL 跳转指令,对应的代码如下:
/* check the disassembly instruction is 'BL' or 'BLX' */
static bool disassembly_ins_is_bl_blx(uint32_t addr) {
uint16_t ins1 = *((uint16_t *)addr);
uint16_t ins2 = *((uint16_t *)(addr + 2));
#define BL_INS_MASK 0xF800
#define BL_INS_HIGH 0xF800
#define BL_INS_LOW 0xF000
#define BLX_INX_MASK 0xFF00
#define BLX_INX 0x4700
if ((ins2 & BL_INS_MASK) == BL_INS_HIGH && (ins1 & BL_INS_MASK) == BL_INS_LOW) {
return true;
} else if ((ins2 & BLX_INX_MASK) == BLX_INX) {
return true;
} else {
return false;
}
}以下是BL 指令的编码说明:

从上述描述中可知J1/J2 在Thumb-2 的指令集中为1,和代码的
BL_INS_HIGH 0xF800 BL_INS_LOW 0xF000
以下是本地实际代码中抓取的BL指令的机器码


从上述的BL 的伪代码说明中可知BL 指令做了两件事,1 更新PC指针 2 更新LR返回地址,跳转到对应的函数的时候把LR押入到栈中这也就是为啥在栈中可以找到LR返回地址的信息。

看完了BL指令我们继续看BLX指令,以下是BLX指令的编码说明。

代码中只是去了对应的指令的高八字节来比较,对应的数值定义如下:
#define BLX_INX 0x4700
以下的代码逻辑是查找任务栈的中的元素是否在代码段中,如果在则查找上一条指令是否为 BL/BLX 指令,如果是的话则认为此地址为任务栈中的一次函数调用路径。
size_t cm_backtrace_call_stack_any(uint32_t *buffer, size_t size, uint32_t sp, uint32_t stack_start_addr, uint32_t stack_size)
{
uint32_t pc;
size_t depth = 0;
/* copy called function address */
for (; sp < stack_start_addr + stack_size; sp += sizeof(size_t)) {
/* the *sp value may be LR, so need decrease a word to PC */
pc = *((uint32_t *) sp) - sizeof(size_t);
/* the Cortex-M using thumb instruction, so the pc must be an odd number */
if (pc % 2 == 0) {
continue;
}
/* fix the PC address in thumb mode */
pc = *((uint32_t *) sp) - 1;
if ((pc >= code_start_addr + sizeof(size_t)) && (pc <= code_start_addr + code_size) && (depth < CMB_CALL_STACK_MAX_DEPTH)
/* check the the instruction before PC address is 'BL' or 'BLX' */
&& disassembly_ins_is_bl_blx(pc - sizeof(size_t)) && (depth < size)) {
/* the second depth function may be already saved, so need ignore repeat */
buffer[depth++] = pc;
}
}
return depth;
}上述跳转的指令只是检查了BL/BLX 指令没有检查B /BX 指令这个也和容易理解,函数调用的时候一定是通过BL/BLX 来执行的,以下是跳转指令的说明。

BX 指令通常配置LR寄存器来使用

我要赚赏金
