gcc之-fomit-frame-point

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 <>:
   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 <>:
   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 <>:
   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 <>
   85d4:   e50b0020    str   r0, [fp, #-32]    ; 0xffffffe0
   85d8:   e51b0008    ldr   r0, [fp, #-8]
   85dc:   e51b1020    ldr   r1, [fp, #-32]    ; 0xffffffe0
   85e0:   ebffffd5    bl   853c <>
   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 <
>:
   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 <>
   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 <>:
   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 <>:
   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 <>:
   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 <>
   85b8:   e58d0014    str   r0, [sp, #20]
   85bc:   e59d002c    ldr   r0, [sp, #44]   ; 0x2c
   85c0:   e59d1014    ldr   r1, [sp, #20]
   85c4:   ebffffd9    bl   8530 <>
   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 <
>:
   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 <>
   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的结构如下:

gcc之-fomit-frame-point_第1张图片

 从上面的例子可以看到一个函数(如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。

你可能感兴趣的:(gcc之-fomit-frame-point)