加载中…
个人资料
linux探险家
linux探险家
  • 博客等级:
  • 博客积分:0
  • 博客访问:40,861
  • 关注人气:7
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Linux下Call Stack追溯的实现机制

(2014-01-27 22:54:22)
标签:

gcc

linux

callstack

stackfram

栈帧

分类: 调试技巧
Kernel里的dump_stack(), oops打印出来的backstrace调用链表是怎样实现的呢?

大家都知道当发生函数调用的时候,函数的参数传递,返回值传递都要遵循一定的规则,在ARM体系架构下,这个规则叫做Procedure Call Standard for the ARM Architecture。在这个规则里规定了函数调用的时候,返回地址在LR里面,第一到第四个参数在r0~r3里面,第五到第八个参数在Stack里面,返回值在r0里面。这是基本规则,C编译器产生汇编指令是必须遵循这些规则,这也是ABI(Application Binary Interface)的一部分。另外,为了实现连续的函数调用,如fun_a()->func_b()->func_c(),每个函数的入口处必须先把LR压到stack里,否则func_b()调了func_c()之后,LR变成了func_c()的返回地址,而func_b()返回地址就丢失了。

有了以上这些原则,可以追溯函数的调用历史。即只要找到堆栈里的LR就知道是那个从那个函数调用过来的。要知道stack里LR的位置就必须要知道当前函数stack开始的地方,否则stack里那个位置存放了LR。通用的定位每个函数stack开始的地方的方法是在编译的时候让编译器在目标代码中嵌入stack frame(栈帧)。此外,gcc还能够在elf文件中生成unwind table,unwind table也能实现追踪call stack。下面主要讲述stack frame。


什么是Stack frame:

stack frame(栈帧)是用来追踪代码的调用过程和调用时的参数,通过读取stack frame信息可以知道到执行到当前位置的函数调用链表(call stack)。

Stack frame的本质是每次函数调用都在stack里记录一个frame,每一次函数调用叫做一个frame。ARM里的fp寄存器就是用来指证当前所在函数的frame的,fp总是指向当前函数堆栈的frame。

Stack frame的产生方法:

stack frame是由编译器产生的,也就是gcc在生成可执行代码时,在每个函数入口的地方“放置”一个stack frame。在调用gcc编译时可以指定参数来要求gcc产生或不产生stack frame,这个参数是:

-fomit-frame-point:让gcc不产生stack frame
-fno-omit-frame-pointer:让gcc产生stack frame

如果不指定这两个参数,是否产生stack frame取决于gcc default是omit-frame-point,还是 no-omit-frame-pointer。

Stack frame是什么样的

通过一个简单的C程序来看一下在binary里,stack frame究竟是怎样的

test.c:
 static int func_b(int a, int b, int c, int d, int e, int f)
{
        return a + b + c + d + e + f;
}

static int func_c(int a, int b)
{
        return a - b;
}

static int func_a(int x, int y)
{
        int a = 3;
        int b = 4;
        int c = 5;
        int d = 6;
        int e = 7;
        int f = 8;
        int ret;

        ret = func_b(a, b, c, d, e, f);

        ret = func_c(a, ret);

        return ret;
}

int main(int argc, char * argv[])
{
        int a = 1;
        int b = 2;
        int ret;

        ret = func_a(a, b);

        return ret;
}


编译有stack frame的binary:
    arm-none-linux-gnueabi-gcc -fno-omit-frame-pointer test.c -o test.no-omit-frame-pointer
编译无stack frame的binary:
    arm-none-linux-gnueabi-gcc -fomit-frame-pointer test.c -o test.omit-frame-pointer

分别objdump以上编译所得的两个binary:
    arm-none-linux-gnueabi-objdump -S test.no-omit-frame-pointer > test.no-omit-frame-pointer.objdump
    arm-none-linux-gnueabi-objdump -S test.omit-frame-pointer > test.omit-frame-pointer.objdump
   
打开两个dump出来的assembly (只关注我们的main(), func_a(), func_b()三个函数,忽略libc的部分):

test.no-omit-frame-pointer.objdump:

000084e4 <<func_b>>:
    84e4:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
    84e8:    e28db000     add    fp, sp, #0
    84ec:    e24dd014     sub    sp, sp, #20
    84f0:    e50b0008     str    r0, [fp, #-8]
    84f4:    e50b100c     str    r1, [fp, #-12]
    84f8:    e50b2010     str    r2, [fp, #-16]
    84fc:    e50b3014     str    r3, [fp, #-20]    ; 0xffffffec
    8500:    e51b2008     ldr    r2, [fp, #-8]
    8504:    e51b300c     ldr    r3, [fp, #-12]
    8508:    e0822003     add    r2, r2, r3
    850c:    e51b3010     ldr    r3, [fp, #-16]
    8510:    e0822003     add    r2, r2, r3
    8514:    e51b3014     ldr    r3, [fp, #-20]    ; 0xffffffec
    8518:    e0822003     add    r2, r2, r3
    851c:    e59b3004     ldr    r3, [fp, #4]
    8520:    e0822003     add    r2, r2, r3
    8524:    e59b3008     ldr    r3, [fp, #8]
    8528:    e0823003     add    r3, r2, r3
    852c:    e1a00003     mov    r0, r3
    8530:    e28bd000     add    sp, fp, #0
    8534:    e8bd0800     ldmfd    sp!, {fp}
    8538:    e12fff1e     bx    lr

0000853c <<func_c>>:
    853c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
    8540:    e28db000     add    fp, sp, #0
    8544:    e24dd00c     sub    sp, sp, #12
    8548:    e50b0008     str    r0, [fp, #-8]
    854c:    e50b100c     str    r1, [fp, #-12]
    8550:    e51b2008     ldr    r2, [fp, #-8]
    8554:    e51b300c     ldr    r3, [fp, #-12]
    8558:    e0633002     rsb    r3, r3, r2
    855c:    e1a00003     mov    r0, r3
    8560:    e28bd000     add    sp, fp, #0
    8564:    e8bd0800     ldmfd    sp!, {fp}
    8568:    e12fff1e     bx    lr

0000856c <<func_a>>:
    856c:    e92d4800     push    {fp, lr}
    8570:    e28db004     add    fp, sp, #4
    8574:    e24dd030     sub    sp, sp, #48    ; 0x30
    8578:    e50b0028     str    r0, [fp, #-40]    ; 0xffffffd8
    857c:    e50b102c     str    r1, [fp, #-44]    ; 0xffffffd4
    8580:    e3a03003     mov    r3, #3
    8584:    e50b3008     str    r3, [fp, #-8]
    8588:    e3a03004     mov    r3, #4
    858c:    e50b300c     str    r3, [fp, #-12]
    8590:    e3a03005     mov    r3, #5
    8594:    e50b3010     str    r3, [fp, #-16]
    8598:    e3a03006     mov    r3, #6
    859c:    e50b3014     str    r3, [fp, #-20]    ; 0xffffffec
    85a0:    e3a03007     mov    r3, #7
    85a4:    e50b3018     str    r3, [fp, #-24]    ; 0xffffffe8
    85a8:    e3a03008     mov    r3, #8
    85ac:    e50b301c     str    r3, [fp, #-28]    ; 0xffffffe4
    85b0:    e51b3018     ldr    r3, [fp, #-24]    ; 0xffffffe8
    85b4:    e58d3000     str    r3, [sp]
    85b8:    e51b301c     ldr    r3, [fp, #-28]    ; 0xffffffe4
    85bc:    e58d3004     str    r3, [sp, #4]
    85c0:    e51b0008     ldr    r0, [fp, #-8]
    85c4:    e51b100c     ldr    r1, [fp, #-12]
    85c8:    e51b2010     ldr    r2, [fp, #-16]
    85cc:    e51b3014     ldr    r3, [fp, #-20]    ; 0xffffffec
    85d0:    ebffffc3     bl    84e4 <<func_b>>
    85d4:    e50b0020     str    r0, [fp, #-32]    ; 0xffffffe0
    85d8:    e51b0008     ldr    r0, [fp, #-8]
    85dc:    e51b1020     ldr    r1, [fp, #-32]    ; 0xffffffe0
    85e0:    ebffffd5     bl    853c <<func_c>>
    85e4:    e50b0020     str    r0, [fp, #-32]    ; 0xffffffe0
    85e8:    e51b3020     ldr    r3, [fp, #-32]    ; 0xffffffe0
    85ec:    e1a00003     mov    r0, r3
    85f0:    e24bd004     sub    sp, fp, #4
    85f4:    e8bd8800     pop    {fp, pc}

000085f8 <<main>>:
    85f8:    e92d4800     push    {fp, lr}
    85fc:    e28db004     add    fp, sp, #4
    8600:    e24dd018     sub    sp, sp, #24
    8604:    e50b0018     str    r0, [fp, #-24]    ; 0xffffffe8
    8608:    e50b101c     str    r1, [fp, #-28]    ; 0xffffffe4
    860c:    e3a03001     mov    r3, #1
    8610:    e50b3008     str    r3, [fp, #-8]
    8614:    e3a03002     mov    r3, #2
    8618:    e50b300c     str    r3, [fp, #-12]
    861c:    e51b0008     ldr    r0, [fp, #-8]
    8620:    e51b100c     ldr    r1, [fp, #-12]
    8624:    ebffffd0     bl    856c <<func_a>>
    8628:    e50b0010     str    r0, [fp, #-16]
    862c:    e51b3010     ldr    r3, [fp, #-16]
    8630:    e1a00003     mov    r0, r3
    8634:    e24bd004     sub    sp, fp, #4
    8638:    e8bd8800     pop    {fp, pc}
test.omit-frame-pointer.objdump:

000084e4 <<func_b>>:
    84e4:    e24dd010     sub    sp, sp, #16
    84e8:    e58d000c     str    r0, [sp, #12]
    84ec:    e58d1008     str    r1, [sp, #8]
    84f0:    e58d2004     str    r2, [sp, #4]
    84f4:    e58d3000     str    r3, [sp]
    84f8:    e59d200c     ldr    r2, [sp, #12]
    84fc:    e59d3008     ldr    r3, [sp, #8]
    8500:    e0822003     add    r2, r2, r3
    8504:    e59d3004     ldr    r3, [sp, #4]
    8508:    e0822003     add    r2, r2, r3
    850c:    e59d3000     ldr    r3, [sp]
    8510:    e0822003     add    r2, r2, r3
    8514:    e59d3010     ldr    r3, [sp, #16]
    8518:    e0822003     add    r2, r2, r3
    851c:    e59d3014     ldr    r3, [sp, #20]
    8520:    e0823003     add    r3, r2, r3
    8524:    e1a00003     mov    r0, r3
    8528:    e28dd010     add    sp, sp, #16
    852c:    e12fff1e     bx    lr

00008530 <<func_c>>:
    8530:    e24dd008     sub    sp, sp, #8
    8534:    e58d0004     str    r0, [sp, #4]
    8538:    e58d1000     str    r1, [sp]
    853c:    e59d2004     ldr    r2, [sp, #4]
    8540:    e59d3000     ldr    r3, [sp]
    8544:    e0633002     rsb    r3, r3, r2
    8548:    e1a00003     mov    r0, r3
    854c:    e28dd008     add    sp, sp, #8
    8550:    e12fff1e     bx    lr

00008554 <<func_a>>:
    8554:    e52de004     push    {lr}        ; (str lr, [sp, #-4]!)
    8558:    e24dd034     sub    sp, sp, #52    ; 0x34
    855c:    e58d000c     str    r0, [sp, #12]
    8560:    e58d1008     str    r1, [sp, #8]
    8564:    e3a03003     mov    r3, #3
    8568:    e58d302c     str    r3, [sp, #44]    ; 0x2c
    856c:    e3a03004     mov    r3, #4
    8570:    e58d3028     str    r3, [sp, #40]    ; 0x28
    8574:    e3a03005     mov    r3, #5
    8578:    e58d3024     str    r3, [sp, #36]    ; 0x24
    857c:    e3a03006     mov    r3, #6
    8580:    e58d3020     str    r3, [sp, #32]
    8584:    e3a03007     mov    r3, #7
    8588:    e58d301c     str    r3, [sp, #28]
    858c:    e3a03008     mov    r3, #8
    8590:    e58d3018     str    r3, [sp, #24]
    8594:    e59d301c     ldr    r3, [sp, #28]
    8598:    e58d3000     str    r3, [sp]
    859c:    e59d3018     ldr    r3, [sp, #24]
    85a0:    e58d3004     str    r3, [sp, #4]
    85a4:    e59d002c     ldr    r0, [sp, #44]    ; 0x2c
    85a8:    e59d1028     ldr    r1, [sp, #40]    ; 0x28
    85ac:    e59d2024     ldr    r2, [sp, #36]    ; 0x24
    85b0:    e59d3020     ldr    r3, [sp, #32]
    85b4:    ebffffca     bl    84e4 <<func_b>>
    85b8:    e58d0014     str    r0, [sp, #20]
    85bc:    e59d002c     ldr    r0, [sp, #44]    ; 0x2c
    85c0:    e59d1014     ldr    r1, [sp, #20]
    85c4:    ebffffd9     bl    8530 <<func_c>>
    85c8:    e58d0014     str    r0, [sp, #20]
    85cc:    e59d3014     ldr    r3, [sp, #20]
    85d0:    e1a00003     mov    r0, r3
    85d4:    e28dd034     add    sp, sp, #52    ; 0x34
    85d8:    e8bd8000     ldmfd    sp!, {pc}

000085dc <<main>>:
    85dc:    e52de004     push    {lr}        ; (str lr, [sp, #-4]!)
    85e0:    e24dd01c     sub    sp, sp, #28
    85e4:    e58d0004     str    r0, [sp, #4]
    85e8:    e58d1000     str    r1, [sp]
    85ec:    e3a03001     mov    r3, #1
    85f0:    e58d3014     str    r3, [sp, #20]
    85f4:    e3a03002     mov    r3, #2
    85f8:    e58d3010     str    r3, [sp, #16]
    85fc:    e59d0014     ldr    r0, [sp, #20]
    8600:    e59d1010     ldr    r1, [sp, #16]
    8604:    ebffffd2     bl    8554 <<func_a>>
    8608:    e58d000c     str    r0, [sp, #12]
    860c:    e59d300c     ldr    r3, [sp, #12]
    8610:    e1a00003     mov    r0, r3
    8614:    e28dd01c     add    sp, sp, #28
    8618:    e8bd8000     ldmfd    sp!, {pc}

    可以看到有stack frame的binary (test.no-omit-frame-pointer.objdump) 中每个main(), func_a(), func_b()入口的地 方都有push fp, lr和add fp, sp, #4;而无stack frame的binary (test.omit-frame-pointer.objdump) 中函数入口的地方并没有这两句指令。push fp的作用是把调用函数(caller)的fp保存到被调用函数(callee)的stack开始处,随后add fp, sp, #4将fp指向被调用函数(callee) 的stack开始的地方。fp总是指向当前函数的stack开始的地方,通过当前函数stack里保存的caller的fp可以追溯到caller的堆栈。通过fp的逐级连接就可以找到调用链上每个函数的stack,从而每个函数的LR(返回地址)都可以从stack里获得,只要在symbol table里搜索一下LR的值,就可以知道caller是哪个函数了。有了stack frame不但可以通过stack里的LR得到caller的地址,还可以知道函数调用时的参数,回想一下前面说的‘第一到第四个参数在r0~r3里面,第五到第八个参数在Stack里面’。具体分析一下上面的汇编代码,结合 Procedure Call Standard for the ARM Architecture规定的调用原则,绘制出stack frame的结构如下:

Linux下Call <wbr>Stack追溯的实现机制

 从上面的例子可以看到一个函数(如func_a())的stack frame里,开始的地方放的是fp, lr(如果是最后一级的被调用函数,如func_b,func_c则lr无需保存到stack),然后是函数的内部变量,之后是该函数的参数1~参数4,最后是其调用的函数(本例中是func_b())的参数5~参数8。在本例中如果在func_b()中要追溯call stack,从lr可以知道哪个函数调用了它,fp寄存器保存了func_b的stack frame的起始地址,该地址上存放了上一级stack frame的起始地址(caller的stack frame),该地址向stack缩小方向存放了caller调用func_b()的时候传递的参数5~8(如果有参数5~8的话),该地址向stack伸长方向到存放调用下一个函数的参数5~8之前的位置存放了caller调用func_b()的时候传递的参数1~4。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有