addr2line用於得到程序指令地址所對應的函數,以及函數所在的源文件名和行號。
在不少嵌入式開發環境中,編譯器的名稱往往不是gcc,而是想arm-rtems-gcc這樣的,對於這種命名形式的編譯器,讀者通常可以找到arm-rtems-addr2line ,arm-rtems-objdump等相應名稱的工具,這是GNU工具集的一種命名慣例。
本文並不是binutils工具集的完整參考手冊,當需要得到更為詳細的幫助信息時,可以參照對應工具的man和info信息。另一個更為簡單的方法時運行相應的工具並指定--help參數,可以獲得該工具的簡單幫助信息。
用法:addr2line [選項] [地址]
將地址轉換成文件名/行號對。
如果沒有在命令行中給出地址,就從標准輸入中讀取它們
選項是:
@<file> 讀取選項從 <file>
-a --addresses 顯示地址
-b --target=<bfdname> 設置二進位文件格式
-e --exe=<executable><name> 設置輸入文件名稱(默認為 a.out)
-i --inlines 解開內聯函數
-j --section=<name> 讀取相對於段的偏移而非地址
-p --pretty-print 讓輸出對人類更可讀
-s --basenames 去除目錄名
-f --functions 顯示函數名
-C --demangle[=style] 解碼函數名
-h --help 顯示本幫助
實戰操練:
建立main.c文件如下:
輸入:
gcc -g main.c -o app -Wall
一定要加-g選項,不然沒有調試信息。
編譯沒有任何警告(作為gcc來說不應該啊^_^)
運行
顯示段錯誤。使用dmesg(該命令可以參考https://linux.cn/article-3587-1.html)追蹤:
注意最后一行 app 可執行文件的描述:segfault(段錯誤)ip為4004e6這就是程序執行出錯的位置。這時,addr2line登場:
顯示在在我的目錄下 main.c的第七行出現段錯誤,現在回過頭去看第一幅圖片,你應該會發現addr2line准確地得到了我們想要調試的東西。
一般常用的兩個option為-f和-e。
下面,我們再來做一件有意思的事情:
main.c如下:
運行如下:
這里的地址是通過程序打印而獲得,在現實工作中往往是程序崩潰是通過某種方式獲得地址,比如上面的dmesg,或者在嵌入式系統的一些IDE上也有類似功能。通過nm -n指令可以看到函數對應的開始地址:只取foo和main函數截圖如下:
可以看出foo函數是有大小的,foo函數的大小就是main的地址減去foo的地址。如果傳遞給addr2line的地址是400526-400541之間的任意地址會輸出什么呢?
測試結果表明,addr2line可以通過函數的任一地址找到所屬函數的相關信息。
根據地址找到了函數名,現在我們寫一個c++程序main.cpp如下 圖:
運行如下:
首先和C語言一樣,addr2line找到了foo函數入口在main.cpp的第五行,(也可以看出函數調用是從大括號處開始),但是函數名卻是_Z3f00v,這就有點奇怪了,怎么不是foo呢?這是因為c++的重載使得這一現象產生。從C語言的角度來看c++的重載函數名稱必須不同,為了做到這一點,c++編譯器的處理方法就是對於每一個函數,將根據輸入參數采取一定的編碼方式,形成不同的c函數名,這個過程就是名字分裂過程。如上的 _Z3f00v就是c++程序中foo()函數的名字分裂后的形式。畢竟c++是從C語言上發展而來的。
我們可以通過--demangle選項獲得函數名:
--demangle=gnu-v3用於還原foo()函數。