关于arm的backtrace

使用场景:嵌入式跑死问题追溯
提供方法:打印函数的调用关系
backtrace原理

1. main函数运行,main函数调用func1, func1调用func2...
2. 当函数调用发生时,会将arm的寄存器PC/LR/SP/FP依次压栈,形成栈帧,本次的主角是FP寄存器
3. main函数调用func1, 会将main函数的栈帧起始地址放入func1的FP寄存器,即func1的FP寄存器指向栈中用于存放main函数栈的起始位置,即main函数PC指针的前一个位置。如下图所示,注意图中的箭头

关于arm的backtrace_第1张图片


接下来我们看下如下代码:

#include 
#include 
#include 

//-------------------- backtrace --------------------------

extern void backtrace(int fp);
extern int div(int a, int b);
extern int main(int argc, char **argv);

#define TOSTRING(x) #x
#define READ_REGISTER(var) __asm volatile("mov %[" TOSTRING(var) "], " TOSTRING(var) "\n\t" : [var] "=r" (var))
typedef uint32_t volatile *volatile vmemptr;
#define VMEM(x) (*(vmemptr)(x))

void backtrace(int fp)
{
    printf("\n-------- bt ------\n");

    if (fp == NULL) {
        printf("fp is NULL\n");
        return;
    }

    printf("bt fp: %p\n", fp);
    printf("bt fp - 4: 0x%08x\n", (void *) ((*(int *)(fp - 4))));

    backtrace((void *) ((*(int *)(fp - 4))));
}

int div(int a, int b)
{
    int c = ++a;
    int d = ++b;

    int e = a * b / c;

    printf("----- backtrace ----- %s\n", __func__);
    int fp = 0;
    // 2 methods of getting fp value
    READ_REGISTER(fp);
    printf("fp: %p\n", fp);
    printf("frame div: %p\n", __builtin_frame_address (0));
    backtrace(fp);

    printf("\n");
    return e;
}

int multi(int a, int b)
{
    int c = ++a;
    int d = ++b;

    int e = a * b * div(c, d);

    printf("frame multi: %p\n", __builtin_frame_address (0));
    return e;
}

int minus(int a, int b)
{
    int c = ++a;
    int d = ++b;

    int e = a - b - multi(c, d);

    printf("frame minus: %p\n", __builtin_frame_address (0));
    return e;
}
    
int add(int a, int b)
{
    int c = ++a;
    int d = ++b;

    int e = a + b + minus(c, d);

    printf("frame add: %p\n", __builtin_frame_address (0));
    return e;
}

int main(int argc, char **argv)
{
    int a = 5;
    int b = 4;
    int c = add(a, b);

    printf("frame main: %p\n", __builtin_frame_address (0));

    // compare the address of variables with frame pointer
    printf("func main:\t a: %p, b: %p, c: %p\n", &a, &b, &c);
    printf("func main:\t argc: %p, argv: %p\n", &argc, argv);

    printf("\n");
    return 0;
}

运行方法

arm-linux-gnueabi-gcc main.c -o main -w -g
qemu-arm main

运行环境:Ubuntu18.04
运行结果
关于arm的backtrace_第2张图片
注意看结果中各个子函数的frame pointer,以及backtrace追溯时FP内容的变化

参考

  1. ARM FP寄存器及frame pointer介绍
  2. How to debug a HardFault on an ARM Cortex-M MCU
  3. Cortex-M3 Devices Generic User Guide
  4. ARMv8-M Fault handling and detection Version 2.0
  5. Fault exceptions and fault handling

未完待续

  1. arm hardfault寄存器及其callback处理
  2. linux中异常信号的捕获

你可能感兴趣的:(c嵌入式编程arm)