arm32栈回溯原理学习以及示例代码

arm32栈回溯原理学习

  • 栈回溯原理
  • 缺点

简单介绍下传统栈回溯原理,方便理解。

栈回溯原理

arm32栈回溯原理学习以及示例代码_第1张图片
如上图所示,是一个传统的arm架构下函数栈数据分布,需要编译选项-fno-omit-fram-pointer -mapcs -mno-sched-prolog

函数进入时,首先会

mov ip sp
push {fp, ip, lr pc}

即将fp、sp、lr、pc依次入栈

示例函数调用关系:A->B->C

|    pc    |	---->b_fp			//stack high addr
|    lr    |
|    sp    |
|   a_fp   |
...{func stack}
|    pc    |	---->c_fp
|    lr    |
|    sp    |
|   b_fp   |
...{func stack}
|    pc    |	---->reg_fp
|    lr    |
|    sp    |
|   c_fp   |
...{func stack}						//stack low addr

  函数进入时,当前fp寄存器指向上层函数栈顶。所以,栈回溯时,获取当前fp寄存器,就可以把last func的fp\sp\lr\pc都获取到。
  再根据获取到的上一个函数的fp指针。循环嵌套即可获取所有调用关系。
  当fp指针指向deadbeef,认为回溯结束。

stauct stackframe {
	unsigned long fp;
	unsigned long sp;
	unsigned long lr;
	unsigned long pc;
};

static void __dump_backtrace(struct stackframe *stack)
{
	struct stackframe *prev;

	printk("Function enter at [<%08x>] from [<%08x>]\n", stack->pc, stack->lr);
	prev = (struct stackframe *)(stack->fp - 12);
	prev->pc = stack->lr;
	if (prev->fp = 0xdeadbeef)
		return;
	
	__dump_backtrace(prev);
}

void dump_stack(void)
{
	struct task_struct *tsk = current;
	struct stackframe stack;

	asm volatile("mov %0, pc" : "=r"(stack.pc));
	asm volatile("mov %0, fp" : "=r"(stack.fp));
	asm volatile("mov %0, sp" : "=r"(stack.sp));
	stack.lr = *((unsigned long *)(stack.fp - 4));

	__dump_backtrace(&stack);
}

  dump_stack接口用于函数中直接调用,所以需要用汇编指令把当前的pc、fp、sp取出来。以及通过fp获取调用lr(即调用dump_stack函数的返回地址)。
  随后进入__dump_backtrace函数,打印pc、lr地址。然后通过fp指针获取上一函数的pc存放位置。减去12字节地址后,就是struct stackframe数据结构地址。
  随后嵌套直至回溯结束。

ps: prev->pc = stack->lr;这行代码可能比较疑惑,因为pc和lr地址不一致,为了防止打印地址不一致,除了第一次进入pc,后续嵌套统一使用lr回溯

daba_aboat等场景中,struct stackframe内容由异常处理时保存的异常时寄存器列表中获取。

...
struct stackframe stack;

stack.fp = regs->fp;
stack.fp = regs->fp;
stack.fp = regs->fp;
stack.fp = regs->fp;

__dump_backtrace(&stack)
...

缺点

注意:此模式每个函数进入时都会保存上述四个寄存器,所以缺点是:

  • 执行效率较低,每次进入都有保存寄存器动作
  • 因为指令多,所以编译生成的代码尺寸也会较大。

所以通常情况下Linux采用unwind方式栈回溯。

你可能感兴趣的:(Linux,汇编,学习,c++,c语言)