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 $
View Code

對其原因待有待深究。

===== update 2019/5/5 ====

aarch64 linux 版本的 gcc有 -funwind-tables 編譯參數,可以實現 backtrace()/backtrace_symbols 正常功能,需要新的 gcc 版本(>= gcc-4.5) 提供支持。原理是記錄每個函數的 入棧指令(一般比APCS的入棧要少的多)到特殊的段.ARM.unwind_idx .ARM.unwind_tab。

詳情見 http://www.alivepea.me/prog/how-backtrace-work/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM