应用场景 :嵌入式跑死问题追溯
提供办法:打印函数的调用关系
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 指针的前一个地位。如下图所示,留神图中的箭头
接下来咱们看下如下代码:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
//-------------------- 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
运行后果:
留神看后果中各个子函数的 frame pointer,以及 backtrace 追溯时 FP 内容的变动
参考:
- ARM FP 寄存器及 frame pointer 介绍
未完待续:
- arm hardfault 寄存器及其 callback 解决
- linux 中异样信号的捕捉