SylixOS backtrace实现

SylixOS backtrace实现

backtrace用于输出当前调用栈信息,根据这些信息可以知道程序运行流程,有助于分析bug

实现原理

下面分别以ARM平台(ARMV7-A)和x86平台(32位)为例,讲述backtrace实现原理。

ARM平台实现

这里先简单介绍下ARM平台下,c语言函数的调用过程,应用程序代码如下:

#include <stdio.h>

void fun2 (int i)
{
    printf("%d\n", i);
}

void fun1 (int i)
{
    fun2(i);
}

int main (int argc, char **argv)
{

    fun1(0x1);

    return  (0);
}

这里调用流程为 main -> fun1 -> fun2,函数的返回过程是相反的,为了能够实现函数的返回,ARM处理器提供了LR寄存器,在函数跳转前,会将返回地址保存在LR寄存器,利用保存的值即可实现函数返回。同时ARM处理器会用R11(FP)作为栈帧寄存器,栈帧你可以理解为当前函数栈空间的起始地址(ARMV7-A 一般采用满递减类型栈空间,所以此时的起始地址就栈底),调用过程中各个函数栈空间如下:

     main<---------------+            fun1<--------------+
                         |                               |            fun2
+------------+           |       +------------+          |       +------------+
|     LR     +<-------+  +-------+     LR     +<-------+ +-------+     LR     |
+------------+        |          +------------+        |         +------------+
|  caller(FP)|        +----------+  (main)FP  |        +---------+  (fun1)FP  |
+------------+                   +------------+                  +------------+
|    argc    |                   |     i      |                  |     i      |
+------------+                   +------------+                  +------------+
|    argv    |                   |            |                  |            |
+------------+                   +------------+                  +------------+
|            |                   |            |                  |            |
|            |                   |            |                  |            |
|            |                   |            |                  |            |
|            |                   |            |                  |            |
|            |                   |            |                  |            |
+------------+                   +------------+                  +------------+

首先main函数会保存自己的LR(R14)FP(R11)寄存器、函数参数压入堆栈,然后利用R0寄存器作为参数,调用fun1函数,函数fun1也需将LR(R14)FP(R11)寄存器保存在栈中,同时将R0传入的参数i保存在栈中,然后调用fun2fun2入口操作与fun1基本相同,只不过后续调用了printf而已。这里需要注意的是当前函数栈帧中保存的LR是当前函数执行完的返回地址,而FP是当前函数调用者的栈帧,比如在函数fun1栈中的LR,是函数fun1返回到main函数的地址,而FP指向main函数的栈帧起始地址。之所以要保存LR,是因为函数调用时,会更新LR的值,保存FP是因为要将FP寄存器指向当前函数的栈帧起始地址来访问栈上一些变量。下面结合反汇编代码了解这个过程.

Disassembly of section .text:

000002d0 <fun2>:
 2d0:	e92d4800 	push	{fp, lr}    ;保存LR及fun1函数的FP
 2d4:	e28db004 	add	fp, sp, #4      ;设置FP指向自身函数栈帧起始地址
 2d8:	e24dd008 	sub	sp, sp, #8      ;SP便宜保存局部变量,保证8字节对齐
 2dc:	e50b0008 	str	r0, [fp, #-8]   ;将参数R0保存,即局部变量i
 2e0:	e59f3018 	ldr	r3, [pc, #24]	; 300 <fun2+0x30>
 2e4:	e08f3003 	add	r3, pc, r3
 2e8:	e1a00003 	mov	r0, r3
 2ec:	e51b1008 	ldr	r1, [fp, #-8]
 2f0:	ebffffed 	bl	2ac <fun2-0x24> ;调用printf
 2f4:	e24bd004 	sub	sp, fp, #4
 2f8:	e8bd4800 	pop	{fp, lr}
 2fc:	e12fff1e 	bx	lr
 300:	0000006c 	andeq	r0, r0, ip, rrx

00000304 <fun1>:
 304:	e92d4800 	push	{fp, lr}    ;保存LR及main函数的FP
 308:	e28db004 	add	fp, sp, #4      ;设置FP指向自身函数栈帧起始地址
 30c:	e24dd008 	sub	sp, sp, #8      ;SP便宜保存局部变量,保证8字节对齐
 310:	e50b0008 	str	r0, [fp, #-8]   ;将参数R0保存,即局部变量i
 314:	e51b0008 	ldr	r0, [fp, #-8]   ;取值,给R0,准备调用fun2,这里感觉有点多余
 318:	ebffffe6 	bl	2b8 <fun2-0x18> ;跳转plt,再跳fun2
 31c:	e24bd004 	sub	sp, fp, #4
 320:	e8bd4800 	pop	{fp, lr}
 324:	e12fff1e 	bx	lr

00000328 <main>:
 328:	e92d4800 	push	{fp, lr}
 32c:	e28db004 	add	fp, sp, #4
 330:	e24dd008 	sub	sp, sp, #8
 334:	e50b0008 	str	r0, [fp, #-8]    ;保存argc
 338:	e50b100c 	str	r1, [fp, #-12]   ;保存argv
 33c:	e3a00001 	mov	r0, #1           ;R0 传参
 340:	ebffffdf 	bl	2c4 <fun2-0xc>   ;由于编译时加入了PIC编译选项,会跳转到plt表中,再跳转到fun1函数
 344:	e3a03000 	mov	r3, #0
 348:	e1a00003 	mov	r0, r3           ;函数返回值
 34c:	e24bd004 	sub	sp, fp, #4
 350:	e8bd4800 	pop	{fp, lr}
 354:	e12fff1e 	bx	lr

以上代码都具有详细注释,如有疑问可以在下方讨论。

通过调用流程图就可以看出,利用当前函数栈帧中的LR就能获取当前函数调用者,而通过栈帧中的FP就能知道调用者的FP,有了调用者的栈帧,就能知道更上一级的调用者。在实际使用时,通过GCC编译器内建函数__builtin_frame_address(0)即可获取当前函数栈帧,此时栈帧指向返回地址LR,对地址-4操作即可获取当前函数调用者栈帧。

x86平台实现

待更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值