Linux下手动获取当前调用栈
被问到如何手动获取当前的调用栈,之前碰到过一时没记起来,现在回头整理一下。
其原理是:使用backtrace()从栈中获取当前调用各层函数调用的返回地址,backtrace_symbols()将对应地址翻译成对应的符号信息,这两个函数在execinfo.h中声明。详细用法见后面的example。这里强调几处需要注意的地方,在man里头也有说明
1,inline函数无返回地址,因此在结果中不显示
2,需要给linker指定对应的参数,才能保证有对应的符号名称信息,GNU工具链是指定-rdynamic
3,尾调优化会使当前栈帧被新的栈帧覆盖,因此查询的到的信息,会与代码里调用关系不能一一对应
4,static函数由于其符号信息未输出,因此不能获取到具体的名称
example代码,编译指令gcc backtrace.c -o backtrace -g -rdynamic
1 #include <execinfo.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 void bt(void) 6 { 7 #define MAX_DEPTH (20) 8 void *buffer[MAX_DEPTH]; 9 int nptrs = backtrace(buffer, MAX_DEPTH); 10 char **stack = backtrace_symbols(buffer, nptrs); 11 int i; 12 13 if (stack) 14 { 15 for (i = 0; i < nptrs; ++i) 16 { 17 printf("%s\n", stack[i]); 18 } 19 20 free(stack); 21 } 22 23 return; 24 } 25 26 static void func2(void) 27 { 28 bt(); 29 } 30 31 inline void func1(void) 32 { 33 func2(); 34 } 35 36 void func(void) 37 { 38 func1(); 39 } 40 41 int main(int argc, char *argv[]) 42 { 43 func(); 44 45 return 0; 46 }
Linux arch 2.6.30-ARCH #1 SMP PREEMPT Fri Jul 31 18:10:38 UTC 2009 i686 Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz GenuineIntel GNU/Linux
gcc4.4.1 环境之行结果如下
[root@arch code]# make backtrace
gcc backtrace.c -o backtrace -g -rdynamic
[root@arch code]# ./backtrace
./backtrace(bt+0x19) [0x80486ed]
./backtrace [0x804874b]
./backtrace(func1+0xb) [0x8048758]
./backtrace(func+0xb) [0x8048765]
./backtrace(main+0xb) [0x8048772]
/lib/libc.so.6(__libc_start_main+0xe6) [0xb7f8da36]
./backtrace [0x8048641]
[root@arch code]# addr2line -e ./backtrace 0x8048765
/root/code/backtrace.c:40
[root@arch code]# addr2line -e ./backtrace 0x8048758
/root/code/backtrace.c:35
[root@arch code]# addr2line -e ./backtrace 0x804874b
/root/code/backtrace.c:30
[root@arch code]# addr2line -e ./backtrace 0x80486ed
/root/code/backtrace.c:10
[root@arch code]#
从实际验证结果可以看出static函数的确没有解析出对应的符号名,但是inline函数仍然有自己的调用栈,这应该是gcc没有实际将其优化展开,仍然将其当作普通函数所致。
并且根据addre2line的结果,我们可以看出backtrace()调用获取到的其实是各个函数调用的返回地址,可以自己根据行号进行一一比对。这里就不多重复了。
不过在Raspbian环境(Linux raspberrypi 3.10.25+ #622 PREEMPT Fri Jan 3 18:41:00 GMT 2014 armv6l GNU/Linux gcc 4.6.3)里,编译执行均没有问题,但是无任何输出,gdb跟踪的结果是backtrace()调用返回0,很奇怪。stackoverflow上有人说是根据GCC ARM Options documentation需要加上-mapcs-frame参数,以让gcc在ARM平台上产生栈帧,可是编译时加上该参数仍然无效。strace跟踪其执行过程发现其执行过程没有任何backtrace字样,如下
pi@raspberrypi ~/code $ strace ./backtrace execve("./backtrace", ["./backtrace"], [/* 16 vars */]) = 0 brk(0) = 0x1082000 uname({sys="Linux", node="raspberrypi", ...}) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f0c000 access("/etc/ld.so.preload", R_OK) = 0 open("/etc/ld.so.preload", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=44, ...}) = 0 mmap2(NULL, 44, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0xb6f0b000 close(3) = 0 open("/usr/lib/arm-linux-gnueabihf/libcofi_rpi.so", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\270\4\0\0004\0\0\0"..., 512) = 512 lseek(3, 7276, SEEK_SET) = 7276 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1080) = 1080 lseek(3, 7001, SEEK_SET) = 7001 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\2\22\4\24\1\25"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0755, st_size=10170, ...}) = 0 mmap2(NULL, 39740, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6ee0000 mprotect(0xb6ee2000, 28672, PROT_NONE) = 0 mmap2(0xb6ee9000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1) = 0xb6ee9000 close(3) = 0 munmap(0xb6f0b000, 44) = 0 open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=43581, ...}) = 0 mmap2(NULL, 43581, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6ed5000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\214y\1\0004\0\0\0"..., 512) = 512 lseek(3, 1194784, SEEK_SET) = 1194784 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1360) = 1360 lseek(3, 1194348, SEEK_SET) = 1194348 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\2\22\4\24\1\25"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0755, st_size=1196144, ...}) = 0 mmap2(NULL, 1238312, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6da6000 mprotect(0xb6ec8000, 28672, PROT_NONE) = 0 mmap2(0xb6ecf000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x121) = 0xb6ecf000 mmap2(0xb6ed2000, 9512, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb6ed2000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb6f0b000 set_tls(0xb6f0b4c0, 0xb6f0bb98, 0xb6f10048, 0xb6f0b4c0, 0xb6f10048) = 0 mprotect(0xb6ecf000, 8192, PROT_READ) = 0 mprotect(0xb6f0f000, 4096, PROT_READ) = 0 munmap(0xb6ed5000, 43581) = 0 open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=43581, ...}) = 0 mmap2(NULL, 43581, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb6ed5000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/arm-linux-gnueabihf/libgcc_s.so.1", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0`\364\0\0004\0\0\0"..., 512) = 512 lseek(3, 130212, SEEK_SET) = 130212 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 1160) = 1160 lseek(3, 129880, SEEK_SET) = 129880 read(3, "A2\0\0\0aeabi\0\1(\0\0\0\0056\0\6\6\10\1\t\1\n\2\22\4\24\1\25"..., 51) = 51 brk(0) = 0x1082000 brk(0x10a3000) = 0x10a3000 fstat64(3, {st_mode=S_IFREG|0644, st_size=131372, ...}) = 0 mmap2(NULL, 162704, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb6d7e000 mprotect(0xb6d9e000, 28672, PROT_NONE) = 0 mmap2(0xb6da5000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1f) = 0xb6da5000 close(3) = 0 munmap(0xb6ed5000, 43581) = 0 exit_group(0) = ? pi@raspberrypi ~/code $
对其原因待有待深究。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。