Linux下Call Stack追溯的实现机制

标签:
gcclinuxcallstackstackfram栈帧 |
分类: 调试技巧 |
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。
大家都知道当发生函数调用的时候,函数的参数传递,返回值传递都要遵循一定的规则,在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,这个参数是:
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:
编译有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:
-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_c(int a, int b) { } static int func_a(int x, int y) { } int main(int argc, char * argv[]) { } |
编译有stack frame的binary:
编译无stack frame的binary:
分别objdump以上编译所得的两个binary:
打开两个dump出来的assembly (只关注我们的main(), func_a(), func_b()三个函数,忽略libc的部分):
test.no-omit-frame-pointer.objdump:
000084e4 <<func_b>>: |